ui: add B2B olmayan stok (orphans) page

This commit is contained in:
M_Kececi
2026-06-24 23:14:58 +03:00
parent 701cc5e439
commit aa100973b3
2 changed files with 57 additions and 32 deletions

View File

@@ -1168,19 +1168,22 @@ func productSeriesResolvePGVariant(ctx context.Context, pg *sql.DB, productCode,
dim3ID = sql.NullInt64{Int64: id, Valid: true} 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. // This prevents writing series assignments for stock variants that don't exist on the PG side.
dim3Key := int64(0) dim3Key := int64(0)
if dim3ID.Valid { if dim3ID.Valid && dim3ID.Int64 > 1000 {
dim3Key = dim3ID.Int64 dim3Key = dim3ID.Int64
} }
var exists int var exists int
if err := pg.QueryRowContext(ctx, ` if err := pg.QueryRowContext(ctx, `
SELECT 1 SELECT 1
FROM mk_mmitem_dim_combo FROM mmitem_dim
WHERE product_code=$1 AND dim1=$2 AND dim3_key=$3 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 LIMIT 1
`, strings.TrimSpace(productCode), dim1ID, dim3Key).Scan(&exists); err != nil { `, mmitemID, dim1ID, dim3Key).Scan(&exists); err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return 0, 0, sql.NullInt64{}, false, nil return 0, 0, sql.NullInt64{}, false, nil
} }

View File

@@ -318,28 +318,34 @@ WHERE dim_column=$1 AND token = ANY($2)
return out, rows.Err() return out, rows.Err()
} }
// Load all known (product_code, dim1, dim3_key) combos for products in this response. // Load authoritative combos from mmitem_dim (mmitem_id + val1(color) + val3(dim3 if any)).
loadProductDimCombos := func(ctx context.Context, pg *sql.DB, productCodes []string) (map[string]struct{}, error) { // 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{}{} out := map[string]struct{}{}
if len(productCodes) == 0 { if len(mmitemIDs) == 0 {
return out, nil return out, nil
} }
rows, err := pg.QueryContext(ctx, ` rows, err := pg.QueryContext(ctx, `
SELECT product_code, dim1, dim3_key SELECT
FROM mk_mmitem_dim_combo mmitem_id,
WHERE product_code = ANY($1) val1 AS dim1,
`, pq.Array(productCodes)) 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 { if err != nil {
return out, err return out, err
} }
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var code string var mmitemID, dim1, dim3Key int64
var dim1, dim3Key int64 if err := rows.Scan(&mmitemID, &dim1, &dim3Key); err != nil {
if err := rows.Scan(&code, &dim1, &dim3Key); err != nil {
return out, err 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{}{} out[key] = struct{}{}
} }
return out, rows.Err() return out, rows.Err()
@@ -349,7 +355,13 @@ WHERE product_code = ANY($1)
mmitemByCode, _ := loadMmitemIDs(ctx, pg, codes) mmitemByCode, _ := loadMmitemIDs(ctx, pg, codes)
dim1ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval1", setToSortedSlice(colorSet)) dim1ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval1", setToSortedSlice(colorSet))
dim3ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval3", setToSortedSlice(dim3Set)) 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) existing, _ := loadProductSeriesAssignments(ctx, pg, codes)
// Per-request cache for per-mmitem dimval3 inference to avoid repeated dfblob scans. // Per-request cache for per-mmitem dimval3 inference to avoid repeated dfblob scans.
@@ -430,7 +442,7 @@ LIMIT 1
dim3Key = row.Dim3ID dim3Key = row.Dim3ID
} }
if row.MappingReady { 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 row.MappingReady = ok
} }
if !row.MappingReady { if !row.MappingReady {
@@ -609,27 +621,32 @@ WHERE dim_column=$1 AND token = ANY($2)
return out, rows.Err() 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{}{} out := map[string]struct{}{}
if len(productCodes) == 0 { if len(mmitemIDs) == 0 {
return out, nil return out, nil
} }
rows, err := pg.QueryContext(ctx, ` rows, err := pg.QueryContext(ctx, `
SELECT product_code, dim1, dim3_key SELECT
FROM mk_mmitem_dim_combo mmitem_id,
WHERE product_code = ANY($1) val1 AS dim1,
`, pq.Array(productCodes)) 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 { if err != nil {
return out, err return out, err
} }
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var code string var mmitemID, dim1, dim3Key int64
var dim1, dim3Key int64 if err := rows.Scan(&mmitemID, &dim1, &dim3Key); err != nil {
if err := rows.Scan(&code, &dim1, &dim3Key); err != nil {
return out, err 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{}{} out[key] = struct{}{}
} }
return out, rows.Err() return out, rows.Err()
@@ -639,7 +656,13 @@ WHERE product_code = ANY($1)
mmitemByCode, _ := loadMmitemIDs(ctx, pg, codes) mmitemByCode, _ := loadMmitemIDs(ctx, pg, codes)
dim1ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval1", setToSortedSlice(colorSet)) dim1ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval1", setToSortedSlice(colorSet))
dim3ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval3", setToSortedSlice(dim3Set)) 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). // Per-request cache for per-mmitem dimval3 inference (dfblob scan).
inferCache := map[string]int64{} inferCache := map[string]int64{}
@@ -720,8 +743,7 @@ LIMIT 1
if row.Dim3Code != "" { if row.Dim3Code != "" {
dim3Key = row.Dim3ID dim3Key = row.Dim3ID
} }
comboKey := fmt.Sprintf("%s|%d|%d", strings.TrimSpace(row.ProductCode), row.Dim1ID, dim3Key) _, comboOK := combos[fmt.Sprintf("%d|%d|%d", row.MmitemID, row.Dim1ID, dim3Key)]
_, comboOK := combos[comboKey]
row.MappingReady = false row.MappingReady = false
switch { switch {
@@ -732,7 +754,7 @@ LIMIT 1
case row.Dim3Code != "" && row.Dim3ID <= 0: case row.Dim3Code != "" && row.Dim3ID <= 0:
row.MappingWarning = "B2B'de dim3 token eslesmesi yok (mk_dim_token_map: dimval3)" row.MappingWarning = "B2B'de dim3 token eslesmesi yok (mk_dim_token_map: dimval3)"
case !comboOK: case !comboOK:
row.MappingWarning = "B2B'de varyant kombosu yok (mk_mmitem_dim_combo)" row.MappingWarning = "B2B'de varyant kombosu yok (mmitem_dim)"
default: default:
// Not an orphan; skip. // Not an orphan; skip.
if baseReady && comboOK { if baseReady && comboOK {