Merge remote-tracking branch 'origin/master'
This commit is contained in:
101
svc/main.go
101
svc/main.go
@@ -10,7 +10,6 @@ import (
|
|||||||
"bssapp-backend/routes"
|
"bssapp-backend/routes"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"log"
|
"log"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -165,7 +164,6 @@ InitRoutes — FULL V3 (Method-aware) PERMISSION EDITION
|
|||||||
func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router {
|
func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router {
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
mountUploads(r)
|
|
||||||
mountSPA(r)
|
mountSPA(r)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -450,24 +448,21 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
|
|||||||
"finance", "view",
|
"finance", "view",
|
||||||
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,
|
bindV3(r, pgDB,
|
||||||
"/api/finance/account-aging-statement/export-excel", "GET",
|
"/api/finance/account-aging-statement/export-excel", "GET",
|
||||||
"finance", "export",
|
"finance", "export",
|
||||||
wrapV3(routes.ExportStatementAgingExcelHandler(mssql)),
|
wrapV3(routes.ExportStatementAgingExcelHandler(mssql)),
|
||||||
)
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/finance/aged-customer-balance-list", "GET",
|
||||||
|
"finance", "view",
|
||||||
|
wrapV3(http.HandlerFunc(routes.GetAgedCustomerBalanceListHandler)),
|
||||||
|
)
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// REPORT (STATEMENTS)
|
// REPORT (STATEMENTS)
|
||||||
@@ -508,7 +503,6 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
|
|||||||
{"/api/orders/close-ready", "GET", "update", routes.OrderCloseReadyListRoute(mssql)},
|
{"/api/orders/close-ready", "GET", "update", routes.OrderCloseReadyListRoute(mssql)},
|
||||||
{"/api/orders/bulk-close", "POST", "update", routes.OrderBulkCloseRoute(mssql)},
|
{"/api/orders/bulk-close", "POST", "update", routes.OrderBulkCloseRoute(mssql)},
|
||||||
{"/api/orders/export", "GET", "export", routes.OrderListExcelRoute(mssql)},
|
{"/api/orders/export", "GET", "export", routes.OrderListExcelRoute(mssql)},
|
||||||
{"/api/orders/export-pdf", "GET", "export", routes.OrderListPDFRoute(mssql)},
|
|
||||||
{"/api/order/check/{id}", "GET", "view", routes.OrderExistsHandler(mssql)},
|
{"/api/order/check/{id}", "GET", "view", routes.OrderExistsHandler(mssql)},
|
||||||
{"/api/order/validate", "POST", "insert", routes.ValidateOrderHandler(mssql)},
|
{"/api/order/validate", "POST", "insert", routes.ValidateOrderHandler(mssql)},
|
||||||
{"/api/order/pdf/{id}", "GET", "export", routes.OrderPDFHandler(mssql)},
|
{"/api/order/pdf/{id}", "GET", "export", routes.OrderPDFHandler(mssql)},
|
||||||
@@ -558,36 +552,6 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
|
|||||||
wrapV3(http.HandlerFunc(routes.GetProductSecondColorsHandler)),
|
wrapV3(http.HandlerFunc(routes.GetProductSecondColorsHandler)),
|
||||||
)
|
)
|
||||||
|
|
||||||
bindV3(r, pgDB,
|
|
||||||
"/api/product-stock-query", "GET",
|
|
||||||
"order", "view",
|
|
||||||
wrapV3(http.HandlerFunc(routes.GetProductStockQueryHandler)),
|
|
||||||
)
|
|
||||||
|
|
||||||
bindV3(r, pgDB,
|
|
||||||
"/api/product-stock-attribute-options", "GET",
|
|
||||||
"order", "view",
|
|
||||||
wrapV3(http.HandlerFunc(routes.GetProductStockAttributeOptionsHandler)),
|
|
||||||
)
|
|
||||||
|
|
||||||
bindV3(r, pgDB,
|
|
||||||
"/api/product-stock-query-by-attributes", "GET",
|
|
||||||
"order", "view",
|
|
||||||
wrapV3(http.HandlerFunc(routes.GetProductStockQueryByAttributesHandler)),
|
|
||||||
)
|
|
||||||
|
|
||||||
bindV3(r, pgDB,
|
|
||||||
"/api/product-images", "GET",
|
|
||||||
"order", "view",
|
|
||||||
wrapV3(routes.GetProductImagesHandler(pgDB)),
|
|
||||||
)
|
|
||||||
|
|
||||||
bindV3(r, pgDB,
|
|
||||||
"/api/product-images/{id}/content", "GET",
|
|
||||||
"order", "view",
|
|
||||||
http.HandlerFunc(routes.GetProductImageContentHandler(pgDB)),
|
|
||||||
)
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// ROLE MANAGEMENT
|
// ROLE MANAGEMENT
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -645,35 +609,7 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
|
|||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupSlog() {
|
|
||||||
level := new(slog.LevelVar)
|
|
||||||
switch strings.ToLower(strings.TrimSpace(os.Getenv("LOG_LEVEL"))) {
|
|
||||||
case "debug":
|
|
||||||
level.Set(slog.LevelDebug)
|
|
||||||
case "warn", "warning":
|
|
||||||
level.Set(slog.LevelWarn)
|
|
||||||
case "error":
|
|
||||||
level.Set(slog.LevelError)
|
|
||||||
default:
|
|
||||||
level.Set(slog.LevelInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
|
||||||
Level: level,
|
|
||||||
})
|
|
||||||
slog.SetDefault(slog.New(handler))
|
|
||||||
}
|
|
||||||
|
|
||||||
func mountUploads(r *mux.Router) {
|
|
||||||
root := uploadsRootDir()
|
|
||||||
log.Printf("🖼️ uploads root: %s", root)
|
|
||||||
r.PathPrefix("/uploads/").Handler(
|
|
||||||
http.StripPrefix("/uploads/", http.FileServer(http.Dir(root))),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
setupSlog()
|
|
||||||
log.Println("🔥🔥🔥 BSSAPP BACKEND STARTED — LOGIN ROUTE SHOULD EXIST 🔥🔥🔥")
|
log.Println("🔥🔥🔥 BSSAPP BACKEND STARTED — LOGIN ROUTE SHOULD EXIST 🔥🔥🔥")
|
||||||
|
|
||||||
// -------------------------------------------------------
|
// -------------------------------------------------------
|
||||||
@@ -838,30 +774,3 @@ func uiRootDir() string {
|
|||||||
|
|
||||||
return "../ui/dist/spa"
|
return "../ui/dist/spa"
|
||||||
}
|
}
|
||||||
|
|
||||||
func uploadsRootDir() string {
|
|
||||||
if root := strings.TrimSpace(os.Getenv("BLOB_ROOT")); root != "" {
|
|
||||||
candidates := []string{
|
|
||||||
root,
|
|
||||||
filepath.Join(root, "uploads"),
|
|
||||||
}
|
|
||||||
for _, d := range candidates {
|
|
||||||
if fi, err := os.Stat(d); err == nil && fi.IsDir() {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
candidates := []string{
|
|
||||||
"./uploads",
|
|
||||||
"../uploads",
|
|
||||||
"../../uploads",
|
|
||||||
}
|
|
||||||
for _, d := range candidates {
|
|
||||||
if fi, err := os.Stat(d); err == nil && fi.IsDir() {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "./uploads"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ type StatementParams struct {
|
|||||||
EndDate string `json:"enddate"`
|
EndDate string `json:"enddate"`
|
||||||
AccountCode string `json:"accountcode"`
|
AccountCode string `json:"accountcode"`
|
||||||
LangCode string `json:"langcode"`
|
LangCode string `json:"langcode"`
|
||||||
Parislemler []string `json:"parislemler"` // ✅ slice olmalı
|
Parislemler []string `json:"parislemler"`
|
||||||
|
ExcludeOpening bool `json:"excludeopening"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -15,59 +14,34 @@ 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(params.EndDate) == "" {
|
||||||
|
return nil, fmt.Errorf("enddate is required")
|
||||||
|
}
|
||||||
|
|
||||||
useType2, useType3 := resolveUseTypes(params.Parislemler)
|
useType2, useType3 := resolveUseTypes(params.Parislemler)
|
||||||
endDateText := strings.TrimSpace(params.EndDate)
|
rateMap, err := loadNearestTryRates(context.Background())
|
||||||
if endDateText == "" {
|
if err != nil {
|
||||||
endDateText = time.Now().Format("2006-01-02")
|
return nil, err
|
||||||
}
|
}
|
||||||
endDate, _ := time.Parse("2006-01-02", endDateText)
|
usdTry := rateMap["USD"]
|
||||||
|
if usdTry <= 0 {
|
||||||
cariFilter := ""
|
usdTry = 1
|
||||||
if strings.TrimSpace(accountCode) != "" {
|
|
||||||
cariFilter = strings.TrimSpace(accountCode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := db.MssqlDB.Query(`
|
rows, err := db.MssqlDB.Query(`
|
||||||
SELECT TOP (100)
|
EXEC dbo.SP_FIFO_MATCH_FINAL
|
||||||
Cari8 = LEFT(LTRIM(RTRIM(CariKodu)), 8),
|
@Cari8 = @Cari8,
|
||||||
CariDetay = LTRIM(RTRIM(CariKodu)),
|
@SonTarih = @SonTarih,
|
||||||
FaturaCari = LTRIM(RTRIM(CariKodu)),
|
@UseType2 = @UseType2,
|
||||||
OdemeCari = LTRIM(RTRIM(CariKodu)),
|
@UseType3 = @UseType3;
|
||||||
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("CARI_BAKIYE_GUN_CACHE query error: %w", err)
|
return nil, fmt.Errorf("SP_FIFO_MATCH_FINAL query error: %w", err)
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
@@ -78,7 +52,6 @@ func GetStatementAging(params models.StatementAgingParams) ([]map[string]interfa
|
|||||||
|
|
||||||
result := make([]map[string]interface{}, 0, 2048)
|
result := make([]map[string]interface{}, 0, 2048)
|
||||||
cari8Set := make(map[string]struct{})
|
cari8Set := make(map[string]struct{})
|
||||||
currencySet := make(map[string]struct{})
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
values := make([]interface{}, len(columns))
|
values := make([]interface{}, len(columns))
|
||||||
scanArgs := make([]interface{}, len(columns))
|
scanArgs := make([]interface{}, len(columns))
|
||||||
@@ -108,11 +81,6 @@ func GetStatementAging(params models.StatementAgingParams) ([]map[string]interfa
|
|||||||
if cari8 != "" {
|
if cari8 != "" {
|
||||||
cari8Set[cari8] = struct{}{}
|
cari8Set[cari8] = struct{}{}
|
||||||
}
|
}
|
||||||
curr := strings.ToUpper(strings.TrimSpace(asString(row["DocCurrencyCode"])))
|
|
||||||
if curr != "" && curr != "TRY" {
|
|
||||||
currencySet[curr] = struct{}{}
|
|
||||||
}
|
|
||||||
currencySet["USD"] = struct{}{}
|
|
||||||
|
|
||||||
result = append(result, row)
|
result = append(result, row)
|
||||||
}
|
}
|
||||||
@@ -125,10 +93,6 @@ func GetStatementAging(params models.StatementAgingParams) ([]map[string]interfa
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
rateSeriesByCurr, err := loadTryRateSeriesByCurrency(context.Background(), currencySet)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range result {
|
for i := range result {
|
||||||
row := result[i]
|
row := result[i]
|
||||||
@@ -137,25 +101,13 @@ func GetStatementAging(params models.StatementAgingParams) ([]map[string]interfa
|
|||||||
if curr == "" {
|
if curr == "" {
|
||||||
curr = "TRY"
|
curr = "TRY"
|
||||||
}
|
}
|
||||||
aciklama := strings.ToUpper(strings.TrimSpace(asString(row["Aciklama"])))
|
|
||||||
targetDate := endDate
|
|
||||||
if aciklama != "ACIKKALEM" {
|
|
||||||
if odemeTarihi, ok := parseDateOnly(asString(row["OdemeTarihi"])); ok {
|
|
||||||
targetDate = odemeTarihi
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tutar := asFloat64(row["EslesenTutar"])
|
tutar := asFloat64(row["EslesenTutar"])
|
||||||
currTry := resolveTryRate(curr, targetDate, rateSeriesByCurr)
|
usdTutar := toUSD(tutar, curr, usdTry, rateMap)
|
||||||
usdTry := resolveTryRate("USD", targetDate, rateSeriesByCurr)
|
|
||||||
tryTutar := toTRYByRate(tutar, curr, currTry)
|
|
||||||
usdTutar := toUSDByRates(tutar, curr, currTry, usdTry)
|
|
||||||
gunKur := usdRateInCurrency(curr, currTry, usdTry)
|
|
||||||
|
|
||||||
row["CariDetay"] = cariDetailMap[cari8]
|
row["CariDetay"] = cariDetailMap[cari8]
|
||||||
row["UsdTutar"] = round2(usdTutar)
|
row["UsdTutar"] = round2(usdTutar)
|
||||||
row["TryTutar"] = round2(tryTutar)
|
row["CurrencyTryRate"] = round6(rateMap[curr])
|
||||||
row["GunKur"] = round6(gunKur)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
@@ -306,138 +258,3 @@ func round2(v float64) float64 {
|
|||||||
func round6(v float64) float64 {
|
func round6(v float64) float64 {
|
||||||
return math.Round(v*1_000_000) / 1_000_000
|
return math.Round(v*1_000_000) / 1_000_000
|
||||||
}
|
}
|
||||||
|
|
||||||
type ratePoint struct {
|
|
||||||
date time.Time
|
|
||||||
rate float64
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadTryRateSeriesByCurrency(ctx context.Context, currencies map[string]struct{}) (map[string][]ratePoint, error) {
|
|
||||||
if len(currencies) == 0 {
|
|
||||||
return map[string][]ratePoint{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
query := fmt.Sprintf(`
|
|
||||||
SELECT CurrencyCode, Rate, CAST([Date] AS date) AS RateDate
|
|
||||||
FROM AllExchangeRates
|
|
||||||
WHERE RelationCurrencyCode = 'TRY'
|
|
||||||
AND ExchangeTypeCode = 6
|
|
||||||
AND Rate > 0
|
|
||||||
AND CurrencyCode IN (%s)
|
|
||||||
`, quotedInList(currencies))
|
|
||||||
|
|
||||||
rows, err := db.MssqlDB.QueryContext(ctx, query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("aging currency series query error: %w", err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
out := make(map[string][]ratePoint, len(currencies))
|
|
||||||
for rows.Next() {
|
|
||||||
var code string
|
|
||||||
var rate float64
|
|
||||||
var dt time.Time
|
|
||||||
if err := rows.Scan(&code, &rate, &dt); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
code = strings.ToUpper(strings.TrimSpace(code))
|
|
||||||
out[code] = append(out[code], ratePoint{date: dt, rate: rate})
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for c := range out {
|
|
||||||
sort.Slice(out[c], func(i, j int) bool { return out[c][i].date.Before(out[c][j].date) })
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveTryRate(currency string, target time.Time, series map[string][]ratePoint) float64 {
|
|
||||||
currency = strings.ToUpper(strings.TrimSpace(currency))
|
|
||||||
if currency == "" || currency == "TRY" {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
points := series[currency]
|
|
||||||
if len(points) == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
best := points[0]
|
|
||||||
bestDiff := absDurationDays(points[0].date.Sub(target))
|
|
||||||
for i := 1; i < len(points); i++ {
|
|
||||||
diff := absDurationDays(points[i].date.Sub(target))
|
|
||||||
if diff < bestDiff || (diff == bestDiff && points[i].date.After(best.date)) {
|
|
||||||
best = points[i]
|
|
||||||
bestDiff = diff
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return best.rate
|
|
||||||
}
|
|
||||||
|
|
||||||
func absDurationDays(d time.Duration) int64 {
|
|
||||||
if d < 0 {
|
|
||||||
d = -d
|
|
||||||
}
|
|
||||||
return int64(d.Hours() / 24)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDateOnly(v string) (time.Time, bool) {
|
|
||||||
v = strings.TrimSpace(v)
|
|
||||||
if v == "" {
|
|
||||||
return time.Time{}, false
|
|
||||||
}
|
|
||||||
t, err := time.Parse("2006-01-02", v)
|
|
||||||
if err != nil {
|
|
||||||
return time.Time{}, false
|
|
||||||
}
|
|
||||||
return t, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func toTRYByRate(amount float64, currency string, currTry float64) float64 {
|
|
||||||
currency = strings.ToUpper(strings.TrimSpace(currency))
|
|
||||||
if currency == "" || currency == "TRY" {
|
|
||||||
return amount
|
|
||||||
}
|
|
||||||
if currTry <= 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return amount * currTry
|
|
||||||
}
|
|
||||||
|
|
||||||
func toUSDByRates(amount float64, currency string, currTry, usdTry float64) float64 {
|
|
||||||
currency = strings.ToUpper(strings.TrimSpace(currency))
|
|
||||||
switch currency {
|
|
||||||
case "USD":
|
|
||||||
return amount
|
|
||||||
case "", "TRY":
|
|
||||||
if usdTry <= 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return amount / usdTry
|
|
||||||
default:
|
|
||||||
if currTry <= 0 || usdTry <= 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return (amount * currTry) / usdTry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns X for "1 USD = X <currency>".
|
|
||||||
func usdRateInCurrency(currency string, currTry, usdTry float64) float64 {
|
|
||||||
currency = strings.ToUpper(strings.TrimSpace(currency))
|
|
||||||
switch currency {
|
|
||||||
case "", "USD":
|
|
||||||
return 1
|
|
||||||
case "TRY":
|
|
||||||
if usdTry <= 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return usdTry
|
|
||||||
default:
|
|
||||||
if currTry <= 0 || usdTry <= 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return usdTry / currTry
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -34,10 +34,38 @@ func GetStatements(ctx context.Context, params models.StatementParams) ([]models
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
piyasaScope, err := buildPiyasaExistsForCariCode(ctx, "b.CurrAccCode")
|
customerPiyasaInClause, err := resolvePiyasaScopeInClause(ctx, "PF.CustomerAtt01")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
vendorPiyasaInClause, err := resolvePiyasaScopeInClause(ctx, "VF.VendorAtt01")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
piyasaScope := fmt.Sprintf(`
|
||||||
|
(
|
||||||
|
(b.CurrAccTypeCode = 3 AND EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM CustomerAttributesFilter PF WITH (NOLOCK)
|
||||||
|
WHERE (PF.CurrAccCode = b.CurrAccCode OR LEFT(PF.CurrAccCode, 8) = LEFT(b.CurrAccCode, 8))
|
||||||
|
AND %s
|
||||||
|
))
|
||||||
|
OR
|
||||||
|
(b.CurrAccTypeCode = 1 AND EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
CurrAccCode,
|
||||||
|
VendorAtt01 = MAX(CASE WHEN AttributeTypeCode = 1 THEN AttributeCode END)
|
||||||
|
FROM prCurrAccAttribute WITH (NOLOCK)
|
||||||
|
WHERE CurrAccTypeCode = 1
|
||||||
|
GROUP BY CurrAccCode
|
||||||
|
) VF
|
||||||
|
WHERE (VF.CurrAccCode = b.CurrAccCode OR LEFT(VF.CurrAccCode, 8) = LEFT(b.CurrAccCode, 8))
|
||||||
|
AND %s
|
||||||
|
))
|
||||||
|
)`, customerPiyasaInClause, vendorPiyasaInClause)
|
||||||
|
|
||||||
query := fmt.Sprintf(`
|
query := fmt.Sprintf(`
|
||||||
;WITH CurrDesc AS (
|
;WITH CurrDesc AS (
|
||||||
@@ -62,7 +90,7 @@ HasMovement AS (
|
|||||||
INNER JOIN CurrAccBookATAttributesFilter f
|
INNER JOIN CurrAccBookATAttributesFilter f
|
||||||
ON f.CurrAccBookID = b.CurrAccBookID
|
ON f.CurrAccBookID = b.CurrAccBookID
|
||||||
AND f.ATAtt01 IN (%s)
|
AND f.ATAtt01 IN (%s)
|
||||||
WHERE LEFT(REPLACE(b.CurrAccCode, ' ', ''), 7) = REPLACE(@Carikod, ' ', '')
|
WHERE LEFT(REPLACE(b.CurrAccCode, ' ', ''), 7) = LEFT(REPLACE(@Carikod, ' ', ''), 7)
|
||||||
AND b.DocumentDate BETWEEN @startdate AND @enddate
|
AND b.DocumentDate BETWEEN @startdate AND @enddate
|
||||||
AND %s
|
AND %s
|
||||||
) THEN 1 ELSE 0 END AS HasMov
|
) THEN 1 ELSE 0 END AS HasMov
|
||||||
@@ -85,7 +113,8 @@ Opening AS (
|
|||||||
LEFT JOIN trCurrAccBookCurrency c
|
LEFT JOIN trCurrAccBookCurrency c
|
||||||
ON c.CurrAccBookID = b.CurrAccBookID
|
ON c.CurrAccBookID = b.CurrAccBookID
|
||||||
AND c.CurrencyCode = b.DocCurrencyCode
|
AND c.CurrencyCode = b.DocCurrencyCode
|
||||||
WHERE LEFT(REPLACE(b.CurrAccCode, ' ', ''), 7) = REPLACE(@Carikod, ' ', '')
|
WHERE LEFT(REPLACE(b.CurrAccCode, ' ', ''), 7) = LEFT(REPLACE(@Carikod, ' ', ''), 7)
|
||||||
|
AND @ExcludeOpening = 0
|
||||||
AND %s
|
AND %s
|
||||||
AND (
|
AND (
|
||||||
(hm.HasMov = 1 AND b.DocumentDate < @startdate) -- hareket varsa: klasik devir
|
(hm.HasMov = 1 AND b.DocumentDate < @startdate) -- hareket varsa: klasik devir
|
||||||
@@ -142,7 +171,7 @@ Movements AS (
|
|||||||
ON c.CurrAccBookID = b.CurrAccBookID
|
ON c.CurrAccBookID = b.CurrAccBookID
|
||||||
AND c.CurrencyCode = b.DocCurrencyCode
|
AND c.CurrencyCode = b.DocCurrencyCode
|
||||||
|
|
||||||
WHERE LEFT(REPLACE(b.CurrAccCode, ' ', ''), 7) = REPLACE(@Carikod, ' ', '')
|
WHERE LEFT(REPLACE(b.CurrAccCode, ' ', ''), 7) = LEFT(REPLACE(@Carikod, ' ', ''), 7)
|
||||||
AND %s
|
AND %s
|
||||||
AND b.DocumentDate BETWEEN @startdate AND @enddate
|
AND b.DocumentDate BETWEEN @startdate AND @enddate
|
||||||
)
|
)
|
||||||
@@ -223,6 +252,7 @@ ORDER BY
|
|||||||
sql.Named("enddate", params.EndDate),
|
sql.Named("enddate", params.EndDate),
|
||||||
sql.Named("Carikod", params.AccountCode),
|
sql.Named("Carikod", params.AccountCode),
|
||||||
sql.Named("LangCode", params.LangCode),
|
sql.Named("LangCode", params.LangCode),
|
||||||
|
sql.Named("ExcludeOpening", params.ExcludeOpening),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("MSSQL query error: %v", err)
|
return nil, fmt.Errorf("MSSQL query error: %v", err)
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ LEFT JOIN cdItemAttributeDesc KisaKarDesc
|
|||||||
ON KisaKar.AttributeTypeCode = KisaKarDesc.AttributeTypeCode
|
ON KisaKar.AttributeTypeCode = KisaKarDesc.AttributeTypeCode
|
||||||
AND KisaKar.AttributeCode = KisaKarDesc.AttributeCode
|
AND KisaKar.AttributeCode = KisaKarDesc.AttributeCode
|
||||||
AND KisaKar.ItemTypeCode = KisaKarDesc.ItemTypeCode
|
AND KisaKar.ItemTypeCode = KisaKarDesc.ItemTypeCode
|
||||||
WHERE a.CurrAccCode LIKE @Carikod
|
WHERE REPLACE(a.CurrAccCode, ' ', '') LIKE REPLACE(@Carikod, ' ', '') + '%%'
|
||||||
AND a.InvoiceDate BETWEEN @StartDate AND @EndDate
|
AND a.InvoiceDate BETWEEN @StartDate AND @EndDate
|
||||||
AND %s
|
AND %s
|
||||||
%s
|
%s
|
||||||
@@ -91,7 +91,7 @@ ORDER BY Belge_Tarihi, Belge_Ref_Numarasi, Urun_Kodu;`,
|
|||||||
)
|
)
|
||||||
|
|
||||||
rows, err := db.MssqlDB.QueryContext(ctx, query,
|
rows, err := db.MssqlDB.QueryContext(ctx, query,
|
||||||
sql.Named("Carikod", "%"+accountCode+"%"),
|
sql.Named("Carikod", normalizeMasterAccountCode(accountCode)),
|
||||||
sql.Named("StartDate", startDate),
|
sql.Named("StartDate", startDate),
|
||||||
sql.Named("EndDate", endDate),
|
sql.Named("EndDate", endDate),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime/debug"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -33,18 +31,10 @@ type balanceSummaryPDF struct {
|
|||||||
TLBakiye13 float64
|
TLBakiye13 float64
|
||||||
VadeGun float64
|
VadeGun float64
|
||||||
VadeBelge float64
|
VadeBelge float64
|
||||||
VadeBase float64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExportCustomerBalancePDFHandler(_ *sql.DB) http.HandlerFunc {
|
func ExportCustomerBalancePDFHandler(_ *sql.DB) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
defer func() {
|
|
||||||
if rec := recover(); rec != nil {
|
|
||||||
log.Printf("PANIC ExportCustomerBalancePDFHandler: %v\n%s", rec, string(debug.Stack()))
|
|
||||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
claims, ok := auth.GetClaimsFromContext(r.Context())
|
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||||||
if !ok || claims == nil {
|
if !ok || claims == nil {
|
||||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||||
@@ -147,6 +137,9 @@ func filterCustomerBalanceRowsForPDF(rows []models.CustomerBalanceListRow, exclu
|
|||||||
func buildCustomerBalancePDFData(rows []models.CustomerBalanceListRow) ([]balanceSummaryPDF, map[string][]models.CustomerBalanceListRow) {
|
func buildCustomerBalancePDFData(rows []models.CustomerBalanceListRow) ([]balanceSummaryPDF, map[string][]models.CustomerBalanceListRow) {
|
||||||
summaryMap := make(map[string]*balanceSummaryPDF)
|
summaryMap := make(map[string]*balanceSummaryPDF)
|
||||||
detailsByMaster := make(map[string][]models.CustomerBalanceListRow)
|
detailsByMaster := make(map[string][]models.CustomerBalanceListRow)
|
||||||
|
vadeWeightMap := make(map[string]float64)
|
||||||
|
vadeGunSumMap := make(map[string]float64)
|
||||||
|
vadeBelgeSumMap := make(map[string]float64)
|
||||||
|
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
master := strings.TrimSpace(row.AnaCariKodu)
|
master := strings.TrimSpace(row.AnaCariKodu)
|
||||||
@@ -194,11 +187,11 @@ 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)
|
w := absFloatExcel(row.USDBakiye12) + absFloatExcel(row.TLBakiye12) + absFloatExcel(row.USDBakiye13) + absFloatExcel(row.TLBakiye13)
|
||||||
if w > 0 {
|
if w > 0 {
|
||||||
s.VadeBase += w
|
vadeWeightMap[master] += w
|
||||||
s.VadeGun += row.VadeGun * w
|
vadeGunSumMap[master] += row.VadeGun * w
|
||||||
s.VadeBelge += row.VadeBelgeGun * w
|
vadeBelgeSumMap[master] += row.VadeBelgeGun * w
|
||||||
}
|
}
|
||||||
|
|
||||||
detailsByMaster[master] = append(detailsByMaster[master], row)
|
detailsByMaster[master] = append(detailsByMaster[master], row)
|
||||||
@@ -212,10 +205,9 @@ 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 base := vadeWeightMap[m]; base > 0 {
|
||||||
if s != nil && s.VadeBase > 0 {
|
summaryMap[m].VadeGun = vadeGunSumMap[m] / base
|
||||||
s.VadeGun = s.VadeGun / s.VadeBase
|
summaryMap[m].VadeBelge = vadeBelgeSumMap[m] / base
|
||||||
s.VadeBelge = s.VadeBelge / s.VadeBase
|
|
||||||
}
|
}
|
||||||
summaries = append(summaries, *summaryMap[m])
|
summaries = append(summaries, *summaryMap[m])
|
||||||
d := detailsByMaster[m]
|
d := detailsByMaster[m]
|
||||||
@@ -248,11 +240,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", "Vade Gun", "Belge Gun"}
|
summaryCols := []string{"Ana Cari Kod", "Ana Cari Detay", "Piyasa", "Temsilci", "Risk", "1_2 Pr.Br", "1_3 Pr.Br", "1_2 USD", "1_2 TRY", "1_3 USD", "1_3 TRY"}
|
||||||
summaryW := normalizeWidths([]float64{18, 46, 14, 18, 12, 20, 20, 12, 12, 12, 12, 10, 10}, tableW)
|
summaryW := normalizeWidths([]float64{20, 43, 18, 18, 16, 27, 27, 15, 15, 15, 15}, tableW)
|
||||||
|
|
||||||
detailCols := []string{"Cari Kod", "Cari Detay", "Sirket", "Muhasebe", "Doviz", "1_2 Pr.Br", "1_3 Pr.Br", "1_2 USD", "1_2 TRY", "1_3 USD", "1_3 TRY", "Vade Gun", "Belge Gun"}
|
detailCols := []string{"Cari Kod", "Cari Detay", "Sirket", "Muhasebe", "Doviz", "1_2 Pr.Br", "1_3 Pr.Br", "1_2 USD", "1_2 TRY", "1_3 USD", "1_3 TRY"}
|
||||||
detailW := normalizeWidths([]float64{22, 40, 9, 16, 8, 20, 20, 12, 12, 12, 12, 9, 9}, tableW)
|
detailW := normalizeWidths([]float64{26, 46, 10, 20, 10, 24, 24, 15, 15, 15, 15}, tableW)
|
||||||
|
|
||||||
header := func() {
|
header := func() {
|
||||||
pdf.AddPage()
|
pdf.AddPage()
|
||||||
@@ -288,44 +280,6 @@ func drawCustomerBalancePDF(
|
|||||||
return pdf.GetY()+needH+marginB > 210.0
|
return pdf.GetY()+needH+marginB > 210.0
|
||||||
}
|
}
|
||||||
|
|
||||||
wrappedLines := func(text string, w float64) [][]byte {
|
|
||||||
t := strings.TrimSpace(sanitizePDFText(text))
|
|
||||||
if t == "" {
|
|
||||||
t = "-"
|
|
||||||
}
|
|
||||||
return splitLinesSafe(pdf, t, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
calcWrappedRowHeight := func(row []string, widths []float64, wrapIdx map[int]bool, lineH float64, minH float64) float64 {
|
|
||||||
maxLines := 1
|
|
||||||
for i, v := range row {
|
|
||||||
if !wrapIdx[i] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ln := len(wrappedLines(v, widths[i]-2))
|
|
||||||
if ln > maxLines {
|
|
||||||
maxLines = ln
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h := float64(maxLines)*lineH + 1.2
|
|
||||||
if h < minH {
|
|
||||||
return minH
|
|
||||||
}
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
drawWrapped := func(text string, x, y, w, rowH, lineH float64, align string) {
|
|
||||||
lines := wrappedLines(text, w-2)
|
|
||||||
total := float64(len(lines)) * lineH
|
|
||||||
startY := y + (rowH-total)/2
|
|
||||||
cy := startY
|
|
||||||
for _, ln := range lines {
|
|
||||||
pdf.SetXY(x+1, cy)
|
|
||||||
pdf.CellFormat(w-2, lineH, sanitizePDFText(string(ln)), "", 0, align, false, 0, "")
|
|
||||||
cy += lineH
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
drawSummaryHeader := func() {
|
drawSummaryHeader := func() {
|
||||||
pdf.SetFont("dejavu", "B", 7.5)
|
pdf.SetFont("dejavu", "B", 7.5)
|
||||||
pdf.SetFillColor(149, 113, 22)
|
pdf.SetFillColor(149, 113, 22)
|
||||||
@@ -362,7 +316,12 @@ func drawCustomerBalancePDF(
|
|||||||
pdf.SetFont("dejavu", "", 7.2)
|
pdf.SetFont("dejavu", "", 7.2)
|
||||||
pdf.SetTextColor(20, 20, 20)
|
pdf.SetTextColor(20, 20, 20)
|
||||||
|
|
||||||
drawSummaryRow := func(s balanceSummaryPDF) {
|
for _, s := range summaries {
|
||||||
|
if needPage(6.2) {
|
||||||
|
header()
|
||||||
|
drawSummaryHeader()
|
||||||
|
}
|
||||||
|
|
||||||
row := []string{
|
row := []string{
|
||||||
s.AnaCariKodu,
|
s.AnaCariKodu,
|
||||||
s.AnaCariAdi,
|
s.AnaCariAdi,
|
||||||
@@ -375,56 +334,31 @@ 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}
|
|
||||||
rowH := calcWrappedRowHeight(row, summaryW, wrapCols, 3.2, 6.2)
|
|
||||||
if needPage(rowH) {
|
|
||||||
header()
|
|
||||||
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 {
|
||||||
if detailed {
|
pdf.Rect(x, y, summaryW[i], 6.2, "")
|
||||||
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"
|
||||||
}
|
}
|
||||||
if wrapCols[i] {
|
pdf.SetXY(x+1, y+1)
|
||||||
drawWrapped(v, x, y, summaryW[i], rowH, 3.2, "L")
|
pdf.CellFormat(summaryW[i]-2, 4.2, v, "", 0, align, false, 0, "")
|
||||||
} else {
|
|
||||||
pdf.SetXY(x+1, y+(rowH-4.2)/2)
|
|
||||||
pdf.CellFormat(summaryW[i]-2, 4.2, sanitizePDFText(v), "", 0, align, false, 0, "")
|
|
||||||
}
|
|
||||||
x += summaryW[i]
|
x += summaryW[i]
|
||||||
}
|
}
|
||||||
pdf.SetY(y + rowH)
|
pdf.SetY(y + 6.2)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !detailed {
|
if !detailed {
|
||||||
for _, s := range summaries {
|
|
||||||
drawSummaryRow(s)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pdf.Ln(1.8)
|
||||||
for _, s := range summaries {
|
for _, s := range summaries {
|
||||||
drawSummaryRow(s)
|
|
||||||
pdf.Ln(1.2)
|
|
||||||
|
|
||||||
rows := detailsByMaster[s.AnaCariKodu]
|
rows := detailsByMaster[s.AnaCariKodu]
|
||||||
if len(rows) == 0 {
|
if len(rows) == 0 {
|
||||||
pdf.Ln(1.0)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,25 +380,7 @@ func drawCustomerBalancePDF(
|
|||||||
pdf.SetTextColor(40, 40, 40)
|
pdf.SetTextColor(40, 40, 40)
|
||||||
|
|
||||||
for _, r := range rows {
|
for _, r := range rows {
|
||||||
line := []string{
|
if needPage(5.8) {
|
||||||
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)
|
||||||
@@ -479,23 +395,33 @@ 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], rowH, "")
|
pdf.Rect(rowX, rowY, detailW[i], 5.8, "")
|
||||||
align := "L"
|
align := "L"
|
||||||
if i >= 5 {
|
if i >= 5 {
|
||||||
align = "R"
|
align = "R"
|
||||||
}
|
}
|
||||||
if detailWrapCols[i] {
|
pdf.SetXY(rowX+1, rowY+0.8)
|
||||||
drawWrapped(v, rowX, rowY, detailW[i], rowH, 3.0, "L")
|
pdf.CellFormat(detailW[i]-2, 4.0, v, "", 0, align, false, 0, "")
|
||||||
} else {
|
|
||||||
pdf.SetXY(rowX+1, rowY+(rowH-4.0)/2)
|
|
||||||
pdf.CellFormat(detailW[i]-2, 4.0, sanitizePDFText(v), "", 0, align, false, 0, "")
|
|
||||||
}
|
|
||||||
rowX += detailW[i]
|
rowX += detailW[i]
|
||||||
}
|
}
|
||||||
pdf.SetY(rowY + rowH)
|
pdf.SetY(rowY + 5.8)
|
||||||
}
|
}
|
||||||
pdf.Ln(1.2)
|
pdf.Ln(1.2)
|
||||||
}
|
}
|
||||||
@@ -548,24 +474,3 @@ 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func sanitizePDFText(s string) string {
|
|
||||||
s = strings.ToValidUTF8(s, "?")
|
|
||||||
s = strings.ReplaceAll(s, "\x00", " ")
|
|
||||||
return strings.Map(func(r rune) rune {
|
|
||||||
if r == '\n' || r == '\r' || r == '\t' {
|
|
||||||
return ' '
|
|
||||||
}
|
|
||||||
if r < 32 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}, s)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -18,7 +18,51 @@ func GetStatementAgingHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedDate := time.Now().Format("2006-01-02")
|
selectedDate := strings.TrimSpace(r.URL.Query().Get("enddate"))
|
||||||
|
if selectedDate == "" {
|
||||||
|
selectedDate = strings.TrimSpace(r.URL.Query().Get("selected_date"))
|
||||||
|
}
|
||||||
|
if selectedDate == "" {
|
||||||
|
selectedDate = time.Now().Format("2006-01-02")
|
||||||
|
}
|
||||||
|
params := models.StatementAgingParams{
|
||||||
|
AccountCode: strings.TrimSpace(r.URL.Query().Get("accountcode")),
|
||||||
|
EndDate: selectedDate,
|
||||||
|
Parislemler: r.URL.Query()["parislemler"],
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := queries.RebuildStatementAgingCache(r.Context()); err != nil {
|
||||||
|
http.Error(w, "Error rebuilding aging cache: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := queries.GetStatementAging(params)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error fetching aging statement: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
if err := json.NewEncoder(w).Encode(rows); err != nil {
|
||||||
|
http.Error(w, "Error encoding response: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /api/finance/aged-customer-balance-list
|
||||||
|
func GetAgedCustomerBalanceListHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||||||
|
if !ok || claims == nil {
|
||||||
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedDate := strings.TrimSpace(r.URL.Query().Get("enddate"))
|
||||||
|
if selectedDate == "" {
|
||||||
|
selectedDate = strings.TrimSpace(r.URL.Query().Get("selected_date"))
|
||||||
|
}
|
||||||
|
if selectedDate == "" {
|
||||||
|
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: selectedDate,
|
EndDate: selectedDate,
|
||||||
|
|||||||
@@ -21,7 +21,13 @@ func ExportStatementAgingExcelHandler(_ *sql.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedDate := time.Now().Format("2006-01-02")
|
selectedDate := strings.TrimSpace(r.URL.Query().Get("enddate"))
|
||||||
|
if selectedDate == "" {
|
||||||
|
selectedDate = strings.TrimSpace(r.URL.Query().Get("selected_date"))
|
||||||
|
}
|
||||||
|
if selectedDate == "" {
|
||||||
|
selectedDate = time.Now().Format("2006-01-02")
|
||||||
|
}
|
||||||
params := models.CustomerBalanceListParams{
|
params := models.CustomerBalanceListParams{
|
||||||
SelectedDate: selectedDate,
|
SelectedDate: selectedDate,
|
||||||
CariSearch: strings.TrimSpace(r.URL.Query().Get("cari_search")),
|
CariSearch: strings.TrimSpace(r.URL.Query().Get("cari_search")),
|
||||||
@@ -34,9 +40,17 @@ func ExportStatementAgingExcelHandler(_ *sql.DB) http.HandlerFunc {
|
|||||||
Il: strings.TrimSpace(r.URL.Query().Get("il")),
|
Il: strings.TrimSpace(r.URL.Query().Get("il")),
|
||||||
Ilce: strings.TrimSpace(r.URL.Query().Get("ilce")),
|
Ilce: strings.TrimSpace(r.URL.Query().Get("ilce")),
|
||||||
}
|
}
|
||||||
|
if accountCode := strings.TrimSpace(r.URL.Query().Get("accountcode")); accountCode != "" {
|
||||||
|
params.CariSearch = accountCode
|
||||||
|
}
|
||||||
excludeZero12 := parseBoolQuery(r.URL.Query().Get("exclude_zero_12"))
|
excludeZero12 := parseBoolQuery(r.URL.Query().Get("exclude_zero_12"))
|
||||||
excludeZero13 := parseBoolQuery(r.URL.Query().Get("exclude_zero_13"))
|
excludeZero13 := parseBoolQuery(r.URL.Query().Get("exclude_zero_13"))
|
||||||
|
|
||||||
|
if err := queries.RebuildStatementAgingCache(r.Context()); err != nil {
|
||||||
|
http.Error(w, "Error rebuilding aging cache: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
rows, err := queries.GetStatementAgingBalanceList(r.Context(), params)
|
rows, err := queries.GetStatementAgingBalanceList(r.Context(), params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "db error: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "db error: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
|||||||
@@ -7,92 +7,30 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"math"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime/debug"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jung-kurt/gofpdf"
|
"github.com/jung-kurt/gofpdf"
|
||||||
)
|
)
|
||||||
|
|
||||||
type agingDetailPDF struct {
|
|
||||||
FaturaCari string
|
|
||||||
OdemeCari string
|
|
||||||
Doviz string
|
|
||||||
FaturaRef string
|
|
||||||
OdemeRef string
|
|
||||||
FaturaTarihi string
|
|
||||||
OdemeTarihi string
|
|
||||||
OdemeDocDate string
|
|
||||||
EslesenTutar float64
|
|
||||||
UsdTutar float64
|
|
||||||
TryTutar float64
|
|
||||||
Aciklama string
|
|
||||||
Gun int
|
|
||||||
GunBelge int
|
|
||||||
GunKur float64
|
|
||||||
odemeDateParsed time.Time
|
|
||||||
odemeDateEmpty bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type agingCurrencyPDF struct {
|
|
||||||
Key string
|
|
||||||
Cari8 string
|
|
||||||
CariDetay string
|
|
||||||
Doviz string
|
|
||||||
AcikKalemTutar float64
|
|
||||||
AcikKalemUSD float64
|
|
||||||
AcikKalemTRY float64
|
|
||||||
OrtGun int
|
|
||||||
OrtBelgeGun int
|
|
||||||
weightedBase float64
|
|
||||||
weightedGunSum float64
|
|
||||||
weightedDocSum float64
|
|
||||||
Details []agingDetailPDF
|
|
||||||
}
|
|
||||||
|
|
||||||
type agingMasterPDF struct {
|
|
||||||
Key string
|
|
||||||
Cari8 string
|
|
||||||
CariDetay string
|
|
||||||
AcikKalemUSD float64
|
|
||||||
AcikKalemTRY float64
|
|
||||||
AcikKalemOrtVadeGun int
|
|
||||||
AcikKalemOrtBelge int
|
|
||||||
NormalUSD float64
|
|
||||||
NormalTRY float64
|
|
||||||
OrtalamaVadeGun int
|
|
||||||
OrtalamaBelgeGun int
|
|
||||||
weightedAllBase float64
|
|
||||||
weightedAllGunSum float64
|
|
||||||
weightedAllDocSum float64
|
|
||||||
weightedOpenBase float64
|
|
||||||
weightedOpenGunSum float64
|
|
||||||
weightedOpenDocSum float64
|
|
||||||
Currencies []agingCurrencyPDF
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExportStatementAgingPDFHandler(_ *sql.DB) http.HandlerFunc {
|
func ExportStatementAgingPDFHandler(_ *sql.DB) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
defer func() {
|
|
||||||
if rec := recover(); rec != nil {
|
|
||||||
log.Printf("PANIC ExportStatementAgingPDFHandler: %v\n%s", rec, string(debug.Stack()))
|
|
||||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
claims, ok := auth.GetClaimsFromContext(r.Context())
|
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||||||
if !ok || claims == nil {
|
if !ok || claims == nil {
|
||||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedDate := time.Now().Format("2006-01-02")
|
selectedDate := strings.TrimSpace(r.URL.Query().Get("enddate"))
|
||||||
listParams := models.CustomerBalanceListParams{
|
if selectedDate == "" {
|
||||||
|
selectedDate = strings.TrimSpace(r.URL.Query().Get("selected_date"))
|
||||||
|
}
|
||||||
|
if selectedDate == "" {
|
||||||
|
selectedDate = time.Now().Format("2006-01-02")
|
||||||
|
}
|
||||||
|
|
||||||
|
params := models.CustomerBalanceListParams{
|
||||||
SelectedDate: selectedDate,
|
SelectedDate: selectedDate,
|
||||||
CariSearch: strings.TrimSpace(r.URL.Query().Get("cari_search")),
|
CariSearch: strings.TrimSpace(r.URL.Query().Get("cari_search")),
|
||||||
CariIlkGrup: strings.TrimSpace(r.URL.Query().Get("cari_ilk_grup")),
|
CariIlkGrup: strings.TrimSpace(r.URL.Query().Get("cari_ilk_grup")),
|
||||||
@@ -104,26 +42,31 @@ func ExportStatementAgingPDFHandler(_ *sql.DB) http.HandlerFunc {
|
|||||||
Il: strings.TrimSpace(r.URL.Query().Get("il")),
|
Il: strings.TrimSpace(r.URL.Query().Get("il")),
|
||||||
Ilce: strings.TrimSpace(r.URL.Query().Get("ilce")),
|
Ilce: strings.TrimSpace(r.URL.Query().Get("ilce")),
|
||||||
}
|
}
|
||||||
|
if accountCode := strings.TrimSpace(r.URL.Query().Get("accountcode")); accountCode != "" {
|
||||||
|
params.CariSearch = accountCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := queries.RebuildStatementAgingCache(r.Context()); err != nil {
|
||||||
|
http.Error(w, "Error rebuilding aging cache: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
detailed := parseBoolQuery(r.URL.Query().Get("detailed"))
|
detailed := parseBoolQuery(r.URL.Query().Get("detailed"))
|
||||||
excludeZero12 := parseBoolQuery(r.URL.Query().Get("exclude_zero_12"))
|
excludeZero12 := parseBoolQuery(r.URL.Query().Get("exclude_zero_12"))
|
||||||
excludeZero13 := parseBoolQuery(r.URL.Query().Get("exclude_zero_13"))
|
excludeZero13 := parseBoolQuery(r.URL.Query().Get("exclude_zero_13"))
|
||||||
|
|
||||||
if err := queries.RebuildStatementAgingCache(r.Context()); err != nil {
|
rows, err := queries.GetStatementAgingBalanceList(r.Context(), params)
|
||||||
http.Error(w, "cache rebuild error: "+err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
rows = filterCustomerBalanceRowsForPDF(rows, excludeZero12, excludeZero13)
|
||||||
summaries, detailsByMaster := buildCustomerBalancePDFData(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)
|
||||||
pdf.SetAutoPageBreak(false, 10)
|
pdf.SetAutoPageBreak(false, 12)
|
||||||
if err := registerDejavuFonts(pdf, "dejavu"); err != nil {
|
if err := registerDejavuFonts(pdf, "dejavu"); err != nil {
|
||||||
http.Error(w, "pdf font error: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "pdf font error: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -132,7 +75,7 @@ func ExportStatementAgingPDFHandler(_ *sql.DB) http.HandlerFunc {
|
|||||||
drawCustomerBalancePDF(
|
drawCustomerBalancePDF(
|
||||||
pdf,
|
pdf,
|
||||||
selectedDate,
|
selectedDate,
|
||||||
listParams.CariSearch,
|
params.CariSearch,
|
||||||
detailed,
|
detailed,
|
||||||
summaries,
|
summaries,
|
||||||
detailsByMaster,
|
detailsByMaster,
|
||||||
@@ -149,457 +92,13 @@ func ExportStatementAgingPDFHandler(_ *sql.DB) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/pdf")
|
|
||||||
filename := "account-aging-summary.pdf"
|
filename := "account-aging-summary.pdf"
|
||||||
if detailed {
|
if detailed {
|
||||||
filename = "account-aging-detailed.pdf"
|
filename = "account-aging-detailed.pdf"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/pdf")
|
||||||
w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=%q", filename))
|
w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=%q", filename))
|
||||||
_, _ = w.Write(buf.Bytes())
|
_, _ = w.Write(buf.Bytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildAgingPDFData(rows []map[string]interface{}) []agingMasterPDF {
|
|
||||||
masterMap := make(map[string]*agingMasterPDF)
|
|
||||||
currMap := make(map[string]*agingCurrencyPDF)
|
|
||||||
|
|
||||||
for _, row := range rows {
|
|
||||||
cari8 := strings.TrimSpace(readStrAny(row["Cari8"], row["cari8"]))
|
|
||||||
if cari8 == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
cariDetay := strings.TrimSpace(readStrAny(row["CariDetay"], row["cari_detay"]))
|
|
||||||
doviz := strings.ToUpper(strings.TrimSpace(readStrAny(row["DocCurrencyCode"], row["doc_currency_code"])))
|
|
||||||
if doviz == "" {
|
|
||||||
doviz = "TRY"
|
|
||||||
}
|
|
||||||
aciklama := strings.ToUpper(strings.TrimSpace(readStrAny(row["Aciklama"], row["aciklama"])))
|
|
||||||
isAcik := aciklama == "ACIKKALEM"
|
|
||||||
|
|
||||||
eslesen := readFloatAny(row["EslesenTutar"], row["eslesen_tutar"])
|
|
||||||
usd := readFloatAny(row["UsdTutar"], row["usd_tutar"])
|
|
||||||
tryVal := readFloatAny(row["TryTutar"], row["try_tutar"])
|
|
||||||
gun := readIntAny(row["GunSayisi"], row["gun_sayisi"])
|
|
||||||
gunBelge := readIntAny(row["GunSayisi_DocDate"], row["gun_sayisi_docdate"])
|
|
||||||
absTry := math.Abs(tryVal)
|
|
||||||
|
|
||||||
m := masterMap[cari8]
|
|
||||||
if m == nil {
|
|
||||||
m = &agingMasterPDF{Key: cari8, Cari8: cari8, CariDetay: cariDetay}
|
|
||||||
masterMap[cari8] = m
|
|
||||||
}
|
|
||||||
if m.CariDetay == "" && cariDetay != "" {
|
|
||||||
m.CariDetay = cariDetay
|
|
||||||
}
|
|
||||||
|
|
||||||
ckey := cari8 + "|" + doviz
|
|
||||||
c := currMap[ckey]
|
|
||||||
if c == nil {
|
|
||||||
c = &agingCurrencyPDF{
|
|
||||||
Key: ckey,
|
|
||||||
Cari8: cari8,
|
|
||||||
CariDetay: cariDetay,
|
|
||||||
Doviz: doviz,
|
|
||||||
Details: make([]agingDetailPDF, 0, 128),
|
|
||||||
}
|
|
||||||
currMap[ckey] = c
|
|
||||||
}
|
|
||||||
if c.CariDetay == "" && cariDetay != "" {
|
|
||||||
c.CariDetay = cariDetay
|
|
||||||
}
|
|
||||||
|
|
||||||
if isAcik {
|
|
||||||
m.AcikKalemUSD += usd
|
|
||||||
m.AcikKalemTRY += tryVal
|
|
||||||
c.AcikKalemTutar += eslesen
|
|
||||||
c.AcikKalemUSD += usd
|
|
||||||
c.AcikKalemTRY += tryVal
|
|
||||||
} else {
|
|
||||||
m.NormalUSD += usd
|
|
||||||
m.NormalTRY += tryVal
|
|
||||||
}
|
|
||||||
|
|
||||||
if absTry > 0 {
|
|
||||||
m.weightedAllBase += absTry
|
|
||||||
m.weightedAllGunSum += absTry * float64(gun)
|
|
||||||
m.weightedAllDocSum += absTry * float64(gunBelge)
|
|
||||||
|
|
||||||
if isAcik {
|
|
||||||
m.weightedOpenBase += absTry
|
|
||||||
m.weightedOpenGunSum += absTry * float64(gun)
|
|
||||||
m.weightedOpenDocSum += absTry * float64(gunBelge)
|
|
||||||
|
|
||||||
c.weightedBase += absTry
|
|
||||||
c.weightedGunSum += absTry * float64(gun)
|
|
||||||
c.weightedDocSum += absTry * float64(gunBelge)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
odemeTar := readStrAny(row["OdemeTarihi"], row["odeme_tarihi"])
|
|
||||||
odemeParsed, ok := parseYMD(odemeTar)
|
|
||||||
detail := agingDetailPDF{
|
|
||||||
FaturaCari: readStrAny(row["FaturaCari"], row["fatura_cari"]),
|
|
||||||
OdemeCari: readStrAny(row["OdemeCari"], row["odeme_cari"]),
|
|
||||||
Doviz: doviz,
|
|
||||||
FaturaRef: readStrAny(row["FaturaRef"], row["fatura_ref"]),
|
|
||||||
OdemeRef: readStrAny(row["OdemeRef"], row["odeme_ref"]),
|
|
||||||
FaturaTarihi: readStrAny(row["FaturaTarihi"], row["fatura_tarihi"]),
|
|
||||||
OdemeTarihi: odemeTar,
|
|
||||||
OdemeDocDate: readStrAny(row["OdemeDocDate"], row["odeme_doc_date"]),
|
|
||||||
EslesenTutar: eslesen,
|
|
||||||
UsdTutar: usd,
|
|
||||||
TryTutar: tryVal,
|
|
||||||
Aciklama: readStrAny(row["Aciklama"], row["aciklama"]),
|
|
||||||
Gun: gun,
|
|
||||||
GunBelge: gunBelge,
|
|
||||||
GunKur: readFloatAny(row["GunKur"], row["gun_kur"]),
|
|
||||||
odemeDateParsed: odemeParsed,
|
|
||||||
odemeDateEmpty: !ok,
|
|
||||||
}
|
|
||||||
c.Details = append(c.Details, detail)
|
|
||||||
}
|
|
||||||
|
|
||||||
masters := make([]agingMasterPDF, 0, len(masterMap))
|
|
||||||
for _, m := range masterMap {
|
|
||||||
if m.weightedOpenBase > 0 {
|
|
||||||
m.AcikKalemOrtVadeGun = int(math.Ceil(m.weightedOpenGunSum / m.weightedOpenBase))
|
|
||||||
m.AcikKalemOrtBelge = int(math.Ceil(m.weightedOpenDocSum / m.weightedOpenBase))
|
|
||||||
}
|
|
||||||
if m.weightedAllBase > 0 {
|
|
||||||
m.OrtalamaVadeGun = int(math.Ceil(m.weightedAllGunSum / m.weightedAllBase))
|
|
||||||
m.OrtalamaBelgeGun = int(math.Ceil(m.weightedAllDocSum / m.weightedAllBase))
|
|
||||||
}
|
|
||||||
|
|
||||||
currs := make([]agingCurrencyPDF, 0, len(currMap))
|
|
||||||
for _, c := range currMap {
|
|
||||||
if c.Cari8 != m.Cari8 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if c.weightedBase > 0 {
|
|
||||||
c.OrtGun = int(math.Ceil(c.weightedGunSum / c.weightedBase))
|
|
||||||
c.OrtBelgeGun = int(math.Ceil(c.weightedDocSum / c.weightedBase))
|
|
||||||
}
|
|
||||||
sort.SliceStable(c.Details, func(i, j int) bool {
|
|
||||||
ai := c.Details[i]
|
|
||||||
aj := c.Details[j]
|
|
||||||
if ai.odemeDateEmpty && !aj.odemeDateEmpty {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if !ai.odemeDateEmpty && aj.odemeDateEmpty {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if ai.odemeDateEmpty && aj.odemeDateEmpty {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return ai.odemeDateParsed.After(aj.odemeDateParsed)
|
|
||||||
})
|
|
||||||
currs = append(currs, *c)
|
|
||||||
}
|
|
||||||
sort.SliceStable(currs, func(i, j int) bool { return currs[i].Doviz < currs[j].Doviz })
|
|
||||||
m.Currencies = currs
|
|
||||||
masters = append(masters, *m)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.SliceStable(masters, func(i, j int) bool { return masters[i].Cari8 < masters[j].Cari8 })
|
|
||||||
return masters
|
|
||||||
}
|
|
||||||
|
|
||||||
func drawStatementAgingPDF(pdf *gofpdf.Fpdf, p models.StatementAgingParams, masters []agingMasterPDF) {
|
|
||||||
pageW, pageH := pdf.GetPageSize()
|
|
||||||
marginL, marginR, marginT, marginB := 8.0, 8.0, 8.0, 10.0
|
|
||||||
tableW := pageW - marginL - marginR
|
|
||||||
|
|
||||||
colorPrimary := [3]int{149, 113, 22}
|
|
||||||
colorLevel2 := [3]int{76, 95, 122}
|
|
||||||
colorLevel3 := [3]int{31, 59, 91}
|
|
||||||
|
|
||||||
level1Cols := []string{"Ana Cari Kod", "Ana Cari Detay", "Açık Kalem USD", "Açık Kalem TRY", "Açık Kalem Ort Vade", "Açık Kalem Ort Belge", "Normal USD", "Normal TRY", "Ortalama Vade", "Ortalama Belge"}
|
|
||||||
level1W := normalizeWidths([]float64{20, 46, 18, 18, 15, 15, 16, 16, 15, 15}, tableW)
|
|
||||||
|
|
||||||
level2Cols := []string{"Ana Cari Kod", "Ana Cari Detay", "Döviz", "Açık Kalem", "Açık Kalem USD", "Açık Kalem TRY", "Ort Gün", "Ort Belge Gün"}
|
|
||||||
level2W := normalizeWidths([]float64{20, 52, 12, 24, 24, 24, 16, 18}, tableW)
|
|
||||||
|
|
||||||
level3Cols := []string{"Fatura Cari", "Ödeme Cari", "Döviz", "Fatura Ref", "Ödeme Ref", "Fatura Tarihi", "Ödeme Vade", "Ödeme DocDate", "Eşleşen", "USD", "TRY", "Açıklama", "Gün", "Gün Belge", "Gün Kur"}
|
|
||||||
level3W := normalizeWidths([]float64{15, 15, 10, 15, 15, 13, 13, 13, 14, 12, 14, 14, 8, 10, 10}, tableW)
|
|
||||||
|
|
||||||
pageHeader := func() {
|
|
||||||
pdf.AddPage()
|
|
||||||
pdf.SetFont("dejavu", "B", 15)
|
|
||||||
pdf.SetTextColor(colorPrimary[0], colorPrimary[1], colorPrimary[2])
|
|
||||||
pdf.SetXY(marginL, marginT)
|
|
||||||
pdf.CellFormat(150, 7, sanitizePDFText("Cari Yaşlandırmalı Ekstre"), "", 0, "L", false, 0, "")
|
|
||||||
|
|
||||||
pdf.SetFont("dejavu", "", 9)
|
|
||||||
pdf.SetTextColor(20, 20, 20)
|
|
||||||
pdf.SetXY(pageW-marginR-95, marginT+1)
|
|
||||||
pdf.CellFormat(95, 5, sanitizePDFText("Son Tarih: "+p.EndDate), "", 0, "R", false, 0, "")
|
|
||||||
pdf.SetXY(pageW-marginR-95, marginT+6)
|
|
||||||
pdf.CellFormat(95, 5, sanitizePDFText("Cari: "+p.AccountCode), "", 0, "R", false, 0, "")
|
|
||||||
|
|
||||||
mode := "1_2"
|
|
||||||
if len(p.Parislemler) > 0 {
|
|
||||||
mode = strings.Join(p.Parislemler, ",")
|
|
||||||
}
|
|
||||||
pdf.SetXY(pageW-marginR-95, marginT+11)
|
|
||||||
pdf.CellFormat(95, 5, sanitizePDFText("Parasal İşlem: "+mode), "", 0, "R", false, 0, "")
|
|
||||||
|
|
||||||
pdf.SetDrawColor(colorPrimary[0], colorPrimary[1], colorPrimary[2])
|
|
||||||
pdf.Line(marginL, marginT+16, pageW-marginR, marginT+16)
|
|
||||||
pdf.SetDrawColor(210, 210, 210)
|
|
||||||
pdf.SetY(marginT + 19)
|
|
||||||
}
|
|
||||||
|
|
||||||
needPage := func(need float64) bool {
|
|
||||||
return pdf.GetY()+need+marginB > pageH
|
|
||||||
}
|
|
||||||
|
|
||||||
drawHeaderRow := func(cols []string, widths []float64, rgb [3]int, h float64) {
|
|
||||||
pdf.SetFont("dejavu", "B", 8.4)
|
|
||||||
pdf.SetFillColor(rgb[0], rgb[1], rgb[2])
|
|
||||||
pdf.SetTextColor(255, 255, 255)
|
|
||||||
y := pdf.GetY()
|
|
||||||
x := marginL
|
|
||||||
for i, c := range cols {
|
|
||||||
pdf.Rect(x, y, widths[i], h, "DF")
|
|
||||||
pdf.SetXY(x+0.8, y+1.0)
|
|
||||||
pdf.CellFormat(widths[i]-1.6, h-2.0, sanitizePDFText(c), "", 0, "C", false, 0, "")
|
|
||||||
x += widths[i]
|
|
||||||
}
|
|
||||||
pdf.SetY(y + h)
|
|
||||||
}
|
|
||||||
|
|
||||||
drawRow := func(vals []string, widths []float64, h float64, fill bool, fillRGB [3]int, centerCols map[int]bool, rightCols map[int]bool) {
|
|
||||||
pdf.SetFont("dejavu", "", 7.6)
|
|
||||||
pdf.SetTextColor(20, 20, 20)
|
|
||||||
y := pdf.GetY()
|
|
||||||
x := marginL
|
|
||||||
for i, v := range vals {
|
|
||||||
if fill {
|
|
||||||
pdf.SetFillColor(fillRGB[0], fillRGB[1], fillRGB[2])
|
|
||||||
pdf.Rect(x, y, widths[i], h, "DF")
|
|
||||||
} else {
|
|
||||||
pdf.Rect(x, y, widths[i], h, "")
|
|
||||||
}
|
|
||||||
align := "L"
|
|
||||||
if rightCols[i] {
|
|
||||||
align = "R"
|
|
||||||
} else if centerCols[i] {
|
|
||||||
align = "C"
|
|
||||||
}
|
|
||||||
pdf.SetXY(x+0.8, y+0.8)
|
|
||||||
pdf.CellFormat(widths[i]-1.6, h-1.6, sanitizePDFText(v), "", 0, align, false, 0, "")
|
|
||||||
x += widths[i]
|
|
||||||
}
|
|
||||||
pdf.SetY(y + h)
|
|
||||||
}
|
|
||||||
|
|
||||||
format2 := func(v float64) string {
|
|
||||||
return trFormat(v, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
pageHeader()
|
|
||||||
for _, m := range masters {
|
|
||||||
if needPage(7 + 7 + 6) {
|
|
||||||
pageHeader()
|
|
||||||
}
|
|
||||||
|
|
||||||
drawHeaderRow(level1Cols, level1W, colorPrimary, 7)
|
|
||||||
drawRow(
|
|
||||||
[]string{
|
|
||||||
m.Cari8, m.CariDetay,
|
|
||||||
format2(m.AcikKalemUSD), format2(m.AcikKalemTRY),
|
|
||||||
fmt.Sprintf("%d", m.AcikKalemOrtVadeGun), fmt.Sprintf("%d", m.AcikKalemOrtBelge),
|
|
||||||
format2(m.NormalUSD), format2(m.NormalTRY),
|
|
||||||
fmt.Sprintf("%d", m.OrtalamaVadeGun), fmt.Sprintf("%d", m.OrtalamaBelgeGun),
|
|
||||||
},
|
|
||||||
level1W, 6.4, true, [3]int{250, 246, 238},
|
|
||||||
map[int]bool{0: true, 4: true, 5: true, 8: true, 9: true},
|
|
||||||
map[int]bool{2: true, 3: true, 6: true, 7: true},
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, c := range m.Currencies {
|
|
||||||
if needPage(6 + 6 + 6) {
|
|
||||||
pageHeader()
|
|
||||||
drawHeaderRow(level1Cols, level1W, colorPrimary, 7)
|
|
||||||
drawRow(
|
|
||||||
[]string{
|
|
||||||
m.Cari8, m.CariDetay,
|
|
||||||
format2(m.AcikKalemUSD), format2(m.AcikKalemTRY),
|
|
||||||
fmt.Sprintf("%d", m.AcikKalemOrtVadeGun), fmt.Sprintf("%d", m.AcikKalemOrtBelge),
|
|
||||||
format2(m.NormalUSD), format2(m.NormalTRY),
|
|
||||||
fmt.Sprintf("%d", m.OrtalamaVadeGun), fmt.Sprintf("%d", m.OrtalamaBelgeGun),
|
|
||||||
},
|
|
||||||
level1W, 6.4, true, [3]int{250, 246, 238},
|
|
||||||
map[int]bool{0: true, 4: true, 5: true, 8: true, 9: true},
|
|
||||||
map[int]bool{2: true, 3: true, 6: true, 7: true},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
drawHeaderRow(level2Cols, level2W, colorLevel2, 6)
|
|
||||||
drawRow(
|
|
||||||
[]string{
|
|
||||||
c.Cari8, c.CariDetay, c.Doviz,
|
|
||||||
format2(c.AcikKalemTutar), format2(c.AcikKalemUSD), format2(c.AcikKalemTRY),
|
|
||||||
fmt.Sprintf("%d", c.OrtGun), fmt.Sprintf("%d", c.OrtBelgeGun),
|
|
||||||
},
|
|
||||||
level2W, 5.8, true, [3]int{236, 240, 247},
|
|
||||||
map[int]bool{0: true, 2: true, 6: true, 7: true},
|
|
||||||
map[int]bool{3: true, 4: true, 5: true},
|
|
||||||
)
|
|
||||||
|
|
||||||
if needPage(5.8) {
|
|
||||||
pageHeader()
|
|
||||||
}
|
|
||||||
drawHeaderRow(level3Cols, level3W, colorLevel3, 5.8)
|
|
||||||
|
|
||||||
for _, d := range c.Details {
|
|
||||||
if needPage(5.2) {
|
|
||||||
pageHeader()
|
|
||||||
drawHeaderRow(level3Cols, level3W, colorLevel3, 5.8)
|
|
||||||
}
|
|
||||||
drawRow(
|
|
||||||
[]string{
|
|
||||||
d.FaturaCari, d.OdemeCari, d.Doviz, d.FaturaRef, d.OdemeRef,
|
|
||||||
d.FaturaTarihi, d.OdemeTarihi, d.OdemeDocDate,
|
|
||||||
format2(d.EslesenTutar), format2(d.UsdTutar), format2(d.TryTutar), d.Aciklama,
|
|
||||||
fmt.Sprintf("%d", d.Gun), fmt.Sprintf("%d", d.GunBelge), trFormat(d.GunKur, 2),
|
|
||||||
},
|
|
||||||
level3W, 5.2, false, [3]int{},
|
|
||||||
map[int]bool{2: true, 5: true, 6: true, 7: true, 11: true, 12: true, 13: true, 14: true},
|
|
||||||
map[int]bool{8: true, 9: true, 10: true},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
pdf.Ln(1.2)
|
|
||||||
}
|
|
||||||
pdf.Ln(1.8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func trFormat(v float64, frac int) string {
|
|
||||||
neg := v < 0
|
|
||||||
if neg {
|
|
||||||
v = -v
|
|
||||||
}
|
|
||||||
pow := math.Pow(10, float64(frac))
|
|
||||||
rounded := math.Round(v*pow) / pow
|
|
||||||
intPart := int64(rounded)
|
|
||||||
decPart := int64(math.Round((rounded - float64(intPart)) * pow))
|
|
||||||
|
|
||||||
intStr := fmt.Sprintf("%d", intPart)
|
|
||||||
var grouped strings.Builder
|
|
||||||
for i, r := range intStr {
|
|
||||||
if i > 0 && (len(intStr)-i)%3 == 0 {
|
|
||||||
grouped.WriteString(".")
|
|
||||||
}
|
|
||||||
grouped.WriteRune(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
out := grouped.String()
|
|
||||||
if frac > 0 {
|
|
||||||
decFmt := fmt.Sprintf("%%0%dd", frac)
|
|
||||||
out += "," + fmt.Sprintf(decFmt, decPart)
|
|
||||||
}
|
|
||||||
if neg {
|
|
||||||
return "-" + out
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func readStrAny(v ...interface{}) string {
|
|
||||||
for _, x := range v {
|
|
||||||
switch t := x.(type) {
|
|
||||||
case nil:
|
|
||||||
case string:
|
|
||||||
if strings.TrimSpace(t) != "" {
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
case []byte:
|
|
||||||
s := strings.TrimSpace(string(t))
|
|
||||||
if s != "" {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
case time.Time:
|
|
||||||
return t.Format("2006-01-02")
|
|
||||||
default:
|
|
||||||
s := strings.TrimSpace(fmt.Sprint(t))
|
|
||||||
if s != "" && s != "<nil>" {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func readFloatAny(v ...interface{}) float64 {
|
|
||||||
for _, x := range v {
|
|
||||||
if x == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch t := x.(type) {
|
|
||||||
case float64:
|
|
||||||
return t
|
|
||||||
case float32:
|
|
||||||
return float64(t)
|
|
||||||
case int:
|
|
||||||
return float64(t)
|
|
||||||
case int32:
|
|
||||||
return float64(t)
|
|
||||||
case int64:
|
|
||||||
return float64(t)
|
|
||||||
case string:
|
|
||||||
if n, ok := parseNumberFlexible(t); ok {
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
case []byte:
|
|
||||||
if n, ok := parseNumberFlexible(string(t)); ok {
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if n, ok := parseNumberFlexible(fmt.Sprint(t)); ok {
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func readIntAny(v ...interface{}) int {
|
|
||||||
return int(math.Ceil(readFloatAny(v...)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseNumberFlexible(s string) (float64, bool) {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
if s == "" {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
hasComma := strings.Contains(s, ",")
|
|
||||||
hasDot := strings.Contains(s, ".")
|
|
||||||
if hasComma && hasDot {
|
|
||||||
if strings.LastIndex(s, ",") > strings.LastIndex(s, ".") {
|
|
||||||
s = strings.ReplaceAll(s, ".", "")
|
|
||||||
s = strings.Replace(s, ",", ".", 1)
|
|
||||||
} else {
|
|
||||||
s = strings.ReplaceAll(s, ",", "")
|
|
||||||
}
|
|
||||||
} else if hasComma {
|
|
||||||
s = strings.ReplaceAll(s, ".", "")
|
|
||||||
s = strings.Replace(s, ",", ".", 1)
|
|
||||||
}
|
|
||||||
n, err := strconv.ParseFloat(s, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
return n, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseYMD(v string) (time.Time, bool) {
|
|
||||||
v = strings.TrimSpace(v)
|
|
||||||
if v == "" {
|
|
||||||
return time.Time{}, false
|
|
||||||
}
|
|
||||||
t, err := time.Parse("2006-01-02", v)
|
|
||||||
if err != nil {
|
|
||||||
return time.Time{}, false
|
|
||||||
}
|
|
||||||
return t, true
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"bssapp-backend/queries"
|
"bssapp-backend/queries"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GET /api/statements
|
// GET /api/statements
|
||||||
@@ -23,6 +24,12 @@ func GetStatementHeadersHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
AccountCode: r.URL.Query().Get("accountcode"),
|
AccountCode: r.URL.Query().Get("accountcode"),
|
||||||
LangCode: r.URL.Query().Get("langcode"),
|
LangCode: r.URL.Query().Get("langcode"),
|
||||||
Parislemler: r.URL.Query()["parislemler"],
|
Parislemler: r.URL.Query()["parislemler"],
|
||||||
|
ExcludeOpening: false,
|
||||||
|
}
|
||||||
|
if raw := r.URL.Query().Get("excludeopening"); raw != "" {
|
||||||
|
if parsed, err := strconv.ParseBool(raw); err == nil {
|
||||||
|
params.ExcludeOpening = parsed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statements, err := queries.GetStatements(r.Context(), params)
|
statements, err := queries.GetStatements(r.Context(), params)
|
||||||
|
|||||||
200
ui/src/pages/AccountAgingComparison.vue
Normal file
200
ui/src/pages/AccountAgingComparison.vue
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
<template>
|
||||||
|
<q-page v-if="canReadFinance" class="q-pa-md">
|
||||||
|
<div class="row q-col-gutter-sm items-end q-mb-md">
|
||||||
|
<div class="col-12 col-sm-4 col-md-3">
|
||||||
|
<q-input v-model="accountCode" filled dense label="Cari Kod" clearable />
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-4 col-md-2">
|
||||||
|
<q-input v-model="endDate" filled dense label="Son Tarih" readonly>
|
||||||
|
<template #append>
|
||||||
|
<q-icon name="event" class="cursor-pointer">
|
||||||
|
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
||||||
|
<q-date v-model="endDate" mask="YYYY-MM-DD" locale="tr-TR" />
|
||||||
|
</q-popup-proxy>
|
||||||
|
</q-icon>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-4 col-md-3">
|
||||||
|
<q-select
|
||||||
|
v-model="parislemler"
|
||||||
|
:options="parislemOptions"
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
multiple
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
label="Parasal İşlem Tipi"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<q-btn color="primary" icon="filter_alt" label="Filtrele" :loading="store.loading" @click="load" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-caption text-grey-7 q-mb-sm">
|
||||||
|
Son Tarih bazlı fatura/ödeme karşılaştırma ekranı
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-table
|
||||||
|
:rows="store.masterRows"
|
||||||
|
:columns="masterColumns"
|
||||||
|
row-key="group_key"
|
||||||
|
flat
|
||||||
|
bordered
|
||||||
|
dense
|
||||||
|
:loading="store.loading"
|
||||||
|
:rows-per-page-options="[25, 50, 100, 0]"
|
||||||
|
>
|
||||||
|
<template #body="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-td auto-width>
|
||||||
|
<q-btn
|
||||||
|
size="sm"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
:icon="expandedMaster[props.row.group_key] ? 'remove' : 'add'"
|
||||||
|
@click="toggleMaster(props.row.group_key)"
|
||||||
|
/>
|
||||||
|
</q-td>
|
||||||
|
<q-td key="cari8" :props="props">{{ props.row.cari8 }}</q-td>
|
||||||
|
<q-td key="cari_detay" :props="props">{{ props.row.cari_detay }}</q-td>
|
||||||
|
<q-td key="acik_kalem_tutari_usd" :props="props" class="text-right">{{ fmt(props.row.acik_kalem_tutari_usd) }}</q-td>
|
||||||
|
<q-td key="acik_kalem_tutari_try" :props="props" class="text-right">{{ fmt(props.row.acik_kalem_tutari_try) }}</q-td>
|
||||||
|
<q-td key="acik_kalem_ort_vade_gun" :props="props" class="text-right">{{ fmt(props.row.acik_kalem_ort_vade_gun) }}</q-td>
|
||||||
|
<q-td key="acik_kalem_ort_belge_gun" :props="props" class="text-right">{{ fmt(props.row.acik_kalem_ort_belge_gun) }}</q-td>
|
||||||
|
</q-tr>
|
||||||
|
|
||||||
|
<q-tr v-if="expandedMaster[props.row.group_key]">
|
||||||
|
<q-td colspan="100%" class="bg-grey-1">
|
||||||
|
<q-table
|
||||||
|
:rows="store.getCurrenciesByMaster(props.row.group_key)"
|
||||||
|
:columns="currencyColumns"
|
||||||
|
row-key="group_key"
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
hide-bottom
|
||||||
|
>
|
||||||
|
<template #body="cprops">
|
||||||
|
<q-tr :props="cprops">
|
||||||
|
<q-td auto-width>
|
||||||
|
<q-btn
|
||||||
|
size="sm"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
:icon="expandedCurrency[cprops.row.group_key] ? 'remove' : 'add'"
|
||||||
|
@click="toggleCurrency(cprops.row.group_key)"
|
||||||
|
/>
|
||||||
|
</q-td>
|
||||||
|
<q-td key="doviz_cinsi" :props="cprops">{{ cprops.row.doviz_cinsi }}</q-td>
|
||||||
|
<q-td key="acik_kalem_tutari" :props="cprops" class="text-right">{{ fmt(cprops.row.acik_kalem_tutari) }}</q-td>
|
||||||
|
<q-td key="acik_kalem_usd" :props="cprops" class="text-right">{{ fmt(cprops.row.acik_kalem_usd) }}</q-td>
|
||||||
|
<q-td key="acik_kalem_try" :props="cprops" class="text-right">{{ fmt(cprops.row.acik_kalem_try) }}</q-td>
|
||||||
|
<q-td key="ort_gun" :props="cprops" class="text-right">{{ fmt(cprops.row.ort_gun) }}</q-td>
|
||||||
|
<q-td key="ort_belge_gun" :props="cprops" class="text-right">{{ fmt(cprops.row.ort_belge_gun) }}</q-td>
|
||||||
|
</q-tr>
|
||||||
|
|
||||||
|
<q-tr v-if="expandedCurrency[cprops.row.group_key]">
|
||||||
|
<q-td colspan="100%" class="bg-white">
|
||||||
|
<q-table
|
||||||
|
:rows="store.getDetailsByCurrency(cprops.row.group_key)"
|
||||||
|
:columns="detailColumns"
|
||||||
|
row-key="detail_key"
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
hide-bottom
|
||||||
|
/>
|
||||||
|
</q-td>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</q-td>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { reactive, ref } from 'vue'
|
||||||
|
import { useStatementAgingStore } from 'src/stores/statementAgingStore'
|
||||||
|
import { usePermission } from 'src/composables/usePermission'
|
||||||
|
|
||||||
|
const { canRead } = usePermission()
|
||||||
|
const canReadFinance = canRead('finance')
|
||||||
|
|
||||||
|
const store = useStatementAgingStore()
|
||||||
|
const accountCode = ref('')
|
||||||
|
const endDate = ref(new Date().toISOString().slice(0, 10))
|
||||||
|
const parislemler = ref(['2'])
|
||||||
|
|
||||||
|
const parislemOptions = [
|
||||||
|
{ label: '1-2 hesap', value: '2' },
|
||||||
|
{ label: '1-3 hesap', value: '3' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const expandedMaster = reactive({})
|
||||||
|
const expandedCurrency = reactive({})
|
||||||
|
|
||||||
|
const masterColumns = [
|
||||||
|
{ name: 'expand', label: '', field: 'expand' },
|
||||||
|
{ name: 'cari8', label: 'Cari', field: 'cari8', align: 'left' },
|
||||||
|
{ name: 'cari_detay', label: 'Cari Detay', field: 'cari_detay', align: 'left' },
|
||||||
|
{ name: 'acik_kalem_tutari_usd', label: 'Açık Kalem USD', field: 'acik_kalem_tutari_usd', align: 'right' },
|
||||||
|
{ name: 'acik_kalem_tutari_try', label: 'Açık Kalem TRY', field: 'acik_kalem_tutari_try', align: 'right' },
|
||||||
|
{ name: 'acik_kalem_ort_vade_gun', label: 'Ort Vade Gün', field: 'acik_kalem_ort_vade_gun', align: 'right' },
|
||||||
|
{ name: 'acik_kalem_ort_belge_gun', label: 'Ort Belge Gün', field: 'acik_kalem_ort_belge_gun', align: 'right' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const currencyColumns = [
|
||||||
|
{ name: 'expand', label: '', field: 'expand' },
|
||||||
|
{ name: 'doviz_cinsi', label: 'Döviz', field: 'doviz_cinsi', align: 'left' },
|
||||||
|
{ name: 'acik_kalem_tutari', label: 'Açık Kalem Tutar', field: 'acik_kalem_tutari', align: 'right' },
|
||||||
|
{ name: 'acik_kalem_usd', label: 'Açık Kalem USD', field: 'acik_kalem_usd', align: 'right' },
|
||||||
|
{ name: 'acik_kalem_try', label: 'Açık Kalem TRY', field: 'acik_kalem_try', align: 'right' },
|
||||||
|
{ name: 'ort_gun', label: 'Ort Gün', field: 'ort_gun', align: 'right' },
|
||||||
|
{ name: 'ort_belge_gun', label: 'Ort Belge Gün', field: 'ort_belge_gun', align: 'right' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const detailColumns = [
|
||||||
|
{ name: 'fatura_cari', label: 'Fatura Cari', field: 'fatura_cari', align: 'left' },
|
||||||
|
{ name: 'odeme_cari', label: 'Ödeme Cari', field: 'odeme_cari', align: 'left' },
|
||||||
|
{ name: '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 Tarihi', field: 'odeme_tarihi', 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: 'gun_sayisi', label: 'Gün Sayısı', field: 'gun_sayisi', align: 'right' },
|
||||||
|
{ name: 'gun_sayisi_docdate', label: 'Belge Gün', field: 'gun_sayisi_docdate', align: 'right' },
|
||||||
|
{ name: 'gun_kur', label: 'Gün Kur', field: 'gun_kur', align: 'right' },
|
||||||
|
{ name: 'aciklama', label: 'Açıklama', field: 'aciklama', align: 'left' }
|
||||||
|
]
|
||||||
|
|
||||||
|
function fmt(v) {
|
||||||
|
const n = Number(v)
|
||||||
|
if (!Number.isFinite(n)) return v ?? ''
|
||||||
|
return n.toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMaster(key) {
|
||||||
|
expandedMaster[key] = !expandedMaster[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCurrency(key) {
|
||||||
|
expandedCurrency[key] = !expandedCurrency[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
await store.load({
|
||||||
|
accountcode: accountCode.value || '',
|
||||||
|
enddate: endDate.value || '',
|
||||||
|
parislemler: parislemler.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
load()
|
||||||
|
</script>
|
||||||
@@ -1,379 +1,68 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-page v-if="canReadFinance" class="q-pa-md page-layout">
|
<q-page v-if="canReadFinance" class="q-px-md q-pb-md q-pt-xs page-col statement-page">
|
||||||
<div class="filter-sticky" :class="{ collapsed: filtersCollapsed }">
|
|
||||||
<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">
|
|
||||||
<q-input
|
|
||||||
v-model="store.filters.selectedDate"
|
|
||||||
label="Tarih (Bugün)"
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
readonly
|
|
||||||
disable
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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 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">
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<q-slide-transition>
|
<q-slide-transition>
|
||||||
<div v-show="!filtersCollapsed" class="filters-panel q-pa-sm q-mb-md">
|
<div v-show="!filtersCollapsed" class="local-filter-bar compact-filter q-pa-sm q-mb-xs">
|
||||||
<div class="row q-col-gutter-sm">
|
<div class="row q-col-gutter-sm items-end">
|
||||||
<div class="col-12 col-sm-6 col-md-4">
|
<div class="col-12 col-md-5">
|
||||||
<q-input
|
<q-select
|
||||||
v-model="store.filters.cariSearch"
|
v-model="selectedCari"
|
||||||
|
:options="filteredOptions"
|
||||||
|
label="Cari kod / isim"
|
||||||
filled
|
filled
|
||||||
dense
|
dense
|
||||||
label="Cari Kodu / Cari Adı"
|
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>
|
||||||
|
|
||||||
<div class="col-12 col-sm-6 col-md-2">
|
<div class="col-12 col-sm-6 col-md-2">
|
||||||
<q-select
|
<q-input v-model="dateTo" label="Son tarih" filled dense clearable readonly>
|
||||||
v-model="store.filters.cariIlkGrup"
|
<template #append>
|
||||||
:options="store.cariIlkGrupOptions"
|
<q-icon name="event" class="cursor-pointer">
|
||||||
multiple
|
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
||||||
emit-value
|
<q-date v-model="dateTo" mask="YYYY-MM-DD" locale="tr-TR" />
|
||||||
map-options
|
</q-popup-proxy>
|
||||||
filled
|
</q-icon>
|
||||||
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>
|
||||||
<template #option="scope">
|
</q-input>
|
||||||
<q-item v-bind="scope.itemProps">
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-checkbox :model-value="scope.selected" />
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
</q-select>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-12 col-sm-6 col-md-3">
|
||||||
<div class="col-12 col-sm-6 col-md-2">
|
|
||||||
<q-select
|
<q-select
|
||||||
v-model="store.filters.piyasa"
|
v-model="selectedMonType"
|
||||||
:options="store.piyasaOptions"
|
:options="monetaryTypeOptions"
|
||||||
multiple
|
label="Parasal İşlem Tipi"
|
||||||
emit-value
|
emit-value
|
||||||
map-options
|
map-options
|
||||||
filled
|
filled
|
||||||
dense
|
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>
|
||||||
|
<div class="col-auto">
|
||||||
<div class="col-12 col-sm-6 col-md-2">
|
<q-btn color="primary" icon="filter_alt" label="Filtrele" @click="onFilterClick" />
|
||||||
<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>
|
||||||
|
<div class="col-auto">
|
||||||
<div class="col-12 col-sm-6 col-md-2">
|
<q-btn flat color="grey-8" icon="restart_alt" label="Sıfırla" @click="resetFilters" />
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-slide-transition>
|
</q-slide-transition>
|
||||||
|
|
||||||
<q-banner v-if="store.error" class="bg-red-1 text-negative q-mb-md rounded-borders">
|
<div class="table-scroll">
|
||||||
{{ store.error }}
|
<div class="sticky-bar row justify-end items-center q-pa-sm bg-grey-1 q-gutter-sm">
|
||||||
</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 class="table-area">
|
|
||||||
<div class="sticky-bar row justify-between items-center q-pa-sm bg-grey-1">
|
|
||||||
<div />
|
|
||||||
<div class="row items-center q-gutter-sm">
|
|
||||||
<q-btn
|
<q-btn
|
||||||
flat
|
flat
|
||||||
color="secondary"
|
color="primary"
|
||||||
icon="list"
|
:icon="filtersCollapsed ? 'unfold_more' : 'unfold_less'"
|
||||||
:label="allDetailsOpen ? 'Tüm Detayları Kapat' : 'Tüm Detayları Aç'"
|
:label="filtersCollapsed ? 'Filtreleri Genişlet' : 'Filtreleri Daralt'"
|
||||||
@click="toggleAllDetails"
|
@click="toggleFiltersCollapsed"
|
||||||
/>
|
/>
|
||||||
<q-btn-dropdown
|
<q-btn-dropdown
|
||||||
v-if="canExportFinance"
|
v-if="canExportFinance"
|
||||||
@@ -405,50 +94,36 @@
|
|||||||
/>
|
/>
|
||||||
<q-btn
|
<q-btn
|
||||||
flat
|
flat
|
||||||
color="primary"
|
color="secondary"
|
||||||
:icon="filtersCollapsed ? 'unfold_more' : 'unfold_less'"
|
icon="list"
|
||||||
:label="filtersCollapsed ? 'Filtreleri Genişlet' : 'Filtreleri Daralt'"
|
:label="allDetailsOpen ? 'Tüm Detayları Kapat' : 'Tüm Detayları Aç'"
|
||||||
@click="toggleFiltersCollapsed"
|
@click="toggleAllDetails"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<q-table
|
<q-table
|
||||||
title="Cari Yaşlandırmalı Cari Bakiye Listesi"
|
class="sticky-table statement-table"
|
||||||
:rows="store.summaryRows"
|
title="Cari Yaşlandırmalı Ekstre"
|
||||||
:columns="summaryColumns"
|
:rows="agingStore.masterRows"
|
||||||
|
:columns="masterColumns"
|
||||||
row-key="group_key"
|
row-key="group_key"
|
||||||
:loading="store.loading"
|
|
||||||
flat
|
flat
|
||||||
bordered
|
bordered
|
||||||
dense
|
dense
|
||||||
wrap-cells
|
|
||||||
separator="cell"
|
|
||||||
hide-bottom
|
hide-bottom
|
||||||
|
wrap-cells
|
||||||
:rows-per-page-options="[0]"
|
:rows-per-page-options="[0]"
|
||||||
:pagination="{ rowsPerPage: 0 }"
|
:loading="agingStore.loading"
|
||||||
:table-style="{ tableLayout: 'fixed', width: '100%' }"
|
:table-style="{ tableLayout: 'fixed', width: '100%' }"
|
||||||
class="balance-table"
|
|
||||||
>
|
>
|
||||||
<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">
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">{{ col.label }}</q-th>
|
||||||
{{ 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="sub-header-row">
|
<q-tr :props="props" class="master-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'"
|
||||||
@@ -456,287 +131,301 @@
|
|||||||
flat
|
flat
|
||||||
round
|
round
|
||||||
size="sm"
|
size="sm"
|
||||||
:icon="expanded[props.row.group_key] ? 'expand_less' : 'expand_more'"
|
:icon="masterExpanded[props.row.group_key] ? 'expand_less' : 'expand_more'"
|
||||||
@click="toggleGroup(props.row.group_key)"
|
@click="toggleMaster(props.row.group_key)"
|
||||||
/>
|
/>
|
||||||
<span v-else-if="col.name === 'prbr_1_2'" class="text-right block prbr-cell">
|
<span
|
||||||
{{ formatCurrencyMap(props.row.bakiye_1_2_map) }}
|
v-else-if="masterNumericCols.includes(col.name)"
|
||||||
</span>
|
:class="['block', masterCenteredCols.includes(col.name) ? 'text-center' : 'text-right']"
|
||||||
<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]) }}
|
{{ formatAmount(props.row[col.field]) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="dayFields.includes(col.name)" class="text-center block">
|
<span v-else>{{ props.row[col.field] ?? '-' }}</span>
|
||||||
{{ 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="expanded[props.row.group_key]" class="detail-host-row">
|
<q-tr v-if="masterExpanded[props.row.group_key]" class="master-sub-row">
|
||||||
<q-td colspan="100%">
|
<q-td colspan="100%" class="q-pa-none">
|
||||||
<div class="detail-wrap">
|
<div class="currency-groups">
|
||||||
<q-table
|
<div class="currency-level-head">
|
||||||
:rows="store.getDetailsByGroup(props.row.group_key)"
|
<div class="cgh-cell cgh-expand"></div>
|
||||||
:columns="detailColumns"
|
<div class="cgh-cell cgh-code">Döviz</div>
|
||||||
row-key="cari_kodu"
|
<div class="cgh-cell cgh-num">Satır</div>
|
||||||
|
<div class="cgh-cell cgh-num">Toplam Tutar</div>
|
||||||
|
<div class="cgh-cell cgh-num">Toplam USD</div>
|
||||||
|
<div class="cgh-cell cgh-num">Normal</div>
|
||||||
|
<div class="cgh-cell cgh-num">Açık Kalem</div>
|
||||||
|
<div class="cgh-cell cgh-center">Ort. Gün</div>
|
||||||
|
<div class="cgh-cell cgh-center">Ort. Gün (DocDate)</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="currRow in agingStore.getCurrenciesByMaster(props.row.group_key)"
|
||||||
|
:key="currRow.group_key"
|
||||||
|
class="currency-group"
|
||||||
|
>
|
||||||
|
<div class="currency-group-header">
|
||||||
|
<div class="cgh-cell cgh-expand">
|
||||||
|
<q-btn
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
|
round
|
||||||
|
size="sm"
|
||||||
|
:icon="currencyExpanded[currRow.group_key] ? 'expand_less' : 'expand_more'"
|
||||||
|
@click="toggleCurrency(currRow.group_key)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="cgh-cell cgh-code">{{ currRow.doc_currency_code }}</div>
|
||||||
|
<div class="cgh-cell cgh-num">{{ formatAmount(currRow.satir_sayisi, 0) }}</div>
|
||||||
|
<div class="cgh-cell cgh-num">{{ formatAmount(currRow.toplam_tutar) }}</div>
|
||||||
|
<div class="cgh-cell cgh-num">{{ formatAmount(currRow.toplam_usd) }}</div>
|
||||||
|
<div class="cgh-cell cgh-num">{{ formatAmount(currRow.normal_tutar) }}</div>
|
||||||
|
<div class="cgh-cell cgh-num">{{ formatAmount(currRow.acik_kalem_tutar) }}</div>
|
||||||
|
<div class="cgh-cell cgh-center">{{ formatAmount(currRow.ortalama_gun, 0) }}</div>
|
||||||
|
<div class="cgh-cell cgh-center">{{ formatAmount(currRow.ortalama_gun_docdate, 0) }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="currencyExpanded[currRow.group_key]" class="detail-host-row">
|
||||||
|
<q-table
|
||||||
|
:rows="agingStore.getDetailsByCurrency(currRow.group_key)"
|
||||||
|
:columns="detailColumns"
|
||||||
|
row-key="detail_key"
|
||||||
|
flat
|
||||||
|
dense
|
||||||
bordered
|
bordered
|
||||||
hide-bottom
|
hide-bottom
|
||||||
:table-style="{ tableLayout: 'fixed', width: '100%' }"
|
:rows-per-page-options="[0]"
|
||||||
class="detail-table"
|
:pagination="{ rowsPerPage: 0 }"
|
||||||
|
class="detail-subtable"
|
||||||
|
:table-style="{ minWidth: '1500px' }"
|
||||||
>
|
>
|
||||||
<template #body-cell-prbr_1_2="scope">
|
<template #body-cell-eslesen_tutar="d">
|
||||||
<q-td :props="scope" class="text-right prbr-cell">
|
<q-td :props="d" class="text-right">{{ formatAmount(d.row.eslesen_tutar) }}</q-td>
|
||||||
{{ formatRowPrBr(scope.row, '1_2') }}
|
|
||||||
</q-td>
|
|
||||||
</template>
|
</template>
|
||||||
<template #body-cell-prbr_1_3="scope">
|
<template #body-cell-usd_tutar="d">
|
||||||
<q-td :props="scope" class="text-right prbr-cell">
|
<q-td :props="d" class="text-right">{{ formatAmount(d.row.usd_tutar) }}</q-td>
|
||||||
{{ formatRowPrBr(scope.row, '1_3') }}
|
|
||||||
</q-td>
|
|
||||||
</template>
|
</template>
|
||||||
<template #body-cell-vade_gun="scope">
|
<template #body-cell-gun_sayisi="d">
|
||||||
<q-td :props="scope" class="text-center">
|
<q-td :props="d" class="text-center">{{ formatAmount(d.row.gun_sayisi, 0) }}</q-td>
|
||||||
{{ formatDay(scope.row.vade_gun) }}
|
|
||||||
</q-td>
|
|
||||||
</template>
|
</template>
|
||||||
<template #body-cell-vade_belge_tarihi_gun="scope">
|
<template #body-cell-gun_sayisi_docdate="d">
|
||||||
<q-td :props="scope" class="text-center">
|
<q-td :props="d" class="text-center">{{ formatAmount(d.row.gun_sayisi_docdate, 0) }}</q-td>
|
||||||
{{ formatDay(scope.row.vade_belge_tarihi_gun) }}
|
|
||||||
</q-td>
|
|
||||||
</template>
|
</template>
|
||||||
</q-table>
|
</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">
|
<div class="text-negative text-subtitle1">Bu modüle erişim yetkiniz yok.</div>
|
||||||
Bu modüle erişim yetkiniz yok.
|
|
||||||
</div>
|
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { useQuasar } from 'quasar'
|
import { useQuasar } from 'quasar'
|
||||||
import { onMounted } from 'vue'
|
import dayjs from 'dayjs'
|
||||||
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 islemTipiOptions = [
|
const $q = useQuasar()
|
||||||
{ label: '1_2 Bakiye Pr.Br', value: 'prbr_1_2' },
|
const accountStore = useAccountStore()
|
||||||
{ label: '1_3 Bakiye Pr.Br', value: 'prbr_1_3' },
|
const agingStore = useStatementAgingStore()
|
||||||
{ label: '1_2 USD Bakiye', value: 'usd_1_2' },
|
|
||||||
{ label: '1_2 TRY Bakiye', value: 'try_1_2' },
|
const selectedCari = ref(null)
|
||||||
{ label: '1_3 USD Bakiye', value: 'usd_1_3' },
|
const filteredOptions = ref([])
|
||||||
{ label: '1_3 TRY Bakiye', value: 'try_1_3' }
|
const dateTo = ref(dayjs().format('YYYY-MM-DD'))
|
||||||
|
|
||||||
|
const masterExpanded = ref({})
|
||||||
|
const currencyExpanded = ref({})
|
||||||
|
const allDetailsOpen = ref(false)
|
||||||
|
const filtersCollapsed = 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: 'satir_sayisi', label: 'Satır', field: 'satir_sayisi', align: 'right', sortable: true },
|
||||||
|
{ name: 'toplam_usd', label: 'Toplam USD', field: 'toplam_usd', align: 'right', sortable: true },
|
||||||
|
{ name: 'normal_usd', label: 'Normal USD', field: 'normal_usd', align: 'right', sortable: true },
|
||||||
|
{ name: 'acik_kalem_usd', label: 'Açık Kalem USD', field: 'acik_kalem_usd', align: 'right', sortable: true },
|
||||||
|
{ name: 'ortalama_gun', label: 'Ort. Gün', field: 'ortalama_gun', align: 'center', sortable: true },
|
||||||
|
{ name: 'ortalama_gun_docdate', label: 'Ort. Gün (DocDate)', field: 'ortalama_gun_docdate', align: 'center', sortable: true }
|
||||||
]
|
]
|
||||||
|
|
||||||
const staticMoneyFields = ['usd_bakiye_1_2', 'tl_bakiye_1_2', 'usd_bakiye_1_3', 'tl_bakiye_1_3']
|
const detailColumns = [
|
||||||
const dayFields = ['vade_gun', 'vade_belge_tarihi_gun']
|
{ name: 'fatura_cari', label: 'Fatura Cari', field: 'fatura_cari', align: 'left' },
|
||||||
|
{ name: 'odeme_cari', label: 'Ödeme Cari', field: 'odeme_cari', 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: '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: 'aciklama', label: 'Açıklama', field: 'aciklama', align: 'left' },
|
||||||
|
{ name: 'doc_currency_code', label: 'Döviz', field: 'doc_currency_code', align: 'left' }
|
||||||
|
]
|
||||||
|
|
||||||
function toNumericSortValue (value) {
|
const masterNumericCols = ['satir_sayisi', 'toplam_usd', 'normal_usd', 'acik_kalem_usd', 'ortalama_gun', 'ortalama_gun_docdate']
|
||||||
if (typeof value === 'number') {
|
const masterCenteredCols = ['ortalama_gun', 'ortalama_gun_docdate']
|
||||||
return Number.isFinite(value) ? value : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
const s = String(value ?? '').trim()
|
function normalizeText(str) {
|
||||||
if (!s) return 0
|
return (str || '')
|
||||||
|
.toString()
|
||||||
const hasComma = s.includes(',')
|
.toLocaleLowerCase('tr-TR')
|
||||||
const hasDot = s.includes('.')
|
.normalize('NFD')
|
||||||
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
let normalized = s.replace(/\s+/g, '')
|
.trim()
|
||||||
|
|
||||||
if (hasComma && hasDot) {
|
|
||||||
const lastComma = normalized.lastIndexOf(',')
|
|
||||||
const lastDot = normalized.lastIndexOf('.')
|
|
||||||
if (lastComma > lastDot) {
|
|
||||||
normalized = normalized.replace(/\./g, '').replace(',', '.')
|
|
||||||
} else {
|
|
||||||
normalized = normalized.replace(/,/g, '')
|
|
||||||
}
|
|
||||||
} else if (hasComma) {
|
|
||||||
normalized = normalized.replace(/\./g, '').replace(',', '.')
|
|
||||||
}
|
|
||||||
|
|
||||||
const n = Number.parseFloat(normalized)
|
|
||||||
return Number.isFinite(n) ? n : 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortTextTr (a, b) {
|
function filterCari(val, update) {
|
||||||
return String(a ?? '').localeCompare(String(b ?? ''), 'tr', { sensitivity: 'base' })
|
const needle = normalizeText(val)
|
||||||
}
|
|
||||||
|
|
||||||
const metricDefs = {
|
update(() => {
|
||||||
prbr_1_2: { name: 'prbr_1_2', label: '1_2 Bakiye\nPr.Br', field: 'prbr_1_2', align: 'right', sortable: false },
|
if (!needle) {
|
||||||
prbr_1_3: { name: 'prbr_1_3', label: '1_3 Bakiye\nPr.Br', field: 'prbr_1_3', align: 'right', sortable: false },
|
filteredOptions.value = accountStore.accountOptions
|
||||||
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) },
|
return
|
||||||
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)
|
|
||||||
})
|
|
||||||
|
|
||||||
const summaryColumns = computed(() => ([
|
|
||||||
{ name: 'expand', label: '', field: 'expand', align: 'center', sortable: false },
|
|
||||||
{ name: 'ana_cari_kodu', label: 'Ana Cari Kodu', field: 'ana_cari_kodu', align: 'left', sortable: true, sort: sortTextTr },
|
|
||||||
{ name: 'ana_cari_adi', label: 'Ana Cari Detay', field: 'ana_cari_adi', align: 'left', sortable: true, sort: sortTextTr },
|
|
||||||
{ name: 'piyasa', label: 'Piyasa', field: 'piyasa', align: 'left', sortable: true, sort: sortTextTr },
|
|
||||||
{ 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
|
|
||||||
}, {
|
filteredOptions.value = accountStore.accountOptions.filter(o => {
|
||||||
usd_bakiye_1_2: 0,
|
const label = normalizeText(o.label)
|
||||||
tl_bakiye_1_2: 0,
|
const value = normalizeText(o.value)
|
||||||
usd_bakiye_1_3: 0,
|
return label.includes(needle) || value.includes(needle)
|
||||||
tl_bakiye_1_3: 0,
|
|
||||||
vade_gun_sum: 0,
|
|
||||||
vade_belge_sum: 0,
|
|
||||||
vade_count: 0
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await accountStore.fetchAccounts()
|
||||||
|
filteredOptions.value = accountStore.accountOptions
|
||||||
})
|
})
|
||||||
|
|
||||||
const detailColumns = computed(() => [
|
async function onFilterClick() {
|
||||||
{ name: 'cari_kodu', label: 'Cari Kodu', field: 'cari_kodu', align: 'left' },
|
if (!selectedCari.value || !dateTo.value) {
|
||||||
{ name: 'cari_detay', label: 'Cari Detay', field: 'cari_detay', align: 'left' },
|
$q.notify({
|
||||||
{ name: 'sirket', label: 'Şirket', field: 'sirket', align: 'left' },
|
type: 'warning',
|
||||||
{ name: 'sirket_detay', label: 'Şirket Detayı', field: 'sirket_detay', align: 'left' },
|
message: 'Lütfen cari ve son tarih seçiniz.',
|
||||||
{ name: 'muhasebe_kodu', label: 'Muhasebe Kodu', field: 'muhasebe_kodu', align: 'left' },
|
position: 'top-right'
|
||||||
{ 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
|
|
||||||
}
|
|
||||||
|
|
||||||
allDetailsOpen.value =
|
|
||||||
store.summaryRows.length > 0 &&
|
|
||||||
store.summaryRows.every(r => expanded.value[r.group_key])
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleAllDetails () {
|
|
||||||
allDetailsOpen.value = !allDetailsOpen.value
|
|
||||||
|
|
||||||
if (allDetailsOpen.value) {
|
|
||||||
const next = {}
|
|
||||||
for (const row of store.summaryRows) {
|
|
||||||
next[row.group_key] = true
|
|
||||||
}
|
|
||||||
expanded.value = next
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
expanded.value = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function downloadAgingBalancePDF (detailed) {
|
|
||||||
if (!canExportFinance.value) {
|
|
||||||
$q.notify({ type: 'negative', message: 'PDF export yetkiniz yok', position: 'top-right' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!store.hasFetched) {
|
|
||||||
$q.notify({ type: 'warning', message: 'Önce Bakiyeleri Getir ile veri yükleyin.', position: 'top-right' })
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const params = {
|
await agingStore.load({
|
||||||
cari_search: String(store.filters.cariSearch || '').trim(),
|
accountcode: selectedCari.value,
|
||||||
cari_ilk_grup: (store.filters.cariIlkGrup || []).join(','),
|
enddate: dateTo.value,
|
||||||
piyasa: (store.filters.piyasa || []).join(','),
|
parislemler: selectedMonType.value
|
||||||
temsilci: (store.filters.temsilci || []).join(','),
|
})
|
||||||
risk_durumu: (store.filters.riskDurumu || []).join(','),
|
|
||||||
islem_tipi: (store.filters.islemTipi || []).join(','),
|
const m = {}
|
||||||
ulke: (store.filters.ulke || []).join(','),
|
const c = {}
|
||||||
il: (store.filters.il || []).join(','),
|
for (const row of agingStore.masterRows) {
|
||||||
ilce: (store.filters.ilce || []).join(','),
|
m[row.group_key] = true
|
||||||
exclude_zero_12: store.filters.excludeZeroBalance12 ? '1' : '0',
|
for (const cr of agingStore.getCurrenciesByMaster(row.group_key)) {
|
||||||
exclude_zero_13: store.filters.excludeZeroBalance13 ? '1' : '0',
|
c[cr.group_key] = true
|
||||||
detailed: detailed ? '1' : '0'
|
}
|
||||||
|
}
|
||||||
|
masterExpanded.value = m
|
||||||
|
currencyExpanded.value = c
|
||||||
|
allDetailsOpen.value = agingStore.masterRows.length > 0
|
||||||
|
} catch (err) {
|
||||||
|
const msg = await extractApiErrorDetail(err)
|
||||||
|
$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
message: msg || 'Veriler yüklenemedi',
|
||||||
|
position: 'top-right'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetFilters() {
|
||||||
|
selectedCari.value = null
|
||||||
|
dateTo.value = dayjs().format('YYYY-MM-DD')
|
||||||
|
selectedMonType.value = monetaryTypeOptions[0].value
|
||||||
|
masterExpanded.value = {}
|
||||||
|
currencyExpanded.value = {}
|
||||||
|
allDetailsOpen.value = false
|
||||||
|
agingStore.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMaster(key) {
|
||||||
|
masterExpanded.value[key] = !masterExpanded.value[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCurrency(key) {
|
||||||
|
currencyExpanded.value[key] = !currencyExpanded.value[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAllDetails() {
|
||||||
|
allDetailsOpen.value = !allDetailsOpen.value
|
||||||
|
|
||||||
|
if (!allDetailsOpen.value) {
|
||||||
|
masterExpanded.value = {}
|
||||||
|
currencyExpanded.value = {}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const blob = await download('/finance/account-aging-statement/export-pdf', params)
|
const m = {}
|
||||||
|
const c = {}
|
||||||
|
for (const row of agingStore.masterRows) {
|
||||||
|
m[row.group_key] = true
|
||||||
|
for (const cr of agingStore.getCurrenciesByMaster(row.group_key)) {
|
||||||
|
c[cr.group_key] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
masterExpanded.value = m
|
||||||
|
currencyExpanded.value = c
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleFiltersCollapsed() {
|
||||||
|
filtersCollapsed.value = !filtersCollapsed.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildExportParams(detailed = false) {
|
||||||
|
return {
|
||||||
|
accountcode: String(selectedCari.value || '').trim(),
|
||||||
|
cari_search: String(selectedCari.value || '').trim(),
|
||||||
|
enddate: dateTo.value,
|
||||||
|
selected_date: dateTo.value,
|
||||||
|
parislemler: selectedMonType.value,
|
||||||
|
detailed: detailed ? '1' : '0',
|
||||||
|
exclude_zero_12: '0',
|
||||||
|
exclude_zero_13: '0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadAgingBalancePDF(detailed) {
|
||||||
|
if (!canExportFinance.value) {
|
||||||
|
$q.notify({ type: 'negative', message: 'PDF export yetkiniz yok', position: 'top-right' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!selectedCari.value || !dateTo.value) {
|
||||||
|
$q.notify({ type: 'warning', message: 'Önce cari ve son tarih seçiniz.', position: 'top-right' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const blob = await download('/finance/account-aging-statement/export-pdf', buildExportParams(detailed))
|
||||||
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) {
|
||||||
@@ -749,37 +438,19 @@ async function downloadAgingBalancePDF (detailed) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function downloadAgingBalanceExcel () {
|
async function downloadAgingBalanceExcel() {
|
||||||
if (!canExportFinance.value) {
|
if (!canExportFinance.value) {
|
||||||
$q.notify({ type: 'negative', message: 'Excel export yetkiniz yok', position: 'top-right' })
|
$q.notify({ type: 'negative', message: 'Excel export yetkiniz yok', position: 'top-right' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (!selectedCari.value || !dateTo.value) {
|
||||||
if (!store.hasFetched) {
|
$q.notify({ type: 'warning', message: 'Önce 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 params = {
|
const file = await download('/finance/account-aging-statement/export-excel', buildExportParams(false))
|
||||||
cari_search: String(store.filters.cariSearch || '').trim(),
|
const blob = new Blob([file], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
||||||
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 url = window.URL.createObjectURL(blob)
|
||||||
const a = document.createElement('a')
|
const a = document.createElement('a')
|
||||||
a.href = url
|
a.href = url
|
||||||
@@ -798,261 +469,224 @@ async function downloadAgingBalanceExcel () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatAmount (value) {
|
function formatAmount(value, fraction = 2) {
|
||||||
const n = Number(value || 0)
|
const n = Number(value || 0)
|
||||||
return new Intl.NumberFormat('tr-TR', {
|
return new Intl.NumberFormat('tr-TR', {
|
||||||
minimumFractionDigits: 2,
|
minimumFractionDigits: fraction,
|
||||||
maximumFractionDigits: 2
|
maximumFractionDigits: fraction
|
||||||
}).format(n)
|
}).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>
|
||||||
.page-layout {
|
.statement-page {
|
||||||
height: calc(100vh - 110px);
|
height: calc(100vh - 56px);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-sticky {
|
.table-scroll {
|
||||||
position: sticky;
|
flex: 1;
|
||||||
top: 0;
|
min-height: 0;
|
||||||
z-index: 20;
|
overflow: hidden;
|
||||||
background: #fff;
|
display: flex;
|
||||||
padding-bottom: 6px;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-sticky.collapsed {
|
.compact-filter {
|
||||||
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 {
|
|
||||||
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) {
|
.statement-page .local-filter-bar {
|
||||||
min-height: 40px;
|
position: sticky;
|
||||||
}
|
top: 0;
|
||||||
|
z-index: 40;
|
||||||
.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: 9;
|
z-index: 30;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
background: var(--q-secondary);
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-table {
|
.statement-table {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-table :deep(.q-table__container) {
|
.statement-table :deep(.q-table__container) {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-table :deep(.q-table__top) {
|
.statement-table :deep(.q-table__top) {
|
||||||
position: sticky;
|
flex: 0 0 auto;
|
||||||
top: 0;
|
position: static;
|
||||||
z-index: 6;
|
|
||||||
background: #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-table :deep(.q-table__middle) {
|
.statement-table :deep(.q-table__middle) {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow: auto !important;
|
||||||
overflow-x: hidden;
|
max-height: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-table :deep(.header-row th) {
|
.statement-table :deep(.header-row th) {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 8;
|
z-index: 30;
|
||||||
background: var(--q-primary);
|
background: var(--q-primary);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-family: 'Roboto', sans-serif;
|
border-bottom: 1px solid rgba(255, 255, 255, 0.22);
|
||||||
|
box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.2), 0 1px 0 rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-table :deep(.totals-row th) {
|
.statement-table :deep(.master-row td) {
|
||||||
position: sticky;
|
background: color-mix(in srgb, var(--q-secondary) 12%, white);
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-table :deep(.sub-header-row td:first-child) {
|
.statement-table :deep(.master-row td:first-child) {
|
||||||
border-left: 3px solid var(--q-primary);
|
border-left: 3px solid var(--q-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-table :deep(.detail-host-row td) {
|
.statement-table :deep(.master-sub-row td) {
|
||||||
background: #f7f7f7;
|
background: #f4f6fb;
|
||||||
border-bottom: 10px solid #fff;
|
border-bottom: 8px solid #fff;
|
||||||
padding-top: 10px;
|
vertical-align: top;
|
||||||
padding-bottom: 12px;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-wrap {
|
.currency-groups {
|
||||||
border: 1px solid rgba(0, 0, 0, 0.14);
|
|
||||||
border-left: 4px solid var(--q-secondary);
|
|
||||||
border-radius: 6px;
|
|
||||||
background: #fff;
|
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
|
background: #f8faff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-table :deep(.header-row th) {
|
.currency-group {
|
||||||
white-space: pre-line;
|
border-left: 4px solid var(--q-secondary);
|
||||||
line-height: 1.15;
|
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
border-right: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
background: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prbr-cell {
|
.currency-level-head {
|
||||||
white-space: pre-line;
|
position: sticky;
|
||||||
word-break: break-word;
|
top: 36px;
|
||||||
line-height: 1.25;
|
z-index: 26;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 48px 100px 80px 1.2fr 1.2fr 1.1fr 1.1fr 110px 140px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
background: var(--q-secondary);
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-table :deep(th),
|
.currency-group-header {
|
||||||
.balance-table :deep(td),
|
position: sticky;
|
||||||
.detail-table :deep(th),
|
top: 72px;
|
||||||
.detail-table :deep(td) {
|
z-index: 24;
|
||||||
white-space: normal !important;
|
display: grid;
|
||||||
word-break: break-word;
|
grid-template-columns: 48px 100px 80px 1.2fr 1.2fr 1.1fr 1.1fr 110px 140px;
|
||||||
overflow-wrap: anywhere;
|
align-items: center;
|
||||||
|
gap: 0;
|
||||||
|
background: #4c5f7a;
|
||||||
|
color: #fff;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.18);
|
||||||
|
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cgh-cell {
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-right: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
font-weight: 600;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
line-height: 1.2;
|
|
||||||
padding: 4px 6px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.balance-table :deep(.q-table__table),
|
.cgh-cell:last-child {
|
||||||
.detail-table :deep(.q-table__table) {
|
border-right: none;
|
||||||
width: 100% !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-table :deep(.q-table__middle) {
|
.cgh-num {
|
||||||
overflow-x: hidden;
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cgh-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cgh-expand {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-host-row :deep(td) {
|
||||||
|
background: #fdfdfd;
|
||||||
|
padding: 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-subtable {
|
||||||
|
border-left: 4px solid var(--q-primary);
|
||||||
|
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
border-right: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statement-table :deep(th),
|
||||||
|
.statement-table :deep(td),
|
||||||
|
.detail-subtable :deep(th),
|
||||||
|
.detail-subtable :deep(td) {
|
||||||
|
padding: 3px 6px !important;
|
||||||
|
font-size: 11px !important;
|
||||||
|
line-height: 1.2 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-subtable :deep(.q-table__top) {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-subtable :deep(thead th) {
|
||||||
|
position: sticky;
|
||||||
|
top: 108px;
|
||||||
|
z-index: 22;
|
||||||
|
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"]),
|
||||||
|
.statement-table :deep(td[data-col="ortalama_gun"]),
|
||||||
|
.statement-table :deep(td[data-col="ortalama_gun_docdate"]) {
|
||||||
|
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 90px 70px 1.1fr 1.1fr 1fr 1fr 90px 120px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1148
ui/src/pages/AgingCustomerBalancelist.go.vue
Normal file
1148
ui/src/pages/AgingCustomerBalancelist.go.vue
Normal file
@@ -0,0 +1,1148 @@
|
|||||||
|
Logo
|
||||||
|
Konular
|
||||||
|
Değişiklik İstekleri
|
||||||
|
Dönüm noktaları
|
||||||
|
Keşfet
|
||||||
|
M_Kececi
|
||||||
|
/
|
||||||
|
bssapp
|
||||||
|
Kod
|
||||||
|
Konular
|
||||||
|
Değişiklik İstekleri
|
||||||
|
İşlemler
|
||||||
|
Paketler
|
||||||
|
Projeler
|
||||||
|
Sürüm
|
||||||
|
Wiki
|
||||||
|
Aktivite
|
||||||
|
Ayarlar
|
||||||
|
Dosyalar
|
||||||
|
AccountAgingStatement.vue
|
||||||
|
ActivityLogs.vue
|
||||||
|
ChangePassword.vue
|
||||||
|
CustomerBalanceList.vue
|
||||||
|
Dashboard.vue
|
||||||
|
ErrorNotFound.vue
|
||||||
|
FirstPasswordChange.vue
|
||||||
|
MainPage.vue
|
||||||
|
MainPanel.vue
|
||||||
|
MePassword.vue
|
||||||
|
OrderBulkClose.vue
|
||||||
|
OrderEntry.vue
|
||||||
|
OrderGateway.vue
|
||||||
|
OrderList.vue
|
||||||
|
OrderPdf.vue
|
||||||
|
OrderProductionUpdate.vue
|
||||||
|
OrderProductionUpdateList.vue
|
||||||
|
PermissionMatrix.vue
|
||||||
|
ProductStockByAttributes.vue
|
||||||
|
ProductStockQuery.vue
|
||||||
|
ProductionWorker.vue
|
||||||
|
ProductionWorkerGateway.vue
|
||||||
|
ResetPassword.vue
|
||||||
|
RoleDepartmentPermissionGateway.vue
|
||||||
|
RoleDepartmentPermissionList.vue
|
||||||
|
RoleDepartmentPermissionPage.vue
|
||||||
|
StatementHeaderReport.vue
|
||||||
|
StatementReport.vue
|
||||||
|
TestMail.vue
|
||||||
|
UserDetail.vue
|
||||||
|
UserGateway.vue
|
||||||
|
UserList.vue
|
||||||
|
UserPermissionPage.vue
|
||||||
|
UserSync.vue
|
||||||
|
dummydata.vue
|
||||||
|
statementofaccount.vue
|
||||||
|
App.vue
|
||||||
|
-.editorconfig
|
||||||
|
.editorconfig
|
||||||
|
.env.development
|
||||||
|
.env.production
|
||||||
|
.gitignore
|
||||||
|
.npmrc
|
||||||
|
README.md
|
||||||
|
babel.config.js
|
||||||
|
index.html
|
||||||
|
jsconfig.json
|
||||||
|
package-lock.json
|
||||||
|
package.json
|
||||||
|
postcss.config.js
|
||||||
|
quasar.config.js
|
||||||
|
quasar.config.js.temporary.compiled.1772696562168.mjs
|
||||||
|
.gitignore
|
||||||
|
readme.md
|
||||||
|
bssapp
|
||||||
|
/
|
||||||
|
ui
|
||||||
|
/
|
||||||
|
src
|
||||||
|
/
|
||||||
|
pages
|
||||||
|
/
|
||||||
|
AccountAgingStatement.vue
|
||||||
|
|
||||||
|
M_Kececi
|
||||||
|
ce31aff645
|
||||||
|
Merge remote-tracking branch 'origin/master'
|
||||||
|
3 gün önce
|
||||||
|
1059 satır
|
||||||
|
35 KiB
|
||||||
|
Vue
|
||||||
|
<template>
|
||||||
|
<q-page v-if="canReadFinance" class="q-pa-md page-layout">
|
||||||
|
<div class="filter-sticky" :class="{ collapsed: filtersCollapsed }">
|
||||||
|
<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">
|
||||||
|
<q-input
|
||||||
|
v-model="store.filters.selectedDate"
|
||||||
|
label="Tarih (Bugün)"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
readonly
|
||||||
|
disable
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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 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">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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 class="table-area">
|
||||||
|
<div class="sticky-bar row justify-between items-center q-pa-sm bg-grey-1">
|
||||||
|
<div />
|
||||||
|
<div class="row items-center q-gutter-sm">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
color="secondary"
|
||||||
|
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>
|
||||||
|
|
||||||
|
<q-table
|
||||||
|
title="Cari Yaşlandırmalı Cari Bakiye Listesi"
|
||||||
|
:rows="store.summaryRows"
|
||||||
|
:columns="summaryColumns"
|
||||||
|
row-key="group_key"
|
||||||
|
:loading="store.loading"
|
||||||
|
flat
|
||||||
|
bordered
|
||||||
|
dense
|
||||||
|
wrap-cells
|
||||||
|
separator="cell"
|
||||||
|
hide-bottom
|
||||||
|
:rows-per-page-options="[0]"
|
||||||
|
:pagination="{ rowsPerPage: 0 }"
|
||||||
|
:table-style="{ tableLayout: 'fixed', width: '100%' }"
|
||||||
|
class="balance-table"
|
||||||
|
>
|
||||||
|
<template #header="props">
|
||||||
|
<q-tr :props="props" class="header-row">
|
||||||
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
|
{{ col.label }}
|
||||||
|
</q-th>
|
||||||
|
</q-tr>
|
||||||
|
<q-tr class="totals-row">
|
||||||
|
<q-th
|
||||||
|
v-for="col in props.cols"
|
||||||
|
:key="`tot-${col.name}`"
|
||||||
|
:class="col.align === 'right' ? 'text-right' : ''"
|
||||||
|
>
|
||||||
|
{{ totalCellValue(col.name) }}
|
||||||
|
</q-th>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body="props">
|
||||||
|
<q-tr :props="props" class="sub-header-row">
|
||||||
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
|
<q-btn
|
||||||
|
v-if="col.name === 'expand'"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
size="sm"
|
||||||
|
:icon="expanded[props.row.group_key] ? 'expand_less' : 'expand_more'"
|
||||||
|
@click="toggleGroup(props.row.group_key)"
|
||||||
|
/>
|
||||||
|
<span v-else-if="col.name === 'prbr_1_2'" class="text-right block prbr-cell">
|
||||||
|
{{ formatCurrencyMap(props.row.bakiye_1_2_map) }}
|
||||||
|
</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-tr>
|
||||||
|
|
||||||
|
<q-tr v-if="expanded[props.row.group_key]" class="detail-host-row">
|
||||||
|
<q-td colspan="100%">
|
||||||
|
<div class="detail-wrap">
|
||||||
|
<q-table
|
||||||
|
:rows="store.getDetailsByGroup(props.row.group_key)"
|
||||||
|
:columns="detailColumns"
|
||||||
|
row-key="cari_kodu"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
bordered
|
||||||
|
hide-bottom
|
||||||
|
:table-style="{ tableLayout: 'fixed', width: '100%' }"
|
||||||
|
class="detail-table"
|
||||||
|
>
|
||||||
|
<template #body-cell-prbr_1_2="scope">
|
||||||
|
<q-td :props="scope" class="text-right prbr-cell">
|
||||||
|
{{ formatRowPrBr(scope.row, '1_2') }}
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-prbr_1_3="scope">
|
||||||
|
<q-td :props="scope" class="text-right prbr-cell">
|
||||||
|
{{ formatRowPrBr(scope.row, '1_3') }}
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-vade_gun="scope">
|
||||||
|
<q-td :props="scope" class="text-center">
|
||||||
|
{{ formatDay(scope.row.vade_gun) }}
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
<template #body-cell-vade_belge_tarihi_gun="scope">
|
||||||
|
<q-td :props="scope" class="text-center">
|
||||||
|
{{ formatDay(scope.row.vade_belge_tarihi_gun) }}
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</div>
|
||||||
|
</q-td>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</q-page>
|
||||||
|
|
||||||
|
<q-page v-else class="q-pa-md flex flex-center">
|
||||||
|
<div class="text-negative text-subtitle1">
|
||||||
|
Bu modüle erişim yetkiniz yok.
|
||||||
|
</div>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import { useQuasar } from 'quasar'
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
import { useAccountAgingBalanceStore } from 'src/stores/accountAgingBalanceStore'
|
||||||
|
import { usePermission } from 'src/composables/usePermission'
|
||||||
|
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 canReadFinance = canRead('finance')
|
||||||
|
const canExportFinance = canExport('finance')
|
||||||
|
|
||||||
|
const islemTipiOptions = [
|
||||||
|
{ label: '1_2 Bakiye Pr.Br', value: 'prbr_1_2' },
|
||||||
|
{ label: '1_3 Bakiye Pr.Br', value: 'prbr_1_3' },
|
||||||
|
{ label: '1_2 USD Bakiye', value: 'usd_1_2' },
|
||||||
|
{ label: '1_2 TRY Bakiye', value: 'try_1_2' },
|
||||||
|
{ label: '1_3 USD Bakiye', value: 'usd_1_3' },
|
||||||
|
{ label: '1_3 TRY Bakiye', value: 'try_1_3' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const staticMoneyFields = ['usd_bakiye_1_2', 'tl_bakiye_1_2', 'usd_bakiye_1_3', 'tl_bakiye_1_3']
|
||||||
|
const dayFields = ['vade_gun', 'vade_belge_tarihi_gun']
|
||||||
|
|
||||||
|
function toNumericSortValue (value) {
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return Number.isFinite(value) ? value : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const s = String(value ?? '').trim()
|
||||||
|
if (!s) return 0
|
||||||
|
|
||||||
|
const hasComma = s.includes(',')
|
||||||
|
const hasDot = s.includes('.')
|
||||||
|
|
||||||
|
let normalized = s.replace(/\s+/g, '')
|
||||||
|
|
||||||
|
if (hasComma && hasDot) {
|
||||||
|
const lastComma = normalized.lastIndexOf(',')
|
||||||
|
const lastDot = normalized.lastIndexOf('.')
|
||||||
|
if (lastComma > lastDot) {
|
||||||
|
normalized = normalized.replace(/\./g, '').replace(',', '.')
|
||||||
|
} else {
|
||||||
|
normalized = normalized.replace(/,/g, '')
|
||||||
|
}
|
||||||
|
} else if (hasComma) {
|
||||||
|
normalized = normalized.replace(/\./g, '').replace(',', '.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const n = Number.parseFloat(normalized)
|
||||||
|
return Number.isFinite(n) ? n : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortTextTr (a, b) {
|
||||||
|
return String(a ?? '').localeCompare(String(b ?? ''), 'tr', { sensitivity: 'base' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const metricDefs = {
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
const summaryColumns = computed(() => ([
|
||||||
|
{ name: 'expand', label: '', field: 'expand', align: 'center', sortable: false },
|
||||||
|
{ name: 'ana_cari_kodu', label: 'Ana Cari Kodu', field: 'ana_cari_kodu', align: 'left', sortable: true, sort: sortTextTr },
|
||||||
|
{ name: 'ana_cari_adi', label: 'Ana Cari Detay', field: 'ana_cari_adi', align: 'left', sortable: true, sort: sortTextTr },
|
||||||
|
{ name: 'piyasa', label: 'Piyasa', field: 'piyasa', align: 'left', sortable: true, sort: sortTextTr },
|
||||||
|
{ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
allDetailsOpen.value =
|
||||||
|
store.summaryRows.length > 0 &&
|
||||||
|
store.summaryRows.every(r => expanded.value[r.group_key])
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAllDetails () {
|
||||||
|
allDetailsOpen.value = !allDetailsOpen.value
|
||||||
|
|
||||||
|
if (allDetailsOpen.value) {
|
||||||
|
const next = {}
|
||||||
|
for (const row of store.summaryRows) {
|
||||||
|
next[row.group_key] = true
|
||||||
|
}
|
||||||
|
expanded.value = next
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
expanded.value = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadAgingBalancePDF (detailed) {
|
||||||
|
if (!canExportFinance.value) {
|
||||||
|
$q.notify({ type: 'negative', message: 'PDF export yetkiniz yok', position: 'top-right' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!store.hasFetched) {
|
||||||
|
$q.notify({ type: 'warning', message: 'Önce Bakiyeleri Getir ile veri yükleyin.', position: 'top-right' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
cari_search: String(store.filters.cariSearch || '').trim(),
|
||||||
|
cari_ilk_grup: (store.filters.cariIlkGrup || []).join(','),
|
||||||
|
piyasa: (store.filters.piyasa || []).join(','),
|
||||||
|
temsilci: (store.filters.temsilci || []).join(','),
|
||||||
|
risk_durumu: (store.filters.riskDurumu || []).join(','),
|
||||||
|
islem_tipi: (store.filters.islemTipi || []).join(','),
|
||||||
|
ulke: (store.filters.ulke || []).join(','),
|
||||||
|
il: (store.filters.il || []).join(','),
|
||||||
|
ilce: (store.filters.ilce || []).join(','),
|
||||||
|
exclude_zero_12: store.filters.excludeZeroBalance12 ? '1' : '0',
|
||||||
|
exclude_zero_13: store.filters.excludeZeroBalance13 ? '1' : '0',
|
||||||
|
detailed: detailed ? '1' : '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await download('/finance/account-aging-statement/export-pdf', params)
|
||||||
|
const pdfUrl = window.URL.createObjectURL(new Blob([blob], { type: 'application/pdf' }))
|
||||||
|
window.open(pdfUrl, '_blank')
|
||||||
|
} catch (err) {
|
||||||
|
const detail = await extractApiErrorDetail(err?.original || err)
|
||||||
|
$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
message: detail || 'PDF oluşturulamadı',
|
||||||
|
position: 'top-right'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function 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>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.page-layout {
|
||||||
|
height: calc(100vh - 110px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-sticky {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 20;
|
||||||
|
background: #fff;
|
||||||
|
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 {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.12);
|
||||||
|
border-radius: 8px;
|
||||||
|
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 {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-table {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-table :deep(.q-table__container) {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-table :deep(.q-table__top) {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 6;
|
||||||
|
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);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-table :deep(.totals-row th) {
|
||||||
|
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);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-table :deep(.sub-header-row td:first-child) {
|
||||||
|
border-left: 3px solid var(--q-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-table :deep(.detail-host-row td) {
|
||||||
|
background: #f7f7f7;
|
||||||
|
border-bottom: 10px solid #fff;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-wrap {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.14);
|
||||||
|
border-left: 4px solid var(--q-secondary);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #fff;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-table :deep(.header-row th) {
|
||||||
|
white-space: pre-line;
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prbr-cell {
|
||||||
|
white-space: pre-line;
|
||||||
|
word-break: break-word;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-table :deep(th),
|
||||||
|
.balance-table :deep(td),
|
||||||
|
.detail-table :deep(th),
|
||||||
|
.detail-table :deep(td) {
|
||||||
|
white-space: normal !important;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.2;
|
||||||
|
padding: 4px 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.balance-table :deep(.q-table__table),
|
||||||
|
.detail-table :deep(.q-table__table) {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-table :deep(.q-table__middle) {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
@@ -84,6 +84,14 @@
|
|||||||
dense
|
dense
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-12 col-sm-6 col-md-2">
|
||||||
|
<q-toggle
|
||||||
|
v-model="excludeOpening"
|
||||||
|
label="Devir bakiyesiz listele"
|
||||||
|
color="primary"
|
||||||
|
dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<q-btn color="primary" icon="filter_alt" label="Filtrele" @click="onFilterClick" />
|
<q-btn color="primary" icon="filter_alt" label="Filtrele" @click="onFilterClick" />
|
||||||
</div>
|
</div>
|
||||||
@@ -358,6 +366,7 @@ const monetaryTypeOptions = [
|
|||||||
{ label: '1-3 r hesap', value: ['1', '3'] }
|
{ label: '1-3 r hesap', value: ['1', '3'] }
|
||||||
]
|
]
|
||||||
const selectedMonType = ref(monetaryTypeOptions[0].value)
|
const selectedMonType = ref(monetaryTypeOptions[0].value)
|
||||||
|
const excludeOpening = ref(false)
|
||||||
|
|
||||||
/* Expand kontrolü */
|
/* Expand kontrolü */
|
||||||
const expandedRows = ref({})
|
const expandedRows = ref({})
|
||||||
@@ -404,7 +413,8 @@ async function onFilterClick() {
|
|||||||
enddate: dateTo.value,
|
enddate: dateTo.value,
|
||||||
accountcode: selectedCari.value,
|
accountcode: selectedCari.value,
|
||||||
langcode: 'TR',
|
langcode: 'TR',
|
||||||
parislemler: selectedMonType.value
|
parislemler: selectedMonType.value,
|
||||||
|
excludeopening: excludeOpening.value
|
||||||
})
|
})
|
||||||
|
|
||||||
await detailStore.loadDetails({
|
await detailStore.loadDetails({
|
||||||
@@ -454,6 +464,7 @@ function resetFilters() {
|
|||||||
dateFrom.value = ''
|
dateFrom.value = ''
|
||||||
dateTo.value = ''
|
dateTo.value = ''
|
||||||
selectedMonType.value = monetaryTypeOptions[0].value
|
selectedMonType.value = monetaryTypeOptions[0].value
|
||||||
|
excludeOpening.value = false
|
||||||
statementheaderStore.headers = []
|
statementheaderStore.headers = []
|
||||||
detailStore.reset()
|
detailStore.reset()
|
||||||
}
|
}
|
||||||
@@ -715,4 +726,3 @@ async function CurrheadDownload() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
redirect: { name: 'account-aging-statement' },
|
component: () => import('pages/AgingCustomerBalancelist.go.vue'),
|
||||||
meta: { permission: 'finance:view' }
|
meta: { permission: 'finance:view' }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ export const useAccountAgingBalanceStore = defineStore('accountAgingBalance', {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
this.filters.selectedDate = new Date().toISOString().slice(0, 10)
|
this.filters.selectedDate = new Date().toISOString().slice(0, 10)
|
||||||
const { data } = await api.get('/finance/account-aging-statement', {
|
const { data } = await api.get('/finance/aged-customer-balance-list', {
|
||||||
params: {
|
params: {
|
||||||
cari_search: String(this.filters.cariSearch || '').trim(),
|
cari_search: String(this.filters.cariSearch || '').trim(),
|
||||||
cari_ilk_grup: (this.filters.cariIlkGrup || []).join(','),
|
cari_ilk_grup: (this.filters.cariIlkGrup || []).join(','),
|
||||||
|
|||||||
@@ -50,32 +50,25 @@ export const useStatementAgingStore = defineStore('statementAging', {
|
|||||||
const currencyKey = `${cari8}|${curr}`
|
const currencyKey = `${cari8}|${curr}`
|
||||||
const tutar = Number(row?.eslesen_tutar) || 0
|
const tutar = Number(row?.eslesen_tutar) || 0
|
||||||
const usd = Number(row?.usd_tutar) || 0
|
const usd = Number(row?.usd_tutar) || 0
|
||||||
const trY = Number(row?.try_tutar) || 0
|
const absUsd = Math.abs(usd)
|
||||||
const absTry = Math.abs(trY)
|
|
||||||
const gun = Number(row?.gun_sayisi) || 0
|
const gun = Number(row?.gun_sayisi) || 0
|
||||||
const gunDoc = Number(row?.gun_sayisi_docdate) || 0
|
const gunDoc = Number(row?.gun_sayisi_docdate) || 0
|
||||||
const aciklama = String(row?.aciklama || '').toUpperCase()
|
const aciklama = String(row?.aciklama || '').toUpperCase()
|
||||||
const isAcik = aciklama === 'ACIKKALEM'
|
|
||||||
|
|
||||||
if (!masterMap[masterKey]) {
|
if (!masterMap[masterKey]) {
|
||||||
masterMap[masterKey] = {
|
masterMap[masterKey] = {
|
||||||
group_key: masterKey,
|
group_key: masterKey,
|
||||||
cari8: masterKey,
|
cari8: masterKey,
|
||||||
cari_detay: String(row?.cari_detay || '').trim(),
|
cari_detay: String(row?.cari_detay || '').trim(),
|
||||||
acik_kalem_tutari_usd: 0,
|
satir_sayisi: 0,
|
||||||
acik_kalem_tutari_try: 0,
|
toplam_usd: 0,
|
||||||
acik_kalem_ort_vade_gun: 0,
|
normal_usd: 0,
|
||||||
acik_kalem_ort_belge_gun: 0,
|
acik_kalem_usd: 0,
|
||||||
normal_usd_tutar: 0,
|
weighted_gun_sum: 0,
|
||||||
normal_try_tutar: 0,
|
weighted_gun_doc_sum: 0,
|
||||||
ortalama_vade_gun: 0,
|
weighted_base: 0,
|
||||||
ortalama_belge_gun: 0,
|
ortalama_gun: 0,
|
||||||
weighted_all_base: 0,
|
ortalama_gun_docdate: 0
|
||||||
weighted_all_gun_sum: 0,
|
|
||||||
weighted_all_doc_sum: 0,
|
|
||||||
weighted_open_base: 0,
|
|
||||||
weighted_open_gun_sum: 0,
|
|
||||||
weighted_open_doc_sum: 0,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,46 +78,48 @@ export const useStatementAgingStore = defineStore('statementAging', {
|
|||||||
master_key: masterKey,
|
master_key: masterKey,
|
||||||
cari8,
|
cari8,
|
||||||
cari_detay: String(row?.cari_detay || '').trim(),
|
cari_detay: String(row?.cari_detay || '').trim(),
|
||||||
doviz_cinsi: curr,
|
doc_currency_code: curr,
|
||||||
acik_kalem_tutari: 0,
|
satir_sayisi: 0,
|
||||||
acik_kalem_usd: 0,
|
toplam_tutar: 0,
|
||||||
acik_kalem_try: 0,
|
toplam_usd: 0,
|
||||||
ort_gun: 0,
|
normal_tutar: 0,
|
||||||
ort_belge_gun: 0,
|
acik_kalem_tutar: 0,
|
||||||
weighted_open_base: 0,
|
weighted_gun_sum: 0,
|
||||||
weighted_open_gun_sum: 0,
|
weighted_gun_doc_sum: 0,
|
||||||
weighted_open_doc_sum: 0
|
weighted_base: 0,
|
||||||
|
ortalama_gun: 0,
|
||||||
|
ortalama_gun_docdate: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const m = masterMap[masterKey]
|
const m = masterMap[masterKey]
|
||||||
const c = currencyMap[currencyKey]
|
const c = currencyMap[currencyKey]
|
||||||
|
|
||||||
if (isAcik) {
|
m.satir_sayisi += 1
|
||||||
m.acik_kalem_tutari_usd += usd
|
m.toplam_usd += usd
|
||||||
m.acik_kalem_tutari_try += trY
|
if (aciklama === 'ACIKKALEM') {
|
||||||
c.acik_kalem_tutari += tutar
|
m.acik_kalem_usd += usd
|
||||||
c.acik_kalem_usd += usd
|
|
||||||
c.acik_kalem_try += trY
|
|
||||||
} else {
|
} else {
|
||||||
m.normal_usd_tutar += usd
|
m.normal_usd += usd
|
||||||
m.normal_try_tutar += trY
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (absTry > 0) {
|
c.satir_sayisi += 1
|
||||||
m.weighted_all_base += absTry
|
c.toplam_tutar += tutar
|
||||||
m.weighted_all_gun_sum += absTry * gun
|
c.toplam_usd += usd
|
||||||
m.weighted_all_doc_sum += absTry * gunDoc
|
if (aciklama === 'ACIKKALEM') {
|
||||||
|
c.acik_kalem_tutar += tutar
|
||||||
if (isAcik) {
|
} else {
|
||||||
m.weighted_open_base += absTry
|
c.normal_tutar += tutar
|
||||||
m.weighted_open_gun_sum += absTry * gun
|
|
||||||
m.weighted_open_doc_sum += absTry * gunDoc
|
|
||||||
|
|
||||||
c.weighted_open_base += absTry
|
|
||||||
c.weighted_open_gun_sum += absTry * gun
|
|
||||||
c.weighted_open_doc_sum += absTry * gunDoc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (absUsd > 0) {
|
||||||
|
m.weighted_base += absUsd
|
||||||
|
m.weighted_gun_sum += absUsd * gun
|
||||||
|
m.weighted_gun_doc_sum += absUsd * gunDoc
|
||||||
|
|
||||||
|
c.weighted_base += absUsd
|
||||||
|
c.weighted_gun_sum += absUsd * gun
|
||||||
|
c.weighted_gun_doc_sum += absUsd * gunDoc
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!detailMap[currencyKey]) detailMap[currencyKey] = []
|
if (!detailMap[currencyKey]) detailMap[currencyKey] = []
|
||||||
@@ -134,10 +129,8 @@ export const useStatementAgingStore = defineStore('statementAging', {
|
|||||||
this.masterRows = Object.values(masterMap)
|
this.masterRows = Object.values(masterMap)
|
||||||
.map((m) => ({
|
.map((m) => ({
|
||||||
...m,
|
...m,
|
||||||
acik_kalem_ort_vade_gun: m.weighted_open_base > 0 ? ceilDay(m.weighted_open_gun_sum / m.weighted_open_base) : 0,
|
ortalama_gun: m.weighted_base > 0 ? (m.weighted_gun_sum / m.weighted_base) : 0,
|
||||||
acik_kalem_ort_belge_gun: m.weighted_open_base > 0 ? ceilDay(m.weighted_open_doc_sum / m.weighted_open_base) : 0,
|
ortalama_gun_docdate: m.weighted_base > 0 ? (m.weighted_gun_doc_sum / m.weighted_base) : 0
|
||||||
ortalama_vade_gun: m.weighted_all_base > 0 ? ceilDay(m.weighted_all_gun_sum / m.weighted_all_base) : 0,
|
|
||||||
ortalama_belge_gun: m.weighted_all_base > 0 ? ceilDay(m.weighted_all_doc_sum / m.weighted_all_base) : 0
|
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => String(a.cari8).localeCompare(String(b.cari8), 'tr', { sensitivity: 'base' }))
|
.sort((a, b) => String(a.cari8).localeCompare(String(b.cari8), 'tr', { sensitivity: 'base' }))
|
||||||
|
|
||||||
@@ -145,32 +138,18 @@ export const useStatementAgingStore = defineStore('statementAging', {
|
|||||||
for (const c of Object.values(currencyMap)) {
|
for (const c of Object.values(currencyMap)) {
|
||||||
const row = {
|
const row = {
|
||||||
...c,
|
...c,
|
||||||
ort_gun: c.weighted_open_base > 0 ? ceilDay(c.weighted_open_gun_sum / c.weighted_open_base) : 0,
|
ortalama_gun: c.weighted_base > 0 ? (c.weighted_gun_sum / c.weighted_base) : 0,
|
||||||
ort_belge_gun: c.weighted_open_base > 0 ? ceilDay(c.weighted_open_doc_sum / c.weighted_open_base) : 0
|
ortalama_gun_docdate: c.weighted_base > 0 ? (c.weighted_gun_doc_sum / c.weighted_base) : 0
|
||||||
}
|
}
|
||||||
if (!currencyByMaster[row.master_key]) currencyByMaster[row.master_key] = []
|
if (!currencyByMaster[row.master_key]) currencyByMaster[row.master_key] = []
|
||||||
currencyByMaster[row.master_key].push(row)
|
currencyByMaster[row.master_key].push(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key of Object.keys(currencyByMaster)) {
|
for (const key of Object.keys(currencyByMaster)) {
|
||||||
currencyByMaster[key].sort((a, b) => String(a.doviz_cinsi).localeCompare(String(b.doviz_cinsi), 'en', { sensitivity: 'base' }))
|
currencyByMaster[key].sort((a, b) => String(a.doc_currency_code).localeCompare(String(b.doc_currency_code), 'en', { sensitivity: 'base' }))
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currencyRowsByMaster = currencyByMaster
|
this.currencyRowsByMaster = currencyByMaster
|
||||||
for (const key of Object.keys(detailMap)) {
|
|
||||||
detailMap[key].sort((a, b) => {
|
|
||||||
const aEmpty = !a?.odeme_tarihi
|
|
||||||
const bEmpty = !b?.odeme_tarihi
|
|
||||||
if (aEmpty && !bEmpty) return -1
|
|
||||||
if (!aEmpty && bEmpty) return 1
|
|
||||||
if (aEmpty && bEmpty) return 0
|
|
||||||
const aTs = Date.parse(a.odeme_tarihi)
|
|
||||||
const bTs = Date.parse(b.odeme_tarihi)
|
|
||||||
const aNum = Number.isFinite(aTs) ? aTs : -Infinity
|
|
||||||
const bNum = Number.isFinite(bTs) ? bTs : -Infinity
|
|
||||||
return bNum - aNum
|
|
||||||
})
|
|
||||||
}
|
|
||||||
this.detailByCurrency = detailMap
|
this.detailByCurrency = detailMap
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -206,17 +185,9 @@ function normalizeRowKeys(row) {
|
|||||||
odeme_doc_date: row.OdemeDocDate ?? row.odeme_doc_date ?? null,
|
odeme_doc_date: row.OdemeDocDate ?? row.odeme_doc_date ?? null,
|
||||||
eslesen_tutar: Number(row.EslesenTutar ?? row.eslesen_tutar ?? 0),
|
eslesen_tutar: Number(row.EslesenTutar ?? row.eslesen_tutar ?? 0),
|
||||||
usd_tutar: Number(row.UsdTutar ?? row.usd_tutar ?? 0),
|
usd_tutar: Number(row.UsdTutar ?? row.usd_tutar ?? 0),
|
||||||
try_tutar: Number(row.TryTutar ?? row.try_tutar ?? 0),
|
|
||||||
gun_sayisi: Number(row.GunSayisi ?? row.gun_sayisi ?? 0),
|
gun_sayisi: Number(row.GunSayisi ?? row.gun_sayisi ?? 0),
|
||||||
gun_sayisi_docdate: Number(row.GunSayisi_DocDate ?? row.gun_sayisi_docdate ?? 0),
|
gun_sayisi_docdate: Number(row.GunSayisi_DocDate ?? row.gun_sayisi_docdate ?? 0),
|
||||||
gun_kur: Number(row.GunKur ?? row.gun_kur ?? 0),
|
|
||||||
aciklama: row.Aciklama ?? row.aciklama ?? null,
|
aciklama: row.Aciklama ?? row.aciklama ?? null,
|
||||||
doc_currency_code: row.DocCurrencyCode ?? row.doc_currency_code ?? null
|
doc_currency_code: row.DocCurrencyCode ?? row.doc_currency_code ?? null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ceilDay(value) {
|
|
||||||
const n = Number(value)
|
|
||||||
if (!Number.isFinite(n)) return 0
|
|
||||||
return Math.ceil(n)
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user