Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-06-04 14:33:10 +03:00
parent 00626152c2
commit 7b1588d69d
11 changed files with 2065 additions and 78 deletions

View File

@@ -5,9 +5,12 @@ import (
"bssapp-backend/utils"
"database/sql"
"encoding/json"
"fmt"
"net/http"
"sort"
"strconv"
"strings"
"time"
)
// Step-1/2 scope (distinct+cascade) comes from the PostgreSQL parameter cache.
@@ -132,6 +135,28 @@ func GetPricingParameterRulesHandler(pg *sql.DB) http.HandlerFunc {
}
}
func ExportPricingRulesHandler(pg *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
traceID := utils.TraceIDFromRequest(r)
ctx := utils.ContextWithTraceID(r.Context(), traceID)
rows, err := queries.ListPricingParameterRules(ctx, pg, pricingRuleFiltersFromRequest(r))
if err != nil {
http.Error(w, "pricing parameter rules export error", http.StatusInternalServerError)
return
}
rows = filterPricingRuleExportRows(rows, r)
sortPricingRuleExportRows(rows, strings.TrimSpace(r.URL.Query().Get("sort_by")), strings.TrimSpace(r.URL.Query().Get("desc")) != "0")
filename := fmt.Sprintf("pricing_rules_all_%s.csv", time.Now().Format("2006-01-02"))
w.Header().Set("Content-Type", "text/csv; charset=utf-8")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))
_, _ = w.Write([]byte("\uFEFF"))
_, _ = w.Write([]byte(buildPricingRuleCSV(rows)))
}
}
func pricingRuleFiltersFromRequest(r *http.Request) queries.PricingRuleOptionFilters {
return queries.PricingRuleOptionFilters{
AskiliYan: splitCSV(r.URL.Query().Get("askili_yan")),
@@ -146,6 +171,266 @@ func pricingRuleFiltersFromRequest(r *http.Request) queries.PricingRuleOptionFil
}
}
func filterPricingRuleExportRows(rows []queries.PricingParameterRuleRow, r *http.Request) []queries.PricingParameterRuleRow {
rangeFilter := func(prefix string) (*float64, *float64) {
parse := func(raw string) *float64 {
raw = strings.TrimSpace(raw)
if raw == "" {
return nil
}
v, err := strconv.ParseFloat(strings.ReplaceAll(raw, ",", "."), 64)
if err != nil {
return nil
}
return &v
}
return parse(r.URL.Query().Get(prefix + "_min")), parse(r.URL.Query().Get(prefix + "_max"))
}
fields := []string{
"try_base", "try1", "try2", "try3", "try4", "try5", "try6", "try_step",
"usd_base", "usd1", "usd2", "usd3", "usd4", "usd5", "usd6", "usd_step",
"eur_base", "eur1", "eur2", "eur3", "eur4", "eur5", "eur6", "eur_step",
}
minMap := map[string]*float64{}
maxMap := map[string]*float64{}
for _, field := range fields {
minMap[field], maxMap[field] = rangeFilter(field)
}
out := make([]queries.PricingParameterRuleRow, 0, len(rows))
for _, row := range rows {
ok := true
for _, field := range fields {
value := pricingRuleNumericValue(row, field)
if minMap[field] != nil && value < *minMap[field] {
ok = false
break
}
if maxMap[field] != nil && value > *maxMap[field] {
ok = false
break
}
}
if ok {
out = append(out, row)
}
}
return out
}
func pricingRuleNumericValue(row queries.PricingParameterRuleRow, field string) float64 {
if row.Rule == nil {
return 0
}
switch field {
case "try_base":
return row.Rule.TryBase
case "try1":
return row.Rule.Try1
case "try2":
return row.Rule.Try2
case "try3":
return row.Rule.Try3
case "try4":
return row.Rule.Try4
case "try5":
return row.Rule.Try5
case "try6":
return row.Rule.Try6
case "try_step":
return row.Rule.TryStep
case "usd_base":
return row.Rule.UsdBase
case "usd1":
return row.Rule.Usd1
case "usd2":
return row.Rule.Usd2
case "usd3":
return row.Rule.Usd3
case "usd4":
return row.Rule.Usd4
case "usd5":
return row.Rule.Usd5
case "usd6":
return row.Rule.Usd6
case "usd_step":
return row.Rule.UsdStep
case "eur_base":
return row.Rule.EurBase
case "eur1":
return row.Rule.Eur1
case "eur2":
return row.Rule.Eur2
case "eur3":
return row.Rule.Eur3
case "eur4":
return row.Rule.Eur4
case "eur5":
return row.Rule.Eur5
case "eur6":
return row.Rule.Eur6
case "eur_step":
return row.Rule.EurStep
default:
return 0
}
}
func sortPricingRuleExportRows(rows []queries.PricingParameterRuleRow, sortBy string, desc bool) {
sortBy = strings.TrimSpace(sortBy)
if sortBy == "" {
return
}
sort.SliceStable(rows, func(i, j int) bool {
li, lj := rows[i], rows[j]
switch sortBy {
case "has_rule":
if desc {
return boolRank(li.HasRule) > boolRank(lj.HasRule)
}
return boolRank(li.HasRule) < boolRank(lj.HasRule)
case "is_active":
liActive, ljActive := false, false
if li.Rule != nil {
liActive = li.Rule.IsActive
}
if lj.Rule != nil {
ljActive = lj.Rule.IsActive
}
if desc {
return boolRank(liActive) > boolRank(ljActive)
}
return boolRank(liActive) < boolRank(ljActive)
case "askili_yan", "kategori", "urun_ilk_grubu", "urun_ana_grubu", "urun_alt_grubu", "icerik", "marka", "brand_code", "brand_group":
vi := pricingRuleStringValue(li, sortBy)
vj := pricingRuleStringValue(lj, sortBy)
if desc {
return strings.Compare(vi, vj) > 0
}
return strings.Compare(vi, vj) < 0
default:
vi := pricingRuleNumericValue(li, sortBy)
vj := pricingRuleNumericValue(lj, sortBy)
if desc {
return vi > vj
}
return vi < vj
}
})
}
func boolRank(v bool) int {
if v {
return 1
}
return 0
}
func pricingRuleStringValue(row queries.PricingParameterRuleRow, field string) string {
switch field {
case "askili_yan":
return row.AskiliYan
case "kategori":
return row.Kategori
case "urun_ilk_grubu":
return row.UrunIlkGrubu
case "urun_ana_grubu":
return row.UrunAnaGrubu
case "urun_alt_grubu":
return row.UrunAltGrubu
case "icerik":
return row.Icerik
case "marka":
return row.Marka
case "brand_code":
return row.BrandCode
case "brand_group":
return row.BrandGroupSec
default:
return ""
}
}
func buildPricingRuleCSV(rows []queries.PricingParameterRuleRow) string {
headers := []string{
"DURUM", "AKTIF", "ASKILI YAN", "KATEGORI", "URUN ILK GRUBU", "URUN ANA GRUBU", "URUN ALT GRUBU",
"ICERIK", "MARKA", "BRAND CODE", "MARKA GRUBU",
"TRY YUVARLAMA", "TRY TABAN", "TRY 1", "TRY 2", "TRY 3", "TRY 4", "TRY 5", "TRY 6",
"USD YUVARLAMA", "USD TABAN", "USD 1", "USD 2", "USD 3", "USD 4", "USD 5", "USD 6",
"EUR YUVARLAMA", "EUR TABAN", "EUR 1", "EUR 2", "EUR 3", "EUR 4", "EUR 5", "EUR 6",
}
var b strings.Builder
for i, h := range headers {
b.WriteString(csvEscapeValue(h))
if i == len(headers)-1 {
b.WriteString("\n")
} else {
b.WriteString(";")
}
}
for _, row := range rows {
active := "Pasif"
if row.Rule == nil || row.Rule.IsActive {
active = "Aktif"
}
values := []string{
map[bool]string{true: "Tanimli", false: "Yeni"}[row.HasRule],
active,
row.AskiliYan,
row.Kategori,
row.UrunIlkGrubu,
row.UrunAnaGrubu,
row.UrunAltGrubu,
row.Icerik,
row.Marka,
row.BrandCode,
row.BrandGroupSec,
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "try_step")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "try_base")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "try1")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "try2")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "try3")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "try4")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "try5")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "try6")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "usd_step")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "usd_base")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "usd1")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "usd2")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "usd3")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "usd4")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "usd5")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "usd6")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "eur_step")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "eur_base")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "eur1")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "eur2")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "eur3")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "eur4")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "eur5")),
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "eur6")),
}
for i, value := range values {
b.WriteString(csvEscapeValue(value))
if i == len(values)-1 {
b.WriteString("\n")
} else {
b.WriteString(";")
}
}
}
return b.String()
}
func csvEscapeValue(value string) string {
text := strings.ReplaceAll(strings.ReplaceAll(strings.TrimSpace(value), "\r", " "), "\n", " ")
if strings.Contains(text, ";") || strings.Contains(text, "\"") {
text = `"` + strings.ReplaceAll(text, `"`, `""`) + `"`
}
return text
}
func splitCSV(raw string) []string {
raw = strings.TrimSpace(raw)
if raw == "" {