From f77da50d1f4ea333f3c5ebddf1acef52c757d9af Mon Sep 17 00:00:00 2001 From: "s.yahyaee" Date: Mon, 6 Apr 2026 14:01:45 +0330 Subject: [PATCH] YARA-743 --- .../dto/update-claim-damaged-parts-v2.dto.ts | 25 +++++++ src/expert-claim/expert-claim.service.ts | 74 +++++++++++++++++++ .../expert-claim.v2.controller.ts | 21 ++++++ 3 files changed, 120 insertions(+) create mode 100644 src/expert-claim/dto/update-claim-damaged-parts-v2.dto.ts diff --git a/src/expert-claim/dto/update-claim-damaged-parts-v2.dto.ts b/src/expert-claim/dto/update-claim-damaged-parts-v2.dto.ts new file mode 100644 index 0000000..1e01288 --- /dev/null +++ b/src/expert-claim/dto/update-claim-damaged-parts-v2.dto.ts @@ -0,0 +1,25 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { + ArrayMinSize, + ArrayUnique, + IsArray, + IsEnum, + IsNotEmpty, +} from "class-validator"; +import { OuterCarPart } from "src/claim-request-management/dto/select-outer-parts-v2.dto"; + +export class UpdateClaimDamagedPartsV2Dto { + @ApiProperty({ + description: "Updated list of selected damaged outer parts", + enum: OuterCarPart, + isArray: true, + example: ["hood", "front_bumper", "front_left_fender"], + }) + @IsNotEmpty() + @IsArray() + @ArrayMinSize(1) + @ArrayUnique() + @IsEnum(OuterCarPart, { each: true }) + selectedParts: OuterCarPart[]; +} + diff --git a/src/expert-claim/expert-claim.service.ts b/src/expert-claim/expert-claim.service.ts index 530b936..210954b 100644 --- a/src/expert-claim/expert-claim.service.ts +++ b/src/expert-claim/expert-claim.service.ts @@ -36,6 +36,7 @@ import { ClaimWorkflowStep } from "src/Types&Enums/claim-request-management/clai import { GetClaimListV2ResponseDto, ClaimListItemV2Dto } from "./dto/claim-list-v2.dto"; import { ClaimDetailV2ResponseDto } from "./dto/claim-detail-v2.dto"; import { ClaimSubmitReplyDto, ClaimSubmitResendDto } from "./dto/reply.dto"; +import { UpdateClaimDamagedPartsV2Dto } from "./dto/update-claim-damaged-parts-v2.dto"; import { ClaimFactorsImageDbService } from "src/claim-request-management/entites/db-service/factor-image.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"; @@ -1969,4 +1970,77 @@ export class ExpertClaimService { updatedAt: (claim as any).updatedAt, }; } + + /** + * V2: Damage expert can modify selected damaged parts before final submit. + * Allowed only when claim is EXPERT_REVIEWING and locked by this expert. + */ + async updateClaimDamagedPartsV2( + claimRequestId: string, + body: UpdateClaimDamagedPartsV2Dto, + actor: any, + ) { + const claim = await this.claimCaseDbService.findById(claimRequestId); + if (!claim) { + throw new NotFoundException("Claim request not found"); + } + if (claim.status !== ClaimCaseStatus.EXPERT_REVIEWING) { + throw new BadRequestException( + `Claim is not in EXPERT_REVIEWING state. Current status: ${claim.status}`, + ); + } + if (!claim.workflow?.locked) { + throw new ForbiddenException( + "You must lock the claim before modifying damaged parts", + ); + } + if (claim.workflow.lockedBy?.actorId?.toString() !== actor.sub) { + throw new ForbiddenException("This claim is locked by another expert"); + } + + const previous = Array.isArray((claim as any).damage?.selectedParts) + ? [...(claim as any).damage.selectedParts] + : []; + const selectedParts = body.selectedParts as string[]; + + if (!(claim as any).damage) (claim as any).damage = {}; + (claim as any).damage.selectedParts = selectedParts; + + // Keep damaged-parts captures consistent with updated selection. + const damagedPartsMedia = (claim as any).media?.damagedParts; + if (damagedPartsMedia) { + const selectedSet = new Set(selectedParts); + if (damagedPartsMedia instanceof Map) { + for (const key of Array.from(damagedPartsMedia.keys())) { + if (!selectedSet.has(key)) damagedPartsMedia.delete(key); + } + } else { + for (const key of Object.keys(damagedPartsMedia)) { + if (!selectedSet.has(key)) delete damagedPartsMedia[key]; + } + } + } + + if (!Array.isArray((claim as any).history)) (claim as any).history = []; + (claim as any).history.push({ + event: "EXPERT_DAMAGED_PARTS_UPDATED", + performedBy: actor.sub, + performedByName: actor.fullName, + performedAt: new Date(), + note: "Damage expert edited selected damaged parts", + metadata: { + previousSelectedParts: previous, + selectedParts, + }, + }); + + await (claim as any).save(); + + return { + claimRequestId: claim._id.toString(), + selectedParts, + previousSelectedParts: previous, + message: "Damaged parts updated successfully.", + }; + } } diff --git a/src/expert-claim/expert-claim.v2.controller.ts b/src/expert-claim/expert-claim.v2.controller.ts index ca146a9..cbc6c57 100644 --- a/src/expert-claim/expert-claim.v2.controller.ts +++ b/src/expert-claim/expert-claim.v2.controller.ts @@ -15,6 +15,7 @@ import { CurrentUser } from "src/decorators/user.decorator"; import { RoleEnum } from "src/Types&Enums/role.enum"; import { ExpertClaimService } from "./expert-claim.service"; import { SubmitExpertReplyV2Dto } from "./dto/expert-claim-v2.dto"; +import { UpdateClaimDamagedPartsV2Dto } from "./dto/update-claim-damaged-parts-v2.dto"; class InPersonVisitV2Dto { @ApiPropertyOptional({ example: 'Paint damage requires physical inspection' }) @@ -107,4 +108,24 @@ export class ExpertClaimV2Controller { body?.note, ); } + + @Patch("request/:claimRequestId/damaged-parts") + @ApiOperation({ + summary: "Edit selected damaged parts (V2)", + description: + "Damage expert can modify selected damaged parts while claim is in EXPERT_REVIEWING and locked by the same expert.", + }) + @ApiParam({ name: "claimRequestId" }) + @ApiBody({ type: UpdateClaimDamagedPartsV2Dto }) + async updateClaimDamagedPartsV2( + @Param("claimRequestId") claimRequestId: string, + @Body() body: UpdateClaimDamagedPartsV2Dto, + @CurrentUser() actor, + ) { + return await this.expertClaimService.updateClaimDamagedPartsV2( + claimRequestId, + body, + actor, + ); + } }