ui: add B2B olmayan stok (orphans) page
This commit is contained in:
@@ -1149,12 +1149,33 @@ func productSeriesResolvePGVariant(ctx context.Context, pg *sql.DB, productCode,
|
||||
}
|
||||
return 0, 0, sql.NullInt64{}, false, err
|
||||
}
|
||||
dim1ID, ok, err := productSeriesResolveDimTokenID(ctx, pg, "dimval1", colorCode, mmitemID)
|
||||
if err != nil {
|
||||
return 0, 0, sql.NullInt64{}, false, err
|
||||
}
|
||||
if !ok || dim1ID <= 0 {
|
||||
return 0, 0, sql.NullInt64{}, false, nil
|
||||
// Authoritative dim1 resolver: for this installation, "color_code" tokens correspond to dfgrp.code on
|
||||
// the dim values referenced by mmitem_dim.val1. Do NOT rely on mk_dim_token_map for dimval1 because
|
||||
// it can be polluted and conflate tokens.
|
||||
var dim1ID int64
|
||||
{
|
||||
tok := strings.ToUpper(strings.TrimSpace(colorCode))
|
||||
if tok == "" || tok == "0" {
|
||||
return 0, 0, sql.NullInt64{}, false, nil
|
||||
}
|
||||
if err := pg.QueryRowContext(ctx, `
|
||||
SELECT md.val1
|
||||
FROM mmitem_dim md
|
||||
JOIN dfgrp d ON d.id = md.val1
|
||||
WHERE md.mmitem_id = $1
|
||||
AND md.is_active = TRUE
|
||||
AND UPPER(BTRIM(d.code)) = $2
|
||||
GROUP BY md.val1
|
||||
LIMIT 1
|
||||
`, mmitemID, tok).Scan(&dim1ID); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return 0, 0, sql.NullInt64{}, false, nil
|
||||
}
|
||||
return 0, 0, sql.NullInt64{}, false, err
|
||||
}
|
||||
if dim1ID <= 0 {
|
||||
return 0, 0, sql.NullInt64{}, false, nil
|
||||
}
|
||||
}
|
||||
var dim3ID sql.NullInt64
|
||||
if strings.TrimSpace(dim3Code) != "" {
|
||||
|
||||
@@ -318,42 +318,10 @@ WHERE dim_column=$1 AND token = ANY($2)
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// 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(mmitemIDs) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
rows, err := pg.QueryContext(ctx, `
|
||||
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 mmitemID, dim1, dim3Key int64
|
||||
if err := rows.Scan(&mmitemID, &dim1, &dim3Key); err != nil {
|
||||
return out, err
|
||||
}
|
||||
key := fmt.Sprintf("%d|%d|%d", mmitemID, dim1, dim3Key)
|
||||
out[key] = struct{}{}
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
// Authoritative dims/combos are resolved via mmitem_dim + dfgrp.code (see helpers below).
|
||||
|
||||
codes := setToSortedSlice(codeSet)
|
||||
mmitemByCode, _ := loadMmitemIDs(ctx, pg, codes)
|
||||
dim1ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval1", setToSortedSlice(colorSet))
|
||||
dim3ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval3", setToSortedSlice(dim3Set))
|
||||
mmitemIDs := make([]int64, 0, len(mmitemByCode))
|
||||
for _, c := range codes {
|
||||
@@ -361,7 +329,13 @@ GROUP BY mmitem_id, val1, CASE WHEN val3 IS NOT NULL AND val3 > 1000 THEN val3 E
|
||||
mmitemIDs = append(mmitemIDs, id)
|
||||
}
|
||||
}
|
||||
combos, _ := loadProductDimCombos(ctx, pg, mmitemIDs)
|
||||
// Normalize color tokens to UPPER/TRIM to match dfgrp.code.
|
||||
colorTokens := setToSortedSlice(colorSet)
|
||||
for i := range colorTokens {
|
||||
colorTokens[i] = strings.ToUpper(strings.TrimSpace(colorTokens[i]))
|
||||
}
|
||||
dim1ByMmitemToken, _ := loadDim1ByMmitemAndToken(ctx, pg, mmitemIDs, colorTokens)
|
||||
combos, _ := loadProductDimCombosFromMmitemDim(ctx, pg, mmitemIDs)
|
||||
existing, _ := loadProductSeriesAssignments(ctx, pg, codes)
|
||||
|
||||
// Per-request cache for per-mmitem dimval3 inference to avoid repeated dfblob scans.
|
||||
@@ -425,7 +399,7 @@ LIMIT 1
|
||||
out := make([]productSeriesMappingRow, 0, len(grouped))
|
||||
for _, row := range grouped {
|
||||
row.MmitemID = mmitemByCode[row.ProductCode]
|
||||
row.Dim1ID = dim1ByToken[row.ColorCode]
|
||||
row.Dim1ID = dim1ByMmitemToken[fmt.Sprintf("%d|%s", row.MmitemID, strings.ToUpper(strings.TrimSpace(row.ColorCode)))]
|
||||
if row.Dim3Code != "" {
|
||||
// Prefer token map; fallback to per-item inference (do not persist).
|
||||
if v := dim3ByToken[row.Dim3Code]; v > 0 {
|
||||
@@ -621,40 +595,8 @@ WHERE dim_column=$1 AND token = ANY($2)
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
loadProductDimCombos := func(ctx context.Context, pg *sql.DB, mmitemIDs []int64) (map[string]struct{}, error) {
|
||||
out := map[string]struct{}{}
|
||||
if len(mmitemIDs) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
rows, err := pg.QueryContext(ctx, `
|
||||
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 mmitemID, dim1, dim3Key int64
|
||||
if err := rows.Scan(&mmitemID, &dim1, &dim3Key); err != nil {
|
||||
return out, err
|
||||
}
|
||||
key := fmt.Sprintf("%d|%d|%d", mmitemID, dim1, dim3Key)
|
||||
out[key] = struct{}{}
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
codes := setToSortedSlice(codeSet)
|
||||
mmitemByCode, _ := loadMmitemIDs(ctx, pg, codes)
|
||||
dim1ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval1", setToSortedSlice(colorSet))
|
||||
dim3ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval3", setToSortedSlice(dim3Set))
|
||||
mmitemIDs := make([]int64, 0, len(mmitemByCode))
|
||||
for _, c := range codes {
|
||||
@@ -662,7 +604,12 @@ GROUP BY mmitem_id, val1, CASE WHEN val3 IS NOT NULL AND val3 > 1000 THEN val3 E
|
||||
mmitemIDs = append(mmitemIDs, id)
|
||||
}
|
||||
}
|
||||
combos, _ := loadProductDimCombos(ctx, pg, mmitemIDs)
|
||||
colorTokens := setToSortedSlice(colorSet)
|
||||
for i := range colorTokens {
|
||||
colorTokens[i] = strings.ToUpper(strings.TrimSpace(colorTokens[i]))
|
||||
}
|
||||
dim1ByMmitemToken, _ := loadDim1ByMmitemAndToken(ctx, pg, mmitemIDs, colorTokens)
|
||||
combos, _ := loadProductDimCombosFromMmitemDim(ctx, pg, mmitemIDs)
|
||||
|
||||
// Per-request cache for per-mmitem dimval3 inference (dfblob scan).
|
||||
inferCache := map[string]int64{}
|
||||
@@ -728,7 +675,7 @@ LIMIT 1
|
||||
continue
|
||||
}
|
||||
row.MmitemID = mmitemByCode[row.ProductCode]
|
||||
row.Dim1ID = dim1ByToken[row.ColorCode]
|
||||
row.Dim1ID = dim1ByMmitemToken[fmt.Sprintf("%d|%s", row.MmitemID, strings.ToUpper(strings.TrimSpace(row.ColorCode)))]
|
||||
if row.Dim3Code != "" {
|
||||
if v := dim3ByToken[row.Dim3Code]; v > 0 {
|
||||
row.Dim3ID = v
|
||||
@@ -750,7 +697,7 @@ LIMIT 1
|
||||
case row.MmitemID <= 0:
|
||||
row.MappingWarning = "B2B'de urun yok (mmitem)"
|
||||
case row.Dim1ID <= 0:
|
||||
row.MappingWarning = "B2B'de renk token eslesmesi yok (mk_dim_token_map: dimval1)"
|
||||
row.MappingWarning = "B2B'de renk bu urunde yok (mmitem_dim/dfgrp.code)"
|
||||
case row.Dim3Code != "" && row.Dim3ID <= 0:
|
||||
row.MappingWarning = "B2B'de dim3 token eslesmesi yok (mk_dim_token_map: dimval3)"
|
||||
case !comboOK:
|
||||
@@ -798,6 +745,68 @@ func productSeriesTotalQtyByCode(rows []productSeriesMappingRow) map[string]floa
|
||||
return out
|
||||
}
|
||||
|
||||
func loadDim1ByMmitemAndToken(ctx context.Context, pg *sql.DB, mmitemIDs []int64, colorTokens []string) (map[string]int64, error) {
|
||||
out := map[string]int64{}
|
||||
if len(mmitemIDs) == 0 || len(colorTokens) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
rows, err := pg.QueryContext(ctx, `
|
||||
SELECT md.mmitem_id, UPPER(BTRIM(d.code)) AS code, md.val1
|
||||
FROM mmitem_dim md
|
||||
JOIN dfgrp d ON d.id = md.val1
|
||||
WHERE md.mmitem_id = ANY($1)
|
||||
AND md.is_active = TRUE
|
||||
AND UPPER(BTRIM(d.code)) = ANY($2)
|
||||
GROUP BY md.mmitem_id, UPPER(BTRIM(d.code)), md.val1
|
||||
`, pq.Array(mmitemIDs), pq.Array(colorTokens))
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var mmid, dim1 int64
|
||||
var code string
|
||||
if err := rows.Scan(&mmid, &code, &dim1); err != nil {
|
||||
return out, err
|
||||
}
|
||||
key := fmt.Sprintf("%d|%s", mmid, strings.TrimSpace(code))
|
||||
if _, ok := out[key]; !ok && dim1 > 0 {
|
||||
out[key] = dim1
|
||||
}
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func loadProductDimCombosFromMmitemDim(ctx context.Context, pg *sql.DB, mmitemIDs []int64) (map[string]struct{}, error) {
|
||||
out := map[string]struct{}{}
|
||||
if len(mmitemIDs) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
rows, err := pg.QueryContext(ctx, `
|
||||
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 mmitemID, dim1, dim3Key int64
|
||||
if err := rows.Scan(&mmitemID, &dim1, &dim3Key); err != nil {
|
||||
return out, err
|
||||
}
|
||||
out[fmt.Sprintf("%d|%d|%d", mmitemID, dim1, dim3Key)] = struct{}{}
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
type saveProductSeriesMappingsRequest struct {
|
||||
Items []struct {
|
||||
ProductCode string `json:"product_code"`
|
||||
@@ -855,10 +864,27 @@ func PostProductSeriesMappingsSaveHandler(pg *sql.DB) http.HandlerFunc {
|
||||
http.Error(w, "PG urun bulunamadi: "+code, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
dim1ID, err := resolveDimTokenIDTx(ctx, tx, "dimval1", color)
|
||||
if err != nil || dim1ID <= 0 {
|
||||
http.Error(w, "Renk token eslesmesi bulunamadi: "+color, http.StatusBadRequest)
|
||||
return
|
||||
// Authoritative dim1 resolver: only allow saving against a color that exists for this product in mmitem_dim.
|
||||
var dim1ID int64
|
||||
{
|
||||
tok := strings.ToUpper(strings.TrimSpace(color))
|
||||
if tok == "" || tok == "0" {
|
||||
http.Error(w, "Renk token eslesmesi bulunamadi: "+color, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := tx.QueryRowContext(ctx, `
|
||||
SELECT md.val1
|
||||
FROM mmitem_dim md
|
||||
JOIN dfgrp d ON d.id = md.val1
|
||||
WHERE md.mmitem_id = $1
|
||||
AND md.is_active = TRUE
|
||||
AND UPPER(BTRIM(d.code)) = $2
|
||||
GROUP BY md.val1
|
||||
LIMIT 1
|
||||
`, mmitemID, tok).Scan(&dim1ID); err != nil || dim1ID <= 0 {
|
||||
http.Error(w, "Renk bu urunde yok: "+color, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
var dim3ID sql.NullInt64
|
||||
if dim3Token != "" {
|
||||
|
||||
Reference in New Issue
Block a user