Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -22,7 +22,7 @@ UI_DIR=/opt/bssapp/ui/dist
|
||||
# DATABASES
|
||||
# ===============================
|
||||
POSTGRES_CONN=host=46.224.33.150 port=5432 user=postgres password=tayitkan dbname=baggib2b sslmode=disable
|
||||
MSSQL_CONN=sqlserver://sa:Gil_0150@100.127.186.137:1433?database=BAGGI_V3&encrypt=disable
|
||||
MSSQL_CONN=sqlserver://sa:Gil_0150@10.0.0.9:1433?database=BAGGI_V3&encrypt=disable
|
||||
|
||||
# ===============================
|
||||
# PDF
|
||||
|
||||
@@ -3,7 +3,6 @@ package db
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
@@ -13,23 +12,24 @@ import (
|
||||
var MssqlDB *sql.DB
|
||||
|
||||
// ConnectMSSQL MSSQL baglantisini ortam degiskeninden baslatir.
|
||||
func ConnectMSSQL() {
|
||||
func ConnectMSSQL() error {
|
||||
connString := strings.TrimSpace(os.Getenv("MSSQL_CONN"))
|
||||
if connString == "" {
|
||||
log.Fatal("MSSQL_CONN tanımlı değil")
|
||||
return fmt.Errorf("MSSQL_CONN tanımlı değil")
|
||||
}
|
||||
|
||||
var err error
|
||||
MssqlDB, err = sql.Open("sqlserver", connString)
|
||||
if err != nil {
|
||||
log.Fatal("MSSQL bağlantı hatası:", err)
|
||||
return fmt.Errorf("MSSQL bağlantı hatası: %w", err)
|
||||
}
|
||||
|
||||
if err = MssqlDB.Ping(); err != nil {
|
||||
log.Fatal("MSSQL erişilemiyor:", err)
|
||||
return fmt.Errorf("MSSQL erişilemiyor: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("MSSQL bağlantısı başarılı")
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetDB() *sql.DB {
|
||||
|
||||
17
svc/main.go
17
svc/main.go
@@ -425,6 +425,12 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
|
||||
wrapV3(routes.ExportStatementHeaderReportPDFHandler(mssql)),
|
||||
)
|
||||
|
||||
bindV3(r, pgDB,
|
||||
"/api/finance/customer-balances", "GET",
|
||||
"finance", "view",
|
||||
wrapV3(http.HandlerFunc(routes.GetCustomerBalanceListHandler)),
|
||||
)
|
||||
|
||||
// ============================================================
|
||||
// REPORT (STATEMENTS)
|
||||
// ============================================================
|
||||
@@ -576,6 +582,7 @@ func main() {
|
||||
// -------------------------------------------------------
|
||||
// 🔑 ENV
|
||||
// -------------------------------------------------------
|
||||
// Önce .env + mail.env yükle. MSSQL başarısızsa .env.local dene.
|
||||
if err := godotenv.Load(".env", "mail.env"); err != nil {
|
||||
log.Println("⚠️ .env / mail.env bulunamadı")
|
||||
}
|
||||
@@ -589,7 +596,15 @@ func main() {
|
||||
// -------------------------------------------------------
|
||||
// 🔗 DATABASE
|
||||
// -------------------------------------------------------
|
||||
db.ConnectMSSQL()
|
||||
if err := db.ConnectMSSQL(); err != nil {
|
||||
log.Println("⚠️ MSSQL ilk deneme başarısız:", err)
|
||||
if err2 := godotenv.Overload(".env.local"); err2 != nil {
|
||||
log.Println("⚠️ .env.local bulunamadı")
|
||||
}
|
||||
if err3 := db.ConnectMSSQL(); err3 != nil {
|
||||
log.Fatal(err3)
|
||||
}
|
||||
}
|
||||
|
||||
pgDB, err := db.ConnectPostgres()
|
||||
if err != nil {
|
||||
|
||||
36
svc/models/customer_balance_list.go
Normal file
36
svc/models/customer_balance_list.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package models
|
||||
|
||||
type CustomerBalanceListParams struct {
|
||||
SelectedDate string
|
||||
CariSearch string
|
||||
CariIlkGrup string
|
||||
Piyasa string
|
||||
Temsilci string
|
||||
RiskDurumu string
|
||||
IslemTipi string
|
||||
Ulke string
|
||||
}
|
||||
|
||||
type CustomerBalanceListRow struct {
|
||||
CariIlkGrup string `json:"cari_ilk_grup"`
|
||||
Piyasa string `json:"piyasa"`
|
||||
Temsilci string `json:"temsilci"`
|
||||
Sirket string `json:"sirket"`
|
||||
AnaCariKodu string `json:"ana_cari_kodu"`
|
||||
AnaCariAdi string `json:"ana_cari_adi"`
|
||||
CariKodu string `json:"cari_kodu"`
|
||||
CariDetay string `json:"cari_detay"`
|
||||
Ozellik03 string `json:"ozellik03"`
|
||||
Ozellik05 string `json:"ozellik05"`
|
||||
Ozellik06 string `json:"ozellik06"`
|
||||
Ozellik07 string `json:"ozellik07"`
|
||||
CariDoviz string `json:"cari_doviz"`
|
||||
Bakiye12 float64 `json:"bakiye_1_2"`
|
||||
TLBakiye12 float64 `json:"tl_bakiye_1_2"`
|
||||
USDBakiye12 float64 `json:"usd_bakiye_1_2"`
|
||||
Bakiye13 float64 `json:"bakiye_1_3"`
|
||||
TLBakiye13 float64 `json:"tl_bakiye_1_3"`
|
||||
USDBakiye13 float64 `json:"usd_bakiye_1_3"`
|
||||
HesapAlinmayanGun NullInt32 `json:"hesap_alinmayan_gun"`
|
||||
KalanFaturaOrtalamaVadeTarihi NullString `json:"kalan_fatura_ortalama_vade_tarihi"`
|
||||
}
|
||||
200
svc/queries/customer_balance_list.go
Normal file
200
svc/queries/customer_balance_list.go
Normal file
@@ -0,0 +1,200 @@
|
||||
package queries
|
||||
|
||||
import (
|
||||
"bssapp-backend/db"
|
||||
"bssapp-backend/internal/authz"
|
||||
"bssapp-backend/models"
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetCustomerBalanceList(ctx context.Context, params models.CustomerBalanceListParams) ([]models.CustomerBalanceListRow, error) {
|
||||
// AuthZ bazli piyasa filtresi
|
||||
piyasaFilter := authz.BuildMSSQLPiyasaFilter(ctx, "D.Ozellik01")
|
||||
if strings.TrimSpace(piyasaFilter) == "" {
|
||||
piyasaFilter = "1=1"
|
||||
}
|
||||
|
||||
// Dinamik WHERE insa et
|
||||
where := make([]string, 0, 16)
|
||||
where = append(where, "D.Islem_Tarihi < DATEADD(DAY, 1, @SecilenTarih)")
|
||||
where = append(where, piyasaFilter)
|
||||
|
||||
if params.CariIlkGrup != "" {
|
||||
where = append(where, "D.Ozellik08 = @CariIlkGrup")
|
||||
}
|
||||
if params.Piyasa != "" {
|
||||
where = append(where, "D.Ozellik01 = @Piyasa")
|
||||
}
|
||||
if params.Temsilci != "" {
|
||||
where = append(where, "COALESCE(NULLIF(D.Ozellik02, ''), D.Ozellik09) = @Temsilci")
|
||||
}
|
||||
if params.RiskDurumu != "" {
|
||||
where = append(where, "D.Ozellik03 = @RiskDurumu")
|
||||
}
|
||||
if params.IslemTipi != "" {
|
||||
where = append(where, "D.PislemTipi = @IslemTipi")
|
||||
}
|
||||
if params.Ulke != "" {
|
||||
where = append(where, "D.Ozellik05 = @Ulke")
|
||||
}
|
||||
whereSQL := strings.Join(where, "\n AND ")
|
||||
cariSearchLike := "%" + strings.TrimSpace(params.CariSearch) + "%"
|
||||
outerWhere := "1=1"
|
||||
if strings.TrimSpace(params.CariSearch) != "" {
|
||||
outerWhere = `(LEFT(B.CariKodu, 8) COLLATE Turkish_100_CI_AI LIKE @CariSearchLike
|
||||
OR B.CariKodu COLLATE Turkish_100_CI_AI LIKE @CariSearchLike
|
||||
OR B.CariDetay COLLATE Turkish_100_CI_AI LIKE @CariSearchLike
|
||||
OR AC.ANA_CARI_ADI COLLATE Turkish_100_CI_AI LIKE @CariSearchLike)`
|
||||
}
|
||||
|
||||
const queryTemplate = `
|
||||
;WITH CTE_ANA_CARI AS (
|
||||
SELECT
|
||||
ANA_CARI_KODU = LEFT(CariKodu, 8),
|
||||
CariDetay,
|
||||
rn = ROW_NUMBER() OVER (
|
||||
PARTITION BY LEFT(CariKodu, 8)
|
||||
ORDER BY CariKodu
|
||||
)
|
||||
FROM dbo.MK_CARI_ILETISIM WITH (NOLOCK)
|
||||
),
|
||||
ANA_CARI AS (
|
||||
SELECT
|
||||
ANA_CARI_KODU,
|
||||
CariDetay AS ANA_CARI_ADI
|
||||
FROM CTE_ANA_CARI
|
||||
WHERE rn = 1
|
||||
),
|
||||
BASE AS (
|
||||
SELECT
|
||||
D.SirketKodu,
|
||||
D.SirketDetay,
|
||||
D.CariKodu,
|
||||
D.CariDetay,
|
||||
D.CariDoviz,
|
||||
D.Ozellik01,
|
||||
D.Ozellik02,
|
||||
D.Ozellik03,
|
||||
D.Ozellik05,
|
||||
D.Ozellik06,
|
||||
D.Ozellik07,
|
||||
D.Ozellik08,
|
||||
D.Ozellik09,
|
||||
D.PislemTipi,
|
||||
D.Bakiye,
|
||||
D.KurBakiye,
|
||||
D.Son_Guncel_Kur
|
||||
FROM dbo.DENEME02DENEME AS D WITH (NOLOCK)
|
||||
WHERE %s
|
||||
)
|
||||
SELECT
|
||||
B.Ozellik08 AS CARI_ILK_GRUP,
|
||||
B.Ozellik01 AS PIYASA,
|
||||
COALESCE(NULLIF(B.Ozellik02, ''), B.Ozellik09) AS Temsilci,
|
||||
LEFT(B.SirketDetay, 10) AS Sirket,
|
||||
LEFT(B.CariKodu, 8) AS ANA_CARI_KODU,
|
||||
AC.ANA_CARI_ADI,
|
||||
B.CariKodu,
|
||||
B.CariDetay,
|
||||
B.Ozellik03,
|
||||
B.Ozellik05,
|
||||
B.Ozellik06,
|
||||
B.Ozellik07,
|
||||
B.CariDoviz,
|
||||
ISNULL(SUM(CASE WHEN B.PislemTipi = '1_2' THEN ISNULL(B.Bakiye, 0) ELSE 0 END), 0) AS Bakiye_1_2,
|
||||
ISNULL(SUM(CASE WHEN B.PislemTipi = '1_2' THEN ISNULL(B.KurBakiye, 0) ELSE 0 END), 0) AS TL_Bakiye_1_2,
|
||||
ISNULL(
|
||||
SUM(CASE WHEN B.PislemTipi = '1_2' THEN ISNULL(B.KurBakiye, 0) ELSE 0 END) / NULLIF(MIN(B.Son_Guncel_Kur), 0),
|
||||
0
|
||||
) AS USD_Bakiye_1_2,
|
||||
ISNULL(SUM(CASE WHEN B.PislemTipi = '1_3' THEN ISNULL(B.Bakiye, 0) ELSE 0 END), 0) AS Bakiye_1_3,
|
||||
ISNULL(SUM(CASE WHEN B.PislemTipi = '1_3' THEN ISNULL(B.KurBakiye, 0) ELSE 0 END), 0) AS TL_Bakiye_1_3,
|
||||
ISNULL(
|
||||
SUM(CASE WHEN B.PislemTipi = '1_3' THEN ISNULL(B.KurBakiye, 0) ELSE 0 END) / NULLIF(MIN(B.Son_Guncel_Kur), 0),
|
||||
0
|
||||
) AS USD_Bakiye_1_3,
|
||||
CAST(NULL AS int) AS Hesap_Alinmayan_Gun,
|
||||
CAST(NULL AS varchar(32)) AS Kalan_Fatura_Ortalama_Vade_Tarihi
|
||||
FROM BASE AS B
|
||||
LEFT JOIN ANA_CARI AS AC
|
||||
ON AC.ANA_CARI_KODU = LEFT(B.CariKodu, 8)
|
||||
WHERE %s
|
||||
GROUP BY
|
||||
B.Ozellik08,
|
||||
B.Ozellik01,
|
||||
COALESCE(NULLIF(B.Ozellik02, ''), B.Ozellik09),
|
||||
LEFT(B.SirketDetay, 10),
|
||||
LEFT(B.CariKodu, 8),
|
||||
AC.ANA_CARI_ADI,
|
||||
B.CariKodu,
|
||||
B.CariDetay,
|
||||
B.Ozellik03,
|
||||
B.Ozellik05,
|
||||
B.Ozellik06,
|
||||
B.Ozellik07,
|
||||
B.CariDoviz
|
||||
ORDER BY
|
||||
LEFT(B.SirketDetay, 10),
|
||||
B.CariKodu
|
||||
OPTION (RECOMPILE);
|
||||
`
|
||||
query := fmt.Sprintf(queryTemplate, whereSQL, outerWhere)
|
||||
|
||||
rows, err := db.MssqlDB.QueryContext(
|
||||
ctx,
|
||||
query,
|
||||
sql.Named("SecilenTarih", params.SelectedDate),
|
||||
sql.Named("CariIlkGrup", params.CariIlkGrup),
|
||||
sql.Named("Piyasa", params.Piyasa),
|
||||
sql.Named("Temsilci", params.Temsilci),
|
||||
sql.Named("RiskDurumu", params.RiskDurumu),
|
||||
sql.Named("IslemTipi", params.IslemTipi),
|
||||
sql.Named("Ulke", params.Ulke),
|
||||
sql.Named("CariSearchLike", cariSearchLike),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("MSSQL query error: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
out := make([]models.CustomerBalanceListRow, 0, 512)
|
||||
|
||||
for rows.Next() {
|
||||
var r models.CustomerBalanceListRow
|
||||
if err := rows.Scan(
|
||||
&r.CariIlkGrup,
|
||||
&r.Piyasa,
|
||||
&r.Temsilci,
|
||||
&r.Sirket,
|
||||
&r.AnaCariKodu,
|
||||
&r.AnaCariAdi,
|
||||
&r.CariKodu,
|
||||
&r.CariDetay,
|
||||
&r.Ozellik03,
|
||||
&r.Ozellik05,
|
||||
&r.Ozellik06,
|
||||
&r.Ozellik07,
|
||||
&r.CariDoviz,
|
||||
&r.Bakiye12,
|
||||
&r.TLBakiye12,
|
||||
&r.USDBakiye12,
|
||||
&r.Bakiye13,
|
||||
&r.TLBakiye13,
|
||||
&r.USDBakiye13,
|
||||
&r.HesapAlinmayanGun,
|
||||
&r.KalanFaturaOrtalamaVadeTarihi,
|
||||
); err != nil {
|
||||
return nil, fmt.Errorf("row scan error: %w", err)
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("rows iteration error: %w", err)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
49
svc/routes/customer_balance_list.go
Normal file
49
svc/routes/customer_balance_list.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"bssapp-backend/auth"
|
||||
"bssapp-backend/models"
|
||||
"bssapp-backend/queries"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GET /api/finance/customer-balances
|
||||
func GetCustomerBalanceListHandler(w http.ResponseWriter, r *http.Request) {
|
||||
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||||
if !ok || claims == nil {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
selectedDate := strings.TrimSpace(r.URL.Query().Get("selected_date"))
|
||||
if selectedDate == "" {
|
||||
selectedDate = time.Now().Format("2006-01-02")
|
||||
}
|
||||
|
||||
params := models.CustomerBalanceListParams{
|
||||
SelectedDate: selectedDate,
|
||||
CariSearch: strings.TrimSpace(r.URL.Query().Get("cari_search")),
|
||||
CariIlkGrup: strings.TrimSpace(r.URL.Query().Get("cari_ilk_grup")),
|
||||
Piyasa: strings.TrimSpace(r.URL.Query().Get("piyasa")),
|
||||
Temsilci: strings.TrimSpace(r.URL.Query().Get("temsilci")),
|
||||
RiskDurumu: strings.TrimSpace(r.URL.Query().Get("risk_durumu")),
|
||||
IslemTipi: strings.TrimSpace(r.URL.Query().Get("islem_tipi")),
|
||||
Ulke: strings.TrimSpace(r.URL.Query().Get("ulke")),
|
||||
}
|
||||
|
||||
rows, err := queries.GetCustomerBalanceList(r.Context(), params)
|
||||
if err != nil {
|
||||
log.Println("GetCustomerBalanceList error:", err)
|
||||
http.Error(w, "db error: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
if err := json.NewEncoder(w).Encode(rows); err != nil {
|
||||
log.Println("GetCustomerBalanceList json encode error:", err)
|
||||
}
|
||||
}
|
||||
137
svc/run.log
Normal file
137
svc/run.log
Normal file
@@ -0,0 +1,137 @@
|
||||
2026/02/23 12:29:31 🔥🔥🔥 BSSAPP BACKEND STARTED — LOGIN ROUTE SHOULD EXIST 🔥🔥🔥
|
||||
2026/02/23 12:29:31 🔐 JWT_SECRET yüklendi
|
||||
MSSQL bağlantısı başarılı
|
||||
2026/02/23 12:29:31 PostgreSQL bağlantısı başarılı
|
||||
2026/02/23 12:29:31 ✅ Admin dept permissions seeded
|
||||
2026/02/23 12:29:31 🟢 auditlog Init called, buffer: 1000
|
||||
2026/02/23 12:29:31 🕵️ AuditLog sistemi başlatıldı (buffer=1000)
|
||||
2026/02/23 12:29:31 ✉️ Graph Mailer hazır (App-only token) | from=baggiss@baggi.com.tr
|
||||
2026/02/23 12:29:31 ✉️ Graph Mailer hazır
|
||||
2026/02/23 12:29:31 🟢 auditlog worker STARTED
|
||||
📋 [DEBUG] İlk 10 kullanıcı:
|
||||
- 1 : ctengiz
|
||||
- 2 : ali.kale
|
||||
- 5 : mehmet.keçeci
|
||||
- 6 : mert.keçeci
|
||||
- 7 : samet.keçeci
|
||||
- 9 : orhan.caliskan
|
||||
- 10 : nilgun.sara
|
||||
- 14 : rustem.kurbanov
|
||||
- 15 : caner.akyol
|
||||
- 16 : kemal.matyakupov
|
||||
2026/02/23 12:29:32 ✅ Route+Perm registered → POST /api/auth/login [auth:login]
|
||||
2026/02/23 12:29:32 ✅ Route+Perm registered → POST /api/auth/refresh [auth:refresh]
|
||||
2026/02/23 12:29:32 ✅ Route+Perm registered → POST /api/password/forgot [auth:update]
|
||||
2026/02/23 12:29:33 ✅ Route+Perm registered → GET /api/password/reset/validate/{token} [auth:view]
|
||||
2026/02/23 12:29:33 ✅ Route+Perm registered → POST /api/password/reset [auth:update]
|
||||
2026/02/23 12:29:34 ✅ Route+Perm registered → POST /api/password/change [auth:update]
|
||||
2026/02/23 12:29:34 ✅ Route+Perm registered → GET /api/activity-logs [system:read]
|
||||
2026/02/23 12:29:35 ✅ Route+Perm registered → POST /api/test-mail [system:update]
|
||||
2026/02/23 12:29:35 ✅ Route+Perm registered → GET /api/roles/{id}/permissions [system:update]
|
||||
2026/02/23 12:29:36 ✅ Route+Perm registered → POST /api/roles/{id}/permissions [system:update]
|
||||
2026/02/23 12:29:36 ✅ Route+Perm registered → GET /api/users/{id}/permissions [system:update]
|
||||
2026/02/23 12:29:36 ✅ Route+Perm registered → POST /api/users/{id}/permissions [system:update]
|
||||
2026/02/23 12:29:37 ✅ Route+Perm registered → GET /api/permissions/routes [system:view]
|
||||
2026/02/23 12:29:37 ✅ Route+Perm registered → GET /api/permissions/effective [system:view]
|
||||
2026/02/23 12:29:38 ✅ Route+Perm registered → GET /api/permissions/matrix [system:view]
|
||||
2026/02/23 12:29:38 ✅ Route+Perm registered → GET /api/role-dept-permissions/list [system:update]
|
||||
2026/02/23 12:29:38 ✅ Route+Perm registered → GET /api/roles/{roleId}/departments/{deptCode}/permissions [system:update]
|
||||
2026/02/23 12:29:39 ✅ Route+Perm registered → POST /api/roles/{roleId}/departments/{deptCode}/permissions [system:update]
|
||||
2026/02/23 12:29:39 ✅ Route+Perm registered → GET /api/users/list [user:view]
|
||||
2026/02/23 12:29:40 ✅ Route+Perm registered → POST /api/users [user:insert]
|
||||
2026/02/23 12:29:40 ✅ Route+Perm registered → GET /api/users/{id} [user:update]
|
||||
2026/02/23 12:29:41 ✅ Route+Perm registered → PUT /api/users/{id} [user:update]
|
||||
2026/02/23 12:29:41 ✅ Route+Perm registered → DELETE /api/users/{id} [user:delete]
|
||||
2026/02/23 12:29:41 ✅ Route+Perm registered → POST /api/users/{id}/admin-reset-password [user:update]
|
||||
2026/02/23 12:29:42 ✅ Route+Perm registered → POST /api/users/{id}/send-password-mail [user:update]
|
||||
2026/02/23 12:29:42 ✅ Route+Perm registered → POST /api/users/create [user:insert]
|
||||
2026/02/23 12:29:43 ✅ Route+Perm registered → GET /api/lookups/users-perm [user:view]
|
||||
2026/02/23 12:29:43 ✅ Route+Perm registered → GET /api/lookups/roles-perm [user:view]
|
||||
2026/02/23 12:29:43 ✅ Route+Perm registered → GET /api/lookups/departments-perm [user:view]
|
||||
2026/02/23 12:29:44 ✅ Route+Perm registered → GET /api/lookups/modules [user:view]
|
||||
2026/02/23 12:29:44 ✅ Route+Perm registered → GET /api/lookups/roles [user:view]
|
||||
2026/02/23 12:29:45 ✅ Route+Perm registered → GET /api/lookups/departments [user:view]
|
||||
2026/02/23 12:29:45 ✅ Route+Perm registered → GET /api/lookups/nebim-users [user:view]
|
||||
2026/02/23 12:29:46 ✅ Route+Perm registered → GET /api/lookups/piyasalar [user:view]
|
||||
2026/02/23 12:29:46 ✅ Route+Perm registered → GET /api/accounts [customer:view]
|
||||
2026/02/23 12:29:46 ✅ Route+Perm registered → GET /api/customer-list [customer:view]
|
||||
2026/02/23 12:29:47 ✅ Route+Perm registered → GET /api/today-currency [finance:view]
|
||||
2026/02/23 12:29:47 ✅ Route+Perm registered → GET /api/export-pdf [finance:export]
|
||||
2026/02/23 12:29:48 ✅ Route+Perm registered → GET /api/exportstamentheaderreport-pdf [finance:export]
|
||||
2026/02/23 12:29:48 ✅ Route+Perm registered → GET /api/finance/customer-balances [finance:view]
|
||||
2026/02/23 12:29:48 ✅ Route+Perm registered → GET /api/statements [finance:view]
|
||||
2026/02/23 12:29:49 ✅ Route+Perm registered → GET /api/statements/{id}/details [finance:view]
|
||||
2026/02/23 12:29:49 ✅ Route+Perm registered → POST /api/order/create [order:insert]
|
||||
2026/02/23 12:29:50 ✅ Route+Perm registered → POST /api/order/update [order:update]
|
||||
2026/02/23 12:29:50 ✅ Route+Perm registered → GET /api/order/get/{id} [order:view]
|
||||
2026/02/23 12:29:51 ✅ Route+Perm registered → GET /api/orders/list [order:view]
|
||||
2026/02/23 12:29:51 ✅ Route+Perm registered → GET /api/orders/production-list [order:update]
|
||||
2026/02/23 12:29:51 ✅ Route+Perm registered → GET /api/orders/production-items/{id} [order:view]
|
||||
2026/02/23 12:29:52 ✅ Route+Perm registered → POST /api/orders/production-items/{id}/insert-missing [order:update]
|
||||
2026/02/23 12:29:52 ✅ Route+Perm registered → POST /api/orders/production-items/{id}/validate [order:update]
|
||||
2026/02/23 12:29:53 ✅ Route+Perm registered → POST /api/orders/production-items/{id}/apply [order:update]
|
||||
2026/02/23 12:29:53 ✅ Route+Perm registered → GET /api/orders/close-ready [order:update]
|
||||
2026/02/23 12:29:54 ✅ Route+Perm registered → POST /api/orders/bulk-close [order:update]
|
||||
2026/02/23 12:29:54 ✅ Route+Perm registered → GET /api/orders/export [order:export]
|
||||
2026/02/23 12:29:54 ✅ Route+Perm registered → GET /api/order/check/{id} [order:view]
|
||||
2026/02/23 12:29:55 ✅ Route+Perm registered → POST /api/order/validate [order:insert]
|
||||
2026/02/23 12:29:55 ✅ Route+Perm registered → GET /api/order/pdf/{id} [order:export]
|
||||
2026/02/23 12:29:56 ✅ Route+Perm registered → GET /api/order-inventory [order:view]
|
||||
2026/02/23 12:29:56 ✅ Route+Perm registered → GET /api/orderpricelistb2b [order:view]
|
||||
2026/02/23 12:29:57 ✅ Route+Perm registered → GET /api/min-price [order:view]
|
||||
2026/02/23 12:29:57 ✅ Route+Perm registered → GET /api/products [order:view]
|
||||
2026/02/23 12:29:57 ✅ Route+Perm registered → GET /api/product-detail [order:view]
|
||||
2026/02/23 12:29:58 ✅ Route+Perm registered → GET /api/product-colors [order:view]
|
||||
2026/02/23 12:29:58 ✅ Route+Perm registered → GET /api/product-colorsize [order:view]
|
||||
2026/02/23 12:29:59 ✅ Route+Perm registered → GET /api/product-secondcolor [order:view]
|
||||
2026/02/23 12:29:59 ✅ Route+Perm registered → GET /api/roles [user:view]
|
||||
2026/02/23 12:29:59 ✅ Route+Perm registered → GET /api/departments [user:view]
|
||||
2026/02/23 12:30:00 ✅ Route+Perm registered → GET /api/piyasalar [user:view]
|
||||
2026/02/23 12:30:01 ✅ Route+Perm registered → POST /api/roles/{id}/departments [user:update]
|
||||
2026/02/23 12:30:01 ✅ Route+Perm registered → POST /api/roles/{id}/piyasalar [user:update]
|
||||
2026/02/23 12:30:01 ✅ Route+Perm registered → POST /api/users/{id}/roles [user:update]
|
||||
2026/02/23 12:30:02 ✅ Route+Perm registered → POST /api/admin/users/{id}/piyasa-sync [admin:user.update]
|
||||
2026/02/23 12:30:02 🌍 CORS Allowed Origin: http://ss.baggi.com.tr/app
|
||||
2026/02/23 12:30:02 🚀 Server running at: 0.0.0.0:8080
|
||||
2026/02/23 12:30:43 ➡️ POST /api/auth/login | auth=false
|
||||
2026/02/23 12:30:44 🔎 LOGIN DEBUG | mk_user_found=false err=mk_user not found hash_len=0
|
||||
2026/02/23 12:30:44 🟡 LEGACY LOGIN PATH: x
|
||||
2026/02/23 12:30:44 🟡 LEGACY LOGIN QUERY HIT: x
|
||||
2026/02/23 12:30:44 ❌ LEGACY SCAN ERROR: sql: no rows in result set
|
||||
2026/02/23 12:30:44 ⬅️ POST /api/auth/login | status=401 | 279.0065ms
|
||||
2026/02/23 12:30:44 ⚠️ LOGGER: claims is NIL
|
||||
2026/02/23 12:30:44 🧾 auditlog INSERT | actor_dfusr=<nil> actor_user=<nil> role=public nav /api/auth/login target=<nil>
|
||||
2026/02/23 12:30:50 ➡️ POST /api/auth/login | auth=false
|
||||
2026/02/23 12:30:50 🧪 MK USER FROM DB
|
||||
2026/02/23 12:30:50 🧪 ID=5 role_id=3 role_code='admin' depts=[UST_YONETIM]
|
||||
2026/02/23 12:30:50 🔎 LOGIN DEBUG | mk_user_found=true err=<nil> hash_len=60
|
||||
2026/02/23 12:30:50 🧪 LOGIN RESPONSE USER DEBUG
|
||||
2026/02/23 12:30:50 🧪 user.ID = 5
|
||||
2026/02/23 12:30:50 🧪 user.Username = mehmet.keçeci
|
||||
2026/02/23 12:30:50 🧪 user.RoleID = 3
|
||||
2026/02/23 12:30:50 🧪 user.RoleCode = 'admin'
|
||||
2026/02/23 12:30:50 🧪 user.IsActive = true
|
||||
2026/02/23 12:30:50 ⬅️ POST /api/auth/login | status=200 | 593.239ms
|
||||
2026/02/23 12:30:50 ⚠️ LOGGER: claims is NIL
|
||||
2026/02/23 12:30:50 🧾 auditlog INSERT | actor_dfusr=<nil> actor_user=<nil> role=public nav /api/auth/login target=<nil>
|
||||
2026/02/23 12:30:52 🔐 GLOBAL AUTH user=5 role=admin
|
||||
2026/02/23 12:30:52 ➡️ GET /api/finance/customer-balances | auth=true
|
||||
2026/02/23 12:30:52 AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/finance/customer-balances
|
||||
2026/02/23 12:30:52 🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] finance:view
|
||||
2026/02/23 12:30:53 ↳ ROLE+DEPT OVERRIDE = true
|
||||
2026/02/23 12:33:21 ⬅️ GET /api/finance/customer-balances | status=200 | 2m28.8586087s
|
||||
2026/02/23 12:33:21 ✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5
|
||||
2026/02/23 12:33:21 🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/finance/customer-balances target=<nil>
|
||||
2026/02/23 13:40:17 ➡️ POST /api/auth/refresh | auth=false
|
||||
2026/02/23 13:40:18 ⬅️ POST /api/auth/refresh | status=200 | 852.618ms
|
||||
2026/02/23 13:40:18 ⚠️ LOGGER: claims is NIL
|
||||
2026/02/23 13:40:18 🧾 auditlog INSERT | actor_dfusr=<nil> actor_user=<nil> role=public nav /api/auth/refresh target=<nil>
|
||||
2026/02/23 13:40:18 🔐 GLOBAL AUTH user=5 role=admin
|
||||
2026/02/23 13:40:18 ➡️ GET /api/finance/customer-balances | auth=true
|
||||
2026/02/23 13:40:18 AUTH_MIDDLEWARE PASS user=5 role=admin method=GET path=/api/finance/customer-balances
|
||||
2026/02/23 13:40:18 🔐 PERM CHECK user=5 role=3 dept=[UST_YONETIM] finance:view
|
||||
2026/02/23 13:40:19 ↳ ROLE+DEPT OVERRIDE = true
|
||||
2026/02/23 13:42:46 ⬅️ GET /api/finance/customer-balances | status=200 | 2m27.9525306s
|
||||
2026/02/23 13:42:46 ✅ LOGGER CLAIMS user=mehmet.keçeci role=admin id=5
|
||||
2026/02/23 13:42:46 🧾 auditlog INSERT | actor_dfusr=5 actor_user=mehmet.keçeci role=admin nav /api/finance/customer-balances target=<nil>
|
||||
exit status 1
|
||||
@@ -1,77 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* THIS FILE IS GENERATED AUTOMATICALLY.
|
||||
* DO NOT EDIT.
|
||||
*
|
||||
* You are probably looking on adding startup/initialization code.
|
||||
* Use "quasar new boot <name>" and add it there.
|
||||
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
||||
* boot: ['file', ...] // do not add ".js" extension to it.
|
||||
*
|
||||
* Boot files are your "main.js"
|
||||
**/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import { Quasar } from 'quasar'
|
||||
import { markRaw } from 'vue'
|
||||
import RootComponent from 'app/src/App.vue'
|
||||
|
||||
import createStore from 'app/src/stores/index'
|
||||
import createRouter from 'app/src/router/index'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export default async function (createAppFn, quasarUserOptions) {
|
||||
|
||||
|
||||
// Create the app instance.
|
||||
// Here we inject into it the Quasar UI, the router & possibly the store.
|
||||
const app = createAppFn(RootComponent)
|
||||
|
||||
|
||||
app.config.performance = true
|
||||
|
||||
|
||||
app.use(Quasar, quasarUserOptions)
|
||||
|
||||
|
||||
|
||||
|
||||
const store = typeof createStore === 'function'
|
||||
? await createStore({})
|
||||
: createStore
|
||||
|
||||
|
||||
app.use(store)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const router = markRaw(
|
||||
typeof createRouter === 'function'
|
||||
? await createRouter({store})
|
||||
: createRouter
|
||||
)
|
||||
|
||||
|
||||
// make router instance available in store
|
||||
|
||||
store.use(({ store }) => { store.router = router })
|
||||
|
||||
|
||||
|
||||
// Expose the app, the router and the store.
|
||||
// Note that we are not mounting the app here, since bootstrapping will be
|
||||
// different depending on whether we are in a browser or on the server.
|
||||
return {
|
||||
app,
|
||||
store,
|
||||
router
|
||||
}
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* THIS FILE IS GENERATED AUTOMATICALLY.
|
||||
* DO NOT EDIT.
|
||||
*
|
||||
* You are probably looking on adding startup/initialization code.
|
||||
* Use "quasar new boot <name>" and add it there.
|
||||
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
||||
* boot: ['file', ...] // do not add ".js" extension to it.
|
||||
*
|
||||
* Boot files are your "main.js"
|
||||
**/
|
||||
|
||||
|
||||
import { createApp } from 'vue'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
import '@quasar/extras/roboto-font/roboto-font.css'
|
||||
|
||||
import '@quasar/extras/material-icons/material-icons.css'
|
||||
|
||||
|
||||
|
||||
|
||||
// We load Quasar stylesheet file
|
||||
import 'quasar/dist/quasar.sass'
|
||||
|
||||
|
||||
|
||||
|
||||
import 'src/css/app.css'
|
||||
|
||||
|
||||
import createQuasarApp from './app.js'
|
||||
import quasarUserOptions from './quasar-user-options.js'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
console.info('[Quasar] Running SPA.')
|
||||
|
||||
|
||||
|
||||
const publicPath = `/`
|
||||
|
||||
|
||||
async function start ({
|
||||
app,
|
||||
router
|
||||
, store
|
||||
}, bootFiles) {
|
||||
|
||||
let hasRedirected = false
|
||||
const getRedirectUrl = url => {
|
||||
try { return router.resolve(url).href }
|
||||
catch (err) {}
|
||||
|
||||
return Object(url) === url
|
||||
? null
|
||||
: url
|
||||
}
|
||||
const redirect = url => {
|
||||
hasRedirected = true
|
||||
|
||||
if (typeof url === 'string' && /^https?:\/\//.test(url)) {
|
||||
window.location.href = url
|
||||
return
|
||||
}
|
||||
|
||||
const href = getRedirectUrl(url)
|
||||
|
||||
// continue if we didn't fail to resolve the url
|
||||
if (href !== null) {
|
||||
window.location.href = href
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
const urlPath = window.location.href.replace(window.location.origin, '')
|
||||
|
||||
for (let i = 0; hasRedirected === false && i < bootFiles.length; i++) {
|
||||
try {
|
||||
await bootFiles[i]({
|
||||
app,
|
||||
router,
|
||||
store,
|
||||
ssrContext: null,
|
||||
redirect,
|
||||
urlPath,
|
||||
publicPath
|
||||
})
|
||||
}
|
||||
catch (err) {
|
||||
if (err && err.url) {
|
||||
redirect(err.url)
|
||||
return
|
||||
}
|
||||
|
||||
console.error('[Quasar] boot error:', err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (hasRedirected === true) return
|
||||
|
||||
|
||||
app.use(router)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
app.mount('#q-app')
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
createQuasarApp(createApp, quasarUserOptions)
|
||||
|
||||
.then(app => {
|
||||
// eventually remove this when Cordova/Capacitor/Electron support becomes old
|
||||
const [ method, mapFn ] = Promise.allSettled !== void 0
|
||||
? [
|
||||
'allSettled',
|
||||
bootFiles => bootFiles.map(result => {
|
||||
if (result.status === 'rejected') {
|
||||
console.error('[Quasar] boot error:', result.reason)
|
||||
return
|
||||
}
|
||||
return result.value.default
|
||||
})
|
||||
]
|
||||
: [
|
||||
'all',
|
||||
bootFiles => bootFiles.map(entry => entry.default)
|
||||
]
|
||||
|
||||
return Promise[ method ]([
|
||||
|
||||
import(/* webpackMode: "eager" */ 'boot/dayjs')
|
||||
|
||||
]).then(bootFiles => {
|
||||
const boot = mapFn(bootFiles).filter(entry => typeof entry === 'function')
|
||||
start(app, boot)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* THIS FILE IS GENERATED AUTOMATICALLY.
|
||||
* DO NOT EDIT.
|
||||
*
|
||||
* You are probably looking on adding startup/initialization code.
|
||||
* Use "quasar new boot <name>" and add it there.
|
||||
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
||||
* boot: ['file', ...] // do not add ".js" extension to it.
|
||||
*
|
||||
* Boot files are your "main.js"
|
||||
**/
|
||||
|
||||
|
||||
|
||||
import App from 'app/src/App.vue'
|
||||
let appPrefetch = typeof App.preFetch === 'function'
|
||||
? App.preFetch
|
||||
: (
|
||||
// Class components return the component options (and the preFetch hook) inside __c property
|
||||
App.__c !== void 0 && typeof App.__c.preFetch === 'function'
|
||||
? App.__c.preFetch
|
||||
: false
|
||||
)
|
||||
|
||||
|
||||
function getMatchedComponents (to, router) {
|
||||
const route = to
|
||||
? (to.matched ? to : router.resolve(to).route)
|
||||
: router.currentRoute.value
|
||||
|
||||
if (!route) { return [] }
|
||||
|
||||
const matched = route.matched.filter(m => m.components !== void 0)
|
||||
|
||||
if (matched.length === 0) { return [] }
|
||||
|
||||
return Array.prototype.concat.apply([], matched.map(m => {
|
||||
return Object.keys(m.components).map(key => {
|
||||
const comp = m.components[key]
|
||||
return {
|
||||
path: m.path,
|
||||
c: comp
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
export function addPreFetchHooks ({ router, store, publicPath }) {
|
||||
// Add router hook for handling preFetch.
|
||||
// Doing it after initial route is resolved so that we don't double-fetch
|
||||
// the data that we already have. Using router.beforeResolve() so that all
|
||||
// async components are resolved.
|
||||
router.beforeResolve((to, from, next) => {
|
||||
const
|
||||
urlPath = window.location.href.replace(window.location.origin, ''),
|
||||
matched = getMatchedComponents(to, router),
|
||||
prevMatched = getMatchedComponents(from, router)
|
||||
|
||||
let diffed = false
|
||||
const preFetchList = matched
|
||||
.filter((m, i) => {
|
||||
return diffed || (diffed = (
|
||||
!prevMatched[i] ||
|
||||
prevMatched[i].c !== m.c ||
|
||||
m.path.indexOf('/:') > -1 // does it has params?
|
||||
))
|
||||
})
|
||||
.filter(m => m.c !== void 0 && (
|
||||
typeof m.c.preFetch === 'function'
|
||||
// Class components return the component options (and the preFetch hook) inside __c property
|
||||
|| (m.c.__c !== void 0 && typeof m.c.__c.preFetch === 'function')
|
||||
))
|
||||
.map(m => m.c.__c !== void 0 ? m.c.__c.preFetch : m.c.preFetch)
|
||||
|
||||
|
||||
if (appPrefetch !== false) {
|
||||
preFetchList.unshift(appPrefetch)
|
||||
appPrefetch = false
|
||||
}
|
||||
|
||||
|
||||
if (preFetchList.length === 0) {
|
||||
return next()
|
||||
}
|
||||
|
||||
let hasRedirected = false
|
||||
const redirect = url => {
|
||||
hasRedirected = true
|
||||
next(url)
|
||||
}
|
||||
const proceed = () => {
|
||||
|
||||
if (hasRedirected === false) { next() }
|
||||
}
|
||||
|
||||
|
||||
|
||||
preFetchList.reduce(
|
||||
(promise, preFetch) => promise.then(() => hasRedirected === false && preFetch({
|
||||
store,
|
||||
currentRoute: to,
|
||||
previousRoute: from,
|
||||
redirect,
|
||||
urlPath,
|
||||
publicPath
|
||||
})),
|
||||
Promise.resolve()
|
||||
)
|
||||
.then(proceed)
|
||||
.catch(e => {
|
||||
console.error(e)
|
||||
proceed()
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* THIS FILE IS GENERATED AUTOMATICALLY.
|
||||
* DO NOT EDIT.
|
||||
*
|
||||
* You are probably looking on adding startup/initialization code.
|
||||
* Use "quasar new boot <name>" and add it there.
|
||||
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
|
||||
* boot: ['file', ...] // do not add ".js" extension to it.
|
||||
*
|
||||
* Boot files are your "main.js"
|
||||
**/
|
||||
|
||||
import lang from 'quasar/lang/tr.js'
|
||||
|
||||
|
||||
|
||||
import {Loading,Dialog,Notify} from 'quasar'
|
||||
|
||||
|
||||
|
||||
export default { config: {"notify":{"position":"top","timeout":2500}},lang,plugins: {Loading,Dialog,Notify} }
|
||||
|
||||
8
ui/.quasar/feature-flags.d.ts
vendored
8
ui/.quasar/feature-flags.d.ts
vendored
@@ -1,8 +0,0 @@
|
||||
/* eslint-disable */
|
||||
import "quasar/dist/types/feature-flag.d.ts";
|
||||
|
||||
declare module "quasar/dist/types/feature-flag.d.ts" {
|
||||
interface QuasarFeatureFlags {
|
||||
store: true;
|
||||
}
|
||||
}
|
||||
8
ui/.quasar/pinia.d.ts
vendored
8
ui/.quasar/pinia.d.ts
vendored
@@ -1,8 +0,0 @@
|
||||
/* eslint-disable */
|
||||
import { Router } from 'vue-router';
|
||||
|
||||
declare module 'pinia' {
|
||||
export interface PiniaCustomProperties {
|
||||
readonly router: Router;
|
||||
}
|
||||
}
|
||||
4
ui/.quasar/quasar.d.ts
vendored
4
ui/.quasar/quasar.d.ts
vendored
@@ -1,4 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/// <reference types="@quasar/app-webpack" />
|
||||
|
||||
/// <reference types="vite/client" />
|
||||
@@ -1,94 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"target": "esnext",
|
||||
"allowJs": true,
|
||||
"resolveJsonModule": true,
|
||||
"moduleDetection": "force",
|
||||
"isolatedModules": true,
|
||||
"module": "preserve",
|
||||
"noEmit": true,
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable"
|
||||
],
|
||||
"paths": {
|
||||
"src": [
|
||||
"./../src"
|
||||
],
|
||||
"src/*": [
|
||||
"./../src/*"
|
||||
],
|
||||
"app": [
|
||||
"./.."
|
||||
],
|
||||
"app/*": [
|
||||
"./../*"
|
||||
],
|
||||
"components": [
|
||||
"./../src/components"
|
||||
],
|
||||
"components/*": [
|
||||
"./../src/components/*"
|
||||
],
|
||||
"layouts": [
|
||||
"./../src/layouts"
|
||||
],
|
||||
"layouts/*": [
|
||||
"./../src/layouts/*"
|
||||
],
|
||||
"pages": [
|
||||
"./../src/pages"
|
||||
],
|
||||
"pages/*": [
|
||||
"./../src/pages/*"
|
||||
],
|
||||
"assets": [
|
||||
"./../src/assets"
|
||||
],
|
||||
"assets/*": [
|
||||
"./../src/assets/*"
|
||||
],
|
||||
"boot": [
|
||||
"./../src/boot"
|
||||
],
|
||||
"boot/*": [
|
||||
"./../src/boot/*"
|
||||
],
|
||||
"stores": [
|
||||
"./../src/stores"
|
||||
],
|
||||
"stores/*": [
|
||||
"./../src/stores/*"
|
||||
],
|
||||
"#q-app": [
|
||||
"./../node_modules/@quasar/app-webpack/types/index.d.ts"
|
||||
],
|
||||
"#q-app/wrappers": [
|
||||
"./../node_modules/@quasar/app-webpack/types/app-wrappers.d.ts"
|
||||
],
|
||||
"#q-app/bex/background": [
|
||||
"./../node_modules/@quasar/app-webpack/types/bex/entrypoints/background.d.ts"
|
||||
],
|
||||
"#q-app/bex/content": [
|
||||
"./../node_modules/@quasar/app-webpack/types/bex/entrypoints/content.d.ts"
|
||||
],
|
||||
"#q-app/bex/private/bex-bridge": [
|
||||
"./../node_modules/@quasar/app-webpack/types/bex/bex-bridge.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"./**/*.d.ts",
|
||||
"./../**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"./../dist",
|
||||
"./../node_modules",
|
||||
"./../src-capacitor",
|
||||
"./../src-cordova",
|
||||
"./../quasar.config.*.temporary.compiled*"
|
||||
]
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* THIS FILE IS GENERATED AUTOMATICALLY.
|
||||
* 1. DO NOT edit this file directly as it won't do anything.
|
||||
* 2. EDIT the original quasar.config file INSTEAD.
|
||||
* 3. DO NOT git commit this file. It should be ignored.
|
||||
*
|
||||
* This file is still here because there was an error in
|
||||
* the original quasar.config file and this allows you to
|
||||
* investigate the Node.js stack error.
|
||||
*
|
||||
* After you fix the original file, this file will be
|
||||
* deleted automatically.
|
||||
**/
|
||||
|
||||
|
||||
// quasar.config.js
|
||||
import { defineConfig } from "@quasar/app-webpack/wrappers";
|
||||
var quasar_config_default = defineConfig(() => {
|
||||
const apiBaseUrl = (process.env.VITE_API_BASE_URL || "/api").trim();
|
||||
return {
|
||||
/* =====================================================
|
||||
APP INFO
|
||||
===================================================== */
|
||||
productName: "Baggi BSS",
|
||||
productDescription: "Baggi Tekstil Business Support System",
|
||||
/* =====================================================
|
||||
BOOT FILES
|
||||
===================================================== */
|
||||
boot: ["dayjs"],
|
||||
/* =====================================================
|
||||
GLOBAL CSS
|
||||
===================================================== */
|
||||
css: ["app.css"],
|
||||
/* =====================================================
|
||||
ICONS / FONTS
|
||||
===================================================== */
|
||||
extras: [
|
||||
"roboto-font",
|
||||
"material-icons"
|
||||
],
|
||||
/* =====================================================
|
||||
BUILD (PRODUCTION)
|
||||
===================================================== */
|
||||
build: {
|
||||
vueRouterMode: "hash",
|
||||
env: {
|
||||
VITE_API_BASE_URL: apiBaseUrl
|
||||
},
|
||||
esbuildTarget: {
|
||||
browser: ["es2022", "firefox115", "chrome115", "safari14"],
|
||||
node: "node20"
|
||||
},
|
||||
// Cache & performance
|
||||
gzip: true,
|
||||
preloadChunks: true
|
||||
},
|
||||
/* =====================================================
|
||||
DEV SERVER (LOCAL)
|
||||
===================================================== */
|
||||
devServer: {
|
||||
server: { type: "http" },
|
||||
port: 9e3,
|
||||
open: true,
|
||||
// DEV proxy (CORS'suz)
|
||||
proxy: [
|
||||
{
|
||||
context: ["/api"],
|
||||
target: "http://localhost:8080",
|
||||
changeOrigin: true,
|
||||
secure: false
|
||||
}
|
||||
]
|
||||
},
|
||||
/* =====================================================
|
||||
QUASAR FRAMEWORK
|
||||
===================================================== */
|
||||
framework: {
|
||||
config: {
|
||||
notify: {
|
||||
position: "top",
|
||||
timeout: 2500
|
||||
}
|
||||
},
|
||||
lang: "tr",
|
||||
plugins: [
|
||||
"Loading",
|
||||
"Dialog",
|
||||
"Notify"
|
||||
]
|
||||
},
|
||||
animations: [],
|
||||
/* =====================================================
|
||||
SSR / PWA (DISABLED)
|
||||
===================================================== */
|
||||
ssr: {
|
||||
prodPort: 3e3,
|
||||
middlewares: ["render"],
|
||||
pwa: false
|
||||
},
|
||||
pwa: {
|
||||
workboxMode: "GenerateSW"
|
||||
},
|
||||
/* =====================================================
|
||||
MOBILE / DESKTOP
|
||||
===================================================== */
|
||||
capacitor: {
|
||||
hideSplashscreen: true
|
||||
},
|
||||
electron: {
|
||||
preloadScripts: ["electron-preload"],
|
||||
inspectPort: 5858,
|
||||
bundler: "packager",
|
||||
builder: {
|
||||
appId: "baggisowtfaresystem"
|
||||
}
|
||||
},
|
||||
bex: {
|
||||
extraScripts: []
|
||||
}
|
||||
};
|
||||
});
|
||||
export {
|
||||
quasar_config_default as default
|
||||
};
|
||||
@@ -195,6 +195,11 @@ const menuItems = [
|
||||
label: 'Cari Ekstre',
|
||||
to: '/app/statementofaccount',
|
||||
permission: 'finance:view'
|
||||
},
|
||||
{
|
||||
label: 'Cari Bakiye Listesi',
|
||||
to: '/app/customer-balance-list',
|
||||
permission: 'finance:view'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
731
ui/src/pages/CustomerBalanceList.vue
Normal file
731
ui/src/pages/CustomerBalanceList.vue
Normal file
@@ -0,0 +1,731 @@
|
||||
<template>
|
||||
<q-page v-if="canReadFinance" class="q-pa-md page-layout">
|
||||
<div class="filter-sticky">
|
||||
<div class="row q-col-gutter-sm q-mb-md">
|
||||
<div class="col-12 col-sm-6 col-md-4">
|
||||
<q-input
|
||||
v-model="store.filters.cariSearch"
|
||||
filled
|
||||
dense
|
||||
label="Cari Kodu / Cari Adı"
|
||||
@keyup.enter="store.applyCariSearch()"
|
||||
>
|
||||
<template #append>
|
||||
<q-btn
|
||||
dense
|
||||
flat
|
||||
round
|
||||
icon="search"
|
||||
@click="store.applyCariSearch()"
|
||||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-sm-6 col-md-2">
|
||||
<q-input
|
||||
v-model="store.filters.selectedDate"
|
||||
label="Tarih"
|
||||
filled
|
||||
dense
|
||||
readonly
|
||||
>
|
||||
<template #append>
|
||||
<q-icon name="event" class="cursor-pointer">
|
||||
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
||||
<q-date v-model="store.filters.selectedDate" mask="YYYY-MM-DD" />
|
||||
</q-popup-proxy>
|
||||
</q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-sm-6 col-md-2">
|
||||
<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 (Özellik05)"
|
||||
:display-value="selectionLabel(store.filters.ulke, 'Ülke')"
|
||||
>
|
||||
<template #before-options>
|
||||
<q-item clickable dense @click.stop="store.selectAll('ulke', store.ulkeOptions)">
|
||||
<q-item-section>Tümünü Seç</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable dense @click.stop="store.clearAll('ulke')">
|
||||
<q-item-section>Tümünü Temizle</q-item-section>
|
||||
</q-item>
|
||||
<q-separator />
|
||||
</template>
|
||||
<template #option="scope">
|
||||
<q-item v-bind="scope.itemProps">
|
||||
<q-item-section avatar>
|
||||
<q-checkbox :model-value="scope.selected" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row q-col-gutter-sm q-mb-md">
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
color="primary"
|
||||
icon="download"
|
||||
label="Bakiyeleri Getir"
|
||||
:loading="store.loading"
|
||||
@click="store.fetchCustomerBalances()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
flat
|
||||
color="grey-8"
|
||||
icon="restart_alt"
|
||||
label="Sıfırla"
|
||||
@click="onReset"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
Bakiyeleri Getir Tuşuna Basmadan Sistem Çalışmaz
|
||||
</q-banner>
|
||||
</div>
|
||||
|
||||
<div class="table-area">
|
||||
<div class="sticky-bar row justify-end items-center q-pa-sm bg-grey-1">
|
||||
<q-btn
|
||||
flat
|
||||
color="secondary"
|
||||
icon="list"
|
||||
:label="allDetailsOpen ? 'Tüm Detayları Kapat' : 'Tüm Detayları Aç'"
|
||||
@click="toggleAllDetails"
|
||||
/>
|
||||
</div>
|
||||
<q-table
|
||||
title="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="col.name === 'hesap_alinmayan_gun'" class="text-right block">
|
||||
-
|
||||
</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>
|
||||
</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 module erisim yetkiniz yok.
|
||||
</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { useCustomerBalanceListStore } from 'src/stores/customerBalanceListStore'
|
||||
import { usePermission } from 'src/composables/usePermission'
|
||||
|
||||
const store = useCustomerBalanceListStore()
|
||||
const expanded = ref({})
|
||||
const allDetailsOpen = ref(false)
|
||||
|
||||
const { canRead } = usePermission()
|
||||
const canReadFinance = canRead('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 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 },
|
||||
try_1_2: { name: 'tl_bakiye_1_2', label: '1_2 TRY_BAKIYE', field: 'tl_bakiye_1_2', align: 'center', sortable: true },
|
||||
usd_1_3: { name: 'usd_bakiye_1_3', label: '1_3 USD_BAKIYE', field: 'usd_bakiye_1_3', align: 'center', sortable: true },
|
||||
try_1_3: { name: 'tl_bakiye_1_3', label: '1_3 TRY_BAKIYE', field: 'tl_bakiye_1_3', align: 'center', sortable: true }
|
||||
}
|
||||
|
||||
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 },
|
||||
{ name: 'ana_cari_adi', label: 'Ana Cari Detay', field: 'ana_cari_adi', align: 'left', sortable: true },
|
||||
{ name: 'piyasa', label: 'Piyasa', field: 'piyasa', align: 'left', sortable: true },
|
||||
{ name: 'temsilci', label: 'Temsilci', field: 'temsilci', align: 'left', sortable: true },
|
||||
{ name: 'risk_durumu', label: 'Risk Durumu', field: 'risk_durumu', align: 'left', sortable: true },
|
||||
...selectedMetricKeys.value.map(k => metricDefs[k]),
|
||||
{ name: 'hesap_alinmayan_gun', label: 'Hesap Alınmayan Gün', field: 'hesap_alinmayan_gun', align: 'right', sortable: false },
|
||||
{ name: 'kalan_fatura_ortalama_vade_tarihi', label: 'Kalan Fatura Ortalama Vade Tarihi', field: 'kalan_fatura_ortalama_vade_tarihi', align: 'left', sortable: true }
|
||||
]))
|
||||
const liveTotals = computed(() => {
|
||||
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
|
||||
return acc
|
||||
}, {
|
||||
usd_bakiye_1_2: 0,
|
||||
tl_bakiye_1_2: 0,
|
||||
usd_bakiye_1_3: 0,
|
||||
tl_bakiye_1_3: 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: 'piyasa', label: 'Piyasa', field: 'piyasa', align: 'left' },
|
||||
{ name: 'temsilci', label: 'Temsilci', field: 'temsilci', align: 'left' },
|
||||
{ name: 'ozellik03', label: 'Risk Durumu', field: 'ozellik03', align: 'left' },
|
||||
{ name: 'ozellik05', label: 'Ülke', field: 'ozellik05', align: 'left' },
|
||||
{ name: 'ozellik06', label: 'Özellik06', field: 'ozellik06', align: 'left' },
|
||||
{ name: 'ozellik07', label: 'Özellik07', field: 'ozellik07', align: 'left' },
|
||||
{ name: 'cari_doviz', label: 'Döviz', field: 'cari_doviz', align: 'left' },
|
||||
...selectedMetricKeys.value.map(k => metricDefs[k]),
|
||||
{ name: 'hesap_alinmayan_gun', label: 'Hesap Alınmayan Gün', field: 'hesap_alinmayan_gun', align: 'right' },
|
||||
{ name: 'kalan_fatura_ortalama_vade_tarihi', label: 'Kalan Fatura Ortalama Vade Tarihi', field: 'kalan_fatura_ortalama_vade_tarihi', align: 'left' }
|
||||
])
|
||||
|
||||
function onReset () {
|
||||
store.resetFilters()
|
||||
store.applyCariSearch()
|
||||
}
|
||||
|
||||
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 = {}
|
||||
}
|
||||
|
||||
function formatAmount (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 === 'hesap_alinmayan_gun') return '-'
|
||||
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(' | ')
|
||||
}
|
||||
|
||||
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)}`
|
||||
}
|
||||
|
||||
</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;
|
||||
}
|
||||
|
||||
.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: normal;
|
||||
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>
|
||||
|
||||
|
||||
@@ -724,6 +724,13 @@
|
||||
:disable="isClosedRow || isViewOnly || !canMutateRows"
|
||||
|
||||
/>
|
||||
<q-btn
|
||||
v-if="canMutateRows"
|
||||
color="secondary"
|
||||
label="Kaydet ve Diğer Renge Geç"
|
||||
@click="onSaveAndNextColor"
|
||||
:disable="isClosedRow || isViewOnly || !canMutateRows"
|
||||
/>
|
||||
<q-btn
|
||||
v-if="isEditing && canMutateRows"
|
||||
color="negative"
|
||||
@@ -2831,6 +2838,76 @@ const onSaveOrUpdateRow = async () => {
|
||||
showEditor.value = false
|
||||
}
|
||||
|
||||
function normalizeColorValue(val) {
|
||||
return String(val || '').trim().toUpperCase()
|
||||
}
|
||||
|
||||
function getNextColorValue() {
|
||||
const options = Array.isArray(renkOptions.value) ? renkOptions.value : []
|
||||
if (!options.length) return null
|
||||
|
||||
const current = normalizeColorValue(form.renk)
|
||||
const idx = options.findIndex(o => normalizeColorValue(o.value) === current)
|
||||
if (idx === -1) return null
|
||||
|
||||
const next = options[idx + 1]
|
||||
return next ? next.value : null
|
||||
}
|
||||
|
||||
const onSaveAndNextColor = async () => {
|
||||
if (!hasRowMutationPermission()) {
|
||||
notifyNoPermission(
|
||||
isEditMode.value
|
||||
? 'Siparis satiri guncelleme yetkiniz yok'
|
||||
: 'Siparis satiri kaydetme yetkiniz yok'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (!form.model) {
|
||||
$q.notify({ type: 'warning', message: 'Model seçiniz' })
|
||||
return
|
||||
}
|
||||
|
||||
if (!form.renk) {
|
||||
$q.notify({ type: 'warning', message: 'Renk seçiniz' })
|
||||
return
|
||||
}
|
||||
|
||||
const ok = await orderStore.saveOrUpdateRowUnified({
|
||||
form,
|
||||
recalcVat: typeof recalcVat === 'function' ? recalcVat : null,
|
||||
resetEditor: () => {},
|
||||
stockMap,
|
||||
$q
|
||||
})
|
||||
|
||||
if (!ok) return
|
||||
|
||||
// Edit state temizle: renk değişimi combo delete tetiklemesin
|
||||
orderStore.editingKey = null
|
||||
orderStore.selected = null
|
||||
|
||||
const nextColor = getNextColorValue()
|
||||
if (!nextColor) {
|
||||
$q.notify({
|
||||
type: 'warning',
|
||||
message: 'Son renktesiniz. Lütfen farklı bir renk seçin.',
|
||||
position: 'top-right'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
form.renk2 = ''
|
||||
await onColorChange(nextColor)
|
||||
|
||||
$q.notify({
|
||||
type: 'info',
|
||||
message: 'Satır kaydedildi. Bir sonraki renge geçildi.',
|
||||
position: 'top-right'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -134,6 +134,13 @@ const routes = [
|
||||
meta: { permission: 'finance:view' }
|
||||
},
|
||||
|
||||
{
|
||||
path: 'customer-balance-list',
|
||||
name: 'customer-balance-list',
|
||||
component: () => import('pages/CustomerBalanceList.vue'),
|
||||
meta: { permission: 'finance:view' }
|
||||
},
|
||||
|
||||
|
||||
/* ================= USERS ================= */
|
||||
|
||||
|
||||
262
ui/src/stores/customerBalanceListStore.js
Normal file
262
ui/src/stores/customerBalanceListStore.js
Normal file
@@ -0,0 +1,262 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import api from 'src/services/api'
|
||||
|
||||
export const useCustomerBalanceListStore = defineStore('customerBalanceList', {
|
||||
state: () => ({
|
||||
filters: {
|
||||
selectedDate: new Date().toISOString().slice(0, 10),
|
||||
cariSearch: '',
|
||||
appliedCariSearch: '',
|
||||
cariIlkGrup: [],
|
||||
piyasa: [],
|
||||
temsilci: [],
|
||||
riskDurumu: [],
|
||||
islemTipi: [],
|
||||
ulke: []
|
||||
},
|
||||
rows: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
hasFetched: false,
|
||||
defaultsInitialized: false
|
||||
}),
|
||||
|
||||
getters: {
|
||||
cariIlkGrupOptions: (state) => uniqueOptions(state.rows, 'cari_ilk_grup'),
|
||||
piyasaOptions: (state) => uniqueOptions(state.rows, 'piyasa'),
|
||||
temsilciOptions: (state) => uniqueOptions(state.rows, 'temsilci'),
|
||||
riskDurumuOptions: (state) => uniqueOptions(state.rows, 'ozellik03'),
|
||||
ulkeOptions: (state) => uniqueOptions(state.rows, 'ozellik05'),
|
||||
|
||||
filteredRows: (state) => {
|
||||
return state.rows.filter((row) => {
|
||||
const cariIlkGrupOk =
|
||||
!state.filters.cariIlkGrup.length ||
|
||||
state.filters.cariIlkGrup.includes(row.cari_ilk_grup)
|
||||
|
||||
const piyasaOk =
|
||||
!state.filters.piyasa.length ||
|
||||
state.filters.piyasa.includes(row.piyasa)
|
||||
|
||||
const temsilciOk =
|
||||
!state.filters.temsilci.length ||
|
||||
state.filters.temsilci.includes(row.temsilci)
|
||||
|
||||
const riskDurumuOk =
|
||||
!state.filters.riskDurumu.length ||
|
||||
state.filters.riskDurumu.includes(row.ozellik03)
|
||||
|
||||
const cariText = normalizeText([
|
||||
row.ana_cari_kodu || '',
|
||||
row.ana_cari_adi || '',
|
||||
row.cari_kodu || '',
|
||||
row.cari_detay || ''
|
||||
].join(' '))
|
||||
const cariSearchNeedle = normalizeText(state.filters.appliedCariSearch || '')
|
||||
const cariSearchOk =
|
||||
!cariSearchNeedle ||
|
||||
cariText.includes(cariSearchNeedle)
|
||||
|
||||
const ulkeOk =
|
||||
!state.filters.ulke.length ||
|
||||
state.filters.ulke.includes(row.ozellik05)
|
||||
|
||||
const islemTipiOk =
|
||||
!state.filters.islemTipi.length ||
|
||||
state.filters.islemTipi.some((t) => {
|
||||
const bak12 = Number(row.bakiye_1_2) || 0
|
||||
const bak13 = Number(row.bakiye_1_3) || 0
|
||||
const usd12 = Number(row.usd_bakiye_1_2) || 0
|
||||
const try12 = Number(row.tl_bakiye_1_2) || 0
|
||||
const usd13 = Number(row.usd_bakiye_1_3) || 0
|
||||
const try13 = Number(row.tl_bakiye_1_3) || 0
|
||||
|
||||
if (t === 'prbr_1_2') return bak12 !== 0
|
||||
if (t === 'prbr_1_3') return bak13 !== 0
|
||||
if (t === 'usd_1_2') return usd12 !== 0
|
||||
if (t === 'try_1_2') return try12 !== 0
|
||||
if (t === 'usd_1_3') return usd13 !== 0
|
||||
if (t === 'try_1_3') return try13 !== 0
|
||||
return false
|
||||
})
|
||||
|
||||
return cariIlkGrupOk && piyasaOk && temsilciOk && riskDurumuOk && cariSearchOk && ulkeOk && islemTipiOk
|
||||
})
|
||||
},
|
||||
|
||||
summaryRows () {
|
||||
const grouped = new Map()
|
||||
|
||||
for (const row of this.filteredRows) {
|
||||
const key = `${row.ana_cari_kodu || ''}||${row.ana_cari_adi || ''}`
|
||||
const current = grouped.get(key) || {
|
||||
group_key: key,
|
||||
ana_cari_kodu: row.ana_cari_kodu || '',
|
||||
ana_cari_adi: row.ana_cari_adi || '',
|
||||
piyasa: '',
|
||||
piyasa_set: new Set(),
|
||||
temsilci: '',
|
||||
temsilci_set: new Set(),
|
||||
risk_durumu: '',
|
||||
risk_set: new Set(),
|
||||
bakiye_1_2_map: {},
|
||||
bakiye_1_3_map: {},
|
||||
usd_bakiye_1_2: 0,
|
||||
tl_bakiye_1_2: 0,
|
||||
usd_bakiye_1_3: 0,
|
||||
tl_bakiye_1_3: 0,
|
||||
kalan_fatura_ortalama_vade_tarihi: ''
|
||||
}
|
||||
|
||||
current.usd_bakiye_1_2 += Number(row.usd_bakiye_1_2) || 0
|
||||
current.tl_bakiye_1_2 += Number(row.tl_bakiye_1_2) || 0
|
||||
current.usd_bakiye_1_3 += Number(row.usd_bakiye_1_3) || 0
|
||||
current.tl_bakiye_1_3 += Number(row.tl_bakiye_1_3) || 0
|
||||
|
||||
const curr = String(row.cari_doviz || '').trim().toUpperCase() || 'N/A'
|
||||
current.bakiye_1_2_map[curr] =
|
||||
(Number(current.bakiye_1_2_map[curr]) || 0) + (Number(row.bakiye_1_2) || 0)
|
||||
current.bakiye_1_3_map[curr] =
|
||||
(Number(current.bakiye_1_3_map[curr]) || 0) + (Number(row.bakiye_1_3) || 0)
|
||||
|
||||
const piyasa = String(row.piyasa || '').trim()
|
||||
if (piyasa) current.piyasa_set.add(piyasa)
|
||||
|
||||
const temsilci = String(row.temsilci || '').trim()
|
||||
if (temsilci) current.temsilci_set.add(temsilci)
|
||||
|
||||
if (
|
||||
!current.kalan_fatura_ortalama_vade_tarihi &&
|
||||
row.kalan_fatura_ortalama_vade_tarihi
|
||||
) {
|
||||
current.kalan_fatura_ortalama_vade_tarihi = row.kalan_fatura_ortalama_vade_tarihi
|
||||
}
|
||||
|
||||
const risk = String(row.ozellik03 || '').trim()
|
||||
if (risk) current.risk_set.add(risk)
|
||||
|
||||
const riskValues = Array.from(current.risk_set)
|
||||
current.risk_durumu =
|
||||
riskValues.length <= 1
|
||||
? (riskValues[0] || '-')
|
||||
: riskValues.join(', ')
|
||||
|
||||
const piyasaValues = Array.from(current.piyasa_set)
|
||||
current.piyasa =
|
||||
piyasaValues.length <= 1
|
||||
? (piyasaValues[0] || '-')
|
||||
: piyasaValues.join(', ')
|
||||
|
||||
const temsilciValues = Array.from(current.temsilci_set)
|
||||
current.temsilci =
|
||||
temsilciValues.length <= 1
|
||||
? (temsilciValues[0] || '-')
|
||||
: temsilciValues.join(', ')
|
||||
|
||||
grouped.set(key, current)
|
||||
}
|
||||
|
||||
return Array.from(grouped.values()).map((r) => {
|
||||
const { risk_set, piyasa_set, temsilci_set, ...rest } = r
|
||||
return rest
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
async fetchCustomerBalances () {
|
||||
this.loading = true
|
||||
this.error = null
|
||||
|
||||
try {
|
||||
const { data } = await api.get('/finance/customer-balances', {
|
||||
params: {
|
||||
selected_date: this.filters.selectedDate,
|
||||
cari_search: String(this.filters.appliedCariSearch || this.filters.cariSearch || '').trim()
|
||||
}
|
||||
})
|
||||
this.rows = Array.isArray(data) ? data : []
|
||||
if (!this.defaultsInitialized) {
|
||||
this.applyInitialFilterDefaults()
|
||||
this.defaultsInitialized = true
|
||||
}
|
||||
this.hasFetched = true
|
||||
} catch (err) {
|
||||
this.rows = []
|
||||
this.hasFetched = false
|
||||
this.error =
|
||||
err?.response?.data?.message ||
|
||||
err?.message ||
|
||||
'Cari bakiye listesi getirilemedi.'
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
getDetailsByGroup (groupKey) {
|
||||
return this.filteredRows.filter(r =>
|
||||
`${r.ana_cari_kodu || ''}||${r.ana_cari_adi || ''}` === groupKey
|
||||
)
|
||||
},
|
||||
|
||||
resetFilters () {
|
||||
this.filters.cariSearch = ''
|
||||
this.filters.appliedCariSearch = ''
|
||||
this.filters.cariIlkGrup = []
|
||||
this.filters.piyasa = []
|
||||
this.filters.temsilci = []
|
||||
this.filters.riskDurumu = []
|
||||
this.filters.islemTipi = []
|
||||
this.filters.ulke = []
|
||||
this.defaultsInitialized = false
|
||||
},
|
||||
|
||||
applyCariSearch () {
|
||||
this.filters.appliedCariSearch = String(this.filters.cariSearch || '').trim()
|
||||
},
|
||||
|
||||
selectAll (field, options) {
|
||||
this.filters[field] = options.map(o => o.value)
|
||||
},
|
||||
|
||||
clearAll (field) {
|
||||
this.filters[field] = []
|
||||
},
|
||||
|
||||
applyInitialFilterDefaults () {
|
||||
const transferKey = normalizeText('transfer')
|
||||
this.filters.cariIlkGrup = this.cariIlkGrupOptions
|
||||
.map(o => o.value)
|
||||
.filter(v => normalizeText(v) !== transferKey)
|
||||
|
||||
const excludedRisk = new Set([
|
||||
normalizeText('avukat'),
|
||||
normalizeText('orta risk'),
|
||||
normalizeText('yuksek risk')
|
||||
])
|
||||
this.filters.riskDurumu = this.riskDurumuOptions
|
||||
.map(o => o.value)
|
||||
.filter(v => !excludedRisk.has(normalizeText(v)))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function uniqueOptions (rows, field) {
|
||||
const set = new Set()
|
||||
for (const r of rows) {
|
||||
const v = String(r[field] || '').trim()
|
||||
if (v) set.add(v)
|
||||
}
|
||||
|
||||
return Array.from(set)
|
||||
.sort((a, b) => a.localeCompare(b, 'tr'))
|
||||
.map(v => ({ label: v, value: v }))
|
||||
}
|
||||
|
||||
function normalizeText (str) {
|
||||
return String(str || '')
|
||||
.toLocaleLowerCase('tr-TR')
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.trim()
|
||||
}
|
||||
Reference in New Issue
Block a user