From 168e52a4758c2337ccf71d64e30b6e939142b42f Mon Sep 17 00:00:00 2001 From: SepehrYahyaee <7heycallmegray@gmail.com> Date: Tue, 14 Jul 2026 11:51:52 +0330 Subject: [PATCH] YARA-1117 and fixed completed status after in person visit has been called --- .../claim-request-management.service.ts | 11 +- src/expert-claim/expert-claim.service.ts | 3 +- src/helpers/claim-capture-phase.ts | 7 +- .../file-reviewer-blame-v4.controller.ts | 9 +- .../file-reviewer-blame-v5.controller.ts | 9 +- .../request-management.service.ts | 107 +++++++++++------- 6 files changed, 92 insertions(+), 54 deletions(-) diff --git a/src/claim-request-management/claim-request-management.service.ts b/src/claim-request-management/claim-request-management.service.ts index 4df7092..d126782 100644 --- a/src/claim-request-management/claim-request-management.service.ts +++ b/src/claim-request-management/claim-request-management.service.ts @@ -10533,7 +10533,8 @@ export class ClaimRequestManagementService { `Claim case with ID ${claimRequestId} not found`, ); } - await this.assertV3InPersonClaim(claimCase); + const blame = await this.assertV3InPersonClaim(claimCase); + const skipMetalPlate = !!(blame as any).isMadeByFileMaker; if (!fileDetail) { throw new BadRequestException("Video file is required."); @@ -10553,7 +10554,7 @@ export class ClaimRequestManagementService { ); } - const captureProgress = getClaimCaptureProgress(claimCase); + const captureProgress = getClaimCaptureProgress(claimCase, { skipMetalPlate }); if (!captureProgress.partsComplete) { throw new BadRequestException( "Upload photos for all selected damaged parts before the walk-around video.", @@ -10564,9 +10565,11 @@ export class ClaimRequestManagementService { "Capture all four car angles before the walk-around video.", ); } - if (!isClaimCaptureStepComplete(claimCase)) { + if (!isClaimCaptureStepComplete(claimCase, { skipMetalPlate })) { throw new BadRequestException( - "Upload capture-phase vehicle documents (chassis, engine, metal plate) before the walk-around video.", + skipMetalPlate + ? "Upload capture-phase vehicle documents (chassis and engine) before the walk-around video." + : "Upload capture-phase vehicle documents (chassis, engine, metal plate) before the walk-around video.", ); } diff --git a/src/expert-claim/expert-claim.service.ts b/src/expert-claim/expert-claim.service.ts index cd7cd12..8262187 100644 --- a/src/expert-claim/expert-claim.service.ts +++ b/src/expert-claim/expert-claim.service.ts @@ -3530,6 +3530,7 @@ export class ExpertClaimService { await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, { claimStatus: ClaimStatus.NEEDS_REVISION, + status: ClaimCaseStatus.COMPLETED, "workflow.locked": false, $unset: { "workflow.lockedAt": "", @@ -3567,7 +3568,7 @@ export class ExpertClaimService { return { claimRequestId, - status: ClaimWorkflowStep.CLAIM_COMPLETED, + status: ClaimCaseStatus.COMPLETED, claimStatus: ClaimStatus.NEEDS_REVISION, message: "In-person visit requested. User will be notified.", }; diff --git a/src/helpers/claim-capture-phase.ts b/src/helpers/claim-capture-phase.ts index 5f30598..43e5046 100644 --- a/src/helpers/claim-capture-phase.ts +++ b/src/helpers/claim-capture-phase.ts @@ -118,8 +118,11 @@ export function getClaimCaptureProgress( }; } -export function isClaimCaptureStepComplete(claimCase: any): boolean { - const p = getClaimCaptureProgress(claimCase); +export function isClaimCaptureStepComplete( + claimCase: any, + options?: { skipMetalPlate?: boolean }, +): boolean { + const p = getClaimCaptureProgress(claimCase, options); return ( p.partsComplete && p.anglesComplete && p.capturePhaseDocsComplete ); diff --git a/src/request-management/file-reviewer-blame-v4.controller.ts b/src/request-management/file-reviewer-blame-v4.controller.ts index c5cbf8a..167d855 100644 --- a/src/request-management/file-reviewer-blame-v4.controller.ts +++ b/src/request-management/file-reviewer-blame-v4.controller.ts @@ -364,7 +364,6 @@ export class FileReviewerBlameV4Controller { @ApiBody({ schema: { type: "object", - required: ["file"], properties: { file: { type: "string", format: "binary" } }, }, }) @@ -385,17 +384,19 @@ export class FileReviewerBlameV4Controller { @Post("upload-video/:requestId") @ApiParam({ name: "requestId" }) @ApiOperation({ - summary: "Blame accident video (FileReviewer final step)", + summary: "Blame accident video (FileReviewer final step — optional)", description: "Last step of the V4 flow. Requires claim walk-around video and completed capture. " + - "Sets blame status to WAITING_FOR_EXPERT.", + "File is optional — omit it to complete the blame without attaching a video.", }) async uploadBlameVideo( @Param("requestId") requestId: string, @CurrentUser() fileReviewer: any, @UploadedFile() file: Express.Multer.File, ) { - await this.mediaPolicyService.assertForBlame(file, requestId, "video"); + if (file) { + await this.mediaPolicyService.assertForBlame(file, requestId, "video"); + } return this.requestManagementService.expertUploadBlameVideoV3( fileReviewer, requestId, diff --git a/src/request-management/file-reviewer-blame-v5.controller.ts b/src/request-management/file-reviewer-blame-v5.controller.ts index 6159bc5..225cc37 100644 --- a/src/request-management/file-reviewer-blame-v5.controller.ts +++ b/src/request-management/file-reviewer-blame-v5.controller.ts @@ -365,7 +365,6 @@ export class FileReviewerBlameV5Controller { @ApiBody({ schema: { type: "object", - required: ["file"], properties: { file: { type: "string", format: "binary" } }, }, }) @@ -386,17 +385,19 @@ export class FileReviewerBlameV5Controller { @Post("upload-video/:requestId") @ApiParam({ name: "requestId" }) @ApiOperation({ - summary: "Blame accident video (FileReviewer final step — V5)", + summary: "Blame accident video (FileReviewer final step — V5, optional)", description: "Last step of the V5 FileReviewer flow. Requires claim walk-around video and completed capture. " + - "Sets blame status to WAITING_FOR_FINANCIAL_EXPERT (pending FinancialExpert approval).", + "File is optional — omit it to complete the blame without attaching a video.", }) async uploadBlameVideo( @Param("requestId") requestId: string, @CurrentUser() fileReviewer: any, @UploadedFile() file: Express.Multer.File, ) { - await this.mediaPolicyService.assertForBlame(file, requestId, "video"); + if (file) { + await this.mediaPolicyService.assertForBlame(file, requestId, "video"); + } return this.requestManagementService.expertUploadBlameVideoV5( fileReviewer, requestId, diff --git a/src/request-management/request-management.service.ts b/src/request-management/request-management.service.ts index 9a8f51e..68ba407 100644 --- a/src/request-management/request-management.service.ts +++ b/src/request-management/request-management.service.ts @@ -9325,21 +9325,31 @@ export class RequestManagementService { async expertUploadBlameVideoV3( expert: any, requestId: string, - file: Express.Multer.File, + file: Express.Multer.File | undefined, ): Promise<{ requestId: string; publicId: string; - videoId: string; + videoId: string | undefined; status: string; message: string; }> { - if (!file) throw new BadRequestException("Video file is required"); - const req = await this.blameRequestDbService.findById(requestId); if (!req) throw new NotFoundException("Request not found"); this.assertBlameV3ExpertInPerson(req); await this.verifyExpertAccessForBlameV2(req, expert); + // Idempotent: if blame is already completed (car-capture auto-completed it + // for V4/V5, or a previous call already ran), return success immediately. + if ((req as any).status === CaseStatus.COMPLETED) { + return { + requestId: String(req._id), + publicId: req.publicId, + videoId: undefined, + status: (req as any).status, + message: "File already completed.", + }; + } + if (!(req.expert?.decision as any)?.fields?.accidentWay) { throw new BadRequestException( "Submit accident fields before uploading the blame accident video.", @@ -9381,19 +9391,23 @@ export class RequestManagementService { const firstIdx = this.getPartyIndex(req, PartyRole.FIRST); if (firstIdx === -1) throw new BadRequestException("First party not found"); const firstParty = req.parties[firstIdx]; - if (firstParty?.evidence?.videoId) { - throw new ConflictException("Blame accident video already uploaded"); + + // Store video if provided; skip silently for V4/V5 where it is optional. + let videoId: string | undefined; + if (file) { + if (firstParty?.evidence?.videoId) { + throw new ConflictException("Blame accident video already uploaded"); + } + const videoDocument = await this.blameVideoDbService.create({ + fileName: file.filename, + path: file.path, + requestId: new Types.ObjectId(requestId), + } as any); + if (!firstParty.evidence) firstParty.evidence = {} as any; + firstParty.evidence.videoId = String((videoDocument as any)._id); + videoId = firstParty.evidence.videoId; } - const videoDocument = await this.blameVideoDbService.create({ - fileName: file.filename, - path: file.path, - requestId: new Types.ObjectId(requestId), - } as any); - - if (!firstParty.evidence) firstParty.evidence = {} as any; - firstParty.evidence.videoId = String((videoDocument as any)._id); - this.pushWorkflowSteps(req, [ WorkflowStep.FIRST_VIDEO, WorkflowStep.WAITING_FOR_GUILT_DECISION, @@ -9410,7 +9424,7 @@ export class RequestManagementService { actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(), actorType: "field_expert", }, - metadata: { videoId: firstParty.evidence.videoId }, + metadata: { videoId: videoId ?? null }, } as any); if (typeof (req as any).markModified === "function") { @@ -9449,10 +9463,11 @@ export class RequestManagementService { return { requestId: String(req._id), publicId: req.publicId, - videoId: firstParty.evidence.videoId, + videoId, status: req.status, - message: - "Blame accident video uploaded. File is now waiting for expert review.", + message: file + ? "Blame accident video uploaded. File is now waiting for expert review." + : "File completed. Claim is now ready for damage expert review.", }; } @@ -9694,21 +9709,31 @@ export class RequestManagementService { async expertUploadBlameVideoV5( expert: any, requestId: string, - file: Express.Multer.File, + file: Express.Multer.File | undefined, ): Promise<{ requestId: string; publicId: string; - videoId: string; + videoId: string | undefined; status: string; message: string; }> { - if (!file) throw new BadRequestException("Video file is required"); - const req = await this.blameRequestDbService.findById(requestId); if (!req) throw new NotFoundException("Request not found"); this.assertBlameV3ExpertInPerson(req); await this.verifyExpertAccessForBlameV2(req, expert); + // Idempotent: blame already completed (car-capture auto-completed it for + // V4/V5, or a previous call already ran). + if ((req as any).status === CaseStatus.COMPLETED) { + return { + requestId: String(req._id), + publicId: req.publicId, + videoId: undefined, + status: (req as any).status, + message: "File already completed.", + }; + } + if (!(req.expert?.decision as any)?.fields?.accidentWay) { throw new BadRequestException( "Submit accident fields before uploading the blame accident video.", @@ -9750,19 +9775,23 @@ export class RequestManagementService { const firstIdx = this.getPartyIndex(req, PartyRole.FIRST); if (firstIdx === -1) throw new BadRequestException("First party not found"); const firstParty = req.parties[firstIdx]; - if (firstParty?.evidence?.videoId) { - throw new ConflictException("Blame accident video already uploaded"); + + // Store video if provided; skip silently for V4/V5 where it is optional. + let videoId: string | undefined; + if (file) { + if (firstParty?.evidence?.videoId) { + throw new ConflictException("Blame accident video already uploaded"); + } + const videoDocument = await this.blameVideoDbService.create({ + fileName: file.filename, + path: file.path, + requestId: new Types.ObjectId(requestId), + } as any); + if (!firstParty.evidence) firstParty.evidence = {} as any; + firstParty.evidence.videoId = String((videoDocument as any)._id); + videoId = firstParty.evidence.videoId; } - const videoDocument = await this.blameVideoDbService.create({ - fileName: file.filename, - path: file.path, - requestId: new Types.ObjectId(requestId), - } as any); - - if (!firstParty.evidence) firstParty.evidence = {} as any; - firstParty.evidence.videoId = String((videoDocument as any)._id); - this.pushWorkflowSteps(req, [ WorkflowStep.FIRST_VIDEO, WorkflowStep.WAITING_FOR_GUILT_DECISION, @@ -9779,7 +9808,7 @@ export class RequestManagementService { actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(), actorType: "file_reviewer", }, - metadata: { videoId: firstParty.evidence.videoId }, + metadata: { videoId: videoId ?? null }, } as any); if (typeof (req as any).markModified === "function") { @@ -9825,11 +9854,11 @@ export class RequestManagementService { return { requestId: String(req._id), publicId: req.publicId, - videoId: firstParty.evidence.videoId, + videoId, status: req.status, - message: - "Blame accident video uploaded. File is now in expert review queue. " + - "After the full claim flow completes and the owner signs, the FileMaker must approve before fanavaran submission.", + message: file + ? "Blame accident video uploaded. File is now in expert review queue. After the full claim flow completes and the owner signs, the FileMaker must approve before fanavaran submission." + : "File completed. Claim is now ready for damage expert review.", }; }