feat: world-прайслист как заглушка для отсутствующих цен

Там, где для LOT нет цены в estimate/warehouse/competitor, подставляется
цена из world-прайслиста. Такие ячейки в таблицах «Цена покупки»/«Цена продажи»
подсвечиваются (amber), участвуют в «Итого» и убирают красную «*».
В CSV-экспорте добавлена колонка «Заглушка (world)» с перечнем столбцов,
где сработал фолбэк.

Добавлены Tx-версии GetLatestLocalPricelistBySource/GetLocalPricesForLots,
чтобы резолв прайслистов внутри транзакции не дедлочил single-connection пул.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-07-10 10:25:46 +03:00
co-authored by Claude Opus 4.8
parent 498cbf5490
commit 70a3ff255f
10 changed files with 586 additions and 52 deletions
+38
View File
@@ -89,6 +89,9 @@ type PriceLevelsItem struct {
DeltaCompEstimatePct *float64 `json:"delta_comp_estimate_pct"`
DeltaCompWhAbs *float64 `json:"delta_comp_wh_abs"`
DeltaCompWhPct *float64 `json:"delta_comp_wh_pct"`
EstimateFromWorld bool `json:"estimate_from_world"`
WarehouseFromWorld bool `json:"warehouse_from_world"`
CompetitorFromWorld bool `json:"competitor_from_world"`
PriceMissing []string `json:"price_missing"`
}
@@ -239,6 +242,29 @@ func (s *QuoteService) CalculatePriceLevels(req *PriceLevelsRequest) (*PriceLeve
}
}
var worldID uint
if req.PricelistIDs != nil {
if explicitID, ok := req.PricelistIDs[string(models.PricelistSourceWorld)]; ok && explicitID > 0 {
worldID = explicitID
}
}
if worldID == 0 && s.pricelistRepo != nil {
if latest, err := s.pricelistRepo.GetLatestActiveBySource(string(models.PricelistSourceWorld)); err == nil {
worldID = latest.ID
}
}
if worldID == 0 && s.localDB != nil {
if localPL, err := s.localDB.GetLatestLocalPricelistBySource(string(models.PricelistSourceWorld)); err == nil && localPL != nil {
worldID = localPL.ServerID
}
}
worldPrices := map[string]float64{}
if worldID != 0 {
if prices, err := s.lookupPricesByPricelistID(worldID, lotNames, req.NoCache); err == nil {
worldPrices = prices
}
}
for _, reqItem := range req.Items {
responseLotName := originalLotNames[reqItem.LotName]
if responseLotName == "" {
@@ -253,14 +279,26 @@ func (s *QuoteService) CalculatePriceLevels(req *PriceLevelsRequest) (*PriceLeve
if p, ok := levelBySource[models.PricelistSourceEstimate].prices[reqItem.LotName]; ok && p > 0 {
price := p
item.EstimatePrice = &price
} else if wp, ok := worldPrices[reqItem.LotName]; ok && wp > 0 {
price := wp
item.EstimatePrice = &price
item.EstimateFromWorld = true
}
if p, ok := levelBySource[models.PricelistSourceWarehouse].prices[reqItem.LotName]; ok && p > 0 {
price := p
item.WarehousePrice = &price
} else if wp, ok := worldPrices[reqItem.LotName]; ok && wp > 0 {
price := wp
item.WarehousePrice = &price
item.WarehouseFromWorld = true
}
if p, ok := levelBySource[models.PricelistSourceCompetitor].prices[reqItem.LotName]; ok && p > 0 {
price := p
item.CompetitorPrice = &price
} else if wp, ok := worldPrices[reqItem.LotName]; ok && wp > 0 {
price := wp
item.CompetitorPrice = &price
item.CompetitorFromWorld = true
}
if item.EstimatePrice == nil {