commit eacfacb13b62d7f232a5aa551fced5565b02a5fe
Author: MEHMETKECECI
Date: Wed Feb 11 17:46:22 2026 +0300
ilk
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..98cd9e7
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+backend
\ No newline at end of file
diff --git a/.idea/backend.iml b/.idea/backend.iml
new file mode 100644
index 0000000..7ee078d
--- /dev/null
+++ b/.idea/backend.iml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/bssapp-backend.iml b/.idea/bssapp-backend.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/.idea/bssapp-backend.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/go.imports.xml b/.idea/go.imports.xml
new file mode 100644
index 0000000..d7202f0
--- /dev/null
+++ b/.idea/go.imports.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..7675e2a
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..e92123c
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/svc/.env b/svc/.env
new file mode 100644
index 0000000..d29fb64
--- /dev/null
+++ b/svc/.env
@@ -0,0 +1,8 @@
+JWT_SECRET=bssapp_super_secret_key_1234567890
+PASSWORD_RESET_SECRET=1dc7d6d52fd0459a8b1f288a6590428e760f54339f8e47beb20db36b6df6070b
+APP_FRONTEND_URL=http://localhost:9000
+API_URL=http://localhost:8080
+
+
+
+
diff --git a/svc/auth/claims.go b/svc/auth/claims.go
new file mode 100644
index 0000000..12859e5
--- /dev/null
+++ b/svc/auth/claims.go
@@ -0,0 +1,47 @@
+package auth
+
+import (
+ "strings"
+
+ "github.com/golang-jwt/jwt/v5"
+)
+
+type Claims struct {
+ // ==================================================
+ // 🔑 IDENTITY
+ // ==================================================
+ ID int64 `json:"id"`
+ Username string `json:"username"`
+ RoleID int64 `json:"role_id"`
+
+ RoleCode string `json:"role_code"`
+ DepartmentCodes []string `json:"department_codes"`
+
+ // ==================================================
+ // 🧾 NEBIM (frontend filtre & backend guard için)
+ // ==================================================
+ V3Username string `json:"v3_username"`
+ V3UserGroup string `json:"v3_usergroup"`
+
+ // ==================================================
+ // 🔐 SESSION
+ // ==================================================
+ SessionID string `json:"session_id"`
+
+ // ==================================================
+ // ⚠️ SECURITY
+ // ==================================================
+ ForcePasswordChange bool `json:"force_password_change"`
+
+ jwt.RegisteredClaims
+}
+
+func (c *Claims) IsAdmin() bool {
+ if c == nil {
+ return false
+ }
+
+ role := strings.ToLower(strings.TrimSpace(c.RoleCode))
+
+ return role == "admin"
+}
diff --git a/svc/auth/claims_mapper.go b/svc/auth/claims_mapper.go
new file mode 100644
index 0000000..fdb1d83
--- /dev/null
+++ b/svc/auth/claims_mapper.go
@@ -0,0 +1,36 @@
+package auth
+
+import (
+ "time"
+
+ "bssapp-backend/models"
+
+ "github.com/golang-jwt/jwt/v5"
+)
+
+func BuildClaimsFromUser(u *models.MkUser, ttl time.Duration) Claims {
+ now := time.Now()
+
+ return Claims{
+ // 🔴 mk_dfusr.id
+ ID: u.ID,
+
+ Username: u.Username,
+ RoleCode: u.RoleCode,
+ RoleID: u.RoleID,
+ // ✅ BURASI
+ DepartmentCodes: u.DepartmentCodes,
+
+ SessionID: u.SessionID,
+
+ ForcePasswordChange: u.ForcePasswordChange,
+
+ RegisteredClaims: jwt.RegisteredClaims{
+ Issuer: "bssapp",
+ Subject: u.Username,
+ IssuedAt: jwt.NewNumericDate(now),
+ NotBefore: jwt.NewNumericDate(now),
+ ExpiresAt: jwt.NewNumericDate(now.Add(ttl)),
+ },
+ }
+}
diff --git a/svc/auth/context.go b/svc/auth/context.go
new file mode 100644
index 0000000..d407d33
--- /dev/null
+++ b/svc/auth/context.go
@@ -0,0 +1,15 @@
+package auth
+
+import (
+ "bssapp-backend/ctxkeys"
+ "context"
+)
+
+func WithClaims(ctx context.Context, claims *Claims) context.Context {
+ return context.WithValue(ctx, ctxkeys.UserContextKey, claims)
+}
+
+func GetClaimsFromContext(ctx context.Context) (*Claims, bool) {
+ claims, ok := ctx.Value(ctxkeys.UserContextKey).(*Claims)
+ return claims, ok
+}
diff --git a/svc/auth/jwt.go b/svc/auth/jwt.go
new file mode 100644
index 0000000..afaa5aa
--- /dev/null
+++ b/svc/auth/jwt.go
@@ -0,0 +1,53 @@
+package auth
+
+import (
+ "errors"
+ "os"
+
+ "github.com/golang-jwt/jwt/v5"
+)
+
+// package auth
+
+func jwtSecret() ([]byte, error) {
+ sec := os.Getenv("JWT_SECRET")
+ if len(sec) < 10 {
+ return nil, errors.New("JWT_SECRET environment boş veya çok kısa")
+ }
+ return []byte(sec), nil
+}
+
+// ✅ TEK VE DOĞRU TOKEN ÜRETİCİ
+func GenerateToken(claims Claims, username string, change bool) (string, error) {
+ secret, err := jwtSecret()
+ if err != nil {
+ return "", err
+ }
+
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ return token.SignedString(secret)
+}
+
+func ValidateToken(tokenStr string) (*Claims, error) {
+ secret, err := jwtSecret()
+ if err != nil {
+ return nil, err
+ }
+
+ token, err := jwt.ParseWithClaims(
+ tokenStr,
+ &Claims{},
+ func(token *jwt.Token) (interface{}, error) {
+ return secret, nil
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ claims, ok := token.Claims.(*Claims)
+ if !ok || !token.Valid {
+ return nil, errors.New("token geçersiz")
+ }
+ return claims, nil
+}
diff --git a/svc/auth/logout.go b/svc/auth/logout.go
new file mode 100644
index 0000000..c37d427
--- /dev/null
+++ b/svc/auth/logout.go
@@ -0,0 +1,44 @@
+package auth
+
+import (
+ "bssapp-backend/internal/auditlog"
+ "bssapp-backend/repository"
+ "database/sql"
+ "encoding/json"
+ "net/http"
+ "time"
+)
+
+func LogoutAllHandler(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ claims, ok := GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ userID := claims.ID
+
+ _ = repository.NewRefreshTokenRepository(db).
+ RevokeAllForUser(userID)
+
+ http.SetCookie(w, &http.Cookie{
+ Name: "mk_refresh",
+ Value: "",
+ Path: "/",
+ Expires: time.Unix(0, 0),
+ HttpOnly: true,
+ })
+
+ auditlog.Write(auditlog.ActivityLog{
+ UserID: auditlog.IntUserIDToUUID(int(userID)),
+ ActionType: "logout_all",
+ ActionCategory: "auth",
+ Description: "user logged out from all devices",
+ IsSuccess: true,
+ })
+
+ _ = json.NewEncoder(w).Encode(map[string]bool{"success": true})
+ }
+}
diff --git a/svc/config/config.go b/svc/config/config.go
new file mode 100644
index 0000000..d912156
--- /dev/null
+++ b/svc/config/config.go
@@ -0,0 +1 @@
+package config
diff --git a/svc/ctxkeys/keys.go b/svc/ctxkeys/keys.go
new file mode 100644
index 0000000..2ebfdd1
--- /dev/null
+++ b/svc/ctxkeys/keys.go
@@ -0,0 +1,5 @@
+package ctxkeys
+
+type ContextKey string
+
+const UserContextKey ContextKey = "jwt_claims"
diff --git a/svc/db/mssql.go b/svc/db/mssql.go
new file mode 100644
index 0000000..1917147
--- /dev/null
+++ b/svc/db/mssql.go
@@ -0,0 +1,31 @@
+package db
+
+import (
+ "database/sql"
+ "fmt"
+ "log"
+
+ _ "github.com/microsoft/go-mssqldb"
+)
+
+var MssqlDB *sql.DB
+
+func ConnectMSSQL() {
+ connString := "sqlserver://sa:Gil_0150@10.0.0.9:1433?databaseName=BAGGI_V3"
+
+ var err error
+ MssqlDB, err = sql.Open("sqlserver", connString)
+ if err != nil {
+ log.Fatal("MSSQL bağlantı hatası:", err)
+ }
+
+ err = MssqlDB.Ping()
+ if err != nil {
+ log.Fatal("MSSQL erişilemiyor:", err)
+ }
+
+ fmt.Println("✅ MSSQL bağlantısı başarılı!")
+}
+func GetDB() *sql.DB {
+ return MssqlDB
+}
diff --git a/svc/db/postgres.go b/svc/db/postgres.go
new file mode 100644
index 0000000..b24cfa8
--- /dev/null
+++ b/svc/db/postgres.go
@@ -0,0 +1,68 @@
+package db
+
+import (
+ "database/sql"
+ "fmt"
+ "log"
+ "os"
+ "time"
+
+ _ "github.com/lib/pq"
+)
+
+var PgDB *sql.DB
+
+// ConnectPostgres → PostgreSQL veritabanına bağlanır
+func ConnectPostgres() (*sql.DB, error) {
+ // Bağlantı stringi (istersen .env’den oku)
+ connStr := os.Getenv("POSTGRES_CONN")
+ if connStr == "" {
+ // fallback → sabit tanımlı bağlantı
+ connStr = "host=172.16.0.3 port=5432 user=postgres password=tayitkan dbname=baggib2b sslmode=disable"
+ }
+
+ db, err := sql.Open("postgres", connStr)
+ if err != nil {
+ return nil, fmt.Errorf("PostgreSQL bağlantı hatası: %w", err)
+ }
+
+ // =======================================================
+ // 🔹 BAĞLANTI HAVUZU (AUDIT LOG UYUMLU)
+ // =======================================================
+ db.SetMaxOpenConns(30) // audit + api paralel çalışsın
+ db.SetMaxIdleConns(10)
+ db.SetConnMaxLifetime(30 * time.Minute)
+ db.SetConnMaxIdleTime(5 * time.Minute) // 🔥 uzun idle audit bağlantılarını kapat
+
+ // 🔹 Test et
+ if err = db.Ping(); err != nil {
+ return nil, fmt.Errorf("PostgreSQL erişilemiyor: %w", err)
+ }
+
+ log.Println("✅ PostgreSQL bağlantısı başarılı!")
+ PgDB = db
+ return db, nil
+
+}
+
+// GetPostgresUsers → test amaçlı ilk 5 kullanıcıyı listeler
+func GetPostgresUsers(db *sql.DB) error {
+ query := `SELECT id, code, email FROM mk_dfusr ORDER BY id LIMIT 5`
+ rows, err := db.Query(query)
+ if err != nil {
+ return fmt.Errorf("PostgreSQL sorgu hatası: %w", err)
+ }
+ defer rows.Close()
+
+ fmt.Println("📋 İlk 5 PostgreSQL kullanıcısı:")
+ for rows.Next() {
+ var id int
+ var code, email string
+ if err := rows.Scan(&id, &code, &email); err != nil {
+ return err
+ }
+ fmt.Printf(" ➜ ID: %-4d | USER: %-20s | EMAIL: %s\n", id, code, email)
+ }
+
+ return rows.Err()
+}
diff --git a/svc/deneme b/svc/deneme
new file mode 100644
index 0000000..e69de29
diff --git a/svc/fonts/DejaVuSans-Bold.ttf b/svc/fonts/DejaVuSans-Bold.ttf
new file mode 100644
index 0000000..6d65fa7
Binary files /dev/null and b/svc/fonts/DejaVuSans-Bold.ttf differ
diff --git a/svc/fonts/DejaVuSans.ttf b/svc/fonts/DejaVuSans.ttf
new file mode 100644
index 0000000..e5f7eec
Binary files /dev/null and b/svc/fonts/DejaVuSans.ttf differ
diff --git a/svc/fonts/FreeSans.ttf b/svc/fonts/FreeSans.ttf
new file mode 100644
index 0000000..e69de29
diff --git a/svc/fonts/FreeSansBold.ttf b/svc/fonts/FreeSansBold.ttf
new file mode 100644
index 0000000..e69de29
diff --git a/svc/go.mod b/svc/go.mod
new file mode 100644
index 0000000..8273984
--- /dev/null
+++ b/svc/go.mod
@@ -0,0 +1,30 @@
+module bssapp-backend
+
+go 1.24.0 // senin Go versiyonuna göre değişir
+
+toolchain go1.24.5
+
+require (
+ github.com/golang-jwt/jwt/v5 v5.2.2
+ github.com/google/uuid v1.6.0
+ github.com/gorilla/mux v1.8.1
+ github.com/joho/godotenv v1.5.1
+ github.com/jung-kurt/gofpdf v1.16.2
+ github.com/lib/pq v1.10.9
+ github.com/microsoft/go-mssqldb v1.9.3
+ github.com/xuri/excelize/v2 v2.10.0
+ golang.org/x/crypto v0.43.0
+ golang.org/x/oauth2 v0.34.0
+)
+
+require (
+ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
+ github.com/golang-sql/sqlexp v0.1.0 // indirect
+ github.com/richardlehane/mscfb v1.0.4 // indirect
+ github.com/richardlehane/msoleps v1.0.4 // indirect
+ github.com/tiendc/go-deepcopy v1.7.1 // indirect
+ github.com/xuri/efp v0.0.1 // indirect
+ github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
+ golang.org/x/net v0.46.0 // indirect
+ golang.org/x/text v0.30.0 // indirect
+)
diff --git a/svc/go.sum b/svc/go.sum
new file mode 100644
index 0000000..903d9dd
--- /dev/null
+++ b/svc/go.sum
@@ -0,0 +1,76 @@
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
+github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4=
+github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
+github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI=
+github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
+github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
+github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
+github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
+github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
+github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
+github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
+github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
+github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc=
+github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
+github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/microsoft/go-mssqldb v1.9.3 h1:hy4p+LDC8LIGvI3JATnLVmBOLMJbmn5X400mr5j0lPs=
+github.com/microsoft/go-mssqldb v1.9.3/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA=
+github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
+github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
+github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
+github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
+github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
+github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
+github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
+github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/tiendc/go-deepcopy v1.7.1 h1:LnubftI6nYaaMOcaz0LphzwraqN8jiWTwm416sitff4=
+github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
+github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
+github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
+github.com/xuri/excelize/v2 v2.10.0 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstfW4=
+github.com/xuri/excelize/v2 v2.10.0/go.mod h1:SC5TzhQkaOsTWpANfm+7bJCldzcnU/jrhqkTi/iBHBU=
+github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
+github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
+golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
+golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
+golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
+golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
+golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
+golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
+golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
+golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
+golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
+golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
+golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/svc/internal/auditlog/events.go b/svc/internal/auditlog/events.go
new file mode 100644
index 0000000..068ed48
--- /dev/null
+++ b/svc/internal/auditlog/events.go
@@ -0,0 +1,35 @@
+package auditlog
+
+import (
+ "context"
+)
+
+func ForcePasswordChangeStarted(
+ ctx context.Context,
+ targetUserID int64,
+ reason string, // admin_reset | login_enforced
+) {
+ Write(ActivityLog{
+ UserID: IntUserIDToUUID(int(targetUserID)),
+ ActionType: "force_password_change_started",
+ ActionCategory: "auth",
+ Description: "kullanıcı için zorunlu parola değişimi başlatıldı",
+ IsSuccess: true,
+ ErrorMessage: reason,
+ })
+}
+
+func ForcePasswordChangeCompleted(
+ ctx context.Context,
+ userID int64,
+ source string, // reset_link | self_change | admin_reset
+) {
+ Write(ActivityLog{
+ UserID: IntUserIDToUUID(int(userID)),
+ ActionType: "force_password_change_completed",
+ ActionCategory: "auth",
+ Description: "kullanıcı parolasını başarıyla güncelledi",
+ IsSuccess: true,
+ ErrorMessage: source,
+ })
+}
diff --git a/svc/internal/auditlog/helpers.go b/svc/internal/auditlog/helpers.go
new file mode 100644
index 0000000..ea023f5
--- /dev/null
+++ b/svc/internal/auditlog/helpers.go
@@ -0,0 +1,73 @@
+package auditlog
+
+import (
+ "crypto/md5"
+ "fmt"
+ "time"
+)
+
+//
+// =======================================================
+// 🕵️ AUDIT LOG — HELPER FUNCTIONS
+// =======================================================
+// Bu dosya:
+// - UUID bekleyen kolonlar için int → uuid dönüşümü
+// - NULL-safe insert yardımcıları
+// içerir
+//
+
+// -------------------------------------------------------
+// 🔹 IntUserIDToUUID
+// -------------------------------------------------------
+// int user_id → deterministic UUID
+// PostgreSQL uuid kolonu ile %100 uyumlu
+//
+// Aynı userID → her zaman aynı UUID
+func IntUserIDToUUID(userID int) string {
+ if userID <= 0 {
+ return ""
+ }
+
+ sum := md5.Sum([]byte(fmt.Sprintf("bssapp-user-%d", userID)))
+
+ return fmt.Sprintf(
+ "%x-%x-%x-%x-%x",
+ sum[0:4],
+ sum[4:6],
+ sum[6:8],
+ sum[8:10],
+ sum[10:16],
+ )
+}
+
+// -------------------------------------------------------
+// 🔹 nullIfZeroTime
+// -------------------------------------------------------
+// Zero time → NULL (SQL uyumlu)
+func nullIfZeroTime(t time.Time) interface{} {
+ if t.IsZero() {
+ return nil
+ }
+ return t
+}
+
+// -------------------------------------------------------
+// 🔹 nullIfZeroInt
+// -------------------------------------------------------
+// 0 → NULL (SQL uyumlu)
+func nullIfZeroInt(v int) interface{} {
+ if v == 0 {
+ return nil
+ }
+ return v
+}
+
+func Int64UserIDToUUID(id int64) string {
+ return IntUserIDToUUID(int(id))
+}
+func nullIfEmpty(s string) any {
+ if s == "" {
+ return nil
+ }
+ return s
+}
diff --git a/svc/internal/auditlog/init.go b/svc/internal/auditlog/init.go
new file mode 100644
index 0000000..c3c3ee4
--- /dev/null
+++ b/svc/internal/auditlog/init.go
@@ -0,0 +1,30 @@
+package auditlog
+
+import (
+ "database/sql"
+ "log"
+ "sync"
+)
+
+var (
+ logQueue chan ActivityLog
+ dbConn *sql.DB
+ once sync.Once
+)
+
+// Init → main.go içinden çağrılacak (tek sefer)
+func Init(db *sql.DB, bufferSize int) {
+ log.Println("🟢 auditlog Init called, buffer:", bufferSize)
+
+ dbConn = db
+ logQueue = make(chan ActivityLog, bufferSize)
+
+ go logWorker()
+}
+
+// Optional: app kapanırken flush/stop istersen
+func Close() {
+ if logQueue != nil {
+ close(logQueue)
+ }
+}
diff --git a/svc/internal/auditlog/model.go b/svc/internal/auditlog/model.go
new file mode 100644
index 0000000..e9aaa47
--- /dev/null
+++ b/svc/internal/auditlog/model.go
@@ -0,0 +1,37 @@
+package auditlog
+
+import "time"
+
+type ActivityLog struct {
+ // identity
+ UserID string // UUID (auth)
+ DfUsrID int64 // DF user id (mk_dfusr.id)
+
+ Username string
+ RoleCode string
+
+ // action
+ ActionType string
+ ActionCategory string
+ ActionTarget string
+ Description string
+
+ // tech
+ IpAddress string
+ UserAgent string
+ SessionID string
+
+ // timing
+ RequestStartedAt time.Time
+ RequestFinishedAt time.Time
+ DurationMs int
+ HttpStatus int
+
+ // result
+ IsSuccess bool
+ ErrorMessage string
+ TargetDfUsrID int64
+ TargetUsername string
+ ChangeBefore any // map[string]any
+ ChangeAfter any
+}
diff --git a/svc/internal/auditlog/worker.go b/svc/internal/auditlog/worker.go
new file mode 100644
index 0000000..354e02e
--- /dev/null
+++ b/svc/internal/auditlog/worker.go
@@ -0,0 +1,141 @@
+package auditlog
+
+import (
+ "encoding/json"
+ "log"
+)
+
+func toJSONB(v any) any {
+ if v == nil {
+ return nil
+ }
+ b, err := json.Marshal(v)
+ if err != nil {
+ // JSON marshal hata olursa log’u bozmayalım
+ log.Println("⚠️ auditlog json marshal error:", err)
+ return nil
+ }
+ return b // pq jsonb için []byte kabul eder
+}
+
+func logWorker() {
+ log.Println("🟢 auditlog worker STARTED")
+
+ for entry := range logQueue {
+
+ // ---------- DFUSR_ID ----------
+ var dfusrID any
+ if entry.DfUsrID > 0 {
+ dfusrID = entry.DfUsrID
+ } else {
+ dfusrID = nil
+ }
+
+ // ---------- USERNAME ----------
+ var username any
+ if entry.Username != "" {
+ username = entry.Username
+ } else {
+ username = nil
+ }
+
+ // ---------- ROLE CODE (SNAPSHOT) ----------
+ roleCode := entry.RoleCode
+ if roleCode == "" {
+ roleCode = "public"
+ }
+
+ // ---------- TARGET ----------
+ var targetDfUsrID any
+ if entry.TargetDfUsrID > 0 {
+ targetDfUsrID = entry.TargetDfUsrID
+ } else {
+ targetDfUsrID = nil
+ }
+ targetUsername := nullIfEmpty(entry.TargetUsername)
+
+ log.Printf(
+ "🧾 auditlog INSERT | actor_dfusr=%v actor_user=%v role=%s %s %s target=%v",
+ dfusrID,
+ username,
+ roleCode,
+ entry.ActionCategory,
+ entry.ActionTarget,
+ targetDfUsrID,
+ )
+
+ _, err := dbConn.Exec(`
+ INSERT INTO mk_user_activity_log (
+ log_id,
+ dfusr_id,
+ username,
+ role_code,
+
+ action_type,
+ action_category,
+ action_target,
+ description,
+
+ ip_address,
+ user_agent,
+ session_id,
+
+ request_started_at,
+ request_finished_at,
+ duration_ms,
+ http_status,
+
+ is_success,
+ error_message,
+
+ -- ✅ NEW
+ target_dfusr_id,
+ target_username,
+ change_before,
+ change_after,
+
+ created_at
+ ) VALUES (
+ gen_random_uuid(),
+ $1,$2,$3,
+ $4,$5,$6,$7,
+ $8,$9,$10,
+ $11,$12,$13,$14,
+ $15,$16,
+ $17,$18,$19,$20,
+ now()
+ )
+ `,
+ dfusrID,
+ username,
+ roleCode,
+
+ entry.ActionType,
+ entry.ActionCategory,
+ entry.ActionTarget,
+ entry.Description,
+
+ entry.IpAddress,
+ entry.UserAgent,
+ entry.SessionID,
+
+ nullIfZeroTime(entry.RequestStartedAt),
+ nullIfZeroTime(entry.RequestFinishedAt),
+ nullIfZeroInt(entry.DurationMs),
+ nullIfZeroInt(entry.HttpStatus),
+
+ entry.IsSuccess,
+ entry.ErrorMessage,
+
+ // ✅ NEW
+ targetDfUsrID,
+ targetUsername,
+ toJSONB(entry.ChangeBefore),
+ toJSONB(entry.ChangeAfter),
+ )
+
+ if err != nil {
+ log.Println("❌ auditlog insert error:", err)
+ }
+ }
+}
diff --git a/svc/internal/auditlog/writer.go b/svc/internal/auditlog/writer.go
new file mode 100644
index 0000000..5969e4f
--- /dev/null
+++ b/svc/internal/auditlog/writer.go
@@ -0,0 +1,25 @@
+package auditlog
+
+import "context"
+
+func Write(log ActivityLog) {
+ if logQueue == nil {
+ return // sistem henüz init edilmediyse sessizce çık
+ }
+
+ select {
+ case logQueue <- log:
+ // kuyruğa alındı
+ default:
+ // kuyruk dolu → drop edilir, ana akış bozulmaz
+ }
+}
+func Enqueue(ctx context.Context, al ActivityLog) {
+
+ select {
+ case logQueue <- al:
+ // ok
+ default:
+ // queue dolu → drop
+ }
+}
diff --git a/svc/internal/authz/context.go b/svc/internal/authz/context.go
new file mode 100644
index 0000000..ea81a32
--- /dev/null
+++ b/svc/internal/authz/context.go
@@ -0,0 +1,36 @@
+package authz
+
+import "context"
+
+type scopeKey string
+
+const (
+ CtxDeptCodesKey scopeKey = "authz.dept_codes"
+ CtxPiyasaCodesKey scopeKey = "authz.piyasa_codes"
+)
+
+func WithDeptCodes(ctx context.Context, codes []string) context.Context {
+ return context.WithValue(ctx, CtxDeptCodesKey, codes)
+}
+
+func WithPiyasaCodes(ctx context.Context, codes []string) context.Context {
+ return context.WithValue(ctx, CtxPiyasaCodesKey, codes)
+}
+
+func GetDeptCodesFromCtx(ctx context.Context) []string {
+ if v := ctx.Value(CtxDeptCodesKey); v != nil {
+ if codes, ok := v.([]string); ok {
+ return codes
+ }
+ }
+ return nil
+}
+
+func GetPiyasaCodesFromCtx(ctx context.Context) []string {
+ if v := ctx.Value(CtxPiyasaCodesKey); v != nil {
+ if codes, ok := v.([]string); ok {
+ return codes
+ }
+ }
+ return nil
+}
diff --git a/svc/internal/authz/mssql.go b/svc/internal/authz/mssql.go
new file mode 100644
index 0000000..a8c0fb2
--- /dev/null
+++ b/svc/internal/authz/mssql.go
@@ -0,0 +1,32 @@
+package authz
+
+import (
+ "context"
+ "fmt"
+ "strings"
+)
+
+func BuildMSSQLPiyasaFilter(
+ ctx context.Context,
+ column string,
+) string {
+
+ codes := GetPiyasaCodesFromCtx(ctx)
+
+ if len(codes) == 0 {
+ return "1=1"
+
+ }
+
+ var quoted []string
+
+ for _, c := range codes {
+ quoted = append(quoted, "'"+c+"'")
+ }
+
+ return fmt.Sprintf(
+ "%s IN (%s)",
+ column,
+ strings.Join(quoted, ","),
+ )
+}
diff --git a/svc/internal/authz/mssql_helpers.go b/svc/internal/authz/mssql_helpers.go
new file mode 100644
index 0000000..cb20bdf
--- /dev/null
+++ b/svc/internal/authz/mssql_helpers.go
@@ -0,0 +1,24 @@
+package authz
+
+import (
+ "fmt"
+ "strings"
+)
+
+func BuildINClause(column string, codes []string) string {
+ if len(codes) == 0 {
+ return "1=0"
+ }
+ var quoted []string
+ for _, c := range codes {
+ c = strings.TrimSpace(strings.ToUpper(c))
+ if c == "" {
+ continue
+ }
+ quoted = append(quoted, "'"+c+"'")
+ }
+ if len(quoted) == 0 {
+ return "1=0"
+ }
+ return fmt.Sprintf("%s IN (%s)", column, strings.Join(quoted, ","))
+}
diff --git a/svc/internal/authz/piyasa_repo.go b/svc/internal/authz/piyasa_repo.go
new file mode 100644
index 0000000..7784328
--- /dev/null
+++ b/svc/internal/authz/piyasa_repo.go
@@ -0,0 +1,74 @@
+package authz
+
+import (
+ "database/sql"
+ "fmt"
+ "sync"
+)
+
+// =====================================================
+// 🧠 PIYASA CACHE (USER → CODES)
+// =====================================================
+
+var (
+ piyasaCache = make(map[int][]string)
+ piyasaMu sync.RWMutex
+)
+
+// =====================================================
+// 📌 GET USER PIYASA CODES (CACHED)
+// =====================================================
+
+func GetUserPiyasaCodes(pg *sql.DB, userID int) ([]string, error) {
+
+ // -----------------------------
+ // CACHE READ
+ // -----------------------------
+ piyasaMu.RLock()
+ if it, ok := piyasaCache[userID]; ok {
+ piyasaMu.RUnlock()
+ return it, nil
+ }
+ piyasaMu.RUnlock()
+
+ // -----------------------------
+ // DB QUERY
+ // -----------------------------
+ rows, err := pg.Query(`
+ SELECT piyasa_code
+ FROM dfusr_piyasa
+ WHERE dfusr_id = $1
+ AND is_allowed = true
+ `, userID)
+ if err != nil {
+ return nil, fmt.Errorf("pg piyasa query error: %w", err)
+ }
+ defer rows.Close()
+
+ var out []string
+ for rows.Next() {
+ var code string
+ if err := rows.Scan(&code); err == nil {
+ out = append(out, code)
+ }
+ }
+
+ // -----------------------------
+ // CACHE WRITE
+ // -----------------------------
+ piyasaMu.Lock()
+ piyasaCache[userID] = out
+ piyasaMu.Unlock()
+
+ return out, nil
+}
+
+// =====================================================
+// 🧹 CLEAR USER PIYASA CACHE
+// =====================================================
+
+func ClearPiyasaCache(userID int) {
+ piyasaMu.Lock()
+ defer piyasaMu.Unlock()
+ delete(piyasaCache, userID)
+}
diff --git a/svc/internal/mailer/config.go b/svc/internal/mailer/config.go
new file mode 100644
index 0000000..d8b48f3
--- /dev/null
+++ b/svc/internal/mailer/config.go
@@ -0,0 +1,50 @@
+package mailer
+
+import (
+ "os"
+ "strconv"
+ "strings"
+)
+
+type Config struct {
+ Host string
+ Port int
+ Username string
+ Password string
+ From string
+ StartTLS bool
+}
+
+func ConfigFromEnv() (Config, error) {
+ var cfg Config
+
+ cfg.Host = strings.TrimSpace(os.Getenv("SMTP_HOST"))
+ cfg.Username = strings.TrimSpace(os.Getenv("SMTP_USERNAME"))
+ cfg.Password = os.Getenv("SMTP_PASSWORD")
+ cfg.From = strings.TrimSpace(os.Getenv("SMTP_FROM"))
+
+ portStr := strings.TrimSpace(os.Getenv("SMTP_PORT"))
+ if portStr == "" {
+ cfg.Port = 587
+ } else {
+ p, err := strconv.Atoi(portStr)
+ if err != nil {
+ return Config{}, err
+ }
+ cfg.Port = p
+ }
+
+ startTLS := strings.TrimSpace(os.Getenv("SMTP_STARTTLS"))
+ if startTLS == "" {
+ cfg.StartTLS = true
+ } else {
+ cfg.StartTLS = strings.EqualFold(startTLS, "true") || startTLS == "1"
+ }
+
+ // minimal validation
+ if cfg.Host == "" || cfg.Username == "" || cfg.Password == "" || cfg.From == "" || cfg.Port <= 0 {
+ return Config{}, ErrInvalidConfig
+ }
+
+ return cfg, nil
+}
diff --git a/svc/internal/mailer/graph_mailer.go b/svc/internal/mailer/graph_mailer.go
new file mode 100644
index 0000000..0d1c9da
--- /dev/null
+++ b/svc/internal/mailer/graph_mailer.go
@@ -0,0 +1,270 @@
+package mailer
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "os"
+ "strconv"
+ "strings"
+ "time"
+
+ "golang.org/x/oauth2/clientcredentials"
+)
+
+type GraphMailer struct {
+ httpClient *http.Client
+ from string // noreply@baggi.com.tr
+ replyTo string // opsiyonel
+}
+
+func NewGraphMailer() (*GraphMailer, error) {
+ tenantID := strings.TrimSpace(os.Getenv("AZURE_TENANT_ID"))
+ clientID := strings.TrimSpace(os.Getenv("AZURE_CLIENT_ID"))
+ clientSecret := strings.TrimSpace(os.Getenv("AZURE_CLIENT_SECRET"))
+ from := strings.TrimSpace(os.Getenv("MAIL_FROM"))
+ replyTo := strings.TrimSpace(os.Getenv("MAIL_REPLY_TO")) // opsiyonel
+
+ if tenantID == "" || clientID == "" || clientSecret == "" || from == "" {
+ return nil, fmt.Errorf("azure graph mailer env missing (AZURE_TENANT_ID/AZURE_CLIENT_ID/AZURE_CLIENT_SECRET/MAIL_FROM)")
+ }
+
+ conf := clientcredentials.Config{
+ ClientID: clientID,
+ ClientSecret: clientSecret,
+ TokenURL: fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantID),
+ Scopes: []string{"https://graph.microsoft.com/.default"},
+ }
+
+ httpClient := conf.Client(context.Background())
+ httpClient.Timeout = 25 * time.Second
+
+ log.Printf("✉️ Graph Mailer hazır (App-only token) | from=%s", from)
+
+ return &GraphMailer{
+ httpClient: httpClient,
+ from: from,
+ replyTo: replyTo,
+ }, nil
+}
+
+func (g *GraphMailer) Send(ctx context.Context, msg Message) error {
+ start := time.Now()
+
+ // ---------- validate ----------
+ cleanTo := make([]string, 0, len(msg.To))
+ for _, t := range msg.To {
+ t = strings.TrimSpace(t)
+ if t != "" {
+ cleanTo = append(cleanTo, t)
+ }
+ }
+ if len(cleanTo) == 0 {
+ return fmt.Errorf("recipient missing")
+ }
+ subject := strings.TrimSpace(msg.Subject)
+ if subject == "" {
+ return fmt.Errorf("subject missing")
+ }
+
+ // internal-safe adjustments
+ isInternal := allRecipientsInDomain(cleanTo, "baggi.com.tr")
+ if isInternal && !strings.HasPrefix(subject, "[BSSApp]") {
+ subject = "[BSSApp] " + subject
+ }
+
+ html := strings.TrimSpace(msg.BodyHTML)
+ text := strings.TrimSpace(msg.Body)
+
+ // Eğer sadece HTML geldiyse text üret (spam skorunu düşürür)
+ if text == "" && html != "" {
+ text = stripHTMLVerySimple(html)
+ }
+ // Eğer sadece text geldiyse basit HTML üret
+ if html == "" && text != "" {
+ html = "" +
+ htmlEscape(text) + "
"
+ }
+ if html == "" {
+ return fmt.Errorf("body missing (Body or BodyHTML)")
+ }
+
+ log.Printf("✉️ [MAIL] SEND START | from=%s | to=%v | internal=%v | subject=%s", g.from, cleanTo, isInternal, subject)
+
+ // ---------- build recipients ----------
+ type recipient struct {
+ EmailAddress struct {
+ Address string `json:"address"`
+ } `json:"emailAddress"`
+ }
+ toRecipients := make([]recipient, 0, len(cleanTo))
+ for _, m := range cleanTo {
+ r := recipient{}
+ r.EmailAddress.Address = m
+ toRecipients = append(toRecipients, r)
+ }
+
+ // ---------- headers to reduce auto-phish/spam signals ----------
+ headers := []map[string]string{
+ {
+ "name": "X-Mailer",
+ "value": "BSSApp Graph Mailer",
+ },
+ {
+ "name": "X-BSSApp-Internal",
+ "value": strconv.FormatBool(isInternal),
+ },
+ }
+
+ // replyTo (opsiyonel)
+ var replyToRecipients []recipient
+ if strings.TrimSpace(g.replyTo) != "" {
+ rt := recipient{}
+ rt.EmailAddress.Address = strings.TrimSpace(g.replyTo)
+ replyToRecipients = []recipient{rt}
+ }
+
+ // ---------- Graph payload ----------
+ message := map[string]any{
+ "subject": subject,
+ "body": map[string]string{
+ "contentType": "HTML",
+ "content": buildHTML(html, text, isInternal),
+ },
+ "toRecipients": toRecipients,
+ "internetMessageHeaders": headers,
+ "importance": "normal",
+ }
+
+ // replyTo SADECE doluysa ekle
+ if len(replyToRecipients) > 0 {
+ message["replyTo"] = replyToRecipients
+ }
+
+ payload := map[string]any{
+ "message": message,
+ "saveToSentItems": true,
+ }
+
+ b, err := json.Marshal(payload)
+ if err != nil {
+ return fmt.Errorf("json marshal: %w", err)
+ }
+
+ url := fmt.Sprintf(
+ "https://graph.microsoft.com/v1.0/users/%s/sendMail",
+ g.from,
+ )
+
+ req, err := http.NewRequestWithContext(
+ ctx,
+ http.MethodPost,
+ url,
+ bytes.NewBuffer(b),
+ )
+ if err != nil {
+ return fmt.Errorf("new request: %w", err)
+ }
+ req.Header.Set("Content-Type", "application/json")
+
+ res, err := g.httpClient.Do(req)
+ if err != nil {
+ return fmt.Errorf("graph request: %w", err)
+ }
+ defer res.Body.Close()
+
+ if res.StatusCode >= 300 {
+ bodyBytes, _ := io.ReadAll(res.Body)
+ log.Printf(
+ "❌ [MAIL] SEND FAILED | status=%s | body=%s",
+ res.Status,
+ string(bodyBytes),
+ )
+ return fmt.Errorf("graph send mail failed: %s", res.Status)
+ }
+
+ log.Printf(
+ "✅ [MAIL] SEND OK | to=%v | duration=%s",
+ cleanTo,
+ time.Since(start),
+ )
+
+ return nil
+}
+
+// ---------- helpers ----------
+
+func allRecipientsInDomain(to []string, domain string) bool {
+ domain = strings.ToLower(strings.TrimSpace(domain))
+ for _, addr := range to {
+ addr = strings.ToLower(strings.TrimSpace(addr))
+ if !strings.HasSuffix(addr, "@"+domain) {
+ return false
+ }
+ }
+ return true
+}
+
+func buildHTML(htmlBody, textBody string, internal bool) string {
+ // Internal ise daha sade, daha az “marketing-like”
+ if internal {
+ return `
+
+ ` + htmlBody + `
+
+
+ Bu e-posta BSSApp sistemi tarafından otomatik oluşturulmuştur.
+
+
`
+ }
+
+ // External
+ return `
+
+ ` + htmlBody + `
+
`
+}
+
+// Çok basit “html -> text” (tam değil ama yeterli)
+func stripHTMLVerySimple(s string) string {
+ s = strings.ReplaceAll(s, "
", "\n")
+ s = strings.ReplaceAll(s, "
", "\n")
+ s = strings.ReplaceAll(s, "
", "\n")
+ s = strings.ReplaceAll(s, "
", "\n\n")
+ // kaba temizlik
+ for {
+ i := strings.Index(s, "<")
+ j := strings.Index(s, ">")
+ if i >= 0 && j > i {
+ s = s[:i] + s[j+1:]
+ continue
+ }
+ break
+ }
+ return strings.TrimSpace(s)
+}
+
+func htmlEscape(s string) string {
+ replacer := strings.NewReplacer(
+ "&", "&",
+ "<", "<",
+ ">", ">",
+ "\"", """,
+ "'", "'",
+ )
+ return replacer.Replace(s)
+}
+func (g *GraphMailer) SendMail(to string, subject string, html string) error {
+ msg := Message{
+ To: []string{to},
+ Subject: subject,
+ BodyHTML: html,
+ }
+
+ // context burada internal olarak veriliyor
+ return g.Send(context.Background(), msg)
+}
diff --git a/svc/internal/mailer/mailer.go b/svc/internal/mailer/mailer.go
new file mode 100644
index 0000000..c7f32b5
--- /dev/null
+++ b/svc/internal/mailer/mailer.go
@@ -0,0 +1,148 @@
+package mailer
+
+import (
+ "context"
+ "crypto/tls"
+ "errors"
+ "fmt"
+ "net"
+ "net/smtp"
+ "strings"
+ "time"
+)
+
+var ErrInvalidConfig = errors.New("invalid smtp config")
+
+type Mailer struct {
+ cfg Config
+}
+
+type Message struct {
+ To []string
+ Subject string
+ Body string
+ BodyHTML string
+}
+
+func New(cfg Config) *Mailer {
+ return &Mailer{cfg: cfg}
+}
+
+func (m *Mailer) Send(ctx context.Context, msg Message) error {
+ if len(msg.To) == 0 {
+ return errors.New("recipient missing")
+ }
+ if strings.TrimSpace(msg.Subject) == "" {
+ return errors.New("subject missing")
+ }
+
+ // timeout kontrolü (ctx)
+ deadline, ok := ctx.Deadline()
+ if !ok {
+ // default timeout
+ var cancel context.CancelFunc
+ ctx, cancel = context.WithTimeout(ctx, 20*time.Second)
+ defer cancel()
+ deadline, _ = ctx.Deadline()
+ }
+ timeout := time.Until(deadline)
+ if timeout <= 0 {
+ return context.DeadlineExceeded
+ }
+
+ addr := fmt.Sprintf("%s:%d", m.cfg.Host, m.cfg.Port)
+
+ dialer := net.Dialer{Timeout: timeout}
+ conn, err := dialer.DialContext(ctx, "tcp", addr)
+ if err != nil {
+ return fmt.Errorf("smtp dial: %w", err)
+ }
+ defer conn.Close()
+
+ c, err := smtp.NewClient(conn, m.cfg.Host)
+ if err != nil {
+ return fmt.Errorf("smtp client: %w", err)
+ }
+ defer c.Close()
+
+ // STARTTLS
+ if m.cfg.StartTLS {
+ tlsCfg := &tls.Config{
+ ServerName: m.cfg.Host,
+ MinVersion: tls.VersionTLS12,
+ }
+ if err := c.StartTLS(tlsCfg); err != nil {
+ return fmt.Errorf("starttls: %w", err)
+ }
+ }
+
+ // AUTH
+ auth := smtp.PlainAuth("", m.cfg.Username, m.cfg.Password, m.cfg.Host)
+ if err := c.Auth(auth); err != nil {
+ return fmt.Errorf("smtp auth: %w", err)
+ }
+
+ // MAIL FROM
+ if err := c.Mail(m.cfg.From); err != nil {
+ return fmt.Errorf("mail from: %w", err)
+ }
+
+ // RCPT TO
+ for _, rcpt := range msg.To {
+ rcpt = strings.TrimSpace(rcpt)
+ if rcpt == "" {
+ continue
+ }
+ if err := c.Rcpt(rcpt); err != nil {
+ return fmt.Errorf("rcpt %s: %w", rcpt, err)
+ }
+ }
+
+ w, err := c.Data()
+ if err != nil {
+ return fmt.Errorf("data: %w", err)
+ }
+
+ // basit mime
+ contentType := "text/plain; charset=UTF-8"
+ body := msg.Body
+ if strings.TrimSpace(msg.BodyHTML) != "" {
+ contentType = "text/html; charset=UTF-8"
+ body = msg.BodyHTML
+ }
+
+ raw := buildMIME(m.cfg.From, msg.To, msg.Subject, contentType, body)
+
+ _, writeErr := w.Write([]byte(raw))
+ closeErr := w.Close()
+
+ if writeErr != nil {
+ return fmt.Errorf("write body: %w", writeErr)
+ }
+ if closeErr != nil {
+ return fmt.Errorf("close body: %w", closeErr)
+ }
+
+ if err := c.Quit(); err != nil {
+ return fmt.Errorf("quit: %w", err)
+ }
+
+ return nil
+}
+
+func buildMIME(from string, to []string, subject, contentType, body string) string {
+ // Subject UTF-8 basit hali (gerekirse sonra MIME encoded-word ekleriz)
+ headers := []string{
+ "From: " + from,
+ "To: " + strings.Join(to, ", "),
+ "Subject: " + subject,
+ "MIME-Version: 1.0",
+ "Content-Type: " + contentType,
+ "",
+ }
+ return strings.Join(headers, "\r\n") + "\r\n" + body + "\r\n"
+}
+
+type MailerInterface interface {
+ Send(ctx context.Context, msg Message) error
+}
diff --git a/svc/internal/mailer/password_reset.go b/svc/internal/mailer/password_reset.go
new file mode 100644
index 0000000..a8e6a94
--- /dev/null
+++ b/svc/internal/mailer/password_reset.go
@@ -0,0 +1,18 @@
+package mailer
+
+import "fmt"
+
+func (m *GraphMailer) SendPasswordResetMail(toEmail string, resetURL string) error {
+ subject := "Parola Sıfırlama"
+
+ html := fmt.Sprintf(`
+ Merhaba,
+ Parolanızı sıfırlamak için aşağıdaki bağlantıya tıklayın:
+
+ %s
+
+ Bu bağlantı 30 dakika geçerlidir ve tek kullanımlıktır.
+ `, resetURL, resetURL)
+
+ return m.SendMail(toEmail, subject, html)
+}
diff --git a/svc/internal/security/errors.go b/svc/internal/security/errors.go
new file mode 100644
index 0000000..ea8b872
--- /dev/null
+++ b/svc/internal/security/errors.go
@@ -0,0 +1,11 @@
+package security
+
+import "errors"
+
+var (
+ ErrPasswordTooShort = errors.New("password must be at least 8 characters")
+ ErrPasswordUpper = errors.New("password must contain an uppercase letter")
+ ErrPasswordLower = errors.New("password must contain a lowercase letter")
+ ErrPasswordDigit = errors.New("password must contain a digit")
+ ErrPasswordSpecial = errors.New("password must contain a special character")
+)
diff --git a/svc/internal/security/password_policy.go b/svc/internal/security/password_policy.go
new file mode 100644
index 0000000..4b9855c
--- /dev/null
+++ b/svc/internal/security/password_policy.go
@@ -0,0 +1,35 @@
+package security
+
+import (
+ "errors"
+ "regexp"
+ "strings"
+)
+
+var (
+ reUpper = regexp.MustCompile(`[A-Z]`)
+ reLower = regexp.MustCompile(`[a-z]`)
+ reDigit = regexp.MustCompile(`[0-9]`)
+ reSpecial = regexp.MustCompile(`[^A-Za-z0-9]`)
+)
+
+func ValidatePassword(pw string) error {
+ pw = strings.TrimSpace(pw)
+
+ if len(pw) < 8 {
+ return errors.New("Parola en az 8 karakter olmalı")
+ }
+ if !reUpper.MatchString(pw) {
+ return errors.New("Parola en az 1 büyük harf içermeli")
+ }
+ if !reLower.MatchString(pw) {
+ return errors.New("Parola en az 1 küçük harf içermeli")
+ }
+ if !reDigit.MatchString(pw) {
+ return errors.New("Parola en az 1 rakam içermeli")
+ }
+ if !reSpecial.MatchString(pw) {
+ return errors.New("Parola en az 1 özel karakter içermeli")
+ }
+ return nil
+}
diff --git a/svc/internal/security/password_reset.go b/svc/internal/security/password_reset.go
new file mode 100644
index 0000000..d364717
--- /dev/null
+++ b/svc/internal/security/password_reset.go
@@ -0,0 +1,13 @@
+package security
+
+import (
+ "os"
+)
+
+func BuildResetURL(token string) string {
+ base := os.Getenv("FRONTEND_URL")
+ if base == "" {
+ base = "http://localhost:9000"
+ }
+ return base + "/password-reset/" + token
+}
diff --git a/svc/internal/security/refresh_token.go b/svc/internal/security/refresh_token.go
new file mode 100644
index 0000000..c59b14b
--- /dev/null
+++ b/svc/internal/security/refresh_token.go
@@ -0,0 +1,23 @@
+package security
+
+import (
+ "crypto/rand"
+ "crypto/sha256"
+ "encoding/hex"
+)
+
+func GenerateRefreshToken() (plain string, hash string, err error) {
+ b := make([]byte, 32) // 256 bit
+ if _, err = rand.Read(b); err != nil {
+ return
+ }
+ plain = hex.EncodeToString(b)
+ sum := sha256.Sum256([]byte(plain))
+ hash = hex.EncodeToString(sum[:])
+ return
+}
+
+func HashRefreshToken(plain string) string {
+ sum := sha256.Sum256([]byte(plain))
+ return hex.EncodeToString(sum[:])
+}
diff --git a/svc/internal/security/reset_token.go b/svc/internal/security/reset_token.go
new file mode 100644
index 0000000..b1acf47
--- /dev/null
+++ b/svc/internal/security/reset_token.go
@@ -0,0 +1,26 @@
+package security
+
+import (
+ "crypto/rand"
+ "crypto/sha256"
+ "encoding/hex"
+)
+
+func GenerateResetToken() (plain string, hash string, err error) {
+ b := make([]byte, 32) // 256 bit
+ if _, err = rand.Read(b); err != nil {
+ return
+ }
+
+ plain = hex.EncodeToString(b)
+
+ sum := sha256.Sum256([]byte(plain))
+ hash = hex.EncodeToString(sum[:])
+
+ return
+}
+
+func HashToken(plain string) string {
+ sum := sha256.Sum256([]byte(plain))
+ return hex.EncodeToString(sum[:])
+}
diff --git a/svc/mail.env b/svc/mail.env
new file mode 100644
index 0000000..0e9f91e
--- /dev/null
+++ b/svc/mail.env
@@ -0,0 +1,4 @@
+AZURE_TENANT_ID=c8e0675d-1f6e-40f3-ba5f-3d1985b92317
+AZURE_CLIENT_ID=94a134b7-757f-4bcc-9e4b-d577b631a9a3
+AZURE_CLIENT_SECRET=PaW8Q~9NzYXHrESZcKoP6.hRxS.CyQshvJ0Y0czx
+MAIL_FROM=baggiss@baggi.com.tr
diff --git a/svc/main.go b/svc/main.go
new file mode 100644
index 0000000..ca41e3d
--- /dev/null
+++ b/svc/main.go
@@ -0,0 +1,587 @@
+package main
+
+import (
+ "bssapp-backend/db"
+ "bssapp-backend/internal/auditlog"
+ "bssapp-backend/internal/mailer"
+ "bssapp-backend/middlewares"
+ "bssapp-backend/permissions"
+ "bssapp-backend/repository"
+ "bssapp-backend/routes"
+ "database/sql"
+ "log"
+ "net/http"
+ "os"
+ "runtime/debug"
+
+ "github.com/gorilla/mux"
+ "github.com/joho/godotenv"
+)
+
+/*
+===========================================================
+✅ CORS
+===========================================================
+*/
+func enableCORS(h http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Access-Control-Allow-Origin", "http://localhost:9000")
+ w.Header().Set("Access-Control-Allow-Credentials", "true")
+ w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
+ w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
+
+ if r.Method == http.MethodOptions {
+ w.WriteHeader(http.StatusOK)
+ return
+ }
+
+ h.ServeHTTP(w, r)
+ })
+}
+
+/*
+===========================================================
+✅ V3 — Method-aware Route Auto Register
+- mk_sys_routes: (path, method, module_code, action)
+- unique: (path, method)
+- admin auto-allow: mk_sys_role_permissions (role_id=3)
+===========================================================
+*/
+func autoRegisterRouteV3(
+ pg *sql.DB,
+ path string,
+ method string,
+ module string,
+ action string,
+) {
+ tx, err := pg.Begin()
+ if err != nil {
+ log.Println("❌ TX begin error:", err)
+ return
+ }
+ defer tx.Rollback()
+
+ // 1) ROUTE REGISTER (path+method)
+ _, err = tx.Exec(`
+ INSERT INTO mk_sys_routes
+ (path, method, module_code, action)
+ VALUES
+ ($1, $2, $3, $4)
+ ON CONFLICT (path, method) DO UPDATE
+ SET
+ module_code = EXCLUDED.module_code,
+ action = EXCLUDED.action
+ `,
+ path,
+ method,
+ module,
+ action,
+ )
+ if err != nil {
+ log.Printf("❌ Route register error (%s %s): %v", method, path, err)
+ return
+ }
+
+ // 2) ADMIN AUTO PERMISSION (module+action bazlı)
+ _, err = tx.Exec(`
+ INSERT INTO mk_sys_role_permissions
+ (role_id, module_code, action, allowed)
+ SELECT
+ id,
+ $1,
+ $2,
+ true
+ FROM dfrole
+ WHERE id = 3 -- ADMIN
+ ON CONFLICT DO NOTHING
+ `,
+ module,
+ action,
+ )
+ if err != nil {
+ log.Printf("❌ Admin perm seed error (%s %s): %v", method, path, err)
+ return
+ }
+
+ if err := tx.Commit(); err != nil {
+ log.Println("❌ TX commit error:", err)
+ return
+ }
+
+ log.Printf("✅ Route+Perm registered → %s %s [%s:%s]",
+ method, path, module, action,
+ )
+}
+
+/*
+===========================================================
+✅ V3 Route Bind Helper
+- tek satırda: autoRegister + wrap + Handle
+===========================================================
+*/
+func bindV3(
+ r *mux.Router,
+ pg *sql.DB,
+ path string,
+ method string,
+ module string,
+ action string,
+ h http.Handler,
+) {
+ // main
+ autoRegisterRouteV3(pg, path, method, module, action)
+
+ r.Handle(path, h).Methods(method, "OPTIONS")
+}
+
+/*
+===========================================================
+InitRoutes — FULL V3 (Method-aware) PERMISSION EDITION
+===========================================================
+*/
+func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router {
+
+ r := mux.NewRouter()
+
+ /*
+ ===========================================================
+ ✅ wrapV3 (method-aware):
+ - AuthMiddleware (JWT)
+ - panic recover
+ - AuthzGuardByRoute(pgDB) => route(path+method) lookup
+ ===========================================================
+ */
+ wrapV3 := func(h http.Handler) http.Handler {
+ return middlewares.AuthMiddleware(
+ pgDB,
+ http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ defer func() {
+ if rec := recover(); rec != nil {
+ log.Printf("🔥 PANIC %s %s\n%v", r.Method, r.URL.Path, rec)
+ debug.PrintStack()
+ http.Error(w, "internal server error", 500)
+ }
+ }()
+
+ // ✅ method-aware route guard
+ middlewares.AuthzGuardByRoute(pgDB)(h).ServeHTTP(w, r)
+ }),
+ )
+ }
+
+ // ============================================================
+ // PUBLIC (NO AUTHZ)
+ // ============================================================
+ bindV3(r, pgDB,
+ "/api/auth/login", "POST",
+ "auth", "login",
+ http.HandlerFunc(routes.LoginHandler(pgDB)),
+ )
+
+ bindV3(r, pgDB,
+ "/api/auth/refresh", "POST",
+ "auth", "refresh",
+ routes.AuthRefreshHandler(pgDB),
+ )
+
+ // ============================================================
+ // SYSTEM
+ // ============================================================
+ bindV3(r, pgDB,
+ "/api/password/change", "POST",
+ "system", "update",
+ wrapV3(http.HandlerFunc(routes.FirstPasswordChangeHandler(pgDB))),
+ )
+
+ bindV3(r, pgDB,
+ "/api/activity-logs", "GET",
+ "user", "view",
+ wrapV3(routes.AdminActivityLogsHandler(pgDB)),
+ )
+
+ bindV3(r, pgDB,
+ "/api/test-mail", "POST",
+ "user", "insert",
+ wrapV3(routes.TestMailHandler(ml)),
+ )
+
+ // ============================================================
+ // PERMISSIONS
+ // ============================================================
+ rolePerm := "/api/roles/{id}/permissions"
+
+ bindV3(r, pgDB,
+ rolePerm, "GET",
+ "user", "update",
+ wrapV3(routes.GetRolePermissionMatrix(pgDB)),
+ )
+ bindV3(r, pgDB,
+ rolePerm, "POST",
+ "user", "update",
+ wrapV3(routes.SaveRolePermissionMatrix(pgDB)),
+ )
+
+ userPerm := "/api/users/{id}/permissions"
+
+ bindV3(r, pgDB,
+ userPerm, "GET",
+ "user", "update",
+ wrapV3(routes.GetUserPermissionsHandler(pgDB)),
+ )
+ bindV3(r, pgDB,
+ userPerm, "POST",
+ "user", "update",
+ wrapV3(routes.SaveUserPermissionsHandler(pgDB)),
+ )
+
+ // ✅ permissions/routes (system:view)
+ bindV3(r, pgDB,
+ "/api/permissions/routes", "GET",
+ "system", "view",
+ wrapV3(routes.GetUserRoutePermissionsHandler(pgDB)),
+ )
+
+ // ✅ permissions/effective (system:view)
+ bindV3(r, pgDB,
+ "/api/permissions/effective", "GET",
+ "system", "view",
+ wrapV3(routes.GetMyEffectivePermissions(pgDB)),
+ )
+
+ // ✅ my permission matrix (system:view) (Senin tabloda user:view görünüyor; düzeltiyoruz)
+ bindV3(r, pgDB,
+ "/api/permissions/matrix", "GET",
+ "system", "view",
+ wrapV3(routes.GetMyPermissionMatrix(pgDB)),
+ )
+
+ // ============================================================
+ // ROLE + DEPARTMENT PERMISSIONS
+ // ============================================================
+ rdPerm := "/api/roles/{roleId}/departments/{deptCode}/permissions"
+ rdHandler := routes.NewRoleDepartmentPermissionHandler(pgDB)
+
+ bindV3(r, pgDB,
+ rdPerm, "GET",
+ "user", "update",
+ wrapV3(http.HandlerFunc(rdHandler.Get)),
+ )
+ bindV3(r, pgDB,
+ rdPerm, "POST",
+ "user", "update",
+ wrapV3(http.HandlerFunc(rdHandler.Save)),
+ )
+
+ // ============================================================
+ // USERS
+ // ============================================================
+ bindV3(r, pgDB,
+ "/api/users/list", "GET",
+ "user", "view",
+ wrapV3(routes.UserListRoute(pgDB)),
+ )
+
+ bindV3(r, pgDB,
+ "/api/users", "POST",
+ "user", "insert",
+ wrapV3(routes.UserCreateRoute(pgDB)),
+ )
+
+ bindV3(r, pgDB,
+ "/api/users/{id}", "GET",
+ "user", "update",
+ wrapV3(routes.UserDetailRoute(pgDB)),
+ )
+ bindV3(r, pgDB,
+ "/api/users/{id}", "PUT",
+ "user", "update",
+ wrapV3(routes.UserDetailRoute(pgDB)),
+ )
+
+ bindV3(r, pgDB,
+ "/api/users/{id}/admin-reset-password", "POST",
+ "user", "update",
+ wrapV3(routes.AdminResetPasswordHandler(pgDB)),
+ )
+
+ bindV3(r, pgDB,
+ "/api/users/{id}/send-password-mail", "POST",
+ "user", "update",
+ wrapV3(routes.SendPasswordResetMailHandler(pgDB, ml)),
+ )
+
+ // ✅ eski kısayol create endpoint (senin eski main.go’da vardı)
+ bindV3(r, pgDB,
+ "/api/users/create", "POST",
+ "user", "insert",
+ wrapV3(routes.UserCreateRoute(pgDB)),
+ )
+
+ // ============================================================
+ // LOOKUPS
+ // ============================================================
+ lookups := map[string]http.Handler{
+ "/api/lookups/roles": routes.GetRoleLookupRoute(pgDB),
+ "/api/lookups/departments": routes.GetDepartmentLookupRoute(pgDB),
+ "/api/lookups/nebim-users": routes.GetNebimUserLookupRoute(pgDB),
+ "/api/lookups/piyasalar": routes.GetPiyasaLookupRoute(pgDB),
+ "/api/lookups/users-perm": routes.GetUsersForPermissionSelectRoute(pgDB),
+ "/api/lookups/roles-perm": routes.GetRolesForPermissionSelectRoute(pgDB),
+ "/api/lookups/departments-perm": routes.GetDepartmentsForPermissionSelectRoute(pgDB),
+ "/api/lookups/modules": routes.GetModuleLookupRoute(pgDB),
+ }
+
+ for path, handler := range lookups {
+ bindV3(r, pgDB,
+ path, "GET",
+ "user", "view",
+ wrapV3(handler),
+ )
+ }
+
+ // ============================================================
+ // CUSTOMER
+ // ============================================================
+ bindV3(r, pgDB,
+ "/api/accounts", "GET",
+ "customer", "view",
+ wrapV3(http.HandlerFunc(routes.GetAccountsHandler)),
+ )
+
+ bindV3(r, pgDB,
+ "/api/customer-list", "GET",
+ "customer", "view",
+ wrapV3(http.HandlerFunc(routes.GetCustomerListHandler)),
+ )
+
+ // ============================================================
+ // FINANCE
+ // ============================================================
+ bindV3(r, pgDB,
+ "/api/today-currency", "GET",
+ "finance", "view",
+ wrapV3(routes.GetTodayCurrencyV3Handler(mssql)),
+ )
+
+ bindV3(r, pgDB,
+ "/api/export-pdf", "GET",
+ "finance", "export",
+ wrapV3(routes.ExportPDFHandler(mssql)),
+ )
+
+ bindV3(r, pgDB,
+ "/api/exportstamentheaderreport-pdf", "GET",
+ "finance", "export",
+ wrapV3(routes.ExportStatementHeaderReportPDFHandler(mssql)),
+ )
+
+ // ============================================================
+ // REPORT (STATEMENTS)
+ // ============================================================
+ bindV3(r, pgDB,
+ "/api/statements", "GET",
+ "finance", "view",
+ wrapV3(http.HandlerFunc(routes.GetStatementHeadersHandler)),
+ )
+
+ // ⚠️ Senin handler: GetStatementDetailsHandler vars["accountCode"] bekliyor,
+ // route’da {id} var. Burada, DB route’unu ve path’i bozmayalım diye {id}’yi koruyorum,
+ // ama handler içinde accountCode := mux.Vars(r)["id"] yapman daha doğru.
+ bindV3(r, pgDB,
+ "/api/statements/{id}/details", "GET",
+ "finance", "view",
+ wrapV3(http.HandlerFunc(routes.GetStatementDetailsHandler)),
+ )
+
+ // ============================================================
+ // ORDER
+ // ============================================================
+ orderRoutes := []struct {
+ Path string
+ Method string
+ Action string
+ Handle http.Handler
+ }{
+ {"/api/order/create", "POST", "insert", routes.CreateOrderHandler(pgDB, mssql)},
+ {"/api/order/update", "POST", "update", http.HandlerFunc(routes.UpdateOrderHandler)},
+ {"/api/order/get/{id}", "GET", "view", routes.GetOrderByIDHandler(mssql)},
+ {"/api/orders/list", "GET", "view", routes.OrderListRoute(mssql)},
+ {"/api/orders/export", "GET", "export", routes.OrderListExcelRoute(mssql)},
+ {"/api/order/check/{id}", "GET", "view", routes.OrderExistsHandler(mssql)},
+ {"/api/order/validate", "POST", "insert", routes.ValidateOrderHandler(mssql)},
+ {"/api/order/pdf/{id}", "GET", "export", routes.OrderPDFHandler(mssql)},
+ {"/api/order-inventory", "GET", "view", http.HandlerFunc(routes.GetOrderInventoryHandler)},
+ {"/api/orderpricelistb2b", "GET", "view", routes.GetOrderPriceListB2BHandler(pgDB, mssql)},
+ {"/api/min-price", "GET", "view", routes.GetOrderPriceListB2BHandler(pgDB, mssql)},
+ }
+
+ for _, rt := range orderRoutes {
+ bindV3(r, pgDB,
+ rt.Path, rt.Method,
+ "order", rt.Action,
+ wrapV3(rt.Handle),
+ )
+ }
+
+ // ============================================================
+ // PRODUCTS (✅ handler mapping fix)
+ // ============================================================
+ bindV3(r, pgDB,
+ "/api/products", "GET",
+ "order", "view",
+ wrapV3(http.HandlerFunc(routes.GetProductListHandler)),
+ )
+
+ bindV3(r, pgDB,
+ "/api/product-detail", "GET",
+ "order", "view",
+ wrapV3(http.HandlerFunc(routes.GetProductDetailHandler)),
+ )
+
+ bindV3(r, pgDB,
+ "/api/product-colors", "GET",
+ "order", "view",
+ wrapV3(http.HandlerFunc(routes.GetProductColorsHandler)),
+ )
+
+ bindV3(r, pgDB,
+ "/api/product-colorsize", "GET",
+ "order", "view",
+ wrapV3(http.HandlerFunc(routes.GetProductColorSizesHandler)),
+ )
+
+ bindV3(r, pgDB,
+ "/api/product-secondcolor", "GET",
+ "order", "view",
+ wrapV3(http.HandlerFunc(routes.GetProductSecondColorsHandler)),
+ )
+
+ // ============================================================
+ // ROLE MANAGEMENT
+ // ============================================================
+ bindV3(r, pgDB,
+ "/api/roles", "GET",
+ "user", "view",
+ wrapV3(routes.GetRolesHandler(pgDB)),
+ )
+
+ bindV3(r, pgDB,
+ "/api/departments", "GET",
+ "user", "view",
+ wrapV3(routes.GetDepartmentsHandler(pgDB)),
+ )
+
+ bindV3(r, pgDB,
+ "/api/piyasalar", "GET",
+ "user", "view",
+ wrapV3(routes.GetPiyasalarHandler(pgDB)),
+ )
+
+ // ============================================================
+ // ROLE RELATIONS
+ // ============================================================
+ bindV3(r, pgDB,
+ "/api/roles/{id}/departments", "POST",
+ "user", "update",
+ wrapV3(routes.UpdateRoleDepartmentsHandler(pgDB)),
+ )
+
+ bindV3(r, pgDB,
+ "/api/roles/{id}/piyasalar", "POST",
+ "user", "update",
+ wrapV3(routes.UpdateRolePiyasalarHandler(pgDB)),
+ )
+
+ // ============================================================
+ // USER ↔ ROLE
+ // ============================================================
+ bindV3(r, pgDB,
+ "/api/users/{id}/roles", "POST",
+ "user", "update",
+ wrapV3(routes.UpdateUserRolesHandler(pgDB)),
+ )
+
+ // ============================================================
+ // ADMIN EXTRA (eski main.go’da vardı, yeni sisteme alındı)
+ // ============================================================
+ bindV3(r, pgDB,
+ "/api/admin/users/{id}/piyasa-sync", "POST",
+ "admin", "user.update",
+ wrapV3(http.HandlerFunc(routes.AdminSyncUserPiyasaHandler)),
+ )
+
+ return r
+}
+
+func main() {
+ log.Println("🔥🔥🔥 BSSAPP BACKEND STARTED — LOGIN ROUTE SHOULD EXIST 🔥🔥🔥")
+
+ // -------------------------------------------------------
+ // 🔑 ENV
+ // -------------------------------------------------------
+ if err := godotenv.Load(".env", "mail.env"); err != nil {
+ log.Println("⚠️ .env / mail.env bulunamadı")
+ }
+
+ jwtSecret := os.Getenv("JWT_SECRET")
+ if len(jwtSecret) < 10 {
+ log.Fatal("❌ JWT_SECRET tanımlı değil veya çok kısa (min 10 karakter)")
+ }
+ log.Println("🔐 JWT_SECRET yüklendi")
+
+ // -------------------------------------------------------
+ // 🔗 DATABASE
+ // -------------------------------------------------------
+ db.ConnectMSSQL()
+
+ pgDB, err := db.ConnectPostgres()
+ if err != nil {
+ log.Fatalf("❌ PostgreSQL bağlantı hatası: %v", err)
+ }
+ defer pgDB.Close()
+
+ // -------------------------------------------------------
+ // 🔐 ADMIN ROLE + DEPARTMENT AUTO SEED
+ // -------------------------------------------------------
+ if err := permissions.SeedAdminRoleDepartments(pgDB); err != nil {
+ log.Println("❌ Admin dept seed failed:", err)
+ } else {
+ log.Println("✅ Admin dept permissions seeded")
+ }
+
+ // -------------------------------------------------------
+ // 🕵️ AUDIT LOG INIT
+ // -------------------------------------------------------
+ auditlog.Init(pgDB, 1000)
+ log.Println("🕵️ AuditLog sistemi başlatıldı (buffer=1000)")
+
+ // -------------------------------------------------------
+ // ✉️ MAILER INIT
+ // -------------------------------------------------------
+ graphMailer, err := mailer.NewGraphMailer()
+ if err != nil {
+ log.Fatalf("❌ Graph Mailer init error: %v", err)
+ }
+ log.Println("✉️ Graph Mailer hazır")
+
+ // -------------------------------------------------------
+ // 👤 DEBUG
+ // -------------------------------------------------------
+ repository.NewUserRepository(pgDB).DebugListUsers()
+
+ // -------------------------------------------------------
+ // 🌍 SERVER
+ // -------------------------------------------------------
+ router := InitRoutes(pgDB, db.MssqlDB, graphMailer)
+
+ handler := enableCORS(
+ middlewares.GlobalAuthMiddleware(
+ pgDB,
+ middlewares.RequestLogger(router),
+ ),
+ )
+
+ log.Println("✅ Server çalışıyor: http://localhost:8080")
+ log.Fatal(http.ListenAndServe(":8080", handler))
+}
diff --git a/svc/middlewares/auditlog_middleware.go b/svc/middlewares/auditlog_middleware.go
new file mode 100644
index 0000000..d0cbd70
--- /dev/null
+++ b/svc/middlewares/auditlog_middleware.go
@@ -0,0 +1,71 @@
+package middlewares
+
+import (
+ "bssapp-backend/auth"
+ "net/http"
+ "time"
+
+ "bssapp-backend/internal/auditlog"
+)
+
+type ResponseWriter struct {
+ http.ResponseWriter
+ status int
+}
+
+func NewResponseWriter(w http.ResponseWriter) *ResponseWriter {
+ return &ResponseWriter{
+ ResponseWriter: w,
+ status: http.StatusOK,
+ }
+}
+
+func (rw *ResponseWriter) WriteHeader(code int) {
+ rw.status = code
+ rw.ResponseWriter.WriteHeader(code)
+}
+
+func (rw *ResponseWriter) Status() int { return rw.status }
+
+func Audit(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ start := time.Now()
+ rw := NewResponseWriter(w)
+
+ next.ServeHTTP(rw, r)
+
+ // ✅ AuthMiddleware sonrası burada claims VAR
+ var dfusrID int64
+ var username, roleCode string
+
+ if claims, ok := auth.GetClaimsFromContext(r.Context()); ok && claims != nil {
+ dfusrID = int64(claims.ID)
+ username = claims.Username
+ roleCode = claims.RoleCode // token’da varsa
+ }
+
+ entry := auditlog.ActivityLog{
+ DfUsrID: dfusrID,
+ Username: username,
+ RoleCode: roleCode,
+
+ ActionType: "route_access",
+ ActionCategory: "nav",
+ ActionTarget: r.URL.Path,
+ Description: r.Method + " " + r.URL.Path,
+
+ IpAddress: r.RemoteAddr,
+ UserAgent: r.UserAgent(),
+ SessionID: "",
+
+ RequestStartedAt: start,
+ RequestFinishedAt: time.Now(),
+ DurationMs: int(time.Since(start).Milliseconds()),
+ HttpStatus: rw.Status(),
+ IsSuccess: rw.Status() < 400,
+ }
+
+ auditlog.Write(entry)
+ })
+}
diff --git a/svc/middlewares/auth_middleware.go b/svc/middlewares/auth_middleware.go
new file mode 100644
index 0000000..ff37dfa
--- /dev/null
+++ b/svc/middlewares/auth_middleware.go
@@ -0,0 +1,39 @@
+package middlewares
+
+import (
+ "bssapp-backend/auth"
+ "database/sql"
+ "log"
+ "net/http"
+ "strings"
+)
+
+func AuthMiddleware(db *sql.DB, next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ authHeader := r.Header.Get("Authorization")
+ if authHeader == "" {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ parts := strings.SplitN(authHeader, " ", 2)
+ if len(parts) != 2 || parts[0] != "Bearer" {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ claims, err := auth.ValidateToken(parts[1])
+ if err != nil {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ // 🔥 BU SATIR ŞART
+ ctx := auth.WithClaims(r.Context(), claims)
+
+ log.Printf("🔐 AUTH CTX SET user=%d role=%s", claims.ID, claims.RoleCode)
+
+ next.ServeHTTP(w, r.WithContext(ctx))
+ })
+}
diff --git a/svc/middlewares/authz_v2.go b/svc/middlewares/authz_v2.go
new file mode 100644
index 0000000..4d347dd
--- /dev/null
+++ b/svc/middlewares/authz_v2.go
@@ -0,0 +1,961 @@
+package middlewares
+
+import (
+ "bssapp-backend/internal/authz"
+ "bssapp-backend/permissions"
+ "bytes"
+ "database/sql"
+ "encoding/json"
+ "io"
+ "log"
+ "net/http"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "bssapp-backend/auth"
+
+ "github.com/gorilla/mux"
+)
+
+/*
+AuthzGuardV2
+- module+action role permission check (mk_sys_role_permissions)
+- optional scope checks (department / piyasa) via intersection:
+ user_allowed ∩ role_allowed
+- cache with TTL
+
+Expected:
+- AuthMiddleware runs before this and sets JWT claims in context.
+- claims should contain RoleID and UserID.
+*/
+
+// =====================================================
+// 🔧 CONFIG / CONSTANTS
+// =====================================================
+
+const (
+ defaultPermTTL = 60 * time.Second
+ defaultScopeTTL = 30 * time.Second
+
+ maxBodyRead = 1 << 20 // 1MB
+)
+
+// =====================================================
+// 🧠 CACHE
+// =====================================================
+
+type cacheItem struct {
+ val any
+ expires time.Time
+}
+
+type ttlCache struct {
+ mu sync.RWMutex
+ ttl time.Duration
+ m map[string]cacheItem
+}
+
+// =====================================================
+// 🌍 GLOBAL SCOPE CACHE (for invalidation)
+// =====================================================
+
+var globalScopeCache *ttlCache
+
+func newTTLCache(ttl time.Duration) *ttlCache {
+ return &ttlCache{
+ ttl: ttl,
+ m: make(map[string]cacheItem),
+ }
+}
+
+func (c *ttlCache) get(key string) (any, bool) {
+ now := time.Now()
+ c.mu.RLock()
+ item, ok := c.m[key]
+ c.mu.RUnlock()
+ if !ok {
+ return nil, false
+ }
+ if now.After(item.expires) {
+ // lazy delete
+ c.mu.Lock()
+ delete(c.m, key)
+ c.mu.Unlock()
+ return nil, false
+ }
+ return item.val, true
+}
+
+func (c *ttlCache) set(key string, val any) {
+ c.mu.Lock()
+ c.m[key] = cacheItem{val: val, expires: time.Now().Add(c.ttl)}
+ c.mu.Unlock()
+}
+
+// =====================================================
+// ✅ MAIN MIDDLEWARE
+// =====================================================
+
+type AuthzV2Options struct {
+ // If true, scope checks are attempted when scope can be extracted.
+ EnableScopeChecks bool
+
+ // If true, when scope is required but cannot be extracted, deny.
+ // If false, when scope cannot be extracted, scope check is skipped.
+ StrictScope bool
+
+ // Override TTLs (optional)
+ PermTTL time.Duration
+ ScopeTTL time.Duration
+
+ // Custom extractors (optional). If nil, built-in extractors are used.
+ ExtractDepartmentCodes func(r *http.Request) []string
+ ExtractPiyasaCodes func(r *http.Request) []string
+
+ // Decide whether this request should be treated as scope-sensitive.
+ // If nil, built-in heuristic is used.
+ IsScopeSensitive func(module string, r *http.Request) bool
+}
+
+func AuthzGuardV2(pg *sql.DB, module string, action string) func(http.Handler) http.Handler {
+ return AuthzGuardV2WithOptions(pg, module, action, AuthzV2Options{
+ EnableScopeChecks: true,
+ StrictScope: false,
+ })
+}
+
+func AuthzGuardV2WithOptions(pg *sql.DB, module string, action string, opt AuthzV2Options) func(http.Handler) http.Handler {
+
+ permTTL := opt.PermTTL
+ if permTTL <= 0 {
+ permTTL = defaultPermTTL
+ }
+ scopeTTL := opt.ScopeTTL
+ if scopeTTL <= 0 {
+ scopeTTL = defaultScopeTTL
+ }
+
+ permCache := newTTLCache(permTTL)
+ if globalScopeCache == nil {
+ globalScopeCache = newTTLCache(scopeTTL)
+ }
+ scopeCache := globalScopeCache
+
+ // default extractors
+ extractDept := opt.ExtractDepartmentCodes
+ if extractDept == nil {
+ extractDept = defaultExtractDepartmentCodes
+ }
+ extractPiy := opt.ExtractPiyasaCodes
+ if extractPiy == nil {
+ extractPiy = defaultExtractPiyasaCodes
+ }
+
+ isScopeSensitive := opt.IsScopeSensitive
+ if isScopeSensitive == nil {
+ isScopeSensitive = defaultIsScopeSensitive
+ }
+
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ // OPTIONS passthrough
+ if r.Method == http.MethodOptions {
+ w.WriteHeader(http.StatusOK)
+ return
+ }
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", 401)
+ return
+ }
+
+ userID := claims.ID
+ roleCode := claims.RoleCode
+
+ // ADMIN BYPASS
+ if claims.IsAdmin() {
+ next.ServeHTTP(w, r)
+ return
+ }
+
+ // resolve role_id from role_code
+ roleID, err := cachedRoleID(pg, permCache, roleCode)
+ if err != nil {
+ log.Println("❌ role resolve error:", err)
+ http.Error(w, "forbidden", http.StatusForbidden)
+ return
+ }
+
+ // --------------------------------------------------
+ // 🔐 PERMISSION RESOLUTION (USER > ROLE > DENY)
+ // --------------------------------------------------
+
+ permRepo := permissions.NewPermissionRepository(pg)
+
+ allowed := false
+ resolved := false // karar verildi mi?
+
+ // --------------------------------------------------
+ // 1️⃣ USER OVERRIDE (ÖNCELİK)
+ // --------------------------------------------------
+
+ overrides, err := permRepo.GetUserOverrides(userID)
+ if err != nil {
+ log.Println("❌ override load error:", err)
+ http.Error(w, "forbidden", http.StatusForbidden)
+ return
+ }
+
+ for _, o := range overrides {
+
+ if o.Module == module &&
+ o.Action == action {
+
+ log.Printf(
+ "🔁 USER OVERRIDE → %s:%s = %v",
+ module,
+ action,
+ o.Allowed,
+ )
+
+ allowed = o.Allowed
+ resolved = true
+ break
+ }
+ }
+
+ // --------------------------------------------------
+ // 2️⃣ ROLE + DEPARTMENT (NEW SYSTEM)
+ // --------------------------------------------------
+
+ if !resolved {
+
+ deptCodes := claims.DepartmentCodes
+
+ roleDeptAllowed, err := permRepo.ResolvePermissionChain(
+ userID,
+ roleID,
+ deptCodes,
+ module,
+ action,
+ )
+
+ if err != nil {
+ log.Println("❌ perm resolve error:", err)
+ http.Error(w, "forbidden", http.StatusForbidden)
+ return
+ }
+
+ if roleDeptAllowed {
+
+ log.Printf("🆕 ROLE+DEPT → %s:%s = true", module, action)
+
+ allowed = true
+ resolved = true
+
+ } else {
+
+ log.Printf("🆕 ROLE+DEPT → %s:%s = false (try legacy)", module, action)
+ }
+ }
+ // --------------------------------------------------
+ // 3️⃣ ROLE ONLY (LEGACY FALLBACK)
+ // --------------------------------------------------
+
+ if !resolved {
+
+ legacyAllowed, err := cachedRolePermission(
+ pg,
+ permCache,
+ roleID,
+ module,
+ action,
+ )
+
+ if err != nil {
+ log.Println("❌ legacy perm error:", err)
+ http.Error(w, "forbidden", http.StatusForbidden)
+ return
+ }
+
+ log.Printf("🕰️ LEGACY ROLE → %s:%s = %v", module, action, legacyAllowed)
+
+ allowed = legacyAllowed
+ resolved = true
+ }
+
+ // --------------------------------------------------
+ // 3️⃣ FINAL DECISION
+ // --------------------------------------------------
+
+ if !allowed {
+
+ log.Printf(
+ "⛔ ACCESS DENIED user=%d %s:%s path=%s",
+ claims.ID,
+ module,
+ action,
+ r.URL.Path,
+ )
+
+ http.Error(w, "forbidden", http.StatusForbidden)
+ return
+ }
+
+ log.Printf(
+ "✅ ACCESS OK user=%d %s:%s %s",
+ claims.ID,
+ module,
+ action,
+ r.URL.Path,
+ )
+
+ // --------------------------------------------------
+ // 4️⃣ OPTIONAL SCOPE CHECKS (FINAL - SECURE)
+ // --------------------------------------------------
+
+ if opt.EnableScopeChecks && isScopeSensitive(module, r) {
+
+ // 🔹 Request’ten gelenler
+ reqDepts := normalizeCodes(extractDept(r))
+ reqPiy := normalizeCodes(extractPiy(r))
+
+ ctx := r.Context()
+
+ // 🔹 USER PIYASA (DB’den)
+ userPiy, err := authz.GetUserPiyasaCodes(pg, int(userID))
+ if err != nil {
+ log.Println("❌ piyasa load error:", err)
+ http.Error(w, "forbidden", 403)
+ return
+ }
+
+ userPiy = normalizeCodes(userPiy)
+
+ // ------------------------------------------------
+ // ✅ PIYASA INTERSECTION
+ // ------------------------------------------------
+
+ var effectivePiy []string
+
+ switch {
+ case len(reqPiy) > 0 && len(userPiy) > 0:
+ effectivePiy = intersect(reqPiy, userPiy)
+
+ case len(reqPiy) > 0 && len(userPiy) == 0:
+ // user piyasa tanımlı değilse request'e güvenme → boş kalsın (StrictScope varsa deny)
+ effectivePiy = nil
+
+ case len(reqPiy) == 0 && len(userPiy) > 0:
+ effectivePiy = userPiy
+ }
+ if len(reqPiy) > 0 && len(effectivePiy) == 0 {
+ // request piyasa istiyor ama user scope karşılamıyor
+ http.Error(w, "forbidden", http.StatusForbidden)
+ return
+ }
+
+ // ------------------------------------------------
+ // ✅ CONTEXT’E YAZ
+ // ------------------------------------------------
+
+ if len(reqDepts) > 0 {
+ ctx = authz.WithDeptCodes(ctx, reqDepts)
+ }
+
+ if len(effectivePiy) > 0 {
+ ctx = authz.WithPiyasaCodes(ctx, effectivePiy)
+ }
+
+ r = r.WithContext(ctx)
+
+ // ------------------------------------------------
+ // ❗ STRICT MODE
+ // ------------------------------------------------
+
+ if len(reqDepts) == 0 && len(effectivePiy) == 0 {
+
+ if opt.StrictScope {
+ http.Error(w, "forbidden", http.StatusForbidden)
+ return
+ }
+
+ next.ServeHTTP(w, r)
+ return
+ }
+
+ // ------------------------------------------------
+ // 🔍 DEPARTMENT CHECK
+ // ------------------------------------------------
+
+ if len(reqDepts) > 0 {
+
+ okDept, err := cachedDeptIntersectionAny(
+ pg,
+ scopeCache,
+ userID,
+ roleID,
+ reqDepts,
+ )
+
+ if err != nil {
+ log.Println("❌ dept scope error:", err)
+ http.Error(w, "forbidden", http.StatusForbidden)
+ return
+ }
+
+ if !okDept {
+ http.Error(w, "forbidden", http.StatusForbidden)
+ return
+ }
+ }
+
+ // ------------------------------------------------
+ // 🔍 PIYASA CHECK
+ // ------------------------------------------------
+
+ if len(effectivePiy) > 0 {
+
+ okPiy, err := cachedPiyasaIntersectionAny(
+ pg,
+ scopeCache,
+ userID,
+ roleID,
+ effectivePiy,
+ )
+
+ if err != nil {
+ log.Println("❌ piyasa scope error:", err)
+ http.Error(w, "forbidden", http.StatusForbidden)
+ return
+ }
+
+ if !okPiy {
+ http.Error(w, "forbidden", http.StatusForbidden)
+ return
+ }
+ }
+ }
+
+ // --------------------------------------------------
+ // ✅ ALLOW
+ // --------------------------------------------------
+
+ next.ServeHTTP(w, r)
+ })
+ }
+}
+
+// =====================================================
+// 🔐 PERMISSION CHECK (mk_sys_role_permissions)
+// =====================================================
+
+func cachedRolePermission(pg *sql.DB, c *ttlCache, roleID int64, module, action string) (bool, error) {
+
+ key := "perm|" + itoa(roleID) + "|" + module + "|" + action
+ if v, ok := c.get(key); ok {
+ return v.(bool), nil
+ }
+
+ var allowed bool
+ err := pg.QueryRow(`
+ SELECT allowed
+ FROM mk_sys_role_permissions
+ WHERE role_id = $1 AND module_code = $2 AND action = $3
+ `, roleID, module, action).Scan(&allowed)
+
+ if err == sql.ErrNoRows {
+ c.set(key, false)
+ return false, nil
+ }
+ if err != nil {
+ return false, err
+ }
+
+ c.set(key, allowed)
+ return allowed, nil
+}
+
+// =====================================================
+// 🧩 SCOPE INTERSECTION
+// user scope ∩ role scope
+// =====================================================
+
+func cachedDeptIntersectionAny(pg *sql.DB, c *ttlCache, userID, roleID int64, deptCodes []string) (bool, error) {
+ // cache by exact request list (sorted would be ideal; normalizeCodes already stabilizes somewhat)
+ key := "deptAny|" + itoa(userID) + "|" + itoa(roleID) + "|" + strings.Join(deptCodes, ",")
+ if v, ok := c.get(key); ok {
+ return v.(bool), nil
+ }
+
+ // ANY match: if request wants multiple codes, allow if at least one is in intersection
+ // Intersection query:
+ // user departments: dfusr_dprt -> mk_dprt(code)
+ // role allowed: dfrole_dprt -> mk_dprt(id) OR directly by id
+ okAny := false
+
+ // We do it in a single query with ANY($3)
+ var dummy int
+ err := pg.QueryRow(`
+ SELECT 1
+ FROM dfusr_dprt ud
+ JOIN mk_dprt d ON d.id = ud.dprt_id
+ JOIN dfrole_dprt rd ON rd.dprt_id = ud.dprt_id
+ WHERE ud.dfusr_id = $1
+ AND rd.dfrole_id = $2
+ AND ud.is_active = true
+ AND rd.is_allowed = true
+ AND d.code = ANY($3)
+ LIMIT 1
+ `, userID, roleID, pqArray(deptCodes)).Scan(&dummy)
+
+ if err == sql.ErrNoRows {
+ c.set(key, false)
+ return false, nil
+ }
+ if err != nil {
+ return false, err
+ }
+
+ okAny = true
+ c.set(key, okAny)
+ return okAny, nil
+}
+
+func cachedPiyasaIntersectionAny(pg *sql.DB, c *ttlCache, userID, roleID int64, piyasaCodes []string) (bool, error) {
+
+ key := "piyAny|" + itoa(userID) + "|" + itoa(roleID) + "|" + strings.Join(piyasaCodes, ",")
+ if v, ok := c.get(key); ok {
+ return v.(bool), nil
+ }
+
+ var dummy int
+ err := pg.QueryRow(`
+ SELECT 1
+ FROM dfusr_piyasa up
+ WHERE up.dfusr_id = $1
+ AND up.is_allowed = true
+ AND up.piyasa_code = ANY($2)
+ LIMIT 1
+ `, userID, pqArray(piyasaCodes)).Scan(&dummy)
+
+ if err == sql.ErrNoRows {
+ c.set(key, false)
+ return false, nil
+ }
+ if err != nil {
+ return false, err
+ }
+
+ c.set(key, true)
+ return true, nil
+}
+
+// =====================================================
+// 🧲 DEFAULT SCOPE DETECTION
+// =====================================================
+
+// defaultIsScopeSensitive decides whether this module likely needs dept/piyasa checks.
+// You can tighten/extend later.
+func defaultIsScopeSensitive(module string, r *http.Request) bool {
+ switch module {
+ case "order", "customer", "report", "finance":
+ return true
+ default:
+ return false
+ }
+}
+
+// =====================================================
+// 🔎 DEFAULT EXTRACTORS
+// =====================================================
+
+// We try to extract scope from:
+// - query params: dprt, dprt_code, department, department_code, piyasa, piyasa_code, market
+// - headers: X-Department, X-Piyasa
+// - json body fields: department / department_code / dprt_code, piyasa / piyasa_code / market_code
+// (body read is safe: we re-inject the body)
+func defaultExtractDepartmentCodes(r *http.Request) []string {
+ var out []string
+
+ // query params
+ for _, k := range []string{"dprt", "dprt_code", "department", "department_code"} {
+ out = append(out, splitCSV(r.URL.Query().Get(k))...)
+ }
+
+ // headers
+ out = append(out, splitCSV(r.Header.Get("X-Department"))...)
+
+ // JSON body (if any)
+ out = append(out, extractFromJSONBody(r, []string{
+ "department", "department_code", "dprt", "dprt_code",
+ })...)
+
+ return out
+}
+
+func defaultExtractPiyasaCodes(r *http.Request) []string {
+ var out []string
+
+ for _, k := range []string{"piyasa", "piyasa_code", "market", "market_code"} {
+ out = append(out, splitCSV(r.URL.Query().Get(k))...)
+ }
+
+ out = append(out, splitCSV(r.Header.Get("X-Piyasa"))...)
+
+ out = append(out, extractFromJSONBody(r, []string{
+ "piyasa", "piyasa_code", "market", "market_code", "customer_attribute",
+ })...)
+
+ return out
+}
+
+func extractFromJSONBody(r *http.Request, keys []string) []string {
+ // Only for methods that might have body
+ switch r.Method {
+ case http.MethodPost, http.MethodPut, http.MethodPatch:
+ default:
+ return nil
+ }
+
+ // read body (and restore)
+ raw, err := readBodyAndRestore(r, maxBodyRead)
+ if err != nil || len(raw) == 0 {
+ return nil
+ }
+
+ // try parse object
+ var obj map[string]any
+ if err := json.Unmarshal(raw, &obj); err != nil {
+ return nil
+ }
+
+ var out []string
+ for _, k := range keys {
+ if v, ok := obj[k]; ok {
+ switch t := v.(type) {
+ case string:
+ out = append(out, splitCSV(t)...)
+ case []any:
+ for _, it := range t {
+ if s, ok := it.(string); ok {
+ out = append(out, splitCSV(s)...)
+ }
+ }
+ }
+ }
+ }
+
+ return out
+}
+
+func readBodyAndRestore(r *http.Request, limit int64) ([]byte, error) {
+ if r.Body == nil {
+ return nil, nil
+ }
+
+ // Read with limit
+ raw, err := io.ReadAll(io.LimitReader(r.Body, limit))
+ if err != nil {
+ return nil, err
+ }
+
+ // restore
+ r.Body = io.NopCloser(bytes.NewBuffer(raw))
+ return raw, nil
+}
+
+// =====================================================
+// 🧼 HELPERS
+// =====================================================
+
+func splitCSV(s string) []string {
+ s = strings.TrimSpace(s)
+ if s == "" {
+ return nil
+ }
+ parts := strings.Split(s, ",")
+ out := make([]string, 0, len(parts))
+ for _, p := range parts {
+ p = strings.TrimSpace(p)
+ if p != "" {
+ out = append(out, p)
+ }
+ }
+ return out
+}
+
+func normalizeCodes(in []string) []string {
+ if len(in) == 0 {
+ return nil
+ }
+ seen := map[string]struct{}{}
+ out := make([]string, 0, len(in))
+ for _, s := range in {
+ s = strings.ToUpper(strings.TrimSpace(s))
+ if s == "" {
+ continue
+ }
+ if _, ok := seen[s]; ok {
+ continue
+ }
+ seen[s] = struct{}{}
+ out = append(out, s)
+ }
+ return out
+}
+
+// pqArray: minimal adapter to pass []string as Postgres array.
+// If you already use lib/pq, replace this with pq.Array.
+// Here we use a simple JSON array -> Postgres can cast text[] from ARRAY[]? Not directly.
+// So: YOU SHOULD USE lib/pq in your project. If it's already there, change pqArray() to pq.Array(slice).
+//
+// For now, we implement as a driver.Value using "{A,B}" format (Postgres text[] literal).
+type pgTextArray string
+
+func (a pgTextArray) Value() (any, error) { return string(a), nil }
+
+func pqArray(ss []string) any {
+ // produce "{A,B,C}" as text[] literal
+ // escape quotes minimally
+ if len(ss) == 0 {
+ return pgTextArray("{}")
+ }
+ var b strings.Builder
+ b.WriteString("{")
+ for i, s := range ss {
+ if i > 0 {
+ b.WriteString(",")
+ }
+ s = strings.ReplaceAll(s, `"`, `\"`)
+ b.WriteString(`"`)
+ b.WriteString(s)
+ b.WriteString(`"`)
+ }
+ b.WriteString("}")
+ return pgTextArray(b.String())
+}
+
+func itoa(n int64) string {
+ return strconv.FormatInt(n, 10)
+}
+
+// isolate strconv usage without importing it globally in this snippet
+func strconvFormatInt(n int64) string {
+ // local minimal
+ // NOTE: in real code just import strconv and use strconv.FormatInt(n, 10)
+ if n == 0 {
+ return "0"
+ }
+ neg := n < 0
+ if neg {
+ n = -n
+ }
+ var buf [32]byte
+ i := len(buf)
+ for n > 0 {
+ i--
+ buf[i] = byte('0' + (n % 10))
+ n /= 10
+ }
+ if neg {
+ i--
+ buf[i] = '-'
+ }
+ return string(buf[i:])
+}
+
+// optional: allow passing scope explicitly from handlers via context (advanced use)
+type scopeKey string
+
+// =====================================================
+// 🔍 ROLE RESOLVER (code -> id) WITH CACHE
+// =====================================================
+
+func cachedRoleID(pg *sql.DB, c *ttlCache, roleCode string) (int64, error) {
+
+ key := "role|" + strings.ToLower(roleCode)
+
+ if v, ok := c.get(key); ok {
+ return v.(int64), nil
+ }
+
+ var id int64
+
+ err := pg.QueryRow(`
+ SELECT id
+ FROM dfrole
+ WHERE LOWER(code) = LOWER($1)
+ `, roleCode).Scan(&id)
+
+ if err != nil {
+ return 0, err
+ }
+
+ c.set(key, id)
+ return id, nil
+}
+
+// =====================================================
+// 🧹 CACHE INVALIDATION (ADMIN)
+// =====================================================
+
+func ClearAuthzScopeCacheForUser(userID int64) {
+
+ // NOTE: this clears ALL scope cache.
+ // Simple & safe. Optimize later if needed.
+
+ if globalScopeCache != nil {
+ globalScopeCache.mu.Lock()
+ defer globalScopeCache.mu.Unlock()
+
+ for k := range globalScopeCache.m {
+ if strings.Contains(k, "|"+itoa(userID)+"|") {
+ delete(globalScopeCache.m, k)
+ }
+ }
+ }
+}
+
+// intersect: A ∩ B
+func intersect(a, b []string) []string {
+
+ set := make(map[string]struct{}, len(a))
+
+ for _, v := range a {
+ set[v] = struct{}{}
+ }
+
+ var out []string
+
+ for _, v := range b {
+ if _, ok := set[v]; ok {
+ out = append(out, v)
+ }
+ }
+
+ return out
+}
+func AuthzGuardByRoute(pg *sql.DB) func(http.Handler) http.Handler {
+
+ return func(next http.Handler) http.Handler {
+
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ // =====================================================
+ // 0️⃣ OPTIONS → PASS (CORS preflight)
+ // =====================================================
+ if r.Method == http.MethodOptions {
+ next.ServeHTTP(w, r)
+ return
+ }
+
+ // =====================================================
+ // 1️⃣ AUTH
+ // =====================================================
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ // =====================================================
+ // 2️⃣ REAL ROUTE TEMPLATE ( /api/users/{id} )
+ // =====================================================
+ route := mux.CurrentRoute(r)
+
+ if route == nil {
+ log.Printf("❌ AUTHZ: route not resolved: %s %s",
+ r.Method, r.URL.Path,
+ )
+
+ http.Error(w, "route not resolved", 403)
+ return
+ }
+
+ pathTemplate, err := route.GetPathTemplate()
+ if err != nil {
+ log.Printf("❌ AUTHZ: path template error: %v", err)
+
+ http.Error(w, "route template error", 403)
+ return
+ }
+
+ // =====================================================
+ // 3️⃣ ROUTE LOOKUP (path + method)
+ // =====================================================
+ var module, action string
+
+ err = pg.QueryRow(`
+ SELECT module_code, action
+ FROM mk_sys_routes
+ WHERE path = $1
+ AND method = $2
+ `,
+ pathTemplate,
+ r.Method,
+ ).Scan(&module, &action)
+
+ if err != nil {
+
+ log.Printf(
+ "❌ AUTHZ: route not registered: %s %s",
+ r.Method,
+ pathTemplate,
+ )
+
+ http.Error(w, "route permission not found", 403)
+ return
+ }
+
+ // =====================================================
+ // 4️⃣ PERMISSION RESOLVE
+ // =====================================================
+ repo := permissions.NewPermissionRepository(pg)
+
+ allowed, err := repo.ResolvePermissionChain(
+ int64(claims.ID),
+ int64(claims.RoleID),
+ claims.DepartmentCodes,
+ module,
+ action,
+ )
+
+ if err != nil {
+
+ log.Printf(
+ "❌ AUTHZ: resolve error user=%d %s:%s err=%v",
+ claims.ID,
+ module,
+ action,
+ err,
+ )
+
+ http.Error(w, "forbidden", 403)
+ return
+ }
+
+ if !allowed {
+
+ log.Printf(
+ "⛔ AUTHZ: denied user=%d %s:%s",
+ claims.ID,
+ module,
+ action,
+ )
+
+ http.Error(w, "forbidden", 403)
+ return
+ }
+
+ // =====================================================
+ // 5️⃣ PASS
+ // =====================================================
+ next.ServeHTTP(w, r)
+ })
+ }
+}
diff --git a/svc/middlewares/current_user.go b/svc/middlewares/current_user.go
new file mode 100644
index 0000000..825350f
--- /dev/null
+++ b/svc/middlewares/current_user.go
@@ -0,0 +1,24 @@
+package middlewares
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/models"
+ "bssapp-backend/utils"
+ "net/http"
+)
+
+func CurrentUser(r *http.Request) (*models.User, bool) {
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+
+ if !ok || claims == nil {
+ return nil, false
+ }
+
+ user := utils.UserFromClaims(claims)
+ if user == nil {
+ return nil, false
+ }
+
+ return user, true
+}
diff --git a/svc/middlewares/force_password_change.go b/svc/middlewares/force_password_change.go
new file mode 100644
index 0000000..91354df
--- /dev/null
+++ b/svc/middlewares/force_password_change.go
@@ -0,0 +1,66 @@
+package middlewares
+
+import (
+ "bssapp-backend/auth"
+ "database/sql"
+ "log"
+ "net/http"
+ "strings"
+)
+
+// 🔓 force_password_change=true iken izinli endpoint prefixleri
+var passwordChangeAllowlist = []string{
+ "/api/password/change",
+ "/api/password/reset",
+ "/api/password/reset/validate",
+ "/api/auth/refresh",
+}
+
+func ForcePasswordChangeGuard(db *sql.DB) func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ log.Println("❌ FPC GUARD: claims NOT FOUND")
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ log.Printf(
+ "🛡️ FPC GUARD user=%s force=%v path=%s",
+ claims.Username,
+ claims.ForcePasswordChange,
+ r.URL.Path,
+ )
+
+ // 🔓 Şifre değişimi zorunlu DEĞİL → serbest
+ if !claims.ForcePasswordChange {
+ next.ServeHTTP(w, r)
+ return
+ }
+
+ // 🔐 Şifre değişimi ZORUNLU → allowlist kontrolü
+ for _, allowed := range passwordChangeAllowlist {
+ if strings.HasPrefix(r.URL.Path, allowed) {
+ log.Printf(
+ "✅ FPC GUARD PASS user=%s path=%s",
+ claims.Username,
+ r.URL.Path,
+ )
+ next.ServeHTTP(w, r)
+ return
+ }
+ }
+
+ // ⛔ Zorunlu ama yanlış endpoint
+ log.Printf(
+ "⛔ FPC GUARD BLOCK user=%s path=%s",
+ claims.Username,
+ r.URL.Path,
+ )
+
+ http.Error(w, "password change required", http.StatusUnauthorized)
+ })
+ }
+}
diff --git a/svc/middlewares/global_auth.go b/svc/middlewares/global_auth.go
new file mode 100644
index 0000000..b962993
--- /dev/null
+++ b/svc/middlewares/global_auth.go
@@ -0,0 +1,58 @@
+package middlewares
+
+import (
+ "bssapp-backend/auth"
+ "log"
+ "net/http"
+ "strings"
+)
+
+var publicPaths = []string{
+ "/api/auth/login",
+ "/api/auth/refresh",
+ "/api/password/forgot",
+ "/api/password/reset",
+}
+
+func GlobalAuthMiddleware(db any, next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ path := r.URL.Path
+
+ // PUBLIC ROUTES
+ for _, p := range publicPaths {
+ if strings.HasPrefix(path, p) {
+ next.ServeHTTP(w, r)
+ return
+ }
+ }
+
+ // JWT
+ authHeader := r.Header.Get("Authorization")
+ if authHeader == "" {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ parts := strings.SplitN(authHeader, " ", 2)
+ if len(parts) != 2 || parts[0] != "Bearer" {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ claims, err := auth.ValidateToken(parts[1])
+ if err != nil {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ ctx := auth.WithClaims(r.Context(), claims)
+
+ log.Printf("🔐 GLOBAL AUTH user=%d role=%s",
+ claims.ID,
+ claims.RoleCode,
+ )
+
+ next.ServeHTTP(w, r.WithContext(ctx))
+ })
+}
diff --git a/svc/middlewares/helpers.go b/svc/middlewares/helpers.go
new file mode 100644
index 0000000..1de99d9
--- /dev/null
+++ b/svc/middlewares/helpers.go
@@ -0,0 +1,56 @@
+package middlewares
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/ctxkeys"
+ "context"
+)
+
+func getClaims(ctx context.Context) *auth.Claims {
+ if v := ctx.Value(ctxkeys.UserContextKey); v != nil {
+ if c, ok := v.(*auth.Claims); ok {
+ return c
+ }
+ }
+ return nil
+}
+
+// --------------------------------------------------
+// 🔐 SESSION
+// --------------------------------------------------
+func GetSessionID(ctx context.Context) string {
+ if c := getClaims(ctx); c != nil {
+ return c.SessionID
+ }
+ return ""
+}
+
+// --------------------------------------------------
+// 🔑 USER ID (mk_dfusr.id)
+// --------------------------------------------------
+func GetUserID(ctx context.Context) int64 {
+ if c := getClaims(ctx); c != nil {
+ return c.ID
+ }
+ return 0
+}
+
+// --------------------------------------------------
+// 👤 USERNAME
+// --------------------------------------------------
+func GetUsername(ctx context.Context) string {
+ if c := getClaims(ctx); c != nil {
+ return c.Username
+ }
+ return ""
+}
+
+// --------------------------------------------------
+// 🧩 ROLE
+// --------------------------------------------------
+func GetRoleCode(ctx context.Context) string {
+ if c := getClaims(ctx); c != nil && c.RoleCode != "" {
+ return c.RoleCode
+ }
+ return "public"
+}
diff --git a/svc/middlewares/rate_limit.go b/svc/middlewares/rate_limit.go
new file mode 100644
index 0000000..be0a884
--- /dev/null
+++ b/svc/middlewares/rate_limit.go
@@ -0,0 +1,59 @@
+package middlewares
+
+import (
+ "net"
+ "net/http"
+ "sync"
+ "time"
+)
+
+type rateEntry struct {
+ Count int
+ ExpiresAt time.Time
+}
+
+var (
+ rateMu sync.Mutex
+ rateDB = make(map[string]*rateEntry)
+)
+
+func RateLimit(keyFn func(*http.Request) string, limit int, window time.Duration) func(http.Handler) http.Handler {
+
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ key := keyFn(r)
+ now := time.Now()
+
+ rateMu.Lock()
+ e, ok := rateDB[key]
+ if !ok || now.After(e.ExpiresAt) {
+ e = &rateEntry{
+ Count: 0,
+ ExpiresAt: now.Add(window),
+ }
+ rateDB[key] = e
+ }
+
+ e.Count++
+ if e.Count > limit {
+ rateMu.Unlock()
+ http.Error(w, "Too many requests", http.StatusTooManyRequests)
+ return
+ }
+ rateMu.Unlock()
+
+ next.ServeHTTP(w, r)
+ })
+ }
+}
+
+// helpers
+func RateByIP(r *http.Request) string {
+ ip, _, _ := net.SplitHostPort(r.RemoteAddr)
+ return "ip:" + ip
+}
+
+func RateByUser(r *http.Request) string {
+ return "user:" + r.URL.Path // id path’ten okunabilir
+}
diff --git a/svc/middlewares/request_logger.go b/svc/middlewares/request_logger.go
new file mode 100644
index 0000000..bd8e4f6
--- /dev/null
+++ b/svc/middlewares/request_logger.go
@@ -0,0 +1,108 @@
+package middlewares
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/internal/auditlog"
+ "log"
+ "net"
+ "net/http"
+ "time"
+)
+
+type statusWriter struct {
+ http.ResponseWriter
+ status int
+}
+
+func (w *statusWriter) WriteHeader(code int) {
+ w.status = code
+ w.ResponseWriter.WriteHeader(code)
+}
+
+func RequestLogger(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ start := time.Now()
+
+ sw := &statusWriter{
+ ResponseWriter: w,
+ status: 200,
+ }
+
+ // ---------- CLAIMS ----------
+ claims, _ := auth.GetClaimsFromContext(r.Context())
+
+ // ---------- IP ----------
+ ip := r.RemoteAddr
+ if host, _, err := net.SplitHostPort(ip); err == nil {
+ ip = host
+ }
+
+ // ---------- UA ----------
+ ua := r.UserAgent()
+
+ // ---------- SESSION ----------
+ sessionID := ""
+ if claims != nil {
+ sessionID = claims.SessionID
+ }
+
+ hasAuth := r.Header.Get("Authorization") != ""
+
+ log.Printf("➡️ %s %s | auth=%v", r.Method, r.URL.Path, hasAuth)
+
+ // ---------- RUN ----------
+ next.ServeHTTP(sw, r)
+
+ finish := time.Now()
+ dur := int(finish.Sub(start).Milliseconds())
+
+ log.Printf("⬅️ %s %s | status=%d | %s", r.Method, r.URL.Path, sw.status, time.Since(start))
+
+ // ---------- AUDIT (route_access) ----------
+ al := auditlog.ActivityLog{
+ ActionType: "route_access",
+ ActionCategory: "nav",
+ ActionTarget: r.URL.Path,
+ Description: r.Method + " " + r.URL.Path,
+
+ IpAddress: ip,
+ UserAgent: ua,
+ SessionID: sessionID,
+
+ RequestStartedAt: start,
+ RequestFinishedAt: finish,
+ DurationMs: dur,
+ HttpStatus: sw.status,
+
+ IsSuccess: sw.status < 400,
+ }
+
+ // ---------- CLAIMS → LOG ----------
+ if claims != nil {
+ al.Username = claims.Username
+ al.RoleCode = claims.RoleCode
+ al.DfUsrID = int64(claims.ID)
+
+ // Eğer claims içinde UUID varsa ekle (sende varsa aç)
+ // al.UserID = claims.UserUUID
+ } else {
+ al.RoleCode = "public"
+ }
+
+ // ---------- ERROR ----------
+ if sw.status >= 400 {
+ al.ErrorMessage = http.StatusText(sw.status)
+ }
+
+ // ✅ ESKİ: auditlog.Write(al)
+ // ✅ YENİ:
+ auditlog.Enqueue(r.Context(), al)
+
+ if claims == nil {
+ log.Println("⚠️ LOGGER: claims is NIL")
+ } else {
+ log.Printf("✅ LOGGER CLAIMS user=%s role=%s id=%d", claims.Username, claims.RoleCode, claims.ID)
+ }
+ })
+}
diff --git a/svc/models/NewOrderNumberResponse.go b/svc/models/NewOrderNumberResponse.go
new file mode 100644
index 0000000..6d5a908
--- /dev/null
+++ b/svc/models/NewOrderNumberResponse.go
@@ -0,0 +1,6 @@
+package models
+
+type NewOrderNumberResponse struct {
+ OrderHeaderID string `json:"OrderHeaderID"`
+ OrderNumber string `json:"OrderNumber"`
+}
diff --git a/svc/models/account.go b/svc/models/account.go
new file mode 100644
index 0000000..f1d5edb
--- /dev/null
+++ b/svc/models/account.go
@@ -0,0 +1,8 @@
+package models
+
+// Cari hesap modeli
+type Account struct {
+ DisplayCode string `json:"display_code"` // "ZLA 0127"
+ AccountCode string `json:"account_code"` // "ZLA0127"
+ AccountName string `json:"account_name"` // "EKO TEXTIL COM SRL" ya da ""
+}
diff --git a/svc/models/custom_time.go b/svc/models/custom_time.go
new file mode 100644
index 0000000..50ee9fb
--- /dev/null
+++ b/svc/models/custom_time.go
@@ -0,0 +1,45 @@
+package models
+
+import (
+ "database/sql"
+ "strings"
+ "time"
+)
+
+type CustomTime struct {
+ sql.NullTime
+}
+
+func (ct *CustomTime) UnmarshalJSON(b []byte) error {
+ s := strings.Trim(string(b), `"`)
+ if s == "" || s == "null" {
+ ct.Valid = false
+ return nil
+ }
+
+ // DESTEKLENEN TÜM FORMATLAR
+ layouts := []string{
+ time.RFC3339, // 2025-11-21T00:10:27Z
+ "2006-01-02T15:04:05", // 2025-11-21T00:10:27
+ "2006-01-02 15:04:05", // 2025-11-21 00:10:27 ← FRONTEND FORMATIN!
+ }
+
+ for _, layout := range layouts {
+ if t, err := time.Parse(layout, s); err == nil {
+ ct.Time = t
+ ct.Valid = true
+ return nil
+ }
+ }
+
+ // Hâlâ parse edemediyse → invalid kabul et
+ ct.Valid = false
+ return nil
+}
+
+func (ct CustomTime) MarshalJSON() ([]byte, error) {
+ if ct.Valid {
+ return []byte(`"` + ct.Time.Format("2006-01-02 15:04:05") + `"`), nil
+ }
+ return []byte(`null`), nil
+}
diff --git a/svc/models/customerlist.go b/svc/models/customerlist.go
new file mode 100644
index 0000000..07eda1e
--- /dev/null
+++ b/svc/models/customerlist.go
@@ -0,0 +1,13 @@
+// models/customerlist.go
+package models
+
+type CustomerList struct {
+ CurrAccTypeCode int `json:"CurrAccTypeCode"` // 🆕 FK için lazım
+ Cari_Kod string `json:"Cari_Kod"`
+ Cari_Ad string `json:"Cari_Ad"`
+ Musteri_Ana_Grubu string `json:"Musteri_Ana_Grubu"`
+ Piyasa string `json:"Piyasa"`
+ Musteri_Temsilcisi string `json:"Musteri_Temsilcisi"`
+ Ulke string `json:"Ulke"`
+ Doviz_cinsi string `json:"Doviz_Cinsi"`
+}
diff --git a/svc/models/invalid_variant.go b/svc/models/invalid_variant.go
new file mode 100644
index 0000000..c6b9a42
--- /dev/null
+++ b/svc/models/invalid_variant.go
@@ -0,0 +1,13 @@
+package models
+
+type InvalidVariant struct {
+ Index int `json:"index"`
+ ClientKey string `json:"clientKey"`
+ ItemCode string `json:"itemCode"`
+ ColorCode string `json:"colorCode"`
+ Dim1 string `json:"dim1"`
+ Dim2 string `json:"dim2"`
+ Qty1 float64 `json:"qty1"`
+ ComboKey string `json:"comboKey,omitempty"`
+ Reason string `json:"reason,omitempty"`
+}
diff --git a/svc/models/mk_user.go b/svc/models/mk_user.go
new file mode 100644
index 0000000..da7a4bf
--- /dev/null
+++ b/svc/models/mk_user.go
@@ -0,0 +1,57 @@
+package models
+
+import "time"
+
+type MkUser struct {
+ // ==================================================
+ // 🔑 PRIMARY ID (mk_dfusr.id)
+ // ==================================================
+ ID int64 `json:"id"` // mk_dfusr.id
+ RoleID int64 `json:"role_id"`
+
+ // ==================================================
+ // 🔁 LEGACY DFUSR (opsiyonel – migrate/debug)
+ // ==================================================
+ LegacyDfUsrID *int64 `json:"legacy_dfusr_id,omitempty"`
+
+ // ==================================================
+ // 👤 CORE
+ // ==================================================
+ Username string `json:"username"`
+ FullName string `json:"full_name"`
+ Email string `json:"email"`
+ Mobile string `json:"mobile"`
+ IsActive bool `json:"is_active"`
+
+ // ==================================================
+ // 🔐 AUTH
+ // ==================================================
+ PasswordHash string `json:"-"`
+ ForcePasswordChange bool `json:"force_password_change"`
+ PasswordUpdatedAt *time.Time `json:"password_updated_at,omitempty"`
+ SessionID string `json:"session_id"`
+
+ // ==================================================
+ // 🧩 ROLE
+ // ==================================================
+ RoleCode string `json:"role_code"`
+
+ // ==================================================
+ // 🧾 NEBIM
+ // ==================================================
+ V3Username string `json:"v3_username"`
+ V3UserGroup string `json:"v3_usergroup"`
+
+ // ==================================================
+ // 🏭 RUNTIME SCOPES
+ // ==================================================
+ DepartmentCodes []string `json:"departments"`
+ PiyasaCodes []string `json:"piyasalar"`
+
+ // ==================================================
+ // ⏱ AUDIT
+ // ==================================================
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+ LastLoginAt *time.Time `json:"last_login_at,omitempty"`
+}
diff --git a/svc/models/modelcombo_error.go b/svc/models/modelcombo_error.go
new file mode 100644
index 0000000..9a97f3e
--- /dev/null
+++ b/svc/models/modelcombo_error.go
@@ -0,0 +1,16 @@
+// models/errors.go
+package models
+
+type ValidationError struct {
+ Code string `json:"code"`
+ Message string `json:"message"`
+ ClientKey string `json:"clientKey,omitempty"`
+ ItemCode string `json:"itemCode,omitempty"`
+ ColorCode string `json:"colorCode,omitempty"`
+ Dim1 string `json:"dim1,omitempty"`
+ Dim2 string `json:"dim2,omitempty"`
+}
+
+func (e *ValidationError) Error() string {
+ return e.Message
+}
diff --git a/svc/models/null_uuid.go b/svc/models/null_uuid.go
new file mode 100644
index 0000000..f380e3c
--- /dev/null
+++ b/svc/models/null_uuid.go
@@ -0,0 +1,120 @@
+package models
+
+import (
+ "database/sql/driver"
+ "encoding/json"
+ "strings"
+)
+
+// ============================================================
+// 🔹 NullUUID — MSSQL UNIQUEIDENTIFIER için nullable tip
+// ============================================================
+type NullUUID struct {
+ UUID string `json:"UUID"`
+ Valid bool `json:"Valid"`
+}
+
+// ------------------------------------------------------------
+//
+// SQL Valuer (INSERT/UPDATE parametresi)
+//
+// ------------------------------------------------------------
+func (n NullUUID) Value() (driver.Value, error) {
+ if !n.Valid || strings.TrimSpace(n.UUID) == "" {
+ return nil, nil
+ }
+ return n.UUID, nil
+}
+
+// ------------------------------------------------------------
+//
+// SQL Scanner (SELECT sonucu)
+//
+// ------------------------------------------------------------
+func (n *NullUUID) Scan(value interface{}) error {
+ if value == nil {
+ n.UUID = ""
+ n.Valid = false
+ return nil
+ }
+
+ switch v := value.(type) {
+ case string:
+ n.UUID = v
+ n.Valid = true
+ case []byte:
+ n.UUID = string(v)
+ n.Valid = true
+ default:
+ n.UUID = ""
+ n.Valid = false
+ }
+ return nil
+}
+
+// ------------------------------------------------------------
+//
+// JSON Marshal — Go → JSON
+// Null ise: null
+// Değer varsa: "guid-string"
+//
+// ------------------------------------------------------------
+func (n NullUUID) MarshalJSON() ([]byte, error) {
+ if !n.Valid || strings.TrimSpace(n.UUID) == "" {
+ return []byte("null"), nil
+ }
+ return json.Marshal(n.UUID)
+}
+
+// ------------------------------------------------------------
+//
+// JSON Unmarshal — JSON → Go
+// Kabul ettiği formatlar:
+// • null
+// • "guid-string"
+// • {"UUID":"...","Valid":true} (ileride istersen)
+//
+// ------------------------------------------------------------
+func (n *NullUUID) UnmarshalJSON(data []byte) error {
+ s := strings.TrimSpace(string(data))
+
+ // null veya boş
+ if s == "null" || s == "" {
+ n.UUID = ""
+ n.Valid = false
+ return nil
+ }
+
+ // Önce string gibi dene: "guid"
+ var str string
+ if err := json.Unmarshal(data, &str); err == nil {
+ str = strings.TrimSpace(str)
+ if str == "" {
+ n.UUID = ""
+ n.Valid = false
+ } else {
+ n.UUID = str
+ n.Valid = true
+ }
+ return nil
+ }
+
+ // Objeyse: { "UUID":"...", "Valid":true }
+ var aux struct {
+ UUID string `json:"UUID"`
+ Valid bool `json:"Valid"`
+ }
+ if err := json.Unmarshal(data, &aux); err != nil {
+ return err
+ }
+
+ aux.UUID = strings.TrimSpace(aux.UUID)
+ if aux.UUID == "" || !aux.Valid {
+ n.UUID = ""
+ n.Valid = false
+ } else {
+ n.UUID = aux.UUID
+ n.Valid = true
+ }
+ return nil
+}
diff --git a/svc/models/order_pdf.go b/svc/models/order_pdf.go
new file mode 100644
index 0000000..189a724
--- /dev/null
+++ b/svc/models/order_pdf.go
@@ -0,0 +1,54 @@
+package models
+
+// ===============================
+// 🔹 ORDER HEADER (PDF için)
+// ===============================
+type OrderHeaderPDF struct {
+ OrderHeaderID string `json:"OrderHeaderID"`
+ OrderNumber string `json:"OrderNumber"`
+ CurrAccCode string `json:"CurrAccCode"`
+ CariAdi string `json:"CariAdi"`
+ OrderDate string `json:"OrderDate"` // yyyy-MM-dd
+ AverageDueDate string `json:"AverageDueDate"` // Termin
+ Description string `json:"Description"`
+}
+
+// ===============================
+// 🔹 ORDER LINE (PDF için)
+// ===============================
+type OrderLinePDF struct {
+ OrderLineID string `json:"OrderLineID"`
+ OrderHeaderID string `json:"OrderHeaderID"`
+
+ Model string `json:"Model"`
+ Renk string `json:"Renk"`
+ Renk2 string `json:"Renk2"`
+ AnaGrup string `json:"AnaGrup"`
+ AltGrup string `json:"AltGrup"`
+ Aciklama string `json:"Aciklama"`
+
+ Fiyat float64 `json:"Fiyat"`
+ Adet float64 `json:"Adet"`
+ Tutar float64 `json:"Tutar"`
+ PB string `json:"PB"`
+
+ TerminTarihi string `json:"TerminTarihi"`
+
+ // 16 beden alanı
+ Size1 float64 `json:"Size1"`
+ Size2 float64 `json:"Size2"`
+ Size3 float64 `json:"Size3"`
+ Size4 float64 `json:"Size4"`
+ Size5 float64 `json:"Size5"`
+ Size6 float64 `json:"Size6"`
+ Size7 float64 `json:"Size7"`
+ Size8 float64 `json:"Size8"`
+ Size9 float64 `json:"Size9"`
+ Size10 float64 `json:"Size10"`
+ Size11 float64 `json:"Size11"`
+ Size12 float64 `json:"Size12"`
+ Size13 float64 `json:"Size13"`
+ Size14 float64 `json:"Size14"`
+ Size15 float64 `json:"Size15"`
+ Size16 float64 `json:"Size16"`
+}
diff --git a/svc/models/orderdetail.go b/svc/models/orderdetail.go
new file mode 100644
index 0000000..f8aee81
--- /dev/null
+++ b/svc/models/orderdetail.go
@@ -0,0 +1,98 @@
+package models
+
+// ============================================================
+// 📦 ORDER DETAIL (trOrderLine)
+// ============================================================
+type OrderDetail struct {
+ OrderLineID string `json:"OrderLineID"`
+
+ // 🔑 Frontend’den gelen benzersiz anahtar (sadece JSON, DB kolonu değil)
+ ClientKey NullString `json:"clientKey"`
+ ComboKey NullString `json:"ComboKey"`
+ SortOrder NullInt32 `json:"SortOrder"`
+ ItemTypeCode NullInt16 `json:"ItemTypeCode"`
+ ItemCode NullString `json:"ItemCode"`
+ ColorCode NullString `json:"ColorCode"`
+ ItemDim1Code NullString `json:"ItemDim1Code"`
+ ItemDim2Code NullString `json:"ItemDim2Code"`
+ ItemDim3Code NullString `json:"ItemDim3Code"`
+
+ Qty1 NullFloat64 `json:"Qty1"`
+ Qty2 NullFloat64 `json:"Qty2"`
+
+ CancelQty1 NullFloat64 `json:"CancelQty1"`
+ CancelQty2 NullFloat64 `json:"CancelQty2"`
+ CancelDate NullTime `json:"CancelDate"`
+
+ OrderCancelReasonCode NullString `json:"OrderCancelReasonCode"`
+ ClosedDate NullTime `json:"ClosedDate"`
+ IsClosed NullBool `json:"IsClosed"`
+
+ SalespersonCode NullString `json:"SalespersonCode"`
+ PaymentPlanCode NullString `json:"PaymentPlanCode"`
+ PurchasePlanCode NullString `json:"PurchasePlanCode"`
+
+ // MSSQL smalldatetime
+ DeliveryDate NullTime `json:"DeliveryDate"`
+
+ // MSSQL date (YYYY-MM-DD)
+ PlannedDateOfLading NullString `json:"PlannedDateOfLading"`
+
+ LineDescription NullString `json:"LineDescription"`
+ UsedBarcode NullString `json:"UsedBarcode"`
+ CostCenterCode NullString `json:"CostCenterCode"`
+
+ VatCode NullString `json:"VatCode"`
+ VatRate NullFloat64 `json:"VatRate"`
+
+ PCTCode NullString `json:"PCTCode"`
+ PCTRate NullFloat64 `json:"PCTRate"`
+
+ LDisRate1 NullFloat64 `json:"LDisRate1"`
+ LDisRate2 NullFloat64 `json:"LDisRate2"`
+ LDisRate3 NullFloat64 `json:"LDisRate3"`
+ LDisRate4 NullFloat64 `json:"LDisRate4"`
+ LDisRate5 NullFloat64 `json:"LDisRate5"`
+
+ DocCurrencyCode NullString `json:"DocCurrencyCode"`
+ PriceCurrencyCode NullString `json:"PriceCurrencyCode"`
+ PriceExchangeRate NullFloat64 `json:"PriceExchangeRate"`
+
+ Price NullFloat64 `json:"Price"`
+
+ PriceListLineID NullString `json:"PriceListLineID"`
+
+ BaseProcessCode NullString `json:"BaseProcessCode"`
+ BaseOrderNumber NullString `json:"BaseOrderNumber"`
+ BaseCustomerTypeCode NullInt32 `json:"BaseCustomerTypeCode"`
+ BaseCustomerCode NullString `json:"BaseCustomerCode"`
+ BaseSubCurrAccID NullString `json:"BaseSubCurrAccID"`
+ BaseStoreCode NullString `json:"BaseStoreCode"`
+
+ SupportRequestHeaderID NullString `json:"SupportRequestHeaderID"`
+
+ OrderHeaderID string `json:"OrderHeaderID"`
+
+ OrderLineSumID NullInt32 `json:"OrderLineSumID"`
+ OrderLineBOMID NullInt32 `json:"OrderLineBOMID"`
+
+ CreatedUserName NullString `json:"CreatedUserName"`
+ CreatedDate NullString `json:"CreatedDate"`
+ LastUpdatedUserName NullString `json:"LastUpdatedUserName"`
+ LastUpdatedDate NullString `json:"LastUpdatedDate"`
+
+ SurplusOrderQtyToleranceRate NullFloat64 `json:"SurplusOrderQtyToleranceRate"`
+
+ PurchaseRequisitionLineID NullString `json:"PurchaseRequisitionLineID"`
+ WithHoldingTaxTypeCode NullString `json:"WithHoldingTaxTypeCode"`
+
+ DOVCode NullString `json:"DOVCode"`
+ OrderLineLinkedProductID NullString `json:"OrderLineLinkedProductID"`
+
+ // JOIN attr
+ UrunIlkGrubu NullString `json:"UrunIlkGrubu"`
+ UrunAnaGrubu NullString `json:"UrunAnaGrubu"`
+ UrunAltGrubu NullString `json:"UrunAltGrubu"`
+ Fit1 NullString `json:"Fit1"`
+ Fit2 NullString `json:"Fit2"`
+}
diff --git a/svc/models/orderheader.go b/svc/models/orderheader.go
new file mode 100644
index 0000000..9b61c0f
--- /dev/null
+++ b/svc/models/orderheader.go
@@ -0,0 +1,109 @@
+package models
+
+// ============================================================
+// 🧾 ORDER HEADER
+// ============================================================
+type OrderHeader struct {
+ OrderHeaderID string `json:"OrderHeaderID"`
+
+ OrderTypeCode NullInt16 `json:"OrderTypeCode"`
+ ProcessCode NullString `json:"ProcessCode"`
+ OrderNumber NullString `json:"OrderNumber"`
+ IsCancelOrder NullBool `json:"IsCancelOrder"`
+
+ // 🔥 Date & Time string olarak geliyor (frontend)
+ OrderDate NullString `json:"OrderDate"` // "YYYY-MM-DD"
+ OrderTime NullString `json:"OrderTime"` // "HH:mm:ss"
+
+ DocumentNumber NullString `json:"DocumentNumber"`
+
+ PaymentTerm NullInt16 `json:"PaymentTerm"`
+ AverageDueDate NullString `json:"AverageDueDate"` // "YYYY-MM-DD"
+
+ Description NullString `json:"Description"`
+ InternalDescription NullString `json:"InternalDescription"`
+
+ CurrAccTypeCode NullInt16 `json:"CurrAccTypeCode"`
+ CurrAccCode NullString `json:"CurrAccCode"`
+
+ CurrAccDescription NullString `json:"CurrAccDescription"`
+
+ // 🔥 GUID alanları NullUUID yapıldı
+ SubCurrAccID NullUUID `json:"SubCurrAccID"`
+ ContactID NullUUID `json:"ContactID"`
+ ShipmentMethodCode NullString `json:"ShipmentMethodCode"`
+ ShippingPostalAddressID NullUUID `json:"ShippingPostalAddressID"`
+ BillingPostalAddressID NullUUID `json:"BillingPostalAddressID"`
+ GuarantorContactID NullUUID `json:"GuarantorContactID"`
+ GuarantorContactID2 NullUUID `json:"GuarantorContactID2"`
+
+ RoundsmanCode NullString `json:"RoundsmanCode"`
+ DeliveryCompanyCode NullString `json:"DeliveryCompanyCode"`
+
+ TaxTypeCode NullInt16 `json:"TaxTypeCode"`
+ WithHoldingTaxTypeCode NullString `json:"WithHoldingTaxTypeCode"`
+ DOVCode NullString `json:"DOVCode"`
+ TaxExemptionCode NullInt16 `json:"TaxExemptionCode"`
+
+ CompanyCode NullInt32 `json:"CompanyCode"`
+ OfficeCode NullString `json:"OfficeCode"`
+ StoreTypeCode NullInt16 `json:"StoreTypeCode"`
+ StoreCode NullString `json:"StoreCode"`
+
+ POSTerminalID NullInt16 `json:"POSTerminalID"`
+ WarehouseCode NullString `json:"WarehouseCode"`
+ ToWarehouseCode NullString `json:"ToWarehouseCode"`
+
+ OrdererCompanyCode NullInt32 `json:"OrdererCompanyCode"`
+ OrdererOfficeCode NullString `json:"OrdererOfficeCode"`
+ OrdererStoreCode NullString `json:"OrdererStoreCode"`
+
+ GLTypeCode NullString `json:"GLTypeCode"`
+ DocCurrencyCode NullString `json:"DocCurrencyCode"`
+ LocalCurrencyCode NullString `json:"LocalCurrencyCode"`
+ ExchangeRate NullFloat64 `json:"ExchangeRate"`
+
+ TDisRate1 NullFloat64 `json:"TDisRate1"`
+ TDisRate2 NullFloat64 `json:"TDisRate2"`
+ TDisRate3 NullFloat64 `json:"TDisRate3"`
+ TDisRate4 NullFloat64 `json:"TDisRate4"`
+ TDisRate5 NullFloat64 `json:"TDisRate5"`
+
+ DiscountReasonCode NullInt16 `json:"DiscountReasonCode"`
+ SurplusOrderQtyToleranceRate NullFloat64 `json:"SurplusOrderQtyToleranceRate"`
+
+ ImportFileNumber NullString `json:"ImportFileNumber"`
+ ExportFileNumber NullString `json:"ExportFileNumber"`
+ IncotermCode1 NullString `json:"IncotermCode1"`
+ IncotermCode2 NullString `json:"IncotermCode2"`
+ LettersOfCreditNumber NullString `json:"LettersOfCreditNumber"`
+ PaymentMethodCode NullString `json:"PaymentMethodCode"`
+
+ IsInclutedVat NullBool `json:"IsInclutedVat"`
+ IsCreditSale NullBool `json:"IsCreditSale"`
+ IsCreditableConfirmed NullBool `json:"IsCreditableConfirmed"`
+
+ CreditableConfirmedUser NullString `json:"CreditableConfirmedUser"`
+ // MSSQL datetime
+ CreditableConfirmedDate CustomTime `json:"CreditableConfirmedDate"`
+
+ IsSalesViaInternet NullBool `json:"IsSalesViaInternet"`
+ IsSuspended NullBool `json:"IsSuspended"`
+ IsCompleted NullBool `json:"IsCompleted"`
+ IsPrinted NullBool `json:"IsPrinted"`
+ IsLocked NullBool `json:"IsLocked"`
+ UserLocked NullBool `json:"UserLocked"`
+ IsClosed NullBool `json:"IsClosed"`
+
+ ApplicationCode NullString `json:"ApplicationCode"`
+ ApplicationID NullUUID `json:"ApplicationID"`
+
+ CreatedUserName NullString `json:"CreatedUserName"`
+ // Frontend "YYYY-MM-DD HH:mm:ss" gönderiyor
+ CreatedDate NullString `json:"CreatedDate"`
+
+ LastUpdatedUserName NullString `json:"LastUpdatedUserName"`
+ LastUpdatedDate NullString `json:"LastUpdatedDate"`
+
+ IsProposalBased NullBool `json:"IsProposalBased"`
+}
diff --git a/svc/models/orderinventory.go b/svc/models/orderinventory.go
new file mode 100644
index 0000000..bdb3b2b
--- /dev/null
+++ b/svc/models/orderinventory.go
@@ -0,0 +1,10 @@
+package models
+
+type OrderInventory struct {
+ UrunKodu string `json:"urun_kodu"`
+ RenkKodu string `json:"renk_kodu"`
+ RenkAciklamasi string `json:"renk_aciklamasi"`
+ Beden string `json:"beden"`
+ Yaka string `json:"yaka"`
+ KullanilabilirEnvanter float64 `json:"kullanilabilir_envanter"`
+}
diff --git a/svc/models/orderlist.go b/svc/models/orderlist.go
new file mode 100644
index 0000000..a91ceff
--- /dev/null
+++ b/svc/models/orderlist.go
@@ -0,0 +1,35 @@
+package models
+
+// ========================================================
+// 📌 OrderList — Sipariş Listeleme Modeli (FINAL & UI SAFE)
+// ========================================================
+type OrderList struct {
+
+ // 🆔 Sipariş Bilgileri
+ OrderHeaderID string `json:"OrderHeaderID"`
+ OrderNumber string `json:"OrderNumber"`
+ OrderDate string `json:"OrderDate"`
+
+ // 🧾 Cari Bilgileri
+ CurrAccCode string `json:"CurrAccCode"`
+ CurrAccDescription string `json:"CurrAccDescription"`
+
+ // 👤 Müşteri Tanımları
+ MusteriTemsilcisi string `json:"MusteriTemsilcisi"`
+ Piyasa string `json:"Piyasa"`
+
+ // ℹ️ Sipariş Durumu
+ CreditableConfirmedDate string `json:"CreditableConfirmedDate"`
+ IsCreditableConfirmed bool `json:"IsCreditableConfirmed"`
+
+ // 💱 Para Birimi
+ DocCurrencyCode string `json:"DocCurrencyCode"`
+
+ // 💰 Tutarlar
+ TotalAmount float64 `json:"TotalAmount"`
+ TotalAmountUSD float64 `json:"TotalAmountUSD"`
+
+ // 📝 Açıklama
+ Description string `json:"Description"`
+ ExchangeRateUSD float64 `json:"ExchangeRateUSD"`
+}
diff --git a/svc/models/ordernull_types.go b/svc/models/ordernull_types.go
new file mode 100644
index 0000000..11e29f8
--- /dev/null
+++ b/svc/models/ordernull_types.go
@@ -0,0 +1,179 @@
+package models
+
+import (
+ "database/sql"
+ "encoding/json"
+ "time"
+)
+
+/* ==============================================================
+ 🔹 NULL TYPE WRAPPERS
+ MSSQL sql.Null* tiplerini JSON'a dönüştürürken:
+ - null yerine "", 0 veya false döndürür
+ - frontend'de (Vue/Quasar) doğrudan okunabilir hale getirir
+============================================================== */
+
+// 🟦 STRING
+type NullString struct {
+ sql.NullString
+}
+
+func (ns *NullString) MarshalJSON() ([]byte, error) {
+ if ns.Valid {
+ return json.Marshal(ns.String)
+ }
+ return json.Marshal("")
+}
+
+func (ns *NullString) UnmarshalJSON(b []byte) error {
+ var s *string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+ if s != nil {
+ ns.Valid = true
+ ns.String = *s
+ } else {
+ ns.Valid = false
+ ns.String = ""
+ }
+ return nil
+}
+
+// 🟩 INT32
+type NullInt32 struct {
+ sql.NullInt32
+}
+
+func (ni *NullInt32) MarshalJSON() ([]byte, error) {
+ if ni.Valid {
+ return json.Marshal(ni.Int32)
+ }
+ return json.Marshal(0)
+}
+
+func (ni *NullInt32) UnmarshalJSON(b []byte) error {
+ var n *int32
+ if err := json.Unmarshal(b, &n); err != nil {
+ return err
+ }
+ if n != nil {
+ ni.Valid = true
+ ni.Int32 = *n
+ } else {
+ ni.Valid = false
+ ni.Int32 = 0
+ }
+ return nil
+}
+
+// 🟧 INT16
+type NullInt16 struct {
+ sql.NullInt16
+}
+
+func (ni *NullInt16) MarshalJSON() ([]byte, error) {
+ if ni.Valid {
+ return json.Marshal(ni.Int16)
+ }
+ return json.Marshal(0)
+}
+
+func (ni *NullInt16) UnmarshalJSON(b []byte) error {
+ var n *int16
+ if err := json.Unmarshal(b, &n); err != nil {
+ return err
+ }
+ if n != nil {
+ ni.Valid = true
+ ni.Int16 = *n
+ } else {
+ ni.Valid = false
+ ni.Int16 = 0
+ }
+ return nil
+}
+
+// 🟨 FLOAT64
+type NullFloat64 struct {
+ sql.NullFloat64
+}
+
+func (nf *NullFloat64) MarshalJSON() ([]byte, error) {
+ if nf.Valid {
+ return json.Marshal(nf.Float64)
+ }
+ return json.Marshal(0.0)
+}
+
+func (nf *NullFloat64) UnmarshalJSON(b []byte) error {
+ var f *float64
+ if err := json.Unmarshal(b, &f); err != nil {
+ return err
+ }
+ if f != nil {
+ nf.Valid = true
+ nf.Float64 = *f
+ } else {
+ nf.Valid = false
+ nf.Float64 = 0
+ }
+ return nil
+}
+
+// 🟥 BOOL
+type NullBool struct {
+ sql.NullBool
+}
+
+func (nb *NullBool) MarshalJSON() ([]byte, error) {
+ if nb.Valid {
+ return json.Marshal(nb.Bool)
+ }
+ return json.Marshal(false)
+}
+
+func (nb *NullBool) UnmarshalJSON(b []byte) error {
+ var v *bool
+ if err := json.Unmarshal(b, &v); err != nil {
+ return err
+ }
+ if v != nil {
+ nb.Valid = true
+ nb.Bool = *v
+ } else {
+ nb.Valid = false
+ nb.Bool = false
+ }
+ return nil
+}
+
+// 🟪 TIME
+type NullTime struct {
+ sql.NullTime
+}
+
+func (nt *NullTime) MarshalJSON() ([]byte, error) {
+ if nt.Valid {
+ return json.Marshal(nt.Time.Format("2006-01-02 15:04:05"))
+ }
+ return json.Marshal(nil)
+}
+
+func (nt *NullTime) UnmarshalJSON(b []byte) error {
+ var s *string
+ if err := json.Unmarshal(b, &s); err != nil {
+ return err
+ }
+ if s != nil && *s != "" {
+ t, err := time.Parse("2006-01-02 15:04:05", *s)
+ if err != nil {
+ return err
+ }
+ nt.Valid = true
+ nt.Time = t
+ } else {
+ nt.Valid = false
+ }
+ return nil
+}
diff --git a/svc/models/orderpricelistb2b.go b/svc/models/orderpricelistb2b.go
new file mode 100644
index 0000000..3a94e7a
--- /dev/null
+++ b/svc/models/orderpricelistb2b.go
@@ -0,0 +1,13 @@
+package models
+
+// OrderPriceListB2B B2B ürün fiyat listesini temsil eder
+type OrderPriceListB2B struct {
+ ModelCode string `json:"modelCode"`
+ CurrencyCode string `json:"currencyCode"`
+ Price float64 `json:"price"`
+ PriceGroupID int `json:"priceGroupId"`
+ LastUpdate string `json:"lastUpdate"`
+ RateToTRY float64 `json:"rateToTRY"`
+ PriceTRY float64 `json:"priceTRY"`
+ BaseCurrency string `json:"baseCurrency"`
+}
diff --git a/svc/models/product.go b/svc/models/product.go
new file mode 100644
index 0000000..eefe700
--- /dev/null
+++ b/svc/models/product.go
@@ -0,0 +1,5 @@
+package models
+
+type Product struct {
+ ProductCode string `json:"ProductCode"` // ✅ büyük P harf ile
+}
diff --git a/svc/models/productcolor.go b/svc/models/productcolor.go
new file mode 100644
index 0000000..850e607
--- /dev/null
+++ b/svc/models/productcolor.go
@@ -0,0 +1,7 @@
+package models
+
+type ProductColor struct {
+ ProductCode string `json:"product_code"`
+ ColorCode string `json:"color_code"`
+ ColorDescription string `json:"color_description"`
+}
diff --git a/svc/models/productcolorsize.go b/svc/models/productcolorsize.go
new file mode 100644
index 0000000..ace2b4f
--- /dev/null
+++ b/svc/models/productcolorsize.go
@@ -0,0 +1,9 @@
+package models
+
+// 🔹 Ürün renk + beden varyasyon listesi (açıklamalar çıkarıldı)
+type ProductColorSize struct {
+ ProductCode string `json:"product_code"`
+ ColorCode string `json:"color_code"`
+ ItemDim1Code string `json:"item_dim1_code"`
+ ItemDim2Code string `json:"item_dim2_code"`
+}
diff --git a/svc/models/productdetail.go b/svc/models/productdetail.go
new file mode 100644
index 0000000..fd8644d
--- /dev/null
+++ b/svc/models/productdetail.go
@@ -0,0 +1,14 @@
+package models
+
+type ProductDetail struct {
+ ProductCode string `json:"ProductCode"`
+ UrunIlkGrubu string `json:"UrunIlkGrubu"`
+ UrunAnaGrubu string `json:"UrunAnaGrubu"`
+ UrunAltGrubu string `json:"UrunAltGrubu"`
+ UrunIcerik string `json:"UrunIcerik"`
+ Drop string `json:"Drop"`
+ Kategori string `json:"Kategori"`
+ AskiliYan string `json:"AskiliYan"`
+ Fit1 string `json:"Fit1"`
+ Fit2 string `json:"Fit2"`
+}
diff --git a/svc/models/productsecondcolor.go b/svc/models/productsecondcolor.go
new file mode 100644
index 0000000..3977480
--- /dev/null
+++ b/svc/models/productsecondcolor.go
@@ -0,0 +1,7 @@
+package models
+
+type ProductSecondColor struct {
+ ProductCode string `json:"product_code"`
+ ColorCode string `json:"color_code"`
+ ItemDim2Code string `json:"item_dim2_code"`
+}
diff --git a/svc/models/statement_detail.go b/svc/models/statement_detail.go
new file mode 100644
index 0000000..f7a6786
--- /dev/null
+++ b/svc/models/statement_detail.go
@@ -0,0 +1,17 @@
+package models
+
+// Detay tablo (alt satır)
+type StatementDetail struct {
+ BelgeTarihi string `json:"belge_tarihi"` // Belge tarihi
+ BelgeRefNumarasi string `json:"belge_ref_numarasi"` // Belge referans numarası
+ UrunAnaGrubu string `json:"urun_ana_grubu"` // Ürün ana grubu
+ UrunAltGrubu string `json:"urun_alt_grubu"` // Ürün alt grubu
+ YetiskinGarson string `json:"yetiskin_garson"` // Yetişkin/Çocuk (garson)
+ Fit string `json:"fit"` // Fit bilgisi
+ Icerik string `json:"icerik"` // İçerik
+ UrunKodu string `json:"urun_kodu"` // Ürün kodu
+ UrunRengi string `json:"urun_rengi"` // Ürün rengi
+ ToplamAdet float64 `json:"toplam_adet"` // Toplam adet
+ ToplamFiyat float64 `json:"toplam_fiyat"` // Birim fiyat
+ ToplamTutar float64 `json:"toplam_tutar"` // Toplam tutar
+}
diff --git a/svc/models/statement_header.go b/svc/models/statement_header.go
new file mode 100644
index 0000000..c7144cf
--- /dev/null
+++ b/svc/models/statement_header.go
@@ -0,0 +1,41 @@
+package models
+
+import (
+ "database/sql"
+ "encoding/json"
+)
+
+type StatementHeader struct {
+ CariKod string `json:"cari_kod"`
+ CariIsim string `json:"cari_isim"`
+ BelgeTarihi string `json:"belge_tarihi"`
+ VadeTarihi string `json:"vade_tarihi"`
+ BelgeNo string `json:"belge_no"`
+ IslemTipi string `json:"islem_tipi"`
+ Aciklama string `json:"aciklama"`
+ ParaBirimi string `json:"para_birimi"`
+ Borc float64 `json:"borc"`
+ Alacak float64 `json:"alacak"`
+ Bakiye float64 `json:"bakiye"`
+ Parislemler sql.NullString `json:"parislemler"`
+
+ // 🔹 PDF için detaylar
+ Details []StatementDetail `json:"details,omitempty"`
+}
+
+// JSON dönüşümünde NULL değerleri "" yap
+func (s StatementHeader) MarshalJSON() ([]byte, error) {
+ type Alias StatementHeader
+ return json.Marshal(&struct {
+ Parislemler string `json:"parislemler"`
+ *Alias
+ }{
+ Parislemler: func() string {
+ if s.Parislemler.Valid {
+ return s.Parislemler.String
+ }
+ return ""
+ }(),
+ Alias: (*Alias)(&s),
+ })
+}
diff --git a/svc/models/statements_params.go b/svc/models/statements_params.go
new file mode 100644
index 0000000..595363d
--- /dev/null
+++ b/svc/models/statements_params.go
@@ -0,0 +1,10 @@
+package models
+
+type StatementParams struct {
+ CariKod string `json:"cari_kod"`
+ StartDate string `json:"startdate"`
+ EndDate string `json:"enddate"`
+ AccountCode string `json:"accountcode"`
+ LangCode string `json:"langcode"`
+ Parislemler []string `json:"parislemler"` // ✅ slice olmalı
+}
diff --git a/svc/models/todaycurrencyv3.go b/svc/models/todaycurrencyv3.go
new file mode 100644
index 0000000..e4eab48
--- /dev/null
+++ b/svc/models/todaycurrencyv3.go
@@ -0,0 +1,10 @@
+package models
+
+// TodayCurrencyV3 sistemdeki döviz kurlarını temsil eder
+type TodayCurrencyV3 struct {
+ CurrencyCode string `json:"currencyCode"`
+ RelationCurrencyCode string `json:"relationCurrencyCode"`
+ ExchangeTypeCode int `json:"exchangeTypeCode"`
+ Rate float64 `json:"rate"`
+ Date string `json:"date"`
+}
diff --git a/svc/models/user_detail.go b/svc/models/user_detail.go
new file mode 100644
index 0000000..8e83a67
--- /dev/null
+++ b/svc/models/user_detail.go
@@ -0,0 +1,88 @@
+package models
+
+import "encoding/json"
+
+// ======================================================
+// 👤 USER DETAIL — GET RESPONSE
+// UserDetail.vue formuna birebir
+// ======================================================
+type UserDetail struct {
+ ID int64 `json:"id"`
+ Code string `json:"code"`
+ IsActive bool `json:"is_active"`
+ FullName string `json:"full_name"`
+ Email string `json:"email"`
+ Mobile string `json:"mobile"`
+ Address string `json:"address"`
+
+ HasPassword bool `json:"has_password"` // 🔐 SADECE DURUM
+
+ // ===== İLİŞKİLER =====
+ Roles []string `json:"roles"`
+ Departments []DeptOption `json:"departments"`
+ Piyasalar []DeptOption `json:"piyasalar"`
+ NebimUsers []NebimOption `json:"nebim_users"`
+}
+
+// ======================================================
+// ✍️ USER WRITE — PUT PAYLOAD
+// ======================================================
+type UserWrite struct {
+ Code string `json:"code"`
+ IsActive bool `json:"is_active"`
+ FullName string `json:"full_name"`
+ Email string `json:"email"`
+ Mobile string `json:"mobile"`
+ Address string `json:"address"`
+
+ Roles []string `json:"roles"`
+ Departments []DeptOption `json:"departments"`
+ Piyasalar []DeptOption `json:"piyasalar"`
+ NebimUsers []NebimOption `json:"nebim_users"`
+}
+
+// ======================================================
+// 🔹 COMMON OPTION MODELS
+// ======================================================
+type DeptOption struct {
+ Code string `json:"code"`
+ Title string `json:"title,omitempty"`
+}
+
+// Flexible JSON decode
+func (d *DeptOption) UnmarshalJSON(data []byte) error {
+
+ var raw struct {
+ Code any `json:"code"`
+ Title string `json:"title"`
+ }
+
+ if err := json.Unmarshal(data, &raw); err != nil {
+ return err
+ }
+
+ d.Title = raw.Title
+
+ switch v := raw.Code.(type) {
+
+ case string:
+ d.Code = v
+
+ case []any:
+ if len(v) > 0 {
+ if s, ok := v[0].(string); ok {
+ d.Code = s
+ }
+ }
+
+ default:
+ d.Code = ""
+ }
+
+ return nil
+}
+
+type NebimOption struct {
+ Username string `json:"username"`
+ UserGroupCode string `json:"user_group_code"`
+}
diff --git a/svc/models/user_list.go b/svc/models/user_list.go
new file mode 100644
index 0000000..87a316e
--- /dev/null
+++ b/svc/models/user_list.go
@@ -0,0 +1,18 @@
+package models
+
+// UserListRow — UserList.vue satır modeli (FINAL)
+type UserListRow struct {
+ ID int64 `json:"id"`
+ Code string `json:"code"`
+ IsActive bool `json:"is_active"`
+
+ // Nebim eşleşmesi (ID yok)
+ NebimUsername *string `json:"nebim_username,omitempty"`
+ UserGroupCode *string `json:"user_group_code,omitempty"`
+
+ // UI’da gösterilecek toplu alanlar
+ RoleNames string `json:"role_names"` // "ADMIN, USER"
+ DepartmentNames string `json:"department_names"` // "UST YONETIM, ..."
+ PiyasaNames string `json:"piyasa_names"` // "AVRUPA, LALELI"
+
+}
diff --git a/svc/models/users.go b/svc/models/users.go
new file mode 100644
index 0000000..6d068e4
--- /dev/null
+++ b/svc/models/users.go
@@ -0,0 +1,20 @@
+package models
+
+type User struct {
+ ID int `json:"id"`
+ Username string `json:"username"`
+
+ IsActive bool `json:"is_active"`
+ Email string `json:"email"`
+
+ RoleID int `json:"role_id"`
+ RoleCode string `json:"role_code"`
+ FullName string
+ Mobile string
+ Address string
+ V3Username string `json:"v3_username"`
+ V3UserGroup int `json:"v3_usergroup"`
+ ForcePasswordChange bool `json:"force_password_change"`
+
+ Upass string // 🔐 dfusr.upass (TEK KAYNAK)
+}
diff --git a/svc/permissions/models.go b/svc/permissions/models.go
new file mode 100644
index 0000000..9e1e520
--- /dev/null
+++ b/svc/permissions/models.go
@@ -0,0 +1,83 @@
+package permissions
+
+/* =====================================================
+ ROLE PERMISSION MATRIX (READ)
+ - Role bazlı matrix ekranı için: role_id + role_code + module/action/allowed
+ - UI isterse "source" alanı ile satırın nereden geldiğini gösterebilir.
+===================================================== */
+
+type PermissionMatrixRow struct {
+ // Role tarafı
+ RoleID int `json:"role_id,omitempty"`
+ RoleCode string `json:"role_code,omitempty"`
+
+ // User override tarafı
+ UserID int64 `json:"user_id,omitempty"`
+
+ // Ortak alanlar
+ Module string `json:"module"`
+ ModuleCode string `json:"module_code"`
+ Action string `json:"action"`
+ Allowed bool `json:"allowed"`
+
+ // role | user
+ Source string `json:"source"`
+}
+
+/* =====================================================
+ ROLE PERMISSION UPDATE (WRITE)
+ - /api/permissions/matrix POST gibi update endpoint'lerinde kullanılır
+===================================================== */
+
+type PermissionUpdateRequest struct {
+ RoleID int `json:"role_id"`
+ Module string `json:"module"`
+ Action string `json:"action"`
+ Allowed bool `json:"allowed"`
+}
+
+/* =====================================================
+ USER OVERRIDE MODELS
+ - mk_sys_user_permissions tablosu için
+===================================================== */
+
+// DB’den okurken döndüğümüz satır (GET /api/users/{id}/permissions)
+type UserOverrideRow struct {
+ Module string `json:"module"`
+ Action string `json:"action"`
+ Allowed bool `json:"allowed"`
+}
+
+// Middleware içinde hızlı okuma için de kullanılabilir (GetUserOverrides)
+type UserPermissionOverride struct {
+ Module string `json:"module"`
+ Action string `json:"action"`
+ Allowed bool `json:"allowed"`
+}
+
+// Repo “upsert/insert” gibi operasyonlarda kullanılacak internal model
+type UserPermission struct {
+ UserID int64 `json:"user_id"`
+ Module string `json:"module"`
+ Action string `json:"action"`
+ Allowed bool `json:"allowed"`
+}
+
+// POST payload için tip (SaveUserOverrides input)
+type UserPermissionRequest struct {
+ Module string `json:"module"`
+ Action string `json:"action"`
+ Allowed bool `json:"allowed"`
+}
+
+// Role + Department Permission Row
+type RoleDepartmentPermission struct {
+ RoleID int `json:"role_id"`
+ DepartmentCode string `json:"department_code"`
+
+ Module string `json:"module"`
+ Action string `json:"action"`
+ Allowed bool `json:"allowed"`
+
+ Source string `json:"source"` // "role_dept"
+}
diff --git a/svc/permissions/repository.go b/svc/permissions/repository.go
new file mode 100644
index 0000000..aa40648
--- /dev/null
+++ b/svc/permissions/repository.go
@@ -0,0 +1,601 @@
+package permissions
+
+import (
+ "database/sql"
+ "log"
+ "strings"
+
+ "github.com/lib/pq"
+)
+
+type PermissionRepository struct {
+ DB *sql.DB
+}
+
+func NewPermissionRepository(db *sql.DB) *PermissionRepository {
+ return &PermissionRepository{DB: db}
+}
+
+/* =====================================================
+ MATRIX READ (V2) - ROLE BASED
+===================================================== */
+
+func (r *PermissionRepository) GetPermissionMatrixForRoles(
+ roleIDs []int,
+) ([]PermissionMatrixRow, error) {
+
+ if len(roleIDs) == 0 {
+ return []PermissionMatrixRow{}, nil
+ }
+
+ query := `
+ SELECT
+ rp.role_id,
+ rol.code,
+ rp.module_code,
+ rp.action,
+ rp.allowed
+ FROM mk_sys_role_permissions rp
+ JOIN dfrole rol ON rol.id = rp.role_id
+ WHERE rp.role_id = ANY($1)
+ ORDER BY rol.id, rp.module_code, rp.action
+ `
+
+ rows, err := r.DB.Query(query, pq.Array(roleIDs))
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ list := make([]PermissionMatrixRow, 0)
+
+ for rows.Next() {
+ var row PermissionMatrixRow
+ if err := rows.Scan(
+ &row.RoleID,
+ &row.RoleCode,
+ &row.Module,
+ &row.Action,
+ &row.Allowed,
+ ); err != nil {
+ return nil, err
+ }
+
+ row.Source = "role"
+ list = append(list, row)
+ }
+
+ return list, nil
+}
+
+/* =====================================================
+ MATRIX UPDATE (V2) - ROLE BASED
+===================================================== */
+
+func (r *PermissionRepository) UpdatePermissions(
+ list []PermissionUpdateRequest,
+) error {
+
+ tx, err := r.DB.Begin()
+ if err != nil {
+ return err
+ }
+ defer tx.Rollback()
+
+ stmt, err := tx.Prepare(`
+ INSERT INTO mk_sys_role_permissions
+ (role_id, module_code, action, allowed)
+ VALUES ($1,$2,$3,$4)
+ ON CONFLICT (role_id, module_code, action)
+ DO UPDATE SET allowed = EXCLUDED.allowed
+ `)
+ if err != nil {
+ return err
+ }
+ defer stmt.Close()
+
+ for _, p := range list {
+ if _, err := stmt.Exec(
+ p.RoleID,
+ p.Module,
+ p.Action,
+ p.Allowed,
+ ); err != nil {
+ return err
+ }
+ }
+
+ return tx.Commit()
+}
+
+/* =====================================================
+ USER OVERRIDES - READ
+ GET /api/users/{id}/permissions
+===================================================== */
+
+// Tek tip: PermissionMatrixRow döndürüyoruz (source=user)
+func (r *PermissionRepository) GetUserOverridesByUserID(
+ userID int64,
+) ([]PermissionMatrixRow, error) {
+
+ rows, err := r.DB.Query(`
+ SELECT
+ user_id,
+ module_code,
+ action,
+ allowed
+ FROM mk_sys_user_permissions
+ WHERE user_id = $1
+ ORDER BY module_code, action
+ `, userID)
+
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ list := make([]PermissionMatrixRow, 0)
+
+ for rows.Next() {
+ var row PermissionMatrixRow
+ if err := rows.Scan(
+ &row.UserID,
+ &row.Module,
+ &row.Action,
+ &row.Allowed,
+ ); err != nil {
+ return nil, err
+ }
+
+ row.Source = "user"
+ list = append(list, row)
+ }
+
+ return list, nil
+}
+
+/* =====================================================
+ USER OVERRIDES - UPSERT SAVE (typed)
+ POST /api/users/{id}/permissions
+===================================================== */
+
+func (r *PermissionRepository) SaveUserOverrides(
+ userID int64,
+ list []UserPermissionRequest,
+) error {
+
+ log.Println("➡️ REPO SaveUserOverrides START")
+ log.Println("USER:", userID)
+ log.Println("ROWS:", len(list))
+
+ tx, err := r.DB.Begin()
+ if err != nil {
+ log.Println("❌ TX BEGIN ERROR:", err)
+ return err
+ }
+ defer tx.Rollback()
+
+ // önce sil
+ _, err = tx.Exec(`
+ DELETE FROM mk_sys_user_permissions
+ WHERE user_id = $1
+ `, userID)
+
+ if err != nil {
+ log.Println("❌ DELETE ERROR:", err)
+ return err
+ }
+
+ stmt, err := tx.Prepare(`
+ INSERT INTO mk_sys_user_permissions
+ (user_id, module_code, action, allowed)
+ VALUES ($1,$2,$3,$4)
+ `)
+
+ if err != nil {
+ log.Println("❌ PREPARE ERROR:", err)
+ return err
+ }
+ defer stmt.Close()
+
+ for _, p := range list {
+ if strings.TrimSpace(p.Module) == "" {
+ log.Printf("⚠️ SKIP EMPTY MODULE user=%d action=%s",
+ userID,
+ p.Action,
+ )
+ continue
+ }
+
+ _, err := stmt.Exec(
+ userID,
+ p.Module,
+ p.Action,
+ p.Allowed,
+ )
+ if err != nil {
+ log.Println("❌ INSERT ERROR:", err)
+ return err
+ }
+ }
+
+ if err := tx.Commit(); err != nil {
+ log.Println("❌ COMMIT ERROR:", err)
+ return err
+ }
+
+ log.Println("✅ REPO SaveUserOverrides DONE")
+ return nil
+
+}
+
+/* =====================================================
+ RESOLUTION HELPERS (middleware için)
+===================================================== */
+
+// user override var mı? varsa *bool döner, yoksa nil
+func (r *PermissionRepository) HasUserOverride(
+ userID int64,
+ module string,
+ action string,
+) (*bool, error) {
+
+ var allowed bool
+
+ err := r.DB.QueryRow(`
+ SELECT allowed
+ FROM mk_sys_user_permissions
+ WHERE user_id=$1
+ AND module_code=$2
+ AND action=$3
+ `,
+ userID,
+ module,
+ action,
+ ).Scan(&allowed)
+
+ if err == sql.ErrNoRows {
+ return nil, nil
+ }
+ if err != nil {
+ return nil, err
+ }
+
+ return &allowed, nil
+}
+
+// roleIDs içinden OR logic: herhangi biri allowed=true ise true
+func (r *PermissionRepository) HasRoleAccess(
+ roleIDs []int,
+ module string,
+ action string,
+) (bool, error) {
+
+ if len(roleIDs) == 0 {
+ return false, nil
+ }
+
+ var count int
+
+ err := r.DB.QueryRow(`
+ SELECT COUNT(*)
+ FROM mk_sys_role_permissions
+ WHERE role_id = ANY($1)
+ AND module_code=$2
+ AND action=$3
+ AND allowed=true
+ `,
+ pq.Array(roleIDs),
+ module,
+ action,
+ ).Scan(&count)
+
+ if err != nil {
+ return false, err
+ }
+
+ return count > 0, nil
+}
+
+// Final decision: user override varsa onu uygula, yoksa role bazlı
+func (r *PermissionRepository) ResolvePermission(
+ userID int64,
+ roleIDs []int,
+ module string,
+ action string,
+) (bool, error) {
+
+ override, err := r.HasUserOverride(userID, module, action)
+ if err != nil {
+ return false, err
+ }
+
+ if override != nil {
+ return *override, nil
+ }
+
+ return r.HasRoleAccess(roleIDs, module, action)
+}
+func (r *PermissionRepository) GetUserOverrides(
+ userID int64,
+) ([]UserPermissionOverride, error) {
+
+ rows, err := r.DB.Query(`
+ SELECT module_code, action, allowed
+ FROM mk_sys_user_permissions
+ WHERE user_id = $1
+ `, userID)
+
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ var list []UserPermissionOverride
+
+ for rows.Next() {
+
+ var o UserPermissionOverride
+
+ if err := rows.Scan(
+ &o.Module,
+ &o.Action,
+ &o.Allowed,
+ ); err != nil {
+ return nil, err
+ }
+
+ list = append(list, o)
+ }
+
+ return list, nil
+}
+func (r *PermissionRepository) UpdateUserOverrides(
+ list []UserPermission,
+) error {
+
+ tx, err := r.DB.Begin()
+ if err != nil {
+ return err
+ }
+ defer tx.Rollback()
+
+ stmt, err := tx.Prepare(`
+ INSERT INTO mk_sys_user_permissions
+ (user_id, module_code, action, allowed)
+
+ VALUES ($1,$2,$3,$4)
+
+ ON CONFLICT (user_id, module_code, action)
+ DO UPDATE SET allowed = EXCLUDED.allowed
+ `)
+ if err != nil {
+ return err
+ }
+ defer stmt.Close()
+
+ for _, p := range list {
+
+ _, err := stmt.Exec(
+ p.UserID,
+ p.Module,
+ p.Action,
+ p.Allowed,
+ )
+
+ if err != nil {
+ return err
+ }
+ }
+
+ return tx.Commit()
+}
+func (r *PermissionRepository) ResolveEffectivePermission(
+ userID int64,
+ roleID int64,
+ deptCode string,
+ module string,
+ action string,
+) (bool, error) {
+
+ // 1️⃣ USER OVERRIDE
+ var allowed bool
+
+ err := r.DB.QueryRow(`
+ SELECT allowed
+ FROM mk_sys_user_permissions
+ WHERE user_id = $1
+ AND module_code = $2
+ AND action = $3
+ `,
+ userID, module, action,
+ ).Scan(&allowed)
+
+ if err == nil {
+ return allowed, nil
+ }
+ if err != sql.ErrNoRows {
+ return false, err
+ }
+
+ // ==================================================
+ // 2️⃣ ROLE + DEPARTMENT
+ // ==================================================
+
+ if len(deptCode) > 0 {
+
+ var allowed bool
+
+ err = r.DB.QueryRow(` -- 🔥 := DEĞİL =
+ SELECT allowed
+ FROM vw_role_dept_permissions
+ WHERE role_id = $1
+ AND department_code = ANY($2)
+ AND module_code = $3
+ AND action = $4
+ ORDER BY allowed DESC
+ LIMIT 1
+ `,
+ roleID,
+ pq.Array([]string{deptCode}),
+ module,
+ action,
+ ).Scan(&allowed)
+
+ if err == nil {
+
+ log.Printf(
+ " ↳ ROLE+DEPT OVERRIDE = %v",
+ allowed,
+ )
+
+ return allowed, nil
+ }
+
+ if err != sql.ErrNoRows {
+ log.Println("❌ ROLE+DEPT ERR:", err)
+ return false, err
+ }
+ }
+
+ // 3️⃣ ROLE DEFAULT
+ err = r.DB.QueryRow(`
+ SELECT allowed
+ FROM mk_sys_role_permissions
+ WHERE role_id = $1
+ AND module_code = $2
+ AND action = $3
+ `,
+ roleID, module, action,
+ ).Scan(&allowed)
+
+ if err == nil {
+ return allowed, nil
+ }
+ if err != sql.ErrNoRows {
+ return false, err
+ }
+
+ // 4️⃣ DENY
+ return false, nil
+}
+func (r *PermissionRepository) ResolvePermissionChain(
+ userID int64,
+ roleID int64,
+ deptCodes []string,
+ module string,
+ action string,
+) (bool, error) {
+
+ log.Printf(
+ "🔐 PERM CHECK user=%d role=%d dept=%v %s:%s",
+ userID,
+ roleID,
+ deptCodes,
+ module,
+ action,
+ )
+
+ // ==================================================
+ // 1️⃣ USER OVERRIDE
+ // ==================================================
+
+ override, err := r.HasUserOverride(userID, module, action)
+ if err != nil {
+ log.Println("❌ USER OVERRIDE ERR:", err)
+ return false, err
+ }
+
+ if override != nil {
+
+ log.Printf(
+ " ↳ USER OVERRIDE = %v",
+ *override,
+ )
+
+ return *override, nil
+ }
+
+ // ==================================================
+ // 2️⃣ ROLE + DEPARTMENT
+ // ==================================================
+
+ if len(deptCodes) > 0 {
+
+ var allowed bool
+
+ err := r.DB.QueryRow(`
+ SELECT allowed
+ FROM vw_role_dept_permissions
+ WHERE role_id = $1
+ AND department_code IN (
+ SELECT UNNEST($2::text[])
+ )
+ AND module_code = $3
+ AND action = $4
+ ORDER BY allowed DESC
+ LIMIT 1
+`,
+ roleID,
+ pq.Array(deptCodes),
+ module,
+ action,
+ ).Scan(&allowed)
+
+ if err == nil {
+
+ log.Printf(
+ " ↳ ROLE+DEPT OVERRIDE = %v",
+ allowed,
+ )
+
+ return allowed, nil
+ }
+
+ if err != sql.ErrNoRows {
+ log.Println("❌ ROLE+DEPT ERR:", err)
+ return false, err
+ }
+ }
+
+ // ==================================================
+ // 3️⃣ ROLE DEFAULT
+ // ==================================================
+
+ var roleAllowed bool
+
+ err = r.DB.QueryRow(`
+ SELECT allowed
+ FROM mk_sys_role_permissions
+ WHERE role_id = $1
+ AND module_code = $2
+ AND action = $3
+ `,
+ roleID,
+ module,
+ action,
+ ).Scan(&roleAllowed)
+
+ if err == nil {
+
+ log.Printf(
+ " ↳ ROLE DEFAULT = %v",
+ roleAllowed,
+ )
+
+ return roleAllowed, nil
+ }
+
+ if err != sql.ErrNoRows {
+ log.Println("❌ ROLE DEFAULT ERR:", err)
+ return false, err
+ }
+
+ // ==================================================
+ // 4️⃣ DENY
+ // ==================================================
+
+ log.Println(" ↳ NO RULE → DENY")
+
+ return false, nil
+}
diff --git a/svc/permissions/role_department_repo.go b/svc/permissions/role_department_repo.go
new file mode 100644
index 0000000..c73040c
--- /dev/null
+++ b/svc/permissions/role_department_repo.go
@@ -0,0 +1,123 @@
+package permissions
+
+import (
+ "bssapp-backend/queries"
+ "database/sql"
+ "log"
+)
+
+type RoleDepartmentPermissionRepo struct {
+ db *sql.DB
+}
+
+func NewRoleDepartmentPermissionRepo(db *sql.DB) *RoleDepartmentPermissionRepo {
+ return &RoleDepartmentPermissionRepo{db: db}
+}
+
+/* ======================================================
+ GET
+====================================================== */
+
+func (r *RoleDepartmentPermissionRepo) Get(
+ roleID int,
+ deptCode string,
+) ([]RoleDepartmentPermission, error) {
+
+ rows, err := r.db.Query(
+ queries.GetRoleDepartmentPermissions,
+ roleID,
+ deptCode,
+ )
+
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ var list []RoleDepartmentPermission
+
+ for rows.Next() {
+
+ var p RoleDepartmentPermission
+
+ if err := rows.Scan(
+ &p.Module,
+ &p.Action,
+ &p.Allowed,
+ ); err != nil {
+ return nil, err
+ }
+
+ p.RoleID = roleID
+ p.DepartmentCode = deptCode
+ p.Source = "role_department"
+
+ list = append(list, p)
+ }
+
+ return list, nil
+}
+
+/* ======================================================
+ SAVE
+====================================================== */
+
+func (r *RoleDepartmentPermissionRepo) Save(
+ roleID int,
+ deptCode string,
+ list []RoleDepartmentPermission,
+) error {
+
+ tx, err := r.db.Begin()
+ if err != nil {
+ return err
+ }
+ defer tx.Rollback()
+
+ stmt, err := tx.Prepare(
+ queries.UpsertRoleDepartmentPermission,
+ )
+ if err != nil {
+ return err
+ }
+ defer stmt.Close()
+
+ for _, p := range list {
+
+ if p.Module == "" {
+ log.Printf(
+ "⚠️ SKIP EMPTY MODULE role=%v dept=%v action=%v",
+ roleID,
+ deptCode,
+ p.Action,
+ )
+ continue
+ }
+
+ _, err := stmt.Exec(
+ roleID,
+ deptCode,
+ p.Module,
+ p.Action,
+ p.Allowed,
+ )
+
+ // 🔴🔴🔴 KRİTİK DEBUG LOG
+ if err != nil {
+
+ log.Printf(
+ "❌ UPSERT FAIL role=%v dept=%v module=%v action=%v allowed=%v err=%v",
+ roleID,
+ deptCode,
+ p.Module,
+ p.Action,
+ p.Allowed,
+ err,
+ )
+
+ return err
+ }
+ }
+
+ return tx.Commit()
+}
diff --git a/svc/permissions/role_policy.go b/svc/permissions/role_policy.go
new file mode 100644
index 0000000..6f14666
--- /dev/null
+++ b/svc/permissions/role_policy.go
@@ -0,0 +1,33 @@
+package permissions
+
+// 🔥 SYSTEM / ADMIN ROLE IDS
+var AdminRoleIDs = map[int]struct{}{
+ 1: {},
+ 3: {},
+}
+
+// -------------------------------------------------------
+// 🧠 ROLE POLICY
+// -------------------------------------------------------
+func ResolveEffectiveRoles(
+ roleIDs []int,
+) (effectiveRoleIDs []int, isAdmin bool) {
+
+ roleMap := make(map[int]struct{})
+
+ for _, roleID := range roleIDs {
+
+ // 🔥 SYSTEM / ADMIN OVERRIDE (1,3,…)
+ if _, ok := AdminRoleIDs[roleID]; ok {
+ return []int{roleID}, true
+ }
+
+ roleMap[roleID] = struct{}{}
+ }
+
+ for id := range roleMap {
+ effectiveRoleIDs = append(effectiveRoleIDs, id)
+ }
+
+ return effectiveRoleIDs, false
+}
diff --git a/svc/permissions/seed.go b/svc/permissions/seed.go
new file mode 100644
index 0000000..5216e0e
--- /dev/null
+++ b/svc/permissions/seed.go
@@ -0,0 +1,40 @@
+package permissions
+
+import "database/sql"
+
+func SeedAdminRoleDepartments(db *sql.DB) error {
+
+ var adminID int
+ var err error
+
+ // Admin role id al
+ err = db.QueryRow(`
+ SELECT id
+ FROM dfrole
+ WHERE code = 'admin'
+ `).Scan(&adminID)
+
+ if err != nil {
+ return err
+ }
+
+ // Seed
+ _, err = db.Exec(`
+INSERT INTO mk_sys_role_department_permissions
+ (role_id, department_code, module_code, action, allowed)
+
+SELECT
+ $1,
+ d.code,
+ r.module_code,
+ r.action,
+ true
+FROM mk_dprt d
+CROSS JOIN mk_sys_routes r
+
+ON CONFLICT (role_id, department_code, module_code, action)
+DO NOTHING
+`, adminID)
+
+ return err
+}
diff --git a/svc/public/Baggi-Tekstil-A.s-Logolu.jpeg b/svc/public/Baggi-Tekstil-A.s-Logolu.jpeg
new file mode 100644
index 0000000..129519a
Binary files /dev/null and b/svc/public/Baggi-Tekstil-A.s-Logolu.jpeg differ
diff --git a/svc/queries/account.go b/svc/queries/account.go
new file mode 100644
index 0000000..a0ac4d8
--- /dev/null
+++ b/svc/queries/account.go
@@ -0,0 +1,74 @@
+package queries
+
+import (
+ "context"
+ "fmt"
+ "log"
+ "strings"
+
+ "bssapp-backend/db"
+ "bssapp-backend/internal/authz"
+ "bssapp-backend/models"
+)
+
+func GetAccounts(ctx context.Context) ([]models.Account, error) {
+
+ piyasaFilter := authz.BuildMSSQLPiyasaFilter(ctx, "f2.CustomerAtt01")
+
+ if strings.TrimSpace(piyasaFilter) == "" {
+ piyasaFilter = "1=1"
+ }
+
+ query := fmt.Sprintf(`
+ SELECT
+ x.AccountCode,
+ MAX(x.AccountName) AS AccountName
+ FROM (
+ SELECT
+ LEFT(b.CurrAccCode, 8) AS AccountCode,
+ COALESCE(d.CurrAccDescription, '') AS AccountName
+ FROM trCurrAccBook b
+ LEFT JOIN cdCurrAccDesc d
+ ON d.CurrAccCode = b.CurrAccCode
+ JOIN CustomerAttributesFilter f2
+ ON f2.CurrAccCode = b.CurrAccCode
+ WHERE %s
+ ) x
+ GROUP BY x.AccountCode
+ ORDER BY x.AccountCode
+`, piyasaFilter)
+
+ log.Println("🔎 ACCOUNT PIYASA FILTER =", piyasaFilter)
+ log.Println("🔎 ACCOUNT QUERY =", query)
+
+ rows, err := db.MssqlDB.Query(query)
+ if err != nil {
+ return nil, fmt.Errorf("MSSQL query error: %w", err)
+ }
+ defer rows.Close()
+
+ var accounts []models.Account
+
+ for rows.Next() {
+
+ var acc models.Account
+
+ if err := rows.Scan(
+ &acc.AccountCode,
+ &acc.AccountName,
+ ); err != nil {
+ return nil, err
+ }
+
+ if len(acc.AccountCode) >= 4 {
+ acc.DisplayCode =
+ strings.TrimSpace(acc.AccountCode[:3] + " " + acc.AccountCode[3:])
+ } else {
+ acc.DisplayCode = acc.AccountCode
+ }
+
+ accounts = append(accounts, acc)
+ }
+
+ return accounts, rows.Err()
+}
diff --git a/svc/queries/currency_cache.go b/svc/queries/currency_cache.go
new file mode 100644
index 0000000..3859591
--- /dev/null
+++ b/svc/queries/currency_cache.go
@@ -0,0 +1,64 @@
+package queries
+
+import (
+ "bssapp-backend/models"
+ "database/sql"
+ "sync"
+ "time"
+)
+
+/* ===============================
+ CACHE STRUCT
+================================ */
+
+type currencyCacheItem struct {
+ data *models.TodayCurrencyV3
+ expiresAt time.Time
+}
+
+var (
+ currencyCache = make(map[string]currencyCacheItem)
+ cacheMutex sync.RWMutex
+ cacheTTL = 5 * time.Minute
+)
+
+/* ===============================
+ MAIN CACHE FUNC
+================================ */
+
+func GetCachedCurrencyV3(db *sql.DB, code string) (*models.TodayCurrencyV3, error) {
+
+ now := time.Now()
+
+ /* ---------- READ CACHE ---------- */
+ cacheMutex.RLock()
+
+ item, ok := currencyCache[code]
+
+ if ok && now.Before(item.expiresAt) {
+ cacheMutex.RUnlock()
+ return item.data, nil
+ }
+
+ cacheMutex.RUnlock()
+
+ /* ---------- FETCH DB ---------- */
+
+ data, err := GetTodayCurrencyV3(db, code)
+ if err != nil {
+ return nil, err
+ }
+
+ /* ---------- WRITE CACHE ---------- */
+
+ cacheMutex.Lock()
+
+ currencyCache[code] = currencyCacheItem{
+ data: data,
+ expiresAt: now.Add(cacheTTL),
+ }
+
+ cacheMutex.Unlock()
+
+ return data, nil
+}
diff --git a/svc/queries/customerlist.go b/svc/queries/customerlist.go
new file mode 100644
index 0000000..6daeb6a
--- /dev/null
+++ b/svc/queries/customerlist.go
@@ -0,0 +1,114 @@
+package queries
+
+import (
+ "context"
+ "fmt"
+
+ "bssapp-backend/db"
+ "bssapp-backend/internal/authz"
+ "bssapp-backend/models"
+)
+
+func GetCustomerList(ctx context.Context) ([]models.CustomerList, error) {
+
+ piyasaFilter := authz.BuildMSSQLPiyasaFilter(
+ ctx,
+ "f.CustomerAtt01",
+ )
+
+ query := fmt.Sprintf(`
+ SELECT
+ c.CurrAccTypeCode,
+ c.CurrAccCode,
+ ISNULL(d.CurrAccDescription, ''),
+
+ dbo.HG_Temizlik(
+ ISNULL((
+ SELECT AttributeDescription
+ FROM cdCurrAccAttributeDesc WITH(NOLOCK)
+ WHERE CurrAccTypeCode = 3
+ AND AttributeTypeCode = 8
+ AND AttributeCode = f.CustomerAtt08
+ AND LangCode = 'TR'
+ ), SPACE(0))
+ ),
+
+ dbo.HG_Temizlik(
+ ISNULL((
+ SELECT AttributeDescription
+ FROM cdCurrAccAttributeDesc WITH(NOLOCK)
+ WHERE CurrAccTypeCode = 3
+ AND AttributeTypeCode = 1
+ AND AttributeCode = f.CustomerAtt01
+ AND LangCode = 'TR'
+ ), SPACE(0))
+ ),
+
+ dbo.HG_Temizlik(
+ ISNULL((
+ SELECT AttributeDescription
+ FROM cdCurrAccAttributeDesc WITH(NOLOCK)
+ WHERE CurrAccTypeCode = 3
+ AND AttributeTypeCode = 2
+ AND AttributeCode = f.CustomerAtt02
+ AND LangCode = 'TR'
+ ), SPACE(0))
+ ),
+
+ dbo.HG_Temizlik(
+ ISNULL((
+ SELECT AttributeDescription
+ FROM cdCurrAccAttributeDesc WITH(NOLOCK)
+ WHERE CurrAccTypeCode = 3
+ AND AttributeTypeCode = 5
+ AND AttributeCode = f.CustomerAtt05
+ AND LangCode = 'TR'
+ ), SPACE(0))
+ ),
+
+ ISNULL(c.CurrencyCode, '')
+
+ FROM cdCurrAcc c
+ LEFT JOIN cdCurrAccDesc d
+ ON c.CurrAccCode = d.CurrAccCode
+ LEFT JOIN CustomerAttributesFilter f
+ ON c.CurrAccCode = f.CurrAccCode
+
+ WHERE
+ c.CompanyCode = 1
+ AND c.CurrAccTypeCode = 3
+ AND c.IsBlocked = 0
+ AND %s
+
+ ORDER BY d.CurrAccDescription
+ `, piyasaFilter)
+
+ rows, err := db.MssqlDB.Query(query)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ var list []models.CustomerList
+
+ for rows.Next() {
+ var c models.CustomerList
+
+ if err := rows.Scan(
+ &c.CurrAccTypeCode,
+ &c.Cari_Kod,
+ &c.Cari_Ad,
+ &c.Musteri_Ana_Grubu,
+ &c.Piyasa,
+ &c.Musteri_Temsilcisi,
+ &c.Ulke,
+ &c.Doviz_cinsi,
+ ); err != nil {
+ return nil, err
+ }
+
+ list = append(list, c)
+ }
+
+ return list, rows.Err()
+}
diff --git a/svc/queries/get_order_list_excel.go b/svc/queries/get_order_list_excel.go
new file mode 100644
index 0000000..81c2c3c
--- /dev/null
+++ b/svc/queries/get_order_list_excel.go
@@ -0,0 +1,52 @@
+package queries
+
+import (
+ "database/sql"
+ "fmt"
+)
+
+func GetOrderListExcel(
+ db *sql.DB,
+ search string,
+ currAcc string,
+ orderDate string,
+) (*sql.Rows, error) {
+
+ q := OrderListBaseQuery + " AND 1=1 "
+ args := []interface{}{}
+
+ // SEARCH
+ if search != "" {
+ q += `
+AND (
+ LOWER(h.OrderNumber) LIKE LOWER(@p1) OR
+ LOWER(h.CurrAccCode) LIKE LOWER(@p1) OR
+ LOWER(ca.CurrAccDescription) LIKE LOWER(@p1) OR
+ LOWER(h.Description) LIKE LOWER(@p1) OR
+ LOWER(mt.AttributeDescription) LIKE LOWER(@p1) OR
+ LOWER(py.AttributeDescription) LIKE LOWER(@p1)
+)
+`
+ args = append(args, "%"+search+"%")
+ }
+
+ // CURRACC
+ if currAcc != "" {
+ q += fmt.Sprintf(" AND h.CurrAccCode = @p%d ", len(args)+1)
+ args = append(args, currAcc)
+ }
+
+ // DATE
+ if orderDate != "" {
+ q += fmt.Sprintf(
+ " AND CONVERT(varchar, h.OrderDate, 23) = @p%d ",
+ len(args)+1,
+ )
+ args = append(args, orderDate)
+ }
+
+ // ORDER BY SONDA
+ q += " ORDER BY h.CreatedDate DESC "
+
+ return db.Query(q, args...)
+}
diff --git a/svc/queries/helpers.go b/svc/queries/helpers.go
new file mode 100644
index 0000000..5dac5b1
--- /dev/null
+++ b/svc/queries/helpers.go
@@ -0,0 +1,174 @@
+package queries
+
+import (
+ "bssapp-backend/models"
+ "database/sql"
+ "fmt"
+ "github.com/google/uuid"
+ "log"
+ "strings"
+ "time"
+)
+
+// ============================================================
+// 🔥 UNIVERSAL DATETIME PARSER
+// ============================================================
+func parseDateTime(str string) (time.Time, error) {
+ layouts := []string{
+ "2006-01-02 15:04:05", // "2025-11-21 12:37:21"
+ time.RFC3339, // "2025-11-21T12:37:21Z"
+ "2006-01-02", // "2025-11-21"
+ }
+ for _, layout := range layouts {
+ if t, err := time.Parse(layout, str); err == nil {
+ return t, nil
+ }
+ }
+ return time.Time{}, fmt.Errorf("datetime parse edilemedi: %s", str)
+}
+
+// ============================================================
+// 📌 DATE (YYYY-MM-DD) → sql.NullTime
+// ============================================================
+func nullableDateString(ns models.NullString) sql.NullTime {
+ if ns.Valid && strings.TrimSpace(ns.String) != "" {
+ t, err := time.Parse("2006-01-02", ns.String)
+ if err == nil {
+ return sql.NullTime{Valid: true, Time: t}
+ }
+ }
+ return sql.NullTime{}
+}
+
+// ============================================================
+// 📌 TIME (HH:mm:ss) → sql.NullString
+// ============================================================
+func nullableTimeString(ns models.NullString) sql.NullString {
+ if ns.Valid && strings.TrimSpace(ns.String) != "" {
+ return sql.NullString{String: ns.String, Valid: true}
+ }
+ return sql.NullString{}
+}
+
+// ============================================================
+// 📌 DATETIME (CustomTime → sql.NullTime)
+// ============================================================
+func nullableDateTime(ct models.CustomTime, fallback time.Time) sql.NullTime {
+ if ct.Valid && !ct.Time.IsZero() {
+ return sql.NullTime{Valid: true, Time: ct.Time}
+ }
+ return sql.NullTime{Valid: true, Time: fallback}
+}
+
+// ============================================================
+// 📌 DATETIME (NullTime → sql.NullTime)
+// ============================================================
+func nullableTime(nt models.NullTime, fallback time.Time) sql.NullTime {
+ if nt.Valid && !nt.Time.IsZero() {
+ return sql.NullTime{Valid: true, Time: nt.Time}
+ }
+ return sql.NullTime{Valid: true, Time: fallback}
+}
+
+// ============================================================
+// 📌 DATETIME (NullString → sql.NullTime)
+// ============================================================
+func nullableDateTimeString(ns models.NullString, fallback time.Time) sql.NullTime {
+ if !ns.Valid || strings.TrimSpace(ns.String) == "" {
+ return sql.NullTime{Time: fallback, Valid: true}
+ }
+ t, err := parseDateTime(ns.String)
+ if err != nil {
+ return sql.NullTime{Time: fallback, Valid: true}
+ }
+ return sql.NullTime{Time: t, Valid: true}
+}
+
+// ============================================================
+// 📌 STRING → sql.NullString
+// ============================================================
+func nullableString(val models.NullString, fallback string) sql.NullString {
+ if val.Valid && strings.TrimSpace(val.String) != "" {
+ return sql.NullString{String: val.String, Valid: true}
+ }
+ return sql.NullString{String: fallback, Valid: true}
+}
+
+// ============================================================
+// 📌 GUID (UNIQUEIDENTIFIER) → interface{}
+// ============================================================
+func nullableUUID(v interface{}) interface{} {
+ switch x := v.(type) {
+
+ // 1️⃣ models.NullUUID → direkt geri dön
+ case models.NullUUID:
+ if !x.Valid {
+ return nil
+ }
+ return x.UUID
+
+ // 2️⃣ models.NullString → GUID parse et
+ case models.NullString:
+ if !x.Valid || strings.TrimSpace(x.String) == "" {
+ return nil
+ }
+ id, err := uuid.Parse(x.String)
+ if err != nil {
+ return nil
+ }
+ return id
+
+ // 3️⃣ string → GUID parse et
+ case string:
+ id, err := uuid.Parse(x)
+ if err != nil {
+ return nil
+ }
+ return id
+ }
+
+ return nil
+}
+
+// ============================================================
+// 📌 NUMERIC → sql.NullX
+// ============================================================
+func nullableFloat64(val models.NullFloat64, fallback float64) sql.NullFloat64 {
+ if val.Valid {
+ return sql.NullFloat64{Float64: val.Float64, Valid: true}
+ }
+ return sql.NullFloat64{Float64: fallback, Valid: true}
+}
+
+func nullableInt16(val models.NullInt16, fallback int16) sql.NullInt16 {
+ if val.Valid {
+ return sql.NullInt16{Int16: val.Int16, Valid: true}
+ }
+ return sql.NullInt16{Int16: fallback, Valid: true}
+}
+
+func nullableInt32(val models.NullInt32, fallback int32) sql.NullInt32 {
+ if val.Valid {
+ return sql.NullInt32{Int32: val.Int32, Valid: true}
+ }
+ return sql.NullInt32{Int32: fallback, Valid: true}
+}
+
+func nullableInt32ToInt16(val models.NullInt32, fallback int16) sql.NullInt16 {
+ if val.Valid {
+ return sql.NullInt16{Int16: int16(val.Int32), Valid: true}
+ }
+ return sql.NullInt16{Int16: fallback, Valid: true}
+}
+
+// ============================================================
+// 📌 BOOL → sql.NullBool
+// ============================================================
+func nullableBool(val models.NullBool, fallback bool) sql.NullBool {
+ if val.Valid {
+ return sql.NullBool{Bool: val.Bool, Valid: true}
+ }
+ return sql.NullBool{Bool: fallback, Valid: true}
+}
+
+var logger = log.Default()
diff --git a/svc/queries/inventoryproduct.go b/svc/queries/inventoryproduct.go
new file mode 100644
index 0000000..ac88ec5
--- /dev/null
+++ b/svc/queries/inventoryproduct.go
@@ -0,0 +1,93 @@
+package queries
+
+// 🔹 Ürüne göre stok detay sorgusu
+const GetInventoryProduct = `
+-- 🔹 Ürüne göre stok detay sorgusu (Nebim V3 uyumlu, parametre güvenli)
+SELECT
+ bsItemTypeDesc.ItemTypeDescription AS InventoryType,
+ inv.WarehouseCode AS Depo_Kodu,
+ wh.WarehouseDescription AS Depo_Adi,
+ inv.ItemCode AS Urun_Kodu,
+ ISNULL(descItem.ItemDescription, '') AS Madde_Aciklamasi,
+ inv.ColorCode AS Renk_Kodu,
+ ISNULL(descColor.ColorDescription, '') AS Renk_Aciklamasi,
+ inv.ItemDim1Code AS Beden,
+ attr.ProductAtt01 AS Urun_Grubu,
+ attr.ProductAtt02 AS Urun_Alt_Grubu,
+ attr.ProductAtt41 AS Kisa_Karisim,
+ attr.ProductAtt42 AS SERI,
+ attr.ProductAtt43 AS FASON_ISCLIK,
+ attr.ProductAtt45 AS ASKILI_YAN,
+ inv.ItemDim2Code AS YAKA,
+ attr.ProductAtt44 AS GARSON_YETISKIN,
+ attr.ProductAtt10 AS MarkaKodu,
+ ROUND(
+ SUM(inv.InventoryQty1)
+ - (SUM(inv.ReserveQty1) + SUM(inv.DispOrderQty1) + SUM(inv.PickingQty1)),
+ cdUnitOfMeasure.RoundDigit
+ ) AS Kullanilabilir_Envanter
+FROM cdItem WITH (NOLOCK)
+JOIN cdUnitOfMeasure WITH (NOLOCK)
+ ON cdItem.UnitOfMeasureCode1 = cdUnitOfMeasure.UnitOfMeasureCode
+JOIN (
+ SELECT
+ CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
+ ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
+ SUM(CASE WHEN SourceTable = 'PickingStates' THEN Qty1 ELSE 0 END) AS PickingQty1,
+ SUM(CASE WHEN SourceTable = 'ReserveStates' THEN Qty1 ELSE 0 END) AS ReserveQty1,
+ SUM(CASE WHEN SourceTable = 'DispOrderStates' THEN Qty1 ELSE 0 END) AS DispOrderQty1,
+ SUM(CASE WHEN SourceTable = 'trStock' THEN (In_Qty1 - Out_Qty1) ELSE 0 END) AS InventoryQty1
+ FROM (
+ SELECT 'PickingStates' AS SourceTable, CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
+ ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, Qty1, 0 AS In_Qty1, 0 AS Out_Qty1
+ FROM PickingStates WITH (NOLOCK)
+ UNION ALL
+ SELECT 'ReserveStates', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
+ ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, Qty1, 0, 0
+ FROM ReserveStates WITH (NOLOCK)
+ UNION ALL
+ SELECT 'DispOrderStates', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
+ ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, Qty1, 0, 0
+ FROM DispOrderStates WITH (NOLOCK)
+ UNION ALL
+ SELECT 'trStock', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
+ ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, 0, SUM(In_Qty1), SUM(Out_Qty1)
+ FROM trStock WITH (NOLOCK)
+ GROUP BY CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
+ ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code
+ ) AS src
+ GROUP BY CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
+ ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code
+) AS inv
+ ON cdItem.ItemTypeCode = inv.ItemTypeCode
+ AND cdItem.ItemCode = inv.ItemCode
+LEFT JOIN ProductAttributesFilter AS attr WITH (NOLOCK)
+ ON attr.ItemCode = inv.ItemCode
+LEFT JOIN bsItemTypeDesc WITH (NOLOCK)
+ ON bsItemTypeDesc.ItemTypeCode = inv.ItemTypeCode
+ AND bsItemTypeDesc.LangCode = 'TR'
+LEFT JOIN cdWarehouseDesc AS wh WITH (NOLOCK)
+ ON wh.WarehouseCode = inv.WarehouseCode
+LEFT JOIN cdItemDesc AS descItem WITH (NOLOCK)
+ ON descItem.ItemTypeCode = inv.ItemTypeCode
+ AND descItem.ItemCode = inv.ItemCode
+ AND descItem.LangCode = 'TR'
+LEFT JOIN cdColorDesc AS descColor WITH (NOLOCK)
+ ON descColor.ColorCode = inv.ColorCode
+ AND descColor.LangCode = 'TR'
+WHERE
+ inv.ItemTypeCode IN (1)
+ AND inv.WarehouseCode IN ('1-0-12','1-0-21','1-0-10','1-0-2','1-1-3','1-2-4','1-2-5','')
+ AND inv.InventoryQty1 >= 0
+ AND cdItem.IsBlocked = 0
+ AND inv.ItemCode = @p1 -- ✅ doğrudan parametre, DECLARE yok
+GROUP BY
+ inv.CompanyCode, inv.OfficeCode, inv.StoreTypeCode, inv.StoreCode,
+ inv.WarehouseCode, inv.ItemTypeCode, inv.ItemCode, inv.ColorCode,
+ inv.ItemDim1Code, inv.ItemDim2Code, inv.ItemDim3Code,
+ cdUnitOfMeasure.RoundDigit, attr.ProductAtt01, attr.ProductAtt02,
+ attr.ProductAtt41, attr.ProductAtt42, attr.ProductAtt43, attr.ProductAtt44,
+ attr.ProductAtt45, attr.ProductAtt10, bsItemTypeDesc.ItemTypeDescription,
+ wh.WarehouseDescription, descItem.ItemDescription, descColor.ColorDescription;
+
+`
diff --git a/svc/queries/order_get.go b/svc/queries/order_get.go
new file mode 100644
index 0000000..b7fb55a
--- /dev/null
+++ b/svc/queries/order_get.go
@@ -0,0 +1,280 @@
+package queries
+
+import (
+ "bssapp-backend/db"
+ "bssapp-backend/models"
+ "database/sql"
+ "errors"
+ "fmt"
+)
+
+// GetOrderByID — Sipariş başlığı (header) ve satırlarını (lines) getirir.
+func GetOrderByID(orderID string) (*models.OrderHeader, []models.OrderDetail, error) {
+ conn := db.GetDB()
+
+ logger.Printf("🧾 [GetOrderByID] begin • id=%s", orderID)
+
+ // =====================================================
+ // HEADER (Cari adı join'li)
+ // =====================================================
+ var header models.OrderHeader
+ qHeader := `
+ SELECT
+ CAST(h.OrderHeaderID AS varchar(36)) AS OrderHeaderID,
+ h.OrderTypeCode,
+ h.ProcessCode,
+ h.OrderNumber,
+ h.IsCancelOrder,
+ h.OrderDate,
+ h.OrderTime,
+ h.DocumentNumber,
+ h.PaymentTerm,
+ h.AverageDueDate,
+ h.Description,
+ h.InternalDescription,
+ h.CurrAccTypeCode,
+ h.CurrAccCode,
+ d.CurrAccDescription,
+ h.SubCurrAccID,
+ h.ContactID,
+ h.ShipmentMethodCode,
+ h.ShippingPostalAddressID,
+ h.BillingPostalAddressID,
+ h.GuarantorContactID,
+ h.GuarantorContactID2,
+ h.RoundsmanCode,
+ h.DeliveryCompanyCode,
+ h.TaxTypeCode,
+ h.WithHoldingTaxTypeCode,
+ h.DOVCode,
+ h.TaxExemptionCode,
+ h.CompanyCode,
+ h.OfficeCode,
+ h.StoreTypeCode,
+ h.StoreCode,
+ h.POSTerminalID,
+ h.WarehouseCode,
+ h.ToWarehouseCode,
+ h.OrdererCompanyCode,
+ h.OrdererOfficeCode,
+ h.OrdererStoreCode,
+ h.GLTypeCode,
+ h.DocCurrencyCode,
+ h.LocalCurrencyCode,
+ h.ExchangeRate,
+ h.TDisRate1,
+ h.TDisRate2,
+ h.TDisRate3,
+ h.TDisRate4,
+ h.TDisRate5,
+ h.DiscountReasonCode,
+ h.SurplusOrderQtyToleranceRate,
+ h.ImportFileNumber,
+ h.ExportFileNumber,
+ h.IncotermCode1,
+ h.IncotermCode2,
+ h.LettersOfCreditNumber,
+ h.PaymentMethodCode,
+ h.IsInclutedVat,
+ h.IsCreditSale,
+ h.IsCreditableConfirmed,
+ h.CreditableConfirmedUser,
+ h.CreditableConfirmedDate,
+ h.IsSalesViaInternet,
+ h.IsSuspended,
+ h.IsCompleted,
+ h.IsPrinted,
+ h.IsLocked,
+ h.UserLocked,
+ h.IsClosed,
+ h.ApplicationCode,
+ h.ApplicationID,
+ h.CreatedUserName,
+ h.CreatedDate,
+ h.LastUpdatedUserName,
+ h.LastUpdatedDate,
+ h.IsProposalBased
+ FROM BAGGI_V3.dbo.trOrderHeader AS h
+ LEFT JOIN BAGGI_V3.dbo.cdCurrAccDesc AS d
+ ON h.CurrAccCode = d.CurrAccCode
+ WHERE h.OrderHeaderID = @p1;
+ `
+
+ err := conn.QueryRow(qHeader, orderID).Scan(
+ &header.OrderHeaderID,
+ &header.OrderTypeCode,
+ &header.ProcessCode,
+ &header.OrderNumber,
+ &header.IsCancelOrder,
+ &header.OrderDate,
+ &header.OrderTime,
+ &header.DocumentNumber,
+ &header.PaymentTerm,
+ &header.AverageDueDate,
+ &header.Description,
+ &header.InternalDescription,
+ &header.CurrAccTypeCode,
+ &header.CurrAccCode,
+ &header.CurrAccDescription,
+ &header.SubCurrAccID,
+ &header.ContactID,
+ &header.ShipmentMethodCode,
+ &header.ShippingPostalAddressID,
+ &header.BillingPostalAddressID,
+ &header.GuarantorContactID,
+ &header.GuarantorContactID2,
+ &header.RoundsmanCode,
+ &header.DeliveryCompanyCode,
+ &header.TaxTypeCode,
+ &header.WithHoldingTaxTypeCode,
+ &header.DOVCode,
+ &header.TaxExemptionCode,
+ &header.CompanyCode,
+ &header.OfficeCode,
+ &header.StoreTypeCode,
+ &header.StoreCode,
+ &header.POSTerminalID,
+ &header.WarehouseCode,
+ &header.ToWarehouseCode,
+ &header.OrdererCompanyCode,
+ &header.OrdererOfficeCode,
+ &header.OrdererStoreCode,
+ &header.GLTypeCode,
+ &header.DocCurrencyCode,
+ &header.LocalCurrencyCode,
+ &header.ExchangeRate,
+ &header.TDisRate1,
+ &header.TDisRate2,
+ &header.TDisRate3,
+ &header.TDisRate4,
+ &header.TDisRate5,
+ &header.DiscountReasonCode,
+ &header.SurplusOrderQtyToleranceRate,
+ &header.ImportFileNumber,
+ &header.ExportFileNumber,
+ &header.IncotermCode1,
+ &header.IncotermCode2,
+ &header.LettersOfCreditNumber,
+ &header.PaymentMethodCode,
+ &header.IsInclutedVat,
+ &header.IsCreditSale,
+ &header.IsCreditableConfirmed,
+ &header.CreditableConfirmedUser,
+ &header.CreditableConfirmedDate,
+ &header.IsSalesViaInternet,
+ &header.IsSuspended,
+ &header.IsCompleted,
+ &header.IsPrinted,
+ &header.IsLocked,
+ &header.UserLocked,
+ &header.IsClosed,
+ &header.ApplicationCode,
+ &header.ApplicationID,
+ &header.CreatedUserName,
+ &header.CreatedDate,
+ &header.LastUpdatedUserName,
+ &header.LastUpdatedDate,
+ &header.IsProposalBased,
+ )
+
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ logger.Printf("⚠️ [GetOrderByID] sipariş bulunamadı: %s", orderID)
+ return nil, nil, sql.ErrNoRows
+ }
+ logger.Printf("❌ [GetOrderByID] header sorgu hatası: %v", err)
+ return nil, nil, err
+ }
+
+ logger.Printf("✅ [GetOrderByID] header loaded • orderNo=%v currAcc=%v",
+ header.OrderNumber, header.CurrAccCode.String)
+
+ // =====================================================
+ // LINES
+ // =====================================================
+ qLines := `
+ SELECT
+ CAST(L.OrderLineID AS varchar(36)) AS OrderLineID,
+ L.SortOrder,
+ L.ItemTypeCode,
+ L.ItemCode,
+ L.ColorCode,
+ L.ItemDim1Code,
+ L.ItemDim2Code,
+ L.ItemDim3Code,
+ L.Qty1,
+ L.Qty2,
+ L.Price,
+ L.VatRate,
+ L.PCTRate,
+ L.DocCurrencyCode,
+ L.DeliveryDate,
+ L.PlannedDateOfLading,
+ L.LineDescription,
+ L.IsClosed,
+ L.CreatedUserName,
+ L.CreatedDate,
+ L.LastUpdatedUserName,
+ L.LastUpdatedDate,
+ P.ProductAtt42Desc AS UrunIlkGrubu,
+ P.ProductAtt01Desc AS UrunAnaGrubu,
+ P.ProductAtt02Desc AS UrunAltGrubu,
+ P.ProductAtt38Desc AS Fit1,
+ P.ProductAtt39Desc AS Fit2
+ FROM BAGGI_V3.dbo.trOrderLine AS L
+ LEFT JOIN ProductFilterWithDescription('TR') AS P
+ ON LTRIM(RTRIM(P.ProductCode)) = LTRIM(RTRIM(L.ItemCode))
+ WHERE L.OrderHeaderID = @p1
+ ORDER BY L.SortOrder ASC;
+ `
+
+ rows, err := conn.Query(qLines, orderID)
+ if err != nil {
+ logger.Printf("❌ [GetOrderByID] line sorgu hatası: %v", err)
+ return &header, nil, err
+ }
+ defer rows.Close()
+
+ lines := make([]models.OrderDetail, 0, 32)
+ for rows.Next() {
+ var ln models.OrderDetail
+ if err := rows.Scan(
+ &ln.OrderLineID,
+ &ln.SortOrder,
+ &ln.ItemTypeCode,
+ &ln.ItemCode,
+ &ln.ColorCode,
+ &ln.ItemDim1Code,
+ &ln.ItemDim2Code,
+ &ln.ItemDim3Code,
+ &ln.Qty1,
+ &ln.Qty2,
+ &ln.Price,
+ &ln.VatRate,
+ &ln.PCTRate,
+ &ln.DocCurrencyCode,
+ &ln.DeliveryDate,
+ &ln.PlannedDateOfLading,
+ &ln.LineDescription,
+ &ln.IsClosed,
+ &ln.CreatedUserName,
+ &ln.CreatedDate,
+ &ln.LastUpdatedUserName,
+ &ln.LastUpdatedDate,
+ &ln.UrunIlkGrubu,
+ &ln.UrunAnaGrubu,
+ &ln.UrunAltGrubu,
+ &ln.Fit1,
+ &ln.Fit2,
+ ); err != nil {
+ return &header, nil, fmt.Errorf("line scan hatası: %w", err)
+ }
+ lines = append(lines, ln)
+ }
+ if err := rows.Err(); err != nil {
+ return &header, nil, fmt.Errorf("line rows hatası: %w", err)
+ }
+
+ logger.Printf("📦 [GetOrderByID] lines loaded • count=%d", len(lines))
+ return &header, lines, nil
+}
diff --git a/svc/queries/order_pdf.go b/svc/queries/order_pdf.go
new file mode 100644
index 0000000..6b200c9
--- /dev/null
+++ b/svc/queries/order_pdf.go
@@ -0,0 +1,161 @@
+package queries
+
+import (
+ "context"
+ "database/sql"
+)
+
+/*
+============================================================
+
+ HEADER GETIRME — OrderHeader struct’ına uygun
+ ============================================================
+*/
+type OrderHeaderDB struct {
+ OrderHeaderID string
+ OrderNumber string
+ CurrAccCode string
+ CurrAccName string
+ DocCurrency string
+ OrderDate sql.NullTime
+ Description sql.NullString
+ InternalDesc sql.NullString
+ OfficeCode sql.NullString
+ CreatedUser sql.NullString
+}
+
+func GetOrderHeaderDB(ctx context.Context, db *sql.DB, id string) (*OrderHeaderDB, error) {
+ q := `
+SELECT
+ CAST(h.OrderHeaderID AS varchar(36)),
+ h.OrderNumber,
+ h.CurrAccCode,
+ d.CurrAccDescription,
+ h.DocCurrencyCode,
+ h.OrderDate,
+ h.Description,
+ h.InternalDescription,
+ h.OfficeCode,
+ h.CreatedUserName
+FROM BAGGI_V3.dbo.trOrderHeader AS h
+LEFT JOIN BAGGI_V3.dbo.cdCurrAccDesc AS d
+ ON h.CurrAccCode = d.CurrAccCode
+WHERE h.OrderHeaderID = @p1
+`
+ row := db.QueryRowContext(ctx, q, id)
+
+ var h OrderHeaderDB
+ err := row.Scan(
+ &h.OrderHeaderID,
+ &h.OrderNumber,
+ &h.CurrAccCode,
+ &h.CurrAccName,
+ &h.DocCurrency,
+ &h.OrderDate,
+ &h.Description,
+ &h.InternalDesc,
+ &h.OfficeCode,
+ &h.CreatedUser,
+ )
+ if err != nil {
+ return nil, err
+ }
+ return &h, nil
+}
+
+/*
+============================================================
+
+ SATIRLARI GETIRME — OrderLineRaw struct’ına uygun
+ ============================================================
+*/
+type OrderLineRawDB struct {
+ OrderLineID sql.NullString
+ ItemCode string
+ ColorCode string
+ ItemDim1Code sql.NullString
+ ItemDim2Code sql.NullString
+ Qty1 sql.NullFloat64
+ Price sql.NullFloat64
+ DocCurrencyCode sql.NullString
+ DeliveryDate sql.NullTime
+ LineDescription sql.NullString
+ UrunAnaGrubu sql.NullString
+ UrunAltGrubu sql.NullString
+ IsClosed sql.NullBool
+ WithHoldingTaxType sql.NullString
+ DOVCode sql.NullString
+ PlannedDateOfLading sql.NullTime
+ CostCenterCode sql.NullString
+ VatCode sql.NullString
+ VatRate sql.NullFloat64
+}
+
+func GetOrderLinesDB(ctx context.Context, db *sql.DB, id string) ([]OrderLineRawDB, error) {
+
+ q := `
+SELECT
+ CAST(L.OrderLineID AS varchar(36)),
+ L.ItemCode,
+ L.ColorCode,
+ L.ItemDim1Code,
+ L.ItemDim2Code,
+ L.Qty1,
+ L.Price,
+ L.DocCurrencyCode,
+ L.DeliveryDate,
+ L.LineDescription,
+ P.ProductAtt01Desc,
+ P.ProductAtt02Desc,
+ L.IsClosed,
+ L.WithHoldingTaxTypeCode,
+ L.DOVCode,
+ L.PlannedDateOfLading,
+ L.CostCenterCode,
+ L.VatCode,
+ L.VatRate
+FROM BAGGI_V3.dbo.trOrderLine AS L
+LEFT JOIN ProductFilterWithDescription('TR') AS P
+ ON LTRIM(RTRIM(P.ProductCode)) = LTRIM(RTRIM(L.ItemCode))
+WHERE L.OrderHeaderID = @p1
+ORDER BY L.SortOrder, L.OrderLineID
+`
+
+ rows, err := db.QueryContext(ctx, q, id)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ var out []OrderLineRawDB
+
+ for rows.Next() {
+ var r OrderLineRawDB
+ err = rows.Scan(
+ &r.OrderLineID,
+ &r.ItemCode,
+ &r.ColorCode,
+ &r.ItemDim1Code,
+ &r.ItemDim2Code,
+ &r.Qty1,
+ &r.Price,
+ &r.DocCurrencyCode,
+ &r.DeliveryDate,
+ &r.LineDescription,
+ &r.UrunAnaGrubu,
+ &r.UrunAltGrubu,
+ &r.IsClosed,
+ &r.WithHoldingTaxType,
+ &r.DOVCode,
+ &r.PlannedDateOfLading,
+ &r.CostCenterCode,
+ &r.VatCode,
+ &r.VatRate,
+ )
+ if err != nil {
+ return nil, err
+ }
+ out = append(out, r)
+ }
+ return out, nil
+}
diff --git a/svc/queries/order_write.go b/svc/queries/order_write.go
new file mode 100644
index 0000000..4e3f41d
--- /dev/null
+++ b/svc/queries/order_write.go
@@ -0,0 +1,1079 @@
+// ===================== PART 1 (Satır 1-300) =====================
+// =======================================================
+// order_write.go — v4.3 FINAL (Insert + Update + Delete)
+// - PCTCode her zaman "%0" olarak yazılır (cdPCT FK uyumlu)
+// - INSERT/UPDATE öncesi ItemVariant Guard + Duplicate Guard (payload içi)
+// - UpdateOrder: DELETE öncesi child tablolar (trOrderLineCurrency) temizlenir
+// =======================================================
+
+package queries
+
+import (
+ "bssapp-backend/db"
+ "bssapp-backend/models"
+ "database/sql"
+ "fmt"
+ "github.com/google/uuid"
+ "strings"
+ "time"
+)
+
+func nf0(v models.NullFloat64) float64 {
+ if !v.Valid {
+ return 0
+ }
+ return v.Float64
+}
+
+// =======================================================
+// COMBO KEY & STRING HELPERS
+// =======================================================
+func normalizeComboKey(s string) string {
+ return strings.ToUpper(strings.TrimSpace(s))
+}
+
+// makeComboKey: frontend tarafında NullString olmayan struct’lar için
+func makeComboKey(ln models.OrderDetail) string {
+ model := safeNS(ln.ItemCode)
+ renk := safeNS(ln.ColorCode)
+ renk2 := safeNS(ln.ItemDim2Code)
+ beden := safeNS(ln.ItemDim1Code)
+
+ return normalizeComboKey(
+ fmt.Sprintf("%s||%s||%s||%s", model, renk, beden, renk2),
+ )
+}
+
+// qtyValue → NullFloat64 güvenli float64
+func qtyValue(q models.NullFloat64) float64 {
+ if !q.Valid {
+ return 0
+ }
+ return q.Float64
+}
+
+// VatCode: NullString → string
+// - NULL → ""
+// - "0" → "" (FK patlamasın, sadece anlamlı kodlar gönderiyoruz)
+// - "%0", "%10" vb → trimlenmiş hali
+func normalizeVatCode(ns models.NullString) string {
+ if !ns.Valid {
+ return ""
+ }
+ s := strings.TrimSpace(ns.String)
+ if s == "0" {
+ return ""
+ }
+ return s
+}
+
+// PCTCode: NullString → string
+// - Artık her durumda "%0" döndürür (tek PCT tipi kullanımı)
+// NOT: SQL tarafında da "%0" cdPCT içinde tanımlı olmalı
+func normalizePCTCode(ns models.NullString) string {
+ return "%0"
+}
+
+// models.NullString → trimlenmiş string (NULL ise "")
+func safeNS(ns models.NullString) string {
+ if !ns.Valid {
+ return ""
+ }
+ return strings.TrimSpace(ns.String)
+}
+
+// =======================================================
+// COMBO KEY HELPERS (DB alanlarından)
+// =======================================================
+
+// makeComboKeyParts: düz string alanlardan comboKey üret
+// comboKey = model || renk || beden || renk2
+func makeComboKeyParts(item, color, dim1, dim2 string) string {
+ item = strings.TrimSpace(item)
+ color = strings.TrimSpace(color)
+ dim1 = strings.TrimSpace(dim1)
+ dim2 = strings.TrimSpace(dim2)
+
+ if item == "" && color == "" && dim1 == "" && dim2 == "" {
+ return ""
+ }
+
+ return normalizeComboKey(item + "||" + color + "||" + dim1 + "||" + dim2)
+}
+
+// comboFromNulls: NullString alanlardan comboKey üret
+func comboFromNulls(item, color, dim1, dim2 models.NullString) string {
+ return makeComboKeyParts(
+ safeNS(item),
+ safeNS(color),
+ safeNS(dim1),
+ safeNS(dim2),
+ )
+}
+
+// =======================================================
+// ✅ ItemVariant Guard — INSERT / UPDATE öncesi
+// =======================================================
+
+// normalizeKeyPart: NullString → trim + UPPER
+func normalizeKeyPart(ns models.NullString) string {
+ s := strings.TrimSpace(safeNS(ns))
+ return strings.ToUpper(s)
+}
+
+// =======================================================
+// AKSBIR DETECTION
+// =======================================================
+
+// =======================================================
+// COMBO KEY BUILDER (AKSBIR AWARE)
+// =======================================================
+
+// Variant check: ItemCode + ColorCode + Dim1 + Dim2
+func ValidateItemVariant(tx *sql.Tx, ln models.OrderDetail) error {
+ fmt.Printf(
+ "🧪 VARIANT GUARD INPUT | ClientKey=%s Item=%q Color=%q Dim1=%q Dim2=%q Dim3=%q Qty1=%v\n",
+ safeNS(ln.ClientKey),
+ safeNS(ln.ItemCode),
+ safeNS(ln.ColorCode),
+ safeNS(ln.ItemDim1Code),
+ safeNS(ln.ItemDim2Code),
+ safeNS(ln.ItemDim3Code),
+ nf0(ln.Qty1),
+ )
+
+ item := normalizeKeyPart(ln.ItemCode)
+ color := normalizeKeyPart(ln.ColorCode)
+ dim1 := normalizeKeyPart(ln.ItemDim1Code)
+ dim2 := normalizeKeyPart(ln.ItemDim2Code)
+
+ // ✅ Placeholder/boş standardizasyon (SENDE "_" geliyor)
+ normalizeEmpty := func(s string) string {
+ s = strings.TrimSpace(strings.ToUpper(s))
+ if s == "_" || s == "-" {
+ return ""
+ }
+ return s
+ }
+
+ item = normalizeEmpty(item)
+ color = normalizeEmpty(color)
+ dim1 = normalizeEmpty(dim1)
+ dim2 = normalizeEmpty(dim2)
+
+ if item == "" {
+ return fmt.Errorf(
+ "ItemCode boş olamaz (ClientKey=%s)",
+ safeNS(ln.ClientKey),
+ )
+ fmt.Printf(
+ "🧪 VARIANT NORMALIZED | Item=%q Color=%q Dim1=%q Dim2=%q\n",
+ item, color, dim1, dim2,
+ )
+
+ }
+
+ // İstersen debug:
+ // fmt.Printf("🧪 VARIANT CHECK item=%q color=%q dim1=%q dim2=%q clientKey=%s\n", item, color, dim1, dim2, safeNS(ln.ClientKey))
+
+ var exists int
+ err := tx.QueryRow(`
+ SELECT CASE WHEN EXISTS (
+ SELECT 1
+ FROM BAGGI_V3.dbo.prItemVariant V WITH (NOLOCK)
+ WHERE ISNULL(LTRIM(RTRIM(V.ItemCode)),'') = @p1
+ AND ISNULL(LTRIM(RTRIM(V.ColorCode)),'') = @p2
+ AND ISNULL(LTRIM(RTRIM(V.ItemDim1Code)),'') = @p3
+ AND ISNULL(LTRIM(RTRIM(V.ItemDim2Code)),'') = @p4
+ ) THEN 1 ELSE 0 END
+ `, item, color, dim1, dim2).Scan(&exists)
+
+ if err != nil {
+ return fmt.Errorf("ItemVariant kontrol query hatası: %w", err)
+ }
+
+ if exists != 1 {
+ return &models.ValidationError{
+ Code: "INVALID_ITEM_VARIANT",
+ Message: "Tanımsız ürün kombinasyonu",
+ ClientKey: safeNS(ln.ClientKey),
+ ItemCode: item,
+ ColorCode: color,
+ Dim1: dim1,
+ Dim2: dim2,
+ }
+ }
+
+ return nil
+}
+
+// ValidateOrderVariants: save/update öncesi payload satırlarını prItemVariant'a göre doğrular.
+// invalid döner; error sadece DB/prepare/query hatalarında döner.
+func ValidateOrderVariants(db *sql.DB, lines []models.OrderDetail) ([]models.InvalidVariant, error) {
+
+ normalizeEmpty := func(s string) string {
+ s = strings.TrimSpace(strings.ToUpper(s))
+ if s == "_" || s == "-" {
+ return ""
+ }
+ return s
+ }
+
+ stmt, err := db.Prepare(`
+ SELECT CASE WHEN EXISTS (
+ SELECT 1
+ FROM BAGGI_V3.dbo.prItemVariant V WITH (NOLOCK)
+ WHERE ISNULL(LTRIM(RTRIM(V.ItemCode)),'') = @p1
+ AND ISNULL(LTRIM(RTRIM(V.ColorCode)),'') = @p2
+ AND ISNULL(LTRIM(RTRIM(V.ItemDim1Code)),'') = @p3
+ AND ISNULL(LTRIM(RTRIM(V.ItemDim2Code)),'') = @p4
+ ) THEN 1 ELSE 0 END
+ `)
+ if err != nil {
+ return nil, fmt.Errorf("validate prepare hatası: %w", err)
+ }
+ defer stmt.Close()
+
+ invalid := make([]models.InvalidVariant, 0)
+
+ for i, ln := range lines {
+ qty := qtyValue(ln.Qty1)
+ if qty <= 0 {
+ continue
+ }
+
+ item := normalizeEmpty(normalizeKeyPart(ln.ItemCode))
+ color := normalizeEmpty(normalizeKeyPart(ln.ColorCode))
+ dim1 := normalizeEmpty(normalizeKeyPart(ln.ItemDim1Code))
+ dim2 := normalizeEmpty(normalizeKeyPart(ln.ItemDim2Code))
+
+ // ItemCode boş ise invalid
+ if strings.TrimSpace(item) == "" {
+ invalid = append(invalid, models.InvalidVariant{
+ Index: i,
+ ClientKey: safeNS(ln.ClientKey),
+ ItemCode: item,
+ ColorCode: color,
+ Dim1: dim1,
+ Dim2: dim2,
+ Qty1: qty,
+ ComboKey: safeNS(ln.ComboKey),
+ Reason: "ItemCode boş",
+ })
+ continue
+ }
+
+ var exists int
+ if err := stmt.QueryRow(item, color, dim1, dim2).Scan(&exists); err != nil {
+ return nil, fmt.Errorf("validate query hatası (i=%d): %w", i, err)
+ }
+
+ if exists != 1 {
+ invalid = append(invalid, models.InvalidVariant{
+ Index: i,
+ ClientKey: safeNS(ln.ClientKey),
+ ItemCode: item,
+ ColorCode: color,
+ Dim1: dim1,
+ Dim2: dim2,
+ Qty1: qty,
+ ComboKey: safeNS(ln.ComboKey),
+ Reason: "prItemVariant’ta yok",
+ })
+ }
+ }
+
+ return invalid, nil
+}
+
+// =======================================================
+// LineResult → frontend senkronu için
+// =======================================================
+
+type OrderLineResult struct {
+ ClientKey string `json:"clientKey"`
+ OrderLineID string `json:"orderLineID"`
+}
+
+// =======================================================
+// PART 1 — InsertOrder (header + lines insert) — FINAL v5.1
+// ✔ OrderHeaderID backend üretir
+// ✔ LOCAL-... numara gelirse gerçek WS numarası üretir
+// ✔ Full debug
+// ✔ Tüm satırlar INSERT edilir
+// ✔ INSERT öncesi ItemVariant Guard
+// ✔ Payload içi Duplicate Guard (comboKey)
+// =======================================================
+
+func InsertOrder(header models.OrderHeader, lines []models.OrderDetail, user *models.User) (string, []OrderLineResult, error) {
+ conn := db.GetDB()
+
+ fmt.Println("🟦 InsertOrder() BAŞLADI -----------------------------")
+
+ tx, err := conn.Begin()
+ if err != nil {
+ return "", nil, fmt.Errorf("tx baslatilamadi: %w", err)
+ }
+ defer tx.Rollback()
+
+ now := time.Now()
+ v3User := fmt.Sprintf("V3U%d-%s", user.V3UserGroup, user.V3Username)
+
+ // =======================================================
+ // 1) BACKEND — OrderHeaderID üretimi (HER ZAMAN)
+ // =======================================================
+ realHeaderID := uuid.New().String()
+
+ fmt.Println("🟩 Backend yeni OrderHeaderID üretti:", realHeaderID)
+
+ header.OrderHeaderID = realHeaderID
+
+ // =======================================================
+ // 2) OrderNumber üretimi (LOCAL-* ise gerçek WS numarası)
+ // =======================================================
+ if !header.OrderNumber.Valid ||
+ strings.HasPrefix(header.OrderNumber.String, "LOCAL-") ||
+ len(strings.TrimSpace(header.OrderNumber.String)) == 0 {
+
+ fmt.Println("🟨 LOCAL numara geldi → gerçek WS numarası üretilecek...")
+
+ var realNumber string
+ err := tx.QueryRow(`
+ SELECT CONCAT(
+ '1-WS-3-',
+ RIGHT('00000' + CAST(NEXT VALUE FOR BAGGI_V3.dbo.Seq_OrderNumber_WS AS VARCHAR(10)), 5)
+ )
+ `).Scan(&realNumber)
+
+ if err != nil {
+ return "", nil, fmt.Errorf("Gerçek sipariş numarası üretilemedi: %w", err)
+ }
+
+ fmt.Println("🟩 Üretilen gerçek WS numarası:", realNumber)
+
+ header.OrderNumber.String = realNumber
+ header.OrderNumber.Valid = true
+ }
+
+ newID := realHeaderID // artık DB’ye bu yazılacak
+
+ // =======================================================
+ // 3) Döviz kuru çözümü
+ // =======================================================
+ exRate := 1.0
+ if header.DocCurrencyCode.Valid && header.DocCurrencyCode.String != "TRY" {
+ if c, err := GetTodayCurrencyV3(conn, header.DocCurrencyCode.String); err == nil {
+ if c.Rate > 0 {
+ exRate = c.Rate
+ }
+ }
+ }
+
+ // =======================================================
+ // 4) HEADER INSERT
+ // =======================================================
+
+ queryHeader := `
+INSERT INTO BAGGI_V3.dbo.trOrderHeader (
+ OrderHeaderID, OrderTypeCode, ProcessCode, OrderNumber, IsCancelOrder,
+ OrderDate, OrderTime, DocumentNumber, PaymentTerm,
+ AverageDueDate, Description, InternalDescription,
+ CurrAccTypeCode, CurrAccCode, SubCurrAccID, ContactID,
+ ShipmentMethodCode, ShippingPostalAddressID, BillingPostalAddressID,
+ GuarantorContactID, GuarantorContactID2, RoundsmanCode,
+ DeliveryCompanyCode, TaxTypeCode, WithHoldingTaxTypeCode, DOVCode,
+ TaxExemptionCode, CompanyCode, OfficeCode, StoreTypeCode, StoreCode,
+ POSTerminalID, WarehouseCode, ToWarehouseCode,
+ OrdererCompanyCode, OrdererOfficeCode, OrdererStoreCode,
+ GLTypeCode, DocCurrencyCode, LocalCurrencyCode, ExchangeRate,
+ TDisRate1, TDisRate2, TDisRate3, TDisRate4, TDisRate5,
+ DiscountReasonCode, SurplusOrderQtyToleranceRate,
+ ImportFileNumber, ExportFileNumber,
+ IncotermCode1, IncotermCode2, LettersOfCreditNumber,
+ PaymentMethodCode, IsInclutedVat, IsCreditSale, IsCreditableConfirmed,
+ CreditableConfirmedUser, CreditableConfirmedDate,
+ IsSalesViaInternet, IsSuspended, IsCompleted, IsPrinted,
+ IsLocked, UserLocked, IsClosed,
+ ApplicationCode, ApplicationID,
+ CreatedUserName, CreatedDate, LastUpdatedUserName, LastUpdatedDate,
+ IsProposalBased
+)
+VALUES (
+ @p1,@p2,@p3,@p4,
+ @p5,@p6,@p7,@p8,
+ @p9,@p10,@p11,
+ @p12,@p13,@p14,@p15,
+ @p16,@p17,@p18,
+ @p19,@p20,@p21,
+ @p22,@p23,@p24,@p25,
+ @p26,@p27,@p28,@p29,@p30,
+ @p31,@p32,@p33,
+ @p34,@p35,@p36,
+ @p37,@p38,@p39,@p40,
+ @p41,@p42,@p43,@p44,@p45,
+ @p46,@p47,
+ @p48,@p49,
+ @p50,@p51,@p52,
+ @p53,@p54,@p55,@p56,@p57,
+ @p58,@p59,@p60,@p61,@p62,
+ @p63,@p64,@p65,
+ @p66,@p67,@p68,@p69,@p70,@p71,@p72,@p73
+);
+`
+
+ fmt.Println("🟪 HEADER INSERT ÇALIŞIYOR...")
+
+ headerParams := []any{
+ header.OrderHeaderID,
+ nullableInt16(header.OrderTypeCode, 1),
+ nullableString(header.ProcessCode, "WS"),
+ nullableString(header.OrderNumber, ""),
+ nullableBool(header.IsCancelOrder, false),
+
+ nullableDateString(header.OrderDate),
+ nullableTimeString(header.OrderTime),
+ nullableString(header.DocumentNumber, ""),
+ nullableInt16(header.PaymentTerm, 0),
+
+ nullableDateString(header.AverageDueDate),
+ nullableString(header.Description, ""),
+ nullableString(header.InternalDescription, ""),
+
+ nullableInt16(header.CurrAccTypeCode, 0),
+ nullableString(header.CurrAccCode, ""),
+ nullableUUID(header.SubCurrAccID),
+ nullableUUID(header.ContactID),
+
+ nullableString(header.ShipmentMethodCode, ""),
+ nullableUUID(header.ShippingPostalAddressID),
+ nullableUUID(header.BillingPostalAddressID),
+
+ nullableUUID(header.GuarantorContactID),
+ nullableUUID(header.GuarantorContactID2),
+ nullableString(header.RoundsmanCode, ""),
+
+ nullableString(header.DeliveryCompanyCode, ""),
+ nullableInt16(header.TaxTypeCode, 0),
+ nullableString(header.WithHoldingTaxTypeCode, ""),
+ nullableString(header.DOVCode, ""),
+
+ nullableInt16(header.TaxExemptionCode, 0),
+ nullableInt32ToInt16(header.CompanyCode, 1),
+ nullableString(header.OfficeCode, "101"),
+ nullableInt16(header.StoreTypeCode, 5),
+ nullableString(header.StoreCode, ""),
+
+ nullableInt16(header.POSTerminalID, 0),
+ nullableString(header.WarehouseCode, "1-0-12"),
+ nullableString(header.ToWarehouseCode, ""),
+
+ nullableInt32ToInt16(header.OrdererCompanyCode, 1),
+ nullableString(header.OrdererOfficeCode, "101"),
+ nullableString(header.OrdererStoreCode, ""),
+
+ nullableString(header.GLTypeCode, ""),
+ nullableString(header.DocCurrencyCode, "TRY"),
+ nullableString(header.LocalCurrencyCode, "TRY"),
+ nullableFloat64(header.ExchangeRate, exRate),
+
+ nullableFloat64(header.TDisRate1, 0),
+ nullableFloat64(header.TDisRate2, 0),
+ nullableFloat64(header.TDisRate3, 0),
+ nullableFloat64(header.TDisRate4, 0),
+ nullableFloat64(header.TDisRate5, 0),
+
+ nullableInt16(header.DiscountReasonCode, 0),
+ nullableFloat64(header.SurplusOrderQtyToleranceRate, 0),
+
+ nullableString(header.ImportFileNumber, ""),
+ nullableString(header.ExportFileNumber, ""),
+
+ nullableString(header.IncotermCode1, ""),
+ nullableString(header.IncotermCode2, ""),
+ nullableString(header.LettersOfCreditNumber, ""),
+
+ nullableString(header.PaymentMethodCode, ""),
+ nullableBool(header.IsInclutedVat, false),
+ nullableBool(header.IsCreditSale, true),
+ nullableBool(header.IsCreditableConfirmed, true),
+
+ nullableString(header.CreditableConfirmedUser, v3User),
+ nullableDateTime(header.CreditableConfirmedDate, now),
+
+ nullableBool(header.IsSalesViaInternet, false),
+ nullableBool(header.IsSuspended, false),
+ nullableBool(header.IsCompleted, false),
+ nullableBool(header.IsPrinted, false),
+
+ nullableBool(header.IsLocked, false),
+ nullableBool(header.UserLocked, false),
+ nullableBool(header.IsClosed, false),
+
+ nullableString(header.ApplicationCode, "Order"),
+ nullableUUID(header.ApplicationID),
+
+ nullableString(header.CreatedUserName, v3User),
+ nullableDateTimeString(header.CreatedDate, now),
+ nullableString(header.LastUpdatedUserName, v3User),
+ nullableDateTimeString(header.LastUpdatedDate, now),
+
+ nullableBool(header.IsProposalBased, false),
+ }
+
+ if _, err := tx.Exec(queryHeader, headerParams...); err != nil {
+ fmt.Println("❌ HEADER INSERT ERROR:", err)
+ return "", nil, fmt.Errorf("header insert hatasi: %w", err)
+ }
+
+ fmt.Println("🟩 HEADER INSERT OK — ID:", newID)
+
+ // =======================================================
+ // 5) LINE INSERT
+ // =======================================================
+
+ insStmt, err := tx.Prepare(`
+INSERT INTO BAGGI_V3.dbo.trOrderLine (
+ OrderLineID,
+ SortOrder, ItemTypeCode, ItemCode, ColorCode,
+ ItemDim1Code, ItemDim2Code, ItemDim3Code,
+ Qty1, Qty2,
+ CancelQty1, CancelQty2,
+ OrderCancelReasonCode,
+ DeliveryDate, PlannedDateOfLading,
+ LineDescription,
+ UsedBarcode, CostCenterCode,
+ VatCode, VatRate, PCTCode, PCTRate,
+ LDisRate1, LDisRate2, LDisRate3, LDisRate4, LDisRate5,
+ DocCurrencyCode, PriceCurrencyCode, PriceExchangeRate,
+ Price,
+ BaseProcessCode, BaseOrderNumber,
+ BaseCustomerTypeCode, BaseCustomerCode,
+ BaseSubCurrAccID, BaseStoreCode,
+ OrderHeaderID,
+ CreatedUserName, CreatedDate,
+ LastUpdatedUserName, LastUpdatedDate,
+ SurplusOrderQtyToleranceRate,
+ WithHoldingTaxTypeCode,
+ DOVCode
+)
+VALUES (
+ @p1,@p2,@p3,@p4,
+ @p5,@p6,@p7,@p8,
+ @p9,@p10,
+ @p11,@p12,
+ @p13,
+ @p14,@p15,
+ @p16,
+ @p17,@p18,
+ @p19,@p20,@p21,@p22,
+ @p23,@p24,@p25,@p26,@p27,
+ @p28,@p29,@p30,
+ @p31,
+ @p32,@p33,
+ @p34,@p35,
+ @p36,@p37,
+ @p38,
+ @p39,@p40,
+ @p41,@p42,
+ @p43,
+ @p44,
+ @p45
+)`)
+ if err != nil {
+ return "", nil, fmt.Errorf("line insert stmt hazirlanamadi: %w", err)
+ }
+ defer insStmt.Close()
+
+ lineResults := make([]OrderLineResult, 0, len(lines))
+
+ // ✅ Duplicate Guard (payload içi)
+ seenCombo := make(map[string]bool)
+
+ for i, ln := range lines {
+ // ===================== PART 2 (Satır 301-600) =====================
+ fmt.Println("────────────────────────────────────")
+ fmt.Printf("🟨 [INSERT] LINE %d — gelen OrderLineID=%s\n", i+1, ln.OrderLineID)
+
+ // Her satır için yeni GUID
+ if ln.OrderLineID == "" {
+ newLineID := uuid.New().String()
+ fmt.Println("🆕 Yeni LineID üretildi:", newLineID)
+ ln.OrderLineID = newLineID
+ }
+
+ // ✅ Duplicate Guard (comboKey)
+ comboKey := normalizeComboKey(safeNS(ln.ComboKey))
+ if comboKey == "" {
+ comboKey = makeComboKey(ln)
+ }
+
+ if qtyValue(ln.Qty1) > 0 && comboKey != "" {
+ if seenCombo[comboKey] {
+ return "", nil, fmt.Errorf(
+ "Duplicate satır (comboKey=%s, ClientKey=%s)",
+ comboKey,
+ safeNS(ln.ClientKey),
+ )
+ }
+ seenCombo[comboKey] = true
+ }
+
+ // V2 Logic → %0 PCT
+ vatCode := normalizeVatCode(ln.VatCode)
+ pctCode := normalizePCTCode(ln.PCTCode)
+
+ var pctParam any
+ if pctCode == "" {
+ pctParam = nil
+ } else {
+ pctParam = pctCode
+ }
+
+ planned := nullableDateString(ln.PlannedDateOfLading)
+ // ✅ INSERT ÖNCESİ ItemVariant GUARD
+ if qtyValue(ln.Qty1) > 0 {
+ if err := ValidateItemVariant(tx, ln); err != nil {
+ fmt.Println("❌ VARIANT GUARD (INSERT):", err)
+ return "", nil, err
+ }
+ }
+ fmt.Printf(
+ "🚨 INSERT LINE[%d] | LineID=%s ClientKey=%s Item=%q Color=%q Dim1=%q Dim2=%q Dim3=%q Qty1=%v\n",
+ i+1,
+ ln.OrderLineID,
+ safeNS(ln.ClientKey),
+ safeNS(ln.ItemCode),
+ safeNS(ln.ColorCode),
+ safeNS(ln.ItemDim1Code),
+ safeNS(ln.ItemDim2Code),
+ safeNS(ln.ItemDim3Code),
+ nf0(ln.Qty1),
+ )
+
+ _, err := insStmt.Exec(
+ ln.OrderLineID,
+ ln.SortOrder,
+ ln.ItemTypeCode,
+ nullableString(ln.ItemCode, ""),
+ safeNS(ln.ColorCode),
+ safeNS(ln.ItemDim1Code),
+ safeNS(ln.ItemDim2Code),
+ nullableString(ln.ItemDim3Code, ""),
+ ln.Qty1, ln.Qty2,
+ ln.CancelQty1, ln.CancelQty2,
+ nullableString(ln.OrderCancelReasonCode, ""),
+ nullableTime(ln.DeliveryDate, now),
+ planned,
+ nullableString(ln.LineDescription, ""),
+ nullableString(ln.UsedBarcode, ""),
+ nullableString(ln.CostCenterCode, ""),
+ vatCode, nf0(ln.VatRate),
+ pctParam, nf0(ln.PCTRate),
+ nf0(ln.LDisRate1),
+ nf0(ln.LDisRate2),
+ nf0(ln.LDisRate3),
+ nf0(ln.LDisRate4),
+ nf0(ln.LDisRate5),
+ nullableString(ln.DocCurrencyCode, "TRY"),
+ nullableString(ln.PriceCurrencyCode, "TRY"),
+ nf0(ln.PriceExchangeRate),
+ nf0(ln.Price),
+ nullableString(ln.BaseProcessCode, ""),
+ nullableString(ln.BaseOrderNumber, ""),
+ ln.BaseCustomerTypeCode,
+ nullableString(ln.BaseCustomerCode, ""),
+ nullableUUID(ln.BaseSubCurrAccID),
+ nullableString(ln.BaseStoreCode, ""),
+ header.OrderHeaderID,
+ v3User, now,
+ v3User, now,
+ nf0(ln.SurplusOrderQtyToleranceRate),
+ nullableString(ln.WithHoldingTaxTypeCode, ""),
+ nullableString(ln.DOVCode, ""),
+ )
+
+ if err != nil {
+ fmt.Println("❌ INSERT LINE ERROR")
+ fmt.Printf(
+ "💥 FAILED LINE | LineID=%s ClientKey=%s Item=%q Color=%q Dim1=%q Dim2=%q Dim3=%q\n",
+ ln.OrderLineID,
+ safeNS(ln.ClientKey),
+ safeNS(ln.ItemCode),
+ safeNS(ln.ColorCode),
+ safeNS(ln.ItemDim1Code),
+ safeNS(ln.ItemDim2Code),
+ safeNS(ln.ItemDim3Code),
+ )
+ fmt.Println("SQL ERROR:", err)
+ return "", nil, fmt.Errorf("line insert hatasi: %w", err)
+ }
+
+ if ln.ClientKey.Valid && ln.ClientKey.String != "" {
+ lineResults = append(lineResults, OrderLineResult{
+ ClientKey: ln.ClientKey.String,
+ OrderLineID: ln.OrderLineID,
+ })
+ }
+ }
+
+ // =======================================================
+ // 6) COMMIT
+ // =======================================================
+ fmt.Println("🟨 COMMIT EDİLİYOR...")
+
+ if err := tx.Commit(); err != nil {
+ fmt.Println("❌ COMMIT ERROR:", err)
+ return "", nil, err
+ }
+
+ fmt.Println("✅ COMMIT OK — INSERT ORDER BİTTİ")
+ fmt.Println("────────────────────────────────────")
+
+ return newID, lineResults, nil
+}
+
+// =======================================================
+// PART 2 — UpdateOrder FULL DEBUG (v4.3)
+// ✔ ComboKey ile açık satır eşleştirme
+// ✔ Kapalı satırları korur
+// ✔ Payload içi Duplicate Guard
+// ✔ INSERT/UPDATE öncesi ItemVariant Guard (tek noktada)
+// ✔ Grid’de olmayan açık satırları siler (önce child)
+// =======================================================
+
+func UpdateOrder(header models.OrderHeader, lines []models.OrderDetail, user *models.User) ([]OrderLineResult, error) {
+ conn := db.GetDB()
+
+ // ======================================================
+ // 🔍 SCAN DEBUG — HEADER bilgisi
+ // ======================================================
+ fmt.Println("══════════════════════════════════════")
+ fmt.Println("🔍 [DEBUG] UpdateOrder çağrıldı")
+ fmt.Printf("🔍 HeaderID: %v\n", header.OrderHeaderID)
+ fmt.Printf("🔍 Line sayısı: %v\n", len(lines))
+ fmt.Printf("🔍 User: %v (V3: %s/%d)\n",
+ user.Username, user.V3Username, user.V3UserGroup)
+ fmt.Println("══════════════════════════════════════")
+
+ tx, err := conn.Begin()
+ if err != nil {
+ return nil, fmt.Errorf("tx baslatilamadi: %w", err)
+ }
+ defer tx.Rollback()
+
+ now := time.Now()
+ v3User := fmt.Sprintf("V3U%d-%s", user.V3UserGroup, user.V3Username)
+
+ // Döviz kuru
+ exRate := 1.0
+ if header.DocCurrencyCode.Valid && header.DocCurrencyCode.String != "TRY" {
+ if c, err := GetTodayCurrencyV3(conn, header.DocCurrencyCode.String); err == nil && c.Rate > 0 {
+ exRate = c.Rate
+ }
+ }
+
+ // =======================================================
+ // 0) Mevcut satırları oku (GUID STRING olarak!)
+ // =======================================================
+
+ existingOpen := make(map[string]bool)
+ existingClosed := make(map[string]bool)
+ existingOpenCombo := make(map[string]string)
+ existingClosedCombo := make(map[string]string)
+
+ rows, err := tx.Query(`
+SELECT
+ CONVERT(varchar(36), OrderLineID),
+ ISNULL(IsClosed, 0),
+ ISNULL(ItemCode,''),
+ ISNULL(ColorCode,''),
+ ISNULL(ItemDim1Code,''),
+ ISNULL(ItemDim2Code,'')
+FROM BAGGI_V3.dbo.trOrderLine
+WHERE OrderHeaderID=@p1
+`, header.OrderHeaderID)
+ if err != nil {
+ return nil, fmt.Errorf("mevcut satirlar okunamadi: %w", err)
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var id, item, color, dim1, dim2 string
+ var closed bool
+
+ if err := rows.Scan(&id, &closed, &item, &color, &dim1, &dim2); err != nil {
+ return nil, err
+ }
+
+ combo := makeComboKeyParts(item, color, dim1, dim2)
+
+ if closed {
+ existingClosed[id] = true
+ if combo != "" {
+ existingClosedCombo[combo] = id
+ }
+ } else {
+ existingOpen[id] = true
+ if combo != "" {
+ existingOpenCombo[combo] = id
+ }
+ }
+ }
+
+ // ======================================================
+ // HEADER UPDATE
+ // ======================================================
+
+ _, err = tx.Exec(`
+UPDATE BAGGI_V3.dbo.trOrderHeader SET
+ OrderDate=@p1,
+ OrderTime=@p2,
+ AverageDueDate=@p3,
+ Description=@p4,
+ InternalDescription=@p5,
+ DocCurrencyCode=@p6,
+ LocalCurrencyCode=@p7,
+ ExchangeRate=@p8,
+ LastUpdatedUserName=@p9,
+ LastUpdatedDate=@p10
+WHERE OrderHeaderID=@p11
+`,
+ nullableDateString(header.OrderDate),
+ nullableTimeString(header.OrderTime),
+ nullableDateString(header.AverageDueDate),
+ nullableString(header.Description, ""),
+ nullableString(header.InternalDescription, ""),
+ nullableString(header.DocCurrencyCode, "TRY"),
+ nullableString(header.LocalCurrencyCode, "TRY"),
+ nullableFloat64(header.ExchangeRate, exRate),
+ v3User,
+ now,
+ header.OrderHeaderID,
+ )
+ if err != nil {
+ return nil, err
+ }
+ // ======================================================
+ // PREPARE STATEMENTS
+ // ======================================================
+
+ insStmt, err := tx.Prepare(`INSERT INTO BAGGI_V3.dbo.trOrderLine (
+OrderLineID, SortOrder, ItemTypeCode, ItemCode, ColorCode,
+ItemDim1Code, ItemDim2Code, ItemDim3Code,
+Qty1, Qty2, CancelQty1, CancelQty2, OrderCancelReasonCode,
+DeliveryDate, PlannedDateOfLading, LineDescription,
+UsedBarcode, CostCenterCode,
+VatCode, VatRate, PCTCode, PCTRate,
+LDisRate1, LDisRate2, LDisRate3, LDisRate4, LDisRate5,
+DocCurrencyCode, PriceCurrencyCode, PriceExchangeRate,
+Price, BaseProcessCode, BaseOrderNumber,
+BaseCustomerTypeCode, BaseCustomerCode,
+BaseSubCurrAccID, BaseStoreCode,
+OrderHeaderID, CreatedUserName, CreatedDate,
+LastUpdatedUserName, LastUpdatedDate,
+SurplusOrderQtyToleranceRate,
+WithHoldingTaxTypeCode, DOVCode)
+VALUES (
+@p1,@p2,@p3,@p4,@p5,@p6,@p7,@p8,@p9,@p10,
+@p11,@p12,@p13,@p14,@p15,@p16,@p17,@p18,
+@p19,@p20,@p21,@p22,@p23,@p24,@p25,@p26,@p27,
+@p28,@p29,@p30,@p31,@p32,@p33,@p34,@p35,
+@p36,@p37,@p38,@p39,@p40,@p41,@p42,@p43,
+@p44,@p45)`)
+
+ if err != nil {
+ return nil, err
+ }
+ defer insStmt.Close()
+
+ updStmt, err := tx.Prepare(`UPDATE BAGGI_V3.dbo.trOrderLine SET
+SortOrder=@p1, ItemTypeCode=@p2, ItemCode=@p3, ColorCode=@p4,
+ItemDim1Code=@p5, ItemDim2Code=@p6, ItemDim3Code=@p7,
+Qty1=@p8, Qty2=@p9, CancelQty1=@p10, CancelQty2=@p11,
+OrderCancelReasonCode=@p12,
+DeliveryDate=@p13, PlannedDateOfLading=@p14,
+LineDescription=@p15, UsedBarcode=@p16, CostCenterCode=@p17,
+VatCode=@p18, VatRate=@p19, PCTCode=@p20, PCTRate=@p21,
+LDisRate1=@p22, LDisRate2=@p23, LDisRate3=@p24,
+LDisRate4=@p25, LDisRate5=@p26,
+DocCurrencyCode=@p27, PriceCurrencyCode=@p28,
+PriceExchangeRate=@p29, Price=@p30,
+BaseProcessCode=@p31, BaseOrderNumber=@p32,
+BaseCustomerTypeCode=@p33, BaseCustomerCode=@p34,
+BaseSubCurrAccID=@p35, BaseStoreCode=@p36,
+LastUpdatedUserName=@p37, LastUpdatedDate=@p38,
+SurplusOrderQtyToleranceRate=@p39,
+WithHoldingTaxTypeCode=@p40, DOVCode=@p41
+WHERE OrderLineID=@p42 AND ISNULL(IsClosed,0)=0`)
+
+ if err != nil {
+ return nil, err
+ }
+ defer updStmt.Close()
+
+ lineResults := make([]OrderLineResult, 0)
+ seenCombo := make(map[string]bool)
+
+ for _, ln := range lines {
+
+ comboKey := normalizeComboKey(safeNS(ln.ComboKey))
+ if comboKey == "" {
+ comboKey = makeComboKey(ln)
+ }
+
+ // Duplicate guard (SADECE aktif)
+ if qtyValue(ln.Qty1) > 0 && comboKey != "" {
+ if seenCombo[comboKey] {
+ return nil, fmt.Errorf("Duplicate satır: %s", comboKey)
+ }
+ seenCombo[comboKey] = true
+ }
+
+ // Kapalı satır
+ if ln.OrderLineID != "" && existingClosed[ln.OrderLineID] {
+ continue
+ }
+ if comboKey != "" {
+ if _, ok := existingClosedCombo[comboKey]; ok {
+ continue
+ }
+ }
+
+ // DELETE SIGNAL
+ if ln.OrderLineID != "" && qtyValue(ln.Qty1) <= 0 {
+ _, err := tx.Exec(`DELETE FROM BAGGI_V3.dbo.trOrderLineCurrency WHERE OrderLineID=@p1`, ln.OrderLineID)
+ if err != nil {
+ return nil, err
+ }
+ _, err = tx.Exec(`
+DELETE FROM BAGGI_V3.dbo.trOrderLine
+WHERE OrderHeaderID=@p1 AND OrderLineID=@p2 AND ISNULL(IsClosed,0)=0
+`, header.OrderHeaderID, ln.OrderLineID)
+ if err != nil {
+ return nil, err
+ }
+ delete(existingOpen, ln.OrderLineID)
+ delete(existingOpenCombo, comboKey)
+ continue
+ }
+ isNew := false
+
+ if ln.OrderLineID == "" {
+ if dbID, ok := existingOpenCombo[comboKey]; ok {
+ ln.OrderLineID = dbID
+ } else {
+ ln.OrderLineID = uuid.New().String()
+ isNew = true
+ }
+ }
+
+ if qtyValue(ln.Qty1) > 0 {
+ if err := ValidateItemVariant(tx, ln); err != nil {
+ return nil, err
+ }
+ }
+
+ if isNew {
+ _, err := insStmt.Exec(
+ ln.OrderLineID, ln.SortOrder, ln.ItemTypeCode,
+ nullableString(ln.ItemCode, ""), safeNS(ln.ColorCode),
+ safeNS(ln.ItemDim1Code), safeNS(ln.ItemDim2Code),
+ nullableString(ln.ItemDim3Code, ""),
+ ln.Qty1, ln.Qty2, ln.CancelQty1, ln.CancelQty2,
+ nullableString(ln.OrderCancelReasonCode, ""),
+ nullableTime(ln.DeliveryDate, now),
+ nullableDateString(ln.PlannedDateOfLading),
+ nullableString(ln.LineDescription, ""),
+ nullableString(ln.UsedBarcode, ""),
+ nullableString(ln.CostCenterCode, ""),
+ normalizeVatCode(ln.VatCode), nf0(ln.VatRate),
+ normalizePCTCode(ln.PCTCode), nf0(ln.PCTRate),
+ nf0(ln.LDisRate1), nf0(ln.LDisRate2),
+ nf0(ln.LDisRate3), nf0(ln.LDisRate4), nf0(ln.LDisRate5),
+ nullableString(ln.DocCurrencyCode, "TRY"),
+ nullableString(ln.PriceCurrencyCode, "TRY"),
+ nf0(ln.PriceExchangeRate), nf0(ln.Price),
+ nullableString(ln.BaseProcessCode, ""),
+ nullableString(ln.BaseOrderNumber, ""),
+ ln.BaseCustomerTypeCode,
+ nullableString(ln.BaseCustomerCode, ""),
+ nullableUUID(ln.BaseSubCurrAccID),
+ nullableString(ln.BaseStoreCode, ""),
+ header.OrderHeaderID,
+ v3User, now, v3User, now,
+ nf0(ln.SurplusOrderQtyToleranceRate),
+ nullableString(ln.WithHoldingTaxTypeCode, ""),
+ nullableString(ln.DOVCode, ""),
+ )
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ _, err := updStmt.Exec(
+ ln.SortOrder, ln.ItemTypeCode,
+ nullableString(ln.ItemCode, ""),
+ safeNS(ln.ColorCode),
+ safeNS(ln.ItemDim1Code),
+ safeNS(ln.ItemDim2Code),
+ nullableString(ln.ItemDim3Code, ""),
+ nf0(ln.Qty1), nf0(ln.Qty2),
+ nf0(ln.CancelQty1), nf0(ln.CancelQty2),
+ nullableString(ln.OrderCancelReasonCode, ""),
+ nullableTime(ln.DeliveryDate, now),
+ nullableDateString(ln.PlannedDateOfLading),
+ nullableString(ln.LineDescription, ""),
+ nullableString(ln.UsedBarcode, ""),
+ nullableString(ln.CostCenterCode, ""),
+ normalizeVatCode(ln.VatCode), nf0(ln.VatRate),
+ normalizePCTCode(ln.PCTCode), nf0(ln.PCTRate),
+ nf0(ln.LDisRate1), nf0(ln.LDisRate2),
+ nf0(ln.LDisRate3), nf0(ln.LDisRate4), nf0(ln.LDisRate5),
+ nullableString(ln.DocCurrencyCode, "TRY"),
+ nullableString(ln.PriceCurrencyCode, "TRY"),
+ nf0(ln.PriceExchangeRate), nf0(ln.Price),
+ nullableString(ln.BaseProcessCode, ""),
+ nullableString(ln.BaseOrderNumber, ""),
+ ln.BaseCustomerTypeCode,
+ nullableString(ln.BaseCustomerCode, ""),
+ nullableUUID(ln.BaseSubCurrAccID),
+ nullableString(ln.BaseStoreCode, ""),
+ v3User, now,
+ nf0(ln.SurplusOrderQtyToleranceRate),
+ nullableString(ln.WithHoldingTaxTypeCode, ""),
+ nullableString(ln.DOVCode, ""),
+ ln.OrderLineID,
+ )
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ delete(existingOpen, ln.OrderLineID)
+ delete(existingOpenCombo, comboKey)
+
+ if ln.ClientKey.Valid {
+ lineResults = append(lineResults, OrderLineResult{
+ ClientKey: ln.ClientKey.String,
+ OrderLineID: ln.OrderLineID,
+ })
+ }
+ }
+
+ // Grid dışı kalan açık satırlar
+ for id := range existingOpen {
+ _, err := tx.Exec(`DELETE FROM BAGGI_V3.dbo.trOrderLineCurrency WHERE OrderLineID=@p1`, id)
+ if err != nil {
+ return nil, err
+ }
+ _, err = tx.Exec(`DELETE FROM BAGGI_V3.dbo.trOrderLine WHERE OrderLineID=@p1 AND ISNULL(IsClosed,0)=0`, id)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if err := tx.Commit(); err != nil {
+ return nil, err
+ }
+
+ return lineResults, nil
+}
diff --git a/svc/queries/orderinventory.go b/svc/queries/orderinventory.go
new file mode 100644
index 0000000..a7c20b1
--- /dev/null
+++ b/svc/queries/orderinventory.go
@@ -0,0 +1,109 @@
+package queries
+
+// 🔹 Sipariş ekranı stok kontrolü (ürün + renk + 2. renk + beden)
+// 🔧 Renk1 boşsa (aksesuar vb.) renksiz stokları da çeker
+const GetOrderInventory = `
+DECLARE @ProductCode NVARCHAR(30) = @p1;
+DECLARE @ColorCode NVARCHAR(30) = @p2;
+DECLARE @ColorCode2 NVARCHAR(30) = @p3;
+
+-- 🔧 Normalize giriş parametreleri
+SET @ColorCode = LTRIM(RTRIM(ISNULL(@ColorCode, '')));
+SET @ColorCode2 = LTRIM(RTRIM(ISNULL(@ColorCode2, '')));
+
+IF @ColorCode = ''
+BEGIN
+ SET @ColorCode = '';
+END
+
+IF @ColorCode2 = ''
+BEGIN
+ SET @ColorCode2 = NULL;
+END
+
+------------------------------------------------------------
+-- 🔹 Ana sorgu
+------------------------------------------------------------
+SELECT
+ Inventory.ItemCode AS Urun_Kodu,
+ Inventory.ColorCode AS Renk_Kodu,
+ ISNULL((
+ SELECT TOP 1 ColorDescription
+ FROM cdColorDesc WITH(NOLOCK)
+ WHERE cdColorDesc.ColorCode = Inventory.ColorCode
+ AND cdColorDesc.LangCode = N'TR'
+ ), '') AS Renk_Aciklamasi,
+
+ -- ✅ NULL bedenleri boş string olarak getir
+ ISNULL(Inventory.ItemDim1Code, '') AS Beden,
+ ISNULL(Inventory.ItemDim2Code, '') AS Yaka,
+
+ ROUND(
+ SUM(Inventory.InventoryQty1) -
+ (SUM(Inventory.ReserveQty1) + SUM(Inventory.DispOrderQty1) + SUM(Inventory.PickingQty1)),
+ cdUnitOfMeasure.RoundDigit
+ ) AS Kullanilabilir_Envanter
+
+FROM cdItem WITH (NOLOCK)
+JOIN cdUnitOfMeasure WITH (NOLOCK)
+ ON cdItem.UnitOfMeasureCode1 = cdUnitOfMeasure.UnitOfMeasureCode
+JOIN (
+ SELECT
+ CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
+ ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
+ SUM(CASE WHEN SourceTable = 'PickingStates' THEN Qty1 ELSE 0 END) AS PickingQty1,
+ SUM(CASE WHEN SourceTable = 'ReserveStates' THEN Qty1 ELSE 0 END) AS ReserveQty1,
+ SUM(CASE WHEN SourceTable = 'DispOrderStates' THEN Qty1 ELSE 0 END) AS DispOrderQty1,
+ SUM(CASE WHEN SourceTable = 'trStock' THEN (In_Qty1 - Out_Qty1) ELSE 0 END) AS InventoryQty1
+ FROM (
+ SELECT 'PickingStates' AS SourceTable, CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
+ ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
+ Qty1, 0 AS In_Qty1, 0 AS Out_Qty1
+ FROM PickingStates
+ UNION ALL
+ SELECT 'ReserveStates', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
+ ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
+ Qty1, 0, 0
+ FROM ReserveStates
+ UNION ALL
+ SELECT 'DispOrderStates', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
+ ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
+ Qty1, 0, 0
+ FROM DispOrderStates
+ UNION ALL
+ SELECT 'trStock', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
+ ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
+ 0, SUM(In_Qty1), SUM(Out_Qty1)
+ FROM trStock WITH (NOLOCK)
+ GROUP BY CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
+ ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code
+ ) AS SourceData
+ GROUP BY CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
+ ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code
+) AS Inventory
+ ON cdItem.ItemTypeCode = Inventory.ItemTypeCode
+ AND cdItem.ItemCode = Inventory.ItemCode
+LEFT JOIN ProductAttributesFilter
+ ON ProductAttributesFilter.ItemCode = Inventory.ItemCode
+WHERE
+ Inventory.ItemTypeCode IN (1)
+ AND Inventory.WarehouseCode IN ('1-0-12','1-0-21','1-0-10','1-0-2','1-1-3','1-2-4','1-2-5')
+ AND cdItem.IsBlocked = 0
+ AND Inventory.ItemCode = @ProductCode
+ AND (
+ -- 🔹 Eğer renk girilmişse o renk; boşsa renksiz stokları getir
+ (LTRIM(RTRIM(@ColorCode)) <> '' AND Inventory.ColorCode = @ColorCode)
+ OR (LTRIM(RTRIM(@ColorCode)) = '' AND (Inventory.ColorCode IS NULL OR LTRIM(RTRIM(Inventory.ColorCode)) = ''))
+ )
+ AND (
+ -- 🔹 2. renk sadece doluysa filtrelensin
+ @ColorCode2 IS NULL
+ OR Inventory.ItemDim2Code = @ColorCode2
+ )
+GROUP BY
+ Inventory.ItemCode,
+ Inventory.ColorCode,
+ ISNULL(Inventory.ItemDim1Code, ''), -- ✅ NULL bedenleri boş string olarak gruplar
+ ISNULL(Inventory.ItemDim2Code, ''), -- ✅ NULL yakaları boş string olarak gruplar
+ cdUnitOfMeasure.RoundDigit;
+`
diff --git a/svc/queries/orderlist.go b/svc/queries/orderlist.go
new file mode 100644
index 0000000..78a7107
--- /dev/null
+++ b/svc/queries/orderlist.go
@@ -0,0 +1,213 @@
+package queries
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/internal/authz"
+ "context"
+ "database/sql"
+ "fmt"
+)
+
+// ========================================================
+// 📌 GetOrderList — FINAL + CURRENCY SAFE + PIYASA AUTHZ
+// ========================================================
+func GetOrderList(
+ ctx context.Context,
+ mssql *sql.DB,
+ pg *sql.DB,
+ search string,
+) (*sql.Rows, error) {
+
+ claims, ok := auth.GetClaimsFromContext(ctx)
+ if !ok || claims == nil {
+ return nil, fmt.Errorf("unauthorized: claims not found")
+ }
+
+ // ----------------------------------------------------
+ // 🔐 PIYASA FILTER (ADMIN BYPASS)
+ // ----------------------------------------------------
+ piyasaWhere := "1=1"
+
+ if !claims.IsAdmin() {
+
+ codes, err := authz.GetUserPiyasaCodes(pg, int(claims.ID))
+ if err != nil {
+ return nil, fmt.Errorf("piyasa codes load error: %w", err)
+ }
+
+ if len(codes) == 0 {
+ // hiç yetkisi yok → hiç kayıt dönmesin
+ piyasaWhere = "1=0"
+ } else {
+ // ⚠️ EXISTS içinde kullanılacak
+ piyasaWhere = authz.BuildINClause(
+ "UPPER(f2.CustomerAtt01)",
+ codes,
+ )
+ }
+ }
+
+ // ----------------------------------------------------
+ // 📄 BASE QUERY
+ // ----------------------------------------------------
+ baseQuery := fmt.Sprintf(`
+SELECT
+ CAST(h.OrderHeaderID AS NVARCHAR(50)) AS OrderHeaderID,
+ ISNULL(h.OrderNumber, '') AS OrderNumber,
+ CONVERT(varchar, h.OrderDate, 23) AS OrderDate,
+
+ ISNULL(h.CurrAccCode, '') AS CurrAccCode,
+ ISNULL(ca.CurrAccDescription, '') AS CurrAccDescription,
+
+ ISNULL(mt.AttributeDescription, '') AS MusteriTemsilcisi,
+ ISNULL(py.AttributeDescription, '') AS Piyasa,
+
+ CONVERT(varchar, h.CreditableConfirmedDate,23) AS CreditableConfirmedDate,
+ ISNULL(h.DocCurrencyCode,'TRY') AS DocCurrencyCode,
+
+ ISNULL(l.TotalAmount,0) AS TotalAmount,
+
+ ----------------------------------------------------------------
+ -- USD HESABI (TRY / EUR / GBP / USD DESTEKLİ)
+ ----------------------------------------------------------------
+ CASE
+ WHEN h.DocCurrencyCode = 'USD'
+ THEN ISNULL(l.TotalAmount,0)
+
+ WHEN h.DocCurrencyCode = 'TRY'
+ AND usd.Rate > 0
+ THEN ISNULL(l.TotalAmount,0) / usd.Rate
+
+ WHEN h.DocCurrencyCode IN ('EUR','GBP')
+ AND cur.Rate > 0
+ AND usd.Rate > 0
+ THEN (ISNULL(l.TotalAmount,0) * cur.Rate) / usd.Rate
+
+ ELSE 0
+ END AS TotalAmountUSD,
+
+ ISNULL(h.IsCreditableConfirmed,0) AS IsCreditableConfirmed,
+ ISNULL(h.Description,'') AS Description,
+
+ usd.Rate AS ExchangeRateUSD
+
+FROM dbo.trOrderHeader h
+
+JOIN (
+ SELECT
+ OrderHeaderID,
+ SUM(Qty1 * Price) AS TotalAmount
+ FROM dbo.trOrderLine
+ GROUP BY OrderHeaderID
+) l
+ ON l.OrderHeaderID = h.OrderHeaderID
+
+LEFT JOIN dbo.cdCurrAccDesc ca
+ ON ca.CurrAccCode = h.CurrAccCode
+ AND ca.LangCode = 'TR'
+
+-- müşteri temsilcisi + piyasa açıklamaları
+LEFT JOIN dbo.CustomerAttributesFilter f
+ ON f.CurrAccCode = h.CurrAccCode
+
+LEFT JOIN dbo.cdCurrAccAttributeDesc mt
+ ON mt.CurrAccTypeCode = 3
+ AND mt.AttributeTypeCode = 2
+ AND mt.AttributeCode = f.CustomerAtt02
+ AND mt.LangCode = 'TR'
+
+LEFT JOIN dbo.cdCurrAccAttributeDesc py
+ ON py.CurrAccTypeCode = 3
+ AND py.AttributeTypeCode = 1
+ AND py.AttributeCode = f.CustomerAtt01
+ AND py.LangCode = 'TR'
+
+----------------------------------------------------------------
+-- USD → TRY
+----------------------------------------------------------------
+OUTER APPLY (
+ SELECT TOP 1 Rate
+ FROM dbo.AllExchangeRates
+ WHERE CurrencyCode = 'USD'
+ AND RelationCurrencyCode = 'TRY'
+ AND ExchangeTypeCode = 6
+ AND Rate > 0
+ AND Date <= CAST(GETDATE() AS date)
+ ORDER BY Date DESC
+) usd
+
+----------------------------------------------------------------
+-- ORDER PB → TRY
+----------------------------------------------------------------
+OUTER APPLY (
+ SELECT TOP 1 Rate
+ FROM dbo.AllExchangeRates
+ WHERE CurrencyCode = h.DocCurrencyCode
+ AND RelationCurrencyCode = 'TRY'
+ AND ExchangeTypeCode = 6
+ AND Rate > 0
+ AND Date <= CAST(GETDATE() AS date)
+ ORDER BY Date DESC
+) cur
+
+WHERE
+ ISNULL(h.IsCancelOrder,0) = 0
+ AND h.OrderTypeCode = 1
+ AND h.ProcessCode = 'WS'
+ AND h.IsClosed = 0
+
+ -- 🔐 PIYASA AUTHZ (EXISTS — SAĞLAM YOL)
+ AND EXISTS (
+ SELECT 1
+ FROM dbo.CustomerAttributesFilter f2
+ WHERE f2.CurrAccCode = h.CurrAccCode
+ AND %s
+ )
+`, piyasaWhere)
+
+ // ----------------------------------------------------
+ // 🔍 SEARCH FILTER (CASE + TR SAFE)
+ // ----------------------------------------------------
+ if search != "" {
+ baseQuery += `
+AND EXISTS (
+ SELECT 1
+ FROM dbo.trOrderHeader h2
+ LEFT JOIN dbo.cdCurrAccDesc ca2
+ ON ca2.CurrAccCode = h2.CurrAccCode
+ AND ca2.LangCode = 'TR'
+ WHERE h2.OrderHeaderID = h.OrderHeaderID
+ AND (
+ LOWER(REPLACE(REPLACE(h2.OrderNumber,'İ','I'),'ı','i'))
+ COLLATE Latin1_General_CI_AI LIKE LOWER(@p1)
+
+ OR LOWER(REPLACE(REPLACE(h2.CurrAccCode,'İ','I'),'ı','i'))
+ COLLATE Latin1_General_CI_AI LIKE LOWER(@p1)
+
+ OR LOWER(REPLACE(REPLACE(ca2.CurrAccDescription,'İ','I'),'ı','i'))
+ COLLATE Latin1_General_CI_AI LIKE LOWER(@p1)
+
+ OR LOWER(REPLACE(REPLACE(h2.Description,'İ','I'),'ı','i'))
+ COLLATE Latin1_General_CI_AI LIKE LOWER(@p1)
+ )
+)
+`
+ }
+
+ // ----------------------------------------------------
+ // 📌 ORDER
+ // ----------------------------------------------------
+ baseQuery += `
+ORDER BY h.CreatedDate DESC
+`
+
+ // ----------------------------------------------------
+ // ▶ EXECUTE
+ // ----------------------------------------------------
+ if search != "" {
+ searchLike := fmt.Sprintf("%%%s%%", search)
+ return mssql.Query(baseQuery, searchLike)
+ }
+
+ return mssql.Query(baseQuery)
+}
diff --git a/svc/queries/orderlist_base.go b/svc/queries/orderlist_base.go
new file mode 100644
index 0000000..02d5afd
--- /dev/null
+++ b/svc/queries/orderlist_base.go
@@ -0,0 +1,75 @@
+package queries
+
+const OrderListBaseQuery = `
+SELECT
+ CAST(h.OrderHeaderID AS NVARCHAR(50)) AS OrderHeaderID,
+ ISNULL(h.OrderNumber, '') AS OrderNumber,
+ CONVERT(varchar, h.OrderDate, 23) AS OrderDate,
+
+ ISNULL(h.CurrAccCode, '') AS CurrAccCode,
+ ISNULL(ca.CurrAccDescription, '') AS CurrAccDescription,
+
+ ISNULL(mt.AttributeDescription, '') AS MusteriTemsilcisi,
+ ISNULL(py.AttributeDescription, '') AS Piyasa,
+
+ CONVERT(varchar, h.CreditableConfirmedDate, 23) AS CreditableConfirmedDate,
+ ISNULL(h.DocCurrencyCode, 'TRY') AS DocCurrencyCode,
+
+ ISNULL(l.TotalAmount, 0) AS TotalAmount,
+
+ CASE
+ WHEN h.DocCurrencyCode = 'USD'
+ THEN ISNULL(l.TotalAmount, 0)
+ WHEN h.DocCurrencyCode = 'TRY'
+ THEN ISNULL(l.TotalAmount, 0) / NULLIF(er.Rate, 1)
+ ELSE 0
+ END AS TotalAmountUSD,
+
+ ISNULL(h.IsCreditableConfirmed, 0) AS IsCreditableConfirmed,
+ ISNULL(h.Description, '') AS Description,
+
+ ISNULL(er.Rate, 1) AS ExchangeRateUSD
+
+FROM dbo.trOrderHeader h
+
+JOIN (
+ SELECT OrderHeaderID, SUM(Qty1 * Price) AS TotalAmount
+ FROM dbo.trOrderLine
+ GROUP BY OrderHeaderID
+) l ON l.OrderHeaderID = h.OrderHeaderID
+
+LEFT JOIN dbo.cdCurrAccDesc ca
+ ON ca.CurrAccCode = h.CurrAccCode AND ca.LangCode = 'TR'
+
+LEFT JOIN dbo.CustomerAttributes f
+ ON f.CurrAccTypeCode = h.CurrAccTypeCode
+ AND f.CurrAccCode = h.CurrAccCode
+
+LEFT JOIN dbo.cdCurrAccAttributeDesc mt
+ ON mt.CurrAccTypeCode = f.CurrAccTypeCode
+ AND mt.AttributeTypeCode = 2
+ AND mt.AttributeCode = f.CustomerAtt02
+ AND mt.LangCode = 'TR'
+
+LEFT JOIN dbo.cdCurrAccAttributeDesc py
+ ON py.CurrAccTypeCode = f.CurrAccTypeCode
+ AND py.AttributeTypeCode = 3
+ AND py.AttributeCode = f.CustomerAtt03
+ AND py.LangCode = 'TR'
+
+OUTER APPLY (
+ SELECT TOP 1 Rate
+ FROM dbo.AllExchangeRates er
+ WHERE er.CurrencyCode = 'USD'
+ AND er.RelationCurrencyCode = 'TRY'
+ AND er.ExchangeTypeCode = 6
+ AND er.Rate > 0
+ ORDER BY ABS(DATEDIFF(DAY, er.Date, GETDATE()))
+) er
+
+WHERE
+ ISNULL(h.IsCancelOrder, 0) = 0
+ AND h.OrderTypeCode = 1
+ AND h.ProcessCode = 'WS'
+ AND h.IsClosed = 0
+`
diff --git a/svc/queries/orderpricelistb2b.go b/svc/queries/orderpricelistb2b.go
new file mode 100644
index 0000000..de26d9e
--- /dev/null
+++ b/svc/queries/orderpricelistb2b.go
@@ -0,0 +1,36 @@
+package queries
+
+import (
+ "bssapp-backend/models"
+ "database/sql"
+ "fmt"
+)
+
+// GetOrderPriceListB2B → model + currency bazlı ürün fiyatını döndürür (PostgreSQL sürümü)
+func GetOrderPriceListB2B(db *sql.DB, modelCode string, currency string) (*models.OrderPriceListB2B, error) {
+ query := `
+ SELECT
+ mmitem.code AS ModelCode,
+ sdprc.crn AS CurrencyCode,
+ sdprc.prc AS Price,
+ sdprc.sdprcgrp_id AS PriceGroupID,
+ TO_CHAR(sdprc.zlins_dttm, 'YYYY-MM-DD') AS LastUpdate
+ FROM sdprc
+ LEFT JOIN mmitem ON sdprc.mmitem_id = mmitem.id
+ WHERE mmitem.code = $1
+ AND sdprc.prc IS NOT NULL
+ AND sdprc.prc > 0
+ AND sdprc.crn = $2
+ AND sdprc.sdprcgrp_id = 1
+ ORDER BY sdprc.zlins_dttm DESC
+ LIMIT 1;
+ `
+
+ row := db.QueryRow(query, modelCode, currency)
+ var p models.OrderPriceListB2B
+ err := row.Scan(&p.ModelCode, &p.CurrencyCode, &p.Price, &p.PriceGroupID, &p.LastUpdate)
+ if err != nil {
+ return nil, fmt.Errorf("ürün fiyatı bulunamadı: %v", err)
+ }
+ return &p, nil
+}
diff --git a/svc/queries/permission_role_dept.go b/svc/queries/permission_role_dept.go
new file mode 100644
index 0000000..49f80ec
--- /dev/null
+++ b/svc/queries/permission_role_dept.go
@@ -0,0 +1,47 @@
+package queries
+
+/* ======================================================
+ ROLE + DEPARTMENT PERMISSIONS
+====================================================== */
+
+// GET
+const GetRoleDepartmentPermissions = `
+SELECT
+ rdp.module_code,
+ rdp.action,
+ rdp.allowed
+FROM vw_role_dept_permissions rdp
+WHERE rdp.role_id = $1
+ AND rdp.department_code = $2
+ORDER BY rdp.module_code, rdp.action
+`
+
+// UPSERT
+const UpsertRoleDepartmentPermission = `
+INSERT INTO mk_sys_role_department_permissions
+(
+ role_id,
+ department_code,
+ module_code,
+ action,
+ allowed
+)
+VALUES ($1,$2,$3,$4,$5)
+
+ON CONFLICT ON CONSTRAINT uq_role_dept_module_action
+DO UPDATE SET
+ allowed = EXCLUDED.allowed;
+
+`
+
+// ======================================================
+// 📦 MODULES
+// ======================================================
+
+const GetModuleLookup = `
+SELECT
+ code AS value,
+ name AS label
+FROM mk_sys_modules
+ORDER BY id
+`
diff --git a/svc/queries/product.go b/svc/queries/product.go
new file mode 100644
index 0000000..73953db
--- /dev/null
+++ b/svc/queries/product.go
@@ -0,0 +1,34 @@
+package queries
+
+import (
+ "bssapp-backend/db"
+ "bssapp-backend/models"
+)
+
+// GetProductList → MSSQL'den ürün listesini döndürür
+func GetProductList() ([]models.Product, error) {
+ rows, err := db.MssqlDB.Query(`
+ SELECT
+ ProductCode
+ FROM ProductFilterWithDescription('TR')
+ WHERE
+ ProductAtt42 IN ('SERI', 'AKSESUAR')
+ AND IsBlocked = 0
+ AND LEN(ProductCode) = 13 -- 🔹 yalnızca 13 karakterlik kodlar
+ ORDER BY ProductCode;
+ `)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ var list []models.Product
+ for rows.Next() {
+ var p models.Product
+ if err := rows.Scan(&p.ProductCode); err != nil {
+ return nil, err
+ }
+ list = append(list, p)
+ }
+ return list, nil
+}
diff --git a/svc/queries/productcolor.go b/svc/queries/productcolor.go
new file mode 100644
index 0000000..f438fe5
--- /dev/null
+++ b/svc/queries/productcolor.go
@@ -0,0 +1,20 @@
+package queries
+
+const GetProductColors = `
+DECLARE @ProductCode VARCHAR(30) = @p1;
+
+SELECT DISTINCT
+ p.ProductCode,
+ v.ColorCode,
+ ISNULL(cd.ColorDescription, '') AS ColorDescription
+FROM ProductFilterWithDescription('TR') AS p
+INNER JOIN prItemVariant AS v WITH(NOLOCK)
+ ON v.ItemCode = p.ProductCode
+LEFT JOIN cdColorDesc AS cd WITH(NOLOCK)
+ ON cd.ColorCode = v.ColorCode
+ AND cd.LangCode = 'TR'
+WHERE
+ p.ProductCode = @ProductCode
+ AND ISNULL(v.ColorCode, '') <> ''
+ORDER BY v.ColorCode;
+`
diff --git a/svc/queries/productcolorsize.go b/svc/queries/productcolorsize.go
new file mode 100644
index 0000000..b6346b9
--- /dev/null
+++ b/svc/queries/productcolorsize.go
@@ -0,0 +1,66 @@
+package queries
+
+const GetProductColorSizes = `
+------------------------------------------------------------
+-- 🧩 GetProductColorSizes
+-- Kullanım:
+-- @p1 = ProductCode
+-- @p2 = ColorCode
+-- @p3 = ColorCode2
+-- Açıklama:
+-- Renk veya ikinci renk boşsa buna göre filtre uygular.
+-- Renksiz (aksesuar) ürünlerde ColorCode='' kayıtlarını getirir.
+------------------------------------------------------------
+
+DECLARE @ProductCode NVARCHAR(30) = @p1;
+DECLARE @ColorCode NVARCHAR(30) = @p2;
+DECLARE @ColorCode2 NVARCHAR(30) = @p3;
+
+-- 🔧 Normalize parametreler (boşlukları kırp ve düzelt)
+SET @ColorCode = LTRIM(RTRIM(ISNULL(@ColorCode, '')));
+SET @ColorCode2 = LTRIM(RTRIM(ISNULL(@ColorCode2, '')));
+
+IF @ColorCode = ''
+BEGIN
+ SET @ColorCode = ''; -- tamamen renksiz ürün
+END
+
+IF @ColorCode2 = ''
+BEGIN
+ SET @ColorCode2 = NULL; -- 2. renk yok
+END
+
+------------------------------------------------------------
+-- 🔹 Ana sorgu
+------------------------------------------------------------
+SELECT
+ Product.ProductCode,
+ ISNULL(prItemVariant.ColorCode, '') AS ColorCode,
+ ISNULL(prItemVariant.ItemDim1Code, '') AS ItemDim1Code,
+ ISNULL(prItemVariant.ItemDim2Code, '') AS ItemDim2Code
+FROM ProductFilterWithDescription('TR') AS Product
+INNER JOIN cdItem WITH(NOLOCK)
+ ON cdItem.ItemTypeCode = 1
+ AND cdItem.ItemCode = Product.ProductCode
+LEFT JOIN prProductLot WITH(NOLOCK)
+ ON prProductLot.ItemTypeCode = cdItem.ItemTypeCode
+ AND prProductLot.ItemCode = cdItem.ItemCode
+ AND prProductLot.IsDefault = 1
+LEFT JOIN prItemVariant WITH(NOLOCK)
+ ON prItemVariant.ItemCode = Product.ProductCode
+WHERE
+ ProductAtt42 IN ('SERI','AKSESUAR')
+ AND Product.IsBlocked = 0
+ AND Product.ProductCode = @ProductCode
+ AND (
+ -- 🔸 1. renk doluysa o renk
+ (LTRIM(RTRIM(@ColorCode)) <> '' AND prItemVariant.ColorCode = @ColorCode)
+ -- 🔸 1. renk boşsa renksiz varyantlar
+ OR (LTRIM(RTRIM(@ColorCode)) = '' AND (prItemVariant.ColorCode IS NULL OR LTRIM(RTRIM(prItemVariant.ColorCode)) = ''))
+ -- 🔸 2. renk varsa ek olarak dahil et
+ OR (@ColorCode2 IS NOT NULL AND prItemVariant.ColorCode = @ColorCode2)
+ )
+ORDER BY
+ prItemVariant.ItemDim1Code,
+ prItemVariant.ItemDim2Code;
+`
diff --git a/svc/queries/productdetail.go b/svc/queries/productdetail.go
new file mode 100644
index 0000000..49f3b16
--- /dev/null
+++ b/svc/queries/productdetail.go
@@ -0,0 +1,36 @@
+package queries
+
+const GetProductDetail = `
+DECLARE @ProductCode VARCHAR(30) = @p1;
+
+SELECT
+ ProductCode,
+ '' AS BOS7,
+ ProductDescription,
+ Product.ProductAtt42,
+ Product.ProductAtt42Desc AS URUN_ILK_GRUBU,
+ Product.ProductAtt01,
+ Product.ProductAtt01Desc AS URUN_ANA_GRUBU,
+ Product.ProductAtt02,
+ Product.ProductAtt02Desc AS URUN_ALT_GRUBU,
+ Product.ProductAtt41,
+ Product.ProductAtt41Desc AS ICERIK,
+ Product.ProductAtt10,
+ Product.ProductAtt10Desc AS MARKA,
+ Product.ProductAtt11,
+ Product.ProductAtt11Desc AS DROP_,
+ Product.ProductAtt29,
+ Product.ProductAtt29Desc AS KARISIM,
+ Product.ProductAtt45,
+ Product.ProductAtt45Desc AS ASKILI_YAN,
+ Product.ProductAtt44,
+ Product.ProductAtt44Desc AS KATEGORI,
+ Product.ProductAtt38,
+ Product.ProductAtt38Desc AS FIT1,
+ Product.ProductAtt39,
+ Product.ProductAtt39Desc AS FIT2
+FROM ProductFilterWithDescription('TR') AS Product
+WHERE ProductAtt42 IN ('SERI','AKSESUAR')
+ AND IsBlocked = 0
+ AND ProductCode = @p1;
+`
diff --git a/svc/queries/productsecondcolor.go b/svc/queries/productsecondcolor.go
new file mode 100644
index 0000000..8213c89
--- /dev/null
+++ b/svc/queries/productsecondcolor.go
@@ -0,0 +1,18 @@
+package queries
+
+const GetProductSecondColors = `
+SELECT DISTINCT
+ Product.ProductCode,
+ ISNULL(prItemVariant.ColorCode, '') AS ColorCode,
+ ISNULL(prItemVariant.ItemDim2Code, '') AS ItemDim2Code
+FROM prItemVariant WITH(NOLOCK)
+ INNER JOIN ProductFilterWithDescription('TR') AS Product
+ ON prItemVariant.ItemCode = Product.ProductCode
+ LEFT JOIN cdColorDesc AS ColorDesc WITH(NOLOCK)
+ ON ColorDesc.ColorCode = prItemVariant.ItemDim2Code
+ AND ColorDesc.LangCode = 'TR'
+WHERE Product.ProductCode = @ProductCode
+ AND prItemVariant.ColorCode = @ColorCode
+ AND ISNULL(prItemVariant.ItemDim2Code, '') <> ''
+GROUP BY Product.ProductCode, prItemVariant.ItemDim2Code, prItemVariant.ColorCode
+`
diff --git a/svc/queries/statement_header.go b/svc/queries/statement_header.go
new file mode 100644
index 0000000..2dbea7e
--- /dev/null
+++ b/svc/queries/statement_header.go
@@ -0,0 +1,172 @@
+package queries
+
+import (
+ "bssapp-backend/db"
+ "bssapp-backend/models"
+ "database/sql"
+ "fmt"
+ "strings"
+)
+
+// Ana tabloyu getiren fonksiyon (Vue header tablosu için)
+func GetStatements(params models.StatementParams) ([]models.StatementHeader, error) {
+ // AccountCode normalize: "ZLA0127" → "ZLA 0127"
+ if len(params.AccountCode) == 7 && strings.ContainsAny(params.AccountCode, "0123456789") {
+ params.AccountCode = params.AccountCode[:3] + " " + params.AccountCode[3:]
+ }
+
+ // Parislemler []string → '1','2','3'
+ parislemFilter := "''"
+ if len(params.Parislemler) > 0 {
+ quoted := make([]string, len(params.Parislemler))
+ for i, v := range params.Parislemler {
+ quoted[i] = fmt.Sprintf("'%s'", v)
+ }
+ parislemFilter = strings.Join(quoted, ",")
+ }
+
+ 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 Parislemtipi
+ FROM trCurrAccBook b
+ LEFT JOIN cdCurrAccDesc d
+ ON b.CurrAccCode = d.CurrAccCode
+ 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)
+ )
+)
+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.Parislemtipi AS 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
+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;
+`, parislemFilter, parislemFilter, parislemFilter)
+
+ rows, err := db.MssqlDB.Query(query,
+ sql.Named("startdate", params.StartDate),
+ sql.Named("enddate", params.EndDate),
+ sql.Named("Carikod", params.AccountCode),
+ sql.Named("LangCode", params.LangCode),
+ )
+ if err != nil {
+ return nil, fmt.Errorf("MSSQL query error: %v", err)
+ }
+ defer rows.Close()
+
+ var results []models.StatementHeader
+ for rows.Next() {
+ var r models.StatementHeader
+ if err := rows.Scan(
+ &r.CariKod,
+ &r.CariIsim,
+ &r.BelgeTarihi,
+ &r.VadeTarihi,
+ &r.BelgeNo,
+ &r.IslemTipi,
+ &r.Aciklama,
+ &r.ParaBirimi,
+ &r.Borc,
+ &r.Alacak,
+ &r.Bakiye,
+ &r.Parislemler,
+ ); err != nil {
+ return nil, err
+ }
+ results = append(results, r)
+ }
+ return results, nil
+}
diff --git a/svc/queries/statement_header_pdf.go b/svc/queries/statement_header_pdf.go
new file mode 100644
index 0000000..c05f7d0
--- /dev/null
+++ b/svc/queries/statement_header_pdf.go
@@ -0,0 +1,187 @@
+// queries/statements_header_pdf.go
+package queries
+
+import (
+ "bssapp-backend/db"
+ "bssapp-backend/models"
+ "database/sql"
+ "fmt"
+ "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) {
+ // Account normalize
+ 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 {
+ log.Printf("❌ Header sorgu hatası: %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
+ }
+ headers = append(headers, h)
+ if h.BelgeNo != "" {
+ belgeNos = append(belgeNos, h.BelgeNo)
+ }
+ }
+ log.Printf("✅ Header verileri alındı: %d kayıt, %d belge no", len(headers), len(belgeNos))
+ return headers, belgeNos, nil
+}
diff --git a/svc/queries/statements_detail.go b/svc/queries/statements_detail.go
new file mode 100644
index 0000000..67b27f9
--- /dev/null
+++ b/svc/queries/statements_detail.go
@@ -0,0 +1,121 @@
+package queries
+
+import (
+ "bssapp-backend/db"
+ "bssapp-backend/models"
+ "database/sql"
+ "fmt"
+ "strings"
+)
+
+/* ============================ DETAIL (ALT TABLO) ============================ */
+
+func GetStatementDetails(accountCode, startDate, endDate string, parislemler []string) ([]models.StatementDetail, error) {
+ // Parislemler filtresi hazırlanır (ör: 1,2,3)
+ inParislem := ""
+ if len(parislemler) > 0 {
+ pp := make([]string, len(parislemler))
+ for i, v := range parislemler {
+ pp[i] = strings.TrimSpace(v)
+ }
+ inParislem = strings.Join(pp, ",")
+ }
+ query := fmt.Sprintf(`
+SELECT
+ CONVERT(varchar(10), a.InvoiceDate, 23) AS Belge_Tarihi,
+ a.InvoiceNumber AS Belge_Ref_Numarasi,
+ COALESCE(MAX(AnaGrupDesc.AttributeDescription), '') AS Urun_Ana_Grubu,
+ COALESCE(MAX(AltGrupDesc.AttributeDescription), '') AS Urun_Alt_Grubu,
+ COALESCE(MAX(GarsonDesc.AttributeDescription), '') AS Yetiskin_Garson,
+ COALESCE(MAX(FitDesc.AttributeDescription), '') AS Fit,
+ COALESCE(MAX(KisaKarDesc.AttributeDescription), '') AS Icerik,
+ a.ItemCode AS Urun_Kodu,
+ a.ColorCode AS Urun_Rengi,
+ SUM(a.Qty1) AS Toplam_Adet,
+ SUM(ABS(a.Doc_Price)) AS Toplam_Fiyat,
+ CAST(SUM(a.Qty1 * ABS(a.Doc_Price)) AS numeric(18,2)) AS Toplam_Tutar
+FROM AllInvoicesWithAttributes a
+LEFT JOIN prItemAttribute AnaGrup
+ ON a.ItemCode = AnaGrup.ItemCode AND AnaGrup.AttributeTypeCode = 1
+LEFT JOIN cdItemAttributeDesc AnaGrupDesc
+ ON AnaGrup.AttributeTypeCode = AnaGrupDesc.AttributeTypeCode
+ AND AnaGrup.AttributeCode = AnaGrupDesc.AttributeCode
+ AND AnaGrup.ItemTypeCode = AnaGrupDesc.ItemTypeCode
+
+LEFT JOIN prItemAttribute AltGrup
+ ON a.ItemCode = AltGrup.ItemCode AND AltGrup.AttributeTypeCode = 2
+LEFT JOIN cdItemAttributeDesc AltGrupDesc
+ ON AltGrup.AttributeTypeCode = AltGrupDesc.AttributeTypeCode
+ AND AltGrup.AttributeCode = AltGrupDesc.AttributeCode
+ AND AltGrup.ItemTypeCode = AltGrupDesc.ItemTypeCode
+
+LEFT JOIN prItemAttribute Garson
+ ON a.ItemCode = Garson.ItemCode AND Garson.AttributeTypeCode = 44
+LEFT JOIN cdItemAttributeDesc GarsonDesc
+ ON Garson.AttributeTypeCode = GarsonDesc.AttributeTypeCode
+ AND Garson.AttributeCode = GarsonDesc.AttributeCode
+ AND Garson.ItemTypeCode = GarsonDesc.ItemTypeCode
+
+LEFT JOIN prItemAttribute FitTbl
+ ON a.ItemCode = FitTbl.ItemCode AND FitTbl.AttributeTypeCode = 38
+LEFT JOIN cdItemAttributeDesc FitDesc
+ ON FitTbl.AttributeTypeCode = FitDesc.AttributeTypeCode
+ AND FitTbl.AttributeCode = FitDesc.AttributeCode
+ AND FitTbl.ItemTypeCode = FitDesc.ItemTypeCode
+
+LEFT JOIN prItemAttribute KisaKar
+ ON a.ItemCode = KisaKar.ItemCode AND KisaKar.AttributeTypeCode = 41
+LEFT JOIN cdItemAttributeDesc KisaKarDesc
+ ON KisaKar.AttributeTypeCode = KisaKarDesc.AttributeTypeCode
+ AND KisaKar.AttributeCode = KisaKarDesc.AttributeCode
+ AND KisaKar.ItemTypeCode = KisaKarDesc.ItemTypeCode
+WHERE a.CurrAccCode LIKE @Carikod
+ AND a.InvoiceDate BETWEEN @StartDate AND @EndDate
+ %s
+GROUP BY a.InvoiceDate, a.InvoiceNumber, a.ItemCode, a.ColorCode
+ORDER BY Belge_Tarihi, Belge_Ref_Numarasi, Urun_Kodu;`,
+ func() string {
+ if inParislem == "" {
+ return ""
+ }
+ return fmt.Sprintf(`AND EXISTS (
+ SELECT 1
+ FROM CurrAccBookATAttributesFilter f
+ WHERE f.CurrAccBookID = a.CurrAccBookID
+ AND f.ATAtt01 IN (%s)
+ )`, inParislem)
+ }(),
+ )
+ rows, err := db.MssqlDB.Query(query,
+ sql.Named("Carikod", "%"+accountCode+"%"),
+ sql.Named("StartDate", startDate),
+ sql.Named("EndDate", endDate),
+ )
+ if err != nil {
+ return nil, fmt.Errorf("detay sorgu hatası: %v", err)
+ }
+ defer rows.Close()
+
+ var results []models.StatementDetail
+ for rows.Next() {
+ var d models.StatementDetail
+ if err := rows.Scan(
+ &d.BelgeTarihi,
+ &d.BelgeRefNumarasi,
+ &d.UrunAnaGrubu,
+ &d.UrunAltGrubu,
+ &d.YetiskinGarson,
+ &d.Fit,
+ &d.Icerik,
+ &d.UrunKodu,
+ &d.UrunRengi,
+ &d.ToplamAdet,
+ &d.ToplamFiyat,
+ &d.ToplamTutar,
+ ); err != nil {
+ return nil, err
+ }
+ results = append(results, d)
+ }
+ return results, nil
+}
diff --git a/svc/queries/statements_pdf.go b/svc/queries/statements_pdf.go
new file mode 100644
index 0000000..a1a84f9
--- /dev/null
+++ b/svc/queries/statements_pdf.go
@@ -0,0 +1,305 @@
+// queries/statements_pdf.go
+package queries
+
+import (
+ "bssapp-backend/db"
+ "bssapp-backend/models"
+ "database/sql"
+ "fmt"
+ "log"
+ "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) {
+ // Account normalize
+ 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 {
+ log.Printf("❌ Header sorgu hatası: %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
+ }
+ headers = append(headers, h)
+ if h.BelgeNo != "" {
+ belgeNos = append(belgeNos, h.BelgeNo)
+ }
+ }
+ log.Printf("✅ Header verileri alındı: %d kayıt, %d belge no", len(headers), len(belgeNos))
+ return headers, belgeNos, nil
+}
+
+/* ============================ DETAIL (Alt Tablo) ============================ */
+
+func GetDetailsMapPDF(belgeNos []string, startDate, endDate string) (map[string][]models.StatementDetail, error) {
+ result := make(map[string][]models.StatementDetail)
+ if len(belgeNos) == 0 {
+ log.Println("⚠️ GetDetailsMapPDF: belge listesi boş")
+ return result, nil
+ }
+
+ qs := make([]string, 0, len(belgeNos))
+ for _, no := range belgeNos {
+ safe := strings.ReplaceAll(no, "'", "''")
+ qs = append(qs, fmt.Sprintf("'%s'", safe))
+ }
+ inBelge := strings.Join(qs, ",")
+
+ query := fmt.Sprintf(`
+;WITH BookMap AS (
+ SELECT b.RefNumber AS InvoiceNumber, b.CurrAccBookID
+ FROM trCurrAccBook b
+ WHERE b.RefNumber IN (%s)
+)
+SELECT
+ CONVERT(varchar(10), a.InvoiceDate, 23) AS Belge_Tarihi,
+ a.InvoiceNumber AS Belge_Ref_Numarasi,
+
+ MAX(ISNULL(AnaGrupDesc.AttributeDescription, '')) AS Urun_Ana_Grubu,
+ MAX(ISNULL(AltGrupDesc.AttributeDescription, '')) AS Urun_Alt_Grubu,
+ MAX(ISNULL(GarsonDesc.AttributeDescription, '')) AS Yetiskin_Garson,
+ MAX(ISNULL(FitDesc.AttributeDescription, '')) AS Fit,
+ MAX(ISNULL(KisaKarDesc.AttributeDescription, '')) AS Icerik,
+
+ a.ItemCode, a.ColorCode,
+ SUM(a.Qty1), SUM(ABS(a.Doc_Price)),
+ CAST(SUM(a.Qty1 * ABS(a.Doc_Price)) AS numeric(18,2))
+
+FROM AllInvoicesWithAttributes a
+JOIN BookMap bm
+ ON bm.InvoiceNumber = a.InvoiceNumber
+
+-- Ana Grup
+LEFT JOIN prItemAttribute AnaGrup
+ ON a.ItemCode = AnaGrup.ItemCode AND AnaGrup.AttributeTypeCode = 1
+LEFT JOIN cdItemAttributeDesc AnaGrupDesc
+ ON AnaGrup.AttributeTypeCode = AnaGrupDesc.AttributeTypeCode
+ AND AnaGrup.AttributeCode = AnaGrupDesc.AttributeCode
+ AND AnaGrup.ItemTypeCode = AnaGrupDesc.ItemTypeCode
+
+-- Alt Grup
+LEFT JOIN prItemAttribute AltGrup
+ ON a.ItemCode = AltGrup.ItemCode AND AltGrup.AttributeTypeCode = 2
+LEFT JOIN cdItemAttributeDesc AltGrupDesc
+ ON AltGrup.AttributeTypeCode = AltGrupDesc.AttributeTypeCode
+ AND AltGrup.AttributeCode = AltGrupDesc.AttributeCode
+ AND AltGrup.ItemTypeCode = AltGrupDesc.ItemTypeCode
+
+-- Garson
+LEFT JOIN prItemAttribute Garson
+ ON a.ItemCode = Garson.ItemCode AND Garson.AttributeTypeCode = 44
+LEFT JOIN cdItemAttributeDesc GarsonDesc
+ ON Garson.AttributeTypeCode = GarsonDesc.AttributeTypeCode
+ AND Garson.AttributeCode = GarsonDesc.AttributeCode
+ AND Garson.ItemTypeCode = GarsonDesc.ItemTypeCode
+
+-- Fit
+LEFT JOIN prItemAttribute FitTbl
+ ON a.ItemCode = FitTbl.ItemCode AND FitTbl.AttributeTypeCode = 38
+LEFT JOIN cdItemAttributeDesc FitDesc
+ ON FitTbl.AttributeTypeCode = FitDesc.AttributeTypeCode
+ AND FitTbl.AttributeCode = FitDesc.AttributeCode
+ AND FitTbl.ItemTypeCode = FitDesc.ItemTypeCode
+
+-- Kısa Karışım
+LEFT JOIN prItemAttribute KisaKar
+ ON a.ItemCode = KisaKar.ItemCode AND KisaKar.AttributeTypeCode = 41
+LEFT JOIN cdItemAttributeDesc KisaKarDesc
+ ON KisaKar.AttributeTypeCode = KisaKarDesc.AttributeTypeCode
+ AND KisaKar.AttributeCode = KisaKarDesc.AttributeCode
+ AND KisaKar.ItemTypeCode = KisaKarDesc.ItemTypeCode
+
+WHERE a.InvoiceDate BETWEEN @StartDate AND @EndDate
+GROUP BY a.InvoiceDate, a.InvoiceNumber, a.ItemCode, a.ColorCode
+ORDER BY a.InvoiceNumber, a.ItemCode, a.ColorCode;`, inBelge)
+ rows, err := db.MssqlDB.Query(query,
+ sql.Named("StartDate", startDate),
+ sql.Named("EndDate", endDate),
+ )
+ if err != nil {
+ log.Printf("❌ Detay sorgu hatası: %v", err)
+ return nil, fmt.Errorf("detay sorgu hatası: %v", err)
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var d models.StatementDetail
+ if err := rows.Scan(
+ &d.BelgeTarihi,
+ &d.BelgeRefNumarasi,
+ &d.UrunAnaGrubu,
+ &d.UrunAltGrubu,
+ &d.YetiskinGarson,
+ &d.Fit,
+ &d.Icerik,
+ &d.UrunKodu,
+ &d.UrunRengi,
+ &d.ToplamAdet,
+ &d.ToplamFiyat,
+ &d.ToplamTutar,
+ ); err != nil {
+ log.Printf("❌ Detay scan hatası: %v", err)
+ return nil, err
+ }
+ result[d.BelgeRefNumarasi] = append(result[d.BelgeRefNumarasi], d)
+ }
+ log.Printf("✅ Detay verileri alındı: %d belge için detay var", len(result))
+ return result, nil
+}
diff --git a/svc/queries/todaycurrencyv3.go b/svc/queries/todaycurrencyv3.go
new file mode 100644
index 0000000..b279367
--- /dev/null
+++ b/svc/queries/todaycurrencyv3.go
@@ -0,0 +1,35 @@
+package queries
+
+import (
+ "bssapp-backend/models"
+ "database/sql"
+ "fmt"
+)
+
+// GetTodayCurrencyV3 tek bir döviz türü için güncel kuru döndürür (MSSQL sürümü)
+func GetTodayCurrencyV3(db *sql.DB, currencyCode string) (*models.TodayCurrencyV3, error) {
+ query := `
+ SELECT TOP 1
+ CurrencyCode,
+ RelationCurrencyCode,
+ ExchangeTypeCode,
+ Rate,
+ CONVERT(varchar, Date, 23) AS Date
+ FROM AllExchangeRates
+ WHERE CurrencyCode = @p1
+ AND RelationCurrencyCode = 'TRY'
+ AND ExchangeTypeCode = 6
+ AND Rate > 0
+ ORDER BY ABS(DATEDIFF(DAY, Date, GETDATE())) ASC
+ `
+
+ row := db.QueryRow(query, currencyCode)
+
+ var c models.TodayCurrencyV3
+ err := row.Scan(&c.CurrencyCode, &c.RelationCurrencyCode, &c.ExchangeTypeCode, &c.Rate, &c.Date)
+ if err != nil {
+ return nil, fmt.Errorf("kur bilgisi alınamadı: %v", err)
+ }
+
+ return &c, nil
+}
diff --git a/svc/queries/user_detail.go b/svc/queries/user_detail.go
new file mode 100644
index 0000000..d768b84
--- /dev/null
+++ b/svc/queries/user_detail.go
@@ -0,0 +1,296 @@
+package queries
+
+// ======================================================
+// 👤 USER HEADER
+// ======================================================
+// ======================================================
+// 👤 USER HEADER (mk_dfusr)
+// ======================================================
+const GetUserHeader = `
+SELECT
+ id,
+ username,
+ is_active,
+ COALESCE(full_name,'') AS full_name,
+ COALESCE(email,'') AS email,
+ COALESCE(mobile,'') AS mobile,
+ COALESCE(address,'') AS address,
+
+ CASE
+ WHEN password_hash IS NOT NULL AND password_hash <> '' THEN true
+ ELSE false
+ END AS has_password
+
+FROM mk_dfusr
+WHERE id = $1
+`
+
+// ======================================================
+// 🔐 ROLES
+// ======================================================
+const GetUserRoles = `
+SELECT r.code
+FROM dfrole_usr ur
+JOIN dfrole r ON r.id = ur.dfrole_id
+WHERE ur.dfusr_id = $1
+ORDER BY r.code
+`
+
+// ======================================================
+// 🏢 DEPARTMENTS
+// ======================================================
+const GetUserDepartments = `
+SELECT d.code, d.title
+FROM dfusr_dprt ud
+JOIN mk_dprt d ON d.id = ud.dprt_id
+WHERE ud.dfusr_id = $1
+ AND ud.is_active = true
+ORDER BY d.code
+`
+
+// ======================================================
+// 🌍 PIYASALAR
+// ======================================================
+const GetUserPiyasalar = `
+SELECT p.code, p.title
+FROM dfusr_piyasa up
+JOIN mk_sales_piy p ON p.code = up.piyasa_code
+WHERE up.dfusr_id = $1
+ AND up.is_allowed = true
+ORDER BY p.code
+`
+
+// ======================================================
+// 🧾 NEBIM USERS
+// ======================================================
+const GetUserNebim = `
+SELECT n.username, n.user_group_code
+FROM dfusr_nebim_user un
+JOIN mk_nebim_user n ON n.id = un.mk_nebim_user_id
+WHERE un.dfusr_id = $1
+ORDER BY n.username
+`
+
+// ======================================================
+// ✍️ UPDATE USER HEADER (SAFE)
+// ======================================================
+// ======================================================
+// ✍️ UPDATE USER HEADER (mk_dfusr)
+// ======================================================
+const UpdateUserHeader = `
+UPDATE mk_dfusr
+SET
+ username = $2,
+ is_active = $3,
+ full_name = $4,
+ email = $5,
+ mobile = $6,
+ address = NULLIF($7, ''),
+ updated_at = NOW()
+WHERE id = $1
+`
+
+// ======================================================
+// ➕ INSERT RELATIONS
+// ======================================================
+
+// ---------- ROLE
+const InsertUserRole = `
+INSERT INTO dfrole_usr (dfusr_id, dfrole_id)
+SELECT $1, r.id
+FROM dfrole r
+WHERE r.code = $2
+ON CONFLICT DO NOTHING
+`
+
+// ---------- DEPARTMENT
+const InsertUserDepartment = `
+INSERT INTO dfusr_dprt (dfusr_id, dprt_id, is_active)
+SELECT $1, d.id, true
+FROM mk_dprt d
+WHERE d.code = $2
+ON CONFLICT DO NOTHING
+`
+
+// ---------- PIYASA
+const InsertUserPiyasa = `
+INSERT INTO dfusr_piyasa (dfusr_id, piyasa_code, is_allowed)
+VALUES ($1, $2, true)
+ON CONFLICT DO NOTHING
+`
+
+// ---------- NEBIM USER
+const InsertUserNebim = `
+INSERT INTO dfusr_nebim_user (dfusr_id, mk_nebim_user_id)
+SELECT $1, n.id
+FROM mk_nebim_user n
+WHERE n.username = $2
+ON CONFLICT DO NOTHING
+`
+
+// ======================================================
+// 📚 LOOKUPS
+// ======================================================
+const GetRoleLookup = `
+SELECT code AS value, code AS label
+FROM dfrole
+ORDER BY code
+`
+
+const GetDepartmentLookup = `
+SELECT code AS value, title AS label
+FROM mk_dprt
+WHERE is_active = true
+ORDER BY code
+`
+
+const GetPiyasaLookup = `
+SELECT code AS value, title AS label
+FROM mk_sales_piy
+WHERE is_active = true
+ORDER BY code
+`
+
+const GetNebimUserLookup = `
+SELECT
+ username AS value,
+ username || ' (' || user_group_code || ')' AS label,
+ user_group_code
+FROM mk_nebim_user
+WHERE is_active = true
+ORDER BY username
+`
+
+// ======================================================
+// 📦 ORDER — NEW NUMBER
+// ======================================================
+
+const GetNextOrderNumber = `
+SELECT
+ COALESCE(MAX(order_number), 0) + 1
+FROM orders
+WHERE order_year = EXTRACT(YEAR FROM NOW())
+`
+
+// ======================================================
+// 🔐 ROLES (ADMIN LIST)
+// ======================================================
+
+const GetRoles = `
+SELECT
+ id,
+ code,
+ title
+FROM dfrole
+ORDER BY code
+`
+
+// ======================================================
+// 🏢 DEPARTMENTS (ADMIN LIST)
+// ======================================================
+
+const GetDepartments = `
+SELECT
+ id,
+ code,
+ title
+FROM mk_dprt
+WHERE is_active = true
+ORDER BY code
+`
+
+// ======================================================
+// 🌍 PIYASALAR (ADMIN LIST)
+// ======================================================
+
+const GetPiyasalar = `
+SELECT
+ code,
+ title
+FROM mk_sales_piy
+WHERE is_active = true
+ORDER BY code
+`
+
+// ======================================================
+// 🔗 ROLE → DEPARTMENT
+// ======================================================
+
+const DeleteRoleDepartments = `
+DELETE FROM dfrole_dprt
+WHERE dfrole_id = $1
+`
+
+const InsertRoleDepartment = `
+INSERT INTO dfrole_dprt (
+ dfrole_id,
+ dprt_id,
+ is_allowed
+)
+SELECT
+ $1,
+ d.id,
+ true
+FROM mk_dprt d
+WHERE d.code = $2
+ON CONFLICT DO NOTHING
+`
+
+// ======================================================
+// 🔗 ROLE → PIYASA
+// ======================================================
+
+const DeleteRolePiyasalar = `
+DELETE FROM dfrole_piyasa
+WHERE dfrole_id = $1
+`
+
+const InsertRolePiyasa = `
+INSERT INTO dfrole_piyasa (
+ dfrole_id,
+ piyasa_code,
+ is_allowed
+)
+VALUES ($1,$2,true)
+ON CONFLICT DO NOTHING
+`
+
+// ======================================================
+// 👤 USER → ROLE
+// ======================================================
+
+const DeleteUserRoles = `
+DELETE FROM dfrole_usr
+WHERE dfusr_id = $1
+`
+
+// ======================================================
+// 🏢 DEPARTMENTS (ADMIN PERM PAGE SELECT) -> { code, title }
+// ======================================================
+const GetDepartmentsForPermissionSelect = `
+SELECT
+ code AS id,
+ title AS title
+FROM mk_dprt
+WHERE is_active = true
+ORDER BY title
+`
+
+// ======================================================
+// 🔐 ROLES (ADMIN PERM PAGE SELECT) -> { id, title }
+// ======================================================
+const GetRolesForPermissionSelect = `
+SELECT
+ id::text AS id,
+ COALESCE(NULLIF(title,''), code) AS title
+FROM dfrole
+ORDER BY COALESCE(NULLIF(title,''), code)
+`
+const GetUserLookupForPermission = `
+SELECT
+ id::text AS id,
+ username || ' - ' || full_name AS title
+FROM mk_dfusr
+WHERE is_active = true
+ORDER BY username
+`
diff --git a/svc/queries/user_list.go b/svc/queries/user_list.go
new file mode 100644
index 0000000..809bbe4
--- /dev/null
+++ b/svc/queries/user_list.go
@@ -0,0 +1,106 @@
+package queries
+
+import (
+ "database/sql"
+ "fmt"
+)
+
+// ========================================================
+// 📌 GetUserList — POSTGRES FINAL (mk_dfusr)
+// ========================================================
+func GetUserList(db *sql.DB, search string) (*sql.Rows, error) {
+
+ baseQuery := `
+SELECT
+ u.id AS id, -- 1
+ u.username AS code, -- 2 (frontend uyumu için alias)
+ u.is_active AS is_active, -- 3
+
+ -- Nebim eşleşmesi (opsiyonel)
+ n.username AS nebim_username, -- 4
+ n.user_group_code AS user_group_code, -- 5
+
+ -- Roller
+ COALESCE(string_agg(DISTINCT r.code, ', '), '') AS role_names, -- 6
+
+ -- Departmanlar
+ COALESCE(string_agg(DISTINCT d.title, ', '), '') AS department_names, -- 7
+
+ -- Piyasalar (ÇOKLU)
+ COALESCE(string_agg(DISTINCT p.title, ', '), '') AS piyasa_names -- 8
+
+FROM mk_dfusr u
+
+-- ==========================
+-- 🔗 Nebim eşleşmesi
+-- ==========================
+LEFT JOIN dfusr_nebim_user un
+ ON un.dfusr_id = u.id
+
+LEFT JOIN mk_nebim_user n
+ ON n.id = un.mk_nebim_user_id
+
+-- ==========================
+-- 🔐 Roller
+-- ==========================
+LEFT JOIN dfrole_usr uru
+ ON uru.dfusr_id = u.id
+
+LEFT JOIN dfrole r
+ ON r.id = uru.dfrole_id
+
+-- ==========================
+-- 🏢 Departmanlar
+-- ==========================
+LEFT JOIN dfusr_dprt ud
+ ON ud.dfusr_id = u.id
+
+LEFT JOIN mk_dprt d
+ ON d.id = ud.dprt_id
+
+-- ==========================
+-- 🌍 Piyasalar
+-- ==========================
+LEFT JOIN dfusr_piyasa up
+ ON up.dfusr_id = u.id
+
+LEFT JOIN mk_sales_piy p
+ ON p.code = up.piyasa_code
+
+WHERE 1 = 1
+`
+
+ // 🔍 SEARCH FILTER
+ if search != "" {
+ baseQuery += `
+ AND (
+ u.username ILIKE $1
+ OR u.email ILIKE $1
+ OR n.username ILIKE $1
+ OR r.code ILIKE $1
+ OR d.title ILIKE $1
+ OR p.title ILIKE $1
+ )
+`
+ }
+
+ // 📌 GROUP + ORDER
+ baseQuery += `
+GROUP BY
+ u.id,
+ u.username,
+ u.is_active,
+ n.username,
+ n.user_group_code
+
+ORDER BY u.username
+`
+
+ // ▶ EXECUTE QUERY
+ if search != "" {
+ searchLike := fmt.Sprintf("%%%s%%", search)
+ return db.Query(baseQuery, searchLike)
+ }
+
+ return db.Query(baseQuery)
+}
diff --git a/svc/queries/user_password.go b/svc/queries/user_password.go
new file mode 100644
index 0000000..1526a07
--- /dev/null
+++ b/svc/queries/user_password.go
@@ -0,0 +1,41 @@
+package queries
+
+// ======================================================
+// 🔐 UPDATE USER PASSWORD (RESET / CHANGE)
+// ======================================================
+const UpdateUserPassword = `
+UPDATE dfusr
+SET
+ upass = $2,
+ last_updated_date = NOW()
+WHERE id = $1
+ AND is_active = true
+`
+
+// Reset token yaz
+const SetUserResetToken = `
+UPDATE dfusr
+SET
+ fpass = $2,
+ fpass_id = NOW()
+WHERE id = $1
+ AND is_active = true
+`
+
+// Reset token kontrol
+const CheckUserResetToken = `
+SELECT id
+FROM dfusr
+WHERE
+ fpass = $1
+ AND is_active = true
+`
+
+// Reset sonrası temizle
+const ClearUserResetToken = `
+UPDATE dfusr
+SET
+ fpass = NULL,
+ fpass_id = NULL
+WHERE id = $1
+`
diff --git a/svc/repository/activitylog_repository.go b/svc/repository/activitylog_repository.go
new file mode 100644
index 0000000..73f2365
--- /dev/null
+++ b/svc/repository/activitylog_repository.go
@@ -0,0 +1,286 @@
+package repository
+
+import (
+ "context"
+ "database/sql"
+ "fmt"
+ "strings"
+ "time"
+)
+
+type ActivityLogRow struct {
+ CreatedAt time.Time `json:"created_at"`
+ RequestStartedAt *time.Time `json:"request_started_at,omitempty"`
+ RequestFinishedAt *time.Time `json:"request_finished_at,omitempty"`
+ DurationMs *int `json:"duration_ms,omitempty"`
+ HttpStatus *int `json:"http_status,omitempty"`
+
+ Username string `json:"username"`
+ RoleCode string `json:"role_code"`
+
+ ActionCategory string `json:"action_category"`
+ ActionType string `json:"action_type"`
+ ActionTarget string `json:"action_target"`
+ Description string `json:"description"`
+
+ // ✅ TARGET USER
+ TargetDfUsrID int64 `json:"target_dfusr_id"`
+ TargetUsername string `json:"target_username"`
+
+ // ✅ DIFF
+ ChangeBefore string `json:"change_before"`
+ ChangeAfter string `json:"change_after"`
+
+ IpAddress string `json:"ip_address"`
+ IsSuccess bool `json:"is_success"`
+ ErrorMsg string `json:"error_message"`
+ UserAgent string `json:"user_agent"`
+ SessionID string `json:"session_id"`
+
+ // audit ids
+ UserUUID string `json:"user_id"`
+ DfUsrID int64 `json:"dfusr_id"`
+
+ Email string `json:"email"`
+ IsActive bool `json:"is_active"`
+}
+
+type ActivityLogQuery struct {
+ Username string
+ RoleCode string
+ ActionCategory string
+ ActionType string
+ ActionTarget string
+ Success *bool
+
+ StatusMin *int
+ StatusMax *int
+
+ DateFrom *time.Time
+ DateTo *time.Time
+
+ Page int
+ Limit int
+}
+
+type ActivityLogResult struct {
+ Total int64 `json:"total"`
+ Items []ActivityLogRow `json:"items"`
+}
+
+func ListActivityLogs(
+ ctx context.Context,
+ db *sql.DB,
+ q ActivityLogQuery,
+) (ActivityLogResult, error) {
+
+ // ---------- defaults ----------
+ if q.Page <= 0 {
+ q.Page = 1
+ }
+ // limit <=0 → unlimited
+ if q.Limit < 0 {
+ q.Limit = 50
+ }
+ if q.Limit > 200 {
+ q.Limit = 200
+ }
+
+ offset := 0
+ if q.Limit > 0 {
+ offset = (q.Page - 1) * q.Limit
+ }
+
+ where := []string{}
+ args := []interface{}{}
+
+ add := func(cond string, val interface{}) {
+ where = append(where, cond)
+ args = append(args, val)
+ }
+
+ // ---------- filters ----------
+ if strings.TrimSpace(q.Username) != "" {
+ add(fmt.Sprintf("l.username ILIKE $%d", len(args)+1),
+ "%"+strings.TrimSpace(q.Username)+"%")
+ }
+ if strings.TrimSpace(q.RoleCode) != "" {
+ add(fmt.Sprintf("r.code = $%d", len(args)+1),
+ strings.TrimSpace(q.RoleCode))
+ }
+ if strings.TrimSpace(q.ActionCategory) != "" {
+ add(fmt.Sprintf("l.action_category = $%d", len(args)+1),
+ strings.TrimSpace(q.ActionCategory))
+ }
+ if strings.TrimSpace(q.ActionType) != "" {
+ add(fmt.Sprintf("l.action_type = $%d", len(args)+1),
+ strings.TrimSpace(q.ActionType))
+ }
+ if strings.TrimSpace(q.ActionTarget) != "" {
+ add(fmt.Sprintf("l.action_target ILIKE $%d", len(args)+1),
+ "%"+strings.TrimSpace(q.ActionTarget)+"%")
+ }
+ if q.Success != nil {
+ add(fmt.Sprintf("l.is_success = $%d", len(args)+1), *q.Success)
+ }
+ if q.StatusMin != nil {
+ add(fmt.Sprintf("l.http_status >= $%d", len(args)+1), *q.StatusMin)
+ }
+ if q.StatusMax != nil {
+ add(fmt.Sprintf("l.http_status <= $%d", len(args)+1), *q.StatusMax)
+ }
+ if q.DateFrom != nil {
+ add(fmt.Sprintf("l.created_at >= $%d", len(args)+1), *q.DateFrom)
+ }
+ if q.DateTo != nil {
+ add(fmt.Sprintf("l.created_at < $%d", len(args)+1),
+ q.DateTo.Add(24*time.Hour))
+ }
+
+ whereSQL := ""
+ if len(where) > 0 {
+ whereSQL = "WHERE " + strings.Join(where, " AND ")
+ }
+
+ // ---------- COUNT ----------
+ countSQL := fmt.Sprintf(`
+ SELECT count(*)
+ FROM mk_user_activity_log l
+ LEFT JOIN mk_dfusr u ON u.id = l.dfusr_id
+ LEFT JOIN dfrole_usr ru ON ru.dfusr_id = u.id
+ LEFT JOIN dfrole r ON r.id = ru.dfrole_id
+ %s
+ `, whereSQL)
+
+ var total int64
+ if err := db.QueryRowContext(ctx, countSQL, args...).Scan(&total); err != nil {
+ return ActivityLogResult{}, err
+ }
+
+ // ---------- LIST ----------
+ listArgs := append([]interface{}{}, args...)
+
+ listSQL := fmt.Sprintf(`
+SELECT
+ l.created_at,
+ l.request_started_at,
+ l.request_finished_at,
+ l.duration_ms,
+ l.http_status,
+
+ COALESCE(u.username, l.username, '') AS username,
+ COALESCE(r.code,''),
+
+ COALESCE(l.action_category,''),
+ COALESCE(l.action_type,''),
+ COALESCE(l.action_target,''),
+ COALESCE(l.description,''),
+
+ COALESCE(l.target_dfusr_id, 0),
+ COALESCE(l.target_username, ''),
+ COALESCE(l.change_before::text, ''),
+ COALESCE(l.change_after::text, ''),
+
+ COALESCE(l.ip_address,''),
+ COALESCE(l.is_success,false),
+ COALESCE(l.error_message,''),
+ COALESCE(l.user_agent,''),
+ COALESCE(l.session_id,''),
+
+ COALESCE(l.user_id::text,''),
+ COALESCE(l.dfusr_id,0),
+
+ COALESCE(u.email,''),
+ COALESCE(u.is_active,false)
+
+FROM mk_user_activity_log l
+LEFT JOIN mk_dfusr u ON u.id = l.dfusr_id
+LEFT JOIN dfrole_usr ru ON ru.dfusr_id = u.id
+LEFT JOIN dfrole r ON r.id = ru.dfrole_id
+%s
+ORDER BY l.created_at DESC
+`, whereSQL)
+ // LIMIT sadece istenirse
+ if q.Limit > 0 {
+
+ listSQL += fmt.Sprintf(
+ " LIMIT $%d OFFSET $%d",
+ len(listArgs)+1,
+ len(listArgs)+2,
+ )
+
+ listArgs = append(listArgs, q.Limit, offset)
+ }
+
+ rows, err := db.QueryContext(ctx, listSQL, listArgs...)
+ if err != nil {
+ return ActivityLogResult{}, err
+ }
+ defer rows.Close()
+
+ items := []ActivityLogRow{}
+
+ for rows.Next() {
+ var it ActivityLogRow
+ var started, finished sql.NullTime
+ var dur, status sql.NullInt64
+
+ if err := rows.Scan(
+ &it.CreatedAt,
+ &started,
+ &finished,
+ &dur,
+ &status,
+
+ &it.Username,
+ &it.RoleCode,
+
+ &it.ActionCategory,
+ &it.ActionType,
+ &it.ActionTarget,
+ &it.Description,
+
+ // ✅ NEW
+ &it.TargetDfUsrID,
+ &it.TargetUsername,
+ &it.ChangeBefore,
+ &it.ChangeAfter,
+
+ &it.IpAddress,
+ &it.IsSuccess,
+ &it.ErrorMsg,
+ &it.UserAgent,
+ &it.SessionID,
+
+ &it.UserUUID,
+ &it.DfUsrID,
+
+ &it.Email,
+ &it.IsActive,
+ ); err != nil {
+ return ActivityLogResult{}, err
+ }
+
+ if started.Valid {
+ it.RequestStartedAt = &started.Time
+ }
+ if finished.Valid {
+ it.RequestFinishedAt = &finished.Time
+ }
+ if dur.Valid {
+ v := int(dur.Int64)
+ it.DurationMs = &v
+ }
+ if status.Valid {
+ v := int(status.Int64)
+ it.HttpStatus = &v
+ }
+
+ items = append(items, it)
+ }
+
+ return ActivityLogResult{
+ Total: total,
+ Items: items,
+ }, nil
+}
diff --git a/svc/repository/mk_user_repository.go b/svc/repository/mk_user_repository.go
new file mode 100644
index 0000000..7eb0bce
--- /dev/null
+++ b/svc/repository/mk_user_repository.go
@@ -0,0 +1,352 @@
+package repository
+
+import (
+ "bssapp-backend/models"
+ "database/sql"
+ "errors"
+ "strings"
+ "time"
+
+ "github.com/lib/pq"
+)
+
+var ErrMkUserNotFound = errors.New("mk_user not found")
+
+type MkUserRepository struct {
+ DB *sql.DB
+}
+
+func NewMkUserRepository(db *sql.DB) *MkUserRepository {
+ return &MkUserRepository{DB: db}
+}
+
+// -------------------------------------------------------
+// 🔍 GET BY USERNAME
+// -------------------------------------------------------
+func (r *MkUserRepository) GetByUsername(username string) (*models.MkUser, error) {
+ username = strings.TrimSpace(username)
+
+ var u models.MkUser
+
+ err := r.DB.QueryRow(`SELECT
+ u.id,
+ u.username,
+ COALESCE(u.email,'') AS email,
+ u.is_active,
+ COALESCE(u.password_hash,'') AS password_hash,
+ u.force_password_change,
+
+ COALESCE(r.id, 0) AS role_id,
+ COALESCE(r.code, '') AS role_code,
+
+ -- ✅ DEPARTMENTS
+ COALESCE(
+ array_agg(DISTINCT d.code)
+ FILTER (WHERE d.code IS NOT NULL),
+ '{}'
+ ) AS department_codes,
+
+ u.password_updated_at,
+ u.created_at,
+ u.updated_at,
+ u.last_login_at
+
+FROM mk_dfusr u
+
+LEFT JOIN dfrole_usr ru
+ ON ru.dfusr_id = u.id
+
+LEFT JOIN dfrole r
+ ON r.id = ru.dfrole_id
+
+-- ✅ USER → DEPT
+LEFT JOIN dfusr_dprt ud
+ ON ud.dfusr_id = u.id
+ AND ud.is_active = true
+
+LEFT JOIN mk_dprt d
+ ON d.id = ud.dprt_id
+
+WHERE LOWER(u.username) = LOWER($1)
+
+GROUP BY
+ u.id, r.id
+
+LIMIT 1
+`, username).Scan(
+ &u.ID,
+ &u.Username,
+ &u.Email,
+ &u.IsActive,
+ &u.PasswordHash,
+ &u.ForcePasswordChange,
+
+ &u.RoleID,
+ &u.RoleCode,
+
+ pq.Array(&u.DepartmentCodes), // ✅
+
+ &u.PasswordUpdatedAt,
+
+ &u.CreatedAt,
+ &u.UpdatedAt,
+ &u.LastLoginAt,
+ )
+
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, ErrMkUserNotFound
+ }
+ return nil, err
+ }
+
+ return &u, nil
+}
+
+// -------------------------------------------------------
+// 🔍 GET BY ID
+// -------------------------------------------------------
+func (r *MkUserRepository) GetByID(id int64) (*models.MkUser, error) {
+ var u models.MkUser
+
+ err := r.DB.QueryRow(`
+ SELECT
+ u.id,
+ u.username,
+ COALESCE(u.email,'') AS email,
+ u.is_active,
+ COALESCE(u.password_hash,'') AS password_hash,
+ u.force_password_change,
+
+ COALESCE(r.id, 0) AS role_id,
+ COALESCE(r.code, '') AS role_code,
+
+ -- ✅ DEPARTMENTS
+ COALESCE(
+ array_agg(DISTINCT d.code)
+ FILTER (WHERE d.code IS NOT NULL),
+ '{}'
+ ) AS department_codes,
+
+ u.password_updated_at,
+ u.created_at,
+ u.updated_at,
+ u.last_login_at
+
+FROM mk_dfusr u
+
+LEFT JOIN dfrole_usr ru
+ ON ru.dfusr_id = u.id
+
+LEFT JOIN dfrole r
+ ON r.id = ru.dfrole_id
+
+-- ✅ USER → DEPT
+LEFT JOIN dfusr_dprt ud
+ ON ud.dfusr_id = u.id
+ AND ud.is_active = true
+
+LEFT JOIN mk_dprt d
+ ON d.id = ud.dprt_id
+
+WHERE LOWER(u.username) = LOWER($1)
+
+GROUP BY
+ u.id, r.id
+
+LIMIT 1
+`, id).Scan(
+ &u.ID,
+ &u.Username,
+ &u.Email,
+ &u.IsActive,
+ &u.PasswordHash,
+ &u.ForcePasswordChange,
+
+ &u.RoleID,
+ &u.RoleCode,
+ pq.Array(&u.DepartmentCodes), // ✅
+ &u.PasswordUpdatedAt,
+ &u.CreatedAt,
+ &u.UpdatedAt,
+ &u.LastLoginAt,
+ )
+
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, ErrMkUserNotFound
+ }
+ return nil, err
+ }
+
+ return &u, nil
+}
+
+// -------------------------------------------------------
+// 🔁 CREATE FROM LEGACY (dfusr → mk_dfusr)
+// - id = dfusr.id ✅
+// - role / v3 YOK
+// -------------------------------------------------------
+// -------------------------------------------------------
+// 🔁 CREATE FROM LEGACY (dfusr → mk_dfusr) FULL MIGRATION
+// -------------------------------------------------------
+func (r *MkUserRepository) CreateFromLegacy(
+ legacy *models.User,
+ passwordHash string,
+) (*models.MkUser, error) {
+
+ var u models.MkUser
+
+ err := r.DB.QueryRow(`
+ INSERT INTO mk_dfusr (
+ id,
+ username,
+ email,
+ full_name,
+ mobile,
+ address,
+ is_active,
+ password_hash,
+ force_password_change,
+ created_at,
+ updated_at
+ )
+ VALUES (
+ $1,$2,$3,$4,$5,$6,$7,$8,true,now(),now()
+ )
+ ON CONFLICT (id)
+ DO UPDATE SET
+ email = EXCLUDED.email,
+ full_name = EXCLUDED.full_name,
+ mobile = EXCLUDED.mobile,
+ address = EXCLUDED.address,
+ password_hash = EXCLUDED.password_hash,
+ force_password_change= true,
+ updated_at = now()
+ RETURNING
+ id,
+ username,
+ COALESCE(email,''),
+ is_active,
+ COALESCE(password_hash,''),
+ force_password_change,
+ password_updated_at,
+ created_at,
+ updated_at,
+ last_login_at
+ `,
+ legacy.ID,
+ legacy.Username,
+ legacy.Email,
+ legacy.FullName,
+ legacy.Mobile,
+ legacy.Address,
+ legacy.IsActive,
+ passwordHash,
+ ).Scan(
+ &u.ID,
+ &u.Username,
+ &u.Email,
+ &u.IsActive,
+ &u.PasswordHash,
+ &u.ForcePasswordChange,
+ &u.PasswordUpdatedAt,
+ &u.CreatedAt,
+ &u.UpdatedAt,
+ &u.LastLoginAt,
+ )
+
+ if err != nil {
+ return nil, err
+ }
+
+ return &u, nil
+}
+
+// -------------------------------------------------------
+// ➕ CREATE NEW USER (NON-LEGACY)
+// - id = sequence (>=1000)
+// -------------------------------------------------------
+func (r *MkUserRepository) CreateNewUser(
+ username string,
+ email string,
+ isActive bool,
+) (*models.MkUser, error) {
+
+ var u models.MkUser
+
+ err := r.DB.QueryRow(`
+ INSERT INTO mk_dfusr (
+ username,
+ email,
+ is_active,
+ force_password_change,
+ created_at,
+ updated_at
+ )
+ VALUES (
+ $1,$2,$3,true,now(),now()
+ )
+ RETURNING
+ id,
+ username,
+ COALESCE(email,'') AS email,
+ is_active,
+ COALESCE(password_hash,'') AS password_hash,
+ force_password_change,
+ password_updated_at,
+ created_at,
+ updated_at,
+ last_login_at
+ `,
+ strings.TrimSpace(username),
+ strings.TrimSpace(email),
+ isActive,
+ ).Scan(
+ &u.ID,
+ &u.Username,
+ &u.Email,
+ &u.IsActive,
+ &u.PasswordHash,
+ &u.ForcePasswordChange,
+ &u.PasswordUpdatedAt,
+ &u.CreatedAt,
+ &u.UpdatedAt,
+ &u.LastLoginAt,
+ )
+
+ if err != nil {
+ return nil, err
+ }
+
+ return &u, nil
+}
+
+// -------------------------------------------------------
+// 🕒 TOUCH LAST LOGIN
+// -------------------------------------------------------
+func (r *MkUserRepository) TouchLastLogin(userID int64) error {
+ _, err := r.DB.Exec(`
+ UPDATE mk_dfusr
+ SET last_login_at = $1,
+ updated_at = $1
+ WHERE id = $2
+ `, time.Now(), userID)
+ return err
+}
+
+// -------------------------------------------------------
+// 🔐 UPDATE PASSWORD
+// -------------------------------------------------------
+func (r *MkUserRepository) UpdatePassword(userID int64, newHash string) error {
+ _, err := r.DB.Exec(`
+ UPDATE mk_dfusr
+ SET
+ password_hash = $1,
+ force_password_change = false,
+ password_updated_at = NOW(),
+ updated_at = NOW()
+ WHERE id = $2
+ `, newHash, userID)
+ return err
+}
diff --git a/svc/repository/permission_repository.go b/svc/repository/permission_repository.go
new file mode 100644
index 0000000..9586e95
--- /dev/null
+++ b/svc/repository/permission_repository.go
@@ -0,0 +1,33 @@
+// repository/permission_repository.go
+package repository
+
+import "database/sql"
+
+type PermissionRepository struct {
+ DB *sql.DB
+}
+
+func NewPermissionRepository(db *sql.DB) *PermissionRepository {
+ return &PermissionRepository{DB: db}
+}
+
+func (r *PermissionRepository) GetPermissionsByRoleID(roleID int64) ([]string, error) {
+ rows, err := r.DB.Query(`
+ SELECT route
+ FROM mkdf_rol_per
+ WHERE role_id = $1 AND can_access = true
+ `, roleID)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ perms := []string{}
+ for rows.Next() {
+ var route string
+ if err := rows.Scan(&route); err == nil {
+ perms = append(perms, route)
+ }
+ }
+ return perms, nil
+}
diff --git a/svc/repository/refresh_token_repository.go b/svc/repository/refresh_token_repository.go
new file mode 100644
index 0000000..d090376
--- /dev/null
+++ b/svc/repository/refresh_token_repository.go
@@ -0,0 +1,80 @@
+package repository
+
+import (
+ "database/sql"
+ "errors"
+ "time"
+)
+
+var ErrRefreshTokenInvalid = errors.New("refresh token invalid")
+
+type RefreshTokenRepository struct {
+ DB *sql.DB
+}
+
+func NewRefreshTokenRepository(db *sql.DB) *RefreshTokenRepository {
+ return &RefreshTokenRepository{DB: db}
+}
+
+// Yeni refresh (HASH saklanır)
+func (r *RefreshTokenRepository) IssueRefreshToken(
+ mkUserID int64,
+ tokenHash string,
+ expiresAt time.Time,
+) error {
+ _, err := r.DB.Exec(`
+ INSERT INTO mk_refresh_tokens (mk_user_id, token_hash, expires_at)
+ VALUES ($1,$2,$3)
+ `, mkUserID, tokenHash, expiresAt)
+ return err
+}
+
+// Tek refresh’i revoke et (rotation / logout)
+func (r *RefreshTokenRepository) RevokeByHash(hash string) error {
+ _, err := r.DB.Exec(`
+ UPDATE mk_refresh_tokens
+ SET revoked_at = now()
+ WHERE token_hash = $1
+ AND revoked_at IS NULL
+ `, hash)
+ return err
+}
+
+// Kullanıcının tüm refresh’lerini revoke et (logout-all / password change)
+func (r *RefreshTokenRepository) RevokeAllForUser(mkUserID int64) error {
+ _, err := r.DB.Exec(`
+ UPDATE mk_refresh_tokens
+ SET revoked_at = now()
+ WHERE mk_user_id = $1
+ AND revoked_at IS NULL
+ `, mkUserID)
+ return err
+}
+
+// Geçerli refresh’i tüket (validate + rotate)
+func (r *RefreshTokenRepository) ConsumeValid(tokenHash string) (int64, error) {
+ var mkUserID int64
+ err := r.DB.QueryRow(`
+ SELECT mk_user_id
+ FROM mk_refresh_tokens
+ WHERE token_hash = $1
+ AND revoked_at IS NULL
+ AND expires_at > now()
+ `, tokenHash).Scan(&mkUserID)
+
+ if err != nil {
+ if err == sql.ErrNoRows {
+ return 0, ErrRefreshTokenInvalid
+ }
+ return 0, err
+ }
+
+ // tek kullanımlık: eskiyi revoke et
+ _, _ = r.DB.Exec(`
+ UPDATE mk_refresh_tokens
+ SET revoked_at = now()
+ WHERE token_hash = $1
+ `, tokenHash)
+
+ return mkUserID, nil
+}
diff --git a/svc/repository/user_repo.go b/svc/repository/user_repo.go
new file mode 100644
index 0000000..e76b874
--- /dev/null
+++ b/svc/repository/user_repo.go
@@ -0,0 +1,276 @@
+package repository
+
+import (
+ "bssapp-backend/models"
+ "database/sql"
+ "errors"
+ "fmt"
+ "log"
+ "strings"
+)
+
+// UserRepository → kullanıcı işlemleri için repository katmanı
+type UserRepository struct {
+ DB *sql.DB
+}
+
+// NewUserRepository → yeni bir UserRepository döner
+func NewUserRepository(db *sql.DB) *UserRepository {
+ return &UserRepository{DB: db}
+}
+
+// -------------------------------------------------------
+// 📋 GetUserList (User Management)
+// -------------------------------------------------------
+func (r *UserRepository) GetUserList() ([]models.User, error) {
+
+ query := `
+ SELECT
+ u.id,
+ u.code,
+ COALESCE(u.upass,'') as upass,
+ u.is_active,
+ COALESCE(u.email,''),
+ COALESCE(uru.dfrole_id, 0) AS role_id,
+ COALESCE(dr.code, '') AS role_code
+ FROM dfusr u
+ LEFT JOIN dfrole_usr uru ON u.id = uru.dfusr_id
+ LEFT JOIN dfrole dr ON dr.id = uru.dfrole_id
+ ORDER BY u.code
+ `
+
+ rows, err := r.DB.Query(query)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ var users []models.User
+ for rows.Next() {
+ var u models.User
+ if err := rows.Scan(
+ &u.ID,
+ &u.Username,
+ &u.Upass, // ✅ dfusr.upass
+ &u.IsActive,
+ &u.Email,
+ &u.RoleID,
+ &u.RoleCode,
+ ); err != nil {
+ return nil, err
+ }
+ users = append(users, u)
+ }
+ return users, nil
+}
+
+// -------------------------------------------------------
+// ➕ CreateUser (legacy dfusr insert) [kullanıyorsan kalsın]
+// -------------------------------------------------------
+func (r *UserRepository) CreateUser(u *models.User) (int, error) {
+
+ query := `
+ INSERT INTO dfusr (
+ code,
+ upass,
+ is_active,
+ email,
+ v3_username,
+ v3usergroup
+ )
+ VALUES ($1, $2, $3, $4, $5, $6)
+ RETURNING id
+ `
+
+ var newID int
+ err := r.DB.QueryRow(
+ query,
+ u.Username,
+ u.Upass, // ✅ upass
+ u.IsActive,
+ u.Email,
+ u.V3Username,
+ u.V3UserGroup,
+ ).Scan(&newID)
+
+ if err != nil {
+ return 0, err
+ }
+ return newID, nil
+}
+
+// -------------------------------------------------------
+// 🔍 GetUserByUsername (legacy) [login için şart değil ama düzelttim]
+// -------------------------------------------------------
+func (r *UserRepository) GetUserByUsername(username string) (*models.User, error) {
+
+ clean := strings.TrimSpace(username)
+
+ query := `
+ SELECT
+ u.id,
+ u.code,
+ COALESCE(u.upass,'') as upass,
+ u.is_active,
+ COALESCE(u.email,''),
+
+ COALESCE(uru.dfrole_id,0) AS role_id,
+ COALESCE(dr.code,'') AS role_code,
+
+ COALESCE(u.v3_username,'') as v3_username,
+ COALESCE(u.v3_usergroup,0) as v3_usergroup,
+ COALESCE(u.force_password_change,false) as force_password_change
+
+ FROM dfusr u
+
+ LEFT JOIN dfrole_usr uru
+ ON uru.dfusr_id = u.id
+
+ LEFT JOIN dfrole dr
+ ON dr.id = uru.dfrole_id
+
+ WHERE u.is_active = true
+ AND (
+ LOWER(u.code) = LOWER($1)
+ OR LOWER(u.email) = LOWER($1)
+ )
+
+ LIMIT 1;
+ `
+
+ row := r.DB.QueryRow(query, clean)
+
+ var user models.User
+
+ err := row.Scan(
+ &user.ID,
+ &user.Username,
+ &user.Upass,
+ &user.IsActive,
+ &user.Email,
+
+ &user.RoleID,
+ &user.RoleCode,
+
+ &user.V3Username,
+ &user.V3UserGroup,
+ &user.ForcePasswordChange,
+ )
+
+ if err != nil {
+
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, errors.New("kullanıcı bulunamadı")
+ }
+
+ return nil, err
+ }
+
+ return &user, nil
+}
+
+// -------------------------------------------------------
+// 🧪 DebugListUsers
+// -------------------------------------------------------
+func (r *UserRepository) DebugListUsers() {
+ rows, err := r.DB.Query(`SELECT id, code FROM dfusr ORDER BY id LIMIT 10`)
+ if err != nil {
+ fmt.Println("❌ [DEBUG] Listeleme hatası:", err)
+ return
+ }
+ defer rows.Close()
+
+ fmt.Println("📋 [DEBUG] İlk 10 kullanıcı:")
+ for rows.Next() {
+ var id int
+ var code string
+ if err := rows.Scan(&id, &code); err == nil {
+ fmt.Printf(" - %d : %s\n", id, code)
+ }
+ }
+}
+
+// -------------------------------------------------------
+// 🔁 SetUserRoles
+// -------------------------------------------------------
+func (r *UserRepository) SetUserRoles(userID int, roleIDs []int) error {
+
+ tx, err := r.DB.Begin()
+ if err != nil {
+ return err
+ }
+ defer tx.Rollback()
+
+ if _, err := tx.Exec(`DELETE FROM dfrole_usr WHERE dfusr_id = $1`, userID); err != nil {
+ return err
+ }
+
+ for _, roleID := range roleIDs {
+ if _, err := tx.Exec(
+ `INSERT INTO dfrole_usr (dfrole_id, dfusr_id) VALUES ($1, $2)`,
+ roleID, userID,
+ ); err != nil {
+ return err
+ }
+ }
+
+ return tx.Commit()
+}
+
+// -------------------------------------------------------
+// 🔐 GetLegacyUserForLogin ✅ LOGIN İÇİN TEK DOĞRU FONKSİYON
+// -------------------------------------------------------
+func (r *UserRepository) GetLegacyUserForLogin(login string) (*models.User, error) {
+ log.Println("🟡 LEGACY LOGIN QUERY HIT:", login)
+
+ login = strings.TrimSpace(login)
+
+ var u models.User
+
+ err := r.DB.QueryRow(`
+ SELECT
+ u.id,
+ u.code,
+ COALESCE(u.upass,'') as upass,
+ u.is_active,
+ COALESCE(u.email,''),
+ COALESCE(u.dfrole_id,0) as role_id,
+ COALESCE(dr.code,'') as role_code,
+ COALESCE(u.force_password_change,false)
+ FROM dfusr u
+ LEFT JOIN dfrole dr ON dr.id = u.dfrole_id
+ WHERE u.is_active = true
+ AND (
+ LOWER(u.code) = LOWER($1)
+ OR LOWER(u.email) = LOWER($1)
+ )
+ LIMIT 1
+ `, login).Scan(
+ &u.ID,
+ &u.Username,
+ &u.Upass,
+ &u.IsActive,
+ &u.Email,
+ &u.RoleID,
+ &u.RoleCode,
+ &u.ForcePasswordChange,
+ )
+
+ if err != nil {
+ log.Printf("❌ LEGACY SCAN ERROR: %v", err)
+ return nil, err
+ }
+
+ prefix := u.Upass
+ if len(prefix) > 4 {
+ prefix = prefix[:4]
+ }
+
+ log.Printf(
+ "🧪 LEGACY UPASS OK len=%d prefix=%s",
+ len(u.Upass),
+ prefix,
+ )
+
+ return &u, nil
+}
diff --git a/svc/repository/user_role_repo.go b/svc/repository/user_role_repo.go
new file mode 100644
index 0000000..b7de144
--- /dev/null
+++ b/svc/repository/user_role_repo.go
@@ -0,0 +1,32 @@
+package repository
+
+import "database/sql"
+
+type UserRole struct {
+ RoleID int
+ RoleCode string
+ IsSystem bool
+}
+
+// repository/user_roles.go
+// repository/user_roles.go
+func GetUserRolesByUserID(db *sql.DB, userID int64) ([]int, error) {
+ rows, err := db.Query(`
+ SELECT role_id
+ FROM dfrole_usr
+ WHERE mk_dfusr_id = $1
+ `, userID)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ var roles []int
+ for rows.Next() {
+ var rid int
+ if err := rows.Scan(&rid); err == nil {
+ roles = append(roles, rid)
+ }
+ }
+ return roles, nil
+}
diff --git a/svc/routes/account.go b/svc/routes/account.go
new file mode 100644
index 0000000..363cfd1
--- /dev/null
+++ b/svc/routes/account.go
@@ -0,0 +1,39 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/internal/authz"
+ "bssapp-backend/queries"
+ "encoding/json"
+ "log"
+ "net/http"
+)
+
+func GetAccountsHandler(w http.ResponseWriter, r *http.Request) {
+
+ // ✅ AUTH (sadece login kontrolü)
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ // ✅ DEBUG (scope kontrol için faydalı)
+ log.Println("🔍 PIYASA CTX:", authz.GetPiyasaCodesFromCtx(r.Context()))
+
+ // ✅ QUERY
+ accounts, err := queries.GetAccounts(r.Context())
+ if err != nil {
+ log.Println("❌ GetAccounts error:", err)
+ http.Error(w, "db error", http.StatusInternalServerError)
+ return
+ }
+
+ // ✅ RESPONSE
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ if err := json.NewEncoder(w).Encode(accounts); err != nil {
+ log.Println("❌ JSON encode error:", err)
+ return
+ }
+}
diff --git a/svc/routes/activitylogs.go b/svc/routes/activitylogs.go
new file mode 100644
index 0000000..df966e6
--- /dev/null
+++ b/svc/routes/activitylogs.go
@@ -0,0 +1,126 @@
+package routes
+
+import (
+ "bssapp-backend/repository"
+ "database/sql"
+ "encoding/json"
+ "net/http"
+ "strconv"
+ "time"
+)
+
+type ActivityLogQuery struct {
+ Username string
+ RoleCode string
+ ActionCategory string
+ ActionType string
+ ActionTarget string
+ Success *bool
+
+ DateFrom *time.Time
+ DateTo *time.Time
+
+ Page int
+ Limit int
+}
+
+func AdminActivityLogsHandler(pgDB *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ q := repository.ActivityLogQuery{}
+
+ // pagination
+ if v := r.URL.Query().Get("page"); v != "" {
+ if i, err := strconv.Atoi(v); err == nil {
+ q.Page = i
+ }
+ }
+
+ if v := r.URL.Query().Get("limit"); v != "" {
+ if i, err := strconv.Atoi(v); err == nil {
+ q.Limit = i
+ }
+ } else {
+ // limit yoksa unlimited
+ q.Limit = 0
+ }
+
+ // filters
+ q.Username = r.URL.Query().Get("username")
+ q.RoleCode = r.URL.Query().Get("role_code")
+ q.ActionCategory = r.URL.Query().Get("action_category")
+ q.ActionType = r.URL.Query().Get("action_type")
+ q.ActionTarget = r.URL.Query().Get("action_target")
+
+ // success
+ if v := r.URL.Query().Get("success"); v != "" {
+ if v == "true" || v == "1" {
+ b := true
+ q.Success = &b
+ } else if v == "false" || v == "0" {
+ b := false
+ q.Success = &b
+ }
+ }
+
+ // status range
+ if v := r.URL.Query().Get("status_min"); v != "" {
+ if i, err := strconv.Atoi(v); err == nil {
+ q.StatusMin = &i
+ }
+ }
+ if v := r.URL.Query().Get("status_max"); v != "" {
+ if i, err := strconv.Atoi(v); err == nil {
+ q.StatusMax = &i
+ }
+ }
+
+ // date range (ISO: 2026-01-03T00:00:00Z veya 2026-01-03)
+ parseDate := func(s string) (*time.Time, error) {
+ if s == "" {
+ return nil, nil
+ }
+ // önce RFC3339 dene
+ if t, err := time.Parse(time.RFC3339, s); err == nil {
+ return &t, nil
+ }
+ // sonra sadece tarih
+ if t, err := time.Parse("2006-01-02", s); err == nil {
+ tt := t
+ return &tt, nil
+ }
+ return nil, http.ErrNotSupported
+ }
+
+ if v := r.URL.Query().Get("date_from"); v != "" {
+ t, err := parseDate(v)
+ if err != nil {
+ http.Error(w, "date_from format hatalı", http.StatusBadRequest)
+ return
+ }
+ q.DateFrom = t
+ }
+ if v := r.URL.Query().Get("date_to"); v != "" {
+ t, err := parseDate(v)
+ if err != nil {
+ http.Error(w, "date_to format hatalı", http.StatusBadRequest)
+ return
+ }
+ q.DateTo = t
+ }
+
+ res, err := repository.ListActivityLogs(r.Context(), pgDB, q)
+ if err != nil {
+ http.Error(w, "Loglar alınamadı", http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ _ = json.NewEncoder(w).Encode(map[string]interface{}{
+ "page": q.Page,
+ "limit": q.Limit,
+ "total": res.Total,
+ "items": res.Items,
+ })
+ }
+}
diff --git a/svc/routes/admin_piyasa.go b/svc/routes/admin_piyasa.go
new file mode 100644
index 0000000..c4de4aa
--- /dev/null
+++ b/svc/routes/admin_piyasa.go
@@ -0,0 +1,61 @@
+package routes
+
+import (
+ "log"
+ "net/http"
+ "strconv"
+
+ "bssapp-backend/auth"
+ "bssapp-backend/internal/authz"
+ "bssapp-backend/middlewares"
+
+ "github.com/gorilla/mux"
+)
+
+// =====================================================
+// 🔐 ADMIN — PIYASA CACHE SYNC
+// =====================================================
+// POST /api/admin/users/{id}/piyasa-sync
+func AdminSyncUserPiyasaHandler(w http.ResponseWriter, r *http.Request) {
+
+ // --------------------------------------------------
+ // 🔐 AUTH
+ // --------------------------------------------------
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", 401)
+ return
+ }
+
+ // --------------------------------------------------
+ // 🆔 USER ID PARAM
+ // --------------------------------------------------
+ vars := mux.Vars(r)
+
+ idStr := vars["id"]
+
+ targetID, err := strconv.ParseInt(idStr, 10, 64)
+ if err != nil {
+ http.Error(w, "invalid user id", http.StatusBadRequest)
+ return
+ }
+
+ // --------------------------------------------------
+ // 🧹 CACHE CLEAR
+ // --------------------------------------------------
+ authz.ClearPiyasaCache(int(targetID))
+ middlewares.ClearAuthzScopeCacheForUser(targetID)
+
+ log.Printf(
+ "🔄 ADMIN PIYASA SYNC | admin=%d target=%d",
+ claims.ID,
+ targetID,
+ )
+
+ // --------------------------------------------------
+ // ✅ OK
+ // --------------------------------------------------
+ w.Header().Set("Content-Type", "application/json")
+
+ w.Write([]byte(`{"status":"ok"}`))
+}
diff --git a/svc/routes/admin_reset_password.go b/svc/routes/admin_reset_password.go
new file mode 100644
index 0000000..543de20
--- /dev/null
+++ b/svc/routes/admin_reset_password.go
@@ -0,0 +1,104 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/internal/auditlog"
+ "bssapp-backend/repository"
+ "database/sql"
+ "encoding/json"
+ "github.com/gorilla/mux"
+ "golang.org/x/crypto/bcrypt"
+ "net/http"
+ "strconv"
+)
+
+type AdminResetPasswordRequest struct {
+ Password string `json:"password"` // opsiyonel
+}
+
+func AdminResetPasswordHandler(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+ // ---------------------------------------------------
+ // 1️⃣ USER ID
+ // ---------------------------------------------------
+ idStr := mux.Vars(r)["id"]
+ userID, err := strconv.ParseInt(idStr, 10, 64)
+ if err != nil || userID <= 0 {
+ http.Error(w, "invalid user id", http.StatusBadRequest)
+ return
+ }
+
+ // ---------------------------------------------------
+ // 2️⃣ PAYLOAD (opsiyonel)
+ // ---------------------------------------------------
+ var req AdminResetPasswordRequest
+ _ = json.NewDecoder(r.Body).Decode(&req)
+
+ // parola verilmediyse backend üretir
+ password := req.Password
+ if password == "" {
+ password = "Temp123!" // 👉 istersek random generator ekleriz
+ }
+
+ // ---------------------------------------------------
+ // 3️⃣ HASH
+ // ---------------------------------------------------
+ hash, err := bcrypt.GenerateFromPassword(
+ []byte(password),
+ bcrypt.DefaultCost,
+ )
+ if err != nil {
+ http.Error(w, "password hash error", http.StatusInternalServerError)
+ return
+ }
+
+ // ---------------------------------------------------
+ // 4️⃣ UPDATE mk_dfusr
+ // ---------------------------------------------------
+ _, err = db.Exec(`
+ UPDATE mk_dfusr
+ SET
+ password_hash = $1,
+ force_password_change = true,
+ password_updated_at = NOW(),
+ updated_at = NOW()
+ WHERE id = $2
+ `, string(hash), userID)
+
+ if err != nil {
+ http.Error(w, "password reset failed", http.StatusInternalServerError)
+ return
+ }
+
+ // ---------------------------------------------------
+ // 5️⃣ REFRESH TOKEN REVOKE
+ // ---------------------------------------------------
+ _ = repository.
+ NewRefreshTokenRepository(db).
+ RevokeAllForUser(userID)
+
+ // ---------------------------------------------------
+ // 6️⃣ AUDIT
+ // ---------------------------------------------------
+ auditlog.Write(auditlog.ActivityLog{
+ ActionType: "ADMIN_PASSWORD_RESET",
+ ActionCategory: "security",
+ ActionTarget: "mk_dfusr.id",
+ IsSuccess: true,
+ })
+
+ // ---------------------------------------------------
+ // 7️⃣ RESPONSE
+ // ---------------------------------------------------
+ _ = json.NewEncoder(w).Encode(map[string]any{
+ "success": true,
+ })
+ }
+}
diff --git a/svc/routes/audit_helper.go b/svc/routes/audit_helper.go
new file mode 100644
index 0000000..402d107
--- /dev/null
+++ b/svc/routes/audit_helper.go
@@ -0,0 +1,60 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/ctxkeys"
+ "bssapp-backend/internal/auditlog"
+ "bssapp-backend/permissions"
+ "bssapp-backend/repository"
+ "database/sql"
+ "encoding/json"
+)
+
+// auditLogFromRequest
+// routes içinden çağrılır
+// auditLogFromRequest
+// routes içinden çağrılır
+func auditLogFromRequest(
+ ctx any,
+ db *sql.DB,
+ actionType string,
+ meta map[string]any,
+) {
+
+ al := auditlog.ActivityLog{
+ ActionType: actionType,
+ ActionCategory: "ADMIN",
+ IsSuccess: true,
+ }
+
+ // JWT → identity
+ if c, ok := ctx.(interface {
+ Value(any) any
+ }); ok {
+ if claims, ok := c.Value(ctxkeys.UserContextKey).(*auth.Claims); ok && claims != nil {
+
+ // ✅ TEK KİMLİK
+ al.DfUsrID = claims.ID
+ al.Username = claims.Username
+ al.RoleCode = claims.RoleCode
+
+ // 🔗 MULTI ROLE → ADMIN CHECK
+ roles, err := repository.GetUserRolesByUserID(db, claims.ID)
+ if err == nil {
+ _, isAdmin := permissions.ResolveEffectiveRoles(roles)
+ if isAdmin {
+ al.RoleCode = "admin"
+ }
+ }
+ }
+ }
+
+ // meta → description
+ if meta != nil {
+ if b, err := json.Marshal(meta); err == nil {
+ al.Description = string(b)
+ }
+ }
+
+ auditlog.Write(al)
+}
diff --git a/svc/routes/auth_refresh.go b/svc/routes/auth_refresh.go
new file mode 100644
index 0000000..f5b177f
--- /dev/null
+++ b/svc/routes/auth_refresh.go
@@ -0,0 +1,92 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/internal/security"
+ "bssapp-backend/repository"
+ "database/sql"
+ "encoding/json"
+ "net/http"
+ "time"
+)
+
+func setRefreshCookie(w http.ResponseWriter, plain string, exp time.Time) {
+ http.SetCookie(w, &http.Cookie{
+ Name: "mk_refresh",
+ Value: plain,
+ Path: "/",
+ Expires: exp,
+ HttpOnly: true,
+ SameSite: http.SameSiteLaxMode,
+ Secure: false, // prod: true
+ })
+}
+
+func AuthRefreshHandler(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ // 1) refresh cookie
+ c, err := r.Cookie("mk_refresh")
+ if err != nil || c.Value == "" {
+ http.Error(w, "refresh token missing", http.StatusUnauthorized)
+ return
+ }
+
+ hash := security.HashRefreshToken(c.Value)
+ rtRepo := repository.NewRefreshTokenRepository(db)
+
+ // 2) validate + consume
+ mkUserID, err := rtRepo.ConsumeValid(hash)
+ if err != nil {
+ http.Error(w, "refresh token invalid", http.StatusUnauthorized)
+ return
+ }
+
+ // 3) rotate
+ newPlain, newHash, err := security.GenerateRefreshToken()
+ if err != nil {
+ http.Error(w, "refresh gen failed", http.StatusInternalServerError)
+ return
+ }
+ refreshExp := time.Now().Add(14 * 24 * time.Hour)
+ if err := rtRepo.IssueRefreshToken(mkUserID, newHash, refreshExp); err != nil {
+ http.Error(w, "refresh store failed", http.StatusInternalServerError)
+ return
+ }
+ setRefreshCookie(w, newPlain, refreshExp)
+
+ // 4) mk user reload
+ mkRepo := repository.NewMkUserRepository(db)
+ mkUser, err := mkRepo.GetByID(mkUserID)
+ if err != nil || !mkUser.IsActive {
+ http.Error(w, "user invalid", http.StatusUnauthorized)
+ return
+ }
+ if mkUser.ForcePasswordChange {
+ http.Error(w, "password change required", http.StatusForbidden)
+ return
+ }
+
+ // 5) new access token
+ claims := auth.BuildClaimsFromUser(mkUser, 15*time.Minute)
+
+ token, err := auth.GenerateToken(
+ claims,
+ mkUser.Username,
+ mkUser.ForcePasswordChange,
+ )
+ if err != nil {
+ http.Error(w, "access token gen failed", http.StatusInternalServerError)
+ return
+ }
+
+ // 6) response
+ _ = json.NewEncoder(w).Encode(map[string]any{
+ "success": true,
+ "token": token,
+ })
+
+ }
+}
diff --git a/svc/routes/customerlist.go b/svc/routes/customerlist.go
new file mode 100644
index 0000000..e61b8e8
--- /dev/null
+++ b/svc/routes/customerlist.go
@@ -0,0 +1,57 @@
+package routes
+
+import (
+ "encoding/json"
+ "log"
+ "net/http"
+
+ "bssapp-backend/auth"
+ "bssapp-backend/queries"
+)
+
+func GetCustomerListHandler(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ // --------------------------------------------------
+ // 🔐 CLAIMS (DEBUG / TRACE)
+ // --------------------------------------------------
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ log.Printf(
+ "📥 /api/customers | user=%d admin=%v",
+ claims.ID,
+ claims.IsAdmin(),
+ )
+
+ // --------------------------------------------------
+ // 🗄️ QUERY (CONTEXT TAŞIYOR)
+ // --------------------------------------------------
+ list, err := queries.GetCustomerList(r.Context())
+ if err != nil {
+ log.Println("❌ Customer list error:", err)
+ http.Error(w, "Veritabanı hatası", http.StatusInternalServerError)
+ return
+ }
+
+ // --------------------------------------------------
+ // 📤 JSON OUTPUT
+ // --------------------------------------------------
+ if err := json.NewEncoder(w).Encode(list); err != nil {
+ log.Printf("❌ JSON encode error: %v", err)
+ return
+ }
+
+ // --------------------------------------------------
+ // 📊 RESULT LOG
+ // --------------------------------------------------
+ log.Printf(
+ "✅ Customer list DONE | user=%d | resultCount=%d",
+ claims.ID,
+ len(list),
+ )
+}
diff --git a/svc/routes/first_password_change.go b/svc/routes/first_password_change.go
new file mode 100644
index 0000000..8530e65
--- /dev/null
+++ b/svc/routes/first_password_change.go
@@ -0,0 +1,169 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/internal/auditlog"
+ "bssapp-backend/internal/security"
+ "bssapp-backend/models"
+ "bssapp-backend/repository"
+ "database/sql"
+ "encoding/json"
+ "log"
+ "net/http"
+ "time"
+
+ "golang.org/x/crypto/bcrypt"
+)
+
+func FirstPasswordChangeHandler(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ // --------------------------------------------------
+ // 1️⃣ JWT CLAIMS
+ // --------------------------------------------------
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ // --------------------------------------------------
+ // 2️⃣ PAYLOAD
+ // --------------------------------------------------
+ var req struct {
+ CurrentPassword string `json:"current_password"`
+ NewPassword string `json:"new_password"`
+ }
+
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "invalid payload", http.StatusBadRequest)
+ return
+ }
+
+ if req.CurrentPassword == "" || req.NewPassword == "" {
+ http.Error(w, "password fields required", http.StatusUnprocessableEntity)
+ return
+ }
+
+ // --------------------------------------------------
+ // 3️⃣ LOAD USER (mk_dfusr)
+ // --------------------------------------------------
+ var currentHash string
+ err := db.QueryRow(`
+ SELECT password_hash
+ FROM mk_dfusr
+ WHERE id = $1
+ `, claims.ID).Scan(¤tHash)
+
+ if err != nil || currentHash == "" {
+ http.Error(w, "user not found", http.StatusUnauthorized)
+ return
+ }
+
+ // --------------------------------------------------
+ // 4️⃣ CURRENT PASSWORD CHECK
+ // --------------------------------------------------
+ if bcrypt.CompareHashAndPassword(
+ []byte(currentHash),
+ []byte(req.CurrentPassword),
+ ) != nil {
+ http.Error(w, "mevcut şifre hatalı", http.StatusUnauthorized)
+ return
+ }
+
+ // --------------------------------------------------
+ // 5️⃣ PASSWORD POLICY
+ // --------------------------------------------------
+ if err := security.ValidatePassword(req.NewPassword); err != nil {
+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+ return
+ }
+
+ // --------------------------------------------------
+ // 6️⃣ HASH NEW PASSWORD
+ // --------------------------------------------------
+ hash, err := bcrypt.GenerateFromPassword(
+ []byte(req.NewPassword),
+ bcrypt.DefaultCost,
+ )
+ if err != nil {
+ http.Error(w, "password hash error", http.StatusInternalServerError)
+ return
+ }
+
+ // --------------------------------------------------
+ // 7️⃣ UPDATE mk_dfusr
+ // --------------------------------------------------
+ _, err = db.Exec(`
+ UPDATE mk_dfusr
+ SET
+ password_hash = $1,
+ force_password_change = false,
+ password_updated_at = NOW(),
+ updated_at = NOW()
+ WHERE id = $2
+ `, string(hash), claims.ID)
+
+ if err != nil {
+ http.Error(w, "password update failed", http.StatusInternalServerError)
+ return
+ }
+
+ // --------------------------------------------------
+ // 8️⃣ REFRESH TOKEN REVOKE
+ // --------------------------------------------------
+ _ = repository.
+ NewRefreshTokenRepository(db).
+ RevokeAllForUser(claims.ID)
+
+ // --------------------------------------------------
+ // 9️⃣ NEW JWT (TEK DOĞRU YOL)
+ // --------------------------------------------------
+ newClaims := auth.BuildClaimsFromUser(
+ &models.MkUser{
+ ID: claims.ID,
+ Username: claims.Username,
+ RoleCode: claims.RoleCode,
+ V3Username: claims.V3Username,
+ V3UserGroup: claims.V3UserGroup,
+ SessionID: claims.SessionID,
+ ForcePasswordChange: false,
+ },
+ 15*time.Minute,
+ )
+
+ newToken, err := auth.GenerateToken(
+ newClaims,
+ claims.Username,
+ false,
+ )
+ if err != nil {
+ http.Error(w, "token generation failed", http.StatusInternalServerError)
+ return
+ }
+
+ // --------------------------------------------------
+ // 🔟 AUDIT
+ // --------------------------------------------------
+ auditlog.ForcePasswordChangeCompleted(
+ r.Context(),
+ claims.ID,
+ "self_change",
+ )
+
+ // --------------------------------------------------
+ // 1️⃣1️⃣ RESPONSE
+ // --------------------------------------------------
+ _ = json.NewEncoder(w).Encode(map[string]any{
+ "token": newToken,
+ "user": map[string]any{
+ "id": claims.ID,
+ "username": claims.Username,
+ "force_password_change": false,
+ },
+ })
+ log.Printf("✅ FIRST-PASS claims user=%d role=%s", claims.ID, claims.RoleCode)
+ }
+}
diff --git a/svc/routes/login.go b/svc/routes/login.go
new file mode 100644
index 0000000..89685f8
--- /dev/null
+++ b/svc/routes/login.go
@@ -0,0 +1,733 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/internal/auditlog"
+ "bssapp-backend/models"
+ "bssapp-backend/queries"
+ "bssapp-backend/repository"
+ "bssapp-backend/services"
+ "database/sql"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/gorilla/mux"
+ "golang.org/x/crypto/bcrypt"
+)
+
+/* ======================================================
+ 🔐 LOGIN
+====================================================== */
+
+type LoginRequest struct {
+ Username string `json:"username"`
+ Password string `json:"password"`
+}
+
+func LoginHandler(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ // --------------------------------------------------
+ // 0️⃣ REQUEST
+ // --------------------------------------------------
+ var req LoginRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "Geçersiz JSON", http.StatusBadRequest)
+ return
+ }
+
+ login := strings.TrimSpace(req.Username)
+ pass := req.Password // ⚠️ TRIM YAPMA
+
+ if login == "" || pass == "" {
+ http.Error(w, "Kullanıcı adı veya parola hatalı", http.StatusUnauthorized)
+ return
+ }
+
+ mkRepo := repository.NewMkUserRepository(db)
+
+ // ==================================================
+ // 1️⃣ mk_dfusr ÖNCELİKLİ
+ // ==================================================
+ mkUser, err := mkRepo.GetByUsername(login)
+ if err == nil {
+ log.Println("🧪 MK USER FROM DB")
+ log.Printf("🧪 ID=%d role_id=%d role_code='%s' depts=%v",
+ mkUser.ID,
+ mkUser.RoleID,
+ mkUser.RoleCode,
+ mkUser.DepartmentCodes,
+ )
+
+ }
+
+ log.Printf(
+ "🔎 LOGIN DEBUG | mk_user_found=%v err=%v hash_len=%d",
+ err == nil,
+ err,
+ func() int {
+ if err == nil {
+ return len(strings.TrimSpace(mkUser.PasswordHash))
+ }
+ return 0
+ }(),
+ )
+
+ if err == nil {
+
+ // mk_dfusr authoritative
+ if strings.TrimSpace(mkUser.PasswordHash) != "" {
+
+ if bcrypt.CompareHashAndPassword(
+ []byte(mkUser.PasswordHash),
+ []byte(pass),
+ ) != nil {
+ http.Error(w, "Kullanıcı adı veya parola hatalı", http.StatusUnauthorized)
+ return
+ }
+
+ _ = mkRepo.TouchLastLogin(mkUser.ID)
+ writeLoginResponse(w, db, mkUser)
+ return
+ }
+ // password_hash boşsa legacy fallback
+ } else if err != repository.ErrMkUserNotFound {
+ log.Println("❌ mk_dfusr lookup error:", err)
+ http.Error(w, "Giriş yapılamadı", http.StatusInternalServerError)
+ return
+ }
+
+ // ==================================================
+ // 2️⃣ dfusr FALLBACK (LEGACY)
+ // ==================================================
+ log.Println("🟡 LEGACY LOGIN PATH:", login)
+
+ legacyRepo := repository.NewUserRepository(db)
+ legacyUser, err := legacyRepo.GetLegacyUserForLogin(login)
+ if err != nil || !legacyUser.IsActive {
+ http.Error(w, "Kullanıcı adı veya parola hatalı", http.StatusUnauthorized)
+ return
+ }
+
+ log.Printf(
+ "🔎 LOGIN DEBUG | legacy_upass_len=%d prefix=%s",
+ len(strings.TrimSpace(legacyUser.Upass)),
+ func() string {
+ if len(legacyUser.Upass) >= 4 {
+ return legacyUser.Upass[:4]
+ }
+ return legacyUser.Upass
+ }(),
+ )
+
+ if !services.CheckPasswordWithLegacy(legacyUser, pass) {
+ http.Error(w, "Kullanıcı adı veya parola hatalı", http.StatusUnauthorized)
+ return
+ }
+
+ // ==================================================
+ // 3️⃣ MIGRATION (dfusr → mk_dfusr)
+ // ==================================================
+ newHash, err := bcrypt.GenerateFromPassword(
+ []byte(pass),
+ bcrypt.DefaultCost,
+ )
+ if err != nil {
+ http.Error(w, "Şifre üretilemedi", http.StatusInternalServerError)
+ return
+ }
+
+ mkUser, err = mkRepo.CreateFromLegacy(legacyUser, string(newHash))
+ if err != nil {
+ log.Println("❌ CREATE_FROM_LEGACY FAILED:", err)
+ http.Error(w, "Kullanıcı migrate edilemedi", http.StatusInternalServerError)
+ return
+ }
+
+ // 🔥 KRİTİK: TOKEN GUARD İÇİN GARANTİ
+ mkUser.ForcePasswordChange = true
+
+ auditlog.Write(auditlog.ActivityLog{
+ ActionType: "LEGACY_USER_MIGRATED",
+ ActionCategory: "security",
+ Description: "dfusr -> mk_dfusr on login",
+ IsSuccess: true,
+ })
+
+ // ==================================================
+ // 4️⃣ SUCCESS
+ // ==================================================
+ writeLoginResponse(w, db, mkUser)
+ }
+}
+
+// ======================================================
+// 🔑 LOGIN RESPONSE
+// ======================================================
+
+func writeLoginResponse(w http.ResponseWriter, db *sql.DB, user *models.MkUser) {
+ // 🔥 ROLE GARANTİSİ
+ if user.RoleID == 0 {
+
+ _ = db.QueryRow(`
+ SELECT dfrole_id
+ FROM dfrole_usr
+ WHERE dfusr_id = $1
+ LIMIT 1
+ `, user.ID).Scan(&user.RoleID)
+ }
+
+ if user.RoleCode == "" && user.RoleID > 0 {
+
+ _ = db.QueryRow(`
+ SELECT code
+ FROM dfrole
+ WHERE id = $1
+ `, user.RoleID).Scan(&user.RoleCode)
+ }
+
+ log.Println("🧪 LOGIN RESPONSE USER DEBUG")
+ log.Printf("🧪 user.ID = %d", user.ID)
+ log.Printf("🧪 user.Username = %s", user.Username)
+ log.Printf("🧪 user.RoleID = %d", user.RoleID)
+ log.Printf("🧪 user.RoleCode = '%s'", user.RoleCode)
+ log.Printf("🧪 user.IsActive = %v", user.IsActive)
+
+ permRepo := repository.NewPermissionRepository(db)
+ perms, _ := permRepo.GetPermissionsByRoleID(user.RoleID)
+
+ // ✅ CLAIMS BUILD
+ claims := auth.BuildClaimsFromUser(user, 15*time.Minute)
+
+ token, err := auth.GenerateToken(
+ claims,
+ user.Username,
+ user.ForcePasswordChange,
+ )
+ if err != nil {
+ http.Error(w, "Token üretilemedi", http.StatusInternalServerError)
+ return
+ }
+
+ _ = json.NewEncoder(w).Encode(map[string]any{
+ "token": token,
+ "user": map[string]any{
+ "id": user.ID,
+ "username": user.Username,
+ "email": user.Email,
+ "is_active": user.IsActive,
+ "role_id": user.RoleID,
+ "role_code": user.RoleCode,
+ "force_password_change": user.ForcePasswordChange,
+ "v3_username": user.V3Username,
+ "v3_usergroup": user.V3UserGroup,
+ },
+ "permissions": perms,
+ })
+}
+
+/* ======================================================
+ 🔎 LOOKUPS (aynen korunuyor)
+====================================================== */
+
+type LookupOption struct {
+ Value string `json:"value"`
+ Label string `json:"label"`
+}
+
+func GetRoleLookupRoute(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ rows, err := db.Query(queries.GetRoleLookup)
+ if err != nil {
+ http.Error(w, "role lookup error", http.StatusInternalServerError)
+ return
+ }
+ defer rows.Close()
+
+ var list []LookupOption
+ for rows.Next() {
+ var o LookupOption
+ if err := rows.Scan(&o.Value, &o.Label); err == nil {
+ list = append(list, o)
+ }
+ }
+ _ = json.NewEncoder(w).Encode(list)
+ }
+}
+
+func GetDepartmentLookupRoute(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ rows, err := db.Query(queries.GetDepartmentLookup)
+ if err != nil {
+ http.Error(w, "department lookup error", http.StatusInternalServerError)
+ return
+ }
+ defer rows.Close()
+
+ var list []LookupOption
+ for rows.Next() {
+ var o LookupOption
+ if err := rows.Scan(&o.Value, &o.Label); err == nil {
+ list = append(list, o)
+ }
+ }
+ _ = json.NewEncoder(w).Encode(list)
+ }
+}
+
+func GetPiyasaLookupRoute(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ rows, err := db.Query(queries.GetPiyasaLookup)
+ if err != nil {
+ http.Error(w, "piyasa lookup error", http.StatusInternalServerError)
+ return
+ }
+ defer rows.Close()
+
+ var list []LookupOption
+ for rows.Next() {
+ var o LookupOption
+ if err := rows.Scan(&o.Value, &o.Label); err == nil {
+ list = append(list, o)
+ }
+ }
+ _ = json.NewEncoder(w).Encode(list)
+ }
+}
+
+// ======================================================
+// 🧾 NEBIM USER LOOKUP
+// GET /api/lookups/nebim-users
+// ======================================================
+func GetNebimUserLookupRoute(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ rows, err := db.Query(queries.GetNebimUserLookup)
+ if err != nil {
+ http.Error(w, "nebim lookup error", http.StatusInternalServerError)
+ return
+ }
+ defer rows.Close()
+
+ var list []map[string]string
+ for rows.Next() {
+ var v, l, g string
+ if err := rows.Scan(&v, &l, &g); err == nil {
+ list = append(list, map[string]string{
+ "value": v,
+ "label": l,
+ "user_group_code": g,
+ })
+ }
+ }
+
+ _ = json.NewEncoder(w).Encode(list)
+ }
+}
+func UserCreateRoute(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ var payload models.UserWrite
+ if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
+ http.Error(w, "Geçersiz payload", http.StatusBadRequest)
+ return
+ }
+
+ if payload.Code == "" {
+ http.Error(w, "Kullanıcı kodu zorunludur", http.StatusUnprocessableEntity)
+ return
+ }
+
+ tx, err := db.Begin()
+ if err != nil {
+ http.Error(w, "Transaction başlatılamadı", http.StatusInternalServerError)
+ return
+ }
+ defer tx.Rollback()
+
+ var newID int64
+ err = tx.QueryRow(`
+ INSERT INTO mk_dfusr (
+ code,
+ is_active,
+ full_name,
+ email,
+ mobile,
+ address,
+ force_password_change,
+ last_updated_date
+ )
+ VALUES ($1,$2,$3,$4,$5,$6,true,NOW())
+ RETURNING id
+ `,
+ payload.Code,
+ payload.IsActive,
+ payload.FullName,
+ payload.Email,
+ payload.Mobile,
+ payload.Address,
+ ).Scan(&newID)
+
+ if err != nil {
+ log.Println("USER INSERT ERROR:", err)
+ http.Error(w, "Kullanıcı oluşturulamadı", http.StatusInternalServerError)
+ return
+ }
+
+ // ROLES
+ for _, role := range payload.Roles {
+ _, _ = tx.Exec(queries.InsertUserRole, newID, role)
+ }
+
+ // DEPARTMENTS
+ for _, d := range payload.Departments {
+ _, _ = tx.Exec(queries.InsertUserDepartment, newID, d.Code)
+ }
+
+ // PIYASALAR
+ for _, p := range payload.Piyasalar {
+ _, _ = tx.Exec(queries.InsertUserPiyasa, newID, p.Code)
+ }
+
+ // NEBIM
+ for _, n := range payload.NebimUsers {
+ _, _ = tx.Exec(queries.InsertUserNebim, newID, n.Username)
+ }
+
+ if err := tx.Commit(); err != nil {
+ http.Error(w, "Commit başarısız", http.StatusInternalServerError)
+ return
+ }
+
+ _ = json.NewEncoder(w).Encode(map[string]any{
+ "success": true,
+ "id": newID,
+ })
+ }
+}
+
+// ======================================================
+// 🔐 ROLES LIST
+// GET /api/roles
+// ======================================================
+
+func GetRolesHandler(db *sql.DB) http.HandlerFunc {
+ type RoleRow struct {
+ ID int64 `json:"id"`
+ Code string `json:"code"`
+ Title string `json:"title"`
+ }
+
+ return func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ rows, err := db.Query(queries.GetRoles)
+ if err != nil {
+ http.Error(w, "roles query error", http.StatusInternalServerError)
+ return
+ }
+ defer rows.Close()
+
+ var list []RoleRow
+ for rows.Next() {
+ var x RoleRow
+ if err := rows.Scan(&x.ID, &x.Code, &x.Title); err == nil {
+ list = append(list, x)
+ }
+ }
+
+ _ = json.NewEncoder(w).Encode(list)
+ }
+}
+
+// ======================================================
+// 🏢 DEPARTMENTS LIST
+// GET /api/departments
+// ======================================================
+
+func GetDepartmentsHandler(db *sql.DB) http.HandlerFunc {
+ type DeptRow struct {
+ ID int64 `json:"id"`
+ Code string `json:"code"`
+ Title string `json:"title"`
+ }
+
+ return func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ rows, err := db.Query(queries.GetDepartments)
+ if err != nil {
+ http.Error(w, "departments query error", http.StatusInternalServerError)
+ return
+ }
+ defer rows.Close()
+
+ var list []DeptRow
+ for rows.Next() {
+ var x DeptRow
+ if err := rows.Scan(&x.ID, &x.Code, &x.Title); err == nil {
+ list = append(list, x)
+ }
+ }
+
+ _ = json.NewEncoder(w).Encode(list)
+ }
+}
+
+// ======================================================
+// 🌍 PIYASALAR LIST
+// GET /api/piyasalar
+// ======================================================
+
+func GetPiyasalarHandler(db *sql.DB) http.HandlerFunc {
+ type PiyRow struct {
+ Code string `json:"code"`
+ Title string `json:"title"`
+ }
+
+ return func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ rows, err := db.Query(queries.GetPiyasalar)
+ if err != nil {
+ http.Error(w, "piyasalar query error", http.StatusInternalServerError)
+ return
+ }
+ defer rows.Close()
+
+ var list []PiyRow
+ for rows.Next() {
+ var x PiyRow
+ if err := rows.Scan(&x.Code, &x.Title); err == nil {
+ list = append(list, x)
+ }
+ }
+
+ _ = json.NewEncoder(w).Encode(list)
+ }
+}
+
+// ======================================================
+// 🔗 ROLE → DEPARTMENTS UPDATE
+// POST /api/roles/{id}/departments
+// body: { "codes": ["D01","D02"] }
+// ======================================================
+
+type CodesPayload struct {
+ Codes []string `json:"codes"`
+}
+
+func UpdateRoleDepartmentsHandler(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ roleIDStr := mux.Vars(r)["id"]
+ roleID, err := strconv.ParseInt(roleIDStr, 10, 64)
+ if err != nil || roleID <= 0 {
+ http.Error(w, "Geçersiz role id", http.StatusBadRequest)
+ return
+ }
+
+ var payload CodesPayload
+ if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
+ http.Error(w, "Geçersiz payload", http.StatusBadRequest)
+ return
+ }
+
+ tx, err := db.Begin()
+ if err != nil {
+ http.Error(w, "Transaction başlatılamadı", http.StatusInternalServerError)
+ return
+ }
+ defer tx.Rollback()
+
+ // reset
+ if _, err := tx.Exec(queries.DeleteRoleDepartments, roleID); err != nil {
+ http.Error(w, "Role departments silinemedi", http.StatusInternalServerError)
+ return
+ }
+
+ // insert new
+ for _, code := range payload.Codes {
+ code = strings.TrimSpace(code)
+ if code == "" {
+ continue
+ }
+ if _, err := tx.Exec(queries.InsertRoleDepartment, roleID, code); err != nil {
+ http.Error(w, "Role department eklenemedi", http.StatusInternalServerError)
+ return
+ }
+ }
+
+ if err := tx.Commit(); err != nil {
+ http.Error(w, "Commit başarısız", http.StatusInternalServerError)
+ return
+ }
+
+ _ = json.NewEncoder(w).Encode(map[string]any{"success": true})
+ }
+}
+
+// ======================================================
+// 🔗 ROLE → PIYASALAR UPDATE
+// POST /api/roles/{id}/piyasalar
+// body: { "codes": ["TR","EU"] } (piyasa_code list)
+// ======================================================
+
+func UpdateRolePiyasalarHandler(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ roleIDStr := mux.Vars(r)["id"]
+ roleID, err := strconv.ParseInt(roleIDStr, 10, 64)
+ if err != nil || roleID <= 0 {
+ http.Error(w, "Geçersiz role id", http.StatusBadRequest)
+ return
+ }
+
+ var payload CodesPayload
+ if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
+ http.Error(w, "Geçersiz payload", http.StatusBadRequest)
+ return
+ }
+
+ tx, err := db.Begin()
+ if err != nil {
+ http.Error(w, "Transaction başlatılamadı", http.StatusInternalServerError)
+ return
+ }
+ defer tx.Rollback()
+
+ // reset
+ if _, err := tx.Exec(queries.DeleteRolePiyasalar, roleID); err != nil {
+ http.Error(w, "Role piyasalar silinemedi", http.StatusInternalServerError)
+ return
+ }
+
+ // insert new
+ for _, code := range payload.Codes {
+ code = strings.TrimSpace(code)
+ if code == "" {
+ continue
+ }
+ if _, err := tx.Exec(queries.InsertRolePiyasa, roleID, code); err != nil {
+ http.Error(w, "Role piyasa eklenemedi", http.StatusInternalServerError)
+ return
+ }
+ }
+
+ if err := tx.Commit(); err != nil {
+ http.Error(w, "Commit başarısız", http.StatusInternalServerError)
+ return
+ }
+
+ _ = json.NewEncoder(w).Encode(map[string]any{"success": true})
+ }
+}
+
+// ======================================================
+// 👤 USER → ROLES UPDATE
+// POST /api/users/{id}/roles
+// body: { "codes": ["admin","user"] } (dfrole.code list)
+// ======================================================
+
+func UpdateUserRolesHandler(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ // ---------- CLAIMS ----------
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || !claims.IsAdmin() {
+ http.Error(w, "forbidden", http.StatusForbidden)
+ return
+ }
+
+ // ---------- TARGET USER ----------
+ userIDStr := mux.Vars(r)["id"]
+ userID, err := strconv.ParseInt(userIDStr, 10, 64)
+ if err != nil || userID <= 0 {
+ http.Error(w, "Geçersiz user id", http.StatusBadRequest)
+ return
+ }
+
+ // ---------- PAYLOAD ----------
+ var payload CodesPayload
+ if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
+ http.Error(w, "Geçersiz payload", http.StatusBadRequest)
+ return
+ }
+
+ // ---------- BEFORE (ROLE LIST) ----------
+ oldRoles, _ := repository.GetUserRolesByUserID(db, userID)
+
+ // ---------- TX ----------
+ tx, err := db.Begin()
+ if err != nil {
+ http.Error(w, "Transaction başlatılamadı", http.StatusInternalServerError)
+ return
+ }
+ defer tx.Rollback()
+
+ // reset
+ if _, err := tx.Exec(queries.DeleteUserRoles, userID); err != nil {
+ http.Error(w, "User roles silinemedi", http.StatusInternalServerError)
+ return
+ }
+
+ // insert new
+ for _, roleCode := range payload.Codes {
+ roleCode = strings.TrimSpace(roleCode)
+ if roleCode == "" {
+ continue
+ }
+ if _, err := tx.Exec(queries.InsertUserRole, userID, roleCode); err != nil {
+ http.Error(w, "User role eklenemedi", http.StatusInternalServerError)
+ return
+ }
+ }
+
+ if err := tx.Commit(); err != nil {
+ http.Error(w, "Commit başarısız", http.StatusInternalServerError)
+ return
+ }
+
+ // ---------- AFTER (ROLE LIST) ----------
+ newRoles, _ := repository.GetUserRolesByUserID(db, userID)
+
+ // ---------- AUDIT ----------
+ auditlog.Enqueue(r.Context(), auditlog.ActivityLog{
+ ActionType: "role_change",
+ ActionCategory: "user_admin",
+ ActionTarget: fmt.Sprintf("/api/users/%d/roles", userID),
+ Description: "user roles updated",
+
+ Username: claims.Username,
+ RoleCode: claims.RoleCode,
+ DfUsrID: int64(claims.ID),
+
+ TargetDfUsrID: userID,
+
+ ChangeBefore: map[string]any{
+ "roles": oldRoles,
+ },
+ ChangeAfter: map[string]any{
+ "roles": newRoles,
+ },
+
+ IsSuccess: true,
+ })
+
+ _ = json.NewEncoder(w).Encode(map[string]any{
+ "success": true,
+ })
+ }
+}
diff --git a/svc/routes/order_list_excel.go b/svc/routes/order_list_excel.go
new file mode 100644
index 0000000..90ff2a8
--- /dev/null
+++ b/svc/routes/order_list_excel.go
@@ -0,0 +1,91 @@
+package routes
+
+import (
+ "bssapp-backend/models"
+ "bssapp-backend/queries"
+ "database/sql"
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/xuri/excelize/v2"
+)
+
+func OrderListExcelRoute(db *sql.DB) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ search := r.URL.Query().Get("search")
+ currAcc := r.URL.Query().Get("CurrAccCode")
+ orderDate := r.URL.Query().Get("OrderDate")
+
+ rows, err := queries.GetOrderListExcel(db, search, currAcc, orderDate)
+ if err != nil {
+ http.Error(w, "Veritabanı hatası", 500)
+ return
+ }
+ defer rows.Close()
+
+ f := excelize.NewFile()
+ sheet := "Orders"
+ f.SetSheetName("Sheet1", sheet)
+
+ headers := []string{
+ "Sipariş No", "Tarih", "Cari Kod", "Cari Adı",
+ "Temsilci", "Piyasa", "Onay Tarihi", "PB",
+ "Tutar", "Tutar (USD)", "Açıklama",
+ }
+
+ for i, h := range headers {
+ cell, _ := excelize.CoordinatesToCellName(i+1, 1)
+ f.SetCellValue(sheet, cell, h)
+ }
+
+ rowIdx := 2
+ for rows.Next() {
+ var o models.OrderList
+ _ = rows.Scan(
+ &o.OrderHeaderID,
+ &o.OrderNumber,
+ &o.OrderDate,
+ &o.CurrAccCode,
+ &o.CurrAccDescription,
+ &o.MusteriTemsilcisi,
+ &o.Piyasa,
+ &o.CreditableConfirmedDate,
+ &o.DocCurrencyCode,
+ &o.TotalAmount,
+ &o.TotalAmountUSD,
+ &o.IsCreditableConfirmed,
+ &o.Description,
+ &o.ExchangeRateUSD,
+ )
+
+ f.SetSheetRow(sheet, fmt.Sprintf("A%d", rowIdx), &[]interface{}{
+ o.OrderNumber,
+ o.OrderDate,
+ o.CurrAccCode,
+ o.CurrAccDescription,
+ o.MusteriTemsilcisi,
+ o.Piyasa,
+ o.CreditableConfirmedDate,
+ o.DocCurrencyCode,
+ o.TotalAmount,
+ o.TotalAmountUSD,
+ o.Description,
+ })
+ rowIdx++
+ }
+
+ filename := fmt.Sprintf(
+ "order_list_%s.xlsx",
+ time.Now().Format("2006-01-02_15-04"),
+ )
+
+ w.Header().Set("Content-Type",
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
+ w.Header().Set("Content-Disposition",
+ "attachment; filename="+filename)
+
+ _ = f.Write(w)
+ })
+}
diff --git a/svc/routes/order_pdf.go b/svc/routes/order_pdf.go
new file mode 100644
index 0000000..83da361
--- /dev/null
+++ b/svc/routes/order_pdf.go
@@ -0,0 +1,1387 @@
+package routes
+
+import (
+ "bytes"
+ "database/sql"
+ "fmt"
+ "github.com/gorilla/mux"
+ "github.com/jung-kurt/gofpdf"
+ "log"
+ "math"
+ "net/http"
+ "os"
+ "sort"
+ "strings"
+ "time"
+)
+
+/* ===========================================================
+ 1) SABİTLER / RENKLER / TİPLER
+ =========================================================== */
+
+// Baggi renkleri
+var (
+ baggiGoldR, baggiGoldG, baggiGoldB = 201, 162, 39
+ baggiCreamR, baggiCreamG, baggiCreamB = 255, 254, 249
+ baggiGrayBorderR, baggiGrayBorderG, baggiGrayBorderB = 187, 187, 187
+)
+
+// Beden kategorileri (frontend birebir)
+const (
+ catAyk = "ayk"
+ catYas = "yas"
+ catPan = "pan"
+ catGom = "gom"
+ catTak = "tak"
+ catAksbir = "aksbir"
+)
+
+var categoryOrder = []string{catAyk, catYas, catPan, catGom, catTak, catAksbir}
+
+var categoryTitle = map[string]string{
+ catAyk: " AYAKKABI",
+ catYas: " YAŞ",
+ catPan: " PANTOLON",
+ catGom: " GÖMLEK",
+ catTak: " TAKIM ELBİSE",
+ catAksbir: " AKSESUAR",
+}
+
+/* ===========================================================
+ HEADER MODEL
+ =========================================================== */
+
+type OrderHeader struct {
+ OrderHeaderID string
+ OrderNumber string
+ CurrAccCode string
+ CurrAccName string
+ DocCurrency string
+ OrderDate time.Time
+ Description string
+ InternalDesc string
+ OfficeCode string
+ CreatedUser string
+ CustomerRep string // 🆕 Müşteri Temsilcisi
+}
+
+/* ===========================================================
+ RAW LINE MODEL
+ =========================================================== */
+
+type OrderLineRaw struct {
+ OrderLineID sql.NullString
+ ItemCode string
+ ColorCode string
+ ItemDim1Code sql.NullString
+ ItemDim2Code sql.NullString
+ Qty1 sql.NullFloat64
+ Price sql.NullFloat64
+ DocCurrencyCode sql.NullString
+ DeliveryDate sql.NullTime
+ LineDescription sql.NullString
+ UrunAnaGrubu sql.NullString
+ UrunAltGrubu sql.NullString
+ IsClosed sql.NullBool
+ WithHoldingTaxType sql.NullString
+ DOVCode sql.NullString
+ PlannedDateOfLading sql.NullTime
+ CostCenterCode sql.NullString
+ VatCode sql.NullString
+ VatRate sql.NullFloat64
+}
+
+/* ===========================================================
+ PDF SATIR MODELİ
+ =========================================================== */
+
+type PdfRow struct {
+ Model string
+ Color string
+ GroupMain string
+ GroupSub string
+ Description string
+ Category string
+ SizeQty map[string]int
+ TotalQty int
+ Price float64
+ Currency string
+ Amount float64
+ Termin string
+ IsClosed bool
+ OrderLineIDs map[string]string
+
+ ClosedSizes map[string]bool // 🆕 her beden için IsClosed bilgisi
+}
+
+/* ===========================================================
+ PDF LAYOUT STRUCT
+ =========================================================== */
+
+type pdfLayout struct {
+ PageW, PageH float64
+ MarginL float64
+ MarginR float64
+ MarginT float64
+ MarginB float64
+
+ ColModelW float64
+ ColRenkW float64
+ ColGroupW float64
+ ColGroupW2 float64
+ ColDescLeft float64
+ ColDescRight float64
+ ColDescW float64
+ ColQtyW float64
+ ColPriceW float64
+ ColCurW float64
+ ColAmountW float64
+ ColTerminW float64
+
+ CentralW float64
+ HeaderMainH float64
+ HeaderSizeH float64
+ RowH float64
+}
+
+/* genel cell padding */
+const OcellPadX = 2
+
+/*
+===========================================================
+
+ PDF LAYOUT OLUŞTURUCU
+ ===========================================================
+*/
+
+/*
+===========================================================
+
+ PDF LAYOUT OLUŞTURUCU — AÇIKLAMA TEK KOLON
+ ===========================================================
+*/
+func newPdfLayout(pdf *gofpdf.Fpdf) pdfLayout {
+ pageW, pageH := pdf.GetPageSize()
+
+ l := pdfLayout{
+ PageW: pageW,
+ PageH: pageH,
+ MarginL: 10,
+ MarginR: 10,
+ MarginT: 10,
+ MarginB: 12,
+
+ RowH: 7,
+ HeaderMainH: 8,
+ HeaderSizeH: 6,
+ }
+
+ totalW := pageW - l.MarginL - l.MarginR
+
+ /* --------------------------------------------------------
+ SOL BLOK GÜNCEL – MODEL/RENK ÇAKIŞMASI GİDERİLDİ
+ -------------------------------------------------------- */
+ l.ColModelW = 24 // eski 18 → genişletildi
+ l.ColRenkW = 24 // eski 14 → biraz geniş
+ l.ColGroupW = 20
+ l.ColGroupW2 = 20
+
+ /* --------------------------------------------------------
+ AÇIKLAMA = TEK GENİŞ KOLON (kategori listesi + açıklama içerir)
+ -------------------------------------------------------- */
+ l.ColDescLeft = 50 // açıklama başlığı + kategori alanı (artık tek kolon)
+ l.ColDescRight = 0 // kullanılmıyor (0 bırakılmalı!)
+
+ left := l.ColModelW + l.ColRenkW + l.ColGroupW + l.ColGroupW2 + l.ColDescLeft
+
+ /* --------------------------------------------------------
+ SAĞ BLOK
+ -------------------------------------------------------- */
+ l.ColQtyW = 12
+ l.ColPriceW = 16
+ l.ColCurW = 10
+ l.ColAmountW = 20
+ l.ColTerminW = 20
+
+ right := l.ColQtyW + l.ColPriceW + l.ColCurW + l.ColAmountW + l.ColTerminW
+
+ /* --------------------------------------------------------
+ ORTA BLOK (BEDEN 16 KOLON)
+ -------------------------------------------------------- */
+ l.CentralW = totalW - left - right
+ if l.CentralW < 70 {
+ l.CentralW = 70
+ }
+
+ return l
+}
+
+/* ===========================================================
+ HELPER FONKSİYONLAR
+ =========================================================== */
+
+func safeTrimUpper(s string) string {
+ return strings.ToUpper(strings.TrimSpace(s))
+}
+
+func f64(v sql.NullFloat64) float64 {
+ if !v.Valid {
+ return 0
+ }
+ return v.Float64
+}
+
+func s64(v sql.NullString) string {
+ if !v.Valid {
+ return ""
+ }
+ return v.String
+}
+
+func normalizeBedenLabelGo(v string) string {
+ // 1️⃣ NULL / boş / whitespace → " " (aksbir null kolonu)
+ s := strings.TrimSpace(v)
+ if s == "" {
+ return " " // 🔥 NULL BEDEN → boş kolon
+ }
+
+ // 2️⃣ Uppercase
+ s = strings.ToUpper(s)
+
+ /* --------------------------------------------------
+ 🔥 AKSBİR ÖZEL (STD eş anlamlıları)
+ -------------------------------------------------- */
+ switch s {
+ case "STD", "STANDART", "STANDARD", "ONE SIZE", "ONESIZE":
+ return "STD"
+ }
+
+ /* --------------------------------------------------
+ 🔢 SADECE "CM" VARSA → NUMERİK KISMI AL
+ 120CM / 120 CM → 120
+ ❌ 105 / 110 / 120 → DOKUNMA
+ -------------------------------------------------- */
+ if strings.HasSuffix(s, "CM") {
+ num := strings.TrimSpace(strings.TrimSuffix(s, "CM"))
+ if num != "" {
+ return num
+ }
+ }
+
+ /* --------------------------------------------------
+ HARF BEDENLER (DOKUNMA)
+ -------------------------------------------------- */
+ switch s {
+ case "XS", "S", "M", "L", "XL",
+ "2XL", "3XL", "4XL", "5XL", "6XL", "7XL":
+ return s
+ }
+
+ // 4️⃣ Sayısal veya başka değerler → olduğu gibi
+ return s
+}
+
+func detectBedenGroupGo(bedenList []string, ana, alt string) string {
+ ana = safeTrimUpper(ana)
+ alt = safeTrimUpper(alt)
+
+ for _, b := range bedenList {
+ switch b {
+ case "XS", "S", "M", "L", "XL":
+ return catGom
+ }
+ }
+
+ if strings.Contains(ana, "PANTOLON") {
+ return catPan
+ }
+ if strings.Contains(alt, "ÇOCUK") || strings.Contains(alt, "GARSON") {
+ return catYas
+ }
+ return catTak
+}
+func defaultSizeListFor(cat string) []string {
+ switch cat {
+ case catAyk:
+ return []string{"39", "40", "41", "42", "43", "44", "45"}
+ case catYas:
+ return []string{"2", "4", "6", "8", "10", "12", "14"}
+ case catPan:
+ return []string{"38", "40", "42", "44", "46", "48", "50", "52", "54", "56", "58", "60", "62", "64", "66", "68"}
+ case catGom:
+ return []string{"XS", "S", "M", "L", "XL", "2XL", "3XL", "4XL", "5XL", "6XL", "7XL"}
+ case catTak:
+ return []string{"44", "46", "48", "50", "52", "54", "56", "58", "60", "62", "64", "66", "68", "70", "72", "74"}
+ case catAksbir:
+ return []string{"", "44", "STD", "110", "115", "120", "125", "130", "135"}
+ }
+ return []string{}
+}
+
+func contains(list []string, v string) bool {
+ for _, x := range list {
+ if x == v {
+ return true
+ }
+ }
+ return false
+}
+
+/* ===========================================================
+ 2) PDF OLUŞTURUCU (A4 YATAY + FOOTER)
+ =========================================================== */
+
+func newOrderPdf() *gofpdf.Fpdf {
+ pdf := gofpdf.New("L", "mm", "A4", "")
+ pdf.SetMargins(10, 10, 10)
+ pdf.SetAutoPageBreak(false, 12)
+
+ // UTF8 fontlar
+ pdf.AddUTF8Font("dejavu", "", "fonts/DejaVuSans.ttf")
+ pdf.AddUTF8Font("dejavu-b", "", "fonts/DejaVuSans-Bold.ttf")
+
+ // Footer: sayfa numarası
+ pdf.AliasNbPages("")
+ pdf.SetFooterFunc(func() {
+ pdf.SetY(-10)
+ pdf.SetFont("dejavu", "", 8)
+ txt := fmt.Sprintf("Sayfa %d/{nb}", pdf.PageNo())
+ pdf.CellFormat(0, 10, txt, "", 0, "R", false, 0, "")
+ })
+
+ return pdf
+}
+
+/* ===========================================================
+ 3) DB FONKSİYONLARI (HEADER + LINES)
+ =========================================================== */
+
+// HEADER
+func getOrderHeaderFromDB(db *sql.DB, orderID string) (*OrderHeader, error) {
+ row := db.QueryRow(`
+ SELECT
+ CAST(h.OrderHeaderID AS varchar(36)),
+ h.OrderNumber,
+ h.CurrAccCode,
+ d.CurrAccDescription,
+ h.DocCurrencyCode,
+ h.OrderDate,
+ h.Description,
+ h.InternalDescription,
+ h.OfficeCode,
+ h.CreatedUserName,
+ ISNULL((
+ SELECT TOP (1) ca.AttributeDescription
+ FROM BAGGI_V3.dbo.cdCurrAccAttributeDesc AS ca WITH (NOLOCK)
+ WHERE ca.CurrAccTypeCode = 3
+ AND ca.AttributeTypeCode = 2 -- 🟡 Müşteri Temsilcisi
+ AND ca.AttributeCode = f.CustomerAtt02
+ AND ca.LangCode = 'TR'
+ ), '') AS CustomerRep
+ FROM BAGGI_V3.dbo.trOrderHeader AS h
+ LEFT JOIN BAGGI_V3.dbo.cdCurrAccDesc AS d
+ ON h.CurrAccCode = d.CurrAccCode
+ LEFT JOIN BAGGI_V3.dbo.CustomerAttributesFilter AS f
+ ON h.CurrAccCode = f.CurrAccCode
+ WHERE h.OrderHeaderID = @p1
+ `, orderID)
+
+ var h OrderHeader
+ var orderDate sql.NullTime
+
+ err := row.Scan(
+ &h.OrderHeaderID,
+ &h.OrderNumber,
+ &h.CurrAccCode,
+ &h.CurrAccName,
+ &h.DocCurrency,
+ &orderDate,
+ &h.Description,
+ &h.InternalDesc,
+ &h.OfficeCode,
+ &h.CreatedUser,
+ &h.CustomerRep, // 🆕 buradan geliyor
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ if orderDate.Valid {
+ h.OrderDate = orderDate.Time
+ }
+ return &h, nil
+}
+
+// LINES
+func getOrderLinesFromDB(db *sql.DB, orderID string) ([]OrderLineRaw, error) {
+ rows, err := db.Query(`
+ SELECT
+ CAST(L.OrderLineID AS varchar(36)),
+ L.ItemCode,
+ L.ColorCode,
+ L.ItemDim1Code,
+ L.ItemDim2Code,
+ L.Qty1,
+ L.Price,
+ L.DocCurrencyCode,
+ L.DeliveryDate,
+ L.LineDescription,
+ P.ProductAtt01Desc,
+ P.ProductAtt02Desc,
+ L.IsClosed,
+ L.WithHoldingTaxTypeCode,
+ L.DOVCode,
+ L.PlannedDateOfLading,
+ L.CostCenterCode,
+ L.VatCode,
+ L.VatRate
+ FROM BAGGI_V3.dbo.trOrderLine AS L
+ LEFT JOIN ProductFilterWithDescription('TR') AS P
+ ON LTRIM(RTRIM(P.ProductCode)) = LTRIM(RTRIM(L.ItemCode))
+ WHERE L.OrderHeaderID = @p1
+ ORDER BY L.SortOrder, L.OrderLineID
+ `, orderID)
+
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ var out []OrderLineRaw
+ for rows.Next() {
+ var l OrderLineRaw
+ if err := rows.Scan(
+ &l.OrderLineID,
+ &l.ItemCode,
+ &l.ColorCode,
+ &l.ItemDim1Code,
+ &l.ItemDim2Code,
+ &l.Qty1,
+ &l.Price,
+ &l.DocCurrencyCode,
+ &l.DeliveryDate,
+ &l.LineDescription,
+ &l.UrunAnaGrubu,
+ &l.UrunAltGrubu,
+ &l.IsClosed,
+ &l.WithHoldingTaxType,
+ &l.DOVCode,
+ &l.PlannedDateOfLading,
+ &l.CostCenterCode,
+ &l.VatCode,
+ &l.VatRate,
+ ); err != nil {
+ return nil, err
+ }
+ out = append(out, l)
+ }
+ return out, rows.Err()
+}
+
+/* ===========================================================
+ 4) NORMALIZE + CATEGORY MAP
+ =========================================================== */
+
+func normalizeOrderLinesForPdf(lines []OrderLineRaw) []PdfRow {
+ type comboKey struct {
+ Model, Color, Color2 string
+ }
+
+ merged := make(map[comboKey]*PdfRow)
+
+ for _, raw := range lines {
+
+ // ❌ ARTIK KAPALI SATIRLARI ATMAYACAĞIZ
+ // if raw.IsClosed.Valid && raw.IsClosed.Bool {
+ // continue
+ // }
+
+ model := safeTrimUpper(raw.ItemCode)
+ color := safeTrimUpper(raw.ColorCode)
+ color2 := safeTrimUpper(s64(raw.ItemDim2Code))
+ displayColor := color
+ if color2 != "" {
+ displayColor = fmt.Sprintf("%s-%s", color, color2)
+ }
+
+ key := comboKey{model, color, color2}
+
+ if _, ok := merged[key]; !ok {
+ merged[key] = &PdfRow{
+ Model: model,
+ Color: displayColor,
+ GroupMain: s64(raw.UrunAnaGrubu),
+ GroupSub: s64(raw.UrunAltGrubu),
+ Description: s64(raw.LineDescription),
+ SizeQty: make(map[string]int),
+ Currency: s64(raw.DocCurrencyCode),
+ Price: f64(raw.Price),
+ OrderLineIDs: make(map[string]string),
+ ClosedSizes: make(map[string]bool), // 🆕
+ }
+ }
+ row := merged[key]
+
+ // beden
+ rawBeden := s64(raw.ItemDim1Code)
+ if raw.ItemDim1Code.Valid {
+ rawBeden = raw.ItemDim1Code.String
+ }
+ normalized := normalizeBedenLabelGo(rawBeden)
+
+ qty := int(math.Round(f64(raw.Qty1)))
+ if qty > 0 {
+ row.SizeQty[normalized] += qty
+ row.TotalQty += qty
+
+ // 🆕 Bu beden kapalı satırdan geldiyse işaretle
+ if raw.IsClosed.Valid && raw.IsClosed.Bool {
+ row.ClosedSizes[normalized] = true
+ }
+ }
+
+ // OrderLineID eşleştirmesi
+ if raw.OrderLineID.Valid {
+ row.OrderLineIDs[normalized] = raw.OrderLineID.String
+ }
+
+ // Termin
+ if row.Termin == "" && raw.DeliveryDate.Valid {
+ row.Termin = raw.DeliveryDate.Time.Format("02.01.2006")
+ }
+ }
+
+ // finalize
+ out := make([]PdfRow, 0, len(merged))
+ for _, r := range merged {
+ var sizes []string
+ for s := range r.SizeQty {
+ sizes = append(sizes, s)
+ }
+ r.Category = detectBedenGroupGo(sizes, r.GroupMain, r.GroupSub)
+ r.Amount = float64(r.TotalQty) * r.Price
+ out = append(out, *r)
+ }
+
+ // Sıralama: Model → Renk → Category
+ sort.Slice(out, func(i, j int) bool {
+ if out[i].Model != out[j].Model {
+ return out[i].Model < out[j].Model
+ }
+ if out[i].Color != out[j].Color {
+ return out[i].Color < out[j].Color
+ }
+ return out[i].Category < out[j].Category
+ })
+
+ return out
+}
+
+/* ===========================================================
+ 5) CATEGORY → SIZE MAP (HEADER İÇİN)
+ =========================================================== */
+
+type CategorySizeMap map[string][]string
+
+// kategori beden map (global – TÜM GRİD İÇİN TEK HEADER)
+func buildCategorySizeMap(rows []PdfRow) CategorySizeMap {
+ cm := make(CategorySizeMap)
+
+ // Her kategori için sabit default listeleri kullan
+ for _, cat := range categoryOrder {
+ base := defaultSizeListFor(cat)
+ if len(base) > 0 {
+ cm[cat] = append([]string{}, base...)
+ }
+ }
+
+ // İstersen ekstra bedenler varsa ekle (opsiyonel)
+ for _, r := range rows {
+ c := r.Category
+ if c == "" {
+ c = catTak
+ }
+ if _, ok := cm[c]; !ok {
+ cm[c] = []string{}
+ }
+ for size := range r.SizeQty {
+ if !contains(cm[c], size) {
+ cm[c] = append(cm[c], size)
+ }
+ }
+ }
+
+ return cm
+}
+
+/* ===========================================================
+ ORDER HEADER (Logo + Gold Label + Sağ Bilgi Kutusu)
+ =========================================================== */
+
+func drawOrderHeader(pdf *gofpdf.Fpdf, h *OrderHeader, showDesc bool) float64 {
+ pageW, _ := pdf.GetPageSize()
+ marginL := 10.0
+ y := 8.0
+
+ /* ----------------------------------------------------
+ 1) LOGO
+ ---------------------------------------------------- */
+ logo := "./public/Baggi-Tekstil-A.s-Logolu.jpeg"
+ if _, err := os.Stat(logo); err == nil {
+ pdf.ImageOptions(logo, marginL, y, 32, 0, false, gofpdf.ImageOptions{}, 0, "")
+ }
+
+ /* ----------------------------------------------------
+ 2) ALTIN BAŞLIK BAR
+ ---------------------------------------------------- */
+ titleW := 150.0
+ titleX := marginL + 40
+ titleY := y + 2
+
+ pdf.SetFillColor(149, 113, 22) // Baggi altın
+ pdf.Rect(titleX, titleY, titleW, 10, "F")
+
+ pdf.SetFont("dejavu-b", "", 13)
+ pdf.SetTextColor(255, 255, 255)
+ pdf.SetXY(titleX+4, titleY+2)
+ pdf.CellFormat(titleW-8, 6, "BAGGI TEKSTİL - SİPARİŞ FORMU", "", 0, "L", false, 0, "")
+
+ /* ----------------------------------------------------
+ 3) SAĞ TARAF BİLGİ KUTUSU
+ ---------------------------------------------------- */
+ boxW := 78.0
+ boxH := 30.0
+ boxX := pageW - marginL - boxW
+ boxY := y - 2
+
+ pdf.SetDrawColor(180, 180, 180)
+ pdf.Rect(boxX, boxY, boxW, boxH, "")
+
+ pdf.SetFont("dejavu-b", "", 9)
+ pdf.SetTextColor(149, 113, 22)
+ rep := strings.TrimSpace(h.CustomerRep)
+ if rep == "" {
+ rep = strings.TrimSpace(h.CreatedUser)
+ }
+
+ info := []string{
+ "Formun Basılma Tarihi: " + time.Now().Format("02.01.2006"),
+ "Sipariş Tarihi: " + h.OrderDate.Format("02.01.2006"),
+ "Sipariş No: " + h.OrderNumber,
+ "Müşteri Temsilcisi: " + rep, // 🔥 YENİ EKLENDİ
+ "Cari Kod: " + h.CurrAccCode,
+ "Müşteri: " + h.CurrAccName,
+ }
+
+ iy := boxY + 3
+ for _, line := range info {
+ pdf.SetXY(boxX+3, iy)
+ pdf.CellFormat(boxW-6, 4.5, line, "", 0, "L", false, 0, "")
+ iy += 4.5
+ }
+
+ /* ----------------------------------------------------
+ 4) ALT AYIRICI ÇİZGİ
+ ---------------------------------------------------- */
+ lineY := boxY + boxH + 3
+ pdf.SetDrawColor(120, 120, 120)
+ pdf.Line(marginL, lineY, pageW-marginL, lineY)
+ pdf.SetDrawColor(200, 200, 200)
+
+ y = lineY + 4
+
+ /* ----------------------------------------------------
+ 5) AÇIKLAMA (Varsa)
+ ---------------------------------------------------- */
+ if showDesc && strings.TrimSpace(h.Description) != "" {
+ text := strings.TrimSpace(h.Description)
+
+ pdf.SetFont("dejavu", "", 8) // wrap’te kullanılacak font
+ lineH := 4.0
+ textW := pageW - marginL*2 - 52
+
+ // Kaç satır olacağını hesapla
+ lines := pdf.SplitLines([]byte(text), textW)
+ descBoxH := float64(len(lines))*lineH + 4 // min boşluk
+
+ if descBoxH < 10 {
+ descBoxH = 10
+ }
+
+ pdf.SetDrawColor(210, 210, 210)
+ pdf.Rect(marginL, y, pageW-marginL*2, descBoxH, "")
+
+ // Başlık
+ pdf.SetFont("dejavu-b", "", 8)
+ pdf.SetTextColor(149, 113, 22)
+ pdf.SetXY(marginL+3, y+2)
+ pdf.CellFormat(40, 4, "Sipariş Genel Açıklaması:", "", 0, "L", false, 0, "")
+
+ // Metin
+ pdf.SetFont("dejavu", "", 8)
+ pdf.SetTextColor(30, 30, 30)
+ pdf.SetXY(marginL+48, y+2)
+ pdf.MultiCell(textW, lineH, text, "", "L", false)
+
+ y += descBoxH + 3
+ }
+
+ return y
+}
+
+/* ===========================================================
+ GRID HEADER — 2 katmanlı + 16 beden kolonlu
+ =========================================================== */
+
+/*
+===========================================================
+
+ GRID HEADER — AÇIKLAMA içinde kategori listesi + 16 beden kolonu
+ ===========================================================
+*/
+func drawGridHeader(pdf *gofpdf.Fpdf, layout pdfLayout, startY float64, catSizes CategorySizeMap) float64 {
+ pdf.SetFont("dejavu-b", "", 6)
+ pdf.SetDrawColor(baggiGrayBorderR, baggiGrayBorderG, baggiGrayBorderB)
+ pdf.SetFillColor(baggiCreamR, baggiCreamG, baggiCreamB)
+ pdf.SetTextColor(20, 20, 20) // 🟣 TÜM HEADER YAZILARI SİYAH
+
+ y := startY
+ x := layout.MarginL
+
+ totalHeaderH := float64(len(categoryOrder)) * layout.HeaderSizeH
+
+ centerLabel := func(h float64) float64 {
+ return y + (h/2.0 - 3)
+ }
+
+ /* ----------------------------------------------------
+ SOL BLOK (Model – Renk – Grup1 – Grup2)
+ ---------------------------------------------------- */
+ cols := []struct {
+ w float64
+ t string
+ }{
+ {layout.ColModelW, "MODEL"},
+ {layout.ColRenkW, "RENK"},
+ {layout.ColGroupW, "ÜRÜN ANA"},
+ {layout.ColGroupW2, "ÜRÜN ALT"},
+ }
+
+ for _, c := range cols {
+ pdf.Rect(x, y, c.w, totalHeaderH, "DF")
+ pdf.SetXY(x, centerLabel(totalHeaderH))
+ pdf.CellFormat(c.w, 6, c.t, "", 0, "C", false, 0, "")
+ x += c.w
+ }
+
+ /* ----------------------------------------------------
+ AÇIKLAMA BAŞLIĞI — TEK HÜCRE & DİKEY ORTALAMA
+ ---------------------------------------------------- */
+ descX := x
+ descW := layout.ColDescLeft
+
+ pdf.Rect(descX, y, descW, totalHeaderH, "DF")
+
+ // AÇIKLAMA yazısı ortalanacak
+ pdf.SetXY(descX, y+(totalHeaderH/2-3))
+ pdf.CellFormat(descW, 6, "AÇIKLAMA", "", 0, "C", false, 0, "")
+
+ /* ----------------------------------------------------
+ AÇIKLAMA sağında kategori listesi dikey şekilde
+ ---------------------------------------------------- */
+ catX := descX + 1
+ catY := y + 2
+
+ pdf.SetFont("dejavu", "", 6.2)
+ for _, cat := range categoryOrder {
+ label := categoryTitle[cat]
+ pdf.SetXY(catX+2, catY)
+ pdf.CellFormat(descW-4, 4.8, label, "", 0, "L", false, 0, "")
+ catY += layout.HeaderSizeH
+ }
+
+ /* ----------------------------------------------------
+ 16’lı BEDEN BLOĞU
+ ---------------------------------------------------- */
+ x = descX + descW
+ colW := layout.CentralW / 16.0
+
+ cy := y
+
+ for _, cat := range categoryOrder {
+
+ // Arka plan
+ pdf.SetFillColor(baggiCreamR, baggiCreamG, baggiCreamB)
+ pdf.Rect(x, cy, layout.CentralW, layout.HeaderSizeH, "DF")
+
+ sizes := catSizes[cat]
+ if len(sizes) == 0 {
+ sizes = defaultSizeListFor(cat)
+ }
+ if cat == catAksbir {
+ pdf.SetFont("dejavu", "", 5) // sadece aksesuar için küçük font
+ } else {
+ pdf.SetFont("dejavu", "", 6) // diğer tüm kategoriler normal font
+ }
+
+ xx := x
+ for i := 0; i < 16; i++ {
+ pdf.Rect(xx, cy, colW, layout.HeaderSizeH, "")
+ if i < len(sizes) {
+ pdf.SetXY(xx, cy+1)
+ pdf.CellFormat(colW, layout.HeaderSizeH-2, sizes[i], "", 0, "C", false, 0, "")
+ }
+ xx += colW
+ }
+
+ cy += layout.HeaderSizeH
+ }
+
+ /* ----------------------------------------------------
+ SAĞ BLOK (ADET – FİYAT – PB – TUTAR – TERMİN)
+ ---------------------------------------------------- */
+ rightX := x + 16*colW
+
+ rightCols := []struct {
+ w float64
+ t string
+ }{
+ {layout.ColQtyW, "ADET"},
+ {layout.ColPriceW, "FİYAT"},
+ {layout.ColCurW, "PB"},
+ {layout.ColAmountW, "TUTAR"},
+ {layout.ColTerminW, "TERMİN"},
+ }
+
+ for _, c := range rightCols {
+ pdf.Rect(rightX, y, c.w, totalHeaderH, "DF")
+ pdf.SetXY(rightX, centerLabel(totalHeaderH))
+ pdf.CellFormat(c.w, 6, c.t, "", 0, "C", false, 0, "")
+ rightX += c.w
+ }
+
+ return y + totalHeaderH
+}
+
+/* ===========================================================
+ AÇIKLAMA WRAP + DİNAMİK SATIR YÜKSEKLİĞİ
+ =========================================================== */
+
+// Açıklama kolonunu çok satırlı yazan helper
+func drawWrappedCell(pdf *gofpdf.Fpdf, text string, x, y, w, h float64) {
+ txt := strings.TrimSpace(text)
+ if txt == "" {
+ return
+ }
+
+ lineH := 3.2
+ lines := pdf.SplitLines([]byte(txt), w-2*OcellPadX)
+ cy := y + 1
+
+ for _, ln := range lines {
+ if cy+lineH > y+h {
+ break
+ }
+ pdf.SetXY(x+OcellPadX, cy)
+ pdf.CellFormat(w-2*OcellPadX, lineH, string(ln), "", 0, "L", false, 0, "")
+ cy += lineH
+ }
+}
+
+// Açıklamaya göre satır yüksekliği hesaplar
+func calcRowHeight(pdf *gofpdf.Fpdf, layout pdfLayout, row PdfRow) float64 {
+ base := layout.RowH
+ desc := strings.TrimSpace(row.Description)
+ if desc == "" {
+ return base
+ }
+
+ // Yeni: açıklama genişliği = sol + sağ
+ descW := layout.ColDescW
+
+ lines := pdf.SplitLines([]byte(desc), descW-2*OcellPadX)
+ lineH := 3.2
+ h := float64(len(lines))*lineH + 2
+
+ if h < base {
+ h = base
+ }
+ return h
+}
+
+/* ===========================================================
+ SATIR ÇİZİMİ — 16 kolonlu beden dizilimi (sola yaslı)
+ =========================================================== */
+
+/*
+===========================================================
+
+ SATIR ÇİZİMİ — Tek açıklama sütunu + 16 beden kolonu
+ ===========================================================
+*/
+func drawPdfRow(pdf *gofpdf.Fpdf, layout pdfLayout, y float64, row PdfRow, catSizes CategorySizeMap, rowH float64) float64 {
+
+ pdf.SetFont("dejavu", "", 7)
+ pdf.SetDrawColor(200, 200, 200)
+ pdf.SetLineWidth(0.15)
+ pdf.SetTextColor(0, 0, 0)
+
+ x := layout.MarginL
+ h := rowH
+
+ centerY := func() float64 {
+ return y + (h-3.5)/2
+ }
+
+ /* ----------------------------------------------------
+ 1) SOL BLOK
+ MODEL – RENK – ÜRÜN ANA – ÜRÜN ALT
+ ---------------------------------------------------- */
+
+ cols := []struct {
+ w float64
+ v string
+ }{
+ {layout.ColModelW, row.Model},
+ {layout.ColRenkW, row.Color},
+ {layout.ColGroupW, row.GroupMain},
+ {layout.ColGroupW2, row.GroupSub},
+ }
+
+ for _, c := range cols {
+ pdf.Rect(x, y, c.w, h, "")
+ pdf.SetXY(x+1.3, centerY())
+ pdf.CellFormat(c.w-2.6, 3.5, c.v, "", 0, "L", false, 0, "")
+ x += c.w
+ }
+
+ /* ----------------------------------------------------
+ 2) AÇIKLAMA (TEK BÜYÜK KOLON)
+ ---------------------------------------------------- */
+
+ descW := layout.ColDescLeft // açıklama = sadece sol kolon
+
+ pdf.Rect(x, y, descW, h, "")
+ drawWrappedCell(pdf, row.Description, x, y, descW, h)
+
+ x += descW
+
+ /* ----------------------------------------------------
+ 3) 16 BEDEN KOLONU
+ ---------------------------------------------------- */
+
+ colW := layout.CentralW / 16.0
+
+ // kategorinin beden listesi
+ sizes := catSizes[row.Category]
+
+ if len(sizes) == 0 {
+ tmp := make([]string, 0, len(row.SizeQty))
+ for s := range row.SizeQty {
+ tmp = append(tmp, s)
+ }
+ sort.Strings(tmp)
+ sizes = tmp
+ }
+
+ xx := x
+ for i := 0; i < 16; i++ {
+ if i < len(sizes) {
+ lbl := sizes[i]
+
+ if q, ok := row.SizeQty[lbl]; ok && q > 0 {
+ // 🔍 Bu beden kapalı mı?
+ isClosedSize := row.ClosedSizes != nil && row.ClosedSizes[lbl]
+
+ if isClosedSize {
+ // Gri dolgu + beyaz yazı
+ pdf.SetFillColor(220, 220, 220) // açık gri zemin
+ pdf.Rect(xx, y, colW, h, "DF") // doldurulmuş hücre
+ pdf.SetTextColor(255, 255, 255) // beyaz text
+ } else {
+ // Normal hücre: sadece border
+ pdf.Rect(xx, y, colW, h, "")
+ pdf.SetTextColor(0, 0, 0) // siyah text
+ }
+
+ pdf.SetXY(xx, centerY())
+ pdf.CellFormat(colW, 3.5, fmt.Sprintf("%d", q), "", 0, "C", false, 0, "")
+
+ // Sonraki işler için text rengini resetle
+ if isClosedSize {
+ pdf.SetTextColor(0, 0, 0)
+ }
+ } else {
+ // Bu beden kolonunda quantity yoksa normal boş hücre
+ pdf.Rect(xx, y, colW, h, "")
+ }
+ } else {
+ // 16 kolonun kalanları (header'da var ama bu kategoride kullanılmayan bedenler)
+ pdf.Rect(xx, y, colW, h, "")
+ }
+
+ xx += colW
+ }
+ x = xx
+
+ /* ----------------------------------------------------
+ 4) SAĞ BLOK: ADET – FİYAT – PB – TUTAR – TERMİN
+ ---------------------------------------------------- */
+
+ rightCols := []struct {
+ w float64
+ v string
+ alg string
+ }{
+ {layout.ColQtyW, fmt.Sprintf("%d", row.TotalQty), "C"},
+ {layout.ColPriceW, fmt.Sprintf("%.2f", row.Price), "R"},
+ {layout.ColCurW, row.Currency, "C"},
+ {layout.ColAmountW, fmt.Sprintf("%.2f", row.Amount), "R"},
+ {layout.ColTerminW, row.Termin, "C"},
+ }
+
+ for _, c := range rightCols {
+ pdf.Rect(x, y, c.w, h, "")
+ pdf.SetXY(x+0.6, centerY())
+ pdf.CellFormat(c.w-1.2, 3.5, c.v, "", 0, c.alg, false, 0, "")
+ x += c.w
+ }
+
+ return y + h
+}
+
+/* ===========================================================
+ FORMATLAYICI: TR PARA BİÇİMİ (1.234.567,89)
+ =========================================================== */
+/* ===========================================================
+ TOPLAM TUTAR / KDV / KDV DAHİL TOPLAM KUTUSU
+ (Sipariş Genel Açıklaması ile grid header arasında)
+=========================================================== */
+
+/* ===========================================================
+ PREMIUM BAGGI GOLD TOTALS BOX
+ (Sipariş Açıklaması ile Grid Header arasındaki kutu)
+=========================================================== */
+
+func drawTotalsBox(
+ pdf *gofpdf.Fpdf,
+ layout pdfLayout,
+ startY float64,
+ subtotal float64,
+ hasVat bool,
+ vatRate float64,
+ vatAmount float64,
+ totalWithVat float64,
+ currency string,
+) float64 {
+
+ x := layout.MarginL
+ w := layout.PageW - layout.MarginL - layout.MarginR
+
+ lineH := 6.0
+ rows := 1
+ if hasVat {
+ rows = 3
+ }
+
+ boxH := float64(rows)*lineH + 5
+
+ /* ----------------------------------------------------
+ ARKA PLAN + ALTIN ÇERÇEVE
+ ---------------------------------------------------- */
+ pdf.SetFillColor(255, 253, 245) // yumuşak krem
+ pdf.SetDrawColor(149, 113, 22) // Baggi gold
+ pdf.SetLineWidth(0.4)
+ pdf.Rect(x, startY, w, boxH, "DF")
+
+ /* ----------------------------------------------------
+ Genel metin stilleri
+ ---------------------------------------------------- */
+ labelX := x + 4
+ valueX := x + w - 70 // değerlerin sağda hizalanacağı kolon
+
+ pdf.SetTextColor(149, 113, 22) // Sol başlık gold
+ pdf.SetFont("dejavu-b", "", 8.5)
+
+ y := startY + 2
+
+ /* ----------------------------------------------------
+ 1️⃣ TOPLAM TUTAR
+ ---------------------------------------------------- */
+ pdf.SetXY(labelX, y)
+ pdf.CellFormat(80, lineH, "TOPLAM TUTAR", "", 0, "L", false, 0, "")
+
+ pdf.SetTextColor(201, 162, 39)
+ pdf.SetFont("dejavu-b", "", 9)
+
+ pdf.SetXY(valueX, y)
+ pdf.CellFormat(65, lineH,
+ fmt.Sprintf("%s %s", formatCurrencyTR(subtotal), currency),
+ "", 0, "R", false, 0, "")
+
+ y += lineH
+
+ /* ----------------------------------------------------
+ 2️⃣ KDV (opsiyonel)
+ ---------------------------------------------------- */
+ if hasVat {
+
+ pdf.SetTextColor(149, 113, 22) // gold başlık
+ pdf.SetFont("dejavu-b", "", 8.5)
+
+ pdf.SetXY(labelX, y)
+ pdf.CellFormat(80, lineH,
+ fmt.Sprintf("KDV (%%%g)", vatRate),
+ "", 0, "L", false, 0, "")
+
+ pdf.SetTextColor(20, 20, 20)
+ pdf.SetFont("dejavu-b", "", 9)
+
+ pdf.SetXY(valueX, y)
+ pdf.CellFormat(65, lineH,
+ fmt.Sprintf("%s %s", formatCurrencyTR(vatAmount), currency),
+ "", 0, "R", false, 0, "")
+
+ y += lineH
+
+ /* ----------------------------------------------------
+ 3️⃣ KDV DAHİL TOPLAM
+ ---------------------------------------------------- */
+ pdf.SetTextColor(201, 162, 39)
+ pdf.SetFont("dejavu-b", "", 8.5)
+
+ pdf.SetXY(labelX, y)
+ pdf.CellFormat(80, lineH, "KDV DAHİL TOPLAM TUTAR", "", 0, "L", false, 0, "")
+
+ pdf.SetTextColor(20, 20, 20)
+ pdf.SetFont("dejavu-b", "", 9)
+
+ pdf.SetXY(valueX, y)
+ pdf.CellFormat(65, lineH,
+ fmt.Sprintf("%s %s", formatCurrencyTR(totalWithVat), currency),
+ "", 0, "R", false, 0, "")
+ }
+
+ /* ----------------------------------------------------
+ Kutu altı boşluk
+ ---------------------------------------------------- */
+ return startY + boxH + 4
+}
+
+/* ===========================================================
+ GRUP TOPLAM BAR (Frontend tarzı sarı bar)
+ =========================================================== */
+
+func drawGroupSummaryBar(pdf *gofpdf.Fpdf, layout pdfLayout, groupName string, totalQty int, totalAmount float64, currency string) float64 {
+ y := pdf.GetY()
+ x := layout.MarginL
+ w := layout.PageW - layout.MarginL - layout.MarginR
+ h := 7.0
+
+ // Açık sarı zemin
+ pdf.SetFillColor(255, 249, 205) // #fff9cd benzeri
+ pdf.SetDrawColor(214, 192, 106)
+ pdf.Rect(x, y, w, h, "DF")
+
+ pdf.SetFont("dejavu-b", "", 8.5)
+ pdf.SetTextColor(20, 20, 20)
+
+ leftTxt := strings.ToUpper(strings.TrimSpace(groupName))
+ if leftTxt == "" {
+ leftTxt = "GENEL"
+ }
+ pdf.SetXY(x+2, y+1.2)
+ pdf.CellFormat(w*0.40, h-2.4, leftTxt, "", 0, "L", false, 0, "")
+
+ rightTxt := fmt.Sprintf(
+ "TOPLAM %s ADET: %d TOPLAM %s TUTAR: %s %s",
+ leftTxt, totalQty,
+ leftTxt, formatCurrencyTR(totalAmount), currency,
+ )
+
+ pdf.SetXY(x+w*0.35, y+1.2)
+ pdf.CellFormat(w*0.60-2, h-2.4, rightTxt, "", 0, "R", false, 0, "")
+
+ // reset
+ pdf.SetTextColor(0, 0, 0)
+ pdf.SetDrawColor(201, 162, 39)
+
+ pdf.SetY(y + h)
+ return y + h
+}
+
+/* ===========================================================
+ MULTIPAGE RENDER ENGINE (Header + GridHeader + Rows)
+ =========================================================== */
+
+func renderOrderGrid(pdf *gofpdf.Fpdf, header *OrderHeader, rows []PdfRow, hasVat bool, vatRate float64) {
+ layout := newPdfLayout(pdf)
+ catSizes := buildCategorySizeMap(rows)
+
+ // Grup: ÜRÜN ANA GRUBU
+ type group struct {
+ Name string
+ Rows []PdfRow
+ Adet int
+ Tutar float64
+ }
+
+ groups := map[string]*group{}
+ var order []string
+
+ for _, r := range rows {
+ name := strings.TrimSpace(r.GroupMain)
+ if name == "" {
+ name = "GENEL"
+ }
+ g, ok := groups[name]
+ if !ok {
+ g = &group{Name: name}
+ groups[name] = g
+ order = append(order, name)
+ }
+ g.Rows = append(g.Rows, r)
+ g.Adet += r.TotalQty
+ g.Tutar += r.Amount
+ }
+
+ groupSummaryH := 7.0
+
+ // 🔹 Genel toplam (grid içindeki satırlardan)
+ var subtotal float64
+ for _, r := range rows {
+ subtotal += r.Amount
+ }
+
+ // 🔹 KDV hesapla (hasVat ve vatRate, OrderPDFHandler'dan geliyor)
+ var vatAmount float64
+ totalWithVat := subtotal
+
+ if hasVat && vatRate > 0 {
+ vatAmount = subtotal * vatRate / 100.0
+ totalWithVat = subtotal + vatAmount
+ }
+
+ var y float64
+ firstPage := true
+
+ newPage := func(showDesc bool, showTotals bool) {
+ pdf.AddPage()
+ // Üst header (logo + sağ kutu + genel açıklama)
+ y = drawOrderHeader(pdf, header, showDesc)
+
+ // 🔸 İlk sayfada, header ile grid arasında TOPLAM kutusu
+ if showTotals {
+ y = drawTotalsBox(
+ pdf,
+ layout,
+ y,
+ subtotal,
+ hasVat,
+ vatRate,
+ vatAmount,
+ totalWithVat,
+ header.DocCurrency,
+ )
+ }
+
+ // Grid header
+ y = drawGridHeader(pdf, layout, y, catSizes)
+ y += 1
+ }
+
+ // İlk sayfa: açıklama + toplam kutusu
+ newPage(firstPage, true)
+ firstPage = false
+
+ for _, name := range order {
+ g := groups[name]
+
+ for _, row := range g.Rows {
+ rh := calcRowHeight(pdf, layout, row)
+
+ if y+rh+groupSummaryH+2 > layout.PageH-layout.MarginB {
+ // Sonraki sayfalarda: açıklama yok, toplam kutusu yok
+ newPage(false, false)
+ }
+ y = drawPdfRow(pdf, layout, y, row, catSizes, rh)
+ pdf.SetY(y) // 🔹 satırın altına imleci getir
+ }
+
+ // Grup toplam barı için yer kontrolü
+ if y+groupSummaryH+2 > layout.PageH-layout.MarginB {
+ newPage(false, false)
+ }
+ y = drawGroupSummaryBar(pdf, layout, g.Name, g.Adet, g.Tutar, header.DocCurrency)
+ y += 1
+ }
+
+ // ⚠️ Eski alttaki "Genel Toplam" yazdırma kaldırıldı; toplam kutusu artık üstte.
+}
+
+/* ===========================================================
+ HTTP HANDLER → /api/order/pdf/{id}
+ =========================================================== */
+
+func OrderPDFHandler(db *sql.DB) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ orderID := mux.Vars(r)["id"]
+ if orderID == "" {
+ http.Error(w, "missing order id", http.StatusBadRequest)
+ return
+ }
+ if db == nil {
+ http.Error(w, "db not initialized", http.StatusInternalServerError)
+ return
+ }
+
+ // Header
+ header, err := getOrderHeaderFromDB(db, orderID)
+ if err != nil {
+ log.Println("header error:", err)
+ http.Error(w, "header not found", http.StatusInternalServerError)
+ return
+ }
+
+ // Lines
+ lines, err := getOrderLinesFromDB(db, orderID)
+ if err != nil {
+ log.Println("lines error:", err)
+ http.Error(w, "lines not found", http.StatusInternalServerError)
+ return
+ }
+ // 🔹 Satırlardan KDV bilgisi yakala (ilk pozitif orana göre)
+ hasVat := false
+ var vatRate float64
+
+ for _, l := range lines {
+ if l.VatRate.Valid && l.VatRate.Float64 > 0 {
+ hasVat = true
+ vatRate = l.VatRate.Float64
+ break
+ }
+ }
+
+ // Normalize
+ rows := normalizeOrderLinesForPdf(lines)
+
+ // PDF
+ pdf := newOrderPdf()
+ renderOrderGrid(pdf, header, rows, hasVat, vatRate)
+
+ var buf bytes.Buffer
+ if err := pdf.Output(&buf); err != nil {
+ http.Error(w, "pdf output error: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/pdf")
+ w.Header().Set(
+ "Content-Disposition",
+ fmt.Sprintf("inline; filename=\"ORDER_%s.pdf\"", header.OrderNumber),
+ )
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write(buf.Bytes())
+ })
+}
diff --git a/svc/routes/order_validate.go b/svc/routes/order_validate.go
new file mode 100644
index 0000000..aa93ded
--- /dev/null
+++ b/svc/routes/order_validate.go
@@ -0,0 +1,45 @@
+package routes
+
+import (
+ "bssapp-backend/models"
+ "bssapp-backend/queries"
+ "database/sql"
+ "encoding/json"
+ "fmt"
+ "net/http"
+)
+
+type validateOrderRequest struct {
+ Header models.OrderHeader `json:"header"`
+ Lines []models.OrderDetail `json:"lines"`
+}
+
+type validateOrderResponse struct {
+ OK bool `json:"ok"`
+ Invalid []models.InvalidVariant `json:"invalid"`
+}
+
+func ValidateOrderHandler(mssql *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ var req validateOrderRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, fmt.Sprintf("JSON parse hatası: %v", err), http.StatusBadRequest)
+ return
+ }
+
+ invalid, err := queries.ValidateOrderVariants(mssql, req.Lines)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Validate error: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ resp := validateOrderResponse{
+ OK: len(invalid) == 0,
+ Invalid: invalid,
+ }
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+ _ = json.NewEncoder(w).Encode(resp)
+ }
+}
diff --git a/svc/routes/orderinventory.go b/svc/routes/orderinventory.go
new file mode 100644
index 0000000..8b4a824
--- /dev/null
+++ b/svc/routes/orderinventory.go
@@ -0,0 +1,89 @@
+package routes
+
+import (
+ "bssapp-backend/db"
+ "bssapp-backend/queries"
+ "context"
+ "encoding/json"
+ "log"
+ "net/http"
+ "time"
+)
+
+// ✅ GET /api/order-inventory?code=...&color=...&color2=...
+func GetOrderInventoryHandler(w http.ResponseWriter, r *http.Request) {
+ code := r.URL.Query().Get("code")
+ color := r.URL.Query().Get("color")
+ color2 := r.URL.Query().Get("color2")
+
+ if code == "" {
+ http.Error(w, "Eksik parametre: code gerekli", http.StatusBadRequest)
+ return
+ }
+
+ // 🔧 Normalize eksik renk parametreleri
+ if color == "" {
+ color = " "
+ }
+ if color2 == "" {
+ color2 = " "
+ }
+
+ log.Printf("🎨 [ORDERINV] İstek alındı -> code=%q | color=%q | color2=%q", code, color, color2)
+
+ // ✅ MSSQL bağlantısını ve timeout'u oluştur
+ ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
+ defer cancel()
+
+ rows, err := db.MssqlDB.QueryContext(ctx, queries.GetOrderInventory, code, color, color2)
+ if err != nil {
+ log.Printf("❌ [ORDERINV] SQL çalıştırılamadı: %v", err)
+ http.Error(w, "SQL hatası: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+ defer func() {
+ if cerr := rows.Close(); cerr != nil {
+ log.Printf("⚠️ [ORDERINV] rows.Close() hatası: %v", cerr)
+ }
+ }()
+
+ type Row struct {
+ UrunKodu string `json:"UrunKodu"`
+ RenkKodu string `json:"RenkKodu"`
+ RenkAciklamasi string `json:"RenkAciklamasi"`
+ Beden string `json:"Beden"`
+ Yaka string `json:"Yaka"`
+ KullanilabilirAdet float64 `json:"KullanilabilirAdet"`
+ }
+
+ var list []Row
+ for rows.Next() {
+ var r Row
+ if err := rows.Scan(
+ &r.UrunKodu,
+ &r.RenkKodu,
+ &r.RenkAciklamasi,
+ &r.Beden,
+ &r.Yaka,
+ &r.KullanilabilirAdet,
+ ); err != nil {
+ log.Printf("⚠️ [ORDERINV] Scan hatası: %v", err)
+ continue
+ }
+ list = append(list, r)
+ }
+
+ if err := rows.Err(); err != nil {
+ log.Printf("❌ [ORDERINV] Rows hatası: %v", err)
+ http.Error(w, "Veri okuma hatası: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ log.Printf("✅ [ORDERINV] %s / %s / %s -> %d kayıt döndü", code, color, color2, len(list))
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+ if err := json.NewEncoder(w).Encode(list); err != nil {
+ log.Printf("❌ [ORDERINV] JSON encode hatası: %v", err)
+ http.Error(w, "JSON encode başarısız", http.StatusInternalServerError)
+ }
+}
diff --git a/svc/routes/orderlist.go b/svc/routes/orderlist.go
new file mode 100644
index 0000000..078cd16
--- /dev/null
+++ b/svc/routes/orderlist.go
@@ -0,0 +1,126 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/db"
+ "bssapp-backend/models"
+ "bssapp-backend/queries"
+ "database/sql"
+ "encoding/json"
+ "log"
+ "net/http"
+ "strings"
+)
+
+// ======================================================
+// 📌 OrderListRoute — Sipariş Listeleme API (AUTHZ + SAFE)
+// ======================================================
+func OrderListRoute(mssql *sql.DB) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ // --------------------------------------------------
+ // 🔍 Query Param (RAW + TRIM)
+ // --------------------------------------------------
+ raw := r.URL.Query().Get("search")
+ search := strings.TrimSpace(raw)
+
+ log.Printf(
+ "📥 /api/orders/list search raw=%q trimmed=%q lenRaw=%d lenTrim=%d",
+ raw,
+ search,
+ len(raw),
+ len(search),
+ )
+
+ // --------------------------------------------------
+ // 🗄️ SQL CALL (WITH CONTEXT)
+ // --------------------------------------------------
+ rows, err := queries.GetOrderList(
+ r.Context(),
+ mssql,
+ db.PgDB, // ✅ artık var
+ search,
+ )
+
+ if err != nil {
+ log.Printf("❌ SQL sorgu hatası: %v", err)
+ http.Error(w, "Veritabanı hatası", http.StatusInternalServerError)
+ return
+ }
+ defer rows.Close()
+
+ // --------------------------------------------------
+ // 📦 Sonuç Listesi
+ // --------------------------------------------------
+ list := make([]models.OrderList, 0, 100)
+ count := 0
+
+ // ==================================================
+ // 🧠 SCAN — SQL SELECT ile BİRE BİR (14 kolon)
+ // ==================================================
+ for rows.Next() {
+
+ var o models.OrderList
+
+ err = rows.Scan(
+ &o.OrderHeaderID, // 1
+ &o.OrderNumber, // 2
+ &o.OrderDate, // 3
+
+ &o.CurrAccCode, // 4
+ &o.CurrAccDescription, // 5
+
+ &o.MusteriTemsilcisi, // 6
+ &o.Piyasa, // 7
+
+ &o.CreditableConfirmedDate, // 8
+ &o.DocCurrencyCode, // 9
+
+ &o.TotalAmount, // 10
+ &o.TotalAmountUSD, // 11
+
+ &o.IsCreditableConfirmed, // 12
+ &o.Description, // 13
+
+ &o.ExchangeRateUSD, // 14
+ )
+
+ if err != nil {
+ log.Printf(
+ "⚠️ SCAN HATASI | OrderHeaderID=%v | err=%v",
+ o.OrderHeaderID,
+ err,
+ )
+ continue
+ }
+
+ list = append(list, o)
+ count++
+ }
+
+ if err := rows.Err(); err != nil {
+ log.Printf("⚠️ rows.Err(): %v", err)
+ }
+
+ // --------------------------------------------------
+ // 📊 RESULT LOG
+ // --------------------------------------------------
+ claims, _ := auth.GetClaimsFromContext(r.Context())
+
+ log.Printf(
+ "✅ Order list DONE | user=%d | search=%q | resultCount=%d",
+ claims.ID,
+ search,
+ count,
+ )
+
+ // --------------------------------------------------
+ // 📤 JSON OUTPUT
+ // --------------------------------------------------
+ if err := json.NewEncoder(w).Encode(list); err != nil {
+ log.Printf("❌ JSON encode hatası: %v", err)
+ }
+ })
+}
diff --git a/svc/routes/orderpricelistb2b.go b/svc/routes/orderpricelistb2b.go
new file mode 100644
index 0000000..a3b625e
--- /dev/null
+++ b/svc/routes/orderpricelistb2b.go
@@ -0,0 +1,131 @@
+package routes
+
+import (
+ "bssapp-backend/models"
+ "bssapp-backend/queries"
+ "database/sql"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+)
+
+// ✅ Model + pb/currency paramına göre fiyat döner (debug log’lu)
+func GetOrderPriceListB2BHandler(pg *sql.DB, mssql *sql.DB) http.HandlerFunc {
+
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ model := r.URL.Query().Get("model")
+ if model == "" {
+ model = r.URL.Query().Get("code")
+ }
+
+ cur := r.URL.Query().Get("currency")
+ if cur == "" {
+ cur = r.URL.Query().Get("pb")
+ }
+
+ if model == "" || cur == "" {
+ http.Error(w, "Eksik parametre", 400)
+ return
+ }
+
+ cur = strings.ToUpper(cur)
+
+ fmt.Printf("💰 [MINPRICE] model=%s cur=%s\n", model, cur)
+
+ var (
+ price float64
+ rate float64 = 1
+ )
+
+ /* ======================
+ USD / EUR → Direkt
+ ====================== */
+
+ if cur == "USD" || cur == "EUR" {
+
+ p, err := queries.GetOrderPriceListB2B(pg, model, cur)
+
+ if err != nil {
+ http.Error(w, cur+" fiyat bulunamadı", 404)
+ return
+ }
+
+ price = p.Price
+
+ } else if cur == "TRY" {
+
+ /* ======================
+ TRY → USD * Kur
+ ====================== */
+
+ // 1️⃣ USD fiyat
+ p, err := queries.GetOrderPriceListB2B(pg, model, "USD")
+ if err != nil {
+ http.Error(w, "USD fiyat bulunamadı", 404)
+ return
+ }
+
+ // 2️⃣ USD→TRY satış
+ r, err := queries.GetCachedCurrencyV3(mssql, "USD")
+
+ if err != nil {
+ http.Error(w, "USD kuru bulunamadı", 503)
+ return
+ }
+
+ rate = r.Rate
+ price = p.Price * rate
+
+ } else if cur == "GBP" {
+
+ /* ======================
+ GBP → USD Bridge
+ ====================== */
+
+ // 1️⃣ USD fiyat
+ p, err := queries.GetOrderPriceListB2B(pg, model, "USD")
+ if err != nil {
+ http.Error(w, "USD fiyat bulunamadı", 404)
+ return
+ }
+
+ // 2️⃣ USD→TRY
+ usd, err := queries.GetCachedCurrencyV3(mssql, "USD")
+
+ if err != nil {
+ http.Error(w, "USD kuru yok", 503)
+ return
+ }
+
+ // 3️⃣ GBP→TRY
+ gbp, err := queries.GetCachedCurrencyV3(mssql, "GBP")
+ if err != nil {
+ http.Error(w, "GBP kuru yok", 503)
+ return
+ }
+
+ rate = usd.Rate / gbp.Rate
+ price = p.Price * rate
+
+ } else {
+
+ http.Error(w, "Desteklenmeyen para birimi", 400)
+ return
+ }
+
+ resp := models.OrderPriceListB2B{
+ ModelCode: model,
+ CurrencyCode: cur,
+ Price: price,
+ RateToTRY: rate,
+ PriceTRY: price,
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(resp)
+
+ fmt.Printf("✅ [MINPRICE] %s → %.2f %s\n", model, price, cur)
+ }
+}
diff --git a/svc/routes/orders.go b/svc/routes/orders.go
new file mode 100644
index 0000000..e5c84f0
--- /dev/null
+++ b/svc/routes/orders.go
@@ -0,0 +1,239 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/models"
+ "bssapp-backend/queries"
+ "bssapp-backend/utils"
+ "database/sql"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+
+ "github.com/gorilla/mux"
+)
+
+// ================================
+// POST /api/order/update
+// ================================
+func UpdateOrderHandler(w http.ResponseWriter, r *http.Request) {
+
+ // --------------------------------------------------
+ // 1️⃣ JWT CLAIMS (TEK KAYNAK)
+ // --------------------------------------------------
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ user := utils.UserFromClaims(claims)
+
+ if !ok || claims == nil {
+ http.Error(w, "Kullanıcı doğrulanamadı", http.StatusUnauthorized)
+ return
+ }
+
+ user = utils.UserFromClaims(claims)
+ if user == nil {
+ http.Error(w, "Kullanıcı doğrulanamadı", http.StatusUnauthorized)
+ return
+ }
+
+ // --------------------------------------------------
+ // 2️⃣ REQUEST BODY
+ // --------------------------------------------------
+ var payload struct {
+ Header models.OrderHeader `json:"header"`
+ Lines []models.OrderDetail `json:"lines"`
+ }
+
+ if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
+ http.Error(w, "Geçersiz JSON", http.StatusBadRequest)
+ return
+ }
+
+ // --------------------------------------------------
+ // 3️⃣ UPDATE
+ // --------------------------------------------------
+ results, err := queries.UpdateOrder(
+ payload.Header,
+ payload.Lines,
+ user, // ✅ *models.User
+ )
+
+ if err != nil {
+
+ // ✅ VALIDATION ERROR
+ var vErr *models.ValidationError
+ if errors.As(err, &vErr) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusBadRequest)
+ _ = json.NewEncoder(w).Encode(vErr)
+ return
+ }
+
+ // ❌ SYSTEM ERROR
+ utils.LogError("ORDER_UPDATE", err)
+
+ w.WriteHeader(http.StatusInternalServerError)
+ _ = json.NewEncoder(w).Encode(map[string]any{
+ "code": "ORDER_UPDATE_FAILED",
+ "message": "Sipariş kaydedilirken beklenmeyen bir hata oluştu.",
+ })
+ return
+ }
+
+ // --------------------------------------------------
+ // 4️⃣ RESPONSE
+ // --------------------------------------------------
+ w.Header().Set("Content-Type", "application/json")
+ _ = json.NewEncoder(w).Encode(map[string]any{
+ "success": true,
+ "lines": results,
+ })
+}
+
+// -------------------------------------------------------------
+// 🟩 CREATE — /api/order/create
+// -------------------------------------------------------------
+func CreateOrderHandler(pg *sql.DB, mssql *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ // --------------------------------------------------
+ // JWT CLAIMS
+ // --------------------------------------------------
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ user := utils.UserFromClaims(claims)
+
+ if !ok || claims == nil {
+ http.Error(w, "Yetkisiz", http.StatusUnauthorized)
+ return
+ }
+
+ user = utils.UserFromClaims(claims)
+ if user == nil {
+ http.Error(w, "Yetkisiz", http.StatusUnauthorized)
+ return
+ }
+
+ var payload struct {
+ Header models.OrderHeader `json:"header"`
+ Lines []models.OrderDetail `json:"lines"`
+ }
+
+ if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
+ http.Error(w, "Geçersiz JSON", http.StatusBadRequest)
+ return
+ }
+
+ // --------------------------------------------------
+ // INSERT
+ // --------------------------------------------------
+ newID, lineResults, err := queries.InsertOrder(
+ payload.Header,
+ payload.Lines,
+ user, // ✅ *models.User
+ )
+
+ if err != nil {
+
+ var vErr *models.ValidationError
+ if errors.As(err, &vErr) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusBadRequest)
+ _ = json.NewEncoder(w).Encode(vErr)
+ return
+ }
+
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ orderNo := ""
+ if payload.Header.OrderNumber.Valid {
+ orderNo = payload.Header.OrderNumber.String
+ }
+
+ // --------------------------------------------------
+ // RESPONSE
+ // --------------------------------------------------
+ _ = json.NewEncoder(w).Encode(map[string]any{
+ "status": "success",
+ "orderID": newID,
+ "orderNumber": orderNo,
+ "lineResults": lineResults,
+ })
+ }
+}
+
+// -------------------------------------------------------------
+// 🟨 GET BY ID — /api/order/get/{id}
+// -------------------------------------------------------------
+func GetOrderByIDHandler(mssql *sql.DB) http.HandlerFunc {
+
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ orderID := mux.Vars(r)["id"]
+ if orderID == "" {
+ http.Error(w, "Eksik parametre: id", http.StatusBadRequest)
+ return
+ }
+
+ fmt.Printf("📦 /api/order/get/%s çağrıldı\n", orderID)
+
+ header, lines, err := queries.GetOrderByID(orderID)
+
+ switch {
+ case errors.Is(err, sql.ErrNoRows):
+ http.Error(w, fmt.Sprintf("Sipariş bulunamadı: %s", orderID), http.StatusNotFound)
+ return
+
+ case err != nil:
+ http.Error(w, fmt.Sprintf("Veritabanı hatası: %v", err), http.StatusInternalServerError)
+ return
+
+ default:
+ _ = json.NewEncoder(w).Encode(map[string]any{
+ "header": header,
+ "lines": lines,
+ })
+ }
+ }
+}
+
+// -------------------------------------------------------------
+// 🔎 ORDER EXISTS — /api/order/check/{id}
+// -------------------------------------------------------------
+func OrderExistsHandler(db *sql.DB) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ id := mux.Vars(r)["id"]
+
+ var count int
+ err := db.QueryRow(`
+ SELECT COUNT(*)
+ FROM trOrderHeader
+ WHERE OrderHeaderID = @p1
+ `, id).Scan(&count)
+
+ if err != nil {
+ http.Error(w, "db error", http.StatusInternalServerError)
+ return
+ }
+
+ _ = json.NewEncoder(w).Encode(map[string]any{
+ "exists": count > 0,
+ })
+ })
+}
diff --git a/svc/routes/password_forgot.go b/svc/routes/password_forgot.go
new file mode 100644
index 0000000..a04aa9a
--- /dev/null
+++ b/svc/routes/password_forgot.go
@@ -0,0 +1,116 @@
+package routes
+
+import (
+ "bssapp-backend/internal/auditlog"
+ "bssapp-backend/internal/mailer"
+ "bssapp-backend/internal/security"
+ "database/sql"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "os"
+ "strings"
+ "time"
+)
+
+func ForgotPasswordHandler(
+ db *sql.DB,
+ mailer *mailer.GraphMailer,
+) http.HandlerFunc {
+
+ type request struct {
+ Email string `json:"email"`
+ }
+
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ // -------------------------------------------------------
+ // 1️⃣ Request parse (enumeration yok)
+ // -------------------------------------------------------
+ var req request
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ respondOK(w)
+ return
+ }
+
+ email := strings.TrimSpace(strings.ToLower(req.Email))
+ if email == "" {
+ respondOK(w)
+ return
+ }
+
+ // -------------------------------------------------------
+ // 2️⃣ Aktif kullanıcıyı bul
+ // -------------------------------------------------------
+ var userID int64
+ err := db.QueryRow(`
+ SELECT id
+ FROM mk_dfusr
+ WHERE email = $1
+ AND is_active = true
+ `, email).Scan(&userID)
+
+ if err != nil {
+ // ❗ kullanıcı yok → bilgi sızdırma yok
+ respondOK(w)
+ return
+ }
+
+ // -------------------------------------------------------
+ // 3️⃣ Reset token üret
+ // -------------------------------------------------------
+ plain, hash, err := security.GenerateResetToken()
+ if err != nil {
+ respondOK(w)
+ return
+ }
+
+ expires := time.Now().Add(30 * time.Minute)
+
+ // -------------------------------------------------------
+ // 4️⃣ DB’ye SADECE HASH kaydet
+ // -------------------------------------------------------
+ _, _ = db.Exec(`
+ INSERT INTO dfusr_password_reset (
+ dfusr_id,
+ token,
+ expires_at
+ )
+ VALUES ($1, $2, $3)
+ `, userID, hash, expires)
+
+ // -------------------------------------------------------
+ // 5️⃣ Reset URL (PLAIN token)
+ // -------------------------------------------------------
+ resetURL := fmt.Sprintf(
+ "%s/password-reset/%s",
+ os.Getenv("FRONTEND_URL"),
+ plain,
+ )
+
+ // -------------------------------------------------------
+ // 6️⃣ Mail gönder (fail olsa bile enumeration yok)
+ // -------------------------------------------------------
+ _ = mailer.SendPasswordResetMail(email, resetURL)
+
+ // -------------------------------------------------------
+ // 7️⃣ AUDIT LOG
+ // -------------------------------------------------------
+ auditlog.Write(auditlog.ActivityLog{
+ ActionType: "PASSWORD_FORGOT_REQUEST",
+ ActionCategory: "security",
+ ActionTarget: email,
+ IsSuccess: true,
+ })
+
+ respondOK(w)
+ }
+}
+
+func respondOK(w http.ResponseWriter) {
+ _ = json.NewEncoder(w).Encode(map[string]bool{
+ "success": true,
+ })
+}
diff --git a/svc/routes/password_reset.go b/svc/routes/password_reset.go
new file mode 100644
index 0000000..b555139
--- /dev/null
+++ b/svc/routes/password_reset.go
@@ -0,0 +1,126 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/internal/auditlog"
+ "bssapp-backend/internal/security"
+ "database/sql"
+ "encoding/json"
+ "net/http"
+
+ "golang.org/x/crypto/bcrypt"
+)
+
+func ChangeOwnPasswordHandler(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ // --------------------------------------------------
+ // 1️⃣ JWT CLAIMS
+ // --------------------------------------------------
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ // --------------------------------------------------
+ // 2️⃣ PAYLOAD
+ // --------------------------------------------------
+ var req struct {
+ CurrentPassword string `json:"current_password"`
+ NewPassword string `json:"new_password"`
+ }
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "invalid payload", http.StatusBadRequest)
+ return
+ }
+
+ if req.CurrentPassword == "" || req.NewPassword == "" {
+ http.Error(w, "current_password and new_password required", http.StatusBadRequest)
+ return
+ }
+
+ // --------------------------------------------------
+ // 3️⃣ PASSWORD POLICY
+ // --------------------------------------------------
+ if err := security.ValidatePassword(req.NewPassword); err != nil {
+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+ return
+ }
+
+ // --------------------------------------------------
+ // 4️⃣ MEVCUT HASH ÇEK
+ // --------------------------------------------------
+ var currentHash string
+ err := db.QueryRow(`
+ SELECT password_hash
+ FROM mk_dfusr
+ WHERE id = $1
+ AND is_active = true
+ `, claims.ID).Scan(¤tHash)
+
+ if err != nil {
+ http.Error(w, "user not found", http.StatusUnauthorized)
+ return
+ }
+
+ // --------------------------------------------------
+ // 5️⃣ CURRENT PASSWORD CHECK
+ // --------------------------------------------------
+ if bcrypt.CompareHashAndPassword(
+ []byte(currentHash),
+ []byte(req.CurrentPassword),
+ ) != nil {
+ http.Error(w, "current password incorrect", http.StatusUnauthorized)
+ return
+ }
+
+ // --------------------------------------------------
+ // 6️⃣ NEW HASH
+ // --------------------------------------------------
+ newHash, err := bcrypt.GenerateFromPassword(
+ []byte(req.NewPassword),
+ bcrypt.DefaultCost,
+ )
+ if err != nil {
+ http.Error(w, "hash error", http.StatusInternalServerError)
+ return
+ }
+
+ // --------------------------------------------------
+ // 7️⃣ UPDATE (⚠️ force_password_change DEĞİŞMEZ)
+ // --------------------------------------------------
+ _, err = db.Exec(`
+ UPDATE mk_dfusr
+ SET
+ password_hash = $1,
+ password_updated_at = now(),
+ updated_at = now()
+ WHERE id = $2
+ `, string(newHash), claims.ID)
+
+ if err != nil {
+ http.Error(w, "password update failed", http.StatusInternalServerError)
+ return
+ }
+
+ // --------------------------------------------------
+ // 8️⃣ AUDIT
+ // --------------------------------------------------
+ auditlog.Write(auditlog.ActivityLog{
+ ActionType: "PASSWORD_CHANGED",
+ ActionCategory: "security",
+ ActionTarget: claims.Username,
+ IsSuccess: true,
+ })
+
+ // --------------------------------------------------
+ // 9️⃣ RESPONSE
+ // --------------------------------------------------
+ _ = json.NewEncoder(w).Encode(map[string]any{
+ "success": true,
+ })
+ }
+}
diff --git a/svc/routes/password_reset_complete.go b/svc/routes/password_reset_complete.go
new file mode 100644
index 0000000..bdcab3a
--- /dev/null
+++ b/svc/routes/password_reset_complete.go
@@ -0,0 +1,152 @@
+// routes/password_reset_complete.go
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/internal/auditlog"
+ "bssapp-backend/internal/security"
+ "bssapp-backend/repository"
+ "database/sql"
+ "encoding/json"
+ "net/http"
+ "time"
+
+ "golang.org/x/crypto/bcrypt"
+)
+
+func CompletePasswordResetHandler(db *sql.DB) http.HandlerFunc {
+
+ type request struct {
+ Token string `json:"token"`
+ Password string `json:"password"`
+ }
+
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ var req request
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Token == "" || req.Password == "" {
+ http.Error(w, "invalid payload", http.StatusBadRequest)
+ return
+ }
+
+ if err := security.ValidatePassword(req.Password); err != nil {
+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+ return
+ }
+
+ tokenHash := security.HashToken(req.Token)
+
+ // 1) token doğrula
+ var userID int64
+ var expiresAt time.Time
+
+ err := db.QueryRow(`
+ SELECT mk_dfusr_id, expires_at
+ FROM mk_dfusr_password_reset
+ WHERE token = $1
+ AND used_at IS NULL
+ `, tokenHash).Scan(&userID, &expiresAt)
+
+ if err != nil || time.Now().After(expiresAt) {
+ http.Error(w, "invalid or expired token", http.StatusUnprocessableEntity)
+ return
+ }
+
+ // 2) yeni hash
+ passHash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
+ if err != nil {
+ http.Error(w, "hash error", http.StatusInternalServerError)
+ return
+ }
+
+ // 3) tx: şifre + used_at
+ tx, err := db.Begin()
+ if err != nil {
+ http.Error(w, "tx error", http.StatusInternalServerError)
+ return
+ }
+ defer tx.Rollback()
+
+ // mk_dfusr güncelle
+ if _, err := tx.Exec(`
+ UPDATE mk_dfusr
+ SET password_hash = $1,
+ force_password_change = false,
+ password_updated_at = now(),
+ updated_at = now()
+ WHERE id = $2
+ `, string(passHash), userID); err != nil {
+ http.Error(w, "update failed", http.StatusInternalServerError)
+ return
+ }
+
+ // token tüket
+ if _, err := tx.Exec(`
+ UPDATE mk_dfusr_password_reset
+ SET used_at = now()
+ WHERE token = $1
+ `, tokenHash); err != nil {
+ http.Error(w, "token update failed", http.StatusInternalServerError)
+ return
+ }
+
+ if err := tx.Commit(); err != nil {
+ http.Error(w, "commit failed", http.StatusInternalServerError)
+ return
+ }
+
+ // 4) refresh revoke
+ _ = repository.NewRefreshTokenRepository(db).RevokeAllForUser(userID)
+
+ // 5) user + perms + jwt
+ mkRepo := repository.NewMkUserRepository(db)
+ u, err := mkRepo.GetByID(userID)
+ if err != nil {
+ http.Error(w, "user fetch failed", http.StatusInternalServerError)
+ return
+ }
+
+ permRepo := repository.NewPermissionRepository(db)
+ perms, _ := permRepo.GetPermissionsByRoleID(u.RoleID)
+
+ // ✅ CLAIMS BUILD
+ claims := auth.BuildClaimsFromUser(u, 15*time.Minute)
+
+ // ✅ TOKEN
+ jwtStr, err := auth.GenerateToken(
+ claims,
+ u.Username,
+ false,
+ )
+ if err != nil {
+ http.Error(w, "token generation failed", http.StatusInternalServerError)
+ return
+ }
+
+ auditlog.Write(auditlog.ActivityLog{
+ ActionType: "PASSWORD_RESET_COMPLETED",
+ ActionCategory: "security",
+ ActionTarget: u.Username,
+ IsSuccess: true,
+ })
+
+ _ = json.NewEncoder(w).Encode(map[string]any{
+ "success": true,
+ "token": jwtStr,
+ "user": map[string]any{
+ "id": u.ID,
+ "username": u.Username,
+ "email": u.Email,
+ "is_active": u.IsActive,
+ "role_id": u.RoleID,
+ "role_code": u.RoleCode,
+ "force_password_change": false,
+ "v3_username": u.V3Username,
+ "v3_usergroup": u.V3UserGroup,
+ },
+ "permissions": perms,
+ })
+ }
+}
diff --git a/svc/routes/password_reset_validate.go b/svc/routes/password_reset_validate.go
new file mode 100644
index 0000000..9c50ead
--- /dev/null
+++ b/svc/routes/password_reset_validate.go
@@ -0,0 +1,63 @@
+package routes
+
+import (
+ "crypto/sha256"
+ "database/sql"
+ "encoding/hex"
+ "net/http"
+ "time"
+
+ "github.com/gorilla/mux"
+)
+
+// GET /api/password/reset/validate/{token}
+func ValidatePasswordResetTokenHandler(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ token := mux.Vars(r)["token"]
+ if token == "" {
+ http.NotFound(w, r)
+ return
+ }
+
+ // 🔐 plain token -> hash
+ h := sha256.Sum256([]byte(token))
+ tokenHash := hex.EncodeToString(h[:])
+
+ var (
+ userID int64
+ expiresAt time.Time
+ usedAt sql.NullTime
+ )
+
+ err := db.QueryRow(`
+ SELECT user_id, expires_at, used_at
+ FROM password_reset_tokens
+ WHERE token_hash = $1
+ LIMIT 1
+ `, tokenHash).Scan(&userID, &expiresAt, &usedAt)
+
+ if err != nil {
+ // ❗ bilgi sızdırma yok
+ http.NotFound(w, r)
+ return
+ }
+
+ // ⏰ Süre kontrolü
+ if time.Now().After(expiresAt) {
+ http.NotFound(w, r)
+ return
+ }
+
+ // 🔁 Tek kullanımlık
+ if usedAt.Valid {
+ http.Error(w, "token already used", http.StatusGone)
+ return
+ }
+
+ // ✅ TOKEN GEÇERLİ
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(`{"valid":true}`))
+ }
+}
diff --git a/svc/routes/permission_debug.go b/svc/routes/permission_debug.go
new file mode 100644
index 0000000..4759fc0
--- /dev/null
+++ b/svc/routes/permission_debug.go
@@ -0,0 +1,63 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "database/sql"
+ "encoding/json"
+ "net/http"
+)
+
+func DebugPermissionV2(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ // sadece auth kontrolü
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", 401)
+ return
+ }
+
+ module := r.URL.Query().Get("module")
+ action := r.URL.Query().Get("action")
+
+ if module == "" || action == "" {
+ http.Error(w, "module & action required", 400)
+ return
+ }
+
+ rows, err := db.Query(`
+ SELECT
+ r.id,
+ r.code,
+ rp.allowed
+ FROM dfrole r
+ LEFT JOIN mk_sys_role_permissions rp
+ ON rp.role_id = r.id
+ AND rp.module_code = $1
+ AND rp.action = $2
+ ORDER BY r.id
+ `, module, action)
+
+ if err != nil {
+ http.Error(w, "db error", 500)
+ return
+ }
+ defer rows.Close()
+
+ type Row struct {
+ RoleID int `json:"role_id"`
+ Code string `json:"code"`
+ Allowed bool `json:"allowed"`
+ }
+
+ var list []Row
+
+ for rows.Next() {
+ var r Row
+ _ = rows.Scan(&r.RoleID, &r.Code, &r.Allowed)
+ list = append(list, r)
+ }
+
+ json.NewEncoder(w).Encode(list)
+ }
+}
diff --git a/svc/routes/permission_matrix_v2.go b/svc/routes/permission_matrix_v2.go
new file mode 100644
index 0000000..bf37ff9
--- /dev/null
+++ b/svc/routes/permission_matrix_v2.go
@@ -0,0 +1,203 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/internal/auditlog"
+ "bssapp-backend/permissions"
+ "database/sql"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/gorilla/mux"
+)
+
+func GetRolePermissionMatrix(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", 401)
+ return
+ }
+
+ roleID, _ := strconv.Atoi(mux.Vars(r)["id"])
+
+ rows, err := db.Query(`
+ SELECT
+ module_code,
+ action,
+ allowed
+ FROM mk_sys_role_permissions
+ WHERE role_id=$1
+ `, roleID)
+
+ if err != nil {
+ http.Error(w, "db error", 500)
+ return
+ }
+ defer rows.Close()
+
+ var list []permissions.PermissionMatrixRow
+
+ for rows.Next() {
+
+ var row permissions.PermissionMatrixRow
+
+ if err := rows.Scan(
+ &row.Module,
+ &row.Action,
+ &row.Allowed,
+ ); err != nil {
+ http.Error(w, "scan error", 500)
+ return
+ }
+
+ row.Source = "role"
+
+ list = append(list, row)
+ }
+
+ json.NewEncoder(w).Encode(list)
+ }
+}
+
+func SaveRolePermissionMatrix(db *sql.DB) http.HandlerFunc {
+
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", 401)
+ return
+ }
+
+ roleID, _ := strconv.Atoi(mux.Vars(r)["id"])
+
+ var list []permissions.PermissionMatrixRow
+
+ if err := json.NewDecoder(r.Body).Decode(&list); err != nil {
+ http.Error(w, "bad payload", 400)
+ return
+ }
+
+ repo := permissions.NewPermissionRepository(db)
+
+ // ================= OLD =================
+
+ oldRows, _ := repo.GetPermissionMatrixForRoles([]int{roleID})
+
+ oldMap := map[string]bool{}
+
+ for _, p := range oldRows {
+ key := p.Module + ":" + p.Action
+ oldMap[key] = p.Allowed
+ }
+
+ // ================= DIFF =================
+
+ var changes []map[string]any
+
+ for _, p := range list {
+
+ key := p.Module + ":" + p.Action
+
+ oldVal, ok := oldMap[key]
+ if !ok {
+ oldVal = false
+ }
+
+ if oldVal != p.Allowed {
+
+ changes = append(changes, map[string]any{
+ "module": p.Module,
+ "action": p.Action,
+ "before": oldVal,
+ "after": p.Allowed,
+ })
+ }
+ }
+
+ // ================= SAVE =================
+
+ tx, err := db.Begin()
+ if err != nil {
+ http.Error(w, "tx error", 500)
+ return
+ }
+ defer tx.Rollback()
+
+ stmt, err := tx.Prepare(`
+ INSERT INTO mk_sys_role_permissions
+ (role_id,module_code,action,allowed)
+ VALUES ($1,$2,$3,$4)
+
+ ON CONFLICT
+ (role_id,module_code,action)
+ DO UPDATE SET allowed=EXCLUDED.allowed
+ `)
+
+ if err != nil {
+ http.Error(w, "prepare error", 500)
+ return
+ }
+ defer stmt.Close()
+
+ for _, p := range list {
+
+ if _, err := stmt.Exec(
+ roleID,
+ p.Module,
+ p.Action,
+ p.Allowed,
+ ); err != nil {
+ http.Error(w, "exec error", 500)
+ return
+ }
+ }
+
+ if err := tx.Commit(); err != nil {
+ http.Error(w, "commit error", 500)
+ return
+ }
+
+ // ================= AUDIT =================
+
+ if len(changes) > 0 {
+
+ var roleCode string
+
+ _ = db.QueryRow(`
+ SELECT code FROM dfrole WHERE id=$1
+ `, roleID).Scan(&roleCode)
+
+ auditlog.Enqueue(r.Context(), auditlog.ActivityLog{
+
+ ActionType: "role_permission_change",
+ ActionCategory: "role_permission",
+
+ ActionTarget: fmt.Sprintf("/api/roles/%d/permissions", roleID),
+
+ Description: "role permission matrix updated",
+
+ Username: claims.Username,
+ RoleCode: claims.RoleCode,
+ DfUsrID: int64(claims.ID),
+
+ ChangeBefore: map[string]any{
+ "permissions": oldRows,
+ },
+
+ ChangeAfter: map[string]any{
+ "changes": changes,
+ },
+
+ IsSuccess: true,
+ })
+ }
+
+ json.NewEncoder(w).Encode(map[string]bool{
+ "success": true,
+ })
+ }
+}
diff --git a/svc/routes/permissions.go b/svc/routes/permissions.go
new file mode 100644
index 0000000..aef0d08
--- /dev/null
+++ b/svc/routes/permissions.go
@@ -0,0 +1,114 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/permissions"
+ "database/sql"
+ "encoding/json"
+ "net/http"
+)
+
+/* =====================================================
+ HANDLER
+===================================================== */
+
+type PermissionHandler struct {
+ DB *sql.DB
+ Repo *permissions.PermissionRepository
+}
+
+func NewPermissionHandler(db *sql.DB) *PermissionHandler {
+ return &PermissionHandler{
+ DB: db,
+ Repo: permissions.NewPermissionRepository(db),
+ }
+}
+
+/* =====================================================
+ POST /api/permissions/matrix
+===================================================== */
+
+func (h *PermissionHandler) UpdatePermissionMatrix(
+ w http.ResponseWriter,
+ r *http.Request,
+) {
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", 401)
+ return
+ }
+
+ var req []permissions.PermissionUpdateRequest
+
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "invalid payload", 400)
+ return
+ }
+
+ if len(req) == 0 {
+ w.WriteHeader(http.StatusOK)
+ return
+ }
+
+ err := h.Repo.UpdatePermissions(req)
+ if err != nil {
+ http.Error(w, "db error", 500)
+ return
+ }
+
+ json.NewEncoder(w).Encode(map[string]any{
+ "success": true,
+ })
+}
+func GetMyPermissionMatrix(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", 401)
+ return
+ }
+
+ var roleID int
+
+ err := db.QueryRow(`
+ SELECT id FROM dfrole WHERE LOWER(code)=LOWER($1)
+ `, claims.RoleCode).Scan(&roleID)
+
+ if err != nil {
+ http.Error(w, "role resolve error", 500)
+ return
+ }
+
+ repo := permissions.NewPermissionRepository(db)
+
+ raw, err := repo.GetPermissionMatrixForRoles([]int{roleID})
+ if err != nil {
+ http.Error(w, "db error", 500)
+ return
+ }
+
+ // 🔥 FRONTEND FORMAT
+ type Row struct {
+ Module string `json:"module"`
+ Action string `json:"action"`
+ Allowed bool `json:"allowed"`
+ }
+
+ list := make([]Row, 0, len(raw))
+
+ for _, p := range raw {
+
+ list = append(list, Row{
+ Module: p.ModuleCode, // 👈 burası önemli
+ Action: p.Action,
+ Allowed: p.Allowed,
+ })
+ }
+
+ _ = json.NewEncoder(w).Encode(list)
+ }
+}
diff --git a/svc/routes/product.go b/svc/routes/product.go
new file mode 100644
index 0000000..1893f08
--- /dev/null
+++ b/svc/routes/product.go
@@ -0,0 +1,27 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/queries"
+ "encoding/json"
+ "net/http"
+)
+
+// ✅ GET /api/products
+func GetProductListHandler(w http.ResponseWriter, r *http.Request) {
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ products, err := queries.GetProductList()
+ if err != nil {
+ http.Error(w, "Ürün listesi alınamadı: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+ _ = json.NewEncoder(w).Encode(products)
+}
diff --git a/svc/routes/productcolor.go b/svc/routes/productcolor.go
new file mode 100644
index 0000000..3a794e6
--- /dev/null
+++ b/svc/routes/productcolor.go
@@ -0,0 +1,48 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/db"
+ "bssapp-backend/models"
+ "bssapp-backend/queries"
+ "encoding/json"
+ "log"
+ "net/http"
+)
+
+func GetProductColorsHandler(w http.ResponseWriter, r *http.Request) {
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ code := r.URL.Query().Get("code")
+ if code == "" {
+ http.Error(w, "Eksik parametre: code gerekli", http.StatusBadRequest)
+ return
+ }
+
+ rows, err := db.MssqlDB.Query(queries.GetProductColors, code)
+ if err != nil {
+ http.Error(w, "Renk listesi alınamadı: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+ defer rows.Close()
+
+ var list []models.ProductColor
+ for rows.Next() {
+ var c models.ProductColor
+ if err := rows.Scan(&c.ProductCode, &c.ColorCode, &c.ColorDescription); err != nil {
+ log.Println("Satır okunamadı:", err)
+ continue
+ }
+ list = append(list, c)
+ }
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+ _ = json.NewEncoder(w).Encode(list)
+
+ log.Printf("✅ [1.COLOR] %s -> %d kayıt", code, len(list))
+}
diff --git a/svc/routes/productcolorsize.go b/svc/routes/productcolorsize.go
new file mode 100644
index 0000000..cb8c048
--- /dev/null
+++ b/svc/routes/productcolorsize.go
@@ -0,0 +1,73 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/db"
+ "bssapp-backend/models"
+ "bssapp-backend/queries"
+ "encoding/json"
+ "log"
+ "net/http"
+)
+
+// ✅ GET /api/product-colorsize?code=S001-DCY18144&color=001&color2=017
+func GetProductColorSizesHandler(w http.ResponseWriter, r *http.Request) {
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ code := r.URL.Query().Get("code")
+ color := r.URL.Query().Get("color")
+ color2 := r.URL.Query().Get("color2")
+
+ // 🔹 Model kodu zorunlu, renk opsiyonel
+ if code == "" {
+ http.Error(w, "Eksik parametre: code gerekli", http.StatusBadRequest)
+ return
+ }
+
+ // 🔧 Eksik parametreleri normalize et
+ if color == "" {
+ color = " "
+ }
+ if color2 == "" {
+ color2 = " "
+ }
+
+ log.Printf("🎨 [COLORSIZE] İstek alındı -> code=%q | color=%q | color2=%q", code, color, color2)
+
+ // ✅ MSSQL’e sıralı 3 parametre gönder (adlı değil!)
+ rows, err := db.MssqlDB.Query(queries.GetProductColorSizes, code, color, color2)
+ if err != nil {
+ log.Printf("❌ [COLORSIZE] DB sorgu hatası: %v", err)
+ http.Error(w, "SQL hatası: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+ defer rows.Close()
+
+ var list []models.ProductColorSize
+ for rows.Next() {
+ var s models.ProductColorSize
+ if err := rows.Scan(&s.ProductCode, &s.ColorCode, &s.ItemDim1Code, &s.ItemDim2Code); err != nil {
+ log.Printf("⚠️ [COLORSIZE] Satır okunamadı: %v", err)
+ continue
+ }
+ list = append(list, s)
+ }
+
+ if err := rows.Err(); err != nil {
+ log.Printf("⚠️ [COLORSIZE] Row iteration hatası: %v", err)
+ }
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+ if err := json.NewEncoder(w).Encode(list); err != nil {
+ log.Printf("❌ [COLORSIZE] JSON encode hatası: %v", err)
+ http.Error(w, "JSON encode başarısız", http.StatusInternalServerError)
+ return
+ }
+
+ log.Printf("✅ [COLORSIZE] %s / %s / %s -> %d kayıt döndü", code, color, color2, len(list))
+}
diff --git a/svc/routes/productdetail.go b/svc/routes/productdetail.go
new file mode 100644
index 0000000..fbaa693
--- /dev/null
+++ b/svc/routes/productdetail.go
@@ -0,0 +1,65 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/db"
+ "bssapp-backend/models"
+ "bssapp-backend/queries"
+ "encoding/json"
+ "log"
+ "net/http"
+)
+
+func GetProductDetailHandler(w http.ResponseWriter, r *http.Request) {
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ code := r.URL.Query().Get("code")
+ if code == "" {
+ http.Error(w, "Eksik parametre: code", http.StatusBadRequest)
+ return
+ }
+
+ row := db.MssqlDB.QueryRow(queries.GetProductDetail, code)
+
+ var p models.ProductDetail
+ err := row.Scan(
+ &p.ProductCode,
+ new(string), // BOS7
+ new(string), // ProductDescription
+ new(string), // ProductAtt42
+ &p.UrunIlkGrubu,
+ new(string), // ProductAtt01
+ &p.UrunAnaGrubu,
+ new(string), // ProductAtt02
+ &p.UrunAltGrubu,
+ new(string), // ProductAtt41
+ &p.UrunIcerik,
+ new(string), // ProductAtt10
+ new(string), // Marka
+ new(string), // ProductAtt11
+ &p.Drop,
+ new(string), // ProductAtt29
+ new(string), // Karisim
+ new(string), // ProductAtt45
+ &p.AskiliYan,
+ new(string), // ProductAtt44
+ &p.Kategori,
+ new(string), // ProductAtt38
+ &p.Fit1,
+ new(string), // ProductAtt39
+ &p.Fit2,
+ )
+ if err != nil {
+ log.Println("❌ Ürün detayları alınamadı:", err)
+ http.Error(w, "Veri okuma hatası: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+ _ = json.NewEncoder(w).Encode(p)
+}
diff --git a/svc/routes/productsecondcolor.go b/svc/routes/productsecondcolor.go
new file mode 100644
index 0000000..500a1cd
--- /dev/null
+++ b/svc/routes/productsecondcolor.go
@@ -0,0 +1,67 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/db"
+ "bssapp-backend/models"
+ "bssapp-backend/queries"
+ "database/sql"
+ "encoding/json"
+ "log"
+ "net/http"
+)
+
+// ✅ GET /api/product-secondcolor?code=S001-DCY18144&color=001
+func GetProductSecondColorsHandler(w http.ResponseWriter, r *http.Request) {
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ code := r.URL.Query().Get("code")
+ color := r.URL.Query().Get("color")
+
+ if code == "" || color == "" {
+ http.Error(w, "Eksik parametre: code ve color gerekli", http.StatusBadRequest)
+ return
+ }
+
+ log.Printf("🎨 [2.COLOR] İstek alındı -> code=%s color=%s", code, color)
+
+ rows, err := db.MssqlDB.Query(
+ queries.GetProductSecondColors,
+ sql.Named("ProductCode", code),
+ sql.Named("ColorCode", color),
+ )
+ if err != nil {
+ log.Println("❌ DB sorgu hatası:", err)
+ http.Error(w, "2. renk listesi alınamadı: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+ defer rows.Close()
+
+ var list []models.ProductSecondColor
+ for rows.Next() {
+ var c models.ProductSecondColor
+ if err := rows.Scan(&c.ProductCode, &c.ColorCode, &c.ItemDim2Code); err != nil {
+ log.Println("⚠️ Satır okunamadı:", err)
+ continue
+ }
+ list = append(list, c)
+ }
+
+ if err := rows.Err(); err != nil {
+ log.Println("⚠️ Row iteration hatası:", err)
+ }
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+ if err := json.NewEncoder(w).Encode(list); err != nil {
+ log.Println("❌ JSON encode hatası:", err)
+ http.Error(w, "JSON encode başarısız", http.StatusInternalServerError)
+ return
+ }
+
+ log.Printf("✅ [2.COLOR] %s / %s -> %d kayıt döndü", code, color, len(list))
+}
diff --git a/svc/routes/role_department_permissions.go b/svc/routes/role_department_permissions.go
new file mode 100644
index 0000000..1d9efbe
--- /dev/null
+++ b/svc/routes/role_department_permissions.go
@@ -0,0 +1,440 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/internal/auditlog"
+ "bssapp-backend/permissions"
+ "bssapp-backend/queries"
+ "database/sql"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+ "strconv"
+
+ "github.com/gorilla/mux"
+)
+
+type IdTitleOption struct {
+ ID string `json:"id"`
+ Title string `json:"title"`
+}
+type Row struct {
+ Route string `json:"route"`
+ CanAccess bool `json:"can_access"`
+}
+
+type RoleDepartmentPermissionHandler struct {
+ DB *sql.DB
+ Repo *permissions.RoleDepartmentPermissionRepo
+}
+
+func NewRoleDepartmentPermissionHandler(db *sql.DB) *RoleDepartmentPermissionHandler {
+
+ return &RoleDepartmentPermissionHandler{
+ DB: db, // ✅ EKLENDİ
+ Repo: permissions.NewRoleDepartmentPermissionRepo(db),
+ }
+}
+
+/* ======================================================
+ GET
+====================================================== */
+
+func (h *RoleDepartmentPermissionHandler) Get(w http.ResponseWriter, r *http.Request) {
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ vars := mux.Vars(r)
+
+ roleID, err := strconv.Atoi(vars["roleId"])
+ if err != nil || roleID <= 0 {
+ http.Error(w, "invalid roleId", http.StatusBadRequest)
+ return
+ }
+
+ deptCode := vars["deptCode"]
+ if deptCode == "" {
+ http.Error(w, "invalid deptCode", http.StatusBadRequest)
+ return
+ }
+
+ list, err := h.Repo.Get(roleID, deptCode)
+ if err != nil {
+ http.Error(w, "db error", http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+ _ = json.NewEncoder(w).Encode(list)
+}
+
+/* ======================================================
+ POST
+====================================================== */
+
+func (h *RoleDepartmentPermissionHandler) Save(w http.ResponseWriter, r *http.Request) {
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ vars := mux.Vars(r)
+
+ roleID, err := strconv.Atoi(vars["roleId"])
+ if err != nil || roleID <= 0 {
+ http.Error(w, "invalid roleId", http.StatusBadRequest)
+ return
+ }
+
+ deptCode := vars["deptCode"]
+ if deptCode == "" {
+ http.Error(w, "invalid deptCode", http.StatusBadRequest)
+ return
+ }
+
+ var list []permissions.RoleDepartmentPermission
+ if err := json.NewDecoder(r.Body).Decode(&list); err != nil {
+ http.Error(w, "bad payload", http.StatusBadRequest)
+ return
+ }
+
+ // ================= OLD =================
+ oldRows, err := h.Repo.Get(roleID, deptCode)
+ if err != nil {
+ log.Println("OLD PERM LOAD ERROR:", err)
+ }
+
+ oldMap := map[string]bool{}
+ for _, p := range oldRows {
+ key := p.Module + ":" + p.Action
+ oldMap[key] = p.Allowed
+ }
+
+ // ================= DIFF =================
+ var changes []map[string]any
+ for _, p := range list {
+ key := p.Module + ":" + p.Action
+ oldVal := oldMap[key]
+ if oldVal != p.Allowed {
+ changes = append(changes, map[string]any{
+ "module": p.Module,
+ "action": p.Action,
+ "before": oldVal,
+ "after": p.Allowed,
+ })
+ }
+ }
+
+ // ================= SAVE =================
+ if err := h.Repo.Save(roleID, deptCode, list); err != nil {
+ http.Error(w, "save error", http.StatusInternalServerError)
+ return
+ }
+
+ // ================= AUDIT =================
+ if len(changes) > 0 {
+ auditlog.Enqueue(r.Context(), auditlog.ActivityLog{
+ ActionType: "role_department_permission_change",
+ ActionCategory: "role_permission",
+ ActionTarget: fmt.Sprintf(
+ "/api/roles/%d/departments/%s/permissions",
+ roleID,
+ deptCode,
+ ),
+ Description: "role+department permissions updated",
+ Username: claims.Username,
+ RoleCode: claims.RoleCode,
+ DfUsrID: int64(claims.ID),
+ ChangeBefore: map[string]any{
+ "permissions": oldRows,
+ },
+ ChangeAfter: map[string]any{
+ "changes": changes,
+ },
+ IsSuccess: true,
+ })
+ }
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+ _ = json.NewEncoder(w).Encode(map[string]bool{"success": true})
+}
+func GetModuleLookupRoute(db *sql.DB) http.HandlerFunc {
+
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ rows, err := db.Query(queries.GetModuleLookup)
+ if err != nil {
+ http.Error(w, "db error", 500)
+ return
+ }
+ defer rows.Close()
+
+ type Row struct {
+ Value string `json:"value"`
+ Label string `json:"label"`
+ }
+
+ var list []Row
+
+ for rows.Next() {
+
+ var r Row
+
+ if err := rows.Scan(
+ &r.Value,
+ &r.Label,
+ ); err != nil {
+ http.Error(w, "scan error", 500)
+ return
+ }
+
+ list = append(list, r)
+ }
+
+ json.NewEncoder(w).Encode(list)
+ }
+}
+func GetRolesForPermissionSelectRoute(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ rows, err := db.Query(queries.GetRolesForPermissionSelect)
+ if err != nil {
+ http.Error(w, "roles select error", 500)
+ return
+ }
+ defer rows.Close()
+
+ list := make([]IdTitleOption, 0)
+ for rows.Next() {
+ var o IdTitleOption
+ if err := rows.Scan(&o.ID, &o.Title); err == nil {
+ list = append(list, o)
+ }
+ }
+
+ _ = json.NewEncoder(w).Encode(list)
+ }
+}
+
+func GetDepartmentsForPermissionSelectRoute(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ rows, err := db.Query(queries.GetDepartmentsForPermissionSelect)
+ if err != nil {
+ http.Error(w, "departments select error", 500)
+ return
+ }
+ defer rows.Close()
+
+ list := make([]IdTitleOption, 0)
+ for rows.Next() {
+ var o IdTitleOption
+ if err := rows.Scan(&o.ID, &o.Title); err == nil {
+ list = append(list, o)
+ }
+ }
+
+ _ = json.NewEncoder(w).Encode(list)
+ }
+}
+func GetUsersForPermissionSelectRoute(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ rows, err := db.Query(queries.GetUserLookupForPermission)
+ if err != nil {
+ http.Error(w, "users lookup error", http.StatusInternalServerError)
+ return
+ }
+ defer rows.Close()
+
+ list := make([]IdTitleOption, 0, 128)
+
+ for rows.Next() {
+
+ var o IdTitleOption
+
+ if err := rows.Scan(&o.ID, &o.Title); err == nil {
+ list = append(list, o)
+ }
+ }
+
+ _ = json.NewEncoder(w).Encode(list)
+ }
+}
+
+func (h *PermissionHandler) GetUserOverrides(w http.ResponseWriter, r *http.Request) {
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ userID, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
+ if err != nil || userID <= 0 {
+ http.Error(w, "invalid id", http.StatusBadRequest)
+ return
+ }
+
+ list, err := h.Repo.GetUserOverridesByUserID(userID)
+ if err != nil {
+ log.Println("❌ USER OVERRIDE LOAD ERROR:", err)
+ http.Error(w, "db error", http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+ _ = json.NewEncoder(w).Encode(list)
+}
+func GetUserRoutePermissionsHandler(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", 401)
+ return
+ }
+
+ repo := permissions.NewPermissionRepository(db)
+
+ // JWT’den departmanlar
+ depts := claims.DepartmentCodes
+
+ rows, err := db.Query(`
+ SELECT DISTINCT
+ module_code,
+ action,
+ path
+ FROM mk_sys_routes
+ `)
+ if err != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+ defer rows.Close()
+
+ type Row struct {
+ Route string `json:"route"`
+ CanAccess bool `json:"can_access"`
+ }
+
+ list := make([]Row, 0, 64)
+
+ for rows.Next() {
+
+ var module, action, path string
+
+ if err := rows.Scan(
+ &module,
+ &action,
+ &path,
+ ); err != nil {
+ continue
+ }
+
+ allowed, err := repo.ResolvePermissionChain(
+ int64(claims.ID),
+ int64(claims.RoleID),
+ depts,
+ module,
+ action,
+ )
+
+ if err != nil {
+ log.Println("PERM RESOLVE ERROR:", err)
+ continue
+ }
+
+ list = append(list, Row{
+ Route: path,
+ CanAccess: allowed,
+ })
+ }
+
+ _ = json.NewEncoder(w).Encode(list)
+ }
+}
+
+func GetMyEffectivePermissions(db *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", 401)
+ return
+ }
+
+ repo := permissions.NewPermissionRepository(db)
+
+ // ✅ JWT'DEN DEPARTMENTS
+ depts := claims.DepartmentCodes
+
+ log.Printf("🧪 EFFECTIVE PERM | user=%d role=%d depts=%v",
+ claims.ID,
+ claims.RoleID,
+ depts,
+ )
+
+ // all system perms
+ all, err := db.Query(`
+ SELECT DISTINCT module_code, action
+ FROM mk_sys_routes
+ `)
+ if err != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+ defer all.Close()
+
+ type Row struct {
+ Module string `json:"module"`
+ Action string `json:"action"`
+ Allowed bool `json:"allowed"`
+ }
+
+ list := make([]Row, 0, 128)
+
+ for all.Next() {
+
+ var m, a string
+ if err := all.Scan(&m, &a); err != nil {
+ continue
+ }
+
+ allowed, err := repo.ResolvePermissionChain(
+ int64(claims.ID),
+ int64(claims.RoleID),
+ depts,
+ m,
+ a,
+ )
+
+ if err != nil {
+ continue
+ }
+
+ list = append(list, Row{
+ Module: m,
+ Action: a,
+ Allowed: allowed,
+ })
+ }
+
+ _ = json.NewEncoder(w).Encode(list)
+ }
+}
diff --git a/svc/routes/statement_detail.go b/svc/routes/statement_detail.go
new file mode 100644
index 0000000..0f5c20f
--- /dev/null
+++ b/svc/routes/statement_detail.go
@@ -0,0 +1,38 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/queries"
+ "encoding/json"
+ "net/http"
+
+ "github.com/gorilla/mux"
+)
+
+// GET /api/statements/{accountCode}/details?startdate=...&enddate=...&parislemler=1&parislemler=2
+func GetStatementDetailsHandler(w http.ResponseWriter, r *http.Request) {
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ vars := mux.Vars(r)
+ accountCode := vars["accountCode"]
+
+ startDate := r.URL.Query().Get("startdate")
+ endDate := r.URL.Query().Get("enddate")
+ parislemler := r.URL.Query()["parislemler"]
+
+ details, err := queries.GetStatementDetails(accountCode, startDate, endDate, parislemler)
+ if err != nil {
+ http.Error(w, "Error fetching statement details: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+ if err := json.NewEncoder(w).Encode(details); err != nil {
+ http.Error(w, "Error encoding response: "+err.Error(), http.StatusInternalServerError)
+ }
+}
diff --git a/svc/routes/statement_header.go b/svc/routes/statement_header.go
new file mode 100644
index 0000000..71ce07d
--- /dev/null
+++ b/svc/routes/statement_header.go
@@ -0,0 +1,38 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/models"
+ "bssapp-backend/queries"
+ "encoding/json"
+ "net/http"
+)
+
+// GET /api/statements
+func GetStatementHeadersHandler(w http.ResponseWriter, r *http.Request) {
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ params := models.StatementParams{
+ StartDate: r.URL.Query().Get("startdate"),
+ EndDate: r.URL.Query().Get("enddate"),
+ AccountCode: r.URL.Query().Get("accountcode"),
+ LangCode: r.URL.Query().Get("langcode"),
+ Parislemler: r.URL.Query()["parislemler"],
+ }
+
+ statements, err := queries.GetStatements(params)
+ if err != nil {
+ http.Error(w, "Error fetching statements: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+ if err := json.NewEncoder(w).Encode(statements); err != nil {
+ http.Error(w, "Error encoding response: "+err.Error(), http.StatusInternalServerError)
+ }
+}
diff --git a/svc/routes/statement_header_pdf.go b/svc/routes/statement_header_pdf.go
new file mode 100644
index 0000000..85834f1
--- /dev/null
+++ b/svc/routes/statement_header_pdf.go
@@ -0,0 +1,395 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/models"
+ "bssapp-backend/queries"
+ "bytes"
+ "database/sql"
+ "fmt"
+ "log"
+ "net/http"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+ "time"
+
+ "github.com/jung-kurt/gofpdf"
+)
+
+/* ============================ SABİTLER ============================ */
+
+// A4 Landscape (mm)
+const (
+ hPageWidth = 297.0
+ hPageHeight = 210.0
+
+ hMarginL = 8.0
+ hMarginT = 8.0
+ hMarginR = 8.0
+ hMarginB = 15.0
+
+ hLineHMain = 5.2
+ hCellPadX = 2.0
+
+ hHeaderRowH = 8.4
+ hGroupBarH = 9.0
+
+ hGridLineWidth = 0.3
+ hLogoW = 42.0
+)
+
+// Kolonlar
+var hMainCols = []string{
+ "Belge No", "Tarih", "Vade", "İşlem",
+ "Açıklama", "Para", "Borç", "Alacak", "Bakiye",
+}
+
+var hMainWbase = []float64{
+ 34, 30, 28, 24, 60, 20, 36, 36, 36,
+}
+
+// Font dosyaları
+const (
+ hFontFamilyReg = "dejavu"
+ hFontFamilyBold = "dejavu-b"
+ hFontPathReg = "fonts/DejaVuSans.ttf"
+ hFontPathBold = "fonts/DejaVuSans-Bold.ttf"
+)
+
+// Renkler
+var (
+ hColorPrimary = [3]int{149, 113, 22} // #957116
+ hColorSecondary = [3]int{218, 193, 151} // #dac197 (Baggi secondary)
+)
+
+/* ============================ FONT / FORMAT ============================ */
+
+func hEnsureFonts(pdf *gofpdf.Fpdf) {
+ if _, err := os.Stat(hFontPathReg); err == nil {
+ pdf.AddUTF8Font(hFontFamilyReg, "", hFontPathReg)
+ }
+ if _, err := os.Stat(hFontPathBold); err == nil {
+ pdf.AddUTF8Font(hFontFamilyBold, "", hFontPathBold)
+ }
+}
+
+func hNormalizeWidths(base []float64, targetTotal float64) []float64 {
+ sum := 0.0
+ for _, v := range base {
+ sum += v
+ }
+ scale := targetTotal / sum
+ out := make([]float64, len(base))
+ for i, v := range base {
+ out[i] = v * scale
+ }
+ return out
+}
+
+func hFormatCurrencyTR(val float64) string {
+ s := fmt.Sprintf("%.2f", val)
+ parts := strings.Split(s, ".")
+ intPart, decPart := parts[0], parts[1]
+
+ var out []string
+ for len(intPart) > 3 {
+ out = append([]string{intPart[len(intPart)-3:]}, out...)
+ intPart = intPart[:len(intPart)-3]
+ }
+ if intPart != "" {
+ out = append([]string{intPart}, out...)
+ }
+ return strings.Join(out, ".") + "," + decPart
+}
+
+func hNeedNewPage(pdf *gofpdf.Fpdf, needH float64) bool {
+ return pdf.GetY()+needH+hMarginB > hPageHeight
+}
+
+/* ============================ YARDIMCI FONKSİYONLAR ============================ */
+
+func hSplitLinesSafe(pdf *gofpdf.Fpdf, text string, width float64) [][]byte {
+ if width <= 0 {
+ width = 1
+ }
+ return pdf.SplitLines([]byte(text), width)
+}
+
+func hDrawWrapText(pdf *gofpdf.Fpdf, text string, x, y, width, lineH float64) {
+ if text == "" {
+ return
+ }
+ lines := hSplitLinesSafe(pdf, text, width)
+ cy := y
+ for _, ln := range lines {
+ pdf.SetXY(x, cy)
+ pdf.CellFormat(width, lineH, string(ln), "", 0, "L", false, 0, "")
+ cy += lineH
+ }
+}
+
+func hCalcRowHeightForText(pdf *gofpdf.Fpdf, text string, colWidth, lineHeight, padX float64) float64 {
+ if text == "" {
+ return lineHeight
+ }
+ lines := hSplitLinesSafe(pdf, text, colWidth-(2*padX))
+ h := float64(len(lines)) * lineHeight
+ if h < lineHeight {
+ h = lineHeight
+ }
+ return h
+}
+
+/* ============================ HEADER ============================ */
+
+func hDrawPageHeader(pdf *gofpdf.Fpdf, cariKod, cariIsim, start, end string) float64 {
+ logoPath, _ := filepath.Abs("./public/Baggi-Tekstil-A.s-Logolu.jpeg")
+
+ // Logo
+ pdf.ImageOptions(logoPath, hMarginL, 2, hLogoW, 0, false, gofpdf.ImageOptions{}, 0, "")
+
+ // Başlıklar
+ pdf.SetFont(hFontFamilyBold, "", 16)
+ pdf.SetTextColor(hColorPrimary[0], hColorPrimary[1], hColorPrimary[2])
+ pdf.SetXY(hMarginL+hLogoW+8, hMarginT+2)
+ pdf.CellFormat(120, 7, "Baggi Software System", "", 0, "L", false, 0, "")
+
+ pdf.SetFont(hFontFamilyBold, "", 12)
+ pdf.SetXY(hMarginL+hLogoW+8, hMarginT+10)
+ pdf.CellFormat(120, 6, "Cari Hesap Raporu", "", 0, "L", false, 0, "")
+
+ // Bugünün tarihi (sağ üst)
+ today := time.Now().Format("02.01.2006")
+ pdf.SetFont(hFontFamilyReg, "", 9)
+ pdf.SetXY(hPageWidth-hMarginR-40, hMarginT+3)
+ pdf.CellFormat(40, 6, "Tarih: "+today, "", 0, "R", false, 0, "")
+
+ // Cari & Tarih kutuları (daha yukarı taşındı)
+ boxY := hMarginT + hLogoW - 6
+ pdf.SetFont(hFontFamilyBold, "", 10)
+
+ pdf.Rect(hMarginL, boxY, 140, 11, "")
+ pdf.SetXY(hMarginL+2, boxY+3)
+ pdf.CellFormat(136, 5, fmt.Sprintf("Cari: %s — %s", cariKod, cariIsim), "", 0, "L", false, 0, "")
+
+ pdf.Rect(hPageWidth-hMarginR-140, boxY, 140, 11, "")
+ pdf.SetXY(hPageWidth-hMarginR-138, boxY+3)
+ pdf.CellFormat(136, 5, fmt.Sprintf("Tarih Aralığı: %s → %s", start, end), "", 0, "R", false, 0, "")
+
+ // Alt çizgi
+ y := boxY + 13
+ pdf.SetDrawColor(hColorPrimary[0], hColorPrimary[1], hColorPrimary[2])
+ pdf.Line(hMarginL, y, hPageWidth-hMarginR, y)
+ pdf.SetDrawColor(200, 200, 200)
+
+ return y + 4
+}
+
+/* ============================ TABLO ============================ */
+
+func hDrawGroupBar(pdf *gofpdf.Fpdf, currency string, sonBakiye float64) {
+ x := hMarginL
+ y := pdf.GetY()
+ w := hPageWidth - hMarginL - hMarginR
+ h := hGroupBarH
+
+ pdf.SetDrawColor(hColorPrimary[0], hColorPrimary[1], hColorPrimary[2])
+ pdf.SetLineWidth(0.6)
+ pdf.Rect(x, y, w, h, "")
+
+ pdf.SetFont(hFontFamilyBold, "", 11.3)
+ pdf.SetTextColor(hColorPrimary[0], hColorPrimary[1], hColorPrimary[2])
+
+ pdf.SetXY(x+hCellPadX+1.0, y+(h-5.0)/2)
+ pdf.CellFormat(w*0.6, 5.0, currency, "", 0, "L", false, 0, "")
+
+ txt := "Son Bakiye = " + hFormatCurrencyTR(sonBakiye)
+ pdf.SetXY(x+w*0.4, y+(h-5.0)/2)
+ pdf.CellFormat(w*0.6-2.0, 5.0, txt, "", 0, "R", false, 0, "")
+
+ pdf.SetLineWidth(hGridLineWidth)
+ pdf.SetDrawColor(200, 200, 200)
+ pdf.SetTextColor(0, 0, 0)
+
+ pdf.Ln(h)
+}
+
+func hDrawMainHeaderRow(pdf *gofpdf.Fpdf, cols []string, widths []float64) {
+ pdf.SetFont(hFontFamilyBold, "", 10.2)
+ pdf.SetTextColor(255, 255, 255)
+ pdf.SetFillColor(hColorPrimary[0], hColorPrimary[1], hColorPrimary[2])
+ pdf.SetDrawColor(120, 90, 20)
+
+ x := hMarginL
+ y := pdf.GetY()
+ for i, c := range cols {
+ pdf.Rect(x, y, widths[i], hHeaderRowH, "DF")
+ pdf.SetXY(x+hCellPadX, y+1.6)
+ pdf.CellFormat(widths[i]-2*hCellPadX, hHeaderRowH-3.2, c, "", 0, "C", false, 0, "")
+ x += widths[i]
+ }
+ pdf.Ln(hHeaderRowH)
+}
+
+func hDrawMainDataRow(pdf *gofpdf.Fpdf, row []string, widths []float64, rowH float64, rowIndex int) {
+ x := hMarginL
+ y := pdf.GetY()
+
+ // ✅ Zebra efekti
+ wTotal := hPageWidth - hMarginL - hMarginR
+ if rowIndex%2 == 1 {
+ pdf.SetFillColor(hColorSecondary[0], hColorSecondary[1], hColorSecondary[2])
+ pdf.Rect(x, y, wTotal, rowH, "F")
+ }
+
+ pdf.SetFont(hFontFamilyReg, "", 8.2)
+ pdf.SetDrawColor(242, 235, 222)
+ pdf.SetTextColor(30, 30, 30)
+
+ for i, val := range row {
+ pdf.Rect(x, y, widths[i], rowH, "")
+ switch {
+ case i == 4: // Açıklama wrap
+ hDrawWrapText(pdf, val, x+hCellPadX, y+0.5, widths[i]-2*hCellPadX, hLineHMain)
+ case i >= 6:
+ pdf.SetXY(x+hCellPadX, y+(rowH-hLineHMain)/2)
+ pdf.CellFormat(widths[i]-2*hCellPadX, hLineHMain, val, "", 0, "R", false, 0, "")
+ default:
+ pdf.SetXY(x+hCellPadX, y+(rowH-hLineHMain)/2)
+ pdf.CellFormat(widths[i]-2*hCellPadX, hLineHMain, val, "", 0, "L", false, 0, "")
+ }
+ x += widths[i]
+ }
+
+ // ❌ pdf.Ln(rowH) YOK
+ // ✅ Manuel olarak Y'yi arttırıyoruz
+ pdf.SetY(y + rowH)
+}
+
+/* ============================ MAIN HANDLER ============================ */
+
+func ExportStatementHeaderReportPDFHandler(mssql *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ started := time.Now()
+
+ accountCode := r.URL.Query().Get("accountcode")
+ startDate := r.URL.Query().Get("startdate")
+ endDate := r.URL.Query().Get("enddate")
+ rawParis := r.URL.Query()["parislemler"]
+
+ var parislemler []string
+ for _, v := range rawParis {
+ v = strings.TrimSpace(v)
+ if v != "" && v != "undefined" && v != "null" {
+ parislemler = append(parislemler, v)
+ }
+ }
+
+ headers, _, err := queries.GetStatementsHPDF(accountCode, startDate, endDate, parislemler)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ type grp struct {
+ code string
+ rows []models.StatementHeader
+ sonBakiye float64
+ }
+ order := []string{}
+ groups := map[string]*grp{}
+
+ for _, h := range headers {
+ g, ok := groups[h.ParaBirimi]
+ if !ok {
+ g = &grp{code: h.ParaBirimi}
+ groups[h.ParaBirimi] = g
+ order = append(order, h.ParaBirimi)
+ }
+ g.rows = append(g.rows, h)
+ }
+ for _, k := range order {
+ sort.SliceStable(groups[k].rows, func(i, j int) bool {
+ ri, rj := groups[k].rows[i], groups[k].rows[j]
+ if ri.BelgeTarihi == rj.BelgeTarihi {
+ return ri.BelgeNo < rj.BelgeNo
+ }
+ return ri.BelgeTarihi < rj.BelgeTarihi
+ })
+ if n := len(groups[k].rows); n > 0 {
+ groups[k].sonBakiye = groups[k].rows[n-1].Bakiye
+ }
+ }
+
+ pdf := gofpdf.New("L", "mm", "A4", "")
+ pdf.SetMargins(hMarginL, hMarginT, hMarginR)
+ pdf.SetAutoPageBreak(false, hMarginB)
+ hEnsureFonts(pdf)
+
+ wAvail := hPageWidth - hMarginL - hMarginR
+ mainWn := hNormalizeWidths(hMainWbase, wAvail)
+
+ cariIsim := ""
+ if len(headers) > 0 {
+ cariIsim = headers[0].CariIsim
+ }
+
+ pageNum := 0
+ newPage := func() {
+ pageNum++
+ pdf.AddPage()
+ tableTop := hDrawPageHeader(pdf, accountCode, cariIsim, startDate, endDate)
+ pdf.SetY(tableTop)
+ }
+
+ newPage()
+
+ for _, cur := range order {
+ g := groups[cur]
+ hDrawGroupBar(pdf, cur, g.sonBakiye)
+ hDrawMainHeaderRow(pdf, hMainCols, mainWn)
+
+ rowIndex := 0
+ for _, h := range g.rows {
+ row := []string{
+ h.BelgeNo, h.BelgeTarihi, h.VadeTarihi, h.IslemTipi,
+ h.Aciklama, h.ParaBirimi,
+ hFormatCurrencyTR(h.Borc),
+ hFormatCurrencyTR(h.Alacak),
+ hFormatCurrencyTR(h.Bakiye),
+ }
+
+ rh := hCalcRowHeightForText(pdf, row[4], mainWn[4], hLineHMain, hCellPadX)
+ if hNeedNewPage(pdf, rh+hHeaderRowH) {
+ newPage()
+ hDrawGroupBar(pdf, cur, g.sonBakiye)
+ hDrawMainHeaderRow(pdf, hMainCols, mainWn)
+ }
+
+ hDrawMainDataRow(pdf, row, mainWn, rh, rowIndex)
+ rowIndex++
+ }
+ pdf.Ln(1)
+ }
+
+ var buf bytes.Buffer
+ if err := pdf.Output(&buf); err != nil {
+ http.Error(w, "PDF oluşturulamadı: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/pdf")
+ w.Header().Set("Content-Disposition", "inline; filename=statement-header-report.pdf")
+ _, _ = w.Write(buf.Bytes())
+
+ log.Printf("✅ Header-only PDF üretimi tamamlandı (%s)", time.Since(started))
+ }
+}
diff --git a/svc/routes/statements_pdf.go b/svc/routes/statements_pdf.go
new file mode 100644
index 0000000..9908fa7
--- /dev/null
+++ b/svc/routes/statements_pdf.go
@@ -0,0 +1,642 @@
+// routes/statements_pdf.go
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/models"
+ "bssapp-backend/queries"
+ "bytes"
+ "database/sql"
+ "fmt"
+ "log"
+ "net/http"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+ "time"
+
+ "github.com/jung-kurt/gofpdf"
+)
+
+/* ============================ SABİTLER ============================ */
+
+// A4 Landscape (mm)
+const (
+ pageWidth = 297.0
+ pageHeight = 210.0
+
+ // Kenar boşlukları (mm)
+ marginL = 8.0
+ marginT = 8.0
+ marginR = 8.0
+ marginB = 15.0
+
+ // Satır aralıkları (mm)
+ lineHMain = 6.0 // ana satır biraz iri
+ lineHDetail = 5.2
+ cellPadX = 2.2
+
+ // Satır yükseklikleri (mm)
+ headerRowH = 8.4
+ subHeaderRowH = 6.2
+ groupBarH = 9.0
+
+ // Çizgi kalınlığı
+ gridLineWidth = 0.3
+
+ // Logo genişliği (yükseklik oranlanır)
+ logoW = 42.0
+)
+
+// Ana tablo kolonları
+var mainCols = []string{
+ "Belge No", "Tarih", "Vade", "İşlem",
+ "Açıklama", "Para", "Borç", "Alacak", "Bakiye",
+}
+
+// Ana tablo kolon genişlikleri (ilk 3 geniş)
+var mainWbase = []float64{
+ 34, // Belge No
+ 30, // Tarih
+ 28, // Vade
+ 24, // İşlem
+ 60, // Açıklama (biraz daraltıldı)
+ 20, // Para
+ 36, // Borç (genişletildi)
+ 36, // Alacak (genişletildi)
+ 36, // Bakiye (genişletildi)
+}
+
+// Detay tablo kolonları ve genişlikleri
+var dCols = []string{
+ "Ana Grup", "Alt Grup", "Garson", "Fit", "İçerik",
+ "Ürün", "Renk", "Adet", "Fiyat", "Tutar",
+}
+var dWbase = []float64{
+ 30, 28, 22, 20, 56, 30, 22, 20, 20, 26}
+
+// Font dosyaları
+const (
+ fontFamilyReg = "dejavu"
+ fontFamilyBold = "dejavu-b"
+ fontPathReg = "fonts/DejaVuSans.ttf"
+ fontPathBold = "fonts/DejaVuSans-Bold.ttf"
+)
+
+// Kurumsal renkler
+var (
+ colorPrimary = [3]int{149, 113, 22} // #957116 (altın)
+ colorSecondary = [3]int{218, 193, 151} // #dac197 (bej)
+ colorDetailFill = [3]int{242, 235, 222} // secondary’den daha açık (detay satır zemin)
+)
+
+/* ============================ HELPERS ============================ */
+
+// Genişlikleri normalize et
+func normalizeWidths(base []float64, targetTotal float64) []float64 {
+ sum := 0.0
+ for _, v := range base {
+ sum += v
+ }
+ out := make([]float64, len(base))
+ if sum <= 0 {
+ each := targetTotal / float64(len(base))
+ for i := range out {
+ out[i] = each
+ }
+ return out
+ }
+ scale := targetTotal / sum
+ for i, v := range base {
+ out[i] = v * scale
+ }
+ return out
+}
+
+// Türkçe formatlı sayı
+func formatCurrencyTR(n float64) string {
+ s := fmt.Sprintf("%.2f", n) // "864000.00"
+ parts := strings.Split(s, ".")
+ intPart := parts[0]
+ decPart := parts[1]
+
+ // Binlik ayırıcı (.)
+ var out strings.Builder
+ for i, c := range intPart {
+ if (len(intPart)-i)%3 == 0 && i != 0 {
+ out.WriteRune('.')
+ }
+ out.WriteRune(c)
+ }
+
+ return out.String() + "," + decPart
+}
+
+// Fontları yükle
+func ensureFonts(pdf *gofpdf.Fpdf) {
+ if _, err := os.Stat(fontPathReg); err == nil {
+ pdf.AddUTF8Font(fontFamilyReg, "", fontPathReg)
+ } else {
+ log.Printf("⚠️ Font bulunamadı: %s", fontPathReg)
+ }
+ if _, err := os.Stat(fontPathBold); err == nil {
+ pdf.AddUTF8Font(fontFamilyBold, "", fontPathBold)
+ } else {
+ log.Printf("⚠️ Font bulunamadı: %s", fontPathBold)
+ }
+}
+
+// Güvenli satır kırma
+func splitLinesSafe(pdf *gofpdf.Fpdf, text string, width float64) [][]byte {
+ if width <= 0 {
+ width = 1
+ }
+ defer func() {
+ if rec := recover(); rec != nil {
+ log.Printf("⚠️ splitLinesSafe recover: %v", rec)
+ }
+ }()
+ return pdf.SplitLines([]byte(text), width)
+}
+
+// Metin wrap çizimi
+func drawWrapText(pdf *gofpdf.Fpdf, text string, x, y, width, lineH float64) {
+ if text == "" {
+ return
+ }
+ lines := splitLinesSafe(pdf, text, width)
+ cy := y
+ for _, ln := range lines {
+ pdf.SetXY(x, cy)
+ pdf.CellFormat(width, lineH, string(ln), "", 0, "L", false, 0, "")
+ cy += lineH
+ }
+}
+
+// Wrap satır yüksekliği
+func calcRowHeightForText(pdf *gofpdf.Fpdf, text string, colWidth, lineHeight, padX float64) float64 {
+ if colWidth <= 2*padX {
+ colWidth = 2*padX + 1
+ }
+ if text == "" {
+ return lineHeight
+ }
+ lines := splitLinesSafe(pdf, text, colWidth-(2*padX))
+ if len(lines) == 0 {
+ return lineHeight
+ }
+ h := float64(len(lines)) * lineHeight
+ if h < lineHeight {
+ h = lineHeight
+ }
+ return h
+}
+
+// Alt tarafa sığmıyor mu?
+func needNewPage(pdf *gofpdf.Fpdf, needH float64) bool {
+ return pdf.GetY()+needH+marginB > pageHeight
+}
+
+// NULL/boş’ları uzun çizgiye çevir
+func nullToDash(s string) string {
+ if strings.TrimSpace(s) == "" {
+ return "—"
+ }
+ return s
+}
+
+/* ============================ HEADER DRAW ============================ */
+
+// Küçük yardımcı: kutu içine otomatik 1-2 satır metin (taşarsa 2)
+func drawLabeledBox(pdf *gofpdf.Fpdf, x, y, w, h float64, label, value string, align string) {
+ // Çerçeve
+ pdf.Rect(x, y, w, h, "")
+ // İç marj
+ ix := x + 2
+ iy := y + 1.8
+ iw := w - 4
+ // Etiket (kalın, küçük)
+ pdf.SetFont(fontFamilyBold, "", 8)
+ pdf.SetXY(ix, iy)
+ pdf.CellFormat(iw, 4, label, "", 0, "L", false, 0, "")
+
+ // Değer (normal, 1-2 satır)
+ pdf.SetFont(fontFamilyReg, "", 8)
+ vy := iy + 4.2
+ lineH := 4.2
+ lines := splitLinesSafe(pdf, value, iw)
+ if len(lines) > 2 {
+ lines = lines[:2] // en fazla 2 satır göster
+ }
+ for _, ln := range lines {
+ pdf.SetXY(ix, vy)
+ pdf.CellFormat(iw, lineH, string(ln), "", 0, align, false, 0, "")
+ vy += lineH
+ }
+}
+
+func drawPageHeader(pdf *gofpdf.Fpdf, cariKod, cariIsim, start, end string) float64 {
+ logoPath, _ := filepath.Abs("./public/Baggi-Tekstil-A.s-Logolu.jpeg")
+
+ // Logo
+ pdf.ImageOptions(logoPath, hMarginL, 2, hLogoW, 0, false, gofpdf.ImageOptions{}, 0, "")
+
+ // Başlıklar
+ pdf.SetFont(hFontFamilyBold, "", 16)
+ pdf.SetTextColor(hColorPrimary[0], hColorPrimary[1], hColorPrimary[2])
+ pdf.SetXY(hMarginL+hLogoW+8, hMarginT+2)
+ pdf.CellFormat(120, 7, "Baggi Software System", "", 0, "L", false, 0, "")
+
+ pdf.SetFont(hFontFamilyBold, "", 12)
+ pdf.SetXY(hMarginL+hLogoW+8, hMarginT+10)
+ pdf.CellFormat(120, 6, "Cari Hesap Raporu", "", 0, "L", false, 0, "")
+
+ // Bugünün tarihi (sağ üst)
+ today := time.Now().Format("02.01.2006")
+ pdf.SetFont(hFontFamilyReg, "", 9)
+ pdf.SetXY(hPageWidth-hMarginR-40, hMarginT+3)
+ pdf.CellFormat(40, 6, "Tarih: "+today, "", 0, "R", false, 0, "")
+
+ // Cari & Tarih kutuları (daha yukarı taşındı)
+ boxY := hMarginT + hLogoW - 6
+ pdf.SetFont(hFontFamilyBold, "", 10)
+
+ pdf.Rect(hMarginL, boxY, 140, 11, "")
+ pdf.SetXY(hMarginL+2, boxY+3)
+ pdf.CellFormat(136, 5, fmt.Sprintf("Cari: %s — %s", cariKod, cariIsim), "", 0, "L", false, 0, "")
+
+ pdf.Rect(hPageWidth-hMarginR-140, boxY, 140, 11, "")
+ pdf.SetXY(hPageWidth-hMarginR-138, boxY+3)
+ pdf.CellFormat(136, 5, fmt.Sprintf("Tarih Aralığı: %s → %s", start, end), "", 0, "R", false, 0, "")
+
+ // Alt çizgi
+ y := boxY + 13
+ pdf.SetDrawColor(hColorPrimary[0], hColorPrimary[1], hColorPrimary[2])
+ pdf.Line(hMarginL, y, hPageWidth-hMarginR, y)
+ pdf.SetDrawColor(200, 200, 200)
+
+ return y + 4
+}
+
+/* ============================ GROUP BAR ============================ */
+
+func drawGroupBar(pdf *gofpdf.Fpdf, currency string, sonBakiye float64) {
+ // Kutu alanı (tam genişlik)
+ x := marginL
+ y := pdf.GetY()
+ w := pageWidth - marginL - marginR
+ h := groupBarH
+
+ // Çerçeve
+ pdf.SetDrawColor(colorPrimary[0], colorPrimary[1], colorPrimary[2])
+ pdf.SetLineWidth(0.6)
+ pdf.Rect(x, y, w, h, "")
+
+ // Metinler
+ pdf.SetFont(fontFamilyBold, "", 11.3) // bir tık büyük
+ pdf.SetTextColor(colorPrimary[0], colorPrimary[1], colorPrimary[2])
+
+ pdf.SetXY(x+cellPadX+1.0, y+(h-5.0)/2)
+ pdf.CellFormat(w*0.6, 5.0, fmt.Sprintf("%s", currency), "", 0, "L", false, 0, "")
+
+ txt := "Son Bakiye = " + formatCurrencyTR(sonBakiye)
+ pdf.SetXY(x+w*0.4, y+(h-5.0)/2)
+ pdf.CellFormat(w*0.6-2.0, 5.0, txt, "", 0, "R", false, 0, "")
+
+ // Renk/kalınlık geri
+ pdf.SetLineWidth(gridLineWidth)
+ pdf.SetDrawColor(200, 200, 200)
+ pdf.SetTextColor(0, 0, 0)
+
+ pdf.Ln(h)
+}
+
+/* ============================ HEADER ROWS ============================ */
+
+func drawMainHeaderRow(pdf *gofpdf.Fpdf, cols []string, widths []float64) {
+ // Ana başlık: Primary arkaplan, beyaz yazı
+ pdf.SetFont(fontFamilyBold, "", 10.2)
+ pdf.SetTextColor(255, 255, 255)
+ pdf.SetFillColor(colorPrimary[0], colorPrimary[1], colorPrimary[2])
+ pdf.SetDrawColor(120, 90, 20)
+
+ x := marginL
+ y := pdf.GetY()
+ for i, c := range cols {
+ pdf.Rect(x, y, widths[i], headerRowH, "DF")
+ pdf.SetXY(x+cellPadX, y+1.6)
+ pdf.CellFormat(widths[i]-2*cellPadX, headerRowH-3.2, c, "", 0, "C", false, 0, "")
+ x += widths[i]
+ }
+ pdf.SetY(y + headerRowH)
+
+}
+
+func drawDetailHeaderRow(pdf *gofpdf.Fpdf, cols []string, widths []float64) {
+ // Detay başlık: Secondary (daha açık)
+ pdf.SetFont(fontFamilyBold, "", 9.2)
+ pdf.SetFillColor(colorSecondary[0], colorSecondary[1], colorSecondary[2])
+ pdf.SetTextColor(0, 0, 0)
+ pdf.SetDrawColor(160, 140, 100)
+
+ x := marginL
+ y := pdf.GetY()
+ for i, c := range cols {
+ pdf.Rect(x, y, widths[i], subHeaderRowH, "DF")
+ pdf.SetXY(x+cellPadX, y+1.2)
+ pdf.CellFormat(widths[i]-2*cellPadX, subHeaderRowH-2.4, c, "", 0, "C", false, 0, "")
+ x += widths[i]
+ }
+ pdf.Ln(subHeaderRowH)
+}
+
+/* ============================ DATA ROWS ============================ */
+
+// Ana veri satırı: daha büyük ve kalın görünsün
+// Ana veri satırı (zebrasız, sıkı grid – satırlar yapışık)
+func drawMainDataRow(pdf *gofpdf.Fpdf, row []string, widths []float64, rowH float64) {
+ x := marginL
+ y := pdf.GetY()
+
+ // Arka plan beyaz, çizgiler gri
+ pdf.SetFont(fontFamilyBold, "", 8.5)
+ pdf.SetDrawColor(210, 210, 210)
+ pdf.SetTextColor(30, 30, 30)
+ pdf.SetFillColor(255, 255, 255)
+
+ for i, val := range row {
+ // Hücre çerçevesi
+ pdf.Rect(x, y, widths[i], rowH, "")
+ switch {
+ case i == 4: // Açıklama wrap
+ drawWrapText(pdf, val, x+cellPadX, y+0.8, widths[i]-2*cellPadX, lineHMain)
+ case i >= 6: // Sayısal sağ
+ pdf.SetXY(x+cellPadX, y+(rowH-lineHMain)/2)
+ pdf.CellFormat(widths[i]-2*cellPadX, lineHMain, val, "", 0, "R", false, 0, "")
+ default: // Normal sol
+ pdf.SetXY(x+cellPadX, y+(rowH-lineHMain)/2)
+ pdf.CellFormat(widths[i]-2*cellPadX, lineHMain, val, "", 0, "L", false, 0, "")
+ }
+ x += widths[i]
+ }
+
+ // ❌ pdf.Ln(rowH) yerine:
+ // ✅ bir alt satıra tam yapışık in
+ pdf.SetY(y + rowH)
+}
+
+// Detay veri satırı: açık zemin, biraz küçük; zebra opsiyonel
+func drawDetailDataRow(pdf *gofpdf.Fpdf, row []string, widths []float64, rowH float64, fillBg bool) {
+ pdf.SetFont(fontFamilyReg, "", 8.0)
+ pdf.SetTextColor(60, 60, 60)
+ pdf.SetDrawColor(220, 220, 220)
+
+ x := marginL
+ y := pdf.GetY()
+
+ for i, val := range row {
+ // Zemin
+ if fillBg {
+ pdf.SetFillColor(colorDetailFill[0], colorDetailFill[1], colorDetailFill[2])
+ pdf.Rect(x, y, widths[i], rowH, "DF")
+ pdf.SetFillColor(255, 255, 255) // geri
+ } else {
+ pdf.Rect(x, y, widths[i], rowH, "")
+ }
+
+ switch {
+ case i == 4: // İçerik wrap
+ drawWrapText(pdf, val, x+cellPadX, y+1.4, widths[i]-2*cellPadX, lineHDetail)
+ case i >= 7: // Sayısal sağ
+ pdf.SetXY(x+cellPadX, y+(rowH-lineHDetail)/2)
+ pdf.CellFormat(widths[i]-2*cellPadX, lineHDetail, val, "", 0, "R", false, 0, "")
+ default: // Sol
+ pdf.SetXY(x+cellPadX, y+(rowH-lineHDetail)/2)
+ pdf.CellFormat(widths[i]-2*cellPadX, lineHDetail, val, "", 0, "L", false, 0, "")
+ }
+ x += widths[i]
+ }
+ pdf.Ln(rowH)
+}
+
+/* ============================ HANDLER ============================ */
+
+func ExportPDFHandler(mssql *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ started := time.Now()
+
+ defer func() {
+ if rec := recover(); rec != nil {
+ log.Printf("❌ PANIC ExportPDFHandler: %v", rec)
+ http.Error(w, "PDF oluşturulurken hata oluştu", http.StatusInternalServerError)
+ }
+ }()
+
+ accountCode := r.URL.Query().Get("accountcode")
+ startDate := r.URL.Query().Get("startdate")
+ endDate := r.URL.Query().Get("enddate")
+
+ // parislemler sanitize
+ rawParis := r.URL.Query()["parislemler"]
+ parislemler := make([]string, 0, len(rawParis))
+ for _, v := range rawParis {
+ v = strings.TrimSpace(v)
+ if v == "" || strings.EqualFold(v, "undefined") || strings.EqualFold(v, "null") {
+ continue
+ }
+ parislemler = append(parislemler, v)
+ }
+ log.Printf("▶️ ExportPDFHandler: account=%s start=%s end=%s parislemler=%v",
+ accountCode, startDate, endDate, parislemler)
+
+ // 1) Header verileri
+ headers, belgeNos, err := queries.GetStatementsPDF(accountCode, startDate, endDate, parislemler)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ log.Printf("✅ Header verileri alındı: %d kayıt, %d belge no", len(headers), len(belgeNos))
+
+ // 2) Detay verileri
+ detailMap, err := queries.GetDetailsMapPDF(belgeNos, startDate, endDate)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ log.Printf("✅ Detay verileri alındı: %d belge için detay var", len(detailMap))
+
+ // 3) Gruplama
+ type grp struct {
+ code string
+ rows []models.StatementHeader
+ sonBakiye float64
+ }
+ order := []string{}
+ groups := map[string]*grp{}
+
+ for _, h := range headers {
+ g, ok := groups[h.ParaBirimi]
+ if !ok {
+ g = &grp{code: h.ParaBirimi}
+ groups[h.ParaBirimi] = g
+ order = append(order, h.ParaBirimi)
+ }
+ g.rows = append(g.rows, h)
+ }
+
+ for _, k := range order {
+ sort.SliceStable(groups[k].rows, func(i, j int) bool {
+ ri, rj := groups[k].rows[i], groups[k].rows[j]
+ if ri.BelgeTarihi == rj.BelgeTarihi {
+ return ri.BelgeNo < rj.BelgeNo
+ }
+ return ri.BelgeTarihi < rj.BelgeTarihi
+ })
+ if n := len(groups[k].rows); n > 0 {
+ groups[k].sonBakiye = groups[k].rows[n-1].Bakiye
+ }
+ }
+
+ // 4) Kolon genişlikleri
+ wAvail := pageWidth - marginL - marginR
+ mainWn := normalizeWidths(mainWbase, wAvail)
+ dWn := normalizeWidths(dWbase, wAvail)
+
+ // 5) PDF init
+ pdf := gofpdf.New("L", "mm", "A4", "")
+ pdf.SetMargins(marginL, marginT, marginR)
+ pdf.SetAutoPageBreak(false, marginB)
+ ensureFonts(pdf)
+ pdf.SetFont(fontFamilyReg, "", 8.5)
+ pdf.SetTextColor(0, 0, 0)
+
+ pageNum := 0
+ cariIsim := ""
+ if len(headers) > 0 {
+ cariIsim = headers[0].CariIsim
+ }
+
+ // Sayfa başlatıcı (header yüksekliği dinamik)
+ newPage := func() {
+ pageNum++
+ pdf.AddPage()
+
+ // drawPageHeader tablo başlangıç yüksekliğini döndürüyor
+ tableTop := drawPageHeader(pdf, accountCode, cariIsim, startDate, endDate)
+
+ // Sayfa numarası
+ pdf.SetFont(fontFamilyReg, "", 6)
+ pdf.SetXY(pageWidth-marginR-28, pageHeight-marginB+3)
+ pdf.CellFormat(28, 5, fmt.Sprintf("Sayfa %d", pageNum), "", 0, "R", false, 0, "")
+
+ // Tablo Y konumunu ayarla
+ pdf.SetY(tableTop)
+ }
+
+ newPage()
+
+ // 6) Gruplar yaz
+ for _, cur := range order {
+ g := groups[cur]
+
+ if needNewPage(pdf, groupBarH+headerRowH) {
+ newPage()
+ }
+ drawGroupBar(pdf, cur, g.sonBakiye)
+ drawMainHeaderRow(pdf, mainCols, mainWn)
+
+ for _, h := range g.rows {
+ row := []string{
+ h.BelgeNo, h.BelgeTarihi, h.VadeTarihi, h.IslemTipi,
+ h.Aciklama, h.ParaBirimi,
+ formatCurrencyTR(h.Borc),
+ formatCurrencyTR(h.Alacak),
+ formatCurrencyTR(h.Bakiye),
+ }
+
+ pdf.SetFont(fontFamilyBold, "", 9.6)
+ rh := calcRowHeightForText(pdf, row[4], mainWn[4], lineHMain, cellPadX)
+
+ if needNewPage(pdf, rh+headerRowH) {
+ newPage()
+ drawGroupBar(pdf, cur, g.sonBakiye)
+ drawMainHeaderRow(pdf, mainCols, mainWn)
+ }
+ drawMainDataRow(pdf, row, mainWn, rh)
+
+ // detaylar
+ details := detailMap[h.BelgeNo]
+ if len(details) > 0 {
+ if needNewPage(pdf, subHeaderRowH) {
+ newPage()
+ drawGroupBar(pdf, cur, g.sonBakiye)
+ drawMainHeaderRow(pdf, mainCols, mainWn)
+ }
+ drawDetailHeaderRow(pdf, dCols, dWn)
+
+ for i, d := range details {
+ drow := []string{
+ nullToDash(d.UrunAnaGrubu),
+ nullToDash(d.UrunAltGrubu),
+ nullToDash(d.YetiskinGarson),
+ nullToDash(d.Fit),
+ nullToDash(d.Icerik),
+ nullToDash(d.UrunKodu),
+ nullToDash(d.UrunRengi),
+ formatCurrencyTR(d.ToplamAdet),
+ formatCurrencyTR(d.ToplamFiyat),
+ formatCurrencyTR(d.ToplamTutar),
+ }
+
+ pdf.SetFont(fontFamilyReg, "", 8)
+ rh2 := calcRowHeightForText(pdf, drow[4], dWn[4], lineHDetail, cellPadX)
+
+ if needNewPage(pdf, rh2) {
+ newPage()
+ drawGroupBar(pdf, cur, g.sonBakiye)
+ drawMainHeaderRow(pdf, mainCols, mainWn)
+ drawDetailHeaderRow(pdf, dCols, dWn)
+ }
+ // zebra: çift indekslerde açık zemin
+ fill := (i%2 == 0)
+ drawDetailDataRow(pdf, drow, dWn, rh2, fill)
+ }
+ }
+ }
+ pdf.Ln(3)
+ }
+
+ // 7) Çıktı
+ var buf bytes.Buffer
+ if err := pdf.Output(&buf); err != nil {
+ http.Error(w, "PDF oluşturulamadı: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/pdf")
+ w.Header().Set("Content-Disposition", "inline; filename=statement.pdf")
+ _, _ = w.Write(buf.Bytes())
+
+ log.Printf("✅ PDF üretimi tamam: %s", time.Since(started))
+ }
+}
+
+/*
+NOTLAR:
+- Header artık dinamik yüksekliğe sahip (drawPageHeader -> contentTopY döner).
+- Logo sol üst köşe, başlık “toolbar” gibi; “Cari / Tarih” kutuları
+ 1–2 satır metni çerçeve içinde otomatik kırar.
+- Grup barı primary renkte çerçeveli ve yazı biraz büyük.
+- Ana satırlar kalın & bir tık büyük (detaydan farklı).
+- Detay satırlar açık (bej) zeminle zebra (i%2==0) uygulanır.
+- SplitLines her yerde splitLinesSafe ile korunuyor (panic yok).
+- AddPage sadece newPage()’de.
+- Font bulunamazsa gömme fontlarla devam edilir (log yazar).
+*/
diff --git a/svc/routes/test_mail.go b/svc/routes/test_mail.go
new file mode 100644
index 0000000..3dc107a
--- /dev/null
+++ b/svc/routes/test_mail.go
@@ -0,0 +1,49 @@
+package routes
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "bssapp-backend/auth"
+ "bssapp-backend/internal/mailer"
+)
+
+func TestMailHandler(ml *mailer.GraphMailer) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ var req struct {
+ To string `json:"to"`
+ }
+
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "invalid body", http.StatusBadRequest)
+ return
+ }
+
+ err := ml.Send(
+ context.Background(),
+ mailer.Message{
+ To: []string{req.To},
+ Subject: "BSSApp Test Mail",
+ BodyHTML: "🎉 Test mail başarılı
Microsoft Graph üzerinden gönderildi.
",
+ },
+ )
+
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+ _ = json.NewEncoder(w).Encode(map[string]string{
+ "message": "Mail gönderildi",
+ })
+ })
+}
diff --git a/svc/routes/todaycurrencyv3.go b/svc/routes/todaycurrencyv3.go
new file mode 100644
index 0000000..5704e95
--- /dev/null
+++ b/svc/routes/todaycurrencyv3.go
@@ -0,0 +1,30 @@
+package routes
+
+import (
+ "bssapp-backend/queries"
+ "database/sql"
+ "encoding/json"
+ "fmt"
+ "net/http"
+)
+
+// ✅ Handler artık parametre alıyor
+func GetTodayCurrencyV3Handler(mssql *sql.DB) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ code := r.URL.Query().Get("code")
+ if code == "" {
+ http.Error(w, "Eksik parametre: code", http.StatusBadRequest)
+ return
+ }
+
+ currency, err := queries.GetTodayCurrencyV3(mssql, code) // 👈 MSSQL
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Kur bulunamadı: %v", err), http.StatusNotFound)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ _ = json.NewEncoder(w).Encode(currency)
+ }
+}
diff --git a/svc/routes/user_detail.go b/svc/routes/user_detail.go
new file mode 100644
index 0000000..9a00706
--- /dev/null
+++ b/svc/routes/user_detail.go
@@ -0,0 +1,320 @@
+package routes
+
+import (
+ "bssapp-backend/internal/auditlog"
+ "bssapp-backend/internal/mailer"
+ "bssapp-backend/internal/security"
+ "bssapp-backend/models"
+ "bssapp-backend/queries"
+ "bytes"
+ "database/sql"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "strconv"
+ "time"
+
+ "github.com/gorilla/mux"
+)
+
+// ======================================================
+// 👤 USER DETAIL ROUTE (GET + PUT)
+// URL: /api/users/{id}
+// ======================================================
+type LoginUser struct {
+ ID int64 `json:"id"`
+ Username string `json:"username"`
+ Email string `json:"email"`
+ RoleID int `json:"role_id"`
+ RoleCode string `json:"role_code"`
+ ForcePasswordChange bool `json:"force_password_change"`
+}
+
+func UserDetailRoute(db *sql.DB) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ idStr := mux.Vars(r)["id"]
+ userID, err := strconv.ParseInt(idStr, 10, 64)
+ if err != nil || userID <= 0 {
+ http.Error(w, "Geçersiz kullanıcı id", http.StatusBadRequest)
+ return
+ }
+
+ switch r.Method {
+ case http.MethodGet:
+ handleUserGet(db, w, userID)
+ case http.MethodPut:
+ handleUserUpdate(db, w, r, userID)
+ case http.MethodOptions:
+ w.WriteHeader(http.StatusOK)
+ default:
+ http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+ }
+ })
+}
+
+// ======================================================
+// 📥 GET USER
+// ======================================================
+// ======================================================
+// 📥 GET USER (FINAL - mk_dfusr Uyumlu)
+// ======================================================
+func handleUserGet(db *sql.DB, w http.ResponseWriter, userID int64) {
+
+ var u models.UserDetail
+
+ // --------------------------------------------------
+ // 🟢 HEADER
+ // --------------------------------------------------
+ err := db.QueryRow(
+ queries.GetUserHeader,
+ userID,
+ ).Scan(
+ &u.ID,
+ &u.Code, // username
+ &u.IsActive,
+ &u.FullName,
+ &u.Email,
+ &u.Mobile,
+ &u.Address,
+ &u.HasPassword,
+ )
+
+ if err != nil {
+
+ // detaylı log (çok önemli)
+ fmt.Println("❌ [UserDetail] HEADER SCAN ERROR:", err)
+
+ if err == sql.ErrNoRows {
+ http.Error(w, "Kullanıcı bulunamadı", http.StatusNotFound)
+ return
+ }
+
+ http.Error(w, "User header error", http.StatusInternalServerError)
+ return
+ }
+
+ // --------------------------------------------------
+ // 🟢 ROLES
+ // --------------------------------------------------
+ roleRows, err := db.Query(queries.GetUserRoles, userID)
+ if err != nil {
+ fmt.Println("⚠️ [UserDetail] ROLE QUERY:", err)
+ } else {
+ defer roleRows.Close()
+
+ for roleRows.Next() {
+ var code string
+
+ if err := roleRows.Scan(&code); err == nil {
+ u.Roles = append(u.Roles, code)
+ }
+ }
+ }
+
+ // --------------------------------------------------
+ // 🟢 DEPARTMENTS
+ // --------------------------------------------------
+ deptRows, err := db.Query(queries.GetUserDepartments, userID)
+ if err != nil {
+ fmt.Println("⚠️ [UserDetail] DEPT QUERY:", err)
+ } else {
+ defer deptRows.Close()
+
+ for deptRows.Next() {
+
+ var d models.DeptOption
+
+ if err := deptRows.Scan(&d.Code, &d.Title); err == nil {
+ u.Departments = append(u.Departments, d)
+ }
+ }
+ }
+
+ // --------------------------------------------------
+ // 🟢 PIYASALAR
+ // --------------------------------------------------
+ piyRows, err := db.Query(queries.GetUserPiyasalar, userID)
+ if err != nil {
+ fmt.Println("⚠️ [UserDetail] PIYASA QUERY:", err)
+ } else {
+ defer piyRows.Close()
+
+ for piyRows.Next() {
+
+ var p models.DeptOption
+
+ if err := piyRows.Scan(&p.Code, &p.Title); err == nil {
+ u.Piyasalar = append(u.Piyasalar, p)
+ }
+ }
+ }
+
+ // --------------------------------------------------
+ // 🟢 NEBIM USERS
+ // --------------------------------------------------
+ nebimRows, err := db.Query(queries.GetUserNebim, userID)
+ if err != nil {
+ fmt.Println("⚠️ [UserDetail] NEBIM QUERY:", err)
+ } else {
+ defer nebimRows.Close()
+
+ for nebimRows.Next() {
+
+ var n models.NebimOption
+
+ if err := nebimRows.Scan(
+ &n.Username,
+ &n.UserGroupCode,
+ ); err == nil {
+
+ u.NebimUsers = append(u.NebimUsers, n)
+ }
+ }
+ }
+
+ // --------------------------------------------------
+ // 🟢 RESPONSE
+ // --------------------------------------------------
+ w.WriteHeader(http.StatusOK)
+
+ if err := json.NewEncoder(w).Encode(u); err != nil {
+ fmt.Println("❌ [UserDetail] JSON ENCODE:", err)
+ }
+}
+
+// ======================================================
+// ✍️ UPDATE USER (PUT)
+// ======================================================
+func handleUserUpdate(db *sql.DB, w http.ResponseWriter, r *http.Request, userID int64) {
+
+ raw, _ := io.ReadAll(r.Body)
+
+ fmt.Println("🟥 RAW BODY:", string(raw))
+
+ // body tekrar okunabilsin diye reset
+ r.Body = io.NopCloser(bytes.NewBuffer(raw))
+
+ var payload models.UserWrite
+
+ if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
+ fmt.Printf("🟦 DECODED PAYLOAD: %+v\n", payload)
+
+ fmt.Println("❌ JSON DECODE ERROR:", err)
+
+ http.Error(w, "Geçersiz payload", http.StatusBadRequest)
+ return
+ }
+
+ tx, err := db.Begin()
+ if err != nil {
+ http.Error(w, "Transaction başlatılamadı", http.StatusInternalServerError)
+ return
+ }
+ defer tx.Rollback()
+
+ _, err = tx.Exec(
+ queries.UpdateUserHeader,
+ userID,
+ payload.Code,
+ payload.IsActive,
+ payload.FullName,
+ payload.Email,
+ payload.Mobile,
+ payload.Address,
+ )
+ if err != nil {
+ http.Error(w, "Header güncellenemedi", http.StatusInternalServerError)
+ return
+ }
+
+ tx.Exec(`DELETE FROM dfrole_usr WHERE dfusr_id = $1`, userID)
+ for _, code := range payload.Roles {
+ tx.Exec(queries.InsertUserRole, userID, code)
+ }
+
+ tx.Exec(`DELETE FROM dfusr_dprt WHERE dfusr_id = $1`, userID)
+ for _, d := range payload.Departments {
+ tx.Exec(queries.InsertUserDepartment, userID, d.Code)
+ }
+
+ tx.Exec(`DELETE FROM dfusr_piyasa WHERE dfusr_id = $1`, userID)
+ for _, p := range payload.Piyasalar {
+ tx.Exec(queries.InsertUserPiyasa, userID, p.Code)
+ }
+
+ tx.Exec(`DELETE FROM dfusr_nebim_user WHERE dfusr_id = $1`, userID)
+ for _, n := range payload.NebimUsers {
+ tx.Exec(queries.InsertUserNebim, userID, n.Username)
+ }
+
+ if err := tx.Commit(); err != nil {
+ http.Error(w, "Commit başarısız", http.StatusInternalServerError)
+ return
+ }
+
+ _ = json.NewEncoder(w).Encode(map[string]any{"success": true})
+}
+
+// ======================================================
+// 🔐 ADMIN — PASSWORD RESET MAIL
+// ======================================================
+func SendPasswordResetMailHandler(
+ db *sql.DB,
+ mailer *mailer.GraphMailer,
+) http.HandlerFunc {
+
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ userID, _ := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
+
+ var email string
+ err := db.QueryRow(`
+ SELECT email
+ FROM mk_dfusr
+ WHERE id = $1 AND is_active = true
+ `, userID).Scan(&email)
+
+ if err != nil || email == "" {
+ w.WriteHeader(http.StatusOK)
+ return
+ }
+
+ // 🔑 TOKEN (PLAIN + HASH)
+ plain, hash, err := security.GenerateResetToken()
+ if err != nil {
+ w.WriteHeader(http.StatusOK)
+ return
+ }
+
+ expires := time.Now().Add(30 * time.Minute)
+
+ // 💾 DB → SADECE HASH
+ _, _ = db.Exec(`
+ INSERT INTO dfusr_password_reset (dfusr_id, token, expires_at)
+ VALUES ($1,$2,$3)
+ `, userID, hash, expires)
+
+ // 🔗 URL → PLAIN
+ resetURL := fmt.Sprintf(
+ "%s/password-reset/%s",
+ os.Getenv("FRONTEND_URL"),
+ plain,
+ )
+
+ _ = mailer.SendPasswordResetMail(email, resetURL)
+
+ // 🕵️ AUDIT
+ auditlog.ForcePasswordChangeStarted(
+ r.Context(),
+ userID,
+ "admin_reset",
+ )
+
+ w.WriteHeader(http.StatusOK)
+ }
+}
diff --git a/svc/routes/user_list.go b/svc/routes/user_list.go
new file mode 100644
index 0000000..56ddc08
--- /dev/null
+++ b/svc/routes/user_list.go
@@ -0,0 +1,81 @@
+package routes
+
+import (
+ "bssapp-backend/models"
+ "bssapp-backend/queries"
+ "database/sql"
+ "encoding/json"
+ "log"
+ "net/http"
+)
+
+// ======================================================
+// 📌 UserListRoute — Kullanıcı Listeleme API (FINAL)
+// ======================================================
+func UserListRoute(pg *sql.DB) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+ // --------------------------------------------------
+ // 🔍 Query Param
+ // --------------------------------------------------
+ search := r.URL.Query().Get("search")
+ log.Printf("📥 /api/users/list çağrıldı | search='%s'", search)
+
+ // --------------------------------------------------
+ // 🗄️ SQL CALL
+ // --------------------------------------------------
+ rows, err := queries.GetUserList(pg, search)
+ if err != nil {
+ log.Printf("❌ SQL sorgu hatası (GetUserList): %v", err)
+ http.Error(w, "Veritabanı hatası", http.StatusInternalServerError)
+ return
+ }
+ defer rows.Close()
+
+ // --------------------------------------------------
+ // 📦 Sonuç Listesi
+ // --------------------------------------------------
+ list := make([]models.UserListRow, 0, 100)
+ count := 0
+
+ // ==================================================
+ // 🧠 SCAN — SQL SELECT ile BİRE BİR (8 kolon)
+ // ==================================================
+ for rows.Next() {
+
+ var u models.UserListRow
+
+ err = rows.Scan(
+ &u.ID, // 1
+ &u.Code, // 2
+ &u.IsActive, // 3
+ &u.NebimUsername, // 4 (nullable)
+ &u.UserGroupCode, // 5 (nullable)
+ &u.RoleNames, // 6
+ &u.DepartmentNames, // 7
+ &u.PiyasaNames, // 8
+ )
+
+ if err != nil {
+ log.Printf("⚠️ Satır atlandı (SCAN hatası): %v", err)
+ continue
+ }
+
+ list = append(list, u)
+ count++
+ }
+
+ if err := rows.Err(); err != nil {
+ log.Printf("⚠️ rows.Err(): %v", err)
+ }
+
+ log.Printf("✅ User listesi tamamlandı — %d kayıt gönderildi.", count)
+
+ // --------------------------------------------------
+ // 📤 JSON OUTPUT
+ // --------------------------------------------------
+ _ = json.NewEncoder(w).Encode(list)
+ })
+}
diff --git a/svc/routes/userpermissions.go b/svc/routes/userpermissions.go
new file mode 100644
index 0000000..be6252b
--- /dev/null
+++ b/svc/routes/userpermissions.go
@@ -0,0 +1,232 @@
+package routes
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/internal/auditlog"
+ "bssapp-backend/permissions"
+ "database/sql"
+ "encoding/json"
+ "fmt"
+ "log"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/gorilla/mux"
+)
+
+/* =====================================================
+ GET USER OVERRIDES
+ GET /api/users/{id}/permissions
+===================================================== */
+
+func GetUserPermissionsHandler(db *sql.DB) http.HandlerFunc {
+
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ // ✅ Auth kontrolü yeterli (Yetki middleware'de)
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ idStr := mux.Vars(r)["id"]
+
+ id, err := strconv.ParseInt(idStr, 10, 64)
+ if err != nil || id <= 0 {
+ http.Error(w, "invalid id", http.StatusBadRequest)
+ return
+ }
+
+ repo := permissions.NewPermissionRepository(db)
+
+ rows, err := repo.GetUserOverridesByUserID(id)
+ if err != nil {
+ http.Error(w, "db error", http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+
+ json.NewEncoder(w).Encode(rows)
+ }
+}
+
+/* =====================================================
+ SAVE USER OVERRIDES
+ POST /api/users/{id}/permissions
+===================================================== */
+
+func SaveUserPermissionsHandler(db *sql.DB) http.HandlerFunc {
+
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ // ✅ Auth kontrolü yeterli
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ userIDStr := mux.Vars(r)["id"]
+
+ userID, err := strconv.ParseInt(userIDStr, 10, 64)
+ if err != nil || userID <= 0 {
+ http.Error(w, "invalid id", http.StatusBadRequest)
+ return
+ }
+
+ /* ================= PAYLOAD ================= */
+
+ var list []permissions.UserPermissionRequest
+
+ if err := json.NewDecoder(r.Body).Decode(&list); err != nil {
+ http.Error(w, "bad payload", http.StatusBadRequest)
+ return
+ }
+
+ repo := permissions.NewPermissionRepository(db)
+
+ /* ================= OLD ================= */
+
+ oldRows, _ := repo.GetUserOverridesByUserID(userID)
+
+ oldMap := map[string]bool{}
+
+ for _, p := range oldRows {
+ key := p.Module + ":" + p.Action
+ oldMap[key] = p.Allowed
+ }
+
+ /* ================= NEW ================= */
+
+ newMap := map[string]bool{}
+
+ for _, p := range list {
+ key := p.Module + ":" + p.Action
+ newMap[key] = p.Allowed
+ }
+
+ /* ================= DIFF ================= */
+
+ var changes []map[string]any
+
+ for key, newVal := range newMap {
+
+ oldVal := oldMap[key]
+
+ if oldVal != newVal {
+
+ parts := strings.Split(key, ":")
+ if len(parts) != 2 {
+ continue
+ }
+
+ changes = append(changes, map[string]any{
+ "module": parts[0],
+ "action": parts[1],
+ "before": oldVal,
+ "after": newVal,
+ })
+ }
+ }
+
+ /* ================= SAVE ================= */
+
+ if err := repo.SaveUserOverrides(userID, list); err != nil {
+ http.Error(w, "db error", http.StatusInternalServerError)
+ return
+ }
+
+ /* ================= AUDIT ================= */
+
+ if len(changes) > 0 && claims != nil {
+
+ var targetUsername string
+
+ _ = db.QueryRow(`
+ SELECT username
+ FROM mk_dfusr
+ WHERE id = $1
+ `, userID).Scan(&targetUsername)
+
+ auditlog.Enqueue(r.Context(), auditlog.ActivityLog{
+
+ ActionType: "user_permission_change",
+ ActionCategory: "permission",
+
+ ActionTarget: fmt.Sprintf(
+ "/api/users/%d/permissions",
+ userID,
+ ),
+
+ Description: "user permission overrides updated",
+
+ Username: claims.Username,
+ RoleCode: claims.RoleCode,
+ DfUsrID: int64(claims.ID),
+
+ TargetDfUsrID: userID,
+ TargetUsername: targetUsername,
+
+ ChangeBefore: map[string]any{
+ "permissions": oldRows,
+ },
+
+ ChangeAfter: map[string]any{
+ "changes": changes,
+ },
+
+ IsSuccess: true,
+ })
+ }
+
+ /* ================= RESPONSE ================= */
+
+ w.Header().Set("Content-Type", "application/json")
+
+ json.NewEncoder(w).Encode(map[string]any{
+ "success": true,
+ })
+ }
+}
+
+/* =====================================================
+ OPTIONAL METHOD
+===================================================== */
+
+func (h *PermissionHandler) SaveUserOverrides(w http.ResponseWriter, r *http.Request) {
+
+ claims, ok := auth.GetClaimsFromContext(r.Context())
+ if !ok || claims == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ userID, _ := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
+
+ var list []permissions.UserPermissionRequest
+
+ if err := json.NewDecoder(r.Body).Decode(&list); err != nil {
+ http.Error(w, "bad payload", 400)
+ return
+ }
+
+ log.Println("➡️ SAVE USER PERMISSIONS")
+ log.Println("USER ID:", userID)
+ log.Println("ITEM COUNT:", len(list))
+
+ if err := h.Repo.SaveUserOverrides(userID, list); err != nil {
+
+ log.Println("🔥 USER PERMISSION SAVE ERROR")
+ log.Println("ERROR:", err)
+
+ http.Error(w, err.Error(), 500)
+ return
+ }
+
+ json.NewEncoder(w).Encode(map[string]bool{
+ "success": true,
+ })
+}
diff --git a/svc/services/auth.go b/svc/services/auth.go
new file mode 100644
index 0000000..474d071
--- /dev/null
+++ b/svc/services/auth.go
@@ -0,0 +1,42 @@
+package services
+
+import (
+ "bssapp-backend/models"
+ "strings"
+
+ "golang.org/x/crypto/bcrypt"
+)
+
+// CheckPasswordWithLegacy
+// - SADECE doğrulama yapar
+// - DB write YAPMAZ
+// - Migration kararını caller verir
+func CheckPasswordWithLegacy(user *models.User, plain string) bool {
+ if user == nil {
+ return false
+ }
+
+ plain = strings.TrimSpace(plain)
+ if plain == "" {
+ return false
+ }
+
+ stored := strings.TrimSpace(user.Upass)
+ if stored == "" {
+ return false
+ }
+
+ // 1️⃣ bcrypt hash mi?
+ if isBcryptHash(stored) {
+ return bcrypt.CompareHashAndPassword([]byte(stored), []byte(plain)) == nil
+ }
+
+ // 2️⃣ TAM LEGACY — düz metin (eski kayıtlar)
+ return stored == plain
+}
+
+func isBcryptHash(s string) bool {
+ return strings.HasPrefix(s, "$2a$") ||
+ strings.HasPrefix(s, "$2b$") ||
+ strings.HasPrefix(s, "$2y$")
+}
diff --git a/svc/utils/error.go b/svc/utils/error.go
new file mode 100644
index 0000000..527cec1
--- /dev/null
+++ b/svc/utils/error.go
@@ -0,0 +1,8 @@
+// utils/logger.go
+package utils
+
+import "log"
+
+func LogError(scope string, err error) {
+ log.Printf("[%s] %v", scope, err)
+}
diff --git a/svc/utils/mail.env b/svc/utils/mail.env
new file mode 100644
index 0000000..bbd1fe0
--- /dev/null
+++ b/svc/utils/mail.env
@@ -0,0 +1,6 @@
+SMTP_HOST=smtp.office365.com
+SMTP_PORT=587
+SMTP_USERNAME=admin@baggi.com.tr
+SMTP_PASSWORD=Tav34909
+SMTP_FROM=admin@baggi.com.tr
+SMTP_STARTTLS=true
diff --git a/svc/utils/utils.go b/svc/utils/utils.go
new file mode 100644
index 0000000..cf9addd
--- /dev/null
+++ b/svc/utils/utils.go
@@ -0,0 +1,17 @@
+package utils
+
+import (
+ "bssapp-backend/auth"
+ "bssapp-backend/models"
+)
+
+func UserFromClaims(c *auth.Claims) *models.User {
+ if c == nil {
+ return nil
+ }
+
+ return &models.User{
+ ID: int(c.ID),
+ Username: c.Username,
+ }
+}
diff --git a/svc/utils/uuid.go b/svc/utils/uuid.go
new file mode 100644
index 0000000..b45d29f
--- /dev/null
+++ b/svc/utils/uuid.go
@@ -0,0 +1,11 @@
+// utils/uuid.go
+package utils
+
+import (
+ "github.com/google/uuid"
+)
+
+// NewUUID returns a new random UUID (v4)
+func NewUUID() string {
+ return uuid.New().String()
+}
diff --git a/ui/-.editorconfig b/ui/-.editorconfig
new file mode 100644
index 0000000..f654551
--- /dev/null
+++ b/ui/-.editorconfig
@@ -0,0 +1,7 @@
+[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
+charset = utf-8
+indent_size = 2
+indent_style = space
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/ui/.editorconfig b/ui/.editorconfig
new file mode 100644
index 0000000..f654551
--- /dev/null
+++ b/ui/.editorconfig
@@ -0,0 +1,7 @@
+[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
+charset = utf-8
+indent_size = 2
+indent_style = space
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/ui/.gitignore b/ui/.gitignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/ui/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/ui/.idea/.gitignore b/ui/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/ui/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/ui/.idea/baggisoftwaresystem.iml b/ui/.idea/baggisoftwaresystem.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/ui/.idea/baggisoftwaresystem.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/.idea/modules.xml b/ui/.idea/modules.xml
new file mode 100644
index 0000000..bd6893d
--- /dev/null
+++ b/ui/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/.idea/vcs.xml b/ui/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/ui/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/.npmrc b/ui/.npmrc
new file mode 100644
index 0000000..eb19082
--- /dev/null
+++ b/ui/.npmrc
@@ -0,0 +1,5 @@
+# pnpm-related options
+shamefully-hoist=true
+strict-peer-dependencies=false
+# to get the latest compatible packages when creating the project https://github.com/pnpm/pnpm/issues/6463
+resolution-mode=highest
diff --git a/ui/.quasar/dev-spa/app.js b/ui/.quasar/dev-spa/app.js
new file mode 100644
index 0000000..608c77d
--- /dev/null
+++ b/ui/.quasar/dev-spa/app.js
@@ -0,0 +1,77 @@
+/* eslint-disable */
+/**
+ * THIS FILE IS GENERATED AUTOMATICALLY.
+ * DO NOT EDIT.
+ *
+ * You are probably looking on adding startup/initialization code.
+ * Use "quasar new boot " and add it there.
+ * One boot file per concern. Then reference the file(s) in quasar.config file > boot:
+ * boot: ['file', ...] // do not add ".js" extension to it.
+ *
+ * Boot files are your "main.js"
+ **/
+
+
+
+
+
+import { Quasar } from 'quasar'
+import { markRaw } from 'vue'
+import RootComponent from 'app/src/App.vue'
+
+import createStore from 'app/src/stores/index'
+import createRouter from 'app/src/router/index'
+
+
+
+
+
+export default async function (createAppFn, quasarUserOptions) {
+
+
+ // Create the app instance.
+ // Here we inject into it the Quasar UI, the router & possibly the store.
+ const app = createAppFn(RootComponent)
+
+
+ app.config.performance = true
+
+
+ app.use(Quasar, quasarUserOptions)
+
+
+
+
+ const store = typeof createStore === 'function'
+ ? await createStore({})
+ : createStore
+
+
+ app.use(store)
+
+
+
+
+
+ const router = markRaw(
+ typeof createRouter === 'function'
+ ? await createRouter({store})
+ : createRouter
+ )
+
+
+ // make router instance available in store
+
+ store.use(({ store }) => { store.router = router })
+
+
+
+ // Expose the app, the router and the store.
+ // Note that we are not mounting the app here, since bootstrapping will be
+ // different depending on whether we are in a browser or on the server.
+ return {
+ app,
+ store,
+ router
+ }
+}
diff --git a/ui/.quasar/dev-spa/client-entry.js b/ui/.quasar/dev-spa/client-entry.js
new file mode 100644
index 0000000..9e9e8bf
--- /dev/null
+++ b/ui/.quasar/dev-spa/client-entry.js
@@ -0,0 +1,158 @@
+/* eslint-disable */
+/**
+ * THIS FILE IS GENERATED AUTOMATICALLY.
+ * DO NOT EDIT.
+ *
+ * You are probably looking on adding startup/initialization code.
+ * Use "quasar new boot " and add it there.
+ * One boot file per concern. Then reference the file(s) in quasar.config file > boot:
+ * boot: ['file', ...] // do not add ".js" extension to it.
+ *
+ * Boot files are your "main.js"
+ **/
+
+
+import { createApp } from 'vue'
+
+
+
+
+
+
+
+import '@quasar/extras/roboto-font/roboto-font.css'
+
+import '@quasar/extras/material-icons/material-icons.css'
+
+
+
+
+// We load Quasar stylesheet file
+import 'quasar/dist/quasar.sass'
+
+
+
+
+import 'src/css/app.css'
+
+
+import createQuasarApp from './app.js'
+import quasarUserOptions from './quasar-user-options.js'
+
+
+
+
+
+
+console.info('[Quasar] Running SPA.')
+
+
+
+const publicPath = `/`
+
+
+async function start ({
+ app,
+ router
+ , store
+}, bootFiles) {
+
+ let hasRedirected = false
+ const getRedirectUrl = url => {
+ try { return router.resolve(url).href }
+ catch (err) {}
+
+ return Object(url) === url
+ ? null
+ : url
+ }
+ const redirect = url => {
+ hasRedirected = true
+
+ if (typeof url === 'string' && /^https?:\/\//.test(url)) {
+ window.location.href = url
+ return
+ }
+
+ const href = getRedirectUrl(url)
+
+ // continue if we didn't fail to resolve the url
+ if (href !== null) {
+ window.location.href = href
+ window.location.reload()
+ }
+ }
+
+ const urlPath = window.location.href.replace(window.location.origin, '')
+
+ for (let i = 0; hasRedirected === false && i < bootFiles.length; i++) {
+ try {
+ await bootFiles[i]({
+ app,
+ router,
+ store,
+ ssrContext: null,
+ redirect,
+ urlPath,
+ publicPath
+ })
+ }
+ catch (err) {
+ if (err && err.url) {
+ redirect(err.url)
+ return
+ }
+
+ console.error('[Quasar] boot error:', err)
+ return
+ }
+ }
+
+ if (hasRedirected === true) return
+
+
+ app.use(router)
+
+
+
+
+
+
+ app.mount('#q-app')
+
+
+
+}
+
+createQuasarApp(createApp, quasarUserOptions)
+
+ .then(app => {
+ // eventually remove this when Cordova/Capacitor/Electron support becomes old
+ const [ method, mapFn ] = Promise.allSettled !== void 0
+ ? [
+ 'allSettled',
+ bootFiles => bootFiles.map(result => {
+ if (result.status === 'rejected') {
+ console.error('[Quasar] boot error:', result.reason)
+ return
+ }
+ return result.value.default
+ })
+ ]
+ : [
+ 'all',
+ bootFiles => bootFiles.map(entry => entry.default)
+ ]
+
+ return Promise[ method ]([
+
+ import(/* webpackMode: "eager" */ 'boot/axios'),
+
+ import(/* webpackMode: "eager" */ 'boot/dayjs')
+
+ ]).then(bootFiles => {
+ const boot = mapFn(bootFiles).filter(entry => typeof entry === 'function')
+ start(app, boot)
+ })
+ })
+
diff --git a/ui/.quasar/dev-spa/client-prefetch.js b/ui/.quasar/dev-spa/client-prefetch.js
new file mode 100644
index 0000000..9bbe3c5
--- /dev/null
+++ b/ui/.quasar/dev-spa/client-prefetch.js
@@ -0,0 +1,116 @@
+/* eslint-disable */
+/**
+ * THIS FILE IS GENERATED AUTOMATICALLY.
+ * DO NOT EDIT.
+ *
+ * You are probably looking on adding startup/initialization code.
+ * Use "quasar new boot " 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()
+ })
+ })
+}
diff --git a/ui/.quasar/dev-spa/quasar-user-options.js b/ui/.quasar/dev-spa/quasar-user-options.js
new file mode 100644
index 0000000..ac1dae3
--- /dev/null
+++ b/ui/.quasar/dev-spa/quasar-user-options.js
@@ -0,0 +1,23 @@
+/* eslint-disable */
+/**
+ * THIS FILE IS GENERATED AUTOMATICALLY.
+ * DO NOT EDIT.
+ *
+ * You are probably looking on adding startup/initialization code.
+ * Use "quasar new boot " 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} }
+
diff --git a/ui/.quasar/feature-flags.d.ts b/ui/.quasar/feature-flags.d.ts
new file mode 100644
index 0000000..dab07b3
--- /dev/null
+++ b/ui/.quasar/feature-flags.d.ts
@@ -0,0 +1,8 @@
+/* eslint-disable */
+import "quasar/dist/types/feature-flag.d.ts";
+
+declare module "quasar/dist/types/feature-flag.d.ts" {
+ interface QuasarFeatureFlags {
+ store: true;
+ }
+}
diff --git a/ui/.quasar/pinia.d.ts b/ui/.quasar/pinia.d.ts
new file mode 100644
index 0000000..5bc0a53
--- /dev/null
+++ b/ui/.quasar/pinia.d.ts
@@ -0,0 +1,8 @@
+/* eslint-disable */
+import { Router } from 'vue-router';
+
+declare module 'pinia' {
+ export interface PiniaCustomProperties {
+ readonly router: Router;
+ }
+}
diff --git a/ui/.quasar/quasar.d.ts b/ui/.quasar/quasar.d.ts
new file mode 100644
index 0000000..e37de88
--- /dev/null
+++ b/ui/.quasar/quasar.d.ts
@@ -0,0 +1,4 @@
+/* eslint-disable */
+///
+
+///
diff --git a/ui/.quasar/tsconfig.json b/ui/.quasar/tsconfig.json
new file mode 100644
index 0000000..99f52b3
--- /dev/null
+++ b/ui/.quasar/tsconfig.json
@@ -0,0 +1,94 @@
+{
+ "compilerOptions": {
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "target": "esnext",
+ "allowJs": true,
+ "resolveJsonModule": true,
+ "moduleDetection": "force",
+ "isolatedModules": true,
+ "module": "preserve",
+ "noEmit": true,
+ "lib": [
+ "esnext",
+ "dom",
+ "dom.iterable"
+ ],
+ "paths": {
+ "src": [
+ "./../src"
+ ],
+ "src/*": [
+ "./../src/*"
+ ],
+ "app": [
+ "./.."
+ ],
+ "app/*": [
+ "./../*"
+ ],
+ "components": [
+ "./../src/components"
+ ],
+ "components/*": [
+ "./../src/components/*"
+ ],
+ "layouts": [
+ "./../src/layouts"
+ ],
+ "layouts/*": [
+ "./../src/layouts/*"
+ ],
+ "pages": [
+ "./../src/pages"
+ ],
+ "pages/*": [
+ "./../src/pages/*"
+ ],
+ "assets": [
+ "./../src/assets"
+ ],
+ "assets/*": [
+ "./../src/assets/*"
+ ],
+ "boot": [
+ "./../src/boot"
+ ],
+ "boot/*": [
+ "./../src/boot/*"
+ ],
+ "stores": [
+ "./../src/stores"
+ ],
+ "stores/*": [
+ "./../src/stores/*"
+ ],
+ "#q-app": [
+ "./../node_modules/@quasar/app-webpack/types/index.d.ts"
+ ],
+ "#q-app/wrappers": [
+ "./../node_modules/@quasar/app-webpack/types/app-wrappers.d.ts"
+ ],
+ "#q-app/bex/background": [
+ "./../node_modules/@quasar/app-webpack/types/bex/entrypoints/background.d.ts"
+ ],
+ "#q-app/bex/content": [
+ "./../node_modules/@quasar/app-webpack/types/bex/entrypoints/content.d.ts"
+ ],
+ "#q-app/bex/private/bex-bridge": [
+ "./../node_modules/@quasar/app-webpack/types/bex/bex-bridge.d.ts"
+ ]
+ }
+ },
+ "include": [
+ "./**/*.d.ts",
+ "./../**/*"
+ ],
+ "exclude": [
+ "./../dist",
+ "./../node_modules",
+ "./../src-capacitor",
+ "./../src-cordova",
+ "./../quasar.config.*.temporary.compiled*"
+ ]
+}
\ No newline at end of file
diff --git a/ui/.vscode/extensions.json b/ui/.vscode/extensions.json
new file mode 100644
index 0000000..f7f50b4
--- /dev/null
+++ b/ui/.vscode/extensions.json
@@ -0,0 +1,13 @@
+{
+ "recommendations": [
+ "editorconfig.editorconfig",
+ "vue.volar",
+ "wayou.vscode-todo-highlight"
+ ],
+ "unwantedRecommendations": [
+ "octref.vetur",
+ "hookyqr.beautify",
+ "dbaeumer.jshint",
+ "ms-vscode.vscode-typescript-tslint-plugin"
+ ]
+}
\ No newline at end of file
diff --git a/ui/.vscode/settings.json b/ui/.vscode/settings.json
new file mode 100644
index 0000000..fdb070a
--- /dev/null
+++ b/ui/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "editor.bracketPairColorization.enabled": true,
+ "editor.guides.bracketPairs": true
+}
\ No newline at end of file
diff --git a/ui/README.md b/ui/README.md
new file mode 100644
index 0000000..ebe8f5b
--- /dev/null
+++ b/ui/README.md
@@ -0,0 +1,24 @@
+# Quasar App (baggisowtfaresystem)
+
+A Quasar Project
+
+## Install the dependencies
+```bash
+yarn
+# or
+npm install
+```
+
+### Start the app in development mode (hot-code reloading, error reporting, etc.)
+```bash
+quasar dev
+```
+
+
+### Build the app for production
+```bash
+quasar build
+```
+
+### Customize the configuration
+See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js).
diff --git a/ui/babel.config.js b/ui/babel.config.js
new file mode 100644
index 0000000..d907762
--- /dev/null
+++ b/ui/babel.config.js
@@ -0,0 +1,12 @@
+export default api => {
+ return {
+ presets: [
+ [
+ '@quasar/babel-preset-app',
+ api.caller(caller => caller && caller.target === 'node')
+ ? { targets: { node: 'current' } }
+ : {}
+ ]
+ ]
+ }
+}
diff --git a/ui/index.html b/ui/index.html
new file mode 100644
index 0000000..3c8c78f
--- /dev/null
+++ b/ui/index.html
@@ -0,0 +1,21 @@
+
+
+
+ <%= productName %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/jsconfig.json b/ui/jsconfig.json
new file mode 100644
index 0000000..f154205
--- /dev/null
+++ b/ui/jsconfig.json
@@ -0,0 +1,3 @@
+{
+ "extends": "./.quasar/tsconfig.json"
+}
\ No newline at end of file
diff --git a/ui/package-lock.json b/ui/package-lock.json
new file mode 100644
index 0000000..beb6054
--- /dev/null
+++ b/ui/package-lock.json
@@ -0,0 +1,12102 @@
+{
+ "name": "baggisowtfaresystem",
+ "version": "0.0.1",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "baggisowtfaresystem",
+ "version": "0.0.1",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@quasar/extras": "^1.16.4",
+ "axios": "^1.12.2",
+ "core-js": "^3.31.1",
+ "dayjs": "^1.11.18",
+ "pinia": "^3.0.1",
+ "quasar": "^2.16.0",
+ "vue": "3.5.20",
+ "vue-router": "^4.0.12"
+ },
+ "devDependencies": {
+ "@quasar/app-webpack": "^4.1.0",
+ "autoprefixer": "^10.4.2"
+ },
+ "engines": {
+ "node": "^28 || ^26 || ^24 || ^22 || ^20 || ^18",
+ "npm": ">= 6.13.4",
+ "yarn": ">= 1.21.1"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz",
+ "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz",
+ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.3",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.28.3",
+ "@babel/helpers": "^7.28.4",
+ "@babel/parser": "^7.28.4",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.4",
+ "@babel/types": "^7.28.4",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/core/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz",
+ "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.3",
+ "@babel/types": "^7.28.2",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-annotate-as-pure": {
+ "version": "7.27.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
+ "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.27.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/helper-create-class-features-plugin": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz",
+ "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "@babel/helper-member-expression-to-functions": "^7.27.1",
+ "@babel/helper-optimise-call-expression": "^7.27.1",
+ "@babel/helper-replace-supers": "^7.27.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
+ "@babel/traverse": "^7.28.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/helper-create-regexp-features-plugin": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz",
+ "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.1",
+ "regexpu-core": "^6.2.0",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/helper-define-polyfill-provider": {
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz",
+ "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "debug": "^4.4.1",
+ "lodash.debounce": "^4.0.8",
+ "resolve": "^1.22.10"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-member-expression-to-functions": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz",
+ "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.28.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-optimise-call-expression": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz",
+ "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-remap-async-to-generator": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz",
+ "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.1",
+ "@babel/helper-wrap-function": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-replace-supers": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz",
+ "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-member-expression-to-functions": "^7.27.1",
+ "@babel/helper-optimise-call-expression": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz",
+ "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-wrap-function": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz",
+ "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.3",
+ "@babel/types": "^7.28.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
+ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz",
+ "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.4"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz",
+ "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz",
+ "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz",
+ "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz",
+ "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
+ "@babel/plugin-transform-optional-chaining": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.13.0"
+ }
+ },
+ "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz",
+ "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/traverse": "^7.28.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-proposal-decorators": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.28.0.tgz",
+ "integrity": "sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/plugin-syntax-decorators": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-proposal-function-sent": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-function-sent/-/plugin-proposal-function-sent-7.27.1.tgz",
+ "integrity": "sha512-xA8Bqt8p12TxOFy3os20LxmOoHjyhzRC3zBql57d2W/YarNHgxHB4IlLHf3nXb7N6vSZ6kAdOoK2z5h0evGMhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-wrap-function": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-proposal-private-property-in-object": {
+ "version": "7.21.0-placeholder-for-preset-env.2",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
+ "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-proposal-throw-expressions": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.27.1.tgz",
+ "integrity": "sha512-pnGZCFdGiN3vHk54wWIvLJV3MXviRjCkSWhPtCkra6AW3AP3AcrRByT5jOnuk6nwu9VYT/B7ujdaLiBnkmc0hw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-decorators": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz",
+ "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-dynamic-import": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz",
+ "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-assertions": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz",
+ "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz",
+ "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-unicode-sets-regex": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz",
+ "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.18.6",
+ "@babel/helper-plugin-utils": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-arrow-functions": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz",
+ "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-async-generator-functions": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz",
+ "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-remap-async-to-generator": "^7.27.1",
+ "@babel/traverse": "^7.28.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-async-to-generator": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz",
+ "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-remap-async-to-generator": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-block-scoped-functions": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz",
+ "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-block-scoping": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.4.tgz",
+ "integrity": "sha512-1yxmvN0MJHOhPVmAsmoW5liWwoILobu/d/ShymZmj867bAdxGbehIrew1DuLpw2Ukv+qDSSPQdYW1dLNE7t11A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-class-properties": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz",
+ "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-class-static-block": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz",
+ "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.28.3",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.12.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-classes": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz",
+ "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-replace-supers": "^7.27.1",
+ "@babel/traverse": "^7.28.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-computed-properties": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz",
+ "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/template": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-destructuring": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz",
+ "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/traverse": "^7.28.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-dotall-regex": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz",
+ "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-duplicate-keys": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz",
+ "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz",
+ "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-dynamic-import": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz",
+ "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-explicit-resource-management": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz",
+ "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/plugin-transform-destructuring": "^7.28.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-exponentiation-operator": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz",
+ "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-export-namespace-from": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz",
+ "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-for-of": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz",
+ "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-function-name": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz",
+ "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-json-strings": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz",
+ "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-literals": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz",
+ "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-logical-assignment-operators": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz",
+ "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-member-expression-literals": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz",
+ "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-amd": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz",
+ "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-commonjs": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz",
+ "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-systemjs": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz",
+ "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-umd": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz",
+ "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-transforms": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-named-capturing-groups-regex": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz",
+ "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-new-target": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz",
+ "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz",
+ "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-numeric-separator": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz",
+ "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-object-rest-spread": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz",
+ "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/plugin-transform-destructuring": "^7.28.0",
+ "@babel/plugin-transform-parameters": "^7.27.7",
+ "@babel/traverse": "^7.28.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-object-super": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz",
+ "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-replace-supers": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-optional-catch-binding": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz",
+ "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-optional-chaining": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz",
+ "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-parameters": {
+ "version": "7.27.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz",
+ "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-private-methods": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz",
+ "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-private-property-in-object": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz",
+ "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^7.27.1",
+ "@babel/helper-create-class-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-property-literals": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz",
+ "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-regenerator": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz",
+ "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-regexp-modifiers": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz",
+ "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-reserved-words": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz",
+ "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-runtime": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz",
+ "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "babel-plugin-polyfill-corejs2": "^0.4.14",
+ "babel-plugin-polyfill-corejs3": "^0.13.0",
+ "babel-plugin-polyfill-regenerator": "^0.6.5",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-runtime/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/plugin-transform-shorthand-properties": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz",
+ "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-spread": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz",
+ "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-sticky-regex": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz",
+ "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-template-literals": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz",
+ "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-typeof-symbol": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz",
+ "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-unicode-escapes": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz",
+ "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-unicode-property-regex": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz",
+ "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-unicode-regex": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz",
+ "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-unicode-sets-regex": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz",
+ "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-regexp-features-plugin": "^7.27.1",
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/preset-env": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz",
+ "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.0",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/helper-validator-option": "^7.27.1",
+ "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1",
+ "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1",
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1",
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1",
+ "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3",
+ "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2",
+ "@babel/plugin-syntax-import-assertions": "^7.27.1",
+ "@babel/plugin-syntax-import-attributes": "^7.27.1",
+ "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
+ "@babel/plugin-transform-arrow-functions": "^7.27.1",
+ "@babel/plugin-transform-async-generator-functions": "^7.28.0",
+ "@babel/plugin-transform-async-to-generator": "^7.27.1",
+ "@babel/plugin-transform-block-scoped-functions": "^7.27.1",
+ "@babel/plugin-transform-block-scoping": "^7.28.0",
+ "@babel/plugin-transform-class-properties": "^7.27.1",
+ "@babel/plugin-transform-class-static-block": "^7.28.3",
+ "@babel/plugin-transform-classes": "^7.28.3",
+ "@babel/plugin-transform-computed-properties": "^7.27.1",
+ "@babel/plugin-transform-destructuring": "^7.28.0",
+ "@babel/plugin-transform-dotall-regex": "^7.27.1",
+ "@babel/plugin-transform-duplicate-keys": "^7.27.1",
+ "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1",
+ "@babel/plugin-transform-dynamic-import": "^7.27.1",
+ "@babel/plugin-transform-explicit-resource-management": "^7.28.0",
+ "@babel/plugin-transform-exponentiation-operator": "^7.27.1",
+ "@babel/plugin-transform-export-namespace-from": "^7.27.1",
+ "@babel/plugin-transform-for-of": "^7.27.1",
+ "@babel/plugin-transform-function-name": "^7.27.1",
+ "@babel/plugin-transform-json-strings": "^7.27.1",
+ "@babel/plugin-transform-literals": "^7.27.1",
+ "@babel/plugin-transform-logical-assignment-operators": "^7.27.1",
+ "@babel/plugin-transform-member-expression-literals": "^7.27.1",
+ "@babel/plugin-transform-modules-amd": "^7.27.1",
+ "@babel/plugin-transform-modules-commonjs": "^7.27.1",
+ "@babel/plugin-transform-modules-systemjs": "^7.27.1",
+ "@babel/plugin-transform-modules-umd": "^7.27.1",
+ "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1",
+ "@babel/plugin-transform-new-target": "^7.27.1",
+ "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1",
+ "@babel/plugin-transform-numeric-separator": "^7.27.1",
+ "@babel/plugin-transform-object-rest-spread": "^7.28.0",
+ "@babel/plugin-transform-object-super": "^7.27.1",
+ "@babel/plugin-transform-optional-catch-binding": "^7.27.1",
+ "@babel/plugin-transform-optional-chaining": "^7.27.1",
+ "@babel/plugin-transform-parameters": "^7.27.7",
+ "@babel/plugin-transform-private-methods": "^7.27.1",
+ "@babel/plugin-transform-private-property-in-object": "^7.27.1",
+ "@babel/plugin-transform-property-literals": "^7.27.1",
+ "@babel/plugin-transform-regenerator": "^7.28.3",
+ "@babel/plugin-transform-regexp-modifiers": "^7.27.1",
+ "@babel/plugin-transform-reserved-words": "^7.27.1",
+ "@babel/plugin-transform-shorthand-properties": "^7.27.1",
+ "@babel/plugin-transform-spread": "^7.27.1",
+ "@babel/plugin-transform-sticky-regex": "^7.27.1",
+ "@babel/plugin-transform-template-literals": "^7.27.1",
+ "@babel/plugin-transform-typeof-symbol": "^7.27.1",
+ "@babel/plugin-transform-unicode-escapes": "^7.27.1",
+ "@babel/plugin-transform-unicode-property-regex": "^7.27.1",
+ "@babel/plugin-transform-unicode-regex": "^7.27.1",
+ "@babel/plugin-transform-unicode-sets-regex": "^7.27.1",
+ "@babel/preset-modules": "0.1.6-no-external-plugins",
+ "babel-plugin-polyfill-corejs2": "^0.4.14",
+ "babel-plugin-polyfill-corejs3": "^0.13.0",
+ "babel-plugin-polyfill-regenerator": "^0.6.5",
+ "core-js-compat": "^3.43.0",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/preset-env/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/preset-modules": {
+ "version": "0.1.6-no-external-plugins",
+ "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz",
+ "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@babel/types": "^7.4.4",
+ "esutils": "^2.0.2"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz",
+ "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.3",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.4",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.4",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz",
+ "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bufbuild/protobuf": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.9.0.tgz",
+ "integrity": "sha512-rnJenoStJ8nvmt9Gzye8nkYd6V22xUAnu4086ER7h1zJ508vStko4pMvDeQ446ilDTFpV5wnoc5YS7XvMwwMqA==",
+ "dev": true,
+ "license": "(Apache-2.0 AND BSD-3-Clause)"
+ },
+ "node_modules/@discoveryjs/json-ext": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
+ "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz",
+ "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@inquirer/checkbox": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-3.0.1.tgz",
+ "integrity": "sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^9.2.1",
+ "@inquirer/figures": "^1.0.6",
+ "@inquirer/type": "^2.0.0",
+ "ansi-escapes": "^4.3.2",
+ "yoctocolors-cjs": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@inquirer/confirm": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-4.0.1.tgz",
+ "integrity": "sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^9.2.1",
+ "@inquirer/type": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@inquirer/core": {
+ "version": "9.2.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz",
+ "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/figures": "^1.0.6",
+ "@inquirer/type": "^2.0.0",
+ "@types/mute-stream": "^0.0.4",
+ "@types/node": "^22.5.5",
+ "@types/wrap-ansi": "^3.0.0",
+ "ansi-escapes": "^4.3.2",
+ "cli-width": "^4.1.0",
+ "mute-stream": "^1.0.0",
+ "signal-exit": "^4.1.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^6.2.0",
+ "yoctocolors-cjs": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@inquirer/core/node_modules/@types/node": {
+ "version": "22.18.8",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.8.tgz",
+ "integrity": "sha512-pAZSHMiagDR7cARo/cch1f3rXy0AEXwsVsVH09FcyeJVAzCnGgmYis7P3JidtTUjyadhTeSo8TgRPswstghDaw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@inquirer/core/node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@inquirer/editor": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-3.0.1.tgz",
+ "integrity": "sha512-VA96GPFaSOVudjKFraokEEmUQg/Lub6OXvbIEZU1SDCmBzRkHGhxoFAVaF30nyiB4m5cEbDgiI2QRacXZ2hw9Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^9.2.1",
+ "@inquirer/type": "^2.0.0",
+ "external-editor": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@inquirer/expand": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-3.0.1.tgz",
+ "integrity": "sha512-ToG8d6RIbnVpbdPdiN7BCxZGiHOTomOX94C2FaT5KOHupV40tKEDozp12res6cMIfRKrXLJyexAZhWVHgbALSQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^9.2.1",
+ "@inquirer/type": "^2.0.0",
+ "yoctocolors-cjs": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@inquirer/figures": {
+ "version": "1.0.13",
+ "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz",
+ "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@inquirer/input": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-3.0.1.tgz",
+ "integrity": "sha512-BDuPBmpvi8eMCxqC5iacloWqv+5tQSJlUafYWUe31ow1BVXjW2a5qe3dh4X/Z25Wp22RwvcaLCc2siHobEOfzg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^9.2.1",
+ "@inquirer/type": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@inquirer/number": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-2.0.1.tgz",
+ "integrity": "sha512-QpR8jPhRjSmlr/mD2cw3IR8HRO7lSVOnqUvQa8scv1Lsr3xoAMMworcYW3J13z3ppjBFBD2ef1Ci6AE5Qn8goQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^9.2.1",
+ "@inquirer/type": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@inquirer/password": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-3.0.1.tgz",
+ "integrity": "sha512-haoeEPUisD1NeE2IanLOiFr4wcTXGWrBOyAyPZi1FfLJuXOzNmxCJPgUrGYKVh+Y8hfGJenIfz5Wb/DkE9KkMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^9.2.1",
+ "@inquirer/type": "^2.0.0",
+ "ansi-escapes": "^4.3.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@inquirer/prompts": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-6.0.1.tgz",
+ "integrity": "sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/checkbox": "^3.0.1",
+ "@inquirer/confirm": "^4.0.1",
+ "@inquirer/editor": "^3.0.1",
+ "@inquirer/expand": "^3.0.1",
+ "@inquirer/input": "^3.0.1",
+ "@inquirer/number": "^2.0.1",
+ "@inquirer/password": "^3.0.1",
+ "@inquirer/rawlist": "^3.0.1",
+ "@inquirer/search": "^2.0.1",
+ "@inquirer/select": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@inquirer/rawlist": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-3.0.1.tgz",
+ "integrity": "sha512-VgRtFIwZInUzTiPLSfDXK5jLrnpkuSOh1ctfaoygKAdPqjcjKYmGh6sCY1pb0aGnCGsmhUxoqLDUAU0ud+lGXQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^9.2.1",
+ "@inquirer/type": "^2.0.0",
+ "yoctocolors-cjs": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@inquirer/search": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-2.0.1.tgz",
+ "integrity": "sha512-r5hBKZk3g5MkIzLVoSgE4evypGqtOannnB3PKTG9NRZxyFRKcfzrdxXXPcoJQsxJPzvdSU2Rn7pB7lw0GCmGAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^9.2.1",
+ "@inquirer/figures": "^1.0.6",
+ "@inquirer/type": "^2.0.0",
+ "yoctocolors-cjs": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@inquirer/select": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-3.0.1.tgz",
+ "integrity": "sha512-lUDGUxPhdWMkN/fHy1Lk7pF3nK1fh/gqeyWXmctefhxLYxlDsc7vsPBEpxrfVGDsVdyYJsiJoD4bJ1b623cV1Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^9.2.1",
+ "@inquirer/figures": "^1.0.6",
+ "@inquirer/type": "^2.0.0",
+ "ansi-escapes": "^4.3.2",
+ "yoctocolors-cjs": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@inquirer/type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz",
+ "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mute-stream": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/@jest/schemas": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+ "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.27.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/types": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+ "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "@types/istanbul-reports": "^3.0.0",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.8",
+ "chalk": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/source-map": {
+ "version": "0.3.11",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
+ "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.25"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@jsonjoy.com/base64": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz",
+ "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/@jsonjoy.com/buffers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.0.0.tgz",
+ "integrity": "sha512-NDigYR3PHqCnQLXYyoLbnEdzMMvzeiCWo1KOut7Q0CoIqg9tUAPKJ1iq/2nFhc5kZtexzutNY0LFjdwWL3Dw3Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/@jsonjoy.com/codegen": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz",
+ "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/@jsonjoy.com/json-pack": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.15.0.tgz",
+ "integrity": "sha512-7jK0nAXj7g2hiwJ7b3wx569ZohkTFYcgDP18OvaYQ+Bg+D7rzrwaYxkdM6snrxIoKCisbudao8kfJZ4NCLiHjw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jsonjoy.com/base64": "^1.1.2",
+ "@jsonjoy.com/buffers": "^1.0.0",
+ "@jsonjoy.com/codegen": "^1.0.0",
+ "@jsonjoy.com/json-pointer": "^1.0.1",
+ "@jsonjoy.com/util": "^1.9.0",
+ "hyperdyperid": "^1.2.0",
+ "thingies": "^2.5.0"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/@jsonjoy.com/json-pointer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz",
+ "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jsonjoy.com/codegen": "^1.0.0",
+ "@jsonjoy.com/util": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/@jsonjoy.com/util": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz",
+ "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jsonjoy.com/buffers": "^1.0.0",
+ "@jsonjoy.com/codegen": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/@leichtgewicht/ip-codec": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
+ "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@parcel/watcher": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
+ "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "detect-libc": "^1.0.3",
+ "is-glob": "^4.0.3",
+ "micromatch": "^4.0.5",
+ "node-addon-api": "^7.0.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "@parcel/watcher-android-arm64": "2.5.1",
+ "@parcel/watcher-darwin-arm64": "2.5.1",
+ "@parcel/watcher-darwin-x64": "2.5.1",
+ "@parcel/watcher-freebsd-x64": "2.5.1",
+ "@parcel/watcher-linux-arm-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm-musl": "2.5.1",
+ "@parcel/watcher-linux-arm64-glibc": "2.5.1",
+ "@parcel/watcher-linux-arm64-musl": "2.5.1",
+ "@parcel/watcher-linux-x64-glibc": "2.5.1",
+ "@parcel/watcher-linux-x64-musl": "2.5.1",
+ "@parcel/watcher-win32-arm64": "2.5.1",
+ "@parcel/watcher-win32-ia32": "2.5.1",
+ "@parcel/watcher-win32-x64": "2.5.1"
+ }
+ },
+ "node_modules/@parcel/watcher-win32-x64": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
+ "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true,
+ "engines": {
+ "node": ">= 10.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@polka/url": {
+ "version": "1.0.0-next.29",
+ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
+ "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@quasar/app-webpack": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@quasar/app-webpack/-/app-webpack-4.3.1.tgz",
+ "integrity": "sha512-u1fgzDcGkMwf82mLNO2P+4uBE0FM0nR/f8Xrpsim7jMTakhQv5mmMzGcZjnGkxZRQbYx9M4FDT3paSfCPJoxrw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@quasar/babel-preset-app": "^2.0.3",
+ "@quasar/render-ssr-error": "^1.0.3",
+ "@quasar/ssl-certificate": "^1.0.0",
+ "@quasar/ssr-helpers": "3.0.2",
+ "@rollup/pluginutils": "^5.1.0",
+ "@types/chrome": "^0.0.271",
+ "@types/compression": "^1.7.5",
+ "@types/cordova": "^11.0.3",
+ "@types/express": "^4.17.21",
+ "@types/webpack-bundle-analyzer": "^4.7.0",
+ "archiver": "^7.0.1",
+ "browserslist": "^4.23.3",
+ "chokidar": "^4.0.0",
+ "ci-info": "^4.0.0",
+ "compression-webpack-plugin": "^11.1.0",
+ "confbox": "^0.1.8",
+ "copy-webpack-plugin": "^12.0.2",
+ "cross-spawn": "^7.0.6",
+ "css-loader": "^7.1.2",
+ "css-minimizer-webpack-plugin": "^7.0.0",
+ "cssnano": "^7.0.6",
+ "dot-prop": "^9.0.0",
+ "dotenv": "^16.4.5",
+ "dotenv-expand": "^11.0.6",
+ "elementtree": "0.1.7",
+ "error-stack-parser": "^2.1.4",
+ "esbuild": "^0.25.0",
+ "express": "^4.21.2",
+ "file-loader": "^6.2.0",
+ "fs-extra": "^11.2.0",
+ "hash-sum": "^2.0.0",
+ "html-minifier-terser": "^7.2.0",
+ "html-webpack-plugin": "^5.6.0",
+ "inquirer": "^11.0.2",
+ "isbinaryfile": "^5.0.2",
+ "kolorist": "^1.8.0",
+ "launch-editor-middleware": "^2.9.1",
+ "lodash": "^4.17.21",
+ "log-update": "^6.1.0",
+ "mini-css-extract-plugin": "^2.9.1",
+ "minimist": "^1.2.8",
+ "node-loader": "^2.0.0",
+ "null-loader": "^4.0.1",
+ "open": "^10.1.0",
+ "postcss": "^8.4.47",
+ "postcss-loader": "^8.1.1",
+ "postcss-rtlcss": "^5.4.0",
+ "rollup-plugin-visualizer": "^5.12.0",
+ "sass-embedded": "^1.80.2",
+ "sass-loader": "^16.0.1",
+ "semver": "^7.6.3",
+ "serialize-javascript": "^6.0.2",
+ "terser-webpack-plugin": "^5.3.10",
+ "tinyglobby": "^0.2.10",
+ "ts-essentials": "^10.0.2",
+ "url-loader": "^4.1.1",
+ "vue-loader": "^17.4.2",
+ "vue-style-loader": "^4.1.3",
+ "webpack": "^5.94.0",
+ "webpack-5-chain": "8.0.2",
+ "webpack-bundle-analyzer": "^4.10.2",
+ "webpack-dev-middleware": "^7.4.2",
+ "webpack-dev-server": "^5.1.0",
+ "webpack-hot-middleware": "^2.26.1",
+ "webpack-merge": "^6.0.1",
+ "webpack-node-externals": "^3.0.0"
+ },
+ "bin": {
+ "quasar": "bin/quasar"
+ },
+ "engines": {
+ "node": "^28 || ^26 || ^24 || ^22 || ^20 || ^18 || ^16",
+ "npm": ">= 6.14.12",
+ "yarn": ">= 1.17.3"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://donate.quasar.dev"
+ },
+ "peerDependencies": {
+ "@electron/packager": ">= 18",
+ "electron-builder": ">= 22",
+ "eslint": "^8.57.1 || ^9.0.0",
+ "pinia": "^2.0.0 || ^3.0.0",
+ "quasar": "^2.16.0",
+ "typescript": ">= 5.4",
+ "vue": "^3.2.29",
+ "vue-router": "^4.0.12",
+ "workbox-webpack-plugin": ">= 6"
+ },
+ "peerDependenciesMeta": {
+ "@electron/packager": {
+ "optional": true
+ },
+ "electron-builder": {
+ "optional": true
+ },
+ "eslint": {
+ "optional": true
+ },
+ "pinia": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ },
+ "workbox-build": {
+ "optional": true
+ },
+ "workbox-webpack-plugin": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@quasar/babel-preset-app": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@quasar/babel-preset-app/-/babel-preset-app-2.0.3.tgz",
+ "integrity": "sha512-PYvVXU/TBwF1JU+nEKw8VTsbNi4mdhu7l+l9HIqfY0XZGWbDQLOGjBR8TO6A8dn5SUoilvRh85TG3ZQV01VCBQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.12.0",
+ "@babel/helper-compilation-targets": "^7.9.6",
+ "@babel/helper-module-imports": "^7.8.3",
+ "@babel/plugin-proposal-decorators": "^7.4.4",
+ "@babel/plugin-proposal-function-sent": "^7.2.0",
+ "@babel/plugin-proposal-throw-expressions": "^7.2.0",
+ "@babel/plugin-syntax-dynamic-import": "^7.2.0",
+ "@babel/plugin-syntax-import-meta": "^7.2.0",
+ "@babel/plugin-transform-class-properties": "^7.23.3",
+ "@babel/plugin-transform-export-namespace-from": "^7.23.4",
+ "@babel/plugin-transform-json-strings": "^7.23.4",
+ "@babel/plugin-transform-numeric-separator": "^7.23.4",
+ "@babel/plugin-transform-runtime": "^7.9.0",
+ "@babel/preset-env": "^7.9.0",
+ "@babel/runtime": "^7.9.0",
+ "babel-loader": "^9.1.2",
+ "babel-plugin-dynamic-import-node": "^2.3.0",
+ "babel-plugin-module-resolver": "^5.0.0",
+ "core-js": "^3.6.5",
+ "core-js-compat": "^3.6.5"
+ },
+ "engines": {
+ "node": ">= 10.0.0",
+ "npm": ">= 5.6.0",
+ "yarn": ">= 1.6.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://donate.quasar.dev"
+ }
+ },
+ "node_modules/@quasar/extras": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-1.17.0.tgz",
+ "integrity": "sha512-KqAHdSJfIDauiR1nJ8rqHWT0diqD0QradZKoVIZJAilHAvgwyPIY7MbyR2z4RIMkUIMUSqBZcbshMpEw+9A30w==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://donate.quasar.dev"
+ }
+ },
+ "node_modules/@quasar/render-ssr-error": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@quasar/render-ssr-error/-/render-ssr-error-1.0.3.tgz",
+ "integrity": "sha512-A8RF99q6/sOSe1Ighnh5syEIbliD3qUYEJd2HyfFyBPSMF+WYGXon5dmzg4nUoK662NgOggInevkDyBDJcZugg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "stack-trace": "^1.0.0-pre2"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://donate.quasar.dev"
+ }
+ },
+ "node_modules/@quasar/ssl-certificate": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@quasar/ssl-certificate/-/ssl-certificate-1.0.0.tgz",
+ "integrity": "sha512-RhZF7rO76T7Ywer1/5lCe7xl3CIiXxSAH1xgwOj0wcHTityDxJqIN/5YIj6BxMvlFw8XkJDoB1udEQafoVFA4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fs-extra": "^11.1.1",
+ "selfsigned": "^2.1.1"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://donate.quasar.dev"
+ }
+ },
+ "node_modules/@quasar/ssr-helpers": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@quasar/ssr-helpers/-/ssr-helpers-3.0.2.tgz",
+ "integrity": "sha512-JhHDk86o1ucR6toNkpIaAsgA0KuKKYDrOKJHN0O7IEvI2bQOCnmL9ttgRI/A4EeWOe4davqNFfwcwjuk0Ecv/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "serialize-javascript": "^6.0.0",
+ "source-map": "^0.7.3"
+ },
+ "engines": {
+ "node": ">= 12.22.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://donate.quasar.dev"
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz",
+ "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@sinclair/typebox": {
+ "version": "0.27.8",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sindresorhus/merge-streams": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz",
+ "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@types/body-parser": {
+ "version": "1.19.6",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
+ "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/bonjour": {
+ "version": "3.5.13",
+ "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz",
+ "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/chrome": {
+ "version": "0.0.271",
+ "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.271.tgz",
+ "integrity": "sha512-K0qgXvkwA5ic+/eygF1xiypHEvCoBgH5lwrhg3yva2mqJuCWyYm0vpZQ22GksAxgGfo0PWev9Zx3plp2clMlwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/filesystem": "*",
+ "@types/har-format": "*"
+ }
+ },
+ "node_modules/@types/compression": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz",
+ "integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/connect": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/connect-history-api-fallback": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz",
+ "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/express-serve-static-core": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/cordova": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-11.0.3.tgz",
+ "integrity": "sha512-kyuRQ40/NWQVhqGIHq78Ehu2Bf9Mlg0LhmSmis6ZFJK7z933FRfYi8tHe/k/0fB+PGfCf95rJC6TO7dopaFvAg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/eslint": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
+ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "*",
+ "@types/json-schema": "*"
+ }
+ },
+ "node_modules/@types/eslint-scope": {
+ "version": "3.7.7",
+ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
+ "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/eslint": "*",
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/express": {
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz",
+ "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^4.17.33",
+ "@types/qs": "*",
+ "@types/serve-static": "*"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "4.19.7",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz",
+ "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/filesystem": {
+ "version": "0.0.36",
+ "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz",
+ "integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/filewriter": "*"
+ }
+ },
+ "node_modules/@types/filewriter": {
+ "version": "0.0.33",
+ "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz",
+ "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/har-format": {
+ "version": "1.2.16",
+ "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz",
+ "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/html-minifier-terser": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
+ "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/http-errors": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
+ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/http-proxy": {
+ "version": "1.17.16",
+ "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz",
+ "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/istanbul-lib-coverage": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/istanbul-lib-report": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+ "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "node_modules/@types/istanbul-reports": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+ "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/mime": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
+ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/mute-stream": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz",
+ "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "24.7.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz",
+ "integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.14.0"
+ }
+ },
+ "node_modules/@types/node-forge": {
+ "version": "1.3.14",
+ "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz",
+ "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/range-parser": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/retry": {
+ "version": "0.12.2",
+ "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz",
+ "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/send": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.0.tgz",
+ "integrity": "sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-index": {
+ "version": "1.9.4",
+ "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz",
+ "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/express": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.9.tgz",
+ "integrity": "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*",
+ "@types/send": "<1"
+ }
+ },
+ "node_modules/@types/serve-static/node_modules/@types/send": {
+ "version": "0.17.5",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz",
+ "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/mime": "^1",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/sockjs": {
+ "version": "0.3.36",
+ "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz",
+ "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/webpack-bundle-analyzer": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@types/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz",
+ "integrity": "sha512-c5i2ThslSNSG8W891BRvOd/RoCjI2zwph8maD22b1adtSns20j+0azDDMCK06DiVrzTgnwiDl5Ntmu1YRJw8Sg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "tapable": "^2.2.0",
+ "webpack": "^5"
+ }
+ },
+ "node_modules/@types/wrap-ansi": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz",
+ "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/ws": {
+ "version": "8.18.1",
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/yargs": {
+ "version": "17.0.33",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
+ "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.3",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.20",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.20.tgz",
+ "integrity": "sha512-8TWXUyiqFd3GmP4JTX9hbiTFRwYHgVL/vr3cqhr4YQ258+9FADwvj7golk2sWNGHR67QgmCZ8gz80nQcMokhwg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.3",
+ "@vue/shared": "3.5.20",
+ "entities": "^4.5.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.20",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.20.tgz",
+ "integrity": "sha512-whB44M59XKjqUEYOMPYU0ijUV0G+4fdrHVKDe32abNdX/kJe1NUEMqsi4cwzXa9kyM9w5S8WqFsrfo1ogtBZGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.20",
+ "@vue/shared": "3.5.20"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.20",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.20.tgz",
+ "integrity": "sha512-SFcxapQc0/feWiSBfkGsa1v4DOrnMAQSYuvDMpEaxbpH5dKbnEM5KobSNSgU+1MbHCl+9ftm7oQWxvwDB6iBfw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.3",
+ "@vue/compiler-core": "3.5.20",
+ "@vue/compiler-dom": "3.5.20",
+ "@vue/compiler-ssr": "3.5.20",
+ "@vue/shared": "3.5.20",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.17",
+ "postcss": "^8.5.6",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.20",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.20.tgz",
+ "integrity": "sha512-RSl5XAMc5YFUXpDQi+UQDdVjH9FnEpLDHIALg5J0ITHxkEzJ8uQLlo7CIbjPYqmZtt6w0TsIPbo1izYXwDG7JA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.20",
+ "@vue/shared": "3.5.20"
+ }
+ },
+ "node_modules/@vue/devtools-api": {
+ "version": "7.7.7",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz",
+ "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-kit": "^7.7.7"
+ }
+ },
+ "node_modules/@vue/devtools-kit": {
+ "version": "7.7.7",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz",
+ "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-shared": "^7.7.7",
+ "birpc": "^2.3.0",
+ "hookable": "^5.5.3",
+ "mitt": "^3.0.1",
+ "perfect-debounce": "^1.0.0",
+ "speakingurl": "^14.0.1",
+ "superjson": "^2.2.2"
+ }
+ },
+ "node_modules/@vue/devtools-shared": {
+ "version": "7.7.7",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz",
+ "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==",
+ "license": "MIT",
+ "dependencies": {
+ "rfdc": "^1.4.1"
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.20",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.20.tgz",
+ "integrity": "sha512-hS8l8x4cl1fmZpSQX/NXlqWKARqEsNmfkwOIYqtR2F616NGfsLUm0G6FQBK6uDKUCVyi1YOL8Xmt/RkZcd/jYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/shared": "3.5.20"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.20",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.20.tgz",
+ "integrity": "sha512-vyQRiH5uSZlOa+4I/t4Qw/SsD/gbth0SW2J7oMeVlMFMAmsG1rwDD6ok0VMmjXY3eI0iHNSSOBilEDW98PLRKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.20",
+ "@vue/shared": "3.5.20"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.20",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.20.tgz",
+ "integrity": "sha512-KBHzPld/Djw3im0CQ7tGCpgRedryIn4CcAl047EhFTCCPT2xFf4e8j6WeKLgEEoqPSl9TYqShc3Q6tpWpz/Xgw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.20",
+ "@vue/runtime-core": "3.5.20",
+ "@vue/shared": "3.5.20",
+ "csstype": "^3.1.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.5.20",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.20.tgz",
+ "integrity": "sha512-HthAS0lZJDH21HFJBVNTtx+ULcIbJQRpjSVomVjfyPkFSpCwvsPTA+jIzOaUm3Hrqx36ozBHePztQFg6pj5aKg==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.5.20",
+ "@vue/shared": "3.5.20"
+ },
+ "peerDependencies": {
+ "vue": "3.5.20"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.20",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.20.tgz",
+ "integrity": "sha512-SoRGP596KU/ig6TfgkCMbXkr4YJ91n/QSdMuqeP5r3hVIYA3CPHUBCc7Skak0EAKV+5lL4KyIh61VA/pK1CIAA==",
+ "license": "MIT"
+ },
+ "node_modules/@webassemblyjs/ast": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
+ "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/helper-numbers": "1.13.2",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2"
+ }
+ },
+ "node_modules/@webassemblyjs/floating-point-hex-parser": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
+ "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@webassemblyjs/helper-api-error": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
+ "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@webassemblyjs/helper-buffer": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
+ "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@webassemblyjs/helper-numbers": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
+ "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/floating-point-hex-parser": "1.13.2",
+ "@webassemblyjs/helper-api-error": "1.13.2",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@webassemblyjs/helper-wasm-bytecode": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
+ "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@webassemblyjs/helper-wasm-section": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
+ "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-buffer": "1.14.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+ "@webassemblyjs/wasm-gen": "1.14.1"
+ }
+ },
+ "node_modules/@webassemblyjs/ieee754": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
+ "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@xtuc/ieee754": "^1.2.0"
+ }
+ },
+ "node_modules/@webassemblyjs/leb128": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
+ "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@webassemblyjs/utf8": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
+ "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@webassemblyjs/wasm-edit": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
+ "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-buffer": "1.14.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+ "@webassemblyjs/helper-wasm-section": "1.14.1",
+ "@webassemblyjs/wasm-gen": "1.14.1",
+ "@webassemblyjs/wasm-opt": "1.14.1",
+ "@webassemblyjs/wasm-parser": "1.14.1",
+ "@webassemblyjs/wast-printer": "1.14.1"
+ }
+ },
+ "node_modules/@webassemblyjs/wasm-gen": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
+ "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+ "@webassemblyjs/ieee754": "1.13.2",
+ "@webassemblyjs/leb128": "1.13.2",
+ "@webassemblyjs/utf8": "1.13.2"
+ }
+ },
+ "node_modules/@webassemblyjs/wasm-opt": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
+ "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-buffer": "1.14.1",
+ "@webassemblyjs/wasm-gen": "1.14.1",
+ "@webassemblyjs/wasm-parser": "1.14.1"
+ }
+ },
+ "node_modules/@webassemblyjs/wasm-parser": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
+ "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@webassemblyjs/helper-api-error": "1.13.2",
+ "@webassemblyjs/helper-wasm-bytecode": "1.13.2",
+ "@webassemblyjs/ieee754": "1.13.2",
+ "@webassemblyjs/leb128": "1.13.2",
+ "@webassemblyjs/utf8": "1.13.2"
+ }
+ },
+ "node_modules/@webassemblyjs/wast-printer": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
+ "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@webassemblyjs/ast": "1.14.1",
+ "@xtuc/long": "4.2.2"
+ }
+ },
+ "node_modules/@xtuc/ieee754": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
+ "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@xtuc/long": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
+ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/abort-controller": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+ "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-import-phases": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
+ "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "peerDependencies": {
+ "acorn": "^8.14.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
+ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
+ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ajv-keywords": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
+ "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3"
+ },
+ "peerDependencies": {
+ "ajv": "^8.8.2"
+ }
+ },
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-escapes/node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-html-community": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz",
+ "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==",
+ "dev": true,
+ "engines": [
+ "node >= 0.8.0"
+ ],
+ "license": "Apache-2.0",
+ "bin": {
+ "ansi-html": "bin/ansi-html"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/anymatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/archiver": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz",
+ "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "archiver-utils": "^5.0.2",
+ "async": "^3.2.4",
+ "buffer-crc32": "^1.0.0",
+ "readable-stream": "^4.0.0",
+ "readdir-glob": "^1.1.2",
+ "tar-stream": "^3.0.0",
+ "zip-stream": "^6.0.1"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/archiver-utils": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz",
+ "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "glob": "^10.0.0",
+ "graceful-fs": "^4.2.0",
+ "is-stream": "^2.0.1",
+ "lazystream": "^1.0.0",
+ "lodash": "^4.17.15",
+ "normalize-path": "^3.0.0",
+ "readable-stream": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/async": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
+ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.21",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
+ "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.24.4",
+ "caniuse-lite": "^1.0.30001702",
+ "fraction.js": "^4.3.7",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/axios": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
+ "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/b4a": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz",
+ "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react-native-b4a": "*"
+ },
+ "peerDependenciesMeta": {
+ "react-native-b4a": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/babel-loader": {
+ "version": "9.2.1",
+ "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz",
+ "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-cache-dir": "^4.0.0",
+ "schema-utils": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 14.15.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.12.0",
+ "webpack": ">=5"
+ }
+ },
+ "node_modules/babel-plugin-dynamic-import-node": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz",
+ "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "object.assign": "^4.1.0"
+ }
+ },
+ "node_modules/babel-plugin-module-resolver": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-5.0.2.tgz",
+ "integrity": "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-babel-config": "^2.1.1",
+ "glob": "^9.3.3",
+ "pkg-up": "^3.1.0",
+ "reselect": "^4.1.7",
+ "resolve": "^1.22.8"
+ }
+ },
+ "node_modules/babel-plugin-module-resolver/node_modules/glob": {
+ "version": "9.3.5",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz",
+ "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "minimatch": "^8.0.2",
+ "minipass": "^4.2.4",
+ "path-scurry": "^1.6.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/babel-plugin-module-resolver/node_modules/minimatch": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz",
+ "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/babel-plugin-module-resolver/node_modules/minipass": {
+ "version": "4.2.8",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz",
+ "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-plugin-polyfill-corejs2": {
+ "version": "0.4.14",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz",
+ "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.7",
+ "@babel/helper-define-polyfill-provider": "^0.6.5",
+ "semver": "^6.3.1"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/babel-plugin-polyfill-corejs3": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz",
+ "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-define-polyfill-provider": "^0.6.5",
+ "core-js-compat": "^3.43.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/babel-plugin-polyfill-regenerator": {
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz",
+ "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-define-polyfill-provider": "^0.6.5"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/bare-events": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.7.0.tgz",
+ "integrity": "sha512-b3N5eTW1g7vXkw+0CXh/HazGTcO5KYuu/RCNaJbDMPI6LHDi+7qe8EmxKUVe1sUbY2KZOVZFyj62x0OEz9qyAA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.8.12",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.12.tgz",
+ "integrity": "sha512-vAPMQdnyKCBtkmQA6FMCBvU9qFIppS3nzyXnEM+Lo2IAhG4Mpjv9cCxMudhgV3YdNNJv6TNqXy97dfRVL2LmaQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/batch": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
+ "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/big.js": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
+ "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/birpc": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.6.1.tgz",
+ "integrity": "sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.3",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+ "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.13.0",
+ "raw-body": "2.5.2",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/body-parser/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/body-parser/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/bonjour-service": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz",
+ "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "multicast-dns": "^7.2.5"
+ }
+ },
+ "node_modules/boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.26.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz",
+ "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.8.9",
+ "caniuse-lite": "^1.0.30001746",
+ "electron-to-chromium": "^1.5.227",
+ "node-releases": "^2.0.21",
+ "update-browserslist-db": "^1.1.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
+ "node_modules/buffer-builder": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz",
+ "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==",
+ "dev": true,
+ "license": "MIT/X11"
+ },
+ "node_modules/buffer-crc32": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz",
+ "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/bundle-name": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz",
+ "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "run-applescript": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
+ "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.0",
+ "es-define-property": "^1.0.0",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camel-case": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz",
+ "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pascal-case": "^3.1.2",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/caniuse-api": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
+ "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.0.0",
+ "caniuse-lite": "^1.0.0",
+ "lodash.memoize": "^4.1.2",
+ "lodash.uniq": "^4.5.0"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001748",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001748.tgz",
+ "integrity": "sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chalk/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/chardet": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/chrome-trace-event": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
+ "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/ci-info": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz",
+ "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/clean-css": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz",
+ "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "source-map": "~0.6.0"
+ },
+ "engines": {
+ "node": ">= 10.0"
+ }
+ },
+ "node_modules/clean-css/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/cli-cursor": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
+ "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-width": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
+ "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/cliui/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cliui/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/clone-deep": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
+ "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-plain-object": "^2.0.4",
+ "kind-of": "^6.0.2",
+ "shallow-clone": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/colord": {
+ "version": "2.9.3",
+ "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
+ "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/colorette": {
+ "version": "2.0.20",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
+ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/colorjs.io": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
+ "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+ "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/common-path-prefix": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz",
+ "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/compress-commons": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz",
+ "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "crc-32": "^1.2.0",
+ "crc32-stream": "^6.0.0",
+ "is-stream": "^2.0.1",
+ "normalize-path": "^3.0.0",
+ "readable-stream": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/compressible": {
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
+ "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": ">= 1.43.0 < 2"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/compression": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
+ "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "compressible": "~2.0.18",
+ "debug": "2.6.9",
+ "negotiator": "~0.6.4",
+ "on-headers": "~1.1.0",
+ "safe-buffer": "5.2.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/compression-webpack-plugin": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-11.1.0.tgz",
+ "integrity": "sha512-zDOQYp10+upzLxW+VRSjEpRRwBXJdsb5lBMlRxx1g8hckIFBpe3DTI0en2w7h+beuq89576RVzfiXrkdPGrHhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "schema-utils": "^4.2.0",
+ "serialize-javascript": "^6.0.2"
+ },
+ "engines": {
+ "node": ">= 18.12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.1.0"
+ }
+ },
+ "node_modules/compression/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/compression/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/compression/node_modules/negotiator": {
+ "version": "0.6.4",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
+ "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/confbox": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
+ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/connect-history-api-fallback": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz",
+ "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/copy-anything": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz",
+ "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==",
+ "license": "MIT",
+ "dependencies": {
+ "is-what": "^4.1.8"
+ },
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
+ "node_modules/copy-webpack-plugin": {
+ "version": "12.0.2",
+ "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz",
+ "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.1",
+ "globby": "^14.0.0",
+ "normalize-path": "^3.0.0",
+ "schema-utils": "^4.2.0",
+ "serialize-javascript": "^6.0.2"
+ },
+ "engines": {
+ "node": ">= 18.12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.1.0"
+ }
+ },
+ "node_modules/core-js": {
+ "version": "3.45.1",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz",
+ "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
+ "node_modules/core-js-compat": {
+ "version": "3.45.1",
+ "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.1.tgz",
+ "integrity": "sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.25.3"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cosmiconfig": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
+ "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "env-paths": "^2.2.1",
+ "import-fresh": "^3.3.0",
+ "js-yaml": "^4.1.0",
+ "parse-json": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/d-fischer"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.9.5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/crc-32": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
+ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "crc32": "bin/crc32.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/crc32-stream": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz",
+ "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "crc-32": "^1.2.0",
+ "readable-stream": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css-declaration-sorter": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.0.tgz",
+ "integrity": "sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^14 || ^16 || >=18"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.9"
+ }
+ },
+ "node_modules/css-loader": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz",
+ "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "icss-utils": "^5.1.0",
+ "postcss": "^8.4.33",
+ "postcss-modules-extract-imports": "^3.1.0",
+ "postcss-modules-local-by-default": "^4.0.5",
+ "postcss-modules-scope": "^3.2.0",
+ "postcss-modules-values": "^4.0.0",
+ "postcss-value-parser": "^4.2.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">= 18.12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "@rspack/core": "0.x || 1.x",
+ "webpack": "^5.27.0"
+ },
+ "peerDependenciesMeta": {
+ "@rspack/core": {
+ "optional": true
+ },
+ "webpack": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/css-minimizer-webpack-plugin": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-7.0.2.tgz",
+ "integrity": "sha512-nBRWZtI77PBZQgcXMNqiIXVshiQOVLGSf2qX/WZfG8IQfMbeHUMXaBWQmiiSTmPJUflQxHjZjzAmuyO7tpL2Jg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "cssnano": "^7.0.4",
+ "jest-worker": "^29.7.0",
+ "postcss": "^8.4.40",
+ "schema-utils": "^4.2.0",
+ "serialize-javascript": "^6.0.2"
+ },
+ "engines": {
+ "node": ">= 18.12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@parcel/css": {
+ "optional": true
+ },
+ "@swc/css": {
+ "optional": true
+ },
+ "clean-css": {
+ "optional": true
+ },
+ "csso": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/css-select": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz",
+ "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0",
+ "css-what": "^6.0.1",
+ "domhandler": "^4.3.1",
+ "domutils": "^2.8.0",
+ "nth-check": "^2.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/css-tree": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
+ "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mdn-data": "2.12.2",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
+ "node_modules/css-what": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
+ "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">= 6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/cssnano": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.1.tgz",
+ "integrity": "sha512-fm4D8ti0dQmFPeF8DXSAA//btEmqCOgAc/9Oa3C1LW94h5usNrJEfrON7b4FkPZgnDEn6OUs5NdxiJZmAtGOpQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssnano-preset-default": "^7.0.9",
+ "lilconfig": "^3.1.3"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/cssnano"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/cssnano-preset-default": {
+ "version": "7.0.9",
+ "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.9.tgz",
+ "integrity": "sha512-tCD6AAFgYBOVpMBX41KjbvRh9c2uUjLXRyV7KHSIrwHiq5Z9o0TFfUCoM3TwVrRsRteN3sVXGNvjVNxYzkpTsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.25.1",
+ "css-declaration-sorter": "^7.2.0",
+ "cssnano-utils": "^5.0.1",
+ "postcss-calc": "^10.1.1",
+ "postcss-colormin": "^7.0.4",
+ "postcss-convert-values": "^7.0.7",
+ "postcss-discard-comments": "^7.0.4",
+ "postcss-discard-duplicates": "^7.0.2",
+ "postcss-discard-empty": "^7.0.1",
+ "postcss-discard-overridden": "^7.0.1",
+ "postcss-merge-longhand": "^7.0.5",
+ "postcss-merge-rules": "^7.0.6",
+ "postcss-minify-font-values": "^7.0.1",
+ "postcss-minify-gradients": "^7.0.1",
+ "postcss-minify-params": "^7.0.4",
+ "postcss-minify-selectors": "^7.0.5",
+ "postcss-normalize-charset": "^7.0.1",
+ "postcss-normalize-display-values": "^7.0.1",
+ "postcss-normalize-positions": "^7.0.1",
+ "postcss-normalize-repeat-style": "^7.0.1",
+ "postcss-normalize-string": "^7.0.1",
+ "postcss-normalize-timing-functions": "^7.0.1",
+ "postcss-normalize-unicode": "^7.0.4",
+ "postcss-normalize-url": "^7.0.1",
+ "postcss-normalize-whitespace": "^7.0.1",
+ "postcss-ordered-values": "^7.0.2",
+ "postcss-reduce-initial": "^7.0.4",
+ "postcss-reduce-transforms": "^7.0.1",
+ "postcss-svgo": "^7.1.0",
+ "postcss-unique-selectors": "^7.0.4"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/cssnano-utils": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.1.tgz",
+ "integrity": "sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/csso": {
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz",
+ "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "css-tree": "~2.2.0"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
+ "npm": ">=7.0.0"
+ }
+ },
+ "node_modules/csso/node_modules/css-tree": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
+ "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mdn-data": "2.0.28",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
+ "npm": ">=7.0.0"
+ }
+ },
+ "node_modules/csso/node_modules/mdn-data": {
+ "version": "2.0.28",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
+ "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "license": "MIT"
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.18",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz",
+ "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==",
+ "license": "MIT"
+ },
+ "node_modules/debounce": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
+ "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deepmerge": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz",
+ "integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/default-browser": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz",
+ "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bundle-name": "^4.1.0",
+ "default-browser-id": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/default-browser-id": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz",
+ "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-lazy-prop": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
+ "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+ "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "peer": true,
+ "bin": {
+ "detect-libc": "bin/detect-libc.js"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/detect-node": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
+ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/dns-packet": {
+ "version": "5.6.1",
+ "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz",
+ "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@leichtgewicht/ip-codec": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/dom-converter": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz",
+ "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "utila": "~0.4"
+ }
+ },
+ "node_modules/dom-serializer": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
+ "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.0.1",
+ "domhandler": "^4.2.0",
+ "entities": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/dom-serializer/node_modules/entities": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
+ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/domhandler": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
+ "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "^2.2.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/domutils": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
+ "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dom-serializer": "^1.0.1",
+ "domelementtype": "^2.2.0",
+ "domhandler": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
+ "node_modules/dot-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
+ "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "no-case": "^3.0.4",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/dot-prop": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz",
+ "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^4.18.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.6.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dotenv-expand": {
+ "version": "11.0.7",
+ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz",
+ "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dotenv": "^16.4.5"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/duplexer": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
+ "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.232",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.232.tgz",
+ "integrity": "sha512-ENirSe7wf8WzyPCibqKUG1Cg43cPaxH4wRR7AJsX7MCABCHBIOFqvaYODSLKUuZdraxUTHRE/0A2Aq8BYKEHOg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/elementtree": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/elementtree/-/elementtree-0.1.7.tgz",
+ "integrity": "sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "sax": "1.1.4"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/emojis-list": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
+ "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.18.3",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
+ "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/environment": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
+ "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/error-stack-parser": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
+ "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "stackframe": "^1.3.4"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.10",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz",
+ "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.10",
+ "@esbuild/android-arm": "0.25.10",
+ "@esbuild/android-arm64": "0.25.10",
+ "@esbuild/android-x64": "0.25.10",
+ "@esbuild/darwin-arm64": "0.25.10",
+ "@esbuild/darwin-x64": "0.25.10",
+ "@esbuild/freebsd-arm64": "0.25.10",
+ "@esbuild/freebsd-x64": "0.25.10",
+ "@esbuild/linux-arm": "0.25.10",
+ "@esbuild/linux-arm64": "0.25.10",
+ "@esbuild/linux-ia32": "0.25.10",
+ "@esbuild/linux-loong64": "0.25.10",
+ "@esbuild/linux-mips64el": "0.25.10",
+ "@esbuild/linux-ppc64": "0.25.10",
+ "@esbuild/linux-riscv64": "0.25.10",
+ "@esbuild/linux-s390x": "0.25.10",
+ "@esbuild/linux-x64": "0.25.10",
+ "@esbuild/netbsd-arm64": "0.25.10",
+ "@esbuild/netbsd-x64": "0.25.10",
+ "@esbuild/openbsd-arm64": "0.25.10",
+ "@esbuild/openbsd-x64": "0.25.10",
+ "@esbuild/openharmony-arm64": "0.25.10",
+ "@esbuild/sunos-x64": "0.25.10",
+ "@esbuild/win32-arm64": "0.25.10",
+ "@esbuild/win32-ia32": "0.25.10",
+ "@esbuild/win32-x64": "0.25.10"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esrecurse/node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "license": "MIT"
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/event-target-shim": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/events": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.x"
+ }
+ },
+ "node_modules/events-universal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
+ "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-events": "^2.7.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
+ "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.3",
+ "content-disposition": "0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "0.7.1",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.3.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.13.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "0.19.0",
+ "serve-static": "1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/express/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/external-editor": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
+ "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chardet": "^0.7.0",
+ "iconv-lite": "^0.4.24",
+ "tmp": "^0.0.33"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-fifo": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
+ "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
+ "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/fastq": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/faye-websocket": {
+ "version": "0.11.4",
+ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
+ "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "websocket-driver": ">=0.5.1"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-loader": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz",
+ "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^4.0.0 || ^5.0.0"
+ }
+ },
+ "node_modules/file-loader/node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/file-loader/node_modules/ajv-keywords": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "ajv": "^6.9.1"
+ }
+ },
+ "node_modules/file-loader/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/file-loader/node_modules/schema-utils": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+ "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/finalhandler/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/finalhandler/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/find-babel-config": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-2.1.2.tgz",
+ "integrity": "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json5": "^2.2.3"
+ }
+ },
+ "node_modules/find-cache-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz",
+ "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "common-path-prefix": "^3.0.0",
+ "pkg-dir": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz",
+ "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^7.1.0",
+ "path-exists": "^5.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "bin": {
+ "flat": "cli.js"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "11.3.2",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz",
+ "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-east-asian-width": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
+ "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/glob": {
+ "version": "10.4.5",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
+ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/glob-to-regex.js": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz",
+ "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/glob-to-regexp": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
+ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/globby": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz",
+ "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/merge-streams": "^2.1.0",
+ "fast-glob": "^3.3.3",
+ "ignore": "^7.0.3",
+ "path-type": "^6.0.0",
+ "slash": "^5.1.0",
+ "unicorn-magic": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/gzip-size": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz",
+ "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "duplexer": "^0.1.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/handle-thing": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
+ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hash-sum": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz",
+ "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/hookable": {
+ "version": "5.5.3",
+ "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
+ "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
+ "license": "MIT"
+ },
+ "node_modules/hpack.js": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
+ "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.1",
+ "obuf": "^1.0.0",
+ "readable-stream": "^2.0.1",
+ "wbuf": "^1.1.0"
+ }
+ },
+ "node_modules/hpack.js/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/hpack.js/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hpack.js/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/html-entities": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz",
+ "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/mdevils"
+ },
+ {
+ "type": "patreon",
+ "url": "https://patreon.com/mdevils"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/html-minifier-terser": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz",
+ "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "camel-case": "^4.1.2",
+ "clean-css": "~5.3.2",
+ "commander": "^10.0.0",
+ "entities": "^4.4.0",
+ "param-case": "^3.0.4",
+ "relateurl": "^0.2.7",
+ "terser": "^5.15.1"
+ },
+ "bin": {
+ "html-minifier-terser": "cli.js"
+ },
+ "engines": {
+ "node": "^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/html-webpack-plugin": {
+ "version": "5.6.4",
+ "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.4.tgz",
+ "integrity": "sha512-V/PZeWsqhfpE27nKeX9EO2sbR+D17A+tLf6qU+ht66jdUsN0QLKJN27Z+1+gHrVMKgndBahes0PU6rRihDgHTw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/html-minifier-terser": "^6.0.0",
+ "html-minifier-terser": "^6.0.2",
+ "lodash": "^4.17.21",
+ "pretty-error": "^4.0.0",
+ "tapable": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/html-webpack-plugin"
+ },
+ "peerDependencies": {
+ "@rspack/core": "0.x || 1.x",
+ "webpack": "^5.20.0"
+ },
+ "peerDependenciesMeta": {
+ "@rspack/core": {
+ "optional": true
+ },
+ "webpack": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/html-webpack-plugin/node_modules/commander": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/html-webpack-plugin/node_modules/html-minifier-terser": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
+ "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "camel-case": "^4.1.2",
+ "clean-css": "^5.2.2",
+ "commander": "^8.3.0",
+ "he": "^1.2.0",
+ "param-case": "^3.0.4",
+ "relateurl": "^0.2.7",
+ "terser": "^5.10.0"
+ },
+ "bin": {
+ "html-minifier-terser": "cli.js"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/htmlparser2": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
+ "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==",
+ "dev": true,
+ "funding": [
+ "https://github.com/fb55/htmlparser2?sponsor=1",
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.0.1",
+ "domhandler": "^4.0.0",
+ "domutils": "^2.5.2",
+ "entities": "^2.0.0"
+ }
+ },
+ "node_modules/htmlparser2/node_modules/entities": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
+ "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/http-deceiver": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
+ "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/http-parser-js": {
+ "version": "0.5.10",
+ "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz",
+ "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/http-proxy": {
+ "version": "1.18.1",
+ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
+ "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eventemitter3": "^4.0.0",
+ "follow-redirects": "^1.0.0",
+ "requires-port": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/http-proxy-middleware": {
+ "version": "2.0.9",
+ "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz",
+ "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-proxy": "^1.17.8",
+ "http-proxy": "^1.18.1",
+ "is-glob": "^4.0.1",
+ "is-plain-obj": "^3.0.0",
+ "micromatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "@types/express": "^4.17.13"
+ },
+ "peerDependenciesMeta": {
+ "@types/express": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/hyperdyperid": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz",
+ "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.18"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/icss-utils": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
+ "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/immutable": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz",
+ "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/inquirer": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-11.1.0.tgz",
+ "integrity": "sha512-CmLAZT65GG/v30c+D2Fk8+ceP6pxD6RL+hIUOWAltCmeyEqWYwqu9v76q03OvjyZ3AB0C1Ala2stn1z/rMqGEw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^9.2.1",
+ "@inquirer/prompts": "^6.0.1",
+ "@inquirer/type": "^2.0.0",
+ "@types/mute-stream": "^0.0.4",
+ "ansi-escapes": "^4.3.2",
+ "mute-stream": "^1.0.0",
+ "run-async": "^3.0.0",
+ "rxjs": "^7.8.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-docker": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
+ "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
+ "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-inside-container": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
+ "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^3.0.0"
+ },
+ "bin": {
+ "is-inside-container": "cli.js"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-network-error": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz",
+ "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz",
+ "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "isobject": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-what": {
+ "version": "4.1.16",
+ "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
+ "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
+ "node_modules/is-wsl": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
+ "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-inside-container": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/isbinaryfile": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.6.tgz",
+ "integrity": "sha512-I+NmIfBHUl+r2wcDd6JwE9yWje/PIVY/R5/CmV8dXLZd5K+L9X2klAOwfAHNnondLXkbHyTAleQAWonpTJBTtw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/gjtorikian/"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/javascript-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz",
+ "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jest-util": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
+ "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "graceful-fs": "^4.2.9",
+ "picomatch": "^2.2.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-util/node_modules/ci-info": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+ "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jest-util/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/jest-worker": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
+ "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "jest-util": "^29.7.0",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
+ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/kolorist": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
+ "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/launch-editor": {
+ "version": "2.11.1",
+ "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz",
+ "integrity": "sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picocolors": "^1.1.1",
+ "shell-quote": "^1.8.3"
+ }
+ },
+ "node_modules/launch-editor-middleware": {
+ "version": "2.11.1",
+ "resolved": "https://registry.npmjs.org/launch-editor-middleware/-/launch-editor-middleware-2.11.1.tgz",
+ "integrity": "sha512-6xpn4pJz5mDg2kUH7L6gK5BuZcZPdVwoSs/DhfebefwLyszNXqFFjksGup/w4CTRzzrr8FSEufDzb/gKFLle6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "launch-editor": "^2.11.1"
+ }
+ },
+ "node_modules/lazystream": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
+ "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "readable-stream": "^2.0.5"
+ },
+ "engines": {
+ "node": ">= 0.6.3"
+ }
+ },
+ "node_modules/lazystream/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/lazystream/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lazystream/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/loader-runner": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
+ "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.11.5"
+ }
+ },
+ "node_modules/loader-utils": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
+ "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^2.1.2"
+ },
+ "engines": {
+ "node": ">=8.9.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz",
+ "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^6.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.debounce": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.memoize": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
+ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.uniq": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
+ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/log-update": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
+ "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-escapes": "^7.0.0",
+ "cli-cursor": "^5.0.0",
+ "slice-ansi": "^7.1.0",
+ "strip-ansi": "^7.1.0",
+ "wrap-ansi": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-update/node_modules/ansi-escapes": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.1.tgz",
+ "integrity": "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "environment": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-update/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/log-update/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/log-update/node_modules/emoji-regex": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.5.0.tgz",
+ "integrity": "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/log-update/node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-update/node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/log-update/node_modules/wrap-ansi": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
+ "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/lower-case": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
+ "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.19",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
+ "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mdn-data": {
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
+ "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/memfs": {
+ "version": "4.49.0",
+ "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.49.0.tgz",
+ "integrity": "sha512-L9uC9vGuc4xFybbdOpRLoOAOq1YEBBsocCs5NVW32DfU+CZWWIn3OVF+lB8Gp4ttBVSMazwrTrjv8ussX/e3VQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jsonjoy.com/json-pack": "^1.11.0",
+ "@jsonjoy.com/util": "^1.9.0",
+ "glob-to-regex.js": "^1.0.1",
+ "thingies": "^2.5.0",
+ "tree-dump": "^1.0.3",
+ "tslib": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/micromatch/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-function": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
+ "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mini-css-extract-plugin": {
+ "version": "2.9.4",
+ "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz",
+ "integrity": "sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "schema-utils": "^4.0.0",
+ "tapable": "^2.2.1"
+ },
+ "engines": {
+ "node": ">= 12.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.0.0"
+ }
+ },
+ "node_modules/minimalistic-assert": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
+ "license": "MIT"
+ },
+ "node_modules/mrmime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
+ "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/multicast-dns": {
+ "version": "7.2.5",
+ "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz",
+ "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dns-packet": "^5.2.2",
+ "thunky": "^1.0.2"
+ },
+ "bin": {
+ "multicast-dns": "cli.js"
+ }
+ },
+ "node_modules/mute-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz",
+ "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/no-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
+ "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lower-case": "^2.0.2",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/node-addon-api": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
+ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true
+ },
+ "node_modules/node-forge": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
+ "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
+ "dev": true,
+ "license": "(BSD-3-Clause OR GPL-2.0)",
+ "engines": {
+ "node": ">= 6.13.0"
+ }
+ },
+ "node_modules/node-loader": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/node-loader/-/node-loader-2.1.0.tgz",
+ "integrity": "sha512-OwjPkyh8+7jW8DMd/iq71uU1Sspufr/C2+c3t0p08J3CrM9ApZ4U53xuisNrDXOHyGi5OYHgtfmmh+aK9zJA6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "loader-utils": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.0.0"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.23",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz",
+ "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
+ }
+ },
+ "node_modules/null-loader": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz",
+ "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^4.0.0 || ^5.0.0"
+ }
+ },
+ "node_modules/null-loader/node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/null-loader/node_modules/ajv-keywords": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "ajv": "^6.9.1"
+ }
+ },
+ "node_modules/null-loader/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/null-loader/node_modules/schema-utils": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.assign": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
+ "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0",
+ "has-symbols": "^1.1.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/obuf": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
+ "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/on-headers": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
+ "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
+ "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-function": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/open": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz",
+ "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "default-browser": "^5.2.1",
+ "define-lazy-prop": "^3.0.0",
+ "is-inside-container": "^1.0.0",
+ "wsl-utils": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/opener": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
+ "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
+ "dev": true,
+ "license": "(WTFPL OR MIT)",
+ "bin": {
+ "opener": "bin/opener-bin.js"
+ }
+ },
+ "node_modules/os-tmpdir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
+ "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^1.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz",
+ "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-retry": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz",
+ "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/retry": "0.12.2",
+ "is-network-error": "^1.0.0",
+ "retry": "^0.13.1"
+ },
+ "engines": {
+ "node": ">=16.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0"
+ },
+ "node_modules/param-case": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
+ "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dot-case": "^3.0.4",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/pascal-case": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
+ "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "no-case": "^3.0.4",
+ "tslib": "^2.0.3"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz",
+ "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-type": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz",
+ "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/perfect-debounce": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
+ "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pinia": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz",
+ "integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-api": "^7.7.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.4.4",
+ "vue": "^2.7.0 || ^3.5.11"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pkg-dir": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz",
+ "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-up": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz",
+ "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pkg-up/node_modules/find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pkg-up/node_modules/locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pkg-up/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/pkg-up/node_modules/p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pkg-up/node_modules/path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-calc": {
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz",
+ "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^7.0.0",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.12 || ^20.9 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.38"
+ }
+ },
+ "node_modules/postcss-colormin": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.4.tgz",
+ "integrity": "sha512-ziQuVzQZBROpKpfeDwmrG+Vvlr0YWmY/ZAk99XD+mGEBuEojoFekL41NCsdhyNUtZI7DPOoIWIR7vQQK9xwluw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.25.1",
+ "caniuse-api": "^3.0.0",
+ "colord": "^2.9.3",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-convert-values": {
+ "version": "7.0.7",
+ "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.7.tgz",
+ "integrity": "sha512-HR9DZLN04Xbe6xugRH6lS4ZQH2zm/bFh/ZyRkpedZozhvh+awAfbA0P36InO4fZfDhvYfNJeNvlTf1sjwGbw/A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.25.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-discard-comments": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.4.tgz",
+ "integrity": "sha512-6tCUoql/ipWwKtVP/xYiFf1U9QgJ0PUvxN7pTcsQ8Ns3Fnwq1pU5D5s1MhT/XySeLq6GXNvn37U46Ded0TckWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^7.1.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-discard-duplicates": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.2.tgz",
+ "integrity": "sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-discard-empty": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.1.tgz",
+ "integrity": "sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-discard-overridden": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.1.tgz",
+ "integrity": "sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-loader": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.2.0.tgz",
+ "integrity": "sha512-tHX+RkpsXVcc7st4dSdDGliI+r4aAQDuv+v3vFYHixb6YgjreG5AG4SEB0kDK8u2s6htqEEpKlkhSBUTvWKYnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cosmiconfig": "^9.0.0",
+ "jiti": "^2.5.1",
+ "semver": "^7.6.2"
+ },
+ "engines": {
+ "node": ">= 18.12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "@rspack/core": "0.x || 1.x",
+ "postcss": "^7.0.0 || ^8.0.1",
+ "webpack": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rspack/core": {
+ "optional": true
+ },
+ "webpack": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-merge-longhand": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.5.tgz",
+ "integrity": "sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.2.0",
+ "stylehacks": "^7.0.5"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-merge-rules": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.6.tgz",
+ "integrity": "sha512-2jIPT4Tzs8K87tvgCpSukRQ2jjd+hH6Bb8rEEOUDmmhOeTcqDg5fEFK8uKIu+Pvc3//sm3Uu6FRqfyv7YF7+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.25.1",
+ "caniuse-api": "^3.0.0",
+ "cssnano-utils": "^5.0.1",
+ "postcss-selector-parser": "^7.1.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-minify-font-values": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.1.tgz",
+ "integrity": "sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-minify-gradients": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.1.tgz",
+ "integrity": "sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "colord": "^2.9.3",
+ "cssnano-utils": "^5.0.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-minify-params": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.4.tgz",
+ "integrity": "sha512-3OqqUddfH8c2e7M35W6zIwv7jssM/3miF9cbCSb1iJiWvtguQjlxZGIHK9JRmc8XAKmE2PFGtHSM7g/VcW97sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.25.1",
+ "cssnano-utils": "^5.0.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-minify-selectors": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.5.tgz",
+ "integrity": "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "postcss-selector-parser": "^7.1.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-modules-extract-imports": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz",
+ "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/postcss-modules-local-by-default": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz",
+ "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "icss-utils": "^5.0.0",
+ "postcss-selector-parser": "^7.0.0",
+ "postcss-value-parser": "^4.1.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/postcss-modules-scope": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz",
+ "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "postcss-selector-parser": "^7.0.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/postcss-modules-values": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz",
+ "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "icss-utils": "^5.0.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >= 14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/postcss-normalize-charset": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.1.tgz",
+ "integrity": "sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-normalize-display-values": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.1.tgz",
+ "integrity": "sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-normalize-positions": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.1.tgz",
+ "integrity": "sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-normalize-repeat-style": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.1.tgz",
+ "integrity": "sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-normalize-string": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.1.tgz",
+ "integrity": "sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-normalize-timing-functions": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.1.tgz",
+ "integrity": "sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-normalize-unicode": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.4.tgz",
+ "integrity": "sha512-LvIURTi1sQoZqj8mEIE8R15yvM+OhbR1avynMtI9bUzj5gGKR/gfZFd8O7VMj0QgJaIFzxDwxGl/ASMYAkqO8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.25.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-normalize-url": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.1.tgz",
+ "integrity": "sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-normalize-whitespace": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.1.tgz",
+ "integrity": "sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-ordered-values": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.2.tgz",
+ "integrity": "sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssnano-utils": "^5.0.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-reduce-initial": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.4.tgz",
+ "integrity": "sha512-rdIC9IlMBn7zJo6puim58Xd++0HdbvHeHaPgXsimMfG1ijC5A9ULvNLSE0rUKVJOvNMcwewW4Ga21ngyJjY/+Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.25.1",
+ "caniuse-api": "^3.0.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-reduce-transforms": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.1.tgz",
+ "integrity": "sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.2.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-rtlcss": {
+ "version": "5.7.1",
+ "resolved": "https://registry.npmjs.org/postcss-rtlcss/-/postcss-rtlcss-5.7.1.tgz",
+ "integrity": "sha512-zE68CuARv5StOG/UQLa0W1Y/raUTzgJlfjtas43yh3/G1BFmoPEaHxPRHgeowXRFFhW33FehrNgsljxRLmPVWw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "rtlcss": "4.3.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz",
+ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-svgo": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.1.0.tgz",
+ "integrity": "sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.2.0",
+ "svgo": "^4.0.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >= 18"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-unique-selectors": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz",
+ "integrity": "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^7.1.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pretty-error": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz",
+ "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lodash": "^4.17.20",
+ "renderkid": "^3.0.0"
+ }
+ },
+ "node_modules/process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.0.6"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/quasar": {
+ "version": "2.18.5",
+ "resolved": "https://registry.npmjs.org/quasar/-/quasar-2.18.5.tgz",
+ "integrity": "sha512-5ItDSsNjqBVRrC7SqcdvT1F5mghVyJ/KmaWNwnaT5mM91a7gWpT/d7wTCIFxxDbWLZdkHKI+cpdudEqnfcSw9A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.18.1",
+ "npm": ">= 6.13.4",
+ "yarn": ">= 1.21.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://donate.quasar.dev"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
+ "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "abort-controller": "^3.0.0",
+ "buffer": "^6.0.3",
+ "events": "^3.3.0",
+ "process": "^0.11.10",
+ "string_decoder": "^1.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/readdir-glob": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz",
+ "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "minimatch": "^5.1.0"
+ }
+ },
+ "node_modules/readdir-glob/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/regenerate": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
+ "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/regenerate-unicode-properties": {
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz",
+ "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "regenerate": "^1.4.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/regexpu-core": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz",
+ "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "regenerate": "^1.4.2",
+ "regenerate-unicode-properties": "^10.2.2",
+ "regjsgen": "^0.8.0",
+ "regjsparser": "^0.13.0",
+ "unicode-match-property-ecmascript": "^2.0.0",
+ "unicode-match-property-value-ecmascript": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/regjsgen": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz",
+ "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/regjsparser": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz",
+ "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "jsesc": "~3.1.0"
+ },
+ "bin": {
+ "regjsparser": "bin/parser"
+ }
+ },
+ "node_modules/relateurl": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
+ "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/renderkid": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz",
+ "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "css-select": "^4.1.3",
+ "dom-converter": "^0.2.0",
+ "htmlparser2": "^6.1.0",
+ "lodash": "^4.17.21",
+ "strip-ansi": "^6.0.1"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/reselect": {
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
+ "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/resolve": {
+ "version": "1.22.10",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/restore-cursor": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
+ "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^7.0.0",
+ "signal-exit": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
+ "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rfdc": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
+ "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
+ "license": "MIT"
+ },
+ "node_modules/rollup-plugin-visualizer": {
+ "version": "5.14.0",
+ "resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.14.0.tgz",
+ "integrity": "sha512-VlDXneTDaKsHIw8yzJAFWtrzguoJ/LnQ+lMpoVfYJ3jJF4Ihe5oYLAqLklIK/35lgUY+1yEzCkHyZ1j4A5w5fA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "open": "^8.4.0",
+ "picomatch": "^4.0.2",
+ "source-map": "^0.7.4",
+ "yargs": "^17.5.1"
+ },
+ "bin": {
+ "rollup-plugin-visualizer": "dist/bin/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "rolldown": "1.x",
+ "rollup": "2.x || 3.x || 4.x"
+ },
+ "peerDependenciesMeta": {
+ "rolldown": {
+ "optional": true
+ },
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/rollup-plugin-visualizer/node_modules/define-lazy-prop": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
+ "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/rollup-plugin-visualizer/node_modules/is-docker": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+ "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "is-docker": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/rollup-plugin-visualizer/node_modules/is-wsl": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+ "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-docker": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/rollup-plugin-visualizer/node_modules/open": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
+ "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-lazy-prop": "^2.0.0",
+ "is-docker": "^2.1.1",
+ "is-wsl": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/rtlcss": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz",
+ "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.1.1",
+ "picocolors": "^1.0.0",
+ "postcss": "^8.4.21",
+ "strip-json-comments": "^3.1.1"
+ },
+ "bin": {
+ "rtlcss": "bin/rtlcss.js"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/run-applescript": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
+ "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/run-async": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz",
+ "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/rxjs": {
+ "version": "7.8.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
+ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/sass": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.2.tgz",
+ "integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peer": true,
+ "dependencies": {
+ "chokidar": "^4.0.0",
+ "immutable": "^5.0.2",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ },
+ "bin": {
+ "sass": "sass.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "optionalDependencies": {
+ "@parcel/watcher": "^2.4.1"
+ }
+ },
+ "node_modules/sass-embedded": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.93.2.tgz",
+ "integrity": "sha512-FvQdkn2dZ8DGiLgi0Uf4zsj7r/BsiLImNa5QJ10eZalY6NfZyjrmWGFcuCN5jNwlDlXFJnftauv+UtvBKLvepQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@bufbuild/protobuf": "^2.5.0",
+ "buffer-builder": "^0.2.0",
+ "colorjs.io": "^0.5.0",
+ "immutable": "^5.0.2",
+ "rxjs": "^7.4.0",
+ "supports-color": "^8.1.1",
+ "sync-child-process": "^1.0.2",
+ "varint": "^6.0.0"
+ },
+ "bin": {
+ "sass": "dist/bin/sass.js"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ },
+ "optionalDependencies": {
+ "sass-embedded-all-unknown": "1.93.2",
+ "sass-embedded-android-arm": "1.93.2",
+ "sass-embedded-android-arm64": "1.93.2",
+ "sass-embedded-android-riscv64": "1.93.2",
+ "sass-embedded-android-x64": "1.93.2",
+ "sass-embedded-darwin-arm64": "1.93.2",
+ "sass-embedded-darwin-x64": "1.93.2",
+ "sass-embedded-linux-arm": "1.93.2",
+ "sass-embedded-linux-arm64": "1.93.2",
+ "sass-embedded-linux-musl-arm": "1.93.2",
+ "sass-embedded-linux-musl-arm64": "1.93.2",
+ "sass-embedded-linux-musl-riscv64": "1.93.2",
+ "sass-embedded-linux-musl-x64": "1.93.2",
+ "sass-embedded-linux-riscv64": "1.93.2",
+ "sass-embedded-linux-x64": "1.93.2",
+ "sass-embedded-unknown-all": "1.93.2",
+ "sass-embedded-win32-arm64": "1.93.2",
+ "sass-embedded-win32-x64": "1.93.2"
+ }
+ },
+ "node_modules/sass-embedded-win32-x64": {
+ "version": "1.93.2",
+ "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.93.2.tgz",
+ "integrity": "sha512-BbSucRP6PVRZGIwlEBkp+6VQl2GWdkWFMN+9EuOTPrLxCJZoq+yhzmbjspd3PeM8+7WJ7AdFu/uRYdO8tor1iQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-loader": {
+ "version": "16.0.5",
+ "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.5.tgz",
+ "integrity": "sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "neo-async": "^2.6.2"
+ },
+ "engines": {
+ "node": ">= 18.12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "@rspack/core": "0.x || 1.x",
+ "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0",
+ "sass": "^1.3.0",
+ "sass-embedded": "*",
+ "webpack": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rspack/core": {
+ "optional": true
+ },
+ "node-sass": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "webpack": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/sax": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.1.4.tgz",
+ "integrity": "sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/schema-utils": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz",
+ "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/json-schema": "^7.0.9",
+ "ajv": "^8.9.0",
+ "ajv-formats": "^2.1.1",
+ "ajv-keywords": "^5.1.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/select-hose": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
+ "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/selfsigned": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz",
+ "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node-forge": "^1.3.0",
+ "node-forge": "^1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/send/node_modules/debug/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/send/node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/serialize-javascript": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
+ "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "node_modules/serve-index": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
+ "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.4",
+ "batch": "0.6.1",
+ "debug": "2.6.9",
+ "escape-html": "~1.0.3",
+ "http-errors": "~1.6.2",
+ "mime-types": "~2.1.17",
+ "parseurl": "~1.3.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/serve-index/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/serve-index/node_modules/depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve-index/node_modules/http-errors": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+ "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.1.0",
+ "statuses": ">= 1.4.0 < 2"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve-index/node_modules/inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/serve-index/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/serve-index/node_modules/setprototypeof": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
+ "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/serve-index/node_modules/statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.19.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/shallow-clone": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
+ "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kind-of": "^6.0.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shell-quote": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
+ "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/sirv": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
+ "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@polka/url": "^1.0.0-next.24",
+ "mrmime": "^2.0.0",
+ "totalist": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/slash": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
+ "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/slice-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
+ "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "is-fullwidth-code-point": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/sockjs": {
+ "version": "0.3.24",
+ "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz",
+ "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "faye-websocket": "^0.11.3",
+ "uuid": "^8.3.2",
+ "websocket-driver": "^0.7.4"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
+ "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/source-map-support/node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/spdy": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz",
+ "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.0",
+ "handle-thing": "^2.0.0",
+ "http-deceiver": "^1.2.7",
+ "select-hose": "^2.0.0",
+ "spdy-transport": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/spdy-transport": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz",
+ "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.0",
+ "detect-node": "^2.0.4",
+ "hpack.js": "^2.1.6",
+ "obuf": "^1.1.2",
+ "readable-stream": "^3.0.6",
+ "wbuf": "^1.7.3"
+ }
+ },
+ "node_modules/spdy-transport/node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/speakingurl": {
+ "version": "14.0.1",
+ "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz",
+ "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stack-trace": {
+ "version": "1.0.0-pre2",
+ "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-1.0.0-pre2.tgz",
+ "integrity": "sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/stackframe": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
+ "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/streamx": {
+ "version": "2.23.0",
+ "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz",
+ "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "events-universal": "^1.0.0",
+ "fast-fifo": "^1.3.2",
+ "text-decoder": "^1.1.0"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/string-width/node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/stylehacks": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.6.tgz",
+ "integrity": "sha512-iitguKivmsueOmTO0wmxURXBP8uqOO+zikLGZ7Mm9e/94R4w5T999Js2taS/KBOnQ/wdC3jN3vNSrkGDrlnqQg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.25.1",
+ "postcss-selector-parser": "^7.1.0"
+ },
+ "engines": {
+ "node": "^18.12.0 || ^20.9.0 || >=22.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.32"
+ }
+ },
+ "node_modules/superjson": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz",
+ "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==",
+ "license": "MIT",
+ "dependencies": {
+ "copy-anything": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/svgo": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz",
+ "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^11.1.0",
+ "css-select": "^5.1.0",
+ "css-tree": "^3.0.1",
+ "css-what": "^6.1.0",
+ "csso": "^5.0.5",
+ "picocolors": "^1.1.1",
+ "sax": "^1.4.1"
+ },
+ "bin": {
+ "svgo": "bin/svgo.js"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/svgo"
+ }
+ },
+ "node_modules/svgo/node_modules/commander": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
+ "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/svgo/node_modules/css-select": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
+ "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0",
+ "css-what": "^6.1.0",
+ "domhandler": "^5.0.2",
+ "domutils": "^3.0.1",
+ "nth-check": "^2.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/svgo/node_modules/dom-serializer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "entities": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/svgo/node_modules/domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "domelementtype": "^2.3.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/svgo/node_modules/domutils": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dom-serializer": "^2.0.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
+ "node_modules/svgo/node_modules/sax": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
+ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/sync-child-process": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz",
+ "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sync-message-port": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/sync-message-port": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz",
+ "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/tapable": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+ "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tar-stream": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
+ "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "b4a": "^1.6.4",
+ "fast-fifo": "^1.2.0",
+ "streamx": "^2.15.0"
+ }
+ },
+ "node_modules/terser": {
+ "version": "5.44.0",
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz",
+ "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@jridgewell/source-map": "^0.3.3",
+ "acorn": "^8.15.0",
+ "commander": "^2.20.0",
+ "source-map-support": "~0.5.20"
+ },
+ "bin": {
+ "terser": "bin/terser"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/terser-webpack-plugin": {
+ "version": "5.3.14",
+ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz",
+ "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.25",
+ "jest-worker": "^27.4.5",
+ "schema-utils": "^4.3.0",
+ "serialize-javascript": "^6.0.2",
+ "terser": "^5.31.1"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.1.0"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "uglify-js": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/terser-webpack-plugin/node_modules/jest-worker": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
+ "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ }
+ },
+ "node_modules/terser/node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/text-decoder": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz",
+ "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "b4a": "^1.6.4"
+ }
+ },
+ "node_modules/thingies": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz",
+ "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "^2"
+ }
+ },
+ "node_modules/thunky": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
+ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tmp": {
+ "version": "0.0.33",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "os-tmpdir": "~1.0.2"
+ },
+ "engines": {
+ "node": ">=0.6.0"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/totalist": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
+ "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tree-dump": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz",
+ "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/streamich"
+ },
+ "peerDependencies": {
+ "tslib": "2"
+ }
+ },
+ "node_modules/ts-essentials": {
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-10.1.1.tgz",
+ "integrity": "sha512-4aTB7KLHKmUvkjNj8V+EdnmuVTiECzn3K+zIbRthumvHu+j44x3w63xpfs0JL3NGIzGXqoQ7AV591xHO+XrOTw==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "typescript": ">=4.5.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD"
+ },
+ "node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.14.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz",
+ "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unicode-canonical-property-names-ecmascript": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
+ "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unicode-match-property-ecmascript": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
+ "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "unicode-canonical-property-names-ecmascript": "^2.0.0",
+ "unicode-property-aliases-ecmascript": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unicode-match-property-value-ecmascript": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz",
+ "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unicode-property-aliases-ecmascript": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz",
+ "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/unicorn-magic": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
+ "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/url-loader": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz",
+ "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "loader-utils": "^2.0.0",
+ "mime-types": "^2.1.27",
+ "schema-utils": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "file-loader": "*",
+ "webpack": "^4.0.0 || ^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "file-loader": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/url-loader/node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/url-loader/node_modules/ajv-keywords": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "ajv": "^6.9.1"
+ }
+ },
+ "node_modules/url-loader/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/url-loader/node_modules/schema-utils": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/utila": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz",
+ "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/varint": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz",
+ "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vue": {
+ "version": "3.5.20",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.20.tgz",
+ "integrity": "sha512-2sBz0x/wis5TkF1XZ2vH25zWq3G1bFEPOfkBcx2ikowmphoQsPH6X0V3mmPCXA2K1N/XGTnifVyDQP4GfDDeQw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.20",
+ "@vue/compiler-sfc": "3.5.20",
+ "@vue/runtime-dom": "3.5.20",
+ "@vue/server-renderer": "3.5.20",
+ "@vue/shared": "3.5.20"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-loader": {
+ "version": "17.4.2",
+ "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.4.2.tgz",
+ "integrity": "sha512-yTKOA4R/VN4jqjw4y5HrynFL8AK0Z3/Jt7eOJXEitsm0GMRHDBjCfCiuTiLP7OESvsZYo2pATCWhDqxC5ZrM6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "hash-sum": "^2.0.0",
+ "watchpack": "^2.4.0"
+ },
+ "peerDependencies": {
+ "webpack": "^4.1.0 || ^5.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/compiler-sfc": {
+ "optional": true
+ },
+ "vue": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-router": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz",
+ "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-api": "^6.6.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "vue": "^3.2.0"
+ }
+ },
+ "node_modules/vue-router/node_modules/@vue/devtools-api": {
+ "version": "6.6.4",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+ "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
+ "license": "MIT"
+ },
+ "node_modules/vue-style-loader": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
+ "integrity": "sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hash-sum": "^1.0.2",
+ "loader-utils": "^1.0.2"
+ }
+ },
+ "node_modules/vue-style-loader/node_modules/hash-sum": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz",
+ "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vue-style-loader/node_modules/json5": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
+ "node_modules/vue-style-loader/node_modules/loader-utils": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
+ "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "big.js": "^5.2.2",
+ "emojis-list": "^3.0.0",
+ "json5": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/watchpack": {
+ "version": "2.4.4",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz",
+ "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "glob-to-regexp": "^0.4.1",
+ "graceful-fs": "^4.1.2"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/wbuf": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
+ "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
+ "node_modules/webpack": {
+ "version": "5.102.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.0.tgz",
+ "integrity": "sha512-hUtqAR3ZLVEYDEABdBioQCIqSoguHbFn1K7WlPPWSuXmx0031BD73PSE35jKyftdSh4YLDoQNgK4pqBt5Q82MA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/eslint-scope": "^3.7.7",
+ "@types/estree": "^1.0.8",
+ "@types/json-schema": "^7.0.15",
+ "@webassemblyjs/ast": "^1.14.1",
+ "@webassemblyjs/wasm-edit": "^1.14.1",
+ "@webassemblyjs/wasm-parser": "^1.14.1",
+ "acorn": "^8.15.0",
+ "acorn-import-phases": "^1.0.3",
+ "browserslist": "^4.24.5",
+ "chrome-trace-event": "^1.0.2",
+ "enhanced-resolve": "^5.17.3",
+ "es-module-lexer": "^1.2.1",
+ "eslint-scope": "5.1.1",
+ "events": "^3.2.0",
+ "glob-to-regexp": "^0.4.1",
+ "graceful-fs": "^4.2.11",
+ "json-parse-even-better-errors": "^2.3.1",
+ "loader-runner": "^4.2.0",
+ "mime-types": "^2.1.27",
+ "neo-async": "^2.6.2",
+ "schema-utils": "^4.3.2",
+ "tapable": "^2.2.3",
+ "terser-webpack-plugin": "^5.3.11",
+ "watchpack": "^2.4.4",
+ "webpack-sources": "^3.3.3"
+ },
+ "bin": {
+ "webpack": "bin/webpack.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependenciesMeta": {
+ "webpack-cli": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webpack-5-chain": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/webpack-5-chain/-/webpack-5-chain-8.0.2.tgz",
+ "integrity": "sha512-gpzlChffrVUu5YwIw9i240/wdcglw53mSEV/7WoK7L/ddfb6Al8/sRjztyPYV8VgJAmkapH5T1AOUfMFryQ/VA==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "deepmerge": "^1.5.2",
+ "javascript-stringify": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/webpack-bundle-analyzer": {
+ "version": "4.10.2",
+ "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz",
+ "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@discoveryjs/json-ext": "0.5.7",
+ "acorn": "^8.0.4",
+ "acorn-walk": "^8.0.0",
+ "commander": "^7.2.0",
+ "debounce": "^1.2.1",
+ "escape-string-regexp": "^4.0.0",
+ "gzip-size": "^6.0.0",
+ "html-escaper": "^2.0.2",
+ "opener": "^1.5.2",
+ "picocolors": "^1.0.0",
+ "sirv": "^2.0.3",
+ "ws": "^7.3.1"
+ },
+ "bin": {
+ "webpack-bundle-analyzer": "lib/bin/analyzer.js"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ }
+ },
+ "node_modules/webpack-bundle-analyzer/node_modules/commander": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
+ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/webpack-dev-middleware": {
+ "version": "7.4.5",
+ "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz",
+ "integrity": "sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "colorette": "^2.0.10",
+ "memfs": "^4.43.1",
+ "mime-types": "^3.0.1",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "schema-utils": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 18.12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "webpack": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webpack-dev-middleware/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/webpack-dev-middleware/node_modules/mime-types": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/webpack-dev-server": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz",
+ "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/bonjour": "^3.5.13",
+ "@types/connect-history-api-fallback": "^1.5.4",
+ "@types/express": "^4.17.21",
+ "@types/express-serve-static-core": "^4.17.21",
+ "@types/serve-index": "^1.9.4",
+ "@types/serve-static": "^1.15.5",
+ "@types/sockjs": "^0.3.36",
+ "@types/ws": "^8.5.10",
+ "ansi-html-community": "^0.0.8",
+ "bonjour-service": "^1.2.1",
+ "chokidar": "^3.6.0",
+ "colorette": "^2.0.10",
+ "compression": "^1.7.4",
+ "connect-history-api-fallback": "^2.0.0",
+ "express": "^4.21.2",
+ "graceful-fs": "^4.2.6",
+ "http-proxy-middleware": "^2.0.9",
+ "ipaddr.js": "^2.1.0",
+ "launch-editor": "^2.6.1",
+ "open": "^10.0.3",
+ "p-retry": "^6.2.0",
+ "schema-utils": "^4.2.0",
+ "selfsigned": "^2.4.1",
+ "serve-index": "^1.9.1",
+ "sockjs": "^0.3.24",
+ "spdy": "^4.0.2",
+ "webpack-dev-middleware": "^7.4.2",
+ "ws": "^8.18.0"
+ },
+ "bin": {
+ "webpack-dev-server": "bin/webpack-dev-server.js"
+ },
+ "engines": {
+ "node": ">= 18.12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "webpack": {
+ "optional": true
+ },
+ "webpack-cli": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webpack-dev-server/node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/webpack-dev-server/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/webpack-dev-server/node_modules/ipaddr.js": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
+ "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/webpack-dev-server/node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/webpack-dev-server/node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/webpack-dev-server/node_modules/ws": {
+ "version": "8.18.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webpack-hot-middleware": {
+ "version": "2.26.1",
+ "resolved": "https://registry.npmjs.org/webpack-hot-middleware/-/webpack-hot-middleware-2.26.1.tgz",
+ "integrity": "sha512-khZGfAeJx6I8K9zKohEWWYN6KDlVw2DHownoe+6Vtwj1LP9WFgegXnVMSkZ/dBEBtXFwrkkydsaPFlB7f8wU2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-html-community": "0.0.8",
+ "html-entities": "^2.1.0",
+ "strip-ansi": "^6.0.0"
+ }
+ },
+ "node_modules/webpack-merge": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz",
+ "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "clone-deep": "^4.0.1",
+ "flat": "^5.0.2",
+ "wildcard": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/webpack-node-externals": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz",
+ "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/webpack-sources": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz",
+ "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/websocket-driver": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
+ "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "http-parser-js": ">=0.5.1",
+ "safe-buffer": ">=5.1.0",
+ "websocket-extensions": ">=0.1.1"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/websocket-extensions": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
+ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wildcard": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz",
+ "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ws": {
+ "version": "7.5.10",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
+ "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.3.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/wsl-utils": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz",
+ "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-wsl": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/yargs/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz",
+ "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/yoctocolors-cjs": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz",
+ "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zip-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz",
+ "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "archiver-utils": "^5.0.0",
+ "compress-commons": "^6.0.2",
+ "readable-stream": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ }
+ }
+}
diff --git a/ui/package.json b/ui/package.json
new file mode 100644
index 0000000..b033588
--- /dev/null
+++ b/ui/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "baggisowtfaresystem",
+ "version": "0.0.1",
+ "description": "A Quasar Project",
+ "productName": "Baggi SS",
+ "author": "MehmetKececi",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "test": "echo \"No test specified\" && exit 0",
+ "dev": "quasar dev",
+ "build": "quasar build",
+ "postinstall": "quasar prepare"
+ },
+ "dependencies": {
+ "@quasar/extras": "^1.16.4",
+ "axios": "^1.12.2",
+ "core-js": "^3.31.1",
+ "dayjs": "^1.11.18",
+ "pinia": "^3.0.1",
+ "quasar": "^2.16.0",
+ "vue": "3.5.20",
+ "vue-router": "^4.0.12"
+ },
+ "devDependencies": {
+ "@quasar/app-webpack": "^4.1.0",
+ "autoprefixer": "^10.4.2"
+ },
+ "browserslist": [
+ "last 10 Chrome versions",
+ "last 10 Firefox versions",
+ "last 4 Edge versions",
+ "last 7 Safari versions",
+ "last 8 Android versions",
+ "last 8 ChromeAndroid versions",
+ "last 8 FirefoxAndroid versions",
+ "last 10 iOS versions",
+ "last 5 Opera versions"
+ ],
+ "engines": {
+ "node": "^28 || ^26 || ^24 || ^22 || ^20 || ^18",
+ "npm": ">= 6.13.4",
+ "yarn": ">= 1.21.1"
+ }
+}
diff --git a/ui/postcss.config.js b/ui/postcss.config.js
new file mode 100644
index 0000000..513a8fd
--- /dev/null
+++ b/ui/postcss.config.js
@@ -0,0 +1,9 @@
+// https://github.com/michael-ciniawsky/postcss-load-config
+import autoprefixer from 'autoprefixer'
+
+export default {
+ plugins: [
+ // to edit target browsers: use "browserslist" field in package.json
+ autoprefixer
+ ]
+}
diff --git a/ui/public/favicon.ico b/ui/public/favicon.ico
new file mode 100644
index 0000000..580e207
Binary files /dev/null and b/ui/public/favicon.ico differ
diff --git a/ui/public/icons/baggi-icon.png b/ui/public/icons/baggi-icon.png
new file mode 100644
index 0000000..2584b73
Binary files /dev/null and b/ui/public/icons/baggi-icon.png differ
diff --git a/ui/public/icons/favicon-128x128.png b/ui/public/icons/favicon-128x128.png
new file mode 100644
index 0000000..a7acfdf
Binary files /dev/null and b/ui/public/icons/favicon-128x128.png differ
diff --git a/ui/public/icons/favicon-16x16.png b/ui/public/icons/favicon-16x16.png
new file mode 100644
index 0000000..e401497
Binary files /dev/null and b/ui/public/icons/favicon-16x16.png differ
diff --git a/ui/public/icons/favicon-32x32.png b/ui/public/icons/favicon-32x32.png
new file mode 100644
index 0000000..5940268
Binary files /dev/null and b/ui/public/icons/favicon-32x32.png differ
diff --git a/ui/public/icons/favicon-96x96.png b/ui/public/icons/favicon-96x96.png
new file mode 100644
index 0000000..0245b4b
Binary files /dev/null and b/ui/public/icons/favicon-96x96.png differ
diff --git a/ui/public/images/Baggi-Exclusive-Logo.jpg b/ui/public/images/Baggi-Exclusive-Logo.jpg
new file mode 100644
index 0000000..19694f1
Binary files /dev/null and b/ui/public/images/Baggi-Exclusive-Logo.jpg differ
diff --git a/ui/public/images/Baggi-Fabrika-resmi.jpg b/ui/public/images/Baggi-Fabrika-resmi.jpg
new file mode 100644
index 0000000..07b80b6
Binary files /dev/null and b/ui/public/images/Baggi-Fabrika-resmi.jpg differ
diff --git a/ui/public/images/Baggi-tekstilas-logolu.jpg b/ui/public/images/Baggi-tekstilas-logolu.jpg
new file mode 100644
index 0000000..8742b5d
Binary files /dev/null and b/ui/public/images/Baggi-tekstilas-logolu.jpg differ
diff --git a/ui/quasar.config.js b/ui/quasar.config.js
new file mode 100644
index 0000000..520371a
--- /dev/null
+++ b/ui/quasar.config.js
@@ -0,0 +1,78 @@
+// quasar.config.js
+import { defineConfig } from '#q-app/wrappers'
+
+export 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: 'http://localhost:8080/api'
+ },
+ esbuildTarget: {
+ browser: ['es2022', 'firefox115', 'chrome115', 'safari14'],
+ node: 'node20'
+ }
+ },
+
+ // 🔹 Geliştirme Sunucusu
+ devServer: {
+ server: { type: 'http' },
+ port: 9000,
+ open: true
+ },
+
+ // 🔹 Quasar Framework ayarları
+ framework: {
+ config: {
+ notify: { position: 'top', timeout: 2500 }
+ },
+ lang: 'tr',
+ plugins: ['Loading', 'Dialog', 'Notify']
+ },
+
+ animations: [],
+
+ ssr: {
+ prodPort: 3000,
+ middlewares: ['render'],
+ pwa: false
+ },
+
+ pwa: {
+ workboxMode: 'GenerateSW'
+ },
+
+ capacitor: {
+ hideSplashscreen: true
+ },
+
+ electron: {
+ preloadScripts: ['electron-preload'],
+ inspectPort: 5858,
+ bundler: 'packager',
+ builder: { appId: 'baggisowtfaresystem' }
+ },
+
+ bex: {
+ extraScripts: []
+ }
+ }
+})
diff --git a/ui/quasar.config.js.temporary.compiled.1770820662971.mjs b/ui/quasar.config.js.temporary.compiled.1770820662971.mjs
new file mode 100644
index 0000000..b4f411c
--- /dev/null
+++ b/ui/quasar.config.js.temporary.compiled.1770820662971.mjs
@@ -0,0 +1,83 @@
+/* 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: "http://localhost:8080/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
+};
diff --git a/ui/src/App.vue b/ui/src/App.vue
new file mode 100644
index 0000000..b22f395
--- /dev/null
+++ b/ui/src/App.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/ui/src/assets/quasar-logo-vertical.svg b/ui/src/assets/quasar-logo-vertical.svg
new file mode 100644
index 0000000..8210831
--- /dev/null
+++ b/ui/src/assets/quasar-logo-vertical.svg
@@ -0,0 +1,15 @@
+
\ No newline at end of file
diff --git a/ui/src/boot/.gitkeep b/ui/src/boot/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/ui/src/boot/axios.js b/ui/src/boot/axios.js
new file mode 100644
index 0000000..400dbfd
--- /dev/null
+++ b/ui/src/boot/axios.js
@@ -0,0 +1,21 @@
+import { boot } from 'quasar/wrappers'
+import axios from 'axios'
+
+export const api = axios.create({
+ baseURL: 'http://localhost:8080/api',
+ timeout: 180000,
+ withCredentials: true // refresh cookie kullanıyorsan kalsın
+})
+
+export default boot(() => {
+ api.interceptors.request.use((config) => {
+ const token = localStorage.getItem('token') // ✅ senin authStore key’in
+
+ if (token) {
+ config.headers = config.headers || {}
+ config.headers.Authorization = `Bearer ${token}`
+ }
+
+ return config
+ })
+})
diff --git a/ui/src/boot/dayjs.js b/ui/src/boot/dayjs.js
new file mode 100644
index 0000000..ba3697e
--- /dev/null
+++ b/ui/src/boot/dayjs.js
@@ -0,0 +1,14 @@
+// src/boot/dayjs.js
+import dayjs from 'dayjs'
+import customParseFormat from 'dayjs/plugin/customParseFormat.js'
+import relativeTime from 'dayjs/plugin/relativeTime.js'
+import localizedFormat from 'dayjs/plugin/localizedFormat.js'
+import 'dayjs/locale/tr.js'
+
+// 🔹 Plugin’leri aktif et
+dayjs.extend(customParseFormat)
+dayjs.extend(relativeTime)
+dayjs.extend(localizedFormat)
+dayjs.locale('tr')
+
+export default dayjs
diff --git a/ui/src/components/EssentialLink.vue b/ui/src/components/EssentialLink.vue
new file mode 100644
index 0000000..54afb06
--- /dev/null
+++ b/ui/src/components/EssentialLink.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+ {{ props.title }}
+ {{ props.caption }}
+
+
+
+
+
diff --git a/ui/src/composables/usePermission.js b/ui/src/composables/usePermission.js
new file mode 100644
index 0000000..a17fd33
--- /dev/null
+++ b/ui/src/composables/usePermission.js
@@ -0,0 +1,20 @@
+import { computed } from 'vue'
+import { usePermissionStore } from 'stores/permissionStore'
+
+export function usePermission () {
+
+ const perm = usePermissionStore()
+
+ return {
+
+ canRead: (m) => computed(() => perm.hasPermission(m, 'read')),
+ canWrite: (m) => computed(() => perm.hasPermission(m, 'write')),
+ canUpdate:(m) => computed(() => perm.hasPermission(m, 'update')),
+ canDelete:(m) => computed(() => perm.hasPermission(m, 'delete')),
+ canExport:(m) => computed(() => perm.hasPermission(m, 'export')),
+
+ canApi: (p) => computed(() => perm.hasApiPermission(p)),
+
+ hasModule: (m) => computed(() => perm.hasModule(m))
+ }
+}
diff --git a/ui/src/css/app.css b/ui/src/css/app.css
new file mode 100644
index 0000000..2a742bc
--- /dev/null
+++ b/ui/src/css/app.css
@@ -0,0 +1,1532 @@
+/* ===========================================================
+ GLOBAL CUSTOM CSS
+ =========================================================== */
+ .with-bg {
+ position: relative;
+ min-height: 100%;
+}
+.with-bg::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background: url('/images/Baggi-tekstilas-logolu.jpg') no-repeat center top;
+ background-size: 400px auto;
+ opacity: 0.15;
+ pointer-events: none;
+ 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;
+ }
+}
+
+/* ===== ÜST BLOKLAR (SABİT) ===== */
+.filter-sticky {
+ position: sticky;
+ top: 56px; /* q-header yüksekliği */
+ z-index: 300;
+ background: #fff;
+}
+
+.filter-collapsible {
+ background: #fff;
+}
+
+/* ===== TABLO SCROLL ===== */
+.table-scroll {
+ margin-top: 0; /* 🔹 Boşluğu kaldır */
+ height: calc(100vh - 56px); /* 🔹 Header yüksekliği kadar kısalt */
+ overflow-y: auto;
+ overflow-x: auto;
+ position: relative;
+}
+
+.sticky-table .q-table__middle {
+ overflow: visible !important;
+ max-height: none !important;
+}
+
+.sticky-table .q-table__top {
+ position: sticky;
+ top: 0;
+ z-index: 220;
+ background: #fff;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.08);
+}
+
+.sticky-table thead th {
+ position: sticky;
+ top: 40px;
+ z-index: 210;
+ background: #fff;
+}
+
+/* 🔹 Toggle bar */
+.sticky-bar {
+ position: sticky;
+ top: 0; /* tablo scroll başladığında en üstte kalsın */
+ z-index: 230;
+ background: #fff;
+ padding: 4px 8px;
+ border-bottom: 1px solid #ddd;
+}
+
+/* ===== KOLON DARALTMA + WRAP ===== */
+.sticky-table thead th {
+ resize: horizontal;
+ overflow: auto;
+ min-width: 80px;
+ max-width: 400px;
+}
+
+.sticky-table td {
+ min-width: 80px;
+ max-width: 400px;
+ white-space: normal !important;
+ word-break: break-word !important;
+ overflow-wrap: break-word !important;
+ line-height: 1.2rem;
+ padding: 4px 8px !important;
+ font-weight: 600;
+ font-size: 0.95rem;
+}
+
+/* ===== GÖRSEL ===== */
+.baggi-ppct {
+ display: block;
+ margin: 30px auto 0;
+ max-width: 400px;
+ opacity: 0.4;
+}
+
+.col-desc {
+ white-space: normal !important;
+ word-break: break-word !important;
+ overflow-wrap: break-word;
+ font-size: 0.75rem !important;
+ line-height: 1.1rem;
+ width: 220px !important;
+ max-width: 220px !important;
+ min-width: 180px !important;
+}
+
+/* ===== TABLO GÖRÜNÜM ===== */
+.custom-table { font-size: 0.8rem; }
+.custom-table th { background: #fff; font-weight: 800; color: #222; }
+.custom-table td { font-weight: 600; color: #333; }
+
+.custom-subtable { font-size: 0.72rem; background: #fafafa; }
+.custom-subtable th { background: #f9f9f9; font-weight: 500; color: #555; }
+.custom-subtable td { font-weight: 400; color: #666; }
+
+/* dar sütunlar için */
+.col-narrow {
+ font-size: 0.72rem;
+ padding: 2px 6px !important;
+ max-width: 90px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/* ===== GRUP SATIRI ===== */
+.group-row {
+ background: #f1f1f1 !important;
+ font-weight: 700 !important;
+ color: #222;
+ border-top: 2px solid #ccc;
+ border-bottom: 2px solid #ccc;
+}
+
+/* ===== BALANCE CARD ===== */
+.balance-card {
+ width: 100%;
+ min-height: 120px;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.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 PAGE (FINAL)
+ =========================================================== */
+
+/* Toolbar */
+.permissions-toolbar {
+ position: sticky;
+ top: 42px; /* q-header yüksekliği */
+ z-index: 300;
+ background: #fff;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 8px 16px;
+ border-bottom: 1px solid #ddd;
+}
+
+/* Table scroll alanı */
+.permissions-table-scroll {
+ height: calc(100vh - 112px); /* header (56) + toolbar (56) */
+ overflow-y: auto;
+ overflow-x: auto;
+ position: relative;
+}
+
+/* Tablo gövdesi */
+.permissions-table .q-table__middle {
+ overflow: auto !important;
+ max-height: none !important;
+ padding-top: 0px; /* 🔑 Başlık yüksekliği kadar boşluk bırak */
+}
+
+/* Sticky başlıklar – toolbar’ın altında */
+.permissions-table thead th {
+ position: sticky;
+ top:10px; /* toolbar altında hizalanır */
+ z-index: 210;
+ background: #fff;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.08);
+}
+
+/* Hücreler */
+.permissions-table td {
+ min-width: 80px;
+ max-width: 400px;
+ white-space: normal !important;
+ word-break: break-word !important;
+ overflow-wrap: break-word !important;
+ line-height: 1.2rem;
+ padding: 4px 8px !important;
+ font-weight: 600;
+ font-size: 0.95rem;
+ background: #fff;
+}
+
+/* İlk kolon (role) sabit */
+.permissions-table .permissions-sticky-col {
+ position: sticky;
+ left: 0;
+ z-index: 205;
+ background: #fff;
+ box-shadow: 2px 0 4px rgba(0,0,0,0.04);
+}
+
+/* ===========================================================
+ 1️⃣ ROOT & GLOBAL RESET
+ =========================================================== */
+:root {
+ --header-h: 0px;
+ --filter-h: 72px;
+ --save-h: 60px;
+ --grid-header-h: 172px;
+ --sub-header-h: 34px;
+ --drawer-w: 240px;
+
+ /* Grid kolon genişlikleri */
+ --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;
+ --col-termin: 142px; /* 🔹 termin tarihi kolon genişliği */
+
+ /* Beden blok ölçüleri */
+ --grp-title-w: 90px;
+ --grp-title-gap: 4px;
+ --beden-w: 44px;
+ --beden-h: 28px;
+ --beden-count: 16;
+
+ /* Tema renkleri */
+ --baggi-gold: #c9a227;
+ --baggi-gold-pale: #fff9e6;
+ --baggi-gold-light: #fff7d2;
+ --baggi-cream: #fffef9;
+ --baggi-gray-border: #bbb;
+}
+
+*, *::before, *::after { box-sizing: border-box; }
+html, body { 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; }
+/* ===========================================================
+ 2️⃣ PAGE STRUCTURE & SCROLL
+ =========================================================== */
+.order-page {
+ display: flex;
+ flex-direction: column;
+ height: calc(100vh - var(--header-h));
+ overflow-y: auto;
+ overflow-x: visible;
+ background: #fff;
+}
+
+.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%;
+}
+
+/* 🔸 Yatay scroll sadece grid alanında */
+.order-scroll-x {
+ flex: 1;
+ overflow-x: auto;
+ overflow-y: visible;
+ background: #fff;
+}
+/* 🔸 Scrollbar stili */
+
+.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;
+}
+
+/* ===========================================================
+ 3️⃣ STICKY STACK (HEADER + TOOLBARS)
+ =========================================================== */
+.q-header {
+ position: sticky;
+ top: 0;
+ z-index: 1000;
+ box-shadow: 0 1px 2px rgba(0,0,0,0.08);
+}
+
+.sticky-stack {
+ position: sticky;
+ top: var(--header-h);
+ margin-top: 0 !important;
+ z-index: 950;
+ display: flex;
+ flex-direction: column;
+ background: #fff;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.05);
+}
+
+/* 🔹 Filtre bar */
+.filter-bar {
+ background: #fafafa;
+ border-bottom: 1px solid #ddd;
+ padding: 12px 24px;
+ margin-top:0 !important;
+}
+
+/* 🔹 Save toolbar */
+.save-toolbar {
+ background: var(--baggi-gold-pale);
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ padding: 10px 16px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ z-index: 940;
+}
+.save-toolbar .label { font-weight: 700; color: #6a5314; }
+.save-toolbar .value { font-weight: 700; color: #000; }
+.save-toolbar .q-btn {
+ font-weight: 600;
+ border-radius: 6px;
+ text-transform: none;
+}
+/* ===========================================================
+ 4️⃣ GRID HEADER (ANA BAŞLIK BLOKU)
+ =========================================================== */
+.order-grid-header {
+ position: sticky;
+ top: calc(var(--header-h) + var(--filter-h) + var(--save-h));
+ z-index: 700;
+ 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);
+ background: var(--baggi-cream);
+ border-bottom: 2px solid var(--baggi-gray-border);
+ box-shadow: 0 2px 3px rgba(0,0,0,0.05);
+}
+
+/* Sabit kolonlar */
+.order-grid-header .col-fixed {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ writing-mode: vertical-lr;
+ transform: rotate(180deg);
+ background: var(--baggi-gold-light);
+ border: 1px solid #aaa;
+ font-weight: 700;
+ font-size: 12.5px;
+ height: var(--grid-header-h);
+}
+
+.order-grid-header .aciklama-col {
+ background: #fff9c4;
+ border-right: 2px solid #a6a6a6;
+}
+/* ===========================================================
+ 5️⃣ BEDEN BLOKLARI & SAĞ TOPLAM
+ =========================================================== */
+.order-grid-header .beden-block {
+ display: flex;
+ flex-direction: column;
+ height: var(--grid-header-h);
+ background: #fff;
+ border: 1px solid #ccc;
+}
+
+.order-grid-header .grp-row {
+ display: flex;
+ align-items: center;
+ height: var(--beden-h);
+}
+
+.order-grid-header .grp-title {
+ width: var(--grp-title-w);
+ text-align: right;
+ font-weight: 700;
+ font-size: 12px;
+ padding-right: 4px;
+}
+
+.order-grid-header .grp-body {
+ display: grid;
+ grid-auto-flow: column;
+ grid-auto-columns: var(--beden-w);
+}
+.order-grid-header .grp-cell.hdr {
+ width: var(--beden-w);
+ height: var(--beden-h);
+ border: 1px solid #bbb;
+ font-size: 11.5px;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.order-grid-header .total-row {
+ display: flex;
+ align-items: stretch;
+ justify-content: space-between;
+ background: #fff59d;
+}
+.order-grid-header .total-cell {
+ width: var(--col-adet);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ writing-mode: vertical-lr;
+ transform: rotate(180deg);
+ border-right: 1px solid #bbb;
+ background: var(--baggi-gold-pale);
+ font-weight: 700;
+ font-size: 12px;
+}
+/* ===========================================================
+ 6️⃣ SUB-HEADER (ÜRÜN GRUBU BAR) — TAM HİZALANMIŞ
+ =========================================================== */
+ .order-sub-header {
+ padding-right: 0 !important; /* 🔹 Ekstra sağ boşluğu kaldır */
+ margin-right: 0 !important;
+}
+.order-sub-header {
+ position: sticky;
+ top: calc(
+ var(--header-h)
+ + var(--filter-h)
+ + var(--save-h)
+ + var(--grid-header-h)
+ );
+ z-index: 650;
+
+ /* 🔹 Header ile birebir grid düzeni */
+ 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);
+
+ align-items: center;
+ justify-items: stretch;
+ height: var(--sub-header-h);
+ min-height: var(--sub-header-h);
+
+ /* 🔹 Görsel */
+ background: linear-gradient(90deg, #fffbe9 0%, #fff4c4 50%, #fff1b0 100%);
+ border-top: 1px solid #d6c06a;
+ border-bottom: 1px solid #d6c06a;
+
+ /* 🔹 Hatalı hizalamaları engelle */
+ box-sizing: border-box;
+ overflow: hidden;
+ padding: 0 !important;
+ margin: 0 !important;
+ padding-right: 0 !important; /* ✅ sağ taşmayı önler */
+}
+
+/* 🔹 Genişlik eşitleme */
+:root {
+ --col-termin: 142px; /* ✅ q-input genişliğiyle birebir */
+}
+
+/* 🔹 Sub-header hover efekti */
+.order-sub-header:hover {
+ background: linear-gradient(90deg, #fff9cf 0%, #fff3b0 70%, #ffe88f 100%);
+}
+
+
+/* 🔹 Sol taraf (MODEL–AÇIKLAMA alanı) */
+.order-sub-header .sub-left {
+ grid-column: 1 / span 5;
+ font-weight: 800;
+ padding-left: 6px;
+ color: #2b1f05;
+ display: flex;
+ align-items: center;
+}
+
+/* 🔹 Orta beden bloğu (header’la aynı yapı) */
+.order-sub-header .sub-center {
+ grid-column: 6 / 7;
+ display: grid;
+ grid-auto-flow: column;
+ grid-auto-columns: var(--beden-w);
+ justify-content: start;
+ align-items: center;
+ width: calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w) * var(--beden-count)));
+ padding-left: var(--grp-title-w);
+ margin-left: var(--grp-title-gap);
+ height: 100%;
+ box-sizing: border-box;
+}
+
+.order-sub-header .beden-cell {
+ width: var(--beden-w);
+ height: 100%;
+ border: 1px solid #d8c16b;
+ border-right: none;
+ background: #fffdf3;
+ font-size: 12px;
+ font-weight: 600;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ box-sizing: border-box;
+}
+.order-sub-header .beden-cell:last-child {
+ border-right: 1px solid #d8c16b;
+}
+
+/* 🔹 Sağ taraf (adet–fiyat–pb–tutar–termin toplamları) */
+.order-sub-header .sub-right {
+ grid-column: 7 / -1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: flex-end;
+ text-align: right;
+ padding-right: 0px;
+ font-weight: 900;
+ color: #3b2f09;
+ line-height: 1.3;
+ text-transform: uppercase;
+ font-size: 13.5px;
+ transform: translateX(-60px);
+}
+
+.order-sub-header:hover {
+ background: linear-gradient(90deg,#fff9cf 0%,#fff3b0 70%,#ffe88f 100%);
+}
+
+:root {
+ --sub-header-h: 60px;
+}
+
+
+/* Taşmayı engelle (ihtiyaten) */
+.order-sub-header {
+ overflow: hidden;
+}
+
+/* ===========================================================
+ 7️⃣ GRID BODY & SATIRLAR — TAM HİZALANMIŞ
+ =========================================================== */
+.order-grid-body {
+ position: relative;
+ background: #fff;
+ margin-top: 0 !important;
+ padding-top: var(--sub-header-h);
+ 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:hover {
+ background: #fffce0;
+}
+.summary-row.is-editing {
+ background: #fff3cd;
+ outline: 2px solid #caa83f;
+}
+
+.summary-row .cell {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: var(--beden-h);
+ padding: 4px 6px;
+ font-size: 13px;
+ color: #222;
+ box-sizing: border-box;
+}
+.summary-row.row-closed {
+ opacity: 0.55;
+ background: #f5f5f5;
+ pointer-events: none;
+}
+.summary-row.row-closed:hover {
+ background: #f5f5f5 !important;
+}
+
+.summary-row:nth-child(odd) { background: #fffef9; }
+
+/* 🔹 Beden blok hizalaması */
+.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-flow: column;
+ grid-auto-columns: var(--beden-w);
+}
+.summary-row .grp-row .cell.beden {
+ width: var(--beden-w);
+ height: var(--beden-h);
+ border: 1px solid #ddd;
+ font-size: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.cell.beden.ghost {
+ opacity: 0;
+ pointer-events: none;
+ border: 1px solid transparent !important;
+}
+
+/* 🔹 Sağ kolonlar */
+.summary-row .cell.adet,
+.summary-row .cell.fiyat,
+.summary-row .cell.pb,
+.summary-row .cell.tutar,
+.summary-row .cell.termin {
+ font-weight: 600;
+ color: #000;
+ border-left: none !important;
+ height: 100%;
+}
+
+.summary-row .cell.tutar {
+ text-align: right;
+ justify-content: flex-end;
+ padding-right: 8px;
+ border-right: none !important;
+}
+
+.summary-row .cell.termin {
+ background: #fffef9;
+ justify-content: center;
+ align-items: center;
+ min-width: var(--col-termin);
+}
+.summary-row .cell.termin .q-input {
+ width: 100%;
+ max-width: 142px !important;
+ box-sizing: border-box;
+}
+.summary-row .cell.termin input {
+ text-align: center;
+ font-size: 13px;
+}
+
+/* ===========================================================
+ 9️⃣ ORDER EDITOR (ALT FORM)
+ =========================================================== */
+.editor {
+ position: relative;
+ z-index: 50;
+ background: #fffef9;
+ border-top: 1px solid #ddd;
+ margin-top: 24px;
+ padding: 16px;
+}
+.editor::before {
+ content: "";
+ display: block;
+ height: 4px;
+ background: linear-gradient(to right,#c9a227,#e5d28b,#fff7d2);
+ margin-bottom: 12px;
+ border-radius: 2px;
+}
+.editor .q-btn:hover { background: #d2b04d; }
+.editor .q-input,
+.editor .q-select { margin-bottom: 8px; font-size: 14px; }
+.cell.termin .termin-label {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 600;
+ font-size: 13px;
+ color: #222;
+ background: #fffef9;
+ border-left: 1px solid #ccc;
+ box-sizing: border-box;
+}
+
+/* ===========================================================
+ 🔟 RESPONSIVE + MİNÖR DÜZEN
+ =========================================================== */
+@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 {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 4px 6px;
+ height: auto;
+ 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 {
+ grid-column: 5 / 6 !important; /* sadece 5. kolon */
+ position: relative !important;
+ width: calc(var(--col-aciklama) + 92px) !important; /* 🔹 74px genişletme */
+ margin-right: -92px !important; /* 🔹 bedenle tam hizalanır */
+ white-space: normal !important;
+ word-break: break-word !important;
+ overflow-wrap: break-word !important;
+ line-height: 1.4 !important;
+ padding: 6px 12px !important;
+ font-size: 13px !important;
+ text-align: left !important;
+ display: flex !important;
+ flex-direction: column !important;
+ align-items: flex-start !important;
+ justify-content: flex-start !important;
+ min-height: 36px !important;
+ background: #fff !important;
+ box-sizing: border-box !important;
+ border-right: 1px solid #ccc !important;
+ z-index: 10 !important;
+}
+/* 🧩 Grid çizgi kontrastı güçlendirme */
+.summary-row .cell,
+.order-grid-header .col-fixed,
+.summary-row .grp-row .cell.beden {
+ border-color: #bbb !important; /* 🔹 daha belirgin çizgi */
+}
+
+.summary-row .cell:not(:last-child) {
+ border-right: 1px solid #bdbdbd !important;
+}
+/* ===========================================================
+ 🧱 ALT GRID ÇİZGİLERİ – TÜM SATIRLAR İÇİN
+ =========================================================== */
+.summary-row {
+ border-bottom: 1px solid #ccc; /* 🔹 satır alt çizgisi */
+}
+
+.summary-row:last-child {
+ border-bottom: 2px solid #b7a33a; /* 🔹 son satırda Baggi gold tonu */
+}
+
+/* 🔹 Hücrelerin alt çizgisi (beden dahil) */
+.summary-row .cell,
+.summary-row .grp-row .cell.beden {
+ border-bottom: 1px solid #ddd !important;
+}
+
+/* 🔹 Hover olduğunda grid çizgileri kaybolmasın */
+.summary-row:hover .cell,
+.summary-row:hover .grp-row .cell.beden {
+ border-bottom: 1px solid #ccc !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;
+}
+
+/* 🔹 Hover olduğunda grid çizgileri kaybolmasın */
+.summary-row:hover .cell,
+.summary-row:hover .grp-row .cell.beden {
+ border-bottom: 1px solid #ccc !important;
+}
+
+/* ===========================================================
+ 🎨 STOK RENKLERİ (LOW–MID–HIGH)
+ =========================================================== */
+.stok-red {
+ color: #e53935; /* 🔴 Kırmızı */
+ font-weight: 600;
+}
+
+.stok-yellow {
+ color: #f9a825; /* 🟡 Sarı */
+ font-weight: 600;
+}
+
+.stok-green {
+ color: #43a047; /* 🟢 Yeşil */
+ font-weight: 600;
+}
+.q-banner.rounded-borders {
+ border-radius: 8px;
+}
+.order-gateway {
+ background: linear-gradient(145deg, #fff 0%, #fafafa 100%);
+ height: 100%;
+}
+
+.order-btn {
+ font-size: 1.2rem;
+ padding: 20px 40px;
+ border-radius: 12px;
+ min-width: 280px;
+ transition: all 0.2s ease;
+}
+.order-btn:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+/* ===========================================================
+ 🧭 DRAWER AÇIKKEN GRID HİZALAMA FIX
+ =========================================================== */
+
+/* Drawer açıkken içerik kaymaması */
+.body--drawer-left-open .order-page {
+ width: calc(100vw - var(--drawer-w)); /* viewport'tan drawer genişliği kadar düş */
+ overflow-x: visible; /* dış overflow’u kes */
+}
+
+/* Scroll konteyner sadece grid içinde çalışsın */
+.order-scroll-x {
+ max-width: 100%;
+ overflow-x: auto;
+ overflow-y: visible;
+ background: #fff;
+ box-sizing: border-box;
+}
+
+/* Scrollbar ve sağ boşluğu dengeler */
+.order-grid-header,
+.order-sub-header,
+.order-grid-body {
+ min-width: fit-content;
+ width: 100%;
+ box-sizing: border-box;
+}
+/* ===========================================================
+ 🧱 DRAWER AÇIKKEN TAM HİZALAMA FIX (v2)
+ =========================================================== */
+
+/* Drawer açıkken tüm üst bloklar sağdan taşmasın */
+.body--drawer-left-open .filter-bar,
+.body--drawer-left-open .save-toolbar,
+.body--drawer-left-open .order-grid-header,
+.body--drawer-left-open .order-sub-header,
+.body--drawer-left-open .order-grid-body {
+ width: calc(100vw - var(--drawer-w)); /* drawer genişliği kadar daralt */
+ margin-left: 0;
+ margin-right: 0;
+ overflow-x: hidden;
+ box-sizing: border-box;
+}
+
+/* Drawer kapalıyken tam genişlik */
+.body--drawer-left-closed .filter-bar,
+.body--drawer-left-closed .save-toolbar,
+.body--drawer-left-closed .order-grid-header,
+.body--drawer-left-closed .order-sub-header,
+.body--drawer-left-closed .order-grid-body {
+ width: 100vw;
+}
+
+/* Order grid sağ sınırı altın kenarlıkla bitir (optik kapanış) */
+.order-grid-header,
+.order-sub-header,
+.order-grid-body {
+ border-right: 2px solid var(--baggi-gold);
+}
+/* ===========================================================
+ 🎯 SAĞ ALT BOŞLUK FİNAL FIX
+ =========================================================== */
+
+/* Drawer açıkken tüm grid konteynerleri sağdan tam sıfırla */
+.body--drawer-left-open .order-page,
+.body--drawer-left-open .filter-bar,
+.body--drawer-left-open .save-toolbar,
+.body--drawer-left-open .order-grid-header,
+.body--drawer-left-open .order-sub-header,
+.body--drawer-left-open .order-grid-body {
+ width: calc(100vw - var(--drawer-w) - 8px); /* 🔹 scrollbar toleransı */
+ padding-right: 0 !important;
+ margin-right: 0 !important;
+ overflow-x: visible !important;
+}
+
+/* Son altın kenarlık hizasını koru */
+.order-grid-body {
+ border-right: 2px solid var(--baggi-gold);
+}
+/* ===========================================================
+ 🎯 GRID SAĞ HİZALAMA (FILTER + SAVE + HEADER)
+ =========================================================== */
+
+/* Ana scroll container referansı */
+.order-scroll-x {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start; /* hizalama sola */
+ overflow-x: auto;
+ overflow-y: visible;
+ background: #fff;
+}
+
+/* Filter ve Save barlar grid genişliğini takip etsin */
+.filter-bar,
+.save-toolbar,
+.order-grid-header,
+.order-sub-header {
+ width: fit-content; /* içeriğe göre genişlik */
+ min-width: 100%; /* minimum ekran kadar */
+ box-sizing: border-box;
+}
+
+/* Grid body’nin genişliği kadar sağ hizalama */
+.order-grid-body {
+ width: fit-content;
+ box-sizing: border-box;
+}
+/* 🔒 Kapalı satır */
+.summary-row.row-closed {
+ background: #e6e6e6 !important;
+ opacity: 0.65;
+ pointer-events: none; /* tüm inputları disable eder */
+}
+
+/* Hover iptal */
+.summary-row.row-closed:hover {
+ background: #e6e6e6 !important;
+}
+
+/* Edit efekti de kapansın */
+.summary-row.row-closed.is-editing {
+ outline: none !important;
+}
+
+/* Sağ kenarda taşma veya padding olmasın */
+.filter-bar,
+.save-toolbar,
+.order-grid-header,
+.order-sub-header,
+.order-grid-body {
+ margin-right: 0 !important;
+ padding-right: 0 !important;
+ border-right: none !important; /* altın çizgi istemiyorsan kaldırılır */
+}
+.summary-row.row-error {
+ background: rgba(193, 0, 21, 0.08);
+}
+
+.row-error-icon {
+ position: absolute;
+ left: 4px;
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+
+/* Drawer açık/kapalı fark etmeden */
+.body--drawer-left-open .order-scroll-x,
+.body--drawer-left-closed .order-scroll-x {
+ width: 100%;
+ overflow-x: auto;
+}
+
+/* ===============================
+ ORDER LIST (ol-) — Sticky Stack
+ =============================== */
+
+:root {
+ /* Quasar header yüksekliği */
+ --ol-header-h: 56px;
+ /* Filter bar yüksekliği (px) — inputlar tek satırsa 56 idealdir */
+ --ol-filter-h: 96px;
+}
+
+/* q-page tek scroller: header altından başlar */
+.ol-page {
+ height: calc(100vh - var(--ol-header-h));
+ overflow: auto; /* 🔑 tek scroll container */
+ background: #fff;
+ display: flex;
+ flex-direction: column;
+}
+
+/* Filter bar: q-header’ın altında sticky */
+.ol-filter-bar {
+ position: sticky;
+ top: 0; /* 🔑 .ol-page scroller’ında en üst */
+ z-index: 600;
+ background: #fff;
+ border-bottom: 1px solid #ddd;
+ padding: 10px 16px;
+ box-shadow: 0 1px 2px rgba(0,0,0,0.06);
+ min-height: var(--ol-filter-h);
+ display: flex;
+ align-items: center;
+}
+
+/* QTable: sticky thead, zebra aktif ve çakışma yok */
+.ol-table .q-table__middle {
+ overflow: visible !important; /* sticky thead için güvenli */
+ max-height: none !important;
+}
+
+/* thead sabitleme: filter bar’ın ALTINA oturur */
+.ol-table thead th {
+ position: sticky;
+ top: var(--ol-filter-h); /* 🔑 filter yüksekliği kadar boşluk */
+ z-index: 500;
+ background: #fff;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.08);
+ font-weight: 700;
+}
+
+/* Zebra */
+.ol-table .q-table__body .q-tr:nth-child(odd) {
+ background-color: #f7f7f7 !important;
+}
+.ol-table .q-table__body .q-tr:nth-child(even) {
+ background-color: #ffffff !important;
+}
+.ol-table .q-table__body .q-tr:hover {
+ background-color: #fff7d1 !important;
+ transition: background-color .15s ease;
+}
+
+/* Hücreler */
+.ol-table .q-td {
+ font-size: .9rem;
+ line-height: 1.3;
+ padding: 6px 8px !important;
+}
+
+/* Güvenli z-index hiyerarşisi */
+.q-header { z-index: 1000 !important; } /* header en üstte */
+.q-drawer { z-index: 950 !important; } /* drawer header’ın altında */
+
+/* Mobile */
+@media (max-width: 768px) {
+ :root { --ol-filter-h: 64px; } /* input kırılıyorsa biraz artır */
+ .ol-filter-bar { padding: 8px 12px; }
+}
+/* ===========================================================
+ 🟡 ORDERLIST ZEBRA FIX (v3)
+ =========================================================== */
+
+/* Her iki tr katmanını da hedefliyoruz (Quasar q-tr + native tr) */
+.ol-table tbody tr:nth-child(odd),
+.ol-table .q-table__body .q-tr:nth-child(odd) {
+ background-color: #faf8ef !important; /* açık krem tonu */
+}
+
+.ol-table tbody tr:nth-child(even),
+.ol-table .q-table__body .q-tr:nth-child(even) {
+ background-color: #ffffff !important;
+}
+
+/* Hover tonu: hafif Baggi gold dokunuşu */
+.ol-table tbody tr:hover,
+.ol-table .q-table__body .q-tr:hover {
+ background-color: #fff4cc !important;
+ transition: background-color 0.2s ease;
+}
+/* =========================================================
+ 📌 OrderList — Toplam USD Banner
+ ========================================================= */
+.ol-qbanner {
+ background: #f9fafb;
+ border: 1px solid #e0e0e0;
+ border-radius: 6px;
+ padding: 8px 12px;
+}
+
+.ol-qbanner-amount {
+ color: #1976d2; /* Quasar primary */
+ margin-left: 6px;
+}
+/* =========================================================
+ 📌 ORDER LIST (ol-) — MULTILINE COLUMN FIX
+ ========================================================= */
+
+/* Ortak çok satırlı davranış */
+.ol-col-multiline {
+ white-space: normal !important;
+ overflow: hidden;
+ word-break: break-word;
+ line-height: 1.25rem;
+}
+
+/* 🧾 Cari Adı — 2 SATIR */
+.ol-col-cari {
+ max-width: 200px;
+ min-width: 150px;
+
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+
+ font-weight: 600;
+ font-size: 0.88rem;
+}
+
+/* 📝 Açıklama — 5 SATIR */
+.ol-col-desc {
+ max-width: 220px;
+ min-width: 160px;
+
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+
+ font-size: 0.82rem;
+ color: #444;
+}
+
+/* Hücre padding iyileştirme */
+.ol-table .q-td.ol-col-cari,
+.ol-table .q-td.ol-col-desc {
+ padding-top: 6px !important;
+ padding-bottom: 6px !important;
+}
+
+/* Header hizalama */
+.ol-table th.ol-col-cari,
+.ol-table th.ol-col-desc {
+ white-space: nowrap;
+}
+/* =========================================================
+ ORDER LIST — FILTER BAR FLEX FIX (FINAL)
+ ========================================================= */
+
+.ol-filter-bar {
+ position: sticky;
+ top: 0;
+ z-index: 600;
+ background: #fff;
+ border-bottom: 1px solid #ddd;
+ padding: 10px 12px;
+}
+
+/* 🔑 TEK SATIR */
+.ol-filter-row {
+ display: flex;
+ align-items: flex-end; /* 🔑 tüm input & butonlar aynı çizgi */
+ gap: 12px;
+ flex-wrap: nowrap; /* ❌ alt satıra düşme */
+}
+
+/* Inputlar */
+.ol-filter-input {
+ min-width: 180px;
+ max-width: 260px;
+}
+
+/* Arama biraz geniş */
+.ol-search {
+ min-width: 280px;
+}
+
+/* Butonlar */
+.ol-filter-actions {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ white-space: nowrap;
+}
+
+/* Toplam banner */
+.ol-filter-total {
+ margin-left: auto; /* 🔑 sağa yasla */
+ background: #f9fafb;
+ border: 1px solid #e0e0e0;
+ border-radius: 6px;
+ padding: 8px 12px;
+ white-space: nowrap;
+}
+
+/* Mobile fallback */
+@media (max-width: 1200px) {
+ .ol-filter-row {
+ flex-wrap: wrap;
+ row-gap: 8px;
+ }
+
+ .ol-filter-total {
+ width: 100%;
+ justify-content: flex-end;
+ }
+}
+
+.order-gateway {
+ min-height: 100vh;
+}
+
+.draft-card {
+ width: 320px;
+ max-width: 90vw;
+}
+/* ===========================================================
+ 🕵️ ACTIVITY LOGS (act-) — GLOBAL
+ =========================================================== */
+
+/* Sayfa konteyneri */
+.act-page {
+ height: calc(100vh - 56px); /* q-header */
+ overflow: auto;
+ background: #fff;
+}
+
+/* Sticky filter bar */
+.act-filter-bar {
+ position: sticky;
+ top: 0;
+ z-index: 620;
+ background: #fff;
+ border-bottom: 1px solid #ddd;
+ padding: 10px 16px;
+ box-shadow: 0 1px 2px rgba(0,0,0,0.06);
+}
+
+/* Filtre satırı */
+.act-filter-row {
+ display: flex;
+ align-items: flex-end;
+ gap: 12px;
+ flex-wrap: nowrap;
+}
+
+/* Input genişlikleri */
+.act-filter-input {
+ min-width: 160px;
+ max-width: 240px;
+}
+
+.act-filter-wide {
+ min-width: 260px;
+}
+
+/* Aksiyon butonları */
+.act-filter-actions {
+ display: flex;
+ gap: 8px;
+ margin-left: auto;
+ white-space: nowrap;
+}
+
+/* Tablo */
+.act-table {
+ font-size: 0.85rem;
+}
+
+/* Sticky thead */
+.act-table thead th {
+ position: sticky;
+ top: 56px; /* filter bar yüksekliği */
+ z-index: 500;
+ background: #fff;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.08);
+ font-weight: 700;
+}
+
+/* Zebra */
+.act-table tbody tr:nth-child(odd) {
+ background: #faf8ef;
+}
+.act-table tbody tr:nth-child(even) {
+ background: #fff;
+}
+.act-table tbody tr:hover {
+ background: #fff4cc;
+}
+
+/* Hücreler */
+.act-table .q-td {
+ padding: 6px 8px !important;
+ line-height: 1.25;
+ font-weight: 600;
+}
+
+/* Başarılı / hatalı satır */
+.act-row-success {
+ background: rgba(67, 160, 71, 0.06);
+}
+.act-row-fail {
+ background: rgba(211, 47, 47, 0.06);
+}
+
+/* Badge renkleri */
+.act-badge-ok {
+ background: #43a047;
+}
+.act-badge-fail {
+ background: #e53935;
+}
+
+/* Dar kolon */
+.act-col-narrow {
+ max-width: 90px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/* Route kolonu */
+.act-col-route {
+ max-width: 260px;
+ white-space: normal;
+ word-break: break-word;
+ font-size: 0.8rem;
+}
+
+/* IP / UA */
+.act-col-meta {
+ font-size: 0.75rem;
+ color: #555;
+}
+/* ================================
+ UserList — Piyasa Wrap (NO CUT)
+ ================================ */
+
+/* Hücre yukarıdan başlasın */
+.ol-col-piyasa {
+ vertical-align: top;
+ padding-top: 6px !important;
+ padding-bottom: 6px !important;
+}
+
+/* Chip konteyner */
+.piyasa-wrap {
+ display: flex;
+ flex-wrap: wrap; /* 🔑 alt satıra insin */
+ align-content: flex-start;
+
+ row-gap: 4px;
+ column-gap: 6px;
+
+ /* ❌ KESİNLİKLE YOK */
+ max-height: none;
+ overflow: visible;
+}
+
+/* Chip */
+.piyasa-chip {
+ flex: 0 0 calc(25% - 6px); /* 🔑 satırda 4 adet */
+ max-width: calc(25% - 6px);
+
+ font-size: 11px;
+ font-weight: 600;
+ line-height: 1.1;
+
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.user-detail-page {
+ background: #fafafa;
+}
+.image-preview {
+ width: 100%;
+ border-radius: 6px;
+}
+
+.image-thumb {
+ width: 100%;
+ border-radius: 4px;
+}
+
+.workorder-page {
+ padding-bottom: 80px;
+}
+/* ===============================
+ PERMISSIONS — BAGGI THEME
+ =============================== */
+
+.permissions-page {
+ background: #fff;
+ height: calc(100vh - 56px);
+ display: flex;
+ flex-direction: column;
+}
+
+/* Scroll alanı */
+.permissions-table-scroll {
+ flex: 1;
+ overflow: auto;
+ background: #fff;
+}
+
+/* Tablo */
+.permissions-table {
+ font-size: 0.85rem;
+}
+
+/* Sticky header */
+.permissions-table thead th {
+ position: sticky;
+ top: 0;
+ z-index: 300;
+ background: var(--baggi-cream);
+ font-weight: 800;
+ color: #222;
+ box-shadow: 0 2px 3px rgba(0,0,0,0.06);
+}
+
+/* Hücreler */
+.permissions-table td {
+ font-weight: 600;
+ padding: 6px 8px !important;
+ color: #333;
+}
+
+/* Zebra */
+.permissions-table tbody tr:nth-child(odd) {
+ background: #fffef7;
+}
+.permissions-table tbody tr:nth-child(even) {
+ background: #ffffff;
+}
+.permissions-table tbody tr:hover {
+ background: #fff4cc;
+}
+
+/* Sol kolon sabit */
+.permissions-sticky-col {
+ position: sticky;
+ left: 0;
+ z-index: 250;
+ background: #fff;
+ box-shadow: 2px 0 4px rgba(0,0,0,0.04);
+ font-weight: 700;
+}
+
+/* Header checkbox */
+.permissions-table .q-th .column {
+ gap: 2px;
+}
+
diff --git a/ui/src/css/quasar.variables.sass b/ui/src/css/quasar.variables.sass
new file mode 100644
index 0000000..de35157
--- /dev/null
+++ b/ui/src/css/quasar.variables.sass
@@ -0,0 +1,8 @@
+$primary : #957116e8 // baggi altın
+$secondary : #dac197 // baggi ikinci ton
+$accent : #ff9800 // turuncu
+$dark : #5a4c4c // siyah-gri
+$positive : #21ba45 // yeşil
+$negative : #c10015 // kırmızı
+$info : #31ccec // açık mavi
+$warning : #f2c037 // sarı
\ No newline at end of file
diff --git a/ui/src/layouts/EmptyLayout.vue b/ui/src/layouts/EmptyLayout.vue
new file mode 100644
index 0000000..bf35919
--- /dev/null
+++ b/ui/src/layouts/EmptyLayout.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/ui/src/layouts/MainLayout.vue b/ui/src/layouts/MainLayout.vue
new file mode 100644
index 0000000..a58a79d
--- /dev/null
+++ b/ui/src/layouts/MainLayout.vue
@@ -0,0 +1,302 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Baggi Software System
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ c.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Şifre Değiştir
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Baggi Software System
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/ActivityLogs.vue b/ui/src/pages/ActivityLogs.vue
new file mode 100644
index 0000000..9748805
--- /dev/null
+++ b/ui/src/pages/ActivityLogs.vue
@@ -0,0 +1,341 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rol Değişiklik Detayı
+
+
+
+
+
+
+
+
+
+
Önce
+
+
+ {{ selectedDiff.before }}
+
+
+
+
+
Sonra
+
+
+ {{ selectedDiff.after }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ formatDate(props.row.created_at) }}
+
+
+
+
+
+
+ {{ props.row.action_target }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+ Log kaydı bulunamadı.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/ChangePassword.vue b/ui/src/pages/ChangePassword.vue
new file mode 100644
index 0000000..4d51503
--- /dev/null
+++ b/ui/src/pages/ChangePassword.vue
@@ -0,0 +1,120 @@
+
+
+
+
+
+🔐 Şifre Değiştir
+
+Mevcut şifrenizi girerek yeni şifre belirleyin
+
+
+
+
+
+
+
+
+
+
+
+
+
+{{ error }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/Dashboard.vue b/ui/src/pages/Dashboard.vue
new file mode 100644
index 0000000..2bffd6f
--- /dev/null
+++ b/ui/src/pages/Dashboard.vue
@@ -0,0 +1,9 @@
+
+
+ DashBoard
+
+
+
+
diff --git a/ui/src/pages/ErrorNotFound.vue b/ui/src/pages/ErrorNotFound.vue
new file mode 100644
index 0000000..4b53e5a
--- /dev/null
+++ b/ui/src/pages/ErrorNotFound.vue
@@ -0,0 +1,27 @@
+
+
+
+
+ 404
+
+
+
+ Oops. Nothing here...
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/FirstPasswordChange.vue b/ui/src/pages/FirstPasswordChange.vue
new file mode 100644
index 0000000..ad0a69d
--- /dev/null
+++ b/ui/src/pages/FirstPasswordChange.vue
@@ -0,0 +1,120 @@
+
+
+
+
+ Şifre Yenileme Zorunlu
+
+ Sistemi kullanabilmek için yeni bir şifre belirlemelisiniz.
+
+
+
+
+
+
+
+
+
+
+
+ {{ error }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/MainPage.vue b/ui/src/pages/MainPage.vue
new file mode 100644
index 0000000..1d2b0ab
--- /dev/null
+++ b/ui/src/pages/MainPage.vue
@@ -0,0 +1,306 @@
+
+
+
+
+
+
+
+
+
+ Kullanıcı Girişi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Parola Sıfırlama
+
+
+
+
+ Kullanıcı adınızı girin.
+
+
+
+
+
+ {{ forgotMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/MainPanel.vue b/ui/src/pages/MainPanel.vue
new file mode 100644
index 0000000..172bc15
--- /dev/null
+++ b/ui/src/pages/MainPanel.vue
@@ -0,0 +1,9 @@
+
+
+ cari hesap tabloları
+
+
+
+
diff --git a/ui/src/pages/MePassword.vue b/ui/src/pages/MePassword.vue
new file mode 100644
index 0000000..637cbc5
--- /dev/null
+++ b/ui/src/pages/MePassword.vue
@@ -0,0 +1,107 @@
+
+
+
+
+
+ 🔐 Şifre Değiştir
+
+ Mevcut şifrenizi girerek yeni şifre belirleyin
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ error }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/OrderEntry.vue b/ui/src/pages/OrderEntry.vue
new file mode 100644
index 0000000..2afd629
--- /dev/null
+++ b/ui/src/pages/OrderEntry.vue
@@ -0,0 +1,2987 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ scope.opt.Cari_Ad }}
+ {{ scope.opt.Cari_Kod }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ form.OrderDate = v"
+ :disable="isEditMode || isClosedOrder || isViewOnly"
+ :readonly="isViewOnly"
+
+ />
+
+
+
+
+ form.AverageDueDate = v"
+ :readonly="isViewOnly"
+ :disable="isViewOnly"
+
+ />
+
+
+
+
+
+
+
+ {{ form.pb }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %
+
+
+
+
+
+
+ {{ form.pb }}
+
+
+
+
+
+ {{ form.pb }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/OrderGateway.vue b/ui/src/pages/OrderGateway.vue
new file mode 100644
index 0000000..5e06f90
--- /dev/null
+++ b/ui/src/pages/OrderGateway.vue
@@ -0,0 +1,272 @@
+
+
+
+
+ 🧾 Sipariş Modülü
+
+
+
+
+
+ 📌 Devam Eden Taslak Bulundu
+
+
+
+
+ No: {{ draftNumber }}
+
+
+ Numara alınamadı
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Bu modüle erişim yetkiniz yok.
+
+
+
+
+
+
+
diff --git a/ui/src/pages/OrderList.vue b/ui/src/pages/OrderList.vue
new file mode 100644
index 0000000..bc3f043
--- /dev/null
+++ b/ui/src/pages/OrderList.vue
@@ -0,0 +1,408 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tüm filtreleri temizle
+
+
+
+
+
+
+
+
+
+
+
+
+ Toplam Görünen Sipariş Tutarı (USD):
+
+ {{ store.totalVisibleUSD.toLocaleString('tr-TR', { minimumFractionDigits: 2 }) }}
+ USD
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Siparişi PDF olarak aç
+
+
+
+
+ {{ props.row.IsCreditableConfirmed ? 'Onaylı' : 'Onaysız' }}
+
+
+
+
+
+
+
+
+
+ {{ formatDate(props.row.OrderDate) }}
+
+
+
+
+
+ {{ formatDate(props.row.CreditableConfirmedDate) }}
+
+
+
+
+
+
+ {{ props.value }}
+
+ {{ props.value }}
+
+
+
+
+
+
+
+ {{ props.value }}
+
+ {{ props.value }}
+
+
+
+
+
+
+
+
+ Siparişi Aç
+
+
+
+
+
+
+
+
+ ❌ {{ store.error }}
+
+
+
+
+
+
+
diff --git a/ui/src/pages/OrderPdf.vue b/ui/src/pages/OrderPdf.vue
new file mode 100644
index 0000000..cc340bc
--- /dev/null
+++ b/ui/src/pages/OrderPdf.vue
@@ -0,0 +1 @@
+
diff --git a/ui/src/pages/PermissionMatrix.vue b/ui/src/pages/PermissionMatrix.vue
new file mode 100644
index 0000000..e4cd983
--- /dev/null
+++ b/ui/src/pages/PermissionMatrix.vue
@@ -0,0 +1,301 @@
+
+
+
+
+ Rol + Departman Yetkilendirme
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ props.row.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/ProductionWorker.vue b/ui/src/pages/ProductionWorker.vue
new file mode 100644
index 0000000..a4c56f2
--- /dev/null
+++ b/ui/src/pages/ProductionWorker.vue
@@ -0,0 +1,224 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ana Görsel
+
+
+
+
+
![]()
+
+
+
+ Ana görsel seçilmedi
+
+
+
+
+
+
+
+
+
+
+
+ Detay Görseller
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Talimat / Tela
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/ProductionWorkerGateway.vue b/ui/src/pages/ProductionWorkerGateway.vue
new file mode 100644
index 0000000..d2c4b70
--- /dev/null
+++ b/ui/src/pages/ProductionWorkerGateway.vue
@@ -0,0 +1,211 @@
+
+
+
+
+
+
+ Üretim İş Emirleri
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ props.value }}
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/ResetPassword.vue b/ui/src/pages/ResetPassword.vue
new file mode 100644
index 0000000..b8ffe92
--- /dev/null
+++ b/ui/src/pages/ResetPassword.vue
@@ -0,0 +1,264 @@
+
+
+
+
+
+
+
+
+
+
+ 🔐 Parola Sıfırlama
+
+
+ Yeni parolanızı belirleyin
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ passwordStrength.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ error }}
+
+
+
+
+
+
+
+
+
+
+
+ Bağlantı Geçersiz
+
+
+
+ Parola sıfırlama bağlantısı süresi dolmuş veya daha önce kullanılmış olabilir.
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/RoleDepartmentPermissionPage.vue b/ui/src/pages/RoleDepartmentPermissionPage.vue
new file mode 100644
index 0000000..e6d2cab
--- /dev/null
+++ b/ui/src/pages/RoleDepartmentPermissionPage.vue
@@ -0,0 +1,435 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/StatementHeaderReport.vue b/ui/src/pages/StatementHeaderReport.vue
new file mode 100644
index 0000000..515e9a7
--- /dev/null
+++ b/ui/src/pages/StatementHeaderReport.vue
@@ -0,0 +1,103 @@
+
+
+
+
+
+
📄 Cari Hesap Raporu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/StatementReport.vue b/ui/src/pages/StatementReport.vue
new file mode 100644
index 0000000..491987f
--- /dev/null
+++ b/ui/src/pages/StatementReport.vue
@@ -0,0 +1,499 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/TestMail.vue b/ui/src/pages/TestMail.vue
new file mode 100644
index 0000000..55ddcee
--- /dev/null
+++ b/ui/src/pages/TestMail.vue
@@ -0,0 +1,63 @@
+
+
+
+
+
+ SMTP Test Mail
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/UserDetail.vue b/ui/src/pages/UserDetail.vue
new file mode 100644
index 0000000..5c0721a
--- /dev/null
+++ b/ui/src/pages/UserDetail.vue
@@ -0,0 +1,383 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ hasPassword ? 'Parola Var' : 'Parola Yok' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Parola İşlemleri
+
+ Kullanıcıya parola oluşturma / sıfırlama bağlantısı e-posta ile gönderilir.
+
+
+
+ E-posta:
+ {{ form.email || '-' }}
+
+
+
+ Son gönderim: {{ lastPasswordMailSentAt }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Roller
+
+
+
+
+
+
+
+ {{ scope.opt.label }}
+
+
+
+
+
+
+
+
+
+
+
+
Piyasalar
+
+
+
+
+
+
+
+ {{ scope.opt.label }}
+
+
+
+
+
+
+
+
+
Nebim Kullanıcıları
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/UserGateway.vue b/ui/src/pages/UserGateway.vue
new file mode 100644
index 0000000..86af299
--- /dev/null
+++ b/ui/src/pages/UserGateway.vue
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Yeni Kullanıcı
+
+ Sisteme yeni kullanıcı ekle
+
+
+
+
+
+
+
+
+ Mevcut Kullanıcılar
+
+ Kullanıcıları görüntüle ve düzenle
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/UserList.vue b/ui/src/pages/UserList.vue
new file mode 100644
index 0000000..f0c76b0
--- /dev/null
+++ b/ui/src/pages/UserList.vue
@@ -0,0 +1,272 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ r }}
+
+
+
+
+
+
+
+
+ {{ d }}
+
+
+
+
+
+
+
+
+
+ {{ p }}
+
+
+
+
+
+
+
+
+
+
+ ❌ {{ store.error }}
+
+
+
+
+
+
diff --git a/ui/src/pages/UserPermissionPage.vue b/ui/src/pages/UserPermissionPage.vue
new file mode 100644
index 0000000..bde7e1a
--- /dev/null
+++ b/ui/src/pages/UserPermissionPage.vue
@@ -0,0 +1,357 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/UserSync.vue b/ui/src/pages/UserSync.vue
new file mode 100644
index 0000000..dd4c278
--- /dev/null
+++ b/ui/src/pages/UserSync.vue
@@ -0,0 +1,172 @@
+
+
+
+
+
👤 Kullanıcı Yönetimi
+
+
+
+
+
+
+
+
+
+
+ PostgreSQL Kullanıcıları
+
+
+
+
+
+
+ {{ props.row.sync_status }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSSQL Kullanıcıları
+
+
+
+
+
+
+ {{ props.row.is_blocked ? 'Engelli' : 'Aktif' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/pages/statementofaccount.vue b/ui/src/pages/statementofaccount.vue
new file mode 100644
index 0000000..9231b0f
--- /dev/null
+++ b/ui/src/pages/statementofaccount.vue
@@ -0,0 +1,521 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/router/index.js b/ui/src/router/index.js
new file mode 100644
index 0000000..c74e02c
--- /dev/null
+++ b/ui/src/router/index.js
@@ -0,0 +1,96 @@
+import { route } from 'quasar/wrappers'
+import { createRouter, createWebHashHistory } from 'vue-router'
+
+import routes from 'src/router/routes.js'
+
+import { useAuthStore } from 'stores/authStore'
+import { usePermissionStore } from 'stores/permissionStore'
+
+
+export default route(function () {
+
+ const router = createRouter({
+ history: createWebHashHistory(),
+ routes
+ })
+
+
+ /* ============================================================
+ 🔐 GLOBAL GUARD
+ ============================================================ */
+ router.beforeEach(async (to, from, next) => {
+
+ const auth = useAuthStore()
+ const perm = usePermissionStore()
+
+
+ /* ================= PUBLIC ================= */
+
+ if (to.meta?.public === true) {
+ return next()
+ }
+
+
+ /* ================= LOGIN ================= */
+
+ if (!auth.isAuthenticated) {
+ return next('/login')
+ }
+
+
+ /* ================= PASSWORD ================= */
+
+ if (
+ auth.mustChangePassword &&
+ to.path !== '/first-password-change'
+ ) {
+ return next('/first-password-change')
+ }
+
+
+ /* ================= ADMIN ================= */
+
+ if (auth.isAdmin) {
+ return next()
+ }
+
+
+ /* ================= LOAD PERMS ================= */
+
+ if (!perm.loaded) {
+ try {
+ await perm.fetchPermissions()
+ } catch (e) {
+ console.error('Permission load failed', e)
+ }
+ }
+
+
+ /* ================= CHECK ================= */
+
+ const required = to.meta?.permission
+
+ if (!required) {
+ return next()
+ }
+
+
+ const allowed = perm.hasApiPermission(required)
+
+ if (!allowed) {
+
+ console.warn('⛔ ACCESS DENIED:', {
+ path: to.fullPath,
+ permission: required
+ })
+
+ return next('/unauthorized')
+ }
+
+
+ next()
+ })
+
+
+ return router
+})
diff --git a/ui/src/router/meta.d.js b/ui/src/router/meta.d.js
new file mode 100644
index 0000000..f750359
--- /dev/null
+++ b/ui/src/router/meta.d.js
@@ -0,0 +1,10 @@
+// src/router/meta.js
+
+/**
+ * Route meta fields reference
+ *
+ * @typedef {Object} RouteMeta
+ * @property {boolean} [public] - Auth gerekmez
+ * @property {string} [permission] - Backend route permission (/api/...)
+ */
+export {}
diff --git a/ui/src/router/routes.js b/ui/src/router/routes.js
new file mode 100644
index 0000000..0f50c21
--- /dev/null
+++ b/ui/src/router/routes.js
@@ -0,0 +1,260 @@
+// src/router/routes.js
+
+const routes = [
+
+ /* ==========================================================
+ 🌍 ROOT
+ ========================================================== */
+ {
+ path: '/',
+ redirect: '/login'
+ },
+
+
+ /* ==========================================================
+ 🔐 PUBLIC
+ ========================================================== */
+ {
+ path: '/login',
+ component: () => import('layouts/EmptyLayout.vue'),
+ meta: { public: true },
+ children: [
+ {
+ path: '',
+ name: 'login',
+ component: () => import('pages/MainPage.vue')
+ }
+ ]
+ },
+
+ {
+ path: '/first-password-change',
+ component: () => import('layouts/EmptyLayout.vue'),
+ meta: { public: true },
+ children: [
+ {
+ path: '',
+ name: 'first-password-change',
+ component: () => import('pages/FirstPasswordChange.vue')
+ }
+ ]
+ },
+
+ {
+ path: '/password-reset/:token',
+ component: () => import('layouts/EmptyLayout.vue'),
+ meta: { public: true },
+ children: [
+ {
+ path: '',
+ name: 'password-reset',
+ component: () => import('pages/ResetPassword.vue')
+ }
+ ]
+ },
+
+
+ /* ==========================================================
+ 🏠 MAIN APP
+ ========================================================== */
+ {
+ path: '/app',
+ component: () => import('layouts/MainLayout.vue'),
+
+ children: [
+
+ /* ================= DASHBOARD ================= */
+
+ {
+ path: '',
+ name: 'dashboard',
+ component: () => import('pages/Dashboard.vue'),
+ meta: { permission: 'system:read' }
+ },
+
+
+ /* ================= PERMISSIONS ================= */
+
+ {
+ path: 'permissions',
+ name: 'permissions',
+ component: () => import('pages/PermissionMatrix.vue'),
+ meta: { permission: 'system:read' }
+ },
+
+ {
+ path: 'role-dept-permissions',
+ name: 'role-dept-permissions',
+ component: () => import('pages/RoleDepartmentPermissionPage.vue'),
+ meta: { permission: 'user:update' }
+ },
+
+ {
+ path: 'user-permissions',
+ name: 'user-permissions',
+ component: () => import('pages/UserPermissionPage.vue'),
+ meta: { permission: 'user:update' }
+ },
+
+
+ /* ================= FINANCE ================= */
+
+ {
+ path: 'statementofaccount',
+ name: 'statementofaccount',
+ component: () => import('pages/statementofaccount.vue'),
+ meta: { permission: 'finance:view' }
+ },
+
+ {
+ path: 'statementreport',
+ name: 'statementreport',
+ component: () => import('pages/StatementReport.vue'),
+ meta: { permission: 'finance:view' }
+ },
+
+ {
+ path: 'statementheaderreport',
+ name: 'statementheaderreport',
+ component: () => import('pages/StatementHeaderReport.vue'),
+ meta: { permission: 'finance:view' }
+ },
+
+
+ /* ================= USERS ================= */
+
+ {
+ path: 'users',
+ name: 'user-gateway',
+ component: () => import('pages/UserGateway.vue'),
+ meta: { permission: 'user:view' }
+ },
+
+ {
+ path: 'users/list',
+ name: 'user-list',
+ component: () => import('pages/UserList.vue'),
+ meta: { permission: 'user:view' }
+ },
+
+ {
+ path: 'users/new',
+ name: 'user-new',
+ component: () => import('pages/UserDetail.vue'),
+ meta: {
+ mode: 'new',
+ permission: 'user:insert'
+ }
+ },
+
+ {
+ path: 'users/edit/:id',
+ name: 'user-edit',
+ component: () => import('pages/UserDetail.vue'),
+ props: true,
+ meta: {
+ mode: 'edit',
+ permission: 'user:update'
+ }
+ },
+
+ {
+ path: 'users/view/:id',
+ name: 'user-view',
+ component: () => import('pages/UserDetail.vue'),
+ props: true,
+ meta: {
+ mode: 'view',
+ permission: 'user:view'
+ }
+ },
+
+
+ /* ================= LOGS ================= */
+
+ {
+ path: 'activity-logs',
+ name: 'activity-logs',
+ component: () => import('pages/ActivityLogs.vue'),
+ meta: { permission: 'user:view' }
+ },
+
+
+ /* ================= TEST MAIL ================= */
+
+ {
+ path: 'test-mail',
+ name: 'test-mail',
+ component: () => import('pages/TestMail.vue'),
+ meta: { permission: 'user:insert' }
+ },
+
+
+ /* ================= ORDERS ================= */
+
+ {
+ path: 'order-gateway',
+ name: 'order-gateway',
+ component: () => import('pages/OrderGateway.vue'),
+ meta: { permission: 'order:view' }
+ },
+
+ {
+ path: 'order-entry/:orderHeaderID',
+ name: 'order-entry',
+ component: () => import('pages/OrderEntry.vue'),
+ props: true,
+ meta: {
+ mode: 'new',
+ permission: 'order:insert'
+ }
+ },
+
+ {
+ path: 'order-edit/:orderHeaderID',
+ name: 'order-edit',
+ component: () => import('pages/OrderEntry.vue'),
+ props: true,
+ meta: {
+ mode: 'edit',
+ permission: 'order:update'
+ }
+ },
+
+ {
+ path: 'order-list',
+ name: 'order-list',
+ component: () => import('pages/OrderList.vue'),
+ meta: { permission: 'order:view' }
+ },
+
+ {
+ path: 'order-pdf/:id',
+ name: 'order-pdf',
+ component: () => import('pages/OrderPdf.vue'),
+ props: true,
+ meta: { permission: 'order:export' }
+ },
+
+
+ /* ================= PASSWORD ================= */
+
+ {
+ path: 'change-password',
+ name: 'change-password',
+ component: () => import('pages/ChangePassword.vue')
+ }
+ ]
+ },
+
+
+ /* ==========================================================
+ ❌ 404
+ ========================================================== */
+ {
+ path: '/:catchAll(.*)*',
+ component: () => import('pages/ErrorNotFound.vue')
+ }
+]
+
+export default routes
diff --git a/ui/src/services/api.js b/ui/src/services/api.js
new file mode 100644
index 0000000..7c85019
--- /dev/null
+++ b/ui/src/services/api.js
@@ -0,0 +1,67 @@
+// src/services/api.js
+import axios from 'axios'
+import qs from 'qs'
+import { useAuthStore } from 'stores/authStore'
+
+const api = axios.create({
+ baseURL: 'http://localhost:8080/api',
+ timeout: 180000,
+ paramsSerializer: params =>
+ qs.stringify(params, { arrayFormat: 'repeat' })
+})
+
+// REQUEST
+api.interceptors.request.use((config) => {
+ const auth = useAuthStore()
+ const url = config.url || ''
+
+ const isPublic =
+ url.startsWith('/auth/login') ||
+ url.startsWith('/auth/refresh') ||
+ url.startsWith('/password/forgot') ||
+ url.startsWith('/password/reset')
+
+
+ if (!isPublic && auth?.token) {
+ config.headers ||= {}
+ config.headers.Authorization = `Bearer ${auth.token}`
+ }
+
+ return config
+})
+
+// RESPONSE
+let isLoggingOut = false
+api.interceptors.response.use(
+ r => r,
+ async (error) => {
+ if (error?.response?.status === 401 && !isLoggingOut) {
+ isLoggingOut = true
+ try {
+ useAuthStore().clearSession()
+ } finally {
+ isLoggingOut = false
+ }
+ }
+ return Promise.reject(error)
+ }
+)
+
+// HELPERS
+export const get = (u, p = {}, c = {}) =>
+ api.get(u, { params: p, ...c }).then(r => r.data)
+
+export const post = (u, b = {}, c = {}) =>
+ api.post(u, b, c).then(r => r.data)
+
+export const put = (u, b = {}, c = {}) =>
+ api.put(u, b, c).then(r => r.data)
+
+export const del = (u, p = {}, c = {}) =>
+ api.delete(u, { params: p, ...c }).then(r => r.data)
+
+export const download = (u, p = {}, c = {}) =>
+ api.get(u, { params: p, responseType: 'blob', ...c })
+ .then(r => r.data)
+
+export default api
diff --git a/ui/src/services/orderService.js b/ui/src/services/orderService.js
new file mode 100644
index 0000000..91e9935
--- /dev/null
+++ b/ui/src/services/orderService.js
@@ -0,0 +1,62 @@
+// src/services/orderService.js
+import { get, post, put } from './api'
+
+/**
+ * 🔹 Tek bir siparişi ID’ye göre getirir.
+ * @param {string} id - OrderHeaderID (GUID)
+ */
+export async function getOrderById(id) {
+ try {
+ const data = await get(`/order/get/${id}`)
+ return data
+ } catch (err) {
+ console.error('❌ getOrderById hatası:', err.message)
+ throw err
+ }
+}
+
+/**
+ * 🔹 Yeni sipariş oluşturur (insert).
+ * Backend: POST /api/order/create
+ * @param {Object} header - Sipariş başlığı (OrderHeader tablosu)
+ * @param {Array} lines - Satırlar (OrderLine tablosu)
+ */
+export async function createOrder(header, lines) {
+ const payload = {
+ header,
+ lines,
+ username: header?.CreatedUserName || 'system',
+ }
+
+ try {
+ const data = await post('/order/create', payload)
+ console.log('✅ Sipariş oluşturuldu:', data)
+ return data
+ } catch (err) {
+ console.error('❌ createOrder hatası:', err.message)
+ throw err
+ }
+}
+
+/**
+ * 🔹 Mevcut siparişi günceller (update).
+ * Backend: PUT /api/order/update
+ * @param {Object} header - Sipariş başlığı (OrderHeader tablosu)
+ * @param {Array} lines - Satırlar (OrderLine tablosu)
+ */
+export async function updateOrder(header, lines) {
+ const payload = {
+ header,
+ lines,
+ username: header?.LastUpdatedUserName || 'system',
+ }
+
+ try {
+ const data = await put('/order/update', payload)
+ console.log('✅ Sipariş güncellendi:', data)
+ return data
+ } catch (err) {
+ console.error('❌ updateOrder hatası:', err.message)
+ throw err
+ }
+}
diff --git a/ui/src/stores/OrdernewListStore.js b/ui/src/stores/OrdernewListStore.js
new file mode 100644
index 0000000..c71aea4
--- /dev/null
+++ b/ui/src/stores/OrdernewListStore.js
@@ -0,0 +1,167 @@
+// src/stores/OrdernewListStore.js
+import { defineStore } from 'pinia'
+import api from 'src/services/api'
+
+let lastRequestId = 0
+
+export const useOrderListStore = defineStore('orderlist', {
+ state: () => ({
+ orders: [],
+ loading: false,
+ error: null,
+
+ filters: {
+ search: '',
+ CurrAccCode: '',
+ OrderDate: ''
+ }
+ }),
+
+ getters: {
+ filteredOrders (state) {
+ let result = state.orders
+
+ if (state.filters.CurrAccCode) {
+ result = result.filter(o => o.CurrAccCode === state.filters.CurrAccCode)
+ }
+
+ if (state.filters.OrderDate) {
+ result = result.filter(o =>
+ o.OrderDate?.startsWith(state.filters.OrderDate)
+ )
+ }
+
+ return result
+ },
+
+ totalVisibleUSD (state) {
+ return state.filteredOrders.reduce(
+ (sum, o) => sum + Number(o.TotalAmountUSD || 0),
+ 0
+ )
+ }
+ },
+
+ actions: {
+ async fetchOrders () {
+
+ // ==============================
+ // 📌 REQUEST ID
+ // ==============================
+ const rid = ++lastRequestId
+
+ // ==============================
+ // 📌 SEARCH SNAPSHOT
+ // ==============================
+ const raw = this.filters.search ?? ''
+ const trimmed = String(raw).trim()
+
+ // ==============================
+ // 📌 REQUEST LOG
+ // ==============================
+ console.groupCollapsed(
+ `%c[orders] FETCH rid=${rid}`,
+ 'color:#1976d2;font-weight:bold'
+ )
+
+ console.log('raw =', JSON.stringify(raw), 'len=', String(raw).length)
+ console.log('trimmed =', JSON.stringify(trimmed), 'len=', trimmed.length)
+ console.log('filters =', JSON.parse(JSON.stringify(this.filters)))
+ console.log('lastRID =', lastRequestId)
+
+ console.groupEnd()
+
+ this.loading = true
+ this.error = null
+
+ try {
+
+ // ==============================
+ // 📌 PARAMS
+ // ==============================
+ const params = {}
+ if (trimmed) params.search = trimmed
+
+ // ==============================
+ // 📌 API CALL
+ // ==============================
+ const res = await api.get('/orders/list', { params })
+
+ // ==============================
+ // 📌 STALE CHECK
+ // ==============================
+ if (rid !== lastRequestId) {
+ console.warn(
+ `[orders] IGNORE stale response rid=${rid} last=${lastRequestId}`
+ )
+ return
+ }
+
+ // ==============================
+ // 📌 DATA
+ // ==============================
+ const data = res?.data
+ this.orders = Array.isArray(data) ? data : []
+
+ // ==============================
+ // 📌 RESPONSE LOG
+ // ==============================
+ console.groupCollapsed(
+ `%c[orders] RESPONSE rid=${rid} count=${this.orders.length}`,
+ 'color:#2e7d32;font-weight:bold'
+ )
+
+ console.log('status =', res?.status)
+
+ console.log(
+ 'sample =',
+ this.orders.slice(0, 5).map(o => ({
+ id: o.OrderHeaderID,
+ no: o.OrderNumber,
+ code: o.CurrAccCode,
+ name: o.CurrAccDescription
+ }))
+ )
+
+ // ==============================
+ // 📌 DUPLICATE CHECK
+ // ==============================
+ const ids = this.orders.map(o => String(o.OrderHeaderID))
+ const dup = ids.filter((v, i) => ids.indexOf(v) !== i)
+
+ if (dup.length) {
+ console.warn(
+ 'DUPLICATE OrderHeaderID sample =',
+ dup.slice(0, 10)
+ )
+ }
+
+ console.groupEnd()
+
+ } catch (err) {
+
+ if (rid !== lastRequestId) return
+
+ console.error(
+ '[orders] FETCH FAILED',
+ err?.response?.status,
+ err?.response?.data || err
+ )
+
+ this.orders = []
+
+ this.error =
+ err?.response?.data ||
+ err?.message ||
+ 'Sipariş listesi alınamadı'
+
+ } finally {
+
+ if (rid === lastRequestId) {
+ this.loading = false
+ }
+
+ }
+ }
+ }
+})
diff --git a/ui/src/stores/UserDetailStore.js b/ui/src/stores/UserDetailStore.js
new file mode 100644
index 0000000..6d1dd0a
--- /dev/null
+++ b/ui/src/stores/UserDetailStore.js
@@ -0,0 +1,238 @@
+// src/stores/userDetailStore.js
+import { defineStore } from 'pinia'
+import api, { get, post, put } from 'src/services/api'
+
+export const useUserDetailStore = defineStore('userDetail', {
+ state: () => ({
+ sendingPasswordMail: false,
+ lastPasswordMailSentAt: null,
+ hasPassword: false,
+
+ /* ================= FLAGS ================= */
+ loading: false,
+ saving: false,
+ error: null,
+
+ /* ================= FORM ================= */
+ form: {
+ id: null,
+ code: '',
+ full_name: '',
+ email: '',
+ mobile: '',
+ is_active: true,
+ address: '',
+ roles: [],
+ departments: [],
+ piyasalar: [],
+ nebim_users: []
+ },
+
+ /* ================= LOOKUPS ================= */
+ roleOptions: [],
+ departmentOptions: [],
+ piyasaOptions: [],
+ nebimUserOptions: []
+ }),
+
+ actions: {
+ /* =====================================================
+ 🔄 RESET (NEW MODE)
+ ===================================================== */
+ resetForm () {
+ this.form = {
+ id: null,
+ code: '',
+ full_name: '',
+ email: '',
+ mobile: '',
+ is_active: true,
+ address: '',
+ roles: [],
+ departments: [],
+ piyasalar: [],
+ nebim_users: []
+ }
+ this.error = null
+ this.hasPassword = false
+ this.lastPasswordMailSentAt = null
+ },
+
+ /* =====================================================
+ 🔐 ADMIN RESET PASSWORD
+ ===================================================== */
+ async adminResetPassword (id, payload) {
+ // token otomatik (interceptor)
+ await post(`/users/${id}/admin-reset-password`, payload)
+ this.hasPassword = true
+ },
+
+ /* =====================================================
+ ✉️ SEND PASSWORD MAIL
+ ===================================================== */
+ async sendPasswordMail (id) {
+ this.sendingPasswordMail = true
+ this.error = null
+
+ try {
+ await post(`/users/${id}/send-password-mail`, {})
+
+ // UI takip (DB’siz): sadece “son gönderim” gösterir
+ this.lastPasswordMailSentAt = new Date().toLocaleString('tr-TR')
+ } catch (e) {
+ this.error = 'Parola maili gönderilemedi'
+ throw e
+ } finally {
+ this.sendingPasswordMail = false
+ }
+ },
+
+ /* =====================================================
+ 📦 PAYLOAD BUILDER (BACKEND SÖZLEŞMESİYLE UYUMLU)
+ ===================================================== */
+ buildPayload () {
+ return {
+ code: this.form.code,
+ full_name: this.form.full_name,
+ email: this.form.email,
+ mobile: this.form.mobile,
+ is_active: this.form.is_active,
+ address: this.form.address,
+
+ roles: this.form.roles,
+
+ // ✅ TEK DEPARTMAN (string → backend array)
+ departments: this.form.departments
+ ? [{ code: this.form.departments }]
+ : [],
+
+ piyasalar: (this.form.piyasalar || []).map(code => ({ code })),
+
+ nebim_users: (this.form.nebim_users || []).map(username => {
+ const opt = (this.nebimUserOptions || []).find(x => x.value === username)
+ return {
+ username,
+ user_group_code: opt?.user_group_code || ''
+ }
+ })
+ }
+ },
+
+ /* =====================================================
+ 📥 GET USER (EDIT MODE)
+ ===================================================== */
+ async fetchUser (id) {
+ this.loading = true
+ this.error = null
+
+ try {
+ const data = await get(`/users/${id}`)
+
+ this.form.id = data.id
+ this.form.code = data.code || ''
+ this.form.full_name = data.full_name || ''
+ this.form.email = data.email || ''
+ this.form.mobile = data.mobile || ''
+ this.form.is_active = !!data.is_active
+ this.form.address = data.address || ''
+
+ this.form.roles = data.roles || []
+ this.form.departments = (data.departments || []).map(x => x.code)
+ this.form.piyasalar = (data.piyasalar || []).map(x => x.code)
+ this.form.nebim_users = (data.nebim_users || []).map(x => x.username)
+
+ this.hasPassword = !!data.has_password
+ } catch (e) {
+ this.error = 'Kullanıcı bilgileri alınamadı'
+ throw e
+ } finally {
+ this.loading = false
+ }
+ },
+
+ /* =====================================================
+ ✍️ UPDATE USER (PUT)
+ ===================================================== */
+ async saveUser (id) {
+ this.saving = true
+ this.error = null
+
+ try {
+ console.log('🟦 saveUser() START', id)
+
+ const payload = this.buildPayload()
+ console.log('📤 PUT payload', payload)
+
+ await put(`/users/${id}`, payload)
+
+ console.log('✅ PUT OK → REFETCH USER')
+ await this.fetchUser(id)
+
+ console.log('🔄 USER REFRESHED', {
+ hasPassword: this.hasPassword,
+ roles: this.form.roles,
+ departments: this.form.departments
+ })
+ } catch (e) {
+ console.error('❌ saveUser FAILED', e)
+ this.error = 'Kullanıcı güncellenemedi'
+ throw e
+ } finally {
+ this.saving = false
+ }
+ },
+
+ /* =====================================================
+ ➕ CREATE USER (POST)
+ ===================================================== */
+ async createUser () {
+ this.saving = true
+ this.error = null
+
+ try {
+ console.log('🟢 createUser() START')
+
+ const payload = this.buildPayload()
+ console.log('📤 POST payload', payload)
+
+ const data = await post('/users', payload)
+
+ console.log('✅ CREATE OK response', data)
+
+ const newId = data?.id
+ if (!newId) {
+ throw new Error('CREATE response id yok')
+ }
+
+ console.log('🔁 FETCH NEW USER id=', newId)
+ await this.fetchUser(newId)
+
+ return newId
+ } catch (e) {
+ console.error('❌ createUser FAILED', e)
+ this.error = 'Kullanıcı oluşturulamadı'
+ throw e
+ } finally {
+ this.saving = false
+ }
+ },
+
+ /* =====================================================
+ 📚 LOOKUPS (NEW + EDIT ORTAK)
+ ===================================================== */
+ async fetchLookups () {
+ // token otomatik
+ const [roles, depts, piyasalar, nebims] = await Promise.all([
+ api.get('/lookups/roles'),
+ api.get('/lookups/departments'),
+ api.get('/lookups/piyasalar'),
+ api.get('/lookups/nebim-users')
+ ])
+
+ this.roleOptions = roles?.data || roles || []
+ this.departmentOptions = depts?.data || depts || []
+ this.piyasaOptions = piyasalar?.data || piyasalar || []
+ this.nebimUserOptions = nebims?.data || nebims || []
+ }
+ }
+})
diff --git a/ui/src/stores/UserListStore.js b/ui/src/stores/UserListStore.js
new file mode 100644
index 0000000..66d2c52
--- /dev/null
+++ b/ui/src/stores/UserListStore.js
@@ -0,0 +1,72 @@
+// src/stores/userListStore.js
+import { defineStore } from 'pinia'
+import api from 'src/services/api'
+
+export const useUserListStore = defineStore('userlist', {
+ state: () => ({
+ users: [],
+ loading: false,
+ error: null,
+
+ filters: {
+ search: '',
+ onlyActive: false
+ }
+ }),
+
+ getters: {
+ filteredUsers(state) {
+ let result = state.users
+ const term = state.filters.search?.toLowerCase() || ''
+
+ if (term) {
+ result = result.filter(u =>
+ u.code?.toLowerCase().includes(term) ||
+ u.nebim_username?.toLowerCase().includes(term) ||
+ u.role_names?.toLowerCase().includes(term) ||
+ u.department_names?.toLowerCase().includes(term) ||
+ u.piyasa_names?.toLowerCase().includes(term)
+ )
+ }
+
+ if (state.filters.onlyActive) {
+ result = result.filter(u => u.is_active)
+ }
+
+ return result
+ }
+ },
+
+ actions: {
+ async fetchUsers() {
+ this.loading = true
+ this.error = null
+
+ try {
+ const params = {}
+
+ if (this.filters.search) {
+ params.search = this.filters.search
+ }
+
+ const { data } = await api.get(
+ '/users/list',
+ { params }
+ )
+
+ this.users = Array.isArray(data) ? data : []
+
+ console.log('✅ User listesi alındı:', this.users.length)
+
+ } catch (err) {
+ console.error('❌ User listesi alınamadı:', err)
+ this.users = []
+ this.error =
+ err?.message ||
+ 'Kullanıcı listesi alınamadı'
+ } finally {
+ this.loading = false
+ }
+ }
+ }
+})
diff --git a/ui/src/stores/accountStore.js b/ui/src/stores/accountStore.js
new file mode 100644
index 0000000..35b351b
--- /dev/null
+++ b/ui/src/stores/accountStore.js
@@ -0,0 +1,43 @@
+// src/stores/accountStore.js
+import { defineStore } from 'pinia'
+import api from 'src/services/api'
+
+export const useAccountStore = defineStore('account', {
+ state: () => ({
+ accountOptions: [],
+ loading: false,
+ error: null
+ }),
+
+ actions: {
+ async fetchAccounts () {
+ this.loading = true
+ this.error = null
+
+ try {
+ // 🔐 Token interceptor ile otomatik eklenir
+ const { data } = await api.get('/accounts')
+
+ this.accountOptions = (Array.isArray(data) ? data : []).map(acc => ({
+ label: `${acc.display_code || ''} ${acc.account_name || ''}`.trim(),
+ value: acc.account_code
+ }))
+
+ } catch (err) {
+ console.error('❌ Error fetching accounts:', err)
+
+ if (err?.response?.status === 401) {
+ this.error = 'Cari hesapları görüntüleme yetkiniz yok.'
+ } else {
+ this.error =
+ err?.response?.data?.message ||
+ err?.message ||
+ 'Cari hesaplar yüklenemedi'
+ }
+ } finally {
+ this.loading = false
+ }
+ }
+
+ }
+})
diff --git a/ui/src/stores/activityLogStore.js b/ui/src/stores/activityLogStore.js
new file mode 100644
index 0000000..9eb3c3c
--- /dev/null
+++ b/ui/src/stores/activityLogStore.js
@@ -0,0 +1,102 @@
+import { defineStore } from 'pinia'
+import { get } from 'src/services/api'
+
+export const useActivityLogStore = defineStore('activityLogStore', {
+ state: () => ({
+ loading: false,
+
+ rows: [],
+ total: 0,
+
+ pagination: {
+ page: 1,
+ rowsPerPage: 0, // ✅ SINIRSIZ
+ sortBy: 'created_at',
+ descending: true
+ }
+ ,
+
+ filters: {
+ username: '',
+ actionCategory: null,
+ actionType: '',
+ success: null,
+ dateFrom: '',
+ dateTo: ''
+ }
+ }),
+
+ actions: {
+ async fetchLogs () {
+ this.loading = true
+ try {
+ const params = {}
+
+ if (this.pagination.rowsPerPage > 0) {
+ params.page = this.pagination.page
+ params.limit = this.pagination.rowsPerPage
+ }
+
+
+ if (this.filters.username)
+ params.username = this.filters.username
+
+ if (this.filters.actionCategory)
+ params.action_category = this.filters.actionCategory
+
+ if (this.filters.actionType)
+ params.action_type = this.filters.actionType
+
+ if (this.filters.success !== null)
+ params.success = this.filters.success
+
+ if (this.filters.dateFrom)
+ params.date_from = this.filters.dateFrom
+
+ if (this.filters.dateTo)
+ params.date_to = this.filters.dateTo
+
+ const data = await get('/activity-logs', params)
+
+ this.rows = data.items || []
+ this.total = data.total || 0
+ } finally {
+ this.loading = false
+ }
+ },
+ quickRoleChange () {
+
+ this.filters.actionCategory = 'role_permission'
+ this.filters.actionType = 'role_department_permission_change'
+
+ this.pagination.page = 1
+
+ this.fetchLogs()
+ }
+ ,
+
+ onTableRequest (props) {
+ const { page, rowsPerPage, sortBy, descending } = props.pagination
+
+ this.pagination.page = page
+ this.pagination.rowsPerPage = rowsPerPage
+ this.pagination.sortBy = sortBy
+ this.pagination.descending = descending
+
+ this.fetchLogs()
+ }
+,
+ resetFilters () {
+ this.filters = {
+ username: '',
+ actionCategory: null,
+ actionType: '',
+ success: null,
+ dateFrom: '',
+ dateTo: ''
+ }
+ this.pagination.page = 1
+ this.fetchLogs()
+ }
+ }
+})
diff --git a/ui/src/stores/authStore.js b/ui/src/stores/authStore.js
new file mode 100644
index 0000000..e17eda2
--- /dev/null
+++ b/ui/src/stores/authStore.js
@@ -0,0 +1,117 @@
+// src/stores/authStore.js
+import { defineStore } from 'pinia'
+import api from 'src/services/api'
+import { usePermissionStore } from 'stores/permissionStore'
+
+export const useAuthStore = defineStore('auth', {
+ state: () => {
+ let user = null
+
+ try {
+ const raw = localStorage.getItem('user')
+ if (raw && raw !== 'undefined' && raw !== 'null') {
+ user = JSON.parse(raw)
+ }
+ } catch {
+ console.warn('⚠️ Invalid user in localStorage, cleared')
+ localStorage.removeItem('user')
+ }
+
+ return {
+ token: localStorage.getItem('token'),
+ user,
+ forcePasswordChange: localStorage.getItem('forcePasswordChange') === '1'
+ }
+ },
+
+ getters: {
+ isAuthenticated: s => !!s.token,
+ mustChangePassword: s => !!s.forcePasswordChange,
+
+ // 🔥 TEK ADMIN KURALI
+ isAdmin: s =>
+ String(s.user?.role_code || '').toLowerCase() === 'admin'
+ },
+
+ actions: {
+ /* =========================================================
+ 🔐 SESSION
+ ========================================================= */
+ setSession ({ token, user }) {
+ this.token = token
+ this.user = user ?? null
+ this.forcePasswordChange = !!user?.force_password_change
+
+ localStorage.setItem('token', token)
+
+ if (user) {
+ localStorage.setItem('user', JSON.stringify(user))
+ } else {
+ localStorage.removeItem('user')
+ }
+
+ localStorage.setItem(
+ 'forcePasswordChange',
+ this.forcePasswordChange ? '1' : '0'
+ )
+ },
+
+ clearSession () {
+ this.token = null
+ this.user = null
+ this.forcePasswordChange = false
+
+ localStorage.removeItem('token')
+ localStorage.removeItem('user')
+ localStorage.removeItem('forcePasswordChange')
+
+ usePermissionStore().clear()
+ },
+
+ /* =========================================================
+ 🔐 LOGIN
+ ========================================================= */
+ async login (username, password) {
+ const res = await api.post('/auth/login', { username, password })
+
+ const token =
+ res?.token ||
+ res?.data?.token ||
+ res?.access_token ||
+ res?.data?.access_token
+
+ const user =
+ res?.user ||
+ res?.data?.user
+
+ // ✅ JWT doğrulama
+ const tokenStr = typeof token === 'string' ? token.trim() : ''
+ const looksLikeJwt = tokenStr.split('.').length === 3
+
+ if (!tokenStr || !looksLikeJwt) {
+ console.error('❌ LOGIN RESPONSE (unexpected):', res)
+ throw new Error('Invalid login token')
+ }
+
+ this.setSession({ token: tokenStr, user })
+
+ // 🔥 PERMISSIONS
+ const perm = usePermissionStore()
+ await perm.fetchPermissions()
+
+
+
+
+ // 🧪 DEBUG (istersen sonra kaldır)
+ console.log('🔐 AUTH DEBUG', {
+ isAdmin: this.isAdmin,
+ users: perm.hasPermission('/api/users/list'),
+ orders: perm.hasPermission('/api/orders/list'),
+ logs: perm.hasPermission('/api/activity-logs'),
+ permissions: perm.hasPermission('/api/permissions/matrix')
+ })
+
+ return true
+ }
+ }
+})
diff --git a/ui/src/stores/deneme b/ui/src/stores/deneme
new file mode 100644
index 0000000..ebdca69
--- /dev/null
+++ b/ui/src/stores/deneme
@@ -0,0 +1,3023 @@
+
+
+
+
+
+
+
+
+
diff --git a/ui/src/stores/deneme2 b/ui/src/stores/deneme2
new file mode 100644
index 0000000..60520f2
--- /dev/null
+++ b/ui/src/stores/deneme2
@@ -0,0 +1,1128 @@
+/* ===========================================================
+ GLOBAL CUSTOM CSS
+ =========================================================== */
+ .with-bg {
+ position: relative;
+ min-height: 100%;
+}
+.with-bg::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background: url('/images/Baggi-tekstilas-logolu.jpg') no-repeat center top;
+ background-size: 400px auto;
+ opacity: 0.15;
+ pointer-events: none;
+ 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;
+ }
+}
+
+/* ===== ÜST BLOKLAR (SABİT) ===== */
+.filter-sticky {
+ position: sticky;
+ top: 56px; /* q-header yüksekliği */
+ z-index: 300;
+ background: #fff;
+}
+
+.filter-collapsible {
+ background: #fff;
+}
+
+/* ===== TABLO SCROLL ===== */
+.table-scroll {
+ margin-top: 0; /* 🔹 Boşluğu kaldır */
+ height: calc(100vh - 56px); /* 🔹 Header yüksekliği kadar kısalt */
+ overflow-y: auto;
+ overflow-x: auto;
+ position: relative;
+}
+
+.sticky-table .q-table__middle {
+ overflow: visible !important;
+ max-height: none !important;
+}
+
+.sticky-table .q-table__top {
+ position: sticky;
+ top: 0;
+ z-index: 220;
+ background: #fff;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.08);
+}
+
+.sticky-table thead th {
+ position: sticky;
+ top: 40px;
+ z-index: 210;
+ background: #fff;
+}
+
+/* 🔹 Toggle bar */
+.sticky-bar {
+ position: sticky;
+ top: 0; /* tablo scroll başladığında en üstte kalsın */
+ z-index: 230;
+ background: #fff;
+ padding: 4px 8px;
+ border-bottom: 1px solid #ddd;
+}
+
+/* ===== KOLON DARALTMA + WRAP ===== */
+.sticky-table thead th {
+ resize: horizontal;
+ overflow: auto;
+ min-width: 80px;
+ max-width: 400px;
+}
+
+.sticky-table td {
+ min-width: 80px;
+ max-width: 400px;
+ white-space: normal !important;
+ word-break: break-word !important;
+ overflow-wrap: break-word !important;
+ line-height: 1.2rem;
+ padding: 4px 8px !important;
+ font-weight: 600;
+ font-size: 0.95rem;
+}
+
+/* ===== GÖRSEL ===== */
+.baggi-ppct {
+ display: block;
+ margin: 30px auto 0;
+ max-width: 400px;
+ opacity: 0.4;
+}
+
+.col-desc {
+ white-space: normal !important;
+ word-break: break-word !important;
+ overflow-wrap: break-word;
+ font-size: 0.75rem !important;
+ line-height: 1.1rem;
+ width: 220px !important;
+ max-width: 220px !important;
+ min-width: 180px !important;
+}
+
+/* ===== TABLO GÖRÜNÜM ===== */
+.custom-table { font-size: 0.8rem; }
+.custom-table th { background: #fff; font-weight: 800; color: #222; }
+.custom-table td { font-weight: 600; color: #333; }
+
+.custom-subtable { font-size: 0.72rem; background: #fafafa; }
+.custom-subtable th { background: #f9f9f9; font-weight: 500; color: #555; }
+.custom-subtable td { font-weight: 400; color: #666; }
+
+/* dar sütunlar için */
+.col-narrow {
+ font-size: 0.72rem;
+ padding: 2px 6px !important;
+ max-width: 90px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/* ===== GRUP SATIRI ===== */
+.group-row {
+ background: #f1f1f1 !important;
+ font-weight: 700 !important;
+ color: #222;
+ border-top: 2px solid #ccc;
+ border-bottom: 2px solid #ccc;
+}
+
+/* ===== BALANCE CARD ===== */
+.balance-card {
+ width: 100%;
+ min-height: 120px;
+ border-radius: 8px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.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 PAGE (FINAL)
+ =========================================================== */
+
+/* Toolbar */
+.permissions-toolbar {
+ position: sticky;
+ top: 42px; /* q-header yüksekliği */
+ z-index: 300;
+ background: #fff;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 8px 16px;
+ border-bottom: 1px solid #ddd;
+}
+
+/* Table scroll alanı */
+.permissions-table-scroll {
+ height: calc(100vh - 112px); /* header (56) + toolbar (56) */
+ overflow-y: auto;
+ overflow-x: auto;
+ position: relative;
+}
+
+/* Tablo gövdesi */
+.permissions-table .q-table__middle {
+ overflow: auto !important;
+ max-height: none !important;
+ padding-top: 0px; /* 🔑 Başlık yüksekliği kadar boşluk bırak */
+}
+
+/* Sticky başlıklar – toolbar’ın altında */
+.permissions-table thead th {
+ position: sticky;
+ top:10px; /* toolbar altında hizalanır */
+ z-index: 210;
+ background: #fff;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.08);
+}
+
+/* Hücreler */
+.permissions-table td {
+ min-width: 80px;
+ max-width: 400px;
+ white-space: normal !important;
+ word-break: break-word !important;
+ overflow-wrap: break-word !important;
+ line-height: 1.2rem;
+ padding: 4px 8px !important;
+ font-weight: 600;
+ font-size: 0.95rem;
+ background: #fff;
+}
+
+/* İlk kolon (role) sabit */
+.permissions-table .permissions-sticky-col {
+ position: sticky;
+ left: 0;
+ z-index: 205;
+ background: #fff;
+ box-shadow: 2px 0 4px rgba(0,0,0,0.04);
+}
+
+/* ===========================================================
+ 1️⃣ ROOT & GLOBAL RESET
+ =========================================================== */
+:root {
+ --header-h: 0px;
+ --filter-h: 72px;
+ --save-h: 60px;
+ --grid-header-h: 172px;
+ --sub-header-h: 34px;
+ --drawer-w: 240px;
+
+ /* Grid kolon genişlikleri */
+ --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;
+ --col-termin: 142px; /* 🔹 termin tarihi kolon genişliği */
+
+ /* Beden blok ölçüleri */
+ --grp-title-w: 90px;
+ --grp-title-gap: 4px;
+ --beden-w: 44px;
+ --beden-h: 28px;
+ --beden-count: 16;
+
+ /* Tema renkleri */
+ --baggi-gold: #c9a227;
+ --baggi-gold-pale: #fff9e6;
+ --baggi-gold-light: #fff7d2;
+ --baggi-cream: #fffef9;
+ --baggi-gray-border: #bbb;
+}
+
+*, *::before, *::after { box-sizing: border-box; }
+html, body { 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; }
+/* ===========================================================
+ 2️⃣ PAGE STRUCTURE & SCROLL
+ =========================================================== */
+.order-page {
+ display: flex;
+ flex-direction: column;
+ height: calc(100vh - var(--header-h));
+ overflow-y: auto;
+ overflow-x: hidden;
+ background: #fff;
+}
+
+.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%;
+}
+
+/* 🔸 Yatay scroll sadece grid alanında */
+.order-scroll-x {
+ flex: 1;
+ overflow-x: auto;
+ overflow-y: visible;
+ background: #fff;
+}
+
+/* 🔸 Scrollbar stili */
+.order-page::-webkit-scrollbar,
+.order-scroll-x::-webkit-scrollbar {
+ height: 8px; width: 8px;
+}
+.order-page::-webkit-scrollbar-thumb,
+.order-scroll-x::-webkit-scrollbar-thumb {
+ background: #c0a75e;
+ border-radius: 4px;
+}
+.order-page::-webkit-scrollbar-track,
+.order-scroll-x::-webkit-scrollbar-track {
+ background: #f9f5e6;
+}
+/* ===========================================================
+ 3️⃣ STICKY STACK (HEADER + TOOLBARS)
+ =========================================================== */
+.q-header {
+ position: sticky;
+ top: 0;
+ z-index: 1000;
+ box-shadow: 0 1px 2px rgba(0,0,0,0.08);
+}
+
+.sticky-stack {
+ position: sticky;
+ top: var(--header-h);
+ margin-top: 0 !important;
+ z-index: 950;
+ display: flex;
+ flex-direction: column;
+ background: #fff;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.05);
+}
+
+/* 🔹 Filtre bar */
+.filter-bar {
+ background: #fafafa;
+ border-bottom: 1px solid #ddd;
+ padding: 12px 24px;
+ margin-top:0 !important;
+}
+
+/* 🔹 Save toolbar */
+.save-toolbar {
+ background: var(--baggi-gold-pale);
+ border-top: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ padding: 10px 16px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ z-index: 940;
+}
+.save-toolbar .label { font-weight: 700; color: #6a5314; }
+.save-toolbar .value { font-weight: 700; color: #000; }
+.save-toolbar .q-btn {
+ font-weight: 600;
+ border-radius: 6px;
+ text-transform: none;
+}
+/* ===========================================================
+ 4️⃣ GRID HEADER (ANA BAŞLIK BLOKU)
+ =========================================================== */
+.order-grid-header {
+ position: sticky;
+ top: calc(var(--header-h) + var(--filter-h) + var(--save-h));
+ z-index: 700;
+ 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);
+ background: var(--baggi-cream);
+ border-bottom: 2px solid var(--baggi-gray-border);
+ box-shadow: 0 2px 3px rgba(0,0,0,0.05);
+}
+
+/* Sabit kolonlar */
+.order-grid-header .col-fixed {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ writing-mode: vertical-lr;
+ transform: rotate(180deg);
+ background: var(--baggi-gold-light);
+ border: 1px solid #aaa;
+ font-weight: 700;
+ font-size: 12.5px;
+ height: var(--grid-header-h);
+}
+
+.order-grid-header .aciklama-col {
+ background: #fff9c4;
+ border-right: 2px solid #a6a6a6;
+}
+/* ===========================================================
+ 5️⃣ BEDEN BLOKLARI & SAĞ TOPLAM
+ =========================================================== */
+.order-grid-header .beden-block {
+ display: flex;
+ flex-direction: column;
+ height: var(--grid-header-h);
+ background: #fff;
+ border: 1px solid #ccc;
+}
+
+.order-grid-header .grp-row {
+ display: flex;
+ align-items: center;
+ height: var(--beden-h);
+}
+
+.order-grid-header .grp-title {
+ width: var(--grp-title-w);
+ text-align: right;
+ font-weight: 700;
+ font-size: 12px;
+ padding-right: 4px;
+}
+
+.order-grid-header .grp-body {
+ display: grid;
+ grid-auto-flow: column;
+ grid-auto-columns: var(--beden-w);
+}
+.order-grid-header .grp-cell.hdr {
+ width: var(--beden-w);
+ height: var(--beden-h);
+ border: 1px solid #bbb;
+ font-size: 11.5px;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.order-grid-header .total-row {
+ display: flex;
+ align-items: stretch;
+ justify-content: space-between;
+ background: #fff59d;
+}
+.order-grid-header .total-cell {
+ width: var(--col-adet);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ writing-mode: vertical-lr;
+ transform: rotate(180deg);
+ border-right: 1px solid #bbb;
+ background: var(--baggi-gold-pale);
+ font-weight: 700;
+ font-size: 12px;
+}
+/* ===========================================================
+ 6️⃣ SUB-HEADER (ÜRÜN GRUBU BAR) — TAM HİZALANMIŞ
+ =========================================================== */
+ .order-sub-header {
+ padding-right: 0 !important; /* 🔹 Ekstra sağ boşluğu kaldır */
+ margin-right: 0 !important;
+}
+.order-sub-header {
+ position: sticky;
+ top: calc(
+ var(--header-h)
+ + var(--filter-h)
+ + var(--save-h)
+ + var(--grid-header-h)
+ );
+ z-index: 650;
+
+ /* 🔹 Header ile birebir grid düzeni */
+ 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);
+
+ align-items: center;
+ justify-items: stretch;
+ height: var(--sub-header-h);
+ min-height: var(--sub-header-h);
+
+ /* 🔹 Görsel */
+ background: linear-gradient(90deg, #fffbe9 0%, #fff4c4 50%, #fff1b0 100%);
+ border-top: 1px solid #d6c06a;
+ border-bottom: 1px solid #d6c06a;
+
+ /* 🔹 Hatalı hizalamaları engelle */
+ box-sizing: border-box;
+ overflow: hidden;
+ padding: 0 !important;
+ margin: 0 !important;
+ padding-right: 0 !important; /* ✅ sağ taşmayı önler */
+}
+
+/* 🔹 Genişlik eşitleme */
+:root {
+ --col-termin: 142px; /* ✅ q-input genişliğiyle birebir */
+}
+
+/* 🔹 Sub-header hover efekti */
+.order-sub-header:hover {
+ background: linear-gradient(90deg, #fff9cf 0%, #fff3b0 70%, #ffe88f 100%);
+}
+
+
+/* 🔹 Sol taraf (MODEL–AÇIKLAMA alanı) */
+.order-sub-header .sub-left {
+ grid-column: 1 / span 5;
+ font-weight: 800;
+ padding-left: 6px;
+ color: #2b1f05;
+ display: flex;
+ align-items: center;
+}
+
+/* 🔹 Orta beden bloğu (header’la aynı yapı) */
+.order-sub-header .sub-center {
+ grid-column: 6 / 7;
+ display: grid;
+ grid-auto-flow: column;
+ grid-auto-columns: var(--beden-w);
+ justify-content: start;
+ align-items: center;
+ width: calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w) * var(--beden-count)));
+ padding-left: var(--grp-title-w);
+ margin-left: var(--grp-title-gap);
+ height: 100%;
+ box-sizing: border-box;
+}
+
+.order-sub-header .beden-cell {
+ width: var(--beden-w);
+ height: 100%;
+ border: 1px solid #d8c16b;
+ border-right: none;
+ background: #fffdf3;
+ font-size: 12px;
+ font-weight: 600;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ box-sizing: border-box;
+}
+.order-sub-header .beden-cell:last-child {
+ border-right: 1px solid #d8c16b;
+}
+
+/* 🔹 Sağ taraf (adet–fiyat–pb–tutar–termin toplamları) */
+.order-sub-header .sub-right {
+ grid-column: 7 / -1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: flex-end;
+ text-align: right;
+ padding-right: 6px;
+ font-weight: 900;
+ color: #3b2f09;
+ line-height: 1.3;
+ text-transform: uppercase;
+ font-size: 13.5px;
+}
+
+.order-sub-header:hover {
+ background: linear-gradient(90deg,#fff9cf 0%,#fff3b0 70%,#ffe88f 100%);
+}
+
+:root {
+ --sub-header-h: 60px;
+}
+/* SUB-HEADER sağ yazıyı 3 beden kolonu sola kaydır */
+.order-sub-header .sub-right {
+ transform: translateX(calc(-1 * var(--beden-w) * 4));
+}
+
+/* Taşmayı engelle (ihtiyaten) */
+.order-sub-header {
+ overflow: hidden;
+}
+
+/* ===========================================================
+ 7️⃣ GRID BODY & SATIRLAR — TAM HİZALANMIŞ
+ =========================================================== */
+.order-grid-body {
+ position: relative;
+ background: #fff;
+ margin-top: 0 !important;
+ padding-top: 0;
+ 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:hover {
+ background: #fffce0;
+}
+.summary-row.is-editing {
+ background: #fff3cd;
+ outline: 2px solid #caa83f;
+}
+
+.summary-row .cell {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: var(--beden-h);
+ padding: 4px 6px;
+ font-size: 13px;
+ color: #222;
+ box-sizing: border-box;
+}
+
+.summary-row:nth-child(odd) { background: #fffef9; }
+
+/* 🔹 Beden blok hizalaması */
+.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-flow: column;
+ grid-auto-columns: var(--beden-w);
+}
+.summary-row .grp-row .cell.beden {
+ width: var(--beden-w);
+ height: var(--beden-h);
+ border: 1px solid #ddd;
+ font-size: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.cell.beden.ghost {
+ opacity: 0;
+ pointer-events: none;
+ border: 1px solid transparent !important;
+}
+
+/* 🔹 Sağ kolonlar */
+.summary-row .cell.adet,
+.summary-row .cell.fiyat,
+.summary-row .cell.pb,
+.summary-row .cell.tutar,
+.summary-row .cell.termin {
+ font-weight: 600;
+ color: #000;
+ border-left: none !important;
+ height: 100%;
+}
+
+.summary-row .cell.tutar {
+ text-align: right;
+ justify-content: flex-end;
+ padding-right: 8px;
+ border-right: none !important;
+}
+
+.summary-row .cell.termin {
+ background: #fffef9;
+ justify-content: center;
+ align-items: center;
+ min-width: var(--col-termin);
+}
+.summary-row .cell.termin .q-input {
+ width: 100%;
+ max-width: 142px !important;
+ box-sizing: border-box;
+}
+.summary-row .cell.termin input {
+ text-align: center;
+ font-size: 13px;
+}
+
+/* ===========================================================
+ 9️⃣ ORDER EDITOR (ALT FORM)
+ =========================================================== */
+.editor {
+ position: relative;
+ z-index: 50;
+ background: #fffef9;
+ border-top: 1px solid #ddd;
+ margin-top: 24px;
+ padding: 16px;
+}
+.editor::before {
+ content: "";
+ display: block;
+ height: 4px;
+ background: linear-gradient(to right,#c9a227,#e5d28b,#fff7d2);
+ margin-bottom: 12px;
+ border-radius: 2px;
+}
+.editor .q-btn:hover { background: #d2b04d; }
+.editor .q-input,
+.editor .q-select { margin-bottom: 8px; font-size: 14px; }
+.cell.termin .termin-label {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 600;
+ font-size: 13px;
+ color: #222;
+ background: #fffef9;
+ border-left: 1px solid #ccc;
+ box-sizing: border-box;
+}
+
+/* ===========================================================
+ 🔟 RESPONSIVE + MİNÖR DÜZEN
+ =========================================================== */
+@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 {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 4px 6px;
+ height: auto;
+ 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 {
+ grid-column: 5 / 6 !important; /* sadece 5. kolon */
+ position: relative !important;
+ width: calc(var(--col-aciklama) + 92px) !important; /* 🔹 74px genişletme */
+ margin-right: -92px !important; /* 🔹 bedenle tam hizalanır */
+ white-space: normal !important;
+ word-break: break-word !important;
+ overflow-wrap: break-word !important;
+ line-height: 1.4 !important;
+ padding: 6px 12px !important;
+ font-size: 13px !important;
+ text-align: left !important;
+ display: flex !important;
+ flex-direction: column !important;
+ align-items: flex-start !important;
+ justify-content: flex-start !important;
+ min-height: 36px !important;
+ background: #fff !important;
+ box-sizing: border-box !important;
+ border-right: 1px solid #ccc !important;
+ z-index: 10 !important;
+}
+/* 🧩 Grid çizgi kontrastı güçlendirme */
+.summary-row .cell,
+.order-grid-header .col-fixed,
+.summary-row .grp-row .cell.beden {
+ border-color: #bbb !important; /* 🔹 daha belirgin çizgi */
+}
+
+.summary-row .cell:not(:last-child) {
+ border-right: 1px solid #bdbdbd !important;
+}
+/* ===========================================================
+ 🧱 ALT GRID ÇİZGİLERİ – TÜM SATIRLAR İÇİN
+ =========================================================== */
+.summary-row {
+ border-bottom: 1px solid #ccc; /* 🔹 satır alt çizgisi */
+}
+
+.summary-row:last-child {
+ border-bottom: 2px solid #b7a33a; /* 🔹 son satırda Baggi gold tonu */
+}
+
+/* 🔹 Hücrelerin alt çizgisi (beden dahil) */
+.summary-row .cell,
+.summary-row .grp-row .cell.beden {
+ border-bottom: 1px solid #ddd !important;
+}
+
+/* 🔹 Hover olduğunda grid çizgileri kaybolmasın */
+.summary-row:hover .cell,
+.summary-row:hover .grp-row .cell.beden {
+ border-bottom: 1px solid #ccc !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;
+}
+
+/* 🔹 Hover olduğunda grid çizgileri kaybolmasın */
+.summary-row:hover .cell,
+.summary-row:hover .grp-row .cell.beden {
+ border-bottom: 1px solid #ccc !important;
+}
+
+/* ===========================================================
+ 🎨 STOK RENKLERİ (LOW–MID–HIGH)
+ =========================================================== */
+.stok-red {
+ color: #e53935; /* 🔴 Kırmızı */
+ font-weight: 600;
+}
+
+.stok-yellow {
+ color: #f9a825; /* 🟡 Sarı */
+ font-weight: 600;
+}
+
+.stok-green {
+ color: #43a047; /* 🟢 Yeşil */
+ font-weight: 600;
+}
+.q-banner.rounded-borders {
+ border-radius: 8px;
+}
+.order-gateway {
+ background: linear-gradient(145deg, #fff 0%, #fafafa 100%);
+ height: 100%;
+}
+
+.order-btn {
+ font-size: 1.2rem;
+ padding: 20px 40px;
+ border-radius: 12px;
+ min-width: 280px;
+ transition: all 0.2s ease;
+}
+.order-btn:hover {
+ transform: translateY(-3px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+/* ===========================================================
+ 🧭 DRAWER AÇIKKEN GRID HİZALAMA FIX
+ =========================================================== */
+
+/* Drawer açıkken içerik kaymaması */
+.body--drawer-left-open .order-page {
+ width: calc(100vw - var(--drawer-w)); /* viewport'tan drawer genişliği kadar düş */
+ overflow-x: hidden; /* dış overflow’u kes */
+}
+
+/* Scroll konteyner sadece grid içinde çalışsın */
+.order-scroll-x {
+ max-width: 100%;
+ overflow-x: auto;
+ overflow-y: visible;
+ background: #fff;
+ box-sizing: border-box;
+}
+
+/* Scrollbar ve sağ boşluğu dengeler */
+.order-grid-header,
+.order-sub-header,
+.order-grid-body {
+ min-width: fit-content;
+ width: 100%;
+ box-sizing: border-box;
+}
+/* ===========================================================
+ 🧱 DRAWER AÇIKKEN TAM HİZALAMA FIX (v2)
+ =========================================================== */
+
+/* Drawer açıkken tüm üst bloklar sağdan taşmasın */
+.body--drawer-left-open .filter-bar,
+.body--drawer-left-open .save-toolbar,
+.body--drawer-left-open .order-grid-header,
+.body--drawer-left-open .order-sub-header,
+.body--drawer-left-open .order-grid-body {
+ width: calc(100vw - var(--drawer-w)); /* drawer genişliği kadar daralt */
+ margin-left: 0;
+ margin-right: 0;
+ overflow-x: hidden;
+ box-sizing: border-box;
+}
+
+/* Drawer kapalıyken tam genişlik */
+.body--drawer-left-closed .filter-bar,
+.body--drawer-left-closed .save-toolbar,
+.body--drawer-left-closed .order-grid-header,
+.body--drawer-left-closed .order-sub-header,
+.body--drawer-left-closed .order-grid-body {
+ width: 100vw;
+}
+
+/* Order grid sağ sınırı altın kenarlıkla bitir (optik kapanış) */
+.order-grid-header,
+.order-sub-header,
+.order-grid-body {
+ border-right: 2px solid var(--baggi-gold);
+}
+/* ===========================================================
+ 🎯 SAĞ ALT BOŞLUK FİNAL FIX
+ =========================================================== */
+
+/* Drawer açıkken tüm grid konteynerleri sağdan tam sıfırla */
+.body--drawer-left-open .order-page,
+.body--drawer-left-open .filter-bar,
+.body--drawer-left-open .save-toolbar,
+.body--drawer-left-open .order-grid-header,
+.body--drawer-left-open .order-sub-header,
+.body--drawer-left-open .order-grid-body {
+ width: calc(100vw - var(--drawer-w) - 8px); /* 🔹 scrollbar toleransı */
+ padding-right: 0 !important;
+ margin-right: 0 !important;
+ overflow-x: hidden !important;
+}
+
+/* Son altın kenarlık hizasını koru */
+.order-grid-body {
+ border-right: 2px solid var(--baggi-gold);
+}
+/* ===========================================================
+ 🎯 GRID SAĞ HİZALAMA (FILTER + SAVE + HEADER)
+ =========================================================== */
+
+/* Ana scroll container referansı */
+.order-scroll-x {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start; /* hizalama sola */
+ overflow-x: auto;
+ overflow-y: visible;
+ background: #fff;
+}
+
+/* Filter ve Save barlar grid genişliğini takip etsin */
+.filter-bar,
+.save-toolbar,
+.order-grid-header,
+.order-sub-header {
+ width: fit-content; /* içeriğe göre genişlik */
+ min-width: 100%; /* minimum ekran kadar */
+ box-sizing: border-box;
+}
+
+/* Grid body’nin genişliği kadar sağ hizalama */
+.order-grid-body {
+ width: fit-content;
+ box-sizing: border-box;
+}
+
+/* Sağ kenarda taşma veya padding olmasın */
+.filter-bar,
+.save-toolbar,
+.order-grid-header,
+.order-sub-header,
+.order-grid-body {
+ margin-right: 0 !important;
+ padding-right: 0 !important;
+ border-right: none !important; /* altın çizgi istemiyorsan kaldırılır */
+}
+
+/* Drawer açık/kapalı fark etmeden */
+.body--drawer-left-open .order-scroll-x,
+.body--drawer-left-closed .order-scroll-x {
+ width: 100%;
+ overflow-x: auto;
+}
+
+/* ===============================
+ ORDER LIST (ol-) — Sticky Stack
+ =============================== */
+
+:root {
+ /* Quasar header yüksekliği */
+ --ol-header-h: 56px;
+ /* Filter bar yüksekliği (px) — inputlar tek satırsa 56 idealdir */
+ --ol-filter-h: 96px;
+}
+
+/* q-page tek scroller: header altından başlar */
+.ol-page {
+ height: calc(100vh - var(--ol-header-h));
+ overflow: auto; /* 🔑 tek scroll container */
+ background: #fff;
+ display: flex;
+ flex-direction: column;
+}
+
+/* Filter bar: q-header’ın altında sticky */
+.ol-filter-bar {
+ position: sticky;
+ top: 0; /* 🔑 .ol-page scroller’ında en üst */
+ z-index: 600;
+ background: #fff;
+ border-bottom: 1px solid #ddd;
+ padding: 10px 16px;
+ box-shadow: 0 1px 2px rgba(0,0,0,0.06);
+ min-height: var(--ol-filter-h);
+ display: flex;
+ align-items: center;
+}
+
+/* QTable: sticky thead, zebra aktif ve çakışma yok */
+.ol-table .q-table__middle {
+ overflow: visible !important; /* sticky thead için güvenli */
+ max-height: none !important;
+}
+
+/* thead sabitleme: filter bar’ın ALTINA oturur */
+.ol-table thead th {
+ position: sticky;
+ top: var(--ol-filter-h); /* 🔑 filter yüksekliği kadar boşluk */
+ z-index: 500;
+ background: #fff;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.08);
+ font-weight: 700;
+}
+
+/* Zebra */
+.ol-table .q-table__body .q-tr:nth-child(odd) {
+ background-color: #f7f7f7 !important;
+}
+.ol-table .q-table__body .q-tr:nth-child(even) {
+ background-color: #ffffff !important;
+}
+.ol-table .q-table__body .q-tr:hover {
+ background-color: #fff7d1 !important;
+ transition: background-color .15s ease;
+}
+
+/* Hücreler */
+.ol-table .q-td {
+ font-size: .9rem;
+ line-height: 1.3;
+ padding: 6px 8px !important;
+}
+
+/* Güvenli z-index hiyerarşisi */
+.q-header { z-index: 1000 !important; } /* header en üstte */
+.q-drawer { z-index: 950 !important; } /* drawer header’ın altında */
+
+/* Mobile */
+@media (max-width: 768px) {
+ :root { --ol-filter-h: 64px; } /* input kırılıyorsa biraz artır */
+ .ol-filter-bar { padding: 8px 12px; }
+}
+/* ===========================================================
+ 🟡 ORDERLIST ZEBRA FIX (v3)
+ =========================================================== */
+
+/* Her iki tr katmanını da hedefliyoruz (Quasar q-tr + native tr) */
+.ol-table tbody tr:nth-child(odd),
+.ol-table .q-table__body .q-tr:nth-child(odd) {
+ background-color: #faf8ef !important; /* açık krem tonu */
+}
+
+.ol-table tbody tr:nth-child(even),
+.ol-table .q-table__body .q-tr:nth-child(even) {
+ background-color: #ffffff !important;
+}
+
+/* Hover tonu: hafif Baggi gold dokunuşu */
+.ol-table tbody tr:hover,
+.ol-table .q-table__body .q-tr:hover {
+ background-color: #fff4cc !important;
+ transition: background-color 0.2s ease;
+}
diff --git a/ui/src/stores/deneme3 b/ui/src/stores/deneme3
new file mode 100644
index 0000000..84a44eb
--- /dev/null
+++ b/ui/src/stores/deneme3
@@ -0,0 +1,653 @@
+// src/stores/orderentryStore.js
+import { defineStore } from 'pinia'
+import axios from 'axios'
+import qs from 'qs'
+import { useAuthStore } from 'src/stores/authStore'
+import dayjs from 'src/boot/dayjs'
+import { ref, watch } from 'vue'
+
+/* ==========================================================
+ Reaktif shared referanslar (bazı UI yardımcıları)
+========================================================== */
+const stockMap = ref({}) // { "48": 12, "50": 7, ... }
+const bedenStock = ref([]) // [{beden:'48', stok:12}, ...]
+const sizeCache = ref({}) // beden/stok cache (component tarafı çağırıyor)
+
+/* ==========================================================
+ STORE
+========================================================== */
+export const useOrderentryStore = defineStore('orderentry', {
+ state: () => ({
+ /* 🔹 Ana durumlar */
+ orders: [], // grid kaynak array’i (summaryRows ile senkron)
+ loading: false,
+ selected: null, // UI’de seçili satır
+ error: null,
+
+ /* 🔹 Cari */
+ customers: [],
+ selectedCustomer: null,
+
+ /* 🔹 Ürün zinciri */
+ products: [],
+ colors: [],
+ secondColors: [],
+ inventory: [],
+
+ selectedProduct: null,
+ selectedColor: null,
+ selectedColor2: null,
+
+ /* 🔹 Transaction & Storage */
+ activeTransactionId: null,
+ persistKey: 'bss_orderentry_data', // ♻️ kalıcı depolama key’i
+ lastSnapshotKey: 'bss_orderentry_snapshot', // son-kaydedilen-sipariş
+
+ /* 🔹 Düzenleme durumu */
+ editingIndex: -1,
+ currentOrderId: null, // edit modunda header ID
+ header: {}, // backend header modeli
+ mode: 'new' // 'new' | 'edit'
+ }),
+
+ getters: {
+ /* 🔹 Toplam adet */
+ totalQty(state) {
+ return state.orders.reduce((sum, r) => sum + (Number(r.adet) || 0), 0)
+ },
+ /* 🔹 Toplam tutar (string fix2) */
+ totalAmount(state) {
+ const n = state.orders.reduce((s, r) => s + (Number(r.tutar) || 0), 0)
+ return isNaN(n) ? '0.00' : n.toFixed(2)
+ },
+ /* 🔹 Müşteri bazlı gruplanmış (opsiyonel) */
+ groupedByCustomer(state) {
+ const out = {}
+ for (const row of state.orders) {
+ const k = row.musteri || '—'
+ if (!out[k]) out[k] = []
+ out[k].push(row)
+ }
+ return out
+ },
+ /* 🔹 2. renk var mı? */
+ hasSecondColor(state) {
+ return Array.isArray(state.secondColors) && state.secondColors.length > 0
+ },
+ /* 🔹 Envanter toplamı */
+ totalInventoryQty(state) {
+ return state.inventory.reduce((s, r) => s + (Number(r.kullanilabilir) || 0), 0)
+ }
+ },
+ actions: {
+ /* ==========================================================
+ STORAGE — Kalıcı kayıt yardımcıları
+ ========================================================== */
+ saveToStorage() {
+ try {
+ const payload = {
+ orders: this.orders,
+ header: this.header,
+ currentOrderId: this.currentOrderId,
+ selectedCustomer: this.selectedCustomer,
+ activeTransactionId: this.activeTransactionId,
+ mode: this.mode,
+ savedAt: dayjs().toISOString()
+ }
+ localStorage.setItem(this.persistKey, JSON.stringify(payload))
+ } catch (err) {
+ console.warn('⚠️ localStorage kaydı başarısız:', err)
+ }
+ },
+
+ /* Kayıt sonrası görüntülenecek "snapshot".
+ UI’yi temizlesen bile sayfa yenilenince bu snapshot geri yüklenebilir. */
+ saveSnapshot(tag = 'post-submit') {
+ try {
+ const snap = {
+ tag,
+ orders: this.orders,
+ header: this.header,
+ currentOrderId: this.currentOrderId,
+ selectedCustomer: this.selectedCustomer,
+ mode: this.mode,
+ savedAt: dayjs().toISOString()
+ }
+ localStorage.setItem(this.lastSnapshotKey, JSON.stringify(snap))
+ } catch (e) {
+ console.warn('⚠️ saveSnapshot hatası:', e)
+ }
+ },
+
+ loadFromStorage() {
+ try {
+ const raw = localStorage.getItem(this.persistKey)
+ if (!raw) return
+ const data = JSON.parse(raw)
+ if (Array.isArray(data.orders)) this.orders = data.orders
+ this.header = data.header || {}
+ this.currentOrderId = data.currentOrderId || null
+ this.selectedCustomer = data.selectedCustomer || null
+ this.activeTransactionId = data.activeTransactionId || null
+ this.mode = data.mode || 'new'
+ console.log(`♻️ Storage yüklendi • mode:${this.mode} • rows:${this.orders.length}`)
+ } catch (err) {
+ console.warn('⚠️ localStorage okuma hatası:', err)
+ }
+ },
+
+ loadSnapshot() {
+ try {
+ const raw = localStorage.getItem(this.lastSnapshotKey)
+ if (!raw) return null
+ return JSON.parse(raw)
+ } catch (e) {
+ console.warn('⚠️ loadSnapshot hatası:', e)
+ return null
+ }
+ },
+
+ clearStorage() {
+ localStorage.removeItem(this.persistKey)
+ // snapshot’ı silmiyoruz → kullanıcı isterse elle siler
+ },
+ /* ==========================================================
+ TRANSACTION STATE
+ ========================================================== */
+ setTransaction(id) {
+ this.activeTransactionId = id
+ this.saveToStorage()
+ },
+
+ async initTransaction() {
+ if (this.activeTransactionId) {
+ console.log('🔹 Aktif transaction:', this.activeTransactionId)
+ return this.activeTransactionId
+ }
+ try {
+ const dummyId = Math.floor(100000 + Math.random() * 900000)
+ this.activeTransactionId = dummyId
+ this.saveToStorage()
+ console.log('🧩 Dummy Transaction başlatıldı:', dummyId)
+ return dummyId
+ } catch (err) {
+ console.error('❌ Dummy transaction başlatılamadı:', err)
+ return null
+ }
+ },
+
+ clearTransaction() {
+ this.activeTransactionId = null
+ this.saveToStorage()
+ },
+
+ /* Orders’ı otomatik kaydeden watcher (component’ten çağrılır) */
+ watchOrders() {
+ watch(
+ () => this.orders,
+ () => {
+ // her değişimde full storage yaz
+ this.saveToStorage()
+ },
+ { deep: true }
+ )
+ },
+ /* ==========================================================
+ CRUD — Frontend grid’i ile senkron temel aksiyonlar
+ ========================================================== */
+
+ addRow(row) {
+ if (!row) return
+ this.orders.push({ ...row })
+ this.saveToStorage()
+ },
+
+ updateRow(idOrIndex, patch) {
+ if (idOrIndex == null) return
+ let idx = -1
+ if (typeof idOrIndex === 'number') {
+ idx = idOrIndex
+ } else {
+ // id ile bul
+ idx = this.orders.findIndex(r => r.id === idOrIndex)
+ }
+ if (idx >= 0 && this.orders[idx]) {
+ this.orders[idx] = { ...this.orders[idx], ...patch }
+ this.saveToStorage()
+ }
+ },
+
+ removeRow(idOrIndex) {
+ let idx = -1
+ if (typeof idOrIndex === 'number') {
+ idx = idOrIndex
+ } else {
+ idx = this.orders.findIndex(r => r.id === idOrIndex)
+ }
+ if (idx >= 0) {
+ this.orders.splice(idx, 1)
+ this.saveToStorage()
+ }
+ },
+ /* ==========================================================
+ PRICE / LIMIT — Minimum fiyat sorgusu (model + PB)
+ Beklenen response: { price, priceTRY, rateToTRY }
+ ========================================================== */
+ async fetchMinPrice(modelCode, pb) {
+ if (!modelCode || !pb) return null
+ try {
+ const baseURL = 'http://localhost:8080'
+ const res = await axios.get(`${baseURL}/api/min-price`, {
+ params: { code: modelCode, pb }
+ })
+ const d = res?.data || null
+ if (!d) return null
+
+ // normalize
+ return {
+ price: Number(d.price ?? d.Price ?? 0),
+ priceTRY: Number(d.priceTRY ?? d.PriceTRY ?? d.price_try ?? 0),
+ rateToTRY: Number(d.rateToTRY ?? d.RateToTRY ?? d.rate ?? 1)
+ }
+ } catch (e) {
+ console.warn('⚠️ fetchMinPrice hata:', e)
+ return null
+ }
+ },
+ /* ==========================================================
+ LOAD (EDIT MODE) — Sunucudan Siparişi Açma
+ ========================================================== */
+ async openById(id) {
+ if (!id) return
+ this.loading = true
+ try {
+ const auth = useAuthStore()
+ const res = await axios.get(`http://localhost:8080/api/order/get/${id}`, {
+ headers: { Authorization: `Bearer ${auth.token}` }
+ })
+ const data = res.data || {}
+
+ // 🔹 sql.Null* flatten helper
+ const flat = (v) => {
+ if (v === null || v === undefined) return null
+ if (typeof v === 'object' && 'Valid' in v) {
+ return v.Valid
+ ? v.String ?? v.Float64 ?? v.Int32 ?? v.Time ?? null
+ : null
+ }
+ return v
+ }
+
+ /* ============================================================
+ 🧾 HEADER MAPPING (73 kolon)
+ ============================================================ */
+ const h = data.header || {}
+ const header = {
+ // Görünen alanlar
+ OrderHeaderID: flat(h.OrderHeaderID) || '',
+ OrderNumber: flat(h.OrderNumber) || '',
+ OrderDate: flat(h.OrderDate)
+ ? String(flat(h.OrderDate)).substring(0, 10)
+ : '',
+ AverageDueDate: flat(h.AverageDueDate)
+ ? String(flat(h.AverageDueDate)).substring(0, 10)
+ : '',
+ Description: flat(h.Description) || '',
+ CurrAccCode: flat(h.CurrAccCode) || '',
+ DocCurrencyCode: flat(h.DocCurrencyCode) || 'TRY',
+
+ // Arka plan alanlar (backend roundtrip)
+ OrderTypeCode: flat(h.OrderTypeCode) || 1,
+ ProcessCode: flat(h.ProcessCode) || 'WS',
+ IsCancelOrder: flat(h.IsCancelOrder) || 0,
+ OrderTime: flat(h.OrderTime) || '',
+ DocumentNumber: flat(h.DocumentNumber) || '',
+ PaymentTerm: flat(h.PaymentTerm) || '',
+ InternalDescription: flat(h.InternalDescription) || '',
+ CurrAccTypeCode: flat(h.CurrAccTypeCode) || '',
+ SubCurrAccID: flat(h.SubCurrAccID) || '',
+ ContactID: flat(h.ContactID) || '',
+ ShipmentMethodCode: flat(h.ShipmentMethodCode) || '',
+ ShippingPostalAddressID: flat(h.ShippingPostalAddressID) || '',
+ BillingPostalAddressID: flat(h.BillingPostalAddressID) || '',
+ GuarantorContactID: flat(h.GuarantorContactID) || '',
+ GuarantorContactID2: flat(h.GuarantorContactID2) || '',
+ RoundsmanCode: flat(h.RoundsmanCode) || '',
+ DeliveryCompanyCode: flat(h.DeliveryCompanyCode) || '',
+ TaxTypeCode: flat(h.TaxTypeCode) || '',
+ WithHoldingTaxTypeCode: flat(h.WithHoldingTaxTypeCode) || '',
+ DOVCode: flat(h.DOVCode) || '',
+ TaxExemptionCode: flat(h.TaxExemptionCode) || 0,
+ CompanyCode: flat(h.CompanyCode) || 1,
+ OfficeCode: flat(h.OfficeCode) || '101',
+ StoreTypeCode: flat(h.StoreTypeCode) || 5,
+ StoreCode: flat(h.StoreCode) || 0,
+ POSTerminalID: flat(h.POSTerminalID) || 0,
+ WarehouseCode: flat(h.WarehouseCode) || '1-0-12',
+ ToWarehouseCode: flat(h.ToWarehouseCode) || '',
+ OrdererCompanyCode: flat(h.OrdererCompanyCode) || 1,
+ OrdererOfficeCode: flat(h.OrdererOfficeCode) || '101',
+ OrdererStoreCode: flat(h.OrdererStoreCode) || '',
+ GLTypeCode: flat(h.GLTypeCode) || '',
+ LocalCurrencyCode: flat(h.LocalCurrencyCode) || 'TRY',
+ ExchangeRate: flat(h.ExchangeRate) || 1,
+ DiscountReasonCode: flat(h.DiscountReasonCode) || 0,
+ SurplusOrderQtyToleranceRate: flat(h.SurplusOrderQtyToleranceRate) || 0,
+ IncotermCode1: flat(h.IncotermCode1) || '',
+ IncotermCode2: flat(h.IncotermCode2) || '',
+ PaymentMethodCode: flat(h.PaymentMethodCode) || '',
+ IsInclutedVat: flat(h.IsInclutedVat) || 0,
+ IsCreditSale: flat(h.IsCreditSale) || 1,
+ IsCreditableConfirmed: flat(h.IsCreditableConfirmed) || 1,
+ CreditableConfirmedUser: flat(h.CreditableConfirmedUser) || '',
+ CreditableConfirmedDate: flat(h.CreditableConfirmedDate) || '',
+ ApplicationCode: flat(h.ApplicationCode) || 'Order',
+ ApplicationID: flat(h.ApplicationID) || '',
+ CreatedUserName: flat(h.CreatedUserName) || '',
+ CreatedDate: flat(h.CreatedDate) || '',
+ LastUpdatedUserName: flat(h.LastUpdatedUserName) || '',
+ LastUpdatedDate: flat(h.LastUpdatedDate) || '',
+ IsProposalBased: flat(h.IsProposalBased) || 0
+ }
+
+ this.header = header
+ this.currentOrderId = header.OrderHeaderID || id
+ this.mode = 'edit'
+
+ // 🔹 Cari görünümü (QSelect)
+ this.selectedCustomer = {
+ value: header.CurrAccCode || '',
+ label: `${header.CurrAccCode || ''} - ${flat(h.CurrAccDescription) || ''}`
+ }
+
+ /* ============================================================
+ 📦 LINES MAPPING (57 kolon)
+ ============================================================ */
+ this.orders = (data.lines || []).map((l, idx) => ({
+ // Görünen alanlar
+ id: flat(l.OrderLineID) || `row-${idx + 1}`,
+ model: flat(l.ItemCode),
+ renk: flat(l.ColorCode),
+ renk2: flat(l.ItemDim2Code),
+ fiyat: Number(flat(l.Price) || 0),
+ pb: flat(l.DocCurrencyCode) || flat(l.PriceCurrencyCode) || 'USD',
+ adet: Number(flat(l.Qty1) || 0),
+ tutar: Number(flat(l.Price) || 0) * Number(flat(l.Qty1) || 0),
+ aciklama: flat(l.LineDescription) || '',
+ terminTarihi: flat(l.DeliveryDate)
+ ? String(flat(l.DeliveryDate)).substring(0, 10)
+ : '',
+ urunAnaGrubu: flat(l.ProductGroup) || '',
+ urunAltGrubu: flat(l.ProductSubGroup) || '',
+ grpKey: l.grpKey || 'tak',
+ bedenMap: l.BedenMap || {},
+
+ // Backend roundtrip alanları
+ SortOrder: flat(l.SortOrder) || 0,
+ ItemTypeCode: flat(l.ItemTypeCode) || 1,
+ ItemDim1Code: flat(l.ItemDim1Code) || '',
+ ItemDim3Code: flat(l.ItemDim3Code) || '',
+ Qty2: flat(l.Qty2) || 0,
+ CancelQty1: flat(l.CancelQty1) || 0,
+ CancelQty2: flat(l.CancelQty2) || 0,
+ CancelDate: flat(l.CancelDate) || null,
+ OrderCancelReasonCode: flat(l.OrderCancelReasonCode) || '',
+ ClosedDate: flat(l.ClosedDate) || null,
+ IsClosed: flat(l.IsClosed) || false,
+ VatRate: flat(l.VatRate) || 10,
+ PCTRate: flat(l.PCTRate) || 0,
+ PriceCurrencyCode: flat(l.PriceCurrencyCode) || 'TRY',
+ PriceExchangeRate: flat(l.PriceExchangeRate) || header.ExchangeRate || 1,
+ CreatedUserName: flat(l.CreatedUserName) || '',
+ CreatedDate: flat(l.CreatedDate) || '',
+ LastUpdatedUserName: flat(l.LastUpdatedUserName) || '',
+ LastUpdatedDate: flat(l.LastUpdatedDate) || '',
+ SurplusOrderQtyToleranceRate:
+ flat(l.SurplusOrderQtyToleranceRate) || 0
+ }))
+
+ /* ============================================================
+ 💾 LOCAL STORAGE
+ ============================================================ */
+ localStorage.setItem(
+ `bssapp:order:last:${id}`,
+ JSON.stringify({ header, lines: this.orders })
+ )
+
+ console.log(`📦 Sipariş (${id}) yüklendi • rows:${this.orders.length}`)
+ } catch (err) {
+ console.error('❌ openById hatası:', err)
+ this.error = err.message
+ } finally {
+ this.loading = false
+ }
+}
+,
+ /* ==========================================================
+ NEW TEMPLATE — Yeni sipariş başlatma
+ ========================================================== */
+ newOrderTemplate() {
+ const today = dayjs().format('YYYY-MM-DD')
+ const due = dayjs().add(30, 'day').format('YYYY-MM-DD')
+
+ this.header = {
+ OrderHeaderID: '',
+ OrderTypeCode: 1,
+ ProcessCode: 'WS',
+ OrderNumber: '',
+ OrderDate: today,
+ AverageDueDate: due,
+ Description: '',
+ CurrAccCode: '',
+ CurrAccDescription: '',
+ DocCurrencyCode: 'USD',
+ LocalCurrencyCode: 'TRY',
+ ExchangeRate: 1,
+ CompanyCode: 1,
+ OfficeCode: '101',
+ StoreTypeCode: 5,
+ WarehouseCode: '1-0-12',
+ IsCreditSale: true,
+ CreatedUserName: '',
+ CreatedDate: today,
+ LastUpdatedUserName: '',
+ LastUpdatedDate: today
+ }
+
+ this.orders = []
+ this.currentOrderId = null
+ this.activeTransactionId = null
+ this.selectedCustomer = null
+ this.mode = 'new'
+ this.error = null
+
+ // Temiz bir başlangıcı storage’a yaz
+ this.saveToStorage()
+ console.log('🧾 Yeni sipariş template yüklendi.')
+ },
+ /* ==========================================================
+ SUBMIT — Create/Update (SQL tablo INSERT/UPDATE)
+ ➜ Kayıt sonrası: transaction kapanır AMA snapshot tutulur.
+ ========================================================== */
+ async submitAll() {
+ const auth = useAuthStore()
+ const baseURL = 'http://localhost:8080'
+
+ const toNullable = (v, type = 'string') => {
+ if (v === null || v === undefined || v === '') {
+ if (type === 'number') return { Float64: 0, Valid: false }
+ if (type === 'time') return { Time: null, Valid: false }
+ return { String: '', Valid: false }
+ }
+ if (type === 'number') return { Float64: Number(v), Valid: true }
+ if (type === 'time') return { Time: v, Valid: true }
+ return { String: String(v), Valid: true }
+ }
+
+ try {
+ this.loading = true
+
+ // Header payload (backend’in beklediği Null* formatıyla)
+ const h = this.header || {}
+ const headerPayload = {
+ OrderHeaderID: h.OrderHeaderID || this.currentOrderId || '',
+ OrderTypeCode: toNullable(1, 'number'),
+ ProcessCode: toNullable('WS'),
+ OrderNumber: toNullable(h.OrderNumber),
+ OrderDate: toNullable(h.OrderDate || dayjs().format('YYYY-MM-DD'), 'time'),
+ AverageDueDate: toNullable(h.AverageDueDate || dayjs().add(30, 'day').format('YYYY-MM-DD'), 'time'),
+ Description: toNullable(h.Description || ''),
+ CurrAccCode: toNullable(h.CurrAccCode || this.selectedCustomer?.value || ''),
+ CurrAccDescription: toNullable(h.CurrAccDescription || this.selectedCustomer?.label || ''),
+ DocCurrencyCode: toNullable(h.DocCurrencyCode || 'USD'),
+ LocalCurrencyCode: toNullable(h.LocalCurrencyCode || 'TRY'),
+ ExchangeRate: toNullable(h.ExchangeRate || 1, 'number'),
+ CompanyCode: toNullable(1, 'number'),
+ OfficeCode: toNullable('101'),
+ StoreTypeCode: toNullable(5, 'number'),
+ WarehouseCode: toNullable(h.WarehouseCode || '1-0-12'),
+ IsCreditSale: true,
+ CreatedUserName: toNullable(auth.user?.Username || 'admin'),
+ CreatedDate: toNullable(h.CreatedDate || dayjs().format('YYYY-MM-DD'), 'time'),
+ LastUpdatedUserName: toNullable(auth.user?.Username || 'admin'),
+ LastUpdatedDate: toNullable(dayjs().format('YYYY-MM-DD HH:mm:ss'), 'time')
+ }
+
+ // Lines payload
+ const linesPayload = this.orders.map((l, idx) => ({
+ OrderLineID: l.id || '',
+ SortOrder: idx + 1,
+ ItemTypeCode: toNullable(1, 'number'),
+ ItemCode: toNullable(l.model),
+ ColorCode: toNullable(l.renk),
+ ItemDim1Code: toNullable(Object.keys(l.bedenMap?.[l.grpKey] || {})[0] || ''),
+ ItemDim2Code: toNullable(l.renk2),
+ Qty1: toNullable(Number(l.adet || 0), 'number'),
+ Price: toNullable(Number(l.fiyat || 0), 'number'),
+ DocCurrencyCode: toNullable(l.pb || 'USD'),
+ VatRate: toNullable(10, 'number'),
+ PCTRate: toNullable(0, 'number'),
+ DeliveryDate: toNullable(l.terminTarihi || null, 'time'),
+ LineDescription: toNullable(l.aciklama || ''),
+ IsClosed: false,
+ CreatedUserName: toNullable(auth.user?.Username || 'admin'),
+ CreatedDate: toNullable(dayjs().format('YYYY-MM-DD HH:mm:ss'), 'time'),
+ LastUpdatedUserName: toNullable(auth.user?.Username || 'admin'),
+ LastUpdatedDate: toNullable(dayjs().format('YYYY-MM-DD HH:mm:ss'), 'time')
+ }))
+
+ // Final payload
+ const payload = {
+ header: headerPayload,
+ lines: linesPayload,
+ user: auth.user?.Username || 'admin'
+ }
+
+ let res
+ if (this.currentOrderId) {
+ // UPDATE
+ res = await axios.post(`${baseURL}/api/order/update`, payload, {
+ headers: { Authorization: `Bearer ${auth.token}` }
+ })
+ console.log('✅ UPDATE ok:', res.data)
+ } else {
+ // CREATE
+ res = await axios.post(`${baseURL}/api/order/create`, payload, {
+ headers: { Authorization: `Bearer ${auth.token}` }
+ })
+ console.log('✅ CREATE ok:', res.data)
+ if (res.data?.orderID) {
+ this.currentOrderId = res.data.orderID
+ this.header.OrderHeaderID = res.data.orderID
+ this.mode = 'edit'
+ }
+ }
+
+ // 🟩 Kayıt sonrası: snapshot’ı al ve storage’a da yaz
+ this.saveSnapshot('post-submit')
+ this.saveToStorage()
+
+ // 🧹 Transaction’ı kapat (UI temizliği ayrı fonksiyonda)
+ this.clearTransaction()
+ this.afterSubmit({ keepLocalStorage: true }) // 👈 önemli
+
+ } catch (err) {
+ console.error('❌ submitAll hatası:', err)
+ this.error = err.message
+ throw err
+ } finally {
+ this.loading = false
+ }
+ },
+ /* ==========================================================
+ AFTER SUBMIT — UI temizliği (snapshot kalır!)
+ keepLocalStorage=true → persistKey SİLİNMEZ
+ ========================================================== */
+ afterSubmit(opts = { keepLocalStorage: true }) {
+ try {
+ // Snapshot zaten kaydedildi; istenirse persistKey’i bırak
+ if (!opts?.keepLocalStorage) {
+ localStorage.removeItem(this.persistKey)
+ } else {
+ // son hal zaten saveToStorage ile yazıldı — dokunma
+ }
+
+ // UI temizliği (hafızada formu boşaltalım)
+ // Ama edit’e dönmek istersen, snapshot/loadFromStorage ile geri getirirsin.
+ this.orders = []
+ // header’ı hafızadan temizliyoruz ama snapshot yerinde.
+ this.header = {}
+ this.selectedCustomer = null
+ this.editingIndex = -1
+ // currentOrderId’yi istersen koruyabilirsin; biz editte geri yüklüyoruz.
+ // burada null’lıyoruz:
+ this.currentOrderId = null
+ this.mode = 'new'
+ this.loading = false
+ this.error = null
+
+ console.log('🧹 afterSubmit: UI temizlendi, snapshot storage’da.')
+ } catch (err) {
+ console.warn('⚠️ afterSubmit temizleme hatası:', err)
+ }
+ },
+
+ /* ==========================================================
+ MANUAL UPDATE — mevcut header/lines yapılarına göre
+ (İsteğe bağlı kullanılır)
+ ========================================================== */
+ async updateOrder() {
+ if (!this.currentOrderId) {
+ console.warn('⚠️ currentOrderId yok, update yapılamaz.')
+ return
+ }
+ try {
+ const auth = useAuthStore()
+ const payload = {
+ header: this.header,
+ lines: this.orders,
+ username: auth.user?.Username || 'admin'
+ }
+ const res = await axios.post(
+ 'http://localhost:8080/api/order/update',
+ payload,
+ { headers: { Authorization: `Bearer ${auth.token}` } }
+ )
+ console.log('✅ Güncelleme tamamlandı:', res.data)
+
+ // kayıt sonrası snapshot + persist
+ this.saveSnapshot('manual-update')
+ this.saveToStorage()
+ } catch (err) {
+ console.error('❌ updateOrder hatası:', err)
+ this.error = err.message
+ }
+ }
+ } // actions
+}) // defineStore
+// (opsiyonel) Bu referanslara component tarafından erişmek istersen:
+export const sharedOrderEntryRefs = {
+ stockMap,
+ bedenStock,
+ sizeCache
+}
diff --git a/ui/src/stores/downloadstHeadStore.js b/ui/src/stores/downloadstHeadStore.js
new file mode 100644
index 0000000..ad60b1f
--- /dev/null
+++ b/ui/src/stores/downloadstHeadStore.js
@@ -0,0 +1,50 @@
+// src/stores/downloadstHeadStore.js
+import { defineStore } from 'pinia'
+import { download } from 'src/services/api'
+
+export const useDownloadstHeadStore = defineStore('downloadstHead', {
+ actions: {
+ // 📄 Statement Header PDF indir / aç
+ async handlestHeadDownload (
+ accountCode,
+ startDate,
+ endDate,
+ parislemler
+ ) {
+ try {
+ // ✅ Params (axios paramsSerializer array=repeat destekliyor)
+ const params = {
+ accountcode: accountCode,
+ startdate: startDate,
+ enddate: endDate
+ }
+
+ if (Array.isArray(parislemler) && parislemler.length > 0) {
+ params.parislemler = parislemler.filter(
+ p => p !== undefined && p !== null && p !== ''
+ )
+ }
+
+ // 🔥 API CALL (TOKEN + BLOB + ERROR HANDLING OTOMATİK)
+ const blob = await download(
+ '/exportstamentheaderreport-pdf',
+ params
+ )
+
+ const pdfUrl = window.URL.createObjectURL(blob)
+ window.open(pdfUrl, '_blank')
+
+ return { ok: true, message: '📄 PDF hazırlandı' }
+
+ } catch (err) {
+ console.error('❌ PDF açma hatası:', err)
+ return {
+ ok: false,
+ message:
+ err?.message ||
+ '❌ PDF açma hatası'
+ }
+ }
+ }
+ }
+})
diff --git a/ui/src/stores/downloadstpdfStore.js b/ui/src/stores/downloadstpdfStore.js
new file mode 100644
index 0000000..d857edd
--- /dev/null
+++ b/ui/src/stores/downloadstpdfStore.js
@@ -0,0 +1,51 @@
+// src/stores/downloadstpdfStore.js
+import { defineStore } from 'pinia'
+import { download } from 'src/services/api'
+
+export const useDownloadstpdfStore = defineStore('downloadstpdf', {
+ actions: {
+ /* ==========================================================
+ 📄 PDF İNDİR / AÇ
+ ========================================================== */
+ async downloadPDF(accountCode, startDate, endDate, parislemler = []) {
+ try {
+ // 🔹 Query params
+ const params = {
+ accountcode: accountCode,
+ startdate: startDate,
+ enddate: endDate
+ }
+
+ if (Array.isArray(parislemler) && parislemler.length > 0) {
+ params.parislemler = parislemler.filter(
+ p => p !== undefined && p !== null && p !== ''
+ )
+ }
+
+ // 🔥 MERKEZİ API — BLOB
+ const blob = await download('/export-pdf', params)
+
+ // 🔹 Blob → URL
+ const pdfUrl = window.URL.createObjectURL(
+ new Blob([blob], { type: 'application/pdf' })
+ )
+
+ // 🔹 Yeni sekmede aç
+ window.open(pdfUrl, '_blank')
+
+ console.log('✅ PDF yeni sekmede açıldı')
+ return { ok: true, message: '📄 PDF hazırlandı' }
+
+ } catch (err) {
+ console.error('❌ PDF açma hatası:', err)
+
+ return {
+ ok: false,
+ message:
+ err?.message ||
+ '❌ PDF alınamadı'
+ }
+ }
+ }
+ }
+})
diff --git a/ui/src/stores/example-store.js b/ui/src/stores/example-store.js
new file mode 100644
index 0000000..83e8390
--- /dev/null
+++ b/ui/src/stores/example-store.js
@@ -0,0 +1,15 @@
+import { defineStore } from 'pinia';
+
+export const useCounterStore = defineStore('counter', {
+ state: () => ({
+ counter: 0,
+ }),
+ getters: {
+ doubleCount: (state) => state.counter * 2,
+ },
+ actions: {
+ increment() {
+ this.counter++;
+ },
+ },
+});
diff --git a/ui/src/stores/index.js b/ui/src/stores/index.js
new file mode 100644
index 0000000..a260a8c
--- /dev/null
+++ b/ui/src/stores/index.js
@@ -0,0 +1,20 @@
+import { defineStore } from '#q-app/wrappers'
+import { createPinia } from 'pinia'
+
+/*
+ * If not building with SSR mode, you can
+ * directly export the Store instantiation;
+ *
+ * The function below can be async too; either use
+ * async/await or return a Promise which resolves
+ * with the Store instance.
+ */
+
+export default defineStore((/* { ssrContext } */) => {
+ const pinia = createPinia()
+
+ // You can add Pinia plugins here
+ // pinia.use(SomePiniaPlugin)
+
+ return pinia
+})
diff --git a/ui/src/stores/mailTestStore.js b/ui/src/stores/mailTestStore.js
new file mode 100644
index 0000000..c3e4091
--- /dev/null
+++ b/ui/src/stores/mailTestStore.js
@@ -0,0 +1,29 @@
+import { defineStore } from 'pinia'
+import { post } from 'src/services/api'
+
+export const useMailTestStore = defineStore('mailTest', {
+ state: () => ({
+ loading: false,
+ lastResult: null
+ }),
+
+ actions: {
+ async sendTestMail (to) {
+ this.loading = true
+ try {
+ const data = await post('/test-mail', {
+ to
+ })
+
+ this.lastResult = data
+ return true
+ } catch (err) {
+ this.lastResult = err
+ throw err
+ } finally {
+ this.loading = false
+ }
+ }
+ }
+})
+
diff --git a/ui/src/stores/mePasswordStore.js b/ui/src/stores/mePasswordStore.js
new file mode 100644
index 0000000..61bc99b
--- /dev/null
+++ b/ui/src/stores/mePasswordStore.js
@@ -0,0 +1,38 @@
+// src/stores/mePasswordStore.js
+import { defineStore } from 'pinia'
+import api from 'src/services/api'
+
+export const useMePasswordStore = defineStore('mePassword', {
+ state: () => ({
+ loading: false,
+ error: null
+ }),
+
+ actions: {
+ async changePassword (currentPassword, newPassword) {
+ this.loading = true
+ this.error = null
+
+ try {
+ // 🔐 Token interceptor ile otomatik
+ await api.post('/me/password', {
+ current_password: currentPassword,
+ new_password: newPassword
+ })
+
+ return true
+
+ } catch (e) {
+ // 🔥 api.js normalize error
+ this.error =
+ e?.message ||
+ 'Şifre güncellenemedi'
+
+ throw e
+
+ } finally {
+ this.loading = false
+ }
+ }
+ }
+})
diff --git a/ui/src/stores/orderentryStore.js b/ui/src/stores/orderentryStore.js
new file mode 100644
index 0000000..3019e53
--- /dev/null
+++ b/ui/src/stores/orderentryStore.js
@@ -0,0 +1,3340 @@
+/* ===========================================================
+ 📦 orderentryStore.js (v3.4 CLEAN — AUTH + LOCAL PERSIST + AUTO RESUME)
+=========================================================== */
+
+import { defineStore } from 'pinia'
+import api from 'src/services/api'
+import dayjs from 'src/boot/dayjs'
+import { ref, toRaw, nextTick } from 'vue' // ✅ düzeltildi
+import { useAuthStore } from 'src/stores/authStore'
+
+// ===========================================================
+// 🔹 Shared Reactive Referanslar (Global, Reaktif Nesneler)
+// ===========================================================
+/* ===========================================================
+ 🔹 BEDEN ŞEMALARI — STORE SOURCE OF TRUTH
+=========================================================== */
+// ⬆️ orderentryStore.js EN ÜSTÜNE
+// ===========================================================
+// 🔑 COMBO KEY CONTRACT (Frontend ↔ Backend) — v1
+// - trim + UPPER
+// - dim1 boşsa " "
+// - dim2 boşsa ""
+// ===========================================================
+const BEDEN_EMPTY = '_'
+
+const norm = (v) => (v == null ? '' : String(v)).trim()
+const normUpper = (v) => norm(v).toUpperCase()
+
+export function buildComboKey(row, beden) {
+ const model = normUpper(row?.model || row?.ItemCode)
+ const renk = normUpper(row?.renk || row?.ColorCode)
+ const renk2 = normUpper(row?.renk2 || row?.ItemDim2Code)
+ const bdn = normUpper(beden)
+ const bedenFinal = bdn === '' ? BEDEN_EMPTY : bdn
+
+ // 🔒 KANONİK SIRA
+ return `${model}||${renk}||${renk2}||${bedenFinal}`
+}
+
+
+
+
+
+
+export const BEDEN_SCHEMA = [
+ { key: 'ayk', title: 'AYAKKABI', values: ['39','40','41','42','43','44','45'] },
+ { key: 'yas', title: 'YAŞ', values: ['2','4','6','8','10','12','14'] },
+ { key: 'pan', title: 'PANTOLON', values: ['38','40','42','44','46','48','50','52','54','56','58','60','62','64','66','68'] },
+ { key: 'gom', title: 'GÖMLEK', values: ['XS','S','M','L','XL','2XL','3XL','4XL','5XL','6XL','7XL'] },
+ { key: 'tak', title: 'TAKIM ELBİSE', values: ['44','46','48','50','52','54','56','58','60','62','64','66','68','70','72','74'] },
+ { key: 'aksbir', title: 'AKSESUAR', values: [' ', '44', 'STD', '110CM', '115CM', '120CM', '125CM', '130CM', '135CM'] }
+]
+
+export const schemaByKey = BEDEN_SCHEMA.reduce((m, g) => {
+ m[g.key] = g
+ return m
+}, {})
+
+
+export const stockMap = ref({})
+export const bedenStock = ref([])
+export const sizeCache = ref({})
+// ===========================================================
+// 🔹 Shared Reactive Referanslar (Global, Reaktif Nesneler)
+// ===========================================================
+// ========================
+// 🧰 GLOBAL DATE NORMALIZER
+// ========================
+
+function newGuid() {
+ return crypto.randomUUID()
+}
+
+
+
+// 🔑 Her beden satırı için deterministik clientKey üretimi
+function makeLineClientKey(row, grpKey, beden) {
+ const base =
+ row.clientRowKey ||
+ row.clientKey ||
+ row.id ||
+ row._id ||
+ row.tmpId ||
+ `${row.model || ''}|${row.renk || ''}|${row.renk2 || ''}`
+
+ return `${base}::${grpKey}::${beden}`
+}
+
+
+
+// ===========================================================
+// 🧩 Pinia Store — ORDER ENTRY STORE (REV 2025-11-03.2)
+// ===========================================================
+export const useOrderEntryStore = defineStore('orderentry', {
+ state: () => ({
+
+ isControlledSubmit: false,
+
+ allowRouteLeaveOnce: false,
+ schemaMap: {},
+ productCache: {},
+ _lastSavedFingerprint: null,
+
+ activeNewHeaderId: localStorage.getItem("bss_active_new_header") || null,
+
+ loading: false,
+ selected: null,
+ error: null,
+
+ customers: [],
+ selectedCustomer: null,
+
+ products: [],
+ colors: [],
+ secondColors: [],
+ inventory: [],
+
+ selectedProduct: null,
+ selectedColor: null,
+ selectedColor2: null,
+
+ OrderHeaderID: null,
+
+ // Persist config
+ persistKey: 'bss_orderentry_data',
+ lastSnapshotKey: 'bss_orderentry_snapshot',
+
+ // Editor state
+ editingKey: null,
+ currentOrderId: null,
+ mode: 'new',
+
+ // Grid state
+ orders: [],
+ header: {},
+ summaryRows: [],
+
+ lastSavedAt: null,
+
+ // Guards
+ preventPersist: false,
+ _uiBusy: false,
+ _unsavedChanges: false,
+ }),
+
+ getters: {
+ getDraftKey() {
+ // NEW taslak → GLOBAL ama tekil
+ return 'bss_orderentry_new_draft'
+ },
+
+ getEditKey() {
+ // EDIT → OrderHeaderID’ye bağlı
+ const id = this.header?.OrderHeaderID
+ return id ? `bss_orderentry_edit:${id}` : null
+ }
+ ,
+ hasUnsavedChanges(state) {
+ try {
+ return (
+ state._lastSavedFingerprint !==
+ state._persistFingerprint?.()
+ )
+ } catch {
+ return false
+ }
+ },
+
+ getPersistKey: (state) =>
+ state.header?.OrderHeaderID
+ ? `${state.persistKey}:${state.header.OrderHeaderID}`
+ : state.persistKey,
+
+ getSnapshotKey: (state) =>
+ state.header?.OrderHeaderID
+ ? `${state.lastSnapshotKey}:${state.header.OrderHeaderID}`
+ : state.lastSnapshotKey,
+
+ totalQty: (state) =>
+ (state.orders || []).reduce((sum, r) => sum + (Number(r?.adet) || 0), 0),
+
+ hasAnyClosedLine(state) {
+ return Array.isArray(state.summaryRows) &&
+ state.summaryRows.some(r => r?.isClosed === true)
+ },
+
+ totalAmount(state) {
+ if (!Array.isArray(state.summaryRows)) return 0
+ return state.summaryRows.reduce(
+ (sum, r) => sum + Number(r?.tutar || 0),
+ 0
+ )
+ }
+ },
+
+ actions: {
+
+ normalizeComboUI(row) {
+ return buildComboKey(row, BEDEN_EMPTY)
+ }
+
+ ,
+ /* ===========================================================
+ 🧩 initSchemaMap — BEDEN ŞEMA İNİT
+ - TEK SOURCE OF TRUTH: BEDEN_SCHEMA
+=========================================================== */
+ initSchemaMap() {
+ if (this.schemaMap && Object.keys(this.schemaMap).length > 0) {
+ return
+ }
+
+ const map = {}
+
+ for (const g of BEDEN_SCHEMA) {
+ map[g.key] = {
+ key: g.key,
+ title: g.title,
+ values: [...g.values]
+ }
+ }
+
+ this.schemaMap = map
+
+ console.log(
+ '🧩 schemaMap INIT edildi:',
+ Object.keys(this.schemaMap)
+ )
+ },
+
+
+ getRowKey(row) {
+ if (!row) return null
+ return row.OrderLineID || row.id || null
+ }
+
+
+ ,
+ updateHeaderTotals() {
+ try {
+ if (!Array.isArray(this.summaryRows)) return 0
+
+ const total = this.summaryRows.reduce(
+ (sum, r) => sum + Number(r?.tutar || 0),
+ 0
+ )
+
+ // Header sadece GÖSTERİM / BACKEND için
+ if (this.header) {
+ this.header.TotalAmount = Number(total.toFixed(2))
+ }
+
+ return total
+ } catch (err) {
+ console.error('❌ updateHeaderTotals hata:', err)
+ return 0
+ }
+ }
+
+ ,
+ /* ===========================================================
+ 🚨 showInvalidVariantDialog — FINAL
+ -----------------------------------------------------------
+ ✔ prItemVariant olmayan satırları listeler
+ ✔ Satıra tıkla → scroll + highlight
+ ✔ Kaydı BLOKLAYAN tek UI noktası
+ =========================================================== */
+ async showInvalidVariantDialog($q, invalidList = []) {
+ if (!Array.isArray(invalidList) || invalidList.length === 0) return
+
+ return new Promise(resolve => {
+ $q.dialog({
+ title: '🚨 Tanımsız Ürün Kombinasyonları',
+ message: `
+
+ ${invalidList.map((v, i) => `
+
+
+ #${i + 1} | Item: ${v.itemCode}
+
+
+ Beden: ${v.dim1 || '(boş)'} |
+ Renk: ${v.colorCode || '-'} |
+ Qty: ${v.qty1}
+
+
+ Sebep: ${v.reason || 'Tanımsız ürün kombinasyonu'}
+
+
+ `).join('')}
+
+ `,
+ html: true,
+ ok: {
+ label: 'Düzelt',
+ color: 'negative'
+ },
+ cancel: false,
+ persistent: true
+ })
+ .onOk(() => resolve())
+ .onDismiss(() => resolve())
+ .onShown(() => {
+ // Satıra tıklama → scroll + highlight
+ const nodes = document.querySelectorAll('.invalid-row')
+ nodes.forEach(n => {
+ n.addEventListener('click', () => {
+ const ck = n.getAttribute('data-clientkey')
+ this.scrollToInvalidRow?.(ck)
+ })
+ })
+ })
+ })
+ }
+ ,
+ /* ===========================================================
+ 🎯 scrollToInvalidRow — FINAL
+ -----------------------------------------------------------
+ ✔ ClientKey bazlı scroll
+ ✔ Hem summaryRows hem orders destekli
+ ✔ Highlight otomatik kalkar
+ =========================================================== */
+ scrollToInvalidRow(clientKey) {
+ if (!clientKey) return
+
+ // 1️⃣ Store içindeki satırı bul
+ const idx = this.summaryRows?.findIndex(
+ r => r.clientKey === clientKey
+ )
+
+ if (idx === -1) {
+ console.warn('❌ Satır bulunamadı:', clientKey)
+ return
+ }
+
+ // 2️⃣ DOM node
+ const el = document.querySelector(
+ `[data-clientkey="${clientKey}"]`
+ )
+
+ if (!el) {
+ console.warn('❌ DOM satırı bulunamadı:', clientKey)
+ return
+ }
+
+ // 3️⃣ Scroll
+ el.scrollIntoView({
+ behavior: 'smooth',
+ block: 'center'
+ })
+
+ // 4️⃣ Highlight
+ el.classList.add('invalid-highlight')
+
+ setTimeout(() => {
+ el.classList.remove('invalid-highlight')
+ }, 2500)
+ }
+ ,
+
+ async checkHeaderExists(orderHeaderID) {
+ try {
+ if (!orderHeaderID) return false
+
+ const res = await api.get(`/orders/check/${orderHeaderID}`)
+
+ // Backend “true/false” döner varsayımı
+ return res?.data?.exists === true
+ } catch (err) {
+ console.warn("⚠ checkHeaderExists hata:", err)
+ return false
+ }
+ }
+ ,
+
+ async fetchOrderPdf(orderId) {
+ try {
+ const resp = await api.get(`/order/pdf/${orderId}`, {
+ responseType: 'blob'
+ })
+ return resp.data
+ } catch (err) {
+ console.error("❌ fetchOrderPdf hata:", err)
+ throw err
+ }
+ }
+ ,
+
+ async downloadOrderPdf(id = null) {
+ try {
+ const orderId = id || this.header?.OrderHeaderID
+ if (!orderId) {
+ console.error('❌ PDF ID bulunamadı')
+ return
+ }
+
+ const res = await api.get(`/order/pdf/${orderId}`, {
+ responseType: 'blob'
+ })
+
+ const blob = new Blob([res.data], { type: 'application/pdf' })
+ const url = URL.createObjectURL(blob)
+
+ window.open(url, '_blank')
+ setTimeout(() => URL.revokeObjectURL(url), 60_000)
+
+ } catch (err) {
+ console.error('❌ PDF açma hatası:', err)
+ throw err
+ }
+ }
+
+
+
+
+
+ ,
+ setActiveNewHeader(id) {
+ this.activeNewHeaderId = id || null
+ if (id) localStorage.setItem("bss_active_new_header", id)
+ else localStorage.removeItem("bss_active_new_header")
+ },
+
+ getActiveNewHeaderId() {
+ return this.activeNewHeaderId || localStorage.getItem("bss_active_new_header")
+ },
+
+ /* ===========================================================
+ 🧩 initFromRoute (v5.6 — groupedRows TOUCH YOK)
+ -----------------------------------------------------------
+ - Route ID ve bss_last_txn arasında en dolu snapshot'ı seçer
+ - header + orders + summaryRows restore edilir
+ - groupedRows hydrate edilmez / resetlenmez / dokunulmaz
+ - Route ID farklıysa router.replace ile URL düzeltilir
+ =========================================================== */
+ async initFromRoute(orderId, router = null) { // ✅ NEW MODE → SADECE global draft
+ if (this.mode === 'new') {
+ const raw = localStorage.getItem(this.getDraftKey)
+ if (raw) {
+ try {
+ const payload = JSON.parse(raw)
+ this.header = payload.header || {}
+ this.orders = payload.orders || []
+ this.summaryRows = payload.summaryRows || this.orders
+ console.log('♻️ NEW draft restore edildi (global)')
+ return
+ } catch {}
+ }
+ console.log('⚪ NEW draft yok, boş başlatılıyor')
+ return
+ }
+ if (!this.schemaMap || !Object.keys(this.schemaMap).length) {
+ this.initSchemaMap()
+ }
+
+ try {
+ console.log('🧩 [initFromRoute] orderId:', orderId)
+
+ const lastTxn = localStorage.getItem('bss_last_txn') || null
+
+ const readPayload = (id) => {
+ if (!id) return null
+ const raw = localStorage.getItem(`bss_orderentry_data:${id}`)
+ if (!raw) return null
+ try {
+ return JSON.parse(raw)
+ } catch {
+ return null
+ }
+ }
+
+ const fromRoute = readPayload(orderId)
+ const fromLast = readPayload(lastTxn)
+
+ const hasData = (p) =>
+ !!p && (
+ (Array.isArray(p.orders) && p.orders.length > 0) ||
+ (Array.isArray(p.summaryRows) && p.summaryRows.length > 0)
+ )
+
+ let chosenId = null
+ let chosenPayload = null
+
+ if (hasData(fromRoute)) {
+ chosenId = orderId
+ chosenPayload = fromRoute
+ console.log('✅ [initFromRoute] Route ID snapshot seçildi:', chosenId)
+ } else if (hasData(fromLast)) {
+ chosenId = lastTxn
+ chosenPayload = fromLast
+ console.log('✅ [initFromRoute] lastTxn snapshot seçildi:', chosenId)
+ }
+
+ /* -------------------------------------------------------
+ 🚫 SNAPSHOT YOK → BOŞ BAŞLA
+ -------------------------------------------------------- */
+ if (!chosenId || !chosenPayload) {
+ console.log('⚪ [initFromRoute] Snapshot yok, boş başlatılıyor')
+
+ this.header = {
+ ...(this.header || {}),
+ OrderHeaderID: orderId || lastTxn || crypto.randomUUID()
+ }
+
+ this.orders = []
+ this.summaryRows = []
+
+ // ❗ groupedRows'a DOKUNMA
+ return
+ }
+
+ /* -------------------------------------------------------
+ ✅ SNAPSHOT RESTORE (SAFE CLONE)
+ -------------------------------------------------------- */
+ this.header = {
+ ...(chosenPayload.header || {}),
+ OrderHeaderID: chosenId
+ }
+
+ const orders = Array.isArray(chosenPayload.orders)
+ ? [...chosenPayload.orders]
+ : []
+
+ const summaryRows = Array.isArray(chosenPayload.summaryRows)
+ ? [...chosenPayload.summaryRows]
+ : orders
+
+ this.orders = orders
+ this.summaryRows = summaryRows
+
+ // ❗ groupedRows hydrate edilmez, resetlenmez
+
+ /* -------------------------------------------------------
+ 🔁 lastTxn SENKRON
+ -------------------------------------------------------- */
+ try {
+ localStorage.setItem('bss_last_txn', chosenId)
+ } catch (e) {
+ console.warn('⚠️ bss_last_txn yazılamadı:', e)
+ }
+
+ /* -------------------------------------------------------
+ 🔁 ROUTE DÜZELTME (GEREKİRSE)
+ -------------------------------------------------------- */
+ if (router && orderId && orderId !== chosenId) {
+ console.log('🔁 [initFromRoute] Route ID düzeltiliyor →', chosenId)
+ await router.replace({
+ name: 'order-entry',
+ params: { orderHeaderID: chosenId }
+ })
+ }
+
+ console.log(
+ '✅ [initFromRoute] Restore tamam. Satır sayısı:',
+ this.summaryRows.length
+ )
+
+ } catch (err) {
+ console.error('❌ [initFromRoute] hata:', err)
+ }
+ }
+
+ ,
+
+ /* ===========================================================
+ 🆕 startNewOrder (v8.3 — FINAL & STABLE)
+ =========================================================== */
+ async startNewOrder({ $q }) {
+
+ if (!this.schemaMap || !Object.keys(this.schemaMap).length) {
+ this.initSchemaMap()
+ }
+
+ const headerId = crypto.randomUUID()
+
+ let orderNumber = `LOCAL-${dayjs().format("YYMMDD-HHmmss")}`
+
+ try {
+ const res = await api.get("/order/new-number")
+ if (res?.data?.OrderNumber) {
+ orderNumber = res.data.OrderNumber
+ }
+ } catch {
+ console.info('ℹ️ Backend order number yok, LOCAL kullanıldı')
+ }
+
+ this.mode = 'new'
+ this.isControlledSubmit = false
+ this.allowRouteLeaveOnce = false
+
+ this.header = {
+ OrderHeaderID: headerId,
+ OrderNumber: orderNumber,
+ OrderDate: new Date().toISOString().slice(0, 10),
+ CurrAccCode: null,
+ DocCurrencyCode: 'USD',
+ PriceCurrencyCode: 'USD',
+ PriceExchangeRate: 1
+ }
+
+ this.orders = []
+ this.summaryRows = []
+
+ // ✅ fingerprint bazlı sistem için reset
+ this._lastSavedFingerprint = null
+
+ // ✅ NEW draft hemen yazılır
+ this.persistLocalStorage?.()
+
+ return this.header
+ }
+
+
+
+ ,
+ dedupeActiveLinesByCombo(lines) {
+ const map = new Map()
+ for (const ln of lines) {
+ const key = buildComboKey({
+ model: ln.ItemCode,
+ renk: ln.ColorCode,
+ renk2: ln.ItemDim2Code
+ }, ln.ItemDim1Code)
+
+ if (!map.has(key)) {
+ ln.ComboKey = key
+ map.set(key, ln)
+ continue
+ }
+
+ const ex = map.get(key)
+ ex.Qty1 = (Number(ex.Qty1) || 0) + (Number(ln.Qty1) || 0)
+
+ // OrderLineID boşsa doldur (editte önemli)
+ if (!ex.OrderLineID && ln.OrderLineID) ex.OrderLineID = ln.OrderLineID
+ }
+ return Array.from(map.values())
+ }
+
+
+ ,
+ /* ===========================================================
+ 🧹 Core reset helper — sadece state'i sıfırlar
+ =========================================================== */
+ resetCoreState() {
+ this.orders = []
+ this.summaryRows = []
+ this.groupedRows = []
+ this.header = {}
+ this.editingKey = null
+ this.currentOrderId = null
+ },resetForNewOrder() {
+ // mevcut her şeyi temizle
+ this.header = {
+ OrderHeaderID: this.header?.OrderHeaderID || null,
+ OrderDate: new Date().toISOString().slice(0,10),
+ CurrAccCode: null,
+ DocCurrencyCode: 'TRY',
+ PriceCurrencyCode: 'TRY',
+ // ihtiyaç duyduğun diğer default header alanları
+ }
+
+ this.orders = []
+ this.summaryRows = []
+ this.productCache = {}
+ this.stockMap = {}
+
+ this.setMode('new')
+ }
+ ,
+ resetForEdit() {
+ // EDIT modda grid temizlenmez — sadece UI state resetlenir
+ this.editingKey = null
+ this.groupedRows = []
+ this.mode = 'edit'
+ }
+
+ ,markAsSaved() {
+ try {
+ this._lastSavedFingerprint = this._persistFingerprint()
+ console.log('✅ markAsSaved → fingerprint senkron')
+ } catch (e) {
+ console.warn('⚠️ markAsSaved hata:', e)
+ }
+ }
+ ,clearLocalSnapshot() {
+ try {
+ const id = this.header?.OrderHeaderID
+ if (!id) return
+ localStorage.removeItem(`bss_orderentry_data:${id}`)
+ console.log('🧹 Local snapshot temizlendi:', id)
+ } catch (e) {
+ console.warn('⚠️ clearLocalSnapshot hata:', e)
+ }
+ },/* ===========================================================
+ 🧹 HARD CLEAN — ALL ORDERENTRY SNAPSHOTS
+=========================================================== */
+ clearAllOrderSnapshots () {
+ Object.keys(localStorage)
+ .filter(k =>
+ k.startsWith('bss_orderentry_data:') ||
+ k.startsWith('bss_orderentry_edit:')
+ )
+ .forEach(k => {
+ console.log('🧹 snapshot silindi:', k)
+ localStorage.removeItem(k)
+ })
+
+ localStorage.removeItem('bss_last_txn')
+ }
+
+
+
+
+ ,
+ /* ===========================================================
+ 🧹 Store Hard Reset — Submit Sonrası Temizlik (FIXED)
+ - Grid, header, toplamlar, local state'ler sıfırlanır
+ - persistKey / lastSnapshotKey NULL yapılmaz (config sabit kalır)
+ - localStorage txn/snapshot temizliği güvenli yapılır
+ =========================================================== */
+ hardResetAfterSubmit() {
+ try {
+ // 🔑 mevcut id’yi yakala (local temizliği için)
+ const id = this.header?.OrderHeaderID || null
+
+ /* -------------------------------------------------------
+ 1) Grid ve satırlar
+ -------------------------------------------------------- */
+ this.orders = []
+ this.summaryRows = []
+ this.groupedRows = []
+
+ /* -------------------------------------------------------
+ 2) Header & meta
+ -------------------------------------------------------- */
+ this.header = {}
+
+ /* -------------------------------------------------------
+ 3) Mode & edit state
+ -------------------------------------------------------- */
+ this.mode = 'new'
+ this.editingKey = null
+ this.currentOrderId = null
+
+ /* -------------------------------------------------------
+ 4) Snapshot / transaction meta
+ ⚠️ persistKey / lastSnapshotKey store config → NULL YAPMA
+ -------------------------------------------------------- */
+ this.activeTransactionId = null
+ this.submitted = false
+
+ // fingerprint / debounce meta varsa sıfırla
+ this._lastSavedFingerprint = null
+ this._lastPersistFingerprint = null
+ if (this._persistTimeout) {
+ clearTimeout(this._persistTimeout)
+ this._persistTimeout = null
+ }
+
+ /* -------------------------------------------------------
+ 5) LocalStorage temizlik (opsiyonel ama submit sonrası doğru)
+ -------------------------------------------------------- */
+ try {
+ if (id) {
+ localStorage.removeItem(`bss_orderentry_data:${id}`)
+ localStorage.removeItem(`bss_orderentry_snapshot:${id}`)
+ }
+ localStorage.removeItem('bss_last_txn')
+ localStorage.removeItem('bss_active_new_header')
+ } catch (e) {
+ console.warn('⚠️ hardResetAfterSubmit localStorage temizliği hata:', e)
+ }
+
+ console.log('🧹 Store resetlendi (submit sonrası).')
+ } catch (err) {
+ console.error('❌ hardResetAfterSubmit hata:', err)
+ }
+ }
+
+ ,
+
+ /* ===========================================================
+ ✏️ openExistingForEdit (v12 — FINAL & CLEAN)
+ -----------------------------------------------------------
+ ✔ Backend authoritative (orderlist açılışı local'i dikkate almaz)
+ ✔ mode=new → backend çağrısı YOK
+ ✔ normalizeOrderLines → grpKey + bedenMap garanti
+ ✔ isClosed varsa → view, yoksa → edit
+ ✔ Form sync opsiyonel
+ ✔ İlk açılışta snapshot yazılır (edit boyunca persist ile güncellenir)
+ =========================================================== */
+ async openExistingForEdit(
+ orderId,
+ { $q = null, form = null, productCache = null } = {}
+ ) {
+ // 🔑 schemaMap garanti
+ if (!this.schemaMap || !Object.keys(this.schemaMap).length) {
+ this.initSchemaMap?.()
+ }
+
+ if (!orderId) return false
+
+ /* =======================================================
+ 🟦 NEW MODE — ASLA backend çağrısı yok
+ ======================================================= */
+ if (this.mode === 'new') {
+ console.log('⚪ openExistingForEdit skip (mode=new)')
+ return false
+ }
+
+ // productCache hem ref hem reactive olabilir → güvenli oku
+ const pc =
+ productCache?.value
+ ? productCache.value
+ : (productCache && typeof productCache === 'object' ? productCache : {})
+
+ try {
+ // geçici varsayım (sonra isClosed durumuna göre set edilecek)
+ this.setMode?.('edit')
+
+ /* =======================================================
+ 🔹 BACKEND — authoritative load
+ ======================================================= */
+ const res = await api.get(`/order/get/${orderId}`)
+ const backend = res?.data
+
+ if (!backend?.header) {
+ throw new Error('Backend header yok')
+ }
+
+ /* =======================================================
+ 🔹 HEADER — SADECE BACKEND
+ (orderlist açılışında local merge YOK)
+ ======================================================= */
+ this.header = {
+ ...backend.header,
+ OrderHeaderID: backend.header.OrderHeaderID || orderId
+ }
+
+ /* =======================================================
+ 🔹 NORMALIZE LINES (TEK KAYNAK)
+ normalizeOrderLines şu alanları üretmeli:
+ ✔ row.grpKey
+ ✔ row.bedenMap[grpKey]
+ ✔ row.isClosed (boolean)
+ ======================================================= */
+ const normalized = this.normalizeOrderLines(
+ backend.lines || [],
+ this.header.DocCurrencyCode || 'USD',
+ pc
+ )
+
+ this.orders = Array.isArray(normalized) ? normalized : []
+ this.summaryRows = [...this.orders]
+
+ /* =======================================================
+ 🔹 MODE KARARI (BACKEND SATIRLARI ÜZERİNDEN)
+ - herhangi bir isClosed=true → view
+ - değilse → edit
+ ======================================================= */
+ const hasClosedLine = (this.summaryRows || []).some(r => r?.isClosed === true)
+ this.setMode?.(hasClosedLine ? 'view' : 'edit')
+
+ /* =======================================================
+ 🔹 FORM SYNC (opsiyonel)
+ ======================================================= */
+ if (form) {
+ Object.assign(form, this.header)
+ }
+
+ /* =======================================================
+ 🔹 LOCAL SNAPSHOT (edit boyunca tutulacak temel)
+ - Açılışta snapshot yaz
+ - Sonraki değişikliklerde zaten persistLocalStorage çağrıları var
+ ======================================================= */
+ this.persistLocalStorage?.()
+ try {
+ localStorage.setItem('bss_last_txn', String(orderId))
+ } catch {}
+
+ console.log('✅ openExistingForEdit OK:', {
+ id: orderId,
+ rows: this.summaryRows.length,
+ mode: this.mode,
+ hasClosedLine
+ })
+
+ return true
+ } catch (err) {
+ console.error('❌ openExistingForEdit hata:', err)
+
+ // new değilse uyar
+ if (this.mode !== 'new') {
+ $q?.notify?.({
+ type: 'negative',
+ message: 'Sipariş yüklenemedi'
+ })
+ }
+
+ return false
+ }
+ }
+
+ ,
+ /* ===========================================================
+ ♻️ hydrateFromLocalStorage (v5.5 — FIXED & CLEAN)
+ -----------------------------------------------------------
+ - Tek assign (double overwrite YOK)
+ - groupedRows hydrate edilmez
+ - mode ASLA set edilmez
+ - header + rows güvenli restore
+ =========================================================== */
+ async hydrateFromLocalStorage(orderId, log = false) {if (this.mode === 'new') {
+ return this.hydrateFromLocalStorageIfExists()
+ }
+
+ try {
+ const key = `bss_orderentry_data:${orderId}`
+ const payload = JSON.parse(localStorage.getItem(key) || 'null')
+
+ if (!payload) {
+ log && console.log('ℹ️ hydrate → snapshot yok:', orderId)
+ return null
+ }
+
+ // 🔑 source bilgisi (mode set edilmez)
+ this.source = payload.source || 'local'
+
+ /* -------------------------------------------------------
+ MSSQL tarih helper’ları
+ -------------------------------------------------------- */
+ const safeDateTime = v => {
+ if (!v) return null
+ const d = dayjs(v)
+ return d.isValid() ? d.format('YYYY-MM-DD HH:mm:ss') : null
+ }
+
+ const safeDateOnly = v => {
+ if (!v) return null
+ const d = dayjs(v)
+ return d.isValid() ? d.format('YYYY-MM-DD') : null
+ }
+
+ const safeTimeOnly = v => {
+ if (!v) return null
+ const d = dayjs(v)
+ return d.isValid() ? d.format('HH:mm:ss') : null
+ }
+
+ /* -------------------------------------------------------
+ HEADER
+ -------------------------------------------------------- */
+ this.header = {
+ ...(payload.header || {}),
+ OrderHeaderID: payload.header?.OrderHeaderID ?? orderId,
+ OrderNumber : payload.header?.OrderNumber ?? null
+ }
+
+ const h = this.header
+ h.CreatedDate = safeDateTime(h.CreatedDate)
+ h.LastUpdatedDate = safeDateTime(h.LastUpdatedDate)
+ h.CreditableConfirmedDate = safeDateTime(h.CreditableConfirmedDate)
+ h.OrderDate = safeDateOnly(h.OrderDate)
+ h.OrderTime = safeTimeOnly(h.OrderTime)
+ this.header = h
+
+ /* -------------------------------------------------------
+ ROWS (TEK KAYNAK)
+ -------------------------------------------------------- */
+ const orders = Array.isArray(payload.orders)
+ ? payload.orders
+ : []
+
+ this.orders = orders
+
+ this.summaryRows = Array.isArray(payload.summaryRows)
+ ? payload.summaryRows
+ : orders
+
+ // ❌ groupedRows hydrate edilmez (computed olmalı)
+ this.groupedRows = []
+
+ /* -------------------------------------------------------
+ SNAPSHOT ÖZET
+ -------------------------------------------------------- */
+ const output = {
+ type : payload.submitted === true ? 'submitted' : 'draft',
+ source : this.source,
+ headerId : orderId,
+ orderNumber: this.header?.OrderNumber ?? null,
+ rows : this.summaryRows.length,
+ submitted :
+ payload.submitted === true ||
+ payload.header?.IsSubmitted === true
+ }
+
+ log && console.log('♻️ hydrate sonuc (FIXED):', output)
+ return output
+
+ } catch (err) {
+ console.warn('⚠️ hydrateFromLocalStorage hata:', err)
+ return null
+ }
+ }
+
+
+ ,
+ hydrateFromLocalStorageIfExists() {
+ try {
+ let raw = null
+
+ if (this.mode === 'new') {
+ raw = localStorage.getItem(this.getDraftKey) // ✅
+ }
+
+ if (this.mode === 'edit') {
+ const key = this.getEditKey // ✅
+ if (key) raw = localStorage.getItem(key)
+ }
+
+ if (!raw) return false
+
+ const payload = JSON.parse(raw)
+
+ this.header = payload.header || {}
+ this.orders = payload.orders || []
+ this.summaryRows = payload.summaryRows || this.orders
+
+ console.log('♻️ hydrate OK:', this.mode)
+ return true
+
+ } catch (err) {
+ console.warn('hydrateFromLocalStorageIfExists hata:', err)
+ return false
+ }
+ }
+
+ ,
+
+ /* ===========================================================
+ 🔀 mergeOrders (local + backend)
+ normalizeISO → kaldırıldı
+ safe MSSQL helpers eklendi
+ =========================================================== */
+ mergeOrders(local, backend, preferLocal = true) {
+ if (!backend && !local) return { header: {}, orders: [] }
+
+ const safeMerge = (base = {}, override = {}) => {
+ const out = { ...base }
+ for (const [k, v] of Object.entries(override || {})) {
+ if (v === undefined || v === null) continue
+ if (typeof v === 'string' && v.trim() === '') continue
+ out[k] = v
+ }
+ return out
+ }
+
+ // Header merge
+ const header = safeMerge(backend?.header || {}, local?.header || {})
+ header.OrderHeaderID =
+ backend?.header?.OrderHeaderID ||
+ local?.header?.OrderHeaderID ||
+ header.OrderHeaderID ||
+ null
+
+ const getKey = (r) =>
+ (r.OrderLineID ||
+ `${r.model || r.ItemCode}_${r.renk || r.ColorCode}_${r.renk2 || r.ColorCode2}`
+ ).toString().toUpperCase()
+
+ const map = new Map()
+
+ // Backend satırları
+ for (const b of (backend?.lines || backend?.orders || [])) {
+ map.set(getKey(b), { ...b, _src: 'backend' })
+ }
+
+ // Local satırları merge et
+ for (const l of (local?.orders || [])) {
+ const k = getKey(l)
+ if (map.has(k)) {
+ const merged = safeMerge(map.get(k), l)
+ merged._src = preferLocal ? 'local' : 'backend'
+ map.set(k, merged)
+ } else {
+ map.set(k, { ...l, _src: 'local-only' })
+ }
+ }
+
+ const mergedOrders = Array.from(map.values())
+ console.log(`🧩 mergeOrders → ${mergedOrders.length} satır birleşti (ID:${header.OrderHeaderID})`)
+
+ // ====================================================
+ // 🕒 HEADER TARİHLERİNİ MSSQL FORMATINA NORMALİZE ET
+ // ====================================================
+ const safeDateTime = v => {
+ if (!v) return null
+ const d = dayjs(v)
+ return d.isValid() ? d.format("YYYY-MM-DD HH:mm:ss") : null
+ }
+
+ const safeDateOnly = v => {
+ if (!v) return null
+ const d = dayjs(v)
+ return d.isValid() ? d.format("YYYY-MM-DD") : null
+ }
+
+ const safeTimeOnly = v => {
+ if (!v) return null
+ const d = dayjs(v)
+ return d.isValid() ? d.format("HH:mm:ss") : null
+ }
+
+ header.CreatedDate = safeDateTime(header.CreatedDate)
+ header.LastUpdatedDate = safeDateTime(header.LastUpdatedDate)
+ header.CreditableConfirmedDate = safeDateTime(header.CreditableConfirmedDate)
+
+ header.OrderDate = safeDateOnly(header.OrderDate)
+ header.OrderTime = safeTimeOnly(header.OrderTime)
+
+ return { header, orders: mergedOrders }
+
+ }
+ ,
+
+ markRowSource(row) {
+ if (row._src === 'local-only') return '🟠 Offline'
+ if (row._src === 'local') return '🔵 Local'
+ return '⚪ Backend'
+ }
+ ,
+
+ /* ===========================================================
+ 🔄 mergeAndPersistBackendOrder (edit mode)
+ =========================================================== */
+ mergeAndPersistBackendOrder(orderId, backendPayload) {
+ const key = `bss_orderentry_data:${orderId}`
+ const localPayload = JSON.parse(localStorage.getItem(key) || 'null')
+
+ const merged = this.mergeOrders(localPayload, backendPayload, true)
+
+ localStorage.setItem(key, JSON.stringify({
+ ...merged,
+ source: 'db',
+ mode: 'edit',
+ updatedAt: new Date().toISOString()
+ }))
+
+ console.log(`💾 mergeAndPersistBackendOrder → ${orderId} localStorage’a yazıldı`)
+ }
+ ,
+
+ persistLocalStorage() {
+ try {
+ if (this.preventPersist || this._uiBusy) return
+
+ const payload = {
+ mode: this.mode,
+ header: toRaw(this.header || {}),
+ orders: toRaw(this.orders || []),
+ summaryRows: toRaw(this.summaryRows || []),
+ updatedAt: new Date().toISOString()
+ }
+
+ /* ===============================
+ 🟢 NEW MODE — GLOBAL TEK TASLAK
+ =============================== */
+ if (this.mode === 'new') {
+ localStorage.setItem(this.getDraftKey, JSON.stringify(payload))
+
+ // 🔒 sadece aktif new header bilgisi
+ this.setActiveNewHeader?.(this.header?.OrderHeaderID)
+
+ return
+ }
+
+ /* ===============================
+ 🔵 EDIT MODE — ID BAZLI
+ =============================== */
+ if (this.mode === 'edit') {
+ const key = this.getEditKey
+ if (!key) return
+ localStorage.setItem(key, JSON.stringify(payload))
+ }
+
+ } catch (e) {
+ console.warn('persistLocalStorage error:', e)
+ }
+ }
+
+
+
+ ,
+ clearEditSnapshotIfExists() {
+ if (this.mode !== 'edit') return
+
+ const key = this.getEditKey // ✅
+ if (!key) return
+
+ localStorage.removeItem(key)
+ console.log('🧹 EDIT snapshot silindi:', key)
+ }
+
+
+
+
+ ,/* ===========================================================
+ 🧠 _persistFingerprint — kritik state’leri tek stringe indirger
+ - X3: orders+header yetmez → mode, summaryRows, id/no, map’ler dahil
+=========================================================== */
+ _persistFingerprint() {
+ // 🔹 orders: çok büyürse pahalı olabilir ama snapshot tutarlılığı için önemli
+ // (istersen burada sadece length + rowKey listesi gibi optimize ederiz)
+ const ordersSnap = JSON.stringify(this.orders || [])
+
+ // 🔹 header: sadece kritik alanları al (tam header yerine daha stabil)
+ const h = this.header || {}
+ const headerSnap = JSON.stringify({
+ OrderHeaderID: h.OrderHeaderID || '',
+ OrderNumber: h.OrderNumber || '',
+ CurrAccCode: h.CurrAccCode || '',
+ DocCurrencyCode: h.DocCurrencyCode || '',
+ ExchangeRate: h.ExchangeRate ?? null
+ })
+
+ // 🔹 summaryRows: hash yerine şimdilik “length + rowKey listesi” (hafif + etkili)
+ const sr = Array.isArray(this.summaryRows) ? this.summaryRows : []
+ const summaryMeta = JSON.stringify({
+ len: sr.length,
+ keys: sr.map(r => this.getRowKey?.(r) || r?.key || r?.id || '').filter(Boolean)
+ })
+
+ // 🔹 comboLineIds / lineIdMap gibi kritik map’ler
+ // (sende hangisi varsa onu otomatik topluyoruz)
+ const mapSnap = JSON.stringify({
+ lineIdMap: this.lineIdMap || null,
+ comboLineIds: this.comboLineIds || null,
+ comboLineIdMap: this.comboLineIdMap || null,
+ comboLineIdSet: this.comboLineIdSet ? Array.from(this.comboLineIdSet) : null
+ })
+
+ // 🔹 mode
+ const modeSnap = String(this.mode || 'new')
+
+ // ✅ Tek fingerprint
+ return `${modeSnap}|${headerSnap}|${summaryMeta}|${mapSnap}|${ordersSnap}`
+ }
+ ,
+ /* ===========================================================
+ 🕒 _safePersistDebounced — snapshot değişmediği sürece yazmaz (X3)
+ - fingerprint: mode + header(id/no) + summaryRows meta + lineIdMap/combo + orders
+ =========================================================== */
+ _safePersistDebounced(delay = 1200) {
+ clearTimeout(this._persistTimeout)
+
+ this._persistTimeout = setTimeout(() => {
+ try {
+ // ✅ Persist guard’ları (varsa)
+ if (this.preventPersist) return
+ if (this._uiBusy) return
+
+ const fp = this._persistFingerprint()
+
+ if (fp === this._lastPersistFingerprint) {
+ return
+ }
+
+ this._lastPersistFingerprint = fp
+
+ this.persistLocalStorage()
+ console.log(`🕒 Otomatik LocalStorage senkron (${this.orders?.length || 0} satır).`)
+ } catch (err) {
+ console.warn('⚠️ Debounce persist hata:', err)
+ }
+ }, delay)
+ }
+
+ ,
+
+ /* ===========================================================
+ 💰 fetchMinPrice — model/pb için min fiyat
+ =========================================================== */
+ async fetchMinPrice(model, currency, $q) {
+ try {
+ const res = await api.get('/min-price', {
+ params: { model, currency }
+ })
+ const data = res?.data || {}
+ console.log('💰 [store.fetchMinPrice] yanıt:', data)
+ return {
+ price: Number(data.price || 0),
+ rateToTRY: Number(data.rateToTRY || 1),
+ priceTRY: Number(data.priceTRY || 0)
+ }
+ } catch (err) {
+ console.error('❌ [store.fetchMinPrice] Min fiyat alınamadı:', err)
+ $q?.notify?.({
+ type: 'warning',
+ message: 'Min. fiyat bilgisi alınamadı, kontrol atlandı ⚠️',
+ position: 'top-right'
+ })
+ return { price: 0, rateToTRY: 1, priceTRY: 0 }
+ }
+ }
+ ,
+ applyCurrencyToLines(newPB) {
+ if (!newPB) return
+
+ // 🔹 Header
+ if (this.header) {
+ this.header.DocCurrencyCode = newPB
+ this.header.PriceCurrencyCode = newPB
+ }
+
+ // 🔹 Lines
+ if (Array.isArray(this.orders)) {
+ this.orders = this.orders.map(r => ({
+ ...r,
+ pb: newPB,
+ DocCurrencyCode: newPB,
+ PriceCurrencyCode: newPB
+ }))
+ }
+
+ // 🔹 Summary
+ if (Array.isArray(this.summaryRows)) {
+ this.summaryRows = this.summaryRows.map(r => ({
+ ...r,
+ pb: newPB,
+ DocCurrencyCode: newPB,
+ PriceCurrencyCode: newPB
+ }))
+ }
+
+ // ❗ totalAmount SET ETME
+ // ✔️ TEK MERKEZ
+ this.updateHeaderTotals?.()
+ }
+ ,
+
+ /* ===========================================================
+ 💠 HEADER SET & CURRENCY PROPAGATION
+ =========================================================== */
+ setHeaderFields(fields, opts = {}) {
+ const {
+ applyCurrencyToLines = false,
+ immediatePersist = false
+ } = opts
+
+ // 1️⃣ HEADER
+ this.header = {
+ ...(this.header || {}),
+ ...fields
+ }
+
+ // 2️⃣ SATIRLARA GERÇEKTEN YAY
+ if (applyCurrencyToLines && Array.isArray(this.summaryRows)) {
+ this.summaryRows = this.summaryRows.map(r => ({
+ ...r,
+ pb: fields.DocCurrencyCode ?? r.pb,
+ DocCurrencyCode: fields.DocCurrencyCode ?? r.DocCurrencyCode,
+ PriceCurrencyCode: fields.PriceCurrencyCode ?? fields.DocCurrencyCode ?? r.PriceCurrencyCode
+ }))
+ }
+
+ // 3️⃣ STORE ORDERS REFERANSI
+ this.orders = [...this.summaryRows]
+
+
+ // 4️⃣ PERSIST
+ if (immediatePersist) {
+ this.persistLocalStorage('header-change')
+ }
+ }
+
+ ,
+
+ applyHeaderCurrencyToOrders() {
+ if (!Array.isArray(this.orders)) return
+
+ const doc = this.header?.DocCurrencyCode ?? null
+ const prc = this.header?.PriceCurrencyCode ?? null
+ const rate = this.header?.PriceExchangeRate ?? null
+
+ let cnt = 0
+
+ for (const r of this.orders) {
+ if (doc) r.DocCurrencyCode = doc
+ if (prc) r.PriceCurrencyCode = prc
+ if (rate != null) r.PriceExchangeRate = rate
+ cnt++
+ }
+
+ console.log(`💱 ${cnt} satırda PB güncellendi → Doc:${doc} Price:${prc} Rate:${rate}`)
+ }
+
+
+ ,/* ===========================================================
+ 📸 saveSnapshot — küçük debug snapshot
+=========================================================== */
+ saveSnapshot(tag = 'snapshot') {
+ try {
+ const id = this.header?.OrderHeaderID
+ if (!id) return
+
+ const key = `bss_orderentry_snapshot:${id}`
+
+ const snap = {
+ tag,
+ mode: this.mode,
+ orders: toRaw(this.orders || []),
+ header: toRaw(this.header || {}),
+ savedAt: dayjs().toISOString()
+ }
+
+ localStorage.setItem(key, JSON.stringify(snap))
+ console.log(`📸 Snapshot kaydedildi [${key}]`)
+ } catch (err) {
+ console.warn('⚠️ saveSnapshot hata:', err)
+ }
+ }
+ ,
+
+ /* ===========================================================
+ ♻️ loadFromStorage — eski generic persist için
+ =========================================================== */
+ loadFromStorage(force = false) {
+ try {
+ const raw = localStorage.getItem(this.getPersistKey)
+ if (!raw) {
+ console.info('ℹ️ LocalStorage boş, grid başlatılmadı.')
+ return false
+ }
+
+ if (!force && this.mode === 'edit') {
+ console.info('⚠️ Edit modda local restore atlandı (force=false).')
+ return false
+ }
+
+ const data = JSON.parse(raw)
+
+ this.orders = Array.isArray(data.orders) ? data.orders : []
+ this.header = data.header || {}
+ this.currentOrderId = data.currentOrderId || null
+ this.selectedCustomer = data.selectedCustomer || null
+
+ // 🔧 Temiz ID
+ this.header.OrderHeaderID = data.header?.OrderHeaderID || null
+
+ this.mode = data.mode || 'new'
+ this.lastSavedAt = data.savedAt || null
+
+ console.log(`♻️ Storage yüklendi • txn:${this.header.OrderHeaderID} (${this.orders.length} satır)`)
+
+ // Header PB -> satırlara
+ this.applyHeaderCurrencyToOrders()
+ this._safePersistDebounced(200)
+
+ return data
+ } catch (err) {
+ console.warn('⚠️ localStorage okuma hatası:', err)
+ return false
+ }
+ }
+ ,
+
+ clearStorage() {
+ try {
+ localStorage.removeItem(this.getPersistKey)
+ console.log(`🗑️ LocalStorage temizlendi [${this.getPersistKey}]`)
+ } catch (err) {
+ console.warn('⚠️ clearStorage hatası:', err)
+ }
+ }
+ ,
+ clearNewDraft() {
+ localStorage.removeItem(this.getDraftKey) // ✅
+ localStorage.removeItem('bss_last_txn')
+ console.log('🧹 NEW taslak temizlendi')
+ }
+
+ ,
+// ===========================================================
+// 🔹 isSameCombo — STORE LEVEL (TEK KAYNAK)
+// - model ZORUNLU eşleşir
+// - renk / renk2 boşsa → joker
+// ===========================================================
+ isSameCombo(a, b) {
+ if (!a || !b) return false
+
+ const n = v => (v == null ? '' : String(v).trim().toUpperCase())
+
+ const A = { model: n(a.model), renk: n(a.renk), renk2: n(a.renk2) }
+ const B = { model: n(b.model), renk: n(b.renk), renk2: n(b.renk2) }
+
+ if (!A.model || !B.model) return false
+
+ const renkOk = (A.renk === B.renk) || !A.renk || !B.renk
+ const renk2Ok = (A.renk2 === B.renk2) || !A.renk2 || !B.renk2
+
+ return A.model === B.model && renkOk && renk2Ok
+ },
+
+
+
+// ===========================================================
+// 🔹 saveOrUpdateRowUnified (v6.6 — COMBO SAFE + FIXED STOCK+PRICE + UI)
+// - v6.5 korunur (stok+min fiyat + this.loadProductSizes)
+// - ✅ NEW MODE: dupIdx artık _deleteSignal satırlarını BAŞTAN hariç tutar
+// - EDIT MODE: sameCombo → update, combo değişti → delete + insert (korundu)
+// - lineIdMap koruması korunur
+// ===========================================================
+ async saveOrUpdateRowUnified({
+ form,
+ recalcVat = null,
+ resetEditor = null,
+ stockMap = null,
+ loadProductSizes = null,
+ $q = null
+ }) {
+ try {
+ console.log('🔥 saveOrUpdateRowUnified v6.6', {
+ model: form?.model,
+ mode: this.mode,
+ editingKey: this.editingKey
+ })
+
+ const getKey =
+ typeof this.getRowKey === 'function'
+ ? this.getRowKey
+ : (r => r?.clientKey || r?.id || r?.OrderLineID)
+
+ const rows = Array.isArray(this.summaryRows)
+ ? [...this.summaryRows]
+ : []
+
+ /* =======================================================
+ 1️⃣ ZORUNLU KONTROLLER
+ ======================================================= */
+ if (!form?.model) {
+ $q?.notify?.({ type: 'warning', message: 'Model seçiniz' })
+ return false
+ }
+
+ if (!form.pb) {
+ form.pb = this.header?.DocCurrencyCode || 'USD'
+ }
+
+ /* =======================================================
+ 2️⃣ STOK KONTROLÜ (FIXED)
+ - stok guard’dan önce this.loadProductSizes(form,true,$q)
+ - opsiyonel callback loadProductSizes(true)
+ - tek dialog + doğru await
+ ======================================================= */
+
+ // ✅ store fonksiyonu
+ try {
+ if (typeof this.loadProductSizes === 'function') {
+ await this.loadProductSizes(form, true, $q)
+ }
+ } catch (err) {
+ console.warn('⚠ this.loadProductSizes hata:', err)
+ }
+
+ // ✅ dışarıdan callback geldiyse
+ try {
+ if (typeof loadProductSizes === 'function') {
+ await loadProductSizes(true)
+ }
+ } catch (err) {
+ console.warn('⚠ loadProductSizes hata:', err)
+ }
+
+ const stockMapLocal = stockMap?.value || stockMap || {}
+ const bedenLabels = form.bedenLabels || []
+ const bedenValues = form.bedenler || []
+
+ const overLimit = []
+ for (let i = 0; i < bedenLabels.length; i++) {
+ const lbl = String(bedenLabels[i] ?? '').trim()
+ const stok = Number(stockMapLocal?.[lbl] ?? 0)
+ const girilen = Number(bedenValues?.[i] ?? 0)
+
+ if (stok > 0 && girilen > stok) {
+ overLimit.push({ beden: lbl, stok, girilen })
+ }
+ }
+
+ if (overLimit.length && $q) {
+ const msg = overLimit
+ .map(x => `• ${x.beden}: ${x.girilen} (Stok: ${x.stok})`)
+ .join('
')
+
+ const stokOK = await new Promise(resolve => {
+ $q.dialog({
+ title: 'Stok Uyarısı',
+ message: `Bazı bedenlerde stoktan fazla giriş yaptınız:
${msg}`,
+ html: true,
+ ok: { label: 'Devam', color: 'primary' },
+ cancel: { label: 'İptal', color: 'negative' }
+ })
+ .onOk(() => resolve(true))
+ .onCancel(() => resolve(false))
+ .onDismiss(() => resolve(false))
+ })
+
+ if (!stokOK) return false
+ }
+
+ /* =======================================================
+ 3️⃣ FİYAT (MIN) KONTROLÜ (FIXED)
+ ======================================================= */
+ let fiyatOK = true
+ try {
+ let minFiyat = 0
+
+ if (typeof this.fetchMinPrice === 'function') {
+ const p = await this.fetchMinPrice(form.model, form.pb, $q)
+ minFiyat = Number(p?.price || 0)
+ } else if (Number(form.minFiyat || 0) > 0) {
+ minFiyat = Number(form.minFiyat)
+ }
+
+ const girilen = Number(form.fiyat || 0)
+
+ if (minFiyat > 0 && girilen > 0 && girilen < minFiyat && $q) {
+ fiyatOK = await new Promise(resolve => {
+ $q.dialog({
+ title: 'Fiyat Uyarısı',
+ message:
+ `Min. Fiyat: ${minFiyat} ${form.pb}
` +
+ `Girdiğiniz: ${girilen} ${form.pb}`,
+ html: true,
+ ok: { label: 'Devam', color: 'primary' },
+ cancel: { label: 'İptal', color: 'negative' }
+ })
+ .onOk(() => resolve(true))
+ .onCancel(() => resolve(false))
+ .onDismiss(() => resolve(false))
+ })
+ }
+ } catch (err) {
+ console.warn('⚠ Min fiyat hata:', err)
+ }
+ if (!fiyatOK) return false
+
+ /* =======================================================
+ 4️⃣ TOPLAM HESABI
+ ======================================================= */
+ const adet = (form.bedenler || []).reduce((a, b) => a + Number(b || 0), 0)
+ form.adet = adet
+ form.tutar = Number((adet * Number(form.fiyat || 0)).toFixed(2))
+
+ const newRow = toSummaryRowFromForm(form)
+
+ /* =======================================================
+ 5️⃣ EDIT MODE (editingKey ZORUNLU)
+ ======================================================= */
+ if (this.editingKey) {
+ const idx = rows.findIndex(r => getKey(r) === this.editingKey)
+ if (idx === -1) {
+ this.editingKey = null
+ resetEditor?.(true)
+ return false
+ }
+
+ const prev = rows[idx]
+
+ if (this.isRowLocked?.(prev)) {
+ $q?.notify?.({ type: 'warning', message: 'Satır kapalı' })
+ this.editingKey = null
+ resetEditor?.(true)
+ return false
+ }
+
+ // ✅ kritik: store-level
+ const sameCombo = this.isSameCombo(prev, newRow)
+
+ const preservedLineIdMap =
+ (prev?.lineIdMap && typeof prev.lineIdMap === 'object')
+ ? { ...prev.lineIdMap }
+ : (newRow?.lineIdMap && typeof newRow.lineIdMap === 'object')
+ ? { ...newRow.lineIdMap }
+ : {}
+
+ /* ===== SAME COMBO → UPDATE ===== */
+ if (sameCombo) {
+ rows[idx] = {
+ ...prev,
+ ...newRow,
+ id: prev.id,
+ OrderLineID: prev.OrderLineID || null,
+ lineIdMap: preservedLineIdMap
+ }
+
+ this.summaryRows = rows
+ this.orders = rows
+
+ this.updateHeaderTotals?.()
+ this.persistLocalStorage?.()
+
+ this.editingKey = null
+ resetEditor?.(true)
+ recalcVat?.()
+
+ $q?.notify?.({ type: 'positive', message: 'Satır güncellendi' })
+ return true
+ }
+
+ /* ===== COMBO CHANGED → DELETE + INSERT ===== */
+ const grpKey =
+ prev?.grpKey ||
+ Object.keys(prev?.bedenMap || {})[0] ||
+ 'tak'
+
+ const emptyMap = {}
+ const srcMap =
+ (prev?.bedenMap?.[grpKey] && typeof prev.bedenMap[grpKey] === 'object')
+ ? prev.bedenMap[grpKey]
+ : (preservedLineIdMap && typeof preservedLineIdMap === 'object')
+ ? preservedLineIdMap
+ : null
+
+ if (srcMap) {
+ for (const beden of Object.keys(srcMap)) emptyMap[beden] = 0
+ } else {
+ emptyMap['STD'] = 0
+ }
+
+ const deleteRow = {
+ ...prev,
+ id: `DEL::${prev.id || prev.OrderLineID || crypto.randomUUID()}`,
+ _deleteSignal: true,
+ adet: 0,
+ Qty1: 0,
+ tutar: 0,
+ ComboKey: '',
+
+ OrderLineID: prev.OrderLineID || null,
+
+ grpKey,
+ bedenMap: { [grpKey]: emptyMap },
+ lineIdMap: preservedLineIdMap,
+ comboLineIds: { ...(prev.comboLineIds || {}) }
+ }
+
+ const insertedRow = {
+ ...newRow,
+ id: crypto.randomUUID(),
+ OrderLineID: null,
+ lineIdMap: {}
+ }
+
+ rows.splice(idx, 1, insertedRow)
+
+ this.summaryRows = rows
+ this.orders = [...rows, deleteRow]
+
+ this.updateHeaderTotals?.()
+ this.persistLocalStorage?.()
+
+ this.editingKey = null
+ resetEditor?.(true)
+ recalcVat?.()
+
+ $q?.notify?.({ type: 'positive', message: 'Kombinasyon değişti' })
+ return true
+ }
+
+ /* =======================================================
+ 6️⃣ NEW MODE (MERGE / INSERT) — COMBO SAFE
+ - aynı combo → bedenMap merge (satır sayısı artmaz)
+ - farklı combo → yeni satır
+ - ✅ FIX: _deleteSignal satırlarını dup aramasında hariç tut
+ ======================================================= */
+ const dupIdx = rows.findIndex(r =>
+ !r?._deleteSignal &&
+ this.isSameCombo(r, newRow)
+ )
+
+ // helper: bedenMap çıkar (gruplu ya da düz)
+ const extractMap = (row) => {
+ const grpKey =
+ row?.grpKey ||
+ Object.keys(row?.bedenMap || {})[0] ||
+ 'GENEL'
+
+ const grouped = row?.bedenMap?.[grpKey]
+ const flat = (row?.bedenMap && typeof row.bedenMap === 'object' && !grouped)
+ ? row.bedenMap
+ : null
+
+ return { grpKey, map: (grouped || flat || {}) }
+ }
+
+ if (dupIdx !== -1) {
+ const prev = rows[dupIdx]
+
+ // delete satırına merge yapma (ek güvenlik)
+ if (prev?._deleteSignal !== true) {
+ const { grpKey: prevGrp, map: prevMap } = extractMap(prev)
+ const { grpKey: newGrp, map: newMap } = extractMap(newRow)
+
+ // hangi grpKey kullanılacak?
+ const grpKey = newRow?.grpKey || prevGrp || newGrp || 'GENEL'
+
+ // MERGE: bedenleri topluyoruz (override değil)
+ const merged = { ...(prevMap || {}) }
+ for (const [k, v] of Object.entries(newMap || {})) {
+ const beden = (k == null || String(k).trim() === '') ? ' ' : String(k).trim()
+ merged[beden] = Number(merged[beden] || 0) + Number(v || 0)
+ }
+
+ // toplam adet/tutar recalc
+ const totalAdet = Object.values(merged).reduce((a, b) => a + Number(b || 0), 0)
+ const price = Number(newRow?.fiyat ?? prev?.fiyat ?? 0)
+ const totalTutar = Number((totalAdet * price).toFixed(2))
+
+ rows[dupIdx] = {
+ ...prev,
+ ...newRow,
+
+ // kritik korumalar
+ id: prev.id,
+ OrderLineID: prev.OrderLineID || null,
+ lineIdMap: { ...(prev.lineIdMap || {}) },
+
+ // MERGED bedenMap
+ grpKey,
+ bedenMap: { [grpKey]: merged },
+
+ // adet/tutar
+ adet: totalAdet,
+ tutar: totalTutar,
+
+ updatedAt: dayjs().toISOString()
+ }
+
+ this.summaryRows = rows
+ this.orders = rows
+
+ this.updateHeaderTotals?.()
+ this.persistLocalStorage?.()
+ resetEditor?.(true)
+ recalcVat?.()
+
+ $q?.notify?.({ type: 'positive', message: 'Aynı kombinasyon bulundu, bedenler birleştirildi' })
+ return true
+ }
+ }
+
+ // dup yoksa (veya dup delete satırıydı) → yeni satır
+ rows.push({
+ ...newRow,
+ id: newRow.id || crypto.randomUUID(),
+ OrderLineID: null,
+ lineIdMap: { ...(newRow.lineIdMap || {}) }
+ })
+
+ this.summaryRows = rows
+ this.orders = rows
+
+ this.updateHeaderTotals?.()
+ this.persistLocalStorage?.()
+ resetEditor?.(true)
+ recalcVat?.()
+
+ $q?.notify?.({ type: 'positive', message: 'Yeni satır eklendi' })
+ return true
+
+ } catch (err) {
+ console.error('❌ saveOrUpdateRowUnified:', err)
+ $q?.notify?.({ type: 'negative', message: 'Satır kaydı başarısız' })
+ return false
+ }
+ }
+
+
+
+
+ ,
+
+ /* ===========================================================
+ 🔄 setTransaction — yeni transaction ID set et
+ =========================================================== */
+ setTransaction(id, autoResume = true) {
+ if (!id) return
+
+ // 🔧 temiz ID
+ this.header.OrderHeaderID = id
+
+ localStorage.setItem('bss_last_txn', id)
+ console.log('🔄 Transaction değiştirildi:', id)
+
+ if (autoResume) {
+ const hasData = Array.isArray(this.orders) && this.orders.length > 0
+ if (!hasData) {
+ const ok = this.hydrateFromLocalStorage(id,true)
+ if (ok) console.info('📦 Local kayıt geri yüklendi (boş grid için).')
+ } else {
+ console.log('🚫 Grid dolu, auto-resume atlandı (mevcut satırlar korundu).')
+ }
+ }
+ }
+ ,
+
+
+ /* ===========================================================
+ 🧹 clearTransaction — sadece NEW MODE taslaklarını temizler
+ =========================================================== */
+ clearTransaction() {
+ try {
+ const id = this.header?.OrderHeaderID
+ if (id) {
+ localStorage.removeItem(`bss_orderentry_data:${id}`)
+ }
+
+ this.orders = []
+ this.summaryRows = []
+ this.groupedRows = []
+ this.header = {}
+ this.mode = 'new'
+
+ localStorage.removeItem('bss_last_txn')
+
+ console.log('🧹 Transaction temizlendi')
+ } catch (err) {
+ console.warn('⚠️ clearTransaction hata:', err)
+ }
+ }
+ ,
+
+
+ // =======================================================
+ // 🔒 KİLİT KONTROLÜ — Sadece EDIT modunda, backend satırı
+ // =======================================================
+ isRowLocked(row) {
+ if (!row) return false
+ // Sadece edit modunda,
+ // ve backend'den gelen gerçek OrderLineID varsa,
+ // ve IsClosed=1 ise satır kilitli
+ return (
+ this.mode === 'edit' &&
+ !!row.OrderLineID &&
+ row.isClosed === true
+ )
+ },
+
+
+ findExistingIndexByForm(form) {
+ return this.orders.findIndex(r => this.isSameCombo(r, form))
+ },
+
+ addRow(row) {
+ if (!row) return
+
+ const existingIndex = this.orders.findIndex(r => {
+ const sameId = r.id && row.id && r.id === row.id
+ const sameCombo = this.isSameCombo(r, row)
+ return sameId || sameCombo
+ })
+
+ if (existingIndex !== -1) {
+ const old = this.orders[existingIndex]
+ this.orders[existingIndex] = {
+ ...old,
+ adet: Number(row.adet ?? old.adet ?? 0),
+ fiyat: Number(row.fiyat ?? old.fiyat ?? 0),
+ tutar: Number(row.fiyat ?? old.fiyat ?? 0) * Number(row.adet ?? old.adet ?? 0),
+ ItemDim1Code: row.ItemDim1Code || old.ItemDim1Code,
+ aciklama: row.aciklama || old.aciklama,
+ updatedAt: dayjs().toISOString()
+ }
+ console.log(`⚠️ Aynı kombinasyon bulundu, satır güncellendi: ${row.model} ${row.renk || ''} ${row.renk2 || ''}`)
+ } else {
+ this.orders.push(toRaw(row))
+ console.log(`➕ Yeni kombinasyon eklendi: ${row.model} ${row.renk || ''} ${row.renk2 || ''}`)
+ }
+
+ this.persistLocalStorage()
+ this.saveSnapshot('after-add')
+ },
+
+ updateRow(index, patch) {
+ if (index < 0 || index >= this.orders.length) return
+ this.orders[index] = {
+ ...this.orders[index],
+ ...toRaw(patch),
+ updatedAt: dayjs().toISOString()
+ }
+ this.persistLocalStorage()
+ this.saveSnapshot('after-update')
+ console.log(`✏️ Satır güncellendi (store): #${index}`)
+ },
+
+
+ removeRow(index) {
+ if (index < 0 || index >= this.orders.length) return
+
+ const removed = this.orders.splice(index, 1)
+ if (Array.isArray(this.summaryRows)) {
+ this.summaryRows.splice(index, 1)
+ }
+
+ this.persistLocalStorage()
+ this.saveSnapshot('after-remove')
+ console.log(`🗑️ Satır silindi: ${removed[0]?.model || '(model yok)'}`)
+ },
+ removeSelectedRow(row, $q = null) {
+ if (!row) return
+
+ // 1) Kilitli satır silinemez
+ if (this.isRowLocked(row)) {
+ $q?.notify?.({
+ type: 'warning',
+ message: '🔒 Bu satır (IsClosed=1) kapatılmış. Silinemez.'
+ })
+ return false
+ }
+
+ // 2) Kullanıcıya onay sor
+ return new Promise(resolve => {
+ $q?.dialog({
+ title: 'Satır Sil',
+ message: `${row.model} / ${row.renk} / ${row.renk2} kombinasyonu silinsin mi?`,
+ ok: { label: 'Evet', color: 'negative' },
+ cancel: { label: 'Vazgeç' }
+ })
+ .onOk(() => {
+ this.removeRowInternal(row)
+ resolve(true)
+ })
+ .onCancel(() => resolve(false))
+ })
+ }
+ ,
+ removeRowInternal(row) {
+ if (!row) return false
+
+ // 1️⃣ Kilit kontrolü
+ if (this.isRowLocked(row)) {
+ console.warn('🔒 Kilitli satır silinemez.')
+ return false
+ }
+
+ const getKey =
+ typeof this.getRowKey === 'function'
+ ? this.getRowKey
+ : (r => r?.clientKey || r?.id || r?.OrderLineID)
+
+ const rowKey = getKey(row)
+ if (!rowKey) return false
+
+ const idx = this.summaryRows.findIndex(r => getKey(r) === rowKey)
+ if (idx === -1) return false
+
+ console.log('🗑️ X2 removeRowInternal →', row)
+
+ // 🔐 UI BUSY
+ this._uiBusy = true
+ this.preventPersist = true
+
+ try {
+ // 2️⃣ UI’dan kaldır
+ this.summaryRows.splice(idx, 1)
+
+ // orders = UI satırları (temiz kopya)
+ this.orders = [...this.summaryRows]
+
+ // 3️⃣ EDIT MODE → DELETE SİNYALİ
+ if (this.mode === 'edit') {
+ const grpKey =
+ row.grpKey ||
+ Object.keys(row.bedenMap || {})[0] ||
+ 'tak'
+
+ // ✅ lineIdMap referansı (varsa)
+ const lineIdMap =
+ (row.lineIdMap && typeof row.lineIdMap === 'object')
+ ? { ...row.lineIdMap }
+ : {}
+
+ const emptyMap = {}
+
+ // Öncelik: bedenMap[grpKey] → lineIdMap → fallback
+ if (row.bedenMap && row.bedenMap[grpKey]) {
+ for (const beden of Object.keys(row.bedenMap[grpKey] || {})) {
+ emptyMap[beden] = 0
+ }
+ } else if (Object.keys(lineIdMap).length) {
+ for (const beden of Object.keys(lineIdMap)) {
+ emptyMap[beden] = 0
+ }
+ } else {
+ emptyMap['STD'] = 0
+ }
+
+ const deleteSignalRow = {
+ ...row,
+
+ // 🔴 UI KEY
+ id: `DEL::${row.id || row.OrderLineID || crypto.randomUUID()}`,
+
+ // 🔴 BACKEND DELETE SIGNAL
+ adet: 0,
+ Qty1: 0,
+ tutar: 0,
+
+ // 🔴 CRITICAL: duplicate guard'a girmesin
+ ComboKey: '',
+
+ // 🔴 legacy tekil alan (varsa kalsın)
+ OrderLineID: row.OrderLineID || null,
+
+ // ✅ CRITICAL
+ grpKey,
+ bedenMap: { [grpKey]: emptyMap },
+ lineIdMap,
+ comboLineIds: { ...(row.comboLineIds || {}) },
+
+ _deleteSignal: true
+ }
+
+ console.log('📡 DELETE sinyali üretildi:', deleteSignalRow)
+
+ this.orders.push(deleteSignalRow)
+ }
+
+ // 4️⃣ Totals (persist YOK)
+ this.updateHeaderTotals?.()
+
+ } finally {
+ // 🔓 GUARD KAPAT
+ this.preventPersist = false
+ this._uiBusy = false
+ }
+
+ // 5️⃣ TEK VE KONTROLLÜ persist
+ this.persistLocalStorage()
+
+ return true
+ }
+
+ ,
+
+
+ /* ===========================================================
+ 📦 normalizeOrderLines (v9 — lineIdMap FIXED + AKSBİR SAFE)
+ -----------------------------------------------------------
+ ✔ grpKey SADECE burada set edilir
+ ✔ detectBedenGroup SADECE store’da kullanılır
+ ✔ aksbir → ' ' bedeni = GERÇEK adet
+ ✔ backend satırlarında BEDEN → OrderLineID map’i üretilir
+ =========================================================== */
+ normalizeOrderLines(lines, pbFallback = 'USD') {
+ if (!Array.isArray(lines)) return []
+
+ const merged = Object.create(null)
+
+ const makeBaseKey = (model, renk, renk2) =>
+ `${model || ''}||${renk || ''}||${renk2 || ''}`
+
+ for (const raw of lines) {
+ if (!raw) continue
+
+ const isClosed =
+ raw.IsClosed === true ||
+ raw.isClosed === true ||
+ raw.IsClosed?.Bool === true
+
+ /* =======================================================
+ 1️⃣ UI / SNAPSHOT KAYNAKLI SATIR
+ -------------------------------------------------------
+ ✔ ComboKey YOK
+ ✔ Sadece model / renk / renk2 bazında gruplanır
+ ======================================================= */
+ if (raw.bedenMap && Object.keys(raw.bedenMap).length) {
+ const model = (raw.model || raw.ItemCode || '').trim()
+ const renk = (raw.renk || raw.ColorCode || '').trim()
+ const renk2 = (raw.renk2 || raw.ItemDim2Code || '').trim()
+
+ // ❗ BEDEN YOK → bu SADECE üst seviye grup anahtarı
+ const modelKey = `${model}||${renk}||${renk2}`
+
+ const grpKey = raw.grpKey || 'tak'
+ const srcMap = raw.bedenMap[grpKey] || {}
+
+ const adet = Object.values(srcMap).reduce((a, b) => a + (Number(b) || 0), 0)
+ const fiyat = Number(raw.fiyat || 0)
+ const pb = raw.pb || raw.DocCurrencyCode || pbFallback
+ const tutar = Number(raw.tutar ?? adet * fiyat)
+
+ merged[modelKey] ??= []
+ merged[modelKey].push({
+ ...raw,
+ grpKey,
+ bedenMap: { [grpKey]: { ...srcMap } },
+ adet,
+ fiyat,
+ pb,
+ tutar,
+ isClosed
+ })
+ continue
+ }
+
+
+ /* =======================================================
+ 2️⃣ BACKEND / LEGACY SATIR (FIXED)
+ -------------------------------------------------------
+ ✔ ComboKey YOK
+ ✔ Sadece model / renk / renk2 bazlı gruplanır
+ ✔ BEDEN sadece bedenMap + lineIdMap için kullanılır
+ ======================================================= */
+ const model = (raw.Model || raw.ItemCode || '').trim()
+ const renk = (raw.ColorCode || '').trim()
+ const renk2 = (raw.ItemDim2Code || '').trim()
+
+ // ❗ BEDEN HARİÇ — üst seviye grup anahtarı
+ const modelKey = `${model}||${renk}||${renk2}`
+
+ merged[modelKey] ??= []
+
+ const beden = (
+ raw.ItemDim1Code == null || String(raw.ItemDim1Code).trim() === ''
+ ? ' '
+ : String(raw.ItemDim1Code).trim().toUpperCase()
+ )
+
+ const qty = Number(raw.Qty1 || raw.Qty || 0)
+
+ let entry = merged[modelKey][0]
+ if (!entry) {
+ entry = {
+ id: raw.OrderLineID || crypto.randomUUID(),
+
+ model,
+ renk,
+ renk2,
+
+ urunAnaGrubu: raw.UrunAnaGrubu || 'GENEL',
+ urunAltGrubu: raw.UrunAltGrubu || '',
+ kategori: raw.Kategori || '',
+
+ aciklama: raw.LineDescription || '',
+ fiyat: Number(raw.Price || 0),
+ pb: raw.DocCurrencyCode || pbFallback,
+
+ __tmpMap: {}, // beden → qty
+ lineIdMap: {}, // beden → OrderLineID
+
+ adet: 0,
+ tutar: 0,
+
+ terminTarihi: raw.DeliveryDate || null,
+ isClosed
+ }
+
+ merged[modelKey].push(entry)
+ }
+
+ /* -------------------------------------------------------
+ 🔑 BEDEN → OrderLineID (DETERMINISTIC & SAFE)
+ -------------------------------------------------------- */
+ const rawLineId =
+ raw.OrderLineID ||
+ raw.OrderLineId ||
+ raw.orderLineID ||
+ null
+
+ if (rawLineId) {
+ entry.lineIdMap[beden] = String(rawLineId)
+ }
+
+ if (qty > 0) {
+ entry.__tmpMap[beden] = (entry.__tmpMap[beden] || 0) + qty
+ entry.adet += qty
+ entry.tutar += qty * entry.fiyat
+ }
+ }
+
+ /* =======================================================
+ 3️⃣ FINAL — grpKey KESİN + AKSBİR FIX
+ ======================================================= */
+ const out = []
+
+ for (const rows of Object.values(merged)) {
+ for (const row of rows) {
+ if (!row.__tmpMap) {
+ out.push(row)
+ continue
+ }
+
+ const bedenList = Object.keys(row.__tmpMap)
+
+ // 🔒 TEK VE KESİN KARAR
+ const grpKey = detectBedenGroup(
+ bedenList,
+ row.urunAnaGrubu,
+ row.kategori
+ )
+
+ row.grpKey = grpKey
+ row.bedenMap = { [grpKey]: { ...row.__tmpMap } }
+
+ /* ===================================================
+ 🔒 AKSBİR — BOŞLUK BEDEN GERÇEK ADETİ ALIR
+ ❗ STD’ye dönme YOK
+ ❗ 0 yazma YOK
+ =================================================== */
+ if (grpKey === 'aksbir') {
+ row.bedenMap[grpKey] ??= {}
+ row.bedenMap[grpKey][' '] = Number(row.adet || 0)
+ }
+
+ delete row.__tmpMap
+ out.push(row)
+ }
+ }
+
+ console.log(
+ `📦 normalizeOrderLines (v9 + lineIdMap) → ${out.length} satır`
+ )
+
+ return out
+ }
+
+ ,
+
+ /**
+ * ===========================================================
+ * loadProductSizes — FINAL v4.2 (EDITOR SAFE)
+ * -----------------------------------------------------------
+ * ✔ grpKey SADECE form.grpKey
+ * ✔ schemaMap TEK OTORİTE
+ * ✔ edit modda BEDEN LABEL DOKUNULMAZ
+ * ✔ ' ' (boş beden) korunur
+ * ===========================================================
+ */
+ async loadProductSizes(form, forceRefresh = false, $q = null) {
+ if (!form?.model) return
+
+ const store = this
+ const prevBusy = !!store._uiBusy
+ const prevPrevent = !!store.preventPersist
+ store._uiBusy = true
+ store.preventPersist = true
+
+ try {
+ const grpKey = form.grpKey
+ if (!grpKey) {
+ console.warn('⛔ loadProductSizes iptal → grpKey yok')
+ return
+ }
+
+ const colorKey = form.renk || 'nocolor'
+ const color2Key = form.renk2 || 'no2color'
+ const cacheKey = `${form.model}_${colorKey}_${color2Key}_${grpKey}`
+
+ /* =======================================================
+ ♻️ CACHE (LABEL DOKUNMADAN)
+ ======================================================= */
+ if (!forceRefresh && sizeCache.value?.[cacheKey]) {
+ const cached = sizeCache.value[cacheKey]
+ bedenStock.value = [...cached.stockArray]
+ stockMap.value = { ...cached.stockMap }
+ console.log(`♻️ loadProductSizes CACHE → ${grpKey}`)
+ return
+ }
+
+ /* =======================================================
+ 📡 API
+ ======================================================= */
+ const params = { code: form.model }
+ if (form.renk) params.color = form.renk
+ if (form.renk2) params.color2 = form.renk2
+
+ const res = await api.get('/product-colorsize', { params })
+ const data = Array.isArray(res?.data) ? res.data : []
+
+ if (!data.length) {
+ bedenStock.value = []
+ stockMap.value = {}
+ return
+ }
+
+ /* =======================================================
+ 📦 STOK MAP (' ' KORUNUR)
+ ======================================================= */
+ const apiStockMap = {}
+ for (const x of data) {
+ const key =
+ x.item_dim1_code === null || x.item_dim1_code === ''
+ ? ' '
+ : String(x.item_dim1_code)
+ apiStockMap[key] = Number(x.kullanilabilir_envanter ?? 0)
+ }
+
+ const finalStockMap = {}
+ for (const lbl of form.bedenLabels) {
+ finalStockMap[lbl] = apiStockMap[lbl] ?? 0
+ }
+
+ stockMap.value = { ...finalStockMap }
+ bedenStock.value = Object.entries(stockMap.value).map(
+ ([beden, stok]) => ({ beden, stok })
+ )
+
+ /* =======================================================
+ 💾 CACHE
+ ======================================================= */
+ sizeCache.value[cacheKey] = {
+ labels: [...form.bedenLabels],
+ stockArray: [...bedenStock.value],
+ stockMap: { ...stockMap.value }
+ }
+
+ console.log(`✅ loadProductSizes FINAL v4.2 → ${grpKey}`)
+ } catch (err) {
+ console.error('❌ loadProductSizes hata:', err)
+ $q?.notify?.({ type: 'negative', message: 'Beden / stok alınamadı' })
+ } finally {
+ store._uiBusy = prevBusy
+ store.preventPersist = prevPrevent
+ console.log('🧩 Editor beden hydrate', {
+ grpKey: form.grpKey,
+ labels: form.bedenLabels,
+ values: form.bedenler
+ })
+
+ }
+ }
+
+
+
+
+ ,
+
+ // =======================================================
+// 🔸 TOPLAM HESAPLAMA (store içi) — X3 SAFE
+// -------------------------------------------------------
+// ✔ f.adet / f.tutar hesaplanır
+// ✔ store.totalAmount ASLA set edilmez
+// ✔ gerçek toplam → header.TotalAmount
+// =======================================================
+ updateTotals(f) {
+ // 1️⃣ Satır adet
+ f.adet = (f.bedenler || []).reduce(
+ (a, b) => a + Number(b || 0),
+ 0
+ )
+
+ // 2️⃣ Satır tutar
+ const fiyat = Number(f.fiyat) || 0
+ f.tutar = Number((f.adet * fiyat).toFixed(2))
+
+ // 3️⃣ Header toplam (tek gerçek state)
+ if (this.header) {
+ const total = (this.summaryRows || []).reduce(
+ (sum, r) => sum + Number(r?.tutar || 0),
+ 0
+ )
+
+ this.header.TotalAmount = Number(total.toFixed(2))
+ }
+
+ return f
+ }
+ ,
+
+ // =======================================================
+ // 🔸 GRUP ANAHTARI TESPİTİ
+ // =======================================================
+ activeGroupKeyForRow(row) {
+ const g = (row?.urunAnaGrubu || '').toUpperCase()
+ if (g.includes('TAKIM')) return 'tak'
+ if (g.includes('PANTOLON')) return 'pan'
+ if (g.includes('GÖMLEK')) return 'gom'
+ if (g.includes('AYAKKABI')) return 'ayk'
+ if (g.includes('YAŞ')) return 'yas'
+ return 'tak'
+ },
+ /* =======================================================
+ 🔹 MODE YÖNETİMİ — new / edit arası geçiş
+======================================================= */
+ setMode(mode) {
+ if (!['new', 'edit', 'view'].includes(mode)) {
+ console.warn('⚠️ Geçersiz mode:', mode)
+ return
+ }
+
+ this.mode = mode
+ console.log(`🧭 Order mode set edildi → ${mode}`)
+ }
+ ,
+ /* ===========================================================
+ 🟦 submitAllReal (v12.1c — FINAL / CLEAN + PRE-VALIDATE)
+ -----------------------------------------------------------
+ ✔ NEW → INSERT, EDIT → UPDATE (tek karar noktası)
+ ✔ Controlled submit → route guard SUSAR
+ ✔ Snapshot temizliği route öncesi
+ ✔ Kaydet → edit replace → backend reload
+ ✔ Listeye giderken guard popup 1 kez bypass
+ ✔ ✅ PRE-VALIDATE → prItemVariant olmayan kombinasyonlar kaydı DURDURUR
+ =========================================================== */
+ async submitAllReal($q, router, form, summaryRows, productCache) {
+ let serverOrderId = null
+ let serverOrderNo = null
+
+ try {
+ this.loading = true
+
+ // 🔒 Kontrollü submit → route leave guard susar
+ this.isControlledSubmit = true
+
+ const isNew = this.mode === 'new'
+ const { header, lines } = this.buildFinalOrderJson()
+
+ // =======================================================
+ // 🧾 DEBUG — FRONTEND → BACKEND GİDEN PAYLOAD
+ // =======================================================
+ console.groupCollapsed(
+ `%c📤 ORDER PAYLOAD (${this.mode})`,
+ 'color:#c9a873;font-weight:bold'
+ )
+
+ console.log('HEADER:', JSON.parse(JSON.stringify(header)))
+
+ lines.forEach((l, i) => {
+ console.log(`LINE[${i}]`, {
+ OrderLineID: l.OrderLineID,
+ ClientKey: l.ClientKey,
+ ItemCode: l.ItemCode,
+ ColorCode: l.ColorCode,
+ ItemDim1Code: l.ItemDim1Code,
+ ItemDim2Code: l.ItemDim2Code,
+ ItemDim3Code: l.ItemDim3Code,
+ Qty1: l.Qty1,
+ ComboKey: l.ComboKey
+ })
+ })
+
+ console.groupEnd()
+
+ // =======================================================
+ // 🧾 DEBUG (opsiyonel helper)
+ // =======================================================
+ this.debugOrderPayload?.(header, lines, 'PRE-VALIDATE')
+
+ // =======================================================
+ // 🧪 PRE-VALIDATE — prItemVariant ön kontrol
+ // - invalid varsa CREATE/UPDATE ÇALIŞMAZ
+ // =======================================================
+ const v = await api.post('/order/validate', { header, lines })
+ const invalid = v?.data?.invalid || []
+
+ if (invalid.length > 0) {
+ await this.showInvalidVariantDialog?.($q, invalid)
+ return // ❌ create / update ÇALIŞMAZ
+ }
+
+ console.log('📤 submitAllReal payload', {
+ mode: this.mode,
+ lines: lines.length,
+ deletes: lines.filter(l => l._deleteSignal).length
+ })
+
+ /* =======================================================
+ 🚀 API CALL — TEK NOKTA
+ ======================================================= */
+ const resp = await api.post(
+ isNew ? '/order/create' : '/order/update',
+ { header, lines }
+ )
+
+ const data = resp?.data || {}
+
+ serverOrderId =
+ data.orderID ||
+ data.orderHeaderID ||
+ data.id ||
+ header?.OrderHeaderID
+
+ serverOrderNo =
+ data.orderNumber ||
+ data.orderNo ||
+ header?.OrderNumber
+
+ if (!serverOrderId) {
+ throw new Error('OrderHeaderID backend’den dönmedi')
+ }
+
+ /* =======================================================
+ 🔁 MODE SWITCH → EDIT
+ ======================================================= */
+ this.setMode('edit')
+
+ // Header patch (ID / No)
+ this.header = {
+ ...this.header,
+ OrderHeaderID: serverOrderId,
+ OrderNumber: serverOrderNo
+ }
+
+ /* =======================================================
+ 🧹 KRİTİK: Snapshot + Dirty temizliği
+ ❗ ROUTE değişmeden ÖNCE
+ ======================================================= */
+ this.updateHeaderTotals?.()
+ this.markAsSaved?.()
+
+ /* =======================================================
+ 🧹 KRİTİK: NEW → EDIT geçişinde TÜM SNAPSHOT TEMİZLENİR
+ ======================================================= */
+ this.clearAllOrderSnapshots()
+
+ $q.notify({
+ type: 'positive',
+ message: `Sipariş kaydedildi: ${serverOrderNo || ''}`.trim()
+ })
+
+ /* =======================================================
+ 🔀 ROUTE REPLACE (EDIT MODE)
+ - aynı sayfa → param değişti
+ - guard 1 kez bypass
+ ======================================================= */
+ this.allowRouteLeaveOnce = true
+
+ await router.replace({
+ name: 'order-entry',
+ params: { orderHeaderID: serverOrderId },
+ query: { mode: 'edit', source: 'backend' }
+ })
+
+ /* =======================================================
+ 🔄 BACKEND RELOAD (TEK GERÇEK KAYNAK)
+ ======================================================= */
+ await this.openExistingForEdit(serverOrderId, {
+ $q,
+ form,
+ summaryRowsRef: summaryRows,
+ productCache
+ })
+
+ /* =======================================================
+ ❓ USER NEXT STEP
+ ======================================================= */
+ const choice = await new Promise(resolve => {
+ $q.dialog({
+ title: 'Sipariş Kaydedildi',
+ options: {
+ type: 'radio',
+ model: 'continue',
+ items: [
+ { label: '✏️ Düzenlemeye Devam', value: 'continue' },
+ { label: '🖨 Yazdır', value: 'print' },
+ { label: '📋 Listeye Dön', value: 'list' }
+ ]
+ },
+ ok: { label: 'Seç' },
+ cancel: { label: 'Kapat' }
+ })
+ .onOk(v => resolve(v))
+ .onCancel(() => resolve('continue'))
+ })
+
+ /* =======================================================
+ 🧭 USER ROUTING
+ ======================================================= */
+ if (choice === 'print') {
+ const id = this.header?.OrderHeaderID || serverOrderId
+ if (id) await this.downloadOrderPdf(id)
+ return
+ }
+
+ if (choice === 'list') {
+ this.allowRouteLeaveOnce = true
+ await router.push({ name: 'order-list' })
+ return
+ }
+
+ // continue → sayfada kal (hiçbir şey yapma)
+
+ } catch (err) {
+ console.error('❌ submitAllReal:', err)
+
+ $q.notify({
+ type: 'negative',
+ message:
+ err?.response?.data?.message ||
+ err?.message ||
+ 'Kayıt sırasında hata'
+ })
+
+ } finally {
+ // 🔓 Guard’lar normale dönsün
+ this.isControlledSubmit = false
+ this.loading = false
+ }
+ }
+
+ ,
+
+ /* =======================================================
+ 🧪 SUBMIT ALL TEST
+ ======================================================= */
+ async submitAllTest($q = null) {
+ try {
+ const { header, lines } = this.buildFinalOrderJson()
+
+ console.log('🧾 TEST HEADER', Object.keys(header).length, 'alan')
+ console.log(JSON.stringify(header, null, 2))
+
+ console.log('🧾 TEST LINES', lines.length, 'satır')
+ console.log(JSON.stringify(lines, null, 2))
+
+ $q?.notify?.({
+ type: 'info',
+ message: `Header (${Object.keys(header).length}) + Lines (${lines.length}) gösterildi`,
+ position: 'top'
+ })
+ } catch (err) {
+ console.error('❌ submitAllTest hata:', err)
+ $q?.notify?.({
+ type: 'negative',
+ message: 'Gösterimde hata oluştu ❌',
+ position: 'top'
+ })
+ }
+ },
+
+
+ /* =======================================================
+ 🧹 KAYIT SONRASI TEMİZLİK
+ ======================================================= */
+ afterSubmit(opts = {
+ keepLocalStorage: true,
+ backendPayload: null,
+ resetMode: true // 🔑 yeni
+ }) {
+ try {
+ console.log('🧹 afterSubmit başlatıldı', opts)
+
+ if (opts?.backendPayload?.header?.OrderHeaderID) {
+ this.mergeAndPersistBackendOrder(
+ opts.backendPayload.header.OrderHeaderID,
+ opts.backendPayload
+ )
+ }
+
+ if (!opts?.keepLocalStorage) {
+ this.clearStorage()
+ this.clearTransaction()
+ } else {
+ this.saveSnapshot()
+ }
+
+ this.orders = []
+ this.header = {}
+ this.editingKey = null
+ this.currentOrderId = null
+
+ // 🔐 MODE RESET OPSİYONEL
+ if (opts.resetMode === true) {
+ this.mode = 'new'
+ }
+
+ console.log('✅ afterSubmit tamamlandı.')
+ } catch (err) {
+ console.error('❌ afterSubmit hata:', err)
+ }
+ }
+
+ ,
+
+
+ /* ===========================================================
+ 🟦 BUILD FINAL ORDER JSON — SAFE v26.1 (FINAL)
+ -----------------------------------------------------------
+ ✔ ComboKey TEK OTORİTE → buildComboKey (bedenKey ile)
+ ✔ UI/Map placeholder: '_' (bedenKey)
+ ✔ DB/payload: '' (bedenPayload) → "_" ASLA GİTMEZ
+ ✔ payload içinde aynı ComboKey TEK satır
+ ✔ backend duplicate guard %100 uyumlu (ComboKey stabil)
+ ✔ Final assert: payload’da "_" yakalanırsa patlatır
+ =========================================================== */
+ buildFinalOrderJson () {
+ const auth = useAuthStore()
+ const u = auth?.user || {}
+ const now = dayjs()
+
+ /* =========================
+ HELPERS
+ ========================== */
+ const toNum = v => Number(v) || 0
+ const safeStr = v => (v == null ? '' : String(v).trim())
+
+ const formatDateOnly = v => (v ? dayjs(v).format('YYYY-MM-DD') : null)
+ const formatTimeOnly = v => dayjs(v).format('HH:mm:ss')
+ const formatDateTime = v => (v ? dayjs(v).format('YYYY-MM-DD HH:mm:ss') : null)
+
+ // ✅ Payload beden normalize: "_" / "-" / "" => ''
+ const normBeden = (v) => {
+ const s = safeStr(v)
+ if (s === '' || s === '_' || s === '-') return '' // payload empty
+ return s
+ }
+
+ /* =========================
+ USER META
+ ========================== */
+ const group = safeStr(u?.v3usergroup)
+ const v3name = safeStr(u?.v3_username)
+ const who = (group && v3name) ? `${group} ${v3name}` : (v3name || 'BSS')
+
+ const PCT_CODE_ZERO = '%0'
+ const VAT_CODE_ZERO = '%0'
+
+ /* =========================
+ HEADER
+ ========================== */
+ const headerId = this.header?.OrderHeaderID || crypto.randomUUID()
+ const docCurrency = safeStr(this.header?.DocCurrencyCode) || 'TRY'
+ const exRate = toNum(this.header?.ExchangeRate) || 1
+
+ const avgDueSource =
+ this.header?.AverageDueDate ||
+ dayjs(this.header?.OrderDate || now).add(14, 'day')
+
+ const header = {
+ ...this.header,
+
+ OrderHeaderID: headerId,
+ OrderDate: formatDateOnly(this.header?.OrderDate || now),
+ OrderTime: formatTimeOnly(now),
+ AverageDueDate: formatDateOnly(avgDueSource),
+
+ DocCurrencyCode: docCurrency,
+ LocalCurrencyCode: safeStr(this.header?.LocalCurrencyCode) || 'TRY',
+ ExchangeRate: exRate,
+
+ CreatedUserName:
+ this.mode === 'edit'
+ ? (this.header?.CreatedUserName || who)
+ : who,
+
+ CreatedDate:
+ this.mode === 'edit'
+ ? formatDateTime(this.header?.CreatedDate || now)
+ : formatDateTime(now),
+
+ LastUpdatedUserName: who,
+ LastUpdatedDate: formatDateTime(now)
+ }
+
+ /* =======================================================
+ LINES — COMBOKEY AGGREGATE (TEK MAP)
+ ======================================================= */
+ const lines = []
+ const lineByCombo = new Map() // 🔒 KEY = ComboKey
+
+ const pushOrMerge = (row, ctx) => {
+ const {
+ grpKey,
+ bedenKey, // ✅ sadece ComboKey / Map için ('_' olabilir)
+ bedenPayload, // ✅ DB için ('' / 'S' / 'M' ...)
+ qty,
+ orderLineId,
+ isDeleteSignal
+ } = ctx
+
+ if (qty <= 0 && !isDeleteSignal) return
+
+ // ComboKey stabil kalsın diye bedenKey kullan
+ const comboKey = buildComboKey(row, bedenKey)
+
+ const makeLine = () => ({
+ OrderLineID: orderLineId || '',
+ ClientKey: makeLineClientKey(row, grpKey, bedenKey),
+ ComboKey: comboKey,
+
+ SortOrder: 0,
+ ItemTypeCode: 1,
+
+ ItemCode: safeStr(row.model),
+ ColorCode: safeStr(row.renk),
+
+ // ✅ PAYLOAD: "_" ASLA YOK
+ ItemDim1Code: bedenPayload,
+
+ ItemDim2Code: safeStr(row.renk2),
+ ItemDim3Code: '',
+
+ Qty1: isDeleteSignal ? 0 : qty,
+ Qty2: 0,
+
+ CancelQty1: 0,
+ CancelQty2: 0,
+
+ DeliveryDate: row.terminTarihi
+ ? formatDateTime(row.terminTarihi)
+ : null,
+
+ PlannedDateOfLading: row.terminTarihi
+ ? formatDateOnly(row.terminTarihi)
+ : null,
+
+ LineDescription: safeStr(row.aciklama),
+ UsedBarcode: '',
+ CostCenterCode: '',
+
+ VatCode: VAT_CODE_ZERO,
+ VatRate: toNum(row.vatRate ?? row.VatRate ?? 0),
+
+ PCTCode: PCT_CODE_ZERO,
+ PCTRate: 0,
+
+ LDisRate1: 0,
+ LDisRate2: 0,
+ LDisRate3: 0,
+ LDisRate4: 0,
+ LDisRate5: 0,
+
+ DocCurrencyCode: header.DocCurrencyCode,
+ PriceCurrencyCode: header.DocCurrencyCode,
+ PriceExchangeRate: toNum(header.ExchangeRate),
+ Price: toNum(row.fiyat),
+
+ BaseProcessCode: 'WS',
+ BaseOrderNumber: header.OrderNumber,
+ BaseCustomerTypeCode: 0,
+ BaseCustomerCode: header.CurrAccCode,
+ BaseSubCurrAccID: null,
+ BaseStoreCode: '',
+
+ OrderHeaderID: headerId,
+
+ CreatedUserName: who,
+ CreatedDate: formatDateTime(row.CreatedDate || now),
+ LastUpdatedUserName: who,
+ LastUpdatedDate: formatDateTime(now),
+
+ SurplusOrderQtyToleranceRate: 0,
+ WithHoldingTaxTypeCode: '',
+ DOVCode: ''
+ })
+
+ const existing = lineByCombo.get(comboKey)
+
+ if (!existing) {
+ const ln = makeLine()
+ lineByCombo.set(comboKey, ln)
+ lines.push(ln)
+ return
+ }
+
+ /* DELETE */
+ if (isDeleteSignal) {
+ if (orderLineId && !existing.OrderLineID) {
+ existing.OrderLineID = orderLineId
+ }
+ existing.Qty1 = 0
+ return
+ }
+
+ /* MERGE */
+ existing.Qty1 += qty
+
+ if (this.mode === 'edit' && orderLineId && !existing.OrderLineID) {
+ existing.OrderLineID = orderLineId
+ }
+
+ existing.Price = toNum(row.fiyat)
+ }
+
+ /* =======================================================
+ ORDER ROW LOOP
+ ======================================================= */
+ for (const row of this.orders || []) {
+ if (row?.isClosed === true) continue
+
+ const grpKey =
+ row.grpKey ||
+ Object.keys(row.bedenMap || {})[0] ||
+ 'GENEL'
+
+ const lineIdMap = row.lineIdMap || {}
+
+ const grouped = row.bedenMap?.[grpKey]
+ const flat =
+ (row.bedenMap && typeof row.bedenMap === 'object' && !grouped)
+ ? row.bedenMap
+ : null
+
+ const map = grouped || flat
+ const hasAnyBeden =
+ map && typeof map === 'object' && Object.keys(map).length > 0
+
+ /* 🔹 BEDENSİZ / AKSBİR */
+ if (!hasAnyBeden) {
+ const qty = toNum(row.qty ?? row.Qty1 ?? row.miktar ?? 0)
+
+ // ✅ ComboKey stabil: bedenKey = '_'
+ const bedenKey = '_'
+ // ✅ Payload: boş string
+ const bedenPayload = ''
+
+ let orderLineId = ''
+ if (this.mode === 'edit') {
+ // lineIdMap burada '_' ile tutuluyorsa onu da oku
+ orderLineId =
+ safeStr(lineIdMap?.[bedenKey]) ||
+ safeStr(lineIdMap?.[bedenPayload]) ||
+ safeStr(row.OrderLineID)
+ }
+
+ pushOrMerge(row, {
+ grpKey,
+ bedenKey,
+ bedenPayload,
+ qty,
+ orderLineId,
+ isDeleteSignal: row._deleteSignal === true && !!orderLineId
+ })
+ continue
+ }
+
+ /* 🔹 BEDENLİ */
+ for (const [bedenRaw, qtyRaw] of Object.entries(map)) {
+ const qty = toNum(qtyRaw)
+
+ // ✅ payload beden: '' / 'S' / 'M' ...
+ const bedenPayload = normBeden(bedenRaw)
+ // ✅ combokey beden: boşsa '_' ile stabil kalsın
+ const bedenKey = bedenPayload || '_'
+
+ let orderLineId = ''
+ if (this.mode === 'edit') {
+ // lineIdMap anahtarı sizde hangi bedenle tutuluyorsa ikisini de dene
+ orderLineId =
+ safeStr(lineIdMap?.[bedenKey]) ||
+ safeStr(lineIdMap?.[bedenPayload]) ||
+ (Object.keys(map).length === 1
+ ? safeStr(row.OrderLineID)
+ : '')
+ }
+
+ pushOrMerge(row, {
+ grpKey,
+ bedenKey,
+ bedenPayload,
+ qty,
+ orderLineId,
+ isDeleteSignal: row._deleteSignal === true && !!orderLineId
+ })
+ }
+ }
+
+ /* =======================================================
+ FINAL SORT
+ ======================================================= */
+ lines.forEach((ln, i) => { ln.SortOrder = i + 1 })
+
+ /* =======================================================
+ ASSERT — payload’da "_" OLAMAZ
+ ======================================================= */
+ if (lines.some(l => (l.ItemDim1Code || '') === '_' )) {
+ console.error('❌ Payload’da "_" yakalandı', lines.filter(l => l.ItemDim1Code === '_'))
+ throw new Error('Payload ItemDim1Code "_" olamaz')
+ }
+ /* =======================================================
+ 🔍 DEBUG — BUILD FINAL ORDER JSON OUTPUT
+ ======================================================= */
+ console.groupCollapsed('%c📦 BUILD FINAL ORDER JSON', 'color:#c9a873;font-weight:bold')
+
+ console.log('🧾 HEADER:', header)
+
+ console.table(
+ lines.map((l, i) => ({
+ i: i + 1,
+ OrderLineID: l.OrderLineID,
+ ClientKey: l.ClientKey,
+ ComboKey: l.ComboKey,
+ ItemCode: l.ItemCode,
+ ColorCode: l.ColorCode,
+ ItemDim1Code: JSON.stringify(l.ItemDim1Code), // <-- kritik
+ ItemDim2Code: l.ItemDim2Code,
+ Qty1: l.Qty1,
+ Price: l.Price
+ }))
+ )
+
+ console.groupEnd()
+
+ return { header, lines }
+ }
+
+
+
+
+
+
+
+ ,/* ===========================================================
+ ✅ STORE ACTIONS — FIXED HELPERS
+ - setRowErrorByClientKey
+ - clearRowErrorByClientKey
+ - applyTerminToRowsIfEmpty
+=========================================================== */
+ setRowErrorByClientKey(clientKey, payload) {
+ if (!clientKey) return
+ if (!Array.isArray(this.summaryRows)) return
+
+ const row = this.summaryRows.find(r => r?.clientKey === clientKey)
+ if (!row) return
+
+ row._error = {
+ code: payload?.code,
+ message: payload?.message
+ }
+ },
+
+ clearRowErrorByClientKey(clientKey) {
+ if (!clientKey) return
+ if (!Array.isArray(this.summaryRows)) return
+
+ const row = this.summaryRows.find(r => r?.clientKey === clientKey)
+ if (!row) return
+
+ if (row._error) {
+ delete row._error
+ }
+ },
+
+ applyTerminToRowsIfEmpty(dateStr) {
+ if (!dateStr) return
+ if (!Array.isArray(this.summaryRows)) return
+
+ // ❗ reassign YOK — patch/mutate
+ for (const r of this.summaryRows) {
+ if (!r?.terminTarihi || r.terminTarihi === '') {
+ r.terminTarihi = dateStr
+ }
+ }
+
+ // opsiyonel ama genelde doğru:
+ this.persistLocalStorage?.()
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ } // actions sonu
+}) // defineStore sonu
+
+
+/* ===========================================================
+ 🔹 BEDEN LABEL NORMALİZASYONU (exported helper)
+ =========================================================== */
+export function normalizeBedenLabel(v) {
+ if (v === null || v === undefined) return ' '
+ let s = String(v).trim()
+ if (s === '') return ' '
+ // 44R, 50L vb. son ekleri at
+ s = s.replace(/(^\d+)\s*[A-Z]?$/i, '$1')
+ s = s.toUpperCase()
+
+ // harfli bedenlerin normalizasyonu
+ const map = {
+ 'XS': 'XS', 'S': 'S', 'M': 'M', 'L': 'L', 'XL': 'XL',
+ 'XXL': '2XL', '2XL': '2XL', '3XL': '3XL', '4XL': '4XL',
+ '5XL': '5XL', '6XL': '6XL', '7XL': '7XL', 'STD': 'STD'
+ }
+ if (map[s]) return map[s]
+
+ // tamamen sayıysa string olarak döndür
+ if (/^\d+$/.test(s)) return s
+
+ // virgüllü değer geldiyse ilkini al
+ if (s.includes(',')) return s.split(',')[0].trim()
+ return s
+}
+
+/* ===========================================================
+ 🔹 BEDEN GRUBU ALGILAMA HELPER’I
+ -----------------------------------------------------------
+ Gelen beden listesini, ürün grubu/kategori bilgisine göre
+ doğru grup anahtarına dönüştürür (ayk, yas, pan, gom, tak, aksbir).
+ -----------------------------------------------------------
+=========================================================== */
+export function detectBedenGroup(bedenList, urunAnaGrubu = '', urunKategori = '') {
+ const list = Array.isArray(bedenList) && bedenList.length > 0
+ ? bedenList.map(v => (v || '').toString().trim().toUpperCase())
+ : [' ']
+
+ const ana = (urunAnaGrubu || '')
+ .toUpperCase()
+ .trim()
+ .replace(/\(.*?\)/g, '')
+ .replace(/[^A-ZÇĞİÖŞÜ0-9\s]/g, '')
+ .replace(/\s+/g, ' ')
+
+ const kat = (urunKategori || '').toUpperCase().trim()
+// 🔸 Aksesuar ise "aksbir"
+ const aksesuarGruplari = [
+ 'AKSESUAR','KRAVAT','PAPYON','KEMER','CORAP','ÇORAP',
+ 'FULAR','MENDIL','MENDİL','KASKOL','ASKI',
+ 'YAKA','KOL DUGMESI','KOL DÜĞMESİ'
+ ]
+ const giyimGruplari = ['GÖMLEK','CEKET','PANTOLON','MONT','YELEK','TAKIM','TSHIRT','TİŞÖRT']
+ // 🔸 Pantolon özel durumu
+ if (
+ aksesuarGruplari.some(g => ana.includes(g) || kat.includes(g)) &&
+ !giyimGruplari.some(g => ana.includes(g))
+ ) return 'aksbir'
+
+ if (ana.includes('PANTOLON') && kat.includes('YETİŞKİN')) return 'pan'
+ // 🔸 Tamamen numerik (örneğin 39-44 arası) → ayakkabı
+ const allNumeric = list.every(v => /^\d+$/.test(v))
+ if (allNumeric) {
+ const nums = list.map(v => parseInt(v, 10)).filter(Boolean)
+ const diffs = nums.slice(1).map((v, i) => v - nums[i])
+ if (diffs.every(d => d === 1) && nums[0] >= 35 && nums[0] <= 46) return 'ayk'
+ }
+
+ // 🔸 Yaş grubu (çocuk/garson)
+ if (kat.includes('GARSON') || kat.includes('ÇOCUK')) return 'yas'
+
+ // 🔸 Harfli beden varsa doğrudan "gom" (gömlek, üst giyim)
+ const harfliBedenler = ['XS','S','M','L','XL','XXL','3XL','4XL']
+ if (list.some(b => harfliBedenler.includes(b))) return 'gom'
+
+
+
+
+ // 🔸 Varsayılan: takım elbise
+ return 'tak'
+}
+
+export function toSummaryRowFromForm(form) {
+ if (!form) return null
+
+ const grpKey = form.grpKey || 'tak'
+ const bedenMap = {}
+
+ const labels = Array.isArray(form.bedenLabels) ? form.bedenLabels : []
+ const values = Array.isArray(form.bedenler) ? form.bedenler : []
+
+ for (let i = 0; i < labels.length; i++) {
+ const rawLbl = labels[i]
+ const lbl =
+ rawLbl == null || String(rawLbl).trim() === ''
+ ? ' '
+ : String(rawLbl).trim()
+
+ const val = Number(values[i] || 0)
+ if (val > 0) {
+ bedenMap[lbl] = val
+ }
+ }
+
+ return {
+ id: form.id || newGuid(),
+ OrderLineID: form.OrderLineID || null,
+
+ model: form.model || '',
+ renk: form.renk || '',
+ renk2: form.renk2 || '',
+
+ urunAnaGrubu: form.urunAnaGrubu || '',
+ urunAltGrubu: form.urunAltGrubu || '',
+ aciklama: form.aciklama || '',
+
+ fiyat: Number(form.fiyat || 0),
+ pb: form.pb || 'USD',
+
+ adet: Number(form.adet || 0),
+ tutar: Number(form.tutar || 0),
+
+ grpKey,
+ bedenMap: {
+ [grpKey]: { ...bedenMap }
+ },
+
+ terminTarihi: (form.terminTarihi || '').substring(0, 10)
+ }
+}
+
+
+
+
+/* ===========================================================
+ 🔹 TOPLAM HESAPLAMA (EXPORT)
+ -----------------------------------------------------------
+ Hem store içinde hem de component tarafında kullanılabilir.
+=========================================================== */
+export function updateTotals(f) {
+ f.adet = (f.bedenler || []).reduce((a, b) => a + Number(b || 0), 0)
+ const fiyat = Number(f.fiyat) || 0
+ f.tutar = (f.adet * fiyat).toFixed(2)
+ return f
+}
+
+/* ===========================================================
+ 🔹 EXPORT SET — Tek Merkezli Dışa Aktarımlar
+ =========================================================== */
+
+/**
+ * 🧩 Shared Reactive Refs
+ * -----------------------------------------------------------
+ * import { sharedOrderEntryRefs } from 'src/stores/orderentryStore'
+ * const { stockMap, bedenStock, sizeCache } = sharedOrderEntryRefs
+ */
+export const sharedOrderEntryRefs = {
+ stockMap,
+ bedenStock,
+ sizeCache,
+
+
+}
+
diff --git a/ui/src/stores/permissionStore.js b/ui/src/stores/permissionStore.js
new file mode 100644
index 0000000..6fb2fb4
--- /dev/null
+++ b/ui/src/stores/permissionStore.js
@@ -0,0 +1,182 @@
+import { defineStore } from 'pinia'
+import api from 'src/services/api'
+import { useAuthStore } from 'stores/authStore'
+
+
+export const usePermissionStore = defineStore('permission', {
+
+ state: () => ({
+
+ // API route yetkileri
+ routes: [],
+
+ // module+action matrix
+ matrix: [],
+
+ loaded: false
+ }),
+
+
+ getters: {
+
+ /* ================= ADMIN ================= */
+
+ isAdmin () {
+ const auth = useAuthStore()
+ return auth.isAdmin === true
+ },
+
+
+ /* ================= API ROUTE ================= */
+
+ hasApiPermission: (state) => (apiPathOrPerm) => {
+
+ const auth = useAuthStore()
+
+ if (auth.isAdmin) return true
+ if (!state.loaded) return false
+ if (!apiPathOrPerm) return true
+
+
+ // ============================
+ // 1️⃣ MODULE:ACTION GELDİYSE
+ // ============================
+
+ if (apiPathOrPerm.includes(':')) {
+
+ const [module, action] = apiPathOrPerm.split(':')
+
+ return state.matrix.some(p =>
+ p.module === module &&
+ p.action === action &&
+ p.allowed === true
+ )
+ }
+
+
+ // ============================
+ // 2️⃣ API PATH GELDİYSE
+ // ===========================
+
+
+ const apiPath = apiPathOrPerm
+
+
+ // exact match
+ if (state.routes.some(p =>
+ p.route === apiPath && p.can_access
+ )) {
+ return true
+ }
+
+
+ // /{id} normalize
+ const normalized = apiPath
+ .replace(/\/\d+/g, '/{id}')
+
+
+ if (state.routes.some(p =>
+ p.route === normalized && p.can_access
+ )) {
+ return true
+ }
+
+
+ // prefix
+ return state.routes.some(p =>
+ p.can_access && apiPath.startsWith(p.route)
+ )
+ },
+
+
+ /* ================= MODULE ================= */
+
+ hasModule: (state) => (module) => {
+
+ const auth = useAuthStore()
+
+ if (auth.isAdmin) return true
+ if (!state.loaded) return false
+
+ return state.matrix.some(p =>
+ p.module === module &&
+ p.allowed === true
+ )
+ },
+
+
+ /* ================= ACTION ================= */
+
+ hasPermission: (state) => (module, action) => {
+
+ const auth = useAuthStore()
+
+ if (auth.isAdmin) return true
+ if (!state.loaded) return false
+
+ return state.matrix.some(p =>
+ p.module === module &&
+ p.action === action &&
+ p.allowed === true
+ )
+ }
+ },
+
+
+ actions: {
+
+ async fetchPermissions () {
+
+ const auth = useAuthStore()
+
+ if (auth.isAdmin) {
+
+ this.routes = []
+ this.matrix = []
+ this.loaded = true
+
+ return
+ }
+
+
+ try {
+
+ // API ROUTES
+ const routesRes = await api.get('/permissions/routes')
+ this.routes = routesRes.data || []
+
+
+ // EFFECTIVE MATRIX
+ const effRes = await api.get('/permissions/effective')
+ this.matrix = effRes.data || []
+ console.group('🔐 PERMISSION DEBUG')
+
+ console.log('API ROUTES:', this.routes)
+ console.log('EFFECTIVE MATRIX:', this.matrix)
+
+ console.groupEnd()
+
+
+ } catch (err) {
+
+ console.error('❌ Permission load failed', err)
+
+ this.routes = []
+ this.matrix = []
+
+
+ } finally {
+
+ this.loaded = true
+ }
+ },
+
+
+ clear () {
+
+ this.routes = []
+ this.matrix = []
+ this.loaded = false
+ }
+ }
+})
diff --git a/ui/src/stores/statementdetailStore.js b/ui/src/stores/statementdetailStore.js
new file mode 100644
index 0000000..056cfa8
--- /dev/null
+++ b/ui/src/stores/statementdetailStore.js
@@ -0,0 +1,66 @@
+// src/stores/statementdetailStore.js
+import { defineStore } from 'pinia'
+import api from 'src/services/api'
+
+export const useStatementdetailStore = defineStore('statementdetail', {
+ state: () => ({
+ details: [],
+ loading: false,
+ error: null
+ }),
+
+ actions: {
+ async loadDetails ({ accountCode, startDate, endDate, parislemler }) {
+ if (!accountCode) {
+ this.error = 'Geçerli bir cari kod seçilmedi.'
+ return
+ }
+
+ this.loading = true
+ this.error = null
+
+ try {
+ // ✅ Params (arrayFormat=repeat global)
+ const params = {
+ startdate: startDate,
+ enddate: endDate
+ }
+
+ if (Array.isArray(parislemler) && parislemler.length > 0) {
+ params.parislemler = parislemler.filter(
+ p => p !== undefined && p !== null && p !== ''
+ )
+ }
+
+ // 🔐 TOKEN + SERIALIZER + ERROR HANDLING OTOMATİK
+ const res = await api.get(
+ `/statements/${accountCode}/details`,
+ { params }
+ )
+
+ this.details = res.data || []
+
+ } catch (err) {
+ console.error('❌ Details yüklenemedi:', err)
+ this.error =
+ err?.data?.message ||
+ err?.message ||
+ 'Detaylar yüklenemedi'
+ } finally {
+ this.loading = false
+ }
+ },
+
+ getDetailsByBelge (belgeNo) {
+ return this.details.filter(
+ d => d.belge_ref_numarasi === belgeNo
+ )
+ },
+
+ reset () {
+ this.details = []
+ this.loading = false
+ this.error = null
+ }
+ }
+})
diff --git a/ui/src/stores/statementheaderStore.js b/ui/src/stores/statementheaderStore.js
new file mode 100644
index 0000000..609256c
--- /dev/null
+++ b/ui/src/stores/statementheaderStore.js
@@ -0,0 +1,163 @@
+// src/stores/statementheaderStore.js
+import { defineStore } from 'pinia'
+import api from 'src/services/api'
+import qs from 'qs'
+import dayjs from 'src/boot/dayjs'
+
+export const useStatementheaderStore = defineStore('statementheader', {
+ state: () => ({
+ headers: [], // Ana tablo verileri
+ details: {}, // Alt tablolar (belge bazlı)
+ loading: false, // Yükleme durumu
+ groupOpen: {} // Para birimi bazlı aç/kapa durumu
+ }),
+
+ getters: {
+ // 🔹 Benzersiz para birimleri listesi
+ currencies(state) {
+ const set = new Set()
+ for (const r of state.headers) {
+ set.add(r.para_birimi || '—')
+ }
+ return Array.from(set).sort()
+ },
+
+ // 🔹 Her para birimi için toplam borç / alacak / bakiye
+ totalsByCurrency(state) {
+ const out = {}
+
+ for (const r of state.headers) {
+ const k = r.para_birimi || '—'
+ if (!out[k]) {
+ out[k] = { borc: 0, alacak: 0, bakiye: 0, count: 0 }
+ }
+
+ out[k].borc += Number(r.borc) || 0
+ out[k].alacak += Number(r.alacak) || 0
+ out[k].bakiye += Number(r.bakiye) || 0
+ out[k].count += 1
+ }
+
+ return out
+ },
+
+ // 🔹 QTable için satırlar (group + data)
+ groupedRows: (state) => {
+ const grouped = {}
+
+ for (const row of state.headers) {
+ const k = row.para_birimi || '—'
+ if (!grouped[k]) grouped[k] = []
+ grouped[k].push(row)
+ }
+
+ const output = []
+
+ for (const [currency, rows] of Object.entries(grouped)) {
+ if (!rows.length) continue
+
+ // 📅 Tarihe göre sırala
+ const sorted = [...rows].sort(
+ (a, b) => new Date(a.belge_tarihi) - new Date(b.belge_tarihi)
+ )
+
+ const lastRow = sorted.at(-1)
+ const lastBalance =
+ lastRow && lastRow.bakiye != null
+ ? Number(lastRow.bakiye)
+ : 0
+
+ // 🔹 Grup satırı
+ output.push({
+ _type: 'group',
+ para_birimi: currency,
+ sonBakiye: lastBalance
+ })
+
+ // 🔹 Alt satırlar
+ if (state.groupOpen[currency] !== false) {
+ sorted.forEach(r => {
+ output.push({ ...r, _type: 'data' })
+ })
+ }
+ }
+
+ return output
+ }
+ },
+
+ actions: {
+ /* ==========================================================
+ 🔄 ANA STATEMENT LİSTESİ
+ ========================================================== */
+ async loadStatements(params = {}) {
+ this.loading = true
+
+ try {
+ const { data } = await api.get(
+ '/statements',
+ {
+ params,
+ paramsSerializer: p =>
+ qs.stringify(p, { arrayFormat: 'repeat' })
+ }
+ )
+
+ this.headers = Array.isArray(data) ? data : []
+
+
+ // 🔹 Yeni gelen para birimleri default açık
+ for (const k of this.currencies) {
+ if (!(k in this.groupOpen)) {
+ this.groupOpen[k] = true
+ }
+ }
+ } catch (err) {
+ console.error('❌ Statements yüklenemedi:', err)
+ this.headers = []
+ } finally {
+ this.loading = false
+ }
+ },
+
+ /* ==========================================================
+ 📄 BELGE DETAYLARI
+ ========================================================== */
+ async loadDetails(belgeNo) {
+ if (!belgeNo || this.details[belgeNo]) return
+
+ try {
+ const { data } = await api.get(
+ `/statements/${belgeNo}/details`
+ )
+
+ this.details[belgeNo] = Array.isArray(data) ? data : []
+
+
+ } catch (err) {
+ console.error('❌ Details yüklenemedi:', err)
+ this.details[belgeNo] = []
+ }
+ },
+
+ /* ==========================================================
+ 🔘 GRUP AÇ / KAPA
+ ========================================================== */
+ toggleGroup(currency) {
+ const key = currency || '—'
+ this.groupOpen[key] = !this.groupOpen[key]
+ },
+
+ openAllGroups() {
+ for (const k of this.currencies) {
+ this.groupOpen[k] = true
+ }
+ },
+
+ closeAllGroups() {
+ for (const k of this.currencies) {
+ this.groupOpen[k] = false
+ }
+ }
+ }
+})
diff --git a/ui/src/stores/store-flag.d.ts b/ui/src/stores/store-flag.d.ts
new file mode 100644
index 0000000..5a3d510
--- /dev/null
+++ b/ui/src/stores/store-flag.d.ts
@@ -0,0 +1,13 @@
+/*
+ WARNING: DO NOT MODIFY OR DELETE
+ This file is auto-generated by Quasar CLI
+ It's recommended to NOT .gitignore it
+ You don't have to use TypeScript in your project, don't worry
+*/
+import "quasar/dist/types/feature-flag.d.ts";
+
+declare module "quasar/dist/types/feature-flag.d.ts" {
+ interface QuasarFeatureFlags {
+ store: true;
+ }
+}
diff --git a/ui/src/stores/userPermissionStore.js b/ui/src/stores/userPermissionStore.js
new file mode 100644
index 0000000..eadaec0
--- /dev/null
+++ b/ui/src/stores/userPermissionStore.js
@@ -0,0 +1,31 @@
+import { defineStore } from 'pinia'
+import api from 'src/services/api'
+
+export const useUserPermissionStore = defineStore('userPerm', {
+
+ state: () => ({
+ rows: [],
+ loading: false,
+ saving: false
+ }),
+
+ actions: {
+
+ async fetch (id) {
+ this.loading = true
+
+ const res = await api.get(`/users/${id}/permissions`)
+ this.rows = res.data || []
+
+ this.loading = false
+ },
+
+ async save (id) {
+ this.saving = true
+
+ await api.post(`/users/${id}/permissions`, this.rows)
+
+ this.saving = false
+ }
+ }
+})
diff --git a/ui/src/stores/userSyncStore.js b/ui/src/stores/userSyncStore.js
new file mode 100644
index 0000000..c968651
--- /dev/null
+++ b/ui/src/stores/userSyncStore.js
@@ -0,0 +1,53 @@
+// src/stores/userSyncStore.js
+import { defineStore } from 'pinia'
+
+export const useUserSyncStore = defineStore('userSync', {
+ state: () => ({
+ pgUsers: [], // Postgre kullanıcıları
+ msUsers: [], // MSSQL kullanıcıları
+ loading: false,
+ selectedPg: null
+ }),
+
+ actions: {
+ // 🧩 Dummy veri ile başlat
+ async loadDummy() {
+ this.loading = true
+ await new Promise(r => setTimeout(r, 600)) // küçük gecikme
+ this.pgUsers = [
+ { id: 1, code: 'mehmetk', full_name: 'Mehmet Keçeci', email: 'm@b.com', mssql_username: 'MKECECI', sync_status: 'synced', is_active: true },
+ { id: 2, code: 'ayse', full_name: 'Ayşe Yılmaz', email: 'a@y.com', mssql_username: null, sync_status: 'pending', is_active: true },
+ { id: 3, code: 'ali', full_name: 'Ali Demir', email: 'a@d.com', mssql_username: 'ALI.D', sync_status: 'blocked', is_active: false }
+ ]
+ this.msUsers = [
+ { username: 'MKECECI', first_name: 'Mehmet', last_name: 'Keçeci', email: 'm@b.com', is_blocked: 0 },
+ { username: 'ALI.D', first_name: 'Ali', last_name: 'Demir', email: 'a@d.com', is_blocked: 1 },
+ { username: 'YENIUSR', first_name: 'Yeni', last_name: 'Kullanıcı', email: 'y@b.com', is_blocked: 0 }
+ ]
+ this.loading = false
+ },
+
+ async syncNow() {
+ this.loading = true
+ console.log('🔄 Dummy sync tetiklendi...')
+ await new Promise(r => setTimeout(r, 800))
+ this.loading = false
+ },
+
+ async map(pgId, msUsername) {
+ const user = this.pgUsers.find(u => u.id === pgId)
+ if (user) {
+ user.mssql_username = msUsername
+ user.sync_status = 'manual'
+ }
+ },
+
+ async unmap(pgId) {
+ const user = this.pgUsers.find(u => u.id === pgId)
+ if (user) {
+ user.mssql_username = null
+ user.sync_status = 'pending'
+ }
+ }
+ }
+})
diff --git a/ui/src/utils/flatten.js b/ui/src/utils/flatten.js
new file mode 100644
index 0000000..5d4c280
--- /dev/null
+++ b/ui/src/utils/flatten.js
@@ -0,0 +1,54 @@
+// src/utils/flatten.js
+
+/**
+ * 🔹 sql.Null* türlerini sadeleştirir.
+ * Backend'den gelen objelerde genellikle { String, Int32, Float64, Time, Bool, Valid } alanları vardır.
+ * Bu yardımcı, bunları otomatik olarak primitive değerlere dönüştürür.
+ * Örnek:
+ * { String: "ABC", Valid: true } → "ABC"
+ * { Float64: 10.5, Valid: true } → 10.5
+ * { Bool: false, Valid: true } → false
+ * { Valid: false } → null
+ */
+export function flat(value) {
+ if (value === null || value === undefined) return null
+
+ // Eğer sql.Null* objesi geldiyse
+ if (typeof value === 'object' && 'Valid' in value) {
+ if (!value.Valid) return null
+
+ // Farklı tiplerde gelen alanları sırayla dene
+ if ('String' in value) return value.String
+ if ('Float64' in value) return value.Float64
+ if ('Int32' in value) return value.Int32
+ if ('Int16' in value) return value.Int16
+ if ('Bool' in value) return value.Bool
+ if ('Time' in value) {
+ try {
+ // Tarih objesi veya ISO string olabilir
+ const t = value.Time
+ return typeof t === 'string' ? t : new Date(t).toISOString()
+ } catch {
+ return null
+ }
+ }
+ return null
+ }
+
+ // Düz değer (primitive)
+ return value
+}
+
+/**
+ * 🔹 Objeyi tamamen düzleştirir (tüm alanlarda flat uygular)
+ * Örnek:
+ * flattenObject({ A: { String: "x", Valid: true }, B: 5 })
+ * → { A: "x", B: 5 }
+ */
+export function flattenObject(obj = {}) {
+ const result = {}
+ for (const [key, val] of Object.entries(obj)) {
+ result[key] = flat(val)
+ }
+ return result
+}
diff --git a/ui/src/utils/formatters.js b/ui/src/utils/formatters.js
new file mode 100644
index 0000000..126bf76
--- /dev/null
+++ b/ui/src/utils/formatters.js
@@ -0,0 +1,86 @@
+/* ===========================================================
+ 📦 src/utils/formatters.js
+ Tarih, para, sayı, yüzde formatlama yardımcıları
+=========================================================== */
+
+/**
+ * 📅 formatDateInput(value)
+ * SQL veya ISO datetime'i "yyyy-MM-dd" formatına çevirir.
+ * için uygundur.
+ * Örnek: "2025-11-07 00:00:00" → "2025-11-07"
+ */
+export function formatDateInput(value) {
+ if (!value) return ''
+ try {
+ if (typeof value === 'string') {
+ // "2025-11-07 00:00:00" → "2025-11-07"
+ if (value.includes(' ')) return value.split(' ')[0]
+ // "2025-11-07T00:00:00Z" → "2025-11-07"
+ if (value.includes('T')) return value.split('T')[0]
+ // zaten yyyy-MM-dd ise aynen dön
+ if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return value
+ }
+ if (value instanceof Date) {
+ return value.toISOString().split('T')[0]
+ }
+ return ''
+ } catch {
+ return ''
+ }
+}
+
+/**
+ * 📅 formatDateDisplay(value)
+ * "2025-10-31" veya "2025-10-31 00:00:00" → "31.10.2025"
+ * Sadece ekranda gösterim için.
+ */
+export function formatDateDisplay(value) {
+ if (!value) return ''
+ try {
+ const d = new Date(value)
+ if (isNaN(d)) return ''
+ const dd = String(d.getDate()).padStart(2, '0')
+ const mm = String(d.getMonth() + 1).padStart(2, '0')
+ const yyyy = d.getFullYear()
+ return `${dd}.${mm}.${yyyy}`
+ } catch {
+ return ''
+ }
+}
+
+/**
+ * 💰 formatMoney(value, currency)
+ * Sayıyı belirtilen para birimiyle biçimlendirir.
+ * Varsayılan: TRY
+ * Örnek: formatMoney(12500, 'USD')
+ */
+export function formatMoney(value, currency = 'TRY') {
+ const n = Number(value || 0)
+ return new Intl.NumberFormat('tr-TR', {
+ style: 'currency',
+ currency,
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2,
+ }).format(n)
+}
+
+/**
+ * 🔢 formatNumber(value, fraction)
+ * Genel sayı biçimlendirme (küsuratlı veya küsuratsız)
+ */
+export function formatNumber(value, fraction = 2) {
+ const n = Number(value || 0)
+ return n.toLocaleString('tr-TR', {
+ minimumFractionDigits: fraction,
+ maximumFractionDigits: fraction,
+ })
+}
+
+/**
+ * 💬 formatPercent(value)
+ * 0.1 → "%10.00" şeklinde gösterir
+ */
+export function formatPercent(value) {
+ const n = Number(value || 0)
+ return `${(n * 100).toFixed(2)}%`
+}