From 50d1e281a5b7a45bf8d8f5b6f469c1fc3ddc8edc Mon Sep 17 00:00:00 2001 From: Mikhail Chusavitin Date: Wed, 18 Feb 2026 10:31:35 +0300 Subject: [PATCH] Update pricing handler counts and defaults --- internal/handlers/pricing.go | 82 ++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 14 deletions(-) diff --git a/internal/handlers/pricing.go b/internal/handlers/pricing.go index 6d165fc..b889d49 100644 --- a/internal/handlers/pricing.go +++ b/internal/handlers/pricing.go @@ -211,13 +211,52 @@ func (h *PricingHandler) ListComponents(c *gin.Context) { return } - // Get quote counts + // Get all lot names for meta expansion + var allLotNames []string + h.db.Model(&models.LotMetadata{}).Pluck("lot_name", &allLotNames) + + // Build per-component source lots for quote counts + componentSources := make(map[string][]string, len(components)) + uniqueLots := make(map[string]struct{}) lotNames := make([]string, len(components)) for i, comp := range components { lotNames[i] = comp.LotName + var sourceLots []string + if strings.TrimSpace(comp.MetaPrices) != "" { + sourceLots = expandMetaPricesWithCache(comp.MetaPrices, comp.LotName, allLotNames) + } else { + sourceLots = []string{comp.LotName} + } + componentSources[comp.LotName] = sourceLots + for _, ln := range sourceLots { + if ln == "" { + continue + } + uniqueLots[ln] = struct{}{} + } } - counts, _ := h.priceRepo.GetQuoteCounts(lotNames) + // Fetch quote counts for all source lots in one query + counts := map[string]int64{} + if len(uniqueLots) > 0 { + allSources := make([]string, 0, len(uniqueLots)) + for ln := range uniqueLots { + allSources = append(allSources, ln) + } + type Result struct { + Lot string + Count int64 + } + var results []Result + _ = h.db.Model(&models.LotLog{}). + Select("lot, COUNT(*) as count"). + Where("lot IN ?", allSources). + Group("lot"). + Scan(&results).Error + for _, r := range results { + counts[r.Lot] = r.Count + } + } // Get meta usage information metaUsage := h.getMetaUsageMap(lotNames) @@ -225,9 +264,14 @@ func (h *PricingHandler) ListComponents(c *gin.Context) { // Combine components with counts result := make([]ComponentWithCount, len(components)) for i, comp := range components { + sourceLots := componentSources[comp.LotName] + var quoteCount int64 + for _, ln := range sourceLots { + quoteCount += counts[ln] + } result[i] = ComponentWithCount{ LotMetadata: comp, - QuoteCount: counts[comp.LotName], + QuoteCount: quoteCount, UsedInMeta: metaUsage[comp.LotName], } } @@ -522,6 +566,8 @@ func (h *PricingHandler) RecalculateAll(c *gin.Context) { return } + force := c.Query("force") == "1" || strings.EqualFold(c.Query("force"), "true") + taskID := h.taskManager.Submit(tasks.TaskTypeRecalculate, func(ctx context.Context, progressCb func(int, string)) (map[string]interface{}, error) { // Get all components with their settings var components []models.LotMetadata @@ -554,7 +600,7 @@ func (h *PricingHandler) RecalculateAll(c *gin.Context) { } // Process components individually to respect their settings - var updated, skipped, manual, unchanged, errors int + var updated, skipped, manual, unchanged, errors, noPeriodData int now := time.Now() for i, comp := range components { @@ -586,7 +632,7 @@ func (h *PricingHandler) RecalculateAll(c *gin.Context) { } // Check if there are new quotes since last update (using cached dates) - if comp.PriceUpdatedAt != nil { + if !force && comp.PriceUpdatedAt != nil { hasNewData := false for _, lot := range sourceLots { if latestDate, ok := lotLatestDate[lot]; ok { @@ -609,8 +655,10 @@ func (h *PricingHandler) RecalculateAll(c *gin.Context) { goto sendProgress } + noPeriodForComp := false // If no prices in period, try all time if len(prices) == 0 && periodDays > 0 { + noPeriodForComp = true prices, err = h.queryMultipleLotPrices(sourceLots, 0) if err != nil { errors++ @@ -619,9 +667,15 @@ func (h *PricingHandler) RecalculateAll(c *gin.Context) { } if len(prices) == 0 { + if noPeriodForComp { + noPeriodData++ + } skipped++ goto sendProgress } + if noPeriodForComp { + noPeriodData++ + } // Calculate price based on method var basePrice float64 @@ -676,15 +730,16 @@ func (h *PricingHandler) RecalculateAll(c *gin.Context) { h.statsRepo.UpdatePopularityScores() // Send final completion message - progressCb(100, fmt.Sprintf("Пересчёт завершён: обновлено %d из %d компонентов", updated, total)) + progressCb(100, fmt.Sprintf("Пересчёт завершён: обновлено %d из %d, без данных за период: %d", updated, total, noPeriodData)) return map[string]interface{}{ - "updated": updated, - "skipped": skipped, - "manual": manual, - "unchanged": unchanged, - "errors": errors, - "total": total, + "updated": updated, + "skipped": skipped, + "manual": manual, + "unchanged": unchanged, + "errors": errors, + "total": total, + "no_period_data": noPeriodData, }, nil }) @@ -1456,8 +1511,7 @@ func (h *PricingHandler) CreateLot(c *gin.Context) { categoryCode := strings.ToUpper(strings.TrimSpace(req.Category)) if categoryCode == "" { - parsedCategory, _ := services.ParsePartNumber(lotName) - categoryCode = strings.ToUpper(strings.TrimSpace(parsedCategory)) + categoryCode = models.DefaultLotCategoryCode } if len(categoryCode) > 20 { c.JSON(http.StatusBadRequest, gin.H{"error": "category too long (max 20)"})