package db import ( "encoding/json" "fmt" "gestion/models" "strconv" "gorm.io/gorm" ) // DefaultDeliverySchedule retourne un planning de livraison par défaut (tous les jours, 9h-20h) func DefaultDeliverySchedule() models.DeliverySchedule { day := models.DaySchedule{Enabled: true, OpenTime: "09:00", CloseTime: "20:00"} return models.DeliverySchedule{ Monday: day, Tuesday: day, Wednesday: day, Thursday: day, Friday: day, Saturday: day, Sunday: day, } } // CalcPointsFromTiers retourne le nombre de points correspondant au total selon les paliers func CalcPointsFromTiers(total float64, tiers []models.PointsTier) int { for _, t := range tiers { if total >= t.Min && (t.Max == 0 || total <= t.Max) { return t.Points } } return 0 } // DefaultSettings retourne les paramètres par défaut func DefaultSettings() models.AppSettings { return models.AppSettings{ PenaltiesEnabled: true, ShowAmendeScore: true, PenaltyTiers: []models.PenaltyTier{ {MinCancel: 0, Amount: 20}, {MinCancel: 1, Amount: 50}, {MinCancel: 2, Amount: 100}, {MinCancel: 3, Amount: 150}, }, PointsEnabled: true, ReferralEnabled: true, ReferralAmount: 0, PointsPools: []models.PointsPool{ { Key: "pool_0", Name: "Pool 1", Categories: []string{}, Tiers: []models.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: []models.PointsTier{ {Min: 30, Max: 100, Points: 1}, {Min: 110, Max: 200, Points: 2}, {Min: 210, Max: 0, Points: 3}, }, }, }, ShopName: "Milieu-Nantais", ContactTelegram: "MLN44LA", DeliveryMode: models.DeliveryModeConfig{ Mode: "single", CategoryRoutes: []models.CategoryRoute{}, }, AdminColorPrimary: "#7c3aed", AdminColorSecondary: "#000000", AdminColorSuccess: "#4ade80", AdminColorDanger: "#ef4444", AdminColorWarning: "#f59e0b", ClientColorPrimary: "#7c3aed", ClientColorSecondary: "#000000", ClientColorSuccess: "#4ade80", ClientColorDanger: "#ef4444", ClientColorWarning: "#f59e0b", ClientTitleGradientFrom: "#a78bfa", ClientTitleGradientTo: "#22d3ee", DeliverySchedule: DefaultDeliverySchedule(), PostalZones: []models.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", }}, }, Telegram2FAEnabled: false, } } // GetSettings récupère les paramètres depuis la DB func (d *Database) GetSettings() (models.AppSettings, error) { settings := DefaultSettings() var rows []struct { Key string `gorm:"column:key"` Value string `gorm:"column:value"` } if err := d.GDB.Table("app_settings").Select("key, value").Scan(&rows).Error; err != nil { return settings, fmt.Errorf("erreur lecture settings: %w", err) } for _, row := range rows { switch row.Key { case "penalties_enabled": settings.PenaltiesEnabled = row.Value == "true" case "show_amende_score": settings.ShowAmendeScore = row.Value == "true" case "points_enabled": settings.PointsEnabled = row.Value == "true" case "points_pools": var pools []models.PointsPool if err := json.Unmarshal([]byte(row.Value), &pools); err == nil { settings.PointsPools = pools } case "points_reward": // row.Value peut valoir la chaîne littérale "null" (récompense // désactivée puis sauvegardée : json.Marshal(nil *PointsReward) // produit "null"). json.Unmarshal d'un null JSON dans une valeur // non-pointeur est un no-op sans erreur (voir doc encoding/json), // donc sans ce garde-fou &reward pointerait vers une struct vide // mais non-nil, et la récompense réapparaîtrait activée. if row.Value != "null" && row.Value != "" { var reward models.PointsReward if err := json.Unmarshal([]byte(row.Value), &reward); err == nil { settings.PointsReward = &reward } } case "referral_enabled": settings.ReferralEnabled = row.Value == "true" case "referral_amount": if v, err := strconv.ParseFloat(row.Value, 64); err == nil { settings.ReferralAmount = v } case "crypto_payment_enabled": settings.CryptoPaymentEnabled = row.Value == "true" case "crypto_only": settings.CryptoOnly = row.Value == "true" case "nowpayments_api_key": settings.NowPaymentsAPIKey = row.Value case "nowpayments_ipn_secret": settings.NowPaymentsIPNSecret = row.Value case "nowpayments_currencies": var currencies []string if err := json.Unmarshal([]byte(row.Value), ¤cies); err == nil { settings.NowPaymentsCurrencies = currencies } case "delivery_schedule": var sched models.DeliverySchedule if err := json.Unmarshal([]byte(row.Value), &sched); err == nil { settings.DeliverySchedule = sched } case "postal_zones": var zones []models.PostalZone if err := json.Unmarshal([]byte(row.Value), &zones); err == nil { settings.PostalZones = zones } case "contact_telegram": settings.ContactTelegram = row.Value case "telegram_bot_token": settings.TelegramBotToken = row.Value case "telegram_bot_username": settings.TelegramBotUsername = row.Value case "telegram_notifications_enabled": settings.TelegramNotificationsEnabled = row.Value == "true" case "delivery_mode": var mode models.DeliveryModeConfig if err := json.Unmarshal([]byte(row.Value), &mode); err == nil { settings.DeliveryMode = mode } case "telegram_2fa_enabled": settings.Telegram2FAEnabled = row.Value == "true" case "shop_name": settings.ShopName = row.Value case "admin_color_primary": settings.AdminColorPrimary = row.Value case "admin_color_secondary": settings.AdminColorSecondary = row.Value case "admin_color_success": settings.AdminColorSuccess = row.Value case "admin_color_danger": settings.AdminColorDanger = row.Value case "admin_color_warning": settings.AdminColorWarning = row.Value case "client_color_primary": settings.ClientColorPrimary = row.Value case "client_color_secondary": settings.ClientColorSecondary = row.Value case "client_color_success": settings.ClientColorSuccess = row.Value case "client_color_danger": settings.ClientColorDanger = row.Value case "client_color_warning": settings.ClientColorWarning = row.Value case "client_title_gradient_from": settings.ClientTitleGradientFrom = row.Value case "client_title_gradient_to": settings.ClientTitleGradientTo = row.Value } } return settings, nil } // UpdateSettings sauvegarde les paramètres dans la DB func (d *Database) UpdateSettings(s models.AppSettings) error { boolStr := func(b bool) string { if b { return "true" } return "false" } if s.PointsPools == nil { s.PointsPools = []models.PointsPool{} } 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 = []models.PointsTier{} } } poolsJSON, err := json.Marshal(s.PointsPools) if err != nil { return fmt.Errorf("erreur sérialisation pools: %w", err) } rewardJSON, err := json.Marshal(s.PointsReward) if err != nil { return fmt.Errorf("erreur sérialisation points_reward: %w", err) } 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) } schedJSON, err := json.Marshal(s.DeliverySchedule) if err != nil { return fmt.Errorf("erreur sérialisation delivery_schedule: %w", err) } if s.PostalZones == nil { s.PostalZones = []models.PostalZone{} } zonesJSON, err := json.Marshal(s.PostalZones) if err != nil { return fmt.Errorf("erreur sérialisation postal_zones: %w", err) } if s.DeliveryMode.CategoryRoutes == nil { s.DeliveryMode.CategoryRoutes = []models.CategoryRoute{} } deliveryModeJSON, err := json.Marshal(s.DeliveryMode) if err != nil { return fmt.Errorf("erreur sérialisation delivery_mode: %w", err) } if s.ContactTelegram == "" { s.ContactTelegram = "MLN44LA" } pairs := [][2]string{ {"penalties_enabled", boolStr(s.PenaltiesEnabled)}, {"show_amende_score", boolStr(s.ShowAmendeScore)}, {"points_enabled", boolStr(s.PointsEnabled)}, {"points_pools", string(poolsJSON)}, {"points_reward", string(rewardJSON)}, {"referral_enabled", boolStr(s.ReferralEnabled)}, {"referral_amount", strconv.FormatFloat(s.ReferralAmount, 'f', 2, 64)}, {"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)}, {"delivery_schedule", string(schedJSON)}, {"postal_zones", string(zonesJSON)}, {"telegram_bot_token", s.TelegramBotToken}, {"telegram_bot_username", s.TelegramBotUsername}, {"telegram_notifications_enabled", boolStr(s.TelegramNotificationsEnabled)}, {"telegram_2fa_enabled", boolStr(s.Telegram2FAEnabled)}, {"delivery_mode", string(deliveryModeJSON)}, {"shop_name", s.ShopName}, {"contact_telegram", s.ContactTelegram}, {"admin_color_primary", s.AdminColorPrimary}, {"admin_color_secondary", s.AdminColorSecondary}, {"admin_color_success", s.AdminColorSuccess}, {"admin_color_danger", s.AdminColorDanger}, {"admin_color_warning", s.AdminColorWarning}, {"client_color_primary", s.ClientColorPrimary}, {"client_color_secondary", s.ClientColorSecondary}, {"client_color_success", s.ClientColorSuccess}, {"client_color_danger", s.ClientColorDanger}, {"client_color_warning", s.ClientColorWarning}, {"client_title_gradient_from", s.ClientTitleGradientFrom}, {"client_title_gradient_to", s.ClientTitleGradientTo}, } upsert := `INSERT INTO app_settings (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value` return d.GDB.Transaction(func(tx *gorm.DB) error { for _, p := range pairs { if err := tx.Exec(upsert, p[0], p[1]).Error; err != nil { return fmt.Errorf("erreur upsert %s: %w", p[0], err) } } return nil }) }