diff --git a/svc/product_series_auto_scheduler.go b/svc/product_series_auto_scheduler.go index f739e73..9775d44 100644 --- a/svc/product_series_auto_scheduler.go +++ b/svc/product_series_auto_scheduler.go @@ -576,6 +576,22 @@ func productSeriesApplyVariant(ctx context.Context, pg *sql.DB, v productSeriesA } } if len(selected) == 0 { + // Apply fallback based on per-size availability, not total. + // If none of the sizes has qty >= 1 (default), do nothing. + minSizeQty := envFloat64("PRODUCT_SERIES_FALLBACK_MIN_SIZE_QTY", 1) + if !productSeriesHasAnySizeAtLeast(v.SizeQty, minSizeQty) { + if productSeriesFallbackLogEnabled() { + log.Printf("[ProductSeriesFallback] skip_insufficient_stock product=%s color=%s dim3=%s min_size_qty=%.2f sizes=%s", + strings.TrimSpace(v.ProductCode), + strings.TrimSpace(v.ColorCode), + strings.TrimSpace(v.Dim3Code), + minSizeQty, + productSeriesFormatSizeQty(v.SizeQty), + ) + } + return 0, 1, nil + } + fallbackID, fallbackCode, err := productSeriesResolveFallbackSeries(ctx, pg) if err != nil { return 0, 1, err @@ -1409,6 +1425,27 @@ func envIntRange(key string, fallback int, min int, max int) int { return n } +func envFloat64(key string, fallback float64) float64 { + raw := strings.TrimSpace(os.Getenv(key)) + if raw == "" { + return fallback + } + v, err := strconv.ParseFloat(raw, 64) + if err != nil { + return fallback + } + return v +} + +func productSeriesHasAnySizeAtLeast(sizeQty map[string]float64, min float64) bool { + for _, q := range sizeQty { + if q >= min { + return true + } + } + return false +} + func envHHMM(key string, fallbackHH int, fallbackMM int) (int, int) { raw := strings.TrimSpace(os.Getenv(key)) if raw == "" {