fix: an issue

This commit is contained in:
Nathnael
2026-08-12 07:11:27 +00:00
parent a603807e8e
commit d4767e0d06
16 changed files with 206 additions and 177 deletions

View File

@@ -256,11 +256,15 @@ export class CompaniesService {
},
];
/** The nationality-based document setting code for a company. */
private documentSettingCodeFor(
nationality: CompanyNationality | null | undefined,
): string {
return nationality === CompanyNationality.Foreign
/**
* The document setting code for a company: one of three mutually exclusive
* sets. A co-operative union or farm resolves to its own set regardless of
* nationality — it holds no business licence, so it owes a different list of
* papers rather than the nationality list plus extras.
*/
private documentSettingCodeFor(company: Company): string {
if (isCooperative(company)) return COOPERATIVE_ONBOARDING_CODE;
return company.nationality === CompanyNationality.Foreign
? "company_onboarding_documents_foreign"
: "company_onboarding_documents_ethiopian";
}
@@ -387,13 +391,16 @@ export class CompaniesService {
const current = needsCompany
? await this.companiesRepo.findById(companyId)
: null;
this.assertRolesAllowedForCooperative(
cooperative ?? isCooperative(current),
roles,
);
const isCoop = cooperative ?? isCooperative(current);
this.assertRolesAllowedForCooperative(isCoop, roles);
this.assertNationalityAllowedForCooperative(isCoop, nationality);
await this.syncCompanyProfiles(companyId, companyType, roles);
const updates: Partial<Company> = {};
if (nationality) updates.nationality = nationality;
// Ticking the box on a draft that was saved as foreign has to correct the
// stored nationality too, or the company keeps resolving to the foreign
// document set.
if (isCoop) updates.nationality = CompanyNationality.Ethiopian;
if (cooperative !== undefined) {
updates.attributes = {
...(current?.attributes ?? {}),
@@ -407,6 +414,7 @@ export class CompaniesService {
}
this.assertRolesAllowedForCooperative(cooperative === true, roles);
this.assertNationalityAllowedForCooperative(cooperative === true, nationality);
const allowedTypes = this.getProfileTypeForCompanyType(companyType);
const chosenTypes = roles.filter((t) => allowedTypes.includes(t));
@@ -458,6 +466,24 @@ export class CompaniesService {
}
}
/**
* A co-operative union or farm is registered in Ethiopia by the co-operative
* promotion agency, so it is always an Ethiopian company — "foreign" is not a
* combination that exists, and allowing it would resolve the company to a
* document set built around an investment licence it cannot hold.
*/
private assertNationalityAllowedForCooperative(
cooperative: boolean,
nationality: CompanyNationality | undefined,
): void {
if (!cooperative) return;
if (nationality === CompanyNationality.Foreign) {
throw new BadRequestException(
"A co-operative union or farm is registered in Ethiopia — it cannot onboard as a foreign company.",
);
}
}
/**
* Reconcile the company's operational profiles with the roles the user has
* selected: create the missing ones, drop the ones they deselected.
@@ -1252,7 +1278,7 @@ export class CompaniesService {
uploaded: FileRecord[],
): Promise<CompanyRevisionChange[]> {
const setting = await this.fileUploadSettingsService
.getByCode(this.documentSettingCodeFor(company.nationality))
.getByCode(this.documentSettingCodeFor(company))
.catch(() => null);
const fields = setting?.fields ?? [];
const singleFileCodes = new Set(
@@ -2036,35 +2062,20 @@ export class CompaniesService {
.filter((f) => !f.get(company))
.map((f) => ({ key: f.key, label: f.label }));
// 2. Nationality-based company documents + which are already uploaded. A
// co-operative adds its own set on top: it provides everything its
// nationality demands, plus the papers standing in for the business licence
// it does not hold.
// 2. Company documents + which are already uploaded. One set applies: the
// company's nationality set, or the co-operative set in its place — a union
// or farm holds no business licence, so it owes its own list rather than the
// nationality list plus extras.
const cooperative = isCooperative(company);
const documentSettingCode = this.documentSettingCodeFor(
company.nationality,
);
const [setting, coopSetting, uploadedFiles] = await Promise.all([
const documentSettingCode = this.documentSettingCodeFor(company);
const [setting, uploadedFiles] = await Promise.all([
this.fileUploadSettingsService
.getByCode(documentSettingCode)
.catch(() => null),
cooperative
? this.fileUploadSettingsService
.getByCode(COOPERATIVE_ONBOARDING_CODE)
.catch(() => null)
: Promise.resolve(null),
this.filesService.findByResource(company.id, "companies"),
]);
const uploadedCodes = new Set(uploadedFiles.map((f) => f.code));
// The co-op set is admin-managed and could name a fileKey the nationality
// set already carries; the nationality field wins so the same slot is never
// rendered (or required) twice.
const baseFields = setting?.fields ?? [];
const baseKeys = new Set(baseFields.map((f) => f.fileKey));
const documents = [
...baseFields,
...(coopSetting?.fields ?? []).filter((f) => !baseKeys.has(f.fileKey)),
]
const documents = (setting?.fields ?? [])
.slice()
.sort((a, b) => a.displayOrder - b.displayOrder)
.map((f) => ({
@@ -2203,9 +2214,6 @@ export class CompaniesService {
return new OnboardingRequirementsResponseDto({
documentSettingCode,
cooperativeDocumentSettingCode: cooperative
? COOPERATIVE_ONBOARDING_CODE
: null,
nationality: company.nationality ?? CompanyNationality.Ethiopian,
cooperative,
companyInfo: {

View File

@@ -66,15 +66,11 @@ export interface OnboardingPoaState {
}
export class OnboardingRequirementsResponseDto {
/** Resolved document setting code (by nationality) the docs were drawn from. */
documentSettingCode: string;
/**
* The co-operative document set, merged on top of the nationality one — null
* for every other company. `documents` below already carries the merged
* result; this is only so the portal can fetch the same extra fields when it
* renders the pickers from the file-settings endpoint.
* Resolved document setting code the docs were drawn from: the company's
* nationality set, or the co-operative set in its place.
*/
cooperativeDocumentSettingCode: string | null;
documentSettingCode: string;
nationality: string;
/**
* The company trades as a co-operative: no business licence, so no eTrade
@@ -118,7 +114,6 @@ export class OnboardingRequirementsResponseDto {
constructor(init: Omit<OnboardingRequirementsResponseDto, never>) {
this.documentSettingCode = init.documentSettingCode;
this.cooperativeDocumentSettingCode = init.cooperativeDocumentSettingCode;
this.nationality = init.nationality;
this.cooperative = init.cooperative;
this.companyInfo = init.companyInfo;

View File

@@ -17,7 +17,6 @@ import {
} from "./interfaces/file-upload-settings.repository.interface";
import {
COMPANY_ONBOARDING_CODE_PREFIX,
COOPERATIVE_ONBOARDING_CODE,
POA_DELEGATION_FILE_KEY,
poaDelegationField,
} from "./poa-delegation.constants";
@@ -57,10 +56,6 @@ export class FileUploadSettingsService {
*/
private withPoaDelegationField(setting: FileUploadSetting): FileUploadSetting {
if (!setting.code.startsWith(COMPANY_ONBOARDING_CODE_PREFIX)) return setting;
// The co-operative set is merged ON TOP of a nationality set that already
// carries the paper; injecting it here too would hand the portal the same
// slot twice.
if (setting.code === COOPERATIVE_ONBOARDING_CODE) return setting;
const fields = setting.fields ?? [];
if (fields.some((f) => f.fileKey === POA_DELEGATION_FILE_KEY)) return setting;

View File

@@ -27,10 +27,11 @@ export const POA_DELEGATION_LABEL = "DARS Delegation Paper";
export const COMPANY_ONBOARDING_CODE_PREFIX = "company_onboarding_documents_";
/**
* The co-operative onboarding set. Unlike the nationality sets it is ADDITIVE —
* merged on top of the company's `_ethiopian`/`_foreign` set rather than
* replacing it — which is why the delegation paper is not injected into it: the
* set it is merged onto already carries one.
* The co-operative onboarding set — the third alternative to `_ethiopian` and
* `_foreign`, not an addition to them: a union or farm resolves to this set
* INSTEAD of its nationality's, because it holds no business licence and so
* owes a different list of papers. The delegation paper is injected into it
* like any other company onboarding set.
*/
export const COOPERATIVE_ONBOARDING_CODE = `${COMPANY_ONBOARDING_CODE_PREFIX}cooperative`;

View File

@@ -155,16 +155,27 @@ const FOREIGN_ONBOARDING_FIELDS: OnboardingField[] = [
// ];
/**
* Extra documents a co-operative union or farm provides, merged on top of its
* nationality set. It has a TIN but no business licence, so the papers that
* evidence the co-operative itself stand in for the trade licence every other
* company uploads.
* Documents required from a co-operative union or farm — the third alternative
* to the two nationality sets, not an addition to them. A co-op is always
* registered in Ethiopia and has a TIN but no business licence, so its
* registration certificate stands in for the commercial registration every
* other Ethiopian company uploads.
*
* Only the registration certificate is seeded, and the set is admin-managed
* like every other onboarding set — what these members must actually produce
* is a backoffice decision, edited in the file-settings editor.
* Admin-managed like every other onboarding set: what these members must
* actually produce is a backoffice decision, edited in the file-settings editor.
*/
const COOPERATIVE_ONBOARDING_FIELDS: OnboardingField[] = [
{
fileKey: "tin_certificate",
fileLabel: "TIN Certificate",
helpText: "Verified against the TIN registry during registration.",
isRequired: true,
isMultiple: false,
maxFiles: 1,
allowedExtensions: DOC_EXTENSIONS,
maxSizeMb: 50,
displayOrder: 1,
},
{
fileKey: "cooperative_registration_certificate",
fileLabel: "Co-operative Union / Farm Registration Certificate",
@@ -175,8 +186,20 @@ const COOPERATIVE_ONBOARDING_FIELDS: OnboardingField[] = [
maxFiles: 1,
allowedExtensions: DOC_EXTENSIONS,
maxSizeMb: 50,
displayOrder: 1,
displayOrder: 2,
},
{
fileKey: "national_id",
fileLabel: "National ID",
helpText: "Verified against the National ID API during registration.",
isRequired: true,
isMultiple: false,
maxFiles: 1,
allowedExtensions: DOC_EXTENSIONS,
maxSizeMb: 50,
displayOrder: 3,
},
poaDelegationDefault(4),
];
interface OnboardingDocumentSetting {
@@ -201,11 +224,11 @@ const COMPANY_ONBOARDING_DOCUMENTS: OnboardingDocumentSetting[] = [
entity: "customer",
fields: FOREIGN_ONBOARDING_FIELDS,
},
// Additive, not a nationality of its own: a union or farm still uploads
// everything its nationality set demands, and these on top.
// The third set: a union or farm resolves here INSTEAD of a nationality set
// (it is always Ethiopian, and holds no business licence).
{
code: "company_onboarding_documents_cooperative",
label: "Co-operative union / farm onboarding documents (additional)",
label: "Co-operative union / farm onboarding documents",
entity: "customer",
fields: COOPERATIVE_ONBOARDING_FIELDS,
},

View File

@@ -39,6 +39,7 @@ import type {
} from "@/services/companies.service";
import { companiesService } from "@/services/companies.service";
import type { UpdateProfilePayload } from "@/types/profile";
import { documentSettingCode } from "@/utils/documentSettingCode";
import { extractApiError } from "@/utils/result";
/** Form steps rendered by CompanyProfileForm. */
@@ -116,13 +117,6 @@ function companyTypeForRoles(_roles: string[]): string {
return "customer";
}
/** Document upload setting code per company nationality. */
function documentSettingCode(nationality: CompanyNationality): string {
return nationality === "foreign"
? "company_onboarding_documents_foreign"
: "company_onboarding_documents_ethiopian";
}
/**
* First-run onboarding wizard with a "draft-first" flow: picking the role(s)
* immediately creates a draft company + profile on the backend, so every
@@ -168,11 +162,15 @@ export default function OnboardingWizardDialog({
const [cooperative, setCooperative] = useState<boolean>(
company?.company?.attributes?.cooperative === true,
);
// Ticking the box drops a role the company can no longer hold, rather than
// letting Continue fail on a selection the API refuses.
// Ticking the box drops the selections the company can no longer hold, rather
// than letting Continue fail on ones the API refuses: a co-op cannot forward
// freight, and is registered in Ethiopia so it is never foreign.
const handleCooperativeChange = useCallback((checked: boolean) => {
setCooperative(checked);
if (checked) setRoles((prev) => prev.filter((r) => r !== "freight_forwarder"));
if (checked) {
setRoles((prev) => prev.filter((r) => r !== "freight_forwarder"));
setNationality((prev) => (prev === "foreign" ? "ethiopian" : prev));
}
}, []);
const [documentFiles, setDocumentFiles] = useState<
Record<string, File | File[] | null>
@@ -419,7 +417,7 @@ export default function OnboardingWizardDialog({
// after the draft — and thus the requirements — exist).
const resolvedDocumentSettingCode =
requirementsQuery.data?.documentSettingCode ??
documentSettingCode(effectiveNationality);
documentSettingCode(effectiveNationality, cooperative);
// Server-confirmed document state, used both to badge already-uploaded fields
// and to keep a refreshed resume from over-shooting the documents step.
@@ -480,8 +478,6 @@ export default function OnboardingWizardDialog({
// startOnboarding has persisted it, and the form's whole company step
// branches on it.
cooperative: requirementsQuery.data?.cooperative ?? cooperative,
extraDocumentSettingCode:
requirementsQuery.data?.cooperativeDocumentSettingCode ?? null,
// A freight forwarder cannot answer the power-of-attorney question — the
// API forces "yes" — so the step offers no way to change it.
declarationLocked: requirementsQuery.data?.poa?.locked ?? false,
@@ -551,6 +547,10 @@ export default function OnboardingWizardDialog({
value={nationality}
onChange={setNationality}
embedded
// A co-op is registered in Ethiopia by the co-operative
// promotion agency — foreign is not on offer rather than
// refused later.
excludeForeign={cooperative}
/>
{/* A co-operative union or farm registers on a TIN alone. It
changes what the next step asks for (typed registration, no

View File

@@ -66,7 +66,6 @@ export default function CompanyProfileForm({
onIdentityChange,
cooperative = false,
declarationLocked = false,
extraDocumentSettingCode,
}: {
documentSettingCode: string;
documentFiles?: Record<string, File | File[] | null>;
@@ -115,8 +114,8 @@ export default function CompanyProfileForm({
/**
* The company trades as a co-operative: a TIN but no business licence, so the
* eTrade lookup is replaced by typed registration details, the per-role
* licence upload is not owed, and its own document set applies on top of the
* nationality one.
* licence upload is not owed, and its own document set applies instead of the
* nationality one (resolved by the caller into `documentSettingCode`).
*/
cooperative?: boolean;
/**
@@ -124,8 +123,6 @@ export default function CompanyProfileForm({
* answer is forced to "yes" and cannot be changed here.
*/
declarationLocked?: boolean;
/** Additional document set merged in (the co-operative one), if any. */
extraDocumentSettingCode?: string | null;
}) {
// A Fayda claim carries the phone as the national registry holds it, which is
// often a local number the form's E.164 validation (and the API's
@@ -206,36 +203,15 @@ export default function CompanyProfileForm({
const documentFiles = controlledFiles ?? internalFiles;
const setDocumentFiles = onDocumentFilesChange ?? setInternalFiles;
const { data: nationalitySetting, isLoading: loadingDocuments } = useQuery(
// One set applies: the company's nationality set, or the co-operative one in
// its place — the caller resolves which (the API resolves the same way when
// it decides what is outstanding).
const { data: uploadSetting, isLoading: loadingDocuments } = useQuery(
api.fileUploadSettings.getByCode.queryOptions({
input: { code: documentSettingCode },
refetchOnMount: false,
}),
);
// A co-operative's own documents come as a second, additive set — it uploads
// everything its nationality demands, plus the papers standing in for the
// business licence it does not hold. The API merges the same two sets when it
// decides what is outstanding.
const { data: extraSetting } = useQuery(
api.fileUploadSettings.getByCode.queryOptions({
input: { code: extraDocumentSettingCode ?? "" },
enabled: Boolean(extraDocumentSettingCode),
refetchOnMount: false,
}),
);
const uploadSetting = useMemo(() => {
if (!nationalitySetting) return nationalitySetting;
if (!extraSetting?.fields?.length) return nationalitySetting;
// Nationality wins a fileKey collision, so a slot is never rendered twice.
const seen = new Set(nationalitySetting.fields.map((f) => f.fileKey));
return {
...nationalitySetting,
fields: [
...nationalitySetting.fields,
...extraSetting.fields.filter((f) => !seen.has(f.fileKey)),
],
};
}, [nationalitySetting, extraSetting]);
// Which fields the current step renders an input for and therefore requires.
// Filled in further down (it depends on values this form owns), and read at

View File

@@ -6,6 +6,7 @@ import { type UseFormReturn } from "react-hook-form";
import useAuth from "@/hooks/useAuth";
import { api } from "@/services/api";
import { documentSettingCode } from "@/utils/documentSettingCode";
import {
operationToProfileType,
type BookingDocuments,
@@ -20,13 +21,6 @@ type BookingForm = UseFormReturn<
BookingFormValues
>;
/** Onboarding document setting code for the company's nationality. */
function documentSettingCode(nationality: string | null | undefined): string {
return nationality === "foreign"
? "company_onboarding_documents_foreign"
: "company_onboarding_documents_ethiopian";
}
function formatSize(bytes?: number): string {
if (!bytes) return "";
if (bytes < 1024) return `${bytes} B`;
@@ -48,14 +42,17 @@ function formatSize(bytes?: number): string {
export function StepDocuments({ form }: { form: BookingForm }) {
const auth = useAuth();
const nationality = auth.company?.company?.nationality as
| string
| null
| undefined;
const company = auth.company?.company;
const nationality = company?.nationality as string | null | undefined;
const docSettingQuery = useQuery(
api.fileUploadSettings.getByCode.queryOptions({
input: { code: documentSettingCode(nationality) },
input: {
code: documentSettingCode(
nationality,
company?.attributes?.cooperative === true,
),
},
}),
);

View File

@@ -2,15 +2,9 @@ import { useQuery } from "@tanstack/react-query";
import useAuth from "@/hooks/useAuth";
import { api } from "@/services/api";
import { documentSettingCode } from "@/utils/documentSettingCode";
/** Onboarding document setting code for the company's nationality. */
export function documentSettingCode(
nationality: string | null | undefined,
): string {
return nationality === "foreign"
? "company_onboarding_documents_foreign"
: "company_onboarding_documents_ethiopian";
}
export { documentSettingCode };
/**
* Fetches the FileUploadSetting that describes the documents a booking requires
@@ -22,14 +16,17 @@ export function documentSettingCode(
*/
export function useBookingDocumentSetting() {
const auth = useAuth();
const nationality = auth.company?.company?.nationality as
| string
| null
| undefined;
const company = auth.company?.company;
const nationality = company?.nationality as string | null | undefined;
return useQuery(
api.fileUploadSettings.getByCode.queryOptions({
input: { code: documentSettingCode(nationality) },
input: {
code: documentSettingCode(
nationality,
company?.attributes?.cooperative === true,
),
},
}),
);
}

View File

@@ -7,6 +7,7 @@ import type { Freight } from "@edr/types";
import { useQuery } from "@tanstack/react-query";
import useAuth from "@/hooks/useAuth";
import { api } from "@/services/api";
import { documentSettingCode } from "@/utils/documentSettingCode";
import type { CompanyDocument } from "@/services/companies.service";
import { downloadStoredFile } from "@/services/files.service";
import { labelForDocCode } from "@/pages/bookings/resubmit/resubmitDocs";
@@ -15,13 +16,6 @@ import { BORDER, GREEN, INK } from "../contract-ui";
type DocumentsValue = Record<string, File | File[] | null>;
type ContractFile = NonNullable<Freight.IContract["files"]>[number];
/** Onboarding document setting code for the company's nationality. */
function documentSettingCode(nationality: string | null | undefined): string {
return nationality === "foreign"
? "company_onboarding_documents_foreign"
: "company_onboarding_documents_ethiopian";
}
function hasFile(value: File | File[] | null | undefined): boolean {
if (!value) return false;
return Array.isArray(value) ? value.length > 0 : true;
@@ -95,13 +89,16 @@ export function ContractDocsEditor({
}) {
const auth = useAuth();
const nationality = auth.company?.company?.nationality as
| string
| null
| undefined;
const company = auth.company?.company;
const nationality = company?.nationality as string | null | undefined;
const settingQuery = useQuery(
api.fileUploadSettings.getByCode.queryOptions({
input: { code: documentSettingCode(nationality) },
input: {
code: documentSettingCode(
nationality,
company?.attributes?.cooperative === true,
),
},
}),
);

View File

@@ -7,6 +7,7 @@ import { type UseFormReturn } from "react-hook-form";
import useAuth from "@/hooks/useAuth";
import { api } from "@/services/api";
import { documentSettingCode } from "@/utils/documentSettingCode";
import {
type ContractDocuments,
type ContractFormInputValues,
@@ -21,12 +22,6 @@ type ContractForm = UseFormReturn<
ContractFormValues
>;
function documentSettingCode(nationality: string | null | undefined): string {
return nationality === "foreign"
? "company_onboarding_documents_foreign"
: "company_onboarding_documents_ethiopian";
}
function formatSize(bytes?: number): string {
if (!bytes) return "";
if (bytes < 1024) return `${bytes} B`;
@@ -61,14 +56,17 @@ export function StepDocuments({
const auth = useAuth();
const [errors, setErrors] = useState<Record<string, string>>({});
const nationality = auth.company?.company?.nationality as
| string
| null
| undefined;
const company = auth.company?.company;
const nationality = company?.nationality as string | null | undefined;
const docSettingQuery = useQuery(
api.fileUploadSettings.getByCode.queryOptions({
input: { code: documentSettingCode(nationality) },
input: {
code: documentSettingCode(
nationality,
company?.attributes?.cooperative === true,
),
},
}),
);

View File

@@ -9,6 +9,8 @@ interface NationalitySelectProps {
onChange: (next: CompanyNationality) => void;
/** Render only the option grid — the wizard supplies its own header/card. */
embedded?: boolean;
/** Hide the foreign option (a co-operative union or farm is always Ethiopian). */
excludeForeign?: boolean;
}
/**
@@ -21,9 +23,10 @@ export default function NationalitySelect({
value,
onChange,
embedded = false,
excludeForeign = false,
}: NationalitySelectProps) {
const grid = (
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
<SimpleGrid cols={{ base: 1, sm: excludeForeign ? 1 : 2 }} spacing="md">
<RoleCard
label="Ethiopian Company"
description="Registered in Ethiopia. You'll provide a TIN certificate, commercial registration and national ID."
@@ -31,13 +34,15 @@ export default function NationalitySelect({
selected={value === "ethiopian"}
onClick={() => onChange("ethiopian")}
/>
<RoleCard
label="Foreign Company"
description="Registered abroad. You'll provide a passport and investment license."
icon={<Globe2 size={22} />}
selected={value === "foreign"}
onClick={() => onChange("foreign")}
/>
{!excludeForeign && (
<RoleCard
label="Foreign Company"
description="Registered abroad. You'll provide a passport and investment license."
icon={<Globe2 size={22} />}
selected={value === "foreign"}
onClick={() => onChange("foreign")}
/>
)}
</SimpleGrid>
);

View File

@@ -6,6 +6,7 @@ import {
type LicenseFileStatus,
} from "@/services/companies.service";
import { getMinFiles } from "@/types/fileUploadSettings";
import { documentSettingCode } from "@/utils/documentSettingCode";
import type { ProfileResponse } from "@/types/profile";
import {
SmartFileInput,
@@ -57,14 +58,8 @@ interface TabDocumentsProps {
onContinue?: () => void;
}
function documentSettingCode(nationality: string | null | undefined): string {
return nationality === "foreign"
? "company_onboarding_documents_foreign"
: "company_onboarding_documents_ethiopian";
}
/**
* The DARS delegation paper ships in the same nationality document set, but it is
* The DARS delegation paper ships in the same document set, but it is
* edited on the Power of Attorney tab (where it is staged for review alongside
* the PoA details), so it is excluded from this tab's uploader.
*/
@@ -83,7 +78,9 @@ export default function TabDocuments({
const docSettingQuery = useQuery(
api.fileUploadSettings.getByCode.queryOptions({
input: { code: documentSettingCode(profile.nationality) },
input: {
code: documentSettingCode(profile.nationality, profile.cooperative),
},
}),
);

View File

@@ -183,14 +183,8 @@ export interface OnboardingPoaState {
* outstanding, so the client never hardcodes required fields or document sets.
*/
export interface OnboardingRequirements {
/** The set the docs came from: the nationality one, or the co-operative one. */
documentSettingCode: string;
/**
* Extra document set merged on top of the nationality one for a co-operative,
* null otherwise. `documents` already carries the merged list; this is only
* so the pickers, which render from the file-settings endpoint, can fetch the
* same extra fields.
*/
cooperativeDocumentSettingCode: string | null;
nationality: string;
/** No business licence: registration typed by hand, no eTrade lookup. */
cooperative: boolean;

View File

@@ -0,0 +1,28 @@
import { describe, expect, it } from "vitest";
import { documentSettingCode } from "./documentSettingCode";
describe("documentSettingCode", () => {
it("resolves the nationality set", () => {
expect(documentSettingCode("ethiopian")).toBe(
"company_onboarding_documents_ethiopian",
);
expect(documentSettingCode("foreign")).toBe(
"company_onboarding_documents_foreign",
);
expect(documentSettingCode(null)).toBe(
"company_onboarding_documents_ethiopian",
);
});
it("replaces the nationality set for a co-operative", () => {
expect(documentSettingCode("ethiopian", true)).toBe(
"company_onboarding_documents_cooperative",
);
// A co-op is never foreign, but a stale flag must not fall back to the
// foreign set — the co-op answer wins.
expect(documentSettingCode("foreign", true)).toBe(
"company_onboarding_documents_cooperative",
);
});
});

View File

@@ -0,0 +1,18 @@
/**
* The company document set a company resolves to — one of three, never a
* combination: a co-operative union or farm uploads its own papers INSTEAD of
* its nationality's (it holds no business licence), and is always Ethiopian.
*
* Mirrors `CompaniesService.documentSettingCodeFor` on the API; prefer the
* server-resolved `documentSettingCode` from onboarding requirements where one
* is available, and use this where only the company is at hand.
*/
export function documentSettingCode(
nationality: string | null | undefined,
cooperative?: boolean | null,
): string {
if (cooperative) return "company_onboarding_documents_cooperative";
return nationality === "foreign"
? "company_onboarding_documents_foreign"
: "company_onboarding_documents_ethiopian";
}