import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, } from 'class-validator'; /** An Ethiopian TIN is exactly 10 digits. */ export const TIN_REGEX = /^\d{10}$/; /** * Draft companies carry a placeholder TIN ("D" + 9 digits) minted server-side by * CompaniesService.generateDraftTin(), because the column is NOT NULL + unique. * Those never travel through a DTO, so this constraint deliberately rejects them * — a "D…" value arriving on a request body is client-supplied and invalid. */ @ValidatorConstraint({ name: 'IsTin', async: false }) export class IsTinConstraint implements ValidatorConstraintInterface { validate(value: unknown): boolean { // Empty is allowed here; pair with @IsOptional / @IsNotEmpty as needed. if (value === undefined || value === null || value === '') return true; if (typeof value !== 'string') return false; return TIN_REGEX.test(value); } defaultMessage(args: ValidationArguments): string { return `${args.property} must be exactly 10 digits`; } } /** Class-validator decorator enforcing the 10-digit TIN format. */ export function IsTin(validationOptions?: ValidationOptions) { return function (object: object, propertyName: string) { registerDecorator({ target: object.constructor, propertyName, options: validationOptions, constraints: [], validator: IsTinConstraint, }); }; } /** * Strip everything that isn't a digit and cap at 10 characters. Tolerant — * never throws; returns the value unchanged when empty/nullish. */ export function normalizeTin( value: string | null | undefined, ): string | null | undefined { if (value === undefined || value === null || value === '') return value; return value.replace(/\D/g, '').slice(0, 10); }