Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -950,6 +950,11 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
|
||||
"pricing", "view",
|
||||
wrapV3(http.HandlerFunc(routes.GetProductSeriesMappingsHandler(pgDB))),
|
||||
)
|
||||
bindV3(r, pgDB,
|
||||
"/api/pricing/product-series/mappings/orphans", "GET",
|
||||
"pricing", "view",
|
||||
wrapV3(http.HandlerFunc(routes.GetProductSeriesOrphanMappingsHandler(pgDB))),
|
||||
)
|
||||
bindV3(r, pgDB,
|
||||
"/api/pricing/product-series/mappings/save", "POST",
|
||||
"pricing", "update",
|
||||
|
||||
@@ -1167,6 +1167,25 @@ func productSeriesResolvePGVariant(ctx context.Context, pg *sql.DB, productCode,
|
||||
}
|
||||
dim3ID = sql.NullInt64{Int64: id, Valid: true}
|
||||
}
|
||||
|
||||
// Only treat variants as ready if they exist in authoritative item-dim combos.
|
||||
// This prevents writing series assignments for stock variants that don't exist on the PG side.
|
||||
dim3Key := int64(0)
|
||||
if dim3ID.Valid {
|
||||
dim3Key = dim3ID.Int64
|
||||
}
|
||||
var exists int
|
||||
if err := pg.QueryRowContext(ctx, `
|
||||
SELECT 1
|
||||
FROM mk_mmitem_dim_combo
|
||||
WHERE product_code=$1 AND dim1=$2 AND dim3_key=$3
|
||||
LIMIT 1
|
||||
`, strings.TrimSpace(productCode), dim1ID, dim3Key).Scan(&exists); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return 0, 0, sql.NullInt64{}, false, nil
|
||||
}
|
||||
return 0, 0, sql.NullInt64{}, false, err
|
||||
}
|
||||
return mmitemID, dim1ID, dim3ID, true, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -133,12 +133,6 @@ CREATE TABLE IF NOT EXISTS mk_dim_token_map (
|
||||
}
|
||||
}
|
||||
|
||||
// One-time bootstrap for mk_dim_token_map from authoritative PG data, if empty.
|
||||
// This avoids heuristics (dfblob filename inference) polluting the token map on first deploy.
|
||||
if err := seedDimTokenMapFromMmitemDim(pg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := seedPricingTargetMapRows(pg, "mk_price_target_map_pg", "sdprcgrp_id"); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -161,81 +155,6 @@ WHERE is_active = TRUE
|
||||
return nil
|
||||
}
|
||||
|
||||
func seedDimTokenMapFromMmitemDim(pg *sql.DB) error {
|
||||
// Seed dimval1 only if it's currently empty.
|
||||
var hasDimval1 int
|
||||
_ = pg.QueryRow(`SELECT 1 FROM mk_dim_token_map WHERE dim_column='dimval1' LIMIT 1`).Scan(&hasDimval1)
|
||||
if hasDimval1 == 0 {
|
||||
// Prefer the most-used dim_id for a given code if duplicates exist, to keep the mapping stable.
|
||||
if _, err := pg.Exec(`
|
||||
WITH usage AS (
|
||||
SELECT val1 AS dim_id, COUNT(*) AS cnt
|
||||
FROM mmitem_dim
|
||||
WHERE val1 IS NOT NULL
|
||||
GROUP BY val1
|
||||
),
|
||||
candidates AS (
|
||||
SELECT UPPER(BTRIM(d.code)) AS token, d.id AS dim_id, u.cnt
|
||||
FROM usage u
|
||||
JOIN dfgrp d ON d.id = u.dim_id
|
||||
WHERE d.code IS NOT NULL
|
||||
AND BTRIM(d.code) <> ''
|
||||
),
|
||||
picked AS (
|
||||
SELECT DISTINCT ON (token) token, dim_id
|
||||
FROM candidates
|
||||
ORDER BY token, cnt DESC, dim_id
|
||||
)
|
||||
INSERT INTO mk_dim_token_map (dim_column, token, dim_id, updated_at)
|
||||
SELECT 'dimval1', token, dim_id, now()
|
||||
FROM picked
|
||||
ON CONFLICT (dim_column, token)
|
||||
DO UPDATE SET dim_id = EXCLUDED.dim_id, updated_at = EXCLUDED.updated_at
|
||||
`); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Seed dimval3 only if it's currently empty and there is at least one val3 in mmitem_dim.
|
||||
var hasDimval3 int
|
||||
_ = pg.QueryRow(`SELECT 1 FROM mk_dim_token_map WHERE dim_column='dimval3' LIMIT 1`).Scan(&hasDimval3)
|
||||
if hasDimval3 == 0 {
|
||||
var anyVal3 int
|
||||
_ = pg.QueryRow(`SELECT 1 FROM mmitem_dim WHERE val3 IS NOT NULL LIMIT 1`).Scan(&anyVal3)
|
||||
if anyVal3 != 0 {
|
||||
if _, err := pg.Exec(`
|
||||
WITH usage AS (
|
||||
SELECT val3 AS dim_id, COUNT(*) AS cnt
|
||||
FROM mmitem_dim
|
||||
WHERE val3 IS NOT NULL
|
||||
GROUP BY val3
|
||||
),
|
||||
candidates AS (
|
||||
SELECT UPPER(BTRIM(d.code)) AS token, d.id AS dim_id, u.cnt
|
||||
FROM usage u
|
||||
JOIN dfgrp d ON d.id = u.dim_id
|
||||
WHERE d.code IS NOT NULL
|
||||
AND BTRIM(d.code) <> ''
|
||||
),
|
||||
picked AS (
|
||||
SELECT DISTINCT ON (token) token, dim_id
|
||||
FROM candidates
|
||||
ORDER BY token, cnt DESC, dim_id
|
||||
)
|
||||
INSERT INTO mk_dim_token_map (dim_column, token, dim_id, updated_at)
|
||||
SELECT 'dimval3', token, dim_id, now()
|
||||
FROM picked
|
||||
ON CONFLICT (dim_column, token)
|
||||
DO UPDATE SET dim_id = EXCLUDED.dim_id, updated_at = EXCLUDED.updated_at
|
||||
`); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func seedPricingTargetMapRows(pg *sql.DB, tableName string, valueColumn string) error {
|
||||
currencies := []string{"TRY", "USD", "EUR"}
|
||||
for _, currency := range currencies {
|
||||
|
||||
@@ -292,86 +292,91 @@ func GetProductSeriesMappingsHandler(pg *sql.DB) http.HandlerFunc {
|
||||
defByID[d.ID] = d
|
||||
}
|
||||
|
||||
// Strict token->dim_id resolver (no inference/persistence). UI should not invent dim mappings.
|
||||
loadDimTokenIDsStrict := func(ctx context.Context, pg *sql.DB, column string, tokens []string) (map[string]int64, error) {
|
||||
out := map[string]int64{}
|
||||
if len(tokens) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
rows, err := pg.QueryContext(ctx, `
|
||||
SELECT token, dim_id
|
||||
FROM mk_dim_token_map
|
||||
WHERE dim_column=$1 AND token = ANY($2)
|
||||
`, column, pq.Array(tokens))
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var tok string
|
||||
var id int64
|
||||
if err := rows.Scan(&tok, &id); err != nil {
|
||||
return out, err
|
||||
}
|
||||
out[strings.TrimSpace(tok)] = id
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// Load all known (product_code, dim1, dim3_key) combos for products in this response.
|
||||
loadProductDimCombos := func(ctx context.Context, pg *sql.DB, productCodes []string) (map[string]struct{}, error) {
|
||||
out := map[string]struct{}{}
|
||||
if len(productCodes) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
rows, err := pg.QueryContext(ctx, `
|
||||
SELECT product_code, dim1, dim3_key
|
||||
FROM mk_mmitem_dim_combo
|
||||
WHERE product_code = ANY($1)
|
||||
`, pq.Array(productCodes))
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var code string
|
||||
var dim1, dim3Key int64
|
||||
if err := rows.Scan(&code, &dim1, &dim3Key); err != nil {
|
||||
return out, err
|
||||
}
|
||||
key := fmt.Sprintf("%s|%d|%d", strings.TrimSpace(code), dim1, dim3Key)
|
||||
out[key] = struct{}{}
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
codes := setToSortedSlice(codeSet)
|
||||
mmitemByCode, _ := loadMmitemIDs(ctx, pg, codes)
|
||||
dim1ByToken, _ := loadDimTokenIDs(ctx, pg, "dimval1", setToSortedSlice(colorSet))
|
||||
dim3ByToken, _ := loadDimTokenIDs(ctx, pg, "dimval3", setToSortedSlice(dim3Set))
|
||||
dim1ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval1", setToSortedSlice(colorSet))
|
||||
dim3ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval3", setToSortedSlice(dim3Set))
|
||||
combos, _ := loadProductDimCombos(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))
|
||||
for _, row := range grouped {
|
||||
row.MmitemID = mmitemByCode[row.ProductCode]
|
||||
row.Dim1ID = dim1ByToken[row.ColorCode]
|
||||
if row.Dim3Code != "" {
|
||||
// dimval3 can be ambiguous, but if mk_dim_token_map has a row we treat it as source of truth.
|
||||
// Strict: only accept explicit token map rows for dimval3.
|
||||
if v := dim3ByToken[row.Dim3Code]; v > 0 {
|
||||
row.Dim3ID = v
|
||||
} else if inferred := inferDim3ForMmitem(row.MmitemID, row.Dim3Code); inferred > 0 {
|
||||
row.Dim3ID = inferred
|
||||
}
|
||||
}
|
||||
// Only show variants that are also known/valid on the PG side (product_code + dim1 + dim3_key).
|
||||
// This prevents UI from listing stock variants that don't exist in PG's authoritative item-dim combos.
|
||||
row.MappingReady = row.MmitemID > 0 && row.Dim1ID > 0 && (row.Dim3Code == "" || row.Dim3ID > 0)
|
||||
dim3Key := int64(0)
|
||||
if row.Dim3Code != "" {
|
||||
dim3Key = row.Dim3ID
|
||||
}
|
||||
if row.MappingReady {
|
||||
_, ok := combos[fmt.Sprintf("%s|%d|%d", strings.TrimSpace(row.ProductCode), row.Dim1ID, dim3Key)]
|
||||
row.MappingReady = ok
|
||||
}
|
||||
if !row.MappingReady {
|
||||
row.MappingWarning = "PG urun veya varyant token eslesmesi bulunamadi"
|
||||
// Skip: do not surface "orphan" variants in the UI.
|
||||
continue
|
||||
}
|
||||
assignKey := assignmentKey(row.ProductCode, row.Dim1ID, row.Dim3ID)
|
||||
appendSeries := func(key string) {
|
||||
@@ -383,11 +388,6 @@ LIMIT 1
|
||||
}
|
||||
}
|
||||
appendSeries(assignKey)
|
||||
// Fallback: if we couldn't match dim3-specific assignments (dim3 token ambiguity / missing),
|
||||
// show the default (dim3 NULL) assignments for the same product+color.
|
||||
if len(row.SeriesIDs) == 0 && row.Dim3Code != "" && row.Dim1ID > 0 {
|
||||
appendSeries(assignmentKey(row.ProductCode, row.Dim1ID, 0))
|
||||
}
|
||||
sort.Slice(row.Series, func(i, j int) bool { return row.Series[i].Code < row.Series[j].Code })
|
||||
sort.Slice(row.SeriesIDs, func(i, j int) bool { return row.SeriesIDs[i] < row.SeriesIDs[j] })
|
||||
out = append(out, *row)
|
||||
@@ -416,6 +416,239 @@ LIMIT 1
|
||||
}
|
||||
}
|
||||
|
||||
func GetProductSeriesOrphanMappingsHandler(pg *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
f := readStockAttrFilters(r)
|
||||
limit := 0
|
||||
if raw := strings.TrimSpace(r.URL.Query().Get("limit")); raw != "" {
|
||||
if n, err := strconv.Atoi(raw); err == nil && n >= 0 {
|
||||
limit = n
|
||||
}
|
||||
}
|
||||
search := firstNonEmpty(r.URL.Query().Get("q"), r.URL.Query().Get("code"), r.URL.Query().Get("product_code"))
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 120*time.Second)
|
||||
defer cancel()
|
||||
|
||||
msRows, err := db.MssqlDB.QueryContext(ctx, queries.GetProductSeriesStockRowsQuery,
|
||||
f.kategori,
|
||||
f.urunAnaGrubu,
|
||||
joinFilterValues(f.urunAltGrubu),
|
||||
joinFilterValues(f.renk),
|
||||
joinFilterValues(f.renk2),
|
||||
joinFilterValues(f.urunIcerigi),
|
||||
joinFilterValues(f.fit),
|
||||
joinFilterValues(f.drop),
|
||||
joinFilterValues(f.beden),
|
||||
strconv.Itoa(limit),
|
||||
search,
|
||||
)
|
||||
if err != nil {
|
||||
http.Error(w, "Stok seri listesi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer msRows.Close()
|
||||
|
||||
grouped := map[string]*productSeriesMappingRow{}
|
||||
codeSet := map[string]struct{}{}
|
||||
colorSet := map[string]struct{}{}
|
||||
dim3Set := map[string]struct{}{}
|
||||
sizeSet := map[string]struct{}{}
|
||||
for msRows.Next() {
|
||||
var raw productSeriesStockRawRow
|
||||
if err := msRows.Scan(
|
||||
&raw.ProductCode,
|
||||
&raw.ProductDescription,
|
||||
&raw.ColorCode,
|
||||
&raw.ColorTitle,
|
||||
&raw.Dim3Code,
|
||||
&raw.SizeCode,
|
||||
&raw.Qty,
|
||||
&raw.UrunAnaGrubu,
|
||||
&raw.UrunAltGrubu,
|
||||
&raw.Marka,
|
||||
&raw.DropVal,
|
||||
&raw.Fit,
|
||||
&raw.UrunIcerigi,
|
||||
&raw.Kategori,
|
||||
); err != nil {
|
||||
http.Error(w, "Stok satiri okunamadi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
raw.ProductCode = strings.TrimSpace(raw.ProductCode)
|
||||
raw.ColorCode = strings.TrimSpace(raw.ColorCode)
|
||||
raw.Dim3Code = strings.TrimSpace(raw.Dim3Code)
|
||||
raw.SizeCode = strings.TrimSpace(raw.SizeCode)
|
||||
key := productSeriesKey(raw.ProductCode, raw.ColorCode, raw.Dim3Code)
|
||||
row := grouped[key]
|
||||
if row == nil {
|
||||
row = &productSeriesMappingRow{
|
||||
RowKey: key,
|
||||
ProductCode: raw.ProductCode,
|
||||
ProductDescription: strings.TrimSpace(raw.ProductDescription),
|
||||
ColorCode: raw.ColorCode,
|
||||
ColorTitle: strings.TrimSpace(raw.ColorTitle),
|
||||
Dim3Code: raw.Dim3Code,
|
||||
UrunAnaGrubu: strings.TrimSpace(raw.UrunAnaGrubu),
|
||||
UrunAltGrubu: strings.TrimSpace(raw.UrunAltGrubu),
|
||||
Marka: strings.TrimSpace(raw.Marka),
|
||||
DropVal: strings.TrimSpace(raw.DropVal),
|
||||
Fit: strings.TrimSpace(raw.Fit),
|
||||
UrunIcerigi: strings.TrimSpace(raw.UrunIcerigi),
|
||||
Kategori: strings.TrimSpace(raw.Kategori),
|
||||
SizeQty: map[string]float64{},
|
||||
SeriesIDs: []int64{},
|
||||
Series: []productSeriesDefinition{},
|
||||
}
|
||||
grouped[key] = row
|
||||
codeSet[raw.ProductCode] = struct{}{}
|
||||
colorSet[raw.ColorCode] = struct{}{}
|
||||
if raw.Dim3Code != "" {
|
||||
dim3Set[raw.Dim3Code] = struct{}{}
|
||||
}
|
||||
}
|
||||
if raw.SizeCode != "" {
|
||||
row.SizeQty[raw.SizeCode] = raw.Qty
|
||||
sizeSet[raw.SizeCode] = struct{}{}
|
||||
}
|
||||
row.TotalQty += raw.Qty
|
||||
}
|
||||
if err := msRows.Err(); err != nil {
|
||||
http.Error(w, "Stok satirlari okunamadi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
defs, err := listProductSeriesDefinitions(ctx, pg, true)
|
||||
if err != nil {
|
||||
http.Error(w, "Seri tanimlari alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Strict token map lookups (no inference).
|
||||
loadDimTokenIDsStrict := func(ctx context.Context, pg *sql.DB, column string, tokens []string) (map[string]int64, error) {
|
||||
out := map[string]int64{}
|
||||
if len(tokens) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
rows, err := pg.QueryContext(ctx, `
|
||||
SELECT token, dim_id
|
||||
FROM mk_dim_token_map
|
||||
WHERE dim_column=$1 AND token = ANY($2)
|
||||
`, column, pq.Array(tokens))
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var tok string
|
||||
var id int64
|
||||
if err := rows.Scan(&tok, &id); err != nil {
|
||||
return out, err
|
||||
}
|
||||
out[strings.TrimSpace(tok)] = id
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
loadProductDimCombos := func(ctx context.Context, pg *sql.DB, productCodes []string) (map[string]struct{}, error) {
|
||||
out := map[string]struct{}{}
|
||||
if len(productCodes) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
rows, err := pg.QueryContext(ctx, `
|
||||
SELECT product_code, dim1, dim3_key
|
||||
FROM mk_mmitem_dim_combo
|
||||
WHERE product_code = ANY($1)
|
||||
`, pq.Array(productCodes))
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var code string
|
||||
var dim1, dim3Key int64
|
||||
if err := rows.Scan(&code, &dim1, &dim3Key); err != nil {
|
||||
return out, err
|
||||
}
|
||||
key := fmt.Sprintf("%s|%d|%d", strings.TrimSpace(code), 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))
|
||||
combos, _ := loadProductDimCombos(ctx, pg, codes)
|
||||
|
||||
out := make([]productSeriesMappingRow, 0, len(grouped))
|
||||
for _, row := range grouped {
|
||||
if row.TotalQty <= 0 {
|
||||
continue
|
||||
}
|
||||
row.MmitemID = mmitemByCode[row.ProductCode]
|
||||
row.Dim1ID = dim1ByToken[row.ColorCode]
|
||||
if row.Dim3Code != "" {
|
||||
if v := dim3ByToken[row.Dim3Code]; v > 0 {
|
||||
row.Dim3ID = v
|
||||
}
|
||||
}
|
||||
|
||||
// Determine orphan reason.
|
||||
baseReady := row.MmitemID > 0 && row.Dim1ID > 0 && (row.Dim3Code == "" || row.Dim3ID > 0)
|
||||
dim3Key := int64(0)
|
||||
if row.Dim3Code != "" {
|
||||
dim3Key = row.Dim3ID
|
||||
}
|
||||
comboKey := fmt.Sprintf("%s|%d|%d", strings.TrimSpace(row.ProductCode), row.Dim1ID, dim3Key)
|
||||
_, comboOK := combos[comboKey]
|
||||
|
||||
row.MappingReady = false
|
||||
switch {
|
||||
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)"
|
||||
case row.Dim3Code != "" && row.Dim3ID <= 0:
|
||||
row.MappingWarning = "B2B'de dim3 token eslesmesi yok (mk_dim_token_map: dimval3)"
|
||||
case !comboOK:
|
||||
row.MappingWarning = "B2B'de varyant kombosu yok (mk_mmitem_dim_combo)"
|
||||
default:
|
||||
// Not an orphan; skip.
|
||||
if baseReady && comboOK {
|
||||
continue
|
||||
}
|
||||
row.MappingWarning = "B2B varyant eslesmesi bulunamadi"
|
||||
}
|
||||
|
||||
out = append(out, *row)
|
||||
}
|
||||
|
||||
productTotals := productSeriesTotalQtyByCode(out)
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
totalI := productTotals[out[i].ProductCode]
|
||||
totalJ := productTotals[out[j].ProductCode]
|
||||
if totalI != totalJ {
|
||||
return totalI > totalJ
|
||||
}
|
||||
if out[i].ProductCode != out[j].ProductCode {
|
||||
return out[i].ProductCode < out[j].ProductCode
|
||||
}
|
||||
if out[i].ColorCode != out[j].ColorCode {
|
||||
return out[i].ColorCode < out[j].ColorCode
|
||||
}
|
||||
return out[i].Dim3Code < out[j].Dim3Code
|
||||
})
|
||||
|
||||
writeJSON(w, productSeriesMappingsResponse{
|
||||
Rows: out,
|
||||
SizeColumns: sortSizeColumns(setToSortedSlice(sizeSet)),
|
||||
Definitions: defs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func productSeriesTotalQtyByCode(rows []productSeriesMappingRow) map[string]float64 {
|
||||
out := make(map[string]float64, len(rows))
|
||||
for _, row := range rows {
|
||||
|
||||
@@ -373,6 +373,11 @@ const menuItems = [
|
||||
to: '/app/pricing/product-series-mappings',
|
||||
permission: 'pricing:view'
|
||||
},
|
||||
{
|
||||
label: 'B2B Olmayan Stok',
|
||||
to: '/app/pricing/product-series-mappings-orphans',
|
||||
permission: 'pricing:view'
|
||||
},
|
||||
{
|
||||
label: 'Ürün Seri Tanımlamaları',
|
||||
to: '/app/pricing/product-series-definitions',
|
||||
|
||||
@@ -425,6 +425,12 @@ const routes = [
|
||||
component: () => import('pages/ProductSeriesMappings.vue'),
|
||||
meta: { permission: 'pricing:view' }
|
||||
},
|
||||
{
|
||||
path: 'pricing/product-series-mappings-orphans',
|
||||
name: 'product-series-mappings-orphans',
|
||||
component: () => import('pages/ProductSeriesMappingsOrphans.vue'),
|
||||
meta: { permission: 'pricing:view' }
|
||||
},
|
||||
{
|
||||
path: 'pricing/product-series-definitions',
|
||||
name: 'product-series-definitions',
|
||||
|
||||
Reference in New Issue
Block a user