mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 12:18:11 +00:00
feat(companies): onboard co-operative unions and farms
They hold a TIN but no business licence, so there is no eTrade record to look their registration up in. A checkbox on the first wizard step marks them, and everything that assumed a trade licence bends around it: - The company step replaces the eTrade lookup with typed registration details — name, region, zone, woreda, kebele, house number — required exactly because they are now on screen. applyEtradeSourcedFields skips the lookup rather than failing it, so what the customer sends is what is stored. - No freight-forwarder role. Forwarding is licensed work, so the option is not offered, and the API refuses it at start-onboarding and at every later role-add rather than letting approval fail on a document they cannot produce. - No per-role business-licence upload, client-side or in the completion gate. - Their own document set (company_onboarding_documents_cooperative) merges on top of the nationality one, admin-managed like every other set. Nationality wins a fileKey collision so no slot renders twice, and the DARS paper is not injected into it — the set it merges onto already carries one. - The owner is typed in full; with no eTrade manager on file the licence comparison reports "nothing to compare against", which backoffice now explains rather than leaving as a bare dash. Stored as an attributes flag, not a column: everything it changes is behavioural, and nothing queries or joins on it.
This commit is contained in:
@@ -822,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} />
|
||||
@@ -945,6 +955,11 @@ export default function CustomerDetailPage() {
|
||||
>
|
||||
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.
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user