Files
bssapp/svc/queries/pricing_parameters.go
2026-06-04 17:43:31 +03:00

804 lines
23 KiB
Go

package queries
import (
"context"
"crypto/md5"
"database/sql"
"encoding/hex"
"fmt"
"log"
"strings"
"time"
"github.com/lib/pq"
)
type PricingParameterSyncResult struct {
Total int `json:"total"`
Upserted int `json:"upserted"`
Deactivated int `json:"deactivated"`
}
type pricingParameterRow struct {
AskiliYan string
Kategori string
UrunIlkGrubu string
UrunAnaGrubu string
UrunAltGrubu string
Icerik string
Marka string
BrandCode string
BrandGroupSec string
}
func PricingParameterRowForImport(
askiliYan, kategori, urunIlkGrubu, urunAnaGrubu, urunAltGrubu,
icerik, marka, brandCode, brandGroupSec string,
) pricingParameterRow {
return pricingParameterRow{
AskiliYan: askiliYan,
Kategori: kategori,
UrunIlkGrubu: urunIlkGrubu,
UrunAnaGrubu: urunAnaGrubu,
UrunAltGrubu: urunAltGrubu,
Icerik: icerik,
Marka: marka,
BrandCode: brandCode,
BrandGroupSec: brandGroupSec,
}
}
type PricingParameterRuleRow struct {
PricingParameterID int64 `json:"pricing_parameter_id"`
ScopeKey string `json:"scope_key"`
AskiliYan string `json:"askili_yan"`
Kategori string `json:"kategori"`
UrunIlkGrubu string `json:"urun_ilk_grubu"`
UrunAnaGrubu string `json:"urun_ana_grubu"`
UrunAltGrubu string `json:"urun_alt_grubu"`
Icerik string `json:"icerik"`
Marka string `json:"marka"`
BrandCode string `json:"brand_code"`
BrandGroupSec string `json:"brand_group"`
HasRule bool `json:"has_rule"`
Rule *PricingRuleRow `json:"rule"`
}
// EnsurePricingParameterTables keeps the MSSQL-derived cascade cache close to
// the pricing rules. Rows are retained when they disappear from MSSQL and
// marked inactive so historical rule scopes remain understandable.
func EnsurePricingParameterTables(pg *sql.DB) error {
stmts := []string{
`
CREATE TABLE IF NOT EXISTS mk_urunpricingprmtr (
id BIGSERIAL PRIMARY KEY,
askili_yan TEXT NOT NULL DEFAULT '',
kategori TEXT NOT NULL DEFAULT '',
urun_ilk_grubu TEXT NOT NULL DEFAULT '',
urun_ana_grubu TEXT NOT NULL DEFAULT '',
urun_alt_grubu TEXT NOT NULL DEFAULT '',
icerik TEXT NOT NULL DEFAULT '',
marka TEXT NOT NULL DEFAULT '',
brand_code TEXT NOT NULL DEFAULT '',
brand_group_sec TEXT NOT NULL DEFAULT '',
is_active BOOLEAN NOT NULL DEFAULT TRUE,
first_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(),
scope_key TEXT GENERATED ALWAYS AS (
md5(askili_yan || chr(31) || kategori || chr(31) || urun_ilk_grubu ||
chr(31) || urun_ana_grubu || chr(31) || urun_alt_grubu || chr(31) ||
icerik || chr(31) || marka || chr(31) || brand_code || chr(31) ||
brand_group_sec)
) STORED
)`,
`DROP INDEX IF EXISTS ux_mk_urunpricingprmtr_scope`,
`
DO $$
BEGIN
IF EXISTS (
SELECT 1
FROM information_schema.columns
WHERE table_schema=current_schema()
AND table_name='mk_urunpricingprmtr'
AND column_name='karisim'
) THEN
DROP INDEX IF EXISTS ux_mk_urunpricingprmtr_active_scope;
DROP INDEX IF EXISTS ix_mk_urunpricingprmtr_scope_history;
ALTER TABLE mk_urunpricingprmtr DROP COLUMN IF EXISTS scope_key;
ALTER TABLE mk_urunpricingprmtr DROP COLUMN karisim;
END IF;
END $$`,
`
ALTER TABLE mk_urunpricingprmtr
ADD COLUMN IF NOT EXISTS scope_key TEXT GENERATED ALWAYS AS (
md5(askili_yan || chr(31) || kategori || chr(31) || urun_ilk_grubu ||
chr(31) || urun_ana_grubu || chr(31) || urun_alt_grubu || chr(31) ||
icerik || chr(31) || marka || chr(31) || brand_code || chr(31) ||
brand_group_sec)
) STORED`,
`
WITH ranked AS (
SELECT
id,
ROW_NUMBER() OVER (PARTITION BY scope_key ORDER BY last_seen_at DESC, id DESC) AS rn
FROM mk_urunpricingprmtr
WHERE is_active=TRUE
)
UPDATE mk_urunpricingprmtr p
SET is_active=FALSE
FROM ranked r
WHERE p.id=r.id
AND r.rn > 1`,
`CREATE UNIQUE INDEX IF NOT EXISTS ux_mk_urunpricingprmtr_active_scope ON mk_urunpricingprmtr (scope_key) WHERE is_active = TRUE`,
`CREATE INDEX IF NOT EXISTS ix_mk_urunpricingprmtr_scope_history ON mk_urunpricingprmtr (scope_key, last_seen_at DESC, id DESC)`,
`CREATE INDEX IF NOT EXISTS ix_mk_urunpricingprmtr_active ON mk_urunpricingprmtr (is_active)`,
`CREATE INDEX IF NOT EXISTS ix_mk_urunpricingprmtr_ilk_ana ON mk_urunpricingprmtr (urun_ilk_grubu, urun_ana_grubu) WHERE is_active = TRUE`,
`CREATE INDEX IF NOT EXISTS ix_mk_urunpricingprmtr_brand ON mk_urunpricingprmtr (brand_code, brand_group_sec) WHERE is_active = TRUE`,
`ALTER TABLE mk_pricing_rule ADD COLUMN IF NOT EXISTS pricing_parameter_id BIGINT REFERENCES mk_urunpricingprmtr(id) ON DELETE SET NULL`,
`DROP INDEX IF EXISTS ux_mk_pricing_rule_parameter`,
`CREATE INDEX IF NOT EXISTS ix_mk_pricing_rule_parameter_latest ON mk_pricing_rule (pricing_parameter_id, created_at DESC, updated_at DESC) WHERE pricing_parameter_id IS NOT NULL`,
}
for _, stmt := range stmts {
if _, err := pg.Exec(stmt); err != nil {
return err
}
}
return nil
}
func FillPricingRuleScopeFromParameter(ctx context.Context, tx *sql.Tx, item *PricingRuleSaveItem) error {
if item == nil || item.PricingParameterID <= 0 {
return nil
}
var p pricingParameterRow
if err := tx.QueryRowContext(ctx, `
SELECT
askili_yan, kategori, urun_ilk_grubu, urun_ana_grubu, urun_alt_grubu,
icerik, marka, brand_code, brand_group_sec
FROM mk_urunpricingprmtr
WHERE id=$1 AND is_active=TRUE
`, item.PricingParameterID).Scan(
&p.AskiliYan,
&p.Kategori,
&p.UrunIlkGrubu,
&p.UrunAnaGrubu,
&p.UrunAltGrubu,
&p.Icerik,
&p.Marka,
&p.BrandCode,
&p.BrandGroupSec,
); err != nil {
return err
}
item.AskiliYan = pricingParameterScopeValue(p.AskiliYan)
item.Kategori = pricingParameterScopeValue(p.Kategori)
item.UrunIlkGrubu = pricingParameterScopeValue(p.UrunIlkGrubu)
item.UrunAnaGrubu = pricingParameterScopeValue(p.UrunAnaGrubu)
item.UrunAltGrubu = pricingParameterScopeValue(p.UrunAltGrubu)
item.Icerik = pricingParameterScopeValue(p.Icerik)
item.Karisim = nil
item.Marka = pricingParameterScopeValue(p.Marka)
item.BrandCode = pricingParameterScopeValue(p.BrandCode)
item.BrandGroupSec = pricingParameterScopeValue(p.BrandGroupSec)
return nil
}
func VersionPricingParameterForRule(ctx context.Context, tx *sql.Tx, pricingParameterID int64) (int64, error) {
if pricingParameterID <= 0 {
return 0, nil
}
var p pricingParameterRow
var scopeKey string
if err := tx.QueryRowContext(ctx, `
SELECT
askili_yan, kategori, urun_ilk_grubu, urun_ana_grubu, urun_alt_grubu,
icerik, marka, brand_code, brand_group_sec, scope_key
FROM mk_urunpricingprmtr
WHERE id=$1
AND is_active=TRUE
`, pricingParameterID).Scan(
&p.AskiliYan,
&p.Kategori,
&p.UrunIlkGrubu,
&p.UrunAnaGrubu,
&p.UrunAltGrubu,
&p.Icerik,
&p.Marka,
&p.BrandCode,
&p.BrandGroupSec,
&scopeKey,
); err != nil {
return 0, err
}
if _, err := tx.ExecContext(ctx, `
UPDATE mk_urunpricingprmtr
SET is_active=FALSE,
last_seen_at=now()
WHERE scope_key=$1
AND is_active=TRUE
`, scopeKey); err != nil {
return 0, err
}
var newID int64
if err := tx.QueryRowContext(ctx, `
INSERT INTO mk_urunpricingprmtr (
askili_yan, kategori, urun_ilk_grubu, urun_ana_grubu, urun_alt_grubu,
icerik, marka, brand_code, brand_group_sec,
is_active, first_seen_at, last_seen_at
)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,TRUE,now(),now())
RETURNING id
`,
p.AskiliYan,
p.Kategori,
p.UrunIlkGrubu,
p.UrunAnaGrubu,
p.UrunAltGrubu,
p.Icerik,
p.Marka,
p.BrandCode,
p.BrandGroupSec,
).Scan(&newID); err != nil {
return 0, err
}
return newID, nil
}
func pricingParameterScopeValue(value string) []string {
value = strings.TrimSpace(value)
if value == "" {
return nil
}
return []string{value}
}
func EnsureActivePricingParameterByScope(ctx context.Context, tx *sql.Tx, row pricingParameterRow) (int64, bool, error) {
row.AskiliYan = strings.TrimSpace(row.AskiliYan)
row.Kategori = strings.TrimSpace(row.Kategori)
row.UrunIlkGrubu = strings.TrimSpace(row.UrunIlkGrubu)
row.UrunAnaGrubu = strings.TrimSpace(row.UrunAnaGrubu)
row.UrunAltGrubu = strings.TrimSpace(row.UrunAltGrubu)
row.Icerik = strings.TrimSpace(row.Icerik)
row.Marka = strings.TrimSpace(row.Marka)
row.BrandCode = strings.TrimSpace(row.BrandCode)
row.BrandGroupSec = strings.TrimSpace(row.BrandGroupSec)
var existingID int64
var isActive bool
err := tx.QueryRowContext(ctx, `
SELECT id, is_active
FROM mk_urunpricingprmtr
WHERE askili_yan=$1
AND kategori=$2
AND urun_ilk_grubu=$3
AND urun_ana_grubu=$4
AND urun_alt_grubu=$5
AND icerik=$6
AND marka=$7
AND brand_code=$8
AND brand_group_sec=$9
ORDER BY is_active DESC, last_seen_at DESC, id DESC
LIMIT 1
`,
row.AskiliYan,
row.Kategori,
row.UrunIlkGrubu,
row.UrunAnaGrubu,
row.UrunAltGrubu,
row.Icerik,
row.Marka,
row.BrandCode,
row.BrandGroupSec,
).Scan(&existingID, &isActive)
if err == nil {
if isActive {
return existingID, false, nil
}
} else if err != sql.ErrNoRows {
return 0, false, err
}
var newID int64
if err := tx.QueryRowContext(ctx, `
INSERT INTO mk_urunpricingprmtr (
askili_yan, kategori, urun_ilk_grubu, urun_ana_grubu, urun_alt_grubu,
icerik, marka, brand_code, brand_group_sec,
is_active, first_seen_at, last_seen_at
)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,TRUE,now(),now())
RETURNING id
`,
row.AskiliYan,
row.Kategori,
row.UrunIlkGrubu,
row.UrunAnaGrubu,
row.UrunAltGrubu,
row.Icerik,
row.Marka,
row.BrandCode,
row.BrandGroupSec,
).Scan(&newID); err != nil {
return 0, false, err
}
return newID, true, nil
}
func SyncPricingParametersFromMSSQL(ctx context.Context, mssql *sql.DB, pg *sql.DB) (PricingParameterSyncResult, error) {
out := PricingParameterSyncResult{}
startedAt := time.Now()
if mssql == nil || pg == nil {
return out, sql.ErrConnDone
}
if err := EnsurePricingRuleTables(pg); err != nil {
return out, err
}
if err := EnsurePricingParameterTables(pg); err != nil {
return out, err
}
if err := EnsureBrandClassificationTables(pg); err != nil {
return out, err
}
rows, err := mssql.QueryContext(ctx, `
SELECT DISTINCT
COALESCE(LTRIM(RTRIM(ProductAtt45Desc)), '') AS AskiliYan,
COALESCE(LTRIM(RTRIM(ProductAtt44Desc)), '') AS Kategori,
COALESCE(LTRIM(RTRIM(ProductAtt42Desc)), '') AS UrunIlkGrubu,
COALESCE(LTRIM(RTRIM(ProductAtt01Desc)), '') AS UrunAnaGrubu,
COALESCE(LTRIM(RTRIM(ProductAtt02Desc)), '') AS UrunAltGrubu,
COALESCE(LTRIM(RTRIM(ProductAtt41Desc)), '') AS Icerik,
COALESCE(LTRIM(RTRIM(ProductAtt10Desc)), '') AS Marka,
COALESCE(LTRIM(RTRIM(ProductAtt10)), '') AS BrandCode
FROM ProductFilterWithDescription('TR')
WHERE ProductAtt42 IN ('SERI', 'AKSESUAR')
AND IsBlocked = 0
AND LEN(LTRIM(RTRIM(ProductCode))) = 13;
`)
if err != nil {
return out, err
}
defer rows.Close()
src := make([]pricingParameterRow, 0, 4096)
for rows.Next() {
var item pricingParameterRow
if err := rows.Scan(
&item.AskiliYan,
&item.Kategori,
&item.UrunIlkGrubu,
&item.UrunAnaGrubu,
&item.UrunAltGrubu,
&item.Icerik,
&item.Marka,
&item.BrandCode,
); err != nil {
return out, err
}
item = trimPricingParameterRow(item)
src = append(src, item)
}
if err := rows.Err(); err != nil {
return out, err
}
out.Total = len(src)
log.Printf("Pricing parameter sync source loaded: rows=%d duration=%s", out.Total, time.Since(startedAt))
groupByBrand, err := pricingParameterBrandGroups(ctx, pg)
if err != nil {
return out, err
}
tx, err := pg.BeginTx(ctx, nil)
if err != nil {
return out, err
}
defer tx.Rollback()
if _, err := tx.ExecContext(ctx, `
CREATE TEMP TABLE tmp_urunpricingprmtr_sync (
askili_yan TEXT NOT NULL,
kategori TEXT NOT NULL,
urun_ilk_grubu TEXT NOT NULL,
urun_ana_grubu TEXT NOT NULL,
urun_alt_grubu TEXT NOT NULL,
icerik TEXT NOT NULL,
marka TEXT NOT NULL,
brand_code TEXT NOT NULL,
brand_group_sec TEXT NOT NULL,
scope_key TEXT NOT NULL PRIMARY KEY
) ON COMMIT DROP
`); err != nil {
return out, err
}
copyStmt, err := tx.PrepareContext(ctx, pq.CopyIn(
"tmp_urunpricingprmtr_sync",
"askili_yan",
"kategori",
"urun_ilk_grubu",
"urun_ana_grubu",
"urun_alt_grubu",
"icerik",
"marka",
"brand_code",
"brand_group_sec",
"scope_key",
))
if err != nil {
return out, err
}
seenScopeKeys := make(map[string]struct{}, len(src))
for _, item := range src {
item.BrandGroupSec = groupByBrand[item.BrandCode]
scopeKey := pricingParameterScopeKey(item)
if _, exists := seenScopeKeys[scopeKey]; exists {
continue
}
seenScopeKeys[scopeKey] = struct{}{}
if _, err := copyStmt.ExecContext(ctx,
item.AskiliYan,
item.Kategori,
item.UrunIlkGrubu,
item.UrunAnaGrubu,
item.UrunAltGrubu,
item.Icerik,
item.Marka,
item.BrandCode,
item.BrandGroupSec,
scopeKey,
); err != nil {
_ = copyStmt.Close()
return out, err
}
}
if _, err := copyStmt.ExecContext(ctx); err != nil {
_ = copyStmt.Close()
return out, err
}
if err := copyStmt.Close(); err != nil {
return out, err
}
out.Upserted = len(seenScopeKeys)
log.Printf("Pricing parameter sync copy loaded: rows=%d duration=%s", out.Upserted, time.Since(startedAt))
res, err := tx.ExecContext(ctx, `
UPDATE mk_urunpricingprmtr p
SET is_active=FALSE
WHERE p.is_active=TRUE
AND NOT EXISTS (
SELECT 1
FROM tmp_urunpricingprmtr_sync t
WHERE t.scope_key=p.scope_key
)
`)
if err != nil {
return out, err
}
if n, err := res.RowsAffected(); err == nil {
out.Deactivated = int(n)
}
if _, err := tx.ExecContext(ctx, `
UPDATE mk_urunpricingprmtr p
SET last_seen_at=now()
FROM tmp_urunpricingprmtr_sync t
WHERE p.scope_key=t.scope_key
AND p.is_active=TRUE
`); err != nil {
return out, err
}
insertResult, err := tx.ExecContext(ctx, `
INSERT INTO mk_urunpricingprmtr (
askili_yan, kategori, urun_ilk_grubu, urun_ana_grubu, urun_alt_grubu,
icerik, marka, brand_code, brand_group_sec,
is_active, first_seen_at, last_seen_at
)
SELECT
askili_yan, kategori, urun_ilk_grubu, urun_ana_grubu, urun_alt_grubu,
icerik, marka, brand_code, brand_group_sec,
TRUE, now(), now()
FROM tmp_urunpricingprmtr_sync t
WHERE NOT EXISTS (
SELECT 1
FROM mk_urunpricingprmtr p
WHERE p.scope_key=t.scope_key
AND p.is_active=TRUE
)
`)
if err != nil {
return out, err
}
if n, err := insertResult.RowsAffected(); err == nil {
out.Upserted = int(n)
}
if err := tx.Commit(); err != nil {
return out, err
}
log.Printf("Pricing parameter sync committed: rows=%d duration=%s", out.Upserted, time.Since(startedAt))
return out, nil
}
func pricingParameterScopeKey(item pricingParameterRow) string {
parts := []string{
item.AskiliYan,
item.Kategori,
item.UrunIlkGrubu,
item.UrunAnaGrubu,
item.UrunAltGrubu,
item.Icerik,
item.Marka,
item.BrandCode,
item.BrandGroupSec,
}
sum := md5.Sum([]byte(strings.Join(parts, string(rune(31)))))
return hex.EncodeToString(sum[:])
}
func trimPricingParameterRow(item pricingParameterRow) pricingParameterRow {
item.AskiliYan = strings.TrimSpace(item.AskiliYan)
item.Kategori = strings.TrimSpace(item.Kategori)
item.UrunIlkGrubu = strings.TrimSpace(item.UrunIlkGrubu)
item.UrunAnaGrubu = strings.TrimSpace(item.UrunAnaGrubu)
item.UrunAltGrubu = strings.TrimSpace(item.UrunAltGrubu)
item.Icerik = strings.TrimSpace(item.Icerik)
item.Marka = strings.TrimSpace(item.Marka)
item.BrandCode = strings.TrimSpace(item.BrandCode)
item.BrandGroupSec = strings.TrimSpace(item.BrandGroupSec)
return item
}
func pricingParameterBrandGroups(ctx context.Context, pg *sql.DB) (map[string]string, error) {
rows, err := pg.QueryContext(ctx, `
SELECT m.brand_code, g.title
FROM mk_brandgrpmatch m
JOIN mk_brandgrp g ON g.id = m.grp_id
`)
if err != nil {
return nil, err
}
defer rows.Close()
out := make(map[string]string, 1024)
for rows.Next() {
var code, group string
if err := rows.Scan(&code, &group); err != nil {
return nil, err
}
out[strings.TrimSpace(code)] = strings.TrimSpace(group)
}
return out, rows.Err()
}
func ListPricingParameterDistinctOptions(ctx context.Context, pg *sql.DB, field string, f PricingRuleOptionFilters, limit int) ([]string, error) {
field = strings.TrimSpace(field)
if limit <= 0 {
limit = 500
}
fieldMap := map[string]string{
"askili_yan": "askili_yan",
"kategori": "kategori",
"urun_ilk_grubu": "urun_ilk_grubu",
"urun_ana_grubu": "urun_ana_grubu",
"urun_alt_grubu": "urun_alt_grubu",
"icerik": "icerik",
"marka": "marka",
"brand_code": "brand_code",
"brand_group": "brand_group_sec",
}
target, ok := fieldMap[field]
if !ok {
return nil, fmt.Errorf("invalid field")
}
type filter struct {
Field string
Values []string
}
filters := []filter{
{"askili_yan", f.AskiliYan},
{"kategori", f.Kategori},
{"urun_ilk_grubu", f.UrunIlkGrubu},
{"urun_ana_grubu", f.UrunAnaGrubu},
{"urun_alt_grubu", f.UrunAltGrubu},
{"icerik", f.Icerik},
{"marka", f.Marka},
{"brand_code", f.BrandCode},
{"brand_group", f.BrandGroupSec},
}
args := make([]any, 0, len(filters)+1)
where := []string{"is_active=TRUE", target + " <> ''"}
for _, item := range filters {
if item.Field == field {
continue
}
values := normalizeTextList(item.Values)
if len(values) == 0 {
continue
}
args = append(args, pq.Array(values))
where = append(where, fieldMap[item.Field]+fmt.Sprintf(" = ANY($%d::text[])", len(args)))
}
args = append(args, limit)
rows, err := pg.QueryContext(ctx, `
SELECT DISTINCT `+target+`
FROM mk_urunpricingprmtr
WHERE `+strings.Join(where, " AND ")+`
ORDER BY `+target+`
LIMIT $`+fmt.Sprint(len(args))+`
`, args...)
if err != nil {
return nil, err
}
defer rows.Close()
out := make([]string, 0, limit)
for rows.Next() {
var value string
if err := rows.Scan(&value); err != nil {
return nil, err
}
value = strings.TrimSpace(value)
if value != "" {
out = append(out, value)
}
}
return out, rows.Err()
}
func ListPricingParameterRules(ctx context.Context, pg *sql.DB, f PricingRuleOptionFilters) ([]PricingParameterRuleRow, error) {
where, args := pricingParameterFilterSQL(f)
rows, err := pg.QueryContext(ctx, `
SELECT
p.id,
p.scope_key,
p.askili_yan,
p.kategori,
p.urun_ilk_grubu,
p.urun_ana_grubu,
p.urun_alt_grubu,
p.icerik,
p.marka,
p.brand_code,
p.brand_group_sec,
COALESCE(r.id::text, ''),
COALESCE(r.is_active, TRUE),
COALESCE(tx.base_mult, 0)::float8,
COALESCE(tx.m1, 0)::float8,
COALESCE(tx.m2, 0)::float8,
COALESCE(tx.m3, 0)::float8,
COALESCE(tx.m4, 0)::float8,
COALESCE(tx.m5, 0)::float8,
COALESCE(tx.m6, 0)::float8,
COALESCE(NULLIF(tr.wholesale_step, 0), tr.step, 0)::float8,
COALESCE(NULLIF(tr.retail_step, 0), tr.step, 0)::float8,
COALESCE(ux.base_mult, 0)::float8,
COALESCE(ux.m1, 0)::float8,
COALESCE(ux.m2, 0)::float8,
COALESCE(ux.m3, 0)::float8,
COALESCE(ux.m4, 0)::float8,
COALESCE(ux.m5, 0)::float8,
COALESCE(ux.m6, 0)::float8,
COALESCE(NULLIF(ur.wholesale_step, 0), ur.step, 0)::float8,
COALESCE(NULLIF(ur.retail_step, 0), ur.step, 0)::float8,
COALESCE(ex.base_mult, 0)::float8,
COALESCE(ex.m1, 0)::float8,
COALESCE(ex.m2, 0)::float8,
COALESCE(ex.m3, 0)::float8,
COALESCE(ex.m4, 0)::float8,
COALESCE(ex.m5, 0)::float8,
COALESCE(ex.m6, 0)::float8,
COALESCE(NULLIF(er.wholesale_step, 0), er.step, 0)::float8,
COALESCE(NULLIF(er.retail_step, 0), er.step, 0)::float8
FROM mk_urunpricingprmtr p
LEFT JOIN LATERAL (
SELECT latest_rule.*
FROM mk_pricing_rule latest_rule
WHERE latest_rule.pricing_parameter_id = p.id
ORDER BY latest_rule.created_at DESC, latest_rule.updated_at DESC, latest_rule.id DESC
LIMIT 1
) r ON TRUE
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'
LEFT JOIN mk_pricex ex ON ex.rule_id = r.id AND ex.currency='EUR'
LEFT JOIN mk_priceroll tr ON tr.rule_id = r.id AND tr.currency='TRY'
LEFT JOIN mk_priceroll ur ON ur.rule_id = r.id AND ur.currency='USD'
LEFT JOIN mk_priceroll er ON er.rule_id = r.id AND er.currency='EUR'
WHERE `+strings.Join(where, " AND ")+`
ORDER BY
p.urun_ilk_grubu,
p.urun_ana_grubu,
p.urun_alt_grubu,
p.marka,
p.brand_code,
p.id
`, args...)
if err != nil {
return nil, err
}
defer rows.Close()
out := make([]PricingParameterRuleRow, 0, 1024)
for rows.Next() {
var item PricingParameterRuleRow
rule := PricingRuleRow{}
if err := rows.Scan(
&item.PricingParameterID,
&item.ScopeKey,
&item.AskiliYan,
&item.Kategori,
&item.UrunIlkGrubu,
&item.UrunAnaGrubu,
&item.UrunAltGrubu,
&item.Icerik,
&item.Marka,
&item.BrandCode,
&item.BrandGroupSec,
&rule.ID,
&rule.IsActive,
&rule.TryBase, &rule.Try1, &rule.Try2, &rule.Try3, &rule.Try4, &rule.Try5, &rule.Try6, &rule.TryWholesaleStep, &rule.TryRetailStep,
&rule.UsdBase, &rule.Usd1, &rule.Usd2, &rule.Usd3, &rule.Usd4, &rule.Usd5, &rule.Usd6, &rule.UsdWholesaleStep, &rule.UsdRetailStep,
&rule.EurBase, &rule.Eur1, &rule.Eur2, &rule.Eur3, &rule.Eur4, &rule.Eur5, &rule.Eur6, &rule.EurWholesaleStep, &rule.EurRetailStep,
); err != nil {
return nil, err
}
rule.PricingParameterID = item.PricingParameterID
rule.AskiliYan = pricingParameterScopeValue(item.AskiliYan)
rule.Kategori = pricingParameterScopeValue(item.Kategori)
rule.UrunIlkGrubu = pricingParameterScopeValue(item.UrunIlkGrubu)
rule.UrunAnaGrubu = pricingParameterScopeValue(item.UrunAnaGrubu)
rule.UrunAltGrubu = pricingParameterScopeValue(item.UrunAltGrubu)
rule.Icerik = pricingParameterScopeValue(item.Icerik)
rule.Karisim = nil
rule.Marka = pricingParameterScopeValue(item.Marka)
rule.BrandCode = pricingParameterScopeValue(item.BrandCode)
rule.BrandGroupSec = pricingParameterScopeValue(item.BrandGroupSec)
item.HasRule = strings.TrimSpace(rule.ID) != ""
if item.HasRule {
item.Rule = &rule
}
out = append(out, item)
}
return out, rows.Err()
}
func pricingParameterFilterSQL(f PricingRuleOptionFilters) ([]string, []any) {
type filter struct {
Column string
Values []string
}
filters := []filter{
{"p.askili_yan", f.AskiliYan},
{"p.kategori", f.Kategori},
{"p.urun_ilk_grubu", f.UrunIlkGrubu},
{"p.urun_ana_grubu", f.UrunAnaGrubu},
{"p.urun_alt_grubu", f.UrunAltGrubu},
{"p.icerik", f.Icerik},
{"p.marka", f.Marka},
{"p.brand_code", f.BrandCode},
{"p.brand_group_sec", f.BrandGroupSec},
}
where := []string{"p.is_active=TRUE"}
args := make([]any, 0, len(filters))
for _, item := range filters {
values := normalizeTextList(item.Values)
if len(values) == 0 {
continue
}
args = append(args, pq.Array(values))
where = append(where, item.Column+fmt.Sprintf(" = ANY($%d::text[])", len(args)))
}
return where, args
}