fix: profile id and licence to registration

This commit is contained in:
Nathnael
2026-07-09 06:40:45 +00:00
parent 38577cdd26
commit aa656eed7e
5 changed files with 68 additions and 43 deletions

View File

@@ -1,4 +1,4 @@
import { Injectable } from "@nestjs/common";
import { Injectable, InternalServerErrorException } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { BaseRepository } from "@edr/api-common";
@@ -20,6 +20,11 @@ const PREFIX_MAP: Record<ProfileType, string> = {
[ProfileType.transporter]: "TR",
};
const SERIES_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
/** Numbers per series letter: A00001..A99999, then B00001. */
const SERIES_SIZE = 99_999;
@Injectable()
export class CompanyProfileRepository extends BaseRepository<CompanyProfile> {
constructor(
@@ -38,9 +43,20 @@ export class CompanyProfileRepository extends BaseRepository<CompanyProfile> {
const result = await this.repository.query(
`SELECT nextval('${seqName}') AS next_id`,
);
const nextId = result[0].next_id as number;
const nextId = Number(result[0].next_id);
const offset = nextId - 1;
const seriesIndex = Math.floor(offset / SERIES_SIZE);
if (seriesIndex >= SERIES_LETTERS.length) {
throw new InternalServerErrorException(
`Company profile reference series exhausted for type "${type}"`,
);
}
const letter = SERIES_LETTERS[seriesIndex];
const number = (offset % SERIES_SIZE) + 1;
const prefix = PREFIX_MAP[type];
return `${prefix}-${String(nextId).padStart(5, "0")}`;
return `${prefix}-${letter}${String(number).padStart(5, "0")}`;
}
async findByCompanyId(companyId: string): Promise<CompanyProfile[]> {

View File

@@ -60,7 +60,7 @@ export class CompanyProfile extends BaseEntity {
type!: ProfileType;
/**
* Official profile reference (e.g. "EX-00001"). Minted only when the profile
* Official profile reference (e.g. "EX-A00001"). Minted only when the profile
* is approved (status → Active); pending/unapproved profiles carry NULL.
* The unique index tolerates this because Postgres treats NULLs as distinct.
* API responses surface it as "" when absent — see ResponseCompanyProfileDto.

View File

@@ -33,8 +33,9 @@ const ETHIOPIAN_ONBOARDING_FIELDS: OnboardingField[] = [
},
{
fileKey: "commercial_license",
fileLabel: "Commercial License",
helpText: "Verified against the government trade system during registration.",
fileLabel: "Commercial Registration",
helpText:
"Verified against the government trade system during registration.",
isRequired: true,
isMultiple: false,
maxFiles: 1,
@@ -108,7 +109,8 @@ const LEGACY_ONBOARDING_FIELDS: OnboardingField[] = [
{
fileKey: "business_license",
fileLabel: "Business License / Trade License",
helpText: "Verified against the government trade system during registration.",
helpText:
"Verified against the government trade system during registration.",
isRequired: true,
isMultiple: false,
maxFiles: 1,
@@ -489,9 +491,14 @@ const SELF_CLEARANCE_SETTINGS: OnboardingDocumentSetting[] = [
const CONTRACT_INTAKE_ENTITY = "contract_intake";
const CONTRACT_INTAKE_FIELDS: OnboardingField[] = [
clearanceField("commercial_framework", "Commercial Framework / Agreement", 1, {
required: false,
}),
clearanceField(
"commercial_framework",
"Commercial Framework / Agreement",
1,
{
required: false,
},
),
clearanceField("onboarding_attachment", "Onboarding Attachment", 2, {
required: false,
}),
@@ -542,7 +549,7 @@ const DRIVER_DOCUMENT_SETTINGS: OnboardingDocumentSetting[] = [
export class FileUploadSettingsSeeder {
private readonly logger = new Logger(FileUploadSettingsSeeder.name);
constructor(private readonly dataSource: DataSource) {}
constructor(private readonly dataSource: DataSource) { }
async run() {
await this.dataSource.transaction(async (manager) => {
@@ -552,35 +559,35 @@ export class FileUploadSettingsSeeder {
const allSettings: Array<
OnboardingDocumentSetting & { description: string }
> = [
...COMPANY_ONBOARDING_DOCUMENTS.map((s) => ({
...s,
description: COMPANY_ONBOARDING_DESCRIPTION,
})),
...CLEARANCE_DOCUMENT_SETTINGS.map((s) => ({
...s,
description: CLEARANCE_DESCRIPTION,
})),
...CONTRACT_CLEARANCE_SETTINGS.map((s) => ({
...s,
description:
"Pre-booking clearance documents collected on the contract (Path B), by operation and freight type.",
})),
...SELF_CLEARANCE_SETTINGS.map((s) => ({
...s,
description:
"Customer self-clearance documents (Path A, no EDR customs service), reviewed by Operations.",
})),
...CONTRACT_INTAKE_SETTINGS.map((s) => ({
...s,
description:
"Commercial/framework documents attached at contract submission.",
})),
...DRIVER_DOCUMENT_SETTINGS.map((s) => ({
...s,
description:
"Documents uploaded against a driver profile (license, ID, contracts, etc.).",
})),
];
...COMPANY_ONBOARDING_DOCUMENTS.map((s) => ({
...s,
description: COMPANY_ONBOARDING_DESCRIPTION,
})),
...CLEARANCE_DOCUMENT_SETTINGS.map((s) => ({
...s,
description: CLEARANCE_DESCRIPTION,
})),
...CONTRACT_CLEARANCE_SETTINGS.map((s) => ({
...s,
description:
"Pre-booking clearance documents collected on the contract (Path B), by operation and freight type.",
})),
...SELF_CLEARANCE_SETTINGS.map((s) => ({
...s,
description:
"Customer self-clearance documents (Path A, no EDR customs service), reviewed by Operations.",
})),
...CONTRACT_INTAKE_SETTINGS.map((s) => ({
...s,
description:
"Commercial/framework documents attached at contract submission.",
})),
...DRIVER_DOCUMENT_SETTINGS.map((s) => ({
...s,
description:
"Documents uploaded against a driver profile (license, ID, contracts, etc.).",
})),
];
for (const documentSetting of allSettings) {
await settingRepository.upsert(
@@ -601,7 +608,9 @@ export class FileUploadSettingsSeeder {
});
if (!setting) {
throw new Error(`file_upload_setting_seed_failed:${documentSetting.code}`);
throw new Error(
`file_upload_setting_seed_failed:${documentSetting.code}`,
);
}
await fieldRepository.delete({ settingId: setting.id });

View File

@@ -10,7 +10,7 @@ const LABEL_BY_CODE = new Map<string, string>([
...REQUIRED_DOC_FIELDS.map((d) => [d.key, d.label] as const),
// Company onboarding document codes (see file-upload-settings seeder).
["tin_certificate", "TIN Certificate"],
["commercial_license", "Commercial License"],
["commercial_license", "Commercial Registration"],
["business_license", "Business License / Trade License"],
["investment_license", "Investment License"],
["national_id", "National ID"],

View File

@@ -26,7 +26,7 @@ export default function NationalitySelect({
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
<RoleCard
label="Ethiopian Company"
description="Registered in Ethiopia. You'll provide a TIN certificate, commercial license and national ID."
description="Registered in Ethiopia. You'll provide a TIN certificate, commercial registration and national ID."
icon={<MapPin size={22} />}
selected={value === "ethiopian"}
onClick={() => onChange("ethiopian")}