ui: update ProductPerformanceProfitability page to enhance table views and tabs management
This commit is contained in:
@@ -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)
|
||||
}
|
||||
@@ -528,6 +528,11 @@ func refreshProductPerformanceStockChunked(ctx context.Context, pg *sql.DB, star
|
||||
return 0, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
copyStmt, err := prepareProductPerformanceStockCopy(ctx, tx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer copyStmt.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var r productPerformanceStockDaily
|
||||
@@ -545,13 +550,23 @@ func refreshProductPerformanceStockChunked(ctx context.Context, pg *sql.DB, star
|
||||
}
|
||||
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
|
||||
}
|
||||
count++
|
||||
chunkRows++
|
||||
if chunkRows >= chunkSize {
|
||||
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 {
|
||||
return count, err
|
||||
}
|
||||
@@ -560,6 +575,10 @@ func refreshProductPerformanceStockChunked(ctx context.Context, pg *sql.DB, star
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
copyStmt, err = prepareProductPerformanceStockCopy(ctx, tx)
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
chunkRows = 0
|
||||
}
|
||||
}
|
||||
@@ -568,11 +587,18 @@ func refreshProductPerformanceStockChunked(ctx context.Context, pg *sql.DB, star
|
||||
}
|
||||
if chunkRows > 0 {
|
||||
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 {
|
||||
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))
|
||||
} else {
|
||||
_ = copyStmt.Close()
|
||||
_ = tx.Rollback()
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
_, err := exec.ExecContext(ctx, `
|
||||
INSERT INTO mk_product_performance_stock_daily (
|
||||
|
||||
@@ -327,6 +327,15 @@ WITH base AS (
|
||||
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 (
|
||||
@@ -335,6 +344,7 @@ target AS (
|
||||
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
|
||||
@@ -993,12 +1003,14 @@ func collectSourceSeeds(pgDB *sql.DB, mssqlDB *sql.DB, limit int) []sourceSeed {
|
||||
return out
|
||||
}
|
||||
}
|
||||
if strings.EqualFold(strings.TrimSpace(os.Getenv("TRANSLATION_DUMMY_SEEDS_ENABLED")), "true") {
|
||||
for _, row := range collectDummySeeds(limit - len(out)) {
|
||||
appendSeed(row)
|
||||
if len(out) >= limit {
|
||||
return out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user