refactor: rm company profile

This commit is contained in:
Nathnael
2026-08-08 15:06:46 +00:00
parent d337fa0d85
commit 7a32bb95ff
25 changed files with 1313 additions and 2899 deletions

View File

@@ -322,41 +322,15 @@ describe("Fayda identity verification binds a person to the company", () => {
// registered phone) with nothing at all. OWNER_VERIFIED is exactly that
// shape: a sub, no contact details.
it("keeps company contact details a Fayda verification never supplied", async () => {
const { service, deps } = makeService({
const { deps } = makeService({
attributes: { ...OWNER_VERIFIED },
});
await expect(
service.updateProfile("user-1", {
companyEmail: "account@example.com",
companyPhone: "+251911777777",
} as never),
).resolves.toBeDefined();
const [, patch] = deps.companiesRepo.update.mock.calls.at(-1)!;
expect(patch.email).toBe("account@example.com");
expect(patch.phone).toBe("+251911777777");
});
it("overwrites company contact details the verification did supply", async () => {
const { service, deps } = makeService({
attributes: {
...OWNER_VERIFIED,
ownerEmail: "abebe@example.com",
ownerPhone: "+251911000000",
},
});
await expect(
service.updateProfile("user-1", {
companyEmail: "someone-else@example.com",
companyPhone: "+251911999999",
} as never),
).resolves.toBeDefined();
const [, patch] = deps.companiesRepo.update.mock.calls.at(-1)!;
expect(patch.email).toBe("abebe@example.com");
expect(patch.phone).toBe("+251911000000");
});
// "Same as owner" copies `ownerEmail ?? null` onto the GM while setting
// `gmFaydaSub`. Locking that null made generalManagerEmail required by
// onboarding, hidden by the portal's link card and unwritable at once.

View File

@@ -231,41 +231,39 @@ export class CompaniesService {
label: string;
get: (company: Company) => unknown;
}[] = [
{
key: "tinNumber",
label: "Company TIN",
get: (c) => (c.tin && !c.tin.startsWith("D") ? c.tin : null),
},
{ key: "companyEmail", label: "Company email", get: (c) => c.email },
{ key: "companyPhone", label: "Company phone", get: (c) => c.phone },
{ key: "companyAddress", label: "Company address", get: (c) => c.address },
{ key: "fanNumber", label: "FAN number", get: (c) => c.fanNumber },
{
key: "contactPersonName",
label: "Contact person name",
get: (c) => c.attributes?.contactPersonName,
},
{
key: "contactPersonPhone",
label: "Contact person phone",
get: (c) => c.attributes?.contactPersonPhone,
},
{
key: "generalManagerName",
label: "General manager name",
get: (c) => c.attributes?.generalManagerName,
},
{
key: "generalManagerEmail",
label: "General manager email",
get: (c) => c.attributes?.generalManagerEmail,
},
{
key: "generalManagerPhone",
label: "General manager phone",
get: (c) => c.attributes?.generalManagerPhone,
},
];
{
key: "tinNumber",
label: "Company TIN",
get: (c) => (c.tin && !c.tin.startsWith("D") ? c.tin : null),
},
{ key: "companyAddress", label: "Company address", get: (c) => c.address },
{ key: "fanNumber", label: "FAN number", get: (c) => c.fanNumber },
{
key: "contactPersonName",
label: "Contact person name",
get: (c) => c.attributes?.contactPersonName,
},
{
key: "contactPersonPhone",
label: "Contact person phone",
get: (c) => c.attributes?.contactPersonPhone,
},
{
key: "generalManagerName",
label: "General manager name",
get: (c) => c.attributes?.generalManagerName,
},
{
key: "generalManagerEmail",
label: "General manager email",
get: (c) => c.attributes?.generalManagerEmail,
},
{
key: "generalManagerPhone",
label: "General manager phone",
get: (c) => c.attributes?.generalManagerPhone,
},
];
/** The nationality-based document setting code for a company. */
private documentSettingCodeFor(
@@ -314,8 +312,6 @@ export class CompaniesService {
fanNumber: dto.fanNumber ?? null,
country: dto.companyLocation ?? "Ethiopia",
address: dto.companyAddress ?? null,
phone: normalizeE164(dto.companyPhone) ?? null,
email: dto.companyEmail ?? null,
attributes: dto.attributes ?? null,
});
@@ -492,7 +488,8 @@ export class CompaniesService {
async findCompanyById(id: string): Promise<Company> {
const company = await this.companiesRepo.findById(id);
if (!company) throw new NotFoundException(`Company ${id} not found`);
company.companyProfiles = await this.companyProfilesRepo.findByCompanyId(id);
company.companyProfiles =
await this.companyProfilesRepo.findByCompanyId(id);
// External profiles carry the onboarding flag the backoffice gates
// approval decisions on (see ResponseCompanyDto.onboardingCompleted).
company.profiles = await this.profilesRepo.findByCompanyId(id);
@@ -515,9 +512,7 @@ export class CompaniesService {
);
}
if (profile.status !== ProfileStatus.Active) {
throw new BadRequestException(
"Selected company profile is not active",
);
throw new BadRequestException("Selected company profile is not active");
}
return profile;
}
@@ -760,11 +755,6 @@ export class CompaniesService {
};
const keys: string[] = [];
if (attrs.ownerFaydaSub) {
// The Company-column mirrors of the owner's verified contact details.
if (held("ownerEmail")) keys.push("companyEmail");
if (held("ownerPhone")) keys.push("companyPhone");
}
for (const subject of IDENTITY_SUBJECTS) {
if (!attrs[`${IDENTITY_PREFIX[subject]}FaydaSub`]) continue;
keys.push(...IDENTITY_OWNED_FIELDS[subject].filter(held));
@@ -789,9 +779,6 @@ export class CompaniesService {
if (dto.nationality !== undefined)
companyUpdates.nationality = dto.nationality;
if (dto.companyName !== undefined) companyUpdates.name = dto.companyName;
if (dto.companyEmail !== undefined) companyUpdates.email = dto.companyEmail;
if (dto.companyPhone !== undefined)
companyUpdates.phone = normalizeE164(dto.companyPhone);
if (dto.companyLocation !== undefined)
companyUpdates.country = dto.companyLocation;
if (dto.companyAddress !== undefined)
@@ -809,7 +796,9 @@ export class CompaniesService {
if (dto.contactPersonPhone !== undefined)
attrUpdates.contactPersonPhone = normalizeE164(dto.contactPersonPhone);
if (dto.contactVerifiedPhone !== undefined)
attrUpdates.contactVerifiedPhone = normalizeE164(dto.contactVerifiedPhone);
attrUpdates.contactVerifiedPhone = normalizeE164(
dto.contactVerifiedPhone,
);
if (dto.generalManagerName !== undefined)
attrUpdates.generalManagerName = dto.generalManagerName;
if (dto.generalManagerEmail !== undefined)
@@ -820,7 +809,8 @@ export class CompaniesService {
if (dto.poaPhone !== undefined)
attrUpdates.poaPhone = normalizeE164(dto.poaPhone);
if (dto.poaEmail !== undefined) attrUpdates.poaEmail = dto.poaEmail;
if (dto.poaLocation !== undefined) attrUpdates.poaLocation = dto.poaLocation;
if (dto.poaLocation !== undefined)
attrUpdates.poaLocation = dto.poaLocation;
if (dto.poaAddress !== undefined) attrUpdates.poaAddress = dto.poaAddress;
if (dto.licenceNumber !== undefined)
@@ -857,12 +847,6 @@ export class CompaniesService {
Object.assign(attrUpdates, dto.faydaIdentity);
}
// companyEmail/companyPhone are the Company-column mirrors of the owner's
// verified contact details (the portal derives and submits them, it never
// lets the customer type them once verified) — lock them the same way
// ownerEmail/ownerPhone themselves are locked below, once there is a
// verified owner to lock them to.
//
// Keyed on the verified VALUE, not on `ownerFaydaSub`: Fayda's email and
// phone claims are optional, so a verification can prove the person while
// supplying neither (see completeIdentityVerification's conditional
@@ -872,9 +856,8 @@ export class CompaniesService {
// forever, and re-verifying could never clear it because Fayda still has
// nothing to return.
if (attrUpdates.ownerFaydaSub) {
if (attrUpdates.ownerEmail && dto.companyEmail !== undefined)
companyUpdates.email = attrUpdates.ownerEmail;
if (attrUpdates.ownerPhone && dto.companyPhone !== undefined)
if (attrUpdates.ownerEmail) companyUpdates.email = attrUpdates.ownerEmail;
if (attrUpdates.ownerPhone)
companyUpdates.phone = normalizeE164(String(attrUpdates.ownerPhone));
}
@@ -1086,9 +1069,7 @@ export class CompaniesService {
}
/** List a company's change requests, newest first (backoffice review). */
async listChangeRequests(
companyId: string,
): Promise<CompanyChangeRequest[]> {
async listChangeRequests(companyId: string): Promise<CompanyChangeRequest[]> {
await this.findCompanyById(companyId);
return this.changeRequestRepo.findByCompanyId(companyId);
}
@@ -1150,8 +1131,7 @@ export class CompaniesService {
reviewerId?: string,
): Promise<CompanyChangeRequest> {
const request = await this.changeRequestRepo.findById(id);
if (!request)
throw new NotFoundException(`Change request ${id} not found`);
if (!request) throw new NotFoundException(`Change request ${id} not found`);
if (request.status !== ChangeRequestStatus.Pending) {
throw new BadRequestException(
`Change request ${id} is already ${request.status}`,
@@ -1164,7 +1144,10 @@ export class CompaniesService {
const snapshot = (request.snapshot ?? {}) as Partial<UpdateProfileDto>;
await this.assertTinAvailable(company, snapshot.tin);
const companyUpdates = this.mapProfileDtoToCompanyUpdates(company, snapshot);
const companyUpdates = this.mapProfileDtoToCompanyUpdates(
company,
snapshot,
);
await this.companiesRepo.update(company.id, companyUpdates);
await this.applyLicenseChanges(request);
await this.applyDocumentChanges(request);
@@ -1294,7 +1277,12 @@ export class CompaniesService {
);
}
if (documentChanges.length > 0) {
await this.recordCompanyRevision(company, {}, submittedBy, documentChanges);
await this.recordCompanyRevision(
company,
{},
submittedBy,
documentChanges,
);
}
return uploaded;
}
@@ -1414,7 +1402,11 @@ export class CompaniesService {
status: ChangeRequestStatus.Pending,
});
if (company) {
this.companyNotifier.changeRequestSubmitted(company, existing.id, false);
this.companyNotifier.changeRequestSubmitted(
company,
existing.id,
false,
);
}
} else {
const history = await this.changeRequestRepo.findByCompanyId(companyId);
@@ -1446,8 +1438,7 @@ export class CompaniesService {
reviewerId?: string,
): Promise<CompanyChangeRequest> {
const request = await this.changeRequestRepo.findById(id);
if (!request)
throw new NotFoundException(`Change request ${id} not found`);
if (!request) throw new NotFoundException(`Change request ${id} not found`);
if (request.status !== ChangeRequestStatus.Pending) {
throw new BadRequestException(
`Change request ${id} is already ${request.status}`,
@@ -1486,8 +1477,7 @@ export class CompaniesService {
reviewerId?: string,
): Promise<CompanyChangeRequest> {
const request = await this.changeRequestRepo.findById(id);
if (!request)
throw new NotFoundException(`Change request ${id} not found`);
if (!request) throw new NotFoundException(`Change request ${id} not found`);
if (request.status !== ChangeRequestStatus.Pending) {
throw new BadRequestException(
`Change request ${id} is already ${request.status}`,
@@ -1569,10 +1559,7 @@ export class CompaniesService {
const reactivating =
status === ProfileStatus.Active &&
existing.status === ProfileStatus.Suspended;
if (
(status === ProfileStatus.Suspended || reactivating) &&
!note?.trim()
) {
if ((status === ProfileStatus.Suspended || reactivating) && !note?.trim()) {
throw new BadRequestException(
status === ProfileStatus.Suspended
? "A message explaining the suspension is required — the customer will see it."
@@ -1594,7 +1581,9 @@ export class CompaniesService {
existing.status === ProfileStatus.Pending ||
existing.status === ProfileStatus.Rejected;
if (awaitingReview) {
const owners = await this.profilesRepo.findByCompanyId(existing.companyId);
const owners = await this.profilesRepo.findByCompanyId(
existing.companyId,
);
if (owners.length > 0 && !owners.some((o) => o.onboardingCompleted)) {
throw new BadRequestException(
"This customer hasn't finished onboarding yet. Their roles can be reviewed once they submit their application.",
@@ -1652,11 +1641,17 @@ export class CompaniesService {
const names = pending.map((f) => f.name).join(", ");
throw new BadRequestException(
`This role has ${pending.length} document(s) awaiting customer correction (${names}). ` +
`Approve it once the customer has re-uploaded them, or withdraw the change request first.`,
`Approve it once the customer has re-uploaded them, or withdraw the change request first.`,
);
}
return this.applyProfileStatus(manager, existing, status, note, reviewerId);
return this.applyProfileStatus(
manager,
existing,
status,
note,
reviewerId,
);
});
}
@@ -1992,7 +1987,9 @@ export class CompaniesService {
.map((f) => ({ key: f.key, label: f.label }));
// 2. Nationality-based company documents + which are already uploaded.
const documentSettingCode = this.documentSettingCodeFor(company.nationality);
const documentSettingCode = this.documentSettingCodeFor(
company.nationality,
);
const [setting, uploadedFiles] = await Promise.all([
this.fileUploadSettingsService
.getByCode(documentSettingCode)
@@ -2074,7 +2071,9 @@ export class CompaniesService {
? [`Upload the ${POA_DELEGATION_LABEL} for your Power of Attorney`]
: []),
...(flaggedDelegation
? [`Re-upload your ${POA_DELEGATION_LABEL} — EDR asked for a correction`]
? [
`Re-upload your ${POA_DELEGATION_LABEL} — EDR asked for a correction`,
]
: []),
...(identity.faydaRequired && !identity.owner.verified
? ["Verify the company owner's identity with Fayda"]
@@ -2088,10 +2087,10 @@ export class CompaniesService {
// verification its representative may have no way to obtain.
...((poaRequired || poaProvided) && !poaProven
? [
identity.faydaRequired
? "Verify your Power of Attorney's identity with Fayda"
: "Name your Power of Attorney, or verify them with Fayda",
]
identity.faydaRequired
? "Verify your Power of Attorney's identity with Fayda"
: "Name your Power of Attorney, or verify them with Fayda",
]
: []),
...(identity.passportRequired && !identity.owner.passportNumber
? ["Add the company owner's passport number"]
@@ -2137,7 +2136,10 @@ export class CompaniesService {
return new OnboardingRequirementsResponseDto({
documentSettingCode,
nationality: company.nationality ?? CompanyNationality.Ethiopian,
companyInfo: { complete: missingInfo.length === 0, missingFields: missingInfo },
companyInfo: {
complete: missingInfo.length === 0,
missingFields: missingInfo,
},
documents,
licenseProfiles,
poa: {
@@ -2179,7 +2181,7 @@ export class CompaniesService {
if (!requirements.isComplete) {
throw new BadRequestException(
requirements.outstanding[0] ??
"Your onboarding is incomplete. Please complete all required steps before submitting.",
"Your onboarding is incomplete. Please complete all required steps before submitting.",
);
}
@@ -2188,7 +2190,10 @@ export class CompaniesService {
const profiles = await this.companyProfilesRepo.findByCompanyId(companyId);
for (const cp of profiles) {
if (cp.status !== ProfileStatus.Pending) {
await this.companyProfilesRepo.updateStatus(cp.id, ProfileStatus.Pending);
await this.companyProfilesRepo.updateStatus(
cp.id,
ProfileStatus.Pending,
);
}
}
@@ -2214,12 +2219,12 @@ export class CompaniesService {
case CompanyStatus.Suspended:
throw new ForbiddenException(
`Your company account is suspended — you can't create ${action} right now. ` +
`Please contact EDR support for details.`,
`Please contact EDR support for details.`,
);
case CompanyStatus.Blacklisted:
throw new ForbiddenException(
`Your company account is blacklisted — you can't create ${action}. ` +
`Please contact EDR support.`,
`Please contact EDR support.`,
);
default:
throw new ForbiddenException(
@@ -2248,8 +2253,7 @@ export class CompaniesService {
switch (profile.status) {
case ProfileStatus.Suspended:
throw new ForbiddenException(
`Your ${role} role is suspended${
profile.reviewNote ? `${profile.reviewNote}` : ""
`Your ${role} role is suspended${profile.reviewNote ? `${profile.reviewNote}` : ""
}. Your other roles are unaffected. Please contact EDR support to resolve this.`,
);
case ProfileStatus.Blacklisted:
@@ -2258,8 +2262,7 @@ export class CompaniesService {
);
case ProfileStatus.Rejected:
throw new ForbiddenException(
`Your ${role} role was rejected${
profile.reviewNote ? `${profile.reviewNote}` : ""
`Your ${role} role was rejected${profile.reviewNote ? `${profile.reviewNote}` : ""
}. Amend and resubmit it from your settings page.`,
);
default:
@@ -2518,9 +2521,7 @@ export class CompaniesService {
LICENSE_RESOURCE,
);
return records
.filter(
(r) => r.code === LICENSE_CODE || r.code === LICENSE_PENDING_CODE,
)
.filter((r) => r.code === LICENSE_CODE || r.code === LICENSE_PENDING_CODE)
.map((r) => ({
id: r.id,
name: r.name,
@@ -2687,7 +2688,7 @@ export class CompaniesService {
if (missing.length > 0) {
throw new BadRequestException(
`A freight forwarder acts on other companies' behalf, so a Power of Attorney is required. ` +
`Add the ${missing.map((f) => f.label.toLowerCase()).join(", ")} first.`,
`Add the ${missing.map((f) => f.label.toLowerCase()).join(", ")} first.`,
);
}
}
@@ -2699,13 +2700,13 @@ export class CompaniesService {
if (!onFile) {
throw new BadRequestException(
`Upload the ${POA_DELEGATION_LABEL} for the Power of Attorney` +
(opts.requirePoa ? " — it is required for freight forwarders." : "."),
(opts.requirePoa ? " — it is required for freight forwarders." : "."),
);
}
if (flagged) {
throw new BadRequestException(
`The ${POA_DELEGATION_LABEL} on file needs to be corrected. ` +
`Re-upload it before continuing.`,
`Re-upload it before continuing.`,
);
}
}
@@ -2769,7 +2770,8 @@ export class CompaniesService {
// of this check entirely.
if (dto.subject === "owner" || dto.subject === "poa") {
const other: IdentitySubject = dto.subject === "poa" ? "owner" : "poa";
const otherSub = company.attributes?.[`${IDENTITY_PREFIX[other]}FaydaSub`];
const otherSub =
company.attributes?.[`${IDENTITY_PREFIX[other]}FaydaSub`];
if (otherSub && otherSub === result.sub) {
throw new BadRequestException(
`This identity is already registered as the company's ${IDENTITY_LABEL[other]}. The Power of Attorney must be a different person from the owner.`,
@@ -2976,8 +2978,8 @@ export class CompaniesService {
const snapshot = {
...(existing?.snapshot ?? {}),
faydaIdentity: {
...(((existing?.snapshot ?? {}) as Record<string, any>)
.faydaIdentity ?? {}),
...(((existing?.snapshot ?? {}) as Record<string, any>).faydaIdentity ??
{}),
...identity,
},
};
@@ -3389,7 +3391,10 @@ export class CompaniesService {
"We couldn't find a business license for this TIN with eTrade. Please double-check the number and try again.",
);
}
return this.etradeService.extractRegistrationData(businessInfo, companyInfo);
return this.etradeService.extractRegistrationData(
businessInfo,
companyInfo,
);
}
async fetchETradeData(tin: string, excludeCompanyId?: string) {
@@ -3421,7 +3426,9 @@ export class CompaniesService {
const tin = dto.tin ?? company.tin;
const registration = await this.resolveEtradeRegistration(tin);
const fresh: Partial<Record<(typeof ETRADE_SOURCED_FIELDS)[number], string>> = {
const fresh: Partial<
Record<(typeof ETRADE_SOURCED_FIELDS)[number], string>
> = {
companyName: registration.companyName,
licenceNumber: registration.licenceNumber,
statusDescription: registration.statusDescription,

View File

@@ -1,9 +1,18 @@
import { IsString, IsNotEmpty, IsOptional, IsEmail, MaxLength, IsBoolean, IsEnum, IsArray, ValidateNested, ArrayMinSize } from 'class-validator';
import { Type } from 'class-transformer';
import { CompanyType } from '../entities/company.entity';
import { ProfileType } from '../entities/company-profile.entity';
import { IsValidPhone } from '../../../common/validators/is-phone-number.validator';
import { IsTin } from '../../../common/validators/is-tin.validator';
import {
IsString,
IsNotEmpty,
IsOptional,
MaxLength,
IsBoolean,
IsEnum,
IsArray,
ValidateNested,
ArrayMinSize,
} from "class-validator";
import { Type } from "class-transformer";
import { CompanyType } from "../entities/company.entity";
import { ProfileType } from "../entities/company-profile.entity";
import { IsTin } from "../../../common/validators/is-tin.validator";
export class CompanyProfileInputDto {
@IsEnum(ProfileType)
@@ -24,17 +33,6 @@ export class CreateCompanyWithProfileDto {
@MaxLength(200)
companyName!: string;
@IsOptional()
@IsEmail()
@MaxLength(150)
companyEmail?: string;
@IsOptional()
@IsString()
@MaxLength(20)
@IsValidPhone()
companyPhone?: string;
@IsOptional()
@IsString()
@MaxLength(32)
@@ -46,7 +44,7 @@ export class CreateCompanyWithProfileDto {
@IsOptional()
@IsString()
@IsTin({ message: 'TIN must be exactly 10 digits' })
@IsTin({ message: "TIN must be exactly 10 digits" })
tin?: string;
@IsOptional()

View File

@@ -2,21 +2,19 @@ import {
buildCompanyIdentityState,
CompanyIdentityStateDto,
} from "./complete-identity-verification.dto";
import { Company } from '../entities/company.entity';
import { ExternalProfile } from '../entities/external-profile.entity';
import { Company } from "../entities/company.entity";
import { ExternalProfile } from "../entities/external-profile.entity";
import {
ChangeRequestStatus,
CompanyChangeRequest,
} from '../entities/company-change-request.entity';
import { ResponseCompanyProfileDto } from './response-company.dto';
} from "../entities/company-change-request.entity";
import { ResponseCompanyProfileDto } from "./response-company.dto";
export class ProfileResponseDto {
companyId: string;
companyName: string;
companyType: string;
nationality: string | null;
companyEmail: string | null;
companyPhone: string | null;
companyLocation: string;
companyAddress: string | null;
tinNumber: string;
@@ -89,8 +87,6 @@ export class ProfileResponseDto {
this.companyProfiles =
company.companyProfiles?.map((p) => new ResponseCompanyProfileDto(p)) ??
[];
this.companyEmail = company.email ?? null;
this.companyPhone = company.phone ?? null;
this.companyLocation = company.country;
this.companyAddress = company.address ?? null;
this.tinNumber = company.tin;
@@ -128,9 +124,9 @@ export class ProfileResponseDto {
const openReview =
changeRequest &&
(changeRequest.status === ChangeRequestStatus.Pending ||
changeRequest.status === ChangeRequestStatus.Rejected ||
changeRequest.status === ChangeRequestStatus.ChangesRequested)
(changeRequest.status === ChangeRequestStatus.Pending ||
changeRequest.status === ChangeRequestStatus.Rejected ||
changeRequest.status === ChangeRequestStatus.ChangesRequested)
? changeRequest
: null;
this.reviewStatus =

View File

@@ -6,11 +6,11 @@ import {
IsEnum,
IsIn,
Matches,
} from 'class-validator';
import { ETHIOPIAN_REGIONS, type EthiopianRegion } from '@edr/types';
import { CompanyNationality } from '../entities/company.entity';
import { IsValidPhone } from '../../../common/validators/is-phone-number.validator';
import { IsTin } from '../../../common/validators/is-tin.validator';
} from "class-validator";
import { ETHIOPIAN_REGIONS, type EthiopianRegion } from "@edr/types";
import { CompanyNationality } from "../entities/company.entity";
import { IsValidPhone } from "../../../common/validators/is-phone-number.validator";
import { IsTin } from "../../../common/validators/is-tin.validator";
export class UpdateProfileDto {
@IsOptional()
@@ -22,17 +22,6 @@ export class UpdateProfileDto {
@MaxLength(200)
companyName?: string;
@IsOptional()
@IsEmail()
@MaxLength(150)
companyEmail?: string;
@IsOptional()
@IsString()
@MaxLength(20)
@IsValidPhone()
companyPhone?: string;
@IsOptional()
@IsString()
@MaxLength(32)
@@ -44,7 +33,7 @@ export class UpdateProfileDto {
@IsOptional()
@IsString()
@IsTin({ message: 'TIN must be exactly 10 digits' })
@IsTin({ message: "TIN must be exactly 10 digits" })
tin?: string;
// Ethiopian VAT registration numbers are 10 digits, the same shape as the
@@ -53,7 +42,7 @@ export class UpdateProfileDto {
// column may hold.
@IsOptional()
@IsString()
@Matches(/^\d{10}$/, { message: 'VAT number must be exactly 10 digits' })
@Matches(/^\d{10}$/, { message: "VAT number must be exactly 10 digits" })
vatNumber?: string;
// `fanNumber` is deliberately absent: the FAN is the Fayda number of the