Merge remote-tracking branch 'origin/master'

This commit is contained in:
2026-02-13 07:27:57 +03:00
parent d571fe2fd5
commit b2ce30fe79
37 changed files with 1667 additions and 447 deletions

View File

@@ -18,7 +18,8 @@ func BuildClaimsFromUser(u *models.MkUser, ttl time.Duration) Claims {
Username: u.Username,
RoleCode: u.RoleCode,
RoleID: u.RoleID,
// ✅ BURASI
V3Username: u.V3Username,
V3UserGroup: u.V3UserGroup,
DepartmentCodes: u.DepartmentCodes,
SessionID: u.SessionID,

View File

@@ -55,6 +55,7 @@ type OrderDetail struct {
LDisRate5 NullFloat64 `json:"LDisRate5"`
DocCurrencyCode NullString `json:"DocCurrencyCode"`
RelationCurrencyCode NullString `json:"RelationCurrencyCode"`
PriceCurrencyCode NullString `json:"PriceCurrencyCode"`
PriceExchangeRate NullFloat64 `json:"PriceExchangeRate"`
@@ -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"`
}

View File

@@ -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"`

View File

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

View File

@@ -8,15 +8,12 @@ import (
"fmt"
)
// GetOrderByID — Sipariş başlığı (header) ve satırlarını (lines) getirir.
// GetOrderByID returns order header and lines.
func GetOrderByID(orderID string) (*models.OrderHeader, []models.OrderDetail, error) {
conn := db.GetDB()
logger.Printf("🧾 [GetOrderByID] begin id=%s", orderID)
logger.Printf("[GetOrderByID] begin id=%s", orderID)
// =====================================================
// HEADER (Cari adı join'li)
// =====================================================
var header models.OrderHeader
qHeader := `
SELECT
@@ -176,24 +173,17 @@ 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
SELECT
CAST(L.OrderLineID AS varchar(36)) AS OrderLineID,
L.SortOrder,
L.ItemTypeCode,
@@ -204,10 +194,25 @@ func GetOrderByID(orderID string) (*models.OrderHeader, []models.OrderDetail, er
L.ItemDim3Code,
L.Qty1,
L.Qty2,
L.Price,
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.DocCurrencyCode,
L.DeliveryDate,
L.PlannedDateOfLading,
L.LineDescription,
@@ -216,21 +221,28 @@ func GetOrderByID(orderID string) (*models.OrderHeader, []models.OrderDetail, er
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
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;
`
WHERE L.OrderHeaderID = @p1
ORDER BY L.SortOrder ASC;
`
rows, err := conn.Query(qLines, orderID)
if err != nil {
logger.Printf("[GetOrderByID] line sorgu hatası: %v", err)
logger.Printf("[GetOrderByID] lines error: %v", err)
return &header, nil, err
}
defer rows.Close()
@@ -250,9 +262,22 @@ func GetOrderByID(orderID string) (*models.OrderHeader, []models.OrderDetail, er
&ln.Qty1,
&ln.Qty2,
&ln.Price,
&ln.DocCurrencyCode,
&ln.RelationCurrencyCode,
&ln.PriceExchangeRate,
&ln.DocPrice,
&ln.DocAmount,
&ln.LineDiscount,
&ln.TotalDiscount,
&ln.TaxBase,
&ln.Pct,
&ln.VatAmount,
&ln.VatDeducation,
&ln.NetAmount,
&ln.LocalPrice,
&ln.LocalAmount,
&ln.VatRate,
&ln.PCTRate,
&ln.DocCurrencyCode,
&ln.DeliveryDate,
&ln.PlannedDateOfLading,
&ln.LineDescription,
@@ -267,14 +292,14 @@ func GetOrderByID(orderID string) (*models.OrderHeader, []models.OrderDetail, er
&ln.Fit1,
&ln.Fit2,
); err != nil {
return &header, nil, fmt.Errorf("line scan hatası: %w", err)
return &header, nil, fmt.Errorf("line scan error: %w", err)
}
lines = append(lines, ln)
}
if err := rows.Err(); err != nil {
return &header, nil, fmt.Errorf("line rows hatası: %w", err)
return &header, nil, fmt.Errorf("line rows error: %w", err)
}
logger.Printf("📦 [GetOrderByID] lines loaded count=%d", len(lines))
logger.Printf("[GetOrderByID] lines loaded count=%d", len(lines))
return &header, lines, nil
}

View File

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

View File

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

View File

@@ -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,

View File

@@ -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())
})
}

View File

@@ -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 {

View File

@@ -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,
RoleID: int(c.RoleID),
RoleCode: c.RoleCode,
V3Username: strings.TrimSpace(c.V3Username),
V3UserGroup: v3Group,
ForcePasswordChange: c.ForcePasswordChange,
}
}

View File

@@ -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
};

View File

@@ -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 ================= */

View File

@@ -1,5 +1,5 @@
<template>
<q-page class="act-page with-bg">
<q-page v-if="canReadUser" class="act-page with-bg">
<!-- =======================================================
🔍 FILTER BAR
@@ -83,6 +83,7 @@
<!-- YENİ -->
<q-btn
v-if="canUpdateUser"
outline
color="secondary"
label="Rol Değişimleri"
@@ -206,6 +207,12 @@
</q-table>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
@@ -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)

View File

@@ -1,5 +1,5 @@
<template>
<q-page class="flex flex-center">
<q-page v-if="canUpdateSystem" class="flex flex-center">
<q-card style="width:420px; max-width:90vw">
<q-card-section>
@@ -45,6 +45,7 @@ class="bg-red-1 text-red q-mt-md"
<q-card-actions align="right">
<q-btn
v-if="canUpdateSystem"
label="GÜNCELLE"
color="primary"
:loading="loading"
@@ -54,6 +55,11 @@ color="primary"
</q-card-actions>
</q-card>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
@@ -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 () {
}
}
</script>

View File

@@ -1,9 +1,17 @@
<template>
<q-page class="flex flex-center">
<q-page v-if="canReadSystem" class="flex flex-center">
<p>DashBoard</p>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
// buraya JS kodların gelecek
import { usePermission } from 'src/composables/usePermission'
const { canRead } = usePermission()
const canReadSystem = canRead('system')
</script>

View File

@@ -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 () {

View File

@@ -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()

View File

@@ -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()

View File

@@ -213,7 +213,7 @@
<div class="text-subtitle2 text-weight-bold">Sipariş Formu</div>
<div>
<q-btn
v-if="isViewOnly && canReadOrder"
v-if="isViewOnly && canExportOrder"
label="🖨 SİPARİŞİ YAZDIR"
color="primary"
icon="print"
@@ -766,11 +766,12 @@ import { useAuthStore } from 'src/stores/authStore'
import { formatDateInput, formatDateDisplay } from 'src/utils/formatters'
import { usePermission } from 'src/composables/usePermission'
const { canRead, canWrite, canUpdate } = usePermission()
const { canRead, canWrite, canUpdate, canExport } = usePermission()
const canReadOrder = canRead('order')
const canWriteOrder = canWrite('order')
const canUpdateOrder = canUpdate('order')
const canExportOrder = canExport('order')
// script setup içinde
const formatDate = formatDateDisplay
@@ -889,8 +890,8 @@ function hasRowMutationPermission() {
}
function onPrintOrder() {
if (!canReadOrder.value) {
notifyNoPermission('Siparisi goruntuleme yetkiniz yok')
if (!canExportOrder.value) {
notifyNoPermission('Siparisi yazdirma yetkiniz yok')
return
}
orderStore.downloadOrderPdf()

View File

@@ -1,12 +1,12 @@
<template>
<q-page class="ol-page">
<!-- 🔍 Sticky Filter -->
<q-page v-if="canReadOrder" class="ol-page">
<!-- 🍠Sticky Filter -->
<div class="ol-filter-bar">
<!-- 🔹 TEK SATIR FLEX -->
<!-- 🹠TEK SATIR FLEX -->
<div class="ol-filter-row">
<!-- 🔍 Arama -->
<!-- 🍠Arama -->
<q-input
class="ol-filter-input ol-search"
dense
@@ -21,7 +21,7 @@
</template>
</q-input>
<!-- 🧾 Cari Kodu -->
<!-- 🧾 Cari Kodu -->
<q-input
class="ol-filter-input"
dense
@@ -31,7 +31,7 @@
clearable
/>
<!-- 📅 Sipariş Tarihi -->
<!-- ğŸ Sipariş Tarihi -->
<q-input
class="ol-filter-input"
dense
@@ -41,10 +41,11 @@
type="date"
/>
<!-- 🔘 Butonlar -->
<!-- 😠Butonlar -->
<div class="ol-filter-actions">
<q-btn
v-if="canReadOrder"
label="Temizle"
icon="clear"
color="grey-7"
@@ -58,6 +59,7 @@
</q-btn>
<q-btn
v-if="canReadOrder"
label="Yenile"
color="primary"
icon="refresh"
@@ -66,6 +68,7 @@
/>
<q-btn
v-if="canExportOrder"
label="Excel'e Aktar"
icon="download"
color="primary"
@@ -77,7 +80,7 @@
</div>
<!-- 💰 Toplam -->
<!-- 🰠Toplam -->
<div class="ol-filter-total">
Toplam Görünen Sipariş Tutarı (USD):
<strong>
@@ -91,7 +94,7 @@
</div>
<!-- 📋 ORDER LIST TABLE -->
<!-- ğŸ ORDER LIST TABLE -->
<q-table
title="Mevcut Siparişler"
class="ol-table"
@@ -103,16 +106,18 @@
:rows="store.filteredOrders"
:columns="columns"
:loading="store.loading"
:table-style="tableStyle"
no-data-label="Sipariş bulunamadı"
:rows-per-page-options="[0]"
hide-bottom
>
<!-- 📄 PDF + DURUM -->
<!-- ğŸ PDF + DURUM -->
<template #body-cell-IsCreditableConfirmed="props">
<q-td :props="props" class="text-center q-gutter-sm">
<q-btn
v-if="canExportOrder"
icon="picture_as_pdf"
color="red"
flat
@@ -136,7 +141,7 @@
</q-td>
</template>
<!-- 📅 Tarih -->
<!-- ğŸ Tarih -->
<template #body-cell-OrderDate="props">
<q-td :props="props" class="text-center">
{{ formatDate(props.row.OrderDate) }}
@@ -149,7 +154,7 @@
</q-td>
</template>
<!-- 🧾 Cari Adı 2 Satır -->
<!-- 🧾 Cari Adı â 2 Satır -->
<template #body-cell-CurrAccDescription="props">
<q-td :props="props" class="ol-col-cari ol-col-multiline">
{{ props.value }}
@@ -159,7 +164,7 @@
</q-td>
</template>
<!-- 📝 ıklama 5 Satır -->
<!-- 🝠ıklama â 5 Satır -->
<template #body-cell-Description="props">
<q-td :props="props" class="ol-col-desc ol-col-multiline">
{{ props.value }}
@@ -169,10 +174,11 @@
</q-td>
</template>
<!-- 🔗 Aç -->
<!-- ğŸ aç -->
<template #body-cell-select="props">
<q-td :props="props" class="text-center">
<q-btn
v-if="canUpdateOrder"
icon="open_in_new"
color="primary"
flat
@@ -180,7 +186,7 @@
dense
@click="selectOrder(props.row)"
>
<q-tooltip>Siparişi Aç</q-tooltip>
<q-tooltip>Siparişi aç</q-tooltip>
</q-btn>
</q-td>
</template>
@@ -193,10 +199,16 @@
</q-banner>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
import { onMounted, watch } from 'vue'
import { computed, onMounted, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useQuasar } from 'quasar'
@@ -204,11 +216,12 @@ import { useOrderListStore } from 'src/stores/OrdernewListStore'
import { useAuthStore } from 'src/stores/authStore'
import { usePermission } from 'src/composables/usePermission'
const { canRead, canWrite, canUpdate } = usePermission()
const { canRead, canWrite, canUpdate, canExport } = usePermission()
const canReadOrder = canRead('order')
const canWriteOrder = canWrite('order')
const canUpdateOrder = canUpdate('order')
const canExportOrder = canExport('order')
/* =========================
INIT
@@ -217,9 +230,13 @@ const canUpdateOrder = canUpdate('order')
const router = useRouter()
const $q = useQuasar()
// ⚠️ ÖNCE store tanımlanır
// ⚠️ ÖNCE store tanımlanır
const store = useOrderListStore()
const tableStyle = computed(() => ({
minWidth: $q.screen.lt.lg ? '1280px' : '100%'
}))
/* =========================
SEARCH DEBOUNCE
========================= */
@@ -241,14 +258,15 @@ watch(
HELPERS
========================= */
function exportExcel () {
if (!canExportOrder.value) {
$q.notify({ type: 'negative', message: 'Excel export yetkiniz yok', position: 'top-right' })
return
}
const auth = useAuthStore()
if (!auth?.token) {
$q.notify({
type: 'negative',
message: 'Oturum bulunamadı',
position: 'top-right'
})
$q.notify({ type: 'negative', message: 'Oturum bulunamadı', position: 'top-right' })
return
}
@@ -261,19 +279,43 @@ function exportExcel () {
const url = `http://localhost:8080/api/orders/export?${params.toString()}`
fetch(url, {
headers: {
Authorization: `Bearer ${auth.token}`
}
method: 'GET',
headers: { Authorization: `Bearer ${auth.token}` }
})
.then(async res => {
// ✅ 200 değilse EXCEL İNDİRME
if (!res.ok) {
const text = await res.text()
throw new Error(`HTTP ${res.status} - ${text}`)
}
// ✅ Content-Type kontrol (debug için)
const ct = res.headers.get('content-type') || ''
if (!ct.includes('spreadsheetml.sheet')) {
const text = await res.text()
throw new Error(`Beklenmeyen Content-Type: ${ct} | Body: ${text}`)
}
return res.blob()
})
.then(res => res.blob())
.then(blob => {
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
link.download = 'siparis_listesi.xlsx'
link.click()
})
.catch(err => {
$q.notify({
type: 'negative',
message: 'Excel export hatası: ' + (err?.message || err),
position: 'top-right',
timeout: 8000
})
console.error(err)
})
}
function formatDate (s) {
if (!s) return ''
const [y, m, d] = s.split('-')
@@ -285,12 +327,18 @@ function formatDate (s) {
========================= */
const columns = [
{ name: 'select', label: '', field: 'select', align: 'center', sortable: false },
{ name: 'OrderNumber', label: 'Sipariş No', field: 'OrderNumber', align: 'left', sortable: true },
{ name: 'OrderDate', label: 'Tarih', field: 'OrderDate', align: 'center', sortable: true },
{ name: 'CurrAccCode', label: 'Cari Kod', field: 'CurrAccCode', align: 'left', sortable: true },
{
name: 'select',
label: '',
field: 'select',
align: 'center',
sortable: false,
style: 'width:42px; min-width:42px; max-width:42px; padding:2px 4px;',
headerStyle: 'width:42px; min-width:42px; max-width:42px; padding:2px 4px;'
},
{ name: 'OrderNumber', label: 'Sipariş No', field: 'OrderNumber', align: 'left', sortable: true, style: 'width:108px; min-width:108px;', headerStyle: 'width:108px; min-width:108px;' },
{ name: 'OrderDate', label: 'Tarih', field: 'OrderDate', align: 'center', sortable: true, style: 'width:78px; min-width:78px;', headerStyle: 'width:78px; min-width:78px;' },
{ name: 'CurrAccCode', label: 'Cari Kod', field: 'CurrAccCode', align: 'left', sortable: true, style: 'width:78px; min-width:78px;', headerStyle: 'width:78px; min-width:78px;' },
{
name: 'CurrAccDescription',
@@ -300,14 +348,13 @@ const columns = [
sortable: true,
classes: 'ol-col-cari',
headerClasses: 'ol-col-cari',
style: 'max-width:200px'
style: 'width:130px; min-width:130px; max-width:130px;',
headerStyle: 'width:130px; min-width:130px; max-width:130px;'
},
{ name: 'MusteriTemsilcisi', label: 'Temsilci', field: 'MusteriTemsilcisi', align: 'left', sortable: true },
{ name: 'Piyasa', label: 'Piyasa', field: 'Piyasa', align: 'left', sortable: true },
{ name: 'CreditableConfirmedDate', label: 'Onay', field: 'CreditableConfirmedDate', align: 'center', sortable: true },
{ name: 'DocCurrencyCode', label: 'PB', field: 'DocCurrencyCode', align: 'center', sortable: true },
{ name: 'MusteriTemsilcisi', label: 'Temsilci', field: 'MusteriTemsilcisi', align: 'left', sortable: true, style: 'width:78px; min-width:78px;', headerStyle: 'width:78px; min-width:78px;' },
{ name: 'Piyasa', label: 'Piyasa', field: 'Piyasa', align: 'left', sortable: true, style: 'width:74px; min-width:74px;', headerStyle: 'width:74px; min-width:74px;' },
{ name: 'CreditableConfirmedDate', label: 'Onay', field: 'CreditableConfirmedDate', align: 'center', sortable: true, style: 'width:82px; min-width:82px;', headerStyle: 'width:82px; min-width:82px;' },
{ name: 'DocCurrencyCode', label: 'PB', field: 'DocCurrencyCode', align: 'center', sortable: true, style: 'width:46px; min-width:46px;', headerStyle: 'width:46px; min-width:46px;' },
{
name: 'TotalAmount',
@@ -315,6 +362,8 @@ const columns = [
field: 'TotalAmount',
align: 'right',
sortable: true,
style: 'width:94px; min-width:94px;',
headerStyle: 'width:94px; min-width:94px;',
format: (val, row) =>
Number(val || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) +
' ' + row.DocCurrencyCode
@@ -326,11 +375,49 @@ const columns = [
field: 'TotalAmountUSD',
align: 'right',
sortable: true,
style: 'width:96px; min-width:96px;',
headerStyle: 'width:96px; min-width:96px;',
format: val =>
Number(val || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) + ' USD'
},
{ name: 'IsCreditableConfirmed', label: 'Durum', field: 'IsCreditableConfirmed', align: 'center', sortable: true },
{
name: 'PackedAmount',
label: 'Paket Tutar',
field: 'PackedAmount',
align: 'right',
sortable: true,
style: 'width:98px; min-width:98px;',
headerStyle: 'width:98px; min-width:98px;',
format: (val, row) =>
Number(val || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) +
' ' + (row.DocCurrencyCode || '')
},
{
name: 'PackedUSD',
label: 'Paket USD',
field: 'PackedUSD',
align: 'right',
sortable: true,
style: 'width:96px; min-width:96px;',
headerStyle: 'width:96px; min-width:96px;',
format: val =>
Number(val || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) + ' USD'
},
{
name: 'PackedRatePct',
label: 'Paket %',
field: 'PackedRatePct',
align: 'right',
sortable: true,
style: 'width:72px; min-width:72px;',
headerStyle: 'width:72px; min-width:72px;',
format: val =>
Number(val || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) + ' %'
},
{ name: 'IsCreditableConfirmed', label: 'Durum', field: 'IsCreditableConfirmed', align: 'center', sortable: true, style: 'width:66px; min-width:66px;', headerStyle: 'width:66px; min-width:66px;' },
{
name: 'Description',
@@ -340,10 +427,10 @@ const columns = [
sortable: false,
classes: 'ol-col-desc',
headerClasses: 'ol-col-desc',
style: 'max-width:220px'
style: 'width:130px; min-width:130px; max-width:130px;',
headerStyle: 'width:130px; min-width:130px; max-width:130px;'
},
{ name: 'pdf', label: 'PDF', field: 'pdf', align: 'center', sortable: false }
{ name: 'pdf', label: 'PDF', field: 'pdf', align: 'center', sortable: false, style: 'width:44px; min-width:44px;', headerStyle: 'width:44px; min-width:44px;' }
]
/* =========================
@@ -351,6 +438,11 @@ const columns = [
========================= */
function selectOrder (row) {
if (!canUpdateOrder.value) {
$q.notify({ type: 'negative', message: 'Siparis guncelleme yetkiniz yok' })
return
}
if (!row?.OrderHeaderID) {
$q.notify({ type: 'warning', message: 'OrderHeaderID bulunamadı' })
return
@@ -364,6 +456,11 @@ function selectOrder (row) {
}
async function printPDF (row) {
if (!canExportOrder.value) {
$q.notify({ type: 'negative', message: 'PDF export yetkiniz yok' })
return
}
if (!row?.OrderHeaderID) return
const token = useAuthStore().token
@@ -402,7 +499,48 @@ function clearFilters () {
========================= */
onMounted(() => {
if (canReadOrder.value) {
store.fetchOrders()
}
})
</script>
<style scoped>
.ol-page {
padding: 8px;
}
.ol-table :deep(.q-table thead th) {
font-size: 11px;
line-height: 1.1;
font-weight: 700;
padding: 3px 6px;
white-space: nowrap;
}
.ol-table :deep(.q-table tbody td) {
font-size: 11px;
line-height: 1.15;
padding: 2px 6px;
white-space: nowrap;
}
.ol-col-multiline {
white-space: normal !important;
word-break: break-word;
line-height: 1.1;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
@media (max-width: 1600px) {
.ol-table :deep(.q-table thead th),
.ol-table :deep(.q-table tbody td) {
font-size: 10px;
padding: 2px 4px;
}
}
</style>

View File

@@ -1 +1,122 @@
<template></template>
<template>
<q-page
v-if="canExportOrder"
class="q-pa-md"
>
<q-card flat bordered class="q-pa-md" style="max-width: 720px">
<q-card-section>
<div class="text-h6">Order PDF</div>
<div class="text-caption text-grey-7">Order ID: {{ orderId || '-' }}</div>
</q-card-section>
<q-card-actions align="right" class="q-gutter-sm">
<q-btn
v-if="canOpenPdf"
color="primary"
icon="picture_as_pdf"
label="PDF Ac"
:loading="loading"
:disable="loading || !orderId"
@click="openPdf"
/>
<q-btn
v-if="canEditOrder"
flat
color="secondary"
label="Siparise Don"
:disable="!orderId"
@click="goToOrder"
/>
</q-card-actions>
</q-card>
</q-page>
<q-page
v-else
class="q-pa-md flex flex-center"
>
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
import { computed, ref } from 'vue'
import { useQuasar } from 'quasar'
import { useRoute, useRouter } from 'vue-router'
import { usePermission } from 'src/composables/usePermission'
import { useOrderEntryStore } from 'src/stores/orderentryStore'
const { canWrite, canUpdate, canExport } = usePermission()
const canExportOrder = canExport('order')
const canWriteOrder = canWrite('order')
const canUpdateOrder = canUpdate('order')
const canEditOrder = computed(() => canWriteOrder.value || canUpdateOrder.value)
const canOpenPdf = computed(() => canExportOrder.value)
const $q = useQuasar()
const route = useRoute()
const router = useRouter()
const orderStore = useOrderEntryStore()
const loading = ref(false)
const orderId = computed(() => String(route.params.id || '').trim())
async function openPdf () {
if (!canOpenPdf.value) {
$q.notify({
type: 'negative',
message: 'Siparis PDF export yetkiniz yok'
})
return
}
if (!orderId.value) {
$q.notify({
type: 'warning',
message: 'Order ID bulunamadi'
})
return
}
loading.value = true
try {
await orderStore.downloadOrderPdf(orderId.value)
} catch (err) {
$q.notify({
type: 'negative',
message: err?.message || 'PDF acilamadi'
})
} finally {
loading.value = false
}
}
function goToOrder () {
if (!canEditOrder.value) {
$q.notify({
type: 'negative',
message: 'Siparis duzenleme yetkiniz yok'
})
return
}
if (!orderId.value) {
$q.notify({
type: 'warning',
message: 'Order ID bulunamadi'
})
return
}
const routeName = canUpdateOrder.value ? 'order-edit' : 'order-entry'
router.push({
name: routeName,
params: { orderHeaderID: orderId.value }
})
}
</script>

View File

@@ -1,5 +1,5 @@
<template>
<q-page padding>
<q-page v-if="canReadSystem" padding>
<div class="text-h6 q-mb-md">
Rol + Departman Yetkilendirme
@@ -76,6 +76,7 @@
<div class="q-mt-md">
<q-btn
v-if="canUpdateUser"
color="primary"
icon="save"
label="Kaydet"
@@ -86,6 +87,12 @@
</div>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
@@ -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 ================= */

View File

@@ -1,5 +1,5 @@
<template>
<q-page class="workorder-page with-bg">
<q-page v-if="canReadOrder" class="workorder-page with-bg">
<!-- ===============================
🔹 ÜST: İŞ EMRİ BİLGİLERİ
@@ -75,7 +75,7 @@
</div>
<q-btn
v-if="isCreateMode"
v-if="isCreateMode && canWriteOrder"
label="Ana Görsel Seç"
icon="image"
class="q-mt-sm"
@@ -113,7 +113,7 @@
</q-card>
</div>
<div v-if="isCreateMode" class="col-12">
<div v-if="isCreateMode && canWriteOrder" class="col-12">
<q-btn
label="Detay Görsel Ekle"
icon="add"
@@ -143,7 +143,7 @@
</div>
<q-btn
v-if="isCreateMode"
v-if="isCreateMode && canWriteOrder"
label="Talimat Görseli Ekle"
icon="add"
flat
@@ -161,7 +161,7 @@
=============================== -->
<div class="row justify-end q-mt-md">
<q-btn
v-if="isCreateMode"
v-if="isCreateMode && canWriteOrder"
label="Kaydet"
color="positive"
icon="save"
@@ -169,7 +169,7 @@
/>
<q-btn
v-if="isViewMode"
v-if="isViewMode && canExportOrder"
label="PDF"
color="primary"
icon="picture_as_pdf"
@@ -178,17 +178,23 @@
</div>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
import { computed, ref } from 'vue'
import { useRoute } from 'vue-router'
import { usePermission } from 'src/composables/usePermission'
const { canRead, canWrite, canUpdate } = usePermission()
const { canRead, canWrite, canExport } = usePermission()
const canReadOrder = canRead('order')
const canWriteOrder = canWrite('order')
const canUpdateOrder = canUpdate('order')
const canExportOrder = canExport('order')
const route = useRoute()
@@ -209,11 +215,15 @@ const detailImages = ref([])
const instructionImages = ref([])
function openImagePicker(type) {
if (!canWriteOrder.value) return
// Şimdilik stub
console.log('Image picker:', type)
}
function saveWorkOrder() {
if (!canWriteOrder.value) return
console.log('SAVE', {
form: form.value,
mainImage: mainImage.value,

View File

@@ -1,5 +1,5 @@
<template>
<q-page class="q-pa-md">
<q-page v-if="canReadOrder" class="q-pa-md">
<!-- =====================================================
🔹 BAŞLIK + AKSİYONLAR
@@ -10,6 +10,7 @@
</div>
<q-btn
v-if="canWriteOrder"
color="primary"
icon="add"
label="Yeni İş Emri"
@@ -83,6 +84,12 @@
</q-table>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
@@ -90,11 +97,10 @@ import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import { usePermission } from 'src/composables/usePermission'
const { canRead, canWrite, canUpdate } = usePermission()
const { canRead, canWrite } = usePermission()
const canReadOrder = canRead('order')
const canWriteOrder = canWrite('order')
const canUpdateOrder = canUpdate('order')
const router = useRouter()
@@ -192,10 +198,12 @@ const filteredRows = computed(() => {
// 🔹 AKSİYONLAR
// =====================================================
function goNew () {
if (!canWriteOrder.value) return
router.push('/app/production-work-orders/new')
}
function goView (evt, row) {
if (!canReadOrder.value) return
router.push(`/app/production-work-orders/view/${row.id}`)
}

View File

@@ -125,13 +125,6 @@ import { useQuasar } from 'quasar'
import api, { post } 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')
/* -------------------------------------------------- */
/* INIT */
@@ -261,4 +254,3 @@ async function submit () {
}
}
</script>

View File

@@ -1,5 +1,5 @@
<template>
<div v-if="!lookupsLoaded" class="q-pa-xl flex flex-center">
<div v-if="canUpdateUser && !lookupsLoaded" class="q-pa-xl flex flex-center">
<q-spinner
color="primary"
@@ -8,7 +8,7 @@
</div>
<q-page class="permissions-page">
<q-page v-if="canUpdateUser" class="permissions-page">
<!-- ================= STICKY STACK ================= -->
<div class="sticky-stack">
@@ -69,6 +69,7 @@
</div>
<q-btn
v-if="canUpdateUser"
color="primary"
icon="save"
label="Kaydet"
@@ -165,6 +166,12 @@
</div>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
@@ -175,11 +182,8 @@ 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 { canUpdate } = usePermission()
const canUpdateUser = canUpdate('user')
/* ================= STATE ================= */
@@ -432,4 +436,3 @@ watch(deptCode, v => console.log('DEPT >>>', v))

View File

@@ -1,10 +1,11 @@
<!-- src/pages/StatementHeaderReport.vue -->
<template>
<q-page class="q-pa-md page-col">
<q-page v-if="canReadFinance" class="q-pa-md page-col">
<!-- Başlık ve PDF butonu -->
<div class="row justify-between items-center q-mb-md">
<div class="text-h6">📄 Cari Hesap Raporu</div>
<q-btn
v-if="canExportFinance"
color="red"
icon="picture_as_pdf"
label="PDF Yazdır"
@@ -44,6 +45,12 @@
</div>
</q-card>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
@@ -53,11 +60,9 @@ import { useDownloadstHeadStore } from 'src/stores/downloadstHeadStore'
import dayjs from 'dayjs'
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, canExport } = usePermission()
const canReadFinance = canRead('finance')
const canExportFinance = canExport('finance')
const $q = useQuasar()
const downloadstHeadStore = useDownloadstHeadStore()
@@ -76,6 +81,15 @@ const selectedMonType = ref(monetaryTypeOptions[0].value)
// indirme butonu
async function handlestHeadDownload() {
if (!canExportFinance.value) {
$q.notify({
type: 'negative',
message: 'PDF export yetkiniz yok',
position: 'top-right'
})
return
}
console.log("▶️ [DEBUG] handlestHeadDownload:", accountCode.value, startDate.value, endDate.value, selectedMonType.value)
if (!accountCode.value || !startDate.value || !endDate.value) {

View File

@@ -1,5 +1,5 @@
<template>
<q-page class="q-pa-md page-col">
<q-page v-if="canReadFinance" class="q-pa-md page-col">
<!-- 🔹 Cari Kod / İsim (sabit) -->
<div class="filter-sticky">
@@ -135,6 +135,7 @@
<!-- PDF Yazdır Dropdown -->
<q-btn-dropdown
v-if="canExportFinance"
flat
color="red"
icon="picture_as_pdf"
@@ -258,6 +259,12 @@
</q-table>
</div>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
@@ -270,11 +277,9 @@ import { useDownloadstpdfStore } from 'src/stores/downloadstpdfStore'
import dayjs from 'dayjs'
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, canExport } = usePermission()
const canReadFinance = canRead('finance')
const canExportFinance = canExport('finance')
const $q = useQuasar()
@@ -436,6 +441,15 @@ function toggleLeftCols() {
/* 🔹 PDF İndirme Butonuna bağla */
async function handleDownload() {
if (!canExportFinance.value) {
$q.notify({
type: 'negative',
message: 'PDF export yetkiniz yok',
position: 'top-right'
})
return
}
console.log(" [DEBUG] handleDownload:", selectedCari.value, dateFrom.value, dateTo.value)
if (!selectedCari.value || !dateFrom.value || !dateTo.value) {
@@ -468,6 +482,15 @@ import { useDownloadstHeadStore } from 'src/stores/downloadstHeadStore'
const downloadstHeadStore = useDownloadstHeadStore()
async function CurrheadDownload() {
if (!canExportFinance.value) {
$q.notify({
type: 'negative',
message: 'PDF export yetkiniz yok',
position: 'top-right'
})
return
}
console.log(" [DEBUG] CurrheadDownload:", selectedCari.value, dateFrom.value, dateTo.value)
if (!selectedCari.value || !dateFrom.value || !dateTo.value) {

View File

@@ -1,6 +1,8 @@
<template>
<q-page class="q-pa-md">
<q-page
v-if="canSendTestMail"
class="q-pa-md"
>
<q-card flat bordered class="q-pa-md" style="max-width: 500px">
<q-card-section>
<div class="text-h6">SMTP Test Mail</div>
@@ -9,7 +11,7 @@
<q-card-section>
<q-input
v-model="to"
label="Gönderilecek mail"
label="Gonderilecek mail"
filled
dense
/>
@@ -17,28 +19,36 @@
<q-card-actions align="right">
<q-btn
v-if="canSendTestMail"
color="primary"
label="Test Mail Gönder"
label="Test Mail Gonder"
:loading="store.loading"
:disable="!canSendTestMail"
@click="send"
/>
</q-card-actions>
</q-card>
</q-page>
<q-page
v-else
class="q-pa-md flex flex-center"
>
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
import { ref } from 'vue'
import { computed, ref } from 'vue'
import { useQuasar } from 'quasar'
import { useMailTestStore } from 'src/stores/mailTestStore'
import { usePermission } from 'src/composables/usePermission'
const { canRead, canWrite, canUpdate } = usePermission()
const canReadOrder = canRead('order')
const canWriteOrder = canWrite('order')
const canUpdateOrder = canUpdate('order')
const { canWrite } = usePermission()
const canWriteUser = canWrite('user')
const canSendTestMail = computed(() => canWriteUser.value)
const $q = useQuasar()
const store = useMailTestStore()
@@ -46,17 +56,25 @@ const store = useMailTestStore()
const to = ref('mehmet.kececi@baggi.com.tr')
async function send () {
if (!canSendTestMail.value) {
$q.notify({
type: 'negative',
message: 'Test mail gonderme yetkiniz yok'
})
return
}
try {
await store.sendTestMail(to.value)
$q.notify({
type: 'positive',
message: 'Test mail gönderildi'
message: 'Test mail gonderildi'
})
} catch (err) {
$q.notify({
type: 'negative',
message: err?.message || 'Mail gönderilemedi'
message: err?.message || 'Mail gonderilemedi'
})
}
}

View File

@@ -1,5 +1,5 @@
<template>
<q-page class="user-detail-page">
<q-page v-if="canAccessPage" class="user-detail-page">
<!-- LOADING -->
<q-inner-loading :showing="loading">
@@ -39,6 +39,7 @@
<div>
<q-btn
v-if="canSaveUser"
:label="saveLabel"
color="primary"
icon="save"
@@ -46,6 +47,7 @@
@click="onSave"
/>
<q-btn
v-if="canReadUser"
label="LİSTEYE DÖN"
flat
icon="arrow_back"
@@ -80,6 +82,7 @@
<div class="row items-center">
<q-btn
v-if="canUpdateUser"
label="PAROLA MAİLİ GÖNDER"
color="primary"
icon="mail"
@@ -240,6 +243,12 @@
</div>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
@@ -251,10 +260,9 @@ import { useUserDetailStore } from 'src/stores/UserDetailStore'
import { usePermission } from 'src/composables/usePermission'
const { canRead, canWrite, canUpdate } = usePermission()
const canReadOrder = canRead('order')
const canWriteOrder = canWrite('order')
const canUpdateOrder = canUpdate('order')
const canReadUser = canRead('user')
const canWriteUser = canWrite('user')
const canUpdateUser = canUpdate('user')
const $q = useQuasar()
const route = useRoute()
@@ -280,6 +288,12 @@ const mode = computed(() => route.meta.mode || 'edit')
const isNew = computed(() => mode.value === 'new')
const isEdit = computed(() => mode.value === 'edit')
const isView = computed(() => mode.value === 'view')
const canAccessPage = computed(() => {
if (isNew.value) return canWriteUser.value
if (isEdit.value) return canUpdateUser.value
return canReadUser.value
})
const canSaveUser = computed(() => isNew.value ? canWriteUser.value : canUpdateUser.value)
const userId = computed(() => (isEdit.value || isView.value) ? Number(route.params.id) : null)
@@ -306,6 +320,8 @@ const canSendPasswordMail = computed(() => {
watch(
() => userId.value,
async (id) => {
if (!canAccessPage.value) return
await store.fetchLookups()
if (!id) {
@@ -320,6 +336,11 @@ watch(
/* ================= ACTIONS ================= */
async function onSave () {
if (!canSaveUser.value) {
$q.notify({ type: 'negative', message: 'Kaydetme yetkiniz yok' })
return
}
try {
console.log('🟢 onSave() START', { mode: mode.value })

View File

@@ -1,5 +1,5 @@
<template>
<q-page class="user-gateway-page flex flex-center">
<q-page v-if="canReadUser" class="user-gateway-page flex flex-center">
<div class="gateway-container">
@@ -14,6 +14,7 @@
<!-- YENİ KULLANICI -->
<q-card
v-if="canWriteUser"
class="gateway-card cursor-pointer"
flat
bordered
@@ -30,6 +31,7 @@
<!-- 👥 MEVCUT KULLANICILAR -->
<q-card
v-if="canReadUser"
class="gateway-card cursor-pointer"
flat
bordered
@@ -49,21 +51,27 @@
</div>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
import { useRouter } from 'vue-router'
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, canWrite } = usePermission()
const canReadUser = canRead('user')
const canWriteUser = canWrite('user')
const router = useRouter()
function goCreate () {
if (!canWriteUser.value) return
router.push({
path: '/app/users/new',
query: { mode: 'new' }
@@ -72,6 +80,7 @@ function goCreate () {
function goList () {
if (!canReadUser.value) return
router.push({ name: 'user-list' })
}
</script>

View File

@@ -1,5 +1,8 @@
<template>
<q-page class="ol-page with-bg">
<q-page
v-if="canReadUser"
class="ol-page with-bg"
>
<!-- 🔍 Sticky Filter -->
<div class="ol-filter-bar">
@@ -27,14 +30,17 @@
<div class="ol-filter-actions">
<q-btn
v-if="canReadUser"
label="Yenile"
icon="refresh"
color="primary"
:loading="store.loading"
:disable="!canReadUser"
@click="store.fetchUsers"
/>
<q-btn
v-if="canWriteUser"
label="Yeni Kullanıcı"
icon="person_add"
color="primary"
@@ -68,6 +74,7 @@
<template #body-cell-open="props">
<q-td class="text-center">
<q-btn
v-if="canReadUser"
icon="open_in_new"
color="primary"
flat
@@ -149,6 +156,15 @@
</q-banner>
</q-page>
<q-page
v-else
class="q-pa-md flex flex-center"
>
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
@@ -159,9 +175,9 @@ import { usePermission } from 'src/composables/usePermission'
const { canRead, canWrite, canUpdate } = usePermission()
const canReadOrder = canRead('order')
const canWriteOrder = canWrite('order')
const canUpdateOrder = canUpdate('order')
const canReadUser = canRead('user')
const canWriteUser = canWrite('user')
const canUpdateUser = canUpdate('user')
const router = useRouter()
const store = useUserListStore()
@@ -247,14 +263,17 @@ function splitNames(val) {
}
function openDetail(id) {
const routeName = canUpdateUser.value ? 'user-edit' : 'user-view'
router.push({
path: `/app/users/edit/${id}`
name: routeName,
params: { id: String(id) }
})
}
function goCreate() {
if (!canWriteUser.value) return
router.push({ name: 'user-new' })
}
@@ -268,5 +287,9 @@ function splitPiyasalar (val) {
}
onMounted(store.fetchUsers)
onMounted(() => {
if (canReadUser.value) {
store.fetchUsers()
}
})
</script>

View File

@@ -1,10 +1,10 @@
<template>
<div v-if="!lookupsLoaded" class="q-pa-xl flex flex-center">
<div v-if="canUpdateUser && !lookupsLoaded" class="q-pa-xl flex flex-center">
<q-spinner color="primary" size="48px" />
</div>
<q-page class="permissions-page">
<q-page v-if="canUpdateUser" class="permissions-page">
<div class="sticky-stack">
@@ -36,6 +36,7 @@
</div>
<q-btn
v-if="canUpdateUser"
color="primary"
icon="save"
label="Kaydet"
@@ -115,6 +116,12 @@
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
@@ -123,11 +130,8 @@ 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 { canUpdate } = usePermission()
const canUpdateUser = canUpdate('user')
/* ================= STATE ================= */

View File

@@ -1,13 +1,18 @@
<template>
<q-page class="q-pa-md user-sync-page">
<q-page
v-if="canReadUser"
class="q-pa-md user-sync-page"
>
<div class="row items-center justify-between q-mb-md">
<div class="text-h6 text-primary">👤 Kullanıcı Yönetimi</div>
<q-btn
v-if="canUpdateUser"
color="primary"
icon="sync"
label="Sync Now"
:loading="store.loading"
:disable="store.loading || !canUpdateUser"
@click="store.syncNow"
/>
</div>
@@ -46,18 +51,20 @@
<template v-slot:body-cell-actions="props">
<q-td :props="props">
<q-btn
v-if="canUpdateUser"
dense flat icon="link"
color="primary"
size="sm"
@click="openMapDialog(props.row)"
:disable="store.loading"
:disable="store.loading || !canUpdateUser"
/>
<q-btn
v-if="canUpdateUser"
dense flat icon="link_off"
color="negative"
size="sm"
@click="store.unmap(props.row.id)"
:disable="!props.row.mssql_username"
:disable="store.loading || !props.row.mssql_username || !canUpdateUser"
/>
</q-td>
</template>
@@ -97,6 +104,15 @@
</div>
</div>
</q-page>
<q-page
v-else
class="q-pa-md flex flex-center"
>
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
@@ -105,11 +121,9 @@ import { useUserSyncStore } from 'src/stores/userSyncStore'
import { Dialog } from 'quasar'
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 store = useUserSyncStore()
@@ -142,6 +156,10 @@ function statusColor(status) {
}
function openMapDialog(pgUser) {
if (!canUpdateUser.value) {
return
}
Dialog.create({
title: 'Kullanıcı Eşleme',
message: 'Bu PostgreSQL kullanıcısını hangi MSSQL kullanıcısına bağlamak istiyorsunuz?',
@@ -158,7 +176,9 @@ function openMapDialog(pgUser) {
}
onMounted(() => {
if (canReadUser.value) {
store.loadDummy()
}
})
</script>

View File

@@ -1,5 +1,5 @@
<template>
<q-page class="q-pa-md page-col">
<q-page v-if="canReadFinance" class="q-pa-md page-col">
<!-- 🔹 Cari Kod / İsim (sabit) -->
<div class="filter-sticky">
@@ -135,6 +135,7 @@
<!-- PDF Yazdır Dropdown -->
<q-btn-dropdown
v-if="canExportFinance"
flat
color="red"
icon="picture_as_pdf"
@@ -258,6 +259,12 @@
</q-table>
</div>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
@@ -270,11 +277,9 @@ import { useDownloadstpdfStore } from 'src/stores/downloadstpdfStore'
import dayjs from 'dayjs'
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, canExport } = usePermission()
const canReadFinance = canRead('finance')
const canExportFinance = canExport('finance')
const $q = useQuasar()
@@ -458,6 +463,15 @@ function toggleLeftCols() {
/* 🔹 PDF İndirme Butonuna bağla */
async function handleDownload() {
if (!canExportFinance.value) {
$q.notify({
type: 'negative',
message: 'PDF export yetkiniz yok',
position: 'top-right'
})
return
}
console.log(" [DEBUG] handleDownload:", selectedCari.value, dateFrom.value, dateTo.value)
if (!selectedCari.value || !dateFrom.value || !dateTo.value) {
@@ -490,6 +504,15 @@ import { useDownloadstHeadStore } from 'src/stores/downloadstHeadStore'
const downloadstHeadStore = useDownloadstHeadStore()
async function CurrheadDownload() {
if (!canExportFinance.value) {
$q.notify({
type: 'negative',
message: 'PDF export yetkiniz yok',
position: 'top-right'
})
return
}
console.log(" [DEBUG] CurrheadDownload:", selectedCari.value, dateFrom.value, dateTo.value)
if (!selectedCari.value || !dateFrom.value || !dateTo.value) {

View File

@@ -2557,6 +2557,52 @@ export const useOrderEntryStore = defineStore('orderentry', {
// =======================================================
this.debugOrderPayload?.(header, lines, 'PRE-VALIDATE')
// =======================================================
// 🧩 DUMMY CURRENCY PAYLOAD (model genişletmeden)
// - trOrderLineCurrency için gerekli alanları satıra basar
// - örnek satırdaki gibi: PriceVI/AmountVI = KDV dahil, Price/Amount = KDV hariç
// =======================================================
const r2 = (n) => Number((Number(n) || 0).toFixed(2))
const r4 = (n) => Number((Number(n) || 0).toFixed(4))
for (const ln of lines) {
const qty = Number(ln?.Qty1 || 0)
const unitBase = Number(ln?.Price || 0) // KDV hariç birim
const vatRate = Number(ln?.VatRate || 0)
const exRate = Number(ln?.PriceExchangeRate || header?.ExchangeRate || 1) || 1
const taxBase = r2(unitBase * qty) // Amount
const vat = r2((taxBase * vatRate) / 100) // Vat
const net = r2(taxBase + vat) // AmountVI / NetAmount
const unitWithVat = qty > 0 ? r4(net / qty) : r4(unitBase * (1 + vatRate / 100))
const docCurrency = String(ln?.DocCurrencyCode || header?.DocCurrencyCode || 'TRY').trim() || 'TRY'
// Backend model alanları
ln.RelationCurrencyCode = docCurrency
ln.DocPrice = unitWithVat
ln.DocAmount = net
ln.LocalPrice = unitBase
ln.LocalAmount = taxBase
ln.LineDiscount = Number(ln?.LineDiscount || 0)
ln.TotalDiscount = Number(ln?.TotalDiscount || 0)
ln.TaxBase = taxBase
ln.Pct = Number(ln?.Pct || 0)
ln.VatAmount = vat
ln.VatDeducation = 0
ln.NetAmount = net
// SQL kolonu isimleriyle dummy alias (decoder ignore etse de payload'da görünür)
ln.CurrencyCode = docCurrency
ln.ExchangeRate = exRate
ln.PriceVI = unitWithVat
ln.AmountVI = net
ln.Amount = taxBase
ln.LDiscount1 = Number(ln?.LDiscount1 || 0)
ln.TDiscount1 = Number(ln?.TDiscount1 || 0)
ln.Vat = vat
}
// =======================================================
// 🧪 PRE-VALIDATE — prItemVariant ön kontrol
// - invalid varsa CREATE/UPDATE ÇALIŞMAZ
@@ -3337,4 +3383,3 @@ export const sharedOrderEntryRefs = {
}