import { registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface, } from "class-validator"; import { REPAIR_LINE_AMOUNT_TOMAN, } from "src/constants/repair-amount-limits"; import { parseMoneyAmountToman } from "src/utils/unicode-digits"; export type RepairLineAmountTomanOptions = { /** When true, `0` is valid (e.g. expert `price` / `salary` split where one side is zero). */ allowZero?: boolean; }; @ValidatorConstraint({ name: "isRepairLineAmountToman", async: false }) export class IsRepairLineAmountTomanConstraint implements ValidatorConstraintInterface { validate(value: unknown, args: ValidationArguments): boolean { const [minToman, maxToman, allowZero] = args.constraints as [ number, number, boolean | undefined, ]; if (value == null || value === "") return true; const amount = parseMoneyAmountToman(value); if (amount == null) return false; if (allowZero && amount === 0) return true; return amount >= minToman && amount <= maxToman; } defaultMessage(args: ValidationArguments): string { const [minToman, maxToman, allowZero] = args.constraints as [ number, number, boolean | undefined, ]; const zeroHint = allowZero ? " Use 0 where one side of price/salary is unused." : ""; return ( `Amount must be in Toman between ${minToman.toLocaleString("en-US")} and ${maxToman.toLocaleString("en-US")}.${zeroHint}` ); } } /** Optional repair-line amount in Toman (digits only after unicode normalize). */ export function IsRepairLineAmountToman( options?: ValidationOptions & RepairLineAmountTomanOptions, ) { const { allowZero, ...validationOptions } = options ?? {}; return (object: object, propertyName: string) => { registerDecorator({ target: object.constructor, propertyName, options: validationOptions, constraints: [ REPAIR_LINE_AMOUNT_TOMAN.MIN, REPAIR_LINE_AMOUNT_TOMAN.MAX, allowZero === true, ], validator: IsRepairLineAmountTomanConstraint, }); }; }