@rsuci/shared-form-components 1.0.86 → 1.0.87

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.
@@ -1 +1 @@
1
- {"version":3,"file":"FormRenderer.d.ts","sourceRoot":"","sources":["../../../src/components/form-renderer/FormRenderer.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAA4D,MAAM,OAAO,CAAC;AACjF,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EAIf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EAEtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAsBlF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,6BAA6B;IAC7B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,yBAAyB;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAClD,oCAAoC;IACpC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,iCAAiC;IACjC,SAAS,EAAE,qBAAqB,CAAC;IACjC,wBAAwB;IACxB,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,wCAAwC;IACxC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,iCAAiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iCAAiC;IACjC,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AA0xBD;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAgBpD,CAAC;AAEF,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"FormRenderer.d.ts","sourceRoot":"","sources":["../../../src/components/form-renderer/FormRenderer.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAA4D,MAAM,OAAO,CAAC;AACjF,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EAIf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EAEtB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAsBlF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,6BAA6B;IAC7B,UAAU,EAAE,iBAAiB,CAAC;IAC9B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,yBAAyB;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAClD,oCAAoC;IACpC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,iCAAiC;IACjC,SAAS,EAAE,qBAAqB,CAAC;IACjC,wBAAwB;IACxB,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,wCAAwC;IACxC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,iCAAiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iCAAiC;IACjC,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAwyBD;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAgBpD,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -206,16 +206,22 @@ const FormRendererInner = () => {
206
206
  navigation.changeInstance(instanceIndex);
207
207
  };
208
208
  // Handler pour l'ajout d'instance
209
- const handleInstanceAdded = (instance) => {
210
- navigation.updateTotalInstances(currentGroup?.instances?.length || 1);
209
+ const handleInstanceAdded = (instance, updatedGroupe) => {
210
+ // Mettre à jour le groupe via l'état React (pas de mutation directe)
211
+ instances.setGroupesWithInstances(prev => prev.map(g => g.code === updatedGroupe.code ? updatedGroupe : g));
212
+ navigation.updateTotalInstances(updatedGroupe.instances?.length || 1);
211
213
  // Naviguer vers la nouvelle instance
212
- if (currentGroup?.instances) {
213
- navigation.changeInstance(currentGroup.instances.length - 1);
214
+ if (updatedGroupe.instances) {
215
+ navigation.changeInstance(updatedGroupe.instances.length - 1);
214
216
  }
215
217
  };
216
218
  // Handler pour la suppression d'instance
217
- const handleInstanceRemoved = (instanceNumber) => {
218
- navigation.updateTotalInstances(currentGroup?.instances?.length || 1);
219
+ const handleInstanceRemoved = (instanceNumber, updatedGroupe, updatedResponses) => {
220
+ // CRITIQUE : mettre à jour les réponses via l'état React
221
+ context.setResponses(updatedResponses);
222
+ // Mettre à jour le groupe via l'état React
223
+ instances.setGroupesWithInstances(prev => prev.map(g => g.code === updatedGroupe.code ? updatedGroupe : g));
224
+ navigation.updateTotalInstances(updatedGroupe.instances?.length || 1);
219
225
  // Revenir à la première instance
220
226
  navigation.changeInstance(0);
221
227
  };
@@ -9,8 +9,8 @@ export interface GroupeInstanceTabsProps {
9
9
  currentInstanceIndex: number;
10
10
  responses: Record<string, EnqueteReponse>;
11
11
  onInstanceChange: (instanceIndex: number) => void;
12
- onInstanceAdded: (instance: GroupeInstance) => void;
13
- onInstanceRemoved: (instanceNumber: number) => void;
12
+ onInstanceAdded: (instance: GroupeInstance, updatedGroupe: GroupeFormulaire) => void;
13
+ onInstanceRemoved: (instanceNumber: number, updatedGroupe: GroupeFormulaire, updatedResponses: Record<string, EnqueteReponse>) => void;
14
14
  disabled?: boolean;
15
15
  }
16
16
  declare const GroupeInstanceTabs: React.FC<GroupeInstanceTabsProps>;
@@ -1 +1 @@
1
- {"version":3,"file":"GroupeInstanceTabs.d.ts","sourceRoot":"","sources":["../../../src/components/form-renderer/GroupeInstanceTabs.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAIvF,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,gBAAgB,CAAC;IACzB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC1C,gBAAgB,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,eAAe,EAAE,CAAC,QAAQ,EAAE,cAAc,KAAK,IAAI,CAAC;IACpD,iBAAiB,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,QAAA,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CA0RzD,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"GroupeInstanceTabs.d.ts","sourceRoot":"","sources":["../../../src/components/form-renderer/GroupeInstanceTabs.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAIvF,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,gBAAgB,CAAC;IACzB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC1C,gBAAgB,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,eAAe,EAAE,CAAC,QAAQ,EAAE,cAAc,EAAE,aAAa,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACrF,iBAAiB,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,KAAK,IAAI,CAAC;IACvI,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,QAAA,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAuQzD,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -26,12 +26,6 @@ const GroupeInstanceTabs = ({ groupe, currentInstanceIndex, responses, onInstanc
26
26
  useEffect(() => {
27
27
  const newMaxInstances = GroupeInstanceManager.getMaxInstances(groupe, responses);
28
28
  if (newMaxInstances !== maxInstances) {
29
- console.log('GroupeInstanceTabs - Changement de maxInstances détecté:', {
30
- groupeCode: groupe.code,
31
- oldMaxInstances: maxInstances,
32
- newMaxInstances,
33
- codeVariable: groupe.codeVariable
34
- });
35
29
  setForceUpdate(prev => prev + 1);
36
30
  }
37
31
  }, [responses, groupe.codeVariable, groupe.maxInstances]);
@@ -39,18 +33,6 @@ const GroupeInstanceTabs = ({ groupe, currentInstanceIndex, responses, onInstanc
39
33
  // Le nombre d'onglets affichés = Math.max(maxInstances, instances existantes)
40
34
  const minDisplayCount = Math.max(maxInstances, groupe.instances.length);
41
35
  const displayedInstances = groupe.instances.slice(0, minDisplayCount);
42
- console.log('GroupeInstanceTabs - Debug avec protection:', {
43
- groupeCode: groupe.code,
44
- codeVariable: groupe.codeVariable,
45
- maxInstances,
46
- totalInstances: groupe.instances.length,
47
- minDisplayCount,
48
- displayedInstances: displayedInstances.length,
49
- canAdd: canAdd.canProceed,
50
- canRemove: canRemove.canProceed,
51
- canAddMessage: canAdd.constraints[0]?.message,
52
- canRemoveMessage: canRemove.constraints[0]?.message
53
- });
54
36
  // Fonction pour déterminer l'état d'une instance
55
37
  const getInstanceState = (instance) => {
56
38
  const responseCount = Object.keys(instance.reponses).length;
@@ -109,8 +91,8 @@ const GroupeInstanceTabs = ({ groupe, currentInstanceIndex, responses, onInstanc
109
91
  setError(null);
110
92
  try {
111
93
  const result = GroupeInstanceManager.addInstance(groupe, responses);
112
- if (result.success && result.instance) {
113
- onInstanceAdded(result.instance);
94
+ if (result.success && result.instance && result.updatedGroupe) {
95
+ onInstanceAdded(result.instance, result.updatedGroupe);
114
96
  }
115
97
  else {
116
98
  setError(result.error || 'Erreur lors de l\'ajout de l\'instance');
@@ -143,8 +125,8 @@ const GroupeInstanceTabs = ({ groupe, currentInstanceIndex, responses, onInstanc
143
125
  return;
144
126
  }
145
127
  const result = GroupeInstanceManager.removeInstance(groupe, currentInstance.numeroInstance, responses);
146
- if (result.success) {
147
- onInstanceRemoved(currentInstance.numeroInstance);
128
+ if (result.success && result.updatedGroupe && result.updatedResponses) {
129
+ onInstanceRemoved(currentInstance.numeroInstance, result.updatedGroupe, result.updatedResponses);
148
130
  }
149
131
  else {
150
132
  setError(result.error || 'Erreur lors de la suppression de l\'instance');
@@ -81,16 +81,16 @@ export function useFormInstances(initialGroupes, initialResponses, options = {})
81
81
  */
82
82
  const addInstance = useCallback((groupe, responses) => {
83
83
  const result = GroupeInstanceManager.addInstance(groupe, responses);
84
- if (result.success && result.instance) {
84
+ if (result.success && result.instance && result.updatedGroupe) {
85
85
  log('Instance ajoutée:', {
86
86
  groupeCode: groupe.code,
87
87
  instanceNumber: result.instance.numeroInstance
88
88
  });
89
- // Mettre à jour les groupes
89
+ // Mettre à jour les groupes avec le nouveau groupe immutable
90
90
  setGroupesWithInstances(prev => {
91
91
  const updated = prev.map(g => {
92
- if (g.code === groupe.code) {
93
- return { ...groupe };
92
+ if (g.code === result.updatedGroupe.code) {
93
+ return result.updatedGroupe;
94
94
  }
95
95
  return g;
96
96
  });
@@ -110,16 +110,16 @@ export function useFormInstances(initialGroupes, initialResponses, options = {})
110
110
  */
111
111
  const removeInstance = useCallback((groupe, instanceNumber, responses) => {
112
112
  const result = GroupeInstanceManager.removeInstance(groupe, instanceNumber, responses);
113
- if (result.success) {
113
+ if (result.success && result.updatedGroupe) {
114
114
  log('Instance supprimée:', {
115
115
  groupeCode: groupe.code,
116
116
  instanceNumber
117
117
  });
118
- // Mettre à jour les groupes
118
+ // Mettre à jour les groupes avec le nouveau groupe immutable
119
119
  setGroupesWithInstances(prev => {
120
120
  const updated = prev.map(g => {
121
- if (g.code === groupe.code) {
122
- return { ...groupe };
121
+ if (g.code === result.updatedGroupe.code) {
122
+ return result.updatedGroupe;
123
123
  }
124
124
  return g;
125
125
  });
@@ -1 +1 @@
1
- {"version":3,"file":"useFormRenderer.d.ts","sourceRoot":"","sources":["../../src/hooks/useFormRenderer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,aAAa,EACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAqB,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAqB,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAoB,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAE9E,MAAM,WAAW,sBAAsB;IACrC,oCAAoC;IACpC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,yBAAyB;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAClD,8CAA8C;IAC9C,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,KAAK,IAAI,CAAC;IACxE,4CAA4C;IAC5C,aAAa,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAChE,yDAAyD;IACzD,iBAAiB,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,OAAO,CAAC;IAC9D,iBAAiB;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IAEpC,UAAU,EAAE,uBAAuB,CAAC;IAGpC,UAAU,EAAE,uBAAuB,CAAC;IAGpC,SAAS,EAAE,sBAAsB,CAAC;IAGlC,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC1C,gCAAgC;IAChC,cAAc,EAAE,CACd,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,aAAa,EACpB,QAAQ,EAAE,kBAAkB,EAC5B,YAAY,CAAC,EAAE,MAAM,KAClB,IAAI,CAAC;IACV,wCAAwC;IACxC,YAAY,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IAGnF,gDAAgD;IAChD,iBAAiB,EAAE,OAAO,CAAC;IAC3B,0CAA0C;IAC1C,oBAAoB,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACpE,iCAAiC;IACjC,YAAY,EAAE,OAAO,CAAC;IACtB,mCAAmC;IACnC,eAAe,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/D,kCAAkC;IAClC,iBAAiB,EAAE,OAAO,CAAC;IAC3B,gCAAgC;IAChC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,4BAA4B;IAC5B,UAAU,EAAE,OAAO,CAAC;IAGpB,iCAAiC;IACjC,IAAI,EAAE,gBAAgB,CAAC;IACvB,+BAA+B;IAC/B,QAAQ,EAAE,oBAAoB,CAAC;IAG/B,kCAAkC;IAClC,YAAY,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC3C,qCAAqC;IACrC,eAAe,EAAE,cAAc,GAAG,SAAS,CAAC;IAC5C,0CAA0C;IAC1C,oBAAoB,EAAE,gBAAgB,EAAE,CAAC;IAGzC,+CAA+C;IAC/C,iBAAiB,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,OAAO,CAAC;IAC7D,kDAAkD;IAClD,cAAc,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACxE,sCAAsC;IACtC,gBAAgB,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,aAAa,CAAC;CAClF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,iBAAiB,EAC7B,OAAO,CAAC,EAAE,eAAe,EACzB,OAAO,GAAE,sBAAuD,GAC/D,qBAAqB,CA8MvB;AAED,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"useFormRenderer.d.ts","sourceRoot":"","sources":["../../src/hooks/useFormRenderer.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@ import { useState, useCallback, useEffect, useMemo } from 'react';
7
7
  import { useFormNavigation } from './useFormNavigation';
8
8
  import { useFormValidation } from './useFormValidation';
9
9
  import { useFormInstances } from './useFormInstances';
10
+ import { GroupeInstanceManager } from '../lib/utils/groupeInstanceManager';
10
11
  /**
11
12
  * Hook principal pour gérer un formulaire d'enquête
12
13
  *
@@ -94,6 +95,20 @@ export function useFormRenderer(formulaire, enquete, options = { config: { mode:
94
95
  // === Mise à jour des réponses ===
95
96
  const updateResponse = useCallback((variableCode, value, variable, numeroMembre) => {
96
97
  const responseKey = getResponseKey(variableCode, numeroMembre);
98
+ // Validation de la variable de contrôle : empêcher la réduction en-dessous des instances existantes
99
+ if (isControlVariable(variable) && value !== null && value !== '') {
100
+ const numericValue = typeof value === 'number' ? value : parseInt(String(value), 10);
101
+ if (!isNaN(numericValue)) {
102
+ const groupesMultiples = (instances.groupesWithInstances.length > 0
103
+ ? instances.groupesWithInstances
104
+ : formulaire?.groupes || []).filter(g => g.estMultiple);
105
+ const validation = GroupeInstanceManager.canModifyControlVariable(variableCode, numericValue, groupesMultiples, responses);
106
+ if (!validation.canProceed) {
107
+ console.warn('[FormRenderer] Variable de contrôle rejetée:', validation.constraints[0]?.message);
108
+ return; // Rejeter le changement - l'input revient à la valeur précédente
109
+ }
110
+ }
111
+ }
97
112
  const newResponse = {
98
113
  variableId: variable.id,
99
114
  variableCode: variableCode,
@@ -129,9 +144,11 @@ export function useFormRenderer(formulaire, enquete, options = { config: { mode:
129
144
  setHasUnsavedChanges(true);
130
145
  }, [
131
146
  getResponseKey,
147
+ isControlVariable,
132
148
  currentGroup,
133
149
  currentInstance,
134
150
  instances,
151
+ formulaire?.groupes,
135
152
  responses,
136
153
  onResponsesChange
137
154
  ]);
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Tests unitaires pour GroupeInstanceManager
3
+ * Couvre le comptage, l'ajout/suppression, la validation et les scénarios d'intégration
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=groupeInstanceManager.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"groupeInstanceManager.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/groupeInstanceManager.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,454 @@
1
+ /**
2
+ * Tests unitaires pour GroupeInstanceManager
3
+ * Couvre le comptage, l'ajout/suppression, la validation et les scénarios d'intégration
4
+ */
5
+ import { GroupeInstanceManager } from '../utils/groupeInstanceManager';
6
+ // === Helpers de création de données de test ===
7
+ function createVariable(code, overrides = {}) {
8
+ return {
9
+ id: Math.floor(Math.random() * 10000),
10
+ code,
11
+ designation: `Variable ${code}`,
12
+ typeCode: 'STRING',
13
+ groupeCode: 'TEST_GROUP',
14
+ ordre: 1,
15
+ estObligatoire: false,
16
+ estVisible: true,
17
+ ...overrides
18
+ };
19
+ }
20
+ function createGroupe(overrides = {}) {
21
+ return {
22
+ code: 'TEST_GROUP',
23
+ designation: 'Groupe Test',
24
+ ordre: 1,
25
+ estMultiple: false,
26
+ variables: [createVariable('S1_01'), createVariable('S1_02')],
27
+ ...overrides
28
+ };
29
+ }
30
+ function createMultipleGroupe(overrides = {}) {
31
+ return createGroupe({
32
+ estMultiple: true,
33
+ codeVariable: 'S0_23',
34
+ ...overrides
35
+ });
36
+ }
37
+ function createInstance(numero, reponses = {}) {
38
+ return {
39
+ numeroInstance: numero,
40
+ libelle: `Groupe Test ${numero}`,
41
+ estComplete: false,
42
+ reponses
43
+ };
44
+ }
45
+ function createResponse(variableCode, valeur, numeroMembre) {
46
+ return {
47
+ variableId: Math.floor(Math.random() * 10000),
48
+ variableCode,
49
+ valeur,
50
+ numeroMembre,
51
+ dateModification: new Date()
52
+ };
53
+ }
54
+ function createControlResponse(value) {
55
+ return {
56
+ S0_23: createResponse('S0_23', value)
57
+ };
58
+ }
59
+ // === Tests ===
60
+ describe('GroupeInstanceManager', () => {
61
+ describe('getMaxInstances', () => {
62
+ it('devrait retourner 1 pour un groupe non multiple', () => {
63
+ const groupe = createGroupe({ estMultiple: false });
64
+ expect(GroupeInstanceManager.getMaxInstances(groupe, {})).toBe(1);
65
+ });
66
+ it('devrait retourner 1 pour un groupe multiple sans codeVariable', () => {
67
+ const groupe = createGroupe({ estMultiple: true, codeVariable: undefined });
68
+ expect(GroupeInstanceManager.getMaxInstances(groupe, {})).toBe(1);
69
+ });
70
+ it('devrait retourner la valeur numerique de la variable de controle', () => {
71
+ const groupe = createMultipleGroupe();
72
+ const responses = createControlResponse(3);
73
+ expect(GroupeInstanceManager.getMaxInstances(groupe, responses)).toBe(3);
74
+ });
75
+ it('devrait parser une valeur string de la variable de controle', () => {
76
+ const groupe = createMultipleGroupe();
77
+ const responses = {
78
+ S0_23: createResponse('S0_23', '5')
79
+ };
80
+ expect(GroupeInstanceManager.getMaxInstances(groupe, responses)).toBe(5);
81
+ });
82
+ it('devrait retourner 1 si aucune reponse pour la variable de controle', () => {
83
+ const groupe = createMultipleGroupe();
84
+ expect(GroupeInstanceManager.getMaxInstances(groupe, {})).toBe(1);
85
+ });
86
+ it('devrait trouver la reponse par variableCode quand la cle directe manque', () => {
87
+ const groupe = createMultipleGroupe();
88
+ const responses = {
89
+ 'some_other_key': createResponse('S0_23', 4)
90
+ };
91
+ expect(GroupeInstanceManager.getMaxInstances(groupe, responses)).toBe(4);
92
+ });
93
+ it('devrait retourner strictement la valeur de controle (pas gonflee par instances existantes)', () => {
94
+ // Apres Fix 1c: getMaxInstances ne doit PAS gonfler au-dela de la variable de controle
95
+ const groupe = createMultipleGroupe({
96
+ instances: [createInstance(1), createInstance(2), createInstance(3), createInstance(4), createInstance(5)]
97
+ });
98
+ const responses = createControlResponse(3);
99
+ // Le max devrait etre 3 (valeur de controle), pas 5 (nombre d'instances)
100
+ expect(GroupeInstanceManager.getMaxInstances(groupe, responses)).toBe(3);
101
+ });
102
+ });
103
+ describe('getInstancesCount', () => {
104
+ it('devrait retourner 1 pour un groupe non multiple', () => {
105
+ const groupe = createGroupe({ estMultiple: false });
106
+ expect(GroupeInstanceManager.getInstancesCount(groupe, {})).toBe(1);
107
+ });
108
+ it('devrait retourner le nombre d\'instances si elles existent', () => {
109
+ const groupe = createMultipleGroupe({
110
+ instances: [createInstance(1), createInstance(2), createInstance(3)]
111
+ });
112
+ expect(GroupeInstanceManager.getInstancesCount(groupe, {})).toBe(3);
113
+ });
114
+ it('devrait utiliser le fallback par numeroMembre quand instances est vide', () => {
115
+ const groupe = createMultipleGroupe({ instances: undefined });
116
+ const responses = {
117
+ 'S1_01_1': createResponse('S1_01', 'val1', 1),
118
+ 'S1_01_2': createResponse('S1_01', 'val2', 2),
119
+ 'S1_02_1': createResponse('S1_02', 'val3', 1),
120
+ 'S1_02_2': createResponse('S1_02', 'val4', 2),
121
+ };
122
+ expect(GroupeInstanceManager.getInstancesCount(groupe, responses)).toBe(2);
123
+ });
124
+ it('devrait retourner au minimum 1', () => {
125
+ const groupe = createMultipleGroupe({ instances: undefined });
126
+ expect(GroupeInstanceManager.getInstancesCount(groupe, {})).toBe(1);
127
+ });
128
+ it('ne devrait PAS fausser le comptage avec des codes variables contenant des chiffres', () => {
129
+ // Apres Fix 1a: le parsing de cles "S1_01" ne doit pas creer un faux instance numero 1
130
+ const groupe = createMultipleGroupe({
131
+ instances: [createInstance(1), createInstance(2)]
132
+ });
133
+ // Meme s'il y a des cles avec des formats ambigus, instances.length prime
134
+ expect(GroupeInstanceManager.getInstancesCount(groupe, {})).toBe(2);
135
+ });
136
+ });
137
+ describe('createInstance', () => {
138
+ it('devrait creer une instance avec le bon numero et libelle', () => {
139
+ const groupe = createMultipleGroupe();
140
+ const instance = GroupeInstanceManager.createInstance(groupe, 3);
141
+ expect(instance.numeroInstance).toBe(3);
142
+ expect(instance.libelle).toBe('Groupe Test 3');
143
+ expect(instance.estComplete).toBe(false);
144
+ expect(instance.reponses).toEqual({});
145
+ });
146
+ });
147
+ describe('initializeInstances', () => {
148
+ it('devrait creer au moins 1 instance si aucune reponse', () => {
149
+ const groupe = createMultipleGroupe();
150
+ const responses = createControlResponse(3);
151
+ const instances = GroupeInstanceManager.initializeInstances(groupe, responses);
152
+ expect(instances.length).toBeGreaterThanOrEqual(1);
153
+ });
154
+ it('devrait creer des instances basees sur les reponses existantes avec numeroMembre', () => {
155
+ const groupe = createMultipleGroupe();
156
+ const responses = {
157
+ ...createControlResponse(3),
158
+ 'S1_01_1': createResponse('S1_01', 'Alice', 1),
159
+ 'S1_02_1': createResponse('S1_02', '25', 1),
160
+ 'S1_01_2': createResponse('S1_01', 'Bob', 2),
161
+ 'S1_02_2': createResponse('S1_02', '30', 2),
162
+ };
163
+ const instances = GroupeInstanceManager.initializeInstances(groupe, responses);
164
+ expect(instances.length).toBe(2);
165
+ });
166
+ it('ne devrait PAS muter l\'objet responses original', () => {
167
+ const groupe = createMultipleGroupe();
168
+ const responses = {
169
+ ...createControlResponse(2),
170
+ 'S1_01_1': createResponse('S1_01', 'Alice', 1),
171
+ };
172
+ const originalKeys = Object.keys(responses).sort();
173
+ GroupeInstanceManager.initializeInstances(groupe, responses);
174
+ expect(Object.keys(responses).sort()).toEqual(originalKeys);
175
+ });
176
+ });
177
+ describe('canAddInstance', () => {
178
+ it('devrait retourner false pour un groupe non multiple', () => {
179
+ const groupe = createGroupe({ estMultiple: false });
180
+ const result = GroupeInstanceManager.canAddInstance(groupe, {});
181
+ expect(result.canProceed).toBe(false);
182
+ });
183
+ it('devrait retourner true si currentCount < maxInstances', () => {
184
+ const groupe = createMultipleGroupe({
185
+ instances: [createInstance(1)]
186
+ });
187
+ const responses = createControlResponse(3);
188
+ const result = GroupeInstanceManager.canAddInstance(groupe, responses);
189
+ expect(result.canProceed).toBe(true);
190
+ });
191
+ it('devrait retourner false si currentCount >= maxInstances', () => {
192
+ const groupe = createMultipleGroupe({
193
+ instances: [createInstance(1), createInstance(2), createInstance(3)]
194
+ });
195
+ const responses = createControlResponse(3);
196
+ const result = GroupeInstanceManager.canAddInstance(groupe, responses);
197
+ expect(result.canProceed).toBe(false);
198
+ });
199
+ });
200
+ describe('canRemoveInstance', () => {
201
+ it('devrait retourner true si plus de 1 instance', () => {
202
+ const groupe = createMultipleGroupe({
203
+ instances: [createInstance(1), createInstance(2)]
204
+ });
205
+ const result = GroupeInstanceManager.canRemoveInstance(groupe, {});
206
+ expect(result.canProceed).toBe(true);
207
+ });
208
+ it('devrait retourner false si 1 seule instance', () => {
209
+ const groupe = createMultipleGroupe({
210
+ instances: [createInstance(1)]
211
+ });
212
+ const result = GroupeInstanceManager.canRemoveInstance(groupe, {});
213
+ expect(result.canProceed).toBe(false);
214
+ });
215
+ it('devrait retourner false si aucune instance', () => {
216
+ const groupe = createMultipleGroupe({
217
+ instances: []
218
+ });
219
+ const result = GroupeInstanceManager.canRemoveInstance(groupe, {});
220
+ expect(result.canProceed).toBe(false);
221
+ });
222
+ });
223
+ describe('addInstance', () => {
224
+ it('devrait ajouter une instance et retourner success=true', () => {
225
+ const groupe = createMultipleGroupe({
226
+ instances: [createInstance(1)]
227
+ });
228
+ const responses = createControlResponse(3);
229
+ const result = GroupeInstanceManager.addInstance(groupe, responses);
230
+ expect(result.success).toBe(true);
231
+ expect(result.instance).toBeDefined();
232
+ expect(result.instance.numeroInstance).toBe(2);
233
+ });
234
+ it('ne devrait PAS muter le groupe original (immutabilite)', () => {
235
+ const groupe = createMultipleGroupe({
236
+ instances: [createInstance(1)]
237
+ });
238
+ const originalLength = groupe.instances.length;
239
+ const responses = createControlResponse(3);
240
+ const result = GroupeInstanceManager.addInstance(groupe, responses);
241
+ expect(result.success).toBe(true);
242
+ // Apres Fix 2a: le groupe original ne doit PAS avoir ete mute
243
+ expect(groupe.instances.length).toBe(originalLength);
244
+ // Le nouveau groupe doit etre retourne dans updatedGroupe
245
+ expect(result.updatedGroupe).toBeDefined();
246
+ expect(result.updatedGroupe.instances.length).toBe(originalLength + 1);
247
+ });
248
+ it('devrait refuser si deja au maximum', () => {
249
+ const groupe = createMultipleGroupe({
250
+ instances: [createInstance(1), createInstance(2)]
251
+ });
252
+ const responses = createControlResponse(2);
253
+ const result = GroupeInstanceManager.addInstance(groupe, responses);
254
+ expect(result.success).toBe(false);
255
+ expect(result.error).toBeDefined();
256
+ });
257
+ });
258
+ describe('removeInstance', () => {
259
+ it('devrait supprimer une instance et retourner success=true', () => {
260
+ const groupe = createMultipleGroupe({
261
+ instances: [createInstance(1), createInstance(2)]
262
+ });
263
+ const responses = {
264
+ ...createControlResponse(2),
265
+ 'S1_01_1': createResponse('S1_01', 'Alice', 1),
266
+ 'S1_01_2': createResponse('S1_01', 'Bob', 2),
267
+ };
268
+ const result = GroupeInstanceManager.removeInstance(groupe, 1, responses);
269
+ expect(result.success).toBe(true);
270
+ });
271
+ it('ne devrait PAS muter le groupe original ni les reponses (immutabilite)', () => {
272
+ const groupe = createMultipleGroupe({
273
+ instances: [createInstance(1), createInstance(2)]
274
+ });
275
+ const responses = {
276
+ ...createControlResponse(2),
277
+ 'S1_01_1': createResponse('S1_01', 'Alice', 1),
278
+ 'S1_01_2': createResponse('S1_01', 'Bob', 2),
279
+ };
280
+ const originalGroupeInstancesLength = groupe.instances.length;
281
+ const originalResponsesKeys = Object.keys(responses).sort();
282
+ const result = GroupeInstanceManager.removeInstance(groupe, 1, responses);
283
+ expect(result.success).toBe(true);
284
+ // Apres Fix 2b: le groupe original ne doit PAS avoir ete mute
285
+ expect(groupe.instances.length).toBe(originalGroupeInstancesLength);
286
+ // Les reponses originales ne doivent PAS avoir ete mutees
287
+ expect(Object.keys(responses).sort()).toEqual(originalResponsesKeys);
288
+ // Les nouveaux objets doivent etre retournes
289
+ expect(result.updatedGroupe).toBeDefined();
290
+ expect(result.updatedResponses).toBeDefined();
291
+ });
292
+ it('devrait refuser si 1 seule instance', () => {
293
+ const groupe = createMultipleGroupe({
294
+ instances: [createInstance(1)]
295
+ });
296
+ const result = GroupeInstanceManager.removeInstance(groupe, 1, {});
297
+ expect(result.success).toBe(false);
298
+ });
299
+ it('devrait reordonner les instances apres suppression (2 supprime de [1,2,3] → [1,2])', () => {
300
+ const groupe = createMultipleGroupe({
301
+ instances: [createInstance(1), createInstance(2), createInstance(3)]
302
+ });
303
+ const responses = {
304
+ ...createControlResponse(3),
305
+ 'S1_01_1': createResponse('S1_01', 'Alice', 1),
306
+ 'S1_01_2': createResponse('S1_01', 'Bob', 2),
307
+ 'S1_01_3': createResponse('S1_01', 'Charlie', 3),
308
+ };
309
+ const result = GroupeInstanceManager.removeInstance(groupe, 2, responses);
310
+ expect(result.success).toBe(true);
311
+ // Apres Fix 2b+2c: verifier via updatedGroupe
312
+ const updatedGroupe = result.updatedGroupe;
313
+ if (updatedGroupe) {
314
+ expect(updatedGroupe.instances.length).toBe(2);
315
+ expect(updatedGroupe.instances[0].numeroInstance).toBe(1);
316
+ expect(updatedGroupe.instances[1].numeroInstance).toBe(2);
317
+ }
318
+ });
319
+ it('devrait mettre a jour les cles de reponses apres reordonnancement', () => {
320
+ const groupe = createMultipleGroupe({
321
+ instances: [createInstance(1), createInstance(2), createInstance(3)]
322
+ });
323
+ const responses = {
324
+ ...createControlResponse(3),
325
+ 'S1_01_1': createResponse('S1_01', 'Alice', 1),
326
+ 'S1_01_2': createResponse('S1_01', 'Bob', 2),
327
+ 'S1_01_3': createResponse('S1_01', 'Charlie', 3),
328
+ };
329
+ const result = GroupeInstanceManager.removeInstance(groupe, 1, responses);
330
+ expect(result.success).toBe(true);
331
+ // Apres Fix 2b+2c: verifier via updatedResponses
332
+ const updatedResponses = result.updatedResponses;
333
+ if (updatedResponses) {
334
+ // Instance 2 (Bob) → instance 1, Instance 3 (Charlie) → instance 2
335
+ expect(updatedResponses['S1_01_1']?.valeur).toBe('Bob');
336
+ expect(updatedResponses['S1_01_2']?.valeur).toBe('Charlie');
337
+ expect(updatedResponses['S1_01_3']).toBeUndefined();
338
+ }
339
+ });
340
+ });
341
+ describe('canModifyControlVariable', () => {
342
+ it('devrait retourner true si newValue >= existingInstances', () => {
343
+ const groupe = createMultipleGroupe({
344
+ instances: [createInstance(1), createInstance(2)]
345
+ });
346
+ const result = GroupeInstanceManager.canModifyControlVariable('S0_23', 3, [groupe], {});
347
+ expect(result.canProceed).toBe(true);
348
+ });
349
+ it('devrait retourner true si newValue = existingInstances (egal)', () => {
350
+ const groupe = createMultipleGroupe({
351
+ instances: [createInstance(1), createInstance(2)]
352
+ });
353
+ const result = GroupeInstanceManager.canModifyControlVariable('S0_23', 2, [groupe], {});
354
+ expect(result.canProceed).toBe(true);
355
+ });
356
+ it('devrait retourner false si newValue < existingInstances', () => {
357
+ const groupe = createMultipleGroupe({
358
+ instances: [createInstance(1), createInstance(2), createInstance(3)]
359
+ });
360
+ const result = GroupeInstanceManager.canModifyControlVariable('S0_23', 2, [groupe], {});
361
+ expect(result.canProceed).toBe(false);
362
+ });
363
+ it('devrait retourner true si aucun groupe n\'utilise cette variable', () => {
364
+ const groupe = createMultipleGroupe({
365
+ codeVariable: 'OTHER_VAR',
366
+ instances: [createInstance(1), createInstance(2)]
367
+ });
368
+ const result = GroupeInstanceManager.canModifyControlVariable('S0_23', 1, [groupe], {});
369
+ expect(result.canProceed).toBe(true);
370
+ });
371
+ });
372
+ describe('isInstanceComplete', () => {
373
+ it('devrait retourner true quand toutes les variables obligatoires sont remplies', () => {
374
+ const groupe = createMultipleGroupe({
375
+ variables: [
376
+ createVariable('S1_01', { estObligatoire: true }),
377
+ createVariable('S1_02', { estObligatoire: false })
378
+ ]
379
+ });
380
+ const instance = createInstance(1, {
381
+ S1_01: createResponse('S1_01', 'Alice', 1)
382
+ });
383
+ expect(GroupeInstanceManager.isInstanceComplete(instance, groupe)).toBe(true);
384
+ });
385
+ it('devrait retourner false quand une variable obligatoire manque', () => {
386
+ const groupe = createMultipleGroupe({
387
+ variables: [
388
+ createVariable('S1_01', { estObligatoire: true }),
389
+ createVariable('S1_02', { estObligatoire: true })
390
+ ]
391
+ });
392
+ const instance = createInstance(1, {
393
+ S1_01: createResponse('S1_01', 'Alice', 1)
394
+ });
395
+ expect(GroupeInstanceManager.isInstanceComplete(instance, groupe)).toBe(false);
396
+ });
397
+ });
398
+ describe('scenarios integration', () => {
399
+ it('S0_23=2, creer 2 instances, supprimer 1, ajouter 1 → doit fonctionner', () => {
400
+ // Setup: S0_23=2, 2 instances
401
+ let groupe = createMultipleGroupe({
402
+ instances: [createInstance(1), createInstance(2)]
403
+ });
404
+ let responses = {
405
+ ...createControlResponse(2),
406
+ 'S1_01_1': createResponse('S1_01', 'Alice', 1),
407
+ 'S1_01_2': createResponse('S1_01', 'Bob', 2),
408
+ };
409
+ // Supprimer instance 1
410
+ const removeResult = GroupeInstanceManager.removeInstance(groupe, 1, responses);
411
+ expect(removeResult.success).toBe(true);
412
+ // Utiliser les objets mis a jour si disponibles (apres Fix 2)
413
+ const updatedGroupe = removeResult.updatedGroupe || groupe;
414
+ const updatedResponses = removeResult.updatedResponses || responses;
415
+ // Ajouter une nouvelle instance
416
+ const addResult = GroupeInstanceManager.addInstance(updatedGroupe, updatedResponses);
417
+ expect(addResult.success).toBe(true);
418
+ });
419
+ it('S0_23=3, creer 3 instances, tenter d\'ajouter → doit bloquer', () => {
420
+ const groupe = createMultipleGroupe({
421
+ instances: [createInstance(1), createInstance(2), createInstance(3)]
422
+ });
423
+ const responses = createControlResponse(3);
424
+ const result = GroupeInstanceManager.addInstance(groupe, responses);
425
+ expect(result.success).toBe(false);
426
+ });
427
+ it('cycle complet ajout/suppression/ajout avec immutabilite', () => {
428
+ // Debut: 1 instance, max 3
429
+ let groupe = createMultipleGroupe({
430
+ instances: [createInstance(1)]
431
+ });
432
+ let responses = createControlResponse(3);
433
+ // Ajouter instance 2
434
+ let result = GroupeInstanceManager.addInstance(groupe, responses);
435
+ expect(result.success).toBe(true);
436
+ groupe = result.updatedGroupe || groupe;
437
+ // Ajouter instance 3
438
+ result = GroupeInstanceManager.addInstance(groupe, responses);
439
+ expect(result.success).toBe(true);
440
+ groupe = result.updatedGroupe || groupe;
441
+ // Tenter d'ajouter instance 4 → refuse
442
+ result = GroupeInstanceManager.addInstance(groupe, responses);
443
+ expect(result.success).toBe(false);
444
+ // Supprimer instance 2
445
+ const removeResult = GroupeInstanceManager.removeInstance(groupe, 2, responses);
446
+ expect(removeResult.success).toBe(true);
447
+ groupe = removeResult.updatedGroupe || groupe;
448
+ responses = removeResult.updatedResponses || responses;
449
+ // Ajouter a nouveau → doit fonctionner
450
+ result = GroupeInstanceManager.addInstance(groupe, responses);
451
+ expect(result.success).toBe(true);
452
+ });
453
+ });
454
+ });
@@ -15,6 +15,8 @@ export declare class GroupeInstanceManager {
15
15
  static getMinInstances(groupe: GroupeFormulaire): number;
16
16
  /**
17
17
  * Compte le nombre d'instances existantes pour un groupe
18
+ * Source de vérité : groupe.instances.length
19
+ * Fallback par numeroMembre uniquement pour l'initialisation
18
20
  */
19
21
  static getInstancesCount(groupe: GroupeFormulaire, responses: Record<string, EnqueteReponse>): number;
20
22
  /**
@@ -42,18 +44,23 @@ export declare class GroupeInstanceManager {
42
44
  */
43
45
  static canModifyControlVariable(variableCode: string, newValue: number, groupesMultiples: GroupeFormulaire[], responses: Record<string, EnqueteReponse>): ConstraintValidationResult;
44
46
  /**
45
- * Ajoute une nouvelle instance à un groupe
47
+ * Ajoute une nouvelle instance à un groupe (immutable)
48
+ * Retourne un nouveau groupe sans muter l'original
46
49
  */
47
50
  static addInstance(groupe: GroupeFormulaire, responses: Record<string, EnqueteReponse>): {
48
51
  success: boolean;
49
52
  instance?: GroupeInstance;
53
+ updatedGroupe?: GroupeFormulaire;
50
54
  error?: string;
51
55
  };
52
56
  /**
53
- * Supprime une instance d'un groupe
57
+ * Supprime une instance d'un groupe (immutable)
58
+ * Retourne de nouveaux objets sans muter les originaux
54
59
  */
55
60
  static removeInstance(groupe: GroupeFormulaire, instanceNumber: number, responses: Record<string, EnqueteReponse>): {
56
61
  success: boolean;
62
+ updatedGroupe?: GroupeFormulaire;
63
+ updatedResponses?: Record<string, EnqueteReponse>;
57
64
  error?: string;
58
65
  };
59
66
  /**
@@ -81,8 +88,12 @@ export declare class GroupeInstanceManager {
81
88
  progressPercentage: number;
82
89
  };
83
90
  /**
84
- * Réorganise les numéros d'instances après suppression
91
+ * Réorganise les numéros d'instances après suppression (immutable)
92
+ * Retourne de nouveaux objets sans muter les originaux
85
93
  */
86
- static reorderInstances(groupe: GroupeFormulaire, responses: Record<string, EnqueteReponse>): void;
94
+ static reorderInstances(instances: GroupeInstance[], responses: Record<string, EnqueteReponse>, groupe: GroupeFormulaire): {
95
+ reorderedInstances: GroupeInstance[];
96
+ reorderedResponses: Record<string, EnqueteReponse>;
97
+ };
87
98
  }
88
99
  //# sourceMappingURL=groupeInstanceManager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"groupeInstanceManager.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/groupeInstanceManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,cAAc,EAEd,0BAA0B,EAE3B,MAAM,qBAAqB,CAAC;AAE7B,qBAAa,qBAAqB;IAEhC;;;OAGG;IACH,MAAM,CAAC,eAAe,CACpB,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GACxC,MAAM;IA0ET;;OAEG;IACH,MAAM,CAAC,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM;IASxD;;OAEG;IACH,MAAM,CAAC,iBAAiB,CACtB,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GACxC,MAAM;IAoDT;;OAEG;IACH,MAAM,CAAC,cAAc,CACnB,MAAM,EAAE,gBAAgB,EACxB,cAAc,EAAE,MAAM,GACrB,cAAc;IAWjB;;OAEG;IACH,MAAM,CAAC,mBAAmB,CACxB,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GACxC,cAAc,EAAE;IAkKnB;;OAEG;IACH,MAAM,CAAC,kBAAkB,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,gBAAgB,GAAG,OAAO;IAStF;;OAEG;IACH,MAAM,CAAC,cAAc,CACnB,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GACxC,0BAA0B;IA+C7B;;OAEG;IACH,MAAM,CAAC,iBAAiB,CACtB,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GACxC,0BAA0B;IAsC7B;;OAEG;IACH,MAAM,CAAC,wBAAwB,CAC7B,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,gBAAgB,EAAE,EACpC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GACxC,0BAA0B;IAsC7B;;OAEG;IACH,MAAM,CAAC,WAAW,CAChB,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GACxC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAqClE;;OAEG;IACH,MAAM,CAAC,cAAc,CACnB,MAAM,EAAE,gBAAgB,EACxB,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GACxC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAoCvC;;OAEG;IACH,MAAM,CAAC,sBAAsB,CAC3B,MAAM,EAAE,gBAAgB,EACxB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GACxC,IAAI;IA8BP;;OAEG;IACH,MAAM,CAAC,qBAAqB,CAC1B,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,gBAAgB,EACxB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GAC9C,IAAI;IAsBP;;OAEG;IACH,MAAM,CAAC,yBAAyB,CAC9B,eAAe,EAAE,cAAc,EAC/B,MAAM,EAAE,gBAAgB,GACvB,0BAA0B;IAgC7B;;OAEG;IACH,MAAM,CAAC,0BAA0B,CAAC,MAAM,EAAE,gBAAgB,GAAG,cAAc,GAAG,IAAI;IAQlF;;OAEG;IACH,MAAM,CAAC,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG;QACvD,kBAAkB,EAAE,MAAM,CAAC;QAC3B,cAAc,EAAE,MAAM,CAAC;QACvB,kBAAkB,EAAE,MAAM,CAAC;KAC5B;IAoBD;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,IAAI;CAuCnG"}
1
+ {"version":3,"file":"groupeInstanceManager.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/groupeInstanceManager.ts"],"names":[],"mappings":"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"}
@@ -9,19 +9,8 @@ export class GroupeInstanceManager {
9
9
  */
10
10
  static getMaxInstances(groupe, responses) {
11
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
12
  return 1;
18
13
  }
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
14
  // Chercher la réponse de la variable de contrôle
26
15
  // D'abord essayer avec la clé directe
27
16
  let controlResponse = responses[groupe.codeVariable];
@@ -31,17 +20,8 @@ export class GroupeInstanceManager {
31
20
  if (foundResponse) {
32
21
  controlResponse = foundResponse;
33
22
  }
34
- console.log('GroupeInstanceManager.getMaxInstances - Recherche par variableCode:', {
35
- found: !!foundResponse,
36
- controlResponse: foundResponse
37
- });
38
23
  }
39
24
  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
25
  // Convertir la valeur en nombre
46
26
  let maxFromControl = 1; // Valeur par défaut
47
27
  if (typeof controlValue === 'number') {
@@ -53,20 +33,9 @@ export class GroupeInstanceManager {
53
33
  maxFromControl = parsed;
54
34
  }
55
35
  }
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);
36
+ // Le max pour l'ajout est strictement la valeur de la variable de contrôle
37
+ // La protection d'affichage des onglets existants est dans GroupeInstanceTabs (minDisplayCount)
38
+ return Math.max(1, maxFromControl);
70
39
  }
71
40
  /**
72
41
  * Récupère le nombre minimum d'instances (basé sur les instances existantes)
@@ -80,19 +49,19 @@ export class GroupeInstanceManager {
80
49
  }
81
50
  /**
82
51
  * Compte le nombre d'instances existantes pour un groupe
52
+ * Source de vérité : groupe.instances.length
53
+ * Fallback par numeroMembre uniquement pour l'initialisation
83
54
  */
84
55
  static getInstancesCount(groupe, responses) {
85
56
  if (!groupe.estMultiple) {
86
57
  return 1;
87
58
  }
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
59
+ // Source de vérité : le tableau instances
60
+ if (groupe.instances && groupe.instances.length > 0) {
61
+ return groupe.instances.length;
62
+ }
63
+ // Fallback pour l'initialisation : compter par numeroMembre uniquement
94
64
  const instanceNumbers = new Set();
95
- // Méthode 1: Par numeroMembre
96
65
  groupe.variables.forEach(variable => {
97
66
  Object.values(responses).forEach(response => {
98
67
  if (response.variableCode === variable.code && response.numeroMembre) {
@@ -100,30 +69,7 @@ export class GroupeInstanceManager {
100
69
  }
101
70
  });
102
71
  });
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;
72
+ return Math.max(1, instanceNumbers.size);
127
73
  }
128
74
  /**
129
75
  * Crée une nouvelle instance pour un groupe multiple
@@ -144,12 +90,6 @@ export class GroupeInstanceManager {
144
90
  if (!groupe.estMultiple) {
145
91
  return [];
146
92
  }
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
93
  // Identifier toutes les instances existantes pour ce groupe
154
94
  const instanceNumbers = new Set();
155
95
  // Collecter les réponses existantes pour ce groupe (avec et sans numeroMembre)
@@ -164,29 +104,8 @@ export class GroupeInstanceManager {
164
104
  }
165
105
  }
166
106
  });
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
- });
107
+ // Note: le parsing de clé par format "CODE_NUMERO" a été supprimé car il
108
+ // produisait des faux positifs pour les codes variables contenant des chiffres (ex: S1_01)
190
109
  const maxInstances = this.getMaxInstances(groupe, responses);
191
110
  // Déterminer le nombre d'instances à créer
192
111
  let instanceCount = Math.max(1, instanceNumbers.size);
@@ -197,12 +116,6 @@ export class GroupeInstanceManager {
197
116
  if (instanceCount === 0) {
198
117
  instanceCount = 1;
199
118
  }
200
- console.log('GroupeInstanceManager.initializeInstances - Calcul instances:', {
201
- groupeCode: groupe.code,
202
- maxInstances,
203
- existingInstancesCount: instanceNumbers.size,
204
- finalInstanceCount: instanceCount
205
- });
206
119
  const instances = [];
207
120
  // Séparer les réponses avec et sans numeroMembre
208
121
  const responsesWithMember = groupResponses.filter(r => r.numeroMembre);
@@ -226,12 +139,6 @@ export class GroupeInstanceManager {
226
139
  if (i === 1) {
227
140
  existingResponse = responsesWithoutMember.find(r => r.variableCode === variable.code);
228
141
  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
142
  // Créer une copie avec numeroMembre pour cette instance
236
143
  existingResponse = {
237
144
  ...existingResponse,
@@ -241,35 +148,13 @@ export class GroupeInstanceManager {
241
148
  }
242
149
  }
243
150
  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
151
  instance.reponses[variable.code] = existingResponse;
252
152
  }
253
153
  });
254
154
  // Vérifier si l'instance est complète
255
155
  instance.estComplete = this.isInstanceComplete(instance, groupe);
256
156
  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
157
  }
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
158
  return instances;
274
159
  }
275
160
  /**
@@ -332,12 +217,6 @@ export class GroupeInstanceManager {
332
217
  const constraints = [];
333
218
  // CORRECTION: Utiliser le nombre d'instances du groupe directement
334
219
  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
220
  if (currentCount <= 1) {
342
221
  constraints.push({
343
222
  type: 'instance_limit',
@@ -368,7 +247,7 @@ export class GroupeInstanceManager {
368
247
  // Trouver tous les groupes qui utilisent cette variable de contrôle
369
248
  const affectedGroups = groupesMultiples.filter(g => g.codeVariable === variableCode);
370
249
  for (const groupe of affectedGroups) {
371
- const existingInstances = this.getInstancesCount(groupe, responses);
250
+ const existingInstances = groupe.instances?.length || this.getInstancesCount(groupe, responses);
372
251
  if (newValue < existingInstances) {
373
252
  constraints.push({
374
253
  type: 'bidirectional_control',
@@ -396,7 +275,8 @@ export class GroupeInstanceManager {
396
275
  };
397
276
  }
398
277
  /**
399
- * Ajoute une nouvelle instance à un groupe
278
+ * Ajoute une nouvelle instance à un groupe (immutable)
279
+ * Retourne un nouveau groupe sans muter l'original
400
280
  */
401
281
  static addInstance(groupe, responses) {
402
282
  const validation = this.canAddInstance(groupe, responses);
@@ -406,29 +286,25 @@ export class GroupeInstanceManager {
406
286
  error: validation.constraints[0]?.message || 'Impossible d\'ajouter une instance'
407
287
  };
408
288
  }
409
- // CORRECTION: Calculer le bon numéro d'instance
410
- if (!groupe.instances) {
411
- groupe.instances = [];
412
- }
413
- const newInstanceNumber = groupe.instances.length + 1;
289
+ const currentInstances = groupe.instances || [];
290
+ const newInstanceNumber = currentInstances.length + 1;
414
291
  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
- });
292
+ // Construire un nouveau groupe avec l'instance ajoutée (sans muter l'original)
293
+ const updatedInstances = [...currentInstances, newInstance];
294
+ const updatedGroupe = {
295
+ ...groupe,
296
+ instances: updatedInstances,
297
+ instancesCount: updatedInstances.length
298
+ };
425
299
  return {
426
300
  success: true,
427
- instance: newInstance
301
+ instance: newInstance,
302
+ updatedGroupe
428
303
  };
429
304
  }
430
305
  /**
431
- * Supprime une instance d'un groupe
306
+ * Supprime une instance d'un groupe (immutable)
307
+ * Retourne de nouveaux objets sans muter les originaux
432
308
  */
433
309
  static removeInstance(groupe, instanceNumber, responses) {
434
310
  const validation = this.canRemoveInstance(groupe, responses);
@@ -438,26 +314,29 @@ export class GroupeInstanceManager {
438
314
  error: validation.constraints[0]?.message || 'Impossible de supprimer cette instance'
439
315
  };
440
316
  }
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
- }
317
+ // Filtrer les instances (sans muter l'original)
318
+ const filteredInstances = (groupe.instances || []).filter(i => i.numeroInstance !== instanceNumber);
319
+ // Construire de nouvelles réponses en excluant celles de l'instance supprimée
320
+ const cleanedResponses = {};
321
+ Object.entries(responses).forEach(([key, response]) => {
322
+ const belongsToGroup = groupe.variables.some(v => v.code === response.variableCode);
323
+ if (belongsToGroup && response.numeroMembre === instanceNumber) {
324
+ // Exclure les réponses de l'instance supprimée
325
+ return;
455
326
  }
327
+ cleanedResponses[key] = { ...response };
456
328
  });
457
329
  // Réorganiser les instances pour maintenir une numérotation séquentielle
458
- this.reorderInstances(groupe, responses);
330
+ const { reorderedInstances, reorderedResponses } = this.reorderInstances(filteredInstances, cleanedResponses, groupe);
331
+ const updatedGroupe = {
332
+ ...groupe,
333
+ instances: reorderedInstances,
334
+ instancesCount: reorderedInstances.length
335
+ };
459
336
  return {
460
- success: true
337
+ success: true,
338
+ updatedGroupe,
339
+ updatedResponses: reorderedResponses
461
340
  };
462
341
  }
463
342
  /**
@@ -473,14 +352,6 @@ export class GroupeInstanceManager {
473
352
  if (!groupe.instances) {
474
353
  groupe.instances = this.initializeInstances(groupe, responses);
475
354
  }
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
355
  groupe.instancesCount = groupe.instances.length;
485
356
  // Mettre à jour le statut de completion des instances
486
357
  groupe.instances.forEach(instance => {
@@ -567,40 +438,64 @@ export class GroupeInstanceManager {
567
438
  };
568
439
  }
569
440
  /**
570
- * Réorganise les numéros d'instances après suppression
441
+ * Réorganise les numéros d'instances après suppression (immutable)
442
+ * Retourne de nouveaux objets sans muter les originaux
571
443
  */
572
- static reorderInstances(groupe, responses) {
573
- if (!groupe.instances) {
574
- return;
444
+ static reorderInstances(instances, responses, groupe) {
445
+ if (!instances || instances.length === 0) {
446
+ return { reorderedInstances: [], reorderedResponses: { ...responses } };
575
447
  }
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) => {
448
+ // Trier les instances par numéro (sur des copies)
449
+ const sortedInstances = [...instances].sort((a, b) => a.numeroInstance - b.numeroInstance);
450
+ // Construire le mapping oldNumber → newNumber
451
+ const numberMapping = new Map();
452
+ sortedInstances.forEach((instance, index) => {
580
453
  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
- });
454
+ if (instance.numeroInstance !== newNumber) {
455
+ numberMapping.set(instance.numeroInstance, newNumber);
456
+ }
457
+ });
458
+ // Créer les nouvelles instances avec les numéros mis à jour
459
+ const reorderedInstances = sortedInstances.map((instance, index) => {
460
+ const newNumber = index + 1;
461
+ if (instance.numeroInstance === newNumber) {
462
+ return { ...instance }; // Copie sans changement de numéro
463
+ }
464
+ // Copier avec nouveau numéro
465
+ const newReponses = {};
466
+ Object.entries(instance.reponses).forEach(([variableCode, response]) => {
467
+ newReponses[variableCode] = { ...response, numeroMembre: newNumber };
468
+ });
469
+ return {
470
+ ...instance,
471
+ numeroInstance: newNumber,
472
+ libelle: `${groupe.designation} ${newNumber}`,
473
+ reponses: newReponses
474
+ };
475
+ });
476
+ // Si aucun renumérotage nécessaire, retourner une copie simple
477
+ if (numberMapping.size === 0) {
478
+ return { reorderedInstances, reorderedResponses: { ...responses } };
479
+ }
480
+ // Construire les nouvelles réponses avec les clés mises à jour
481
+ const reorderedResponses = {};
482
+ Object.entries(responses).forEach(([key, response]) => {
483
+ const belongsToGroup = groupe.variables.some(v => v.code === response.variableCode);
484
+ if (belongsToGroup && response.numeroMembre && numberMapping.has(response.numeroMembre)) {
485
+ const newNumber = numberMapping.get(response.numeroMembre);
486
+ const newKey = `${response.variableCode}_${newNumber}`;
487
+ reorderedResponses[newKey] = { ...response, numeroMembre: newNumber };
488
+ }
489
+ else if (belongsToGroup && response.numeroMembre && !numberMapping.has(response.numeroMembre)) {
490
+ // Réponse du groupe mais pas de renumérotage nécessaire - garder avec la bonne clé
491
+ const correctKey = `${response.variableCode}_${response.numeroMembre}`;
492
+ reorderedResponses[correctKey] = { ...response };
493
+ }
494
+ else {
495
+ // Réponse hors du groupe, garder telle quelle
496
+ reorderedResponses[key] = { ...response };
603
497
  }
604
498
  });
499
+ return { reorderedInstances, reorderedResponses };
605
500
  }
606
501
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsuci/shared-form-components",
3
- "version": "1.0.86",
3
+ "version": "1.0.87",
4
4
  "description": "Composants partagés de rendu de formulaires RSU v2 - Package local pour frontend Admin et Public",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",