Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -22,7 +22,7 @@ UI_DIR=/opt/bssapp/ui/dist
|
|||||||
# DATABASES
|
# DATABASES
|
||||||
# ===============================
|
# ===============================
|
||||||
POSTGRES_CONN=host=46.224.33.150 port=5432 user=postgres password=tayitkan dbname=baggib2b sslmode=disable
|
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
|
# PDF
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package db
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -13,23 +12,24 @@ import (
|
|||||||
var MssqlDB *sql.DB
|
var MssqlDB *sql.DB
|
||||||
|
|
||||||
// ConnectMSSQL MSSQL baglantisini ortam degiskeninden baslatir.
|
// ConnectMSSQL MSSQL baglantisini ortam degiskeninden baslatir.
|
||||||
func ConnectMSSQL() {
|
func ConnectMSSQL() error {
|
||||||
connString := strings.TrimSpace(os.Getenv("MSSQL_CONN"))
|
connString := strings.TrimSpace(os.Getenv("MSSQL_CONN"))
|
||||||
if connString == "" {
|
if connString == "" {
|
||||||
log.Fatal("MSSQL_CONN tanımlı değil")
|
return fmt.Errorf("MSSQL_CONN tanımlı değil")
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
MssqlDB, err = sql.Open("sqlserver", connString)
|
MssqlDB, err = sql.Open("sqlserver", connString)
|
||||||
if err != nil {
|
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 {
|
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ı")
|
fmt.Println("MSSQL bağlantısı başarılı")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDB() *sql.DB {
|
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)),
|
wrapV3(routes.ExportStatementHeaderReportPDFHandler(mssql)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/finance/customer-balances", "GET",
|
||||||
|
"finance", "view",
|
||||||
|
wrapV3(http.HandlerFunc(routes.GetCustomerBalanceListHandler)),
|
||||||
|
)
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// REPORT (STATEMENTS)
|
// REPORT (STATEMENTS)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -576,6 +582,7 @@ func main() {
|
|||||||
// -------------------------------------------------------
|
// -------------------------------------------------------
|
||||||
// 🔑 ENV
|
// 🔑 ENV
|
||||||
// -------------------------------------------------------
|
// -------------------------------------------------------
|
||||||
|
// Önce .env + mail.env yükle. MSSQL başarısızsa .env.local dene.
|
||||||
if err := godotenv.Load(".env", "mail.env"); err != nil {
|
if err := godotenv.Load(".env", "mail.env"); err != nil {
|
||||||
log.Println("⚠️ .env / mail.env bulunamadı")
|
log.Println("⚠️ .env / mail.env bulunamadı")
|
||||||
}
|
}
|
||||||
@@ -589,7 +596,15 @@ func main() {
|
|||||||
// -------------------------------------------------------
|
// -------------------------------------------------------
|
||||||
// 🔗 DATABASE
|
// 🔗 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()
|
pgDB, err := db.ConnectPostgres()
|
||||||
if err != nil {
|
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',
|
label: 'Cari Ekstre',
|
||||||
to: '/app/statementofaccount',
|
to: '/app/statementofaccount',
|
||||||
permission: 'finance:view'
|
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"
|
: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
|
<q-btn
|
||||||
v-if="isEditing && canMutateRows"
|
v-if="isEditing && canMutateRows"
|
||||||
color="negative"
|
color="negative"
|
||||||
@@ -2831,6 +2838,76 @@ const onSaveOrUpdateRow = async () => {
|
|||||||
showEditor.value = false
|
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' }
|
meta: { permission: 'finance:view' }
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'customer-balance-list',
|
||||||
|
name: 'customer-balance-list',
|
||||||
|
component: () => import('pages/CustomerBalanceList.vue'),
|
||||||
|
meta: { permission: 'finance:view' }
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
/* ================= USERS ================= */
|
/* ================= 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