From 01e457630ced2b16f2c6b87da17ac86d94e4ba96 Mon Sep 17 00:00:00 2001 From: M_Kececi Date: Wed, 24 Jun 2026 11:25:11 +0300 Subject: [PATCH] Merge remote-tracking branch 'origin/master' --- svc/product_series_auto_scheduler.go | 57 ++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/svc/product_series_auto_scheduler.go b/svc/product_series_auto_scheduler.go index 2f6b8e0..3a712b6 100644 --- a/svc/product_series_auto_scheduler.go +++ b/svc/product_series_auto_scheduler.go @@ -793,6 +793,11 @@ ORDER BY d.id, r.priority DESC, r.id } func productSeriesSelectRules(v productSeriesAutoVariant, rules []productSeriesAutoRule) []productSeriesAutoRule { + selectionMode := strings.TrimSpace(strings.ToLower(os.Getenv("PRODUCT_SERIES_SELECTION_MODE"))) + if selectionMode == "" { + selectionMode = "dfs" + } + candidates := make([]productSeriesAutoRule, 0, len(rules)) for _, rule := range rules { if !productSeriesRuleFitsGroup(v.GroupKey, rule) { @@ -805,6 +810,58 @@ func productSeriesSelectRules(v productSeriesAutoVariant, rules []productSeriesA if len(candidates) == 0 { return nil } + + if selectionMode == "greedy" { + // Greedy: start from the widest series (most sizes), then narrower, until nothing fits. + // This matches the operational expectation: cover as much of the stock-size space as possible + // using broad series first, then progressively narrower series; fallback=1 only if nothing matches. + sort.Slice(candidates, func(i, j int) bool { + li := len(candidates[i].Ratio) + lj := len(candidates[j].Ratio) + if li != lj { + return li > lj + } + if candidates[i].Priority != candidates[j].Priority { + return candidates[i].Priority > candidates[j].Priority + } + // Prefer numeric codes ascending when comparable, otherwise lexicographic. + ai, ei := strconv.Atoi(strings.TrimSpace(candidates[i].Code)) + aj, ej := strconv.Atoi(strings.TrimSpace(candidates[j].Code)) + if ei == nil && ej == nil && ai != aj { + return ai < aj + } + return candidates[i].Code < candidates[j].Code + }) + + stock := copyProductSeriesStock(v.SizeQty) + picked := make([]productSeriesAutoRule, 0, 8) + used := make([]bool, len(candidates)) + for { + pickedOne := false + for i := range candidates { + if used[i] { + continue + } + rule := candidates[i] + if productSeriesCanConsume(stock, rule) { + used[i] = true + picked = append(picked, rule) + productSeriesConsume(stock, rule, -1) + pickedOne = true + break // restart from widest again + } + } + if !pickedOne { + break + } + } + if len(picked) == 0 { + return nil + } + sort.Slice(picked, func(i, j int) bool { return picked[i].Code < picked[j].Code }) + return picked + } + if len(candidates) > 24 { candidates = candidates[:24] }