Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -533,6 +533,7 @@ func productSeriesApplyVariant(ctx context.Context, pg *sql.DB, v productSeriesA
|
||||
if err != nil {
|
||||
return 0, 1, err
|
||||
}
|
||||
appliedFallback := false
|
||||
if !ready {
|
||||
if productSeriesFallbackLogEnabled() {
|
||||
log.Printf("[ProductSeriesFallback] skip_not_ready product=%s color=%s dim3=%s", strings.TrimSpace(v.ProductCode), strings.TrimSpace(v.ColorCode), strings.TrimSpace(v.Dim3Code))
|
||||
@@ -607,6 +608,30 @@ func productSeriesApplyVariant(ctx context.Context, pg *sql.DB, v productSeriesA
|
||||
return 0, 1, err
|
||||
}
|
||||
if len(existingIDs) > 0 {
|
||||
// Optional grace window: if we recently applied non-fallback rules for this variant,
|
||||
// don't immediately flap back to fallback due to a transient/partial stock snapshot.
|
||||
graceMin := envIntRange("PRODUCT_SERIES_FALLBACK_GRACE_MINUTES", 2, 0, 60)
|
||||
if graceMin > 0 {
|
||||
rowKey := productSeriesAutoKey(v.ProductCode, v.ColorCode, v.Dim3Code)
|
||||
kind, at, err := productSeriesLoadLastApplyState(ctx, pg, rowKey)
|
||||
if err != nil {
|
||||
return 0, 1, err
|
||||
}
|
||||
if kind == "rules" && time.Since(at) < time.Duration(graceMin)*time.Minute {
|
||||
// keep existing mapping as-is for now
|
||||
if productSeriesFallbackLogEnabled() {
|
||||
log.Printf("[ProductSeriesFallback] grace_skip product=%s color=%s dim3=%s grace_min=%d last_rules_at=%s",
|
||||
strings.TrimSpace(v.ProductCode),
|
||||
strings.TrimSpace(v.ColorCode),
|
||||
strings.TrimSpace(v.Dim3Code),
|
||||
graceMin,
|
||||
at.Format(time.RFC3339),
|
||||
)
|
||||
}
|
||||
return 0, 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
anyStillValid := false
|
||||
for _, sid := range existingIDs {
|
||||
// If we can't find the rule definition in memory, assume it's a manual/unknown mapping and keep it.
|
||||
@@ -641,6 +666,7 @@ func productSeriesApplyVariant(ctx context.Context, pg *sql.DB, v productSeriesA
|
||||
}
|
||||
// Use the fallback series as the single selected rule.
|
||||
selected = []productSeriesAutoRule{{SeriesID: fallbackID}}
|
||||
appliedFallback = true
|
||||
} else {
|
||||
if productSeriesFallbackLogEnabled() {
|
||||
log.Printf("[ProductSeriesFallback] missing_fallback product=%s color=%s dim3=%s fallback_code=%s", strings.TrimSpace(v.ProductCode), strings.TrimSpace(v.ColorCode), strings.TrimSpace(v.Dim3Code), strings.TrimSpace(fallbackCode))
|
||||
@@ -676,11 +702,27 @@ VALUES ($1, $2, $3, $4)
|
||||
if err := tx.Commit(); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
// Track what we last applied for this variant to reduce flapping and help ops debugging.
|
||||
rowKey := productSeriesAutoKey(v.ProductCode, v.ColorCode, v.Dim3Code)
|
||||
kind := "rules"
|
||||
if appliedFallback {
|
||||
kind = "fallback"
|
||||
}
|
||||
ids := make([]string, 0, len(selected))
|
||||
for _, r := range selected {
|
||||
ids = append(ids, fmt.Sprintf("%d", r.SeriesID))
|
||||
}
|
||||
_, _ = pg.ExecContext(ctx, `
|
||||
INSERT INTO mk_product_series_apply_state (row_key, last_apply_at, last_apply_kind, last_series_ids, updated_at)
|
||||
VALUES ($1, now(), $2, $3, now())
|
||||
ON CONFLICT (row_key) DO UPDATE
|
||||
SET last_apply_at=EXCLUDED.last_apply_at,
|
||||
last_apply_kind=EXCLUDED.last_apply_kind,
|
||||
last_series_ids=EXCLUDED.last_series_ids,
|
||||
updated_at=now()
|
||||
`, rowKey, kind, strings.Join(ids, ","))
|
||||
|
||||
if productSeriesDebugMatch(v) {
|
||||
ids := make([]string, 0, len(selected))
|
||||
for _, r := range selected {
|
||||
ids = append(ids, fmt.Sprintf("%d", r.SeriesID))
|
||||
}
|
||||
log.Printf("[ProductSeriesDebug] wrote=%d product=%s color=%s dim3=%s series_ids=%s",
|
||||
len(selected),
|
||||
strings.TrimSpace(v.ProductCode),
|
||||
@@ -692,6 +734,23 @@ VALUES ($1, $2, $3, $4)
|
||||
return len(selected), 0, nil
|
||||
}
|
||||
|
||||
func productSeriesLoadLastApplyState(ctx context.Context, pg *sql.DB, rowKey string) (kind string, at time.Time, err error) {
|
||||
var k string
|
||||
var t time.Time
|
||||
err = pg.QueryRowContext(ctx, `
|
||||
SELECT COALESCE(last_apply_kind,''), COALESCE(last_apply_at, now())
|
||||
FROM mk_product_series_apply_state
|
||||
WHERE row_key=$1
|
||||
`, rowKey).Scan(&k, &t)
|
||||
if err == sql.ErrNoRows {
|
||||
return "", time.Time{}, nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
return k, t, nil
|
||||
}
|
||||
|
||||
func productSeriesFindRuleByID(rules []productSeriesAutoRule, seriesID int64) (productSeriesAutoRule, bool) {
|
||||
for _, r := range rules {
|
||||
if r.SeriesID == seriesID {
|
||||
|
||||
Reference in New Issue
Block a user