Merge pull request 'Fixed daqi being enforced on REPAIR' (#288) from s.yahyaee/yara724-api:main into main

Reviewed-on: Yara724/api#288
This commit is contained in:
2026-09-05 18:42:37 +03:30
6 changed files with 107 additions and 26 deletions

View File

@@ -43,4 +43,22 @@ describe("SubmitExpertReplyV2Dto", () => {
expect(await validate(dto)).toHaveLength(0); 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 }) @IsRepairLineAmountToman({ allowZero: true })
totalPayment: string; 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() @IsNotEmpty()
@ValidateNested() @ValidateNested()
@Type(() => DaghiDetailsV2Dto) @Type(() => DaghiDetailsV2Dto)
daghi: DaghiDetailsV2Dto; daghi?: DaghiDetailsV2Dto;
@ApiProperty({ @ApiProperty({
example: false, example: false,

View File

@@ -27,6 +27,31 @@ function createService() {
} }
describe("ExpertClaimService expert-reply pricing", () => { 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 () => { it("rejects a blank pricing line on the legacy submit endpoint before changing status", async () => {
const service = createService() as any; const service = createService() as any;
const findAndUpdate = jest.fn(); 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 { 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 { FactorStatus } from "src/Types&Enums/claim-request-management/factor-status.enum";
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 { BranchDbService } from "src/client/entities/db-service/branch.db.service"; import { BranchDbService } from "src/client/entities/db-service/branch.db.service";
import { ClaimSubmitResendV2Dto } from "./dto/expert-claim-v2.dto"; import { ClaimSubmitResendV2Dto } from "./dto/expert-claim-v2.dto";
import { BlameRequestDbService } from "src/request-management/entities/db-service/blame-request.db.service"; 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[], parts: import("./dto/expert-claim-v2.dto").PartPricingV2Dto[],
) { ) {
for (const part of parts) { for (const part of parts) {
if (part.typeOfDamage === TypeOfDamage.Repair) continue;
if (!part.daghi || !part.daghi.option) { if (!part.daghi || !part.daghi.option) {
throw new BadRequestException( throw new BadRequestException(
`Daghi option is required for part ${part.partId}`, `Daghi option is required for part ${part.partId}`,
@@ -972,16 +974,23 @@ export class ExpertClaimService {
} }
} }
return parts.map((part) => ({ return parts.map((part) => {
if (part.typeOfDamage === TypeOfDamage.Repair) {
const { daghi: _daghi, ...repairPart } = part;
return repairPart;
}
return {
...part, ...part,
daghi: { daghi: {
option: part.daghi.option, option: part.daghi!.option,
...(part.daghi.price && { price: part.daghi.price }), ...(part.daghi!.price && { price: part.daghi!.price }),
...(part.daghi.branchId && Types.ObjectId.isValid(part.daghi.branchId) && { ...(part.daghi!.branchId &&
branchId: new Types.ObjectId(part.daghi.branchId), Types.ObjectId.isValid(part.daghi!.branchId) && {
branchId: new Types.ObjectId(part.daghi!.branchId),
}), }),
}, },
})); };
});
} }
private findClosestCar(input: string, cars: { carName: string }[]) { private findClosestCar(input: string, cars: { carName: string }[]) {
@@ -1711,6 +1720,7 @@ export class ExpertClaimService {
// Validate daghi fields // Validate daghi fields
if (reply.parts && reply.parts.length > 0) { if (reply.parts && reply.parts.length > 0) {
for (const part of reply.parts) { for (const part of reply.parts) {
if (part.typeOfDamage === TypeOfDamage.Repair) continue;
if (!part.daghi || !part.daghi.option) { if (!part.daghi || !part.daghi.option) {
throw new BadRequestException( throw new BadRequestException(
`Daghi option is required for part ${part.partId}`, `Daghi option is required for part ${part.partId}`,
@@ -1758,16 +1768,22 @@ export class ExpertClaimService {
: ClaimStepsEnum.WaitingForUserToReact; : ClaimStepsEnum.WaitingForUserToReact;
// Process parts: convert branchId string to ObjectId if present // Process parts: convert branchId string to ObjectId if present
const processedParts = reply.parts.map((part) => ({ const processedParts = reply.parts.map((part) => {
if (part.typeOfDamage === TypeOfDamage.Repair) {
const { daghi: _daghi, ...repairPart } = part;
return repairPart;
}
return {
...part, ...part,
daghi: { daghi: {
option: part.daghi.option, option: part.daghi!.option,
...(part.daghi.price && { price: part.daghi.price }), ...(part.daghi!.price && { price: part.daghi!.price }),
...(part.daghi.branchId && { ...(part.daghi!.branchId && {
branchId: new Types.ObjectId(part.daghi.branchId), branchId: new Types.ObjectId(part.daghi!.branchId),
}), }),
}, },
})); };
});
const expertProfileSnapshot = await this.snapshotDamageExpert(userId.sub); const expertProfileSnapshot = await this.snapshotDamageExpert(userId.sub);

View File

@@ -33,6 +33,20 @@ describe("getExpertReplyPricingValidationError", () => {
).toBeNull(); ).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", () => { it("rejects a repair line without a price", () => {
expect( expect(
getExpertReplyPricingValidationError([ getExpertReplyPricingValidationError([

View File

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