Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-06-17 21:56:49 +03:00
parent e1e9d4baf1
commit e14c1c176a
34 changed files with 7402 additions and 704 deletions

View File

@@ -0,0 +1,123 @@
package main
import (
"bssapp-backend/db"
"bssapp-backend/queries"
"context"
"database/sql"
"log"
"os"
"strconv"
"strings"
"sync/atomic"
"time"
)
func startProductPricingFxDeltaScheduler(pgDB *sql.DB) {
enabled := strings.TrimSpace(strings.ToLower(os.Getenv("PRODUCT_PRICING_FX_DELTA_ENABLED")))
if enabled == "0" || enabled == "false" || enabled == "off" {
log.Println("Product pricing FX delta scheduler disabled")
return
}
if pgDB == nil {
return
}
intervalMin := 1
if raw := strings.TrimSpace(os.Getenv("PRODUCT_PRICING_FX_DELTA_INTERVAL_MIN")); raw != "" {
if parsed, err := strconv.Atoi(raw); err == nil && parsed >= 1 {
intervalMin = parsed
}
}
batchSize := 200
if raw := strings.TrimSpace(os.Getenv("PRODUCT_PRICING_FX_DELTA_BATCH_SIZE")); raw != "" {
if parsed, err := strconv.Atoi(raw); err == nil && parsed >= 10 && parsed <= 2000 {
batchSize = parsed
}
}
var running int32 = 0
runOnce := func(reason string) {
if db.PgDB == nil {
return
}
if !atomic.CompareAndSwapInt32(&running, 0, 1) {
log.Printf("[PricingFxDelta] skip (%s): already running", reason)
return
}
defer atomic.StoreInt32(&running, 0)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
defer cancel()
totalClaimed := 0
totalWritten := 0
for {
// Claim a batch.
tx, err := pgDB.BeginTx(ctx, nil)
if err != nil {
log.Printf("[PricingFxDelta] begin_tx_error (%s): %v", reason, err)
return
}
items, err := queries.ClaimPriceRecalcQueue(ctx, tx, batchSize)
if err != nil {
_ = tx.Rollback()
log.Printf("[PricingFxDelta] claim_error (%s): %v", reason, err)
return
}
if err := tx.Commit(); err != nil {
log.Printf("[PricingFxDelta] claim_commit_error (%s): %v", reason, err)
return
}
if len(items) == 0 {
break
}
totalClaimed += len(items)
codes := make([]string, 0, len(items))
for _, it := range items {
if it.ProductCode != "" {
codes = append(codes, it.ProductCode)
}
}
written, _, err := queries.PublishDerivedPricesFromAnchor(ctx, pgDB, codes, "", false)
if err != nil {
// Mark all failed.
tx2, _ := pgDB.BeginTx(ctx, nil)
if tx2 != nil {
for _, it := range items {
_ = queries.MarkPriceRecalcQueueFailed(ctx, tx2, it.ID, it.Attempts, err.Error())
}
_ = tx2.Commit()
}
log.Printf("[PricingFxDelta] publish_error (%s): claimed=%d err=%v", reason, len(items), err)
return
}
totalWritten += written
// Mark all done (even if some were skipped due to missing anchor).
tx3, _ := pgDB.BeginTx(ctx, nil)
if tx3 != nil {
for _, it := range items {
_ = queries.MarkPriceRecalcQueueDone(ctx, tx3, it.ID)
}
_ = tx3.Commit()
}
}
log.Printf("[PricingFxDelta] ok (%s): claimed=%d sdprc_written=%d interval_min=%d batch_size=%d", reason, totalClaimed, totalWritten, intervalMin, batchSize)
}
go func() {
time.Sleep(2 * time.Second)
runOnce("startup")
ticker := time.NewTicker(time.Duration(intervalMin) * time.Minute)
defer ticker.Stop()
for range ticker.C {
runOnce("scheduled")
}
}()
}