diff --git a/apps/edr-freight-web/backoffice/src/components/contracts/detail/ContractDetailTabCards.tsx b/apps/edr-freight-web/backoffice/src/components/contracts/detail/ContractDetailTabCards.tsx index de8ff1953..d29e045c4 100644 --- a/apps/edr-freight-web/backoffice/src/components/contracts/detail/ContractDetailTabCards.tsx +++ b/apps/edr-freight-web/backoffice/src/components/contracts/detail/ContractDetailTabCards.tsx @@ -170,12 +170,13 @@ export function ContractCustomerCard({ /> - + {/* Whoever the eTrade licence names as the business's manager. */} + diff --git a/apps/edr-freight-web/backoffice/src/components/customers/ChangeRequestReview.tsx b/apps/edr-freight-web/backoffice/src/components/customers/ChangeRequestReview.tsx index d86e0079c..65c4f25e3 100644 --- a/apps/edr-freight-web/backoffice/src/components/customers/ChangeRequestReview.tsx +++ b/apps/edr-freight-web/backoffice/src/components/customers/ChangeRequestReview.tsx @@ -43,9 +43,17 @@ export const FIELD_LABELS: Record = { contactPersonPosition: "Contact position", contactPersonEmail: "Contact email", contactPersonPhone: "Contact phone", - generalManagerName: "General manager", - generalManagerEmail: "GM email", - generalManagerPhone: "GM phone", + ownerName: "Owner name", + ownerEmail: "Owner email", + ownerPhone: "Owner phone", + poaDeclared: "Has a Power of Attorney", + poaPassportNumber: "PoA passport number", + // Nothing writes these any more — the general manager was removed — but + // change requests filed before that still carry them, and without a label + // the reviewer sees a raw attribute key. + generalManagerName: "General manager (retired)", + generalManagerEmail: "GM email (retired)", + generalManagerPhone: "GM phone (retired)", poaName: "PoA name", poaPhone: "PoA phone", poaEmail: "PoA email", @@ -79,9 +87,9 @@ export function currentValue(company: Company, key: string): string { nationality: c.nationality, contactPersonName: c.contactPersonName ?? attrs.contactPersonName, contactPersonPhone: c.contactPersonPhone ?? attrs.contactPersonPhone, - generalManagerName: c.generalManagerName ?? attrs.generalManagerName, - generalManagerEmail: c.generalManagerEmail ?? attrs.generalManagerEmail, - generalManagerPhone: c.generalManagerPhone ?? attrs.generalManagerPhone, + ownerName: c.ownerName ?? attrs.ownerName, + ownerEmail: c.ownerEmail ?? attrs.ownerEmail, + ownerPhone: c.ownerPhone ?? attrs.ownerPhone, }; const v = key in map ? map[key] : (c[key] ?? attrs[key]); return v === null || v === undefined || v === "" ? "—" : String(v); diff --git a/apps/edr-freight-web/backoffice/src/pages/customers/CustomerDetailPage.tsx b/apps/edr-freight-web/backoffice/src/pages/customers/CustomerDetailPage.tsx index f31ce02e9..f197e1952 100644 --- a/apps/edr-freight-web/backoffice/src/pages/customers/CustomerDetailPage.tsx +++ b/apps/edr-freight-web/backoffice/src/pages/customers/CustomerDetailPage.tsx @@ -17,6 +17,7 @@ import { Text, } from "@mantine/core"; import { + AlertTriangle, ArrowLeft, ArrowRight, Banknote, @@ -638,8 +639,9 @@ export default function CustomerDetailPage() { const hasPoaDetails = poaFields.some((f) => f.value?.trim()); // Shared with the portal (buildCompanyIdentityState) — same derivation, so // this page can never disagree with the rule the API actually enforces. - const ownerIdentity = company?.identity?.owner; - const poaIdentity = company?.identity?.poa; + const identityState = company?.identity; + const ownerIdentity = identityState?.owner; + const poaIdentity = identityState?.poa; const hasEtradeRecord = Boolean(company?.licenceNumber?.trim()); // A freight forwarder acts on other companies' behalf, so its PoA — details // and DARS delegation paper both — is mandatory rather than optional. @@ -647,7 +649,7 @@ export default function CustomerDetailPage() { (p) => p.type === "freight_forwarder", ); const delegationMissing = - (hasPoaDetails || poaMandatory) && poaLive.length === 0; + company?.identity?.poaDeclared === "yes" && poaLive.length === 0; if (isLoading) { return ( @@ -834,18 +836,9 @@ export default function CustomerDetailPage() { value={company.contactPersonPhone} /> - - - + + + @@ -912,6 +905,11 @@ export default function CustomerDetailPage() { Owner identity + {identityState?.subject === "owner" && ( + + Verifies for this company + + )} {ownerIdentity?.verified ? ( Fayda verified @@ -922,6 +920,36 @@ export default function CustomerDetailPage() { )} + + {/* THE check: is the owner the company put forward the person + the eTrade licence actually names? Advisory — eTrade and + Fayda transliterate Amharic names differently, so this is a + prompt to look, not a verdict. */} + {identityState?.ownerMatchesEtrade === false ? ( + } + title="Does not match the eTrade licence" + > + The licence names{" "} + {identityState.etradeManagerName}, but this + company recorded {company.ownerName}. + + ) : identityState?.ownerMatchesEtrade === true ? ( + + Matches the eTrade licence + + ) : ( + + No eTrade manager name on file to compare against. + + )} {ownerIdentity?.verified ? ( diff --git a/apps/edr-freight-web/backoffice/src/services/customers.service.ts b/apps/edr-freight-web/backoffice/src/services/customers.service.ts index eb86dd752..5b3cc4527 100644 --- a/apps/edr-freight-web/backoffice/src/services/customers.service.ts +++ b/apps/edr-freight-web/backoffice/src/services/customers.service.ts @@ -24,7 +24,7 @@ const cleanParams = (params: object) => ), ); -/** Lift attributes JSONB into the flat contact/manager fields the UI reads. */ +/** Lift attributes JSONB into the flat contact/owner fields the UI reads. */ function mapCompany(dto: Record): Company { const attrs = (dto.attributes as Record | null) ?? {}; return { @@ -32,9 +32,9 @@ function mapCompany(dto: Record): Company { companyProfiles: (dto.companyProfiles as Company["companyProfiles"]) ?? [], contactPersonName: (attrs.contactPersonName as string | null) ?? null, contactPersonPhone: (attrs.contactPersonPhone as string | null) ?? null, - generalManagerName: (attrs.generalManagerName as string | null) ?? null, - generalManagerEmail: (attrs.generalManagerEmail as string | null) ?? null, - generalManagerPhone: (attrs.generalManagerPhone as string | null) ?? null, + ownerName: (attrs.ownerName as string | null) ?? null, + ownerEmail: (attrs.ownerEmail as string | null) ?? null, + ownerPhone: (attrs.ownerPhone as string | null) ?? null, poaName: (attrs.poaName as string | null) ?? null, poaEmail: (attrs.poaEmail as string | null) ?? null, poaPhone: (attrs.poaPhone as string | null) ?? null, diff --git a/apps/edr-freight-web/backoffice/src/types/booking.ts b/apps/edr-freight-web/backoffice/src/types/booking.ts index 7ae93a1b7..dd1734ad6 100644 --- a/apps/edr-freight-web/backoffice/src/types/booking.ts +++ b/apps/edr-freight-web/backoffice/src/types/booking.ts @@ -64,9 +64,9 @@ export interface BookingCompany { email?: string | null; contactPersonName?: string | null; contactPersonPhone?: string | null; - generalManagerName?: string | null; - generalManagerEmail?: string | null; - generalManagerPhone?: string | null; + ownerName?: string | null; + ownerEmail?: string | null; + ownerPhone?: string | null; website?: string | null; } diff --git a/apps/edr-freight-web/backoffice/src/types/customer.ts b/apps/edr-freight-web/backoffice/src/types/customer.ts index cd2d67842..4fd920916 100644 --- a/apps/edr-freight-web/backoffice/src/types/customer.ts +++ b/apps/edr-freight-web/backoffice/src/types/customer.ts @@ -179,23 +179,34 @@ export interface IdentityVerificationState { verifiedAt: string | null; birthdate: string | null; gender: string | null; -} - -/** Mirrors `OwnerIdentityStateDto`. */ -export interface OwnerIdentityState extends IdentityVerificationState { + /** Typed passport number — the foreign-company alternative to Fayda. */ passportNumber: string | null; } /** - * Owner/PoA Fayda verification, shared with the portal's derivation + * The company's single identity verification, shared with the portal's derivation * (`buildCompanyIdentityState`) so backoffice never re-derives — or * disagrees with — the rule the API actually enforces. */ export interface CompanyIdentityState { - faydaRequired: boolean; - passportRequired: boolean; - owner: OwnerIdentityState; + /** Foreign company: a passport number proves the person as Fayda would. */ + passportAccepted: boolean; + /** Whether the company named a representative. Null = never answered. */ + poaDeclared: "yes" | "no" | null; + /** Whose verification the company is gated on — PoA if declared, else owner. */ + subject: "owner" | "poa" | null; + owner: IdentityVerificationState; poa: IdentityVerificationState; + identityProven: boolean; + /** The manager named on the eTrade licence, captured at lookup. */ + etradeManagerName: string | null; + /** + * Does the owner the company put forward match the eTrade licence? + * THE reviewer check. Null when there is nothing to compare. Advisory — + * eTrade and Fayda transliterate Amharic names differently, so a `false` is + * "look at this", not "reject this". + */ + ownerMatchesEtrade: boolean | null; complete: boolean; } @@ -216,9 +227,10 @@ export interface Company { email?: string | null; contactPersonName?: string | null; contactPersonPhone?: string | null; - generalManagerName?: string | null; - generalManagerEmail?: string | null; - generalManagerPhone?: string | null; + /** The owner — whoever the eTrade licence names as the business's manager. */ + ownerName?: string | null; + ownerEmail?: string | null; + ownerPhone?: string | null; poaName?: string | null; poaEmail?: string | null; poaPhone?: string | null;