Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -258,16 +258,40 @@ norm AS (
|
|||||||
COALESCE(price, 0) AS price
|
COALESCE(price, 0) AS price
|
||||||
FROM input
|
FROM input
|
||||||
),
|
),
|
||||||
dims_cache AS (
|
-- Prefer PG's authoritative variant dimension table (mmitem_dim). Fall back to cache table if needed.
|
||||||
|
dims_mmitem_dim AS (
|
||||||
|
SELECT
|
||||||
|
norm.product_code AS product_code,
|
||||||
|
md.val1::bigint AS dim1,
|
||||||
|
CASE
|
||||||
|
WHEN md.val2 IS NULL OR md.val2 = 0 THEN NULL
|
||||||
|
ELSE md.val2::bigint
|
||||||
|
END AS dim3
|
||||||
|
FROM norm
|
||||||
|
JOIN mmitem mm
|
||||||
|
ON mm.code = norm.product_code
|
||||||
|
JOIN mmitem_dim md
|
||||||
|
ON md.mmitem_id = mm.id
|
||||||
|
AND COALESCE(md.is_active, TRUE) = TRUE
|
||||||
|
WHERE md.val1 IS NOT NULL
|
||||||
|
AND md.val1 > 0
|
||||||
|
GROUP BY norm.product_code, md.val1, md.val2
|
||||||
|
),
|
||||||
|
dims_cache_table AS (
|
||||||
SELECT
|
SELECT
|
||||||
NULLIF(BTRIM(c.product_code), '') AS product_code,
|
NULLIF(BTRIM(c.product_code), '') AS product_code,
|
||||||
c.dim1,
|
c.dim1::bigint AS dim1,
|
||||||
c.dim3
|
c.dim3::bigint AS dim3
|
||||||
FROM mk_mmitem_dim_combo c
|
FROM mk_mmitem_dim_combo c
|
||||||
JOIN norm
|
JOIN norm
|
||||||
ON norm.product_code = c.product_code
|
ON norm.product_code = c.product_code
|
||||||
WHERE c.dim1 IS NOT NULL
|
WHERE c.dim1 IS NOT NULL
|
||||||
),
|
),
|
||||||
|
dims_cache AS (
|
||||||
|
SELECT product_code, dim1, dim3 FROM dims_mmitem_dim
|
||||||
|
UNION
|
||||||
|
SELECT product_code, dim1, dim3 FROM dims_cache_table
|
||||||
|
),
|
||||||
dims_sdprc AS (
|
dims_sdprc AS (
|
||||||
SELECT
|
SELECT
|
||||||
norm.product_code AS product_code,
|
norm.product_code AS product_code,
|
||||||
|
|||||||
@@ -591,18 +591,17 @@ DO UPDATE SET dim_id = EXCLUDED.dim_id, updated_at = EXCLUDED.updated_at
|
|||||||
if err := rows.Scan(&colorCode, &dim1Code, &dim3Code); err != nil {
|
if err := rows.Scan(&colorCode, &dim1Code, &dim3Code); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Resolve to PG dim ids (e-commerce expects integer ids, e.g. dim1=82, dim3=2182).
|
// Resolve to PG dim ids. For this installation we align with PG's authoritative mmitem_dim model:
|
||||||
// Nebim varies by installation; we try a few fallbacks. In most setups:
|
// - dim1 corresponds to mmitem_dim.val1 (typically Color).
|
||||||
// - dim1 is size (ItemDim1Code)
|
// - dim3 corresponds to mmitem_dim.val2 (typically Size).
|
||||||
// - dim3 is color (ColorCode)
|
|
||||||
d1 := int64(0)
|
d1 := int64(0)
|
||||||
if id, ok := resolveDimvalFromToken(pgTx, "dimval1", dim1Code); ok {
|
if id, ok := resolveDimvalFromToken(pgTx, "dimval1", colorCode); ok {
|
||||||
d1 = id
|
d1 = id
|
||||||
resolvedDim1++
|
resolvedDim1++
|
||||||
} else if id, ok := resolveDimvalFromToken(pgTx, "dimval1", dim3Code); ok {
|
} else if id, ok := resolveDimvalFromToken(pgTx, "dimval1", dim3Code); ok {
|
||||||
d1 = id
|
d1 = id
|
||||||
resolvedDim1++
|
resolvedDim1++
|
||||||
} else if id, ok := resolveDimvalFromToken(pgTx, "dimval1", colorCode); ok {
|
} else if id, ok := resolveDimvalFromToken(pgTx, "dimval1", dim1Code); ok {
|
||||||
d1 = id
|
d1 = id
|
||||||
resolvedDim1++
|
resolvedDim1++
|
||||||
}
|
}
|
||||||
@@ -610,12 +609,11 @@ DO UPDATE SET dim_id = EXCLUDED.dim_id, updated_at = EXCLUDED.updated_at
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var d3 sql.NullInt64
|
var d3 sql.NullInt64
|
||||||
// IMPORTANT: In this Nebim setup, both ItemDim1Code and ItemDim3Code are mapped in PG as dimval1 ids.
|
// dim3 corresponds to mmitem_dim.val2 (usually ItemDim1Code).
|
||||||
// We therefore resolve dim3 via dimval1 as well (not dimval3).
|
if id, ok := resolveDimvalFromToken(pgTx, "dimval1", dim1Code); ok {
|
||||||
if id, ok := resolveDimvalFromToken(pgTx, "dimval1", dim3Code); ok {
|
|
||||||
d3 = sql.NullInt64{Int64: id, Valid: true}
|
d3 = sql.NullInt64{Int64: id, Valid: true}
|
||||||
resolvedDim3++
|
resolvedDim3++
|
||||||
} else if id, ok := resolveDimvalFromToken(pgTx, "dimval1", colorCode); ok {
|
} else if id, ok := resolveDimvalFromToken(pgTx, "dimval1", dim3Code); ok {
|
||||||
d3 = sql.NullInt64{Int64: id, Valid: true}
|
d3 = sql.NullInt64{Int64: id, Valid: true}
|
||||||
resolvedDim3++
|
resolvedDim3++
|
||||||
}
|
}
|
||||||
@@ -645,6 +643,119 @@ DO UPDATE SET dim_id = EXCLUDED.dim_id, updated_at = EXCLUDED.updated_at
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensureMMItemDimRows := func(mmItemID int64, combos []dimCombo, extraVal3 map[string]int64) {
|
||||||
|
if mmItemID <= 0 || len(combos) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Best-effort: don't assume a specific unique constraint exists on mmitem_dim.
|
||||||
|
for _, c := range combos {
|
||||||
|
if c.Dim1 <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v2 := int64(0)
|
||||||
|
var v2any any = nil
|
||||||
|
if c.Dim3.Valid && c.Dim3.Int64 > 0 {
|
||||||
|
v2 = c.Dim3.Int64
|
||||||
|
v2any = v2
|
||||||
|
}
|
||||||
|
// If we managed to resolve an "ItemDim3Code" id too, store it in val3 and mark mmdim_id=3.
|
||||||
|
v3 := int64(0)
|
||||||
|
if extraVal3 != nil {
|
||||||
|
if vv, ok := extraVal3[fmt.Sprintf("%d|%d", c.Dim1, v2)]; ok && vv > 0 {
|
||||||
|
v3 = vv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mmdimID := int64(2)
|
||||||
|
var v3any any = nil
|
||||||
|
if v3 > 0 {
|
||||||
|
mmdimID = 3
|
||||||
|
v3any = v3
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = pgTx.ExecContext(ctx, `
|
||||||
|
INSERT INTO mmitem_dim (mmitem_id, mmdim_id, val1, val2, val3, is_active, qty)
|
||||||
|
SELECT $1, $2, $3, $4, $5, TRUE, 0
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM mmitem_dim
|
||||||
|
WHERE mmitem_id = $1
|
||||||
|
AND mmdim_id = $2
|
||||||
|
AND val1 = $3
|
||||||
|
AND COALESCE(val2, 0) = COALESCE($4::bigint, 0)
|
||||||
|
AND COALESCE(val3, 0) = COALESCE($5::bigint, 0)
|
||||||
|
LIMIT 1
|
||||||
|
);
|
||||||
|
`, mmItemID, mmdimID, c.Dim1, v2any, v3any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadDimsFromPgMMItemDim := func(mmItemID int64, productCode string) ([]dimCombo, error) {
|
||||||
|
started := time.Now()
|
||||||
|
if mmItemID <= 0 {
|
||||||
|
return nil, fmt.Errorf("invalid mmitem_id")
|
||||||
|
}
|
||||||
|
rows, err := pgTx.QueryContext(ctx, `
|
||||||
|
SELECT mmdim_id, val1, val2, val3
|
||||||
|
FROM mmitem_dim
|
||||||
|
WHERE mmitem_id = $1
|
||||||
|
AND COALESCE(is_active, TRUE) = TRUE
|
||||||
|
`, mmItemID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
out := make([]dimCombo, 0, 64)
|
||||||
|
seen := make(map[string]struct{}, 128)
|
||||||
|
readRows := 0
|
||||||
|
for rows.Next() {
|
||||||
|
readRows++
|
||||||
|
var mmdimID sql.NullInt64
|
||||||
|
var v1 sql.NullInt64
|
||||||
|
var v2 sql.NullInt64
|
||||||
|
var v3 sql.NullInt64
|
||||||
|
if err := rows.Scan(&mmdimID, &v1, &v2, &v3); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !v1.Valid || v1.Int64 <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
d1 := v1.Int64
|
||||||
|
|
||||||
|
var d3 sql.NullInt64
|
||||||
|
// In production data we've observed the variant key as (val1, val2). val3 may exist but is not
|
||||||
|
// used as the second axis for pricing/campaign matching in this app.
|
||||||
|
_ = mmdimID
|
||||||
|
_ = v3
|
||||||
|
if v2.Valid && v2.Int64 > 0 {
|
||||||
|
d3 = sql.NullInt64{Int64: v2.Int64, Valid: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
key := fmt.Sprintf("%d|%d", d1, func() int64 {
|
||||||
|
if d3.Valid {
|
||||||
|
return d3.Int64
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}())
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
out = append(out, dimCombo{Dim1: d1, Dim3: d3})
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
logger.Info("save:pg:dims:mmitem-dim:loaded",
|
||||||
|
"product_code", strings.TrimSpace(productCode),
|
||||||
|
"mmitem_id", mmItemID,
|
||||||
|
"rows_read", readRows,
|
||||||
|
"dims", len(out),
|
||||||
|
"duration_ms", time.Since(started).Milliseconds(),
|
||||||
|
)
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
upsertDimCombosCache := func(productCode string, dims []dimCombo) error {
|
upsertDimCombosCache := func(productCode string, dims []dimCombo) error {
|
||||||
productCode = strings.TrimSpace(productCode)
|
productCode = strings.TrimSpace(productCode)
|
||||||
if productCode == "" || len(dims) == 0 {
|
if productCode == "" || len(dims) == 0 {
|
||||||
@@ -983,9 +1094,17 @@ VALUES (
|
|||||||
mmItemID = 0
|
mmItemID = 0
|
||||||
}
|
}
|
||||||
dims := []dimCombo{}
|
dims := []dimCombo{}
|
||||||
// Prefer cached dim combos (fast). If not present, load from Nebim stock query (used by product-stock-query UI).
|
// Prefer PG's own authoritative dim combo table (mmitem_dim). Cache is only a fast-path fallback.
|
||||||
if mmItemID > 0 {
|
if mmItemID > 0 {
|
||||||
|
// 1) Authoritative source: mmitem_dim (PG).
|
||||||
|
if d, err := loadDimsFromPgMMItemDim(mmItemID, code); err == nil && len(d) > 0 {
|
||||||
|
dims = d
|
||||||
|
_ = upsertDimCombosCache(code, dims) // best-effort cache fill
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Cache fallback (fast).
|
||||||
cacheStarted := time.Now()
|
cacheStarted := time.Now()
|
||||||
|
if len(dims) == 0 {
|
||||||
cached, cacheErr := loadDimCombosFromCache(code)
|
cached, cacheErr := loadDimCombosFromCache(code)
|
||||||
if cacheErr == nil && len(cached) > 0 {
|
if cacheErr == nil && len(cached) > 0 {
|
||||||
dims = cached
|
dims = cached
|
||||||
@@ -1002,16 +1121,18 @@ VALUES (
|
|||||||
"duration_ms", time.Since(cacheStarted).Milliseconds(),
|
"duration_ms", time.Since(cacheStarted).Milliseconds(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Last resort: MSSQL stock tokens (legacy).
|
||||||
if len(dims) == 0 {
|
if len(dims) == 0 {
|
||||||
d, err := loadDimsFromMssqlStock(code)
|
d, err := loadDimsFromMssqlStock(code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("save:pg:dims:mssql:error", "product_code", code, "err", err)
|
logger.Error("save:pg:dims:mssql:error", "product_code", code, "err", err)
|
||||||
} else {
|
} else {
|
||||||
dims = d
|
dims = d
|
||||||
if err := upsertDimCombosCache(code, dims); err != nil {
|
_ = upsertDimCombosCache(code, dims)
|
||||||
logger.Error("save:pg:dims:cache:error", "product_code", code, "err", err)
|
// If PG doesn't have mmitem_dim rows for this product yet, try to seed them.
|
||||||
}
|
ensureMMItemDimRows(mmItemID, dims, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -642,6 +642,7 @@ func GetWholesaleCampaignVariantRowsHandler(pg *sql.DB, mssql *sql.DB) http.Hand
|
|||||||
|
|
||||||
// Resolve mmitem ids in bulk.
|
// Resolve mmitem ids in bulk.
|
||||||
codeToItem := make(map[string]int64, len(codes))
|
codeToItem := make(map[string]int64, len(codes))
|
||||||
|
itemToCode := make(map[int64]string, len(codes))
|
||||||
{
|
{
|
||||||
rows, err := pg.QueryContext(ctx, `
|
rows, err := pg.QueryContext(ctx, `
|
||||||
SELECT code, id
|
SELECT code, id
|
||||||
@@ -663,6 +664,7 @@ WHERE code = ANY($1::text[])
|
|||||||
c = strings.TrimSpace(c)
|
c = strings.TrimSpace(c)
|
||||||
if c != "" && id > 0 {
|
if c != "" && id > 0 {
|
||||||
codeToItem[c] = id
|
codeToItem[c] = id
|
||||||
|
itemToCode[id] = c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rows.Close()
|
rows.Close()
|
||||||
@@ -727,15 +729,6 @@ DO UPDATE SET dim_id = EXCLUDED.dim_id, updated_at = EXCLUDED.updated_at
|
|||||||
return id, true
|
return id, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// MSSQL: variant+stock list for selected products.
|
|
||||||
joined := strings.Join(codes, ",")
|
|
||||||
msRows, err := mssql.QueryContext(ctx, queries.GetWholesaleCampaignVariantStockByProducts, joined)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "variant stock query error: "+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer msRows.Close()
|
|
||||||
|
|
||||||
type tmpRow struct {
|
type tmpRow struct {
|
||||||
ProductCode string
|
ProductCode string
|
||||||
VariantCode string
|
VariantCode string
|
||||||
@@ -744,8 +737,138 @@ DO UPDATE SET dim_id = EXCLUDED.dim_id, updated_at = EXCLUDED.updated_at
|
|||||||
Dim1 int64
|
Dim1 int64
|
||||||
Dim3Key int64
|
Dim3Key int64
|
||||||
}
|
}
|
||||||
// Deduplicate by (mmitem_id, dim1, dim3_key) and aggregate stock qty.
|
|
||||||
|
// Build base variant keys from PG's authoritative table (mmitem_dim).
|
||||||
|
itemIDs := make([]int64, 0, len(codeToItem))
|
||||||
|
for _, id := range codeToItem {
|
||||||
|
itemIDs = append(itemIDs, id)
|
||||||
|
}
|
||||||
tmpMap := make(map[string]tmpRow, 4096)
|
tmpMap := make(map[string]tmpRow, 4096)
|
||||||
|
hasMMItemDim := make(map[int64]bool, len(itemIDs))
|
||||||
|
dimIDs := make([]int64, 0, 8192)
|
||||||
|
if len(itemIDs) > 0 {
|
||||||
|
rows, err := pg.QueryContext(ctx, `
|
||||||
|
SELECT mmitem_id, mmdim_id, val1, val2, val3
|
||||||
|
FROM mmitem_dim
|
||||||
|
WHERE mmitem_id = ANY($1::bigint[])
|
||||||
|
AND COALESCE(is_active, TRUE) = TRUE
|
||||||
|
`, pq.Array(itemIDs))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "mmitem_dim lookup error: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for rows.Next() {
|
||||||
|
var itemID int64
|
||||||
|
var mmdimID sql.NullInt64
|
||||||
|
var v1 sql.NullInt64
|
||||||
|
var v2 sql.NullInt64
|
||||||
|
var v3 sql.NullInt64
|
||||||
|
if err := rows.Scan(&itemID, &mmdimID, &v1, &v2, &v3); err != nil {
|
||||||
|
rows.Close()
|
||||||
|
http.Error(w, "mmitem_dim scan error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hasMMItemDim[itemID] = true
|
||||||
|
if !v1.Valid || v1.Int64 <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
d1 := v1.Int64
|
||||||
|
|
||||||
|
d3k := int64(0)
|
||||||
|
// Variant key in this app is (val1, val2). val3 may exist but is not the second axis for matching.
|
||||||
|
_ = mmdimID
|
||||||
|
_ = v3
|
||||||
|
if v2.Valid && v2.Int64 > 0 {
|
||||||
|
d3k = v2.Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
code := strings.TrimSpace(itemToCode[itemID])
|
||||||
|
if code == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := fmt.Sprintf("%d|%d|%d", itemID, d1, d3k)
|
||||||
|
if _, ok := tmpMap[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tmpMap[key] = tmpRow{
|
||||||
|
ProductCode: code,
|
||||||
|
VariantCode: "",
|
||||||
|
StockQty: 0,
|
||||||
|
ItemID: itemID,
|
||||||
|
Dim1: d1,
|
||||||
|
Dim3Key: d3k,
|
||||||
|
}
|
||||||
|
dimIDs = append(dimIDs, d1)
|
||||||
|
if d3k > 0 {
|
||||||
|
dimIDs = append(dimIDs, d3k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve dim ids -> tokens for a readable VariantCode.
|
||||||
|
idToToken := map[int64]string{}
|
||||||
|
if len(dimIDs) > 0 {
|
||||||
|
// uniq
|
||||||
|
uniq := make([]int64, 0, len(dimIDs))
|
||||||
|
seen := make(map[int64]struct{}, len(dimIDs))
|
||||||
|
for _, id := range dimIDs {
|
||||||
|
if id <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := seen[id]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[id] = struct{}{}
|
||||||
|
uniq = append(uniq, id)
|
||||||
|
}
|
||||||
|
if len(uniq) > 0 {
|
||||||
|
rows, err := pg.QueryContext(ctx, `
|
||||||
|
SELECT DISTINCT ON (dim_id) dim_id, token
|
||||||
|
FROM mk_dim_token_map
|
||||||
|
WHERE dim_column = 'dimval1'
|
||||||
|
AND dim_id = ANY($1::bigint[])
|
||||||
|
ORDER BY dim_id, updated_at DESC;
|
||||||
|
`, pq.Array(uniq))
|
||||||
|
if err == nil {
|
||||||
|
for rows.Next() {
|
||||||
|
var id int64
|
||||||
|
var tok string
|
||||||
|
_ = rows.Scan(&id, &tok)
|
||||||
|
tok = strings.TrimSpace(tok)
|
||||||
|
if tok != "" {
|
||||||
|
idToToken[id] = tok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range tmpMap {
|
||||||
|
t1 := strings.TrimSpace(idToToken[v.Dim1])
|
||||||
|
if t1 == "" {
|
||||||
|
t1 = fmt.Sprintf("%d", v.Dim1)
|
||||||
|
}
|
||||||
|
if v.Dim3Key > 0 {
|
||||||
|
t3 := strings.TrimSpace(idToToken[v.Dim3Key])
|
||||||
|
if t3 == "" {
|
||||||
|
t3 = fmt.Sprintf("%d", v.Dim3Key)
|
||||||
|
}
|
||||||
|
v.VariantCode = t1 + "-" + t3
|
||||||
|
} else {
|
||||||
|
v.VariantCode = t1
|
||||||
|
}
|
||||||
|
tmpMap[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "variant stock query error: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer msRows.Close()
|
||||||
for msRows.Next() {
|
for msRows.Next() {
|
||||||
var itemCode, colorCode, dim1Code, dim3Code string
|
var itemCode, colorCode, dim1Code, dim3Code string
|
||||||
var qty sql.NullFloat64
|
var qty sql.NullFloat64
|
||||||
@@ -762,66 +885,84 @@ DO UPDATE SET dim_id = EXCLUDED.dim_id, updated_at = EXCLUDED.updated_at
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Variant token: prefer ColorCode; ItemDim1Code may represent a different attribute.
|
// Map Nebim tokens to PG integer ids (dimval1 namespace).
|
||||||
t1 := strings.TrimSpace(colorCode)
|
// This app uses key: dim1=<color>, dim3=<size> to match mmitem_dim (val1,val2).
|
||||||
if t1 == "" || t1 == "0" {
|
|
||||||
t1 = strings.TrimSpace(dim1Code)
|
|
||||||
}
|
|
||||||
t3 := strings.TrimSpace(dim3Code)
|
|
||||||
varCode := strings.TrimSpace(t1)
|
|
||||||
if varCode != "" && t3 != "" && t3 != "0" {
|
|
||||||
varCode = varCode + "-" + strings.TrimSpace(t3)
|
|
||||||
}
|
|
||||||
if varCode == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
d1 := int64(0)
|
d1 := int64(0)
|
||||||
// IMPORTANT: In this Nebim setup, both ItemDim1Code and ItemDim3Code are mapped in PG as dimval1 ids.
|
if id, ok := resolveDimID("dimval1", colorCode); ok {
|
||||||
// We therefore resolve both axes via dimval1 and store the second axis in dim3 (still a bigint).
|
|
||||||
if id, ok := resolveDimID("dimval1", dim1Code); ok {
|
|
||||||
d1 = id
|
|
||||||
} else if id, ok := resolveDimID("dimval1", dim3Code); ok {
|
|
||||||
d1 = id
|
|
||||||
} else if id, ok := resolveDimID("dimval1", colorCode); ok {
|
|
||||||
d1 = id
|
d1 = id
|
||||||
}
|
}
|
||||||
if d1 <= 0 {
|
if d1 <= 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
d3k := int64(0)
|
d3k := int64(0)
|
||||||
if id, ok := resolveDimID("dimval1", dim3Code); ok {
|
if id, ok := resolveDimID("dimval1", dim1Code); ok {
|
||||||
d3k = id
|
d3k = id
|
||||||
} else if id, ok := resolveDimID("dimval1", colorCode); ok {
|
|
||||||
d3k = id
|
|
||||||
}
|
|
||||||
|
|
||||||
q := 0.0
|
|
||||||
if qty.Valid {
|
|
||||||
q = qty.Float64
|
|
||||||
}
|
}
|
||||||
key := fmt.Sprintf("%d|%d|%d", itemID, d1, d3k)
|
key := fmt.Sprintf("%d|%d|%d", itemID, d1, d3k)
|
||||||
if prev, ok := tmpMap[key]; ok {
|
prev, ok := tmpMap[key]
|
||||||
prev.StockQty += q
|
if !ok {
|
||||||
// Keep the first non-empty variant code.
|
// If PG does not have mmitem_dim rows for this item yet, seed it from MSSQL and include it.
|
||||||
if prev.VariantCode == "" {
|
if !hasMMItemDim[itemID] {
|
||||||
prev.VariantCode = varCode
|
var v2 any = nil
|
||||||
|
if d3k > 0 {
|
||||||
|
v2 = d3k
|
||||||
}
|
}
|
||||||
tmpMap[key] = prev
|
v3 := int64(0)
|
||||||
} else {
|
if id, ok := resolveDimID("dimval1", dim3Code); ok {
|
||||||
|
v3 = id
|
||||||
|
}
|
||||||
|
mmdimID := int64(2)
|
||||||
|
var v3any any = nil
|
||||||
|
if v3 > 0 {
|
||||||
|
mmdimID = 3
|
||||||
|
v3any = v3
|
||||||
|
}
|
||||||
|
_, _ = pg.ExecContext(ctx, `
|
||||||
|
INSERT INTO mmitem_dim (mmitem_id, mmdim_id, val1, val2, val3, is_active, qty)
|
||||||
|
SELECT $1, $2, $3, $4, $5, TRUE, 0
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM mmitem_dim
|
||||||
|
WHERE mmitem_id = $1
|
||||||
|
AND mmdim_id = $2
|
||||||
|
AND val1 = $3
|
||||||
|
AND COALESCE(val2, 0) = COALESCE($4::bigint, 0)
|
||||||
|
AND COALESCE(val3, 0) = COALESCE($5::bigint, 0)
|
||||||
|
LIMIT 1
|
||||||
|
);
|
||||||
|
`, itemID, mmdimID, d1, v2, v3any)
|
||||||
|
hasMMItemDim[itemID] = true
|
||||||
|
|
||||||
|
code := strings.TrimSpace(itemToCode[itemID])
|
||||||
|
if code != "" {
|
||||||
tmpMap[key] = tmpRow{
|
tmpMap[key] = tmpRow{
|
||||||
ProductCode: itemCode,
|
ProductCode: code,
|
||||||
VariantCode: varCode,
|
VariantCode: "",
|
||||||
StockQty: q,
|
StockQty: 0,
|
||||||
ItemID: itemID,
|
ItemID: itemID,
|
||||||
Dim1: d1,
|
Dim1: d1,
|
||||||
Dim3Key: d3k,
|
Dim3Key: d3k,
|
||||||
}
|
}
|
||||||
|
// Keep dim token cache for VariantCode formatting.
|
||||||
|
dimIDs = append(dimIDs, d1)
|
||||||
|
if d3k > 0 {
|
||||||
|
dimIDs = append(dimIDs, d3k)
|
||||||
|
}
|
||||||
|
prev = tmpMap[key]
|
||||||
|
ok = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := msRows.Err(); err != nil {
|
if !ok {
|
||||||
http.Error(w, "variant stock read error: "+err.Error(), http.StatusInternalServerError)
|
continue
|
||||||
return
|
}
|
||||||
|
}
|
||||||
|
q := 0.0
|
||||||
|
if qty.Valid {
|
||||||
|
q = qty.Float64
|
||||||
|
}
|
||||||
|
prev.StockQty += q
|
||||||
|
tmpMap[key] = prev
|
||||||
|
_ = colorCode // display-only
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp := make([]tmpRow, 0, len(tmpMap))
|
tmp := make([]tmpRow, 0, len(tmpMap))
|
||||||
|
|||||||
Reference in New Issue
Block a user