forked from Yara724/api
YARA-1117 and fixed completed status after in person visit has been called
This commit is contained in:
@@ -10533,7 +10533,8 @@ export class ClaimRequestManagementService {
|
|||||||
`Claim case with ID ${claimRequestId} not found`,
|
`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) {
|
if (!fileDetail) {
|
||||||
throw new BadRequestException("Video file is required.");
|
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) {
|
if (!captureProgress.partsComplete) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
"Upload photos for all selected damaged parts before the walk-around video.",
|
"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.",
|
"Capture all four car angles before the walk-around video.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (!isClaimCaptureStepComplete(claimCase)) {
|
if (!isClaimCaptureStepComplete(claimCase, { skipMetalPlate })) {
|
||||||
throw new BadRequestException(
|
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.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3530,6 +3530,7 @@ export class ExpertClaimService {
|
|||||||
|
|
||||||
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
||||||
claimStatus: ClaimStatus.NEEDS_REVISION,
|
claimStatus: ClaimStatus.NEEDS_REVISION,
|
||||||
|
status: ClaimCaseStatus.COMPLETED,
|
||||||
"workflow.locked": false,
|
"workflow.locked": false,
|
||||||
$unset: {
|
$unset: {
|
||||||
"workflow.lockedAt": "",
|
"workflow.lockedAt": "",
|
||||||
@@ -3567,7 +3568,7 @@ export class ExpertClaimService {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
claimRequestId,
|
claimRequestId,
|
||||||
status: ClaimWorkflowStep.CLAIM_COMPLETED,
|
status: ClaimCaseStatus.COMPLETED,
|
||||||
claimStatus: ClaimStatus.NEEDS_REVISION,
|
claimStatus: ClaimStatus.NEEDS_REVISION,
|
||||||
message: "In-person visit requested. User will be notified.",
|
message: "In-person visit requested. User will be notified.",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -118,8 +118,11 @@ export function getClaimCaptureProgress(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isClaimCaptureStepComplete(claimCase: any): boolean {
|
export function isClaimCaptureStepComplete(
|
||||||
const p = getClaimCaptureProgress(claimCase);
|
claimCase: any,
|
||||||
|
options?: { skipMetalPlate?: boolean },
|
||||||
|
): boolean {
|
||||||
|
const p = getClaimCaptureProgress(claimCase, options);
|
||||||
return (
|
return (
|
||||||
p.partsComplete && p.anglesComplete && p.capturePhaseDocsComplete
|
p.partsComplete && p.anglesComplete && p.capturePhaseDocsComplete
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -364,7 +364,6 @@ export class FileReviewerBlameV4Controller {
|
|||||||
@ApiBody({
|
@ApiBody({
|
||||||
schema: {
|
schema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
required: ["file"],
|
|
||||||
properties: { file: { type: "string", format: "binary" } },
|
properties: { file: { type: "string", format: "binary" } },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -385,17 +384,19 @@ export class FileReviewerBlameV4Controller {
|
|||||||
@Post("upload-video/:requestId")
|
@Post("upload-video/:requestId")
|
||||||
@ApiParam({ name: "requestId" })
|
@ApiParam({ name: "requestId" })
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Blame accident video (FileReviewer final step)",
|
summary: "Blame accident video (FileReviewer final step — optional)",
|
||||||
description:
|
description:
|
||||||
"Last step of the V4 flow. Requires claim walk-around video and completed capture. " +
|
"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(
|
async uploadBlameVideo(
|
||||||
@Param("requestId") requestId: string,
|
@Param("requestId") requestId: string,
|
||||||
@CurrentUser() fileReviewer: any,
|
@CurrentUser() fileReviewer: any,
|
||||||
@UploadedFile() file: Express.Multer.File,
|
@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(
|
return this.requestManagementService.expertUploadBlameVideoV3(
|
||||||
fileReviewer,
|
fileReviewer,
|
||||||
requestId,
|
requestId,
|
||||||
|
|||||||
@@ -365,7 +365,6 @@ export class FileReviewerBlameV5Controller {
|
|||||||
@ApiBody({
|
@ApiBody({
|
||||||
schema: {
|
schema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
required: ["file"],
|
|
||||||
properties: { file: { type: "string", format: "binary" } },
|
properties: { file: { type: "string", format: "binary" } },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -386,17 +385,19 @@ export class FileReviewerBlameV5Controller {
|
|||||||
@Post("upload-video/:requestId")
|
@Post("upload-video/:requestId")
|
||||||
@ApiParam({ name: "requestId" })
|
@ApiParam({ name: "requestId" })
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Blame accident video (FileReviewer final step — V5)",
|
summary: "Blame accident video (FileReviewer final step — V5, optional)",
|
||||||
description:
|
description:
|
||||||
"Last step of the V5 FileReviewer flow. Requires claim walk-around video and completed capture. " +
|
"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(
|
async uploadBlameVideo(
|
||||||
@Param("requestId") requestId: string,
|
@Param("requestId") requestId: string,
|
||||||
@CurrentUser() fileReviewer: any,
|
@CurrentUser() fileReviewer: any,
|
||||||
@UploadedFile() file: Express.Multer.File,
|
@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(
|
return this.requestManagementService.expertUploadBlameVideoV5(
|
||||||
fileReviewer,
|
fileReviewer,
|
||||||
requestId,
|
requestId,
|
||||||
|
|||||||
@@ -9325,21 +9325,31 @@ export class RequestManagementService {
|
|||||||
async expertUploadBlameVideoV3(
|
async expertUploadBlameVideoV3(
|
||||||
expert: any,
|
expert: any,
|
||||||
requestId: string,
|
requestId: string,
|
||||||
file: Express.Multer.File,
|
file: Express.Multer.File | undefined,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
requestId: string;
|
requestId: string;
|
||||||
publicId: string;
|
publicId: string;
|
||||||
videoId: string;
|
videoId: string | undefined;
|
||||||
status: string;
|
status: string;
|
||||||
message: string;
|
message: string;
|
||||||
}> {
|
}> {
|
||||||
if (!file) throw new BadRequestException("Video file is required");
|
|
||||||
|
|
||||||
const req = await this.blameRequestDbService.findById(requestId);
|
const req = await this.blameRequestDbService.findById(requestId);
|
||||||
if (!req) throw new NotFoundException("Request not found");
|
if (!req) throw new NotFoundException("Request not found");
|
||||||
this.assertBlameV3ExpertInPerson(req);
|
this.assertBlameV3ExpertInPerson(req);
|
||||||
await this.verifyExpertAccessForBlameV2(req, expert);
|
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) {
|
if (!(req.expert?.decision as any)?.fields?.accidentWay) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
"Submit accident fields before uploading the blame accident video.",
|
"Submit accident fields before uploading the blame accident video.",
|
||||||
@@ -9381,19 +9391,23 @@ export class RequestManagementService {
|
|||||||
const firstIdx = this.getPartyIndex(req, PartyRole.FIRST);
|
const firstIdx = this.getPartyIndex(req, PartyRole.FIRST);
|
||||||
if (firstIdx === -1) throw new BadRequestException("First party not found");
|
if (firstIdx === -1) throw new BadRequestException("First party not found");
|
||||||
const firstParty = req.parties[firstIdx];
|
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, [
|
this.pushWorkflowSteps(req, [
|
||||||
WorkflowStep.FIRST_VIDEO,
|
WorkflowStep.FIRST_VIDEO,
|
||||||
WorkflowStep.WAITING_FOR_GUILT_DECISION,
|
WorkflowStep.WAITING_FOR_GUILT_DECISION,
|
||||||
@@ -9410,7 +9424,7 @@ export class RequestManagementService {
|
|||||||
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
||||||
actorType: "field_expert",
|
actorType: "field_expert",
|
||||||
},
|
},
|
||||||
metadata: { videoId: firstParty.evidence.videoId },
|
metadata: { videoId: videoId ?? null },
|
||||||
} as any);
|
} as any);
|
||||||
|
|
||||||
if (typeof (req as any).markModified === "function") {
|
if (typeof (req as any).markModified === "function") {
|
||||||
@@ -9449,10 +9463,11 @@ export class RequestManagementService {
|
|||||||
return {
|
return {
|
||||||
requestId: String(req._id),
|
requestId: String(req._id),
|
||||||
publicId: req.publicId,
|
publicId: req.publicId,
|
||||||
videoId: firstParty.evidence.videoId,
|
videoId,
|
||||||
status: req.status,
|
status: req.status,
|
||||||
message:
|
message: file
|
||||||
"Blame accident video uploaded. File is now waiting for expert review.",
|
? "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(
|
async expertUploadBlameVideoV5(
|
||||||
expert: any,
|
expert: any,
|
||||||
requestId: string,
|
requestId: string,
|
||||||
file: Express.Multer.File,
|
file: Express.Multer.File | undefined,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
requestId: string;
|
requestId: string;
|
||||||
publicId: string;
|
publicId: string;
|
||||||
videoId: string;
|
videoId: string | undefined;
|
||||||
status: string;
|
status: string;
|
||||||
message: string;
|
message: string;
|
||||||
}> {
|
}> {
|
||||||
if (!file) throw new BadRequestException("Video file is required");
|
|
||||||
|
|
||||||
const req = await this.blameRequestDbService.findById(requestId);
|
const req = await this.blameRequestDbService.findById(requestId);
|
||||||
if (!req) throw new NotFoundException("Request not found");
|
if (!req) throw new NotFoundException("Request not found");
|
||||||
this.assertBlameV3ExpertInPerson(req);
|
this.assertBlameV3ExpertInPerson(req);
|
||||||
await this.verifyExpertAccessForBlameV2(req, expert);
|
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) {
|
if (!(req.expert?.decision as any)?.fields?.accidentWay) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
"Submit accident fields before uploading the blame accident video.",
|
"Submit accident fields before uploading the blame accident video.",
|
||||||
@@ -9750,19 +9775,23 @@ export class RequestManagementService {
|
|||||||
const firstIdx = this.getPartyIndex(req, PartyRole.FIRST);
|
const firstIdx = this.getPartyIndex(req, PartyRole.FIRST);
|
||||||
if (firstIdx === -1) throw new BadRequestException("First party not found");
|
if (firstIdx === -1) throw new BadRequestException("First party not found");
|
||||||
const firstParty = req.parties[firstIdx];
|
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, [
|
this.pushWorkflowSteps(req, [
|
||||||
WorkflowStep.FIRST_VIDEO,
|
WorkflowStep.FIRST_VIDEO,
|
||||||
WorkflowStep.WAITING_FOR_GUILT_DECISION,
|
WorkflowStep.WAITING_FOR_GUILT_DECISION,
|
||||||
@@ -9779,7 +9808,7 @@ export class RequestManagementService {
|
|||||||
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
|
||||||
actorType: "file_reviewer",
|
actorType: "file_reviewer",
|
||||||
},
|
},
|
||||||
metadata: { videoId: firstParty.evidence.videoId },
|
metadata: { videoId: videoId ?? null },
|
||||||
} as any);
|
} as any);
|
||||||
|
|
||||||
if (typeof (req as any).markModified === "function") {
|
if (typeof (req as any).markModified === "function") {
|
||||||
@@ -9825,11 +9854,11 @@ export class RequestManagementService {
|
|||||||
return {
|
return {
|
||||||
requestId: String(req._id),
|
requestId: String(req._id),
|
||||||
publicId: req.publicId,
|
publicId: req.publicId,
|
||||||
videoId: firstParty.evidence.videoId,
|
videoId,
|
||||||
status: req.status,
|
status: req.status,
|
||||||
message:
|
message: file
|
||||||
"Blame accident video uploaded. File is now in expert review queue. " +
|
? "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."
|
||||||
"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.",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user