ui: update ProductPerformanceProfitability page to enhance table views and tabs management

This commit is contained in:
M_Kececi
2026-07-02 21:18:36 +03:00
parent d4e853806a
commit b5098fb94f
3 changed files with 145 additions and 5 deletions
@@ -0,0 +1,82 @@
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\n",
result.SalesRows,
result.StockRows,
result.KpiRows,
)
}
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)
}
+47 -1
View File
@@ -528,6 +528,11 @@ func refreshProductPerformanceStockChunked(ctx context.Context, pg *sql.DB, star
return 0, err return 0, err
} }
defer tx.Rollback() defer tx.Rollback()
copyStmt, err := prepareProductPerformanceStockCopy(ctx, tx)
if err != nil {
return 0, err
}
defer copyStmt.Close()
for rows.Next() { for rows.Next() {
var r productPerformanceStockDaily var r productPerformanceStockDaily
@@ -545,13 +550,23 @@ func refreshProductPerformanceStockChunked(ctx context.Context, pg *sql.DB, star
} }
continue continue
} }
if err := insertProductPerformanceStock(ctx, tx, r); err != nil { if _, err := copyStmt.ExecContext(ctx,
r.StockDate, r.ProductCode, r.ColorCode, r.YakaKodu, r.StockQty,
r.InQty, r.OutQty, r.KpiInQty, r.KpiOutQty, r.SalesMovementQty,
r.ProductionInQty, r.PurchaseInQty, r.ConsumptionOutQty, r.CountDiffQty,
); err != nil {
return count, err return count, err
} }
count++ count++
chunkRows++ chunkRows++
if chunkRows >= chunkSize { if chunkRows >= chunkSize {
log.Printf("[ProductPerformanceRefresh] stock chunk commit start inserted_rows=%d scanned_rows=%d", count, seen) log.Printf("[ProductPerformanceRefresh] stock chunk commit start inserted_rows=%d scanned_rows=%d", count, seen)
if _, err := copyStmt.ExecContext(ctx); err != nil {
return count, err
}
if err := copyStmt.Close(); err != nil {
return count, err
}
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
return count, err return count, err
} }
@@ -560,6 +575,10 @@ func refreshProductPerformanceStockChunked(ctx context.Context, pg *sql.DB, star
if err != nil { if err != nil {
return count, err return count, err
} }
copyStmt, err = prepareProductPerformanceStockCopy(ctx, tx)
if err != nil {
return count, err
}
chunkRows = 0 chunkRows = 0
} }
} }
@@ -568,11 +587,18 @@ func refreshProductPerformanceStockChunked(ctx context.Context, pg *sql.DB, star
} }
if chunkRows > 0 { if chunkRows > 0 {
log.Printf("[ProductPerformanceRefresh] stock final chunk commit start inserted_rows=%d scanned_rows=%d", count, seen) log.Printf("[ProductPerformanceRefresh] stock final chunk commit start inserted_rows=%d scanned_rows=%d", count, seen)
if _, err := copyStmt.ExecContext(ctx); err != nil {
return count, err
}
if err := copyStmt.Close(); err != nil {
return count, err
}
if err := tx.Commit(); err != nil { if err := tx.Commit(); err != nil {
return count, err return count, err
} }
log.Printf("[ProductPerformanceRefresh] stock final chunk commit done inserted_rows=%d scanned_rows=%d elapsed=%s", count, seen, time.Since(started).Round(time.Second)) log.Printf("[ProductPerformanceRefresh] stock final chunk commit done inserted_rows=%d scanned_rows=%d elapsed=%s", count, seen, time.Since(started).Round(time.Second))
} else { } else {
_ = copyStmt.Close()
_ = tx.Rollback() _ = tx.Rollback()
} }
log.Printf("[ProductPerformanceRefresh] stock refresh done inserted_rows=%d scanned_rows=%d elapsed=%s", count, seen, time.Since(started).Round(time.Second)) log.Printf("[ProductPerformanceRefresh] stock refresh done inserted_rows=%d scanned_rows=%d elapsed=%s", count, seen, time.Since(started).Round(time.Second))
@@ -583,6 +609,26 @@ type productPerformanceStockExec interface {
ExecContext(context.Context, string, ...any) (sql.Result, error) ExecContext(context.Context, string, ...any) (sql.Result, error)
} }
func prepareProductPerformanceStockCopy(ctx context.Context, tx *sql.Tx) (*sql.Stmt, error) {
return tx.PrepareContext(ctx, pq.CopyIn(
"mk_product_performance_stock_daily",
"stock_date",
"product_code",
"color_code",
"yaka_kodu",
"stock_qty",
"in_qty",
"out_qty",
"kpi_in_qty",
"kpi_out_qty",
"sales_movement_qty",
"production_in_qty",
"purchase_in_qty",
"consumption_out_qty",
"count_diff_qty",
))
}
func insertProductPerformanceStock(ctx context.Context, exec productPerformanceStockExec, r productPerformanceStockDaily) error { func insertProductPerformanceStock(ctx context.Context, exec productPerformanceStockExec, r productPerformanceStockDaily) error {
_, err := exec.ExecContext(ctx, ` _, err := exec.ExecContext(ctx, `
INSERT INTO mk_product_performance_stock_daily ( INSERT INTO mk_product_performance_stock_daily (
+16 -4
View File
@@ -327,6 +327,15 @@ WITH base AS (
COALESCE(NULLIF(source_text_tr, ''), translated_text, '') AS source_text_tr COALESCE(NULLIF(source_text_tr, ''), translated_text, '') AS source_text_tr
FROM mk_translator FROM mk_translator
WHERE COALESCE(NULLIF(source_text_tr, ''), translated_text, '') <> '' 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 ORDER BY t_key, CASE WHEN lang_code='tr' THEN 0 ELSE 1 END, updated_at DESC
), ),
target AS ( target AS (
@@ -335,6 +344,7 @@ target AS (
COALESCE(translated_text, '') AS translated_text COALESCE(translated_text, '') AS translated_text
FROM mk_translator FROM mk_translator
WHERE lang_code=$1 WHERE lang_code=$1
AND (status = 'approved' OR is_manual = true)
ORDER BY t_key, is_manual DESC, updated_at DESC ORDER BY t_key, is_manual DESC, updated_at DESC
) )
SELECT SELECT
@@ -993,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
}
} }
} }