Compare commits

...

3 Commits

6 changed files with 107 additions and 26 deletions

View File

@@ -43,4 +43,22 @@ describe("SubmitExpertReplyV2Dto", () => {
expect(await validate(dto)).toHaveLength(0);
});
it("accepts a repair part without daghi", async () => {
const dto = plainToInstance(SubmitExpertReplyV2Dto, {
description: "Damage assessment",
parts: [
{
partId: 201,
typeOfDamage: TypeOfDamage.Repair,
price: "10000",
salary: "0",
totalPayment: "10000",
factorNeeded: false,
},
],
});
expect(await validate(dto)).toHaveLength(0);
});
});

View File

@@ -94,11 +94,17 @@ export class PartPricingV2Dto {
@IsRepairLineAmountToman({ allowZero: true })
totalPayment: string;
@ApiProperty({ type: DaghiDetailsV2Dto })
@ApiPropertyOptional({
type: DaghiDetailsV2Dto,
description: "Required for change lines; omit for repair lines.",
})
@ValidateIf(
(part: PartPricingV2Dto) => part.typeOfDamage === TypeOfDamage.Change,
)
@IsNotEmpty()
@ValidateNested()
@Type(() => DaghiDetailsV2Dto)
daghi: DaghiDetailsV2Dto;
daghi?: DaghiDetailsV2Dto;
@ApiProperty({
example: false,

View File

@@ -27,6 +27,31 @@ function createService() {
}
describe("ExpertClaimService expert-reply pricing", () => {
it("allows a repair line without daghi and removes a stray daghi payload", () => {
const service = createService() as any;
expect(
service.validateAndNormalizeDaghiForExpertReplyV2([
{
partId: 201,
typeOfDamage: TypeOfDamage.Repair,
price: "10000",
salary: "0",
totalPayment: "10000",
daghi: { option: DaghiOption.NO_VALUE },
},
]),
).toEqual([
{
partId: 201,
typeOfDamage: TypeOfDamage.Repair,
price: "10000",
salary: "0",
totalPayment: "10000",
},
]);
});
it("rejects a blank pricing line on the legacy submit endpoint before changing status", async () => {
const service = createService() as any;
const findAndUpdate = jest.fn();

View File

@@ -102,6 +102,7 @@ import { ClaimFactorsImageDbService } from "src/claim-request-management/entites
import { ClaimRequiredDocumentDbService } from "src/claim-request-management/entites/db-service/claim-required-document.db.service";
import { FactorStatus } from "src/Types&Enums/claim-request-management/factor-status.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 { BranchDbService } from "src/client/entities/db-service/branch.db.service";
import { ClaimSubmitResendV2Dto } from "./dto/expert-claim-v2.dto";
import { BlameRequestDbService } from "src/request-management/entities/db-service/blame-request.db.service";
@@ -947,6 +948,7 @@ export class ExpertClaimService {
parts: import("./dto/expert-claim-v2.dto").PartPricingV2Dto[],
) {
for (const part of parts) {
if (part.typeOfDamage === TypeOfDamage.Repair) continue;
if (!part.daghi || !part.daghi.option) {
throw new BadRequestException(
`Daghi option is required for part ${part.partId}`,
@@ -972,16 +974,23 @@ export class ExpertClaimService {
}
}
return parts.map((part) => ({
...part,
daghi: {
option: part.daghi.option,
...(part.daghi.price && { price: part.daghi.price }),
...(part.daghi.branchId && Types.ObjectId.isValid(part.daghi.branchId) && {
branchId: new Types.ObjectId(part.daghi.branchId),
}),
},
}));
return parts.map((part) => {
if (part.typeOfDamage === TypeOfDamage.Repair) {
const { daghi: _daghi, ...repairPart } = part;
return repairPart;
}
return {
...part,
daghi: {
option: part.daghi!.option,
...(part.daghi!.price && { price: part.daghi!.price }),
...(part.daghi!.branchId &&
Types.ObjectId.isValid(part.daghi!.branchId) && {
branchId: new Types.ObjectId(part.daghi!.branchId),
}),
},
};
});
}
private findClosestCar(input: string, cars: { carName: string }[]) {
@@ -1711,6 +1720,7 @@ export class ExpertClaimService {
// Validate daghi fields
if (reply.parts && reply.parts.length > 0) {
for (const part of reply.parts) {
if (part.typeOfDamage === TypeOfDamage.Repair) continue;
if (!part.daghi || !part.daghi.option) {
throw new BadRequestException(
`Daghi option is required for part ${part.partId}`,
@@ -1758,16 +1768,22 @@ export class ExpertClaimService {
: ClaimStepsEnum.WaitingForUserToReact;
// Process parts: convert branchId string to ObjectId if present
const processedParts = reply.parts.map((part) => ({
...part,
daghi: {
option: part.daghi.option,
...(part.daghi.price && { price: part.daghi.price }),
...(part.daghi.branchId && {
branchId: new Types.ObjectId(part.daghi.branchId),
}),
},
}));
const processedParts = reply.parts.map((part) => {
if (part.typeOfDamage === TypeOfDamage.Repair) {
const { daghi: _daghi, ...repairPart } = part;
return repairPart;
}
return {
...part,
daghi: {
option: part.daghi!.option,
...(part.daghi!.price && { price: part.daghi!.price }),
...(part.daghi!.branchId && {
branchId: new Types.ObjectId(part.daghi!.branchId),
}),
},
};
});
const expertProfileSnapshot = await this.snapshotDamageExpert(userId.sub);

View File

@@ -33,6 +33,20 @@ describe("getExpertReplyPricingValidationError", () => {
).toBeNull();
});
it("accepts a repair line without daghi", () => {
expect(
getExpertReplyPricingValidationError([
{
partId: 201,
typeOfDamage: TypeOfDamage.Repair,
price: "10000",
salary: "0",
totalPayment: "10000",
},
]),
).toBeNull();
});
it("rejects a repair line without a price", () => {
expect(
getExpertReplyPricingValidationError([

View File

@@ -42,9 +42,11 @@ export function getExpertReplyPricingValidationError(
return `${label} requires typeOfDamage to be either '${TypeOfDamage.Repair}' or '${TypeOfDamage.Change}'.`;
}
const daghi = part.daghi;
if (
!part.daghi ||
!Object.values(DaghiOption).includes(part.daghi.option as DaghiOption)
part.typeOfDamage === TypeOfDamage.Change &&
(!daghi ||
!Object.values(DaghiOption).includes(daghi.option as DaghiOption))
) {
return `${label} requires a valid daghi option.`;
}
@@ -52,7 +54,7 @@ export function getExpertReplyPricingValidationError(
const price = parseMoneyAmountToman(part.price);
const salary = parseMoneyAmountToman(part.salary);
const totalPayment = parseMoneyAmountToman(part.totalPayment);
const daghiPrice = parseMoneyAmountToman(part.daghi.price);
const daghiPrice = parseMoneyAmountToman(daghi?.price);
const hasPrice =
part.price != null &&
(typeof part.price !== "string" || part.price.trim() !== "");
@@ -80,7 +82,7 @@ export function getExpertReplyPricingValidationError(
}
if (
part.daghi.option === DaghiOption.RECYCLED_PARTS_VALUE &&
daghi?.option === DaghiOption.RECYCLED_PARTS_VALUE &&
(daghiPrice === null ||
daghiPrice < REPAIR_LINE_AMOUNT_TOMAN.MIN ||
daghiPrice > REPAIR_LINE_AMOUNT_TOMAN.MAX)