diff --git a/svc/routes/wholesale_campaigns.go b/svc/routes/wholesale_campaigns.go index 4dc1249..9490bbb 100644 --- a/svc/routes/wholesale_campaigns.go +++ b/svc/routes/wholesale_campaigns.go @@ -774,6 +774,19 @@ DO UPDATE SET dim_id = EXCLUDED.dim_id, updated_at = EXCLUDED.updated_at hasMMItemDim := make(map[int64]bool, len(itemIDs)) dim1IDs := make([]int64, 0, 8192) dim3IDs := make([]int64, 0, 8192) + itemDim1Candidates := make(map[int64][]int64, len(itemIDs)) + itemDim3Candidates := make(map[int64][]int64, len(itemIDs)) + addCandidate := func(dst map[int64][]int64, itemID int64, id int64) { + if itemID <= 0 || id <= 0 { + return + } + for _, existing := range dst[itemID] { + if existing == id { + return + } + } + dst[itemID] = append(dst[itemID], id) + } if len(itemIDs) > 0 { rows, err := pg.QueryContext(ctx, ` SELECT mmitem_id, mmdim_id, val1, val2, val3 @@ -804,9 +817,11 @@ WHERE mmitem_id = ANY($1::bigint[]) d1 := v1.Int64 _ = mmdimID _ = v2 + addCandidate(itemDim1Candidates, itemID, d1) d3k := int64(0) if v3.Valid && v3.Int64 > 0 { d3k = v3.Int64 + addCandidate(itemDim3Candidates, itemID, d3k) } code := strings.TrimSpace(itemToCode[itemID]) @@ -927,6 +942,84 @@ LIMIT 1 return "" } + sortDimIDs := func(ids []int64) []int64 { + out := append([]int64(nil), ids...) + sort.Slice(out, func(i, j int) bool { return out[i] < out[j] }) + return out + } + sortTokens := func(tokens []string) []string { + out := append([]string(nil), tokens...) + sort.Slice(out, func(i, j int) bool { + li := strings.TrimLeft(out[i], "0") + lj := strings.TrimLeft(out[j], "0") + if li == "" { + li = "0" + } + if lj == "" { + lj = "0" + } + ni, ei := strconv.ParseInt(li, 10, 64) + nj, ej := strconv.ParseInt(lj, 10, 64) + if ei == nil && ej == nil && ni != nj { + return ni < nj + } + return out[i] < out[j] + }) + return out + } + addToken := func(dst map[int64][]string, itemID int64, token string) { + token = strings.ToUpper(normalizeDimParam(token)) + if itemID <= 0 || token == "" { + return + } + for _, existing := range dst[itemID] { + if existing == token { + return + } + } + dst[itemID] = append(dst[itemID], token) + } + buildInferredMap := func(column string, tokenByItem map[int64][]string, idsByItem map[int64][]int64) map[string]int64 { + out := make(map[string]int64, 128) + for itemID, tokens := range tokenByItem { + sortedTokens := sortTokens(tokens) + sortedIDs := sortDimIDs(idsByItem[itemID]) + if len(sortedTokens) == 0 || len(sortedTokens) != len(sortedIDs) { + continue + } + for i, token := range sortedTokens { + id := sortedIDs[i] + if token == "" || id <= 0 { + continue + } + key := column + "|" + token + if _, exists := out[key]; !exists { + out[key] = id + } + } + } + return out + } + persistDimToken := func(column string, token string, id int64) { + token = strings.ToUpper(normalizeDimParam(token)) + if column == "" || token == "" || id <= 0 { + return + } + _, _ = pg.ExecContext(ctx, ` +INSERT INTO mk_dim_token_map (dim_column, token, dim_id, updated_at) +VALUES ($1,$2,$3,now()) +ON CONFLICT (dim_column, token) +DO UPDATE SET dim_id = EXCLUDED.dim_id, updated_at = EXCLUDED.updated_at +`, column, token, id) + } + type msVariantRow struct { + ItemCode string + ColorCode string + Dim1Code string + Dim3Code string + Qty sql.NullFloat64 + } + // MSSQL: stock list for selected products; map to (mmitem_id, dim1, dim3_key) via token->id mapping. joined := strings.Join(codes, ",") msRows, err := mssql.QueryContext(ctx, queries.GetWholesaleCampaignVariantStockByProducts, joined) @@ -934,11 +1027,14 @@ LIMIT 1 http.Error(w, "variant stock query error: "+err.Error(), http.StatusInternalServerError) return } - defer msRows.Close() + msVariants := make([]msVariantRow, 0, 1024) + colorTokensByItem := make(map[int64][]string, len(itemIDs)) + dim3TokensByItem := make(map[int64][]string, len(itemIDs)) for msRows.Next() { var itemCode, colorCode, dim1Code, dim3Code string var qty sql.NullFloat64 if err := msRows.Scan(&itemCode, &colorCode, &dim1Code, &dim3Code, &qty); err != nil { + msRows.Close() http.Error(w, "variant stock scan error", http.StatusInternalServerError) return } @@ -950,18 +1046,62 @@ LIMIT 1 if itemID <= 0 { continue } + msVariants = append(msVariants, msVariantRow{ + ItemCode: itemCode, + ColorCode: colorCode, + Dim1Code: dim1Code, + Dim3Code: dim3Code, + Qty: qty, + }) + addToken(colorTokensByItem, itemID, colorCode) + addToken(dim3TokensByItem, itemID, dim3Code) + } + if err := msRows.Err(); err != nil { + msRows.Close() + http.Error(w, "variant stock rows error", http.StatusInternalServerError) + return + } + msRows.Close() + + inferredDim1 := buildInferredMap("dimval1", colorTokensByItem, itemDim1Candidates) + inferredDim3 := buildInferredMap("dimval3", dim3TokensByItem, itemDim3Candidates) + resolveProductDimID := func(column string, token string, inferred map[string]int64) (int64, bool) { + if id, ok := resolveDimID(column, token); ok { + return id, true + } + token = strings.ToUpper(normalizeDimParam(token)) + if token == "" { + return 0, false + } + if id := inferred[column+"|"+token]; id > 0 { + persistDimToken(column, token, id) + return id, true + } + return 0, false + } + + for _, ms := range msVariants { + itemCode := ms.ItemCode + colorCode := ms.ColorCode + dim1Code := ms.Dim1Code + dim3Code := ms.Dim3Code + qty := ms.Qty + itemID := codeToItem[itemCode] + if itemID <= 0 { + continue + } // Map Nebim tokens to PG integer ids. Color and yaka must use separate token namespaces, // because the same visible token (for example "001") can exist in both dimensions. d1 := int64(0) - if id, ok := resolveDimID("dimval1", colorCode); ok { + if id, ok := resolveProductDimID("dimval1", colorCode, inferredDim1); ok { d1 = id } if d1 <= 0 { continue } d3k := int64(0) - if id, ok := resolveDimID("dimval3", dim3Code); ok { + if id, ok := resolveProductDimID("dimval3", dim3Code, inferredDim3); ok { d3k = id } key := fmt.Sprintf("%d|%d|%d", itemID, d1, d3k) @@ -974,7 +1114,7 @@ LIMIT 1 v2 = sizeID } v3 := int64(0) - if id, ok := resolveDimID("dimval3", dim3Code); ok { + if id, ok := resolveProductDimID("dimval3", dim3Code, inferredDim3); ok { v3 = id } mmdimID := int64(2)