Compare commits
149
Commits
8d1a59f41f
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db13574e9d | ||
|
|
ba9a744aef | ||
|
|
82ad3783b3 | ||
|
|
322bed753f | ||
|
|
bdc9459330 | ||
|
|
2d85a33623 | ||
|
|
5f1c37859b | ||
|
|
31a71ab566 | ||
|
|
25ef023df5 | ||
|
|
0c51729f7a | ||
|
|
20f5fea2e8 | ||
|
|
65e8aa2b62 | ||
|
|
1f8355b77c | ||
|
|
11048f1555 | ||
|
|
b83a3d22ce | ||
|
|
77af9ef533 | ||
|
|
4395d6bae2 | ||
|
|
6bc19e7a43 | ||
|
|
797d069e59 | ||
|
|
36e0ec9ff5 | ||
|
|
f14a96a024 | ||
|
|
f877b611b7 | ||
|
|
dbb3f3f380 | ||
|
|
03287d4850 | ||
|
|
12ce3a3eca | ||
|
|
52420bc5ff | ||
|
|
d5aa43ccb5 | ||
|
|
04461acb58 | ||
|
|
cd101811c4 | ||
|
|
c4028010c0 | ||
|
|
c2d010751f | ||
|
|
a9ab9fd9b0 | ||
|
|
1945fd4456 | ||
|
|
7b8f8905e5 | ||
|
|
89df3d2d3d | ||
|
|
474904481f | ||
|
|
6eaf0e3ed3 | ||
|
|
8249761415 | ||
|
|
dbd84f7399 | ||
|
|
9edc806345 | ||
|
|
7a7008cc1b | ||
|
|
3e0253f6be | ||
|
|
b80b5024f4 | ||
|
|
5eaf8be9e6 | ||
|
|
cc2930caf2 | ||
|
|
26b4a63c45 | ||
|
|
c6a3d1552e | ||
|
|
005d2eafac | ||
|
|
289f204752 | ||
|
|
1448dab9d7 | ||
|
|
5d4216d42e | ||
|
|
3993c363cb | ||
|
|
a0da3f2bed | ||
|
|
19a42e0551 | ||
|
|
53cdd1a4a1 | ||
|
|
fe29e763c5 | ||
|
|
9e1ecb9b3f | ||
|
|
fbdde54eee | ||
|
|
b2113a8d49 | ||
|
|
28e33d5e58 | ||
|
|
fbdc4934ff | ||
|
|
5b512fd30a | ||
|
|
7879172c54 | ||
|
|
c602e1e119 | ||
|
|
27934bccdf | ||
|
|
48bde09c2c | ||
|
|
480cad0d6f | ||
|
|
9f6e288d04 | ||
|
|
d0717900a6 | ||
|
|
25bc9b2d02 | ||
|
|
4c56062ef4 | ||
|
|
60b2f76537 | ||
|
|
81384426ff | ||
|
|
3697e35064 | ||
|
|
4ae6475875 | ||
|
|
5f006b8be0 | ||
|
|
38c168f7bd | ||
|
|
be125a6880 | ||
|
|
3b2bcda462 | ||
|
|
9e22cc5ec5 | ||
|
|
5e2a85b98e | ||
|
|
a45478d15f | ||
|
|
b133d43ebe | ||
|
|
9fee8c8ab2 | ||
|
|
95d72ec74b | ||
|
|
4cd1b118d0 | ||
|
|
2f885a4841 | ||
|
|
4aebb36ff2 | ||
|
|
fd086d668f | ||
|
|
5288f19976 | ||
|
|
60eef45769 | ||
|
|
a05e6bf1e2 | ||
|
|
94c83c3825 | ||
|
|
a0dd832d2b | ||
|
|
c2d27b2d77 | ||
|
|
9f45160648 | ||
|
|
c7ff2328fb | ||
|
|
e7a4c58934 | ||
|
|
5d5a38647d | ||
|
|
75e5136ab7 | ||
|
|
64105815a0 | ||
|
|
98e3587608 | ||
|
|
a0d06b5fcf | ||
|
|
be6feb8aa8 | ||
|
|
c536b85a4c | ||
|
|
d419f52e3c | ||
|
|
f3d4661cff | ||
|
|
f07ea2359d | ||
|
|
1ff7a03a81 | ||
|
|
05e192e30b | ||
|
|
c00ce200d7 | ||
|
|
b5098fb94f | ||
|
|
d4e853806a | ||
|
|
8d0f3c302a | ||
|
|
2fc9699ff1 | ||
|
|
593a7d4816 | ||
|
|
d98ce372c7 | ||
|
|
1bf51b9e82 | ||
|
|
cbb853d433 | ||
|
|
810c0e5eff | ||
|
|
dfad548963 | ||
|
|
52b39725ec | ||
|
|
3095c06cbf | ||
|
|
aa100973b3 | ||
|
|
701cc5e439 | ||
|
|
6b9cd6fe5a | ||
|
|
583af0230a | ||
|
|
be7ccdc466 | ||
|
|
e67fad9f90 | ||
|
|
215b7fd321 | ||
|
|
ec21dfac70 | ||
|
|
10fd77bece | ||
|
|
62bf272f17 | ||
|
|
aca2cf5d02 | ||
|
|
a56a0a8b21 | ||
|
|
a972565859 | ||
|
|
ccf34559c2 | ||
|
|
79ffe7862c | ||
|
|
01e457630c | ||
|
|
8866361a65 | ||
|
|
9526dfe645 | ||
|
|
b8296d2da9 | ||
|
|
feb7dc4f05 | ||
|
|
8b97e26d70 | ||
|
|
057f52a1f9 | ||
|
|
136c1e74fb | ||
|
|
f6cf6bb4cc | ||
|
|
750444b9c8 | ||
|
|
6b96e6b52b |
Generated
+1
@@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module type="WEB_MODULE" version="4">
|
<module type="WEB_MODULE" version="4">
|
||||||
<component name="Go" enabled="true" />
|
<component name="Go" enabled="true" />
|
||||||
|
<component name="GoModuleSettings" enabled="true" />
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$" />
|
<content url="file://$MODULE_DIR$" />
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
|||||||
@@ -33,6 +33,23 @@ PDF_FONT_DIR=D:\\baggitekstilas\\software projects\\bssapp\\bssapp\\svc\\fonts
|
|||||||
API_HOST=0.0.0.0
|
API_HOST=0.0.0.0
|
||||||
API_PORT=8080
|
API_PORT=8080
|
||||||
|
|
||||||
|
# ===============================
|
||||||
|
# PRODUCT PERFORMANCE CACHE
|
||||||
|
# ===============================
|
||||||
|
# MSSQL yogun kullanildigi icin uygulama acilisinda cache isi calismaz.
|
||||||
|
# Ilk full refresh 19:00 sonrasi manuel calistirilmali.
|
||||||
|
PRODUCT_PERFORMANCE_ENABLED=true
|
||||||
|
PRODUCT_PERFORMANCE_RUN_ON_STARTUP=false
|
||||||
|
PRODUCT_PERFORMANCE_DELTA_DAYS=45
|
||||||
|
PRODUCT_PERFORMANCE_DELTA_HHMM=02:00
|
||||||
|
PRODUCT_PERFORMANCE_DELTA_TIMEOUT_HOURS=6
|
||||||
|
PRODUCT_PERFORMANCE_FULL_ENABLED=true
|
||||||
|
PRODUCT_PERFORMANCE_FULL_WEEKDAY=saturday
|
||||||
|
PRODUCT_PERFORMANCE_FULL_HHMM=16:00
|
||||||
|
PRODUCT_PERFORMANCE_FULL_START_DATE=2022-01-01
|
||||||
|
PRODUCT_PERFORMANCE_FULL_TIMEOUT_HOURS=18
|
||||||
|
PRODUCT_PERFORMANCE_RESUME_ON_STARTUP=true
|
||||||
|
|
||||||
|
|
||||||
AZURE_TRANSLATOR_KEY=d055c693-a84e-4594-8aef-a6c05c42623a
|
AZURE_TRANSLATOR_KEY=d055c693-a84e-4594-8aef-a6c05c42623a
|
||||||
AZURE_TRANSLATOR_ENDPOINT=https://api.cognitive.microsofttranslator.com
|
AZURE_TRANSLATOR_ENDPOINT=https://api.cognitive.microsofttranslator.com
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ bssapp
|
|||||||
|
|
||||||
# Env
|
# Env
|
||||||
.env
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.argebaggi.local
|
||||||
mail.env
|
mail.env
|
||||||
|
|
||||||
# Runtime fonts
|
# Runtime fonts
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bssapp-backend/db"
|
||||||
|
"bssapp-backend/queries"
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
mode := flag.String("mode", "delta", "refresh mode: full or delta")
|
||||||
|
stage := flag.String("stage", "all", "start stage: sales, stock, price, kpi, all")
|
||||||
|
startRaw := flag.String("start", "", "start date YYYY-MM-DD")
|
||||||
|
endRaw := flag.String("end", "", "end date YYYY-MM-DD")
|
||||||
|
prefix := flag.String("prefix", "", "optional product code prefix")
|
||||||
|
mssqlTimeoutSec := flag.Int("mssql-timeout-sec", 0, "override MSSQL connection/dial timeout seconds")
|
||||||
|
skipDelete := flag.Bool("skip-delete", false, "skip deleting existing cache rows")
|
||||||
|
resumeAfter := flag.Int("resume-after", 0, "skip this many MSSQL rows before inserting")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
_ = godotenv.Load(".env", "mail.env", ".env.local")
|
||||||
|
|
||||||
|
if *mssqlTimeoutSec > 0 {
|
||||||
|
timeout := strconv.Itoa(*mssqlTimeoutSec)
|
||||||
|
_ = os.Setenv("MSSQL_CONNECTION_TIMEOUT_SEC", timeout)
|
||||||
|
_ = os.Setenv("MSSQL_DIAL_TIMEOUT_SEC", timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.ConnectMSSQL(); err != nil {
|
||||||
|
log.Fatalf("mssql connect failed: %v", err)
|
||||||
|
}
|
||||||
|
pgDB, err := db.ConnectPostgres()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("postgres connect failed: %v", err)
|
||||||
|
}
|
||||||
|
defer pgDB.Close()
|
||||||
|
|
||||||
|
startDate, err := parseDateFlag(*startRaw)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("invalid -start: %v", err)
|
||||||
|
}
|
||||||
|
endDate, err := parseDateFlag(*endRaw)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("invalid -end: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
result, err := queries.RefreshProductPerformance(ctx, pgDB, queries.ProductPerformanceRefreshRequest{
|
||||||
|
Mode: strings.TrimSpace(*mode),
|
||||||
|
Stage: strings.TrimSpace(*stage),
|
||||||
|
StartDate: startDate,
|
||||||
|
EndDate: endDate,
|
||||||
|
ProductPrefix: strings.TrimSpace(*prefix),
|
||||||
|
SkipDelete: *skipDelete,
|
||||||
|
ResumeAfter: *resumeAfter,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("refresh failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("product performance refresh done: sales=%d stock=%d kpi=%d snapshots=%d\n",
|
||||||
|
result.SalesRows,
|
||||||
|
result.StockRows,
|
||||||
|
result.KpiRows,
|
||||||
|
result.SnapshotRows,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDateFlag(raw string) (time.Time, error) {
|
||||||
|
raw = strings.TrimSpace(raw)
|
||||||
|
if raw == "" {
|
||||||
|
return time.Time{}, nil
|
||||||
|
}
|
||||||
|
return time.ParseInLocation("2006-01-02", raw, time.Local)
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
var MssqlDB *sql.DB
|
var MssqlDB *sql.DB
|
||||||
var UretimDB *sql.DB
|
var UretimDB *sql.DB
|
||||||
|
var ArgeBaggiDB *sql.DB
|
||||||
|
|
||||||
func envInt(name string, fallback int) int {
|
func envInt(name string, fallback int) int {
|
||||||
raw := strings.TrimSpace(os.Getenv(name))
|
raw := strings.TrimSpace(os.Getenv(name))
|
||||||
@@ -158,3 +159,41 @@ func ConnectMSSQLUretim() error {
|
|||||||
func GetUretimDB() *sql.DB {
|
func GetUretimDB() *sql.DB {
|
||||||
return UretimDB
|
return UretimDB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConnectMSSQLArgeBaggi initializes the optional ArgeBAGGI connection.
|
||||||
|
// Credentials must be supplied through ARGEBAGGI_MSSQL_CONN; never keep them
|
||||||
|
// in source control.
|
||||||
|
func ConnectMSSQLArgeBaggi() error {
|
||||||
|
connString := strings.TrimSpace(os.Getenv("ARGEBAGGI_MSSQL_CONN"))
|
||||||
|
if connString == "" {
|
||||||
|
return fmt.Errorf("ARGEBAGGI_MSSQL_CONN tanimli degil")
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionTimeoutSec := envInt("ARGEBAGGI_MSSQL_CONNECTION_TIMEOUT_SEC", 10)
|
||||||
|
dialTimeoutSec := envInt("ARGEBAGGI_MSSQL_DIAL_TIMEOUT_SEC", connectionTimeoutSec)
|
||||||
|
connString = ensureMSSQLTimeouts(connString, connectionTimeoutSec, dialTimeoutSec)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
ArgeBaggiDB, err = sql.Open("sqlserver", connString)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ArgeBAGGI MSSQL baglanti hatasi: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgeBaggiDB.SetMaxOpenConns(envInt("ARGEBAGGI_MSSQL_MAX_OPEN_CONNS", 10))
|
||||||
|
ArgeBaggiDB.SetMaxIdleConns(envInt("ARGEBAGGI_MSSQL_MAX_IDLE_CONNS", 5))
|
||||||
|
ArgeBaggiDB.SetConnMaxLifetime(time.Duration(envInt("ARGEBAGGI_MSSQL_CONN_MAX_LIFETIME_MIN", 30)) * time.Minute)
|
||||||
|
ArgeBaggiDB.SetConnMaxIdleTime(time.Duration(envInt("ARGEBAGGI_MSSQL_CONN_MAX_IDLE_MIN", 10)) * time.Minute)
|
||||||
|
|
||||||
|
if err = ArgeBaggiDB.Ping(); err != nil {
|
||||||
|
_ = ArgeBaggiDB.Close()
|
||||||
|
ArgeBaggiDB = nil
|
||||||
|
return fmt.Errorf("ArgeBAGGI MSSQL erisilemiyor: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("ArgeBAGGI MSSQL baglantisi basarili (connection timeout=%ds, dial timeout=%ds)\n", connectionTimeoutSec, dialTimeoutSec)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetArgeBaggiDB() *sql.DB {
|
||||||
|
return ArgeBaggiDB
|
||||||
|
}
|
||||||
|
|||||||
+38
-11
@@ -13,25 +13,29 @@ import (
|
|||||||
|
|
||||||
var PgDB *sql.DB
|
var PgDB *sql.DB
|
||||||
|
|
||||||
// ConnectPostgres PostgreSQL veritabanına bağlanır.
|
// ConnectPostgres PostgreSQL veritabanına bağlanır.
|
||||||
func ConnectPostgres() (*sql.DB, error) {
|
func ConnectPostgres() (*sql.DB, error) {
|
||||||
connStr := strings.TrimSpace(os.Getenv("POSTGRES_CONN"))
|
connStr := strings.TrimSpace(os.Getenv("POSTGRES_CONN"))
|
||||||
if connStr == "" {
|
if connStr == "" {
|
||||||
return nil, fmt.Errorf("POSTGRES_CONN tanımlı değil")
|
return nil, fmt.Errorf("POSTGRES_CONN tanımlı değil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make DB writes attributable in Postgres audit triggers via current_setting('application_name').
|
||||||
|
// Prefer setting it in POSTGRES_CONN, but default it here if missing.
|
||||||
|
connStr = ensurePGApplicationName(connStr, strings.TrimSpace(os.Getenv("POSTGRES_APPLICATION_NAME")))
|
||||||
|
|
||||||
db, err := sql.Open("postgres", connStr)
|
db, err := sql.Open("postgres", connStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("PostgreSQL bağlantı hatası: %w", err)
|
return nil, fmt.Errorf("PostgreSQL bağlantı hatası: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bağlantı havuzu ayarları (audit log uyumlu).
|
// Bağlantı havuzu ayarları (audit log uyumlu).
|
||||||
db.SetMaxOpenConns(30)
|
db.SetMaxOpenConns(30)
|
||||||
db.SetMaxIdleConns(10)
|
db.SetMaxIdleConns(10)
|
||||||
db.SetConnMaxLifetime(30 * time.Minute)
|
db.SetConnMaxLifetime(30 * time.Minute)
|
||||||
db.SetConnMaxIdleTime(5 * time.Minute)
|
db.SetConnMaxIdleTime(5 * time.Minute)
|
||||||
|
|
||||||
// Bağlantıyı test et.
|
// Bağlantıyı test et.
|
||||||
if err = db.Ping(); err != nil {
|
if err = db.Ping(); err != nil {
|
||||||
// Some managed PostgreSQL servers require TLS. If the current DSN uses
|
// Some managed PostgreSQL servers require TLS. If the current DSN uses
|
||||||
// sslmode=disable and server rejects with "no encryption", retry once
|
// sslmode=disable and server rejects with "no encryption", retry once
|
||||||
@@ -40,6 +44,7 @@ func ConnectPostgres() (*sql.DB, error) {
|
|||||||
strings.Contains(err.Error(), "no encryption") &&
|
strings.Contains(err.Error(), "no encryption") &&
|
||||||
strings.Contains(strings.ToLower(connStr), "sslmode=disable") {
|
strings.Contains(strings.ToLower(connStr), "sslmode=disable") {
|
||||||
secureConnStr := strings.Replace(connStr, "sslmode=disable", "sslmode=require", 1)
|
secureConnStr := strings.Replace(connStr, "sslmode=disable", "sslmode=require", 1)
|
||||||
|
secureConnStr = ensurePGApplicationName(secureConnStr, strings.TrimSpace(os.Getenv("POSTGRES_APPLICATION_NAME")))
|
||||||
log.Println("PostgreSQL TLS gerektiriyor, sslmode=require ile tekrar deneniyor")
|
log.Println("PostgreSQL TLS gerektiriyor, sslmode=require ile tekrar deneniyor")
|
||||||
|
|
||||||
_ = db.Close()
|
_ = db.Close()
|
||||||
@@ -54,28 +59,50 @@ func ConnectPostgres() (*sql.DB, error) {
|
|||||||
db.SetConnMaxIdleTime(5 * time.Minute)
|
db.SetConnMaxIdleTime(5 * time.Minute)
|
||||||
|
|
||||||
if err = db.Ping(); err != nil {
|
if err = db.Ping(); err != nil {
|
||||||
return nil, fmt.Errorf("PostgreSQL erişilemiyor (TLS retry): %w", err)
|
return nil, fmt.Errorf("PostgreSQL eriÅŸilemiyor (TLS retry): %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("PostgreSQL erişilemiyor: %w", err)
|
return nil, fmt.Errorf("PostgreSQL eriÅŸilemiyor: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("PostgreSQL bağlantısı başarılı")
|
log.Println("PostgreSQL bağlantısı başarılı")
|
||||||
PgDB = db
|
PgDB = db
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPostgresUsers test amaçlı ilk 5 kullanıcıyı listeler.
|
func ensurePGApplicationName(connStr string, appName string) string {
|
||||||
|
if strings.TrimSpace(appName) == "" {
|
||||||
|
appName = "bssapp-backend"
|
||||||
|
}
|
||||||
|
low := strings.ToLower(connStr)
|
||||||
|
if strings.Contains(low, "application_name=") {
|
||||||
|
return connStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL style DSN: postgres://...?... (lib/pq supports it)
|
||||||
|
if strings.HasPrefix(low, "postgres://") || strings.HasPrefix(low, "postgresql://") {
|
||||||
|
sep := "?"
|
||||||
|
if strings.Contains(connStr, "?") {
|
||||||
|
sep = "&"
|
||||||
|
}
|
||||||
|
return connStr + sep + "application_name=" + appName
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keyword DSN: "host=... user=... dbname=... sslmode=..."
|
||||||
|
return strings.TrimSpace(connStr) + " application_name=" + appName
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPostgresUsers test amaçlı ilk 5 kullanıcıyı listeler.
|
||||||
func GetPostgresUsers(db *sql.DB) error {
|
func GetPostgresUsers(db *sql.DB) error {
|
||||||
query := `SELECT id, code, email FROM mk_dfusr ORDER BY id LIMIT 5`
|
query := `SELECT id, code, email FROM mk_dfusr ORDER BY id LIMIT 5`
|
||||||
rows, err := db.Query(query)
|
rows, err := db.Query(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("PostgreSQL sorgu hatası: %w", err)
|
return fmt.Errorf("PostgreSQL sorgu hatası: %w", err)
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
fmt.Println("İlk 5 PostgreSQL kullanıcısı:")
|
fmt.Println("İlk 5 PostgreSQL kullanıcısı:")
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var id int
|
var id int
|
||||||
var code, email string
|
var code, email string
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
exit status 0xffffffff
|
||||||
@@ -0,0 +1,299 @@
|
|||||||
|
time=2026-06-29T15:41:11.450+03:00 level=INFO msg="backend start" app=bssapp-backend scope=main
|
||||||
|
time=2026-06-29T15:41:11.491+03:00 level=INFO msg="🔥🔥🔥 BSSAPP BACKEND STARTED — LOGIN ROUTE SHOULD EXIST 🔥🔥🔥" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:11.523+03:00 level=INFO msg="🔐 JWT_SECRET yüklendi" app=bssapp-backend
|
||||||
|
MSSQL baglantisi basarili (connection timeout=120s, dial timeout=120s)
|
||||||
|
URETIM MSSQL baglantisi basarili (connection timeout=120s, dial timeout=120s)
|
||||||
|
time=2026-06-29T15:41:11.988+03:00 level=INFO msg="PostgreSQL bağlantısı başarılı" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:12.338+03:00 level=INFO msg="✅ Admin dept permissions seeded" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:12.338+03:00 level=INFO msg="🟢 auditlog Init called, buffer: 1000" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:12.338+03:00 level=INFO msg="🕵️ AuditLog sistemi başlatıldı (buffer=1000)" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:12.338+03:00 level=INFO msg="🟢 auditlog worker STARTED" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:12.447+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE EXTENSION IF NOT EXISTS pg_trgm\"" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:12.553+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE INDEX IF NOT EXISTS idx_mk_translator_t_key_lang ON mk_translator (t_key, lang_code)\"" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:12.662+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE INDEX IF NOT EXISTS idx_mk_translator_status_lang_updated ON mk_translator (status, lang_code...\"" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:12.767+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE INDEX IF NOT EXISTS idx_mk_translator_manual_status ON mk_translator (is_manual, status)\"" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:12.871+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE INDEX IF NOT EXISTS idx_mk_translator_source_type_expr ON mk_translator ((COALESCE(NULLIF(pro...\"" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:12.973+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE INDEX IF NOT EXISTS idx_mk_translator_source_text_trgm ON mk_translator USING gin (source_tex...\"" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:13.083+03:00 level=INFO msg="[TranslationPerf] index_ready sql=\"CREATE INDEX IF NOT EXISTS idx_mk_translator_translated_text_trgm ON mk_translator USING gin (transl...\"" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:28.657+03:00 level=INFO msg="✉️ Graph Mailer hazır (App-only token) | from=baggiss@baggi.com.tr" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:28.657+03:00 level=INFO msg="✉️ Graph Mailer hazır" app=bssapp-backend
|
||||||
|
📋 [DEBUG] İlk 10 kullanıcı:
|
||||||
|
- 1 : ctengiz
|
||||||
|
- 2 : ali.kale
|
||||||
|
- 5 : mehmet.keçeci
|
||||||
|
- 6 : mert.keçeci
|
||||||
|
- 7 : samet.keçeci
|
||||||
|
- 9 : orhan.caliskan
|
||||||
|
- 10 : nilgun.sara
|
||||||
|
- 14 : rustem.kurbanov
|
||||||
|
- 15 : caner.akyol
|
||||||
|
- 16 : kemal.matyakupov
|
||||||
|
time=2026-06-29T15:41:29.664+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/auth/login [auth:login]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:30.592+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/auth/refresh [auth:refresh]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:31.591+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/password/forgot [auth:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:32.507+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/password/reset/validate/{token} [auth:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:33.468+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/password/reset [auth:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:34.431+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/password/change [auth:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:35.355+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/activity-logs [system:read]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:36.316+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/test-mail [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:37.229+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/system/market-mail-mappings/lookups [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:38.104+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/system/market-mail-mappings [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:39.006+03:00 level=INFO msg="✅ Route+Perm registered → PUT /api/system/market-mail-mappings/{marketId} [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:39.955+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/system/costing-mail-mappings/lookups [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:40.895+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/system/costing-mail-mappings [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:41.853+03:00 level=INFO msg="✅ Route+Perm registered → PUT /api/system/costing-mail-mappings/{group} [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:42.755+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/system/pricing-mail-mappings/lookups [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:43.666+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/system/pricing-mail-mappings [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:44.571+03:00 level=INFO msg="✅ Route+Perm registered → PUT /api/system/pricing-mail-mappings/{group} [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:45.491+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/system/order-price-list-mail-mappings/lookups [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:46.403+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/system/order-price-list-mail-mappings [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:47.355+03:00 level=INFO msg="✅ Route+Perm registered → PUT /api/system/order-price-list-mail-mappings/{group} [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:48.332+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/system/order-price-list-user-price-groups/lookups [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:49.253+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/system/order-price-list-user-price-groups [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:50.165+03:00 level=INFO msg="✅ Route+Perm registered → PUT /api/system/order-price-list-user-price-groups/{id} [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:51.073+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/language/translations [language:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:51.973+03:00 level=INFO msg="✅ Route+Perm registered → PUT /api/language/translations/{id} [language:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:52.967+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/language/translations/upsert-missing [language:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:53.910+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/language/translations/sync-sources [language:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:54.837+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/language/translations/translate-selected [language:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:55.808+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/language/translations/bulk-approve [language:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:56.725+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/language/translations/bulk-update [language:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:57.669+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/roles/{id}/permissions [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:58.664+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/roles/{id}/permissions [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:41:59.554+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/users/{id}/permissions [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:00.500+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/users/{id}/permissions [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:01.430+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/users/{id}/order-price-list-price-groups [user:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:02.375+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/users/order-price-list-price-groups/lookups [user:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:03.355+03:00 level=INFO msg="✅ Route+Perm registered → PUT /api/users/{id}/order-price-list-price-groups [user:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:04.295+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/permissions/routes [system:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:05.231+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/permissions/effective [system:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:06.173+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/permissions/matrix [system:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:07.109+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/role-dept-permissions/list [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:08.011+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/roles/{roleId}/departments/{deptCode}/permissions [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:08.934+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/roles/{roleId}/departments/{deptCode}/permissions [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:09.893+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/roles/{roleId}/departments/{deptCode}/members [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:10.775+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/roles/{roleId}/departments/{deptCode}/members [system:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:11.780+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/users/list [user:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:12.713+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/users [user:insert]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:13.664+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/users/{id} [user:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:14.607+03:00 level=INFO msg="✅ Route+Perm registered → PUT /api/users/{id} [user:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:15.547+03:00 level=INFO msg="✅ Route+Perm registered → DELETE /api/users/{id} [user:delete]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:16.518+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/users/{id}/admin-reset-password [user:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:17.416+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/users/{id}/send-password-mail [user:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:18.324+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/users/create [user:insert]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:19.277+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/departments [user:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:20.282+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/nebim-users [user:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:21.217+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/piyasalar [user:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:22.158+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/users-perm [user:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:23.136+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/roles-perm [user:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:24.117+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/departments-perm [user:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:25.118+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/modules [user:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:26.015+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/lookups/roles [user:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:26.927+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/accounts [customer:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:27.875+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/customer-list [customer:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:28.763+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/today-currency [finance:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:29.651+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/export-pdf [finance:export]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:30.569+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/export-pdf [finance:export]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:32.534+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/exportstamentheaderreport-pdf [finance:export]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:33.448+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/customer-balances [finance:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:34.348+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/customer-balances/export-pdf [finance:export]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:35.245+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/customer-balances/export-excel [finance:export]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:36.185+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/account-aging-statement [finance:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:37.086+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/account-aging-statement/export-pdf [finance:export]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:37.983+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/account-aging-statement/export-screen-pdf [finance:export]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:38.918+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/account-aging-statement/export-excel [finance:export]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:39.840+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/finance/aged-customer-balance-list [finance:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:40.767+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/statements [finance:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:41.670+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/statements/{id}/details [finance:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:42.592+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/order/create [order:insert]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:43.557+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/order/update [order:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:44.465+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/order/{id}/bulk-due-date [order:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:45.376+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/order/get/{id} [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:46.342+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orders/list [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:47.266+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orders/production-list [order:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:48.177+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orders/production-items/cditem-lookups [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:49.084+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orders/production-items/{id} [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:50.038+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/orders/production-items/{id}/insert-missing [order:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:50.995+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/orders/production-items/{id}/validate [order:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:51.949+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/orders/production-items/{id}/apply [order:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:52.868+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orders/close-ready [order:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:53.773+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/orders/bulk-close [order:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:54.664+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orders/export [order:export]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:55.565+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/order/check/{id} [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:56.469+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/order/validate [order:insert]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:57.370+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/order/pdf/{id} [order:export]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:58.297+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/order/send-market-mail [order:read]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:42:59.232+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/order-inventory [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:00.117+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/orderpricelistb2b [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:01.035+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/min-price [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:01.945+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/products [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:02.855+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-detail [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:03.830+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-cditem [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:04.818+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-colors [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:05.732+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-newcolors [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:06.688+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-colorsize [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:07.614+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-secondcolor [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:08.550+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-newsecondcolor [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:09.459+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-attributes [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:10.377+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-item-attributes [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:11.293+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-stock-query [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:12.205+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-stock-attribute-options [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:13.156+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-stock-query-by-attributes [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:14.084+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-images [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:15.031+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-images/{id}/content [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:15.892+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/order/price-list/products [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:16.795+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/order/price-list/options [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:17.755+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/order/price-list/my-price-groups [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:18.648+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/order/price-list/campaigns [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:19.564+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/order/price-list/variant-rows [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:20.499+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/order/price-list/export-excel [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:21.398+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/order/price-list/export-pdf [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:22.375+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/order/price-list/export-notify [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:23.291+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/product-size-match/rules [order:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:24.221+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/products [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:25.123+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/products/options [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:26.103+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/products/export-all [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:27.014+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/products/price-list/export-excel [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:27.922+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/products/price-list/export-pdf [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:28.826+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/products/calculate-snapshots [pricing:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:29.795+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/products/{code}/price-history [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:30.748+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/products/{code}/price-history/delete-latest [pricing:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:31.725+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/products/{code}/price-history/delete-selected [pricing:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:32.615+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/products/save [pricing:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:33.535+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/product-performance [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:34.467+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/product-performance/summary [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:35.370+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/product-performance/markets [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:36.287+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/product-performance/countries [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:37.181+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/product-performance/customers [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:39.332+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/product-performance/refresh [pricing:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:40.243+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/product-series/definitions [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:41.188+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/product-series/definitions [pricing:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:42.100+03:00 level=INFO msg="✅ Route+Perm registered → PUT /api/pricing/product-series/definitions/{id} [pricing:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:43.038+03:00 level=INFO msg="✅ Route+Perm registered → DELETE /api/pricing/product-series/definitions/{id} [pricing:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:43.957+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/product-series/mappings [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:44.899+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/product-series/mappings/orphans [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:45.835+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/product-series/mappings/save [pricing:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:46.738+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/wholesale-campaigns [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:47.659+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/wholesale-campaigns/assignments [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:48.603+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/wholesale-campaigns/variants [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:49.594+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/wholesale-campaigns/variant-rows [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:50.549+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/wholesale-campaigns/save [pricing:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:51.451+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/wholesale-campaigns/{code}/campaign-history [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:52.372+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/wholesale-campaigns/{code}/campaign-history/delete-selected [pricing:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:53.287+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/brand-classification/lookups [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:54.164+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/brand-classification/brands [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:55.120+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/brand-classification/brands/sync [pricing:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:56.023+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/brand-classification/brand/{code}/group [pricing:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:56.949+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/brand-classification/brands/group-bulk [pricing:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:57.868+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/brand-group-currency [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:58.833+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/brand-group-currency/bulk-save [pricing:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:43:59.763+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/pricing-rules [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:00.680+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/pricing-rules/bulk-save [pricing:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:01.570+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/pricing-rules/import [pricing:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:02.472+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/pricing-rules/options [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:03.423+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/pricing-rules/parameters [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:04.293+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/pricing-rules/export-all [pricing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:05.228+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/no-cost-products [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:06.168+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-products [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:07.108+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-history [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:08.025+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-detail-groups [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:08.893+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-detail-header [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:09.837+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/production-types [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:10.850+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/detail-editor-options [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:11.791+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-detail-exchange-rates [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:12.719+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-detail-line-history [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:13.671+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/has-cost-detail-similar-history [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:14.645+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/production-product-costing/has-cost-detail-bulk-prices [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:15.528+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/production-product-costing/has-cost-detail/last-detail [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:16.433+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/production-product-costing/options/hammadde-by-nos [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:17.368+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/production-product-costing/onml/save [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:18.358+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/onml/pdf [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:19.331+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/production-product-costing/onml/delete [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:20.246+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/default-quantities [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:21.151+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/production-product-costing/default-quantities/upsert [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:22.099+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/production-product-costing/default-quantities/update-bulk [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:23.019+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/production-product-costing/default-quantities/calc-avg [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:23.954+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/production-product-costing/default-quantities/lookup [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:24.948+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/production-product-costing/default-quantities/refresh [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:25.896+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/production-product-costing/tbstok/exists-bulk [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:26.851+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/last10-warnings [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:27.756+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/options/urun-ana-grup [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:28.694+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/options/urun-alt-grup [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:29.598+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/options/urun-ana-alt-combos [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:30.600+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/options/mtbolum [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:31.588+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/pricing/production-product-costing/maliyet-parca-eslestirme [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:32.613+03:00 level=INFO msg="✅ Route+Perm registered → DELETE /api/pricing/production-product-costing/maliyet-parca-eslestirme [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:33.550+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/production-product-costing/maliyet-parca-eslestirme/upsert [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:34.514+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/pricing/production-product-costing/maliyet-parca-eslestirme/set-active [costing:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:35.402+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/roles [user:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:36.301+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/departments [user:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:37.224+03:00 level=INFO msg="✅ Route+Perm registered → GET /api/piyasalar [user:view]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:38.110+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/roles/{id}/departments [user:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:39.073+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/roles/{id}/piyasalar [user:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:40.025+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/users/{id}/roles [user:update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:41.004+03:00 level=INFO msg="✅ Route+Perm registered → POST /api/admin/users/{id}/piyasa-sync [admin:user.update]" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:41.005+03:00 level=INFO msg="🌍 CORS Allowed Origin: http://ss.baggi.com.tr/app" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:41.006+03:00 level=INFO msg="🚀 Server running at: 0.0.0.0:8080" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:41.006+03:00 level=INFO msg="🕓 Translation sync next run at 2026-06-30T04:00:00+03:00 (in 12h15m19s)" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:43.006+03:00 level=INFO msg="[PricingFxFull] scheduled next_at=2026-07-06T06:00:00+03:00 in=158h15m17s" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:43.673+03:00 level=INFO msg="[PricingFxDelta] ok (startup): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:44:46.006+03:00 level=INFO msg="[ProductSeriesFull] scheduled next_at=2026-06-30T05:00:00+03:00 in=13h15m14s" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:45:16.284+03:00 level=INFO msg="✅ Brand sync ok (startup): total=197 upserted=197 deleted=0" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:45:23.771+03:00 level=INFO msg="Pricing parameter sync source loaded: rows=1003 duration=40.7644195s" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:45:24.388+03:00 level=INFO msg="Pricing parameter sync copy loaded: rows=1003 duration=41.3818724s" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:45:24.796+03:00 level=INFO msg="Pricing parameter sync committed: rows=0 duration=41.7900213s" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:45:24.797+03:00 level=INFO msg="Pricing parameter sync ok (startup): total=1003 upserted=0 deactivated=0" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:45:43.997+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:46:43.997+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:47:44.011+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:48:44.034+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:49:44.010+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:50:44.005+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:51:44.007+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:52:43.996+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:53:44.001+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:54:44.001+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:55:43.999+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:56:44.002+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:57:44.001+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:58:44.090+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T15:59:43.999+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:00:44.002+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:01:43.999+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:02:44.005+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:03:44.000+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:04:43.999+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:05:44.055+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:06:43.998+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:07:44.001+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:08:44.009+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:09:44.000+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:10:43.997+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:10:44.115+03:00 level=INFO msg="[ProductPricingCalcJob] ok (startup): requested=15178 calculated=8893 skipped=6285 fx_date=2026-06-29 snapshot_publish=false products=0 sdprc_written=0 publish_skipped=0 interval_hours=24 duration=25m58s" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:11:44.046+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:12:44.012+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:13:44.026+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:14:44.366+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:15:44.347+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:15:50.731+03:00 level=INFO msg="✅ Brand sync ok (scheduled): total=197 upserted=197 deleted=0" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:16:02.711+03:00 level=INFO msg="Pricing parameter sync source loaded: rows=1003 duration=37.9122008s" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:16:03.306+03:00 level=INFO msg="Pricing parameter sync copy loaded: rows=1003 duration=38.5077364s" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:16:03.679+03:00 level=INFO msg="Pricing parameter sync committed: rows=0 duration=38.8807898s" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:16:03.683+03:00 level=INFO msg="Pricing parameter sync ok (scheduled): total=1003 upserted=0 deactivated=0" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:16:44.025+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:17:44.018+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:18:44.028+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:19:44.107+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:20:44.026+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:21:44.024+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:22:44.204+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:23:44.029+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:24:44.044+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:25:44.029+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:26:44.028+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:27:44.011+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:28:44.033+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:29:44.037+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:30:44.022+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:31:44.002+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
|
time=2026-06-29T16:32:44.022+03:00 level=INFO msg="[PricingFxDelta] ok (scheduled): claimed=0 sdprc_written=0 interval_min=1 batch_size=200" app=bssapp-backend
|
||||||
+134
@@ -227,6 +227,7 @@ InitRoutes — FULL V3 (Method-aware) PERMISSION EDITION
|
|||||||
func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router {
|
func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router {
|
||||||
|
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
|
mountUploads(r)
|
||||||
mountSPA(r)
|
mountSPA(r)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -395,6 +396,10 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
|
|||||||
"language", "update",
|
"language", "update",
|
||||||
wrapV3(routes.GetTranslationRowsHandler(pgDB)),
|
wrapV3(routes.GetTranslationRowsHandler(pgDB)),
|
||||||
)
|
)
|
||||||
|
r.Handle(
|
||||||
|
"/api/language/translations/runtime",
|
||||||
|
http.HandlerFunc(routes.GetRuntimeTranslationsHandler(pgDB)),
|
||||||
|
).Methods("GET", "OPTIONS")
|
||||||
bindV3(r, pgDB,
|
bindV3(r, pgDB,
|
||||||
"/api/language/translations/{id}", "PUT",
|
"/api/language/translations/{id}", "PUT",
|
||||||
"language", "update",
|
"language", "update",
|
||||||
@@ -825,6 +830,11 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
|
|||||||
"order", "view",
|
"order", "view",
|
||||||
wrapV3(http.HandlerFunc(routes.GetProductImagesHandler(pgDB))),
|
wrapV3(http.HandlerFunc(routes.GetProductImagesHandler(pgDB))),
|
||||||
)
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/product-images/batch", "POST",
|
||||||
|
"order", "view",
|
||||||
|
wrapV3(http.HandlerFunc(routes.PostProductImagesBatchHandler(pgDB))),
|
||||||
|
)
|
||||||
bindV3(r, pgDB,
|
bindV3(r, pgDB,
|
||||||
"/api/product-images/{id}/content", "GET",
|
"/api/product-images/{id}/content", "GET",
|
||||||
"order", "view",
|
"order", "view",
|
||||||
@@ -925,6 +935,91 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
|
|||||||
"pricing", "update",
|
"pricing", "update",
|
||||||
wrapV3(routes.PostProductPricingSaveHandler(pgDB, ml)),
|
wrapV3(routes.PostProductPricingSaveHandler(pgDB, ml)),
|
||||||
)
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/pricing/product-performance", "GET",
|
||||||
|
"pricing", "view",
|
||||||
|
wrapV3(routes.GetProductPerformanceHandler(pgDB)),
|
||||||
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/pricing/product-performance/export-excel", "POST",
|
||||||
|
"pricing", "view",
|
||||||
|
wrapV3(routes.ExportProductPerformanceExcelHandler(pgDB)),
|
||||||
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/pricing/product-performance/summary", "GET",
|
||||||
|
"pricing", "view",
|
||||||
|
wrapV3(routes.GetProductPerformanceSummaryHandler(pgDB)),
|
||||||
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/pricing/product-performance/general", "GET",
|
||||||
|
"pricing", "view",
|
||||||
|
wrapV3(routes.GetProductPerformanceGeneralHandler(pgDB)),
|
||||||
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/pricing/product-performance/orders", "GET",
|
||||||
|
"pricing", "view",
|
||||||
|
wrapV3(routes.GetProductPerformanceOrdersHandler(pgDB)),
|
||||||
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/pricing/product-performance/orders/groups", "GET",
|
||||||
|
"pricing", "view",
|
||||||
|
wrapV3(routes.GetProductPerformanceOrderGroupsHandler(pgDB)),
|
||||||
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/pricing/product-performance/orders/product-customers", "GET",
|
||||||
|
"pricing", "view",
|
||||||
|
wrapV3(routes.GetProductPerformanceOrderProductCustomersHandler(pgDB)),
|
||||||
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/pricing/product-performance/orders/market-details", "GET",
|
||||||
|
"pricing", "view",
|
||||||
|
wrapV3(routes.GetProductPerformanceOrderMarketDetailsHandler(pgDB)),
|
||||||
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/pricing/product-performance/markets", "GET",
|
||||||
|
"pricing", "view",
|
||||||
|
wrapV3(routes.GetProductPerformanceMarketsHandler(pgDB)),
|
||||||
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/pricing/product-performance/countries", "GET",
|
||||||
|
"pricing", "view",
|
||||||
|
wrapV3(routes.GetProductPerformanceCountriesHandler(pgDB)),
|
||||||
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/pricing/product-performance/customers", "GET",
|
||||||
|
"pricing", "view",
|
||||||
|
wrapV3(routes.GetProductPerformanceCustomersHandler(pgDB)),
|
||||||
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/pricing/product-performance/sales-breakdown", "GET",
|
||||||
|
"pricing", "view",
|
||||||
|
wrapV3(routes.GetProductPerformanceSalesBreakdownHandler(pgDB)),
|
||||||
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/pricing/product-performance/grouped", "GET",
|
||||||
|
"pricing", "view",
|
||||||
|
wrapV3(routes.GetProductPerformanceGroupedHandler(pgDB)),
|
||||||
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/pricing/product-performance/grouped", "POST",
|
||||||
|
"pricing", "view",
|
||||||
|
wrapV3(routes.GetProductPerformanceGroupedHandler(pgDB)),
|
||||||
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/pricing/product-performance/grouped/filter-options", "POST",
|
||||||
|
"pricing", "view",
|
||||||
|
wrapV3(routes.GetProductPerformanceGroupedFilterOptionsHandler(pgDB)),
|
||||||
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/pricing/product-performance/sales-details", "GET",
|
||||||
|
"pricing", "view",
|
||||||
|
wrapV3(routes.GetProductPerformanceSalesDetailsHandler(pgDB)),
|
||||||
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/pricing/product-performance/stock-sizes", "GET",
|
||||||
|
"pricing", "view",
|
||||||
|
wrapV3(routes.GetProductPerformanceStockSizesHandler()),
|
||||||
|
)
|
||||||
bindV3(r, pgDB,
|
bindV3(r, pgDB,
|
||||||
"/api/pricing/product-series/definitions", "GET",
|
"/api/pricing/product-series/definitions", "GET",
|
||||||
"pricing", "view",
|
"pricing", "view",
|
||||||
@@ -950,6 +1045,11 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
|
|||||||
"pricing", "view",
|
"pricing", "view",
|
||||||
wrapV3(http.HandlerFunc(routes.GetProductSeriesMappingsHandler(pgDB))),
|
wrapV3(http.HandlerFunc(routes.GetProductSeriesMappingsHandler(pgDB))),
|
||||||
)
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/pricing/product-series/mappings/orphans", "GET",
|
||||||
|
"pricing", "view",
|
||||||
|
wrapV3(http.HandlerFunc(routes.GetProductSeriesOrphanMappingsHandler(pgDB))),
|
||||||
|
)
|
||||||
bindV3(r, pgDB,
|
bindV3(r, pgDB,
|
||||||
"/api/pricing/product-series/mappings/save", "POST",
|
"/api/pricing/product-series/mappings/save", "POST",
|
||||||
"pricing", "update",
|
"pricing", "update",
|
||||||
@@ -1290,6 +1390,7 @@ func main() {
|
|||||||
// (e.g. SSH-tunnel PostgreSQL on 127.0.0.1:15432).
|
// (e.g. SSH-tunnel PostgreSQL on 127.0.0.1:15432).
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
_ = godotenv.Overload(".env.local")
|
_ = godotenv.Overload(".env.local")
|
||||||
|
_ = godotenv.Overload(".env.argebaggi.local")
|
||||||
}
|
}
|
||||||
|
|
||||||
jwtSecret := os.Getenv("JWT_SECRET")
|
jwtSecret := os.Getenv("JWT_SECRET")
|
||||||
@@ -1317,6 +1418,15 @@ func main() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := db.ConnectMSSQLArgeBaggi(); err != nil {
|
||||||
|
if strings.Contains(err.Error(), "ARGEBAGGI_MSSQL_CONN tanimli degil") {
|
||||||
|
log.Println("ArgeBAGGI DB baglantisi atlandi: ARGEBAGGI_MSSQL_CONN tanimli degil")
|
||||||
|
} else {
|
||||||
|
// This source enriches one screen; a temporary outage must not stop
|
||||||
|
// the whole application from starting.
|
||||||
|
log.Println("ArgeBAGGI DB baglantisi basarisiz:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pgDB, err := db.ConnectPostgres()
|
pgDB, err := db.ConnectPostgres()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1363,6 +1473,9 @@ func main() {
|
|||||||
if err := queries.EnsureProductSeriesAutoInfraTables(pgDB); err != nil {
|
if err := queries.EnsureProductSeriesAutoInfraTables(pgDB); err != nil {
|
||||||
log.Println("product series auto infra bootstrap failed:", err)
|
log.Println("product series auto infra bootstrap failed:", err)
|
||||||
}
|
}
|
||||||
|
if err := queries.EnsureProductPerformanceTables(pgDB); err != nil {
|
||||||
|
log.Println("product performance infra bootstrap failed:", err)
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------
|
// -------------------------------------------------------
|
||||||
// ✉️ MAILER INIT
|
// ✉️ MAILER INIT
|
||||||
@@ -1389,6 +1502,7 @@ func main() {
|
|||||||
startProductPricingFxDeltaScheduler(pgDB)
|
startProductPricingFxDeltaScheduler(pgDB)
|
||||||
startProductPricingFxFullScheduler(pgDB)
|
startProductPricingFxFullScheduler(pgDB)
|
||||||
startProductSeriesAutoSchedulers(pgDB)
|
startProductSeriesAutoSchedulers(pgDB)
|
||||||
|
startProductPerformanceScheduler(pgDB)
|
||||||
|
|
||||||
handler := enableCORS(
|
handler := enableCORS(
|
||||||
middlewares.GlobalAuthMiddleware(
|
middlewares.GlobalAuthMiddleware(
|
||||||
@@ -1414,6 +1528,26 @@ func main() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mountUploads(r *mux.Router) {
|
||||||
|
root := strings.TrimSpace(os.Getenv("BLOB_ROOT"))
|
||||||
|
if root == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uploadsRoot := filepath.Join(root, "uploads")
|
||||||
|
if fi, err := os.Stat(uploadsRoot); err != nil || !fi.IsDir() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fileServer := http.StripPrefix("/uploads/", http.FileServer(http.Dir(uploadsRoot)))
|
||||||
|
r.PathPrefix("/uploads/").Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
||||||
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Cache-Control", "public, max-age=3600")
|
||||||
|
fileServer.ServeHTTP(w, r)
|
||||||
|
})).Methods(http.MethodGet, http.MethodHead, http.MethodOptions)
|
||||||
|
}
|
||||||
|
|
||||||
func mountSPA(r *mux.Router) {
|
func mountSPA(r *mux.Router) {
|
||||||
r.NotFoundHandler = http.HandlerFunc(spaIndex)
|
r.NotFoundHandler = http.HandlerFunc(spaIndex)
|
||||||
r.HandleFunc("/", spaIndex).Methods(http.MethodGet)
|
r.HandleFunc("/", spaIndex).Methods(http.MethodGet)
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ var routeMetaCache sync.Map
|
|||||||
|
|
||||||
var routeMetaFallback = map[string]routeMeta{
|
var routeMetaFallback = map[string]routeMeta{
|
||||||
"GET /api/product-images": {module: "order", action: "view"},
|
"GET /api/product-images": {module: "order", action: "view"},
|
||||||
|
"POST /api/product-images/batch": {module: "order", action: "view"},
|
||||||
"GET /api/product-images/{id}/content": {module: "order", action: "view"},
|
"GET /api/product-images/{id}/content": {module: "order", action: "view"},
|
||||||
"GET /api/product-stock-query-by-attributes": {module: "order", action: "view"},
|
"GET /api/product-stock-query-by-attributes": {module: "order", action: "view"},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ var publicPaths = []string{
|
|||||||
"/api/auth/refresh",
|
"/api/auth/refresh",
|
||||||
"/api/password/forgot",
|
"/api/password/forgot",
|
||||||
"/api/password/reset",
|
"/api/password/reset",
|
||||||
|
"/api/language/translations/runtime",
|
||||||
"/api/product-images/",
|
"/api/product-images/",
|
||||||
"/uploads/",
|
"/uploads/",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,441 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type ProductPerformanceRow struct {
|
||||||
|
KpiDate string `json:"kpi_date"`
|
||||||
|
ProductCode string `json:"product_code"`
|
||||||
|
ColorCode string `json:"color_code"`
|
||||||
|
ColorDescription string `json:"color_description"`
|
||||||
|
YakaKodu string `json:"yaka_kodu"`
|
||||||
|
ItemDescription string `json:"item_description"`
|
||||||
|
Kategori string `json:"kategori"`
|
||||||
|
Seri string `json:"seri"`
|
||||||
|
YasGrubu string `json:"yas_grubu"`
|
||||||
|
AskiliYan string `json:"askili_yan"`
|
||||||
|
UrunIlkGrubu string `json:"urun_ilk_grubu"`
|
||||||
|
UrunAnaGrubu string `json:"urun_ana_grubu"`
|
||||||
|
UrunAltGrubu string `json:"urun_alt_grubu"`
|
||||||
|
MarketKey string `json:"market_key"`
|
||||||
|
StockQty float64 `json:"stock_qty"`
|
||||||
|
SalesQty30 float64 `json:"sales_qty_30d"`
|
||||||
|
SalesQty90 float64 `json:"sales_qty_90d"`
|
||||||
|
SalesQty180 float64 `json:"sales_qty_180d"`
|
||||||
|
SalesQty365 float64 `json:"sales_qty_365d"`
|
||||||
|
SalesQty730 float64 `json:"sales_qty_730d"`
|
||||||
|
SalesQtyTotal float64 `json:"sales_qty_total"`
|
||||||
|
SalesUSD30 float64 `json:"sales_usd_30d"`
|
||||||
|
SalesUSD90 float64 `json:"sales_usd_90d"`
|
||||||
|
SalesUSD180 float64 `json:"sales_usd_180d"`
|
||||||
|
SalesUSD365 float64 `json:"sales_usd_365d"`
|
||||||
|
SalesUSDTotal float64 `json:"sales_usd_total"`
|
||||||
|
AvgDailySales90 float64 `json:"avg_daily_sales_90d"`
|
||||||
|
AvgDailySales180 float64 `json:"avg_daily_sales_180d"`
|
||||||
|
AvgDailySales365 float64 `json:"avg_daily_sales_365d"`
|
||||||
|
AvgDailySalesTotal float64 `json:"avg_daily_sales_total"`
|
||||||
|
AvgStock90 float64 `json:"avg_stock_90d"`
|
||||||
|
AvgStock180 float64 `json:"avg_stock_180d"`
|
||||||
|
AvgStock365 float64 `json:"avg_stock_365d"`
|
||||||
|
AvgStockTotal float64 `json:"avg_stock_total"`
|
||||||
|
StockDays90 float64 `json:"stock_days_90d"`
|
||||||
|
StockDays180 float64 `json:"stock_days_180d"`
|
||||||
|
StockDays365 float64 `json:"stock_days_365d"`
|
||||||
|
StockDaysTotal float64 `json:"stock_days_total"`
|
||||||
|
StockTurnover90 float64 `json:"stock_turnover_90d"`
|
||||||
|
StockTurnover180 float64 `json:"stock_turnover_180d"`
|
||||||
|
StockTurnover365 float64 `json:"stock_turnover_365d"`
|
||||||
|
StockTurnoverTotal float64 `json:"stock_turnover_total"`
|
||||||
|
AvgPriceUSD90 float64 `json:"avg_price_usd_90d"`
|
||||||
|
AvgPriceUSD180 float64 `json:"avg_price_usd_180d"`
|
||||||
|
CostPriceUSD float64 `json:"cost_price_usd"`
|
||||||
|
BasePriceUSD float64 `json:"base_price_usd"`
|
||||||
|
BasePriceTRY float64 `json:"base_price_try"`
|
||||||
|
GrossProfitUSD90 float64 `json:"gross_profit_usd_90d"`
|
||||||
|
GrossProfitUSD180 float64 `json:"gross_profit_usd_180d"`
|
||||||
|
GrossMargin90 float64 `json:"gross_margin_90d"`
|
||||||
|
GrossMargin180 float64 `json:"gross_margin_180d"`
|
||||||
|
UnitProfitCost90 float64 `json:"unit_profit_cost_90d"`
|
||||||
|
UnitProfitCost180 float64 `json:"unit_profit_cost_180d"`
|
||||||
|
UnitProfitBase90 float64 `json:"unit_profit_base_90d"`
|
||||||
|
UnitProfitBase180 float64 `json:"unit_profit_base_180d"`
|
||||||
|
MarketCount90 int `json:"market_count_90d"`
|
||||||
|
MarketCount180 int `json:"market_count_180d"`
|
||||||
|
MarketCount365 int `json:"market_count_365d"`
|
||||||
|
MarketCountTotal int `json:"market_count_total"`
|
||||||
|
CustomerCount90 int `json:"customer_count_90d"`
|
||||||
|
CustomerCount180 int `json:"customer_count_180d"`
|
||||||
|
CustomerCount365 int `json:"customer_count_365d"`
|
||||||
|
CustomerCountTotal int `json:"customer_count_total"`
|
||||||
|
SalesIndex90 float64 `json:"sales_index_90d"`
|
||||||
|
SalesIndex180 float64 `json:"sales_index_180d"`
|
||||||
|
SalesIndex365 float64 `json:"sales_index_365d"`
|
||||||
|
SalesIndexTotal float64 `json:"sales_index_total"`
|
||||||
|
PriceIndex90 float64 `json:"price_index_90d"`
|
||||||
|
MarginIndex90 float64 `json:"margin_index_90d"`
|
||||||
|
PerformanceScore90 float64 `json:"performance_score_90d"`
|
||||||
|
PerformanceScore180 float64 `json:"performance_score_180d"`
|
||||||
|
PerformanceScore365 float64 `json:"performance_score_365d"`
|
||||||
|
PerformanceScoreTotal float64 `json:"performance_score_total"`
|
||||||
|
PerformanceScore float64 `json:"performance_score"`
|
||||||
|
PerformanceBucket string `json:"performance_bucket"`
|
||||||
|
Recommendation string `json:"recommendation"`
|
||||||
|
LastSaleDate string `json:"last_sale_date"`
|
||||||
|
LastRefNumber string `json:"last_ref_number"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductPerformanceSummary struct {
|
||||||
|
KpiDate string `json:"kpi_date"`
|
||||||
|
TotalRows int `json:"total_rows"`
|
||||||
|
StarCount int `json:"star_count"`
|
||||||
|
StockRisk int `json:"stock_risk"`
|
||||||
|
NoStockDemand int `json:"no_stock_demand"`
|
||||||
|
TotalStock float64 `json:"total_stock"`
|
||||||
|
StockCostUSD float64 `json:"stock_cost_usd"`
|
||||||
|
RiskCostUSD float64 `json:"risk_cost_usd"`
|
||||||
|
SalesQty90 float64 `json:"sales_qty_90d"`
|
||||||
|
SalesUSD90 float64 `json:"sales_usd_90d"`
|
||||||
|
GrossProfit90 float64 `json:"gross_profit_usd_90d"`
|
||||||
|
MarketCount90 int `json:"market_count_90d"`
|
||||||
|
CustomerCount90 int `json:"customer_count_90d"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductPerformanceGeneralRow struct {
|
||||||
|
PeriodStart string `json:"period_start"`
|
||||||
|
PeriodEnd string `json:"period_end"`
|
||||||
|
ProductCode string `json:"product_code"`
|
||||||
|
ColorCode string `json:"color_code"`
|
||||||
|
ColorDescription string `json:"color_description"`
|
||||||
|
YakaKodu string `json:"yaka_kodu"`
|
||||||
|
ItemDescription string `json:"item_description"`
|
||||||
|
Kategori string `json:"kategori"`
|
||||||
|
Seri string `json:"seri"`
|
||||||
|
YasGrubu string `json:"yas_grubu"`
|
||||||
|
AskiliYan string `json:"askili_yan"`
|
||||||
|
UrunIlkGrubu string `json:"urun_ilk_grubu"`
|
||||||
|
UrunAnaGrubu string `json:"urun_ana_grubu"`
|
||||||
|
UrunAltGrubu string `json:"urun_alt_grubu"`
|
||||||
|
MarketKey string `json:"market_key"`
|
||||||
|
StockQty float64 `json:"stock_qty"`
|
||||||
|
SalesQtyTotal float64 `json:"sales_qty_total"`
|
||||||
|
SalesUSDTotal float64 `json:"sales_usd_total"`
|
||||||
|
AvgDailySalesTotal float64 `json:"avg_daily_sales_total"`
|
||||||
|
StockDaysTotal float64 `json:"stock_days_total"`
|
||||||
|
AvgPriceUSDTotal float64 `json:"avg_price_usd_total"`
|
||||||
|
CostPriceUSD float64 `json:"cost_price_usd"`
|
||||||
|
BasePriceUSD float64 `json:"base_price_usd"`
|
||||||
|
GrossProfitUSDTotal float64 `json:"gross_profit_usd_total"`
|
||||||
|
GrossMarginTotal float64 `json:"gross_margin_total"`
|
||||||
|
UnitProfitCostTotal float64 `json:"unit_profit_cost_total"`
|
||||||
|
UnitProfitBaseTotal float64 `json:"unit_profit_base_total"`
|
||||||
|
MarketCountTotal int `json:"market_count_total"`
|
||||||
|
CustomerCountTotal int `json:"customer_count_total"`
|
||||||
|
InvoiceCountTotal int `json:"invoice_count_total"`
|
||||||
|
SalesIndexTotal float64 `json:"sales_index_total"`
|
||||||
|
PerformanceScore float64 `json:"performance_score"`
|
||||||
|
PerformanceBucket string `json:"performance_bucket"`
|
||||||
|
Recommendation string `json:"recommendation"`
|
||||||
|
FirstSaleDate string `json:"first_sale_date"`
|
||||||
|
LastSaleDate string `json:"last_sale_date"`
|
||||||
|
LastRefNumber string `json:"last_ref_number"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductPerformanceOrderAnalysisRow struct {
|
||||||
|
ProductCode string `json:"product_code"`
|
||||||
|
ColorCode string `json:"color_code"`
|
||||||
|
ColorDescription string `json:"color_description"`
|
||||||
|
YakaKodu string `json:"yaka_kodu"`
|
||||||
|
ItemDescription string `json:"item_description"`
|
||||||
|
Kategori string `json:"kategori"`
|
||||||
|
Seri string `json:"seri"`
|
||||||
|
YasGrubu string `json:"yas_grubu"`
|
||||||
|
AskiliYan string `json:"askili_yan"`
|
||||||
|
UrunIlkGrubu string `json:"urun_ilk_grubu"`
|
||||||
|
UrunAnaGrubu string `json:"urun_ana_grubu"`
|
||||||
|
UrunAltGrubu string `json:"urun_alt_grubu"`
|
||||||
|
MarketKey string `json:"market_key"`
|
||||||
|
OrderQty float64 `json:"order_qty"`
|
||||||
|
OrderUSD float64 `json:"order_usd"`
|
||||||
|
AvgOrderPriceUSD float64 `json:"avg_order_price_usd"`
|
||||||
|
StockQty float64 `json:"stock_qty"`
|
||||||
|
NetStockAfterOrder float64 `json:"net_stock_after_order"`
|
||||||
|
CostPriceUSD float64 `json:"cost_price_usd"`
|
||||||
|
BasePriceUSD float64 `json:"base_price_usd"`
|
||||||
|
UnitProfitCostUSD float64 `json:"unit_profit_cost_usd"`
|
||||||
|
UnitProfitBaseUSD float64 `json:"unit_profit_base_usd"`
|
||||||
|
ExpectedProfitCostUSD float64 `json:"expected_profit_cost_usd"`
|
||||||
|
ExpectedProfitBaseUSD float64 `json:"expected_profit_base_usd"`
|
||||||
|
ExpectedMarginCost float64 `json:"expected_margin_cost"`
|
||||||
|
MarketCount int `json:"market_count"`
|
||||||
|
CustomerCount int `json:"customer_count"`
|
||||||
|
OrderCount int `json:"order_count"`
|
||||||
|
LineCount int `json:"line_count"`
|
||||||
|
FirstOrderDate string `json:"first_order_date"`
|
||||||
|
LastOrderDate string `json:"last_order_date"`
|
||||||
|
EarliestDueDate string `json:"earliest_due_date"`
|
||||||
|
OverdueQty float64 `json:"overdue_qty"`
|
||||||
|
PerformanceBucket string `json:"performance_bucket"`
|
||||||
|
Recommendation string `json:"recommendation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductPerformanceOrderGroupRow struct {
|
||||||
|
Breakdown string `json:"breakdown"`
|
||||||
|
GroupKey string `json:"group_key"`
|
||||||
|
MarketKey string `json:"market_key"`
|
||||||
|
CustomerCode string `json:"customer_code"`
|
||||||
|
CustomerName string `json:"customer_name"`
|
||||||
|
ProductCount int `json:"product_count"`
|
||||||
|
OrderQty float64 `json:"order_qty"`
|
||||||
|
OrderUSD float64 `json:"order_usd"`
|
||||||
|
AvgOrderPriceUSD float64 `json:"avg_order_price_usd"`
|
||||||
|
BaseCostUSD float64 `json:"base_cost_usd"`
|
||||||
|
CostAmountUSD float64 `json:"cost_amount_usd"`
|
||||||
|
ExpectedProfitBaseUSD float64 `json:"expected_profit_base_usd"`
|
||||||
|
ExpectedProfitCostUSD float64 `json:"expected_profit_cost_usd"`
|
||||||
|
ExpectedMarginBase float64 `json:"expected_margin_base"`
|
||||||
|
ExpectedMarginCost float64 `json:"expected_margin_cost"`
|
||||||
|
StockQty float64 `json:"stock_qty"`
|
||||||
|
NetStockAfterOrder float64 `json:"net_stock_after_order"`
|
||||||
|
MarketCount int `json:"market_count"`
|
||||||
|
CustomerCount int `json:"customer_count"`
|
||||||
|
OrderCount int `json:"order_count"`
|
||||||
|
LineCount int `json:"line_count"`
|
||||||
|
OverdueQty float64 `json:"overdue_qty"`
|
||||||
|
PerformanceBucket string `json:"performance_bucket"`
|
||||||
|
Recommendation string `json:"recommendation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductPerformanceOrderProductCustomerRow struct {
|
||||||
|
ProductCode string `json:"product_code"`
|
||||||
|
ColorCode string `json:"color_code"`
|
||||||
|
ColorDescription string `json:"color_description"`
|
||||||
|
YakaKodu string `json:"yaka_kodu"`
|
||||||
|
ItemDescription string `json:"item_description"`
|
||||||
|
Kategori string `json:"kategori"`
|
||||||
|
AskiliYan string `json:"askili_yan"`
|
||||||
|
UrunIlkGrubu string `json:"urun_ilk_grubu"`
|
||||||
|
UrunAnaGrubu string `json:"urun_ana_grubu"`
|
||||||
|
UrunAltGrubu string `json:"urun_alt_grubu"`
|
||||||
|
MarketKey string `json:"market_key"`
|
||||||
|
CustomerCode string `json:"customer_code"`
|
||||||
|
CustomerName string `json:"customer_name"`
|
||||||
|
OrderQty float64 `json:"order_qty"`
|
||||||
|
OrderUSD float64 `json:"order_usd"`
|
||||||
|
AvgOrderPriceUSD float64 `json:"avg_order_price_usd"`
|
||||||
|
StockQty float64 `json:"stock_qty"`
|
||||||
|
NetStockAfterOrder float64 `json:"net_stock_after_order"`
|
||||||
|
CostPriceUSD float64 `json:"cost_price_usd"`
|
||||||
|
BasePriceUSD float64 `json:"base_price_usd"`
|
||||||
|
ExpectedProfitBaseUSD float64 `json:"expected_profit_base_usd"`
|
||||||
|
ExpectedProfitCostUSD float64 `json:"expected_profit_cost_usd"`
|
||||||
|
ExpectedMarginBase float64 `json:"expected_margin_base"`
|
||||||
|
ExpectedMarginCost float64 `json:"expected_margin_cost"`
|
||||||
|
OrderCount int `json:"order_count"`
|
||||||
|
LineCount int `json:"line_count"`
|
||||||
|
OverdueQty float64 `json:"overdue_qty"`
|
||||||
|
PerformanceBucket string `json:"performance_bucket"`
|
||||||
|
Recommendation string `json:"recommendation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductPerformanceOrderMarketDetailRow struct {
|
||||||
|
MarketKey string `json:"market_key"`
|
||||||
|
CustomerCode string `json:"customer_code"`
|
||||||
|
CustomerName string `json:"customer_name"`
|
||||||
|
OrderNumber string `json:"order_number"`
|
||||||
|
OrderDate string `json:"order_date"`
|
||||||
|
DueDate string `json:"due_date"`
|
||||||
|
ProductCode string `json:"product_code"`
|
||||||
|
ColorCode string `json:"color_code"`
|
||||||
|
ColorDescription string `json:"color_description"`
|
||||||
|
YakaKodu string `json:"yaka_kodu"`
|
||||||
|
ItemDescription string `json:"item_description"`
|
||||||
|
Kategori string `json:"kategori"`
|
||||||
|
AskiliYan string `json:"askili_yan"`
|
||||||
|
UrunIlkGrubu string `json:"urun_ilk_grubu"`
|
||||||
|
UrunAnaGrubu string `json:"urun_ana_grubu"`
|
||||||
|
UrunAltGrubu string `json:"urun_alt_grubu"`
|
||||||
|
OrderQty float64 `json:"order_qty"`
|
||||||
|
OrderUSD float64 `json:"order_usd"`
|
||||||
|
AvgOrderPriceUSD float64 `json:"avg_order_price_usd"`
|
||||||
|
StockQty float64 `json:"stock_qty"`
|
||||||
|
NetStockAfterOrder float64 `json:"net_stock_after_order"`
|
||||||
|
CostPriceUSD float64 `json:"cost_price_usd"`
|
||||||
|
BasePriceUSD float64 `json:"base_price_usd"`
|
||||||
|
ExpectedProfitBaseUSD float64 `json:"expected_profit_base_usd"`
|
||||||
|
ExpectedProfitCostUSD float64 `json:"expected_profit_cost_usd"`
|
||||||
|
ExpectedMarginBase float64 `json:"expected_margin_base"`
|
||||||
|
ExpectedMarginCost float64 `json:"expected_margin_cost"`
|
||||||
|
IsOverdue bool `json:"is_overdue"`
|
||||||
|
PerformanceBucket string `json:"performance_bucket"`
|
||||||
|
Recommendation string `json:"recommendation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductPerformanceMarketRow struct {
|
||||||
|
MarketKey string `json:"market_key"`
|
||||||
|
Kategori string `json:"kategori"`
|
||||||
|
Seri string `json:"seri"`
|
||||||
|
YasGrubu string `json:"yas_grubu"`
|
||||||
|
AskiliYan string `json:"askili_yan"`
|
||||||
|
UrunIlkGrubu string `json:"urun_ilk_grubu"`
|
||||||
|
UrunAnaGrubu string `json:"urun_ana_grubu"`
|
||||||
|
UrunAltGrubu string `json:"urun_alt_grubu"`
|
||||||
|
ProductCount int `json:"product_count"`
|
||||||
|
StarCount int `json:"star_count"`
|
||||||
|
StockRiskCount int `json:"stock_risk_count"`
|
||||||
|
StockQty float64 `json:"stock_qty"`
|
||||||
|
StockCostValueUSD float64 `json:"stock_cost_value_usd"`
|
||||||
|
RiskStockCostValueUSD float64 `json:"risk_stock_cost_value_usd"`
|
||||||
|
SalesQty90 float64 `json:"sales_qty_90d"`
|
||||||
|
SalesUSD90 float64 `json:"sales_usd_90d"`
|
||||||
|
GrossProfitUSD90 float64 `json:"gross_profit_usd_90d"`
|
||||||
|
AvgGrossMargin90 float64 `json:"avg_gross_margin_90d"`
|
||||||
|
AvgStockDays90 float64 `json:"avg_stock_days_90d"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductPerformanceCountryRow struct {
|
||||||
|
Country string `json:"country"`
|
||||||
|
CustomerSegment string `json:"customer_segment"`
|
||||||
|
MarketKey string `json:"market_key"`
|
||||||
|
Kategori string `json:"kategori"`
|
||||||
|
Seri string `json:"seri"`
|
||||||
|
ProductCount int `json:"product_count"`
|
||||||
|
SalesQty90 float64 `json:"sales_qty_90d"`
|
||||||
|
SalesUSD90 float64 `json:"sales_usd_90d"`
|
||||||
|
AvgPriceUSD90 float64 `json:"avg_price_usd_90d"`
|
||||||
|
CustomerCount90 int `json:"customer_count_90d"`
|
||||||
|
InvoiceCount90 int `json:"invoice_count_90d"`
|
||||||
|
SalesQty365 float64 `json:"sales_qty_365d"`
|
||||||
|
SalesUSD365 float64 `json:"sales_usd_365d"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductPerformanceCustomerRow struct {
|
||||||
|
Breakdown string `json:"breakdown"`
|
||||||
|
MarketKey string `json:"market_key"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
CustomerSegment string `json:"customer_segment"`
|
||||||
|
CustomerCode string `json:"customer_code"`
|
||||||
|
CustomerName string `json:"customer_name"`
|
||||||
|
ProductCount int `json:"product_count"`
|
||||||
|
SalesQty90 float64 `json:"sales_qty_90d"`
|
||||||
|
SalesUSD90 float64 `json:"sales_usd_90d"`
|
||||||
|
AvgPriceUSD90 float64 `json:"avg_price_usd_90d"`
|
||||||
|
InvoiceCount90 int `json:"invoice_count_90d"`
|
||||||
|
SalesQty365 float64 `json:"sales_qty_365d"`
|
||||||
|
SalesUSD365 float64 `json:"sales_usd_365d"`
|
||||||
|
LastSaleDate string `json:"last_sale_date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductPerformanceSalesBreakdownRow struct {
|
||||||
|
Breakdown string `json:"breakdown"`
|
||||||
|
ProductCode string `json:"product_code"`
|
||||||
|
ColorCode string `json:"color_code"`
|
||||||
|
ColorDescription string `json:"color_description"`
|
||||||
|
YakaKodu string `json:"yaka_kodu"`
|
||||||
|
ItemDescription string `json:"item_description"`
|
||||||
|
Kategori string `json:"kategori"`
|
||||||
|
AskiliYan string `json:"askili_yan"`
|
||||||
|
UrunIlkGrubu string `json:"urun_ilk_grubu"`
|
||||||
|
UrunAnaGrubu string `json:"urun_ana_grubu"`
|
||||||
|
UrunAltGrubu string `json:"urun_alt_grubu"`
|
||||||
|
MarketKey string `json:"market_key"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
CustomerSegment string `json:"customer_segment"`
|
||||||
|
CustomerCode string `json:"customer_code"`
|
||||||
|
CustomerName string `json:"customer_name"`
|
||||||
|
ProductCount int `json:"product_count"`
|
||||||
|
ProductGroupCount90 int `json:"product_group_count_90d"`
|
||||||
|
ProductGroupCount180 int `json:"product_group_count_180d"`
|
||||||
|
ProductGroupCount365 int `json:"product_group_count_365d"`
|
||||||
|
ProductGroupCountTotal int `json:"product_group_count_total"`
|
||||||
|
MarketCount90 int `json:"market_count_90d"`
|
||||||
|
MarketCount180 int `json:"market_count_180d"`
|
||||||
|
MarketCount365 int `json:"market_count_365d"`
|
||||||
|
MarketCountTotal int `json:"market_count_total"`
|
||||||
|
CustomerCount90 int `json:"customer_count_90d"`
|
||||||
|
InvoiceCount90 int `json:"invoice_count_90d"`
|
||||||
|
SalesQty90 float64 `json:"sales_qty_90d"`
|
||||||
|
SalesUSD90 float64 `json:"sales_usd_90d"`
|
||||||
|
AvgPriceUSD90 float64 `json:"avg_price_usd_90d"`
|
||||||
|
BasePriceUSD90 float64 `json:"base_price_usd_90d"`
|
||||||
|
CostPriceUSD90 float64 `json:"cost_price_usd_90d"`
|
||||||
|
GrossProfitBase90 float64 `json:"gross_profit_base_usd_90d"`
|
||||||
|
GrossProfitCost90 float64 `json:"gross_profit_cost_usd_90d"`
|
||||||
|
GrossMarginBase90 float64 `json:"gross_margin_base_90d"`
|
||||||
|
GrossMarginCost90 float64 `json:"gross_margin_cost_90d"`
|
||||||
|
CustomerCount180 int `json:"customer_count_180d"`
|
||||||
|
InvoiceCount180 int `json:"invoice_count_180d"`
|
||||||
|
SalesQty180 float64 `json:"sales_qty_180d"`
|
||||||
|
SalesUSD180 float64 `json:"sales_usd_180d"`
|
||||||
|
AvgPriceUSD180 float64 `json:"avg_price_usd_180d"`
|
||||||
|
BasePriceUSD180 float64 `json:"base_price_usd_180d"`
|
||||||
|
CostPriceUSD180 float64 `json:"cost_price_usd_180d"`
|
||||||
|
GrossProfitBase180 float64 `json:"gross_profit_base_usd_180d"`
|
||||||
|
GrossProfitCost180 float64 `json:"gross_profit_cost_usd_180d"`
|
||||||
|
GrossMarginBase180 float64 `json:"gross_margin_base_180d"`
|
||||||
|
GrossMarginCost180 float64 `json:"gross_margin_cost_180d"`
|
||||||
|
SalesQty365 float64 `json:"sales_qty_365d"`
|
||||||
|
SalesUSD365 float64 `json:"sales_usd_365d"`
|
||||||
|
AvgPriceUSD365 float64 `json:"avg_price_usd_365d"`
|
||||||
|
BasePriceUSD365 float64 `json:"base_price_usd_365d"`
|
||||||
|
CostPriceUSD365 float64 `json:"cost_price_usd_365d"`
|
||||||
|
GrossProfitBase365 float64 `json:"gross_profit_base_usd_365d"`
|
||||||
|
GrossProfitCost365 float64 `json:"gross_profit_cost_usd_365d"`
|
||||||
|
GrossMarginBase365 float64 `json:"gross_margin_base_365d"`
|
||||||
|
GrossMarginCost365 float64 `json:"gross_margin_cost_365d"`
|
||||||
|
CustomerCount365 int `json:"customer_count_365d"`
|
||||||
|
InvoiceCount365 int `json:"invoice_count_365d"`
|
||||||
|
CustomerCountTotal int `json:"customer_count_total"`
|
||||||
|
InvoiceCountTotal int `json:"invoice_count_total"`
|
||||||
|
SalesQtyTotal float64 `json:"sales_qty_total"`
|
||||||
|
SalesUSDTotal float64 `json:"sales_usd_total"`
|
||||||
|
AvgPriceUSDTotal float64 `json:"avg_price_usd_total"`
|
||||||
|
BasePriceUSDTotal float64 `json:"base_price_usd_total"`
|
||||||
|
CostPriceUSDTotal float64 `json:"cost_price_usd_total"`
|
||||||
|
GrossProfitBaseTotal float64 `json:"gross_profit_base_usd_total"`
|
||||||
|
GrossProfitCostTotal float64 `json:"gross_profit_cost_usd_total"`
|
||||||
|
GrossMarginBaseTotal float64 `json:"gross_margin_base_total"`
|
||||||
|
GrossMarginCostTotal float64 `json:"gross_margin_cost_total"`
|
||||||
|
StockQty float64 `json:"stock_qty"`
|
||||||
|
AvgStock90 float64 `json:"avg_stock_90d"`
|
||||||
|
AvgStock180 float64 `json:"avg_stock_180d"`
|
||||||
|
AvgStock365 float64 `json:"avg_stock_365d"`
|
||||||
|
AvgStockTotal float64 `json:"avg_stock_total"`
|
||||||
|
StockTurnover90 float64 `json:"stock_turnover_90d"`
|
||||||
|
StockTurnover180 float64 `json:"stock_turnover_180d"`
|
||||||
|
StockTurnover365 float64 `json:"stock_turnover_365d"`
|
||||||
|
StockTurnoverTotal float64 `json:"stock_turnover_total"`
|
||||||
|
HasCost bool `json:"has_cost"`
|
||||||
|
SalesIndex90 float64 `json:"sales_index_90d"`
|
||||||
|
SalesIndex180 float64 `json:"sales_index_180d"`
|
||||||
|
SalesIndex365 float64 `json:"sales_index_365d"`
|
||||||
|
SalesIndexTotal float64 `json:"sales_index_total"`
|
||||||
|
CustomerScore90 float64 `json:"customer_score_90d"`
|
||||||
|
CustomerScore180 float64 `json:"customer_score_180d"`
|
||||||
|
CustomerScore365 float64 `json:"customer_score_365d"`
|
||||||
|
CustomerScoreTotal float64 `json:"customer_score_total"`
|
||||||
|
PerformanceScore90 float64 `json:"performance_score_90d"`
|
||||||
|
PerformanceScore180 float64 `json:"performance_score_180d"`
|
||||||
|
PerformanceScore365 float64 `json:"performance_score_365d"`
|
||||||
|
PerformanceScoreTotal float64 `json:"performance_score_total"`
|
||||||
|
PerformanceScore float64 `json:"performance_score"`
|
||||||
|
PerformanceBucket string `json:"performance_bucket"`
|
||||||
|
Recommendation string `json:"recommendation"`
|
||||||
|
LastSaleDate string `json:"last_sale_date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductPerformanceSalesDetailRow struct {
|
||||||
|
SalesDate string `json:"sales_date"`
|
||||||
|
RefNumber string `json:"ref_number"`
|
||||||
|
MarketKey string `json:"market_key"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
CustomerSegment string `json:"customer_segment"`
|
||||||
|
CustomerCode string `json:"customer_code"`
|
||||||
|
CustomerName string `json:"customer_name"`
|
||||||
|
SalesQty float64 `json:"sales_qty"`
|
||||||
|
SalesUSD float64 `json:"sales_usd"`
|
||||||
|
AvgPriceUSD float64 `json:"avg_price_usd"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductPerformanceStockSizeRow struct {
|
||||||
|
SizeCode string `json:"size_code"`
|
||||||
|
StockQty float64 `json:"stock_qty"`
|
||||||
|
}
|
||||||
@@ -1,22 +1,29 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
type ProductionNoCostProductRow struct {
|
type ProductionNoCostProductRow struct {
|
||||||
UretimSekli string `json:"UretimSekli"`
|
UretimSekli string `json:"UretimSekli"`
|
||||||
UrtSiparisNo string `json:"nUrtSiparisNo"`
|
UrtSiparisNo string `json:"nUrtSiparisNo"`
|
||||||
IslemTarihi string `json:"dteIslemTarihi"`
|
IslemTarihi string `json:"dteIslemTarihi"`
|
||||||
FirmaKodu string `json:"FirmaKodu"`
|
FirmaKodu string `json:"FirmaKodu"`
|
||||||
FirmaAdi string `json:"FirmaAdi"`
|
FirmaAdi string `json:"FirmaAdi"`
|
||||||
SonIsEmriVeren string `json:"SonIsEmriVeren"`
|
SonIsEmriVeren string `json:"SonIsEmriVeren"`
|
||||||
ModelAdi string `json:"sAdi"`
|
ModelAdi string `json:"sAdi"`
|
||||||
Kodu string `json:"sKodu"`
|
Kodu string `json:"sKodu"`
|
||||||
SKullaniciAdi string `json:"sKullaniciAdi"`
|
SKullaniciAdi string `json:"sKullaniciAdi"`
|
||||||
SKullaniciGunc string `json:"sKullaniciAdiGunc"`
|
SKullaniciGunc string `json:"sKullaniciAdiGunc"`
|
||||||
MMiktarG float64 `json:"lMMiktar_G"`
|
MMiktarG float64 `json:"lMMiktar_G"`
|
||||||
MModelKodu string `json:"sMModelKodu"`
|
CeketDepoGiris float64 `json:"ceketDepoGiris"`
|
||||||
|
PantolonDepoGiris float64 `json:"pantolonDepoGiris"`
|
||||||
|
YelekDepoGiris float64 `json:"yelekDepoGiris"`
|
||||||
|
MModelKodu string `json:"sMModelKodu"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductionHasCostProductRow struct {
|
type ProductionHasCostProductRow struct {
|
||||||
UretimSekli string `json:"UretimSekli"`
|
UretimSekli string `json:"UretimSekli"`
|
||||||
|
UrtSiparisNo string `json:"nUrtSiparisNo"`
|
||||||
|
CeketDepoGiris float64 `json:"ceketDepoGiris"`
|
||||||
|
PantolonDepoGiris float64 `json:"pantolonDepoGiris"`
|
||||||
|
YelekDepoGiris float64 `json:"yelekDepoGiris"`
|
||||||
NOnMLNo string `json:"nOnMLNo"`
|
NOnMLNo string `json:"nOnMLNo"`
|
||||||
UrunKodu string `json:"UrunKodu"`
|
UrunKodu string `json:"UrunKodu"`
|
||||||
UrunAdi string `json:"UrunAdi"`
|
UrunAdi string `json:"UrunAdi"`
|
||||||
|
|||||||
@@ -0,0 +1,214 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bssapp-backend/queries"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func startProductPerformanceScheduler(pgDB *sql.DB) {
|
||||||
|
enabled := strings.TrimSpace(strings.ToLower(os.Getenv("PRODUCT_PERFORMANCE_ENABLED")))
|
||||||
|
if enabled == "0" || enabled == "false" || enabled == "off" {
|
||||||
|
log.Println("Product performance scheduler disabled")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if pgDB == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
deltaDays := productPerformanceEnvInt("PRODUCT_PERFORMANCE_DELTA_DAYS", 45, 1, 365)
|
||||||
|
deltaHHMM := productPerformanceEnvString("PRODUCT_PERFORMANCE_DELTA_HHMM", "02:00")
|
||||||
|
deltaTimeoutHours := productPerformanceEnvInt("PRODUCT_PERFORMANCE_DELTA_TIMEOUT_HOURS", 6, 1, 24)
|
||||||
|
runOnStartup := productPerformanceEnvBool("PRODUCT_PERFORMANCE_DELTA_RUN_ON_STARTUP", false)
|
||||||
|
fullEnabled := productPerformanceEnvBool("PRODUCT_PERFORMANCE_FULL_ENABLED", false)
|
||||||
|
fullWeekday := productPerformanceEnvWeekday("PRODUCT_PERFORMANCE_FULL_WEEKDAY", time.Saturday)
|
||||||
|
fullHHMM := productPerformanceEnvString("PRODUCT_PERFORMANCE_FULL_HHMM", "16:00")
|
||||||
|
fullStartDate := productPerformanceEnvDate("PRODUCT_PERFORMANCE_FULL_START_DATE", time.Date(2022, 1, 1, 0, 0, 0, 0, time.Local))
|
||||||
|
fullTimeoutHours := productPerformanceEnvInt("PRODUCT_PERFORMANCE_FULL_TIMEOUT_HOURS", 18, 1, 72)
|
||||||
|
fullRunOnStartup := productPerformanceEnvBool("PRODUCT_PERFORMANCE_FULL_RUN_ON_STARTUP", false)
|
||||||
|
|
||||||
|
var running int32
|
||||||
|
runDelta := func(reason string) {
|
||||||
|
if !atomic.CompareAndSwapInt32(&running, 0, 1) {
|
||||||
|
log.Printf("[ProductPerformanceJob] skip (%s): already running", reason)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer atomic.StoreInt32(&running, 0)
|
||||||
|
|
||||||
|
endDate := time.Now()
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(deltaTimeoutHours)*time.Hour)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
result, err := queries.RefreshProductPerformance(ctx, pgDB, queries.ProductPerformanceRefreshRequest{
|
||||||
|
Mode: "delta",
|
||||||
|
Stage: "all",
|
||||||
|
StartDate: endDate.AddDate(0, 0, -deltaDays),
|
||||||
|
EndDate: endDate,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ProductPerformanceJob] error (%s): %v", reason, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("[ProductPerformanceJob] ok (%s): mode=%s stage=%s start=%s end=%s sales=%d stock=%d kpi=%d snapshots=%d duration_ms=%d",
|
||||||
|
reason, result.Mode, result.Stage, result.StartDate, result.EndDate, result.SalesRows, result.StockRows, result.KpiRows, result.SnapshotRows, result.DurationMS)
|
||||||
|
}
|
||||||
|
runFull := func(reason string) {
|
||||||
|
if !atomic.CompareAndSwapInt32(&running, 0, 1) {
|
||||||
|
log.Printf("[ProductPerformanceJob] skip (%s): already running", reason)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer atomic.StoreInt32(&running, 0)
|
||||||
|
|
||||||
|
endDate := time.Now()
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(fullTimeoutHours)*time.Hour)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
result, err := queries.RefreshProductPerformance(ctx, pgDB, queries.ProductPerformanceRefreshRequest{
|
||||||
|
Mode: "full",
|
||||||
|
Stage: "all",
|
||||||
|
StartDate: fullStartDate,
|
||||||
|
EndDate: endDate,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ProductPerformanceJob] error (%s): %v", reason, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("[ProductPerformanceJob] ok (%s): mode=%s stage=%s start=%s end=%s sales=%d stock=%d kpi=%d snapshots=%d duration_ms=%d",
|
||||||
|
reason, result.Mode, result.Stage, result.StartDate, result.EndDate, result.SalesRows, result.StockRows, result.KpiRows, result.SnapshotRows, result.DurationMS)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[ProductPerformanceJob] scheduled daily_delta=%s lookback_days=%d run_delta_on_startup=%t",
|
||||||
|
deltaHHMM, deltaDays, runOnStartup)
|
||||||
|
if fullEnabled {
|
||||||
|
log.Printf("[ProductPerformanceJob] scheduled weekly_full=%s %s start_date=%s timeout_hours=%d run_full_on_startup=%t",
|
||||||
|
fullWeekday.String(), fullHHMM, fullStartDate.Format("2006-01-02"), fullTimeoutHours, fullRunOnStartup)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if runOnStartup {
|
||||||
|
time.Sleep(20 * time.Second)
|
||||||
|
runDelta("startup-delta")
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
next := productPerformanceNextDaily(time.Now(), deltaHHMM)
|
||||||
|
log.Printf("[ProductPerformanceJob] daily delta next_at=%s in=%s", next.Format(time.RFC3339), time.Until(next).Round(time.Second))
|
||||||
|
time.Sleep(time.Until(next))
|
||||||
|
runDelta("daily-delta")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if fullEnabled {
|
||||||
|
go func() {
|
||||||
|
if fullRunOnStartup {
|
||||||
|
time.Sleep(30 * time.Second)
|
||||||
|
runFull("startup-full")
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
next := productPerformanceNextWeekly(time.Now(), fullWeekday, fullHHMM)
|
||||||
|
log.Printf("[ProductPerformanceJob] weekly full next_at=%s in=%s", next.Format(time.RFC3339), time.Until(next).Round(time.Second))
|
||||||
|
time.Sleep(time.Until(next))
|
||||||
|
runFull("weekly-full")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceNextDaily(now time.Time, hhmm string) time.Time {
|
||||||
|
hour, minute := productPerformanceParseHHMM(hhmm, 2, 0)
|
||||||
|
next := time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, now.Location())
|
||||||
|
if !next.After(now) {
|
||||||
|
next = next.AddDate(0, 0, 1)
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceNextWeekly(now time.Time, weekday time.Weekday, hhmm string) time.Time {
|
||||||
|
hour, minute := productPerformanceParseHHMM(hhmm, 16, 0)
|
||||||
|
daysUntil := (int(weekday) - int(now.Weekday()) + 7) % 7
|
||||||
|
next := time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, now.Location()).AddDate(0, 0, daysUntil)
|
||||||
|
if !next.After(now) {
|
||||||
|
next = next.AddDate(0, 0, 7)
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceParseHHMM(raw string, fallbackHour, fallbackMinute int) (int, int) {
|
||||||
|
parts := strings.Split(strings.TrimSpace(raw), ":")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return fallbackHour, fallbackMinute
|
||||||
|
}
|
||||||
|
h, herr := strconv.Atoi(parts[0])
|
||||||
|
m, merr := strconv.Atoi(parts[1])
|
||||||
|
if herr != nil || merr != nil || h < 0 || h > 23 || m < 0 || m > 59 {
|
||||||
|
return fallbackHour, fallbackMinute
|
||||||
|
}
|
||||||
|
return h, m
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceEnvString(name, fallback string) string {
|
||||||
|
raw := strings.TrimSpace(os.Getenv(name))
|
||||||
|
if raw == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceEnvInt(name string, fallback, min, max int) int {
|
||||||
|
raw := strings.TrimSpace(os.Getenv(name))
|
||||||
|
if raw == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
n, err := strconv.Atoi(raw)
|
||||||
|
if err != nil || n < min || n > max {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceEnvBool(name string, fallback bool) bool {
|
||||||
|
raw := strings.TrimSpace(strings.ToLower(os.Getenv(name)))
|
||||||
|
if raw == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return raw == "1" || raw == "true" || raw == "on" || raw == "yes"
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceEnvDate(name string, fallback time.Time) time.Time {
|
||||||
|
raw := strings.TrimSpace(os.Getenv(name))
|
||||||
|
if raw == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
parsed, err := time.ParseInLocation("2006-01-02", raw, time.Local)
|
||||||
|
if err != nil {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceEnvWeekday(name string, fallback time.Weekday) time.Weekday {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(os.Getenv(name))) {
|
||||||
|
case "sunday", "sun", "pazar", "0":
|
||||||
|
return time.Sunday
|
||||||
|
case "monday", "mon", "pazartesi", "1":
|
||||||
|
return time.Monday
|
||||||
|
case "tuesday", "tue", "sali", "salı", "2":
|
||||||
|
return time.Tuesday
|
||||||
|
case "wednesday", "wed", "carsamba", "çarşamba", "3":
|
||||||
|
return time.Wednesday
|
||||||
|
case "thursday", "thu", "persembe", "perşembe", "4":
|
||||||
|
return time.Thursday
|
||||||
|
case "friday", "fri", "cuma", "5":
|
||||||
|
return time.Friday
|
||||||
|
case "saturday", "sat", "cumartesi", "6":
|
||||||
|
return time.Saturday
|
||||||
|
default:
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,10 +101,60 @@ func productSeriesFallbackAutoCreateEnabled() bool {
|
|||||||
return raw == "1" || raw == "true" || raw == "on" || raw == "yes"
|
return raw == "1" || raw == "true" || raw == "on" || raw == "yes"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func productSeriesFallbackLogEnabled() bool {
|
||||||
|
raw := strings.TrimSpace(strings.ToLower(os.Getenv("PRODUCT_SERIES_FALLBACK_LOG")))
|
||||||
|
if raw == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return raw == "1" || raw == "true" || raw == "on" || raw == "yes"
|
||||||
|
}
|
||||||
|
|
||||||
|
func productSeriesFallbackOverrideExisting() bool {
|
||||||
|
// Default off: do not override existing (possibly manual) assignments.
|
||||||
|
raw := strings.TrimSpace(strings.ToLower(os.Getenv("PRODUCT_SERIES_FALLBACK_OVERRIDE_EXISTING")))
|
||||||
|
if raw == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return raw == "1" || raw == "true" || raw == "on" || raw == "yes"
|
||||||
|
}
|
||||||
|
|
||||||
|
func productSeriesDebugMatch(v productSeriesAutoVariant) bool {
|
||||||
|
p := strings.TrimSpace(os.Getenv("PRODUCT_SERIES_DEBUG_PRODUCT"))
|
||||||
|
if p != "" && !strings.EqualFold(strings.TrimSpace(v.ProductCode), p) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c := strings.TrimSpace(os.Getenv("PRODUCT_SERIES_DEBUG_COLOR"))
|
||||||
|
if c != "" && !strings.EqualFold(strings.TrimSpace(v.ColorCode), c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
d := strings.TrimSpace(os.Getenv("PRODUCT_SERIES_DEBUG_DIM3"))
|
||||||
|
if d != "" && !strings.EqualFold(strings.TrimSpace(v.Dim3Code), d) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return p != "" || c != "" || d != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func productSeriesFormatSizeQty(m map[string]float64) string {
|
||||||
|
if len(m) == 0 {
|
||||||
|
return "{}"
|
||||||
|
}
|
||||||
|
keys := make([]string, 0, len(m))
|
||||||
|
for k := range m {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
parts := make([]string, 0, len(keys))
|
||||||
|
for _, k := range keys {
|
||||||
|
parts = append(parts, fmt.Sprintf("%s=%.0f", k, m[k]))
|
||||||
|
}
|
||||||
|
return "{" + strings.Join(parts, ",") + "}"
|
||||||
|
}
|
||||||
|
|
||||||
func productSeriesFallbackCode() string {
|
func productSeriesFallbackCode() string {
|
||||||
code := strings.TrimSpace(os.Getenv("PRODUCT_SERIES_FALLBACK_SERIES_CODE"))
|
code := strings.TrimSpace(os.Getenv("PRODUCT_SERIES_FALLBACK_SERIES_CODE"))
|
||||||
if code == "" {
|
if code == "" {
|
||||||
code = "1-"
|
// In this installation, the default series code is "1" (not "1-").
|
||||||
|
code = "1"
|
||||||
}
|
}
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
@@ -242,9 +292,12 @@ func startProductSeriesAutoSchedulers(pgDB *sql.DB) {
|
|||||||
apply := envBool("PRODUCT_SERIES_AUTO_APPLY", false)
|
apply := envBool("PRODUCT_SERIES_AUTO_APPLY", false)
|
||||||
deltaIntervalMin := envIntRange("PRODUCT_SERIES_DELTA_INTERVAL_MIN", 60, 5, 1440)
|
deltaIntervalMin := envIntRange("PRODUCT_SERIES_DELTA_INTERVAL_MIN", 60, 5, 1440)
|
||||||
queueBatch := envIntRange("PRODUCT_SERIES_DELTA_BATCH_SIZE", 300, 10, 2000)
|
queueBatch := envIntRange("PRODUCT_SERIES_DELTA_BATCH_SIZE", 300, 10, 2000)
|
||||||
fullHH, fullMM := envHHMM("PRODUCT_SERIES_FULL_HHMM", 5, 30)
|
fullHH, fullMM := envHHMM("PRODUCT_SERIES_FULL_HHMM", 5, 0)
|
||||||
runFullOnStartup := envBool("PRODUCT_SERIES_FULL_RUN_ON_STARTUP", false)
|
// Deploys should not trigger heavy jobs. Startup runs are gated behind an extra allow flag so
|
||||||
runDeltaOnStartup := envBool("PRODUCT_SERIES_DELTA_RUN_ON_STARTUP", false)
|
// a stale/old env var value can't accidentally bring back "runs on every deploy" behavior.
|
||||||
|
allowStartupRuns := envBool("PRODUCT_SERIES_ALLOW_STARTUP_RUNS", false)
|
||||||
|
runFullOnStartup := allowStartupRuns && envBool("PRODUCT_SERIES_FULL_RUN_ON_STARTUP", false)
|
||||||
|
runDeltaOnStartup := allowStartupRuns && envBool("PRODUCT_SERIES_DELTA_RUN_ON_STARTUP", false)
|
||||||
|
|
||||||
var deltaRunning int32
|
var deltaRunning int32
|
||||||
var fullRunning int32
|
var fullRunning int32
|
||||||
@@ -313,14 +366,14 @@ func startProductSeriesAutoSchedulers(pgDB *sql.DB) {
|
|||||||
runFull("startup_manual")
|
runFull("startup_manual")
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
next := nextProductSeriesWeeklyRun(time.Now(), fullHH, fullMM)
|
next := nextProductSeriesDailyRun(time.Now(), fullHH, fullMM)
|
||||||
wait := time.Until(next)
|
wait := time.Until(next)
|
||||||
if wait < 0 {
|
if wait < 0 {
|
||||||
wait = time.Minute
|
wait = time.Minute
|
||||||
}
|
}
|
||||||
log.Printf("[ProductSeriesFull] scheduled next_at=%s in=%s", next.Format(time.RFC3339), wait.Round(time.Second))
|
log.Printf("[ProductSeriesFull] scheduled next_at=%s in=%s", next.Format(time.RFC3339), wait.Round(time.Second))
|
||||||
time.Sleep(wait)
|
time.Sleep(wait)
|
||||||
runFull("weekly")
|
runFull("daily")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@@ -483,36 +536,170 @@ func productSeriesApplyVariant(ctx context.Context, pg *sql.DB, v productSeriesA
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 1, err
|
return 0, 1, err
|
||||||
}
|
}
|
||||||
|
appliedFallback := false
|
||||||
if !ready {
|
if !ready {
|
||||||
|
if productSeriesFallbackLogEnabled() {
|
||||||
|
log.Printf("[ProductSeriesFallback] skip_not_ready product=%s color=%s dim3=%s", strings.TrimSpace(v.ProductCode), strings.TrimSpace(v.ColorCode), strings.TrimSpace(v.Dim3Code))
|
||||||
|
}
|
||||||
return 0, 1, nil
|
return 0, 1, nil
|
||||||
}
|
}
|
||||||
|
if productSeriesDebugMatch(v) {
|
||||||
|
log.Printf("[ProductSeriesDebug] variant product=%s color=%s dim3=%s mmitem_id=%d dim1=%d dim3_id=%v group=%s total=%.0f sizes=%s",
|
||||||
|
strings.TrimSpace(v.ProductCode),
|
||||||
|
strings.TrimSpace(v.ColorCode),
|
||||||
|
strings.TrimSpace(v.Dim3Code),
|
||||||
|
mmitemID,
|
||||||
|
dim1ID,
|
||||||
|
func() any {
|
||||||
|
if dim3ID.Valid {
|
||||||
|
return dim3ID.Int64
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}(),
|
||||||
|
strings.TrimSpace(v.GroupKey),
|
||||||
|
math.Round(v.TotalQty),
|
||||||
|
productSeriesFormatSizeQty(v.SizeQty),
|
||||||
|
)
|
||||||
|
}
|
||||||
selected := productSeriesSelectRules(v, rules)
|
selected := productSeriesSelectRules(v, rules)
|
||||||
|
if productSeriesDebugMatch(v) {
|
||||||
|
if len(selected) == 0 {
|
||||||
|
log.Printf("[ProductSeriesDebug] selected=0 product=%s color=%s dim3=%s", strings.TrimSpace(v.ProductCode), strings.TrimSpace(v.ColorCode), strings.TrimSpace(v.Dim3Code))
|
||||||
|
} else {
|
||||||
|
ids := make([]string, 0, len(selected))
|
||||||
|
for _, r := range selected {
|
||||||
|
ids = append(ids, fmt.Sprintf("%d", r.SeriesID))
|
||||||
|
}
|
||||||
|
log.Printf("[ProductSeriesDebug] selected=%d product=%s color=%s dim3=%s series_ids=%s",
|
||||||
|
len(selected),
|
||||||
|
strings.TrimSpace(v.ProductCode),
|
||||||
|
strings.TrimSpace(v.ColorCode),
|
||||||
|
strings.TrimSpace(v.Dim3Code),
|
||||||
|
strings.Join(ids, ","),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
if len(selected) == 0 {
|
if len(selected) == 0 {
|
||||||
fallbackID, _, err := productSeriesResolveFallbackSeries(ctx, pg)
|
// Apply fallback based on per-size availability, not total.
|
||||||
|
// If none of the sizes has qty >= 1 (default), do nothing.
|
||||||
|
minSizeQty := envFloat64("PRODUCT_SERIES_FALLBACK_MIN_SIZE_QTY", 1)
|
||||||
|
if !productSeriesHasAnySizeAtLeast(v.SizeQty, minSizeQty) {
|
||||||
|
if productSeriesFallbackLogEnabled() {
|
||||||
|
log.Printf("[ProductSeriesFallback] skip_insufficient_stock product=%s color=%s dim3=%s min_size_qty=%.2f sizes=%s",
|
||||||
|
strings.TrimSpace(v.ProductCode),
|
||||||
|
strings.TrimSpace(v.ColorCode),
|
||||||
|
strings.TrimSpace(v.Dim3Code),
|
||||||
|
minSizeQty,
|
||||||
|
productSeriesFormatSizeQty(v.SizeQty),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return 0, 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fallbackID, fallbackCode, err := productSeriesResolveFallbackSeries(ctx, pg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 1, err
|
return 0, 1, err
|
||||||
}
|
}
|
||||||
if fallbackID > 0 {
|
if fallbackID > 0 {
|
||||||
// Only apply fallback when the variant has no assignments yet.
|
if !productSeriesFallbackOverrideExisting() {
|
||||||
var exists int
|
// By default we don't override existing assignments. However, if the existing assignment(s)
|
||||||
checkErr := pg.QueryRowContext(ctx, `
|
// are no longer compatible with current stock (e.g., stock sold out), keeping them causes
|
||||||
SELECT 1
|
// stale/wrong series to stick forever (already_exists). In that case, we should replace
|
||||||
FROM zbggseri
|
// them with fallback=1.
|
||||||
WHERE mmitem_id=$1
|
existingIDs, err := productSeriesLoadExistingSeriesIDs(ctx, pg, mmitemID, dim1ID, dim3ID)
|
||||||
AND dim1=$2
|
if err != nil {
|
||||||
AND (($3::bigint IS NULL AND dim3 IS NULL) OR dim3=$3::bigint)
|
return 0, 1, err
|
||||||
LIMIT 1
|
}
|
||||||
`, mmitemID, dim1ID, nullableInt64ForAuto(dim3ID)).Scan(&exists)
|
if len(existingIDs) > 0 {
|
||||||
if checkErr == nil {
|
// Optional grace window: if we recently applied non-fallback rules for this variant,
|
||||||
// keep existing manual/previous assignment; nothing to do
|
// don't immediately flap back to fallback due to a transient/partial stock snapshot.
|
||||||
return 0, 0, nil
|
graceMin := envIntRange("PRODUCT_SERIES_FALLBACK_GRACE_MINUTES", 2, 0, 60)
|
||||||
|
if graceMin > 0 {
|
||||||
|
rowKey := productSeriesAutoKey(v.ProductCode, v.ColorCode, v.Dim3Code)
|
||||||
|
kind, at, err := productSeriesLoadLastApplyState(ctx, pg, rowKey)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 1, err
|
||||||
|
}
|
||||||
|
if kind == "rules" && time.Since(at) < time.Duration(graceMin)*time.Minute {
|
||||||
|
// keep existing mapping as-is for now
|
||||||
|
if productSeriesFallbackLogEnabled() {
|
||||||
|
log.Printf("[ProductSeriesFallback] grace_skip product=%s color=%s dim3=%s grace_min=%d last_rules_at=%s",
|
||||||
|
strings.TrimSpace(v.ProductCode),
|
||||||
|
strings.TrimSpace(v.ColorCode),
|
||||||
|
strings.TrimSpace(v.Dim3Code),
|
||||||
|
graceMin,
|
||||||
|
at.Format(time.RFC3339),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return 0, 0, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anyStillValid := false
|
||||||
|
for _, sid := range existingIDs {
|
||||||
|
// If we can't find the rule definition in memory, assume it's a manual/unknown mapping and keep it.
|
||||||
|
rule, ok := productSeriesFindRuleByID(rules, sid)
|
||||||
|
if !ok {
|
||||||
|
anyStillValid = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if productSeriesCanConsume(v.SizeQty, rule) {
|
||||||
|
anyStillValid = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if anyStillValid {
|
||||||
|
_ = productSeriesResetInvalidStreak(ctx, pg, productSeriesAutoKey(v.ProductCode, v.ColorCode, v.Dim3Code))
|
||||||
|
if productSeriesFallbackLogEnabled() {
|
||||||
|
log.Printf("[ProductSeriesFallback] already_exists product=%s color=%s dim3=%s fallback=%s(%d)", strings.TrimSpace(v.ProductCode), strings.TrimSpace(v.ColorCode), strings.TrimSpace(v.Dim3Code), strings.TrimSpace(fallbackCode), fallbackID)
|
||||||
|
}
|
||||||
|
// keep existing manual/previous assignment; nothing to do
|
||||||
|
return 0, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guardrail: if stock snapshots are partial/transient, "existing invalid" can flap.
|
||||||
|
// Require N consecutive invalid observations before overwriting existing mapping.
|
||||||
|
confirmations := envIntRange("PRODUCT_SERIES_EXISTING_INVALID_CONFIRMATIONS", 3, 1, 20)
|
||||||
|
rowKey := productSeriesAutoKey(v.ProductCode, v.ColorCode, v.Dim3Code)
|
||||||
|
streak, err := productSeriesBumpInvalidStreak(ctx, pg, rowKey)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 1, err
|
||||||
|
}
|
||||||
|
if streak < confirmations {
|
||||||
|
if productSeriesFallbackLogEnabled() {
|
||||||
|
log.Printf("[ProductSeriesFallback] existing_invalid_defer product=%s color=%s dim3=%s streak=%d/%d fallback=%s(%d)",
|
||||||
|
strings.TrimSpace(v.ProductCode),
|
||||||
|
strings.TrimSpace(v.ColorCode),
|
||||||
|
strings.TrimSpace(v.Dim3Code),
|
||||||
|
streak,
|
||||||
|
confirmations,
|
||||||
|
strings.TrimSpace(fallbackCode),
|
||||||
|
fallbackID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// keep existing mapping for now
|
||||||
|
return 0, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if productSeriesFallbackLogEnabled() {
|
||||||
|
log.Printf("[ProductSeriesFallback] existing_invalid_apply product=%s color=%s dim3=%s fallback=%s(%d)", strings.TrimSpace(v.ProductCode), strings.TrimSpace(v.ColorCode), strings.TrimSpace(v.Dim3Code), strings.TrimSpace(fallbackCode), fallbackID)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if checkErr != nil && checkErr != sql.ErrNoRows {
|
if productSeriesFallbackLogEnabled() {
|
||||||
return 0, 1, checkErr
|
if productSeriesFallbackOverrideExisting() {
|
||||||
|
log.Printf("[ProductSeriesFallback] override_apply product=%s color=%s dim3=%s fallback=%s(%d)", strings.TrimSpace(v.ProductCode), strings.TrimSpace(v.ColorCode), strings.TrimSpace(v.Dim3Code), strings.TrimSpace(fallbackCode), fallbackID)
|
||||||
|
} else {
|
||||||
|
log.Printf("[ProductSeriesFallback] apply product=%s color=%s dim3=%s fallback=%s(%d)", strings.TrimSpace(v.ProductCode), strings.TrimSpace(v.ColorCode), strings.TrimSpace(v.Dim3Code), strings.TrimSpace(fallbackCode), fallbackID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Use the fallback series as the single selected rule.
|
// Use the fallback series as the single selected rule.
|
||||||
selected = []productSeriesAutoRule{{SeriesID: fallbackID}}
|
selected = []productSeriesAutoRule{{SeriesID: fallbackID}}
|
||||||
|
appliedFallback = true
|
||||||
} else {
|
} else {
|
||||||
|
if productSeriesFallbackLogEnabled() {
|
||||||
|
log.Printf("[ProductSeriesFallback] missing_fallback product=%s color=%s dim3=%s fallback_code=%s", strings.TrimSpace(v.ProductCode), strings.TrimSpace(v.ColorCode), strings.TrimSpace(v.Dim3Code), strings.TrimSpace(fallbackCode))
|
||||||
|
}
|
||||||
return 0, 1, nil
|
return 0, 1, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -525,6 +712,11 @@ LIMIT 1
|
|||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// Tag the DB session for audit attribution (scheduler vs http handler).
|
||||||
|
// This affects only statements in this transaction.
|
||||||
|
_, _ = tx.ExecContext(ctx, `SELECT set_config('application_name', current_setting('application_name') || '|series-auto', true)`)
|
||||||
|
|
||||||
if _, err := tx.ExecContext(ctx, `
|
if _, err := tx.ExecContext(ctx, `
|
||||||
DELETE FROM zbggseri
|
DELETE FROM zbggseri
|
||||||
WHERE mmitem_id=$1
|
WHERE mmitem_id=$1
|
||||||
@@ -544,9 +736,120 @@ VALUES ($1, $2, $3, $4)
|
|||||||
if err := tx.Commit(); err != nil {
|
if err := tx.Commit(); err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
|
// Track what we last applied for this variant to reduce flapping and help ops debugging.
|
||||||
|
rowKey := productSeriesAutoKey(v.ProductCode, v.ColorCode, v.Dim3Code)
|
||||||
|
kind := "rules"
|
||||||
|
if appliedFallback {
|
||||||
|
kind = "fallback"
|
||||||
|
}
|
||||||
|
ids := make([]string, 0, len(selected))
|
||||||
|
for _, r := range selected {
|
||||||
|
ids = append(ids, fmt.Sprintf("%d", r.SeriesID))
|
||||||
|
}
|
||||||
|
_, _ = pg.ExecContext(ctx, `
|
||||||
|
INSERT INTO mk_product_series_apply_state (row_key, last_apply_at, last_apply_kind, last_series_ids, updated_at)
|
||||||
|
VALUES ($1, now(), $2, $3, now())
|
||||||
|
ON CONFLICT (row_key) DO UPDATE
|
||||||
|
SET last_apply_at=EXCLUDED.last_apply_at,
|
||||||
|
last_apply_kind=EXCLUDED.last_apply_kind,
|
||||||
|
last_series_ids=EXCLUDED.last_series_ids,
|
||||||
|
invalid_streak=0,
|
||||||
|
last_invalid_at=NULL,
|
||||||
|
updated_at=now()
|
||||||
|
`, rowKey, kind, strings.Join(ids, ","))
|
||||||
|
|
||||||
|
if productSeriesDebugMatch(v) {
|
||||||
|
log.Printf("[ProductSeriesDebug] wrote=%d product=%s color=%s dim3=%s series_ids=%s",
|
||||||
|
len(selected),
|
||||||
|
strings.TrimSpace(v.ProductCode),
|
||||||
|
strings.TrimSpace(v.ColorCode),
|
||||||
|
strings.TrimSpace(v.Dim3Code),
|
||||||
|
strings.Join(ids, ","),
|
||||||
|
)
|
||||||
|
}
|
||||||
return len(selected), 0, nil
|
return len(selected), 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func productSeriesLoadLastApplyState(ctx context.Context, pg *sql.DB, rowKey string) (kind string, at time.Time, err error) {
|
||||||
|
var k string
|
||||||
|
var t time.Time
|
||||||
|
err = pg.QueryRowContext(ctx, `
|
||||||
|
SELECT COALESCE(last_apply_kind,''), COALESCE(last_apply_at, now())
|
||||||
|
FROM mk_product_series_apply_state
|
||||||
|
WHERE row_key=$1
|
||||||
|
`, rowKey).Scan(&k, &t)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return "", time.Time{}, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, err
|
||||||
|
}
|
||||||
|
return k, t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func productSeriesBumpInvalidStreak(ctx context.Context, pg *sql.DB, rowKey string) (int, error) {
|
||||||
|
var next int
|
||||||
|
err := pg.QueryRowContext(ctx, `
|
||||||
|
INSERT INTO mk_product_series_apply_state (row_key, last_apply_at, last_apply_kind, last_series_ids, invalid_streak, last_invalid_at, updated_at)
|
||||||
|
VALUES ($1, now(), '', '', 1, now(), now())
|
||||||
|
ON CONFLICT (row_key) DO UPDATE
|
||||||
|
SET invalid_streak=mk_product_series_apply_state.invalid_streak + 1,
|
||||||
|
last_invalid_at=now(),
|
||||||
|
updated_at=now()
|
||||||
|
RETURNING invalid_streak
|
||||||
|
`, rowKey).Scan(&next)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return next, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func productSeriesResetInvalidStreak(ctx context.Context, pg *sql.DB, rowKey string) error {
|
||||||
|
_, err := pg.ExecContext(ctx, `
|
||||||
|
UPDATE mk_product_series_apply_state
|
||||||
|
SET invalid_streak=0,
|
||||||
|
last_invalid_at=NULL,
|
||||||
|
updated_at=now()
|
||||||
|
WHERE row_key=$1
|
||||||
|
`, rowKey)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func productSeriesFindRuleByID(rules []productSeriesAutoRule, seriesID int64) (productSeriesAutoRule, bool) {
|
||||||
|
for _, r := range rules {
|
||||||
|
if r.SeriesID == seriesID {
|
||||||
|
return r, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return productSeriesAutoRule{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func productSeriesLoadExistingSeriesIDs(ctx context.Context, pg *sql.DB, mmitemID int64, dim1ID int64, dim3ID sql.NullInt64) ([]int64, error) {
|
||||||
|
rows, err := pg.QueryContext(ctx, `
|
||||||
|
SELECT seri_id
|
||||||
|
FROM zbggseri
|
||||||
|
WHERE mmitem_id=$1
|
||||||
|
AND dim1=$2
|
||||||
|
AND (($3::bigint IS NULL AND dim3 IS NULL) OR dim3=$3::bigint)
|
||||||
|
ORDER BY seri_id
|
||||||
|
`, mmitemID, dim1ID, nullableInt64ForAuto(dim3ID))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
out := []int64{}
|
||||||
|
for rows.Next() {
|
||||||
|
var id int64
|
||||||
|
if err := rows.Scan(&id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if id > 0 {
|
||||||
|
out = append(out, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
func productSeriesLoadMSSQLStockVariants(ctx context.Context) (map[string]productSeriesAutoVariant, int, error) {
|
func productSeriesLoadMSSQLStockVariants(ctx context.Context) (map[string]productSeriesAutoVariant, int, error) {
|
||||||
rows, err := db.MssqlDB.QueryContext(ctx, queries.GetProductSeriesStockRowsQuery,
|
rows, err := db.MssqlDB.QueryContext(ctx, queries.GetProductSeriesStockRowsQuery,
|
||||||
"", "", "", "", "", "", "", "", "", "0", "",
|
"", "", "", "", "", "", "", "", "", "0", "",
|
||||||
@@ -659,13 +962,19 @@ ORDER BY d.id, r.priority DESC, r.id
|
|||||||
}
|
}
|
||||||
out := make([]productSeriesAutoRule, 0, len(byID))
|
out := make([]productSeriesAutoRule, 0, len(byID))
|
||||||
for _, r := range byID {
|
for _, r := range byID {
|
||||||
if len(r.Ratio) == 0 {
|
// Always augment ratio map from the dfgrp.title tokens.
|
||||||
for _, part := range strings.Split(r.Title, "-") {
|
// mk_product_series_rule can be partially populated (or manually edited), which would allow
|
||||||
size := normalizeProductSeriesSize(part)
|
// a "subset" rule (e.g. only M+XL) to match stock and incorrectly assign a series like
|
||||||
if size != "" {
|
// "XS-S-M-L-XL-2XL". Title tokens are the canonical definition of a series in this setup.
|
||||||
r.Ratio[size] = 1
|
for _, part := range strings.Split(r.Title, "-") {
|
||||||
}
|
size := normalizeProductSeriesSize(part)
|
||||||
|
if size == "" {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
if _, ok := r.Ratio[size]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r.Ratio[size] = 1
|
||||||
}
|
}
|
||||||
if len(r.Ratio) > 0 {
|
if len(r.Ratio) > 0 {
|
||||||
out = append(out, *r)
|
out = append(out, *r)
|
||||||
@@ -684,6 +993,11 @@ ORDER BY d.id, r.priority DESC, r.id
|
|||||||
}
|
}
|
||||||
|
|
||||||
func productSeriesSelectRules(v productSeriesAutoVariant, rules []productSeriesAutoRule) []productSeriesAutoRule {
|
func productSeriesSelectRules(v productSeriesAutoVariant, rules []productSeriesAutoRule) []productSeriesAutoRule {
|
||||||
|
selectionMode := strings.TrimSpace(strings.ToLower(os.Getenv("PRODUCT_SERIES_SELECTION_MODE")))
|
||||||
|
if selectionMode == "" {
|
||||||
|
selectionMode = "dfs"
|
||||||
|
}
|
||||||
|
|
||||||
candidates := make([]productSeriesAutoRule, 0, len(rules))
|
candidates := make([]productSeriesAutoRule, 0, len(rules))
|
||||||
for _, rule := range rules {
|
for _, rule := range rules {
|
||||||
if !productSeriesRuleFitsGroup(v.GroupKey, rule) {
|
if !productSeriesRuleFitsGroup(v.GroupKey, rule) {
|
||||||
@@ -696,6 +1010,58 @@ func productSeriesSelectRules(v productSeriesAutoVariant, rules []productSeriesA
|
|||||||
if len(candidates) == 0 {
|
if len(candidates) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if selectionMode == "greedy" {
|
||||||
|
// Greedy: start from the widest series (most sizes), then narrower, until nothing fits.
|
||||||
|
// This matches the operational expectation: cover as much of the stock-size space as possible
|
||||||
|
// using broad series first, then progressively narrower series; fallback=1 only if nothing matches.
|
||||||
|
sort.Slice(candidates, func(i, j int) bool {
|
||||||
|
li := len(candidates[i].Ratio)
|
||||||
|
lj := len(candidates[j].Ratio)
|
||||||
|
if li != lj {
|
||||||
|
return li > lj
|
||||||
|
}
|
||||||
|
if candidates[i].Priority != candidates[j].Priority {
|
||||||
|
return candidates[i].Priority > candidates[j].Priority
|
||||||
|
}
|
||||||
|
// Prefer numeric codes ascending when comparable, otherwise lexicographic.
|
||||||
|
ai, ei := strconv.Atoi(strings.TrimSpace(candidates[i].Code))
|
||||||
|
aj, ej := strconv.Atoi(strings.TrimSpace(candidates[j].Code))
|
||||||
|
if ei == nil && ej == nil && ai != aj {
|
||||||
|
return ai < aj
|
||||||
|
}
|
||||||
|
return candidates[i].Code < candidates[j].Code
|
||||||
|
})
|
||||||
|
|
||||||
|
stock := copyProductSeriesStock(v.SizeQty)
|
||||||
|
picked := make([]productSeriesAutoRule, 0, 8)
|
||||||
|
used := make([]bool, len(candidates))
|
||||||
|
for {
|
||||||
|
pickedOne := false
|
||||||
|
for i := range candidates {
|
||||||
|
if used[i] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rule := candidates[i]
|
||||||
|
if productSeriesCanConsume(stock, rule) {
|
||||||
|
used[i] = true
|
||||||
|
picked = append(picked, rule)
|
||||||
|
productSeriesConsume(stock, rule, -1)
|
||||||
|
pickedOne = true
|
||||||
|
break // restart from widest again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !pickedOne {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(picked) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
sort.Slice(picked, func(i, j int) bool { return picked[i].Code < picked[j].Code })
|
||||||
|
return picked
|
||||||
|
}
|
||||||
|
|
||||||
if len(candidates) > 24 {
|
if len(candidates) > 24 {
|
||||||
candidates = candidates[:24]
|
candidates = candidates[:24]
|
||||||
}
|
}
|
||||||
@@ -786,12 +1152,33 @@ func productSeriesResolvePGVariant(ctx context.Context, pg *sql.DB, productCode,
|
|||||||
}
|
}
|
||||||
return 0, 0, sql.NullInt64{}, false, err
|
return 0, 0, sql.NullInt64{}, false, err
|
||||||
}
|
}
|
||||||
dim1ID, ok, err := productSeriesResolveDimTokenID(ctx, pg, "dimval1", colorCode, mmitemID)
|
// Authoritative dim1 resolver: for this installation, "color_code" tokens correspond to dfgrp.code on
|
||||||
if err != nil {
|
// the dim values referenced by mmitem_dim.val1. Do NOT rely on mk_dim_token_map for dimval1 because
|
||||||
return 0, 0, sql.NullInt64{}, false, err
|
// it can be polluted and conflate tokens.
|
||||||
}
|
var dim1ID int64
|
||||||
if !ok || dim1ID <= 0 {
|
{
|
||||||
return 0, 0, sql.NullInt64{}, false, nil
|
tok := strings.ToUpper(strings.TrimSpace(colorCode))
|
||||||
|
if tok == "" || tok == "0" {
|
||||||
|
return 0, 0, sql.NullInt64{}, false, nil
|
||||||
|
}
|
||||||
|
if err := pg.QueryRowContext(ctx, `
|
||||||
|
SELECT md.val1
|
||||||
|
FROM mmitem_dim md
|
||||||
|
JOIN dfgrp d ON d.id = md.val1
|
||||||
|
WHERE md.mmitem_id = $1
|
||||||
|
AND md.is_active = TRUE
|
||||||
|
AND UPPER(BTRIM(d.code)) = $2
|
||||||
|
GROUP BY md.val1
|
||||||
|
LIMIT 1
|
||||||
|
`, mmitemID, tok).Scan(&dim1ID); err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return 0, 0, sql.NullInt64{}, false, nil
|
||||||
|
}
|
||||||
|
return 0, 0, sql.NullInt64{}, false, err
|
||||||
|
}
|
||||||
|
if dim1ID <= 0 {
|
||||||
|
return 0, 0, sql.NullInt64{}, false, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var dim3ID sql.NullInt64
|
var dim3ID sql.NullInt64
|
||||||
if strings.TrimSpace(dim3Code) != "" {
|
if strings.TrimSpace(dim3Code) != "" {
|
||||||
@@ -804,6 +1191,28 @@ func productSeriesResolvePGVariant(ctx context.Context, pg *sql.DB, productCode,
|
|||||||
}
|
}
|
||||||
dim3ID = sql.NullInt64{Int64: id, Valid: true}
|
dim3ID = sql.NullInt64{Int64: id, Valid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only treat variants as ready if they exist in authoritative item-dim combos (mmitem_dim).
|
||||||
|
// This prevents writing series assignments for stock variants that don't exist on the PG side.
|
||||||
|
dim3Key := int64(0)
|
||||||
|
if dim3ID.Valid && dim3ID.Int64 > 1000 {
|
||||||
|
dim3Key = dim3ID.Int64
|
||||||
|
}
|
||||||
|
var exists int
|
||||||
|
if err := pg.QueryRowContext(ctx, `
|
||||||
|
SELECT 1
|
||||||
|
FROM mmitem_dim
|
||||||
|
WHERE mmitem_id=$1
|
||||||
|
AND is_active=TRUE
|
||||||
|
AND val1=$2
|
||||||
|
AND COALESCE(CASE WHEN val3 IS NOT NULL AND val3 > 1000 THEN val3 ELSE 0 END, 0) = $3
|
||||||
|
LIMIT 1
|
||||||
|
`, mmitemID, dim1ID, dim3Key).Scan(&exists); err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return 0, 0, sql.NullInt64{}, false, nil
|
||||||
|
}
|
||||||
|
return 0, 0, sql.NullInt64{}, false, err
|
||||||
|
}
|
||||||
return mmitemID, dim1ID, dim3ID, true, nil
|
return mmitemID, dim1ID, dim3ID, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -813,29 +1222,39 @@ func productSeriesResolveDimTokenID(ctx context.Context, pg *sql.DB, column stri
|
|||||||
return 0, false, nil
|
return 0, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// dimval3 tokens like "001" can map to different dim ids per product in this installation.
|
|
||||||
// Prefer per-mmitem inference from dfblob (src_id filter) to avoid global mk_dim_token_map mismatches.
|
|
||||||
if column == "dimval3" && mmitemID > 0 {
|
|
||||||
if inferred, ok := productSeriesInferDimIDFromImages(pg, mmitemID, column, tok); ok {
|
|
||||||
return inferred, true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var id int64
|
var id int64
|
||||||
err := pg.QueryRowContext(ctx, `SELECT dim_id FROM mk_dim_token_map WHERE dim_column=$1 AND token=$2`, column, tok).Scan(&id)
|
err := pg.QueryRowContext(ctx, `SELECT dim_id FROM mk_dim_token_map WHERE dim_column=$1 AND token=$2`, column, tok).Scan(&id)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
// mk_dim_token_map can become polluted by heuristic inference/persistence (especially for dimval1).
|
||||||
|
// If we can cross-check against item-scoped images, prefer the item-specific inference to avoid
|
||||||
|
// conflating different tokens onto the same dim_id.
|
||||||
|
if column == "dimval1" && mmitemID > 0 {
|
||||||
|
if inferred, ok := productSeriesInferDimIDFromImages(pg, mmitemID, column, tok); ok && inferred > 0 && inferred != id {
|
||||||
|
log.Printf("[DimTokenConflict] column=dimval1 token=%s mmitem_id=%d mapped=%d inferred=%d", tok, mmitemID, id, inferred)
|
||||||
|
return inferred, true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
return id, id > 0, nil
|
return id, id > 0, nil
|
||||||
}
|
}
|
||||||
if err != sql.ErrNoRows {
|
if err != sql.ErrNoRows {
|
||||||
return 0, false, err
|
return 0, false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: infer from dfblob filenames. For dimval3 do not persist globally.
|
// Fallback: infer from dfblob filenames.
|
||||||
|
// Prefer item-scoped inference when we have mmitem_id. We intentionally avoid persisting these
|
||||||
|
// inferred rows into mk_dim_token_map to prevent global pollution.
|
||||||
if mmitemID > 0 {
|
if mmitemID > 0 {
|
||||||
if inferred, ok := productSeriesInferDimIDFromImages(pg, mmitemID, column, tok); ok {
|
if inferred, ok := productSeriesInferDimIDFromImages(pg, mmitemID, column, tok); ok && inferred > 0 {
|
||||||
return inferred, true, nil
|
return inferred, true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// As a last resort, do a global inference. This is intentionally disabled for dimval1 because it is
|
||||||
|
// too easy to mis-infer and conflate tokens across the whole catalog.
|
||||||
|
if column == "dimval1" {
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
v := productSeriesResolveDimvalFromFileNameToken(pg, column, tok, 0)
|
v := productSeriesResolveDimvalFromFileNameToken(pg, column, tok, 0)
|
||||||
if v == "" {
|
if v == "" {
|
||||||
return 0, false, nil
|
return 0, false, nil
|
||||||
@@ -844,15 +1263,6 @@ func productSeriesResolveDimTokenID(ctx context.Context, pg *sql.DB, column stri
|
|||||||
if perr != nil || parsed <= 0 {
|
if perr != nil || parsed <= 0 {
|
||||||
return 0, false, nil
|
return 0, false, nil
|
||||||
}
|
}
|
||||||
if column == "dimval1" {
|
|
||||||
// Persist only for dimval1 where tokens are globally stable.
|
|
||||||
_, _ = pg.ExecContext(ctx, `
|
|
||||||
INSERT INTO mk_dim_token_map (dim_column, token, dim_id, updated_at)
|
|
||||||
VALUES ($1,$2,$3,now())
|
|
||||||
ON CONFLICT (dim_column, token)
|
|
||||||
DO UPDATE SET dim_id = EXCLUDED.dim_id, updated_at = EXCLUDED.updated_at
|
|
||||||
`, column, tok, parsed)
|
|
||||||
}
|
|
||||||
return parsed, true, nil
|
return parsed, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1134,7 +1544,26 @@ func productSeriesAutoKey(productCode, colorCode, dim3Code string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func normalizeProductSeriesSize(v string) string {
|
func normalizeProductSeriesSize(v string) string {
|
||||||
return strings.ToUpper(strings.TrimSpace(v))
|
s := strings.ToUpper(strings.TrimSpace(v))
|
||||||
|
// Some installations store series size tokens with a "seriesCode/size" prefix in title/rules
|
||||||
|
// (e.g. "2/48-50-52"). Treat "2/48" as size "48".
|
||||||
|
if i := strings.IndexByte(s, '/'); i > 0 {
|
||||||
|
prefix := strings.TrimSpace(s[:i])
|
||||||
|
suffix := strings.TrimSpace(s[i+1:])
|
||||||
|
if suffix != "" {
|
||||||
|
allDigits := true
|
||||||
|
for _, r := range prefix {
|
||||||
|
if r < '0' || r > '9' {
|
||||||
|
allDigits = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if allDigits {
|
||||||
|
s = suffix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyProductSeriesStock(in map[string]float64) map[string]float64 {
|
func copyProductSeriesStock(in map[string]float64) map[string]float64 {
|
||||||
@@ -1152,15 +1581,13 @@ func nullableInt64ForAuto(v sql.NullInt64) any {
|
|||||||
return v.Int64
|
return v.Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func nextProductSeriesWeeklyRun(now time.Time, hh int, mm int) time.Time {
|
func nextProductSeriesDailyRun(now time.Time, hh int, mm int) time.Time {
|
||||||
loc := now.Location()
|
loc := now.Location()
|
||||||
base := time.Date(now.Year(), now.Month(), now.Day(), hh, mm, 0, 0, loc)
|
base := time.Date(now.Year(), now.Month(), now.Day(), hh, mm, 0, 0, loc)
|
||||||
daysUntilMon := (int(time.Monday) - int(now.Weekday()) + 7) % 7
|
if base.After(now) {
|
||||||
candidate := base.AddDate(0, 0, daysUntilMon)
|
return base
|
||||||
if !candidate.After(now) {
|
|
||||||
candidate = candidate.AddDate(0, 0, 7)
|
|
||||||
}
|
}
|
||||||
return candidate
|
return base.AddDate(0, 0, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func envBool(key string, fallback bool) bool {
|
func envBool(key string, fallback bool) bool {
|
||||||
@@ -1183,6 +1610,27 @@ func envIntRange(key string, fallback int, min int, max int) int {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func envFloat64(key string, fallback float64) float64 {
|
||||||
|
raw := strings.TrimSpace(os.Getenv(key))
|
||||||
|
if raw == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
v, err := strconv.ParseFloat(raw, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func productSeriesHasAnySizeAtLeast(sizeQty map[string]float64, min float64) bool {
|
||||||
|
for _, q := range sizeQty {
|
||||||
|
if q >= min {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func envHHMM(key string, fallbackHH int, fallbackMM int) (int, int) {
|
func envHHMM(key string, fallbackHH int, fallbackMM int) (int, int) {
|
||||||
raw := strings.TrimSpace(os.Getenv(key))
|
raw := strings.TrimSpace(os.Getenv(key))
|
||||||
if raw == "" {
|
if raw == "" {
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ func GetAccounts(ctx context.Context) ([]models.Account, error) {
|
|||||||
c.CurrAccCode,
|
c.CurrAccCode,
|
||||||
a.AttributeTypeCode,
|
a.AttributeTypeCode,
|
||||||
a.AttributeCode
|
a.AttributeCode
|
||||||
FROM cdCurrAcc c WITH (NOLOCK)
|
FROM cdCurrAcc c
|
||||||
LEFT JOIN prCurrAccAttribute a WITH (NOLOCK)
|
LEFT JOIN prCurrAccAttribute a
|
||||||
ON a.CurrAccTypeCode = c.CurrAccTypeCode
|
ON a.CurrAccTypeCode = c.CurrAccTypeCode
|
||||||
AND a.CurrAccCode = c.CurrAccCode
|
AND a.CurrAccCode = c.CurrAccCode
|
||||||
WHERE c.CurrAccTypeCode = 1
|
WHERE c.CurrAccTypeCode = 1
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ SELECT DISTINCT
|
|||||||
LTRIM(RTRIM(a.AttributeCode)) AS BrandCode,
|
LTRIM(RTRIM(a.AttributeCode)) AS BrandCode,
|
||||||
COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) AS BrandName,
|
COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) AS BrandName,
|
||||||
ISNULL(a.IsBlocked, 0) AS IsBlocked
|
ISNULL(a.IsBlocked, 0) AS IsBlocked
|
||||||
FROM dbo.cdItemAttribute a WITH(NOLOCK)
|
FROM dbo.cdItemAttribute a
|
||||||
LEFT JOIN dbo.cdItemAttributeDesc d WITH(NOLOCK)
|
LEFT JOIN dbo.cdItemAttributeDesc d
|
||||||
ON d.ItemTypeCode = a.ItemTypeCode
|
ON d.ItemTypeCode = a.ItemTypeCode
|
||||||
AND d.AttributeTypeCode = a.AttributeTypeCode
|
AND d.AttributeTypeCode = a.AttributeTypeCode
|
||||||
AND d.AttributeCode = a.AttributeCode
|
AND d.AttributeCode = a.AttributeCode
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ WITH BaseCari AS
|
|||||||
PARTITION BY LEFT(CB.CurrAccCode, 8)
|
PARTITION BY LEFT(CB.CurrAccCode, 8)
|
||||||
ORDER BY CB.CurrAccCode
|
ORDER BY CB.CurrAccCode
|
||||||
)
|
)
|
||||||
FROM cdCurrAcc CB WITH (NOLOCK)
|
FROM cdCurrAcc CB
|
||||||
WHERE CB.CurrAccTypeCode IN (1,3)
|
WHERE CB.CurrAccTypeCode IN (1,3)
|
||||||
AND LEFT(CB.CurrAccCode, 8) IN (%s)
|
AND LEFT(CB.CurrAccCode, 8) IN (%s)
|
||||||
),
|
),
|
||||||
@@ -264,7 +264,7 @@ SELECT
|
|||||||
ILCE = ISNULL(CASE WHEN F.CurrAccTypeCode=1 THEN VDesc.VendorAtt07Desc ELSE CDesc.CustomerAtt07Desc END, ''),
|
ILCE = ISNULL(CASE WHEN F.CurrAccTypeCode=1 THEN VDesc.VendorAtt07Desc ELSE CDesc.CustomerAtt07Desc END, ''),
|
||||||
Risk_Durumu = ISNULL(CASE WHEN F.CurrAccTypeCode=1 THEN VDesc.VendorAtt03Desc ELSE CDesc.CustomerAtt03Desc END, '')
|
Risk_Durumu = ISNULL(CASE WHEN F.CurrAccTypeCode=1 THEN VDesc.VendorAtt03Desc ELSE CDesc.CustomerAtt03Desc END, '')
|
||||||
FROM FirstCari F
|
FROM FirstCari F
|
||||||
LEFT JOIN cdCurrAccDesc cd WITH (NOLOCK)
|
LEFT JOIN cdCurrAccDesc cd
|
||||||
ON cd.CurrAccTypeCode = F.CurrAccTypeCode
|
ON cd.CurrAccTypeCode = F.CurrAccTypeCode
|
||||||
AND cd.CurrAccCode = F.CurrAccCode
|
AND cd.CurrAccCode = F.CurrAccCode
|
||||||
AND cd.LangCode = 'TR'
|
AND cd.LangCode = 'TR'
|
||||||
@@ -441,18 +441,18 @@ func loadCariMetaMap(ctx context.Context, lines []mkCariBakiyeLine) (map[string]
|
|||||||
ILCE = ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt07Desc ELSE cad.CustomerAtt07Desc END, ''),
|
ILCE = ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt07Desc ELSE cad.CustomerAtt07Desc END, ''),
|
||||||
TC = ISNULL(c.IdentityNum, ''),
|
TC = ISNULL(c.IdentityNum, ''),
|
||||||
Risk_Durumu = ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt03Desc ELSE cad.CustomerAtt03Desc END, '')
|
Risk_Durumu = ISNULL(CASE WHEN c.CurrAccTypeCode=1 THEN vad.VendorAtt03Desc ELSE cad.CustomerAtt03Desc END, '')
|
||||||
FROM cdCurrAcc c WITH(NOLOCK)
|
FROM cdCurrAcc c
|
||||||
LEFT JOIN cdCurrAccDesc d WITH(NOLOCK)
|
LEFT JOIN cdCurrAccDesc d
|
||||||
ON d.CurrAccTypeCode = c.CurrAccTypeCode
|
ON d.CurrAccTypeCode = c.CurrAccTypeCode
|
||||||
AND d.CurrAccCode = c.CurrAccCode
|
AND d.CurrAccCode = c.CurrAccCode
|
||||||
AND d.LangCode = 'TR'
|
AND d.LangCode = 'TR'
|
||||||
LEFT JOIN VendorAttributes va WITH(NOLOCK)
|
LEFT JOIN VendorAttributes va
|
||||||
ON va.CurrAccTypeCode = c.CurrAccTypeCode
|
ON va.CurrAccTypeCode = c.CurrAccTypeCode
|
||||||
AND va.CurrAccCode = c.CurrAccCode
|
AND va.CurrAccCode = c.CurrAccCode
|
||||||
LEFT JOIN VendorAttributeDescriptions('TR') vad
|
LEFT JOIN VendorAttributeDescriptions('TR') vad
|
||||||
ON vad.CurrAccTypeCode = c.CurrAccTypeCode
|
ON vad.CurrAccTypeCode = c.CurrAccTypeCode
|
||||||
AND vad.CurrAccCode = c.CurrAccCode
|
AND vad.CurrAccCode = c.CurrAccCode
|
||||||
LEFT JOIN CustomerAttributes ca WITH(NOLOCK)
|
LEFT JOIN CustomerAttributes ca
|
||||||
ON ca.CurrAccTypeCode = c.CurrAccTypeCode
|
ON ca.CurrAccTypeCode = c.CurrAccTypeCode
|
||||||
AND ca.CurrAccCode = c.CurrAccCode
|
AND ca.CurrAccCode = c.CurrAccCode
|
||||||
LEFT JOIN CustomerAttributeDescriptions('TR') cad
|
LEFT JOIN CustomerAttributeDescriptions('TR') cad
|
||||||
@@ -530,7 +530,7 @@ func loadGLAccountMap(ctx context.Context, lines []mkCariBakiyeLine) (map[string
|
|||||||
|
|
||||||
query := fmt.Sprintf(`
|
query := fmt.Sprintf(`
|
||||||
SELECT CurrAccTypeCode, CurrAccCode, CompanyCode, GLAccCode
|
SELECT CurrAccTypeCode, CurrAccCode, CompanyCode, GLAccCode
|
||||||
FROM prCurrAccGLAccount WITH(NOLOCK)
|
FROM prCurrAccGLAccount
|
||||||
WHERE PostAccTypeCode = 100
|
WHERE PostAccTypeCode = 100
|
||||||
AND CompanyCode IN (%s)
|
AND CompanyCode IN (%s)
|
||||||
AND (%s)
|
AND (%s)
|
||||||
@@ -561,7 +561,7 @@ func loadGLAccountMap(ctx context.Context, lines []mkCariBakiyeLine) (map[string
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadCompanyMap(ctx context.Context) (map[int]string, error) {
|
func loadCompanyMap(ctx context.Context) (map[int]string, error) {
|
||||||
rows, err := db.MssqlDB.QueryContext(ctx, `SELECT CompanyCode, CompanyName FROM cdCompany WITH(NOLOCK)`)
|
rows, err := db.MssqlDB.QueryContext(ctx, `SELECT CompanyCode, CompanyName FROM cdCompany`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("company map query error: %w", err)
|
return nil, fmt.Errorf("company map query error: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func GetCustomerList(ctx context.Context) ([]models.CustomerList, error) {
|
|||||||
dbo.HG_Temizlik(
|
dbo.HG_Temizlik(
|
||||||
ISNULL((
|
ISNULL((
|
||||||
SELECT AttributeDescription
|
SELECT AttributeDescription
|
||||||
FROM cdCurrAccAttributeDesc WITH(NOLOCK)
|
FROM cdCurrAccAttributeDesc
|
||||||
WHERE CurrAccTypeCode = 3
|
WHERE CurrAccTypeCode = 3
|
||||||
AND AttributeTypeCode = 8
|
AND AttributeTypeCode = 8
|
||||||
AND AttributeCode = f.CustomerAtt08
|
AND AttributeCode = f.CustomerAtt08
|
||||||
@@ -36,7 +36,7 @@ func GetCustomerList(ctx context.Context) ([]models.CustomerList, error) {
|
|||||||
dbo.HG_Temizlik(
|
dbo.HG_Temizlik(
|
||||||
ISNULL((
|
ISNULL((
|
||||||
SELECT AttributeDescription
|
SELECT AttributeDescription
|
||||||
FROM cdCurrAccAttributeDesc WITH(NOLOCK)
|
FROM cdCurrAccAttributeDesc
|
||||||
WHERE CurrAccTypeCode = 3
|
WHERE CurrAccTypeCode = 3
|
||||||
AND AttributeTypeCode = 1
|
AND AttributeTypeCode = 1
|
||||||
AND AttributeCode = f.CustomerAtt01
|
AND AttributeCode = f.CustomerAtt01
|
||||||
@@ -47,7 +47,7 @@ func GetCustomerList(ctx context.Context) ([]models.CustomerList, error) {
|
|||||||
dbo.HG_Temizlik(
|
dbo.HG_Temizlik(
|
||||||
ISNULL((
|
ISNULL((
|
||||||
SELECT AttributeDescription
|
SELECT AttributeDescription
|
||||||
FROM cdCurrAccAttributeDesc WITH(NOLOCK)
|
FROM cdCurrAccAttributeDesc
|
||||||
WHERE CurrAccTypeCode = 3
|
WHERE CurrAccTypeCode = 3
|
||||||
AND AttributeTypeCode = 2
|
AND AttributeTypeCode = 2
|
||||||
AND AttributeCode = f.CustomerAtt02
|
AND AttributeCode = f.CustomerAtt02
|
||||||
@@ -58,7 +58,7 @@ func GetCustomerList(ctx context.Context) ([]models.CustomerList, error) {
|
|||||||
dbo.HG_Temizlik(
|
dbo.HG_Temizlik(
|
||||||
ISNULL((
|
ISNULL((
|
||||||
SELECT AttributeDescription
|
SELECT AttributeDescription
|
||||||
FROM cdCurrAccAttributeDesc WITH(NOLOCK)
|
FROM cdCurrAccAttributeDesc
|
||||||
WHERE CurrAccTypeCode = 3
|
WHERE CurrAccTypeCode = 3
|
||||||
AND AttributeTypeCode = 5
|
AND AttributeTypeCode = 5
|
||||||
AND AttributeCode = f.CustomerAtt05
|
AND AttributeCode = f.CustomerAtt05
|
||||||
@@ -69,7 +69,7 @@ func GetCustomerList(ctx context.Context) ([]models.CustomerList, error) {
|
|||||||
ISNULL(c.CurrencyCode, ''),
|
ISNULL(c.CurrencyCode, ''),
|
||||||
ISNULL((
|
ISNULL((
|
||||||
SELECT TOP 1 CAST(pa.PostalAddressID AS varchar(36))
|
SELECT TOP 1 CAST(pa.PostalAddressID AS varchar(36))
|
||||||
FROM prCurrAccPostalAddress pa WITH(NOLOCK)
|
FROM prCurrAccPostalAddress pa
|
||||||
WHERE pa.CurrAccTypeCode = c.CurrAccTypeCode
|
WHERE pa.CurrAccTypeCode = c.CurrAccTypeCode
|
||||||
AND pa.CurrAccCode = c.CurrAccCode
|
AND pa.CurrAccCode = c.CurrAccCode
|
||||||
AND pa.AddressTypeCode = 2
|
AND pa.AddressTypeCode = 2
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ LEFT JOIN (
|
|||||||
|
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT DISTINCT OrderLineID
|
SELECT DISTINCT OrderLineID
|
||||||
FROM BAGGI_V3.dbo.trInvoiceLine WITH (NOLOCK)
|
FROM BAGGI_V3.dbo.trInvoiceLine
|
||||||
WHERE OrderLineID IS NOT NULL
|
WHERE OrderLineID IS NOT NULL
|
||||||
) il
|
) il
|
||||||
ON il.OrderLineID = l.OrderLineID
|
ON il.OrderLineID = l.OrderLineID
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ SELECT
|
|||||||
- (SUM(inv.ReserveQty1) + SUM(inv.DispOrderQty1) + SUM(inv.PickingQty1)),
|
- (SUM(inv.ReserveQty1) + SUM(inv.DispOrderQty1) + SUM(inv.PickingQty1)),
|
||||||
cdUnitOfMeasure.RoundDigit
|
cdUnitOfMeasure.RoundDigit
|
||||||
) AS Kullanilabilir_Envanter
|
) AS Kullanilabilir_Envanter
|
||||||
FROM cdItem WITH (NOLOCK)
|
FROM cdItem
|
||||||
JOIN cdUnitOfMeasure WITH (NOLOCK)
|
JOIN cdUnitOfMeasure
|
||||||
ON cdItem.UnitOfMeasureCode1 = cdUnitOfMeasure.UnitOfMeasureCode
|
ON cdItem.UnitOfMeasureCode1 = cdUnitOfMeasure.UnitOfMeasureCode
|
||||||
JOIN (
|
JOIN (
|
||||||
SELECT
|
SELECT
|
||||||
@@ -40,19 +40,19 @@ JOIN (
|
|||||||
FROM (
|
FROM (
|
||||||
SELECT 'PickingStates' AS SourceTable, CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
SELECT 'PickingStates' AS SourceTable, CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||||
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, Qty1, 0 AS In_Qty1, 0 AS Out_Qty1
|
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, Qty1, 0 AS In_Qty1, 0 AS Out_Qty1
|
||||||
FROM PickingStates WITH (NOLOCK)
|
FROM PickingStates
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT 'ReserveStates', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
SELECT 'ReserveStates', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||||
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, Qty1, 0, 0
|
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, Qty1, 0, 0
|
||||||
FROM ReserveStates WITH (NOLOCK)
|
FROM ReserveStates
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT 'DispOrderStates', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
SELECT 'DispOrderStates', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||||
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, Qty1, 0, 0
|
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, Qty1, 0, 0
|
||||||
FROM DispOrderStates WITH (NOLOCK)
|
FROM DispOrderStates
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT 'trStock', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
SELECT 'trStock', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||||
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, 0, SUM(In_Qty1), SUM(Out_Qty1)
|
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code, 0, SUM(In_Qty1), SUM(Out_Qty1)
|
||||||
FROM trStock WITH (NOLOCK)
|
FROM trStock
|
||||||
GROUP BY CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
GROUP BY CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||||
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code
|
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code
|
||||||
) AS src
|
) AS src
|
||||||
@@ -61,18 +61,18 @@ JOIN (
|
|||||||
) AS inv
|
) AS inv
|
||||||
ON cdItem.ItemTypeCode = inv.ItemTypeCode
|
ON cdItem.ItemTypeCode = inv.ItemTypeCode
|
||||||
AND cdItem.ItemCode = inv.ItemCode
|
AND cdItem.ItemCode = inv.ItemCode
|
||||||
LEFT JOIN ProductAttributesFilter AS attr WITH (NOLOCK)
|
LEFT JOIN ProductAttributesFilter AS attr
|
||||||
ON attr.ItemCode = inv.ItemCode
|
ON attr.ItemCode = inv.ItemCode
|
||||||
LEFT JOIN bsItemTypeDesc WITH (NOLOCK)
|
LEFT JOIN bsItemTypeDesc
|
||||||
ON bsItemTypeDesc.ItemTypeCode = inv.ItemTypeCode
|
ON bsItemTypeDesc.ItemTypeCode = inv.ItemTypeCode
|
||||||
AND bsItemTypeDesc.LangCode = 'TR'
|
AND bsItemTypeDesc.LangCode = 'TR'
|
||||||
LEFT JOIN cdWarehouseDesc AS wh WITH (NOLOCK)
|
LEFT JOIN cdWarehouseDesc AS wh
|
||||||
ON wh.WarehouseCode = inv.WarehouseCode
|
ON wh.WarehouseCode = inv.WarehouseCode
|
||||||
LEFT JOIN cdItemDesc AS descItem WITH (NOLOCK)
|
LEFT JOIN cdItemDesc AS descItem
|
||||||
ON descItem.ItemTypeCode = inv.ItemTypeCode
|
ON descItem.ItemTypeCode = inv.ItemTypeCode
|
||||||
AND descItem.ItemCode = inv.ItemCode
|
AND descItem.ItemCode = inv.ItemCode
|
||||||
AND descItem.LangCode = 'TR'
|
AND descItem.LangCode = 'TR'
|
||||||
LEFT JOIN cdColorDesc AS descColor WITH (NOLOCK)
|
LEFT JOIN cdColorDesc AS descColor
|
||||||
ON descColor.ColorCode = inv.ColorCode
|
ON descColor.ColorCode = inv.ColorCode
|
||||||
AND descColor.LangCode = 'TR'
|
AND descColor.LangCode = 'TR'
|
||||||
WHERE
|
WHERE
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ SELECT
|
|||||||
T.avg_doc_price,
|
T.avg_doc_price,
|
||||||
CONVERT(varchar(10), T.min_invoice_date, 23) AS min_invoice_date,
|
CONVERT(varchar(10), T.min_invoice_date, 23) AS min_invoice_date,
|
||||||
CONVERT(varchar(10), T.max_invoice_date, 23) AS max_invoice_date
|
CONVERT(varchar(10), T.max_invoice_date, 23) AS max_invoice_date
|
||||||
FROM dbo.cache_last10_avg_purchase_price T WITH (NOLOCK)
|
FROM dbo.cache_last10_avg_purchase_price T
|
||||||
INNER JOIN C
|
INNER JOIN C
|
||||||
ON C.ItemCode = T.ItemCode
|
ON C.ItemCode = T.ItemCode
|
||||||
`, strings.Join(valParts, ","))
|
`, strings.Join(valParts, ","))
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ SELECT
|
|||||||
WHEN ISNULL(L.IsClosed, 0) = 1
|
WHEN ISNULL(L.IsClosed, 0) = 1
|
||||||
OR EXISTS (
|
OR EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM BAGGI_V3.dbo.trInvoiceLine il WITH (NOLOCK)
|
FROM BAGGI_V3.dbo.trInvoiceLine il
|
||||||
WHERE il.OrderLineID = L.OrderLineID
|
WHERE il.OrderLineID = L.OrderLineID
|
||||||
)
|
)
|
||||||
THEN CAST(1 AS bit)
|
THEN CAST(1 AS bit)
|
||||||
@@ -237,10 +237,10 @@ SELECT
|
|||||||
P.ProductAtt38Desc AS Fit1,
|
P.ProductAtt38Desc AS Fit1,
|
||||||
P.ProductAtt39Desc AS Fit2
|
P.ProductAtt39Desc AS Fit2
|
||||||
FROM BAGGI_V3.dbo.trOrderLine AS L
|
FROM BAGGI_V3.dbo.trOrderLine AS L
|
||||||
LEFT JOIN BAGGI_V3.dbo.trOrderLineCurrency AS CD WITH (NOLOCK)
|
LEFT JOIN BAGGI_V3.dbo.trOrderLineCurrency AS CD
|
||||||
ON CD.OrderLineID = L.OrderLineID
|
ON CD.OrderLineID = L.OrderLineID
|
||||||
AND CD.CurrencyCode = ISNULL(NULLIF(LTRIM(RTRIM(L.DocCurrencyCode)), ''), 'TRY')
|
AND CD.CurrencyCode = ISNULL(NULLIF(LTRIM(RTRIM(L.DocCurrencyCode)), ''), 'TRY')
|
||||||
LEFT JOIN BAGGI_V3.dbo.trOrderLineCurrency AS CL WITH (NOLOCK)
|
LEFT JOIN BAGGI_V3.dbo.trOrderLineCurrency AS CL
|
||||||
ON CL.OrderLineID = L.OrderLineID
|
ON CL.OrderLineID = L.OrderLineID
|
||||||
AND CL.CurrencyCode = 'TRY'
|
AND CL.CurrencyCode = 'TRY'
|
||||||
LEFT JOIN ProductFilterWithDescription('TR') AS P
|
LEFT JOIN ProductFilterWithDescription('TR') AS P
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ SELECT
|
|||||||
L.VatCode,
|
L.VatCode,
|
||||||
L.VatRate
|
L.VatRate
|
||||||
FROM BAGGI_V3.dbo.trOrderLine AS L
|
FROM BAGGI_V3.dbo.trOrderLine AS L
|
||||||
LEFT JOIN BAGGI_V3.dbo.trOrderLineCurrency AS CD WITH (NOLOCK)
|
LEFT JOIN BAGGI_V3.dbo.trOrderLineCurrency AS CD
|
||||||
ON CD.OrderLineID = L.OrderLineID
|
ON CD.OrderLineID = L.OrderLineID
|
||||||
AND CD.CurrencyCode = ISNULL(NULLIF(LTRIM(RTRIM(L.DocCurrencyCode)), ''), 'TRY')
|
AND CD.CurrencyCode = ISNULL(NULLIF(LTRIM(RTRIM(L.DocCurrencyCode)), ''), 'TRY')
|
||||||
LEFT JOIN ProductFilterWithDescription('TR') AS P
|
LEFT JOIN ProductFilterWithDescription('TR') AS P
|
||||||
|
|||||||
@@ -492,7 +492,7 @@ func variantCacheKey(item, color, dim2 string) string {
|
|||||||
func loadVariantDim1SetTx(tx *sql.Tx, item, color, dim2 string) (map[string]struct{}, error) {
|
func loadVariantDim1SetTx(tx *sql.Tx, item, color, dim2 string) (map[string]struct{}, error) {
|
||||||
rows, err := tx.Query(`
|
rows, err := tx.Query(`
|
||||||
SELECT ISNULL(LTRIM(RTRIM(V.ItemDim1Code)),'') AS ItemDim1Code
|
SELECT ISNULL(LTRIM(RTRIM(V.ItemDim1Code)),'') AS ItemDim1Code
|
||||||
FROM BAGGI_V3.dbo.prItemVariant V WITH (NOLOCK)
|
FROM BAGGI_V3.dbo.prItemVariant V
|
||||||
WHERE ISNULL(LTRIM(RTRIM(V.ItemCode)),'') = @p1
|
WHERE ISNULL(LTRIM(RTRIM(V.ItemCode)),'') = @p1
|
||||||
AND (
|
AND (
|
||||||
(
|
(
|
||||||
@@ -538,7 +538,7 @@ func loadVariantDim1SetTx(tx *sql.Tx, item, color, dim2 string) (map[string]stru
|
|||||||
func loadVariantDim1SetDB(conn *sql.DB, item, color, dim2 string) (map[string]struct{}, error) {
|
func loadVariantDim1SetDB(conn *sql.DB, item, color, dim2 string) (map[string]struct{}, error) {
|
||||||
rows, err := conn.Query(`
|
rows, err := conn.Query(`
|
||||||
SELECT ISNULL(LTRIM(RTRIM(V.ItemDim1Code)),'') AS ItemDim1Code
|
SELECT ISNULL(LTRIM(RTRIM(V.ItemDim1Code)),'') AS ItemDim1Code
|
||||||
FROM BAGGI_V3.dbo.prItemVariant V WITH (NOLOCK)
|
FROM BAGGI_V3.dbo.prItemVariant V
|
||||||
WHERE ISNULL(LTRIM(RTRIM(V.ItemCode)),'') = @p1
|
WHERE ISNULL(LTRIM(RTRIM(V.ItemCode)),'') = @p1
|
||||||
AND (
|
AND (
|
||||||
(
|
(
|
||||||
@@ -1396,7 +1396,7 @@ WHERE OrderHeaderID=@p1
|
|||||||
err := tx.QueryRow(`
|
err := tx.QueryRow(`
|
||||||
SELECT CASE WHEN EXISTS (
|
SELECT CASE WHEN EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM BAGGI_V3.dbo.trInvoiceLine WITH (NOLOCK)
|
FROM BAGGI_V3.dbo.trInvoiceLine
|
||||||
WHERE OrderLineID=@p1
|
WHERE OrderLineID=@p1
|
||||||
) THEN 1 ELSE 0 END
|
) THEN 1 ELSE 0 END
|
||||||
`, lineID).Scan(&exists)
|
`, lineID).Scan(&exists)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ SELECT
|
|||||||
Inventory.ColorCode AS Renk_Kodu,
|
Inventory.ColorCode AS Renk_Kodu,
|
||||||
ISNULL((
|
ISNULL((
|
||||||
SELECT TOP 1 ColorDescription
|
SELECT TOP 1 ColorDescription
|
||||||
FROM cdColorDesc WITH(NOLOCK)
|
FROM cdColorDesc
|
||||||
WHERE cdColorDesc.ColorCode = Inventory.ColorCode
|
WHERE cdColorDesc.ColorCode = Inventory.ColorCode
|
||||||
AND cdColorDesc.LangCode = N'TR'
|
AND cdColorDesc.LangCode = N'TR'
|
||||||
), '') AS Renk_Aciklamasi,
|
), '') AS Renk_Aciklamasi,
|
||||||
@@ -44,8 +44,8 @@ SELECT
|
|||||||
cdUnitOfMeasure.RoundDigit
|
cdUnitOfMeasure.RoundDigit
|
||||||
) AS Kullanilabilir_Envanter
|
) AS Kullanilabilir_Envanter
|
||||||
|
|
||||||
FROM cdItem WITH (NOLOCK)
|
FROM cdItem
|
||||||
JOIN cdUnitOfMeasure WITH (NOLOCK)
|
JOIN cdUnitOfMeasure
|
||||||
ON cdItem.UnitOfMeasureCode1 = cdUnitOfMeasure.UnitOfMeasureCode
|
ON cdItem.UnitOfMeasureCode1 = cdUnitOfMeasure.UnitOfMeasureCode
|
||||||
JOIN (
|
JOIN (
|
||||||
SELECT
|
SELECT
|
||||||
@@ -74,7 +74,7 @@ JOIN (
|
|||||||
SELECT 'trStock', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
SELECT 'trStock', CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||||
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
|
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code,
|
||||||
0, SUM(In_Qty1), SUM(Out_Qty1)
|
0, SUM(In_Qty1), SUM(Out_Qty1)
|
||||||
FROM trStock WITH (NOLOCK)
|
FROM trStock
|
||||||
GROUP BY CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
GROUP BY CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||||
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code
|
ItemTypeCode, ItemCode, ColorCode, ItemDim1Code, ItemDim2Code, ItemDim3Code
|
||||||
) AS SourceData
|
) AS SourceData
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ JOIN (
|
|||||||
ON h2.OrderHeaderID = l.OrderHeaderID
|
ON h2.OrderHeaderID = l.OrderHeaderID
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT DISTINCT OrderLineID
|
SELECT DISTINCT OrderLineID
|
||||||
FROM BAGGI_V3.dbo.trInvoiceLine WITH (NOLOCK)
|
FROM BAGGI_V3.dbo.trInvoiceLine
|
||||||
WHERE OrderLineID IS NOT NULL
|
WHERE OrderLineID IS NOT NULL
|
||||||
) il
|
) il
|
||||||
ON il.OrderLineID = l.OrderLineID
|
ON il.OrderLineID = l.OrderLineID
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ SELECT
|
|||||||
ISNULL(l.ColorCode,'') AS OldColor,
|
ISNULL(l.ColorCode,'') AS OldColor,
|
||||||
ISNULL((
|
ISNULL((
|
||||||
SELECT TOP 1 LTRIM(RTRIM(cd.ColorDescription))
|
SELECT TOP 1 LTRIM(RTRIM(cd.ColorDescription))
|
||||||
FROM dbo.cdColorDesc cd WITH (NOLOCK)
|
FROM dbo.cdColorDesc cd
|
||||||
WHERE cd.ColorCode = l.ColorCode
|
WHERE cd.ColorCode = l.ColorCode
|
||||||
AND cd.LangCode = N'TR'
|
AND cd.LangCode = N'TR'
|
||||||
), '') AS OldColorDescription,
|
), '') AS OldColorDescription,
|
||||||
@@ -68,7 +68,7 @@ SELECT
|
|||||||
FROM dbo.trOrderLine l
|
FROM dbo.trOrderLine l
|
||||||
JOIN dbo.trOrderHeader h
|
JOIN dbo.trOrderHeader h
|
||||||
ON h.OrderHeaderID = l.OrderHeaderID
|
ON h.OrderHeaderID = l.OrderHeaderID
|
||||||
LEFT JOIN dbo.trOrderLineCurrency c WITH (NOLOCK)
|
LEFT JOIN dbo.trOrderLineCurrency c
|
||||||
ON c.OrderLineID = l.OrderLineID
|
ON c.OrderLineID = l.OrderLineID
|
||||||
AND c.CurrencyCode = ISNULL(h.DocCurrencyCode,'TRY')
|
AND c.CurrencyCode = ISNULL(h.DocCurrencyCode,'TRY')
|
||||||
|
|
||||||
@@ -206,7 +206,7 @@ SELECT
|
|||||||
ISNULL(ItemDim1Code,'') AS ItemDim1Code,
|
ISNULL(ItemDim1Code,'') AS ItemDim1Code,
|
||||||
ISNULL(ItemDim2Code,'') AS ItemDim2Code,
|
ISNULL(ItemDim2Code,'') AS ItemDim2Code,
|
||||||
ISNULL(ItemDim3Code,'') AS ItemDim3Code
|
ISNULL(ItemDim3Code,'') AS ItemDim3Code
|
||||||
FROM dbo.trOrderLine WITH(NOLOCK)
|
FROM dbo.trOrderLine
|
||||||
WHERE OrderHeaderID = @p1
|
WHERE OrderHeaderID = @p1
|
||||||
`, orderHeaderID)
|
`, orderHeaderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -519,7 +519,7 @@ SET
|
|||||||
WHEN NULLIF(@p8,'') IS NULL THEN ItemTaxGrCode
|
WHEN NULLIF(@p8,'') IS NULL THEN ItemTaxGrCode
|
||||||
WHEN EXISTS (
|
WHEN EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM dbo.cdItemTaxGr g WITH(NOLOCK)
|
FROM dbo.cdItemTaxGr g
|
||||||
WHERE LTRIM(RTRIM(g.ItemTaxGrCode)) = LTRIM(RTRIM(@p8))
|
WHERE LTRIM(RTRIM(g.ItemTaxGrCode)) = LTRIM(RTRIM(@p8))
|
||||||
) THEN @p8
|
) THEN @p8
|
||||||
ELSE ItemTaxGrCode
|
ELSE ItemTaxGrCode
|
||||||
@@ -1670,13 +1670,13 @@ func GetOrderProductionLookupOptions(mssql *sql.DB) (models.OrderProductionCdIte
|
|||||||
{"ItemDimTypeCodes", `SELECT
|
{"ItemDimTypeCodes", `SELECT
|
||||||
CAST(t.ItemDimTypeCode AS NVARCHAR(50)) AS Code,
|
CAST(t.ItemDimTypeCode AS NVARCHAR(50)) AS Code,
|
||||||
ISNULL(d.ItemDimTypeDescription, CAST(t.ItemDimTypeCode AS NVARCHAR(50))) AS [Description]
|
ISNULL(d.ItemDimTypeDescription, CAST(t.ItemDimTypeCode AS NVARCHAR(50))) AS [Description]
|
||||||
FROM dbo.bsItemDimType t WITH(NOLOCK)
|
FROM dbo.bsItemDimType t
|
||||||
LEFT JOIN dbo.bsItemDimTypeDesc d WITH(NOLOCK)
|
LEFT JOIN dbo.bsItemDimTypeDesc d
|
||||||
ON d.ItemDimTypeCode = t.ItemDimTypeCode
|
ON d.ItemDimTypeCode = t.ItemDimTypeCode
|
||||||
AND d.LangCode = 'TR'
|
AND d.LangCode = 'TR'
|
||||||
WHERE ISNULL(t.IsBlocked, 0) = 0
|
WHERE ISNULL(t.IsBlocked, 0) = 0
|
||||||
ORDER BY t.ItemDimTypeCode`, &out.ItemDimTypeCodes},
|
ORDER BY t.ItemDimTypeCode`, &out.ItemDimTypeCodes},
|
||||||
{"ProductTypeCodes", `SELECT DISTINCT CAST(ProductTypeCode AS NVARCHAR(50)) AS Code, CAST(ProductTypeCode AS NVARCHAR(50)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE ProductTypeCode IS NOT NULL ORDER BY Code`, &out.ProductTypeCodes},
|
{"ProductTypeCodes", `SELECT DISTINCT CAST(ProductTypeCode AS NVARCHAR(50)) AS Code, CAST(ProductTypeCode AS NVARCHAR(50)) AS [Description] FROM dbo.cdItem WHERE ProductTypeCode IS NOT NULL ORDER BY Code`, &out.ProductTypeCodes},
|
||||||
{"ProductHierarchyIDs", `SELECT
|
{"ProductHierarchyIDs", `SELECT
|
||||||
CAST(h.ProductHierarchyID AS NVARCHAR(50)) AS Code,
|
CAST(h.ProductHierarchyID AS NVARCHAR(50)) AS Code,
|
||||||
LTRIM(RTRIM(
|
LTRIM(RTRIM(
|
||||||
@@ -1688,25 +1688,25 @@ func GetOrderProductionLookupOptions(mssql *sql.DB) (models.OrderProductionCdIte
|
|||||||
END
|
END
|
||||||
)
|
)
|
||||||
)) AS [Description]
|
)) AS [Description]
|
||||||
FROM dbo.dfProductHierarchy h WITH(NOLOCK)
|
FROM dbo.dfProductHierarchy h
|
||||||
LEFT JOIN dbo.cdProductHierarchyLevelDesc d WITH(NOLOCK)
|
LEFT JOIN dbo.cdProductHierarchyLevelDesc d
|
||||||
ON d.ProductHierarchyLevelCode = h.ProductHierarchyLevelCode01
|
ON d.ProductHierarchyLevelCode = h.ProductHierarchyLevelCode01
|
||||||
AND d.LangCode = 'TR'
|
AND d.LangCode = 'TR'
|
||||||
ORDER BY h.ProductHierarchyID`, &out.ProductHierarchyIDs},
|
ORDER BY h.ProductHierarchyID`, &out.ProductHierarchyIDs},
|
||||||
{"UnitOfMeasureCode1List", `SELECT DISTINCT CAST(UnitOfMeasureCode1 AS NVARCHAR(50)) AS Code, CAST(UnitOfMeasureCode1 AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE NULLIF(LTRIM(RTRIM(CAST(UnitOfMeasureCode1 AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.UnitOfMeasureCode1List},
|
{"UnitOfMeasureCode1List", `SELECT DISTINCT CAST(UnitOfMeasureCode1 AS NVARCHAR(50)) AS Code, CAST(UnitOfMeasureCode1 AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WHERE NULLIF(LTRIM(RTRIM(CAST(UnitOfMeasureCode1 AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.UnitOfMeasureCode1List},
|
||||||
{"ItemAccountGrCodes", `SELECT DISTINCT CAST(ItemAccountGrCode AS NVARCHAR(50)) AS Code, CAST(ItemAccountGrCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE NULLIF(LTRIM(RTRIM(CAST(ItemAccountGrCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.ItemAccountGrCodes},
|
{"ItemAccountGrCodes", `SELECT DISTINCT CAST(ItemAccountGrCode AS NVARCHAR(50)) AS Code, CAST(ItemAccountGrCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WHERE NULLIF(LTRIM(RTRIM(CAST(ItemAccountGrCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.ItemAccountGrCodes},
|
||||||
{"ItemTaxGrCodes", `SELECT DISTINCT CAST(ItemTaxGrCode AS NVARCHAR(50)) AS Code, CAST(ItemTaxGrCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE NULLIF(LTRIM(RTRIM(CAST(ItemTaxGrCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.ItemTaxGrCodes},
|
{"ItemTaxGrCodes", `SELECT DISTINCT CAST(ItemTaxGrCode AS NVARCHAR(50)) AS Code, CAST(ItemTaxGrCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WHERE NULLIF(LTRIM(RTRIM(CAST(ItemTaxGrCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.ItemTaxGrCodes},
|
||||||
{"ItemPaymentPlanGrCodes", `SELECT DISTINCT CAST(ItemPaymentPlanGrCode AS NVARCHAR(50)) AS Code, CAST(ItemPaymentPlanGrCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE NULLIF(LTRIM(RTRIM(CAST(ItemPaymentPlanGrCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.ItemPaymentPlanGrCodes},
|
{"ItemPaymentPlanGrCodes", `SELECT DISTINCT CAST(ItemPaymentPlanGrCode AS NVARCHAR(50)) AS Code, CAST(ItemPaymentPlanGrCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WHERE NULLIF(LTRIM(RTRIM(CAST(ItemPaymentPlanGrCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.ItemPaymentPlanGrCodes},
|
||||||
{"ItemDiscountGrCodes", `SELECT DISTINCT CAST(ItemDiscountGrCode AS NVARCHAR(50)) AS Code, CAST(ItemDiscountGrCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE NULLIF(LTRIM(RTRIM(CAST(ItemDiscountGrCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.ItemDiscountGrCodes},
|
{"ItemDiscountGrCodes", `SELECT DISTINCT CAST(ItemDiscountGrCode AS NVARCHAR(50)) AS Code, CAST(ItemDiscountGrCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WHERE NULLIF(LTRIM(RTRIM(CAST(ItemDiscountGrCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.ItemDiscountGrCodes},
|
||||||
{"ItemVendorGrCodes", `SELECT DISTINCT CAST(ItemVendorGrCode AS NVARCHAR(50)) AS Code, CAST(ItemVendorGrCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE NULLIF(LTRIM(RTRIM(CAST(ItemVendorGrCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.ItemVendorGrCodes},
|
{"ItemVendorGrCodes", `SELECT DISTINCT CAST(ItemVendorGrCode AS NVARCHAR(50)) AS Code, CAST(ItemVendorGrCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WHERE NULLIF(LTRIM(RTRIM(CAST(ItemVendorGrCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.ItemVendorGrCodes},
|
||||||
{"PromotionGroupCodes", `SELECT DISTINCT CAST(PromotionGroupCode AS NVARCHAR(50)) AS Code, CAST(PromotionGroupCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE NULLIF(LTRIM(RTRIM(CAST(PromotionGroupCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.PromotionGroupCodes},
|
{"PromotionGroupCodes", `SELECT DISTINCT CAST(PromotionGroupCode AS NVARCHAR(50)) AS Code, CAST(PromotionGroupCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WHERE NULLIF(LTRIM(RTRIM(CAST(PromotionGroupCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.PromotionGroupCodes},
|
||||||
{"ProductCollectionGrCodes", `SELECT DISTINCT CAST(ProductCollectionGrCode AS NVARCHAR(50)) AS Code, CAST(ProductCollectionGrCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE NULLIF(LTRIM(RTRIM(CAST(ProductCollectionGrCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.ProductCollectionGrCodes},
|
{"ProductCollectionGrCodes", `SELECT DISTINCT CAST(ProductCollectionGrCode AS NVARCHAR(50)) AS Code, CAST(ProductCollectionGrCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WHERE NULLIF(LTRIM(RTRIM(CAST(ProductCollectionGrCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.ProductCollectionGrCodes},
|
||||||
{"StorePriceLevelCodes", `SELECT DISTINCT CAST(StorePriceLevelCode AS NVARCHAR(50)) AS Code, CAST(StorePriceLevelCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE NULLIF(LTRIM(RTRIM(CAST(StorePriceLevelCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.StorePriceLevelCodes},
|
{"StorePriceLevelCodes", `SELECT DISTINCT CAST(StorePriceLevelCode AS NVARCHAR(50)) AS Code, CAST(StorePriceLevelCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WHERE NULLIF(LTRIM(RTRIM(CAST(StorePriceLevelCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.StorePriceLevelCodes},
|
||||||
{"PerceptionOfFashionCodes", `SELECT DISTINCT CAST(PerceptionOfFashionCode AS NVARCHAR(50)) AS Code, CAST(PerceptionOfFashionCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE NULLIF(LTRIM(RTRIM(CAST(PerceptionOfFashionCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.PerceptionOfFashionCodes},
|
{"PerceptionOfFashionCodes", `SELECT DISTINCT CAST(PerceptionOfFashionCode AS NVARCHAR(50)) AS Code, CAST(PerceptionOfFashionCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WHERE NULLIF(LTRIM(RTRIM(CAST(PerceptionOfFashionCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.PerceptionOfFashionCodes},
|
||||||
{"CommercialRoleCodes", `SELECT DISTINCT CAST(CommercialRoleCode AS NVARCHAR(50)) AS Code, CAST(CommercialRoleCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE NULLIF(LTRIM(RTRIM(CAST(CommercialRoleCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.CommercialRoleCodes},
|
{"CommercialRoleCodes", `SELECT DISTINCT CAST(CommercialRoleCode AS NVARCHAR(50)) AS Code, CAST(CommercialRoleCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WHERE NULLIF(LTRIM(RTRIM(CAST(CommercialRoleCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.CommercialRoleCodes},
|
||||||
{"StoreCapacityLevelCodes", `SELECT DISTINCT CAST(StoreCapacityLevelCode AS NVARCHAR(50)) AS Code, CAST(StoreCapacityLevelCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE NULLIF(LTRIM(RTRIM(CAST(StoreCapacityLevelCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.StoreCapacityLevelCodes},
|
{"StoreCapacityLevelCodes", `SELECT DISTINCT CAST(StoreCapacityLevelCode AS NVARCHAR(50)) AS Code, CAST(StoreCapacityLevelCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WHERE NULLIF(LTRIM(RTRIM(CAST(StoreCapacityLevelCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.StoreCapacityLevelCodes},
|
||||||
{"CustomsTariffNumbers", `SELECT DISTINCT CAST(CustomsTariffNumberCode AS NVARCHAR(50)) AS Code, CAST(CustomsTariffNumberCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE NULLIF(LTRIM(RTRIM(CAST(CustomsTariffNumberCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.CustomsTariffNumbers},
|
{"CustomsTariffNumbers", `SELECT DISTINCT CAST(CustomsTariffNumberCode AS NVARCHAR(50)) AS Code, CAST(CustomsTariffNumberCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WHERE NULLIF(LTRIM(RTRIM(CAST(CustomsTariffNumberCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.CustomsTariffNumbers},
|
||||||
{"CompanyCodes", `SELECT DISTINCT CAST(CompanyCode AS NVARCHAR(50)) AS Code, CAST(CompanyCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WITH(NOLOCK) WHERE NULLIF(LTRIM(RTRIM(CAST(CompanyCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.CompanyCodes},
|
{"CompanyCodes", `SELECT DISTINCT CAST(CompanyCode AS NVARCHAR(50)) AS Code, CAST(CompanyCode AS NVARCHAR(200)) AS [Description] FROM dbo.cdItem WHERE NULLIF(LTRIM(RTRIM(CAST(CompanyCode AS NVARCHAR(200)))), '') IS NOT NULL ORDER BY Code`, &out.CompanyCodes},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pair := range queryPairs {
|
for _, pair := range queryPairs {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func buildPiyasaExistsForCariCode(ctx context.Context, cariCodeExpr string) (str
|
|||||||
return fmt.Sprintf(`
|
return fmt.Sprintf(`
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM CustomerAttributesFilter PF WITH(NOLOCK)
|
FROM CustomerAttributesFilter PF
|
||||||
WHERE (PF.CurrAccCode = %s OR LEFT(PF.CurrAccCode, 8) = LEFT(%s, 8))
|
WHERE (PF.CurrAccCode = %s OR LEFT(PF.CurrAccCode, 8) = LEFT(%s, 8))
|
||||||
AND %s
|
AND %s
|
||||||
)`, cariCodeExpr, cariCodeExpr, inClause), nil
|
)`, cariCodeExpr, cariCodeExpr, inClause), nil
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,760 @@
|
|||||||
|
package queries
|
||||||
|
|
||||||
|
func productPerformanceSalesSQL() string {
|
||||||
|
return `
|
||||||
|
;WITH SalesLines AS (
|
||||||
|
SELECT
|
||||||
|
I.InvoiceLineID,
|
||||||
|
SalesDate = CAST(I.InvoiceDate AS date),
|
||||||
|
I.InvoiceHeaderID,
|
||||||
|
I.InvoiceNumber,
|
||||||
|
ProductCode = UPPER(LTRIM(RTRIM(I.ItemCode))),
|
||||||
|
ColorCode = UPPER(LTRIM(RTRIM(ISNULL(I.ColorCode, '')))),
|
||||||
|
YakaKodu = UPPER(LTRIM(RTRIM(ISNULL(I.ItemDim2Code, '')))),
|
||||||
|
ItemDescription = dbo.HG_Temizlik(ISNULL((
|
||||||
|
SELECT ItemDescription
|
||||||
|
FROM cdItemDesc WITH(NOLOCK)
|
||||||
|
WHERE cdItemDesc.ItemTypeCode = I.ItemTypeCode
|
||||||
|
AND cdItemDesc.ItemCode = I.ItemCode
|
||||||
|
AND cdItemDesc.LangCode = 'TR'
|
||||||
|
), SPACE(0))),
|
||||||
|
UrunAnaGrubu = dbo.HG_Temizlik(ISNULL(PAF.ProductAtt01, SPACE(0))),
|
||||||
|
UrunAltGrubu = dbo.HG_Temizlik(ISNULL(PAF.ProductAtt02, SPACE(0))),
|
||||||
|
Kategori = dbo.HG_Temizlik(ISNULL(PAF.ProductAtt44, SPACE(0))),
|
||||||
|
UrunIlkGrubu = dbo.HG_Temizlik(ISNULL(PAF.ProductAtt42, SPACE(0))),
|
||||||
|
AskiliYan = dbo.HG_Temizlik(ISNULL(PAF.ProductAtt45, SPACE(0))),
|
||||||
|
ChannelCode = CASE
|
||||||
|
WHEN I.ProcessCode IN ('R') THEN dbo.HG_Temizlik(ISNULL((
|
||||||
|
SELECT OfficeDescription
|
||||||
|
FROM cdOfficeDesc WITH(NOLOCK)
|
||||||
|
WHERE cdOfficeDesc.OfficeCode = I.OfficeCode
|
||||||
|
AND cdOfficeDesc.LangCode = 'TR'
|
||||||
|
), SPACE(0)))
|
||||||
|
ELSE dbo.HG_Temizlik(ISNULL((
|
||||||
|
SELECT AttributeDescription
|
||||||
|
FROM cdCurrAccAttributeDesc WITH(NOLOCK)
|
||||||
|
WHERE cdCurrAccAttributeDesc.CurrAccTypeCode = 3
|
||||||
|
AND AttributeTypeCode = 1
|
||||||
|
AND CAF.CustomerAtt01 = AttributeCode
|
||||||
|
AND cdCurrAccAttributeDesc.LangCode = 'TR'
|
||||||
|
), SPACE(0)))
|
||||||
|
END,
|
||||||
|
CustomerCountry = dbo.HG_Temizlik(ISNULL((
|
||||||
|
SELECT AttributeDescription
|
||||||
|
FROM cdCurrAccAttributeDesc WITH(NOLOCK)
|
||||||
|
WHERE cdCurrAccAttributeDesc.CurrAccTypeCode = 3
|
||||||
|
AND AttributeTypeCode = 5
|
||||||
|
AND CAF.CustomerAtt05 = AttributeCode
|
||||||
|
AND cdCurrAccAttributeDesc.LangCode = 'TR'
|
||||||
|
), SPACE(0))),
|
||||||
|
CustomerSegment = dbo.HG_Temizlik(ISNULL((
|
||||||
|
SELECT AttributeDescription
|
||||||
|
FROM cdCurrAccAttributeDesc WITH(NOLOCK)
|
||||||
|
WHERE cdCurrAccAttributeDesc.CurrAccTypeCode = 3
|
||||||
|
AND AttributeTypeCode = 11
|
||||||
|
AND CAF.CustomerAtt11 = AttributeCode
|
||||||
|
AND cdCurrAccAttributeDesc.LangCode = 'TR'
|
||||||
|
), SPACE(0))),
|
||||||
|
CustomerCode = CASE
|
||||||
|
WHEN I.ProcessCode = 'R' THEN dbo.HG_Temizlik(ISNULL(I.StoreCode, ''))
|
||||||
|
ELSE dbo.HG_Temizlik(ISNULL(I.CurrAccCode, ''))
|
||||||
|
END,
|
||||||
|
CustomerDescription = CASE
|
||||||
|
WHEN I.ProcessCode = 'R' THEN dbo.HG_Temizlik(ISNULL((
|
||||||
|
SELECT CurrAccDescription
|
||||||
|
FROM cdCurrAccDesc WITH(NOLOCK)
|
||||||
|
WHERE cdCurrAccDesc.CurrAccTypeCode = 5
|
||||||
|
AND cdCurrAccDesc.CurrAccCode = I.StoreCode
|
||||||
|
AND cdCurrAccDesc.LangCode = 'TR'
|
||||||
|
), SPACE(0)))
|
||||||
|
ELSE dbo.HG_Temizlik(ISNULL((
|
||||||
|
SELECT CurrAccDescription
|
||||||
|
FROM cdCurrAccDesc WITH(NOLOCK)
|
||||||
|
WHERE cdCurrAccDesc.CurrAccTypeCode = I.CurrAccTypeCode
|
||||||
|
AND cdCurrAccDesc.CurrAccCode = I.CurrAccCode
|
||||||
|
AND cdCurrAccDesc.LangCode = 'TR'
|
||||||
|
), SPACE(0)))
|
||||||
|
END,
|
||||||
|
Qty1 = ISNULL(I.Qty1, 0),
|
||||||
|
TLAmount = CASE
|
||||||
|
WHEN Amount.DocCurrencyCode = 'TRY' THEN Amount.DocNetAmount
|
||||||
|
WHEN CurRate.Rate > 0 THEN Amount.DocNetAmount * CurRate.Rate
|
||||||
|
WHEN ISNULL(I.Loc_ExchangeRate, 0) > 0 THEN Amount.DocNetAmount * I.Loc_ExchangeRate
|
||||||
|
ELSE 0
|
||||||
|
END,
|
||||||
|
USDAmount = CASE
|
||||||
|
WHEN Amount.DocCurrencyCode = 'USD' THEN Amount.DocNetAmount
|
||||||
|
WHEN Amount.DocCurrencyCode = 'TRY' AND UsdRate.Rate > 0 THEN Amount.DocNetAmount / UsdRate.Rate
|
||||||
|
WHEN CurRate.Rate > 0 AND UsdRate.Rate > 0 THEN (Amount.DocNetAmount * CurRate.Rate) / UsdRate.Rate
|
||||||
|
WHEN ISNULL(I.Loc_ExchangeRate, 0) > 0 AND UsdRate.Rate > 0 THEN (Amount.DocNetAmount * I.Loc_ExchangeRate) / UsdRate.Rate
|
||||||
|
ELSE 0
|
||||||
|
END,
|
||||||
|
RN = ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY I.InvoiceLineID
|
||||||
|
ORDER BY I.InvoiceDate DESC, I.InvoiceNumber
|
||||||
|
)
|
||||||
|
FROM AllInvoicesWithAttributes I WITH(NOLOCK)
|
||||||
|
LEFT OUTER JOIN CustomerAttributesFilter CAF WITH(NOLOCK)
|
||||||
|
ON CAF.CurrAccTypeCode = I.CurrAccTypeCode
|
||||||
|
AND CAF.CurrAccCode = I.CurrAccCode
|
||||||
|
LEFT OUTER JOIN ProductAttributesFilter PAF WITH(NOLOCK)
|
||||||
|
ON PAF.ItemCode = I.ItemCode
|
||||||
|
LEFT OUTER JOIN dbo.trInvoiceLineCurrency ILC WITH(NOLOCK)
|
||||||
|
ON ILC.InvoiceLineID = I.InvoiceLineID
|
||||||
|
AND UPPER(LTRIM(RTRIM(ILC.CurrencyCode))) = UPPER(LTRIM(RTRIM(ISNULL(NULLIF(LTRIM(RTRIM(I.Doc_CurrencyCode)), ''), 'TRY'))))
|
||||||
|
OUTER APPLY (
|
||||||
|
SELECT
|
||||||
|
DocCurrencyCode = UPPER(LTRIM(RTRIM(ISNULL(NULLIF(LTRIM(RTRIM(I.Doc_CurrencyCode)), ''), ISNULL(NULLIF(LTRIM(RTRIM(ILC.CurrencyCode)), ''), 'TRY'))))),
|
||||||
|
DocNetAmount = COALESCE(
|
||||||
|
NULLIF(ISNULL(ILC.NetAmount, 0), 0),
|
||||||
|
NULLIF(ISNULL(I.Doc_Amount, 0), 0),
|
||||||
|
NULLIF(ISNULL(I.Doc_Price, 0) * ISNULL(I.Qty1, 0), 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
) Amount
|
||||||
|
OUTER APPLY (
|
||||||
|
SELECT TOP 1 Rate
|
||||||
|
FROM AllExchangeRates WITH(NOLOCK)
|
||||||
|
WHERE CurrencyCode = 'USD'
|
||||||
|
AND RelationCurrencyCode = 'TRY'
|
||||||
|
AND ExchangeTypeCode = 6
|
||||||
|
AND Rate > 0
|
||||||
|
AND Date <= CAST(I.InvoiceDate AS date)
|
||||||
|
ORDER BY Date DESC
|
||||||
|
) UsdRate
|
||||||
|
OUTER APPLY (
|
||||||
|
SELECT TOP 1 Rate
|
||||||
|
FROM AllExchangeRates WITH(NOLOCK)
|
||||||
|
WHERE CurrencyCode = Amount.DocCurrencyCode
|
||||||
|
AND RelationCurrencyCode = 'TRY'
|
||||||
|
AND ExchangeTypeCode = 6
|
||||||
|
AND Rate > 0
|
||||||
|
AND Date <= CAST(I.InvoiceDate AS date)
|
||||||
|
ORDER BY Date DESC
|
||||||
|
) CurRate
|
||||||
|
WHERE I.InvoiceDate >= @p1
|
||||||
|
AND I.InvoiceDate < DATEADD(DAY, 1, @p2)
|
||||||
|
AND I.ItemTypeCode = 1
|
||||||
|
AND LEN(LTRIM(RTRIM(I.ItemCode))) = 13
|
||||||
|
AND (@p3 = '' OR UPPER(LTRIM(RTRIM(I.ItemCode))) LIKE @p3 + '%')
|
||||||
|
AND (
|
||||||
|
(I.CompanyCode = 1 AND I.ATAtt01 IN (1,2) AND I.ProcessCode IN ('WS','R'))
|
||||||
|
OR (I.CompanyCode = 4 AND I.ProcessCode = 'R')
|
||||||
|
)
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM trDebitHeader DH WITH(NOLOCK)
|
||||||
|
INNER JOIN trDebitLine DL WITH(NOLOCK)
|
||||||
|
ON DL.DebitHeaderID = DH.DebitHeaderID
|
||||||
|
WHERE DH.ApplicationCode = 'Invoi'
|
||||||
|
AND DH.ApplicationID = I.InvoiceHeaderID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
SalesDate,
|
||||||
|
ProductCode,
|
||||||
|
ColorCode,
|
||||||
|
YakaKodu,
|
||||||
|
ItemDescription = MAX(ItemDescription),
|
||||||
|
Kategori = MAX(Kategori),
|
||||||
|
Seri = '',
|
||||||
|
YasGrubu = '',
|
||||||
|
AskiliYan = ISNULL(NULLIF(MAX(AskiliYan), '-'), ''),
|
||||||
|
UrunIlkGrubu = MAX(UrunIlkGrubu),
|
||||||
|
UrunAnaGrubu = MAX(UrunAnaGrubu),
|
||||||
|
UrunAltGrubu = MAX(UrunAltGrubu),
|
||||||
|
MarketKey = CONCAT(MAX(Kategori), '|', MAX(UrunIlkGrubu), '|', ISNULL(NULLIF(MAX(AskiliYan), '-'), ''), '|', MAX(UrunAnaGrubu), '|', MAX(UrunAltGrubu), '|', ChannelCode),
|
||||||
|
ChannelCode,
|
||||||
|
CustomerCountry = ISNULL(NULLIF(CustomerCountry, ''), '-'),
|
||||||
|
CustomerSegment = ISNULL(NULLIF(CustomerSegment, ''), '-'),
|
||||||
|
CustomerCode = ISNULL(NULLIF(CustomerCode, ''), '-'),
|
||||||
|
CustomerDescription = MAX(ISNULL(NULLIF(CustomerDescription, ''), '-')),
|
||||||
|
SalesQty = ISNULL(SUM(Qty1), 0),
|
||||||
|
SalesTL = ISNULL(SUM(TLAmount), 0),
|
||||||
|
SalesUSD = ISNULL(SUM(USDAmount), 0),
|
||||||
|
AvgPriceUSD = ISNULL(SUM(USDAmount) / NULLIF(SUM(Qty1), 0), 0),
|
||||||
|
InvoiceLineCount = COUNT(DISTINCT InvoiceLineID),
|
||||||
|
InvoiceCount = COUNT(DISTINCT InvoiceHeaderID),
|
||||||
|
CustomerCount = COUNT(DISTINCT CustomerCode),
|
||||||
|
LastRefNumber = MAX(InvoiceNumber)
|
||||||
|
FROM SalesLines
|
||||||
|
WHERE RN = 1
|
||||||
|
GROUP BY SalesDate, ProductCode, ColorCode, YakaKodu, ChannelCode, CustomerCountry, CustomerSegment, CustomerCode
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceStockSQLUnused() string {
|
||||||
|
return `
|
||||||
|
;WITH ActiveWarehouses AS (
|
||||||
|
SELECT WarehouseCode
|
||||||
|
FROM (VALUES
|
||||||
|
('1-0-12'),('1.01.2014'),('1.02.2005'),('1.02.2004'),('1-0-43'),
|
||||||
|
('4.02.2001'),('1-0-55'),('1.01.2003'),('1-0-21'),('1-0-2'),
|
||||||
|
('1.01.2004'),('1-0-49'),('1-0-37'),('1-0-29'),('1-0-28'),
|
||||||
|
('1-0-10'),('100'),('1.02.2006'),('1-0-42'),('1.01.2002'),
|
||||||
|
('1-0-52')
|
||||||
|
) W(WarehouseCode)
|
||||||
|
),
|
||||||
|
Raw AS (
|
||||||
|
SELECT
|
||||||
|
ProductCode = UPPER(LTRIM(RTRIM(S.ItemCode))),
|
||||||
|
ColorCode = UPPER(LTRIM(RTRIM(ISNULL(S.ColorCode, '')))),
|
||||||
|
YakaKodu = UPPER(LTRIM(RTRIM(ISNULL(S.ItemDim2Code, '')))),
|
||||||
|
MovementDate = CAST(S.DocumentDate AS date),
|
||||||
|
ProcessCode = CASE
|
||||||
|
WHEN LTRIM(RTRIM(ISNULL(S.ProcessCode, ''))) <> '' THEN LTRIM(RTRIM(S.ProcessCode))
|
||||||
|
ELSE LTRIM(RTRIM(ISNULL(S.InnerProcessCode, '')))
|
||||||
|
END,
|
||||||
|
InQty = SUM(S.In_Qty1),
|
||||||
|
OutQty = SUM(S.Out_Qty1),
|
||||||
|
NetQty = SUM(S.InventoryQty1)
|
||||||
|
FROM StockWithCost S WITH(NOLOCK)
|
||||||
|
INNER JOIN ActiveWarehouses W
|
||||||
|
ON W.WarehouseCode = LTRIM(RTRIM(S.WarehouseCode))
|
||||||
|
WHERE S.ItemTypeCode = 1
|
||||||
|
AND LEN(LTRIM(RTRIM(S.ItemCode))) = 13
|
||||||
|
AND (@p3 = '' OR UPPER(LTRIM(RTRIM(S.ItemCode))) LIKE @p3 + '%')
|
||||||
|
AND S.DocumentDate < DATEADD(DAY, 1, @p2)
|
||||||
|
GROUP BY
|
||||||
|
S.ItemCode,
|
||||||
|
S.ColorCode,
|
||||||
|
S.ItemDim2Code,
|
||||||
|
CAST(S.DocumentDate AS date),
|
||||||
|
CASE
|
||||||
|
WHEN LTRIM(RTRIM(ISNULL(S.ProcessCode, ''))) <> '' THEN LTRIM(RTRIM(S.ProcessCode))
|
||||||
|
ELSE LTRIM(RTRIM(ISNULL(S.InnerProcessCode, '')))
|
||||||
|
END
|
||||||
|
),
|
||||||
|
Opening AS (
|
||||||
|
SELECT
|
||||||
|
StockDate = @p1,
|
||||||
|
ProductCode,
|
||||||
|
ColorCode,
|
||||||
|
YakaKodu,
|
||||||
|
NetQty = SUM(NetQty),
|
||||||
|
InQty = CAST(0 AS decimal(18,4)),
|
||||||
|
OutQty = CAST(0 AS decimal(18,4)),
|
||||||
|
KpiInQty = CAST(0 AS decimal(18,4)),
|
||||||
|
KpiOutQty = CAST(0 AS decimal(18,4)),
|
||||||
|
SalesMovementQty = CAST(0 AS decimal(18,4)),
|
||||||
|
ProductionInQty = CAST(0 AS decimal(18,4)),
|
||||||
|
PurchaseInQty = CAST(0 AS decimal(18,4)),
|
||||||
|
ConsumptionOutQty = CAST(0 AS decimal(18,4)),
|
||||||
|
CountDiffQty = CAST(0 AS decimal(18,4))
|
||||||
|
FROM Raw
|
||||||
|
WHERE MovementDate < @p1
|
||||||
|
GROUP BY ProductCode, ColorCode, YakaKodu
|
||||||
|
),
|
||||||
|
Daily AS (
|
||||||
|
SELECT
|
||||||
|
StockDate = MovementDate,
|
||||||
|
ProductCode,
|
||||||
|
ColorCode,
|
||||||
|
YakaKodu,
|
||||||
|
NetQty = SUM(NetQty),
|
||||||
|
InQty = SUM(InQty),
|
||||||
|
OutQty = SUM(OutQty),
|
||||||
|
KpiInQty = SUM(CASE WHEN ProcessCode IN ('OP','BP','CI') THEN InQty ELSE 0 END),
|
||||||
|
KpiOutQty = SUM(CASE WHEN ProcessCode IN ('R','WS','OC','CO') THEN OutQty ELSE 0 END),
|
||||||
|
SalesMovementQty = SUM(CASE WHEN ProcessCode IN ('R','WS') THEN OutQty ELSE 0 END),
|
||||||
|
ProductionInQty = SUM(CASE WHEN ProcessCode = 'OP' THEN InQty ELSE 0 END),
|
||||||
|
PurchaseInQty = SUM(CASE WHEN ProcessCode = 'BP' THEN InQty ELSE 0 END),
|
||||||
|
ConsumptionOutQty = SUM(CASE WHEN ProcessCode = 'OC' THEN OutQty ELSE 0 END),
|
||||||
|
CountDiffQty = SUM(CASE WHEN ProcessCode IN ('CO','CI') THEN NetQty ELSE 0 END)
|
||||||
|
FROM Raw
|
||||||
|
WHERE MovementDate BETWEEN @p1 AND @p2
|
||||||
|
GROUP BY MovementDate, ProductCode, ColorCode, YakaKodu
|
||||||
|
),
|
||||||
|
Series AS (
|
||||||
|
SELECT * FROM Opening
|
||||||
|
UNION ALL
|
||||||
|
SELECT * FROM Daily
|
||||||
|
),
|
||||||
|
Collapsed AS (
|
||||||
|
SELECT
|
||||||
|
StockDate,
|
||||||
|
ProductCode,
|
||||||
|
ColorCode,
|
||||||
|
YakaKodu,
|
||||||
|
NetQty = SUM(NetQty),
|
||||||
|
InQty = SUM(InQty),
|
||||||
|
OutQty = SUM(OutQty),
|
||||||
|
KpiInQty = SUM(KpiInQty),
|
||||||
|
KpiOutQty = SUM(KpiOutQty),
|
||||||
|
SalesMovementQty = SUM(SalesMovementQty),
|
||||||
|
ProductionInQty = SUM(ProductionInQty),
|
||||||
|
PurchaseInQty = SUM(PurchaseInQty),
|
||||||
|
ConsumptionOutQty = SUM(ConsumptionOutQty),
|
||||||
|
CountDiffQty = SUM(CountDiffQty)
|
||||||
|
FROM Series
|
||||||
|
GROUP BY StockDate, ProductCode, ColorCode, YakaKodu
|
||||||
|
),
|
||||||
|
Running AS (
|
||||||
|
SELECT
|
||||||
|
StockDate,
|
||||||
|
ProductCode,
|
||||||
|
ColorCode,
|
||||||
|
YakaKodu,
|
||||||
|
StockQty = SUM(NetQty) OVER (
|
||||||
|
PARTITION BY ProductCode, ColorCode, YakaKodu
|
||||||
|
ORDER BY StockDate
|
||||||
|
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
|
||||||
|
),
|
||||||
|
InQty,
|
||||||
|
OutQty,
|
||||||
|
KpiInQty,
|
||||||
|
KpiOutQty,
|
||||||
|
SalesMovementQty,
|
||||||
|
ProductionInQty,
|
||||||
|
PurchaseInQty,
|
||||||
|
ConsumptionOutQty,
|
||||||
|
CountDiffQty
|
||||||
|
FROM Collapsed
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
StockDate,
|
||||||
|
ProductCode,
|
||||||
|
ColorCode,
|
||||||
|
YakaKodu,
|
||||||
|
StockQty,
|
||||||
|
InQty,
|
||||||
|
OutQty,
|
||||||
|
KpiInQty,
|
||||||
|
KpiOutQty,
|
||||||
|
SalesMovementQty,
|
||||||
|
ProductionInQty,
|
||||||
|
PurchaseInQty,
|
||||||
|
ConsumptionOutQty,
|
||||||
|
CountDiffQty
|
||||||
|
FROM Running
|
||||||
|
WHERE StockDate BETWEEN @p1 AND @p2
|
||||||
|
ORDER BY StockDate, ProductCode, ColorCode, YakaKodu
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformancePriceSQL() string {
|
||||||
|
return `
|
||||||
|
;WITH ProductCodes AS (
|
||||||
|
SELECT DISTINCT ItemCode
|
||||||
|
FROM cdItem WITH(NOLOCK)
|
||||||
|
WHERE ItemTypeCode = 1
|
||||||
|
AND LEN(LTRIM(RTRIM(ItemCode))) = 13
|
||||||
|
AND (@p1 = '' OR UPPER(LTRIM(RTRIM(ItemCode))) LIKE @p1 + '%')
|
||||||
|
UNION
|
||||||
|
SELECT DISTINCT ItemCode
|
||||||
|
FROM trPriceListLine WITH(NOLOCK)
|
||||||
|
WHERE ItemTypeCode = 1
|
||||||
|
AND LEN(LTRIM(RTRIM(ItemCode))) = 13
|
||||||
|
AND (@p1 = '' OR UPPER(LTRIM(RTRIM(ItemCode))) LIKE @p1 + '%')
|
||||||
|
UNION
|
||||||
|
SELECT DISTINCT ItemCode
|
||||||
|
FROM prItemBasePrice WITH(NOLOCK)
|
||||||
|
WHERE ItemTypeCode = 1
|
||||||
|
AND LEN(LTRIM(RTRIM(ItemCode))) = 13
|
||||||
|
AND (@p1 = '' OR UPPER(LTRIM(RTRIM(ItemCode))) LIKE @p1 + '%')
|
||||||
|
),
|
||||||
|
LatestPrice AS (
|
||||||
|
SELECT
|
||||||
|
p.ItemCode,
|
||||||
|
DocCurrencyCode = LTRIM(RTRIM(p.DocCurrencyCode)),
|
||||||
|
p.Price,
|
||||||
|
rn = ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY p.ItemCode, LTRIM(RTRIM(p.DocCurrencyCode))
|
||||||
|
ORDER BY p.ValidDate DESC, p.ValidTime DESC, p.LastUpdatedDate DESC
|
||||||
|
)
|
||||||
|
FROM trPriceListLine p WITH(NOLOCK)
|
||||||
|
INNER JOIN ProductCodes pc ON pc.ItemCode = p.ItemCode
|
||||||
|
WHERE p.ItemTypeCode = 1
|
||||||
|
AND ISNULL(p.IsDisabled, 0) = 0
|
||||||
|
AND LTRIM(RTRIM(p.DocCurrencyCode)) IN ('USD', 'TRY')
|
||||||
|
AND (
|
||||||
|
(LTRIM(RTRIM(p.DocCurrencyCode)) = 'USD' AND LTRIM(RTRIM(p.PriceGroupCode)) = 'TM-USD')
|
||||||
|
OR (LTRIM(RTRIM(p.DocCurrencyCode)) = 'TRY' AND LTRIM(RTRIM(p.PriceGroupCode)) = 'TM-TRY')
|
||||||
|
)
|
||||||
|
AND p.Price > 0
|
||||||
|
),
|
||||||
|
BasePrice AS (
|
||||||
|
SELECT
|
||||||
|
ItemCode,
|
||||||
|
BasePriceUsd = MAX(CASE WHEN DocCurrencyCode = 'USD' THEN Price END),
|
||||||
|
BasePriceTry = MAX(CASE WHEN DocCurrencyCode = 'TRY' THEN Price END)
|
||||||
|
FROM LatestPrice
|
||||||
|
WHERE rn = 1
|
||||||
|
GROUP BY ItemCode
|
||||||
|
),
|
||||||
|
Cost AS (
|
||||||
|
SELECT
|
||||||
|
b.ItemCode,
|
||||||
|
CostPriceUsd = CAST(b.Price AS decimal(18,2)),
|
||||||
|
LastPricingDate = CAST(b.PriceDate AS date),
|
||||||
|
rn = ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY b.ItemCode
|
||||||
|
ORDER BY b.PriceDate DESC, b.LastUpdatedDate DESC
|
||||||
|
)
|
||||||
|
FROM prItemBasePrice b WITH(NOLOCK)
|
||||||
|
INNER JOIN ProductCodes pc ON pc.ItemCode = b.ItemCode
|
||||||
|
WHERE b.ItemTypeCode = 1
|
||||||
|
AND b.BasePriceCode = 1
|
||||||
|
AND LTRIM(RTRIM(b.CurrencyCode)) = 'USD'
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
UPPER(LTRIM(RTRIM(pc.ItemCode))) AS ItemCode,
|
||||||
|
ItemDescription = dbo.HG_Temizlik(ISNULL((
|
||||||
|
SELECT ItemDescription
|
||||||
|
FROM cdItemDesc WITH(NOLOCK)
|
||||||
|
WHERE cdItemDesc.ItemTypeCode = 1
|
||||||
|
AND cdItemDesc.ItemCode = pc.ItemCode
|
||||||
|
AND cdItemDesc.LangCode = 'TR'
|
||||||
|
), SPACE(0))),
|
||||||
|
Kategori = dbo.HG_Temizlik(ISNULL(paf.ProductAtt44, SPACE(0))),
|
||||||
|
AskiliYan = dbo.HG_Temizlik(ISNULL(paf.ProductAtt45, SPACE(0))),
|
||||||
|
UrunIlkGrubu = dbo.HG_Temizlik(ISNULL(paf.ProductAtt42, SPACE(0))),
|
||||||
|
UrunAnaGrubu = dbo.HG_Temizlik(ISNULL(paf.ProductAtt01, SPACE(0))),
|
||||||
|
UrunAltGrubu = dbo.HG_Temizlik(ISNULL(paf.ProductAtt02, SPACE(0))),
|
||||||
|
CostPriceUsd = ISNULL(c.CostPriceUsd, 0),
|
||||||
|
BasePriceUsd = ISNULL(bp.BasePriceUsd, 0),
|
||||||
|
BasePriceTry = ISNULL(bp.BasePriceTry, 0),
|
||||||
|
c.LastPricingDate
|
||||||
|
FROM ProductCodes pc
|
||||||
|
LEFT JOIN ProductAttributesFilter paf WITH(NOLOCK)
|
||||||
|
ON paf.ItemCode = pc.ItemCode
|
||||||
|
LEFT JOIN BasePrice bp ON bp.ItemCode = pc.ItemCode
|
||||||
|
LEFT JOIN Cost c ON c.ItemCode = pc.ItemCode AND c.rn = 1
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceKPISQL() string {
|
||||||
|
return `
|
||||||
|
WITH LatestStock AS (
|
||||||
|
SELECT DISTINCT ON (product_code, color_code, yaka_kodu)
|
||||||
|
product_code, color_code, yaka_kodu, stock_qty
|
||||||
|
FROM mk_product_performance_stock_daily
|
||||||
|
WHERE stock_date <= $1
|
||||||
|
ORDER BY product_code, color_code, yaka_kodu, stock_date DESC
|
||||||
|
),
|
||||||
|
Stock90Start AS (
|
||||||
|
SELECT DISTINCT ON (product_code, color_code, yaka_kodu)
|
||||||
|
product_code, color_code, yaka_kodu, stock_qty
|
||||||
|
FROM mk_product_performance_stock_daily
|
||||||
|
WHERE stock_date <= $1::date - INTERVAL '89 days'
|
||||||
|
ORDER BY product_code, color_code, yaka_kodu, stock_date DESC
|
||||||
|
),
|
||||||
|
Stock180Start AS (
|
||||||
|
SELECT DISTINCT ON (product_code, color_code, yaka_kodu)
|
||||||
|
product_code, color_code, yaka_kodu, stock_qty
|
||||||
|
FROM mk_product_performance_stock_daily
|
||||||
|
WHERE stock_date <= $1::date - INTERVAL '179 days'
|
||||||
|
ORDER BY product_code, color_code, yaka_kodu, stock_date DESC
|
||||||
|
),
|
||||||
|
Stock365Start AS (
|
||||||
|
SELECT DISTINCT ON (product_code, color_code, yaka_kodu)
|
||||||
|
product_code, color_code, yaka_kodu, stock_qty
|
||||||
|
FROM mk_product_performance_stock_daily
|
||||||
|
WHERE stock_date <= $1::date - INTERVAL '359 days'
|
||||||
|
ORDER BY product_code, color_code, yaka_kodu, stock_date DESC
|
||||||
|
),
|
||||||
|
StockTotalStart AS (
|
||||||
|
SELECT DISTINCT ON (product_code, color_code, yaka_kodu)
|
||||||
|
product_code, color_code, yaka_kodu, stock_qty
|
||||||
|
FROM mk_product_performance_stock_daily
|
||||||
|
WHERE stock_date <= DATE '2022-01-01'
|
||||||
|
ORDER BY product_code, color_code, yaka_kodu, stock_date DESC
|
||||||
|
),
|
||||||
|
SalesAgg AS (
|
||||||
|
SELECT
|
||||||
|
product_code,
|
||||||
|
color_code,
|
||||||
|
yaka_kodu,
|
||||||
|
market_key,
|
||||||
|
MAX(item_description) AS item_description,
|
||||||
|
MAX(kategori) AS kategori,
|
||||||
|
MAX(seri) AS seri,
|
||||||
|
MAX(yas_grubu) AS yas_grubu,
|
||||||
|
COALESCE(MAX(NULLIF(askili_yan, '-')), '') AS askili_yan,
|
||||||
|
MAX(urun_ilk_grubu) AS urun_ilk_grubu,
|
||||||
|
MAX(urun_ana_grubu) AS urun_ana_grubu,
|
||||||
|
MAX(urun_alt_grubu) AS urun_alt_grubu,
|
||||||
|
SUM(sales_qty) FILTER (WHERE sales_date >= $1::date - INTERVAL '29 days') AS sales_qty_30d,
|
||||||
|
SUM(sales_qty) FILTER (WHERE sales_date >= $1::date - INTERVAL '89 days') AS sales_qty_90d,
|
||||||
|
SUM(sales_qty) FILTER (WHERE sales_date >= $1::date - INTERVAL '179 days') AS sales_qty_180d,
|
||||||
|
SUM(sales_qty) FILTER (WHERE sales_date >= $1::date - INTERVAL '359 days') AS sales_qty_365d,
|
||||||
|
SUM(sales_qty) FILTER (WHERE sales_date >= $1::date - INTERVAL '729 days') AS sales_qty_730d,
|
||||||
|
SUM(sales_qty) FILTER (WHERE sales_date >= DATE '2022-01-01') AS sales_qty_total,
|
||||||
|
SUM(sales_usd) FILTER (WHERE sales_date >= $1::date - INTERVAL '29 days') AS sales_usd_30d,
|
||||||
|
SUM(sales_usd) FILTER (WHERE sales_date >= $1::date - INTERVAL '89 days') AS sales_usd_90d,
|
||||||
|
SUM(sales_usd) FILTER (WHERE sales_date >= $1::date - INTERVAL '179 days') AS sales_usd_180d,
|
||||||
|
SUM(sales_usd) FILTER (WHERE sales_date >= $1::date - INTERVAL '359 days') AS sales_usd_365d,
|
||||||
|
SUM(sales_usd) FILTER (WHERE sales_date >= DATE '2022-01-01') AS sales_usd_total,
|
||||||
|
COUNT(DISTINCT NULLIF(customer_code, '-')) FILTER (WHERE sales_date >= $1::date - INTERVAL '89 days' AND COALESCE(sales_usd, 0) > 0) AS customer_count_90d,
|
||||||
|
COUNT(DISTINCT NULLIF(customer_code, '-')) FILTER (WHERE sales_date >= $1::date - INTERVAL '179 days' AND COALESCE(sales_usd, 0) > 0) AS customer_count_180d,
|
||||||
|
COUNT(DISTINCT NULLIF(customer_code, '-')) FILTER (WHERE sales_date >= $1::date - INTERVAL '359 days' AND COALESCE(sales_usd, 0) > 0) AS customer_count_365d,
|
||||||
|
COUNT(DISTINCT NULLIF(customer_code, '-')) FILTER (WHERE sales_date >= DATE '2022-01-01' AND COALESCE(sales_usd, 0) > 0) AS customer_count_total,
|
||||||
|
MIN(sales_date) FILTER (WHERE sales_date >= $1::date - INTERVAL '89 days') AS first_sale_date_90d,
|
||||||
|
MIN(sales_date) FILTER (WHERE sales_date >= $1::date - INTERVAL '179 days') AS first_sale_date_180d,
|
||||||
|
MIN(sales_date) FILTER (WHERE sales_date >= $1::date - INTERVAL '359 days') AS first_sale_date_365d,
|
||||||
|
MIN(sales_date) FILTER (WHERE sales_date >= DATE '2022-01-01') AS first_sale_date_total,
|
||||||
|
MAX(sales_date) AS last_sale_date,
|
||||||
|
MAX(last_ref_number) AS last_ref_number
|
||||||
|
FROM mk_product_performance_sales_daily
|
||||||
|
WHERE sales_date >= DATE '2022-01-01'
|
||||||
|
AND sales_date <= $1::date
|
||||||
|
AND upper(translate(btrim(COALESCE(urun_ilk_grubu,'')), U&'\0130\015E\011E\00DC\00D6\00C7\0131\015F\011F\00FC\00F6\00E7', 'ISGUOCisguoc')) NOT IN ('MALZEMELI FASON', 'MALZEMESIZ FASON', 'MAZLEMELI FASON', 'MAZEMESIZ FASON', 'DIGER')
|
||||||
|
GROUP BY product_code, color_code, yaka_kodu, market_key
|
||||||
|
),
|
||||||
|
Scope AS (
|
||||||
|
SELECT
|
||||||
|
product_code, color_code, yaka_kodu, market_key,
|
||||||
|
item_description, kategori, seri, yas_grubu, askili_yan, urun_ilk_grubu, urun_ana_grubu, urun_alt_grubu,
|
||||||
|
sales_qty_30d, sales_qty_90d, sales_qty_180d, sales_qty_365d, sales_qty_730d, sales_qty_total,
|
||||||
|
sales_usd_30d, sales_usd_90d, sales_usd_180d, sales_usd_365d, sales_usd_total,
|
||||||
|
customer_count_90d, customer_count_180d, customer_count_365d, customer_count_total,
|
||||||
|
first_sale_date_90d, first_sale_date_180d, first_sale_date_365d, first_sale_date_total,
|
||||||
|
last_sale_date, last_ref_number
|
||||||
|
FROM SalesAgg
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
ls.product_code,
|
||||||
|
ls.color_code,
|
||||||
|
ls.yaka_kodu,
|
||||||
|
'STOK' AS market_key,
|
||||||
|
'' AS item_description,
|
||||||
|
'' AS kategori,
|
||||||
|
'' AS seri,
|
||||||
|
'' AS yas_grubu,
|
||||||
|
'' AS askili_yan,
|
||||||
|
COALESCE(pd.urun_ilk_grubu, '') AS urun_ilk_grubu,
|
||||||
|
'' AS urun_ana_grubu,
|
||||||
|
'' AS urun_alt_grubu,
|
||||||
|
0 AS sales_qty_30d,
|
||||||
|
0 AS sales_qty_90d,
|
||||||
|
0 AS sales_qty_180d,
|
||||||
|
0 AS sales_qty_365d,
|
||||||
|
0 AS sales_qty_730d,
|
||||||
|
0 AS sales_qty_total,
|
||||||
|
0 AS sales_usd_30d,
|
||||||
|
0 AS sales_usd_90d,
|
||||||
|
0 AS sales_usd_180d,
|
||||||
|
0 AS sales_usd_365d,
|
||||||
|
0 AS sales_usd_total,
|
||||||
|
0 AS customer_count_90d,
|
||||||
|
0 AS customer_count_180d,
|
||||||
|
0 AS customer_count_365d,
|
||||||
|
0 AS customer_count_total,
|
||||||
|
NULL::date AS first_sale_date_90d,
|
||||||
|
NULL::date AS first_sale_date_180d,
|
||||||
|
NULL::date AS first_sale_date_365d,
|
||||||
|
NULL::date AS first_sale_date_total,
|
||||||
|
NULL::date AS last_sale_date,
|
||||||
|
'' AS last_ref_number
|
||||||
|
FROM LatestStock ls
|
||||||
|
LEFT JOIN mk_product_performance_price_dim pd ON pd.product_code=ls.product_code
|
||||||
|
WHERE COALESCE(ls.stock_qty, 0) <> 0
|
||||||
|
AND upper(translate(btrim(COALESCE(pd.urun_ilk_grubu,'')), U&'\0130\015E\011E\00DC\00D6\00C7\0131\015F\011F\00FC\00F6\00E7', 'ISGUOCisguoc')) NOT IN ('MALZEMELI FASON', 'MALZEMESIZ FASON', 'MAZLEMELI FASON', 'MAZEMESIZ FASON', 'DIGER')
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM SalesAgg sa
|
||||||
|
WHERE sa.product_code = ls.product_code
|
||||||
|
AND sa.color_code = ls.color_code
|
||||||
|
AND sa.yaka_kodu = ls.yaka_kodu
|
||||||
|
)
|
||||||
|
),
|
||||||
|
PriceDim AS (
|
||||||
|
SELECT
|
||||||
|
product_code,
|
||||||
|
COALESCE(urun_ilk_grubu, '') AS urun_ilk_grubu,
|
||||||
|
COALESCE(urun_ana_grubu, '') AS urun_ana_grubu,
|
||||||
|
COALESCE(urun_alt_grubu, '') AS urun_alt_grubu,
|
||||||
|
CASE
|
||||||
|
WHEN cost_price_usd > 0 AND base_price_usd > 0 AND cost_price_usd > base_price_usd THEN base_price_usd
|
||||||
|
ELSE cost_price_usd
|
||||||
|
END AS cost_price_usd,
|
||||||
|
CASE
|
||||||
|
WHEN cost_price_usd > 0 AND base_price_usd > 0 AND cost_price_usd > base_price_usd THEN cost_price_usd
|
||||||
|
ELSE base_price_usd
|
||||||
|
END AS base_price_usd,
|
||||||
|
base_price_try
|
||||||
|
FROM mk_product_performance_price_dim
|
||||||
|
),
|
||||||
|
Base AS (
|
||||||
|
SELECT
|
||||||
|
s.*,
|
||||||
|
COALESCE(ls.stock_qty, 0) AS stock_qty,
|
||||||
|
COALESCE(pd.cost_price_usd, 0) AS cost_price_usd,
|
||||||
|
COALESCE(pd.base_price_usd, 0) AS base_price_usd,
|
||||||
|
COALESCE(pd.base_price_try, 0) AS base_price_try,
|
||||||
|
COALESCE(s.sales_qty_90d, 0) / 90.0 AS avg_daily_sales_90d,
|
||||||
|
COALESCE(s.sales_qty_180d, 0) / 180.0 AS avg_daily_sales_180d,
|
||||||
|
COALESCE(s.sales_qty_365d, 0) / 360.0 AS avg_daily_sales_365d,
|
||||||
|
COALESCE(s.sales_qty_total, 0) / GREATEST(1, ($1::date - DATE '2022-01-01') + 1) AS avg_daily_sales_total,
|
||||||
|
(COALESCE(s90.stock_qty, ls.stock_qty, 0) + COALESCE(ls.stock_qty, 0)) / 2.0 AS avg_stock_90d,
|
||||||
|
(COALESCE(s180.stock_qty, ls.stock_qty, 0) + COALESCE(ls.stock_qty, 0)) / 2.0 AS avg_stock_180d,
|
||||||
|
(COALESCE(s365.stock_qty, ls.stock_qty, 0) + COALESCE(ls.stock_qty, 0)) / 2.0 AS avg_stock_365d,
|
||||||
|
(COALESCE(stotal.stock_qty, ls.stock_qty, 0) + COALESCE(ls.stock_qty, 0)) / 2.0 AS avg_stock_total,
|
||||||
|
CASE WHEN COALESCE(s.sales_qty_90d, 0) = 0 THEN 0 ELSE COALESCE(s.sales_usd_90d, 0) / NULLIF(s.sales_qty_90d, 0) END AS avg_price_usd_90d,
|
||||||
|
CASE WHEN COALESCE(s.sales_qty_180d, 0) = 0 THEN 0 ELSE COALESCE(s.sales_usd_180d, 0) / NULLIF(s.sales_qty_180d, 0) END AS avg_price_usd_180d,
|
||||||
|
COALESCE(s.sales_usd_90d, 0) - (COALESCE(s.sales_qty_90d, 0) * COALESCE(pd.cost_price_usd, 0)) AS gross_profit_usd_90d,
|
||||||
|
COALESCE(s.sales_usd_180d, 0) - (COALESCE(s.sales_qty_180d, 0) * COALESCE(pd.cost_price_usd, 0)) AS gross_profit_usd_180d,
|
||||||
|
CASE WHEN COALESCE(s.sales_usd_90d, 0) = 0 THEN 0
|
||||||
|
ELSE (COALESCE(s.sales_usd_90d, 0) - (COALESCE(s.sales_qty_90d, 0) * COALESCE(pd.cost_price_usd, 0))) / NULLIF(s.sales_usd_90d, 0)
|
||||||
|
END AS gross_margin_90d,
|
||||||
|
CASE WHEN COALESCE(s.sales_usd_180d, 0) = 0 THEN 0
|
||||||
|
ELSE (COALESCE(s.sales_usd_180d, 0) - (COALESCE(s.sales_qty_180d, 0) * COALESCE(pd.cost_price_usd, 0))) / NULLIF(s.sales_usd_180d, 0)
|
||||||
|
END AS gross_margin_180d,
|
||||||
|
CASE WHEN COALESCE(s.sales_qty_90d, 0) = 0 THEN 0 ELSE (COALESCE(s.sales_usd_90d, 0) / NULLIF(s.sales_qty_90d, 0)) - COALESCE(pd.cost_price_usd, 0) END AS unit_profit_cost_90d,
|
||||||
|
CASE WHEN COALESCE(s.sales_qty_180d, 0) = 0 THEN 0 ELSE (COALESCE(s.sales_usd_180d, 0) / NULLIF(s.sales_qty_180d, 0)) - COALESCE(pd.cost_price_usd, 0) END AS unit_profit_cost_180d,
|
||||||
|
CASE WHEN COALESCE(s.sales_qty_90d, 0) = 0 THEN 0 ELSE (COALESCE(s.sales_usd_90d, 0) / NULLIF(s.sales_qty_90d, 0)) - COALESCE(pd.base_price_usd, 0) END AS unit_profit_base_90d,
|
||||||
|
CASE WHEN COALESCE(s.sales_qty_180d, 0) = 0 THEN 0 ELSE (COALESCE(s.sales_usd_180d, 0) / NULLIF(s.sales_qty_180d, 0)) - COALESCE(pd.base_price_usd, 0) END AS unit_profit_base_180d
|
||||||
|
FROM Scope s
|
||||||
|
LEFT JOIN LatestStock ls ON ls.product_code=s.product_code AND ls.color_code=s.color_code AND ls.yaka_kodu=s.yaka_kodu
|
||||||
|
LEFT JOIN PriceDim pd ON pd.product_code=s.product_code
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT x.stock_qty
|
||||||
|
FROM mk_product_performance_stock_daily x
|
||||||
|
WHERE x.product_code=s.product_code
|
||||||
|
AND x.color_code=s.color_code
|
||||||
|
AND x.yaka_kodu=s.yaka_kodu
|
||||||
|
AND x.stock_date <= COALESCE(s.first_sale_date_90d, $1::date - INTERVAL '89 days')
|
||||||
|
ORDER BY x.stock_date DESC
|
||||||
|
LIMIT 1
|
||||||
|
) s90 ON TRUE
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT x.stock_qty
|
||||||
|
FROM mk_product_performance_stock_daily x
|
||||||
|
WHERE x.product_code=s.product_code
|
||||||
|
AND x.color_code=s.color_code
|
||||||
|
AND x.yaka_kodu=s.yaka_kodu
|
||||||
|
AND x.stock_date <= COALESCE(s.first_sale_date_180d, $1::date - INTERVAL '179 days')
|
||||||
|
ORDER BY x.stock_date DESC
|
||||||
|
LIMIT 1
|
||||||
|
) s180 ON TRUE
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT x.stock_qty
|
||||||
|
FROM mk_product_performance_stock_daily x
|
||||||
|
WHERE x.product_code=s.product_code
|
||||||
|
AND x.color_code=s.color_code
|
||||||
|
AND x.yaka_kodu=s.yaka_kodu
|
||||||
|
AND x.stock_date <= COALESCE(s.first_sale_date_365d, $1::date - INTERVAL '359 days')
|
||||||
|
ORDER BY x.stock_date DESC
|
||||||
|
LIMIT 1
|
||||||
|
) s365 ON TRUE
|
||||||
|
LEFT JOIN LATERAL (
|
||||||
|
SELECT x.stock_qty
|
||||||
|
FROM mk_product_performance_stock_daily x
|
||||||
|
WHERE x.product_code=s.product_code
|
||||||
|
AND x.color_code=s.color_code
|
||||||
|
AND x.yaka_kodu=s.yaka_kodu
|
||||||
|
AND x.stock_date <= COALESCE(s.first_sale_date_total, DATE '2022-01-01')
|
||||||
|
ORDER BY x.stock_date DESC
|
||||||
|
LIMIT 1
|
||||||
|
) stotal ON TRUE
|
||||||
|
WHERE upper(translate(btrim(COALESCE(NULLIF(s.urun_ilk_grubu,''), pd.urun_ilk_grubu, '')), U&'\0130\015E\011E\00DC\00D6\00C7\0131\015F\011F\00FC\00F6\00E7', 'ISGUOCisguoc')) NOT IN ('MALZEMELI FASON', 'MALZEMESIZ FASON', 'MAZLEMELI FASON', 'MAZEMESIZ FASON', 'DIGER')
|
||||||
|
),
|
||||||
|
Spread AS (
|
||||||
|
SELECT
|
||||||
|
product_code,
|
||||||
|
color_code,
|
||||||
|
yaka_kodu,
|
||||||
|
COUNT(DISTINCT NULLIF(NULLIF(market_key, 'STOK'), '')) FILTER (WHERE COALESCE(sales_usd_90d, 0) > 0) AS market_count_90d,
|
||||||
|
COUNT(DISTINCT NULLIF(NULLIF(market_key, 'STOK'), '')) FILTER (WHERE COALESCE(sales_usd_180d, 0) > 0) AS market_count_180d,
|
||||||
|
COUNT(DISTINCT NULLIF(NULLIF(market_key, 'STOK'), '')) FILTER (WHERE COALESCE(sales_usd_365d, 0) > 0) AS market_count_365d,
|
||||||
|
COUNT(DISTINCT NULLIF(NULLIF(market_key, 'STOK'), '')) FILTER (WHERE COALESCE(sales_usd_total, 0) > 0) AS market_count_total
|
||||||
|
FROM SalesAgg
|
||||||
|
GROUP BY product_code, color_code, yaka_kodu
|
||||||
|
),
|
||||||
|
Market AS (
|
||||||
|
SELECT
|
||||||
|
market_key,
|
||||||
|
AVG(NULLIF(sales_qty_90d, 0)) AS market_avg_sales_qty_90d,
|
||||||
|
AVG(NULLIF(sales_qty_180d, 0)) AS market_avg_sales_qty_180d,
|
||||||
|
AVG(NULLIF(sales_qty_365d, 0)) AS market_avg_sales_qty_365d,
|
||||||
|
AVG(NULLIF(sales_qty_total, 0)) AS market_avg_sales_qty_total,
|
||||||
|
AVG(NULLIF(avg_price_usd_90d, 0)) AS market_avg_price_usd_90d,
|
||||||
|
AVG(NULLIF(gross_margin_90d, 0)) AS market_avg_margin_90d
|
||||||
|
FROM Base
|
||||||
|
GROUP BY market_key
|
||||||
|
),
|
||||||
|
Scored AS (
|
||||||
|
SELECT
|
||||||
|
b.*,
|
||||||
|
COALESCE(sp.market_count_90d, 0) AS market_count_90d,
|
||||||
|
COALESCE(sp.market_count_180d, 0) AS market_count_180d,
|
||||||
|
COALESCE(sp.market_count_365d, 0) AS market_count_365d,
|
||||||
|
COALESCE(sp.market_count_total, 0) AS market_count_total,
|
||||||
|
CASE WHEN b.avg_daily_sales_90d = 0 THEN 0 ELSE b.avg_stock_90d / NULLIF(b.avg_daily_sales_90d, 0) END AS stock_days_90d,
|
||||||
|
CASE WHEN b.avg_daily_sales_180d = 0 THEN 0 ELSE b.avg_stock_180d / NULLIF(b.avg_daily_sales_180d, 0) END AS stock_days_180d,
|
||||||
|
CASE WHEN b.avg_daily_sales_365d = 0 THEN 0 ELSE b.avg_stock_365d / NULLIF(b.avg_daily_sales_365d, 0) END AS stock_days_365d,
|
||||||
|
CASE WHEN b.avg_daily_sales_total = 0 THEN 0 ELSE b.avg_stock_total / NULLIF(b.avg_daily_sales_total, 0) END AS stock_days_total,
|
||||||
|
CASE WHEN b.avg_stock_90d = 0 THEN 0 ELSE b.sales_qty_90d / NULLIF(b.avg_stock_90d, 0) * 4.0 END AS stock_turnover_90d,
|
||||||
|
CASE WHEN b.avg_stock_180d = 0 THEN 0 ELSE b.sales_qty_180d / NULLIF(b.avg_stock_180d, 0) * 2.0 END AS stock_turnover_180d,
|
||||||
|
CASE WHEN b.avg_stock_365d = 0 THEN 0 ELSE b.sales_qty_365d / NULLIF(b.avg_stock_365d, 0) END AS stock_turnover_365d,
|
||||||
|
CASE WHEN b.avg_stock_total = 0 THEN 0 ELSE b.sales_qty_total / NULLIF(b.avg_stock_total, 0) * 360.0 / GREATEST(1, ($1::date - DATE '2022-01-01') + 1) END AS stock_turnover_total,
|
||||||
|
CASE WHEN COALESCE(m.market_avg_sales_qty_90d, 0) = 0 THEN 0 ELSE b.sales_qty_90d / NULLIF(m.market_avg_sales_qty_90d, 0) END AS sales_index_90d,
|
||||||
|
CASE WHEN COALESCE(m.market_avg_sales_qty_180d, 0) = 0 THEN 0 ELSE b.sales_qty_180d / NULLIF(m.market_avg_sales_qty_180d, 0) END AS sales_index_180d,
|
||||||
|
CASE WHEN COALESCE(m.market_avg_sales_qty_365d, 0) = 0 THEN 0 ELSE b.sales_qty_365d / NULLIF(m.market_avg_sales_qty_365d, 0) END AS sales_index_365d,
|
||||||
|
CASE WHEN COALESCE(m.market_avg_sales_qty_total, 0) = 0 THEN 0 ELSE b.sales_qty_total / NULLIF(m.market_avg_sales_qty_total, 0) END AS sales_index_total,
|
||||||
|
CASE WHEN COALESCE(m.market_avg_price_usd_90d, 0) = 0 THEN 0 ELSE b.avg_price_usd_90d / NULLIF(m.market_avg_price_usd_90d, 0) END AS price_index_90d,
|
||||||
|
CASE WHEN COALESCE(m.market_avg_margin_90d, 0) = 0 THEN 0 ELSE b.gross_margin_90d / NULLIF(m.market_avg_margin_90d, 0) END AS margin_index_90d
|
||||||
|
FROM Base b
|
||||||
|
LEFT JOIN Market m ON m.market_key=b.market_key
|
||||||
|
LEFT JOIN Spread sp ON sp.product_code=b.product_code AND sp.color_code=b.color_code AND sp.yaka_kodu=b.yaka_kodu
|
||||||
|
)
|
||||||
|
INSERT INTO mk_product_performance_kpi_daily (
|
||||||
|
kpi_date, product_code, color_code, yaka_kodu, item_description,
|
||||||
|
kategori, seri, yas_grubu, askili_yan, urun_ilk_grubu, urun_ana_grubu, urun_alt_grubu, market_key, stock_qty,
|
||||||
|
sales_qty_30d, sales_qty_90d, sales_qty_180d, sales_qty_365d, sales_qty_730d, sales_qty_total,
|
||||||
|
sales_usd_30d, sales_usd_90d, sales_usd_180d, sales_usd_365d, sales_usd_total, avg_daily_sales_90d, avg_daily_sales_180d, avg_daily_sales_365d, avg_daily_sales_total,
|
||||||
|
avg_stock_90d, avg_stock_180d, avg_stock_365d, avg_stock_total,
|
||||||
|
stock_days_90d, stock_days_180d, stock_days_365d, stock_days_total, stock_turnover_90d, stock_turnover_180d, stock_turnover_365d, stock_turnover_total,
|
||||||
|
avg_price_usd_90d, avg_price_usd_180d, cost_price_usd, base_price_usd, base_price_try,
|
||||||
|
gross_profit_usd_90d, gross_profit_usd_180d, gross_margin_90d, gross_margin_180d,
|
||||||
|
unit_profit_cost_90d, unit_profit_cost_180d, unit_profit_base_90d, unit_profit_base_180d,
|
||||||
|
market_count_90d, market_count_180d, market_count_365d, market_count_total,
|
||||||
|
customer_count_90d, customer_count_180d, customer_count_365d, customer_count_total,
|
||||||
|
sales_index_90d, sales_index_180d, sales_index_365d, sales_index_total, price_index_90d, margin_index_90d, performance_score,
|
||||||
|
performance_bucket, recommendation, last_sale_date, last_ref_number, updated_at
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
$1::date,
|
||||||
|
product_code, color_code, yaka_kodu, item_description,
|
||||||
|
kategori, seri, yas_grubu, askili_yan, urun_ilk_grubu, urun_ana_grubu, urun_alt_grubu, market_key, stock_qty,
|
||||||
|
COALESCE(sales_qty_30d, 0), COALESCE(sales_qty_90d, 0), COALESCE(sales_qty_180d, 0), COALESCE(sales_qty_365d, 0), COALESCE(sales_qty_730d, 0), COALESCE(sales_qty_total, 0),
|
||||||
|
COALESCE(sales_usd_30d, 0), COALESCE(sales_usd_90d, 0), COALESCE(sales_usd_180d, 0), COALESCE(sales_usd_365d, 0), COALESCE(sales_usd_total, 0), COALESCE(avg_daily_sales_90d, 0), COALESCE(avg_daily_sales_180d, 0), COALESCE(avg_daily_sales_365d, 0), COALESCE(avg_daily_sales_total, 0),
|
||||||
|
COALESCE(avg_stock_90d, 0), COALESCE(avg_stock_180d, 0), COALESCE(avg_stock_365d, 0), COALESCE(avg_stock_total, 0),
|
||||||
|
COALESCE(stock_days_90d, 0), COALESCE(stock_days_180d, 0), COALESCE(stock_days_365d, 0), COALESCE(stock_days_total, 0), COALESCE(stock_turnover_90d, 0), COALESCE(stock_turnover_180d, 0), COALESCE(stock_turnover_365d, 0), COALESCE(stock_turnover_total, 0),
|
||||||
|
COALESCE(avg_price_usd_90d, 0), COALESCE(avg_price_usd_180d, 0), COALESCE(cost_price_usd, 0), COALESCE(base_price_usd, 0), COALESCE(base_price_try, 0),
|
||||||
|
COALESCE(gross_profit_usd_90d, 0), COALESCE(gross_profit_usd_180d, 0), COALESCE(gross_margin_90d, 0), COALESCE(gross_margin_180d, 0),
|
||||||
|
COALESCE(unit_profit_cost_90d, 0), COALESCE(unit_profit_cost_180d, 0), COALESCE(unit_profit_base_90d, 0), COALESCE(unit_profit_base_180d, 0),
|
||||||
|
COALESCE(market_count_90d, 0), COALESCE(market_count_180d, 0), COALESCE(market_count_365d, 0), COALESCE(market_count_total, 0),
|
||||||
|
COALESCE(customer_count_90d, 0), COALESCE(customer_count_180d, 0), COALESCE(customer_count_365d, 0), COALESCE(customer_count_total, 0),
|
||||||
|
COALESCE(sales_index_90d, 0), COALESCE(sales_index_180d, 0), COALESCE(sales_index_365d, 0), COALESCE(sales_index_total, 0), COALESCE(price_index_90d, 0), COALESCE(margin_index_90d, 0),
|
||||||
|
CASE WHEN COALESCE(sales_usd_90d, 0) <= 0 THEN 1 ELSE
|
||||||
|
ROUND(
|
||||||
|
LEAST(30, GREATEST(COALESCE(gross_margin_90d, 0), 0) / 0.45 * 30)
|
||||||
|
+ LEAST(20, COALESCE(stock_turnover_90d, 0) / 4.0 * 20)
|
||||||
|
+ LEAST(20, COALESCE(sales_usd_90d, 0) / 25000.0 * 20)
|
||||||
|
+ LEAST(5, COALESCE(market_count_90d, 0) / 3.0 * 5)
|
||||||
|
+ LEAST(25, COALESCE(customer_count_90d, 0) / 8.0 * 25)
|
||||||
|
, 4)
|
||||||
|
END AS performance_score,
|
||||||
|
CASE
|
||||||
|
WHEN COALESCE(sales_index_90d,0) >= 1.25 AND COALESCE(gross_margin_90d,0) >= 0.45 THEN 'YILDIZ_URUN'
|
||||||
|
WHEN COALESCE(stock_qty,0) <= 0 AND COALESCE(sales_qty_90d,0) > 0 THEN 'STOKSUZ_TALEP'
|
||||||
|
WHEN COALESCE(stock_days_90d,0) > 180 AND COALESCE(sales_index_90d,0) < 0.75 THEN 'STOK_RISKI'
|
||||||
|
WHEN COALESCE(price_index_90d,0) > 1.10 AND COALESCE(sales_index_90d,0) < 0.80 THEN 'FIYAT_BASKISI'
|
||||||
|
WHEN COALESCE(price_index_90d,0) < 0.90 AND COALESCE(sales_index_90d,0) > 1.00 THEN 'FIYAT_FIRSATI'
|
||||||
|
ELSE 'TAKIP'
|
||||||
|
END AS performance_bucket,
|
||||||
|
CASE
|
||||||
|
WHEN COALESCE(sales_index_90d,0) >= 1.25 AND COALESCE(gross_margin_90d,0) >= 0.45 THEN 'Piyasa ustu satis ve iyi marj: stok ve fiyat gucu takip edilmeli.'
|
||||||
|
WHEN COALESCE(stock_qty,0) <= 0 AND COALESCE(sales_qty_90d,0) > 0 THEN 'Talep var, stok yok: uretim/satin alma planina alinmali.'
|
||||||
|
WHEN COALESCE(stock_days_90d,0) > 180 AND COALESCE(sales_index_90d,0) < 0.75 THEN 'Stok yuksek, satis piyasa alti: kampanya veya fiyat kontrolu gerekli.'
|
||||||
|
WHEN COALESCE(price_index_90d,0) > 1.10 AND COALESCE(sales_index_90d,0) < 0.80 THEN 'Fiyat piyasa ustunde ve satis zayif: fiyat revizyonu degerlendirilmeli.'
|
||||||
|
WHEN COALESCE(price_index_90d,0) < 0.90 AND COALESCE(sales_index_90d,0) > 1.00 THEN 'Satis guclu, fiyat piyasa alti: taban fiyat artisi degerlendirilebilir.'
|
||||||
|
ELSE 'Duzenli takip.'
|
||||||
|
END AS recommendation,
|
||||||
|
last_sale_date,
|
||||||
|
COALESCE(last_ref_number, ''),
|
||||||
|
now()
|
||||||
|
FROM Scored
|
||||||
|
`
|
||||||
|
}
|
||||||
@@ -162,50 +162,50 @@ func GetAllProductPricingRows(ctx context.Context, chunkSize int, filters Produc
|
|||||||
CREATE CLUSTERED INDEX IX_req_codes_ProductCode ON #req_codes(ProductCode);
|
CREATE CLUSTERED INDEX IX_req_codes_ProductCode ON #req_codes(ProductCode);
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(s.ItemCode)) AS ItemCode,
|
s.ItemCode AS ItemCode,
|
||||||
SUM(s.In_Qty1 - s.Out_Qty1) AS InventoryQty1
|
SUM(s.In_Qty1 - s.Out_Qty1) AS InventoryQty1
|
||||||
INTO #stock_base
|
INTO #stock_base
|
||||||
FROM trStock s WITH(NOLOCK)
|
FROM trStock s
|
||||||
INNER JOIN #req_codes rc
|
INNER JOIN #req_codes rc
|
||||||
ON rc.ProductCode = LTRIM(RTRIM(s.ItemCode))
|
ON rc.ProductCode = s.ItemCode
|
||||||
WHERE s.ItemTypeCode = 1
|
WHERE s.ItemTypeCode = 1
|
||||||
AND LEN(LTRIM(RTRIM(s.ItemCode))) = 13
|
AND LEN(s.ItemCode) = 13
|
||||||
GROUP BY LTRIM(RTRIM(s.ItemCode));
|
GROUP BY s.ItemCode;
|
||||||
|
|
||||||
CREATE CLUSTERED INDEX IX_stock_base_ItemCode ON #stock_base(ItemCode);
|
CREATE CLUSTERED INDEX IX_stock_base_ItemCode ON #stock_base(ItemCode);
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(p.ItemCode)) AS ItemCode,
|
p.ItemCode AS ItemCode,
|
||||||
SUM(p.Qty1) AS PickingQty1
|
SUM(p.Qty1) AS PickingQty1
|
||||||
INTO #pick_base
|
INTO #pick_base
|
||||||
FROM PickingStates p
|
FROM PickingStates p
|
||||||
INNER JOIN #req_codes rc
|
INNER JOIN #req_codes rc
|
||||||
ON rc.ProductCode = LTRIM(RTRIM(p.ItemCode))
|
ON rc.ProductCode = p.ItemCode
|
||||||
WHERE p.ItemTypeCode = 1
|
WHERE p.ItemTypeCode = 1
|
||||||
AND LEN(LTRIM(RTRIM(p.ItemCode))) = 13
|
AND LEN(p.ItemCode) = 13
|
||||||
GROUP BY LTRIM(RTRIM(p.ItemCode));
|
GROUP BY p.ItemCode;
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(r.ItemCode)) AS ItemCode,
|
r.ItemCode AS ItemCode,
|
||||||
SUM(r.Qty1) AS ReserveQty1
|
SUM(r.Qty1) AS ReserveQty1
|
||||||
INTO #reserve_base
|
INTO #reserve_base
|
||||||
FROM ReserveStates r
|
FROM ReserveStates r
|
||||||
INNER JOIN #req_codes rc
|
INNER JOIN #req_codes rc
|
||||||
ON rc.ProductCode = LTRIM(RTRIM(r.ItemCode))
|
ON rc.ProductCode = r.ItemCode
|
||||||
WHERE r.ItemTypeCode = 1
|
WHERE r.ItemTypeCode = 1
|
||||||
AND LEN(LTRIM(RTRIM(r.ItemCode))) = 13
|
AND LEN(r.ItemCode) = 13
|
||||||
GROUP BY LTRIM(RTRIM(r.ItemCode));
|
GROUP BY r.ItemCode;
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(d.ItemCode)) AS ItemCode,
|
d.ItemCode AS ItemCode,
|
||||||
SUM(d.Qty1) AS DispOrderQty1
|
SUM(d.Qty1) AS DispOrderQty1
|
||||||
INTO #disp_base
|
INTO #disp_base
|
||||||
FROM DispOrderStates d
|
FROM DispOrderStates d
|
||||||
INNER JOIN #req_codes rc
|
INNER JOIN #req_codes rc
|
||||||
ON rc.ProductCode = LTRIM(RTRIM(d.ItemCode))
|
ON rc.ProductCode = d.ItemCode
|
||||||
WHERE d.ItemTypeCode = 1
|
WHERE d.ItemTypeCode = 1
|
||||||
AND LEN(LTRIM(RTRIM(d.ItemCode))) = 13
|
AND LEN(d.ItemCode) = 13
|
||||||
GROUP BY LTRIM(RTRIM(d.ItemCode));
|
GROUP BY d.ItemCode;
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
rc.ProductCode,
|
rc.ProductCode,
|
||||||
@@ -315,16 +315,16 @@ func enrichAllProductPricingRows(ctx context.Context, out []models.ProductPricin
|
|||||||
),
|
),
|
||||||
latest_pricelist_line AS (
|
latest_pricelist_line AS (
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(p.ItemCode)) AS ItemCode,
|
p.ItemCode AS ItemCode,
|
||||||
LTRIM(RTRIM(p.DocCurrencyCode)) AS DocCurrencyCode,
|
LTRIM(RTRIM(p.DocCurrencyCode)) AS DocCurrencyCode,
|
||||||
CAST(p.Price AS DECIMAL(18, 2)) AS Price,
|
CAST(p.Price AS DECIMAL(18, 2)) AS Price,
|
||||||
ROW_NUMBER() OVER (
|
ROW_NUMBER() OVER (
|
||||||
PARTITION BY LTRIM(RTRIM(p.ItemCode)), LTRIM(RTRIM(p.DocCurrencyCode))
|
PARTITION BY p.ItemCode, LTRIM(RTRIM(p.DocCurrencyCode))
|
||||||
ORDER BY p.ValidDate DESC, p.ValidTime DESC, p.LastUpdatedDate DESC
|
ORDER BY p.ValidDate DESC, p.ValidTime DESC, p.LastUpdatedDate DESC
|
||||||
) AS rn
|
) AS rn
|
||||||
FROM dbo.trPriceListLine p WITH(NOLOCK)
|
FROM dbo.trPriceListLine p
|
||||||
INNER JOIN req_codes rc
|
INNER JOIN req_codes rc
|
||||||
ON rc.ProductCode = LTRIM(RTRIM(p.ItemCode))
|
ON rc.ProductCode = p.ItemCode
|
||||||
WHERE p.ItemTypeCode = 1
|
WHERE p.ItemTypeCode = 1
|
||||||
AND ISNULL(p.IsDisabled, 0) = 0
|
AND ISNULL(p.IsDisabled, 0) = 0
|
||||||
AND LTRIM(RTRIM(p.DocCurrencyCode)) IN ('USD', 'TRY')
|
AND LTRIM(RTRIM(p.DocCurrencyCode)) IN ('USD', 'TRY')
|
||||||
@@ -346,29 +346,29 @@ func enrichAllProductPricingRows(ctx context.Context, out []models.ProductPricin
|
|||||||
),
|
),
|
||||||
latest_base_price AS (
|
latest_base_price AS (
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(b.ItemCode)) AS ItemCode,
|
b.ItemCode AS ItemCode,
|
||||||
CAST(b.Price AS DECIMAL(18, 2)) AS CostPrice,
|
CAST(b.Price AS DECIMAL(18, 2)) AS CostPrice,
|
||||||
CONVERT(VARCHAR(10), b.PriceDate, 23) AS LastPricingDate,
|
CONVERT(VARCHAR(10), b.PriceDate, 23) AS LastPricingDate,
|
||||||
ROW_NUMBER() OVER (
|
ROW_NUMBER() OVER (
|
||||||
PARTITION BY LTRIM(RTRIM(b.ItemCode))
|
PARTITION BY b.ItemCode
|
||||||
ORDER BY b.PriceDate DESC, b.LastUpdatedDate DESC
|
ORDER BY b.PriceDate DESC, b.LastUpdatedDate DESC
|
||||||
) AS rn
|
) AS rn
|
||||||
FROM prItemBasePrice b
|
FROM prItemBasePrice b
|
||||||
INNER JOIN req_codes rc
|
INNER JOIN req_codes rc
|
||||||
ON rc.ProductCode = LTRIM(RTRIM(b.ItemCode))
|
ON rc.ProductCode = b.ItemCode
|
||||||
WHERE b.ItemTypeCode = 1
|
WHERE b.ItemTypeCode = 1
|
||||||
AND b.BasePriceCode = 1
|
AND b.BasePriceCode = 1
|
||||||
AND LTRIM(RTRIM(b.CurrencyCode)) = 'USD'
|
AND LTRIM(RTRIM(b.CurrencyCode)) = 'USD'
|
||||||
),
|
),
|
||||||
stock_entry_dates AS (
|
stock_entry_dates AS (
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(s.ItemCode)) AS ItemCode,
|
s.ItemCode AS ItemCode,
|
||||||
CONVERT(VARCHAR(10), MAX(s.OperationDate), 23) AS StockEntryDate
|
CONVERT(VARCHAR(10), MAX(s.OperationDate), 23) AS StockEntryDate
|
||||||
FROM trStock s WITH(NOLOCK)
|
FROM trStock s
|
||||||
INNER JOIN req_codes rc
|
INNER JOIN req_codes rc
|
||||||
ON rc.ProductCode = LTRIM(RTRIM(s.ItemCode))
|
ON rc.ProductCode = s.ItemCode
|
||||||
WHERE s.ItemTypeCode = 1
|
WHERE s.ItemTypeCode = 1
|
||||||
AND LEN(LTRIM(RTRIM(s.ItemCode))) = 13
|
AND LEN(s.ItemCode) = 13
|
||||||
AND s.In_Qty1 > 0
|
AND s.In_Qty1 > 0
|
||||||
AND LTRIM(RTRIM(s.InnerProcessCode)) = 'OP'
|
AND LTRIM(RTRIM(s.InnerProcessCode)) = 'OP'
|
||||||
AND LTRIM(RTRIM(s.WarehouseCode)) IN (
|
AND LTRIM(RTRIM(s.WarehouseCode)) IN (
|
||||||
@@ -376,7 +376,7 @@ func enrichAllProductPricingRows(ctx context.Context, out []models.ProductPricin
|
|||||||
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
||||||
'1-0-33','101','1-014','1-0-49','1-0-36'
|
'1-0-33','101','1-014','1-0-49','1-0-36'
|
||||||
)
|
)
|
||||||
GROUP BY LTRIM(RTRIM(s.ItemCode))
|
GROUP BY s.ItemCode
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
rc.ProductCode,
|
rc.ProductCode,
|
||||||
@@ -431,7 +431,7 @@ func enrichAllProductPricingRows(ctx context.Context, out []models.ProductPricin
|
|||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(m.UrunKodu)) AS UrunKodu,
|
LTRIM(RTRIM(m.UrunKodu)) AS UrunKodu,
|
||||||
CONVERT(VARCHAR(10), MAX(m.Tarihi), 23) AS LastCostingDate
|
CONVERT(VARCHAR(10), MAX(m.Tarihi), 23) AS LastCostingDate
|
||||||
FROM dbo.spUrtOnMLMas m WITH(NOLOCK)
|
FROM dbo.spUrtOnMLMas m
|
||||||
INNER JOIN req_codes rc
|
INNER JOIN req_codes rc
|
||||||
ON rc.ProductCode = LTRIM(RTRIM(m.UrunKodu))
|
ON rc.ProductCode = LTRIM(RTRIM(m.UrunKodu))
|
||||||
GROUP BY LTRIM(RTRIM(m.UrunKodu));
|
GROUP BY LTRIM(RTRIM(m.UrunKodu));
|
||||||
@@ -878,15 +878,15 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
|||||||
CREATE CLUSTERED INDEX IX_req_codes_ProductCode ON #req_codes(ProductCode);
|
CREATE CLUSTERED INDEX IX_req_codes_ProductCode ON #req_codes(ProductCode);
|
||||||
|
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(s.ItemCode)) AS ItemCode,
|
s.ItemCode AS ItemCode,
|
||||||
SUM(s.In_Qty1 - s.Out_Qty1) AS InventoryQty1
|
SUM(s.In_Qty1 - s.Out_Qty1) AS InventoryQty1
|
||||||
INTO #stock_base
|
INTO #stock_base
|
||||||
FROM trStock s WITH(NOLOCK)
|
FROM trStock s
|
||||||
INNER JOIN #req_codes rc
|
INNER JOIN #req_codes rc
|
||||||
ON rc.ProductCode = LTRIM(RTRIM(s.ItemCode))
|
ON rc.ProductCode = s.ItemCode
|
||||||
WHERE s.ItemTypeCode = 1
|
WHERE s.ItemTypeCode = 1
|
||||||
AND LEN(LTRIM(RTRIM(s.ItemCode))) = 13
|
AND LEN(s.ItemCode) = 13
|
||||||
GROUP BY LTRIM(RTRIM(s.ItemCode));
|
GROUP BY s.ItemCode;
|
||||||
|
|
||||||
CREATE CLUSTERED INDEX IX_stock_base_ItemCode ON #stock_base(ItemCode);
|
CREATE CLUSTERED INDEX IX_stock_base_ItemCode ON #stock_base(ItemCode);
|
||||||
|
|
||||||
@@ -927,7 +927,7 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
|||||||
),
|
),
|
||||||
attr AS (
|
attr AS (
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(a.ItemCode)) AS ProductCode,
|
a.ItemCode AS ProductCode,
|
||||||
MAX(CASE WHEN a.AttributeTypeCode = 45 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS AskiliYan,
|
MAX(CASE WHEN a.AttributeTypeCode = 45 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS AskiliYan,
|
||||||
MAX(CASE WHEN a.AttributeTypeCode = 44 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS Kategori,
|
MAX(CASE WHEN a.AttributeTypeCode = 44 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS Kategori,
|
||||||
MAX(CASE WHEN a.AttributeTypeCode = 42 THEN LTRIM(RTRIM(a.AttributeCode)) ELSE '' END) AS UrunIlkGrubuCode,
|
MAX(CASE WHEN a.AttributeTypeCode = 42 THEN LTRIM(RTRIM(a.AttributeCode)) ELSE '' END) AS UrunIlkGrubuCode,
|
||||||
@@ -938,17 +938,17 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
|||||||
MAX(CASE WHEN a.AttributeTypeCode = 29 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS Karisim,
|
MAX(CASE WHEN a.AttributeTypeCode = 29 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS Karisim,
|
||||||
MAX(CASE WHEN a.AttributeTypeCode = 10 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS Marka,
|
MAX(CASE WHEN a.AttributeTypeCode = 10 THEN COALESCE(NULLIF(LTRIM(RTRIM(d.AttributeDescription)), ''), LTRIM(RTRIM(a.AttributeCode))) ELSE '' END) AS Marka,
|
||||||
MAX(CASE WHEN a.AttributeTypeCode = 10 THEN LTRIM(RTRIM(a.AttributeCode)) ELSE '' END) AS BrandCode
|
MAX(CASE WHEN a.AttributeTypeCode = 10 THEN LTRIM(RTRIM(a.AttributeCode)) ELSE '' END) AS BrandCode
|
||||||
FROM dbo.prItemAttribute a WITH(NOLOCK)
|
FROM dbo.prItemAttribute a
|
||||||
INNER JOIN req_codes rc
|
INNER JOIN req_codes rc
|
||||||
ON rc.ProductCode = LTRIM(RTRIM(a.ItemCode))
|
ON rc.ProductCode = a.ItemCode
|
||||||
LEFT JOIN dbo.cdItemAttributeDesc d WITH(NOLOCK)
|
LEFT JOIN dbo.cdItemAttributeDesc d
|
||||||
ON d.ItemTypeCode = a.ItemTypeCode
|
ON d.ItemTypeCode = a.ItemTypeCode
|
||||||
AND d.AttributeTypeCode = a.AttributeTypeCode
|
AND d.AttributeTypeCode = a.AttributeTypeCode
|
||||||
AND d.AttributeCode = a.AttributeCode
|
AND d.AttributeCode = a.AttributeCode
|
||||||
AND d.LangCode = 'TR'
|
AND d.LangCode = 'TR'
|
||||||
WHERE a.ItemTypeCode = 1
|
WHERE a.ItemTypeCode = 1
|
||||||
AND a.AttributeTypeCode IN (1,2,10,29,41,42,44,45)
|
AND a.AttributeTypeCode IN (1,2,10,29,41,42,44,45)
|
||||||
GROUP BY LTRIM(RTRIM(a.ItemCode))
|
GROUP BY a.ItemCode
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
rc.ProductCode,
|
rc.ProductCode,
|
||||||
@@ -963,13 +963,13 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
|||||||
COALESCE(attr.Marka, '') AS Marka,
|
COALESCE(attr.Marka, '') AS Marka,
|
||||||
COALESCE(attr.BrandCode, '') AS BrandCode
|
COALESCE(attr.BrandCode, '') AS BrandCode
|
||||||
FROM req_codes rc
|
FROM req_codes rc
|
||||||
INNER JOIN dbo.cdItem ci WITH(NOLOCK)
|
INNER JOIN dbo.cdItem ci
|
||||||
ON ci.ItemTypeCode = 1
|
ON ci.ItemTypeCode = 1
|
||||||
AND LTRIM(RTRIM(ci.ItemCode)) = rc.ProductCode
|
AND ci.ItemCode = rc.ProductCode
|
||||||
LEFT JOIN attr
|
LEFT JOIN attr
|
||||||
ON attr.ProductCode = rc.ProductCode
|
ON attr.ProductCode = rc.ProductCode
|
||||||
WHERE ISNULL(ci.IsBlocked, 0) = 0
|
WHERE ISNULL(ci.IsBlocked, 0) = 0
|
||||||
AND LEN(LTRIM(RTRIM(ci.ItemCode))) = 13
|
AND LEN(ci.ItemCode) = 13
|
||||||
AND COALESCE(attr.UrunIlkGrubuCode, '') IN ('SERI', 'AKSESUAR')
|
AND COALESCE(attr.UrunIlkGrubuCode, '') IN ('SERI', 'AKSESUAR')
|
||||||
ORDER BY rc.ProductCode ASC
|
ORDER BY rc.ProductCode ASC
|
||||||
OFFSET ` + strconv.Itoa(offset) + ` ROWS
|
OFFSET ` + strconv.Itoa(offset) + ` ROWS
|
||||||
@@ -1109,16 +1109,16 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
|||||||
-- Base prices from Nebim V3 price lists (trPriceListLine).
|
-- Base prices from Nebim V3 price lists (trPriceListLine).
|
||||||
-- Pick the latest record per (ItemCode, Currency) using ValidDate/ValidTime, then LastUpdatedDate.
|
-- Pick the latest record per (ItemCode, Currency) using ValidDate/ValidTime, then LastUpdatedDate.
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(p.ItemCode)) AS ItemCode,
|
p.ItemCode AS ItemCode,
|
||||||
LTRIM(RTRIM(p.DocCurrencyCode)) AS DocCurrencyCode,
|
LTRIM(RTRIM(p.DocCurrencyCode)) AS DocCurrencyCode,
|
||||||
CAST(p.Price AS DECIMAL(18, 2)) AS Price,
|
CAST(p.Price AS DECIMAL(18, 2)) AS Price,
|
||||||
ROW_NUMBER() OVER (
|
ROW_NUMBER() OVER (
|
||||||
PARTITION BY LTRIM(RTRIM(p.ItemCode)), LTRIM(RTRIM(p.DocCurrencyCode))
|
PARTITION BY p.ItemCode, LTRIM(RTRIM(p.DocCurrencyCode))
|
||||||
ORDER BY p.ValidDate DESC, p.ValidTime DESC, p.LastUpdatedDate DESC
|
ORDER BY p.ValidDate DESC, p.ValidTime DESC, p.LastUpdatedDate DESC
|
||||||
) AS rn
|
) AS rn
|
||||||
FROM dbo.trPriceListLine p WITH(NOLOCK)
|
FROM dbo.trPriceListLine p
|
||||||
INNER JOIN req_codes rc
|
INNER JOIN req_codes rc
|
||||||
ON rc.ProductCode = LTRIM(RTRIM(p.ItemCode))
|
ON rc.ProductCode = p.ItemCode
|
||||||
WHERE p.ItemTypeCode = 1
|
WHERE p.ItemTypeCode = 1
|
||||||
AND ISNULL(p.IsDisabled, 0) = 0
|
AND ISNULL(p.IsDisabled, 0) = 0
|
||||||
AND LTRIM(RTRIM(p.DocCurrencyCode)) IN ('USD', 'TRY')
|
AND LTRIM(RTRIM(p.DocCurrencyCode)) IN ('USD', 'TRY')
|
||||||
@@ -1140,29 +1140,29 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
|||||||
),
|
),
|
||||||
latest_base_price AS (
|
latest_base_price AS (
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(b.ItemCode)) AS ItemCode,
|
b.ItemCode AS ItemCode,
|
||||||
CAST(b.Price AS DECIMAL(18, 2)) AS CostPrice,
|
CAST(b.Price AS DECIMAL(18, 2)) AS CostPrice,
|
||||||
CONVERT(VARCHAR(10), b.PriceDate, 23) AS LastPricingDate,
|
CONVERT(VARCHAR(10), b.PriceDate, 23) AS LastPricingDate,
|
||||||
ROW_NUMBER() OVER (
|
ROW_NUMBER() OVER (
|
||||||
PARTITION BY LTRIM(RTRIM(b.ItemCode))
|
PARTITION BY b.ItemCode
|
||||||
ORDER BY b.PriceDate DESC, b.LastUpdatedDate DESC
|
ORDER BY b.PriceDate DESC, b.LastUpdatedDate DESC
|
||||||
) AS rn
|
) AS rn
|
||||||
FROM prItemBasePrice b
|
FROM prItemBasePrice b
|
||||||
INNER JOIN req_codes rc
|
INNER JOIN req_codes rc
|
||||||
ON rc.ProductCode = LTRIM(RTRIM(b.ItemCode))
|
ON rc.ProductCode = b.ItemCode
|
||||||
WHERE b.ItemTypeCode = 1
|
WHERE b.ItemTypeCode = 1
|
||||||
AND b.BasePriceCode = 1
|
AND b.BasePriceCode = 1
|
||||||
AND LTRIM(RTRIM(b.CurrencyCode)) = 'USD'
|
AND LTRIM(RTRIM(b.CurrencyCode)) = 'USD'
|
||||||
),
|
),
|
||||||
stock_entry_dates AS (
|
stock_entry_dates AS (
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(s.ItemCode)) AS ItemCode,
|
s.ItemCode AS ItemCode,
|
||||||
CONVERT(VARCHAR(10), MAX(s.OperationDate), 23) AS StockEntryDate
|
CONVERT(VARCHAR(10), MAX(s.OperationDate), 23) AS StockEntryDate
|
||||||
FROM trStock s WITH(NOLOCK)
|
FROM trStock s
|
||||||
INNER JOIN req_codes rc
|
INNER JOIN req_codes rc
|
||||||
ON rc.ProductCode = LTRIM(RTRIM(s.ItemCode))
|
ON rc.ProductCode = s.ItemCode
|
||||||
WHERE s.ItemTypeCode = 1
|
WHERE s.ItemTypeCode = 1
|
||||||
AND LEN(LTRIM(RTRIM(s.ItemCode))) = 13
|
AND LEN(s.ItemCode) = 13
|
||||||
AND s.In_Qty1 > 0
|
AND s.In_Qty1 > 0
|
||||||
AND LTRIM(RTRIM(s.InnerProcessCode)) = 'OP'
|
AND LTRIM(RTRIM(s.InnerProcessCode)) = 'OP'
|
||||||
AND LTRIM(RTRIM(s.WarehouseCode)) IN (
|
AND LTRIM(RTRIM(s.WarehouseCode)) IN (
|
||||||
@@ -1170,51 +1170,51 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
|||||||
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
||||||
'1-0-33','101','1-014','1-0-49','1-0-36'
|
'1-0-33','101','1-014','1-0-49','1-0-36'
|
||||||
)
|
)
|
||||||
GROUP BY LTRIM(RTRIM(s.ItemCode))
|
GROUP BY s.ItemCode
|
||||||
),
|
),
|
||||||
stock_base AS (
|
stock_base AS (
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(s.ItemCode)) AS ItemCode,
|
s.ItemCode AS ItemCode,
|
||||||
SUM(s.In_Qty1 - s.Out_Qty1) AS InventoryQty1
|
SUM(s.In_Qty1 - s.Out_Qty1) AS InventoryQty1
|
||||||
FROM trStock s WITH(NOLOCK)
|
FROM trStock s
|
||||||
INNER JOIN req_codes rc
|
INNER JOIN req_codes rc
|
||||||
ON rc.ProductCode = LTRIM(RTRIM(s.ItemCode))
|
ON rc.ProductCode = s.ItemCode
|
||||||
WHERE s.ItemTypeCode = 1
|
WHERE s.ItemTypeCode = 1
|
||||||
AND LEN(LTRIM(RTRIM(s.ItemCode))) = 13
|
AND LEN(s.ItemCode) = 13
|
||||||
GROUP BY LTRIM(RTRIM(s.ItemCode))
|
GROUP BY s.ItemCode
|
||||||
),
|
),
|
||||||
pick_base AS (
|
pick_base AS (
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(p.ItemCode)) AS ItemCode,
|
p.ItemCode AS ItemCode,
|
||||||
SUM(p.Qty1) AS PickingQty1
|
SUM(p.Qty1) AS PickingQty1
|
||||||
FROM PickingStates p
|
FROM PickingStates p
|
||||||
INNER JOIN req_codes rc
|
INNER JOIN req_codes rc
|
||||||
ON rc.ProductCode = LTRIM(RTRIM(p.ItemCode))
|
ON rc.ProductCode = p.ItemCode
|
||||||
WHERE p.ItemTypeCode = 1
|
WHERE p.ItemTypeCode = 1
|
||||||
AND LEN(LTRIM(RTRIM(p.ItemCode))) = 13
|
AND LEN(p.ItemCode) = 13
|
||||||
GROUP BY LTRIM(RTRIM(p.ItemCode))
|
GROUP BY p.ItemCode
|
||||||
),
|
),
|
||||||
reserve_base AS (
|
reserve_base AS (
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(r.ItemCode)) AS ItemCode,
|
r.ItemCode AS ItemCode,
|
||||||
SUM(r.Qty1) AS ReserveQty1
|
SUM(r.Qty1) AS ReserveQty1
|
||||||
FROM ReserveStates r
|
FROM ReserveStates r
|
||||||
INNER JOIN req_codes rc
|
INNER JOIN req_codes rc
|
||||||
ON rc.ProductCode = LTRIM(RTRIM(r.ItemCode))
|
ON rc.ProductCode = r.ItemCode
|
||||||
WHERE r.ItemTypeCode = 1
|
WHERE r.ItemTypeCode = 1
|
||||||
AND LEN(LTRIM(RTRIM(r.ItemCode))) = 13
|
AND LEN(r.ItemCode) = 13
|
||||||
GROUP BY LTRIM(RTRIM(r.ItemCode))
|
GROUP BY r.ItemCode
|
||||||
),
|
),
|
||||||
disp_base AS (
|
disp_base AS (
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(d.ItemCode)) AS ItemCode,
|
d.ItemCode AS ItemCode,
|
||||||
SUM(d.Qty1) AS DispOrderQty1
|
SUM(d.Qty1) AS DispOrderQty1
|
||||||
FROM DispOrderStates d
|
FROM DispOrderStates d
|
||||||
INNER JOIN req_codes rc
|
INNER JOIN req_codes rc
|
||||||
ON rc.ProductCode = LTRIM(RTRIM(d.ItemCode))
|
ON rc.ProductCode = d.ItemCode
|
||||||
WHERE d.ItemTypeCode = 1
|
WHERE d.ItemTypeCode = 1
|
||||||
AND LEN(LTRIM(RTRIM(d.ItemCode))) = 13
|
AND LEN(d.ItemCode) = 13
|
||||||
GROUP BY LTRIM(RTRIM(d.ItemCode))
|
GROUP BY d.ItemCode
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
rc.ProductCode,
|
rc.ProductCode,
|
||||||
@@ -1311,7 +1311,7 @@ func GetProductPricingPage(ctx context.Context, page int, limit int, filters Pro
|
|||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(m.UrunKodu)) AS UrunKodu,
|
LTRIM(RTRIM(m.UrunKodu)) AS UrunKodu,
|
||||||
CONVERT(VARCHAR(10), MAX(m.Tarihi), 23) AS LastCostingDate
|
CONVERT(VARCHAR(10), MAX(m.Tarihi), 23) AS LastCostingDate
|
||||||
FROM dbo.spUrtOnMLMas m WITH(NOLOCK)
|
FROM dbo.spUrtOnMLMas m
|
||||||
INNER JOIN req_codes rc
|
INNER JOIN req_codes rc
|
||||||
ON rc.ProductCode = LTRIM(RTRIM(m.UrunKodu))
|
ON rc.ProductCode = LTRIM(RTRIM(m.UrunKodu))
|
||||||
GROUP BY LTRIM(RTRIM(m.UrunKodu));
|
GROUP BY LTRIM(RTRIM(m.UrunKodu));
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ SELECT DISTINCT
|
|||||||
LTRIM(RTRIM(ISNULL(V.ColorCode,''))) AS ColorCode,
|
LTRIM(RTRIM(ISNULL(V.ColorCode,''))) AS ColorCode,
|
||||||
LTRIM(RTRIM(ISNULL(V.ItemDim1Code,''))) AS ItemDim1Code,
|
LTRIM(RTRIM(ISNULL(V.ItemDim1Code,''))) AS ItemDim1Code,
|
||||||
LTRIM(RTRIM(ISNULL(V.ItemDim2Code,''))) AS ItemDim3Code
|
LTRIM(RTRIM(ISNULL(V.ItemDim2Code,''))) AS ItemDim3Code
|
||||||
FROM prItemVariant V WITH(NOLOCK)
|
FROM prItemVariant V
|
||||||
WHERE V.ItemTypeCode = 1
|
WHERE V.ItemTypeCode = 1
|
||||||
AND V.ItemCode = @ProductCode
|
AND V.ItemCode = @ProductCode
|
||||||
AND LEN(V.ItemCode) = 13
|
AND LEN(V.ItemCode) = 13
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ CREATE CLUSTERED INDEX IX_AttrFiltered_ProductCode ON #AttrFiltered(ProductCode)
|
|||||||
(
|
(
|
||||||
SELECT T.ItemCode, T.ColorCode, T.ItemDim1Code, T.ItemDim2Code,
|
SELECT T.ItemCode, T.ColorCode, T.ItemDim1Code, T.ItemDim2Code,
|
||||||
SUM(T.In_Qty1 - T.Out_Qty1) AS InventoryQty1, 0 AS PickingQty1, 0 AS ReserveQty1, 0 AS DispOrderQty1
|
SUM(T.In_Qty1 - T.Out_Qty1) AS InventoryQty1, 0 AS PickingQty1, 0 AS ReserveQty1, 0 AS DispOrderQty1
|
||||||
FROM trStock T WITH (NOLOCK)
|
FROM trStock T
|
||||||
INNER JOIN #AttrFiltered AF ON AF.ProductCode = T.ItemCode
|
INNER JOIN #AttrFiltered AF ON AF.ProductCode = T.ItemCode
|
||||||
WHERE T.ItemTypeCode = 1
|
WHERE T.ItemTypeCode = 1
|
||||||
AND LEN(T.ItemCode) = 13
|
AND LEN(T.ItemCode) = 13
|
||||||
@@ -144,7 +144,7 @@ Avail AS
|
|||||||
I.ItemDim2Code,
|
I.ItemDim2Code,
|
||||||
CAST(ROUND(I.InventoryQty1 - I.PickingQty1 - I.ReserveQty1 - I.DispOrderQty1, 2) AS DECIMAL(18,2)) AS Qty
|
CAST(ROUND(I.InventoryQty1 - I.PickingQty1 - I.ReserveQty1 - I.DispOrderQty1, 2) AS DECIMAL(18,2)) AS Qty
|
||||||
FROM Inv I
|
FROM Inv I
|
||||||
JOIN cdItem CI WITH (NOLOCK)
|
JOIN cdItem CI
|
||||||
ON CI.ItemTypeCode = 1
|
ON CI.ItemTypeCode = 1
|
||||||
AND CI.ItemCode = I.ItemCode
|
AND CI.ItemCode = I.ItemCode
|
||||||
WHERE CI.IsBlocked = 0
|
WHERE CI.IsBlocked = 0
|
||||||
@@ -190,7 +190,7 @@ JOIN Avail A
|
|||||||
AND A.ColorCode = G.ColorCode
|
AND A.ColorCode = G.ColorCode
|
||||||
AND ISNULL(A.ItemDim2Code, '') = ISNULL(G.ItemDim2Code, '')
|
AND ISNULL(A.ItemDim2Code, '') = ISNULL(G.ItemDim2Code, '')
|
||||||
JOIN #AttrFiltered AF ON AF.ProductCode = G.ItemCode
|
JOIN #AttrFiltered AF ON AF.ProductCode = G.ItemCode
|
||||||
LEFT JOIN cdColorDesc C WITH (NOLOCK)
|
LEFT JOIN cdColorDesc C
|
||||||
ON C.ColorCode = G.ColorCode
|
ON C.ColorCode = G.ColorCode
|
||||||
AND C.LangCode = 'TR'
|
AND C.LangCode = 'TR'
|
||||||
ORDER BY G.ItemCode, G.ColorCode, G.ItemDim2Code, A.ItemDim1Code
|
ORDER BY G.ItemCode, G.ColorCode, G.ItemDim2Code, A.ItemDim1Code
|
||||||
|
|||||||
@@ -70,18 +70,45 @@ CREATE TABLE IF NOT EXISTS mk_product_series_job_log (
|
|||||||
error_text TEXT NOT NULL DEFAULT ''
|
error_text TEXT NOT NULL DEFAULT ''
|
||||||
)`,
|
)`,
|
||||||
`CREATE INDEX IF NOT EXISTS ix_mk_product_series_job_log_started ON mk_product_series_job_log (started_at DESC)`,
|
`CREATE INDEX IF NOT EXISTS ix_mk_product_series_job_log_started ON mk_product_series_job_log (started_at DESC)`,
|
||||||
|
`
|
||||||
|
CREATE TABLE IF NOT EXISTS mk_product_series_apply_state (
|
||||||
|
row_key TEXT PRIMARY KEY,
|
||||||
|
last_apply_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
last_apply_kind TEXT NOT NULL DEFAULT '',
|
||||||
|
last_series_ids TEXT NOT NULL DEFAULT '',
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT ck_mk_product_series_apply_state_kind CHECK (last_apply_kind IN ('', 'rules', 'fallback'))
|
||||||
|
)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS ix_mk_product_series_apply_state_updated ON mk_product_series_apply_state (updated_at DESC)`,
|
||||||
|
// Backward compatible schema upgrades.
|
||||||
|
`ALTER TABLE mk_product_series_apply_state ADD COLUMN IF NOT EXISTS invalid_streak INTEGER NOT NULL DEFAULT 0`,
|
||||||
|
`ALTER TABLE mk_product_series_apply_state ADD COLUMN IF NOT EXISTS last_invalid_at TIMESTAMPTZ`,
|
||||||
}
|
}
|
||||||
for _, stmt := range stmts {
|
for _, stmt := range stmts {
|
||||||
if _, err := pg.Exec(stmt); err != nil {
|
if _, err := pg.Exec(stmt); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure a default fallback series exists for "stok var ama seri secilemedi" cases.
|
||||||
|
// The scheduler may apply it when no other rule matches. We create only the definition;
|
||||||
|
// we intentionally do NOT auto-seed mk_product_series_rule from its title.
|
||||||
|
// Default code for this installation is "1".
|
||||||
|
_, _ = pg.Exec(`
|
||||||
|
INSERT INTO dfgrp (code, title, is_active, typ, master, parent_filter, sort_order, is_required, notes)
|
||||||
|
SELECT '1', '', TRUE, 'opt', 'zbggseri', '', 0, FALSE, 'auto-created fallback series'
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM dfgrp WHERE master='zbggseri' AND code='1'
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
|
||||||
_, err := pg.Exec(`
|
_, err := pg.Exec(`
|
||||||
INSERT INTO mk_product_series_rule (series_id, size_group, size_code, ratio_qty, priority, source, notes)
|
INSERT INTO mk_product_series_rule (series_id, size_group, size_code, ratio_qty, priority, source, notes)
|
||||||
SELECT d.id, '', BTRIM(x.size_code), 1, 0, 'dfgrp_title', 'auto-seeded from dfgrp.title'
|
SELECT d.id, '', BTRIM(x.size_code), 1, 0, 'dfgrp_title', 'auto-seeded from dfgrp.title'
|
||||||
FROM dfgrp d
|
FROM dfgrp d
|
||||||
CROSS JOIN LATERAL regexp_split_to_table(COALESCE(d.title, ''), '-') AS x(size_code)
|
CROSS JOIN LATERAL regexp_split_to_table(COALESCE(d.title, ''), '-') AS x(size_code)
|
||||||
WHERE d.master='zbggseri'
|
WHERE d.master='zbggseri'
|
||||||
|
AND COALESCE(d.code,'') <> '1'
|
||||||
AND BTRIM(x.size_code) <> ''
|
AND BTRIM(x.size_code) <> ''
|
||||||
ON CONFLICT (series_id, size_group, size_code) DO NOTHING
|
ON CONFLICT (series_id, size_group, size_code) DO NOTHING
|
||||||
`)
|
`)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ const GetProductAttributes = `
|
|||||||
t.ItemTypeCode,
|
t.ItemTypeCode,
|
||||||
t.AttributeTypeCode,
|
t.AttributeTypeCode,
|
||||||
ISNULL(NULLIF(td.AttributeTypeDescription, ''), CAST(t.AttributeTypeCode AS NVARCHAR(30))) AS AttributeTypeDescription
|
ISNULL(NULLIF(td.AttributeTypeDescription, ''), CAST(t.AttributeTypeCode AS NVARCHAR(30))) AS AttributeTypeDescription
|
||||||
FROM dbo.cdItemAttributeType AS t WITH(NOLOCK)
|
FROM dbo.cdItemAttributeType AS t
|
||||||
LEFT JOIN dbo.cdItemAttributeTypeDesc AS td WITH(NOLOCK)
|
LEFT JOIN dbo.cdItemAttributeTypeDesc AS td
|
||||||
ON td.ItemTypeCode = t.ItemTypeCode
|
ON td.ItemTypeCode = t.ItemTypeCode
|
||||||
AND td.AttributeTypeCode = t.AttributeTypeCode
|
AND td.AttributeTypeCode = t.AttributeTypeCode
|
||||||
AND td.LangCode = 'TR'
|
AND td.LangCode = 'TR'
|
||||||
@@ -21,8 +21,8 @@ Attr AS (
|
|||||||
a.AttributeTypeCode,
|
a.AttributeTypeCode,
|
||||||
ISNULL(a.AttributeCode, '') AS AttributeCode,
|
ISNULL(a.AttributeCode, '') AS AttributeCode,
|
||||||
ISNULL(d.AttributeDescription, ISNULL(a.AttributeCode, '')) AS AttributeDescription
|
ISNULL(d.AttributeDescription, ISNULL(a.AttributeCode, '')) AS AttributeDescription
|
||||||
FROM dbo.cdItemAttribute AS a WITH(NOLOCK)
|
FROM dbo.cdItemAttribute AS a
|
||||||
LEFT JOIN dbo.cdItemAttributeDesc AS d WITH(NOLOCK)
|
LEFT JOIN dbo.cdItemAttributeDesc AS d
|
||||||
ON d.ItemTypeCode = a.ItemTypeCode
|
ON d.ItemTypeCode = a.ItemTypeCode
|
||||||
AND d.AttributeTypeCode = a.AttributeTypeCode
|
AND d.AttributeTypeCode = a.AttributeTypeCode
|
||||||
AND d.AttributeCode = a.AttributeCode
|
AND d.AttributeCode = a.AttributeCode
|
||||||
@@ -53,7 +53,7 @@ SELECT
|
|||||||
a.ItemTypeCode,
|
a.ItemTypeCode,
|
||||||
a.AttributeTypeCode,
|
a.AttributeTypeCode,
|
||||||
ISNULL(a.AttributeCode, '') AS AttributeCode
|
ISNULL(a.AttributeCode, '') AS AttributeCode
|
||||||
FROM dbo.prItemAttribute AS a WITH(NOLOCK)
|
FROM dbo.prItemAttribute AS a
|
||||||
WHERE a.ItemTypeCode = @p1
|
WHERE a.ItemTypeCode = @p1
|
||||||
AND ISNULL(LTRIM(RTRIM(a.ItemCode)), '') = ISNULL(LTRIM(RTRIM(@p2)), '')
|
AND ISNULL(LTRIM(RTRIM(a.ItemCode)), '') = ISNULL(LTRIM(RTRIM(@p2)), '')
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ SELECT DISTINCT
|
|||||||
v.ColorCode,
|
v.ColorCode,
|
||||||
ISNULL(cd.ColorDescription, '') AS ColorDescription
|
ISNULL(cd.ColorDescription, '') AS ColorDescription
|
||||||
FROM ProductFilterWithDescription('TR') AS p
|
FROM ProductFilterWithDescription('TR') AS p
|
||||||
INNER JOIN prItemVariant AS v WITH(NOLOCK)
|
INNER JOIN prItemVariant AS v
|
||||||
ON v.ItemCode = p.ProductCode
|
ON v.ItemCode = p.ProductCode
|
||||||
LEFT JOIN cdColorDesc AS cd WITH(NOLOCK)
|
LEFT JOIN cdColorDesc AS cd
|
||||||
ON cd.ColorCode = v.ColorCode
|
ON cd.ColorCode = v.ColorCode
|
||||||
AND cd.LangCode = 'TR'
|
AND cd.LangCode = 'TR'
|
||||||
WHERE
|
WHERE
|
||||||
|
|||||||
@@ -39,14 +39,14 @@ SELECT
|
|||||||
ISNULL(prItemVariant.ItemDim1Code, '') AS ItemDim1Code,
|
ISNULL(prItemVariant.ItemDim1Code, '') AS ItemDim1Code,
|
||||||
ISNULL(prItemVariant.ItemDim2Code, '') AS ItemDim2Code
|
ISNULL(prItemVariant.ItemDim2Code, '') AS ItemDim2Code
|
||||||
FROM ProductFilterWithDescription('TR') AS Product
|
FROM ProductFilterWithDescription('TR') AS Product
|
||||||
INNER JOIN cdItem WITH(NOLOCK)
|
INNER JOIN cdItem
|
||||||
ON cdItem.ItemTypeCode = 1
|
ON cdItem.ItemTypeCode = 1
|
||||||
AND cdItem.ItemCode = Product.ProductCode
|
AND cdItem.ItemCode = Product.ProductCode
|
||||||
LEFT JOIN prProductLot WITH(NOLOCK)
|
LEFT JOIN prProductLot
|
||||||
ON prProductLot.ItemTypeCode = cdItem.ItemTypeCode
|
ON prProductLot.ItemTypeCode = cdItem.ItemTypeCode
|
||||||
AND prProductLot.ItemCode = cdItem.ItemCode
|
AND prProductLot.ItemCode = cdItem.ItemCode
|
||||||
AND prProductLot.IsDefault = 1
|
AND prProductLot.IsDefault = 1
|
||||||
LEFT JOIN prItemVariant WITH(NOLOCK)
|
LEFT JOIN prItemVariant
|
||||||
ON prItemVariant.ItemCode = Product.ProductCode
|
ON prItemVariant.ItemCode = Product.ProductCode
|
||||||
WHERE
|
WHERE
|
||||||
ProductAtt42 IN ('SERI','AKSESUAR')
|
ProductAtt42 IN ('SERI','AKSESUAR')
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func LookupFirmaIDByKodu(ctx context.Context, uretimDB *sql.DB, firmaKodu string
|
|||||||
}
|
}
|
||||||
sqlText := `
|
sqlText := `
|
||||||
SELECT TOP 1 ISNULL(F.nFirmaID, 0) AS nFirmaID
|
SELECT TOP 1 ISNULL(F.nFirmaID, 0) AS nFirmaID
|
||||||
FROM dbo.tbFirma F WITH (NOLOCK)
|
FROM dbo.tbFirma F
|
||||||
WHERE LTRIM(RTRIM(ISNULL(F.sKodu, ''))) = @p1
|
WHERE LTRIM(RTRIM(ISNULL(F.sKodu, ''))) = @p1
|
||||||
ORDER BY F.nFirmaID
|
ORDER BY F.nFirmaID
|
||||||
`
|
`
|
||||||
@@ -234,10 +234,10 @@ func UpsertV3ItemBasePriceUSD(
|
|||||||
var targetCountry string
|
var targetCountry string
|
||||||
err := mssqlDB.QueryRowContext(ctx, `
|
err := mssqlDB.QueryRowContext(ctx, `
|
||||||
SELECT TOP 1 C.CountryCode
|
SELECT TOP 1 C.CountryCode
|
||||||
FROM dbo.cdCountry C WITH (NOLOCK)
|
FROM dbo.cdCountry C
|
||||||
WHERE C.CountryCode <> 'TR'
|
WHERE C.CountryCode <> 'TR'
|
||||||
AND NOT EXISTS (
|
AND NOT EXISTS (
|
||||||
SELECT 1 FROM dbo.prItemBasePrice P WITH (NOLOCK)
|
SELECT 1 FROM dbo.prItemBasePrice P
|
||||||
WHERE P.ItemTypeCode = 1
|
WHERE P.ItemTypeCode = 1
|
||||||
AND LTRIM(RTRIM(P.ItemCode)) = LTRIM(RTRIM(@p1))
|
AND LTRIM(RTRIM(P.ItemCode)) = LTRIM(RTRIM(@p1))
|
||||||
AND P.BasePriceCode = 1
|
AND P.BasePriceCode = 1
|
||||||
@@ -448,7 +448,7 @@ func GetProductionProductCostingMTBolumOptions(ctx context.Context, uretimDB *sq
|
|||||||
SELECT TOP (@p2)
|
SELECT TOP (@p2)
|
||||||
ISNULL(B.nUrtMTBolumID, 0) AS nUrtMTBolumID,
|
ISNULL(B.nUrtMTBolumID, 0) AS nUrtMTBolumID,
|
||||||
ISNULL(B.sAdi, '') AS sAdi
|
ISNULL(B.sAdi, '') AS sAdi
|
||||||
FROM dbo.spUrtMTBolum B WITH (NOLOCK)
|
FROM dbo.spUrtMTBolum B
|
||||||
WHERE ISNULL(B.bAktif, 0) = 1
|
WHERE ISNULL(B.bAktif, 0) = 1
|
||||||
AND ISNULL(B.nUrtTipiID, 0) = 1
|
AND ISNULL(B.nUrtTipiID, 0) = 1
|
||||||
AND (@p1 = '' OR ISNULL(B.sAdi, '') LIKE @p3 OR CONVERT(VARCHAR(32), ISNULL(B.nUrtMTBolumID, 0)) LIKE @p3)
|
AND (@p1 = '' OR ISNULL(B.sAdi, '') LIKE @p3 OR CONVERT(VARCHAR(32), ISNULL(B.nUrtMTBolumID, 0)) LIKE @p3)
|
||||||
@@ -477,15 +477,15 @@ SELECT
|
|||||||
CAST(CASE WHEN ISNULL(M.bAktif, 0) = 1 THEN 1 ELSE 0 END AS bit) AS bAktif,
|
CAST(CASE WHEN ISNULL(M.bAktif, 0) = 1 THEN 1 ELSE 0 END AS bit) AS bAktif,
|
||||||
CONVERT(VARCHAR(16), M.dteIslemTarihi, 120) AS dteIslemTarihi,
|
CONVERT(VARCHAR(16), M.dteIslemTarihi, 120) AS dteIslemTarihi,
|
||||||
ISNULL(M.sKullaniciAdi, '') AS sKullaniciAdi
|
ISNULL(M.sKullaniciAdi, '') AS sKullaniciAdi
|
||||||
FROM dbo.mk_MaliyetParcaEslestirme M WITH (NOLOCK)
|
FROM dbo.mk_MaliyetParcaEslestirme M
|
||||||
LEFT JOIN dbo.spUrtMTBolum B WITH (NOLOCK)
|
LEFT JOIN dbo.spUrtMTBolum B
|
||||||
ON B.nUrtMTBolumID = M.nUrtMTBolumID
|
ON B.nUrtMTBolumID = M.nUrtMTBolumID
|
||||||
AND ISNULL(B.nUrtTipiID, 0) = 1
|
AND ISNULL(B.nUrtTipiID, 0) = 1
|
||||||
OUTER APPLY (
|
OUTER APPLY (
|
||||||
SELECT
|
SELECT
|
||||||
STUFF((
|
STUFF((
|
||||||
SELECT ',' + LTRIM(RTRIM(CONVERT(VARCHAR(32), X.nHammaddeTuruNo)))
|
SELECT ',' + LTRIM(RTRIM(CONVERT(VARCHAR(32), X.nHammaddeTuruNo)))
|
||||||
FROM dbo.mk_MaliyetParcaEslestirme_HammaddeTuru X WITH (NOLOCK)
|
FROM dbo.mk_MaliyetParcaEslestirme_HammaddeTuru X
|
||||||
WHERE X.mapping_id = M.id
|
WHERE X.mapping_id = M.id
|
||||||
ORDER BY X.nHammaddeTuruNo
|
ORDER BY X.nHammaddeTuruNo
|
||||||
FOR XML PATH(''), TYPE
|
FOR XML PATH(''), TYPE
|
||||||
@@ -630,7 +630,7 @@ SELECT
|
|||||||
ISNULL(H.nHammaddeTuruNo, 0) AS nHammaddeTuruNo,
|
ISNULL(H.nHammaddeTuruNo, 0) AS nHammaddeTuruNo,
|
||||||
ISNULL(H.sAciklama, '') AS sAciklama,
|
ISNULL(H.sAciklama, '') AS sAciklama,
|
||||||
ISNULL(H.MTnUrtMTBolumID, 0) AS MTnUrtMTBolumID
|
ISNULL(H.MTnUrtMTBolumID, 0) AS MTnUrtMTBolumID
|
||||||
FROM dbo.spUrtOnMLHammaddeTuru H WITH (NOLOCK)
|
FROM dbo.spUrtOnMLHammaddeTuru H
|
||||||
WHERE H.nHammaddeTuruNo IN (%s)
|
WHERE H.nHammaddeTuruNo IN (%s)
|
||||||
`, strings.Join(ph, ","))
|
`, strings.Join(ph, ","))
|
||||||
|
|
||||||
@@ -793,6 +793,82 @@ ORDER BY SonIsEmri.dteIslemTarihi DESC, LTRIM(RTRIM(R.sMModelKodu)) ASC
|
|||||||
return uretimDB.QueryContext(ctx, sqlText, fromDate, search)
|
return uretimDB.QueryContext(ctx, sqlText, fromDate, search)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ArgeBaggiDepotEntryTotals struct {
|
||||||
|
Ceket float64
|
||||||
|
Pantolon float64
|
||||||
|
Yelek float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetArgeBaggiDepotEntryTotals returns operation 985/986/987 quantities keyed
|
||||||
|
// by production work order. The requested work orders are queried in one batch
|
||||||
|
// to avoid an ArgeBAGGI round trip for every list row.
|
||||||
|
func GetArgeBaggiDepotEntryTotals(ctx context.Context, argeDB *sql.DB, workOrders []string) (map[string]ArgeBaggiDepotEntryTotals, error) {
|
||||||
|
out := make(map[string]ArgeBaggiDepotEntryTotals)
|
||||||
|
if argeDB == nil || len(workOrders) == 0 {
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]struct{}, len(workOrders))
|
||||||
|
values := make([]string, 0, len(workOrders))
|
||||||
|
for _, raw := range workOrders {
|
||||||
|
value := strings.TrimSpace(raw)
|
||||||
|
if value == "" || value == "0" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, exists := seen[value]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[value] = struct{}{}
|
||||||
|
values = append(values, value)
|
||||||
|
}
|
||||||
|
if len(values) == 0 {
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
placeholders := make([]string, 0, len(values))
|
||||||
|
args := make([]any, 0, len(values))
|
||||||
|
for i, value := range values {
|
||||||
|
placeholders = append(placeholders, fmt.Sprintf("@p%d", i+1))
|
||||||
|
args = append(args, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlText := fmt.Sprintf(`
|
||||||
|
SELECT
|
||||||
|
LTRIM(RTRIM(CONVERT(VARCHAR(64), MN.model_adi))) AS IsEmriNo,
|
||||||
|
SUM(CASE WHEN O.oper_num = 985 THEN CONVERT(float, B.adet) ELSE 0 END) AS CeketDepoGiris,
|
||||||
|
SUM(CASE WHEN O.oper_num = 986 THEN CONVERT(float, B.adet) ELSE 0 END) AS PantolonDepoGiris,
|
||||||
|
SUM(CASE WHEN O.oper_num = 987 THEN CONVERT(float, B.adet) ELSE 0 END) AS YelekDepoGiris
|
||||||
|
FROM dbo.is_acma_bar B
|
||||||
|
INNER JOIN dbo.is_acma I ON I.is_num = B.is_num
|
||||||
|
INNER JOIN dbo.model_def MD ON I.imdef_num = MD.mdef_num
|
||||||
|
INNER JOIN dbo.oper_urun_no O ON B.oper_num = O.oper_num
|
||||||
|
INNER JOIN dbo.model_num MN ON I.model_num = MN.model_num
|
||||||
|
INNER JOIN dbo.bant_no BN ON I.bant_num = BN.bant_no
|
||||||
|
INNER JOIN dbo.model_oper MO ON O.oper_num = MO.oper_num AND MD.mdef_num = MO.model_num
|
||||||
|
INNER JOIN dbo.model_oper_tkp_def TD ON MO.tkp_cik = TD.tkp_cik
|
||||||
|
WHERE B.tarih >= '2025-01-01'
|
||||||
|
AND TD.tkp_cik IN (1,2,3,4,5)
|
||||||
|
AND O.oper_num IN (985,986,987)
|
||||||
|
AND LTRIM(RTRIM(CONVERT(VARCHAR(64), MN.model_adi))) IN (%s)
|
||||||
|
GROUP BY LTRIM(RTRIM(CONVERT(VARCHAR(64), MN.model_adi)))
|
||||||
|
`, strings.Join(placeholders, ","))
|
||||||
|
|
||||||
|
rows, err := argeDB.QueryContext(ctx, sqlText, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
var workOrder string
|
||||||
|
var totals ArgeBaggiDepotEntryTotals
|
||||||
|
if err := rows.Scan(&workOrder, &totals.Ceket, &totals.Pantolon, &totals.Yelek); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out[strings.TrimSpace(workOrder)] = totals
|
||||||
|
}
|
||||||
|
return out, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
func GetProductionHasCostProducts(
|
func GetProductionHasCostProducts(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
uretimDB *sql.DB,
|
uretimDB *sql.DB,
|
||||||
@@ -816,12 +892,22 @@ WITH SonOnMaliyet AS (
|
|||||||
FROM dbo.spUrtOnMLMas M
|
FROM dbo.spUrtOnMLMas M
|
||||||
WHERE LTRIM(RTRIM(M.UrunKodu)) <> ''
|
WHERE LTRIM(RTRIM(M.UrunKodu)) <> ''
|
||||||
),
|
),
|
||||||
SonSiparis AS (
|
SonSiparisSirali AS (
|
||||||
SELECT
|
SELECT
|
||||||
LTRIM(RTRIM(sMModelKodu)) AS UrunKodu,
|
LTRIM(RTRIM(SD.sMModelKodu)) AS UrunKodu,
|
||||||
MAX(dteIslemTarihi) AS SonSiparisTarihi
|
SD.dteIslemTarihi AS SonSiparisTarihi,
|
||||||
FROM dbo.spUrtSiparisDet
|
SM.nUrtSiparisNo,
|
||||||
GROUP BY LTRIM(RTRIM(sMModelKodu))
|
ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY LTRIM(RTRIM(SD.sMModelKodu))
|
||||||
|
ORDER BY SD.dteIslemTarihi DESC, SD.nUrtSiparisID DESC
|
||||||
|
) AS rn
|
||||||
|
FROM dbo.spUrtSiparisDet SD
|
||||||
|
INNER JOIN dbo.spUrtSiparis SM ON SM.nUrtSiparisID = SD.nUrtSiparisID
|
||||||
|
),
|
||||||
|
SonSiparis AS (
|
||||||
|
SELECT UrunKodu, SonSiparisTarihi, nUrtSiparisNo
|
||||||
|
FROM SonSiparisSirali
|
||||||
|
WHERE rn = 1
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
CASE
|
CASE
|
||||||
@@ -831,6 +917,7 @@ SELECT
|
|||||||
THEN N'FASON ICIN URETILEN'
|
THEN N'FASON ICIN URETILEN'
|
||||||
ELSE N'Tanimsiz'
|
ELSE N'Tanimsiz'
|
||||||
END AS UretimSekli,
|
END AS UretimSekli,
|
||||||
|
RTRIM(CONVERT(VARCHAR(32), ISNULL(SS.nUrtSiparisNo, 0))) AS nUrtSiparisNo,
|
||||||
RTRIM(CONVERT(VARCHAR(32), ISNULL(OM.nOnMLNo, 0))) AS nOnMLNo,
|
RTRIM(CONVERT(VARCHAR(32), ISNULL(OM.nOnMLNo, 0))) AS nOnMLNo,
|
||||||
LTRIM(RTRIM(ISNULL(OM.UrunKodu, ''))) AS UrunKodu,
|
LTRIM(RTRIM(ISNULL(OM.UrunKodu, ''))) AS UrunKodu,
|
||||||
ISNULL(OM.UrunAdi, '') AS UrunAdi,
|
ISNULL(OM.UrunAdi, '') AS UrunAdi,
|
||||||
@@ -954,7 +1041,7 @@ func GetProductionHasCostDetailRowsByOnMLNo(
|
|||||||
ON T.nHammaddeTuruNo = D.nHammaddeTuruNo
|
ON T.nHammaddeTuruNo = D.nHammaddeTuruNo
|
||||||
LEFT JOIN dbo.spUrtMTBolum B
|
LEFT JOIN dbo.spUrtMTBolum B
|
||||||
ON B.nUrtMTBolumID = D.nUrtMTBolumID
|
ON B.nUrtMTBolumID = D.nUrtMTBolumID
|
||||||
LEFT JOIN dbo.tbStok SX WITH (NOLOCK)
|
LEFT JOIN dbo.tbStok SX
|
||||||
ON (SX.IsBlocked = 0 OR SX.IsBlocked IS NULL)
|
ON (SX.IsBlocked = 0 OR SX.IsBlocked IS NULL)
|
||||||
AND ISNULL(SX.sKodu,'') = ISNULL(D.sKodu,'')
|
AND ISNULL(SX.sKodu,'') = ISNULL(D.sKodu,'')
|
||||||
WHERE D.nOnMLNo = @p1
|
WHERE D.nOnMLNo = @p1
|
||||||
@@ -1168,7 +1255,7 @@ HammaddeTekil AS (
|
|||||||
CASE WHEN H.MTnUrtMTBolumID = RMik.nUrtMTBolumID THEN 0 ELSE 1 END,
|
CASE WHEN H.MTnUrtMTBolumID = RMik.nUrtMTBolumID THEN 0 ELSE 1 END,
|
||||||
H.nHammaddeTuruNo
|
H.nHammaddeTuruNo
|
||||||
) HT
|
) HT
|
||||||
LEFT JOIN dbo.spUrtMTBolum B WITH (NOLOCK)
|
LEFT JOIN dbo.spUrtMTBolum B
|
||||||
ON B.nUrtMTBolumID = HT.MTnUrtMTBolumID
|
ON B.nUrtMTBolumID = HT.MTnUrtMTBolumID
|
||||||
AND ISNULL(B.nUrtTipiID, 0) = 1
|
AND ISNULL(B.nUrtTipiID, 0) = 1
|
||||||
WHERE HT.nHammaddeTuruNo IS NOT NULL
|
WHERE HT.nHammaddeTuruNo IS NOT NULL
|
||||||
@@ -1246,8 +1333,8 @@ func GetProductionHasCostDetailHammaddeTypeOptions(
|
|||||||
COALESCE(NULLIF(LTRIM(RTRIM(T.sAciklama3)), ''), NULLIF(LTRIM(RTRIM(T.sAciklama2)), ''), N'TANIMSIZ') AS sAciklama3,
|
COALESCE(NULLIF(LTRIM(RTRIM(T.sAciklama3)), ''), NULLIF(LTRIM(RTRIM(T.sAciklama2)), ''), N'TANIMSIZ') AS sAciklama3,
|
||||||
ISNULL(T.MTnUrtMTBolumID, 0) AS mtUrtMTBolumID,
|
ISNULL(T.MTnUrtMTBolumID, 0) AS mtUrtMTBolumID,
|
||||||
ISNULL(B.sAdi, '') AS sParcaAdi
|
ISNULL(B.sAdi, '') AS sParcaAdi
|
||||||
FROM dbo.spUrtOnMLHammaddeTuru T WITH (NOLOCK)
|
FROM dbo.spUrtOnMLHammaddeTuru T
|
||||||
LEFT JOIN dbo.spUrtMTBolum B WITH (NOLOCK)
|
LEFT JOIN dbo.spUrtMTBolum B
|
||||||
ON B.nUrtMTBolumID = T.MTnUrtMTBolumID
|
ON B.nUrtMTBolumID = T.MTnUrtMTBolumID
|
||||||
AND ISNULL(B.nUrtTipiID, 0) = 1
|
AND ISNULL(B.nUrtTipiID, 0) = 1
|
||||||
WHERE
|
WHERE
|
||||||
@@ -1320,10 +1407,10 @@ SELECT
|
|||||||
ISNULL(T.sAciklama, '') AS sAciklama,
|
ISNULL(T.sAciklama, '') AS sAciklama,
|
||||||
ISNULL(T.MTnUrtMTBolumID, 0) AS mtUrtMTBolumID,
|
ISNULL(T.MTnUrtMTBolumID, 0) AS mtUrtMTBolumID,
|
||||||
ISNULL(B.sAdi, '') AS sParcaAdi
|
ISNULL(B.sAdi, '') AS sParcaAdi
|
||||||
FROM dbo.spUrtOnMLHammaddeTuru T WITH (NOLOCK)
|
FROM dbo.spUrtOnMLHammaddeTuru T
|
||||||
INNER JOIN req R
|
INNER JOIN req R
|
||||||
ON R.nHammaddeTuruNo = T.nHammaddeTuruNo
|
ON R.nHammaddeTuruNo = T.nHammaddeTuruNo
|
||||||
LEFT JOIN dbo.spUrtMTBolum B WITH (NOLOCK)
|
LEFT JOIN dbo.spUrtMTBolum B
|
||||||
ON B.nUrtMTBolumID = T.MTnUrtMTBolumID
|
ON B.nUrtMTBolumID = T.MTnUrtMTBolumID
|
||||||
AND ISNULL(B.nUrtTipiID, 0) = 1
|
AND ISNULL(B.nUrtTipiID, 0) = 1
|
||||||
WHERE ISNULL(T.bAktif, 0) = 1
|
WHERE ISNULL(T.bAktif, 0) = 1
|
||||||
@@ -1373,8 +1460,8 @@ SELECT TOP (@p3)
|
|||||||
-- We intentionally don't depend on mk_MaliyetParcaEslestirme_vmiktarlar.bAktif
|
-- We intentionally don't depend on mk_MaliyetParcaEslestirme_vmiktarlar.bAktif
|
||||||
-- to keep this feature robust across schema changes; only active hammadde types are listed.
|
-- to keep this feature robust across schema changes; only active hammadde types are listed.
|
||||||
CAST(1 AS bit) AS bAktif
|
CAST(1 AS bit) AS bAktif
|
||||||
FROM dbo.mk_MaliyetParcaEslestirme_vmiktarlar V WITH (NOLOCK)
|
FROM dbo.mk_MaliyetParcaEslestirme_vmiktarlar V
|
||||||
LEFT JOIN dbo.spUrtOnMLHammaddeTuru H WITH (NOLOCK)
|
LEFT JOIN dbo.spUrtOnMLHammaddeTuru H
|
||||||
ON H.nHammaddeTuruNo = V.nHammaddeTuruNo
|
ON H.nHammaddeTuruNo = V.nHammaddeTuruNo
|
||||||
WHERE
|
WHERE
|
||||||
(@p1 = '' OR CONVERT(VARCHAR(32), ISNULL(V.nHammaddeTuruNo, 0)) LIKE @p2)
|
(@p1 = '' OR CONVERT(VARCHAR(32), ISNULL(V.nHammaddeTuruNo, 0)) LIKE @p2)
|
||||||
@@ -1427,8 +1514,8 @@ func RefreshProductionProductCostingDefaultQty(ctx context.Context, uretimDB *sq
|
|||||||
D.dteIslemTarihi DESC,
|
D.dteIslemTarihi DESC,
|
||||||
D.nOnMLDetNo DESC
|
D.nOnMLDetNo DESC
|
||||||
) AS rn
|
) AS rn
|
||||||
FROM dbo.spUrtOnMLMasDet D WITH (NOLOCK)
|
FROM dbo.spUrtOnMLMasDet D
|
||||||
INNER JOIN dbo.spUrtOnMLMas M WITH (NOLOCK)
|
INNER JOIN dbo.spUrtOnMLMas M
|
||||||
ON M.nOnMLNo = D.nOnMLNo
|
ON M.nOnMLNo = D.nOnMLNo
|
||||||
WHERE ISNULL(D.lMiktar, 0) > 0
|
WHERE ISNULL(D.lMiktar, 0) > 0
|
||||||
AND ISNULL(M.bIptal, 0) = 0
|
AND ISNULL(M.bIptal, 0) = 0
|
||||||
@@ -1470,8 +1557,8 @@ func CalcProductionProductCostingDefaultQtyFromLastOnML(ctx context.Context, ure
|
|||||||
;WITH ranked AS (
|
;WITH ranked AS (
|
||||||
SELECT TOP (@p2)
|
SELECT TOP (@p2)
|
||||||
ISNULL(D.lMiktar, 0) AS lMiktar
|
ISNULL(D.lMiktar, 0) AS lMiktar
|
||||||
FROM dbo.spUrtOnMLMasDet D WITH (NOLOCK)
|
FROM dbo.spUrtOnMLMasDet D
|
||||||
INNER JOIN dbo.spUrtOnMLMas M WITH (NOLOCK)
|
INNER JOIN dbo.spUrtOnMLMas M
|
||||||
ON M.nOnMLNo = D.nOnMLNo
|
ON M.nOnMLNo = D.nOnMLNo
|
||||||
WHERE D.nHammaddeTuruNo = @p1
|
WHERE D.nHammaddeTuruNo = @p1
|
||||||
AND ISNULL(D.lMiktar, 0) > 0
|
AND ISNULL(D.lMiktar, 0) > 0
|
||||||
@@ -1535,9 +1622,9 @@ WITH req(nHammaddeTuruNo) AS (
|
|||||||
ISNULL(H.sAciklama, '') AS sAciklama,
|
ISNULL(H.sAciklama, '') AS sAciklama,
|
||||||
CAST(ISNULL(V.lDefaultMiktar, 0) AS FLOAT) AS lDefaultMiktar
|
CAST(ISNULL(V.lDefaultMiktar, 0) AS FLOAT) AS lDefaultMiktar
|
||||||
FROM req R
|
FROM req R
|
||||||
INNER JOIN dbo.mk_MaliyetParcaEslestirme_vmiktarlar V WITH (NOLOCK)
|
INNER JOIN dbo.mk_MaliyetParcaEslestirme_vmiktarlar V
|
||||||
ON V.nHammaddeTuruNo = R.nHammaddeTuruNo
|
ON V.nHammaddeTuruNo = R.nHammaddeTuruNo
|
||||||
INNER JOIN dbo.spUrtOnMLHammaddeTuru H WITH (NOLOCK)
|
INNER JOIN dbo.spUrtOnMLHammaddeTuru H
|
||||||
ON H.nHammaddeTuruNo = V.nHammaddeTuruNo
|
ON H.nHammaddeTuruNo = V.nHammaddeTuruNo
|
||||||
WHERE ISNULL(H.bAktif, 0) = 1;
|
WHERE ISNULL(H.bAktif, 0) = 1;
|
||||||
`
|
`
|
||||||
@@ -1653,8 +1740,8 @@ ranked AS (
|
|||||||
M.nOnMLNo DESC,
|
M.nOnMLNo DESC,
|
||||||
D.nOnMLDetNo DESC
|
D.nOnMLDetNo DESC
|
||||||
) AS rn
|
) AS rn
|
||||||
FROM dbo.spUrtOnMLMasDet D WITH (NOLOCK)
|
FROM dbo.spUrtOnMLMasDet D
|
||||||
INNER JOIN dbo.spUrtOnMLMas M WITH (NOLOCK)
|
INNER JOIN dbo.spUrtOnMLMas M
|
||||||
ON M.nOnMLNo = D.nOnMLNo
|
ON M.nOnMLNo = D.nOnMLNo
|
||||||
INNER JOIN req R
|
INNER JOIN req R
|
||||||
ON R.nHammaddeTuruNo = D.nHammaddeTuruNo
|
ON R.nHammaddeTuruNo = D.nHammaddeTuruNo
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ SELECT
|
|||||||
CAST(@p1 AS NVARCHAR(30)) AS ProductCode,
|
CAST(@p1 AS NVARCHAR(30)) AS ProductCode,
|
||||||
LTRIM(RTRIM(c.ColorCode)) AS ColorCode,
|
LTRIM(RTRIM(c.ColorCode)) AS ColorCode,
|
||||||
ISNULL(NULLIF(LTRIM(RTRIM(cd.ColorDescription)), ''), ISNULL(NULLIF(LTRIM(RTRIM(c.ColorHex)), ''), LTRIM(RTRIM(c.ColorCode)))) AS ColorDescription
|
ISNULL(NULLIF(LTRIM(RTRIM(cd.ColorDescription)), ''), ISNULL(NULLIF(LTRIM(RTRIM(c.ColorHex)), ''), LTRIM(RTRIM(c.ColorCode)))) AS ColorDescription
|
||||||
FROM dbo.cdColor AS c WITH(NOLOCK)
|
FROM dbo.cdColor AS c
|
||||||
LEFT JOIN dbo.cdColorDesc AS cd WITH(NOLOCK)
|
LEFT JOIN dbo.cdColorDesc AS cd
|
||||||
ON cd.ColorCode = c.ColorCode
|
ON cd.ColorCode = c.ColorCode
|
||||||
AND cd.LangCode = 'TR'
|
AND cd.LangCode = 'TR'
|
||||||
WHERE ISNULL(c.IsBlocked, 0) = 0
|
WHERE ISNULL(c.IsBlocked, 0) = 0
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ SELECT
|
|||||||
LTRIM(RTRIM(ISNULL(@ColorCode, ''))) AS ColorCode,
|
LTRIM(RTRIM(ISNULL(@ColorCode, ''))) AS ColorCode,
|
||||||
LTRIM(RTRIM(d2.ItemDim2Code)) AS ItemDim2Code,
|
LTRIM(RTRIM(d2.ItemDim2Code)) AS ItemDim2Code,
|
||||||
ISNULL(NULLIF(LTRIM(RTRIM(cd.ColorDescription)), ''), LTRIM(RTRIM(d2.ItemDim2Code))) AS ColorDescription
|
ISNULL(NULLIF(LTRIM(RTRIM(cd.ColorDescription)), ''), LTRIM(RTRIM(d2.ItemDim2Code))) AS ColorDescription
|
||||||
FROM dbo.cdItemDim2 AS d2 WITH(NOLOCK)
|
FROM dbo.cdItemDim2 AS d2
|
||||||
LEFT JOIN dbo.cdColorDesc AS cd WITH(NOLOCK)
|
LEFT JOIN dbo.cdColorDesc AS cd
|
||||||
ON cd.ColorCode = d2.ItemDim2Code
|
ON cd.ColorCode = d2.ItemDim2Code
|
||||||
AND cd.LangCode = 'TR'
|
AND cd.LangCode = 'TR'
|
||||||
WHERE ISNULL(d2.IsBlocked, 0) = 0
|
WHERE ISNULL(d2.IsBlocked, 0) = 0
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ SELECT
|
|||||||
ISNULL(prItemVariant.ColorCode, '') AS ColorCode,
|
ISNULL(prItemVariant.ColorCode, '') AS ColorCode,
|
||||||
ISNULL(prItemVariant.ItemDim2Code, '') AS ItemDim2Code,
|
ISNULL(prItemVariant.ItemDim2Code, '') AS ItemDim2Code,
|
||||||
ISNULL(ColorDesc.ColorDescription, '') AS ColorDescription
|
ISNULL(ColorDesc.ColorDescription, '') AS ColorDescription
|
||||||
FROM prItemVariant WITH(NOLOCK)
|
FROM prItemVariant
|
||||||
INNER JOIN ProductFilterWithDescription('TR') AS Product
|
INNER JOIN ProductFilterWithDescription('TR') AS Product
|
||||||
ON prItemVariant.ItemCode = Product.ProductCode
|
ON prItemVariant.ItemCode = Product.ProductCode
|
||||||
LEFT JOIN cdColorDesc AS ColorDesc WITH(NOLOCK)
|
LEFT JOIN cdColorDesc AS ColorDesc
|
||||||
ON ColorDesc.ColorCode = prItemVariant.ItemDim2Code
|
ON ColorDesc.ColorCode = prItemVariant.ItemDim2Code
|
||||||
AND ColorDesc.LangCode = 'TR'
|
AND ColorDesc.LangCode = 'TR'
|
||||||
WHERE Product.ProductCode = @ProductCode
|
WHERE Product.ProductCode = @ProductCode
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package queries
|
|||||||
// GetProductStockQuery:
|
// GetProductStockQuery:
|
||||||
// Urun kodu bazli, STOCK/PICK/RESERVE/DISP ayrik CTE ile optimize sorgu.
|
// Urun kodu bazli, STOCK/PICK/RESERVE/DISP ayrik CTE ile optimize sorgu.
|
||||||
const GetProductStockQuery = `
|
const GetProductStockQuery = `
|
||||||
DECLARE @ProductCode NVARCHAR(50) = @p1;
|
DECLARE @ProductCode NVARCHAR(50) = UPPER(LTRIM(RTRIM(@p1)));
|
||||||
|
|
||||||
;WITH STOCK AS
|
;WITH STOCK AS
|
||||||
(
|
(
|
||||||
@@ -14,20 +14,20 @@ DECLARE @ProductCode NVARCHAR(50) = @p1;
|
|||||||
StoreCode,
|
StoreCode,
|
||||||
WarehouseCode,
|
WarehouseCode,
|
||||||
ItemTypeCode,
|
ItemTypeCode,
|
||||||
ItemCode,
|
UPPER(LTRIM(RTRIM(ItemCode))) AS ItemCode,
|
||||||
ColorCode,
|
ColorCode,
|
||||||
ItemDim1Code,
|
ItemDim1Code,
|
||||||
ItemDim2Code,
|
ItemDim2Code,
|
||||||
ItemDim3Code,
|
ItemDim3Code,
|
||||||
SUM(In_Qty1 - Out_Qty1) AS InventoryQty1
|
SUM(In_Qty1 - Out_Qty1) AS InventoryQty1
|
||||||
FROM trStock WITH(NOLOCK)
|
FROM trStock
|
||||||
WHERE ItemTypeCode = 1
|
WHERE ItemTypeCode = 1
|
||||||
AND ItemCode = @ProductCode
|
AND UPPER(LTRIM(RTRIM(ItemCode))) = @ProductCode
|
||||||
AND LEN(ItemCode) = 13
|
AND LEN(LTRIM(RTRIM(ItemCode))) = 13
|
||||||
AND LEN(@ProductCode) = 13
|
AND LEN(@ProductCode) = 13
|
||||||
GROUP BY
|
GROUP BY
|
||||||
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||||
ItemTypeCode, ItemCode, ColorCode,
|
ItemTypeCode, UPPER(LTRIM(RTRIM(ItemCode))), ColorCode,
|
||||||
ItemDim1Code, ItemDim2Code, ItemDim3Code
|
ItemDim1Code, ItemDim2Code, ItemDim3Code
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -35,17 +35,17 @@ PICK AS
|
|||||||
(
|
(
|
||||||
SELECT
|
SELECT
|
||||||
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||||
ItemTypeCode, ItemCode, ColorCode,
|
ItemTypeCode, UPPER(LTRIM(RTRIM(ItemCode))) AS ItemCode, ColorCode,
|
||||||
ItemDim1Code, ItemDim2Code, ItemDim3Code,
|
ItemDim1Code, ItemDim2Code, ItemDim3Code,
|
||||||
SUM(Qty1) AS PickingQty1
|
SUM(Qty1) AS PickingQty1
|
||||||
FROM PickingStates
|
FROM PickingStates
|
||||||
WHERE ItemTypeCode = 1
|
WHERE ItemTypeCode = 1
|
||||||
AND ItemCode = @ProductCode
|
AND UPPER(LTRIM(RTRIM(ItemCode))) = @ProductCode
|
||||||
AND LEN(ItemCode) = 13
|
AND LEN(LTRIM(RTRIM(ItemCode))) = 13
|
||||||
AND LEN(@ProductCode) = 13
|
AND LEN(@ProductCode) = 13
|
||||||
GROUP BY
|
GROUP BY
|
||||||
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||||
ItemTypeCode, ItemCode, ColorCode,
|
ItemTypeCode, UPPER(LTRIM(RTRIM(ItemCode))), ColorCode,
|
||||||
ItemDim1Code, ItemDim2Code, ItemDim3Code
|
ItemDim1Code, ItemDim2Code, ItemDim3Code
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -53,17 +53,17 @@ RESERVE AS
|
|||||||
(
|
(
|
||||||
SELECT
|
SELECT
|
||||||
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||||
ItemTypeCode, ItemCode, ColorCode,
|
ItemTypeCode, UPPER(LTRIM(RTRIM(ItemCode))) AS ItemCode, ColorCode,
|
||||||
ItemDim1Code, ItemDim2Code, ItemDim3Code,
|
ItemDim1Code, ItemDim2Code, ItemDim3Code,
|
||||||
SUM(Qty1) AS ReserveQty1
|
SUM(Qty1) AS ReserveQty1
|
||||||
FROM ReserveStates
|
FROM ReserveStates
|
||||||
WHERE ItemTypeCode = 1
|
WHERE ItemTypeCode = 1
|
||||||
AND ItemCode = @ProductCode
|
AND UPPER(LTRIM(RTRIM(ItemCode))) = @ProductCode
|
||||||
AND LEN(ItemCode) = 13
|
AND LEN(LTRIM(RTRIM(ItemCode))) = 13
|
||||||
AND LEN(@ProductCode) = 13
|
AND LEN(@ProductCode) = 13
|
||||||
GROUP BY
|
GROUP BY
|
||||||
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||||
ItemTypeCode, ItemCode, ColorCode,
|
ItemTypeCode, UPPER(LTRIM(RTRIM(ItemCode))), ColorCode,
|
||||||
ItemDim1Code, ItemDim2Code, ItemDim3Code
|
ItemDim1Code, ItemDim2Code, ItemDim3Code
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -71,17 +71,17 @@ DISP AS
|
|||||||
(
|
(
|
||||||
SELECT
|
SELECT
|
||||||
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||||
ItemTypeCode, ItemCode, ColorCode,
|
ItemTypeCode, UPPER(LTRIM(RTRIM(ItemCode))) AS ItemCode, ColorCode,
|
||||||
ItemDim1Code, ItemDim2Code, ItemDim3Code,
|
ItemDim1Code, ItemDim2Code, ItemDim3Code,
|
||||||
SUM(Qty1) AS DispOrderQty1
|
SUM(Qty1) AS DispOrderQty1
|
||||||
FROM DispOrderStates
|
FROM DispOrderStates
|
||||||
WHERE ItemTypeCode = 1
|
WHERE ItemTypeCode = 1
|
||||||
AND ItemCode = @ProductCode
|
AND UPPER(LTRIM(RTRIM(ItemCode))) = @ProductCode
|
||||||
AND LEN(ItemCode) = 13
|
AND LEN(LTRIM(RTRIM(ItemCode))) = 13
|
||||||
AND LEN(@ProductCode) = 13
|
AND LEN(@ProductCode) = 13
|
||||||
GROUP BY
|
GROUP BY
|
||||||
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
CompanyCode, OfficeCode, StoreTypeCode, StoreCode, WarehouseCode,
|
||||||
ItemTypeCode, ItemCode, ColorCode,
|
ItemTypeCode, UPPER(LTRIM(RTRIM(ItemCode))), ColorCode,
|
||||||
ItemDim1Code, ItemDim2Code, ItemDim3Code
|
ItemDim1Code, ItemDim2Code, ItemDim3Code
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -184,25 +184,25 @@ AND DP.ItemDim1Code=S.ItemDim1Code
|
|||||||
AND DP.ItemDim2Code=S.ItemDim2Code
|
AND DP.ItemDim2Code=S.ItemDim2Code
|
||||||
AND DP.ItemDim3Code=S.ItemDim3Code
|
AND DP.ItemDim3Code=S.ItemDim3Code
|
||||||
|
|
||||||
JOIN cdItem WITH(NOLOCK)
|
JOIN cdItem
|
||||||
ON S.ItemCode = cdItem.ItemCode
|
ON UPPER(LTRIM(RTRIM(S.ItemCode))) = UPPER(LTRIM(RTRIM(cdItem.ItemCode)))
|
||||||
AND S.ItemTypeCode = cdItem.ItemTypeCode
|
AND S.ItemTypeCode = cdItem.ItemTypeCode
|
||||||
|
|
||||||
LEFT JOIN cdUnitOfMeasure WITH(NOLOCK)
|
LEFT JOIN cdUnitOfMeasure
|
||||||
ON cdItem.UnitOfMeasureCode1 = cdUnitOfMeasure.UnitOfMeasureCode
|
ON cdItem.UnitOfMeasureCode1 = cdUnitOfMeasure.UnitOfMeasureCode
|
||||||
|
|
||||||
LEFT JOIN ProductFilterWithDescription('TR') P
|
LEFT JOIN ProductFilterWithDescription('TR') P
|
||||||
ON P.ProductCode = S.ItemCode
|
ON UPPER(LTRIM(RTRIM(P.ProductCode))) = UPPER(LTRIM(RTRIM(S.ItemCode)))
|
||||||
|
|
||||||
LEFT JOIN bsItemTypeDesc WITH(NOLOCK)
|
LEFT JOIN bsItemTypeDesc
|
||||||
ON bsItemTypeDesc.ItemTypeCode = S.ItemTypeCode
|
ON bsItemTypeDesc.ItemTypeCode = S.ItemTypeCode
|
||||||
AND bsItemTypeDesc.LangCode='TR'
|
AND bsItemTypeDesc.LangCode='TR'
|
||||||
|
|
||||||
LEFT JOIN cdWarehouseDesc W WITH(NOLOCK)
|
LEFT JOIN cdWarehouseDesc W
|
||||||
ON W.WarehouseCode = S.WarehouseCode
|
ON W.WarehouseCode = S.WarehouseCode
|
||||||
AND W.LangCode='TR'
|
AND W.LangCode='TR'
|
||||||
|
|
||||||
LEFT JOIN cdColorDesc C WITH(NOLOCK)
|
LEFT JOIN cdColorDesc C
|
||||||
ON C.ColorCode = S.ColorCode
|
ON C.ColorCode = S.ColorCode
|
||||||
AND C.LangCode='TR'
|
AND C.LangCode='TR'
|
||||||
|
|
||||||
@@ -212,16 +212,16 @@ LEFT JOIN (
|
|||||||
ItemTypeCode,
|
ItemTypeCode,
|
||||||
Price,
|
Price,
|
||||||
ROW_NUMBER() OVER (PARTITION BY ItemCode, ItemTypeCode ORDER BY PriceDate DESC) AS RowNum
|
ROW_NUMBER() OVER (PARTITION BY ItemCode, ItemTypeCode ORDER BY PriceDate DESC) AS RowNum
|
||||||
FROM prItemBasePrice WITH(NOLOCK)
|
FROM prItemBasePrice
|
||||||
) prFilteredBasePrice
|
) prFilteredBasePrice
|
||||||
ON prFilteredBasePrice.ItemCode = S.ItemCode
|
ON UPPER(LTRIM(RTRIM(prFilteredBasePrice.ItemCode))) = UPPER(LTRIM(RTRIM(S.ItemCode)))
|
||||||
AND prFilteredBasePrice.ItemTypeCode = S.ItemTypeCode
|
AND prFilteredBasePrice.ItemTypeCode = S.ItemTypeCode
|
||||||
AND prFilteredBasePrice.RowNum = 1
|
AND prFilteredBasePrice.RowNum = 1
|
||||||
|
|
||||||
WHERE
|
WHERE
|
||||||
S.ItemTypeCode IN (1)
|
S.ItemTypeCode IN (1)
|
||||||
AND S.ItemCode = @ProductCode
|
AND UPPER(LTRIM(RTRIM(S.ItemCode))) = @ProductCode
|
||||||
AND LEN(S.ItemCode) = 13
|
AND LEN(LTRIM(RTRIM(S.ItemCode))) = 13
|
||||||
AND LEN(@ProductCode) = 13
|
AND LEN(@ProductCode) = 13
|
||||||
AND (
|
AND (
|
||||||
S.InventoryQty1
|
S.InventoryQty1
|
||||||
@@ -234,6 +234,6 @@ WHERE
|
|||||||
(
|
(
|
||||||
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
||||||
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
||||||
'1-0-33','101','1-014','1-0-49','1-0-36'
|
'1-0-33','101','1-014','1-0-49','1-0-36','1-0-4','1-0-29'
|
||||||
);
|
);
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ IF OBJECT_ID('dbo.ProductFilterTRCache','U') IS NOT NULL
|
|||||||
BEGIN
|
BEGIN
|
||||||
INSERT INTO #AttrBase (ProductCode, Kategori, UrunAnaGrubu, UrunAltGrubu, UrunIcerigi, Fit, DropVal)
|
INSERT INTO #AttrBase (ProductCode, Kategori, UrunAnaGrubu, UrunAltGrubu, UrunIcerigi, Fit, DropVal)
|
||||||
SELECT
|
SELECT
|
||||||
ProductCode,
|
UPPER(LTRIM(RTRIM(ProductCode))) AS ProductCode,
|
||||||
Kategori = LTRIM(RTRIM(ProductAtt44Desc)),
|
Kategori = LTRIM(RTRIM(ProductAtt44Desc)),
|
||||||
UrunAnaGrubu = LTRIM(RTRIM(ProductAtt01Desc)),
|
UrunAnaGrubu = LTRIM(RTRIM(ProductAtt01Desc)),
|
||||||
UrunAltGrubu = LTRIM(RTRIM(ProductAtt02Desc)),
|
UrunAltGrubu = LTRIM(RTRIM(ProductAtt02Desc)),
|
||||||
@@ -48,7 +48,7 @@ ELSE
|
|||||||
BEGIN
|
BEGIN
|
||||||
INSERT INTO #AttrBase (ProductCode, Kategori, UrunAnaGrubu, UrunAltGrubu, UrunIcerigi, Fit, DropVal)
|
INSERT INTO #AttrBase (ProductCode, Kategori, UrunAnaGrubu, UrunAltGrubu, UrunIcerigi, Fit, DropVal)
|
||||||
SELECT
|
SELECT
|
||||||
ProductCode,
|
UPPER(LTRIM(RTRIM(ProductCode))) AS ProductCode,
|
||||||
Kategori = LTRIM(RTRIM(ProductAtt44Desc)),
|
Kategori = LTRIM(RTRIM(ProductAtt44Desc)),
|
||||||
UrunAnaGrubu = LTRIM(RTRIM(ProductAtt01Desc)),
|
UrunAnaGrubu = LTRIM(RTRIM(ProductAtt01Desc)),
|
||||||
UrunAltGrubu = LTRIM(RTRIM(ProductAtt02Desc)),
|
UrunAltGrubu = LTRIM(RTRIM(ProductAtt02Desc)),
|
||||||
@@ -101,40 +101,40 @@ CREATE NONCLUSTERED INDEX IX_AttrBase_Filter ON #AttrBase(Kategori, UrunAnaGrubu
|
|||||||
FROM
|
FROM
|
||||||
(
|
(
|
||||||
SELECT
|
SELECT
|
||||||
P.ItemCode, P.ColorCode, P.ItemDim1Code, P.ItemDim2Code,
|
UPPER(LTRIM(RTRIM(P.ItemCode))) AS ItemCode, P.ColorCode, P.ItemDim1Code, P.ItemDim2Code,
|
||||||
P.Qty1 AS PickingQty1, 0 AS ReserveQty1, 0 AS DispOrderQty1, 0 AS InventoryQty1
|
P.Qty1 AS PickingQty1, 0 AS ReserveQty1, 0 AS DispOrderQty1, 0 AS InventoryQty1
|
||||||
FROM PickingStates P
|
FROM PickingStates P
|
||||||
INNER JOIN #AttrBase AB ON AB.ProductCode = P.ItemCode
|
INNER JOIN #AttrBase AB ON AB.ProductCode = UPPER(LTRIM(RTRIM(P.ItemCode)))
|
||||||
WHERE P.ItemTypeCode = 1
|
WHERE P.ItemTypeCode = 1
|
||||||
AND LEN(P.ItemCode) = 13
|
AND LEN(LTRIM(RTRIM(P.ItemCode))) = 13
|
||||||
|
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT
|
SELECT
|
||||||
R.ItemCode, R.ColorCode, R.ItemDim1Code, R.ItemDim2Code,
|
UPPER(LTRIM(RTRIM(R.ItemCode))) AS ItemCode, R.ColorCode, R.ItemDim1Code, R.ItemDim2Code,
|
||||||
0, R.Qty1, 0, 0
|
0, R.Qty1, 0, 0
|
||||||
FROM ReserveStates R
|
FROM ReserveStates R
|
||||||
INNER JOIN #AttrBase AB ON AB.ProductCode = R.ItemCode
|
INNER JOIN #AttrBase AB ON AB.ProductCode = UPPER(LTRIM(RTRIM(R.ItemCode)))
|
||||||
WHERE R.ItemTypeCode = 1
|
WHERE R.ItemTypeCode = 1
|
||||||
AND LEN(R.ItemCode) = 13
|
AND LEN(LTRIM(RTRIM(R.ItemCode))) = 13
|
||||||
|
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT
|
SELECT
|
||||||
D.ItemCode, D.ColorCode, D.ItemDim1Code, D.ItemDim2Code,
|
UPPER(LTRIM(RTRIM(D.ItemCode))) AS ItemCode, D.ColorCode, D.ItemDim1Code, D.ItemDim2Code,
|
||||||
0, 0, D.Qty1, 0
|
0, 0, D.Qty1, 0
|
||||||
FROM DispOrderStates D
|
FROM DispOrderStates D
|
||||||
INNER JOIN #AttrBase AB ON AB.ProductCode = D.ItemCode
|
INNER JOIN #AttrBase AB ON AB.ProductCode = UPPER(LTRIM(RTRIM(D.ItemCode)))
|
||||||
WHERE D.ItemTypeCode = 1
|
WHERE D.ItemTypeCode = 1
|
||||||
AND LEN(D.ItemCode) = 13
|
AND LEN(LTRIM(RTRIM(D.ItemCode))) = 13
|
||||||
|
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT
|
SELECT
|
||||||
T.ItemCode, T.ColorCode, T.ItemDim1Code, T.ItemDim2Code,
|
UPPER(LTRIM(RTRIM(T.ItemCode))) AS ItemCode, T.ColorCode, T.ItemDim1Code, T.ItemDim2Code,
|
||||||
0, 0, 0, SUM(T.In_Qty1 - T.Out_Qty1)
|
0, 0, 0, SUM(T.In_Qty1 - T.Out_Qty1)
|
||||||
FROM trStock T WITH (NOLOCK)
|
FROM trStock T
|
||||||
INNER JOIN #AttrBase AB ON AB.ProductCode = T.ItemCode
|
INNER JOIN #AttrBase AB ON AB.ProductCode = UPPER(LTRIM(RTRIM(T.ItemCode)))
|
||||||
WHERE T.ItemTypeCode = 1
|
WHERE T.ItemTypeCode = 1
|
||||||
AND LEN(T.ItemCode) = 13
|
AND LEN(LTRIM(RTRIM(T.ItemCode))) = 13
|
||||||
GROUP BY T.ItemCode, T.ColorCode, T.ItemDim1Code, T.ItemDim2Code
|
GROUP BY UPPER(LTRIM(RTRIM(T.ItemCode))), T.ColorCode, T.ItemDim1Code, T.ItemDim2Code
|
||||||
) X
|
) X
|
||||||
GROUP BY X.ItemCode, X.ColorCode, X.ItemDim1Code, X.ItemDim2Code
|
GROUP BY X.ItemCode, X.ColorCode, X.ItemDim1Code, X.ItemDim2Code
|
||||||
),
|
),
|
||||||
@@ -148,7 +148,7 @@ Avail AS
|
|||||||
Beden = LTRIM(RTRIM(I.ItemDim1Code)),
|
Beden = LTRIM(RTRIM(I.ItemDim1Code)),
|
||||||
Kullanilabilir = (I.InventoryQty1 - I.PickingQty1 - I.ReserveQty1 - I.DispOrderQty1)
|
Kullanilabilir = (I.InventoryQty1 - I.PickingQty1 - I.ReserveQty1 - I.DispOrderQty1)
|
||||||
FROM INV I
|
FROM INV I
|
||||||
LEFT JOIN cdColorDesc C WITH (NOLOCK)
|
LEFT JOIN cdColorDesc C
|
||||||
ON C.ColorCode = I.ColorCode
|
ON C.ColorCode = I.ColorCode
|
||||||
AND C.LangCode = 'TR'
|
AND C.LangCode = 'TR'
|
||||||
WHERE (I.InventoryQty1 - I.PickingQty1 - I.ReserveQty1 - I.DispOrderQty1) > 0
|
WHERE (I.InventoryQty1 - I.PickingQty1 - I.ReserveQty1 - I.DispOrderQty1) > 0
|
||||||
@@ -180,7 +180,7 @@ SELECT 'renk', X.FieldValue
|
|||||||
FROM (
|
FROM (
|
||||||
SELECT DISTINCT FieldValue = CASE WHEN A.RenkAciklama <> '' THEN A.RenkAciklama ELSE A.Renk END
|
SELECT DISTINCT FieldValue = CASE WHEN A.RenkAciklama <> '' THEN A.RenkAciklama ELSE A.Renk END
|
||||||
FROM Avail A
|
FROM Avail A
|
||||||
INNER JOIN #AttrBase AB ON AB.ProductCode = A.ItemCode
|
INNER JOIN #AttrBase AB ON AB.ProductCode = UPPER(LTRIM(RTRIM(A.ItemCode)))
|
||||||
WHERE (CASE WHEN A.RenkAciklama <> '' THEN A.RenkAciklama ELSE A.Renk END) <> ''
|
WHERE (CASE WHEN A.RenkAciklama <> '' THEN A.RenkAciklama ELSE A.Renk END) <> ''
|
||||||
AND (@Kategori IS NULL OR AB.Kategori = @Kategori)
|
AND (@Kategori IS NULL OR AB.Kategori = @Kategori)
|
||||||
AND (@UrunAnaGrubu IS NULL OR AB.UrunAnaGrubu = @UrunAnaGrubu)
|
AND (@UrunAnaGrubu IS NULL OR AB.UrunAnaGrubu = @UrunAnaGrubu)
|
||||||
@@ -196,7 +196,7 @@ SELECT 'renk2', X.FieldValue
|
|||||||
FROM (
|
FROM (
|
||||||
SELECT DISTINCT FieldValue = A.Renk2
|
SELECT DISTINCT FieldValue = A.Renk2
|
||||||
FROM Avail A
|
FROM Avail A
|
||||||
INNER JOIN #AttrBase AB ON AB.ProductCode = A.ItemCode
|
INNER JOIN #AttrBase AB ON AB.ProductCode = UPPER(LTRIM(RTRIM(A.ItemCode)))
|
||||||
WHERE A.Renk2 <> ''
|
WHERE A.Renk2 <> ''
|
||||||
AND (@Kategori IS NULL OR AB.Kategori = @Kategori)
|
AND (@Kategori IS NULL OR AB.Kategori = @Kategori)
|
||||||
AND (@UrunAnaGrubu IS NULL OR AB.UrunAnaGrubu = @UrunAnaGrubu)
|
AND (@UrunAnaGrubu IS NULL OR AB.UrunAnaGrubu = @UrunAnaGrubu)
|
||||||
@@ -245,7 +245,7 @@ SELECT 'beden', X.FieldValue
|
|||||||
FROM (
|
FROM (
|
||||||
SELECT DISTINCT FieldValue = A.Beden
|
SELECT DISTINCT FieldValue = A.Beden
|
||||||
FROM Avail A
|
FROM Avail A
|
||||||
INNER JOIN #AttrBase AB ON AB.ProductCode = A.ItemCode
|
INNER JOIN #AttrBase AB ON AB.ProductCode = UPPER(LTRIM(RTRIM(A.ItemCode)))
|
||||||
WHERE A.Beden <> ''
|
WHERE A.Beden <> ''
|
||||||
AND (@Kategori IS NULL OR AB.Kategori = @Kategori)
|
AND (@Kategori IS NULL OR AB.Kategori = @Kategori)
|
||||||
AND (@UrunAnaGrubu IS NULL OR AB.UrunAnaGrubu = @UrunAnaGrubu)
|
AND (@UrunAnaGrubu IS NULL OR AB.UrunAnaGrubu = @UrunAnaGrubu)
|
||||||
@@ -346,7 +346,7 @@ BEGIN
|
|||||||
ProductAtt46Desc
|
ProductAtt46Desc
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
C.ProductCode,
|
UPPER(LTRIM(RTRIM(C.ProductCode))) AS ProductCode,
|
||||||
C.ProductDescription,
|
C.ProductDescription,
|
||||||
C.ProductAtt01Desc,
|
C.ProductAtt01Desc,
|
||||||
C.ProductAtt02Desc,
|
C.ProductAtt02Desc,
|
||||||
@@ -379,7 +379,7 @@ BEGIN
|
|||||||
ISNULL(PF.ProductAtt45Desc, '') AS ProductAtt45Desc,
|
ISNULL(PF.ProductAtt45Desc, '') AS ProductAtt45Desc,
|
||||||
ISNULL(PF.ProductAtt46Desc, '') AS ProductAtt46Desc
|
ISNULL(PF.ProductAtt46Desc, '') AS ProductAtt46Desc
|
||||||
FROM dbo.ProductFilterTRCache C
|
FROM dbo.ProductFilterTRCache C
|
||||||
LEFT JOIN ProductFilterWithDescription('TR') PF ON PF.ProductCode = C.ProductCode
|
LEFT JOIN ProductFilterWithDescription('TR') PF ON UPPER(LTRIM(RTRIM(PF.ProductCode))) = UPPER(LTRIM(RTRIM(C.ProductCode)))
|
||||||
WHERE LEN(C.ProductCode) = 13
|
WHERE LEN(C.ProductCode) = 13
|
||||||
AND (@Kategori IS NULL OR C.ProductAtt44Desc = @Kategori)
|
AND (@Kategori IS NULL OR C.ProductAtt44Desc = @Kategori)
|
||||||
AND (@UrunAnaGrubu IS NULL OR C.ProductAtt01Desc = @UrunAnaGrubu)
|
AND (@UrunAnaGrubu IS NULL OR C.ProductAtt01Desc = @UrunAnaGrubu)
|
||||||
@@ -390,9 +390,9 @@ BEGIN
|
|||||||
END
|
END
|
||||||
ELSE
|
ELSE
|
||||||
BEGIN
|
BEGIN
|
||||||
INSERT INTO #AttrFiltered
|
INSERT INTO #AttrFiltered
|
||||||
(
|
(
|
||||||
ProductCode,
|
ProductCode,
|
||||||
ProductDescription,
|
ProductDescription,
|
||||||
ProductAtt01Desc,
|
ProductAtt01Desc,
|
||||||
ProductAtt02Desc,
|
ProductAtt02Desc,
|
||||||
@@ -426,7 +426,7 @@ BEGIN
|
|||||||
ProductAtt46Desc
|
ProductAtt46Desc
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
ProductCode,
|
UPPER(LTRIM(RTRIM(ProductCode))) AS ProductCode,
|
||||||
ProductDescription,
|
ProductDescription,
|
||||||
ProductAtt01Desc,
|
ProductAtt01Desc,
|
||||||
ProductAtt02Desc,
|
ProductAtt02Desc,
|
||||||
@@ -493,69 +493,69 @@ INV AS
|
|||||||
(
|
(
|
||||||
SELECT
|
SELECT
|
||||||
P.CompanyCode, P.OfficeCode, P.StoreTypeCode, P.StoreCode, P.WarehouseCode,
|
P.CompanyCode, P.OfficeCode, P.StoreTypeCode, P.StoreCode, P.WarehouseCode,
|
||||||
P.ItemTypeCode, P.ItemCode, P.ColorCode, P.ItemDim1Code, P.ItemDim2Code, P.ItemDim3Code,
|
P.ItemTypeCode, UPPER(LTRIM(RTRIM(P.ItemCode))) AS ItemCode, P.ColorCode, P.ItemDim1Code, P.ItemDim2Code, P.ItemDim3Code,
|
||||||
P.Qty1 AS PickingQty1, 0 AS ReserveQty1, 0 AS DispOrderQty1, 0 AS InventoryQty1
|
P.Qty1 AS PickingQty1, 0 AS ReserveQty1, 0 AS DispOrderQty1, 0 AS InventoryQty1
|
||||||
FROM PickingStates P
|
FROM PickingStates P
|
||||||
INNER JOIN #AttrFiltered AF ON AF.ProductCode = P.ItemCode
|
INNER JOIN #AttrFiltered AF ON AF.ProductCode = UPPER(LTRIM(RTRIM(P.ItemCode)))
|
||||||
WHERE P.ItemTypeCode = 1
|
WHERE P.ItemTypeCode = 1
|
||||||
AND LEN(P.ItemCode) = 13
|
AND LEN(LTRIM(RTRIM(P.ItemCode))) = 13
|
||||||
AND P.WarehouseCode IN
|
AND P.WarehouseCode IN
|
||||||
(
|
(
|
||||||
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
||||||
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
||||||
'1-0-33','101','1-014','1-0-49','1-0-36'
|
'1-0-33','101','1-014','1-0-49','1-0-36','1-0-4','1-0-29'
|
||||||
)
|
)
|
||||||
|
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT
|
SELECT
|
||||||
R.CompanyCode, R.OfficeCode, R.StoreTypeCode, R.StoreCode, R.WarehouseCode,
|
R.CompanyCode, R.OfficeCode, R.StoreTypeCode, R.StoreCode, R.WarehouseCode,
|
||||||
R.ItemTypeCode, R.ItemCode, R.ColorCode, R.ItemDim1Code, R.ItemDim2Code, R.ItemDim3Code,
|
R.ItemTypeCode, UPPER(LTRIM(RTRIM(R.ItemCode))) AS ItemCode, R.ColorCode, R.ItemDim1Code, R.ItemDim2Code, R.ItemDim3Code,
|
||||||
0, R.Qty1, 0, 0
|
0, R.Qty1, 0, 0
|
||||||
FROM ReserveStates R
|
FROM ReserveStates R
|
||||||
INNER JOIN #AttrFiltered AF ON AF.ProductCode = R.ItemCode
|
INNER JOIN #AttrFiltered AF ON AF.ProductCode = UPPER(LTRIM(RTRIM(R.ItemCode)))
|
||||||
WHERE R.ItemTypeCode = 1
|
WHERE R.ItemTypeCode = 1
|
||||||
AND LEN(R.ItemCode) = 13
|
AND LEN(LTRIM(RTRIM(R.ItemCode))) = 13
|
||||||
AND R.WarehouseCode IN
|
AND R.WarehouseCode IN
|
||||||
(
|
(
|
||||||
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
||||||
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
||||||
'1-0-33','101','1-014','1-0-49','1-0-36'
|
'1-0-33','101','1-014','1-0-49','1-0-36','1-0-4','1-0-29'
|
||||||
)
|
)
|
||||||
|
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT
|
SELECT
|
||||||
D.CompanyCode, D.OfficeCode, D.StoreTypeCode, D.StoreCode, D.WarehouseCode,
|
D.CompanyCode, D.OfficeCode, D.StoreTypeCode, D.StoreCode, D.WarehouseCode,
|
||||||
D.ItemTypeCode, D.ItemCode, D.ColorCode, D.ItemDim1Code, D.ItemDim2Code, D.ItemDim3Code,
|
D.ItemTypeCode, UPPER(LTRIM(RTRIM(D.ItemCode))) AS ItemCode, D.ColorCode, D.ItemDim1Code, D.ItemDim2Code, D.ItemDim3Code,
|
||||||
0, 0, D.Qty1, 0
|
0, 0, D.Qty1, 0
|
||||||
FROM DispOrderStates D
|
FROM DispOrderStates D
|
||||||
INNER JOIN #AttrFiltered AF ON AF.ProductCode = D.ItemCode
|
INNER JOIN #AttrFiltered AF ON AF.ProductCode = UPPER(LTRIM(RTRIM(D.ItemCode)))
|
||||||
WHERE D.ItemTypeCode = 1
|
WHERE D.ItemTypeCode = 1
|
||||||
AND LEN(D.ItemCode) = 13
|
AND LEN(LTRIM(RTRIM(D.ItemCode))) = 13
|
||||||
AND D.WarehouseCode IN
|
AND D.WarehouseCode IN
|
||||||
(
|
(
|
||||||
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
||||||
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
||||||
'1-0-33','101','1-014','1-0-49','1-0-36'
|
'1-0-33','101','1-014','1-0-49','1-0-36','1-0-4','1-0-29'
|
||||||
)
|
)
|
||||||
|
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT
|
SELECT
|
||||||
T.CompanyCode, T.OfficeCode, T.StoreTypeCode, T.StoreCode, T.WarehouseCode,
|
T.CompanyCode, T.OfficeCode, T.StoreTypeCode, T.StoreCode, T.WarehouseCode,
|
||||||
T.ItemTypeCode, T.ItemCode, T.ColorCode, T.ItemDim1Code, T.ItemDim2Code, T.ItemDim3Code,
|
T.ItemTypeCode, UPPER(LTRIM(RTRIM(T.ItemCode))) AS ItemCode, T.ColorCode, T.ItemDim1Code, T.ItemDim2Code, T.ItemDim3Code,
|
||||||
0, 0, 0, SUM(T.In_Qty1 - T.Out_Qty1)
|
0, 0, 0, SUM(T.In_Qty1 - T.Out_Qty1)
|
||||||
FROM trStock T WITH (NOLOCK)
|
FROM trStock T
|
||||||
INNER JOIN #AttrFiltered AF ON AF.ProductCode = T.ItemCode
|
INNER JOIN #AttrFiltered AF ON AF.ProductCode = UPPER(LTRIM(RTRIM(T.ItemCode)))
|
||||||
WHERE T.ItemTypeCode = 1
|
WHERE T.ItemTypeCode = 1
|
||||||
AND LEN(T.ItemCode) = 13
|
AND LEN(LTRIM(RTRIM(T.ItemCode))) = 13
|
||||||
AND T.WarehouseCode IN
|
AND T.WarehouseCode IN
|
||||||
(
|
(
|
||||||
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
||||||
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
||||||
'1-0-33','101','1-014','1-0-49','1-0-36'
|
'1-0-33','101','1-014','1-0-49','1-0-36','1-0-4','1-0-29'
|
||||||
)
|
)
|
||||||
GROUP BY
|
GROUP BY
|
||||||
T.CompanyCode, T.OfficeCode, T.StoreTypeCode, T.StoreCode, T.WarehouseCode,
|
T.CompanyCode, T.OfficeCode, T.StoreTypeCode, T.StoreCode, T.WarehouseCode,
|
||||||
T.ItemTypeCode, T.ItemCode, T.ColorCode, T.ItemDim1Code, T.ItemDim2Code, T.ItemDim3Code
|
T.ItemTypeCode, UPPER(LTRIM(RTRIM(T.ItemCode))), T.ColorCode, T.ItemDim1Code, T.ItemDim2Code, T.ItemDim3Code
|
||||||
) X
|
) X
|
||||||
GROUP BY
|
GROUP BY
|
||||||
X.CompanyCode, X.OfficeCode, X.StoreTypeCode, X.StoreCode, X.WarehouseCode,
|
X.CompanyCode, X.OfficeCode, X.StoreTypeCode, X.StoreCode, X.WarehouseCode,
|
||||||
@@ -578,23 +578,23 @@ Avail AS
|
|||||||
Kullanilabilir = ROUND(I.InventoryQty1 - I.PickingQty1 - I.ReserveQty1 - I.DispOrderQty1, U.RoundDigit),
|
Kullanilabilir = ROUND(I.InventoryQty1 - I.PickingQty1 - I.ReserveQty1 - I.DispOrderQty1, U.RoundDigit),
|
||||||
RenkAciklama = LTRIM(RTRIM(C.ColorDescription))
|
RenkAciklama = LTRIM(RTRIM(C.ColorDescription))
|
||||||
FROM INV I
|
FROM INV I
|
||||||
JOIN cdItem CI WITH (NOLOCK)
|
JOIN cdItem CI
|
||||||
ON CI.ItemTypeCode = I.ItemTypeCode
|
ON CI.ItemTypeCode = I.ItemTypeCode
|
||||||
AND CI.ItemCode = I.ItemCode
|
AND UPPER(LTRIM(RTRIM(CI.ItemCode))) = I.ItemCode
|
||||||
LEFT JOIN cdUnitOfMeasure U WITH (NOLOCK)
|
LEFT JOIN cdUnitOfMeasure U
|
||||||
ON U.UnitOfMeasureCode = CI.UnitOfMeasureCode1
|
ON U.UnitOfMeasureCode = CI.UnitOfMeasureCode1
|
||||||
LEFT JOIN cdColorDesc C WITH (NOLOCK)
|
LEFT JOIN cdColorDesc C
|
||||||
ON C.ColorCode = I.ColorCode
|
ON C.ColorCode = I.ColorCode
|
||||||
AND C.LangCode = 'TR'
|
AND C.LangCode = 'TR'
|
||||||
WHERE I.ItemTypeCode = 1
|
WHERE I.ItemTypeCode = 1
|
||||||
AND LEN(I.ItemCode) = 13
|
AND LEN(LTRIM(RTRIM(I.ItemCode))) = 13
|
||||||
AND (I.InventoryQty1 - I.PickingQty1 - I.ReserveQty1 - I.DispOrderQty1) > 0
|
AND (I.InventoryQty1 - I.PickingQty1 - I.ReserveQty1 - I.DispOrderQty1) > 0
|
||||||
AND CI.IsBlocked = 0
|
AND CI.IsBlocked = 0
|
||||||
AND I.WarehouseCode IN
|
AND I.WarehouseCode IN
|
||||||
(
|
(
|
||||||
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
'1-0-14','1-0-10','1-0-8','1-2-5','1-2-4','1-0-12','100','1-0-28',
|
||||||
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
'1-0-24','1-2-6','1-1-14','1-0-2','1-0-52','1-1-2','1-0-21','1-1-3',
|
||||||
'1-0-33','101','1-014','1-0-49','1-0-36'
|
'1-0-33','101','1-014','1-0-49','1-0-36','1-0-4','1-0-29'
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
Grouped AS
|
Grouped AS
|
||||||
@@ -604,7 +604,7 @@ Grouped AS
|
|||||||
A.ColorCode,
|
A.ColorCode,
|
||||||
A.ItemDim2Code
|
A.ItemDim2Code
|
||||||
FROM Avail A
|
FROM Avail A
|
||||||
INNER JOIN #AttrFiltered AF ON AF.ProductCode = A.ItemCode
|
INNER JOIN #AttrFiltered AF ON AF.ProductCode = UPPER(LTRIM(RTRIM(A.ItemCode)))
|
||||||
WHERE (@RenkList IS NULL OR CHARINDEX(NCHAR(31) + ISNULL((CASE WHEN A.RenkAciklama <> '' THEN A.RenkAciklama ELSE A.ColorCode END),'') + NCHAR(31), NCHAR(31) + @RenkList + NCHAR(31)) > 0)
|
WHERE (@RenkList IS NULL OR CHARINDEX(NCHAR(31) + ISNULL((CASE WHEN A.RenkAciklama <> '' THEN A.RenkAciklama ELSE A.ColorCode END),'') + NCHAR(31), NCHAR(31) + @RenkList + NCHAR(31)) > 0)
|
||||||
AND (@Renk2List IS NULL OR CHARINDEX(NCHAR(31) + ISNULL(A.ItemDim2Code,'') + NCHAR(31), NCHAR(31) + @Renk2List + NCHAR(31)) > 0)
|
AND (@Renk2List IS NULL OR CHARINDEX(NCHAR(31) + ISNULL(A.ItemDim2Code,'') + NCHAR(31), NCHAR(31) + @Renk2List + NCHAR(31)) > 0)
|
||||||
AND (
|
AND (
|
||||||
@@ -669,19 +669,19 @@ INNER JOIN Grouped G
|
|||||||
AND G.ColorCode = A.ColorCode
|
AND G.ColorCode = A.ColorCode
|
||||||
AND ISNULL(G.ItemDim2Code, '') = ISNULL(A.ItemDim2Code, '')
|
AND ISNULL(G.ItemDim2Code, '') = ISNULL(A.ItemDim2Code, '')
|
||||||
INNER JOIN #AttrFiltered AF
|
INNER JOIN #AttrFiltered AF
|
||||||
ON AF.ProductCode = A.ItemCode
|
ON AF.ProductCode = UPPER(LTRIM(RTRIM(A.ItemCode)))
|
||||||
LEFT JOIN cdWarehouseDesc W WITH (NOLOCK)
|
LEFT JOIN cdWarehouseDesc W
|
||||||
ON W.WarehouseCode = A.WarehouseCode
|
ON W.WarehouseCode = A.WarehouseCode
|
||||||
AND W.LangCode = 'TR'
|
AND W.LangCode = 'TR'
|
||||||
LEFT JOIN bsItemTypeDesc IT WITH (NOLOCK)
|
LEFT JOIN bsItemTypeDesc IT
|
||||||
ON IT.ItemTypeCode = A.ItemTypeCode
|
ON IT.ItemTypeCode = A.ItemTypeCode
|
||||||
AND IT.LangCode = 'TR'
|
AND IT.LangCode = 'TR'
|
||||||
OUTER APPLY (
|
OUTER APPLY (
|
||||||
SELECT TOP 1 Price
|
SELECT TOP 1 Price
|
||||||
FROM prItemBasePrice PB WITH (NOLOCK)
|
FROM prItemBasePrice PB
|
||||||
WHERE PB.ItemTypeCode = 1
|
WHERE PB.ItemTypeCode = 1
|
||||||
AND PB.ItemCode = A.ItemCode
|
AND UPPER(LTRIM(RTRIM(PB.ItemCode))) = UPPER(LTRIM(RTRIM(A.ItemCode)))
|
||||||
AND LEN(PB.ItemCode) = 13
|
AND LEN(LTRIM(RTRIM(PB.ItemCode))) = 13
|
||||||
ORDER BY PB.PriceDate DESC
|
ORDER BY PB.PriceDate DESC
|
||||||
) P
|
) P
|
||||||
OPTION (RECOMPILE);
|
OPTION (RECOMPILE);
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ WITH BaseCari AS (
|
|||||||
PARTITION BY LEFT(CurrAccCode, 8)
|
PARTITION BY LEFT(CurrAccCode, 8)
|
||||||
ORDER BY CurrAccCode
|
ORDER BY CurrAccCode
|
||||||
)
|
)
|
||||||
FROM cdCurrAcc WITH (NOLOCK)
|
FROM cdCurrAcc
|
||||||
WHERE CurrAccTypeCode IN (1,3)
|
WHERE CurrAccTypeCode IN (1,3)
|
||||||
AND LEFT(CurrAccCode, 8) IN (%s)
|
AND LEFT(CurrAccCode, 8) IN (%s)
|
||||||
)
|
)
|
||||||
@@ -167,7 +167,7 @@ SELECT
|
|||||||
b.MasterCari,
|
b.MasterCari,
|
||||||
CariDetay = ISNULL(d.CurrAccDescription, '')
|
CariDetay = ISNULL(d.CurrAccDescription, '')
|
||||||
FROM BaseCari b
|
FROM BaseCari b
|
||||||
LEFT JOIN cdCurrAccDesc d WITH (NOLOCK)
|
LEFT JOIN cdCurrAccDesc d
|
||||||
ON d.CurrAccTypeCode = b.CurrAccTypeCode
|
ON d.CurrAccTypeCode = b.CurrAccTypeCode
|
||||||
AND d.CurrAccCode = b.CurrAccCode
|
AND d.CurrAccCode = b.CurrAccCode
|
||||||
AND d.LangCode = 'TR'
|
AND d.LangCode = 'TR'
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func GetStatements(ctx context.Context, params models.StatementParams) ([]models
|
|||||||
(
|
(
|
||||||
(b.CurrAccTypeCode = 3 AND EXISTS (
|
(b.CurrAccTypeCode = 3 AND EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM CustomerAttributesFilter PF WITH (NOLOCK)
|
FROM CustomerAttributesFilter PF
|
||||||
WHERE (PF.CurrAccCode = b.CurrAccCode OR LEFT(PF.CurrAccCode, 8) = LEFT(b.CurrAccCode, 8))
|
WHERE (PF.CurrAccCode = b.CurrAccCode OR LEFT(PF.CurrAccCode, 8) = LEFT(b.CurrAccCode, 8))
|
||||||
AND %s
|
AND %s
|
||||||
))
|
))
|
||||||
@@ -57,7 +57,7 @@ func GetStatements(ctx context.Context, params models.StatementParams) ([]models
|
|||||||
SELECT
|
SELECT
|
||||||
CurrAccCode,
|
CurrAccCode,
|
||||||
VendorAtt01 = MAX(CASE WHEN AttributeTypeCode = 1 THEN AttributeCode END)
|
VendorAtt01 = MAX(CASE WHEN AttributeTypeCode = 1 THEN AttributeCode END)
|
||||||
FROM prCurrAccAttribute WITH (NOLOCK)
|
FROM prCurrAccAttribute
|
||||||
WHERE CurrAccTypeCode = 1
|
WHERE CurrAccTypeCode = 1
|
||||||
GROUP BY CurrAccCode
|
GROUP BY CurrAccCode
|
||||||
) VF
|
) VF
|
||||||
|
|||||||
@@ -64,13 +64,13 @@ WITH C AS (
|
|||||||
HIT AS (
|
HIT AS (
|
||||||
SELECT DISTINCT C.code
|
SELECT DISTINCT C.code
|
||||||
FROM C
|
FROM C
|
||||||
JOIN dbo.tbStok S WITH (NOLOCK)
|
JOIN dbo.tbStok S
|
||||||
ON S.sKodu = C.code
|
ON S.sKodu = C.code
|
||||||
OR S.sKodu = C.code_nospace
|
OR S.sKodu = C.code_nospace
|
||||||
UNION
|
UNION
|
||||||
SELECT DISTINCT C.code
|
SELECT DISTINCT C.code
|
||||||
FROM C
|
FROM C
|
||||||
JOIN dbo.tbStok S WITH (NOLOCK)
|
JOIN dbo.tbStok S
|
||||||
ON S.sModel = C.code
|
ON S.sModel = C.code
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ VARIANT_MASTER AS (
|
|||||||
LTRIM(RTRIM(ISNULL(V.ColorCode,''))) AS ColorCode,
|
LTRIM(RTRIM(ISNULL(V.ColorCode,''))) AS ColorCode,
|
||||||
LTRIM(RTRIM(ISNULL(V.ItemDim2Code,''))) AS ItemDim3Code,
|
LTRIM(RTRIM(ISNULL(V.ItemDim2Code,''))) AS ItemDim3Code,
|
||||||
MAX(LTRIM(RTRIM(ISNULL(V.ItemDim1Code,'')))) AS ItemDim1Code
|
MAX(LTRIM(RTRIM(ISNULL(V.ItemDim1Code,'')))) AS ItemDim1Code
|
||||||
FROM prItemVariant V WITH(NOLOCK)
|
FROM prItemVariant V
|
||||||
JOIN INP ON INP.ItemCode = V.ItemCode
|
JOIN INP ON INP.ItemCode = V.ItemCode
|
||||||
WHERE V.ItemTypeCode = 1
|
WHERE V.ItemTypeCode = 1
|
||||||
AND LEN(V.ItemCode) = 13
|
AND LEN(V.ItemCode) = 13
|
||||||
@@ -36,7 +36,7 @@ VARIANT_STOCK AS (
|
|||||||
LTRIM(RTRIM(ISNULL(S.ColorCode,''))) AS ColorCode,
|
LTRIM(RTRIM(ISNULL(S.ColorCode,''))) AS ColorCode,
|
||||||
LTRIM(RTRIM(ISNULL(S.ItemDim2Code,''))) AS ItemDim3Code,
|
LTRIM(RTRIM(ISNULL(S.ItemDim2Code,''))) AS ItemDim3Code,
|
||||||
MAX(LTRIM(RTRIM(ISNULL(S.ItemDim1Code,'')))) AS ItemDim1Code
|
MAX(LTRIM(RTRIM(ISNULL(S.ItemDim1Code,'')))) AS ItemDim1Code
|
||||||
FROM trStock S WITH(NOLOCK)
|
FROM trStock S
|
||||||
JOIN INP ON INP.ItemCode = S.ItemCode
|
JOIN INP ON INP.ItemCode = S.ItemCode
|
||||||
WHERE S.ItemTypeCode = 1
|
WHERE S.ItemTypeCode = 1
|
||||||
AND LEN(S.ItemCode) = 13
|
AND LEN(S.ItemCode) = 13
|
||||||
@@ -63,7 +63,7 @@ STOCK AS (
|
|||||||
LTRIM(RTRIM(ISNULL(S.ItemDim2Code,''))) AS ItemDim3Code,
|
LTRIM(RTRIM(ISNULL(S.ItemDim2Code,''))) AS ItemDim3Code,
|
||||||
MAX(LTRIM(RTRIM(ISNULL(S.ItemDim1Code,'')))) AS ItemDim1Code,
|
MAX(LTRIM(RTRIM(ISNULL(S.ItemDim1Code,'')))) AS ItemDim1Code,
|
||||||
SUM(S.In_Qty1 - S.Out_Qty1) AS InventoryQty1
|
SUM(S.In_Qty1 - S.Out_Qty1) AS InventoryQty1
|
||||||
FROM trStock S WITH(NOLOCK)
|
FROM trStock S
|
||||||
JOIN INP ON INP.ItemCode = S.ItemCode
|
JOIN INP ON INP.ItemCode = S.ItemCode
|
||||||
WHERE S.ItemTypeCode = 1
|
WHERE S.ItemTypeCode = 1
|
||||||
AND LEN(S.ItemCode) = 13
|
AND LEN(S.ItemCode) = 13
|
||||||
|
|||||||
@@ -215,10 +215,10 @@ SELECT TOP (1)
|
|||||||
ISNULL(h.CurrAccCode, ''),
|
ISNULL(h.CurrAccCode, ''),
|
||||||
ISNULL(LTRIM(RTRIM(f.CustomerAtt01)), '') AS MarketCode,
|
ISNULL(LTRIM(RTRIM(f.CustomerAtt01)), '') AS MarketCode,
|
||||||
ISNULL(py.AttributeDescription, '') AS MarketTitle
|
ISNULL(py.AttributeDescription, '') AS MarketTitle
|
||||||
FROM BAGGI_V3.dbo.trOrderHeader h WITH (NOLOCK)
|
FROM BAGGI_V3.dbo.trOrderHeader h
|
||||||
LEFT JOIN BAGGI_V3.dbo.CustomerAttributesFilter f WITH (NOLOCK)
|
LEFT JOIN BAGGI_V3.dbo.CustomerAttributesFilter f
|
||||||
ON f.CurrAccCode = h.CurrAccCode
|
ON f.CurrAccCode = h.CurrAccCode
|
||||||
LEFT JOIN BAGGI_V3.dbo.cdCurrAccAttributeDesc py WITH (NOLOCK)
|
LEFT JOIN BAGGI_V3.dbo.cdCurrAccAttributeDesc py
|
||||||
ON py.CurrAccTypeCode = h.CurrAccTypeCode
|
ON py.CurrAccTypeCode = h.CurrAccTypeCode
|
||||||
AND py.AttributeTypeCode = 1
|
AND py.AttributeTypeCode = 1
|
||||||
AND py.AttributeCode = f.CustomerAtt01
|
AND py.AttributeCode = f.CustomerAtt01
|
||||||
|
|||||||
@@ -691,7 +691,7 @@ func getOrderHeaderFromDB(db *sql.DB, orderID string) (*OrderHeader, error) {
|
|||||||
h.CreatedUserName,
|
h.CreatedUserName,
|
||||||
ISNULL((
|
ISNULL((
|
||||||
SELECT TOP (1) ca.AttributeDescription
|
SELECT TOP (1) ca.AttributeDescription
|
||||||
FROM BAGGI_V3.dbo.cdCurrAccAttributeDesc AS ca WITH (NOLOCK)
|
FROM BAGGI_V3.dbo.cdCurrAccAttributeDesc AS ca
|
||||||
WHERE ca.CurrAccTypeCode IN (1,3)
|
WHERE ca.CurrAccTypeCode IN (1,3)
|
||||||
AND ca.AttributeTypeCode = 2 -- 🟡 Müşteri Temsilcisi
|
AND ca.AttributeTypeCode = 2 -- 🟡 Müşteri Temsilcisi
|
||||||
AND ca.AttributeCode = f.CustomerAtt02
|
AND ca.AttributeCode = f.CustomerAtt02
|
||||||
@@ -764,7 +764,7 @@ func getOrderLinesFromDB(db *sql.DB, orderID string) ([]OrderLineRaw, error) {
|
|||||||
WHEN ISNULL(L.IsClosed, 0) = 1
|
WHEN ISNULL(L.IsClosed, 0) = 1
|
||||||
OR EXISTS (
|
OR EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM BAGGI_V3.dbo.trInvoiceLine il WITH (NOLOCK)
|
FROM BAGGI_V3.dbo.trInvoiceLine il
|
||||||
WHERE il.OrderLineID = L.OrderLineID
|
WHERE il.OrderLineID = L.OrderLineID
|
||||||
)
|
)
|
||||||
THEN CAST(1 AS bit)
|
THEN CAST(1 AS bit)
|
||||||
@@ -777,7 +777,7 @@ func getOrderLinesFromDB(db *sql.DB, orderID string) ([]OrderLineRaw, error) {
|
|||||||
L.VatCode,
|
L.VatCode,
|
||||||
L.VatRate
|
L.VatRate
|
||||||
FROM BAGGI_V3.dbo.trOrderLine AS L
|
FROM BAGGI_V3.dbo.trOrderLine AS L
|
||||||
LEFT JOIN BAGGI_V3.dbo.trOrderLineCurrency AS CD WITH (NOLOCK)
|
LEFT JOIN BAGGI_V3.dbo.trOrderLineCurrency AS CD
|
||||||
ON CD.OrderLineID = L.OrderLineID
|
ON CD.OrderLineID = L.OrderLineID
|
||||||
AND CD.CurrencyCode = ISNULL(NULLIF(LTRIM(RTRIM(L.DocCurrencyCode)), ''), 'TRY')
|
AND CD.CurrencyCode = ISNULL(NULLIF(LTRIM(RTRIM(L.DocCurrencyCode)), ''), 'TRY')
|
||||||
LEFT JOIN ProductFilterWithDescription('TR') AS P
|
LEFT JOIN ProductFilterWithDescription('TR') AS P
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ SELECT
|
|||||||
StoreCapacityLevelCode,
|
StoreCapacityLevelCode,
|
||||||
CustomsTariffNumberCode,
|
CustomsTariffNumberCode,
|
||||||
CompanyCode
|
CompanyCode
|
||||||
FROM dbo.cdItem WITH(NOLOCK)
|
FROM dbo.cdItem
|
||||||
WHERE ItemCode = @p1;
|
WHERE ItemCode = @p1;
|
||||||
`
|
`
|
||||||
row := db.MssqlDB.QueryRow(query, code)
|
row := db.MssqlDB.QueryRow(query, code)
|
||||||
|
|||||||
+277
-17
@@ -17,14 +17,33 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ProductImageItem struct {
|
type ProductImageItem struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
FileName string `json:"file_name"`
|
FileName string `json:"file_name"`
|
||||||
FileSize int64 `json:"file_size"`
|
FileSize int64 `json:"file_size"`
|
||||||
Storage string `json:"storage_path"`
|
Storage string `json:"storage_path"`
|
||||||
ContentURL string `json:"content_url"`
|
ContentURL string `json:"content_url"`
|
||||||
UUID string `json:"uuid,omitempty"`
|
UUID string `json:"uuid,omitempty"`
|
||||||
ThumbURL string `json:"thumb_url,omitempty"`
|
ThumbURL string `json:"thumb_url,omitempty"`
|
||||||
FullURL string `json:"full_url,omitempty"`
|
FullURL string `json:"full_url,omitempty"`
|
||||||
|
StoredInDB bool `json:"stored_in_db,omitempty"`
|
||||||
|
HasDBContent bool `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductImageBatchItem struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Dim1 string `json:"dim1"`
|
||||||
|
Dim3 string `json:"dim3"`
|
||||||
|
Limit int `json:"limit,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductImageBatchRequest struct {
|
||||||
|
Items []ProductImageBatchItem `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProductImageBatchResponseItem struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Images []ProductImageItem `json:"images"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var uuidPattern = regexp.MustCompile(`(?i)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`)
|
var uuidPattern = regexp.MustCompile(`(?i)[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`)
|
||||||
@@ -119,6 +138,206 @@ func extractImageUUID(storagePath, fileName string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func enrichProductImageItem(it *ProductImageItem) {
|
||||||
|
if it == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if u := extractImageUUID(it.Storage, it.FileName); u != "" {
|
||||||
|
it.UUID = u
|
||||||
|
it.ThumbURL = productImagePublicURLIfExists("uploads/image/t300/" + u + ".jpg")
|
||||||
|
it.FullURL = productImagePublicURLIfExists("uploads/image/" + u + ".jpg")
|
||||||
|
}
|
||||||
|
if it.FullURL == "" {
|
||||||
|
it.FullURL = productImagePublicURLIfExists(it.Storage)
|
||||||
|
}
|
||||||
|
if it.FullURL == "" {
|
||||||
|
it.FullURL = productImagePublicURLIfExists(it.FileName)
|
||||||
|
}
|
||||||
|
if it.ID > 0 && (it.HasDBContent || productImageStoredFileExists(it.Storage, it.FileName)) {
|
||||||
|
it.ContentURL = fmt.Sprintf("/api/product-images/%d/content", it.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productImageItemHasURL(it *ProductImageItem) bool {
|
||||||
|
return it != nil && (it.ThumbURL != "" || it.FullURL != "" || it.ContentURL != "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func productImageStoredFileExists(storagePath, fileName string) bool {
|
||||||
|
if resolved, _ := resolveStoragePath(storagePath); resolved != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if resolved, _ := resolveStoragePath(fileName); resolved != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func productImagePublicURLIfExists(storagePath string) string {
|
||||||
|
raw := strings.TrimSpace(storagePath)
|
||||||
|
if raw == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
resolved, _ := resolveStoragePath(raw)
|
||||||
|
if resolved == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized := strings.ReplaceAll(raw, "\\", "/")
|
||||||
|
lower := strings.ToLower(normalized)
|
||||||
|
if idx := strings.Index(lower, "/uploads/"); idx >= 0 {
|
||||||
|
return normalized[idx:]
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(lower, "uploads/") {
|
||||||
|
return "/" + normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
root := strings.TrimSpace(os.Getenv("BLOB_ROOT"))
|
||||||
|
if root == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
rel, err := filepath.Rel(root, resolved)
|
||||||
|
if err != nil || rel == "." || strings.HasPrefix(rel, "..") || filepath.IsAbs(rel) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
rel = filepath.ToSlash(rel)
|
||||||
|
if strings.HasPrefix(strings.ToLower(rel), "uploads/") {
|
||||||
|
return "/" + rel
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /api/product-images/batch
|
||||||
|
func PostProductImagesBatchHandler(pg *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reqID := strings.TrimSpace(r.Header.Get("X-Request-ID"))
|
||||||
|
if reqID == "" {
|
||||||
|
reqID = uuid.NewString()
|
||||||
|
}
|
||||||
|
w.Header().Set("X-Request-ID", reqID)
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
|
||||||
|
var req ProductImageBatchRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, "Gecersiz gorsel batch istegi: "+err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(req.Items) > 300 {
|
||||||
|
req.Items = req.Items[:300]
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanItems := make([]ProductImageBatchItem, 0, len(req.Items))
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for _, item := range req.Items {
|
||||||
|
item.Key = strings.TrimSpace(item.Key)
|
||||||
|
item.Code = strings.TrimSpace(item.Code)
|
||||||
|
item.Dim1 = normalizeDimParam(item.Dim1)
|
||||||
|
item.Dim3 = normalizeDimParam(item.Dim3)
|
||||||
|
if item.Key == "" {
|
||||||
|
item.Key = item.Code + "|" + item.Dim1 + "|" + item.Dim3
|
||||||
|
}
|
||||||
|
if item.Code == "" || seen[item.Key] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[item.Key] = true
|
||||||
|
cleanItems = append(cleanItems, item)
|
||||||
|
}
|
||||||
|
if len(cleanItems) == 0 {
|
||||||
|
_ = json.NewEncoder(w).Encode([]ProductImageBatchResponseItem{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := json.Marshal(cleanItems)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Gorsel batch hazirlanamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := pg.Query(`
|
||||||
|
WITH req AS (
|
||||||
|
SELECT *
|
||||||
|
FROM jsonb_to_recordset($1::jsonb) AS x(key text, code text, dim1 text, dim3 text)
|
||||||
|
),
|
||||||
|
matched_item AS (
|
||||||
|
SELECT DISTINCT ON (r.key)
|
||||||
|
r.key,
|
||||||
|
r.code,
|
||||||
|
r.dim1,
|
||||||
|
r.dim3,
|
||||||
|
m.id AS mmitem_id
|
||||||
|
FROM req r
|
||||||
|
JOIN mmitem m ON
|
||||||
|
UPPER(REPLACE(COALESCE(m.code,''), ' ', '')) = UPPER(REPLACE(COALESCE(r.code,''), ' ', ''))
|
||||||
|
OR UPPER(REPLACE(REGEXP_REPLACE(COALESCE(m.code,''), '^.*-', ''), ' ', '')) =
|
||||||
|
UPPER(REPLACE(REGEXP_REPLACE(COALESCE(r.code,''), '^.*-', ''), ' ', ''))
|
||||||
|
ORDER BY r.key, m.id
|
||||||
|
),
|
||||||
|
ranked AS (
|
||||||
|
SELECT
|
||||||
|
mi.key,
|
||||||
|
b.id,
|
||||||
|
COALESCE(b.file_name,'') AS file_name,
|
||||||
|
COALESCE(b.file_size,0) AS file_size,
|
||||||
|
COALESCE(b.storage_path,'') AS storage_path,
|
||||||
|
COALESCE(b.stored_in_db,false) AS stored_in_db,
|
||||||
|
(COALESCE(octet_length(b.bin),0) > 0) AS has_db_content,
|
||||||
|
ROW_NUMBER() OVER (
|
||||||
|
PARTITION BY mi.key
|
||||||
|
ORDER BY
|
||||||
|
CASE
|
||||||
|
WHEN mi.dim1 <> '' AND mi.dim3 <> '' AND COALESCE(b.dimval1::text,'') = mi.dim1 AND COALESCE(b.dimval3::text,'') = mi.dim3 THEN 0
|
||||||
|
WHEN mi.dim1 <> '' AND COALESCE(b.dimval1::text,'') = mi.dim1 AND COALESCE(b.dimval3::text,'') = '' THEN 1
|
||||||
|
WHEN COALESCE(b.dimval1::text,'') = '' AND COALESCE(b.dimval3::text,'') = '' THEN 2
|
||||||
|
ELSE 3
|
||||||
|
END,
|
||||||
|
COALESCE(b.sort_order,999999),
|
||||||
|
b.id
|
||||||
|
) AS rn
|
||||||
|
FROM matched_item mi
|
||||||
|
JOIN dfblob b ON b.src_table='mmitem'
|
||||||
|
AND b.typ='img'
|
||||||
|
AND b.src_id=mi.mmitem_id
|
||||||
|
)
|
||||||
|
SELECT key, id, file_name, file_size, storage_path, stored_in_db, has_db_content
|
||||||
|
FROM ranked
|
||||||
|
WHERE rn <= 5
|
||||||
|
ORDER BY key, rn
|
||||||
|
`, string(payload))
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("product_images.batch.query_failed", "req_id", reqID, "err", err.Error())
|
||||||
|
http.Error(w, "Gorsel batch sorgu hatasi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
grouped := map[string][]ProductImageItem{}
|
||||||
|
for rows.Next() {
|
||||||
|
var key string
|
||||||
|
var it ProductImageItem
|
||||||
|
if err := rows.Scan(&key, &it.ID, &it.FileName, &it.FileSize, &it.Storage, &it.StoredInDB, &it.HasDBContent); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
enrichProductImageItem(&it)
|
||||||
|
if !productImageItemHasURL(&it) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
grouped[key] = append(grouped[key], it)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
http.Error(w, "Gorsel batch okuma hatasi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]ProductImageBatchResponseItem, 0, len(cleanItems))
|
||||||
|
for _, item := range cleanItems {
|
||||||
|
out = append(out, ProductImageBatchResponseItem{
|
||||||
|
Key: item.Key,
|
||||||
|
Images: grouped[item.Key],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GET /api/product-images?code=...&dim1=...&dim3=...
|
// GET /api/product-images?code=...&dim1=...&dim3=...
|
||||||
func GetProductImagesHandler(pg *sql.DB) http.HandlerFunc {
|
func GetProductImagesHandler(pg *sql.DB) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -190,7 +409,9 @@ SELECT
|
|||||||
id,
|
id,
|
||||||
COALESCE(file_name,'') AS file_name,
|
COALESCE(file_name,'') AS file_name,
|
||||||
COALESCE(file_size,0) AS file_size,
|
COALESCE(file_size,0) AS file_size,
|
||||||
COALESCE(storage_path,'') AS storage_path
|
COALESCE(storage_path,'') AS storage_path,
|
||||||
|
COALESCE(stored_in_db,false) AS stored_in_db,
|
||||||
|
(COALESCE(octet_length(bin),0) > 0) AS has_db_content
|
||||||
FROM dfblob
|
FROM dfblob
|
||||||
WHERE typ='img'
|
WHERE typ='img'
|
||||||
AND src_table='mmitem'
|
AND src_table='mmitem'
|
||||||
@@ -221,16 +442,13 @@ ORDER BY
|
|||||||
items := make([]ProductImageItem, 0, 16)
|
items := make([]ProductImageItem, 0, 16)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var it ProductImageItem
|
var it ProductImageItem
|
||||||
if err := rows.Scan(&it.ID, &it.FileName, &it.FileSize, &it.Storage); err != nil {
|
if err := rows.Scan(&it.ID, &it.FileName, &it.FileSize, &it.Storage, &it.StoredInDB, &it.HasDBContent); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
it.ContentURL = fmt.Sprintf("/api/product-images/%d/content", it.ID)
|
enrichProductImageItem(&it)
|
||||||
if u := extractImageUUID(it.Storage, it.FileName); u != "" {
|
if productImageItemHasURL(&it) {
|
||||||
it.UUID = u
|
items = append(items, it)
|
||||||
it.ThumbURL = "/uploads/image/t300/" + u + ".jpg"
|
|
||||||
it.FullURL = "/uploads/image/" + u + ".jpg"
|
|
||||||
}
|
}
|
||||||
items = append(items, it)
|
|
||||||
}
|
}
|
||||||
if err := rows.Err(); err != nil {
|
if err := rows.Err(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -372,7 +590,8 @@ WHERE id = $1
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if storedInDB && len(binData) > 0 {
|
_ = storedInDB
|
||||||
|
if len(binData) > 0 {
|
||||||
w.Header().Set("Content-Type", http.DetectContentType(binData))
|
w.Header().Set("Content-Type", http.DetectContentType(binData))
|
||||||
w.Header().Set("Cache-Control", "public, max-age=3600")
|
w.Header().Set("Cache-Control", "public, max-age=3600")
|
||||||
_, _ = w.Write(binData)
|
_, _ = w.Write(binData)
|
||||||
@@ -380,6 +599,9 @@ WHERE id = $1
|
|||||||
}
|
}
|
||||||
|
|
||||||
resolved, _ := resolveStoragePath(storagePath)
|
resolved, _ := resolveStoragePath(storagePath)
|
||||||
|
if resolved == "" {
|
||||||
|
resolved, _ = resolveStoragePath(fileName)
|
||||||
|
}
|
||||||
if resolved == "" {
|
if resolved == "" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
@@ -413,14 +635,42 @@ func resolveStoragePath(storagePath string) (string, []string) {
|
|||||||
raw = filepath.ToSlash(filepath.Clean(raw))
|
raw = filepath.ToSlash(filepath.Clean(raw))
|
||||||
|
|
||||||
relUploads := filepath.FromSlash(filepath.Join("uploads", raw))
|
relUploads := filepath.FromSlash(filepath.Join("uploads", raw))
|
||||||
|
baseName := filepath.Base(raw)
|
||||||
|
relImage := filepath.FromSlash(filepath.Join("uploads", "image", baseName))
|
||||||
|
relImageThumb := filepath.FromSlash(filepath.Join("uploads", "image", "t300", baseName))
|
||||||
candidates := []string{
|
candidates := []string{
|
||||||
filepath.Clean(storagePath),
|
filepath.Clean(storagePath),
|
||||||
filepath.FromSlash(filepath.Clean(strings.TrimPrefix(storagePath, "/"))),
|
filepath.FromSlash(filepath.Clean(strings.TrimPrefix(storagePath, "/"))),
|
||||||
filepath.FromSlash(filepath.Clean(raw)),
|
filepath.FromSlash(filepath.Clean(raw)),
|
||||||
relUploads,
|
relUploads,
|
||||||
|
relImage,
|
||||||
|
relImageThumb,
|
||||||
filepath.Join(".", relUploads),
|
filepath.Join(".", relUploads),
|
||||||
|
filepath.Join(".", relImage),
|
||||||
|
filepath.Join(".", relImageThumb),
|
||||||
filepath.Join("..", relUploads),
|
filepath.Join("..", relUploads),
|
||||||
|
filepath.Join("..", relImage),
|
||||||
|
filepath.Join("..", relImageThumb),
|
||||||
filepath.Join("..", "..", relUploads),
|
filepath.Join("..", "..", relUploads),
|
||||||
|
filepath.Join("..", "..", relImage),
|
||||||
|
filepath.Join("..", "..", relImageThumb),
|
||||||
|
}
|
||||||
|
|
||||||
|
if u := extractImageUUID(raw, baseName); u != "" {
|
||||||
|
uFull := filepath.FromSlash(filepath.Join("uploads", "image", u+".jpg"))
|
||||||
|
uThumb := filepath.FromSlash(filepath.Join("uploads", "image", "t300", u+".jpg"))
|
||||||
|
candidates = append(candidates,
|
||||||
|
filepath.FromSlash(filepath.Join("image", u+".jpg")),
|
||||||
|
filepath.FromSlash(filepath.Join("image", "t300", u+".jpg")),
|
||||||
|
uFull,
|
||||||
|
uThumb,
|
||||||
|
filepath.Join(".", uFull),
|
||||||
|
filepath.Join(".", uThumb),
|
||||||
|
filepath.Join("..", uFull),
|
||||||
|
filepath.Join("..", uThumb),
|
||||||
|
filepath.Join("..", "..", uFull),
|
||||||
|
filepath.Join("..", "..", uThumb),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if root := strings.TrimSpace(os.Getenv("BLOB_ROOT")); root != "" {
|
if root := strings.TrimSpace(os.Getenv("BLOB_ROOT")); root != "" {
|
||||||
@@ -428,7 +678,17 @@ func resolveStoragePath(storagePath string) (string, []string) {
|
|||||||
filepath.Join(root, raw),
|
filepath.Join(root, raw),
|
||||||
filepath.Join(root, relUploads),
|
filepath.Join(root, relUploads),
|
||||||
filepath.Join(root, "uploads", raw),
|
filepath.Join(root, "uploads", raw),
|
||||||
|
filepath.Join(root, relImage),
|
||||||
|
filepath.Join(root, relImageThumb),
|
||||||
)
|
)
|
||||||
|
if u := extractImageUUID(raw, baseName); u != "" {
|
||||||
|
candidates = append(candidates,
|
||||||
|
filepath.Join(root, "uploads", "image", u+".jpg"),
|
||||||
|
filepath.Join(root, "uploads", "image", "t300", u+".jpg"),
|
||||||
|
filepath.Join(root, "image", u+".jpg"),
|
||||||
|
filepath.Join(root, "image", "t300", u+".jpg"),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range candidates {
|
for _, p := range candidates {
|
||||||
|
|||||||
@@ -0,0 +1,450 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bssapp-backend/queries"
|
||||||
|
"bssapp-backend/utils"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetProductPerformanceHandler(pg *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
traceID := utils.TraceIDFromRequest(r)
|
||||||
|
ctx, cancel := context.WithTimeout(utils.ContextWithTraceID(r.Context(), traceID), 180*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
f := queries.ProductPerformanceFilters{
|
||||||
|
Search: strings.TrimSpace(r.URL.Query().Get("q")),
|
||||||
|
ProductCode: strings.TrimSpace(r.URL.Query().Get("product_code")),
|
||||||
|
MarketKey: strings.TrimSpace(r.URL.Query().Get("market_key")),
|
||||||
|
Kategori: strings.TrimSpace(r.URL.Query().Get("kategori")),
|
||||||
|
Seri: strings.TrimSpace(r.URL.Query().Get("seri")),
|
||||||
|
Bucket: strings.TrimSpace(r.URL.Query().Get("bucket")),
|
||||||
|
SortBy: strings.TrimSpace(r.URL.Query().Get("sort_by")),
|
||||||
|
Descending: boolQuery(r, "descending", true),
|
||||||
|
Limit: intQuery(r, "limit", 50000),
|
||||||
|
Page: intQuery(r, "page", 1),
|
||||||
|
}
|
||||||
|
rows, total, err := queries.ListProductPerformance(ctx, pg, f)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "urun performans listesi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("X-Total-Count", strconv.Itoa(total))
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
"rows": rows,
|
||||||
|
"total_count": total,
|
||||||
|
"page": f.Page,
|
||||||
|
"limit": f.Limit,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProductPerformanceSalesDetailsHandler(pg *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
rows, err := queries.ListProductPerformanceSalesDetails(
|
||||||
|
ctx,
|
||||||
|
pg,
|
||||||
|
strings.TrimSpace(r.URL.Query().Get("product_code")),
|
||||||
|
strings.TrimSpace(r.URL.Query().Get("color_code")),
|
||||||
|
strings.TrimSpace(r.URL.Query().Get("yaka_kodu")),
|
||||||
|
intQuery(r, "limit", 100),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "urun satis detayi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProductPerformanceStockSizesHandler() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), 60*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
rows, err := queries.ListProductPerformanceStockSizes(
|
||||||
|
ctx,
|
||||||
|
strings.TrimSpace(r.URL.Query().Get("product_code")),
|
||||||
|
strings.TrimSpace(r.URL.Query().Get("color_code")),
|
||||||
|
strings.TrimSpace(r.URL.Query().Get("yaka_kodu")),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "urun beden stok detayi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProductPerformanceSummaryHandler(pg *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
traceID := utils.TraceIDFromRequest(r)
|
||||||
|
ctx, cancel := context.WithTimeout(utils.ContextWithTraceID(r.Context(), traceID), 180*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
summary, err := queries.GetProductPerformanceSummary(ctx, pg)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "urun performans ozeti alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(summary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProductPerformanceGeneralHandler(pg *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
traceID := utils.TraceIDFromRequest(r)
|
||||||
|
ctx, cancel := context.WithTimeout(utils.ContextWithTraceID(r.Context(), traceID), 180*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
rows, err := queries.ListProductPerformanceGeneral(ctx, pg, intQuery(r, "limit", 50000))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "genel urun performans KPI alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProductPerformanceOrdersHandler(pg *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
traceID := utils.TraceIDFromRequest(r)
|
||||||
|
ctx, cancel := context.WithTimeout(utils.ContextWithTraceID(r.Context(), traceID), 180*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
rows, err := queries.ListProductPerformanceOrderAnalysis(ctx, pg, intQuery(r, "limit", 50000))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "siparis analiz verisi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProductPerformanceOrderGroupsHandler(pg *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
traceID := utils.TraceIDFromRequest(r)
|
||||||
|
ctx, cancel := context.WithTimeout(utils.ContextWithTraceID(r.Context(), traceID), 180*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
rows, err := queries.ListProductPerformanceOrderGroups(
|
||||||
|
ctx,
|
||||||
|
pg,
|
||||||
|
strings.TrimSpace(r.URL.Query().Get("breakdown")),
|
||||||
|
intQuery(r, "limit", 50000),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "siparis grup analiz verisi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProductPerformanceOrderProductCustomersHandler(pg *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
traceID := utils.TraceIDFromRequest(r)
|
||||||
|
ctx, cancel := context.WithTimeout(utils.ContextWithTraceID(r.Context(), traceID), 180*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
rows, err := queries.ListProductPerformanceOrderProductCustomers(ctx, pg, intQuery(r, "limit", 50000))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "urun musteri siparis analiz verisi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProductPerformanceOrderMarketDetailsHandler(pg *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
traceID := utils.TraceIDFromRequest(r)
|
||||||
|
ctx, cancel := context.WithTimeout(utils.ContextWithTraceID(r.Context(), traceID), 180*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
rows, err := queries.ListProductPerformanceOrderMarketDetails(ctx, pg, intQuery(r, "limit", 50000))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "piyasa siparis detay analiz verisi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProductPerformanceMarketsHandler(pg *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
traceID := utils.TraceIDFromRequest(r)
|
||||||
|
ctx, cancel := context.WithTimeout(utils.ContextWithTraceID(r.Context(), traceID), 180*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
rows, err := queries.ListProductPerformanceMarkets(ctx, pg, intQuery(r, "limit", 50000))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "piyasa performans ozeti alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProductPerformanceCountriesHandler(pg *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
traceID := utils.TraceIDFromRequest(r)
|
||||||
|
ctx, cancel := context.WithTimeout(utils.ContextWithTraceID(r.Context(), traceID), 180*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
rows, err := queries.ListProductPerformanceCountries(ctx, pg, intQuery(r, "limit", 50000))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "ulke performans ozeti alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProductPerformanceCustomersHandler(pg *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
traceID := utils.TraceIDFromRequest(r)
|
||||||
|
ctx, cancel := context.WithTimeout(utils.ContextWithTraceID(r.Context(), traceID), 180*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
rows, err := queries.ListProductPerformanceCustomers(
|
||||||
|
ctx,
|
||||||
|
pg,
|
||||||
|
strings.TrimSpace(r.URL.Query().Get("breakdown")),
|
||||||
|
intQuery(r, "limit", 50000),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "musteri performans kirilimi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProductPerformanceSalesBreakdownHandler(pg *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
traceID := utils.TraceIDFromRequest(r)
|
||||||
|
ctx, cancel := context.WithTimeout(utils.ContextWithTraceID(r.Context(), traceID), 180*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
rows, err := queries.ListProductPerformanceSalesBreakdown(
|
||||||
|
ctx,
|
||||||
|
pg,
|
||||||
|
strings.TrimSpace(r.URL.Query().Get("mode")),
|
||||||
|
intQuery(r, "limit", 50000),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "satis kpi kirilimi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProductPerformanceGroupedHandler(pg *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
traceID := utils.TraceIDFromRequest(r)
|
||||||
|
ctx, cancel := context.WithTimeout(utils.ContextWithTraceID(r.Context(), traceID), 180*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
mode := strings.TrimSpace(r.URL.Query().Get("mode"))
|
||||||
|
mainGroup := strings.TrimSpace(r.URL.Query().Get("urun_ana_grubu"))
|
||||||
|
sortBy := strings.TrimSpace(r.URL.Query().Get("sort_by"))
|
||||||
|
descending := boolQuery(r, "descending", true)
|
||||||
|
groupLevels := splitCSVQuery(r.URL.Query().Get("group_levels"))
|
||||||
|
limit := intQuery(r, "limit", 50000)
|
||||||
|
expanded := map[string]bool{}
|
||||||
|
filters := map[string][]string{}
|
||||||
|
expandThroughLevel := -1
|
||||||
|
if rawExpandLevel := strings.TrimSpace(r.URL.Query().Get("expand_through_level")); rawExpandLevel != "" {
|
||||||
|
if parsed, err := strconv.Atoi(rawExpandLevel); err == nil {
|
||||||
|
expandThroughLevel = parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const maxExpandedGroupKeys = 1000
|
||||||
|
addExpandedKey := func(key string) {
|
||||||
|
key = strings.TrimSpace(key)
|
||||||
|
if key != "" && len(expanded) < maxExpandedGroupKeys {
|
||||||
|
expanded[key] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.Method == http.MethodPost {
|
||||||
|
var body struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
GroupLevels []string `json:"group_levels"`
|
||||||
|
ExpandedKeys []string `json:"expanded_keys"`
|
||||||
|
ExpandThroughLevel *int `json:"expand_through_level"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
MainGroup string `json:"urun_ana_grubu"`
|
||||||
|
Filters map[string][]string `json:"filters"`
|
||||||
|
SortBy string `json:"sort_by"`
|
||||||
|
Descending *bool `json:"descending"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||||
|
http.Error(w, "gecersiz grup istegi: "+err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(body.Mode) != "" {
|
||||||
|
mode = strings.TrimSpace(body.Mode)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(body.MainGroup) != "" {
|
||||||
|
mainGroup = strings.TrimSpace(body.MainGroup)
|
||||||
|
}
|
||||||
|
if len(body.GroupLevels) > 0 {
|
||||||
|
groupLevels = body.GroupLevels
|
||||||
|
}
|
||||||
|
if body.Limit > 0 {
|
||||||
|
limit = body.Limit
|
||||||
|
}
|
||||||
|
for _, key := range body.ExpandedKeys {
|
||||||
|
addExpandedKey(key)
|
||||||
|
}
|
||||||
|
if body.ExpandThroughLevel != nil {
|
||||||
|
expandThroughLevel = *body.ExpandThroughLevel
|
||||||
|
}
|
||||||
|
if len(body.Filters) > 0 {
|
||||||
|
filters = body.Filters
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(body.SortBy) != "" {
|
||||||
|
sortBy = strings.TrimSpace(body.SortBy)
|
||||||
|
}
|
||||||
|
if body.Descending != nil {
|
||||||
|
descending = *body.Descending
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, key := range strings.Split(r.URL.Query().Get("expanded_keys"), ",") {
|
||||||
|
addExpandedKey(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := queries.ListProductPerformanceGrouped(ctx, pg, queries.ProductPerformanceGroupedRequest{
|
||||||
|
Mode: mode,
|
||||||
|
GroupLevels: groupLevels,
|
||||||
|
ExpandedKeys: expanded,
|
||||||
|
ExpandThroughLevel: expandThroughLevel,
|
||||||
|
Limit: limit,
|
||||||
|
MainGroup: mainGroup,
|
||||||
|
Filters: filters,
|
||||||
|
SortBy: sortBy,
|
||||||
|
Descending: descending,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ProductPerformance][grouped] failed trace_id=%s mode=%s levels=%v expand_through_level=%d filters=%d: %v", traceID, mode, groupLevels, expandThroughLevel, len(filters), err)
|
||||||
|
http.Error(w, "urun performans grup verisi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProductPerformanceGroupedFilterOptionsHandler(pg *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
traceID := utils.TraceIDFromRequest(r)
|
||||||
|
ctx, cancel := context.WithTimeout(utils.ContextWithTraceID(r.Context(), traceID), 60*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
mode := strings.TrimSpace(r.URL.Query().Get("mode"))
|
||||||
|
mainGroup := strings.TrimSpace(r.URL.Query().Get("urun_ana_grubu"))
|
||||||
|
groupLevels := splitCSVQuery(r.URL.Query().Get("group_levels"))
|
||||||
|
fields := splitCSVQuery(r.URL.Query().Get("fields"))
|
||||||
|
limit := intQuery(r, "limit", 50000)
|
||||||
|
|
||||||
|
if r.Method == http.MethodPost {
|
||||||
|
var body struct {
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
GroupLevels []string `json:"group_levels"`
|
||||||
|
MainGroup string `json:"urun_ana_grubu"`
|
||||||
|
Fields []string `json:"fields"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||||
|
http.Error(w, "gecersiz filtre secenek istegi: "+err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(body.Mode) != "" {
|
||||||
|
mode = strings.TrimSpace(body.Mode)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(body.MainGroup) != "" {
|
||||||
|
mainGroup = strings.TrimSpace(body.MainGroup)
|
||||||
|
}
|
||||||
|
if len(body.GroupLevels) > 0 {
|
||||||
|
groupLevels = body.GroupLevels
|
||||||
|
}
|
||||||
|
if len(body.Fields) > 0 {
|
||||||
|
fields = body.Fields
|
||||||
|
}
|
||||||
|
if body.Limit > 0 {
|
||||||
|
limit = body.Limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options, err := queries.ListProductPerformanceGroupedFilterOptions(ctx, pg, queries.ProductPerformanceGroupedFilterOptionsRequest{
|
||||||
|
Mode: mode,
|
||||||
|
GroupLevels: groupLevels,
|
||||||
|
MainGroup: mainGroup,
|
||||||
|
Fields: fields,
|
||||||
|
Limit: limit,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[ProductPerformance][grouped-filter-options] failed trace_id=%s mode=%s levels=%v fields=%d: %v", traceID, mode, groupLevels, len(fields), err)
|
||||||
|
http.Error(w, "urun performans filtre secenekleri alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = json.NewEncoder(w).Encode(options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitCSVQuery(value string) []string {
|
||||||
|
parts := strings.Split(value, ",")
|
||||||
|
out := make([]string, 0, len(parts))
|
||||||
|
for _, part := range parts {
|
||||||
|
part = strings.TrimSpace(part)
|
||||||
|
if part != "" {
|
||||||
|
out = append(out, part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func intQuery(r *http.Request, key string, fallback int) int {
|
||||||
|
raw := strings.TrimSpace(r.URL.Query().Get(key))
|
||||||
|
if raw == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
n, err := strconv.Atoi(raw)
|
||||||
|
if err != nil || n <= 0 {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolQuery(r *http.Request, key string, fallback bool) bool {
|
||||||
|
raw := strings.TrimSpace(strings.ToLower(r.URL.Query().Get(key)))
|
||||||
|
if raw == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return raw == "1" || raw == "true" || raw == "yes" || raw == "on"
|
||||||
|
}
|
||||||
@@ -0,0 +1,1140 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bssapp-backend/models"
|
||||||
|
"bssapp-backend/queries"
|
||||||
|
"bssapp-backend/utils"
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/xuri/excelize/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type productPerformanceExcelExportRequest struct {
|
||||||
|
Filters map[string][]string `json:"filters"`
|
||||||
|
SortBy string `json:"sort_by"`
|
||||||
|
Descending *bool `json:"descending"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type productPerformanceExcelColumn struct {
|
||||||
|
Header string
|
||||||
|
Kind string
|
||||||
|
Value func(*productPerformanceExcelVariant, productPerformanceExcelStats) any
|
||||||
|
}
|
||||||
|
|
||||||
|
type productPerformanceExcelStats struct {
|
||||||
|
AvgSalesUSD90 float64
|
||||||
|
AvgSalesUSD180 float64
|
||||||
|
AvgSalesUSD365 float64
|
||||||
|
AvgSalesUSDTotal float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type productPerformanceExcelVariant struct {
|
||||||
|
KpiDate string
|
||||||
|
ProductCode string
|
||||||
|
ColorCode string
|
||||||
|
ColorDescription string
|
||||||
|
YakaKodu string
|
||||||
|
ItemDescription string
|
||||||
|
Kategori string
|
||||||
|
Seri string
|
||||||
|
YasGrubu string
|
||||||
|
AskiliYan string
|
||||||
|
UrunIlkGrubu string
|
||||||
|
UrunAnaGrubu string
|
||||||
|
UrunAltGrubu string
|
||||||
|
StockQty float64
|
||||||
|
AvgStock90 float64
|
||||||
|
AvgStock180 float64
|
||||||
|
AvgStock365 float64
|
||||||
|
AvgStockTotal float64
|
||||||
|
BasePriceUSD float64
|
||||||
|
CostPriceUSD float64
|
||||||
|
|
||||||
|
SalesQty90 float64
|
||||||
|
SalesQty180 float64
|
||||||
|
SalesQty365 float64
|
||||||
|
SalesUSD90 float64
|
||||||
|
SalesUSD180 float64
|
||||||
|
SalesUSD365 float64
|
||||||
|
|
||||||
|
SalesQtyTotalProduct float64
|
||||||
|
SalesUSDTotalProduct float64
|
||||||
|
SalesQtyTotalGeneral float64
|
||||||
|
SalesUSDTotalGeneral float64
|
||||||
|
HasGeneralTotal bool
|
||||||
|
|
||||||
|
MarketCount90 int
|
||||||
|
CustomerCount90 int
|
||||||
|
MarketCountTotal int
|
||||||
|
CustomerCountTotal int
|
||||||
|
Markets90 map[string]bool
|
||||||
|
MarketsTotal map[string]bool
|
||||||
|
MarketFiltered bool
|
||||||
|
|
||||||
|
PerformanceBucket string
|
||||||
|
Recommendation string
|
||||||
|
LastSaleDate string
|
||||||
|
LastRefNumber string
|
||||||
|
UpdatedAt string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExportProductPerformanceExcelHandler(pg *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
traceID := utils.TraceIDFromRequest(r)
|
||||||
|
ctx, cancel := context.WithTimeout(utils.ContextWithTraceID(r.Context(), traceID), 180*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var req productPerformanceExcelExportRequest
|
||||||
|
if r.Method == http.MethodPost {
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, "gecersiz excel istegi: "+err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
productRows, _, err := queries.ListProductPerformance(ctx, pg, queries.ProductPerformanceFilters{
|
||||||
|
Limit: 50000,
|
||||||
|
Page: 1,
|
||||||
|
SortBy: "product_code",
|
||||||
|
Descending: false,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "urun performans excel listesi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
generalRows, err := queries.ListProductPerformanceGeneral(ctx, pg, 50000)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "urun performans genel excel listesi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rows := buildProductPerformanceExcelVariants(productRows, generalRows, req.Filters)
|
||||||
|
sortProductPerformanceExcelVariants(rows, req.SortBy, req.Descending == nil || *req.Descending)
|
||||||
|
stats := productPerformanceExcelStatsFor(rows)
|
||||||
|
|
||||||
|
file, err := buildProductPerformanceExcelFile(rows, stats)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "urun performans excel dosyasi hazirlanamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() { _ = file.Close() }()
|
||||||
|
|
||||||
|
buf, err := file.WriteToBuffer()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
filename := fmt.Sprintf("product_performance_renk_yaka_%s.xlsx", time.Now().Format("20060102_150405"))
|
||||||
|
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||||
|
w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"")
|
||||||
|
w.Header().Set("Content-Length", fmt.Sprint(len(buf.Bytes())))
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write(buf.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildProductPerformanceExcelVariants(productRows []models.ProductPerformanceRow, generalRows []models.ProductPerformanceGeneralRow, filters map[string][]string) []*productPerformanceExcelVariant {
|
||||||
|
byKey := map[string]*productPerformanceExcelVariant{}
|
||||||
|
marketFiltered := productPerformanceExcelHasFilter(filters, "market_key")
|
||||||
|
for _, row := range productRows {
|
||||||
|
if !productPerformanceExcelProductRowMatchesFilters(row, filters) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := productPerformanceExcelVariantKey(row.ProductCode, row.ColorCode, row.YakaKodu)
|
||||||
|
v := byKey[key]
|
||||||
|
if v == nil {
|
||||||
|
v = &productPerformanceExcelVariant{Markets90: map[string]bool{}, MarketsTotal: map[string]bool{}, MarketFiltered: marketFiltered}
|
||||||
|
byKey[key] = v
|
||||||
|
}
|
||||||
|
v.addProductRow(row)
|
||||||
|
}
|
||||||
|
for _, row := range generalRows {
|
||||||
|
if !productPerformanceExcelGeneralRowMatchesFilters(row, filters) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := productPerformanceExcelVariantKey(row.ProductCode, row.ColorCode, row.YakaKodu)
|
||||||
|
v := byKey[key]
|
||||||
|
if v == nil {
|
||||||
|
v = &productPerformanceExcelVariant{Markets90: map[string]bool{}, MarketsTotal: map[string]bool{}, MarketFiltered: marketFiltered}
|
||||||
|
byKey[key] = v
|
||||||
|
}
|
||||||
|
v.addGeneralRow(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]*productPerformanceExcelVariant, 0, len(byKey))
|
||||||
|
for _, row := range byKey {
|
||||||
|
if productPerformanceExcelVariantMatchesPostFilters(row, filters) {
|
||||||
|
out = append(out, row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *productPerformanceExcelVariant) addProductRow(row models.ProductPerformanceRow) {
|
||||||
|
v.fillDimensions(
|
||||||
|
row.KpiDate, row.ProductCode, row.ColorCode, row.ColorDescription, row.YakaKodu, row.ItemDescription,
|
||||||
|
row.Kategori, row.Seri, row.YasGrubu, row.AskiliYan, row.UrunIlkGrubu, row.UrunAnaGrubu, row.UrunAltGrubu,
|
||||||
|
row.BasePriceUSD, row.CostPriceUSD, row.StockQty, row.LastSaleDate, row.LastRefNumber, row.UpdatedAt,
|
||||||
|
)
|
||||||
|
v.SalesQty90 += row.SalesQty90
|
||||||
|
v.SalesQty180 += row.SalesQty180
|
||||||
|
v.SalesQty365 += row.SalesQty365
|
||||||
|
v.SalesUSD90 += row.SalesUSD90
|
||||||
|
v.SalesUSD180 += row.SalesUSD180
|
||||||
|
v.SalesUSD365 += row.SalesUSD365
|
||||||
|
v.AvgStock90 = productPerformanceExcelFirstNonZero(v.AvgStock90, row.AvgStock90)
|
||||||
|
v.AvgStock180 = productPerformanceExcelFirstNonZero(v.AvgStock180, row.AvgStock180)
|
||||||
|
v.AvgStock365 = productPerformanceExcelFirstNonZero(v.AvgStock365, row.AvgStock365)
|
||||||
|
v.AvgStockTotal = productPerformanceExcelFirstNonZero(v.AvgStockTotal, row.AvgStockTotal)
|
||||||
|
v.SalesQtyTotalProduct += row.SalesQtyTotal
|
||||||
|
v.SalesUSDTotalProduct += row.SalesUSDTotal
|
||||||
|
v.MarketCount90 = productPerformanceExcelMaxInt(v.MarketCount90, row.MarketCount90)
|
||||||
|
if row.SalesUSD90 > 0 {
|
||||||
|
v.CustomerCount90 += row.CustomerCount90
|
||||||
|
}
|
||||||
|
v.PerformanceBucket = productPerformanceExcelFirstNonEmpty(v.PerformanceBucket, row.PerformanceBucket)
|
||||||
|
v.Recommendation = productPerformanceExcelFirstNonEmpty(v.Recommendation, row.Recommendation)
|
||||||
|
v.addMarket(row.MarketKey, row.SalesQty90, row.SalesUSD90, row.SalesQtyTotal, row.SalesUSDTotal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *productPerformanceExcelVariant) addGeneralRow(row models.ProductPerformanceGeneralRow) {
|
||||||
|
v.fillDimensions(
|
||||||
|
productPerformanceExcelFirstNonEmpty(row.PeriodEnd, row.PeriodStart), row.ProductCode, row.ColorCode, row.ColorDescription, row.YakaKodu, row.ItemDescription,
|
||||||
|
row.Kategori, row.Seri, row.YasGrubu, row.AskiliYan, row.UrunIlkGrubu, row.UrunAnaGrubu, row.UrunAltGrubu,
|
||||||
|
row.BasePriceUSD, row.CostPriceUSD, row.StockQty, row.LastSaleDate, row.LastRefNumber, "",
|
||||||
|
)
|
||||||
|
v.SalesQtyTotalGeneral += row.SalesQtyTotal
|
||||||
|
v.SalesUSDTotalGeneral += row.SalesUSDTotal
|
||||||
|
v.HasGeneralTotal = true
|
||||||
|
v.MarketCountTotal = productPerformanceExcelMaxInt(v.MarketCountTotal, row.MarketCountTotal)
|
||||||
|
if row.SalesUSDTotal > 0 {
|
||||||
|
v.CustomerCountTotal += row.CustomerCountTotal
|
||||||
|
}
|
||||||
|
v.PerformanceBucket = productPerformanceExcelFirstNonEmpty(v.PerformanceBucket, row.PerformanceBucket)
|
||||||
|
v.Recommendation = productPerformanceExcelFirstNonEmpty(v.Recommendation, row.Recommendation)
|
||||||
|
v.addMarket(row.MarketKey, 0, 0, row.SalesQtyTotal, row.SalesUSDTotal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *productPerformanceExcelVariant) fillDimensions(kpiDate, productCode, colorCode, colorDescription, yakaKodu, itemDescription, kategori, seri, yasGrubu, askiliYan, urunIlkGrubu, urunAnaGrubu, urunAltGrubu string, basePrice, costPrice, stockQty float64, lastSaleDate, lastRefNumber, updatedAt string) {
|
||||||
|
v.KpiDate = productPerformanceExcelFirstNonEmpty(v.KpiDate, kpiDate)
|
||||||
|
v.ProductCode = productPerformanceExcelFirstNonEmpty(v.ProductCode, productCode)
|
||||||
|
v.ColorCode = productPerformanceExcelFirstNonEmpty(v.ColorCode, colorCode)
|
||||||
|
v.ColorDescription = productPerformanceExcelFirstNonEmpty(v.ColorDescription, colorDescription)
|
||||||
|
v.YakaKodu = productPerformanceExcelFirstNonEmpty(v.YakaKodu, yakaKodu)
|
||||||
|
v.ItemDescription = productPerformanceExcelFirstNonEmpty(v.ItemDescription, itemDescription)
|
||||||
|
v.Kategori = productPerformanceExcelFirstNonEmpty(v.Kategori, kategori)
|
||||||
|
v.Seri = productPerformanceExcelFirstNonEmpty(v.Seri, seri)
|
||||||
|
v.YasGrubu = productPerformanceExcelFirstNonEmpty(v.YasGrubu, yasGrubu)
|
||||||
|
v.AskiliYan = productPerformanceExcelFirstNonEmpty(v.AskiliYan, productPerformanceExcelCleanOptional(askiliYan))
|
||||||
|
v.UrunIlkGrubu = productPerformanceExcelFirstNonEmpty(v.UrunIlkGrubu, productPerformanceExcelCleanOptional(urunIlkGrubu))
|
||||||
|
v.UrunAnaGrubu = productPerformanceExcelFirstNonEmpty(v.UrunAnaGrubu, urunAnaGrubu)
|
||||||
|
v.UrunAltGrubu = productPerformanceExcelFirstNonEmpty(v.UrunAltGrubu, urunAltGrubu)
|
||||||
|
v.BasePriceUSD = productPerformanceExcelFirstNonZero(v.BasePriceUSD, basePrice)
|
||||||
|
v.CostPriceUSD = productPerformanceExcelFirstNonZero(v.CostPriceUSD, costPrice)
|
||||||
|
v.CostPriceUSD, v.BasePriceUSD = productPerformanceExcelNormalizeCostPair(v.CostPriceUSD, v.BasePriceUSD)
|
||||||
|
if stockQty > v.StockQty {
|
||||||
|
v.StockQty = stockQty
|
||||||
|
}
|
||||||
|
if lastSaleDate > v.LastSaleDate {
|
||||||
|
v.LastSaleDate = lastSaleDate
|
||||||
|
v.LastRefNumber = lastRefNumber
|
||||||
|
}
|
||||||
|
if updatedAt > v.UpdatedAt {
|
||||||
|
v.UpdatedAt = updatedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *productPerformanceExcelVariant) addMarket(marketKey string, salesQty90, salesUSD90, salesQtyTotal, salesUSDTotal float64) {
|
||||||
|
market := productPerformanceExcelMarketName(marketKey)
|
||||||
|
if market == "" || market == "STOK" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if salesUSD90 > 0 {
|
||||||
|
v.Markets90[market] = true
|
||||||
|
}
|
||||||
|
if salesUSDTotal > 0 {
|
||||||
|
v.MarketsTotal[market] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *productPerformanceExcelVariant) totalQty() float64 {
|
||||||
|
if v.HasGeneralTotal {
|
||||||
|
return v.SalesQtyTotalGeneral
|
||||||
|
}
|
||||||
|
return v.SalesQtyTotalProduct
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *productPerformanceExcelVariant) totalUSD() float64 {
|
||||||
|
if v.HasGeneralTotal {
|
||||||
|
return v.SalesUSDTotalGeneral
|
||||||
|
}
|
||||||
|
return v.SalesUSDTotalProduct
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *productPerformanceExcelVariant) marketCount90() int {
|
||||||
|
if v.MarketFiltered {
|
||||||
|
return len(v.Markets90)
|
||||||
|
}
|
||||||
|
return productPerformanceExcelMaxInt(v.MarketCount90, len(v.Markets90))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *productPerformanceExcelVariant) marketCountTotal() int {
|
||||||
|
if v.MarketFiltered {
|
||||||
|
return len(v.MarketsTotal)
|
||||||
|
}
|
||||||
|
return productPerformanceExcelMaxInt(v.MarketCountTotal, len(v.MarketsTotal))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *productPerformanceExcelVariant) marketsLabel() string {
|
||||||
|
return productPerformanceExcelSortedSetLabel(v.MarketsTotal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildProductPerformanceExcelFile(rows []*productPerformanceExcelVariant, stats productPerformanceExcelStats) (*excelize.File, error) {
|
||||||
|
f := excelize.NewFile()
|
||||||
|
sheet := "RenkYaka"
|
||||||
|
f.SetSheetName("Sheet1", sheet)
|
||||||
|
|
||||||
|
columns := productPerformanceExcelColumns()
|
||||||
|
headerStyle, _ := f.NewStyle(&excelize.Style{
|
||||||
|
Font: &excelize.Font{Bold: true, Color: "FFFFFF"},
|
||||||
|
Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"1F4E78"}},
|
||||||
|
Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
|
||||||
|
})
|
||||||
|
textStyle, _ := f.NewStyle(&excelize.Style{NumFmt: 49})
|
||||||
|
intStyle, _ := f.NewStyle(&excelize.Style{NumFmt: 3})
|
||||||
|
decimalStyle, _ := f.NewStyle(&excelize.Style{NumFmt: 4})
|
||||||
|
percentStyle, _ := f.NewStyle(&excelize.Style{NumFmt: 10})
|
||||||
|
|
||||||
|
for i, col := range columns {
|
||||||
|
cell, _ := excelize.CoordinatesToCellName(i+1, 1)
|
||||||
|
_ = f.SetCellStr(sheet, cell, col.Header)
|
||||||
|
}
|
||||||
|
if len(columns) > 0 {
|
||||||
|
lastHeader, _ := excelize.CoordinatesToCellName(len(columns), 1)
|
||||||
|
_ = f.SetCellStyle(sheet, "A1", lastHeader, headerStyle)
|
||||||
|
}
|
||||||
|
|
||||||
|
for rowIndex, row := range rows {
|
||||||
|
excelRow := rowIndex + 2
|
||||||
|
for colIndex, col := range columns {
|
||||||
|
cell, _ := excelize.CoordinatesToCellName(colIndex+1, excelRow)
|
||||||
|
value := col.Value(row, stats)
|
||||||
|
if col.Kind == "text" {
|
||||||
|
_ = f.SetCellStr(sheet, cell, strings.TrimSpace(fmt.Sprint(value)))
|
||||||
|
} else {
|
||||||
|
_ = f.SetCellValue(sheet, cell, productPerformanceExcelFloat(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastRow := productPerformanceExcelMaxInt(len(rows)+1, 2)
|
||||||
|
if len(columns) > 0 {
|
||||||
|
lastFilterCell, _ := excelize.CoordinatesToCellName(len(columns), lastRow)
|
||||||
|
_ = f.AutoFilter(sheet, "A1:"+lastFilterCell, []excelize.AutoFilterOptions{})
|
||||||
|
}
|
||||||
|
for i, col := range columns {
|
||||||
|
colName, _ := excelize.ColumnNumberToName(i + 1)
|
||||||
|
_ = f.SetColWidth(sheet, colName, colName, productPerformanceExcelColumnWidth(col.Header, col.Kind))
|
||||||
|
style := decimalStyle
|
||||||
|
switch col.Kind {
|
||||||
|
case "text":
|
||||||
|
style = textStyle
|
||||||
|
case "int":
|
||||||
|
style = intStyle
|
||||||
|
case "percent":
|
||||||
|
style = percentStyle
|
||||||
|
}
|
||||||
|
_ = f.SetCellStyle(sheet, fmt.Sprintf("%s2", colName), fmt.Sprintf("%s%d", colName, lastRow), style)
|
||||||
|
}
|
||||||
|
_ = f.SetPanes(sheet, &excelize.Panes{Freeze: true, YSplit: 1, TopLeftCell: "A2", ActivePane: "bottomLeft"})
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelColumns() []productPerformanceExcelColumn {
|
||||||
|
base := []productPerformanceExcelColumn{
|
||||||
|
textExcelColumn("KPI Tarihi", func(v *productPerformanceExcelVariant) any { return v.KpiDate }),
|
||||||
|
textExcelColumn("Ürün İlk Grubu", func(v *productPerformanceExcelVariant) any { return v.UrunIlkGrubu }),
|
||||||
|
textExcelColumn("Askılı/Yan", func(v *productPerformanceExcelVariant) any { return v.AskiliYan }),
|
||||||
|
textExcelColumn("Kategori", func(v *productPerformanceExcelVariant) any { return v.Kategori }),
|
||||||
|
textExcelColumn("Ürün Ana Grubu", func(v *productPerformanceExcelVariant) any { return v.UrunAnaGrubu }),
|
||||||
|
textExcelColumn("Ürün Alt Grubu", func(v *productPerformanceExcelVariant) any { return v.UrunAltGrubu }),
|
||||||
|
textExcelColumn("Ürün", func(v *productPerformanceExcelVariant) any { return v.ProductCode }),
|
||||||
|
textExcelColumn("Açıklama", func(v *productPerformanceExcelVariant) any { return v.ItemDescription }),
|
||||||
|
textExcelColumn("Renk", func(v *productPerformanceExcelVariant) any { return v.ColorCode }),
|
||||||
|
textExcelColumn("Renk Açıklama", func(v *productPerformanceExcelVariant) any { return v.ColorDescription }),
|
||||||
|
textExcelColumn("Yaka", func(v *productPerformanceExcelVariant) any { return v.YakaKodu }),
|
||||||
|
textExcelColumn("Renk/Yaka", func(v *productPerformanceExcelVariant) any {
|
||||||
|
return productPerformanceExcelColorYaka(v.ColorCode, v.ColorDescription, v.YakaKodu)
|
||||||
|
}),
|
||||||
|
textExcelColumn("Piyasalar", func(v *productPerformanceExcelVariant) any { return v.marketsLabel() }),
|
||||||
|
numberExcelColumn("Toplam Stok", "int", func(v *productPerformanceExcelVariant) any { return v.StockQty }),
|
||||||
|
}
|
||||||
|
base = append(base, productPerformanceExcelPeriodColumns("90G", "90d", func(v *productPerformanceExcelVariant) (float64, float64, int, int) {
|
||||||
|
return v.SalesQty90, v.SalesUSD90, v.marketCount90(), v.CustomerCount90
|
||||||
|
})...)
|
||||||
|
base = append(base, productPerformanceExcelPeriodColumns("180G", "180d", func(v *productPerformanceExcelVariant) (float64, float64, int, int) {
|
||||||
|
return v.SalesQty180, v.SalesUSD180, v.marketCount90(), v.CustomerCount90
|
||||||
|
})...)
|
||||||
|
base = append(base, productPerformanceExcelPeriodColumns("360G", "365d", func(v *productPerformanceExcelVariant) (float64, float64, int, int) {
|
||||||
|
return v.SalesQty365, v.SalesUSD365, v.marketCount90(), v.CustomerCount90
|
||||||
|
})...)
|
||||||
|
base = append(base, productPerformanceExcelPeriodColumns("Genel", "total", func(v *productPerformanceExcelVariant) (float64, float64, int, int) {
|
||||||
|
return v.totalQty(), v.totalUSD(), v.marketCountTotal(), v.CustomerCountTotal
|
||||||
|
})...)
|
||||||
|
base = append(base,
|
||||||
|
textExcelColumn("Durum", func(v *productPerformanceExcelVariant) any { return productPerformanceExcelStatus(v) }),
|
||||||
|
textExcelColumn("Öneri", func(v *productPerformanceExcelVariant) any { return productPerformanceExcelRecommendation(v) }),
|
||||||
|
textExcelColumn("Son Satış", func(v *productPerformanceExcelVariant) any { return v.LastSaleDate }),
|
||||||
|
textExcelColumn("Son Ref", func(v *productPerformanceExcelVariant) any { return v.LastRefNumber }),
|
||||||
|
textExcelColumn("Güncelleme", func(v *productPerformanceExcelVariant) any { return v.UpdatedAt }),
|
||||||
|
)
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelPeriodColumns(label, suffix string, values func(*productPerformanceExcelVariant) (float64, float64, int, int)) []productPerformanceExcelColumn {
|
||||||
|
return []productPerformanceExcelColumn{
|
||||||
|
numberExcelColumn(label+" Toplam Adet", "int", func(v *productPerformanceExcelVariant) any {
|
||||||
|
qty, _, _, _ := values(v)
|
||||||
|
return qty
|
||||||
|
}),
|
||||||
|
numberExcelColumn(label+" Toplam Ciro USD", "number", func(v *productPerformanceExcelVariant) any {
|
||||||
|
_, usd, _, _ := values(v)
|
||||||
|
return usd
|
||||||
|
}),
|
||||||
|
numberExcelColumn(label+" Ort. Satış Fiyatı USD", "number", func(v *productPerformanceExcelVariant) any {
|
||||||
|
qty, usd, _, _ := values(v)
|
||||||
|
return productPerformanceExcelAvgPrice(usd, qty)
|
||||||
|
}),
|
||||||
|
numberExcelColumn(label+" Yıllık Stok Devir", "number", func(v *productPerformanceExcelVariant) any {
|
||||||
|
qty, _, _, _ := values(v)
|
||||||
|
return productPerformanceExcelStockTurnover(qty, productPerformanceExcelAvgStock(v, suffix), productPerformanceExcelPeriodDays(v, suffix))
|
||||||
|
}),
|
||||||
|
numberExcelColumn(label+" Taban Maliyet USD", "number", func(v *productPerformanceExcelVariant) any { return v.BasePriceUSD }),
|
||||||
|
numberExcelColumn(label+" Çıplak Maliyet USD", "number", func(v *productPerformanceExcelVariant) any { return v.CostPriceUSD }),
|
||||||
|
numberExcelColumn(label+" Taban Toplam K/Z USD", "number", func(v *productPerformanceExcelVariant) any {
|
||||||
|
qty, usd, _, _ := values(v)
|
||||||
|
return productPerformanceExcelGrossProfit(usd, qty, v.BasePriceUSD)
|
||||||
|
}),
|
||||||
|
numberExcelColumn(label+" Çıplak Toplam K/Z USD", "number", func(v *productPerformanceExcelVariant) any {
|
||||||
|
qty, usd, _, _ := values(v)
|
||||||
|
return productPerformanceExcelGrossProfit(usd, qty, v.CostPriceUSD)
|
||||||
|
}),
|
||||||
|
numberExcelColumn(label+" Taban Marj", "percent", func(v *productPerformanceExcelVariant) any {
|
||||||
|
qty, usd, _, _ := values(v)
|
||||||
|
return productPerformanceExcelMargin(usd, qty, v.BasePriceUSD)
|
||||||
|
}),
|
||||||
|
numberExcelColumn(label+" Çıplak Marj", "percent", func(v *productPerformanceExcelVariant) any {
|
||||||
|
qty, usd, _, _ := values(v)
|
||||||
|
return productPerformanceExcelMargin(usd, qty, v.CostPriceUSD)
|
||||||
|
}),
|
||||||
|
numberExcelColumn(label+" Tekil Piyasa", "int", func(v *productPerformanceExcelVariant) any {
|
||||||
|
_, _, markets, _ := values(v)
|
||||||
|
return markets
|
||||||
|
}),
|
||||||
|
numberExcelColumn(label+" Tekil Müşteri", "int", func(v *productPerformanceExcelVariant) any {
|
||||||
|
_, _, _, customers := values(v)
|
||||||
|
return customers
|
||||||
|
}),
|
||||||
|
numberExcelColumn(label+" Ürün Skor", "number", func(v *productPerformanceExcelVariant) any {
|
||||||
|
qty, usd, markets, customers := values(v)
|
||||||
|
days := productPerformanceExcelPeriodDays(v, suffix)
|
||||||
|
return productPerformanceExcelProductScore(suffix, usd, productPerformanceExcelStockTurnover(qty, productPerformanceExcelAvgStock(v, suffix), days), float64(markets), float64(customers), productPerformanceExcelMargin(usd, qty, v.CostPriceUSD), days)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func textExcelColumn(header string, value func(*productPerformanceExcelVariant) any) productPerformanceExcelColumn {
|
||||||
|
return productPerformanceExcelColumn{Header: header, Kind: "text", Value: func(v *productPerformanceExcelVariant, _ productPerformanceExcelStats) any { return value(v) }}
|
||||||
|
}
|
||||||
|
|
||||||
|
func numberExcelColumn(header, kind string, value func(*productPerformanceExcelVariant) any) productPerformanceExcelColumn {
|
||||||
|
return productPerformanceExcelColumn{Header: header, Kind: kind, Value: func(v *productPerformanceExcelVariant, _ productPerformanceExcelStats) any { return value(v) }}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelProductRowMatchesFilters(row models.ProductPerformanceRow, filters map[string][]string) bool {
|
||||||
|
for field, selected := range productPerformanceExcelFilterSets(filters) {
|
||||||
|
if field == "performance_bucket" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !productPerformanceExcelMatchesAny(selected, productPerformanceExcelProductFilterCandidates(row, field)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelGeneralRowMatchesFilters(row models.ProductPerformanceGeneralRow, filters map[string][]string) bool {
|
||||||
|
for field, selected := range productPerformanceExcelFilterSets(filters) {
|
||||||
|
if field == "performance_bucket" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !productPerformanceExcelMatchesAny(selected, productPerformanceExcelGeneralFilterCandidates(row, field)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelVariantMatchesPostFilters(row *productPerformanceExcelVariant, filters map[string][]string) bool {
|
||||||
|
selected := productPerformanceExcelFilterSets(filters)["performance_bucket"]
|
||||||
|
if len(selected) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return productPerformanceExcelMatchesAny(selected, []string{row.PerformanceBucket, productPerformanceExcelStatus(row)})
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelFilterSets(filters map[string][]string) map[string]map[string]bool {
|
||||||
|
out := map[string]map[string]bool{}
|
||||||
|
for field, values := range filters {
|
||||||
|
field = strings.TrimSpace(field)
|
||||||
|
for _, value := range values {
|
||||||
|
value = productPerformanceExcelNormalize(value)
|
||||||
|
if value == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if out[field] == nil {
|
||||||
|
out[field] = map[string]bool{}
|
||||||
|
}
|
||||||
|
out[field][value] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelHasFilter(filters map[string][]string, field string) bool {
|
||||||
|
for _, value := range filters[strings.TrimSpace(field)] {
|
||||||
|
if strings.TrimSpace(value) != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelProductFilterCandidates(row models.ProductPerformanceRow, field string) []string {
|
||||||
|
switch strings.TrimSpace(field) {
|
||||||
|
case "product_code":
|
||||||
|
return []string{row.ProductCode}
|
||||||
|
case "item_description":
|
||||||
|
return []string{row.ItemDescription}
|
||||||
|
case "color_code":
|
||||||
|
return []string{row.ColorCode}
|
||||||
|
case "yaka_kodu":
|
||||||
|
return []string{row.YakaKodu}
|
||||||
|
case "color_yaka":
|
||||||
|
return []string{productPerformanceExcelColorYaka(row.ColorCode, row.ColorDescription, row.YakaKodu), row.ColorCode + "/" + row.YakaKodu}
|
||||||
|
case "kategori":
|
||||||
|
return []string{row.Kategori}
|
||||||
|
case "askili_yan":
|
||||||
|
return []string{productPerformanceExcelCleanOptional(row.AskiliYan)}
|
||||||
|
case "urun_ilk_grubu":
|
||||||
|
return []string{productPerformanceExcelCleanOptional(row.UrunIlkGrubu)}
|
||||||
|
case "urun_ana_grubu":
|
||||||
|
return []string{row.UrunAnaGrubu}
|
||||||
|
case "urun_alt_grubu":
|
||||||
|
return []string{row.UrunAltGrubu}
|
||||||
|
case "market_key":
|
||||||
|
return []string{row.MarketKey, productPerformanceExcelMarketName(row.MarketKey)}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelGeneralFilterCandidates(row models.ProductPerformanceGeneralRow, field string) []string {
|
||||||
|
switch strings.TrimSpace(field) {
|
||||||
|
case "product_code":
|
||||||
|
return []string{row.ProductCode}
|
||||||
|
case "item_description":
|
||||||
|
return []string{row.ItemDescription}
|
||||||
|
case "color_code":
|
||||||
|
return []string{row.ColorCode}
|
||||||
|
case "yaka_kodu":
|
||||||
|
return []string{row.YakaKodu}
|
||||||
|
case "color_yaka":
|
||||||
|
return []string{productPerformanceExcelColorYaka(row.ColorCode, row.ColorDescription, row.YakaKodu), row.ColorCode + "/" + row.YakaKodu}
|
||||||
|
case "kategori":
|
||||||
|
return []string{row.Kategori}
|
||||||
|
case "askili_yan":
|
||||||
|
return []string{productPerformanceExcelCleanOptional(row.AskiliYan)}
|
||||||
|
case "urun_ilk_grubu":
|
||||||
|
return []string{productPerformanceExcelCleanOptional(row.UrunIlkGrubu)}
|
||||||
|
case "urun_ana_grubu":
|
||||||
|
return []string{row.UrunAnaGrubu}
|
||||||
|
case "urun_alt_grubu":
|
||||||
|
return []string{row.UrunAltGrubu}
|
||||||
|
case "market_key":
|
||||||
|
return []string{row.MarketKey, productPerformanceExcelMarketName(row.MarketKey)}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelMatchesAny(selected map[string]bool, candidates []string) bool {
|
||||||
|
if len(selected) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, candidate := range candidates {
|
||||||
|
if selected[productPerformanceExcelNormalize(candidate)] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortProductPerformanceExcelVariants(rows []*productPerformanceExcelVariant, sortBy string, desc bool) {
|
||||||
|
sortBy = strings.TrimSpace(sortBy)
|
||||||
|
if sortBy == "" || sortBy == "image" {
|
||||||
|
sortBy = "product_code"
|
||||||
|
desc = false
|
||||||
|
}
|
||||||
|
sort.SliceStable(rows, func(i, j int) bool {
|
||||||
|
cmp := productPerformanceExcelCompare(rows[i], rows[j], sortBy)
|
||||||
|
if cmp == 0 {
|
||||||
|
cmp = strings.Compare(productPerformanceExcelHierarchySortKey(rows[i]), productPerformanceExcelHierarchySortKey(rows[j]))
|
||||||
|
}
|
||||||
|
if desc {
|
||||||
|
return cmp > 0
|
||||||
|
}
|
||||||
|
return cmp < 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelCompare(a, b *productPerformanceExcelVariant, sortBy string) int {
|
||||||
|
if productPerformanceExcelIsNumericSort(sortBy) {
|
||||||
|
av := productPerformanceExcelSortNumber(a, sortBy)
|
||||||
|
bv := productPerformanceExcelSortNumber(b, sortBy)
|
||||||
|
if av < bv {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if av > bv {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return strings.Compare(productPerformanceExcelNormalize(productPerformanceExcelSortText(a, sortBy)), productPerformanceExcelNormalize(productPerformanceExcelSortText(b, sortBy)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelIsNumericSort(sortBy string) bool {
|
||||||
|
switch sortBy {
|
||||||
|
case "stock_qty", "sales_qty_90d", "sales_qty_180d", "sales_qty_365d", "sales_qty_total",
|
||||||
|
"sales_usd_90d", "sales_usd_180d", "sales_usd_365d", "sales_usd_total",
|
||||||
|
"avg_price_usd_90d", "avg_price_usd_180d", "avg_price_usd_365d", "avg_price_usd_total",
|
||||||
|
"stock_turnover_90d", "stock_turnover_180d", "stock_turnover_365d", "stock_turnover_total",
|
||||||
|
"base_price_usd", "cost_price_usd", "market_count_90d", "customer_count_90d", "market_count_total", "customer_count_total",
|
||||||
|
"performance_score", "performance_score_90d", "performance_score_total":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelSortNumber(row *productPerformanceExcelVariant, sortBy string) float64 {
|
||||||
|
switch sortBy {
|
||||||
|
case "stock_qty":
|
||||||
|
return row.StockQty
|
||||||
|
case "sales_qty_90d":
|
||||||
|
return row.SalesQty90
|
||||||
|
case "sales_qty_180d":
|
||||||
|
return row.SalesQty180
|
||||||
|
case "sales_qty_365d":
|
||||||
|
return row.SalesQty365
|
||||||
|
case "sales_qty_total":
|
||||||
|
return row.totalQty()
|
||||||
|
case "sales_usd_90d":
|
||||||
|
return row.SalesUSD90
|
||||||
|
case "sales_usd_180d":
|
||||||
|
return row.SalesUSD180
|
||||||
|
case "sales_usd_365d":
|
||||||
|
return row.SalesUSD365
|
||||||
|
case "sales_usd_total":
|
||||||
|
return row.totalUSD()
|
||||||
|
case "avg_price_usd_90d":
|
||||||
|
return productPerformanceExcelAvgPrice(row.SalesUSD90, row.SalesQty90)
|
||||||
|
case "avg_price_usd_180d":
|
||||||
|
return productPerformanceExcelAvgPrice(row.SalesUSD180, row.SalesQty180)
|
||||||
|
case "avg_price_usd_365d":
|
||||||
|
return productPerformanceExcelAvgPrice(row.SalesUSD365, row.SalesQty365)
|
||||||
|
case "avg_price_usd_total":
|
||||||
|
return productPerformanceExcelAvgPrice(row.totalUSD(), row.totalQty())
|
||||||
|
case "stock_turnover_90d":
|
||||||
|
return productPerformanceExcelStockTurnover(row.SalesQty90, productPerformanceExcelAvgStock(row, "90d"), productPerformanceExcelPeriodDays(row, "90d"))
|
||||||
|
case "stock_turnover_180d":
|
||||||
|
return productPerformanceExcelStockTurnover(row.SalesQty180, productPerformanceExcelAvgStock(row, "180d"), productPerformanceExcelPeriodDays(row, "180d"))
|
||||||
|
case "stock_turnover_365d":
|
||||||
|
return productPerformanceExcelStockTurnover(row.SalesQty365, productPerformanceExcelAvgStock(row, "365d"), productPerformanceExcelPeriodDays(row, "365d"))
|
||||||
|
case "stock_turnover_total":
|
||||||
|
return productPerformanceExcelStockTurnover(row.totalQty(), productPerformanceExcelAvgStock(row, "total"), productPerformanceExcelPeriodDays(row, "total"))
|
||||||
|
case "base_price_usd":
|
||||||
|
return row.BasePriceUSD
|
||||||
|
case "cost_price_usd":
|
||||||
|
return row.CostPriceUSD
|
||||||
|
case "market_count_90d":
|
||||||
|
return float64(row.marketCount90())
|
||||||
|
case "customer_count_90d":
|
||||||
|
return float64(row.CustomerCount90)
|
||||||
|
case "market_count_total":
|
||||||
|
return float64(row.marketCountTotal())
|
||||||
|
case "customer_count_total":
|
||||||
|
return float64(row.CustomerCountTotal)
|
||||||
|
case "performance_score", "performance_score_90d":
|
||||||
|
days := productPerformanceExcelPeriodDays(row, "90d")
|
||||||
|
return productPerformanceExcelProductScore("90d", row.SalesUSD90, productPerformanceExcelStockTurnover(row.SalesQty90, productPerformanceExcelAvgStock(row, "90d"), days), float64(row.marketCount90()), float64(row.CustomerCount90), productPerformanceExcelMargin(row.SalesUSD90, row.SalesQty90, row.CostPriceUSD), days)
|
||||||
|
case "performance_score_total":
|
||||||
|
days := productPerformanceExcelPeriodDays(row, "total")
|
||||||
|
return productPerformanceExcelProductScore("total", row.totalUSD(), productPerformanceExcelStockTurnover(row.totalQty(), productPerformanceExcelAvgStock(row, "total"), days), float64(row.marketCountTotal()), float64(row.CustomerCountTotal), productPerformanceExcelMargin(row.totalUSD(), row.totalQty(), row.CostPriceUSD), days)
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelSortText(row *productPerformanceExcelVariant, sortBy string) string {
|
||||||
|
switch sortBy {
|
||||||
|
case "product_code":
|
||||||
|
return row.ProductCode
|
||||||
|
case "color_yaka":
|
||||||
|
return productPerformanceExcelColorYaka(row.ColorCode, row.ColorDescription, row.YakaKodu)
|
||||||
|
case "color_code":
|
||||||
|
return row.ColorCode
|
||||||
|
case "yaka_kodu":
|
||||||
|
return row.YakaKodu
|
||||||
|
case "item_description":
|
||||||
|
return row.ItemDescription
|
||||||
|
case "kategori":
|
||||||
|
return row.Kategori
|
||||||
|
case "askili_yan":
|
||||||
|
return row.AskiliYan
|
||||||
|
case "urun_ilk_grubu":
|
||||||
|
return row.UrunIlkGrubu
|
||||||
|
case "urun_ana_grubu":
|
||||||
|
return row.UrunAnaGrubu
|
||||||
|
case "urun_alt_grubu":
|
||||||
|
return row.UrunAltGrubu
|
||||||
|
case "market_key":
|
||||||
|
return row.marketsLabel()
|
||||||
|
case "performance_bucket":
|
||||||
|
return productPerformanceExcelStatus(row)
|
||||||
|
default:
|
||||||
|
return productPerformanceExcelHierarchySortKey(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelStatsFor(rows []*productPerformanceExcelVariant) productPerformanceExcelStats {
|
||||||
|
var stats productPerformanceExcelStats
|
||||||
|
var c90, c180, c365, cTotal float64
|
||||||
|
for _, row := range rows {
|
||||||
|
if row.SalesUSD90 > 0 {
|
||||||
|
stats.AvgSalesUSD90 += row.SalesUSD90
|
||||||
|
c90++
|
||||||
|
}
|
||||||
|
if row.SalesUSD180 > 0 {
|
||||||
|
stats.AvgSalesUSD180 += row.SalesUSD180
|
||||||
|
c180++
|
||||||
|
}
|
||||||
|
if row.SalesUSD365 > 0 {
|
||||||
|
stats.AvgSalesUSD365 += row.SalesUSD365
|
||||||
|
c365++
|
||||||
|
}
|
||||||
|
if row.totalUSD() > 0 {
|
||||||
|
stats.AvgSalesUSDTotal += row.totalUSD()
|
||||||
|
cTotal++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c90 > 0 {
|
||||||
|
stats.AvgSalesUSD90 /= c90
|
||||||
|
}
|
||||||
|
if c180 > 0 {
|
||||||
|
stats.AvgSalesUSD180 /= c180
|
||||||
|
}
|
||||||
|
if c365 > 0 {
|
||||||
|
stats.AvgSalesUSD365 /= c365
|
||||||
|
}
|
||||||
|
if cTotal > 0 {
|
||||||
|
stats.AvgSalesUSDTotal /= cTotal
|
||||||
|
}
|
||||||
|
return stats
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelVariantKey(productCode, colorCode, yakaKodu string) string {
|
||||||
|
return strings.ToUpper(strings.TrimSpace(productCode)) + "|" + strings.ToUpper(strings.TrimSpace(colorCode)) + "|" + strings.ToUpper(strings.TrimSpace(yakaKodu))
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelHierarchySortKey(row *productPerformanceExcelVariant) string {
|
||||||
|
return strings.Join([]string{row.UrunIlkGrubu, row.AskiliYan, row.Kategori, row.UrunAnaGrubu, row.UrunAltGrubu, row.ProductCode, row.ColorCode, row.YakaKodu}, "|")
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelColorYaka(colorCode, colorDescription, yakaKodu string) string {
|
||||||
|
color := strings.TrimSpace(colorCode)
|
||||||
|
if desc := strings.TrimSpace(colorDescription); color != "" && desc != "" {
|
||||||
|
color = color + "-" + strings.ToUpper(desc)
|
||||||
|
}
|
||||||
|
yaka := strings.TrimSpace(yakaKodu)
|
||||||
|
parts := make([]string, 0, 2)
|
||||||
|
if color != "" && color != "-" {
|
||||||
|
parts = append(parts, color)
|
||||||
|
}
|
||||||
|
if yaka != "" && yaka != "-" {
|
||||||
|
parts = append(parts, yaka)
|
||||||
|
}
|
||||||
|
return strings.Join(parts, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelMarketName(value string) string {
|
||||||
|
parts := strings.Split(value, "|")
|
||||||
|
for i := len(parts) - 1; i >= 0; i-- {
|
||||||
|
part := strings.TrimSpace(parts[i])
|
||||||
|
if part != "" {
|
||||||
|
return part
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelStatus(row *productPerformanceExcelVariant) string {
|
||||||
|
switch {
|
||||||
|
case row.totalQty() > 0 && row.totalUSD() <= 0:
|
||||||
|
return "Ciro/Fiyat Kontrol"
|
||||||
|
case row.totalQty() <= 0 && row.StockQty > 0:
|
||||||
|
return "Stok Riski"
|
||||||
|
case productPerformanceExcelMargin(row.totalUSD(), row.totalQty(), row.CostPriceUSD) < 0:
|
||||||
|
return "Fiyat Baskısı"
|
||||||
|
case productPerformanceExcelProductScore("total", row.totalUSD(), productPerformanceExcelStockTurnover(row.totalQty(), productPerformanceExcelAvgStock(row, "total"), productPerformanceExcelPeriodDays(row, "total")), float64(row.marketCountTotal()), float64(row.CustomerCountTotal), productPerformanceExcelMargin(row.totalUSD(), row.totalQty(), row.CostPriceUSD), productPerformanceExcelPeriodDays(row, "total")) >= 70:
|
||||||
|
return "Yıldız Ürün"
|
||||||
|
default:
|
||||||
|
return productPerformanceExcelBucketLabel(row.PerformanceBucket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelRecommendation(row *productPerformanceExcelVariant) string {
|
||||||
|
switch {
|
||||||
|
case row.totalQty() > 0 && row.totalUSD() <= 0:
|
||||||
|
return "Satış adedi var ama USD ciro/fiyat yok. Satış tutarı aktarımı ve döviz dönüşümü kontrol edilmeli."
|
||||||
|
case row.totalQty() <= 0 && row.StockQty > 0:
|
||||||
|
return "Satış yok, stok duruyor. Piyasa/fiyat aksiyonu gerekli."
|
||||||
|
case row.Recommendation != "":
|
||||||
|
return row.Recommendation
|
||||||
|
default:
|
||||||
|
return "Düzenli takip."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelBucketLabel(value string) string {
|
||||||
|
switch strings.TrimSpace(value) {
|
||||||
|
case "YILDIZ_URUN":
|
||||||
|
return "Yıldız Ürün"
|
||||||
|
case "STOKSUZ_TALEP":
|
||||||
|
return "Stoksuz Talep"
|
||||||
|
case "STOK_RISKI":
|
||||||
|
return "Stok Riski"
|
||||||
|
case "FIYAT_BASKISI":
|
||||||
|
return "Fiyat Baskısı"
|
||||||
|
case "FIYAT_FIRSATI":
|
||||||
|
return "Fiyat Fırsatı"
|
||||||
|
case "MALIYET_YOK":
|
||||||
|
return "Maliyet Yok"
|
||||||
|
case "TAKIP":
|
||||||
|
return "Takip"
|
||||||
|
default:
|
||||||
|
return strings.TrimSpace(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelCleanOptional(value string) string {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if value == "-" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelNormalize(value string) string {
|
||||||
|
return strings.ToUpper(strings.TrimSpace(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelFirstNonEmpty(values ...string) string {
|
||||||
|
for _, value := range values {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if value != "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelFirstNonZero(values ...float64) float64 {
|
||||||
|
for _, value := range values {
|
||||||
|
if value != 0 && !math.IsNaN(value) && !math.IsInf(value, 0) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelNormalizeCostPair(costPriceUSD, basePriceUSD float64) (float64, float64) {
|
||||||
|
if costPriceUSD > 0 && basePriceUSD > 0 && costPriceUSD > basePriceUSD {
|
||||||
|
return basePriceUSD, costPriceUSD
|
||||||
|
}
|
||||||
|
return costPriceUSD, basePriceUSD
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelAvgPrice(salesUSD, qty float64) float64 {
|
||||||
|
if qty <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return salesUSD / qty
|
||||||
|
}
|
||||||
|
|
||||||
|
const productPerformanceExcelStockTurnoverYearDays = 360.0
|
||||||
|
|
||||||
|
func productPerformanceExcelAvgStock(row *productPerformanceExcelVariant, suffix string) float64 {
|
||||||
|
switch suffix {
|
||||||
|
case "90d":
|
||||||
|
return productPerformanceExcelFirstNonZero(row.AvgStock90, row.StockQty)
|
||||||
|
case "180d":
|
||||||
|
return productPerformanceExcelFirstNonZero(row.AvgStock180, row.StockQty)
|
||||||
|
case "365d":
|
||||||
|
return productPerformanceExcelFirstNonZero(row.AvgStock365, row.StockQty)
|
||||||
|
case "total":
|
||||||
|
return productPerformanceExcelFirstNonZero(row.AvgStockTotal, row.StockQty)
|
||||||
|
default:
|
||||||
|
return row.StockQty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelPeriodDays(row *productPerformanceExcelVariant, suffix string) float64 {
|
||||||
|
switch suffix {
|
||||||
|
case "90d":
|
||||||
|
return 90
|
||||||
|
case "180d":
|
||||||
|
return 180
|
||||||
|
case "365d":
|
||||||
|
return 360
|
||||||
|
case "total":
|
||||||
|
end, err := time.Parse("2006-01-02", strings.TrimSpace(row.KpiDate))
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
start := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
if end.Before(start) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return end.Sub(start).Hours()/24 + 1
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelStockTurnover(salesQty, avgStock, periodDays float64) float64 {
|
||||||
|
if salesQty <= 0 || avgStock <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
raw := salesQty / avgStock
|
||||||
|
if periodDays <= 0 {
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
return raw * productPerformanceExcelStockTurnoverYearDays / periodDays
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelGrossProfit(salesUSD, qty, unitCost float64) float64 {
|
||||||
|
if qty <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return salesUSD - qty*unitCost
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelMargin(salesUSD, qty, unitCost float64) float64 {
|
||||||
|
if salesUSD <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return productPerformanceExcelGrossProfit(salesUSD, qty, unitCost) / salesUSD
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelProductScore(suffix string, salesUSD, stockTurnover, marketCount, customerCount, margin float64, periodDays ...float64) float64 {
|
||||||
|
if salesUSD <= 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
days := productPerformanceExcelScorePeriodDays(suffix, periodDays...)
|
||||||
|
revenueScore := productPerformanceExcelRatioScore(salesUSD, productPerformanceExcelProductRevenueTargetForDays(suffix, days))
|
||||||
|
score := 0.30*productPerformanceExcelMarginScore(margin) +
|
||||||
|
0.20*productPerformanceExcelRatioScore(stockTurnover, 4) +
|
||||||
|
0.20*revenueScore +
|
||||||
|
0.05*productPerformanceExcelRatioScore(marketCount, productPerformanceExcelMarketSpreadTarget(suffix)) +
|
||||||
|
0.25*productPerformanceExcelRatioScore(customerCount, productPerformanceExcelCustomerSpreadTargetForDays(suffix, days))
|
||||||
|
return productPerformanceExcelRoundScore(score)
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelMarketSpreadTarget(suffix string) float64 {
|
||||||
|
switch suffix {
|
||||||
|
case "90d":
|
||||||
|
return 3
|
||||||
|
case "180d":
|
||||||
|
return 3
|
||||||
|
case "365d":
|
||||||
|
return 6
|
||||||
|
default:
|
||||||
|
return 6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelCustomerSpreadTarget(suffix string) float64 {
|
||||||
|
return productPerformanceExcelCustomerSpreadTargetForDays(suffix, productPerformanceExcelScorePeriodDays(suffix))
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelCustomerSpreadTargetForDays(suffix string, periodDays float64) float64 {
|
||||||
|
switch suffix {
|
||||||
|
case "90d":
|
||||||
|
return 8
|
||||||
|
case "180d":
|
||||||
|
return 20
|
||||||
|
case "365d":
|
||||||
|
return 50
|
||||||
|
default:
|
||||||
|
return 20 * productPerformanceExcelTotalPeriodMultiplier(periodDays)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelProductRevenueTarget(suffix string) float64 {
|
||||||
|
return productPerformanceExcelProductRevenueTargetForDays(suffix, productPerformanceExcelScorePeriodDays(suffix))
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelProductRevenueTargetForDays(suffix string, periodDays float64) float64 {
|
||||||
|
switch suffix {
|
||||||
|
case "180d":
|
||||||
|
return 50000
|
||||||
|
case "365d":
|
||||||
|
return 100000
|
||||||
|
case "total":
|
||||||
|
return 50000 * productPerformanceExcelTotalPeriodMultiplier(periodDays)
|
||||||
|
default:
|
||||||
|
return 25000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelScorePeriodDays(suffix string, periodDays ...float64) float64 {
|
||||||
|
if len(periodDays) > 0 && periodDays[0] > 0 {
|
||||||
|
return periodDays[0]
|
||||||
|
}
|
||||||
|
switch suffix {
|
||||||
|
case "90d":
|
||||||
|
return 90
|
||||||
|
case "180d":
|
||||||
|
return 180
|
||||||
|
case "365d":
|
||||||
|
return 360
|
||||||
|
case "total":
|
||||||
|
start := time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
now := time.Now()
|
||||||
|
if now.Before(start) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return now.Sub(start).Hours()/24 + 1
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelTotalPeriodMultiplier(periodDays float64) float64 {
|
||||||
|
if periodDays <= 0 {
|
||||||
|
periodDays = 180
|
||||||
|
}
|
||||||
|
return math.Max(1, periodDays/180.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelMarginScore(margin float64) float64 {
|
||||||
|
if margin < 0 {
|
||||||
|
margin = 0
|
||||||
|
}
|
||||||
|
return productPerformanceExcelRatioScore(margin, 0.45)
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelRatioScore(value, target float64) float64 {
|
||||||
|
if target <= 0 || value <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
score := value * 100 / target
|
||||||
|
if score > 100 {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
if score < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return score
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelRoundScore(score float64) float64 {
|
||||||
|
if score < 0 {
|
||||||
|
score = 0
|
||||||
|
}
|
||||||
|
if score > 100 {
|
||||||
|
score = 100
|
||||||
|
}
|
||||||
|
return math.Round(score*10000) / 10000
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelFloat(value any) float64 {
|
||||||
|
switch v := value.(type) {
|
||||||
|
case float64:
|
||||||
|
if math.IsNaN(v) || math.IsInf(v, 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
case float32:
|
||||||
|
return productPerformanceExcelFloat(float64(v))
|
||||||
|
case int:
|
||||||
|
return float64(v)
|
||||||
|
case int64:
|
||||||
|
return float64(v)
|
||||||
|
case int32:
|
||||||
|
return float64(v)
|
||||||
|
case uint:
|
||||||
|
return float64(v)
|
||||||
|
case uint64:
|
||||||
|
return float64(v)
|
||||||
|
case uint32:
|
||||||
|
return float64(v)
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelSortedSetLabel(values map[string]bool) string {
|
||||||
|
out := make([]string, 0, len(values))
|
||||||
|
for value := range values {
|
||||||
|
if strings.TrimSpace(value) != "" {
|
||||||
|
out = append(out, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(out)
|
||||||
|
return strings.Join(out, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelColumnWidth(header, kind string) float64 {
|
||||||
|
if kind == "text" {
|
||||||
|
if strings.Contains(header, "Piyasalar") || strings.Contains(header, "Öneri") {
|
||||||
|
return 34
|
||||||
|
}
|
||||||
|
if len([]rune(header)) > 14 {
|
||||||
|
return 24
|
||||||
|
}
|
||||||
|
return 16
|
||||||
|
}
|
||||||
|
if strings.Contains(header, "Marj") || strings.Contains(header, "Skor") {
|
||||||
|
return 14
|
||||||
|
}
|
||||||
|
return 16
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelMaxInt(a, b int) int {
|
||||||
|
if a > b {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
@@ -160,7 +160,7 @@ SELECT TOP (400)
|
|||||||
CONVERT(VARCHAR(8), p.ValidTime, 108) AS ValidTime,
|
CONVERT(VARCHAR(8), p.ValidTime, 108) AS ValidTime,
|
||||||
CONVERT(VARCHAR(19), p.LastUpdatedDate, 120) AS LastUpdatedDate,
|
CONVERT(VARCHAR(19), p.LastUpdatedDate, 120) AS LastUpdatedDate,
|
||||||
CAST(ISNULL(p.IsDisabled, 0) AS BIT) AS IsDisabled
|
CAST(ISNULL(p.IsDisabled, 0) AS BIT) AS IsDisabled
|
||||||
FROM dbo.trPriceListLine p WITH(NOLOCK)
|
FROM dbo.trPriceListLine p
|
||||||
WHERE p.ItemTypeCode = 1
|
WHERE p.ItemTypeCode = 1
|
||||||
AND LTRIM(RTRIM(p.ItemCode)) = @p1
|
AND LTRIM(RTRIM(p.ItemCode)) = @p1
|
||||||
AND ` + wherePG + `
|
AND ` + wherePG + `
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ VALUES (
|
|||||||
// Re-read header id.
|
// Re-read header id.
|
||||||
err = tx.QueryRowContext(ctx, `
|
err = tx.QueryRowContext(ctx, `
|
||||||
SELECT TOP (1) CONVERT(NVARCHAR(36), PriceListHeaderID)
|
SELECT TOP (1) CONVERT(NVARCHAR(36), PriceListHeaderID)
|
||||||
FROM dbo.trPriceListHeader WITH (NOLOCK)
|
FROM dbo.trPriceListHeader
|
||||||
WHERE CompanyCode = 1
|
WHERE CompanyCode = 1
|
||||||
AND LTRIM(RTRIM(PriceGroupCode)) = @pg
|
AND LTRIM(RTRIM(PriceGroupCode)) = @pg
|
||||||
AND LTRIM(RTRIM(DocCurrencyCode)) = @cur
|
AND LTRIM(RTRIM(DocCurrencyCode)) = @cur
|
||||||
@@ -900,7 +900,7 @@ FROM (
|
|||||||
PARTITION BY LTRIM(RTRIM(PriceGroupCode)), LTRIM(RTRIM(DocCurrencyCode))
|
PARTITION BY LTRIM(RTRIM(PriceGroupCode)), LTRIM(RTRIM(DocCurrencyCode))
|
||||||
ORDER BY ValidDate DESC, ValidTime DESC, LastUpdatedDate DESC
|
ORDER BY ValidDate DESC, ValidTime DESC, LastUpdatedDate DESC
|
||||||
) AS rn
|
) AS rn
|
||||||
FROM dbo.trPriceListLine WITH(NOLOCK)
|
FROM dbo.trPriceListLine
|
||||||
WHERE ItemTypeCode = 1
|
WHERE ItemTypeCode = 1
|
||||||
AND LTRIM(RTRIM(ItemCode)) = @ItemCode
|
AND LTRIM(RTRIM(ItemCode)) = @ItemCode
|
||||||
AND ISNULL(IsDisabled, 0) = 0
|
AND ISNULL(IsDisabled, 0) = 0
|
||||||
@@ -970,7 +970,7 @@ WHERE rn = 1;
|
|||||||
var latestPrice sql.NullFloat64
|
var latestPrice sql.NullFloat64
|
||||||
_ = msTx.QueryRowContext(ctx, `
|
_ = msTx.QueryRowContext(ctx, `
|
||||||
SELECT TOP (1) CAST(Price AS FLOAT)
|
SELECT TOP (1) CAST(Price AS FLOAT)
|
||||||
FROM dbo.trPriceListLine WITH(NOLOCK)
|
FROM dbo.trPriceListLine
|
||||||
WHERE ItemTypeCode = 1
|
WHERE ItemTypeCode = 1
|
||||||
AND LTRIM(RTRIM(ItemCode)) = @p1
|
AND LTRIM(RTRIM(ItemCode)) = @p1
|
||||||
AND LTRIM(RTRIM(DocCurrencyCode)) = @p2
|
AND LTRIM(RTRIM(DocCurrencyCode)) = @p2
|
||||||
@@ -988,7 +988,7 @@ ORDER BY ValidDate DESC, ValidTime DESC, LastUpdatedDate DESC;
|
|||||||
if nextSort <= 0 {
|
if nextSort <= 0 {
|
||||||
_ = msTx.QueryRowContext(ctx, `
|
_ = msTx.QueryRowContext(ctx, `
|
||||||
SELECT ISNULL(MAX(SortOrder), 0) + 1
|
SELECT ISNULL(MAX(SortOrder), 0) + 1
|
||||||
FROM dbo.trPriceListLine WITH(NOLOCK)
|
FROM dbo.trPriceListLine
|
||||||
WHERE PriceListHeaderID = CONVERT(UNIQUEIDENTIFIER, @p1);
|
WHERE PriceListHeaderID = CONVERT(UNIQUEIDENTIFIER, @p1);
|
||||||
`, sql.Named("p1", headerID)).Scan(&nextSort)
|
`, sql.Named("p1", headerID)).Scan(&nextSort)
|
||||||
if nextSort <= 0 {
|
if nextSort <= 0 {
|
||||||
|
|||||||
+523
-32
@@ -7,6 +7,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -106,11 +107,43 @@ func PostProductSeriesDefinitionHandler(pg *sql.DB) http.HandlerFunc {
|
|||||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
err := pg.QueryRowContext(ctx, `
|
err := pg.QueryRowContext(ctx, `
|
||||||
INSERT INTO dfgrp (code, title, is_active, typ, master, parent_filter, sort_order, is_required, notes)
|
WITH updated AS (
|
||||||
VALUES ($1, $2, COALESCE($3, TRUE), 'opt', 'zbggseri', $4, $5, FALSE, $6)
|
UPDATE dfgrp
|
||||||
RETURNING id
|
SET title=$2,
|
||||||
|
is_active=COALESCE($3, TRUE),
|
||||||
|
parent_filter=$4,
|
||||||
|
sort_order=$5,
|
||||||
|
notes=$6
|
||||||
|
WHERE master='zbggseri' AND code=$1
|
||||||
|
RETURNING id
|
||||||
|
),
|
||||||
|
inserted AS (
|
||||||
|
INSERT INTO dfgrp (code, title, is_active, typ, master, parent_filter, sort_order, is_required, notes)
|
||||||
|
SELECT $1, $2, COALESCE($3, TRUE), 'opt', 'zbggseri', $4, $5, FALSE, $6
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM updated)
|
||||||
|
RETURNING id
|
||||||
|
)
|
||||||
|
SELECT id FROM updated
|
||||||
|
UNION ALL
|
||||||
|
SELECT id FROM inserted
|
||||||
|
LIMIT 1
|
||||||
`, req.Code, req.Title, req.IsActive, req.ParentFilter, req.SortOrder, req.Notes).Scan(&req.ID)
|
`, req.Code, req.Title, req.IsActive, req.ParentFilter, req.SortOrder, req.Notes).Scan(&req.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if isPostgresUniqueViolation(err, "ndx_dfgrp_uq4") {
|
||||||
|
if selErr := pg.QueryRowContext(ctx, `
|
||||||
|
UPDATE dfgrp
|
||||||
|
SET title=$2,
|
||||||
|
is_active=COALESCE($3, TRUE),
|
||||||
|
parent_filter=$4,
|
||||||
|
sort_order=$5,
|
||||||
|
notes=$6
|
||||||
|
WHERE master='zbggseri' AND code=$1
|
||||||
|
RETURNING id
|
||||||
|
`, req.Code, req.Title, req.IsActive, req.ParentFilter, req.SortOrder, req.Notes).Scan(&req.ID); selErr == nil {
|
||||||
|
writeJSON(w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
http.Error(w, "Seri tanimi eklenemedi: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Seri tanimi eklenemedi: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -150,6 +183,10 @@ SET code=$2,
|
|||||||
WHERE id=$1 AND master='zbggseri'
|
WHERE id=$1 AND master='zbggseri'
|
||||||
`, id, req.Code, req.Title, req.IsActive, req.ParentFilter, req.SortOrder, req.Notes)
|
`, id, req.Code, req.Title, req.IsActive, req.ParentFilter, req.SortOrder, req.Notes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if isPostgresUniqueViolation(err, "ndx_dfgrp_uq4") {
|
||||||
|
http.Error(w, "Bu seri kodu zaten var. Mevcut seri satirini duzenleyin veya farkli kod kullanin.", http.StatusConflict)
|
||||||
|
return
|
||||||
|
}
|
||||||
http.Error(w, "Seri tanimi guncellenemedi: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Seri tanimi guncellenemedi: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -291,10 +328,50 @@ func GetProductSeriesMappingsHandler(pg *sql.DB) http.HandlerFunc {
|
|||||||
defByID[d.ID] = d
|
defByID[d.ID] = d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strict token->dim_id resolver (no inference/persistence). UI should not invent dim mappings.
|
||||||
|
loadDimTokenIDsStrict := func(ctx context.Context, pg *sql.DB, column string, tokens []string) (map[string]int64, error) {
|
||||||
|
out := map[string]int64{}
|
||||||
|
if len(tokens) == 0 {
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
rows, err := pg.QueryContext(ctx, `
|
||||||
|
SELECT token, dim_id
|
||||||
|
FROM mk_dim_token_map
|
||||||
|
WHERE dim_column=$1 AND token = ANY($2)
|
||||||
|
`, column, pq.Array(tokens))
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
var tok string
|
||||||
|
var id int64
|
||||||
|
if err := rows.Scan(&tok, &id); err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
out[strings.TrimSpace(tok)] = id
|
||||||
|
}
|
||||||
|
return out, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authoritative dims/combos are resolved via mmitem_dim + dfgrp.code (see helpers below).
|
||||||
|
|
||||||
codes := setToSortedSlice(codeSet)
|
codes := setToSortedSlice(codeSet)
|
||||||
mmitemByCode, _ := loadMmitemIDs(ctx, pg, codes)
|
mmitemByCode, _ := loadMmitemIDs(ctx, pg, codes)
|
||||||
dim1ByToken, _ := loadDimTokenIDs(ctx, pg, "dimval1", setToSortedSlice(colorSet))
|
dim3ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval3", setToSortedSlice(dim3Set))
|
||||||
dim3ByToken, _ := loadDimTokenIDs(ctx, pg, "dimval3", setToSortedSlice(dim3Set))
|
mmitemIDs := make([]int64, 0, len(mmitemByCode))
|
||||||
|
for _, c := range codes {
|
||||||
|
if id := mmitemByCode[c]; id > 0 {
|
||||||
|
mmitemIDs = append(mmitemIDs, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Normalize color tokens to UPPER/TRIM to match dfgrp.code.
|
||||||
|
colorTokens := setToSortedSlice(colorSet)
|
||||||
|
for i := range colorTokens {
|
||||||
|
colorTokens[i] = strings.ToUpper(strings.TrimSpace(colorTokens[i]))
|
||||||
|
}
|
||||||
|
dim1ByMmitemToken, _ := loadDim1ByMmitemAndToken(ctx, pg, mmitemIDs, colorTokens)
|
||||||
|
combos, _ := loadProductDimCombosFromMmitemDim(ctx, pg, mmitemIDs)
|
||||||
existing, _ := loadProductSeriesAssignments(ctx, pg, codes)
|
existing, _ := loadProductSeriesAssignments(ctx, pg, codes)
|
||||||
|
|
||||||
// Per-request cache for per-mmitem dimval3 inference to avoid repeated dfblob scans.
|
// Per-request cache for per-mmitem dimval3 inference to avoid repeated dfblob scans.
|
||||||
@@ -309,7 +386,6 @@ func GetProductSeriesMappingsHandler(pg *sql.DB) http.HandlerFunc {
|
|||||||
if v, ok := inferCache[key]; ok {
|
if v, ok := inferCache[key]; ok {
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
// Use the same pattern approach as resolveDimvalFromFileNameToken, but scoped to src_id.
|
|
||||||
patterns := buildNameLikePatterns(tok)
|
patterns := buildNameLikePatterns(tok)
|
||||||
if len(patterns) == 0 {
|
if len(patterns) == 0 {
|
||||||
inferCache[key] = 0
|
inferCache[key] = 0
|
||||||
@@ -359,18 +435,29 @@ LIMIT 1
|
|||||||
out := make([]productSeriesMappingRow, 0, len(grouped))
|
out := make([]productSeriesMappingRow, 0, len(grouped))
|
||||||
for _, row := range grouped {
|
for _, row := range grouped {
|
||||||
row.MmitemID = mmitemByCode[row.ProductCode]
|
row.MmitemID = mmitemByCode[row.ProductCode]
|
||||||
row.Dim1ID = dim1ByToken[row.ColorCode]
|
row.Dim1ID = dim1ByMmitemToken[fmt.Sprintf("%d|%s", row.MmitemID, strings.ToUpper(strings.TrimSpace(row.ColorCode)))]
|
||||||
if row.Dim3Code != "" {
|
if row.Dim3Code != "" {
|
||||||
// dimval3 tokens can be ambiguous globally; prefer per-mmitem inference.
|
// Prefer token map; fallback to per-item inference (do not persist).
|
||||||
if inferred := inferDim3ForMmitem(row.MmitemID, row.Dim3Code); inferred > 0 {
|
if v := dim3ByToken[row.Dim3Code]; v > 0 {
|
||||||
|
row.Dim3ID = v
|
||||||
|
} else if inferred := inferDim3ForMmitem(row.MmitemID, row.Dim3Code); inferred > 0 {
|
||||||
row.Dim3ID = inferred
|
row.Dim3ID = inferred
|
||||||
} else {
|
|
||||||
row.Dim3ID = dim3ByToken[row.Dim3Code]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Only show variants that are also known/valid on the PG side (product_code + dim1 + dim3_key).
|
||||||
|
// This prevents UI from listing stock variants that don't exist in PG's authoritative item-dim combos.
|
||||||
row.MappingReady = row.MmitemID > 0 && row.Dim1ID > 0 && (row.Dim3Code == "" || row.Dim3ID > 0)
|
row.MappingReady = row.MmitemID > 0 && row.Dim1ID > 0 && (row.Dim3Code == "" || row.Dim3ID > 0)
|
||||||
|
dim3Key := int64(0)
|
||||||
|
if row.Dim3Code != "" {
|
||||||
|
dim3Key = row.Dim3ID
|
||||||
|
}
|
||||||
|
if row.MappingReady {
|
||||||
|
_, ok := combos[fmt.Sprintf("%d|%d|%d", row.MmitemID, row.Dim1ID, dim3Key)]
|
||||||
|
row.MappingReady = ok
|
||||||
|
}
|
||||||
if !row.MappingReady {
|
if !row.MappingReady {
|
||||||
row.MappingWarning = "PG urun veya varyant token eslesmesi bulunamadi"
|
// Skip: do not surface "orphan" variants in the UI.
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
assignKey := assignmentKey(row.ProductCode, row.Dim1ID, row.Dim3ID)
|
assignKey := assignmentKey(row.ProductCode, row.Dim1ID, row.Dim3ID)
|
||||||
appendSeries := func(key string) {
|
appendSeries := func(key string) {
|
||||||
@@ -382,11 +469,6 @@ LIMIT 1
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
appendSeries(assignKey)
|
appendSeries(assignKey)
|
||||||
// Fallback: if we couldn't match dim3-specific assignments (dim3 token ambiguity / missing),
|
|
||||||
// show the default (dim3 NULL) assignments for the same product+color.
|
|
||||||
if len(row.SeriesIDs) == 0 && row.Dim3Code != "" && row.Dim1ID > 0 {
|
|
||||||
appendSeries(assignmentKey(row.ProductCode, row.Dim1ID, 0))
|
|
||||||
}
|
|
||||||
sort.Slice(row.Series, func(i, j int) bool { return row.Series[i].Code < row.Series[j].Code })
|
sort.Slice(row.Series, func(i, j int) bool { return row.Series[i].Code < row.Series[j].Code })
|
||||||
sort.Slice(row.SeriesIDs, func(i, j int) bool { return row.SeriesIDs[i] < row.SeriesIDs[j] })
|
sort.Slice(row.SeriesIDs, func(i, j int) bool { return row.SeriesIDs[i] < row.SeriesIDs[j] })
|
||||||
out = append(out, *row)
|
out = append(out, *row)
|
||||||
@@ -415,6 +497,282 @@ LIMIT 1
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetProductSeriesOrphanMappingsHandler(pg *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
f := readStockAttrFilters(r)
|
||||||
|
limit := 0
|
||||||
|
if raw := strings.TrimSpace(r.URL.Query().Get("limit")); raw != "" {
|
||||||
|
if n, err := strconv.Atoi(raw); err == nil && n >= 0 {
|
||||||
|
limit = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
search := firstNonEmpty(r.URL.Query().Get("q"), r.URL.Query().Get("code"), r.URL.Query().Get("product_code"))
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), 120*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
msRows, err := db.MssqlDB.QueryContext(ctx, queries.GetProductSeriesStockRowsQuery,
|
||||||
|
f.kategori,
|
||||||
|
f.urunAnaGrubu,
|
||||||
|
joinFilterValues(f.urunAltGrubu),
|
||||||
|
joinFilterValues(f.renk),
|
||||||
|
joinFilterValues(f.renk2),
|
||||||
|
joinFilterValues(f.urunIcerigi),
|
||||||
|
joinFilterValues(f.fit),
|
||||||
|
joinFilterValues(f.drop),
|
||||||
|
joinFilterValues(f.beden),
|
||||||
|
strconv.Itoa(limit),
|
||||||
|
search,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Stok seri listesi alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer msRows.Close()
|
||||||
|
|
||||||
|
grouped := map[string]*productSeriesMappingRow{}
|
||||||
|
codeSet := map[string]struct{}{}
|
||||||
|
colorSet := map[string]struct{}{}
|
||||||
|
dim3Set := map[string]struct{}{}
|
||||||
|
sizeSet := map[string]struct{}{}
|
||||||
|
for msRows.Next() {
|
||||||
|
var raw productSeriesStockRawRow
|
||||||
|
if err := msRows.Scan(
|
||||||
|
&raw.ProductCode,
|
||||||
|
&raw.ProductDescription,
|
||||||
|
&raw.ColorCode,
|
||||||
|
&raw.ColorTitle,
|
||||||
|
&raw.Dim3Code,
|
||||||
|
&raw.SizeCode,
|
||||||
|
&raw.Qty,
|
||||||
|
&raw.UrunAnaGrubu,
|
||||||
|
&raw.UrunAltGrubu,
|
||||||
|
&raw.Marka,
|
||||||
|
&raw.DropVal,
|
||||||
|
&raw.Fit,
|
||||||
|
&raw.UrunIcerigi,
|
||||||
|
&raw.Kategori,
|
||||||
|
); err != nil {
|
||||||
|
http.Error(w, "Stok satiri okunamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
raw.ProductCode = strings.TrimSpace(raw.ProductCode)
|
||||||
|
raw.ColorCode = strings.TrimSpace(raw.ColorCode)
|
||||||
|
raw.Dim3Code = strings.TrimSpace(raw.Dim3Code)
|
||||||
|
raw.SizeCode = strings.TrimSpace(raw.SizeCode)
|
||||||
|
key := productSeriesKey(raw.ProductCode, raw.ColorCode, raw.Dim3Code)
|
||||||
|
row := grouped[key]
|
||||||
|
if row == nil {
|
||||||
|
row = &productSeriesMappingRow{
|
||||||
|
RowKey: key,
|
||||||
|
ProductCode: raw.ProductCode,
|
||||||
|
ProductDescription: strings.TrimSpace(raw.ProductDescription),
|
||||||
|
ColorCode: raw.ColorCode,
|
||||||
|
ColorTitle: strings.TrimSpace(raw.ColorTitle),
|
||||||
|
Dim3Code: raw.Dim3Code,
|
||||||
|
UrunAnaGrubu: strings.TrimSpace(raw.UrunAnaGrubu),
|
||||||
|
UrunAltGrubu: strings.TrimSpace(raw.UrunAltGrubu),
|
||||||
|
Marka: strings.TrimSpace(raw.Marka),
|
||||||
|
DropVal: strings.TrimSpace(raw.DropVal),
|
||||||
|
Fit: strings.TrimSpace(raw.Fit),
|
||||||
|
UrunIcerigi: strings.TrimSpace(raw.UrunIcerigi),
|
||||||
|
Kategori: strings.TrimSpace(raw.Kategori),
|
||||||
|
SizeQty: map[string]float64{},
|
||||||
|
SeriesIDs: []int64{},
|
||||||
|
Series: []productSeriesDefinition{},
|
||||||
|
}
|
||||||
|
grouped[key] = row
|
||||||
|
codeSet[raw.ProductCode] = struct{}{}
|
||||||
|
colorSet[raw.ColorCode] = struct{}{}
|
||||||
|
if raw.Dim3Code != "" {
|
||||||
|
dim3Set[raw.Dim3Code] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if raw.SizeCode != "" {
|
||||||
|
row.SizeQty[raw.SizeCode] = raw.Qty
|
||||||
|
sizeSet[raw.SizeCode] = struct{}{}
|
||||||
|
}
|
||||||
|
row.TotalQty += raw.Qty
|
||||||
|
}
|
||||||
|
if err := msRows.Err(); err != nil {
|
||||||
|
http.Error(w, "Stok satirlari okunamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defs, err := listProductSeriesDefinitions(ctx, pg, true)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Seri tanimlari alinamadi: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strict token map lookups (no inference).
|
||||||
|
loadDimTokenIDsStrict := func(ctx context.Context, pg *sql.DB, column string, tokens []string) (map[string]int64, error) {
|
||||||
|
out := map[string]int64{}
|
||||||
|
if len(tokens) == 0 {
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
rows, err := pg.QueryContext(ctx, `
|
||||||
|
SELECT token, dim_id
|
||||||
|
FROM mk_dim_token_map
|
||||||
|
WHERE dim_column=$1 AND token = ANY($2)
|
||||||
|
`, column, pq.Array(tokens))
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
var tok string
|
||||||
|
var id int64
|
||||||
|
if err := rows.Scan(&tok, &id); err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
out[strings.TrimSpace(tok)] = id
|
||||||
|
}
|
||||||
|
return out, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
codes := setToSortedSlice(codeSet)
|
||||||
|
mmitemByCode, _ := loadMmitemIDs(ctx, pg, codes)
|
||||||
|
dim3ByToken, _ := loadDimTokenIDsStrict(ctx, pg, "dimval3", setToSortedSlice(dim3Set))
|
||||||
|
mmitemIDs := make([]int64, 0, len(mmitemByCode))
|
||||||
|
for _, c := range codes {
|
||||||
|
if id := mmitemByCode[c]; id > 0 {
|
||||||
|
mmitemIDs = append(mmitemIDs, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
colorTokens := setToSortedSlice(colorSet)
|
||||||
|
for i := range colorTokens {
|
||||||
|
colorTokens[i] = strings.ToUpper(strings.TrimSpace(colorTokens[i]))
|
||||||
|
}
|
||||||
|
dim1ByMmitemToken, _ := loadDim1ByMmitemAndToken(ctx, pg, mmitemIDs, colorTokens)
|
||||||
|
combos, _ := loadProductDimCombosFromMmitemDim(ctx, pg, mmitemIDs)
|
||||||
|
|
||||||
|
// Per-request cache for per-mmitem dimval3 inference (dfblob scan).
|
||||||
|
inferCache := map[string]int64{}
|
||||||
|
inferDim3ForMmitem := func(mmitemID int64, token string) int64 {
|
||||||
|
mmitemID = int64(mmitemID)
|
||||||
|
tok := strings.ToUpper(normalizeDimParam(token))
|
||||||
|
if mmitemID <= 0 || tok == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
key := fmt.Sprintf("%d|%s", mmitemID, tok)
|
||||||
|
if v, ok := inferCache[key]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
patterns := buildNameLikePatterns(tok)
|
||||||
|
if len(patterns) == 0 {
|
||||||
|
inferCache[key] = 0
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
var dimv string
|
||||||
|
err := pg.QueryRowContext(ctx, `
|
||||||
|
SELECT x.dimv
|
||||||
|
FROM (
|
||||||
|
SELECT COALESCE(dimval3::text, '') AS dimv, COUNT(*) AS cnt
|
||||||
|
FROM dfblob
|
||||||
|
WHERE src_table='mmitem'
|
||||||
|
AND typ='img'
|
||||||
|
AND src_id=$7
|
||||||
|
AND COALESCE(dimval3::text, '') <> ''
|
||||||
|
AND (
|
||||||
|
UPPER(COALESCE(file_name,'')) LIKE $1 OR
|
||||||
|
UPPER(COALESCE(file_name,'')) LIKE $2 OR
|
||||||
|
UPPER(COALESCE(file_name,'')) LIKE $3 OR
|
||||||
|
UPPER(COALESCE(file_name,'')) LIKE $4 OR
|
||||||
|
UPPER(COALESCE(file_name,'')) LIKE $5 OR
|
||||||
|
UPPER(COALESCE(file_name,'')) LIKE $6
|
||||||
|
)
|
||||||
|
GROUP BY COALESCE(dimval3::text, '')
|
||||||
|
) x
|
||||||
|
ORDER BY x.cnt DESC, x.dimv
|
||||||
|
LIMIT 1
|
||||||
|
`, patterns[0], patterns[1], patterns[2], patterns[3], patterns[4], patterns[5], mmitemID).Scan(&dimv)
|
||||||
|
if err != nil {
|
||||||
|
inferCache[key] = 0
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
dimv = strings.TrimSpace(dimv)
|
||||||
|
if dimv == "" || dimv == "0" {
|
||||||
|
inferCache[key] = 0
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
id, err := strconv.ParseInt(dimv, 10, 64)
|
||||||
|
if err != nil || id <= 0 {
|
||||||
|
inferCache[key] = 0
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
inferCache[key] = id
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make([]productSeriesMappingRow, 0, len(grouped))
|
||||||
|
for _, row := range grouped {
|
||||||
|
if row.TotalQty <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
row.MmitemID = mmitemByCode[row.ProductCode]
|
||||||
|
row.Dim1ID = dim1ByMmitemToken[fmt.Sprintf("%d|%s", row.MmitemID, strings.ToUpper(strings.TrimSpace(row.ColorCode)))]
|
||||||
|
if row.Dim3Code != "" {
|
||||||
|
if v := dim3ByToken[row.Dim3Code]; v > 0 {
|
||||||
|
row.Dim3ID = v
|
||||||
|
} else if inferred := inferDim3ForMmitem(row.MmitemID, row.Dim3Code); inferred > 0 {
|
||||||
|
row.Dim3ID = inferred
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine orphan reason.
|
||||||
|
baseReady := row.MmitemID > 0 && row.Dim1ID > 0 && (row.Dim3Code == "" || row.Dim3ID > 0)
|
||||||
|
dim3Key := int64(0)
|
||||||
|
if row.Dim3Code != "" {
|
||||||
|
dim3Key = row.Dim3ID
|
||||||
|
}
|
||||||
|
_, comboOK := combos[fmt.Sprintf("%d|%d|%d", row.MmitemID, row.Dim1ID, dim3Key)]
|
||||||
|
|
||||||
|
row.MappingReady = false
|
||||||
|
switch {
|
||||||
|
case row.MmitemID <= 0:
|
||||||
|
row.MappingWarning = "B2B'de urun yok"
|
||||||
|
case row.Dim1ID <= 0:
|
||||||
|
row.MappingWarning = "B2B'de bu urun icin renk yok"
|
||||||
|
case row.Dim3Code != "" && row.Dim3ID <= 0:
|
||||||
|
row.MappingWarning = "B2B'de ikinci renk eslesmesi yok"
|
||||||
|
case !comboOK:
|
||||||
|
row.MappingWarning = "B2B'de bu urun/renk/ikinci renk kombinasyonu yok"
|
||||||
|
default:
|
||||||
|
// Not an orphan; skip.
|
||||||
|
if baseReady && comboOK {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
row.MappingWarning = "B2B varyant eslesmesi bulunamadi"
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, *row)
|
||||||
|
}
|
||||||
|
|
||||||
|
productTotals := productSeriesTotalQtyByCode(out)
|
||||||
|
sort.Slice(out, func(i, j int) bool {
|
||||||
|
totalI := productTotals[out[i].ProductCode]
|
||||||
|
totalJ := productTotals[out[j].ProductCode]
|
||||||
|
if totalI != totalJ {
|
||||||
|
return totalI > totalJ
|
||||||
|
}
|
||||||
|
if out[i].ProductCode != out[j].ProductCode {
|
||||||
|
return out[i].ProductCode < out[j].ProductCode
|
||||||
|
}
|
||||||
|
if out[i].ColorCode != out[j].ColorCode {
|
||||||
|
return out[i].ColorCode < out[j].ColorCode
|
||||||
|
}
|
||||||
|
return out[i].Dim3Code < out[j].Dim3Code
|
||||||
|
})
|
||||||
|
|
||||||
|
writeJSON(w, productSeriesMappingsResponse{
|
||||||
|
Rows: out,
|
||||||
|
SizeColumns: sortSizeColumns(setToSortedSlice(sizeSet)),
|
||||||
|
Definitions: defs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func productSeriesTotalQtyByCode(rows []productSeriesMappingRow) map[string]float64 {
|
func productSeriesTotalQtyByCode(rows []productSeriesMappingRow) map[string]float64 {
|
||||||
out := make(map[string]float64, len(rows))
|
out := make(map[string]float64, len(rows))
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
@@ -423,6 +781,68 @@ func productSeriesTotalQtyByCode(rows []productSeriesMappingRow) map[string]floa
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadDim1ByMmitemAndToken(ctx context.Context, pg *sql.DB, mmitemIDs []int64, colorTokens []string) (map[string]int64, error) {
|
||||||
|
out := map[string]int64{}
|
||||||
|
if len(mmitemIDs) == 0 || len(colorTokens) == 0 {
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
rows, err := pg.QueryContext(ctx, `
|
||||||
|
SELECT md.mmitem_id, UPPER(BTRIM(d.code)) AS code, md.val1
|
||||||
|
FROM mmitem_dim md
|
||||||
|
JOIN dfgrp d ON d.id = md.val1
|
||||||
|
WHERE md.mmitem_id = ANY($1)
|
||||||
|
AND md.is_active = TRUE
|
||||||
|
AND UPPER(BTRIM(d.code)) = ANY($2)
|
||||||
|
GROUP BY md.mmitem_id, UPPER(BTRIM(d.code)), md.val1
|
||||||
|
`, pq.Array(mmitemIDs), pq.Array(colorTokens))
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
var mmid, dim1 int64
|
||||||
|
var code string
|
||||||
|
if err := rows.Scan(&mmid, &code, &dim1); err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
key := fmt.Sprintf("%d|%s", mmid, strings.TrimSpace(code))
|
||||||
|
if _, ok := out[key]; !ok && dim1 > 0 {
|
||||||
|
out[key] = dim1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadProductDimCombosFromMmitemDim(ctx context.Context, pg *sql.DB, mmitemIDs []int64) (map[string]struct{}, error) {
|
||||||
|
out := map[string]struct{}{}
|
||||||
|
if len(mmitemIDs) == 0 {
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
rows, err := pg.QueryContext(ctx, `
|
||||||
|
SELECT
|
||||||
|
mmitem_id,
|
||||||
|
val1 AS dim1,
|
||||||
|
CASE WHEN val3 IS NOT NULL AND val3 > 1000 THEN val3 ELSE 0 END AS dim3_key
|
||||||
|
FROM mmitem_dim
|
||||||
|
WHERE mmitem_id = ANY($1)
|
||||||
|
AND is_active = TRUE
|
||||||
|
AND val1 IS NOT NULL
|
||||||
|
GROUP BY mmitem_id, val1, CASE WHEN val3 IS NOT NULL AND val3 > 1000 THEN val3 ELSE 0 END
|
||||||
|
`, pq.Array(mmitemIDs))
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
var mmitemID, dim1, dim3Key int64
|
||||||
|
if err := rows.Scan(&mmitemID, &dim1, &dim3Key); err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
out[fmt.Sprintf("%d|%d|%d", mmitemID, dim1, dim3Key)] = struct{}{}
|
||||||
|
}
|
||||||
|
return out, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
type saveProductSeriesMappingsRequest struct {
|
type saveProductSeriesMappingsRequest struct {
|
||||||
Items []struct {
|
Items []struct {
|
||||||
ProductCode string `json:"product_code"`
|
ProductCode string `json:"product_code"`
|
||||||
@@ -449,23 +869,58 @@ func PostProductSeriesMappingsSaveHandler(pg *sql.DB) http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
|
// Tag the DB session for audit attribution (http handler vs scheduler).
|
||||||
|
// This affects only statements in this transaction.
|
||||||
|
_, _ = tx.ExecContext(ctx, `SELECT set_config('application_name', current_setting('application_name') || '|series-save', true)`)
|
||||||
|
|
||||||
|
// Safety defaults:
|
||||||
|
// - Don't allow accidental clearing when UI sends empty series_ids.
|
||||||
|
// - Don't allow saving fallback code=1 from UI unless explicitly enabled.
|
||||||
|
allowClear := strings.TrimSpace(strings.ToLower(r.URL.Query().Get("clear"))) == "true"
|
||||||
|
allowFallback := strings.TrimSpace(strings.ToLower(r.URL.Query().Get("allow_fallback"))) == "true"
|
||||||
|
|
||||||
|
log.Printf("[ProductSeriesMappingsSave] items=%d allow_clear=%t allow_fallback=%t remote=%s ua=%s",
|
||||||
|
len(req.Items), allowClear, allowFallback, strings.TrimSpace(r.RemoteAddr), strings.TrimSpace(r.UserAgent()))
|
||||||
|
|
||||||
|
var fallbackSeriesID int64 = 0
|
||||||
|
_ = tx.QueryRowContext(ctx, `SELECT id FROM dfgrp WHERE master='zbggseri' AND code='1'`).Scan(&fallbackSeriesID)
|
||||||
|
|
||||||
saved := 0
|
saved := 0
|
||||||
|
skipped := 0
|
||||||
for _, item := range req.Items {
|
for _, item := range req.Items {
|
||||||
code := strings.TrimSpace(item.ProductCode)
|
code := strings.TrimSpace(item.ProductCode)
|
||||||
color := strings.TrimSpace(item.ColorCode)
|
color := strings.TrimSpace(item.ColorCode)
|
||||||
dim3Token := strings.TrimSpace(item.Dim3Code)
|
dim3Token := strings.TrimSpace(item.Dim3Code)
|
||||||
if code == "" || color == "" {
|
if code == "" || color == "" {
|
||||||
|
skipped++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mmitemID, err := resolveMmitemIDTx(ctx, tx, code)
|
mmitemID, err := resolveMmitemIDTx(ctx, tx, code)
|
||||||
if err != nil || mmitemID <= 0 {
|
if err != nil || mmitemID <= 0 {
|
||||||
http.Error(w, "PG urun bulunamadi: "+code, http.StatusBadRequest)
|
http.Error(w, "B2B urun bulunamadi: "+code, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dim1ID, err := resolveDimTokenIDTx(ctx, tx, "dimval1", color)
|
// Authoritative dim1 resolver: only allow saving against a color that exists for this product in mmitem_dim.
|
||||||
if err != nil || dim1ID <= 0 {
|
var dim1ID int64
|
||||||
http.Error(w, "Renk token eslesmesi bulunamadi: "+color, http.StatusBadRequest)
|
{
|
||||||
return
|
tok := strings.ToUpper(strings.TrimSpace(color))
|
||||||
|
if tok == "" || tok == "0" {
|
||||||
|
http.Error(w, "Renk token eslesmesi bulunamadi: "+color, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := tx.QueryRowContext(ctx, `
|
||||||
|
SELECT md.val1
|
||||||
|
FROM mmitem_dim md
|
||||||
|
JOIN dfgrp d ON d.id = md.val1
|
||||||
|
WHERE md.mmitem_id = $1
|
||||||
|
AND md.is_active = TRUE
|
||||||
|
AND UPPER(BTRIM(d.code)) = $2
|
||||||
|
GROUP BY md.val1
|
||||||
|
LIMIT 1
|
||||||
|
`, mmitemID, tok).Scan(&dim1ID); err != nil || dim1ID <= 0 {
|
||||||
|
http.Error(w, "Renk bu urunde yok: "+color, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var dim3ID sql.NullInt64
|
var dim3ID sql.NullInt64
|
||||||
if dim3Token != "" {
|
if dim3Token != "" {
|
||||||
@@ -477,6 +932,39 @@ func PostProductSeriesMappingsSaveHandler(pg *sql.DB) http.HandlerFunc {
|
|||||||
dim3ID = sql.NullInt64{Int64: id, Valid: true}
|
dim3ID = sql.NullInt64{Int64: id, Valid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalize/validate series ids.
|
||||||
|
seen := map[int64]struct{}{}
|
||||||
|
seriesIDs := make([]int64, 0, len(item.SeriesIDs))
|
||||||
|
for _, seriesID := range item.SeriesIDs {
|
||||||
|
if seriesID <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fallbackSeriesID > 0 && seriesID == fallbackSeriesID && !allowFallback {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := seen[seriesID]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[seriesID] = struct{}{}
|
||||||
|
seriesIDs = append(seriesIDs, seriesID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent accidental destructive writes from UI.
|
||||||
|
if len(seriesIDs) == 0 && !allowClear {
|
||||||
|
log.Printf("[ProductSeriesMappingsSave] skip_empty_series_ids product=%s color=%s dim3=%s", code, color, dim3Token)
|
||||||
|
skipped++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
out := make([]string, 0, len(seriesIDs))
|
||||||
|
for _, id := range seriesIDs {
|
||||||
|
out = append(out, fmt.Sprintf("%d", id))
|
||||||
|
}
|
||||||
|
log.Printf("[ProductSeriesMappingsSave] write product=%s color=%s dim3=%s series_ids=%s",
|
||||||
|
code, color, dim3Token, strings.Join(out, ","))
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := tx.ExecContext(ctx, `
|
if _, err := tx.ExecContext(ctx, `
|
||||||
DELETE FROM zbggseri
|
DELETE FROM zbggseri
|
||||||
WHERE mmitem_id=$1
|
WHERE mmitem_id=$1
|
||||||
@@ -486,15 +974,7 @@ WHERE mmitem_id=$1
|
|||||||
http.Error(w, "Seri eslesmesi temizlenemedi: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Seri eslesmesi temizlenemedi: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
seen := map[int64]struct{}{}
|
for _, seriesID := range seriesIDs {
|
||||||
for _, seriesID := range item.SeriesIDs {
|
|
||||||
if seriesID <= 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := seen[seriesID]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[seriesID] = struct{}{}
|
|
||||||
if _, err := tx.ExecContext(ctx, `
|
if _, err := tx.ExecContext(ctx, `
|
||||||
INSERT INTO zbggseri (mmitem_id, dim1, seri_id, dim3)
|
INSERT INTO zbggseri (mmitem_id, dim1, seri_id, dim3)
|
||||||
VALUES ($1, $2, $3, $4)
|
VALUES ($1, $2, $3, $4)
|
||||||
@@ -510,7 +990,7 @@ VALUES ($1, $2, $3, $4)
|
|||||||
http.Error(w, "Seri eslesmeleri kaydedilemedi: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Seri eslesmeleri kaydedilemedi: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeJSON(w, map[string]any{"saved": saved})
|
writeJSON(w, map[string]any{"saved": saved, "skipped": skipped})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -711,3 +1191,14 @@ func writeJSON(w http.ResponseWriter, payload any) {
|
|||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
_ = json.NewEncoder(w).Encode(payload)
|
_ = json.NewEncoder(w).Encode(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isPostgresUniqueViolation(err error, constraint string) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
pqErr, ok := err.(*pq.Error)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return string(pqErr.Code) == "23505" && (constraint == "" || pqErr.Constraint == constraint)
|
||||||
|
}
|
||||||
|
|||||||
@@ -158,6 +158,26 @@ func GetProductionNoCostProductsHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if argeDB := db.GetArgeBaggiDB(); argeDB != nil && len(list) > 0 {
|
||||||
|
workOrders := make([]string, 0, len(list))
|
||||||
|
for i := range list {
|
||||||
|
workOrders = append(workOrders, list[i].UrtSiparisNo)
|
||||||
|
}
|
||||||
|
argeCtx, cancelArge := context.WithTimeout(r.Context(), 15*time.Second)
|
||||||
|
totalsByWorkOrder, argeErr := queries.GetArgeBaggiDepotEntryTotals(argeCtx, argeDB, workOrders)
|
||||||
|
cancelArge()
|
||||||
|
if argeErr != nil {
|
||||||
|
log.Printf("[ProductionNoCost] ArgeBAGGI enrichment error: %v", argeErr)
|
||||||
|
} else {
|
||||||
|
for i := range list {
|
||||||
|
totals := totalsByWorkOrder[strings.TrimSpace(list[i].UrtSiparisNo)]
|
||||||
|
list[i].CeketDepoGiris = totals.Ceket
|
||||||
|
list[i].PantolonDepoGiris = totals.Pantolon
|
||||||
|
list[i].YelekDepoGiris = totals.Yelek
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ = json.NewEncoder(w).Encode(list)
|
_ = json.NewEncoder(w).Encode(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,6 +211,7 @@ func GetProductionHasCostProductsHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
var item models.ProductionHasCostProductRow
|
var item models.ProductionHasCostProductRow
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&item.UretimSekli,
|
&item.UretimSekli,
|
||||||
|
&item.UrtSiparisNo,
|
||||||
&item.NOnMLNo,
|
&item.NOnMLNo,
|
||||||
&item.UrunKodu,
|
&item.UrunKodu,
|
||||||
&item.UrunAdi,
|
&item.UrunAdi,
|
||||||
@@ -219,6 +240,26 @@ func GetProductionHasCostProductsHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if argeDB := db.GetArgeBaggiDB(); argeDB != nil && len(list) > 0 {
|
||||||
|
workOrders := make([]string, 0, len(list))
|
||||||
|
for i := range list {
|
||||||
|
workOrders = append(workOrders, list[i].UrtSiparisNo)
|
||||||
|
}
|
||||||
|
argeCtx, cancelArge := context.WithTimeout(r.Context(), 15*time.Second)
|
||||||
|
totalsByWorkOrder, argeErr := queries.GetArgeBaggiDepotEntryTotals(argeCtx, argeDB, workOrders)
|
||||||
|
cancelArge()
|
||||||
|
if argeErr != nil {
|
||||||
|
log.Printf("[ProductionHasCost] ArgeBAGGI enrichment error: %v", argeErr)
|
||||||
|
} else {
|
||||||
|
for i := range list {
|
||||||
|
totals := totalsByWorkOrder[strings.TrimSpace(list[i].UrtSiparisNo)]
|
||||||
|
list[i].CeketDepoGiris = totals.Ceket
|
||||||
|
list[i].PantolonDepoGiris = totals.Pantolon
|
||||||
|
list[i].YelekDepoGiris = totals.Yelek
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ = json.NewEncoder(w).Encode(list)
|
_ = json.NewEncoder(w).Encode(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1427,6 +1468,12 @@ func postProductionProductCostingOnMLSaveHandler(w http.ResponseWriter, r *http.
|
|||||||
totalUSD := 0.0
|
totalUSD := 0.0
|
||||||
totalEUR := 0.0
|
totalEUR := 0.0
|
||||||
for _, r := range req.Detail.Upserts {
|
for _, r := range req.Detail.Upserts {
|
||||||
|
// The top header in the detail screen only includes rows marked
|
||||||
|
// "Maliyete Dahil". Persist that exact business total to the master
|
||||||
|
// record as well; excluded detail rows must not inflate the cost.
|
||||||
|
if r.MaliyeteDahil != 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
qty := r.LMiktar
|
qty := r.LMiktar
|
||||||
if qty < 0 {
|
if qty < 0 {
|
||||||
qty = 0
|
qty = 0
|
||||||
@@ -1494,7 +1541,7 @@ func postProductionProductCostingOnMLSaveHandler(w http.ResponseWriter, r *http.
|
|||||||
isUpdate := false
|
isUpdate := false
|
||||||
{
|
{
|
||||||
var flag int
|
var flag int
|
||||||
_ = tx.QueryRowContext(ctx, `SELECT CASE WHEN EXISTS (SELECT 1 FROM dbo.spUrtOnMLMas WITH (NOLOCK) WHERE nOnMLNo=@p1) THEN 1 ELSE 0 END`, nOnMLNo).Scan(&flag)
|
_ = tx.QueryRowContext(ctx, `SELECT CASE WHEN EXISTS (SELECT 1 FROM dbo.spUrtOnMLMas WHERE nOnMLNo=@p1) THEN 1 ELSE 0 END`, nOnMLNo).Scan(&flag)
|
||||||
isUpdate = flag == 1
|
isUpdate = flag == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1644,7 +1691,7 @@ SELECT
|
|||||||
C.code,
|
C.code,
|
||||||
ISNULL((
|
ISNULL((
|
||||||
SELECT TOP 1 ISNULL(CONVERT(int, ISNULL(S.nStokTipi, 0)), 0) AS nStokTipiID
|
SELECT TOP 1 ISNULL(CONVERT(int, ISNULL(S.nStokTipi, 0)), 0) AS nStokTipiID
|
||||||
FROM dbo.tbStok S WITH (NOLOCK)
|
FROM dbo.tbStok S
|
||||||
WHERE ISNULL(S.IsBlocked, 0) = 0
|
WHERE ISNULL(S.IsBlocked, 0) = 0
|
||||||
AND (
|
AND (
|
||||||
REPLACE(LTRIM(RTRIM(ISNULL(S.sKodu,''))), ' ', '') = REPLACE(C.code, ' ', '')
|
REPLACE(LTRIM(RTRIM(ISNULL(S.sKodu,''))), ' ', '') = REPLACE(C.code, ' ', '')
|
||||||
@@ -1739,7 +1786,7 @@ FROM C
|
|||||||
var mtID int
|
var mtID int
|
||||||
err := tx.QueryRowContext(ctx, `
|
err := tx.QueryRowContext(ctx, `
|
||||||
SELECT TOP 1 ISNULL(MTnUrtMTBolumID, 0) AS MTnUrtMTBolumID
|
SELECT TOP 1 ISNULL(MTnUrtMTBolumID, 0) AS MTnUrtMTBolumID
|
||||||
FROM dbo.spUrtOnMLHammaddeTuru WITH (NOLOCK)
|
FROM dbo.spUrtOnMLHammaddeTuru
|
||||||
WHERE nHammaddeTuruNo = @p1
|
WHERE nHammaddeTuruNo = @p1
|
||||||
`, row.NHammaddeTuruNo).Scan(&mtID)
|
`, row.NHammaddeTuruNo).Scan(&mtID)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
@@ -1834,7 +1881,7 @@ WHERE nHammaddeTuruNo = @p1
|
|||||||
perRowCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
perRowCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||||
err := lookupDB.QueryRowContext(perRowCtx, `
|
err := lookupDB.QueryRowContext(perRowCtx, `
|
||||||
SELECT TOP 1 ISNULL(CONVERT(int, ISNULL(S.nStokTipi, 0)), 0) AS nStokTipiID
|
SELECT TOP 1 ISNULL(CONVERT(int, ISNULL(S.nStokTipi, 0)), 0) AS nStokTipiID
|
||||||
FROM dbo.tbStok S WITH (NOLOCK)
|
FROM dbo.tbStok S
|
||||||
WHERE ISNULL(S.IsBlocked, 0) = 0
|
WHERE ISNULL(S.IsBlocked, 0) = 0
|
||||||
AND (
|
AND (
|
||||||
REPLACE(LTRIM(RTRIM(ISNULL(S.sKodu,''))), ' ', '') = REPLACE(@p1, ' ', '')
|
REPLACE(LTRIM(RTRIM(ISNULL(S.sKodu,''))), ' ', '') = REPLACE(@p1, ' ', '')
|
||||||
@@ -2009,7 +2056,7 @@ WHEN NOT MATCHED THEN
|
|||||||
nUrtUBolumID := 13
|
nUrtUBolumID := 13
|
||||||
_ = tx.QueryRowContext(ctx, `
|
_ = tx.QueryRowContext(ctx, `
|
||||||
SELECT TOP 1 ISNULL(CONVERT(int, nUrtUBolumID), 0) AS nUrtUBolumID
|
SELECT TOP 1 ISNULL(CONVERT(int, nUrtUBolumID), 0) AS nUrtUBolumID
|
||||||
FROM dbo.spUrtRecMBolum WITH (NOLOCK)
|
FROM dbo.spUrtRecMBolum
|
||||||
WHERE nUrtReceteID = @p1
|
WHERE nUrtReceteID = @p1
|
||||||
ORDER BY nUrtRecMBolumID ASC
|
ORDER BY nUrtRecMBolumID ASC
|
||||||
`, req.Header.NUrtReceteID).Scan(&nUrtUBolumID)
|
`, req.Header.NUrtReceteID).Scan(&nUrtUBolumID)
|
||||||
@@ -2024,7 +2071,7 @@ SELECT
|
|||||||
ISNULL(CONVERT(int, nUrtMBolumID), 0) AS nUrtMBolumID,
|
ISNULL(CONVERT(int, nUrtMBolumID), 0) AS nUrtMBolumID,
|
||||||
LTRIM(RTRIM(ISNULL(nHStokID_G,''))) AS nHStokID_G,
|
LTRIM(RTRIM(ISNULL(nHStokID_G,''))) AS nHStokID_G,
|
||||||
ISNULL(CONVERT(float, lHMiktar_G), 0) AS lHMiktar_G
|
ISNULL(CONVERT(float, lHMiktar_G), 0) AS lHMiktar_G
|
||||||
FROM dbo.spUrtRecMBolum WITH (NOLOCK)
|
FROM dbo.spUrtRecMBolum
|
||||||
WHERE nUrtReceteID = @p1
|
WHERE nUrtReceteID = @p1
|
||||||
`, req.Header.NUrtReceteID); err == nil {
|
`, req.Header.NUrtReceteID); err == nil {
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
@@ -2090,7 +2137,7 @@ FROM dbo.spUrtRecMBolum WITH (UPDLOCK, HOLDLOCK)
|
|||||||
// FK guard: only insert if nUrtMBolumID exists in spUrtMBolum.
|
// FK guard: only insert if nUrtMBolumID exists in spUrtMBolum.
|
||||||
var bolumExists int
|
var bolumExists int
|
||||||
if err := tx.QueryRowContext(ctx, `
|
if err := tx.QueryRowContext(ctx, `
|
||||||
SELECT CASE WHEN EXISTS (SELECT 1 FROM dbo.spUrtMBolum WITH (NOLOCK) WHERE nUrtMBolumID = @p1) THEN 1 ELSE 0 END
|
SELECT CASE WHEN EXISTS (SELECT 1 FROM dbo.spUrtMBolum WHERE nUrtMBolumID = @p1) THEN 1 ELSE 0 END
|
||||||
`, k.nUrtMBolumID).Scan(&bolumExists); err != nil || bolumExists != 1 {
|
`, k.nUrtMBolumID).Scan(&bolumExists); err != nil || bolumExists != 1 {
|
||||||
logger.Error("recipe insert blocked (missing spUrtMBolum FK)", "trace_id", traceID, "n_urt_m_bolum_id", k.nUrtMBolumID, "s_kodu", k.sKodu, "err", err)
|
logger.Error("recipe insert blocked (missing spUrtMBolum FK)", "trace_id", traceID, "n_urt_m_bolum_id", k.nUrtMBolumID, "s_kodu", k.sKodu, "err", err)
|
||||||
http.Error(w, "Recete insert engellendi (bolum FK yok)", http.StatusBadRequest)
|
http.Error(w, "Recete insert engellendi (bolum FK yok)", http.StatusBadRequest)
|
||||||
@@ -2468,7 +2515,7 @@ func PostProductionProductCostingOnMLDeleteHandler(w http.ResponseWriter, r *htt
|
|||||||
SELECT TOP 1
|
SELECT TOP 1
|
||||||
LTRIM(RTRIM(ISNULL(UrunKodu,''))) AS UrunKodu,
|
LTRIM(RTRIM(ISNULL(UrunKodu,''))) AS UrunKodu,
|
||||||
COALESCE(Tarihi, dteKayitTarihi, GETDATE()) AS Tarihi
|
COALESCE(Tarihi, dteKayitTarihi, GETDATE()) AS Tarihi
|
||||||
FROM dbo.spUrtOnMLMas WITH (NOLOCK)
|
FROM dbo.spUrtOnMLMas
|
||||||
WHERE nOnMLNo = @p1
|
WHERE nOnMLNo = @p1
|
||||||
`, req.NOnMLNo).Scan(&urunKodu, &maliyetTarihi)
|
`, req.NOnMLNo).Scan(&urunKodu, &maliyetTarihi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ func sendCostingSummaryMail(
|
|||||||
WITH X AS (SELECT CONVERT(int, V.id) AS id FROM (VALUES %s) AS V(id))
|
WITH X AS (SELECT CONVERT(int, V.id) AS id FROM (VALUES %s) AS V(id))
|
||||||
SELECT X.id, LTRIM(RTRIM(ISNULL(M.sAdi,''))) AS title
|
SELECT X.id, LTRIM(RTRIM(ISNULL(M.sAdi,''))) AS title
|
||||||
FROM X
|
FROM X
|
||||||
LEFT JOIN dbo.spUrtMTBolum M WITH (NOLOCK)
|
LEFT JOIN dbo.spUrtMTBolum M
|
||||||
ON M.nUrtMTBolumID = X.id
|
ON M.nUrtMTBolumID = X.id
|
||||||
`, strings.Join(vals, ","))
|
`, strings.Join(vals, ","))
|
||||||
rows, err := uretim.QueryContext(ctx, q, args...)
|
rows, err := uretim.QueryContext(ctx, q, args...)
|
||||||
|
|||||||
+122
-4
@@ -134,6 +134,12 @@ type TranslateSelectedPayload struct {
|
|||||||
Limit int `json:"limit"`
|
Limit int `json:"limit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RuntimeTranslationsResponse struct {
|
||||||
|
Lang string `json:"lang"`
|
||||||
|
ByKey map[string]string `json:"by_key"`
|
||||||
|
ByText map[string]string `json:"by_text"`
|
||||||
|
}
|
||||||
|
|
||||||
type BulkUpdateItem struct {
|
type BulkUpdateItem struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
SourceTextTR *string `json:"source_text_tr"`
|
SourceTextTR *string `json:"source_text_tr"`
|
||||||
@@ -296,6 +302,92 @@ ORDER BY t_key, lang_code
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetRuntimeTranslationsHandler(db *sql.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
|
||||||
|
lang := normalizeRuntimeTranslationLang(firstNonEmpty(
|
||||||
|
r.URL.Query().Get("lang"),
|
||||||
|
r.Header.Get("Accept-Language"),
|
||||||
|
))
|
||||||
|
if lang == "" {
|
||||||
|
lang = "tr"
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := RuntimeTranslationsResponse{
|
||||||
|
Lang: lang,
|
||||||
|
ByKey: map[string]string{},
|
||||||
|
ByText: map[string]string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := db.Query(`
|
||||||
|
WITH base AS (
|
||||||
|
SELECT DISTINCT ON (t_key)
|
||||||
|
t_key,
|
||||||
|
COALESCE(NULLIF(source_text_tr, ''), translated_text, '') AS source_text_tr
|
||||||
|
FROM mk_translator
|
||||||
|
WHERE COALESCE(NULLIF(source_text_tr, ''), translated_text, '') <> ''
|
||||||
|
AND status = 'approved'
|
||||||
|
AND (
|
||||||
|
COALESCE(NULLIF(provider_meta->>'source_type', ''), 'dummy') <> 'dummy'
|
||||||
|
OR is_manual = true
|
||||||
|
)
|
||||||
|
AND char_length(COALESCE(NULLIF(source_text_tr, ''), translated_text, '')) <= 180
|
||||||
|
AND COALESCE(NULLIF(source_text_tr, ''), translated_text, '') !~ '[<>{}\[\]]'
|
||||||
|
AND position(chr(96) in COALESCE(NULLIF(source_text_tr, ''), translated_text, '')) = 0
|
||||||
|
AND COALESCE(NULLIF(source_text_tr, ''), translated_text, '') !~ '(=>|\|\||&&|\?\?|/\*|\*/|\.trim\(|\.replace\(|\.map\(|\.filter\()'
|
||||||
|
ORDER BY t_key, CASE WHEN lang_code='tr' THEN 0 ELSE 1 END, updated_at DESC
|
||||||
|
),
|
||||||
|
target AS (
|
||||||
|
SELECT DISTINCT ON (t_key)
|
||||||
|
t_key,
|
||||||
|
COALESCE(translated_text, '') AS translated_text
|
||||||
|
FROM mk_translator
|
||||||
|
WHERE lang_code=$1
|
||||||
|
AND (status = 'approved' OR is_manual = true)
|
||||||
|
ORDER BY t_key, is_manual DESC, updated_at DESC
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
base.t_key,
|
||||||
|
base.source_text_tr,
|
||||||
|
CASE
|
||||||
|
WHEN $1 = 'tr' THEN base.source_text_tr
|
||||||
|
ELSE COALESCE(NULLIF(target.translated_text, ''), base.source_text_tr)
|
||||||
|
END AS display_text
|
||||||
|
FROM base
|
||||||
|
LEFT JOIN target ON target.t_key = base.t_key
|
||||||
|
`, lang)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "runtime translations query error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var key, sourceText, displayText string
|
||||||
|
if err := rows.Scan(&key, &sourceText, &displayText); err != nil {
|
||||||
|
http.Error(w, "runtime translations scan error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
key = strings.TrimSpace(key)
|
||||||
|
sourceText = normalizeRuntimeTranslationText(sourceText)
|
||||||
|
displayText = normalizeRuntimeTranslationText(displayText)
|
||||||
|
if key != "" && displayText != "" {
|
||||||
|
resp.ByKey[key] = displayText
|
||||||
|
}
|
||||||
|
if sourceText != "" && displayText != "" {
|
||||||
|
resp.ByText[sourceText] = displayText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
http.Error(w, "runtime translations rows error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = json.NewEncoder(w).Encode(resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func UpdateTranslationRowHandler(db *sql.DB) http.HandlerFunc {
|
func UpdateTranslationRowHandler(db *sql.DB) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
@@ -911,10 +1003,12 @@ func collectSourceSeeds(pgDB *sql.DB, mssqlDB *sql.DB, limit int) []sourceSeed {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, row := range collectDummySeeds(limit - len(out)) {
|
if strings.EqualFold(strings.TrimSpace(os.Getenv("TRANSLATION_DUMMY_SEEDS_ENABLED")), "true") {
|
||||||
appendSeed(row)
|
for _, row := range collectDummySeeds(limit - len(out)) {
|
||||||
if len(out) >= limit {
|
appendSeed(row)
|
||||||
return out
|
if len(out) >= limit {
|
||||||
|
return out
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1667,6 +1761,30 @@ func normalizeTranslationLang(v string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeRuntimeTranslationLang(v string) string {
|
||||||
|
raw := strings.ToLower(strings.TrimSpace(v))
|
||||||
|
if raw == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if strings.Contains(raw, ",") {
|
||||||
|
raw = strings.TrimSpace(strings.Split(raw, ",")[0])
|
||||||
|
}
|
||||||
|
if strings.Contains(raw, ";") {
|
||||||
|
raw = strings.TrimSpace(strings.Split(raw, ";")[0])
|
||||||
|
}
|
||||||
|
if strings.Contains(raw, "-") {
|
||||||
|
raw = strings.TrimSpace(strings.Split(raw, "-")[0])
|
||||||
|
}
|
||||||
|
if strings.Contains(raw, "_") {
|
||||||
|
raw = strings.TrimSpace(strings.Split(raw, "_")[0])
|
||||||
|
}
|
||||||
|
return normalizeTranslationLang(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeRuntimeTranslationText(v string) string {
|
||||||
|
return strings.Join(strings.Fields(strings.TrimSpace(v)), " ")
|
||||||
|
}
|
||||||
|
|
||||||
func normalizeTranslationStatus(v string) string {
|
func normalizeTranslationStatus(v string) string {
|
||||||
status := strings.ToLower(strings.TrimSpace(v))
|
status := strings.ToLower(strings.TrimSpace(v))
|
||||||
if _, ok := translationStatusSet[status]; ok {
|
if _, ok := translationStatusSet[status]; ok {
|
||||||
|
|||||||
+15
-1
@@ -1,8 +1,20 @@
|
|||||||
// quasar.config.js
|
// quasar.config.js
|
||||||
import { defineConfig } from '#q-app/wrappers'
|
import { defineConfig } from '#q-app/wrappers'
|
||||||
|
import { execSync } from 'node:child_process'
|
||||||
|
|
||||||
|
function readGitBuildInfo () {
|
||||||
|
try {
|
||||||
|
const commitCount = execSync('git rev-list --count HEAD', { encoding: 'utf8' }).trim()
|
||||||
|
const commitHash = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim()
|
||||||
|
return { version: `v1.0.${commitCount}`, commit: commitHash }
|
||||||
|
} catch {
|
||||||
|
return { version: 'v1.0.0', commit: 'local' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default defineConfig(() => {
|
export default defineConfig(() => {
|
||||||
const apiBaseUrl = (process.env.VITE_API_BASE_URL || '/api').trim()
|
const apiBaseUrl = (process.env.VITE_API_BASE_URL || '/api').trim()
|
||||||
|
const gitBuildInfo = readGitBuildInfo()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
||||||
@@ -36,7 +48,9 @@ export default defineConfig(() => {
|
|||||||
build: {
|
build: {
|
||||||
vueRouterMode: 'hash',
|
vueRouterMode: 'hash',
|
||||||
env: {
|
env: {
|
||||||
VITE_API_BASE_URL: apiBaseUrl
|
VITE_API_BASE_URL: apiBaseUrl,
|
||||||
|
APP_VERSION: gitBuildInfo.version,
|
||||||
|
APP_COMMIT: gitBuildInfo.commit
|
||||||
},
|
},
|
||||||
|
|
||||||
esbuildTarget: {
|
esbuildTarget: {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { boot } from 'quasar/wrappers'
|
import { boot } from 'quasar/wrappers'
|
||||||
import { useLocaleStore } from 'src/stores/localeStore'
|
import { useLocaleStore } from 'src/stores/localeStore'
|
||||||
|
import { useRuntimeTranslationStore } from 'src/stores/runtimeTranslationStore'
|
||||||
|
|
||||||
export default boot(() => {
|
export default boot(async () => {
|
||||||
const localeStore = useLocaleStore()
|
const localeStore = useLocaleStore()
|
||||||
localeStore.setLocale(localeStore.locale)
|
localeStore.setLocale(localeStore.locale)
|
||||||
|
const runtimeTranslations = useRuntimeTranslationStore()
|
||||||
|
await runtimeTranslations.loadLocale(localeStore.locale)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { computed } from 'vue'
|
|||||||
import { messages } from 'src/i18n/messages'
|
import { messages } from 'src/i18n/messages'
|
||||||
import { DEFAULT_LOCALE } from 'src/i18n/languages'
|
import { DEFAULT_LOCALE } from 'src/i18n/languages'
|
||||||
import { useLocaleStore } from 'src/stores/localeStore'
|
import { useLocaleStore } from 'src/stores/localeStore'
|
||||||
|
import { useRuntimeTranslationStore } from 'src/stores/runtimeTranslationStore'
|
||||||
|
|
||||||
function lookup(obj, path) {
|
function lookup(obj, path) {
|
||||||
return String(path || '')
|
return String(path || '')
|
||||||
@@ -13,6 +14,7 @@ function lookup(obj, path) {
|
|||||||
|
|
||||||
export function useI18n() {
|
export function useI18n() {
|
||||||
const localeStore = useLocaleStore()
|
const localeStore = useLocaleStore()
|
||||||
|
const runtimeTranslations = useRuntimeTranslationStore()
|
||||||
|
|
||||||
const currentLocale = computed(() => localeStore.locale)
|
const currentLocale = computed(() => localeStore.locale)
|
||||||
|
|
||||||
@@ -24,6 +26,11 @@ export function useI18n() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function t(key) {
|
function t(key) {
|
||||||
|
const runtimeValue = runtimeTranslations.translateKey(key)
|
||||||
|
if (runtimeValue && runtimeValue !== key) {
|
||||||
|
return runtimeValue
|
||||||
|
}
|
||||||
|
|
||||||
for (const locale of fallbackLocales(currentLocale.value)) {
|
for (const locale of fallbackLocales(currentLocale.value)) {
|
||||||
const val = lookup(messages[locale] || {}, key)
|
const val = lookup(messages[locale] || {}, key)
|
||||||
if (val != null) return val
|
if (val != null) return val
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import { nextTick, onBeforeUnmount, onMounted, watch } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
|
import { DEFAULT_LOCALE } from 'src/i18n/languages'
|
||||||
|
import { useLocaleStore } from 'src/stores/localeStore'
|
||||||
|
import { useRuntimeTranslationStore } from 'src/stores/runtimeTranslationStore'
|
||||||
|
|
||||||
|
const textOriginals = new WeakMap()
|
||||||
|
const attrOriginals = new WeakMap()
|
||||||
|
const TRANSLATABLE_ATTRS = ['placeholder', 'title', 'aria-label']
|
||||||
|
const SKIP_TAGS = new Set(['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA'])
|
||||||
|
|
||||||
|
function normalizeRuntimeText (value) {
|
||||||
|
return String(value || '').trim().replace(/\s+/g, ' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
function preserveOuterWhitespace (original, translated) {
|
||||||
|
const text = String(original || '')
|
||||||
|
const leading = text.match(/^\s*/)?.[0] || ''
|
||||||
|
const trailing = text.match(/\s*$/)?.[0] || ''
|
||||||
|
return `${leading}${translated}${trailing}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldSkipNode (node) {
|
||||||
|
const parent = node?.parentElement
|
||||||
|
if (!parent) return true
|
||||||
|
if (SKIP_TAGS.has(parent.tagName)) return true
|
||||||
|
if (parent.closest?.('[data-no-runtime-i18n="true"]')) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRuntimeDomTranslations () {
|
||||||
|
const route = useRoute()
|
||||||
|
const localeStore = useLocaleStore()
|
||||||
|
const runtimeTranslations = useRuntimeTranslationStore()
|
||||||
|
let observer = null
|
||||||
|
let applyTimer = null
|
||||||
|
|
||||||
|
function translateTextNode (node) {
|
||||||
|
if (shouldSkipNode(node)) return
|
||||||
|
const current = String(node.nodeValue || '')
|
||||||
|
if (!normalizeRuntimeText(current)) return
|
||||||
|
|
||||||
|
if (!textOriginals.has(node)) {
|
||||||
|
textOriginals.set(node, current)
|
||||||
|
}
|
||||||
|
const original = textOriginals.get(node)
|
||||||
|
const key = normalizeRuntimeText(original)
|
||||||
|
const translated = localeStore.locale === DEFAULT_LOCALE
|
||||||
|
? key
|
||||||
|
: runtimeTranslations.byText[key]
|
||||||
|
|
||||||
|
const nextValue = preserveOuterWhitespace(original, translated || key)
|
||||||
|
if (node.nodeValue !== nextValue) {
|
||||||
|
node.nodeValue = nextValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function translateElementAttrs (el) {
|
||||||
|
if (!el || el.nodeType !== Node.ELEMENT_NODE) return
|
||||||
|
if (el.closest?.('[data-no-runtime-i18n="true"]')) return
|
||||||
|
let originals = attrOriginals.get(el)
|
||||||
|
if (!originals) {
|
||||||
|
originals = {}
|
||||||
|
attrOriginals.set(el, originals)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const attr of TRANSLATABLE_ATTRS) {
|
||||||
|
if (!el.hasAttribute(attr)) continue
|
||||||
|
const current = el.getAttribute(attr)
|
||||||
|
if (!normalizeRuntimeText(current)) continue
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(originals, attr)) {
|
||||||
|
originals[attr] = current
|
||||||
|
}
|
||||||
|
const key = normalizeRuntimeText(originals[attr])
|
||||||
|
const translated = localeStore.locale === DEFAULT_LOCALE
|
||||||
|
? key
|
||||||
|
: runtimeTranslations.byText[key]
|
||||||
|
const nextValue = translated || key
|
||||||
|
if (el.getAttribute(attr) !== nextValue) {
|
||||||
|
el.setAttribute(attr, nextValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function translateRoot (root = document.body) {
|
||||||
|
if (typeof document === 'undefined' || !root) return
|
||||||
|
|
||||||
|
const walker = document.createTreeWalker(
|
||||||
|
root,
|
||||||
|
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
|
||||||
|
{
|
||||||
|
acceptNode (node) {
|
||||||
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
|
return normalizeRuntimeText(node.nodeValue) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
|
||||||
|
}
|
||||||
|
return NodeFilter.FILTER_ACCEPT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
let node = walker.currentNode
|
||||||
|
while (node) {
|
||||||
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
|
translateTextNode(node)
|
||||||
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
||||||
|
translateElementAttrs(node)
|
||||||
|
}
|
||||||
|
node = walker.nextNode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleApply () {
|
||||||
|
if (applyTimer) clearTimeout(applyTimer)
|
||||||
|
applyTimer = setTimeout(async () => {
|
||||||
|
applyTimer = null
|
||||||
|
await nextTick()
|
||||||
|
translateRoot(document.body)
|
||||||
|
}, 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await runtimeTranslations.loadLocale(localeStore.locale)
|
||||||
|
scheduleApply()
|
||||||
|
observer = new MutationObserver(() => scheduleApply())
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
characterData: true,
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: TRANSLATABLE_ATTRS
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => localeStore.locale,
|
||||||
|
async (locale) => {
|
||||||
|
await runtimeTranslations.loadLocale(locale)
|
||||||
|
scheduleApply()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => route.fullPath,
|
||||||
|
() => scheduleApply()
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => runtimeTranslations.version,
|
||||||
|
() => scheduleApply()
|
||||||
|
)
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (observer) observer.disconnect()
|
||||||
|
if (applyTimer) clearTimeout(applyTimer)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -171,6 +171,7 @@ import { Dialog, useQuasar } from 'quasar'
|
|||||||
import { useAuthStore } from 'stores/authStore'
|
import { useAuthStore } from 'stores/authStore'
|
||||||
import { usePermissionStore } from 'stores/permissionStore'
|
import { usePermissionStore } from 'stores/permissionStore'
|
||||||
import { useI18n } from 'src/composables/useI18n'
|
import { useI18n } from 'src/composables/useI18n'
|
||||||
|
import { useRuntimeDomTranslations } from 'src/composables/useRuntimeDomTranslations'
|
||||||
import { UI_LANGUAGE_OPTIONS } from 'src/i18n/languages'
|
import { UI_LANGUAGE_OPTIONS } from 'src/i18n/languages'
|
||||||
import { useLocaleStore } from 'src/stores/localeStore'
|
import { useLocaleStore } from 'src/stores/localeStore'
|
||||||
import { activityLogsMenuItem, getAuthUserId } from 'src/modules/activityLogs'
|
import { activityLogsMenuItem, getAuthUserId } from 'src/modules/activityLogs'
|
||||||
@@ -185,6 +186,7 @@ const auth = useAuthStore()
|
|||||||
const perm = usePermissionStore()
|
const perm = usePermissionStore()
|
||||||
const localeStore = useLocaleStore()
|
const localeStore = useLocaleStore()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
useRuntimeDomTranslations()
|
||||||
|
|
||||||
const languageOptions = UI_LANGUAGE_OPTIONS
|
const languageOptions = UI_LANGUAGE_OPTIONS
|
||||||
const selectedLocale = computed({
|
const selectedLocale = computed({
|
||||||
@@ -354,7 +356,7 @@ const menuItems = [
|
|||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
label: 'Ürün Fiyatlandırma',
|
label: 'Ürün Fiyatlandırma ve Analiz',
|
||||||
icon: 'sell',
|
icon: 'sell',
|
||||||
|
|
||||||
children: [
|
children: [
|
||||||
@@ -363,6 +365,11 @@ const menuItems = [
|
|||||||
to: '/app/pricing/product-pricing',
|
to: '/app/pricing/product-pricing',
|
||||||
permission: 'pricing:view'
|
permission: 'pricing:view'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Ürün Performans ve Karlılık Analizi',
|
||||||
|
to: '/app/pricing/product-performance-profitability',
|
||||||
|
permission: 'pricing:view'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Toptan Kampanya Yönetimi',
|
label: 'Toptan Kampanya Yönetimi',
|
||||||
to: '/app/pricing/wholesale-campaigns',
|
to: '/app/pricing/wholesale-campaigns',
|
||||||
@@ -373,6 +380,11 @@ const menuItems = [
|
|||||||
to: '/app/pricing/product-series-mappings',
|
to: '/app/pricing/product-series-mappings',
|
||||||
permission: 'pricing:view'
|
permission: 'pricing:view'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'B2B Olmayan Stok',
|
||||||
|
to: '/app/pricing/product-series-mappings-orphans',
|
||||||
|
permission: 'pricing:view'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Ürün Seri Tanımlamaları',
|
label: 'Ürün Seri Tanımlamaları',
|
||||||
to: '/app/pricing/product-series-definitions',
|
to: '/app/pricing/product-series-definitions',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<div class="col-12 col-md">
|
<div class="col-12 col-md">
|
||||||
<div class="text-h6">Marka Sınıflandırma</div>
|
<div class="text-h6">Marka Sınıflandırma</div>
|
||||||
<div class="text-caption text-grey-7">
|
<div class="text-caption text-grey-7">
|
||||||
Kaynak: BAGGI_V3 `cdItemAttribute` (ItemTypeCode=1, AttributeTypeCode=10)
|
Kaynak: NEBIM_V3 marka bilgileri
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,10 @@
|
|||||||
</q-form>
|
</q-form>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
||||||
|
<div class="login-version" :title="`Commit: ${appCommit}`">
|
||||||
|
Baggi BSS {{ appVersion }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 🔐 FORGOT PASSWORD -->
|
<!-- 🔐 FORGOT PASSWORD -->
|
||||||
<q-dialog v-model="forgotOpen" persistent>
|
<q-dialog v-model="forgotOpen" persistent>
|
||||||
<q-card style="width:420px; max-width:90vw">
|
<q-card style="width:420px; max-width:90vw">
|
||||||
@@ -129,6 +133,8 @@ import api from 'src/services/api'
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
const $q = useQuasar()
|
const $q = useQuasar()
|
||||||
|
const appVersion = process.env.APP_VERSION || 'v1.0.0'
|
||||||
|
const appCommit = process.env.APP_COMMIT || 'local'
|
||||||
|
|
||||||
const username = ref('')
|
const username = ref('')
|
||||||
const password = ref('')
|
const password = ref('')
|
||||||
@@ -266,6 +272,21 @@ async function sendResetMail () {
|
|||||||
.full-width {
|
.full-width {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-version {
|
||||||
|
position: absolute;
|
||||||
|
left: 16px;
|
||||||
|
bottom: 12px;
|
||||||
|
z-index: 2;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: rgba(45, 45, 45, 0.75);
|
||||||
|
background: rgba(255, 255, 255, 0.68);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.2px;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
/* ===============================
|
/* ===============================
|
||||||
LOGIN INPUT TEXT COLOR (GOLD)
|
LOGIN INPUT TEXT COLOR (GOLD)
|
||||||
=============================== */
|
=============================== */
|
||||||
|
|||||||
@@ -1222,49 +1222,58 @@ async function reloadData ({ page = 1 } = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadImagesForRows (list) {
|
async function loadImagesForRows (list) {
|
||||||
const targets = []
|
const items = []
|
||||||
|
const rowByKey = new Map()
|
||||||
const seen = new Set()
|
const seen = new Set()
|
||||||
for (const row of list) {
|
for (const row of list) {
|
||||||
const key = `${row.productCode}|${row.dim1 || 0}|${row.dim3 || 0}`
|
const key = `${row.productCode}|${row.dim1 || 0}|${row.dim3 || 0}`
|
||||||
if (!row.productCode || seen.has(key)) continue
|
if (!row.productCode || seen.has(key)) continue
|
||||||
seen.add(key)
|
seen.add(key)
|
||||||
targets.push({ row, key })
|
|
||||||
}
|
|
||||||
const concurrency = 12
|
|
||||||
let cursor = 0
|
|
||||||
let loaded = 0
|
|
||||||
const workers = Array.from({ length: Math.min(concurrency, targets.length) }, async () => {
|
|
||||||
for (;;) {
|
|
||||||
const target = targets[cursor]
|
|
||||||
cursor += 1
|
|
||||||
if (!target) return
|
|
||||||
const { row, key } = target
|
|
||||||
if (imageCache.has(key)) {
|
if (imageCache.has(key)) {
|
||||||
row.imageUrl = imageCache.get(key)
|
row.imageUrl = imageCache.get(key)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
rowByKey.set(key, row)
|
||||||
|
items.push({
|
||||||
|
key,
|
||||||
|
code: row.productCode,
|
||||||
|
dim1: row.dim1 || '',
|
||||||
|
dim3: row.dim3 || ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (!items.length) {
|
||||||
|
rows.value = [...rows.value]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunks = []
|
||||||
|
for (let i = 0; i < items.length; i += 250) chunks.push(items.slice(i, i + 250))
|
||||||
|
for (const chunk of chunks) {
|
||||||
try {
|
try {
|
||||||
const res = await api.get('/product-images', {
|
const res = await api.post('/product-images/batch', { items: chunk }, { timeout: 30000 })
|
||||||
params: {
|
const returned = new Set()
|
||||||
code: row.productCode,
|
for (const item of Array.isArray(res?.data) ? res.data : []) {
|
||||||
dim1_id: row.dim1 || '',
|
const key = toText(item?.key)
|
||||||
dim3_id: row.dim3 || ''
|
if (!key) continue
|
||||||
},
|
returned.add(key)
|
||||||
timeout: 15000
|
const list = Array.isArray(item.images) ? item.images : []
|
||||||
})
|
const url = resolveProductImageUrl(list[0])
|
||||||
const first = Array.isArray(res?.data) ? res.data[0] : null
|
imageCache.set(key, url)
|
||||||
const url = resolveProductImageUrl(first)
|
imageListCache.set(key, list)
|
||||||
imageCache.set(key, url)
|
const row = rowByKey.get(key)
|
||||||
row.imageUrl = url
|
if (row) row.imageUrl = url
|
||||||
imageListCache.set(key, Array.isArray(res?.data) ? res.data : [])
|
}
|
||||||
|
for (const item of chunk) {
|
||||||
|
if (!returned.has(item.key)) {
|
||||||
|
imageCache.set(item.key, '')
|
||||||
|
imageListCache.set(item.key, [])
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
imageCache.set(key, '')
|
for (const item of chunk) imageCache.set(item.key, '')
|
||||||
}
|
}
|
||||||
loaded += 1
|
rows.value = [...rows.value]
|
||||||
if (loaded % 12 === 0) rows.value = [...rows.value]
|
}
|
||||||
}
|
|
||||||
})
|
|
||||||
await Promise.all(workers)
|
|
||||||
rows.value = [...rows.value]
|
rows.value = [...rows.value]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1280,14 +1289,12 @@ function normalizeUploadsPath (storagePath) {
|
|||||||
|
|
||||||
function resolveProductImageUrl (item) {
|
function resolveProductImageUrl (item) {
|
||||||
if (!item || typeof item !== 'object') return ''
|
if (!item || typeof item !== 'object') return ''
|
||||||
const imageId = Number(item.id || item.ID || 0)
|
const contentUrl = toText(item.content_url || item.ContentURL)
|
||||||
if (Number.isFinite(imageId) && imageId > 0) return `/api/product-images/${imageId}/content`
|
if (contentUrl) return contentUrl.startsWith('/api/') ? contentUrl : contentUrl
|
||||||
const thumbUrl = toText(item.thumb_url || item.thumbUrl)
|
const thumbUrl = toText(item.thumb_url || item.thumbUrl)
|
||||||
if (thumbUrl) return thumbUrl
|
if (thumbUrl) return thumbUrl
|
||||||
const fullUrl = toText(item.full_url || item.fullUrl)
|
const fullUrl = toText(item.full_url || item.fullUrl)
|
||||||
if (fullUrl) return fullUrl
|
if (fullUrl) return fullUrl
|
||||||
const contentUrl = toText(item.content_url || item.ContentURL)
|
|
||||||
if (contentUrl) return contentUrl.startsWith('/api/') ? contentUrl : contentUrl
|
|
||||||
const uploadsPath = normalizeUploadsPath(item.storage_path || item.storage)
|
const uploadsPath = normalizeUploadsPath(item.storage_path || item.storage)
|
||||||
if (uploadsPath) return uploadsPath
|
if (uploadsPath) return uploadsPath
|
||||||
const fileName = toText(item.file_name || item.FileName)
|
const fileName = toText(item.file_name || item.FileName)
|
||||||
|
|||||||
@@ -371,7 +371,7 @@ const importKeyFieldLabels = [
|
|||||||
const importFieldMap = {
|
const importFieldMap = {
|
||||||
AKTIF: 'is_active',
|
AKTIF: 'is_active',
|
||||||
'HESAP AKTIF': 'calc_enabled',
|
'HESAP AKTIF': 'calc_enabled',
|
||||||
'PG YAYIN': 'publish_postgres',
|
'B2B YAYIN': 'publish_postgres',
|
||||||
'NEBIM YAYIN': 'publish_nebim',
|
'NEBIM YAYIN': 'publish_nebim',
|
||||||
'TRY TOPTAN YUVARLAMA': 'try_wholesale_step',
|
'TRY TOPTAN YUVARLAMA': 'try_wholesale_step',
|
||||||
'TRY PERAKENDE MODU': 'try_retail_mode',
|
'TRY PERAKENDE MODU': 'try_retail_mode',
|
||||||
@@ -454,7 +454,7 @@ const columns = [
|
|||||||
col('brand_group', 'MARKA GRUBU', 'brand_group', 76, { classes: 'ps-col', headerClasses: 'ps-col' }),
|
col('brand_group', 'MARKA GRUBU', 'brand_group', 76, { classes: 'ps-col', headerClasses: 'ps-col' }),
|
||||||
col('anchor_mode', 'ANCHOR MODE', 'anchor_mode', 66, { classes: 'ps-col', headerClasses: 'ps-col' }),
|
col('anchor_mode', 'ANCHOR MODE', 'anchor_mode', 66, { classes: 'ps-col', headerClasses: 'ps-col' }),
|
||||||
col('calc_enabled', 'HESAP AKTIF', 'calc_enabled', 66, { classes: 'ps-col', headerClasses: 'ps-col' }),
|
col('calc_enabled', 'HESAP AKTIF', 'calc_enabled', 66, { classes: 'ps-col', headerClasses: 'ps-col' }),
|
||||||
col('publish_postgres', 'PG YAYIN', 'publish_postgres', 62, { classes: 'ps-col', headerClasses: 'ps-col' }),
|
col('publish_postgres', 'B2B YAYIN', 'publish_postgres', 62, { classes: 'ps-col', headerClasses: 'ps-col' }),
|
||||||
col('publish_nebim', 'NEBIM YAYIN', 'publish_nebim', 66, { classes: 'ps-col', headerClasses: 'ps-col' }),
|
col('publish_nebim', 'NEBIM YAYIN', 'publish_nebim', 66, { classes: 'ps-col', headerClasses: 'ps-col' }),
|
||||||
|
|
||||||
col('try_wholesale_step', 'TRY TOPTAN YUVARLAMA', 'try_wholesale_step', 76, { align: 'right', classes: 'try-col', headerClasses: 'try-col' }),
|
col('try_wholesale_step', 'TRY TOPTAN YUVARLAMA', 'try_wholesale_step', 76, { align: 'right', classes: 'try-col', headerClasses: 'try-col' }),
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -71,6 +71,31 @@
|
|||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-fixed kategori">
|
||||||
|
<div class="filter-head">
|
||||||
|
<span>KATEGORI</span>
|
||||||
|
<q-btn dense flat round size="sm" icon="filter_list" :color="activeFilterCount('kategori') ? 'primary' : 'grey-8'">
|
||||||
|
<q-badge v-if="activeFilterCount('kategori')" floating color="primary">{{ activeFilterCount('kategori') }}</q-badge>
|
||||||
|
<q-menu class="column-filter-menu" anchor="bottom left" self="top left">
|
||||||
|
<div class="q-pa-sm">
|
||||||
|
<q-input v-model="filterSearch.kategori" dense outlined clearable placeholder="Ara" autofocus />
|
||||||
|
<div class="row q-gutter-xs q-mt-sm">
|
||||||
|
<q-btn dense flat size="sm" label="Tumu" @click="selectAllFilter('kategori')" />
|
||||||
|
<q-btn dense flat size="sm" label="Temizle" @click="clearFilter('kategori')" />
|
||||||
|
</div>
|
||||||
|
<q-separator class="q-my-sm" />
|
||||||
|
<q-option-group
|
||||||
|
v-model="columnFilters.kategori"
|
||||||
|
:options="filteredFilterOptions('kategori')"
|
||||||
|
type="checkbox"
|
||||||
|
dense
|
||||||
|
class="column-filter-options"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="col-fixed renk">
|
<div class="col-fixed renk">
|
||||||
<div class="filter-head">
|
<div class="filter-head">
|
||||||
<span>RENK</span>
|
<span>RENK</span>
|
||||||
@@ -214,6 +239,7 @@
|
|||||||
>
|
>
|
||||||
<div class="sub-col model">{{ row.product_code || '-' }}</div>
|
<div class="sub-col model">{{ row.product_code || '-' }}</div>
|
||||||
<div class="sub-col desc">{{ row.product_description || '-' }}</div>
|
<div class="sub-col desc">{{ row.product_description || '-' }}</div>
|
||||||
|
<div class="sub-col kategori">{{ row.kategori || '-' }}</div>
|
||||||
<div class="sub-col renk">
|
<div class="sub-col renk">
|
||||||
<div class="renk-kodu">{{ variantCode(row) }}</div>
|
<div class="renk-kodu">{{ variantCode(row) }}</div>
|
||||||
<div class="renk-aciklama">{{ row.color_title || '-' }}</div>
|
<div class="renk-aciklama">{{ row.color_title || '-' }}</div>
|
||||||
@@ -287,6 +313,7 @@ const errorMessage = ref('')
|
|||||||
const columnFilters = ref({
|
const columnFilters = ref({
|
||||||
model: [],
|
model: [],
|
||||||
desc: [],
|
desc: [],
|
||||||
|
kategori: [],
|
||||||
renk: [],
|
renk: [],
|
||||||
ana: [],
|
ana: [],
|
||||||
alt: [],
|
alt: [],
|
||||||
@@ -295,6 +322,7 @@ const columnFilters = ref({
|
|||||||
const filterSearch = ref({
|
const filterSearch = ref({
|
||||||
model: '',
|
model: '',
|
||||||
desc: '',
|
desc: '',
|
||||||
|
kategori: '',
|
||||||
renk: '',
|
renk: '',
|
||||||
ana: '',
|
ana: '',
|
||||||
alt: '',
|
alt: '',
|
||||||
@@ -341,6 +369,7 @@ const productGroups = computed(() => displayRows.value)
|
|||||||
const filterOptions = computed(() => ({
|
const filterOptions = computed(() => ({
|
||||||
model: uniqueOptions(rows.value.map(row => row.product_code)),
|
model: uniqueOptions(rows.value.map(row => row.product_code)),
|
||||||
desc: uniqueOptions(rows.value.map(row => row.product_description)),
|
desc: uniqueOptions(rows.value.map(row => row.product_description)),
|
||||||
|
kategori: uniqueOptions(rows.value.map(row => row.kategori)),
|
||||||
renk: uniqueOptions(rows.value.map(row => variantCode(row))),
|
renk: uniqueOptions(rows.value.map(row => variantCode(row))),
|
||||||
ana: uniqueOptions(rows.value.map(row => row.urun_ana_grubu)),
|
ana: uniqueOptions(rows.value.map(row => row.urun_ana_grubu)),
|
||||||
alt: uniqueOptions(rows.value.map(row => row.urun_alt_grubu)),
|
alt: uniqueOptions(rows.value.map(row => row.urun_alt_grubu)),
|
||||||
@@ -386,6 +415,7 @@ function clearFilter (key) {
|
|||||||
function rowPassesFilters (row) {
|
function rowPassesFilters (row) {
|
||||||
return filterMatch('model', row.product_code) &&
|
return filterMatch('model', row.product_code) &&
|
||||||
filterMatch('desc', row.product_description) &&
|
filterMatch('desc', row.product_description) &&
|
||||||
|
filterMatch('kategori', row.kategori) &&
|
||||||
filterMatch('renk', variantCode(row)) &&
|
filterMatch('renk', variantCode(row)) &&
|
||||||
filterMatch('ana', row.urun_ana_grubu) &&
|
filterMatch('ana', row.urun_ana_grubu) &&
|
||||||
filterMatch('alt', row.urun_alt_grubu) &&
|
filterMatch('alt', row.urun_alt_grubu) &&
|
||||||
@@ -548,7 +578,7 @@ function mapRowSizesToSchema (row) {
|
|||||||
const rawSizeLabels = sizeEntries.map(([rawSize]) => normalizeBedenLabel(rawSize))
|
const rawSizeLabels = sizeEntries.map(([rawSize]) => normalizeBedenLabel(rawSize))
|
||||||
const schemaMap = getSchemaMap()
|
const schemaMap = getSchemaMap()
|
||||||
const grpKey = detectRowGroupKey(row, rawSizeLabels)
|
const grpKey = detectRowGroupKey(row, rawSizeLabels)
|
||||||
const schema = grpKey && schemaMap?.[grpKey] ? schemaMap[grpKey] : null
|
const schema = pickSchemaForRow(schemaMap, grpKey, rawSizeLabels)
|
||||||
const schemaLabelMap = normalizedSchemaLabelMap(schema)
|
const schemaLabelMap = normalizedSchemaLabelMap(schema)
|
||||||
const mapped = {}
|
const mapped = {}
|
||||||
|
|
||||||
@@ -569,6 +599,26 @@ function mapRowSizesToSchema (row) {
|
|||||||
return { grpKey, schema, mapped }
|
return { grpKey, schema, mapped }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pickSchemaForRow (schemaMap, grpKey, rawSizeLabels) {
|
||||||
|
const primary = grpKey && schemaMap?.[grpKey] ? schemaMap[grpKey] : null
|
||||||
|
const rawSet = new Set((rawSizeLabels || []).map(v => normalizeBedenLabel(v)).filter(Boolean))
|
||||||
|
if (!rawSet.size) return primary
|
||||||
|
const primarySet = new Set((primary?.values || []).map(v => normalizeBedenLabel(v)))
|
||||||
|
if ([...rawSet].some(v => primarySet.has(v))) return primary
|
||||||
|
|
||||||
|
let best = primary
|
||||||
|
let bestScore = 0
|
||||||
|
for (const schema of Object.values(schemaMap || {})) {
|
||||||
|
const schemaSet = new Set((schema?.values || []).map(v => normalizeBedenLabel(v)))
|
||||||
|
const score = [...rawSet].reduce((sum, v) => sum + (schemaSet.has(v) ? 1 : 0), 0)
|
||||||
|
if (score > bestScore) {
|
||||||
|
best = schema
|
||||||
|
bestScore = score
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best
|
||||||
|
}
|
||||||
|
|
||||||
function formatQty (value) {
|
function formatQty (value) {
|
||||||
const n = Number(value || 0)
|
const n = Number(value || 0)
|
||||||
if (!Number.isFinite(n) || n === 0) return ''
|
if (!Number.isFinite(n) || n === 0) return ''
|
||||||
@@ -605,7 +655,7 @@ function escapeHtml (value) {
|
|||||||
function exportVisibleExcel () {
|
function exportVisibleExcel () {
|
||||||
const groups = schemaRows.value
|
const groups = schemaRows.value
|
||||||
const rowSpan = Math.max(groups.length, 1)
|
const rowSpan = Math.max(groups.length, 1)
|
||||||
const leftHeaders = ['MODEL', 'DESC', 'RENK', 'URUN ANA GRUBU', 'URUN ALT GRUBU', 'MARKA']
|
const leftHeaders = ['MODEL', 'DESC', 'KATEGORI', 'RENK', 'URUN ANA GRUBU', 'URUN ALT GRUBU', 'MARKA']
|
||||||
const headerRows = (groups.length ? groups : [{ key: 'tak', title: 'TAKIM ELBISE', values: [] }]).map((grp, index) => {
|
const headerRows = (groups.length ? groups : [{ key: 'tak', title: 'TAKIM ELBISE', values: [] }]).map((grp, index) => {
|
||||||
const left = index === 0
|
const left = index === 0
|
||||||
? leftHeaders.map(h => `<th rowspan="${rowSpan}">${escapeHtml(h)}</th>`).join('')
|
? leftHeaders.map(h => `<th rowspan="${rowSpan}">${escapeHtml(h)}</th>`).join('')
|
||||||
@@ -631,6 +681,7 @@ function exportVisibleExcel () {
|
|||||||
return `<tr>
|
return `<tr>
|
||||||
<td>${escapeHtml(row.product_code || '')}</td>
|
<td>${escapeHtml(row.product_code || '')}</td>
|
||||||
<td>${escapeHtml(row.product_description || '')}</td>
|
<td>${escapeHtml(row.product_description || '')}</td>
|
||||||
|
<td>${escapeHtml(row.kategori || '')}</td>
|
||||||
<td>${escapeHtml(variantCode(row))}</td>
|
<td>${escapeHtml(variantCode(row))}</td>
|
||||||
<td>${escapeHtml(row.urun_ana_grubu || '')}</td>
|
<td>${escapeHtml(row.urun_ana_grubu || '')}</td>
|
||||||
<td>${escapeHtml(row.urun_alt_grubu || '')}</td>
|
<td>${escapeHtml(row.urun_alt_grubu || '')}</td>
|
||||||
@@ -665,6 +716,7 @@ onMounted(async () => {
|
|||||||
--psq-sticky-offset: 12px;
|
--psq-sticky-offset: 12px;
|
||||||
--grp-title-w: 90px;
|
--grp-title-w: 90px;
|
||||||
--col-desc: var(--col-aciklama);
|
--col-desc: var(--col-aciklama);
|
||||||
|
--col-kategori-series: 86px;
|
||||||
--col-marka-series: var(--col-marka, 90px);
|
--col-marka-series: var(--col-marka, 90px);
|
||||||
--psq-header-h: var(--grid-header-h);
|
--psq-header-h: var(--grid-header-h);
|
||||||
--series-total-w: 76px;
|
--series-total-w: 76px;
|
||||||
@@ -681,6 +733,7 @@ onMounted(async () => {
|
|||||||
grid-template-columns:
|
grid-template-columns:
|
||||||
var(--col-model)
|
var(--col-model)
|
||||||
var(--col-desc)
|
var(--col-desc)
|
||||||
|
var(--col-kategori-series)
|
||||||
var(--col-renk)
|
var(--col-renk)
|
||||||
var(--col-ana)
|
var(--col-ana)
|
||||||
var(--col-alt)
|
var(--col-alt)
|
||||||
@@ -774,11 +827,11 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.total-header-cell {
|
.total-header-cell {
|
||||||
grid-column: 8 / 9;
|
grid-column: 9 / 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.series-header-cell {
|
.series-header-cell {
|
||||||
grid-column: 9 / 10;
|
grid-column: 10 / 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
.series-flat-row {
|
.series-flat-row {
|
||||||
@@ -816,12 +869,14 @@ onMounted(async () => {
|
|||||||
|
|
||||||
.series-flat-row .sub-col.model { grid-column: 1; }
|
.series-flat-row .sub-col.model { grid-column: 1; }
|
||||||
.series-flat-row .sub-col.desc { grid-column: 2; }
|
.series-flat-row .sub-col.desc { grid-column: 2; }
|
||||||
.series-flat-row .sub-col.renk { grid-column: 3; }
|
.series-flat-row .sub-col.kategori { grid-column: 3; }
|
||||||
.series-flat-row .sub-col.ana { grid-column: 4; }
|
.series-flat-row .sub-col.renk { grid-column: 4; }
|
||||||
.series-flat-row .sub-col.alt { grid-column: 5; }
|
.series-flat-row .sub-col.ana { grid-column: 5; }
|
||||||
.series-flat-row .sub-col.marka { grid-column: 6; }
|
.series-flat-row .sub-col.alt { grid-column: 6; }
|
||||||
|
.series-flat-row .sub-col.marka { grid-column: 7; }
|
||||||
|
|
||||||
.series-flat-row .sub-col.model,
|
.series-flat-row .sub-col.model,
|
||||||
|
.series-flat-row .sub-col.kategori,
|
||||||
.series-flat-row .sub-col.renk,
|
.series-flat-row .sub-col.renk,
|
||||||
.series-flat-row .sub-col.ana,
|
.series-flat-row .sub-col.ana,
|
||||||
.series-flat-row .sub-col.alt,
|
.series-flat-row .sub-col.alt,
|
||||||
@@ -851,7 +906,7 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.flat-size-cells {
|
.flat-size-cells {
|
||||||
grid-column: 7;
|
grid-column: 8;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
grid-auto-columns: var(--beden-w);
|
grid-auto-columns: var(--beden-w);
|
||||||
@@ -893,7 +948,7 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.total-cell {
|
.total-cell {
|
||||||
grid-column: 8;
|
grid-column: 9;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@@ -906,7 +961,7 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.series-select-cell {
|
.series-select-cell {
|
||||||
grid-column: 9 / 10;
|
grid-column: 10 / 11;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -0,0 +1,919 @@
|
|||||||
|
<template>
|
||||||
|
<q-page
|
||||||
|
class="order-page product-series-page q-pa-md"
|
||||||
|
:style="{
|
||||||
|
'--grid-header-h': showGridHeader ? `${schemaRows.length * 28}px` : '0px',
|
||||||
|
'--beden-count': 16
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="sticky-stack">
|
||||||
|
<div class="save-toolbar row items-center q-gutter-sm">
|
||||||
|
<div>
|
||||||
|
<div class="text-subtitle2 text-weight-bold">B2B'de Olmayan Stoklu Varyantlar</div>
|
||||||
|
<div class="text-caption text-grey-8">
|
||||||
|
Stogu olan ancak B2B tarafinda urun/renk/dim3 kombosu bulunmayan varyantlar listelenir. Bu ekrandan kaydetme yapilmaz.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-space />
|
||||||
|
<q-btn color="primary" outline icon="grid_on" label="Excel" :disable="!displayRows.length" @click="exportVisibleExcel" />
|
||||||
|
<q-btn color="secondary" outline icon="refresh" label="Yenile" :loading="loading" @click="reload" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="showGridHeader" class="order-grid-header series-grid-header">
|
||||||
|
<div class="col-fixed model">
|
||||||
|
<div class="filter-head">
|
||||||
|
<span>MODEL</span>
|
||||||
|
<q-btn dense flat round size="sm" icon="filter_list" :color="activeFilterCount('model') ? 'primary' : 'grey-8'">
|
||||||
|
<q-badge v-if="activeFilterCount('model')" floating color="primary">{{ activeFilterCount('model') }}</q-badge>
|
||||||
|
<q-menu class="column-filter-menu" anchor="bottom left" self="top left">
|
||||||
|
<div class="q-pa-sm">
|
||||||
|
<q-input v-model="filterSearch.model" dense outlined clearable placeholder="Ara" autofocus />
|
||||||
|
<div class="row q-gutter-xs q-mt-sm">
|
||||||
|
<q-btn dense flat size="sm" label="Tumu" @click="selectAllFilter('model')" />
|
||||||
|
<q-btn dense flat size="sm" label="Temizle" @click="clearFilter('model')" />
|
||||||
|
</div>
|
||||||
|
<q-separator class="q-my-sm" />
|
||||||
|
<q-option-group
|
||||||
|
v-model="columnFilters.model"
|
||||||
|
:options="filteredFilterOptions('model')"
|
||||||
|
type="checkbox"
|
||||||
|
dense
|
||||||
|
class="column-filter-options"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-fixed desc-col">
|
||||||
|
<div class="filter-head">
|
||||||
|
<span>DESC</span>
|
||||||
|
<q-btn dense flat round size="sm" icon="filter_list" :color="activeFilterCount('desc') ? 'primary' : 'grey-8'">
|
||||||
|
<q-badge v-if="activeFilterCount('desc')" floating color="primary">{{ activeFilterCount('desc') }}</q-badge>
|
||||||
|
<q-menu class="column-filter-menu" anchor="bottom left" self="top left">
|
||||||
|
<div class="q-pa-sm">
|
||||||
|
<q-input v-model="filterSearch.desc" dense outlined clearable placeholder="Ara" autofocus />
|
||||||
|
<div class="row q-gutter-xs q-mt-sm">
|
||||||
|
<q-btn dense flat size="sm" label="Tumu" @click="selectAllFilter('desc')" />
|
||||||
|
<q-btn dense flat size="sm" label="Temizle" @click="clearFilter('desc')" />
|
||||||
|
</div>
|
||||||
|
<q-separator class="q-my-sm" />
|
||||||
|
<q-option-group
|
||||||
|
v-model="columnFilters.desc"
|
||||||
|
:options="filteredFilterOptions('desc')"
|
||||||
|
type="checkbox"
|
||||||
|
dense
|
||||||
|
class="column-filter-options"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-fixed kategori">
|
||||||
|
<div class="filter-head">
|
||||||
|
<span>KATEGORI</span>
|
||||||
|
<q-btn dense flat round size="sm" icon="filter_list" :color="activeFilterCount('kategori') ? 'primary' : 'grey-8'">
|
||||||
|
<q-badge v-if="activeFilterCount('kategori')" floating color="primary">{{ activeFilterCount('kategori') }}</q-badge>
|
||||||
|
<q-menu class="column-filter-menu" anchor="bottom left" self="top left">
|
||||||
|
<div class="q-pa-sm">
|
||||||
|
<q-input v-model="filterSearch.kategori" dense outlined clearable placeholder="Ara" autofocus />
|
||||||
|
<div class="row q-gutter-xs q-mt-sm">
|
||||||
|
<q-btn dense flat size="sm" label="Tumu" @click="selectAllFilter('kategori')" />
|
||||||
|
<q-btn dense flat size="sm" label="Temizle" @click="clearFilter('kategori')" />
|
||||||
|
</div>
|
||||||
|
<q-separator class="q-my-sm" />
|
||||||
|
<q-option-group
|
||||||
|
v-model="columnFilters.kategori"
|
||||||
|
:options="filteredFilterOptions('kategori')"
|
||||||
|
type="checkbox"
|
||||||
|
dense
|
||||||
|
class="column-filter-options"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-fixed renk">
|
||||||
|
<div class="filter-head">
|
||||||
|
<span>RENK</span>
|
||||||
|
<q-btn dense flat round size="sm" icon="filter_list" :color="activeFilterCount('renk') ? 'primary' : 'grey-8'">
|
||||||
|
<q-badge v-if="activeFilterCount('renk')" floating color="primary">{{ activeFilterCount('renk') }}</q-badge>
|
||||||
|
<q-menu class="column-filter-menu" anchor="bottom left" self="top left">
|
||||||
|
<div class="q-pa-sm">
|
||||||
|
<q-input v-model="filterSearch.renk" dense outlined clearable placeholder="Ara" autofocus />
|
||||||
|
<div class="row q-gutter-xs q-mt-sm">
|
||||||
|
<q-btn dense flat size="sm" label="Tumu" @click="selectAllFilter('renk')" />
|
||||||
|
<q-btn dense flat size="sm" label="Temizle" @click="clearFilter('renk')" />
|
||||||
|
</div>
|
||||||
|
<q-separator class="q-my-sm" />
|
||||||
|
<q-option-group
|
||||||
|
v-model="columnFilters.renk"
|
||||||
|
:options="filteredFilterOptions('renk')"
|
||||||
|
type="checkbox"
|
||||||
|
dense
|
||||||
|
class="column-filter-options"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-fixed ana">
|
||||||
|
<div class="filter-head">
|
||||||
|
<span>URUN ANA GRUBU</span>
|
||||||
|
<q-btn dense flat round size="sm" icon="filter_list" :color="activeFilterCount('ana') ? 'primary' : 'grey-8'">
|
||||||
|
<q-badge v-if="activeFilterCount('ana')" floating color="primary">{{ activeFilterCount('ana') }}</q-badge>
|
||||||
|
<q-menu class="column-filter-menu" anchor="bottom left" self="top left">
|
||||||
|
<div class="q-pa-sm">
|
||||||
|
<q-input v-model="filterSearch.ana" dense outlined clearable placeholder="Ara" autofocus />
|
||||||
|
<div class="row q-gutter-xs q-mt-sm">
|
||||||
|
<q-btn dense flat size="sm" label="Tumu" @click="selectAllFilter('ana')" />
|
||||||
|
<q-btn dense flat size="sm" label="Temizle" @click="clearFilter('ana')" />
|
||||||
|
</div>
|
||||||
|
<q-separator class="q-my-sm" />
|
||||||
|
<q-option-group
|
||||||
|
v-model="columnFilters.ana"
|
||||||
|
:options="filteredFilterOptions('ana')"
|
||||||
|
type="checkbox"
|
||||||
|
dense
|
||||||
|
class="column-filter-options"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-fixed alt">
|
||||||
|
<div class="filter-head">
|
||||||
|
<span>URUN ALT GRUBU</span>
|
||||||
|
<q-btn dense flat round size="sm" icon="filter_list" :color="activeFilterCount('alt') ? 'primary' : 'grey-8'">
|
||||||
|
<q-badge v-if="activeFilterCount('alt')" floating color="primary">{{ activeFilterCount('alt') }}</q-badge>
|
||||||
|
<q-menu class="column-filter-menu" anchor="bottom left" self="top left">
|
||||||
|
<div class="q-pa-sm">
|
||||||
|
<q-input v-model="filterSearch.alt" dense outlined clearable placeholder="Ara" autofocus />
|
||||||
|
<div class="row q-gutter-xs q-mt-sm">
|
||||||
|
<q-btn dense flat size="sm" label="Tumu" @click="selectAllFilter('alt')" />
|
||||||
|
<q-btn dense flat size="sm" label="Temizle" @click="clearFilter('alt')" />
|
||||||
|
</div>
|
||||||
|
<q-separator class="q-my-sm" />
|
||||||
|
<q-option-group
|
||||||
|
v-model="columnFilters.alt"
|
||||||
|
:options="filteredFilterOptions('alt')"
|
||||||
|
type="checkbox"
|
||||||
|
dense
|
||||||
|
class="column-filter-options"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-fixed marka">
|
||||||
|
<div class="filter-head">
|
||||||
|
<span>MARKA</span>
|
||||||
|
<q-btn dense flat round size="sm" icon="filter_list" :color="activeFilterCount('marka') ? 'primary' : 'grey-8'">
|
||||||
|
<q-badge v-if="activeFilterCount('marka')" floating color="primary">{{ activeFilterCount('marka') }}</q-badge>
|
||||||
|
<q-menu class="column-filter-menu" anchor="bottom left" self="top left">
|
||||||
|
<div class="q-pa-sm">
|
||||||
|
<q-input v-model="filterSearch.marka" dense outlined clearable placeholder="Ara" autofocus />
|
||||||
|
<div class="row q-gutter-xs q-mt-sm">
|
||||||
|
<q-btn dense flat size="sm" label="Tumu" @click="selectAllFilter('marka')" />
|
||||||
|
<q-btn dense flat size="sm" label="Temizle" @click="clearFilter('marka')" />
|
||||||
|
</div>
|
||||||
|
<q-separator class="q-my-sm" />
|
||||||
|
<q-option-group
|
||||||
|
v-model="columnFilters.marka"
|
||||||
|
:options="filteredFilterOptions('marka')"
|
||||||
|
type="checkbox"
|
||||||
|
dense
|
||||||
|
class="column-filter-options"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="beden-block">
|
||||||
|
<div
|
||||||
|
v-for="grp in schemaRows"
|
||||||
|
:key="'series-hdr-' + grp.key"
|
||||||
|
class="grp-row"
|
||||||
|
>
|
||||||
|
<div class="grp-title">{{ grp.title }}</div>
|
||||||
|
<div class="grp-body">
|
||||||
|
<div v-for="v in paddedSchemaValues(grp)" :key="'hdr-' + grp.key + '-' + v.key" class="grp-cell hdr" :class="{ ghost: v.ghost }">
|
||||||
|
{{ v.ghost ? '' : v.value }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="total-header-cell">TOPLAM</div>
|
||||||
|
<div class="series-header-cell">SERI</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-banner v-if="errorMessage" class="bg-red-1 text-negative q-my-sm rounded-borders" dense>
|
||||||
|
{{ errorMessage }}
|
||||||
|
</q-banner>
|
||||||
|
|
||||||
|
<q-banner v-else-if="!loading && !displayRows.length" class="bg-blue-1 text-primary q-my-sm rounded-borders" dense>
|
||||||
|
B2B'de olmayan stoklu varyant bulunamadi.
|
||||||
|
</q-banner>
|
||||||
|
|
||||||
|
<q-inner-loading :showing="loading">
|
||||||
|
<q-spinner color="primary" size="42px" />
|
||||||
|
</q-inner-loading>
|
||||||
|
|
||||||
|
<div class="order-scroll-y series-scroll">
|
||||||
|
<div v-if="displayRows.length" class="order-grid-body series-grid-body">
|
||||||
|
<div
|
||||||
|
v-for="row in displayRows"
|
||||||
|
:key="row.row_key"
|
||||||
|
class="series-flat-row warning"
|
||||||
|
>
|
||||||
|
<div class="sub-col model">{{ row.product_code || '-' }}</div>
|
||||||
|
<div class="sub-col desc">{{ row.product_description || '-' }}</div>
|
||||||
|
<div class="sub-col kategori">{{ row.kategori || '-' }}</div>
|
||||||
|
<div class="sub-col renk">
|
||||||
|
<div class="renk-kodu">{{ variantCode(row) }}</div>
|
||||||
|
<div class="renk-aciklama">{{ row.color_title || '-' }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="sub-col ana">{{ row.urun_ana_grubu || '-' }}</div>
|
||||||
|
<div class="sub-col alt">{{ row.urun_alt_grubu || '-' }}</div>
|
||||||
|
<div class="sub-col marka">{{ row.marka || '-' }}</div>
|
||||||
|
|
||||||
|
<div class="flat-size-cells">
|
||||||
|
<div v-for="sz in rowSizeCells(row)" :key="`${row.row_key}-${sz.key}`" class="beden-cell" :class="{ ghost: sz.ghost }">
|
||||||
|
{{ formatQty(row._mapped_size_qty?.[sz.value]) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="total-cell">{{ formatQty(row.total_qty) }}</div>
|
||||||
|
|
||||||
|
<div class="series-select-cell" @click.stop>
|
||||||
|
<q-select
|
||||||
|
v-model="row._series_ids"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
multiple
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
use-chips
|
||||||
|
:options="seriesOptions"
|
||||||
|
option-value="id"
|
||||||
|
option-label="label"
|
||||||
|
disable
|
||||||
|
/>
|
||||||
|
<div class="series-meta">
|
||||||
|
<q-badge color="grey-7">Salt okunur</q-badge>
|
||||||
|
<span v-if="row.mapping_warning" class="mapping-warning">{{ row.mapping_warning }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import { Notify } from 'quasar'
|
||||||
|
import api from 'src/services/api'
|
||||||
|
import {
|
||||||
|
detectBedenGroup,
|
||||||
|
normalizeBedenLabel,
|
||||||
|
schemaByKey as fallbackSchemaByKey,
|
||||||
|
useOrderEntryStore
|
||||||
|
} from 'src/stores/orderentryStore'
|
||||||
|
|
||||||
|
const orderStore = useOrderEntryStore()
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const rows = ref([])
|
||||||
|
const definitions = ref([])
|
||||||
|
const errorMessage = ref('')
|
||||||
|
const columnFilters = ref({
|
||||||
|
model: [],
|
||||||
|
desc: [],
|
||||||
|
kategori: [],
|
||||||
|
renk: [],
|
||||||
|
ana: [],
|
||||||
|
alt: [],
|
||||||
|
marka: []
|
||||||
|
})
|
||||||
|
const filterSearch = ref({
|
||||||
|
model: '',
|
||||||
|
desc: '',
|
||||||
|
kategori: '',
|
||||||
|
renk: '',
|
||||||
|
ana: '',
|
||||||
|
alt: '',
|
||||||
|
marka: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const showGridHeader = computed(() => !loading.value && productGroups.value.length > 0)
|
||||||
|
|
||||||
|
const seriesOptions = computed(() => definitions.value.map(item => ({
|
||||||
|
...item,
|
||||||
|
label: formatSeries(item)
|
||||||
|
})))
|
||||||
|
|
||||||
|
const displayRows = computed(() => {
|
||||||
|
const list = rows.value.filter(rowPassesFilters)
|
||||||
|
const productTotals = productTotalQtyMap(list)
|
||||||
|
return list.sort((a, b) => {
|
||||||
|
const groupDiff = Number(productTotals.get(b.product_code) || 0) - Number(productTotals.get(a.product_code) || 0)
|
||||||
|
if (groupDiff !== 0) return groupDiff
|
||||||
|
const code = String(a.product_code || '').localeCompare(String(b.product_code || ''), 'tr')
|
||||||
|
if (code !== 0) return code
|
||||||
|
const color = String(a.color_code || '').localeCompare(String(b.color_code || ''), 'tr')
|
||||||
|
if (color !== 0) return color
|
||||||
|
const dim3 = String(a.dim3_code || '').localeCompare(String(b.dim3_code || ''), 'tr')
|
||||||
|
if (dim3 !== 0) return dim3
|
||||||
|
return Number(b.total_qty || 0) - Number(a.total_qty || 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function productTotalQtyMap (list) {
|
||||||
|
const totals = new Map()
|
||||||
|
for (const row of list || []) {
|
||||||
|
const code = String(row?.product_code || '').trim()
|
||||||
|
if (!code) continue
|
||||||
|
totals.set(code, Number(totals.get(code) || 0) + Number(row?.total_qty || 0))
|
||||||
|
}
|
||||||
|
return totals
|
||||||
|
}
|
||||||
|
|
||||||
|
const productGroups = computed(() => displayRows.value)
|
||||||
|
|
||||||
|
const filterOptions = computed(() => ({
|
||||||
|
model: uniqueOptions(rows.value.map(row => row.product_code)),
|
||||||
|
desc: uniqueOptions(rows.value.map(row => row.product_description)),
|
||||||
|
kategori: uniqueOptions(rows.value.map(row => row.kategori)),
|
||||||
|
renk: uniqueOptions(rows.value.map(row => variantCode(row))),
|
||||||
|
ana: uniqueOptions(rows.value.map(row => row.urun_ana_grubu)),
|
||||||
|
alt: uniqueOptions(rows.value.map(row => row.urun_alt_grubu)),
|
||||||
|
marka: uniqueOptions(rows.value.map(row => row.marka))
|
||||||
|
}))
|
||||||
|
|
||||||
|
const schemaRows = computed(() => {
|
||||||
|
const map = getSchemaMap()
|
||||||
|
const preferred = ['tak', 'ayk', 'ayk_garson', 'yas', 'pan', 'gom', 'aksbir']
|
||||||
|
const ordered = preferred
|
||||||
|
.map(key => map?.[key])
|
||||||
|
.filter(Boolean)
|
||||||
|
const extras = Object.values(map || {})
|
||||||
|
.filter(grp => grp?.key && !preferred.includes(grp.key))
|
||||||
|
return [...ordered, ...extras]
|
||||||
|
})
|
||||||
|
|
||||||
|
function uniqueOptions (values) {
|
||||||
|
return [...new Set(values.map(v => String(v || '').trim()).filter(Boolean))]
|
||||||
|
.sort((a, b) => a.localeCompare(b, 'tr'))
|
||||||
|
.map(value => ({ label: value, value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
function filteredFilterOptions (key) {
|
||||||
|
const needle = normalizeFilterText(filterSearch.value[key])
|
||||||
|
const opts = filterOptions.value[key] || []
|
||||||
|
if (!needle) return opts
|
||||||
|
return opts.filter(opt => normalizeFilterText(opt.label).includes(needle))
|
||||||
|
}
|
||||||
|
|
||||||
|
function activeFilterCount (key) {
|
||||||
|
return Array.isArray(columnFilters.value[key]) ? columnFilters.value[key].length : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAllFilter (key) {
|
||||||
|
columnFilters.value[key] = filteredFilterOptions(key).map(opt => opt.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFilter (key) {
|
||||||
|
columnFilters.value[key] = []
|
||||||
|
}
|
||||||
|
|
||||||
|
function rowPassesFilters (row) {
|
||||||
|
return filterMatch('model', row.product_code) &&
|
||||||
|
filterMatch('desc', row.product_description) &&
|
||||||
|
filterMatch('kategori', row.kategori) &&
|
||||||
|
filterMatch('renk', variantCode(row)) &&
|
||||||
|
filterMatch('ana', row.urun_ana_grubu) &&
|
||||||
|
filterMatch('alt', row.urun_alt_grubu) &&
|
||||||
|
filterMatch('marka', row.marka)
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterMatch (key, value) {
|
||||||
|
const selected = columnFilters.value[key] || []
|
||||||
|
if (!selected.length) return true
|
||||||
|
return selected.includes(String(value || '').trim())
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeFilterText (value) {
|
||||||
|
return String(value || '')
|
||||||
|
.trim()
|
||||||
|
.toLocaleLowerCase('tr-TR')
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeRow (row) {
|
||||||
|
const ids = Array.isArray(row.series_ids) ? row.series_ids.map(Number).filter(Boolean) : []
|
||||||
|
const mappedInfo = mapRowSizesToSchema(row)
|
||||||
|
return {
|
||||||
|
...row,
|
||||||
|
series_ids: ids,
|
||||||
|
_series_ids: [...ids],
|
||||||
|
_grp_key: mappedInfo.grpKey,
|
||||||
|
_schema: mappedInfo.schema,
|
||||||
|
_mapped_size_qty: mappedInfo.mapped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reload () {
|
||||||
|
loading.value = true
|
||||||
|
errorMessage.value = ''
|
||||||
|
try {
|
||||||
|
if (!orderStore.schemaMap || !Object.keys(orderStore.schemaMap).length) {
|
||||||
|
orderStore.initSchemaMap()
|
||||||
|
}
|
||||||
|
await orderStore.ensureProductSizeMatchRules()
|
||||||
|
const res = await api.get('/pricing/product-series/mappings/orphans', { timeout: 180000 })
|
||||||
|
rows.value = (res.data?.rows || []).map(normalizeRow)
|
||||||
|
definitions.value = res.data?.definitions || []
|
||||||
|
} catch (err) {
|
||||||
|
errorMessage.value = err?.response?.data || err?.message || 'B2B olmayan stoklu varyantlar alinamadi'
|
||||||
|
Notify.create({ type: 'negative', message: errorMessage.value })
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function paddedSchemaValues (grp) {
|
||||||
|
const values = Array.isArray(grp?.values) ? grp.values.map(v => normalizeBedenLabel(v)) : []
|
||||||
|
const out = values.slice(0, 16).map((value, index) => ({ key: `${value}-${index}`, value, ghost: false, toString: () => value }))
|
||||||
|
while (out.length < 16) {
|
||||||
|
const index = out.length
|
||||||
|
out.push({ key: `ghost-${index}`, value: `__ghost_${index}`, ghost: true, toString: () => '' })
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function rowSizeCells (row) {
|
||||||
|
const schema = row?._schema || getSchemaMap()?.[row?._grp_key] || fallbackSchemaByKey.tak
|
||||||
|
return paddedSchemaValues(schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSchemaMap () {
|
||||||
|
return Object.keys(orderStore.schemaMap || {}).length
|
||||||
|
? orderStore.schemaMap
|
||||||
|
: fallbackSchemaByKey
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizedSchemaLabelMap (schema) {
|
||||||
|
const out = new Map()
|
||||||
|
for (const label of schema?.values || []) {
|
||||||
|
const normalized = normalizeBedenLabel(label)
|
||||||
|
if (!out.has(normalized)) out.set(normalized, label)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectRowGroupKey (row, rawSizeLabels) {
|
||||||
|
const fallback = detectRowGroupKeyFallback(row, rawSizeLabels)
|
||||||
|
if (fallback) return fallback
|
||||||
|
return detectBedenGroup(
|
||||||
|
rawSizeLabels,
|
||||||
|
row.urun_ana_grubu || '',
|
||||||
|
row.kategori || '',
|
||||||
|
row.kategori || '',
|
||||||
|
row.urun_alt_grubu || ''
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeMatchText (value) {
|
||||||
|
return String(value || '')
|
||||||
|
.trim()
|
||||||
|
.toLocaleUpperCase('tr-TR')
|
||||||
|
.normalize('NFD')
|
||||||
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectRowGroupKeyFallback (row, rawSizeLabels) {
|
||||||
|
const ana = normalizeMatchText(row?.urun_ana_grubu)
|
||||||
|
const alt = normalizeMatchText(row?.urun_alt_grubu)
|
||||||
|
const marka = normalizeMatchText(row?.marka)
|
||||||
|
const text = `${ana} ${alt} ${marka}`
|
||||||
|
const sizes = (rawSizeLabels || []).map(v => normalizeBedenLabel(v))
|
||||||
|
const hasLetterSize = sizes.some(v => ['XS', 'S', 'M', 'L', 'XL', '2XL', '3XL', '4XL', '5XL', '6XL', '7XL'].includes(v))
|
||||||
|
|
||||||
|
if (['KRAVAT', 'PAPYON', 'KEMER', 'CORAP', 'FULAR', 'MENDIL', 'KASKOL', 'ASKI', 'AKSESUAR'].some(key => text.includes(key))) return 'aksbir'
|
||||||
|
if (text.includes('AYAKKABI')) return text.includes('GARSON') ? 'ayk_garson' : 'ayk'
|
||||||
|
if (text.includes('PANTOLON')) return 'pan'
|
||||||
|
if (text.includes('GOMLEK') || hasLetterSize) return 'gom'
|
||||||
|
if (text.includes('TAKIM') || text.includes('DAMATLIK') || text.includes('CEKET') || text.includes('KABAN') || text.includes('MONT') || text.includes('YELEK')) return 'tak'
|
||||||
|
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapRowSizesToSchema (row) {
|
||||||
|
const sizeEntries = Object.entries(row?.size_qty || {})
|
||||||
|
const rawSizeLabels = sizeEntries.map(([rawSize]) => normalizeBedenLabel(rawSize))
|
||||||
|
const schemaMap = getSchemaMap()
|
||||||
|
const grpKey = detectRowGroupKey(row, rawSizeLabels)
|
||||||
|
const schema = pickSchemaForRow(schemaMap, grpKey, rawSizeLabels)
|
||||||
|
const schemaLabelMap = normalizedSchemaLabelMap(schema)
|
||||||
|
const mapped = {}
|
||||||
|
|
||||||
|
for (const [rawSize, rawQty] of sizeEntries) {
|
||||||
|
const normalized = normalizeBedenLabel(rawSize)
|
||||||
|
const target = normalizeBedenLabel(schemaLabelMap.get(normalized) || normalized)
|
||||||
|
mapped[target] = Number(mapped[target] || 0) + Number(rawQty || 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grpKey === 'aksbir' && !Object.keys(mapped).length && Number(row?.total_qty || 0) > 0) {
|
||||||
|
mapped[' '] = Number(row.total_qty || 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grpKey !== 'aksbir' && Object.keys(mapped).some(k => String(k).trim() !== '')) {
|
||||||
|
delete mapped[' ']
|
||||||
|
}
|
||||||
|
|
||||||
|
return { grpKey, schema, mapped }
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickSchemaForRow (schemaMap, grpKey, rawSizeLabels) {
|
||||||
|
const primary = grpKey && schemaMap?.[grpKey] ? schemaMap[grpKey] : null
|
||||||
|
const rawSet = new Set((rawSizeLabels || []).map(v => normalizeBedenLabel(v)).filter(Boolean))
|
||||||
|
if (!rawSet.size) return primary
|
||||||
|
const primarySet = new Set((primary?.values || []).map(v => normalizeBedenLabel(v)))
|
||||||
|
if ([...rawSet].some(v => primarySet.has(v))) return primary
|
||||||
|
|
||||||
|
let best = primary
|
||||||
|
let bestScore = 0
|
||||||
|
for (const schema of Object.values(schemaMap || {})) {
|
||||||
|
const schemaSet = new Set((schema?.values || []).map(v => normalizeBedenLabel(v)))
|
||||||
|
const score = [...rawSet].reduce((sum, v) => sum + (schemaSet.has(v) ? 1 : 0), 0)
|
||||||
|
if (score > bestScore) {
|
||||||
|
best = schema
|
||||||
|
bestScore = score
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatQty (value) {
|
||||||
|
const n = Number(value || 0)
|
||||||
|
if (!Number.isFinite(n) || n === 0) return ''
|
||||||
|
return n.toLocaleString('tr-TR', { maximumFractionDigits: 2 })
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatSeries (item) {
|
||||||
|
const code = String(item?.code || '').trim()
|
||||||
|
const title = String(item?.title || '').trim()
|
||||||
|
return title ? `${code}/${title}` : code
|
||||||
|
}
|
||||||
|
|
||||||
|
function variantCode (row) {
|
||||||
|
const c = String(row?.color_code || '').trim()
|
||||||
|
const d = String(row?.dim3_code || '').trim()
|
||||||
|
return d ? `${c}-${d}` : (c || '-')
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportFileStamp () {
|
||||||
|
const now = new Date()
|
||||||
|
const pad = v => String(v).padStart(2, '0')
|
||||||
|
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function csvEscape (v) {
|
||||||
|
const s = String(v ?? '')
|
||||||
|
if (!s.includes(',') && !s.includes('"') && !s.includes('\n')) return s
|
||||||
|
return `"${s.replace(/"/g, '""')}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportVisibleExcel () {
|
||||||
|
const cols = ['MODEL', 'DESC', 'KATEGORI', 'RENK', 'URUN ANA GRUBU', 'URUN ALT GRUBU', 'MARKA']
|
||||||
|
const sizeCols = schemaRows.value.flatMap(grp => paddedSchemaValues(grp).map(v => (v.ghost ? '' : v.value))).filter(Boolean)
|
||||||
|
cols.push(...sizeCols)
|
||||||
|
cols.push('TOPLAM', 'SERI', 'UYARI')
|
||||||
|
|
||||||
|
const lines = [cols.map(csvEscape).join(',')]
|
||||||
|
for (const row of displayRows.value) {
|
||||||
|
const sizeValues = []
|
||||||
|
const cells = rowSizeCells(row)
|
||||||
|
for (const cell of cells) {
|
||||||
|
sizeValues.push(cell.ghost ? '' : formatQty(row._mapped_size_qty?.[cell.value]))
|
||||||
|
}
|
||||||
|
lines.push([
|
||||||
|
row.product_code || '',
|
||||||
|
row.product_description || '',
|
||||||
|
row.kategori || '',
|
||||||
|
variantCode(row),
|
||||||
|
row.urun_ana_grubu || '',
|
||||||
|
row.urun_alt_grubu || '',
|
||||||
|
row.marka || '',
|
||||||
|
...sizeValues,
|
||||||
|
formatQty(row.total_qty),
|
||||||
|
'',
|
||||||
|
row.mapping_warning || ''
|
||||||
|
].map(csvEscape).join(','))
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = new Blob([lines.join('\n')], { type: 'text/csv;charset=utf-8;' })
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = url
|
||||||
|
a.download = `b2b-olmayan-stoklu-varyantlar_${exportFileStamp()}.csv`
|
||||||
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
|
document.body.removeChild(a)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => reload())
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.product-series-page {
|
||||||
|
--filter-h: 0px;
|
||||||
|
--psq-sticky-offset: 12px;
|
||||||
|
--grp-title-w: 90px;
|
||||||
|
--col-desc: var(--col-aciklama);
|
||||||
|
--col-kategori-series: 86px;
|
||||||
|
--col-marka-series: var(--col-marka, 90px);
|
||||||
|
--psq-header-h: var(--grid-header-h);
|
||||||
|
--series-total-w: 76px;
|
||||||
|
--series-col-w: minmax(220px, 1fr);
|
||||||
|
background: #f5f1da;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-scroll {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-grid-header,
|
||||||
|
.series-flat-row {
|
||||||
|
grid-template-columns:
|
||||||
|
var(--col-model)
|
||||||
|
var(--col-desc)
|
||||||
|
var(--col-kategori-series)
|
||||||
|
var(--col-renk)
|
||||||
|
var(--col-ana)
|
||||||
|
var(--col-alt)
|
||||||
|
var(--col-marka-series)
|
||||||
|
calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w) * var(--beden-count)))
|
||||||
|
var(--series-total-w)
|
||||||
|
var(--series-col-w) !important;
|
||||||
|
width: 100%;
|
||||||
|
min-width: min-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-grid-header {
|
||||||
|
top: calc(var(--header-h) + var(--filter-h) + var(--save-h) + var(--psq-sticky-offset)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-grid-header .col-fixed,
|
||||||
|
.total-header-cell,
|
||||||
|
.series-header-cell {
|
||||||
|
writing-mode: horizontal-tb !important;
|
||||||
|
transform: none !important;
|
||||||
|
height: var(--psq-header-h) !important;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 10px !important;
|
||||||
|
line-height: 1 !important;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
padding: 0 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-head {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 2px;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-head span {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-filter-menu {
|
||||||
|
width: 280px;
|
||||||
|
max-width: 86vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-filter-options {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-grid-header .beden-block {
|
||||||
|
height: var(--psq-header-h) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-grid-header .grp-row {
|
||||||
|
height: var(--beden-h) !important;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-grid-header .grp-title {
|
||||||
|
width: var(--grp-title-w) !important;
|
||||||
|
text-align: center !important;
|
||||||
|
padding-right: 0 !important;
|
||||||
|
font-size: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-grid-header .grp-cell.hdr {
|
||||||
|
height: var(--beden-h) !important;
|
||||||
|
font-size: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-grid-header .grp-cell.hdr.ghost {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-header-cell,
|
||||||
|
.series-header-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #fff8d1;
|
||||||
|
border-left: 1px solid #d4c79f;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-header-cell {
|
||||||
|
grid-column: 9 / 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-header-cell {
|
||||||
|
grid-column: 10 / 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-flat-row {
|
||||||
|
display: grid;
|
||||||
|
min-height: 72px;
|
||||||
|
align-items: center;
|
||||||
|
background: #fff9c4 !important;
|
||||||
|
border-top: 1px solid #d4c79f !important;
|
||||||
|
border-bottom: 1px solid #d4c79f !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-flat-row.dirty {
|
||||||
|
background: #fff0cf !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-flat-row.warning {
|
||||||
|
box-shadow: inset 3px 0 0 #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-flat-row .sub-col {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #111;
|
||||||
|
min-width: 0;
|
||||||
|
border-right: 1px solid #d4c79f;
|
||||||
|
white-space: normal;
|
||||||
|
overflow: visible;
|
||||||
|
line-height: 1.2;
|
||||||
|
word-break: break-word;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-flat-row .sub-col.model { grid-column: 1; }
|
||||||
|
.series-flat-row .sub-col.desc { grid-column: 2; }
|
||||||
|
.series-flat-row .sub-col.kategori { grid-column: 3; }
|
||||||
|
.series-flat-row .sub-col.renk { grid-column: 4; }
|
||||||
|
.series-flat-row .sub-col.ana { grid-column: 5; }
|
||||||
|
.series-flat-row .sub-col.alt { grid-column: 6; }
|
||||||
|
.series-flat-row .sub-col.marka { grid-column: 7; }
|
||||||
|
|
||||||
|
.series-flat-row .sub-col.model,
|
||||||
|
.series-flat-row .sub-col.kategori,
|
||||||
|
.series-flat-row .sub-col.renk,
|
||||||
|
.series-flat-row .sub-col.ana,
|
||||||
|
.series-flat-row .sub-col.alt,
|
||||||
|
.series-flat-row .sub-col.marka {
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-flat-row .sub-col.desc {
|
||||||
|
justify-content: flex-start;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-flat-row .sub-col.renk {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-flat-row .sub-col.renk .renk-kodu {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-flat-row .sub-col.renk .renk-aciklama {
|
||||||
|
font-size: 11px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flat-size-cells {
|
||||||
|
grid-column: 8;
|
||||||
|
display: grid;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
grid-auto-columns: var(--beden-w);
|
||||||
|
justify-content: start;
|
||||||
|
align-items: stretch;
|
||||||
|
width: calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w) * var(--beden-count)));
|
||||||
|
padding-left: calc(var(--grp-title-w) + var(--grp-title-gap));
|
||||||
|
margin-left: 0;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flat-size-cells::before,
|
||||||
|
.flat-size-cells::after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flat-size-cells .beden-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: var(--beden-w);
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #d4c79f;
|
||||||
|
border-right: none;
|
||||||
|
background: #fffef6;
|
||||||
|
color: #1f1f1f;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flat-size-cells .beden-cell:last-child {
|
||||||
|
border-right: 1px solid #d4c79f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flat-size-cells .beden-cell.ghost {
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-cell {
|
||||||
|
grid-column: 9;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 0 10px;
|
||||||
|
border-left: 1px solid #d4c79f;
|
||||||
|
border-right: 1px solid #d4c79f;
|
||||||
|
color: var(--q-primary, #1976d2);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-select-cell {
|
||||||
|
grid-column: 10 / 11;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-left: 1px solid #d4c79f;
|
||||||
|
background: #fffdf0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-select-cell :deep(.q-field__control) {
|
||||||
|
min-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-chip {
|
||||||
|
max-width: 170px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.series-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
min-height: 18px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mapping-warning {
|
||||||
|
color: #b45309;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -423,7 +423,6 @@ const productImageListByCode = ref({})
|
|||||||
const productImageListLoading = ref({})
|
const productImageListLoading = ref({})
|
||||||
const productImageFallbackByKey = ref({})
|
const productImageFallbackByKey = ref({})
|
||||||
const productImageContentLoading = ref({})
|
const productImageContentLoading = ref({})
|
||||||
const productImageBlobUrls = ref([])
|
|
||||||
const productImageListBlockedUntil = ref(0)
|
const productImageListBlockedUntil = ref(0)
|
||||||
const productCardDialog = ref(false)
|
const productCardDialog = ref(false)
|
||||||
const productCardData = ref({})
|
const productCardData = ref({})
|
||||||
@@ -444,6 +443,8 @@ const IMAGE_LIST_CONCURRENCY = 8
|
|||||||
const FILTER_OPTIONS_CACHE_TTL_MS = 60 * 1000
|
const FILTER_OPTIONS_CACHE_TTL_MS = 60 * 1000
|
||||||
const FILTER_OPTIONS_DEBOUNCE_MS = 250
|
const FILTER_OPTIONS_DEBOUNCE_MS = 250
|
||||||
let imageListActiveRequests = 0
|
let imageListActiveRequests = 0
|
||||||
|
let productImageBatchTimer = null
|
||||||
|
const productImageBatchQueue = new Map()
|
||||||
let filterOptionsDebounceTimer = null
|
let filterOptionsDebounceTimer = null
|
||||||
let filterOptionsRequestSeq = 0
|
let filterOptionsRequestSeq = 0
|
||||||
const imageListWaitQueue = []
|
const imageListWaitQueue = []
|
||||||
@@ -635,14 +636,9 @@ function resolveProductImageUrl(item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let contentUrl = ''
|
let contentUrl = ''
|
||||||
const imageId = Number(item.id || item.ID || 0)
|
const contentURL = String(item.content_url || item.ContentURL || '').trim()
|
||||||
if (Number.isFinite(imageId) && imageId > 0) {
|
if (contentURL.startsWith('/api/')) contentUrl = contentURL
|
||||||
contentUrl = `/api/product-images/${imageId}/content`
|
else if (contentURL.startsWith('/')) contentUrl = `/api${contentURL}`
|
||||||
} else {
|
|
||||||
const contentURL = String(item.content_url || item.ContentURL || '').trim()
|
|
||||||
if (contentURL.startsWith('/api/')) contentUrl = contentURL
|
|
||||||
else if (contentURL.startsWith('/')) contentUrl = `/api${contentURL}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const uploadsPath = normalizeUploadsPath(item.storage_path || item.storage || '')
|
const uploadsPath = normalizeUploadsPath(item.storage_path || item.storage || '')
|
||||||
let publicUrl = ''
|
let publicUrl = ''
|
||||||
@@ -706,34 +702,82 @@ function clearGalleryQueryIndex() {
|
|||||||
async function resolveProductImageUrlForCarousel(item) {
|
async function resolveProductImageUrlForCarousel(item) {
|
||||||
const resolved = resolveProductImageUrl(item)
|
const resolved = resolveProductImageUrl(item)
|
||||||
const contentUrl = String(resolved.contentUrl || '').trim()
|
const contentUrl = String(resolved.contentUrl || '').trim()
|
||||||
if (contentUrl) {
|
if (contentUrl) return contentUrl
|
||||||
try {
|
|
||||||
const blobRes = await api.get(contentUrl, { baseURL: '', responseType: 'blob' })
|
const directUrl = String(resolved.fullUrl || resolved.thumbUrl || resolved.publicUrl || '').trim()
|
||||||
const blob = blobRes?.data
|
if (directUrl) return directUrl
|
||||||
if (blob instanceof Blob) {
|
|
||||||
const objectUrl = URL.createObjectURL(blob)
|
return ''
|
||||||
productImageBlobUrls.value.push(objectUrl)
|
|
||||||
return objectUrl
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// fall through to public url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const fullUrl = String(resolved.fullUrl || '').trim()
|
|
||||||
if (fullUrl) return fullUrl
|
|
||||||
const publicUrl = String(resolved.publicUrl || '').trim()
|
|
||||||
return String(publicUrl || fullUrl || contentUrl || '').trim()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProductImageUrl(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
|
function getProductImageUrl(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
|
||||||
const key = buildImageKey(code, color, secondColor, dim1Id, dim3Id)
|
const key = buildImageKey(code, color, secondColor, dim1Id, dim3Id)
|
||||||
const existing = productImageCache.value[key]
|
const existing = productImageCache.value[key]
|
||||||
if (existing !== undefined) return existing || ''
|
if (existing !== undefined) return existing || ''
|
||||||
|
queueProductImageBatch(code, color, secondColor, dim1Id, dim3Id)
|
||||||
void ensureProductImage(code, color, secondColor, dim1Id, dim3Id)
|
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function queueProductImageBatch(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
|
||||||
|
const key = buildImageKey(code, color, secondColor, dim1Id, dim3Id)
|
||||||
|
const codeTrim = String(code || '').trim().toUpperCase()
|
||||||
|
if (!codeTrim || productImageCache.value[key] !== undefined || productImageLoading.value[key]) return
|
||||||
|
productImageLoading.value[key] = true
|
||||||
|
productImageBatchQueue.set(key, {
|
||||||
|
key,
|
||||||
|
code: codeTrim,
|
||||||
|
dim1: String(dim1Id || color || '').trim().toUpperCase(),
|
||||||
|
dim3: String(dim3Id || secondColor || '').trim().toUpperCase(),
|
||||||
|
listKey: buildImageKey(
|
||||||
|
codeTrim,
|
||||||
|
String(color || '').trim().toUpperCase(),
|
||||||
|
String(secondColor || '').trim().toUpperCase(),
|
||||||
|
String(dim1Id || '').trim().toUpperCase(),
|
||||||
|
String(dim3Id || '').trim().toUpperCase()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
if (productImageBatchTimer) window.clearTimeout(productImageBatchTimer)
|
||||||
|
productImageBatchTimer = window.setTimeout(flushProductImageBatch, 80)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function flushProductImageBatch() {
|
||||||
|
const batch = Array.from(productImageBatchQueue.values()).slice(0, 250)
|
||||||
|
for (const item of batch) productImageBatchQueue.delete(item.key)
|
||||||
|
if (!batch.length) return
|
||||||
|
try {
|
||||||
|
const res = await api.post('/product-images/batch', {
|
||||||
|
items: batch.map(item => ({ key: item.key, code: item.code, dim1: item.dim1, dim3: item.dim3 }))
|
||||||
|
}, { timeout: 30000 })
|
||||||
|
const returned = new Set()
|
||||||
|
for (const item of Array.isArray(res?.data) ? res.data : []) {
|
||||||
|
const key = String(item?.key || '').trim()
|
||||||
|
if (!key) continue
|
||||||
|
returned.add(key)
|
||||||
|
const queued = batch.find(entry => entry.key === key)
|
||||||
|
const list = Array.isArray(item.images) ? item.images : []
|
||||||
|
const first = list[0] || null
|
||||||
|
const resolved = resolveProductImageUrl(first)
|
||||||
|
const url = resolved.contentUrl || resolved.thumbUrl || resolved.fullUrl || resolved.publicUrl || ''
|
||||||
|
productImageCache.value[key] = String(url || '').trim()
|
||||||
|
productImageFallbackByKey.value[key] = resolved.thumbUrl || resolved.fullUrl || resolved.publicUrl || ''
|
||||||
|
if (queued) productImageListByCode.value[queued.listKey] = list
|
||||||
|
delete productImageLoading.value[key]
|
||||||
|
}
|
||||||
|
for (const item of batch) {
|
||||||
|
if (!returned.has(item.key)) {
|
||||||
|
productImageCache.value[item.key] = ''
|
||||||
|
productImageListByCode.value[item.listKey] = []
|
||||||
|
delete productImageLoading.value[item.key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
for (const item of batch) {
|
||||||
|
delete productImageLoading.value[item.key]
|
||||||
|
void ensureProductImage(item.code, item.dim1, item.dim3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function onProductImageError(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
|
async function onProductImageError(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
|
||||||
const key = buildImageKey(code, color, secondColor, dim1Id, dim3Id)
|
const key = buildImageKey(code, color, secondColor, dim1Id, dim3Id)
|
||||||
productImageCache.value[key] = String(productImageFallbackByKey.value[key] || '').trim()
|
productImageCache.value[key] = String(productImageFallbackByKey.value[key] || '').trim()
|
||||||
@@ -804,11 +848,11 @@ async function ensureProductImage(code, color, secondColor = '', dim1Id = '', di
|
|||||||
|
|
||||||
productImageCache.value[key] = String(
|
productImageCache.value[key] = String(
|
||||||
preferredCardUrl ||
|
preferredCardUrl ||
|
||||||
primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.thumbUrl || primaryResolved.contentUrl ||
|
primaryResolved.contentUrl || primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.thumbUrl ||
|
||||||
secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.thumbUrl || secondaryResolved.contentUrl ||
|
secondaryResolved.contentUrl || secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.thumbUrl ||
|
||||||
''
|
''
|
||||||
).trim()
|
).trim()
|
||||||
productImageFallbackByKey.value[key] = primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.contentUrl || secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.contentUrl || ''
|
productImageFallbackByKey.value[key] = primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.thumbUrl || secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.thumbUrl || ''
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[ProductStockByAttributes] product image fetch failed', { code, color, err })
|
console.warn('[ProductStockByAttributes] product image fetch failed', { code, color, err })
|
||||||
productImageCache.value[key] = ''
|
productImageCache.value[key] = ''
|
||||||
@@ -1520,10 +1564,6 @@ onUnmounted(() => {
|
|||||||
clearTimeout(filterOptionsDebounceTimer)
|
clearTimeout(filterOptionsDebounceTimer)
|
||||||
filterOptionsDebounceTimer = null
|
filterOptionsDebounceTimer = null
|
||||||
}
|
}
|
||||||
for (const url of productImageBlobUrls.value) {
|
|
||||||
try { URL.revokeObjectURL(url) } catch {}
|
|
||||||
}
|
|
||||||
productImageBlobUrls.value = []
|
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -390,7 +390,6 @@ const productImageListByCode = ref({})
|
|||||||
const productImageListLoading = ref({})
|
const productImageListLoading = ref({})
|
||||||
const productImageFallbackByKey = ref({})
|
const productImageFallbackByKey = ref({})
|
||||||
const productImageContentLoading = ref({})
|
const productImageContentLoading = ref({})
|
||||||
const productImageBlobUrls = ref([])
|
|
||||||
const productImageListBlockedUntil = ref(0)
|
const productImageListBlockedUntil = ref(0)
|
||||||
const productCardDialog = ref(false)
|
const productCardDialog = ref(false)
|
||||||
const productCardData = ref({})
|
const productCardData = ref({})
|
||||||
@@ -409,6 +408,8 @@ const productImageFullscreenDragOriginX = ref(0)
|
|||||||
const productImageFullscreenDragOriginY = ref(0)
|
const productImageFullscreenDragOriginY = ref(0)
|
||||||
const IMAGE_LIST_CONCURRENCY = 8
|
const IMAGE_LIST_CONCURRENCY = 8
|
||||||
let imageListActiveRequests = 0
|
let imageListActiveRequests = 0
|
||||||
|
let productImageBatchTimer = null
|
||||||
|
const productImageBatchQueue = new Map()
|
||||||
const imageListWaitQueue = []
|
const imageListWaitQueue = []
|
||||||
const activeSchema = ref(storeSchemaByKey.tak)
|
const activeSchema = ref(storeSchemaByKey.tak)
|
||||||
const activeGrpKey = ref('tak')
|
const activeGrpKey = ref('tak')
|
||||||
@@ -616,14 +617,9 @@ function resolveProductImageUrl(item) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let contentUrl = ''
|
let contentUrl = ''
|
||||||
const imageId = Number(item.id || item.ID || 0)
|
const contentURL = String(item.content_url || item.ContentURL || '').trim()
|
||||||
if (Number.isFinite(imageId) && imageId > 0) {
|
if (contentURL.startsWith('/api/')) contentUrl = contentURL
|
||||||
contentUrl = `/api/product-images/${imageId}/content`
|
else if (contentURL.startsWith('/')) contentUrl = `/api${contentURL}`
|
||||||
} else {
|
|
||||||
const contentURL = String(item.content_url || item.ContentURL || '').trim()
|
|
||||||
if (contentURL.startsWith('/api/')) contentUrl = contentURL
|
|
||||||
else if (contentURL.startsWith('/')) contentUrl = `/api${contentURL}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const uploadsPath = normalizeUploadsPath(item.storage_path || item.storage || '')
|
const uploadsPath = normalizeUploadsPath(item.storage_path || item.storage || '')
|
||||||
let publicUrl = ''
|
let publicUrl = ''
|
||||||
@@ -685,33 +681,82 @@ function clearGalleryQueryIndex() {
|
|||||||
async function resolveProductImageUrlForCarousel(item) {
|
async function resolveProductImageUrlForCarousel(item) {
|
||||||
const resolved = resolveProductImageUrl(item)
|
const resolved = resolveProductImageUrl(item)
|
||||||
const contentUrl = String(resolved.contentUrl || '').trim()
|
const contentUrl = String(resolved.contentUrl || '').trim()
|
||||||
if (contentUrl) {
|
if (contentUrl) return contentUrl
|
||||||
try {
|
|
||||||
const blobRes = await api.get(contentUrl, { baseURL: '', responseType: 'blob' })
|
const directUrl = String(resolved.fullUrl || resolved.thumbUrl || resolved.publicUrl || '').trim()
|
||||||
const blob = blobRes?.data
|
if (directUrl) return directUrl
|
||||||
if (blob instanceof Blob) {
|
|
||||||
const objectUrl = URL.createObjectURL(blob)
|
return ''
|
||||||
productImageBlobUrls.value.push(objectUrl)
|
|
||||||
return objectUrl
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// fall through to public url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const fullUrl = String(resolved.fullUrl || '').trim()
|
|
||||||
if (fullUrl) return fullUrl
|
|
||||||
const publicUrl = String(resolved.publicUrl || '').trim()
|
|
||||||
return String(publicUrl || fullUrl || contentUrl || '').trim()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProductImageUrl(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
|
function getProductImageUrl(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
|
||||||
const key = buildImageKey(code, color, secondColor, dim1Id, dim3Id)
|
const key = buildImageKey(code, color, secondColor, dim1Id, dim3Id)
|
||||||
const existing = productImageCache.value[key]
|
const existing = productImageCache.value[key]
|
||||||
if (existing !== undefined) return existing || ''
|
if (existing !== undefined) return existing || ''
|
||||||
void ensureProductImage(code, color, secondColor, dim1Id, dim3Id)
|
queueProductImageBatch(code, color, secondColor, dim1Id, dim3Id)
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function queueProductImageBatch(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
|
||||||
|
const key = buildImageKey(code, color, secondColor, dim1Id, dim3Id)
|
||||||
|
const codeTrim = String(code || '').trim().toUpperCase()
|
||||||
|
if (!codeTrim || productImageCache.value[key] !== undefined || productImageLoading.value[key]) return
|
||||||
|
productImageLoading.value[key] = true
|
||||||
|
productImageBatchQueue.set(key, {
|
||||||
|
key,
|
||||||
|
code: codeTrim,
|
||||||
|
dim1: String(dim1Id || color || '').trim().toUpperCase(),
|
||||||
|
dim3: String(dim3Id || secondColor || '').trim().toUpperCase(),
|
||||||
|
listKey: buildImageKey(
|
||||||
|
codeTrim,
|
||||||
|
String(color || '').trim().toUpperCase(),
|
||||||
|
String(secondColor || '').trim().toUpperCase(),
|
||||||
|
String(dim1Id || '').trim().toUpperCase(),
|
||||||
|
String(dim3Id || '').trim().toUpperCase()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
if (productImageBatchTimer) window.clearTimeout(productImageBatchTimer)
|
||||||
|
productImageBatchTimer = window.setTimeout(flushProductImageBatch, 80)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function flushProductImageBatch() {
|
||||||
|
const batch = Array.from(productImageBatchQueue.values()).slice(0, 250)
|
||||||
|
for (const item of batch) productImageBatchQueue.delete(item.key)
|
||||||
|
if (!batch.length) return
|
||||||
|
try {
|
||||||
|
const res = await api.post('/product-images/batch', {
|
||||||
|
items: batch.map(item => ({ key: item.key, code: item.code, dim1: item.dim1, dim3: item.dim3 }))
|
||||||
|
}, { timeout: 30000 })
|
||||||
|
const returned = new Set()
|
||||||
|
for (const item of Array.isArray(res?.data) ? res.data : []) {
|
||||||
|
const key = String(item?.key || '').trim()
|
||||||
|
if (!key) continue
|
||||||
|
returned.add(key)
|
||||||
|
const queued = batch.find(entry => entry.key === key)
|
||||||
|
const list = Array.isArray(item.images) ? item.images : []
|
||||||
|
const first = list[0] || null
|
||||||
|
const resolved = resolveProductImageUrl(first)
|
||||||
|
const url = resolved.contentUrl || resolved.thumbUrl || resolved.fullUrl || resolved.publicUrl || ''
|
||||||
|
productImageCache.value[key] = String(url || '').trim()
|
||||||
|
productImageFallbackByKey.value[key] = resolved.thumbUrl || resolved.fullUrl || resolved.publicUrl || ''
|
||||||
|
if (queued) productImageListByCode.value[queued.listKey] = list
|
||||||
|
delete productImageLoading.value[key]
|
||||||
|
}
|
||||||
|
for (const item of batch) {
|
||||||
|
if (!returned.has(item.key)) {
|
||||||
|
productImageCache.value[item.key] = ''
|
||||||
|
productImageListByCode.value[item.listKey] = []
|
||||||
|
delete productImageLoading.value[item.key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
for (const item of batch) {
|
||||||
|
delete productImageLoading.value[item.key]
|
||||||
|
void ensureProductImage(item.code, item.dim1, item.dim3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function onProductImageError(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
|
async function onProductImageError(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
|
||||||
const key = buildImageKey(code, color, secondColor, dim1Id, dim3Id)
|
const key = buildImageKey(code, color, secondColor, dim1Id, dim3Id)
|
||||||
productImageCache.value[key] = String(productImageFallbackByKey.value[key] || '').trim()
|
productImageCache.value[key] = String(productImageFallbackByKey.value[key] || '').trim()
|
||||||
@@ -779,11 +824,11 @@ async function ensureProductImage(code, color, secondColor = '', dim1Id = '', di
|
|||||||
const secondaryResolved = resolveProductImageUrl(secondaryItem)
|
const secondaryResolved = resolveProductImageUrl(secondaryItem)
|
||||||
productImageCache.value[key] = String(
|
productImageCache.value[key] = String(
|
||||||
preferredCardUrl ||
|
preferredCardUrl ||
|
||||||
primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.thumbUrl || primaryResolved.contentUrl ||
|
primaryResolved.contentUrl || primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.thumbUrl ||
|
||||||
secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.thumbUrl || secondaryResolved.contentUrl ||
|
secondaryResolved.contentUrl || secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.thumbUrl ||
|
||||||
''
|
''
|
||||||
).trim()
|
).trim()
|
||||||
productImageFallbackByKey.value[key] = primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.contentUrl || secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.contentUrl || ''
|
productImageFallbackByKey.value[key] = primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.thumbUrl || secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.thumbUrl || ''
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('[ProductStockQuery] product image fetch failed', { code, color, err })
|
console.warn('[ProductStockQuery] product image fetch failed', { code, color, err })
|
||||||
productImageCache.value[key] = ''
|
productImageCache.value[key] = ''
|
||||||
@@ -1317,10 +1362,6 @@ function resetForm() {
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('mousemove', onFullscreenMouseMove)
|
window.removeEventListener('mousemove', onFullscreenMouseMove)
|
||||||
window.removeEventListener('mouseup', onFullscreenMouseUp)
|
window.removeEventListener('mouseup', onFullscreenMouseUp)
|
||||||
for (const url of productImageBlobUrls.value) {
|
|
||||||
try { URL.revokeObjectURL(url) } catch {}
|
|
||||||
}
|
|
||||||
productImageBlobUrls.value = []
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|||||||
@@ -40,6 +40,14 @@
|
|||||||
:loading="loading"
|
:loading="loading"
|
||||||
@click="fetchRows"
|
@click="fetchRows"
|
||||||
/>
|
/>
|
||||||
|
<q-btn
|
||||||
|
label="Excel'e Aktar"
|
||||||
|
icon="download"
|
||||||
|
color="primary"
|
||||||
|
outline
|
||||||
|
:disable="loading || rows.length === 0"
|
||||||
|
@click="exportVisibleRows"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -141,7 +149,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { usePermission } from 'src/composables/usePermission'
|
import { usePermission } from 'src/composables/usePermission'
|
||||||
import { get, extractApiErrorDetail } from 'src/services/api'
|
import { get, extractApiErrorDetail } from 'src/services/api'
|
||||||
@@ -168,6 +176,10 @@ const dateColumns = new Set(['Tarihi', 'dteKayitTarihi', 'dteGuncellemeTarihi',
|
|||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'open', label: '', field: 'open', align: 'center', sortable: false, style: 'width:3%', headerStyle: 'width:3%' },
|
{ name: 'open', label: '', field: 'open', align: 'center', sortable: false, style: 'width:3%', headerStyle: 'width:3%' },
|
||||||
{ name: 'UretimSekli', label: 'Uretim Sekli', field: 'UretimSekli', align: 'left', sortable: true, style: 'width:9%', headerStyle: 'width:9%' },
|
{ name: 'UretimSekli', label: 'Uretim Sekli', field: 'UretimSekli', align: 'left', sortable: true, style: 'width:9%', headerStyle: 'width:9%' },
|
||||||
|
{ name: 'nUrtSiparisNo', label: 'Uretim Siparis No', field: 'nUrtSiparisNo', align: 'left', sortable: true, style: 'width:6%', headerStyle: 'width:6%' },
|
||||||
|
{ name: 'ceketDepoGiris', label: 'C-Ceket Depo Giris', field: 'ceketDepoGiris', align: 'right', sortable: true, format: val => formatMoney(val), style: 'width:6%', headerStyle: 'width:6%' },
|
||||||
|
{ name: 'pantolonDepoGiris', label: 'P-Pantolon Depo Giris', field: 'pantolonDepoGiris', align: 'right', sortable: true, format: val => formatMoney(val), style: 'width:6%', headerStyle: 'width:6%' },
|
||||||
|
{ name: 'yelekDepoGiris', label: 'Y-Yelek Depo Giris', field: 'yelekDepoGiris', align: 'right', sortable: true, format: val => formatMoney(val), style: 'width:6%', headerStyle: 'width:6%' },
|
||||||
{ name: 'nOnMLNo', label: 'nOnMLNo', field: 'nOnMLNo', align: 'left', sortable: true, style: 'width:6%', headerStyle: 'width:6%' },
|
{ name: 'nOnMLNo', label: 'nOnMLNo', field: 'nOnMLNo', align: 'left', sortable: true, style: 'width:6%', headerStyle: 'width:6%' },
|
||||||
{ name: 'UrunKodu', label: 'UrunKodu', field: 'UrunKodu', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
|
{ name: 'UrunKodu', label: 'UrunKodu', field: 'UrunKodu', align: 'left', sortable: true, style: 'width:7%', headerStyle: 'width:7%' },
|
||||||
{ name: 'UrunAdi', label: 'UrunAdi', field: 'UrunAdi', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
|
{ name: 'UrunAdi', label: 'UrunAdi', field: 'UrunAdi', align: 'left', sortable: true, style: 'width:8%', headerStyle: 'width:8%' },
|
||||||
@@ -186,6 +198,30 @@ const columns = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const columnFilters = reactive({})
|
const columnFilters = reactive({})
|
||||||
|
const LIST_STATE_STORAGE_KEY = 'bssapp:production-product-costing:has-cost-list-state:v1'
|
||||||
|
|
||||||
|
function persistListState () {
|
||||||
|
try {
|
||||||
|
sessionStorage.setItem(LIST_STATE_STORAGE_KEY, JSON.stringify({
|
||||||
|
filters: { search: String(filters.search || '') },
|
||||||
|
columnFilters: JSON.parse(JSON.stringify(columnFilters)),
|
||||||
|
pagination: { ...tablePagination.value }
|
||||||
|
}))
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreListState () {
|
||||||
|
try {
|
||||||
|
const raw = sessionStorage.getItem(LIST_STATE_STORAGE_KEY)
|
||||||
|
if (!raw) return
|
||||||
|
const saved = JSON.parse(raw)
|
||||||
|
filters.search = String(saved?.filters?.search || '')
|
||||||
|
Object.assign(columnFilters, saved?.columnFilters && typeof saved.columnFilters === 'object' ? saved.columnFilters : {})
|
||||||
|
if (saved?.pagination && typeof saved.pagination === 'object') {
|
||||||
|
tablePagination.value = { ...tablePagination.value, ...saved.pagination }
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
function getColumnFilter (name) {
|
function getColumnFilter (name) {
|
||||||
if (!columnFilters[name]) {
|
if (!columnFilters[name]) {
|
||||||
@@ -293,9 +329,11 @@ function clearAllColumnFilters () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let searchTimer = null
|
let searchTimer = null
|
||||||
|
let filterWatchEnabled = false
|
||||||
watch(
|
watch(
|
||||||
() => filters.search,
|
() => filters.search,
|
||||||
() => {
|
() => {
|
||||||
|
if (!filterWatchEnabled) return
|
||||||
clearTimeout(searchTimer)
|
clearTimeout(searchTimer)
|
||||||
searchTimer = setTimeout(() => {
|
searchTimer = setTimeout(() => {
|
||||||
fetchRows()
|
fetchRows()
|
||||||
@@ -348,19 +386,66 @@ function clearFilters () {
|
|||||||
fetchRows()
|
fetchRows()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeExcelCsvCell (value) {
|
||||||
|
let text = String(value ?? '')
|
||||||
|
// Prevent spreadsheet applications from evaluating data as a formula.
|
||||||
|
if (/^[=+\-@]/.test(text)) text = `'${text}`
|
||||||
|
return `"${text.replace(/"/g, '""')}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const excelNumericColumns = new Set(['ceketDepoGiris', 'pantolonDepoGiris', 'yelekDepoGiris', 'lTutarTL', 'lTutarUSD', 'lTutarEURO'])
|
||||||
|
|
||||||
|
function formatExcelCsvCell (col, rawValue, displayValue) {
|
||||||
|
if (excelNumericColumns.has(col?.name)) {
|
||||||
|
const numericValue = Number(rawValue)
|
||||||
|
if (Number.isFinite(numericValue)) return String(numericValue).replace('.', ',')
|
||||||
|
}
|
||||||
|
return escapeExcelCsvCell(displayValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportVisibleRows () {
|
||||||
|
const visibleRows = Array.isArray(rows.value) ? rows.value : []
|
||||||
|
if (visibleRows.length === 0) return
|
||||||
|
|
||||||
|
const exportColumns = columns.filter(col => col.name !== 'open')
|
||||||
|
const csvLines = [
|
||||||
|
exportColumns.map(col => escapeExcelCsvCell(col.label)).join(';'),
|
||||||
|
...visibleRows.map(row => exportColumns.map(col => {
|
||||||
|
const rawValue = typeof col.field === 'function' ? col.field(row) : row?.[col.field]
|
||||||
|
const displayValue = typeof col.format === 'function' ? col.format(rawValue, row) : rawValue
|
||||||
|
return formatExcelCsvCell(col, rawValue, displayValue)
|
||||||
|
}).join(';'))
|
||||||
|
]
|
||||||
|
|
||||||
|
const blob = new Blob([`\uFEFF${csvLines.join('\r\n')}`], { type: 'text/csv;charset=utf-8;' })
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = `mevcut_maliyetli_urunler_${new Date().toISOString().slice(0, 10)}.csv`
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
link.remove()
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
function openRow (row) {
|
function openRow (row) {
|
||||||
const urunKodu = String(row?.UrunKodu || '').trim()
|
const urunKodu = String(row?.UrunKodu || '').trim()
|
||||||
if (!urunKodu) return
|
if (!urunKodu) return
|
||||||
|
|
||||||
|
persistListState()
|
||||||
|
|
||||||
router.push({
|
router.push({
|
||||||
name: 'production-product-costing-has-cost-history',
|
name: 'production-product-costing-has-cost-history',
|
||||||
query: { urun_kodu: urunKodu }
|
query: { urun_kodu: urunKodu }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
if (!canReadOrder.value) return
|
if (!canReadOrder.value) return
|
||||||
fetchRows()
|
restoreListState()
|
||||||
|
await nextTick()
|
||||||
|
filterWatchEnabled = true
|
||||||
|
await fetchRows()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -457,7 +457,7 @@
|
|||||||
|
|
||||||
<div v-else class="column q-gutter-md pcd-content-body">
|
<div v-else class="column q-gutter-md pcd-content-body">
|
||||||
<div
|
<div
|
||||||
v-for="(grp, gi) in detailGroups"
|
v-for="(grp, gi) in detailGroupsWithTotals"
|
||||||
:key="groupKey(grp, gi)"
|
:key="groupKey(grp, gi)"
|
||||||
class="pcd-group-card"
|
class="pcd-group-card"
|
||||||
>
|
>
|
||||||
@@ -469,7 +469,11 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="sub-right pcd-sub-right-clickable" @click="toggleGroup(grp, gi)">
|
<div class="sub-right pcd-sub-right-clickable" @click="toggleGroup(grp, gi)">
|
||||||
Grup Toplami TRY: {{ formatBarMoney(resolveGroupTRYTutar(grp)) }} | USD: {{ formatBarMoney(resolveGroupUSDTutar(grp)) }}
|
<span class="pcd-group-total-text" data-no-runtime-i18n="true">
|
||||||
|
Grup Toplamı TRY: {{ formatBarMoney(grp.tryTotal) }}
|
||||||
|
<span class="pcd-group-total-divider">|</span>
|
||||||
|
USD: {{ formatBarMoney(grp.usdTotal) }}
|
||||||
|
</span>
|
||||||
<q-icon
|
<q-icon
|
||||||
:name="isGroupOpen(grp, gi) ? 'expand_less' : 'expand_more'"
|
:name="isGroupOpen(grp, gi) ? 'expand_less' : 'expand_more'"
|
||||||
size="18px"
|
size="18px"
|
||||||
@@ -840,7 +844,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="text-subtitle1 text-weight-bold">Fiyat Kontrolu (Satinalma Ortalama)</div>
|
<div class="text-subtitle1 text-weight-bold">Fiyat Kontrolu (Satinalma Ortalama)</div>
|
||||||
<div class="text-caption text-grey-7">
|
<div class="text-caption text-grey-7">
|
||||||
BAGGI_V3 satinalma gecmisindeki son 10 USD ortalamasindan %10'dan fazla sapan tum satirlar.
|
NEBIM_V3 satinalma gecmisindeki son 10 USD ortalamasindan %10'dan fazla sapan tum satirlar.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<q-badge color="primary" outline>{{ priceDeviationRows.length }} satir</q-badge>
|
<q-badge color="primary" outline>{{ priceDeviationRows.length }} satir</q-badge>
|
||||||
@@ -1105,6 +1109,7 @@ import { onBeforeRouteLeave, useRoute, useRouter } from 'vue-router'
|
|||||||
import { usePermission } from 'src/composables/usePermission'
|
import { usePermission } from 'src/composables/usePermission'
|
||||||
import { get, post, download, extractApiErrorDetail } from 'src/services/api'
|
import { get, post, download, extractApiErrorDetail } from 'src/services/api'
|
||||||
import { createTraceId, slog } from 'src/utils/slog'
|
import { createTraceId, slog } from 'src/utils/slog'
|
||||||
|
import { formatDataSourceLabel } from 'src/utils/formatters'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -1556,8 +1561,14 @@ function ensureBeforeUnloadGuard (enabled) {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function shouldIncludeRowInCostingTotal (row) {
|
||||||
|
// The explicit "Maliyete Dahil" selection is authoritative for the top
|
||||||
|
// header and the amount persisted to spUrtOnMLMas, including CM rows.
|
||||||
|
return normalizeBooleanFlag(row?.maliyeteDahil ?? row?.maliyete_dahil ?? row?.Maliyete_dahil)
|
||||||
|
}
|
||||||
|
|
||||||
const toolbarSummary = computed(() => flatDetailRows.value.reduce((acc, row) => {
|
const toolbarSummary = computed(() => flatDetailRows.value.reduce((acc, row) => {
|
||||||
if (!row?.maliyeteDahil) return acc
|
if (!shouldIncludeRowInCostingTotal(row)) return acc
|
||||||
acc.tryTotal += resolveRowTRYTutar(row)
|
acc.tryTotal += resolveRowTRYTutar(row)
|
||||||
acc.usdTotal += resolveRowUSDTutar(row)
|
acc.usdTotal += resolveRowUSDTutar(row)
|
||||||
acc.eurTotal += resolveRowEURTutar(row)
|
acc.eurTotal += resolveRowEURTutar(row)
|
||||||
@@ -2334,8 +2345,8 @@ function resolveHistorySourceType (item, forcedType = '') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveHistorySourceLabel (sourceType) {
|
function resolveHistorySourceLabel (sourceType) {
|
||||||
if (sourceType === 'purchase') return 'BAGGI_V3'
|
if (sourceType === 'purchase') return formatDataSourceLabel('BAGGI_V3')
|
||||||
if (sourceType === 'recipe') return 'URETIM'
|
if (sourceType === 'recipe') return formatDataSourceLabel('URETIM')
|
||||||
return 'HISTORY'
|
return 'HISTORY'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2693,7 +2704,9 @@ function normalizeDetailRows (items, groupName = '') {
|
|||||||
// Use fiyat_girilen which is the unit price in original currency.
|
// Use fiyat_girilen which is the unit price in original currency.
|
||||||
inputPrice: x?.inputPrice ?? normalizeInputPrice(x?.fiyat_girilen),
|
inputPrice: x?.inputPrice ?? normalizeInputPrice(x?.fiyat_girilen),
|
||||||
inputPricePrBr: originalCurrency,
|
inputPricePrBr: originalCurrency,
|
||||||
maliyeteDahil: x?.maliyeteDahil ?? normalizeBooleanFlag(x?.maliyete_dahil ?? x?.Maliyete_dahil),
|
maliyeteDahil: normalizeBooleanFlag(x?.maliyeteDahil ?? x?.maliyete_dahil ?? x?.Maliyete_dahil),
|
||||||
|
__lastValidTRYAmount: parseMoneyInput(x?.lTutar ?? x?.lTutarTL),
|
||||||
|
__lastValidUSDAmount: parseMoneyInput(x?.usdTutar ?? x?.lTutarUSD),
|
||||||
cmPriceTypeId: normalizeCMPriceTypeId(x?.cmPriceTypeId ?? x?.cm_price_type_id, groupName || x?.sAciklama3),
|
cmPriceTypeId: normalizeCMPriceTypeId(x?.cmPriceTypeId ?? x?.cm_price_type_id, groupName || x?.sAciklama3),
|
||||||
draftChanged: Boolean(x?.draftChanged),
|
draftChanged: Boolean(x?.draftChanged),
|
||||||
priceUpdateState: String(x?.priceUpdateState || '').trim()
|
priceUpdateState: String(x?.priceUpdateState || '').trim()
|
||||||
@@ -2795,6 +2808,13 @@ function recalculateDetailRow (row, options = {}) {
|
|||||||
row.lTutarGBP = row.gbpTutar
|
row.lTutarGBP = row.gbpTutar
|
||||||
row.lTutarTL = row.lTutar
|
row.lTutarTL = row.lTutar
|
||||||
|
|
||||||
|
if (row.lTutar !== 0 || options.markChanged) {
|
||||||
|
row.__lastValidTRYAmount = row.lTutar
|
||||||
|
}
|
||||||
|
if (row.usdTutar !== 0 || options.markChanged) {
|
||||||
|
row.__lastValidUSDAmount = row.usdTutar
|
||||||
|
}
|
||||||
|
|
||||||
if (!options.preserveInputs) {
|
if (!options.preserveInputs) {
|
||||||
row.miktarInput = normalizeQuantityInput(quantity)
|
row.miktarInput = normalizeQuantityInput(quantity)
|
||||||
row.inputPrice = normalizeInputPrice(inputPrice)
|
row.inputPrice = normalizeInputPrice(inputPrice)
|
||||||
@@ -2816,58 +2836,124 @@ function recalculateDetailRow (row, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function recalculateAllDetailRows () {
|
function recalculateAllDetailRows () {
|
||||||
detailGroups.value = detailGroups.value.map(grp => ({
|
// Preserve row identity. QTable editors and the computed group totals must
|
||||||
...grp,
|
// observe the exact same reactive row objects. Replacing rows with
|
||||||
items: (Array.isArray(grp?.items) ? grp.items : []).map(row => recalculateDetailRow({ ...row }, { preserveInputs: true }))
|
// `{ ...row }` after the async exchange-rate request left QTable editing an
|
||||||
}))
|
// older object while totals watched the replacement object.
|
||||||
|
detailGroups.value.forEach(grp => {
|
||||||
|
const items = Array.isArray(grp?.items) ? grp.items : []
|
||||||
|
items.forEach(row => {
|
||||||
|
recalculateDetailRow(row, { preserveInputs: true })
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveRowTRYTutar (row) {
|
function resolveRowTRYTutar (row) {
|
||||||
const tryAmount = Number(row?.lTutar || 0)
|
const quantity = resolveNumericRowQuantity(row)
|
||||||
return Number.isFinite(tryAmount) ? tryAmount : 0
|
const inputPrice = resolveNumericRowInputPrice(row)
|
||||||
|
const inputCurrency = resolveInputCurrency(row)
|
||||||
|
const liveAmount = resolveTRYUnitPriceByInput(
|
||||||
|
inputPrice,
|
||||||
|
inputCurrency,
|
||||||
|
resolveExchangeRateValue('USD'),
|
||||||
|
resolveExchangeRateValue('EUR'),
|
||||||
|
resolveExchangeRateValue('GBP')
|
||||||
|
) * quantity
|
||||||
|
|
||||||
|
if (Number.isFinite(liveAmount) && inputPrice > 0) return liveAmount
|
||||||
|
return parseMoneyInput(row?.lTutar)
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveRowEURTutar (row) {
|
function resolveRowEURTutar (row) {
|
||||||
const eurAmount = Number(row?.eurTutar || 0)
|
return parseMoneyInput(row?.eurTutar)
|
||||||
return Number.isFinite(eurAmount) ? eurAmount : 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveRowGBPTutar (row) {
|
function resolveRowGBPTutar (row) {
|
||||||
const gbpAmount = Number(row?.gbpTutar || 0)
|
return parseMoneyInput(row?.gbpTutar)
|
||||||
return Number.isFinite(gbpAmount) ? gbpAmount : 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveRowUSDTutar (row) {
|
function resolveRowUSDTutar (row) {
|
||||||
const usdAmount = Number(row?.usdTutar || 0)
|
const quantity = resolveNumericRowQuantity(row)
|
||||||
if (Number.isFinite(usdAmount)) return usdAmount
|
const inputPrice = resolveNumericRowInputPrice(row)
|
||||||
|
const inputCurrency = resolveInputCurrency(row)
|
||||||
|
const liveAmount = resolveUSDUnitPriceByInput(
|
||||||
|
inputPrice,
|
||||||
|
inputCurrency,
|
||||||
|
resolveExchangeRateValue('USD'),
|
||||||
|
resolveExchangeRateValue('EUR'),
|
||||||
|
resolveExchangeRateValue('GBP')
|
||||||
|
) * quantity
|
||||||
|
|
||||||
const miktar = resolveNumericRowQuantity(row)
|
if (Number.isFinite(liveAmount) && inputPrice > 0) return liveAmount
|
||||||
const dovizFiyati = Number(row?.lDovizFiyati || 0)
|
|
||||||
const calc = miktar * dovizFiyati
|
const rawUSDAmount = row?.usdTutar
|
||||||
|
if (rawUSDAmount !== undefined && rawUSDAmount !== null && String(rawUSDAmount).trim() !== '') {
|
||||||
|
return parseMoneyInput(rawUSDAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dovizFiyati = parseMoneyInput(row?.lDovizFiyati)
|
||||||
|
const calc = quantity * dovizFiyati
|
||||||
return Number.isFinite(calc) ? calc : 0
|
return Number.isFinite(calc) ? calc : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldIgnoreGroupMaliyeteDahil (grp) {
|
|
||||||
return isCMGroupName(grp?.sAciklama3)
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldIncludeRowInGroupTotal (grp, row) {
|
function shouldIncludeRowInGroupTotal (grp, row) {
|
||||||
return shouldIgnoreGroupMaliyeteDahil(grp) || normalizeBooleanFlag(row?.maliyeteDahil)
|
if (!row) return false
|
||||||
|
|
||||||
|
// CM1/CM2 sub-headers always show their full group value. For every other
|
||||||
|
// group, the explicit "Maliyete Dahil" selection controls the subtotal.
|
||||||
|
if (isCMGroupName(grp?.sAciklama3 || row?.sAciklama3)) return true
|
||||||
|
|
||||||
|
const inclusionValue = row?.maliyeteDahil ?? row?.maliyete_dahil ?? row?.Maliyete_dahil
|
||||||
|
if (inclusionValue === false || inclusionValue === 0) return false
|
||||||
|
const normalizedValue = String(inclusionValue ?? '').trim().toLowerCase()
|
||||||
|
return normalizedValue !== '0' && normalizedValue !== 'false' && normalizedValue !== 'hayir'
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveGroupTRYTutar (grp) {
|
function resolveGroupRowTRYAmount (row) {
|
||||||
const items = Array.isArray(grp?.items) ? grp.items : []
|
const columnAmount = parseMoneyInput(row?.lTutar)
|
||||||
return items.reduce((acc, row) => acc + (shouldIncludeRowInGroupTotal(grp, row) ? resolveRowTRYTutar(row) : 0), 0)
|
if (columnAmount !== 0) return columnAmount
|
||||||
|
|
||||||
|
const liveAmount = resolveRowTRYTutar(row)
|
||||||
|
if (liveAmount !== 0) return liveAmount
|
||||||
|
|
||||||
|
return parseMoneyInput(row?.__lastValidTRYAmount ?? row?.lTutarTL)
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveGroupUSDTutar (grp) {
|
function resolveGroupRowUSDAmount (row) {
|
||||||
const items = Array.isArray(grp?.items) ? grp.items : []
|
const columnAmount = parseMoneyInput(row?.usdTutar ?? row?.lTutarUSD)
|
||||||
return items.reduce((acc, row) => acc + (shouldIncludeRowInGroupTotal(grp, row) ? resolveRowUSDTutar(row) : 0), 0)
|
if (columnAmount !== 0) return columnAmount
|
||||||
|
|
||||||
|
const liveAmount = resolveRowUSDTutar(row)
|
||||||
|
if (liveAmount !== 0) return liveAmount
|
||||||
|
|
||||||
|
return parseMoneyInput(row?.__lastValidUSDAmount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OrderEntry groupedRows ile ayni desen: sub-header degerleri ayri bir
|
||||||
|
// state/cache icinde tutulmaz; iki gorunen tutar kolonu satirlardan reaktif
|
||||||
|
// olarak her degisiklikte yeniden toplanir.
|
||||||
|
const detailGroupsWithTotals = computed(() => (
|
||||||
|
detailGroups.value.map(group => {
|
||||||
|
const items = Array.isArray(group?.items) ? group.items : []
|
||||||
|
const totals = items.reduce((acc, row) => {
|
||||||
|
if (!shouldIncludeRowInGroupTotal(group, row)) return acc
|
||||||
|
acc.tryTotal += resolveGroupRowTRYAmount(row)
|
||||||
|
acc.usdTotal += resolveGroupRowUSDAmount(row)
|
||||||
|
return acc
|
||||||
|
}, { tryTotal: 0, usdTotal: 0 })
|
||||||
|
|
||||||
|
return {
|
||||||
|
...group,
|
||||||
|
tryTotal: totals.tryTotal,
|
||||||
|
usdTotal: totals.usdTotal
|
||||||
|
}
|
||||||
|
})
|
||||||
|
))
|
||||||
|
|
||||||
function resolveGroupQuantity (grp) {
|
function resolveGroupQuantity (grp) {
|
||||||
const items = Array.isArray(grp?.items) ? grp.items : []
|
return (Array.isArray(grp?.items) ? grp.items : []).reduce((total, row) => (
|
||||||
return items.reduce((acc, row) => acc + (Number(row?.lMiktar || 0) || 0), 0)
|
total + resolveNumericRowQuantity(row)
|
||||||
|
), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function groupKey (grp, gi) {
|
function groupKey (grp, gi) {
|
||||||
@@ -3243,7 +3329,7 @@ function onRowQuantityInput (row, value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function triggerUIUpdate () {
|
function triggerUIUpdate () {
|
||||||
detailGroups.value = [...detailGroups.value]
|
// detailGroupsWithTotals is computed from the reactive row fields.
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeRowQuantityDisplay (row) {
|
function normalizeRowQuantityDisplay (row) {
|
||||||
@@ -3341,25 +3427,26 @@ async function fetchBulkItemPrices () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let appliedCount = 0
|
let appliedCount = 0
|
||||||
detailGroups.value = detailGroups.value.map(grp => ({
|
detailGroups.value.forEach(grp => {
|
||||||
...grp,
|
const items = Array.isArray(grp?.items) ? grp.items : []
|
||||||
items: (Array.isArray(grp?.items) ? grp.items : []).map(row => {
|
items.forEach(row => {
|
||||||
const match = updates.find(update => rowMatchesBulkUpdate(row, update))
|
const match = updates.find(update => rowMatchesBulkUpdate(row, update))
|
||||||
if (!match) return row
|
if (!match) return
|
||||||
appliedCount += 1
|
appliedCount += 1
|
||||||
return recalculateDetailRow({
|
row.inputPrice = match.inputPrice
|
||||||
...row,
|
row.fiyat_girilen = match.fiyat_girilen
|
||||||
inputPrice: match.inputPrice,
|
row.inputPricePrBr = match.inputPricePrBr
|
||||||
fiyat_girilen: match.fiyat_girilen,
|
row.fiyat_doviz = match.fiyat_doviz
|
||||||
inputPricePrBr: match.inputPricePrBr,
|
recalculateDetailRow(row, {
|
||||||
fiyat_doviz: match.fiyat_doviz
|
|
||||||
}, {
|
|
||||||
priceType: match.priceType || 'SAF',
|
priceType: match.priceType || 'SAF',
|
||||||
updateState: 'bulk',
|
updateState: 'bulk',
|
||||||
markChanged: true
|
markChanged: true
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}))
|
})
|
||||||
|
// Update sub-headers immediately after the bulk response is applied. Do
|
||||||
|
// not wait for the optional previous-cost autofill request below.
|
||||||
|
triggerUIUpdate()
|
||||||
|
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: appliedCount > 0 ? 'positive' : 'warning',
|
type: appliedCount > 0 ? 'positive' : 'warning',
|
||||||
@@ -3399,14 +3486,14 @@ async function fetchBulkItemPrices () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
let filled = 0
|
let filled = 0
|
||||||
detailGroups.value = detailGroups.value.map(grp => ({
|
detailGroups.value.forEach(grp => {
|
||||||
...grp,
|
const items = Array.isArray(grp?.items) ? grp.items : []
|
||||||
items: (Array.isArray(grp?.items) ? grp.items : []).map(row => {
|
items.forEach(row => {
|
||||||
if (!row?.requiredPlaceholder) return row
|
if (!row?.requiredPlaceholder) return
|
||||||
if (isCMGroupName(row?.sAciklama3) || normalizeGroupName(row?.sAciklama3) === 'FABRIC') return row
|
if (isCMGroupName(row?.sAciklama3) || normalizeGroupName(row?.sAciklama3) === 'FABRIC') return
|
||||||
const no = parseInt(String(row?.nHammaddeTuruNo || '').trim() || '0', 10) || 0
|
const no = parseInt(String(row?.nHammaddeTuruNo || '').trim() || '0', 10) || 0
|
||||||
const hit = byNo[no]
|
const hit = byNo[no]
|
||||||
if (!hit) return row
|
if (!hit) return
|
||||||
|
|
||||||
const next = { ...row }
|
const next = { ...row }
|
||||||
const hasCode = String(next.sKodu || '').trim() !== ''
|
const hasCode = String(next.sKodu || '').trim() !== ''
|
||||||
@@ -3426,20 +3513,20 @@ async function fetchBulkItemPrices () {
|
|||||||
|
|
||||||
// Only mark if something changed
|
// Only mark if something changed
|
||||||
const changed = (next.sKodu !== row.sKodu) || (next.sAciklama !== row.sAciklama) || (next.inputPrice !== row.inputPrice) || (next.inputPricePrBr !== row.inputPricePrBr)
|
const changed = (next.sKodu !== row.sKodu) || (next.sAciklama !== row.sAciklama) || (next.inputPrice !== row.inputPrice) || (next.inputPricePrBr !== row.inputPricePrBr)
|
||||||
if (!changed) return row
|
if (!changed) return
|
||||||
filled += 1
|
filled += 1
|
||||||
return recalculateDetailRow({
|
Object.assign(row, next, {
|
||||||
...next,
|
__autoFilledFromPrev: true,
|
||||||
__autoFilledFromPrev: true
|
|
||||||
,
|
|
||||||
__autoFilledFromPrevSameFirma: Boolean(hit?.is_same_firma || hit?.isSameFirma)
|
__autoFilledFromPrevSameFirma: Boolean(hit?.is_same_firma || hit?.isSameFirma)
|
||||||
}, {
|
})
|
||||||
|
recalculateDetailRow(row, {
|
||||||
priceType: 'PREV',
|
priceType: 'PREV',
|
||||||
updateState: 'autofill',
|
updateState: 'autofill',
|
||||||
markChanged: true
|
markChanged: true
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}))
|
})
|
||||||
|
triggerUIUpdate()
|
||||||
|
|
||||||
if (filled > 0) {
|
if (filled > 0) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
@@ -3456,6 +3543,11 @@ async function fetchBulkItemPrices () {
|
|||||||
error: String(e?.message || e || '')
|
error: String(e?.message || e || '')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// Bulk price and previous-cost autofill paths can update many rows at once.
|
||||||
|
// Recalculate once more as a whole so group sub-header totals react immediately.
|
||||||
|
recalculateAllDetailRows()
|
||||||
|
schedulePersistLocalDraft()
|
||||||
|
triggerUIUpdate()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
@@ -3927,10 +4019,14 @@ async function ensureNoCostRequiredRowsFromMappings (mappings) {
|
|||||||
const groupName = normalizeGroupName(meta?.groupName || '')
|
const groupName = normalizeGroupName(meta?.groupName || '')
|
||||||
const hammaddeAdi = String(meta?.hammaddeAdi || '').trim()
|
const hammaddeAdi = String(meta?.hammaddeAdi || '').trim()
|
||||||
const effectiveGroupName = groupName || 'TANIMSIZ'
|
const effectiveGroupName = groupName || 'TANIMSIZ'
|
||||||
const mtBolumID = (meta?.mtBolumID > 0 ? meta.mtBolumID : mappingMtBolumID) || 0
|
// The product-type mapping is authoritative for the required part. A raw
|
||||||
const metaParcaCandidate = normalizeGroupName((meta?.parcaAdi || '').trim())
|
// material type can be required for multiple parts, while its master
|
||||||
// Defensive: if backend/lookup accidentally returns group label (DT/TP/...) as part name, ignore it.
|
// record only carries one default MT section. Using that default here
|
||||||
const desiredParcaAdi = normalizeGroupName((metaParcaCandidate && !isKnownGroupName(metaParcaCandidate)) ? metaParcaCandidate : mappingParcaAdi)
|
// incorrectly made an existing recipe row satisfy every mapped part.
|
||||||
|
const mtBolumID = mappingMtBolumID > 0 ? mappingMtBolumID : (meta?.mtBolumID || 0)
|
||||||
|
const desiredParcaAdi = mappingMtBolumID > 0
|
||||||
|
? mappingParcaAdi
|
||||||
|
: normalizeGroupName(meta?.parcaAdi || mappingParcaAdi)
|
||||||
|
|
||||||
const anyMatch = flatDetailRows.value.find(r => {
|
const anyMatch = flatDetailRows.value.find(r => {
|
||||||
if (normalizeHammaddeNo(r?.nHammaddeTuruNo) !== hNo) return false
|
if (normalizeHammaddeNo(r?.nHammaddeTuruNo) !== hNo) return false
|
||||||
@@ -4770,7 +4866,7 @@ async function confirmBrPriceDeviationIfNeeded () {
|
|||||||
html: true,
|
html: true,
|
||||||
message: `
|
message: `
|
||||||
<div style="margin-bottom:10px;">
|
<div style="margin-bottom:10px;">
|
||||||
Bazı satırlarda girilen fiyat, BAGGI_V3 satınalma geçmişindeki <b>son 10</b> kaydın USD ortalamasından <b>%10</b>'dan fazla sapıyor.
|
Bazı satırlarda girilen fiyat, NEBIM_V3 satınalma geçmişindeki <b>son 10</b> kaydın USD ortalamasından <b>%10</b>'dan fazla sapıyor.
|
||||||
</div>
|
</div>
|
||||||
<div style="max-height: 360px; overflow:auto; border:1px solid #e0e0e0; border-radius:6px;">
|
<div style="max-height: 360px; overflow:auto; border:1px solid #e0e0e0; border-radius:6px;">
|
||||||
<table style="width:100%; border-collapse:collapse; font-size:13px;">
|
<table style="width:100%; border-collapse:collapse; font-size:13px;">
|
||||||
@@ -5061,13 +5157,31 @@ async function saveChanges () {
|
|||||||
|
|
||||||
// If we created a new OnML (no-cost), switch to has-cost detail mode.
|
// If we created a new OnML (no-cost), switch to has-cost detail mode.
|
||||||
if (isNoCostDetail.value && newOnMLNo > 0) {
|
if (isNoCostDetail.value && newOnMLNo > 0) {
|
||||||
router.replace({
|
await router.replace({
|
||||||
name: 'production-product-costing-has-cost-detail',
|
name: 'production-product-costing-has-cost-detail',
|
||||||
query: {
|
query: {
|
||||||
n_onml_no: String(newOnMLNo),
|
n_onml_no: String(newOnMLNo),
|
||||||
urun_kodu: String(header?.UrunKodu || productCode.value || '').trim()
|
urun_kodu: String(header?.UrunKodu || productCode.value || '').trim()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
$q.dialog({
|
||||||
|
title: 'Maliyet Kaydedildi',
|
||||||
|
message: 'Maliyeti olmayan urunler listesine donup kaldiginiz yerden devam etmek ister misiniz?',
|
||||||
|
ok: {
|
||||||
|
label: 'Listeye Don',
|
||||||
|
color: 'primary',
|
||||||
|
icon: 'arrow_back'
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
label: 'Bu Sayfada Devam Et',
|
||||||
|
color: 'grey-7',
|
||||||
|
flat: true
|
||||||
|
},
|
||||||
|
persistent: true
|
||||||
|
}).onOk(() => {
|
||||||
|
router.push({ name: 'production-product-costing-no-cost' })
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5077,6 +5191,24 @@ async function saveChanges () {
|
|||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
try { refreshLast10Warnings() } catch {}
|
try { refreshLast10Warnings() } catch {}
|
||||||
}, 1200)
|
}, 1200)
|
||||||
|
|
||||||
|
$q.dialog({
|
||||||
|
title: 'Maliyet Guncellendi',
|
||||||
|
message: 'Mevcut maliyeti olan urunler listesine donup kaldiginiz yerden devam etmek ister misiniz?',
|
||||||
|
ok: {
|
||||||
|
label: 'Listeye Don',
|
||||||
|
color: 'primary',
|
||||||
|
icon: 'arrow_back'
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
label: 'Bu Sayfada Devam Et',
|
||||||
|
color: 'grey-7',
|
||||||
|
flat: true
|
||||||
|
},
|
||||||
|
persistent: true
|
||||||
|
}).onOk(() => {
|
||||||
|
router.push({ name: 'production-product-costing-has-cost' })
|
||||||
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Surface backend message (http.Error text) when available.
|
// Surface backend message (http.Error text) when available.
|
||||||
const msg = String(
|
const msg = String(
|
||||||
@@ -5525,8 +5657,8 @@ watch(
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
min-height: 42px;
|
min-height: 46px;
|
||||||
height: 42px;
|
height: auto;
|
||||||
border-top: 1px solid #d6c06a;
|
border-top: 1px solid #d6c06a;
|
||||||
border-bottom: 1px solid #d6c06a;
|
border-bottom: 1px solid #d6c06a;
|
||||||
background: linear-gradient(90deg, #fffbe9 0%, #fff4c4 50%, #fff1b0 100%);
|
background: linear-gradient(90deg, #fffbe9 0%, #fff4c4 50%, #fff1b0 100%);
|
||||||
@@ -5548,7 +5680,9 @@ watch(
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: wrap;
|
||||||
|
row-gap: 4px;
|
||||||
|
max-width: 70%;
|
||||||
}
|
}
|
||||||
.pcd-sub-header .sub-left {
|
.pcd-sub-header .sub-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -5565,8 +5699,31 @@ watch(
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 6px 10px;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.pcd-group-total-text {
|
||||||
|
color: #2f2408;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 900;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.pcd-group-total-divider {
|
||||||
|
margin: 0 4px;
|
||||||
|
color: #8d6d13;
|
||||||
|
}
|
||||||
|
.pcd-group-debug {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 2px 7px;
|
||||||
|
border: 1px solid rgba(180, 35, 24, 0.35);
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #8f1d14;
|
||||||
|
background: rgba(255, 255, 255, 0.78);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: none;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.pcd-sub-mt-qty {
|
.pcd-sub-mt-qty {
|
||||||
@@ -5576,6 +5733,18 @@ watch(
|
|||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.pcd-sub-header {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
.pcd-sub-header .sub-right {
|
||||||
|
max-width: 78%;
|
||||||
|
}
|
||||||
|
.pcd-group-debug {
|
||||||
|
flex-basis: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.pcd-detail-table :deep(.q-table__middle) {
|
.pcd-detail-table :deep(.q-table__middle) {
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
}
|
}
|
||||||
@@ -5716,12 +5885,69 @@ watch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pcd-history-table :deep(.q-table thead th) {
|
.pcd-history-table :deep(.q-table thead th) {
|
||||||
font-size: 12px;
|
font-size: 9.5px;
|
||||||
|
line-height: 1.1;
|
||||||
|
white-space: normal;
|
||||||
|
padding: 3px 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pcd-history-table :deep(.q-table tbody td) {
|
.pcd-history-table :deep(.q-table tbody td) {
|
||||||
font-size: 12px;
|
font-size: 9.5px;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
padding: 2px;
|
||||||
|
white-space: normal;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pcd-history-table :deep(.q-table__middle) {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pcd-history-table :deep(.q-table) {
|
||||||
|
table-layout: fixed;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pcd-history-table :deep(.q-table th),
|
||||||
|
.pcd-history-table :deep(.q-table td) {
|
||||||
|
min-width: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pcd-history-table :deep(.q-table th:nth-child(1)), .pcd-history-table :deep(.q-table td:nth-child(1)) { width: 5% !important; }
|
||||||
|
.pcd-history-table :deep(.q-table th:nth-child(2)), .pcd-history-table :deep(.q-table td:nth-child(2)) { width: 6% !important; }
|
||||||
|
.pcd-history-table :deep(.q-table th:nth-child(3)), .pcd-history-table :deep(.q-table td:nth-child(3)) { width: 7% !important; }
|
||||||
|
.pcd-history-table :deep(.q-table th:nth-child(4)), .pcd-history-table :deep(.q-table td:nth-child(4)) { width: 6% !important; }
|
||||||
|
.pcd-history-table :deep(.q-table th:nth-child(5)), .pcd-history-table :deep(.q-table td:nth-child(5)) { width: 8% !important; }
|
||||||
|
.pcd-history-table :deep(.q-table th:nth-child(6)), .pcd-history-table :deep(.q-table td:nth-child(6)) { width: 7% !important; }
|
||||||
|
.pcd-history-table :deep(.q-table th:nth-child(7)), .pcd-history-table :deep(.q-table td:nth-child(7)) { width: 10% !important; }
|
||||||
|
.pcd-history-table :deep(.q-table th:nth-child(8)), .pcd-history-table :deep(.q-table td:nth-child(8)) { width: 5% !important; }
|
||||||
|
.pcd-history-table :deep(.q-table th:nth-child(9)), .pcd-history-table :deep(.q-table td:nth-child(9)) { width: 7% !important; }
|
||||||
|
.pcd-history-table :deep(.q-table th:nth-child(10)), .pcd-history-table :deep(.q-table td:nth-child(10)) { width: 5% !important; }
|
||||||
|
.pcd-history-table :deep(.q-table th:nth-child(11)), .pcd-history-table :deep(.q-table td:nth-child(11)) { width: 7% !important; }
|
||||||
|
.pcd-history-table :deep(.q-table th:nth-child(12)), .pcd-history-table :deep(.q-table td:nth-child(12)) { width: 5% !important; }
|
||||||
|
.pcd-history-table :deep(.q-table th:nth-child(13)), .pcd-history-table :deep(.q-table td:nth-child(13)) { width: 3% !important; }
|
||||||
|
.pcd-history-table :deep(.q-table th:nth-child(14)), .pcd-history-table :deep(.q-table td:nth-child(14)) { width: 5% !important; }
|
||||||
|
.pcd-history-table :deep(.q-table th:nth-child(15)), .pcd-history-table :deep(.q-table td:nth-child(15)) { width: 6% !important; }
|
||||||
|
.pcd-history-table :deep(.q-table th:nth-child(16)), .pcd-history-table :deep(.q-table td:nth-child(16)) { width: 4% !important; }
|
||||||
|
.pcd-history-table :deep(.q-table th:nth-child(17)), .pcd-history-table :deep(.q-table td:nth-child(17)) { width: 4% !important; }
|
||||||
|
|
||||||
|
.pcd-history-table :deep(.pcd-history-source-chip) {
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 2px 4px;
|
||||||
|
font-size: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pcd-history-table :deep(.q-btn) {
|
||||||
|
min-width: 0;
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: 5px;
|
||||||
|
font-size: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pcd-history-table :deep(.pcd-history-row-purchase td) {
|
.pcd-history-table :deep(.pcd-history-row-purchase td) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="text-h6">Maliyet Parca Eslestirme</div>
|
<div class="text-h6">Maliyet Parca Eslestirme</div>
|
||||||
<div class="text-caption text-grey-7">
|
<div class="text-caption text-grey-7">
|
||||||
V3 Urun Ilk Grubu (42. ozellik) + Urun Ana/Alt Grup + URETIM Parca Bolum + Hammadde Turleri eslestirmesi (URETIM mk_ tablolarinda tutulur)
|
NEBIM_V3 urun gruplari + TEDARIK URETIM PROGRAMI parca bolum + hammadde turleri eslestirmesi
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,14 @@
|
|||||||
:loading="loading"
|
:loading="loading"
|
||||||
@click="fetchRows"
|
@click="fetchRows"
|
||||||
/>
|
/>
|
||||||
|
<q-btn
|
||||||
|
label="Excel'e Aktar"
|
||||||
|
icon="download"
|
||||||
|
color="primary"
|
||||||
|
outline
|
||||||
|
:disable="loading || rows.length === 0"
|
||||||
|
@click="exportVisibleRows"
|
||||||
|
/>
|
||||||
<div class="npc-missing-count text-caption text-grey-7">
|
<div class="npc-missing-count text-caption text-grey-7">
|
||||||
Maliyeti Girilmemis Satir Sayisi: <b>{{ missingCostRowCount }}</b>
|
Maliyeti Girilmemis Satir Sayisi: <b>{{ missingCostRowCount }}</b>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,26 +95,52 @@
|
|||||||
<q-menu class="npc-filter-menu" fit>
|
<q-menu class="npc-filter-menu" fit>
|
||||||
<div class="npc-filter-menu-content">
|
<div class="npc-filter-menu-content">
|
||||||
<div class="text-caption text-weight-bold q-mb-sm">{{ props.col.label }}</div>
|
<div class="text-caption text-weight-bold q-mb-sm">{{ props.col.label }}</div>
|
||||||
<q-input
|
<template v-if="isNumericColumn(props.col.name)">
|
||||||
v-model="getColumnFilter(props.col.name).text"
|
<div class="row q-col-gutter-sm">
|
||||||
dense
|
<div class="col-6">
|
||||||
outlined
|
<q-input
|
||||||
clearable
|
v-model="getColumnFilter(props.col.name).min"
|
||||||
label="Icerir"
|
dense
|
||||||
/>
|
outlined
|
||||||
<q-select
|
clearable
|
||||||
v-model="getColumnFilter(props.col.name).selected"
|
inputmode="decimal"
|
||||||
class="q-mt-sm"
|
label="Min"
|
||||||
dense
|
/>
|
||||||
outlined
|
</div>
|
||||||
multiple
|
<div class="col-6">
|
||||||
use-chips
|
<q-input
|
||||||
use-input
|
v-model="getColumnFilter(props.col.name).max"
|
||||||
emit-value
|
dense
|
||||||
map-options
|
outlined
|
||||||
:options="getColumnDistinctOptions(props.col.name)"
|
clearable
|
||||||
label="Deger Sec"
|
inputmode="decimal"
|
||||||
/>
|
label="Max"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<q-input
|
||||||
|
v-model="getColumnFilter(props.col.name).text"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
clearable
|
||||||
|
label="Icerir"
|
||||||
|
/>
|
||||||
|
<q-select
|
||||||
|
v-model="getColumnFilter(props.col.name).selected"
|
||||||
|
class="q-mt-sm"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
multiple
|
||||||
|
use-chips
|
||||||
|
use-input
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
:options="getColumnDistinctOptions(props.col.name)"
|
||||||
|
label="Deger Sec"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
<div class="row justify-end q-gutter-sm q-mt-sm">
|
<div class="row justify-end q-gutter-sm q-mt-sm">
|
||||||
<q-btn dense flat color="grey-7" label="Temizle" @click="clearColumnFilter(props.col.name)" />
|
<q-btn dense flat color="grey-7" label="Temizle" @click="clearColumnFilter(props.col.name)" />
|
||||||
</div>
|
</div>
|
||||||
@@ -150,7 +184,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
|
||||||
import { useQuasar } from 'quasar'
|
import { useQuasar } from 'quasar'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { usePermission } from 'src/composables/usePermission'
|
import { usePermission } from 'src/composables/usePermission'
|
||||||
@@ -218,6 +252,9 @@ const columns = [
|
|||||||
style: 'width:7%',
|
style: 'width:7%',
|
||||||
headerStyle: 'width:7%'
|
headerStyle: 'width:7%'
|
||||||
},
|
},
|
||||||
|
{ name: 'ceketDepoGiris', label: 'C-Ceket Depo Giris', field: 'ceketDepoGiris', align: 'right', sortable: true, format: val => formatQuantity(val), style: 'width:7%', headerStyle: 'width:7%' },
|
||||||
|
{ name: 'pantolonDepoGiris', label: 'P-Pantolon Depo Giris', field: 'pantolonDepoGiris', align: 'right', sortable: true, format: val => formatQuantity(val), style: 'width:7%', headerStyle: 'width:7%' },
|
||||||
|
{ name: 'yelekDepoGiris', label: 'Y-Yelek Depo Giris', field: 'yelekDepoGiris', align: 'right', sortable: true, format: val => formatQuantity(val), style: 'width:7%', headerStyle: 'width:7%' },
|
||||||
{
|
{
|
||||||
name: 'sMModelKodu',
|
name: 'sMModelKodu',
|
||||||
label: 'Model Kodu',
|
label: 'Model Kodu',
|
||||||
@@ -236,17 +273,66 @@ const columns = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const columnFilters = reactive({})
|
const columnFilters = reactive({})
|
||||||
|
const LIST_STATE_STORAGE_KEY = 'bssapp:production-product-costing:no-cost-list-state:v1'
|
||||||
|
|
||||||
|
function persistListState () {
|
||||||
|
try {
|
||||||
|
sessionStorage.setItem(LIST_STATE_STORAGE_KEY, JSON.stringify({
|
||||||
|
filters: {
|
||||||
|
search: String(filters.search || ''),
|
||||||
|
fromDate: String(filters.fromDate || '')
|
||||||
|
},
|
||||||
|
columnFilters: JSON.parse(JSON.stringify(columnFilters)),
|
||||||
|
scrollY: Number(window.scrollY || 0)
|
||||||
|
}))
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreListState () {
|
||||||
|
try {
|
||||||
|
const raw = sessionStorage.getItem(LIST_STATE_STORAGE_KEY)
|
||||||
|
if (!raw) return 0
|
||||||
|
const saved = JSON.parse(raw)
|
||||||
|
filters.search = String(saved?.filters?.search || '')
|
||||||
|
filters.fromDate = String(saved?.filters?.fromDate || '2025-06-01')
|
||||||
|
Object.assign(columnFilters, saved?.columnFilters && typeof saved.columnFilters === 'object' ? saved.columnFilters : {})
|
||||||
|
return Number(saved?.scrollY || 0)
|
||||||
|
} catch {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getColumnFilter (name) {
|
function getColumnFilter (name) {
|
||||||
if (!columnFilters[name]) {
|
if (!columnFilters[name]) {
|
||||||
columnFilters[name] = {
|
columnFilters[name] = {
|
||||||
text: '',
|
text: '',
|
||||||
selected: []
|
selected: [],
|
||||||
|
min: '',
|
||||||
|
max: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return columnFilters[name]
|
return columnFilters[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isNumericColumn (name) {
|
||||||
|
return ['lMMiktar_G', 'ceketDepoGiris', 'pantolonDepoGiris', 'yelekDepoGiris'].includes(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatQuantity (value) {
|
||||||
|
return Number(value || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseNumberFilter (value) {
|
||||||
|
const raw = String(value ?? '').trim()
|
||||||
|
if (!raw) return null
|
||||||
|
const normalized = raw
|
||||||
|
.replace(/\s/g, '')
|
||||||
|
.replace(/\./g, '')
|
||||||
|
.replace(',', '.')
|
||||||
|
const n = Number(normalized)
|
||||||
|
return Number.isFinite(n) ? n : null
|
||||||
|
}
|
||||||
|
|
||||||
function formatDateTR (value) {
|
function formatDateTR (value) {
|
||||||
const s = String(value || '').trim()
|
const s = String(value || '').trim()
|
||||||
if (!s) return ''
|
if (!s) return ''
|
||||||
@@ -264,10 +350,19 @@ const rows = computed(() => {
|
|||||||
const cf = getColumnFilter(col.name)
|
const cf = getColumnFilter(col.name)
|
||||||
const text = String(cf.text || '').trim().toLowerCase()
|
const text = String(cf.text || '').trim().toLowerCase()
|
||||||
const selected = Array.isArray(cf.selected) ? cf.selected : []
|
const selected = Array.isArray(cf.selected) ? cf.selected : []
|
||||||
|
const min = parseNumberFilter(cf.min)
|
||||||
|
const max = parseNumberFilter(cf.max)
|
||||||
|
|
||||||
if (!text && selected.length === 0) continue
|
if (!text && selected.length === 0 && min === null && max === null) continue
|
||||||
|
|
||||||
result = result.filter((row) => {
|
result = result.filter((row) => {
|
||||||
|
if (isNumericColumn(col.name)) {
|
||||||
|
const numericValue = Number(row?.[col.name] || 0)
|
||||||
|
if (min !== null && numericValue < min) return false
|
||||||
|
if (max !== null && numericValue > max) return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
const value = getColumnComparableValue(row, col.name)
|
const value = getColumnComparableValue(row, col.name)
|
||||||
const valueLC = value.toLowerCase()
|
const valueLC = value.toLowerCase()
|
||||||
|
|
||||||
@@ -310,13 +405,18 @@ function getColumnDistinctOptions (colName) {
|
|||||||
|
|
||||||
function isColumnFilterActive (name) {
|
function isColumnFilterActive (name) {
|
||||||
const cf = getColumnFilter(name)
|
const cf = getColumnFilter(name)
|
||||||
return !!String(cf.text || '').trim() || (Array.isArray(cf.selected) && cf.selected.length > 0)
|
return !!String(cf.text || '').trim() ||
|
||||||
|
(Array.isArray(cf.selected) && cf.selected.length > 0) ||
|
||||||
|
parseNumberFilter(cf.min) !== null ||
|
||||||
|
parseNumberFilter(cf.max) !== null
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearColumnFilter (name) {
|
function clearColumnFilter (name) {
|
||||||
const cf = getColumnFilter(name)
|
const cf = getColumnFilter(name)
|
||||||
cf.text = ''
|
cf.text = ''
|
||||||
cf.selected = []
|
cf.selected = []
|
||||||
|
cf.min = ''
|
||||||
|
cf.max = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearAllColumnFilters () {
|
function clearAllColumnFilters () {
|
||||||
@@ -327,9 +427,11 @@ function clearAllColumnFilters () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let searchTimer = null
|
let searchTimer = null
|
||||||
|
let filterWatchEnabled = false
|
||||||
watch(
|
watch(
|
||||||
() => filters.search,
|
() => filters.search,
|
||||||
() => {
|
() => {
|
||||||
|
if (!filterWatchEnabled) return
|
||||||
clearTimeout(searchTimer)
|
clearTimeout(searchTimer)
|
||||||
searchTimer = setTimeout(() => {
|
searchTimer = setTimeout(() => {
|
||||||
fetchRows()
|
fetchRows()
|
||||||
@@ -340,6 +442,7 @@ watch(
|
|||||||
watch(
|
watch(
|
||||||
() => filters.fromDate,
|
() => filters.fromDate,
|
||||||
() => {
|
() => {
|
||||||
|
if (!filterWatchEnabled) return
|
||||||
fetchRows()
|
fetchRows()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -373,6 +476,49 @@ function clearFilters () {
|
|||||||
fetchRows()
|
fetchRows()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeExcelCsvCell (value) {
|
||||||
|
let text = String(value ?? '')
|
||||||
|
// Prevent spreadsheet applications from evaluating data as a formula.
|
||||||
|
if (/^[=+\-@]/.test(text)) text = `'${text}`
|
||||||
|
return `"${text.replace(/"/g, '""')}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatExcelCsvCell (col, rawValue, displayValue) {
|
||||||
|
if (isNumericColumn(col?.name)) {
|
||||||
|
const numericValue = Number(rawValue)
|
||||||
|
if (Number.isFinite(numericValue)) return String(numericValue).replace('.', ',')
|
||||||
|
}
|
||||||
|
return escapeExcelCsvCell(displayValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportVisibleRows () {
|
||||||
|
const visibleRows = Array.isArray(rows.value) ? rows.value : []
|
||||||
|
if (visibleRows.length === 0) {
|
||||||
|
$q.notify({ type: 'warning', message: 'Excel aktarimi icin ekranda satir bulunamadi.', position: 'top-right' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportColumns = columns.filter(col => col.name !== 'open')
|
||||||
|
const csvLines = [
|
||||||
|
exportColumns.map(col => escapeExcelCsvCell(col.label)).join(';'),
|
||||||
|
...visibleRows.map(row => exportColumns.map(col => {
|
||||||
|
const rawValue = typeof col.field === 'function' ? col.field(row) : row?.[col.field]
|
||||||
|
const displayValue = typeof col.format === 'function' ? col.format(rawValue, row) : rawValue
|
||||||
|
return formatExcelCsvCell(col, rawValue, displayValue)
|
||||||
|
}).join(';'))
|
||||||
|
]
|
||||||
|
|
||||||
|
const blob = new Blob([`\uFEFF${csvLines.join('\r\n')}`], { type: 'text/csv;charset=utf-8;' })
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = url
|
||||||
|
link.download = `maliyeti_olmayan_urunler_${new Date().toISOString().slice(0, 10)}.csv`
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
link.remove()
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
function openRow (row) {
|
function openRow (row) {
|
||||||
const productCode = String(row?.sMModelKodu || '').trim()
|
const productCode = String(row?.sMModelKodu || '').trim()
|
||||||
const recipeCode = String(row?.sKodu || '').trim()
|
const recipeCode = String(row?.sKodu || '').trim()
|
||||||
@@ -392,6 +538,8 @@ function openRow (row) {
|
|||||||
recipe_code: recipeCode
|
recipe_code: recipeCode
|
||||||
})
|
})
|
||||||
|
|
||||||
|
persistListState()
|
||||||
|
|
||||||
router.push({
|
router.push({
|
||||||
name: 'production-product-costing-has-cost-detail',
|
name: 'production-product-costing-has-cost-detail',
|
||||||
query: {
|
query: {
|
||||||
@@ -403,9 +551,16 @@ function openRow (row) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
if (!canReadOrder.value) return
|
if (!canReadOrder.value) return
|
||||||
fetchRows()
|
const savedScrollY = restoreListState()
|
||||||
|
await nextTick()
|
||||||
|
filterWatchEnabled = true
|
||||||
|
await fetchRows()
|
||||||
|
if (savedScrollY > 0) {
|
||||||
|
await nextTick()
|
||||||
|
window.scrollTo({ top: savedScrollY, behavior: 'auto' })
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -207,6 +207,7 @@ import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|||||||
import { useQuasar } from 'quasar'
|
import { useQuasar } from 'quasar'
|
||||||
import { usePermission } from 'src/composables/usePermission'
|
import { usePermission } from 'src/composables/usePermission'
|
||||||
import { useTranslationStore } from 'src/stores/translationStore'
|
import { useTranslationStore } from 'src/stores/translationStore'
|
||||||
|
import { formatDataSourceLabel } from 'src/utils/formatters'
|
||||||
|
|
||||||
const $q = useQuasar()
|
const $q = useQuasar()
|
||||||
const store = useTranslationStore()
|
const store = useTranslationStore()
|
||||||
@@ -231,15 +232,10 @@ const tablePagination = ref({
|
|||||||
let filterReloadTimer = null
|
let filterReloadTimer = null
|
||||||
|
|
||||||
const sourceTypeOptions = [
|
const sourceTypeOptions = [
|
||||||
{ label: 'dummy', value: 'dummy' },
|
{ label: formatDataSourceLabel('dummy'), value: 'dummy' },
|
||||||
{ label: 'postgre', value: 'postgre' },
|
{ label: formatDataSourceLabel('postgre'), value: 'postgre' },
|
||||||
{ label: 'mssql', value: 'mssql' }
|
{ label: formatDataSourceLabel('mssql'), value: 'mssql' }
|
||||||
]
|
]
|
||||||
const sourceTypeLabelMap = {
|
|
||||||
dummy: 'UI',
|
|
||||||
postgre: 'PostgreSQL',
|
|
||||||
mssql: 'MSSQL'
|
|
||||||
}
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'actions', label: 'Güncelle', field: 'actions', align: 'left' },
|
{ name: 'actions', label: 'Güncelle', field: 'actions', align: 'left' },
|
||||||
@@ -413,7 +409,7 @@ function cellClass (key, field) {
|
|||||||
|
|
||||||
function sourceTypeLabel (key) {
|
function sourceTypeLabel (key) {
|
||||||
const val = String(rowDraft(key).source_type || 'dummy').toLowerCase()
|
const val = String(rowDraft(key).source_type || 'dummy').toLowerCase()
|
||||||
return sourceTypeLabelMap[val] || val || '-'
|
return formatDataSourceLabel(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSelected (key, checked) {
|
function toggleSelected (key, checked) {
|
||||||
|
|||||||
+24
-24
@@ -3,9 +3,8 @@
|
|||||||
v-if="canReadUser"
|
v-if="canReadUser"
|
||||||
class="q-pa-md user-sync-page"
|
class="q-pa-md user-sync-page"
|
||||||
>
|
>
|
||||||
|
|
||||||
<div class="row items-center justify-between q-mb-md">
|
<div class="row items-center justify-between q-mb-md">
|
||||||
<div class="text-h6 text-primary">👤 Kullanıcı Yönetimi</div>
|
<div class="text-h6 text-primary">Kullanici Yonetimi</div>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="canUpdateUser"
|
v-if="canUpdateUser"
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -20,11 +19,10 @@
|
|||||||
<q-separator />
|
<q-separator />
|
||||||
|
|
||||||
<div class="row q-col-gutter-md q-mt-md">
|
<div class="row q-col-gutter-md q-mt-md">
|
||||||
<!-- 🔹 PostgreSQL Kullanıcıları -->
|
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<q-card flat bordered>
|
<q-card flat bordered>
|
||||||
<q-card-section class="bg-primary text-white text-subtitle1">
|
<q-card-section class="bg-primary text-white text-subtitle1">
|
||||||
PostgreSQL Kullanıcıları
|
B2B Kullanicilari
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-table
|
<q-table
|
||||||
@@ -36,7 +34,7 @@
|
|||||||
bordered
|
bordered
|
||||||
separator="cell"
|
separator="cell"
|
||||||
>
|
>
|
||||||
<template v-slot:body-cell-sync_status="props">
|
<template #body-cell-sync_status="props">
|
||||||
<q-td :props="props">
|
<q-td :props="props">
|
||||||
<q-chip
|
<q-chip
|
||||||
:color="statusColor(props.row.sync_status)"
|
:color="statusColor(props.row.sync_status)"
|
||||||
@@ -48,23 +46,27 @@
|
|||||||
</q-td>
|
</q-td>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:body-cell-actions="props">
|
<template #body-cell-actions="props">
|
||||||
<q-td :props="props">
|
<q-td :props="props">
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="canUpdateUser"
|
v-if="canUpdateUser"
|
||||||
dense flat icon="link"
|
dense
|
||||||
|
flat
|
||||||
|
icon="link"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
@click="openMapDialog(props.row)"
|
|
||||||
:disable="store.loading || !canUpdateUser"
|
:disable="store.loading || !canUpdateUser"
|
||||||
|
@click="openMapDialog(props.row)"
|
||||||
/>
|
/>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="canUpdateUser"
|
v-if="canUpdateUser"
|
||||||
dense flat icon="link_off"
|
dense
|
||||||
|
flat
|
||||||
|
icon="link_off"
|
||||||
color="negative"
|
color="negative"
|
||||||
size="sm"
|
size="sm"
|
||||||
@click="store.unmap(props.row.id)"
|
|
||||||
:disable="store.loading || !props.row.mssql_username || !canUpdateUser"
|
:disable="store.loading || !props.row.mssql_username || !canUpdateUser"
|
||||||
|
@click="store.unmap(props.row.id)"
|
||||||
/>
|
/>
|
||||||
</q-td>
|
</q-td>
|
||||||
</template>
|
</template>
|
||||||
@@ -72,11 +74,10 @@
|
|||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 🔸 MSSQL Kullanıcıları -->
|
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<q-card flat bordered>
|
<q-card flat bordered>
|
||||||
<q-card-section class="bg-secondary text-white text-subtitle1">
|
<q-card-section class="bg-secondary text-white text-subtitle1">
|
||||||
MSSQL Kullanıcıları
|
NEBIM_V3 Kullanicilari
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-table
|
<q-table
|
||||||
@@ -88,7 +89,7 @@
|
|||||||
bordered
|
bordered
|
||||||
separator="cell"
|
separator="cell"
|
||||||
>
|
>
|
||||||
<template v-slot:body-cell-is_blocked="props">
|
<template #body-cell-is_blocked="props">
|
||||||
<q-td :props="props">
|
<q-td :props="props">
|
||||||
<q-chip
|
<q-chip
|
||||||
:color="props.row.is_blocked ? 'negative' : 'positive'"
|
:color="props.row.is_blocked ? 'negative' : 'positive'"
|
||||||
@@ -117,9 +118,9 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
import { useUserSyncStore } from 'src/stores/userSyncStore'
|
|
||||||
import { Dialog } from 'quasar'
|
import { Dialog } from 'quasar'
|
||||||
import { usePermission } from 'src/composables/usePermission'
|
import { usePermission } from 'src/composables/usePermission'
|
||||||
|
import { useUserSyncStore } from 'src/stores/userSyncStore'
|
||||||
|
|
||||||
const { canRead, canUpdate } = usePermission()
|
const { canRead, canUpdate } = usePermission()
|
||||||
const canReadUser = canRead('user')
|
const canReadUser = canRead('user')
|
||||||
@@ -132,20 +133,20 @@ const pgColumns = [
|
|||||||
{ name: 'code', label: 'Kodu', field: 'code', align: 'left' },
|
{ name: 'code', label: 'Kodu', field: 'code', align: 'left' },
|
||||||
{ name: 'full_name', label: 'Ad Soyad', field: 'full_name', align: 'left' },
|
{ name: 'full_name', label: 'Ad Soyad', field: 'full_name', align: 'left' },
|
||||||
{ name: 'email', label: 'E-posta', field: 'email', align: 'left' },
|
{ name: 'email', label: 'E-posta', field: 'email', align: 'left' },
|
||||||
{ name: 'mssql_username', label: 'MSSQL Kullanıcı', field: 'mssql_username', align: 'left' },
|
{ name: 'mssql_username', label: 'NEBIM_V3 Kullanicisi', field: 'mssql_username', align: 'left' },
|
||||||
{ name: 'sync_status', label: 'Durum', field: 'sync_status', align: 'center' },
|
{ name: 'sync_status', label: 'Durum', field: 'sync_status', align: 'center' },
|
||||||
{ name: 'actions', label: 'İşlemler', field: 'actions', align: 'center' }
|
{ name: 'actions', label: 'Islemler', field: 'actions', align: 'center' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const msColumns = [
|
const msColumns = [
|
||||||
{ name: 'username', label: 'Kullanıcı Adı', field: 'username', align: 'left' },
|
{ name: 'username', label: 'Kullanici Adi', field: 'username', align: 'left' },
|
||||||
{ name: 'first_name', label: 'Ad', field: 'first_name', align: 'left' },
|
{ name: 'first_name', label: 'Ad', field: 'first_name', align: 'left' },
|
||||||
{ name: 'last_name', label: 'Soyad', field: 'last_name', align: 'left' },
|
{ name: 'last_name', label: 'Soyad', field: 'last_name', align: 'left' },
|
||||||
{ name: 'email', label: 'E-posta', field: 'email', align: 'left' },
|
{ name: 'email', label: 'E-posta', field: 'email', align: 'left' },
|
||||||
{ name: 'is_blocked', label: 'Durum', field: 'is_blocked', align: 'center' }
|
{ name: 'is_blocked', label: 'Durum', field: 'is_blocked', align: 'center' }
|
||||||
]
|
]
|
||||||
|
|
||||||
function statusColor(status) {
|
function statusColor (status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'synced': return 'positive'
|
case 'synced': return 'positive'
|
||||||
case 'manual': return 'primary'
|
case 'manual': return 'primary'
|
||||||
@@ -155,14 +156,12 @@ function statusColor(status) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openMapDialog(pgUser) {
|
function openMapDialog (pgUser) {
|
||||||
if (!canUpdateUser.value) {
|
if (!canUpdateUser.value) return
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Dialog.create({
|
Dialog.create({
|
||||||
title: 'Kullanıcı Eşleme',
|
title: 'Kullanici Esleme',
|
||||||
message: 'Bu PostgreSQL kullanıcısını hangi MSSQL kullanıcısına bağlamak istiyorsunuz?',
|
message: 'Bu B2B kullanicisini hangi NEBIM_V3 kullanicisina baglamak istiyorsunuz?',
|
||||||
options: {
|
options: {
|
||||||
type: 'radio',
|
type: 'radio',
|
||||||
model: '',
|
model: '',
|
||||||
@@ -186,6 +185,7 @@ onMounted(() => {
|
|||||||
.user-sync-page {
|
.user-sync-page {
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.q-card-section {
|
.q-card-section {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -394,6 +394,12 @@ const routes = [
|
|||||||
component: () => import('pages/ProductPricing.vue'),
|
component: () => import('pages/ProductPricing.vue'),
|
||||||
meta: { permission: 'pricing:view' }
|
meta: { permission: 'pricing:view' }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'pricing/product-performance-profitability',
|
||||||
|
name: 'product-performance-profitability',
|
||||||
|
component: () => import('pages/ProductPerformanceProfitability.vue'),
|
||||||
|
meta: { permission: 'pricing:view' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'pricing/brand-classification',
|
path: 'pricing/brand-classification',
|
||||||
name: 'brand-classification',
|
name: 'brand-classification',
|
||||||
@@ -425,6 +431,12 @@ const routes = [
|
|||||||
component: () => import('pages/ProductSeriesMappings.vue'),
|
component: () => import('pages/ProductSeriesMappings.vue'),
|
||||||
meta: { permission: 'pricing:view' }
|
meta: { permission: 'pricing:view' }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'pricing/product-series-mappings-orphans',
|
||||||
|
name: 'product-series-mappings-orphans',
|
||||||
|
component: () => import('pages/ProductSeriesMappingsOrphans.vue'),
|
||||||
|
meta: { permission: 'pricing:view' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'pricing/product-series-definitions',
|
path: 'pricing/product-series-definitions',
|
||||||
name: 'product-series-definitions',
|
name: 'product-series-definitions',
|
||||||
|
|||||||
+31
-1
@@ -3,6 +3,7 @@ import qs from 'qs'
|
|||||||
import { useAuthStore } from 'stores/authStore'
|
import { useAuthStore } from 'stores/authStore'
|
||||||
import { DEFAULT_LOCALE, normalizeLocale } from 'src/i18n/languages'
|
import { DEFAULT_LOCALE, normalizeLocale } from 'src/i18n/languages'
|
||||||
import { slog } from 'src/utils/slog'
|
import { slog } from 'src/utils/slog'
|
||||||
|
import { formatDataSourceLabel } from 'src/utils/formatters'
|
||||||
|
|
||||||
const rawBaseUrl =
|
const rawBaseUrl =
|
||||||
(typeof process !== 'undefined' && process.env?.VITE_API_BASE_URL) || '/api'
|
(typeof process !== 'undefined' && process.env?.VITE_API_BASE_URL) || '/api'
|
||||||
@@ -63,7 +64,7 @@ function sanitizeApiErrorDetail(detail, status) {
|
|||||||
return `Unexpected HTML error response (${status || '-'})`
|
return `Unexpected HTML error response (${status || '-'})`
|
||||||
}
|
}
|
||||||
|
|
||||||
const compact = normalized.replace(/\s+/g, ' ').trim()
|
const compact = normalizeTechnicalSourceLabels(normalized.replace(/\s+/g, ' ').trim())
|
||||||
if (compact.length > 320) {
|
if (compact.length > 320) {
|
||||||
return `${compact.slice(0, 320)}...`
|
return `${compact.slice(0, 320)}...`
|
||||||
}
|
}
|
||||||
@@ -71,6 +72,17 @@ function sanitizeApiErrorDetail(detail, status) {
|
|||||||
return compact
|
return compact
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeTechnicalSourceLabels(text) {
|
||||||
|
return String(text || '')
|
||||||
|
.replace(/\bPostgreSQL\b/gi, formatDataSourceLabel('postgresql'))
|
||||||
|
.replace(/\bPostgres\b/gi, formatDataSourceLabel('postgres'))
|
||||||
|
.replace(/\bpostgre\b/gi, formatDataSourceLabel('postgre'))
|
||||||
|
.replace(/\bPG\b/g, formatDataSourceLabel('pg'))
|
||||||
|
.replace(/\bMSSQL\b/gi, formatDataSourceLabel('mssql'))
|
||||||
|
.replace(/\bBAGGI_V3\b/gi, formatDataSourceLabel('BAGGI_V3'))
|
||||||
|
.replace(/\bURETIM\b/g, formatDataSourceLabel('URETIM'))
|
||||||
|
}
|
||||||
|
|
||||||
function redirectToLogin() {
|
function redirectToLogin() {
|
||||||
if (typeof window === 'undefined') return
|
if (typeof window === 'undefined') return
|
||||||
if (window.location.hash === '#/login') return
|
if (window.location.hash === '#/login') return
|
||||||
@@ -203,6 +215,8 @@ api.interceptors.response.use(
|
|||||||
requestUrl.startsWith('/me/password')
|
requestUrl.startsWith('/me/password')
|
||||||
const isPublicRequest = isPublicPath(requestUrl)
|
const isPublicRequest = isPublicPath(requestUrl)
|
||||||
|
|
||||||
|
normalizeErrorResponsePayload(error)
|
||||||
|
|
||||||
if ((status >= 500 || hasBlob) && error) {
|
if ((status >= 500 || hasBlob) && error) {
|
||||||
const method = String(requestConfig.method || 'GET').toUpperCase()
|
const method = String(requestConfig.method || 'GET').toUpperCase()
|
||||||
const detail = sanitizeApiErrorDetail(
|
const detail = sanitizeApiErrorDetail(
|
||||||
@@ -262,6 +276,22 @@ api.interceptors.response.use(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
function normalizeErrorResponsePayload(error) {
|
||||||
|
const data = error?.response?.data
|
||||||
|
if (!data || typeof Blob !== 'undefined' && data instanceof Blob) return
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
error.response.data = normalizeTechnicalSourceLabels(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (typeof data === 'object') {
|
||||||
|
for (const key of ['detail', 'message', 'error']) {
|
||||||
|
if (typeof data[key] === 'string') {
|
||||||
|
data[key] = normalizeTechnicalSourceLabels(data[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const get = (u, p = {}, c = {}) =>
|
export const get = (u, p = {}, c = {}) =>
|
||||||
api.get(u, { params: p, ...c }).then(r => r.data)
|
api.get(u, { params: p, ...c }).then(r => r.data)
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,13 @@ const SIZE_GROUP_TITLES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const FALLBACK_SCHEMA_MAP = {
|
const FALLBACK_SCHEMA_MAP = {
|
||||||
tak: { key: 'tak', title: 'TAKIM ELBISE', values: ['44', '46', '48', '50', '52', '54', '56', '58', '60', '62', '64', '66', '68', '70', '72', '74'] }
|
tak: { key: 'tak', title: 'TAKIM ELBISE', values: ['44', '46', '48', '50', '52', '54', '56', '58', '60', '62', '64', '66', '68', '70', '72', '74'] },
|
||||||
|
ayk: { key: 'ayk', title: 'AYAKKABI', values: ['39', '40', '41', '42', '43', '44', '45'] },
|
||||||
|
ayk_garson: { key: 'ayk_garson', title: 'AYAKKABI GARSON', values: ['31', '32', '33', '34', '35', '36', '37', '38'] },
|
||||||
|
yas: { key: 'yas', title: 'YAS', values: ['2', '4', '6', '8', '10', '12', '14'] },
|
||||||
|
pan: { key: 'pan', title: 'PANTOLON', values: ['38', '40', '42', '44', '46', '48', '50', '52', '54', '56', '58', '60'] },
|
||||||
|
gom: { key: 'gom', title: 'GOMLEK', values: ['XS', 'S', 'M', 'L', 'XL', '2XL', '3XL', '4XL', '5XL', '6XL', '7XL'] },
|
||||||
|
aksbir: { key: 'aksbir', title: 'AKSESUAR', values: [' '] }
|
||||||
}
|
}
|
||||||
|
|
||||||
export const schemaByKey = { ...FALLBACK_SCHEMA_MAP }
|
export const schemaByKey = { ...FALLBACK_SCHEMA_MAP }
|
||||||
|
|||||||
@@ -0,0 +1,264 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import api from 'src/services/api'
|
||||||
|
|
||||||
|
const GROUPED_TTL_MS = 2 * 60 * 1000
|
||||||
|
const IMAGE_TTL_MS = 15 * 60 * 1000
|
||||||
|
|
||||||
|
function resolveProductImageUrl (item) {
|
||||||
|
return resolveProductImageUrls(item)[0] || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveProductImageUrls (item) {
|
||||||
|
if (!item || typeof item !== 'object') return []
|
||||||
|
const urls = []
|
||||||
|
const addURL = value => {
|
||||||
|
const url = String(value || '').trim()
|
||||||
|
if (url && !urls.includes(url)) urls.push(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const thumbURL = String(item.thumb_url || item.thumbUrl || '').trim()
|
||||||
|
addURL(thumbURL)
|
||||||
|
|
||||||
|
const fullURL = String(item.full_url || item.fullUrl || '').trim()
|
||||||
|
addURL(fullURL)
|
||||||
|
|
||||||
|
const contentURL = String(item.content_url || item.ContentURL || '').trim()
|
||||||
|
if (contentURL.startsWith('/api/')) addURL(contentURL)
|
||||||
|
else if (contentURL.startsWith('/')) addURL(`/api${contentURL}`)
|
||||||
|
return urls
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizedFilterMap (filters = {}) {
|
||||||
|
return Object.fromEntries(Object.entries(filters || {})
|
||||||
|
.map(([key, values]) => [
|
||||||
|
String(key || '').trim(),
|
||||||
|
normalizedList(values, true)
|
||||||
|
])
|
||||||
|
.filter(([key, values]) => key && values.length)
|
||||||
|
.sort((a, b) => a[0].localeCompare(b[0], 'tr')))
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizedList (value, sort = false) {
|
||||||
|
if (!Array.isArray(value)) return []
|
||||||
|
const out = value.map(x => String(x || '').trim()).filter(Boolean)
|
||||||
|
return sort ? out.sort() : out
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useProductPerformanceStore = defineStore('product-performance-store', {
|
||||||
|
state: () => ({
|
||||||
|
groupedRowsByKey: {},
|
||||||
|
groupedLoadedAtByKey: {},
|
||||||
|
groupedLoadingByKey: {},
|
||||||
|
groupedInFlightByKey: {},
|
||||||
|
groupedFilterOptionsByKey: {},
|
||||||
|
groupedFilterOptionsLoadedAtByKey: {},
|
||||||
|
groupedFilterOptionsInFlightByKey: {},
|
||||||
|
imageUrlByKey: {},
|
||||||
|
imageListByKey: {},
|
||||||
|
imageLoadedAtByKey: {}
|
||||||
|
}),
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
groupedRows: state => key => state.groupedRowsByKey[key] || [],
|
||||||
|
hasImageKey: state => key => Object.prototype.hasOwnProperty.call(state.imageListByKey, key),
|
||||||
|
imageUrl: state => key => state.imageUrlByKey[key] || '',
|
||||||
|
imageList: state => key => state.imageListByKey[key] || []
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
groupedCacheKey (params = {}) {
|
||||||
|
return JSON.stringify({
|
||||||
|
mode: String(params.mode || ''),
|
||||||
|
mainGroup: String(params.urunAnaGrubu || params.urun_ana_grubu || ''),
|
||||||
|
groupLevels: normalizedList(params.groupLevels),
|
||||||
|
expandedKeys: normalizedList(params.expandedKeys, true),
|
||||||
|
expandThroughLevel: Number(params.expandThroughLevel ?? params.expand_through_level ?? -1),
|
||||||
|
filters: normalizedFilterMap(params.filters),
|
||||||
|
sortBy: String(params.sortBy || params.sort_by || ''),
|
||||||
|
descending: params.descending !== false,
|
||||||
|
limit: Number(params.limit || 0)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
groupedFilterOptionsCacheKey (params = {}) {
|
||||||
|
return JSON.stringify({
|
||||||
|
mode: String(params.mode || ''),
|
||||||
|
mainGroup: String(params.urunAnaGrubu || params.urun_ana_grubu || ''),
|
||||||
|
groupLevels: normalizedList(params.groupLevels),
|
||||||
|
fields: normalizedList(params.fields, true),
|
||||||
|
limit: Number(params.limit || 0)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchGroupedRows (params = {}, options = {}) {
|
||||||
|
const key = this.groupedCacheKey(params)
|
||||||
|
const now = Date.now()
|
||||||
|
const loadedAt = Number(this.groupedLoadedAtByKey[key] || 0)
|
||||||
|
const cached = this.groupedRowsByKey[key]
|
||||||
|
const logPrefix = '[ProductPerformance][grouped]'
|
||||||
|
if (!options.force && Array.isArray(cached) && now - loadedAt < GROUPED_TTL_MS) {
|
||||||
|
console.info(`${logPrefix} cache hit`, {
|
||||||
|
mode: params.mode,
|
||||||
|
rows: cached.length,
|
||||||
|
ageMs: now - loadedAt
|
||||||
|
})
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
if (this.groupedInFlightByKey[key]) {
|
||||||
|
console.info(`${logPrefix} in-flight reuse`, {
|
||||||
|
mode: params.mode,
|
||||||
|
groupLevels: normalizedList(params.groupLevels),
|
||||||
|
expandedKeys: normalizedList(params.expandedKeys, true).length,
|
||||||
|
expandThroughLevel: Number(params.expandThroughLevel ?? params.expand_through_level ?? -1)
|
||||||
|
})
|
||||||
|
return this.groupedInFlightByKey[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
this.groupedLoadingByKey = { ...this.groupedLoadingByKey, [key]: true }
|
||||||
|
const startedAt = performance.now()
|
||||||
|
console.info(`${logPrefix} request start`, {
|
||||||
|
mode: params.mode,
|
||||||
|
groupLevels: normalizedList(params.groupLevels),
|
||||||
|
expandedKeys: normalizedList(params.expandedKeys, true).length,
|
||||||
|
expandThroughLevel: Number(params.expandThroughLevel ?? params.expand_through_level ?? -1),
|
||||||
|
limit: params.limit
|
||||||
|
})
|
||||||
|
const request = api.post('/pricing/product-performance/grouped', {
|
||||||
|
mode: params.mode,
|
||||||
|
urun_ana_grubu: params.urunAnaGrubu || params.urun_ana_grubu || '',
|
||||||
|
group_levels: Array.isArray(params.groupLevels) ? params.groupLevels : String(params.groupLevels || '').split(',').filter(Boolean),
|
||||||
|
expanded_keys: Array.isArray(params.expandedKeys) ? params.expandedKeys : String(params.expandedKeys || '').split(',').filter(Boolean),
|
||||||
|
expand_through_level: Number(params.expandThroughLevel ?? params.expand_through_level ?? -1),
|
||||||
|
filters: normalizedFilterMap(params.filters),
|
||||||
|
sort_by: String(params.sortBy || params.sort_by || ''),
|
||||||
|
descending: params.descending !== false,
|
||||||
|
limit: params.limit
|
||||||
|
}, {
|
||||||
|
params: {
|
||||||
|
mode: params.mode,
|
||||||
|
limit: params.limit,
|
||||||
|
urun_ana_grubu: params.urunAnaGrubu || params.urun_ana_grubu || '',
|
||||||
|
expand_through_level: Number(params.expandThroughLevel ?? params.expand_through_level ?? -1),
|
||||||
|
sort_by: String(params.sortBy || params.sort_by || ''),
|
||||||
|
descending: params.descending !== false
|
||||||
|
},
|
||||||
|
timeout: 90000
|
||||||
|
}).then(resp => {
|
||||||
|
const rows = Array.isArray(resp?.data) ? resp.data : []
|
||||||
|
this.groupedRowsByKey = { ...this.groupedRowsByKey, [key]: rows }
|
||||||
|
this.groupedLoadedAtByKey = { ...this.groupedLoadedAtByKey, [key]: Date.now() }
|
||||||
|
console.info(`${logPrefix} request done`, {
|
||||||
|
mode: params.mode,
|
||||||
|
rows: rows.length,
|
||||||
|
elapsedSec: Number(((performance.now() - startedAt) / 1000).toFixed(2))
|
||||||
|
})
|
||||||
|
return rows
|
||||||
|
}).catch(err => {
|
||||||
|
const message = err?.response?.data || err?.message || String(err || '')
|
||||||
|
console.warn(`${logPrefix} request failed: ${message}`, {
|
||||||
|
mode: params.mode,
|
||||||
|
elapsedSec: Number(((performance.now() - startedAt) / 1000).toFixed(2)),
|
||||||
|
message
|
||||||
|
})
|
||||||
|
throw err
|
||||||
|
}).finally(() => {
|
||||||
|
const nextLoading = { ...this.groupedLoadingByKey }
|
||||||
|
const nextInFlight = { ...this.groupedInFlightByKey }
|
||||||
|
delete nextLoading[key]
|
||||||
|
delete nextInFlight[key]
|
||||||
|
this.groupedLoadingByKey = nextLoading
|
||||||
|
this.groupedInFlightByKey = nextInFlight
|
||||||
|
})
|
||||||
|
|
||||||
|
this.groupedInFlightByKey = { ...this.groupedInFlightByKey, [key]: request }
|
||||||
|
return request
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchGroupedFilterOptions (params = {}, options = {}) {
|
||||||
|
const key = this.groupedFilterOptionsCacheKey(params)
|
||||||
|
const now = Date.now()
|
||||||
|
const loadedAt = Number(this.groupedFilterOptionsLoadedAtByKey[key] || 0)
|
||||||
|
const cached = this.groupedFilterOptionsByKey[key]
|
||||||
|
if (!options.force && cached && now - loadedAt < GROUPED_TTL_MS) return cached
|
||||||
|
if (this.groupedFilterOptionsInFlightByKey[key]) return this.groupedFilterOptionsInFlightByKey[key]
|
||||||
|
|
||||||
|
const request = api.post('/pricing/product-performance/grouped/filter-options', {
|
||||||
|
mode: params.mode,
|
||||||
|
urun_ana_grubu: params.urunAnaGrubu || params.urun_ana_grubu || '',
|
||||||
|
group_levels: Array.isArray(params.groupLevels) ? params.groupLevels : String(params.groupLevels || '').split(',').filter(Boolean),
|
||||||
|
fields: Array.isArray(params.fields) ? params.fields : String(params.fields || '').split(',').filter(Boolean),
|
||||||
|
limit: params.limit || 50000
|
||||||
|
}, { timeout: 60000 }).then(resp => {
|
||||||
|
const data = resp?.data && typeof resp.data === 'object' ? resp.data : {}
|
||||||
|
this.groupedFilterOptionsByKey = { ...this.groupedFilterOptionsByKey, [key]: data }
|
||||||
|
this.groupedFilterOptionsLoadedAtByKey = { ...this.groupedFilterOptionsLoadedAtByKey, [key]: Date.now() }
|
||||||
|
return data
|
||||||
|
}).finally(() => {
|
||||||
|
const nextInFlight = { ...this.groupedFilterOptionsInFlightByKey }
|
||||||
|
delete nextInFlight[key]
|
||||||
|
this.groupedFilterOptionsInFlightByKey = nextInFlight
|
||||||
|
})
|
||||||
|
|
||||||
|
this.groupedFilterOptionsInFlightByKey = { ...this.groupedFilterOptionsInFlightByKey, [key]: request }
|
||||||
|
return request
|
||||||
|
},
|
||||||
|
|
||||||
|
setImageCache (key, urls = []) {
|
||||||
|
const normalizedKey = String(key || '').trim()
|
||||||
|
if (!normalizedKey) return
|
||||||
|
const list = Array.isArray(urls) ? urls.filter(Boolean) : []
|
||||||
|
this.imageListByKey = { ...this.imageListByKey, [normalizedKey]: list }
|
||||||
|
this.imageUrlByKey = { ...this.imageUrlByKey, [normalizedKey]: list[0] || '' }
|
||||||
|
this.imageLoadedAtByKey = { ...this.imageLoadedAtByKey, [normalizedKey]: Date.now() }
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchImageBatch (items = [], options = {}) {
|
||||||
|
const now = Date.now()
|
||||||
|
const pending = []
|
||||||
|
const seen = new Set()
|
||||||
|
for (const item of items) {
|
||||||
|
const key = String(item?.key || '').trim()
|
||||||
|
if (!key || seen.has(key)) continue
|
||||||
|
seen.add(key)
|
||||||
|
const loadedAt = Number(this.imageLoadedAtByKey[key] || 0)
|
||||||
|
if (!options.force && Object.prototype.hasOwnProperty.call(this.imageListByKey, key) && now - loadedAt < IMAGE_TTL_MS) continue
|
||||||
|
pending.push({
|
||||||
|
key,
|
||||||
|
code: String(item?.code || '').trim(),
|
||||||
|
dim1: String(item?.dim1 || '').trim(),
|
||||||
|
dim3: String(item?.dim3 || '').trim()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pending.length) {
|
||||||
|
const keys = Array.from(seen.values())
|
||||||
|
return {
|
||||||
|
lists: Object.fromEntries(keys.map(key => [key, this.imageListByKey[key] || []])),
|
||||||
|
urls: Object.fromEntries(keys.map(key => [key, this.imageUrlByKey[key] || '']))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resp = await api.post('/product-images/batch', { items: pending }, { timeout: 60000 })
|
||||||
|
const returned = new Set()
|
||||||
|
const lists = {}
|
||||||
|
const urlsByKey = {}
|
||||||
|
for (const item of Array.isArray(resp?.data) ? resp.data : []) {
|
||||||
|
const key = String(item?.key || '').trim()
|
||||||
|
if (!key) continue
|
||||||
|
returned.add(key)
|
||||||
|
const urls = (Array.isArray(item.images) ? item.images : []).flatMap(resolveProductImageUrls).filter(Boolean)
|
||||||
|
this.setImageCache(key, urls)
|
||||||
|
lists[key] = urls
|
||||||
|
urlsByKey[key] = urls[0] || ''
|
||||||
|
}
|
||||||
|
for (const item of pending) {
|
||||||
|
if (returned.has(item.key)) continue
|
||||||
|
this.setImageCache(item.key, [])
|
||||||
|
lists[item.key] = []
|
||||||
|
urlsByKey[item.key] = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return { lists, urls: urlsByKey }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import api from 'src/services/api'
|
||||||
|
import { DEFAULT_LOCALE, normalizeLocale } from 'src/i18n/languages'
|
||||||
|
|
||||||
|
function normalizeRuntimeText (value) {
|
||||||
|
return String(value || '').trim().replace(/\s+/g, ' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useRuntimeTranslationStore = defineStore('runtimeTranslation', () => {
|
||||||
|
const locale = ref(DEFAULT_LOCALE)
|
||||||
|
const byKey = ref({})
|
||||||
|
const byText = ref({})
|
||||||
|
const loading = ref(false)
|
||||||
|
const loadedLocales = ref({})
|
||||||
|
const version = ref(0)
|
||||||
|
|
||||||
|
async function loadLocale (nextLocale, options = {}) {
|
||||||
|
const normalized = normalizeLocale(nextLocale)
|
||||||
|
const force = Boolean(options?.force)
|
||||||
|
locale.value = normalized
|
||||||
|
|
||||||
|
if (!force && loadedLocales.value[normalized]) {
|
||||||
|
byKey.value = loadedLocales.value[normalized].byKey
|
||||||
|
byText.value = loadedLocales.value[normalized].byText
|
||||||
|
version.value += 1
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await api.get('/language/translations/runtime', {
|
||||||
|
params: { lang: normalized },
|
||||||
|
timeout: 180000
|
||||||
|
})
|
||||||
|
const payload = res?.data || {}
|
||||||
|
const nextByKey = payload?.by_key && typeof payload.by_key === 'object' ? payload.by_key : {}
|
||||||
|
const nextByTextRaw = payload?.by_text && typeof payload.by_text === 'object' ? payload.by_text : {}
|
||||||
|
const nextByText = {}
|
||||||
|
for (const [source, translated] of Object.entries(nextByTextRaw)) {
|
||||||
|
const sourceKey = normalizeRuntimeText(source)
|
||||||
|
const display = normalizeRuntimeText(translated)
|
||||||
|
if (sourceKey && display) nextByText[sourceKey] = display
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedLocales.value = {
|
||||||
|
...loadedLocales.value,
|
||||||
|
[normalized]: {
|
||||||
|
byKey: nextByKey,
|
||||||
|
byText: nextByText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
byKey.value = nextByKey
|
||||||
|
byText.value = nextByText
|
||||||
|
version.value += 1
|
||||||
|
return true
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[runtime-i18n] dictionary load failed', normalized, err)
|
||||||
|
byKey.value = {}
|
||||||
|
byText.value = {}
|
||||||
|
version.value += 1
|
||||||
|
return false
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function translateKey (key) {
|
||||||
|
const raw = String(key || '').trim()
|
||||||
|
if (!raw) return ''
|
||||||
|
return byKey.value[raw] || raw
|
||||||
|
}
|
||||||
|
|
||||||
|
function translateText (text) {
|
||||||
|
const normalized = normalizeRuntimeText(text)
|
||||||
|
if (!normalized) return ''
|
||||||
|
return byText.value[normalized] || normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
locale,
|
||||||
|
byKey,
|
||||||
|
byText,
|
||||||
|
loading,
|
||||||
|
version,
|
||||||
|
loadLocale,
|
||||||
|
translateKey,
|
||||||
|
translateText
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -84,3 +84,13 @@ export function formatPercent(value) {
|
|||||||
const n = Number(value || 0)
|
const n = Number(value || 0)
|
||||||
return `${(n * 100).toFixed(2)}%`
|
return `${(n * 100).toFixed(2)}%`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatDataSourceLabel(value) {
|
||||||
|
const raw = String(value || '').trim()
|
||||||
|
const normalized = raw.toLowerCase().replace(/[\s-]+/g, '_')
|
||||||
|
if (['pg', 'postgre', 'postgres', 'postgresql'].includes(normalized)) return 'B2B'
|
||||||
|
if (['mssql', 'baggi_v3', 'baggi'].includes(normalized)) return 'NEBIM_V3'
|
||||||
|
if (normalized === 'uretim') return 'TEDARIK URETIM PROGRAMI'
|
||||||
|
if (normalized === 'dummy' || normalized === 'ui') return 'UI'
|
||||||
|
return raw || '-'
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user