Merge remote-tracking branch 'origin/master'

This commit is contained in:
2026-02-13 07:27:57 +03:00
parent d571fe2fd5
commit 7f56bb40c5
38 changed files with 1709 additions and 457 deletions

View File

@@ -12,40 +12,230 @@ func GetOrderListExcel(
orderDate string,
) (*sql.Rows, error) {
q := OrderListBaseQuery + " AND 1=1 "
args := []interface{}{}
q := `
SELECT
CAST(h.OrderHeaderID AS NVARCHAR(50)) AS OrderHeaderID,
ISNULL(h.OrderNumber,'') AS OrderNumber,
CONVERT(varchar,h.OrderDate,23) AS OrderDate,
// SEARCH
ISNULL(h.CurrAccCode,'') AS CurrAccCode,
ISNULL(ca.CurrAccDescription,'') AS CurrAccDescription,
ISNULL(mt.AttributeDescription,'') AS MusteriTemsilcisi,
ISNULL(py.AttributeDescription,'') AS Piyasa,
ISNULL(h.DocCurrencyCode,'TRY') AS DocCurrencyCode,
-------------------------------------------------
-- BELGE PB TOPLAM
-------------------------------------------------
ISNULL(t.TotalAmount,0) AS TotalAmount,
-------------------------------------------------
-- USD TOPLAM
-------------------------------------------------
CASE
WHEN h.DocCurrencyCode = 'USD'
THEN ISNULL(t.TotalAmount,0)
WHEN h.DocCurrencyCode = 'TRY'
AND usd.Rate > 0
THEN ISNULL(t.TotalTRY,0) / usd.Rate
WHEN h.DocCurrencyCode IN ('EUR','GBP')
AND cur.Rate > 0
AND usd.Rate > 0
THEN (ISNULL(t.TotalAmount,0) * cur.Rate) / usd.Rate
ELSE 0
END AS TotalAmountUSD,
ISNULL(t.PackedAmount,0) AS PackedAmount,
CASE
WHEN h.DocCurrencyCode = 'USD'
THEN ISNULL(t.PackedAmount,0)
WHEN h.DocCurrencyCode = 'TRY'
AND usd.Rate > 0
THEN ISNULL(t.PackedTRY,0) / usd.Rate
WHEN cur.Rate > 0
AND usd.Rate > 0
THEN (ISNULL(t.PackedAmount,0) * cur.Rate) / usd.Rate
ELSE 0
END AS PackedUSD,
CASE
WHEN ISNULL(t.TotalAmount,0) > 0
THEN (ISNULL(t.PackedAmount,0) * 100.0) / NULLIF(t.TotalAmount,0)
ELSE 0
END AS PackedRatePct,
ISNULL(h.Description,'') AS Description,
usd.Rate AS ExchangeRateUSD
FROM dbo.trOrderHeader h
-------------------------------------------------
-- ✅ LINE CURRENCY TOPLAM
-------------------------------------------------
LEFT JOIN (
SELECT
l.OrderHeaderID,
-- Belge para birimi toplam
SUM(
CASE
WHEN c.CurrencyCode = h.DocCurrencyCode
THEN c.NetAmount
ELSE 0
END
) AS TotalAmount,
-- TRY toplam
SUM(
CASE
WHEN c.CurrencyCode = 'TRY'
THEN c.NetAmount
ELSE 0
END
) AS TotalTRY,
-- Paketlenen (OrderLine IsClosed=1) belge PB toplam
SUM(
CASE
WHEN ISNULL(l.IsClosed,0) = 1
AND c.CurrencyCode = h.DocCurrencyCode
THEN c.NetAmount
ELSE 0
END
) AS PackedAmount,
-- Paketlenen TRY toplam
SUM(
CASE
WHEN ISNULL(l.IsClosed,0) = 1
AND c.CurrencyCode = 'TRY'
THEN c.NetAmount
ELSE 0
END
) AS PackedTRY
FROM dbo.trOrderLineCurrency c
JOIN dbo.trOrderLine l
ON l.OrderLineID = c.OrderLineID
JOIN dbo.trOrderHeader h
ON h.OrderHeaderID = l.OrderHeaderID
GROUP BY l.OrderHeaderID
) t ON t.OrderHeaderID = h.OrderHeaderID
-------------------------------------------------
-- CARİ
-------------------------------------------------
LEFT JOIN dbo.cdCurrAccDesc ca
ON ca.CurrAccCode = h.CurrAccCode
AND ca.LangCode='TR'
-------------------------------------------------
-- TEMSİLCİ / PİYASA
-------------------------------------------------
LEFT JOIN dbo.CustomerAttributesFilter f
ON f.CurrAccCode = h.CurrAccCode
LEFT JOIN dbo.cdCurrAccAttributeDesc mt
ON mt.CurrAccTypeCode=3
AND mt.AttributeTypeCode=2
AND mt.AttributeCode=f.CustomerAtt02
AND mt.LangCode='TR'
LEFT JOIN dbo.cdCurrAccAttributeDesc py
ON py.CurrAccTypeCode=3
AND py.AttributeTypeCode=1
AND py.AttributeCode=f.CustomerAtt01
AND py.LangCode='TR'
-------------------------------------------------
-- USD → TRY
-------------------------------------------------
OUTER APPLY (
SELECT TOP 1 Rate
FROM dbo.AllExchangeRates
WHERE CurrencyCode='USD'
AND RelationCurrencyCode='TRY'
AND ExchangeTypeCode=6
AND Rate>0
AND Date<=CAST(GETDATE() AS date)
ORDER BY Date DESC
) usd
-------------------------------------------------
-- DOC → TRY
-------------------------------------------------
OUTER APPLY (
SELECT TOP 1 Rate
FROM dbo.AllExchangeRates
WHERE CurrencyCode=h.DocCurrencyCode
AND RelationCurrencyCode='TRY'
AND ExchangeTypeCode=6
AND Rate>0
AND Date<=CAST(GETDATE() AS date)
ORDER BY Date DESC
) cur
WHERE
ISNULL(h.IsCancelOrder,0)=0
AND h.OrderTypeCode=1
AND h.ProcessCode='WS'
AND h.IsClosed=0
`
args := []any{}
// ================= SEARCH =================
if search != "" {
q += `
AND (
LOWER(h.OrderNumber) LIKE LOWER(@p1) OR
LOWER(h.CurrAccCode) LIKE LOWER(@p1) OR
LOWER(ca.CurrAccDescription) LIKE LOWER(@p1) OR
LOWER(h.Description) LIKE LOWER(@p1) OR
LOWER(mt.AttributeDescription) LIKE LOWER(@p1) OR
LOWER(py.AttributeDescription) LIKE LOWER(@p1)
LOWER(h.OrderNumber) LIKE LOWER(@p1)
OR LOWER(h.CurrAccCode) LIKE LOWER(@p1)
OR LOWER(ca.CurrAccDescription) LIKE LOWER(@p1)
OR LOWER(h.Description) LIKE LOWER(@p1)
OR LOWER(mt.AttributeDescription) LIKE LOWER(@p1)
OR LOWER(py.AttributeDescription) LIKE LOWER(@p1)
)
`
args = append(args, "%"+search+"%")
}
// CURRACC
// ================= CURRACC =================
if currAcc != "" {
q += fmt.Sprintf(" AND h.CurrAccCode = @p%d ", len(args)+1)
args = append(args, currAcc)
}
// DATE
// ================= DATE =================
if orderDate != "" {
q += fmt.Sprintf(
" AND CONVERT(varchar, h.OrderDate, 23) = @p%d ",
" AND CONVERT(varchar,h.OrderDate,23) = @p%d ",
len(args)+1,
)
args = append(args, orderDate)
}
// ORDER BY SONDA
// ================= ORDER =================
q += " ORDER BY h.CreatedDate DESC "
return db.Query(q, args...)

View File

@@ -8,15 +8,12 @@ import (
"fmt"
)
// GetOrderByID — Sipariş başlığı (header) ve satırlarını (lines) getirir.
// GetOrderByID returns order header and lines.
func GetOrderByID(orderID string) (*models.OrderHeader, []models.OrderDetail, error) {
conn := db.GetDB()
logger.Printf("🧾 [GetOrderByID] begin id=%s", orderID)
logger.Printf("[GetOrderByID] begin id=%s", orderID)
// =====================================================
// HEADER (Cari adı join'li)
// =====================================================
var header models.OrderHeader
qHeader := `
SELECT
@@ -176,61 +173,76 @@ func GetOrderByID(orderID string) (*models.OrderHeader, []models.OrderDetail, er
&header.LastUpdatedDate,
&header.IsProposalBased,
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
logger.Printf("⚠️ [GetOrderByID] sipariş bulunamadı: %s", orderID)
logger.Printf("[GetOrderByID] not found: %s", orderID)
return nil, nil, sql.ErrNoRows
}
logger.Printf("[GetOrderByID] header sorgu hatası: %v", err)
logger.Printf("[GetOrderByID] header error: %v", err)
return nil, nil, err
}
logger.Printf("✅ [GetOrderByID] header loaded • orderNo=%v currAcc=%v",
header.OrderNumber, header.CurrAccCode.String)
// =====================================================
// LINES
// =====================================================
qLines := `
SELECT
CAST(L.OrderLineID AS varchar(36)) AS OrderLineID,
L.SortOrder,
L.ItemTypeCode,
L.ItemCode,
L.ColorCode,
L.ItemDim1Code,
L.ItemDim2Code,
L.ItemDim3Code,
L.Qty1,
L.Qty2,
L.Price,
L.VatRate,
L.PCTRate,
L.DocCurrencyCode,
L.DeliveryDate,
L.PlannedDateOfLading,
L.LineDescription,
L.IsClosed,
L.CreatedUserName,
L.CreatedDate,
L.LastUpdatedUserName,
L.LastUpdatedDate,
P.ProductAtt42Desc AS UrunIlkGrubu,
P.ProductAtt01Desc AS UrunAnaGrubu,
P.ProductAtt02Desc AS UrunAltGrubu,
P.ProductAtt38Desc AS Fit1,
P.ProductAtt39Desc AS Fit2
FROM BAGGI_V3.dbo.trOrderLine AS L
LEFT JOIN ProductFilterWithDescription('TR') AS P
ON LTRIM(RTRIM(P.ProductCode)) = LTRIM(RTRIM(L.ItemCode))
WHERE L.OrderHeaderID = @p1
ORDER BY L.SortOrder ASC;
`
SELECT
CAST(L.OrderLineID AS varchar(36)) AS OrderLineID,
L.SortOrder,
L.ItemTypeCode,
L.ItemCode,
L.ColorCode,
L.ItemDim1Code,
L.ItemDim2Code,
L.ItemDim3Code,
L.Qty1,
L.Qty2,
ISNULL(CD.Price, 0) AS Price,
ISNULL(CD.CurrencyCode, ISNULL(L.DocCurrencyCode, 'TRY')) AS DocCurrencyCode,
ISNULL(CD.RelationCurrencyCode, ISNULL(L.DocCurrencyCode, 'TRY')) AS RelationCurrencyCode,
ISNULL(CD.ExchangeRate, ISNULL(L.PriceExchangeRate, 1)) AS PriceExchangeRate,
ISNULL(CD.PriceVI, ISNULL(L.Price, 0)) AS DocPrice,
ISNULL(CD.AmountVI, ISNULL(L.Price, 0) * ISNULL(L.Qty1, 0)) AS DocAmount,
ISNULL(CD.LDiscount1, 0) AS LineDiscount,
ISNULL(CD.TDiscount1, 0) AS TotalDiscount,
ISNULL(CD.TaxBase, 0) AS TaxBase,
ISNULL(CD.Pct, 0) AS Pct,
ISNULL(CD.Vat, 0) AS VatAmount,
ISNULL(CD.VatDeducation, 0) AS VatDeducation,
ISNULL(CD.NetAmount, 0) AS NetAmount,
ISNULL(CL.Price, ISNULL(CD.Price, 0)) AS LocalPrice,
ISNULL(CL.Amount, ISNULL(CD.Amount, 0)) AS LocalAmount,
L.VatRate,
L.PCTRate,
L.DeliveryDate,
L.PlannedDateOfLading,
L.LineDescription,
L.IsClosed,
L.CreatedUserName,
L.CreatedDate,
L.LastUpdatedUserName,
L.LastUpdatedDate,
P.ProductAtt42Desc AS UrunIlkGrubu,
P.ProductAtt01Desc AS UrunAnaGrubu,
P.ProductAtt02Desc AS UrunAltGrubu,
P.ProductAtt38Desc AS Fit1,
P.ProductAtt39Desc AS Fit2
FROM BAGGI_V3.dbo.trOrderLine AS L
LEFT JOIN BAGGI_V3.dbo.trOrderLineCurrency AS CD WITH (NOLOCK)
ON CD.OrderLineID = L.OrderLineID
AND CD.CurrencyCode = ISNULL(NULLIF(LTRIM(RTRIM(L.DocCurrencyCode)), ''), 'TRY')
LEFT JOIN BAGGI_V3.dbo.trOrderLineCurrency AS CL WITH (NOLOCK)
ON CL.OrderLineID = L.OrderLineID
AND CL.CurrencyCode = 'TRY'
LEFT JOIN ProductFilterWithDescription('TR') AS P
ON LTRIM(RTRIM(P.ProductCode)) = LTRIM(RTRIM(L.ItemCode))
WHERE L.OrderHeaderID = @p1
ORDER BY L.SortOrder ASC;
`
rows, err := conn.Query(qLines, orderID)
if err != nil {
logger.Printf("[GetOrderByID] line sorgu hatası: %v", err)
logger.Printf("[GetOrderByID] lines error: %v", err)
return &header, nil, err
}
defer rows.Close()
@@ -250,9 +262,22 @@ func GetOrderByID(orderID string) (*models.OrderHeader, []models.OrderDetail, er
&ln.Qty1,
&ln.Qty2,
&ln.Price,
&ln.DocCurrencyCode,
&ln.RelationCurrencyCode,
&ln.PriceExchangeRate,
&ln.DocPrice,
&ln.DocAmount,
&ln.LineDiscount,
&ln.TotalDiscount,
&ln.TaxBase,
&ln.Pct,
&ln.VatAmount,
&ln.VatDeducation,
&ln.NetAmount,
&ln.LocalPrice,
&ln.LocalAmount,
&ln.VatRate,
&ln.PCTRate,
&ln.DocCurrencyCode,
&ln.DeliveryDate,
&ln.PlannedDateOfLading,
&ln.LineDescription,
@@ -267,14 +292,14 @@ func GetOrderByID(orderID string) (*models.OrderHeader, []models.OrderDetail, er
&ln.Fit1,
&ln.Fit2,
); err != nil {
return &header, nil, fmt.Errorf("line scan hatası: %w", err)
return &header, nil, fmt.Errorf("line scan error: %w", err)
}
lines = append(lines, ln)
}
if err := rows.Err(); err != nil {
return &header, nil, fmt.Errorf("line rows hatası: %w", err)
return &header, nil, fmt.Errorf("line rows error: %w", err)
}
logger.Printf("📦 [GetOrderByID] lines loaded count=%d", len(lines))
logger.Printf("[GetOrderByID] lines loaded count=%d", len(lines))
return &header, lines, nil
}

View File

@@ -13,9 +13,10 @@ import (
"bssapp-backend/models"
"database/sql"
"fmt"
"github.com/google/uuid"
"strings"
"time"
"github.com/google/uuid"
)
func nf0(v models.NullFloat64) float64 {
@@ -25,6 +26,311 @@ func nf0(v models.NullFloat64) float64 {
return v.Float64
}
// =======================================================
// ✅ trOrderLineCurrency UPSERT (Doc Price / Amount yaz)
// =======================================================
func upsertLineCurrency(
tx *sql.Tx,
lineID string,
docCurrency string,
exRate float64,
docPrice float64,
qty float64,
lDis float64, // Line Discount %
tDis float64, // Total Discount %
vatRate float64,
ln models.OrderDetail,
user string,
) error {
// ================= NORMALIZE =================
if docCurrency == "" {
docCurrency = "TRY"
}
if exRate <= 0 {
exRate = 1
}
relationCurrency := safeNS(ln.RelationCurrencyCode)
if relationCurrency == "" {
relationCurrency = docCurrency
}
if ln.LineDiscount.Valid {
lDis = ln.LineDiscount.Float64
}
if ln.TotalDiscount.Valid {
tDis = ln.TotalDiscount.Float64
}
basePrice := docPrice // Price (KDV hariç)
if docCurrency == "TRY" && ln.LocalPrice.Valid && ln.LocalPrice.Float64 > 0 {
basePrice = ln.LocalPrice.Float64
}
amountBase := basePrice * qty // Amount (KDV hariç)
if docCurrency == "TRY" && ln.LocalAmount.Valid && ln.LocalAmount.Float64 > 0 {
amountBase = ln.LocalAmount.Float64
}
priceVI := basePrice // PriceVI (KDV dahil)
if ln.DocPrice.Valid && ln.DocPrice.Float64 > 0 {
priceVI = ln.DocPrice.Float64
}
amountVI := priceVI * qty // AmountVI (KDV dahil)
if ln.DocAmount.Valid && ln.DocAmount.Float64 > 0 {
amountVI = ln.DocAmount.Float64
if qty > 0 {
priceVI = amountVI / qty
}
}
// ================= DISCOUNT =================
afterLineDis := amountBase * (1 - lDis/100)
afterTotalDis := afterLineDis * (1 - tDis/100)
// ================= TAX =================
taxBase := afterTotalDis
if ln.TaxBase.Valid {
taxBase = ln.TaxBase.Float64
}
vat := taxBase * vatRate / 100
if ln.VatAmount.Valid {
vat = ln.VatAmount.Float64
}
vatDeducation := 0.0
if ln.VatDeducation.Valid {
vatDeducation = ln.VatDeducation.Float64
}
net := taxBase + vat - vatDeducation
if ln.NetAmount.Valid {
net = ln.NetAmount.Float64
}
pct := 0.0
if ln.Pct.Valid {
pct = ln.Pct.Float64
}
// payload yoksa doc tutarı net ile hizala
if !ln.DocAmount.Valid {
amountVI = net
if qty > 0 {
priceVI = amountVI / qty
}
}
// ================= LOCAL =================
localPrice := basePrice * exRate
localAmount := amountBase * exRate
localPriceVI := priceVI * exRate
localAmountVI := amountVI * exRate
localTaxBase := taxBase * exRate
localVat := vat * exRate
localVatDeducation := vatDeducation * exRate
localNet := net * exRate
// ================= DOC =================
_, err := tx.Exec(`
MERGE BAGGI_V3.dbo.trOrderLineCurrency AS T
USING (SELECT @p1 AS OrderLineID, @p2 AS CurrencyCode) AS S
ON T.OrderLineID=S.OrderLineID AND T.CurrencyCode=S.CurrencyCode
WHEN MATCHED THEN UPDATE SET
RelationCurrencyCode=@p3,
ExchangeRate=@p4,
PriceVI=@p5,
AmountVI=@p6,
Price=@p7,
Amount=@p8,
LDiscount1=@p9,
TDiscount1=@p10,
TaxBase=@p11,
Pct=@p12,
Vat=@p13,
VatDeducation=@p14,
NetAmount=@p15,
LastUpdatedUserName=@p16,
LastUpdatedDate=GETDATE()
WHEN NOT MATCHED THEN INSERT (
OrderLineID,
CurrencyCode,
RelationCurrencyCode,
ExchangeRate,
PriceVI,
AmountVI,
Price,
Amount,
LDiscount1,
TDiscount1,
TaxBase,
Pct,
Vat,
VatDeducation,
NetAmount,
CreatedUserName,
CreatedDate
)
VALUES (
@p1,@p2,@p3,
@p4,
@p5,@p6,
@p7,@p8,
@p9,@p10,
@p11,@p12,@p13,@p14,@p15,
@p16,GETDATE()
);`,
lineID,
docCurrency,
relationCurrency,
exRate,
priceVI,
amountVI,
basePrice,
amountBase,
lDis,
tDis,
taxBase,
pct,
vat,
vatDeducation,
net,
user,
)
if err != nil {
return err
}
if docCurrency != "TRY" {
_, err = tx.Exec(`
MERGE BAGGI_V3.dbo.trOrderLineCurrency AS T
USING (SELECT @p1 AS OrderLineID,'TRY' AS CurrencyCode) AS S
ON T.OrderLineID=S.OrderLineID AND T.CurrencyCode=S.CurrencyCode
WHEN MATCHED THEN UPDATE SET
RelationCurrencyCode=@p2,
ExchangeRate=@p3,
PriceVI=@p4,
AmountVI=@p5,
Price=@p6,
Amount=@p7,
LDiscount1=@p8,
TDiscount1=@p9,
TaxBase=@p10,
Pct=@p11,
Vat=@p12,
VatDeducation=@p13,
NetAmount=@p14,
LastUpdatedUserName=@p15,
LastUpdatedDate=GETDATE()
WHEN NOT MATCHED THEN INSERT (
OrderLineID,
CurrencyCode,
RelationCurrencyCode,
ExchangeRate,
PriceVI,
AmountVI,
Price,
Amount,
LDiscount1,
TDiscount1,
TaxBase,
Pct,
Vat,
VatDeducation,
NetAmount,
CreatedUserName,
CreatedDate
)
VALUES (
@p1,'TRY',@p2,
@p3,
@p4,@p5,
@p6,@p7,
@p8,@p9,
@p10,@p11,@p12,@p13,@p14,
@p15,GETDATE()
);`,
lineID,
docCurrency,
exRate,
localPriceVI,
localAmountVI,
localPrice,
localAmount,
lDis,
tDis,
localTaxBase,
pct,
localVat,
localVatDeducation,
localNet,
user,
)
if err != nil {
return err
}
}
return nil
}
// =======================================================
// COMBO KEY & STRING HELPERS
// =======================================================
@@ -52,6 +358,32 @@ func qtyValue(q models.NullFloat64) float64 {
return q.Float64
}
func buildV3AuditUser(user *models.User) string {
if user == nil {
return "V3-0"
}
v3Name := strings.ToUpper(strings.TrimSpace(user.V3Username))
v3Group := user.V3UserGroup
if v3Group >= 100 && v3Group%100 == 0 {
v3Group = v3Group / 100
}
if v3Name != "" && v3Group > 0 {
return fmt.Sprintf("V3-%s%d", v3Name, v3Group)
}
if v3Name != "" {
return "V3-" + v3Name
}
if username := strings.TrimSpace(user.Username); username != "" {
return username
}
if v3Group > 0 {
return fmt.Sprintf("V3-%d", v3Group)
}
return "V3-0"
}
// VatCode: NullString → string
// - NULL → ""
// - "0" → "" (FK patlamasın, sadece anlamlı kodlar gönderiyoruz)
@@ -173,9 +505,6 @@ func ValidateItemVariant(tx *sql.Tx, ln models.OrderDetail) error {
}
// İstersen debug:
// fmt.Printf("🧪 VARIANT CHECK item=%q color=%q dim1=%q dim2=%q clientKey=%s\n", item, color, dim1, dim2, safeNS(ln.ClientKey))
var exists int
err := tx.QueryRow(`
SELECT CASE WHEN EXISTS (
@@ -297,12 +626,6 @@ type OrderLineResult struct {
// =======================================================
// PART 1 — InsertOrder (header + lines insert) — FINAL v5.1
// ✔ OrderHeaderID backend üretir
// ✔ LOCAL-... numara gelirse gerçek WS numarası üretir
// ✔ Full debug
// ✔ Tüm satırlar INSERT edilir
// ✔ INSERT öncesi ItemVariant Guard
// ✔ Payload içi Duplicate Guard (comboKey)
// =======================================================
func InsertOrder(header models.OrderHeader, lines []models.OrderDetail, user *models.User) (string, []OrderLineResult, error) {
@@ -317,7 +640,7 @@ func InsertOrder(header models.OrderHeader, lines []models.OrderDetail, user *mo
defer tx.Rollback()
now := time.Now()
v3User := fmt.Sprintf("V3U%d-%s", user.V3UserGroup, user.V3Username)
v3User := buildV3AuditUser(user)
// =======================================================
// 1) BACKEND — OrderHeaderID üretimi (HER ZAMAN)
@@ -368,7 +691,6 @@ func InsertOrder(header models.OrderHeader, lines []models.OrderDetail, user *mo
}
}
}
// =======================================================
// 4) HEADER INSERT
// =======================================================
@@ -423,6 +745,7 @@ VALUES (
fmt.Println("🟪 HEADER INSERT ÇALIŞIYOR...")
// ✅ exRate burada gerçekten kullanılıyor (ExchangeRate parametresi)
headerParams := []any{
header.OrderHeaderID,
nullableInt16(header.OrderTypeCode, 1),
@@ -474,7 +797,7 @@ VALUES (
nullableString(header.GLTypeCode, ""),
nullableString(header.DocCurrencyCode, "TRY"),
nullableString(header.LocalCurrencyCode, "TRY"),
nullableFloat64(header.ExchangeRate, exRate),
nullableFloat64(header.ExchangeRate, exRate), // ✅ exRate kullanıldı
nullableFloat64(header.TDisRate1, 0),
nullableFloat64(header.TDisRate2, 0),
@@ -520,6 +843,7 @@ VALUES (
nullableBool(header.IsProposalBased, false),
}
// ✅ queryHeader artık gerçekten kullanılıyor → "Unused variable 'queryHeader'" biter
if _, err := tx.Exec(queryHeader, headerParams...); err != nil {
fmt.Println("❌ HEADER INSERT ERROR:", err)
return "", nil, fmt.Errorf("header insert hatasi: %w", err)
@@ -527,6 +851,7 @@ VALUES (
fmt.Println("🟩 HEADER INSERT OK — ID:", newID)
// headerParams ... (senin mevcut hali aynen)
// =======================================================
// 5) LINE INSERT
// =======================================================
@@ -590,7 +915,6 @@ VALUES (
seenCombo := make(map[string]bool)
for i, ln := range lines {
// ===================== PART 2 (Satır 301-600) =====================
fmt.Println("────────────────────────────────────")
fmt.Printf("🟨 [INSERT] LINE %d — gelen OrderLineID=%s\n", i+1, ln.OrderLineID)
@@ -630,6 +954,7 @@ VALUES (
}
planned := nullableDateString(ln.PlannedDateOfLading)
// ✅ INSERT ÖNCESİ ItemVariant GUARD
if qtyValue(ln.Qty1) > 0 {
if err := ValidateItemVariant(tx, ln); err != nil {
@@ -637,6 +962,7 @@ VALUES (
return "", nil, err
}
}
fmt.Printf(
"🚨 INSERT LINE[%d] | LineID=%s ClientKey=%s Item=%q Color=%q Dim1=%q Dim2=%q Dim3=%q Qty1=%v\n",
i+1,
@@ -708,6 +1034,28 @@ VALUES (
return "", nil, fmt.Errorf("line insert hatasi: %w", err)
}
// ✅ NEW: trOrderLineCurrency yaz
if err := upsertLineCurrency(
tx,
ln.OrderLineID,
safeNS(ln.DocCurrencyCode),
nf0(ln.PriceExchangeRate),
nf0(ln.Price),
nf0(ln.Qty1),
nf0(ln.LDisRate1), // ✅ Line discount
0, // ✅ Total discount (istersen headerdan alırsın)
nf0(ln.VatRate), // ✅ Vat rate
ln,
v3User,
); err != nil {
return "", nil, fmt.Errorf("currency insert hatası: %w", err)
}
if ln.ClientKey.Valid && ln.ClientKey.String != "" {
lineResults = append(lineResults, OrderLineResult{
ClientKey: ln.ClientKey.String,
@@ -734,25 +1082,16 @@ VALUES (
// =======================================================
// PART 2 — UpdateOrder FULL DEBUG (v4.3)
// ✔ ComboKey ile açık satır eşleştirme
// ✔ Kapalı satırları korur
// ✔ Payload içi Duplicate Guard
// ✔ INSERT/UPDATE öncesi ItemVariant Guard (tek noktada)
// ✔ Gridde olmayan açık satırları siler (önce child)
// =======================================================
func UpdateOrder(header models.OrderHeader, lines []models.OrderDetail, user *models.User) ([]OrderLineResult, error) {
conn := db.GetDB()
// ======================================================
// 🔍 SCAN DEBUG — HEADER bilgisi
// ======================================================
fmt.Println("══════════════════════════════════════")
fmt.Println("🔍 [DEBUG] UpdateOrder çağrıldı")
fmt.Printf("🔍 HeaderID: %v\n", header.OrderHeaderID)
fmt.Printf("🔍 Line sayısı: %v\n", len(lines))
fmt.Printf("🔍 User: %v (V3: %s/%d)\n",
user.Username, user.V3Username, user.V3UserGroup)
fmt.Printf("🔍 User: %v (V3: %s/%d)\n", user.Username, user.V3Username, user.V3UserGroup)
fmt.Println("══════════════════════════════════════")
tx, err := conn.Begin()
@@ -762,9 +1101,9 @@ func UpdateOrder(header models.OrderHeader, lines []models.OrderDetail, user *mo
defer tx.Rollback()
now := time.Now()
v3User := fmt.Sprintf("V3U%d-%s", user.V3UserGroup, user.V3Username)
v3User := buildV3AuditUser(user)
// Döviz kuru
// Döviz kuru (Header ExchangeRate fallback)
exRate := 1.0
if header.DocCurrencyCode.Valid && header.DocCurrencyCode.String != "TRY" {
if c, err := GetTodayCurrencyV3(conn, header.DocCurrencyCode.String); err == nil && c.Rate > 0 {
@@ -775,11 +1114,16 @@ func UpdateOrder(header models.OrderHeader, lines []models.OrderDetail, user *mo
// =======================================================
// 0) Mevcut satırları oku (GUID STRING olarak!)
// =======================================================
existingOpen := make(map[string]bool)
existingClosed := make(map[string]bool)
existingOpenCombo := make(map[string]string)
existingClosedCombo := make(map[string]string)
existingOpenMeta := make(map[string]struct {
item string
color string
dim1 string
dim2 string
})
rows, err := tx.Query(`
SELECT
@@ -814,16 +1158,41 @@ WHERE OrderHeaderID=@p1
}
} else {
existingOpen[id] = true
existingOpenMeta[id] = struct {
item string
color string
dim1 string
dim2 string
}{
item: strings.TrimSpace(item),
color: strings.TrimSpace(color),
dim1: strings.TrimSpace(dim1),
dim2: strings.TrimSpace(dim2),
}
if combo != "" {
existingOpenCombo[combo] = id
}
}
}
isLineInvoiced := func(lineID string) (bool, error) {
var exists int
err := tx.QueryRow(`
SELECT CASE WHEN EXISTS (
SELECT 1
FROM BAGGI_V3.dbo.trInvoiceLine WITH (NOLOCK)
WHERE OrderLineID=@p1
) THEN 1 ELSE 0 END
`, lineID).Scan(&exists)
if err != nil {
return false, fmt.Errorf("invoice line kontrol query hatasi: %w", err)
}
return exists == 1, nil
}
// ======================================================
// HEADER UPDATE
// ======================================================
_, err = tx.Exec(`
UPDATE BAGGI_V3.dbo.trOrderHeader SET
OrderDate=@p1,
@@ -853,11 +1222,12 @@ WHERE OrderHeaderID=@p11
if err != nil {
return nil, err
}
// ======================================================
// PREPARE STATEMENTS
// ======================================================
insStmt, err := tx.Prepare(`INSERT INTO BAGGI_V3.dbo.trOrderLine (
insStmt, err := tx.Prepare(`
INSERT INTO BAGGI_V3.dbo.trOrderLine (
OrderLineID, SortOrder, ItemTypeCode, ItemCode, ColorCode,
ItemDim1Code, ItemDim2Code, ItemDim3Code,
Qty1, Qty2, CancelQty1, CancelQty2, OrderCancelReasonCode,
@@ -872,21 +1242,23 @@ BaseSubCurrAccID, BaseStoreCode,
OrderHeaderID, CreatedUserName, CreatedDate,
LastUpdatedUserName, LastUpdatedDate,
SurplusOrderQtyToleranceRate,
WithHoldingTaxTypeCode, DOVCode)
WithHoldingTaxTypeCode, DOVCode
)
VALUES (
@p1,@p2,@p3,@p4,@p5,@p6,@p7,@p8,@p9,@p10,
@p11,@p12,@p13,@p14,@p15,@p16,@p17,@p18,
@p19,@p20,@p21,@p22,@p23,@p24,@p25,@p26,@p27,
@p28,@p29,@p30,@p31,@p32,@p33,@p34,@p35,
@p36,@p37,@p38,@p39,@p40,@p41,@p42,@p43,
@p44,@p45)`)
@p44,@p45
)`)
if err != nil {
return nil, err
}
defer insStmt.Close()
updStmt, err := tx.Prepare(`UPDATE BAGGI_V3.dbo.trOrderLine SET
updStmt, err := tx.Prepare(`
UPDATE BAGGI_V3.dbo.trOrderLine SET
SortOrder=@p1, ItemTypeCode=@p2, ItemCode=@p3, ColorCode=@p4,
ItemDim1Code=@p5, ItemDim2Code=@p6, ItemDim3Code=@p7,
Qty1=@p8, Qty2=@p9, CancelQty1=@p10, CancelQty2=@p11,
@@ -905,17 +1277,18 @@ LastUpdatedUserName=@p37, LastUpdatedDate=@p38,
SurplusOrderQtyToleranceRate=@p39,
WithHoldingTaxTypeCode=@p40, DOVCode=@p41
WHERE OrderLineID=@p42 AND ISNULL(IsClosed,0)=0`)
if err != nil {
return nil, err
}
defer updStmt.Close()
// ======================================================
// LOOP
// ======================================================
lineResults := make([]OrderLineResult, 0)
seenCombo := make(map[string]bool)
for _, ln := range lines {
comboKey := normalizeComboKey(safeNS(ln.ComboKey))
if comboKey == "" {
comboKey = makeComboKey(ln)
@@ -929,7 +1302,7 @@ WHERE OrderLineID=@p42 AND ISNULL(IsClosed,0)=0`)
seenCombo[comboKey] = true
}
// Kapalı satır
// Kapalı satır guard
if ln.OrderLineID != "" && existingClosed[ln.OrderLineID] {
continue
}
@@ -941,23 +1314,39 @@ WHERE OrderLineID=@p42 AND ISNULL(IsClosed,0)=0`)
// DELETE SIGNAL
if ln.OrderLineID != "" && qtyValue(ln.Qty1) <= 0 {
_, err := tx.Exec(`DELETE FROM BAGGI_V3.dbo.trOrderLineCurrency WHERE OrderLineID=@p1`, ln.OrderLineID)
invoiced, err := isLineInvoiced(ln.OrderLineID)
if err != nil {
return nil, err
}
_, err = tx.Exec(`
if invoiced {
return nil, &models.ValidationError{
Code: "ORDER_LINE_INVOICED",
Message: fmt.Sprintf("Faturalanmis satir silinemez (OrderLineID=%s)", ln.OrderLineID),
ClientKey: safeNS(ln.ClientKey),
ItemCode: strings.TrimSpace(safeNS(ln.ItemCode)),
ColorCode: strings.TrimSpace(safeNS(ln.ColorCode)),
Dim1: strings.TrimSpace(safeNS(ln.ItemDim1Code)),
Dim2: strings.TrimSpace(safeNS(ln.ItemDim2Code)),
}
}
if _, err := tx.Exec(`DELETE FROM BAGGI_V3.dbo.trOrderLineCurrency WHERE OrderLineID=@p1`, ln.OrderLineID); err != nil {
return nil, err
}
if _, err := tx.Exec(`
DELETE FROM BAGGI_V3.dbo.trOrderLine
WHERE OrderHeaderID=@p1 AND OrderLineID=@p2 AND ISNULL(IsClosed,0)=0
`, header.OrderHeaderID, ln.OrderLineID)
if err != nil {
`, header.OrderHeaderID, ln.OrderLineID); err != nil {
return nil, err
}
delete(existingOpen, ln.OrderLineID)
delete(existingOpenCombo, comboKey)
continue
}
isNew := false
// ID resolve: boşsa combo'dan yakala, yoksa yeni üret
if ln.OrderLineID == "" {
if dbID, ok := existingOpenCombo[comboKey]; ok {
ln.OrderLineID = dbID
@@ -967,6 +1356,7 @@ WHERE OrderHeaderID=@p1 AND OrderLineID=@p2 AND ISNULL(IsClosed,0)=0
}
}
// Variant guard
if qtyValue(ln.Qty1) > 0 {
if err := ValidateItemVariant(tx, ln); err != nil {
return nil, err
@@ -1048,9 +1438,28 @@ WHERE OrderHeaderID=@p1 AND OrderLineID=@p2 AND ISNULL(IsClosed,0)=0
}
}
// ✅ Currency UPSERT (insert/update sonrası ortak)
if err := upsertLineCurrency(
tx,
ln.OrderLineID,
safeNS(ln.DocCurrencyCode),
nf0(ln.PriceExchangeRate),
nf0(ln.Price),
nf0(ln.Qty1),
nf0(ln.LDisRate1),
0, // TODO: header TDisRate toplamını istersen buraya bağlarız
nf0(ln.VatRate), // satır vat oranı
ln,
v3User,
); err != nil {
return nil, err
}
// Bu satır işlendi -> existingOpen setinden düş
delete(existingOpen, ln.OrderLineID)
delete(existingOpenCombo, comboKey)
// Sonuç mapping
if ln.ClientKey.Valid {
lineResults = append(lineResults, OrderLineResult{
ClientKey: ln.ClientKey.String,
@@ -1059,18 +1468,31 @@ WHERE OrderHeaderID=@p1 AND OrderLineID=@p2 AND ISNULL(IsClosed,0)=0
}
}
// Grid dışı kalan açık satırlar
// =======================================================
// Grid dışı kalan açık satırlar (payload'da yok -> sil)
// =======================================================
for id := range existingOpen {
_, err := tx.Exec(`DELETE FROM BAGGI_V3.dbo.trOrderLineCurrency WHERE OrderLineID=@p1`, id)
invoiced, err := isLineInvoiced(id)
if err != nil {
return nil, err
}
_, err = tx.Exec(`DELETE FROM BAGGI_V3.dbo.trOrderLine WHERE OrderLineID=@p1 AND ISNULL(IsClosed,0)=0`, id)
if err != nil {
if invoiced {
meta := existingOpenMeta[id]
fmt.Printf("[ORDER_UPDATE] skip delete invoiced line id=%s item=%s color=%s dim1=%s dim2=%s\n",
id, meta.item, meta.color, meta.dim1, meta.dim2)
continue
}
if _, err := tx.Exec(`DELETE FROM BAGGI_V3.dbo.trOrderLineCurrency WHERE OrderLineID=@p1`, id); err != nil {
return nil, err
}
if _, err := tx.Exec(`DELETE FROM BAGGI_V3.dbo.trOrderLine WHERE OrderLineID=@p1 AND ISNULL(IsClosed,0)=0`, id); err != nil {
return nil, err
}
}
// =======================================================
// COMMIT + RETURN
// =======================================================
if err := tx.Commit(); err != nil {
return nil, err
}

View File

@@ -10,6 +10,10 @@ import (
// ========================================================
// 📌 GetOrderList — FINAL + CURRENCY SAFE + PIYASA AUTHZ
//
// ✅ TotalAmount artık trOrderLineCurrency(NetAmount) üzerinden
// ve CurrencyCode = Header.DocCurrencyCode satırından gelir.
//
// ========================================================
func GetOrderList(
ctx context.Context,
@@ -36,10 +40,8 @@ func GetOrderList(
}
if len(codes) == 0 {
// hiç yetkisi yok → hiç kayıt dönmesin
piyasaWhere = "1=0"
} else {
// ⚠️ EXISTS içinde kullanılacak
piyasaWhere = authz.BuildINClause(
"UPPER(f2.CustomerAtt01)",
codes,
@@ -86,6 +88,29 @@ SELECT
ELSE 0
END AS TotalAmountUSD,
ISNULL(l.PackedAmount,0) AS PackedAmount,
CASE
WHEN h.DocCurrencyCode = 'USD'
THEN ISNULL(l.PackedAmount,0)
WHEN h.DocCurrencyCode = 'TRY'
AND usd.Rate > 0
THEN ISNULL(l.PackedTRY,0) / usd.Rate
WHEN cur.Rate > 0
AND usd.Rate > 0
THEN (ISNULL(l.PackedAmount,0) * cur.Rate) / usd.Rate
ELSE 0
END AS PackedUSD,
CASE
WHEN ISNULL(l.TotalAmount,0) > 0
THEN (ISNULL(l.PackedAmount,0) * 100.0) / NULLIF(l.TotalAmount,0)
ELSE 0
END AS PackedRatePct,
ISNULL(h.IsCreditableConfirmed,0) AS IsCreditableConfirmed,
ISNULL(h.Description,'') AS Description,
@@ -93,12 +118,40 @@ SELECT
FROM dbo.trOrderHeader h
-- ✅ TOPLAM ARTIK trOrderLineCurrency'den: CurrencyCode = DocCurrencyCode
JOIN (
SELECT
OrderHeaderID,
SUM(Qty1 * Price) AS TotalAmount
FROM dbo.trOrderLine
GROUP BY OrderHeaderID
l.OrderHeaderID,
SUM(ISNULL(c.NetAmount,0)) AS TotalAmount,
SUM(
CASE
WHEN ISNULL(c.CurrencyCode,'') = 'TRY'
THEN ISNULL(c.NetAmount,0)
ELSE 0
END
) AS TotalTRY,
SUM(
CASE
WHEN ISNULL(l.IsClosed,0) = 1
THEN ISNULL(c.NetAmount,0)
ELSE 0
END
) AS PackedAmount,
SUM(
CASE
WHEN ISNULL(l.IsClosed,0) = 1
AND ISNULL(c.CurrencyCode,'') = 'TRY'
THEN ISNULL(c.NetAmount,0)
ELSE 0
END
) AS PackedTRY
FROM dbo.trOrderLine l
JOIN dbo.trOrderHeader h2
ON h2.OrderHeaderID = l.OrderHeaderID
LEFT JOIN dbo.trOrderLineCurrency c
ON c.OrderLineID = l.OrderLineID
AND c.CurrencyCode = ISNULL(h2.DocCurrencyCode,'TRY')
GROUP BY l.OrderHeaderID
) l
ON l.OrderHeaderID = h.OrderHeaderID