Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -3,6 +3,7 @@ package queries
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -15,6 +16,22 @@ import (
|
||||
// - mk_pricex: per-currency multipliers (base + 1..6).
|
||||
// - mk_priceroll: per-currency rounding steps for wholesale (1-5) and retail (6+).
|
||||
|
||||
func normalizeRetailMode(v string) string {
|
||||
v = strings.ToUpper(strings.TrimSpace(v))
|
||||
switch v {
|
||||
case "", "STEP":
|
||||
return "STEP"
|
||||
case "END_99", "END_49", "BAND_99", "BAND_49":
|
||||
return v
|
||||
default:
|
||||
return "STEP"
|
||||
}
|
||||
}
|
||||
|
||||
func NormalizeRetailModeForRoute(v string) string {
|
||||
return normalizeRetailMode(v)
|
||||
}
|
||||
|
||||
func EnsurePricingRuleTables(pg *sql.DB) error {
|
||||
stmts := []string{
|
||||
`
|
||||
@@ -32,10 +49,26 @@ CREATE TABLE IF NOT EXISTS mk_pricing_rule (
|
||||
brand_code TEXT[] NOT NULL DEFAULT '{}'::text[],
|
||||
brand_group TEXT[] NOT NULL DEFAULT '{}'::text[],
|
||||
|
||||
strategy_code TEXT NOT NULL DEFAULT 'CORE',
|
||||
anchor_mode TEXT NOT NULL DEFAULT 'USD',
|
||||
calc_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
publish_postgres BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
publish_nebim BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
)`,
|
||||
`ALTER TABLE mk_pricing_rule ADD COLUMN IF NOT EXISTS strategy_code TEXT NOT NULL DEFAULT 'CORE'`,
|
||||
`ALTER TABLE mk_pricing_rule ADD COLUMN IF NOT EXISTS anchor_mode TEXT NOT NULL DEFAULT 'USD'`,
|
||||
`ALTER TABLE mk_pricing_rule ADD COLUMN IF NOT EXISTS calc_enabled BOOLEAN NOT NULL DEFAULT TRUE`,
|
||||
`ALTER TABLE mk_pricing_rule ADD COLUMN IF NOT EXISTS publish_postgres BOOLEAN NOT NULL DEFAULT TRUE`,
|
||||
`ALTER TABLE mk_pricing_rule ADD COLUMN IF NOT EXISTS publish_nebim BOOLEAN NOT NULL DEFAULT TRUE`,
|
||||
`UPDATE mk_pricing_rule SET strategy_code='CORE' WHERE COALESCE(NULLIF(BTRIM(strategy_code), ''), '') = ''`,
|
||||
`UPDATE mk_pricing_rule SET anchor_mode='USD' WHERE COALESCE(NULLIF(BTRIM(anchor_mode), ''), '') = ''`,
|
||||
`ALTER TABLE mk_pricing_rule DROP CONSTRAINT IF EXISTS ck_mk_pricing_rule_strategy_code`,
|
||||
`ALTER TABLE mk_pricing_rule ADD CONSTRAINT ck_mk_pricing_rule_strategy_code CHECK (strategy_code IN ('CORE','PREMIUM','SARTORIAL'))`,
|
||||
`ALTER TABLE mk_pricing_rule DROP CONSTRAINT IF EXISTS ck_mk_pricing_rule_anchor_mode`,
|
||||
`ALTER TABLE mk_pricing_rule ADD CONSTRAINT ck_mk_pricing_rule_anchor_mode CHECK (anchor_mode IN ('TRY','USD'))`,
|
||||
`CREATE INDEX IF NOT EXISTS ix_mk_pricing_rule_active ON mk_pricing_rule (is_active)`,
|
||||
`
|
||||
CREATE TABLE IF NOT EXISTS mk_pricex (
|
||||
@@ -60,13 +93,16 @@ CREATE TABLE IF NOT EXISTS mk_priceroll (
|
||||
step NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
wholesale_step NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
retail_step NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
retail_mode TEXT NOT NULL DEFAULT 'STEP',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (rule_id, currency)
|
||||
)`,
|
||||
`ALTER TABLE mk_priceroll ADD COLUMN IF NOT EXISTS wholesale_step NUMERIC(18,6) NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_priceroll ADD COLUMN IF NOT EXISTS retail_step NUMERIC(18,6) NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_priceroll ADD COLUMN IF NOT EXISTS retail_mode TEXT NOT NULL DEFAULT 'STEP'`,
|
||||
`UPDATE mk_priceroll SET wholesale_step = step, retail_step = step WHERE step <> 0 AND wholesale_step = 0 AND retail_step = 0`,
|
||||
`UPDATE mk_priceroll SET retail_mode='STEP' WHERE COALESCE(NULLIF(BTRIM(retail_mode), ''), '') = ''`,
|
||||
`CREATE INDEX IF NOT EXISTS ix_mk_priceroll_currency ON mk_priceroll (currency)`,
|
||||
}
|
||||
for _, s := range stmts {
|
||||
@@ -92,7 +128,12 @@ type PricingRuleRow struct {
|
||||
BrandCode []string `json:"brand_code"`
|
||||
BrandGroupSec []string `json:"brand_group"`
|
||||
|
||||
IsActive bool `json:"is_active"`
|
||||
StrategyCode string `json:"strategy_code"`
|
||||
AnchorMode string `json:"anchor_mode"`
|
||||
CalcEnabled bool `json:"calc_enabled"`
|
||||
PublishPostgres bool `json:"publish_postgres"`
|
||||
PublishNebim bool `json:"publish_nebim"`
|
||||
IsActive bool `json:"is_active"`
|
||||
|
||||
// multipliers/rolls are per currency
|
||||
TryBase float64 `json:"try_base"`
|
||||
@@ -104,6 +145,7 @@ type PricingRuleRow struct {
|
||||
Try6 float64 `json:"try6"`
|
||||
TryWholesaleStep float64 `json:"try_wholesale_step"`
|
||||
TryRetailStep float64 `json:"try_retail_step"`
|
||||
TryRetailMode string `json:"try_retail_mode"`
|
||||
|
||||
UsdBase float64 `json:"usd_base"`
|
||||
Usd1 float64 `json:"usd1"`
|
||||
@@ -114,6 +156,7 @@ type PricingRuleRow struct {
|
||||
Usd6 float64 `json:"usd6"`
|
||||
UsdWholesaleStep float64 `json:"usd_wholesale_step"`
|
||||
UsdRetailStep float64 `json:"usd_retail_step"`
|
||||
UsdRetailMode string `json:"usd_retail_mode"`
|
||||
|
||||
EurBase float64 `json:"eur_base"`
|
||||
Eur1 float64 `json:"eur1"`
|
||||
@@ -124,6 +167,7 @@ type PricingRuleRow struct {
|
||||
Eur6 float64 `json:"eur6"`
|
||||
EurWholesaleStep float64 `json:"eur_wholesale_step"`
|
||||
EurRetailStep float64 `json:"eur_retail_step"`
|
||||
EurRetailMode string `json:"eur_retail_mode"`
|
||||
}
|
||||
|
||||
type PricingRuleSaveItem struct {
|
||||
@@ -141,7 +185,12 @@ type PricingRuleSaveItem struct {
|
||||
BrandCode []string `json:"brand_code"`
|
||||
BrandGroupSec []string `json:"brand_group"`
|
||||
|
||||
IsActive bool `json:"is_active"`
|
||||
StrategyCode string `json:"strategy_code"`
|
||||
AnchorMode string `json:"anchor_mode"`
|
||||
CalcEnabled bool `json:"calc_enabled"`
|
||||
PublishPostgres bool `json:"publish_postgres"`
|
||||
PublishNebim bool `json:"publish_nebim"`
|
||||
IsActive bool `json:"is_active"`
|
||||
|
||||
TryBase float64 `json:"try_base"`
|
||||
Try1 float64 `json:"try1"`
|
||||
@@ -152,6 +201,7 @@ type PricingRuleSaveItem struct {
|
||||
Try6 float64 `json:"try6"`
|
||||
TryWholesaleStep float64 `json:"try_wholesale_step"`
|
||||
TryRetailStep float64 `json:"try_retail_step"`
|
||||
TryRetailMode string `json:"try_retail_mode"`
|
||||
|
||||
UsdBase float64 `json:"usd_base"`
|
||||
Usd1 float64 `json:"usd1"`
|
||||
@@ -162,6 +212,7 @@ type PricingRuleSaveItem struct {
|
||||
Usd6 float64 `json:"usd6"`
|
||||
UsdWholesaleStep float64 `json:"usd_wholesale_step"`
|
||||
UsdRetailStep float64 `json:"usd_retail_step"`
|
||||
UsdRetailMode string `json:"usd_retail_mode"`
|
||||
|
||||
EurBase float64 `json:"eur_base"`
|
||||
Eur1 float64 `json:"eur1"`
|
||||
@@ -172,6 +223,174 @@ type PricingRuleSaveItem struct {
|
||||
Eur6 float64 `json:"eur6"`
|
||||
EurWholesaleStep float64 `json:"eur_wholesale_step"`
|
||||
EurRetailStep float64 `json:"eur_retail_step"`
|
||||
EurRetailMode string `json:"eur_retail_mode"`
|
||||
}
|
||||
|
||||
// BulkSavePricingRulesFast persists multipliers + rounding steps in a set-based way.
|
||||
// This is intentionally "dumb": it updates/creates a mk_pricing_rule row (latest by pricing_parameter_id)
|
||||
// and upserts mk_pricex/mk_priceroll for TRY/USD/EUR.
|
||||
func BulkSavePricingRulesFast(ctx context.Context, tx *sql.Tx, items []PricingRuleSaveItem) (int, error) {
|
||||
if len(items) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
raw, err := json.Marshal(items)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Notes:
|
||||
// - rule_id resolution:
|
||||
// 1) explicit id (if provided)
|
||||
// 2) latest rule for pricing_parameter_id (if provided)
|
||||
// 3) otherwise new UUID
|
||||
// - mk_pricing_rule has no unique constraint on pricing_parameter_id by design, so we target "latest" row.
|
||||
// - created_at uses default; updated_at is bumped on every save.
|
||||
q := `
|
||||
WITH input AS (
|
||||
SELECT *
|
||||
FROM jsonb_to_recordset($1::jsonb) AS x(
|
||||
id text,
|
||||
pricing_parameter_id bigint,
|
||||
calc_enabled boolean,
|
||||
publish_postgres boolean,
|
||||
publish_nebim boolean,
|
||||
is_active boolean,
|
||||
try_retail_mode text,
|
||||
usd_retail_mode text,
|
||||
eur_retail_mode text,
|
||||
|
||||
try_base float8, try1 float8, try2 float8, try3 float8, try4 float8, try5 float8, try6 float8,
|
||||
try_wholesale_step float8, try_retail_step float8,
|
||||
|
||||
usd_base float8, usd1 float8, usd2 float8, usd3 float8, usd4 float8, usd5 float8, usd6 float8,
|
||||
usd_wholesale_step float8, usd_retail_step float8,
|
||||
|
||||
eur_base float8, eur1 float8, eur2 float8, eur3 float8, eur4 float8, eur5 float8, eur6 float8,
|
||||
eur_wholesale_step float8, eur_retail_step float8
|
||||
)
|
||||
),
|
||||
norm AS (
|
||||
SELECT
|
||||
NULLIF(BTRIM(id), '') AS id_txt,
|
||||
COALESCE(pricing_parameter_id, 0) AS pricing_parameter_id,
|
||||
COALESCE(calc_enabled, TRUE) AS calc_enabled,
|
||||
COALESCE(publish_postgres, TRUE) AS publish_postgres,
|
||||
COALESCE(publish_nebim, TRUE) AS publish_nebim,
|
||||
COALESCE(is_active, TRUE) AS is_active,
|
||||
COALESCE(NULLIF(UPPER(BTRIM(try_retail_mode)), ''), 'STEP') AS try_retail_mode,
|
||||
COALESCE(NULLIF(UPPER(BTRIM(usd_retail_mode)), ''), 'STEP') AS usd_retail_mode,
|
||||
COALESCE(NULLIF(UPPER(BTRIM(eur_retail_mode)), ''), 'STEP') AS eur_retail_mode,
|
||||
|
||||
COALESCE(try_base, 0) AS try_base, COALESCE(try1, 0) AS try1, COALESCE(try2, 0) AS try2, COALESCE(try3, 0) AS try3, COALESCE(try4, 0) AS try4, COALESCE(try5, 0) AS try5, COALESCE(try6, 0) AS try6,
|
||||
COALESCE(try_wholesale_step, 0) AS try_wholesale_step, COALESCE(try_retail_step, 0) AS try_retail_step,
|
||||
|
||||
COALESCE(usd_base, 0) AS usd_base, COALESCE(usd1, 0) AS usd1, COALESCE(usd2, 0) AS usd2, COALESCE(usd3, 0) AS usd3, COALESCE(usd4, 0) AS usd4, COALESCE(usd5, 0) AS usd5, COALESCE(usd6, 0) AS usd6,
|
||||
COALESCE(usd_wholesale_step, 0) AS usd_wholesale_step, COALESCE(usd_retail_step, 0) AS usd_retail_step,
|
||||
|
||||
COALESCE(eur_base, 0) AS eur_base, COALESCE(eur1, 0) AS eur1, COALESCE(eur2, 0) AS eur2, COALESCE(eur3, 0) AS eur3, COALESCE(eur4, 0) AS eur4, COALESCE(eur5, 0) AS eur5, COALESCE(eur6, 0) AS eur6,
|
||||
COALESCE(eur_wholesale_step, 0) AS eur_wholesale_step, COALESCE(eur_retail_step, 0) AS eur_retail_step
|
||||
FROM input
|
||||
),
|
||||
resolved AS (
|
||||
SELECT
|
||||
COALESCE(
|
||||
NULLIF(id_txt, '')::uuid,
|
||||
latest.id,
|
||||
gen_random_uuid()
|
||||
) AS rule_id,
|
||||
pricing_parameter_id,
|
||||
calc_enabled,
|
||||
publish_postgres,
|
||||
publish_nebim,
|
||||
is_active,
|
||||
try_retail_mode,
|
||||
usd_retail_mode,
|
||||
eur_retail_mode,
|
||||
|
||||
try_base, try1, try2, try3, try4, try5, try6,
|
||||
try_wholesale_step, try_retail_step,
|
||||
usd_base, usd1, usd2, usd3, usd4, usd5, usd6,
|
||||
usd_wholesale_step, usd_retail_step,
|
||||
eur_base, eur1, eur2, eur3, eur4, eur5, eur6,
|
||||
eur_wholesale_step, eur_retail_step
|
||||
FROM norm n
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT r.id
|
||||
FROM mk_pricing_rule r
|
||||
WHERE r.pricing_parameter_id = n.pricing_parameter_id
|
||||
ORDER BY r.created_at DESC, r.updated_at DESC, r.id DESC
|
||||
LIMIT 1
|
||||
) latest ON (n.id_txt IS NULL AND n.pricing_parameter_id > 0)
|
||||
),
|
||||
upsert_rule AS (
|
||||
INSERT INTO mk_pricing_rule (
|
||||
id,
|
||||
pricing_parameter_id,
|
||||
calc_enabled,
|
||||
publish_postgres,
|
||||
publish_nebim,
|
||||
is_active,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
rule_id,
|
||||
NULLIF(pricing_parameter_id, 0),
|
||||
calc_enabled,
|
||||
publish_postgres,
|
||||
publish_nebim,
|
||||
is_active,
|
||||
now()
|
||||
FROM resolved
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
pricing_parameter_id = EXCLUDED.pricing_parameter_id,
|
||||
calc_enabled = EXCLUDED.calc_enabled,
|
||||
publish_postgres = EXCLUDED.publish_postgres,
|
||||
publish_nebim = EXCLUDED.publish_nebim,
|
||||
is_active = EXCLUDED.is_active,
|
||||
updated_at = now()
|
||||
RETURNING id
|
||||
),
|
||||
upsert_pricex AS (
|
||||
INSERT INTO mk_pricex (rule_id, currency, base_mult, m1, m2, m3, m4, m5, m6, updated_at)
|
||||
SELECT rule_id, 'TRY', try_base, try1, try2, try3, try4, try5, try6, now() FROM resolved
|
||||
UNION ALL
|
||||
SELECT rule_id, 'USD', usd_base, usd1, usd2, usd3, usd4, usd5, usd6, now() FROM resolved
|
||||
UNION ALL
|
||||
SELECT rule_id, 'EUR', eur_base, eur1, eur2, eur3, eur4, eur5, eur6, now() FROM resolved
|
||||
ON CONFLICT (rule_id, currency) DO UPDATE SET
|
||||
base_mult = EXCLUDED.base_mult,
|
||||
m1 = EXCLUDED.m1,
|
||||
m2 = EXCLUDED.m2,
|
||||
m3 = EXCLUDED.m3,
|
||||
m4 = EXCLUDED.m4,
|
||||
m5 = EXCLUDED.m5,
|
||||
m6 = EXCLUDED.m6,
|
||||
updated_at = now()
|
||||
RETURNING 1
|
||||
),
|
||||
upsert_priceroll AS (
|
||||
INSERT INTO mk_priceroll (rule_id, currency, wholesale_step, retail_step, retail_mode, updated_at)
|
||||
SELECT rule_id, 'TRY', try_wholesale_step, try_retail_step, try_retail_mode, now() FROM resolved
|
||||
UNION ALL
|
||||
SELECT rule_id, 'USD', usd_wholesale_step, usd_retail_step, usd_retail_mode, now() FROM resolved
|
||||
UNION ALL
|
||||
SELECT rule_id, 'EUR', eur_wholesale_step, eur_retail_step, eur_retail_mode, now() FROM resolved
|
||||
ON CONFLICT (rule_id, currency) DO UPDATE SET
|
||||
wholesale_step = EXCLUDED.wholesale_step,
|
||||
retail_step = EXCLUDED.retail_step,
|
||||
retail_mode = EXCLUDED.retail_mode,
|
||||
updated_at = now()
|
||||
RETURNING 1
|
||||
)
|
||||
SELECT COUNT(*)::int FROM resolved;
|
||||
`
|
||||
|
||||
var updated int
|
||||
if err := tx.QueryRowContext(ctx, q, raw).Scan(&updated); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func ListPricingRules(ctx context.Context, pg *sql.DB) ([]PricingRuleRow, error) {
|
||||
@@ -190,6 +409,11 @@ SELECT
|
||||
r.marka,
|
||||
r.brand_code,
|
||||
r.brand_group,
|
||||
r.strategy_code,
|
||||
r.anchor_mode,
|
||||
r.calc_enabled,
|
||||
r.publish_postgres,
|
||||
r.publish_nebim,
|
||||
r.is_active,
|
||||
|
||||
COALESCE(tx.base_mult, 0)::float8 AS try_base,
|
||||
@@ -201,6 +425,7 @@ SELECT
|
||||
COALESCE(tx.m6, 0)::float8 AS try6,
|
||||
COALESCE(NULLIF(tr.wholesale_step, 0), tr.step, 0)::float8 AS try_wholesale_step,
|
||||
COALESCE(NULLIF(tr.retail_step, 0), tr.step, 0)::float8 AS try_retail_step,
|
||||
COALESCE(NULLIF(BTRIM(tr.retail_mode), ''), 'STEP') AS try_retail_mode,
|
||||
|
||||
COALESCE(ux.base_mult, 0)::float8 AS usd_base,
|
||||
COALESCE(ux.m1, 0)::float8 AS usd1,
|
||||
@@ -211,6 +436,7 @@ SELECT
|
||||
COALESCE(ux.m6, 0)::float8 AS usd6,
|
||||
COALESCE(NULLIF(ur.wholesale_step, 0), ur.step, 0)::float8 AS usd_wholesale_step,
|
||||
COALESCE(NULLIF(ur.retail_step, 0), ur.step, 0)::float8 AS usd_retail_step,
|
||||
COALESCE(NULLIF(BTRIM(ur.retail_mode), ''), 'STEP') AS usd_retail_mode,
|
||||
|
||||
COALESCE(ex.base_mult, 0)::float8 AS eur_base,
|
||||
COALESCE(ex.m1, 0)::float8 AS eur1,
|
||||
@@ -220,7 +446,8 @@ SELECT
|
||||
COALESCE(ex.m5, 0)::float8 AS eur5,
|
||||
COALESCE(ex.m6, 0)::float8 AS eur6,
|
||||
COALESCE(NULLIF(er.wholesale_step, 0), er.step, 0)::float8 AS eur_wholesale_step,
|
||||
COALESCE(NULLIF(er.retail_step, 0), er.step, 0)::float8 AS eur_retail_step
|
||||
COALESCE(NULLIF(er.retail_step, 0), er.step, 0)::float8 AS eur_retail_step,
|
||||
COALESCE(NULLIF(BTRIM(er.retail_mode), ''), 'STEP') AS eur_retail_mode
|
||||
FROM mk_pricing_rule r
|
||||
LEFT JOIN mk_pricex tx ON tx.rule_id = r.id AND tx.currency='TRY'
|
||||
LEFT JOIN mk_pricex ux ON ux.rule_id = r.id AND ux.currency='USD'
|
||||
@@ -252,14 +479,22 @@ ORDER BY r.created_at DESC;
|
||||
pq.Array(&r.Marka),
|
||||
pq.Array(&r.BrandCode),
|
||||
pq.Array(&r.BrandGroupSec),
|
||||
&r.StrategyCode,
|
||||
&r.AnchorMode,
|
||||
&r.CalcEnabled,
|
||||
&r.PublishPostgres,
|
||||
&r.PublishNebim,
|
||||
&r.IsActive,
|
||||
|
||||
&r.TryBase, &r.Try1, &r.Try2, &r.Try3, &r.Try4, &r.Try5, &r.Try6, &r.TryWholesaleStep, &r.TryRetailStep,
|
||||
&r.UsdBase, &r.Usd1, &r.Usd2, &r.Usd3, &r.Usd4, &r.Usd5, &r.Usd6, &r.UsdWholesaleStep, &r.UsdRetailStep,
|
||||
&r.EurBase, &r.Eur1, &r.Eur2, &r.Eur3, &r.Eur4, &r.Eur5, &r.Eur6, &r.EurWholesaleStep, &r.EurRetailStep,
|
||||
&r.TryBase, &r.Try1, &r.Try2, &r.Try3, &r.Try4, &r.Try5, &r.Try6, &r.TryWholesaleStep, &r.TryRetailStep, &r.TryRetailMode,
|
||||
&r.UsdBase, &r.Usd1, &r.Usd2, &r.Usd3, &r.Usd4, &r.Usd5, &r.Usd6, &r.UsdWholesaleStep, &r.UsdRetailStep, &r.UsdRetailMode,
|
||||
&r.EurBase, &r.Eur1, &r.Eur2, &r.Eur3, &r.Eur4, &r.Eur5, &r.Eur6, &r.EurWholesaleStep, &r.EurRetailStep, &r.EurRetailMode,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.TryRetailMode = normalizeRetailMode(r.TryRetailMode)
|
||||
r.UsdRetailMode = normalizeRetailMode(r.UsdRetailMode)
|
||||
r.EurRetailMode = normalizeRetailMode(r.EurRetailMode)
|
||||
out = append(out, r)
|
||||
}
|
||||
return out, rows.Err()
|
||||
@@ -282,6 +517,42 @@ func normalizeTextList(in []string) []string {
|
||||
return out
|
||||
}
|
||||
|
||||
func deriveStrategyCodeFromBrandGroup(values []string) string {
|
||||
for _, value := range values {
|
||||
normalized := strings.ToUpper(strings.TrimSpace(value))
|
||||
switch normalized {
|
||||
case "CORE", "PREMIUM", "SARTORIAL":
|
||||
return normalized
|
||||
}
|
||||
}
|
||||
return "CORE"
|
||||
}
|
||||
|
||||
func deriveAnchorModeFromBrandGroup(ctx context.Context, tx *sql.Tx, values []string) string {
|
||||
for _, value := range values {
|
||||
normalized := strings.TrimSpace(value)
|
||||
if normalized == "" {
|
||||
continue
|
||||
}
|
||||
var mode string
|
||||
err := tx.QueryRowContext(ctx, `
|
||||
SELECT anchor_mode
|
||||
FROM mk_brandgrp
|
||||
WHERE UPPER(BTRIM(code)) = UPPER(BTRIM($1))
|
||||
OR UPPER(BTRIM(title)) = UPPER(BTRIM($1))
|
||||
ORDER BY id
|
||||
LIMIT 1
|
||||
`, normalized).Scan(&mode)
|
||||
if err == nil {
|
||||
mode = strings.ToUpper(strings.TrimSpace(mode))
|
||||
if mode == "TRY" || mode == "USD" {
|
||||
return mode
|
||||
}
|
||||
}
|
||||
}
|
||||
return "USD"
|
||||
}
|
||||
|
||||
// UpsertPricingRule persists rule scope + per-currency multipliers/roundings.
|
||||
// Parameter-backed worksheet saves append a new rule version so older prices
|
||||
// remain queryable. Legacy rules without a parameter id keep update behavior.
|
||||
@@ -306,6 +577,11 @@ func UpsertPricingRule(ctx context.Context, tx *sql.Tx, item PricingRuleSaveItem
|
||||
item.Marka = normalizeTextList(item.Marka)
|
||||
item.BrandCode = normalizeTextList(item.BrandCode)
|
||||
item.BrandGroupSec = normalizeTextList(item.BrandGroupSec)
|
||||
item.StrategyCode = deriveStrategyCodeFromBrandGroup(item.BrandGroupSec)
|
||||
item.AnchorMode = deriveAnchorModeFromBrandGroup(ctx, tx, item.BrandGroupSec)
|
||||
item.TryRetailMode = normalizeRetailMode(item.TryRetailMode)
|
||||
item.UsdRetailMode = normalizeRetailMode(item.UsdRetailMode)
|
||||
item.EurRetailMode = normalizeRetailMode(item.EurRetailMode)
|
||||
|
||||
id := strings.TrimSpace(item.ID)
|
||||
if item.PricingParameterID > 0 {
|
||||
@@ -317,12 +593,15 @@ func UpsertPricingRule(ctx context.Context, tx *sql.Tx, item PricingRuleSaveItem
|
||||
INSERT INTO mk_pricing_rule (
|
||||
pricing_parameter_id,
|
||||
askili_yan,kategori,urun_ilk_grubu,urun_ana_grubu,urun_alt_grubu,
|
||||
icerik,karisim,marka,brand_code,brand_group,is_active,created_at,updated_at
|
||||
icerik,karisim,marka,brand_code,brand_group,
|
||||
strategy_code,anchor_mode,calc_enabled,publish_postgres,publish_nebim,
|
||||
is_active,created_at,updated_at
|
||||
)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,now(),now())
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,now(),now())
|
||||
RETURNING id
|
||||
`, nullablePricingParameterID(item.PricingParameterID), pq.Array(item.AskiliYan), pq.Array(item.Kategori), pq.Array(item.UrunIlkGrubu), pq.Array(item.UrunAnaGrubu), pq.Array(item.UrunAltGrubu),
|
||||
pq.Array(item.Icerik), pq.Array(item.Karisim), pq.Array(item.Marka), pq.Array(item.BrandCode), pq.Array(item.BrandGroupSec),
|
||||
item.StrategyCode, item.AnchorMode, item.CalcEnabled, item.PublishPostgres, item.PublishNebim,
|
||||
item.IsActive,
|
||||
).Scan(&id); err != nil {
|
||||
return "", err
|
||||
@@ -341,13 +620,19 @@ UPDATE mk_pricing_rule SET
|
||||
marka=$10,
|
||||
brand_code=$11,
|
||||
brand_group=$12,
|
||||
is_active=$13,
|
||||
strategy_code=$13,
|
||||
anchor_mode=$14,
|
||||
calc_enabled=$15,
|
||||
publish_postgres=$16,
|
||||
publish_nebim=$17,
|
||||
is_active=$18,
|
||||
updated_at=now()
|
||||
WHERE id=$1
|
||||
`, id,
|
||||
nullablePricingParameterID(item.PricingParameterID),
|
||||
pq.Array(item.AskiliYan), pq.Array(item.Kategori), pq.Array(item.UrunIlkGrubu), pq.Array(item.UrunAnaGrubu), pq.Array(item.UrunAltGrubu),
|
||||
pq.Array(item.Icerik), pq.Array(item.Karisim), pq.Array(item.Marka), pq.Array(item.BrandCode), pq.Array(item.BrandGroupSec),
|
||||
item.StrategyCode, item.AnchorMode, item.CalcEnabled, item.PublishPostgres, item.PublishNebim,
|
||||
item.IsActive,
|
||||
); err != nil {
|
||||
return "", err
|
||||
@@ -371,41 +656,176 @@ ON CONFLICT (rule_id, currency) DO UPDATE SET
|
||||
`, id, cur, base, m1, m2, m3, m4, m5, m6)
|
||||
return err
|
||||
}
|
||||
upsertRoll := func(cur string, wholesaleStep, retailStep float64) error {
|
||||
upsertRoll := func(cur string, wholesaleStep, retailStep float64, retailMode string) error {
|
||||
_, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO mk_priceroll (rule_id, currency, step, wholesale_step, retail_step, created_at, updated_at)
|
||||
VALUES ($1,$2,$3,$4,$5,now(),now())
|
||||
INSERT INTO mk_priceroll (rule_id, currency, step, wholesale_step, retail_step, retail_mode, created_at, updated_at)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,now(),now())
|
||||
ON CONFLICT (rule_id, currency) DO UPDATE SET
|
||||
step=EXCLUDED.step,
|
||||
wholesale_step=EXCLUDED.wholesale_step,
|
||||
retail_step=EXCLUDED.retail_step,
|
||||
retail_mode=EXCLUDED.retail_mode,
|
||||
updated_at=now()
|
||||
`, id, cur, wholesaleStep, wholesaleStep, retailStep)
|
||||
`, id, cur, wholesaleStep, wholesaleStep, retailStep, retailMode)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := upsertX("TRY", item.TryBase, item.Try1, item.Try2, item.Try3, item.Try4, item.Try5, item.Try6); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := upsertRoll("TRY", item.TryWholesaleStep, item.TryRetailStep); err != nil {
|
||||
if err := upsertRoll("TRY", item.TryWholesaleStep, item.TryRetailStep, item.TryRetailMode); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := upsertX("USD", item.UsdBase, item.Usd1, item.Usd2, item.Usd3, item.Usd4, item.Usd5, item.Usd6); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := upsertRoll("USD", item.UsdWholesaleStep, item.UsdRetailStep); err != nil {
|
||||
if err := upsertRoll("USD", item.UsdWholesaleStep, item.UsdRetailStep, item.UsdRetailMode); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := upsertX("EUR", item.EurBase, item.Eur1, item.Eur2, item.Eur3, item.Eur4, item.Eur5, item.Eur6); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := upsertRoll("EUR", item.EurWholesaleStep, item.EurRetailStep); err != nil {
|
||||
if err := upsertRoll("EUR", item.EurWholesaleStep, item.EurRetailStep, item.EurRetailMode); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// UpdatePricingRuleByIDFast updates an existing rule without parameter versioning/scope fill.
|
||||
// This is the fast path for worksheet saves where rule_id is already known.
|
||||
func UpdatePricingRuleByIDFast(ctx context.Context, tx *sql.Tx, item PricingRuleSaveItem) error {
|
||||
if tx == nil {
|
||||
return fmt.Errorf("nil tx")
|
||||
}
|
||||
ruleID := strings.TrimSpace(item.ID)
|
||||
if ruleID == "" {
|
||||
return fmt.Errorf("missing rule id")
|
||||
}
|
||||
|
||||
item.TryRetailMode = normalizeRetailMode(item.TryRetailMode)
|
||||
item.UsdRetailMode = normalizeRetailMode(item.UsdRetailMode)
|
||||
item.EurRetailMode = normalizeRetailMode(item.EurRetailMode)
|
||||
|
||||
if _, err := tx.ExecContext(ctx, `
|
||||
UPDATE mk_pricing_rule SET
|
||||
calc_enabled=$2,
|
||||
publish_postgres=$3,
|
||||
publish_nebim=$4,
|
||||
is_active=$5,
|
||||
updated_at=now()
|
||||
WHERE id=$1
|
||||
`, ruleID, item.CalcEnabled, item.PublishPostgres, item.PublishNebim, item.IsActive); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
upsertX := func(cur string, base, m1, m2, m3, m4, m5, m6 float64) error {
|
||||
_, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO mk_pricex (rule_id, currency, base_mult, m1, m2, m3, m4, m5, m6, created_at, updated_at)
|
||||
VALUES (NULLIF($1,'')::uuid,$2,$3,$4,$5,$6,$7,$8,$9,now(),now())
|
||||
ON CONFLICT (rule_id, currency) DO UPDATE SET
|
||||
base_mult=EXCLUDED.base_mult,
|
||||
m1=EXCLUDED.m1,
|
||||
m2=EXCLUDED.m2,
|
||||
m3=EXCLUDED.m3,
|
||||
m4=EXCLUDED.m4,
|
||||
m5=EXCLUDED.m5,
|
||||
m6=EXCLUDED.m6,
|
||||
updated_at=now()
|
||||
`, ruleID, cur, base, m1, m2, m3, m4, m5, m6)
|
||||
return err
|
||||
}
|
||||
|
||||
upsertRoll := func(cur string, wholesaleStep, retailStep float64, retailMode string) error {
|
||||
_, err := tx.ExecContext(ctx, `
|
||||
INSERT INTO mk_priceroll (rule_id, currency, step, wholesale_step, retail_step, retail_mode, created_at, updated_at)
|
||||
VALUES (NULLIF($1,'')::uuid,$2,$3,$4,$5,$6,now(),now())
|
||||
ON CONFLICT (rule_id, currency) DO UPDATE SET
|
||||
step=EXCLUDED.step,
|
||||
wholesale_step=EXCLUDED.wholesale_step,
|
||||
retail_step=EXCLUDED.retail_step,
|
||||
retail_mode=EXCLUDED.retail_mode,
|
||||
updated_at=now()
|
||||
`, ruleID, cur, wholesaleStep, wholesaleStep, retailStep, retailMode)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := upsertX("TRY", item.TryBase, item.Try1, item.Try2, item.Try3, item.Try4, item.Try5, item.Try6); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := upsertRoll("TRY", item.TryWholesaleStep, item.TryRetailStep, item.TryRetailMode); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := upsertX("USD", item.UsdBase, item.Usd1, item.Usd2, item.Usd3, item.Usd4, item.Usd5, item.Usd6); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := upsertRoll("USD", item.UsdWholesaleStep, item.UsdRetailStep, item.UsdRetailMode); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := upsertX("EUR", item.EurBase, item.Eur1, item.Eur2, item.Eur3, item.Eur4, item.Eur5, item.Eur6); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := upsertRoll("EUR", item.EurWholesaleStep, item.EurRetailStep, item.EurRetailMode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpsertPricingRuleByParameterIDFast ensures there is a rule row for a pricing_parameter_id and
|
||||
// updates its multipliers/roundings in place. This avoids expensive parameter versioning and
|
||||
// scope fill during worksheet-style bulk saves.
|
||||
func UpsertPricingRuleByParameterIDFast(ctx context.Context, tx *sql.Tx, item PricingRuleSaveItem) (string, error) {
|
||||
if tx == nil {
|
||||
return "", fmt.Errorf("nil tx")
|
||||
}
|
||||
if item.PricingParameterID <= 0 {
|
||||
return "", fmt.Errorf("missing pricing_parameter_id")
|
||||
}
|
||||
|
||||
// Find latest rule for this parameter id (if any).
|
||||
var ruleID string
|
||||
_ = tx.QueryRowContext(ctx, `
|
||||
SELECT id::text
|
||||
FROM mk_pricing_rule
|
||||
WHERE pricing_parameter_id = $1
|
||||
ORDER BY created_at DESC, updated_at DESC, id DESC
|
||||
LIMIT 1
|
||||
FOR UPDATE
|
||||
`, item.PricingParameterID).Scan(&ruleID)
|
||||
ruleID = strings.TrimSpace(ruleID)
|
||||
|
||||
if ruleID == "" {
|
||||
// Create minimal rule row; other fields have defaults and parameter scope is read from mk_urunpricingprmtr.
|
||||
if err := tx.QueryRowContext(ctx, `
|
||||
INSERT INTO mk_pricing_rule (
|
||||
pricing_parameter_id,
|
||||
calc_enabled,
|
||||
publish_postgres,
|
||||
publish_nebim,
|
||||
is_active,
|
||||
created_at,
|
||||
updated_at
|
||||
)
|
||||
VALUES ($1,$2,$3,$4,$5,now(),now())
|
||||
RETURNING id::text
|
||||
`, item.PricingParameterID, item.CalcEnabled, item.PublishPostgres, item.PublishNebim, item.IsActive).Scan(&ruleID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
ruleID = strings.TrimSpace(ruleID)
|
||||
}
|
||||
if ruleID == "" {
|
||||
return "", fmt.Errorf("failed to resolve rule id")
|
||||
}
|
||||
|
||||
// Reuse the ID-fast updater now that we have an id.
|
||||
item.ID = ruleID
|
||||
if err := UpdatePricingRuleByIDFast(ctx, tx, item); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ruleID, nil
|
||||
}
|
||||
|
||||
func nullablePricingParameterID(id int64) any {
|
||||
if id <= 0 {
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user