package db import ( "encoding/json" "fmt" ) // DaySchedule représente les horaires de livraison pour un jour de la semaine type DaySchedule struct { Enabled bool `json:"enabled"` OpenTime string `json:"open_time"` // ex: "09:00" CloseTime string `json:"close_time"` // ex: "20:00" } // DeliverySchedule représente les horaires de livraison pour chaque jour type DeliverySchedule struct { Monday DaySchedule `json:"monday"` Tuesday DaySchedule `json:"tuesday"` Wednesday DaySchedule `json:"wednesday"` Thursday DaySchedule `json:"thursday"` Friday DaySchedule `json:"friday"` Saturday DaySchedule `json:"saturday"` Sunday DaySchedule `json:"sunday"` } // DefaultDeliverySchedule retourne un planning de livraison par défaut (tous les jours, 9h-20h) func DefaultDeliverySchedule() DeliverySchedule { day := DaySchedule{Enabled: true, OpenTime: "09:00", CloseTime: "20:00"} return DeliverySchedule{ Monday: day, Tuesday: day, Wednesday: day, Thursday: day, Friday: day, Saturday: day, Sunday: day, } } // PostalZone représente une zone de livraison avec un minimum de commande type PostalZone struct { Name string `json:"name"` MinAmount float64 `json:"min_amount"` Codes []string `json:"codes"` } // PointsTier représente un palier du barème de points // Si Max == 0, il n'y a pas de borne supérieure (illimité) type PointsTier struct { Min float64 `json:"min"` Max float64 `json:"max"` // 0 = illimité Points int `json:"points"` } // CalcPointsFromTiers retourne le nombre de points correspondant au total selon les paliers func CalcPointsFromTiers(total float64, tiers []PointsTier) int { for _, t := range tiers { if total >= t.Min && (t.Max == 0 || total <= t.Max) { return t.Points } } return 0 } // PointsPool représente un type de point personnalisable par l'admin // Pool[0] → colonne `point`, Pool[1] → colonne `point_zipette` type PointsPool struct { Key string `json:"key"` // identifiant interne (ex: "pool_0") Name string `json:"name"` // nom affiché (ex: "Cannabis", "Accessoires") Categories []string `json:"categories"` // catégories de produits assignées à ce pool Tiers []PointsTier `json:"tiers"` // barème de points } // AppSettings contient les paramètres globaux de l'application type AppSettings struct { PenaltiesEnabled bool `json:"penalties_enabled"` ShowAmendeScore bool `json:"show_amende_score"` // afficher le score d'amendes aux clients/cabine PointsEnabled bool `json:"points_enabled"` // afficher/activer le système de points PointsPools []PointsPool `json:"points_pools"` // types de points personnalisés ReferralEnabled bool `json:"referral_enabled"` // activer/désactiver le système de parrainage CryptoPaymentEnabled bool `json:"crypto_payment_enabled"` // activer/désactiver le paiement crypto CryptoOnly bool `json:"crypto_only"` // forcer le paiement crypto uniquement (pas d'espèces) NowPaymentsAPIKey string `json:"nowpayments_api_key"` // clé API NowPayments NowPaymentsIPNSecret string `json:"nowpayments_ipn_secret"` // secret IPN NowPayments NowPaymentsCurrencies []string `json:"nowpayments_currencies"` // cryptos acceptées (ex: ["btc","eth","ltc"]) DeliverySchedule DeliverySchedule `json:"delivery_schedule"` // horaires de livraison par jour PostalZones []PostalZone `json:"postal_zones"` // zones de livraison avec minimum de commande } // DefaultSettings retourne les paramètres par défaut func DefaultSettings() AppSettings { return AppSettings{ PenaltiesEnabled: true, ShowAmendeScore: true, PointsEnabled: true, ReferralEnabled: true, PointsPools: []PointsPool{ { Key: "pool_0", Name: "Pool 1", Categories: []string{}, Tiers: []PointsTier{ {Min: 30, Max: 50, Points: 1}, {Min: 60, Max: 150, Points: 2}, {Min: 160, Max: 300, Points: 3}, {Min: 310, Max: 400, Points: 5}, {Min: 401, Max: 0, Points: 10}, }, }, { Key: "pool_1", Name: "Pool 2", Categories: []string{}, Tiers: []PointsTier{ {Min: 30, Max: 100, Points: 1}, {Min: 110, Max: 200, Points: 2}, {Min: 210, Max: 0, Points: 3}, }, }, }, DeliverySchedule: DefaultDeliverySchedule(), PostalZones: []PostalZone{ {Name: "Zone 30€", MinAmount: 30, Codes: []string{"44000", "44100", "44200", "44300"}}, {Name: "Zone 50€", MinAmount: 50, Codes: []string{ "44400", "44880", "44120", "44230", "44115", "44980", "44470", "44240", "44700", "44800", "44340", "44620", "44830", }}, {Name: "Zone 100€", MinAmount: 100, Codes: []string{ "44860", "44220", "44118", "44710", "44690", "44119", }}, }, } } // GetSettings récupère les paramètres depuis la DB func (d *Database) GetSettings() (AppSettings, error) { settings := DefaultSettings() rows, err := d.Query(`SELECT key, value FROM app_settings`) if err != nil { return settings, fmt.Errorf("erreur lecture settings: %w", err) } defer rows.Close() for rows.Next() { var key, value string if err := rows.Scan(&key, &value); err != nil { continue } switch key { case "penalties_enabled": settings.PenaltiesEnabled = value == "true" case "show_amende_score": settings.ShowAmendeScore = value == "true" case "points_enabled": settings.PointsEnabled = value == "true" case "points_pools": var pools []PointsPool if err := json.Unmarshal([]byte(value), &pools); err == nil { settings.PointsPools = pools } case "referral_enabled": settings.ReferralEnabled = value == "true" case "crypto_payment_enabled": settings.CryptoPaymentEnabled = value == "true" case "crypto_only": settings.CryptoOnly = value == "true" case "nowpayments_api_key": settings.NowPaymentsAPIKey = value case "nowpayments_ipn_secret": settings.NowPaymentsIPNSecret = value case "nowpayments_currencies": var currencies []string if err := json.Unmarshal([]byte(value), ¤cies); err == nil { settings.NowPaymentsCurrencies = currencies } case "delivery_schedule": var sched DeliverySchedule if err := json.Unmarshal([]byte(value), &sched); err == nil { settings.DeliverySchedule = sched } case "postal_zones": var zones []PostalZone if err := json.Unmarshal([]byte(value), &zones); err == nil { settings.PostalZones = zones } } } return settings, rows.Err() } // UpdateSettings sauvegarde les paramètres dans la DB func (d *Database) UpdateSettings(s AppSettings) error { boolStr := func(b bool) string { if b { return "true" } return "false" } if s.PointsPools == nil { s.PointsPools = []PointsPool{} } // S'assurer que chaque pool a des slices non-nil for i := range s.PointsPools { if s.PointsPools[i].Categories == nil { s.PointsPools[i].Categories = []string{} } if s.PointsPools[i].Tiers == nil { s.PointsPools[i].Tiers = []PointsTier{} } } poolsJSON, err := json.Marshal(s.PointsPools) if err != nil { return fmt.Errorf("erreur sérialisation pools: %w", err) } tx, err := d.Begin() if err != nil { return err } defer tx.Rollback() upsert := `INSERT INTO app_settings (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value` if s.NowPaymentsCurrencies == nil { s.NowPaymentsCurrencies = []string{} } currenciesJSON, err := json.Marshal(s.NowPaymentsCurrencies) if err != nil { return fmt.Errorf("erreur sérialisation nowpayments_currencies: %w", err) } pairs := [][2]string{ {"penalties_enabled", boolStr(s.PenaltiesEnabled)}, {"show_amende_score", boolStr(s.ShowAmendeScore)}, {"points_enabled", boolStr(s.PointsEnabled)}, {"points_pools", string(poolsJSON)}, {"referral_enabled", boolStr(s.ReferralEnabled)}, {"crypto_payment_enabled", boolStr(s.CryptoPaymentEnabled)}, {"crypto_only", boolStr(s.CryptoOnly)}, {"nowpayments_api_key", s.NowPaymentsAPIKey}, {"nowpayments_ipn_secret", s.NowPaymentsIPNSecret}, {"nowpayments_currencies", string(currenciesJSON)}, } schedJSON, err := json.Marshal(s.DeliverySchedule) if err != nil { return fmt.Errorf("erreur sérialisation delivery_schedule: %w", err) } pairs = append(pairs, [2]string{"delivery_schedule", string(schedJSON)}) if s.PostalZones == nil { s.PostalZones = []PostalZone{} } zonesJSON, err := json.Marshal(s.PostalZones) if err != nil { return fmt.Errorf("erreur sérialisation postal_zones: %w", err) } pairs = append(pairs, [2]string{"postal_zones", string(zonesJSON)}) for _, p := range pairs { if _, err = tx.Exec(upsert, p[0], p[1]); err != nil { return fmt.Errorf("erreur upsert %s: %w", p[0], err) } } return tx.Commit() }