mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 22:18:12 +00:00
@@ -51,6 +51,8 @@ import { NO_ACCESS_PATH, resolveLandingPath } from "./lib/landing";
|
||||
import NoAccessPage from "./pages/NoAccessPage";
|
||||
import FileUploadSettingsPage from "./pages/documents/FileUploadSettingsPage";
|
||||
import DropdownSettingsPage from "./pages/dropdown_settings/DropdownSettingsPage";
|
||||
import StampSettings from "./record-management/components/Settings/uploadTeeterandSingature";
|
||||
import InvoiceStampSettingsPage from "./pages/settings/InvoiceStampSettingsPage";
|
||||
import ContractTemplatesPage from "./pages/contract_templates/ContractTemplatesPage";
|
||||
import PortalContentPage from "./pages/portal_content/PortalContentPage";
|
||||
import ContractTemplateEditorPage from "./pages/contract_templates/ContractTemplateEditorPage";
|
||||
@@ -117,6 +119,20 @@ import {
|
||||
findActiveSidebarLabel,
|
||||
} from "@/components/layout/sidebar-sections";
|
||||
|
||||
/**
|
||||
* The per-shipment clearance detail page is the shared destination of three
|
||||
* hubs (Operations → Clearance, Clearance Documents, Self-Clearance Review),
|
||||
* none of which are gated on `bookings:clearance_view`. Gating the detail on
|
||||
* that key alone bounced reviewers back to their landing page (Bookings) the
|
||||
* moment they opened a row, so accept any key that can reach a hub.
|
||||
*/
|
||||
const CLEARANCE_DETAIL_PERMS = [
|
||||
FREIGHT_PERMS.bookings.clearanceView,
|
||||
FREIGHT_PERMS.contracts.clearanceReview,
|
||||
FREIGHT_PERMS.contracts.clearanceEtActions,
|
||||
FREIGHT_PERMS.contracts.opsClearanceReview,
|
||||
];
|
||||
|
||||
const DashboardShell = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
@@ -285,9 +301,7 @@ const App = () => {
|
||||
<Route
|
||||
path="clearance/:id"
|
||||
element={
|
||||
<RequirePermission
|
||||
permission={FREIGHT_PERMS.bookings.clearanceView}
|
||||
>
|
||||
<RequirePermission permission={CLEARANCE_DETAIL_PERMS}>
|
||||
<DocumentClearanceDetailPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
@@ -321,9 +335,7 @@ const App = () => {
|
||||
<Route
|
||||
path="bookings/:bookingId/clearance"
|
||||
element={
|
||||
<RequirePermission
|
||||
permission={FREIGHT_PERMS.bookings.clearanceView}
|
||||
>
|
||||
<RequirePermission permission={CLEARANCE_DETAIL_PERMS}>
|
||||
<DocumentClearanceDetailPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
@@ -773,6 +785,24 @@ const App = () => {
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="stamp-settings"
|
||||
element={
|
||||
<RequirePermission permission={FREIGHT_PERMS.settings.stamp.view}>
|
||||
<StampSettings />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="invoice-stamp-settings"
|
||||
element={
|
||||
<RequirePermission
|
||||
permission={FREIGHT_PERMS.settings.invoiceStamp.view}
|
||||
>
|
||||
<InvoiceStampSettingsPage />
|
||||
</RequirePermission>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="audit-logs"
|
||||
element={
|
||||
|
||||
@@ -170,12 +170,13 @@ export function ContractCustomerCard({
|
||||
/>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard icon={User} title="General manager" accent="grape">
|
||||
{/* Whoever the eTrade licence names as the business's manager. */}
|
||||
<SectionCard icon={User} title="Owner" accent="grape">
|
||||
<InfoRows
|
||||
rows={[
|
||||
{ icon: User, label: "Name", value: company.generalManagerName },
|
||||
{ icon: Mail, label: "Email", value: company.generalManagerEmail },
|
||||
{ icon: Phone, label: "Phone", value: company.generalManagerPhone },
|
||||
{ icon: User, label: "Name", value: company.ownerName },
|
||||
{ icon: Mail, label: "Email", value: company.ownerEmail },
|
||||
{ icon: Phone, label: "Phone", value: company.ownerPhone },
|
||||
]}
|
||||
/>
|
||||
</SectionCard>
|
||||
|
||||
@@ -43,9 +43,17 @@ export const FIELD_LABELS: Record<string, string> = {
|
||||
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);
|
||||
|
||||
@@ -485,6 +485,18 @@ export const buildSidebarSections = (demoItems: SidebarItem[]): SidebarSection[]
|
||||
icon: <Settings />,
|
||||
permission: FREIGHT_PERMS.settings.dropdown.view,
|
||||
},
|
||||
{
|
||||
label: "Stamp settings",
|
||||
href: "/dashboard/stamp-settings",
|
||||
icon: <FileSignature />,
|
||||
permission: FREIGHT_PERMS.settings.stamp.view,
|
||||
},
|
||||
{
|
||||
label: "Invoice stamp",
|
||||
href: "/dashboard/invoice-stamp-settings",
|
||||
icon: <Receipt />,
|
||||
permission: FREIGHT_PERMS.settings.invoiceStamp.view,
|
||||
},
|
||||
{
|
||||
label: "Contract templates",
|
||||
href: "/dashboard/contract-templates",
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { stampSettingsService } from "@/services/stampSettings.service";
|
||||
import { useErrorHandler } from "@/shared/hooks/useErrorHandler";
|
||||
|
||||
const QUERY_KEY = ["stampSettings"];
|
||||
|
||||
export const useStampSettingsQuery = () =>
|
||||
useQuery({
|
||||
queryKey: QUERY_KEY,
|
||||
queryFn: () => stampSettingsService.get(),
|
||||
});
|
||||
|
||||
export const useSetStamp = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const { handleError } = useErrorHandler(t);
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (stampImageBase64: string) =>
|
||||
stampSettingsService.set(stampImageBase64),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
|
||||
toast.success(t("stampSettings.updated", "Company stamp updated"));
|
||||
},
|
||||
onError: handleError,
|
||||
});
|
||||
};
|
||||
|
||||
export const useClearStamp = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const { handleError } = useErrorHandler(t);
|
||||
|
||||
return useMutation({
|
||||
mutationFn: () => stampSettingsService.clear(),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: QUERY_KEY });
|
||||
toast.success(t("stampSettings.cleared", "Company stamp removed"));
|
||||
},
|
||||
onError: handleError,
|
||||
});
|
||||
};
|
||||
@@ -320,6 +320,16 @@ export const FREIGHT_PERMS = {
|
||||
view: "edr_freight_app:settings:dropdown:view",
|
||||
manage: "edr_freight_app:settings:dropdown:manage",
|
||||
},
|
||||
stamp: {
|
||||
view: "edr_freight_app:settings:stamp:view",
|
||||
manage: "edr_freight_app:settings:stamp:manage",
|
||||
},
|
||||
// Company stamp/seal image stamped onto invoice/receipt PDFs — separate
|
||||
// from `stamp` above, which is the per-employee approval-record teeter.
|
||||
invoiceStamp: {
|
||||
view: "edr_freight_app:settings:invoice_stamp:view",
|
||||
manage: "edr_freight_app:settings:invoice_stamp:manage",
|
||||
},
|
||||
exchangeRate: {
|
||||
view: "edr_freight_app:settings:exchange_rate:view",
|
||||
manage: "edr_freight_app:settings:exchange_rate:manage",
|
||||
|
||||
@@ -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 (
|
||||
@@ -820,6 +822,16 @@ export default function CustomerDetailPage() {
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
{/* Why this company's registration was typed rather than
|
||||
fetched, and why it carries no business licence. */}
|
||||
<InfoField
|
||||
label="Registration"
|
||||
value={
|
||||
company.cooperative
|
||||
? "Co-operative union / farm (no trade licence)"
|
||||
: "eTrade trade licence"
|
||||
}
|
||||
/>
|
||||
<InfoField label="Address" value={company.address} />
|
||||
<InfoField label="Website" value={company.website} />
|
||||
<InfoField label="Email" value={company.email} />
|
||||
@@ -834,18 +846,9 @@ export default function CustomerDetailPage() {
|
||||
value={company.contactPersonPhone}
|
||||
/>
|
||||
<Box />
|
||||
<InfoField
|
||||
label="General manager"
|
||||
value={company.generalManagerName}
|
||||
/>
|
||||
<InfoField
|
||||
label="GM email"
|
||||
value={company.generalManagerEmail}
|
||||
/>
|
||||
<InfoField
|
||||
label="GM phone"
|
||||
value={company.generalManagerPhone}
|
||||
/>
|
||||
<InfoField label="Owner" value={company.ownerName} />
|
||||
<InfoField label="Owner email" value={company.ownerEmail} />
|
||||
<InfoField label="Owner phone" value={company.ownerPhone} />
|
||||
</SimpleGrid>
|
||||
</Stack>
|
||||
</Card>
|
||||
@@ -912,6 +915,11 @@ export default function CustomerDetailPage() {
|
||||
<Text fw={600} c="edr-text">
|
||||
Owner identity
|
||||
</Text>
|
||||
{identityState?.subject === "owner" && (
|
||||
<Badge size="sm" color="blue" variant="light">
|
||||
Verifies for this company
|
||||
</Badge>
|
||||
)}
|
||||
{ownerIdentity?.verified ? (
|
||||
<Badge size="sm" color="edr-green" variant="light">
|
||||
Fayda verified
|
||||
@@ -922,6 +930,41 @@ export default function CustomerDetailPage() {
|
||||
</Badge>
|
||||
)}
|
||||
</Group>
|
||||
|
||||
{/* 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 ? (
|
||||
<Alert
|
||||
color="amber"
|
||||
variant="light"
|
||||
icon={<AlertTriangle size={16} />}
|
||||
title="Does not match the eTrade licence"
|
||||
>
|
||||
The licence names{" "}
|
||||
<strong>{identityState.etradeManagerName}</strong>, but this
|
||||
company recorded <strong>{company.ownerName}</strong>.
|
||||
</Alert>
|
||||
) : identityState?.ownerMatchesEtrade === true ? (
|
||||
<Badge
|
||||
size="sm"
|
||||
color="edr-green"
|
||||
variant="light"
|
||||
style={{ alignSelf: "flex-start" }}
|
||||
>
|
||||
Matches the eTrade licence
|
||||
</Badge>
|
||||
) : company.cooperative ? (
|
||||
<Text size="xs" c="dimmed">
|
||||
A co-operative union or farm holds no trade licence, so
|
||||
there is no eTrade record to check the owner against.
|
||||
</Text>
|
||||
) : (
|
||||
<Text size="xs" c="dimmed">
|
||||
No eTrade manager name on file to compare against.
|
||||
</Text>
|
||||
)}
|
||||
{ownerIdentity?.verified ? (
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="lg">
|
||||
<InfoField label="Name" value={ownerIdentity.name} />
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/shared/common/ui/card";
|
||||
import { Button } from "@/shared/common/ui/button";
|
||||
import { Save, Trash2 } from "lucide-react";
|
||||
|
||||
import { StampUpload } from "@/components/contracts/StampUpload";
|
||||
import {
|
||||
useClearStamp,
|
||||
useSetStamp,
|
||||
useStampSettingsQuery,
|
||||
} from "@/hooks/useStampSettings";
|
||||
|
||||
/**
|
||||
* The one company stamp/seal stamped onto every generated invoice/receipt
|
||||
* PDF (InvoiceDocumentService). Single global image — no per-employee choice.
|
||||
*/
|
||||
export default function InvoiceStampSettingsPage() {
|
||||
const { data, isLoading } = useStampSettingsQuery();
|
||||
const setStamp = useSetStamp();
|
||||
const clearStamp = useClearStamp();
|
||||
const [draft, setDraft] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setDraft(null);
|
||||
}, [data?.stampImageUrl]);
|
||||
|
||||
const value = draft !== null ? draft : (data?.stampImageUrl ?? null);
|
||||
const dirty = draft !== null && draft !== data?.stampImageUrl;
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!draft) return;
|
||||
await setStamp.mutateAsync(draft);
|
||||
};
|
||||
|
||||
const handleClear = async () => {
|
||||
if (!data?.stampImageUrl) return;
|
||||
await clearStamp.mutateAsync();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4 w-full max-w-screen-sm mx-auto">
|
||||
<Card className="shadow-lg border-gray-200 dark:border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle>Invoice stamp</CardTitle>
|
||||
<CardDescription>
|
||||
Stamped onto every generated invoice and receipt PDF. Replacing it
|
||||
here changes it everywhere at once — there is no per-invoice or
|
||||
per-user choice.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<StampUpload
|
||||
value={isLoading ? null : value}
|
||||
onChange={setDraft}
|
||||
label="Company stamp"
|
||||
description="Shown on every invoice/receipt PDF in place of the plain seal."
|
||||
/>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={!dirty || setStamp.isPending}
|
||||
>
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
Save
|
||||
</Button>
|
||||
{data?.stampImageUrl && !dirty && (
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleClear}
|
||||
disabled={clearStamp.isPending}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Remove
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -609,10 +609,18 @@ const UploadTeeterAndSignature = () => {
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
{/* Teeter Tab */}
|
||||
{/* Teeter Tab — single active stamp only: remove the current one to upload a replacement. */}
|
||||
<TabsContent value="teeter" className="p-4 space-y-6">
|
||||
{teeters.length > 0 && (
|
||||
<div className="space-y-6">
|
||||
{teeters.length > 1 && (
|
||||
<p className="rounded border border-amber-200 bg-amber-50 p-2 text-sm text-amber-700 dark:border-amber-900 dark:bg-amber-950 dark:text-amber-300">
|
||||
{t(
|
||||
"signatureUpload.multipleStampsWarning",
|
||||
"Only one stamp is allowed. Remove the extras below to keep a single active stamp.",
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
{teeters.map(({ id, url }) => (
|
||||
<div key={id} className="space-y-3">
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300">
|
||||
@@ -635,6 +643,7 @@ const UploadTeeterAndSignature = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{teeters.length === 0 && (
|
||||
<div className="border-2 border-dashed border-primary-300 dark:border-primary-600 rounded-lg p-6 text-center space-y-4">
|
||||
{!stampBlocks && !showLanguagePicker && (
|
||||
<Button
|
||||
@@ -792,6 +801,7 @@ const UploadTeeterAndSignature = () => {
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
|
||||
@@ -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<string, unknown>): Company {
|
||||
const attrs = (dto.attributes as Record<string, unknown> | null) ?? {};
|
||||
return {
|
||||
@@ -32,9 +32,9 @@ function mapCompany(dto: Record<string, unknown>): 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,
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { api as client } from "../auth/http";
|
||||
import { unwrap } from "@/utils/endpoint";
|
||||
import type { ApiResponse } from "@/types/apiResponse";
|
||||
|
||||
const BASE = "/stamp-settings";
|
||||
|
||||
/** Company stamp/seal used on generated invoice/receipt PDFs. */
|
||||
export interface StampSettings {
|
||||
stampImageUrl: string | null;
|
||||
updatedById: string | null;
|
||||
updatedAt: string | null;
|
||||
}
|
||||
|
||||
export const stampSettingsService = {
|
||||
get: async (): Promise<StampSettings> => {
|
||||
const response = await client.get<ApiResponse<StampSettings>>(BASE);
|
||||
return unwrap(response.data);
|
||||
},
|
||||
|
||||
set: async (stampImageBase64: string): Promise<StampSettings> => {
|
||||
const response = await client.put<ApiResponse<StampSettings>>(BASE, {
|
||||
stampImageBase64,
|
||||
});
|
||||
return unwrap(response.data);
|
||||
},
|
||||
|
||||
clear: async (): Promise<StampSettings> => {
|
||||
const response = await client.delete<ApiResponse<StampSettings>>(BASE);
|
||||
return unwrap(response.data);
|
||||
},
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -211,14 +222,21 @@ export interface Company {
|
||||
fanNumber?: string | null;
|
||||
country: string;
|
||||
nationality?: CompanyNationality | null;
|
||||
/**
|
||||
* A co-operative union or farm: a TIN but no trade licence, so its
|
||||
* registration was typed rather than fetched from eTrade, there is no eTrade
|
||||
* manager to check the owner against, and it holds no freight-forwarder role.
|
||||
*/
|
||||
cooperative?: boolean;
|
||||
address?: string | null;
|
||||
phone?: string | null;
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user