fix(commercial) : rendre obligatoires les 6 champs comptables — 422 back par champ + bouton « Valider » grisé tant que l'onglet est incomplet (ERP-110)

spec-front marquait SIREN / N° compte / Mode TVA / N° TVA / Délai / Type de règlement obligatoires, mais ni le back (colonnes nullable, aucun NotBlank) ni le front (canValidateAccounting ne gardait que bank/RIB) ne l'imposaient : on validait un onglet vide.

- Back : ClientAccountingCompletenessValidator (calqué sur RG-1.04) invoqué par le ClientProcessor quand les 6 champs sont présents dans le payload (validation d'onglet) → 422 par propertyPath. PATCH partiel ciblé non impacté.
- Front : helper hasAllRequiredAccountingFields + canValidateAccounting étendu dans new.vue / edit.vue (cohérent avec les onglets Contact/Adresse).
- Spec-back : RG-1.30 documente la règle et résout l'incohérence spec-front/spec-back.
This commit is contained in:
2026-06-04 14:45:14 +02:00
parent ad32d8147d
commit c2282fdac5
8 changed files with 263 additions and 0 deletions
@@ -4,6 +4,7 @@ import {
buildClientFormTabKeys,
canSelectDeliveryOrBilling,
canSelectProspect,
hasAllRequiredAccountingFields,
hasAtLeastOneValidContact,
isBankRequiredForPaymentType,
isBillingEmailRequired,
@@ -209,3 +210,36 @@ describe('regles type de reglement (RG-1.12 / RG-1.13)', () => {
expect(isRibRequiredForPaymentType(null)).toBe(false)
})
})
describe('hasAllRequiredAccountingFields (RG-1.30)', () => {
const complete = {
siren: '123456789',
accountNumber: '00012345678',
nTva: 'FR12345678901',
tvaModeIri: '/api/tva_modes/1',
paymentDelayIri: '/api/payment_delays/1',
paymentTypeIri: '/api/payment_types/1',
}
it('vrai quand les six champs obligatoires sont remplis', () => {
expect(hasAllRequiredAccountingFields(complete)).toBe(true)
})
it('faux si un champ est manquant (null ou vide apres trim)', () => {
expect(hasAllRequiredAccountingFields({ ...complete, siren: null })).toBe(false)
expect(hasAllRequiredAccountingFields({ ...complete, accountNumber: ' ' })).toBe(false)
expect(hasAllRequiredAccountingFields({ ...complete, tvaModeIri: null })).toBe(false)
expect(hasAllRequiredAccountingFields({ ...complete, paymentTypeIri: null })).toBe(false)
})
it('faux quand tout est vide (onglet non rempli)', () => {
expect(hasAllRequiredAccountingFields({
siren: null,
accountNumber: null,
nTva: null,
tvaModeIri: null,
paymentDelayIri: null,
paymentTypeIri: null,
})).toBe(false)
})
})
@@ -208,3 +208,32 @@ export function isBankRequiredForPaymentType(code: string | null | undefined): b
export function isRibRequiredForPaymentType(code: string | null | undefined): boolean {
return code === PAYMENT_TYPE_LCR
}
/** Sous-ensemble du brouillon comptable portant les six champs obligatoires. */
export interface AccountingRequiredDraft {
siren: string | null
accountNumber: string | null
nTva: string | null
tvaModeIri: string | null
paymentDelayIri: string | null
paymentTypeIri: string | null
}
/**
* RG-1.30 : les six champs scalaires de l'onglet Comptabilite sont obligatoires
* pour valider l'onglet (SIREN, N de compte, Mode de TVA, N de TVA, Delai de
* reglement, Type de reglement). bank / RIB restent conditionnels (RG-1.12 /
* RG-1.13) et sont evalues a part. Miroir front du
* ClientAccountingCompletenessValidator : meme gate que les onglets Contact /
* Adresse (bouton « Valider » desactive tant que l'onglet n'est pas complet).
*/
export function hasAllRequiredAccountingFields(accounting: AccountingRequiredDraft): boolean {
const filled = (v: string | null): boolean => v !== null && v.trim() !== ''
return filled(accounting.siren)
&& filled(accounting.accountNumber)
&& filled(accounting.nTva)
&& filled(accounting.tvaModeIri)
&& filled(accounting.paymentDelayIri)
&& filled(accounting.paymentTypeIri)
}