@rsuci/shared-form-components 1.0.28 → 1.0.32
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/dist/components/form-renderer/ConfirmationModal.d.ts +20 -0
- package/dist/components/form-renderer/ConfirmationModal.d.ts.map +1 -0
- package/dist/components/form-renderer/ConfirmationModal.js +79 -0
- package/dist/components/form-renderer/FormActions.d.ts +21 -0
- package/dist/components/form-renderer/FormActions.d.ts.map +1 -0
- package/dist/components/form-renderer/FormActions.js +81 -0
- package/dist/components/form-renderer/FormNavigationButtons.d.ts +23 -0
- package/dist/components/form-renderer/FormNavigationButtons.d.ts.map +1 -0
- package/dist/components/form-renderer/FormNavigationButtons.js +35 -0
- package/dist/components/form-renderer/FormProgress.d.ts +19 -0
- package/dist/components/form-renderer/FormProgress.d.ts.map +1 -0
- package/dist/components/form-renderer/FormProgress.js +26 -0
- package/dist/components/form-renderer/FormRenderer.d.ts +39 -0
- package/dist/components/form-renderer/FormRenderer.d.ts.map +1 -0
- package/dist/components/form-renderer/FormRenderer.js +113 -0
- package/dist/components/form-renderer/FormRendererContext.d.ts +109 -0
- package/dist/components/form-renderer/FormRendererContext.d.ts.map +1 -0
- package/dist/components/form-renderer/FormRendererContext.js +114 -0
- package/dist/components/form-renderer/GroupeInstanceTabs.d.ts +18 -0
- package/dist/components/form-renderer/GroupeInstanceTabs.d.ts.map +1 -0
- package/dist/components/form-renderer/GroupeInstanceTabs.js +174 -0
- package/dist/components/form-renderer/index.d.ts +17 -0
- package/dist/components/form-renderer/index.d.ts.map +1 -0
- package/dist/components/form-renderer/index.js +23 -0
- package/dist/components/inputs/NumberInput.js +1 -1
- package/dist/hooks/useFormInstances.d.ts +87 -0
- package/dist/hooks/useFormInstances.d.ts.map +1 -0
- package/dist/hooks/useFormInstances.js +197 -0
- package/dist/hooks/useFormNavigation.d.ts +72 -0
- package/dist/hooks/useFormNavigation.d.ts.map +1 -0
- package/dist/hooks/useFormNavigation.js +147 -0
- package/dist/hooks/useFormRenderer.d.ts +87 -0
- package/dist/hooks/useFormRenderer.d.ts.map +1 -0
- package/dist/hooks/useFormRenderer.js +177 -0
- package/dist/hooks/useFormValidation.d.ts +50 -0
- package/dist/hooks/useFormValidation.d.ts.map +1 -0
- package/dist/hooks/useFormValidation.js +175 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -0
- package/dist/lib/__tests__/date-functions.test.d.ts +5 -0
- package/dist/lib/__tests__/date-functions.test.d.ts.map +1 -0
- package/dist/lib/__tests__/date-functions.test.js +184 -0
- package/dist/lib/utils/groupeInstanceManager.d.ts +88 -0
- package/dist/lib/utils/groupeInstanceManager.d.ts.map +1 -0
- package/dist/lib/utils/groupeInstanceManager.js +606 -0
- package/dist/types/form-renderer.d.ts +115 -1
- package/dist/types/form-renderer.d.ts.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gestionnaire des instances multiples pour les groupes d'enquête
|
|
3
|
+
* RSU v2 - Moteur de Rendu des Formulaires d'Enquête
|
|
4
|
+
*/
|
|
5
|
+
export class GroupeInstanceManager {
|
|
6
|
+
/**
|
|
7
|
+
* Récupère le nombre maximum d'instances autorisées depuis la variable de contrôle
|
|
8
|
+
* RÈGLE DE PROTECTION : Ne jamais permettre de réduire sous le nombre d'instances existantes
|
|
9
|
+
*/
|
|
10
|
+
static getMaxInstances(groupe, responses) {
|
|
11
|
+
if (!groupe.estMultiple || !groupe.codeVariable) {
|
|
12
|
+
console.log('GroupeInstanceManager.getMaxInstances - Groupe non multiple ou sans variable de contrôle:', {
|
|
13
|
+
estMultiple: groupe.estMultiple,
|
|
14
|
+
codeVariable: groupe.codeVariable,
|
|
15
|
+
groupeCode: groupe.code
|
|
16
|
+
});
|
|
17
|
+
return 1;
|
|
18
|
+
}
|
|
19
|
+
console.log('GroupeInstanceManager.getMaxInstances - Recherche variable de contrôle:', {
|
|
20
|
+
groupeCode: groupe.code,
|
|
21
|
+
codeVariable: groupe.codeVariable,
|
|
22
|
+
responsesKeys: Object.keys(responses),
|
|
23
|
+
responses: responses
|
|
24
|
+
});
|
|
25
|
+
// Chercher la réponse de la variable de contrôle
|
|
26
|
+
// D'abord essayer avec la clé directe
|
|
27
|
+
let controlResponse = responses[groupe.codeVariable];
|
|
28
|
+
// Si pas trouvé, chercher dans toutes les réponses par variableCode
|
|
29
|
+
if (!controlResponse) {
|
|
30
|
+
const foundResponse = Object.values(responses).find(response => response.variableCode === groupe.codeVariable);
|
|
31
|
+
if (foundResponse) {
|
|
32
|
+
controlResponse = foundResponse;
|
|
33
|
+
}
|
|
34
|
+
console.log('GroupeInstanceManager.getMaxInstances - Recherche par variableCode:', {
|
|
35
|
+
found: !!foundResponse,
|
|
36
|
+
controlResponse: foundResponse
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const controlValue = controlResponse?.valeur;
|
|
40
|
+
console.log('GroupeInstanceManager.getMaxInstances - Valeur de contrôle trouvée:', {
|
|
41
|
+
controlResponse: controlResponse,
|
|
42
|
+
controlValue: controlValue,
|
|
43
|
+
valueType: typeof controlValue
|
|
44
|
+
});
|
|
45
|
+
// Convertir la valeur en nombre
|
|
46
|
+
let maxFromControl = 1; // Valeur par défaut
|
|
47
|
+
if (typeof controlValue === 'number') {
|
|
48
|
+
maxFromControl = controlValue;
|
|
49
|
+
}
|
|
50
|
+
else if (typeof controlValue === 'string') {
|
|
51
|
+
const parsed = parseInt(controlValue, 10);
|
|
52
|
+
if (!isNaN(parsed)) {
|
|
53
|
+
maxFromControl = parsed;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// PROTECTION DES DONNÉES : Obtenir le nombre d'instances existantes
|
|
57
|
+
const existingInstancesCount = groupe.instances?.length || 0;
|
|
58
|
+
// Le maximum effectif est au moins égal au nombre d'instances existantes
|
|
59
|
+
const protectedMax = Math.max(maxFromControl, existingInstancesCount);
|
|
60
|
+
console.log('GroupeInstanceManager.getMaxInstances - Résultat avec protection:', {
|
|
61
|
+
groupeCode: groupe.code,
|
|
62
|
+
codeVariable: groupe.codeVariable,
|
|
63
|
+
maxFromControl: maxFromControl,
|
|
64
|
+
existingInstancesCount: existingInstancesCount,
|
|
65
|
+
protectedMax: protectedMax,
|
|
66
|
+
isProtectionActive: protectedMax > maxFromControl
|
|
67
|
+
});
|
|
68
|
+
// S'assurer qu'on a au moins 1 instance
|
|
69
|
+
return Math.max(1, protectedMax);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Récupère le nombre minimum d'instances (basé sur les instances existantes)
|
|
73
|
+
*/
|
|
74
|
+
static getMinInstances(groupe) {
|
|
75
|
+
if (!groupe.estMultiple) {
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
78
|
+
const existingInstances = groupe.instances?.length || 0;
|
|
79
|
+
return Math.max(1, existingInstances);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Compte le nombre d'instances existantes pour un groupe
|
|
83
|
+
*/
|
|
84
|
+
static getInstancesCount(groupe, responses) {
|
|
85
|
+
if (!groupe.estMultiple) {
|
|
86
|
+
return 1;
|
|
87
|
+
}
|
|
88
|
+
console.log('GroupeInstanceManager.getInstancesCount - Début:', {
|
|
89
|
+
groupeCode: groupe.code,
|
|
90
|
+
variableCodes: groupe.variables.map(v => v.code),
|
|
91
|
+
responsesKeys: Object.keys(responses)
|
|
92
|
+
});
|
|
93
|
+
// Compter les instances basées sur les réponses
|
|
94
|
+
const instanceNumbers = new Set();
|
|
95
|
+
// Méthode 1: Par numeroMembre
|
|
96
|
+
groupe.variables.forEach(variable => {
|
|
97
|
+
Object.values(responses).forEach(response => {
|
|
98
|
+
if (response.variableCode === variable.code && response.numeroMembre) {
|
|
99
|
+
instanceNumbers.add(response.numeroMembre);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
// Méthode 2: Par clé avec format "CODE_NUMERO"
|
|
104
|
+
groupe.variables.forEach(variable => {
|
|
105
|
+
Object.keys(responses).forEach(key => {
|
|
106
|
+
const response = responses[key];
|
|
107
|
+
if (response.variableCode === variable.code) {
|
|
108
|
+
// Extraire le numéro d'instance de la clé si elle suit le format "CODE_NUMERO"
|
|
109
|
+
const keyParts = key.split('_');
|
|
110
|
+
if (keyParts.length >= 2) {
|
|
111
|
+
const lastPart = keyParts[keyParts.length - 1];
|
|
112
|
+
const instanceNum = parseInt(lastPart, 10);
|
|
113
|
+
if (!isNaN(instanceNum) && instanceNum > 0) {
|
|
114
|
+
instanceNumbers.add(instanceNum);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
const count = Math.max(1, instanceNumbers.size);
|
|
121
|
+
console.log('GroupeInstanceManager.getInstancesCount - Résultat:', {
|
|
122
|
+
groupeCode: groupe.code,
|
|
123
|
+
instanceNumbers: Array.from(instanceNumbers).sort(),
|
|
124
|
+
finalCount: count
|
|
125
|
+
});
|
|
126
|
+
return count;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Crée une nouvelle instance pour un groupe multiple
|
|
130
|
+
*/
|
|
131
|
+
static createInstance(groupe, numeroInstance) {
|
|
132
|
+
const libelle = `${groupe.designation} ${numeroInstance}`;
|
|
133
|
+
return {
|
|
134
|
+
numeroInstance,
|
|
135
|
+
libelle,
|
|
136
|
+
estComplete: false,
|
|
137
|
+
reponses: {}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Initialise les instances pour un groupe multiple
|
|
142
|
+
*/
|
|
143
|
+
static initializeInstances(groupe, responses) {
|
|
144
|
+
if (!groupe.estMultiple) {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
console.log('GroupeInstanceManager.initializeInstances - Début:', {
|
|
148
|
+
groupeCode: groupe.code,
|
|
149
|
+
codeVariable: groupe.codeVariable,
|
|
150
|
+
responsesKeys: Object.keys(responses),
|
|
151
|
+
allResponses: responses
|
|
152
|
+
});
|
|
153
|
+
// Identifier toutes les instances existantes pour ce groupe
|
|
154
|
+
const instanceNumbers = new Set();
|
|
155
|
+
// Collecter les réponses existantes pour ce groupe (avec et sans numeroMembre)
|
|
156
|
+
const groupResponses = [];
|
|
157
|
+
// Parcourir toutes les réponses pour trouver celles de ce groupe
|
|
158
|
+
Object.values(responses).forEach(response => {
|
|
159
|
+
const belongsToGroup = groupe.variables.some(v => v.code === response.variableCode);
|
|
160
|
+
if (belongsToGroup) {
|
|
161
|
+
groupResponses.push(response);
|
|
162
|
+
if (response.numeroMembre) {
|
|
163
|
+
instanceNumbers.add(response.numeroMembre);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
// Aussi chercher par clé avec format "CODE_NUMERO"
|
|
168
|
+
Object.keys(responses).forEach(key => {
|
|
169
|
+
const response = responses[key];
|
|
170
|
+
const belongsToGroup = groupe.variables.some(v => v.code === response.variableCode);
|
|
171
|
+
if (belongsToGroup) {
|
|
172
|
+
// Extraire le numéro d'instance de la clé si elle suit le format "CODE_NUMERO"
|
|
173
|
+
const keyParts = key.split('_');
|
|
174
|
+
if (keyParts.length >= 2) {
|
|
175
|
+
const lastPart = keyParts[keyParts.length - 1];
|
|
176
|
+
const instanceNum = parseInt(lastPart, 10);
|
|
177
|
+
if (!isNaN(instanceNum) && instanceNum > 0) {
|
|
178
|
+
instanceNumbers.add(instanceNum);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
console.log('GroupeInstanceManager.initializeInstances - Données trouvées:', {
|
|
184
|
+
groupeCode: groupe.code,
|
|
185
|
+
instanceNumbers: Array.from(instanceNumbers).sort(),
|
|
186
|
+
groupResponsesCount: groupResponses.length,
|
|
187
|
+
responsesWithNumeroMembre: groupResponses.filter(r => r.numeroMembre).length,
|
|
188
|
+
responsesWithoutNumeroMembre: groupResponses.filter(r => !r.numeroMembre).length
|
|
189
|
+
});
|
|
190
|
+
const maxInstances = this.getMaxInstances(groupe, responses);
|
|
191
|
+
// Déterminer le nombre d'instances à créer
|
|
192
|
+
let instanceCount = Math.max(1, instanceNumbers.size);
|
|
193
|
+
// CORRECTION: Ne pas forcer la création de maxInstances instances
|
|
194
|
+
// Respecter la limite de la variable de contrôle
|
|
195
|
+
instanceCount = Math.min(instanceCount, maxInstances);
|
|
196
|
+
// Si aucune instance existante, créer au moins 1 instance
|
|
197
|
+
if (instanceCount === 0) {
|
|
198
|
+
instanceCount = 1;
|
|
199
|
+
}
|
|
200
|
+
console.log('GroupeInstanceManager.initializeInstances - Calcul instances:', {
|
|
201
|
+
groupeCode: groupe.code,
|
|
202
|
+
maxInstances,
|
|
203
|
+
existingInstancesCount: instanceNumbers.size,
|
|
204
|
+
finalInstanceCount: instanceCount
|
|
205
|
+
});
|
|
206
|
+
const instances = [];
|
|
207
|
+
// Séparer les réponses avec et sans numeroMembre
|
|
208
|
+
const responsesWithMember = groupResponses.filter(r => r.numeroMembre);
|
|
209
|
+
const responsesWithoutMember = groupResponses.filter(r => !r.numeroMembre);
|
|
210
|
+
for (let i = 1; i <= instanceCount; i++) {
|
|
211
|
+
const instance = this.createInstance(groupe, i);
|
|
212
|
+
// Remplir les réponses existantes pour cette instance
|
|
213
|
+
groupe.variables.forEach(variable => {
|
|
214
|
+
let existingResponse;
|
|
215
|
+
// 1. Chercher d'abord par numeroMembre
|
|
216
|
+
existingResponse = responsesWithMember.find(r => r.variableCode === variable.code && r.numeroMembre === i);
|
|
217
|
+
// 2. Si pas trouvé, chercher par clé avec format "CODE_NUMERO"
|
|
218
|
+
if (!existingResponse) {
|
|
219
|
+
const keyWithInstance = `${variable.code}_${i}`;
|
|
220
|
+
existingResponse = responses[keyWithInstance];
|
|
221
|
+
}
|
|
222
|
+
// 3. Si toujours pas trouvé et qu'on a des réponses sans numeroMembre,
|
|
223
|
+
// les distribuer aux instances (première instance = première réponse, etc.)
|
|
224
|
+
if (!existingResponse && responsesWithoutMember.length > 0) {
|
|
225
|
+
// Pour la première instance, prendre les réponses sans numeroMembre
|
|
226
|
+
if (i === 1) {
|
|
227
|
+
existingResponse = responsesWithoutMember.find(r => r.variableCode === variable.code);
|
|
228
|
+
if (existingResponse) {
|
|
229
|
+
console.log('GroupeInstanceManager.initializeInstances - Attribution réponse sans numeroMembre:', {
|
|
230
|
+
groupeCode: groupe.code,
|
|
231
|
+
instanceNumber: i,
|
|
232
|
+
variableCode: variable.code,
|
|
233
|
+
originalResponse: existingResponse
|
|
234
|
+
});
|
|
235
|
+
// Créer une copie avec numeroMembre pour cette instance
|
|
236
|
+
existingResponse = {
|
|
237
|
+
...existingResponse,
|
|
238
|
+
numeroMembre: i
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (existingResponse) {
|
|
244
|
+
console.log('GroupeInstanceManager.initializeInstances - Réponse trouvée:', {
|
|
245
|
+
groupeCode: groupe.code,
|
|
246
|
+
instanceNumber: i,
|
|
247
|
+
variableCode: variable.code,
|
|
248
|
+
hasNumeroMembre: !!existingResponse.numeroMembre,
|
|
249
|
+
response: existingResponse
|
|
250
|
+
});
|
|
251
|
+
instance.reponses[variable.code] = existingResponse;
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
// Vérifier si l'instance est complète
|
|
255
|
+
instance.estComplete = this.isInstanceComplete(instance, groupe);
|
|
256
|
+
instances.push(instance);
|
|
257
|
+
console.log('GroupeInstanceManager.initializeInstances - Instance créée:', {
|
|
258
|
+
groupeCode: groupe.code,
|
|
259
|
+
instanceNumber: i,
|
|
260
|
+
responsesCount: Object.keys(instance.reponses).length,
|
|
261
|
+
isComplete: instance.estComplete
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
console.log('GroupeInstanceManager.initializeInstances - Résultat final:', {
|
|
265
|
+
groupeCode: groupe.code,
|
|
266
|
+
instancesCreated: instances.length,
|
|
267
|
+
instances: instances.map(inst => ({
|
|
268
|
+
numero: inst.numeroInstance,
|
|
269
|
+
responsesCount: Object.keys(inst.reponses).length,
|
|
270
|
+
isComplete: inst.estComplete
|
|
271
|
+
}))
|
|
272
|
+
});
|
|
273
|
+
return instances;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Vérifie si une instance est complètement renseignée
|
|
277
|
+
*/
|
|
278
|
+
static isInstanceComplete(instance, groupe) {
|
|
279
|
+
const requiredVariables = groupe.variables.filter(v => v.estObligatoire);
|
|
280
|
+
return requiredVariables.every(variable => {
|
|
281
|
+
const response = instance.reponses[variable.code];
|
|
282
|
+
return response && response.valeur !== null && response.valeur !== '';
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Valide si on peut ajouter une nouvelle instance
|
|
287
|
+
*/
|
|
288
|
+
static canAddInstance(groupe, responses) {
|
|
289
|
+
const constraints = [];
|
|
290
|
+
if (!groupe.estMultiple) {
|
|
291
|
+
constraints.push({
|
|
292
|
+
type: 'instance_limit',
|
|
293
|
+
groupeCode: groupe.code,
|
|
294
|
+
message: 'Ce groupe ne supporte pas les instances multiples'
|
|
295
|
+
});
|
|
296
|
+
return {
|
|
297
|
+
isValid: false,
|
|
298
|
+
errors: [],
|
|
299
|
+
constraints,
|
|
300
|
+
canProceed: false
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
const currentCount = this.getInstancesCount(groupe, responses);
|
|
304
|
+
const maxInstances = this.getMaxInstances(groupe, responses);
|
|
305
|
+
if (currentCount >= maxInstances) {
|
|
306
|
+
constraints.push({
|
|
307
|
+
type: 'instance_limit',
|
|
308
|
+
groupeCode: groupe.code,
|
|
309
|
+
variableCode: groupe.codeVariable,
|
|
310
|
+
currentValue: currentCount,
|
|
311
|
+
requiredValue: maxInstances,
|
|
312
|
+
message: `Maximum ${maxInstances} instances autorisées (variable ${groupe.codeVariable})`
|
|
313
|
+
});
|
|
314
|
+
return {
|
|
315
|
+
isValid: false,
|
|
316
|
+
errors: [],
|
|
317
|
+
constraints,
|
|
318
|
+
canProceed: false
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
isValid: true,
|
|
323
|
+
errors: [],
|
|
324
|
+
constraints: [],
|
|
325
|
+
canProceed: true
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Valide si on peut supprimer une instance
|
|
330
|
+
*/
|
|
331
|
+
static canRemoveInstance(groupe, responses) {
|
|
332
|
+
const constraints = [];
|
|
333
|
+
// CORRECTION: Utiliser le nombre d'instances du groupe directement
|
|
334
|
+
const currentCount = groupe.instances?.length || 0;
|
|
335
|
+
console.log('GroupeInstanceManager.canRemoveInstance - Debug:', {
|
|
336
|
+
groupeCode: groupe.code,
|
|
337
|
+
instancesLength: groupe.instances?.length,
|
|
338
|
+
currentCount,
|
|
339
|
+
canRemove: currentCount > 1
|
|
340
|
+
});
|
|
341
|
+
if (currentCount <= 1) {
|
|
342
|
+
constraints.push({
|
|
343
|
+
type: 'instance_limit',
|
|
344
|
+
groupeCode: groupe.code,
|
|
345
|
+
currentValue: currentCount,
|
|
346
|
+
requiredValue: 1,
|
|
347
|
+
message: 'Au moins une instance doit être conservée'
|
|
348
|
+
});
|
|
349
|
+
return {
|
|
350
|
+
isValid: false,
|
|
351
|
+
errors: [],
|
|
352
|
+
constraints,
|
|
353
|
+
canProceed: false
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
isValid: true,
|
|
358
|
+
errors: [],
|
|
359
|
+
constraints: [],
|
|
360
|
+
canProceed: true
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Valide si on peut modifier la variable de contrôle
|
|
365
|
+
*/
|
|
366
|
+
static canModifyControlVariable(variableCode, newValue, groupesMultiples, responses) {
|
|
367
|
+
const constraints = [];
|
|
368
|
+
// Trouver tous les groupes qui utilisent cette variable de contrôle
|
|
369
|
+
const affectedGroups = groupesMultiples.filter(g => g.codeVariable === variableCode);
|
|
370
|
+
for (const groupe of affectedGroups) {
|
|
371
|
+
const existingInstances = this.getInstancesCount(groupe, responses);
|
|
372
|
+
if (newValue < existingInstances) {
|
|
373
|
+
constraints.push({
|
|
374
|
+
type: 'bidirectional_control',
|
|
375
|
+
variableCode,
|
|
376
|
+
groupeCode: groupe.code,
|
|
377
|
+
currentValue: newValue,
|
|
378
|
+
requiredValue: existingInstances,
|
|
379
|
+
message: `Impossible de réduire à ${newValue}. Le groupe "${groupe.designation}" a déjà ${existingInstances} instances créées.`
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (constraints.length > 0) {
|
|
384
|
+
return {
|
|
385
|
+
isValid: false,
|
|
386
|
+
errors: [],
|
|
387
|
+
constraints,
|
|
388
|
+
canProceed: false
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
isValid: true,
|
|
393
|
+
errors: [],
|
|
394
|
+
constraints: [],
|
|
395
|
+
canProceed: true
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Ajoute une nouvelle instance à un groupe
|
|
400
|
+
*/
|
|
401
|
+
static addInstance(groupe, responses) {
|
|
402
|
+
const validation = this.canAddInstance(groupe, responses);
|
|
403
|
+
if (!validation.canProceed) {
|
|
404
|
+
return {
|
|
405
|
+
success: false,
|
|
406
|
+
error: validation.constraints[0]?.message || 'Impossible d\'ajouter une instance'
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
// CORRECTION: Calculer le bon numéro d'instance
|
|
410
|
+
if (!groupe.instances) {
|
|
411
|
+
groupe.instances = [];
|
|
412
|
+
}
|
|
413
|
+
const newInstanceNumber = groupe.instances.length + 1;
|
|
414
|
+
const newInstance = this.createInstance(groupe, newInstanceNumber);
|
|
415
|
+
// Ajouter l'instance au groupe
|
|
416
|
+
groupe.instances.push(newInstance);
|
|
417
|
+
// Mettre à jour les propriétés calculées
|
|
418
|
+
groupe.instancesCount = groupe.instances.length;
|
|
419
|
+
console.log('GroupeInstanceManager.addInstance - Instance ajoutée:', {
|
|
420
|
+
groupeCode: groupe.code,
|
|
421
|
+
newInstanceNumber,
|
|
422
|
+
totalInstances: groupe.instances.length,
|
|
423
|
+
maxInstances: this.getMaxInstances(groupe, responses)
|
|
424
|
+
});
|
|
425
|
+
return {
|
|
426
|
+
success: true,
|
|
427
|
+
instance: newInstance
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Supprime une instance d'un groupe
|
|
432
|
+
*/
|
|
433
|
+
static removeInstance(groupe, instanceNumber, responses) {
|
|
434
|
+
const validation = this.canRemoveInstance(groupe, responses);
|
|
435
|
+
if (!validation.canProceed) {
|
|
436
|
+
return {
|
|
437
|
+
success: false,
|
|
438
|
+
error: validation.constraints[0]?.message || 'Impossible de supprimer cette instance'
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
// Supprimer l'instance du tableau
|
|
442
|
+
if (groupe.instances) {
|
|
443
|
+
groupe.instances = groupe.instances.filter(i => i.numeroInstance !== instanceNumber);
|
|
444
|
+
groupe.instancesCount = groupe.instances.length;
|
|
445
|
+
}
|
|
446
|
+
// Supprimer les réponses associées à cette instance
|
|
447
|
+
Object.keys(responses).forEach(key => {
|
|
448
|
+
const response = responses[key];
|
|
449
|
+
if (response.numeroMembre === instanceNumber) {
|
|
450
|
+
// Trouver si cette réponse appartient à ce groupe
|
|
451
|
+
const belongsToGroup = groupe.variables.some(v => v.code === response.variableCode);
|
|
452
|
+
if (belongsToGroup) {
|
|
453
|
+
delete responses[key];
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
// Réorganiser les instances pour maintenir une numérotation séquentielle
|
|
458
|
+
this.reorderInstances(groupe, responses);
|
|
459
|
+
return {
|
|
460
|
+
success: true
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Met à jour les propriétés calculées d'un groupe multiple
|
|
465
|
+
*/
|
|
466
|
+
static updateGroupeProperties(groupe, responses) {
|
|
467
|
+
if (!groupe.estMultiple) {
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
groupe.maxInstances = this.getMaxInstances(groupe, responses);
|
|
471
|
+
groupe.minInstances = this.getMinInstances(groupe);
|
|
472
|
+
// Initialiser les instances si elles n'existent pas
|
|
473
|
+
if (!groupe.instances) {
|
|
474
|
+
groupe.instances = this.initializeInstances(groupe, responses);
|
|
475
|
+
}
|
|
476
|
+
// PROTECTION DES DONNÉES : Ne plus supprimer automatiquement les instances
|
|
477
|
+
// La logique de protection est maintenant dans getMaxInstances()
|
|
478
|
+
console.log('GroupeInstanceManager.updateGroupeProperties - Protection des données active:', {
|
|
479
|
+
groupeCode: groupe.code,
|
|
480
|
+
currentInstances: groupe.instances.length,
|
|
481
|
+
maxInstances: groupe.maxInstances,
|
|
482
|
+
protectionActive: groupe.instances.length > 0
|
|
483
|
+
});
|
|
484
|
+
groupe.instancesCount = groupe.instances.length;
|
|
485
|
+
// Mettre à jour le statut de completion des instances
|
|
486
|
+
groupe.instances.forEach(instance => {
|
|
487
|
+
instance.estComplete = this.isInstanceComplete(instance, groupe);
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Synchronise les réponses d'une instance avec le store global
|
|
492
|
+
*/
|
|
493
|
+
static syncInstanceResponses(instance, groupe, globalResponses) {
|
|
494
|
+
// Copier les réponses de l'instance vers le store global
|
|
495
|
+
Object.entries(instance.reponses).forEach(([variableCode, response]) => {
|
|
496
|
+
const globalKey = `${variableCode}_${instance.numeroInstance}`;
|
|
497
|
+
globalResponses[globalKey] = {
|
|
498
|
+
...response,
|
|
499
|
+
numeroMembre: instance.numeroInstance
|
|
500
|
+
};
|
|
501
|
+
});
|
|
502
|
+
// Copier les réponses du store global vers l'instance
|
|
503
|
+
groupe.variables.forEach(variable => {
|
|
504
|
+
const existingResponse = Object.values(globalResponses).find(r => r.variableCode === variable.code && r.numeroMembre === instance.numeroInstance);
|
|
505
|
+
if (existingResponse) {
|
|
506
|
+
instance.reponses[variable.code] = existingResponse;
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Valide la navigation vers l'instance suivante
|
|
512
|
+
*/
|
|
513
|
+
static canNavigateToNextInstance(currentInstance, groupe) {
|
|
514
|
+
const constraints = [];
|
|
515
|
+
if (!this.isInstanceComplete(currentInstance, groupe)) {
|
|
516
|
+
const requiredVariables = groupe.variables.filter(v => v.estObligatoire);
|
|
517
|
+
const missingVariables = requiredVariables.filter(v => {
|
|
518
|
+
const response = currentInstance.reponses[v.code];
|
|
519
|
+
return !response || response.valeur === null || response.valeur === '';
|
|
520
|
+
});
|
|
521
|
+
constraints.push({
|
|
522
|
+
type: 'navigation_block',
|
|
523
|
+
groupeCode: groupe.code,
|
|
524
|
+
message: `Veuillez renseigner tous les champs obligatoires de cette instance avant de continuer. Champs manquants: ${missingVariables.map(v => v.designation).join(', ')}`
|
|
525
|
+
});
|
|
526
|
+
return {
|
|
527
|
+
isValid: false,
|
|
528
|
+
errors: [],
|
|
529
|
+
constraints,
|
|
530
|
+
canProceed: false
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
return {
|
|
534
|
+
isValid: true,
|
|
535
|
+
errors: [],
|
|
536
|
+
constraints: [],
|
|
537
|
+
canProceed: true
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Trouve la prochaine instance incomplète dans un groupe
|
|
542
|
+
*/
|
|
543
|
+
static findNextIncompleteInstance(groupe) {
|
|
544
|
+
if (!groupe.instances) {
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
return groupe.instances.find(instance => !instance.estComplete) || null;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Calcule la progression d'un groupe multiple
|
|
551
|
+
*/
|
|
552
|
+
static calculateGroupProgress(groupe) {
|
|
553
|
+
if (!groupe.estMultiple || !groupe.instances) {
|
|
554
|
+
return {
|
|
555
|
+
completedInstances: 0,
|
|
556
|
+
totalInstances: 1,
|
|
557
|
+
progressPercentage: 0
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
const completedInstances = groupe.instances.filter(i => i.estComplete).length;
|
|
561
|
+
const totalInstances = groupe.instances.length;
|
|
562
|
+
const progressPercentage = totalInstances > 0 ? (completedInstances / totalInstances) * 100 : 0;
|
|
563
|
+
return {
|
|
564
|
+
completedInstances,
|
|
565
|
+
totalInstances,
|
|
566
|
+
progressPercentage
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Réorganise les numéros d'instances après suppression
|
|
571
|
+
*/
|
|
572
|
+
static reorderInstances(groupe, responses) {
|
|
573
|
+
if (!groupe.instances) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
// Trier les instances par numéro
|
|
577
|
+
groupe.instances.sort((a, b) => a.numeroInstance - b.numeroInstance);
|
|
578
|
+
// Réassigner les numéros séquentiellement
|
|
579
|
+
groupe.instances.forEach((instance, index) => {
|
|
580
|
+
const newNumber = index + 1;
|
|
581
|
+
const oldNumber = instance.numeroInstance;
|
|
582
|
+
if (newNumber !== oldNumber) {
|
|
583
|
+
// Mettre à jour le numéro de l'instance
|
|
584
|
+
instance.numeroInstance = newNumber;
|
|
585
|
+
instance.libelle = `${groupe.designation} ${newNumber}`;
|
|
586
|
+
// Mettre à jour les réponses dans le store global
|
|
587
|
+
Object.keys(responses).forEach(key => {
|
|
588
|
+
const response = responses[key];
|
|
589
|
+
if (response.numeroMembre === oldNumber) {
|
|
590
|
+
const belongsToGroup = groupe.variables.some(v => v.code === response.variableCode);
|
|
591
|
+
if (belongsToGroup) {
|
|
592
|
+
response.numeroMembre = newNumber;
|
|
593
|
+
// Mettre à jour la clé si nécessaire
|
|
594
|
+
delete responses[key];
|
|
595
|
+
responses[`${response.variableCode}_${newNumber}`] = response;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
// Mettre à jour les réponses dans l'instance
|
|
600
|
+
Object.keys(instance.reponses).forEach(variableCode => {
|
|
601
|
+
instance.reponses[variableCode].numeroMembre = newNumber;
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
}
|