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

@@ -20,6 +20,7 @@ type mkCariBakiyeLine struct {
CariDoviz string CariDoviz string
SirketKodu int SirketKodu int
PislemTipi string PislemTipi string
ParasalIslemTipi string
YerelBakiye float64 YerelBakiye float64
Bakiye float64 Bakiye float64
VadeGun float64 VadeGun float64
@@ -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)
if expr := strings.TrimSpace(resolveParasalIslemSelectExpr(ctx, "SELECT * FROM dbo.MK_CARI_BAKIYE_LIST('2000-01-01')")); 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("SonTarih", selectedDate), sql.Named("SonTarih", selectedDate),
sql.Named("CariSearch", strings.TrimSpace(cariSearch)), sql.Named("CariSearch", strings.TrimSpace(cariSearch)),
) )
if err != nil { 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 : []