Merge remote-tracking branch 'origin/master'
This commit is contained in:
24
svc/main.go
24
svc/main.go
@@ -431,6 +431,30 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
|
|||||||
wrapV3(http.HandlerFunc(routes.GetCustomerBalanceListHandler)),
|
wrapV3(http.HandlerFunc(routes.GetCustomerBalanceListHandler)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/finance/customer-balances/export-pdf", "GET",
|
||||||
|
"finance", "export",
|
||||||
|
wrapV3(routes.ExportCustomerBalancePDFHandler(mssql)),
|
||||||
|
)
|
||||||
|
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/finance/customer-balances/export-excel", "GET",
|
||||||
|
"finance", "export",
|
||||||
|
wrapV3(routes.ExportCustomerBalanceExcelHandler(mssql)),
|
||||||
|
)
|
||||||
|
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/finance/account-aging-statement", "GET",
|
||||||
|
"finance", "view",
|
||||||
|
wrapV3(http.HandlerFunc(routes.GetStatementAgingHandler)),
|
||||||
|
)
|
||||||
|
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/finance/account-aging-statement/export-pdf", "GET",
|
||||||
|
"finance", "export",
|
||||||
|
wrapV3(routes.ExportStatementAgingPDFHandler(mssql)),
|
||||||
|
)
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// REPORT (STATEMENTS)
|
// REPORT (STATEMENTS)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
@@ -9,28 +9,36 @@ type CustomerBalanceListParams struct {
|
|||||||
RiskDurumu string
|
RiskDurumu string
|
||||||
IslemTipi string
|
IslemTipi string
|
||||||
Ulke string
|
Ulke string
|
||||||
|
Il string
|
||||||
|
Ilce string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CustomerBalanceListRow struct {
|
type CustomerBalanceListRow struct {
|
||||||
CariIlkGrup string `json:"cari_ilk_grup"`
|
CariIlkGrup string `json:"cari_ilk_grup"`
|
||||||
Piyasa string `json:"piyasa"`
|
Piyasa string `json:"piyasa"`
|
||||||
Temsilci string `json:"temsilci"`
|
Temsilci string `json:"temsilci"`
|
||||||
Sirket string `json:"sirket"`
|
Sirket string `json:"sirket"`
|
||||||
AnaCariKodu string `json:"ana_cari_kodu"`
|
AnaCariKodu string `json:"ana_cari_kodu"`
|
||||||
AnaCariAdi string `json:"ana_cari_adi"`
|
AnaCariAdi string `json:"ana_cari_adi"`
|
||||||
CariKodu string `json:"cari_kodu"`
|
CariKodu string `json:"cari_kodu"`
|
||||||
CariDetay string `json:"cari_detay"`
|
CariDetay string `json:"cari_detay"`
|
||||||
Ozellik03 string `json:"ozellik03"`
|
CariTip string `json:"cari_tip"`
|
||||||
Ozellik05 string `json:"ozellik05"`
|
Kanal1 string `json:"kanal_1"`
|
||||||
Ozellik06 string `json:"ozellik06"`
|
Ozellik03 string `json:"ozellik03"`
|
||||||
Ozellik07 string `json:"ozellik07"`
|
Ozellik05 string `json:"ozellik05"`
|
||||||
CariDoviz string `json:"cari_doviz"`
|
Ozellik06 string `json:"ozellik06"`
|
||||||
Bakiye12 float64 `json:"bakiye_1_2"`
|
Ozellik07 string `json:"ozellik07"`
|
||||||
TLBakiye12 float64 `json:"tl_bakiye_1_2"`
|
Il string `json:"il"`
|
||||||
USDBakiye12 float64 `json:"usd_bakiye_1_2"`
|
Ilce string `json:"ilce"`
|
||||||
Bakiye13 float64 `json:"bakiye_1_3"`
|
MuhasebeKodu string `json:"muhasebe_kodu"`
|
||||||
TLBakiye13 float64 `json:"tl_bakiye_1_3"`
|
TC string `json:"tc"`
|
||||||
USDBakiye13 float64 `json:"usd_bakiye_1_3"`
|
RiskDurumu string `json:"risk_durumu"`
|
||||||
HesapAlinmayanGun NullInt32 `json:"hesap_alinmayan_gun"`
|
SirketDetay string `json:"sirket_detay"`
|
||||||
KalanFaturaOrtalamaVadeTarihi NullString `json:"kalan_fatura_ortalama_vade_tarihi"`
|
CariDoviz string `json:"cari_doviz"`
|
||||||
|
Bakiye12 float64 `json:"bakiye_1_2"`
|
||||||
|
TLBakiye12 float64 `json:"tl_bakiye_1_2"`
|
||||||
|
USDBakiye12 float64 `json:"usd_bakiye_1_2"`
|
||||||
|
Bakiye13 float64 `json:"bakiye_1_3"`
|
||||||
|
TLBakiye13 float64 `json:"tl_bakiye_1_3"`
|
||||||
|
USDBakiye13 float64 `json:"usd_bakiye_1_3"`
|
||||||
}
|
}
|
||||||
|
|||||||
7
svc/models/statement_aging_params.go
Normal file
7
svc/models/statement_aging_params.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type StatementAgingParams struct {
|
||||||
|
AccountCode string `json:"accountcode"`
|
||||||
|
EndDate string `json:"enddate"`
|
||||||
|
Parislemler []string `json:"parislemler"`
|
||||||
|
}
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
package queries
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bssapp-backend/models"
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetCustomerBalanceList(
|
|
||||||
ctx context.Context,
|
|
||||||
params models.CustomerBalanceListParams,
|
|
||||||
) ([]models.CustomerBalanceListRow, error) {
|
|
||||||
|
|
||||||
//------------------------------------------------
|
|
||||||
// 1️⃣ DATA ÇEK
|
|
||||||
//------------------------------------------------
|
|
||||||
|
|
||||||
balances, err := getFastBalances(ctx, params.SelectedDate)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
masterMap, err := getCariMasterMap(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------
|
|
||||||
// 2️⃣ MERGE
|
|
||||||
//------------------------------------------------
|
|
||||||
|
|
||||||
resultMap := make(map[string]*models.CustomerBalanceListRow)
|
|
||||||
|
|
||||||
for _, b := range balances {
|
|
||||||
|
|
||||||
key := b.CariKodu + "|" + b.CariDoviz
|
|
||||||
|
|
||||||
r, ok := resultMap[key]
|
|
||||||
if !ok {
|
|
||||||
|
|
||||||
r = &models.CustomerBalanceListRow{
|
|
||||||
CariKodu: b.CariKodu,
|
|
||||||
CariDoviz: b.CariDoviz,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Master
|
|
||||||
if m, ok := masterMap[b.CariKodu]; ok {
|
|
||||||
|
|
||||||
r.CariDetay = m.CariDetay
|
|
||||||
r.Piyasa = m.Piyasa
|
|
||||||
r.Temsilci = m.Temsilci
|
|
||||||
|
|
||||||
r.Ozellik03 = m.Ozellik03
|
|
||||||
r.Ozellik05 = m.Ozellik05
|
|
||||||
r.Ozellik06 = m.Ozellik06
|
|
||||||
r.Ozellik07 = m.Ozellik07
|
|
||||||
r.CariIlkGrup = m.Ozellik08
|
|
||||||
}
|
|
||||||
|
|
||||||
resultMap[key] = r
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------
|
|
||||||
// 3️⃣ TOPLA
|
|
||||||
//------------------------------------------------
|
|
||||||
|
|
||||||
if b.PislemTipi == "1_2" {
|
|
||||||
|
|
||||||
r.Bakiye12 += b.Bakiye
|
|
||||||
r.TLBakiye12 += b.KurBakiye
|
|
||||||
r.USDBakiye12 += b.KurBakiye / b.UsdKur
|
|
||||||
|
|
||||||
} else if b.PislemTipi == "1_3" {
|
|
||||||
|
|
||||||
r.Bakiye13 += b.Bakiye
|
|
||||||
r.TLBakiye13 += b.KurBakiye
|
|
||||||
r.USDBakiye13 += b.KurBakiye / b.UsdKur
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------
|
|
||||||
// 4️⃣ SLICE DÖN
|
|
||||||
//------------------------------------------------
|
|
||||||
|
|
||||||
out := make([]models.CustomerBalanceListRow, 0, len(resultMap))
|
|
||||||
|
|
||||||
for _, v := range resultMap {
|
|
||||||
out = append(out, *v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
package queries
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bssapp-backend/db"
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CariMasterRow struct {
|
|
||||||
CariKodu string
|
|
||||||
CariDetay string
|
|
||||||
Piyasa string
|
|
||||||
Temsilci string
|
|
||||||
Ozellik03 string
|
|
||||||
Ozellik05 string
|
|
||||||
Ozellik06 string
|
|
||||||
Ozellik07 string
|
|
||||||
Ozellik08 string
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCariMasterMap(
|
|
||||||
ctx context.Context,
|
|
||||||
) (map[string]CariMasterRow, error) {
|
|
||||||
|
|
||||||
const q = `
|
|
||||||
WITH CTE AS (
|
|
||||||
SELECT
|
|
||||||
*,
|
|
||||||
rn = ROW_NUMBER() OVER (
|
|
||||||
PARTITION BY LEFT(CariKodu,8)
|
|
||||||
ORDER BY CariKodu
|
|
||||||
)
|
|
||||||
FROM dbo.MK_CARI_ILETISIM WITH(NOLOCK)
|
|
||||||
)
|
|
||||||
SELECT
|
|
||||||
CariKodu,
|
|
||||||
CariDetay,
|
|
||||||
PIYASA,
|
|
||||||
CARI_TEMSILCI,
|
|
||||||
Ozellik03,
|
|
||||||
Ozellik05,
|
|
||||||
Ozellik06,
|
|
||||||
Ozellik07,
|
|
||||||
Ozellik08
|
|
||||||
FROM CTE
|
|
||||||
WHERE rn=1
|
|
||||||
`
|
|
||||||
|
|
||||||
rows, err := db.MssqlDB.QueryContext(ctx, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
out := make(map[string]CariMasterRow, 4096)
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
|
|
||||||
var r CariMasterRow
|
|
||||||
|
|
||||||
err := rows.Scan(
|
|
||||||
&r.CariKodu,
|
|
||||||
&r.CariDetay,
|
|
||||||
&r.Piyasa,
|
|
||||||
&r.Temsilci,
|
|
||||||
&r.Ozellik03,
|
|
||||||
&r.Ozellik05,
|
|
||||||
&r.Ozellik06,
|
|
||||||
&r.Ozellik07,
|
|
||||||
&r.Ozellik08,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
out[r.CariKodu] = r
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
@@ -3,62 +3,10 @@ package queries
|
|||||||
import (
|
import (
|
||||||
"bssapp-backend/models"
|
"bssapp-backend/models"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"sync"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/* ===============================
|
// GetCachedCurrencyV3 keeps compatibility with existing order routes.
|
||||||
CACHE STRUCT
|
func GetCachedCurrencyV3(db *sql.DB, currencyCode string) (*models.TodayCurrencyV3, error) {
|
||||||
================================ */
|
return GetTodayCurrencyV3(db, strings.ToUpper(strings.TrimSpace(currencyCode)))
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
784
svc/queries/customer_balance_list.go
Normal file
784
svc/queries/customer_balance_list.go
Normal file
@@ -0,0 +1,784 @@
|
|||||||
|
package queries
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bssapp-backend/auth"
|
||||||
|
"bssapp-backend/db"
|
||||||
|
"bssapp-backend/internal/authz"
|
||||||
|
"bssapp-backend/models"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mkCariBakiyeLine struct {
|
||||||
|
CurrAccTypeCode int
|
||||||
|
CariKodu string
|
||||||
|
CariDoviz string
|
||||||
|
SirketKodu int
|
||||||
|
PislemTipi string
|
||||||
|
YerelBakiye float64
|
||||||
|
Bakiye float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type cariMeta struct {
|
||||||
|
CariDetay string
|
||||||
|
CariTip string
|
||||||
|
Kanal1 string
|
||||||
|
Piyasa string
|
||||||
|
Temsilci string
|
||||||
|
Ulke string
|
||||||
|
Il string
|
||||||
|
Ilce string
|
||||||
|
TC string
|
||||||
|
RiskDurumu string
|
||||||
|
MuhasebeKodu string
|
||||||
|
SirketDetay string
|
||||||
|
}
|
||||||
|
|
||||||
|
type masterCariMeta struct {
|
||||||
|
CariDetay string
|
||||||
|
Kanal1 string
|
||||||
|
Piyasa string
|
||||||
|
Temsilci string
|
||||||
|
Ulke string
|
||||||
|
Il string
|
||||||
|
Ilce string
|
||||||
|
RiskDurumu string
|
||||||
|
}
|
||||||
|
|
||||||
|
type balanceFilters struct {
|
||||||
|
cariIlkGrup map[string]struct{}
|
||||||
|
piyasa map[string]struct{}
|
||||||
|
temsilci map[string]struct{}
|
||||||
|
riskDurumu map[string]struct{}
|
||||||
|
islemTipi map[string]struct{}
|
||||||
|
ulke map[string]struct{}
|
||||||
|
il map[string]struct{}
|
||||||
|
ilce map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCustomerBalanceList(ctx context.Context, params models.CustomerBalanceListParams) ([]models.CustomerBalanceListRow, error) {
|
||||||
|
if strings.TrimSpace(params.SelectedDate) == "" {
|
||||||
|
return nil, fmt.Errorf("selected_date is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
lines, err := loadBalanceLines(ctx, params.SelectedDate, params.CariSearch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
metaMap, err := loadCariMetaMap(ctx, lines)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("customer_balance_list: cari meta query failed, fallback without meta: %v", err)
|
||||||
|
metaMap = map[string]cariMeta{}
|
||||||
|
}
|
||||||
|
|
||||||
|
masterMetaMap, err := loadMasterCariMetaMap(ctx, lines)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("customer_balance_list: master cari meta query failed, fallback without master meta: %v", err)
|
||||||
|
masterMetaMap = map[string]masterCariMeta{}
|
||||||
|
}
|
||||||
|
|
||||||
|
companyMap, err := loadCompanyMap(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
glMap, err := loadGLAccountMap(ctx, lines)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rateMap, err := loadNearestTryRates(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
usdTry := rateMap["USD"]
|
||||||
|
if usdTry <= 0 {
|
||||||
|
usdTry = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
filters := buildFilters(params)
|
||||||
|
agg := make(map[string]*models.CustomerBalanceListRow, len(lines))
|
||||||
|
|
||||||
|
for _, ln := range lines {
|
||||||
|
cari := strings.TrimSpace(ln.CariKodu)
|
||||||
|
if cari == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
curr := strings.ToUpper(strings.TrimSpace(ln.CariDoviz))
|
||||||
|
if curr == "" {
|
||||||
|
curr = "TRY"
|
||||||
|
}
|
||||||
|
|
||||||
|
meta := metaMap[metaKey(ln.CurrAccTypeCode, cari)]
|
||||||
|
meta.MuhasebeKodu = glMap[glKey(ln.CurrAccTypeCode, cari, ln.SirketKodu)]
|
||||||
|
meta.SirketDetay = companyMap[ln.SirketKodu]
|
||||||
|
master := deriveMasterCari(cari)
|
||||||
|
mm := masterMetaMap[master]
|
||||||
|
|
||||||
|
if strings.TrimSpace(mm.Kanal1) != "" {
|
||||||
|
meta.Kanal1 = mm.Kanal1
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(mm.Piyasa) != "" {
|
||||||
|
meta.Piyasa = mm.Piyasa
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(mm.Temsilci) != "" {
|
||||||
|
meta.Temsilci = mm.Temsilci
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(mm.Ulke) != "" {
|
||||||
|
meta.Ulke = mm.Ulke
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(mm.Il) != "" {
|
||||||
|
meta.Il = mm.Il
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(mm.Ilce) != "" {
|
||||||
|
meta.Ilce = mm.Ilce
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(mm.RiskDurumu) != "" {
|
||||||
|
meta.RiskDurumu = mm.RiskDurumu
|
||||||
|
}
|
||||||
|
|
||||||
|
if !filters.matchLine(ln.PislemTipi, meta) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := strconv.Itoa(ln.CurrAccTypeCode) + "|" + cari + "|" + curr + "|" + strconv.Itoa(ln.SirketKodu)
|
||||||
|
row, ok := agg[key]
|
||||||
|
if !ok {
|
||||||
|
row = &models.CustomerBalanceListRow{
|
||||||
|
CariIlkGrup: meta.Kanal1,
|
||||||
|
Piyasa: meta.Piyasa,
|
||||||
|
Temsilci: meta.Temsilci,
|
||||||
|
Sirket: strconv.Itoa(ln.SirketKodu),
|
||||||
|
AnaCariKodu: master,
|
||||||
|
AnaCariAdi: firstNonEmpty(mm.CariDetay, meta.CariDetay),
|
||||||
|
CariKodu: cari,
|
||||||
|
CariDetay: meta.CariDetay,
|
||||||
|
CariTip: meta.CariTip,
|
||||||
|
Kanal1: meta.Kanal1,
|
||||||
|
Ozellik03: meta.RiskDurumu,
|
||||||
|
Ozellik05: meta.Ulke,
|
||||||
|
Ozellik06: meta.Il,
|
||||||
|
Ozellik07: meta.Ilce,
|
||||||
|
Il: meta.Il,
|
||||||
|
Ilce: meta.Ilce,
|
||||||
|
MuhasebeKodu: meta.MuhasebeKodu,
|
||||||
|
TC: meta.TC,
|
||||||
|
RiskDurumu: meta.RiskDurumu,
|
||||||
|
SirketDetay: meta.SirketDetay,
|
||||||
|
CariDoviz: curr,
|
||||||
|
}
|
||||||
|
agg[key] = row
|
||||||
|
}
|
||||||
|
|
||||||
|
usd := toUSD(ln.Bakiye, curr, usdTry, rateMap)
|
||||||
|
|
||||||
|
switch strings.TrimSpace(ln.PislemTipi) {
|
||||||
|
case "1_2":
|
||||||
|
row.Bakiye12 += ln.Bakiye
|
||||||
|
row.TLBakiye12 += ln.YerelBakiye
|
||||||
|
row.USDBakiye12 += usd
|
||||||
|
case "1_3":
|
||||||
|
row.Bakiye13 += ln.Bakiye
|
||||||
|
row.TLBakiye13 += ln.YerelBakiye
|
||||||
|
row.USDBakiye13 += usd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]models.CustomerBalanceListRow, 0, len(agg))
|
||||||
|
for _, v := range agg {
|
||||||
|
out = append(out, *v)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(out, func(i, j int) bool {
|
||||||
|
if out[i].AnaCariKodu == out[j].AnaCariKodu {
|
||||||
|
if out[i].CariKodu == out[j].CariKodu {
|
||||||
|
return out[i].CariDoviz < out[j].CariDoviz
|
||||||
|
}
|
||||||
|
return out[i].CariKodu < out[j].CariKodu
|
||||||
|
}
|
||||||
|
return out[i].AnaCariKodu < out[j].AnaCariKodu
|
||||||
|
})
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMasterCariMetaMap(ctx context.Context, lines []mkCariBakiyeLine) (map[string]masterCariMeta, error) {
|
||||||
|
masters := make(map[string]struct{})
|
||||||
|
for _, ln := range lines {
|
||||||
|
m := strings.TrimSpace(deriveMasterCari(ln.CariKodu))
|
||||||
|
if m != "" {
|
||||||
|
masters[m] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(masters) == 0 {
|
||||||
|
return map[string]masterCariMeta{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
WITH BaseCari AS
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
CB.CurrAccCode,
|
||||||
|
CB.CurrAccTypeCode,
|
||||||
|
MasterCari = LEFT(CB.CurrAccCode, 8),
|
||||||
|
rn = ROW_NUMBER() OVER
|
||||||
|
(
|
||||||
|
PARTITION BY LEFT(CB.CurrAccCode, 8)
|
||||||
|
ORDER BY CB.CurrAccCode
|
||||||
|
)
|
||||||
|
FROM cdCurrAcc CB WITH (NOLOCK)
|
||||||
|
WHERE CB.CurrAccTypeCode IN (1,3)
|
||||||
|
AND LEFT(CB.CurrAccCode, 8) IN (%s)
|
||||||
|
),
|
||||||
|
FirstCari AS
|
||||||
|
(
|
||||||
|
SELECT *
|
||||||
|
FROM BaseCari
|
||||||
|
WHERE rn = 1
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
CariKodu = F.MasterCari,
|
||||||
|
CariDetay = ISNULL(cd.CurrAccDescription, ''),
|
||||||
|
KANAL_1 = ISNULL(CASE WHEN F.CurrAccTypeCode=1 THEN VDesc.VendorAtt08Desc ELSE CDesc.CustomerAtt08Desc END, ''),
|
||||||
|
PIYASA = ISNULL(CASE WHEN F.CurrAccTypeCode=1 THEN VDesc.VendorAtt01Desc ELSE CDesc.CustomerAtt01Desc END, ''),
|
||||||
|
CARI_TEMSILCI = ISNULL(
|
||||||
|
CASE
|
||||||
|
WHEN ISNULL(CASE WHEN F.CurrAccTypeCode = 1 THEN VDesc.VendorAtt02Desc ELSE CDesc.CustomerAtt02Desc END,'') = ''
|
||||||
|
THEN ISNULL(CASE WHEN F.CurrAccTypeCode = 1 THEN VAttr.VendorAtt09 ELSE CAttr.CustomerAtt09 END,'')
|
||||||
|
ELSE CASE WHEN F.CurrAccTypeCode = 1 THEN VDesc.VendorAtt02Desc ELSE CDesc.CustomerAtt02Desc END
|
||||||
|
END,''
|
||||||
|
),
|
||||||
|
ULKE = ISNULL(CASE WHEN F.CurrAccTypeCode=1 THEN VDesc.VendorAtt05Desc ELSE CDesc.CustomerAtt05Desc END, ''),
|
||||||
|
IL = ISNULL(CASE WHEN F.CurrAccTypeCode=1 THEN VDesc.VendorAtt06Desc ELSE CDesc.CustomerAtt06Desc END, ''),
|
||||||
|
ILCE = ISNULL(CASE WHEN F.CurrAccTypeCode=1 THEN VDesc.VendorAtt07Desc ELSE CDesc.CustomerAtt07Desc END, ''),
|
||||||
|
Risk_Durumu = ISNULL(CASE WHEN F.CurrAccTypeCode=1 THEN VDesc.VendorAtt03Desc ELSE CDesc.CustomerAtt03Desc END, '')
|
||||||
|
FROM FirstCari F
|
||||||
|
LEFT JOIN cdCurrAccDesc cd WITH (NOLOCK)
|
||||||
|
ON cd.CurrAccTypeCode = F.CurrAccTypeCode
|
||||||
|
AND cd.CurrAccCode = F.CurrAccCode
|
||||||
|
AND cd.LangCode = 'TR'
|
||||||
|
LEFT JOIN VendorAttributeDescriptions('TR') VDesc
|
||||||
|
ON VDesc.CurrAccCode = F.CurrAccCode
|
||||||
|
AND VDesc.CurrAccTypeCode = F.CurrAccTypeCode
|
||||||
|
LEFT JOIN CustomerAttributeDescriptions('TR') CDesc
|
||||||
|
ON CDesc.CurrAccCode = F.CurrAccCode
|
||||||
|
AND CDesc.CurrAccTypeCode = F.CurrAccTypeCode
|
||||||
|
LEFT JOIN VendorAttributes VAttr
|
||||||
|
ON VAttr.CurrAccCode = F.CurrAccCode
|
||||||
|
AND VAttr.CurrAccTypeCode = F.CurrAccTypeCode
|
||||||
|
LEFT JOIN CustomerAttributes CAttr
|
||||||
|
ON CAttr.CurrAccCode = F.CurrAccCode
|
||||||
|
AND CAttr.CurrAccTypeCode = F.CurrAccTypeCode
|
||||||
|
ORDER BY F.MasterCari;
|
||||||
|
`, quotedInList(masters))
|
||||||
|
|
||||||
|
rows, err := db.MssqlDB.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("master cari meta query error: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
out := make(map[string]masterCariMeta, len(masters))
|
||||||
|
for rows.Next() {
|
||||||
|
var master string
|
||||||
|
var m masterCariMeta
|
||||||
|
if err := rows.Scan(
|
||||||
|
&master,
|
||||||
|
&m.CariDetay,
|
||||||
|
&m.Kanal1,
|
||||||
|
&m.Piyasa,
|
||||||
|
&m.Temsilci,
|
||||||
|
&m.Ulke,
|
||||||
|
&m.Il,
|
||||||
|
&m.Ilce,
|
||||||
|
&m.RiskDurumu,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out[strings.TrimSpace(master)] = m
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadBalanceLines(ctx context.Context, selectedDate, cariSearch string) ([]mkCariBakiyeLine, error) {
|
||||||
|
query := `
|
||||||
|
SELECT
|
||||||
|
CurrAccTypeCode,
|
||||||
|
CariKodu,
|
||||||
|
CariDoviz,
|
||||||
|
SirketKodu,
|
||||||
|
PislemTipi,
|
||||||
|
YerelBakiye,
|
||||||
|
Bakiye
|
||||||
|
FROM dbo.MK_CARI_BAKIYE_LIST(@SonTarih)
|
||||||
|
WHERE (@CariSearch = '' OR CariKodu LIKE '%' + @CariSearch + '%')
|
||||||
|
`
|
||||||
|
|
||||||
|
rows, err := db.MssqlDB.QueryContext(ctx, query,
|
||||||
|
sql.Named("SonTarih", selectedDate),
|
||||||
|
sql.Named("CariSearch", strings.TrimSpace(cariSearch)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("MK_CARI_BAKIYE_LIST query error: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
out := make([]mkCariBakiyeLine, 0, 4096)
|
||||||
|
for rows.Next() {
|
||||||
|
var r mkCariBakiyeLine
|
||||||
|
if err := rows.Scan(
|
||||||
|
&r.CurrAccTypeCode,
|
||||||
|
&r.CariKodu,
|
||||||
|
&r.CariDoviz,
|
||||||
|
&r.SirketKodu,
|
||||||
|
&r.PislemTipi,
|
||||||
|
&r.YerelBakiye,
|
||||||
|
&r.Bakiye,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out = append(out, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadCariMetaMap(ctx context.Context, lines []mkCariBakiyeLine) (map[string]cariMeta, error) {
|
||||||
|
vendorCodes := make(map[string]struct{})
|
||||||
|
customerCodes := make(map[string]struct{})
|
||||||
|
|
||||||
|
for _, ln := range lines {
|
||||||
|
code := strings.TrimSpace(ln.CariKodu)
|
||||||
|
if code == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ln.CurrAccTypeCode == 1 {
|
||||||
|
vendorCodes[code] = struct{}{}
|
||||||
|
} else if ln.CurrAccTypeCode == 3 {
|
||||||
|
customerCodes[code] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(vendorCodes) == 0 && len(customerCodes) == 0 {
|
||||||
|
return map[string]cariMeta{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
whereParts := make([]string, 0, 2)
|
||||||
|
if len(vendorCodes) > 0 {
|
||||||
|
whereParts = append(whereParts, fmt.Sprintf("(c.CurrAccTypeCode=1 AND c.CurrAccCode IN (%s))", quotedInList(vendorCodes)))
|
||||||
|
}
|
||||||
|
if len(customerCodes) > 0 {
|
||||||
|
whereParts = append(whereParts, fmt.Sprintf("(c.CurrAccTypeCode=3 AND c.CurrAccCode IN (%s))", quotedInList(customerCodes)))
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
SELECT
|
||||||
|
c.CurrAccTypeCode,
|
||||||
|
c.CurrAccCode,
|
||||||
|
CariDetay = ISNULL(d.CurrAccDescription, ''),
|
||||||
|
CariTip = CASE WHEN c.CurrAccTypeCode = 1 THEN N'Satıcı' ELSE N'Müşteri' END,
|
||||||
|
KANAL_1 = ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt08Desc ELSE cad.CustomerAtt08Desc END, ''),
|
||||||
|
PIYASA = ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt01Desc ELSE cad.CustomerAtt01Desc END, ''),
|
||||||
|
CARI_TEMSILCI = ISNULL(
|
||||||
|
CASE
|
||||||
|
WHEN ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt02Desc ELSE cad.CustomerAtt02Desc END, '') = ''
|
||||||
|
THEN ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN va.VendorAtt09 ELSE ca.CustomerAtt09 END, '')
|
||||||
|
ELSE CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt02Desc ELSE cad.CustomerAtt02Desc END
|
||||||
|
END,
|
||||||
|
''),
|
||||||
|
ULKE = ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt05Desc ELSE cad.CustomerAtt05Desc END, ''),
|
||||||
|
IL = ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt06Desc ELSE cad.CustomerAtt06Desc END, ''),
|
||||||
|
ILCE = ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt07Desc ELSE cad.CustomerAtt07Desc END, ''),
|
||||||
|
TC = ISNULL(c.IdentityNum, ''),
|
||||||
|
Risk_Durumu = ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt03Desc ELSE cad.CustomerAtt03Desc END, '')
|
||||||
|
FROM cdCurrAcc c WITH(NOLOCK)
|
||||||
|
LEFT JOIN cdCurrAccDesc d WITH(NOLOCK)
|
||||||
|
ON d.CurrAccTypeCode = c.CurrAccTypeCode
|
||||||
|
AND d.CurrAccCode = c.CurrAccCode
|
||||||
|
AND d.LangCode = 'TR'
|
||||||
|
LEFT JOIN VendorAttributes va WITH(NOLOCK)
|
||||||
|
ON va.CurrAccTypeCode = c.CurrAccTypeCode
|
||||||
|
AND va.CurrAccCode = c.CurrAccCode
|
||||||
|
LEFT JOIN VendorAttributeDescriptions('TR') vad
|
||||||
|
ON vad.CurrAccTypeCode = c.CurrAccTypeCode
|
||||||
|
AND vad.CurrAccCode = c.CurrAccCode
|
||||||
|
LEFT JOIN CustomerAttributes ca WITH(NOLOCK)
|
||||||
|
ON ca.CurrAccTypeCode = c.CurrAccTypeCode
|
||||||
|
AND ca.CurrAccCode = c.CurrAccCode
|
||||||
|
LEFT JOIN CustomerAttributeDescriptions('TR') cad
|
||||||
|
ON cad.CurrAccTypeCode = c.CurrAccTypeCode
|
||||||
|
AND cad.CurrAccCode = c.CurrAccCode
|
||||||
|
WHERE c.CurrAccTypeCode IN (1,3)
|
||||||
|
AND (%s)
|
||||||
|
`, strings.Join(whereParts, " OR "))
|
||||||
|
|
||||||
|
rows, err := db.MssqlDB.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cari meta query error: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
out := make(map[string]cariMeta, len(lines))
|
||||||
|
for rows.Next() {
|
||||||
|
var t int
|
||||||
|
var code string
|
||||||
|
var m cariMeta
|
||||||
|
if err := rows.Scan(
|
||||||
|
&t,
|
||||||
|
&code,
|
||||||
|
&m.CariDetay,
|
||||||
|
&m.CariTip,
|
||||||
|
&m.Kanal1,
|
||||||
|
&m.Piyasa,
|
||||||
|
&m.Temsilci,
|
||||||
|
&m.Ulke,
|
||||||
|
&m.Il,
|
||||||
|
&m.Ilce,
|
||||||
|
&m.TC,
|
||||||
|
&m.RiskDurumu,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out[metaKey(t, code)] = m
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadGLAccountMap(ctx context.Context, lines []mkCariBakiyeLine) (map[string]string, error) {
|
||||||
|
vendorCodes := make(map[string]struct{})
|
||||||
|
customerCodes := make(map[string]struct{})
|
||||||
|
companyCodes := make(map[int]struct{})
|
||||||
|
|
||||||
|
for _, ln := range lines {
|
||||||
|
code := strings.TrimSpace(ln.CariKodu)
|
||||||
|
if code == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
companyCodes[ln.SirketKodu] = struct{}{}
|
||||||
|
if ln.CurrAccTypeCode == 1 {
|
||||||
|
vendorCodes[code] = struct{}{}
|
||||||
|
} else if ln.CurrAccTypeCode == 3 {
|
||||||
|
customerCodes[code] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(companyCodes) == 0 || (len(vendorCodes) == 0 && len(customerCodes) == 0) {
|
||||||
|
return map[string]string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
whereParts := make([]string, 0, 2)
|
||||||
|
if len(vendorCodes) > 0 {
|
||||||
|
whereParts = append(whereParts, fmt.Sprintf("(CurrAccTypeCode=1 AND CurrAccCode IN (%s))", quotedInList(vendorCodes)))
|
||||||
|
}
|
||||||
|
if len(customerCodes) > 0 {
|
||||||
|
whereParts = append(whereParts, fmt.Sprintf("(CurrAccTypeCode=3 AND CurrAccCode IN (%s))", quotedInList(customerCodes)))
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
SELECT CurrAccTypeCode, CurrAccCode, CompanyCode, GLAccCode
|
||||||
|
FROM prCurrAccGLAccount WITH(NOLOCK)
|
||||||
|
WHERE PostAccTypeCode = 100
|
||||||
|
AND CompanyCode IN (%s)
|
||||||
|
AND (%s)
|
||||||
|
`, intInList(companyCodes), strings.Join(whereParts, " OR "))
|
||||||
|
|
||||||
|
rows, err := db.MssqlDB.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("gl account query error: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
out := make(map[string]string)
|
||||||
|
for rows.Next() {
|
||||||
|
var t int
|
||||||
|
var code string
|
||||||
|
var company int
|
||||||
|
var gl sql.NullString
|
||||||
|
if err := rows.Scan(&t, &code, &company, &gl); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out[glKey(t, code, company)] = strings.TrimSpace(gl.String)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadCompanyMap(ctx context.Context) (map[int]string, error) {
|
||||||
|
rows, err := db.MssqlDB.QueryContext(ctx, `SELECT CompanyCode, CompanyName FROM cdCompany WITH(NOLOCK)`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("company map query error: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
out := make(map[int]string)
|
||||||
|
for rows.Next() {
|
||||||
|
var code int
|
||||||
|
var name sql.NullString
|
||||||
|
if err := rows.Scan(&code, &name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out[code] = strings.TrimSpace(name.String)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadNearestTryRates(ctx context.Context) (map[string]float64, error) {
|
||||||
|
query := `
|
||||||
|
WITH Ranked AS (
|
||||||
|
SELECT
|
||||||
|
CurrencyCode,
|
||||||
|
Rate,
|
||||||
|
rn = ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY CurrencyCode
|
||||||
|
ORDER BY ABS(DATEDIFF(DAY, Date, GETDATE())), Date DESC
|
||||||
|
)
|
||||||
|
FROM AllExchangeRates
|
||||||
|
WHERE RelationCurrencyCode = 'TRY'
|
||||||
|
AND ExchangeTypeCode = 6
|
||||||
|
AND Rate > 0
|
||||||
|
)
|
||||||
|
SELECT CurrencyCode, Rate
|
||||||
|
FROM Ranked
|
||||||
|
WHERE rn = 1
|
||||||
|
`
|
||||||
|
|
||||||
|
rows, err := db.MssqlDB.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("exchange rates query error: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
out := map[string]float64{"TRY": 1}
|
||||||
|
for rows.Next() {
|
||||||
|
var code string
|
||||||
|
var rate float64
|
||||||
|
if err := rows.Scan(&code, &rate); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
code = strings.ToUpper(strings.TrimSpace(code))
|
||||||
|
if code != "" && rate > 0 {
|
||||||
|
out[code] = rate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toUSD(amount float64, currency string, usdTry float64, rateMap map[string]float64) float64 {
|
||||||
|
if usdTry <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
switch currency {
|
||||||
|
case "USD":
|
||||||
|
return amount
|
||||||
|
case "TRY":
|
||||||
|
return amount / usdTry
|
||||||
|
default:
|
||||||
|
currTry := rateMap[currency]
|
||||||
|
if currTry <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return (amount * currTry) / usdTry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deriveMasterCari(cari string) string {
|
||||||
|
cari = strings.TrimSpace(cari)
|
||||||
|
if cari == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
base := cari
|
||||||
|
if idx := strings.Index(base, "/"); idx > 0 {
|
||||||
|
base = base[:idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
base = strings.TrimSpace(base)
|
||||||
|
if len(base) >= 8 {
|
||||||
|
return strings.TrimSpace(base[:8])
|
||||||
|
}
|
||||||
|
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildFilters(params models.CustomerBalanceListParams) balanceFilters {
|
||||||
|
return balanceFilters{
|
||||||
|
cariIlkGrup: parseCSVSet(params.CariIlkGrup),
|
||||||
|
piyasa: parseCSVSet(params.Piyasa),
|
||||||
|
temsilci: parseCSVSet(params.Temsilci),
|
||||||
|
riskDurumu: parseCSVSet(params.RiskDurumu),
|
||||||
|
islemTipi: parseCSVSet(params.IslemTipi),
|
||||||
|
ulke: parseCSVSet(params.Ulke),
|
||||||
|
il: parseCSVSet(params.Il),
|
||||||
|
ilce: parseCSVSet(params.Ilce),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f balanceFilters) matchLine(islemTipi string, m cariMeta) bool {
|
||||||
|
if !matchSet(f.islemTipi, islemTipi) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !matchSet(f.cariIlkGrup, m.Kanal1) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !matchSet(f.piyasa, m.Piyasa) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !matchSet(f.temsilci, m.Temsilci) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !matchSet(f.riskDurumu, m.RiskDurumu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !matchSet(f.ulke, m.Ulke) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !matchSet(f.il, m.Il) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !matchSet(f.ilce, m.Ilce) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchSet(set map[string]struct{}, value string) bool {
|
||||||
|
if len(set) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
trimmed := strings.TrimSpace(value)
|
||||||
|
if trimmed == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
_, ok := set[trimmed]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCSVSet(v string) map[string]struct{} {
|
||||||
|
out := make(map[string]struct{})
|
||||||
|
for _, p := range strings.Split(v, ",") {
|
||||||
|
t := strings.TrimSpace(p)
|
||||||
|
if t == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out[t] = struct{}{}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAuthorizedPiyasaCodes(ctx context.Context) ([]string, error) {
|
||||||
|
claims, ok := auth.GetClaimsFromContext(ctx)
|
||||||
|
if !ok || claims == nil {
|
||||||
|
return nil, fmt.Errorf("unauthorized: claims not found")
|
||||||
|
}
|
||||||
|
if claims.IsAdmin() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rawCodes := authz.GetPiyasaCodesFromCtx(ctx)
|
||||||
|
if len(rawCodes) == 0 {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
unique := make(map[string]struct{}, len(rawCodes))
|
||||||
|
out := make([]string, 0, len(rawCodes))
|
||||||
|
for _, code := range rawCodes {
|
||||||
|
norm := strings.ToUpper(strings.TrimSpace(code))
|
||||||
|
if norm == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, exists := unique[norm]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
unique[norm] = struct{}{}
|
||||||
|
out = append(out, norm)
|
||||||
|
}
|
||||||
|
if len(out) == 0 {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildPiyasaWhereClause(codes []string, column string) string {
|
||||||
|
if len(codes) == 0 {
|
||||||
|
return "1=1"
|
||||||
|
}
|
||||||
|
return authz.BuildINClause(column, codes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func metaKey(currType int, code string) string {
|
||||||
|
return strconv.Itoa(currType) + "|" + strings.TrimSpace(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func glKey(currType int, code string, company int) string {
|
||||||
|
return strconv.Itoa(currType) + "|" + strings.TrimSpace(code) + "|" + strconv.Itoa(company)
|
||||||
|
}
|
||||||
|
|
||||||
|
func quotedInList(set map[string]struct{}) string {
|
||||||
|
vals := make([]string, 0, len(set))
|
||||||
|
for v := range set {
|
||||||
|
esc := strings.ReplaceAll(strings.TrimSpace(v), "'", "''")
|
||||||
|
if esc != "" {
|
||||||
|
vals = append(vals, "'"+esc+"'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(vals) == 0 {
|
||||||
|
return "''"
|
||||||
|
}
|
||||||
|
sort.Strings(vals)
|
||||||
|
return strings.Join(vals, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func intInList(set map[int]struct{}) string {
|
||||||
|
vals := make([]int, 0, len(set))
|
||||||
|
for v := range set {
|
||||||
|
vals = append(vals, v)
|
||||||
|
}
|
||||||
|
if len(vals) == 0 {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
sort.Ints(vals)
|
||||||
|
parts := make([]string, 0, len(vals))
|
||||||
|
for _, v := range vals {
|
||||||
|
parts = append(parts, strconv.Itoa(v))
|
||||||
|
}
|
||||||
|
return strings.Join(parts, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstNonEmpty(v ...string) string {
|
||||||
|
for _, s := range v {
|
||||||
|
if strings.TrimSpace(s) != "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package queries
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bssapp-backend/db"
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FastBalanceRow struct {
|
|
||||||
CariKodu string
|
|
||||||
CariDoviz string
|
|
||||||
PislemTipi string
|
|
||||||
SirketKodu int
|
|
||||||
YerelBakiye float64
|
|
||||||
Bakiye float64
|
|
||||||
IslemTarihi string
|
|
||||||
IslemKur float64
|
|
||||||
TLBakiye float64
|
|
||||||
TLYerel float64
|
|
||||||
GuncelKur float64
|
|
||||||
KurBakiye float64
|
|
||||||
UsdKur float64
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFastBalances(
|
|
||||||
ctx context.Context,
|
|
||||||
date string,
|
|
||||||
) ([]FastBalanceRow, error) {
|
|
||||||
|
|
||||||
rows, err := db.MssqlDB.QueryContext(
|
|
||||||
ctx,
|
|
||||||
`EXEC dbo.SP_CARI_BAKIYE_API_FAST @Tarih`,
|
|
||||||
sql.Named("Tarih", date),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
out := make([]FastBalanceRow, 0, 4096)
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
|
|
||||||
var r FastBalanceRow
|
|
||||||
|
|
||||||
err := rows.Scan(
|
|
||||||
&r.CariKodu,
|
|
||||||
&r.CariDoviz,
|
|
||||||
&r.PislemTipi,
|
|
||||||
&r.SirketKodu,
|
|
||||||
&r.YerelBakiye,
|
|
||||||
&r.Bakiye,
|
|
||||||
&r.IslemTarihi,
|
|
||||||
&r.IslemKur,
|
|
||||||
&r.TLBakiye,
|
|
||||||
&r.TLYerel,
|
|
||||||
&r.GuncelKur,
|
|
||||||
&r.KurBakiye,
|
|
||||||
&r.UsdKur,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
out = append(out, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
414
svc/queries/statement_aging.go
Normal file
414
svc/queries/statement_aging.go
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
package queries
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bssapp-backend/db"
|
||||||
|
"bssapp-backend/models"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetStatementAging(params models.StatementAgingParams) ([]map[string]interface{}, error) {
|
||||||
|
accountCode := normalizeMasterAccountCode(params.AccountCode)
|
||||||
|
if strings.TrimSpace(accountCode) == "" {
|
||||||
|
return nil, fmt.Errorf("accountcode is required")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(params.EndDate) == "" {
|
||||||
|
return nil, fmt.Errorf("enddate is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
useType2, useType3 := resolveUseTypes(params.Parislemler)
|
||||||
|
endDate, _ := time.Parse("2006-01-02", strings.TrimSpace(params.EndDate))
|
||||||
|
|
||||||
|
rows, err := db.MssqlDB.Query(`
|
||||||
|
EXEC dbo.SP_FIFO_MATCH_FINAL
|
||||||
|
@Cari8 = @Cari8,
|
||||||
|
@SonTarih = @SonTarih,
|
||||||
|
@UseType2 = @UseType2,
|
||||||
|
@UseType3 = @UseType3;
|
||||||
|
`,
|
||||||
|
sql.Named("Cari8", accountCode),
|
||||||
|
sql.Named("SonTarih", params.EndDate),
|
||||||
|
sql.Named("UseType2", useType2),
|
||||||
|
sql.Named("UseType3", useType3),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("SP_FIFO_MATCH_FINAL query error: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
columns, err := rows.Columns()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("columns read error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]map[string]interface{}, 0, 2048)
|
||||||
|
cari8Set := make(map[string]struct{})
|
||||||
|
currencySet := make(map[string]struct{})
|
||||||
|
for rows.Next() {
|
||||||
|
values := make([]interface{}, len(columns))
|
||||||
|
scanArgs := make([]interface{}, len(columns))
|
||||||
|
for i := range values {
|
||||||
|
scanArgs[i] = &values[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Scan(scanArgs...); err != nil {
|
||||||
|
return nil, fmt.Errorf("row scan error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
row := make(map[string]interface{}, len(columns))
|
||||||
|
for i, col := range columns {
|
||||||
|
switch v := values[i].(type) {
|
||||||
|
case nil:
|
||||||
|
row[col] = nil
|
||||||
|
case []byte:
|
||||||
|
row[col] = string(v)
|
||||||
|
case time.Time:
|
||||||
|
row[col] = v.Format("2006-01-02")
|
||||||
|
default:
|
||||||
|
row[col] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cari8 := strings.TrimSpace(asString(row["Cari8"]))
|
||||||
|
if cari8 != "" {
|
||||||
|
cari8Set[cari8] = struct{}{}
|
||||||
|
}
|
||||||
|
curr := strings.ToUpper(strings.TrimSpace(asString(row["DocCurrencyCode"])))
|
||||||
|
if curr != "" && curr != "TRY" {
|
||||||
|
currencySet[curr] = struct{}{}
|
||||||
|
}
|
||||||
|
currencySet["USD"] = struct{}{}
|
||||||
|
|
||||||
|
result = append(result, row)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("rows error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cariDetailMap, err := loadAgingMasterCariDetailMap(context.Background(), cari8Set)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rateSeriesByCurr, err := loadTryRateSeriesByCurrency(context.Background(), currencySet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range result {
|
||||||
|
row := result[i]
|
||||||
|
cari8 := strings.TrimSpace(asString(row["Cari8"]))
|
||||||
|
curr := strings.ToUpper(strings.TrimSpace(asString(row["DocCurrencyCode"])))
|
||||||
|
if curr == "" {
|
||||||
|
curr = "TRY"
|
||||||
|
}
|
||||||
|
aciklama := strings.ToUpper(strings.TrimSpace(asString(row["Aciklama"])))
|
||||||
|
targetDate := endDate
|
||||||
|
if aciklama != "ACIKKALEM" {
|
||||||
|
if odemeTarihi, ok := parseDateOnly(asString(row["OdemeTarihi"])); ok {
|
||||||
|
targetDate = odemeTarihi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tutar := asFloat64(row["EslesenTutar"])
|
||||||
|
currTry := resolveTryRate(curr, targetDate, rateSeriesByCurr)
|
||||||
|
usdTry := resolveTryRate("USD", targetDate, rateSeriesByCurr)
|
||||||
|
tryTutar := toTRYByRate(tutar, curr, currTry)
|
||||||
|
usdTutar := toUSDByRates(tutar, curr, currTry, usdTry)
|
||||||
|
gunKur := usdRateInCurrency(curr, currTry, usdTry)
|
||||||
|
|
||||||
|
row["CariDetay"] = cariDetailMap[cari8]
|
||||||
|
row["UsdTutar"] = round2(usdTutar)
|
||||||
|
row["TryTutar"] = round2(tryTutar)
|
||||||
|
row["GunKur"] = round6(gunKur)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveUseTypes(parislemler []string) (int, int) {
|
||||||
|
if len(parislemler) == 0 {
|
||||||
|
return 1, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
useType2 := 0
|
||||||
|
useType3 := 0
|
||||||
|
|
||||||
|
for _, v := range parislemler {
|
||||||
|
switch strings.TrimSpace(v) {
|
||||||
|
case "2":
|
||||||
|
useType2 = 1
|
||||||
|
case "3":
|
||||||
|
useType3 = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if useType2 == 0 && useType3 == 0 {
|
||||||
|
return 1, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return useType2, useType3
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadAgingMasterCariDetailMap(ctx context.Context, cari8Set map[string]struct{}) (map[string]string, error) {
|
||||||
|
if len(cari8Set) == 0 {
|
||||||
|
return map[string]string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
WITH BaseCari AS (
|
||||||
|
SELECT
|
||||||
|
CurrAccCode,
|
||||||
|
CurrAccTypeCode,
|
||||||
|
MasterCari = LEFT(CurrAccCode, 8),
|
||||||
|
rn = ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY LEFT(CurrAccCode, 8)
|
||||||
|
ORDER BY CurrAccCode
|
||||||
|
)
|
||||||
|
FROM cdCurrAcc WITH (NOLOCK)
|
||||||
|
WHERE CurrAccTypeCode IN (1,3)
|
||||||
|
AND LEFT(CurrAccCode, 8) IN (%s)
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
b.MasterCari,
|
||||||
|
CariDetay = ISNULL(d.CurrAccDescription, '')
|
||||||
|
FROM BaseCari b
|
||||||
|
LEFT JOIN cdCurrAccDesc d WITH (NOLOCK)
|
||||||
|
ON d.CurrAccTypeCode = b.CurrAccTypeCode
|
||||||
|
AND d.CurrAccCode = b.CurrAccCode
|
||||||
|
AND d.LangCode = 'TR'
|
||||||
|
WHERE b.rn = 1;
|
||||||
|
`, quotedInList(cari8Set))
|
||||||
|
|
||||||
|
rows, err := db.MssqlDB.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("aging cari detail query error: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
out := make(map[string]string, len(cari8Set))
|
||||||
|
for rows.Next() {
|
||||||
|
var cari8 string
|
||||||
|
var detail sql.NullString
|
||||||
|
if err := rows.Scan(&cari8, &detail); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out[strings.TrimSpace(cari8)] = strings.TrimSpace(detail.String)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func asString(v interface{}) string {
|
||||||
|
switch x := v.(type) {
|
||||||
|
case nil:
|
||||||
|
return ""
|
||||||
|
case string:
|
||||||
|
return x
|
||||||
|
case []byte:
|
||||||
|
return string(x)
|
||||||
|
default:
|
||||||
|
return fmt.Sprint(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func asFloat64(v interface{}) float64 {
|
||||||
|
switch x := v.(type) {
|
||||||
|
case nil:
|
||||||
|
return 0
|
||||||
|
case float64:
|
||||||
|
return x
|
||||||
|
case float32:
|
||||||
|
return float64(x)
|
||||||
|
case int64:
|
||||||
|
return float64(x)
|
||||||
|
case int32:
|
||||||
|
return float64(x)
|
||||||
|
case int:
|
||||||
|
return float64(x)
|
||||||
|
case string:
|
||||||
|
return parseNumberString(x)
|
||||||
|
case []byte:
|
||||||
|
return parseNumberString(string(x))
|
||||||
|
default:
|
||||||
|
return parseNumberString(fmt.Sprint(x))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseNumberString(s string) float64 {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
if s == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
hasComma := strings.Contains(s, ",")
|
||||||
|
hasDot := strings.Contains(s, ".")
|
||||||
|
if hasComma && hasDot {
|
||||||
|
if strings.LastIndex(s, ",") > strings.LastIndex(s, ".") {
|
||||||
|
s = strings.ReplaceAll(s, ".", "")
|
||||||
|
s = strings.Replace(s, ",", ".", 1)
|
||||||
|
} else {
|
||||||
|
s = strings.ReplaceAll(s, ",", "")
|
||||||
|
}
|
||||||
|
} else if hasComma {
|
||||||
|
s = strings.ReplaceAll(s, ".", "")
|
||||||
|
s = strings.Replace(s, ",", ".", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func round2(v float64) float64 {
|
||||||
|
return math.Round(v*100) / 100
|
||||||
|
}
|
||||||
|
|
||||||
|
func round6(v float64) float64 {
|
||||||
|
return math.Round(v*1_000_000) / 1_000_000
|
||||||
|
}
|
||||||
|
|
||||||
|
type ratePoint struct {
|
||||||
|
date time.Time
|
||||||
|
rate float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadTryRateSeriesByCurrency(ctx context.Context, currencies map[string]struct{}) (map[string][]ratePoint, error) {
|
||||||
|
if len(currencies) == 0 {
|
||||||
|
return map[string][]ratePoint{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
SELECT CurrencyCode, Rate, CAST([Date] AS date) AS RateDate
|
||||||
|
FROM AllExchangeRates
|
||||||
|
WHERE RelationCurrencyCode = 'TRY'
|
||||||
|
AND ExchangeTypeCode = 6
|
||||||
|
AND Rate > 0
|
||||||
|
AND CurrencyCode IN (%s)
|
||||||
|
`, quotedInList(currencies))
|
||||||
|
|
||||||
|
rows, err := db.MssqlDB.QueryContext(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("aging currency series query error: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
out := make(map[string][]ratePoint, len(currencies))
|
||||||
|
for rows.Next() {
|
||||||
|
var code string
|
||||||
|
var rate float64
|
||||||
|
var dt time.Time
|
||||||
|
if err := rows.Scan(&code, &rate, &dt); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
code = strings.ToUpper(strings.TrimSpace(code))
|
||||||
|
out[code] = append(out[code], ratePoint{date: dt, rate: rate})
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for c := range out {
|
||||||
|
sort.Slice(out[c], func(i, j int) bool { return out[c][i].date.Before(out[c][j].date) })
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveTryRate(currency string, target time.Time, series map[string][]ratePoint) float64 {
|
||||||
|
currency = strings.ToUpper(strings.TrimSpace(currency))
|
||||||
|
if currency == "" || currency == "TRY" {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
points := series[currency]
|
||||||
|
if len(points) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
best := points[0]
|
||||||
|
bestDiff := absDurationDays(points[0].date.Sub(target))
|
||||||
|
for i := 1; i < len(points); i++ {
|
||||||
|
diff := absDurationDays(points[i].date.Sub(target))
|
||||||
|
if diff < bestDiff || (diff == bestDiff && points[i].date.After(best.date)) {
|
||||||
|
best = points[i]
|
||||||
|
bestDiff = diff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best.rate
|
||||||
|
}
|
||||||
|
|
||||||
|
func absDurationDays(d time.Duration) int64 {
|
||||||
|
if d < 0 {
|
||||||
|
d = -d
|
||||||
|
}
|
||||||
|
return int64(d.Hours() / 24)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDateOnly(v string) (time.Time, bool) {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
t, err := time.Parse("2006-01-02", v)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
return t, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func toTRYByRate(amount float64, currency string, currTry float64) float64 {
|
||||||
|
currency = strings.ToUpper(strings.TrimSpace(currency))
|
||||||
|
if currency == "" || currency == "TRY" {
|
||||||
|
return amount
|
||||||
|
}
|
||||||
|
if currTry <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return amount * currTry
|
||||||
|
}
|
||||||
|
|
||||||
|
func toUSDByRates(amount float64, currency string, currTry, usdTry float64) float64 {
|
||||||
|
currency = strings.ToUpper(strings.TrimSpace(currency))
|
||||||
|
switch currency {
|
||||||
|
case "USD":
|
||||||
|
return amount
|
||||||
|
case "", "TRY":
|
||||||
|
if usdTry <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return amount / usdTry
|
||||||
|
default:
|
||||||
|
if currTry <= 0 || usdTry <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return (amount * currTry) / usdTry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns X for "1 USD = X <currency>".
|
||||||
|
func usdRateInCurrency(currency string, currTry, usdTry float64) float64 {
|
||||||
|
currency = strings.ToUpper(strings.TrimSpace(currency))
|
||||||
|
switch currency {
|
||||||
|
case "", "USD":
|
||||||
|
return 1
|
||||||
|
case "TRY":
|
||||||
|
if usdTry <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return usdTry
|
||||||
|
default:
|
||||||
|
if currTry <= 0 || usdTry <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return usdTry / currTry
|
||||||
|
}
|
||||||
|
}
|
||||||
145
svc/routes/customer_balance_excel.go
Normal file
145
svc/routes/customer_balance_excel.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bssapp-backend/auth"
|
||||||
|
"bssapp-backend/models"
|
||||||
|
"bssapp-backend/queries"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xuri/excelize/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExportCustomerBalanceExcelHandler(_ *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||||||
|
if !ok || claims == nil {
|
||||||
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedDate := strings.TrimSpace(r.URL.Query().Get("selected_date"))
|
||||||
|
if selectedDate == "" {
|
||||||
|
selectedDate = time.Now().Format("2006-01-02")
|
||||||
|
}
|
||||||
|
|
||||||
|
params := models.CustomerBalanceListParams{
|
||||||
|
SelectedDate: selectedDate,
|
||||||
|
CariSearch: strings.TrimSpace(r.URL.Query().Get("cari_search")),
|
||||||
|
CariIlkGrup: strings.TrimSpace(r.URL.Query().Get("cari_ilk_grup")),
|
||||||
|
Piyasa: strings.TrimSpace(r.URL.Query().Get("piyasa")),
|
||||||
|
Temsilci: strings.TrimSpace(r.URL.Query().Get("temsilci")),
|
||||||
|
RiskDurumu: strings.TrimSpace(r.URL.Query().Get("risk_durumu")),
|
||||||
|
IslemTipi: strings.TrimSpace(r.URL.Query().Get("islem_tipi")),
|
||||||
|
Ulke: strings.TrimSpace(r.URL.Query().Get("ulke")),
|
||||||
|
Il: strings.TrimSpace(r.URL.Query().Get("il")),
|
||||||
|
Ilce: strings.TrimSpace(r.URL.Query().Get("ilce")),
|
||||||
|
}
|
||||||
|
|
||||||
|
excludeZero12 := parseBoolQuery(r.URL.Query().Get("exclude_zero_12"))
|
||||||
|
excludeZero13 := parseBoolQuery(r.URL.Query().Get("exclude_zero_13"))
|
||||||
|
|
||||||
|
rows, err := queries.GetCustomerBalanceList(r.Context(), params)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "db error: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rows = filterCustomerBalanceRowsForPDF(rows, excludeZero12, excludeZero13)
|
||||||
|
summaries, _ := buildCustomerBalancePDFData(rows)
|
||||||
|
|
||||||
|
f := excelize.NewFile()
|
||||||
|
sheet := "CariBakiye"
|
||||||
|
f.SetSheetName("Sheet1", sheet)
|
||||||
|
|
||||||
|
headers := []string{
|
||||||
|
"Ana Cari Kodu",
|
||||||
|
"Ana Cari Detay",
|
||||||
|
"Piyasa",
|
||||||
|
"Temsilci",
|
||||||
|
"Risk Durumu",
|
||||||
|
"1_2 Bakiye Pr.Br",
|
||||||
|
"1_3 Bakiye Pr.Br",
|
||||||
|
"1_2 USD Bakiye",
|
||||||
|
"1_2 TRY Bakiye",
|
||||||
|
"1_3 USD Bakiye",
|
||||||
|
"1_3 TRY Bakiye",
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, h := range headers {
|
||||||
|
cell, _ := excelize.CoordinatesToCellName(i+1, 1)
|
||||||
|
f.SetCellValue(sheet, cell, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalUSD12, totalTRY12, totalUSD13, totalTRY13 float64
|
||||||
|
totalPrBr12 := map[string]float64{}
|
||||||
|
totalPrBr13 := map[string]float64{}
|
||||||
|
|
||||||
|
for _, s := range summaries {
|
||||||
|
totalUSD12 += s.USDBakiye12
|
||||||
|
totalTRY12 += s.TLBakiye12
|
||||||
|
totalUSD13 += s.USDBakiye13
|
||||||
|
totalTRY13 += s.TLBakiye13
|
||||||
|
for k, v := range s.Bakiye12Map {
|
||||||
|
totalPrBr12[k] += v
|
||||||
|
}
|
||||||
|
for k, v := range s.Bakiye13Map {
|
||||||
|
totalPrBr13[k] += v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f.SetSheetRow(sheet, "A2", &[]any{
|
||||||
|
"TOPLAM",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
formatCurrencyMapPDF(totalPrBr12),
|
||||||
|
formatCurrencyMapPDF(totalPrBr13),
|
||||||
|
totalUSD12,
|
||||||
|
totalTRY12,
|
||||||
|
totalUSD13,
|
||||||
|
totalTRY13,
|
||||||
|
})
|
||||||
|
|
||||||
|
rowNo := 3
|
||||||
|
for _, s := range summaries {
|
||||||
|
f.SetSheetRow(sheet, fmt.Sprintf("A%d", rowNo), &[]any{
|
||||||
|
s.AnaCariKodu,
|
||||||
|
s.AnaCariAdi,
|
||||||
|
s.Piyasa,
|
||||||
|
s.Temsilci,
|
||||||
|
s.RiskDurumu,
|
||||||
|
formatCurrencyMapPDF(s.Bakiye12Map),
|
||||||
|
formatCurrencyMapPDF(s.Bakiye13Map),
|
||||||
|
s.USDBakiye12,
|
||||||
|
s.TLBakiye12,
|
||||||
|
s.USDBakiye13,
|
||||||
|
s.TLBakiye13,
|
||||||
|
})
|
||||||
|
rowNo++
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = f.SetColWidth(sheet, "A", "A", 16)
|
||||||
|
_ = f.SetColWidth(sheet, "B", "B", 34)
|
||||||
|
_ = f.SetColWidth(sheet, "C", "E", 18)
|
||||||
|
_ = f.SetColWidth(sheet, "F", "G", 34)
|
||||||
|
_ = f.SetColWidth(sheet, "H", "K", 18)
|
||||||
|
|
||||||
|
buf, err := f.WriteToBuffer()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("cari_bakiye_listesi_%s.xlsx", time.Now().Format("20060102_150405"))
|
||||||
|
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"")
|
||||||
|
w.Header().Set("Content-Length", fmt.Sprint(len(buf.Bytes())))
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write(buf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,6 +33,8 @@ func GetCustomerBalanceListHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
RiskDurumu: strings.TrimSpace(r.URL.Query().Get("risk_durumu")),
|
RiskDurumu: strings.TrimSpace(r.URL.Query().Get("risk_durumu")),
|
||||||
IslemTipi: strings.TrimSpace(r.URL.Query().Get("islem_tipi")),
|
IslemTipi: strings.TrimSpace(r.URL.Query().Get("islem_tipi")),
|
||||||
Ulke: strings.TrimSpace(r.URL.Query().Get("ulke")),
|
Ulke: strings.TrimSpace(r.URL.Query().Get("ulke")),
|
||||||
|
Il: strings.TrimSpace(r.URL.Query().Get("il")),
|
||||||
|
Ilce: strings.TrimSpace(r.URL.Query().Get("ilce")),
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := queries.GetCustomerBalanceList(r.Context(), params)
|
rows, err := queries.GetCustomerBalanceList(r.Context(), params)
|
||||||
|
|||||||
461
svc/routes/customer_balance_pdf.go
Normal file
461
svc/routes/customer_balance_pdf.go
Normal file
@@ -0,0 +1,461 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bssapp-backend/auth"
|
||||||
|
"bssapp-backend/models"
|
||||||
|
"bssapp-backend/queries"
|
||||||
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jung-kurt/gofpdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type balanceSummaryPDF struct {
|
||||||
|
AnaCariKodu string
|
||||||
|
AnaCariAdi string
|
||||||
|
Piyasa string
|
||||||
|
Temsilci string
|
||||||
|
RiskDurumu string
|
||||||
|
|
||||||
|
Bakiye12Map map[string]float64
|
||||||
|
Bakiye13Map map[string]float64
|
||||||
|
USDBakiye12 float64
|
||||||
|
TLBakiye12 float64
|
||||||
|
USDBakiye13 float64
|
||||||
|
TLBakiye13 float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExportCustomerBalancePDFHandler(_ *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||||||
|
if !ok || claims == nil {
|
||||||
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedDate := strings.TrimSpace(r.URL.Query().Get("selected_date"))
|
||||||
|
if selectedDate == "" {
|
||||||
|
selectedDate = time.Now().Format("2006-01-02")
|
||||||
|
}
|
||||||
|
|
||||||
|
params := models.CustomerBalanceListParams{
|
||||||
|
SelectedDate: selectedDate,
|
||||||
|
CariSearch: strings.TrimSpace(r.URL.Query().Get("cari_search")),
|
||||||
|
CariIlkGrup: strings.TrimSpace(r.URL.Query().Get("cari_ilk_grup")),
|
||||||
|
Piyasa: strings.TrimSpace(r.URL.Query().Get("piyasa")),
|
||||||
|
Temsilci: strings.TrimSpace(r.URL.Query().Get("temsilci")),
|
||||||
|
RiskDurumu: strings.TrimSpace(r.URL.Query().Get("risk_durumu")),
|
||||||
|
IslemTipi: strings.TrimSpace(r.URL.Query().Get("islem_tipi")),
|
||||||
|
Ulke: strings.TrimSpace(r.URL.Query().Get("ulke")),
|
||||||
|
Il: strings.TrimSpace(r.URL.Query().Get("il")),
|
||||||
|
Ilce: strings.TrimSpace(r.URL.Query().Get("ilce")),
|
||||||
|
}
|
||||||
|
|
||||||
|
detailed := parseBoolQuery(r.URL.Query().Get("detailed"))
|
||||||
|
excludeZero12 := parseBoolQuery(r.URL.Query().Get("exclude_zero_12"))
|
||||||
|
excludeZero13 := parseBoolQuery(r.URL.Query().Get("exclude_zero_13"))
|
||||||
|
|
||||||
|
rows, err := queries.GetCustomerBalanceList(r.Context(), params)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "db error: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rows = filterCustomerBalanceRowsForPDF(rows, excludeZero12, excludeZero13)
|
||||||
|
summaries, detailsByMaster := buildCustomerBalancePDFData(rows)
|
||||||
|
|
||||||
|
pdf := gofpdf.New("L", "mm", "A4", "")
|
||||||
|
pdf.SetMargins(8, 8, 8)
|
||||||
|
pdf.SetAutoPageBreak(false, 12)
|
||||||
|
if err := registerDejavuFonts(pdf, "dejavu"); err != nil {
|
||||||
|
http.Error(w, "pdf font error: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
drawCustomerBalancePDF(
|
||||||
|
pdf,
|
||||||
|
selectedDate,
|
||||||
|
params.CariSearch,
|
||||||
|
detailed,
|
||||||
|
summaries,
|
||||||
|
detailsByMaster,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := pdf.Error(); err != nil {
|
||||||
|
http.Error(w, "pdf render error: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := pdf.Output(&buf); err != nil {
|
||||||
|
http.Error(w, "pdf output error: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := "customer-balance-summary.pdf"
|
||||||
|
if detailed {
|
||||||
|
filename = "customer-balance-detailed.pdf"
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/pdf")
|
||||||
|
w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=%q", filename))
|
||||||
|
_, _ = w.Write(buf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseBoolQuery(v string) bool {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(v)) {
|
||||||
|
case "1", "true", "yes", "on":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterCustomerBalanceRowsForPDF(rows []models.CustomerBalanceListRow, excludeZero12, excludeZero13 bool) []models.CustomerBalanceListRow {
|
||||||
|
out := make([]models.CustomerBalanceListRow, 0, len(rows))
|
||||||
|
for _, row := range rows {
|
||||||
|
if excludeZero12 && row.Bakiye12 == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if excludeZero13 && row.Bakiye13 == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, row)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildCustomerBalancePDFData(rows []models.CustomerBalanceListRow) ([]balanceSummaryPDF, map[string][]models.CustomerBalanceListRow) {
|
||||||
|
summaryMap := make(map[string]*balanceSummaryPDF)
|
||||||
|
detailsByMaster := make(map[string][]models.CustomerBalanceListRow)
|
||||||
|
|
||||||
|
for _, row := range rows {
|
||||||
|
master := strings.TrimSpace(row.AnaCariKodu)
|
||||||
|
if master == "" {
|
||||||
|
master = strings.TrimSpace(row.CariKodu)
|
||||||
|
}
|
||||||
|
if master == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
s := summaryMap[master]
|
||||||
|
if s == nil {
|
||||||
|
s = &balanceSummaryPDF{
|
||||||
|
AnaCariKodu: master,
|
||||||
|
AnaCariAdi: strings.TrimSpace(row.AnaCariAdi),
|
||||||
|
Piyasa: strings.TrimSpace(row.Piyasa),
|
||||||
|
Temsilci: strings.TrimSpace(row.Temsilci),
|
||||||
|
RiskDurumu: strings.TrimSpace(row.RiskDurumu),
|
||||||
|
Bakiye12Map: map[string]float64{},
|
||||||
|
Bakiye13Map: map[string]float64{},
|
||||||
|
}
|
||||||
|
summaryMap[master] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.AnaCariAdi == "" && strings.TrimSpace(row.AnaCariAdi) != "" {
|
||||||
|
s.AnaCariAdi = strings.TrimSpace(row.AnaCariAdi)
|
||||||
|
}
|
||||||
|
if s.Piyasa == "" && strings.TrimSpace(row.Piyasa) != "" {
|
||||||
|
s.Piyasa = strings.TrimSpace(row.Piyasa)
|
||||||
|
}
|
||||||
|
if s.Temsilci == "" && strings.TrimSpace(row.Temsilci) != "" {
|
||||||
|
s.Temsilci = strings.TrimSpace(row.Temsilci)
|
||||||
|
}
|
||||||
|
if s.RiskDurumu == "" && strings.TrimSpace(row.RiskDurumu) != "" {
|
||||||
|
s.RiskDurumu = strings.TrimSpace(row.RiskDurumu)
|
||||||
|
}
|
||||||
|
|
||||||
|
curr := strings.ToUpper(strings.TrimSpace(row.CariDoviz))
|
||||||
|
if curr == "" {
|
||||||
|
curr = "N/A"
|
||||||
|
}
|
||||||
|
s.Bakiye12Map[curr] += row.Bakiye12
|
||||||
|
s.Bakiye13Map[curr] += row.Bakiye13
|
||||||
|
s.USDBakiye12 += row.USDBakiye12
|
||||||
|
s.TLBakiye12 += row.TLBakiye12
|
||||||
|
s.USDBakiye13 += row.USDBakiye13
|
||||||
|
s.TLBakiye13 += row.TLBakiye13
|
||||||
|
|
||||||
|
detailsByMaster[master] = append(detailsByMaster[master], row)
|
||||||
|
}
|
||||||
|
|
||||||
|
masters := make([]string, 0, len(summaryMap))
|
||||||
|
for m := range summaryMap {
|
||||||
|
masters = append(masters, m)
|
||||||
|
}
|
||||||
|
sort.Strings(masters)
|
||||||
|
|
||||||
|
summaries := make([]balanceSummaryPDF, 0, len(masters))
|
||||||
|
for _, m := range masters {
|
||||||
|
summaries = append(summaries, *summaryMap[m])
|
||||||
|
d := detailsByMaster[m]
|
||||||
|
sort.SliceStable(d, func(i, j int) bool {
|
||||||
|
if d[i].CariKodu == d[j].CariKodu {
|
||||||
|
if d[i].CariDoviz == d[j].CariDoviz {
|
||||||
|
si, _ := strconv.Atoi(d[i].Sirket)
|
||||||
|
sj, _ := strconv.Atoi(d[j].Sirket)
|
||||||
|
return si < sj
|
||||||
|
}
|
||||||
|
return d[i].CariDoviz < d[j].CariDoviz
|
||||||
|
}
|
||||||
|
return d[i].CariKodu < d[j].CariKodu
|
||||||
|
})
|
||||||
|
detailsByMaster[m] = d
|
||||||
|
}
|
||||||
|
|
||||||
|
return summaries, detailsByMaster
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawCustomerBalancePDF(
|
||||||
|
pdf *gofpdf.Fpdf,
|
||||||
|
selectedDate string,
|
||||||
|
searchText string,
|
||||||
|
detailed bool,
|
||||||
|
summaries []balanceSummaryPDF,
|
||||||
|
detailsByMaster map[string][]models.CustomerBalanceListRow,
|
||||||
|
) {
|
||||||
|
pageW, _ := pdf.GetPageSize()
|
||||||
|
marginL, marginT, marginR, marginB := 8.0, 8.0, 8.0, 12.0
|
||||||
|
tableW := pageW - marginL - marginR
|
||||||
|
|
||||||
|
summaryCols := []string{"Ana Cari Kod", "Ana Cari Detay", "Piyasa", "Temsilci", "Risk", "1_2 Pr.Br", "1_3 Pr.Br", "1_2 USD", "1_2 TRY", "1_3 USD", "1_3 TRY"}
|
||||||
|
summaryW := normalizeWidths([]float64{20, 43, 18, 18, 16, 27, 27, 15, 15, 15, 15}, tableW)
|
||||||
|
|
||||||
|
detailCols := []string{"Cari Kod", "Cari Detay", "Sirket", "Muhasebe", "Doviz", "1_2 Pr.Br", "1_3 Pr.Br", "1_2 USD", "1_2 TRY", "1_3 USD", "1_3 TRY"}
|
||||||
|
detailW := normalizeWidths([]float64{26, 46, 10, 20, 10, 24, 24, 15, 15, 15, 15}, tableW)
|
||||||
|
|
||||||
|
header := func() {
|
||||||
|
pdf.AddPage()
|
||||||
|
pdf.SetFont("dejavu", "B", 15)
|
||||||
|
pdf.SetTextColor(149, 113, 22)
|
||||||
|
pdf.SetXY(marginL, marginT)
|
||||||
|
pdf.CellFormat(120, 7, "Cari Bakiye Listesi", "", 0, "L", false, 0, "")
|
||||||
|
|
||||||
|
pdf.SetFont("dejavu", "", 9)
|
||||||
|
pdf.SetTextColor(20, 20, 20)
|
||||||
|
pdf.SetXY(pageW-marginR-80, marginT+1)
|
||||||
|
pdf.CellFormat(80, 5, "Tarih: "+selectedDate, "", 0, "R", false, 0, "")
|
||||||
|
|
||||||
|
mode := "Detaysiz"
|
||||||
|
if detailed {
|
||||||
|
mode = "Detayli"
|
||||||
|
}
|
||||||
|
pdf.SetXY(pageW-marginR-80, marginT+6)
|
||||||
|
pdf.CellFormat(80, 5, "Mod: "+mode, "", 0, "R", false, 0, "")
|
||||||
|
|
||||||
|
if strings.TrimSpace(searchText) != "" {
|
||||||
|
pdf.SetXY(marginL, marginT+8)
|
||||||
|
pdf.CellFormat(tableW, 5, "Arama: "+searchText, "", 0, "L", false, 0, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
pdf.SetDrawColor(149, 113, 22)
|
||||||
|
pdf.Line(marginL, marginT+14, pageW-marginR, marginT+14)
|
||||||
|
pdf.SetDrawColor(210, 210, 210)
|
||||||
|
pdf.SetY(marginT + 17)
|
||||||
|
}
|
||||||
|
|
||||||
|
needPage := func(needH float64) bool {
|
||||||
|
return pdf.GetY()+needH+marginB > 210.0
|
||||||
|
}
|
||||||
|
|
||||||
|
drawSummaryHeader := func() {
|
||||||
|
pdf.SetFont("dejavu", "B", 7.5)
|
||||||
|
pdf.SetFillColor(149, 113, 22)
|
||||||
|
pdf.SetTextColor(255, 255, 255)
|
||||||
|
y := pdf.GetY()
|
||||||
|
x := marginL
|
||||||
|
for i, c := range summaryCols {
|
||||||
|
pdf.Rect(x, y, summaryW[i], 7, "DF")
|
||||||
|
pdf.SetXY(x+1, y+1.2)
|
||||||
|
pdf.CellFormat(summaryW[i]-2, 4.6, c, "", 0, "C", false, 0, "")
|
||||||
|
x += summaryW[i]
|
||||||
|
}
|
||||||
|
pdf.SetY(y + 7)
|
||||||
|
}
|
||||||
|
|
||||||
|
drawDetailHeader := func() {
|
||||||
|
pdf.SetFont("dejavu", "B", 7.2)
|
||||||
|
pdf.SetFillColor(149, 113, 22)
|
||||||
|
pdf.SetTextColor(255, 255, 255)
|
||||||
|
y := pdf.GetY()
|
||||||
|
x := marginL
|
||||||
|
for i, c := range detailCols {
|
||||||
|
pdf.Rect(x, y, detailW[i], 6, "DF")
|
||||||
|
pdf.SetXY(x+1, y+1)
|
||||||
|
pdf.CellFormat(detailW[i]-2, 4, c, "", 0, "C", false, 0, "")
|
||||||
|
x += detailW[i]
|
||||||
|
}
|
||||||
|
pdf.SetY(y + 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
header()
|
||||||
|
drawSummaryHeader()
|
||||||
|
|
||||||
|
pdf.SetFont("dejavu", "", 7.2)
|
||||||
|
pdf.SetTextColor(20, 20, 20)
|
||||||
|
|
||||||
|
for _, s := range summaries {
|
||||||
|
if needPage(6.2) {
|
||||||
|
header()
|
||||||
|
drawSummaryHeader()
|
||||||
|
}
|
||||||
|
|
||||||
|
row := []string{
|
||||||
|
s.AnaCariKodu,
|
||||||
|
s.AnaCariAdi,
|
||||||
|
s.Piyasa,
|
||||||
|
s.Temsilci,
|
||||||
|
s.RiskDurumu,
|
||||||
|
formatCurrencyMapPDF(s.Bakiye12Map),
|
||||||
|
formatCurrencyMapPDF(s.Bakiye13Map),
|
||||||
|
formatMoneyPDF(s.USDBakiye12),
|
||||||
|
formatMoneyPDF(s.TLBakiye12),
|
||||||
|
formatMoneyPDF(s.USDBakiye13),
|
||||||
|
formatMoneyPDF(s.TLBakiye13),
|
||||||
|
}
|
||||||
|
|
||||||
|
y := pdf.GetY()
|
||||||
|
x := marginL
|
||||||
|
for i, v := range row {
|
||||||
|
pdf.Rect(x, y, summaryW[i], 6.2, "")
|
||||||
|
align := "L"
|
||||||
|
if i >= 7 {
|
||||||
|
align = "R"
|
||||||
|
}
|
||||||
|
pdf.SetXY(x+1, y+1)
|
||||||
|
pdf.CellFormat(summaryW[i]-2, 4.2, v, "", 0, align, false, 0, "")
|
||||||
|
x += summaryW[i]
|
||||||
|
}
|
||||||
|
pdf.SetY(y + 6.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !detailed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pdf.Ln(1.8)
|
||||||
|
for _, s := range summaries {
|
||||||
|
rows := detailsByMaster[s.AnaCariKodu]
|
||||||
|
if len(rows) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if needPage(12.4) {
|
||||||
|
header()
|
||||||
|
}
|
||||||
|
|
||||||
|
pdf.SetFont("dejavu", "B", 8)
|
||||||
|
pdf.SetFillColor(218, 193, 151)
|
||||||
|
pdf.SetTextColor(20, 20, 20)
|
||||||
|
y := pdf.GetY()
|
||||||
|
pdf.Rect(marginL, y, tableW, 6.2, "DF")
|
||||||
|
pdf.SetXY(marginL+1.5, y+1)
|
||||||
|
pdf.CellFormat(tableW-3, 4.2, "Detay: "+s.AnaCariKodu, "", 0, "L", false, 0, "")
|
||||||
|
pdf.SetY(y + 6.2)
|
||||||
|
drawDetailHeader()
|
||||||
|
|
||||||
|
pdf.SetFont("dejavu", "", 7)
|
||||||
|
pdf.SetTextColor(40, 40, 40)
|
||||||
|
|
||||||
|
for _, r := range rows {
|
||||||
|
if needPage(5.8) {
|
||||||
|
header()
|
||||||
|
pdf.SetFont("dejavu", "B", 8)
|
||||||
|
pdf.SetFillColor(218, 193, 151)
|
||||||
|
pdf.SetTextColor(20, 20, 20)
|
||||||
|
y := pdf.GetY()
|
||||||
|
pdf.Rect(marginL, y, tableW, 6.2, "DF")
|
||||||
|
pdf.SetXY(marginL+1.5, y+1)
|
||||||
|
pdf.CellFormat(tableW-3, 4.2, "Detay: "+s.AnaCariKodu, "", 0, "L", false, 0, "")
|
||||||
|
pdf.SetY(y + 6.2)
|
||||||
|
drawDetailHeader()
|
||||||
|
pdf.SetFont("dejavu", "", 7)
|
||||||
|
pdf.SetTextColor(40, 40, 40)
|
||||||
|
}
|
||||||
|
|
||||||
|
line := []string{
|
||||||
|
r.CariKodu,
|
||||||
|
r.CariDetay,
|
||||||
|
r.Sirket,
|
||||||
|
r.MuhasebeKodu,
|
||||||
|
r.CariDoviz,
|
||||||
|
formatMoneyPDF(r.Bakiye12),
|
||||||
|
formatMoneyPDF(r.Bakiye13),
|
||||||
|
formatMoneyPDF(r.USDBakiye12),
|
||||||
|
formatMoneyPDF(r.TLBakiye12),
|
||||||
|
formatMoneyPDF(r.USDBakiye13),
|
||||||
|
formatMoneyPDF(r.TLBakiye13),
|
||||||
|
}
|
||||||
|
|
||||||
|
rowY := pdf.GetY()
|
||||||
|
rowX := marginL
|
||||||
|
for i, v := range line {
|
||||||
|
pdf.Rect(rowX, rowY, detailW[i], 5.8, "")
|
||||||
|
align := "L"
|
||||||
|
if i >= 5 {
|
||||||
|
align = "R"
|
||||||
|
}
|
||||||
|
pdf.SetXY(rowX+1, rowY+0.8)
|
||||||
|
pdf.CellFormat(detailW[i]-2, 4.0, v, "", 0, align, false, 0, "")
|
||||||
|
rowX += detailW[i]
|
||||||
|
}
|
||||||
|
pdf.SetY(rowY + 5.8)
|
||||||
|
}
|
||||||
|
pdf.Ln(1.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatCurrencyMapPDF(m map[string]float64) string {
|
||||||
|
if len(m) == 0 {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
keys := make([]string, 0, len(m))
|
||||||
|
for k := range m {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
parts := make([]string, 0, len(keys))
|
||||||
|
for _, k := range keys {
|
||||||
|
if m[k] == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts = append(parts, k+": "+formatMoneyPDF(m[k]))
|
||||||
|
}
|
||||||
|
if len(parts) == 0 {
|
||||||
|
return "-"
|
||||||
|
}
|
||||||
|
return strings.Join(parts, " | ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatMoneyPDF(v float64) string {
|
||||||
|
s := fmt.Sprintf("%.2f", v)
|
||||||
|
parts := strings.SplitN(s, ".", 2)
|
||||||
|
intPart, decPart := parts[0], "00"
|
||||||
|
if len(parts) == 2 {
|
||||||
|
decPart = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
sign := ""
|
||||||
|
if strings.HasPrefix(intPart, "-") {
|
||||||
|
sign = "-"
|
||||||
|
intPart = strings.TrimPrefix(intPart, "-")
|
||||||
|
}
|
||||||
|
|
||||||
|
var out []string
|
||||||
|
for len(intPart) > 3 {
|
||||||
|
out = append([]string{intPart[len(intPart)-3:]}, out...)
|
||||||
|
intPart = intPart[:len(intPart)-3]
|
||||||
|
}
|
||||||
|
if intPart != "" {
|
||||||
|
out = append([]string{intPart}, out...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sign + strings.Join(out, ".") + "," + decPart
|
||||||
|
}
|
||||||
41
svc/routes/statement_aging.go
Normal file
41
svc/routes/statement_aging.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bssapp-backend/auth"
|
||||||
|
"bssapp-backend/models"
|
||||||
|
"bssapp-backend/queries"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GET /api/finance/account-aging-statement
|
||||||
|
func GetStatementAgingHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||||||
|
if !ok || claims == nil {
|
||||||
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
params := models.StatementAgingParams{
|
||||||
|
AccountCode: strings.TrimSpace(r.URL.Query().Get("accountcode")),
|
||||||
|
EndDate: strings.TrimSpace(r.URL.Query().Get("enddate")),
|
||||||
|
Parislemler: r.URL.Query()["parislemler"],
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.AccountCode == "" || params.EndDate == "" {
|
||||||
|
http.Error(w, "accountcode and enddate are required", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := queries.GetStatementAging(params)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error fetching aging statement: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
if err := json.NewEncoder(w).Encode(rows); err != nil {
|
||||||
|
http.Error(w, "Error encoding response: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
573
svc/routes/statement_aging_pdf.go
Normal file
573
svc/routes/statement_aging_pdf.go
Normal file
@@ -0,0 +1,573 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bssapp-backend/auth"
|
||||||
|
"bssapp-backend/models"
|
||||||
|
"bssapp-backend/queries"
|
||||||
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jung-kurt/gofpdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type agingDetailPDF struct {
|
||||||
|
FaturaCari string
|
||||||
|
OdemeCari string
|
||||||
|
Doviz string
|
||||||
|
FaturaRef string
|
||||||
|
OdemeRef string
|
||||||
|
FaturaTarihi string
|
||||||
|
OdemeTarihi string
|
||||||
|
OdemeDocDate string
|
||||||
|
EslesenTutar float64
|
||||||
|
UsdTutar float64
|
||||||
|
TryTutar float64
|
||||||
|
Aciklama string
|
||||||
|
Gun int
|
||||||
|
GunBelge int
|
||||||
|
GunKur float64
|
||||||
|
odemeDateParsed time.Time
|
||||||
|
odemeDateEmpty bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type agingCurrencyPDF struct {
|
||||||
|
Key string
|
||||||
|
Cari8 string
|
||||||
|
CariDetay string
|
||||||
|
Doviz string
|
||||||
|
AcikKalemTutar float64
|
||||||
|
AcikKalemUSD float64
|
||||||
|
AcikKalemTRY float64
|
||||||
|
OrtGun int
|
||||||
|
OrtBelgeGun int
|
||||||
|
weightedBase float64
|
||||||
|
weightedGunSum float64
|
||||||
|
weightedDocSum float64
|
||||||
|
Details []agingDetailPDF
|
||||||
|
}
|
||||||
|
|
||||||
|
type agingMasterPDF struct {
|
||||||
|
Key string
|
||||||
|
Cari8 string
|
||||||
|
CariDetay string
|
||||||
|
AcikKalemUSD float64
|
||||||
|
AcikKalemTRY float64
|
||||||
|
AcikKalemOrtVadeGun int
|
||||||
|
AcikKalemOrtBelge int
|
||||||
|
NormalUSD float64
|
||||||
|
NormalTRY float64
|
||||||
|
OrtalamaVadeGun int
|
||||||
|
OrtalamaBelgeGun int
|
||||||
|
weightedAllBase float64
|
||||||
|
weightedAllGunSum float64
|
||||||
|
weightedAllDocSum float64
|
||||||
|
weightedOpenBase float64
|
||||||
|
weightedOpenGunSum float64
|
||||||
|
weightedOpenDocSum float64
|
||||||
|
Currencies []agingCurrencyPDF
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExportStatementAgingPDFHandler(_ *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||||||
|
if !ok || claims == nil {
|
||||||
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
params := models.StatementAgingParams{
|
||||||
|
AccountCode: strings.TrimSpace(r.URL.Query().Get("accountcode")),
|
||||||
|
EndDate: strings.TrimSpace(r.URL.Query().Get("enddate")),
|
||||||
|
Parislemler: r.URL.Query()["parislemler"],
|
||||||
|
}
|
||||||
|
if params.AccountCode == "" || params.EndDate == "" {
|
||||||
|
http.Error(w, "accountcode and enddate are required", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := queries.GetStatementAging(params)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "db error: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
masters := buildAgingPDFData(rows)
|
||||||
|
|
||||||
|
pdf := gofpdf.New("L", "mm", "A4", "")
|
||||||
|
pdf.SetMargins(8, 8, 8)
|
||||||
|
pdf.SetAutoPageBreak(false, 10)
|
||||||
|
if err := registerDejavuFonts(pdf, "dejavu"); err != nil {
|
||||||
|
http.Error(w, "pdf font error: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
drawStatementAgingPDF(pdf, params, masters)
|
||||||
|
|
||||||
|
if err := pdf.Error(); err != nil {
|
||||||
|
http.Error(w, "pdf render error: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := pdf.Output(&buf); err != nil {
|
||||||
|
http.Error(w, "pdf output error: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/pdf")
|
||||||
|
w.Header().Set("Content-Disposition", `inline; filename="account-aging-detailed.pdf"`)
|
||||||
|
_, _ = w.Write(buf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildAgingPDFData(rows []map[string]interface{}) []agingMasterPDF {
|
||||||
|
masterMap := make(map[string]*agingMasterPDF)
|
||||||
|
currMap := make(map[string]*agingCurrencyPDF)
|
||||||
|
|
||||||
|
for _, row := range rows {
|
||||||
|
cari8 := strings.TrimSpace(readStrAny(row["Cari8"], row["cari8"]))
|
||||||
|
if cari8 == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cariDetay := strings.TrimSpace(readStrAny(row["CariDetay"], row["cari_detay"]))
|
||||||
|
doviz := strings.ToUpper(strings.TrimSpace(readStrAny(row["DocCurrencyCode"], row["doc_currency_code"])))
|
||||||
|
if doviz == "" {
|
||||||
|
doviz = "TRY"
|
||||||
|
}
|
||||||
|
aciklama := strings.ToUpper(strings.TrimSpace(readStrAny(row["Aciklama"], row["aciklama"])))
|
||||||
|
isAcik := aciklama == "ACIKKALEM"
|
||||||
|
|
||||||
|
eslesen := readFloatAny(row["EslesenTutar"], row["eslesen_tutar"])
|
||||||
|
usd := readFloatAny(row["UsdTutar"], row["usd_tutar"])
|
||||||
|
tryVal := readFloatAny(row["TryTutar"], row["try_tutar"])
|
||||||
|
gun := readIntAny(row["GunSayisi"], row["gun_sayisi"])
|
||||||
|
gunBelge := readIntAny(row["GunSayisi_DocDate"], row["gun_sayisi_docdate"])
|
||||||
|
absTry := math.Abs(tryVal)
|
||||||
|
|
||||||
|
m := masterMap[cari8]
|
||||||
|
if m == nil {
|
||||||
|
m = &agingMasterPDF{Key: cari8, Cari8: cari8, CariDetay: cariDetay}
|
||||||
|
masterMap[cari8] = m
|
||||||
|
}
|
||||||
|
if m.CariDetay == "" && cariDetay != "" {
|
||||||
|
m.CariDetay = cariDetay
|
||||||
|
}
|
||||||
|
|
||||||
|
ckey := cari8 + "|" + doviz
|
||||||
|
c := currMap[ckey]
|
||||||
|
if c == nil {
|
||||||
|
c = &agingCurrencyPDF{
|
||||||
|
Key: ckey,
|
||||||
|
Cari8: cari8,
|
||||||
|
CariDetay: cariDetay,
|
||||||
|
Doviz: doviz,
|
||||||
|
Details: make([]agingDetailPDF, 0, 128),
|
||||||
|
}
|
||||||
|
currMap[ckey] = c
|
||||||
|
}
|
||||||
|
if c.CariDetay == "" && cariDetay != "" {
|
||||||
|
c.CariDetay = cariDetay
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAcik {
|
||||||
|
m.AcikKalemUSD += usd
|
||||||
|
m.AcikKalemTRY += tryVal
|
||||||
|
c.AcikKalemTutar += eslesen
|
||||||
|
c.AcikKalemUSD += usd
|
||||||
|
c.AcikKalemTRY += tryVal
|
||||||
|
} else {
|
||||||
|
m.NormalUSD += usd
|
||||||
|
m.NormalTRY += tryVal
|
||||||
|
}
|
||||||
|
|
||||||
|
if absTry > 0 {
|
||||||
|
m.weightedAllBase += absTry
|
||||||
|
m.weightedAllGunSum += absTry * float64(gun)
|
||||||
|
m.weightedAllDocSum += absTry * float64(gunBelge)
|
||||||
|
|
||||||
|
if isAcik {
|
||||||
|
m.weightedOpenBase += absTry
|
||||||
|
m.weightedOpenGunSum += absTry * float64(gun)
|
||||||
|
m.weightedOpenDocSum += absTry * float64(gunBelge)
|
||||||
|
|
||||||
|
c.weightedBase += absTry
|
||||||
|
c.weightedGunSum += absTry * float64(gun)
|
||||||
|
c.weightedDocSum += absTry * float64(gunBelge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
odemeTar := readStrAny(row["OdemeTarihi"], row["odeme_tarihi"])
|
||||||
|
odemeParsed, ok := parseYMD(odemeTar)
|
||||||
|
detail := agingDetailPDF{
|
||||||
|
FaturaCari: readStrAny(row["FaturaCari"], row["fatura_cari"]),
|
||||||
|
OdemeCari: readStrAny(row["OdemeCari"], row["odeme_cari"]),
|
||||||
|
Doviz: doviz,
|
||||||
|
FaturaRef: readStrAny(row["FaturaRef"], row["fatura_ref"]),
|
||||||
|
OdemeRef: readStrAny(row["OdemeRef"], row["odeme_ref"]),
|
||||||
|
FaturaTarihi: readStrAny(row["FaturaTarihi"], row["fatura_tarihi"]),
|
||||||
|
OdemeTarihi: odemeTar,
|
||||||
|
OdemeDocDate: readStrAny(row["OdemeDocDate"], row["odeme_doc_date"]),
|
||||||
|
EslesenTutar: eslesen,
|
||||||
|
UsdTutar: usd,
|
||||||
|
TryTutar: tryVal,
|
||||||
|
Aciklama: readStrAny(row["Aciklama"], row["aciklama"]),
|
||||||
|
Gun: gun,
|
||||||
|
GunBelge: gunBelge,
|
||||||
|
GunKur: readFloatAny(row["GunKur"], row["gun_kur"]),
|
||||||
|
odemeDateParsed: odemeParsed,
|
||||||
|
odemeDateEmpty: !ok,
|
||||||
|
}
|
||||||
|
c.Details = append(c.Details, detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
masters := make([]agingMasterPDF, 0, len(masterMap))
|
||||||
|
for _, m := range masterMap {
|
||||||
|
if m.weightedOpenBase > 0 {
|
||||||
|
m.AcikKalemOrtVadeGun = int(math.Ceil(m.weightedOpenGunSum / m.weightedOpenBase))
|
||||||
|
m.AcikKalemOrtBelge = int(math.Ceil(m.weightedOpenDocSum / m.weightedOpenBase))
|
||||||
|
}
|
||||||
|
if m.weightedAllBase > 0 {
|
||||||
|
m.OrtalamaVadeGun = int(math.Ceil(m.weightedAllGunSum / m.weightedAllBase))
|
||||||
|
m.OrtalamaBelgeGun = int(math.Ceil(m.weightedAllDocSum / m.weightedAllBase))
|
||||||
|
}
|
||||||
|
|
||||||
|
currs := make([]agingCurrencyPDF, 0, len(currMap))
|
||||||
|
for _, c := range currMap {
|
||||||
|
if c.Cari8 != m.Cari8 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c.weightedBase > 0 {
|
||||||
|
c.OrtGun = int(math.Ceil(c.weightedGunSum / c.weightedBase))
|
||||||
|
c.OrtBelgeGun = int(math.Ceil(c.weightedDocSum / c.weightedBase))
|
||||||
|
}
|
||||||
|
sort.SliceStable(c.Details, func(i, j int) bool {
|
||||||
|
ai := c.Details[i]
|
||||||
|
aj := c.Details[j]
|
||||||
|
if ai.odemeDateEmpty && !aj.odemeDateEmpty {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !ai.odemeDateEmpty && aj.odemeDateEmpty {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if ai.odemeDateEmpty && aj.odemeDateEmpty {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return ai.odemeDateParsed.After(aj.odemeDateParsed)
|
||||||
|
})
|
||||||
|
currs = append(currs, *c)
|
||||||
|
}
|
||||||
|
sort.SliceStable(currs, func(i, j int) bool { return currs[i].Doviz < currs[j].Doviz })
|
||||||
|
m.Currencies = currs
|
||||||
|
masters = append(masters, *m)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.SliceStable(masters, func(i, j int) bool { return masters[i].Cari8 < masters[j].Cari8 })
|
||||||
|
return masters
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawStatementAgingPDF(pdf *gofpdf.Fpdf, p models.StatementAgingParams, masters []agingMasterPDF) {
|
||||||
|
pageW, pageH := pdf.GetPageSize()
|
||||||
|
marginL, marginR, marginT, marginB := 8.0, 8.0, 8.0, 10.0
|
||||||
|
tableW := pageW - marginL - marginR
|
||||||
|
|
||||||
|
colorPrimary := [3]int{149, 113, 22}
|
||||||
|
colorLevel2 := [3]int{76, 95, 122}
|
||||||
|
colorLevel3 := [3]int{31, 59, 91}
|
||||||
|
|
||||||
|
level1Cols := []string{"Ana Cari Kod", "Ana Cari Detay", "Açık Kalem USD", "Açık Kalem TRY", "Açık Kalem Ort Vade", "Açık Kalem Ort Belge", "Normal USD", "Normal TRY", "Ortalama Vade", "Ortalama Belge"}
|
||||||
|
level1W := normalizeWidths([]float64{20, 46, 18, 18, 15, 15, 16, 16, 15, 15}, tableW)
|
||||||
|
|
||||||
|
level2Cols := []string{"Ana Cari Kod", "Ana Cari Detay", "Döviz", "Açık Kalem", "Açık Kalem USD", "Açık Kalem TRY", "Ort Gün", "Ort Belge Gün"}
|
||||||
|
level2W := normalizeWidths([]float64{20, 52, 12, 24, 24, 24, 16, 18}, tableW)
|
||||||
|
|
||||||
|
level3Cols := []string{"Fatura Cari", "Ödeme Cari", "Döviz", "Fatura Ref", "Ödeme Ref", "Fatura Tarihi", "Ödeme Vade", "Ödeme DocDate", "Eşleşen", "USD", "TRY", "Açıklama", "Gün", "Gün Belge", "Gün Kur"}
|
||||||
|
level3W := normalizeWidths([]float64{15, 15, 10, 15, 15, 13, 13, 13, 14, 12, 14, 14, 8, 10, 10}, tableW)
|
||||||
|
|
||||||
|
pageHeader := func() {
|
||||||
|
pdf.AddPage()
|
||||||
|
pdf.SetFont("dejavu", "B", 15)
|
||||||
|
pdf.SetTextColor(colorPrimary[0], colorPrimary[1], colorPrimary[2])
|
||||||
|
pdf.SetXY(marginL, marginT)
|
||||||
|
pdf.CellFormat(150, 7, "Cari Yaşlandırmalı Ekstre", "", 0, "L", false, 0, "")
|
||||||
|
|
||||||
|
pdf.SetFont("dejavu", "", 9)
|
||||||
|
pdf.SetTextColor(20, 20, 20)
|
||||||
|
pdf.SetXY(pageW-marginR-95, marginT+1)
|
||||||
|
pdf.CellFormat(95, 5, "Son Tarih: "+p.EndDate, "", 0, "R", false, 0, "")
|
||||||
|
pdf.SetXY(pageW-marginR-95, marginT+6)
|
||||||
|
pdf.CellFormat(95, 5, "Cari: "+p.AccountCode, "", 0, "R", false, 0, "")
|
||||||
|
|
||||||
|
mode := "1_2"
|
||||||
|
if len(p.Parislemler) > 0 {
|
||||||
|
mode = strings.Join(p.Parislemler, ",")
|
||||||
|
}
|
||||||
|
pdf.SetXY(pageW-marginR-95, marginT+11)
|
||||||
|
pdf.CellFormat(95, 5, "Parasal İşlem: "+mode, "", 0, "R", false, 0, "")
|
||||||
|
|
||||||
|
pdf.SetDrawColor(colorPrimary[0], colorPrimary[1], colorPrimary[2])
|
||||||
|
pdf.Line(marginL, marginT+16, pageW-marginR, marginT+16)
|
||||||
|
pdf.SetDrawColor(210, 210, 210)
|
||||||
|
pdf.SetY(marginT + 19)
|
||||||
|
}
|
||||||
|
|
||||||
|
needPage := func(need float64) bool {
|
||||||
|
return pdf.GetY()+need+marginB > pageH
|
||||||
|
}
|
||||||
|
|
||||||
|
drawHeaderRow := func(cols []string, widths []float64, rgb [3]int, h float64) {
|
||||||
|
pdf.SetFont("dejavu", "B", 8.4)
|
||||||
|
pdf.SetFillColor(rgb[0], rgb[1], rgb[2])
|
||||||
|
pdf.SetTextColor(255, 255, 255)
|
||||||
|
y := pdf.GetY()
|
||||||
|
x := marginL
|
||||||
|
for i, c := range cols {
|
||||||
|
pdf.Rect(x, y, widths[i], h, "DF")
|
||||||
|
pdf.SetXY(x+0.8, y+1.0)
|
||||||
|
pdf.CellFormat(widths[i]-1.6, h-2.0, c, "", 0, "C", false, 0, "")
|
||||||
|
x += widths[i]
|
||||||
|
}
|
||||||
|
pdf.SetY(y + h)
|
||||||
|
}
|
||||||
|
|
||||||
|
drawRow := func(vals []string, widths []float64, h float64, fill bool, fillRGB [3]int, centerCols map[int]bool, rightCols map[int]bool) {
|
||||||
|
pdf.SetFont("dejavu", "", 7.6)
|
||||||
|
pdf.SetTextColor(20, 20, 20)
|
||||||
|
y := pdf.GetY()
|
||||||
|
x := marginL
|
||||||
|
for i, v := range vals {
|
||||||
|
if fill {
|
||||||
|
pdf.SetFillColor(fillRGB[0], fillRGB[1], fillRGB[2])
|
||||||
|
pdf.Rect(x, y, widths[i], h, "DF")
|
||||||
|
} else {
|
||||||
|
pdf.Rect(x, y, widths[i], h, "")
|
||||||
|
}
|
||||||
|
align := "L"
|
||||||
|
if rightCols[i] {
|
||||||
|
align = "R"
|
||||||
|
} else if centerCols[i] {
|
||||||
|
align = "C"
|
||||||
|
}
|
||||||
|
pdf.SetXY(x+0.8, y+0.8)
|
||||||
|
pdf.CellFormat(widths[i]-1.6, h-1.6, v, "", 0, align, false, 0, "")
|
||||||
|
x += widths[i]
|
||||||
|
}
|
||||||
|
pdf.SetY(y + h)
|
||||||
|
}
|
||||||
|
|
||||||
|
format2 := func(v float64) string {
|
||||||
|
return trFormat(v, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
pageHeader()
|
||||||
|
for _, m := range masters {
|
||||||
|
if needPage(7 + 7 + 6) {
|
||||||
|
pageHeader()
|
||||||
|
}
|
||||||
|
|
||||||
|
drawHeaderRow(level1Cols, level1W, colorPrimary, 7)
|
||||||
|
drawRow(
|
||||||
|
[]string{
|
||||||
|
m.Cari8, m.CariDetay,
|
||||||
|
format2(m.AcikKalemUSD), format2(m.AcikKalemTRY),
|
||||||
|
fmt.Sprintf("%d", m.AcikKalemOrtVadeGun), fmt.Sprintf("%d", m.AcikKalemOrtBelge),
|
||||||
|
format2(m.NormalUSD), format2(m.NormalTRY),
|
||||||
|
fmt.Sprintf("%d", m.OrtalamaVadeGun), fmt.Sprintf("%d", m.OrtalamaBelgeGun),
|
||||||
|
},
|
||||||
|
level1W, 6.4, true, [3]int{250, 246, 238},
|
||||||
|
map[int]bool{0: true, 4: true, 5: true, 8: true, 9: true},
|
||||||
|
map[int]bool{2: true, 3: true, 6: true, 7: true},
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, c := range m.Currencies {
|
||||||
|
if needPage(6 + 6 + 6) {
|
||||||
|
pageHeader()
|
||||||
|
drawHeaderRow(level1Cols, level1W, colorPrimary, 7)
|
||||||
|
drawRow(
|
||||||
|
[]string{
|
||||||
|
m.Cari8, m.CariDetay,
|
||||||
|
format2(m.AcikKalemUSD), format2(m.AcikKalemTRY),
|
||||||
|
fmt.Sprintf("%d", m.AcikKalemOrtVadeGun), fmt.Sprintf("%d", m.AcikKalemOrtBelge),
|
||||||
|
format2(m.NormalUSD), format2(m.NormalTRY),
|
||||||
|
fmt.Sprintf("%d", m.OrtalamaVadeGun), fmt.Sprintf("%d", m.OrtalamaBelgeGun),
|
||||||
|
},
|
||||||
|
level1W, 6.4, true, [3]int{250, 246, 238},
|
||||||
|
map[int]bool{0: true, 4: true, 5: true, 8: true, 9: true},
|
||||||
|
map[int]bool{2: true, 3: true, 6: true, 7: true},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
drawHeaderRow(level2Cols, level2W, colorLevel2, 6)
|
||||||
|
drawRow(
|
||||||
|
[]string{
|
||||||
|
c.Cari8, c.CariDetay, c.Doviz,
|
||||||
|
format2(c.AcikKalemTutar), format2(c.AcikKalemUSD), format2(c.AcikKalemTRY),
|
||||||
|
fmt.Sprintf("%d", c.OrtGun), fmt.Sprintf("%d", c.OrtBelgeGun),
|
||||||
|
},
|
||||||
|
level2W, 5.8, true, [3]int{236, 240, 247},
|
||||||
|
map[int]bool{0: true, 2: true, 6: true, 7: true},
|
||||||
|
map[int]bool{3: true, 4: true, 5: true},
|
||||||
|
)
|
||||||
|
|
||||||
|
if needPage(5.8) {
|
||||||
|
pageHeader()
|
||||||
|
}
|
||||||
|
drawHeaderRow(level3Cols, level3W, colorLevel3, 5.8)
|
||||||
|
|
||||||
|
for _, d := range c.Details {
|
||||||
|
if needPage(5.2) {
|
||||||
|
pageHeader()
|
||||||
|
drawHeaderRow(level3Cols, level3W, colorLevel3, 5.8)
|
||||||
|
}
|
||||||
|
drawRow(
|
||||||
|
[]string{
|
||||||
|
d.FaturaCari, d.OdemeCari, d.Doviz, d.FaturaRef, d.OdemeRef,
|
||||||
|
d.FaturaTarihi, d.OdemeTarihi, d.OdemeDocDate,
|
||||||
|
format2(d.EslesenTutar), format2(d.UsdTutar), format2(d.TryTutar), d.Aciklama,
|
||||||
|
fmt.Sprintf("%d", d.Gun), fmt.Sprintf("%d", d.GunBelge), trFormat(d.GunKur, 2),
|
||||||
|
},
|
||||||
|
level3W, 5.2, false, [3]int{},
|
||||||
|
map[int]bool{2: true, 5: true, 6: true, 7: true, 11: true, 12: true, 13: true, 14: true},
|
||||||
|
map[int]bool{8: true, 9: true, 10: true},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pdf.Ln(1.2)
|
||||||
|
}
|
||||||
|
pdf.Ln(1.8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func trFormat(v float64, frac int) string {
|
||||||
|
neg := v < 0
|
||||||
|
if neg {
|
||||||
|
v = -v
|
||||||
|
}
|
||||||
|
pow := math.Pow(10, float64(frac))
|
||||||
|
rounded := math.Round(v*pow) / pow
|
||||||
|
intPart := int64(rounded)
|
||||||
|
decPart := int64(math.Round((rounded - float64(intPart)) * pow))
|
||||||
|
|
||||||
|
intStr := fmt.Sprintf("%d", intPart)
|
||||||
|
var grouped strings.Builder
|
||||||
|
for i, r := range intStr {
|
||||||
|
if i > 0 && (len(intStr)-i)%3 == 0 {
|
||||||
|
grouped.WriteString(".")
|
||||||
|
}
|
||||||
|
grouped.WriteRune(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := grouped.String()
|
||||||
|
if frac > 0 {
|
||||||
|
decFmt := fmt.Sprintf("%%0%dd", frac)
|
||||||
|
out += "," + fmt.Sprintf(decFmt, decPart)
|
||||||
|
}
|
||||||
|
if neg {
|
||||||
|
return "-" + out
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func readStrAny(v ...interface{}) string {
|
||||||
|
for _, x := range v {
|
||||||
|
switch t := x.(type) {
|
||||||
|
case nil:
|
||||||
|
case string:
|
||||||
|
if strings.TrimSpace(t) != "" {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
case []byte:
|
||||||
|
s := strings.TrimSpace(string(t))
|
||||||
|
if s != "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
case time.Time:
|
||||||
|
return t.Format("2006-01-02")
|
||||||
|
default:
|
||||||
|
s := strings.TrimSpace(fmt.Sprint(t))
|
||||||
|
if s != "" && s != "<nil>" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFloatAny(v ...interface{}) float64 {
|
||||||
|
for _, x := range v {
|
||||||
|
if x == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch t := x.(type) {
|
||||||
|
case float64:
|
||||||
|
return t
|
||||||
|
case float32:
|
||||||
|
return float64(t)
|
||||||
|
case int:
|
||||||
|
return float64(t)
|
||||||
|
case int32:
|
||||||
|
return float64(t)
|
||||||
|
case int64:
|
||||||
|
return float64(t)
|
||||||
|
case string:
|
||||||
|
if n, ok := parseNumberFlexible(t); ok {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
case []byte:
|
||||||
|
if n, ok := parseNumberFlexible(string(t)); ok {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if n, ok := parseNumberFlexible(fmt.Sprint(t)); ok {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func readIntAny(v ...interface{}) int {
|
||||||
|
return int(math.Ceil(readFloatAny(v...)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseNumberFlexible(s string) (float64, bool) {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
if s == "" {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
hasComma := strings.Contains(s, ",")
|
||||||
|
hasDot := strings.Contains(s, ".")
|
||||||
|
if hasComma && hasDot {
|
||||||
|
if strings.LastIndex(s, ",") > strings.LastIndex(s, ".") {
|
||||||
|
s = strings.ReplaceAll(s, ".", "")
|
||||||
|
s = strings.Replace(s, ",", ".", 1)
|
||||||
|
} else {
|
||||||
|
s = strings.ReplaceAll(s, ",", "")
|
||||||
|
}
|
||||||
|
} else if hasComma {
|
||||||
|
s = strings.ReplaceAll(s, ".", "")
|
||||||
|
s = strings.Replace(s, ",", ".", 1)
|
||||||
|
}
|
||||||
|
n, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseYMD(v string) (time.Time, bool) {
|
||||||
|
v = strings.TrimSpace(v)
|
||||||
|
if v == "" {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
t, err := time.Parse("2006-01-02", v)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
return t, true
|
||||||
|
}
|
||||||
75
ui/.quasar/prod-spa/app.js
Normal file
75
ui/.quasar/prod-spa/app.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* THIS FILE IS GENERATED AUTOMATICALLY.
|
||||||
|
* DO NOT EDIT.
|
||||||
|
*
|
||||||
|
* You are probably looking on adding startup/initialization code.
|
||||||
|
* Use "quasar new boot <name>" and add it there.
|
||||||
|
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
||||||
|
* boot: ['file', ...] // do not add ".js" extension to it.
|
||||||
|
*
|
||||||
|
* Boot files are your "main.js"
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import { Quasar } from 'quasar'
|
||||||
|
import { markRaw } from 'vue'
|
||||||
|
import RootComponent from 'app/src/App.vue'
|
||||||
|
|
||||||
|
import createStore from 'app/src/stores/index'
|
||||||
|
import createRouter from 'app/src/router/index'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default async function (createAppFn, quasarUserOptions) {
|
||||||
|
|
||||||
|
|
||||||
|
// Create the app instance.
|
||||||
|
// Here we inject into it the Quasar UI, the router & possibly the store.
|
||||||
|
const app = createAppFn(RootComponent)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app.use(Quasar, quasarUserOptions)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const store = typeof createStore === 'function'
|
||||||
|
? await createStore({})
|
||||||
|
: createStore
|
||||||
|
|
||||||
|
|
||||||
|
app.use(store)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const router = markRaw(
|
||||||
|
typeof createRouter === 'function'
|
||||||
|
? await createRouter({store})
|
||||||
|
: createRouter
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
// make router instance available in store
|
||||||
|
|
||||||
|
store.use(({ store }) => { store.router = router })
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Expose the app, the router and the store.
|
||||||
|
// Note that we are not mounting the app here, since bootstrapping will be
|
||||||
|
// different depending on whether we are in a browser or on the server.
|
||||||
|
return {
|
||||||
|
app,
|
||||||
|
store,
|
||||||
|
router
|
||||||
|
}
|
||||||
|
}
|
||||||
154
ui/.quasar/prod-spa/client-entry.js
Normal file
154
ui/.quasar/prod-spa/client-entry.js
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* THIS FILE IS GENERATED AUTOMATICALLY.
|
||||||
|
* DO NOT EDIT.
|
||||||
|
*
|
||||||
|
* You are probably looking on adding startup/initialization code.
|
||||||
|
* Use "quasar new boot <name>" and add it there.
|
||||||
|
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
||||||
|
* boot: ['file', ...] // do not add ".js" extension to it.
|
||||||
|
*
|
||||||
|
* Boot files are your "main.js"
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import '@quasar/extras/roboto-font/roboto-font.css'
|
||||||
|
|
||||||
|
import '@quasar/extras/material-icons/material-icons.css'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// We load Quasar stylesheet file
|
||||||
|
import 'quasar/dist/quasar.sass'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import 'src/css/app.css'
|
||||||
|
|
||||||
|
|
||||||
|
import createQuasarApp from './app.js'
|
||||||
|
import quasarUserOptions from './quasar-user-options.js'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const publicPath = `/`
|
||||||
|
|
||||||
|
|
||||||
|
async function start ({
|
||||||
|
app,
|
||||||
|
router
|
||||||
|
, store
|
||||||
|
}, bootFiles) {
|
||||||
|
|
||||||
|
let hasRedirected = false
|
||||||
|
const getRedirectUrl = url => {
|
||||||
|
try { return router.resolve(url).href }
|
||||||
|
catch (err) {}
|
||||||
|
|
||||||
|
return Object(url) === url
|
||||||
|
? null
|
||||||
|
: url
|
||||||
|
}
|
||||||
|
const redirect = url => {
|
||||||
|
hasRedirected = true
|
||||||
|
|
||||||
|
if (typeof url === 'string' && /^https?:\/\//.test(url)) {
|
||||||
|
window.location.href = url
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const href = getRedirectUrl(url)
|
||||||
|
|
||||||
|
// continue if we didn't fail to resolve the url
|
||||||
|
if (href !== null) {
|
||||||
|
window.location.href = href
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlPath = window.location.href.replace(window.location.origin, '')
|
||||||
|
|
||||||
|
for (let i = 0; hasRedirected === false && i < bootFiles.length; i++) {
|
||||||
|
try {
|
||||||
|
await bootFiles[i]({
|
||||||
|
app,
|
||||||
|
router,
|
||||||
|
store,
|
||||||
|
ssrContext: null,
|
||||||
|
redirect,
|
||||||
|
urlPath,
|
||||||
|
publicPath
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
if (err && err.url) {
|
||||||
|
redirect(err.url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error('[Quasar] boot error:', err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasRedirected === true) return
|
||||||
|
|
||||||
|
|
||||||
|
app.use(router)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app.mount('#q-app')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
createQuasarApp(createApp, quasarUserOptions)
|
||||||
|
|
||||||
|
.then(app => {
|
||||||
|
// eventually remove this when Cordova/Capacitor/Electron support becomes old
|
||||||
|
const [ method, mapFn ] = Promise.allSettled !== void 0
|
||||||
|
? [
|
||||||
|
'allSettled',
|
||||||
|
bootFiles => bootFiles.map(result => {
|
||||||
|
if (result.status === 'rejected') {
|
||||||
|
console.error('[Quasar] boot error:', result.reason)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return result.value.default
|
||||||
|
})
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
'all',
|
||||||
|
bootFiles => bootFiles.map(entry => entry.default)
|
||||||
|
]
|
||||||
|
|
||||||
|
return Promise[ method ]([
|
||||||
|
|
||||||
|
import(/* webpackMode: "eager" */ 'boot/dayjs')
|
||||||
|
|
||||||
|
]).then(bootFiles => {
|
||||||
|
const boot = mapFn(bootFiles).filter(entry => typeof entry === 'function')
|
||||||
|
start(app, boot)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
116
ui/.quasar/prod-spa/client-prefetch.js
Normal file
116
ui/.quasar/prod-spa/client-prefetch.js
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* THIS FILE IS GENERATED AUTOMATICALLY.
|
||||||
|
* DO NOT EDIT.
|
||||||
|
*
|
||||||
|
* You are probably looking on adding startup/initialization code.
|
||||||
|
* Use "quasar new boot <name>" and add it there.
|
||||||
|
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
||||||
|
* boot: ['file', ...] // do not add ".js" extension to it.
|
||||||
|
*
|
||||||
|
* Boot files are your "main.js"
|
||||||
|
**/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import App from 'app/src/App.vue'
|
||||||
|
let appPrefetch = typeof App.preFetch === 'function'
|
||||||
|
? App.preFetch
|
||||||
|
: (
|
||||||
|
// Class components return the component options (and the preFetch hook) inside __c property
|
||||||
|
App.__c !== void 0 && typeof App.__c.preFetch === 'function'
|
||||||
|
? App.__c.preFetch
|
||||||
|
: false
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
function getMatchedComponents (to, router) {
|
||||||
|
const route = to
|
||||||
|
? (to.matched ? to : router.resolve(to).route)
|
||||||
|
: router.currentRoute.value
|
||||||
|
|
||||||
|
if (!route) { return [] }
|
||||||
|
|
||||||
|
const matched = route.matched.filter(m => m.components !== void 0)
|
||||||
|
|
||||||
|
if (matched.length === 0) { return [] }
|
||||||
|
|
||||||
|
return Array.prototype.concat.apply([], matched.map(m => {
|
||||||
|
return Object.keys(m.components).map(key => {
|
||||||
|
const comp = m.components[key]
|
||||||
|
return {
|
||||||
|
path: m.path,
|
||||||
|
c: comp
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addPreFetchHooks ({ router, store, publicPath }) {
|
||||||
|
// Add router hook for handling preFetch.
|
||||||
|
// Doing it after initial route is resolved so that we don't double-fetch
|
||||||
|
// the data that we already have. Using router.beforeResolve() so that all
|
||||||
|
// async components are resolved.
|
||||||
|
router.beforeResolve((to, from, next) => {
|
||||||
|
const
|
||||||
|
urlPath = window.location.href.replace(window.location.origin, ''),
|
||||||
|
matched = getMatchedComponents(to, router),
|
||||||
|
prevMatched = getMatchedComponents(from, router)
|
||||||
|
|
||||||
|
let diffed = false
|
||||||
|
const preFetchList = matched
|
||||||
|
.filter((m, i) => {
|
||||||
|
return diffed || (diffed = (
|
||||||
|
!prevMatched[i] ||
|
||||||
|
prevMatched[i].c !== m.c ||
|
||||||
|
m.path.indexOf('/:') > -1 // does it has params?
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.filter(m => m.c !== void 0 && (
|
||||||
|
typeof m.c.preFetch === 'function'
|
||||||
|
// Class components return the component options (and the preFetch hook) inside __c property
|
||||||
|
|| (m.c.__c !== void 0 && typeof m.c.__c.preFetch === 'function')
|
||||||
|
))
|
||||||
|
.map(m => m.c.__c !== void 0 ? m.c.__c.preFetch : m.c.preFetch)
|
||||||
|
|
||||||
|
|
||||||
|
if (appPrefetch !== false) {
|
||||||
|
preFetchList.unshift(appPrefetch)
|
||||||
|
appPrefetch = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (preFetchList.length === 0) {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
let hasRedirected = false
|
||||||
|
const redirect = url => {
|
||||||
|
hasRedirected = true
|
||||||
|
next(url)
|
||||||
|
}
|
||||||
|
const proceed = () => {
|
||||||
|
|
||||||
|
if (hasRedirected === false) { next() }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
preFetchList.reduce(
|
||||||
|
(promise, preFetch) => promise.then(() => hasRedirected === false && preFetch({
|
||||||
|
store,
|
||||||
|
currentRoute: to,
|
||||||
|
previousRoute: from,
|
||||||
|
redirect,
|
||||||
|
urlPath,
|
||||||
|
publicPath
|
||||||
|
})),
|
||||||
|
Promise.resolve()
|
||||||
|
)
|
||||||
|
.then(proceed)
|
||||||
|
.catch(e => {
|
||||||
|
console.error(e)
|
||||||
|
proceed()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
23
ui/.quasar/prod-spa/quasar-user-options.js
Normal file
23
ui/.quasar/prod-spa/quasar-user-options.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/**
|
||||||
|
* THIS FILE IS GENERATED AUTOMATICALLY.
|
||||||
|
* DO NOT EDIT.
|
||||||
|
*
|
||||||
|
* You are probably looking on adding startup/initialization code.
|
||||||
|
* Use "quasar new boot <name>" and add it there.
|
||||||
|
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
||||||
|
* boot: ['file', ...] // do not add ".js" extension to it.
|
||||||
|
*
|
||||||
|
* Boot files are your "main.js"
|
||||||
|
**/
|
||||||
|
|
||||||
|
import lang from 'quasar/lang/tr.js'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import {Loading,Dialog,Notify} from 'quasar'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default { config: {"notify":{"position":"top","timeout":2500}},lang,plugins: {Loading,Dialog,Notify} }
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-layout view="hHh Lpr fFf">
|
<q-layout view="hHh Lpr fFf">
|
||||||
|
|
||||||
<!-- HEADER -->
|
<!-- HEADER -->
|
||||||
@@ -200,6 +200,16 @@ const menuItems = [
|
|||||||
label: 'Cari Bakiye Listesi',
|
label: 'Cari Bakiye Listesi',
|
||||||
to: '/app/customer-balance-list',
|
to: '/app/customer-balance-list',
|
||||||
permission: 'finance:view'
|
permission: 'finance:view'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Cari Yaşlandırmalı Ekstre',
|
||||||
|
to: '/app/account-aging-statement',
|
||||||
|
permission: 'finance:view'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Cari Yaşlandırmalı Cari Bakiye Listesi',
|
||||||
|
to: '/app/aged-customer-balance-list',
|
||||||
|
permission: 'finance:view'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -308,3 +318,4 @@ const filteredMenu = computed(() => {
|
|||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
669
ui/src/pages/AccountAgingStatement.vue
Normal file
669
ui/src/pages/AccountAgingStatement.vue
Normal file
@@ -0,0 +1,669 @@
|
|||||||
|
<template>
|
||||||
|
<q-page v-if="canReadFinance" class="q-px-md q-pb-md q-pt-xs page-col statement-page">
|
||||||
|
<div class="filter-sticky compact-filter q-pa-sm q-mb-xs">
|
||||||
|
<div class="row q-col-gutter-sm items-end">
|
||||||
|
<div class="col-12 col-md-5">
|
||||||
|
<q-select
|
||||||
|
v-model="selectedCari"
|
||||||
|
:options="filteredOptions"
|
||||||
|
label="Cari kod / isim"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
clearable
|
||||||
|
use-input
|
||||||
|
input-debounce="300"
|
||||||
|
@filter="filterCari"
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
:loading="accountStore.loading"
|
||||||
|
option-value="value"
|
||||||
|
option-label="label"
|
||||||
|
behavior="menu"
|
||||||
|
:keep-selected="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-6 col-md-2">
|
||||||
|
<q-input v-model="dateTo" label="Son tarih" filled dense clearable readonly>
|
||||||
|
<template #append>
|
||||||
|
<q-icon name="event" class="cursor-pointer">
|
||||||
|
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
||||||
|
<q-date v-model="dateTo" mask="YYYY-MM-DD" locale="tr-TR" />
|
||||||
|
</q-popup-proxy>
|
||||||
|
</q-icon>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-6 col-md-3">
|
||||||
|
<q-select
|
||||||
|
v-model="selectedMonType"
|
||||||
|
:options="monetaryTypeOptions"
|
||||||
|
label="Parasal İşlem Tipi"
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<q-btn color="primary" icon="filter_alt" label="Filtrele" @click="onFilterClick" />
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<q-btn flat color="grey-8" icon="restart_alt" label="Sıfırla" @click="resetFilters" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-scroll">
|
||||||
|
<div class="sticky-bar row justify-end items-center q-pa-sm bg-grey-1">
|
||||||
|
<q-btn-dropdown
|
||||||
|
v-if="canExportFinance"
|
||||||
|
flat
|
||||||
|
color="red"
|
||||||
|
icon="picture_as_pdf"
|
||||||
|
label="Yazdır"
|
||||||
|
class="q-mr-sm"
|
||||||
|
>
|
||||||
|
<q-list style="min-width: 220px">
|
||||||
|
<q-item clickable v-close-popup @click="downloadAgingPDF">
|
||||||
|
<q-item-section class="text-primary">
|
||||||
|
Detaylı Yaşlandırma Ekstresi Yazdır
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-btn-dropdown>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
color="secondary"
|
||||||
|
icon="list"
|
||||||
|
:label="allDetailsOpen ? 'Tüm Detayları Kapat' : 'Tüm Detayları Aç'"
|
||||||
|
@click="toggleAllDetails"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-table
|
||||||
|
class="sticky-table statement-table"
|
||||||
|
title="Cari Yaşlandırmalı Ekstre"
|
||||||
|
:rows="agingStore.masterRows"
|
||||||
|
:columns="masterColumns"
|
||||||
|
row-key="group_key"
|
||||||
|
flat
|
||||||
|
bordered
|
||||||
|
dense
|
||||||
|
hide-bottom
|
||||||
|
wrap-cells
|
||||||
|
:rows-per-page-options="[0]"
|
||||||
|
:loading="agingStore.loading"
|
||||||
|
:table-style="{ tableLayout: 'fixed', width: '100%' }"
|
||||||
|
>
|
||||||
|
<template #header="props">
|
||||||
|
<q-tr :props="props" class="header-row">
|
||||||
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">{{ col.label }}</q-th>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body="props">
|
||||||
|
<q-tr :props="props" class="master-row">
|
||||||
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
|
<q-btn
|
||||||
|
v-if="col.name === 'expand'"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
size="sm"
|
||||||
|
:icon="masterExpanded[props.row.group_key] ? 'expand_less' : 'expand_more'"
|
||||||
|
@click="toggleMaster(props.row.group_key)"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-else-if="masterNumericCols.includes(col.name)"
|
||||||
|
:class="['block', masterCenteredCols.includes(col.name) ? 'text-center' : 'text-right']"
|
||||||
|
>
|
||||||
|
{{ masterDayCols.includes(col.name) ? formatDay(props.row[col.field]) : formatAmount(props.row[col.field]) }}
|
||||||
|
</span>
|
||||||
|
<span v-else>{{ props.row[col.field] ?? '-' }}</span>
|
||||||
|
</q-td>
|
||||||
|
</q-tr>
|
||||||
|
|
||||||
|
<q-tr v-if="masterExpanded[props.row.group_key]" class="master-sub-row">
|
||||||
|
<q-td colspan="100%" class="q-pa-none">
|
||||||
|
<div class="currency-groups">
|
||||||
|
<div class="currency-level-head">
|
||||||
|
<div class="cgh-cell cgh-expand"></div>
|
||||||
|
<div class="cgh-cell cgh-code">Ana Cari Kod</div>
|
||||||
|
<div class="cgh-cell cgh-code">Ana Cari Detay</div>
|
||||||
|
<div class="cgh-cell cgh-code">Döviz Cinsi</div>
|
||||||
|
<div class="cgh-cell cgh-num">Açık Kalem Tutarı</div>
|
||||||
|
<div class="cgh-cell cgh-num">Açık Kalem USD</div>
|
||||||
|
<div class="cgh-cell cgh-num">Açık Kalem TRY</div>
|
||||||
|
<div class="cgh-cell cgh-center">Ort Gün</div>
|
||||||
|
<div class="cgh-cell cgh-center">Ort Belge Gün</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="currRow in agingStore.getCurrenciesByMaster(props.row.group_key)"
|
||||||
|
:key="currRow.group_key"
|
||||||
|
class="currency-group"
|
||||||
|
>
|
||||||
|
<div class="currency-group-header">
|
||||||
|
<div class="cgh-cell cgh-expand">
|
||||||
|
<q-btn
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
size="sm"
|
||||||
|
:icon="currencyExpanded[currRow.group_key] ? 'expand_less' : 'expand_more'"
|
||||||
|
@click="toggleCurrency(currRow.group_key)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="cgh-cell cgh-code">{{ currRow.cari8 }}</div>
|
||||||
|
<div class="cgh-cell cgh-code">{{ currRow.cari_detay || '-' }}</div>
|
||||||
|
<div class="cgh-cell cgh-code">{{ currRow.doviz_cinsi }}</div>
|
||||||
|
<div class="cgh-cell cgh-num">{{ formatAmount(currRow.acik_kalem_tutari) }}</div>
|
||||||
|
<div class="cgh-cell cgh-num">{{ formatAmount(currRow.acik_kalem_usd) }}</div>
|
||||||
|
<div class="cgh-cell cgh-num">{{ formatAmount(currRow.acik_kalem_try) }}</div>
|
||||||
|
<div class="cgh-cell cgh-center">{{ formatDay(currRow.ort_gun) }}</div>
|
||||||
|
<div class="cgh-cell cgh-center">{{ formatDay(currRow.ort_belge_gun) }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="currencyExpanded[currRow.group_key]" class="detail-host-row">
|
||||||
|
<q-table
|
||||||
|
:rows="agingStore.getDetailsByCurrency(currRow.group_key)"
|
||||||
|
:columns="detailColumns"
|
||||||
|
row-key="detail_key"
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
bordered
|
||||||
|
hide-bottom
|
||||||
|
:rows-per-page-options="[0]"
|
||||||
|
:pagination="{ rowsPerPage: 0 }"
|
||||||
|
class="detail-subtable"
|
||||||
|
:table-style="{ minWidth: '1500px' }"
|
||||||
|
>
|
||||||
|
<template #body-cell-eslesen_tutar="d">
|
||||||
|
<q-td :props="d" class="text-right">{{ formatAmount(d.row.eslesen_tutar) }}</q-td>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-usd_tutar="d">
|
||||||
|
<q-td :props="d" class="text-right">{{ formatAmount(d.row.usd_tutar) }}</q-td>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-try_tutar="d">
|
||||||
|
<q-td :props="d" class="text-right">{{ formatAmount(d.row.try_tutar) }}</q-td>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-gun_sayisi="d">
|
||||||
|
<q-td :props="d" class="text-center">{{ formatDay(d.row.gun_sayisi) }}</q-td>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-gun_sayisi_docdate="d">
|
||||||
|
<q-td :props="d" class="text-center">{{ formatDay(d.row.gun_sayisi_docdate) }}</q-td>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-gun_kur="d">
|
||||||
|
<q-td :props="d" class="text-center">{{ formatAmount(d.row.gun_kur, 2) }}</q-td>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-aciklama="d">
|
||||||
|
<q-td :props="d" class="text-center">{{ d.row.aciklama || '-' }}</q-td>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-td>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</div>
|
||||||
|
</q-page>
|
||||||
|
|
||||||
|
<q-page v-else class="q-pa-md flex flex-center">
|
||||||
|
<div class="text-negative text-subtitle1">Bu modüle erişim yetkiniz yok.</div>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { onMounted, ref } from 'vue'
|
||||||
|
import { useQuasar } from 'quasar'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { usePermission } from 'src/composables/usePermission'
|
||||||
|
import { useAccountStore } from 'src/stores/accountStore'
|
||||||
|
import { useStatementAgingStore } from 'src/stores/statementAgingStore'
|
||||||
|
import { download, extractApiErrorDetail } from 'src/services/api'
|
||||||
|
|
||||||
|
const { canRead, canExport } = usePermission()
|
||||||
|
const canReadFinance = canRead('finance')
|
||||||
|
const canExportFinance = canExport('finance')
|
||||||
|
|
||||||
|
const $q = useQuasar()
|
||||||
|
const accountStore = useAccountStore()
|
||||||
|
const agingStore = useStatementAgingStore()
|
||||||
|
|
||||||
|
const selectedCari = ref(null)
|
||||||
|
const filteredOptions = ref([])
|
||||||
|
const dateTo = ref(dayjs().format('YYYY-MM-DD'))
|
||||||
|
|
||||||
|
const masterExpanded = ref({})
|
||||||
|
const currencyExpanded = ref({})
|
||||||
|
const allDetailsOpen = ref(false)
|
||||||
|
|
||||||
|
const monetaryTypeOptions = [
|
||||||
|
{ label: '1-2 hesap', value: ['1', '2'] },
|
||||||
|
{ label: '1-3 r hesap', value: ['1', '3'] }
|
||||||
|
]
|
||||||
|
const selectedMonType = ref(monetaryTypeOptions[0].value)
|
||||||
|
|
||||||
|
const masterColumns = [
|
||||||
|
{ name: 'expand', label: '', field: 'expand', align: 'center' },
|
||||||
|
{ name: 'cari8', label: 'Ana Cari Kod', field: 'cari8', align: 'left', sortable: true },
|
||||||
|
{ name: 'cari_detay', label: 'Ana Cari Detay', field: 'cari_detay', align: 'left', sortable: true },
|
||||||
|
{ name: 'acik_kalem_tutari_usd', label: 'Açık Kalem Tutarı USD', field: 'acik_kalem_tutari_usd', align: 'right', sortable: true },
|
||||||
|
{ name: 'acik_kalem_tutari_try', label: 'Açık Kalem Tutarı TRY', field: 'acik_kalem_tutari_try', align: 'right', sortable: true },
|
||||||
|
{ name: 'acik_kalem_ort_vade_gun', label: 'Açık Kalem Ort Vade Gün', field: 'acik_kalem_ort_vade_gun', align: 'center', sortable: true },
|
||||||
|
{ name: 'acik_kalem_ort_belge_gun', label: 'Açık Kalem Ort Belge Gün', field: 'acik_kalem_ort_belge_gun', align: 'center', sortable: true },
|
||||||
|
{ name: 'normal_usd_tutar', label: 'Normal USD Tutar', field: 'normal_usd_tutar', align: 'right', sortable: true },
|
||||||
|
{ name: 'normal_try_tutar', label: 'Normal TRY Tutar', field: 'normal_try_tutar', align: 'right', sortable: true },
|
||||||
|
{ name: 'ortalama_vade_gun', label: 'Ortalama Vade Gün', field: 'ortalama_vade_gun', align: 'center', sortable: true },
|
||||||
|
{ name: 'ortalama_belge_gun', label: 'Ortalama Belge Gün', field: 'ortalama_belge_gun', align: 'center', sortable: true }
|
||||||
|
]
|
||||||
|
|
||||||
|
const detailColumns = [
|
||||||
|
{ name: 'fatura_cari', label: 'Fatura Cari', field: 'fatura_cari', align: 'left' },
|
||||||
|
{ name: 'odeme_cari', label: 'Ödeme Cari', field: 'odeme_cari', align: 'left' },
|
||||||
|
{ name: 'doc_currency_code', label: 'Döviz Cinsi', field: 'doc_currency_code', align: 'left' },
|
||||||
|
{ name: 'fatura_ref', label: 'Fatura Ref', field: 'fatura_ref', align: 'left' },
|
||||||
|
{ name: 'odeme_ref', label: 'Ödeme Ref', field: 'odeme_ref', align: 'left' },
|
||||||
|
{ name: 'fatura_tarihi', label: 'Fatura Tarihi', field: 'fatura_tarihi', align: 'left' },
|
||||||
|
{ name: 'odeme_tarihi', label: 'Ödeme Vade', field: 'odeme_tarihi', align: 'left' },
|
||||||
|
{ name: 'odeme_doc_date', label: 'Ödeme DocDate', field: 'odeme_doc_date', align: 'left' },
|
||||||
|
{ name: 'eslesen_tutar', label: 'Eşleşen Tutar', field: 'eslesen_tutar', align: 'right' },
|
||||||
|
{ name: 'usd_tutar', label: 'USD Tutar', field: 'usd_tutar', align: 'right' },
|
||||||
|
{ name: 'try_tutar', label: 'TRY Tutar', field: 'try_tutar', align: 'right' },
|
||||||
|
{ name: 'aciklama', label: 'Açıklama', field: 'aciklama', align: 'center' },
|
||||||
|
{ name: 'gun_sayisi', label: 'Gün', field: 'gun_sayisi', align: 'center' },
|
||||||
|
{ name: 'gun_sayisi_docdate', label: 'Gün (DocDate)', field: 'gun_sayisi_docdate', align: 'center' },
|
||||||
|
{ name: 'gun_kur', label: 'Gün Kur', field: 'gun_kur', align: 'center' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const masterNumericCols = ['acik_kalem_tutari_usd', 'acik_kalem_tutari_try', 'acik_kalem_ort_vade_gun', 'acik_kalem_ort_belge_gun', 'normal_usd_tutar', 'normal_try_tutar', 'ortalama_vade_gun', 'ortalama_belge_gun']
|
||||||
|
const masterDayCols = ['acik_kalem_ort_vade_gun', 'acik_kalem_ort_belge_gun', 'ortalama_vade_gun', 'ortalama_belge_gun']
|
||||||
|
const masterCenteredCols = ['acik_kalem_ort_vade_gun', 'acik_kalem_ort_belge_gun', 'ortalama_vade_gun', 'ortalama_belge_gun']
|
||||||
|
|
||||||
|
function normalizeText(str) {
|
||||||
|
return (str || '')
|
||||||
|
.toString()
|
||||||
|
.toLocaleLowerCase('tr-TR')
|
||||||
|
.normalize('NFD')
|
||||||
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterCari(val, update) {
|
||||||
|
const needle = normalizeText(val)
|
||||||
|
|
||||||
|
update(() => {
|
||||||
|
if (!needle) {
|
||||||
|
filteredOptions.value = accountStore.accountOptions
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredOptions.value = accountStore.accountOptions.filter(o => {
|
||||||
|
const label = normalizeText(o.label)
|
||||||
|
const value = normalizeText(o.value)
|
||||||
|
return label.includes(needle) || value.includes(needle)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await accountStore.fetchAccounts()
|
||||||
|
filteredOptions.value = accountStore.accountOptions
|
||||||
|
})
|
||||||
|
|
||||||
|
async function onFilterClick() {
|
||||||
|
if (!selectedCari.value || !dateTo.value) {
|
||||||
|
$q.notify({
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Lütfen cari ve son tarih seçiniz.',
|
||||||
|
position: 'top-right'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await agingStore.load({
|
||||||
|
accountcode: selectedCari.value,
|
||||||
|
enddate: dateTo.value,
|
||||||
|
parislemler: selectedMonType.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const m = {}
|
||||||
|
const c = {}
|
||||||
|
for (const row of agingStore.masterRows) {
|
||||||
|
m[row.group_key] = true
|
||||||
|
for (const cr of agingStore.getCurrenciesByMaster(row.group_key)) {
|
||||||
|
c[cr.group_key] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
masterExpanded.value = m
|
||||||
|
currencyExpanded.value = c
|
||||||
|
allDetailsOpen.value = agingStore.masterRows.length > 0
|
||||||
|
} catch (err) {
|
||||||
|
const msg = await extractApiErrorDetail(err)
|
||||||
|
$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
message: msg || 'Veriler yüklenemedi',
|
||||||
|
position: 'top-right'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetFilters() {
|
||||||
|
selectedCari.value = null
|
||||||
|
dateTo.value = dayjs().format('YYYY-MM-DD')
|
||||||
|
selectedMonType.value = monetaryTypeOptions[0].value
|
||||||
|
masterExpanded.value = {}
|
||||||
|
currencyExpanded.value = {}
|
||||||
|
allDetailsOpen.value = false
|
||||||
|
agingStore.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMaster(key) {
|
||||||
|
masterExpanded.value[key] = !masterExpanded.value[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCurrency(key) {
|
||||||
|
currencyExpanded.value[key] = !currencyExpanded.value[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAllDetails() {
|
||||||
|
allDetailsOpen.value = !allDetailsOpen.value
|
||||||
|
|
||||||
|
if (!allDetailsOpen.value) {
|
||||||
|
masterExpanded.value = {}
|
||||||
|
currencyExpanded.value = {}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const m = {}
|
||||||
|
const c = {}
|
||||||
|
for (const row of agingStore.masterRows) {
|
||||||
|
m[row.group_key] = true
|
||||||
|
for (const cr of agingStore.getCurrenciesByMaster(row.group_key)) {
|
||||||
|
c[cr.group_key] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
masterExpanded.value = m
|
||||||
|
currencyExpanded.value = c
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatAmount(value, fraction = 2) {
|
||||||
|
const n = Number(value || 0)
|
||||||
|
return new Intl.NumberFormat('tr-TR', {
|
||||||
|
minimumFractionDigits: fraction,
|
||||||
|
maximumFractionDigits: fraction
|
||||||
|
}).format(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDay(value) {
|
||||||
|
const n = Number(value || 0)
|
||||||
|
const v = Number.isFinite(n) ? Math.ceil(n) : 0
|
||||||
|
return new Intl.NumberFormat('tr-TR', {
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 0
|
||||||
|
}).format(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadAgingPDF () {
|
||||||
|
if (!canExportFinance.value) {
|
||||||
|
$q.notify({ type: 'negative', message: 'PDF export yetkiniz yok', position: 'top-right' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selectedCari.value || !dateTo.value) {
|
||||||
|
$q.notify({ type: 'warning', message: 'Lütfen cari ve son tarih seçiniz.', position: 'top-right' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const blob = await download('/finance/account-aging-statement/export-pdf', {
|
||||||
|
accountcode: selectedCari.value,
|
||||||
|
enddate: dateTo.value,
|
||||||
|
parislemler: selectedMonType.value
|
||||||
|
})
|
||||||
|
const pdfUrl = window.URL.createObjectURL(new Blob([blob], { type: 'application/pdf' }))
|
||||||
|
window.open(pdfUrl, '_blank')
|
||||||
|
} catch (err) {
|
||||||
|
const detail = await extractApiErrorDetail(err?.original || err)
|
||||||
|
$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
message: detail || 'PDF oluşturulamadı',
|
||||||
|
position: 'top-right'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.statement-page {
|
||||||
|
--master-head-h: 34px;
|
||||||
|
--lvl2-head-h: 34px;
|
||||||
|
--lvl3-head-h: 34px;
|
||||||
|
height: calc(100vh - 56px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-scroll {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact-filter {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-bar {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 30;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
background: var(--q-secondary);
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.statement-table {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statement-table :deep(.q-table__container) {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statement-table :deep(.q-table__top) {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statement-table :deep(.q-table__middle) {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto !important;
|
||||||
|
max-height: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statement-table :deep(.header-row th) {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 30;
|
||||||
|
height: var(--master-head-h);
|
||||||
|
background: var(--q-primary);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.22);
|
||||||
|
box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.2), 0 1px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.statement-table :deep(.master-row td) {
|
||||||
|
background: color-mix(in srgb, var(--q-secondary) 12%, white);
|
||||||
|
border-bottom: 2px solid rgba(0, 0, 0, 0.18);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statement-table :deep(.master-row td:first-child) {
|
||||||
|
border-left: 3px solid var(--q-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.statement-table :deep(.master-sub-row td) {
|
||||||
|
background: #f4f6fb;
|
||||||
|
border-bottom: 8px solid #fff;
|
||||||
|
vertical-align: top;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.currency-groups {
|
||||||
|
padding: 6px;
|
||||||
|
background: #f8faff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.currency-group {
|
||||||
|
border-left: 4px solid var(--q-secondary);
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
border-right: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
background: #fff;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.currency-level-head {
|
||||||
|
position: sticky;
|
||||||
|
top: var(--master-head-h);
|
||||||
|
z-index: 27;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 48px 120px 280px 110px 1fr 1fr 1fr 120px 150px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
background: var(--q-secondary);
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
min-height: var(--lvl2-head-h);
|
||||||
|
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.currency-group-header {
|
||||||
|
position: sticky;
|
||||||
|
top: calc(var(--master-head-h) + var(--lvl2-head-h));
|
||||||
|
z-index: 26;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 48px 120px 280px 110px 1fr 1fr 1fr 120px 150px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
background: #4c5f7a;
|
||||||
|
color: #fff;
|
||||||
|
min-height: var(--lvl3-head-h);
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.18);
|
||||||
|
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cgh-cell {
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-right: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cgh-cell:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cgh-num {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cgh-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cgh-expand {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-host-row :deep(td) {
|
||||||
|
background: #fdfdfd;
|
||||||
|
padding: 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-subtable {
|
||||||
|
border-left: 4px solid var(--q-primary);
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
border-right: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statement-table :deep(th),
|
||||||
|
.statement-table :deep(td),
|
||||||
|
.detail-subtable :deep(th),
|
||||||
|
.detail-subtable :deep(td) {
|
||||||
|
padding: 3px 6px !important;
|
||||||
|
font-size: 11px !important;
|
||||||
|
line-height: 1.2 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-subtable :deep(.q-table__top) {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-subtable :deep(.q-table__middle) {
|
||||||
|
overflow: visible !important;
|
||||||
|
max-height: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-subtable :deep(thead th) {
|
||||||
|
position: sticky;
|
||||||
|
top: calc(var(--master-head-h) + var(--lvl2-head-h) + var(--lvl3-head-h));
|
||||||
|
z-index: 25;
|
||||||
|
background: #1f3b5b;
|
||||||
|
color: #fff;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-subtable :deep(td[data-col="gun_sayisi"]),
|
||||||
|
.detail-subtable :deep(td[data-col="gun_sayisi_docdate"]),
|
||||||
|
.detail-subtable :deep(td[data-col="gun_kur"]),
|
||||||
|
.detail-subtable :deep(td[data-col="aciklama"]),
|
||||||
|
.detail-subtable :deep(th[data-col="gun_sayisi"]),
|
||||||
|
.detail-subtable :deep(th[data-col="gun_sayisi_docdate"]),
|
||||||
|
.detail-subtable :deep(th[data-col="gun_kur"]),
|
||||||
|
.detail-subtable :deep(th[data-col="aciklama"]),
|
||||||
|
.statement-table :deep(td[data-col="acik_kalem_ort_vade_gun"]),
|
||||||
|
.statement-table :deep(td[data-col="acik_kalem_ort_belge_gun"]),
|
||||||
|
.statement-table :deep(td[data-col="ortalama_vade_gun"]),
|
||||||
|
.statement-table :deep(td[data-col="ortalama_belge_gun"]),
|
||||||
|
.statement-table :deep(th[data-col="acik_kalem_ort_vade_gun"]),
|
||||||
|
.statement-table :deep(th[data-col="acik_kalem_ort_belge_gun"]),
|
||||||
|
.statement-table :deep(th[data-col="ortalama_vade_gun"]),
|
||||||
|
.statement-table :deep(th[data-col="ortalama_belge_gun"]) {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1366px) {
|
||||||
|
.statement-table :deep(th),
|
||||||
|
.statement-table :deep(td),
|
||||||
|
.detail-subtable :deep(th),
|
||||||
|
.detail-subtable :deep(td) {
|
||||||
|
font-size: 10px !important;
|
||||||
|
padding: 2px 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.currency-group-header {
|
||||||
|
grid-template-columns: 44px 100px 220px 90px 1fr 1fr 1fr 90px 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
13
ui/src/pages/AgedCustomerBalanceListDummy.vue
Normal file
13
ui/src/pages/AgedCustomerBalanceListDummy.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<q-page class="q-pa-md">
|
||||||
|
<q-card flat bordered class="q-pa-lg">
|
||||||
|
<div class="text-h6">Cari Yaşlandırmalı Cari Bakiye Listesi</div>
|
||||||
|
<div class="text-subtitle2 q-mt-sm text-grey-7">
|
||||||
|
Dummy ekran hazır. Bu modülün filtre ve tablo kurgusu bir sonraki adımda eklenecek.
|
||||||
|
</div>
|
||||||
|
</q-card>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
</script>
|
||||||
13
ui/src/pages/AgedStatementDummy.vue
Normal file
13
ui/src/pages/AgedStatementDummy.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<q-page class="q-pa-md">
|
||||||
|
<q-card flat bordered class="q-pa-lg">
|
||||||
|
<div class="text-h6">Cari Yaşlandırmalı Ekstre</div>
|
||||||
|
<div class="text-subtitle2 q-mt-sm text-grey-7">
|
||||||
|
Dummy ekran hazır. Bu modülün filtre ve tablo kurgusu bir sonraki adımda eklenecek.
|
||||||
|
</div>
|
||||||
|
</q-card>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
</script>
|
||||||
@@ -1,263 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-page v-if="canReadFinance" class="q-pa-md page-layout">
|
<q-page v-if="canReadFinance" class="q-pa-md page-layout">
|
||||||
<div class="filter-sticky">
|
<div class="filter-sticky">
|
||||||
<div class="row q-col-gutter-sm q-mb-md">
|
<div class="top-actions row q-col-gutter-sm items-end q-mb-sm">
|
||||||
<div class="col-12 col-sm-6 col-md-4">
|
<div class="col-12 col-sm-6 col-md-2">
|
||||||
<q-input
|
<q-input
|
||||||
v-model="store.filters.cariSearch"
|
v-model="store.filters.selectedDate"
|
||||||
filled
|
label="Tarih"
|
||||||
dense
|
filled
|
||||||
label="Cari Kodu / Cari Adı"
|
dense
|
||||||
@keyup.enter="store.applyCariSearch()"
|
readonly
|
||||||
>
|
>
|
||||||
<template #append>
|
<template #append>
|
||||||
<q-btn
|
<q-icon name="event" class="cursor-pointer">
|
||||||
dense
|
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
||||||
flat
|
<q-date v-model="store.filters.selectedDate" mask="YYYY-MM-DD" />
|
||||||
round
|
</q-popup-proxy>
|
||||||
icon="search"
|
</q-icon>
|
||||||
@click="store.applyCariSearch()"
|
</template>
|
||||||
/>
|
</q-input>
|
||||||
</template>
|
</div>
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12 col-sm-6 col-md-2">
|
<div class="col-12 col-sm-6 col-md-3">
|
||||||
<q-input
|
<q-toggle
|
||||||
v-model="store.filters.selectedDate"
|
v-model="store.filters.excludeZeroBalance12"
|
||||||
label="Tarih"
|
dense
|
||||||
filled
|
label="1_2 Bakiyesi Sıfır Olanları Alma"
|
||||||
dense
|
@update:model-value="onToggle12Changed"
|
||||||
readonly
|
/>
|
||||||
>
|
</div>
|
||||||
<template #append>
|
|
||||||
<q-icon name="event" class="cursor-pointer">
|
|
||||||
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
|
||||||
<q-date v-model="store.filters.selectedDate" mask="YYYY-MM-DD" />
|
|
||||||
</q-popup-proxy>
|
|
||||||
</q-icon>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12 col-sm-6 col-md-2">
|
<div class="col-12 col-sm-6 col-md-3">
|
||||||
<q-select
|
<q-toggle
|
||||||
v-model="store.filters.cariIlkGrup"
|
v-model="store.filters.excludeZeroBalance13"
|
||||||
:options="store.cariIlkGrupOptions"
|
dense
|
||||||
multiple
|
label="1_3 Bakiyesi Sıfır Olanları Alma"
|
||||||
emit-value
|
@update:model-value="onToggle13Changed"
|
||||||
map-options
|
/>
|
||||||
filled
|
</div>
|
||||||
dense
|
|
||||||
options-dense
|
|
||||||
class="compact-select"
|
|
||||||
label="Cari İlk Grup"
|
|
||||||
:display-value="selectionLabel(store.filters.cariIlkGrup, 'Cari İlk Grup')"
|
|
||||||
>
|
|
||||||
<template #before-options>
|
|
||||||
<q-item clickable dense @click.stop="store.selectAll('cariIlkGrup', store.cariIlkGrupOptions)">
|
|
||||||
<q-item-section>Tümünü Seç</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable dense @click.stop="store.clearAll('cariIlkGrup')">
|
|
||||||
<q-item-section>Tümünü Temizle</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-separator />
|
|
||||||
</template>
|
|
||||||
<template #option="scope">
|
|
||||||
<q-item v-bind="scope.itemProps">
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-checkbox :model-value="scope.selected" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
</q-select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12 col-sm-6 col-md-2">
|
|
||||||
<q-select
|
|
||||||
v-model="store.filters.piyasa"
|
|
||||||
:options="store.piyasaOptions"
|
|
||||||
multiple
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
options-dense
|
|
||||||
class="compact-select"
|
|
||||||
label="Piyasa"
|
|
||||||
:display-value="selectionLabel(store.filters.piyasa, 'Piyasa')"
|
|
||||||
>
|
|
||||||
<template #before-options>
|
|
||||||
<q-item clickable dense @click.stop="store.selectAll('piyasa', store.piyasaOptions)">
|
|
||||||
<q-item-section>Tümünü Seç</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable dense @click.stop="store.clearAll('piyasa')">
|
|
||||||
<q-item-section>Tümünü Temizle</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-separator />
|
|
||||||
</template>
|
|
||||||
<template #option="scope">
|
|
||||||
<q-item v-bind="scope.itemProps">
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-checkbox :model-value="scope.selected" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
</q-select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12 col-sm-6 col-md-2">
|
|
||||||
<q-select
|
|
||||||
v-model="store.filters.temsilci"
|
|
||||||
:options="store.temsilciOptions"
|
|
||||||
multiple
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
options-dense
|
|
||||||
class="compact-select"
|
|
||||||
label="Temsilci"
|
|
||||||
:display-value="selectionLabel(store.filters.temsilci, 'Temsilci')"
|
|
||||||
>
|
|
||||||
<template #before-options>
|
|
||||||
<q-item clickable dense @click.stop="store.selectAll('temsilci', store.temsilciOptions)">
|
|
||||||
<q-item-section>Tümünü Seç</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable dense @click.stop="store.clearAll('temsilci')">
|
|
||||||
<q-item-section>Tümünü Temizle</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-separator />
|
|
||||||
</template>
|
|
||||||
<template #option="scope">
|
|
||||||
<q-item v-bind="scope.itemProps">
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-checkbox :model-value="scope.selected" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
</q-select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12 col-sm-6 col-md-2">
|
|
||||||
<q-select
|
|
||||||
v-model="store.filters.riskDurumu"
|
|
||||||
:options="store.riskDurumuOptions"
|
|
||||||
multiple
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
options-dense
|
|
||||||
class="compact-select"
|
|
||||||
label="Risk Durumu"
|
|
||||||
:display-value="selectionLabel(store.filters.riskDurumu, 'Risk Durumu')"
|
|
||||||
>
|
|
||||||
<template #before-options>
|
|
||||||
<q-item clickable dense @click.stop="store.selectAll('riskDurumu', store.riskDurumuOptions)">
|
|
||||||
<q-item-section>Tümünü Seç</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable dense @click.stop="store.clearAll('riskDurumu')">
|
|
||||||
<q-item-section>Tümünü Temizle</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-separator />
|
|
||||||
</template>
|
|
||||||
<template #option="scope">
|
|
||||||
<q-item v-bind="scope.itemProps">
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-checkbox :model-value="scope.selected" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
</q-select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12 col-sm-6 col-md-2">
|
|
||||||
<q-select
|
|
||||||
v-model="store.filters.islemTipi"
|
|
||||||
:options="islemTipiOptions"
|
|
||||||
multiple
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
options-dense
|
|
||||||
class="compact-select"
|
|
||||||
label="İşlem Tipi"
|
|
||||||
:display-value="selectionLabel(store.filters.islemTipi, 'İşlem Tipi')"
|
|
||||||
>
|
|
||||||
<template #before-options>
|
|
||||||
<q-item clickable dense @click.stop="store.selectAll('islemTipi', islemTipiOptions)">
|
|
||||||
<q-item-section>Tümünü Seç</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable dense @click.stop="store.clearAll('islemTipi')">
|
|
||||||
<q-item-section>Tümünü Temizle</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-separator />
|
|
||||||
</template>
|
|
||||||
<template #option="scope">
|
|
||||||
<q-item v-bind="scope.itemProps">
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-checkbox :model-value="scope.selected" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
</q-select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12 col-sm-6 col-md-2">
|
|
||||||
<q-select
|
|
||||||
v-model="store.filters.ulke"
|
|
||||||
:options="store.ulkeOptions"
|
|
||||||
multiple
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
options-dense
|
|
||||||
class="compact-select"
|
|
||||||
label="Ülke (Özellik05)"
|
|
||||||
:display-value="selectionLabel(store.filters.ulke, 'Ülke')"
|
|
||||||
>
|
|
||||||
<template #before-options>
|
|
||||||
<q-item clickable dense @click.stop="store.selectAll('ulke', store.ulkeOptions)">
|
|
||||||
<q-item-section>Tümünü Seç</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable dense @click.stop="store.clearAll('ulke')">
|
|
||||||
<q-item-section>Tümünü Temizle</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-separator />
|
|
||||||
</template>
|
|
||||||
<template #option="scope">
|
|
||||||
<q-item v-bind="scope.itemProps">
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-checkbox :model-value="scope.selected" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
</q-select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row q-col-gutter-sm q-mb-md">
|
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<q-btn
|
<q-btn
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -279,140 +59,472 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="filters-panel q-pa-sm q-mb-md">
|
||||||
|
<div class="row q-col-gutter-sm">
|
||||||
|
<div class="col-12 col-sm-6 col-md-4">
|
||||||
|
<q-input
|
||||||
|
v-model="store.filters.cariSearch"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
label="Cari Kodu / Cari Adı"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-sm-6 col-md-2">
|
||||||
|
<q-select
|
||||||
|
v-model="store.filters.cariIlkGrup"
|
||||||
|
:options="store.cariIlkGrupOptions"
|
||||||
|
multiple
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
options-dense
|
||||||
|
class="compact-select"
|
||||||
|
label="Cari İlk Grup"
|
||||||
|
:display-value="selectionLabel(store.filters.cariIlkGrup, 'Cari İlk Grup')"
|
||||||
|
>
|
||||||
|
<template #before-options>
|
||||||
|
<q-item clickable dense @click.stop="store.selectAll('cariIlkGrup', store.cariIlkGrupOptions)">
|
||||||
|
<q-item-section>Tümünü Seç</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable dense @click.stop="store.clearAll('cariIlkGrup')">
|
||||||
|
<q-item-section>Tümünü Temizle</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator />
|
||||||
|
</template>
|
||||||
|
<template #option="scope">
|
||||||
|
<q-item v-bind="scope.itemProps">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-checkbox :model-value="scope.selected" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-sm-6 col-md-2">
|
||||||
|
<q-select
|
||||||
|
v-model="store.filters.piyasa"
|
||||||
|
:options="store.piyasaOptions"
|
||||||
|
multiple
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
options-dense
|
||||||
|
class="compact-select"
|
||||||
|
label="Piyasa"
|
||||||
|
:display-value="selectionLabel(store.filters.piyasa, 'Piyasa')"
|
||||||
|
>
|
||||||
|
<template #before-options>
|
||||||
|
<q-item clickable dense @click.stop="store.selectAll('piyasa', store.piyasaOptions)">
|
||||||
|
<q-item-section>Tümünü Seç</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable dense @click.stop="store.clearAll('piyasa')">
|
||||||
|
<q-item-section>Tümünü Temizle</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator />
|
||||||
|
</template>
|
||||||
|
<template #option="scope">
|
||||||
|
<q-item v-bind="scope.itemProps">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-checkbox :model-value="scope.selected" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-sm-6 col-md-2">
|
||||||
|
<q-select
|
||||||
|
v-model="store.filters.temsilci"
|
||||||
|
:options="store.temsilciOptions"
|
||||||
|
multiple
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
options-dense
|
||||||
|
class="compact-select"
|
||||||
|
label="Temsilci"
|
||||||
|
:display-value="selectionLabel(store.filters.temsilci, 'Temsilci')"
|
||||||
|
>
|
||||||
|
<template #before-options>
|
||||||
|
<q-item clickable dense @click.stop="store.selectAll('temsilci', store.temsilciOptions)">
|
||||||
|
<q-item-section>Tümünü Seç</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable dense @click.stop="store.clearAll('temsilci')">
|
||||||
|
<q-item-section>Tümünü Temizle</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator />
|
||||||
|
</template>
|
||||||
|
<template #option="scope">
|
||||||
|
<q-item v-bind="scope.itemProps">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-checkbox :model-value="scope.selected" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-sm-6 col-md-2">
|
||||||
|
<q-select
|
||||||
|
v-model="store.filters.riskDurumu"
|
||||||
|
:options="store.riskDurumuOptions"
|
||||||
|
multiple
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
options-dense
|
||||||
|
class="compact-select"
|
||||||
|
label="Risk Durumu"
|
||||||
|
:display-value="selectionLabel(store.filters.riskDurumu, 'Risk Durumu')"
|
||||||
|
>
|
||||||
|
<template #before-options>
|
||||||
|
<q-item clickable dense @click.stop="store.selectAll('riskDurumu', store.riskDurumuOptions)">
|
||||||
|
<q-item-section>Tümünü Seç</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable dense @click.stop="store.clearAll('riskDurumu')">
|
||||||
|
<q-item-section>Tümünü Temizle</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator />
|
||||||
|
</template>
|
||||||
|
<template #option="scope">
|
||||||
|
<q-item v-bind="scope.itemProps">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-checkbox :model-value="scope.selected" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-sm-6 col-md-2">
|
||||||
|
<q-select
|
||||||
|
v-model="store.filters.islemTipi"
|
||||||
|
:options="islemTipiOptions"
|
||||||
|
multiple
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
options-dense
|
||||||
|
class="compact-select"
|
||||||
|
label="İşlem Tipi"
|
||||||
|
:display-value="selectionLabel(store.filters.islemTipi, 'İşlem Tipi')"
|
||||||
|
>
|
||||||
|
<template #before-options>
|
||||||
|
<q-item clickable dense @click.stop="store.selectAll('islemTipi', islemTipiOptions)">
|
||||||
|
<q-item-section>Tümünü Seç</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable dense @click.stop="store.clearAll('islemTipi')">
|
||||||
|
<q-item-section>Tümünü Temizle</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator />
|
||||||
|
</template>
|
||||||
|
<template #option="scope">
|
||||||
|
<q-item v-bind="scope.itemProps">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-checkbox :model-value="scope.selected" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-sm-6 col-md-2">
|
||||||
|
<q-select
|
||||||
|
v-model="store.filters.ulke"
|
||||||
|
:options="store.ulkeOptions"
|
||||||
|
multiple
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
options-dense
|
||||||
|
class="compact-select"
|
||||||
|
label="Ülke"
|
||||||
|
:display-value="selectionLabel(store.filters.ulke, 'Ülke')"
|
||||||
|
>
|
||||||
|
<template #before-options>
|
||||||
|
<q-item clickable dense @click.stop="store.selectAll('ulke', store.ulkeOptions)">
|
||||||
|
<q-item-section>Tümünü Seç</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable dense @click.stop="store.clearAll('ulke')">
|
||||||
|
<q-item-section>Tümünü Temizle</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator />
|
||||||
|
</template>
|
||||||
|
<template #option="scope">
|
||||||
|
<q-item v-bind="scope.itemProps">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-checkbox :model-value="scope.selected" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-sm-6 col-md-2">
|
||||||
|
<q-select
|
||||||
|
v-model="store.filters.il"
|
||||||
|
:options="store.ilOptions"
|
||||||
|
multiple
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
options-dense
|
||||||
|
class="compact-select"
|
||||||
|
label="İl"
|
||||||
|
:display-value="selectionLabel(store.filters.il, 'İl')"
|
||||||
|
>
|
||||||
|
<template #before-options>
|
||||||
|
<q-item clickable dense @click.stop="store.selectAll('il', store.ilOptions)">
|
||||||
|
<q-item-section>Tümünü Seç</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable dense @click.stop="store.clearAll('il')">
|
||||||
|
<q-item-section>Tümünü Temizle</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator />
|
||||||
|
</template>
|
||||||
|
<template #option="scope">
|
||||||
|
<q-item v-bind="scope.itemProps">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-checkbox :model-value="scope.selected" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-sm-6 col-md-2">
|
||||||
|
<q-select
|
||||||
|
v-model="store.filters.ilce"
|
||||||
|
:options="store.ilceOptions"
|
||||||
|
multiple
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
options-dense
|
||||||
|
class="compact-select"
|
||||||
|
label="İlçe"
|
||||||
|
:display-value="selectionLabel(store.filters.ilce, 'İlçe')"
|
||||||
|
>
|
||||||
|
<template #before-options>
|
||||||
|
<q-item clickable dense @click.stop="store.selectAll('ilce', store.ilceOptions)">
|
||||||
|
<q-item-section>Tümünü Seç</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable dense @click.stop="store.clearAll('ilce')">
|
||||||
|
<q-item-section>Tümünü Temizle</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator />
|
||||||
|
</template>
|
||||||
|
<template #option="scope">
|
||||||
|
<q-item v-bind="scope.itemProps">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-checkbox :model-value="scope.selected" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<q-banner v-if="store.error" class="bg-red-1 text-negative q-mb-md rounded-borders">
|
<q-banner v-if="store.error" class="bg-red-1 text-negative q-mb-md rounded-borders">
|
||||||
{{ store.error }}
|
{{ store.error }}
|
||||||
</q-banner>
|
</q-banner>
|
||||||
|
|
||||||
<q-banner v-if="!store.hasFetched && !store.loading" class="bg-blue-1 text-primary q-mb-md rounded-borders">
|
<q-banner v-if="!store.hasFetched && !store.loading" class="bg-blue-1 text-primary q-mb-md rounded-borders">
|
||||||
Bakiyeleri Getir Tuşuna Basmadan Sistem Çalışmaz
|
Bakiyeleri Getir tuşuna basmadan sistem çalışmaz.
|
||||||
</q-banner>
|
</q-banner>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-area">
|
<div class="table-area">
|
||||||
<div class="sticky-bar row justify-end items-center q-pa-sm bg-grey-1">
|
<div class="sticky-bar row justify-between items-center q-pa-sm bg-grey-1">
|
||||||
<q-btn
|
<div />
|
||||||
flat
|
<div class="row items-center q-gutter-sm">
|
||||||
color="secondary"
|
<q-btn
|
||||||
icon="list"
|
flat
|
||||||
:label="allDetailsOpen ? 'Tüm Detayları Kapat' : 'Tüm Detayları Aç'"
|
color="secondary"
|
||||||
@click="toggleAllDetails"
|
icon="list"
|
||||||
/>
|
:label="allDetailsOpen ? 'Tüm Detayları Kapat' : 'Tüm Detayları Aç'"
|
||||||
</div>
|
@click="toggleAllDetails"
|
||||||
<q-table
|
/>
|
||||||
title="Cari Bakiye Listesi"
|
<q-btn-dropdown
|
||||||
:rows="store.summaryRows"
|
v-if="canExportFinance"
|
||||||
:columns="summaryColumns"
|
flat
|
||||||
row-key="group_key"
|
color="red"
|
||||||
:loading="store.loading"
|
icon="picture_as_pdf"
|
||||||
flat
|
label="Yazdır"
|
||||||
bordered
|
|
||||||
dense
|
|
||||||
wrap-cells
|
|
||||||
separator="cell"
|
|
||||||
hide-bottom
|
|
||||||
:rows-per-page-options="[0]"
|
|
||||||
:pagination="{ rowsPerPage: 0 }"
|
|
||||||
:table-style="{ tableLayout: 'fixed', width: '100%' }"
|
|
||||||
class="balance-table"
|
|
||||||
>
|
|
||||||
<template #header="props">
|
|
||||||
<q-tr :props="props" class="header-row">
|
|
||||||
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
|
||||||
{{ col.label }}
|
|
||||||
</q-th>
|
|
||||||
</q-tr>
|
|
||||||
<q-tr class="totals-row">
|
|
||||||
<q-th
|
|
||||||
v-for="col in props.cols"
|
|
||||||
:key="`tot-${col.name}`"
|
|
||||||
:class="col.align === 'right' ? 'text-right' : ''"
|
|
||||||
>
|
>
|
||||||
{{ totalCellValue(col.name) }}
|
<q-list style="min-width: 240px">
|
||||||
</q-th>
|
<q-item clickable v-close-popup @click="downloadCustomerBalancePDF(true)">
|
||||||
</q-tr>
|
<q-item-section class="text-primary">
|
||||||
</template>
|
Detaylı Cari Bakiye Listesi Yazdır
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-close-popup @click="downloadCustomerBalancePDF(false)">
|
||||||
|
<q-item-section class="text-secondary">
|
||||||
|
Detaysız Cari Bakiye Listesi Yazdır
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-btn-dropdown>
|
||||||
|
<q-btn
|
||||||
|
v-if="canExportFinance"
|
||||||
|
flat
|
||||||
|
color="green-8"
|
||||||
|
icon="table_view"
|
||||||
|
label="Excel"
|
||||||
|
@click="downloadCustomerBalanceExcel"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<template #body="props">
|
<q-table
|
||||||
<q-tr :props="props" class="sub-header-row">
|
title="Cari Bakiye Listesi"
|
||||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
:rows="store.summaryRows"
|
||||||
<q-btn
|
:columns="summaryColumns"
|
||||||
v-if="col.name === 'expand'"
|
row-key="group_key"
|
||||||
dense
|
:loading="store.loading"
|
||||||
flat
|
flat
|
||||||
round
|
bordered
|
||||||
size="sm"
|
dense
|
||||||
:icon="expanded[props.row.group_key] ? 'expand_less' : 'expand_more'"
|
wrap-cells
|
||||||
@click="toggleGroup(props.row.group_key)"
|
separator="cell"
|
||||||
/>
|
hide-bottom
|
||||||
<span v-else-if="col.name === 'prbr_1_2'" class="text-right block prbr-cell">
|
:rows-per-page-options="[0]"
|
||||||
{{ formatCurrencyMap(props.row.bakiye_1_2_map) }}
|
:pagination="{ rowsPerPage: 0 }"
|
||||||
</span>
|
:table-style="{ tableLayout: 'fixed', width: '100%' }"
|
||||||
<span v-else-if="col.name === 'prbr_1_3'" class="text-right block prbr-cell">
|
class="balance-table"
|
||||||
{{ formatCurrencyMap(props.row.bakiye_1_3_map) }}
|
>
|
||||||
</span>
|
<template #header="props">
|
||||||
<span v-else-if="staticMoneyFields.includes(col.name)" class="text-center block">
|
<q-tr :props="props" class="header-row">
|
||||||
{{ formatAmount(props.row[col.field]) }}
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
</span>
|
{{ col.label }}
|
||||||
<span v-else-if="col.name === 'hesap_alinmayan_gun'" class="text-right block">
|
</q-th>
|
||||||
-
|
</q-tr>
|
||||||
</span>
|
<q-tr class="totals-row">
|
||||||
<span v-else>
|
<q-th
|
||||||
{{ props.row[col.field] || '-' }}
|
v-for="col in props.cols"
|
||||||
</span>
|
:key="`tot-${col.name}`"
|
||||||
</q-td>
|
:class="col.align === 'right' ? 'text-right' : ''"
|
||||||
</q-tr>
|
>
|
||||||
|
{{ totalCellValue(col.name) }}
|
||||||
|
</q-th>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
<q-tr v-if="expanded[props.row.group_key]" class="detail-host-row">
|
<template #body="props">
|
||||||
<q-td colspan="100%">
|
<q-tr :props="props" class="sub-header-row">
|
||||||
<div class="detail-wrap">
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
<q-table
|
<q-btn
|
||||||
:rows="store.getDetailsByGroup(props.row.group_key)"
|
v-if="col.name === 'expand'"
|
||||||
:columns="detailColumns"
|
|
||||||
row-key="cari_kodu"
|
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
bordered
|
round
|
||||||
hide-bottom
|
size="sm"
|
||||||
:table-style="{ tableLayout: 'fixed', width: '100%' }"
|
:icon="expanded[props.row.group_key] ? 'expand_less' : 'expand_more'"
|
||||||
class="detail-table"
|
@click="toggleGroup(props.row.group_key)"
|
||||||
>
|
/>
|
||||||
<template #body-cell-prbr_1_2="scope">
|
<span v-else-if="col.name === 'prbr_1_2'" class="text-right block prbr-cell">
|
||||||
<q-td :props="scope" class="text-right prbr-cell">
|
{{ formatCurrencyMap(props.row.bakiye_1_2_map) }}
|
||||||
{{ formatRowPrBr(scope.row, '1_2') }}
|
</span>
|
||||||
</q-td>
|
<span v-else-if="col.name === 'prbr_1_3'" class="text-right block prbr-cell">
|
||||||
</template>
|
{{ formatCurrencyMap(props.row.bakiye_1_3_map) }}
|
||||||
<template #body-cell-prbr_1_3="scope">
|
</span>
|
||||||
<q-td :props="scope" class="text-right prbr-cell">
|
<span v-else-if="staticMoneyFields.includes(col.name)" class="text-center block">
|
||||||
{{ formatRowPrBr(scope.row, '1_3') }}
|
{{ formatAmount(props.row[col.field]) }}
|
||||||
</q-td>
|
</span>
|
||||||
</template>
|
<span v-else>{{ props.row[col.field] || '-' }}</span>
|
||||||
</q-table>
|
</q-td>
|
||||||
</div>
|
</q-tr>
|
||||||
</q-td>
|
|
||||||
</q-tr>
|
<q-tr v-if="expanded[props.row.group_key]" class="detail-host-row">
|
||||||
</template>
|
<q-td colspan="100%">
|
||||||
</q-table>
|
<div class="detail-wrap">
|
||||||
|
<q-table
|
||||||
|
:rows="store.getDetailsByGroup(props.row.group_key)"
|
||||||
|
:columns="detailColumns"
|
||||||
|
row-key="cari_kodu"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
bordered
|
||||||
|
hide-bottom
|
||||||
|
:table-style="{ tableLayout: 'fixed', width: '100%' }"
|
||||||
|
class="detail-table"
|
||||||
|
>
|
||||||
|
<template #body-cell-prbr_1_2="scope">
|
||||||
|
<q-td :props="scope" class="text-right prbr-cell">
|
||||||
|
{{ formatRowPrBr(scope.row, '1_2') }}
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-prbr_1_3="scope">
|
||||||
|
<q-td :props="scope" class="text-right prbr-cell">
|
||||||
|
{{ formatRowPrBr(scope.row, '1_3') }}
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</div>
|
||||||
|
</q-td>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
</div>
|
</div>
|
||||||
</q-page>
|
</q-page>
|
||||||
|
|
||||||
<q-page v-else class="q-pa-md flex flex-center">
|
<q-page v-else class="q-pa-md flex flex-center">
|
||||||
<div class="text-negative text-subtitle1">
|
<div class="text-negative text-subtitle1">
|
||||||
Bu module erisim yetkiniz yok.
|
Bu modüle erişim yetkiniz yok.
|
||||||
</div>
|
</div>
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
import { useQuasar } from 'quasar'
|
||||||
import { useCustomerBalanceListStore } from 'src/stores/customerBalanceListStore'
|
import { useCustomerBalanceListStore } from 'src/stores/customerBalanceListStore'
|
||||||
import { usePermission } from 'src/composables/usePermission'
|
import { usePermission } from 'src/composables/usePermission'
|
||||||
|
import { download, extractApiErrorDetail } from 'src/services/api'
|
||||||
|
|
||||||
const store = useCustomerBalanceListStore()
|
const store = useCustomerBalanceListStore()
|
||||||
const expanded = ref({})
|
const expanded = ref({})
|
||||||
const allDetailsOpen = ref(false)
|
const allDetailsOpen = ref(false)
|
||||||
|
const $q = useQuasar()
|
||||||
|
|
||||||
const { canRead } = usePermission()
|
const { canRead, canExport } = usePermission()
|
||||||
const canReadFinance = canRead('finance')
|
const canReadFinance = canRead('finance')
|
||||||
|
const canExportFinance = canExport('finance')
|
||||||
|
|
||||||
const islemTipiOptions = [
|
const islemTipiOptions = [
|
||||||
{ label: '1_2 Bakiye Pr.Br', value: 'prbr_1_2' },
|
{ label: '1_2 Bakiye Pr.Br', value: 'prbr_1_2' },
|
||||||
@@ -422,14 +534,49 @@ const islemTipiOptions = [
|
|||||||
{ label: '1_3 USD Bakiye', value: 'usd_1_3' },
|
{ label: '1_3 USD Bakiye', value: 'usd_1_3' },
|
||||||
{ label: '1_3 TRY Bakiye', value: 'try_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']
|
||||||
|
|
||||||
|
function toNumericSortValue (value) {
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return Number.isFinite(value) ? value : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const s = String(value ?? '').trim()
|
||||||
|
if (!s) return 0
|
||||||
|
|
||||||
|
const hasComma = s.includes(',')
|
||||||
|
const hasDot = s.includes('.')
|
||||||
|
|
||||||
|
let normalized = s.replace(/\s+/g, '')
|
||||||
|
|
||||||
|
if (hasComma && hasDot) {
|
||||||
|
const lastComma = normalized.lastIndexOf(',')
|
||||||
|
const lastDot = normalized.lastIndexOf('.')
|
||||||
|
if (lastComma > lastDot) {
|
||||||
|
normalized = normalized.replace(/\./g, '').replace(',', '.')
|
||||||
|
} else {
|
||||||
|
normalized = normalized.replace(/,/g, '')
|
||||||
|
}
|
||||||
|
} else if (hasComma) {
|
||||||
|
normalized = normalized.replace(/\./g, '').replace(',', '.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const n = Number.parseFloat(normalized)
|
||||||
|
return Number.isFinite(n) ? n : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortTextTr (a, b) {
|
||||||
|
return String(a ?? '').localeCompare(String(b ?? ''), 'tr', { sensitivity: 'base' })
|
||||||
|
}
|
||||||
|
|
||||||
const metricDefs = {
|
const metricDefs = {
|
||||||
prbr_1_2: { name: 'prbr_1_2', label: '1_2 Bakiye\nPr.Br', field: 'prbr_1_2', align: 'right', sortable: false },
|
prbr_1_2: { name: 'prbr_1_2', label: '1_2 Bakiye\nPr.Br', field: 'prbr_1_2', align: 'right', sortable: false },
|
||||||
prbr_1_3: { name: 'prbr_1_3', label: '1_3 Bakiye\nPr.Br', field: 'prbr_1_3', align: 'right', sortable: false },
|
prbr_1_3: { name: 'prbr_1_3', label: '1_3 Bakiye\nPr.Br', field: 'prbr_1_3', align: 'right', sortable: false },
|
||||||
usd_1_2: { name: 'usd_bakiye_1_2', label: '1_2 USD_BAKIYE', field: 'usd_bakiye_1_2', align: 'center', sortable: true },
|
usd_1_2: { name: 'usd_bakiye_1_2', label: '1_2 USD_BAKIYE', field: 'usd_bakiye_1_2', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) },
|
||||||
try_1_2: { name: 'tl_bakiye_1_2', label: '1_2 TRY_BAKIYE', field: 'tl_bakiye_1_2', align: 'center', sortable: true },
|
try_1_2: { name: 'tl_bakiye_1_2', label: '1_2 TRY_BAKIYE', field: 'tl_bakiye_1_2', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) },
|
||||||
usd_1_3: { name: 'usd_bakiye_1_3', label: '1_3 USD_BAKIYE', field: 'usd_bakiye_1_3', align: 'center', sortable: true },
|
usd_1_3: { name: 'usd_bakiye_1_3', label: '1_3 USD_BAKIYE', field: 'usd_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 }
|
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(() => {
|
||||||
@@ -440,15 +587,14 @@ const selectedMetricKeys = computed(() => {
|
|||||||
|
|
||||||
const summaryColumns = computed(() => ([
|
const summaryColumns = computed(() => ([
|
||||||
{ name: 'expand', label: '', field: 'expand', align: 'center', sortable: false },
|
{ name: 'expand', label: '', field: 'expand', align: 'center', sortable: false },
|
||||||
{ name: 'ana_cari_kodu', label: 'Ana Cari Kodu', field: 'ana_cari_kodu', align: 'left', sortable: true },
|
{ name: 'ana_cari_kodu', label: 'Ana Cari Kodu', field: 'ana_cari_kodu', align: 'left', sortable: true, sort: sortTextTr },
|
||||||
{ name: 'ana_cari_adi', label: 'Ana Cari Detay', field: 'ana_cari_adi', align: 'left', sortable: true },
|
{ name: 'ana_cari_adi', label: 'Ana Cari Detay', field: 'ana_cari_adi', align: 'left', sortable: true, sort: sortTextTr },
|
||||||
{ name: 'piyasa', label: 'Piyasa', field: 'piyasa', align: 'left', sortable: true },
|
{ name: 'piyasa', label: 'Piyasa', field: 'piyasa', align: 'left', sortable: true, sort: sortTextTr },
|
||||||
{ name: 'temsilci', label: 'Temsilci', field: 'temsilci', align: 'left', sortable: true },
|
{ name: 'temsilci', label: 'Temsilci', field: 'temsilci', align: 'left', sortable: true, sort: sortTextTr },
|
||||||
{ name: 'risk_durumu', label: 'Risk Durumu', field: 'risk_durumu', align: 'left', sortable: true },
|
{ name: 'risk_durumu', label: 'Risk Durumu', field: 'risk_durumu', align: 'left', sortable: true, sort: sortTextTr },
|
||||||
...selectedMetricKeys.value.map(k => metricDefs[k]),
|
...selectedMetricKeys.value.map((k) => metricDefs[k])
|
||||||
{ name: 'hesap_alinmayan_gun', label: 'Hesap Alınmayan Gün', field: 'hesap_alinmayan_gun', align: 'right', sortable: false },
|
|
||||||
{ name: 'kalan_fatura_ortalama_vade_tarihi', label: 'Kalan Fatura Ortalama Vade Tarihi', field: 'kalan_fatura_ortalama_vade_tarihi', align: 'left', sortable: true }
|
|
||||||
]))
|
]))
|
||||||
|
|
||||||
const liveTotals = computed(() => {
|
const liveTotals = computed(() => {
|
||||||
return store.filteredRows.reduce((acc, row) => {
|
return store.filteredRows.reduce((acc, row) => {
|
||||||
acc.usd_bakiye_1_2 += Number(row.usd_bakiye_1_2) || 0
|
acc.usd_bakiye_1_2 += Number(row.usd_bakiye_1_2) || 0
|
||||||
@@ -468,23 +614,36 @@ const detailColumns = computed(() => [
|
|||||||
{ name: 'cari_kodu', label: 'Cari Kodu', field: 'cari_kodu', align: 'left' },
|
{ name: 'cari_kodu', label: 'Cari Kodu', field: 'cari_kodu', align: 'left' },
|
||||||
{ name: 'cari_detay', label: 'Cari Detay', field: 'cari_detay', align: 'left' },
|
{ name: 'cari_detay', label: 'Cari Detay', field: 'cari_detay', align: 'left' },
|
||||||
{ name: 'sirket', label: 'Şirket', field: 'sirket', align: 'left' },
|
{ name: 'sirket', label: 'Şirket', field: 'sirket', align: 'left' },
|
||||||
|
{ name: 'sirket_detay', label: 'Şirket Detayı', field: 'sirket_detay', align: 'left' },
|
||||||
|
{ name: 'muhasebe_kodu', label: 'Muhasebe Kodu', field: 'muhasebe_kodu', align: 'left' },
|
||||||
{ name: 'piyasa', label: 'Piyasa', field: 'piyasa', align: 'left' },
|
{ name: 'piyasa', label: 'Piyasa', field: 'piyasa', align: 'left' },
|
||||||
{ name: 'temsilci', label: 'Temsilci', field: 'temsilci', align: 'left' },
|
{ name: 'temsilci', label: 'Temsilci', field: 'temsilci', align: 'left' },
|
||||||
{ name: 'ozellik03', label: 'Risk Durumu', field: 'ozellik03', align: 'left' },
|
{ name: 'risk_durumu', label: 'Risk Durumu', field: 'risk_durumu', align: 'left' },
|
||||||
{ name: 'ozellik05', label: 'Ülke', field: 'ozellik05', align: 'left' },
|
{ name: 'ozellik05', label: 'Ülke', field: 'ozellik05', align: 'left' },
|
||||||
{ name: 'ozellik06', label: 'Özellik06', field: 'ozellik06', align: 'left' },
|
{ name: 'il', label: 'İl', field: 'il', align: 'left' },
|
||||||
{ name: 'ozellik07', label: 'Özellik07', field: 'ozellik07', align: 'left' },
|
{ name: 'ilce', label: 'İlçe', field: 'ilce', align: 'left' },
|
||||||
{ name: 'cari_doviz', label: 'Döviz', field: 'cari_doviz', align: 'left' },
|
{ name: 'cari_doviz', label: 'Döviz', field: 'cari_doviz', align: 'left' },
|
||||||
...selectedMetricKeys.value.map(k => metricDefs[k]),
|
...selectedMetricKeys.value.map((k) => metricDefs[k])
|
||||||
{ name: 'hesap_alinmayan_gun', label: 'Hesap Alınmayan Gün', field: 'hesap_alinmayan_gun', align: 'right' },
|
|
||||||
{ name: 'kalan_fatura_ortalama_vade_tarihi', label: 'Kalan Fatura Ortalama Vade Tarihi', field: 'kalan_fatura_ortalama_vade_tarihi', align: 'left' }
|
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
function onReset () {
|
function onReset () {
|
||||||
store.resetFilters()
|
store.resetFilters()
|
||||||
store.applyCariSearch()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onToggle12Changed (val) {
|
||||||
|
if (val) {
|
||||||
|
store.filters.excludeZeroBalance13 = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onToggle13Changed (val) {
|
||||||
|
if (val) {
|
||||||
|
store.filters.excludeZeroBalance12 = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function toggleGroup (key) {
|
function toggleGroup (key) {
|
||||||
expanded.value[key] = !expanded.value[key]
|
expanded.value[key] = !expanded.value[key]
|
||||||
if (!expanded.value[key]) {
|
if (!expanded.value[key]) {
|
||||||
@@ -512,6 +671,97 @@ function toggleAllDetails () {
|
|||||||
expanded.value = {}
|
expanded.value = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function downloadCustomerBalancePDF (detailed) {
|
||||||
|
if (!canExportFinance.value) {
|
||||||
|
$q.notify({ type: 'negative', message: 'PDF export yetkiniz yok', position: 'top-right' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!store.hasFetched) {
|
||||||
|
$q.notify({ type: 'warning', message: 'Önce Bakiyeleri Getir ile veri yükleyin.', position: 'top-right' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
selected_date: store.filters.selectedDate,
|
||||||
|
cari_search: String(store.filters.cariSearch || '').trim(),
|
||||||
|
cari_ilk_grup: (store.filters.cariIlkGrup || []).join(','),
|
||||||
|
piyasa: (store.filters.piyasa || []).join(','),
|
||||||
|
temsilci: (store.filters.temsilci || []).join(','),
|
||||||
|
risk_durumu: (store.filters.riskDurumu || []).join(','),
|
||||||
|
islem_tipi: (store.filters.islemTipi || []).join(','),
|
||||||
|
ulke: (store.filters.ulke || []).join(','),
|
||||||
|
il: (store.filters.il || []).join(','),
|
||||||
|
ilce: (store.filters.ilce || []).join(','),
|
||||||
|
exclude_zero_12: store.filters.excludeZeroBalance12 ? '1' : '0',
|
||||||
|
exclude_zero_13: store.filters.excludeZeroBalance13 ? '1' : '0',
|
||||||
|
detailed: detailed ? '1' : '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await download('/finance/customer-balances/export-pdf', params)
|
||||||
|
const pdfUrl = window.URL.createObjectURL(new Blob([blob], { type: 'application/pdf' }))
|
||||||
|
window.open(pdfUrl, '_blank')
|
||||||
|
} catch (err) {
|
||||||
|
const detail = await extractApiErrorDetail(err?.original || err)
|
||||||
|
$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
message: detail || 'PDF oluşturulamadı',
|
||||||
|
position: 'top-right'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadCustomerBalanceExcel () {
|
||||||
|
if (!canExportFinance.value) {
|
||||||
|
$q.notify({ type: 'negative', message: 'Excel export yetkiniz yok', position: 'top-right' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!store.hasFetched) {
|
||||||
|
$q.notify({ type: 'warning', message: 'Önce Bakiyeleri Getir ile veri yükleyin.', position: 'top-right' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
selected_date: store.filters.selectedDate,
|
||||||
|
cari_search: String(store.filters.cariSearch || '').trim(),
|
||||||
|
cari_ilk_grup: (store.filters.cariIlkGrup || []).join(','),
|
||||||
|
piyasa: (store.filters.piyasa || []).join(','),
|
||||||
|
temsilci: (store.filters.temsilci || []).join(','),
|
||||||
|
risk_durumu: (store.filters.riskDurumu || []).join(','),
|
||||||
|
islem_tipi: (store.filters.islemTipi || []).join(','),
|
||||||
|
ulke: (store.filters.ulke || []).join(','),
|
||||||
|
il: (store.filters.il || []).join(','),
|
||||||
|
ilce: (store.filters.ilce || []).join(','),
|
||||||
|
exclude_zero_12: store.filters.excludeZeroBalance12 ? '1' : '0',
|
||||||
|
exclude_zero_13: store.filters.excludeZeroBalance13 ? '1' : '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = await download('/finance/customer-balances/export-excel', params)
|
||||||
|
const blob = new Blob(
|
||||||
|
[file],
|
||||||
|
{ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }
|
||||||
|
)
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = url
|
||||||
|
a.download = 'cari_bakiye_listesi.xlsx'
|
||||||
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
|
a.remove()
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
} catch (err) {
|
||||||
|
const detail = await extractApiErrorDetail(err?.original || err)
|
||||||
|
$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
message: detail || 'Excel oluşturulamadı',
|
||||||
|
position: 'top-right'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function formatAmount (value) {
|
function formatAmount (value) {
|
||||||
const n = Number(value || 0)
|
const n = Number(value || 0)
|
||||||
return new Intl.NumberFormat('tr-TR', {
|
return new Intl.NumberFormat('tr-TR', {
|
||||||
@@ -538,7 +788,6 @@ function totalCellValue (colName) {
|
|||||||
if (colName === 'tl_bakiye_1_2') return formatAmount(liveTotals.value.tl_bakiye_1_2)
|
if (colName === 'tl_bakiye_1_2') return formatAmount(liveTotals.value.tl_bakiye_1_2)
|
||||||
if (colName === 'usd_bakiye_1_3') return formatAmount(liveTotals.value.usd_bakiye_1_3)
|
if (colName === 'usd_bakiye_1_3') return formatAmount(liveTotals.value.usd_bakiye_1_3)
|
||||||
if (colName === 'tl_bakiye_1_3') return formatAmount(liveTotals.value.tl_bakiye_1_3)
|
if (colName === 'tl_bakiye_1_3') return formatAmount(liveTotals.value.tl_bakiye_1_3)
|
||||||
if (colName === 'hesap_alinmayan_gun') return '-'
|
|
||||||
return '-'
|
return '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,7 +813,7 @@ function formatCurrencyMap (mapObj) {
|
|||||||
if (!entries.length) return '-'
|
if (!entries.length) return '-'
|
||||||
return entries
|
return entries
|
||||||
.map(([curr, amount]) => `${curr}: ${formatAmount(amount)}`)
|
.map(([curr, amount]) => `${curr}: ${formatAmount(amount)}`)
|
||||||
.join(' | ')
|
.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatRowPrBr (row, tip) {
|
function formatRowPrBr (row, tip) {
|
||||||
@@ -577,7 +826,6 @@ function formatRowPrBr (row, tip) {
|
|||||||
if (amount === 0) return '-'
|
if (amount === 0) return '-'
|
||||||
return `${curr} ${formatAmount(amount)}`
|
return `${curr} ${formatAmount(amount)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -596,6 +844,12 @@ function formatRowPrBr (row, tip) {
|
|||||||
padding-bottom: 6px;
|
padding-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filters-panel {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
.compact-select :deep(.q-field__control) {
|
.compact-select :deep(.q-field__control) {
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
}
|
}
|
||||||
@@ -652,7 +906,7 @@ function formatRowPrBr (row, tip) {
|
|||||||
background: var(--q-primary);
|
background: var(--q-primary);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: 'Roboto', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-table :deep(.totals-row th) {
|
.balance-table :deep(.totals-row th) {
|
||||||
@@ -662,7 +916,7 @@ function formatRowPrBr (row, tip) {
|
|||||||
background: var(--q-secondary);
|
background: var(--q-secondary);
|
||||||
color: var(--q-dark);
|
color: var(--q-dark);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: 'Roboto', sans-serif;
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -701,7 +955,7 @@ function formatRowPrBr (row, tip) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.prbr-cell {
|
.prbr-cell {
|
||||||
white-space: normal;
|
white-space: pre-line;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
}
|
}
|
||||||
|
|||||||
13
ui/src/pages/CustomerBalanceListDummy.vue
Normal file
13
ui/src/pages/CustomerBalanceListDummy.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<q-page class="q-pa-md">
|
||||||
|
<q-card flat bordered class="q-pa-lg">
|
||||||
|
<div class="text-h6">Cari Bakiye Listesi</div>
|
||||||
|
<div class="text-subtitle2 q-mt-sm text-grey-7">
|
||||||
|
Dummy ekran hazır. Bu modülün filtre ve tablo kurgusu bir sonraki adımda eklenecek.
|
||||||
|
</div>
|
||||||
|
</q-card>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
</script>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-page v-if="canReadFinance" class="q-pa-md page-col statement-page">
|
<q-page v-if="canReadFinance" class="q-pa-md page-col statement-page">
|
||||||
|
|
||||||
<!-- 🔹 Cari Kod / İsim (sabit) -->
|
<!-- Cari Kod / İsim (sabit) -->
|
||||||
<div class="filter-sticky">
|
<div class="filter-sticky">
|
||||||
<q-select
|
<q-select
|
||||||
v-model="selectedCari"
|
v-model="selectedCari"
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 🔹 Filtre Alanı -->
|
<!-- Filtre Alanı -->
|
||||||
<div class="filter-collapsible">
|
<div class="filter-collapsible">
|
||||||
<div class="row items-center justify-between q-pa-sm bg-grey-2">
|
<div class="row items-center justify-between q-pa-sm bg-grey-2">
|
||||||
<div class="text-subtitle1">Filtreler</div>
|
<div class="text-subtitle1">Filtreler</div>
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
</q-slide-transition>
|
</q-slide-transition>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 🔹 Tablo Alanı -->
|
<!-- Tablo Alanı -->
|
||||||
<div class="table-scroll">
|
<div class="table-scroll">
|
||||||
|
|
||||||
<!-- Toggle butonları (sticky üst bar) -->
|
<!-- Toggle butonları (sticky üst bar) -->
|
||||||
@@ -438,7 +438,7 @@ function toggleRowDetails(row) {
|
|||||||
expandedRows.value[row.belge_no] = !expandedRows.value[row.belge_no]
|
expandedRows.value[row.belge_no] = !expandedRows.value[row.belge_no]
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 🔹 Tüm detayları aç/kapat */
|
/* Tüm detayları aç/kapat */
|
||||||
function toggleAllDetails() {
|
function toggleAllDetails() {
|
||||||
allDetailsOpen.value = !allDetailsOpen.value
|
allDetailsOpen.value = !allDetailsOpen.value
|
||||||
if (allDetailsOpen.value) {
|
if (allDetailsOpen.value) {
|
||||||
@@ -454,7 +454,7 @@ function toggleAllDetails() {
|
|||||||
function normalizeText (str) {
|
function normalizeText (str) {
|
||||||
return (str || '')
|
return (str || '')
|
||||||
.toString()
|
.toString()
|
||||||
.toLocaleLowerCase('tr-TR') // 🔥 Türkçe uyumlu
|
.toLocaleLowerCase('tr-TR') // Türkçe uyumlu
|
||||||
.normalize('NFD') // aksan temizleme
|
.normalize('NFD') // aksan temizleme
|
||||||
.replace(/[\u0300-\u036f]/g, '')
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
.trim()
|
.trim()
|
||||||
@@ -480,7 +480,7 @@ function formatAmount(n) {
|
|||||||
|
|
||||||
const filtersOpen = ref(true)
|
const filtersOpen = ref(true)
|
||||||
|
|
||||||
/* 🔹 Kolon gizle/göster */
|
/* Kolon gizle/göster */
|
||||||
const visibleColumns = ref([])
|
const visibleColumns = ref([])
|
||||||
const showLeftCols = ref(true)
|
const showLeftCols = ref(true)
|
||||||
|
|
||||||
@@ -501,7 +501,7 @@ function toggleLeftCols() {
|
|||||||
showLeftCols.value = !showLeftCols.value
|
showLeftCols.value = !showLeftCols.value
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 🔹 PDF İndirme Butonuna bağla */
|
/* PDF İndirme Butonuna bağla */
|
||||||
async function handleDownload() {
|
async function handleDownload() {
|
||||||
if (!canExportFinance.value) {
|
if (!canExportFinance.value) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
@@ -536,14 +536,14 @@ async function handleDownload() {
|
|||||||
selectedMonType.value // <-- eklendi (['1','2'] veya ['1','3'])
|
selectedMonType.value // <-- eklendi (['1','2'] veya ['1','3'])
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log("📤 [DEBUG] Store’dan gelen result:", result)
|
console.log("[DEBUG] Store’dan gelen result:", result)
|
||||||
|
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: result.ok ? 'positive' : 'negative',
|
type: result.ok ? 'positive' : 'negative',
|
||||||
message: result.message,
|
message: result.message,
|
||||||
position: 'top-right'
|
position: 'top-right'
|
||||||
})
|
})
|
||||||
}/* 🔹 Cari Hesap Ekstresi (2. seçenek) */
|
}/* Cari Hesap Ekstresi (2. seçenek) */
|
||||||
import { useDownloadstHeadStore } from 'src/stores/downloadstHeadStore'
|
import { useDownloadstHeadStore } from 'src/stores/downloadstHeadStore'
|
||||||
|
|
||||||
const downloadstHeadStore = useDownloadstHeadStore()
|
const downloadstHeadStore = useDownloadstHeadStore()
|
||||||
@@ -582,7 +582,7 @@ async function CurrheadDownload() {
|
|||||||
selectedMonType.value // parasal işlem tipi (parislemler)
|
selectedMonType.value // parasal işlem tipi (parislemler)
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log("📤 [DEBUG] CurrheadDownloadresult:", result)
|
console.log("[DEBUG] CurrheadDownloadresult:", result)
|
||||||
|
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: result.ok ? 'positive' : 'negative',
|
type: result.ok ? 'positive' : 'negative',
|
||||||
@@ -689,3 +689,4 @@ async function CurrheadDownload() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -140,6 +140,18 @@ const routes = [
|
|||||||
component: () => import('pages/CustomerBalanceList.vue'),
|
component: () => import('pages/CustomerBalanceList.vue'),
|
||||||
meta: { permission: 'finance:view' }
|
meta: { permission: 'finance:view' }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'account-aging-statement',
|
||||||
|
name: 'account-aging-statement',
|
||||||
|
component: () => import('pages/AccountAgingStatement.vue'),
|
||||||
|
meta: { permission: 'finance:view' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'aged-customer-balance-list',
|
||||||
|
name: 'aged-customer-balance-list',
|
||||||
|
component: () => import('pages/AgedCustomerBalanceListDummy.vue'),
|
||||||
|
meta: { permission: 'finance:view' }
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
/* ================= USERS ================= */
|
/* ================= USERS ================= */
|
||||||
|
|||||||
@@ -5,14 +5,17 @@ export const useCustomerBalanceListStore = defineStore('customerBalanceList', {
|
|||||||
state: () => ({
|
state: () => ({
|
||||||
filters: {
|
filters: {
|
||||||
selectedDate: new Date().toISOString().slice(0, 10),
|
selectedDate: new Date().toISOString().slice(0, 10),
|
||||||
|
excludeZeroBalance12: false,
|
||||||
|
excludeZeroBalance13: false,
|
||||||
cariSearch: '',
|
cariSearch: '',
|
||||||
appliedCariSearch: '',
|
|
||||||
cariIlkGrup: [],
|
cariIlkGrup: [],
|
||||||
piyasa: [],
|
piyasa: [],
|
||||||
temsilci: [],
|
temsilci: [],
|
||||||
riskDurumu: [],
|
riskDurumu: [],
|
||||||
islemTipi: [],
|
islemTipi: [],
|
||||||
ulke: []
|
ulke: [],
|
||||||
|
il: [],
|
||||||
|
ilce: []
|
||||||
},
|
},
|
||||||
rows: [],
|
rows: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
@@ -25,26 +28,47 @@ export const useCustomerBalanceListStore = defineStore('customerBalanceList', {
|
|||||||
cariIlkGrupOptions: (state) => uniqueOptions(state.rows, 'cari_ilk_grup'),
|
cariIlkGrupOptions: (state) => uniqueOptions(state.rows, 'cari_ilk_grup'),
|
||||||
piyasaOptions: (state) => uniqueOptions(state.rows, 'piyasa'),
|
piyasaOptions: (state) => uniqueOptions(state.rows, 'piyasa'),
|
||||||
temsilciOptions: (state) => uniqueOptions(state.rows, 'temsilci'),
|
temsilciOptions: (state) => uniqueOptions(state.rows, 'temsilci'),
|
||||||
riskDurumuOptions: (state) => uniqueOptions(state.rows, 'ozellik03'),
|
riskDurumuOptions: (state) => uniqueOptions(state.rows, 'risk_durumu'),
|
||||||
ulkeOptions: (state) => uniqueOptions(state.rows, 'ozellik05'),
|
ulkeOptions: (state) => uniqueOptions(state.rows, 'ozellik05'),
|
||||||
|
ilOptions: (state) => uniqueOptions(state.rows, 'il'),
|
||||||
|
ilceOptions: (state) => uniqueOptions(state.rows, 'ilce'),
|
||||||
|
|
||||||
filteredRows: (state) => {
|
filteredRows: (state) => {
|
||||||
|
const selectedCariIlkGrup = new Set((state.filters.cariIlkGrup || []).map(v => normalizeText(v)))
|
||||||
|
const selectedPiyasa = new Set((state.filters.piyasa || []).map(v => normalizeText(v)))
|
||||||
|
const selectedTemsilci = new Set((state.filters.temsilci || []).map(v => normalizeText(v)))
|
||||||
|
const selectedRiskDurumu = new Set((state.filters.riskDurumu || []).map(v => normalizeText(v)))
|
||||||
|
const selectedUlke = new Set((state.filters.ulke || []).map(v => normalizeText(v)))
|
||||||
|
const selectedIl = new Set((state.filters.il || []).map(v => normalizeText(v)))
|
||||||
|
const selectedIlce = new Set((state.filters.ilce || []).map(v => normalizeText(v)))
|
||||||
|
|
||||||
|
const matchMulti = (selectedSet, value) => {
|
||||||
|
if (!selectedSet.size) return true
|
||||||
|
const normalized = normalizeText(value)
|
||||||
|
if (!normalized) return true
|
||||||
|
return selectedSet.has(normalized)
|
||||||
|
}
|
||||||
|
|
||||||
return state.rows.filter((row) => {
|
return state.rows.filter((row) => {
|
||||||
|
const bak12 = Number(row.bakiye_1_2) || 0
|
||||||
|
const bak13 = Number(row.bakiye_1_3) || 0
|
||||||
|
const usd12 = Number(row.usd_bakiye_1_2) || 0
|
||||||
|
const try12 = Number(row.tl_bakiye_1_2) || 0
|
||||||
|
const usd13 = Number(row.usd_bakiye_1_3) || 0
|
||||||
|
const try13 = Number(row.tl_bakiye_1_3) || 0
|
||||||
|
const cariSearchNeedle = normalizeText(state.filters.cariSearch || '')
|
||||||
|
|
||||||
const cariIlkGrupOk =
|
const cariIlkGrupOk =
|
||||||
!state.filters.cariIlkGrup.length ||
|
matchMulti(selectedCariIlkGrup, row.cari_ilk_grup)
|
||||||
state.filters.cariIlkGrup.includes(row.cari_ilk_grup)
|
|
||||||
|
|
||||||
const piyasaOk =
|
const piyasaOk =
|
||||||
!state.filters.piyasa.length ||
|
matchMulti(selectedPiyasa, row.piyasa)
|
||||||
state.filters.piyasa.includes(row.piyasa)
|
|
||||||
|
|
||||||
const temsilciOk =
|
const temsilciOk =
|
||||||
!state.filters.temsilci.length ||
|
matchMulti(selectedTemsilci, row.temsilci)
|
||||||
state.filters.temsilci.includes(row.temsilci)
|
|
||||||
|
|
||||||
const riskDurumuOk =
|
const riskDurumuOk =
|
||||||
!state.filters.riskDurumu.length ||
|
matchMulti(selectedRiskDurumu, row.risk_durumu)
|
||||||
state.filters.riskDurumu.includes(row.ozellik03)
|
|
||||||
|
|
||||||
const cariText = normalizeText([
|
const cariText = normalizeText([
|
||||||
row.ana_cari_kodu || '',
|
row.ana_cari_kodu || '',
|
||||||
@@ -52,25 +76,22 @@ export const useCustomerBalanceListStore = defineStore('customerBalanceList', {
|
|||||||
row.cari_kodu || '',
|
row.cari_kodu || '',
|
||||||
row.cari_detay || ''
|
row.cari_detay || ''
|
||||||
].join(' '))
|
].join(' '))
|
||||||
const cariSearchNeedle = normalizeText(state.filters.appliedCariSearch || '')
|
|
||||||
const cariSearchOk =
|
const cariSearchOk =
|
||||||
!cariSearchNeedle ||
|
!cariSearchNeedle ||
|
||||||
cariText.includes(cariSearchNeedle)
|
cariText.includes(cariSearchNeedle)
|
||||||
|
|
||||||
const ulkeOk =
|
const ulkeOk =
|
||||||
!state.filters.ulke.length ||
|
matchMulti(selectedUlke, row.ozellik05)
|
||||||
state.filters.ulke.includes(row.ozellik05)
|
|
||||||
|
const ilOk =
|
||||||
|
matchMulti(selectedIl, row.il)
|
||||||
|
|
||||||
|
const ilceOk =
|
||||||
|
matchMulti(selectedIlce, row.ilce)
|
||||||
|
|
||||||
const islemTipiOk =
|
const islemTipiOk =
|
||||||
!state.filters.islemTipi.length ||
|
!state.filters.islemTipi.length ||
|
||||||
state.filters.islemTipi.some((t) => {
|
state.filters.islemTipi.some((t) => {
|
||||||
const bak12 = Number(row.bakiye_1_2) || 0
|
|
||||||
const bak13 = Number(row.bakiye_1_3) || 0
|
|
||||||
const usd12 = Number(row.usd_bakiye_1_2) || 0
|
|
||||||
const try12 = Number(row.tl_bakiye_1_2) || 0
|
|
||||||
const usd13 = Number(row.usd_bakiye_1_3) || 0
|
|
||||||
const try13 = Number(row.tl_bakiye_1_3) || 0
|
|
||||||
|
|
||||||
if (t === 'prbr_1_2') return bak12 !== 0
|
if (t === 'prbr_1_2') return bak12 !== 0
|
||||||
if (t === 'prbr_1_3') return bak13 !== 0
|
if (t === 'prbr_1_3') return bak13 !== 0
|
||||||
if (t === 'usd_1_2') return usd12 !== 0
|
if (t === 'usd_1_2') return usd12 !== 0
|
||||||
@@ -80,7 +101,12 @@ export const useCustomerBalanceListStore = defineStore('customerBalanceList', {
|
|||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
return cariIlkGrupOk && piyasaOk && temsilciOk && riskDurumuOk && cariSearchOk && ulkeOk && islemTipiOk
|
const excludeZero12Ok = !state.filters.excludeZeroBalance12 || bak12 !== 0
|
||||||
|
const excludeZero13Ok = !state.filters.excludeZeroBalance13 || bak13 !== 0
|
||||||
|
|
||||||
|
return cariIlkGrupOk && piyasaOk && temsilciOk && riskDurumuOk &&
|
||||||
|
cariSearchOk && ulkeOk && ilOk && ilceOk && islemTipiOk &&
|
||||||
|
excludeZero12Ok && excludeZero13Ok
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -88,10 +114,10 @@ export const useCustomerBalanceListStore = defineStore('customerBalanceList', {
|
|||||||
const grouped = new Map()
|
const grouped = new Map()
|
||||||
|
|
||||||
for (const row of this.filteredRows) {
|
for (const row of this.filteredRows) {
|
||||||
const key = `${row.ana_cari_kodu || ''}||${row.ana_cari_adi || ''}`
|
const key = String(row.ana_cari_kodu || '').trim()
|
||||||
const current = grouped.get(key) || {
|
const current = grouped.get(key) || {
|
||||||
group_key: key,
|
group_key: key,
|
||||||
ana_cari_kodu: row.ana_cari_kodu || '',
|
ana_cari_kodu: key,
|
||||||
ana_cari_adi: row.ana_cari_adi || '',
|
ana_cari_adi: row.ana_cari_adi || '',
|
||||||
piyasa: '',
|
piyasa: '',
|
||||||
piyasa_set: new Set(),
|
piyasa_set: new Set(),
|
||||||
@@ -104,8 +130,7 @@ export const useCustomerBalanceListStore = defineStore('customerBalanceList', {
|
|||||||
usd_bakiye_1_2: 0,
|
usd_bakiye_1_2: 0,
|
||||||
tl_bakiye_1_2: 0,
|
tl_bakiye_1_2: 0,
|
||||||
usd_bakiye_1_3: 0,
|
usd_bakiye_1_3: 0,
|
||||||
tl_bakiye_1_3: 0,
|
tl_bakiye_1_3: 0
|
||||||
kalan_fatura_ortalama_vade_tarihi: ''
|
|
||||||
}
|
}
|
||||||
|
|
||||||
current.usd_bakiye_1_2 += Number(row.usd_bakiye_1_2) || 0
|
current.usd_bakiye_1_2 += Number(row.usd_bakiye_1_2) || 0
|
||||||
@@ -113,6 +138,10 @@ export const useCustomerBalanceListStore = defineStore('customerBalanceList', {
|
|||||||
current.usd_bakiye_1_3 += Number(row.usd_bakiye_1_3) || 0
|
current.usd_bakiye_1_3 += Number(row.usd_bakiye_1_3) || 0
|
||||||
current.tl_bakiye_1_3 += Number(row.tl_bakiye_1_3) || 0
|
current.tl_bakiye_1_3 += Number(row.tl_bakiye_1_3) || 0
|
||||||
|
|
||||||
|
if (!String(current.ana_cari_adi || '').trim() && String(row.ana_cari_adi || '').trim()) {
|
||||||
|
current.ana_cari_adi = row.ana_cari_adi
|
||||||
|
}
|
||||||
|
|
||||||
const curr = String(row.cari_doviz || '').trim().toUpperCase() || 'N/A'
|
const curr = String(row.cari_doviz || '').trim().toUpperCase() || 'N/A'
|
||||||
current.bakiye_1_2_map[curr] =
|
current.bakiye_1_2_map[curr] =
|
||||||
(Number(current.bakiye_1_2_map[curr]) || 0) + (Number(row.bakiye_1_2) || 0)
|
(Number(current.bakiye_1_2_map[curr]) || 0) + (Number(row.bakiye_1_2) || 0)
|
||||||
@@ -125,14 +154,7 @@ export const useCustomerBalanceListStore = defineStore('customerBalanceList', {
|
|||||||
const temsilci = String(row.temsilci || '').trim()
|
const temsilci = String(row.temsilci || '').trim()
|
||||||
if (temsilci) current.temsilci_set.add(temsilci)
|
if (temsilci) current.temsilci_set.add(temsilci)
|
||||||
|
|
||||||
if (
|
const risk = String(row.risk_durumu || row.ozellik03 || '').trim()
|
||||||
!current.kalan_fatura_ortalama_vade_tarihi &&
|
|
||||||
row.kalan_fatura_ortalama_vade_tarihi
|
|
||||||
) {
|
|
||||||
current.kalan_fatura_ortalama_vade_tarihi = row.kalan_fatura_ortalama_vade_tarihi
|
|
||||||
}
|
|
||||||
|
|
||||||
const risk = String(row.ozellik03 || '').trim()
|
|
||||||
if (risk) current.risk_set.add(risk)
|
if (risk) current.risk_set.add(risk)
|
||||||
|
|
||||||
const riskValues = Array.from(current.risk_set)
|
const riskValues = Array.from(current.risk_set)
|
||||||
@@ -172,7 +194,7 @@ 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.appliedCariSearch || this.filters.cariSearch || '').trim()
|
cari_search: String(this.filters.cariSearch || '').trim()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.rows = Array.isArray(data) ? data : []
|
this.rows = Array.isArray(data) ? data : []
|
||||||
@@ -195,26 +217,25 @@ export const useCustomerBalanceListStore = defineStore('customerBalanceList', {
|
|||||||
|
|
||||||
getDetailsByGroup (groupKey) {
|
getDetailsByGroup (groupKey) {
|
||||||
return this.filteredRows.filter(r =>
|
return this.filteredRows.filter(r =>
|
||||||
`${r.ana_cari_kodu || ''}||${r.ana_cari_adi || ''}` === groupKey
|
String(r.ana_cari_kodu || '').trim() === String(groupKey || '').trim()
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
resetFilters () {
|
resetFilters () {
|
||||||
|
this.filters.excludeZeroBalance12 = false
|
||||||
|
this.filters.excludeZeroBalance13 = false
|
||||||
this.filters.cariSearch = ''
|
this.filters.cariSearch = ''
|
||||||
this.filters.appliedCariSearch = ''
|
|
||||||
this.filters.cariIlkGrup = []
|
this.filters.cariIlkGrup = []
|
||||||
this.filters.piyasa = []
|
this.filters.piyasa = []
|
||||||
this.filters.temsilci = []
|
this.filters.temsilci = []
|
||||||
this.filters.riskDurumu = []
|
this.filters.riskDurumu = []
|
||||||
this.filters.islemTipi = []
|
this.filters.islemTipi = []
|
||||||
this.filters.ulke = []
|
this.filters.ulke = []
|
||||||
|
this.filters.il = []
|
||||||
|
this.filters.ilce = []
|
||||||
this.defaultsInitialized = false
|
this.defaultsInitialized = false
|
||||||
},
|
},
|
||||||
|
|
||||||
applyCariSearch () {
|
|
||||||
this.filters.appliedCariSearch = String(this.filters.cariSearch || '').trim()
|
|
||||||
},
|
|
||||||
|
|
||||||
selectAll (field, options) {
|
selectAll (field, options) {
|
||||||
this.filters[field] = options.map(o => o.value)
|
this.filters[field] = options.map(o => o.value)
|
||||||
},
|
},
|
||||||
@@ -224,10 +245,14 @@ export const useCustomerBalanceListStore = defineStore('customerBalanceList', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
applyInitialFilterDefaults () {
|
applyInitialFilterDefaults () {
|
||||||
const transferKey = normalizeText('transfer')
|
const excludedCariIlkGrup = new Set([
|
||||||
|
normalizeText('transfer'),
|
||||||
|
normalizeText('perakende'),
|
||||||
|
normalizeText('dtf')
|
||||||
|
])
|
||||||
this.filters.cariIlkGrup = this.cariIlkGrupOptions
|
this.filters.cariIlkGrup = this.cariIlkGrupOptions
|
||||||
.map(o => o.value)
|
.map(o => o.value)
|
||||||
.filter(v => normalizeText(v) !== transferKey)
|
.filter(v => !excludedCariIlkGrup.has(normalizeText(v)))
|
||||||
|
|
||||||
const excludedRisk = new Set([
|
const excludedRisk = new Set([
|
||||||
normalizeText('avukat'),
|
normalizeText('avukat'),
|
||||||
@@ -260,3 +285,4 @@ function normalizeText (str) {
|
|||||||
.replace(/[\u0300-\u036f]/g, '')
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
.trim()
|
.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
222
ui/src/stores/statementAgingStore.js
Normal file
222
ui/src/stores/statementAgingStore.js
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import api from 'src/services/api'
|
||||||
|
import qs from 'qs'
|
||||||
|
|
||||||
|
export const useStatementAgingStore = defineStore('statementAging', {
|
||||||
|
state: () => ({
|
||||||
|
rows: [],
|
||||||
|
masterRows: [],
|
||||||
|
currencyRowsByMaster: {},
|
||||||
|
detailByCurrency: {},
|
||||||
|
loading: false
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
async load(params = {}) {
|
||||||
|
this.loading = true
|
||||||
|
try {
|
||||||
|
const { data } = await api.get('/finance/account-aging-statement', {
|
||||||
|
params,
|
||||||
|
paramsSerializer: p => qs.stringify(p, { arrayFormat: 'repeat' })
|
||||||
|
})
|
||||||
|
|
||||||
|
const base = Array.isArray(data) ? data.map(normalizeRowKeys) : []
|
||||||
|
this.rows = base.map((r, idx) => ({
|
||||||
|
...r,
|
||||||
|
detail_key: `${idx}-${r.cari8 || ''}-${r.doc_currency_code || ''}-${r.fatura_ref || ''}-${r.odeme_ref || ''}`
|
||||||
|
}))
|
||||||
|
|
||||||
|
this.rebuildHierarchical()
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Aging statement load failed:', err)
|
||||||
|
this.reset()
|
||||||
|
throw err
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
rebuildHierarchical() {
|
||||||
|
const masterMap = {}
|
||||||
|
const currencyMap = {}
|
||||||
|
const detailMap = {}
|
||||||
|
|
||||||
|
for (const row of this.rows) {
|
||||||
|
const cari8 = String(row?.cari8 || '').trim()
|
||||||
|
const curr = String(row?.doc_currency_code || '').trim().toUpperCase() || 'N/A'
|
||||||
|
if (!cari8) continue
|
||||||
|
|
||||||
|
const masterKey = cari8
|
||||||
|
const currencyKey = `${cari8}|${curr}`
|
||||||
|
const tutar = Number(row?.eslesen_tutar) || 0
|
||||||
|
const usd = Number(row?.usd_tutar) || 0
|
||||||
|
const trY = Number(row?.try_tutar) || 0
|
||||||
|
const absTry = Math.abs(trY)
|
||||||
|
const gun = Number(row?.gun_sayisi) || 0
|
||||||
|
const gunDoc = Number(row?.gun_sayisi_docdate) || 0
|
||||||
|
const aciklama = String(row?.aciklama || '').toUpperCase()
|
||||||
|
const isAcik = aciklama === 'ACIKKALEM'
|
||||||
|
|
||||||
|
if (!masterMap[masterKey]) {
|
||||||
|
masterMap[masterKey] = {
|
||||||
|
group_key: masterKey,
|
||||||
|
cari8: masterKey,
|
||||||
|
cari_detay: String(row?.cari_detay || '').trim(),
|
||||||
|
acik_kalem_tutari_usd: 0,
|
||||||
|
acik_kalem_tutari_try: 0,
|
||||||
|
acik_kalem_ort_vade_gun: 0,
|
||||||
|
acik_kalem_ort_belge_gun: 0,
|
||||||
|
normal_usd_tutar: 0,
|
||||||
|
normal_try_tutar: 0,
|
||||||
|
ortalama_vade_gun: 0,
|
||||||
|
ortalama_belge_gun: 0,
|
||||||
|
weighted_all_base: 0,
|
||||||
|
weighted_all_gun_sum: 0,
|
||||||
|
weighted_all_doc_sum: 0,
|
||||||
|
weighted_open_base: 0,
|
||||||
|
weighted_open_gun_sum: 0,
|
||||||
|
weighted_open_doc_sum: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currencyMap[currencyKey]) {
|
||||||
|
currencyMap[currencyKey] = {
|
||||||
|
group_key: currencyKey,
|
||||||
|
master_key: masterKey,
|
||||||
|
cari8,
|
||||||
|
cari_detay: String(row?.cari_detay || '').trim(),
|
||||||
|
doviz_cinsi: curr,
|
||||||
|
acik_kalem_tutari: 0,
|
||||||
|
acik_kalem_usd: 0,
|
||||||
|
acik_kalem_try: 0,
|
||||||
|
ort_gun: 0,
|
||||||
|
ort_belge_gun: 0,
|
||||||
|
weighted_open_base: 0,
|
||||||
|
weighted_open_gun_sum: 0,
|
||||||
|
weighted_open_doc_sum: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const m = masterMap[masterKey]
|
||||||
|
const c = currencyMap[currencyKey]
|
||||||
|
|
||||||
|
if (isAcik) {
|
||||||
|
m.acik_kalem_tutari_usd += usd
|
||||||
|
m.acik_kalem_tutari_try += trY
|
||||||
|
c.acik_kalem_tutari += tutar
|
||||||
|
c.acik_kalem_usd += usd
|
||||||
|
c.acik_kalem_try += trY
|
||||||
|
} else {
|
||||||
|
m.normal_usd_tutar += usd
|
||||||
|
m.normal_try_tutar += trY
|
||||||
|
}
|
||||||
|
|
||||||
|
if (absTry > 0) {
|
||||||
|
m.weighted_all_base += absTry
|
||||||
|
m.weighted_all_gun_sum += absTry * gun
|
||||||
|
m.weighted_all_doc_sum += absTry * gunDoc
|
||||||
|
|
||||||
|
if (isAcik) {
|
||||||
|
m.weighted_open_base += absTry
|
||||||
|
m.weighted_open_gun_sum += absTry * gun
|
||||||
|
m.weighted_open_doc_sum += absTry * gunDoc
|
||||||
|
|
||||||
|
c.weighted_open_base += absTry
|
||||||
|
c.weighted_open_gun_sum += absTry * gun
|
||||||
|
c.weighted_open_doc_sum += absTry * gunDoc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!detailMap[currencyKey]) detailMap[currencyKey] = []
|
||||||
|
detailMap[currencyKey].push(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.masterRows = Object.values(masterMap)
|
||||||
|
.map((m) => ({
|
||||||
|
...m,
|
||||||
|
acik_kalem_ort_vade_gun: m.weighted_open_base > 0 ? ceilDay(m.weighted_open_gun_sum / m.weighted_open_base) : 0,
|
||||||
|
acik_kalem_ort_belge_gun: m.weighted_open_base > 0 ? ceilDay(m.weighted_open_doc_sum / m.weighted_open_base) : 0,
|
||||||
|
ortalama_vade_gun: m.weighted_all_base > 0 ? ceilDay(m.weighted_all_gun_sum / m.weighted_all_base) : 0,
|
||||||
|
ortalama_belge_gun: m.weighted_all_base > 0 ? ceilDay(m.weighted_all_doc_sum / m.weighted_all_base) : 0
|
||||||
|
}))
|
||||||
|
.sort((a, b) => String(a.cari8).localeCompare(String(b.cari8), 'tr', { sensitivity: 'base' }))
|
||||||
|
|
||||||
|
const currencyByMaster = {}
|
||||||
|
for (const c of Object.values(currencyMap)) {
|
||||||
|
const row = {
|
||||||
|
...c,
|
||||||
|
ort_gun: c.weighted_open_base > 0 ? ceilDay(c.weighted_open_gun_sum / c.weighted_open_base) : 0,
|
||||||
|
ort_belge_gun: c.weighted_open_base > 0 ? ceilDay(c.weighted_open_doc_sum / c.weighted_open_base) : 0
|
||||||
|
}
|
||||||
|
if (!currencyByMaster[row.master_key]) currencyByMaster[row.master_key] = []
|
||||||
|
currencyByMaster[row.master_key].push(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of Object.keys(currencyByMaster)) {
|
||||||
|
currencyByMaster[key].sort((a, b) => String(a.doviz_cinsi).localeCompare(String(b.doviz_cinsi), 'en', { sensitivity: 'base' }))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currencyRowsByMaster = currencyByMaster
|
||||||
|
for (const key of Object.keys(detailMap)) {
|
||||||
|
detailMap[key].sort((a, b) => {
|
||||||
|
const aEmpty = !a?.odeme_tarihi
|
||||||
|
const bEmpty = !b?.odeme_tarihi
|
||||||
|
if (aEmpty && !bEmpty) return -1
|
||||||
|
if (!aEmpty && bEmpty) return 1
|
||||||
|
if (aEmpty && bEmpty) return 0
|
||||||
|
const aTs = Date.parse(a.odeme_tarihi)
|
||||||
|
const bTs = Date.parse(b.odeme_tarihi)
|
||||||
|
const aNum = Number.isFinite(aTs) ? aTs : -Infinity
|
||||||
|
const bNum = Number.isFinite(bTs) ? bTs : -Infinity
|
||||||
|
return bNum - aNum
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.detailByCurrency = detailMap
|
||||||
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.rows = []
|
||||||
|
this.masterRows = []
|
||||||
|
this.currencyRowsByMaster = {}
|
||||||
|
this.detailByCurrency = {}
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
|
|
||||||
|
getCurrenciesByMaster(masterKey) {
|
||||||
|
return this.currencyRowsByMaster[String(masterKey || '').trim()] || []
|
||||||
|
},
|
||||||
|
|
||||||
|
getDetailsByCurrency(currencyKey) {
|
||||||
|
return this.detailByCurrency[String(currencyKey || '').trim()] || []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function normalizeRowKeys(row) {
|
||||||
|
if (!row || typeof row !== 'object') return {}
|
||||||
|
return {
|
||||||
|
cari8: row.Cari8 ?? row.cari8 ?? null,
|
||||||
|
cari_detay: row.CariDetay ?? row.cari_detay ?? null,
|
||||||
|
fatura_cari: row.FaturaCari ?? row.fatura_cari ?? null,
|
||||||
|
odeme_cari: row.OdemeCari ?? row.odeme_cari ?? null,
|
||||||
|
fatura_ref: row.FaturaRef ?? row.fatura_ref ?? null,
|
||||||
|
odeme_ref: row.OdemeRef ?? row.odeme_ref ?? null,
|
||||||
|
fatura_tarihi: row.FaturaTarihi ?? row.fatura_tarihi ?? null,
|
||||||
|
odeme_tarihi: row.OdemeTarihi ?? row.odeme_tarihi ?? null,
|
||||||
|
odeme_doc_date: row.OdemeDocDate ?? row.odeme_doc_date ?? null,
|
||||||
|
eslesen_tutar: Number(row.EslesenTutar ?? row.eslesen_tutar ?? 0),
|
||||||
|
usd_tutar: Number(row.UsdTutar ?? row.usd_tutar ?? 0),
|
||||||
|
try_tutar: Number(row.TryTutar ?? row.try_tutar ?? 0),
|
||||||
|
gun_sayisi: Number(row.GunSayisi ?? row.gun_sayisi ?? 0),
|
||||||
|
gun_sayisi_docdate: Number(row.GunSayisi_DocDate ?? row.gun_sayisi_docdate ?? 0),
|
||||||
|
gun_kur: Number(row.GunKur ?? row.gun_kur ?? 0),
|
||||||
|
aciklama: row.Aciklama ?? row.aciklama ?? null,
|
||||||
|
doc_currency_code: row.DocCurrencyCode ?? row.doc_currency_code ?? null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ceilDay(value) {
|
||||||
|
const n = Number(value)
|
||||||
|
if (!Number.isFinite(n)) return 0
|
||||||
|
return Math.ceil(n)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user