192 lines
6.9 KiB
TypeScript
192 lines
6.9 KiB
TypeScript
/**
|
|
* Helpers purs des écrans Consultation / Modification transporteur (M4, ERP-170) —
|
|
* miroir de `providerDetail.ts` (M3). Mappent le payload `GET /api/carriers/{id}`
|
|
* (relations embarquées via les groupes `carrier:item:read` + `qualimat:read` +
|
|
* read-groups cross-module client/supplier/site/adresses) vers les brouillons
|
|
* « plats » partagés avec les blocs Adresse / Contact / Prix.
|
|
*
|
|
* Ne touchent ni à l'API ni à l'état réactif (testables unitairement). Les champs
|
|
* nuls peuvent être OMIS (skip_null_values) → toujours lire avec `?? null`.
|
|
*/
|
|
|
|
import { formatPhoneFR } from '~/shared/utils/phone'
|
|
import type {
|
|
CarrierAddressFormDraft,
|
|
CarrierContactFormDraft,
|
|
CarrierMainDraft,
|
|
CarrierPriceFormDraft,
|
|
} from '~/modules/transport/types/carrierForm'
|
|
|
|
/** Référence Hydra embarquée minimale (@id toujours présent). */
|
|
export interface HydraRef {
|
|
'@id': string
|
|
[key: string]: unknown
|
|
}
|
|
|
|
/** Une relation peut être embarquée (objet), un IRI nu (chaîne) ou absente. */
|
|
export type Relation = HydraRef | string | null | undefined
|
|
|
|
/** Adresse embarquée (groupe carrier:item:read). */
|
|
export interface CarrierAddressRead extends HydraRef {
|
|
id: number
|
|
country?: string | null
|
|
postalCode?: string | null
|
|
city?: string | null
|
|
street?: string | null
|
|
streetComplement?: string | null
|
|
}
|
|
|
|
/** Contact embarqué (groupe carrier:item:read). */
|
|
export interface CarrierContactRead extends HydraRef {
|
|
id: number
|
|
firstName?: string | null
|
|
lastName?: string | null
|
|
jobTitle?: string | null
|
|
phonePrimary?: string | null
|
|
phoneSecondary?: string | null
|
|
email?: string | null
|
|
}
|
|
|
|
/** Prix embarqué (groupe carrier:item:read + relations cross-module). */
|
|
export interface CarrierPriceRead extends HydraRef {
|
|
id: number
|
|
direction?: string | null
|
|
client?: Relation
|
|
clientDeliveryAddress?: Relation
|
|
departureSite?: Relation
|
|
supplier?: Relation
|
|
supplierSupplyAddress?: Relation
|
|
deliverySite?: Relation
|
|
containerType?: string | null
|
|
pricingUnit?: string | null
|
|
price?: string | null
|
|
priceState?: string | null
|
|
}
|
|
|
|
/**
|
|
* Détail d'un transporteur (`GET /api/carriers/{id}`). Tous les champs optionnels :
|
|
* skip_null_values peut omettre n'importe quelle clé.
|
|
*/
|
|
export interface CarrierDetail extends HydraRef {
|
|
id: number
|
|
name?: string | null
|
|
certificationType?: string | null
|
|
isChartered?: boolean
|
|
indexationRate?: string | null
|
|
containerType?: string | null
|
|
volumeM3?: string | null
|
|
liotPlates?: string | null
|
|
dischargeDocument?: Relation
|
|
qualimatCarrier?: Relation
|
|
isArchived?: boolean
|
|
// Adresse UNIQUE (OneToOne, ERP-172) : objet embarqué (ou absent), pas une liste.
|
|
address?: CarrierAddressRead | null
|
|
contacts?: CarrierContactRead[]
|
|
prices?: CarrierPriceRead[]
|
|
}
|
|
|
|
/** Extrait l'IRI d'une relation (objet embarqué, IRI nu, ou null si absente). */
|
|
export function iriOf(relation: Relation): string | null {
|
|
if (relation === null || relation === undefined) {
|
|
return null
|
|
}
|
|
if (typeof relation === 'string') {
|
|
return relation
|
|
}
|
|
return relation['@id'] ?? null
|
|
}
|
|
|
|
/**
|
|
* Libellé d'affichage d'une relation embarquée : `name` (site) à défaut une adresse
|
|
* condensée (voie · CP · ville). Chaîne vide si la relation est un IRI nu / absente.
|
|
*/
|
|
export function labelOfRelation(relation: Relation): string {
|
|
if (!relation || typeof relation === 'string') {
|
|
return ''
|
|
}
|
|
const name = relation.name as string | undefined
|
|
if (name) {
|
|
return name
|
|
}
|
|
const parts = [relation.street, relation.postalCode, relation.city].filter(Boolean)
|
|
return parts.join(' · ')
|
|
}
|
|
|
|
/** Mappe le détail vers le brouillon du formulaire principal. */
|
|
export function mapMainToDraft(detail: CarrierDetail): CarrierMainDraft {
|
|
return {
|
|
name: detail.name ?? '',
|
|
certificationType: detail.certificationType ?? null,
|
|
isChartered: detail.isChartered ?? false,
|
|
indexationRate: detail.indexationRate ?? '',
|
|
containerType: detail.containerType ?? null,
|
|
volumeM3: detail.volumeM3 ?? '',
|
|
liotPlates: detail.liotPlates ?? '',
|
|
dischargeDocumentIri: iriOf(detail.dischargeDocument),
|
|
qualimatCarrierIri: iriOf(detail.qualimatCarrier),
|
|
}
|
|
}
|
|
|
|
/** Mappe une adresse embarquée vers un brouillon. */
|
|
export function mapAddressToDraft(address: CarrierAddressRead): CarrierAddressFormDraft {
|
|
return {
|
|
id: address.id,
|
|
country: address.country ?? 'France',
|
|
postalCode: address.postalCode ?? null,
|
|
city: address.city ?? null,
|
|
street: address.street ?? null,
|
|
streetComplement: address.streetComplement ?? null,
|
|
}
|
|
}
|
|
|
|
/** Mappe un contact embarqué vers un brouillon (téléphones formatés XX XX XX XX XX). */
|
|
export function mapContactToDraft(contact: CarrierContactRead): CarrierContactFormDraft {
|
|
const secondary = contact.phoneSecondary ?? null
|
|
return {
|
|
id: contact.id,
|
|
firstName: contact.firstName ?? null,
|
|
lastName: contact.lastName ?? null,
|
|
jobTitle: contact.jobTitle ?? null,
|
|
phonePrimary: contact.phonePrimary ? formatPhoneFR(contact.phonePrimary) : null,
|
|
phoneSecondary: secondary ? formatPhoneFR(secondary) : null,
|
|
email: contact.email ?? null,
|
|
hasSecondaryPhone: secondary !== null && secondary !== '',
|
|
}
|
|
}
|
|
|
|
/** Mappe un prix embarqué vers un brouillon (relations en IRI). */
|
|
export function mapPriceToDraft(price: CarrierPriceRead): CarrierPriceFormDraft {
|
|
const direction = price.direction === 'CLIENT' || price.direction === 'FOURNISSEUR'
|
|
? price.direction
|
|
: null
|
|
return {
|
|
id: price.id,
|
|
direction,
|
|
clientIri: iriOf(price.client),
|
|
clientDeliveryAddressIri: iriOf(price.clientDeliveryAddress),
|
|
departureSiteIri: iriOf(price.departureSite),
|
|
supplierIri: iriOf(price.supplier),
|
|
supplierSupplyAddressIri: iriOf(price.supplierSupplyAddress),
|
|
deliverySiteIri: iriOf(price.deliverySite),
|
|
containerType: price.containerType ?? null,
|
|
pricingUnit: price.pricingUnit ?? null,
|
|
price: price.price ?? null,
|
|
priceState: price.priceState ?? null,
|
|
}
|
|
}
|
|
|
|
/** Bouton « Modifier » : visible avec la permission `manage` (Admin / Bureau). */
|
|
export function canEditCarrier(can: (code: string) => boolean): boolean {
|
|
return can('transport.carriers.manage')
|
|
}
|
|
|
|
/** Bouton « Archiver » : permission archive ET transporteur encore actif (Admin seul). */
|
|
export function showArchiveAction(can: (code: string) => boolean, isArchived: boolean): boolean {
|
|
return can('transport.carriers.archive') && !isArchived
|
|
}
|
|
|
|
/** Bouton « Restaurer » : permission archive ET transporteur déjà archivé (Admin seul). */
|
|
export function showRestoreAction(can: (code: string) => boolean, isArchived: boolean): boolean {
|
|
return can('transport.carriers.archive') && isArchived
|
|
}
|