feat: price_quality в конфигураторе; фикс пустого lot_description в экспорте цен

price_quality теперь синхронизируется не только в прайслист, но и в
LocalComponent/services.ComponentView (/api/components) — виден в
выпадающем списке поиска (цветная точка), в таблице конфигуратора
(левая колонка-пиктограмма) и на вкладке «Ценообразование» (подсветка
строки). Цветовая шкала вынесена в один модуль web/static/price-quality.js
(подключён в base.html), чтобы не дублировать расчёт цвета по шаблонам.
Шкала — градиент 0 (красный) → 5 (жёлтый) → 9 (зелёный).

Экспорт цен (internal/services/export.go): resolveLotDescriptions был
заглушкой, всегда возвращавшей пустую карту — строки BOM/не-BOM в
экспорте всегда оставались без описания LOT. Заменён на реальный
запрос к local_pricelist_items текущего выбранного прайслиста
(LocalDB.GetLocalDescriptionsForLots), по аналогии с уже существующим
GetLocalLotCategoriesByServerPricelistID.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Mikhail Chusavitin
2026-07-22 19:01:47 +03:00
co-authored by Claude Sonnet 5
parent 3348008198
commit ee92c3b392
11 changed files with 169 additions and 24 deletions
+1
View File
@@ -14,4 +14,5 @@ type ComponentView struct {
Category string `json:"category"`
CategoryName string `json:"category_name"`
Model string `json:"model"`
PriceQuality *int `json:"price_quality,omitempty"`
}
+29 -2
View File
@@ -687,8 +687,35 @@ func (s *ExportService) batchLookupPrices(serverPricelistID *uint, lots []string
return prices
}
func (s *ExportService) resolveLotDescriptions(_ *models.Configuration, _ *localdb.LocalConfiguration) map[string]string {
return map[string]string{}
// resolveLotDescriptions returns each LOT's description from the configuration's
// currently selected estimate pricelist (falling back to the latest active one),
// mirroring the pricelist resolution in resolvePricingTotals. This is the single
// source of LOT description across the project — see bible-local/03-database.md.
func (s *ExportService) resolveLotDescriptions(cfg *models.Configuration, localCfg *localdb.LocalConfiguration) map[string]string {
if s.localDB == nil {
return map[string]string{}
}
lots := collectPricingLots(cfg, localCfg, true)
if len(lots) == 0 {
return map[string]string{}
}
estimateID := cfg.PricelistID
if estimateID == nil || *estimateID == 0 {
if latest, err := s.localDB.GetLatestLocalPricelistBySource("estimate"); err == nil && latest != nil {
estimateID = &latest.ServerID
}
}
if estimateID == nil || *estimateID == 0 {
return map[string]string{}
}
descriptions, err := s.localDB.GetLocalDescriptionsForLots(*estimateID, lots)
if err != nil {
return map[string]string{}
}
return descriptions
}
func collectPricingLots(cfg *models.Configuration, localCfg *localdb.LocalConfiguration, includeBOM bool) []string {