1
0
forked from Yara724/api

Compare commits

...

3 Commits

Author SHA1 Message Date
SepehrYahyaee
168e52a475 YARA-1117 and fixed completed status after in person visit has been called 2026-07-14 11:51:52 +03:30
SepehrYahyaee
4aa6e03afb YARA-1119 2026-07-14 11:32:03 +03:30
SepehrYahyaee
36a34e27b3 YARA-1115 2026-07-14 11:23:39 +03:30
6 changed files with 144 additions and 73 deletions

View File

@@ -161,6 +161,7 @@ import {
getClaimCaptureProgress, getClaimCaptureProgress,
isCapturePhaseDamagedPartyDocKey, isCapturePhaseDamagedPartyDocKey,
isClaimCaptureStepComplete, isClaimCaptureStepComplete,
OPTIONAL_CAPTURE_PHASE_DOC_KEYS_V4V5,
} from "src/helpers/claim-capture-phase"; } from "src/helpers/claim-capture-phase";
import { HttpService } from "@nestjs/axios"; import { HttpService } from "@nestjs/axios";
import { firstValueFrom } from "rxjs"; import { firstValueFrom } from "rxjs";
@@ -8138,7 +8139,7 @@ export class ClaimRequestManagementService {
file: Express.Multer.File, file: Express.Multer.File,
currentUserId: string, currentUserId: string,
actor?: { sub: string; role?: string }, actor?: { sub: string; role?: string },
options?: { v3InPersonFlow?: boolean }, options?: { v3InPersonFlow?: boolean; skipMetalPlate?: boolean },
): Promise<UploadRequiredDocumentV2ResponseDto> { ): Promise<UploadRequiredDocumentV2ResponseDto> {
try { try {
const claimCase = await this.claimCaseDbService.findById(claimRequestId); const claimCase = await this.claimCaseDbService.findById(claimRequestId);
@@ -8315,12 +8316,16 @@ export class ClaimRequestManagementService {
k === body.documentKey || k === body.documentKey ||
this.isRequiredDocumentUploadedOnClaim(claimCase, k); this.isRequiredDocumentUploadedOnClaim(claimCase, k);
remaining = CAPTURE_PHASE_DAMAGED_PARTY_DOC_KEYS.filter( remaining = CAPTURE_PHASE_DAMAGED_PARTY_DOC_KEYS.filter(
(k) => !afterThis(k), (k) => {
if (options?.skipMetalPlate && OPTIONAL_CAPTURE_PHASE_DOC_KEYS_V4V5.includes(k as any)) return false;
return !afterThis(k);
},
).length; ).length;
if (remaining === 0) { if (remaining === 0) {
const progressAfterDoc = getClaimCaptureProgress(claimCase, { const progressAfterDoc = getClaimCaptureProgress(claimCase, {
assumeCapturePhaseDocKey: body.documentKey, assumeCapturePhaseDocKey: body.documentKey,
skipMetalPlate: options?.skipMetalPlate,
}); });
if ( if (
@@ -10196,9 +10201,10 @@ export class ClaimRequestManagementService {
private shapeCaptureRequirementsForV3( private shapeCaptureRequirementsForV3(
claimCase: any, claimCase: any,
_blame: any, blame: any,
base: GetCaptureRequirementsV2ResponseDto, base: GetCaptureRequirementsV2ResponseDto,
): GetCaptureRequirementsV2ResponseDto { ): GetCaptureRequirementsV2ResponseDto {
const skipMetalPlate = !!(blame as any)?.isMadeByFileMaker;
const step = claimCase.workflow?.currentStep; const step = claimCase.workflow?.currentStep;
const capturePartDone = this.claimV3StepCompleted( const capturePartDone = this.claimV3StepCompleted(
claimCase, claimCase,
@@ -10258,7 +10264,9 @@ export class ClaimRequestManagementService {
if (step === ClaimWorkflowStep.CAPTURE_PART_DAMAGES) { if (step === ClaimWorkflowStep.CAPTURE_PART_DAMAGES) {
const capturePhaseDocs = base.requiredDocuments.filter( const capturePhaseDocs = base.requiredDocuments.filter(
(d) => d.preferUploadDuringCapture, (d) =>
d.preferUploadDuringCapture &&
!(skipMetalPlate && OPTIONAL_CAPTURE_PHASE_DOC_KEYS_V4V5.includes(d.key as any)),
); );
return { return {
...base, ...base,
@@ -10298,6 +10306,7 @@ export class ClaimRequestManagementService {
); );
} }
const blame = await this.assertV3InPersonClaim(claimCase); const blame = await this.assertV3InPersonClaim(claimCase);
const skipMetalPlate = !!(blame as any).isMadeByFileMaker;
const isCaptureDoc = isCapturePhaseDamagedPartyDocKey(body.documentKey); const isCaptureDoc = isCapturePhaseDamagedPartyDocKey(body.documentKey);
@@ -10306,7 +10315,7 @@ export class ClaimRequestManagementService {
this.assertV3ClaimWorkflowStep( this.assertV3ClaimWorkflowStep(
claimCase, claimCase,
ClaimWorkflowStep.CAPTURE_PART_DAMAGES, ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
"Upload chassis, engine, and metal plate photos during capture after damaged-part photos and car angles.", "Upload chassis and engine photos during capture after damaged-part photos and car angles.",
); );
if ( if (
!this.claimV3StepCompleted( !this.claimV3StepCompleted(
@@ -10318,15 +10327,15 @@ export class ClaimRequestManagementService {
"Complete outer and other part selection before capture-phase vehicle documents.", "Complete outer and other part selection before capture-phase vehicle documents.",
); );
} }
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 chassis, engine, or metal plate photos.", "Upload photos for all selected damaged parts before chassis or engine photos.",
); );
} }
if (!captureProgress.anglesComplete) { if (!captureProgress.anglesComplete) {
throw new BadRequestException( throw new BadRequestException(
"Capture all four car angles before chassis, engine, or metal plate photos.", "Capture all four car angles before chassis or engine photos.",
); );
} }
} else { } else {
@@ -10346,7 +10355,7 @@ export class ClaimRequestManagementService {
file, file,
currentUserId, currentUserId,
actor, actor,
{ v3InPersonFlow: true }, { v3InPersonFlow: true, skipMetalPlate },
); );
} }
@@ -10524,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.");
@@ -10544,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.",
@@ -10555,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.",
); );
} }

View File

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

View File

@@ -16,6 +16,12 @@ export const CAPTURE_PHASE_DAMAGED_PARTY_DOC_KEYS = [
"damaged_metal_plate", "damaged_metal_plate",
] as const; ] as const;
/** Metal-plate keys that are optional in the V4/V5 FileMaker flow. */
export const OPTIONAL_CAPTURE_PHASE_DOC_KEYS_V4V5 = [
"damaged_metal_plate",
"guilty_metal_plate",
] as const;
export type CapturePhaseSequence = export type CapturePhaseSequence =
| "parts" | "parts"
| "angles" | "angles"
@@ -52,7 +58,7 @@ function isRequiredDocumentUploadedOnClaim(
export function getClaimCaptureProgress( export function getClaimCaptureProgress(
claimCase: any, claimCase: any,
options?: { assumeCapturePhaseDocKey?: string }, options?: { assumeCapturePhaseDocKey?: string; skipMetalPlate?: boolean },
): ClaimCaptureProgress { ): ClaimCaptureProgress {
const carType = claimCase?.vehicle?.carType as ClaimVehicleTypeV2 | undefined; const carType = claimCase?.vehicle?.carType as ClaimVehicleTypeV2 | undefined;
const selectedNorm = normalizeDamageSelectedParts( const selectedNorm = normalizeDamageSelectedParts(
@@ -83,6 +89,7 @@ export function getClaimCaptureProgress(
const capturePhaseDocsRemaining = CAPTURE_PHASE_DAMAGED_PARTY_DOC_KEYS.filter( const capturePhaseDocsRemaining = CAPTURE_PHASE_DAMAGED_PARTY_DOC_KEYS.filter(
(k) => { (k) => {
if (options?.skipMetalPlate && OPTIONAL_CAPTURE_PHASE_DOC_KEYS_V4V5.includes(k as any)) return false;
if (k === options?.assumeCapturePhaseDocKey) return false; if (k === options?.assumeCapturePhaseDocKey) return false;
return !isRequiredDocumentUploadedOnClaim(claimCase, k); return !isRequiredDocumentUploadedOnClaim(claimCase, k);
}, },
@@ -111,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
); );

View File

@@ -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,

View File

@@ -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,

View File

@@ -8695,13 +8695,16 @@ export class RequestManagementService {
partyRole: PartyRole, partyRole: PartyRole,
actor: any, actor: any,
): void { ): void {
// V4/V5 FileMaker files skip the location step — it is auto-completed here.
const skipLocation = !!(req as any).isMadeByFileMaker;
if (partyRole === PartyRole.FIRST) { if (partyRole === PartyRole.FIRST) {
this.pushWorkflowSteps(req, [ const completedSteps = skipLocation
WorkflowStep.CREATED, ? [WorkflowStep.CREATED, WorkflowStep.FIRST_INITIAL_FORM, WorkflowStep.FIRST_LOCATION]
WorkflowStep.FIRST_INITIAL_FORM, : [WorkflowStep.CREATED, WorkflowStep.FIRST_INITIAL_FORM];
]); this.pushWorkflowSteps(req, completedSteps);
req.workflow.currentStep = WorkflowStep.FIRST_LOCATION; req.workflow.currentStep = skipLocation ? WorkflowStep.FIRST_VOICE : WorkflowStep.FIRST_LOCATION;
req.workflow.nextStep = WorkflowStep.FIRST_VOICE; req.workflow.nextStep = skipLocation ? WorkflowStep.FIRST_DESCRIPTION : WorkflowStep.FIRST_VOICE;
const firstIdx = this.getPartyIndex(req, PartyRole.FIRST); const firstIdx = this.getPartyIndex(req, PartyRole.FIRST);
const guiltyUserId = req.parties?.[firstIdx]?.person?.userId; const guiltyUserId = req.parties?.[firstIdx]?.person?.userId;
@@ -8716,9 +8719,12 @@ export class RequestManagementService {
(req as any).markModified("expert"); (req as any).markModified("expert");
} }
} else { } else {
this.pushWorkflowSteps(req, [WorkflowStep.SECOND_INITIAL_FORM]); const completedSteps = skipLocation
req.workflow.currentStep = WorkflowStep.SECOND_LOCATION; ? [WorkflowStep.SECOND_INITIAL_FORM, WorkflowStep.SECOND_LOCATION]
req.workflow.nextStep = WorkflowStep.SECOND_VOICE; : [WorkflowStep.SECOND_INITIAL_FORM];
this.pushWorkflowSteps(req, completedSteps);
req.workflow.currentStep = skipLocation ? WorkflowStep.SECOND_VOICE : WorkflowStep.SECOND_LOCATION;
req.workflow.nextStep = skipLocation ? WorkflowStep.SECOND_DESCRIPTION : WorkflowStep.SECOND_VOICE;
} }
} }
@@ -9319,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.",
@@ -9375,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,
@@ -9404,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") {
@@ -9443,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.",
}; };
} }
@@ -9558,6 +9579,17 @@ export class RequestManagementService {
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");
await this.verifyExpertAccessForBlameV2(req, actor); await this.verifyExpertAccessForBlameV2(req, actor);
// V4/V5: location step is skipped — store whatever is sent but do not
// validate against workflow phase (the step was auto-completed at inquiry time).
if ((req as any).isMadeByFileMaker) {
const role = this.resolvePartyRoleV3(req, partyRole);
const idx = this.getPartyIndex(req, role);
if (idx !== -1 && body) req.parties[idx].location = body as any;
await (req as any).save();
return { requestId: req._id, publicId: req.publicId, partyRole: role };
}
const role = this.resolvePartyRoleV3(req, partyRole); const role = this.resolvePartyRoleV3(req, partyRole);
this.assertBlameV3PartyDetailPhase(req, role); this.assertBlameV3PartyDetailPhase(req, role);
@@ -9677,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.",
@@ -9733,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,
@@ -9762,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") {
@@ -9808,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.",
}; };
} }