Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -10,6 +10,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
type ProductPricingFilters struct {
|
||||
@@ -34,7 +36,7 @@ type ProductPricingPage struct {
|
||||
Limit int
|
||||
}
|
||||
|
||||
func GetProductPricingPage(ctx context.Context, page int, limit int, filters ProductPricingFilters) (ProductPricingPage, error) {
|
||||
func GetProductPricingPage(ctx context.Context, page int, limit int, filters ProductPricingFilters, includeTotal bool, sortBy string, descending bool) (ProductPricingPage, error) {
|
||||
result := ProductPricingPage{
|
||||
Rows: []models.ProductPricing{},
|
||||
TotalCount: 0,
|
||||
@@ -114,34 +116,53 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
||||
}
|
||||
whereSQL := strings.Join(whereParts, " AND ")
|
||||
|
||||
countQuery := `
|
||||
SELECT COUNT(DISTINCT LTRIM(RTRIM(ProductCode)))
|
||||
FROM ProductFilterWithDescription('TR')
|
||||
WHERE ` + whereSQL + `;
|
||||
`
|
||||
var totalCount int
|
||||
if err := db.MssqlDB.QueryRowContext(ctx, countQuery, args...).Scan(&totalCount); err != nil {
|
||||
return result, err
|
||||
}
|
||||
result.TotalCount = totalCount
|
||||
if totalCount == 0 {
|
||||
if includeTotal {
|
||||
countQuery := `
|
||||
SELECT COUNT(DISTINCT LTRIM(RTRIM(ProductCode)))
|
||||
FROM ProductFilterWithDescription('TR')
|
||||
WHERE ` + whereSQL + `;
|
||||
`
|
||||
var totalCount int
|
||||
if err := db.MssqlDB.QueryRowContext(ctx, countQuery, args...).Scan(&totalCount); err != nil {
|
||||
return result, err
|
||||
}
|
||||
result.TotalCount = totalCount
|
||||
if totalCount == 0 {
|
||||
result.TotalPages = 0
|
||||
result.Page = 1
|
||||
return result, nil
|
||||
}
|
||||
totalPages := int(math.Ceil(float64(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 {
|
||||
// Skip COUNT(*) for performance; client will infer hasMore from page size.
|
||||
result.TotalCount = 0
|
||||
result.TotalPages = 0
|
||||
result.Page = 1
|
||||
return result, nil
|
||||
result.Page = page
|
||||
result.Limit = limit
|
||||
}
|
||||
totalPages := int(math.Ceil(float64(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
|
||||
|
||||
// Stage 1: fetch only paged products first (fast path).
|
||||
sortBy = strings.TrimSpace(sortBy)
|
||||
orderDir := "DESC"
|
||||
if !descending {
|
||||
orderDir = "ASC"
|
||||
}
|
||||
// Only allow a small safe list.
|
||||
orderExpr := "CAST(ROUND(ISNULL(sb.InventoryQty1, 0), 2) AS DECIMAL(18, 2))"
|
||||
if sortBy == "productCode" {
|
||||
orderExpr = "rc.ProductCode"
|
||||
orderDir = "ASC"
|
||||
}
|
||||
productQuery := `
|
||||
IF OBJECT_ID('tempdb..#req_codes') IS NOT NULL DROP TABLE #req_codes;
|
||||
IF OBJECT_ID('tempdb..#stock_base') IS NOT NULL DROP TABLE #stock_base;
|
||||
@@ -156,7 +177,8 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
||||
MAX(f.UrunAltGrubu) AS UrunAltGrubu,
|
||||
MAX(f.Icerik) AS Icerik,
|
||||
MAX(f.Karisim) AS Karisim,
|
||||
MAX(f.Marka) AS Marka
|
||||
MAX(f.Marka) AS Marka,
|
||||
MAX(f.BrandCode) AS BrandCode
|
||||
INTO #req_codes
|
||||
FROM (
|
||||
SELECT
|
||||
@@ -169,7 +191,8 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '') AS UrunAltGrubu,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '') AS Icerik,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '') AS Karisim,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '') AS Marka
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '') AS Marka,
|
||||
COALESCE(LTRIM(RTRIM(ProductAtt10)), '') AS BrandCode
|
||||
FROM ProductFilterWithDescription('TR')
|
||||
WHERE ` + whereSQL + `
|
||||
) f
|
||||
@@ -200,12 +223,13 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
||||
rc.UrunAltGrubu,
|
||||
rc.Icerik,
|
||||
rc.Karisim,
|
||||
rc.Marka
|
||||
rc.Marka,
|
||||
rc.BrandCode
|
||||
FROM #req_codes rc
|
||||
LEFT JOIN #stock_base sb
|
||||
ON sb.ItemCode = rc.ProductCode
|
||||
ORDER BY
|
||||
CAST(ROUND(ISNULL(sb.InventoryQty1, 0), 2) AS DECIMAL(18, 2)) DESC,
|
||||
` + orderExpr + ` ` + orderDir + `,
|
||||
rc.ProductCode ASC
|
||||
OFFSET ` + strconv.Itoa(offset) + ` ROWS
|
||||
FETCH NEXT ` + strconv.Itoa(limit) + ` ROWS ONLY;
|
||||
@@ -252,6 +276,7 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
||||
&item.Icerik,
|
||||
&item.Karisim,
|
||||
&item.Marka,
|
||||
&item.BrandCode,
|
||||
); err != nil {
|
||||
return result, err
|
||||
}
|
||||
@@ -284,6 +309,39 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
||||
FROM (VALUES ` + strings.Join(valueRows, ",") + `) v(ProductCode)
|
||||
WHERE LEN(LTRIM(RTRIM(v.ProductCode))) > 0
|
||||
),
|
||||
latest_pricelist_line AS (
|
||||
-- Base prices from Nebim V3 price lists (trPriceListLine).
|
||||
-- Pick the latest record per (ItemCode, Currency) using ValidDate/ValidTime, then LastUpdatedDate.
|
||||
SELECT
|
||||
LTRIM(RTRIM(p.ItemCode)) AS ItemCode,
|
||||
LTRIM(RTRIM(p.DocCurrencyCode)) AS DocCurrencyCode,
|
||||
CAST(p.Price AS DECIMAL(18, 2)) AS Price,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY LTRIM(RTRIM(p.ItemCode)), LTRIM(RTRIM(p.DocCurrencyCode))
|
||||
ORDER BY p.ValidDate DESC, p.ValidTime DESC, p.LastUpdatedDate DESC
|
||||
) AS rn
|
||||
FROM dbo.trPriceListLine p WITH(NOLOCK)
|
||||
INNER JOIN req_codes rc
|
||||
ON rc.ProductCode = LTRIM(RTRIM(p.ItemCode))
|
||||
WHERE p.ItemTypeCode = 1
|
||||
AND ISNULL(p.IsDisabled, 0) = 0
|
||||
AND LTRIM(RTRIM(p.DocCurrencyCode)) IN ('USD', 'TRY')
|
||||
AND (
|
||||
(LTRIM(RTRIM(p.DocCurrencyCode)) = 'USD' AND LTRIM(RTRIM(p.PriceGroupCode)) = 'TM-USD')
|
||||
OR (LTRIM(RTRIM(p.DocCurrencyCode)) = 'TRY' AND LTRIM(RTRIM(p.PriceGroupCode)) = 'TM-TRY')
|
||||
)
|
||||
AND p.Price IS NOT NULL
|
||||
AND p.Price > 0
|
||||
),
|
||||
base_prices AS (
|
||||
SELECT
|
||||
ItemCode,
|
||||
MAX(CASE WHEN DocCurrencyCode = 'USD' THEN Price END) AS BasePriceUsd,
|
||||
MAX(CASE WHEN DocCurrencyCode = 'TRY' THEN Price END) AS BasePriceTry
|
||||
FROM latest_pricelist_line
|
||||
WHERE rn = 1
|
||||
GROUP BY ItemCode
|
||||
),
|
||||
latest_base_price AS (
|
||||
SELECT
|
||||
LTRIM(RTRIM(b.ItemCode)) AS ItemCode,
|
||||
@@ -365,6 +423,8 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
||||
SELECT
|
||||
rc.ProductCode,
|
||||
COALESCE(lp.CostPrice, 0) AS CostPrice,
|
||||
COALESCE(bp.BasePriceUsd, 0) AS BasePriceUsd,
|
||||
COALESCE(bp.BasePriceTry, 0) AS BasePriceTry,
|
||||
CAST(ROUND(
|
||||
ISNULL(sb.InventoryQty1, 0)
|
||||
- ISNULL(pb.PickingQty1, 0)
|
||||
@@ -372,11 +432,14 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
||||
- ISNULL(db.DispOrderQty1, 0)
|
||||
, 2) AS DECIMAL(18, 2)) AS StockQty,
|
||||
COALESCE(se.StockEntryDate, '') AS StockEntryDate,
|
||||
'' AS LastCostingDate,
|
||||
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 base_prices bp
|
||||
ON bp.ItemCode = rc.ProductCode
|
||||
LEFT JOIN stock_entry_dates se
|
||||
ON se.ItemCode = rc.ProductCode
|
||||
LEFT JOIN stock_base sb
|
||||
@@ -397,8 +460,11 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
||||
|
||||
type metrics struct {
|
||||
CostPrice float64
|
||||
BasePriceUsd float64
|
||||
BasePriceTry float64
|
||||
StockQty float64
|
||||
StockEntryDate string
|
||||
LastCostingDate string
|
||||
LastPricingDate string
|
||||
}
|
||||
metricsByCode := make(map[string]metrics, len(out))
|
||||
@@ -410,8 +476,11 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
||||
if err := metricsRows.Scan(
|
||||
&code,
|
||||
&m.CostPrice,
|
||||
&m.BasePriceUsd,
|
||||
&m.BasePriceTry,
|
||||
&m.StockQty,
|
||||
&m.StockEntryDate,
|
||||
&m.LastCostingDate,
|
||||
&m.LastPricingDate,
|
||||
); err != nil {
|
||||
return result, err
|
||||
@@ -425,12 +494,222 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
||||
for i := range out {
|
||||
if m, ok := metricsByCode[strings.TrimSpace(out[i].ProductCode)]; ok {
|
||||
out[i].CostPrice = m.CostPrice
|
||||
out[i].BasePriceUsd = m.BasePriceUsd
|
||||
out[i].BasePriceTry = m.BasePriceTry
|
||||
out[i].StockQty = m.StockQty
|
||||
out[i].StockEntryDate = m.StockEntryDate
|
||||
out[i].LastCostingDate = m.LastCostingDate
|
||||
out[i].LastPricingDate = m.LastPricingDate
|
||||
}
|
||||
}
|
||||
|
||||
// Stage 3: fetch costing date from UretimDB (separate MSSQL catalog).
|
||||
// Pricing DB may not contain spUrtOnMLMas; do not fail listing on costing query errors.
|
||||
if uretimDB := db.GetUretimDB(); uretimDB != nil {
|
||||
costingQuery := `
|
||||
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
|
||||
)
|
||||
SELECT
|
||||
LTRIM(RTRIM(m.UrunKodu)) AS UrunKodu,
|
||||
CONVERT(VARCHAR(10), MAX(m.Tarihi), 23) AS LastCostingDate
|
||||
FROM dbo.spUrtOnMLMas m WITH(NOLOCK)
|
||||
INNER JOIN req_codes rc
|
||||
ON rc.ProductCode = LTRIM(RTRIM(m.UrunKodu))
|
||||
GROUP BY LTRIM(RTRIM(m.UrunKodu));
|
||||
`
|
||||
costRows, err := uretimDB.QueryContext(ctx, costingQuery, metricArgs...)
|
||||
if err == nil {
|
||||
costingByCode := make(map[string]string, len(out))
|
||||
for costRows.Next() {
|
||||
var code, d string
|
||||
if err := costRows.Scan(&code, &d); err != nil {
|
||||
_ = costRows.Close()
|
||||
costRows = nil
|
||||
break
|
||||
}
|
||||
costingByCode[strings.TrimSpace(code)] = strings.TrimSpace(d)
|
||||
}
|
||||
if costRows != nil {
|
||||
_ = costRows.Close()
|
||||
for i := range out {
|
||||
if d, ok := costingByCode[strings.TrimSpace(out[i].ProductCode)]; ok && d != "" {
|
||||
out[i].LastCostingDate = d
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stage 4: fetch latest tier prices (USD1..6, EUR1..6, TRY1..6) from PostgreSQL sdprc/mmitem.
|
||||
if pg := db.PgDB; pg != nil {
|
||||
type tierRow struct {
|
||||
Code string
|
||||
Grp int
|
||||
Crn string
|
||||
Prc float64
|
||||
}
|
||||
tierSQL := `
|
||||
WITH ranked AS (
|
||||
SELECT
|
||||
mmitem.code AS code,
|
||||
sdprc.sdprcgrp_id AS grp,
|
||||
sdprc.crn AS crn,
|
||||
COALESCE(sdprc.prc, 0) AS prc,
|
||||
ROW_NUMBER() OVER (
|
||||
PARTITION BY mmitem.code, sdprc.crn, sdprc.sdprcgrp_id
|
||||
ORDER BY sdprc.zlins_dttm DESC
|
||||
) AS rn
|
||||
FROM sdprc
|
||||
JOIN mmitem ON mmitem.id = sdprc.mmitem_id
|
||||
WHERE mmitem.code = ANY($1)
|
||||
AND sdprc.sdprcgrp_id BETWEEN 1 AND 6
|
||||
AND sdprc.crn IN ('USD', 'EUR', 'TRY')
|
||||
AND sdprc.prc IS NOT NULL
|
||||
AND sdprc.prc > 0
|
||||
)
|
||||
SELECT code, grp, crn, prc
|
||||
FROM ranked
|
||||
WHERE rn = 1;
|
||||
`
|
||||
rows, err := pg.QueryContext(ctx, tierSQL, pq.Array(codes))
|
||||
if err == nil {
|
||||
defer rows.Close()
|
||||
type key struct {
|
||||
Code string
|
||||
}
|
||||
tiers := make(map[string]map[string]map[int]float64, len(out))
|
||||
for rows.Next() {
|
||||
var r tierRow
|
||||
if err := rows.Scan(&r.Code, &r.Grp, &r.Crn, &r.Prc); err != nil {
|
||||
break
|
||||
}
|
||||
r.Code = strings.TrimSpace(r.Code)
|
||||
r.Crn = strings.TrimSpace(strings.ToUpper(r.Crn))
|
||||
if r.Code == "" || r.Grp < 1 || r.Grp > 6 || r.Crn == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := tiers[r.Code]; !ok {
|
||||
tiers[r.Code] = map[string]map[int]float64{}
|
||||
}
|
||||
if _, ok := tiers[r.Code][r.Crn]; !ok {
|
||||
tiers[r.Code][r.Crn] = map[int]float64{}
|
||||
}
|
||||
tiers[r.Code][r.Crn][r.Grp] = r.Prc
|
||||
}
|
||||
|
||||
for i := range out {
|
||||
code := strings.TrimSpace(out[i].ProductCode)
|
||||
m, ok := tiers[code]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
apply := func(crn string, grp int, v float64) {
|
||||
switch crn {
|
||||
case "USD":
|
||||
switch grp {
|
||||
case 1:
|
||||
out[i].USD1 = v
|
||||
case 2:
|
||||
out[i].USD2 = v
|
||||
case 3:
|
||||
out[i].USD3 = v
|
||||
case 4:
|
||||
out[i].USD4 = v
|
||||
case 5:
|
||||
out[i].USD5 = v
|
||||
case 6:
|
||||
out[i].USD6 = v
|
||||
}
|
||||
case "EUR":
|
||||
switch grp {
|
||||
case 1:
|
||||
out[i].EUR1 = v
|
||||
case 2:
|
||||
out[i].EUR2 = v
|
||||
case 3:
|
||||
out[i].EUR3 = v
|
||||
case 4:
|
||||
out[i].EUR4 = v
|
||||
case 5:
|
||||
out[i].EUR5 = v
|
||||
case 6:
|
||||
out[i].EUR6 = v
|
||||
}
|
||||
case "TRY":
|
||||
switch grp {
|
||||
case 1:
|
||||
out[i].TRY1 = v
|
||||
case 2:
|
||||
out[i].TRY2 = v
|
||||
case 3:
|
||||
out[i].TRY3 = v
|
||||
case 4:
|
||||
out[i].TRY4 = v
|
||||
case 5:
|
||||
out[i].TRY5 = v
|
||||
case 6:
|
||||
out[i].TRY6 = v
|
||||
}
|
||||
}
|
||||
}
|
||||
for crn, byGrp := range m {
|
||||
for grp, v := range byGrp {
|
||||
apply(crn, grp, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stage 5: brand group (classification) from Postgres mk_brandgrpmatch.
|
||||
// Show classification result in BrandGroupSec field (read-only in UI).
|
||||
if pg := db.PgDB; pg != nil {
|
||||
brandCodes := make([]string, 0, len(out))
|
||||
seen := make(map[string]struct{}, len(out))
|
||||
for _, it := range out {
|
||||
code := strings.TrimSpace(it.BrandCode)
|
||||
if code == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[code]; ok {
|
||||
continue
|
||||
}
|
||||
seen[code] = struct{}{}
|
||||
brandCodes = append(brandCodes, code)
|
||||
}
|
||||
if len(brandCodes) > 0 {
|
||||
rows, err := pg.QueryContext(ctx, `
|
||||
SELECT
|
||||
m.brand_code,
|
||||
COALESCE(g.title, '') AS grp_title
|
||||
FROM mk_brandgrpmatch m
|
||||
JOIN mk_brandgrp g ON g.id = m.grp_id
|
||||
WHERE m.brand_code = ANY($1)
|
||||
`, pq.Array(brandCodes))
|
||||
if err == nil {
|
||||
defer rows.Close()
|
||||
grpByBrand := make(map[string]string, len(brandCodes))
|
||||
for rows.Next() {
|
||||
var code, title string
|
||||
if err := rows.Scan(&code, &title); err != nil {
|
||||
break
|
||||
}
|
||||
grpByBrand[strings.TrimSpace(code)] = strings.TrimSpace(title)
|
||||
}
|
||||
for i := range out {
|
||||
if title, ok := grpByBrand[strings.TrimSpace(out[i].BrandCode)]; ok {
|
||||
out[i].BrandGroupSec = title
|
||||
} else {
|
||||
out[i].BrandGroupSec = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.Rows = out
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user