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

74
svc/queries/account.go Normal file
View File

@@ -0,0 +1,74 @@
package queries
import (
"context"
"fmt"
"log"
"strings"
"bssapp-backend/db"
"bssapp-backend/internal/authz"
"bssapp-backend/models"
)
func GetAccounts(ctx context.Context) ([]models.Account, error) {
piyasaFilter := authz.BuildMSSQLPiyasaFilter(ctx, "f2.CustomerAtt01")
if strings.TrimSpace(piyasaFilter) == "" {
piyasaFilter = "1=1"
}
query := fmt.Sprintf(`
SELECT
x.AccountCode,
MAX(x.AccountName) AS AccountName
FROM (
SELECT
LEFT(b.CurrAccCode, 8) AS AccountCode,
COALESCE(d.CurrAccDescription, '') AS AccountName
FROM trCurrAccBook b
LEFT JOIN cdCurrAccDesc d
ON d.CurrAccCode = b.CurrAccCode
JOIN CustomerAttributesFilter f2
ON f2.CurrAccCode = b.CurrAccCode
WHERE %s
) x
GROUP BY x.AccountCode
ORDER BY x.AccountCode
`, piyasaFilter)
log.Println("🔎 ACCOUNT PIYASA FILTER =", piyasaFilter)
log.Println("🔎 ACCOUNT QUERY =", query)
rows, err := db.MssqlDB.Query(query)
if err != nil {
return nil, fmt.Errorf("MSSQL query error: %w", err)
}
defer rows.Close()
var accounts []models.Account
for rows.Next() {
var acc models.Account
if err := rows.Scan(
&acc.AccountCode,
&acc.AccountName,
); err != nil {
return nil, err
}
if len(acc.AccountCode) >= 4 {
acc.DisplayCode =
strings.TrimSpace(acc.AccountCode[:3] + " " + acc.AccountCode[3:])
} else {
acc.DisplayCode = acc.AccountCode
}
accounts = append(accounts, acc)
}
return accounts, rows.Err()
}

View File

@@ -0,0 +1,64 @@
package queries
import (
"bssapp-backend/models"
"database/sql"
"sync"
"time"
)
/* ===============================
CACHE STRUCT
================================ */
type currencyCacheItem struct {
data *models.TodayCurrencyV3
expiresAt time.Time
}
var (
currencyCache = make(map[string]currencyCacheItem)
cacheMutex sync.RWMutex
cacheTTL = 5 * time.Minute
)
/* ===============================
MAIN CACHE FUNC
================================ */
func GetCachedCurrencyV3(db *sql.DB, code string) (*models.TodayCurrencyV3, error) {
now := time.Now()
/* ---------- READ CACHE ---------- */
cacheMutex.RLock()
item, ok := currencyCache[code]
if ok && now.Before(item.expiresAt) {
cacheMutex.RUnlock()
return item.data, nil
}
cacheMutex.RUnlock()
/* ---------- FETCH DB ---------- */
data, err := GetTodayCurrencyV3(db, code)
if err != nil {
return nil, err
}
/* ---------- WRITE CACHE ---------- */
cacheMutex.Lock()
currencyCache[code] = currencyCacheItem{
data: data,
expiresAt: now.Add(cacheTTL),
}
cacheMutex.Unlock()
return data, nil
}

114
svc/queries/customerlist.go Normal file
View File

@@ -0,0 +1,114 @@
package queries
import (
"context"
"fmt"
"bssapp-backend/db"
"bssapp-backend/internal/authz"
"bssapp-backend/models"
)
func GetCustomerList(ctx context.Context) ([]models.CustomerList, error) {
piyasaFilter := authz.BuildMSSQLPiyasaFilter(
ctx,
"f.CustomerAtt01",
)
query := fmt.Sprintf(`
SELECT
c.CurrAccTypeCode,
c.CurrAccCode,
ISNULL(d.CurrAccDescription, ''),
dbo.HG_Temizlik(
ISNULL((
SELECT AttributeDescription
FROM cdCurrAccAttributeDesc WITH(NOLOCK)
WHERE CurrAccTypeCode = 3
AND AttributeTypeCode = 8
AND AttributeCode = f.CustomerAtt08
AND LangCode = 'TR'
), SPACE(0))
),
dbo.HG_Temizlik(
ISNULL((
SELECT AttributeDescription
FROM cdCurrAccAttributeDesc WITH(NOLOCK)
WHERE CurrAccTypeCode = 3
AND AttributeTypeCode = 1
AND AttributeCode = f.CustomerAtt01
AND LangCode = 'TR'
), SPACE(0))
),
dbo.HG_Temizlik(
ISNULL((
SELECT AttributeDescription
FROM cdCurrAccAttributeDesc WITH(NOLOCK)
WHERE CurrAccTypeCode = 3
AND AttributeTypeCode = 2
AND AttributeCode = f.CustomerAtt02
AND LangCode = 'TR'
), SPACE(0))
),
dbo.HG_Temizlik(
ISNULL((
SELECT AttributeDescription
FROM cdCurrAccAttributeDesc WITH(NOLOCK)
WHERE CurrAccTypeCode = 3
AND AttributeTypeCode = 5
AND AttributeCode = f.CustomerAtt05
AND LangCode = 'TR'
), SPACE(0))
),
ISNULL(c.CurrencyCode, '')
FROM cdCurrAcc c
LEFT JOIN cdCurrAccDesc d
ON c.CurrAccCode = d.CurrAccCode
LEFT JOIN CustomerAttributesFilter f
ON c.CurrAccCode = f.CurrAccCode
WHERE
c.CompanyCode = 1
AND c.CurrAccTypeCode = 3
AND c.IsBlocked = 0
AND %s
ORDER BY d.CurrAccDescription
`, piyasaFilter)
rows, err := db.MssqlDB.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()
var list []models.CustomerList
for rows.Next() {
var c models.CustomerList
if err := rows.Scan(
&c.CurrAccTypeCode,
&c.Cari_Kod,
&c.Cari_Ad,
&c.Musteri_Ana_Grubu,
&c.Piyasa,
&c.Musteri_Temsilcisi,
&c.Ulke,
&c.Doviz_cinsi,
); err != nil {
return nil, err
}
list = append(list, c)
}
return list, rows.Err()
}

View File

@@ -0,0 +1,52 @@
package queries
import (
"database/sql"
"fmt"
)
func GetOrderListExcel(
db *sql.DB,
search string,
currAcc string,
orderDate string,
) (*sql.Rows, error) {
q := OrderListBaseQuery + " AND 1=1 "
args := []interface{}{}
// SEARCH
if search != "" {
q += `
AND (
LOWER(h.OrderNumber) LIKE LOWER(@p1) OR
LOWER(h.CurrAccCode) LIKE LOWER(@p1) OR
LOWER(ca.CurrAccDescription) LIKE LOWER(@p1) OR
LOWER(h.Description) LIKE LOWER(@p1) OR
LOWER(mt.AttributeDescription) LIKE LOWER(@p1) OR
LOWER(py.AttributeDescription) LIKE LOWER(@p1)
)
`
args = append(args, "%"+search+"%")
}
// CURRACC
if currAcc != "" {
q += fmt.Sprintf(" AND h.CurrAccCode = @p%d ", len(args)+1)
args = append(args, currAcc)
}
// DATE
if orderDate != "" {
q += fmt.Sprintf(
" AND CONVERT(varchar, h.OrderDate, 23) = @p%d ",
len(args)+1,
)
args = append(args, orderDate)
}
// ORDER BY SONDA
q += " ORDER BY h.CreatedDate DESC "
return db.Query(q, args...)
}

174
svc/queries/helpers.go Normal file
View File

@@ -0,0 +1,174 @@
package queries
import (
"bssapp-backend/models"
"database/sql"
"fmt"
"github.com/google/uuid"
"log"
"strings"
"time"
)
// ============================================================
// 🔥 UNIVERSAL DATETIME PARSER
// ============================================================
func parseDateTime(str string) (time.Time, error) {
layouts := []string{
"2006-01-02 15:04:05", // "2025-11-21 12:37:21"
time.RFC3339, // "2025-11-21T12:37:21Z"
"2006-01-02", // "2025-11-21"
}
for _, layout := range layouts {
if t, err := time.Parse(layout, str); err == nil {
return t, nil
}
}
return time.Time{}, fmt.Errorf("datetime parse edilemedi: %s", str)
}
// ============================================================
// 📌 DATE (YYYY-MM-DD) → sql.NullTime
// ============================================================
func nullableDateString(ns models.NullString) sql.NullTime {
if ns.Valid && strings.TrimSpace(ns.String) != "" {
t, err := time.Parse("2006-01-02", ns.String)
if err == nil {
return sql.NullTime{Valid: true, Time: t}
}
}
return sql.NullTime{}
}
// ============================================================
// 📌 TIME (HH:mm:ss) → sql.NullString
// ============================================================
func nullableTimeString(ns models.NullString) sql.NullString {
if ns.Valid && strings.TrimSpace(ns.String) != "" {
return sql.NullString{String: ns.String, Valid: true}
}
return sql.NullString{}
}
// ============================================================
// 📌 DATETIME (CustomTime → sql.NullTime)
// ============================================================
func nullableDateTime(ct models.CustomTime, fallback time.Time) sql.NullTime {
if ct.Valid && !ct.Time.IsZero() {
return sql.NullTime{Valid: true, Time: ct.Time}
}
return sql.NullTime{Valid: true, Time: fallback}
}
// ============================================================
// 📌 DATETIME (NullTime → sql.NullTime)
// ============================================================
func nullableTime(nt models.NullTime, fallback time.Time) sql.NullTime {
if nt.Valid && !nt.Time.IsZero() {
return sql.NullTime{Valid: true, Time: nt.Time}
}
return sql.NullTime{Valid: true, Time: fallback}
}
// ============================================================
// 📌 DATETIME (NullString → sql.NullTime)
// ============================================================
func nullableDateTimeString(ns models.NullString, fallback time.Time) sql.NullTime {
if !ns.Valid || strings.TrimSpace(ns.String) == "" {
return sql.NullTime{Time: fallback, Valid: true}
}
t, err := parseDateTime(ns.String)
if err != nil {
return sql.NullTime{Time: fallback, Valid: true}
}
return sql.NullTime{Time: t, Valid: true}
}
// ============================================================
// 📌 STRING → sql.NullString
// ============================================================
func nullableString(val models.NullString, fallback string) sql.NullString {
if val.Valid && strings.TrimSpace(val.String) != "" {
return sql.NullString{String: val.String, Valid: true}
}
return sql.NullString{String: fallback, Valid: true}
}
// ============================================================
// 📌 GUID (UNIQUEIDENTIFIER) → interface{}
// ============================================================
func nullableUUID(v interface{}) interface{} {
switch x := v.(type) {
// 1⃣ models.NullUUID → direkt geri dön
case models.NullUUID:
if !x.Valid {
return nil
}
return x.UUID
// 2⃣ models.NullString → GUID parse et
case models.NullString:
if !x.Valid || strings.TrimSpace(x.String) == "" {
return nil
}
id, err := uuid.Parse(x.String)
if err != nil {
return nil
}
return id
// 3⃣ string → GUID parse et
case string:
id, err := uuid.Parse(x)
if err != nil {
return nil
}
return id
}
return nil
}
// ============================================================
// 📌 NUMERIC → sql.NullX
// ============================================================
func nullableFloat64(val models.NullFloat64, fallback float64) sql.NullFloat64 {
if val.Valid {
return sql.NullFloat64{Float64: val.Float64, Valid: true}
}
return sql.NullFloat64{Float64: fallback, Valid: true}
}
func nullableInt16(val models.NullInt16, fallback int16) sql.NullInt16 {
if val.Valid {
return sql.NullInt16{Int16: val.Int16, Valid: true}
}
return sql.NullInt16{Int16: fallback, Valid: true}
}
func nullableInt32(val models.NullInt32, fallback int32) sql.NullInt32 {
if val.Valid {
return sql.NullInt32{Int32: val.Int32, Valid: true}
}
return sql.NullInt32{Int32: fallback, Valid: true}
}
func nullableInt32ToInt16(val models.NullInt32, fallback int16) sql.NullInt16 {
if val.Valid {
return sql.NullInt16{Int16: int16(val.Int32), Valid: true}
}
return sql.NullInt16{Int16: fallback, Valid: true}
}
// ============================================================
// 📌 BOOL → sql.NullBool
// ============================================================
func nullableBool(val models.NullBool, fallback bool) sql.NullBool {
if val.Valid {
return sql.NullBool{Bool: val.Bool, Valid: true}
}
return sql.NullBool{Bool: fallback, Valid: true}
}
var logger = log.Default()

View File

@@ -0,0 +1,93 @@
package queries
// 🔹 Ürüne göre stok detay sorgusu
const GetInventoryProduct = `
-- 🔹 Ürüne göre stok detay sorgusu (Nebim V3 uyumlu, parametre güvenli)
SELECT
bsItemTypeDesc.ItemTypeDescription AS InventoryType,
inv.WarehouseCode AS Depo_Kodu,
wh.WarehouseDescription AS Depo_Adi,
inv.ItemCode AS Urun_Kodu,
ISNULL(descItem.ItemDescription, '') AS Madde_Aciklamasi,
inv.ColorCode AS Renk_Kodu,
ISNULL(descColor.ColorDescription, '') AS Renk_Aciklamasi,
inv.ItemDim1Code AS Beden,
attr.ProductAtt01 AS Urun_Grubu,
attr.ProductAtt02 AS Urun_Alt_Grubu,
attr.ProductAtt41 AS Kisa_Karisim,
attr.ProductAtt42 AS SERI,
attr.ProductAtt43 AS FASON_ISCLIK,
attr.ProductAtt45 AS ASKILI_YAN,
inv.ItemDim2Code AS YAKA,
attr.ProductAtt44 AS GARSON_YETISKIN,
attr.ProductAtt10 AS MarkaKodu,
ROUND(
SUM(inv.InventoryQty1)
- (SUM(inv.ReserveQty1) + SUM(inv.DispOrderQty1) + SUM(inv.PickingQty1)),
cdUnitOfMeasure.RoundDigit
) AS Kullanilabilir_Envanter
FROM cdItem WITH (NOLOCK)
JOIN cdUnitOfMeasure WITH (NOLOCK)
ON cdItem.UnitOfMeasureCode1 = cdUnitOfMeasure.UnitOfMeasureCode
JOIN (
SELECT
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
SUM(CASE WHEN SourceTable = 'PickingStates' THEN Qty1 ELSE 0 END) AS PickingQty1,
SUM(CASE WHEN SourceTable = 'ReserveStates' THEN Qty1 ELSE 0 END) AS ReserveQty1,
SUM(CASE WHEN SourceTable = 'DispOrderStates' THEN Qty1 ELSE 0 END) AS DispOrderQty1,
SUM(CASE WHEN SourceTable = 'trStock' THEN (In_Qty1 - Out_Qty1) ELSE 0 END) AS InventoryQty1
FROM (
SELECT 'PickingStates' AS SourceTable, CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, Qty1, 0 AS In_Qty1, 0 AS Out_Qty1
FROM PickingStates WITH (NOLOCK)
UNION ALL
SELECT 'ReserveStates', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, Qty1, 0, 0
FROM ReserveStates WITH (NOLOCK)
UNION ALL
SELECT 'DispOrderStates', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, Qty1, 0, 0
FROM DispOrderStates WITH (NOLOCK)
UNION ALL
SELECT 'trStock', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, 0, SUM(In_Qty1), SUM(Out_Qty1)
FROM trStock WITH (NOLOCK)
GROUP BY CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code
) AS src
GROUP BY CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code
) AS inv
ON cdItem.ItemTypeCode = inv.ItemTypeCode
AND cdItem.ItemCode = inv.ItemCode
LEFT JOIN ProductAttributesFilter AS attr WITH (NOLOCK)
ON attr.ItemCode = inv.ItemCode
LEFT JOIN bsItemTypeDesc WITH (NOLOCK)
ON bsItemTypeDesc.ItemTypeCode = inv.ItemTypeCode
AND bsItemTypeDesc.LangCode = 'TR'
LEFT JOIN cdWarehouseDesc AS wh WITH (NOLOCK)
ON wh.WarehouseCode = inv.WarehouseCode
LEFT JOIN cdItemDesc AS descItem WITH (NOLOCK)
ON descItem.ItemTypeCode = inv.ItemTypeCode
AND descItem.ItemCode = inv.ItemCode
AND descItem.LangCode = 'TR'
LEFT JOIN cdColorDesc AS descColor WITH (NOLOCK)
ON descColor.ColorCode = inv.ColorCode
AND descColor.LangCode = 'TR'
WHERE
inv.ItemTypeCode IN (1)
AND inv.WarehouseCode IN ('1-0-12','1-0-21','1-0-10','1-0-2','1-1-3','1-2-4','1-2-5','')
AND inv.InventoryQty1 >= 0
AND cdItem.IsBlocked = 0
AND inv.ItemCode = @p1 -- ✅ doğrudan parametre, DECLARE yok
GROUP BY
inv.CompanyCode, inv.OfficeCode, inv.StoreTypeCode, inv.StoreCode,
inv.WarehouseCode, inv.ItemTypeCode, inv.ItemCode, inv.ColorCode,
inv.ItemDim1Code, inv.ItemDim2Code, inv.ItemDim3Code,
cdUnitOfMeasure.RoundDigit, attr.ProductAtt01, attr.ProductAtt02,
attr.ProductAtt41, attr.ProductAtt42, attr.ProductAtt43, attr.ProductAtt44,
attr.ProductAtt45, attr.ProductAtt10, bsItemTypeDesc.ItemTypeDescription,
wh.WarehouseDescription, descItem.ItemDescription, descColor.ColorDescription;
`

280
svc/queries/order_get.go Normal file
View File

@@ -0,0 +1,280 @@
package queries
import (
"bssapp-backend/db"
"bssapp-backend/models"
"database/sql"
"errors"
"fmt"
)
// GetOrderByID — Sipariş başlığı (header) ve satırlarını (lines) getirir.
func GetOrderByID(orderID string) (*models.OrderHeader, []models.OrderDetail, error) {
conn := db.GetDB()
logger.Printf("🧾 [GetOrderByID] begin • id=%s", orderID)
// =====================================================
// HEADER (Cari adı join'li)
// =====================================================
var header models.OrderHeader
qHeader := `
SELECT
CAST(h.OrderHeaderID AS varchar(36)) AS OrderHeaderID,
h.OrderTypeCode,
h.ProcessCode,
h.OrderNumber,
h.IsCancelOrder,
h.OrderDate,
h.OrderTime,
h.DocumentNumber,
h.PaymentTerm,
h.AverageDueDate,
h.Description,
h.InternalDescription,
h.CurrAccTypeCode,
h.CurrAccCode,
d.CurrAccDescription,
h.SubCurrAccID,
h.ContactID,
h.ShipmentMethodCode,
h.ShippingPostalAddressID,
h.BillingPostalAddressID,
h.GuarantorContactID,
h.GuarantorContactID2,
h.RoundsmanCode,
h.DeliveryCompanyCode,
h.TaxTypeCode,
h.WithHoldingTaxTypeCode,
h.DOVCode,
h.TaxExemptionCode,
h.CompanyCode,
h.OfficeCode,
h.StoreTypeCode,
h.StoreCode,
h.POSTerminalID,
h.WarehouseCode,
h.ToWarehouseCode,
h.OrdererCompanyCode,
h.OrdererOfficeCode,
h.OrdererStoreCode,
h.GLTypeCode,
h.DocCurrencyCode,
h.LocalCurrencyCode,
h.ExchangeRate,
h.TDisRate1,
h.TDisRate2,
h.TDisRate3,
h.TDisRate4,
h.TDisRate5,
h.DiscountReasonCode,
h.SurplusOrderQtyToleranceRate,
h.ImportFileNumber,
h.ExportFileNumber,
h.IncotermCode1,
h.IncotermCode2,
h.LettersOfCreditNumber,
h.PaymentMethodCode,
h.IsInclutedVat,
h.IsCreditSale,
h.IsCreditableConfirmed,
h.CreditableConfirmedUser,
h.CreditableConfirmedDate,
h.IsSalesViaInternet,
h.IsSuspended,
h.IsCompleted,
h.IsPrinted,
h.IsLocked,
h.UserLocked,
h.IsClosed,
h.ApplicationCode,
h.ApplicationID,
h.CreatedUserName,
h.CreatedDate,
h.LastUpdatedUserName,
h.LastUpdatedDate,
h.IsProposalBased
FROM BAGGI_V3.dbo.trOrderHeader AS h
LEFT JOIN BAGGI_V3.dbo.cdCurrAccDesc AS d
ON h.CurrAccCode = d.CurrAccCode
WHERE h.OrderHeaderID = @p1;
`
err := conn.QueryRow(qHeader, orderID).Scan(
&header.OrderHeaderID,
&header.OrderTypeCode,
&header.ProcessCode,
&header.OrderNumber,
&header.IsCancelOrder,
&header.OrderDate,
&header.OrderTime,
&header.DocumentNumber,
&header.PaymentTerm,
&header.AverageDueDate,
&header.Description,
&header.InternalDescription,
&header.CurrAccTypeCode,
&header.CurrAccCode,
&header.CurrAccDescription,
&header.SubCurrAccID,
&header.ContactID,
&header.ShipmentMethodCode,
&header.ShippingPostalAddressID,
&header.BillingPostalAddressID,
&header.GuarantorContactID,
&header.GuarantorContactID2,
&header.RoundsmanCode,
&header.DeliveryCompanyCode,
&header.TaxTypeCode,
&header.WithHoldingTaxTypeCode,
&header.DOVCode,
&header.TaxExemptionCode,
&header.CompanyCode,
&header.OfficeCode,
&header.StoreTypeCode,
&header.StoreCode,
&header.POSTerminalID,
&header.WarehouseCode,
&header.ToWarehouseCode,
&header.OrdererCompanyCode,
&header.OrdererOfficeCode,
&header.OrdererStoreCode,
&header.GLTypeCode,
&header.DocCurrencyCode,
&header.LocalCurrencyCode,
&header.ExchangeRate,
&header.TDisRate1,
&header.TDisRate2,
&header.TDisRate3,
&header.TDisRate4,
&header.TDisRate5,
&header.DiscountReasonCode,
&header.SurplusOrderQtyToleranceRate,
&header.ImportFileNumber,
&header.ExportFileNumber,
&header.IncotermCode1,
&header.IncotermCode2,
&header.LettersOfCreditNumber,
&header.PaymentMethodCode,
&header.IsInclutedVat,
&header.IsCreditSale,
&header.IsCreditableConfirmed,
&header.CreditableConfirmedUser,
&header.CreditableConfirmedDate,
&header.IsSalesViaInternet,
&header.IsSuspended,
&header.IsCompleted,
&header.IsPrinted,
&header.IsLocked,
&header.UserLocked,
&header.IsClosed,
&header.ApplicationCode,
&header.ApplicationID,
&header.CreatedUserName,
&header.CreatedDate,
&header.LastUpdatedUserName,
&header.LastUpdatedDate,
&header.IsProposalBased,
)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
logger.Printf("⚠️ [GetOrderByID] sipariş bulunamadı: %s", orderID)
return nil, nil, sql.ErrNoRows
}
logger.Printf("❌ [GetOrderByID] header sorgu hatası: %v", err)
return nil, nil, err
}
logger.Printf("✅ [GetOrderByID] header loaded • orderNo=%v currAcc=%v",
header.OrderNumber, header.CurrAccCode.String)
// =====================================================
// LINES
// =====================================================
qLines := `
SELECT
CAST(L.OrderLineID AS varchar(36)) AS OrderLineID,
L.SortOrder,
L.ItemTypeCode,
L.ItemCode,
L.ColorCode,
L.ItemDim1Code,
L.ItemDim2Code,
L.ItemDim3Code,
L.Qty1,
L.Qty2,
L.Price,
L.VatRate,
L.PCTRate,
L.DocCurrencyCode,
L.DeliveryDate,
L.PlannedDateOfLading,
L.LineDescription,
L.IsClosed,
L.CreatedUserName,
L.CreatedDate,
L.LastUpdatedUserName,
L.LastUpdatedDate,
P.ProductAtt42Desc AS UrunIlkGrubu,
P.ProductAtt01Desc AS UrunAnaGrubu,
P.ProductAtt02Desc AS UrunAltGrubu,
P.ProductAtt38Desc AS Fit1,
P.ProductAtt39Desc AS Fit2
FROM BAGGI_V3.dbo.trOrderLine AS L
LEFT JOIN ProductFilterWithDescription('TR') AS P
ON LTRIM(RTRIM(P.ProductCode)) = LTRIM(RTRIM(L.ItemCode))
WHERE L.OrderHeaderID = @p1
ORDER BY L.SortOrder ASC;
`
rows, err := conn.Query(qLines, orderID)
if err != nil {
logger.Printf("❌ [GetOrderByID] line sorgu hatası: %v", err)
return &header, nil, err
}
defer rows.Close()
lines := make([]models.OrderDetail, 0, 32)
for rows.Next() {
var ln models.OrderDetail
if err := rows.Scan(
&ln.OrderLineID,
&ln.SortOrder,
&ln.ItemTypeCode,
&ln.ItemCode,
&ln.ColorCode,
&ln.ItemDim1Code,
&ln.ItemDim2Code,
&ln.ItemDim3Code,
&ln.Qty1,
&ln.Qty2,
&ln.Price,
&ln.VatRate,
&ln.PCTRate,
&ln.DocCurrencyCode,
&ln.DeliveryDate,
&ln.PlannedDateOfLading,
&ln.LineDescription,
&ln.IsClosed,
&ln.CreatedUserName,
&ln.CreatedDate,
&ln.LastUpdatedUserName,
&ln.LastUpdatedDate,
&ln.UrunIlkGrubu,
&ln.UrunAnaGrubu,
&ln.UrunAltGrubu,
&ln.Fit1,
&ln.Fit2,
); err != nil {
return &header, nil, fmt.Errorf("line scan hatası: %w", err)
}
lines = append(lines, ln)
}
if err := rows.Err(); err != nil {
return &header, nil, fmt.Errorf("line rows hatası: %w", err)
}
logger.Printf("📦 [GetOrderByID] lines loaded • count=%d", len(lines))
return &header, lines, nil
}

161
svc/queries/order_pdf.go Normal file
View File

@@ -0,0 +1,161 @@
package queries
import (
"context"
"database/sql"
)
/*
============================================================
HEADER GETIRME — OrderHeader structına uygun
============================================================
*/
type OrderHeaderDB struct {
OrderHeaderID string
OrderNumber string
CurrAccCode string
CurrAccName string
DocCurrency string
OrderDate sql.NullTime
Description sql.NullString
InternalDesc sql.NullString
OfficeCode sql.NullString
CreatedUser sql.NullString
}
func GetOrderHeaderDB(ctx context.Context, db *sql.DB, id string) (*OrderHeaderDB, error) {
q := `
SELECT
CAST(h.OrderHeaderID AS varchar(36)),
h.OrderNumber,
h.CurrAccCode,
d.CurrAccDescription,
h.DocCurrencyCode,
h.OrderDate,
h.Description,
h.InternalDescription,
h.OfficeCode,
h.CreatedUserName
FROM BAGGI_V3.dbo.trOrderHeader AS h
LEFT JOIN BAGGI_V3.dbo.cdCurrAccDesc AS d
ON h.CurrAccCode = d.CurrAccCode
WHERE h.OrderHeaderID = @p1
`
row := db.QueryRowContext(ctx, q, id)
var h OrderHeaderDB
err := row.Scan(
&h.OrderHeaderID,
&h.OrderNumber,
&h.CurrAccCode,
&h.CurrAccName,
&h.DocCurrency,
&h.OrderDate,
&h.Description,
&h.InternalDesc,
&h.OfficeCode,
&h.CreatedUser,
)
if err != nil {
return nil, err
}
return &h, nil
}
/*
============================================================
SATIRLARI GETIRME — OrderLineRaw structına uygun
============================================================
*/
type OrderLineRawDB struct {
OrderLineID sql.NullString
ItemCode string
ColorCode string
ItemDim1Code sql.NullString
ItemDim2Code sql.NullString
Qty1 sql.NullFloat64
Price sql.NullFloat64
DocCurrencyCode sql.NullString
DeliveryDate sql.NullTime
LineDescription sql.NullString
UrunAnaGrubu sql.NullString
UrunAltGrubu sql.NullString
IsClosed sql.NullBool
WithHoldingTaxType sql.NullString
DOVCode sql.NullString
PlannedDateOfLading sql.NullTime
CostCenterCode sql.NullString
VatCode sql.NullString
VatRate sql.NullFloat64
}
func GetOrderLinesDB(ctx context.Context, db *sql.DB, id string) ([]OrderLineRawDB, error) {
q := `
SELECT
CAST(L.OrderLineID AS varchar(36)),
L.ItemCode,
L.ColorCode,
L.ItemDim1Code,
L.ItemDim2Code,
L.Qty1,
L.Price,
L.DocCurrencyCode,
L.DeliveryDate,
L.LineDescription,
P.ProductAtt01Desc,
P.ProductAtt02Desc,
L.IsClosed,
L.WithHoldingTaxTypeCode,
L.DOVCode,
L.PlannedDateOfLading,
L.CostCenterCode,
L.VatCode,
L.VatRate
FROM BAGGI_V3.dbo.trOrderLine AS L
LEFT JOIN ProductFilterWithDescription('TR') AS P
ON LTRIM(RTRIM(P.ProductCode)) = LTRIM(RTRIM(L.ItemCode))
WHERE L.OrderHeaderID = @p1
ORDER BY L.SortOrder, L.OrderLineID
`
rows, err := db.QueryContext(ctx, q, id)
if err != nil {
return nil, err
}
defer rows.Close()
var out []OrderLineRawDB
for rows.Next() {
var r OrderLineRawDB
err = rows.Scan(
&r.OrderLineID,
&r.ItemCode,
&r.ColorCode,
&r.ItemDim1Code,
&r.ItemDim2Code,
&r.Qty1,
&r.Price,
&r.DocCurrencyCode,
&r.DeliveryDate,
&r.LineDescription,
&r.UrunAnaGrubu,
&r.UrunAltGrubu,
&r.IsClosed,
&r.WithHoldingTaxType,
&r.DOVCode,
&r.PlannedDateOfLading,
&r.CostCenterCode,
&r.VatCode,
&r.VatRate,
)
if err != nil {
return nil, err
}
out = append(out, r)
}
return out, nil
}

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

@@ -0,0 +1,1079 @@
// ===================== PART 1 (Satır 1-300) =====================
// =======================================================
// order_write.go — v4.3 FINAL (Insert + Update + Delete)
// - PCTCode her zaman "%0" olarak yazılır (cdPCT FK uyumlu)
// - INSERT/UPDATE öncesi ItemVariant Guard + Duplicate Guard (payload içi)
// - UpdateOrder: DELETE öncesi child tablolar (trOrderLineCurrency) temizlenir
// =======================================================
package queries
import (
"bssapp-backend/db"
"bssapp-backend/models"
"database/sql"
"fmt"
"github.com/google/uuid"
"strings"
"time"
)
func nf0(v models.NullFloat64) float64 {
if !v.Valid {
return 0
}
return v.Float64
}
// =======================================================
// COMBO KEY & STRING HELPERS
// =======================================================
func normalizeComboKey(s string) string {
return strings.ToUpper(strings.TrimSpace(s))
}
// makeComboKey: frontend tarafında NullString olmayan structlar için
func makeComboKey(ln models.OrderDetail) string {
model := safeNS(ln.ItemCode)
renk := safeNS(ln.ColorCode)
renk2 := safeNS(ln.ItemDim2Code)
beden := safeNS(ln.ItemDim1Code)
return normalizeComboKey(
fmt.Sprintf("%s||%s||%s||%s", model, renk, beden, renk2),
)
}
// qtyValue → NullFloat64 güvenli float64
func qtyValue(q models.NullFloat64) float64 {
if !q.Valid {
return 0
}
return q.Float64
}
// VatCode: NullString → string
// - NULL → ""
// - "0" → "" (FK patlamasın, sadece anlamlı kodlar gönderiyoruz)
// - "%0", "%10" vb → trimlenmiş hali
func normalizeVatCode(ns models.NullString) string {
if !ns.Valid {
return ""
}
s := strings.TrimSpace(ns.String)
if s == "0" {
return ""
}
return s
}
// PCTCode: NullString → string
// - Artık her durumda "%0" döndürür (tek PCT tipi kullanımı)
// NOT: SQL tarafında da "%0" cdPCT içinde tanımlı olmalı
func normalizePCTCode(ns models.NullString) string {
return "%0"
}
// models.NullString → trimlenmiş string (NULL ise "")
func safeNS(ns models.NullString) string {
if !ns.Valid {
return ""
}
return strings.TrimSpace(ns.String)
}
// =======================================================
// COMBO KEY HELPERS (DB alanlarından)
// =======================================================
// makeComboKeyParts: düz string alanlardan comboKey üret
// comboKey = model || renk || beden || renk2
func makeComboKeyParts(item, color, dim1, dim2 string) string {
item = strings.TrimSpace(item)
color = strings.TrimSpace(color)
dim1 = strings.TrimSpace(dim1)
dim2 = strings.TrimSpace(dim2)
if item == "" && color == "" && dim1 == "" && dim2 == "" {
return ""
}
return normalizeComboKey(item + "||" + color + "||" + dim1 + "||" + dim2)
}
// comboFromNulls: NullString alanlardan comboKey üret
func comboFromNulls(item, color, dim1, dim2 models.NullString) string {
return makeComboKeyParts(
safeNS(item),
safeNS(color),
safeNS(dim1),
safeNS(dim2),
)
}
// =======================================================
// ✅ ItemVariant Guard — INSERT / UPDATE öncesi
// =======================================================
// normalizeKeyPart: NullString → trim + UPPER
func normalizeKeyPart(ns models.NullString) string {
s := strings.TrimSpace(safeNS(ns))
return strings.ToUpper(s)
}
// =======================================================
// AKSBIR DETECTION
// =======================================================
// =======================================================
// COMBO KEY BUILDER (AKSBIR AWARE)
// =======================================================
// Variant check: ItemCode + ColorCode + Dim1 + Dim2
func ValidateItemVariant(tx *sql.Tx, ln models.OrderDetail) error {
fmt.Printf(
"🧪 VARIANT GUARD INPUT | ClientKey=%s Item=%q Color=%q Dim1=%q Dim2=%q Dim3=%q Qty1=%v\n",
safeNS(ln.ClientKey),
safeNS(ln.ItemCode),
safeNS(ln.ColorCode),
safeNS(ln.ItemDim1Code),
safeNS(ln.ItemDim2Code),
safeNS(ln.ItemDim3Code),
nf0(ln.Qty1),
)
item := normalizeKeyPart(ln.ItemCode)
color := normalizeKeyPart(ln.ColorCode)
dim1 := normalizeKeyPart(ln.ItemDim1Code)
dim2 := normalizeKeyPart(ln.ItemDim2Code)
// ✅ Placeholder/boş standardizasyon (SENDE "_" geliyor)
normalizeEmpty := func(s string) string {
s = strings.TrimSpace(strings.ToUpper(s))
if s == "_" || s == "-" {
return ""
}
return s
}
item = normalizeEmpty(item)
color = normalizeEmpty(color)
dim1 = normalizeEmpty(dim1)
dim2 = normalizeEmpty(dim2)
if item == "" {
return fmt.Errorf(
"ItemCode boş olamaz (ClientKey=%s)",
safeNS(ln.ClientKey),
)
fmt.Printf(
"🧪 VARIANT NORMALIZED | Item=%q Color=%q Dim1=%q Dim2=%q\n",
item, color, dim1, dim2,
)
}
// İstersen debug:
// fmt.Printf("🧪 VARIANT CHECK item=%q color=%q dim1=%q dim2=%q clientKey=%s\n", item, color, dim1, dim2, safeNS(ln.ClientKey))
var exists int
err := tx.QueryRow(`
SELECT CASE WHEN EXISTS (
SELECT 1
FROM BAGGI_V3.dbo.prItemVariant V WITH (NOLOCK)
WHERE ISNULL(LTRIM(RTRIM(V.ItemCode)),'') = @p1
AND ISNULL(LTRIM(RTRIM(V.ColorCode)),'') = @p2
AND ISNULL(LTRIM(RTRIM(V.ItemDim1Code)),'') = @p3
AND ISNULL(LTRIM(RTRIM(V.ItemDim2Code)),'') = @p4
) THEN 1 ELSE 0 END
`, item, color, dim1, dim2).Scan(&exists)
if err != nil {
return fmt.Errorf("ItemVariant kontrol query hatası: %w", err)
}
if exists != 1 {
return &models.ValidationError{
Code: "INVALID_ITEM_VARIANT",
Message: "Tanımsız ürün kombinasyonu",
ClientKey: safeNS(ln.ClientKey),
ItemCode: item,
ColorCode: color,
Dim1: dim1,
Dim2: dim2,
}
}
return nil
}
// ValidateOrderVariants: save/update öncesi payload satırlarını prItemVariant'a göre doğrular.
// invalid döner; error sadece DB/prepare/query hatalarında döner.
func ValidateOrderVariants(db *sql.DB, lines []models.OrderDetail) ([]models.InvalidVariant, error) {
normalizeEmpty := func(s string) string {
s = strings.TrimSpace(strings.ToUpper(s))
if s == "_" || s == "-" {
return ""
}
return s
}
stmt, err := db.Prepare(`
SELECT CASE WHEN EXISTS (
SELECT 1
FROM BAGGI_V3.dbo.prItemVariant V WITH (NOLOCK)
WHERE ISNULL(LTRIM(RTRIM(V.ItemCode)),'') = @p1
AND ISNULL(LTRIM(RTRIM(V.ColorCode)),'') = @p2
AND ISNULL(LTRIM(RTRIM(V.ItemDim1Code)),'') = @p3
AND ISNULL(LTRIM(RTRIM(V.ItemDim2Code)),'') = @p4
) THEN 1 ELSE 0 END
`)
if err != nil {
return nil, fmt.Errorf("validate prepare hatası: %w", err)
}
defer stmt.Close()
invalid := make([]models.InvalidVariant, 0)
for i, ln := range lines {
qty := qtyValue(ln.Qty1)
if qty <= 0 {
continue
}
item := normalizeEmpty(normalizeKeyPart(ln.ItemCode))
color := normalizeEmpty(normalizeKeyPart(ln.ColorCode))
dim1 := normalizeEmpty(normalizeKeyPart(ln.ItemDim1Code))
dim2 := normalizeEmpty(normalizeKeyPart(ln.ItemDim2Code))
// ItemCode boş ise invalid
if strings.TrimSpace(item) == "" {
invalid = append(invalid, models.InvalidVariant{
Index: i,
ClientKey: safeNS(ln.ClientKey),
ItemCode: item,
ColorCode: color,
Dim1: dim1,
Dim2: dim2,
Qty1: qty,
ComboKey: safeNS(ln.ComboKey),
Reason: "ItemCode boş",
})
continue
}
var exists int
if err := stmt.QueryRow(item, color, dim1, dim2).Scan(&exists); err != nil {
return nil, fmt.Errorf("validate query hatası (i=%d): %w", i, err)
}
if exists != 1 {
invalid = append(invalid, models.InvalidVariant{
Index: i,
ClientKey: safeNS(ln.ClientKey),
ItemCode: item,
ColorCode: color,
Dim1: dim1,
Dim2: dim2,
Qty1: qty,
ComboKey: safeNS(ln.ComboKey),
Reason: "prItemVariantta yok",
})
}
}
return invalid, nil
}
// =======================================================
// LineResult → frontend senkronu için
// =======================================================
type OrderLineResult struct {
ClientKey string `json:"clientKey"`
OrderLineID string `json:"orderLineID"`
}
// =======================================================
// PART 1 — InsertOrder (header + lines insert) — FINAL v5.1
// ✔ OrderHeaderID backend üretir
// ✔ LOCAL-... numara gelirse gerçek WS numarası üretir
// ✔ Full debug
// ✔ Tüm satırlar INSERT edilir
// ✔ INSERT öncesi ItemVariant Guard
// ✔ Payload içi Duplicate Guard (comboKey)
// =======================================================
func InsertOrder(header models.OrderHeader, lines []models.OrderDetail, user *models.User) (string, []OrderLineResult, error) {
conn := db.GetDB()
fmt.Println("🟦 InsertOrder() BAŞLADI -----------------------------")
tx, err := conn.Begin()
if err != nil {
return "", nil, fmt.Errorf("tx baslatilamadi: %w", err)
}
defer tx.Rollback()
now := time.Now()
v3User := fmt.Sprintf("V3U%d-%s", user.V3UserGroup, user.V3Username)
// =======================================================
// 1) BACKEND — OrderHeaderID üretimi (HER ZAMAN)
// =======================================================
realHeaderID := uuid.New().String()
fmt.Println("🟩 Backend yeni OrderHeaderID üretti:", realHeaderID)
header.OrderHeaderID = realHeaderID
// =======================================================
// 2) OrderNumber üretimi (LOCAL-* ise gerçek WS numarası)
// =======================================================
if !header.OrderNumber.Valid ||
strings.HasPrefix(header.OrderNumber.String, "LOCAL-") ||
len(strings.TrimSpace(header.OrderNumber.String)) == 0 {
fmt.Println("🟨 LOCAL numara geldi → gerçek WS numarası üretilecek...")
var realNumber string
err := tx.QueryRow(`
SELECT CONCAT(
'1-WS-3-',
RIGHT('00000' + CAST(NEXT VALUE FOR BAGGI_V3.dbo.Seq_OrderNumber_WS AS VARCHAR(10)), 5)
)
`).Scan(&realNumber)
if err != nil {
return "", nil, fmt.Errorf("Gerçek sipariş numarası üretilemedi: %w", err)
}
fmt.Println("🟩 Üretilen gerçek WS numarası:", realNumber)
header.OrderNumber.String = realNumber
header.OrderNumber.Valid = true
}
newID := realHeaderID // artık DBye bu yazılacak
// =======================================================
// 3) Döviz kuru çözümü
// =======================================================
exRate := 1.0
if header.DocCurrencyCode.Valid && header.DocCurrencyCode.String != "TRY" {
if c, err := GetTodayCurrencyV3(conn, header.DocCurrencyCode.String); err == nil {
if c.Rate > 0 {
exRate = c.Rate
}
}
}
// =======================================================
// 4) HEADER INSERT
// =======================================================
queryHeader := `
INSERT INTO BAGGI_V3.dbo.trOrderHeader (
OrderHeaderID, OrderTypeCode, ProcessCode, OrderNumber, IsCancelOrder,
OrderDate, OrderTime, DocumentNumber, PaymentTerm,
AverageDueDate, Description, InternalDescription,
CurrAccTypeCode, CurrAccCode, SubCurrAccID, ContactID,
ShipmentMethodCode, ShippingPostalAddressID, BillingPostalAddressID,
GuarantorContactID, GuarantorContactID2, RoundsmanCode,
DeliveryCompanyCode, TaxTypeCode, WithHoldingTaxTypeCode, DOVCode,
TaxExemptionCode, CompanyCode, OfficeCode, StoreTypeCode, StoreCode,
POSTerminalID, WarehouseCode, ToWarehouseCode,
OrdererCompanyCode, OrdererOfficeCode, OrdererStoreCode,
GLTypeCode, DocCurrencyCode, LocalCurrencyCode, ExchangeRate,
TDisRate1, TDisRate2, TDisRate3, TDisRate4, TDisRate5,
DiscountReasonCode, SurplusOrderQtyToleranceRate,
ImportFileNumber, ExportFileNumber,
IncotermCode1, IncotermCode2, LettersOfCreditNumber,
PaymentMethodCode, IsInclutedVat, IsCreditSale, IsCreditableConfirmed,
CreditableConfirmedUser, CreditableConfirmedDate,
IsSalesViaInternet, IsSuspended, IsCompleted, IsPrinted,
IsLocked, UserLocked, IsClosed,
ApplicationCode, ApplicationID,
CreatedUserName, CreatedDate, LastUpdatedUserName, LastUpdatedDate,
IsProposalBased
)
VALUES (
@p1,@p2,@p3,@p4,
@p5,@p6,@p7,@p8,
@p9,@p10,@p11,
@p12,@p13,@p14,@p15,
@p16,@p17,@p18,
@p19,@p20,@p21,
@p22,@p23,@p24,@p25,
@p26,@p27,@p28,@p29,@p30,
@p31,@p32,@p33,
@p34,@p35,@p36,
@p37,@p38,@p39,@p40,
@p41,@p42,@p43,@p44,@p45,
@p46,@p47,
@p48,@p49,
@p50,@p51,@p52,
@p53,@p54,@p55,@p56,@p57,
@p58,@p59,@p60,@p61,@p62,
@p63,@p64,@p65,
@p66,@p67,@p68,@p69,@p70,@p71,@p72,@p73
);
`
fmt.Println("🟪 HEADER INSERT ÇALIŞIYOR...")
headerParams := []any{
header.OrderHeaderID,
nullableInt16(header.OrderTypeCode, 1),
nullableString(header.ProcessCode, "WS"),
nullableString(header.OrderNumber, ""),
nullableBool(header.IsCancelOrder, false),
nullableDateString(header.OrderDate),
nullableTimeString(header.OrderTime),
nullableString(header.DocumentNumber, ""),
nullableInt16(header.PaymentTerm, 0),
nullableDateString(header.AverageDueDate),
nullableString(header.Description, ""),
nullableString(header.InternalDescription, ""),
nullableInt16(header.CurrAccTypeCode, 0),
nullableString(header.CurrAccCode, ""),
nullableUUID(header.SubCurrAccID),
nullableUUID(header.ContactID),
nullableString(header.ShipmentMethodCode, ""),
nullableUUID(header.ShippingPostalAddressID),
nullableUUID(header.BillingPostalAddressID),
nullableUUID(header.GuarantorContactID),
nullableUUID(header.GuarantorContactID2),
nullableString(header.RoundsmanCode, ""),
nullableString(header.DeliveryCompanyCode, ""),
nullableInt16(header.TaxTypeCode, 0),
nullableString(header.WithHoldingTaxTypeCode, ""),
nullableString(header.DOVCode, ""),
nullableInt16(header.TaxExemptionCode, 0),
nullableInt32ToInt16(header.CompanyCode, 1),
nullableString(header.OfficeCode, "101"),
nullableInt16(header.StoreTypeCode, 5),
nullableString(header.StoreCode, ""),
nullableInt16(header.POSTerminalID, 0),
nullableString(header.WarehouseCode, "1-0-12"),
nullableString(header.ToWarehouseCode, ""),
nullableInt32ToInt16(header.OrdererCompanyCode, 1),
nullableString(header.OrdererOfficeCode, "101"),
nullableString(header.OrdererStoreCode, ""),
nullableString(header.GLTypeCode, ""),
nullableString(header.DocCurrencyCode, "TRY"),
nullableString(header.LocalCurrencyCode, "TRY"),
nullableFloat64(header.ExchangeRate, exRate),
nullableFloat64(header.TDisRate1, 0),
nullableFloat64(header.TDisRate2, 0),
nullableFloat64(header.TDisRate3, 0),
nullableFloat64(header.TDisRate4, 0),
nullableFloat64(header.TDisRate5, 0),
nullableInt16(header.DiscountReasonCode, 0),
nullableFloat64(header.SurplusOrderQtyToleranceRate, 0),
nullableString(header.ImportFileNumber, ""),
nullableString(header.ExportFileNumber, ""),
nullableString(header.IncotermCode1, ""),
nullableString(header.IncotermCode2, ""),
nullableString(header.LettersOfCreditNumber, ""),
nullableString(header.PaymentMethodCode, ""),
nullableBool(header.IsInclutedVat, false),
nullableBool(header.IsCreditSale, true),
nullableBool(header.IsCreditableConfirmed, true),
nullableString(header.CreditableConfirmedUser, v3User),
nullableDateTime(header.CreditableConfirmedDate, now),
nullableBool(header.IsSalesViaInternet, false),
nullableBool(header.IsSuspended, false),
nullableBool(header.IsCompleted, false),
nullableBool(header.IsPrinted, false),
nullableBool(header.IsLocked, false),
nullableBool(header.UserLocked, false),
nullableBool(header.IsClosed, false),
nullableString(header.ApplicationCode, "Order"),
nullableUUID(header.ApplicationID),
nullableString(header.CreatedUserName, v3User),
nullableDateTimeString(header.CreatedDate, now),
nullableString(header.LastUpdatedUserName, v3User),
nullableDateTimeString(header.LastUpdatedDate, now),
nullableBool(header.IsProposalBased, false),
}
if _, err := tx.Exec(queryHeader, headerParams...); err != nil {
fmt.Println("❌ HEADER INSERT ERROR:", err)
return "", nil, fmt.Errorf("header insert hatasi: %w", err)
}
fmt.Println("🟩 HEADER INSERT OK — ID:", newID)
// =======================================================
// 5) LINE INSERT
// =======================================================
insStmt, err := tx.Prepare(`
INSERT INTO BAGGI_V3.dbo.trOrderLine (
OrderLineID,
SortOrder, ItemTypeCode, ItemCode, ColorCode,
ItemDim1Code, ItemDim2Code, ItemDim3Code,
Qty1, Qty2,
CancelQty1, CancelQty2,
OrderCancelReasonCode,
DeliveryDate, PlannedDateOfLading,
LineDescription,
UsedBarcode, CostCenterCode,
VatCode, VatRate, PCTCode, PCTRate,
LDisRate1, LDisRate2, LDisRate3, LDisRate4, LDisRate5,
DocCurrencyCode, PriceCurrencyCode, PriceExchangeRate,
Price,
BaseProcessCode, BaseOrderNumber,
BaseCustomerTypeCode, BaseCustomerCode,
BaseSubCurrAccID, BaseStoreCode,
OrderHeaderID,
CreatedUserName, CreatedDate,
LastUpdatedUserName, LastUpdatedDate,
SurplusOrderQtyToleranceRate,
WithHoldingTaxTypeCode,
DOVCode
)
VALUES (
@p1,@p2,@p3,@p4,
@p5,@p6,@p7,@p8,
@p9,@p10,
@p11,@p12,
@p13,
@p14,@p15,
@p16,
@p17,@p18,
@p19,@p20,@p21,@p22,
@p23,@p24,@p25,@p26,@p27,
@p28,@p29,@p30,
@p31,
@p32,@p33,
@p34,@p35,
@p36,@p37,
@p38,
@p39,@p40,
@p41,@p42,
@p43,
@p44,
@p45
)`)
if err != nil {
return "", nil, fmt.Errorf("line insert stmt hazirlanamadi: %w", err)
}
defer insStmt.Close()
lineResults := make([]OrderLineResult, 0, len(lines))
// ✅ Duplicate Guard (payload içi)
seenCombo := make(map[string]bool)
for i, ln := range lines {
// ===================== PART 2 (Satır 301-600) =====================
fmt.Println("────────────────────────────────────")
fmt.Printf("🟨 [INSERT] LINE %d — gelen OrderLineID=%s\n", i+1, ln.OrderLineID)
// Her satır için yeni GUID
if ln.OrderLineID == "" {
newLineID := uuid.New().String()
fmt.Println("🆕 Yeni LineID üretildi:", newLineID)
ln.OrderLineID = newLineID
}
// ✅ Duplicate Guard (comboKey)
comboKey := normalizeComboKey(safeNS(ln.ComboKey))
if comboKey == "" {
comboKey = makeComboKey(ln)
}
if qtyValue(ln.Qty1) > 0 && comboKey != "" {
if seenCombo[comboKey] {
return "", nil, fmt.Errorf(
"Duplicate satır (comboKey=%s, ClientKey=%s)",
comboKey,
safeNS(ln.ClientKey),
)
}
seenCombo[comboKey] = true
}
// V2 Logic → %0 PCT
vatCode := normalizeVatCode(ln.VatCode)
pctCode := normalizePCTCode(ln.PCTCode)
var pctParam any
if pctCode == "" {
pctParam = nil
} else {
pctParam = pctCode
}
planned := nullableDateString(ln.PlannedDateOfLading)
// ✅ INSERT ÖNCESİ ItemVariant GUARD
if qtyValue(ln.Qty1) > 0 {
if err := ValidateItemVariant(tx, ln); err != nil {
fmt.Println("❌ VARIANT GUARD (INSERT):", err)
return "", nil, err
}
}
fmt.Printf(
"🚨 INSERT LINE[%d] | LineID=%s ClientKey=%s Item=%q Color=%q Dim1=%q Dim2=%q Dim3=%q Qty1=%v\n",
i+1,
ln.OrderLineID,
safeNS(ln.ClientKey),
safeNS(ln.ItemCode),
safeNS(ln.ColorCode),
safeNS(ln.ItemDim1Code),
safeNS(ln.ItemDim2Code),
safeNS(ln.ItemDim3Code),
nf0(ln.Qty1),
)
_, err := insStmt.Exec(
ln.OrderLineID,
ln.SortOrder,
ln.ItemTypeCode,
nullableString(ln.ItemCode, ""),
safeNS(ln.ColorCode),
safeNS(ln.ItemDim1Code),
safeNS(ln.ItemDim2Code),
nullableString(ln.ItemDim3Code, ""),
ln.Qty1, ln.Qty2,
ln.CancelQty1, ln.CancelQty2,
nullableString(ln.OrderCancelReasonCode, ""),
nullableTime(ln.DeliveryDate, now),
planned,
nullableString(ln.LineDescription, ""),
nullableString(ln.UsedBarcode, ""),
nullableString(ln.CostCenterCode, ""),
vatCode, nf0(ln.VatRate),
pctParam, nf0(ln.PCTRate),
nf0(ln.LDisRate1),
nf0(ln.LDisRate2),
nf0(ln.LDisRate3),
nf0(ln.LDisRate4),
nf0(ln.LDisRate5),
nullableString(ln.DocCurrencyCode, "TRY"),
nullableString(ln.PriceCurrencyCode, "TRY"),
nf0(ln.PriceExchangeRate),
nf0(ln.Price),
nullableString(ln.BaseProcessCode, ""),
nullableString(ln.BaseOrderNumber, ""),
ln.BaseCustomerTypeCode,
nullableString(ln.BaseCustomerCode, ""),
nullableUUID(ln.BaseSubCurrAccID),
nullableString(ln.BaseStoreCode, ""),
header.OrderHeaderID,
v3User, now,
v3User, now,
nf0(ln.SurplusOrderQtyToleranceRate),
nullableString(ln.WithHoldingTaxTypeCode, ""),
nullableString(ln.DOVCode, ""),
)
if err != nil {
fmt.Println("❌ INSERT LINE ERROR")
fmt.Printf(
"💥 FAILED LINE | LineID=%s ClientKey=%s Item=%q Color=%q Dim1=%q Dim2=%q Dim3=%q\n",
ln.OrderLineID,
safeNS(ln.ClientKey),
safeNS(ln.ItemCode),
safeNS(ln.ColorCode),
safeNS(ln.ItemDim1Code),
safeNS(ln.ItemDim2Code),
safeNS(ln.ItemDim3Code),
)
fmt.Println("SQL ERROR:", err)
return "", nil, fmt.Errorf("line insert hatasi: %w", err)
}
if ln.ClientKey.Valid && ln.ClientKey.String != "" {
lineResults = append(lineResults, OrderLineResult{
ClientKey: ln.ClientKey.String,
OrderLineID: ln.OrderLineID,
})
}
}
// =======================================================
// 6) COMMIT
// =======================================================
fmt.Println("🟨 COMMIT EDİLİYOR...")
if err := tx.Commit(); err != nil {
fmt.Println("❌ COMMIT ERROR:", err)
return "", nil, err
}
fmt.Println("✅ COMMIT OK — INSERT ORDER BİTTİ")
fmt.Println("────────────────────────────────────")
return newID, lineResults, nil
}
// =======================================================
// PART 2 — UpdateOrder FULL DEBUG (v4.3)
// ✔ ComboKey ile açık satır eşleştirme
// ✔ Kapalı satırları korur
// ✔ Payload içi Duplicate Guard
// ✔ INSERT/UPDATE öncesi ItemVariant Guard (tek noktada)
// ✔ Gridde olmayan açık satırları siler (önce child)
// =======================================================
func UpdateOrder(header models.OrderHeader, lines []models.OrderDetail, user *models.User) ([]OrderLineResult, error) {
conn := db.GetDB()
// ======================================================
// 🔍 SCAN DEBUG — HEADER bilgisi
// ======================================================
fmt.Println("══════════════════════════════════════")
fmt.Println("🔍 [DEBUG] UpdateOrder çağrıldı")
fmt.Printf("🔍 HeaderID: %v\n", header.OrderHeaderID)
fmt.Printf("🔍 Line sayısı: %v\n", len(lines))
fmt.Printf("🔍 User: %v (V3: %s/%d)\n",
user.Username, user.V3Username, user.V3UserGroup)
fmt.Println("══════════════════════════════════════")
tx, err := conn.Begin()
if err != nil {
return nil, fmt.Errorf("tx baslatilamadi: %w", err)
}
defer tx.Rollback()
now := time.Now()
v3User := fmt.Sprintf("V3U%d-%s", user.V3UserGroup, user.V3Username)
// Döviz kuru
exRate := 1.0
if header.DocCurrencyCode.Valid && header.DocCurrencyCode.String != "TRY" {
if c, err := GetTodayCurrencyV3(conn, header.DocCurrencyCode.String); err == nil && c.Rate > 0 {
exRate = c.Rate
}
}
// =======================================================
// 0) Mevcut satırları oku (GUID STRING olarak!)
// =======================================================
existingOpen := make(map[string]bool)
existingClosed := make(map[string]bool)
existingOpenCombo := make(map[string]string)
existingClosedCombo := make(map[string]string)
rows, err := tx.Query(`
SELECT
CONVERT(varchar(36), OrderLineID),
ISNULL(IsClosed, 0),
ISNULL(ItemCode,''),
ISNULL(ColorCode,''),
ISNULL(ItemDim1Code,''),
ISNULL(ItemDim2Code,'')
FROM BAGGI_V3.dbo.trOrderLine
WHERE OrderHeaderID=@p1
`, header.OrderHeaderID)
if err != nil {
return nil, fmt.Errorf("mevcut satirlar okunamadi: %w", err)
}
defer rows.Close()
for rows.Next() {
var id, item, color, dim1, dim2 string
var closed bool
if err := rows.Scan(&id, &closed, &item, &color, &dim1, &dim2); err != nil {
return nil, err
}
combo := makeComboKeyParts(item, color, dim1, dim2)
if closed {
existingClosed[id] = true
if combo != "" {
existingClosedCombo[combo] = id
}
} else {
existingOpen[id] = true
if combo != "" {
existingOpenCombo[combo] = id
}
}
}
// ======================================================
// HEADER UPDATE
// ======================================================
_, err = tx.Exec(`
UPDATE BAGGI_V3.dbo.trOrderHeader SET
OrderDate=@p1,
OrderTime=@p2,
AverageDueDate=@p3,
Description=@p4,
InternalDescription=@p5,
DocCurrencyCode=@p6,
LocalCurrencyCode=@p7,
ExchangeRate=@p8,
LastUpdatedUserName=@p9,
LastUpdatedDate=@p10
WHERE OrderHeaderID=@p11
`,
nullableDateString(header.OrderDate),
nullableTimeString(header.OrderTime),
nullableDateString(header.AverageDueDate),
nullableString(header.Description, ""),
nullableString(header.InternalDescription, ""),
nullableString(header.DocCurrencyCode, "TRY"),
nullableString(header.LocalCurrencyCode, "TRY"),
nullableFloat64(header.ExchangeRate, exRate),
v3User,
now,
header.OrderHeaderID,
)
if err != nil {
return nil, err
}
// ======================================================
// PREPARE STATEMENTS
// ======================================================
insStmt, err := tx.Prepare(`INSERT INTO BAGGI_V3.dbo.trOrderLine (
OrderLineID, SortOrder, ItemTypeCode, ItemCode, ColorCode,
ItemDim1Code, ItemDim2Code, ItemDim3Code,
Qty1, Qty2, CancelQty1, CancelQty2, OrderCancelReasonCode,
DeliveryDate, PlannedDateOfLading, LineDescription,
UsedBarcode, CostCenterCode,
VatCode, VatRate, PCTCode, PCTRate,
LDisRate1, LDisRate2, LDisRate3, LDisRate4, LDisRate5,
DocCurrencyCode, PriceCurrencyCode, PriceExchangeRate,
Price, BaseProcessCode, BaseOrderNumber,
BaseCustomerTypeCode, BaseCustomerCode,
BaseSubCurrAccID, BaseStoreCode,
OrderHeaderID, CreatedUserName, CreatedDate,
LastUpdatedUserName, LastUpdatedDate,
SurplusOrderQtyToleranceRate,
WithHoldingTaxTypeCode, DOVCode)
VALUES (
@p1,@p2,@p3,@p4,@p5,@p6,@p7,@p8,@p9,@p10,
@p11,@p12,@p13,@p14,@p15,@p16,@p17,@p18,
@p19,@p20,@p21,@p22,@p23,@p24,@p25,@p26,@p27,
@p28,@p29,@p30,@p31,@p32,@p33,@p34,@p35,
@p36,@p37,@p38,@p39,@p40,@p41,@p42,@p43,
@p44,@p45)`)
if err != nil {
return nil, err
}
defer insStmt.Close()
updStmt, err := tx.Prepare(`UPDATE BAGGI_V3.dbo.trOrderLine SET
SortOrder=@p1, ItemTypeCode=@p2, ItemCode=@p3, ColorCode=@p4,
ItemDim1Code=@p5, ItemDim2Code=@p6, ItemDim3Code=@p7,
Qty1=@p8, Qty2=@p9, CancelQty1=@p10, CancelQty2=@p11,
OrderCancelReasonCode=@p12,
DeliveryDate=@p13, PlannedDateOfLading=@p14,
LineDescription=@p15, UsedBarcode=@p16, CostCenterCode=@p17,
VatCode=@p18, VatRate=@p19, PCTCode=@p20, PCTRate=@p21,
LDisRate1=@p22, LDisRate2=@p23, LDisRate3=@p24,
LDisRate4=@p25, LDisRate5=@p26,
DocCurrencyCode=@p27, PriceCurrencyCode=@p28,
PriceExchangeRate=@p29, Price=@p30,
BaseProcessCode=@p31, BaseOrderNumber=@p32,
BaseCustomerTypeCode=@p33, BaseCustomerCode=@p34,
BaseSubCurrAccID=@p35, BaseStoreCode=@p36,
LastUpdatedUserName=@p37, LastUpdatedDate=@p38,
SurplusOrderQtyToleranceRate=@p39,
WithHoldingTaxTypeCode=@p40, DOVCode=@p41
WHERE OrderLineID=@p42 AND ISNULL(IsClosed,0)=0`)
if err != nil {
return nil, err
}
defer updStmt.Close()
lineResults := make([]OrderLineResult, 0)
seenCombo := make(map[string]bool)
for _, ln := range lines {
comboKey := normalizeComboKey(safeNS(ln.ComboKey))
if comboKey == "" {
comboKey = makeComboKey(ln)
}
// Duplicate guard (SADECE aktif)
if qtyValue(ln.Qty1) > 0 && comboKey != "" {
if seenCombo[comboKey] {
return nil, fmt.Errorf("Duplicate satır: %s", comboKey)
}
seenCombo[comboKey] = true
}
// Kapalı satır
if ln.OrderLineID != "" && existingClosed[ln.OrderLineID] {
continue
}
if comboKey != "" {
if _, ok := existingClosedCombo[comboKey]; ok {
continue
}
}
// DELETE SIGNAL
if ln.OrderLineID != "" && qtyValue(ln.Qty1) <= 0 {
_, err := tx.Exec(`DELETE FROM BAGGI_V3.dbo.trOrderLineCurrency WHERE OrderLineID=@p1`, ln.OrderLineID)
if err != nil {
return nil, err
}
_, err = tx.Exec(`
DELETE FROM BAGGI_V3.dbo.trOrderLine
WHERE OrderHeaderID=@p1 AND OrderLineID=@p2 AND ISNULL(IsClosed,0)=0
`, header.OrderHeaderID, ln.OrderLineID)
if err != nil {
return nil, err
}
delete(existingOpen, ln.OrderLineID)
delete(existingOpenCombo, comboKey)
continue
}
isNew := false
if ln.OrderLineID == "" {
if dbID, ok := existingOpenCombo[comboKey]; ok {
ln.OrderLineID = dbID
} else {
ln.OrderLineID = uuid.New().String()
isNew = true
}
}
if qtyValue(ln.Qty1) > 0 {
if err := ValidateItemVariant(tx, ln); err != nil {
return nil, err
}
}
if isNew {
_, err := insStmt.Exec(
ln.OrderLineID, ln.SortOrder, ln.ItemTypeCode,
nullableString(ln.ItemCode, ""), safeNS(ln.ColorCode),
safeNS(ln.ItemDim1Code), safeNS(ln.ItemDim2Code),
nullableString(ln.ItemDim3Code, ""),
ln.Qty1, ln.Qty2, ln.CancelQty1, ln.CancelQty2,
nullableString(ln.OrderCancelReasonCode, ""),
nullableTime(ln.DeliveryDate, now),
nullableDateString(ln.PlannedDateOfLading),
nullableString(ln.LineDescription, ""),
nullableString(ln.UsedBarcode, ""),
nullableString(ln.CostCenterCode, ""),
normalizeVatCode(ln.VatCode), nf0(ln.VatRate),
normalizePCTCode(ln.PCTCode), nf0(ln.PCTRate),
nf0(ln.LDisRate1), nf0(ln.LDisRate2),
nf0(ln.LDisRate3), nf0(ln.LDisRate4), nf0(ln.LDisRate5),
nullableString(ln.DocCurrencyCode, "TRY"),
nullableString(ln.PriceCurrencyCode, "TRY"),
nf0(ln.PriceExchangeRate), nf0(ln.Price),
nullableString(ln.BaseProcessCode, ""),
nullableString(ln.BaseOrderNumber, ""),
ln.BaseCustomerTypeCode,
nullableString(ln.BaseCustomerCode, ""),
nullableUUID(ln.BaseSubCurrAccID),
nullableString(ln.BaseStoreCode, ""),
header.OrderHeaderID,
v3User, now, v3User, now,
nf0(ln.SurplusOrderQtyToleranceRate),
nullableString(ln.WithHoldingTaxTypeCode, ""),
nullableString(ln.DOVCode, ""),
)
if err != nil {
return nil, err
}
} else {
_, err := updStmt.Exec(
ln.SortOrder, ln.ItemTypeCode,
nullableString(ln.ItemCode, ""),
safeNS(ln.ColorCode),
safeNS(ln.ItemDim1Code),
safeNS(ln.ItemDim2Code),
nullableString(ln.ItemDim3Code, ""),
nf0(ln.Qty1), nf0(ln.Qty2),
nf0(ln.CancelQty1), nf0(ln.CancelQty2),
nullableString(ln.OrderCancelReasonCode, ""),
nullableTime(ln.DeliveryDate, now),
nullableDateString(ln.PlannedDateOfLading),
nullableString(ln.LineDescription, ""),
nullableString(ln.UsedBarcode, ""),
nullableString(ln.CostCenterCode, ""),
normalizeVatCode(ln.VatCode), nf0(ln.VatRate),
normalizePCTCode(ln.PCTCode), nf0(ln.PCTRate),
nf0(ln.LDisRate1), nf0(ln.LDisRate2),
nf0(ln.LDisRate3), nf0(ln.LDisRate4), nf0(ln.LDisRate5),
nullableString(ln.DocCurrencyCode, "TRY"),
nullableString(ln.PriceCurrencyCode, "TRY"),
nf0(ln.PriceExchangeRate), nf0(ln.Price),
nullableString(ln.BaseProcessCode, ""),
nullableString(ln.BaseOrderNumber, ""),
ln.BaseCustomerTypeCode,
nullableString(ln.BaseCustomerCode, ""),
nullableUUID(ln.BaseSubCurrAccID),
nullableString(ln.BaseStoreCode, ""),
v3User, now,
nf0(ln.SurplusOrderQtyToleranceRate),
nullableString(ln.WithHoldingTaxTypeCode, ""),
nullableString(ln.DOVCode, ""),
ln.OrderLineID,
)
if err != nil {
return nil, err
}
}
delete(existingOpen, ln.OrderLineID)
delete(existingOpenCombo, comboKey)
if ln.ClientKey.Valid {
lineResults = append(lineResults, OrderLineResult{
ClientKey: ln.ClientKey.String,
OrderLineID: ln.OrderLineID,
})
}
}
// Grid dışı kalan açık satırlar
for id := range existingOpen {
_, err := tx.Exec(`DELETE FROM BAGGI_V3.dbo.trOrderLineCurrency WHERE OrderLineID=@p1`, id)
if err != nil {
return nil, err
}
_, err = tx.Exec(`DELETE FROM BAGGI_V3.dbo.trOrderLine WHERE OrderLineID=@p1 AND ISNULL(IsClosed,0)=0`, id)
if err != nil {
return nil, err
}
}
if err := tx.Commit(); err != nil {
return nil, err
}
return lineResults, nil
}

View File

@@ -0,0 +1,109 @@
package queries
// 🔹 Sipariş ekranı stok kontrolü (ürün + renk + 2. renk + beden)
// 🔧 Renk1 boşsa (aksesuar vb.) renksiz stokları da çeker
const GetOrderInventory = `
DECLARE @ProductCode NVARCHAR(30) = @p1;
DECLARE @ColorCode NVARCHAR(30) = @p2;
DECLARE @ColorCode2 NVARCHAR(30) = @p3;
-- 🔧 Normalize giriş parametreleri
SET @ColorCode = LTRIM(RTRIM(ISNULL(@ColorCode, '')));
SET @ColorCode2 = LTRIM(RTRIM(ISNULL(@ColorCode2, '')));
IF @ColorCode = ''
BEGIN
SET @ColorCode = '';
END
IF @ColorCode2 = ''
BEGIN
SET @ColorCode2 = NULL;
END
------------------------------------------------------------
-- 🔹 Ana sorgu
------------------------------------------------------------
SELECT
Inventory.ItemCode AS Urun_Kodu,
Inventory.ColorCode AS Renk_Kodu,
ISNULL((
SELECT TOP 1 ColorDescription
FROM cdColorDesc WITH(NOLOCK)
WHERE cdColorDesc.ColorCode = Inventory.ColorCode
AND cdColorDesc.LangCode = N'TR'
), '') AS Renk_Aciklamasi,
-- ✅ NULL bedenleri boş string olarak getir
ISNULL(Inventory.ItemDim1Code, '') AS Beden,
ISNULL(Inventory.ItemDim2Code, '') AS Yaka,
ROUND(
SUM(Inventory.InventoryQty1) -
(SUM(Inventory.ReserveQty1) + SUM(Inventory.DispOrderQty1) + SUM(Inventory.PickingQty1)),
cdUnitOfMeasure.RoundDigit
) AS Kullanilabilir_Envanter
FROM cdItem WITH (NOLOCK)
JOIN cdUnitOfMeasure WITH (NOLOCK)
ON cdItem.UnitOfMeasureCode1 = cdUnitOfMeasure.UnitOfMeasureCode
JOIN (
SELECT
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
SUM(CASE WHEN SourceTable = 'PickingStates' THEN Qty1 ELSE 0 END) AS PickingQty1,
SUM(CASE WHEN SourceTable = 'ReserveStates' THEN Qty1 ELSE 0 END) AS ReserveQty1,
SUM(CASE WHEN SourceTable = 'DispOrderStates' THEN Qty1 ELSE 0 END) AS DispOrderQty1,
SUM(CASE WHEN SourceTable = 'trStock' THEN (In_Qty1 - Out_Qty1) ELSE 0 END) AS InventoryQty1
FROM (
SELECT 'PickingStates' AS SourceTable, CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
Qty1, 0 AS In_Qty1, 0 AS Out_Qty1
FROM PickingStates
UNION ALL
SELECT 'ReserveStates', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
Qty1, 0, 0
FROM ReserveStates
UNION ALL
SELECT 'DispOrderStates', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
Qty1, 0, 0
FROM DispOrderStates
UNION ALL
SELECT 'trStock', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
0, SUM(In_Qty1), SUM(Out_Qty1)
FROM trStock WITH (NOLOCK)
GROUP BY CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code
) AS SourceData
GROUP BY CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code
) AS Inventory
ON cdItem.ItemTypeCode = Inventory.ItemTypeCode
AND cdItem.ItemCode = Inventory.ItemCode
LEFT JOIN ProductAttributesFilter
ON ProductAttributesFilter.ItemCode = Inventory.ItemCode
WHERE
Inventory.ItemTypeCode IN (1)
AND Inventory.WarehouseCode IN ('1-0-12','1-0-21','1-0-10','1-0-2','1-1-3','1-2-4','1-2-5')
AND cdItem.IsBlocked = 0
AND Inventory.ItemCode = @ProductCode
AND (
-- 🔹 Eğer renk girilmişse o renk; boşsa renksiz stokları getir
(LTRIM(RTRIM(@ColorCode)) <> '' AND Inventory.ColorCode = @ColorCode)
OR (LTRIM(RTRIM(@ColorCode)) = '' AND (Inventory.ColorCode IS NULL OR LTRIM(RTRIM(Inventory.ColorCode)) = ''))
)
AND (
-- 🔹 2. renk sadece doluysa filtrelensin
@ColorCode2 IS NULL
OR Inventory.ItemDim2Code = @ColorCode2
)
GROUP BY
Inventory.ItemCode,
Inventory.ColorCode,
ISNULL(Inventory.ItemDim1Code, ''), -- ✅ NULL bedenleri boş string olarak gruplar
ISNULL(Inventory.ItemDim2Code, ''), -- ✅ NULL yakaları boş string olarak gruplar
cdUnitOfMeasure.RoundDigit;
`

213
svc/queries/orderlist.go Normal file
View File

@@ -0,0 +1,213 @@
package queries
import (
"bssapp-backend/auth"
"bssapp-backend/internal/authz"
"context"
"database/sql"
"fmt"
)
// ========================================================
// 📌 GetOrderList — FINAL + CURRENCY SAFE + PIYASA AUTHZ
// ========================================================
func GetOrderList(
ctx context.Context,
mssql *sql.DB,
pg *sql.DB,
search string,
) (*sql.Rows, error) {
claims, ok := auth.GetClaimsFromContext(ctx)
if !ok || claims == nil {
return nil, fmt.Errorf("unauthorized: claims not found")
}
// ----------------------------------------------------
// 🔐 PIYASA FILTER (ADMIN BYPASS)
// ----------------------------------------------------
piyasaWhere := "1=1"
if !claims.IsAdmin() {
codes, err := authz.GetUserPiyasaCodes(pg, int(claims.ID))
if err != nil {
return nil, fmt.Errorf("piyasa codes load error: %w", err)
}
if len(codes) == 0 {
// hiç yetkisi yok → hiç kayıt dönmesin
piyasaWhere = "1=0"
} else {
// ⚠️ EXISTS içinde kullanılacak
piyasaWhere = authz.BuildINClause(
"UPPER(f2.CustomerAtt01)",
codes,
)
}
}
// ----------------------------------------------------
// 📄 BASE QUERY
// ----------------------------------------------------
baseQuery := fmt.Sprintf(`
SELECT
CAST(h.OrderHeaderID AS NVARCHAR(50)) AS OrderHeaderID,
ISNULL(h.OrderNumber, '') AS OrderNumber,
CONVERT(varchar, h.OrderDate, 23) AS OrderDate,
ISNULL(h.CurrAccCode, '') AS CurrAccCode,
ISNULL(ca.CurrAccDescription, '') AS CurrAccDescription,
ISNULL(mt.AttributeDescription, '') AS MusteriTemsilcisi,
ISNULL(py.AttributeDescription, '') AS Piyasa,
CONVERT(varchar, h.CreditableConfirmedDate,23) AS CreditableConfirmedDate,
ISNULL(h.DocCurrencyCode,'TRY') AS DocCurrencyCode,
ISNULL(l.TotalAmount,0) AS TotalAmount,
----------------------------------------------------------------
-- USD HESABI (TRY / EUR / GBP / USD DESTEKLİ)
----------------------------------------------------------------
CASE
WHEN h.DocCurrencyCode = 'USD'
THEN ISNULL(l.TotalAmount,0)
WHEN h.DocCurrencyCode = 'TRY'
AND usd.Rate > 0
THEN ISNULL(l.TotalAmount,0) / usd.Rate
WHEN h.DocCurrencyCode IN ('EUR','GBP')
AND cur.Rate > 0
AND usd.Rate > 0
THEN (ISNULL(l.TotalAmount,0) * cur.Rate) / usd.Rate
ELSE 0
END AS TotalAmountUSD,
ISNULL(h.IsCreditableConfirmed,0) AS IsCreditableConfirmed,
ISNULL(h.Description,'') AS Description,
usd.Rate AS ExchangeRateUSD
FROM dbo.trOrderHeader h
JOIN (
SELECT
OrderHeaderID,
SUM(Qty1 * Price) AS TotalAmount
FROM dbo.trOrderLine
GROUP BY OrderHeaderID
) l
ON l.OrderHeaderID = h.OrderHeaderID
LEFT JOIN dbo.cdCurrAccDesc ca
ON ca.CurrAccCode = h.CurrAccCode
AND ca.LangCode = 'TR'
-- müşteri temsilcisi + piyasa açıklamaları
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
----------------------------------------------------------------
-- ORDER PB → 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
-- 🔐 PIYASA AUTHZ (EXISTS — SAĞLAM YOL)
AND EXISTS (
SELECT 1
FROM dbo.CustomerAttributesFilter f2
WHERE f2.CurrAccCode = h.CurrAccCode
AND %s
)
`, piyasaWhere)
// ----------------------------------------------------
// 🔍 SEARCH FILTER (CASE + TR SAFE)
// ----------------------------------------------------
if search != "" {
baseQuery += `
AND EXISTS (
SELECT 1
FROM dbo.trOrderHeader h2
LEFT JOIN dbo.cdCurrAccDesc ca2
ON ca2.CurrAccCode = h2.CurrAccCode
AND ca2.LangCode = 'TR'
WHERE h2.OrderHeaderID = h.OrderHeaderID
AND (
LOWER(REPLACE(REPLACE(h2.OrderNumber,'İ','I'),'ı','i'))
COLLATE Latin1_General_CI_AI LIKE LOWER(@p1)
OR LOWER(REPLACE(REPLACE(h2.CurrAccCode,'İ','I'),'ı','i'))
COLLATE Latin1_General_CI_AI LIKE LOWER(@p1)
OR LOWER(REPLACE(REPLACE(ca2.CurrAccDescription,'İ','I'),'ı','i'))
COLLATE Latin1_General_CI_AI LIKE LOWER(@p1)
OR LOWER(REPLACE(REPLACE(h2.Description,'İ','I'),'ı','i'))
COLLATE Latin1_General_CI_AI LIKE LOWER(@p1)
)
)
`
}
// ----------------------------------------------------
// 📌 ORDER
// ----------------------------------------------------
baseQuery += `
ORDER BY h.CreatedDate DESC
`
// ----------------------------------------------------
// ▶ EXECUTE
// ----------------------------------------------------
if search != "" {
searchLike := fmt.Sprintf("%%%s%%", search)
return mssql.Query(baseQuery, searchLike)
}
return mssql.Query(baseQuery)
}

View File

@@ -0,0 +1,75 @@
package queries
const OrderListBaseQuery = `
SELECT
CAST(h.OrderHeaderID AS NVARCHAR(50)) AS OrderHeaderID,
ISNULL(h.OrderNumber, '') AS OrderNumber,
CONVERT(varchar, h.OrderDate, 23) AS OrderDate,
ISNULL(h.CurrAccCode, '') AS CurrAccCode,
ISNULL(ca.CurrAccDescription, '') AS CurrAccDescription,
ISNULL(mt.AttributeDescription, '') AS MusteriTemsilcisi,
ISNULL(py.AttributeDescription, '') AS Piyasa,
CONVERT(varchar, h.CreditableConfirmedDate, 23) AS CreditableConfirmedDate,
ISNULL(h.DocCurrencyCode, 'TRY') AS DocCurrencyCode,
ISNULL(l.TotalAmount, 0) AS TotalAmount,
CASE
WHEN h.DocCurrencyCode = 'USD'
THEN ISNULL(l.TotalAmount, 0)
WHEN h.DocCurrencyCode = 'TRY'
THEN ISNULL(l.TotalAmount, 0) / NULLIF(er.Rate, 1)
ELSE 0
END AS TotalAmountUSD,
ISNULL(h.IsCreditableConfirmed, 0) AS IsCreditableConfirmed,
ISNULL(h.Description, '') AS Description,
ISNULL(er.Rate, 1) AS ExchangeRateUSD
FROM dbo.trOrderHeader h
JOIN (
SELECT OrderHeaderID, SUM(Qty1 * Price) AS TotalAmount
FROM dbo.trOrderLine
GROUP BY OrderHeaderID
) l ON l.OrderHeaderID = h.OrderHeaderID
LEFT JOIN dbo.cdCurrAccDesc ca
ON ca.CurrAccCode = h.CurrAccCode AND ca.LangCode = 'TR'
LEFT JOIN dbo.CustomerAttributes f
ON f.CurrAccTypeCode = h.CurrAccTypeCode
AND f.CurrAccCode = h.CurrAccCode
LEFT JOIN dbo.cdCurrAccAttributeDesc mt
ON mt.CurrAccTypeCode = f.CurrAccTypeCode
AND mt.AttributeTypeCode = 2
AND mt.AttributeCode = f.CustomerAtt02
AND mt.LangCode = 'TR'
LEFT JOIN dbo.cdCurrAccAttributeDesc py
ON py.CurrAccTypeCode = f.CurrAccTypeCode
AND py.AttributeTypeCode = 3
AND py.AttributeCode = f.CustomerAtt03
AND py.LangCode = 'TR'
OUTER APPLY (
SELECT TOP 1 Rate
FROM dbo.AllExchangeRates er
WHERE er.CurrencyCode = 'USD'
AND er.RelationCurrencyCode = 'TRY'
AND er.ExchangeTypeCode = 6
AND er.Rate > 0
ORDER BY ABS(DATEDIFF(DAY, er.Date, GETDATE()))
) er
WHERE
ISNULL(h.IsCancelOrder, 0) = 0
AND h.OrderTypeCode = 1
AND h.ProcessCode = 'WS'
AND h.IsClosed = 0
`

View File

@@ -0,0 +1,36 @@
package queries
import (
"bssapp-backend/models"
"database/sql"
"fmt"
)
// GetOrderPriceListB2B → model + currency bazlı ürün fiyatını döndürür (PostgreSQL sürümü)
func GetOrderPriceListB2B(db *sql.DB, modelCode string, currency string) (*models.OrderPriceListB2B, error) {
query := `
SELECT
mmitem.code AS ModelCode,
sdprc.crn AS CurrencyCode,
sdprc.prc AS Price,
sdprc.sdprcgrp_id AS PriceGroupID,
TO_CHAR(sdprc.zlins_dttm, 'YYYY-MM-DD') AS LastUpdate
FROM sdprc
LEFT JOIN mmitem ON sdprc.mmitem_id = mmitem.id
WHERE mmitem.code = $1
AND sdprc.prc IS NOT NULL
AND sdprc.prc > 0
AND sdprc.crn = $2
AND sdprc.sdprcgrp_id = 1
ORDER BY sdprc.zlins_dttm DESC
LIMIT 1;
`
row := db.QueryRow(query, modelCode, currency)
var p models.OrderPriceListB2B
err := row.Scan(&p.ModelCode, &p.CurrencyCode, &p.Price, &p.PriceGroupID, &p.LastUpdate)
if err != nil {
return nil, fmt.Errorf("ürün fiyatı bulunamadı: %v", err)
}
return &p, nil
}

View File

@@ -0,0 +1,47 @@
package queries
/* ======================================================
ROLE + DEPARTMENT PERMISSIONS
====================================================== */
// GET
const GetRoleDepartmentPermissions = `
SELECT
rdp.module_code,
rdp.action,
rdp.allowed
FROM vw_role_dept_permissions rdp
WHERE rdp.role_id = $1
AND rdp.department_code = $2
ORDER BY rdp.module_code, rdp.action
`
// UPSERT
const UpsertRoleDepartmentPermission = `
INSERT INTO mk_sys_role_department_permissions
(
role_id,
department_code,
module_code,
action,
allowed
)
VALUES ($1,$2,$3,$4,$5)
ON CONFLICT ON CONSTRAINT uq_role_dept_module_action
DO UPDATE SET
allowed = EXCLUDED.allowed;
`
// ======================================================
// 📦 MODULES
// ======================================================
const GetModuleLookup = `
SELECT
code AS value,
name AS label
FROM mk_sys_modules
ORDER BY id
`

34
svc/queries/product.go Normal file
View File

@@ -0,0 +1,34 @@
package queries
import (
"bssapp-backend/db"
"bssapp-backend/models"
)
// GetProductList → MSSQL'den ürün listesini döndürür
func GetProductList() ([]models.Product, error) {
rows, err := db.MssqlDB.Query(`
SELECT
ProductCode
FROM ProductFilterWithDescription('TR')
WHERE
ProductAtt42 IN ('SERI', 'AKSESUAR')
AND IsBlocked = 0
AND LEN(ProductCode) = 13 -- 🔹 yalnızca 13 karakterlik kodlar
ORDER BY ProductCode;
`)
if err != nil {
return nil, err
}
defer rows.Close()
var list []models.Product
for rows.Next() {
var p models.Product
if err := rows.Scan(&p.ProductCode); err != nil {
return nil, err
}
list = append(list, p)
}
return list, nil
}

View File

@@ -0,0 +1,20 @@
package queries
const GetProductColors = `
DECLARE @ProductCode VARCHAR(30) = @p1;
SELECT DISTINCT
p.ProductCode,
v.ColorCode,
ISNULL(cd.ColorDescription, '') AS ColorDescription
FROM ProductFilterWithDescription('TR') AS p
INNER JOIN prItemVariant AS v WITH(NOLOCK)
ON v.ItemCode = p.ProductCode
LEFT JOIN cdColorDesc AS cd WITH(NOLOCK)
ON cd.ColorCode = v.ColorCode
AND cd.LangCode = 'TR'
WHERE
p.ProductCode = @ProductCode
AND ISNULL(v.ColorCode, '') <> ''
ORDER BY v.ColorCode;
`

View File

@@ -0,0 +1,66 @@
package queries
const GetProductColorSizes = `
------------------------------------------------------------
-- 🧩 GetProductColorSizes
-- Kullanım:
-- @p1 = ProductCode
-- @p2 = ColorCode
-- @p3 = ColorCode2
-- Açıklama:
-- Renk veya ikinci renk boşsa buna göre filtre uygular.
-- Renksiz (aksesuar) ürünlerde ColorCode='' kayıtlarını getirir.
------------------------------------------------------------
DECLARE @ProductCode NVARCHAR(30) = @p1;
DECLARE @ColorCode NVARCHAR(30) = @p2;
DECLARE @ColorCode2 NVARCHAR(30) = @p3;
-- 🔧 Normalize parametreler (boşlukları kırp ve düzelt)
SET @ColorCode = LTRIM(RTRIM(ISNULL(@ColorCode, '')));
SET @ColorCode2 = LTRIM(RTRIM(ISNULL(@ColorCode2, '')));
IF @ColorCode = ''
BEGIN
SET @ColorCode = ''; -- tamamen renksiz ürün
END
IF @ColorCode2 = ''
BEGIN
SET @ColorCode2 = NULL; -- 2. renk yok
END
------------------------------------------------------------
-- 🔹 Ana sorgu
------------------------------------------------------------
SELECT
Product.ProductCode,
ISNULL(prItemVariant.ColorCode, '') AS ColorCode,
ISNULL(prItemVariant.ItemDim1Code, '') AS ItemDim1Code,
ISNULL(prItemVariant.ItemDim2Code, '') AS ItemDim2Code
FROM ProductFilterWithDescription('TR') AS Product
INNER JOIN cdItem WITH(NOLOCK)
ON cdItem.ItemTypeCode = 1
AND cdItem.ItemCode = Product.ProductCode
LEFT JOIN prProductLot WITH(NOLOCK)
ON prProductLot.ItemTypeCode = cdItem.ItemTypeCode
AND prProductLot.ItemCode = cdItem.ItemCode
AND prProductLot.IsDefault = 1
LEFT JOIN prItemVariant WITH(NOLOCK)
ON prItemVariant.ItemCode = Product.ProductCode
WHERE
ProductAtt42 IN ('SERI','AKSESUAR')
AND Product.IsBlocked = 0
AND Product.ProductCode = @ProductCode
AND (
-- 🔸 1. renk doluysa o renk
(LTRIM(RTRIM(@ColorCode)) <> '' AND prItemVariant.ColorCode = @ColorCode)
-- 🔸 1. renk boşsa renksiz varyantlar
OR (LTRIM(RTRIM(@ColorCode)) = '' AND (prItemVariant.ColorCode IS NULL OR LTRIM(RTRIM(prItemVariant.ColorCode)) = ''))
-- 🔸 2. renk varsa ek olarak dahil et
OR (@ColorCode2 IS NOT NULL AND prItemVariant.ColorCode = @ColorCode2)
)
ORDER BY
prItemVariant.ItemDim1Code,
prItemVariant.ItemDim2Code;
`

View File

@@ -0,0 +1,36 @@
package queries
const GetProductDetail = `
DECLARE @ProductCode VARCHAR(30) = @p1;
SELECT
ProductCode,
'' AS BOS7,
ProductDescription,
Product.ProductAtt42,
Product.ProductAtt42Desc AS URUN_ILK_GRUBU,
Product.ProductAtt01,
Product.ProductAtt01Desc AS URUN_ANA_GRUBU,
Product.ProductAtt02,
Product.ProductAtt02Desc AS URUN_ALT_GRUBU,
Product.ProductAtt41,
Product.ProductAtt41Desc AS ICERIK,
Product.ProductAtt10,
Product.ProductAtt10Desc AS MARKA,
Product.ProductAtt11,
Product.ProductAtt11Desc AS DROP_,
Product.ProductAtt29,
Product.ProductAtt29Desc AS KARISIM,
Product.ProductAtt45,
Product.ProductAtt45Desc AS ASKILI_YAN,
Product.ProductAtt44,
Product.ProductAtt44Desc AS KATEGORI,
Product.ProductAtt38,
Product.ProductAtt38Desc AS FIT1,
Product.ProductAtt39,
Product.ProductAtt39Desc AS FIT2
FROM ProductFilterWithDescription('TR') AS Product
WHERE ProductAtt42 IN ('SERI','AKSESUAR')
AND IsBlocked = 0
AND ProductCode = @p1;
`

View File

@@ -0,0 +1,18 @@
package queries
const GetProductSecondColors = `
SELECT DISTINCT
Product.ProductCode,
ISNULL(prItemVariant.ColorCode, '') AS ColorCode,
ISNULL(prItemVariant.ItemDim2Code, '') AS ItemDim2Code
FROM prItemVariant WITH(NOLOCK)
INNER JOIN ProductFilterWithDescription('TR') AS Product
ON prItemVariant.ItemCode = Product.ProductCode
LEFT JOIN cdColorDesc AS ColorDesc WITH(NOLOCK)
ON ColorDesc.ColorCode = prItemVariant.ItemDim2Code
AND ColorDesc.LangCode = 'TR'
WHERE Product.ProductCode = @ProductCode
AND prItemVariant.ColorCode = @ColorCode
AND ISNULL(prItemVariant.ItemDim2Code, '') <> ''
GROUP BY Product.ProductCode, prItemVariant.ItemDim2Code, prItemVariant.ColorCode
`

View File

@@ -0,0 +1,172 @@
package queries
import (
"bssapp-backend/db"
"bssapp-backend/models"
"database/sql"
"fmt"
"strings"
)
// Ana tabloyu getiren fonksiyon (Vue header tablosu için)
func GetStatements(params models.StatementParams) ([]models.StatementHeader, error) {
// AccountCode normalize: "ZLA0127" → "ZLA 0127"
if len(params.AccountCode) == 7 && strings.ContainsAny(params.AccountCode, "0123456789") {
params.AccountCode = params.AccountCode[:3] + " " + params.AccountCode[3:]
}
// Parislemler []string → '1','2','3'
parislemFilter := "''"
if len(params.Parislemler) > 0 {
quoted := make([]string, len(params.Parislemler))
for i, v := range params.Parislemler {
quoted[i] = fmt.Sprintf("'%s'", v)
}
parislemFilter = strings.Join(quoted, ",")
}
query := fmt.Sprintf(`
;WITH Opening AS (
SELECT
b.CurrAccCode AS Cari_Kod,
b.DocCurrencyCode AS Para_Birimi,
SUM(c.Debit - c.Credit) AS Devir_Bakiyesi
FROM trCurrAccBook b
LEFT JOIN trCurrAccBookCurrency c
ON c.CurrAccBookID = b.CurrAccBookID
AND c.CurrencyCode = b.DocCurrencyCode
WHERE b.CurrAccCode LIKE '%%' + @Carikod + '%%'
AND b.DocumentDate < @startdate
AND EXISTS (
SELECT 1
FROM CurrAccBookATAttributesFilter f2
WHERE f2.CurrAccBookID = b.CurrAccBookID
AND f2.ATAtt01 IN (%s)
)
GROUP BY b.CurrAccCode, b.DocCurrencyCode
),
Movements AS (
SELECT
b.CurrAccCode AS Cari_Kod,
d.CurrAccDescription AS Cari_Isim,
CONVERT(varchar(10), b.DocumentDate, 23) AS Belge_Tarihi,
CONVERT(varchar(10), b.DueDate, 23) AS Vade_Tarihi,
b.RefNumber AS Belge_No,
b.BaseApplicationCode AS Islem_Tipi,
b.LineDescription AS Aciklama,
b.DocCurrencyCode AS Para_Birimi,
c.Debit AS Borc,
c.Credit AS Alacak,
SUM(c.Debit - c.Credit)
OVER (PARTITION BY b.CurrAccCode, c.CurrencyCode
ORDER BY b.DocumentDate, b.CurrAccBookID) AS Hareket_Bakiyesi,
f.ATAtt01 AS Parislemtipi
FROM trCurrAccBook b
LEFT JOIN cdCurrAccDesc d
ON b.CurrAccCode = d.CurrAccCode
LEFT JOIN trCurrAccBookCurrency c
ON b.CurrAccBookID = c.CurrAccBookID
AND b.DocCurrencyCode = c.CurrencyCode
LEFT JOIN CurrAccBookATAttributesFilter f
ON b.CurrAccBookID = f.CurrAccBookID
WHERE b.CurrAccCode LIKE '%%' + @Carikod + '%%'
AND b.DocumentDate BETWEEN @startdate AND @enddate
AND EXISTS (
SELECT 1
FROM CurrAccBookATAttributesFilter f2
WHERE f2.CurrAccBookID = b.CurrAccBookID
AND f2.ATAtt01 IN (%s)
)
)
SELECT
m.Cari_Kod,
m.Cari_Isim,
m.Belge_Tarihi,
m.Vade_Tarihi,
m.Belge_No,
m.Islem_Tipi,
m.Aciklama,
m.Para_Birimi,
m.Borc,
m.Alacak,
ISNULL(o.Devir_Bakiyesi,0) + m.Hareket_Bakiyesi AS Bakiye,
m.Parislemtipi AS Parislemler
FROM Movements m
LEFT JOIN Opening o
ON o.Cari_Kod = m.Cari_Kod
AND o.Para_Birimi = m.Para_Birimi
UNION ALL
-- Devir satırı
SELECT
@Carikod AS Cari_Kod,
MAX(d.CurrAccDescription) AS Cari_Isim,
CONVERT(varchar(10), @startdate, 23) AS Belge_Tarihi,
CONVERT(varchar(10), @startdate, 23) AS Vade_Tarihi,
'Baslangic_devir' AS Belge_No,
'Devir' AS Islem_Tipi,
'Devir Bakiyesi' AS Aciklama,
b.DocCurrencyCode AS Para_Birimi,
SUM(c.Debit) AS Borc,
SUM(c.Credit) AS Alacak,
SUM(c.Debit) - SUM(c.Credit) AS Bakiye,
(
SELECT STRING_AGG(x.ATAtt01, ',')
FROM (
SELECT DISTINCT f2.ATAtt01
FROM CurrAccBookATAttributesFilter f2
INNER JOIN trCurrAccBook bb
ON f2.CurrAccBookID = bb.CurrAccBookID
WHERE bb.CurrAccCode LIKE '%%' + @Carikod + '%%'
AND bb.DocumentDate < @startdate
AND f2.ATAtt01 IN (%s)
) x
) AS Parislemler
FROM trCurrAccBook b
LEFT JOIN cdCurrAccDesc d
ON b.CurrAccCode = d.CurrAccCode
LEFT JOIN trCurrAccBookCurrency c
ON b.CurrAccBookID = c.CurrAccBookID
AND b.DocCurrencyCode = c.CurrencyCode
WHERE b.CurrAccCode LIKE '%%' + @Carikod + '%%'
AND b.DocumentDate < @startdate
GROUP BY b.DocCurrencyCode
ORDER BY Para_Birimi, Belge_Tarihi;
`, parislemFilter, parislemFilter, parislemFilter)
rows, err := db.MssqlDB.Query(query,
sql.Named("startdate", params.StartDate),
sql.Named("enddate", params.EndDate),
sql.Named("Carikod", params.AccountCode),
sql.Named("LangCode", params.LangCode),
)
if err != nil {
return nil, fmt.Errorf("MSSQL query error: %v", err)
}
defer rows.Close()
var results []models.StatementHeader
for rows.Next() {
var r models.StatementHeader
if err := rows.Scan(
&r.CariKod,
&r.CariIsim,
&r.BelgeTarihi,
&r.VadeTarihi,
&r.BelgeNo,
&r.IslemTipi,
&r.Aciklama,
&r.ParaBirimi,
&r.Borc,
&r.Alacak,
&r.Bakiye,
&r.Parislemler,
); err != nil {
return nil, err
}
results = append(results, r)
}
return results, nil
}

View File

@@ -0,0 +1,187 @@
// queries/statements_header_pdf.go
package queries
import (
"bssapp-backend/db"
"bssapp-backend/models"
"database/sql"
"fmt"
"log"
"strings"
)
// küçük yardımcı: boşlukları temizle, her değeri ayrı tırnakla sar
func buildQuotedHList(vals []string) string {
var pp []string
for _, v := range vals {
v = strings.TrimSpace(v)
if v != "" {
pp = append(pp, fmt.Sprintf("'%s'", v)) // '1','2' gibi
}
}
if len(pp) == 0 {
return ""
}
return strings.Join(pp, ",")
}
/* ============================ HEADER (Ana Tablo) ============================ */
func GetStatementsHPDF(accountCode, startDate, endDate string, parislemler []string) ([]models.StatementHeader, []string, error) {
// Account normalize
if len(accountCode) == 7 && strings.ContainsAny(accountCode, "0123456789") {
accountCode = accountCode[:3] + " " + accountCode[3:]
}
// IN list parse et
inList := buildQuotedHList(parislemler)
parislemCond := "''"
if inList != "" {
parislemCond = inList
}
query := fmt.Sprintf(`
;WITH Opening AS (
SELECT
b.CurrAccCode AS Cari_Kod,
b.DocCurrencyCode AS Para_Birimi,
SUM(c.Debit - c.Credit) AS Devir_Bakiyesi
FROM trCurrAccBook b
LEFT JOIN trCurrAccBookCurrency c
ON c.CurrAccBookID = b.CurrAccBookID
AND c.CurrencyCode = b.DocCurrencyCode
WHERE b.CurrAccCode LIKE @Carikod
AND b.DocumentDate < @StartDate
AND EXISTS (
SELECT 1
FROM CurrAccBookATAttributesFilter f2
WHERE f2.CurrAccBookID = b.CurrAccBookID
AND f2.ATAtt01 IN (%s)
)
GROUP BY b.CurrAccCode, b.DocCurrencyCode
),
Movements AS (
SELECT
b.CurrAccCode AS Cari_Kod,
d.CurrAccDescription AS Cari_Isim,
CONVERT(varchar(10), b.DocumentDate, 23) AS Belge_Tarihi,
CONVERT(varchar(10), b.DueDate, 23) AS Vade_Tarihi,
b.RefNumber AS Belge_No,
b.BaseApplicationCode AS Islem_Tipi,
b.LineDescription AS Aciklama,
b.DocCurrencyCode AS Para_Birimi,
c.Debit AS Borc,
c.Credit AS Alacak,
SUM(c.Debit - c.Credit)
OVER (PARTITION BY b.CurrAccCode, c.CurrencyCode
ORDER BY b.DocumentDate, b.CurrAccBookID) AS Hareket_Bakiyesi,
f.ATAtt01 AS Parislemler
FROM trCurrAccBook b
LEFT JOIN cdCurrAccDesc d
ON b.CurrAccCode = d.CurrAccCode AND d.LangCode = 'TR'
LEFT JOIN trCurrAccBookCurrency c
ON b.CurrAccBookID = c.CurrAccBookID
AND b.DocCurrencyCode = c.CurrencyCode
LEFT JOIN CurrAccBookATAttributesFilter f
ON b.CurrAccBookID = f.CurrAccBookID
WHERE b.CurrAccCode LIKE @Carikod
AND b.DocumentDate BETWEEN @StartDate AND @EndDate
AND EXISTS (
SELECT 1
FROM CurrAccBookATAttributesFilter f2
WHERE f2.CurrAccBookID = b.CurrAccBookID
AND f2.ATAtt01 IN (%s)
)
)`, parislemCond, parislemCond)
query += fmt.Sprintf(`
SELECT
m.Cari_Kod,
m.Cari_Isim,
m.Belge_Tarihi,
m.Vade_Tarihi,
m.Belge_No,
m.Islem_Tipi,
m.Aciklama,
m.Para_Birimi,
m.Borc,
m.Alacak,
ISNULL(o.Devir_Bakiyesi,0) + m.Hareket_Bakiyesi AS Bakiye,
m.Parislemler
FROM Movements m
LEFT JOIN Opening o
ON o.Cari_Kod = m.Cari_Kod
AND o.Para_Birimi = m.Para_Birimi
UNION ALL
-- Devir satırı
SELECT
@Carikod AS Cari_Kod,
MAX(d.CurrAccDescription) AS Cari_Isim,
CONVERT(varchar(10), @StartDate, 23) AS Belge_Tarihi,
CONVERT(varchar(10), @StartDate, 23) AS Vade_Tarihi,
'Baslangic_devir' AS Belge_No,
'Devir' AS Islem_Tipi,
'Devir Bakiyesi' AS Aciklama,
b.DocCurrencyCode AS Para_Birimi,
SUM(c.Debit) AS Borc,
SUM(c.Credit) AS Alacak,
SUM(c.Debit) - SUM(c.Credit) AS Bakiye,
(
SELECT STRING_AGG(x.ATAtt01, ',')
FROM (
SELECT DISTINCT f2.ATAtt01
FROM CurrAccBookATAttributesFilter f2
INNER JOIN trCurrAccBook bb
ON f2.CurrAccBookID = bb.CurrAccBookID
WHERE bb.CurrAccCode LIKE @Carikod
AND bb.DocumentDate < @StartDate
AND f2.ATAtt01 IN (%s)
) x
) AS Parislemler
FROM trCurrAccBook b
LEFT JOIN cdCurrAccDesc d
ON b.CurrAccCode = d.CurrAccCode AND d.LangCode = 'TR'
LEFT JOIN trCurrAccBookCurrency c
ON b.CurrAccBookID = c.CurrAccBookID
AND b.DocCurrencyCode = c.CurrencyCode
WHERE b.CurrAccCode LIKE @Carikod
AND b.DocumentDate < @StartDate
GROUP BY b.DocCurrencyCode
ORDER BY Para_Birimi, Belge_Tarihi;`, parislemCond)
rows, err := db.MssqlDB.Query(query,
sql.Named("Carikod", "%"+accountCode+"%"),
sql.Named("StartDate", startDate),
sql.Named("EndDate", endDate),
)
if err != nil {
log.Printf("❌ Header sorgu hatası: %v", err)
return nil, nil, fmt.Errorf("header sorgu hatası: %v", err)
}
defer rows.Close()
var headers []models.StatementHeader
var belgeNos []string
for rows.Next() {
var h models.StatementHeader
if err := rows.Scan(
&h.CariKod, &h.CariIsim,
&h.BelgeTarihi, &h.VadeTarihi,
&h.BelgeNo, &h.IslemTipi,
&h.Aciklama, &h.ParaBirimi,
&h.Borc, &h.Alacak,
&h.Bakiye, &h.Parislemler,
); err != nil {
log.Printf("❌ Header scan hatası: %v", err)
return nil, nil, err
}
headers = append(headers, h)
if h.BelgeNo != "" {
belgeNos = append(belgeNos, h.BelgeNo)
}
}
log.Printf("✅ Header verileri alındı: %d kayıt, %d belge no", len(headers), len(belgeNos))
return headers, belgeNos, nil
}

View File

@@ -0,0 +1,121 @@
package queries
import (
"bssapp-backend/db"
"bssapp-backend/models"
"database/sql"
"fmt"
"strings"
)
/* ============================ DETAIL (ALT TABLO) ============================ */
func GetStatementDetails(accountCode, startDate, endDate string, parislemler []string) ([]models.StatementDetail, error) {
// Parislemler filtresi hazırlanır (ör: 1,2,3)
inParislem := ""
if len(parislemler) > 0 {
pp := make([]string, len(parislemler))
for i, v := range parislemler {
pp[i] = strings.TrimSpace(v)
}
inParislem = strings.Join(pp, ",")
}
query := fmt.Sprintf(`
SELECT
CONVERT(varchar(10), a.InvoiceDate, 23) AS Belge_Tarihi,
a.InvoiceNumber AS Belge_Ref_Numarasi,
COALESCE(MAX(AnaGrupDesc.AttributeDescription), '') AS Urun_Ana_Grubu,
COALESCE(MAX(AltGrupDesc.AttributeDescription), '') AS Urun_Alt_Grubu,
COALESCE(MAX(GarsonDesc.AttributeDescription), '') AS Yetiskin_Garson,
COALESCE(MAX(FitDesc.AttributeDescription), '') AS Fit,
COALESCE(MAX(KisaKarDesc.AttributeDescription), '') AS Icerik,
a.ItemCode AS Urun_Kodu,
a.ColorCode AS Urun_Rengi,
SUM(a.Qty1) AS Toplam_Adet,
SUM(ABS(a.Doc_Price)) AS Toplam_Fiyat,
CAST(SUM(a.Qty1 * ABS(a.Doc_Price)) AS numeric(18,2)) AS Toplam_Tutar
FROM AllInvoicesWithAttributes a
LEFT JOIN prItemAttribute AnaGrup
ON a.ItemCode = AnaGrup.ItemCode AND AnaGrup.AttributeTypeCode = 1
LEFT JOIN cdItemAttributeDesc AnaGrupDesc
ON AnaGrup.AttributeTypeCode = AnaGrupDesc.AttributeTypeCode
AND AnaGrup.AttributeCode = AnaGrupDesc.AttributeCode
AND AnaGrup.ItemTypeCode = AnaGrupDesc.ItemTypeCode
LEFT JOIN prItemAttribute AltGrup
ON a.ItemCode = AltGrup.ItemCode AND AltGrup.AttributeTypeCode = 2
LEFT JOIN cdItemAttributeDesc AltGrupDesc
ON AltGrup.AttributeTypeCode = AltGrupDesc.AttributeTypeCode
AND AltGrup.AttributeCode = AltGrupDesc.AttributeCode
AND AltGrup.ItemTypeCode = AltGrupDesc.ItemTypeCode
LEFT JOIN prItemAttribute Garson
ON a.ItemCode = Garson.ItemCode AND Garson.AttributeTypeCode = 44
LEFT JOIN cdItemAttributeDesc GarsonDesc
ON Garson.AttributeTypeCode = GarsonDesc.AttributeTypeCode
AND Garson.AttributeCode = GarsonDesc.AttributeCode
AND Garson.ItemTypeCode = GarsonDesc.ItemTypeCode
LEFT JOIN prItemAttribute FitTbl
ON a.ItemCode = FitTbl.ItemCode AND FitTbl.AttributeTypeCode = 38
LEFT JOIN cdItemAttributeDesc FitDesc
ON FitTbl.AttributeTypeCode = FitDesc.AttributeTypeCode
AND FitTbl.AttributeCode = FitDesc.AttributeCode
AND FitTbl.ItemTypeCode = FitDesc.ItemTypeCode
LEFT JOIN prItemAttribute KisaKar
ON a.ItemCode = KisaKar.ItemCode AND KisaKar.AttributeTypeCode = 41
LEFT JOIN cdItemAttributeDesc KisaKarDesc
ON KisaKar.AttributeTypeCode = KisaKarDesc.AttributeTypeCode
AND KisaKar.AttributeCode = KisaKarDesc.AttributeCode
AND KisaKar.ItemTypeCode = KisaKarDesc.ItemTypeCode
WHERE a.CurrAccCode LIKE @Carikod
AND a.InvoiceDate BETWEEN @StartDate AND @EndDate
%s
GROUP BY a.InvoiceDate, a.InvoiceNumber, a.ItemCode, a.ColorCode
ORDER BY Belge_Tarihi, Belge_Ref_Numarasi, Urun_Kodu;`,
func() string {
if inParislem == "" {
return ""
}
return fmt.Sprintf(`AND EXISTS (
SELECT 1
FROM CurrAccBookATAttributesFilter f
WHERE f.CurrAccBookID = a.CurrAccBookID
AND f.ATAtt01 IN (%s)
)`, inParislem)
}(),
)
rows, err := db.MssqlDB.Query(query,
sql.Named("Carikod", "%"+accountCode+"%"),
sql.Named("StartDate", startDate),
sql.Named("EndDate", endDate),
)
if err != nil {
return nil, fmt.Errorf("detay sorgu hatası: %v", err)
}
defer rows.Close()
var results []models.StatementDetail
for rows.Next() {
var d models.StatementDetail
if err := rows.Scan(
&d.BelgeTarihi,
&d.BelgeRefNumarasi,
&d.UrunAnaGrubu,
&d.UrunAltGrubu,
&d.YetiskinGarson,
&d.Fit,
&d.Icerik,
&d.UrunKodu,
&d.UrunRengi,
&d.ToplamAdet,
&d.ToplamFiyat,
&d.ToplamTutar,
); err != nil {
return nil, err
}
results = append(results, d)
}
return results, nil
}

View File

@@ -0,0 +1,305 @@
// queries/statements_pdf.go
package queries
import (
"bssapp-backend/db"
"bssapp-backend/models"
"database/sql"
"fmt"
"log"
"strings"
)
// küçük yardımcı: boşlukları temizle, her değeri ayrı tırnakla sar
func buildQuotedList(vals []string) string {
var pp []string
for _, v := range vals {
v = strings.TrimSpace(v)
if v != "" {
pp = append(pp, fmt.Sprintf("'%s'", v)) // '1','2' gibi
}
}
if len(pp) == 0 {
return ""
}
return strings.Join(pp, ",")
}
/* ============================ HEADER (Ana Tablo) ============================ */
func GetStatementsPDF(accountCode, startDate, endDate string, parislemler []string) ([]models.StatementHeader, []string, error) {
// Account normalize
if len(accountCode) == 7 && strings.ContainsAny(accountCode, "0123456789") {
accountCode = accountCode[:3] + " " + accountCode[3:]
}
// IN list parse et
inList := buildQuotedList(parislemler)
parislemCond := "''"
if inList != "" {
parislemCond = inList
}
query := fmt.Sprintf(`
;WITH Opening AS (
SELECT
b.CurrAccCode AS Cari_Kod,
b.DocCurrencyCode AS Para_Birimi,
SUM(c.Debit - c.Credit) AS Devir_Bakiyesi
FROM trCurrAccBook b
LEFT JOIN trCurrAccBookCurrency c
ON c.CurrAccBookID = b.CurrAccBookID
AND c.CurrencyCode = b.DocCurrencyCode
WHERE b.CurrAccCode LIKE @Carikod
AND b.DocumentDate < @StartDate
AND EXISTS (
SELECT 1
FROM CurrAccBookATAttributesFilter f2
WHERE f2.CurrAccBookID = b.CurrAccBookID
AND f2.ATAtt01 IN (%s)
)
GROUP BY b.CurrAccCode, b.DocCurrencyCode
),
Movements AS (
SELECT
b.CurrAccCode AS Cari_Kod,
d.CurrAccDescription AS Cari_Isim,
CONVERT(varchar(10), b.DocumentDate, 23) AS Belge_Tarihi,
CONVERT(varchar(10), b.DueDate, 23) AS Vade_Tarihi,
b.RefNumber AS Belge_No,
b.BaseApplicationCode AS Islem_Tipi,
b.LineDescription AS Aciklama,
b.DocCurrencyCode AS Para_Birimi,
c.Debit AS Borc,
c.Credit AS Alacak,
SUM(c.Debit - c.Credit)
OVER (PARTITION BY b.CurrAccCode, c.CurrencyCode
ORDER BY b.DocumentDate, b.CurrAccBookID) AS Hareket_Bakiyesi,
f.ATAtt01 AS Parislemler
FROM trCurrAccBook b
LEFT JOIN cdCurrAccDesc d
ON b.CurrAccCode = d.CurrAccCode AND d.LangCode = 'TR'
LEFT JOIN trCurrAccBookCurrency c
ON b.CurrAccBookID = c.CurrAccBookID
AND b.DocCurrencyCode = c.CurrencyCode
LEFT JOIN CurrAccBookATAttributesFilter f
ON b.CurrAccBookID = f.CurrAccBookID
WHERE b.CurrAccCode LIKE @Carikod
AND b.DocumentDate BETWEEN @StartDate AND @EndDate
AND EXISTS (
SELECT 1
FROM CurrAccBookATAttributesFilter f2
WHERE f2.CurrAccBookID = b.CurrAccBookID
AND f2.ATAtt01 IN (%s)
)
)`, parislemCond, parislemCond)
query += fmt.Sprintf(`
SELECT
m.Cari_Kod,
m.Cari_Isim,
m.Belge_Tarihi,
m.Vade_Tarihi,
m.Belge_No,
m.Islem_Tipi,
m.Aciklama,
m.Para_Birimi,
m.Borc,
m.Alacak,
ISNULL(o.Devir_Bakiyesi,0) + m.Hareket_Bakiyesi AS Bakiye,
m.Parislemler
FROM Movements m
LEFT JOIN Opening o
ON o.Cari_Kod = m.Cari_Kod
AND o.Para_Birimi = m.Para_Birimi
UNION ALL
-- Devir satırı
SELECT
@Carikod AS Cari_Kod,
MAX(d.CurrAccDescription) AS Cari_Isim,
CONVERT(varchar(10), @StartDate, 23) AS Belge_Tarihi,
CONVERT(varchar(10), @StartDate, 23) AS Vade_Tarihi,
'Baslangic_devir' AS Belge_No,
'Devir' AS Islem_Tipi,
'Devir Bakiyesi' AS Aciklama,
b.DocCurrencyCode AS Para_Birimi,
SUM(c.Debit) AS Borc,
SUM(c.Credit) AS Alacak,
SUM(c.Debit) - SUM(c.Credit) AS Bakiye,
(
SELECT STRING_AGG(x.ATAtt01, ',')
FROM (
SELECT DISTINCT f2.ATAtt01
FROM CurrAccBookATAttributesFilter f2
INNER JOIN trCurrAccBook bb
ON f2.CurrAccBookID = bb.CurrAccBookID
WHERE bb.CurrAccCode LIKE @Carikod
AND bb.DocumentDate < @StartDate
AND f2.ATAtt01 IN (%s)
) x
) AS Parislemler
FROM trCurrAccBook b
LEFT JOIN cdCurrAccDesc d
ON b.CurrAccCode = d.CurrAccCode AND d.LangCode = 'TR'
LEFT JOIN trCurrAccBookCurrency c
ON b.CurrAccBookID = c.CurrAccBookID
AND b.DocCurrencyCode = c.CurrencyCode
WHERE b.CurrAccCode LIKE @Carikod
AND b.DocumentDate < @StartDate
GROUP BY b.DocCurrencyCode
ORDER BY Para_Birimi, Belge_Tarihi;`, parislemCond)
rows, err := db.MssqlDB.Query(query,
sql.Named("Carikod", "%"+accountCode+"%"),
sql.Named("StartDate", startDate),
sql.Named("EndDate", endDate),
)
if err != nil {
log.Printf("❌ Header sorgu hatası: %v", err)
return nil, nil, fmt.Errorf("header sorgu hatası: %v", err)
}
defer rows.Close()
var headers []models.StatementHeader
var belgeNos []string
for rows.Next() {
var h models.StatementHeader
if err := rows.Scan(
&h.CariKod, &h.CariIsim,
&h.BelgeTarihi, &h.VadeTarihi,
&h.BelgeNo, &h.IslemTipi,
&h.Aciklama, &h.ParaBirimi,
&h.Borc, &h.Alacak,
&h.Bakiye, &h.Parislemler,
); err != nil {
log.Printf("❌ Header scan hatası: %v", err)
return nil, nil, err
}
headers = append(headers, h)
if h.BelgeNo != "" {
belgeNos = append(belgeNos, h.BelgeNo)
}
}
log.Printf("✅ Header verileri alındı: %d kayıt, %d belge no", len(headers), len(belgeNos))
return headers, belgeNos, nil
}
/* ============================ DETAIL (Alt Tablo) ============================ */
func GetDetailsMapPDF(belgeNos []string, startDate, endDate string) (map[string][]models.StatementDetail, error) {
result := make(map[string][]models.StatementDetail)
if len(belgeNos) == 0 {
log.Println("⚠️ GetDetailsMapPDF: belge listesi boş")
return result, nil
}
qs := make([]string, 0, len(belgeNos))
for _, no := range belgeNos {
safe := strings.ReplaceAll(no, "'", "''")
qs = append(qs, fmt.Sprintf("'%s'", safe))
}
inBelge := strings.Join(qs, ",")
query := fmt.Sprintf(`
;WITH BookMap AS (
SELECT b.RefNumber AS InvoiceNumber, b.CurrAccBookID
FROM trCurrAccBook b
WHERE b.RefNumber IN (%s)
)
SELECT
CONVERT(varchar(10), a.InvoiceDate, 23) AS Belge_Tarihi,
a.InvoiceNumber AS Belge_Ref_Numarasi,
MAX(ISNULL(AnaGrupDesc.AttributeDescription, '')) AS Urun_Ana_Grubu,
MAX(ISNULL(AltGrupDesc.AttributeDescription, '')) AS Urun_Alt_Grubu,
MAX(ISNULL(GarsonDesc.AttributeDescription, '')) AS Yetiskin_Garson,
MAX(ISNULL(FitDesc.AttributeDescription, '')) AS Fit,
MAX(ISNULL(KisaKarDesc.AttributeDescription, '')) AS Icerik,
a.ItemCode, a.ColorCode,
SUM(a.Qty1), SUM(ABS(a.Doc_Price)),
CAST(SUM(a.Qty1 * ABS(a.Doc_Price)) AS numeric(18,2))
FROM AllInvoicesWithAttributes a
JOIN BookMap bm
ON bm.InvoiceNumber = a.InvoiceNumber
-- Ana Grup
LEFT JOIN prItemAttribute AnaGrup
ON a.ItemCode = AnaGrup.ItemCode AND AnaGrup.AttributeTypeCode = 1
LEFT JOIN cdItemAttributeDesc AnaGrupDesc
ON AnaGrup.AttributeTypeCode = AnaGrupDesc.AttributeTypeCode
AND AnaGrup.AttributeCode = AnaGrupDesc.AttributeCode
AND AnaGrup.ItemTypeCode = AnaGrupDesc.ItemTypeCode
-- Alt Grup
LEFT JOIN prItemAttribute AltGrup
ON a.ItemCode = AltGrup.ItemCode AND AltGrup.AttributeTypeCode = 2
LEFT JOIN cdItemAttributeDesc AltGrupDesc
ON AltGrup.AttributeTypeCode = AltGrupDesc.AttributeTypeCode
AND AltGrup.AttributeCode = AltGrupDesc.AttributeCode
AND AltGrup.ItemTypeCode = AltGrupDesc.ItemTypeCode
-- Garson
LEFT JOIN prItemAttribute Garson
ON a.ItemCode = Garson.ItemCode AND Garson.AttributeTypeCode = 44
LEFT JOIN cdItemAttributeDesc GarsonDesc
ON Garson.AttributeTypeCode = GarsonDesc.AttributeTypeCode
AND Garson.AttributeCode = GarsonDesc.AttributeCode
AND Garson.ItemTypeCode = GarsonDesc.ItemTypeCode
-- Fit
LEFT JOIN prItemAttribute FitTbl
ON a.ItemCode = FitTbl.ItemCode AND FitTbl.AttributeTypeCode = 38
LEFT JOIN cdItemAttributeDesc FitDesc
ON FitTbl.AttributeTypeCode = FitDesc.AttributeTypeCode
AND FitTbl.AttributeCode = FitDesc.AttributeCode
AND FitTbl.ItemTypeCode = FitDesc.ItemTypeCode
-- Kısa Karışım
LEFT JOIN prItemAttribute KisaKar
ON a.ItemCode = KisaKar.ItemCode AND KisaKar.AttributeTypeCode = 41
LEFT JOIN cdItemAttributeDesc KisaKarDesc
ON KisaKar.AttributeTypeCode = KisaKarDesc.AttributeTypeCode
AND KisaKar.AttributeCode = KisaKarDesc.AttributeCode
AND KisaKar.ItemTypeCode = KisaKarDesc.ItemTypeCode
WHERE a.InvoiceDate BETWEEN @StartDate AND @EndDate
GROUP BY a.InvoiceDate, a.InvoiceNumber, a.ItemCode, a.ColorCode
ORDER BY a.InvoiceNumber, a.ItemCode, a.ColorCode;`, inBelge)
rows, err := db.MssqlDB.Query(query,
sql.Named("StartDate", startDate),
sql.Named("EndDate", endDate),
)
if err != nil {
log.Printf("❌ Detay sorgu hatası: %v", err)
return nil, fmt.Errorf("detay sorgu hatası: %v", err)
}
defer rows.Close()
for rows.Next() {
var d models.StatementDetail
if err := rows.Scan(
&d.BelgeTarihi,
&d.BelgeRefNumarasi,
&d.UrunAnaGrubu,
&d.UrunAltGrubu,
&d.YetiskinGarson,
&d.Fit,
&d.Icerik,
&d.UrunKodu,
&d.UrunRengi,
&d.ToplamAdet,
&d.ToplamFiyat,
&d.ToplamTutar,
); err != nil {
log.Printf("❌ Detay scan hatası: %v", err)
return nil, err
}
result[d.BelgeRefNumarasi] = append(result[d.BelgeRefNumarasi], d)
}
log.Printf("✅ Detay verileri alındı: %d belge için detay var", len(result))
return result, nil
}

View File

@@ -0,0 +1,35 @@
package queries
import (
"bssapp-backend/models"
"database/sql"
"fmt"
)
// GetTodayCurrencyV3 tek bir döviz türü için güncel kuru döndürür (MSSQL sürümü)
func GetTodayCurrencyV3(db *sql.DB, currencyCode string) (*models.TodayCurrencyV3, error) {
query := `
SELECT TOP 1
CurrencyCode,
RelationCurrencyCode,
ExchangeTypeCode,
Rate,
CONVERT(varchar, Date, 23) AS Date
FROM AllExchangeRates
WHERE CurrencyCode = @p1
AND RelationCurrencyCode = 'TRY'
AND ExchangeTypeCode = 6
AND Rate > 0
ORDER BY ABS(DATEDIFF(DAY, Date, GETDATE())) ASC
`
row := db.QueryRow(query, currencyCode)
var c models.TodayCurrencyV3
err := row.Scan(&c.CurrencyCode, &c.RelationCurrencyCode, &c.ExchangeTypeCode, &c.Rate, &c.Date)
if err != nil {
return nil, fmt.Errorf("kur bilgisi alınamadı: %v", err)
}
return &c, nil
}

296
svc/queries/user_detail.go Normal file
View File

@@ -0,0 +1,296 @@
package queries
// ======================================================
// 👤 USER HEADER
// ======================================================
// ======================================================
// 👤 USER HEADER (mk_dfusr)
// ======================================================
const GetUserHeader = `
SELECT
id,
username,
is_active,
COALESCE(full_name,'') AS full_name,
COALESCE(email,'') AS email,
COALESCE(mobile,'') AS mobile,
COALESCE(address,'') AS address,
CASE
WHEN password_hash IS NOT NULL AND password_hash <> '' THEN true
ELSE false
END AS has_password
FROM mk_dfusr
WHERE id = $1
`
// ======================================================
// 🔐 ROLES
// ======================================================
const GetUserRoles = `
SELECT r.code
FROM dfrole_usr ur
JOIN dfrole r ON r.id = ur.dfrole_id
WHERE ur.dfusr_id = $1
ORDER BY r.code
`
// ======================================================
// 🏢 DEPARTMENTS
// ======================================================
const GetUserDepartments = `
SELECT d.code, d.title
FROM dfusr_dprt ud
JOIN mk_dprt d ON d.id = ud.dprt_id
WHERE ud.dfusr_id = $1
AND ud.is_active = true
ORDER BY d.code
`
// ======================================================
// 🌍 PIYASALAR
// ======================================================
const GetUserPiyasalar = `
SELECT p.code, p.title
FROM dfusr_piyasa up
JOIN mk_sales_piy p ON p.code = up.piyasa_code
WHERE up.dfusr_id = $1
AND up.is_allowed = true
ORDER BY p.code
`
// ======================================================
// 🧾 NEBIM USERS
// ======================================================
const GetUserNebim = `
SELECT n.username, n.user_group_code
FROM dfusr_nebim_user un
JOIN mk_nebim_user n ON n.id = un.mk_nebim_user_id
WHERE un.dfusr_id = $1
ORDER BY n.username
`
// ======================================================
// ✍️ UPDATE USER HEADER (SAFE)
// ======================================================
// ======================================================
// ✍️ UPDATE USER HEADER (mk_dfusr)
// ======================================================
const UpdateUserHeader = `
UPDATE mk_dfusr
SET
username = $2,
is_active = $3,
full_name = $4,
email = $5,
mobile = $6,
address = NULLIF($7, ''),
updated_at = NOW()
WHERE id = $1
`
// ======================================================
// INSERT RELATIONS
// ======================================================
// ---------- ROLE
const InsertUserRole = `
INSERT INTO dfrole_usr (dfusr_id, dfrole_id)
SELECT $1, r.id
FROM dfrole r
WHERE r.code = $2
ON CONFLICT DO NOTHING
`
// ---------- DEPARTMENT
const InsertUserDepartment = `
INSERT INTO dfusr_dprt (dfusr_id, dprt_id, is_active)
SELECT $1, d.id, true
FROM mk_dprt d
WHERE d.code = $2
ON CONFLICT DO NOTHING
`
// ---------- PIYASA
const InsertUserPiyasa = `
INSERT INTO dfusr_piyasa (dfusr_id, piyasa_code, is_allowed)
VALUES ($1, $2, true)
ON CONFLICT DO NOTHING
`
// ---------- NEBIM USER
const InsertUserNebim = `
INSERT INTO dfusr_nebim_user (dfusr_id, mk_nebim_user_id)
SELECT $1, n.id
FROM mk_nebim_user n
WHERE n.username = $2
ON CONFLICT DO NOTHING
`
// ======================================================
// 📚 LOOKUPS
// ======================================================
const GetRoleLookup = `
SELECT code AS value, code AS label
FROM dfrole
ORDER BY code
`
const GetDepartmentLookup = `
SELECT code AS value, title AS label
FROM mk_dprt
WHERE is_active = true
ORDER BY code
`
const GetPiyasaLookup = `
SELECT code AS value, title AS label
FROM mk_sales_piy
WHERE is_active = true
ORDER BY code
`
const GetNebimUserLookup = `
SELECT
username AS value,
username || ' (' || user_group_code || ')' AS label,
user_group_code
FROM mk_nebim_user
WHERE is_active = true
ORDER BY username
`
// ======================================================
// 📦 ORDER — NEW NUMBER
// ======================================================
const GetNextOrderNumber = `
SELECT
COALESCE(MAX(order_number), 0) + 1
FROM orders
WHERE order_year = EXTRACT(YEAR FROM NOW())
`
// ======================================================
// 🔐 ROLES (ADMIN LIST)
// ======================================================
const GetRoles = `
SELECT
id,
code,
title
FROM dfrole
ORDER BY code
`
// ======================================================
// 🏢 DEPARTMENTS (ADMIN LIST)
// ======================================================
const GetDepartments = `
SELECT
id,
code,
title
FROM mk_dprt
WHERE is_active = true
ORDER BY code
`
// ======================================================
// 🌍 PIYASALAR (ADMIN LIST)
// ======================================================
const GetPiyasalar = `
SELECT
code,
title
FROM mk_sales_piy
WHERE is_active = true
ORDER BY code
`
// ======================================================
// 🔗 ROLE → DEPARTMENT
// ======================================================
const DeleteRoleDepartments = `
DELETE FROM dfrole_dprt
WHERE dfrole_id = $1
`
const InsertRoleDepartment = `
INSERT INTO dfrole_dprt (
dfrole_id,
dprt_id,
is_allowed
)
SELECT
$1,
d.id,
true
FROM mk_dprt d
WHERE d.code = $2
ON CONFLICT DO NOTHING
`
// ======================================================
// 🔗 ROLE → PIYASA
// ======================================================
const DeleteRolePiyasalar = `
DELETE FROM dfrole_piyasa
WHERE dfrole_id = $1
`
const InsertRolePiyasa = `
INSERT INTO dfrole_piyasa (
dfrole_id,
piyasa_code,
is_allowed
)
VALUES ($1,$2,true)
ON CONFLICT DO NOTHING
`
// ======================================================
// 👤 USER → ROLE
// ======================================================
const DeleteUserRoles = `
DELETE FROM dfrole_usr
WHERE dfusr_id = $1
`
// ======================================================
// 🏢 DEPARTMENTS (ADMIN PERM PAGE SELECT) -> { code, title }
// ======================================================
const GetDepartmentsForPermissionSelect = `
SELECT
code AS id,
title AS title
FROM mk_dprt
WHERE is_active = true
ORDER BY title
`
// ======================================================
// 🔐 ROLES (ADMIN PERM PAGE SELECT) -> { id, title }
// ======================================================
const GetRolesForPermissionSelect = `
SELECT
id::text AS id,
COALESCE(NULLIF(title,''), code) AS title
FROM dfrole
ORDER BY COALESCE(NULLIF(title,''), code)
`
const GetUserLookupForPermission = `
SELECT
id::text AS id,
username || ' - ' || full_name AS title
FROM mk_dfusr
WHERE is_active = true
ORDER BY username
`

106
svc/queries/user_list.go Normal file
View File

@@ -0,0 +1,106 @@
package queries
import (
"database/sql"
"fmt"
)
// ========================================================
// 📌 GetUserList — POSTGRES FINAL (mk_dfusr)
// ========================================================
func GetUserList(db *sql.DB, search string) (*sql.Rows, error) {
baseQuery := `
SELECT
u.id AS id, -- 1
u.username AS code, -- 2 (frontend uyumu için alias)
u.is_active AS is_active, -- 3
-- Nebim eşleşmesi (opsiyonel)
n.username AS nebim_username, -- 4
n.user_group_code AS user_group_code, -- 5
-- Roller
COALESCE(string_agg(DISTINCT r.code, ', '), '') AS role_names, -- 6
-- Departmanlar
COALESCE(string_agg(DISTINCT d.title, ', '), '') AS department_names, -- 7
-- Piyasalar (ÇOKLU)
COALESCE(string_agg(DISTINCT p.title, ', '), '') AS piyasa_names -- 8
FROM mk_dfusr u
-- ==========================
-- 🔗 Nebim eşleşmesi
-- ==========================
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
-- ==========================
-- 🔐 Roller
-- ==========================
LEFT JOIN dfrole_usr uru
ON uru.dfusr_id = u.id
LEFT JOIN dfrole r
ON r.id = uru.dfrole_id
-- ==========================
-- 🏢 Departmanlar
-- ==========================
LEFT JOIN dfusr_dprt ud
ON ud.dfusr_id = u.id
LEFT JOIN mk_dprt d
ON d.id = ud.dprt_id
-- ==========================
-- 🌍 Piyasalar
-- ==========================
LEFT JOIN dfusr_piyasa up
ON up.dfusr_id = u.id
LEFT JOIN mk_sales_piy p
ON p.code = up.piyasa_code
WHERE 1 = 1
`
// 🔍 SEARCH FILTER
if search != "" {
baseQuery += `
AND (
u.username ILIKE $1
OR u.email ILIKE $1
OR n.username ILIKE $1
OR r.code ILIKE $1
OR d.title ILIKE $1
OR p.title ILIKE $1
)
`
}
// 📌 GROUP + ORDER
baseQuery += `
GROUP BY
u.id,
u.username,
u.is_active,
n.username,
n.user_group_code
ORDER BY u.username
`
// ▶ EXECUTE QUERY
if search != "" {
searchLike := fmt.Sprintf("%%%s%%", search)
return db.Query(baseQuery, searchLike)
}
return db.Query(baseQuery)
}

View File

@@ -0,0 +1,41 @@
package queries
// ======================================================
// 🔐 UPDATE USER PASSWORD (RESET / CHANGE)
// ======================================================
const UpdateUserPassword = `
UPDATE dfusr
SET
upass = $2,
last_updated_date = NOW()
WHERE id = $1
AND is_active = true
`
// Reset token yaz
const SetUserResetToken = `
UPDATE dfusr
SET
fpass = $2,
fpass_id = NOW()
WHERE id = $1
AND is_active = true
`
// Reset token kontrol
const CheckUserResetToken = `
SELECT id
FROM dfusr
WHERE
fpass = $1
AND is_active = true
`
// Reset sonrası temizle
const ClearUserResetToken = `
UPDATE dfusr
SET
fpass = NULL,
fpass_id = NULL
WHERE id = $1
`