628 lines
19 KiB
Go
628 lines
19 KiB
Go
package routes
|
|
|
|
import (
|
|
"bssapp-backend/queries"
|
|
"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.
|
|
// For now we implement:
|
|
// - Postgres tables (bootstrap)
|
|
// - List/Save rules (bulk)
|
|
// - Options endpoint for cascade (mk_urunpricingprmtr)
|
|
|
|
type PricingRuleBulkSavePayload struct {
|
|
Items []queries.PricingRuleSaveItem `json:"items"`
|
|
}
|
|
|
|
type PricingRuleImportItem struct {
|
|
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"`
|
|
IsActive bool `json:"is_active"`
|
|
TryBase float64 `json:"try_base"`
|
|
Try1 float64 `json:"try1"`
|
|
Try2 float64 `json:"try2"`
|
|
Try3 float64 `json:"try3"`
|
|
Try4 float64 `json:"try4"`
|
|
Try5 float64 `json:"try5"`
|
|
Try6 float64 `json:"try6"`
|
|
TryWholesaleStep float64 `json:"try_wholesale_step"`
|
|
TryRetailStep float64 `json:"try_retail_step"`
|
|
UsdBase float64 `json:"usd_base"`
|
|
Usd1 float64 `json:"usd1"`
|
|
Usd2 float64 `json:"usd2"`
|
|
Usd3 float64 `json:"usd3"`
|
|
Usd4 float64 `json:"usd4"`
|
|
Usd5 float64 `json:"usd5"`
|
|
Usd6 float64 `json:"usd6"`
|
|
UsdWholesaleStep float64 `json:"usd_wholesale_step"`
|
|
UsdRetailStep float64 `json:"usd_retail_step"`
|
|
EurBase float64 `json:"eur_base"`
|
|
Eur1 float64 `json:"eur1"`
|
|
Eur2 float64 `json:"eur2"`
|
|
Eur3 float64 `json:"eur3"`
|
|
Eur4 float64 `json:"eur4"`
|
|
Eur5 float64 `json:"eur5"`
|
|
Eur6 float64 `json:"eur6"`
|
|
EurWholesaleStep float64 `json:"eur_wholesale_step"`
|
|
EurRetailStep float64 `json:"eur_retail_step"`
|
|
}
|
|
|
|
type PricingRuleImportPayload struct {
|
|
Items []PricingRuleImportItem `json:"items"`
|
|
}
|
|
|
|
type PricingRuleImportResult struct {
|
|
Success bool `json:"success"`
|
|
Processed int `json:"processed"`
|
|
Matched int `json:"matched"`
|
|
Skipped int `json:"skipped"`
|
|
Updated int `json:"updated"`
|
|
ActivatedScopeCount int `json:"activated_scope_count"`
|
|
ErrorCount int `json:"error_count"`
|
|
}
|
|
|
|
func GetPricingRulesHandler(pg *sql.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
traceID := utils.TraceIDFromRequest(r)
|
|
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
|
|
|
rows, err := queries.ListPricingRules(ctx, pg)
|
|
if err != nil {
|
|
http.Error(w, "pricing rules list error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
_ = json.NewEncoder(w).Encode(rows)
|
|
}
|
|
}
|
|
|
|
// Very small “bulk upsert” for step-1/2: we only need to persist the multipliers+roundings for now.
|
|
// Rules are identified by UUID; new rows can be created via empty id (server generates).
|
|
func SavePricingRulesBulkHandler(pg *sql.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
|
|
var payload PricingRuleBulkSavePayload
|
|
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
|
http.Error(w, "invalid payload", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
traceID := utils.TraceIDFromRequest(r)
|
|
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
|
|
|
tx, err := pg.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
http.Error(w, "pg transaction start error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
updated := 0
|
|
for _, it := range payload.Items {
|
|
// Zero means that no rounding rule has been configured yet.
|
|
if it.TryWholesaleStep < 0 || it.TryRetailStep < 0 || it.UsdWholesaleStep < 0 || it.UsdRetailStep < 0 || it.EurWholesaleStep < 0 || it.EurRetailStep < 0 {
|
|
http.Error(w, "invalid rounding step", http.StatusBadRequest)
|
|
return
|
|
}
|
|
id, err := queries.UpsertPricingRule(ctx, tx, it)
|
|
if err != nil {
|
|
http.Error(w, "pricing rule save error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if id != "" {
|
|
updated++
|
|
}
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
http.Error(w, "pg transaction commit error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"success": true, "updated": updated})
|
|
}
|
|
}
|
|
|
|
func ImportPricingRulesHandler(pg *sql.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
|
|
var payload PricingRuleImportPayload
|
|
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
|
http.Error(w, "invalid payload", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if len(payload.Items) == 0 {
|
|
_ = json.NewEncoder(w).Encode(PricingRuleImportResult{Success: true})
|
|
return
|
|
}
|
|
|
|
traceID := utils.TraceIDFromRequest(r)
|
|
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
|
|
|
tx, err := pg.BeginTx(ctx, nil)
|
|
if err != nil {
|
|
http.Error(w, "pg transaction start error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
updated := 0
|
|
matched := 0
|
|
skipped := 0
|
|
for _, raw := range payload.Items {
|
|
if raw.TryWholesaleStep < 0 || raw.TryRetailStep < 0 || raw.UsdWholesaleStep < 0 || raw.UsdRetailStep < 0 || raw.EurWholesaleStep < 0 || raw.EurRetailStep < 0 {
|
|
http.Error(w, "invalid rounding step", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
pricingParameterID, err := queries.FindActivePricingParameterByScope(ctx, tx, queries.PricingParameterRowForImport(
|
|
raw.AskiliYan,
|
|
raw.Kategori,
|
|
raw.UrunIlkGrubu,
|
|
raw.UrunAnaGrubu,
|
|
raw.UrunAltGrubu,
|
|
raw.Icerik,
|
|
raw.Marka,
|
|
raw.BrandCode,
|
|
raw.BrandGroupSec,
|
|
))
|
|
if err == sql.ErrNoRows {
|
|
skipped++
|
|
continue
|
|
}
|
|
if err != nil {
|
|
http.Error(w, "pricing parameter resolve error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
matched++
|
|
|
|
_, err = queries.UpsertPricingRule(ctx, tx, queries.PricingRuleSaveItem{
|
|
PricingParameterID: pricingParameterID,
|
|
IsActive: raw.IsActive,
|
|
TryBase: raw.TryBase,
|
|
Try1: raw.Try1,
|
|
Try2: raw.Try2,
|
|
Try3: raw.Try3,
|
|
Try4: raw.Try4,
|
|
Try5: raw.Try5,
|
|
Try6: raw.Try6,
|
|
TryWholesaleStep: raw.TryWholesaleStep,
|
|
TryRetailStep: raw.TryRetailStep,
|
|
UsdBase: raw.UsdBase,
|
|
Usd1: raw.Usd1,
|
|
Usd2: raw.Usd2,
|
|
Usd3: raw.Usd3,
|
|
Usd4: raw.Usd4,
|
|
Usd5: raw.Usd5,
|
|
Usd6: raw.Usd6,
|
|
UsdWholesaleStep: raw.UsdWholesaleStep,
|
|
UsdRetailStep: raw.UsdRetailStep,
|
|
EurBase: raw.EurBase,
|
|
Eur1: raw.Eur1,
|
|
Eur2: raw.Eur2,
|
|
Eur3: raw.Eur3,
|
|
Eur4: raw.Eur4,
|
|
Eur5: raw.Eur5,
|
|
Eur6: raw.Eur6,
|
|
EurWholesaleStep: raw.EurWholesaleStep,
|
|
EurRetailStep: raw.EurRetailStep,
|
|
})
|
|
if err != nil {
|
|
http.Error(w, "pricing rule import error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
updated++
|
|
}
|
|
|
|
if err := tx.Commit(); err != nil {
|
|
http.Error(w, "pg transaction commit error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
_ = json.NewEncoder(w).Encode(PricingRuleImportResult{
|
|
Success: true,
|
|
Processed: len(payload.Items),
|
|
Matched: matched,
|
|
Skipped: skipped,
|
|
Updated: updated,
|
|
ActivatedScopeCount: 0,
|
|
ErrorCount: 0,
|
|
})
|
|
}
|
|
}
|
|
|
|
func GetPricingRuleOptionsHandler(pg *sql.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
|
|
field := strings.TrimSpace(r.URL.Query().Get("field"))
|
|
if field == "" {
|
|
http.Error(w, "missing field", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
limit := 500
|
|
if raw := strings.TrimSpace(r.URL.Query().Get("limit")); raw != "" {
|
|
if n, err := strconv.Atoi(raw); err == nil && n > 0 && n <= 5000 {
|
|
limit = n
|
|
}
|
|
}
|
|
|
|
f := pricingRuleFiltersFromRequest(r)
|
|
|
|
traceID := utils.TraceIDFromRequest(r)
|
|
ctx := utils.ContextWithTraceID(r.Context(), traceID)
|
|
|
|
opts, err := queries.ListPricingParameterDistinctOptions(ctx, pg, field, f, limit)
|
|
if err != nil {
|
|
http.Error(w, "options lookup error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"field": field,
|
|
"options": opts,
|
|
})
|
|
}
|
|
}
|
|
|
|
func GetPricingParameterRulesHandler(pg *sql.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
|
|
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 list error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
_ = json.NewEncoder(w).Encode(rows)
|
|
}
|
|
}
|
|
|
|
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")),
|
|
Kategori: splitCSV(r.URL.Query().Get("kategori")),
|
|
UrunIlkGrubu: splitCSV(r.URL.Query().Get("urun_ilk_grubu")),
|
|
UrunAnaGrubu: splitCSV(r.URL.Query().Get("urun_ana_grubu")),
|
|
UrunAltGrubu: splitCSV(r.URL.Query().Get("urun_alt_grubu")),
|
|
Icerik: splitCSV(r.URL.Query().Get("icerik")),
|
|
Marka: splitCSV(r.URL.Query().Get("marka")),
|
|
BrandCode: splitCSV(r.URL.Query().Get("brand_code")),
|
|
BrandGroupSec: splitCSV(r.URL.Query().Get("brand_group")),
|
|
}
|
|
}
|
|
|
|
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_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",
|
|
}
|
|
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_wholesale_step":
|
|
return row.Rule.TryWholesaleStep
|
|
case "try_retail_step":
|
|
return row.Rule.TryRetailStep
|
|
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_wholesale_step":
|
|
return row.Rule.UsdWholesaleStep
|
|
case "usd_retail_step":
|
|
return row.Rule.UsdRetailStep
|
|
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_wholesale_step":
|
|
return row.Rule.EurWholesaleStep
|
|
case "eur_retail_step":
|
|
return row.Rule.EurRetailStep
|
|
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 TOPTAN YUVARLAMA", "TRY PERAKENDE YUVARLAMA", "TRY TABAN", "TRY 1", "TRY 2", "TRY 3", "TRY 4", "TRY 5", "TRY 6",
|
|
"USD TOPTAN YUVARLAMA", "USD PERAKENDE YUVARLAMA", "USD TABAN", "USD 1", "USD 2", "USD 3", "USD 4", "USD 5", "USD 6",
|
|
"EUR TOPTAN YUVARLAMA", "EUR PERAKENDE 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,
|
|
csvExcelTextValue(row.BrandCode),
|
|
row.BrandGroupSec,
|
|
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "try_wholesale_step")),
|
|
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "try_retail_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_wholesale_step")),
|
|
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "usd_retail_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_wholesale_step")),
|
|
fmt.Sprintf("%.2f", pricingRuleNumericValue(row, "eur_retail_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 csvExcelTextValue(value string) string {
|
|
text := strings.TrimSpace(value)
|
|
if text == "" {
|
|
return ""
|
|
}
|
|
return `="` + strings.ReplaceAll(text, `"`, `""`) + `"`
|
|
}
|
|
|
|
func splitCSV(raw string) []string {
|
|
raw = strings.TrimSpace(raw)
|
|
if raw == "" {
|
|
return nil
|
|
}
|
|
parts := strings.Split(raw, ",")
|
|
out := make([]string, 0, len(parts))
|
|
for _, p := range parts {
|
|
p = strings.TrimSpace(p)
|
|
if p != "" {
|
|
out = append(out, p)
|
|
}
|
|
}
|
|
return out
|
|
}
|