Compare commits

...

5 Commits

Author SHA1 Message Date
M_Kececi
1ced1b1649 Merge remote-tracking branch 'origin/master' 2026-02-19 12:28:13 +03:00
M_Kececi
76e7ca2e4a Merge remote-tracking branch 'origin/master' 2026-02-19 09:32:26 +03:00
M_Kececi
ed81fdf84f Merge remote-tracking branch 'origin/master' 2026-02-19 09:03:21 +03:00
M_Kececi
026c40c0b3 Merge remote-tracking branch 'origin/master' 2026-02-19 02:00:49 +03:00
M_Kececi
0136e6638b Merge remote-tracking branch 'origin/master' 2026-02-19 01:34:56 +03:00
59 changed files with 769 additions and 1062 deletions

33
svc/.env.local Normal file
View File

@@ -0,0 +1,33 @@
# ===============================
# SECURITY
# ===============================
JWT_SECRET=bssapp_super_secret_key_1234567890
PASSWORD_RESET_SECRET=1dc7d6d52fd0459a8b1f288a6590428e760f54339f8e47beb20db36b6df6070b
# ===============================
# URLS (PRODUCTION)
# ===============================
APP_FRONTEND_URL=http://46.224.33.150
API_URL=http://46.224.33.150
# Eğer Nginx ile /api varsa:
# API_URL=http://46.224.33.150/api
# ===============================
# UI
# ===============================
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
# ===============================
# PDF
# ===============================
PDF_FONT_DIR=/opt/bssapp/svc/fonts
API_HOST=0.0.0.0
API_PORT=8080

View File

@@ -12,12 +12,11 @@ import (
var MssqlDB *sql.DB var MssqlDB *sql.DB
// ConnectMSSQL MSSQL baglantisini ortam degiskeninden baslatir.
func ConnectMSSQL() { func ConnectMSSQL() {
connString := strings.TrimSpace(os.Getenv("MSSQL_CONN")) connString := strings.TrimSpace(os.Getenv("MSSQL_CONN"))
if connString == "" { if connString == "" {
// Fallback log.Fatal("MSSQL_CONN tanımlı değil")
connString = "server=100.127.186.137;user id=sa;password=Gi l_0150;port=1433;database=BAGGI_V3"
} }
var err error var err error
@@ -26,12 +25,11 @@ func ConnectMSSQL() {
log.Fatal("MSSQL bağlantı hatası:", err) log.Fatal("MSSQL bağlantı hatası:", err)
} }
err = MssqlDB.Ping() if err = MssqlDB.Ping(); err != nil {
if err != nil {
log.Fatal("MSSQL erişilemiyor:", err) log.Fatal("MSSQL erişilemiyor:", err)
} }
fmt.Println("MSSQL bağlantısı başarılı!") fmt.Println("MSSQL bağlantısı başarılı")
} }
func GetDB() *sql.DB { func GetDB() *sql.DB {

View File

@@ -13,14 +13,11 @@ import (
var PgDB *sql.DB var PgDB *sql.DB
// ConnectPostgres PostgreSQL veritabanına bağlanır // ConnectPostgres PostgreSQL veritabanına bağlanır.
func ConnectPostgres() (*sql.DB, error) { func ConnectPostgres() (*sql.DB, error) {
// Bağlantı stringi (istersen .envden oku) connStr := strings.TrimSpace(os.Getenv("POSTGRES_CONN"))
connStr := os.Getenv("POSTGRES_CONN")
if connStr == "" { if connStr == "" {
// fallback → sabit tanımlı bağlantı return nil, fmt.Errorf("POSTGRES_CONN tanımlı değil")
connStr = "host= 46.224.33.150 port=5432 user=postgres password=tayitkan dbname=baggib2b sslmode=disable"
//connStr = "host=172.16.0.3 port=5432 user=postgres password=tayitkan dbname=baggib2b sslmode=disable"
} }
db, err := sql.Open("postgres", connStr) db, err := sql.Open("postgres", connStr)
@@ -28,15 +25,13 @@ func ConnectPostgres() (*sql.DB, error) {
return nil, fmt.Errorf("PostgreSQL bağlantı hatası: %w", err) return nil, fmt.Errorf("PostgreSQL bağlantı hatası: %w", err)
} }
// ======================================================= // Bağlantı havuzu ayarları (audit log uyumlu).
// 🔹 BAĞLANTI HAVUZU (AUDIT LOG UYUMLU) db.SetMaxOpenConns(30)
// =======================================================
db.SetMaxOpenConns(30) // audit + api paralel çalışsın
db.SetMaxIdleConns(10) db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(30 * time.Minute) db.SetConnMaxLifetime(30 * time.Minute)
db.SetConnMaxIdleTime(5 * time.Minute) // 🔥 uzun idle audit bağlantılarını kapat db.SetConnMaxIdleTime(5 * time.Minute)
// 🔹 Test et // Bağlantıyı test et.
if err = db.Ping(); err != nil { if err = db.Ping(); err != nil {
// Some managed PostgreSQL servers require TLS. If the current DSN uses // Some managed PostgreSQL servers require TLS. If the current DSN uses
// sslmode=disable and server rejects with "no encryption", retry once // sslmode=disable and server rejects with "no encryption", retry once
@@ -45,7 +40,7 @@ func ConnectPostgres() (*sql.DB, error) {
strings.Contains(err.Error(), "no encryption") && strings.Contains(err.Error(), "no encryption") &&
strings.Contains(strings.ToLower(connStr), "sslmode=disable") { strings.Contains(strings.ToLower(connStr), "sslmode=disable") {
secureConnStr := strings.Replace(connStr, "sslmode=disable", "sslmode=require", 1) secureConnStr := strings.Replace(connStr, "sslmode=disable", "sslmode=require", 1)
log.Println("⚠️ PostgreSQL requires TLS, retrying with sslmode=require") log.Println("PostgreSQL TLS gerektiriyor, sslmode=require ile tekrar deneniyor")
_ = db.Close() _ = db.Close()
db, err = sql.Open("postgres", secureConnStr) db, err = sql.Open("postgres", secureConnStr)
@@ -66,13 +61,12 @@ func ConnectPostgres() (*sql.DB, error) {
} }
} }
log.Println("PostgreSQL bağlantısı başarılı!") log.Println("PostgreSQL bağlantısı başarılı")
PgDB = db PgDB = db
return db, nil return db, nil
} }
// GetPostgresUsers test amaçlı ilk 5 kullanıcıyı listeler // GetPostgresUsers test amaçlı ilk 5 kullanıcıyı listeler.
func GetPostgresUsers(db *sql.DB) error { func GetPostgresUsers(db *sql.DB) error {
query := `SELECT id, code, email FROM mk_dfusr ORDER BY id LIMIT 5` query := `SELECT id, code, email FROM mk_dfusr ORDER BY id LIMIT 5`
rows, err := db.Query(query) rows, err := db.Query(query)
@@ -81,14 +75,14 @@ func GetPostgresUsers(db *sql.DB) error {
} }
defer rows.Close() defer rows.Close()
fmt.Println("📋 İlk 5 PostgreSQL kullanıcısı:") fmt.Println("İlk 5 PostgreSQL kullanıcısı:")
for rows.Next() { for rows.Next() {
var id int var id int
var code, email string var code, email string
if err := rows.Scan(&id, &code, &email); err != nil { if err := rows.Scan(&id, &code, &email); err != nil {
return err return err
} }
fmt.Printf(" ID: %-4d | USER: %-20s | EMAIL: %s\n", id, code, email) fmt.Printf(" -> ID: %-4d | USER: %-20s | EMAIL: %s\n", id, code, email)
} }
return rows.Err() return rows.Err()

View File

@@ -212,19 +212,19 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
// ============================================================ // ============================================================
bindV3(r, pgDB, bindV3(r, pgDB,
"/api/password/change", "POST", "/api/password/change", "POST",
"system", "update", "auth", "update",
wrapV3(http.HandlerFunc(routes.FirstPasswordChangeHandler(pgDB))), wrapV3(http.HandlerFunc(routes.FirstPasswordChangeHandler(pgDB))),
) )
bindV3(r, pgDB, bindV3(r, pgDB,
"/api/activity-logs", "GET", "/api/activity-logs", "GET",
"user", "view", "system", "read",
wrapV3(routes.AdminActivityLogsHandler(pgDB)), wrapV3(routes.AdminActivityLogsHandler(pgDB)),
) )
bindV3(r, pgDB, bindV3(r, pgDB,
"/api/test-mail", "POST", "/api/test-mail", "POST",
"user", "insert", "system", "update",
wrapV3(routes.TestMailHandler(ml)), wrapV3(routes.TestMailHandler(ml)),
) )
@@ -235,12 +235,12 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
bindV3(r, pgDB, bindV3(r, pgDB,
rolePerm, "GET", rolePerm, "GET",
"user", "update", "system", "update",
wrapV3(routes.GetRolePermissionMatrix(pgDB)), wrapV3(routes.GetRolePermissionMatrix(pgDB)),
) )
bindV3(r, pgDB, bindV3(r, pgDB,
rolePerm, "POST", rolePerm, "POST",
"user", "update", "system", "update",
wrapV3(routes.SaveRolePermissionMatrix(pgDB)), wrapV3(routes.SaveRolePermissionMatrix(pgDB)),
) )
@@ -248,12 +248,12 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
bindV3(r, pgDB, bindV3(r, pgDB,
userPerm, "GET", userPerm, "GET",
"user", "update", "system", "update",
wrapV3(routes.GetUserPermissionsHandler(pgDB)), wrapV3(routes.GetUserPermissionsHandler(pgDB)),
) )
bindV3(r, pgDB, bindV3(r, pgDB,
userPerm, "POST", userPerm, "POST",
"user", "update", "system", "update",
wrapV3(routes.SaveUserPermissionsHandler(pgDB)), wrapV3(routes.SaveUserPermissionsHandler(pgDB)),
) )
@@ -286,17 +286,17 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
bindV3(r, pgDB, bindV3(r, pgDB,
"/api/role-dept-permissions/list", "GET", "/api/role-dept-permissions/list", "GET",
"user", "update", "system", "update",
wrapV3(http.HandlerFunc(rdHandler.List)), wrapV3(http.HandlerFunc(rdHandler.List)),
) )
bindV3(r, pgDB, bindV3(r, pgDB,
rdPerm, "GET", rdPerm, "GET",
"user", "update", "system", "update",
wrapV3(http.HandlerFunc(rdHandler.Get)), wrapV3(http.HandlerFunc(rdHandler.Get)),
) )
bindV3(r, pgDB, bindV3(r, pgDB,
rdPerm, "POST", rdPerm, "POST",
"user", "update", "system", "update",
wrapV3(http.HandlerFunc(rdHandler.Save)), wrapV3(http.HandlerFunc(rdHandler.Save)),
) )

View File

@@ -10,6 +10,7 @@ import (
// Ana tabloyu getiren fonksiyon (Vue header tablosu için) // Ana tabloyu getiren fonksiyon (Vue header tablosu için)
func GetStatements(params models.StatementParams) ([]models.StatementHeader, error) { func GetStatements(params models.StatementParams) ([]models.StatementHeader, error) {
// AccountCode normalize: "ZLA0127" → "ZLA 0127" // AccountCode normalize: "ZLA0127" → "ZLA 0127"
if len(params.AccountCode) == 7 && strings.ContainsAny(params.AccountCode, "0123456789") { if len(params.AccountCode) == 7 && strings.ContainsAny(params.AccountCode, "0123456789") {
params.AccountCode = params.AccountCode[:3] + " " + params.AccountCode[3:] params.AccountCode = params.AccountCode[:3] + " " + params.AccountCode[3:]
@@ -27,7 +28,6 @@ func GetStatements(params models.StatementParams) ([]models.StatementHeader, err
if v == "" { if v == "" {
continue continue
} }
// Escape tek tırnak to avoid malformed SQL when list is injected into IN (...).
quoted = append(quoted, fmt.Sprintf("'%s'", strings.ReplaceAll(v, "'", "''"))) quoted = append(quoted, fmt.Sprintf("'%s'", strings.ReplaceAll(v, "'", "''")))
} }
if len(quoted) > 0 { if len(quoted) > 0 {
@@ -36,7 +36,6 @@ func GetStatements(params models.StatementParams) ([]models.StatementHeader, err
} }
query := fmt.Sprintf(` query := fmt.Sprintf(`
;WITH CurrDesc AS ( ;WITH CurrDesc AS (
SELECT SELECT
CurrAccCode, CurrAccCode,
@@ -45,44 +44,68 @@ func GetStatements(params models.StatementParams) ([]models.StatementHeader, err
WHERE LangCode = @LangCode WHERE LangCode = @LangCode
GROUP BY CurrAccCode GROUP BY CurrAccCode
), ),
/* =========================================================
✅ Bu aralıkta hareket var mı?
Varsa : Devir = startdate öncesi
Yoksa : Devir = enddate dahil (enddate itibariyle bakiye)
========================================================= */
HasMovement AS (
SELECT
CASE WHEN EXISTS (
SELECT 1
FROM trCurrAccBook b
INNER JOIN CurrAccBookATAttributesFilter f
ON f.CurrAccBookID = b.CurrAccBookID
AND f.ATAtt01 IN (%s)
WHERE b.CurrAccCode LIKE '%%' + @Carikod + '%%'
AND b.DocumentDate BETWEEN @startdate AND @enddate
) THEN 1 ELSE 0 END AS HasMov
),
/* =========================================================
✅ Opening (Devir) — TEK CARİ KOD ALTINDA KONSOLİDE
Cari_Kod = @Carikod (sabit)
========================================================= */
Opening AS ( Opening AS (
SELECT SELECT
b.CurrAccCode AS Cari_Kod, @Carikod AS Cari_Kod,
b.DocCurrencyCode AS Para_Birimi, b.DocCurrencyCode AS Para_Birimi,
SUM(c.Debit - c.Credit) AS Devir_Bakiyesi SUM(ISNULL(c.Debit,0) - ISNULL(c.Credit,0)) AS Devir_Bakiyesi
FROM trCurrAccBook b FROM trCurrAccBook b
CROSS JOIN HasMovement hm
INNER JOIN CurrAccBookATAttributesFilter f2
ON f2.CurrAccBookID = b.CurrAccBookID
AND f2.ATAtt01 IN (%s)
LEFT JOIN trCurrAccBookCurrency c LEFT JOIN trCurrAccBookCurrency c
ON c.CurrAccBookID = b.CurrAccBookID ON c.CurrAccBookID = b.CurrAccBookID
AND c.CurrencyCode = b.DocCurrencyCode AND c.CurrencyCode = b.DocCurrencyCode
WHERE b.CurrAccCode LIKE '%%' + @Carikod + '%%' WHERE b.CurrAccCode LIKE '%%' + @Carikod + '%%'
AND b.DocumentDate < @startdate AND (
(hm.HasMov = 1 AND b.DocumentDate < @startdate) -- hareket varsa: klasik devir
AND EXISTS ( OR (hm.HasMov = 0 AND b.DocumentDate <= @enddate) -- hareket yoksa: enddate itibariyle bakiye
SELECT 1
FROM CurrAccBookATAttributesFilter f2
WHERE f2.CurrAccBookID = b.CurrAccBookID
AND f2.ATAtt01 IN (%s)
) )
GROUP BY b.DocCurrencyCode
GROUP BY
b.CurrAccCode,
b.DocCurrencyCode
),
OpeningByCurrency AS (
SELECT
Para_Birimi,
SUM(Devir_Bakiyesi) AS Devir_Bakiyesi
FROM Opening
GROUP BY Para_Birimi
), ),
/* =========================================================
✅ Hareketler (Movements) — TEK CARİ KOD ALTINDA KONSOLİDE
Cari_Kod = @Carikod (sabit)
Running sadece aralıktaki hareketlerden gelir.
========================================================= */
Movements AS ( Movements AS (
SELECT SELECT
b.CurrAccCode AS Cari_Kod, @Carikod AS Cari_Kod,
d.CurrAccDescription AS Cari_Isim,
COALESCE(
(SELECT TOP 1 cd.CurrAccDescription
FROM CurrDesc cd
WHERE cd.CurrAccCode = @Carikod),
(SELECT TOP 1 cd.CurrAccDescription
FROM CurrDesc cd
WHERE cd.CurrAccCode LIKE '%%' + @Carikod + '%%'
ORDER BY cd.CurrAccCode)
) AS Cari_Isim,
CONVERT(varchar(10), b.DocumentDate, 23) AS Belge_Tarihi, CONVERT(varchar(10), b.DocumentDate, 23) AS Belge_Tarihi,
CONVERT(varchar(10), b.DueDate, 23) AS Vade_Tarihi, CONVERT(varchar(10), b.DueDate, 23) AS Vade_Tarihi,
@@ -93,38 +116,27 @@ Movements AS (
b.DocCurrencyCode AS Para_Birimi, b.DocCurrencyCode AS Para_Birimi,
c.Debit AS Borc, ISNULL(c.Debit,0) AS Borc,
c.Credit AS Alacak, ISNULL(c.Credit,0) AS Alacak,
SUM(c.Debit - c.Credit) SUM(ISNULL(c.Debit,0) - ISNULL(c.Credit,0))
OVER ( OVER (
PARTITION BY b.CurrAccCode, c.CurrencyCode PARTITION BY b.DocCurrencyCode
ORDER BY b.DocumentDate, b.CurrAccBookID ORDER BY b.DocumentDate, b.CurrAccBookID
) AS Hareket_Bakiyesi, ) AS Hareket_Bakiyesi,
f.ATAtt01 AS Parislemtipi f.ATAtt01 AS Parislemtipi
FROM trCurrAccBook b FROM trCurrAccBook b
INNER JOIN CurrAccBookATAttributesFilter f
LEFT JOIN CurrDesc d ON f.CurrAccBookID = b.CurrAccBookID
ON b.CurrAccCode = d.CurrAccCode AND f.ATAtt01 IN (%s)
LEFT JOIN trCurrAccBookCurrency c LEFT JOIN trCurrAccBookCurrency c
ON b.CurrAccBookID = c.CurrAccBookID ON c.CurrAccBookID = b.CurrAccBookID
AND b.DocCurrencyCode = c.CurrencyCode AND c.CurrencyCode = b.DocCurrencyCode
LEFT JOIN CurrAccBookATAttributesFilter f
ON b.CurrAccBookID = f.CurrAccBookID
WHERE b.CurrAccCode LIKE '%%' + @Carikod + '%%' WHERE b.CurrAccCode LIKE '%%' + @Carikod + '%%'
AND b.DocumentDate BETWEEN @startdate AND @enddate AND b.DocumentDate BETWEEN @startdate AND @enddate
AND EXISTS (
SELECT 1
FROM CurrAccBookATAttributesFilter f2
WHERE f2.CurrAccBookID = b.CurrAccBookID
AND f2.ATAtt01 IN (%s)
)
) )
SELECT SELECT
@@ -136,27 +148,28 @@ SELECT
m.Islem_Tipi, m.Islem_Tipi,
m.Aciklama, m.Aciklama,
m.Para_Birimi, m.Para_Birimi,
m.Borc, m.Borc,
m.Alacak, m.Alacak,
/* ✅ Bakiye = Devir + Aralıktaki Running */
ISNULL(o.Devir_Bakiyesi,0) + m.Hareket_Bakiyesi AS Bakiye, ISNULL(o.Devir_Bakiyesi,0) + m.Hareket_Bakiyesi AS Bakiye,
m.Parislemtipi AS Parislemler m.Parislemtipi AS Parislemler
FROM Movements m FROM Movements m
LEFT JOIN Opening o LEFT JOIN Opening o
ON o.Cari_Kod = m.Cari_Kod ON o.Cari_Kod = m.Cari_Kod
AND o.Para_Birimi = m.Para_Birimi AND o.Para_Birimi = m.Para_Birimi
UNION ALL UNION ALL
/* =========================================================
/* ✅ Devir satırı sadece Openingden */ ✅ Devir Satırı (kur bazında) — Opening'den gelir
Hareket varsa: startdate öncesi
Hareket yoksa: enddate itibariyle bakiye
========================================================= */
SELECT SELECT
@Carikod AS Cari_Kod, o.Cari_Kod,
COALESCE( COALESCE(
(SELECT TOP 1 cd.CurrAccDescription (SELECT TOP 1 cd.CurrAccDescription
FROM CurrDesc cd FROM CurrDesc cd
@@ -167,38 +180,31 @@ SELECT
ORDER BY cd.CurrAccCode) ORDER BY cd.CurrAccCode)
) AS Cari_Isim, ) AS Cari_Isim,
CONVERT(varchar(10), @startdate, 23), CONVERT(varchar(10), @startdate, 23) AS Belge_Tarihi,
CONVERT(varchar(10), @startdate, 23), CONVERT(varchar(10), @startdate, 23) AS Vade_Tarihi,
'Baslangic_devir', 'Baslangic_devir' AS Belge_No,
'Devir', 'Devir' AS Islem_Tipi,
'Devir Bakiyesi', 'Devir Bakiyesi' AS Aciklama,
obc.Para_Birimi, o.Para_Birimi,
CASE CASE WHEN o.Devir_Bakiyesi >= 0 THEN o.Devir_Bakiyesi ELSE 0 END AS Borc,
WHEN obc.Devir_Bakiyesi >= 0 THEN obc.Devir_Bakiyesi CASE WHEN o.Devir_Bakiyesi < 0 THEN ABS(o.Devir_Bakiyesi) ELSE 0 END AS Alacak,
ELSE 0
END,
CASE o.Devir_Bakiyesi AS Bakiye,
WHEN obc.Devir_Bakiyesi < 0 THEN ABS(obc.Devir_Bakiyesi)
ELSE 0
END,
obc.Devir_Bakiyesi,
CAST(NULL AS varchar(32)) AS Parislemler CAST(NULL AS varchar(32)) AS Parislemler
FROM OpeningByCurrency obc FROM Opening o
ORDER BY ORDER BY
Para_Birimi, Para_Birimi,
Belge_Tarihi; Belge_Tarihi;
`, `,
parislemFilter, parislemFilter, // HasMovement
parislemFilter, parislemFilter, // Opening
parislemFilter, // Movements
) )
rows, err := db.MssqlDB.Query(query, rows, err := db.MssqlDB.Query(query,

View File

@@ -1,187 +1,18 @@
// queries/statements_header_pdf.go
package queries package queries
import ( import (
"bssapp-backend/db"
"bssapp-backend/models" "bssapp-backend/models"
"database/sql"
"fmt"
"log" "log"
"strings"
) )
// küçük yardımcı: boşlukları temizle, her değeri ayrı tırnakla sar
func buildQuotedHList(vals []string) string {
var pp []string
for _, v := range vals {
v = strings.TrimSpace(v)
if v != "" {
pp = append(pp, fmt.Sprintf("'%s'", v)) // '1','2' gibi
}
}
if len(pp) == 0 {
return ""
}
return strings.Join(pp, ",")
}
/* ============================ HEADER (Ana Tablo) ============================ */
func GetStatementsHPDF(accountCode, startDate, endDate string, parislemler []string) ([]models.StatementHeader, []string, error) { func GetStatementsHPDF(accountCode, startDate, endDate string, parislemler []string) ([]models.StatementHeader, []string, error) {
// Account normalize headers, err := getStatementsForPDF(accountCode, startDate, endDate, parislemler)
if len(accountCode) == 7 && strings.ContainsAny(accountCode, "0123456789") {
accountCode = accountCode[:3] + " " + accountCode[3:]
}
// IN list parse et
inList := buildQuotedHList(parislemler)
parislemCond := "''"
if inList != "" {
parislemCond = inList
}
query := fmt.Sprintf(`
;WITH Opening AS (
SELECT
b.CurrAccCode AS Cari_Kod,
b.DocCurrencyCode AS Para_Birimi,
SUM(c.Debit - c.Credit) AS Devir_Bakiyesi
FROM trCurrAccBook b
LEFT JOIN trCurrAccBookCurrency c
ON c.CurrAccBookID = b.CurrAccBookID
AND c.CurrencyCode = b.DocCurrencyCode
WHERE b.CurrAccCode LIKE @Carikod
AND b.DocumentDate < @StartDate
AND EXISTS (
SELECT 1
FROM CurrAccBookATAttributesFilter f2
WHERE f2.CurrAccBookID = b.CurrAccBookID
AND f2.ATAtt01 IN (%s)
)
GROUP BY b.CurrAccCode, b.DocCurrencyCode
),
Movements AS (
SELECT
b.CurrAccCode AS Cari_Kod,
d.CurrAccDescription AS Cari_Isim,
CONVERT(varchar(10), b.DocumentDate, 23) AS Belge_Tarihi,
CONVERT(varchar(10), b.DueDate, 23) AS Vade_Tarihi,
b.RefNumber AS Belge_No,
b.BaseApplicationCode AS Islem_Tipi,
b.LineDescription AS Aciklama,
b.DocCurrencyCode AS Para_Birimi,
c.Debit AS Borc,
c.Credit AS Alacak,
SUM(c.Debit - c.Credit)
OVER (PARTITION BY b.CurrAccCode, c.CurrencyCode
ORDER BY b.DocumentDate, b.CurrAccBookID) AS Hareket_Bakiyesi,
f.ATAtt01 AS Parislemler
FROM trCurrAccBook b
LEFT JOIN cdCurrAccDesc d
ON b.CurrAccCode = d.CurrAccCode AND d.LangCode = 'TR'
LEFT JOIN trCurrAccBookCurrency c
ON b.CurrAccBookID = c.CurrAccBookID
AND b.DocCurrencyCode = c.CurrencyCode
LEFT JOIN CurrAccBookATAttributesFilter f
ON b.CurrAccBookID = f.CurrAccBookID
WHERE b.CurrAccCode LIKE @Carikod
AND b.DocumentDate BETWEEN @StartDate AND @EndDate
AND EXISTS (
SELECT 1
FROM CurrAccBookATAttributesFilter f2
WHERE f2.CurrAccBookID = b.CurrAccBookID
AND f2.ATAtt01 IN (%s)
)
)`, parislemCond, parislemCond)
query += fmt.Sprintf(`
SELECT
m.Cari_Kod,
m.Cari_Isim,
m.Belge_Tarihi,
m.Vade_Tarihi,
m.Belge_No,
m.Islem_Tipi,
m.Aciklama,
m.Para_Birimi,
m.Borc,
m.Alacak,
ISNULL(o.Devir_Bakiyesi,0) + m.Hareket_Bakiyesi AS Bakiye,
m.Parislemler
FROM Movements m
LEFT JOIN Opening o
ON o.Cari_Kod = m.Cari_Kod
AND o.Para_Birimi = m.Para_Birimi
UNION ALL
-- Devir satırı
SELECT
@Carikod AS Cari_Kod,
MAX(d.CurrAccDescription) AS Cari_Isim,
CONVERT(varchar(10), @StartDate, 23) AS Belge_Tarihi,
CONVERT(varchar(10), @StartDate, 23) AS Vade_Tarihi,
'Baslangic_devir' AS Belge_No,
'Devir' AS Islem_Tipi,
'Devir Bakiyesi' AS Aciklama,
b.DocCurrencyCode AS Para_Birimi,
SUM(c.Debit) AS Borc,
SUM(c.Credit) AS Alacak,
SUM(c.Debit) - SUM(c.Credit) AS Bakiye,
(
SELECT STRING_AGG(x.ATAtt01, ',')
FROM (
SELECT DISTINCT f2.ATAtt01
FROM CurrAccBookATAttributesFilter f2
INNER JOIN trCurrAccBook bb
ON f2.CurrAccBookID = bb.CurrAccBookID
WHERE bb.CurrAccCode LIKE @Carikod
AND bb.DocumentDate < @StartDate
AND f2.ATAtt01 IN (%s)
) x
) AS Parislemler
FROM trCurrAccBook b
LEFT JOIN cdCurrAccDesc d
ON b.CurrAccCode = d.CurrAccCode AND d.LangCode = 'TR'
LEFT JOIN trCurrAccBookCurrency c
ON b.CurrAccBookID = c.CurrAccBookID
AND b.DocCurrencyCode = c.CurrencyCode
WHERE b.CurrAccCode LIKE @Carikod
AND b.DocumentDate < @StartDate
GROUP BY b.DocCurrencyCode
ORDER BY Para_Birimi, Belge_Tarihi;`, parislemCond)
rows, err := db.MssqlDB.Query(query,
sql.Named("Carikod", "%"+accountCode+"%"),
sql.Named("StartDate", startDate),
sql.Named("EndDate", endDate),
)
if err != nil { if err != nil {
log.Printf("Header sorgu hatası: %v", err) log.Printf("Header query error: %v", err)
return nil, nil, fmt.Errorf("header sorgu hatası: %v", err)
}
defer rows.Close()
var headers []models.StatementHeader
var belgeNos []string
for rows.Next() {
var h models.StatementHeader
if err := rows.Scan(
&h.CariKod, &h.CariIsim,
&h.BelgeTarihi, &h.VadeTarihi,
&h.BelgeNo, &h.IslemTipi,
&h.Aciklama, &h.ParaBirimi,
&h.Borc, &h.Alacak,
&h.Bakiye, &h.Parislemler,
); err != nil {
log.Printf("❌ Header scan hatası: %v", err)
return nil, nil, err return nil, nil, err
} }
headers = append(headers, h)
if h.BelgeNo != "" { belgeNos := collectBelgeNos(headers)
belgeNos = append(belgeNos, h.BelgeNo) log.Printf("Header rows fetched: %d, belge no count: %d", len(headers), len(belgeNos))
}
}
log.Printf("✅ Header verileri alındı: %d kayıt, %d belge no", len(headers), len(belgeNos))
return headers, belgeNos, nil return headers, belgeNos, nil
} }

View File

@@ -0,0 +1,35 @@
package queries
import "bssapp-backend/models"
func getStatementsForPDF(
accountCode string,
startDate string,
endDate string,
parislemler []string,
) ([]models.StatementHeader, error) {
return GetStatements(models.StatementParams{
AccountCode: accountCode,
StartDate: startDate,
EndDate: endDate,
LangCode: "TR",
Parislemler: parislemler,
})
}
func collectBelgeNos(headers []models.StatementHeader) []string {
seen := make(map[string]struct{}, len(headers))
out := make([]string, 0, len(headers))
for _, h := range headers {
no := h.BelgeNo
if no == "" || no == "Baslangic_devir" {
continue
}
if _, ok := seen[no]; ok {
continue
}
seen[no] = struct{}{}
out = append(out, no)
}
return out
}

View File

@@ -10,179 +10,15 @@ import (
"strings" "strings"
) )
// küçük yardımcı: boşlukları temizle, her değeri ayrı tırnakla sar
func buildQuotedList(vals []string) string {
var pp []string
for _, v := range vals {
v = strings.TrimSpace(v)
if v != "" {
pp = append(pp, fmt.Sprintf("'%s'", v)) // '1','2' gibi
}
}
if len(pp) == 0 {
return ""
}
return strings.Join(pp, ",")
}
/* ============================ HEADER (Ana Tablo) ============================ */
func GetStatementsPDF(accountCode, startDate, endDate string, parislemler []string) ([]models.StatementHeader, []string, error) { func GetStatementsPDF(accountCode, startDate, endDate string, parislemler []string) ([]models.StatementHeader, []string, error) {
// Account normalize headers, err := getStatementsForPDF(accountCode, startDate, endDate, parislemler)
if len(accountCode) == 7 && strings.ContainsAny(accountCode, "0123456789") {
accountCode = accountCode[:3] + " " + accountCode[3:]
}
// IN list parse et
inList := buildQuotedList(parislemler)
parislemCond := "''"
if inList != "" {
parislemCond = inList
}
query := fmt.Sprintf(`
;WITH Opening AS (
SELECT
b.CurrAccCode AS Cari_Kod,
b.DocCurrencyCode AS Para_Birimi,
SUM(c.Debit - c.Credit) AS Devir_Bakiyesi
FROM trCurrAccBook b
LEFT JOIN trCurrAccBookCurrency c
ON c.CurrAccBookID = b.CurrAccBookID
AND c.CurrencyCode = b.DocCurrencyCode
WHERE b.CurrAccCode LIKE @Carikod
AND b.DocumentDate < @StartDate
AND EXISTS (
SELECT 1
FROM CurrAccBookATAttributesFilter f2
WHERE f2.CurrAccBookID = b.CurrAccBookID
AND f2.ATAtt01 IN (%s)
)
GROUP BY b.CurrAccCode, b.DocCurrencyCode
),
Movements AS (
SELECT
b.CurrAccCode AS Cari_Kod,
d.CurrAccDescription AS Cari_Isim,
CONVERT(varchar(10), b.DocumentDate, 23) AS Belge_Tarihi,
CONVERT(varchar(10), b.DueDate, 23) AS Vade_Tarihi,
b.RefNumber AS Belge_No,
b.BaseApplicationCode AS Islem_Tipi,
b.LineDescription AS Aciklama,
b.DocCurrencyCode AS Para_Birimi,
c.Debit AS Borc,
c.Credit AS Alacak,
SUM(c.Debit - c.Credit)
OVER (PARTITION BY b.CurrAccCode, c.CurrencyCode
ORDER BY b.DocumentDate, b.CurrAccBookID) AS Hareket_Bakiyesi,
f.ATAtt01 AS Parislemler
FROM trCurrAccBook b
LEFT JOIN cdCurrAccDesc d
ON b.CurrAccCode = d.CurrAccCode AND d.LangCode = 'TR'
LEFT JOIN trCurrAccBookCurrency c
ON b.CurrAccBookID = c.CurrAccBookID
AND b.DocCurrencyCode = c.CurrencyCode
LEFT JOIN CurrAccBookATAttributesFilter f
ON b.CurrAccBookID = f.CurrAccBookID
WHERE b.CurrAccCode LIKE @Carikod
AND b.DocumentDate BETWEEN @StartDate AND @EndDate
AND EXISTS (
SELECT 1
FROM CurrAccBookATAttributesFilter f2
WHERE f2.CurrAccBookID = b.CurrAccBookID
AND f2.ATAtt01 IN (%s)
)
)`, parislemCond, parislemCond)
query += fmt.Sprintf(`
SELECT
m.Cari_Kod,
m.Cari_Isim,
m.Belge_Tarihi,
m.Vade_Tarihi,
m.Belge_No,
m.Islem_Tipi,
m.Aciklama,
m.Para_Birimi,
m.Borc,
m.Alacak,
ISNULL(o.Devir_Bakiyesi,0) + m.Hareket_Bakiyesi AS Bakiye,
m.Parislemler
FROM Movements m
LEFT JOIN Opening o
ON o.Cari_Kod = m.Cari_Kod
AND o.Para_Birimi = m.Para_Birimi
UNION ALL
-- Devir satırı
SELECT
@Carikod AS Cari_Kod,
MAX(d.CurrAccDescription) AS Cari_Isim,
CONVERT(varchar(10), @StartDate, 23) AS Belge_Tarihi,
CONVERT(varchar(10), @StartDate, 23) AS Vade_Tarihi,
'Baslangic_devir' AS Belge_No,
'Devir' AS Islem_Tipi,
'Devir Bakiyesi' AS Aciklama,
b.DocCurrencyCode AS Para_Birimi,
SUM(c.Debit) AS Borc,
SUM(c.Credit) AS Alacak,
SUM(c.Debit) - SUM(c.Credit) AS Bakiye,
(
SELECT STRING_AGG(x.ATAtt01, ',')
FROM (
SELECT DISTINCT f2.ATAtt01
FROM CurrAccBookATAttributesFilter f2
INNER JOIN trCurrAccBook bb
ON f2.CurrAccBookID = bb.CurrAccBookID
WHERE bb.CurrAccCode LIKE @Carikod
AND bb.DocumentDate < @StartDate
AND f2.ATAtt01 IN (%s)
) x
) AS Parislemler
FROM trCurrAccBook b
LEFT JOIN cdCurrAccDesc d
ON b.CurrAccCode = d.CurrAccCode AND d.LangCode = 'TR'
LEFT JOIN trCurrAccBookCurrency c
ON b.CurrAccBookID = c.CurrAccBookID
AND b.DocCurrencyCode = c.CurrencyCode
WHERE b.CurrAccCode LIKE @Carikod
AND b.DocumentDate < @StartDate
GROUP BY b.DocCurrencyCode
ORDER BY Para_Birimi, Belge_Tarihi;`, parislemCond)
rows, err := db.MssqlDB.Query(query,
sql.Named("Carikod", "%"+accountCode+"%"),
sql.Named("StartDate", startDate),
sql.Named("EndDate", endDate),
)
if err != nil { if err != nil {
log.Printf("Header sorgu hatası: %v", err) log.Printf("Header query error: %v", err)
return nil, nil, fmt.Errorf("header sorgu hatası: %v", err)
}
defer rows.Close()
var headers []models.StatementHeader
var belgeNos []string
for rows.Next() {
var h models.StatementHeader
if err := rows.Scan(
&h.CariKod, &h.CariIsim,
&h.BelgeTarihi, &h.VadeTarihi,
&h.BelgeNo, &h.IslemTipi,
&h.Aciklama, &h.ParaBirimi,
&h.Borc, &h.Alacak,
&h.Bakiye, &h.Parislemler,
); err != nil {
log.Printf("❌ Header scan hatası: %v", err)
return nil, nil, err return nil, nil, err
} }
headers = append(headers, h)
if h.BelgeNo != "" { belgeNos := collectBelgeNos(headers)
belgeNos = append(belgeNos, h.BelgeNo) log.Printf("Header rows fetched: %d, belge no count: %d", len(headers), len(belgeNos))
}
}
log.Printf("✅ Header verileri alındı: %d kayıt, %d belge no", len(headers), len(belgeNos))
return headers, belgeNos, nil return headers, belgeNos, nil
} }
@@ -191,7 +27,7 @@ ORDER BY Para_Birimi, Belge_Tarihi;`, parislemCond)
func GetDetailsMapPDF(belgeNos []string, startDate, endDate string) (map[string][]models.StatementDetail, error) { func GetDetailsMapPDF(belgeNos []string, startDate, endDate string) (map[string][]models.StatementDetail, error) {
result := make(map[string][]models.StatementDetail) result := make(map[string][]models.StatementDetail)
if len(belgeNos) == 0 { if len(belgeNos) == 0 {
log.Println("⚠️ GetDetailsMapPDF: belge listesi boş") log.Println("GetDetailsMapPDF: belge listesi bos")
return result, nil return result, nil
} }
@@ -219,7 +55,11 @@ SELECT
MAX(ISNULL(KisaKarDesc.AttributeDescription, '')) AS Icerik, MAX(ISNULL(KisaKarDesc.AttributeDescription, '')) AS Icerik,
a.ItemCode, a.ColorCode, a.ItemCode, a.ColorCode,
SUM(a.Qty1), SUM(ABS(a.Doc_Price)), SUM(a.Qty1),
CAST(
SUM(a.Qty1 * ABS(a.Doc_Price))
/ NULLIF(SUM(a.Qty1), 0)
AS numeric(18,4)),
CAST(SUM(a.Qty1 * ABS(a.Doc_Price)) AS numeric(18,2)) CAST(SUM(a.Qty1 * ABS(a.Doc_Price)) AS numeric(18,2))
FROM AllInvoicesWithAttributes a FROM AllInvoicesWithAttributes a
@@ -258,7 +98,7 @@ LEFT JOIN cdItemAttributeDesc FitDesc
AND FitTbl.AttributeCode = FitDesc.AttributeCode AND FitTbl.AttributeCode = FitDesc.AttributeCode
AND FitTbl.ItemTypeCode = FitDesc.ItemTypeCode AND FitTbl.ItemTypeCode = FitDesc.ItemTypeCode
-- Kısa Karışım -- Kisa Karisim
LEFT JOIN prItemAttribute KisaKar LEFT JOIN prItemAttribute KisaKar
ON a.ItemCode = KisaKar.ItemCode AND KisaKar.AttributeTypeCode = 41 ON a.ItemCode = KisaKar.ItemCode AND KisaKar.AttributeTypeCode = 41
LEFT JOIN cdItemAttributeDesc KisaKarDesc LEFT JOIN cdItemAttributeDesc KisaKarDesc
@@ -274,8 +114,8 @@ ORDER BY a.InvoiceNumber, a.ItemCode, a.ColorCode;`, inBelge)
sql.Named("EndDate", endDate), sql.Named("EndDate", endDate),
) )
if err != nil { if err != nil {
log.Printf("Detay sorgu hatası: %v", err) log.Printf("Detail query error: %v", err)
return nil, fmt.Errorf("detay sorgu hatası: %v", err) return nil, fmt.Errorf("detay sorgu hatasi: %v", err)
} }
defer rows.Close() defer rows.Close()
@@ -295,11 +135,11 @@ ORDER BY a.InvoiceNumber, a.ItemCode, a.ColorCode;`, inBelge)
&d.ToplamFiyat, &d.ToplamFiyat,
&d.ToplamTutar, &d.ToplamTutar,
); err != nil { ); err != nil {
log.Printf("Detay scan hatası: %v", err) log.Printf("Detail scan error: %v", err)
return nil, err return nil, err
} }
result[d.BelgeRefNumarasi] = append(result[d.BelgeRefNumarasi], d) result[d.BelgeRefNumarasi] = append(result[d.BelgeRefNumarasi], d)
} }
log.Printf("Detay verileri alındı: %d belge için detay var", len(result)) log.Printf("Detail rows fetched for %d belge", len(result))
return result, nil return result, nil
} }

View File

@@ -234,11 +234,18 @@ func (r *UserRepository) GetLegacyUserForLogin(login string) (*models.User, erro
COALESCE(u.upass,'') as upass, COALESCE(u.upass,'') as upass,
u.is_active, u.is_active,
COALESCE(u.email,''), COALESCE(u.email,''),
COALESCE(u.dfrole_id,0) as role_id, COALESCE(ru.dfrole_id,0) as role_id,
COALESCE(dr.code,'') as role_code, COALESCE(dr.code,'') as role_code,
COALESCE(u.force_password_change,false) COALESCE(u.force_password_change,false)
FROM dfusr u FROM dfusr u
LEFT JOIN dfrole dr ON dr.id = u.dfrole_id LEFT JOIN LATERAL (
SELECT dfrole_id
FROM dfrole_usr
WHERE dfusr_id = u.id
ORDER BY dfrole_id
LIMIT 1
) ru ON true
LEFT JOIN dfrole dr ON dr.id = ru.dfrole_id
WHERE u.is_active = true WHERE u.is_active = true
AND ( AND (
LOWER(u.code) = LOWER($1) LOWER(u.code) = LOWER($1)

View File

@@ -62,7 +62,7 @@ func AdminResetPasswordHandler(db *sql.DB) http.HandlerFunc {
// --------------------------------------------------- // ---------------------------------------------------
// 4⃣ UPDATE mk_dfusr // 4⃣ UPDATE mk_dfusr
// --------------------------------------------------- // ---------------------------------------------------
_, err = db.Exec(` res, err := db.Exec(`
UPDATE mk_dfusr UPDATE mk_dfusr
SET SET
password_hash = $1, password_hash = $1,
@@ -77,6 +77,24 @@ func AdminResetPasswordHandler(db *sql.DB) http.HandlerFunc {
return return
} }
affected, _ := res.RowsAffected()
if affected == 0 {
_, err = db.Exec(`
UPDATE dfusr
SET
upass = $1,
force_password_change = true,
last_updated_date = NOW()
WHERE id = $2
AND is_active = true
`, string(hash), userID)
if err != nil {
http.Error(w, "legacy password reset failed", http.StatusInternalServerError)
return
}
}
// --------------------------------------------------- // ---------------------------------------------------
// 5⃣ REFRESH TOKEN REVOKE // 5⃣ REFRESH TOKEN REVOKE
// --------------------------------------------------- // ---------------------------------------------------

View File

@@ -30,6 +30,12 @@ type LoginRequest struct {
Password string `json:"password"` Password string `json:"password"`
} }
func looksLikeBcryptHash(value string) bool {
return strings.HasPrefix(value, "$2a$") ||
strings.HasPrefix(value, "$2b$") ||
strings.HasPrefix(value, "$2y$")
}
func ensureLegacyUserReadyForSession(db *sql.DB, legacyUser *models.User) (int64, error) { func ensureLegacyUserReadyForSession(db *sql.DB, legacyUser *models.User) (int64, error) {
desiredID := int64(legacyUser.ID) desiredID := int64(legacyUser.ID)
@@ -148,20 +154,37 @@ func LoginHandler(db *sql.DB) http.HandlerFunc {
if err == nil { if err == nil {
// mk_dfusr authoritative // mk_dfusr authoritative
if strings.TrimSpace(mkUser.PasswordHash) != "" { mkHash := strings.TrimSpace(mkUser.PasswordHash)
if mkHash != "" {
if bcrypt.CompareHashAndPassword( if looksLikeBcryptHash(mkHash) {
[]byte(mkUser.PasswordHash), cmpErr := bcrypt.CompareHashAndPassword(
[]byte(mkHash),
[]byte(pass), []byte(pass),
) != nil { )
http.Error(w, "Kullanıcı adı veya parola hatalı", http.StatusUnauthorized) if cmpErr == nil {
return
}
_ = mkRepo.TouchLastLogin(mkUser.ID) _ = mkRepo.TouchLastLogin(mkUser.ID)
writeLoginResponse(w, db, mkUser) writeLoginResponse(w, db, mkUser)
return return
} }
if !mkUser.ForcePasswordChange {
http.Error(w, "invalid credentials", http.StatusUnauthorized)
return
}
log.Printf(
"LOGIN FALLBACK legacy allowed (force_password_change=true) username=%s id=%d",
mkUser.Username,
mkUser.ID,
)
} else {
log.Printf(
"LOGIN FALLBACK legacy allowed (non-bcrypt mk hash) username=%s id=%d",
mkUser.Username,
mkUser.ID,
)
}
}
// password_hash boşsa legacy fallback // password_hash boşsa legacy fallback
} else if err != repository.ErrMkUserNotFound { } else if err != repository.ErrMkUserNotFound {
log.Println("❌ mk_dfusr lookup error:", err) log.Println("❌ mk_dfusr lookup error:", err)

View File

@@ -14,6 +14,7 @@ import (
"strings" "strings"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/lib/pq"
) )
type IdTitleOption struct { type IdTitleOption struct {
@@ -57,7 +58,7 @@ type RoleDepartmentPermissionHandler struct {
func NewRoleDepartmentPermissionHandler(db *sql.DB) *RoleDepartmentPermissionHandler { func NewRoleDepartmentPermissionHandler(db *sql.DB) *RoleDepartmentPermissionHandler {
return &RoleDepartmentPermissionHandler{ return &RoleDepartmentPermissionHandler{
DB: db, // ✅ EKLENDİ DB: db, // Added
Repo: permissions.NewRoleDepartmentPermissionRepo(db), Repo: permissions.NewRoleDepartmentPermissionRepo(db),
} }
} }
@@ -417,7 +418,7 @@ func (h *PermissionHandler) GetUserOverrides(w http.ResponseWriter, r *http.Requ
list, err := h.Repo.GetUserOverridesByUserID(userID) list, err := h.Repo.GetUserOverridesByUserID(userID)
if err != nil { if err != nil {
log.Println("USER OVERRIDE LOAD ERROR:", err) log.Println("USER OVERRIDE LOAD ERROR:", err)
http.Error(w, "db error", http.StatusInternalServerError) http.Error(w, "db error", http.StatusInternalServerError)
return return
} }
@@ -425,6 +426,138 @@ func (h *PermissionHandler) GetUserOverrides(w http.ResponseWriter, r *http.Requ
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
_ = json.NewEncoder(w).Encode(list) _ = json.NewEncoder(w).Encode(list)
} }
type routePermissionSeed struct {
Module string
Action string
Path string
}
type moduleActionSeed struct {
Module string
Action string
}
type permissionSnapshot struct {
user map[string]bool
roleDept map[string]bool
role map[string]bool
}
func permissionKey(module, action string) string {
return module + "|" + action
}
func loadPermissionSnapshot(
db *sql.DB,
userID int64,
roleID int64,
deptCodes []string,
) (permissionSnapshot, error) {
snapshot := permissionSnapshot{
user: make(map[string]bool, 128),
roleDept: make(map[string]bool, 128),
role: make(map[string]bool, 128),
}
userRows, err := db.Query(`
SELECT module_code, action, allowed
FROM mk_sys_user_permissions
WHERE user_id = $1
`, userID)
if err != nil {
return snapshot, err
}
for userRows.Next() {
var module, action string
var allowed bool
if err := userRows.Scan(&module, &action, &allowed); err != nil {
_ = userRows.Close()
return snapshot, err
}
snapshot.user[permissionKey(module, action)] = allowed
}
if err := userRows.Err(); err != nil {
_ = userRows.Close()
return snapshot, err
}
_ = userRows.Close()
if len(deptCodes) > 0 {
roleDeptRows, err := db.Query(`
SELECT module_code, action, BOOL_OR(allowed) AS allowed
FROM vw_role_dept_permissions
WHERE role_id = $1
AND department_code = ANY($2)
GROUP BY module_code, action
`,
roleID,
pq.Array(deptCodes),
)
if err != nil {
return snapshot, err
}
for roleDeptRows.Next() {
var module, action string
var allowed bool
if err := roleDeptRows.Scan(&module, &action, &allowed); err != nil {
_ = roleDeptRows.Close()
return snapshot, err
}
snapshot.roleDept[permissionKey(module, action)] = allowed
}
if err := roleDeptRows.Err(); err != nil {
_ = roleDeptRows.Close()
return snapshot, err
}
_ = roleDeptRows.Close()
}
roleRows, err := db.Query(`
SELECT module_code, action, allowed
FROM mk_sys_role_permissions
WHERE role_id = $1
`, roleID)
if err != nil {
return snapshot, err
}
for roleRows.Next() {
var module, action string
var allowed bool
if err := roleRows.Scan(&module, &action, &allowed); err != nil {
_ = roleRows.Close()
return snapshot, err
}
snapshot.role[permissionKey(module, action)] = allowed
}
if err := roleRows.Err(); err != nil {
_ = roleRows.Close()
return snapshot, err
}
_ = roleRows.Close()
return snapshot, nil
}
func resolvePermissionFromSnapshot(
s permissionSnapshot,
module string,
action string,
) bool {
key := permissionKey(module, action)
if allowed, ok := s.user[key]; ok {
return allowed
}
if allowed, ok := s.roleDept[key]; ok {
return allowed
}
if allowed, ok := s.role[key]; ok {
return allowed
}
return false
}
func GetUserRoutePermissionsHandler(db *sql.DB) http.HandlerFunc { func GetUserRoutePermissionsHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
@@ -436,10 +569,16 @@ func GetUserRoutePermissionsHandler(db *sql.DB) http.HandlerFunc {
return return
} }
repo := permissions.NewPermissionRepository(db) snapshot, err := loadPermissionSnapshot(
db,
// JWTden departmanlar int64(claims.ID),
depts := claims.DepartmentCodes int64(claims.RoleID),
claims.DepartmentCodes,
)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
rows, err := db.Query(` rows, err := db.Query(`
SELECT DISTINCT SELECT DISTINCT
@@ -454,17 +593,9 @@ func GetUserRoutePermissionsHandler(db *sql.DB) http.HandlerFunc {
} }
defer rows.Close() defer rows.Close()
type Row struct { routeSeeds := make([]routePermissionSeed, 0, 128)
Route string `json:"route"`
CanAccess bool `json:"can_access"`
}
list := make([]Row, 0, 64)
for rows.Next() { for rows.Next() {
var module, action, path string var module, action, path string
if err := rows.Scan( if err := rows.Scan(
&module, &module,
&action, &action,
@@ -473,22 +604,26 @@ func GetUserRoutePermissionsHandler(db *sql.DB) http.HandlerFunc {
continue continue
} }
allowed, err := repo.ResolvePermissionChain( routeSeeds = append(routeSeeds, routePermissionSeed{
int64(claims.ID), Module: module,
int64(claims.RoleID), Action: action,
depts, Path: path,
module, })
action, }
) if err := rows.Err(); err != nil {
http.Error(w, err.Error(), 500)
if err != nil { return
log.Println("PERM RESOLVE ERROR:", err)
continue
} }
list := make([]Row, 0, len(routeSeeds))
for _, route := range routeSeeds {
list = append(list, Row{ list = append(list, Row{
Route: path, Route: route.Path,
CanAccess: allowed, CanAccess: resolvePermissionFromSnapshot(
snapshot,
route.Module,
route.Action,
),
}) })
} }
@@ -507,18 +642,17 @@ func GetMyEffectivePermissions(db *sql.DB) http.HandlerFunc {
return return
} }
repo := permissions.NewPermissionRepository(db) snapshot, err := loadPermissionSnapshot(
db,
// ✅ JWT'DEN DEPARTMENTS int64(claims.ID),
depts := claims.DepartmentCodes int64(claims.RoleID),
claims.DepartmentCodes,
log.Printf("🧪 EFFECTIVE PERM | user=%d role=%d depts=%v",
claims.ID,
claims.RoleID,
depts,
) )
if err != nil {
http.Error(w, err.Error(), 500)
return
}
// all system perms
all, err := db.Query(` all, err := db.Query(`
SELECT DISTINCT module_code, action SELECT DISTINCT module_code, action
FROM mk_sys_routes FROM mk_sys_routes
@@ -529,14 +663,7 @@ func GetMyEffectivePermissions(db *sql.DB) http.HandlerFunc {
} }
defer all.Close() defer all.Close()
type Row struct { moduleActions := make([]moduleActionSeed, 0, 128)
Module string `json:"module"`
Action string `json:"action"`
Allowed bool `json:"allowed"`
}
list := make([]Row, 0, 128)
for all.Next() { for all.Next() {
var m, a string var m, a string
@@ -544,22 +671,32 @@ func GetMyEffectivePermissions(db *sql.DB) http.HandlerFunc {
continue continue
} }
allowed, err := repo.ResolvePermissionChain( moduleActions = append(moduleActions, moduleActionSeed{
int64(claims.ID),
int64(claims.RoleID),
depts,
m,
a,
)
if err != nil {
continue
}
list = append(list, Row{
Module: m, Module: m,
Action: a, Action: a,
Allowed: allowed, })
}
if err := all.Err(); err != nil {
http.Error(w, err.Error(), 500)
return
}
type Row struct {
Module string `json:"module"`
Action string `json:"action"`
Allowed bool `json:"allowed"`
}
list := make([]Row, 0, len(moduleActions))
for _, item := range moduleActions {
list = append(list, Row{
Module: item.Module,
Action: item.Action,
Allowed: resolvePermissionFromSnapshot(
snapshot,
item.Module,
item.Action,
),
}) })
} }

View File

@@ -2,6 +2,10 @@ package services
import ( import (
"bssapp-backend/models" "bssapp-backend/models"
"crypto/md5"
"crypto/sha1"
"encoding/hex"
"log"
"strings" "strings"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@@ -16,7 +20,6 @@ func CheckPasswordWithLegacy(user *models.User, plain string) bool {
return false return false
} }
plain = strings.TrimSpace(plain)
if plain == "" { if plain == "" {
return false return false
} }
@@ -28,11 +31,100 @@ func CheckPasswordWithLegacy(user *models.User, plain string) bool {
// 1⃣ bcrypt hash mi? // 1⃣ bcrypt hash mi?
if isBcryptHash(stored) { if isBcryptHash(stored) {
return bcrypt.CompareHashAndPassword([]byte(stored), []byte(plain)) == nil candidates := make([]string, 0, 10)
seen := map[string]struct{}{}
add := func(v string) {
if v == "" {
return
}
if _, ok := seen[v]; ok {
return
}
seen[v] = struct{}{}
candidates = append(candidates, v)
}
add(plain)
trimmed := strings.TrimSpace(plain)
add(trimmed)
bases := append([]string(nil), candidates...)
for _, base := range bases {
md5Sum := md5.Sum([]byte(base))
md5Hex := hex.EncodeToString(md5Sum[:])
add(md5Hex)
add(strings.ToUpper(md5Hex))
sha1Sum := sha1.Sum([]byte(base))
sha1Hex := hex.EncodeToString(sha1Sum[:])
add(sha1Hex)
add(strings.ToUpper(sha1Hex))
}
var lastErr error
for _, candidate := range candidates {
if err := bcrypt.CompareHashAndPassword([]byte(stored), []byte(candidate)); err == nil {
return true
} else {
lastErr = err
}
if encoded, ok := encodeLegacySingleByte(candidate); ok {
if err := bcrypt.CompareHashAndPassword([]byte(stored), encoded); err == nil {
return true
} else {
lastErr = err
}
}
}
if lastErr != nil {
log.Printf(
"LEGACY BCRYPT MISMATCH stored_len=%d candidates=%d last_err=%v",
len(stored),
len(candidates),
lastErr,
)
}
return false
} }
// 2⃣ TAM LEGACY — düz metin (eski kayıtlar) // 2⃣ TAM LEGACY — düz metin (eski kayıtlar)
return stored == plain if stored == plain {
return true
}
trimmed := strings.TrimSpace(plain)
if trimmed != plain && trimmed != "" && stored == trimmed {
return true
}
// 3⃣ Legacy hash variants seen in old dfusr.upass data.
if isHexDigest(stored, 32) {
sumRaw := md5.Sum([]byte(plain))
if strings.EqualFold(stored, hex.EncodeToString(sumRaw[:])) {
return true
}
if trimmed != plain && trimmed != "" {
sumTrim := md5.Sum([]byte(trimmed))
if strings.EqualFold(stored, hex.EncodeToString(sumTrim[:])) {
return true
}
}
}
if isHexDigest(stored, 40) {
sumRaw := sha1.Sum([]byte(plain))
if strings.EqualFold(stored, hex.EncodeToString(sumRaw[:])) {
return true
}
if trimmed != plain && trimmed != "" {
sumTrim := sha1.Sum([]byte(trimmed))
if strings.EqualFold(stored, hex.EncodeToString(sumTrim[:])) {
return true
}
}
}
return false
} }
func isBcryptHash(s string) bool { func isBcryptHash(s string) bool {
@@ -40,3 +132,46 @@ func isBcryptHash(s string) bool {
strings.HasPrefix(s, "$2b$") || strings.HasPrefix(s, "$2b$") ||
strings.HasPrefix(s, "$2y$") strings.HasPrefix(s, "$2y$")
} }
func isHexDigest(s string, expectedLen int) bool {
if len(s) != expectedLen {
return false
}
for _, r := range s {
if (r < '0' || r > '9') &&
(r < 'a' || r > 'f') &&
(r < 'A' || r > 'F') {
return false
}
}
return true
}
// encodeLegacySingleByte converts text to a Turkish-compatible single-byte
// representation (similar to Windows-1254 / ISO-8859-9) for legacy bcrypt data.
func encodeLegacySingleByte(s string) ([]byte, bool) {
out := make([]byte, 0, len(s))
for _, r := range s {
switch r {
case 'Ğ':
out = append(out, 0xD0)
case 'ğ':
out = append(out, 0xF0)
case 'İ':
out = append(out, 0xDD)
case 'ı':
out = append(out, 0xFD)
case 'Ş':
out = append(out, 0xDE)
case 'ş':
out = append(out, 0xFE)
default:
if r >= 0 && r <= 0xFF {
out = append(out, byte(r))
} else {
return nil, false
}
}
}
return out, true
}

View File

@@ -1 +1 @@
VITE_API_BASE_URL=http://localhost:8080 VITE_API_BASE_URL=http://localhost:8080/api

View File

@@ -146,8 +146,6 @@ createQuasarApp(createApp, quasarUserOptions)
return Promise[ method ]([ return Promise[ method ]([
import(/* webpackMode: "eager" */ 'boot/axios'),
import(/* webpackMode: "eager" */ 'boot/dayjs') import(/* webpackMode: "eager" */ 'boot/dayjs')
]).then(bootFiles => { ]).then(bootFiles => {

View File

@@ -1,75 +0,0 @@
/* eslint-disable */
/**
* THIS FILE IS GENERATED AUTOMATICALLY.
* DO NOT EDIT.
*
* You are probably looking on adding startup/initialization code.
* Use "quasar new boot <name>" and add it there.
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
* boot: ['file', ...] // do not add ".js" extension to it.
*
* Boot files are your "main.js"
**/
import { Quasar } from 'quasar'
import { markRaw } from 'vue'
import RootComponent from 'app/src/App.vue'
import createStore from 'app/src/stores/index'
import createRouter from 'app/src/router/index'
export default async function (createAppFn, quasarUserOptions) {
// Create the app instance.
// Here we inject into it the Quasar UI, the router & possibly the store.
const app = createAppFn(RootComponent)
app.use(Quasar, quasarUserOptions)
const store = typeof createStore === 'function'
? await createStore({})
: createStore
app.use(store)
const router = markRaw(
typeof createRouter === 'function'
? await createRouter({store})
: createRouter
)
// make router instance available in store
store.use(({ store }) => { store.router = router })
// Expose the app, the router and the store.
// Note that we are not mounting the app here, since bootstrapping will be
// different depending on whether we are in a browser or on the server.
return {
app,
store,
router
}
}

View File

@@ -1,154 +0,0 @@
/* eslint-disable */
/**
* THIS FILE IS GENERATED AUTOMATICALLY.
* DO NOT EDIT.
*
* You are probably looking on adding startup/initialization code.
* Use "quasar new boot <name>" and add it there.
* One boot file per concern. Then reference the file(s) in quasar.config file > boot:
* boot: ['file', ...] // do not add ".js" extension to it.
*
* Boot files are your "main.js"
**/
import { createApp } from 'vue'
import '@quasar/extras/roboto-font/roboto-font.css'
import '@quasar/extras/material-icons/material-icons.css'
// We load Quasar stylesheet file
import 'quasar/dist/quasar.sass'
import 'src/css/app.css'
import createQuasarApp from './app.js'
import quasarUserOptions from './quasar-user-options.js'
const publicPath = `/`
async function start ({
app,
router
, store
}, bootFiles) {
let hasRedirected = false
const getRedirectUrl = url => {
try { return router.resolve(url).href }
catch (err) {}
return Object(url) === url
? null
: url
}
const redirect = url => {
hasRedirected = true
if (typeof url === 'string' && /^https?:\/\//.test(url)) {
window.location.href = url
return
}
const href = getRedirectUrl(url)
// continue if we didn't fail to resolve the url
if (href !== null) {
window.location.href = href
window.location.reload()
}
}
const urlPath = window.location.href.replace(window.location.origin, '')
for (let i = 0; hasRedirected === false && i < bootFiles.length; i++) {
try {
await bootFiles[i]({
app,
router,
store,
ssrContext: null,
redirect,
urlPath,
publicPath
})
}
catch (err) {
if (err && err.url) {
redirect(err.url)
return
}
console.error('[Quasar] boot error:', err)
return
}
}
if (hasRedirected === true) return
app.use(router)
app.mount('#q-app')
}
createQuasarApp(createApp, quasarUserOptions)
.then(app => {
// eventually remove this when Cordova/Capacitor/Electron support becomes old
const [ method, mapFn ] = Promise.allSettled !== void 0
? [
'allSettled',
bootFiles => bootFiles.map(result => {
if (result.status === 'rejected') {
console.error('[Quasar] boot error:', result.reason)
return
}
return result.value.default
})
]
: [
'all',
bootFiles => bootFiles.map(entry => entry.default)
]
return Promise[ method ]([
import(/* webpackMode: "eager" */ 'boot/dayjs')
]).then(bootFiles => {
const boot = mapFn(bootFiles).filter(entry => typeof entry === 'function')
start(app, boot)
})
})

View File

@@ -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()
})
})
}

View File

@@ -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} }

View File

@@ -1 +0,0 @@
.ol-page[data-v-68dfbebc]{padding:10px}.ol-filter-bar[data-v-68dfbebc]{margin-bottom:8px}.ol-filter-row[data-v-68dfbebc]{align-items:center;display:flex;flex-wrap:nowrap;gap:10px}.ol-filter-input[data-v-68dfbebc]{flex:0 0 136px;min-width:118px;width:136px}.ol-search[data-v-68dfbebc]{flex:1 1 360px;max-width:420px;min-width:240px}.ol-filter-actions[data-v-68dfbebc]{display:flex;flex:0 0 auto;flex-wrap:nowrap;gap:8px}.ol-filter-total[data-v-68dfbebc]{align-items:flex-end;align-self:center;display:flex;flex:0 0 auto;flex-direction:column;gap:2px;justify-content:center;line-height:1.2;margin-left:auto;min-width:250px}.ol-total-line[data-v-68dfbebc]{align-items:baseline;display:flex;gap:8px;white-space:nowrap}.ol-total-label[data-v-68dfbebc]{color:#4b5563;font-size:12px}.ol-total-value[data-v-68dfbebc]{font-size:13px}.ol-table[data-v-68dfbebc] .q-table thead th{font-size:11px;padding:4px 6px;white-space:nowrap}.ol-table[data-v-68dfbebc] .q-table tbody td{font-size:11px;padding:3px 6px}.ol-col-multiline[data-v-68dfbebc]{display:block;display:-webkit-box;line-height:1.15;overflow:hidden;text-overflow:ellipsis;white-space:normal!important;word-break:break-word;-webkit-box-orient:vertical;-webkit-line-clamp:2;max-height:2.35em}.ol-col-cari[data-v-68dfbebc]{max-width:160px}.ol-col-short[data-v-68dfbebc]{max-width:88px}.ol-col-desc[data-v-68dfbebc]{max-width:160px}.ol-pack-rate-cell[data-v-68dfbebc]{font-weight:700}.pack-rate-danger[data-v-68dfbebc]{color:#c62828}.pack-rate-warn[data-v-68dfbebc]{color:#8a6d00}.pack-rate-ok[data-v-68dfbebc]{color:#1f7a4f}@media (max-width:1440px){.ol-filter-row[data-v-68dfbebc]{align-items:flex-start;flex-wrap:wrap}.ol-filter-actions[data-v-68dfbebc]{flex-wrap:wrap}.ol-filter-input[data-v-68dfbebc]{flex:1 1 140px}.ol-filter-total[data-v-68dfbebc]{align-items:flex-start;margin-left:0;margin-top:6px;min-width:100%}.ol-total-line[data-v-68dfbebc]{justify-content:space-between;width:100%}}

View File

@@ -1 +0,0 @@
.bulk-close-page[data-v-734820af]{padding:10px}.bulk-filter-bar[data-v-734820af]{margin-bottom:8px}.bulk-filter-row[data-v-734820af]{align-items:flex-start;display:flex;flex-wrap:wrap;gap:10px}.bulk-search[data-v-734820af]{flex:1 1 420px;max-width:520px;min-width:320px}.bulk-filter-actions[data-v-734820af]{display:flex;flex-wrap:wrap;gap:8px}.bulk-summary[data-v-734820af]{align-items:flex-end;display:flex;flex-direction:column;font-size:12px;margin-left:auto;min-width:140px}.bulk-table[data-v-734820af] .q-table thead th{font-size:11px;font-weight:700;white-space:nowrap}.bulk-table[data-v-734820af] .q-table tbody td{font-size:11px;white-space:nowrap}.pack-rate-danger[data-v-734820af]{color:#c62828}.pack-rate-warn[data-v-734820af]{color:#8a6d00}.pack-rate-ok[data-v-734820af]{color:#1f7a4f}@media (max-width:1200px){.bulk-summary[data-v-734820af]{align-items:flex-start;margin-left:0}}

View File

@@ -1 +0,0 @@
.rdp-list-page[data-v-a17e51d4]{--rdp-header-h:56px;--rdp-filter-h:96px;background:#fff;display:flex;flex-direction:column;height:calc(100vh - var(--rdp-header-h));overflow:auto;padding:10px}.rdp-filter-bar[data-v-a17e51d4]{align-items:center;background:#fff;border-bottom:1px solid #ddd;box-shadow:0 1px 2px #0000000f;display:flex;margin-bottom:8px;min-height:var(--rdp-filter-h);padding:10px 12px;position:sticky;top:0;z-index:600}.rdp-filter-row[data-v-a17e51d4]{align-items:flex-end;display:flex;flex-wrap:nowrap;gap:12px}.rdp-filter-input[data-v-a17e51d4]{max-width:300px;min-width:180px}.rdp-search[data-v-a17e51d4]{flex:1 1 360px;max-width:520px;min-width:300px}.rdp-config-menus[data-v-a17e51d4],.rdp-filter-actions[data-v-a17e51d4]{align-items:center;display:flex;flex:0 0 auto;gap:8px;white-space:nowrap}.rdp-menu-list[data-v-a17e51d4]{max-height:420px;min-width:260px}.rdp-summary[data-v-a17e51d4]{align-self:center;background:#f9fafb;border:1px solid #e0e0e0;border-radius:6px;color:#4b5563;font-size:12px;margin-left:auto;padding:8px 12px;white-space:nowrap}.rdp-table[data-v-a17e51d4] .q-table__middle{max-height:none!important;overflow:visible!important}.rdp-table[data-v-a17e51d4] .q-table thead th{background:#fff;box-shadow:0 2px 4px #00000014;font-size:11px;position:sticky;top:var(--rdp-filter-h);white-space:nowrap;z-index:500}.rdp-table[data-v-a17e51d4] .q-table tbody td{font-size:11px;padding:3px 6px}.rdp-table[data-v-a17e51d4] .q-checkbox__inner{pointer-events:none}.rdp-table[data-v-a17e51d4] .freeze-col{background:#fff;position:sticky;z-index:510}.rdp-table[data-v-a17e51d4] thead .freeze-col{background:#fff;z-index:520}.rdp-table[data-v-a17e51d4] .freeze-1{left:0}.rdp-table[data-v-a17e51d4] .freeze-2{left:56px}.rdp-table[data-v-a17e51d4] .freeze-3{left:276px}@media (max-width:1400px){.rdp-filter-row[data-v-a17e51d4]{align-items:flex-start;flex-wrap:wrap}.rdp-config-menus[data-v-a17e51d4],.rdp-filter-actions[data-v-a17e51d4]{flex-wrap:wrap}.rdp-summary[data-v-a17e51d4]{margin-left:0;margin-top:6px;min-width:100%}}

View File

@@ -1 +0,0 @@
.perm-gateway[data-v-57a9abef]{padding:24px}

View File

@@ -1 +0,0 @@
.user-gateway-page[data-v-7b115e06]{background:#fafafa}.gateway-container[data-v-7b115e06]{max-width:900px;padding:24px;width:100%}.gateway-header[data-v-7b115e06]{text-align:center}.gateway-actions[data-v-7b115e06]{justify-content:center}.gateway-card[data-v-7b115e06]{transition:all .2s ease;width:280px}.gateway-card[data-v-7b115e06]:hover{box-shadow:0 8px 24px #00000014;transform:translateY(-4px)}

View File

@@ -1 +0,0 @@
.with-bg{min-height:100%;position:relative}.with-bg:before{background:url(/images/Baggi-tekstilas-logolu.jpg) no-repeat top;background-size:400px auto;content:"";inset:0;opacity:.15;pointer-events:none;position:absolute;z-index:0}.with-bg>*{position:relative;z-index:1}.q-page{margin-top:5px}@media (max-width:768px){.with-bg:before{background-size:260px auto}}.filter-sticky{position:sticky;top:56px;z-index:300}.filter-collapsible,.filter-sticky{background:#fff}.table-scroll{height:calc(100vh - 56px);margin-top:0;overflow-x:auto;overflow-y:auto;position:relative}.sticky-table .q-table__middle{max-height:none!important;overflow:visible!important}.sticky-table .q-table__top{background:#fff;box-shadow:0 2px 4px #00000014;position:sticky;top:0;z-index:220}.sticky-table thead th{background:#fff;position:sticky;top:40px;z-index:210}.sticky-bar{background:#fff;border-bottom:1px solid #ddd;padding:4px 8px;position:sticky;top:0;z-index:230}.sticky-table thead th{max-width:400px;min-width:80px;overflow:auto;resize:horizontal}.sticky-table td{font-size:.95rem;font-weight:600;line-height:1.2rem;max-width:400px;min-width:80px;overflow-wrap:break-word!important;padding:4px 8px!important;white-space:normal!important;word-break:break-word!important}.baggi-ppct{display:block;margin:30px auto 0;max-width:400px;opacity:.4}.col-desc{font-size:.75rem!important;line-height:1.1rem;max-width:220px!important;min-width:180px!important;overflow-wrap:break-word;white-space:normal!important;width:220px!important;word-break:break-word!important}.custom-table{font-size:.8rem}.custom-table th{background:#fff;color:#222;font-weight:800}.custom-table td{color:#333;font-weight:600}.custom-subtable{background:#fafafa;font-size:.72rem}.custom-subtable th{background:#f9f9f9;color:#555;font-weight:500}.custom-subtable td{color:#666;font-weight:400}.col-narrow{font-size:.72rem;max-width:90px;overflow:hidden;padding:2px 6px!important;text-overflow:ellipsis;white-space:nowrap}.group-row{background:#f1f1f1!important;border-bottom:2px solid #ccc;border-top:2px solid #ccc;color:#222;font-weight:700!important}.balance-card{align-items:center;border-radius:8px;display:flex;justify-content:center;min-height:120px;width:100%}.q-table td[data-col=BELGE_NO],.q-table td[data-col=Belge_No],.q-table td[data-col=belge_no]{color:var(--q-primary)!important;font-weight:600!important}.permissions-toolbar{align-items:center;background:#fff;border-bottom:1px solid #ddd;display:flex;gap:12px;padding:8px 16px;position:sticky;top:42px;z-index:300}.permissions-table-scroll{height:calc(100vh - 112px);overflow-x:auto;overflow-y:auto;position:relative}.permissions-table .q-table__middle{max-height:none!important;overflow:auto!important;padding-top:0}.permissions-table thead th{background:#fff;box-shadow:0 2px 4px #00000014;top:10px;z-index:210}.permissions-table td{background:#fff;font-size:.95rem;line-height:1.2rem;max-width:400px;min-width:80px;overflow-wrap:break-word!important;padding:4px 8px!important;white-space:normal!important;word-break:break-word!important}.permissions-table .permissions-sticky-col{background:#fff;box-shadow:2px 0 4px #0000000a;left:0;position:sticky;z-index:205}:root{--header-h:0px;--filter-h:72px;--save-h:60px;--grid-header-h:172px;--sub-header-h:34px;--drawer-w:240px;--col-model:90px;--col-renk:80px;--col-ana:100px;--col-alt:100px;--col-aciklama:140px;--col-adet:70px;--col-fiyat:70px;--col-pb:70px;--col-tutar:70px;--grp-title-w:90px;--grp-title-gap:4px;--beden-w:44px;--beden-h:28px;--beden-count:16;--baggi-gold:#c9a227;--baggi-gold-pale:#fff9e6;--baggi-gold-light:#fff7d2;--baggi-cream:#fffef9;--baggi-gray-border:#bbb}*,:after,:before{box-sizing:border-box}body,html{height:100%;margin:0}body{background:#fff;color:#222;font-family:Inter,Segoe UI,Arial,sans-serif;font-size:14px;line-height:1.4}#q-app,.q-page-container{margin:0;padding:0}.q-layout__page{top:0!important}.order-page{background:#fff;display:flex;flex-direction:column;height:calc(100vh - var(--header-h));overflow-x:visible;overflow-y:auto}.body--drawer-left-open .q-page-container{margin-left:var(--drawer-w);width:calc(100% - var(--drawer-w))}.body--drawer-left-closed .q-page-container{margin-left:0;width:100%}.order-scroll-x{flex:1}.order-page::-webkit-scrollbar-thumb,.order-scroll-x::-webkit-scrollbar{height:8px;width:8px}.order-scroll-x::-webkit-scrollbar-thumb{background:#c0a75e;border-radius:4px}.order-scroll-x::-webkit-scrollbar-track{background:#f9f5e6}.q-header{box-shadow:0 1px 2px #00000014;position:sticky;top:0;z-index:1000}.sticky-stack{background:#fff;box-shadow:0 1px 3px #0000000d;display:flex;flex-direction:column;margin-top:0!important;position:sticky;top:var(--header-h);z-index:950}.filter-bar{background:#fafafa;margin-top:0!important;padding:12px 24px}.filter-bar,.save-toolbar{border-bottom:1px solid #ddd}.save-toolbar{align-items:center;background:var(--baggi-gold-pale);border-top:1px solid #ddd;display:flex;justify-content:space-between;padding:10px 16px;z-index:940}.save-toolbar .label{color:#6a5314;font-weight:700}.save-toolbar .value{color:#000;font-weight:700}.save-toolbar .q-btn{border-radius:6px;font-weight:600;text-transform:none}.order-grid-header{background:var(--baggi-cream);border-bottom:2px solid var(--baggi-gray-border);box-shadow:0 2px 3px #0000000d;display:grid;grid-auto-flow:column;grid-template-columns:var(--col-model) var(--col-renk) var(--col-ana) var(--col-alt) var(--col-aciklama) calc(var(--grp-title-w) + var(--grp-title-gap) + var(--beden-w)*var(--beden-count)) var(--col-adet) var(--col-fiyat) var(--col-pb) var(--col-tutar) var(--col-termin);position:sticky;top:calc(var(--header-h) + var(--filter-h) + var(--save-h));z-index:700}.order-grid-header .col-fixed{align-items:center;background:var(--baggi-gold-light);border:1px solid #aaa;display:flex;font-size:12.5px;font-weight:700;height:var(--grid-header-h);justify-content:center;transform:rotate(180deg);writing-mode:vertical-lr}.order-grid-header .aciklama-col{background:#fff9c4;border-right:2px solid #a6a6a6}.order-grid-header .beden-block{background:#fff;border:1px solid #ccc;display:flex;flex-direction:column;height:var(--grid-header-h)}.order-grid-header .grp-row{align-items:center;display:flex;height:var(--beden-h)}.order-grid-header .grp-title{font-size:12px;font-weight:700;padding-right:4px;text-align:right;width:var(--grp-title-w)}.order-grid-header .grp-body{display:grid;grid-auto-columns:var(--beden-w);grid-auto-flow:column}.order-grid-header .grp-cell.hdr{align-items:center;border:1px solid #bbb;display:flex;font-size:11.5px;font-weight:600;height:var(--beden-h);justify-content:center;width:var(--beden-w)}.order-grid-header .total-row{align-items:stretch;background:#fff59d;display:flex;justify-content:space-between}.order-grid-header .total-cell{align-items:center;background:var(--baggi-gold-pale);border-right:1px solid #bbb;display:flex;font-size:12px;font-weight:700;justify-content:center;transform:rotate(180deg);width:var(--col-adet);writing-mode:vertical-lr}.order-sub-header{align-items:center;background:linear-gradient(90deg,#fffbe9,#fff4c4 50%,#fff1b0);border-bottom:1px solid #d6c06a;border-top:1px solid #d6c06a;box-sizing:border-box;display:grid;grid-auto-flow:column;grid-template-columns:var(--col-model) var(--col-renk) var(--col-ana) var(--col-alt) var(--col-aciklama) calc(var(--grp-title-w) + var(--grp-title-gap) + var(--beden-w)*var(--beden-count)) var(--col-adet) var(--col-fiyat) var(--col-pb) var(--col-tutar) var(--col-termin);height:var(--sub-header-h);justify-items:stretch;margin-right:0!important;margin:0!important;min-height:var(--sub-header-h);padding-right:0!important;padding:0!important;position:sticky;top:calc(var(--header-h) + var(--filter-h) + var(--save-h) + var(--grid-header-h));z-index:650}:root{--col-termin:142px}.order-sub-header .sub-left{align-items:center;color:#2b1f05;display:flex;font-weight:800;grid-column:1/span 5;padding-left:6px}.order-sub-header .sub-center{align-items:center;box-sizing:border-box;display:grid;grid-auto-columns:var(--beden-w);grid-auto-flow:column;grid-column:6/7;height:100%;justify-content:start;margin-left:var(--grp-title-gap);padding-left:var(--grp-title-w);width:calc(var(--grp-title-w) + var(--grp-title-gap) + var(--beden-w)*var(--beden-count))}.order-sub-header .beden-cell{align-items:center;background:#fffdf3;border:1px solid #d8c16b;border-right:none;box-sizing:border-box;display:flex;font-size:12px;font-weight:600;height:100%;justify-content:center;width:var(--beden-w)}.order-sub-header .beden-cell:last-child{border-right:1px solid #d8c16b}.order-sub-header .sub-right{align-items:flex-end;color:#3b2f09;display:flex;flex-direction:column;font-size:13.5px;font-weight:900;grid-column:7/-1;justify-content:center;line-height:1.3;padding-right:0;text-align:right;text-transform:uppercase;transform:translateX(-60px)}.order-sub-header:hover{background:linear-gradient(90deg,#fff9cf,#fff3b0 70%,#ffe88f)}:root{--sub-header-h:60px}.order-sub-header{overflow:hidden}.order-grid-body{background:#fff;margin-top:0!important;padding-top:var(--sub-header-h);position:relative;z-index:100}.summary-row{display:grid;grid-template-columns:var(--col-model) var(--col-renk) var(--col-ana) var(--col-alt) var(--col-aciklama) calc(var(--grp-title-w) + var(--grp-title-gap) + var(--beden-w)*var(--beden-count)) var(--col-adet) var(--col-fiyat) var(--col-pb) var(--col-tutar) var(--col-termin)}.summary-row .cell{box-sizing:border-box;color:#222;font-size:13px;height:var(--beden-h)}.summary-row.row-closed{background:#f5f5f5;opacity:.55}.summary-row.row-closed:hover{background:#f5f5f5!important}.summary-row:nth-child(odd){background:#fffef9}.summary-row .grp-area{display:flex;flex-direction:column;justify-content:center;transform:translateX(calc(var(--grp-title-w) - var(--beden-w)))}.summary-row .grp-row{display:grid;grid-auto-columns:var(--beden-w);grid-auto-flow:column}.summary-row .grp-row .cell.beden{align-items:center;border:1px solid #ddd;display:flex;font-size:12px;height:var(--beden-h);justify-content:center;width:var(--beden-w)}.cell.beden.ghost{border:1px solid #0000!important;opacity:0;pointer-events:none}.summary-row .cell.adet,.summary-row .cell.fiyat,.summary-row .cell.pb,.summary-row .cell.termin,.summary-row .cell.tutar{border-left:none!important;color:#000;font-weight:600;height:100%}.summary-row .cell.tutar{border-right:none!important;justify-content:flex-end;padding-right:8px;text-align:right}.summary-row .cell.termin{align-items:center;background:#fffef9;justify-content:center;min-width:var(--col-termin)}.summary-row .cell.termin .q-input{box-sizing:border-box;max-width:142px!important;width:100%}.summary-row .cell.termin input{font-size:13px;text-align:center}.editor{background:#fffef9;border-top:1px solid #ddd;margin-top:24px;padding:16px;position:relative;z-index:50}.editor:before{background:linear-gradient(90deg,#c9a227,#e5d28b,#fff7d2);border-radius:2px;content:"";display:block;height:4px;margin-bottom:12px}.editor .q-input,.editor .q-select{font-size:14px;margin-bottom:8px}.cell.termin .termin-label{align-items:center;background:#fffef9;border-left:1px solid #ccc;box-sizing:border-box;color:#222;display:flex;font-size:13px;font-weight:600;height:100%;justify-content:center;width:100%}@media (max-width:1024px){:root{--beden-w:40px;--col-aciklama:120px}.order-grid-header .col-fixed{font-size:11px}.order-sub-header{font-size:12.5px}}@media (max-width:768px){:root{--beden-w:36px;--col-model:70px;--col-renk:60px;--col-aciklama:100px}.order-page{font-size:13px}.order-grid-header .total-cell{font-size:10.5px}}.summary-row .cell{align-items:center;display:flex;height:auto;justify-content:center;padding:4px 6px;text-align:center;white-space:normal;word-wrap:break-word}.summary-row .grp-area,.summary-row .grp-row,.summary-row .grp-row .cell.beden{align-items:center;height:100%}.summary-row .cell.aciklama{align-items:flex-start!important;background:#fff!important;border-right:1px solid #ccc!important;box-sizing:border-box!important;display:flex!important;flex-direction:column!important;font-size:13px!important;grid-column:5/6!important;justify-content:flex-start!important;line-height:1.4!important;margin-right:-92px!important;min-height:36px!important;overflow-wrap:break-word!important;padding:6px 12px!important;position:relative!important;text-align:left!important;white-space:normal!important;width:calc(var(--col-aciklama) + 92px)!important;word-break:break-word!important;z-index:10!important}.order-grid-header .col-fixed,.summary-row .cell,.summary-row .grp-row .cell.beden{border-color:#bbb!important}.summary-row .cell:not(:last-child){border-right:1px solid #bdbdbd!important}.summary-row{border-bottom:1px solid #ccc}.summary-row:last-child{border-bottom:2px solid #b7a33a}.summary-row .cell,.summary-row .grp-row .cell.beden{border-bottom:1px solid #ddd!important}.summary-row:hover{background:#fffce0}.summary-row.is-editing{background:#fff3cd;outline:2px solid #caa83f;z-index:2}.editor .q-btn:hover{background:#d2b04d;color:#fff}.summary-row:hover .cell,.summary-row:hover .grp-row .cell.beden{border-bottom:1px solid #ccc!important}.stok-red{color:#e53935;font-weight:600}.stok-yellow{color:#f9a825;font-weight:600}.stok-green{color:#43a047;font-weight:600}.q-banner.rounded-borders{border-radius:8px}.order-gateway{background:linear-gradient(145deg,#fff,#fafafa);height:100%}.order-btn{border-radius:12px;font-size:1.2rem;min-width:280px;padding:20px 40px;transition:all .2s ease}.order-btn:hover{box-shadow:0 4px 12px #00000026;transform:translateY(-3px)}.body--drawer-left-open .order-page{overflow-x:visible;width:calc(100vw - var(--drawer-w))}.order-scroll-x{box-sizing:border-box;max-width:100%}.order-grid-body,.order-grid-header,.order-sub-header{box-sizing:border-box;min-width:fit-content;width:100%}.body--drawer-left-open .filter-bar,.body--drawer-left-open .order-grid-body,.body--drawer-left-open .order-grid-header,.body--drawer-left-open .order-sub-header,.body--drawer-left-open .save-toolbar{box-sizing:border-box;margin-left:0;margin-right:0;overflow-x:hidden;width:calc(100vw - var(--drawer-w))}.body--drawer-left-closed .filter-bar,.body--drawer-left-closed .order-grid-body,.body--drawer-left-closed .order-grid-header,.body--drawer-left-closed .order-sub-header,.body--drawer-left-closed .save-toolbar{width:100vw}.order-grid-body,.order-grid-header,.order-sub-header{border-right:2px solid var(--baggi-gold)}.body--drawer-left-open .filter-bar,.body--drawer-left-open .order-grid-body,.body--drawer-left-open .order-grid-header,.body--drawer-left-open .order-page,.body--drawer-left-open .order-sub-header,.body--drawer-left-open .save-toolbar{margin-right:0!important;overflow-x:visible!important;padding-right:0!important;width:calc(100vw - var(--drawer-w) - 8px)}.order-grid-body{border-right:2px solid var(--baggi-gold)}.order-scroll-x{align-items:flex-start;background:#fff;display:flex;flex-direction:column;overflow-x:auto;overflow-y:visible}.filter-bar,.order-grid-header,.order-sub-header,.save-toolbar{box-sizing:border-box;min-width:100%;width:fit-content}.order-grid-body{box-sizing:border-box;width:fit-content}.summary-row.row-closed{opacity:.65;pointer-events:none}.summary-row.row-closed,.summary-row.row-closed:hover{background:#e6e6e6!important}.summary-row.row-closed.is-editing{outline:none!important}.filter-bar,.order-grid-body,.order-grid-header,.order-sub-header,.save-toolbar{border-right:none!important;margin-right:0!important;padding-right:0!important}.summary-row.row-error{background:#c1001514}.row-error-icon{left:4px;position:absolute;top:50%;transform:translateY(-50%)}.body--drawer-left-closed .order-scroll-x,.body--drawer-left-open .order-scroll-x{overflow-x:auto;width:100%}:root{--ol-header-h:56px;--ol-filter-h:96px}.ol-page{background:#fff;display:flex;flex-direction:column;height:calc(100vh - var(--ol-header-h));overflow:auto}.ol-filter-bar{align-items:center;box-shadow:0 1px 2px #0000000f;display:flex;min-height:var(--ol-filter-h);padding:10px 16px}.ol-table .q-table__middle{max-height:none!important;overflow:visible!important}.ol-table thead th{background:#fff;box-shadow:0 2px 4px #00000014;font-weight:700;position:sticky;top:var(--ol-filter-h);z-index:500}.ol-table .q-table__body .q-tr:nth-child(odd){background-color:#f7f7f7!important}.ol-table .q-table__body .q-tr:nth-child(2n){background-color:#fff!important}.ol-table .q-table__body .q-tr:hover{background-color:#fff7d1!important;transition:background-color .15s ease}.ol-table .q-td{font-size:.9rem;line-height:1.3;padding:6px 8px!important}.q-header{z-index:1000!important}.q-drawer{z-index:950!important}@media (max-width:768px){:root{--ol-filter-h:64px}.ol-filter-bar{padding:8px 12px}}.ol-table .q-table__body .q-tr:nth-child(odd),.ol-table tbody tr:nth-child(odd){background-color:#faf8ef!important}.ol-table .q-table__body .q-tr:nth-child(2n),.ol-table tbody tr:nth-child(2n){background-color:#fff!important}.ol-table .q-table__body .q-tr:hover,.ol-table tbody tr:hover{background-color:#fff4cc!important;transition:background-color .2s ease}.ol-qbanner{background:#f9fafb;border:1px solid #e0e0e0;border-radius:6px;padding:8px 12px}.ol-qbanner-amount{color:#1976d2;margin-left:6px}.ol-col-multiline{line-height:1.25rem;overflow:hidden;white-space:normal!important;word-break:break-word}.ol-col-cari{font-size:.88rem;font-weight:600;max-width:200px;min-width:150px}.ol-col-cari,.ol-col-desc{display:-webkit-box;-webkit-box-orient:vertical}.ol-col-desc{color:#444;font-size:.82rem;max-width:220px;min-width:160px}.ol-table .q-td.ol-col-cari,.ol-table .q-td.ol-col-desc{padding-bottom:6px!important;padding-top:6px!important}.ol-table th.ol-col-cari,.ol-table th.ol-col-desc{white-space:nowrap}.ol-filter-bar{background:#fff;border-bottom:1px solid #ddd;padding:10px 12px;position:sticky;top:0;z-index:600}.ol-filter-row{align-items:flex-end;display:flex;flex-wrap:nowrap;gap:12px}.ol-filter-input{max-width:260px;min-width:180px}.ol-search{min-width:280px}.ol-filter-actions{align-items:center;display:flex;gap:8px;white-space:nowrap}.ol-filter-total{background:#f9fafb;border:1px solid #e0e0e0;border-radius:6px;margin-left:auto;padding:8px 12px;white-space:nowrap}@media (max-width:1200px){.ol-filter-row{flex-wrap:wrap;row-gap:8px}.ol-filter-total{justify-content:flex-end;width:100%}}.order-gateway{min-height:100vh}.draft-card{max-width:90vw;width:320px}.act-page{background:#fff;height:calc(100vh - 56px);overflow:auto}.act-filter-bar{background:#fff;border-bottom:1px solid #ddd;box-shadow:0 1px 2px #0000000f;padding:10px 16px;position:sticky;top:0;z-index:620}.act-filter-row{align-items:flex-end;display:flex;flex-wrap:nowrap;gap:12px}.act-filter-input{max-width:240px;min-width:160px}.act-filter-wide{min-width:260px}.act-filter-actions{display:flex;gap:8px;margin-left:auto;white-space:nowrap}.act-table{font-size:.85rem}.act-table thead th{background:#fff;box-shadow:0 2px 4px #00000014;font-weight:700;position:sticky;top:56px;z-index:500}.act-table tbody tr:nth-child(odd){background:#faf8ef}.act-table tbody tr:nth-child(2n){background:#fff}.act-table tbody tr:hover{background:#fff4cc}.act-table .q-td{font-weight:600;line-height:1.25;padding:6px 8px!important}.act-row-success{background:#43a0470f}.act-row-fail{background:#d32f2f0f}.act-badge-ok{background:#43a047}.act-badge-fail{background:#e53935}.act-col-narrow{max-width:90px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.act-col-route{font-size:.8rem;max-width:260px;white-space:normal;word-break:break-word}.act-col-meta{color:#555;font-size:.75rem}.ol-col-piyasa{padding-bottom:6px!important;padding-top:6px!important;vertical-align:top}.piyasa-wrap{align-content:flex-start;column-gap:6px;display:flex;flex-wrap:wrap;max-height:none;overflow:visible;row-gap:4px}.piyasa-chip{flex:0 0 calc(25% - 6px);font-size:11px;font-weight:600;line-height:1.1;max-width:calc(25% - 6px);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.user-detail-page{background:#fafafa}.image-preview{border-radius:6px;width:100%}.image-thumb{border-radius:4px;width:100%}.workorder-page{padding-bottom:80px}.permissions-page{background:#fff;display:flex;flex-direction:column;height:calc(100vh - 56px)}.permissions-table-scroll{background:#fff;flex:1;overflow:auto}.permissions-table{font-size:.85rem}.permissions-table thead th{background:var(--baggi-cream);box-shadow:0 2px 3px #0000000f;color:#222;font-weight:800;position:sticky;top:0;z-index:300}.permissions-table td{color:#333;font-weight:600;padding:6px 8px!important}.permissions-table tbody tr:nth-child(odd){background:#fffef7}.permissions-table tbody tr:nth-child(2n){background:#fff}.permissions-table tbody tr:hover{background:#fff4cc}.permissions-sticky-col{background:#fff;box-shadow:2px 0 4px #0000000a;font-weight:700;left:0;position:sticky;z-index:250}.permissions-table .q-th .column{gap:2px}

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

View File

@@ -1 +0,0 @@
<!DOCTYPE html><html><head><title>Baggi SS</title><meta charset=utf-8><meta name=description content="A Quasar Project"><meta name=format-detection content="telephone=no"><meta name=msapplication-tap-highlight content=no><meta name=viewport content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width"><link rel=icon type=image/png sizes=128x128 href=/icons/favicon-128x128.png><link rel=icon type=image/png sizes=96x96 href=/icons/favicon-96x96.png><link rel=icon type=image/png sizes=32x32 href=/icons/favicon-32x32.png><link rel=icon type=image/png sizes=16x16 href=/icons/favicon-16x16.png><link rel=icon type=image/ico href=/favicon.ico><script defer src=/js/vendor.9ea1812a.js></script><script defer src=/js/app.d0936c73.js></script><link href=/css/vendor.724dcfab.css rel=stylesheet><link href=/css/app.53116624.css rel=stylesheet></head><body><div id=q-app></div></body></html>

9
ui/package-lock.json generated
View File

@@ -19,7 +19,8 @@
}, },
"devDependencies": { "devDependencies": {
"@quasar/app-webpack": "^4.1.0", "@quasar/app-webpack": "^4.1.0",
"autoprefixer": "^10.4.2" "autoprefixer": "^10.4.2",
"baseline-browser-mapping": "^2.9.19"
}, },
"engines": { "engines": {
"node": "^28 || ^26 || ^24 || ^22 || ^20 || ^18", "node": "^28 || ^26 || ^24 || ^22 || ^20 || ^18",
@@ -3855,9 +3856,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/baseline-browser-mapping": { "node_modules/baseline-browser-mapping": {
"version": "2.8.12", "version": "2.9.19",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.12.tgz", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
"integrity": "sha512-vAPMQdnyKCBtkmQA6FMCBvU9qFIppS3nzyXnEM+Lo2IAhG4Mpjv9cCxMudhgV3YdNNJv6TNqXy97dfRVL2LmaQ==", "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {

View File

@@ -23,7 +23,8 @@
}, },
"devDependencies": { "devDependencies": {
"@quasar/app-webpack": "^4.1.0", "@quasar/app-webpack": "^4.1.0",
"autoprefixer": "^10.4.2" "autoprefixer": "^10.4.2",
"baseline-browser-mapping": "^2.9.19"
}, },
"browserslist": [ "browserslist": [
"last 10 Chrome versions", "last 10 Chrome versions",

View File

@@ -2,6 +2,8 @@
import { defineConfig } from '#q-app/wrappers' import { defineConfig } from '#q-app/wrappers'
export default defineConfig(() => { export default defineConfig(() => {
const apiBaseUrl = (process.env.VITE_API_BASE_URL || '/api').trim()
return { return {
/* ===================================================== /* =====================================================
@@ -33,6 +35,9 @@ export default defineConfig(() => {
===================================================== */ ===================================================== */
build: { build: {
vueRouterMode: 'hash', vueRouterMode: 'hash',
env: {
VITE_API_BASE_URL: apiBaseUrl
},
esbuildTarget: { esbuildTarget: {
browser: ['es2022', 'firefox115', 'chrome115', 'safari14'], browser: ['es2022', 'firefox115', 'chrome115', 'safari14'],
@@ -52,14 +57,15 @@ export default defineConfig(() => {
port: 9000, port: 9000,
open: true, open: true,
// DEV proxy (CORSsuz) // DEV proxy (CORS'suz)
proxy: { proxy: [
'/api': { {
context: ['/api'],
target: 'http://localhost:8080', target: 'http://localhost:8080',
changeOrigin: true, changeOrigin: true,
secure: false secure: false
} }
} ]
}, },
/* ===================================================== /* =====================================================
@@ -119,3 +125,4 @@ export default defineConfig(() => {
} }
} }
}) })

View File

@@ -1,83 +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(() => {
return {
// ✅ UYGULAMA KİMLİĞİ (WEB'DE GÖRÜNEN İSİM)
productName: "Baggi BSS",
productDescription: "Baggi Tekstil Business Support System",
// 🔹 Boot dosyaları
boot: ["axios", "dayjs"],
// 🔹 Global CSS
css: ["app.css"],
// 🔹 Ekstra icon/font setleri
extras: [
"roboto-font",
"material-icons"
],
// 🔹 Derleme Ayarları
build: {
vueRouterMode: "hash",
env: {
VITE_API_BASE_URL: "/api"
},
esbuildTarget: {
browser: ["es2022", "firefox115", "chrome115", "safari14"],
node: "node20"
}
},
// 🔹 Geliştirme Sunucusu
devServer: {
server: { type: "http" },
port: 9e3,
open: true
},
// 🔹 Quasar Framework ayarları
framework: {
config: {
notify: { position: "top", timeout: 2500 }
},
lang: "tr",
plugins: ["Loading", "Dialog", "Notify"]
},
animations: [],
ssr: {
prodPort: 3e3,
middlewares: ["render"],
pwa: false
},
pwa: {
workboxMode: "GenerateSW"
},
capacitor: {
hideSplashscreen: true
},
electron: {
preloadScripts: ["electron-preload"],
inspectPort: 5858,
bundler: "packager",
builder: { appId: "baggisowtfaresystem" }
},
bex: {
extraScripts: []
}
};
});
export {
quasar_config_default as default
};

View File

@@ -226,25 +226,25 @@ const menuItems = [
{ {
label: 'Rol + Departman Yetkileri', label: 'Rol + Departman Yetkileri',
to: '/app/role-dept-permissions', to: '/app/role-dept-permissions',
permission: 'user:update' permission: 'system:update'
}, },
{ {
label: 'Kullanıcı Yetkileri', label: 'Kullanıcı Yetkileri',
to: '/app/user-permissions', to: '/app/user-permissions',
permission: 'user:update' permission: 'system:update'
}, },
{ {
label: 'Loglar', label: 'Loglar',
to: '/app/activity-logs', to: '/app/activity-logs',
permission: 'user:view' permission: 'system:read'
}, },
{ {
label: 'Test Mail', label: 'Test Mail',
to: '/app/test-mail', to: '/app/test-mail',
permission: 'user:insert' permission: 'system:update'
} }
] ]
@@ -258,7 +258,7 @@ const menuItems = [
{ {
label: 'Kullanıcılar', label: 'Kullanıcılar',
to: '/app/users', to: '/app/users',
permission: 'user:view' permission: 'system:read'
} }
] ]
} }

View File

@@ -46,6 +46,9 @@
<q-item clickable @click="selectAllModules"> <q-item clickable @click="selectAllModules">
<q-item-section>Tümünü Seç</q-item-section> <q-item-section>Tümünü Seç</q-item-section>
</q-item> </q-item>
<q-item clickable @click="clearAllModules">
<q-item-section>Tümünü Temizle</q-item-section>
</q-item>
<q-separator /> <q-separator />
<q-item <q-item
v-for="m in store.modules" v-for="m in store.modules"
@@ -78,6 +81,9 @@
<q-item clickable @click="selectAllActionsForActive"> <q-item clickable @click="selectAllActionsForActive">
<q-item-section>Tümünü Seç</q-item-section> <q-item-section>Tümünü Seç</q-item-section>
</q-item> </q-item>
<q-item clickable @click="clearAllActionsForActive">
<q-item-section>Tümünü Temizle</q-item-section>
</q-item>
<q-separator /> <q-separator />
<q-item <q-item
v-for="a in actionsForActiveModule" v-for="a in actionsForActiveModule"
@@ -180,6 +186,7 @@ const canUpdateUser = canUpdate('user')
const selectedModules = ref([]) const selectedModules = ref([])
const selectedActionsByModule = ref({}) const selectedActionsByModule = ref({})
const activeModuleCode = ref('') const activeModuleCode = ref('')
const allowEmptySelection = ref(false)
const actionLabelMap = { const actionLabelMap = {
update: 'Güncelleme', update: 'Güncelleme',
@@ -284,9 +291,15 @@ function syncSelections () {
} }
const selected = selectedModules.value.filter((m) => availableModules.includes(m)) const selected = selectedModules.value.filter((m) => availableModules.includes(m))
selectedModules.value = selected.length ? selected : [...availableModules] if (selected.length) {
selectedModules.value = selected
} else {
selectedModules.value = allowEmptySelection.value ? [] : [...availableModules]
}
if (!selectedModules.value.includes(activeModuleCode.value)) { if (!selectedModules.value.length) {
activeModuleCode.value = ''
} else if (!selectedModules.value.includes(activeModuleCode.value)) {
activeModuleCode.value = selectedModules.value[0] activeModuleCode.value = selectedModules.value[0]
} }
@@ -295,7 +308,7 @@ function syncSelections () {
const allActions = actionsByModule.value[m] || [] const allActions = actionsByModule.value[m] || []
const prev = selectedActionsByModule.value[m] || [] const prev = selectedActionsByModule.value[m] || []
const filtered = prev.filter((a) => allActions.includes(a)) const filtered = prev.filter((a) => allActions.includes(a))
next[m] = filtered.length ? filtered : [...allActions] next[m] = filtered.length ? filtered : (allowEmptySelection.value ? [] : [...allActions])
}) })
selectedActionsByModule.value = next selectedActionsByModule.value = next
} }
@@ -313,6 +326,7 @@ function isModuleSelected (moduleCode) {
} }
function toggleModule (moduleCode, checked) { function toggleModule (moduleCode, checked) {
allowEmptySelection.value = false
const set = new Set(selectedModules.value) const set = new Set(selectedModules.value)
if (checked) { if (checked) {
set.add(moduleCode) set.add(moduleCode)
@@ -321,9 +335,8 @@ function toggleModule (moduleCode, checked) {
} }
selectedModules.value = [...set] selectedModules.value = [...set]
if (!selectedModules.value.length) { if (!selectedModules.value.length) {
selectedModules.value = [moduleCode] activeModuleCode.value = ''
} } else if (!selectedModules.value.includes(activeModuleCode.value)) {
if (!selectedModules.value.includes(activeModuleCode.value)) {
activeModuleCode.value = selectedModules.value[0] activeModuleCode.value = selectedModules.value[0]
} }
syncSelections() syncSelections()
@@ -334,24 +347,31 @@ function onModuleRowClick (moduleCode) {
} }
function selectAllModules () { function selectAllModules () {
allowEmptySelection.value = false
selectedModules.value = (store.modules || []).map((m) => m.value) selectedModules.value = (store.modules || []).map((m) => m.value)
syncSelections() syncSelections()
} }
function clearAllModules () {
allowEmptySelection.value = true
selectedModules.value = []
selectedActionsByModule.value = {}
activeModuleCode.value = ''
syncSelections()
}
function isActionSelected (moduleCode, action) { function isActionSelected (moduleCode, action) {
return (selectedActionsByModule.value[moduleCode] || []).includes(action) return (selectedActionsByModule.value[moduleCode] || []).includes(action)
} }
function toggleAction (moduleCode, action, checked) { function toggleAction (moduleCode, action, checked) {
allowEmptySelection.value = false
const current = new Set(selectedActionsByModule.value[moduleCode] || []) const current = new Set(selectedActionsByModule.value[moduleCode] || [])
if (checked) { if (checked) {
current.add(action) current.add(action)
} else { } else {
current.delete(action) current.delete(action)
} }
if (current.size === 0) {
current.add(action)
}
selectedActionsByModule.value = { selectedActionsByModule.value = {
...selectedActionsByModule.value, ...selectedActionsByModule.value,
[moduleCode]: [...current] [moduleCode]: [...current]
@@ -359,6 +379,7 @@ function toggleAction (moduleCode, action, checked) {
} }
function selectAllActionsForActive () { function selectAllActionsForActive () {
allowEmptySelection.value = false
if (!activeModuleCode.value) return if (!activeModuleCode.value) return
selectedActionsByModule.value = { selectedActionsByModule.value = {
...selectedActionsByModule.value, ...selectedActionsByModule.value,
@@ -366,6 +387,15 @@ function selectAllActionsForActive () {
} }
} }
function clearAllActionsForActive () {
allowEmptySelection.value = true
if (!activeModuleCode.value) return
selectedActionsByModule.value = {
...selectedActionsByModule.value,
[activeModuleCode.value]: []
}
}
const permissionColumns = computed(() => { const permissionColumns = computed(() => {
const cols = [] const cols = []
selectedModules.value.forEach((m) => { selectedModules.value.forEach((m) => {

View File

@@ -224,6 +224,15 @@
filled filled
behavior="menu" behavior="menu"
> >
<template #before-options>
<q-item clickable @click="selectAllPiyasalar">
<q-item-section>Tümünü Seç</q-item-section>
</q-item>
<q-item clickable @click="clearPiyasalar">
<q-item-section>Temizle</q-item-section>
</q-item>
<q-separator />
</template>
<template #selected-item="scope"> <template #selected-item="scope">
<q-chip <q-chip
removable removable
@@ -240,6 +249,8 @@
<q-checkbox <q-checkbox
:model-value="scope.selected" :model-value="scope.selected"
tabindex="-1" tabindex="-1"
@update:model-value="() => scope.toggleOption(scope.opt)"
@click.stop
/> />
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
@@ -352,6 +363,16 @@ const canSendPasswordMail = computed(() => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test((form.value.email || '').trim()) return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test((form.value.email || '').trim())
}) })
function selectAllPiyasalar () {
form.value.piyasalar = (piyasaOptions.value || [])
.map((o) => o.value)
.filter(Boolean)
}
function clearPiyasalar () {
form.value.piyasalar = []
}
/* ================= LIFECYCLE ================= */ /* ================= LIFECYCLE ================= */
watch( watch(
() => userId.value, () => userId.value,

View File

@@ -47,7 +47,12 @@
<template #append> <template #append>
<q-icon name="event" class="cursor-pointer"> <q-icon name="event" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale"> <q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-date v-model="dateFrom" mask="YYYY-MM-DD" locale="tr-TR"/> <q-date
v-model="dateFrom"
mask="YYYY-MM-DD"
locale="tr-TR"
:options="isValidFromDate"
/>
</q-popup-proxy> </q-popup-proxy>
</q-icon> </q-icon>
</template> </template>
@@ -63,7 +68,12 @@
<template #append> <template #append>
<q-icon name="event" class="cursor-pointer"> <q-icon name="event" class="cursor-pointer">
<q-popup-proxy cover transition-show="scale" transition-hide="scale"> <q-popup-proxy cover transition-show="scale" transition-hide="scale">
<q-date v-model="dateTo" mask="YYYY-MM-DD" locale="tr-TR" /> <q-date
v-model="dateTo"
mask="YYYY-MM-DD"
locale="tr-TR"
:options="isValidToDate"
/>
</q-popup-proxy> </q-popup-proxy>
</q-icon> </q-icon>
</template> </template>
@@ -334,6 +344,29 @@ onMounted(async () => {
const dateFrom = ref(dayjs().startOf('year').format('YYYY-MM-DD')) const dateFrom = ref(dayjs().startOf('year').format('YYYY-MM-DD'))
const dateTo = ref(dayjs().format('YYYY-MM-DD')) const dateTo = ref(dayjs().format('YYYY-MM-DD'))
function isValidFromDate (date) {
if (!dateTo.value) return true
return !dayjs(date).isAfter(dayjs(dateTo.value), 'day')
}
function isValidToDate (date) {
if (!dateFrom.value) return true
return !dayjs(date).isBefore(dayjs(dateFrom.value), 'day')
}
function hasInvalidDateRange () {
if (!dateFrom.value || !dateTo.value) return false
return dayjs(dateFrom.value).isAfter(dayjs(dateTo.value), 'day')
}
function notifyInvalidDateRange () {
$q.notify({
type: 'warning',
message: '⚠️ Başlangıç tarihi bitiş tarihinden sonra olamaz.',
position: 'top-right'
})
}
/* Parasal İşlem Tipi */ /* Parasal İşlem Tipi */
const monetaryTypeOptions = [ const monetaryTypeOptions = [
{ label: '1-2 hesap', value: ['1', '2'] }, { label: '1-2 hesap', value: ['1', '2'] },
@@ -375,6 +408,11 @@ async function onFilterClick() {
return return
} }
if (hasInvalidDateRange()) {
notifyInvalidDateRange()
return
}
await statementheaderStore.loadStatements({ await statementheaderStore.loadStatements({
startdate: dateFrom.value, startdate: dateFrom.value,
enddate: dateTo.value, enddate: dateTo.value,
@@ -485,6 +523,11 @@ async function handleDownload() {
return return
} }
if (hasInvalidDateRange()) {
notifyInvalidDateRange()
return
}
// ✅ Seçilen parasal işlem tipini gönder // ✅ Seçilen parasal işlem tipini gönder
const result = await downloadstpdfStore.downloadPDF( const result = await downloadstpdfStore.downloadPDF(
selectedCari.value, // accountCode selectedCari.value, // accountCode
@@ -526,6 +569,11 @@ async function CurrheadDownload() {
return return
} }
if (hasInvalidDateRange()) {
notifyInvalidDateRange()
return
}
// ✅ Yeni store fonksiyonu doğru şekilde çağrılıyor // ✅ Yeni store fonksiyonu doğru şekilde çağrılıyor
const result = await downloadstHeadStore.handlestHeadDownload( const result = await downloadstHeadStore.handlestHeadDownload(
selectedCari.value, // accountCode selectedCari.value, // accountCode

View File

@@ -14,6 +14,9 @@ export default route(function () {
routes routes
}) })
if (typeof window !== 'undefined' && process.env.DEV) {
window.__router = router
}
/* ============================================================ /* ============================================================
🔐 GLOBAL GUARD 🔐 GLOBAL GUARD
@@ -23,6 +26,17 @@ export default route(function () {
const auth = useAuthStore() const auth = useAuthStore()
const perm = usePermissionStore() const perm = usePermissionStore()
if (typeof window !== 'undefined') {
console.warn('🧭 ROUTE GUARD HIT:', {
path: to.fullPath,
meta: to.meta
})
}
if (typeof window !== 'undefined' && process.env.DEV) {
window.__auth = auth
window.__perm = perm
}
/* ================= PUBLIC ================= */ /* ================= PUBLIC ================= */

View File

@@ -69,7 +69,7 @@ const routes = [
path: '', path: '',
name: 'dashboard', name: 'dashboard',
component: () => import('pages/Dashboard.vue'), component: () => import('pages/Dashboard.vue'),
meta: { permission: 'system:read' } meta: {}
}, },
@@ -86,28 +86,28 @@ const routes = [
path: 'role-dept-permissions', path: 'role-dept-permissions',
name: 'role-dept-permissions', name: 'role-dept-permissions',
component: () => import('pages/RoleDepartmentPermissionGateway.vue'), component: () => import('pages/RoleDepartmentPermissionGateway.vue'),
meta: { permission: 'user:update' } meta: { permission: 'system:update' }
}, },
{ {
path: 'role-dept-permissions/list', path: 'role-dept-permissions/list',
name: 'role-dept-permissions-list', name: 'role-dept-permissions-list',
component: () => import('pages/RoleDepartmentPermissionList.vue'), component: () => import('pages/RoleDepartmentPermissionList.vue'),
meta: { permission: 'user:update' } meta: { permission: 'system:update' }
}, },
{ {
path: 'role-dept-permissions/editor', path: 'role-dept-permissions/editor',
name: 'role-dept-permissions-editor', name: 'role-dept-permissions-editor',
component: () => import('pages/RoleDepartmentPermissionPage.vue'), component: () => import('pages/RoleDepartmentPermissionPage.vue'),
meta: { permission: 'user:update' } meta: { permission: 'system:update' }
}, },
{ {
path: 'user-permissions', path: 'user-permissions',
name: 'user-permissions', name: 'user-permissions',
component: () => import('pages/UserPermissionPage.vue'), component: () => import('pages/UserPermissionPage.vue'),
meta: { permission: 'user:update' } meta: { permission: 'system:update' }
}, },
@@ -190,7 +190,7 @@ const routes = [
path: 'activity-logs', path: 'activity-logs',
name: 'activity-logs', name: 'activity-logs',
component: () => import('pages/ActivityLogs.vue'), component: () => import('pages/ActivityLogs.vue'),
meta: { permission: 'user:view' } meta: { permission: 'system:read' }
}, },
@@ -200,7 +200,7 @@ const routes = [
path: 'test-mail', path: 'test-mail',
name: 'test-mail', name: 'test-mail',
component: () => import('pages/TestMail.vue'), component: () => import('pages/TestMail.vue'),
meta: { permission: 'user:insert' } meta: { permission: 'system:update' }
}, },

View File

@@ -2,7 +2,10 @@ import axios from 'axios'
import qs from 'qs' import qs from 'qs'
import { useAuthStore } from 'stores/authStore' import { useAuthStore } from 'stores/authStore'
export const API_BASE_URL = '/api' const rawBaseUrl =
(typeof process !== 'undefined' && process.env?.VITE_API_BASE_URL) || '/api'
export const API_BASE_URL = String(rawBaseUrl).trim().replace(/\/+$/, '')
const AUTH_REFRESH_PATH = '/auth/refresh' const AUTH_REFRESH_PATH = '/auth/refresh'
const api = axios.create({ const api = axios.create({

View File

@@ -78,15 +78,7 @@ export const useAuthStore = defineStore('auth', {
========================================================= */ ========================================================= */
setSession ({ token, user }) { setSession ({ token, user }) {
this.token = token this.token = token
if (user) { this.user = user || null
// Keep prior role fields if backend returns partial user payload.
this.user = {
...(this.user || {}),
...user
}
} else {
this.user = null
}
this.forcePasswordChange = !!user?.force_password_change this.forcePasswordChange = !!user?.force_password_change
localStorage.setItem('token', token) localStorage.setItem('token', token)

View File

@@ -165,13 +165,12 @@ export const usePermissionStore = defineStore('permission', {
try { try {
// API ROUTES const [routesRes, effRes] = await Promise.all([
const routesRes = await api.get('/permissions/routes') api.get('/permissions/routes'),
api.get('/permissions/effective')
])
this.routes = routesRes.data || [] this.routes = routesRes.data || []
// EFFECTIVE MATRIX
const effRes = await api.get('/permissions/effective')
this.matrix = effRes.data || [] this.matrix = effRes.data || []
console.group('🔐 PERMISSION DEBUG') console.group('🔐 PERMISSION DEBUG')