From f7bbb03cc1c8642ab48a267bbbe534c780a8f57c Mon Sep 17 00:00:00 2001 From: Nathnael Date: Wed, 24 Jun 2026 13:22:20 +0000 Subject: [PATCH] fix: make the reference generate on apporaval --- ...002-MakeCompanyProfileReferenceNullable.ts | 31 ++++++++++++++++ .../modules/companies/companies.service.ts | 35 +++++++++++-------- .../companies/dto/response-company.dto.ts | 2 +- .../entities/company-profile.entity.ts | 11 ++++-- 4 files changed, 61 insertions(+), 18 deletions(-) create mode 100644 apps/edr-freight-api/src/migrations/1810000000002-MakeCompanyProfileReferenceNullable.ts diff --git a/apps/edr-freight-api/src/migrations/1810000000002-MakeCompanyProfileReferenceNullable.ts b/apps/edr-freight-api/src/migrations/1810000000002-MakeCompanyProfileReferenceNullable.ts new file mode 100644 index 000000000..bc1765cfe --- /dev/null +++ b/apps/edr-freight-api/src/migrations/1810000000002-MakeCompanyProfileReferenceNullable.ts @@ -0,0 +1,31 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +/** + * Company-profile references are now minted only when a profile is approved + * (status → Active); pending profiles carry NULL. Drop the NOT NULL constraint + * on freight.company_profiles.reference. The existing unique index is kept — + * Postgres treats NULLs as distinct, so multiple pending (NULL) profiles don't + * collide. + */ +export class MakeCompanyProfileReferenceNullable1810000000002 + implements MigrationInterface +{ + name = "MakeCompanyProfileReferenceNullable1810000000002"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "freight"."company_profiles" ALTER COLUMN "reference" DROP NOT NULL`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + // Reinstating NOT NULL requires every row to have a reference; any pending + // (NULL) profiles get a placeholder so the constraint can be re-applied. + await queryRunner.query( + `UPDATE "freight"."company_profiles" SET "reference" = 'PENDING-' || left(replace("id"::text, '-', ''), 12) WHERE "reference" IS NULL`, + ); + await queryRunner.query( + `ALTER TABLE "freight"."company_profiles" ALTER COLUMN "reference" SET NOT NULL`, + ); + } +} diff --git a/apps/edr-freight-api/src/modules/companies/companies.service.ts b/apps/edr-freight-api/src/modules/companies/companies.service.ts index b145f354c..071161650 100644 --- a/apps/edr-freight-api/src/modules/companies/companies.service.ts +++ b/apps/edr-freight-api/src/modules/companies/companies.service.ts @@ -193,15 +193,13 @@ export class CompaniesService { input.type, ); if (existing) continue; - const reference = await this.companyProfilesRepo.generateReference( - input.type, - ); + // No reference yet — these profiles await backoffice approval, which + // is when the reference is minted (see setCompanyProfileStatus). await this.companyProfilesRepo.create({ companyId: company.id, type: input.type, - reference, businessLicense: input.businessLicense ?? null, - status: ProfileStatus.Active, + status: ProfileStatus.Pending, }); } company.companyProfiles = await this.companyProfilesRepo.findByCompanyId( @@ -310,12 +308,11 @@ export class CompaniesService { type, ); if (existing) continue; - const reference = await this.companyProfilesRepo.generateReference(type); + // No reference yet — minted on backoffice approval (setCompanyProfileStatus). await this.companyProfilesRepo.create({ companyId, type, - reference, - status: ProfileStatus.Active, + status: ProfileStatus.Pending, }); } } @@ -680,10 +677,20 @@ export class CompaniesService { profileId: string, status: ProfileStatus, ): Promise { - const updated = await this.companyProfilesRepo.updateStatus( - profileId, - status, - ); + const existing = await this.companyProfilesRepo.findById(profileId); + if (!existing) + throw new NotFoundException(`Company profile ${profileId} not found`); + + // A reference number is only minted the first time a profile is approved + // (status → Active). Pending/unapproved profiles carry no reference. + const patch: Partial = { status }; + if (status === ProfileStatus.Active && !existing.reference) { + patch.reference = await this.companyProfilesRepo.generateReference( + existing.type, + ); + } + + const updated = await this.companyProfilesRepo.update(profileId, patch); if (!updated) throw new NotFoundException(`Company profile ${profileId} not found`); @@ -718,7 +725,7 @@ export class CompaniesService { const existing = await this.companyProfilesRepo.findByType(companyId, type); if (existing) { throw new ConflictException( - `Company already has a ${type} profile (${existing.reference})`, + `Company already has a ${type} profile (${existing.reference ?? "pending approval"})`, ); } @@ -931,7 +938,7 @@ export class CompaniesService { const licenseProfiles = (company.companyProfiles ?? []).map((p) => ({ profileId: p.id, type: p.type, - reference: p.reference, + reference: p.reference ?? "", uploaded: (p.businessLicenseFiles?.length ?? 0) > 0, })); const missingLicenses = licenseProfiles.filter((p) => !p.uploaded); diff --git a/apps/edr-freight-api/src/modules/companies/dto/response-company.dto.ts b/apps/edr-freight-api/src/modules/companies/dto/response-company.dto.ts index b62182968..5d90d8d60 100644 --- a/apps/edr-freight-api/src/modules/companies/dto/response-company.dto.ts +++ b/apps/edr-freight-api/src/modules/companies/dto/response-company.dto.ts @@ -28,7 +28,7 @@ export class ResponseCompanyProfileDto { this.id = profile.id; this.companyId = profile.companyId; this.type = profile.type; - this.reference = profile.reference; + this.reference = profile.reference ?? ''; this.status = profile.status; this.businessLicense = profile.businessLicense; this.licenseFiles = profile.businessLicenseFiles ?? []; diff --git a/apps/edr-freight-api/src/modules/companies/entities/company-profile.entity.ts b/apps/edr-freight-api/src/modules/companies/entities/company-profile.entity.ts index c0cb41a63..e61668a07 100644 --- a/apps/edr-freight-api/src/modules/companies/entities/company-profile.entity.ts +++ b/apps/edr-freight-api/src/modules/companies/entities/company-profile.entity.ts @@ -40,14 +40,19 @@ export class CompanyProfile extends BaseEntity { @Column({ name: "type", type: "varchar", length: 32, enum: ProfileType }) type!: ProfileType; + /** + * Official profile reference (e.g. "EX-00001"). 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. + */ @Column({ name: "reference", type: "varchar", length: 20, - nullable: false, - unique: true, + nullable: true, }) - reference!: string; + reference!: string | null; @Column({ name: "status",