Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -621,27 +621,147 @@ func productSeriesResolvePGVariant(ctx context.Context, pg *sql.DB, productCode,
|
|||||||
}
|
}
|
||||||
return 0, 0, sql.NullInt64{}, false, err
|
return 0, 0, sql.NullInt64{}, false, err
|
||||||
}
|
}
|
||||||
var dim1ID int64
|
dim1ID, ok, err := productSeriesResolveDimTokenID(ctx, pg, "dimval1", colorCode, mmitemID)
|
||||||
if err := pg.QueryRowContext(ctx, `SELECT dim_id FROM mk_dim_token_map WHERE dim_column='dimval1' AND token=$1`, strings.TrimSpace(colorCode)).Scan(&dim1ID); err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return 0, 0, sql.NullInt64{}, false, nil
|
|
||||||
}
|
|
||||||
return 0, 0, sql.NullInt64{}, false, err
|
return 0, 0, sql.NullInt64{}, false, err
|
||||||
}
|
}
|
||||||
|
if !ok || 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) != "" {
|
||||||
var id int64
|
id, ok, err := productSeriesResolveDimTokenID(ctx, pg, "dimval3", dim3Code, mmitemID)
|
||||||
if err := pg.QueryRowContext(ctx, `SELECT dim_id FROM mk_dim_token_map WHERE dim_column='dimval3' AND token=$1`, strings.TrimSpace(dim3Code)).Scan(&id); err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return 0, 0, sql.NullInt64{}, false, nil
|
|
||||||
}
|
|
||||||
return 0, 0, sql.NullInt64{}, false, err
|
return 0, 0, sql.NullInt64{}, false, err
|
||||||
}
|
}
|
||||||
|
if !ok || id <= 0 {
|
||||||
|
return 0, 0, sql.NullInt64{}, false, nil
|
||||||
|
}
|
||||||
dim3ID = sql.NullInt64{Int64: id, Valid: true}
|
dim3ID = sql.NullInt64{Int64: id, Valid: true}
|
||||||
}
|
}
|
||||||
return mmitemID, dim1ID, dim3ID, true, nil
|
return mmitemID, dim1ID, dim3ID, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func productSeriesResolveDimTokenID(ctx context.Context, pg *sql.DB, column string, token string, mmitemID int64) (int64, bool, error) {
|
||||||
|
tok := strings.ToUpper(strings.TrimSpace(token))
|
||||||
|
if tok == "" || tok == "0" {
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dimval3 tokens like "001" can map to different dim ids per product in this installation.
|
||||||
|
// Prefer per-mmitem inference from dfblob (src_id filter) to avoid global mk_dim_token_map mismatches.
|
||||||
|
if column == "dimval3" && mmitemID > 0 {
|
||||||
|
if inferred, ok := productSeriesInferDimIDFromImages(pg, mmitemID, column, tok); ok {
|
||||||
|
return inferred, true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var id int64
|
||||||
|
err := pg.QueryRowContext(ctx, `SELECT dim_id FROM mk_dim_token_map WHERE dim_column=$1 AND token=$2`, column, tok).Scan(&id)
|
||||||
|
if err == nil {
|
||||||
|
return id, id > 0, nil
|
||||||
|
}
|
||||||
|
if err != sql.ErrNoRows {
|
||||||
|
return 0, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: infer from dfblob filenames. For dimval3 do not persist globally.
|
||||||
|
if mmitemID > 0 {
|
||||||
|
if inferred, ok := productSeriesInferDimIDFromImages(pg, mmitemID, column, tok); ok {
|
||||||
|
return inferred, true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v := productSeriesResolveDimvalFromFileNameToken(pg, column, tok, 0)
|
||||||
|
if v == "" {
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
parsed, perr := strconv.ParseInt(v, 10, 64)
|
||||||
|
if perr != nil || parsed <= 0 {
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
if column == "dimval1" {
|
||||||
|
// Persist only for dimval1 where tokens are globally stable.
|
||||||
|
_, _ = 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, tok, parsed)
|
||||||
|
}
|
||||||
|
return parsed, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func productSeriesBuildNameLikePatterns(token string) []string {
|
||||||
|
t := strings.ToUpper(strings.TrimSpace(token))
|
||||||
|
if t == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return []string{
|
||||||
|
"% " + t + " %",
|
||||||
|
"%-" + t + "-%",
|
||||||
|
"%-" + t + "_%",
|
||||||
|
"%_" + t + "_%",
|
||||||
|
"%(" + t + ")%",
|
||||||
|
t + " %",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productSeriesResolveDimvalFromFileNameToken(pg *sql.DB, column, token string, mmitemID int64) string {
|
||||||
|
patterns := productSeriesBuildNameLikePatterns(token)
|
||||||
|
if len(patterns) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
srcFilter := ""
|
||||||
|
args := []any{patterns[0], patterns[1], patterns[2], patterns[3], patterns[4], patterns[5]}
|
||||||
|
if mmitemID > 0 {
|
||||||
|
srcFilter = " AND src_id=$7"
|
||||||
|
args = append(args, mmitemID)
|
||||||
|
}
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
SELECT x.dimv
|
||||||
|
FROM (
|
||||||
|
SELECT COALESCE(%s::text, '') AS dimv, COUNT(*) AS cnt
|
||||||
|
FROM dfblob
|
||||||
|
WHERE src_table='mmitem'
|
||||||
|
AND typ='img'
|
||||||
|
AND COALESCE(%s::text, '') <> ''
|
||||||
|
%s
|
||||||
|
AND (
|
||||||
|
UPPER(COALESCE(file_name,'')) LIKE $1 OR
|
||||||
|
UPPER(COALESCE(file_name,'')) LIKE $2 OR
|
||||||
|
UPPER(COALESCE(file_name,'')) LIKE $3 OR
|
||||||
|
UPPER(COALESCE(file_name,'')) LIKE $4 OR
|
||||||
|
UPPER(COALESCE(file_name,'')) LIKE $5 OR
|
||||||
|
UPPER(COALESCE(file_name,'')) LIKE $6
|
||||||
|
)
|
||||||
|
GROUP BY COALESCE(%s::text, '')
|
||||||
|
) x
|
||||||
|
ORDER BY x.cnt DESC, x.dimv
|
||||||
|
LIMIT 1
|
||||||
|
`, column, column, srcFilter, column)
|
||||||
|
var v string
|
||||||
|
if err := pg.QueryRow(query, args...).Scan(&v); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" || v == "0" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func productSeriesInferDimIDFromImages(pg *sql.DB, mmitemID int64, column, token string) (int64, bool) {
|
||||||
|
v := productSeriesResolveDimvalFromFileNameToken(pg, column, token, mmitemID)
|
||||||
|
if v == "" {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
id, err := strconv.ParseInt(v, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return id, true
|
||||||
|
}
|
||||||
|
|
||||||
func productSeriesClaimQueue(ctx context.Context, tx *sql.Tx, limit int) ([]productSeriesQueueItem, error) {
|
func productSeriesClaimQueue(ctx context.Context, tx *sql.Tx, limit int) ([]productSeriesQueueItem, error) {
|
||||||
rows, err := tx.QueryContext(ctx, `
|
rows, err := tx.QueryContext(ctx, `
|
||||||
WITH picked AS (
|
WITH picked AS (
|
||||||
|
|||||||
@@ -297,12 +297,76 @@ func GetProductSeriesMappingsHandler(pg *sql.DB) http.HandlerFunc {
|
|||||||
dim3ByToken, _ := loadDimTokenIDs(ctx, pg, "dimval3", setToSortedSlice(dim3Set))
|
dim3ByToken, _ := loadDimTokenIDs(ctx, pg, "dimval3", setToSortedSlice(dim3Set))
|
||||||
existing, _ := loadProductSeriesAssignments(ctx, pg, codes)
|
existing, _ := loadProductSeriesAssignments(ctx, pg, codes)
|
||||||
|
|
||||||
|
// Per-request cache for per-mmitem dimval3 inference to avoid repeated dfblob scans.
|
||||||
|
inferCache := map[string]int64{}
|
||||||
|
inferDim3ForMmitem := func(mmitemID int64, token string) int64 {
|
||||||
|
mmitemID = int64(mmitemID)
|
||||||
|
tok := strings.ToUpper(normalizeDimParam(token))
|
||||||
|
if mmitemID <= 0 || tok == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
key := fmt.Sprintf("%d|%s", mmitemID, tok)
|
||||||
|
if v, ok := inferCache[key]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
// Use the same pattern approach as resolveDimvalFromFileNameToken, but scoped to src_id.
|
||||||
|
patterns := buildNameLikePatterns(tok)
|
||||||
|
if len(patterns) == 0 {
|
||||||
|
inferCache[key] = 0
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var dimv string
|
||||||
|
err := pg.QueryRowContext(ctx, `
|
||||||
|
SELECT x.dimv
|
||||||
|
FROM (
|
||||||
|
SELECT COALESCE(dimval3::text, '') AS dimv, COUNT(*) AS cnt
|
||||||
|
FROM dfblob
|
||||||
|
WHERE src_table='mmitem'
|
||||||
|
AND typ='img'
|
||||||
|
AND src_id=$7
|
||||||
|
AND COALESCE(dimval3::text, '') <> ''
|
||||||
|
AND (
|
||||||
|
UPPER(COALESCE(file_name,'')) LIKE $1 OR
|
||||||
|
UPPER(COALESCE(file_name,'')) LIKE $2 OR
|
||||||
|
UPPER(COALESCE(file_name,'')) LIKE $3 OR
|
||||||
|
UPPER(COALESCE(file_name,'')) LIKE $4 OR
|
||||||
|
UPPER(COALESCE(file_name,'')) LIKE $5 OR
|
||||||
|
UPPER(COALESCE(file_name,'')) LIKE $6
|
||||||
|
)
|
||||||
|
GROUP BY COALESCE(dimval3::text, '')
|
||||||
|
) x
|
||||||
|
ORDER BY x.cnt DESC, x.dimv
|
||||||
|
LIMIT 1
|
||||||
|
`, patterns[0], patterns[1], patterns[2], patterns[3], patterns[4], patterns[5], mmitemID).Scan(&dimv)
|
||||||
|
if err != nil {
|
||||||
|
inferCache[key] = 0
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
dimv = strings.TrimSpace(dimv)
|
||||||
|
if dimv == "" || dimv == "0" {
|
||||||
|
inferCache[key] = 0
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
id, err := strconv.ParseInt(dimv, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
inferCache[key] = 0
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
inferCache[key] = id
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
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 = dim1ByToken[row.ColorCode]
|
||||||
if row.Dim3Code != "" {
|
if row.Dim3Code != "" {
|
||||||
row.Dim3ID = dim3ByToken[row.Dim3Code]
|
// dimval3 tokens can be ambiguous globally; prefer per-mmitem inference.
|
||||||
|
if inferred := inferDim3ForMmitem(row.MmitemID, row.Dim3Code); inferred > 0 {
|
||||||
|
row.Dim3ID = inferred
|
||||||
|
} else {
|
||||||
|
row.Dim3ID = dim3ByToken[row.Dim3Code]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
row.MappingReady = row.MmitemID > 0 && row.Dim1ID > 0 && (row.Dim3Code == "" || row.Dim3ID > 0)
|
row.MappingReady = row.MmitemID > 0 && row.Dim1ID > 0 && (row.Dim3Code == "" || row.Dim3ID > 0)
|
||||||
if !row.MappingReady {
|
if !row.MappingReady {
|
||||||
@@ -515,7 +579,45 @@ WHERE dim_column=$1 AND token = ANY($2)
|
|||||||
}
|
}
|
||||||
out[strings.TrimSpace(token)] = id
|
out[strings.TrimSpace(token)] = id
|
||||||
}
|
}
|
||||||
return out, rows.Err()
|
if err := rows.Err(); err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Best-effort fallback: infer missing token->dim_id from dfblob file_name patterns.
|
||||||
|
// NOTE: For dimval3, the same token can map to different dim ids per product in this
|
||||||
|
// installation, so we do NOT infer/persist globally here. Per-product inference is
|
||||||
|
// handled in the row loop (using mmitem_id) to avoid wrong matches.
|
||||||
|
for _, rawTok := range tokens {
|
||||||
|
tok := strings.ToUpper(normalizeDimParam(rawTok))
|
||||||
|
if tok == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := out[tok]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if column == "dimval3" {
|
||||||
|
// avoid global inference for dimval3
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v := resolveDimvalFromFileNameToken(pg, column, tok)
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
id, err := strconv.ParseInt(v, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Persist for future requests (best-effort).
|
||||||
|
_, _ = 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, tok, id)
|
||||||
|
out[tok] = id
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadProductSeriesAssignments(ctx context.Context, pg *sql.DB, codes []string) (map[string][]int64, error) {
|
func loadProductSeriesAssignments(ctx context.Context, pg *sql.DB, codes []string) (map[string][]int64, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user