@amsom-habitat/cresol 0.1.0

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/README.md ADDED
@@ -0,0 +1,508 @@
1
+ # Cresol
2
+
3
+ Assistant de **création de sollicitation**, extrait de Cally2 et réutilisable par les outils
4
+ AMSOM Habitat.
5
+
6
+ L'assistant est **autonome** : il charge lui-même la nomenclature, l'arbre patrimonial et les
7
+ collaborateurs, puis poste la création et les documents joints. Il ne connaît rien de l'outil
8
+ qui l'héberge. Celui-ci lui donne :
9
+
10
+ - les **réclamants** possibles (le dossier consulté) ;
11
+ - éventuellement un **brouillon pré-alimenté** et des **champs figés** ;
12
+ - le contenu des **volets latéraux** (sollicitations similaires, évènements), qui restent les
13
+ siens.
14
+
15
+ En retour, il reçoit des **événements** à chaque moment clé : avant et après la création, et
16
+ aux changements de réclamant, de motif et d'étape.
17
+
18
+ ```text
19
+ ┌──────────────── outil hôte ────────────────┐
20
+ │ props : reclamants, modelValue, readonly… │
21
+ │ slots : volets similaires / évènements │
22
+ └───────────────┬────────────────▲───────────┘
23
+ │ │ événements : beforeCreate, created,
24
+ ▼ │ createError, motifChange…
25
+ ┌──────────────── Cresol ────────────────┐
26
+ │ [Réclamant] › Catégorisation › Cible › │
27
+ │ Détails → POST création + documents │
28
+ └────────────────────────────────────────┘
29
+ ```
30
+
31
+ ## Sommaire
32
+
33
+ - [Installation](#installation)
34
+ - [Configuration](#configuration)
35
+ - [Démarrage rapide](#démarrage-rapide)
36
+ - [Props](#props)
37
+ - [Événements](#événements)
38
+ - [Slots](#slots)
39
+ - [Le choix du réclamant](#le-choix-du-réclamant)
40
+ - [Champs figés (`readonly`)](#champs-figés-readonly)
41
+ - [Restreindre la nomenclature](#restreindre-la-nomenclature)
42
+ - [Le brouillon (`v-model`)](#le-brouillon-v-model)
43
+ - [La création](#la-création)
44
+ - [Exports](#exports)
45
+ - [Endpoints consommés](#endpoints-consommés)
46
+ - [Développement](#développement)
47
+
48
+ ## Installation
49
+
50
+ ```bash
51
+ npm i @amsom-habitat/cresol
52
+ ```
53
+
54
+ Pas de feuille de style à importer : le CSS est injecté par le JS du paquet. L'outil fournit, lui,
55
+ Bootstrap (`@amsom-habitat/bootstrap-5`) et le composant global `font-awesome-icon`. Les icônes
56
+ portées par les données (motifs, catégories…) sont enregistrées à la volée par le paquet.
57
+
58
+ ## Configuration
59
+
60
+ À poser **une fois** au démarrage de l'outil :
61
+
62
+ ```javascript
63
+ import { configureCresol } from '@amsom-habitat/cresol'
64
+
65
+ configureCresol({ baseUrl: import.meta.env.VITE_API_URL })
66
+ ```
67
+
68
+ | Option | Défaut (`@amsom-habitat/user-manager`) | Rôle |
69
+ | --------------- | -------------------------------------- | ----------------------------------- |
70
+ | `baseUrl` | — (**obligatoire**) | Racine de l'API métier |
71
+ | `getToken` | cookie partagé du domaine | Jeton envoyé à chaque appel |
72
+ | `setToken` | écrit le cookie | Jeton renouvelé (refresh 449) |
73
+ | `logout` | déconnexion du domaine | Refresh impossible |
74
+ | `goToLoginPage` | page de login du domaine | 401 / 498 sur un appel qui redirige |
75
+
76
+ Un outil du domaine ne pose que `baseUrl` : le jeton suit le SSO. Les autres options servent à un
77
+ outil qui gère son authentification lui-même.
78
+
79
+ Deux props surchargent la configuration **pour une instance** : `baseUrl` et `token`. Ordre de
80
+ priorité : **prop › `configureCresol` › défaut**. Elles servent aux cas de bord : storybook, démo,
81
+ seconde API, outil hors du domaine. Sans `baseUrl` nulle part, l'appel échoue avec une erreur
82
+ explicite plutôt que de partir en relatif sur l'origine de la page.
83
+
84
+ ## Démarrage rapide
85
+
86
+ ```vue
87
+ <template>
88
+ <creer-sollicitation-modal
89
+ v-if="open"
90
+ :reclamants="[{ type: 'L', id: 2200523, libelle: 'Contrat' }]"
91
+ :model-value="{ provenance: 'T' }"
92
+ :readonly="['reclamant']"
93
+ @created="onCreated"
94
+ @error="$toast.error($event)"
95
+ @close="open = false"
96
+ >
97
+ <template #aside-similaires="{ idMotif, codeProgramme, idCible }">
98
+ <mes-similaires :id-motif="idMotif" :code-programme="codeProgramme" :id-cible="idCible" />
99
+ </template>
100
+ <template #aside-evenements>
101
+ <mes-evenements-de-patrimoine />
102
+ </template>
103
+ </creer-sollicitation-modal>
104
+ </template>
105
+
106
+ <script>
107
+ import { CreerSollicitationModal } from '@amsom-habitat/cresol'
108
+
109
+ export default {
110
+ components: { CreerSollicitationModal },
111
+ data: () => ({ open: true }),
112
+ methods: {
113
+ onCreated(sollicitations) {
114
+ this.$toast.success(`${sollicitations.length} sollicitation(s) créée(s)`)
115
+ // … recharger le listing de l'outil
116
+ }
117
+ }
118
+ }
119
+ </script>
120
+ ```
121
+
122
+ Deux composants, **même contrat** (props, événements, slots, définis une seule fois dans
123
+ `src/props.js`) :
124
+
125
+ - **`CreerSollicitationForm`** : l'assistant seul, à placer dans une page ou dans sa propre modale ;
126
+ - **`CreerSollicitationModal`** : le même, dans une `AmsomModal`. Il ajoute les props `titre`
127
+ (défaut `'Nouvelle sollicitation'`) et `closeOnCreated` (défaut `true` : émet `close` juste
128
+ après `created`), et l'événement `close` (croix, ou création réussie). Ses attributs non
129
+ déclarés (`reducible`, `title`…) descendent sur `AmsomModal`.
130
+
131
+ ## Props
132
+
133
+ | Prop | Type | Défaut | Rôle |
134
+ | -------------------- | ---------------- | ---------- | -------------------------------------------------------------------------------------- |
135
+ | `reclamants` | Array | `[]` | Réclamants proposables : `[{ type, id, libelle }]` ([détail](#le-choix-du-réclamant)) |
136
+ | `reclamantMode` | String | `'select'` | Présentation du choix : `select`, `switch`, `autocomplete`, `step` |
137
+ | `modelValue` | Object | `null` | Brouillon pré-alimenté, partiel ; suivi en `v-model` ([détail](#le-brouillon-v-model)) |
138
+ | `readonly` | Boolean \| Array | `false` | Champs figés : `true`, `false` ou une liste ([détail](#champs-figés-readonly)) |
139
+ | `modules` | Array | `null` | Modules du réclamant ; laissés `null`, chargés pour un réclamant contrat |
140
+ | `idModule` | String \| Number | `null` | Module à préférer comme cible par défaut parmi ceux du réclamant |
141
+ | `nomenclatureFilter` | Function | `null` | Prédicat : quels motifs sont proposables ([détail](#restreindre-la-nomenclature)) |
142
+ | `aside` | Boolean | `true` | Affiche le volet latéral (ses onglets viennent des slots) |
143
+ | `nbSimilaires` | Number | `0` | Badge de l'onglet « Similarité » |
144
+ | `baseUrl` | String | `null` | API de cette instance (sinon `configureCresol`) |
145
+ | `token` | String | `null` | Jeton de cette instance (sinon `configureCresol`) |
146
+
147
+ Chaque prop porte **une seule responsabilité**. Les valeurs de la saisie passent par
148
+ `modelValue`, les choix possibles par `reclamants`, ce qui est modifiable par `readonly`, et la
149
+ présentation du réclamant par `reclamantMode`. Il n'y a donc pas de prop `provenance` : on la
150
+ passe dans `modelValue` (`{ provenance: 'M' }`).
151
+
152
+ `modelValue` n'est lu qu'**à l'ouverture** : le modifier ensuite ne réinitialise pas l'assistant.
153
+ Pour repartir d'un autre brouillon, on remonte le composant (`v-if` ou `:key`).
154
+
155
+ ## Événements
156
+
157
+ | Événement | Quand | Charge utile |
158
+ | ------------------- | ----------------------------------------- | ------------------------------------------------------------ |
159
+ | `update:modelValue` | à chaque modification du brouillon | le brouillon |
160
+ | `reclamantChange` | l'utilisateur change de réclamant | l'entrée de `reclamants` choisie |
161
+ | `motifChange` | le motif ou les motifs associés changent | `{ idCategorie, idType, idNature, idMotif, motifsAssocies }` |
162
+ | `stepChange` | changement d'étape | `{ step, key, previous: { step, key } }` |
163
+ | `beforeCreate` | clic sur « Créer », **avant** l'appel API | `{ payload, cancel, waitUntil }` |
164
+ | `created` | création réussie (documents compris) | `(sollicitations, { payload, fichiers })` |
165
+ | `createError` | l'API a refusé la création | `{ error, message, payload }` |
166
+ | `error` | **toute** erreur affichable | `message` (chaîne) |
167
+ | `close` | (modale) fermeture demandée | — |
168
+
169
+ `reclamantChange`, `motifChange` et `stepChange` ne sont **pas émis pour l'état initial** : l'outil
170
+ le connaît déjà, puisque c'est lui qui l'a fourni. Ils rendent compte de ce que fait l'utilisateur.
171
+
172
+ ### Le cycle de la création
173
+
174
+ ```text
175
+ clic « Créer »
176
+ │
177
+ ├─ beforeCreate ── cancel() / waitUntil(false | rejet) ──▶ rien n'est envoyé, l'assistant reste ouvert
178
+ │
179
+ ├─ POST creer_sollicitation ── échec ──▶ createError + error, l'assistant reste ouvert
180
+ │
181
+ ├─ POST upload_sollicitation_ged (un par document, en parallèle)
182
+ │ échec d'un document ──▶ error (la création, elle, est acquise)
183
+ │
184
+ └─ created(sollicitations, { payload, fichiers }) (modale : puis close)
185
+ ```
186
+
187
+ Pendant tout le cycle, l'assistant est sous overlay et un second clic est ignoré.
188
+
189
+ **Succès et échec sont séparés** (`created` / `createError`) : l'outil n'y fait pas la même
190
+ chose (fermer et recharger d'un côté, garder la saisie et expliquer de l'autre), et chacun
191
+ porte une charge utile différente. `error` reste le **canal unique des messages** pour qui ne
192
+ veut que toaster : il reçoit aussi les échecs de création, ceux des documents et ceux des
193
+ chargements. Règle simple :
194
+
195
+ - pour **afficher** une erreur, écouter `error` ;
196
+ - pour **réagir** à l'échec de la création (journaliser, proposer autre chose…), écouter
197
+ `createError`.
198
+
199
+ ### `beforeCreate` : intervenir avant l'envoi
200
+
201
+ L'événement porte un objet :
202
+
203
+ | Clé | Rôle |
204
+ | -------------------- | ------------------------------------------------------------------------------ |
205
+ | `payload` | le corps qui va partir ([forme](#la-création)) ; **modifiable en place** |
206
+ | `cancel()` | annule la création |
207
+ | `waitUntil(promise)` | fait attendre l'envoi ; résolue à `false`, ou rejetée, elle annule la création |
208
+
209
+ ```javascript
210
+ // Confirmation
211
+ onBeforeCreate({ payload, waitUntil }) {
212
+ waitUntil(this.$confirm(`Créer ${payload.lignes.length} sollicitation(s) ?`))
213
+ }
214
+
215
+ // Veto synchrone
216
+ onBeforeCreate({ payload, cancel }) {
217
+ if (!this.peutCreer(payload)) cancel()
218
+ }
219
+
220
+ // Enrichir le payload
221
+ onBeforeCreate({ payload }) {
222
+ payload.lignes.forEach((ligne) => (ligne.description += `\n[${this.origine}]`))
223
+ }
224
+ ```
225
+
226
+ Une annulation est **silencieuse** : rien n'est émis ensuite. C'est l'outil qui l'a décidée, il
227
+ sait déjà pourquoi.
228
+
229
+ ### `created` : le compte rendu
230
+
231
+ ```javascript
232
+ onCreated(sollicitations, { payload, fichiers }) {
233
+ // sollicitations : une par ligne, sérialisées par l'API ([{ id, idMotif, … }])
234
+ // fichiers : un compte rendu par document joint
235
+ // [{ idMotif, nom, idSollicitation, ok: true | false, message }]
236
+ const enEchec = fichiers.filter((f) => !f.ok)
237
+ }
238
+ ```
239
+
240
+ Un document en échec **n'annule pas** la création : il est signalé par `error` et marqué
241
+ `ok: false`, et l'utilisateur pourra le joindre depuis le détail de la sollicitation.
242
+
243
+ ### Les autres événements
244
+
245
+ ```javascript
246
+ // Recharger ce qui dépend du réclamant (sollicitations similaires, évènements…)
247
+ onReclamantChange(reclamant) {}
248
+
249
+ // Suggestions, alertes métier propres à l'outil sur un motif
250
+ onMotifChange({ idMotif, motifsAssocies }) {}
251
+
252
+ // Suivi d'usage, aide contextuelle par étape
253
+ onStepChange({ key, previous }) {} // key : 'reclamant' | 'categorie' | 'cible' | 'details'
254
+ ```
255
+
256
+ ## Slots
257
+
258
+ | Slot | Contenu | Absent ⇒ |
259
+ | ------------------ | ----------------------------------------------- | --------------- |
260
+ | `aside-similaires` | onglet « Similarité » ; scopé (voir ci-dessous) | pas d'onglet |
261
+ | `aside-evenements` | onglet « Événements » | pas d'onglet |
262
+ | `aside` | remplace tout le volet latéral | volet à onglets |
263
+
264
+ Les deux volets appartiennent à l'**outil** : c'est lui qui interroge son API et rend ses cartes.
265
+ Le paquet ne lui fournit que le **scope** de la saisie en cours :
266
+
267
+ | Clé du scope | Valeur |
268
+ | --------------- | ----------------------------------------------------------------------- |
269
+ | `idMotif` | motif de la ligne principale |
270
+ | `codeProgramme` | programme de la localisation ciblée (nul quand la cible est la demande) |
271
+ | `idCible` | id de la demande ciblée (nul sinon) |
272
+ | `reclamant` | le réclamant sélectionné |
273
+
274
+ Le badge de l'onglet revient par la prop `nbSimilaires`.
275
+
276
+ ## Le choix du réclamant
277
+
278
+ `reclamants` est une **liste ordonnée** d'entrées `{ type, id, libelle }` :
279
+
280
+ - `type` est le code `KRCLENT.RCLT` : `D` = demande, `U` = individu « Autre », `T` = tiers, tout
281
+ autre code = **contrat** (`L`, `P`…) ;
282
+ - `libelle` est ce qui est affiché (côté Cally : « Contrat », « Demande », « Partie externe ») ;
283
+ - il n'y a pas de clé technique : un réclamant s'identifie par son couple `type` + `id`.
284
+
285
+ Le premier de la liste est sélectionné, sauf si `modelValue.reclamant` (`{ type, id }`) en
286
+ désigne un autre. Le type choisi pilote la suite :
287
+
288
+ | Réclamant | Cible proposée |
289
+ | ------------------------- | ------------------------------------------------------------------------------ |
290
+ | contrat | le contrat lui-même, sur l'un de ses modules (chargés si `modules` est `null`) |
291
+ | demande (`D`) | toujours la demande, sans localisation |
292
+ | partie externe (`U`, `T`) | une localisation du patrimoine (pas de contrat propre) |
293
+
294
+ Présentation, selon `reclamantMode` :
295
+
296
+ | `reclamantMode` | Rendu |
297
+ | --------------- | --------------------------------------------------------------------- |
298
+ | `select` | liste déroulante (défaut) |
299
+ | `switch` | boutons côte à côte |
300
+ | `autocomplete` | `AmsomAutocomplete`, pour une liste longue (recherche sur le libellé) |
301
+ | `step` | une **étape dédiée**, avant la catégorisation |
302
+
303
+ Avec un seul réclamant, ou `'reclamant'` dans `readonly`, il est seulement affiché, quel que soit
304
+ le mode. En mode `step`, le stepper compte une étape de plus et `stepChange` émet
305
+ `key: 'reclamant'`.
306
+
307
+ ## Champs figés (`readonly`)
308
+
309
+ Une seule prop, trois formes :
310
+
311
+ ```vue
312
+ <!-- tout se saisit (défaut) -->
313
+ <creer-sollicitation-form :readonly="false" />
314
+
315
+ <!-- partiel : l'outil impose le réclamant et le canal -->
316
+ <creer-sollicitation-form :readonly="['reclamant', 'provenance']" />
317
+
318
+ <!-- tout est figé : l'utilisateur relit et valide -->
319
+ <creer-sollicitation-form readonly />
320
+ ```
321
+
322
+ | Champ | Figé, il affiche… | … et l'assistant ne fait plus |
323
+ | ------------- | -------------------------------------------------------------------- | ----------------------------------------------- |
324
+ | `reclamant` | son libellé | — |
325
+ | `provenance` | le canal (icône + libellé) | — |
326
+ | `motif` | la chaîne catégorie › type › nature › motif ; motifs associés grisés | — |
327
+ | `cible` | un résumé par ligne | déduire la cible du réclamant ou de ses modules |
328
+ | `description` | le texte | — |
329
+ | `dateFaits` | la date | — |
330
+ | `responsable` | son nom, ou « Déterminé automatiquement » | suggérer un responsable d'après le paramétrage |
331
+ | `fichier` | le document, sans ajout ni suppression | — |
332
+
333
+ La liste est exportée (`READONLY_FIELDS`) et une valeur inconnue est signalée par Vue.
334
+
335
+ Un champ figé affiche **ce qu'on lui fournit**, par `reclamants` et `modelValue`. Les champs
336
+ obligatoires restent exigés pour avancer : figer `provenance`, `motif`, `cible` ou `description`
337
+ engage l'outil à les fournir, sans quoi l'utilisateur reste bloqué à l'étape. Un responsable figé
338
+ et vide est déterminé côté serveur.
339
+
340
+ ## Restreindre la nomenclature
341
+
342
+ `nomenclatureFilter` est un **prédicat** qui reçoit le motif entier : on filtre sur n'importe
343
+ quel champ, pas seulement sur les identifiants.
344
+
345
+ ```javascript
346
+ // Sur une demande, seuls certains motifs sont proposables (règle d'office de Cally).
347
+ (motif, { reclamant }) => reclamant?.type !== 'D' || MOTIFS_DEMANDE.includes(motif.id)
348
+
349
+ // N'importe quel champ du référentiel fait l'affaire.
350
+ (motif) => !motif.libelle.startsWith('[INTERNE]')
351
+ ```
352
+
353
+ Il s'applique à la recherche **et** à la sélection guidée, et il est rejoué quand le réclamant
354
+ change. Absent, toute la nomenclature active est proposée.
355
+
356
+ ## Le brouillon (`v-model`)
357
+
358
+ ```javascript
359
+ {
360
+ reclamant: { type, id },
361
+ provenance: 'T', // PROVENANCES_CONTACT
362
+ lignes: [ lignePrincipale, ...motifsAssociés ],
363
+ }
364
+ ```
365
+
366
+ Un brouillon **partiel** suffit : il est fusionné avec le squelette (`getDefaultDraft`,
367
+ `getDefaultLigne`, `getDefaultPatrimoine`, exportés), l'appelant n'a pas à connaître toutes les
368
+ clés.
369
+
370
+ Une ligne :
371
+
372
+ | Clé | Rôle |
373
+ | ---------------------------------------------- | ------------------------------------------------------------------- |
374
+ | `idCategorie`, `idType`, `idNature`, `idMotif` | nomenclature (hiérarchique : changer un niveau efface les suivants) |
375
+ | `cibleMode` | `contrat`, `patrimoine` ou `demande` (voir ci-dessous) |
376
+ | `patrimoine` | localisation : commune, adresse, étage, module… |
377
+ | `contratReclamant`, `contratCible` | contrat ciblé en mode `contrat` |
378
+ | `reprendreLocataireActuel` | viser l'occupant actuel du module plutôt que le contrat ciblé |
379
+ | `description`, `dateFaits` | détail (`dateFaits` en timestamp Unix) |
380
+ | `responsable` | collaborateur, ou son id ; `null` = déterminé côté serveur |
381
+ | `fichier` | document joint (`AmsomUploadFile`), envoyé après la création |
382
+
383
+ - **Cible** (`cibleMode`) :
384
+ - `contrat` : un contrat et l'un de ses modules. C'est celui du réclamant si
385
+ `contratReclamant`, sinon le `contratCible` trouvé par la recherche. Quand ce contrat n'occupe
386
+ plus le module, `reprendreLocataireActuel` bascule sur son occupant actuel ;
387
+ - `patrimoine` : une adresse (partie commune) et, facultativement, un module (le logement) ;
388
+ - `demande` : la demande elle-même, pour un réclamant demande.
389
+ - **Héritage des motifs associés** : `lignes[0]` porte toujours sa cible et son détail. Sur une
390
+ ligne associée :
391
+ - `description: null` = **hérite** de la principale, une valeur la surcharge ;
392
+ - la cible se surcharge par `cibleOverride` ;
393
+ - la date se surcharge par `dateFaitsOverride`, parce que `null` est une date valide
394
+ (« pas de date ») et ne peut pas marquer l'héritage ;
395
+ - le **responsable n'hérite jamais**.
396
+
397
+ ## La création
398
+
399
+ `beforeCreate` expose le corps envoyé à `POST /cally2/creer_sollicitation`. L'héritage y est
400
+ **déjà résolu** : chaque ligne est autonome et porte les mêmes clés.
401
+
402
+ ```javascript
403
+ {
404
+ typeReclamant: 'L',
405
+ reclamant: 2200523,
406
+ provenance: 'T',
407
+ lignes: [
408
+ {
409
+ categorie, type, nature, motif,
410
+ description, dateFaits, responsable, // responsable : id ou null
411
+ patrimoine, // absent quand la cible est la demande
412
+ codeTypeCible, idCible, // cible contractuelle explicite…
413
+ deduireCible // …ou à déduire du module par le serveur
414
+ }
415
+ ]
416
+ }
417
+ ```
418
+
419
+ L'API renvoie les sollicitations créées (une par ligne). Chaque document joint part ensuite par
420
+ `POST /cally2/upload_sollicitation_ged`, apparié à la sollicitation **du même motif**. Le fichier
421
+ ne figure jamais dans le corps de création.
422
+
423
+ ## Exports
424
+
425
+ ```javascript
426
+ import {
427
+ CreerSollicitationForm,
428
+ CreerSollicitationModal,
429
+ configureCresol, // configuration du transport
430
+ resetCresolCache, // vide le cache des référentiels
431
+ getDefaultDraft, // squelettes du brouillon
432
+ getDefaultLigne,
433
+ getDefaultPatrimoine,
434
+ READONLY_FIELDS, // champs acceptés par `readonly`
435
+ PROVENANCES_CONTACT, // [{ id, libelle, icon }]
436
+ DEFAULT_PROVENANCE_CONTACT
437
+ } from '@amsom-habitat/cresol'
438
+ ```
439
+
440
+ Les référentiels et l'arbre patrimonial sont mis en **cache au niveau du module**, par API : deux
441
+ ouvertures de l'assistant ne rechargent rien. `resetCresolCache()` le vide (changement
442
+ d'utilisateur, tests).
443
+
444
+ ## Endpoints consommés
445
+
446
+ | Méthode | Chemin | Usage |
447
+ | ------- | ----------------------------------------------------------- | -------------------------------- |
448
+ | GET | `/v2/sollicitation/{motifs,categories,types,natures,etats}` | Nomenclature (sans jeton) |
449
+ | GET | `/v2/sollicitation/responsables` | Paramétrage des responsables |
450
+ | GET | `/collaborateurs?actif=true` | Responsables proposables |
451
+ | GET | `/cally2/patrimoine_communes` · `_entrees` · `_modules` | Arbre patrimonial (cible) |
452
+ | GET | `/cally2/contrat_modules` | Modules d'un contrat |
453
+ | GET | `/cockpit/circuit_achat/contrats` | Recherche de contrat (n° ou nom) |
454
+ | POST | `/cally2/creer_sollicitation` | Création (1 à n lignes) |
455
+ | POST | `/cally2/upload_sollicitation_ged` | Document joint à une ligne |
456
+
457
+ ## Développement
458
+
459
+ ```bash
460
+ npm i
461
+ cp .env.example .env # VITE_API_URL=http://…
462
+ npm run storybook # http://localhost:6006
463
+ npm run lint
464
+ npm run build
465
+ ```
466
+
467
+ Les stories parlent à une **vraie API** : la nomenclature, le patrimoine et les collaborateurs
468
+ viennent du serveur désigné par `.env`. Les props `baseUrl` et `token` restent disponibles en
469
+ contrôle de story pour surcharger ponctuellement. Le journal sous chaque story liste les
470
+ événements émis, avec leur charge utile.
471
+
472
+ | Story | Montre |
473
+ | -------------------------------------------------------------- | ------------------------------ |
474
+ | `Contrat` / `Demande` | un réclamant de chaque sorte |
475
+ | `ReclamantSwitch` / `ReclamantAutocomplete` / `ReclamantEtape` | les présentations du réclamant |
476
+ | `ReclamantEtProvenanceFiges` | `readonly` partiel |
477
+ | `ConfirmationAvantCreation` | `beforeCreate` + `waitUntil` |
478
+ | `BrouillonPrealimente` | `modelValue` partiel |
479
+ | `SansVolet` | `aside: false` |
480
+
481
+ ### Organisation du code
482
+
483
+ ```text
484
+ src/
485
+ CreerSollicitationForm.vue racine : fournit brouillon, données et options ; porte la création
486
+ CreerSollicitationModal.vue coquille modale (props et événements relayés)
487
+ props.js contrat public unique (props + événements)
488
+ components/ étapes et blocs de saisie (lisent le brouillon injecté)
489
+ composables/
490
+ useSollicitationDraft.js le brouillon : état, validation, champs figés, payload
491
+ useCresolData.js référentiels et patrimoine (cache module, par API)
492
+ useCresolContext.js options de l'outil (injectées, sans prop-drilling)
493
+ api/ un fichier par domaine ; transport/ = axios-overlay + jeton
494
+ utils/ fonctions pures (texte, dates, erreurs, icônes)
495
+ ```
496
+
497
+ Toute la logique de saisie (héritage, validation, champs figés, payload) vit dans
498
+ `useSollicitationDraft` : les composants ne font qu'afficher et appeler ses setters.
499
+
500
+ ### Publier
501
+
502
+ Le numéro de `package.json` doit avoir son entrée `## VX.Y.…` dans `changelog.md`
503
+ (`check-version.sh`).
504
+
505
+ ```bash
506
+ make publish # main : lint, build, tag, npm publish
507
+ make alpha_publish # dev : version prerelease beta
508
+ ```