This commit is contained in:
2026-02-11 17:46:22 +03:00
commit eacfacb13b
266 changed files with 51337 additions and 0 deletions

1079
svc/queries/order_write.go Normal file
View File

@@ -0,0 +1,1079 @@
// ===================== 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
}