feat: expose eTrade registration and fayda birthdate/gender in company profile

This commit is contained in:
Nathnael
2026-07-29 13:38:39 +00:00
parent 0165a64e76
commit 16ef64fae2
5 changed files with 200 additions and 18 deletions

View File

@@ -607,17 +607,11 @@ export default function CustomerDetailPage() {
{ label: "PoA address", value: company?.poaAddress },
];
const hasPoaDetails = poaFields.some((f) => f.value?.trim());
// Fayda verification is written into the company attributes by the portal's
// verification flow.
const attrs = (company?.attributes ?? {}) as Record<string, unknown>;
const faydaVerified = (prefix: "owner" | "poa") => ({
verified: Boolean(attrs[`${prefix}FaydaSub`]),
at: (attrs[`${prefix}FaydaVerifiedAt`] as string | undefined) ?? null,
});
const ownerIdentity = faydaVerified("owner");
const ownerPassportNumber =
(attrs.ownerPassportNumber as string | undefined) ?? null;
const poaIdentity = faydaVerified("poa");
// 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 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.
const poaMandatory = (company?.companyProfiles ?? []).some(
@@ -766,10 +760,10 @@ export default function CustomerDetailPage() {
<InfoField
label="Owner identity"
value={
ownerIdentity.verified
ownerIdentity?.verified
? "Fayda verified"
: ownerPassportNumber
? `Passport ${ownerPassportNumber}`
: ownerIdentity?.passportNumber
? `Passport ${ownerIdentity.passportNumber}`
: "Not verified"
}
/>
@@ -804,6 +798,97 @@ export default function CustomerDetailPage() {
</Stack>
</Card>
<Card>
<Stack gap="lg">
<Group gap="xs" wrap="nowrap">
<Text fw={600} c="edr-text">
eTrade registration
</Text>
{hasEtradeRecord ? (
<Badge size="sm" color="edr-green" variant="light">
Verified with eTrade
</Badge>
) : (
<Badge size="sm" color="gray" variant="light">
No eTrade record
</Badge>
)}
</Group>
{hasEtradeRecord ? (
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="lg">
<InfoField
label="License number"
value={company.licenceNumber}
/>
<InfoField label="Status" value={company.statusDescription} />
<InfoField
label="Date registered"
value={company.dateRegistered}
/>
<InfoField label="Renewed from" value={company.renewedFrom} />
<InfoField label="Renewal date" value={company.renewalDate} />
<InfoField label="Renewed to" value={company.renewedTo} />
<InfoField label="Region" value={company.region} />
<InfoField label="Zone" value={company.zone} />
<InfoField label="Woreda" value={company.woreda} />
<InfoField label="Kebele" value={company.kebele} />
<InfoField label="House No" value={company.houseNo} />
</SimpleGrid>
) : (
<Text size="sm" c="dimmed">
No eTrade registration record on file for this customer's
TIN.
</Text>
)}
</Stack>
</Card>
<Card>
<Stack gap="lg">
<Group gap="xs" wrap="nowrap">
<Text fw={600} c="edr-text">
Owner identity
</Text>
{ownerIdentity?.verified ? (
<Badge size="sm" color="edr-green" variant="light">
Fayda verified
</Badge>
) : (
<Badge size="sm" color="gray" variant="light">
Not verified
</Badge>
)}
</Group>
{ownerIdentity?.verified ? (
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="lg">
<InfoField label="Name" value={ownerIdentity.name} />
<InfoField label="Phone" value={ownerIdentity.phone} />
<InfoField label="Email" value={ownerIdentity.email} />
<InfoField label="Address" value={ownerIdentity.address} />
<InfoField
label="Verified at"
value={formatDate(ownerIdentity.verifiedAt)}
/>
<InfoField
label="Birthdate"
value={ownerIdentity.birthdate}
/>
<InfoField label="Gender" value={ownerIdentity.gender} />
<InfoField
label="Passport number"
value={ownerIdentity.passportNumber}
/>
</SimpleGrid>
) : (
<Text size="sm" c="dimmed">
{ownerIdentity?.passportNumber
? `Not Fayda verified — identified by passport ${ownerIdentity.passportNumber}.`
: "The company owner has not verified their identity with Fayda."}
</Text>
)}
</Stack>
</Card>
<Card>
<Stack gap="lg">
<Group justify="space-between" wrap="nowrap">
@@ -844,9 +929,22 @@ export default function CustomerDetailPage() {
<InfoField
label="PoA Fayda"
value={
poaIdentity.verified ? "Verified" : "Not verified"
poaIdentity?.verified ? "Verified" : "Not verified"
}
/>
{poaIdentity?.verified && (
<>
<InfoField
label="PoA verified at"
value={formatDate(poaIdentity.verifiedAt)}
/>
<InfoField
label="PoA birthdate"
value={poaIdentity.birthdate}
/>
<InfoField label="PoA gender" value={poaIdentity.gender} />
</>
)}
</SimpleGrid>
) : (
<Text size="sm" c="dimmed">

View File

@@ -135,6 +135,36 @@ export interface CustomerResetTarget {
phoneIsDomestic: boolean | null;
}
/** One person's Fayda verification state — mirrors `IdentityVerificationStateDto`. */
export interface IdentityVerificationState {
verified: boolean;
name: string | null;
phone: string | null;
email: string | null;
address: string | null;
verifiedAt: string | null;
birthdate: string | null;
gender: string | null;
}
/** Mirrors `OwnerIdentityStateDto`. */
export interface OwnerIdentityState extends IdentityVerificationState {
passportNumber: string | null;
}
/**
* Owner/PoA Fayda 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;
poa: IdentityVerificationState;
complete: boolean;
}
/** Mirrors backend `Company` (+ its `companyProfiles`). */
export interface Company {
id: string;
@@ -161,6 +191,20 @@ export interface Company {
poaAddress?: string | null;
website?: string | null;
attributes?: Record<string, unknown> | null;
// eTrade-sourced registration record — populated by the onboarding TIN
// lookup, locked/read-only on the portal from the moment it's fetched.
licenceNumber?: string | null;
statusDescription?: string | null;
dateRegistered?: string | null;
renewedFrom?: string | null;
renewalDate?: string | null;
renewedTo?: string | null;
region?: string | null;
zone?: string | null;
woreda?: string | null;
kebele?: string | null;
houseNo?: string | null;
identity?: CompanyIdentityState;
companyProfiles: CompanyProfile[];
/**
* Whether the customer submitted their onboarding application. A company row