fix(portal): accept a business licence the server already holds

A resumed onboarding asked for the per-role business licence again and refused
to submit until it was uploaded a second time, however many were already on
file. `getInfo` never populates `companyProfiles[].licenseFiles`, and the
wizard validates against exactly that — so every profile looked empty, while
`markOnboardingComplete` would have accepted the application as it stood.

Validation now reads `licenseProfiles[].uploaded` from the onboarding
requirements: the server's own verdict, already fetched by the step, and the
same source the API enforces on submit.

Reachable today by anyone who resumes onboarding after a licence upload, and by
every company that switches back to eTrade registration — which is where the
e2e suite hit it.
This commit is contained in:
Nathnael
2026-08-18 11:47:18 +00:00
parent 6469c7fa52
commit f62054d4a4
2 changed files with 23 additions and 1 deletions

View File

@@ -492,6 +492,15 @@ export default function OnboardingWizardDialog({
licenseFiles, licenseFiles,
onLicenseChange: setLicenseFiles, onLicenseChange: setLicenseFiles,
uploadedDocumentKeys, uploadedDocumentKeys,
// What the server says is already on file, per operational profile. The
// wizard's own `roleProfiles` cannot say: getInfo leaves `licenseFiles`
// empty, so a resumed wizard asked for a licence it had already been given
// and refused to submit until it was uploaded a second time.
uploadedLicenceProfileIds: (
requirementsQuery.data?.licenseProfiles ?? []
)
.filter((p) => p.uploaded)
.map((p) => p.profileId),
onUploadDocuments: handleUploadDocuments, onUploadDocuments: handleUploadDocuments,
// The company's single identity verification, and whose it is. Fayda is // The company's single identity verification, and whose it is. Fayda is
// mandatory for an Ethiopian company; a foreign one may instead type a // mandatory for an Ethiopian company; a foreign one may instead type a

View File

@@ -61,6 +61,7 @@ export default function CompanyProfileForm({
onLicenseChange, onLicenseChange,
submitError, submitError,
uploadedDocumentKeys, uploadedDocumentKeys,
uploadedLicenceProfileIds,
onUploadDocuments, onUploadDocuments,
identity: rawIdentity, identity: rawIdentity,
onIdentityChange, onIdentityChange,
@@ -96,6 +97,16 @@ export default function CompanyProfileForm({
submitError?: string | null; submitError?: string | null;
/** fileKeys whose company document is already uploaded server-side (resume). */ /** fileKeys whose company document is already uploaded server-side (resume). */
uploadedDocumentKeys?: string[]; uploadedDocumentKeys?: string[];
/**
* Profile ids whose business licence the server already holds.
*
* `roleProfiles.existingFiles` cannot answer this on a resumed wizard:
* `getInfo` does not populate `licenseFiles`, so every profile looks empty
* however many licences are on file. Taken from the onboarding requirements,
* which is the server's own verdict and what `markOnboardingComplete`
* enforces.
*/
uploadedLicenceProfileIds?: string[];
/** /**
* Auto-upload the currently-selected company documents (the Documents step's * Auto-upload the currently-selected company documents (the Documents step's
* "Continue" action). Resolves to an error message string on failure so the * "Continue" action). Resolves to an error message string on failure so the
@@ -688,7 +699,9 @@ export default function CompanyProfileForm({
if (cooperative) return errs; if (cooperative) return errs;
for (const p of roleProfiles ?? []) { for (const p of roleProfiles ?? []) {
const hasNew = (licenseFiles?.[p.id]?.length ?? 0) > 0; const hasNew = (licenseFiles?.[p.id]?.length ?? 0) > 0;
const hasExisting = p.existingFiles.length > 0; const hasExisting =
p.existingFiles.length > 0 ||
(uploadedLicenceProfileIds ?? []).includes(p.id);
if (!hasNew && !hasExisting) { if (!hasNew && !hasExisting) {
errs[p.id] = "Business license is required"; errs[p.id] = "Business license is required";
} }