@movalib/movalib-commons 1.68.32 → 1.68.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/FEATURE_minimize-rdv-dialog.md +74 -0
- package/.claude/MOVA-393-export-fec.md +392 -0
- package/.claude/POSTAL_CODE_AUTOCOMPLETE.md +78 -0
- package/.claude/PROMPT_fix-404-navigation.md +237 -0
- package/.claude/PROMPT_version-refresh-cleanup.md +157 -0
- package/.claude/REPAIR_ORDER_FEATURE.md +416 -0
- package/.claude/SESSION_INVENTORY_MODULE.md +75 -0
- package/.claude/TICKET_PERF_MY_SERVICES.md +67 -0
- package/.claude/feature-forfaits-creation.md +263 -0
- package/.claude/recaptcha-enterprise-setup.md +159 -0
- package/.claude/settings.json +8 -0
- package/.claude/settings.local.json +12 -0
- package/dist/index.d.ts +1 -0
- package/dist/src/models/Customer.d.ts +2 -0
- package/dist/src/models/EmailVerification.d.ts +13 -0
- package/dist/src/models/EmailVerification.js +2 -0
- package/dist/src/models/Garage.d.ts +1 -0
- package/dist/src/models/Garage.js +8 -0
- package/dist/src/models/Settings.d.ts +2 -1
- package/dist/src/models/Settings.js +1 -0
- package/index.ts +1 -0
- package/package.json +1 -1
- package/src/models/Customer.ts +3 -1
- package/src/models/EmailVerification.ts +13 -0
- package/src/models/Garage.ts +6 -0
- package/src/models/Settings.ts +2 -1
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# Feature : Ordre de Réparation (OR)
|
|
2
|
+
|
|
3
|
+
> **Pour Claude Code** : lis ce fichier en entier avant de commencer. Chaque partie est indépendante et validable. Ne passe à la partie suivante qu'après confirmation.
|
|
4
|
+
> Base-toi sur l'implémentation existante des `MyCalendarQuote, MyCalendarEventMemoList` en zone mémos pour tout ce qui n'est pas explicitement spécifié.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Contexte général
|
|
9
|
+
|
|
10
|
+
- **Feature flag** : `garage.settings[]` contient `CUSTOM_REPAIR_ORDER` avec `valueBool: true`. Toute l'UI et l'accessibilité de cette feature sont conditionnées par ce flag. cf : getSettingBool(garage?.settings ?? [], CodeSettings.CUSTOM_REPAIR_ORDER)
|
|
11
|
+
- **Zone concernée** : Zone mémos (sidebar/panel des mémos sur un événement): MyCalendarEventMemoList
|
|
12
|
+
- **Objet métier** : Un OR est un objet de type `Event` avec `eventType = "REPAIR_ORDER"`
|
|
13
|
+
- **États d'un OR** : `DRAFT` | `CLOSED`
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Endpoints
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
GET /garage/:garageId/repair-order
|
|
21
|
+
POST /garage/:garageId/repair-order
|
|
22
|
+
PATCH /garage/:garageId/repair-order/:id
|
|
23
|
+
DELETE /garage/:garageId/repair-order/:id
|
|
24
|
+
|
|
25
|
+
// Transformations
|
|
26
|
+
POST /garage/:garageId/repair-order/:id/quote
|
|
27
|
+
POST /garage/:garageId/repair-order/:id/event
|
|
28
|
+
|
|
29
|
+
// Transformation en facture : POST standard de facture avec eventId en plus ( InvoiceService.createInvoice() avec eventID ( id de l'OR))
|
|
30
|
+
// => l'API gère automatiquement le passage de l'OR en CLOSED
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Découpage en parties
|
|
36
|
+
|
|
37
|
+
| Partie | Contenu | Statut |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| **1** | Types TypeScript & constantes | ☐ |
|
|
40
|
+
| **2** | Service API (RepairOrderService) | ☐ |
|
|
41
|
+
| **3** | Toggle devis / OR dans la zone mémos | ☐ |
|
|
42
|
+
| **4** | Liste des OR DRAFT + dialog OR CLOSED | ☐ |
|
|
43
|
+
| **5** | Formulaire création / édition OR | ☐ |
|
|
44
|
+
| **6** | Actions de transformation (devis / facture / RDV) | ☐ |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
# PARTIE 1 — Types TypeScript & Constantes
|
|
51
|
+
|
|
52
|
+
> **Objectif** : Définir les types et constantes nécessaires à toute la feature.
|
|
53
|
+
> **Fichiers à créer/modifier** : cherche où sont définis le type `Event` et les types liés aux mémos pour s'aligner.
|
|
54
|
+
|
|
55
|
+
## 1.1 — Constante de setting
|
|
56
|
+
|
|
57
|
+
CodeSettings.CUSTOM_REPAIR_ORDER
|
|
58
|
+
|
|
59
|
+
## 1.2 — Enum état OR
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
export enum RepairOrderState {
|
|
63
|
+
DRAFT = 'DRAFT',
|
|
64
|
+
CLOSED = 'CLOSED',
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 1.3 — Type RepairOrder
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
// Se baser sur le type Event.EventType = "QUOTE existant en zone mémos comme référence de structure.
|
|
72
|
+
// Ajouter les champs spécifiques à l'OR :
|
|
73
|
+
|
|
74
|
+
export interface RepairOrder {
|
|
75
|
+
id: string;
|
|
76
|
+
eventType: 'REPAIR_ORDER';
|
|
77
|
+
state: RepairOrderState;
|
|
78
|
+
repairerId?: string; // ID de l'utilisateur réparateur (composant existant) EmployeeChoice.tsx
|
|
79
|
+
// ... reprendre tous les champs de QuoteEventDialog + formEventLight
|
|
80
|
+
// Cherche l'interface QuoteEventDialog pour compléter fidèlement
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 1.4 — Type payload création/édition
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// Même pattern que ( QuoteEventDialog.tsx fichier qui gere les appels API pour les devis de zone mémos)
|
|
88
|
+
export interface RepairOrderPayload {
|
|
89
|
+
state: RepairOrderState;
|
|
90
|
+
repairerId?: string;
|
|
91
|
+
// ... aligner sur QuotePayload
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## ✅ Validation Partie 1
|
|
96
|
+
- [ ] Constante CUSTOM_REPAIR_ORDER ajoutée
|
|
97
|
+
- [ ] Enum RepairOrderState créé
|
|
98
|
+
- [ ] Interface RepairOrder créée (alignée sur Quote)
|
|
99
|
+
- [ ] Interface RepairOrderPayload créée
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
# PARTIE 2 — Service API
|
|
106
|
+
|
|
107
|
+
> **Objectif** : Créer le service `RepairOrderService` avec tous les appels API.
|
|
108
|
+
> **Fichiers à créer** : cherche où est défini `src/services/GarageService.ts` (ou équivalent mémos) et créer `RepairOrderService` au même endroit avec le même pattern.
|
|
109
|
+
Attention je veux le meme systeme de pagination qui est présent pour les devis. cf: MyCalendarQuote.tsx
|
|
110
|
+
>
|
|
111
|
+
## 2.1 — CRUD de base
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
// RepairOrderService.ts
|
|
115
|
+
|
|
116
|
+
const BASE = (garageId: string) =>
|
|
117
|
+
`${API_BASE_URL}/garage/${garageId}/repair-order`;
|
|
118
|
+
|
|
119
|
+
// GET — liste tous les OR du garage
|
|
120
|
+
getAll(garageId: string): Promise<RepairOrder[]>
|
|
121
|
+
|
|
122
|
+
// POST — création
|
|
123
|
+
create(garageId: string, payload: RepairOrderPayload): Promise<RepairOrder>
|
|
124
|
+
|
|
125
|
+
// PATCH — modification (DRAFT uniquement)
|
|
126
|
+
update(garageId: string, id: string, payload: Partial<RepairOrderPayload>): Promise<RepairOrder>
|
|
127
|
+
|
|
128
|
+
// DELETE — suppression (DRAFT uniquement)
|
|
129
|
+
delete(garageId: string, id: string): Promise<void>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## 2.2 — Transformations
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
// → Devis
|
|
136
|
+
toQuote(garageId: string, repairOrderId: string): Promise<Quote>
|
|
137
|
+
// POST /garage/:garageId/repair-order/:id/quote
|
|
138
|
+
|
|
139
|
+
// → Facture
|
|
140
|
+
// Utiliser le POST standard de création de facture
|
|
141
|
+
// Ajouter eventId = repairOrderId dans le payload
|
|
142
|
+
// Cherche comment est fait le POST facture existant et ajouter le champ eventId
|
|
143
|
+
// L'API gère automatiquement le passage de l'OR en CLOSED
|
|
144
|
+
|
|
145
|
+
// → RDV (Event)
|
|
146
|
+
toEvent(garageId: string, repairOrderId: string): Promise<Event>
|
|
147
|
+
// POST /garage/:garageId/repair-order/:id/event
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## ✅ Validation Partie 2
|
|
151
|
+
- [ ] Service créé avec le même pattern que QuoteService
|
|
152
|
+
- [ ] CRUD complet implémenté
|
|
153
|
+
- [ ] Les 3 méthodes de transformation implémentées
|
|
154
|
+
- [ ] Gestion des erreurs alignée sur le pattern existant
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
# PARTIE 3 — Toggle Devis / OR dans la zone mémos
|
|
161
|
+
|
|
162
|
+
> **Objectif** : Ajouter un toggle dans le header de la zone mémos pour switcher entre la vue Devis et la vue OR.
|
|
163
|
+
> **Condition d'affichage** : uniquement si `garage.settings` contient `CUSTOM_REPAIR_ORDER` avec `valueBool: true`.
|
|
164
|
+
> **Fichiers à modifier** : cherche le composant de la zone mémos (MemoZone, MemoPanel, SidebarMemos ou similaire).
|
|
165
|
+
|
|
166
|
+
## 3.1 — Condition d'activation
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
// Cherche comment les autres settings (ex: CUSTOM_*) sont lus dans le composant mémos
|
|
170
|
+
// Appliquer le même pattern pour :
|
|
171
|
+
const isRepairOrderEnabled = garage.settings?.find(
|
|
172
|
+
(s) => s.key === 'CUSTOM_REPAIR_ORDER'
|
|
173
|
+
)?.valueBool ?? false;
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## 3.2 — Toggle UI
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
// Ajouter dans le header de la zone mémos, uniquement si isRepairOrderEnabled
|
|
180
|
+
|
|
181
|
+
// State local :
|
|
182
|
+
const [memoView, setMemoView] = useState<'QUOTE' | 'REPAIR_ORDER'>('QUOTE');
|
|
183
|
+
|
|
184
|
+
// Composant toggle (utiliser ToggleButtonGroup MUI ou le composant toggle existant dans le projet) :
|
|
185
|
+
{isRepairOrderEnabled && (
|
|
186
|
+
<ToggleButtonGroup
|
|
187
|
+
value={memoView}
|
|
188
|
+
exclusive
|
|
189
|
+
onChange={(_, value) => value && setMemoView(value)}
|
|
190
|
+
size="small"
|
|
191
|
+
>
|
|
192
|
+
<ToggleButton value="QUOTE">Devis</ToggleButton>
|
|
193
|
+
<ToggleButton value="REPAIR_ORDER">OR</ToggleButton>
|
|
194
|
+
</ToggleButtonGroup>
|
|
195
|
+
)}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## 3.3 — Affichage conditionnel
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
// Dans le rendu de la zone mémos :
|
|
202
|
+
{memoView === 'QUOTE' && <QuoteMemoList ... />} {/* composant existant, inchangé */}
|
|
203
|
+
{memoView === 'REPAIR_ORDER' && isRepairOrderEnabled && <RepairOrderMemoList ... />}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## ✅ Validation Partie 3
|
|
207
|
+
- [ ] Toggle invisible si CUSTOM_REPAIR_ORDER = false
|
|
208
|
+
- [ ] Toggle visible et fonctionnel si CUSTOM_REPAIR_ORDER = true
|
|
209
|
+
- [ ] Vue devis inchangée
|
|
210
|
+
- [ ] Bascule vers vue OR affiche un placeholder (le composant sera fait en Partie 4)
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
# PARTIE 4 — Liste des OR DRAFT + Dialog OR CLOSED
|
|
217
|
+
|
|
218
|
+
> **Objectif** : Créer le composant liste OR (zone mémos) et le dialog listant les OR CLOSED.
|
|
219
|
+
> **Base** : Reprendre le composant liste Devis en zone mémos comme référence visuelle et structurelle.
|
|
220
|
+
|
|
221
|
+
## 4.1 — Composant `RepairOrderMemoList`
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
// Nouveau composant, même structure que le composant liste devis en zone mémos.
|
|
225
|
+
|
|
226
|
+
// Comportements :
|
|
227
|
+
// - Appelle RepairOrderService.getAll() au mount
|
|
228
|
+
// - Filtre les résultats : afficher UNIQUEMENT state === 'DRAFT'
|
|
229
|
+
// - Même card/ligne que les devis (aligner sur l'existant)
|
|
230
|
+
// - Bouton "Nouvel OR" → ouvre le formulaire OR (Partie 5)
|
|
231
|
+
// - Bouton "OR archivés" (ou icône) → ouvre RepairOrderClosedDialog
|
|
232
|
+
|
|
233
|
+
// Chaque item de la liste :
|
|
234
|
+
// - Affiche : numéro OR, client, véhicule, réparateur, montant
|
|
235
|
+
// - Actions : éditer (si DRAFT), supprimer (si DRAFT)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## 4.2 — Composant `RepairOrderClosedDialog`
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
// Dialog MUI listant tous les OR en état CLOSED.
|
|
242
|
+
// Cherche un dialog de liste existant dans le projet comme référence de layout.
|
|
243
|
+
|
|
244
|
+
// Contenu :
|
|
245
|
+
// - Titre : "Ordres de réparation archivés"
|
|
246
|
+
// - Champ recherche : filtre par numéro OR ou nom client (filtre local sur les données déjà chargées)
|
|
247
|
+
// - Liste des OR CLOSED : numéro, client, véhicule, date, montant
|
|
248
|
+
// - Chaque ligne : read-only, pas d'action de suppression
|
|
249
|
+
// - Bouton fermer
|
|
250
|
+
|
|
251
|
+
// Props :
|
|
252
|
+
interface RepairOrderClosedDialogProps {
|
|
253
|
+
open: boolean;
|
|
254
|
+
onClose: () => void;
|
|
255
|
+
garageId: string;
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## ✅ Validation Partie 4
|
|
260
|
+
- [ ] Liste affiche uniquement les DRAFT
|
|
261
|
+
- [ ] Bouton "OR archivés" ouvre le dialog CLOSED
|
|
262
|
+
- [ ] Recherche par numéro / client fonctionnelle dans le dialog
|
|
263
|
+
- [ ] Les OR CLOSED sont read-only dans le dialog
|
|
264
|
+
- [ ] Bouton "Nouvel OR" présent (peut pointer vers un placeholder pour l'instant)
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
# PARTIE 5 — Formulaire Création / Édition OR
|
|
271
|
+
|
|
272
|
+
> **Objectif** : Créer le formulaire OR (création et édition), basé sur le formulaire devis existant en zone mémos, avec un champ réparateur en plus.
|
|
273
|
+
> **Base** : Reprendre le formulaire devis mémos comme référence. Ajouter uniquement ce qui diffère.
|
|
274
|
+
|
|
275
|
+
## 5.1 — Champ Réparateur
|
|
276
|
+
|
|
277
|
+
```tsx
|
|
278
|
+
// Un composant de sélection d'utilisateur du garage existe déjà dans le projet.
|
|
279
|
+
// Cherche son nom (ex: GarageUserSelect, RepairerSelect, UserSelector ou similaire).
|
|
280
|
+
// L'intégrer dans le formulaire avec le champ : repairerId
|
|
281
|
+
|
|
282
|
+
// Placement suggéré : après les infos client/véhicule, avant les lignes de détail
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## 5.2 — Structure du formulaire
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
[Infos client / véhicule] ← identique au formulaire devis
|
|
289
|
+
[Réparateur] ← nouveau champ spécifique OR
|
|
290
|
+
[Lignes de détail] ← identique au formulaire devis
|
|
291
|
+
[Totaux] ← identique au formulaire devis
|
|
292
|
+
[Actions] ← voir 5.3
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## 5.3 — Actions du formulaire
|
|
296
|
+
|
|
297
|
+
**À la création (mode création) :**
|
|
298
|
+
```
|
|
299
|
+
[Annuler] [Enregistrer comme brouillon]
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Après création (mode édition sur un DRAFT) :**
|
|
303
|
+
```
|
|
304
|
+
[Supprimer] [Télécharger] [Enregistrer]
|
|
305
|
+
```
|
|
306
|
+
> Le bouton "Télécharger" ouvre :
|
|
307
|
+
> ```tsx
|
|
308
|
+
> <DownloadRepairOrderDialog
|
|
309
|
+
> open={isShowConfirmDownloadRepairOrder}
|
|
310
|
+
> onClose={onCloseConfirmDownloadRepairOrder}
|
|
311
|
+
> event={localEvent}
|
|
312
|
+
> garage={garage}
|
|
313
|
+
> />
|
|
314
|
+
> ```
|
|
315
|
+
> Cherche comment `DownloadRepairOrderDialog` est déjà utilisé dans le projet et reproduire le même pattern d'ouverture/fermeture.
|
|
316
|
+
|
|
317
|
+
**Mode lecture (OR CLOSED) :**
|
|
318
|
+
```
|
|
319
|
+
Formulaire entièrement read-only, aucune action d'édition
|
|
320
|
+
Les actions de transformation (Partie 6) restent visibles
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## 5.4 — Gestion du state local
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
// Même pattern que le formulaire devis mémos (Formik ou useState selon ce qui est utilisé)
|
|
327
|
+
// Cherche le formulaire devis mémos et aligner exactement
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## ✅ Validation Partie 5
|
|
331
|
+
- [ ] Formulaire création fonctionnel (POST)
|
|
332
|
+
- [ ] Formulaire édition fonctionnel (PATCH, DRAFT uniquement)
|
|
333
|
+
- [ ] Champ réparateur intégré
|
|
334
|
+
- [ ] Bouton télécharger ouvre DownloadRepairOrderDialog
|
|
335
|
+
- [ ] OR CLOSED : formulaire read-only
|
|
336
|
+
- [ ] Suppression uniquement sur DRAFT
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
# PARTIE 6 — Actions de Transformation
|
|
343
|
+
|
|
344
|
+
> **Objectif** : Ajouter les actions de transformation d'un OR vers Devis / Facture / RDV.
|
|
345
|
+
> **Condition** : Ces actions sont disponibles sur un OR (DRAFT ou CLOSED, à confirmer avec le back).
|
|
346
|
+
> **Base** : Cherche comment la transformation devis → RDV est faite actuellement et appliquer le même pattern.
|
|
347
|
+
|
|
348
|
+
## 6.1 — Placement des actions
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
// Dans le formulaire OR (Partie 5), section actions :
|
|
352
|
+
// Ajouter un groupe d'actions "Transformer en" visible après la création de l'OR
|
|
353
|
+
|
|
354
|
+
[Transformer en Devis] [Transformer en Facture] [Transformer en RDV]
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## 6.2 — Transformer en Devis
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
// Appel : RepairOrderService.toQuote(garageId, repairOrderId)
|
|
361
|
+
// POST /garage/:garageId/repair-order/:id/quote
|
|
362
|
+
// => L'API gère le passage de l'OR en CLOSED
|
|
363
|
+
// Après succès : fermer le formulaire OR, rafraîchir la liste mémos côté devis
|
|
364
|
+
// Optionnel : ouvrir directement le devis créé (aligner sur le comportement existant)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## 6.3 — Transformer en Facture
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
// Utiliser le POST standard de création de facture
|
|
371
|
+
// Cherche comment est déclenché ce POST depuis les devis actuellement
|
|
372
|
+
// Ajouter eventId = repairOrderId dans le payload
|
|
373
|
+
// => L'API gère le passage de l'OR en CLOSED automatiquement
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## 6.4 — Transformer en RDV
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
// Même comportement que la transformation devis → RDV existante
|
|
380
|
+
// Cherche ce comportement dans le code et reproduire à l'identique
|
|
381
|
+
// POST /garage/:garageId/repair-order/:id/event
|
|
382
|
+
// => Ouvrir le dialog de création de RDV existant, pré-rempli avec les données de l'OR
|
|
383
|
+
// => L'API gère le passage de l'OR en CLOSED
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## 6.5 — Feedback utilisateur
|
|
387
|
+
|
|
388
|
+
```ts
|
|
389
|
+
// Après chaque transformation réussie :
|
|
390
|
+
// - Snackbar/toast de confirmation (aligner sur le pattern existant)
|
|
391
|
+
// - Rafraîchissement de la liste mémos
|
|
392
|
+
// - Fermeture du formulaire OR si pertinent
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## ✅ Validation Partie 6
|
|
396
|
+
- [ ] Transformation → Devis fonctionnelle
|
|
397
|
+
- [ ] Transformation → Facture fonctionnelle (avec eventId dans payload)
|
|
398
|
+
- [ ] Transformation → RDV ouvre le dialog existant pré-rempli
|
|
399
|
+
- [ ] L'OR passe bien en CLOSED après transformation (vérifié via GET)
|
|
400
|
+
- [ ] Feedback utilisateur présent sur chaque action
|
|
401
|
+
- [ ] Liste mémos rafraîchie après transformation
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Checklist globale finale
|
|
408
|
+
|
|
409
|
+
- [ ] Partie 1 — Types & constantes
|
|
410
|
+
- [ ] Partie 2 — Service API
|
|
411
|
+
- [ ] Partie 3 — Toggle zone mémos
|
|
412
|
+
- [ ] Partie 4 — Liste DRAFT + dialog CLOSED
|
|
413
|
+
- [ ] Partie 5 — Formulaire création/édition
|
|
414
|
+
- [ ] Partie 6 — Transformations
|
|
415
|
+
|
|
416
|
+
> **Rappel Claude Code** : à chaque partie, lis d'abord le code existant correspondant (Quote, devis mémos, transformations existantes) avant d'écrire une seule ligne. L'objectif est de s'intégrer proprement dans les patterns déjà en place, pas de réinventer.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Session — Module Inventaire (stock)
|
|
2
|
+
|
|
3
|
+
Date : 2026-05-22
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Fichiers créés
|
|
8
|
+
|
|
9
|
+
| Fichier | Rôle |
|
|
10
|
+
|---|---|
|
|
11
|
+
| `src/models/Inventory.ts` | Classe `Inventory`, enums `InventoryStatus` (DRAFT / IN_PROGRESS / CLOSED / CANCELLED) et `InventoryType` (FULL / PARTIAL), labels FR |
|
|
12
|
+
| `src/models/Billing/Stock/InventoryLine.ts` | Classe `InventoryLine` — lignes de comptage (théorique, comptée, écart, `isCounted`, `hasGap`, `isAddedDuringSession`) |
|
|
13
|
+
| `src/services/billing/InventoryService.ts` | Tous les appels API inventaire (voir détail ci-dessous) |
|
|
14
|
+
| `src/pages/billing/Inventories.tsx` | Page liste des inventaires avec filtres + colonne actions |
|
|
15
|
+
| `src/components/Billing/Form/PickerInventoryStatus.tsx` | Picker bouton + popover pour filtrer par statut (même pattern que `PickerMovementType`) |
|
|
16
|
+
| `src/components/Billing/Dialog/CreateInventoryDialog.tsx` | Dialog création d'inventaire (type, dépôt, includeZeroStock, scope produits/emplacements pour PARTIAL, commentaire) |
|
|
17
|
+
| `src/components/Billing/Dialog/InventoryLinesDialog.tsx` | Dialog comptage des lignes : inline edit quantité + commentaire (champs non-contrôlés via refs), ajout ligne hors-scope, suppression ligne manuelle |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Fichiers modifiés
|
|
22
|
+
|
|
23
|
+
| Fichier | Changement |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `src/pages/billing/billingIndex.tsx` | Entrée menu "Inventaires" + route `/inventories` gatées par `isStockActive` |
|
|
26
|
+
| `src/models/Billing/Stock/Depot.ts` | Classe `DepotLocation` ajoutée (id, code, label) |
|
|
27
|
+
| `src/services/billing/DepotService.ts` | `useFetchDepotLocations`, `useFetchAllDepotLocations`, `DepotLocationService` (create/update/delete), `invalidateQueriesDepotLocation` |
|
|
28
|
+
| `src/pages/setting/tabContent/Depot/DepotModule.tsx` | Refonte en cards par dépôt avec liste d'emplacements inline (edit/delete) + bouton création emplacement |
|
|
29
|
+
| `src/models/Billing/Product.ts` | Champ `depotLocationIds: string[]` ajouté |
|
|
30
|
+
| `src/components/Billing/Dialog/ProductFormDialog.tsx` | Picker `PickerDepotLocations` inséré sous le switch "Suivi en stock" |
|
|
31
|
+
| `src/components/Billing/Form/PickerDepotLocations.tsx` | Autocomplete multi-sélection emplacements groupés par dépôt, chips deletable, bg `primary.light` sur options sélectionnées |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## InventoryService — méthodes
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
useFetchInventories(garageId, page, limit, filter)
|
|
39
|
+
// filter: { depotId?, status?, startDate?, endDate? }
|
|
40
|
+
|
|
41
|
+
useFetchInventoryLines(garageId, inventoryId, page, limit, filter)
|
|
42
|
+
// filter: { depotLocationId?, isCounted?, hasGap? }
|
|
43
|
+
|
|
44
|
+
InventoryService.create(garageId, payload, queryClient)
|
|
45
|
+
// payload: { type, depotId, startDate, comment, includeZeroStock, scope? }
|
|
46
|
+
// scope: { catalogIds[], locationIds[] } — PARTIAL seulement
|
|
47
|
+
|
|
48
|
+
InventoryService.start(garageId, inventoryId, queryClient)
|
|
49
|
+
// POST .../start (DRAFT → IN_PROGRESS)
|
|
50
|
+
|
|
51
|
+
InventoryService.close(garageId, inventoryId, queryClient, forceClose?)
|
|
52
|
+
// POST .../close?forceClose=true (IN_PROGRESS → CLOSED)
|
|
53
|
+
|
|
54
|
+
InventoryService.cancel(garageId, inventoryId, queryClient)
|
|
55
|
+
// POST .../cancel
|
|
56
|
+
|
|
57
|
+
InventoryService.addLine(garageId, inventoryId, catalogId, queryClient)
|
|
58
|
+
// POST .../lines { catalogId }
|
|
59
|
+
|
|
60
|
+
InventoryService.deleteLine(garageId, inventoryId, lineId, queryClient)
|
|
61
|
+
// DELETE .../lines/{lineId} (isAddedDuringSession && !isCounted)
|
|
62
|
+
|
|
63
|
+
InventoryService.patchLine(garageId, inventoryId, lineId, payload, queryClient)
|
|
64
|
+
// PATCH .../lines/{lineId} { countedQuantity, comment? }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Points d'attention
|
|
70
|
+
|
|
71
|
+
- **DepotLocationFormDialog** : dialog Formik+Yup pour créer/modifier un emplacement, même style que `DepotFormDialog`.
|
|
72
|
+
- **InventoryLinesDialog** — champs quantité et commentaire sont **non-contrôlés** (`defaultValue` + `inputRef`) pour éviter la perte de focus à chaque frappe (re-render de `displayedColumns` dans `MovaTableBack`).
|
|
73
|
+
- **CreateInventoryDialog** — produits sélectionnés stockés dans un state séparé (`selectedProducts`) indépendant des résultats de recherche, pour que les chips restent affichés quand la recherche est vidée.
|
|
74
|
+
- **Filtres Inventaires** : réutilise `AtPeriodFilterMenu` de `Stocks.tsx` (export nommé).
|
|
75
|
+
- **billingIndex** : les entrées "Mouvements" et "Inventaires" sont toutes deux gatées par `isStockActive(garage)`.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# TICKET — Refacto performance MyServices.tsx
|
|
2
|
+
|
|
3
|
+
**Branche :** `vite-compiler-up-to-date`
|
|
4
|
+
**Fichier :** `src/pages/setting/tabContent/MyServices.tsx`
|
|
5
|
+
**Date :** 2026-05-12
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Contexte
|
|
10
|
+
|
|
11
|
+
Le composant `MyServices` présentait de graves problèmes de performance sur les tableaux de prestations, notamment lors de la modification des valeurs (downtime, appointmentDelay). Chaque appel à `refreshGarage` (déclenché après une sauvegarde) mettait à jour la prop `garage` dans le parent, ce qui provoquait un remount complet de tous les composants de ligne — le `memo()` était inefficace car les composants étaient définis à l'intérieur du render.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Travail réalisé
|
|
16
|
+
|
|
17
|
+
### 1. Refacto architecture — extraction des composants + Context
|
|
18
|
+
|
|
19
|
+
**Problème :** `CategoryPrestationRow` et `PrestationRow` étaient définis à l'intérieur de `MyServices`. React recréait un nouveau type de composant à chaque render → unmount/remount complet de toutes les lignes à chaque `refreshGarage`.
|
|
20
|
+
|
|
21
|
+
**Solution :**
|
|
22
|
+
- Extraction de `CategoryPrestationRow` et `PrestationRow` en composants top-level
|
|
23
|
+
- Création de `MyServicesContext` pour partager sans prop drilling : `garageId`, `garageKey`, `prestationCategories`, `teamManagementActive`, `showSnackbar`, `refreshGarage`, `savePrestation`
|
|
24
|
+
- `PrestationRow` consomme le cache TanStack Query via `useQuery({ select })` — ne re-render que si SA prestation change, indépendamment du reste
|
|
25
|
+
|
|
26
|
+
**Fix stale closure :** `handleInputChange` utilise `setFormRow(prev => ...)` — plus de lecture périmée sur les frappes rapides.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
### 2. Fix toggle "Visible par les clients" ne se mettant pas à jour
|
|
31
|
+
|
|
32
|
+
**Problème :** Le `setQueryData` dans `handleChangeAvailableOnline` utilisait `[GARAGES_SETTINGS, garageId]` (string) au lieu de `garageKey` qui contient `garage.id` (nombre) — la clé ne correspondait pas à l'entrée de cache existante.
|
|
33
|
+
|
|
34
|
+
**Solution :** `setQueryData(garageKey, ...)` à la place.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
### 3. Loader pendant la sauvegarde des valeurs de prestation
|
|
39
|
+
|
|
40
|
+
Pendant l'appel API de mise à jour (downtime / appointmentDelay), les cellules concernées affichent un `CircularProgress` à la place de la valeur jusqu'à la résolution de la Promise.
|
|
41
|
+
|
|
42
|
+
- `savePrestation` retourne désormais une `Promise<void>`
|
|
43
|
+
- `PrestationRow` gère un état `isSaving` activé au submit, désactivé dans le `.finally()`
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
### 4. Barre de recherche sur chaque tableau
|
|
48
|
+
|
|
49
|
+
**Tableau des prestations actives :**
|
|
50
|
+
- Champ de recherche filtrant par nom de prestation
|
|
51
|
+
- Sans recherche : affichage normal par catégories (accordion)
|
|
52
|
+
- Avec recherche : liste plate triée alphabétiquement, header adapté (Nom / Immobilisation / Délai / Visible / Actions)
|
|
53
|
+
- Aucun résultat : placeholder "Aucune prestation ne correspond à votre recherche"
|
|
54
|
+
|
|
55
|
+
**Tableau des prestations inactives :**
|
|
56
|
+
- Même comportement de filtrage sur `visibleRows`
|
|
57
|
+
- Placeholder différencié selon que la liste est vide naturellement ou à cause de la recherche
|
|
58
|
+
|
|
59
|
+
**Les deux inputs disposent d'une icône `ClearIcon` (croix) qui apparaît uniquement quand du texte est présent, et remet l'input à vide au clic.**
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Points techniques notables
|
|
64
|
+
|
|
65
|
+
- `garageKey` conserve le type original de `garage.id` (nombre) pour correspondre à la clé TanStack Query existante ; `garageId` (string) est réservé aux appels API REST
|
|
66
|
+
- `memo()` sur `CategoryPrestationRow` a un comparateur explicite sur `code`, `color` et `prestations`
|
|
67
|
+
- `filteredActiveCategories` et `flatActivePrestations` sont mémoïsés avec `useMemo`
|