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 }