WIP: save current pricing and pricelist changes
This commit is contained in:
@@ -3,6 +3,7 @@ package services
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"git.mchus.pro/mchus/quoteforge/internal/localdb"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/models"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/repository"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/services/pricing"
|
||||
@@ -17,17 +18,23 @@ var (
|
||||
type QuoteService struct {
|
||||
componentRepo *repository.ComponentRepository
|
||||
statsRepo *repository.StatsRepository
|
||||
pricelistRepo *repository.PricelistRepository
|
||||
localDB *localdb.LocalDB
|
||||
pricingService *pricing.Service
|
||||
}
|
||||
|
||||
func NewQuoteService(
|
||||
componentRepo *repository.ComponentRepository,
|
||||
statsRepo *repository.StatsRepository,
|
||||
pricelistRepo *repository.PricelistRepository,
|
||||
localDB *localdb.LocalDB,
|
||||
pricingService *pricing.Service,
|
||||
) *QuoteService {
|
||||
return &QuoteService{
|
||||
componentRepo: componentRepo,
|
||||
statsRepo: statsRepo,
|
||||
pricelistRepo: pricelistRepo,
|
||||
localDB: localDB,
|
||||
pricingService: pricingService,
|
||||
}
|
||||
}
|
||||
@@ -57,6 +64,34 @@ type QuoteRequest struct {
|
||||
} `json:"items"`
|
||||
}
|
||||
|
||||
type PriceLevelsRequest struct {
|
||||
Items []struct {
|
||||
LotName string `json:"lot_name"`
|
||||
Quantity int `json:"quantity"`
|
||||
} `json:"items"`
|
||||
PricelistIDs map[string]uint `json:"pricelist_ids,omitempty"`
|
||||
}
|
||||
|
||||
type PriceLevelsItem struct {
|
||||
LotName string `json:"lot_name"`
|
||||
Quantity int `json:"quantity"`
|
||||
EstimatePrice *float64 `json:"estimate_price"`
|
||||
WarehousePrice *float64 `json:"warehouse_price"`
|
||||
CompetitorPrice *float64 `json:"competitor_price"`
|
||||
DeltaWhEstimateAbs *float64 `json:"delta_wh_estimate_abs"`
|
||||
DeltaWhEstimatePct *float64 `json:"delta_wh_estimate_pct"`
|
||||
DeltaCompEstimateAbs *float64 `json:"delta_comp_estimate_abs"`
|
||||
DeltaCompEstimatePct *float64 `json:"delta_comp_estimate_pct"`
|
||||
DeltaCompWhAbs *float64 `json:"delta_comp_wh_abs"`
|
||||
DeltaCompWhPct *float64 `json:"delta_comp_wh_pct"`
|
||||
PriceMissing []string `json:"price_missing"`
|
||||
}
|
||||
|
||||
type PriceLevelsResult struct {
|
||||
Items []PriceLevelsItem `json:"items"`
|
||||
ResolvedPricelistIDs map[string]uint `json:"resolved_pricelist_ids"`
|
||||
}
|
||||
|
||||
func (s *QuoteService) ValidateAndCalculate(req *QuoteRequest) (*QuoteValidationResult, error) {
|
||||
if len(req.Items) == 0 {
|
||||
return nil, ErrEmptyQuote
|
||||
@@ -130,6 +165,132 @@ func (s *QuoteService) ValidateAndCalculate(req *QuoteRequest) (*QuoteValidation
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *QuoteService) CalculatePriceLevels(req *PriceLevelsRequest) (*PriceLevelsResult, error) {
|
||||
if len(req.Items) == 0 {
|
||||
return nil, ErrEmptyQuote
|
||||
}
|
||||
|
||||
result := &PriceLevelsResult{
|
||||
Items: make([]PriceLevelsItem, 0, len(req.Items)),
|
||||
ResolvedPricelistIDs: map[string]uint{},
|
||||
}
|
||||
|
||||
for _, reqItem := range req.Items {
|
||||
item := PriceLevelsItem{
|
||||
LotName: reqItem.LotName,
|
||||
Quantity: reqItem.Quantity,
|
||||
PriceMissing: make([]string, 0, 3),
|
||||
}
|
||||
|
||||
estimatePrice, estimateID := s.lookupLevelPrice(models.PricelistSourceEstimate, reqItem.LotName, req.PricelistIDs)
|
||||
warehousePrice, warehouseID := s.lookupLevelPrice(models.PricelistSourceWarehouse, reqItem.LotName, req.PricelistIDs)
|
||||
competitorPrice, competitorID := s.lookupLevelPrice(models.PricelistSourceCompetitor, reqItem.LotName, req.PricelistIDs)
|
||||
|
||||
item.EstimatePrice = estimatePrice
|
||||
item.WarehousePrice = warehousePrice
|
||||
item.CompetitorPrice = competitorPrice
|
||||
|
||||
if estimateID != 0 {
|
||||
result.ResolvedPricelistIDs[string(models.PricelistSourceEstimate)] = estimateID
|
||||
}
|
||||
if warehouseID != 0 {
|
||||
result.ResolvedPricelistIDs[string(models.PricelistSourceWarehouse)] = warehouseID
|
||||
}
|
||||
if competitorID != 0 {
|
||||
result.ResolvedPricelistIDs[string(models.PricelistSourceCompetitor)] = competitorID
|
||||
}
|
||||
|
||||
if item.EstimatePrice == nil {
|
||||
item.PriceMissing = append(item.PriceMissing, string(models.PricelistSourceEstimate))
|
||||
}
|
||||
if item.WarehousePrice == nil {
|
||||
item.PriceMissing = append(item.PriceMissing, string(models.PricelistSourceWarehouse))
|
||||
}
|
||||
if item.CompetitorPrice == nil {
|
||||
item.PriceMissing = append(item.PriceMissing, string(models.PricelistSourceCompetitor))
|
||||
}
|
||||
|
||||
item.DeltaWhEstimateAbs, item.DeltaWhEstimatePct = calculateDelta(item.WarehousePrice, item.EstimatePrice)
|
||||
item.DeltaCompEstimateAbs, item.DeltaCompEstimatePct = calculateDelta(item.CompetitorPrice, item.EstimatePrice)
|
||||
item.DeltaCompWhAbs, item.DeltaCompWhPct = calculateDelta(item.CompetitorPrice, item.WarehousePrice)
|
||||
|
||||
result.Items = append(result.Items, item)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func calculateDelta(target, base *float64) (*float64, *float64) {
|
||||
if target == nil || base == nil {
|
||||
return nil, nil
|
||||
}
|
||||
abs := *target - *base
|
||||
if *base == 0 {
|
||||
return &abs, nil
|
||||
}
|
||||
pct := (abs / *base) * 100
|
||||
return &abs, &pct
|
||||
}
|
||||
|
||||
func (s *QuoteService) lookupLevelPrice(source models.PricelistSource, lotName string, pricelistIDs map[string]uint) (*float64, uint) {
|
||||
sourceKey := string(source)
|
||||
if id, ok := pricelistIDs[sourceKey]; ok && id > 0 {
|
||||
price, found := s.lookupPriceByPricelistID(id, lotName)
|
||||
if found {
|
||||
return &price, id
|
||||
}
|
||||
return nil, id
|
||||
}
|
||||
|
||||
if s.pricelistRepo != nil {
|
||||
price, id, err := s.pricelistRepo.GetPriceForLotBySource(sourceKey, lotName)
|
||||
if err == nil && price > 0 {
|
||||
return &price, id
|
||||
}
|
||||
|
||||
latest, latestErr := s.pricelistRepo.GetLatestActiveBySource(sourceKey)
|
||||
if latestErr == nil {
|
||||
return nil, latest.ID
|
||||
}
|
||||
}
|
||||
|
||||
if s.localDB != nil {
|
||||
localPL, err := s.localDB.GetLatestLocalPricelistBySource(sourceKey)
|
||||
if err != nil {
|
||||
return nil, 0
|
||||
}
|
||||
price, err := s.localDB.GetLocalPriceForLot(localPL.ID, lotName)
|
||||
if err != nil || price <= 0 {
|
||||
return nil, localPL.ServerID
|
||||
}
|
||||
return &price, localPL.ServerID
|
||||
}
|
||||
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
func (s *QuoteService) lookupPriceByPricelistID(pricelistID uint, lotName string) (float64, bool) {
|
||||
if s.pricelistRepo != nil {
|
||||
price, err := s.pricelistRepo.GetPriceForLot(pricelistID, lotName)
|
||||
if err == nil && price > 0 {
|
||||
return price, true
|
||||
}
|
||||
}
|
||||
|
||||
if s.localDB != nil {
|
||||
localPL, err := s.localDB.GetLocalPricelistByServerID(pricelistID)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
price, err := s.localDB.GetLocalPriceForLot(localPL.ID, lotName)
|
||||
if err == nil && price > 0 {
|
||||
return price, true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// RecordUsage records that components were used in a quote
|
||||
func (s *QuoteService) RecordUsage(items []models.ConfigItem) error {
|
||||
if s.statsRepo == nil {
|
||||
|
||||
Reference in New Issue
Block a user