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

@@ -15,10 +15,11 @@ func BuildClaimsFromUser(u *models.MkUser, ttl time.Duration) Claims {
// 🔴 mk_dfusr.id // 🔴 mk_dfusr.id
ID: u.ID, ID: u.ID,
Username: u.Username, Username: u.Username,
RoleCode: u.RoleCode, RoleCode: u.RoleCode,
RoleID: u.RoleID, RoleID: u.RoleID,
// ✅ BURASI V3Username: u.V3Username,
V3UserGroup: u.V3UserGroup,
DepartmentCodes: u.DepartmentCodes, DepartmentCodes: u.DepartmentCodes,
SessionID: u.SessionID, SessionID: u.SessionID,

View File

@@ -54,9 +54,10 @@ type OrderDetail struct {
LDisRate4 NullFloat64 `json:"LDisRate4"` LDisRate4 NullFloat64 `json:"LDisRate4"`
LDisRate5 NullFloat64 `json:"LDisRate5"` LDisRate5 NullFloat64 `json:"LDisRate5"`
DocCurrencyCode NullString `json:"DocCurrencyCode"` DocCurrencyCode NullString `json:"DocCurrencyCode"`
PriceCurrencyCode NullString `json:"PriceCurrencyCode"` RelationCurrencyCode NullString `json:"RelationCurrencyCode"`
PriceExchangeRate NullFloat64 `json:"PriceExchangeRate"` PriceCurrencyCode NullString `json:"PriceCurrencyCode"`
PriceExchangeRate NullFloat64 `json:"PriceExchangeRate"`
Price NullFloat64 `json:"Price"` Price NullFloat64 `json:"Price"`
@@ -95,4 +96,29 @@ type OrderDetail struct {
UrunAltGrubu NullString `json:"UrunAltGrubu"` UrunAltGrubu NullString `json:"UrunAltGrubu"`
Fit1 NullString `json:"Fit1"` Fit1 NullString `json:"Fit1"`
Fit2 NullString `json:"Fit2"` 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 // 💰 Tutarlar
TotalAmount float64 `json:"TotalAmount"` TotalAmount float64 `json:"TotalAmount"`
TotalAmountUSD float64 `json:"TotalAmountUSD"` TotalAmountUSD float64 `json:"TotalAmountUSD"`
PackedAmount float64 `json:"PackedAmount"`
PackedUSD float64 `json:"PackedUSD"`
PackedRatePct float64 `json:"PackedRatePct"`
// 📝 Açıklama // 📝 Açıklama
Description string `json:"Description"` Description string `json:"Description"`

View File

@@ -12,40 +12,230 @@ func GetOrderListExcel(
orderDate string, orderDate string,
) (*sql.Rows, error) { ) (*sql.Rows, error) {
q := OrderListBaseQuery + " AND 1=1 " q := `
args := []interface{}{} 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 != "" { if search != "" {
q += ` q += `
AND ( AND (
LOWER(h.OrderNumber) LIKE LOWER(@p1) OR LOWER(h.OrderNumber) LIKE LOWER(@p1)
LOWER(h.CurrAccCode) LIKE LOWER(@p1) OR OR LOWER(h.CurrAccCode) LIKE LOWER(@p1)
LOWER(ca.CurrAccDescription) LIKE LOWER(@p1) OR OR LOWER(ca.CurrAccDescription) LIKE LOWER(@p1)
LOWER(h.Description) LIKE LOWER(@p1) OR OR LOWER(h.Description) LIKE LOWER(@p1)
LOWER(mt.AttributeDescription) LIKE LOWER(@p1) OR OR LOWER(mt.AttributeDescription) LIKE LOWER(@p1)
LOWER(py.AttributeDescription) LIKE LOWER(@p1) OR LOWER(py.AttributeDescription) LIKE LOWER(@p1)
) )
` `
args = append(args, "%"+search+"%") args = append(args, "%"+search+"%")
} }
// CURRACC // ================= CURRACC =================
if currAcc != "" { if currAcc != "" {
q += fmt.Sprintf(" AND h.CurrAccCode = @p%d ", len(args)+1) q += fmt.Sprintf(" AND h.CurrAccCode = @p%d ", len(args)+1)
args = append(args, currAcc) args = append(args, currAcc)
} }
// DATE // ================= DATE =================
if orderDate != "" { if orderDate != "" {
q += fmt.Sprintf( q += fmt.Sprintf(
" AND CONVERT(varchar, h.OrderDate, 23) = @p%d ", " AND CONVERT(varchar,h.OrderDate,23) = @p%d ",
len(args)+1, len(args)+1,
) )
args = append(args, orderDate) args = append(args, orderDate)
} }
// ORDER BY SONDA // ================= ORDER =================
q += " ORDER BY h.CreatedDate DESC " q += " ORDER BY h.CreatedDate DESC "
return db.Query(q, args...) return db.Query(q, args...)

View File

@@ -8,15 +8,12 @@ import (
"fmt" "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) { func GetOrderByID(orderID string) (*models.OrderHeader, []models.OrderDetail, error) {
conn := db.GetDB() 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 var header models.OrderHeader
qHeader := ` qHeader := `
SELECT SELECT
@@ -176,61 +173,76 @@ func GetOrderByID(orderID string) (*models.OrderHeader, []models.OrderDetail, er
&header.LastUpdatedDate, &header.LastUpdatedDate,
&header.IsProposalBased, &header.IsProposalBased,
) )
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { 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 return nil, nil, sql.ErrNoRows
} }
logger.Printf("[GetOrderByID] header sorgu hatası: %v", err) logger.Printf("[GetOrderByID] header error: %v", err)
return nil, nil, err return nil, nil, err
} }
logger.Printf("✅ [GetOrderByID] header loaded • orderNo=%v currAcc=%v",
header.OrderNumber, header.CurrAccCode.String)
// =====================================================
// LINES
// =====================================================
qLines := ` qLines := `
SELECT SELECT
CAST(L.OrderLineID AS varchar(36)) AS OrderLineID, CAST(L.OrderLineID AS varchar(36)) AS OrderLineID,
L.SortOrder, L.SortOrder,
L.ItemTypeCode, L.ItemTypeCode,
L.ItemCode, L.ItemCode,
L.ColorCode, L.ColorCode,
L.ItemDim1Code, L.ItemDim1Code,
L.ItemDim2Code, L.ItemDim2Code,
L.ItemDim3Code, L.ItemDim3Code,
L.Qty1, L.Qty1,
L.Qty2, L.Qty2,
L.Price,
L.VatRate, ISNULL(CD.Price, 0) AS Price,
L.PCTRate, ISNULL(CD.CurrencyCode, ISNULL(L.DocCurrencyCode, 'TRY')) AS DocCurrencyCode,
L.DocCurrencyCode, ISNULL(CD.RelationCurrencyCode, ISNULL(L.DocCurrencyCode, 'TRY')) AS RelationCurrencyCode,
L.DeliveryDate, ISNULL(CD.ExchangeRate, ISNULL(L.PriceExchangeRate, 1)) AS PriceExchangeRate,
L.PlannedDateOfLading, ISNULL(CD.PriceVI, ISNULL(L.Price, 0)) AS DocPrice,
L.LineDescription, ISNULL(CD.AmountVI, ISNULL(L.Price, 0) * ISNULL(L.Qty1, 0)) AS DocAmount,
L.IsClosed, ISNULL(CD.LDiscount1, 0) AS LineDiscount,
L.CreatedUserName, ISNULL(CD.TDiscount1, 0) AS TotalDiscount,
L.CreatedDate, ISNULL(CD.TaxBase, 0) AS TaxBase,
L.LastUpdatedUserName, ISNULL(CD.Pct, 0) AS Pct,
L.LastUpdatedDate, ISNULL(CD.Vat, 0) AS VatAmount,
P.ProductAtt42Desc AS UrunIlkGrubu, ISNULL(CD.VatDeducation, 0) AS VatDeducation,
P.ProductAtt01Desc AS UrunAnaGrubu, ISNULL(CD.NetAmount, 0) AS NetAmount,
P.ProductAtt02Desc AS UrunAltGrubu, ISNULL(CL.Price, ISNULL(CD.Price, 0)) AS LocalPrice,
P.ProductAtt38Desc AS Fit1, ISNULL(CL.Amount, ISNULL(CD.Amount, 0)) AS LocalAmount,
P.ProductAtt39Desc AS Fit2
FROM BAGGI_V3.dbo.trOrderLine AS L L.VatRate,
LEFT JOIN ProductFilterWithDescription('TR') AS P L.PCTRate,
ON LTRIM(RTRIM(P.ProductCode)) = LTRIM(RTRIM(L.ItemCode)) L.DeliveryDate,
WHERE L.OrderHeaderID = @p1 L.PlannedDateOfLading,
ORDER BY L.SortOrder ASC; 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) rows, err := conn.Query(qLines, orderID)
if err != nil { if err != nil {
logger.Printf("[GetOrderByID] line sorgu hatası: %v", err) logger.Printf("[GetOrderByID] lines error: %v", err)
return &header, nil, err return &header, nil, err
} }
defer rows.Close() defer rows.Close()
@@ -250,9 +262,22 @@ func GetOrderByID(orderID string) (*models.OrderHeader, []models.OrderDetail, er
&ln.Qty1, &ln.Qty1,
&ln.Qty2, &ln.Qty2,
&ln.Price, &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.VatRate,
&ln.PCTRate, &ln.PCTRate,
&ln.DocCurrencyCode,
&ln.DeliveryDate, &ln.DeliveryDate,
&ln.PlannedDateOfLading, &ln.PlannedDateOfLading,
&ln.LineDescription, &ln.LineDescription,
@@ -267,14 +292,14 @@ func GetOrderByID(orderID string) (*models.OrderHeader, []models.OrderDetail, er
&ln.Fit1, &ln.Fit1,
&ln.Fit2, &ln.Fit2,
); err != nil { ); 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) lines = append(lines, ln)
} }
if err := rows.Err(); err != nil { 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 return &header, lines, nil
} }

View File

@@ -13,9 +13,10 @@ import (
"bssapp-backend/models" "bssapp-backend/models"
"database/sql" "database/sql"
"fmt" "fmt"
"github.com/google/uuid"
"strings" "strings"
"time" "time"
"github.com/google/uuid"
) )
func nf0(v models.NullFloat64) float64 { func nf0(v models.NullFloat64) float64 {
@@ -25,6 +26,311 @@ func nf0(v models.NullFloat64) float64 {
return v.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 // COMBO KEY & STRING HELPERS
// ======================================================= // =======================================================
@@ -52,6 +358,32 @@ func qtyValue(q models.NullFloat64) float64 {
return q.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 // VatCode: NullString → string
// - NULL → "" // - NULL → ""
// - "0" → "" (FK patlamasın, sadece anlamlı kodlar gönderiyoruz) // - "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 var exists int
err := tx.QueryRow(` err := tx.QueryRow(`
SELECT CASE WHEN EXISTS ( SELECT CASE WHEN EXISTS (
@@ -297,12 +626,6 @@ type OrderLineResult struct {
// ======================================================= // =======================================================
// PART 1 — InsertOrder (header + lines insert) — FINAL v5.1 // 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) { 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() defer tx.Rollback()
now := time.Now() now := time.Now()
v3User := fmt.Sprintf("V3U%d-%s", user.V3UserGroup, user.V3Username) v3User := buildV3AuditUser(user)
// ======================================================= // =======================================================
// 1) BACKEND — OrderHeaderID üretimi (HER ZAMAN) // 1) BACKEND — OrderHeaderID üretimi (HER ZAMAN)
@@ -368,7 +691,6 @@ func InsertOrder(header models.OrderHeader, lines []models.OrderDetail, user *mo
} }
} }
} }
// ======================================================= // =======================================================
// 4) HEADER INSERT // 4) HEADER INSERT
// ======================================================= // =======================================================
@@ -423,6 +745,7 @@ VALUES (
fmt.Println("🟪 HEADER INSERT ÇALIŞIYOR...") fmt.Println("🟪 HEADER INSERT ÇALIŞIYOR...")
// ✅ exRate burada gerçekten kullanılıyor (ExchangeRate parametresi)
headerParams := []any{ headerParams := []any{
header.OrderHeaderID, header.OrderHeaderID,
nullableInt16(header.OrderTypeCode, 1), nullableInt16(header.OrderTypeCode, 1),
@@ -474,7 +797,7 @@ VALUES (
nullableString(header.GLTypeCode, ""), nullableString(header.GLTypeCode, ""),
nullableString(header.DocCurrencyCode, "TRY"), nullableString(header.DocCurrencyCode, "TRY"),
nullableString(header.LocalCurrencyCode, "TRY"), nullableString(header.LocalCurrencyCode, "TRY"),
nullableFloat64(header.ExchangeRate, exRate), nullableFloat64(header.ExchangeRate, exRate), // ✅ exRate kullanıldı
nullableFloat64(header.TDisRate1, 0), nullableFloat64(header.TDisRate1, 0),
nullableFloat64(header.TDisRate2, 0), nullableFloat64(header.TDisRate2, 0),
@@ -520,6 +843,7 @@ VALUES (
nullableBool(header.IsProposalBased, false), nullableBool(header.IsProposalBased, false),
} }
// ✅ queryHeader artık gerçekten kullanılıyor → "Unused variable 'queryHeader'" biter
if _, err := tx.Exec(queryHeader, headerParams...); err != nil { if _, err := tx.Exec(queryHeader, headerParams...); err != nil {
fmt.Println("❌ HEADER INSERT ERROR:", err) fmt.Println("❌ HEADER INSERT ERROR:", err)
return "", nil, fmt.Errorf("header insert hatasi: %w", err) return "", nil, fmt.Errorf("header insert hatasi: %w", err)
@@ -527,6 +851,7 @@ VALUES (
fmt.Println("🟩 HEADER INSERT OK — ID:", newID) fmt.Println("🟩 HEADER INSERT OK — ID:", newID)
// headerParams ... (senin mevcut hali aynen)
// ======================================================= // =======================================================
// 5) LINE INSERT // 5) LINE INSERT
// ======================================================= // =======================================================
@@ -590,7 +915,6 @@ VALUES (
seenCombo := make(map[string]bool) seenCombo := make(map[string]bool)
for i, ln := range lines { for i, ln := range lines {
// ===================== PART 2 (Satır 301-600) =====================
fmt.Println("────────────────────────────────────") fmt.Println("────────────────────────────────────")
fmt.Printf("🟨 [INSERT] LINE %d — gelen OrderLineID=%s\n", i+1, ln.OrderLineID) fmt.Printf("🟨 [INSERT] LINE %d — gelen OrderLineID=%s\n", i+1, ln.OrderLineID)
@@ -630,6 +954,7 @@ VALUES (
} }
planned := nullableDateString(ln.PlannedDateOfLading) planned := nullableDateString(ln.PlannedDateOfLading)
// ✅ INSERT ÖNCESİ ItemVariant GUARD // ✅ INSERT ÖNCESİ ItemVariant GUARD
if qtyValue(ln.Qty1) > 0 { if qtyValue(ln.Qty1) > 0 {
if err := ValidateItemVariant(tx, ln); err != nil { if err := ValidateItemVariant(tx, ln); err != nil {
@@ -637,6 +962,7 @@ VALUES (
return "", nil, err return "", nil, err
} }
} }
fmt.Printf( fmt.Printf(
"🚨 INSERT LINE[%d] | LineID=%s ClientKey=%s Item=%q Color=%q Dim1=%q Dim2=%q Dim3=%q Qty1=%v\n", "🚨 INSERT LINE[%d] | LineID=%s ClientKey=%s Item=%q Color=%q Dim1=%q Dim2=%q Dim3=%q Qty1=%v\n",
i+1, i+1,
@@ -708,6 +1034,28 @@ VALUES (
return "", nil, fmt.Errorf("line insert hatasi: %w", err) 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 != "" { if ln.ClientKey.Valid && ln.ClientKey.String != "" {
lineResults = append(lineResults, OrderLineResult{ lineResults = append(lineResults, OrderLineResult{
ClientKey: ln.ClientKey.String, ClientKey: ln.ClientKey.String,
@@ -734,25 +1082,16 @@ VALUES (
// ======================================================= // =======================================================
// PART 2 — UpdateOrder FULL DEBUG (v4.3) // 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) { func UpdateOrder(header models.OrderHeader, lines []models.OrderDetail, user *models.User) ([]OrderLineResult, error) {
conn := db.GetDB() conn := db.GetDB()
// ======================================================
// 🔍 SCAN DEBUG — HEADER bilgisi
// ======================================================
fmt.Println("══════════════════════════════════════") fmt.Println("══════════════════════════════════════")
fmt.Println("🔍 [DEBUG] UpdateOrder çağrıldı") fmt.Println("🔍 [DEBUG] UpdateOrder çağrıldı")
fmt.Printf("🔍 HeaderID: %v\n", header.OrderHeaderID) fmt.Printf("🔍 HeaderID: %v\n", header.OrderHeaderID)
fmt.Printf("🔍 Line sayısı: %v\n", len(lines)) fmt.Printf("🔍 Line sayısı: %v\n", len(lines))
fmt.Printf("🔍 User: %v (V3: %s/%d)\n", fmt.Printf("🔍 User: %v (V3: %s/%d)\n", user.Username, user.V3Username, user.V3UserGroup)
user.Username, user.V3Username, user.V3UserGroup)
fmt.Println("══════════════════════════════════════") fmt.Println("══════════════════════════════════════")
tx, err := conn.Begin() tx, err := conn.Begin()
@@ -762,9 +1101,9 @@ func UpdateOrder(header models.OrderHeader, lines []models.OrderDetail, user *mo
defer tx.Rollback() defer tx.Rollback()
now := time.Now() 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 exRate := 1.0
if header.DocCurrencyCode.Valid && header.DocCurrencyCode.String != "TRY" { if header.DocCurrencyCode.Valid && header.DocCurrencyCode.String != "TRY" {
if c, err := GetTodayCurrencyV3(conn, header.DocCurrencyCode.String); err == nil && c.Rate > 0 { 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!) // 0) Mevcut satırları oku (GUID STRING olarak!)
// ======================================================= // =======================================================
existingOpen := make(map[string]bool) existingOpen := make(map[string]bool)
existingClosed := make(map[string]bool) existingClosed := make(map[string]bool)
existingOpenCombo := make(map[string]string) existingOpenCombo := make(map[string]string)
existingClosedCombo := 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(` rows, err := tx.Query(`
SELECT SELECT
@@ -814,16 +1158,41 @@ WHERE OrderHeaderID=@p1
} }
} else { } else {
existingOpen[id] = true 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 != "" { if combo != "" {
existingOpenCombo[combo] = id 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 // HEADER UPDATE
// ====================================================== // ======================================================
_, err = tx.Exec(` _, err = tx.Exec(`
UPDATE BAGGI_V3.dbo.trOrderHeader SET UPDATE BAGGI_V3.dbo.trOrderHeader SET
OrderDate=@p1, OrderDate=@p1,
@@ -853,11 +1222,12 @@ WHERE OrderHeaderID=@p11
if err != nil { if err != nil {
return nil, err return nil, err
} }
// ====================================================== // ======================================================
// PREPARE STATEMENTS // PREPARE STATEMENTS
// ====================================================== // ======================================================
insStmt, err := tx.Prepare(`
insStmt, err := tx.Prepare(`INSERT INTO BAGGI_V3.dbo.trOrderLine ( INSERT INTO BAGGI_V3.dbo.trOrderLine (
OrderLineID, SortOrder, ItemTypeCode, ItemCode, ColorCode, OrderLineID, SortOrder, ItemTypeCode, ItemCode, ColorCode,
ItemDim1Code, ItemDim2Code, ItemDim3Code, ItemDim1Code, ItemDim2Code, ItemDim3Code,
Qty1, Qty2, CancelQty1, CancelQty2, OrderCancelReasonCode, Qty1, Qty2, CancelQty1, CancelQty2, OrderCancelReasonCode,
@@ -872,21 +1242,23 @@ BaseSubCurrAccID, BaseStoreCode,
OrderHeaderID, CreatedUserName, CreatedDate, OrderHeaderID, CreatedUserName, CreatedDate,
LastUpdatedUserName, LastUpdatedDate, LastUpdatedUserName, LastUpdatedDate,
SurplusOrderQtyToleranceRate, SurplusOrderQtyToleranceRate,
WithHoldingTaxTypeCode, DOVCode) WithHoldingTaxTypeCode, DOVCode
)
VALUES ( VALUES (
@p1,@p2,@p3,@p4,@p5,@p6,@p7,@p8,@p9,@p10, @p1,@p2,@p3,@p4,@p5,@p6,@p7,@p8,@p9,@p10,
@p11,@p12,@p13,@p14,@p15,@p16,@p17,@p18, @p11,@p12,@p13,@p14,@p15,@p16,@p17,@p18,
@p19,@p20,@p21,@p22,@p23,@p24,@p25,@p26,@p27, @p19,@p20,@p21,@p22,@p23,@p24,@p25,@p26,@p27,
@p28,@p29,@p30,@p31,@p32,@p33,@p34,@p35, @p28,@p29,@p30,@p31,@p32,@p33,@p34,@p35,
@p36,@p37,@p38,@p39,@p40,@p41,@p42,@p43, @p36,@p37,@p38,@p39,@p40,@p41,@p42,@p43,
@p44,@p45)`) @p44,@p45
)`)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer insStmt.Close() 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, SortOrder=@p1, ItemTypeCode=@p2, ItemCode=@p3, ColorCode=@p4,
ItemDim1Code=@p5, ItemDim2Code=@p6, ItemDim3Code=@p7, ItemDim1Code=@p5, ItemDim2Code=@p6, ItemDim3Code=@p7,
Qty1=@p8, Qty2=@p9, CancelQty1=@p10, CancelQty2=@p11, Qty1=@p8, Qty2=@p9, CancelQty1=@p10, CancelQty2=@p11,
@@ -905,17 +1277,18 @@ LastUpdatedUserName=@p37, LastUpdatedDate=@p38,
SurplusOrderQtyToleranceRate=@p39, SurplusOrderQtyToleranceRate=@p39,
WithHoldingTaxTypeCode=@p40, DOVCode=@p41 WithHoldingTaxTypeCode=@p40, DOVCode=@p41
WHERE OrderLineID=@p42 AND ISNULL(IsClosed,0)=0`) WHERE OrderLineID=@p42 AND ISNULL(IsClosed,0)=0`)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer updStmt.Close() defer updStmt.Close()
// ======================================================
// LOOP
// ======================================================
lineResults := make([]OrderLineResult, 0) lineResults := make([]OrderLineResult, 0)
seenCombo := make(map[string]bool) seenCombo := make(map[string]bool)
for _, ln := range lines { for _, ln := range lines {
comboKey := normalizeComboKey(safeNS(ln.ComboKey)) comboKey := normalizeComboKey(safeNS(ln.ComboKey))
if comboKey == "" { if comboKey == "" {
comboKey = makeComboKey(ln) comboKey = makeComboKey(ln)
@@ -929,7 +1302,7 @@ WHERE OrderLineID=@p42 AND ISNULL(IsClosed,0)=0`)
seenCombo[comboKey] = true seenCombo[comboKey] = true
} }
// Kapalı satır // Kapalı satır guard
if ln.OrderLineID != "" && existingClosed[ln.OrderLineID] { if ln.OrderLineID != "" && existingClosed[ln.OrderLineID] {
continue continue
} }
@@ -941,23 +1314,39 @@ WHERE OrderLineID=@p42 AND ISNULL(IsClosed,0)=0`)
// DELETE SIGNAL // DELETE SIGNAL
if ln.OrderLineID != "" && qtyValue(ln.Qty1) <= 0 { 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 { if err != nil {
return nil, err 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 DELETE FROM BAGGI_V3.dbo.trOrderLine
WHERE OrderHeaderID=@p1 AND OrderLineID=@p2 AND ISNULL(IsClosed,0)=0 WHERE OrderHeaderID=@p1 AND OrderLineID=@p2 AND ISNULL(IsClosed,0)=0
`, header.OrderHeaderID, ln.OrderLineID) `, header.OrderHeaderID, ln.OrderLineID); err != nil {
if err != nil {
return nil, err return nil, err
} }
delete(existingOpen, ln.OrderLineID) delete(existingOpen, ln.OrderLineID)
delete(existingOpenCombo, comboKey) delete(existingOpenCombo, comboKey)
continue continue
} }
isNew := false isNew := false
// ID resolve: boşsa combo'dan yakala, yoksa yeni üret
if ln.OrderLineID == "" { if ln.OrderLineID == "" {
if dbID, ok := existingOpenCombo[comboKey]; ok { if dbID, ok := existingOpenCombo[comboKey]; ok {
ln.OrderLineID = dbID 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 qtyValue(ln.Qty1) > 0 {
if err := ValidateItemVariant(tx, ln); err != nil { if err := ValidateItemVariant(tx, ln); err != nil {
return nil, err 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(existingOpen, ln.OrderLineID)
delete(existingOpenCombo, comboKey) delete(existingOpenCombo, comboKey)
// Sonuç mapping
if ln.ClientKey.Valid { if ln.ClientKey.Valid {
lineResults = append(lineResults, OrderLineResult{ lineResults = append(lineResults, OrderLineResult{
ClientKey: ln.ClientKey.String, 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 { for id := range existingOpen {
_, err := tx.Exec(`DELETE FROM BAGGI_V3.dbo.trOrderLineCurrency WHERE OrderLineID=@p1`, id) invoiced, err := isLineInvoiced(id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = tx.Exec(`DELETE FROM BAGGI_V3.dbo.trOrderLine WHERE OrderLineID=@p1 AND ISNULL(IsClosed,0)=0`, id) if invoiced {
if err != nil { 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 return nil, err
} }
} }
// =======================================================
// COMMIT + RETURN
// =======================================================
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
return nil, err return nil, err
} }

View File

@@ -10,6 +10,10 @@ import (
// ======================================================== // ========================================================
// 📌 GetOrderList — FINAL + CURRENCY SAFE + PIYASA AUTHZ // 📌 GetOrderList — FINAL + CURRENCY SAFE + PIYASA AUTHZ
//
// ✅ TotalAmount artık trOrderLineCurrency(NetAmount) üzerinden
// ve CurrencyCode = Header.DocCurrencyCode satırından gelir.
//
// ======================================================== // ========================================================
func GetOrderList( func GetOrderList(
ctx context.Context, ctx context.Context,
@@ -36,10 +40,8 @@ func GetOrderList(
} }
if len(codes) == 0 { if len(codes) == 0 {
// hiç yetkisi yok → hiç kayıt dönmesin
piyasaWhere = "1=0" piyasaWhere = "1=0"
} else { } else {
// ⚠️ EXISTS içinde kullanılacak
piyasaWhere = authz.BuildINClause( piyasaWhere = authz.BuildINClause(
"UPPER(f2.CustomerAtt01)", "UPPER(f2.CustomerAtt01)",
codes, codes,
@@ -86,6 +88,29 @@ SELECT
ELSE 0 ELSE 0
END AS TotalAmountUSD, 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.IsCreditableConfirmed,0) AS IsCreditableConfirmed,
ISNULL(h.Description,'') AS Description, ISNULL(h.Description,'') AS Description,
@@ -93,12 +118,40 @@ SELECT
FROM dbo.trOrderHeader h FROM dbo.trOrderHeader h
-- ✅ TOPLAM ARTIK trOrderLineCurrency'den: CurrencyCode = DocCurrencyCode
JOIN ( JOIN (
SELECT SELECT
OrderHeaderID, l.OrderHeaderID,
SUM(Qty1 * Price) AS TotalAmount SUM(ISNULL(c.NetAmount,0)) AS TotalAmount,
FROM dbo.trOrderLine SUM(
GROUP BY OrderHeaderID 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 ) l
ON l.OrderHeaderID = h.OrderHeaderID 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), FILTER (WHERE d.code IS NOT NULL),
'{}' '{}'
) AS department_codes, ) 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.password_updated_at,
u.created_at, u.created_at,
@@ -67,6 +69,13 @@ LEFT JOIN dfusr_dprt ud
LEFT JOIN mk_dprt d LEFT JOIN mk_dprt d
ON d.id = ud.dprt_id 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) WHERE LOWER(u.username) = LOWER($1)
GROUP BY GROUP BY
@@ -85,6 +94,8 @@ LIMIT 1
&u.RoleCode, &u.RoleCode,
pq.Array(&u.DepartmentCodes), // ✅ pq.Array(&u.DepartmentCodes), // ✅
&u.V3Username,
&u.V3UserGroup,
&u.PasswordUpdatedAt, &u.PasswordUpdatedAt,
@@ -127,6 +138,8 @@ func (r *MkUserRepository) GetByID(id int64) (*models.MkUser, error) {
FILTER (WHERE d.code IS NOT NULL), FILTER (WHERE d.code IS NOT NULL),
'{}' '{}'
) AS department_codes, ) 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.password_updated_at,
u.created_at, u.created_at,
@@ -149,7 +162,14 @@ LEFT JOIN dfusr_dprt ud
LEFT JOIN mk_dprt d LEFT JOIN mk_dprt d
ON d.id = ud.dprt_id 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 GROUP BY
u.id, r.id u.id, r.id
@@ -166,6 +186,8 @@ LIMIT 1
&u.RoleID, &u.RoleID,
&u.RoleCode, &u.RoleCode,
pq.Array(&u.DepartmentCodes), // ✅ pq.Array(&u.DepartmentCodes), // ✅
&u.V3Username,
&u.V3UserGroup,
&u.PasswordUpdatedAt, &u.PasswordUpdatedAt,
&u.CreatedAt, &u.CreatedAt,
&u.UpdatedAt, &u.UpdatedAt,

View File

@@ -1,7 +1,6 @@
package routes package routes
import ( import (
"bssapp-backend/models"
"bssapp-backend/queries" "bssapp-backend/queries"
"database/sql" "database/sql"
"fmt" "fmt"
@@ -14,25 +13,50 @@ import (
func OrderListExcelRoute(db *sql.DB) http.Handler { func OrderListExcelRoute(db *sql.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-store")
// ======================
// PARAMS
// ======================
search := r.URL.Query().Get("search") search := r.URL.Query().Get("search")
currAcc := r.URL.Query().Get("CurrAccCode") currAcc := r.URL.Query().Get("CurrAccCode")
orderDate := r.URL.Query().Get("OrderDate") orderDate := r.URL.Query().Get("OrderDate")
// ======================
// QUERY
// ======================
rows, err := queries.GetOrderListExcel(db, search, currAcc, orderDate) rows, err := queries.GetOrderListExcel(db, search, currAcc, orderDate)
if err != nil { if err != nil {
http.Error(w, "Veritabanı hatası", 500) http.Error(w, err.Error(), 500)
return return
} }
defer rows.Close() defer rows.Close()
// ======================
// EXCEL INIT
// ======================
f := excelize.NewFile() f := excelize.NewFile()
sheet := "Orders" sheet := "Orders"
f.SetSheetName("Sheet1", sheet) f.SetSheetName("Sheet1", sheet)
// ======================
// HEADERS
// ======================
headers := []string{ headers := []string{
"Sipariş No", "Tarih", "Cari Kod", "Cari Adı", "Sipariş No",
"Temsilci", "Piyasa", "Onay Tarihi", "PB", "Tarih",
"Tutar", "Tutar (USD)", "Açıklama", "Cari Kod",
"Cari Adı",
"Temsilci",
"Piyasa",
"PB",
"Tutar",
"Tutar (USD)",
"Paketlenen Tutar",
"Paketlenen (USD)",
"Paketlenme (%)",
"USD Kur",
"Açıklama",
} }
for i, h := range headers { for i, h := range headers {
@@ -40,52 +64,108 @@ func OrderListExcelRoute(db *sql.DB) http.Handler {
f.SetCellValue(sheet, cell, h) f.SetCellValue(sheet, cell, h)
} }
rowIdx := 2 // ======================
// ROWS
// ======================
row := 2
for rows.Next() { for rows.Next() {
var o models.OrderList
_ = rows.Scan( // 🔴 15 KOLON = 15 DEĞİŞKEN
&o.OrderHeaderID, var (
&o.OrderNumber, id, no, date, code, name string
&o.OrderDate, rep, piyasa, cur string
&o.CurrAccCode,
&o.CurrAccDescription, total float64
&o.MusteriTemsilcisi, totalUSD float64
&o.Piyasa, packedAmount float64
&o.CreditableConfirmedDate, packedUSD float64
&o.DocCurrencyCode, packedRatePct float64
&o.TotalAmount, usdRate float64
&o.TotalAmountUSD,
&o.IsCreditableConfirmed, desc string
&o.Description,
&o.ExchangeRateUSD,
) )
f.SetSheetRow(sheet, fmt.Sprintf("A%d", rowIdx), &[]interface{}{ // 🔴 SELECT SIRASIYLA BİREBİR
o.OrderNumber, err := rows.Scan(
o.OrderDate, &id, // 1
o.CurrAccCode, &no, // 2
o.CurrAccDescription, &date, // 3
o.MusteriTemsilcisi, &code, // 4
o.Piyasa, &name, // 5
o.CreditableConfirmedDate, &rep, // 6
o.DocCurrencyCode, &piyasa, // 7
o.TotalAmount, &cur, // 8
o.TotalAmountUSD, &total, // 9
o.Description, &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( filename := fmt.Sprintf(
"order_list_%s.xlsx", "siparis_listesi_%s.xlsx",
time.Now().Format("2006-01-02_15-04"), time.Now().Format("20060102_150405"),
) )
w.Header().Set("Content-Type", // ======================
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") // RESPONSE
w.Header().Set("Content-Disposition", // ======================
"attachment; filename="+filename) 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 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() { for rows.Next() {
@@ -80,11 +80,14 @@ func OrderListRoute(mssql *sql.DB) http.Handler {
&o.TotalAmount, // 10 &o.TotalAmount, // 10
&o.TotalAmountUSD, // 11 &o.TotalAmountUSD, // 11
&o.PackedAmount, // 12
&o.PackedUSD, // 13
&o.PackedRatePct, // 14
&o.IsCreditableConfirmed, // 12 &o.IsCreditableConfirmed, // 15
&o.Description, // 13 &o.Description, // 16
&o.ExchangeRateUSD, // 14 &o.ExchangeRateUSD, // 17
) )
if err != nil { if err != nil {

View File

@@ -3,6 +3,8 @@ package utils
import ( import (
"bssapp-backend/auth" "bssapp-backend/auth"
"bssapp-backend/models" "bssapp-backend/models"
"strconv"
"strings"
) )
func UserFromClaims(c *auth.Claims) *models.User { func UserFromClaims(c *auth.Claims) *models.User {
@@ -10,8 +12,20 @@ func UserFromClaims(c *auth.Claims) *models.User {
return nil return nil
} }
v3Group := 0
if raw := strings.TrimSpace(c.V3UserGroup); raw != "" {
if parsed, err := strconv.Atoi(raw); err == nil {
v3Group = parsed
}
}
return &models.User{ return &models.User{
ID: int(c.ID), ID: int(c.ID),
Username: c.Username, 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 { useAuthStore } from 'stores/authStore'
import { usePermissionStore } from 'stores/permissionStore' 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 ================= */ /* ================= STORES ================= */

View File

@@ -1,5 +1,5 @@
<template> <template>
<q-page class="act-page with-bg"> <q-page v-if="canReadUser" class="act-page with-bg">
<!-- ======================================================= <!-- =======================================================
🔍 FILTER BAR 🔍 FILTER BAR
@@ -83,6 +83,7 @@
<!-- YENİ --> <!-- YENİ -->
<q-btn <q-btn
v-if="canUpdateUser"
outline outline
color="secondary" color="secondary"
label="Rol Değişimleri" label="Rol Değişimleri"
@@ -206,6 +207,12 @@
</q-table> </q-table>
</q-page> </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> </template>
@@ -217,11 +224,9 @@ import { useActivityLogStore } from 'src/stores/activityLogStore'
import { useAuthStore } from 'stores/authStore.js' import { useAuthStore } from 'stores/authStore.js'
import { usePermission } from 'src/composables/usePermission' import { usePermission } from 'src/composables/usePermission'
const { canRead, canWrite, canUpdate } = usePermission() const { canRead, canUpdate } = usePermission()
const canReadUser = canRead('user')
const canReadOrder = canRead('order') const canUpdateUser = canUpdate('user')
const canWriteOrder = canWrite('order')
const canUpdateOrder = canUpdate('order')
const diffDialog = ref(false) const diffDialog = ref(false)

View File

@@ -1,5 +1,5 @@
<template> <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 style="width:420px; max-width:90vw">
<q-card-section> <q-card-section>
@@ -45,6 +45,7 @@ class="bg-red-1 text-red q-mt-md"
<q-card-actions align="right"> <q-card-actions align="right">
<q-btn <q-btn
v-if="canUpdateSystem"
label="GÜNCELLE" label="GÜNCELLE"
color="primary" color="primary"
:loading="loading" :loading="loading"
@@ -54,6 +55,11 @@ color="primary"
</q-card-actions> </q-card-actions>
</q-card> </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> </q-page>
</template> </template>
@@ -64,11 +70,8 @@ import api from 'src/services/api'
import { useAuthStore } from 'stores/authStore.js' import { useAuthStore } from 'stores/authStore.js'
import { usePermission } from 'src/composables/usePermission' import { usePermission } from 'src/composables/usePermission'
const { canRead, canWrite, canUpdate } = usePermission() const { canUpdate } = usePermission()
const canUpdateSystem = canUpdate('system')
const canReadOrder = canRead('order')
const canWriteOrder = canWrite('order')
const canUpdateOrder = canUpdate('order')
const $q = useQuasar() const $q = useQuasar()
const auth = useAuthStore() const auth = useAuthStore()
@@ -117,4 +120,3 @@ async function submit () {
} }
} }
</script> </script>

View File

@@ -1,9 +1,17 @@
<template> <template>
<q-page class="flex flex-center"> <q-page v-if="canReadSystem" class="flex flex-center">
<p>DashBoard</p> <p>DashBoard</p>
</q-page> </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> </template>
<script setup> <script setup>
// buraya JS kodların gelecek import { usePermission } from 'src/composables/usePermission'
const { canRead } = usePermission()
const canReadSystem = canRead('system')
</script> </script>

View File

@@ -59,13 +59,6 @@ import { ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import api from 'src/services/api' import api from 'src/services/api'
import { useAuthStore } from 'stores/authStore.js' 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 router = useRouter()
const auth = useAuthStore() 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 { useAuthStore } from 'stores/authStore'
import { useQuasar } from 'quasar' import { useQuasar } from 'quasar'
import api from 'src/services/api' 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 router = useRouter()
const auth = useAuthStore() const auth = useAuthStore()

View File

@@ -60,13 +60,6 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useQuasar } from 'quasar' import { useQuasar } from 'quasar'
import { useMePasswordStore } from 'stores/mePasswordStore' 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 $q = useQuasar()
const store = useMePasswordStore() const store = useMePasswordStore()

View File

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

View File

@@ -1,12 +1,12 @@
<template> <template>
<q-page class="ol-page"> <q-page v-if="canReadOrder" class="ol-page">
<!-- 🔍 Sticky Filter --> <!-- 🍠Sticky Filter -->
<div class="ol-filter-bar"> <div class="ol-filter-bar">
<!-- 🔹 TEK SATIR FLEX --> <!-- 🹠TEK SATIR FLEX -->
<div class="ol-filter-row"> <div class="ol-filter-row">
<!-- 🔍 Arama --> <!-- 🍠Arama -->
<q-input <q-input
class="ol-filter-input ol-search" class="ol-filter-input ol-search"
dense dense
@@ -21,7 +21,7 @@
</template> </template>
</q-input> </q-input>
<!-- 🧾 Cari Kodu --> <!-- 🧾 Cari Kodu -->
<q-input <q-input
class="ol-filter-input" class="ol-filter-input"
dense dense
@@ -31,7 +31,7 @@
clearable clearable
/> />
<!-- 📅 Sipariş Tarihi --> <!-- ğŸ Sipariş Tarihi -->
<q-input <q-input
class="ol-filter-input" class="ol-filter-input"
dense dense
@@ -41,10 +41,11 @@
type="date" type="date"
/> />
<!-- 🔘 Butonlar --> <!-- 😠Butonlar -->
<div class="ol-filter-actions"> <div class="ol-filter-actions">
<q-btn <q-btn
v-if="canReadOrder"
label="Temizle" label="Temizle"
icon="clear" icon="clear"
color="grey-7" color="grey-7"
@@ -58,6 +59,7 @@
</q-btn> </q-btn>
<q-btn <q-btn
v-if="canReadOrder"
label="Yenile" label="Yenile"
color="primary" color="primary"
icon="refresh" icon="refresh"
@@ -66,6 +68,7 @@
/> />
<q-btn <q-btn
v-if="canExportOrder"
label="Excel'e Aktar" label="Excel'e Aktar"
icon="download" icon="download"
color="primary" color="primary"
@@ -77,7 +80,7 @@
</div> </div>
<!-- 💰 Toplam --> <!-- 🰠Toplam -->
<div class="ol-filter-total"> <div class="ol-filter-total">
Toplam Görünen Sipariş Tutarı (USD): Toplam Görünen Sipariş Tutarı (USD):
<strong> <strong>
@@ -91,7 +94,7 @@
</div> </div>
<!-- 📋 ORDER LIST TABLE --> <!-- ğŸ ORDER LIST TABLE -->
<q-table <q-table
title="Mevcut Siparişler" title="Mevcut Siparişler"
class="ol-table" class="ol-table"
@@ -103,16 +106,18 @@
:rows="store.filteredOrders" :rows="store.filteredOrders"
:columns="columns" :columns="columns"
:loading="store.loading" :loading="store.loading"
:table-style="tableStyle"
no-data-label="Sipariş bulunamadı" no-data-label="Sipariş bulunamadı"
:rows-per-page-options="[0]" :rows-per-page-options="[0]"
hide-bottom hide-bottom
> >
<!-- 📄 PDF + DURUM --> <!-- ğŸ PDF + DURUM -->
<template #body-cell-IsCreditableConfirmed="props"> <template #body-cell-IsCreditableConfirmed="props">
<q-td :props="props" class="text-center q-gutter-sm"> <q-td :props="props" class="text-center q-gutter-sm">
<q-btn <q-btn
v-if="canExportOrder"
icon="picture_as_pdf" icon="picture_as_pdf"
color="red" color="red"
flat flat
@@ -136,7 +141,7 @@
</q-td> </q-td>
</template> </template>
<!-- 📅 Tarih --> <!-- ğŸ Tarih -->
<template #body-cell-OrderDate="props"> <template #body-cell-OrderDate="props">
<q-td :props="props" class="text-center"> <q-td :props="props" class="text-center">
{{ formatDate(props.row.OrderDate) }} {{ formatDate(props.row.OrderDate) }}
@@ -149,7 +154,7 @@
</q-td> </q-td>
</template> </template>
<!-- 🧾 Cari Adı 2 Satır --> <!-- 🧾 Cari Adı â 2 Satır -->
<template #body-cell-CurrAccDescription="props"> <template #body-cell-CurrAccDescription="props">
<q-td :props="props" class="ol-col-cari ol-col-multiline"> <q-td :props="props" class="ol-col-cari ol-col-multiline">
{{ props.value }} {{ props.value }}
@@ -159,7 +164,7 @@
</q-td> </q-td>
</template> </template>
<!-- 📝 ıklama 5 Satır --> <!-- 🝠ıklama â 5 Satır -->
<template #body-cell-Description="props"> <template #body-cell-Description="props">
<q-td :props="props" class="ol-col-desc ol-col-multiline"> <q-td :props="props" class="ol-col-desc ol-col-multiline">
{{ props.value }} {{ props.value }}
@@ -169,10 +174,11 @@
</q-td> </q-td>
</template> </template>
<!-- 🔗 Aç --> <!-- ğŸ aç -->
<template #body-cell-select="props"> <template #body-cell-select="props">
<q-td :props="props" class="text-center"> <q-td :props="props" class="text-center">
<q-btn <q-btn
v-if="canUpdateOrder"
icon="open_in_new" icon="open_in_new"
color="primary" color="primary"
flat flat
@@ -180,7 +186,7 @@
dense dense
@click="selectOrder(props.row)" @click="selectOrder(props.row)"
> >
<q-tooltip>Siparişi Aç</q-tooltip> <q-tooltip>Siparişi aç</q-tooltip>
</q-btn> </q-btn>
</q-td> </q-td>
</template> </template>
@@ -193,10 +199,16 @@
</q-banner> </q-banner>
</q-page> </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> </template>
<script setup> <script setup>
import { onMounted, watch } from 'vue' import { computed, onMounted, watch } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useQuasar } from 'quasar' import { useQuasar } from 'quasar'
@@ -204,11 +216,12 @@ import { useOrderListStore } from 'src/stores/OrdernewListStore'
import { useAuthStore } from 'src/stores/authStore' import { useAuthStore } from 'src/stores/authStore'
import { usePermission } from 'src/composables/usePermission' import { usePermission } from 'src/composables/usePermission'
const { canRead, canWrite, canUpdate } = usePermission() const { canRead, canWrite, canUpdate, canExport } = usePermission()
const canReadOrder = canRead('order') const canReadOrder = canRead('order')
const canWriteOrder = canWrite('order') const canWriteOrder = canWrite('order')
const canUpdateOrder = canUpdate('order') const canUpdateOrder = canUpdate('order')
const canExportOrder = canExport('order')
/* ========================= /* =========================
INIT INIT
@@ -217,9 +230,13 @@ const canUpdateOrder = canUpdate('order')
const router = useRouter() const router = useRouter()
const $q = useQuasar() const $q = useQuasar()
// ⚠️ ÖNCE store tanımlanır // ⚠️ ÖNCE store tanımlanır
const store = useOrderListStore() const store = useOrderListStore()
const tableStyle = computed(() => ({
minWidth: $q.screen.lt.lg ? '1280px' : '100%'
}))
/* ========================= /* =========================
SEARCH DEBOUNCE SEARCH DEBOUNCE
========================= */ ========================= */
@@ -241,14 +258,15 @@ watch(
HELPERS HELPERS
========================= */ ========================= */
function exportExcel () { function exportExcel () {
if (!canExportOrder.value) {
$q.notify({ type: 'negative', message: 'Excel export yetkiniz yok', position: 'top-right' })
return
}
const auth = useAuthStore() const auth = useAuthStore()
if (!auth?.token) { if (!auth?.token) {
$q.notify({ $q.notify({ type: 'negative', message: 'Oturum bulunamadı', position: 'top-right' })
type: 'negative',
message: 'Oturum bulunamadı',
position: 'top-right'
})
return return
} }
@@ -261,19 +279,43 @@ function exportExcel () {
const url = `http://localhost:8080/api/orders/export?${params.toString()}` const url = `http://localhost:8080/api/orders/export?${params.toString()}`
fetch(url, { fetch(url, {
headers: { method: 'GET',
Authorization: `Bearer ${auth.token}` headers: { Authorization: `Bearer ${auth.token}` }
}
}) })
.then(res => res.blob()) .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(blob => { .then(blob => {
const link = document.createElement('a') const link = document.createElement('a')
link.href = URL.createObjectURL(blob) link.href = URL.createObjectURL(blob)
link.download = 'siparis_listesi.xlsx' link.download = 'siparis_listesi.xlsx'
link.click() 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) { function formatDate (s) {
if (!s) return '' if (!s) return ''
const [y, m, d] = s.split('-') const [y, m, d] = s.split('-')
@@ -285,12 +327,18 @@ function formatDate (s) {
========================= */ ========================= */
const columns = [ const columns = [
{ name: 'select', label: '', field: 'select', align: 'center', sortable: false }, {
name: 'select',
{ name: 'OrderNumber', label: 'Sipariş No', field: 'OrderNumber', align: 'left', sortable: true }, label: '',
{ name: 'OrderDate', label: 'Tarih', field: 'OrderDate', align: 'center', sortable: true }, field: 'select',
align: 'center',
{ name: 'CurrAccCode', label: 'Cari Kod', field: 'CurrAccCode', align: 'left', sortable: true }, 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', name: 'CurrAccDescription',
@@ -300,14 +348,13 @@ const columns = [
sortable: true, sortable: true,
classes: 'ol-col-cari', classes: 'ol-col-cari',
headerClasses: '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, style: 'width:78px; min-width:78px;', headerStyle: 'width:78px; min-width:78px;' },
{ name: 'MusteriTemsilcisi', label: 'Temsilci', field: 'MusteriTemsilcisi', align: 'left', sortable: true }, { name: 'Piyasa', label: 'Piyasa', field: 'Piyasa', align: 'left', sortable: true, style: 'width:74px; min-width:74px;', headerStyle: 'width:74px; min-width:74px;' },
{ name: 'Piyasa', label: 'Piyasa', field: 'Piyasa', align: 'left', sortable: true }, { 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: 'CreditableConfirmedDate', label: 'Onay', field: 'CreditableConfirmedDate', align: 'center', sortable: true },
{ name: 'DocCurrencyCode', label: 'PB', field: 'DocCurrencyCode', align: 'center', sortable: true },
{ {
name: 'TotalAmount', name: 'TotalAmount',
@@ -315,6 +362,8 @@ const columns = [
field: 'TotalAmount', field: 'TotalAmount',
align: 'right', align: 'right',
sortable: true, sortable: true,
style: 'width:94px; min-width:94px;',
headerStyle: 'width:94px; min-width:94px;',
format: (val, row) => format: (val, row) =>
Number(val || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) + Number(val || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) +
' ' + row.DocCurrencyCode ' ' + row.DocCurrencyCode
@@ -326,11 +375,49 @@ const columns = [
field: 'TotalAmountUSD', field: 'TotalAmountUSD',
align: 'right', align: 'right',
sortable: true, sortable: true,
style: 'width:96px; min-width:96px;',
headerStyle: 'width:96px; min-width:96px;',
format: val => format: val =>
Number(val || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) + ' USD' 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', name: 'Description',
@@ -340,10 +427,10 @@ const columns = [
sortable: false, sortable: false,
classes: 'ol-col-desc', classes: 'ol-col-desc',
headerClasses: '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, style: 'width:44px; min-width:44px;', headerStyle: 'width:44px; min-width:44px;' }
{ name: 'pdf', label: 'PDF', field: 'pdf', align: 'center', sortable: false }
] ]
/* ========================= /* =========================
@@ -351,6 +438,11 @@ const columns = [
========================= */ ========================= */
function selectOrder (row) { function selectOrder (row) {
if (!canUpdateOrder.value) {
$q.notify({ type: 'negative', message: 'Siparis guncelleme yetkiniz yok' })
return
}
if (!row?.OrderHeaderID) { if (!row?.OrderHeaderID) {
$q.notify({ type: 'warning', message: 'OrderHeaderID bulunamadı' }) $q.notify({ type: 'warning', message: 'OrderHeaderID bulunamadı' })
return return
@@ -364,6 +456,11 @@ function selectOrder (row) {
} }
async function printPDF (row) { async function printPDF (row) {
if (!canExportOrder.value) {
$q.notify({ type: 'negative', message: 'PDF export yetkiniz yok' })
return
}
if (!row?.OrderHeaderID) return if (!row?.OrderHeaderID) return
const token = useAuthStore().token const token = useAuthStore().token
@@ -402,7 +499,48 @@ function clearFilters () {
========================= */ ========================= */
onMounted(() => { onMounted(() => {
store.fetchOrders() if (canReadOrder.value) {
store.fetchOrders()
}
}) })
</script> </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> <template>
<q-page padding> <q-page v-if="canReadSystem" padding>
<div class="text-h6 q-mb-md"> <div class="text-h6 q-mb-md">
Rol + Departman Yetkilendirme Rol + Departman Yetkilendirme
@@ -76,6 +76,7 @@
<div class="q-mt-md"> <div class="q-mt-md">
<q-btn <q-btn
v-if="canUpdateUser"
color="primary" color="primary"
icon="save" icon="save"
label="Kaydet" label="Kaydet"
@@ -86,6 +87,12 @@
</div> </div>
</q-page> </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> </template>
@@ -96,11 +103,9 @@ import { Notify } from 'quasar'
import api from 'src/services/api' import api from 'src/services/api'
import { usePermission } from 'src/composables/usePermission' import { usePermission } from 'src/composables/usePermission'
const { canRead, canWrite, canUpdate } = usePermission() const { canRead, canUpdate } = usePermission()
const canReadSystem = canRead('system')
const canReadOrder = canRead('order') const canUpdateUser = canUpdate('user')
const canWriteOrder = canWrite('order')
const canUpdateOrder = canUpdate('order')
/* ================= STATE ================= */ /* ================= STATE ================= */

View File

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

View File

@@ -1,5 +1,5 @@
<template> <template>
<q-page class="q-pa-md"> <q-page v-if="canReadOrder" class="q-pa-md">
<!-- ===================================================== <!-- =====================================================
🔹 BAŞLIK + AKSİYONLAR 🔹 BAŞLIK + AKSİYONLAR
@@ -10,6 +10,7 @@
</div> </div>
<q-btn <q-btn
v-if="canWriteOrder"
color="primary" color="primary"
icon="add" icon="add"
label="Yeni İş Emri" label="Yeni İş Emri"
@@ -83,6 +84,12 @@
</q-table> </q-table>
</q-page> </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> </template>
<script setup> <script setup>
@@ -90,11 +97,10 @@ import { ref, computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { usePermission } from 'src/composables/usePermission' import { usePermission } from 'src/composables/usePermission'
const { canRead, canWrite, canUpdate } = usePermission() const { canRead, canWrite } = usePermission()
const canReadOrder = canRead('order') const canReadOrder = canRead('order')
const canWriteOrder = canWrite('order') const canWriteOrder = canWrite('order')
const canUpdateOrder = canUpdate('order')
const router = useRouter() const router = useRouter()
@@ -192,10 +198,12 @@ const filteredRows = computed(() => {
// 🔹 AKSİYONLAR // 🔹 AKSİYONLAR
// ===================================================== // =====================================================
function goNew () { function goNew () {
if (!canWriteOrder.value) return
router.push('/app/production-work-orders/new') router.push('/app/production-work-orders/new')
} }
function goView (evt, row) { function goView (evt, row) {
if (!canReadOrder.value) return
router.push(`/app/production-work-orders/view/${row.id}`) 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 api, { post } from 'src/services/api'
import { useAuthStore } from 'stores/authStore.js' 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 */ /* INIT */
@@ -261,4 +254,3 @@ async function submit () {
} }
} }
</script> </script>

View File

@@ -1,5 +1,5 @@
<template> <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 <q-spinner
color="primary" color="primary"
@@ -8,7 +8,7 @@
</div> </div>
<q-page class="permissions-page"> <q-page v-if="canUpdateUser" class="permissions-page">
<!-- ================= STICKY STACK ================= --> <!-- ================= STICKY STACK ================= -->
<div class="sticky-stack"> <div class="sticky-stack">
@@ -69,6 +69,7 @@
</div> </div>
<q-btn <q-btn
v-if="canUpdateUser"
color="primary" color="primary"
icon="save" icon="save"
label="Kaydet" label="Kaydet"
@@ -165,6 +166,12 @@
</div> </div>
</q-page> </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> </template>
@@ -175,11 +182,8 @@ import { Notify } from 'quasar'
import api from 'src/services/api' import api from 'src/services/api'
import { usePermission } from 'src/composables/usePermission' import { usePermission } from 'src/composables/usePermission'
const { canRead, canWrite, canUpdate } = usePermission() const { canUpdate } = usePermission()
const canUpdateUser = canUpdate('user')
const canReadOrder = canRead('order')
const canWriteOrder = canWrite('order')
const canUpdateOrder = canUpdate('order')
/* ================= STATE ================= */ /* ================= STATE ================= */
@@ -432,4 +436,3 @@ watch(deptCode, v => console.log('DEPT >>>', v))

View File

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

View File

@@ -1,5 +1,5 @@
<template> <template>
<q-page class="q-pa-md page-col"> <q-page v-if="canReadFinance" class="q-pa-md page-col">
<!-- 🔹 Cari Kod / İsim (sabit) --> <!-- 🔹 Cari Kod / İsim (sabit) -->
<div class="filter-sticky"> <div class="filter-sticky">
@@ -135,6 +135,7 @@
<!-- PDF Yazdır Dropdown --> <!-- PDF Yazdır Dropdown -->
<q-btn-dropdown <q-btn-dropdown
v-if="canExportFinance"
flat flat
color="red" color="red"
icon="picture_as_pdf" icon="picture_as_pdf"
@@ -258,6 +259,12 @@
</q-table> </q-table>
</div> </div>
</q-page> </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> </template>
<script setup> <script setup>
@@ -270,11 +277,9 @@ import { useDownloadstpdfStore } from 'src/stores/downloadstpdfStore'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { usePermission } from 'src/composables/usePermission' import { usePermission } from 'src/composables/usePermission'
const { canRead, canWrite, canUpdate } = usePermission() const { canRead, canExport } = usePermission()
const canReadFinance = canRead('finance')
const canReadOrder = canRead('order') const canExportFinance = canExport('finance')
const canWriteOrder = canWrite('order')
const canUpdateOrder = canUpdate('order')
const $q = useQuasar() const $q = useQuasar()
@@ -436,6 +441,15 @@ function toggleLeftCols() {
/* 🔹 PDF İndirme Butonuna bağla */ /* 🔹 PDF İndirme Butonuna bağla */
async function handleDownload() { 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) console.log(" [DEBUG] handleDownload:", selectedCari.value, dateFrom.value, dateTo.value)
if (!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() const downloadstHeadStore = useDownloadstHeadStore()
async function CurrheadDownload() { 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) console.log(" [DEBUG] CurrheadDownload:", selectedCari.value, dateFrom.value, dateTo.value)
if (!selectedCari.value || !dateFrom.value || !dateTo.value) { if (!selectedCari.value || !dateFrom.value || !dateTo.value) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
<template> <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" /> <q-spinner color="primary" size="48px" />
</div> </div>
<q-page class="permissions-page"> <q-page v-if="canUpdateUser" class="permissions-page">
<div class="sticky-stack"> <div class="sticky-stack">
@@ -36,6 +36,7 @@
</div> </div>
<q-btn <q-btn
v-if="canUpdateUser"
color="primary" color="primary"
icon="save" icon="save"
label="Kaydet" label="Kaydet"
@@ -115,6 +116,12 @@
</q-page> </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> </template>
<script setup> <script setup>
@@ -123,11 +130,8 @@ import { Notify } from 'quasar'
import api from 'src/services/api' import api from 'src/services/api'
import { usePermission } from 'src/composables/usePermission' import { usePermission } from 'src/composables/usePermission'
const { canRead, canWrite, canUpdate } = usePermission() const { canUpdate } = usePermission()
const canUpdateUser = canUpdate('user')
const canReadOrder = canRead('order')
const canWriteOrder = canWrite('order')
const canUpdateOrder = canUpdate('order')
/* ================= STATE ================= */ /* ================= STATE ================= */

View File

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

View File

@@ -1,5 +1,5 @@
<template> <template>
<q-page class="q-pa-md page-col"> <q-page v-if="canReadFinance" class="q-pa-md page-col">
<!-- 🔹 Cari Kod / İsim (sabit) --> <!-- 🔹 Cari Kod / İsim (sabit) -->
<div class="filter-sticky"> <div class="filter-sticky">
@@ -135,6 +135,7 @@
<!-- PDF Yazdır Dropdown --> <!-- PDF Yazdır Dropdown -->
<q-btn-dropdown <q-btn-dropdown
v-if="canExportFinance"
flat flat
color="red" color="red"
icon="picture_as_pdf" icon="picture_as_pdf"
@@ -258,6 +259,12 @@
</q-table> </q-table>
</div> </div>
</q-page> </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> </template>
<script setup> <script setup>
@@ -270,11 +277,9 @@ import { useDownloadstpdfStore } from 'src/stores/downloadstpdfStore'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { usePermission } from 'src/composables/usePermission' import { usePermission } from 'src/composables/usePermission'
const { canRead, canWrite, canUpdate } = usePermission() const { canRead, canExport } = usePermission()
const canReadFinance = canRead('finance')
const canReadOrder = canRead('order') const canExportFinance = canExport('finance')
const canWriteOrder = canWrite('order')
const canUpdateOrder = canUpdate('order')
const $q = useQuasar() const $q = useQuasar()
@@ -458,6 +463,15 @@ function toggleLeftCols() {
/* 🔹 PDF İndirme Butonuna bağla */ /* 🔹 PDF İndirme Butonuna bağla */
async function handleDownload() { 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) console.log(" [DEBUG] handleDownload:", selectedCari.value, dateFrom.value, dateTo.value)
if (!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() const downloadstHeadStore = useDownloadstHeadStore()
async function CurrheadDownload() { 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) console.log(" [DEBUG] CurrheadDownload:", selectedCari.value, dateFrom.value, dateTo.value)
if (!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') 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 // 🧪 PRE-VALIDATE — prItemVariant ön kontrol
// - invalid varsa CREATE/UPDATE ÇALIŞMAZ // - invalid varsa CREATE/UPDATE ÇALIŞMAZ
@@ -3337,4 +3383,3 @@ export const sharedOrderEntryRefs = {
} }