forked from Yara724/api
Merge pull request 'Added complete validation for expert claim submit state' (#286) from s.yahyaee/yara724-api:main into main
Reviewed-on: Yara724/api#286
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
export enum TypeOfDamage {
|
export enum TypeOfDamage {
|
||||||
Repair = "repair",
|
Repair = "تعمیر",
|
||||||
Change = "change",
|
Change = "تعویض",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { plainToInstance } from "class-transformer";
|
import { plainToInstance } from "class-transformer";
|
||||||
import { validate } from "class-validator";
|
import { validate } from "class-validator";
|
||||||
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 { SubmitExpertReplyV2Dto } from "./expert-claim-v2.dto";
|
import { SubmitExpertReplyV2Dto } from "./expert-claim-v2.dto";
|
||||||
|
|
||||||
describe("SubmitExpertReplyV2Dto", () => {
|
describe("SubmitExpertReplyV2Dto", () => {
|
||||||
@@ -10,7 +11,7 @@ describe("SubmitExpertReplyV2Dto", () => {
|
|||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
partId: 201,
|
partId: 201,
|
||||||
typeOfDamage: "Minor",
|
typeOfDamage: TypeOfDamage.Repair,
|
||||||
price: "",
|
price: "",
|
||||||
salary: "",
|
salary: "",
|
||||||
totalPayment: "",
|
totalPayment: "",
|
||||||
@@ -24,4 +25,22 @@ describe("SubmitExpertReplyV2Dto", () => {
|
|||||||
|
|
||||||
expect(errors).not.toHaveLength(0);
|
expect(errors).not.toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("accepts a replacement part without a price", async () => {
|
||||||
|
const dto = plainToInstance(SubmitExpertReplyV2Dto, {
|
||||||
|
description: "Damage assessment",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
partId: 201,
|
||||||
|
typeOfDamage: TypeOfDamage.Change,
|
||||||
|
salary: "0",
|
||||||
|
totalPayment: "0",
|
||||||
|
factorNeeded: false,
|
||||||
|
daghi: { option: DaghiOption.NO_VALUE },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(await validate(dto)).toHaveLength(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
ArrayMinSize,
|
ArrayMinSize,
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
|
ValidateIf,
|
||||||
ValidateNested,
|
ValidateNested,
|
||||||
IsEnum,
|
IsEnum,
|
||||||
IsInt,
|
IsInt,
|
||||||
@@ -15,6 +16,7 @@ import { IsRepairLineAmountToman } from 'src/common/validators/repair-line-amoun
|
|||||||
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';
|
||||||
|
import { TypeOfDamage } from 'src/Types&Enums/claim-request-management/type-of-damage.enum';
|
||||||
export class DaghiDetailsV2Dto {
|
export class DaghiDetailsV2Dto {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
enum: DaghiOption,
|
enum: DaghiOption,
|
||||||
@@ -27,8 +29,12 @@ export class DaghiDetailsV2Dto {
|
|||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
description: `Required when option is '${DaghiOption.RECYCLED_PARTS_VALUE}' (Toman)`,
|
description: `Required when option is '${DaghiOption.RECYCLED_PARTS_VALUE}' (Toman)`,
|
||||||
})
|
})
|
||||||
@IsOptional()
|
@ValidateIf(
|
||||||
|
(daghi: DaghiDetailsV2Dto) =>
|
||||||
|
daghi.option === DaghiOption.RECYCLED_PARTS_VALUE,
|
||||||
|
)
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
@IsRepairLineAmountToman()
|
@IsRepairLineAmountToman()
|
||||||
price?: string;
|
price?: string;
|
||||||
|
|
||||||
@@ -48,14 +54,23 @@ export class PartPricingV2Dto {
|
|||||||
@IsInt()
|
@IsInt()
|
||||||
partId: number;
|
partId: number;
|
||||||
|
|
||||||
@ApiProperty({ example: 'Minor' })
|
|
||||||
@IsString()
|
|
||||||
typeOfDamage: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
example: "5000000",
|
enum: TypeOfDamage,
|
||||||
description: "Part price in Toman (integer string). Use 0 if the full amount is in salary.",
|
description: "'repair' requires price; 'change' may omit it.",
|
||||||
})
|
})
|
||||||
|
@IsEnum(TypeOfDamage)
|
||||||
|
typeOfDamage: TypeOfDamage;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
example: "5000000",
|
||||||
|
description: "Required for repair lines; omitted for change lines. Use 0 if the full amount is in salary.",
|
||||||
|
})
|
||||||
|
@ValidateIf(
|
||||||
|
(part: PartPricingV2Dto) =>
|
||||||
|
part.typeOfDamage === TypeOfDamage.Repair ||
|
||||||
|
(part.price != null &&
|
||||||
|
(typeof part.price !== 'string' || part.price.trim() !== '')),
|
||||||
|
)
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsRepairLineAmountToman({ allowZero: true })
|
@IsRepairLineAmountToman({ allowZero: true })
|
||||||
@@ -76,10 +91,11 @@ export class PartPricingV2Dto {
|
|||||||
})
|
})
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsRepairLineAmountToman()
|
@IsRepairLineAmountToman({ allowZero: true })
|
||||||
totalPayment: string;
|
totalPayment: string;
|
||||||
|
|
||||||
@ApiProperty({ type: DaghiDetailsV2Dto })
|
@ApiProperty({ type: DaghiDetailsV2Dto })
|
||||||
|
@IsNotEmpty()
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => DaghiDetailsV2Dto)
|
@Type(() => DaghiDetailsV2Dto)
|
||||||
daghi: DaghiDetailsV2Dto;
|
daghi: DaghiDetailsV2Dto;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { BadRequestException } from "@nestjs/common";
|
import { BadRequestException } from "@nestjs/common";
|
||||||
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
|
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
|
||||||
|
import { DaghiOption } from "src/Types&Enums/claim-request-management/daghi-option.enum";
|
||||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||||
|
import { TypeOfDamage } from "src/Types&Enums/claim-request-management/type-of-damage.enum";
|
||||||
import { ExpertClaimService } from "./expert-claim.service";
|
import { ExpertClaimService } from "./expert-claim.service";
|
||||||
|
|
||||||
const blankPricingReply = {
|
const blankPricingReply = {
|
||||||
@@ -8,10 +10,12 @@ const blankPricingReply = {
|
|||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
partId: 201,
|
partId: 201,
|
||||||
|
typeOfDamage: TypeOfDamage.Repair,
|
||||||
price: "",
|
price: "",
|
||||||
salary: "",
|
salary: "",
|
||||||
totalPayment: "",
|
totalPayment: "",
|
||||||
factorNeeded: false,
|
factorNeeded: false,
|
||||||
|
daghi: { option: DaghiOption.NO_VALUE },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,92 @@
|
|||||||
import { getExpertReplyPricingValidationError } from "./expert-reply-pricing";
|
import { getExpertReplyPricingValidationError } from "./expert-reply-pricing";
|
||||||
|
import { TypeOfDamage } from "src/Types&Enums/claim-request-management/type-of-damage.enum";
|
||||||
|
import { DaghiOption } from "src/Types&Enums/claim-request-management/daghi-option.enum";
|
||||||
|
|
||||||
describe("getExpertReplyPricingValidationError", () => {
|
describe("getExpertReplyPricingValidationError", () => {
|
||||||
it("rejects a blank pricing line", () => {
|
it("rejects a blank pricing line", () => {
|
||||||
expect(
|
expect(
|
||||||
getExpertReplyPricingValidationError([
|
getExpertReplyPricingValidationError([
|
||||||
{ partId: 201, price: "", salary: "", totalPayment: "" },
|
{
|
||||||
|
partId: 201,
|
||||||
|
typeOfDamage: TypeOfDamage.Repair,
|
||||||
|
price: "",
|
||||||
|
salary: "",
|
||||||
|
totalPayment: "",
|
||||||
|
daghi: { option: DaghiOption.NO_VALUE },
|
||||||
|
},
|
||||||
]),
|
]),
|
||||||
).toMatch(/requires valid price, salary, and totalPayment/);
|
).toMatch(/requires valid salary and totalPayment/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("accepts a fully priced line with a zero-valued split", () => {
|
it("accepts a fully priced line with a zero-valued split", () => {
|
||||||
expect(
|
expect(
|
||||||
getExpertReplyPricingValidationError([
|
getExpertReplyPricingValidationError([
|
||||||
{ partId: 201, price: "0", salary: "10000", totalPayment: "10000" },
|
{
|
||||||
|
partId: 201,
|
||||||
|
typeOfDamage: TypeOfDamage.Repair,
|
||||||
|
price: "0",
|
||||||
|
salary: "10000",
|
||||||
|
totalPayment: "10000",
|
||||||
|
daghi: { option: DaghiOption.NO_VALUE },
|
||||||
|
},
|
||||||
]),
|
]),
|
||||||
).toBeNull();
|
).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("rejects a repair line without a price", () => {
|
||||||
|
expect(
|
||||||
|
getExpertReplyPricingValidationError([
|
||||||
|
{
|
||||||
|
partId: 201,
|
||||||
|
typeOfDamage: TypeOfDamage.Repair,
|
||||||
|
salary: "0",
|
||||||
|
totalPayment: "0",
|
||||||
|
daghi: { option: DaghiOption.NO_VALUE },
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
).toMatch(/price is also required/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects recycled-value daghi without its price", () => {
|
||||||
|
expect(
|
||||||
|
getExpertReplyPricingValidationError([
|
||||||
|
{
|
||||||
|
partId: 201,
|
||||||
|
typeOfDamage: TypeOfDamage.Change,
|
||||||
|
salary: "0",
|
||||||
|
totalPayment: "0",
|
||||||
|
daghi: { option: DaghiOption.RECYCLED_PARTS_VALUE },
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
).toMatch(/requires a valid daghi price/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accepts a replacement line without a part price", () => {
|
||||||
|
expect(
|
||||||
|
getExpertReplyPricingValidationError([
|
||||||
|
{
|
||||||
|
partId: 201,
|
||||||
|
typeOfDamage: TypeOfDamage.Change,
|
||||||
|
salary: "0",
|
||||||
|
totalPayment: "0",
|
||||||
|
daghi: { option: DaghiOption.NO_VALUE },
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects an invalid price when a replacement line supplies one", () => {
|
||||||
|
expect(
|
||||||
|
getExpertReplyPricingValidationError([
|
||||||
|
{
|
||||||
|
partId: 201,
|
||||||
|
typeOfDamage: TypeOfDamage.Change,
|
||||||
|
price: "not-a-number",
|
||||||
|
salary: "0",
|
||||||
|
totalPayment: "0",
|
||||||
|
daghi: { option: DaghiOption.NO_VALUE },
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
).toMatch(/requires valid salary and totalPayment/);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { REPAIR_LINE_AMOUNT_TOMAN } from "src/constants/repair-amount-limits";
|
import { REPAIR_LINE_AMOUNT_TOMAN } from "src/constants/repair-amount-limits";
|
||||||
|
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 { parseMoneyAmountToman } from "src/utils/unicode-digits";
|
import { parseMoneyAmountToman } from "src/utils/unicode-digits";
|
||||||
|
|
||||||
type ExpertReplyPricingPart = {
|
type ExpertReplyPricingPart = {
|
||||||
partId?: unknown;
|
partId?: unknown;
|
||||||
|
typeOfDamage?: unknown;
|
||||||
price?: unknown;
|
price?: unknown;
|
||||||
salary?: unknown;
|
salary?: unknown;
|
||||||
totalPayment?: unknown;
|
totalPayment?: unknown;
|
||||||
|
daghi?: { option?: unknown; price?: unknown };
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,13 +32,30 @@ export function getExpertReplyPricingValidationError(
|
|||||||
? `Part ${String(part.partId)}`
|
? `Part ${String(part.partId)}`
|
||||||
: `Damaged part ${index + 1}`;
|
: `Damaged part ${index + 1}`;
|
||||||
|
|
||||||
if (!part || typeof part !== "object") {
|
if (!part || typeof part !== "object" || part.partId == null) {
|
||||||
return `${label} must include price, salary, and totalPayment.`;
|
return `${label} must include a damaged-part partId.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!Object.values(TypeOfDamage).includes(part.typeOfDamage as TypeOfDamage)
|
||||||
|
) {
|
||||||
|
return `${label} requires typeOfDamage to be either '${TypeOfDamage.Repair}' or '${TypeOfDamage.Change}'.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!part.daghi ||
|
||||||
|
!Object.values(DaghiOption).includes(part.daghi.option as DaghiOption)
|
||||||
|
) {
|
||||||
|
return `${label} requires a valid daghi option.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const price = parseMoneyAmountToman(part.price);
|
const price = parseMoneyAmountToman(part.price);
|
||||||
const salary = parseMoneyAmountToman(part.salary);
|
const salary = parseMoneyAmountToman(part.salary);
|
||||||
const totalPayment = parseMoneyAmountToman(part.totalPayment);
|
const totalPayment = parseMoneyAmountToman(part.totalPayment);
|
||||||
|
const daghiPrice = parseMoneyAmountToman(part.daghi.price);
|
||||||
|
const hasPrice =
|
||||||
|
part.price != null &&
|
||||||
|
(typeof part.price !== "string" || part.price.trim() !== "");
|
||||||
|
|
||||||
const splitAmountIsValid = (amount: number | null) =>
|
const splitAmountIsValid = (amount: number | null) =>
|
||||||
amount !== null &&
|
amount !== null &&
|
||||||
@@ -43,15 +64,28 @@ export function getExpertReplyPricingValidationError(
|
|||||||
amount <= REPAIR_LINE_AMOUNT_TOMAN.MAX));
|
amount <= REPAIR_LINE_AMOUNT_TOMAN.MAX));
|
||||||
const totalIsValid =
|
const totalIsValid =
|
||||||
totalPayment !== null &&
|
totalPayment !== null &&
|
||||||
totalPayment >= REPAIR_LINE_AMOUNT_TOMAN.MIN &&
|
totalPayment >= 0 &&
|
||||||
totalPayment <= REPAIR_LINE_AMOUNT_TOMAN.MAX;
|
totalPayment <= REPAIR_LINE_AMOUNT_TOMAN.MAX;
|
||||||
|
const priceIsRequired = part.typeOfDamage === TypeOfDamage.Repair;
|
||||||
|
const priceIsValid =
|
||||||
|
!hasPrice || (price !== null && splitAmountIsValid(price));
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!splitAmountIsValid(price) ||
|
|
||||||
!splitAmountIsValid(salary) ||
|
!splitAmountIsValid(salary) ||
|
||||||
!totalIsValid
|
!totalIsValid ||
|
||||||
|
(priceIsRequired && !splitAmountIsValid(price)) ||
|
||||||
|
!priceIsValid
|
||||||
) {
|
) {
|
||||||
return `${label} requires valid price, salary, and totalPayment. Price and salary may be 0; totalPayment must be between ${REPAIR_LINE_AMOUNT_TOMAN.MIN.toLocaleString("en-US")} and ${REPAIR_LINE_AMOUNT_TOMAN.MAX.toLocaleString("en-US")} Toman.`;
|
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.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
part.daghi.option === DaghiOption.RECYCLED_PARTS_VALUE &&
|
||||||
|
(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}'.`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user