Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-03-09 13:19:14 +03:00
parent 6df18ed14d
commit 0d303f0c0f
12 changed files with 382 additions and 280 deletions

View File

@@ -1,27 +0,0 @@
/* Indexes for order validate performance */
IF NOT EXISTS (
SELECT 1
FROM sys.indexes
WHERE name = 'IX_trOrderLine_OrderHeader_ItemCode'
AND object_id = OBJECT_ID('dbo.trOrderLine')
)
BEGIN
CREATE NONCLUSTERED INDEX IX_trOrderLine_OrderHeader_ItemCode
ON dbo.trOrderLine (OrderHeaderID, ItemCode)
INCLUDE (ItemTypeCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, LineDescription, SortOrder, OrderLineID);
END
GO
IF NOT EXISTS (
SELECT 1
FROM sys.indexes
WHERE name = 'IX_prItemVariant_Combo'
AND object_id = OBJECT_ID('dbo.prItemVariant')
)
BEGIN
CREATE NONCLUSTERED INDEX IX_prItemVariant_Combo
ON dbo.prItemVariant (ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code)
INCLUDE (PLU);
END
GO

View File

@@ -1,18 +0,0 @@
USE [BAGGI_V3]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE OR ALTER PROCEDURE [dbo].[SP_BUILD_STATEMENT_AGING_PIPELINE]
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
EXEC dbo.SP_BUILD_CARI_VADE_GUN_STAGING;
EXEC dbo.SP_BUILD_CARI_BAKIYE_CACHE;
END
GO

View File

@@ -101,15 +101,19 @@ func GetAccounts(ctx context.Context) ([]models.Account, error) {
return nil, err return nil, err
} }
if len(acc.AccountCode) >= 4 { acc.DisplayCode = formatAccountDisplayCode(acc.AccountCode)
acc.DisplayCode =
strings.TrimSpace(acc.AccountCode[:3] + " " + acc.AccountCode[3:])
} else {
acc.DisplayCode = acc.AccountCode
}
accounts = append(accounts, acc) accounts = append(accounts, acc)
} }
return accounts, rows.Err() return accounts, rows.Err()
} }
func formatAccountDisplayCode(code string) string {
trimmed := strings.TrimSpace(code)
runes := []rune(trimmed)
if len(runes) <= 3 {
return trimmed
}
return strings.TrimSpace(string(runes[:3]) + " " + string(runes[3:]))
}

View File

@@ -15,15 +15,16 @@ import (
) )
type mkCariBakiyeLine struct { type mkCariBakiyeLine struct {
CurrAccTypeCode int CurrAccTypeCode int
CariKodu string CariKodu string
CariDoviz string CariDoviz string
SirketKodu int SirketKodu int
PislemTipi string PislemTipi string
YerelBakiye float64 ParasalIslemTipi string
Bakiye float64 YerelBakiye float64
VadeGun float64 Bakiye float64
VadeBelgeGun float64 VadeGun float64
VadeBelgeGun float64
} }
type cariMeta struct { type cariMeta struct {
@@ -181,13 +182,13 @@ func GetCustomerBalanceList(ctx context.Context, params models.CustomerBalanceLi
} }
usd := toUSD(ln.Bakiye, curr, usdTry, rateMap) usd := toUSD(ln.Bakiye, curr, usdTry, rateMap)
add12, add13 := resolveBalanceBuckets(ln)
switch strings.TrimSpace(ln.PislemTipi) { if add12 {
case "1_2":
row.Bakiye12 += ln.Bakiye row.Bakiye12 += ln.Bakiye
row.TLBakiye12 += ln.YerelBakiye row.TLBakiye12 += ln.YerelBakiye
row.USDBakiye12 += usd row.USDBakiye12 += usd
case "1_3": }
if add13 {
row.Bakiye13 += ln.Bakiye row.Bakiye13 += ln.Bakiye
row.TLBakiye13 += ln.YerelBakiye row.TLBakiye13 += ln.YerelBakiye
row.USDBakiye13 += usd row.USDBakiye13 += usd
@@ -319,13 +320,14 @@ func loadBalanceLines(ctx context.Context, selectedDate, cariSearch string) ([]m
return nil, err return nil, err
} }
query := fmt.Sprintf(` queryTemplate := `
SELECT SELECT
CurrAccTypeCode, CurrAccTypeCode,
CariKodu, CariKodu,
CariDoviz, CariDoviz,
SirketKodu, SirketKodu,
PislemTipi, PislemTipi,
%s
YerelBakiye, YerelBakiye,
Bakiye, Bakiye,
CAST(0 AS DECIMAL(18,4)) AS Vade_Gun, CAST(0 AS DECIMAL(18,4)) AS Vade_Gun,
@@ -333,13 +335,33 @@ func loadBalanceLines(ctx context.Context, selectedDate, cariSearch string) ([]m
FROM dbo.MK_CARI_BAKIYE_LIST(@SonTarih) FROM dbo.MK_CARI_BAKIYE_LIST(@SonTarih)
WHERE (@CariSearch = '' OR CariKodu LIKE '%%' + @CariSearch + '%%') WHERE (@CariSearch = '' OR CariKodu LIKE '%%' + @CariSearch + '%%')
AND %s AND %s
`, piyasaScope) `
rows, err := db.MssqlDB.QueryContext(ctx, query, selectParasalCandidates := make([]string, 0, 7)
sql.Named("SonTarih", selectedDate), if expr := strings.TrimSpace(resolveParasalIslemSelectExpr(ctx, "SELECT * FROM dbo.MK_CARI_BAKIYE_LIST('2000-01-01')")); expr != "" {
sql.Named("CariSearch", strings.TrimSpace(cariSearch)), selectParasalCandidates = append(selectParasalCandidates, expr)
}
selectParasalCandidates = append(selectParasalCandidates,
"CAST(ATAtt01 AS varchar(16)) AS ParasalIslemTipi,",
"CAST(ParasalIslemTipi AS varchar(16)) AS ParasalIslemTipi,",
"CAST(ParislemTipi AS varchar(16)) AS ParasalIslemTipi,",
"CAST(ParIslemTipi AS varchar(16)) AS ParasalIslemTipi,",
"CAST('' AS varchar(16)) AS ParasalIslemTipi,",
) )
if err != nil {
var rows *sql.Rows
for i, sel := range selectParasalCandidates {
query := fmt.Sprintf(queryTemplate, sel, piyasaScope)
rows, err = db.MssqlDB.QueryContext(ctx, query,
sql.Named("SonTarih", selectedDate),
sql.Named("CariSearch", strings.TrimSpace(cariSearch)),
)
if err == nil {
break
}
if i < len(selectParasalCandidates)-1 && isInvalidColumnError(err) {
continue
}
return nil, fmt.Errorf("MK_CARI_BAKIYE_LIST query error: %w", err) return nil, fmt.Errorf("MK_CARI_BAKIYE_LIST query error: %w", err)
} }
defer rows.Close() defer rows.Close()
@@ -353,6 +375,7 @@ func loadBalanceLines(ctx context.Context, selectedDate, cariSearch string) ([]m
&r.CariDoviz, &r.CariDoviz,
&r.SirketKodu, &r.SirketKodu,
&r.PislemTipi, &r.PislemTipi,
&r.ParasalIslemTipi,
&r.YerelBakiye, &r.YerelBakiye,
&r.Bakiye, &r.Bakiye,
&r.VadeGun, &r.VadeGun,
@@ -648,7 +671,7 @@ func buildFilters(params models.CustomerBalanceListParams) balanceFilters {
piyasa: parseCSVSet(params.Piyasa), piyasa: parseCSVSet(params.Piyasa),
temsilci: parseCSVSet(params.Temsilci), temsilci: parseCSVSet(params.Temsilci),
riskDurumu: parseCSVSet(params.RiskDurumu), riskDurumu: parseCSVSet(params.RiskDurumu),
islemTipi: parseCSVSet(params.IslemTipi), islemTipi: parseIslemTipiSet(params.IslemTipi),
ulke: parseCSVSet(params.Ulke), ulke: parseCSVSet(params.Ulke),
il: parseCSVSet(params.Il), il: parseCSVSet(params.Il),
ilce: parseCSVSet(params.Ilce), ilce: parseCSVSet(params.Ilce),
@@ -707,6 +730,28 @@ func parseCSVSet(v string) map[string]struct{} {
return out return out
} }
func parseIslemTipiSet(v string) map[string]struct{} {
raw := parseCSVSet(v)
if len(raw) == 0 {
return raw
}
out := make(map[string]struct{}, 2)
for token := range raw {
switch strings.ToLower(strings.TrimSpace(token)) {
case "1_2", "prbr_1_2", "usd_1_2", "try_1_2", "tl_1_2", "usd_bakiye_1_2", "tl_bakiye_1_2":
out["1_2"] = struct{}{}
case "1_3", "prbr_1_3", "usd_1_3", "try_1_3", "tl_1_3", "usd_bakiye_1_3", "tl_bakiye_1_3":
out["1_3"] = struct{}{}
}
}
if len(out) == 0 {
return raw
}
return out
}
func getAuthorizedPiyasaCodes(ctx context.Context) ([]string, error) { func getAuthorizedPiyasaCodes(ctx context.Context) ([]string, error) {
claims, ok := auth.GetClaimsFromContext(ctx) claims, ok := auth.GetClaimsFromContext(ctx)
if !ok || claims == nil { if !ok || claims == nil {
@@ -794,3 +839,181 @@ func firstNonEmpty(v ...string) string {
} }
return "" return ""
} }
func isInvalidColumnError(err error) bool {
if err == nil {
return false
}
msg := strings.ToLower(err.Error())
return strings.Contains(msg, "invalid column name")
}
func shouldSkipBalanceLine(ln mkCariBakiyeLine) bool {
add12, add13 := resolveBalanceBuckets(ln)
p := strings.TrimSpace(ln.PislemTipi)
if p == "1_2" {
return !add12
}
if p == "1_3" {
return !add13
}
return false
}
func resolveBalanceBuckets(ln mkCariBakiyeLine) (add12 bool, add13 bool) {
p := strings.TrimSpace(ln.PislemTipi)
t := normalizeParasalIslemTipi(ln.ParasalIslemTipi)
switch t {
case "1":
return true, true
case "2", "1_2":
return true, false
case "3", "1_3":
return false, true
}
// Parasal tip yoksa eski davranis: PislemTipi'ne gore ayir.
if p == "1_2" {
return true, false
}
if p == "1_3" {
return false, true
}
return false, false
}
func normalizeParasalIslemTipi(v string) string {
s := strings.TrimSpace(v)
if s == "" {
return ""
}
lower := strings.ToLower(s)
compact := strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(lower, " ", ""), "-", "_"), "/", "_")
if strings.Contains(compact, "1_2") {
return "1_2"
}
if strings.Contains(compact, "1_3") {
return "1_3"
}
// "1,2" / "1,3" gibi liste formatlarini dogrudan yakala.
tokenized := strings.NewReplacer(" ", "", ";", ",", "|", ",", "/", ",", "-", ",", "_", ",").Replace(lower)
parts := strings.Split(tokenized, ",")
has1 := false
has2 := false
has3 := false
for _, p := range parts {
t := strings.TrimSpace(p)
switch t {
case "1":
has1 = true
case "2":
has2 = true
case "3":
has3 = true
}
}
if has1 && has2 {
return "1_2"
}
if has1 && has3 {
return "1_3"
}
if has2 && !has1 && !has3 {
return "2"
}
if has3 && !has1 && !has2 {
return "3"
}
if has1 && !has2 && !has3 {
return "1"
}
// "2.00", "2,00", " 2 " gibi varyasyonlari tek tipe indir.
s = strings.ReplaceAll(s, ",", ".")
if n, err := strconv.ParseFloat(s, 64); err == nil {
return strconv.Itoa(int(n))
}
// Metinsel geldiyse ilk rakam bloğunu al.
start := -1
end := -1
for i, r := range s {
if r >= '0' && r <= '9' {
if start == -1 {
start = i
}
end = i
continue
}
if start != -1 {
break
}
}
if start == -1 || end < start {
return s
}
return s[start : end+1]
}
func resolveParasalIslemSelectExpr(ctx context.Context, sampleQuery string) string {
sampleQuery = strings.TrimSpace(sampleQuery)
if sampleQuery == "" {
return ""
}
metaQuery := `
SELECT name
FROM sys.dm_exec_describe_first_result_set(@tsql, NULL, 0)
WHERE error_number IS NULL
AND name IS NOT NULL
`
rows, err := db.MssqlDB.QueryContext(ctx, metaQuery, sql.Named("tsql", sampleQuery))
if err != nil {
return ""
}
defer rows.Close()
type candidate struct {
key string
expr string
}
priority := []candidate{
{key: "ata tt01", expr: "CAST(%s AS varchar(16)) AS ParasalIslemTipi,"},
{key: "atatt01", expr: "CAST(%s AS varchar(16)) AS ParasalIslemTipi,"},
{key: "parasalislemtipi", expr: "CAST(%s AS varchar(16)) AS ParasalIslemTipi,"},
{key: "parislemtipi", expr: "CAST(%s AS varchar(16)) AS ParasalIslemTipi,"},
{key: "parislemtur", expr: "CAST(%s AS varchar(16)) AS ParasalIslemTipi,"},
}
available := make(map[string]string)
for rows.Next() {
var col sql.NullString
if err := rows.Scan(&col); err != nil {
return ""
}
name := strings.TrimSpace(col.String)
if name == "" {
continue
}
normalized := strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(name, "_", ""), " ", ""))
available[normalized] = name
}
if err := rows.Err(); err != nil {
return ""
}
for _, c := range priority {
key := strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(c.key, "_", ""), " ", ""))
if col, ok := available[key]; ok {
return fmt.Sprintf(c.expr, quoteSQLIdent(col))
}
}
return ""
}
func quoteSQLIdent(ident string) string {
return "[" + strings.ReplaceAll(strings.TrimSpace(ident), "]", "]]") + "]"
}

View File

@@ -137,13 +137,13 @@ func GetStatementAgingBalanceList(ctx context.Context, params models.CustomerBal
usd := toUSD(ln.Bakiye, curr, usdTry, rateMap) usd := toUSD(ln.Bakiye, curr, usdTry, rateMap)
tl := toTRY(ln.Bakiye, curr, rateMap) tl := toTRY(ln.Bakiye, curr, rateMap)
add12, add13 := resolveBalanceBuckets(ln)
switch strings.TrimSpace(ln.PislemTipi) { if add12 {
case "1_2":
row.Bakiye12 += ln.Bakiye row.Bakiye12 += ln.Bakiye
row.TLBakiye12 += tl row.TLBakiye12 += tl
row.USDBakiye12 += usd row.USDBakiye12 += usd
case "1_3": }
if add13 {
row.Bakiye13 += ln.Bakiye row.Bakiye13 += ln.Bakiye
row.TLBakiye13 += tl row.TLBakiye13 += tl
row.USDBakiye13 += usd row.USDBakiye13 += usd
@@ -187,13 +187,14 @@ func loadAgingBalanceLines(ctx context.Context, cariSearch string) ([]mkCariBaki
return nil, err return nil, err
} }
query := fmt.Sprintf(` queryTemplate := `
SELECT SELECT
CurrAccTypeCode, CurrAccTypeCode,
CariKodu = LTRIM(RTRIM(CariKodu)), CariKodu = LTRIM(RTRIM(CariKodu)),
CariDoviz = LTRIM(RTRIM(CariDoviz)), CariDoviz = LTRIM(RTRIM(CariDoviz)),
SirketKodu, SirketKodu,
PislemTipi, PislemTipi,
%s
YerelBakiye = CAST(0 AS DECIMAL(18,2)), YerelBakiye = CAST(0 AS DECIMAL(18,2)),
Bakiye, Bakiye,
Vade_Gun, Vade_Gun,
@@ -202,10 +203,30 @@ func loadAgingBalanceLines(ctx context.Context, cariSearch string) ([]mkCariBaki
WHERE (@CariSearch = '' OR LTRIM(RTRIM(CariKodu)) LIKE '%%' + @CariSearch + '%%') WHERE (@CariSearch = '' OR LTRIM(RTRIM(CariKodu)) LIKE '%%' + @CariSearch + '%%')
AND %s AND %s
ORDER BY CariKodu, CariDoviz, PislemTipi ORDER BY CariKodu, CariDoviz, PislemTipi
`, piyasaScope) `
rows, err := db.MssqlDB.QueryContext(ctx, query, sql.Named("CariSearch", strings.TrimSpace(cariSearch))) selectParasalCandidates := make([]string, 0, 7)
if err != nil { if expr := strings.TrimSpace(resolveParasalIslemSelectExpr(ctx, "SELECT * FROM dbo.CARI_BAKIYE_GUN_CACHE")); expr != "" {
selectParasalCandidates = append(selectParasalCandidates, expr)
}
selectParasalCandidates = append(selectParasalCandidates,
"CAST(ATAtt01 AS varchar(16)) AS ParasalIslemTipi,",
"CAST(ParasalIslemTipi AS varchar(16)) AS ParasalIslemTipi,",
"CAST(ParislemTipi AS varchar(16)) AS ParasalIslemTipi,",
"CAST(ParIslemTipi AS varchar(16)) AS ParasalIslemTipi,",
"CAST('' AS varchar(16)) AS ParasalIslemTipi,",
)
var rows *sql.Rows
for i, sel := range selectParasalCandidates {
query := fmt.Sprintf(queryTemplate, sel, piyasaScope)
rows, err = db.MssqlDB.QueryContext(ctx, query, sql.Named("CariSearch", strings.TrimSpace(cariSearch)))
if err == nil {
break
}
if i < len(selectParasalCandidates)-1 && isInvalidColumnError(err) {
continue
}
return nil, fmt.Errorf("CARI_BAKIYE_GUN_CACHE query error: %w", err) return nil, fmt.Errorf("CARI_BAKIYE_GUN_CACHE query error: %w", err)
} }
defer rows.Close() defer rows.Close()
@@ -219,6 +240,7 @@ func loadAgingBalanceLines(ctx context.Context, cariSearch string) ([]mkCariBaki
&r.CariDoviz, &r.CariDoviz,
&r.SirketKodu, &r.SirketKodu,
&r.PislemTipi, &r.PislemTipi,
&r.ParasalIslemTipi,
&r.YerelBakiye, &r.YerelBakiye,
&r.Bakiye, &r.Bakiye,
&r.VadeGun, &r.VadeGun,

View File

@@ -23,7 +23,10 @@ SELECT
a.ItemCode AS Urun_Kodu, a.ItemCode AS Urun_Kodu,
a.ColorCode AS Urun_Rengi, a.ColorCode AS Urun_Rengi,
SUM(a.Qty1) AS Toplam_Adet, SUM(a.Qty1) AS Toplam_Adet,
SUM(ABS(a.Doc_Price)) AS Toplam_Fiyat, CAST(
SUM(a.Qty1 * ABS(a.Doc_Price)) / NULLIF(SUM(a.Qty1), 0)
AS numeric(18,2)
) AS Toplam_Fiyat,
CAST(SUM(a.Qty1 * ABS(a.Doc_Price)) AS numeric(18,2)) AS Toplam_Tutar CAST(SUM(a.Qty1 * ABS(a.Doc_Price)) AS numeric(18,2)) AS Toplam_Tutar
FROM AllInvoicesWithAttributes a FROM AllInvoicesWithAttributes a
LEFT JOIN prItemAttribute AnaGrup LEFT JOIN prItemAttribute AnaGrup

View File

@@ -23,25 +23,34 @@ type ProductImageItem struct {
ContentURL string `json:"content_url"` ContentURL string `json:"content_url"`
} }
//
// LIST PRODUCT IMAGES
//
// GET /api/product-images?code=...&color=... // GET /api/product-images?code=...&color=...
func GetProductImagesHandler(pg *sql.DB) http.HandlerFunc { func GetProductImagesHandler(pg *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
reqID := strings.TrimSpace(r.Header.Get("X-Request-ID")) reqID := strings.TrimSpace(r.Header.Get("X-Request-ID"))
if reqID == "" { if reqID == "" {
reqID = uuid.NewString() reqID = uuid.NewString()
} }
w.Header().Set("X-Request-ID", reqID) w.Header().Set("X-Request-ID", reqID)
code := strings.TrimSpace(r.URL.Query().Get("code")) code := strings.TrimSpace(r.URL.Query().Get("code"))
color := strings.TrimSpace(r.URL.Query().Get("color")) color := strings.TrimSpace(r.URL.Query().Get("color"))
if code == "" { if code == "" {
slog.Warn("product_images.list.bad_request", slog.Warn("product_images.list.bad_request",
"req_id", reqID, "req_id", reqID,
"path", r.URL.Path, "path", r.URL.Path,
"query", r.URL.RawQuery, "query", r.URL.RawQuery,
"reason", "missing_code", "reason", "missing_code",
) )
http.Error(w, "Eksik parametre: code gerekli", http.StatusBadRequest) http.Error(w, "Eksik parametre: code gerekli", http.StatusBadRequest)
return return
} }
@@ -50,38 +59,59 @@ func GetProductImagesHandler(pg *sql.DB) http.HandlerFunc {
SELECT SELECT
b.id, b.id,
b.file_name, b.file_name,
COALESCE(b.file_size, 0) AS file_size, COALESCE(b.file_size,0) AS file_size,
COALESCE(b.storage_path, '') AS storage_path COALESCE(b.storage_path,'') AS storage_path
FROM dfblob b FROM dfblob b
JOIN mmitem i JOIN mmitem i
ON i.id = b.src_id ON i.id = b.src_id
WHERE b.typ = 'img' WHERE b.typ = 'img'
AND b.src_table = 'mmitem' AND b.src_table = 'mmitem'
AND UPPER(i.code) = UPPER($1) AND UPPER(i.code) = UPPER($1)
AND ($2 = '' OR b.file_name ILIKE '%' || '-' || $2 || '-%') AND (
ORDER BY COALESCE(b.sort_order, 999999), b.zlins_dttm DESC, b.id DESC $2 = ''
OR b.file_name ILIKE '%' || '-' || $2 || '-%'
OR b.file_name ILIKE '%' || '-' || $2 || '_%'
)
ORDER BY
COALESCE(b.sort_order,999999),
b.zlins_dttm DESC,
b.id DESC
` `
rows, err := pg.Query(query, code, color) rows, err := pg.Query(query, code, color)
if err != nil { if err != nil {
slog.Error("product_images.list.query_failed", slog.Error("product_images.list.query_failed",
"req_id", reqID, "req_id", reqID,
"code", code, "code", code,
"color", color, "color", color,
"err", err.Error(), "err", err.Error(),
) )
http.Error(w, "Gorsel sorgu hatasi: "+err.Error(), http.StatusInternalServerError) http.Error(w, "Gorsel sorgu hatasi: "+err.Error(), http.StatusInternalServerError)
return return
} }
defer rows.Close() defer rows.Close()
items := make([]ProductImageItem, 0, 16) items := make([]ProductImageItem, 0, 16)
for rows.Next() { for rows.Next() {
var it ProductImageItem var it ProductImageItem
if err := rows.Scan(&it.ID, &it.FileName, &it.FileSize, &it.Storage); err != nil {
if err := rows.Scan(
&it.ID,
&it.FileName,
&it.FileSize,
&it.Storage,
); err != nil {
continue continue
} }
it.ContentURL = fmt.Sprintf("/api/product-images/%d/content", it.ID) it.ContentURL = fmt.Sprintf("/api/product-images/%d/content", it.ID)
items = append(items, it) items = append(items, it)
} }
@@ -97,24 +127,35 @@ ORDER BY COALESCE(b.sort_order, 999999), b.zlins_dttm DESC, b.id DESC
} }
} }
//
// GET IMAGE CONTENT
//
// GET /api/product-images/{id}/content // GET /api/product-images/{id}/content
func GetProductImageContentHandler(pg *sql.DB) http.HandlerFunc { func GetProductImageContentHandler(pg *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
reqID := strings.TrimSpace(r.Header.Get("X-Request-ID")) reqID := strings.TrimSpace(r.Header.Get("X-Request-ID"))
if reqID == "" { if reqID == "" {
reqID = uuid.NewString() reqID = uuid.NewString()
} }
w.Header().Set("X-Request-ID", reqID) w.Header().Set("X-Request-ID", reqID)
idStr := mux.Vars(r)["id"] idStr := mux.Vars(r)["id"]
id, err := strconv.ParseInt(idStr, 10, 64) id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil || id <= 0 { if err != nil || id <= 0 {
slog.Warn("product_images.content.bad_request", slog.Warn("product_images.content.bad_request",
"req_id", reqID, "req_id", reqID,
"id_raw", idStr, "id_raw", idStr,
"path", r.URL.Path, "path", r.URL.Path,
"reason", "invalid_id", "reason", "invalid_id",
) )
http.Error(w, "Gecersiz gorsel id", http.StatusBadRequest) http.Error(w, "Gecersiz gorsel id", http.StatusBadRequest)
return return
} }
@@ -128,83 +169,89 @@ func GetProductImageContentHandler(pg *sql.DB) http.HandlerFunc {
err = pg.QueryRow(` err = pg.QueryRow(`
SELECT SELECT
COALESCE(file_name, ''), COALESCE(file_name,''),
COALESCE(storage_path, ''), COALESCE(storage_path,''),
COALESCE(stored_in_db, false), COALESCE(stored_in_db,false),
bin bin
FROM dfblob FROM dfblob
WHERE id = $1 WHERE id = $1
AND typ = 'img' AND typ = 'img'
`, id).Scan(&fileName, &storagePath, &storedInDB, &binData) `, id).Scan(&fileName, &storagePath, &storedInDB, &binData)
if err != nil { if err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
slog.Warn("product_images.content.not_found_row", slog.Warn("product_images.content.not_found_row",
"req_id", reqID, "req_id", reqID,
"id", id, "id", id,
) )
http.NotFound(w, r) http.NotFound(w, r)
return return
} }
slog.Error("product_images.content.query_failed", slog.Error("product_images.content.query_failed",
"req_id", reqID, "req_id", reqID,
"id", id, "id", id,
"err", err.Error(), "err", err.Error(),
) )
http.Error(w, "Gorsel okunamadi: "+err.Error(), http.StatusInternalServerError) http.Error(w, "Gorsel okunamadi: "+err.Error(), http.StatusInternalServerError)
return return
} }
// DB içinde binary saklıysa
if storedInDB && len(binData) > 0 { if storedInDB && len(binData) > 0 {
slog.Info("product_images.content.served_from_db",
"req_id", reqID,
"id", id,
"file_name", fileName,
"bytes", len(binData),
)
w.Header().Set("Content-Type", http.DetectContentType(binData)) w.Header().Set("Content-Type", http.DetectContentType(binData))
w.Header().Set("Cache-Control", "public, max-age=3600") w.Header().Set("Cache-Control", "public, max-age=3600")
_, _ = w.Write(binData) _, _ = w.Write(binData)
return return
} }
resolved, tried := resolveStoragePath(storagePath) resolved, tried := resolveStoragePath(storagePath)
if resolved == "" { if resolved == "" {
slog.Warn("product_images.content.file_not_found", slog.Warn("product_images.content.file_not_found",
"req_id", reqID, "req_id", reqID,
"id", id, "id", id,
"stored_in_db", storedInDB,
"file_name", fileName, "file_name", fileName,
"storage_path", storagePath, "storage_path", storagePath,
"tried", tried, "tried", tried,
) )
http.NotFound(w, r) http.NotFound(w, r)
return return
} }
slog.Info("product_images.content.served_from_file",
"req_id", reqID,
"id", id,
"file_name", fileName,
"storage_path", storagePath,
"resolved_path", resolved,
)
w.Header().Set("Cache-Control", "public, max-age=3600") w.Header().Set("Cache-Control", "public, max-age=3600")
http.ServeFile(w, r, resolved) http.ServeFile(w, r, resolved)
} }
} }
//
// FILE PATH RESOLVER
//
func resolveStoragePath(storagePath string) (string, []string) { func resolveStoragePath(storagePath string) (string, []string) {
raw := strings.TrimSpace(storagePath) raw := strings.TrimSpace(storagePath)
if raw == "" { if raw == "" {
return "", nil return "", nil
} }
// URL/query temizligi ve platforma uygun normalize
if i := strings.Index(raw, "?"); i >= 0 { if i := strings.Index(raw, "?"); i >= 0 {
raw = raw[:i] raw = raw[:i]
} }
raw = strings.ReplaceAll(raw, "\\", "/") raw = strings.ReplaceAll(raw, "\\", "/")
if scheme := strings.Index(raw, "://"); scheme >= 0 { if scheme := strings.Index(raw, "://"); scheme >= 0 {
rest := raw[scheme+3:] rest := raw[scheme+3:]
if i := strings.Index(rest, "/"); i >= 0 { if i := strings.Index(rest, "/"); i >= 0 {
@@ -215,47 +262,36 @@ func resolveStoragePath(storagePath string) (string, []string) {
raw = strings.TrimPrefix(raw, "./") raw = strings.TrimPrefix(raw, "./")
raw = strings.TrimPrefix(raw, "/") raw = strings.TrimPrefix(raw, "/")
raw = strings.TrimPrefix(raw, "uploads/") raw = strings.TrimPrefix(raw, "uploads/")
raw = filepath.ToSlash(filepath.Clean(raw)) raw = filepath.ToSlash(filepath.Clean(raw))
relUploads := filepath.FromSlash(filepath.Join("uploads", raw)) relUploads := filepath.FromSlash(filepath.Join("uploads", raw))
rawT300 := raw
relUploadsT300 := relUploads
if strings.Contains(filepath.ToSlash(relUploads), "uploads/image/") &&
!strings.Contains(filepath.ToSlash(relUploads), "uploads/image/t300/") {
rawT300 = strings.Replace(filepath.ToSlash(raw), "image/", "image/t300/", 1)
relUploadsT300 = filepath.FromSlash(
strings.Replace(filepath.ToSlash(relUploads), "uploads/image/", "uploads/image/t300/", 1),
)
}
candidates := []string{ candidates := []string{
filepath.Clean(storagePath), filepath.Clean(storagePath),
filepath.FromSlash(filepath.Clean(strings.TrimPrefix(storagePath, "/"))), filepath.FromSlash(filepath.Clean(strings.TrimPrefix(storagePath, "/"))),
filepath.FromSlash(filepath.Clean(raw)), filepath.FromSlash(filepath.Clean(raw)),
relUploads, relUploads,
relUploadsT300,
filepath.Join(".", relUploads), filepath.Join(".", relUploads),
filepath.Join(".", relUploadsT300),
filepath.Join("..", relUploads), filepath.Join("..", relUploads),
filepath.Join("..", relUploadsT300),
filepath.Join("..", "..", relUploads), filepath.Join("..", "..", relUploads),
filepath.Join("..", "..", relUploadsT300),
} }
if root := strings.TrimSpace(os.Getenv("BLOB_ROOT")); root != "" { if root := strings.TrimSpace(os.Getenv("BLOB_ROOT")); root != "" {
candidates = append(candidates, candidates = append(candidates,
filepath.Join(root, raw), filepath.Join(root, raw),
filepath.Join(root, filepath.FromSlash(rawT300)),
filepath.Join(root, relUploads), filepath.Join(root, relUploads),
filepath.Join(root, relUploadsT300),
filepath.Join(root, "uploads", raw), filepath.Join(root, "uploads", raw),
filepath.Join(root, "uploads", filepath.FromSlash(rawT300)),
) )
} }
for _, p := range candidates { for _, p := range candidates {
if p == "" { if p == "" {
continue continue
} }
if st, err := os.Stat(p); err == nil && !st.IsDir() { if st, err := os.Stat(p); err == nil && !st.IsDir() {
return p, candidates return p, candidates
} }

View File

@@ -1,125 +0,0 @@
/* eslint-disable */
/**
* THIS FILE IS GENERATED AUTOMATICALLY.
* 1. DO NOT edit this file directly as it won't do anything.
* 2. EDIT the original quasar.config file INSTEAD.
* 3. DO NOT git commit this file. It should be ignored.
*
* This file is still here because there was an error in
* the original quasar.config file and this allows you to
* investigate the Node.js stack error.
*
* After you fix the original file, this file will be
* deleted automatically.
**/
// quasar.config.js
import { defineConfig } from "@quasar/app-webpack/wrappers";
var quasar_config_default = defineConfig(() => {
const apiBaseUrl = (process.env.VITE_API_BASE_URL || "/api").trim();
return {
/* =====================================================
APP INFO
===================================================== */
productName: "Baggi BSS",
productDescription: "Baggi Tekstil Business Support System",
/* =====================================================
BOOT FILES
===================================================== */
boot: ["dayjs"],
/* =====================================================
GLOBAL CSS
===================================================== */
css: ["app.css"],
/* =====================================================
ICONS / FONTS
===================================================== */
extras: [
"roboto-font",
"material-icons"
],
/* =====================================================
BUILD (PRODUCTION)
===================================================== */
build: {
vueRouterMode: "hash",
env: {
VITE_API_BASE_URL: apiBaseUrl
},
esbuildTarget: {
browser: ["es2022", "firefox115", "chrome115", "safari14"],
node: "node20"
},
// Cache & performance
gzip: true,
preloadChunks: true
},
/* =====================================================
DEV SERVER (LOCAL)
===================================================== */
devServer: {
server: { type: "http" },
port: 9e3,
open: true,
// DEV proxy (CORS'suz)
proxy: [
{
context: ["/api"],
target: "http://localhost:8080",
changeOrigin: true,
secure: false
}
]
},
/* =====================================================
QUASAR FRAMEWORK
===================================================== */
framework: {
config: {
notify: {
position: "top",
timeout: 2500
}
},
lang: "tr",
plugins: [
"Loading",
"Dialog",
"Notify"
]
},
animations: [],
/* =====================================================
SSR / PWA (DISABLED)
===================================================== */
ssr: {
prodPort: 3e3,
middlewares: ["render"],
pwa: false
},
pwa: {
workboxMode: "GenerateSW"
},
/* =====================================================
MOBILE / DESKTOP
===================================================== */
capacitor: {
hideSplashscreen: true
},
electron: {
preloadScripts: ["electron-preload"],
inspectPort: 5858,
bundler: "packager",
builder: {
appId: "baggisowtfaresystem"
}
},
bex: {
extraScripts: []
}
};
});
export {
quasar_config_default as default
};

View File

@@ -635,12 +635,8 @@ const canReadFinance = canRead('finance')
const canExportFinance = canExport('finance') const canExportFinance = canExport('finance')
const islemTipiOptions = [ const islemTipiOptions = [
{ label: '1_2 Bakiye Pr.Br', value: 'prbr_1_2' }, { label: '1_2', value: '1_2' },
{ label: '1_3 Bakiye Pr.Br', value: 'prbr_1_3' }, { label: '1_3', value: '1_3' }
{ label: '1_2 USD Bakiye', value: 'usd_1_2' },
{ label: '1_2 TRY Bakiye', value: 'try_1_2' },
{ label: '1_3 USD Bakiye', value: 'usd_1_3' },
{ label: '1_3 TRY Bakiye', value: 'try_1_3' }
] ]
const staticMoneyFields = ['usd_bakiye_1_2', 'tl_bakiye_1_2', 'usd_bakiye_1_3', 'tl_bakiye_1_3'] const staticMoneyFields = ['usd_bakiye_1_2', 'tl_bakiye_1_2', 'usd_bakiye_1_3', 'tl_bakiye_1_3']
@@ -688,11 +684,7 @@ const metricDefs = {
try_1_3: { name: 'tl_bakiye_1_3', label: '1_3 TRY_BAKIYE', field: 'tl_bakiye_1_3', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) } try_1_3: { name: 'tl_bakiye_1_3', label: '1_3 TRY_BAKIYE', field: 'tl_bakiye_1_3', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) }
} }
const selectedMetricKeys = computed(() => { const selectedMetricKeys = computed(() => [...Object.keys(metricDefs)])
const selected = store.filters.islemTipi || []
if (!selected.length) return [...Object.keys(metricDefs)]
return selected.filter((k) => k in metricDefs)
})
const summaryColumns = computed(() => ([ const summaryColumns = computed(() => ([
{ name: 'expand', label: '', field: 'expand', align: 'center', sortable: false }, { name: 'expand', label: '', field: 'expand', align: 'center', sortable: false },

View File

@@ -538,12 +538,8 @@ const canReadFinance = canRead('finance')
const canExportFinance = canExport('finance') const canExportFinance = canExport('finance')
const islemTipiOptions = [ const islemTipiOptions = [
{ label: '1_2 Bakiye Pr.Br', value: 'prbr_1_2' }, { label: '1_2', value: '1_2' },
{ label: '1_3 Bakiye Pr.Br', value: 'prbr_1_3' }, { label: '1_3', value: '1_3' }
{ label: '1_2 USD Bakiye', value: 'usd_1_2' },
{ label: '1_2 TRY Bakiye', value: 'try_1_2' },
{ label: '1_3 USD Bakiye', value: 'usd_1_3' },
{ label: '1_3 TRY Bakiye', value: 'try_1_3' }
] ]
const staticMoneyFields = ['usd_bakiye_1_2', 'tl_bakiye_1_2', 'usd_bakiye_1_3', 'tl_bakiye_1_3'] const staticMoneyFields = ['usd_bakiye_1_2', 'tl_bakiye_1_2', 'usd_bakiye_1_3', 'tl_bakiye_1_3']
@@ -590,11 +586,7 @@ const metricDefs = {
try_1_3: { name: 'tl_bakiye_1_3', label: '1_3 TRY_BAKIYE', field: 'tl_bakiye_1_3', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) } try_1_3: { name: 'tl_bakiye_1_3', label: '1_3 TRY_BAKIYE', field: 'tl_bakiye_1_3', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) }
} }
const selectedMetricKeys = computed(() => { const selectedMetricKeys = computed(() => Object.keys(metricDefs))
const selected = store.filters.islemTipi || []
if (!selected.length) return Object.keys(metricDefs)
return selected.filter((k) => k in metricDefs)
})
const summaryColumns = computed(() => ([ const summaryColumns = computed(() => ([
{ name: 'expand', label: '', field: 'expand', align: 'center', sortable: false }, { name: 'expand', label: '', field: 'expand', align: 'center', sortable: false },

View File

@@ -78,12 +78,8 @@ export const useAccountAgingBalanceStore = defineStore('accountAgingBalance', {
const islemTipiOk = const islemTipiOk =
!state.filters.islemTipi.length || !state.filters.islemTipi.length ||
state.filters.islemTipi.some((t) => { state.filters.islemTipi.some((t) => {
if (t === 'prbr_1_2') return bak12 !== 0 if (t === '1_2') return bak12 !== 0 || usd12 !== 0 || try12 !== 0
if (t === 'prbr_1_3') return bak13 !== 0 if (t === '1_3') return bak13 !== 0 || usd13 !== 0 || try13 !== 0
if (t === 'usd_1_2') return usd12 !== 0
if (t === 'try_1_2') return try12 !== 0
if (t === 'usd_1_3') return usd13 !== 0
if (t === 'try_1_3') return try13 !== 0
return false return false
}) })

View File

@@ -92,12 +92,8 @@ export const useCustomerBalanceListStore = defineStore('customerBalanceList', {
const islemTipiOk = const islemTipiOk =
!state.filters.islemTipi.length || !state.filters.islemTipi.length ||
state.filters.islemTipi.some((t) => { state.filters.islemTipi.some((t) => {
if (t === 'prbr_1_2') return bak12 !== 0 if (t === '1_2') return bak12 !== 0 || usd12 !== 0 || try12 !== 0
if (t === 'prbr_1_3') return bak13 !== 0 if (t === '1_3') return bak13 !== 0 || usd13 !== 0 || try13 !== 0
if (t === 'usd_1_2') return usd12 !== 0
if (t === 'try_1_2') return try12 !== 0
if (t === 'usd_1_3') return usd13 !== 0
if (t === 'try_1_3') return try13 !== 0
return false return false
}) })
@@ -194,7 +190,15 @@ export const useCustomerBalanceListStore = defineStore('customerBalanceList', {
const { data } = await api.get('/finance/customer-balances', { const { data } = await api.get('/finance/customer-balances', {
params: { params: {
selected_date: this.filters.selectedDate, selected_date: this.filters.selectedDate,
cari_search: String(this.filters.cariSearch || '').trim() cari_search: String(this.filters.cariSearch || '').trim(),
cari_ilk_grup: (this.filters.cariIlkGrup || []).join(','),
piyasa: (this.filters.piyasa || []).join(','),
temsilci: (this.filters.temsilci || []).join(','),
risk_durumu: (this.filters.riskDurumu || []).join(','),
islem_tipi: (this.filters.islemTipi || []).join(','),
ulke: (this.filters.ulke || []).join(','),
il: (this.filters.il || []).join(','),
ilce: (this.filters.ilce || []).join(',')
} }
}) })
this.rows = Array.isArray(data) ? data : [] this.rows = Array.isArray(data) ? data : []