diff --git a/svc/product_series_auto_scheduler.go b/svc/product_series_auto_scheduler.go index c4aedba..650ffd6 100644 --- a/svc/product_series_auto_scheduler.go +++ b/svc/product_series_auto_scheduler.go @@ -1168,19 +1168,22 @@ func productSeriesResolvePGVariant(ctx context.Context, pg *sql.DB, productCode, dim3ID = sql.NullInt64{Int64: id, Valid: true} } - // Only treat variants as ready if they exist in authoritative item-dim combos. + // Only treat variants as ready if they exist in authoritative item-dim combos (mmitem_dim). // This prevents writing series assignments for stock variants that don't exist on the PG side. dim3Key := int64(0) - if dim3ID.Valid { + if dim3ID.Valid && dim3ID.Int64 > 1000 { dim3Key = dim3ID.Int64 } var exists int if err := pg.QueryRowContext(ctx, ` SELECT 1 -FROM mk_mmitem_dim_combo -WHERE product_code=$1 AND dim1=$2 AND dim3_key=$3 +FROM mmitem_dim +WHERE mmitem_id=$1 + AND is_active=TRUE + AND val1=$2 + AND COALESCE(CASE WHEN val3 IS NOT NULL AND val3 > 1000 THEN val3 ELSE 0 END, 0) = $3 LIMIT 1 -`, strings.TrimSpace(productCode), dim1ID, dim3Key).Scan(&exists); err != nil { +`, mmitemID, dim1ID, dim3Key).Scan(&exists); err != nil { if err == sql.ErrNoRows { return 0, 0, sql.NullInt64{}, false, nil } diff --git a/svc/routes/product_series.go b/svc/routes/product_series.go index e81521f..98248df 100644 --- a/svc/routes/product_series.go +++ b/svc/routes/product_series.go @@ -318,28 +318,34 @@ WHERE dim_column=$1 AND token = ANY($2) return out, rows.Err() } - // Load all known (product_code, dim1, dim3_key) combos for products in this response. - loadProductDimCombos := func(ctx context.Context, pg *sql.DB, productCodes []string) (map[string]struct{}, error) { + // Load authoritative combos from mmitem_dim (mmitem_id + val1(color) + val3(dim3 if any)). + // We do NOT rely on mk_mmitem_dim_combo here because that cache may be empty/stale. + loadProductDimCombos := func(ctx context.Context, pg *sql.DB, mmitemIDs []int64) (map[string]struct{}, error) { out := map[string]struct{}{} - if len(productCodes) == 0 { + if len(mmitemIDs) == 0 { return out, nil } rows, err := pg.QueryContext(ctx, ` -SELECT product_code, dim1, dim3_key -FROM mk_mmitem_dim_combo -WHERE product_code = ANY($1) -`, pq.Array(productCodes)) +SELECT + mmitem_id, + val1 AS dim1, + CASE WHEN val3 IS NOT NULL AND val3 > 1000 THEN val3 ELSE 0 END AS dim3_key +FROM mmitem_dim +WHERE mmitem_id = ANY($1) + AND is_active = TRUE + AND val1 IS NOT NULL +GROUP BY mmitem_id, val1, CASE WHEN val3 IS NOT NULL AND val3 > 1000 THEN val3 ELSE 0 END +`, pq.Array(mmitemIDs)) if err != nil { return out, err } defer rows.Close() for rows.Next() { - var code string - var dim1, dim3Key int64 - if err := rows.Scan(&code, &dim1, &dim3Key); err != nil { + var mmitemID, dim1, dim3Key int64 + if err := rows.Scan(&mmitemID, &dim1, &dim3Key); err != nil { return out, err } - key := fmt.Sprintf("%s|%d|%d", strings.TrimSpace(code), dim1, dim3Key) + key := fmt.Sprintf("%d|%d|%d", mmitemID, dim1, dim3Key) out[key] = struct{}{} } return out, rows.Err() @@ -349,7 +355,13 @@ WHERE product_code = ANY($1) mmitemByCode, _ := loadMmitemIDs(ctx, pg, codes) dim1ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval1", setToSortedSlice(colorSet)) dim3ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval3", setToSortedSlice(dim3Set)) - combos, _ := loadProductDimCombos(ctx, pg, codes) + mmitemIDs := make([]int64, 0, len(mmitemByCode)) + for _, c := range codes { + if id := mmitemByCode[c]; id > 0 { + mmitemIDs = append(mmitemIDs, id) + } + } + combos, _ := loadProductDimCombos(ctx, pg, mmitemIDs) existing, _ := loadProductSeriesAssignments(ctx, pg, codes) // Per-request cache for per-mmitem dimval3 inference to avoid repeated dfblob scans. @@ -430,7 +442,7 @@ LIMIT 1 dim3Key = row.Dim3ID } if row.MappingReady { - _, ok := combos[fmt.Sprintf("%s|%d|%d", strings.TrimSpace(row.ProductCode), row.Dim1ID, dim3Key)] + _, ok := combos[fmt.Sprintf("%d|%d|%d", row.MmitemID, row.Dim1ID, dim3Key)] row.MappingReady = ok } if !row.MappingReady { @@ -609,27 +621,32 @@ WHERE dim_column=$1 AND token = ANY($2) return out, rows.Err() } - loadProductDimCombos := func(ctx context.Context, pg *sql.DB, productCodes []string) (map[string]struct{}, error) { + loadProductDimCombos := func(ctx context.Context, pg *sql.DB, mmitemIDs []int64) (map[string]struct{}, error) { out := map[string]struct{}{} - if len(productCodes) == 0 { + if len(mmitemIDs) == 0 { return out, nil } rows, err := pg.QueryContext(ctx, ` -SELECT product_code, dim1, dim3_key -FROM mk_mmitem_dim_combo -WHERE product_code = ANY($1) -`, pq.Array(productCodes)) +SELECT + mmitem_id, + val1 AS dim1, + CASE WHEN val3 IS NOT NULL AND val3 > 1000 THEN val3 ELSE 0 END AS dim3_key +FROM mmitem_dim +WHERE mmitem_id = ANY($1) + AND is_active = TRUE + AND val1 IS NOT NULL +GROUP BY mmitem_id, val1, CASE WHEN val3 IS NOT NULL AND val3 > 1000 THEN val3 ELSE 0 END +`, pq.Array(mmitemIDs)) if err != nil { return out, err } defer rows.Close() for rows.Next() { - var code string - var dim1, dim3Key int64 - if err := rows.Scan(&code, &dim1, &dim3Key); err != nil { + var mmitemID, dim1, dim3Key int64 + if err := rows.Scan(&mmitemID, &dim1, &dim3Key); err != nil { return out, err } - key := fmt.Sprintf("%s|%d|%d", strings.TrimSpace(code), dim1, dim3Key) + key := fmt.Sprintf("%d|%d|%d", mmitemID, dim1, dim3Key) out[key] = struct{}{} } return out, rows.Err() @@ -639,7 +656,13 @@ WHERE product_code = ANY($1) mmitemByCode, _ := loadMmitemIDs(ctx, pg, codes) dim1ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval1", setToSortedSlice(colorSet)) dim3ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval3", setToSortedSlice(dim3Set)) - combos, _ := loadProductDimCombos(ctx, pg, codes) + mmitemIDs := make([]int64, 0, len(mmitemByCode)) + for _, c := range codes { + if id := mmitemByCode[c]; id > 0 { + mmitemIDs = append(mmitemIDs, id) + } + } + combos, _ := loadProductDimCombos(ctx, pg, mmitemIDs) // Per-request cache for per-mmitem dimval3 inference (dfblob scan). inferCache := map[string]int64{} @@ -720,8 +743,7 @@ LIMIT 1 if row.Dim3Code != "" { dim3Key = row.Dim3ID } - comboKey := fmt.Sprintf("%s|%d|%d", strings.TrimSpace(row.ProductCode), row.Dim1ID, dim3Key) - _, comboOK := combos[comboKey] + _, comboOK := combos[fmt.Sprintf("%d|%d|%d", row.MmitemID, row.Dim1ID, dim3Key)] row.MappingReady = false switch { @@ -732,7 +754,7 @@ LIMIT 1 case row.Dim3Code != "" && row.Dim3ID <= 0: row.MappingWarning = "B2B'de dim3 token eslesmesi yok (mk_dim_token_map: dimval3)" case !comboOK: - row.MappingWarning = "B2B'de varyant kombosu yok (mk_mmitem_dim_combo)" + row.MappingWarning = "B2B'de varyant kombosu yok (mmitem_dim)" default: // Not an orphan; skip. if baseReady && comboOK {