Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-06-18 17:49:30 +03:00
parent 149cea778e
commit d1fbe60aeb
5 changed files with 216 additions and 105 deletions

View File

@@ -672,6 +672,20 @@ func cleanProductPricingFilterValues(values []string) []string {
return clean
}
func isProductCodeOnlyPricingFilter(filters ProductPricingFilters) bool {
return len(cleanProductPricingFilterValues(filters.ProductCode)) > 0 &&
strings.TrimSpace(filters.Search) == "" &&
len(cleanProductPricingFilterValues(filters.BrandGroup)) == 0 &&
len(cleanProductPricingFilterValues(filters.AskiliYan)) == 0 &&
len(cleanProductPricingFilterValues(filters.Kategori)) == 0 &&
len(cleanProductPricingFilterValues(filters.UrunIlkGrubu)) == 0 &&
len(cleanProductPricingFilterValues(filters.UrunAnaGrubu)) == 0 &&
len(cleanProductPricingFilterValues(filters.UrunAltGrubu)) == 0 &&
len(cleanProductPricingFilterValues(filters.Icerik)) == 0 &&
len(cleanProductPricingFilterValues(filters.Karisim)) == 0 &&
len(cleanProductPricingFilterValues(filters.Marka)) == 0
}
func GetProductPricingPage(ctx context.Context, page int, limit int, filters ProductPricingFilters, includeTotal bool, sortBy string, descending bool) (ProductPricingPage, error) {
result := ProductPricingPage{
Rows: []models.ProductPricing{},
@@ -751,8 +765,28 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
}, " OR ")+")")
}
whereSQL := strings.Join(whereParts, " AND ")
cleanProductCodes := cleanProductPricingFilterValues(filters.ProductCode)
productCodeOnlyFilter := isProductCodeOnlyPricingFilter(filters)
if includeTotal {
if includeTotal && productCodeOnlyFilter {
result.TotalCount = len(cleanProductCodes)
if result.TotalCount == 0 {
result.TotalPages = 0
result.Page = 1
return result, nil
}
totalPages := int(math.Ceil(float64(result.TotalCount) / float64(limit)))
if totalPages <= 0 {
totalPages = 1
}
if page > totalPages {
page = totalPages
offset = (page - 1) * limit
}
result.Page = page
result.Limit = limit
result.TotalPages = totalPages
} else if includeTotal {
countQuery := `
SELECT COUNT(DISTINCT LTRIM(RTRIM(ProductCode)))
FROM ProductFilterWithDescription('TR')
@@ -789,7 +823,7 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
// Stage 1: fetch only paged products first. Exact product-code filters do not
// need the stock sort temp table here; detailed metrics are fetched below.
productCodeFastPath := len(cleanProductPricingFilterValues(filters.ProductCode)) > 0
productCodeFastPath := len(cleanProductCodes) > 0
sortBy = strings.TrimSpace(sortBy)
orderDir := "DESC"
if !descending {
@@ -876,7 +910,72 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
OFFSET ` + strconv.Itoa(offset) + ` ROWS
FETCH NEXT ` + strconv.Itoa(limit) + ` ROWS ONLY;
`
if productCodeFastPath {
if productCodeOnlyFilter {
valueRows := make([]string, 0, len(cleanProductCodes))
directArgs := make([]any, 0, len(cleanProductCodes))
for i, code := range cleanProductCodes {
paramName := "@p" + strconv.Itoa(i+1)
valueRows = append(valueRows, "("+paramName+")")
directArgs = append(directArgs, code)
}
args = directArgs
productQuery = `
WITH req_codes AS (
SELECT DISTINCT LTRIM(RTRIM(v.ProductCode)) AS ProductCode
FROM (VALUES ` + strings.Join(valueRows, ",") + `) v(ProductCode)
WHERE LEN(LTRIM(RTRIM(v.ProductCode))) > 0
),
attr AS (
SELECT
LTRIM(RTRIM(a.ItemCode)) AS ProductCode,
MAX(CASE WHEN a.AttributeTypeCode = 45 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS AskiliYan,
MAX(CASE WHEN a.AttributeTypeCode = 44 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS Kategori,
MAX(CASE WHEN a.AttributeTypeCode = 42 THEN LTRIM(RTRIM(a.AttributeCode)) ELSE '' END) AS UrunIlkGrubuCode,
MAX(CASE WHEN a.AttributeTypeCode = 42 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS UrunIlkGrubu,
MAX(CASE WHEN a.AttributeTypeCode = 1 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS UrunAnaGrubu,
MAX(CASE WHEN a.AttributeTypeCode = 2 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS UrunAltGrubu,
MAX(CASE WHEN a.AttributeTypeCode = 41 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS Icerik,
MAX(CASE WHEN a.AttributeTypeCode = 29 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS Karisim,
MAX(CASE WHEN a.AttributeTypeCode = 10 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS Marka,
MAX(CASE WHEN a.AttributeTypeCode = 10 THEN LTRIM(RTRIM(a.AttributeCode)) ELSE '' END) AS BrandCode
FROM dbo.prItemAttribute a WITH(NOLOCK)
INNER JOIN req_codes rc
ON rc.ProductCode = LTRIM(RTRIM(a.ItemCode))
LEFT JOIN dbo.cdItemAttributeDesc d WITH(NOLOCK)
ON d.ItemTypeCode = a.ItemTypeCode
AND d.AttributeTypeCode = a.AttributeTypeCode
AND d.AttributeCode = a.AttributeCode
AND d.LangCode = 'TR'
WHERE a.ItemTypeCode = 1
AND a.AttributeTypeCode IN (1,2,10,29,41,42,44,45)
GROUP BY LTRIM(RTRIM(a.ItemCode))
)
SELECT
rc.ProductCode,
CAST('' AS NVARCHAR(100)) AS BrandGroupSec,
COALESCE(attr.AskiliYan, '') AS AskiliYan,
COALESCE(attr.Kategori, '') AS Kategori,
COALESCE(attr.UrunIlkGrubu, '') AS UrunIlkGrubu,
COALESCE(attr.UrunAnaGrubu, '') AS UrunAnaGrubu,
COALESCE(attr.UrunAltGrubu, '') AS UrunAltGrubu,
COALESCE(attr.Icerik, '') AS Icerik,
COALESCE(attr.Karisim, '') AS Karisim,
COALESCE(attr.Marka, '') AS Marka,
COALESCE(attr.BrandCode, '') AS BrandCode
FROM req_codes rc
INNER JOIN dbo.cdItem ci WITH(NOLOCK)
ON ci.ItemTypeCode = 1
AND LTRIM(RTRIM(ci.ItemCode)) = rc.ProductCode
LEFT JOIN attr
ON attr.ProductCode = rc.ProductCode
WHERE ISNULL(ci.IsBlocked, 0) = 0
AND LEN(LTRIM(RTRIM(ci.ItemCode))) = 13
AND COALESCE(attr.UrunIlkGrubuCode, '') IN ('SERI', 'AKSESUAR')
ORDER BY rc.ProductCode ASC
OFFSET ` + strconv.Itoa(offset) + ` ROWS
FETCH NEXT ` + strconv.Itoa(limit) + ` ROWS ONLY;
`
} else if productCodeFastPath {
productQuery = `
IF OBJECT_ID('tempdb..#req_codes') IS NOT NULL DROP TABLE #req_codes;

View File

@@ -258,8 +258,9 @@ norm AS (
COALESCE(price, 0) AS price
FROM input
),
-- Prefer PG's authoritative variant dimension table (mmitem_dim). Fall back to cache table if needed.
dims_mmitem_dim AS (
-- Only PG's authoritative variant dimension table may drive delta writes.
-- Do not union sdprc/cache dimensions; stale rows there can re-create wrong variant keys.
dims AS (
SELECT
norm.product_code AS product_code,
md.val1::bigint AS dim1,
@@ -275,41 +276,7 @@ norm AS (
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
NULLIF(BTRIM(c.product_code), '') AS product_code,
c.dim1::bigint AS dim1,
c.dim3::bigint AS dim3
FROM mk_mmitem_dim_combo c
JOIN norm
ON norm.product_code = c.product_code
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 (
SELECT
norm.product_code AS product_code,
s.dim1 AS dim1,
s.dim3 AS dim3
FROM norm
JOIN mmitem mm
ON mm.code = norm.product_code
JOIN sdprc s
ON s.mmitem_id = mm.id
WHERE s.dim1 IS NOT NULL
AND s.dim1 > 0
GROUP BY norm.product_code, s.dim1, s.dim3
),
dims AS (
SELECT product_code, dim1, dim3 FROM dims_cache
UNION
SELECT product_code, dim1, dim3 FROM dims_sdprc
GROUP BY norm.product_code, md.val1, md.val3
),
mapped AS (
SELECT

View File

@@ -431,37 +431,6 @@ WHERE is_active = TRUE
return out
}
loadDimCombosFromCache := func(productCode string) ([]dimCombo, error) {
productCode = strings.TrimSpace(productCode)
if productCode == "" {
return nil, nil
}
rows, err := pgTx.QueryContext(ctx, `
SELECT dim1, dim3
FROM mk_mmitem_dim_combo
WHERE product_code = $1
ORDER BY dim1, dim3_key
`, productCode)
if err != nil {
return nil, err
}
defer rows.Close()
out := make([]dimCombo, 0, 32)
for rows.Next() {
var d1 int64
var d3 sql.NullInt64
if err := rows.Scan(&d1, &d3); err != nil {
return nil, err
}
if d1 <= 0 {
continue
}
out = append(out, dimCombo{Dim1: d1, Dim3: d3})
}
return out, rows.Err()
}
parseDimID := func(s string) (int64, bool) {
s = strings.TrimSpace(s)
if s == "" {
@@ -812,7 +781,7 @@ filtered AS (
AND price > 0
),
grouped AS (
-- Ensure one row per business key to avoid unique violations under strict constraints (e.g. uq_sdprc_3).
-- Ensure one row per business key to avoid unique violations under strict constraints.
SELECT
sdprcgrp_id,
currency AS crn,
@@ -822,16 +791,35 @@ grouped AS (
FROM filtered
GROUP BY sdprcgrp_id, currency, dim1, dim3
),
upserted AS (
updated AS (
UPDATE sdprc s
SET prc = g.prc,
zlins_dttm = now()
FROM grouped g
WHERE s.mmitem_id = $2::bigint
AND s.sdprcgrp_id = g.sdprcgrp_id
AND s.crn = g.crn
AND s.dim1 = g.dim1
AND COALESCE(s.dim3, 0) = COALESCE(g.dim3, 0)
AND s.prc IS DISTINCT FROM g.prc
RETURNING 1
),
inserted AS (
INSERT INTO sdprc (mmitem_id, sdprcgrp_id, crn, dim1, dim3, prc, zlins_dttm)
SELECT $2::bigint, g.sdprcgrp_id, g.crn, g.dim1, g.dim3, g.prc, now()
FROM grouped g
ON CONFLICT ON CONSTRAINT uq_sdprc_3
DO UPDATE SET prc = EXCLUDED.prc, zlins_dttm = EXCLUDED.zlins_dttm
WHERE sdprc.prc IS DISTINCT FROM EXCLUDED.prc
WHERE NOT EXISTS (
SELECT 1
FROM sdprc s
WHERE s.mmitem_id = $2::bigint
AND s.sdprcgrp_id = g.sdprcgrp_id
AND s.crn = g.crn
AND s.dim1 = g.dim1
AND COALESCE(s.dim3, 0) = COALESCE(g.dim3, 0)
)
RETURNING 1
)
SELECT COUNT(*)::int FROM upserted;
SELECT ((SELECT COUNT(*) FROM updated) + (SELECT COUNT(*) FROM inserted))::int;
`
var inserted int
if err := pgTx.QueryRowContext(ctx, q, raw, mmItemID).Scan(&inserted); err != nil {
@@ -1083,28 +1071,8 @@ VALUES (
_ = upsertDimCombosCache(code, dims) // best-effort cache fill
}
// 2) Cache fallback (fast).
cacheStarted := time.Now()
if len(dims) == 0 {
cached, cacheErr := loadDimCombosFromCache(code)
if cacheErr == nil && len(cached) > 0 {
dims = cached
logger.Info("save:pg:dims:cache:hit",
"product_code", code,
"dims", len(dims),
"duration_ms", time.Since(cacheStarted).Milliseconds(),
)
} else if cacheErr != nil {
logger.Error("save:pg:dims:cache-load:error", "product_code", code, "err", cacheErr)
} else {
logger.Info("save:pg:dims:cache:miss",
"product_code", code,
"duration_ms", time.Since(cacheStarted).Milliseconds(),
)
}
}
// 3) Last resort: MSSQL stock tokens (legacy).
// 2) Last resort: MSSQL stock tokens, then seed mmitem_dim. Do not use
// mk_mmitem_dim_combo as a write source; stale cache rows can create wrong keys.
if len(dims) == 0 {
d, err := loadDimsFromMssqlStock(code)
if err != nil {