Merge remote-tracking branch 'origin/master'
This commit is contained in:
18
scripts/sql/setup_statement_aging_cache_pipeline.sql
Normal file
18
scripts/sql/setup_statement_aging_cache_pipeline.sql
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
USE [BAGGI_V3]
|
||||||
|
GO
|
||||||
|
|
||||||
|
SET ANSI_NULLS ON
|
||||||
|
GO
|
||||||
|
SET QUOTED_IDENTIFIER ON
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE OR ALTER PROCEDURE [dbo].[SP_BUILD_STATEMENT_AGING_PIPELINE]
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SET XACT_ABORT ON;
|
||||||
|
|
||||||
|
EXEC dbo.SP_BUILD_CARI_VADE_GUN_STAGING;
|
||||||
|
EXEC dbo.SP_BUILD_CARI_BAKIYE_CACHE;
|
||||||
|
END
|
||||||
|
GO
|
||||||
12
svc/main.go
12
svc/main.go
@@ -449,12 +449,24 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
|
|||||||
wrapV3(http.HandlerFunc(routes.GetStatementAgingHandler)),
|
wrapV3(http.HandlerFunc(routes.GetStatementAgingHandler)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/finance/account-aging-statement/rebuild-cache", "POST",
|
||||||
|
"finance", "update",
|
||||||
|
wrapV3(http.HandlerFunc(routes.RebuildStatementAgingCacheHandler)),
|
||||||
|
)
|
||||||
|
|
||||||
bindV3(r, pgDB,
|
bindV3(r, pgDB,
|
||||||
"/api/finance/account-aging-statement/export-pdf", "GET",
|
"/api/finance/account-aging-statement/export-pdf", "GET",
|
||||||
"finance", "export",
|
"finance", "export",
|
||||||
wrapV3(routes.ExportStatementAgingPDFHandler(mssql)),
|
wrapV3(routes.ExportStatementAgingPDFHandler(mssql)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/finance/account-aging-statement/export-excel", "GET",
|
||||||
|
"finance", "export",
|
||||||
|
wrapV3(routes.ExportStatementAgingExcelHandler(mssql)),
|
||||||
|
)
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// REPORT (STATEMENTS)
|
// REPORT (STATEMENTS)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
@@ -41,4 +41,6 @@ type CustomerBalanceListRow struct {
|
|||||||
Bakiye13 float64 `json:"bakiye_1_3"`
|
Bakiye13 float64 `json:"bakiye_1_3"`
|
||||||
TLBakiye13 float64 `json:"tl_bakiye_1_3"`
|
TLBakiye13 float64 `json:"tl_bakiye_1_3"`
|
||||||
USDBakiye13 float64 `json:"usd_bakiye_1_3"`
|
USDBakiye13 float64 `json:"usd_bakiye_1_3"`
|
||||||
|
VadeGun float64 `json:"vade_gun"`
|
||||||
|
VadeBelgeGun float64 `json:"vade_belge_tarihi_gun"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ type mkCariBakiyeLine struct {
|
|||||||
PislemTipi string
|
PislemTipi string
|
||||||
YerelBakiye float64
|
YerelBakiye float64
|
||||||
Bakiye float64
|
Bakiye float64
|
||||||
|
VadeGun float64
|
||||||
|
VadeBelgeGun float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type cariMeta struct {
|
type cariMeta struct {
|
||||||
@@ -320,7 +322,9 @@ func loadBalanceLines(ctx context.Context, selectedDate, cariSearch string) ([]m
|
|||||||
SirketKodu,
|
SirketKodu,
|
||||||
PislemTipi,
|
PislemTipi,
|
||||||
YerelBakiye,
|
YerelBakiye,
|
||||||
Bakiye
|
Bakiye,
|
||||||
|
CAST(0 AS DECIMAL(18,4)) AS Vade_Gun,
|
||||||
|
CAST(0 AS DECIMAL(18,4)) AS Vade_BelgeTarihi_Gun
|
||||||
FROM dbo.MK_CARI_BAKIYE_LIST(@SonTarih)
|
FROM dbo.MK_CARI_BAKIYE_LIST(@SonTarih)
|
||||||
WHERE (@CariSearch = '' OR CariKodu LIKE '%' + @CariSearch + '%')
|
WHERE (@CariSearch = '' OR CariKodu LIKE '%' + @CariSearch + '%')
|
||||||
`
|
`
|
||||||
@@ -345,6 +349,8 @@ func loadBalanceLines(ctx context.Context, selectedDate, cariSearch string) ([]m
|
|||||||
&r.PislemTipi,
|
&r.PislemTipi,
|
||||||
&r.YerelBakiye,
|
&r.YerelBakiye,
|
||||||
&r.Bakiye,
|
&r.Bakiye,
|
||||||
|
&r.VadeGun,
|
||||||
|
&r.VadeBelgeGun,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,30 +15,59 @@ import (
|
|||||||
|
|
||||||
func GetStatementAging(params models.StatementAgingParams) ([]map[string]interface{}, error) {
|
func GetStatementAging(params models.StatementAgingParams) ([]map[string]interface{}, error) {
|
||||||
accountCode := normalizeMasterAccountCode(params.AccountCode)
|
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)
|
useType2, useType3 := resolveUseTypes(params.Parislemler)
|
||||||
endDate, _ := time.Parse("2006-01-02", strings.TrimSpace(params.EndDate))
|
endDateText := strings.TrimSpace(params.EndDate)
|
||||||
|
if endDateText == "" {
|
||||||
|
endDateText = time.Now().Format("2006-01-02")
|
||||||
|
}
|
||||||
|
endDate, _ := time.Parse("2006-01-02", endDateText)
|
||||||
|
|
||||||
|
cariFilter := ""
|
||||||
|
if strings.TrimSpace(accountCode) != "" {
|
||||||
|
cariFilter = strings.TrimSpace(accountCode)
|
||||||
|
}
|
||||||
|
|
||||||
rows, err := db.MssqlDB.Query(`
|
rows, err := db.MssqlDB.Query(`
|
||||||
EXEC dbo.SP_FIFO_MATCH_FINAL
|
SELECT TOP (100)
|
||||||
@Cari8 = @Cari8,
|
Cari8 = LEFT(LTRIM(RTRIM(CariKodu)), 8),
|
||||||
@SonTarih = @SonTarih,
|
CariDetay = LTRIM(RTRIM(CariKodu)),
|
||||||
@UseType2 = @UseType2,
|
FaturaCari = LTRIM(RTRIM(CariKodu)),
|
||||||
@UseType3 = @UseType3;
|
OdemeCari = LTRIM(RTRIM(CariKodu)),
|
||||||
|
FaturaRef = CAST(NULL AS NVARCHAR(50)),
|
||||||
|
OdemeRef = CAST(NULL AS NVARCHAR(50)),
|
||||||
|
FaturaTarihi = CAST(NULL AS DATE),
|
||||||
|
OdemeTarihi = CAST(NULL AS DATE),
|
||||||
|
OdemeDocDate = CAST(NULL AS DATE),
|
||||||
|
EslesenTutar = CAST(Bakiye AS DECIMAL(18,2)),
|
||||||
|
GunSayisi = CAST(Vade_Gun AS DECIMAL(18,2)),
|
||||||
|
GunSayisi_DocDate = CAST(Vade_BelgeTarihi_Gun AS DECIMAL(18,2)),
|
||||||
|
Aciklama = CAST('AcikKalem' AS NVARCHAR(30)),
|
||||||
|
DocCurrencyCode = LTRIM(RTRIM(CariDoviz)),
|
||||||
|
PislemTipi,
|
||||||
|
SirketKodu,
|
||||||
|
CurrAccTypeCode,
|
||||||
|
Bakiye,
|
||||||
|
Vade_Gun,
|
||||||
|
Vade_BelgeTarihi_Gun,
|
||||||
|
SonTarih,
|
||||||
|
HesaplamaTarihi
|
||||||
|
FROM dbo.CARI_BAKIYE_GUN_CACHE
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(@UseType2 = 1 AND PislemTipi = '1_2')
|
||||||
|
OR
|
||||||
|
(@UseType3 = 1 AND PislemTipi = '1_3')
|
||||||
|
)
|
||||||
|
AND (@CariFilter = '' OR LTRIM(RTRIM(CariKodu)) LIKE @CariFilter + '%')
|
||||||
|
ORDER BY CariKodu, CariDoviz, PislemTipi;
|
||||||
`,
|
`,
|
||||||
sql.Named("Cari8", accountCode),
|
|
||||||
sql.Named("SonTarih", params.EndDate),
|
|
||||||
sql.Named("UseType2", useType2),
|
sql.Named("UseType2", useType2),
|
||||||
sql.Named("UseType3", useType3),
|
sql.Named("UseType3", useType3),
|
||||||
|
sql.Named("CariFilter", cariFilter),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("SP_FIFO_MATCH_FINAL query error: %w", err)
|
return nil, fmt.Errorf("CARI_BAKIYE_GUN_CACHE query error: %w", err)
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
|
|||||||
244
svc/queries/statement_aging_balance_list.go
Normal file
244
svc/queries/statement_aging_balance_list.go
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
package queries
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bssapp-backend/models"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"bssapp-backend/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetStatementAgingBalanceList(ctx context.Context, params models.CustomerBalanceListParams) ([]models.CustomerBalanceListRow, error) {
|
||||||
|
selectedDate := strings.TrimSpace(params.SelectedDate)
|
||||||
|
if selectedDate == "" {
|
||||||
|
selectedDate = time.Now().Format("2006-01-02")
|
||||||
|
}
|
||||||
|
|
||||||
|
lines, err := loadAgingBalanceLines(ctx, strings.TrimSpace(params.CariSearch))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
metaMap, err := loadCariMetaMap(ctx, lines)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("statement_aging_balance: cari meta query failed, fallback without meta: %v", err)
|
||||||
|
metaMap = map[string]cariMeta{}
|
||||||
|
}
|
||||||
|
|
||||||
|
masterMetaMap, err := loadMasterCariMetaMap(ctx, lines)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("statement_aging_balance: 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))
|
||||||
|
weightMap := make(map[string]float64, len(lines))
|
||||||
|
vadeSumMap := make(map[string]float64, len(lines))
|
||||||
|
vadeBelgeSumMap := make(map[string]float64, 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)
|
||||||
|
tl := toTRY(ln.Bakiye, curr, rateMap)
|
||||||
|
|
||||||
|
switch strings.TrimSpace(ln.PislemTipi) {
|
||||||
|
case "1_2":
|
||||||
|
row.Bakiye12 += ln.Bakiye
|
||||||
|
row.TLBakiye12 += tl
|
||||||
|
row.USDBakiye12 += usd
|
||||||
|
case "1_3":
|
||||||
|
row.Bakiye13 += ln.Bakiye
|
||||||
|
row.TLBakiye13 += tl
|
||||||
|
row.USDBakiye13 += usd
|
||||||
|
}
|
||||||
|
|
||||||
|
w := math.Abs(ln.Bakiye)
|
||||||
|
if w > 0 {
|
||||||
|
weightMap[key] += w
|
||||||
|
vadeSumMap[key] += (ln.VadeGun * w)
|
||||||
|
vadeBelgeSumMap[key] += (ln.VadeBelgeGun * w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]models.CustomerBalanceListRow, 0, len(agg))
|
||||||
|
for k, v := range agg {
|
||||||
|
base := weightMap[k]
|
||||||
|
if base > 0 {
|
||||||
|
v.VadeGun = vadeSumMap[k] / base
|
||||||
|
v.VadeBelgeGun = vadeBelgeSumMap[k] / base
|
||||||
|
}
|
||||||
|
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
|
||||||
|
})
|
||||||
|
|
||||||
|
_ = selectedDate
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadAgingBalanceLines(ctx context.Context, cariSearch string) ([]mkCariBakiyeLine, error) {
|
||||||
|
query := `
|
||||||
|
SELECT
|
||||||
|
CurrAccTypeCode,
|
||||||
|
CariKodu = LTRIM(RTRIM(CariKodu)),
|
||||||
|
CariDoviz = LTRIM(RTRIM(CariDoviz)),
|
||||||
|
SirketKodu,
|
||||||
|
PislemTipi,
|
||||||
|
YerelBakiye = CAST(0 AS DECIMAL(18,2)),
|
||||||
|
Bakiye,
|
||||||
|
Vade_Gun,
|
||||||
|
Vade_BelgeTarihi_Gun
|
||||||
|
FROM dbo.CARI_BAKIYE_GUN_CACHE
|
||||||
|
WHERE (@CariSearch = '' OR LTRIM(RTRIM(CariKodu)) LIKE '%' + @CariSearch + '%')
|
||||||
|
ORDER BY CariKodu, CariDoviz, PislemTipi
|
||||||
|
`
|
||||||
|
|
||||||
|
rows, err := db.MssqlDB.QueryContext(ctx, query, sql.Named("CariSearch", strings.TrimSpace(cariSearch)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("CARI_BAKIYE_GUN_CACHE 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,
|
||||||
|
&r.VadeGun,
|
||||||
|
&r.VadeBelgeGun,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out = append(out, r)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toTRY(amount float64, currency string, rateMap map[string]float64) float64 {
|
||||||
|
switch currency {
|
||||||
|
case "TRY":
|
||||||
|
return amount
|
||||||
|
case "":
|
||||||
|
return amount
|
||||||
|
default:
|
||||||
|
currTry := rateMap[currency]
|
||||||
|
if currTry <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return amount * currTry
|
||||||
|
}
|
||||||
|
}
|
||||||
20
svc/queries/statement_aging_cache.go
Normal file
20
svc/queries/statement_aging_cache.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package queries
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bssapp-backend/db"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RebuildStatementAgingCache runs only step 2 + step 3.
|
||||||
|
func RebuildStatementAgingCache(ctx context.Context) error {
|
||||||
|
if _, err := db.MssqlDB.ExecContext(ctx, `EXEC dbo.SP_BUILD_CARI_VADE_GUN_STAGING;`); err != nil {
|
||||||
|
return fmt.Errorf("SP_BUILD_CARI_VADE_GUN_STAGING error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := db.MssqlDB.ExecContext(ctx, `EXEC dbo.SP_BUILD_CARI_BAKIYE_CACHE;`); err != nil {
|
||||||
|
return fmt.Errorf("SP_BUILD_CARI_BAKIYE_CACHE error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -67,6 +67,8 @@ func ExportCustomerBalanceExcelHandler(_ *sql.DB) http.HandlerFunc {
|
|||||||
"1_2 TRY Bakiye",
|
"1_2 TRY Bakiye",
|
||||||
"1_3 USD Bakiye",
|
"1_3 USD Bakiye",
|
||||||
"1_3 TRY Bakiye",
|
"1_3 TRY Bakiye",
|
||||||
|
"Vade Gun",
|
||||||
|
"Belge Tarihi Gun",
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, h := range headers {
|
for i, h := range headers {
|
||||||
@@ -75,6 +77,7 @@ func ExportCustomerBalanceExcelHandler(_ *sql.DB) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var totalUSD12, totalTRY12, totalUSD13, totalTRY13 float64
|
var totalUSD12, totalTRY12, totalUSD13, totalTRY13 float64
|
||||||
|
var totalVadeBase, totalVadeSum, totalVadeBelgeSum float64
|
||||||
totalPrBr12 := map[string]float64{}
|
totalPrBr12 := map[string]float64{}
|
||||||
totalPrBr13 := map[string]float64{}
|
totalPrBr13 := map[string]float64{}
|
||||||
|
|
||||||
@@ -83,6 +86,12 @@ func ExportCustomerBalanceExcelHandler(_ *sql.DB) http.HandlerFunc {
|
|||||||
totalTRY12 += s.TLBakiye12
|
totalTRY12 += s.TLBakiye12
|
||||||
totalUSD13 += s.USDBakiye13
|
totalUSD13 += s.USDBakiye13
|
||||||
totalTRY13 += s.TLBakiye13
|
totalTRY13 += s.TLBakiye13
|
||||||
|
w := absFloatExcel(s.USDBakiye12) + absFloatExcel(s.TLBakiye12) + absFloatExcel(s.USDBakiye13) + absFloatExcel(s.TLBakiye13)
|
||||||
|
if w > 0 {
|
||||||
|
totalVadeBase += w
|
||||||
|
totalVadeSum += s.VadeGun * w
|
||||||
|
totalVadeBelgeSum += s.VadeBelge * w
|
||||||
|
}
|
||||||
for k, v := range s.Bakiye12Map {
|
for k, v := range s.Bakiye12Map {
|
||||||
totalPrBr12[k] += v
|
totalPrBr12[k] += v
|
||||||
}
|
}
|
||||||
@@ -91,6 +100,13 @@ func ExportCustomerBalanceExcelHandler(_ *sql.DB) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
totalVade := 0.0
|
||||||
|
totalVadeBelge := 0.0
|
||||||
|
if totalVadeBase > 0 {
|
||||||
|
totalVade = totalVadeSum / totalVadeBase
|
||||||
|
totalVadeBelge = totalVadeBelgeSum / totalVadeBase
|
||||||
|
}
|
||||||
|
|
||||||
f.SetSheetRow(sheet, "A2", &[]any{
|
f.SetSheetRow(sheet, "A2", &[]any{
|
||||||
"TOPLAM",
|
"TOPLAM",
|
||||||
"",
|
"",
|
||||||
@@ -103,6 +119,8 @@ func ExportCustomerBalanceExcelHandler(_ *sql.DB) http.HandlerFunc {
|
|||||||
totalTRY12,
|
totalTRY12,
|
||||||
totalUSD13,
|
totalUSD13,
|
||||||
totalTRY13,
|
totalTRY13,
|
||||||
|
totalVade,
|
||||||
|
totalVadeBelge,
|
||||||
})
|
})
|
||||||
|
|
||||||
rowNo := 3
|
rowNo := 3
|
||||||
@@ -119,6 +137,8 @@ func ExportCustomerBalanceExcelHandler(_ *sql.DB) http.HandlerFunc {
|
|||||||
s.TLBakiye12,
|
s.TLBakiye12,
|
||||||
s.USDBakiye13,
|
s.USDBakiye13,
|
||||||
s.TLBakiye13,
|
s.TLBakiye13,
|
||||||
|
s.VadeGun,
|
||||||
|
s.VadeBelge,
|
||||||
})
|
})
|
||||||
rowNo++
|
rowNo++
|
||||||
}
|
}
|
||||||
@@ -127,7 +147,7 @@ func ExportCustomerBalanceExcelHandler(_ *sql.DB) http.HandlerFunc {
|
|||||||
_ = f.SetColWidth(sheet, "B", "B", 34)
|
_ = f.SetColWidth(sheet, "B", "B", 34)
|
||||||
_ = f.SetColWidth(sheet, "C", "E", 18)
|
_ = f.SetColWidth(sheet, "C", "E", 18)
|
||||||
_ = f.SetColWidth(sheet, "F", "G", 34)
|
_ = f.SetColWidth(sheet, "F", "G", 34)
|
||||||
_ = f.SetColWidth(sheet, "H", "K", 18)
|
_ = f.SetColWidth(sheet, "H", "M", 18)
|
||||||
|
|
||||||
buf, err := f.WriteToBuffer()
|
buf, err := f.WriteToBuffer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -143,3 +163,10 @@ func ExportCustomerBalanceExcelHandler(_ *sql.DB) http.HandlerFunc {
|
|||||||
_, _ = w.Write(buf.Bytes())
|
_, _ = w.Write(buf.Bytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func absFloatExcel(v float64) float64 {
|
||||||
|
if v < 0 {
|
||||||
|
return -v
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ type balanceSummaryPDF struct {
|
|||||||
TLBakiye12 float64
|
TLBakiye12 float64
|
||||||
USDBakiye13 float64
|
USDBakiye13 float64
|
||||||
TLBakiye13 float64
|
TLBakiye13 float64
|
||||||
|
VadeGun float64
|
||||||
|
VadeBelge float64
|
||||||
|
VadeBase float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExportCustomerBalancePDFHandler(_ *sql.DB) http.HandlerFunc {
|
func ExportCustomerBalancePDFHandler(_ *sql.DB) http.HandlerFunc {
|
||||||
@@ -182,6 +185,12 @@ func buildCustomerBalancePDFData(rows []models.CustomerBalanceListRow) ([]balanc
|
|||||||
s.TLBakiye12 += row.TLBakiye12
|
s.TLBakiye12 += row.TLBakiye12
|
||||||
s.USDBakiye13 += row.USDBakiye13
|
s.USDBakiye13 += row.USDBakiye13
|
||||||
s.TLBakiye13 += row.TLBakiye13
|
s.TLBakiye13 += row.TLBakiye13
|
||||||
|
w := absFloat(row.Bakiye12) + absFloat(row.Bakiye13)
|
||||||
|
if w > 0 {
|
||||||
|
s.VadeBase += w
|
||||||
|
s.VadeGun += row.VadeGun * w
|
||||||
|
s.VadeBelge += row.VadeBelgeGun * w
|
||||||
|
}
|
||||||
|
|
||||||
detailsByMaster[master] = append(detailsByMaster[master], row)
|
detailsByMaster[master] = append(detailsByMaster[master], row)
|
||||||
}
|
}
|
||||||
@@ -194,6 +203,11 @@ func buildCustomerBalancePDFData(rows []models.CustomerBalanceListRow) ([]balanc
|
|||||||
|
|
||||||
summaries := make([]balanceSummaryPDF, 0, len(masters))
|
summaries := make([]balanceSummaryPDF, 0, len(masters))
|
||||||
for _, m := range masters {
|
for _, m := range masters {
|
||||||
|
s := summaryMap[m]
|
||||||
|
if s != nil && s.VadeBase > 0 {
|
||||||
|
s.VadeGun = s.VadeGun / s.VadeBase
|
||||||
|
s.VadeBelge = s.VadeBelge / s.VadeBase
|
||||||
|
}
|
||||||
summaries = append(summaries, *summaryMap[m])
|
summaries = append(summaries, *summaryMap[m])
|
||||||
d := detailsByMaster[m]
|
d := detailsByMaster[m]
|
||||||
sort.SliceStable(d, func(i, j int) bool {
|
sort.SliceStable(d, func(i, j int) bool {
|
||||||
@@ -225,11 +239,11 @@ func drawCustomerBalancePDF(
|
|||||||
marginL, marginT, marginR, marginB := 8.0, 8.0, 8.0, 12.0
|
marginL, marginT, marginR, marginB := 8.0, 8.0, 8.0, 12.0
|
||||||
tableW := pageW - marginL - marginR
|
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"}
|
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", "Vade Gun", "Belge Gun"}
|
||||||
summaryW := normalizeWidths([]float64{20, 52, 16, 20, 14, 24, 24, 14, 14, 14, 14}, tableW)
|
summaryW := normalizeWidths([]float64{18, 46, 14, 18, 12, 20, 20, 12, 12, 12, 12, 10, 10}, 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"}
|
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", "Vade Gun", "Belge Gun"}
|
||||||
detailW := normalizeWidths([]float64{26, 46, 10, 20, 10, 24, 24, 15, 15, 15, 15}, tableW)
|
detailW := normalizeWidths([]float64{22, 40, 9, 16, 8, 20, 20, 12, 12, 12, 12, 9, 9}, tableW)
|
||||||
|
|
||||||
header := func() {
|
header := func() {
|
||||||
pdf.AddPage()
|
pdf.AddPage()
|
||||||
@@ -352,18 +366,27 @@ func drawCustomerBalancePDF(
|
|||||||
formatMoneyPDF(s.TLBakiye12),
|
formatMoneyPDF(s.TLBakiye12),
|
||||||
formatMoneyPDF(s.USDBakiye13),
|
formatMoneyPDF(s.USDBakiye13),
|
||||||
formatMoneyPDF(s.TLBakiye13),
|
formatMoneyPDF(s.TLBakiye13),
|
||||||
|
formatMoneyPDF(s.VadeGun),
|
||||||
|
formatMoneyPDF(s.VadeBelge),
|
||||||
}
|
}
|
||||||
wrapCols := map[int]bool{1: true, 3: true}
|
wrapCols := map[int]bool{1: true, 3: true}
|
||||||
rowH := calcWrappedRowHeight(row, summaryW, wrapCols, 3.2, 6.2)
|
rowH := calcWrappedRowHeight(row, summaryW, wrapCols, 3.2, 6.2)
|
||||||
if needPage(rowH) {
|
if needPage(rowH) {
|
||||||
header()
|
header()
|
||||||
drawSummaryHeader()
|
drawSummaryHeader()
|
||||||
|
pdf.SetFont("dejavu", "", 7.2)
|
||||||
|
pdf.SetTextColor(20, 20, 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
y := pdf.GetY()
|
y := pdf.GetY()
|
||||||
x := marginL
|
x := marginL
|
||||||
for i, v := range row {
|
for i, v := range row {
|
||||||
pdf.Rect(x, y, summaryW[i], rowH, "")
|
if detailed {
|
||||||
|
pdf.SetFillColor(246, 241, 231)
|
||||||
|
pdf.Rect(x, y, summaryW[i], rowH, "FD")
|
||||||
|
} else {
|
||||||
|
pdf.Rect(x, y, summaryW[i], rowH, "")
|
||||||
|
}
|
||||||
align := "L"
|
align := "L"
|
||||||
if i >= 7 {
|
if i >= 7 {
|
||||||
align = "R"
|
align = "R"
|
||||||
@@ -414,7 +437,25 @@ func drawCustomerBalancePDF(
|
|||||||
pdf.SetTextColor(40, 40, 40)
|
pdf.SetTextColor(40, 40, 40)
|
||||||
|
|
||||||
for _, r := range rows {
|
for _, r := range rows {
|
||||||
if needPage(5.8) {
|
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),
|
||||||
|
formatMoneyPDF(r.VadeGun),
|
||||||
|
formatMoneyPDF(r.VadeBelgeGun),
|
||||||
|
}
|
||||||
|
detailWrapCols := map[int]bool{1: true}
|
||||||
|
rowH := calcWrappedRowHeight(line, detailW, detailWrapCols, 3.0, 5.8)
|
||||||
|
|
||||||
|
if needPage(rowH) {
|
||||||
header()
|
header()
|
||||||
pdf.SetFont("dejavu", "B", 8)
|
pdf.SetFont("dejavu", "B", 8)
|
||||||
pdf.SetFillColor(218, 193, 151)
|
pdf.SetFillColor(218, 193, 151)
|
||||||
@@ -429,33 +470,23 @@ func drawCustomerBalancePDF(
|
|||||||
pdf.SetTextColor(40, 40, 40)
|
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()
|
rowY := pdf.GetY()
|
||||||
rowX := marginL
|
rowX := marginL
|
||||||
for i, v := range line {
|
for i, v := range line {
|
||||||
pdf.Rect(rowX, rowY, detailW[i], 5.8, "")
|
pdf.Rect(rowX, rowY, detailW[i], rowH, "")
|
||||||
align := "L"
|
align := "L"
|
||||||
if i >= 5 {
|
if i >= 5 {
|
||||||
align = "R"
|
align = "R"
|
||||||
}
|
}
|
||||||
pdf.SetXY(rowX+1, rowY+0.8)
|
if detailWrapCols[i] {
|
||||||
pdf.CellFormat(detailW[i]-2, 4.0, v, "", 0, align, false, 0, "")
|
drawWrapped(v, rowX, rowY, detailW[i], rowH, 3.0, "L")
|
||||||
|
} else {
|
||||||
|
pdf.SetXY(rowX+1, rowY+(rowH-4.0)/2)
|
||||||
|
pdf.CellFormat(detailW[i]-2, 4.0, v, "", 0, align, false, 0, "")
|
||||||
|
}
|
||||||
rowX += detailW[i]
|
rowX += detailW[i]
|
||||||
}
|
}
|
||||||
pdf.SetY(rowY + 5.8)
|
pdf.SetY(rowY + rowH)
|
||||||
}
|
}
|
||||||
pdf.Ln(1.2)
|
pdf.Ln(1.2)
|
||||||
}
|
}
|
||||||
@@ -508,3 +539,10 @@ func formatMoneyPDF(v float64) string {
|
|||||||
|
|
||||||
return sign + strings.Join(out, ".") + "," + decPart
|
return sign + strings.Join(out, ".") + "," + decPart
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func absFloat(v float64) float64 {
|
||||||
|
if v < 0 {
|
||||||
|
return -v
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ func buildMissingVariants(mssql *sql.DB, orderHeaderID string, lines []models.Or
|
|||||||
newColor := strings.TrimSpace(line.NewColor)
|
newColor := strings.TrimSpace(line.NewColor)
|
||||||
newDim2 := strings.TrimSpace(line.NewDim2)
|
newDim2 := strings.TrimSpace(line.NewDim2)
|
||||||
|
|
||||||
if lineID == "" || newItem == "" || newColor == "" {
|
if lineID == "" || newItem == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,9 +286,6 @@ func validateUpdateLines(lines []models.OrderProductionUpdateLine) error {
|
|||||||
if strings.TrimSpace(line.NewItemCode) == "" {
|
if strings.TrimSpace(line.NewItemCode) == "" {
|
||||||
return errors.New("Yeni urun kodu zorunlu")
|
return errors.New("Yeni urun kodu zorunlu")
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(line.NewColor) == "" {
|
|
||||||
return errors.New("Yeni renk kodu zorunlu")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GET /api/finance/account-aging-statement
|
// GET /api/finance/account-aging-statement
|
||||||
@@ -17,18 +18,35 @@ func GetStatementAgingHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectedDate := time.Now().Format("2006-01-02")
|
||||||
params := models.StatementAgingParams{
|
params := models.StatementAgingParams{
|
||||||
AccountCode: strings.TrimSpace(r.URL.Query().Get("accountcode")),
|
AccountCode: strings.TrimSpace(r.URL.Query().Get("accountcode")),
|
||||||
EndDate: strings.TrimSpace(r.URL.Query().Get("enddate")),
|
EndDate: selectedDate,
|
||||||
Parislemler: r.URL.Query()["parislemler"],
|
Parislemler: r.URL.Query()["parislemler"],
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.AccountCode == "" || params.EndDate == "" {
|
listParams := models.CustomerBalanceListParams{
|
||||||
http.Error(w, "accountcode and enddate are required", http.StatusBadRequest)
|
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")),
|
||||||
|
}
|
||||||
|
if params.AccountCode != "" {
|
||||||
|
listParams.CariSearch = params.AccountCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := queries.RebuildStatementAgingCache(r.Context()); err != nil {
|
||||||
|
http.Error(w, "Error rebuilding aging cache: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := queries.GetStatementAging(params)
|
rows, err := queries.GetStatementAgingBalanceList(r.Context(), listParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Error fetching aging statement: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Error fetching aging statement: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|||||||
34
svc/routes/statement_aging_cache.go
Normal file
34
svc/routes/statement_aging_cache.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bssapp-backend/auth"
|
||||||
|
"bssapp-backend/queries"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type agingCacheRefreshResponse struct {
|
||||||
|
OK bool `json:"ok"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /api/finance/account-aging-statement/rebuild-cache
|
||||||
|
// Runs only step2 + step3.
|
||||||
|
func RebuildStatementAgingCacheHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||||||
|
if !ok || claims == nil {
|
||||||
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := queries.RebuildStatementAgingCache(r.Context()); err != nil {
|
||||||
|
http.Error(w, "cache rebuild error: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
_ = json.NewEncoder(w).Encode(agingCacheRefreshResponse{
|
||||||
|
OK: true,
|
||||||
|
Message: "SP_BUILD_CARI_VADE_GUN_STAGING -> SP_BUILD_CARI_BAKIYE_CACHE çalıştırıldı.",
|
||||||
|
})
|
||||||
|
}
|
||||||
90
svc/routes/statement_aging_excel.go
Normal file
90
svc/routes/statement_aging_excel.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
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 ExportStatementAgingExcelHandler(_ *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 := 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.GetStatementAgingBalanceList(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 := "CariYaslandirma"
|
||||||
|
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", "Vade Gun", "Belge Tarihi Gun",
|
||||||
|
}
|
||||||
|
for i, h := range headers {
|
||||||
|
cell, _ := excelize.CoordinatesToCellName(i+1, 1)
|
||||||
|
f.SetCellValue(sheet, cell, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
rowNo := 2
|
||||||
|
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, s.VadeGun, s.VadeBelge,
|
||||||
|
})
|
||||||
|
rowNo++
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = f.SetColWidth(sheet, "A", "A", 16)
|
||||||
|
_ = f.SetColWidth(sheet, "B", "B", 34)
|
||||||
|
_ = f.SetColWidth(sheet, "C", "F", 18)
|
||||||
|
_ = f.SetColWidth(sheet, "G", "M", 18)
|
||||||
|
|
||||||
|
buf, err := f.WriteToBuffer()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("cari_yaslandirmali_bakiye_%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())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,23 +82,30 @@ func ExportStatementAgingPDFHandler(_ *sql.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
params := models.StatementAgingParams{
|
selectedDate := time.Now().Format("2006-01-02")
|
||||||
AccountCode: strings.TrimSpace(r.URL.Query().Get("accountcode")),
|
listParams := models.CustomerBalanceListParams{
|
||||||
EndDate: strings.TrimSpace(r.URL.Query().Get("enddate")),
|
SelectedDate: selectedDate,
|
||||||
Parislemler: r.URL.Query()["parislemler"],
|
CariSearch: strings.TrimSpace(r.URL.Query().Get("cari_search")),
|
||||||
}
|
CariIlkGrup: strings.TrimSpace(r.URL.Query().Get("cari_ilk_grup")),
|
||||||
if params.AccountCode == "" || params.EndDate == "" {
|
Piyasa: strings.TrimSpace(r.URL.Query().Get("piyasa")),
|
||||||
http.Error(w, "accountcode and enddate are required", http.StatusBadRequest)
|
Temsilci: strings.TrimSpace(r.URL.Query().Get("temsilci")),
|
||||||
return
|
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.GetStatementAging(params)
|
rows, err := queries.GetStatementAgingBalanceList(r.Context(), listParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "db error: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "db error: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
rows = filterCustomerBalanceRowsForPDF(rows, excludeZero12, excludeZero13)
|
||||||
masters := buildAgingPDFData(rows)
|
summaries, detailsByMaster := buildCustomerBalancePDFData(rows)
|
||||||
|
|
||||||
pdf := gofpdf.New("L", "mm", "A4", "")
|
pdf := gofpdf.New("L", "mm", "A4", "")
|
||||||
pdf.SetMargins(8, 8, 8)
|
pdf.SetMargins(8, 8, 8)
|
||||||
@@ -108,7 +115,14 @@ func ExportStatementAgingPDFHandler(_ *sql.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
drawStatementAgingPDF(pdf, params, masters)
|
drawCustomerBalancePDF(
|
||||||
|
pdf,
|
||||||
|
selectedDate,
|
||||||
|
listParams.CariSearch,
|
||||||
|
detailed,
|
||||||
|
summaries,
|
||||||
|
detailsByMaster,
|
||||||
|
)
|
||||||
|
|
||||||
if err := pdf.Error(); err != nil {
|
if err := pdf.Error(); err != nil {
|
||||||
http.Error(w, "pdf render error: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "pdf render error: "+err.Error(), http.StatusInternalServerError)
|
||||||
@@ -122,7 +136,11 @@ func ExportStatementAgingPDFHandler(_ *sql.DB) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/pdf")
|
w.Header().Set("Content-Type", "application/pdf")
|
||||||
w.Header().Set("Content-Disposition", `inline; filename="account-aging-detailed.pdf"`)
|
filename := "account-aging-summary.pdf"
|
||||||
|
if detailed {
|
||||||
|
filename = "account-aging-detailed.pdf"
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=%q", filename))
|
||||||
_, _ = w.Write(buf.Bytes())
|
_, _ = w.Write(buf.Bytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
/* 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
/* 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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
/* 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()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
/* 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} }
|
|
||||||
|
|
||||||
@@ -303,6 +303,14 @@ body {
|
|||||||
margin-left: 0; width: 100%;
|
margin-left: 0; width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
.body--drawer-left-open .q-page-container,
|
||||||
|
.body--drawer-left-closed .q-page-container {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* 🔸 Yatay scroll sadece grid alanında */
|
/* 🔸 Yatay scroll sadece grid alanında */
|
||||||
.order-scroll-x {
|
.order-scroll-x {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
@@ -129,9 +129,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted, watch } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { Dialog } from 'quasar'
|
import { Dialog, useQuasar } from 'quasar'
|
||||||
|
|
||||||
import { useAuthStore } from 'stores/authStore'
|
import { useAuthStore } from 'stores/authStore'
|
||||||
import { usePermissionStore } from 'stores/permissionStore'
|
import { usePermissionStore } from 'stores/permissionStore'
|
||||||
@@ -140,13 +140,15 @@ import { usePermissionStore } from 'stores/permissionStore'
|
|||||||
/* ================= STORES ================= */
|
/* ================= STORES ================= */
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
const $q = useQuasar()
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
const perm = usePermissionStore()
|
const perm = usePermissionStore()
|
||||||
|
|
||||||
|
|
||||||
/* ================= UI ================= */
|
/* ================= UI ================= */
|
||||||
|
|
||||||
const leftDrawerOpen = ref(true)
|
const leftDrawerOpen = ref(!$q.screen.lt.md)
|
||||||
|
|
||||||
function toggleLeftDrawer () {
|
function toggleLeftDrawer () {
|
||||||
leftDrawerOpen.value = !leftDrawerOpen.value
|
leftDrawerOpen.value = !leftDrawerOpen.value
|
||||||
@@ -172,8 +174,18 @@ onMounted(async () => {
|
|||||||
if (!perm.loaded) {
|
if (!perm.loaded) {
|
||||||
await perm.fetchPermissions()
|
await perm.fetchPermissions()
|
||||||
}
|
}
|
||||||
|
leftDrawerOpen.value = !$q.screen.lt.md
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.fullPath,
|
||||||
|
() => {
|
||||||
|
if ($q.screen.lt.md) {
|
||||||
|
leftDrawerOpen.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
/* ================= MENU CONFIG ================= */
|
/* ================= MENU CONFIG ================= */
|
||||||
|
|
||||||
|
|||||||
@@ -1,102 +1,454 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-page v-if="canReadFinance" class="q-px-md q-pb-md page-col statement-page">
|
<q-page v-if="canReadFinance" class="q-pa-md page-layout">
|
||||||
<div class="local-filter-bar compact-filter q-pa-sm q-mb-xs">
|
<div class="filter-sticky" :class="{ collapsed: filtersCollapsed }">
|
||||||
<div class="row q-col-gutter-sm items-end">
|
<div class="top-actions row q-col-gutter-sm items-end q-mb-sm" :class="{ 'single-line': filtersCollapsed }">
|
||||||
<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">
|
<div class="col-12 col-sm-6 col-md-2">
|
||||||
<q-input v-model="dateTo" label="Son tarih" filled dense clearable readonly>
|
<q-input
|
||||||
<template #append>
|
v-model="store.filters.selectedDate"
|
||||||
<q-icon name="event" class="cursor-pointer">
|
label="Tarih (Bugün)"
|
||||||
<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
|
filled
|
||||||
dense
|
dense
|
||||||
|
readonly
|
||||||
|
disable
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
|
||||||
<q-btn color="primary" icon="filter_alt" label="Filtrele" @click="onFilterClick" />
|
<div class="col-12 col-sm-6 col-md-3">
|
||||||
|
<q-toggle
|
||||||
|
v-model="store.filters.excludeZeroBalance12"
|
||||||
|
dense
|
||||||
|
label="1_2 Bakiyesi Sıfır Olanları Alma"
|
||||||
|
@update:model-value="onToggle12Changed"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-sm-6 col-md-3">
|
||||||
|
<q-toggle
|
||||||
|
v-model="store.filters.excludeZeroBalance13"
|
||||||
|
dense
|
||||||
|
label="1_3 Bakiyesi Sıfır Olanları Alma"
|
||||||
|
@update:model-value="onToggle13Changed"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<q-btn flat color="grey-8" icon="restart_alt" label="Sıfırla" @click="resetFilters" />
|
<q-btn
|
||||||
|
color="primary"
|
||||||
|
icon="download"
|
||||||
|
label="Verileri Yenile"
|
||||||
|
:loading="store.loading"
|
||||||
|
@click="store.fetchBalances()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-auto">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
color="grey-8"
|
||||||
|
icon="restart_alt"
|
||||||
|
label="Sıfırla"
|
||||||
|
@click="onReset"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<q-slide-transition>
|
||||||
|
<div v-show="!filtersCollapsed" 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-slide-transition>
|
||||||
|
|
||||||
|
<q-banner v-if="store.error" class="bg-red-1 text-negative q-mb-md rounded-borders">
|
||||||
|
{{ store.error }}
|
||||||
|
</q-banner>
|
||||||
|
|
||||||
|
<q-banner v-if="!store.hasFetched && !store.loading" class="bg-blue-1 text-primary q-mb-md rounded-borders">
|
||||||
|
Veriler bugünün cache tablosundan okunur. Verileri Yenile ile tekrar yükleyebilirsiniz.
|
||||||
|
</q-banner>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-scroll">
|
<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ç'"
|
||||||
|
@click="toggleAllDetails"
|
||||||
|
/>
|
||||||
|
<q-btn-dropdown
|
||||||
|
v-if="canExportFinance"
|
||||||
|
flat
|
||||||
|
color="red"
|
||||||
|
icon="picture_as_pdf"
|
||||||
|
label="Yazdır"
|
||||||
|
>
|
||||||
|
<q-list style="min-width: 240px">
|
||||||
|
<q-item clickable v-close-popup @click="downloadAgingBalancePDF(true)">
|
||||||
|
<q-item-section class="text-primary">
|
||||||
|
Detaylı Cari Yaşlandırmalı Bakiye Listesi Yazdır
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-close-popup @click="downloadAgingBalancePDF(false)">
|
||||||
|
<q-item-section class="text-secondary">
|
||||||
|
Detaysız Cari Yaşlandırmalı 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="downloadAgingBalanceExcel"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
color="primary"
|
||||||
|
:icon="filtersCollapsed ? 'unfold_more' : 'unfold_less'"
|
||||||
|
:label="filtersCollapsed ? 'Filtreleri Genişlet' : 'Filtreleri Daralt'"
|
||||||
|
@click="toggleFiltersCollapsed"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<q-table
|
<q-table
|
||||||
class="sticky-table statement-table"
|
title="Cari Yaşlandırmalı Cari Bakiye Listesi"
|
||||||
title="Cari Yaşlandırmalı Ekstre"
|
:rows="store.summaryRows"
|
||||||
:rows="agingStore.masterRows"
|
:columns="summaryColumns"
|
||||||
:columns="masterColumns"
|
|
||||||
row-key="group_key"
|
row-key="group_key"
|
||||||
|
:loading="store.loading"
|
||||||
flat
|
flat
|
||||||
bordered
|
bordered
|
||||||
dense
|
dense
|
||||||
hide-bottom
|
|
||||||
wrap-cells
|
wrap-cells
|
||||||
|
separator="cell"
|
||||||
|
hide-bottom
|
||||||
:rows-per-page-options="[0]"
|
:rows-per-page-options="[0]"
|
||||||
:loading="agingStore.loading"
|
:pagination="{ rowsPerPage: 0 }"
|
||||||
:table-style="{ tableLayout: 'fixed', width: '100%' }"
|
:table-style="{ tableLayout: 'fixed', width: '100%' }"
|
||||||
|
class="balance-table"
|
||||||
>
|
>
|
||||||
<template #top-right>
|
|
||||||
<q-btn
|
|
||||||
color="red"
|
|
||||||
text-color="white"
|
|
||||||
icon="picture_as_pdf"
|
|
||||||
label="Detaylı PDF Yazdır"
|
|
||||||
@click="downloadAgingPDF"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #header="props">
|
<template #header="props">
|
||||||
<q-tr :props="props" class="header-row">
|
<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-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-th>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #body="props">
|
<template #body="props">
|
||||||
<q-tr :props="props" class="master-row">
|
<q-tr :props="props" class="sub-header-row">
|
||||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="col.name === 'expand'"
|
v-if="col.name === 'expand'"
|
||||||
@@ -104,319 +456,287 @@
|
|||||||
flat
|
flat
|
||||||
round
|
round
|
||||||
size="sm"
|
size="sm"
|
||||||
:icon="masterExpanded[props.row.group_key] ? 'expand_less' : 'expand_more'"
|
:icon="expanded[props.row.group_key] ? 'expand_less' : 'expand_more'"
|
||||||
@click="toggleMaster(props.row.group_key)"
|
@click="toggleGroup(props.row.group_key)"
|
||||||
/>
|
/>
|
||||||
<span
|
<span v-else-if="col.name === 'prbr_1_2'" class="text-right block prbr-cell">
|
||||||
v-else-if="masterNumericCols.includes(col.name)"
|
{{ formatCurrencyMap(props.row.bakiye_1_2_map) }}
|
||||||
: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>
|
||||||
<span v-else>{{ props.row[col.field] ?? '-' }}</span>
|
<span v-else-if="col.name === 'prbr_1_3'" class="text-right block prbr-cell">
|
||||||
|
{{ formatCurrencyMap(props.row.bakiye_1_3_map) }}
|
||||||
|
</span>
|
||||||
|
<span v-else-if="staticMoneyFields.includes(col.name)" class="text-center block">
|
||||||
|
{{ formatAmount(props.row[col.field]) }}
|
||||||
|
</span>
|
||||||
|
<span v-else-if="dayFields.includes(col.name)" class="text-center block">
|
||||||
|
{{ formatDay(props.row[col.field]) }}
|
||||||
|
</span>
|
||||||
|
<span v-else>{{ props.row[col.field] || '-' }}</span>
|
||||||
</q-td>
|
</q-td>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
|
|
||||||
<q-tr v-if="masterExpanded[props.row.group_key]" class="master-sub-row">
|
<q-tr v-if="expanded[props.row.group_key]" class="detail-host-row">
|
||||||
<q-td colspan="100%" class="q-pa-none">
|
<q-td colspan="100%">
|
||||||
<div class="currency-groups">
|
<div class="detail-wrap">
|
||||||
<div class="currency-level-head">
|
<q-table
|
||||||
<div class="cgh-cell cgh-expand"></div>
|
:rows="store.getDetailsByGroup(props.row.group_key)"
|
||||||
<div class="cgh-cell cgh-code">Ana Cari Kod</div>
|
:columns="detailColumns"
|
||||||
<div class="cgh-cell cgh-code">Ana Cari Detay</div>
|
row-key="cari_kodu"
|
||||||
<div class="cgh-cell cgh-code">Döviz Cinsi</div>
|
dense
|
||||||
<div class="cgh-cell cgh-num">Açık Kalem Tutarı</div>
|
flat
|
||||||
<div class="cgh-cell cgh-num">Açık Kalem USD</div>
|
bordered
|
||||||
<div class="cgh-cell cgh-num">Açık Kalem TRY</div>
|
hide-bottom
|
||||||
<div class="cgh-cell cgh-center">Ort Gün</div>
|
:table-style="{ tableLayout: 'fixed', width: '100%' }"
|
||||||
<div class="cgh-cell cgh-center">Ort Belge Gün</div>
|
class="detail-table"
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-for="currRow in agingStore.getCurrenciesByMaster(props.row.group_key)"
|
|
||||||
:key="currRow.group_key"
|
|
||||||
class="currency-group"
|
|
||||||
>
|
>
|
||||||
<div class="currency-group-header">
|
<template #body-cell-prbr_1_2="scope">
|
||||||
<div class="cgh-cell cgh-expand">
|
<q-td :props="scope" class="text-right prbr-cell">
|
||||||
<q-btn
|
{{ formatRowPrBr(scope.row, '1_2') }}
|
||||||
dense
|
</q-td>
|
||||||
flat
|
</template>
|
||||||
round
|
<template #body-cell-prbr_1_3="scope">
|
||||||
size="sm"
|
<q-td :props="scope" class="text-right prbr-cell">
|
||||||
:icon="currencyExpanded[currRow.group_key] ? 'expand_less' : 'expand_more'"
|
{{ formatRowPrBr(scope.row, '1_3') }}
|
||||||
@click="toggleCurrency(currRow.group_key)"
|
</q-td>
|
||||||
/>
|
</template>
|
||||||
</div>
|
<template #body-cell-vade_gun="scope">
|
||||||
<div class="cgh-cell cgh-code">{{ currRow.cari8 }}</div>
|
<q-td :props="scope" class="text-center">
|
||||||
<div class="cgh-cell cgh-code">{{ currRow.cari_detay || '-' }}</div>
|
{{ formatDay(scope.row.vade_gun) }}
|
||||||
<div class="cgh-cell cgh-code">{{ currRow.doviz_cinsi }}</div>
|
</q-td>
|
||||||
<div class="cgh-cell cgh-num">{{ formatAmount(currRow.acik_kalem_tutari) }}</div>
|
</template>
|
||||||
<div class="cgh-cell cgh-num">{{ formatAmount(currRow.acik_kalem_usd) }}</div>
|
<template #body-cell-vade_belge_tarihi_gun="scope">
|
||||||
<div class="cgh-cell cgh-num">{{ formatAmount(currRow.acik_kalem_try) }}</div>
|
<q-td :props="scope" class="text-center">
|
||||||
<div class="cgh-cell cgh-center">{{ formatDay(currRow.ort_gun) }}</div>
|
{{ formatDay(scope.row.vade_belge_tarihi_gun) }}
|
||||||
<div class="cgh-cell cgh-center">{{ formatDay(currRow.ort_belge_gun) }}</div>
|
</q-td>
|
||||||
</div>
|
</template>
|
||||||
|
</q-table>
|
||||||
<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>
|
</div>
|
||||||
</q-td>
|
</q-td>
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
</q-table>
|
</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">Bu modüle erişim yetkiniz yok.</div>
|
<div class="text-negative text-subtitle1">
|
||||||
|
Bu modüle erişim yetkiniz yok.
|
||||||
|
</div>
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { useQuasar } from 'quasar'
|
import { useQuasar } from 'quasar'
|
||||||
import dayjs from 'dayjs'
|
import { onMounted } from 'vue'
|
||||||
|
import { useAccountAgingBalanceStore } from 'src/stores/accountAgingBalanceStore'
|
||||||
import { usePermission } from 'src/composables/usePermission'
|
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'
|
import { download, extractApiErrorDetail } from 'src/services/api'
|
||||||
|
|
||||||
|
const store = useAccountAgingBalanceStore()
|
||||||
|
const expanded = ref({})
|
||||||
|
const allDetailsOpen = ref(false)
|
||||||
|
const filtersCollapsed = ref(false)
|
||||||
|
const $q = useQuasar()
|
||||||
|
|
||||||
const { canRead, canExport } = usePermission()
|
const { canRead, canExport } = usePermission()
|
||||||
const canReadFinance = canRead('finance')
|
const canReadFinance = canRead('finance')
|
||||||
const canExportFinance = canExport('finance')
|
const canExportFinance = canExport('finance')
|
||||||
|
|
||||||
const $q = useQuasar()
|
const islemTipiOptions = [
|
||||||
const accountStore = useAccountStore()
|
{ label: '1_2 Bakiye Pr.Br', value: 'prbr_1_2' },
|
||||||
const agingStore = useStatementAgingStore()
|
{ label: '1_3 Bakiye Pr.Br', value: 'prbr_1_3' },
|
||||||
|
{ label: '1_2 USD Bakiye', value: 'usd_1_2' },
|
||||||
const selectedCari = ref(null)
|
{ label: '1_2 TRY Bakiye', value: 'try_1_2' },
|
||||||
const filteredOptions = ref([])
|
{ label: '1_3 USD Bakiye', value: 'usd_1_3' },
|
||||||
const dateTo = ref(dayjs().format('YYYY-MM-DD'))
|
{ label: '1_3 TRY Bakiye', value: 'try_1_3' }
|
||||||
|
|
||||||
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 = [
|
const staticMoneyFields = ['usd_bakiye_1_2', 'tl_bakiye_1_2', 'usd_bakiye_1_3', 'tl_bakiye_1_3']
|
||||||
{ name: 'fatura_cari', label: 'Fatura Cari', field: 'fatura_cari', align: 'left' },
|
const dayFields = ['vade_gun', 'vade_belge_tarihi_gun']
|
||||||
{ 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']
|
function toNumericSortValue (value) {
|
||||||
const masterDayCols = ['acik_kalem_ort_vade_gun', 'acik_kalem_ort_belge_gun', 'ortalama_vade_gun', 'ortalama_belge_gun']
|
if (typeof value === 'number') {
|
||||||
const masterCenteredCols = ['acik_kalem_ort_vade_gun', 'acik_kalem_ort_belge_gun', 'ortalama_vade_gun', 'ortalama_belge_gun']
|
return Number.isFinite(value) ? value : 0
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeText(str) {
|
const s = String(value ?? '').trim()
|
||||||
return (str || '')
|
if (!s) return 0
|
||||||
.toString()
|
|
||||||
.toLocaleLowerCase('tr-TR')
|
|
||||||
.normalize('NFD')
|
|
||||||
.replace(/[\u0300-\u036f]/g, '')
|
|
||||||
.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterCari(val, update) {
|
const hasComma = s.includes(',')
|
||||||
const needle = normalizeText(val)
|
const hasDot = s.includes('.')
|
||||||
|
|
||||||
update(() => {
|
let normalized = s.replace(/\s+/g, '')
|
||||||
if (!needle) {
|
|
||||||
filteredOptions.value = accountStore.accountOptions
|
if (hasComma && hasDot) {
|
||||||
return
|
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(',', '.')
|
||||||
|
}
|
||||||
|
|
||||||
filteredOptions.value = accountStore.accountOptions.filter(o => {
|
const n = Number.parseFloat(normalized)
|
||||||
const label = normalizeText(o.label)
|
return Number.isFinite(n) ? n : 0
|
||||||
const value = normalizeText(o.value)
|
|
||||||
return label.includes(needle) || value.includes(needle)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
function sortTextTr (a, b) {
|
||||||
await accountStore.fetchAccounts()
|
return String(a ?? '').localeCompare(String(b ?? ''), 'tr', { sensitivity: 'base' })
|
||||||
filteredOptions.value = accountStore.accountOptions
|
}
|
||||||
|
|
||||||
|
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_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, 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, 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, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) },
|
||||||
|
try_1_3: { name: 'tl_bakiye_1_3', label: '1_3 TRY_BAKIYE', field: 'tl_bakiye_1_3', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) }
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedMetricKeys = computed(() => {
|
||||||
|
const selected = store.filters.islemTipi || []
|
||||||
|
if (!selected.length) return [...Object.keys(metricDefs)]
|
||||||
|
return selected.filter((k) => k in metricDefs)
|
||||||
})
|
})
|
||||||
|
|
||||||
async function onFilterClick() {
|
const summaryColumns = computed(() => ([
|
||||||
if (!selectedCari.value || !dateTo.value) {
|
{ name: 'expand', label: '', field: 'expand', align: 'center', sortable: false },
|
||||||
$q.notify({
|
{ name: 'ana_cari_kodu', label: 'Ana Cari Kodu', field: 'ana_cari_kodu', align: 'left', sortable: true, sort: sortTextTr },
|
||||||
type: 'warning',
|
{ name: 'ana_cari_adi', label: 'Ana Cari Detay', field: 'ana_cari_adi', align: 'left', sortable: true, sort: sortTextTr },
|
||||||
message: 'Lütfen cari ve son tarih seçiniz.',
|
{ name: 'piyasa', label: 'Piyasa', field: 'piyasa', align: 'left', sortable: true, sort: sortTextTr },
|
||||||
position: 'top-right'
|
{ 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, sort: sortTextTr },
|
||||||
|
...selectedMetricKeys.value.map((k) => metricDefs[k]),
|
||||||
|
{ name: 'vade_gun', label: 'Vade Gun', field: 'vade_gun', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) },
|
||||||
|
{ name: 'vade_belge_tarihi_gun', label: 'Belge Tarihi Gun', field: 'vade_belge_tarihi_gun', align: 'center', sortable: true, sort: (a, b) => toNumericSortValue(a) - toNumericSortValue(b) }
|
||||||
|
]))
|
||||||
|
|
||||||
|
const liveTotals = computed(() => {
|
||||||
|
return store.filteredRows.reduce((acc, row) => {
|
||||||
|
acc.usd_bakiye_1_2 += Number(row.usd_bakiye_1_2) || 0
|
||||||
|
acc.tl_bakiye_1_2 += Number(row.tl_bakiye_1_2) || 0
|
||||||
|
acc.usd_bakiye_1_3 += Number(row.usd_bakiye_1_3) || 0
|
||||||
|
acc.tl_bakiye_1_3 += Number(row.tl_bakiye_1_3) || 0
|
||||||
|
const vadeGun = Number(row.vade_gun) || 0
|
||||||
|
const vadeBelge = Number(row.vade_belge_tarihi_gun) || 0
|
||||||
|
if (vadeGun !== 0 || vadeBelge !== 0) {
|
||||||
|
acc.vade_count += 1
|
||||||
|
acc.vade_gun_sum += vadeGun
|
||||||
|
acc.vade_belge_sum += vadeBelge
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {
|
||||||
|
usd_bakiye_1_2: 0,
|
||||||
|
tl_bakiye_1_2: 0,
|
||||||
|
usd_bakiye_1_3: 0,
|
||||||
|
tl_bakiye_1_3: 0,
|
||||||
|
vade_gun_sum: 0,
|
||||||
|
vade_belge_sum: 0,
|
||||||
|
vade_count: 0
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const detailColumns = computed(() => [
|
||||||
|
{ name: 'cari_kodu', label: 'Cari Kodu', field: 'cari_kodu', align: 'left' },
|
||||||
|
{ name: 'cari_detay', label: 'Cari Detay', field: 'cari_detay', 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: 'temsilci', label: 'Temsilci', field: 'temsilci', align: 'left' },
|
||||||
|
{ name: 'risk_durumu', label: 'Risk Durumu', field: 'risk_durumu', align: 'left' },
|
||||||
|
{ name: 'ozellik05', label: 'Ülke', field: 'ozellik05', align: 'left' },
|
||||||
|
{ name: 'il', label: 'İl', field: 'il', align: 'left' },
|
||||||
|
{ name: 'ilce', label: 'İlçe', field: 'ilce', align: 'left' },
|
||||||
|
{ name: 'cari_doviz', label: 'Döviz', field: 'cari_doviz', align: 'left' },
|
||||||
|
...selectedMetricKeys.value.map((k) => metricDefs[k]),
|
||||||
|
{ name: 'vade_gun', label: 'Vade Gun', field: 'vade_gun', align: 'center' },
|
||||||
|
{ name: 'vade_belge_tarihi_gun', label: 'Belge Tarihi Gun', field: 'vade_belge_tarihi_gun', align: 'center' }
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
function onReset () {
|
||||||
|
store.resetFilters()
|
||||||
|
store.fetchBalances()
|
||||||
|
}
|
||||||
|
|
||||||
|
function onToggle12Changed (val) {
|
||||||
|
if (val) {
|
||||||
|
store.filters.excludeZeroBalance13 = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onToggle13Changed (val) {
|
||||||
|
if (val) {
|
||||||
|
store.filters.excludeZeroBalance12 = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleFiltersCollapsed () {
|
||||||
|
filtersCollapsed.value = !filtersCollapsed.value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function toggleGroup (key) {
|
||||||
|
expanded.value[key] = !expanded.value[key]
|
||||||
|
if (!expanded.value[key]) {
|
||||||
|
allDetailsOpen.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
allDetailsOpen.value =
|
||||||
await agingStore.load({
|
store.summaryRows.length > 0 &&
|
||||||
accountcode: selectedCari.value,
|
store.summaryRows.every(r => expanded.value[r.group_key])
|
||||||
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() {
|
function toggleAllDetails () {
|
||||||
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
|
allDetailsOpen.value = !allDetailsOpen.value
|
||||||
|
|
||||||
if (!allDetailsOpen.value) {
|
if (allDetailsOpen.value) {
|
||||||
masterExpanded.value = {}
|
const next = {}
|
||||||
currencyExpanded.value = {}
|
for (const row of store.summaryRows) {
|
||||||
|
next[row.group_key] = true
|
||||||
|
}
|
||||||
|
expanded.value = next
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const m = {}
|
expanded.value = {}
|
||||||
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) {
|
async function downloadAgingBalancePDF (detailed) {
|
||||||
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) {
|
if (!canExportFinance.value) {
|
||||||
$q.notify({ type: 'negative', message: 'PDF export yetkiniz yok', position: 'top-right' })
|
$q.notify({ type: 'negative', message: 'PDF export yetkiniz yok', position: 'top-right' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!selectedCari.value || !dateTo.value) {
|
if (!store.hasFetched) {
|
||||||
$q.notify({ type: 'warning', message: 'Lütfen cari ve son tarih seçiniz.', position: 'top-right' })
|
$q.notify({ type: 'warning', message: 'Önce Bakiyeleri Getir ile veri yükleyin.', position: 'top-right' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const blob = await download('/finance/account-aging-statement/export-pdf', {
|
const params = {
|
||||||
accountcode: selectedCari.value,
|
cari_search: String(store.filters.cariSearch || '').trim(),
|
||||||
enddate: dateTo.value,
|
cari_ilk_grup: (store.filters.cariIlkGrup || []).join(','),
|
||||||
parislemler: selectedMonType.value
|
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/account-aging-statement/export-pdf', params)
|
||||||
const pdfUrl = window.URL.createObjectURL(new Blob([blob], { type: 'application/pdf' }))
|
const pdfUrl = window.URL.createObjectURL(new Blob([blob], { type: 'application/pdf' }))
|
||||||
window.open(pdfUrl, '_blank')
|
window.open(pdfUrl, '_blank')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -428,245 +748,311 @@ async function downloadAgingPDF () {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function downloadAgingBalanceExcel () {
|
||||||
|
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 = {
|
||||||
|
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/account-aging-statement/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_yaslandirmali_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) {
|
||||||
|
const n = Number(value || 0)
|
||||||
|
return new Intl.NumberFormat('tr-TR', {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2
|
||||||
|
}).format(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDay (value) {
|
||||||
|
const n = Number(value || 0)
|
||||||
|
return new Intl.NumberFormat('tr-TR', {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2
|
||||||
|
}).format(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectionLabel (arr, label) {
|
||||||
|
const count = Array.isArray(arr) ? arr.length : 0
|
||||||
|
if (count === 0) return `Tümü (${label})`
|
||||||
|
if (count === 1) return '1 seçim'
|
||||||
|
return `${count} seçim`
|
||||||
|
}
|
||||||
|
|
||||||
|
function totalCellValue (colName) {
|
||||||
|
if (colName === 'expand') return 'Toplam'
|
||||||
|
if (colName === 'piyasa') return '-'
|
||||||
|
if (colName === 'temsilci') return '-'
|
||||||
|
if (colName === 'risk_durumu') return '-'
|
||||||
|
if (colName === 'prbr_1_2') return formatCurrencyMap(totalByCurrency('1_2'))
|
||||||
|
if (colName === 'prbr_1_3') return formatCurrencyMap(totalByCurrency('1_3'))
|
||||||
|
if (colName === 'usd_bakiye_1_2') return formatAmount(liveTotals.value.usd_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 === 'tl_bakiye_1_3') return formatAmount(liveTotals.value.tl_bakiye_1_3)
|
||||||
|
if (colName === 'vade_gun') return formatDay(liveTotals.value.vade_count > 0 ? liveTotals.value.vade_gun_sum / liveTotals.value.vade_count : 0)
|
||||||
|
if (colName === 'vade_belge_tarihi_gun') return formatDay(liveTotals.value.vade_count > 0 ? liveTotals.value.vade_belge_sum / liveTotals.value.vade_count : 0)
|
||||||
|
return '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
function totalByCurrency (tip) {
|
||||||
|
const key = tip === '1_2' ? 'bakiye_1_2_map' : 'bakiye_1_3_map'
|
||||||
|
const out = {}
|
||||||
|
|
||||||
|
for (const r of store.summaryRows) {
|
||||||
|
const m = r[key] || {}
|
||||||
|
for (const [curr, val] of Object.entries(m)) {
|
||||||
|
out[curr] = (Number(out[curr]) || 0) + (Number(val) || 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCurrencyMap (mapObj) {
|
||||||
|
const entries = Object.entries(mapObj || {})
|
||||||
|
.filter(([, amount]) => Number(amount) !== 0)
|
||||||
|
.sort((a, b) => a[0].localeCompare(b[0], 'en'))
|
||||||
|
|
||||||
|
if (!entries.length) return '-'
|
||||||
|
return entries
|
||||||
|
.map(([curr, amount]) => `${curr}: ${formatAmount(amount)}`)
|
||||||
|
.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatRowPrBr (row, tip) {
|
||||||
|
const curr = String(row.cari_doviz || '').trim().toUpperCase() || 'N/A'
|
||||||
|
|
||||||
|
const amount = tip === '1_2'
|
||||||
|
? (Number(row.bakiye_1_2) || 0)
|
||||||
|
: (Number(row.bakiye_1_3) || 0)
|
||||||
|
|
||||||
|
if (amount === 0) return '-'
|
||||||
|
return `${curr} ${formatAmount(amount)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
store.filters.selectedDate = new Date().toISOString().slice(0, 10)
|
||||||
|
await store.fetchBalances()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.statement-page {
|
.page-layout {
|
||||||
--master-head-h: 34px;
|
height: calc(100vh - 110px);
|
||||||
--lvl2-head-h: 34px;
|
|
||||||
--lvl3-head-h: 34px;
|
|
||||||
height: calc(100vh - 56px);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-top: 0 !important;
|
|
||||||
padding-top: 56px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-scroll {
|
.filter-sticky {
|
||||||
flex: 1;
|
position: sticky;
|
||||||
min-height: 0;
|
top: 0;
|
||||||
overflow: hidden;
|
z-index: 20;
|
||||||
display: flex;
|
background: #fff;
|
||||||
flex-direction: column;
|
padding-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statement-page .local-filter-bar {
|
.filter-sticky.collapsed {
|
||||||
position: sticky !important;
|
padding-bottom: 0;
|
||||||
top: 0 !important;
|
|
||||||
z-index: 980 !important;
|
|
||||||
margin-top: 0 !important;
|
|
||||||
padding-top: 0 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.compact-filter {
|
.top-actions.single-line {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-actions.single-line > [class*='col-'],
|
||||||
|
.top-actions.single-line > .col-auto {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
min-width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-actions.single-line > .col-auto {
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters-panel {
|
||||||
border: 1px solid rgba(0, 0, 0, 0.12);
|
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.compact-select :deep(.q-field__control) {
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact-select :deep(.q-field__native),
|
||||||
|
.compact-select :deep(.q-field__input) {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-area {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.sticky-bar {
|
.sticky-bar {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 30;
|
z-index: 9;
|
||||||
flex: 0 0 auto;
|
|
||||||
background: var(--q-secondary);
|
|
||||||
color: #fff;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.statement-table {
|
.balance-table {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statement-table :deep(.q-table__container) {
|
.balance-table :deep(.q-table__container) {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statement-table :deep(.q-table__top) {
|
.balance-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;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 30;
|
z-index: 6;
|
||||||
height: var(--master-head-h);
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-table :deep(.q-table__middle) {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-table :deep(.header-row th) {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 8;
|
||||||
background: var(--q-primary);
|
background: var(--q-primary);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.22);
|
font-family: 'Roboto', sans-serif;
|
||||||
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) {
|
.balance-table :deep(.totals-row th) {
|
||||||
background: color-mix(in srgb, var(--q-secondary) 12%, white);
|
position: sticky;
|
||||||
|
top: 38px;
|
||||||
|
z-index: 7;
|
||||||
|
background: var(--q-secondary);
|
||||||
|
color: var(--q-dark);
|
||||||
|
font-weight: 700;
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-table :deep(.q-table__middle) {
|
||||||
|
max-height: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-table :deep(.sub-header-row td) {
|
||||||
|
background: #fff;
|
||||||
border-bottom: 2px solid rgba(0, 0, 0, 0.18);
|
border-bottom: 2px solid rgba(0, 0, 0, 0.18);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.statement-table :deep(.master-row td:first-child) {
|
.balance-table :deep(.sub-header-row td:first-child) {
|
||||||
border-left: 3px solid var(--q-primary);
|
border-left: 3px solid var(--q-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.statement-table :deep(.master-sub-row td) {
|
.balance-table :deep(.detail-host-row td) {
|
||||||
background: #f4f6fb;
|
background: #f7f7f7;
|
||||||
border-bottom: 8px solid #fff;
|
border-bottom: 10px solid #fff;
|
||||||
vertical-align: top;
|
padding-top: 10px;
|
||||||
padding: 0 !important;
|
padding-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.currency-groups {
|
.detail-wrap {
|
||||||
padding: 6px;
|
border: 1px solid rgba(0, 0, 0, 0.14);
|
||||||
background: #f8faff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.currency-group {
|
|
||||||
border-left: 4px solid var(--q-secondary);
|
border-left: 4px solid var(--q-secondary);
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
border-radius: 6px;
|
||||||
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;
|
background: #fff;
|
||||||
position: relative;
|
padding: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.currency-level-head {
|
.balance-table :deep(.header-row th) {
|
||||||
position: sticky;
|
white-space: pre-line;
|
||||||
top: var(--master-head-h);
|
line-height: 1.15;
|
||||||
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 {
|
.prbr-cell {
|
||||||
position: sticky;
|
white-space: pre-line;
|
||||||
top: calc(var(--master-head-h) + var(--lvl2-head-h));
|
word-break: break-word;
|
||||||
z-index: 26;
|
line-height: 1.25;
|
||||||
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 {
|
.balance-table :deep(th),
|
||||||
padding: 6px 8px;
|
.balance-table :deep(td),
|
||||||
border-right: 1px solid rgba(255, 255, 255, 0.2);
|
.detail-table :deep(th),
|
||||||
font-weight: 600;
|
.detail-table :deep(td) {
|
||||||
|
white-space: normal !important;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
line-height: 1.2;
|
||||||
|
padding: 4px 6px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cgh-cell:last-child {
|
.balance-table :deep(.q-table__table),
|
||||||
border-right: none;
|
.detail-table :deep(.q-table__table) {
|
||||||
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cgh-num {
|
.detail-table :deep(.q-table__middle) {
|
||||||
text-align: right;
|
overflow-x: hidden;
|
||||||
}
|
|
||||||
|
|
||||||
.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>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<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" :class="{ collapsed: filtersCollapsed }">
|
||||||
<div class="top-actions row q-col-gutter-sm items-end q-mb-sm">
|
<div class="top-actions row q-col-gutter-sm items-end q-mb-sm" :class="{ 'single-line': filtersCollapsed }">
|
||||||
<div class="col-12 col-sm-6 col-md-2">
|
<div class="col-12 col-sm-6 col-md-2">
|
||||||
<q-input
|
<q-input
|
||||||
v-model="store.filters.selectedDate"
|
v-model="store.filters.selectedDate"
|
||||||
@@ -59,7 +59,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filters-panel q-pa-sm q-mb-md">
|
<q-slide-transition>
|
||||||
|
<div v-show="!filtersCollapsed" class="filters-panel q-pa-sm q-mb-md">
|
||||||
<div class="row q-col-gutter-sm">
|
<div class="row q-col-gutter-sm">
|
||||||
<div class="col-12 col-sm-6 col-md-4">
|
<div class="col-12 col-sm-6 col-md-4">
|
||||||
<q-input
|
<q-input
|
||||||
@@ -358,7 +359,8 @@
|
|||||||
</q-select>
|
</q-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</q-slide-transition>
|
||||||
|
|
||||||
<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 }}
|
||||||
@@ -408,6 +410,13 @@
|
|||||||
label="Excel"
|
label="Excel"
|
||||||
@click="downloadCustomerBalanceExcel"
|
@click="downloadCustomerBalanceExcel"
|
||||||
/>
|
/>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
color="primary"
|
||||||
|
:icon="filtersCollapsed ? 'unfold_more' : 'unfold_less'"
|
||||||
|
:label="filtersCollapsed ? 'Filtreleri Genişlet' : 'Filtreleri Daralt'"
|
||||||
|
@click="toggleFiltersCollapsed"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -501,6 +510,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</q-table>
|
</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">
|
||||||
@@ -520,6 +530,7 @@ 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 filtersCollapsed = ref(false)
|
||||||
const $q = useQuasar()
|
const $q = useQuasar()
|
||||||
|
|
||||||
const { canRead, canExport } = usePermission()
|
const { canRead, canExport } = usePermission()
|
||||||
@@ -643,6 +654,10 @@ function onToggle13Changed (val) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleFiltersCollapsed () {
|
||||||
|
filtersCollapsed.value = !filtersCollapsed.value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function toggleGroup (key) {
|
function toggleGroup (key) {
|
||||||
expanded.value[key] = !expanded.value[key]
|
expanded.value[key] = !expanded.value[key]
|
||||||
@@ -844,6 +859,28 @@ function formatRowPrBr (row, tip) {
|
|||||||
padding-bottom: 6px;
|
padding-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter-sticky.collapsed {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-actions.single-line {
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-actions.single-line > [class*='col-'],
|
||||||
|
.top-actions.single-line > .col-auto {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
min-width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-actions.single-line > .col-auto {
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.filters-panel {
|
.filters-panel {
|
||||||
border: 1px solid rgba(0, 0, 0, 0.12);
|
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|||||||
@@ -11,6 +11,14 @@
|
|||||||
:loading="store.loading"
|
:loading="store.loading"
|
||||||
@click="refreshAll"
|
@click="refreshAll"
|
||||||
/>
|
/>
|
||||||
|
<q-btn
|
||||||
|
class="q-ml-sm"
|
||||||
|
color="secondary"
|
||||||
|
icon="save"
|
||||||
|
label="Secili Degisiklikleri Kaydet"
|
||||||
|
:loading="store.saving"
|
||||||
|
@click="onBulkSubmit"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filter-bar row q-col-gutter-md">
|
<div class="filter-bar row q-col-gutter-md">
|
||||||
@@ -76,6 +84,27 @@
|
|||||||
:rows-per-page-options="[0]"
|
:rows-per-page-options="[0]"
|
||||||
hide-bottom
|
hide-bottom
|
||||||
>
|
>
|
||||||
|
<template #header-cell-select="props">
|
||||||
|
<q-th :props="props" class="text-center" style="width: 44px">
|
||||||
|
<q-checkbox
|
||||||
|
size="sm"
|
||||||
|
:model-value="allSelectedVisible"
|
||||||
|
:indeterminate="someSelectedVisible && !allSelectedVisible"
|
||||||
|
@update:model-value="toggleSelectAllVisible"
|
||||||
|
/>
|
||||||
|
</q-th>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body-cell-select="props">
|
||||||
|
<q-td :props="props" class="text-center" style="width: 44px">
|
||||||
|
<q-checkbox
|
||||||
|
size="sm"
|
||||||
|
:model-value="!!selectedMap[props.row.RowKey]"
|
||||||
|
@update:model-value="(val) => toggleRowSelection(props.row.RowKey, val)"
|
||||||
|
/>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template #body-cell-actions="props">
|
<template #body-cell-actions="props">
|
||||||
<q-td :props="props" class="text-center">
|
<q-td :props="props" class="text-center">
|
||||||
<q-btn
|
<q-btn
|
||||||
@@ -98,6 +127,7 @@
|
|||||||
v-model="props.row.NewItemCode"
|
v-model="props.row.NewItemCode"
|
||||||
dense
|
dense
|
||||||
filled
|
filled
|
||||||
|
maxlength="13"
|
||||||
label="Yeni Urun"
|
label="Yeni Urun"
|
||||||
@update:model-value="val => onNewItemChange(props.row, val)"
|
@update:model-value="val => onNewItemChange(props.row, val)"
|
||||||
>
|
>
|
||||||
@@ -143,10 +173,12 @@
|
|||||||
emit-value
|
emit-value
|
||||||
map-options
|
map-options
|
||||||
use-input
|
use-input
|
||||||
|
new-value-mode="add-unique"
|
||||||
dense
|
dense
|
||||||
filled
|
filled
|
||||||
label="Yeni Renk"
|
label="Yeni Renk"
|
||||||
@update:model-value="() => onNewColorChange(props.row)"
|
@update:model-value="() => onNewColorChange(props.row)"
|
||||||
|
@new-value="(val, done) => onCreateColorValue(props.row, val, done)"
|
||||||
/>
|
/>
|
||||||
</q-td>
|
</q-td>
|
||||||
</template>
|
</template>
|
||||||
@@ -161,9 +193,11 @@
|
|||||||
emit-value
|
emit-value
|
||||||
map-options
|
map-options
|
||||||
use-input
|
use-input
|
||||||
|
new-value-mode="add-unique"
|
||||||
dense
|
dense
|
||||||
filled
|
filled
|
||||||
label="Yeni 2. Renk"
|
label="Yeni 2. Renk"
|
||||||
|
@new-value="(val, done) => onCreateSecondColorValue(props.row, val, done)"
|
||||||
/>
|
/>
|
||||||
</q-td>
|
</q-td>
|
||||||
</template>
|
</template>
|
||||||
@@ -214,8 +248,10 @@ const descFilter = ref('')
|
|||||||
const productOptions = ref([])
|
const productOptions = ref([])
|
||||||
const productSearch = ref('')
|
const productSearch = ref('')
|
||||||
const rowSavingId = ref('')
|
const rowSavingId = ref('')
|
||||||
|
const selectedMap = ref({})
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
|
{ name: 'select', label: '', field: 'select', align: 'center', sortable: false, style: 'width:44px;', headerStyle: 'width:44px;' },
|
||||||
{ name: 'OldItemCode', label: 'Eski Urun Kodu', field: 'OldItemCode', align: 'left', sortable: true, style: 'min-width:120px;white-space:nowrap', headerStyle: 'min-width:120px;white-space:nowrap', headerClasses: 'col-old', classes: 'col-old' },
|
{ name: 'OldItemCode', label: 'Eski Urun Kodu', field: 'OldItemCode', align: 'left', sortable: true, style: 'min-width:120px;white-space:nowrap', headerStyle: 'min-width:120px;white-space:nowrap', headerClasses: 'col-old', classes: 'col-old' },
|
||||||
{ name: 'OldColor', label: 'Eski Urun Rengi', field: 'OldColor', align: 'left', sortable: true, style: 'min-width:100px;white-space:nowrap', headerStyle: 'min-width:100px;white-space:nowrap', headerClasses: 'col-old', classes: 'col-old' },
|
{ name: 'OldColor', label: 'Eski Urun Rengi', field: 'OldColor', align: 'left', sortable: true, style: 'min-width:100px;white-space:nowrap', headerStyle: 'min-width:100px;white-space:nowrap', headerClasses: 'col-old', classes: 'col-old' },
|
||||||
{ name: 'OldDim2', label: 'Eski 2. Renk', field: 'OldDim2', align: 'left', sortable: true, style: 'min-width:90px;white-space:nowrap', headerStyle: 'min-width:90px;white-space:nowrap', headerClasses: 'col-old', classes: 'col-old' },
|
{ name: 'OldDim2', label: 'Eski 2. Renk', field: 'OldDim2', align: 'left', sortable: true, style: 'min-width:90px;white-space:nowrap', headerStyle: 'min-width:90px;white-space:nowrap', headerClasses: 'col-old', classes: 'col-old' },
|
||||||
@@ -274,18 +310,21 @@ const filteredRows = computed(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const visibleRowKeys = computed(() => filteredRows.value.map(r => r.RowKey))
|
||||||
|
const selectedVisibleCount = computed(() => visibleRowKeys.value.filter(k => !!selectedMap.value[k]).length)
|
||||||
|
const allSelectedVisible = computed(() => visibleRowKeys.value.length > 0 && selectedVisibleCount.value === visibleRowKeys.value.length)
|
||||||
|
const someSelectedVisible = computed(() => selectedVisibleCount.value > 0)
|
||||||
|
|
||||||
function onSelectProduct (row, code) {
|
function onSelectProduct (row, code) {
|
||||||
productSearch.value = ''
|
productSearch.value = ''
|
||||||
onNewItemChange(row, code)
|
onNewItemChange(row, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onNewItemChange (row, val) {
|
function onNewItemChange (row, val) {
|
||||||
const next = String(val || '').trim()
|
const next = String(val || '').trim().toUpperCase()
|
||||||
if (next && !isValidModelCode(next)) {
|
if (next.length > 13) {
|
||||||
$q.notify({ type: 'negative', message: 'Model kodu formati gecersiz. Ornek: S000-DMY00001' })
|
$q.notify({ type: 'negative', message: 'Model kodu en fazla 13 karakter olabilir.' })
|
||||||
row.NewItemCode = ''
|
row.NewItemCode = next.slice(0, 13)
|
||||||
row.NewColor = ''
|
|
||||||
row.NewDim2 = ''
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
row.NewItemCode = next ? next.toUpperCase() : ''
|
row.NewItemCode = next ? next.toUpperCase() : ''
|
||||||
@@ -297,6 +336,7 @@ function onNewItemChange (row, val) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onNewColorChange (row) {
|
function onNewColorChange (row) {
|
||||||
|
row.NewColor = normalizeShortCode(row.NewColor, 3)
|
||||||
row.NewDim2 = ''
|
row.NewDim2 = ''
|
||||||
if (row.NewItemCode && row.NewColor) {
|
if (row.NewItemCode && row.NewColor) {
|
||||||
store.fetchSecondColors(row.NewItemCode, row.NewColor)
|
store.fetchSecondColors(row.NewItemCode, row.NewColor)
|
||||||
@@ -319,9 +359,91 @@ function getSecondColorOptions (row) {
|
|||||||
return store.secondColorOptionsByKey[key] || []
|
return store.secondColorOptionsByKey[key] || []
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidModelCode (value) {
|
function toggleRowSelection (rowKey, checked) {
|
||||||
const text = String(value || '').trim().toUpperCase()
|
const next = { ...selectedMap.value }
|
||||||
return /^[A-Z][0-9]{3}-[A-Z]{3}[0-9]{5}$/.test(text)
|
if (checked) next[rowKey] = true
|
||||||
|
else delete next[rowKey]
|
||||||
|
selectedMap.value = next
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSelectAllVisible (checked) {
|
||||||
|
const next = { ...selectedMap.value }
|
||||||
|
for (const key of visibleRowKeys.value) {
|
||||||
|
if (checked) next[key] = true
|
||||||
|
else delete next[key]
|
||||||
|
}
|
||||||
|
selectedMap.value = next
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCreateColorValue (row, val, done) {
|
||||||
|
const code = normalizeShortCode(val, 3)
|
||||||
|
if (!code) {
|
||||||
|
done(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
row.NewColor = code
|
||||||
|
onNewColorChange(row)
|
||||||
|
done(code, 'add-unique')
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCreateSecondColorValue (row, val, done) {
|
||||||
|
const code = normalizeShortCode(val, 3)
|
||||||
|
if (!code) {
|
||||||
|
done(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
row.NewDim2 = code
|
||||||
|
done(code, 'add-unique')
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeShortCode (value, maxLen) {
|
||||||
|
return String(value || '').trim().toUpperCase().slice(0, maxLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateRowInput (row) {
|
||||||
|
const newItemCode = String(row.NewItemCode || '').trim().toUpperCase()
|
||||||
|
const newColor = normalizeShortCode(row.NewColor, 3)
|
||||||
|
const newDim2 = normalizeShortCode(row.NewDim2, 3)
|
||||||
|
const oldColor = String(row.OldColor || '').trim()
|
||||||
|
const oldDim2 = String(row.OldDim2 || '').trim()
|
||||||
|
|
||||||
|
if (!newItemCode) return 'Yeni model kodu zorunludur.'
|
||||||
|
if (newItemCode.length !== 13) return 'Yeni model kodu 13 karakter olmalidir.'
|
||||||
|
if (oldColor && !newColor) return 'Eski kayitta 1. renk oldugu icin yeni 1. renk zorunludur.'
|
||||||
|
if (newColor && newColor.length !== 3) return 'Yeni 1. renk kodu 3 karakter olmalidir.'
|
||||||
|
if (oldDim2 && !newDim2) return 'Eski kayitta 2. renk oldugu icin yeni 2. renk zorunludur.'
|
||||||
|
if (newDim2 && newDim2.length !== 3) return 'Yeni 2. renk kodu 3 karakter olmalidir.'
|
||||||
|
if (newDim2 && !newColor) return '2. renk girmek icin 1. renk zorunludur.'
|
||||||
|
|
||||||
|
row.NewItemCode = newItemCode
|
||||||
|
row.NewColor = newColor
|
||||||
|
row.NewDim2 = newDim2
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function collectLinesFromRows (selectedRows) {
|
||||||
|
const lines = []
|
||||||
|
for (const row of selectedRows) {
|
||||||
|
const errMsg = validateRowInput(row)
|
||||||
|
if (errMsg) {
|
||||||
|
return { errMsg, lines: [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseLine = {
|
||||||
|
NewItemCode: String(row.NewItemCode || '').trim().toUpperCase(),
|
||||||
|
NewColor: normalizeShortCode(row.NewColor, 3),
|
||||||
|
NewDim2: normalizeShortCode(row.NewDim2, 3),
|
||||||
|
NewDesc: String((row.NewDesc || row.OldDesc) || '').trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const id of (row.OrderLineIDs || [])) {
|
||||||
|
lines.push({
|
||||||
|
OrderLineID: id,
|
||||||
|
...baseLine
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { errMsg: '', lines }
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildGroupKey (item) {
|
function buildGroupKey (item) {
|
||||||
@@ -416,23 +538,12 @@ async function refreshAll () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function onRowSubmit (row) {
|
async function onRowSubmit (row) {
|
||||||
const baseLine = {
|
const { errMsg, lines } = collectLinesFromRows([row])
|
||||||
NewItemCode: String(row.NewItemCode || '').trim(),
|
if (errMsg) {
|
||||||
NewColor: String(row.NewColor || '').trim(),
|
$q.notify({ type: 'negative', message: errMsg })
|
||||||
NewDim2: String(row.NewDim2 || '').trim(),
|
|
||||||
NewDesc: String((row.NewDesc || row.OldDesc) || '').trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!baseLine.NewItemCode || !baseLine.NewColor) {
|
|
||||||
$q.notify({ type: 'negative', message: 'Yeni urun ve renk zorunludur.' })
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines = (row.OrderLineIDs || []).map(id => ({
|
|
||||||
OrderLineID: id,
|
|
||||||
...baseLine
|
|
||||||
}))
|
|
||||||
|
|
||||||
if (!lines.length) {
|
if (!lines.length) {
|
||||||
$q.notify({ type: 'negative', message: 'Satir bulunamadi.' })
|
$q.notify({ type: 'negative', message: 'Satir bulunamadi.' })
|
||||||
return
|
return
|
||||||
@@ -467,6 +578,52 @@ async function onRowSubmit (row) {
|
|||||||
rowSavingId.value = ''
|
rowSavingId.value = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function onBulkSubmit () {
|
||||||
|
const selectedRows = rows.value.filter(r => !!selectedMap.value[r.RowKey])
|
||||||
|
if (!selectedRows.length) {
|
||||||
|
$q.notify({ type: 'warning', message: 'Lutfen en az bir satir seciniz.' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { errMsg, lines } = collectLinesFromRows(selectedRows)
|
||||||
|
if (errMsg) {
|
||||||
|
$q.notify({ type: 'negative', message: errMsg })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!lines.length) {
|
||||||
|
$q.notify({ type: 'negative', message: 'Secili satirlarda guncellenecek kayit bulunamadi.' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const validate = await store.validateUpdates(orderHeaderID.value, lines)
|
||||||
|
const missingCount = validate?.missingCount || 0
|
||||||
|
if (missingCount > 0) {
|
||||||
|
const missingList = (validate?.missing || []).map(v => (
|
||||||
|
`${v.ItemCode} / ${v.ColorCode} / ${v.ItemDim1Code} / ${v.ItemDim2Code}`
|
||||||
|
))
|
||||||
|
$q.dialog({
|
||||||
|
title: 'Eksik Varyantlar',
|
||||||
|
message: `Eksik varyant bulundu: ${missingCount}<br><br>${missingList.join('<br>')}`,
|
||||||
|
html: true,
|
||||||
|
ok: { label: 'Ekle ve Guncelle', color: 'primary' },
|
||||||
|
cancel: { label: 'Vazgec', flat: true }
|
||||||
|
}).onOk(async () => {
|
||||||
|
await store.applyUpdates(orderHeaderID.value, lines, true)
|
||||||
|
await store.fetchItems(orderHeaderID.value)
|
||||||
|
selectedMap.value = {}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await store.applyUpdates(orderHeaderID.value, lines, false)
|
||||||
|
await store.fetchItems(orderHeaderID.value)
|
||||||
|
selectedMap.value = {}
|
||||||
|
} catch (err) {
|
||||||
|
$q.notify({ type: 'negative', message: 'Toplu kayit islemi basarisiz.' })
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ const routes = [
|
|||||||
{
|
{
|
||||||
path: 'aged-customer-balance-list',
|
path: 'aged-customer-balance-list',
|
||||||
name: 'aged-customer-balance-list',
|
name: 'aged-customer-balance-list',
|
||||||
component: () => import('pages/AgedCustomerBalanceListDummy.vue'),
|
component: () => import('pages/AccountAgingStatement.vue'),
|
||||||
meta: { permission: 'finance:view' }
|
meta: { permission: 'finance:view' }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
263
ui/src/stores/accountAgingBalanceStore.js
Normal file
263
ui/src/stores/accountAgingBalanceStore.js
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import api from 'src/services/api'
|
||||||
|
|
||||||
|
export const useAccountAgingBalanceStore = defineStore('accountAgingBalance', {
|
||||||
|
state: () => ({
|
||||||
|
filters: {
|
||||||
|
selectedDate: new Date().toISOString().slice(0, 10),
|
||||||
|
excludeZeroBalance12: false,
|
||||||
|
excludeZeroBalance13: false,
|
||||||
|
cariSearch: '',
|
||||||
|
cariIlkGrup: [],
|
||||||
|
piyasa: [],
|
||||||
|
temsilci: [],
|
||||||
|
riskDurumu: [],
|
||||||
|
islemTipi: [],
|
||||||
|
ulke: [],
|
||||||
|
il: [],
|
||||||
|
ilce: []
|
||||||
|
},
|
||||||
|
rows: [],
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
hasFetched: false,
|
||||||
|
defaultsInitialized: false
|
||||||
|
}),
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
cariIlkGrupOptions: (state) => uniqueOptions(state.rows, 'cari_ilk_grup'),
|
||||||
|
piyasaOptions: (state) => uniqueOptions(state.rows, 'piyasa'),
|
||||||
|
temsilciOptions: (state) => uniqueOptions(state.rows, 'temsilci'),
|
||||||
|
riskDurumuOptions: (state) => uniqueOptions(state.rows, 'risk_durumu'),
|
||||||
|
ulkeOptions: (state) => uniqueOptions(state.rows, 'ozellik05'),
|
||||||
|
ilOptions: (state) => uniqueOptions(state.rows, 'il'),
|
||||||
|
ilceOptions: (state) => uniqueOptions(state.rows, 'ilce'),
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
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 = matchMulti(selectedCariIlkGrup, row.cari_ilk_grup)
|
||||||
|
const piyasaOk = matchMulti(selectedPiyasa, row.piyasa)
|
||||||
|
const temsilciOk = matchMulti(selectedTemsilci, row.temsilci)
|
||||||
|
const riskDurumuOk = matchMulti(selectedRiskDurumu, row.risk_durumu)
|
||||||
|
|
||||||
|
const cariText = normalizeText([
|
||||||
|
row.ana_cari_kodu || '',
|
||||||
|
row.ana_cari_adi || '',
|
||||||
|
row.cari_kodu || '',
|
||||||
|
row.cari_detay || ''
|
||||||
|
].join(' '))
|
||||||
|
const cariSearchOk = !cariSearchNeedle || cariText.includes(cariSearchNeedle)
|
||||||
|
|
||||||
|
const ulkeOk = matchMulti(selectedUlke, row.ozellik05)
|
||||||
|
const ilOk = matchMulti(selectedIl, row.il)
|
||||||
|
const ilceOk = matchMulti(selectedIlce, row.ilce)
|
||||||
|
|
||||||
|
const islemTipiOk =
|
||||||
|
!state.filters.islemTipi.length ||
|
||||||
|
state.filters.islemTipi.some((t) => {
|
||||||
|
if (t === 'prbr_1_2') return bak12 !== 0
|
||||||
|
if (t === 'prbr_1_3') return bak13 !== 0
|
||||||
|
if (t === 'usd_1_2') return usd12 !== 0
|
||||||
|
if (t === 'try_1_2') return try12 !== 0
|
||||||
|
if (t === 'usd_1_3') return usd13 !== 0
|
||||||
|
if (t === 'try_1_3') return try13 !== 0
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
summaryRows () {
|
||||||
|
const grouped = new Map()
|
||||||
|
|
||||||
|
for (const row of this.filteredRows) {
|
||||||
|
const key = String(row.ana_cari_kodu || '').trim()
|
||||||
|
const current = grouped.get(key) || {
|
||||||
|
group_key: key,
|
||||||
|
ana_cari_kodu: key,
|
||||||
|
ana_cari_adi: row.ana_cari_adi || '',
|
||||||
|
piyasa: '',
|
||||||
|
piyasa_set: new Set(),
|
||||||
|
temsilci: '',
|
||||||
|
temsilci_set: new Set(),
|
||||||
|
risk_durumu: '',
|
||||||
|
risk_set: new Set(),
|
||||||
|
bakiye_1_2_map: {},
|
||||||
|
bakiye_1_3_map: {},
|
||||||
|
usd_bakiye_1_2: 0,
|
||||||
|
tl_bakiye_1_2: 0,
|
||||||
|
usd_bakiye_1_3: 0,
|
||||||
|
tl_bakiye_1_3: 0,
|
||||||
|
vade_gun_sum: 0,
|
||||||
|
vade_belge_sum: 0,
|
||||||
|
vade_count: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
current.usd_bakiye_1_2 += Number(row.usd_bakiye_1_2) || 0
|
||||||
|
current.tl_bakiye_1_2 += Number(row.tl_bakiye_1_2) || 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
|
||||||
|
|
||||||
|
const vadeGun = Number(row.vade_gun) || 0
|
||||||
|
const vadeBelge = Number(row.vade_belge_tarihi_gun) || 0
|
||||||
|
if (vadeGun !== 0 || vadeBelge !== 0) {
|
||||||
|
current.vade_gun_sum += vadeGun
|
||||||
|
current.vade_belge_sum += vadeBelge
|
||||||
|
current.vade_count += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
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'
|
||||||
|
current.bakiye_1_2_map[curr] =
|
||||||
|
(Number(current.bakiye_1_2_map[curr]) || 0) + (Number(row.bakiye_1_2) || 0)
|
||||||
|
current.bakiye_1_3_map[curr] =
|
||||||
|
(Number(current.bakiye_1_3_map[curr]) || 0) + (Number(row.bakiye_1_3) || 0)
|
||||||
|
|
||||||
|
const piyasa = String(row.piyasa || '').trim()
|
||||||
|
if (piyasa) current.piyasa_set.add(piyasa)
|
||||||
|
const temsilci = String(row.temsilci || '').trim()
|
||||||
|
if (temsilci) current.temsilci_set.add(temsilci)
|
||||||
|
const risk = String(row.risk_durumu || row.ozellik03 || '').trim()
|
||||||
|
if (risk) current.risk_set.add(risk)
|
||||||
|
|
||||||
|
const riskValues = Array.from(current.risk_set)
|
||||||
|
current.risk_durumu = riskValues.length <= 1 ? (riskValues[0] || '-') : riskValues.join(', ')
|
||||||
|
const piyasaValues = Array.from(current.piyasa_set)
|
||||||
|
current.piyasa = piyasaValues.length <= 1 ? (piyasaValues[0] || '-') : piyasaValues.join(', ')
|
||||||
|
const temsilciValues = Array.from(current.temsilci_set)
|
||||||
|
current.temsilci = temsilciValues.length <= 1 ? (temsilciValues[0] || '-') : temsilciValues.join(', ')
|
||||||
|
|
||||||
|
grouped.set(key, current)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(grouped.values()).map((r) => {
|
||||||
|
const { risk_set, piyasa_set, temsilci_set, vade_gun_sum, vade_belge_sum, vade_count, ...rest } = r
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
vade_gun: vade_count > 0 ? vade_gun_sum / vade_count : 0,
|
||||||
|
vade_belge_tarihi_gun: vade_count > 0 ? vade_belge_sum / vade_count : 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
async fetchBalances () {
|
||||||
|
this.loading = true
|
||||||
|
this.error = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.filters.selectedDate = new Date().toISOString().slice(0, 10)
|
||||||
|
const { data } = await api.get('/finance/account-aging-statement', {
|
||||||
|
params: {
|
||||||
|
cari_search: String(this.filters.cariSearch || '').trim(),
|
||||||
|
cari_ilk_grup: (this.filters.cariIlkGrup || []).join(','),
|
||||||
|
piyasa: (this.filters.piyasa || []).join(','),
|
||||||
|
temsilci: (this.filters.temsilci || []).join(','),
|
||||||
|
risk_durumu: (this.filters.riskDurumu || []).join(','),
|
||||||
|
islem_tipi: (this.filters.islemTipi || []).join(','),
|
||||||
|
ulke: (this.filters.ulke || []).join(','),
|
||||||
|
il: (this.filters.il || []).join(','),
|
||||||
|
ilce: (this.filters.ilce || []).join(',')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.rows = Array.isArray(data) ? data : []
|
||||||
|
if (!this.defaultsInitialized) {
|
||||||
|
this.applyInitialFilterDefaults()
|
||||||
|
this.defaultsInitialized = true
|
||||||
|
}
|
||||||
|
this.hasFetched = true
|
||||||
|
} catch (err) {
|
||||||
|
this.rows = []
|
||||||
|
this.hasFetched = false
|
||||||
|
this.error = err?.response?.data?.message || err?.message || 'Cari yaşlandırmalı bakiye listesi getirilemedi.'
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getDetailsByGroup (groupKey) {
|
||||||
|
return this.filteredRows.filter(r => String(r.ana_cari_kodu || '').trim() === String(groupKey || '').trim())
|
||||||
|
},
|
||||||
|
|
||||||
|
resetFilters () {
|
||||||
|
this.filters.excludeZeroBalance12 = false
|
||||||
|
this.filters.excludeZeroBalance13 = false
|
||||||
|
this.filters.cariSearch = ''
|
||||||
|
this.filters.cariIlkGrup = []
|
||||||
|
this.filters.piyasa = []
|
||||||
|
this.filters.temsilci = []
|
||||||
|
this.filters.riskDurumu = []
|
||||||
|
this.filters.islemTipi = []
|
||||||
|
this.filters.ulke = []
|
||||||
|
this.filters.il = []
|
||||||
|
this.filters.ilce = []
|
||||||
|
this.defaultsInitialized = false
|
||||||
|
},
|
||||||
|
|
||||||
|
selectAll (field, options) {
|
||||||
|
this.filters[field] = options.map(o => o.value)
|
||||||
|
},
|
||||||
|
|
||||||
|
clearAll (field) {
|
||||||
|
this.filters[field] = []
|
||||||
|
},
|
||||||
|
|
||||||
|
applyInitialFilterDefaults () {
|
||||||
|
const excludedCariIlkGrup = new Set([normalizeText('transfer'), normalizeText('perakende'), normalizeText('dtf')])
|
||||||
|
this.filters.cariIlkGrup = this.cariIlkGrupOptions.map(o => o.value).filter(v => !excludedCariIlkGrup.has(normalizeText(v)))
|
||||||
|
|
||||||
|
const excludedRisk = new Set([normalizeText('avukat'), normalizeText('orta risk'), normalizeText('yuksek risk')])
|
||||||
|
this.filters.riskDurumu = this.riskDurumuOptions.map(o => o.value).filter(v => !excludedRisk.has(normalizeText(v)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function uniqueOptions (rows, field) {
|
||||||
|
const set = new Set()
|
||||||
|
for (const r of rows) {
|
||||||
|
const v = String(r[field] || '').trim()
|
||||||
|
if (v) set.add(v)
|
||||||
|
}
|
||||||
|
return Array.from(set).sort((a, b) => a.localeCompare(b, 'tr')).map(v => ({ label: v, value: v }))
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeText (str) {
|
||||||
|
return String(str || '')
|
||||||
|
.toLocaleLowerCase('tr-TR')
|
||||||
|
.normalize('NFD')
|
||||||
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
|
.trim()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user