fix: 194 and 183 plane takss

This commit is contained in:
Nathnael
2026-07-20 07:41:25 +00:00
parent 3d4996e4df
commit c7195f077a
13 changed files with 180 additions and 17 deletions

View File

@@ -0,0 +1,54 @@
import {
validate,
IsNotEmpty,
IsOptional,
IsString,
} from 'class-validator';
import { IsTin, normalizeTin } from './is-tin.validator';
class Required {
@IsString()
@IsNotEmpty()
@IsTin({ message: 'TIN must be exactly 10 digits' })
tin!: string;
}
class Optional {
@IsOptional()
@IsString()
@IsTin({ message: 'TIN must be exactly 10 digits' })
tin?: string;
}
async function errs(cls: any, tin: any) {
const o = new cls();
o.tin = tin;
return (await validate(o)).length;
}
describe('IsTin', () => {
it('accepts a real 10-digit TIN', async () => {
expect(await errs(Required, '0012345678')).toBe(0);
});
it.each([
['letters', 'ABCDEFGHIJ'],
['symbols', '!!!!!!!!!!'],
['too short', '123'],
['too long', '12345678901'],
['draft TIN', 'D123456789'],
['spaced', '012 345678'],
])('rejects %s', async (_label, value) => {
expect(await errs(Required, value)).toBeGreaterThan(0);
});
it('rejects empty on the required DTO but allows omission on the optional one', async () => {
expect(await errs(Required, '')).toBeGreaterThan(0);
expect(await errs(Optional, undefined)).toBe(0);
});
it('normalizes messy input', () => {
expect(normalizeTin(' 001-234-5678 ')).toBe('0012345678');
expect(normalizeTin('')).toBe('');
});
});

View File

@@ -0,0 +1,54 @@
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);
}