// ===================== 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 struct’lar 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: "prItemVariant’ta 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 DB’ye 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) // ✔ Grid’de 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 }