From e2c04fab5d3c8b6aae63c9f5a64c36b67a9ae972 Mon Sep 17 00:00:00 2001 From: M_Kececi Date: Sun, 15 Mar 2026 22:59:42 +0300 Subject: [PATCH] Merge remote-tracking branch 'origin/master' --- svc/routes/product_images.go | 222 +++++++++++++++++----- ui/src/pages/ProductStockByAttributes.vue | 20 +- ui/src/pages/ProductStockQuery.vue | 20 +- 3 files changed, 213 insertions(+), 49 deletions(-) diff --git a/svc/routes/product_images.go b/svc/routes/product_images.go index 9f83f85..61d3f77 100644 --- a/svc/routes/product_images.go +++ b/svc/routes/product_images.go @@ -37,6 +37,78 @@ func normalizeDimParam(v string) string { return s } +func uniqueNonEmpty(items ...string) []string { + out := make([]string, 0, len(items)) + seen := make(map[string]struct{}, len(items)) + for _, it := range items { + v := normalizeDimParam(it) + if v == "" { + continue + } + if _, ok := seen[v]; ok { + continue + } + seen[v] = struct{}{} + out = append(out, v) + } + return out +} + +func buildNameLikePatterns(token string) []string { + t := strings.ToUpper(strings.TrimSpace(token)) + if t == "" { + return nil + } + return []string{ + "% " + t + " %", + "%-" + t + "-%", + "%-" + t + "_%", + "%_" + t + "_%", + "%(" + t + ")%", + t + " %", + } +} + +func resolveDimvalFromFileNameToken(pg *sql.DB, column, token string) string { + patterns := buildNameLikePatterns(token) + if len(patterns) == 0 { + return "" + } + 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, '') <> '' + 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, column) + var v string + if err := pg.QueryRow(query, + patterns[0], + patterns[1], + patterns[2], + patterns[3], + patterns[4], + patterns[5], + ).Scan(&v); err != nil { + return "" + } + return normalizeDimParam(v) +} + func extractImageUUID(storagePath, fileName string) string { if m := uuidPattern.FindString(storagePath); m != "" { return strings.ToLower(m) @@ -112,20 +184,8 @@ LIMIT 1 return } - // Rule: - // dim1!=0 && dim3!=0 => dimval1=dim1 AND dimval3=dim3 - // dim1!=0 && dim3==0 => dimval1=dim1 - // dim1==0 && dim3==0 => generic photos - dim1Filter := normalizeDimParam(dim1ID) - if dim1Filter == "" { - dim1Filter = normalizeDimParam(dim1) - } - dim3Filter := normalizeDimParam(dim3ID) - if dim3Filter == "" { - dim3Filter = normalizeDimParam(dim3) - } - - query := ` + runQuery := func(dim1Filter, dim3Filter string) ([]ProductImageItem, error) { + query := ` SELECT id, COALESCE(file_name,'') AS file_name, @@ -135,26 +195,110 @@ FROM dfblob WHERE typ='img' AND src_table='mmitem' AND src_id=$1` - args := []interface{}{mmItemID} - argPos := 2 - if dim1Filter != "" { - query += fmt.Sprintf(" AND COALESCE(dimval1::text,'') = $%d", argPos) - args = append(args, dim1Filter) - argPos++ - if dim3Filter != "" { - query += fmt.Sprintf(" AND COALESCE(dimval3::text,'') = $%d", argPos) - args = append(args, dim3Filter) + args := []interface{}{mmItemID} + argPos := 2 + if dim1Filter != "" { + query += fmt.Sprintf(" AND COALESCE(dimval1::text,'') = $%d", argPos) + args = append(args, dim1Filter) argPos++ + if dim3Filter != "" { + query += fmt.Sprintf(" AND COALESCE(dimval3::text,'') = $%d", argPos) + args = append(args, dim3Filter) + argPos++ + } } - } - query += ` + query += ` ORDER BY COALESCE(sort_order,999999), zlins_dttm DESC, id DESC` - rows, err := pg.Query(query, args...) - if err != nil { + rows, err := pg.Query(query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + items := make([]ProductImageItem, 0, 16) + for rows.Next() { + var it ProductImageItem + if err := rows.Scan(&it.ID, &it.FileName, &it.FileSize, &it.Storage); err != nil { + continue + } + it.ContentURL = fmt.Sprintf("/api/product-images/%d/content", it.ID) + if u := extractImageUUID(it.Storage, it.FileName); u != "" { + it.UUID = u + it.ThumbURL = "/uploads/image/t300/" + u + ".jpg" + it.FullURL = "/uploads/image/" + u + ".jpg" + } + items = append(items, it) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil + } + + // Rule: + // dim1!=0 && dim3!=0 => dimval1=dim1 AND dimval3=dim3 + // dim1!=0 && dim3==0 => dimval1=dim1 + // dim1==0 && dim3==0 => generic photos + // + // Frontend'den yanlis dim id gelebildigi icin: + // 1) once *_id ile deneriz + // 2) sonuc yoksa kod degeriyle fallback deneriz. + resolvedDim1ID := normalizeDimParam(dim1ID) + if resolvedDim1ID == "" && normalizeDimParam(dim1) != "" { + resolvedDim1ID = resolveDimvalFromFileNameToken(pg, "dimval1", dim1) + } + resolvedDim3ID := normalizeDimParam(dim3ID) + if resolvedDim3ID == "" && normalizeDimParam(dim3) != "" { + resolvedDim3ID = resolveDimvalFromFileNameToken(pg, "dimval3", dim3) + } + + dim1Candidates := uniqueNonEmpty(resolvedDim1ID, dim1ID, dim1) + if len(dim1Candidates) == 0 { + dim1Candidates = []string{""} + } + dim3Candidates := uniqueNonEmpty(resolvedDim3ID, dim3ID, dim3) + + items := make([]ProductImageItem, 0, 16) + selectedDim1 := "" + selectedDim3 := "" + var queryErr error + + for _, d1 := range dim1Candidates { + localDim3Candidates := []string{""} + if d1 != "" { + if len(dim3Candidates) > 0 { + localDim3Candidates = append([]string{}, dim3Candidates...) + localDim3Candidates = append(localDim3Candidates, "") + } + } + + for _, d3 := range localDim3Candidates { + var runErr error + items, runErr = runQuery(d1, d3) + if runErr != nil { + queryErr = runErr + continue + } + if len(items) > 0 { + selectedDim1 = d1 + selectedDim3 = d3 + break + } + if selectedDim1 == "" && selectedDim3 == "" { + selectedDim1 = d1 + selectedDim3 = d3 + } + } + if len(items) > 0 { + break + } + } + + if queryErr != nil && len(items) == 0 { slog.Error("product_images.list.query_failed", "req_id", reqID, "code", code, @@ -162,35 +306,23 @@ ORDER BY "dim1_id", dim1ID, "dim3", dim3, "dim3_id", dim3ID, - "err", err.Error(), + "err", queryErr.Error(), ) - http.Error(w, "Gorsel sorgu hatasi: "+err.Error(), http.StatusInternalServerError) + http.Error(w, "Gorsel sorgu hatasi: "+queryErr.Error(), http.StatusInternalServerError) return } - defer rows.Close() - - items := make([]ProductImageItem, 0, 16) - for rows.Next() { - var it ProductImageItem - if err := rows.Scan(&it.ID, &it.FileName, &it.FileSize, &it.Storage); err != nil { - continue - } - it.ContentURL = fmt.Sprintf("/api/product-images/%d/content", it.ID) - if u := extractImageUUID(it.Storage, it.FileName); u != "" { - it.UUID = u - it.ThumbURL = "/uploads/image/t300/" + u + ".jpg" - it.FullURL = "/uploads/image/" + u + ".jpg" - } - items = append(items, it) - } slog.Info("product_images.list.ok", "req_id", reqID, "code", code, "dim1", dim1, "dim1_id", dim1ID, + "resolved_dim1_id", resolvedDim1ID, "dim3", dim3, "dim3_id", dim3ID, + "resolved_dim3_id", resolvedDim3ID, + "selected_dim1", selectedDim1, + "selected_dim3", selectedDim3, "count", len(items), ) diff --git a/ui/src/pages/ProductStockByAttributes.vue b/ui/src/pages/ProductStockByAttributes.vue index 1ce4af1..5f41106 100644 --- a/ui/src/pages/ProductStockByAttributes.vue +++ b/ui/src/pages/ProductStockByAttributes.vue @@ -486,7 +486,15 @@ function resolvePhotoDim3(item, secondColorDisplay = '') { } function resolvePhotoDim1ID(item) { - const candidates = [item?.ItemDim1Code, item?.itemDim1Code, item?.ITEMDIM1CODE, item?.Beden] + const candidates = [ + item?.PhotoDim1ID, + item?.photoDim1ID, + item?.Dim1ID, + item?.dim1ID, + item?.ColorID, + item?.colorID, + item?.RenkID + ] for (const value of candidates) { const s = String(value || '').trim() if (/^\d+$/.test(s)) return s @@ -495,7 +503,15 @@ function resolvePhotoDim1ID(item) { } function resolvePhotoDim3ID(item) { - const candidates = [item?.ItemDim3Code, item?.itemDim3Code, item?.ITEMDIM3CODE, item?.Renk2] + const candidates = [ + item?.PhotoDim3ID, + item?.photoDim3ID, + item?.Dim3ID, + item?.dim3ID, + item?.SecondColorID, + item?.secondColorID, + item?.Renk2ID + ] for (const value of candidates) { const s = String(value || '').trim() if (/^\d+$/.test(s)) return s diff --git a/ui/src/pages/ProductStockQuery.vue b/ui/src/pages/ProductStockQuery.vue index 71922f2..5860867 100644 --- a/ui/src/pages/ProductStockQuery.vue +++ b/ui/src/pages/ProductStockQuery.vue @@ -479,7 +479,15 @@ function resolvePhotoDim3(item, secondColorDisplay = '') { } function resolvePhotoDim1ID(item) { - const candidates = [item?.ItemDim1Code, item?.itemDim1Code, item?.ITEMDIM1CODE, item?.Beden] + const candidates = [ + item?.PhotoDim1ID, + item?.photoDim1ID, + item?.Dim1ID, + item?.dim1ID, + item?.ColorID, + item?.colorID, + item?.RenkID + ] for (const value of candidates) { const s = String(value || '').trim() if (/^\d+$/.test(s)) return s @@ -488,7 +496,15 @@ function resolvePhotoDim1ID(item) { } function resolvePhotoDim3ID(item) { - const candidates = [item?.ItemDim3Code, item?.itemDim3Code, item?.ITEMDIM3CODE, item?.Renk2] + const candidates = [ + item?.PhotoDim3ID, + item?.photoDim3ID, + item?.Dim3ID, + item?.dim3ID, + item?.SecondColorID, + item?.secondColorID, + item?.Renk2ID + ] for (const value of candidates) { const s = String(value || '').trim() if (/^\d+$/.test(s)) return s