YARA-1117 and fixed completed status after in person visit has been called

This commit is contained in:
SepehrYahyaee
2026-07-14 11:51:52 +03:30
parent 4aa6e03afb
commit 168e52a475
6 changed files with 92 additions and 54 deletions

View File

@@ -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.",
);
}

View File

@@ -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.",
};

View File

@@ -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
);

View File

@@ -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,
) {
if (file) {
await this.mediaPolicyService.assertForBlame(file, requestId, "video");
}
return this.requestManagementService.expertUploadBlameVideoV3(
fileReviewer,
requestId,

View File

@@ -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,
) {
if (file) {
await this.mediaPolicyService.assertForBlame(file, requestId, "video");
}
return this.requestManagementService.expertUploadBlameVideoV5(
fileReviewer,
requestId,

View File

@@ -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,18 +9391,22 @@ 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];
// 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;
}
this.pushWorkflowSteps(req, [
WorkflowStep.FIRST_VIDEO,
@@ -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,18 +9775,22 @@ 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];
// 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;
}
this.pushWorkflowSteps(req, [
WorkflowStep.FIRST_VIDEO,
@@ -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.",
};
}