From 7f56bb40c5d9b7a648c9a529ed3d5ae2bd543f78 Mon Sep 17 00:00:00 2001 From: MEHMETKECECI Date: Fri, 13 Feb 2026 07:27:57 +0300 Subject: [PATCH] Merge remote-tracking branch 'origin/master' --- svc/auth/claims_mapper.go | 9 +- svc/models/orderdetail.go | 32 +- svc/models/orderlist.go | 3 + svc/queries/get_order_list_excel.go | 216 +++++++- svc/queries/order_get.go | 131 +++-- svc/queries/order_write.go | 512 ++++++++++++++++-- svc/queries/orderlist.go | 65 ++- svc/repository/mk_user_repository.go | 24 +- svc/routes/order_list_excel.go | 164 ++++-- svc/routes/orderlist.go | 11 +- svc/utils/utils.go | 18 +- ...ig.js.temporary.compiled.1770889244552.mjs | 83 --- ui/src/layouts/MainLayout.vue | 7 - ui/src/pages/ActivityLogs.vue | 17 +- ui/src/pages/ChangePassword.vue | 16 +- ui/src/pages/Dashboard.vue | 12 +- ui/src/pages/FirstPasswordChange.vue | 8 - ui/src/pages/MainPage.vue | 7 - ui/src/pages/MePassword.vue | 7 - ui/src/pages/OrderEntry.vue | 9 +- ui/src/pages/OrderList.vue | 258 +++++++-- ui/src/pages/OrderPdf.vue | 123 ++++- ui/src/pages/PermissionMatrix.vue | 17 +- ui/src/pages/ProductionWorker.vue | 26 +- ui/src/pages/ProductionWorkerGateway.vue | 14 +- ui/src/pages/ResetPassword.vue | 8 - ui/src/pages/RoleDepartmentPermissionPage.vue | 19 +- ui/src/pages/StatementHeaderReport.vue | 26 +- ui/src/pages/StatementReport.vue | 35 +- ui/src/pages/TestMail.vue | 42 +- ui/src/pages/UserDetail.vue | 31 +- ui/src/pages/UserGateway.vue | 21 +- ui/src/pages/UserList.vue | 35 +- ui/src/pages/UserPermissionPage.vue | 18 +- ui/src/pages/UserSync.vue | 38 +- ui/src/pages/statementofaccount.vue | 35 +- ui/src/stores/OrdernewListStore.js | 22 +- ui/src/stores/orderentryStore.js | 47 +- 38 files changed, 1709 insertions(+), 457 deletions(-) delete mode 100644 ui/quasar.config.js.temporary.compiled.1770889244552.mjs diff --git a/svc/auth/claims_mapper.go b/svc/auth/claims_mapper.go index fdb1d83..8d939c6 100644 --- a/svc/auth/claims_mapper.go +++ b/svc/auth/claims_mapper.go @@ -15,10 +15,11 @@ func BuildClaimsFromUser(u *models.MkUser, ttl time.Duration) Claims { // 🔴 mk_dfusr.id ID: u.ID, - Username: u.Username, - RoleCode: u.RoleCode, - RoleID: u.RoleID, - // ✅ BURASI + Username: u.Username, + RoleCode: u.RoleCode, + RoleID: u.RoleID, + V3Username: u.V3Username, + V3UserGroup: u.V3UserGroup, DepartmentCodes: u.DepartmentCodes, SessionID: u.SessionID, diff --git a/svc/models/orderdetail.go b/svc/models/orderdetail.go index f8aee81..fd60125 100644 --- a/svc/models/orderdetail.go +++ b/svc/models/orderdetail.go @@ -54,9 +54,10 @@ type OrderDetail struct { LDisRate4 NullFloat64 `json:"LDisRate4"` LDisRate5 NullFloat64 `json:"LDisRate5"` - DocCurrencyCode NullString `json:"DocCurrencyCode"` - PriceCurrencyCode NullString `json:"PriceCurrencyCode"` - PriceExchangeRate NullFloat64 `json:"PriceExchangeRate"` + DocCurrencyCode NullString `json:"DocCurrencyCode"` + RelationCurrencyCode NullString `json:"RelationCurrencyCode"` + PriceCurrencyCode NullString `json:"PriceCurrencyCode"` + PriceExchangeRate NullFloat64 `json:"PriceExchangeRate"` Price NullFloat64 `json:"Price"` @@ -95,4 +96,29 @@ type OrderDetail struct { UrunAltGrubu NullString `json:"UrunAltGrubu"` Fit1 NullString `json:"Fit1"` Fit2 NullString `json:"Fit2"` + // ============================ + // 💰 Currency / Amount Fields + // (trOrderLineCurrency) + // ============================ + + // Döviz bazlı + DocPrice NullFloat64 `json:"DocPrice"` // PriceVI + DocAmount NullFloat64 `json:"DocAmount"` // AmountVI + + // Yerel para (TRY) + LocalPrice NullFloat64 `json:"LocalPrice"` // Price + LocalAmount NullFloat64 `json:"LocalAmount"` // Amount + + // İndirimler + LineDiscount NullFloat64 `json:"LineDiscount"` // LDiscount1 + TotalDiscount NullFloat64 `json:"TotalDiscount"` // TDiscount1 + + // Vergi / Matrah + TaxBase NullFloat64 `json:"TaxBase"` + VatAmount NullFloat64 `json:"VatAmount"` + VatDeducation NullFloat64 `json:"VatDeducation"` + NetAmount NullFloat64 `json:"NetAmount"` + + // Genel oran + Pct NullFloat64 `json:"Pct"` } diff --git a/svc/models/orderlist.go b/svc/models/orderlist.go index a91ceff..b635b5d 100644 --- a/svc/models/orderlist.go +++ b/svc/models/orderlist.go @@ -28,6 +28,9 @@ type OrderList struct { // 💰 Tutarlar TotalAmount float64 `json:"TotalAmount"` TotalAmountUSD float64 `json:"TotalAmountUSD"` + PackedAmount float64 `json:"PackedAmount"` + PackedUSD float64 `json:"PackedUSD"` + PackedRatePct float64 `json:"PackedRatePct"` // 📝 Açıklama Description string `json:"Description"` diff --git a/svc/queries/get_order_list_excel.go b/svc/queries/get_order_list_excel.go index 81c2c3c..b66d4fa 100644 --- a/svc/queries/get_order_list_excel.go +++ b/svc/queries/get_order_list_excel.go @@ -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...) diff --git a/svc/queries/order_get.go b/svc/queries/order_get.go index b7fb55a..4e8c8da 100644 --- a/svc/queries/order_get.go +++ b/svc/queries/order_get.go @@ -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 } diff --git a/svc/queries/order_write.go b/svc/queries/order_write.go index 4e3f41d..1e2b71e 100644 --- a/svc/queries/order_write.go +++ b/svc/queries/order_write.go @@ -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 header’dan 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) -// ✔ 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.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 } diff --git a/svc/queries/orderlist.go b/svc/queries/orderlist.go index 78a7107..9e7cbe7 100644 --- a/svc/queries/orderlist.go +++ b/svc/queries/orderlist.go @@ -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 diff --git a/svc/repository/mk_user_repository.go b/svc/repository/mk_user_repository.go index 7eb0bce..aad9974 100644 --- a/svc/repository/mk_user_repository.go +++ b/svc/repository/mk_user_repository.go @@ -45,6 +45,8 @@ func (r *MkUserRepository) GetByUsername(username string) (*models.MkUser, error FILTER (WHERE d.code IS NOT NULL), '{}' ) AS department_codes, + COALESCE(MAX(n.username), '') AS v3_username, + COALESCE(MAX(n.user_group_code::text), '') AS v3_usergroup, u.password_updated_at, u.created_at, @@ -67,6 +69,13 @@ LEFT JOIN dfusr_dprt ud LEFT JOIN mk_dprt d ON d.id = ud.dprt_id +LEFT JOIN dfusr_nebim_user un + ON un.dfusr_id = u.id + +LEFT JOIN mk_nebim_user n + ON n.id = un.mk_nebim_user_id + AND n.is_active = true + WHERE LOWER(u.username) = LOWER($1) GROUP BY @@ -85,6 +94,8 @@ LIMIT 1 &u.RoleCode, pq.Array(&u.DepartmentCodes), // ✅ + &u.V3Username, + &u.V3UserGroup, &u.PasswordUpdatedAt, @@ -127,6 +138,8 @@ func (r *MkUserRepository) GetByID(id int64) (*models.MkUser, error) { FILTER (WHERE d.code IS NOT NULL), '{}' ) AS department_codes, + COALESCE(MAX(n.username), '') AS v3_username, + COALESCE(MAX(n.user_group_code::text), '') AS v3_usergroup, u.password_updated_at, u.created_at, @@ -149,7 +162,14 @@ LEFT JOIN dfusr_dprt ud LEFT JOIN mk_dprt d ON d.id = ud.dprt_id -WHERE LOWER(u.username) = LOWER($1) +LEFT JOIN dfusr_nebim_user un + ON un.dfusr_id = u.id + +LEFT JOIN mk_nebim_user n + ON n.id = un.mk_nebim_user_id + AND n.is_active = true + +WHERE u.id = $1 GROUP BY u.id, r.id @@ -166,6 +186,8 @@ LIMIT 1 &u.RoleID, &u.RoleCode, pq.Array(&u.DepartmentCodes), // ✅ + &u.V3Username, + &u.V3UserGroup, &u.PasswordUpdatedAt, &u.CreatedAt, &u.UpdatedAt, diff --git a/svc/routes/order_list_excel.go b/svc/routes/order_list_excel.go index 90ff2a8..e27abdf 100644 --- a/svc/routes/order_list_excel.go +++ b/svc/routes/order_list_excel.go @@ -1,7 +1,6 @@ package routes import ( - "bssapp-backend/models" "bssapp-backend/queries" "database/sql" "fmt" @@ -14,25 +13,50 @@ import ( func OrderListExcelRoute(db *sql.DB) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Cache-Control", "no-store") + + // ====================== + // PARAMS + // ====================== search := r.URL.Query().Get("search") currAcc := r.URL.Query().Get("CurrAccCode") orderDate := r.URL.Query().Get("OrderDate") + // ====================== + // QUERY + // ====================== rows, err := queries.GetOrderListExcel(db, search, currAcc, orderDate) if err != nil { - http.Error(w, "Veritabanı hatası", 500) + http.Error(w, err.Error(), 500) return } defer rows.Close() + // ====================== + // EXCEL INIT + // ====================== f := excelize.NewFile() sheet := "Orders" f.SetSheetName("Sheet1", sheet) + // ====================== + // HEADERS + // ====================== headers := []string{ - "Sipariş No", "Tarih", "Cari Kod", "Cari Adı", - "Temsilci", "Piyasa", "Onay Tarihi", "PB", - "Tutar", "Tutar (USD)", "Açıklama", + "Sipariş No", + "Tarih", + "Cari Kod", + "Cari Adı", + "Temsilci", + "Piyasa", + "PB", + "Tutar", + "Tutar (USD)", + "Paketlenen Tutar", + "Paketlenen (USD)", + "Paketlenme (%)", + "USD Kur", + "Açıklama", } for i, h := range headers { @@ -40,52 +64,108 @@ func OrderListExcelRoute(db *sql.DB) http.Handler { f.SetCellValue(sheet, cell, h) } - rowIdx := 2 + // ====================== + // ROWS + // ====================== + row := 2 + for rows.Next() { - var o models.OrderList - _ = rows.Scan( - &o.OrderHeaderID, - &o.OrderNumber, - &o.OrderDate, - &o.CurrAccCode, - &o.CurrAccDescription, - &o.MusteriTemsilcisi, - &o.Piyasa, - &o.CreditableConfirmedDate, - &o.DocCurrencyCode, - &o.TotalAmount, - &o.TotalAmountUSD, - &o.IsCreditableConfirmed, - &o.Description, - &o.ExchangeRateUSD, + + // 🔴 15 KOLON = 15 DEĞİŞKEN + var ( + id, no, date, code, name string + rep, piyasa, cur string + + total float64 + totalUSD float64 + packedAmount float64 + packedUSD float64 + packedRatePct float64 + usdRate float64 + + desc string ) - f.SetSheetRow(sheet, fmt.Sprintf("A%d", rowIdx), &[]interface{}{ - o.OrderNumber, - o.OrderDate, - o.CurrAccCode, - o.CurrAccDescription, - o.MusteriTemsilcisi, - o.Piyasa, - o.CreditableConfirmedDate, - o.DocCurrencyCode, - o.TotalAmount, - o.TotalAmountUSD, - o.Description, + // 🔴 SELECT SIRASIYLA BİREBİR + err := rows.Scan( + &id, // 1 + &no, // 2 + &date, // 3 + &code, // 4 + &name, // 5 + &rep, // 6 + &piyasa, // 7 + &cur, // 8 + &total, // 9 + &totalUSD, // 10 + &packedAmount, // 11 + &packedUSD, // 12 + &packedRatePct, // 13 + &desc, // 14 + &usdRate, // 15 + ) + + if err != nil { + http.Error(w, "Scan error: "+err.Error(), 500) + return + } + + // ====================== + // WRITE ROW + // ====================== + f.SetSheetRow(sheet, fmt.Sprintf("A%d", row), &[]any{ + no, + date, + code, + name, + rep, + piyasa, + cur, + total, + totalUSD, + packedAmount, + packedUSD, + packedRatePct, + usdRate, + desc, }) - rowIdx++ + + row++ + } + + // ====================== + // BUFFER WRITE + // ====================== + buf, err := f.WriteToBuffer() + if err != nil { + http.Error(w, err.Error(), 500) + return } filename := fmt.Sprintf( - "order_list_%s.xlsx", - time.Now().Format("2006-01-02_15-04"), + "siparis_listesi_%s.xlsx", + time.Now().Format("20060102_150405"), ) - w.Header().Set("Content-Type", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") - w.Header().Set("Content-Disposition", - "attachment; filename="+filename) + // ====================== + // RESPONSE + // ====================== + w.Header().Set( + "Content-Type", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ) - _ = f.Write(w) + w.Header().Set( + "Content-Disposition", + "attachment; filename=\""+filename+"\"", + ) + + w.Header().Set( + "Content-Length", + fmt.Sprint(len(buf.Bytes())), + ) + + w.WriteHeader(http.StatusOK) + _, _ = w.Write(buf.Bytes()) }) } diff --git a/svc/routes/orderlist.go b/svc/routes/orderlist.go index 078cd16..09d9f83 100644 --- a/svc/routes/orderlist.go +++ b/svc/routes/orderlist.go @@ -58,7 +58,7 @@ func OrderListRoute(mssql *sql.DB) http.Handler { count := 0 // ================================================== - // 🧠 SCAN — SQL SELECT ile BİRE BİR (14 kolon) + // 🧠 SCAN — SQL SELECT ile BİRE BİR (17 kolon) // ================================================== for rows.Next() { @@ -80,11 +80,14 @@ func OrderListRoute(mssql *sql.DB) http.Handler { &o.TotalAmount, // 10 &o.TotalAmountUSD, // 11 + &o.PackedAmount, // 12 + &o.PackedUSD, // 13 + &o.PackedRatePct, // 14 - &o.IsCreditableConfirmed, // 12 - &o.Description, // 13 + &o.IsCreditableConfirmed, // 15 + &o.Description, // 16 - &o.ExchangeRateUSD, // 14 + &o.ExchangeRateUSD, // 17 ) if err != nil { diff --git a/svc/utils/utils.go b/svc/utils/utils.go index cf9addd..207ce9e 100644 --- a/svc/utils/utils.go +++ b/svc/utils/utils.go @@ -3,6 +3,8 @@ package utils import ( "bssapp-backend/auth" "bssapp-backend/models" + "strconv" + "strings" ) func UserFromClaims(c *auth.Claims) *models.User { @@ -10,8 +12,20 @@ func UserFromClaims(c *auth.Claims) *models.User { return nil } + v3Group := 0 + if raw := strings.TrimSpace(c.V3UserGroup); raw != "" { + if parsed, err := strconv.Atoi(raw); err == nil { + v3Group = parsed + } + } + return &models.User{ - ID: int(c.ID), - Username: c.Username, + ID: int(c.ID), + Username: c.Username, + RoleID: int(c.RoleID), + RoleCode: c.RoleCode, + V3Username: strings.TrimSpace(c.V3Username), + V3UserGroup: v3Group, + ForcePasswordChange: c.ForcePasswordChange, } } diff --git a/ui/quasar.config.js.temporary.compiled.1770889244552.mjs b/ui/quasar.config.js.temporary.compiled.1770889244552.mjs deleted file mode 100644 index b4f411c..0000000 --- a/ui/quasar.config.js.temporary.compiled.1770889244552.mjs +++ /dev/null @@ -1,83 +0,0 @@ -/* eslint-disable */ -/** - * THIS FILE IS GENERATED AUTOMATICALLY. - * 1. DO NOT edit this file directly as it won't do anything. - * 2. EDIT the original quasar.config file INSTEAD. - * 3. DO NOT git commit this file. It should be ignored. - * - * This file is still here because there was an error in - * the original quasar.config file and this allows you to - * investigate the Node.js stack error. - * - * After you fix the original file, this file will be - * deleted automatically. - **/ - - -// quasar.config.js -import { defineConfig } from "@quasar/app-webpack/wrappers"; -var quasar_config_default = defineConfig(() => { - return { - // ✅ UYGULAMA KİMLİĞİ (WEB'DE GÖRÜNEN İSİM) - productName: "Baggi BSS", - productDescription: "Baggi Tekstil Business Support System", - // 🔹 Boot dosyaları - boot: ["axios", "dayjs"], - // 🔹 Global CSS - css: ["app.css"], - // 🔹 Ekstra icon/font setleri - extras: [ - "roboto-font", - "material-icons" - ], - // 🔹 Derleme Ayarları - build: { - vueRouterMode: "hash", - env: { - VITE_API_BASE_URL: "http://localhost:8080/api" - }, - esbuildTarget: { - browser: ["es2022", "firefox115", "chrome115", "safari14"], - node: "node20" - } - }, - // 🔹 Geliştirme Sunucusu - devServer: { - server: { type: "http" }, - port: 9e3, - open: true - }, - // 🔹 Quasar Framework ayarları - framework: { - config: { - notify: { position: "top", timeout: 2500 } - }, - lang: "tr", - plugins: ["Loading", "Dialog", "Notify"] - }, - animations: [], - ssr: { - prodPort: 3e3, - middlewares: ["render"], - pwa: false - }, - pwa: { - workboxMode: "GenerateSW" - }, - capacitor: { - hideSplashscreen: true - }, - electron: { - preloadScripts: ["electron-preload"], - inspectPort: 5858, - bundler: "packager", - builder: { appId: "baggisowtfaresystem" } - }, - bex: { - extraScripts: [] - } - }; -}); -export { - quasar_config_default as default -}; diff --git a/ui/src/layouts/MainLayout.vue b/ui/src/layouts/MainLayout.vue index a58a79d..fbc5d65 100644 --- a/ui/src/layouts/MainLayout.vue +++ b/ui/src/layouts/MainLayout.vue @@ -135,13 +135,6 @@ import { Dialog } from 'quasar' import { useAuthStore } from 'stores/authStore' import { usePermissionStore } from 'stores/permissionStore' -import { usePermission } from 'src/composables/usePermission' - -const { canRead, canWrite, canUpdate } = usePermission() - -const canReadOrder = canRead('order') -const canWriteOrder = canWrite('order') -const canUpdateOrder = canUpdate('order') /* ================= STORES ================= */ diff --git a/ui/src/pages/ActivityLogs.vue b/ui/src/pages/ActivityLogs.vue index 9748805..5569058 100644 --- a/ui/src/pages/ActivityLogs.vue +++ b/ui/src/pages/ActivityLogs.vue @@ -1,5 +1,5 @@ @@ -217,11 +224,9 @@ import { useActivityLogStore } from 'src/stores/activityLogStore' import { useAuthStore } from 'stores/authStore.js' import { usePermission } from 'src/composables/usePermission' -const { canRead, canWrite, canUpdate } = usePermission() - -const canReadOrder = canRead('order') -const canWriteOrder = canWrite('order') -const canUpdateOrder = canUpdate('order') +const { canRead, canUpdate } = usePermission() +const canReadUser = canRead('user') +const canUpdateUser = canUpdate('user') const diffDialog = ref(false) diff --git a/ui/src/pages/ChangePassword.vue b/ui/src/pages/ChangePassword.vue index 4d51503..0265f43 100644 --- a/ui/src/pages/ChangePassword.vue +++ b/ui/src/pages/ChangePassword.vue @@ -1,5 +1,5 @@ @@ -64,11 +70,8 @@ import api from 'src/services/api' import { useAuthStore } from 'stores/authStore.js' import { usePermission } from 'src/composables/usePermission' -const { canRead, canWrite, canUpdate } = usePermission() - -const canReadOrder = canRead('order') -const canWriteOrder = canWrite('order') -const canUpdateOrder = canUpdate('order') +const { canUpdate } = usePermission() +const canUpdateSystem = canUpdate('system') const $q = useQuasar() const auth = useAuthStore() @@ -117,4 +120,3 @@ async function submit () { } } - diff --git a/ui/src/pages/Dashboard.vue b/ui/src/pages/Dashboard.vue index 2bffd6f..fd52804 100644 --- a/ui/src/pages/Dashboard.vue +++ b/ui/src/pages/Dashboard.vue @@ -1,9 +1,17 @@ diff --git a/ui/src/pages/FirstPasswordChange.vue b/ui/src/pages/FirstPasswordChange.vue index ad0a69d..ed2dc82 100644 --- a/ui/src/pages/FirstPasswordChange.vue +++ b/ui/src/pages/FirstPasswordChange.vue @@ -59,13 +59,6 @@ import { ref } from 'vue' import { useRouter } from 'vue-router' import api from 'src/services/api' import { useAuthStore } from 'stores/authStore.js' -import { usePermission } from 'src/composables/usePermission' - -const { canRead, canWrite, canUpdate } = usePermission() - -const canReadOrder = canRead('order') -const canWriteOrder = canWrite('order') -const canUpdateOrder = canUpdate('order') const router = useRouter() const auth = useAuthStore() @@ -117,4 +110,3 @@ async function submit () { - diff --git a/ui/src/pages/MainPage.vue b/ui/src/pages/MainPage.vue index 1d2b0ab..83c1ca7 100644 --- a/ui/src/pages/MainPage.vue +++ b/ui/src/pages/MainPage.vue @@ -123,13 +123,6 @@ import { useRouter } from 'vue-router' import { useAuthStore } from 'stores/authStore' import { useQuasar } from 'quasar' import api from 'src/services/api' -import { usePermission } from 'src/composables/usePermission' - -const { canRead, canWrite, canUpdate } = usePermission() - -const canReadOrder = canRead('order') -const canWriteOrder = canWrite('order') -const canUpdateOrder = canUpdate('order') const router = useRouter() const auth = useAuthStore() diff --git a/ui/src/pages/MePassword.vue b/ui/src/pages/MePassword.vue index 637cbc5..3030e89 100644 --- a/ui/src/pages/MePassword.vue +++ b/ui/src/pages/MePassword.vue @@ -60,13 +60,6 @@ import { ref, computed } from 'vue' import { useQuasar } from 'quasar' import { useMePasswordStore } from 'stores/mePasswordStore' -import { usePermission } from 'src/composables/usePermission' - -const { canRead, canWrite, canUpdate } = usePermission() - -const canReadOrder = canRead('order') -const canWriteOrder = canWrite('order') -const canUpdateOrder = canUpdate('order') const $q = useQuasar() const store = useMePasswordStore() diff --git a/ui/src/pages/OrderEntry.vue b/ui/src/pages/OrderEntry.vue index ae55200..6ea63a8 100644 --- a/ui/src/pages/OrderEntry.vue +++ b/ui/src/pages/OrderEntry.vue @@ -213,7 +213,7 @@
Sipariş Formu
- - + +
- +
- + - + - + - +
- +
- Toplam Görünen Sipariş Tutarı (USD): - - {{ store.totalVisibleUSD.toLocaleString('tr-TR', { minimumFractionDigits: 2 }) }} - USD - + + Toplam USD: + + {{ store.totalVisibleUSD.toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }} + + + + Paketlenen USD: + + {{ store.totalPackedVisibleUSD.toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }} + + + + Paketlenme %: + + {{ store.packedVisibleRatePct.toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) }} + +
@@ -91,7 +107,7 @@
- + - + - + - + - + - + @@ -193,10 +212,16 @@ + + +
+ Bu module erisim yetkiniz yok. +
+
+ + diff --git a/ui/src/pages/OrderPdf.vue b/ui/src/pages/OrderPdf.vue index cc340bc..42c8ce4 100644 --- a/ui/src/pages/OrderPdf.vue +++ b/ui/src/pages/OrderPdf.vue @@ -1 +1,122 @@ - + + + diff --git a/ui/src/pages/PermissionMatrix.vue b/ui/src/pages/PermissionMatrix.vue index e4cd983..68bce72 100644 --- a/ui/src/pages/PermissionMatrix.vue +++ b/ui/src/pages/PermissionMatrix.vue @@ -1,5 +1,5 @@ @@ -96,11 +103,9 @@ import { Notify } from 'quasar' import api from 'src/services/api' import { usePermission } from 'src/composables/usePermission' -const { canRead, canWrite, canUpdate } = usePermission() - -const canReadOrder = canRead('order') -const canWriteOrder = canWrite('order') -const canUpdateOrder = canUpdate('order') +const { canRead, canUpdate } = usePermission() +const canReadSystem = canRead('system') +const canUpdateUser = canUpdate('user') /* ================= STATE ================= */ diff --git a/ui/src/pages/ProductionWorker.vue b/ui/src/pages/ProductionWorker.vue index a4c56f2..9ecf76f 100644 --- a/ui/src/pages/ProductionWorker.vue +++ b/ui/src/pages/ProductionWorker.vue @@ -1,5 +1,5 @@ diff --git a/ui/src/pages/UserList.vue b/ui/src/pages/UserList.vue index f0c76b0..4b24e6a 100644 --- a/ui/src/pages/UserList.vue +++ b/ui/src/pages/UserList.vue @@ -1,5 +1,8 @@ diff --git a/ui/src/pages/UserPermissionPage.vue b/ui/src/pages/UserPermissionPage.vue index bde7e1a..28356ae 100644 --- a/ui/src/pages/UserPermissionPage.vue +++ b/ui/src/pages/UserPermissionPage.vue @@ -1,10 +1,10 @@ diff --git a/ui/src/pages/statementofaccount.vue b/ui/src/pages/statementofaccount.vue index 9231b0f..33146cf 100644 --- a/ui/src/pages/statementofaccount.vue +++ b/ui/src/pages/statementofaccount.vue @@ -1,5 +1,5 @@