Files
bssapp/svc/queries/order_write.go
2026-02-11 17:46:22 +03:00

1080 lines
32 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ===================== PART 1 (Satır 1-300) =====================
// =======================================================
// order_write.go — v4.3 FINAL (Insert + Update + Delete)
// - PCTCode her zaman "%0" olarak yazılır (cdPCT FK uyumlu)
// - INSERT/UPDATE öncesi ItemVariant Guard + Duplicate Guard (payload içi)
// - UpdateOrder: DELETE öncesi child tablolar (trOrderLineCurrency) temizlenir
// =======================================================
package queries
import (
"bssapp-backend/db"
"bssapp-backend/models"
"database/sql"
"fmt"
"github.com/google/uuid"
"strings"
"time"
)
func nf0(v models.NullFloat64) float64 {
if !v.Valid {
return 0
}
return v.Float64
}
// =======================================================
// COMBO KEY & STRING HELPERS
// =======================================================
func normalizeComboKey(s string) string {
return strings.ToUpper(strings.TrimSpace(s))
}
// makeComboKey: frontend tarafında NullString olmayan structlar için
func makeComboKey(ln models.OrderDetail) string {
model := safeNS(ln.ItemCode)
renk := safeNS(ln.ColorCode)
renk2 := safeNS(ln.ItemDim2Code)
beden := safeNS(ln.ItemDim1Code)
return normalizeComboKey(
fmt.Sprintf("%s||%s||%s||%s", model, renk, beden, renk2),
)
}
// qtyValue → NullFloat64 güvenli float64
func qtyValue(q models.NullFloat64) float64 {
if !q.Valid {
return 0
}
return q.Float64
}
// VatCode: NullString → string
// - NULL → ""
// - "0" → "" (FK patlamasın, sadece anlamlı kodlar gönderiyoruz)
// - "%0", "%10" vb → trimlenmiş hali
func normalizeVatCode(ns models.NullString) string {
if !ns.Valid {
return ""
}
s := strings.TrimSpace(ns.String)
if s == "0" {
return ""
}
return s
}
// PCTCode: NullString → string
// - Artık her durumda "%0" döndürür (tek PCT tipi kullanımı)
// NOT: SQL tarafında da "%0" cdPCT içinde tanımlı olmalı
func normalizePCTCode(ns models.NullString) string {
return "%0"
}
// models.NullString → trimlenmiş string (NULL ise "")
func safeNS(ns models.NullString) string {
if !ns.Valid {
return ""
}
return strings.TrimSpace(ns.String)
}
// =======================================================
// COMBO KEY HELPERS (DB alanlarından)
// =======================================================
// makeComboKeyParts: düz string alanlardan comboKey üret
// comboKey = model || renk || beden || renk2
func makeComboKeyParts(item, color, dim1, dim2 string) string {
item = strings.TrimSpace(item)
color = strings.TrimSpace(color)
dim1 = strings.TrimSpace(dim1)
dim2 = strings.TrimSpace(dim2)
if item == "" && color == "" && dim1 == "" && dim2 == "" {
return ""
}
return normalizeComboKey(item + "||" + color + "||" + dim1 + "||" + dim2)
}
// comboFromNulls: NullString alanlardan comboKey üret
func comboFromNulls(item, color, dim1, dim2 models.NullString) string {
return makeComboKeyParts(
safeNS(item),
safeNS(color),
safeNS(dim1),
safeNS(dim2),
)
}
// =======================================================
// ✅ ItemVariant Guard — INSERT / UPDATE öncesi
// =======================================================
// normalizeKeyPart: NullString → trim + UPPER
func normalizeKeyPart(ns models.NullString) string {
s := strings.TrimSpace(safeNS(ns))
return strings.ToUpper(s)
}
// =======================================================
// AKSBIR DETECTION
// =======================================================
// =======================================================
// COMBO KEY BUILDER (AKSBIR AWARE)
// =======================================================
// Variant check: ItemCode + ColorCode + Dim1 + Dim2
func ValidateItemVariant(tx *sql.Tx, ln models.OrderDetail) error {
fmt.Printf(
"🧪 VARIANT GUARD INPUT | ClientKey=%s Item=%q Color=%q Dim1=%q Dim2=%q Dim3=%q Qty1=%v\n",
safeNS(ln.ClientKey),
safeNS(ln.ItemCode),
safeNS(ln.ColorCode),
safeNS(ln.ItemDim1Code),
safeNS(ln.ItemDim2Code),
safeNS(ln.ItemDim3Code),
nf0(ln.Qty1),
)
item := normalizeKeyPart(ln.ItemCode)
color := normalizeKeyPart(ln.ColorCode)
dim1 := normalizeKeyPart(ln.ItemDim1Code)
dim2 := normalizeKeyPart(ln.ItemDim2Code)
// ✅ Placeholder/boş standardizasyon (SENDE "_" geliyor)
normalizeEmpty := func(s string) string {
s = strings.TrimSpace(strings.ToUpper(s))
if s == "_" || s == "-" {
return ""
}
return s
}
item = normalizeEmpty(item)
color = normalizeEmpty(color)
dim1 = normalizeEmpty(dim1)
dim2 = normalizeEmpty(dim2)
if item == "" {
return fmt.Errorf(
"ItemCode boş olamaz (ClientKey=%s)",
safeNS(ln.ClientKey),
)
fmt.Printf(
"🧪 VARIANT NORMALIZED | Item=%q Color=%q Dim1=%q Dim2=%q\n",
item, color, dim1, dim2,
)
}
// İ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 (
SELECT 1
FROM BAGGI_V3.dbo.prItemVariant V WITH (NOLOCK)
WHERE ISNULL(LTRIM(RTRIM(V.ItemCode)),'') = @p1
AND ISNULL(LTRIM(RTRIM(V.ColorCode)),'') = @p2
AND ISNULL(LTRIM(RTRIM(V.ItemDim1Code)),'') = @p3
AND ISNULL(LTRIM(RTRIM(V.ItemDim2Code)),'') = @p4
) THEN 1 ELSE 0 END
`, item, color, dim1, dim2).Scan(&exists)
if err != nil {
return fmt.Errorf("ItemVariant kontrol query hatası: %w", err)
}
if exists != 1 {
return &models.ValidationError{
Code: "INVALID_ITEM_VARIANT",
Message: "Tanımsız ürün kombinasyonu",
ClientKey: safeNS(ln.ClientKey),
ItemCode: item,
ColorCode: color,
Dim1: dim1,
Dim2: dim2,
}
}
return nil
}
// ValidateOrderVariants: save/update öncesi payload satırlarını prItemVariant'a göre doğrular.
// invalid döner; error sadece DB/prepare/query hatalarında döner.
func ValidateOrderVariants(db *sql.DB, lines []models.OrderDetail) ([]models.InvalidVariant, error) {
normalizeEmpty := func(s string) string {
s = strings.TrimSpace(strings.ToUpper(s))
if s == "_" || s == "-" {
return ""
}
return s
}
stmt, err := db.Prepare(`
SELECT CASE WHEN EXISTS (
SELECT 1
FROM BAGGI_V3.dbo.prItemVariant V WITH (NOLOCK)
WHERE ISNULL(LTRIM(RTRIM(V.ItemCode)),'') = @p1
AND ISNULL(LTRIM(RTRIM(V.ColorCode)),'') = @p2
AND ISNULL(LTRIM(RTRIM(V.ItemDim1Code)),'') = @p3
AND ISNULL(LTRIM(RTRIM(V.ItemDim2Code)),'') = @p4
) THEN 1 ELSE 0 END
`)
if err != nil {
return nil, fmt.Errorf("validate prepare hatası: %w", err)
}
defer stmt.Close()
invalid := make([]models.InvalidVariant, 0)
for i, ln := range lines {
qty := qtyValue(ln.Qty1)
if qty <= 0 {
continue
}
item := normalizeEmpty(normalizeKeyPart(ln.ItemCode))
color := normalizeEmpty(normalizeKeyPart(ln.ColorCode))
dim1 := normalizeEmpty(normalizeKeyPart(ln.ItemDim1Code))
dim2 := normalizeEmpty(normalizeKeyPart(ln.ItemDim2Code))
// ItemCode boş ise invalid
if strings.TrimSpace(item) == "" {
invalid = append(invalid, models.InvalidVariant{
Index: i,
ClientKey: safeNS(ln.ClientKey),
ItemCode: item,
ColorCode: color,
Dim1: dim1,
Dim2: dim2,
Qty1: qty,
ComboKey: safeNS(ln.ComboKey),
Reason: "ItemCode boş",
})
continue
}
var exists int
if err := stmt.QueryRow(item, color, dim1, dim2).Scan(&exists); err != nil {
return nil, fmt.Errorf("validate query hatası (i=%d): %w", i, err)
}
if exists != 1 {
invalid = append(invalid, models.InvalidVariant{
Index: i,
ClientKey: safeNS(ln.ClientKey),
ItemCode: item,
ColorCode: color,
Dim1: dim1,
Dim2: dim2,
Qty1: qty,
ComboKey: safeNS(ln.ComboKey),
Reason: "prItemVariantta yok",
})
}
}
return invalid, nil
}
// =======================================================
// LineResult → frontend senkronu için
// =======================================================
type OrderLineResult struct {
ClientKey string `json:"clientKey"`
OrderLineID string `json:"orderLineID"`
}
// =======================================================
// 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) {
conn := db.GetDB()
fmt.Println("🟦 InsertOrder() BAŞLADI -----------------------------")
tx, err := conn.Begin()
if err != nil {
return "", nil, fmt.Errorf("tx baslatilamadi: %w", err)
}
defer tx.Rollback()
now := time.Now()
v3User := fmt.Sprintf("V3U%d-%s", user.V3UserGroup, user.V3Username)
// =======================================================
// 1) BACKEND — OrderHeaderID üretimi (HER ZAMAN)
// =======================================================
realHeaderID := uuid.New().String()
fmt.Println("🟩 Backend yeni OrderHeaderID üretti:", realHeaderID)
header.OrderHeaderID = realHeaderID
// =======================================================
// 2) OrderNumber üretimi (LOCAL-* ise gerçek WS numarası)
// =======================================================
if !header.OrderNumber.Valid ||
strings.HasPrefix(header.OrderNumber.String, "LOCAL-") ||
len(strings.TrimSpace(header.OrderNumber.String)) == 0 {
fmt.Println("🟨 LOCAL numara geldi → gerçek WS numarası üretilecek...")
var realNumber string
err := tx.QueryRow(`
SELECT CONCAT(
'1-WS-3-',
RIGHT('00000' + CAST(NEXT VALUE FOR BAGGI_V3.dbo.Seq_OrderNumber_WS AS VARCHAR(10)), 5)
)
`).Scan(&realNumber)
if err != nil {
return "", nil, fmt.Errorf("Gerçek sipariş numarası üretilemedi: %w", err)
}
fmt.Println("🟩 Üretilen gerçek WS numarası:", realNumber)
header.OrderNumber.String = realNumber
header.OrderNumber.Valid = true
}
newID := realHeaderID // artık DBye bu yazılacak
// =======================================================
// 3) Döviz kuru çözümü
// =======================================================
exRate := 1.0
if header.DocCurrencyCode.Valid && header.DocCurrencyCode.String != "TRY" {
if c, err := GetTodayCurrencyV3(conn, header.DocCurrencyCode.String); err == nil {
if c.Rate > 0 {
exRate = c.Rate
}
}
}
// =======================================================
// 4) HEADER INSERT
// =======================================================
queryHeader := `
INSERT INTO BAGGI_V3.dbo.trOrderHeader (
OrderHeaderID, OrderTypeCode, ProcessCode, OrderNumber, IsCancelOrder,
OrderDate, OrderTime, DocumentNumber, PaymentTerm,
AverageDueDate, Description, InternalDescription,
CurrAccTypeCode, CurrAccCode, SubCurrAccID, ContactID,
ShipmentMethodCode, ShippingPostalAddressID, BillingPostalAddressID,
GuarantorContactID, GuarantorContactID2, RoundsmanCode,
DeliveryCompanyCode, TaxTypeCode, WithHoldingTaxTypeCode, DOVCode,
TaxExemptionCode, CompanyCode, OfficeCode, StoreTypeCode, StoreCode,
POSTerminalID, WarehouseCode, ToWarehouseCode,
OrdererCompanyCode, OrdererOfficeCode, OrdererStoreCode,
GLTypeCode, DocCurrencyCode, LocalCurrencyCode, ExchangeRate,
TDisRate1, TDisRate2, TDisRate3, TDisRate4, TDisRate5,
DiscountReasonCode, SurplusOrderQtyToleranceRate,
ImportFileNumber, ExportFileNumber,
IncotermCode1, IncotermCode2, LettersOfCreditNumber,
PaymentMethodCode, IsInclutedVat, IsCreditSale, IsCreditableConfirmed,
CreditableConfirmedUser, CreditableConfirmedDate,
IsSalesViaInternet, IsSuspended, IsCompleted, IsPrinted,
IsLocked, UserLocked, IsClosed,
ApplicationCode, ApplicationID,
CreatedUserName, CreatedDate, LastUpdatedUserName, LastUpdatedDate,
IsProposalBased
)
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,
@p46,@p47,
@p48,@p49,
@p50,@p51,@p52,
@p53,@p54,@p55,@p56,@p57,
@p58,@p59,@p60,@p61,@p62,
@p63,@p64,@p65,
@p66,@p67,@p68,@p69,@p70,@p71,@p72,@p73
);
`
fmt.Println("🟪 HEADER INSERT ÇALIŞIYOR...")
headerParams := []any{
header.OrderHeaderID,
nullableInt16(header.OrderTypeCode, 1),
nullableString(header.ProcessCode, "WS"),
nullableString(header.OrderNumber, ""),
nullableBool(header.IsCancelOrder, false),
nullableDateString(header.OrderDate),
nullableTimeString(header.OrderTime),
nullableString(header.DocumentNumber, ""),
nullableInt16(header.PaymentTerm, 0),
nullableDateString(header.AverageDueDate),
nullableString(header.Description, ""),
nullableString(header.InternalDescription, ""),
nullableInt16(header.CurrAccTypeCode, 0),
nullableString(header.CurrAccCode, ""),
nullableUUID(header.SubCurrAccID),
nullableUUID(header.ContactID),
nullableString(header.ShipmentMethodCode, ""),
nullableUUID(header.ShippingPostalAddressID),
nullableUUID(header.BillingPostalAddressID),
nullableUUID(header.GuarantorContactID),
nullableUUID(header.GuarantorContactID2),
nullableString(header.RoundsmanCode, ""),
nullableString(header.DeliveryCompanyCode, ""),
nullableInt16(header.TaxTypeCode, 0),
nullableString(header.WithHoldingTaxTypeCode, ""),
nullableString(header.DOVCode, ""),
nullableInt16(header.TaxExemptionCode, 0),
nullableInt32ToInt16(header.CompanyCode, 1),
nullableString(header.OfficeCode, "101"),
nullableInt16(header.StoreTypeCode, 5),
nullableString(header.StoreCode, ""),
nullableInt16(header.POSTerminalID, 0),
nullableString(header.WarehouseCode, "1-0-12"),
nullableString(header.ToWarehouseCode, ""),
nullableInt32ToInt16(header.OrdererCompanyCode, 1),
nullableString(header.OrdererOfficeCode, "101"),
nullableString(header.OrdererStoreCode, ""),
nullableString(header.GLTypeCode, ""),
nullableString(header.DocCurrencyCode, "TRY"),
nullableString(header.LocalCurrencyCode, "TRY"),
nullableFloat64(header.ExchangeRate, exRate),
nullableFloat64(header.TDisRate1, 0),
nullableFloat64(header.TDisRate2, 0),
nullableFloat64(header.TDisRate3, 0),
nullableFloat64(header.TDisRate4, 0),
nullableFloat64(header.TDisRate5, 0),
nullableInt16(header.DiscountReasonCode, 0),
nullableFloat64(header.SurplusOrderQtyToleranceRate, 0),
nullableString(header.ImportFileNumber, ""),
nullableString(header.ExportFileNumber, ""),
nullableString(header.IncotermCode1, ""),
nullableString(header.IncotermCode2, ""),
nullableString(header.LettersOfCreditNumber, ""),
nullableString(header.PaymentMethodCode, ""),
nullableBool(header.IsInclutedVat, false),
nullableBool(header.IsCreditSale, true),
nullableBool(header.IsCreditableConfirmed, true),
nullableString(header.CreditableConfirmedUser, v3User),
nullableDateTime(header.CreditableConfirmedDate, now),
nullableBool(header.IsSalesViaInternet, false),
nullableBool(header.IsSuspended, false),
nullableBool(header.IsCompleted, false),
nullableBool(header.IsPrinted, false),
nullableBool(header.IsLocked, false),
nullableBool(header.UserLocked, false),
nullableBool(header.IsClosed, false),
nullableString(header.ApplicationCode, "Order"),
nullableUUID(header.ApplicationID),
nullableString(header.CreatedUserName, v3User),
nullableDateTimeString(header.CreatedDate, now),
nullableString(header.LastUpdatedUserName, v3User),
nullableDateTimeString(header.LastUpdatedDate, now),
nullableBool(header.IsProposalBased, false),
}
if _, err := tx.Exec(queryHeader, headerParams...); err != nil {
fmt.Println("❌ HEADER INSERT ERROR:", err)
return "", nil, fmt.Errorf("header insert hatasi: %w", err)
}
fmt.Println("🟩 HEADER INSERT OK — ID:", newID)
// =======================================================
// 5) LINE INSERT
// =======================================================
insStmt, err := tx.Prepare(`
INSERT INTO BAGGI_V3.dbo.trOrderLine (
OrderLineID,
SortOrder, ItemTypeCode, ItemCode, ColorCode,
ItemDim1Code, ItemDim2Code, ItemDim3Code,
Qty1, Qty2,
CancelQty1, CancelQty2,
OrderCancelReasonCode,
DeliveryDate, PlannedDateOfLading,
LineDescription,
UsedBarcode, CostCenterCode,
VatCode, VatRate, PCTCode, PCTRate,
LDisRate1, LDisRate2, LDisRate3, LDisRate4, LDisRate5,
DocCurrencyCode, PriceCurrencyCode, PriceExchangeRate,
Price,
BaseProcessCode, BaseOrderNumber,
BaseCustomerTypeCode, BaseCustomerCode,
BaseSubCurrAccID, BaseStoreCode,
OrderHeaderID,
CreatedUserName, CreatedDate,
LastUpdatedUserName, LastUpdatedDate,
SurplusOrderQtyToleranceRate,
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
)`)
if err != nil {
return "", nil, fmt.Errorf("line insert stmt hazirlanamadi: %w", err)
}
defer insStmt.Close()
lineResults := make([]OrderLineResult, 0, len(lines))
// ✅ Duplicate Guard (payload içi)
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)
// Her satır için yeni GUID
if ln.OrderLineID == "" {
newLineID := uuid.New().String()
fmt.Println("🆕 Yeni LineID üretildi:", newLineID)
ln.OrderLineID = newLineID
}
// ✅ Duplicate Guard (comboKey)
comboKey := normalizeComboKey(safeNS(ln.ComboKey))
if comboKey == "" {
comboKey = makeComboKey(ln)
}
if qtyValue(ln.Qty1) > 0 && comboKey != "" {
if seenCombo[comboKey] {
return "", nil, fmt.Errorf(
"Duplicate satır (comboKey=%s, ClientKey=%s)",
comboKey,
safeNS(ln.ClientKey),
)
}
seenCombo[comboKey] = true
}
// V2 Logic → %0 PCT
vatCode := normalizeVatCode(ln.VatCode)
pctCode := normalizePCTCode(ln.PCTCode)
var pctParam any
if pctCode == "" {
pctParam = nil
} else {
pctParam = pctCode
}
planned := nullableDateString(ln.PlannedDateOfLading)
// ✅ INSERT ÖNCESİ ItemVariant GUARD
if qtyValue(ln.Qty1) > 0 {
if err := ValidateItemVariant(tx, ln); err != nil {
fmt.Println("❌ VARIANT GUARD (INSERT):", err)
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,
ln.OrderLineID,
safeNS(ln.ClientKey),
safeNS(ln.ItemCode),
safeNS(ln.ColorCode),
safeNS(ln.ItemDim1Code),
safeNS(ln.ItemDim2Code),
safeNS(ln.ItemDim3Code),
nf0(ln.Qty1),
)
_, err := insStmt.Exec(
ln.OrderLineID,
ln.SortOrder,
ln.ItemTypeCode,
nullableString(ln.ItemCode, ""),
safeNS(ln.ColorCode),
safeNS(ln.ItemDim1Code),
safeNS(ln.ItemDim2Code),
nullableString(ln.ItemDim3Code, ""),
ln.Qty1, ln.Qty2,
ln.CancelQty1, ln.CancelQty2,
nullableString(ln.OrderCancelReasonCode, ""),
nullableTime(ln.DeliveryDate, now),
planned,
nullableString(ln.LineDescription, ""),
nullableString(ln.UsedBarcode, ""),
nullableString(ln.CostCenterCode, ""),
vatCode, nf0(ln.VatRate),
pctParam, nf0(ln.PCTRate),
nf0(ln.LDisRate1),
nf0(ln.LDisRate2),
nf0(ln.LDisRate3),
nf0(ln.LDisRate4),
nf0(ln.LDisRate5),
nullableString(ln.DocCurrencyCode, "TRY"),
nullableString(ln.PriceCurrencyCode, "TRY"),
nf0(ln.PriceExchangeRate),
nf0(ln.Price),
nullableString(ln.BaseProcessCode, ""),
nullableString(ln.BaseOrderNumber, ""),
ln.BaseCustomerTypeCode,
nullableString(ln.BaseCustomerCode, ""),
nullableUUID(ln.BaseSubCurrAccID),
nullableString(ln.BaseStoreCode, ""),
header.OrderHeaderID,
v3User, now,
v3User, now,
nf0(ln.SurplusOrderQtyToleranceRate),
nullableString(ln.WithHoldingTaxTypeCode, ""),
nullableString(ln.DOVCode, ""),
)
if err != nil {
fmt.Println("❌ INSERT LINE ERROR")
fmt.Printf(
"💥 FAILED LINE | LineID=%s ClientKey=%s Item=%q Color=%q Dim1=%q Dim2=%q Dim3=%q\n",
ln.OrderLineID,
safeNS(ln.ClientKey),
safeNS(ln.ItemCode),
safeNS(ln.ColorCode),
safeNS(ln.ItemDim1Code),
safeNS(ln.ItemDim2Code),
safeNS(ln.ItemDim3Code),
)
fmt.Println("SQL ERROR:", err)
return "", nil, fmt.Errorf("line insert hatasi: %w", err)
}
if ln.ClientKey.Valid && ln.ClientKey.String != "" {
lineResults = append(lineResults, OrderLineResult{
ClientKey: ln.ClientKey.String,
OrderLineID: ln.OrderLineID,
})
}
}
// =======================================================
// 6) COMMIT
// =======================================================
fmt.Println("🟨 COMMIT EDİLİYOR...")
if err := tx.Commit(); err != nil {
fmt.Println("❌ COMMIT ERROR:", err)
return "", nil, err
}
fmt.Println("✅ COMMIT OK — INSERT ORDER BİTTİ")
fmt.Println("────────────────────────────────────")
return newID, lineResults, nil
}
// =======================================================
// 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.Println("══════════════════════════════════════")
tx, err := conn.Begin()
if err != nil {
return nil, fmt.Errorf("tx baslatilamadi: %w", err)
}
defer tx.Rollback()
now := time.Now()
v3User := fmt.Sprintf("V3U%d-%s", user.V3UserGroup, user.V3Username)
// Döviz kuru
exRate := 1.0
if header.DocCurrencyCode.Valid && header.DocCurrencyCode.String != "TRY" {
if c, err := GetTodayCurrencyV3(conn, header.DocCurrencyCode.String); err == nil && c.Rate > 0 {
exRate = c.Rate
}
}
// =======================================================
// 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)
rows, err := tx.Query(`
SELECT
CONVERT(varchar(36), OrderLineID),
ISNULL(IsClosed, 0),
ISNULL(ItemCode,''),
ISNULL(ColorCode,''),
ISNULL(ItemDim1Code,''),
ISNULL(ItemDim2Code,'')
FROM BAGGI_V3.dbo.trOrderLine
WHERE OrderHeaderID=@p1
`, header.OrderHeaderID)
if err != nil {
return nil, fmt.Errorf("mevcut satirlar okunamadi: %w", err)
}
defer rows.Close()
for rows.Next() {
var id, item, color, dim1, dim2 string
var closed bool
if err := rows.Scan(&id, &closed, &item, &color, &dim1, &dim2); err != nil {
return nil, err
}
combo := makeComboKeyParts(item, color, dim1, dim2)
if closed {
existingClosed[id] = true
if combo != "" {
existingClosedCombo[combo] = id
}
} else {
existingOpen[id] = true
if combo != "" {
existingOpenCombo[combo] = id
}
}
}
// ======================================================
// HEADER UPDATE
// ======================================================
_, err = tx.Exec(`
UPDATE BAGGI_V3.dbo.trOrderHeader SET
OrderDate=@p1,
OrderTime=@p2,
AverageDueDate=@p3,
Description=@p4,
InternalDescription=@p5,
DocCurrencyCode=@p6,
LocalCurrencyCode=@p7,
ExchangeRate=@p8,
LastUpdatedUserName=@p9,
LastUpdatedDate=@p10
WHERE OrderHeaderID=@p11
`,
nullableDateString(header.OrderDate),
nullableTimeString(header.OrderTime),
nullableDateString(header.AverageDueDate),
nullableString(header.Description, ""),
nullableString(header.InternalDescription, ""),
nullableString(header.DocCurrencyCode, "TRY"),
nullableString(header.LocalCurrencyCode, "TRY"),
nullableFloat64(header.ExchangeRate, exRate),
v3User,
now,
header.OrderHeaderID,
)
if err != nil {
return nil, err
}
// ======================================================
// PREPARE STATEMENTS
// ======================================================
insStmt, err := tx.Prepare(`INSERT INTO BAGGI_V3.dbo.trOrderLine (
OrderLineID, SortOrder, ItemTypeCode, ItemCode, ColorCode,
ItemDim1Code, ItemDim2Code, ItemDim3Code,
Qty1, Qty2, CancelQty1, CancelQty2, OrderCancelReasonCode,
DeliveryDate, PlannedDateOfLading, LineDescription,
UsedBarcode, CostCenterCode,
VatCode, VatRate, PCTCode, PCTRate,
LDisRate1, LDisRate2, LDisRate3, LDisRate4, LDisRate5,
DocCurrencyCode, PriceCurrencyCode, PriceExchangeRate,
Price, BaseProcessCode, BaseOrderNumber,
BaseCustomerTypeCode, BaseCustomerCode,
BaseSubCurrAccID, BaseStoreCode,
OrderHeaderID, CreatedUserName, CreatedDate,
LastUpdatedUserName, LastUpdatedDate,
SurplusOrderQtyToleranceRate,
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)`)
if err != nil {
return nil, err
}
defer insStmt.Close()
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,
OrderCancelReasonCode=@p12,
DeliveryDate=@p13, PlannedDateOfLading=@p14,
LineDescription=@p15, UsedBarcode=@p16, CostCenterCode=@p17,
VatCode=@p18, VatRate=@p19, PCTCode=@p20, PCTRate=@p21,
LDisRate1=@p22, LDisRate2=@p23, LDisRate3=@p24,
LDisRate4=@p25, LDisRate5=@p26,
DocCurrencyCode=@p27, PriceCurrencyCode=@p28,
PriceExchangeRate=@p29, Price=@p30,
BaseProcessCode=@p31, BaseOrderNumber=@p32,
BaseCustomerTypeCode=@p33, BaseCustomerCode=@p34,
BaseSubCurrAccID=@p35, BaseStoreCode=@p36,
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()
lineResults := make([]OrderLineResult, 0)
seenCombo := make(map[string]bool)
for _, ln := range lines {
comboKey := normalizeComboKey(safeNS(ln.ComboKey))
if comboKey == "" {
comboKey = makeComboKey(ln)
}
// Duplicate guard (SADECE aktif)
if qtyValue(ln.Qty1) > 0 && comboKey != "" {
if seenCombo[comboKey] {
return nil, fmt.Errorf("Duplicate satır: %s", comboKey)
}
seenCombo[comboKey] = true
}
// Kapalı satır
if ln.OrderLineID != "" && existingClosed[ln.OrderLineID] {
continue
}
if comboKey != "" {
if _, ok := existingClosedCombo[comboKey]; ok {
continue
}
}
// DELETE SIGNAL
if ln.OrderLineID != "" && qtyValue(ln.Qty1) <= 0 {
_, err := tx.Exec(`DELETE FROM BAGGI_V3.dbo.trOrderLineCurrency WHERE OrderLineID=@p1`, ln.OrderLineID)
if err != nil {
return nil, err
}
_, 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 {
return nil, err
}
delete(existingOpen, ln.OrderLineID)
delete(existingOpenCombo, comboKey)
continue
}
isNew := false
if ln.OrderLineID == "" {
if dbID, ok := existingOpenCombo[comboKey]; ok {
ln.OrderLineID = dbID
} else {
ln.OrderLineID = uuid.New().String()
isNew = true
}
}
if qtyValue(ln.Qty1) > 0 {
if err := ValidateItemVariant(tx, ln); err != nil {
return nil, err
}
}
if isNew {
_, err := insStmt.Exec(
ln.OrderLineID, ln.SortOrder, ln.ItemTypeCode,
nullableString(ln.ItemCode, ""), safeNS(ln.ColorCode),
safeNS(ln.ItemDim1Code), safeNS(ln.ItemDim2Code),
nullableString(ln.ItemDim3Code, ""),
ln.Qty1, ln.Qty2, ln.CancelQty1, ln.CancelQty2,
nullableString(ln.OrderCancelReasonCode, ""),
nullableTime(ln.DeliveryDate, now),
nullableDateString(ln.PlannedDateOfLading),
nullableString(ln.LineDescription, ""),
nullableString(ln.UsedBarcode, ""),
nullableString(ln.CostCenterCode, ""),
normalizeVatCode(ln.VatCode), nf0(ln.VatRate),
normalizePCTCode(ln.PCTCode), nf0(ln.PCTRate),
nf0(ln.LDisRate1), nf0(ln.LDisRate2),
nf0(ln.LDisRate3), nf0(ln.LDisRate4), nf0(ln.LDisRate5),
nullableString(ln.DocCurrencyCode, "TRY"),
nullableString(ln.PriceCurrencyCode, "TRY"),
nf0(ln.PriceExchangeRate), nf0(ln.Price),
nullableString(ln.BaseProcessCode, ""),
nullableString(ln.BaseOrderNumber, ""),
ln.BaseCustomerTypeCode,
nullableString(ln.BaseCustomerCode, ""),
nullableUUID(ln.BaseSubCurrAccID),
nullableString(ln.BaseStoreCode, ""),
header.OrderHeaderID,
v3User, now, v3User, now,
nf0(ln.SurplusOrderQtyToleranceRate),
nullableString(ln.WithHoldingTaxTypeCode, ""),
nullableString(ln.DOVCode, ""),
)
if err != nil {
return nil, err
}
} else {
_, err := updStmt.Exec(
ln.SortOrder, ln.ItemTypeCode,
nullableString(ln.ItemCode, ""),
safeNS(ln.ColorCode),
safeNS(ln.ItemDim1Code),
safeNS(ln.ItemDim2Code),
nullableString(ln.ItemDim3Code, ""),
nf0(ln.Qty1), nf0(ln.Qty2),
nf0(ln.CancelQty1), nf0(ln.CancelQty2),
nullableString(ln.OrderCancelReasonCode, ""),
nullableTime(ln.DeliveryDate, now),
nullableDateString(ln.PlannedDateOfLading),
nullableString(ln.LineDescription, ""),
nullableString(ln.UsedBarcode, ""),
nullableString(ln.CostCenterCode, ""),
normalizeVatCode(ln.VatCode), nf0(ln.VatRate),
normalizePCTCode(ln.PCTCode), nf0(ln.PCTRate),
nf0(ln.LDisRate1), nf0(ln.LDisRate2),
nf0(ln.LDisRate3), nf0(ln.LDisRate4), nf0(ln.LDisRate5),
nullableString(ln.DocCurrencyCode, "TRY"),
nullableString(ln.PriceCurrencyCode, "TRY"),
nf0(ln.PriceExchangeRate), nf0(ln.Price),
nullableString(ln.BaseProcessCode, ""),
nullableString(ln.BaseOrderNumber, ""),
ln.BaseCustomerTypeCode,
nullableString(ln.BaseCustomerCode, ""),
nullableUUID(ln.BaseSubCurrAccID),
nullableString(ln.BaseStoreCode, ""),
v3User, now,
nf0(ln.SurplusOrderQtyToleranceRate),
nullableString(ln.WithHoldingTaxTypeCode, ""),
nullableString(ln.DOVCode, ""),
ln.OrderLineID,
)
if err != nil {
return nil, err
}
}
delete(existingOpen, ln.OrderLineID)
delete(existingOpenCombo, comboKey)
if ln.ClientKey.Valid {
lineResults = append(lineResults, OrderLineResult{
ClientKey: ln.ClientKey.String,
OrderLineID: ln.OrderLineID,
})
}
}
// Grid dışı kalan açık satırlar
for id := range existingOpen {
_, err := tx.Exec(`DELETE FROM BAGGI_V3.dbo.trOrderLineCurrency WHERE OrderLineID=@p1`, 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 {
return nil, err
}
}
if err := tx.Commit(); err != nil {
return nil, err
}
return lineResults, nil
}