package queries import ( "database/sql" "fmt" ) func EnsurePricingCalcInfraTables(pg *sql.DB) error { stmts := []string{ ` CREATE TABLE IF NOT EXISTS mk_fx_rate_cache ( rate_date DATE PRIMARY KEY, usd_try NUMERIC(18,6) NOT NULL DEFAULT 0, eur_try NUMERIC(18,6) NOT NULL DEFAULT 0, usd_eur NUMERIC(18,6) NOT NULL DEFAULT 0, source_system TEXT NOT NULL DEFAULT 'MSSQL', source_updated_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() )`, `CREATE INDEX IF NOT EXISTS ix_mk_fx_rate_cache_updated_at ON mk_fx_rate_cache (updated_at DESC)`, ` CREATE TABLE IF NOT EXISTS mk_price_snapshot ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), product_code TEXT NOT NULL, pricing_parameter_id BIGINT REFERENCES mk_urunpricingprmtr(id) ON DELETE CASCADE, rule_id UUID REFERENCES mk_pricing_rule(id) ON DELETE SET NULL, strategy_code TEXT NOT NULL DEFAULT 'CORE', anchor_mode TEXT NOT NULL DEFAULT 'USD', fx_date DATE NOT NULL, cost_date DATE, base_price_try NUMERIC(18,6) NOT NULL DEFAULT 0, base_price_usd NUMERIC(18,6) NOT NULL DEFAULT 0, try1 NUMERIC(18,6) NOT NULL DEFAULT 0, try2 NUMERIC(18,6) NOT NULL DEFAULT 0, try3 NUMERIC(18,6) NOT NULL DEFAULT 0, try4 NUMERIC(18,6) NOT NULL DEFAULT 0, try5 NUMERIC(18,6) NOT NULL DEFAULT 0, try6 NUMERIC(18,6) NOT NULL DEFAULT 0, usd1 NUMERIC(18,6) NOT NULL DEFAULT 0, usd2 NUMERIC(18,6) NOT NULL DEFAULT 0, usd3 NUMERIC(18,6) NOT NULL DEFAULT 0, usd4 NUMERIC(18,6) NOT NULL DEFAULT 0, usd5 NUMERIC(18,6) NOT NULL DEFAULT 0, usd6 NUMERIC(18,6) NOT NULL DEFAULT 0, eur1 NUMERIC(18,6) NOT NULL DEFAULT 0, eur2 NUMERIC(18,6) NOT NULL DEFAULT 0, eur3 NUMERIC(18,6) NOT NULL DEFAULT 0, eur4 NUMERIC(18,6) NOT NULL DEFAULT 0, eur5 NUMERIC(18,6) NOT NULL DEFAULT 0, eur6 NUMERIC(18,6) NOT NULL DEFAULT 0, calc_hash TEXT NOT NULL DEFAULT '', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), CONSTRAINT uq_mk_price_snapshot_product_scope UNIQUE (product_code, pricing_parameter_id), CONSTRAINT ck_mk_price_snapshot_strategy_code CHECK (strategy_code IN ('CORE','PREMIUM','SARTORIAL')), CONSTRAINT ck_mk_price_snapshot_anchor_mode CHECK (anchor_mode IN ('TRY','USD')) )`, `CREATE INDEX IF NOT EXISTS ix_mk_price_snapshot_rule ON mk_price_snapshot (rule_id)`, `CREATE INDEX IF NOT EXISTS ix_mk_price_snapshot_updated_at ON mk_price_snapshot (updated_at DESC)`, ` CREATE TABLE IF NOT EXISTS mk_price_target_map_pg ( id BIGSERIAL PRIMARY KEY, currency TEXT NOT NULL, level_no SMALLINT NOT NULL, sdprcgrp_id INTEGER, description TEXT NOT NULL DEFAULT '', is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), CONSTRAINT uq_mk_price_target_map_pg UNIQUE (currency, level_no), CONSTRAINT ck_mk_price_target_map_pg_currency CHECK (currency IN ('TRY','USD','EUR')), CONSTRAINT ck_mk_price_target_map_pg_level_no CHECK (level_no BETWEEN 1 AND 6) )`, `CREATE INDEX IF NOT EXISTS ix_mk_price_target_map_pg_active ON mk_price_target_map_pg (is_active, currency, level_no)`, ` CREATE TABLE IF NOT EXISTS mk_price_target_map_nebim ( id BIGSERIAL PRIMARY KEY, currency TEXT NOT NULL, level_no SMALLINT NOT NULL, price_group_code TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), CONSTRAINT uq_mk_price_target_map_nebim UNIQUE (currency, level_no), CONSTRAINT ck_mk_price_target_map_nebim_currency CHECK (currency IN ('TRY','USD','EUR')), CONSTRAINT ck_mk_price_target_map_nebim_level_no CHECK (level_no BETWEEN 1 AND 6) )`, `CREATE INDEX IF NOT EXISTS ix_mk_price_target_map_nebim_active ON mk_price_target_map_nebim (is_active, currency, level_no)`, ` CREATE TABLE IF NOT EXISTS mk_price_recalc_queue ( id BIGSERIAL PRIMARY KEY, product_code TEXT NOT NULL, pricing_parameter_id BIGINT REFERENCES mk_urunpricingprmtr(id) ON DELETE SET NULL, reason TEXT NOT NULL DEFAULT '', status TEXT NOT NULL DEFAULT 'pending', attempts SMALLINT NOT NULL DEFAULT 0, available_at TIMESTAMPTZ NOT NULL DEFAULT now(), queued_at TIMESTAMPTZ NOT NULL DEFAULT now(), processed_at TIMESTAMPTZ, last_error TEXT NOT NULL DEFAULT '', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), CONSTRAINT ck_mk_price_recalc_queue_status CHECK (status IN ('pending','processing','done','failed')) )`, `CREATE INDEX IF NOT EXISTS ix_mk_price_recalc_queue_status ON mk_price_recalc_queue (status, available_at, queued_at)`, `CREATE UNIQUE INDEX IF NOT EXISTS uq_mk_price_recalc_queue_pending ON mk_price_recalc_queue (product_code, COALESCE(pricing_parameter_id, 0)) WHERE status IN ('pending','processing')`, ` CREATE TABLE IF NOT EXISTS mk_mmitem_dim_combo ( product_code TEXT NOT NULL, dim1 INTEGER NOT NULL, dim3 INTEGER, dim3_key INTEGER GENERATED ALWAYS AS (COALESCE(dim3, 0)) STORED, updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), CONSTRAINT pk_mk_mmitem_dim_combo PRIMARY KEY (product_code, dim1, dim3_key) )`, `CREATE INDEX IF NOT EXISTS ix_mk_mmitem_dim_combo_product ON mk_mmitem_dim_combo (product_code, updated_at DESC)`, ` CREATE TABLE IF NOT EXISTS mk_dim_token_map ( dim_column TEXT NOT NULL, -- dimval1 or dimval3 token TEXT NOT NULL, -- normalized token (e.g. "001", "82", etc.) dim_id INTEGER NOT NULL, updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), CONSTRAINT pk_mk_dim_token_map PRIMARY KEY (dim_column, token) )`, `CREATE INDEX IF NOT EXISTS ix_mk_dim_token_map_updated ON mk_dim_token_map (updated_at DESC)`, } for _, stmt := range stmts { if _, err := pg.Exec(stmt); err != nil { return err } } if err := seedPricingTargetMapRows(pg, "mk_price_target_map_pg", "sdprcgrp_id"); err != nil { return err } if err := seedPricingTargetMapRows(pg, "mk_price_target_map_nebim", "price_group_code"); err != nil { return err } // Repair invalid/missing pg target mappings after manual edits or table resets. // sdprcgrp_id is expected to be 1..6 in this installation. if _, err := pg.Exec(` UPDATE mk_price_target_map_pg SET sdprcgrp_id = level_no, updated_at = now() WHERE is_active = TRUE AND (sdprcgrp_id IS NULL OR sdprcgrp_id NOT BETWEEN 1 AND 6) `); err != nil { return err } return nil } func seedPricingTargetMapRows(pg *sql.DB, tableName string, valueColumn string) error { currencies := []string{"TRY", "USD", "EUR"} for _, currency := range currencies { for level := 1; level <= 6; level++ { stmt := fmt.Sprintf(` INSERT INTO %s (currency, level_no, %s, description, is_active, created_at, updated_at) VALUES ($1, $2, NULL, '', TRUE, now(), now()) ON CONFLICT (currency, level_no) DO NOTHING `, tableName, valueColumn) // PG targets: default sdprcgrp_id = level_no (1..6). This keeps sdprc writes valid after resets. if tableName == "mk_price_target_map_pg" && valueColumn == "sdprcgrp_id" { stmt = fmt.Sprintf(` INSERT INTO %s (currency, level_no, %s, description, is_active, created_at, updated_at) VALUES ($1, $2, $2, '', TRUE, now(), now()) ON CONFLICT (currency, level_no) DO NOTHING `, tableName, valueColumn) } if valueColumn == "price_group_code" { stmt = fmt.Sprintf(` INSERT INTO %s (currency, level_no, %s, description, is_active, created_at, updated_at) VALUES ($1, $2, '', '', TRUE, now(), now()) ON CONFLICT (currency, level_no) DO NOTHING `, tableName, valueColumn) } if _, err := pg.Exec(stmt, currency, level); err != nil { return err } } } return nil }