c25c33116d
Auto Tag Develop / tag (push) Successful in 8s
Empilée sur ERP-143 (#105). ## Périmètre ERP-144 Onglet **Comptabilité** de l'écran `/providers/new` — gated par permission + blocs RIB conditionnels. - Champs (`Malio*`) : SIREN / Numéro de compte / Mode de TVA (`/api/tva_modes`) / N° de TVA / Délai (`/api/payment_delays`) / Type de règlement (`/api/payment_types`) / Banque (`/api/banks`). - **RG-3.07** : Banque visible **et** obligatoire **seulement si** Type = `VIREMENT` (affichage conditionnel + payload `bank` forcé à null sinon). - **RG-3.08** : blocs RIB (Libellé/BIC/IBAN) affichés et requis si Type = `LCR` ; « + RIB » gated (dernier RIB complet) / Supprimer (modal). À la validation, **POST des RIB AVANT** le PATCH des scalaires (le back valide RG-3.08 sur le PATCH). - **Gating** : onglet présent uniquement si `technique.providers.accounting.view` ; **éditable** uniquement si `.manage` (sinon lecture seule). Masqué pour Bureau/Commerciale. - « Valider » → PATCH `/api/providers/{id}` (groupe `provider:write:accounting`) + sous-ressource RIBs (`/providers/{id}/ribs` + `/provider_ribs/{id}`). Erreurs 422 inline (scalaires) et par ligne (RIB). - `useProviderReferentials.loadAccounting()` (chargé seulement si l'onglet est accessible). Helpers purs `utils/forms/providerAccounting.ts`. - i18n `technique.providers.form.accounting` + `confirmDelete.rib`. > NB : les placeholders **Rapports / Échanges** relèvent des écrans Consultation/Modification (ERP-145) — le flux de **création** ne porte que 3 onglets (Contact/Adresse/Comptabilité), conformément à la spec. ## Conformité - `useApi()` only ; `Malio*` only ; pas de masque email ; aucun texte FR en dur ; pas d'import inter-module (helpers ré-implémentés côté Technique, règle ABSOLUE n°1). ## Vérifications - Vitest : 454/454 (18 nouveaux : helpers compta RG-3.07/3.08, workflow VIREMENT/LCR, ordre RIB→scalaires, 422 inline + par ligne, lecture seule sans manage). - ESLint : OK. - `nuxi typecheck` : 0 erreur sur les fichiers source du ticket. - Golden path navigateur : page compile, onglet Comptabilité visible (gating accounting.view OK pour admin). Contenu de l'onglet gaté derrière le déverrouillage des 3 onglets (multiselect `Malio` non pilotable en a11y) — couvert par les tests unitaires + typecheck. Reviewed-on: #106 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
559 lines
22 KiB
TypeScript
559 lines
22 KiB
TypeScript
import { computed, reactive, ref, type Ref } from 'vue'
|
|
import { useFormErrors } from '~/shared/composables/useFormErrors'
|
|
import { mapViolationsToRecord } from '~/shared/utils/api'
|
|
import {
|
|
emptyProviderAccounting,
|
|
emptyProviderAddress,
|
|
emptyProviderContact,
|
|
emptyProviderMain,
|
|
emptyProviderRib,
|
|
type ProviderAccountingDraft,
|
|
type ProviderAddressFormDraft,
|
|
type ProviderAddressResponse,
|
|
type ProviderContactFormDraft,
|
|
type ProviderContactResponse,
|
|
type ProviderMainDraft,
|
|
type ProviderMainResponse,
|
|
type ProviderRibFormDraft,
|
|
type ProviderRibResponse,
|
|
} from '~/modules/technique/types/providerForm'
|
|
import {
|
|
buildProviderContactPayload,
|
|
isProviderContactBlank,
|
|
} from '~/modules/technique/utils/forms/providerContact'
|
|
import {
|
|
buildProviderAddressPayload,
|
|
isProviderAddressValid,
|
|
} from '~/modules/technique/utils/forms/providerAddress'
|
|
import {
|
|
buildProviderAccountingPayload,
|
|
buildProviderRibPayload,
|
|
isRibBlank,
|
|
isRibComplete,
|
|
} from '~/modules/technique/utils/forms/providerAccounting'
|
|
|
|
/**
|
|
* Workflow de l'ecran « Ajouter un prestataire » (M3 Technique, ERP-141) —
|
|
* miroir conceptuel de la logique de creation fournisseur (M2), extraite ici en
|
|
* composable.
|
|
*
|
|
* Particularites M3 (cf. spec-front § « Ecran Ajouter ») :
|
|
* - PAS d'onglet « Information » : le formulaire principal est minimal (Nom +
|
|
* Categorie + Site).
|
|
* - Selecteur de site SUR le formulaire principal (RG-3.03, relation directe
|
|
* `provider.sites`).
|
|
* - Creation incrementale par onglets (Contact · Adresse · Comptabilite) :
|
|
* POST principal puis PATCH partiels par groupe de serialisation
|
|
* (`provider:write:*`, mode strict — spec-back § 2.10). Le contenu des onglets
|
|
* arrive aux tickets ERP-142 → 144 ; ce composable pose le POST principal et
|
|
* l'orchestration des onglets.
|
|
*
|
|
* Etat 100 % local a l'instance (refs/reactive) — aucune persistance URL.
|
|
*/
|
|
|
|
/**
|
|
* Cles des onglets du FLUX DE CREATION. Pas d'onglet « Information » au M3 ;
|
|
* « Rapports » / « Echanges » n'apparaissent qu'en consultation/modification.
|
|
* L'onglet « Comptabilite » n'est present que pour les roles qui peuvent le voir
|
|
* (`technique.providers.accounting.view` — Admin, Compta).
|
|
*/
|
|
export function buildProviderCreateTabKeys(canAccountingView: boolean): string[] {
|
|
return canAccountingView
|
|
? ['contact', 'address', 'accounting']
|
|
: ['contact', 'address']
|
|
}
|
|
|
|
export function useProviderForm() {
|
|
const api = useApi()
|
|
const { t } = useI18n()
|
|
const toast = useToast()
|
|
const { can } = usePermissions()
|
|
|
|
// Erreurs de validation par champ (ERP-101) du formulaire principal.
|
|
const mainErrors = useFormErrors()
|
|
|
|
// ── Etat du prestataire cree ────────────────────────────────────────────
|
|
const providerId = ref<number | null>(null)
|
|
const mainLocked = ref(false)
|
|
const mainSubmitting = ref(false)
|
|
const tabSubmitting = ref(false)
|
|
|
|
// ── Formulaire principal ──────────────────────────────────────────────────
|
|
const main = reactive<ProviderMainDraft>(emptyProviderMain())
|
|
|
|
// ── Onglets : ordre + gating progressif ───────────────────────────────────
|
|
const canAccountingView = computed(() => can('technique.providers.accounting.view'))
|
|
const canAccountingManage = computed(() => can('technique.providers.accounting.manage'))
|
|
const tabKeys = computed(() => buildProviderCreateTabKeys(canAccountingView.value))
|
|
|
|
// Index du dernier onglet deverrouille (-1 tant que le prestataire n'est pas cree).
|
|
const unlockedIndex = ref(-1)
|
|
const activeTab = ref<string>('contact')
|
|
// Onglets valides (passent en lecture seule).
|
|
const validated = reactive<Record<string, boolean>>({})
|
|
|
|
function isValidated(key: string): boolean {
|
|
return validated[key] === true
|
|
}
|
|
|
|
function tabIndex(key: string): number {
|
|
return tabKeys.value.indexOf(key)
|
|
}
|
|
|
|
/**
|
|
* Validation FRONT du formulaire principal : RG-3.03 (>= 1 site) et RG-3.09
|
|
* (>= 1 categorie). Pose les erreurs inline et retourne false si invalide.
|
|
* Le back reste la couche autoritaire (ERP-101) ; ce pre-check evite un
|
|
* aller-retour inutile et porte la garantie RG-3.03 cote front.
|
|
*/
|
|
function validateMainFront(): boolean {
|
|
let valid = true
|
|
if (main.siteIris.length === 0) {
|
|
mainErrors.setError('sites', t('technique.providers.form.errors.siteRequired'))
|
|
valid = false
|
|
}
|
|
if (main.categoryIris.length === 0) {
|
|
mainErrors.setError('categories', t('technique.providers.form.errors.categoryRequired'))
|
|
valid = false
|
|
}
|
|
return valid
|
|
}
|
|
|
|
/**
|
|
* Payload du POST principal (groupe `provider:write:main`). `companyName` est
|
|
* omis s'il est vide afin que la 422 porte la violation NotBlank (RG-3.11) sur
|
|
* le champ plutot qu'une erreur de type. Les relations M2M partent en IRI.
|
|
*/
|
|
function buildMainPayload(): Record<string, unknown> {
|
|
const payload: Record<string, unknown> = {
|
|
categories: [...main.categoryIris],
|
|
sites: [...main.siteIris],
|
|
}
|
|
if (main.companyName?.trim()) {
|
|
payload.companyName = main.companyName
|
|
}
|
|
return payload
|
|
}
|
|
|
|
/**
|
|
* POST /providers (groupe `provider:write:main`). Pre-check front RG-3.03/3.09,
|
|
* puis creation. Au succes : verrouille le bloc principal, deverrouille le 1er
|
|
* onglet et bascule sur « Contact ». Retourne true si cree, false sinon.
|
|
*/
|
|
async function submitMain(): Promise<boolean> {
|
|
if (mainSubmitting.value) return false
|
|
mainErrors.clearErrors()
|
|
if (!validateMainFront()) return false
|
|
|
|
mainSubmitting.value = true
|
|
try {
|
|
const created = await api.post<ProviderMainResponse>('/providers', buildMainPayload(), {
|
|
headers: { Accept: 'application/ld+json' },
|
|
toast: false,
|
|
})
|
|
|
|
providerId.value = created.id
|
|
// Reaffiche la valeur normalisee renvoyee par le serveur (UPPERCASE, RG-3.11).
|
|
main.companyName = created.companyName ?? main.companyName
|
|
|
|
mainLocked.value = true
|
|
unlockedIndex.value = 0
|
|
activeTab.value = tabKeys.value[0] ?? 'contact'
|
|
toast.success({ title: t('technique.providers.toast.createSuccess') })
|
|
return true
|
|
}
|
|
catch (error) {
|
|
// 409 = doublon de nom (RG-3.10) → erreur inline dediee + toast ;
|
|
// 422 → mapping inline par champ ; autre → toast de fallback (ERP-101).
|
|
const status = (error as { response?: { status?: number } })?.response?.status
|
|
if (status === 409) {
|
|
const message = t('technique.providers.form.duplicateCompany')
|
|
mainErrors.setError('companyName', message)
|
|
toast.error({ title: t('technique.providers.toast.error'), message })
|
|
}
|
|
else {
|
|
mainErrors.handleApiError(error, { fallbackMessage: t('technique.providers.toast.error') })
|
|
}
|
|
return false
|
|
}
|
|
finally {
|
|
mainSubmitting.value = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PATCH partiel du prestataire (mode strict : un seul groupe de serialisation
|
|
* par appel — spec-back § 2.10). Sert l'onglet Comptabilite a champs scalaires
|
|
* (ERP-144) ; les onglets Contact/Adresse passent par leurs sous-ressources
|
|
* (POST/PATCH par ligne, ERP-142/143). No-op tant que le prestataire n'existe pas.
|
|
*/
|
|
async function patchProvider(payload: Record<string, unknown>): Promise<void> {
|
|
if (providerId.value === null) return
|
|
await api.patch(`/providers/${providerId.value}`, payload, { toast: false })
|
|
}
|
|
|
|
/**
|
|
* Marque un onglet valide (passe en lecture seule), deverrouille et avance a
|
|
* l'onglet suivant. Retourne true si c'etait le dernier onglet du flux
|
|
* (creation terminee), false sinon.
|
|
*/
|
|
function completeTab(key: string): boolean {
|
|
validated[key] = true
|
|
const index = tabIndex(key)
|
|
const next = tabKeys.value[index + 1]
|
|
if (next === undefined) {
|
|
return true
|
|
}
|
|
unlockedIndex.value = Math.max(unlockedIndex.value, index + 1)
|
|
activeTab.value = next
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* Soumet TOUS les blocs d'une collection en collectant les erreurs PAR INDEX :
|
|
* on n'arrete pas au premier bloc en echec (decision ERP-101). Reinitialise la
|
|
* cible, tente chaque ligne via `saveRow`, mappe les 422 inline ou delegue le
|
|
* fallback a `onUnmappedError`. `shouldSkip` ignore les amorces vides. Retourne
|
|
* true si au moins un bloc a echoue. Miroir de `useSupplierFormErrors.submitRows`.
|
|
*/
|
|
async function submitRows<T>(
|
|
rows: T[],
|
|
target: Ref<Record<string, string>[]>,
|
|
saveRow: (row: T, index: number) => Promise<void>,
|
|
onUnmappedError: (error: unknown, index: number) => void,
|
|
shouldSkip?: (row: T, index: number) => boolean,
|
|
): Promise<boolean> {
|
|
target.value = []
|
|
let hasError = false
|
|
for (let index = 0; index < rows.length; index++) {
|
|
const row = rows[index] as T
|
|
if (shouldSkip?.(row, index)) {
|
|
continue
|
|
}
|
|
try {
|
|
await saveRow(row, index)
|
|
}
|
|
catch (error) {
|
|
const response = (error as { response?: { status?: number, _data?: unknown } })?.response
|
|
const mapped = response?.status === 422 ? mapViolationsToRecord(response._data) : {}
|
|
if (Object.keys(mapped).length > 0) {
|
|
target.value[index] = mapped
|
|
}
|
|
else {
|
|
onUnmappedError(error, index)
|
|
}
|
|
hasError = true
|
|
}
|
|
}
|
|
return hasError
|
|
}
|
|
|
|
// ── Onglet Contact (ERP-142) ──────────────────────────────────────────────
|
|
const contacts = ref<ProviderContactFormDraft[]>([emptyProviderContact()])
|
|
// Erreurs 422 par ligne (alignees sur l'index du v-for), peuplees par submitRows.
|
|
const contactErrors = ref<Record<string, string>[]>([])
|
|
|
|
// « + Nouveau contact » desactive tant que le dernier bloc est vide (RG-3.04).
|
|
const canAddContact = computed(() => {
|
|
const last = contacts.value[contacts.value.length - 1]
|
|
return last !== undefined && !isProviderContactBlank(last)
|
|
})
|
|
|
|
function addContact(): void {
|
|
if (canAddContact.value) {
|
|
contacts.value.push(emptyProviderContact())
|
|
}
|
|
}
|
|
|
|
function removeContact(index: number): void {
|
|
contacts.value.splice(index, 1)
|
|
contactErrors.value.splice(index, 1)
|
|
}
|
|
|
|
/**
|
|
* Valide l'onglet Contact : POST des nouveaux contacts sur
|
|
* /providers/{id}/contacts, PATCH des existants sur /provider_contacts/{id}
|
|
* (sous-ressource, groupe provider:write:contacts). RG-3.12 : au moins un bloc
|
|
* valide. Si l'onglet ne contient QUE des amorces vides, on les soumet pour
|
|
* declencher la 422 RG-3.04 inline (sur `firstName`) plutot que de finaliser un
|
|
* onglet vide. Retourne true si l'onglet a ete valide (avance/termine).
|
|
*/
|
|
async function submitContacts(onError: (error: unknown) => void): Promise<boolean> {
|
|
if (providerId.value === null || tabSubmitting.value) {
|
|
return false
|
|
}
|
|
tabSubmitting.value = true
|
|
try {
|
|
const hasSubmittable = contacts.value.some(c => c.id !== null || !isProviderContactBlank(c))
|
|
const hasError = await submitRows(
|
|
contacts.value,
|
|
contactErrors,
|
|
async (contact) => {
|
|
const body = buildProviderContactPayload(contact)
|
|
if (contact.id === null) {
|
|
const created = await api.post<ProviderContactResponse>(
|
|
`/providers/${providerId.value}/contacts`,
|
|
body,
|
|
{ headers: { Accept: 'application/ld+json' }, toast: false },
|
|
)
|
|
contact.id = created.id
|
|
contact.iri = created['@id'] ?? null
|
|
}
|
|
else {
|
|
await api.patch(`/provider_contacts/${contact.id}`, body, { toast: false })
|
|
}
|
|
},
|
|
onError,
|
|
contact => hasSubmittable && contact.id === null && isProviderContactBlank(contact),
|
|
)
|
|
if (hasError) {
|
|
return false
|
|
}
|
|
completeTab('contact')
|
|
return true
|
|
}
|
|
finally {
|
|
tabSubmitting.value = false
|
|
}
|
|
}
|
|
|
|
// ── Onglet Adresse (ERP-143) ──────────────────────────────────────────────
|
|
const addresses = ref<ProviderAddressFormDraft[]>([emptyProviderAddress()])
|
|
// Erreurs 422 par ligne (alignees sur l'index du v-for).
|
|
const addressErrors = ref<Record<string, string>[]>([])
|
|
|
|
// « + Nouvelle adresse » desactive tant que la derniere adresse n'a pas
|
|
// au moins un site ET une categorie (RG-3.05 / RG-3.09).
|
|
const canAddAddress = computed(() => {
|
|
const last = addresses.value[addresses.value.length - 1]
|
|
return last !== undefined && isProviderAddressValid(last)
|
|
})
|
|
|
|
function addAddress(): void {
|
|
if (canAddAddress.value) {
|
|
addresses.value.push(emptyProviderAddress())
|
|
}
|
|
}
|
|
|
|
function removeAddress(index: number): void {
|
|
addresses.value.splice(index, 1)
|
|
addressErrors.value.splice(index, 1)
|
|
}
|
|
|
|
/**
|
|
* Valide l'onglet Adresse : POST des nouvelles adresses sur
|
|
* /providers/{id}/addresses, PATCH des existantes sur /provider_addresses/{id}
|
|
* (sous-ressource, groupe provider:write:addresses). Erreurs 422 collectees par
|
|
* ligne. Retourne true si l'onglet a ete valide (avance/termine).
|
|
*/
|
|
async function submitAddresses(onError: (error: unknown) => void): Promise<boolean> {
|
|
if (providerId.value === null || tabSubmitting.value) {
|
|
return false
|
|
}
|
|
tabSubmitting.value = true
|
|
try {
|
|
const hasError = await submitRows(
|
|
addresses.value,
|
|
addressErrors,
|
|
async (address) => {
|
|
const body = buildProviderAddressPayload(address)
|
|
if (address.id === null) {
|
|
const created = await api.post<ProviderAddressResponse>(
|
|
`/providers/${providerId.value}/addresses`,
|
|
body,
|
|
{ headers: { Accept: 'application/ld+json' }, toast: false },
|
|
)
|
|
address.id = created.id
|
|
}
|
|
else {
|
|
await api.patch(`/provider_addresses/${address.id}`, body, { toast: false })
|
|
}
|
|
},
|
|
onError,
|
|
)
|
|
if (hasError) {
|
|
return false
|
|
}
|
|
completeTab('address')
|
|
return true
|
|
}
|
|
finally {
|
|
tabSubmitting.value = false
|
|
}
|
|
}
|
|
|
|
// ── Onglet Comptabilite (ERP-144) ─────────────────────────────────────────
|
|
const accounting = reactive<ProviderAccountingDraft>(emptyProviderAccounting())
|
|
const ribs = ref<ProviderRibFormDraft[]>([])
|
|
const accountingErrors = useFormErrors()
|
|
// Erreurs 422 par ligne de RIB (alignees sur l'index du v-for).
|
|
const ribErrors = ref<Record<string, string>[]>([])
|
|
|
|
// L'onglet est editable seulement avec accounting.manage (sinon lecture seule).
|
|
const accountingReadonly = computed(() => isValidated('accounting') || !canAccountingManage.value)
|
|
|
|
/**
|
|
* Met a jour le type de reglement (IRI) en propageant ses RG inter-champs :
|
|
* - hors VIREMENT (RG-3.07) : on vide la banque (sans objet) ;
|
|
* - LCR (RG-3.08) : on garantit au moins un bloc RIB visible ; hors LCR, on
|
|
* purge les erreurs de RIB (les blocs sont conserves mais non persistes).
|
|
* `isBankRequired` / `isRibRequired` sont calcules par l'appelant (page) a
|
|
* partir du code resolu via les referentiels.
|
|
*/
|
|
function setPaymentType(iri: string | null, isBankRequired: boolean, isRibRequired: boolean): void {
|
|
accounting.paymentTypeIri = iri
|
|
if (!isBankRequired) {
|
|
accounting.bankIri = null
|
|
}
|
|
if (isRibRequired) {
|
|
if (ribs.value.length === 0) {
|
|
ribs.value.push(emptyProviderRib())
|
|
}
|
|
}
|
|
else {
|
|
ribErrors.value = []
|
|
}
|
|
}
|
|
|
|
// « + RIB » desactive tant que le dernier bloc RIB n'est pas complet (RG-3.08).
|
|
const canAddRib = computed(() => {
|
|
const last = ribs.value[ribs.value.length - 1]
|
|
return last !== undefined && isRibComplete(last)
|
|
})
|
|
|
|
function addRib(): void {
|
|
if (canAddRib.value) {
|
|
ribs.value.push(emptyProviderRib())
|
|
}
|
|
}
|
|
|
|
function removeRib(index: number): void {
|
|
ribs.value.splice(index, 1)
|
|
ribErrors.value.splice(index, 1)
|
|
// Garde au moins un bloc RIB visible (sous LCR).
|
|
if (ribs.value.length === 0) {
|
|
ribs.value.push(emptyProviderRib())
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Valide l'onglet Comptabilite : (1) sous LCR, POST/PATCH des RIB d'abord
|
|
* (le back valide RG-3.08 sur le PATCH scalaires, les RIB doivent donc exister
|
|
* AVANT) ; (2) PATCH des scalaires comptables (groupe provider:write:accounting,
|
|
* banque envoyee seulement si VIREMENT — RG-3.07). Erreurs RIB par ligne ;
|
|
* erreurs scalaires inline (bank/paymentType). Retourne true si l'onglet a ete
|
|
* valide.
|
|
*/
|
|
async function submitAccounting(
|
|
isBankRequired: boolean,
|
|
isRibRequired: boolean,
|
|
onRibError: (error: unknown) => void,
|
|
): Promise<boolean> {
|
|
if (providerId.value === null || tabSubmitting.value) {
|
|
return false
|
|
}
|
|
tabSubmitting.value = true
|
|
accountingErrors.clearErrors()
|
|
try {
|
|
// 1) RIB d'abord, uniquement sous LCR. Une amorce vide neuve est sautee
|
|
// s'il reste un autre RIB soumettable ; sinon (LCR sans aucun RIB rempli)
|
|
// on la soumet pour declencher la 422 NotBlank inline.
|
|
if (isRibRequired) {
|
|
const hasSubmittableRib = ribs.value.some(r => r.id !== null || !isRibBlank(r))
|
|
const ribHasError = await submitRows(
|
|
ribs.value,
|
|
ribErrors,
|
|
async (rib) => {
|
|
const body = buildProviderRibPayload(rib)
|
|
if (rib.id === null) {
|
|
const created = await api.post<ProviderRibResponse>(
|
|
`/providers/${providerId.value}/ribs`,
|
|
body,
|
|
{ headers: { Accept: 'application/ld+json' }, toast: false },
|
|
)
|
|
rib.id = created.id
|
|
}
|
|
else {
|
|
await api.patch(`/provider_ribs/${rib.id}`, body, { toast: false })
|
|
}
|
|
},
|
|
onRibError,
|
|
rib => hasSubmittableRib && rib.id === null && isRibBlank(rib),
|
|
)
|
|
if (ribHasError) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 2) PATCH des scalaires comptables (erreurs inline sur leurs champs).
|
|
try {
|
|
await api.patch(
|
|
`/providers/${providerId.value}`,
|
|
buildProviderAccountingPayload(accounting, isBankRequired),
|
|
{ toast: false },
|
|
)
|
|
}
|
|
catch (error) {
|
|
accountingErrors.handleApiError(error, { fallbackMessage: t('technique.providers.toast.error') })
|
|
return false
|
|
}
|
|
|
|
completeTab('accounting')
|
|
return true
|
|
}
|
|
finally {
|
|
tabSubmitting.value = false
|
|
}
|
|
}
|
|
|
|
return {
|
|
// etat
|
|
main,
|
|
providerId,
|
|
mainLocked,
|
|
mainSubmitting,
|
|
tabSubmitting,
|
|
mainErrors,
|
|
// onglets
|
|
canAccountingView,
|
|
canAccountingManage,
|
|
tabKeys,
|
|
activeTab,
|
|
unlockedIndex,
|
|
validated,
|
|
isValidated,
|
|
// contacts
|
|
contacts,
|
|
contactErrors,
|
|
canAddContact,
|
|
addContact,
|
|
removeContact,
|
|
submitContacts,
|
|
// adresses
|
|
addresses,
|
|
addressErrors,
|
|
canAddAddress,
|
|
addAddress,
|
|
removeAddress,
|
|
submitAddresses,
|
|
// comptabilite
|
|
accounting,
|
|
ribs,
|
|
accountingErrors,
|
|
ribErrors,
|
|
accountingReadonly,
|
|
setPaymentType,
|
|
canAddRib,
|
|
addRib,
|
|
removeRib,
|
|
submitAccounting,
|
|
// actions
|
|
validateMainFront,
|
|
buildMainPayload,
|
|
submitMain,
|
|
patchProvider,
|
|
completeTab,
|
|
submitRows,
|
|
}
|
|
}
|