140 lines
3.5 KiB
Go
140 lines
3.5 KiB
Go
package services
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"git.mchus.pro/mchus/quoteforge/internal/models"
|
|
"git.mchus.pro/mchus/quoteforge/internal/repository"
|
|
"git.mchus.pro/mchus/quoteforge/internal/services/pricing"
|
|
)
|
|
|
|
var (
|
|
ErrEmptyQuote = errors.New("quote cannot be empty")
|
|
ErrComponentNotFound = errors.New("component not found")
|
|
ErrNoPriceAvailable = errors.New("no price available for component")
|
|
)
|
|
|
|
type QuoteService struct {
|
|
componentRepo *repository.ComponentRepository
|
|
statsRepo *repository.StatsRepository
|
|
pricingService *pricing.Service
|
|
}
|
|
|
|
func NewQuoteService(
|
|
componentRepo *repository.ComponentRepository,
|
|
statsRepo *repository.StatsRepository,
|
|
pricingService *pricing.Service,
|
|
) *QuoteService {
|
|
return &QuoteService{
|
|
componentRepo: componentRepo,
|
|
statsRepo: statsRepo,
|
|
pricingService: pricingService,
|
|
}
|
|
}
|
|
|
|
type QuoteItem struct {
|
|
LotName string `json:"lot_name"`
|
|
Quantity int `json:"quantity"`
|
|
UnitPrice float64 `json:"unit_price"`
|
|
TotalPrice float64 `json:"total_price"`
|
|
Description string `json:"description"`
|
|
Category string `json:"category"`
|
|
HasPrice bool `json:"has_price"`
|
|
}
|
|
|
|
type QuoteValidationResult struct {
|
|
Valid bool `json:"valid"`
|
|
Items []QuoteItem `json:"items"`
|
|
Errors []string `json:"errors"`
|
|
Warnings []string `json:"warnings"`
|
|
Total float64 `json:"total"`
|
|
}
|
|
|
|
type QuoteRequest struct {
|
|
Items []struct {
|
|
LotName string `json:"lot_name"`
|
|
Quantity int `json:"quantity"`
|
|
} `json:"items"`
|
|
}
|
|
|
|
func (s *QuoteService) ValidateAndCalculate(req *QuoteRequest) (*QuoteValidationResult, error) {
|
|
if len(req.Items) == 0 {
|
|
return nil, ErrEmptyQuote
|
|
}
|
|
|
|
result := &QuoteValidationResult{
|
|
Valid: true,
|
|
Items: make([]QuoteItem, 0, len(req.Items)),
|
|
Errors: make([]string, 0),
|
|
Warnings: make([]string, 0),
|
|
}
|
|
|
|
lotNames := make([]string, len(req.Items))
|
|
quantities := make(map[string]int)
|
|
for i, item := range req.Items {
|
|
lotNames[i] = item.LotName
|
|
quantities[item.LotName] = item.Quantity
|
|
}
|
|
|
|
components, err := s.componentRepo.GetMultiple(lotNames)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
componentMap := make(map[string]*models.LotMetadata)
|
|
for i := range components {
|
|
componentMap[components[i].LotName] = &components[i]
|
|
}
|
|
|
|
var total float64
|
|
|
|
for _, reqItem := range req.Items {
|
|
comp, exists := componentMap[reqItem.LotName]
|
|
if !exists {
|
|
result.Valid = false
|
|
result.Errors = append(result.Errors, "Component not found: "+reqItem.LotName)
|
|
continue
|
|
}
|
|
|
|
item := QuoteItem{
|
|
LotName: reqItem.LotName,
|
|
Quantity: reqItem.Quantity,
|
|
HasPrice: false,
|
|
}
|
|
|
|
if comp.Lot != nil {
|
|
item.Description = comp.Lot.LotDescription
|
|
}
|
|
if comp.Category != nil {
|
|
item.Category = comp.Category.Code
|
|
}
|
|
|
|
// Get effective price (override or calculated)
|
|
price, err := s.pricingService.GetEffectivePrice(reqItem.LotName)
|
|
if err == nil && price != nil && *price > 0 {
|
|
item.UnitPrice = *price
|
|
item.TotalPrice = *price * float64(reqItem.Quantity)
|
|
item.HasPrice = true
|
|
total += item.TotalPrice
|
|
} else {
|
|
result.Warnings = append(result.Warnings, "No price available for: "+reqItem.LotName)
|
|
}
|
|
|
|
result.Items = append(result.Items, item)
|
|
}
|
|
|
|
result.Total = total
|
|
return result, nil
|
|
}
|
|
|
|
// RecordUsage records that components were used in a quote
|
|
func (s *QuoteService) RecordUsage(items []models.ConfigItem) error {
|
|
for _, item := range items {
|
|
revenue := item.UnitPrice * float64(item.Quantity)
|
|
if err := s.statsRepo.IncrementUsage(item.LotName, item.Quantity, revenue); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|