package routes import ( "bssapp-backend/queries" "bssapp-backend/utils" "database/sql" "encoding/json" "net/http" "strconv" "strings" ) // 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"` } 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.TryStep < 0 || it.UsdStep < 0 || it.EurStep < 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 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 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 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 }