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
|
return 0, 0, sql.NullInt64{}, false, err
|
||||||
}
|
}
|
||||||
dim1ID, ok, err := productSeriesResolveDimTokenID(ctx, pg, "dimval1", colorCode, mmitemID)
|
// Authoritative dim1 resolver: for this installation, "color_code" tokens correspond to dfgrp.code on
|
||||||
if err != nil {
|
// the dim values referenced by mmitem_dim.val1. Do NOT rely on mk_dim_token_map for dimval1 because
|
||||||
return 0, 0, sql.NullInt64{}, false, err
|
// it can be polluted and conflate tokens.
|
||||||
}
|
var dim1ID int64
|
||||||
if !ok || dim1ID <= 0 {
|
{
|
||||||
return 0, 0, sql.NullInt64{}, false, nil
|
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
|
var dim3ID sql.NullInt64
|
||||||
if strings.TrimSpace(dim3Code) != "" {
|
if strings.TrimSpace(dim3Code) != "" {
|
||||||
|
|||||||
@@ -318,42 +318,10 @@ WHERE dim_column=$1 AND token = ANY($2)
|
|||||||
return out, rows.Err()
|
return out, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load authoritative combos from mmitem_dim (mmitem_id + val1(color) + val3(dim3 if any)).
|
// Authoritative dims/combos are resolved via mmitem_dim + dfgrp.code (see helpers below).
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
|
|
||||||
codes := setToSortedSlice(codeSet)
|
codes := setToSortedSlice(codeSet)
|
||||||
mmitemByCode, _ := loadMmitemIDs(ctx, pg, codes)
|
mmitemByCode, _ := loadMmitemIDs(ctx, pg, codes)
|
||||||
dim1ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval1", setToSortedSlice(colorSet))
|
|
||||||
dim3ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval3", setToSortedSlice(dim3Set))
|
dim3ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval3", setToSortedSlice(dim3Set))
|
||||||
mmitemIDs := make([]int64, 0, len(mmitemByCode))
|
mmitemIDs := make([]int64, 0, len(mmitemByCode))
|
||||||
for _, c := range codes {
|
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)
|
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)
|
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.
|
||||||
@@ -425,7 +399,7 @@ LIMIT 1
|
|||||||
out := make([]productSeriesMappingRow, 0, len(grouped))
|
out := make([]productSeriesMappingRow, 0, len(grouped))
|
||||||
for _, row := range grouped {
|
for _, row := range grouped {
|
||||||
row.MmitemID = mmitemByCode[row.ProductCode]
|
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 row.Dim3Code != "" {
|
||||||
// Prefer token map; fallback to per-item inference (do not persist).
|
// Prefer token map; fallback to per-item inference (do not persist).
|
||||||
if v := dim3ByToken[row.Dim3Code]; v > 0 {
|
if v := dim3ByToken[row.Dim3Code]; v > 0 {
|
||||||
@@ -621,40 +595,8 @@ WHERE dim_column=$1 AND token = ANY($2)
|
|||||||
return out, rows.Err()
|
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)
|
codes := setToSortedSlice(codeSet)
|
||||||
mmitemByCode, _ := loadMmitemIDs(ctx, pg, codes)
|
mmitemByCode, _ := loadMmitemIDs(ctx, pg, codes)
|
||||||
dim1ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval1", setToSortedSlice(colorSet))
|
|
||||||
dim3ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval3", setToSortedSlice(dim3Set))
|
dim3ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval3", setToSortedSlice(dim3Set))
|
||||||
mmitemIDs := make([]int64, 0, len(mmitemByCode))
|
mmitemIDs := make([]int64, 0, len(mmitemByCode))
|
||||||
for _, c := range codes {
|
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)
|
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).
|
// Per-request cache for per-mmitem dimval3 inference (dfblob scan).
|
||||||
inferCache := map[string]int64{}
|
inferCache := map[string]int64{}
|
||||||
@@ -728,7 +675,7 @@ LIMIT 1
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
row.MmitemID = mmitemByCode[row.ProductCode]
|
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 row.Dim3Code != "" {
|
||||||
if v := dim3ByToken[row.Dim3Code]; v > 0 {
|
if v := dim3ByToken[row.Dim3Code]; v > 0 {
|
||||||
row.Dim3ID = v
|
row.Dim3ID = v
|
||||||
@@ -750,7 +697,7 @@ LIMIT 1
|
|||||||
case row.MmitemID <= 0:
|
case row.MmitemID <= 0:
|
||||||
row.MappingWarning = "B2B'de urun yok (mmitem)"
|
row.MappingWarning = "B2B'de urun yok (mmitem)"
|
||||||
case row.Dim1ID <= 0:
|
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:
|
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:
|
||||||
@@ -798,6 +745,68 @@ func productSeriesTotalQtyByCode(rows []productSeriesMappingRow) map[string]floa
|
|||||||
return out
|
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 {
|
type saveProductSeriesMappingsRequest struct {
|
||||||
Items []struct {
|
Items []struct {
|
||||||
ProductCode string `json:"product_code"`
|
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)
|
http.Error(w, "PG urun bulunamadi: "+code, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dim1ID, err := resolveDimTokenIDTx(ctx, tx, "dimval1", color)
|
// Authoritative dim1 resolver: only allow saving against a color that exists for this product in mmitem_dim.
|
||||||
if err != nil || dim1ID <= 0 {
|
var dim1ID int64
|
||||||
http.Error(w, "Renk token eslesmesi bulunamadi: "+color, http.StatusBadRequest)
|
{
|
||||||
return
|
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
|
var dim3ID sql.NullInt64
|
||||||
if dim3Token != "" {
|
if dim3Token != "" {
|
||||||
|
|||||||
Reference in New Issue
Block a user