Removed some validations that were unneccesary for expert submit

This commit is contained in:
SepehrYahyaee
2026-09-06 12:09:34 +03:30
parent 233a16d96c
commit c9b5b6f765
5 changed files with 74 additions and 43 deletions

View File

@@ -5,22 +5,18 @@ import {
ValidatorConstraint, ValidatorConstraint,
ValidatorConstraintInterface, ValidatorConstraintInterface,
} from "class-validator"; } from "class-validator";
import { normalizeMoneyAmountString } from "src/utils/unicode-digits"; import { parseMoneyAmountToman } from "src/utils/unicode-digits";
@ValidatorConstraint({ name: "isMoneyAmountString", async: false }) @ValidatorConstraint({ name: "isMoneyAmountString", async: false })
export class IsMoneyAmountStringConstraint export class IsMoneyAmountStringConstraint implements ValidatorConstraintInterface {
implements ValidatorConstraintInterface
{
validate(value: unknown): boolean { validate(value: unknown): boolean {
if (value == null || value === "") return true; if (value == null || value === "") return true;
if (typeof value !== "string") return false; if (typeof value !== "string") return false;
const n = normalizeMoneyAmountString(value); return parseMoneyAmountToman(value) !== null;
if (!n) return false;
return /^\d+(\.\d+)?$/.test(n);
} }
defaultMessage(): string { defaultMessage(): string {
return "Must be a non-negative amount (digits only, optional decimal)."; return "Must be a non-negative whole-Toman amount.";
} }
} }

View File

@@ -26,7 +26,7 @@ describe("SubmitExpertReplyV2Dto", () => {
expect(errors).not.toHaveLength(0); expect(errors).not.toHaveLength(0);
}); });
it("accepts a replacement part without a price", async () => { it("rejects a replacement part without a price", async () => {
const dto = plainToInstance(SubmitExpertReplyV2Dto, { const dto = plainToInstance(SubmitExpertReplyV2Dto, {
description: "Damage assessment", description: "Damage assessment",
parts: [ parts: [
@@ -41,7 +41,7 @@ describe("SubmitExpertReplyV2Dto", () => {
], ],
}); });
expect(await validate(dto)).toHaveLength(0); expect(await validate(dto)).not.toHaveLength(0);
}); });
it("accepts a repair part without daghi", async () => { it("accepts a repair part without daghi", async () => {
@@ -61,4 +61,33 @@ describe("SubmitExpertReplyV2Dto", () => {
expect(await validate(dto)).toHaveLength(0); expect(await validate(dto)).toHaveLength(0);
}); });
it("accepts the expert's repair and replacement pricing rules", async () => {
const dto = plainToInstance(SubmitExpertReplyV2Dto, {
description: "Damage assessment",
parts: [
{
partId: 11,
typeOfDamage: TypeOfDamage.Repair,
salary: "۵,۰۰۰",
totalPayment: "5000",
factorNeeded: false,
},
{
partId: 23,
typeOfDamage: TypeOfDamage.Change,
price: "۵۰,۰۰۰",
salary: "۱۰۰,۰۰۰",
totalPayment: "150000",
factorNeeded: false,
daghi: {
option: DaghiOption.RECYCLED_PARTS_VALUE,
price: "۱۰۰,۵۰۰,۰۰۰",
},
},
],
});
expect(await validate(dto)).toHaveLength(0);
});
}); });

View File

@@ -12,7 +12,7 @@ import {
IsInt, IsInt,
} from 'class-validator'; } from 'class-validator';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { IsRepairLineAmountToman } from 'src/common/validators/repair-line-amount-toman.validator'; import { IsMoneyAmountString } from 'src/common/validators/money-amount-string.validator';
import { ClaimRequiredDocumentType } from 'src/Types&Enums/claim-request-management/required-document-type.enum'; import { ClaimRequiredDocumentType } from 'src/Types&Enums/claim-request-management/required-document-type.enum';
import { DamagedPartItem } from 'src/claim-request-management/dto/capture-requirements-v2.dto'; import { DamagedPartItem } from 'src/claim-request-management/dto/capture-requirements-v2.dto';
import { DaghiOption } from 'src/Types&Enums/claim-request-management/daghi-option.enum'; import { DaghiOption } from 'src/Types&Enums/claim-request-management/daghi-option.enum';
@@ -35,7 +35,7 @@ export class DaghiDetailsV2Dto {
) )
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
@IsRepairLineAmountToman() @IsMoneyAmountString()
price?: string; price?: string;
@ApiPropertyOptional({ @ApiPropertyOptional({
@@ -56,24 +56,24 @@ export class PartPricingV2Dto {
@ApiProperty({ @ApiProperty({
enum: TypeOfDamage, enum: TypeOfDamage,
description: "'repair' requires price; 'change' may omit it.", description: "'change' requires price; 'repair' may omit it.",
}) })
@IsEnum(TypeOfDamage) @IsEnum(TypeOfDamage)
typeOfDamage: TypeOfDamage; typeOfDamage: TypeOfDamage;
@ApiPropertyOptional({ @ApiPropertyOptional({
example: "5000000", example: "5000000",
description: "Required for repair lines; omitted for change lines. Use 0 if the full amount is in salary.", description: "Required for change lines; omitted for repair lines. Use 0 if unused.",
}) })
@ValidateIf( @ValidateIf(
(part: PartPricingV2Dto) => (part: PartPricingV2Dto) =>
part.typeOfDamage === TypeOfDamage.Repair || part.typeOfDamage === TypeOfDamage.Change ||
(part.price != null && (part.price != null &&
(typeof part.price !== 'string' || part.price.trim() !== '')), (typeof part.price !== 'string' || part.price.trim() !== '')),
) )
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
@IsRepairLineAmountToman({ allowZero: true }) @IsMoneyAmountString()
price: string; price: string;
@ApiProperty({ @ApiProperty({
@@ -82,7 +82,7 @@ export class PartPricingV2Dto {
}) })
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
@IsRepairLineAmountToman({ allowZero: true }) @IsMoneyAmountString()
salary: string; salary: string;
@ApiProperty({ @ApiProperty({
@@ -91,7 +91,7 @@ export class PartPricingV2Dto {
}) })
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
@IsRepairLineAmountToman({ allowZero: true }) @IsMoneyAmountString()
totalPayment: string; totalPayment: string;
@ApiPropertyOptional({ @ApiPropertyOptional({

View File

@@ -47,7 +47,7 @@ describe("getExpertReplyPricingValidationError", () => {
).toBeNull(); ).toBeNull();
}); });
it("rejects a repair line without a price", () => { it("accepts a repair line without a price", () => {
expect( expect(
getExpertReplyPricingValidationError([ getExpertReplyPricingValidationError([
{ {
@@ -55,10 +55,9 @@ describe("getExpertReplyPricingValidationError", () => {
typeOfDamage: TypeOfDamage.Repair, typeOfDamage: TypeOfDamage.Repair,
salary: "0", salary: "0",
totalPayment: "0", totalPayment: "0",
daghi: { option: DaghiOption.NO_VALUE },
}, },
]), ]),
).toMatch(/price is also required/); ).toBeNull();
}); });
it("rejects recycled-value daghi without its price", () => { it("rejects recycled-value daghi without its price", () => {
@@ -67,6 +66,7 @@ describe("getExpertReplyPricingValidationError", () => {
{ {
partId: 201, partId: 201,
typeOfDamage: TypeOfDamage.Change, typeOfDamage: TypeOfDamage.Change,
price: "0",
salary: "0", salary: "0",
totalPayment: "0", totalPayment: "0",
daghi: { option: DaghiOption.RECYCLED_PARTS_VALUE }, daghi: { option: DaghiOption.RECYCLED_PARTS_VALUE },
@@ -75,7 +75,7 @@ describe("getExpertReplyPricingValidationError", () => {
).toMatch(/requires a valid daghi price/); ).toMatch(/requires a valid daghi price/);
}); });
it("accepts a replacement line without a part price", () => { it("rejects a replacement line without a part price", () => {
expect( expect(
getExpertReplyPricingValidationError([ getExpertReplyPricingValidationError([
{ {
@@ -86,7 +86,7 @@ describe("getExpertReplyPricingValidationError", () => {
daghi: { option: DaghiOption.NO_VALUE }, daghi: { option: DaghiOption.NO_VALUE },
}, },
]), ]),
).toBeNull(); ).toMatch(/price is also required/);
}); });
it("rejects an invalid price when a replacement line supplies one", () => { it("rejects an invalid price when a replacement line supplies one", () => {
@@ -103,4 +103,22 @@ describe("getExpertReplyPricingValidationError", () => {
]), ]),
).toMatch(/requires valid salary and totalPayment/); ).toMatch(/requires valid salary and totalPayment/);
}); });
it("accepts a recycled-parts daghi price above the repair-line cap", () => {
expect(
getExpertReplyPricingValidationError([
{
partId: 23,
typeOfDamage: TypeOfDamage.Change,
price: "۵۰,۰۰۰",
salary: "۱۰۰,۰۰۰",
totalPayment: "150000",
daghi: {
option: DaghiOption.RECYCLED_PARTS_VALUE,
price: "۱۰۰,۵۰۰,۰۰۰",
},
},
]),
).toBeNull();
});
}); });

View File

@@ -1,4 +1,3 @@
import { REPAIR_LINE_AMOUNT_TOMAN } from "src/constants/repair-amount-limits";
import { DaghiOption } from "src/Types&Enums/claim-request-management/daghi-option.enum"; import { DaghiOption } from "src/Types&Enums/claim-request-management/daghi-option.enum";
import { TypeOfDamage } from "src/Types&Enums/claim-request-management/type-of-damage.enum"; import { TypeOfDamage } from "src/Types&Enums/claim-request-management/type-of-damage.enum";
import { parseMoneyAmountToman } from "src/utils/unicode-digits"; import { parseMoneyAmountToman } from "src/utils/unicode-digits";
@@ -59,33 +58,22 @@ export function getExpertReplyPricingValidationError(
part.price != null && part.price != null &&
(typeof part.price !== "string" || part.price.trim() !== ""); (typeof part.price !== "string" || part.price.trim() !== "");
const splitAmountIsValid = (amount: number | null) => const amountIsValid = (amount: number | null) => amount !== null;
amount !== null && const priceIsRequired = part.typeOfDamage === TypeOfDamage.Change;
(amount === 0 || const priceIsValid = !hasPrice || amountIsValid(price);
(amount >= REPAIR_LINE_AMOUNT_TOMAN.MIN &&
amount <= REPAIR_LINE_AMOUNT_TOMAN.MAX));
const totalIsValid =
totalPayment !== null &&
totalPayment >= 0 &&
totalPayment <= REPAIR_LINE_AMOUNT_TOMAN.MAX;
const priceIsRequired = part.typeOfDamage === TypeOfDamage.Repair;
const priceIsValid =
!hasPrice || (price !== null && splitAmountIsValid(price));
if ( if (
!splitAmountIsValid(salary) || !amountIsValid(salary) ||
!totalIsValid || !amountIsValid(totalPayment) ||
(priceIsRequired && !splitAmountIsValid(price)) || (priceIsRequired && !amountIsValid(price)) ||
!priceIsValid !priceIsValid
) { ) {
return `${label} requires valid salary and totalPayment; price is also required for '${TypeOfDamage.Repair}' damage. Price and salary may be 0; totalPayment may be 0.`; return `${label} requires valid salary and totalPayment; price is also required for '${TypeOfDamage.Change}' damage. Price, salary, and totalPayment may be 0.`;
} }
if ( if (
daghi?.option === DaghiOption.RECYCLED_PARTS_VALUE && daghi?.option === DaghiOption.RECYCLED_PARTS_VALUE &&
(daghiPrice === null || daghiPrice === null
daghiPrice < REPAIR_LINE_AMOUNT_TOMAN.MIN ||
daghiPrice > REPAIR_LINE_AMOUNT_TOMAN.MAX)
) { ) {
return `${label} requires a valid daghi price when option is '${DaghiOption.RECYCLED_PARTS_VALUE}'.`; return `${label} requires a valid daghi price when option is '${DaghiOption.RECYCLED_PARTS_VALUE}'.`;
} }