1315 lines
37 KiB
Go
1315 lines
37 KiB
Go
package queries
|
|
|
|
import (
|
|
"bssapp-backend/db"
|
|
"bssapp-backend/models"
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/lib/pq"
|
|
)
|
|
|
|
type ProductPricingFilters struct {
|
|
Search string
|
|
ProductCode []string
|
|
BrandGroup []string
|
|
AskiliYan []string
|
|
Kategori []string
|
|
UrunIlkGrubu []string
|
|
UrunAnaGrubu []string
|
|
UrunAltGrubu []string
|
|
Icerik []string
|
|
Karisim []string
|
|
Marka []string
|
|
}
|
|
|
|
type ProductPricingPage struct {
|
|
Rows []models.ProductPricing
|
|
TotalCount int
|
|
TotalPages int
|
|
Page int
|
|
Limit int
|
|
}
|
|
|
|
func GetAllProductPricingRows(ctx context.Context, chunkSize int, filters ProductPricingFilters, sortBy string, descending bool) ([]models.ProductPricing, error) {
|
|
if chunkSize <= 0 || chunkSize > 1000 {
|
|
chunkSize = 1000
|
|
}
|
|
|
|
paramIndex := 1
|
|
args := make([]any, 0, 64)
|
|
nextParam := func() string {
|
|
name := "@p" + strconv.Itoa(paramIndex)
|
|
paramIndex++
|
|
return name
|
|
}
|
|
whereParts := []string{
|
|
"ProductAtt42 IN ('SERI', 'AKSESUAR')",
|
|
"IsBlocked = 0",
|
|
"LEN(LTRIM(RTRIM(ProductCode))) = 13",
|
|
}
|
|
addInFilter := func(expr string, values []string) {
|
|
clean := make([]string, 0, len(values))
|
|
for _, v := range values {
|
|
v = strings.TrimSpace(v)
|
|
if v == "" {
|
|
continue
|
|
}
|
|
clean = append(clean, v)
|
|
}
|
|
if len(clean) == 0 {
|
|
return
|
|
}
|
|
ors := make([]string, 0, len(clean))
|
|
for _, v := range clean {
|
|
p := nextParam()
|
|
ors = append(ors, expr+" = "+p)
|
|
args = append(args, v)
|
|
}
|
|
whereParts = append(whereParts, "("+strings.Join(ors, " OR ")+")")
|
|
}
|
|
brandGroupExpr := `CASE ABS(CHECKSUM(LTRIM(RTRIM(ProductCode)))) % 3
|
|
WHEN 0 THEN 'MARKA GRUBU A'
|
|
WHEN 1 THEN 'MARKA GRUBU B'
|
|
ELSE 'MARKA GRUBU C'
|
|
END`
|
|
addInFilter("LTRIM(RTRIM(ProductCode))", filters.ProductCode)
|
|
addInFilter(brandGroupExpr, filters.BrandGroup)
|
|
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '')", filters.AskiliYan)
|
|
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '')", filters.Kategori)
|
|
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '')", filters.UrunIlkGrubu)
|
|
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '')", filters.UrunAnaGrubu)
|
|
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '')", filters.UrunAltGrubu)
|
|
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '')", filters.Icerik)
|
|
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '')", filters.Karisim)
|
|
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '')", filters.Marka)
|
|
if q := strings.TrimSpace(filters.Search); q != "" {
|
|
p := nextParam()
|
|
args = append(args, "%"+q+"%")
|
|
whereParts = append(whereParts, "("+strings.Join([]string{
|
|
"LTRIM(RTRIM(ProductCode)) LIKE " + p,
|
|
"COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '') LIKE " + p,
|
|
"COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '') LIKE " + p,
|
|
"COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '') LIKE " + p,
|
|
"COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '') LIKE " + p,
|
|
"COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '') LIKE " + p,
|
|
"COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '') LIKE " + p,
|
|
"COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '') LIKE " + p,
|
|
"COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '') LIKE " + p,
|
|
}, " OR ")+")")
|
|
}
|
|
whereSQL := strings.Join(whereParts, " AND ")
|
|
|
|
sortBy = strings.TrimSpace(sortBy)
|
|
orderDir := "DESC"
|
|
if !descending {
|
|
orderDir = "ASC"
|
|
}
|
|
orderExpr := "CAST(ROUND(ISNULL(sb.InventoryQty1, 0) - ISNULL(pb.PickingQty1, 0) - ISNULL(rb.ReserveQty1, 0) - ISNULL(db.DispOrderQty1, 0), 2) AS DECIMAL(18, 2))"
|
|
if sortBy == "productCode" {
|
|
orderExpr = "rc.ProductCode"
|
|
orderDir = "ASC"
|
|
}
|
|
orderBySQL := orderExpr + ` ` + orderDir
|
|
if !strings.EqualFold(strings.TrimSpace(orderExpr), "rc.ProductCode") {
|
|
orderBySQL += `,
|
|
rc.ProductCode ASC`
|
|
}
|
|
|
|
baseQuery := `
|
|
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;
|
|
IF OBJECT_ID('tempdb..#pick_base') IS NOT NULL DROP TABLE #pick_base;
|
|
IF OBJECT_ID('tempdb..#reserve_base') IS NOT NULL DROP TABLE #reserve_base;
|
|
IF OBJECT_ID('tempdb..#disp_base') IS NOT NULL DROP TABLE #disp_base;
|
|
|
|
SELECT
|
|
f.ProductCode,
|
|
MAX(f.BrandGroupSec) AS BrandGroupSec,
|
|
MAX(f.AskiliYan) AS AskiliYan,
|
|
MAX(f.Kategori) AS Kategori,
|
|
MAX(f.UrunIlkGrubu) AS UrunIlkGrubu,
|
|
MAX(f.UrunAnaGrubu) AS UrunAnaGrubu,
|
|
MAX(f.UrunAltGrubu) AS UrunAltGrubu,
|
|
MAX(f.Icerik) AS Icerik,
|
|
MAX(f.Karisim) AS Karisim,
|
|
MAX(f.Marka) AS Marka,
|
|
MAX(f.BrandCode) AS BrandCode
|
|
INTO #req_codes
|
|
FROM (
|
|
SELECT
|
|
LTRIM(RTRIM(ProductCode)) AS ProductCode,
|
|
` + brandGroupExpr + ` AS BrandGroupSec,
|
|
COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '') AS AskiliYan,
|
|
COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '') AS Kategori,
|
|
COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '') AS UrunIlkGrubu,
|
|
COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '') AS UrunAnaGrubu,
|
|
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(ProductAtt10)), '') AS BrandCode
|
|
FROM ProductFilterWithDescription('TR')
|
|
WHERE ` + whereSQL + `
|
|
) f
|
|
GROUP BY f.ProductCode;
|
|
|
|
CREATE CLUSTERED INDEX IX_req_codes_ProductCode ON #req_codes(ProductCode);
|
|
|
|
SELECT
|
|
LTRIM(RTRIM(s.ItemCode)) AS ItemCode,
|
|
SUM(s.In_Qty1 - s.Out_Qty1) AS InventoryQty1
|
|
INTO #stock_base
|
|
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));
|
|
|
|
CREATE CLUSTERED INDEX IX_stock_base_ItemCode ON #stock_base(ItemCode);
|
|
|
|
SELECT
|
|
LTRIM(RTRIM(p.ItemCode)) AS ItemCode,
|
|
SUM(p.Qty1) AS PickingQty1
|
|
INTO #pick_base
|
|
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));
|
|
|
|
SELECT
|
|
LTRIM(RTRIM(r.ItemCode)) AS ItemCode,
|
|
SUM(r.Qty1) AS ReserveQty1
|
|
INTO #reserve_base
|
|
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));
|
|
|
|
SELECT
|
|
LTRIM(RTRIM(d.ItemCode)) AS ItemCode,
|
|
SUM(d.Qty1) AS DispOrderQty1
|
|
INTO #disp_base
|
|
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,
|
|
rc.BrandGroupSec,
|
|
rc.AskiliYan,
|
|
rc.Kategori,
|
|
rc.UrunIlkGrubu,
|
|
rc.UrunAnaGrubu,
|
|
rc.UrunAltGrubu,
|
|
rc.Icerik,
|
|
rc.Karisim,
|
|
rc.Marka,
|
|
rc.BrandCode,
|
|
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 #req_codes rc
|
|
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
|
|
ORDER BY
|
|
` + orderBySQL + `;
|
|
`
|
|
|
|
rows, err := db.MssqlDB.QueryContext(ctx, baseQuery, args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
out := make([]models.ProductPricing, 0, 2048)
|
|
for rows.Next() {
|
|
var item models.ProductPricing
|
|
if err := rows.Scan(
|
|
&item.ProductCode,
|
|
&item.BrandGroupSec,
|
|
&item.AskiliYan,
|
|
&item.Kategori,
|
|
&item.UrunIlkGrubu,
|
|
&item.UrunAnaGrubu,
|
|
&item.UrunAltGrubu,
|
|
&item.Icerik,
|
|
&item.Karisim,
|
|
&item.Marka,
|
|
&item.BrandCode,
|
|
&item.StockQty,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, item)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
if len(out) == 0 {
|
|
return out, nil
|
|
}
|
|
|
|
if err := enrichAllProductPricingRows(ctx, out, chunkSize); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func enrichAllProductPricingRows(ctx context.Context, out []models.ProductPricing, chunkSize int) error {
|
|
if len(out) == 0 {
|
|
return nil
|
|
}
|
|
if chunkSize <= 0 || chunkSize > 1000 {
|
|
chunkSize = 1000
|
|
}
|
|
|
|
indexByCode := make(map[string]int, len(out))
|
|
codes := make([]string, 0, len(out))
|
|
for i := range out {
|
|
code := strings.TrimSpace(out[i].ProductCode)
|
|
if code == "" {
|
|
continue
|
|
}
|
|
indexByCode[code] = i
|
|
codes = append(codes, code)
|
|
}
|
|
|
|
for _, chunk := range chunkStringSlice(codes, chunkSize) {
|
|
valueRows := make([]string, 0, len(chunk))
|
|
metricArgs := make([]any, 0, len(chunk))
|
|
for i, code := range chunk {
|
|
paramName := "@p" + strconv.Itoa(i+1)
|
|
valueRows = append(valueRows, "("+paramName+")")
|
|
metricArgs = append(metricArgs, code)
|
|
}
|
|
|
|
metricsQuery := `
|
|
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
|
|
),
|
|
latest_pricelist_line AS (
|
|
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,
|
|
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.InnerProcessCode)) = 'OP'
|
|
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))
|
|
)
|
|
SELECT
|
|
rc.ProductCode,
|
|
COALESCE(lp.CostPrice, 0) AS CostPrice,
|
|
COALESCE(bp.BasePriceUsd, 0) AS BasePriceUsd,
|
|
COALESCE(bp.BasePriceTry, 0) AS BasePriceTry,
|
|
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 base_prices bp
|
|
ON bp.ItemCode = rc.ProductCode
|
|
LEFT JOIN stock_entry_dates se
|
|
ON se.ItemCode = rc.ProductCode;
|
|
`
|
|
|
|
metricRows, err := db.MssqlDB.QueryContext(ctx, metricsQuery, metricArgs...)
|
|
if err != nil {
|
|
return fmt.Errorf("metrics query failed: %w", err)
|
|
}
|
|
for metricRows.Next() {
|
|
var code string
|
|
var costPrice, basePriceUsd, basePriceTry float64
|
|
var stockEntryDate, lastPricingDate string
|
|
if err := metricRows.Scan(&code, &costPrice, &basePriceUsd, &basePriceTry, &stockEntryDate, &lastPricingDate); err != nil {
|
|
_ = metricRows.Close()
|
|
return err
|
|
}
|
|
if idx, ok := indexByCode[strings.TrimSpace(code)]; ok {
|
|
out[idx].CostPrice = costPrice
|
|
out[idx].BasePriceUsd = basePriceUsd
|
|
out[idx].BasePriceTry = basePriceTry
|
|
out[idx].StockEntryDate = stockEntryDate
|
|
out[idx].LastPricingDate = lastPricingDate
|
|
}
|
|
}
|
|
if err := metricRows.Err(); err != nil {
|
|
_ = metricRows.Close()
|
|
return err
|
|
}
|
|
_ = metricRows.Close()
|
|
|
|
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 {
|
|
for costRows.Next() {
|
|
var code, d string
|
|
if err := costRows.Scan(&code, &d); err != nil {
|
|
_ = costRows.Close()
|
|
return err
|
|
}
|
|
if idx, ok := indexByCode[strings.TrimSpace(code)]; ok && strings.TrimSpace(d) != "" {
|
|
out[idx].LastCostingDate = strings.TrimSpace(d)
|
|
}
|
|
}
|
|
if err := costRows.Err(); err != nil {
|
|
_ = costRows.Close()
|
|
return err
|
|
}
|
|
_ = costRows.Close()
|
|
}
|
|
}
|
|
|
|
if pg := db.PgDB; pg != nil {
|
|
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;
|
|
`
|
|
pgRows, err := pg.QueryContext(ctx, tierSQL, pq.Array(chunk))
|
|
if err == nil {
|
|
for pgRows.Next() {
|
|
var code, crn string
|
|
var grp int
|
|
var prc float64
|
|
if err := pgRows.Scan(&code, &grp, &crn, &prc); err != nil {
|
|
_ = pgRows.Close()
|
|
return err
|
|
}
|
|
idx, ok := indexByCode[strings.TrimSpace(code)]
|
|
if !ok {
|
|
continue
|
|
}
|
|
switch strings.ToUpper(strings.TrimSpace(crn)) {
|
|
case "USD":
|
|
switch grp {
|
|
case 1:
|
|
out[idx].USD1 = prc
|
|
case 2:
|
|
out[idx].USD2 = prc
|
|
case 3:
|
|
out[idx].USD3 = prc
|
|
case 4:
|
|
out[idx].USD4 = prc
|
|
case 5:
|
|
out[idx].USD5 = prc
|
|
case 6:
|
|
out[idx].USD6 = prc
|
|
}
|
|
case "EUR":
|
|
switch grp {
|
|
case 1:
|
|
out[idx].EUR1 = prc
|
|
case 2:
|
|
out[idx].EUR2 = prc
|
|
case 3:
|
|
out[idx].EUR3 = prc
|
|
case 4:
|
|
out[idx].EUR4 = prc
|
|
case 5:
|
|
out[idx].EUR5 = prc
|
|
case 6:
|
|
out[idx].EUR6 = prc
|
|
}
|
|
case "TRY":
|
|
switch grp {
|
|
case 1:
|
|
out[idx].TRY1 = prc
|
|
case 2:
|
|
out[idx].TRY2 = prc
|
|
case 3:
|
|
out[idx].TRY3 = prc
|
|
case 4:
|
|
out[idx].TRY4 = prc
|
|
case 5:
|
|
out[idx].TRY5 = prc
|
|
case 6:
|
|
out[idx].TRY6 = prc
|
|
}
|
|
}
|
|
}
|
|
if err := pgRows.Err(); err != nil {
|
|
_ = pgRows.Close()
|
|
return err
|
|
}
|
|
_ = pgRows.Close()
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
for _, chunk := range chunkStringSlice(brandCodes, chunkSize) {
|
|
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(chunk))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
grpByBrand := make(map[string]string, len(chunk))
|
|
for rows.Next() {
|
|
var code, title string
|
|
if err := rows.Scan(&code, &title); err != nil {
|
|
_ = rows.Close()
|
|
return err
|
|
}
|
|
grpByBrand[strings.TrimSpace(code)] = strings.TrimSpace(title)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
_ = rows.Close()
|
|
return err
|
|
}
|
|
_ = rows.Close()
|
|
for i := range out {
|
|
if title, ok := grpByBrand[strings.TrimSpace(out[i].BrandCode)]; ok {
|
|
out[i].BrandGroupSec = title
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func chunkStringSlice(values []string, size int) [][]string {
|
|
if size <= 0 {
|
|
size = 1000
|
|
}
|
|
out := make([][]string, 0, (len(values)+size-1)/size)
|
|
for start := 0; start < len(values); start += size {
|
|
end := start + size
|
|
if end > len(values) {
|
|
end = len(values)
|
|
}
|
|
out = append(out, values[start:end])
|
|
}
|
|
return out
|
|
}
|
|
|
|
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,
|
|
TotalPages: 0,
|
|
Page: page,
|
|
Limit: limit,
|
|
}
|
|
if page <= 0 {
|
|
page = 1
|
|
}
|
|
if limit <= 0 {
|
|
limit = 500
|
|
}
|
|
offset := (page - 1) * limit
|
|
|
|
paramIndex := 1
|
|
args := make([]any, 0, 64)
|
|
nextParam := func() string {
|
|
name := "@p" + strconv.Itoa(paramIndex)
|
|
paramIndex++
|
|
return name
|
|
}
|
|
whereParts := []string{
|
|
"ProductAtt42 IN ('SERI', 'AKSESUAR')",
|
|
"IsBlocked = 0",
|
|
"LEN(LTRIM(RTRIM(ProductCode))) = 13",
|
|
}
|
|
addInFilter := func(expr string, values []string) {
|
|
clean := make([]string, 0, len(values))
|
|
for _, v := range values {
|
|
v = strings.TrimSpace(v)
|
|
if v == "" {
|
|
continue
|
|
}
|
|
clean = append(clean, v)
|
|
}
|
|
if len(clean) == 0 {
|
|
return
|
|
}
|
|
ors := make([]string, 0, len(clean))
|
|
for _, v := range clean {
|
|
p := nextParam()
|
|
ors = append(ors, expr+" = "+p)
|
|
args = append(args, v)
|
|
}
|
|
whereParts = append(whereParts, "("+strings.Join(ors, " OR ")+")")
|
|
}
|
|
brandGroupExpr := `CASE ABS(CHECKSUM(LTRIM(RTRIM(ProductCode)))) % 3
|
|
WHEN 0 THEN 'MARKA GRUBU A'
|
|
WHEN 1 THEN 'MARKA GRUBU B'
|
|
ELSE 'MARKA GRUBU C'
|
|
END`
|
|
addInFilter("LTRIM(RTRIM(ProductCode))", filters.ProductCode)
|
|
addInFilter(brandGroupExpr, filters.BrandGroup)
|
|
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '')", filters.AskiliYan)
|
|
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '')", filters.Kategori)
|
|
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '')", filters.UrunIlkGrubu)
|
|
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '')", filters.UrunAnaGrubu)
|
|
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '')", filters.UrunAltGrubu)
|
|
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '')", filters.Icerik)
|
|
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '')", filters.Karisim)
|
|
addInFilter("COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '')", filters.Marka)
|
|
if q := strings.TrimSpace(filters.Search); q != "" {
|
|
p := nextParam()
|
|
args = append(args, "%"+q+"%")
|
|
whereParts = append(whereParts, "("+strings.Join([]string{
|
|
"LTRIM(RTRIM(ProductCode)) LIKE " + p,
|
|
"COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '') LIKE " + p,
|
|
"COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '') LIKE " + p,
|
|
"COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '') LIKE " + p,
|
|
"COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '') LIKE " + p,
|
|
"COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '') LIKE " + p,
|
|
"COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '') LIKE " + p,
|
|
"COALESCE(LTRIM(RTRIM(ProductAtt29Desc)), '') LIKE " + p,
|
|
"COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '') LIKE " + p,
|
|
}, " OR ")+")")
|
|
}
|
|
whereSQL := strings.Join(whereParts, " AND ")
|
|
|
|
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 = page
|
|
result.Limit = limit
|
|
}
|
|
|
|
// 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"
|
|
}
|
|
orderBySQL := orderExpr + ` ` + orderDir
|
|
if !strings.EqualFold(strings.TrimSpace(orderExpr), "rc.ProductCode") {
|
|
orderBySQL += `,
|
|
rc.ProductCode 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;
|
|
|
|
SELECT
|
|
f.ProductCode,
|
|
MAX(f.BrandGroupSec) AS BrandGroupSec,
|
|
MAX(f.AskiliYan) AS AskiliYan,
|
|
MAX(f.Kategori) AS Kategori,
|
|
MAX(f.UrunIlkGrubu) AS UrunIlkGrubu,
|
|
MAX(f.UrunAnaGrubu) AS UrunAnaGrubu,
|
|
MAX(f.UrunAltGrubu) AS UrunAltGrubu,
|
|
MAX(f.Icerik) AS Icerik,
|
|
MAX(f.Karisim) AS Karisim,
|
|
MAX(f.Marka) AS Marka,
|
|
MAX(f.BrandCode) AS BrandCode
|
|
INTO #req_codes
|
|
FROM (
|
|
SELECT
|
|
LTRIM(RTRIM(ProductCode)) AS ProductCode,
|
|
` + brandGroupExpr + ` AS BrandGroupSec,
|
|
COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '') AS AskiliYan,
|
|
COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '') AS Kategori,
|
|
COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '') AS UrunIlkGrubu,
|
|
COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '') AS UrunAnaGrubu,
|
|
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(ProductAtt10)), '') AS BrandCode
|
|
FROM ProductFilterWithDescription('TR')
|
|
WHERE ` + whereSQL + `
|
|
) f
|
|
GROUP BY f.ProductCode;
|
|
|
|
CREATE CLUSTERED INDEX IX_req_codes_ProductCode ON #req_codes(ProductCode);
|
|
|
|
SELECT
|
|
LTRIM(RTRIM(s.ItemCode)) AS ItemCode,
|
|
SUM(s.In_Qty1 - s.Out_Qty1) AS InventoryQty1
|
|
INTO #stock_base
|
|
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));
|
|
|
|
CREATE CLUSTERED INDEX IX_stock_base_ItemCode ON #stock_base(ItemCode);
|
|
|
|
SELECT
|
|
rc.ProductCode,
|
|
rc.BrandGroupSec,
|
|
rc.AskiliYan,
|
|
rc.Kategori,
|
|
rc.UrunIlkGrubu,
|
|
rc.UrunAnaGrubu,
|
|
rc.UrunAltGrubu,
|
|
rc.Icerik,
|
|
rc.Karisim,
|
|
rc.Marka,
|
|
rc.BrandCode
|
|
FROM #req_codes rc
|
|
LEFT JOIN #stock_base sb
|
|
ON sb.ItemCode = rc.ProductCode
|
|
ORDER BY
|
|
` + orderBySQL + `
|
|
OFFSET ` + strconv.Itoa(offset) + ` ROWS
|
|
FETCH NEXT ` + strconv.Itoa(limit) + ` ROWS ONLY;
|
|
`
|
|
|
|
var (
|
|
rows *sql.Rows
|
|
rowsErr error
|
|
)
|
|
for attempt := 1; attempt <= 3; attempt++ {
|
|
var err error
|
|
rows, err = db.MssqlDB.QueryContext(ctx, productQuery, args...)
|
|
if err == nil {
|
|
rowsErr = nil
|
|
break
|
|
}
|
|
rowsErr = err
|
|
if ctx.Err() != nil || !isTransientMSSQLNetworkError(err) || attempt == 3 {
|
|
break
|
|
}
|
|
wait := time.Duration(attempt*300) * time.Millisecond
|
|
select {
|
|
case <-ctx.Done():
|
|
break
|
|
case <-time.After(wait):
|
|
}
|
|
}
|
|
if rowsErr != nil {
|
|
return result, rowsErr
|
|
}
|
|
defer rows.Close()
|
|
|
|
out := make([]models.ProductPricing, 0, limit)
|
|
for rows.Next() {
|
|
var item models.ProductPricing
|
|
if err := rows.Scan(
|
|
&item.ProductCode,
|
|
&item.BrandGroupSec,
|
|
&item.AskiliYan,
|
|
&item.Kategori,
|
|
&item.UrunIlkGrubu,
|
|
&item.UrunAnaGrubu,
|
|
&item.UrunAltGrubu,
|
|
&item.Icerik,
|
|
&item.Karisim,
|
|
&item.Marka,
|
|
&item.BrandCode,
|
|
); err != nil {
|
|
return result, err
|
|
}
|
|
out = append(out, item)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return result, err
|
|
}
|
|
if len(out) == 0 {
|
|
result.Rows = out
|
|
return result, 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))
|
|
}
|
|
valueRows := make([]string, 0, len(codes))
|
|
metricArgs := make([]any, 0, len(codes))
|
|
for i, code := range codes {
|
|
paramName := "@p" + strconv.Itoa(i+1)
|
|
valueRows = append(valueRows, "("+paramName+")")
|
|
metricArgs = append(metricArgs, code)
|
|
}
|
|
|
|
metricsQuery := `
|
|
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
|
|
),
|
|
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,
|
|
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.InnerProcessCode)) = 'OP'
|
|
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,
|
|
COALESCE(bp.BasePriceUsd, 0) AS BasePriceUsd,
|
|
COALESCE(bp.BasePriceTry, 0) AS BasePriceTry,
|
|
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,
|
|
'' 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
|
|
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, metricArgs...)
|
|
if err != nil {
|
|
return result, fmt.Errorf("metrics query failed: %w", err)
|
|
}
|
|
defer metricsRows.Close()
|
|
|
|
type metrics struct {
|
|
CostPrice float64
|
|
BasePriceUsd float64
|
|
BasePriceTry float64
|
|
StockQty float64
|
|
StockEntryDate string
|
|
LastCostingDate 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.BasePriceUsd,
|
|
&m.BasePriceTry,
|
|
&m.StockQty,
|
|
&m.StockEntryDate,
|
|
&m.LastCostingDate,
|
|
&m.LastPricingDate,
|
|
); err != nil {
|
|
return result, err
|
|
}
|
|
metricsByCode[strings.TrimSpace(code)] = m
|
|
}
|
|
if err := metricsRows.Err(); err != nil {
|
|
return result, err
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func isTransientMSSQLNetworkError(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
e := strings.ToLower(err.Error())
|
|
return strings.Contains(e, "i/o timeout") ||
|
|
strings.Contains(e, "timeout") ||
|
|
strings.Contains(e, "wsarecv") ||
|
|
strings.Contains(e, "connection attempt failed") ||
|
|
strings.Contains(e, "no connection could be made") ||
|
|
strings.Contains(e, "broken pipe") ||
|
|
strings.Contains(e, "connection reset")
|
|
}
|