codevdesign 1.0.78 → 2.0.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.
Files changed (94) hide show
  1. package/assets/csqc.css +28 -30
  2. package/composants/csqcAide.vue +1 -1
  3. package/composants/csqcAlerteErreur.vue +1 -1
  4. package/composants/csqcChaise/chaiseConteneur.vue +4 -4
  5. package/composants/csqcChaise/chaiseItem.vue +54 -54
  6. package/composants/csqcCodeBudgetaireGenerique.vue +260 -256
  7. package/composants/csqcConfirmation.vue +76 -75
  8. package/composants/csqcDate.vue +88 -86
  9. package/composants/csqcDialogue.vue +120 -118
  10. package/composants/csqcEditeurTexteRiche.vue +378 -378
  11. package/composants/csqcEntete.vue +17 -17
  12. package/composants/csqcImportCSV.vue +2 -2
  13. package/composants/csqcModaleSaisie.vue +97 -97
  14. package/composants/csqcRecherche.vue +9 -9
  15. package/composants/csqcRechercheUtilisateur.vue +198 -198
  16. package/composants/csqcSnackbar.vue +207 -207
  17. package/composants/csqcSwitch.vue +6 -6
  18. package/composants/csqcTable/csqcTable.vue +19 -14
  19. package/composants/csqcTable/csqcTableModaleChoixColonnes.vue +5 -5
  20. package/composants/csqcTable/sortableDataTable.ts +1 -1
  21. package/composants/csqcTexteBilingue.vue +1 -1
  22. package/composants/csqcTiroir.vue +197 -197
  23. package/composants/gabarit/csqcMenu.vue +4 -4
  24. package/composants/gabarit/pivEntete.vue +6 -5
  25. package/composants/gabarit/pivPiedPage.vue +44 -29
  26. package/composants/validateurs.ts +8 -2
  27. package/editeur.ts +1 -1
  28. package/importCSV.ts +1 -1
  29. package/index.ts +80 -80
  30. package/locales/en.json +1 -1
  31. package/locales/fr.json +3 -3
  32. package/modeles/assurancesAssuranceGeneraleGrics.ts +3 -3
  33. package/modeles/assurancesAssurancePersonnelleGrics.ts +6 -6
  34. package/modeles/assurancesContratGrics.ts +6 -6
  35. package/modeles/assurancesDetailsPrimeReguliereGrics.ts +4 -4
  36. package/modeles/assurancesDonneesAssureurGrics.ts +5 -5
  37. package/modeles/assurancesEmployeGrics.ts +4 -4
  38. package/modeles/assurancesGrics.ts +6 -6
  39. package/modeles/assurancesRegimeAssuranceGrics.ts +2 -2
  40. package/modeles/assurancesRegimeBaseEmployeurGrics.ts +2 -2
  41. package/modeles/assurancesRegimeBaseGrics.ts +2 -2
  42. package/modeles/composants/csqcMenuModele.ts +18 -18
  43. package/modeles/composants/datatableColonne.ts +19 -19
  44. package/modeles/employeAdresseGrics.ts +6 -6
  45. package/modeles/employeAdressesPersonnellesGrics.ts +4 -4
  46. package/modeles/employeAffectationCorpsEmploiGrics.ts +2 -2
  47. package/modeles/employeBanquesCongeBanqueGrics.ts +2 -2
  48. package/modeles/employeBanquesCongeGrics.ts +6 -6
  49. package/modeles/employeBanquesCongeRegimeAbsenceGrics.ts +2 -2
  50. package/modeles/employeCourrielsPersonnels.ts +2 -2
  51. package/modeles/employeCourrielsProfessionnels.ts +2 -2
  52. package/modeles/employeEmploisCategorieGrics.ts +2 -2
  53. package/modeles/employeEmploisClasseGrics.ts +2 -2
  54. package/modeles/employeEmploisCorpsEmploiGrics.ts +2 -2
  55. package/modeles/employeEmploisEtatEmploiGrics.ts +2 -2
  56. package/modeles/employeEmploisGrics.ts +29 -29
  57. package/modeles/employeEmploisGroupePaieGrics.ts +2 -2
  58. package/modeles/employeEmploisLieuTravailPrincipalGrics.ts +3 -3
  59. package/modeles/employeEmploisLieuxTravailSecondairesGrics.ts +3 -3
  60. package/modeles/employeEmploisRegimeAbsenceGrics.ts +2 -2
  61. package/modeles/employeEmploisSecteurGrics.ts +2 -2
  62. package/modeles/employeEmploisStatutEngagementGrics.ts +2 -2
  63. package/modeles/employeExperienceEmploiGrics.ts +2 -2
  64. package/modeles/employeExperienceEmployeGrics.ts +5 -5
  65. package/modeles/employeExperienceExperiencesGrics.ts +4 -4
  66. package/modeles/employeExperienceExperiencesTotalesGrics.ts +7 -7
  67. package/modeles/employeExperienceGrics.ts +9 -9
  68. package/modeles/employeGrics.ts +23 -23
  69. package/modeles/employeTelephoneGrics.ts +4 -4
  70. package/modeles/employeTelephonesPersonnelsGrics.ts +3 -3
  71. package/modeles/employeTelephonesProfessionnelsGrics.ts +3 -3
  72. package/modeles/groupeCE.ts +6 -6
  73. package/modeles/groupeCEIntervalle.ts +6 -6
  74. package/modeles/historiquesAbsenceBanqueGrics.ts +2 -2
  75. package/modeles/historiquesAbsenceGrics.ts +13 -13
  76. package/modeles/historiquesAbsenceLieuTravailGrics.ts +2 -2
  77. package/modeles/historiquesAbsenceSousBanqueGrics.ts +2 -2
  78. package/modeles/motifsAbsenceBanque.ts +2 -2
  79. package/modeles/motifsAbsenceGrics.ts +9 -9
  80. package/modeles/motifsAbsenceRegimeAbsence.ts +2 -2
  81. package/modeles/motifsAbsenceSousMotifs.ts +2 -2
  82. package/modeles/motifsAbsenceTraitementBanques.ts +3 -3
  83. package/modeles/syndicat.ts +18 -18
  84. package/modeles/syndicatGroupeCe.ts +3 -3
  85. package/modeles/syndicatResponsable.ts +8 -8
  86. package/modeles/syndicatUnite.ts +3 -3
  87. package/modeles/unite.ts +15 -15
  88. package/modeles/uniteTypeEnseignement.ts +4 -4
  89. package/modeles/utilisateur.ts +8 -8
  90. package/outils/appAxios.ts +16 -16
  91. package/outils/csqcOutils.ts +6 -5
  92. package/outils/csqcRafraichisseurTokenParent.ts +20 -4
  93. package/outils/rafraichisseurToken.ts +1 -1
  94. package/package.json +13 -13
@@ -19,16 +19,15 @@
19
19
  :rules="reglesVuetify"
20
20
  :hint="afficherHint ? placeholder : ''"
21
21
  @blur="sauvegarder"
22
- @keydown.enter="sauvegarder"
23
22
  @keydown="caractereAutorises"
24
- @update:modelValue="gererChangement"
23
+ @update:model-value="gererChangement"
25
24
  @paste="gererPaste"
26
25
  >
27
26
  <template #item="{ props, item }">
28
27
  <v-list-item
29
28
  v-bind="props"
30
- :title="item.raw.nom || item.raw.code"
31
- :subtitle="item.raw.nom ? item.raw.code : undefined"
29
+ :title="item.nom || item.code"
30
+ :subtitle="item.nom ? item.code : undefined"
32
31
  />
33
32
  </template>
34
33
  </v-combobox>
@@ -37,294 +36,299 @@
37
36
  </template>
38
37
 
39
38
  <script setup lang="ts">
40
- import { ref, computed, onMounted, nextTick, watch } from 'vue'
41
- import type { VForm } from 'vuetify/components'
42
-
43
- const emit = defineEmits<{
44
- 'update:modelValue': [string | null]
45
- 'update:codeBudgetairesProp': [CodeBudgetaireItem[]]
46
- 'update:valide': [boolean]
47
- }>()
48
- type CodeBudgetaireItem = string | [string, string]
49
-
50
- const props = withDefaults(
51
- defineProps<{
52
- codeBudgetairesProp: CodeBudgetaireItem[]
53
- modelValue: string | null
54
- afficherHint?: boolean
55
- regleMessageErreur: string
56
- format?: string
57
- activerExtension?: boolean
58
- reglesSupp?: ((v: string) => true | string)[]
59
- }>(),
60
- {
61
- afficherHint: false,
62
- format: '999-9-99999-999',
63
- activerExtension: false,
64
- reglesSupp: () => [],
65
- },
66
- )
67
-
68
- const formValide = ref(false)
69
- const form = ref<VForm | null>(null)
70
- const codeBudgetaire = ref(props.modelValue ?? '')
71
- const derniereValeurSauvegardee = ref<string | null>(null)
72
- const format = props.format
73
- const activerExtension = props.activerExtension
74
-
75
- onMounted(() => {
76
- derniereValeurSauvegardee.value = codeBudgetaire.value
77
- })
78
-
79
- const itemsCombobox = computed(() => {
80
- return props.codeBudgetairesProp.map(item => {
81
- if (typeof item === 'string') {
82
- return {
83
- code: item,
84
- nom: '', // pas de nom
85
- }
86
- }
87
-
88
- const [code, nomBrut] = item
89
- const nom = (nomBrut ?? '').toString().trim()
90
-
91
- return { code, nom }
92
- })
93
- })
94
-
95
- const placeholder = computed(() => {
96
- const base = format.replace(/9/g, '0')
97
- const extension = activerExtension ? '-XXX/XXX' : ''
98
- return base + extension
99
- })
100
-
101
- const estValide = computed(() => {
102
- const val = codeBudgetaire.value?.toUpperCase().trim() || ''
103
- const base = val.slice(0, 15)
104
- const extension = val.slice(15)
105
-
106
- if (!/^\d{3}-\d{1}-\d{5}-\d{3}$/.test(base)) return false
107
-
108
- if (!activerExtension) return val.length === 15
109
-
110
- if (val.length === 15) return true
111
- if (val.length !== 22) return false
112
- if (extension.length !== 7) return false
113
-
114
- if (extension[3] !== '/') return false
115
- if (!/^[A-Z0-9/]$/i.test(extension[0]!)) return false
39
+ import { ref, computed, onMounted, nextTick, watch } from 'vue'
40
+ import type { VForm } from 'vuetify/components'
41
+
42
+ const emit = defineEmits<{
43
+ 'update:modelValue': [string | null]
44
+ 'update:codeBudgetairesProp': [CodeBudgetaireItem[]]
45
+ 'update:valide': [boolean]
46
+ }>()
47
+ type CodeBudgetaireItem = string | [string, string]
48
+
49
+ const props = withDefaults(
50
+ defineProps<{
51
+ codeBudgetairesProp: CodeBudgetaireItem[]
52
+ modelValue: string | null
53
+ afficherHint?: boolean
54
+ regleMessageErreur: string
55
+ format?: string
56
+ activerExtension?: boolean
57
+ reglesSupp?: ((v: string) => true | string)[]
58
+ }>(),
59
+ {
60
+ afficherHint: false,
61
+ format: '999-9-99999-999',
62
+ activerExtension: false,
63
+ reglesSupp: () => [],
64
+ },
65
+ )
116
66
 
117
- const alphanumAt = [1, 2, 4, 5, 6]
118
- return alphanumAt.every(i => /^[A-Z0-9]$/i.test(extension[i]!))
119
- })
67
+ const formValide = ref(false)
68
+ const form = ref<VForm | null>(null)
69
+ const codeBudgetaire = ref(props.modelValue ?? '')
70
+ const derniereValeurSauvegardee = ref<string | null>(null)
71
+ const format = props.format
72
+ const activerExtension = props.activerExtension
73
+
74
+ onMounted(() => {
75
+ derniereValeurSauvegardee.value = codeBudgetaire.value
76
+ })
77
+
78
+ const itemsCombobox = computed(() => {
79
+ return props.codeBudgetairesProp.map(item => {
80
+ if (typeof item === 'string') {
81
+ return {
82
+ code: item,
83
+ nom: '', // pas de nom
84
+ }
85
+ }
120
86
 
121
- // Règles Vuetify combinées (interne + supplémentaires)
122
- const reglesVuetify = computed(() => [
123
- // règle interne
124
- () => (estValide.value ? true : props.regleMessageErreur),
125
-
126
- // règles supplémentaires venant du parent
127
- ...props.reglesSupp.map(rule => {
128
- return () => rule(codeBudgetaire.value) // on passe la valeur formatée
129
- }),
130
- ])
131
-
132
- // Validité globale du composant (interne + toutes les règles supp)
133
- const estValideComplet = computed(() => {
134
- if (!estValide.value) return false
135
- return props.reglesSupp.every(rule => rule(codeBudgetaire.value) === true)
136
- })
87
+ const [code, nomBrut] = item
88
+ const nom = (nomBrut ?? '').toString().trim()
137
89
 
138
- const caractereAutorises = (e: KeyboardEvent) => {
139
- if (e.ctrlKey || e.metaKey) return
90
+ return { code, nom }
91
+ })
92
+ })
140
93
 
141
- const touchesSpecifiques = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Home', 'End']
142
- if (touchesSpecifiques.includes(e.key)) return
94
+ const placeholder = computed(() => {
95
+ const base = format.replace(/9/g, '0')
96
+ const extension = activerExtension ? '-XXX/XXX' : ''
97
+ return base + extension
98
+ })
143
99
 
144
- const input = e.target as HTMLInputElement
145
- let position = input.selectionStart ?? 0
100
+ const estValide = computed(() => {
101
+ const val = codeBudgetaire.value?.toUpperCase().trim() || ''
102
+ const base = val.slice(0, 15)
103
+ const extension = val.slice(15)
146
104
 
147
- // Gestion de la partie de base (15 premiers caractères)
148
- if (position < 15) {
149
- if (!/^\d$/.test(e.key)) {
150
- e.preventDefault()
151
- return
152
- }
105
+ if (!/^\d{3}-\d{1}-\d{5}-\d{3}$/.test(base)) return false
153
106
 
154
- // Insérer chiffre et auto-ajout des tirets
155
- e.preventDefault()
107
+ if (!activerExtension) return val.length === 15
156
108
 
157
- const value = codeBudgetaire.value.replace(/-/g, '')
158
- const clean = value.slice(0, 12) + e.key
109
+ if (val.length === 15) return true
110
+ if (val.length !== 22) return false
111
+ if (extension.length !== 7) return false
159
112
 
160
- let formatted = ''
161
- if (clean.length > 0) formatted += clean.slice(0, 3)
162
- if (clean.length > 3) formatted += '-' + clean.slice(3, 4)
163
- if (clean.length > 4) formatted += '-' + clean.slice(4, 9)
164
- if (clean.length > 9) formatted += '-' + clean.slice(9, 12)
113
+ if (extension[3] !== '/') return false
114
+ if (!/^[A-Z0-9/]$/i.test(extension[0]!)) return false
165
115
 
166
- codeBudgetaire.value = formatted.slice(0, 15)
116
+ const alphanumAt = [1, 2, 4, 5, 6]
117
+ return alphanumAt.every(i => /^[A-Z0-9]$/i.test(extension[i]!))
118
+ })
167
119
 
168
- nextTick(() => {
169
- const newPos = codeBudgetaire.value.length
170
- input.selectionStart = input.selectionEnd = newPos
171
- })
120
+ // Règles Vuetify combinées (interne + supplémentaires)
121
+ const reglesVuetify = computed(() => [
122
+ // règle interne
123
+ () => (estValide.value ? true : props.regleMessageErreur),
172
124
 
173
- return
174
- }
125
+ // règles supplémentaires venant du parent
126
+ ...props.reglesSupp.map(rule => {
127
+ return () => rule(codeBudgetaire.value) // on passe la valeur formatée
128
+ }),
129
+ ])
175
130
 
176
- // --- Gestion de l'extension ---
177
- if (!activerExtension || position >= 22 || codeBudgetaire.value.length >= 22) {
178
- e.preventDefault()
179
- return
180
- }
131
+ // Validité globale du composant (interne + toutes les règles supp)
132
+ const estValideComplet = computed(() => {
133
+ if (!estValide.value) return false
134
+ return props.reglesSupp.every(rule => rule(codeBudgetaire.value) === true)
135
+ })
181
136
 
182
- const extensionPos = position - 15
137
+ const caractereAutorises = (e: KeyboardEvent) => {
138
+ if (e.key === 'Enter') {
139
+ sauvegarder()
140
+ return
141
+ }
183
142
 
184
- // Règle 1 : extension[0] = alphanum ou /
185
- if (extensionPos === 0) {
186
- if (!/^[A-Z0-9/]$/i.test(e.key)) {
187
- e.preventDefault()
188
- return
189
- }
190
- return
191
- }
143
+ if (e.ctrlKey || e.metaKey) return
192
144
 
193
- // Règle 2 : extension[1,2,4,5,6] = alphanum
194
- if ([1, 2, 4, 5, 6].includes(extensionPos)) {
195
- if (!/^[A-Z0-9]$/i.test(e.key)) {
196
- e.preventDefault()
197
- return
198
- }
199
- return
200
- }
145
+ const touchesSpecifiques = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Home', 'End']
146
+ if (touchesSpecifiques.includes(e.key)) return
201
147
 
202
- // Règle 3 : slash automatique à position 3 (index 18)
203
- if (extensionPos === 3) {
204
- e.preventDefault()
205
- const before = codeBudgetaire.value.slice(0, position)
206
- const after = codeBudgetaire.value.slice(position)
207
- codeBudgetaire.value = before + '/' + after
208
- nextTick(() => {
209
- input.selectionStart = input.selectionEnd = position + 1
210
- })
211
- return
212
- }
148
+ const input = e.target as HTMLInputElement
149
+ const position = input.selectionStart ?? 0
213
150
 
214
- // Tout autre cas = bloqué
151
+ // Gestion de la partie de base (15 premiers caractères)
152
+ if (position < 15) {
153
+ if (!/^\d$/.test(e.key)) {
215
154
  e.preventDefault()
155
+ return
216
156
  }
217
157
 
218
- const formaterCodeBudgetaire = (valeur: string): string => {
219
- if (!valeur) return ''
158
+ // Insérer chiffre et auto-ajout des tirets
159
+ e.preventDefault()
220
160
 
221
- const upper = valeur.toUpperCase().replace(/[^A-Z0-9/]/g, '')
222
- const chiffres = upper.replace(/[^0-9]/g, '').slice(0, 12)
161
+ const value = codeBudgetaire.value.replace(/-/g, '')
162
+ const clean = value.slice(0, 12) + e.key
223
163
 
224
- let base = ''
225
- if (chiffres.length > 0) base += chiffres.slice(0, 3)
226
- if (chiffres.length > 3) base += '-' + chiffres.slice(3, 4)
227
- if (chiffres.length > 4) base += '-' + chiffres.slice(4, 9)
228
- if (chiffres.length > 9) base += '-' + chiffres.slice(9, 12)
164
+ let formatted = ''
165
+ if (clean.length > 0) formatted += clean.slice(0, 3)
166
+ if (clean.length > 3) formatted += '-' + clean.slice(3, 4)
167
+ if (clean.length > 4) formatted += '-' + clean.slice(4, 9)
168
+ if (clean.length > 9) formatted += '-' + clean.slice(9, 12)
229
169
 
230
- if (!activerExtension || base.length < 15) return base
170
+ codeBudgetaire.value = formatted.slice(0, 15)
231
171
 
232
- const reste = upper.slice(chiffres.length).replace(/[^A-Z0-9/]/gi, '')
233
-
234
- // Extraire les 7 premiers caractères restants pour l’extension
235
- let ext = reste.slice(0, 7).split('')
236
-
237
- // Ne garder que le premier slash s’il est à l’index 0 ou 3
238
- ext = ext.filter((c, i) => {
239
- if (c !== '/') return true
240
- return i === 0 || i === 3
241
- })
242
-
243
- // Enlever les slash non autorisés
244
- ext = ext.map((c, i) => {
245
- if (c === '/' && i !== 0 && i !== 3) return ''
246
- if (c !== '/' && !/^[A-Z0-9]$/i.test(c)) return ''
247
- return c
248
- })
172
+ nextTick(() => {
173
+ const newPos = codeBudgetaire.value.length
174
+ input.selectionStart = input.selectionEnd = newPos
175
+ })
249
176
 
250
- // Forcer le slash à la 4e position
251
- if (ext.length > 3) {
252
- ext[3] = '/'
253
- }
177
+ return
178
+ }
254
179
 
255
- // Réduire à 7 caractères
256
- ext = ext.slice(0, 7)
180
+ // --- Gestion de l'extension ---
181
+ if (!activerExtension || position >= 22 || codeBudgetaire.value.length >= 22) {
182
+ e.preventDefault()
183
+ return
184
+ }
257
185
 
258
- return (base + ext.join('')).slice(0, 22)
259
- }
186
+ const extensionPos = position - 15
260
187
 
261
- const gererPaste = (e: ClipboardEvent) => {
188
+ // Règle 1 : extension[0] = alphanum ou /
189
+ if (extensionPos === 0) {
190
+ if (!/^[A-Z0-9/]$/i.test(e.key)) {
262
191
  e.preventDefault()
263
- const clipboardData = e.clipboardData
264
- if (!clipboardData) return
265
- let pasted = clipboardData.getData('text') || ''
266
- codeBudgetaire.value = formaterCodeBudgetaire(pasted)
267
-
268
- setTimeout(() => {
269
- const input = e.target as HTMLInputElement
270
- input.selectionStart = input.selectionEnd = codeBudgetaire.value.length
271
- }, 0)
272
- }
273
-
274
- const sauvegarder = () => {
275
- codeBudgetaire.value = formaterCodeBudgetaire(codeBudgetaire.value)
276
-
277
- if (!estValideComplet.value) return
278
- if (codeBudgetaire.value === derniereValeurSauvegardee.value) return
279
-
280
- const codeNormalise = codeBudgetaire.value.trim().toUpperCase()
281
-
282
- const existe = props.codeBudgetairesProp.some(item => {
283
- const code = typeof item === 'string' ? item : item[0]
284
- return code.trim().toUpperCase() === codeNormalise
285
- })
286
-
287
- if (!existe) {
288
- const nouvelleListe = [...props.codeBudgetairesProp, codeBudgetaire.value]
289
- emit('update:codeBudgetairesProp', nouvelleListe)
290
- }
291
-
292
- derniereValeurSauvegardee.value = codeBudgetaire.value
293
- emit('update:modelValue', codeBudgetaire.value)
192
+ return
294
193
  }
194
+ return
195
+ }
295
196
 
296
- const extraireCode = (val: unknown): string => {
297
- if (val == null) return ''
298
- if (typeof val === 'string') return val
299
-
300
- // Cas où Vuetify enverrait { code, label }
301
- if (typeof val === 'object' && 'code' in (val as Record<string, unknown>)) {
302
- const obj = val as { code?: unknown }
303
- return typeof obj.code === 'string' ? obj.code : String(obj.code ?? '')
304
- }
305
-
306
- return String(val)
197
+ // Règle 2 : extension[1,2,4,5,6] = alphanum
198
+ if ([1, 2, 4, 5, 6].includes(extensionPos)) {
199
+ if (!/^[A-Z0-9]$/i.test(e.key)) {
200
+ e.preventDefault()
201
+ return
307
202
  }
203
+ return
204
+ }
205
+
206
+ // Règle 3 : slash automatique à position 3 (index 18)
207
+ if (extensionPos === 3) {
208
+ e.preventDefault()
209
+ const before = codeBudgetaire.value.slice(0, position)
210
+ const after = codeBudgetaire.value.slice(position)
211
+ codeBudgetaire.value = before + '/' + after
212
+ nextTick(() => {
213
+ input.selectionStart = input.selectionEnd = position + 1
214
+ })
215
+ return
216
+ }
308
217
 
309
- const gererChangement = (val: unknown) => {
310
- const code = extraireCode(val)
311
- codeBudgetaire.value = formaterCodeBudgetaire(code)
312
-
313
- const valeurFormatee = codeBudgetaire.value.trim().toUpperCase()
218
+ // Tout autre cas = bloqué
219
+ e.preventDefault()
220
+ }
314
221
 
315
- const estDansListe = props.codeBudgetairesProp.some(item => {
316
- const codeItem = typeof item === 'string' ? item : item[0]
317
- return codeItem.trim().toUpperCase() === valeurFormatee
318
- })
222
+ const formaterCodeBudgetaire = (valeur: string): string => {
223
+ if (!valeur) return ''
319
224
 
320
- if (
321
- estDansListe &&
322
- valeurFormatee !== (derniereValeurSauvegardee.value ?? '').toUpperCase() &&
323
- estValideComplet.value
324
- ) {
325
- sauvegarder()
326
- }
327
- }
225
+ const upper = valeur.toUpperCase().replace(/[^A-Z0-9/]/g, '')
226
+ const chiffres = upper.replace(/[^0-9]/g, '').slice(0, 12)
227
+
228
+ let base = ''
229
+ if (chiffres.length > 0) base += chiffres.slice(0, 3)
230
+ if (chiffres.length > 3) base += '-' + chiffres.slice(3, 4)
231
+ if (chiffres.length > 4) base += '-' + chiffres.slice(4, 9)
232
+ if (chiffres.length > 9) base += '-' + chiffres.slice(9, 12)
233
+
234
+ if (!activerExtension || base.length < 15) return base
235
+
236
+ const reste = upper.slice(chiffres.length).replace(/[^A-Z0-9/]/gi, '')
237
+
238
+ // Extraire les 7 premiers caractères restants pour l’extension
239
+ let ext = reste.slice(0, 7).split('')
240
+
241
+ // Ne garder que le premier slash s’il est à l’index 0 ou 3
242
+ ext = ext.filter((c, i) => {
243
+ if (c !== '/') return true
244
+ return i === 0 || i === 3
245
+ })
246
+
247
+ // Enlever les slash non autorisés
248
+ ext = ext.map((c, i) => {
249
+ if (c === '/' && i !== 0 && i !== 3) return ''
250
+ if (c !== '/' && !/^[A-Z0-9]$/i.test(c)) return ''
251
+ return c
252
+ })
253
+
254
+ // Forcer le slash à la 4e position
255
+ if (ext.length > 3) {
256
+ ext[3] = '/'
257
+ }
258
+
259
+ // Réduire à 7 caractères
260
+ ext = ext.slice(0, 7)
261
+
262
+ return (base + ext.join('')).slice(0, 22)
263
+ }
264
+
265
+ const gererPaste = (e: ClipboardEvent) => {
266
+ e.preventDefault()
267
+ const clipboardData = e.clipboardData
268
+ if (!clipboardData) return
269
+ const pasted = clipboardData.getData('text') || ''
270
+ codeBudgetaire.value = formaterCodeBudgetaire(pasted)
271
+
272
+ setTimeout(() => {
273
+ const input = e.target as HTMLInputElement
274
+ input.selectionStart = input.selectionEnd = codeBudgetaire.value.length
275
+ }, 0)
276
+ }
277
+
278
+ const sauvegarder = () => {
279
+ codeBudgetaire.value = formaterCodeBudgetaire(codeBudgetaire.value)
280
+
281
+ if (!estValideComplet.value) return
282
+ if (codeBudgetaire.value === derniereValeurSauvegardee.value) return
283
+
284
+ const codeNormalise = codeBudgetaire.value.trim().toUpperCase()
285
+
286
+ const existe = props.codeBudgetairesProp.some(item => {
287
+ const code = typeof item === 'string' ? item : item[0]
288
+ return code.trim().toUpperCase() === codeNormalise
289
+ })
290
+
291
+ if (!existe) {
292
+ const nouvelleListe = [...props.codeBudgetairesProp, codeBudgetaire.value]
293
+ emit('update:codeBudgetairesProp', nouvelleListe)
294
+ }
295
+
296
+ derniereValeurSauvegardee.value = codeBudgetaire.value
297
+ emit('update:modelValue', codeBudgetaire.value)
298
+ }
299
+
300
+ const extraireCode = (val: unknown): string => {
301
+ if (val == null) return ''
302
+ if (typeof val === 'string') return val
303
+
304
+ // Cas où Vuetify enverrait { code, label }
305
+ if (typeof val === 'object' && 'code' in (val as Record<string, unknown>)) {
306
+ const obj = val as { code?: unknown }
307
+ return typeof obj.code === 'string' ? obj.code : String(obj.code ?? '')
308
+ }
309
+
310
+ return String(val)
311
+ }
312
+
313
+ const gererChangement = (val: unknown) => {
314
+ const code = extraireCode(val)
315
+ codeBudgetaire.value = formaterCodeBudgetaire(code)
316
+
317
+ const valeurFormatee = codeBudgetaire.value.trim().toUpperCase()
318
+
319
+ const estDansListe = props.codeBudgetairesProp.some(item => {
320
+ const codeItem = typeof item === 'string' ? item : item[0]
321
+ return codeItem.trim().toUpperCase() === valeurFormatee
322
+ })
323
+
324
+ if (
325
+ estDansListe &&
326
+ valeurFormatee !== (derniereValeurSauvegardee.value ?? '').toUpperCase() &&
327
+ estValideComplet.value
328
+ ) {
329
+ sauvegarder()
330
+ }
331
+ }
328
332
 
329
333
  watch(
330
334
  () => codeBudgetaire.value,