Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-04-20 09:42:34 +03:00
parent a1f5c653c6
commit 7ef12df93a
4 changed files with 212 additions and 180 deletions

View File

@@ -5,6 +5,7 @@ import (
"bssapp-backend/models" "bssapp-backend/models"
"context" "context"
"database/sql" "database/sql"
"fmt"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -16,176 +17,24 @@ func GetProductPricingList(ctx context.Context, limit int, afterProductCode stri
} }
afterProductCode = strings.TrimSpace(afterProductCode) afterProductCode = strings.TrimSpace(afterProductCode)
cursorFilter := "" // Stage 1: fetch only paged products first (fast path).
args := make([]any, 0, 1) productQuery := `
if afterProductCode != "" { SELECT TOP (` + strconv.Itoa(limit) + `)
cursorFilter = "WHERE bp.ProductCode > @p1" LTRIM(RTRIM(ProductCode)) AS ProductCode,
args = append(args, afterProductCode) COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '') AS AskiliYan,
} COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '') AS Kategori,
COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '') AS UrunIlkGrubu,
query := ` COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '') AS UrunAnaGrubu,
WITH base_products AS ( COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '') AS UrunAltGrubu,
SELECT COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '') AS Icerik,
LTRIM(RTRIM(ProductCode)) AS ProductCode, COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '') AS Karisim,
COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '') AS AskiliYan, COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '') AS Marka
COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '') AS Kategori, FROM ProductFilterWithDescription('TR')
COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '') AS UrunIlkGrubu, WHERE ProductAtt42 IN ('SERI', 'AKSESUAR')
COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '') AS UrunAnaGrubu, AND IsBlocked = 0
COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '') AS UrunAltGrubu, AND LEN(LTRIM(RTRIM(ProductCode))) = 13
COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '') AS Icerik, AND (@p1 = '' OR LTRIM(RTRIM(ProductCode)) > @p1)
COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '') AS Karisim, ORDER BY LTRIM(RTRIM(ProductCode));
COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '') AS Marka
FROM ProductFilterWithDescription('TR')
WHERE ProductAtt42 IN ('SERI', 'AKSESUAR')
AND IsBlocked = 0
AND LEN(LTRIM(RTRIM(ProductCode))) = 13
),
paged_products AS (
SELECT TOP (` + strconv.Itoa(limit) + `)
bp.ProductCode
FROM base_products bp
` + cursorFilter + `
ORDER BY bp.ProductCode
),
latest_base_price AS (
SELECT
LTRIM(RTRIM(b.ItemCode)) AS ItemCode,
CAST(b.Price AS DECIMAL(18, 2)) AS CostPrice,
CONVERT(VARCHAR(10), b.PriceDate, 23) AS LastPricingDate,
ROW_NUMBER() OVER (
PARTITION BY LTRIM(RTRIM(b.ItemCode))
ORDER BY b.PriceDate DESC, b.LastUpdatedDate DESC
) AS rn
FROM prItemBasePrice b
WHERE b.ItemTypeCode = 1
AND b.BasePriceCode = 1
AND LTRIM(RTRIM(b.CurrencyCode)) = 'USD'
AND EXISTS (
SELECT 1
FROM paged_products pp
WHERE pp.ProductCode = LTRIM(RTRIM(b.ItemCode))
)
),
stock_entry_dates AS (
SELECT
LTRIM(RTRIM(s.ItemCode)) AS ItemCode,
CONVERT(VARCHAR(10), MAX(s.OperationDate), 23) AS StockEntryDate
FROM trStock s WITH(NOLOCK)
WHERE s.ItemTypeCode = 1
AND LEN(LTRIM(RTRIM(s.ItemCode))) = 13
AND s.In_Qty1 > 0
AND LTRIM(RTRIM(s.WarehouseCode)) IN (
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
'1-0-33','101','1-014','1-0-49','1-0-36'
)
AND EXISTS (
SELECT 1
FROM paged_products pp
WHERE pp.ProductCode = LTRIM(RTRIM(s.ItemCode))
)
GROUP BY LTRIM(RTRIM(s.ItemCode))
),
stock_base AS (
SELECT
LTRIM(RTRIM(s.ItemCode)) AS ItemCode,
SUM(s.In_Qty1 - s.Out_Qty1) AS InventoryQty1
FROM trStock s WITH(NOLOCK)
WHERE s.ItemTypeCode = 1
AND LEN(LTRIM(RTRIM(s.ItemCode))) = 13
AND EXISTS (
SELECT 1
FROM paged_products pp
WHERE pp.ProductCode = LTRIM(RTRIM(s.ItemCode))
)
GROUP BY LTRIM(RTRIM(s.ItemCode))
),
pick_base AS (
SELECT
LTRIM(RTRIM(p.ItemCode)) AS ItemCode,
SUM(p.Qty1) AS PickingQty1
FROM PickingStates p
WHERE p.ItemTypeCode = 1
AND LEN(LTRIM(RTRIM(p.ItemCode))) = 13
AND EXISTS (
SELECT 1
FROM paged_products pp
WHERE pp.ProductCode = LTRIM(RTRIM(p.ItemCode))
)
GROUP BY LTRIM(RTRIM(p.ItemCode))
),
reserve_base AS (
SELECT
LTRIM(RTRIM(r.ItemCode)) AS ItemCode,
SUM(r.Qty1) AS ReserveQty1
FROM ReserveStates r
WHERE r.ItemTypeCode = 1
AND LEN(LTRIM(RTRIM(r.ItemCode))) = 13
AND EXISTS (
SELECT 1
FROM paged_products pp
WHERE pp.ProductCode = LTRIM(RTRIM(r.ItemCode))
)
GROUP BY LTRIM(RTRIM(r.ItemCode))
),
disp_base AS (
SELECT
LTRIM(RTRIM(d.ItemCode)) AS ItemCode,
SUM(d.Qty1) AS DispOrderQty1
FROM DispOrderStates d
WHERE d.ItemTypeCode = 1
AND LEN(LTRIM(RTRIM(d.ItemCode))) = 13
AND EXISTS (
SELECT 1
FROM paged_products pp
WHERE pp.ProductCode = LTRIM(RTRIM(d.ItemCode))
)
GROUP BY LTRIM(RTRIM(d.ItemCode))
),
stock_totals AS (
SELECT
pp.ProductCode AS ItemCode,
CAST(ROUND(
ISNULL(sb.InventoryQty1, 0)
- ISNULL(pb.PickingQty1, 0)
- ISNULL(rb.ReserveQty1, 0)
- ISNULL(db.DispOrderQty1, 0)
, 2) AS DECIMAL(18, 2)) AS StockQty
FROM paged_products pp
LEFT JOIN stock_base sb
ON sb.ItemCode = pp.ProductCode
LEFT JOIN pick_base pb
ON pb.ItemCode = pp.ProductCode
LEFT JOIN reserve_base rb
ON rb.ItemCode = pp.ProductCode
LEFT JOIN disp_base db
ON db.ItemCode = pp.ProductCode
)
SELECT
bp.ProductCode AS ProductCode,
COALESCE(lp.CostPrice, 0) AS CostPrice,
COALESCE(st.StockQty, 0) AS StockQty,
COALESCE(se.StockEntryDate, '') AS StockEntryDate,
COALESCE(lp.LastPricingDate, '') AS LastPricingDate,
bp.AskiliYan,
bp.Kategori,
bp.UrunIlkGrubu,
bp.UrunAnaGrubu,
bp.UrunAltGrubu,
bp.Icerik,
bp.Karisim,
bp.Marka
FROM paged_products pp
INNER JOIN base_products bp
ON bp.ProductCode = pp.ProductCode
LEFT JOIN latest_base_price lp
ON lp.ItemCode = bp.ProductCode
AND lp.rn = 1
LEFT JOIN stock_entry_dates se
ON se.ItemCode = bp.ProductCode
LEFT JOIN stock_totals st
ON st.ItemCode = bp.ProductCode
ORDER BY bp.ProductCode;
` `
var ( var (
@@ -194,7 +43,7 @@ func GetProductPricingList(ctx context.Context, limit int, afterProductCode stri
) )
for attempt := 1; attempt <= 3; attempt++ { for attempt := 1; attempt <= 3; attempt++ {
var err error var err error
rows, err = db.MssqlDB.QueryContext(ctx, query, args...) rows, err = db.MssqlDB.QueryContext(ctx, productQuery, afterProductCode)
if err == nil { if err == nil {
rowsErr = nil rowsErr = nil
break break
@@ -215,15 +64,11 @@ func GetProductPricingList(ctx context.Context, limit int, afterProductCode stri
} }
defer rows.Close() defer rows.Close()
var out []models.ProductPricing out := make([]models.ProductPricing, 0, limit)
for rows.Next() { for rows.Next() {
var item models.ProductPricing var item models.ProductPricing
if err := rows.Scan( if err := rows.Scan(
&item.ProductCode, &item.ProductCode,
&item.CostPrice,
&item.StockQty,
&item.StockEntryDate,
&item.LastPricingDate,
&item.AskiliYan, &item.AskiliYan,
&item.Kategori, &item.Kategori,
&item.UrunIlkGrubu, &item.UrunIlkGrubu,
@@ -237,6 +82,171 @@ func GetProductPricingList(ctx context.Context, limit int, afterProductCode stri
} }
out = append(out, item) out = append(out, item)
} }
if err := rows.Err(); err != nil {
return nil, err
}
if len(out) == 0 {
return out, nil
}
// Stage 2: fetch metrics only for paged product codes.
codes := make([]string, 0, len(out))
for _, item := range out {
codes = append(codes, strings.TrimSpace(item.ProductCode))
}
codesCSV := strings.Join(codes, ",")
metricsQuery := `
WITH req_codes AS (
SELECT DISTINCT LTRIM(RTRIM(value)) AS ProductCode
FROM STRING_SPLIT(@p1, ',')
WHERE LEN(LTRIM(RTRIM(value))) > 0
),
latest_base_price AS (
SELECT
LTRIM(RTRIM(b.ItemCode)) AS ItemCode,
CAST(b.Price AS DECIMAL(18, 2)) AS CostPrice,
CONVERT(VARCHAR(10), b.PriceDate, 23) AS LastPricingDate,
ROW_NUMBER() OVER (
PARTITION BY LTRIM(RTRIM(b.ItemCode))
ORDER BY b.PriceDate DESC, b.LastUpdatedDate DESC
) AS rn
FROM prItemBasePrice b
INNER JOIN req_codes rc
ON rc.ProductCode = LTRIM(RTRIM(b.ItemCode))
WHERE b.ItemTypeCode = 1
AND b.BasePriceCode = 1
AND LTRIM(RTRIM(b.CurrencyCode)) = 'USD'
),
stock_entry_dates AS (
SELECT
LTRIM(RTRIM(s.ItemCode)) AS ItemCode,
CONVERT(VARCHAR(10), MAX(s.OperationDate), 23) AS StockEntryDate
FROM trStock s WITH(NOLOCK)
INNER JOIN req_codes rc
ON rc.ProductCode = LTRIM(RTRIM(s.ItemCode))
WHERE s.ItemTypeCode = 1
AND LEN(LTRIM(RTRIM(s.ItemCode))) = 13
AND s.In_Qty1 > 0
AND LTRIM(RTRIM(s.WarehouseCode)) IN (
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
'1-0-33','101','1-014','1-0-49','1-0-36'
)
GROUP BY LTRIM(RTRIM(s.ItemCode))
),
stock_base AS (
SELECT
LTRIM(RTRIM(s.ItemCode)) AS ItemCode,
SUM(s.In_Qty1 - s.Out_Qty1) AS InventoryQty1
FROM trStock s WITH(NOLOCK)
INNER JOIN req_codes rc
ON rc.ProductCode = LTRIM(RTRIM(s.ItemCode))
WHERE s.ItemTypeCode = 1
AND LEN(LTRIM(RTRIM(s.ItemCode))) = 13
GROUP BY LTRIM(RTRIM(s.ItemCode))
),
pick_base AS (
SELECT
LTRIM(RTRIM(p.ItemCode)) AS ItemCode,
SUM(p.Qty1) AS PickingQty1
FROM PickingStates p
INNER JOIN req_codes rc
ON rc.ProductCode = LTRIM(RTRIM(p.ItemCode))
WHERE p.ItemTypeCode = 1
AND LEN(LTRIM(RTRIM(p.ItemCode))) = 13
GROUP BY LTRIM(RTRIM(p.ItemCode))
),
reserve_base AS (
SELECT
LTRIM(RTRIM(r.ItemCode)) AS ItemCode,
SUM(r.Qty1) AS ReserveQty1
FROM ReserveStates r
INNER JOIN req_codes rc
ON rc.ProductCode = LTRIM(RTRIM(r.ItemCode))
WHERE r.ItemTypeCode = 1
AND LEN(LTRIM(RTRIM(r.ItemCode))) = 13
GROUP BY LTRIM(RTRIM(r.ItemCode))
),
disp_base AS (
SELECT
LTRIM(RTRIM(d.ItemCode)) AS ItemCode,
SUM(d.Qty1) AS DispOrderQty1
FROM DispOrderStates d
INNER JOIN req_codes rc
ON rc.ProductCode = LTRIM(RTRIM(d.ItemCode))
WHERE d.ItemTypeCode = 1
AND LEN(LTRIM(RTRIM(d.ItemCode))) = 13
GROUP BY LTRIM(RTRIM(d.ItemCode))
)
SELECT
rc.ProductCode,
COALESCE(lp.CostPrice, 0) AS CostPrice,
CAST(ROUND(
ISNULL(sb.InventoryQty1, 0)
- ISNULL(pb.PickingQty1, 0)
- ISNULL(rb.ReserveQty1, 0)
- ISNULL(db.DispOrderQty1, 0)
, 2) AS DECIMAL(18, 2)) AS StockQty,
COALESCE(se.StockEntryDate, '') AS StockEntryDate,
COALESCE(lp.LastPricingDate, '') AS LastPricingDate
FROM req_codes rc
LEFT JOIN latest_base_price lp
ON lp.ItemCode = rc.ProductCode
AND lp.rn = 1
LEFT JOIN stock_entry_dates se
ON se.ItemCode = rc.ProductCode
LEFT JOIN stock_base sb
ON sb.ItemCode = rc.ProductCode
LEFT JOIN pick_base pb
ON pb.ItemCode = rc.ProductCode
LEFT JOIN reserve_base rb
ON rb.ItemCode = rc.ProductCode
LEFT JOIN disp_base db
ON db.ItemCode = rc.ProductCode;
`
metricsRows, err := db.MssqlDB.QueryContext(ctx, metricsQuery, codesCSV)
if err != nil {
return nil, fmt.Errorf("metrics query failed: %w", err)
}
defer metricsRows.Close()
type metrics struct {
CostPrice float64
StockQty float64
StockEntryDate string
LastPricingDate string
}
metricsByCode := make(map[string]metrics, len(out))
for metricsRows.Next() {
var (
code string
m metrics
)
if err := metricsRows.Scan(
&code,
&m.CostPrice,
&m.StockQty,
&m.StockEntryDate,
&m.LastPricingDate,
); err != nil {
return nil, err
}
metricsByCode[strings.TrimSpace(code)] = m
}
if err := metricsRows.Err(); err != nil {
return nil, err
}
for i := range out {
if m, ok := metricsByCode[strings.TrimSpace(out[i].ProductCode)]; ok {
out[i].CostPrice = m.CostPrice
out[i].StockQty = m.StockQty
out[i].StockEntryDate = m.StockEntryDate
out[i].LastPricingDate = m.LastPricingDate
}
}
return out, nil return out, nil
} }

View File

@@ -26,7 +26,8 @@ func GetProductPricingListHandler(w http.ResponseWriter, r *http.Request) {
} }
log.Printf("[ProductPricing] trace=%s start user=%s id=%d", traceID, claims.Username, claims.ID) log.Printf("[ProductPricing] trace=%s start user=%s id=%d", traceID, claims.Username, claims.ID)
ctx, cancel := context.WithTimeout(r.Context(), 180*time.Second) // Cloudflare upstream timeout is lower than 180s; fail fast and return API 504 instead of CDN 524.
ctx, cancel := context.WithTimeout(r.Context(), 110*time.Second)
defer cancel() defer cancel()
limit := 500 limit := 500

View File

@@ -59,6 +59,7 @@ var (
reScriptLabelProp = regexp.MustCompile(`\blabel\s*:\s*['"]([^'"]{2,180})['"]`) reScriptLabelProp = regexp.MustCompile(`\blabel\s*:\s*['"]([^'"]{2,180})['"]`)
reScriptUIProp = regexp.MustCompile(`\b(?:label|message|title|placeholder|hint)\s*:\s*['"]([^'"]{2,180})['"]`) reScriptUIProp = regexp.MustCompile(`\b(?:label|message|title|placeholder|hint)\s*:\s*['"]([^'"]{2,180})['"]`)
reTemplateDynamic = regexp.MustCompile(`[{][{]|[}][}]`) reTemplateDynamic = regexp.MustCompile(`[{][{]|[}][}]`)
reCodeLikeText = regexp.MustCompile(`(?i)(\bconst\b|\blet\b|\bvar\b|\breturn\b|\bfunction\b|=>|\|\||&&|\?\?|//|/\*|\*/|\.trim\(|\.replace\(|\.map\(|\.filter\()`)
) )
var translationNoiseTokens = map[string]struct{}{ var translationNoiseTokens = map[string]struct{}{
@@ -1800,9 +1801,22 @@ func isCandidateText(s string) bool {
if strings.Contains(s, "/api/") { if strings.Contains(s, "/api/") {
return false return false
} }
if reCodeLikeText.MatchString(s) {
return false
}
if strings.ContainsAny(s, "{}[];`") { if strings.ContainsAny(s, "{}[];`") {
return false return false
} }
symbolCount := 0
for _, r := range s {
switch r {
case '(', ')', '=', ':', '/', '\\', '|', '&', '*', '<', '>', '_':
symbolCount++
}
}
if symbolCount >= 4 {
return false
}
return true return true
} }

View File

@@ -894,9 +894,16 @@ async function reloadData () {
console.info('[product-pricing][ui] reload:start', { console.info('[product-pricing][ui] reload:start', {
at: new Date(startedAt).toISOString() at: new Date(startedAt).toISOString()
}) })
nextCursor.value = '' try {
await fetchChunk({ reset: true }) nextCursor.value = ''
await ensureEnoughVisibleRows(120, 6) await fetchChunk({ reset: true })
await ensureEnoughVisibleRows(120, 6)
} catch (err) {
console.error('[product-pricing][ui] reload:error', {
duration_ms: Date.now() - startedAt,
message: String(err?.message || err || 'reload failed')
})
}
console.info('[product-pricing][ui] reload:done', { console.info('[product-pricing][ui] reload:done', {
duration_ms: Date.now() - startedAt, duration_ms: Date.now() - startedAt,
row_count: Array.isArray(store.rows) ? store.rows.length : 0, row_count: Array.isArray(store.rows) ? store.rows.length : 0,