fix : retours métier ERP-193 (4 répertoires) (#139)
Auto Tag Develop / tag (push) Successful in 11s
Auto Tag Develop / tag (push) Successful in 11s
Lot de retours métier **ERP-193** (« Fix tous les retours starseed »), transverse aux 4 répertoires (clients, fournisseurs, prestataires, transporteurs).
## Contenu
- **Pagination** : défaut à 25 items/page sur les 4 répertoires.
- **Libellé** : colonne « Dernière activité » → « Dernière modification ».
- **Consultation** : masquage des onglets vides (coquilles « à venir » + onglets de données sans donnée).
- **Chiffre d'affaires** : plafonné à 999 999 999 999,99 (clamp front + `Assert\LessThanOrEqual` back).
- **Date de création** : interdiction des dates futures (`:max` MalioDate + `Assert\LessThanOrEqual('today')` back).
- **Caractères spéciaux** : blocage des caractères parasites (`²³§~#|…`) dans les champs texte via une allow-list par profil (nom de personne / texte libre / adresse / code alphanumérique) — filtrage front à la frappe + `Assert\Regex` back autoritaire. Email/IBAN/BIC/TVA conservent leurs validateurs de format.
- **UI** : champs en consultation et onglets validés grisés (`readonly` → `disabled`).
- **UI** : boutons « Archiver » en rouge (variant `danger`).
## Tests
- Back : nouveaux tests RG (plafond CA, dates futures, caractères spéciaux) + garde-fou contraintes — suite complète verte (813 tests).
- Front : nouveaux tests unitaires (sanitizers, helpers date/montant) — 615 tests verts, eslint clean.
---------
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Reviewed-on: #139
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #139.
This commit is contained in:
@@ -17,7 +17,7 @@ import {
|
||||
type CarrierPriceFormDraft,
|
||||
} from '~/modules/transport/types/carrierForm'
|
||||
import { buildCarrierAddressPayload } from '~/modules/transport/utils/forms/carrierAddress'
|
||||
import { buildCarrierContactPayload, isCarrierContactBlank, isCarrierContactNamed } from '~/modules/transport/utils/forms/carrierContact'
|
||||
import { buildCarrierContactPayload, isCarrierContactBlank } from '~/modules/transport/utils/forms/carrierContact'
|
||||
import { buildCarrierPricePayload, isCarrierPriceValid } from '~/modules/transport/utils/forms/carrierPrice'
|
||||
import {
|
||||
mapAddressToDraft,
|
||||
@@ -416,6 +416,11 @@ export function useCarrierForm() {
|
||||
})
|
||||
}
|
||||
|
||||
/** Toast de succès après suppression serveur confirmée d'une sous-ressource. */
|
||||
function notifyRemovalSuccess(): void {
|
||||
toast.success({ title: t('success.title'), message: t('success.deleted') })
|
||||
}
|
||||
|
||||
/**
|
||||
* Soumet TOUS les blocs d'une collection en collectant les erreurs PAR INDEX :
|
||||
* on n'arrête pas au premier bloc en échec (décision ERP-101). Réinitialise la
|
||||
@@ -488,6 +493,10 @@ export function useCarrierForm() {
|
||||
await api.patch(`/carrier_addresses/${address.value.id}`, body, { toast: false })
|
||||
}
|
||||
completeTab('addresses')
|
||||
// ERP-193 : l'onglet Contact est OPTIONNEL — il ne doit pas verrouiller
|
||||
// l'accès à Prix. Dès les Adresses validées, on déverrouille jusqu'à Prix
|
||||
// (Contacts reste accessible mais n'est plus une étape bloquante).
|
||||
unlockedIndex.value = tabKeys.value.length - 1
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
@@ -511,12 +520,13 @@ export function useCarrierForm() {
|
||||
// Erreurs 422 par ligne (alignées sur l'index du v-for), peuplées par submitRows.
|
||||
const contactErrors = ref<Record<string, string>[]>([])
|
||||
|
||||
// « + Nouveau contact » désactivé tant que le DERNIER bloc n'est pas « nommé »
|
||||
// (prénom OU nom) — aligné sur M1/M2/M3 (fonction / téléphone / email seuls ne
|
||||
// suffisent pas à ajouter un nouveau bloc).
|
||||
// « + Nouveau contact » désactivé tant que le DERNIER bloc est VIDE. ERP-193 :
|
||||
// l'onglet Contact n'est plus obligatoire — on ne réclame plus prénom OU nom,
|
||||
// un seul champ rempli (fonction / téléphone / email) suffit pour empiler un
|
||||
// bloc suivant (et évite d'accumuler des blocs totalement vides).
|
||||
const canAddContact = computed(() => {
|
||||
const last = contacts.value[contacts.value.length - 1]
|
||||
return last !== undefined && isCarrierContactNamed(last)
|
||||
return last !== undefined && !isCarrierContactBlank(last)
|
||||
})
|
||||
|
||||
function addContact(): void {
|
||||
@@ -535,16 +545,18 @@ export function useCarrierForm() {
|
||||
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||
makeEmpty: emptyCarrierContact,
|
||||
onError: notifyRemovalError,
|
||||
onSuccess: notifyRemovalSuccess,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide l'onglet Contacts : POST des nouveaux contacts sur
|
||||
* /carriers/{id}/contacts, PATCH des existants sur /carrier_contacts/{id}
|
||||
* (groupe carrier:write:contacts). RG-4.08 (≥ 1 champ rempli, max 2 téléphones)
|
||||
* re-validée back → 422 par ligne. Si l'onglet ne contient QUE des amorces
|
||||
* vides, on soumet la 1re pour déclencher la 422 RG-4.08 inline plutôt que de
|
||||
* finaliser un onglet vide. Retourne true si l'onglet a été validé.
|
||||
* (groupe carrier:write:contacts). Max 2 téléphones re-validé back → 422 par
|
||||
* ligne. ERP-193 : l'onglet Contact est OPTIONNEL — les amorces vides neuves
|
||||
* sont systématiquement ignorées (pas de contact vide créé) et un onglet sans
|
||||
* aucun bloc rempli est simplement finalisé, déverrouillant l'onglet Prix.
|
||||
* Retourne true si l'onglet a été validé.
|
||||
*/
|
||||
async function submitContacts(onError: (error: unknown) => void): Promise<boolean> {
|
||||
if (carrierId.value === null || tabSubmitting.value) {
|
||||
@@ -552,7 +564,6 @@ export function useCarrierForm() {
|
||||
}
|
||||
tabSubmitting.value = true
|
||||
try {
|
||||
const hasSubmittable = contacts.value.some(c => c.id !== null || !isCarrierContactBlank(c))
|
||||
const hasError = await submitRows(
|
||||
contacts.value,
|
||||
contactErrors,
|
||||
@@ -571,9 +582,9 @@ export function useCarrierForm() {
|
||||
}
|
||||
},
|
||||
onError,
|
||||
// Amorce vide neuve ignorée s'il reste un autre bloc soumettable ;
|
||||
// sinon on la soumet pour déclencher la 422 RG-4.08 (sur firstName).
|
||||
contact => hasSubmittable && contact.id === null && isCarrierContactBlank(contact),
|
||||
// Amorce vide neuve toujours ignorée (bloc Contact optionnel, ERP-193) :
|
||||
// un onglet sans aucun bloc rempli se finalise sans rien créer.
|
||||
contact => contact.id === null && isCarrierContactBlank(contact),
|
||||
)
|
||||
if (hasError) {
|
||||
return false
|
||||
@@ -648,6 +659,7 @@ export function useCarrierForm() {
|
||||
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||
makeEmpty: emptyCarrierPrice,
|
||||
onError: notifyRemovalError,
|
||||
onSuccess: notifyRemovalSuccess,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user