chore: fix

This commit is contained in:
2026-03-14 15:28:40 +01:00
parent 9d52f3193a
commit 0043646cff
10 changed files with 156 additions and 77 deletions
+59 -11
View File
@@ -2,6 +2,7 @@ package db
import ( import (
"database/sql" "database/sql"
"encoding/json"
"fmt" "fmt"
"gestion/models" "gestion/models"
"log" "log"
@@ -57,7 +58,7 @@ func (d *Database) GetClientByID(id int) (*models.Client, error) {
// GetAllClients récupère tous les clients // GetAllClients récupère tous les clients
func (d *Database) GetAllClients() ([]*models.Client, error) { func (d *Database) GetAllClients() ([]*models.Client, error) {
query := `SELECT id, username, password, nom, prenom, telephone, command, point, point_zipette, amende, referral_balance, created_at query := `SELECT id, username, password, nom, prenom, telephone, command, point, point_zipette, amende, referral_balance, COALESCE(points_extra, '{}'::jsonb), created_at
FROM clients ORDER BY created_at DESC` FROM clients ORDER BY created_at DESC`
rows, err := d.Query(query) rows, err := d.Query(query)
@@ -69,6 +70,7 @@ func (d *Database) GetAllClients() ([]*models.Client, error) {
var clients []*models.Client var clients []*models.Client
for rows.Next() { for rows.Next() {
client := &models.Client{} client := &models.Client{}
var pointsExtraJSON []byte
err := rows.Scan( err := rows.Scan(
&client.ID, &client.ID,
&client.Username, &client.Username,
@@ -81,11 +83,16 @@ func (d *Database) GetAllClients() ([]*models.Client, error) {
&client.PointZipette, &client.PointZipette,
&client.Amende, &client.Amende,
&client.ReferralBalance, &client.ReferralBalance,
&pointsExtraJSON,
&client.CreatedAt, &client.CreatedAt,
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("erreur lors du scan du client: %w", err) return nil, fmt.Errorf("erreur lors du scan du client: %w", err)
} }
client.PointsExtra = map[string]int{}
if len(pointsExtraJSON) > 0 {
json.Unmarshal(pointsExtraJSON, &client.PointsExtra)
}
clients = append(clients, client) clients = append(clients, client)
} }
@@ -470,12 +477,24 @@ func (d *Database) CheckClientCanOrder(username string) (bool, float64, error) {
return true, 0, nil return true, 0, nil
} }
func (d *Database) ResetClientPoint(username string, resetCancellationsPoint bool) error { // ResetClientPoint réinitialise les points d'un client.
// poolIdx=0 → point, poolIdx=1 → point_zipette, poolIdx=-1 → tous
// poolIdx>=2 → points_extra[extraPoolKey]
func (d *Database) ResetClientPoint(username string, poolIdx int, extraPoolKey string) error {
var query string var query string
if resetCancellationsPoint { switch {
query = `UPDATE clients SET point = 0, point_zipette = 0, updated_at = CURRENT_TIMESTAMP WHERE username = $1` case poolIdx == 0:
} else {
query = `UPDATE clients SET point = 0, updated_at = CURRENT_TIMESTAMP WHERE username = $1` query = `UPDATE clients SET point = 0, updated_at = CURRENT_TIMESTAMP WHERE username = $1`
case poolIdx == 1:
query = `UPDATE clients SET point_zipette = 0, updated_at = CURRENT_TIMESTAMP WHERE username = $1`
case poolIdx >= 2 && extraPoolKey != "":
_, err := d.Exec(
`UPDATE clients SET points_extra = points_extra - $2, updated_at = CURRENT_TIMESTAMP WHERE username = $1`,
username, extraPoolKey,
)
return err
default: // -1 → reset total
query = `UPDATE clients SET point = 0, point_zipette = 0, points_extra = '{}'::jsonb, updated_at = CURRENT_TIMESTAMP WHERE username = $1`
} }
result, err := d.Exec(query, username) result, err := d.Exec(query, username)
@@ -943,18 +962,47 @@ func (d *Database) CalculateAndAddPointsForCommandTx(tx *sql.Tx, commandID int,
} }
totalPoints = pts0 + pts1 totalPoints = pts0 + pts1
log.Printf("💰 [CalcPointsTx] pool[0]=%d pts, pool[1]=%d pts", pts0, pts1) // Pools supplémentaires (index 2+) → points_extra JSONB
for i := 2; i < len(pools); i++ {
ptsExtra := CalcPointsFromTiers(poolTotals[i], pools[i].Tiers)
if ptsExtra == 0 {
continue
}
totalPoints += ptsExtra
poolKey := pools[i].Key
_, err = tx.Exec(`
UPDATE clients
SET points_extra = jsonb_set(
COALESCE(points_extra, '{}'::jsonb),
ARRAY[$2],
to_jsonb(COALESCE((points_extra->>$2)::int, 0) + $3)
), updated_at = CURRENT_TIMESTAMP
WHERE username = $1
`, username, poolKey, ptsExtra)
if err != nil {
log.Printf("❌ [CalcPointsTx] Erreur UPDATE points_extra pool[%d]: %v", i, err)
return 0, "", fmt.Errorf("erreur mise à jour points pool[%d]: %w", i, err)
}
log.Printf("💰 [CalcPointsTx] pool[%d] (%s): +%d pts → points_extra", i, pools[i].Name, ptsExtra)
}
log.Printf("💰 [CalcPointsTx] pool[0]=%d pts, pool[1]=%d pts, total=%d pts", pts0, pts1, totalPoints)
if totalPoints == 0 { if totalPoints == 0 {
return 0, "", nil return 0, "", nil
} }
if pts0 > 0 && pts1 > 0 { var categoryParts []string
pointCategory = pools[0].Name + " & " + pools[1].Name if pts0 > 0 {
} else if pts1 > 0 { categoryParts = append(categoryParts, pools[0].Name)
pointCategory = pools[1].Name }
if pts1 > 0 {
categoryParts = append(categoryParts, pools[1].Name)
}
if len(categoryParts) > 0 {
pointCategory = strings.Join(categoryParts, " & ")
} else { } else {
pointCategory = pools[0].Name pointCategory = "points"
} }
if len(pools) == 1 || pts1 == 0 { if len(pools) == 1 || pts1 == 0 {
+5
View File
@@ -161,6 +161,11 @@ func InitDB() *Database {
log.Fatalf("❌ Erreur migration commandes.referral_used: %v", err) log.Fatalf("❌ Erreur migration commandes.referral_used: %v", err)
} }
// Migration: points extra pour les pools de points supplémentaires (pool[2+])
if _, err = database.Exec(`ALTER TABLE clients ADD COLUMN IF NOT EXISTS points_extra JSONB NOT NULL DEFAULT '{}'::jsonb`); err != nil {
log.Fatalf("❌ Erreur migration clients.points_extra: %v", err)
}
// Lancer le nettoyage périodique des tokens expirés // Lancer le nettoyage périodique des tokens expirés
go database.cleanExpiredTokensPeriodically() go database.cleanExpiredTokensPeriodically()
+20
View File
@@ -50,6 +50,15 @@ func GetMyCompletedOrders(c *gin.Context) {
// ✅ Récupérer les infos client pour statistiques // ✅ Récupérer les infos client pour statistiques
client, err := database.GetClientByUsername(usernameStr) client, err := database.GetClientByUsername(usernameStr)
// ✅ Récupérer les noms des pools de points
poolNames := []string{"Pool 1", "Pool 2"}
if settings, sErr := database.GetSettings(); sErr == nil && len(settings.PointsPools) > 0 {
poolNames = make([]string, len(settings.PointsPools))
for i, p := range settings.PointsPools {
poolNames[i] = p.Name
}
}
response := gin.H{ response := gin.H{
"success": true, "success": true,
"commands": commands, "commands": commands,
@@ -57,10 +66,21 @@ func GetMyCompletedOrders(c *gin.Context) {
} }
if err == nil && client != nil { if err == nil && client != nil {
// Construire le tableau générique des valeurs par pool
poolPoints := make([]int, len(poolNames))
if len(poolPoints) > 0 {
poolPoints[0] = client.Point
}
if len(poolPoints) > 1 {
poolPoints[1] = client.PointZipette
}
response["client_stats"] = gin.H{ response["client_stats"] = gin.H{
"username": client.Username, "username": client.Username,
"total_commands": client.Command, "total_commands": client.Command,
"points": client.Point, "points": client.Point,
"pool_points": poolPoints,
"pool_names": poolNames,
"penalties": client.Amende, "penalties": client.Amende,
} }
} }
+12 -5
View File
@@ -952,16 +952,23 @@ func ResetClientPointAdmin(c *gin.Context) {
return return
} }
var req struct { var req struct {
ResetCancellationsPoints bool `json:"reset_cancellations_points"` Pool int `json:"pool"` // 0=pool principal, 1=pool secondaire, -1=tous (défaut)
} }
req.Pool = -1 // défaut : reset total
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err == nil {
req.ResetCancellationsPoints = false // valeur reçue correctement
} }
database := c.MustGet("database").(*db.Database) database := c.MustGet("database").(*db.Database)
err := database.ResetClientPoint(username, req.ResetCancellationsPoints) extraPoolKey := ""
if req.Pool >= 2 {
if settings, err := database.GetSettings(); err == nil && req.Pool < len(settings.PointsPools) {
extraPoolKey = settings.PointsPools[req.Pool].Key
}
}
err := database.ResetClientPoint(username, req.Pool, extraPoolKey)
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{ c.JSON(http.StatusInternalServerError, gin.H{
"error": "Erreur lors de la réinitialisation", "error": "Erreur lors de la réinitialisation",
+9
View File
@@ -19,12 +19,21 @@ func GetPublicSettings(c *gin.Context) {
settings = db.DefaultSettings() settings = db.DefaultSettings()
} }
poolNames := make([]string, len(settings.PointsPools))
poolKeys := make([]string, len(settings.PointsPools))
for i, p := range settings.PointsPools {
poolNames[i] = p.Name
poolKeys[i] = p.Key
}
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"success": true, "success": true,
"penalties_enabled": settings.PenaltiesEnabled, "penalties_enabled": settings.PenaltiesEnabled,
"show_amende_score": settings.ShowAmendeScore, "show_amende_score": settings.ShowAmendeScore,
"points_enabled": settings.PointsEnabled, "points_enabled": settings.PointsEnabled,
"points_separated": len(settings.PointsPools) > 1, "points_separated": len(settings.PointsPools) > 1,
"pool_names": poolNames,
"pool_keys": poolKeys,
"referral_enabled": settings.ReferralEnabled, "referral_enabled": settings.ReferralEnabled,
"delivery_schedule": settings.DeliverySchedule, "delivery_schedule": settings.DeliverySchedule,
}) })
+3 -2
View File
@@ -11,8 +11,9 @@ type Client struct {
Prenom string `json:"prenom"` Prenom string `json:"prenom"`
Telephone string `json:"telephone"` Telephone string `json:"telephone"`
Command int `json:"command"` Command int `json:"command"`
Point int `json:"point"` Point int `json:"point"`
PointZipette int `json:"points_zipette"` PointZipette int `json:"points_zipette"`
PointsExtra map[string]int `json:"points_extra"` // pools[2+]
Amende float64 `json:"amende"` Amende float64 `json:"amende"`
CancellationsCount int `json:"cancellations_count"` CancellationsCount int `json:"cancellations_count"`
LastPenaltyReason string `json:"last_penalty_reason"` LastPenaltyReason string `json:"last_penalty_reason"`
+6 -1
View File
@@ -730,9 +730,11 @@ export interface PublicSettings {
points_enabled: boolean; points_enabled: boolean;
points_separated: boolean; points_separated: boolean;
referral_enabled: boolean; referral_enabled: boolean;
pool_names: string[];
} }
export const getPublicSettings = async (): Promise<PublicSettings> => { export const getPublicSettings = async (): Promise<PublicSettings> => {
const defaults: PublicSettings = { penalties_enabled: true, show_amende_score: true, points_enabled: true, points_separated: true, referral_enabled: true, pool_names: ['Pool 1', 'Pool 2'] };
try { try {
const { data } = await apiClient.get(`${V1}/app-settings`); const { data } = await apiClient.get(`${V1}/app-settings`);
return { return {
@@ -741,9 +743,12 @@ export const getPublicSettings = async (): Promise<PublicSettings> => {
points_enabled: data.points_enabled ?? true, points_enabled: data.points_enabled ?? true,
points_separated: data.points_separated ?? true, points_separated: data.points_separated ?? true,
referral_enabled: data.referral_enabled ?? true, referral_enabled: data.referral_enabled ?? true,
pool_names: Array.isArray(data.pool_names) && data.pool_names.length > 0
? data.pool_names
: defaults.pool_names,
}; };
} catch { } catch {
return { penalties_enabled: true, show_amende_score: true, points_enabled: true, points_separated: true, referral_enabled: true }; return defaults;
} }
}; };
+2 -1
View File
@@ -585,7 +585,8 @@ export interface ClientStats {
telephone?: string; telephone?: string;
total_commands: number; total_commands: number;
points: number; points: number;
points_zipette: number; // ✅ AJOUTER CETTE LIGNE pool_points: number[];
pool_names: string[];
penalties: number; penalties: number;
} }
@@ -45,7 +45,7 @@ export default function OrderHistoryScreen() {
const [orders, setOrders] = useState<CompletedOrder[]>([]); const [orders, setOrders] = useState<CompletedOrder[]>([]);
const [stats, setStats] = useState<ClientStats | null>(null); const [stats, setStats] = useState<ClientStats | null>(null);
const [penalties, setPenalties] = useState<PenaltyInfo | null>(null); const [penalties, setPenalties] = useState<PenaltyInfo | null>(null);
const [appSettings, setAppSettings] = useState<PublicSettings>({ penalties_enabled: true, show_amende_score: true, points_enabled: true, points_separated: true, referral_enabled: false }); const [appSettings, setAppSettings] = useState<PublicSettings>({ penalties_enabled: true, show_amende_score: true, points_enabled: true, points_separated: true, referral_enabled: false, pool_names: ['Pool 1', 'Pool 2'] });
const [referralBalance, setReferralBalance] = useState(0); const [referralBalance, setReferralBalance] = useState(0);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
@@ -216,7 +216,9 @@ export default function OrderHistoryScreen() {
if (loading && !refreshing) if (loading && !refreshing)
return <LoadingSpinner message="Chargement historique..." />; return <LoadingSpinner message="Chargement historique..." />;
const totalPoints = (stats?.points || 0) + (stats?.points_zipette || 0); const poolNames = stats?.pool_names?.length ? stats.pool_names : appSettings.pool_names;
const poolPoints = stats?.pool_points ?? [stats?.points ?? 0];
const totalPoints = poolPoints.reduce((s, v) => s + (v || 0), 0);
const penaltyCount = penalties?.total_penalty || stats?.penalties || 0; const penaltyCount = penalties?.total_penalty || stats?.penalties || 0;
return ( return (
@@ -247,64 +249,33 @@ export default function OrderHistoryScreen() {
<Text style={styles.statLabel}>Commandes</Text> <Text style={styles.statLabel}>Commandes</Text>
</View> </View>
{appSettings.points_enabled && ( {appSettings.points_enabled && (
appSettings.points_separated ? ( poolNames.length <= 1 ? (
<View style={[styles.statCard, shadows.sm]}>
<Ionicons name="trophy-outline" size={24} color={colors.warning} />
<Text style={styles.statValue}>{poolPoints[0] || 0}</Text>
<Text style={styles.statLabel}>Pts {poolNames[0] ?? 'Points'}</Text>
</View>
) : (
<> <>
<View style={[styles.statCard, shadows.sm]}> {poolNames.map((name, i) => (
<Ionicons <View key={i} style={[styles.statCard, shadows.sm]}>
name="leaf-outline"
size={24}
color={colors.categoryWeedHash}
/>
<Text style={styles.statValue}>
{stats?.points || 0}
</Text>
<Text style={styles.statLabel}>
Pts Weed/Hash
</Text>
</View>
<View style={[styles.statCard, shadows.sm]}>
<Ionicons
name="flash-outline"
size={24}
color={colors.info}
/>
<Text style={styles.statValue}>
{stats?.points_zipette || 0}
</Text>
<Text style={styles.statLabel}>
Pts Zipette
</Text>
</View>
{(stats?.points || 0) > 0 && (stats?.points_zipette || 0) > 0 && (
<View style={[styles.statCard, shadows.sm]}>
<Ionicons <Ionicons
name="trophy-outline" name={i === 0 ? "leaf-outline" : i === 1 ? "flash-outline" : "star-outline"}
size={24} size={24}
color={colors.warning} color={i === 0 ? colors.categoryWeedHash : i === 1 ? colors.info : colors.accent}
/> />
<Text style={styles.statValue}> <Text style={styles.statValue}>{poolPoints[i] || 0}</Text>
{totalPoints} <Text style={styles.statLabel}>Pts {name}</Text>
</Text> </View>
<Text style={styles.statLabel}> ))}
Total Points {totalPoints > 0 && (
</Text> <View style={[styles.statCard, shadows.sm]}>
<Ionicons name="trophy-outline" size={24} color={colors.warning} />
<Text style={styles.statValue}>{totalPoints}</Text>
<Text style={styles.statLabel}>Total Points</Text>
</View> </View>
)} )}
</> </>
) : (
<View style={[styles.statCard, shadows.sm]}>
<Ionicons
name="trophy-outline"
size={24}
color={colors.warning}
/>
<Text style={styles.statValue}>
{stats?.points || 0}
</Text>
<Text style={styles.statLabel}>
Points
</Text>
</View>
) )
)} )}
{appSettings.show_amende_score && ( {appSettings.show_amende_score && (
@@ -537,8 +537,9 @@ export default function OrderTrackingScreen() {
</Text> </Text>
</View> </View>
)} )}
{(order.status === "en_route" || {order.status === "en_route" &&
order.status === "arrived") && ( eta?.eta_minutes != null &&
eta.eta_minutes > 0 && (
<View style={styles.trackRow}> <View style={styles.trackRow}>
<Ionicons <Ionicons
name="timer-outline" name="timer-outline"
@@ -546,8 +547,19 @@ export default function OrderTrackingScreen() {
color={colors.warning} color={colors.warning}
/> />
<Text style={styles.trackText}> <Text style={styles.trackText}>
Temps de livraison estimé : Temps de livraison estimé : ~{eta.eta_minutes} min
~ </Text>
</View>
)}
{order.status === "arrived" && (
<View style={styles.trackRow}>
<Ionicons
name="timer-outline"
size={16}
color={colors.warning}
/>
<Text style={styles.trackText}>
Temps de livraison estimé : ~
{eta?.eta_minutes != null && {eta?.eta_minutes != null &&
eta.eta_minutes > 0 && eta.eta_minutes > 0 &&
eta.eta_minutes < 5 eta.eta_minutes < 5