1
0
forked from Yara724/api

merge upstream

This commit is contained in:
2026-07-05 15:47:59 +03:30
12 changed files with 443 additions and 100 deletions

View File

@@ -8232,10 +8232,12 @@ export class ClaimRequestManagementService {
if (allDocumentsUploaded) {
if (options?.v3InPersonFlow) {
$set["status"] = ClaimCaseStatus.SELECTING_OUTER_PARTS;
$set["workflow.currentStep"] =
ClaimWorkflowStep.SELECT_OUTER_PARTS;
$set["workflow.nextStep"] = ClaimWorkflowStep.SELECT_OTHER_PARTS;
// V4 split flow: mark the claim as waiting for the FileReviewer.
// The FileReviewer calls accident-fields then select-outer-parts, at
// which point advanceV3ClaimToOuterPartsIfReady() moves the claim to
// SELECTING_OUTER_PARTS / SELECT_OUTER_PARTS.
$set["status"] = ClaimCaseStatus.WAITING_FOR_FILE_REVIEWER;
$set["workflow.nextStep"] = ClaimWorkflowStep.SELECT_OUTER_PARTS;
completedStepsEntries.push(
ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
@@ -8251,7 +8253,7 @@ export class ClaimRequestManagementService {
metadata: {
stepKey: ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
description:
"Initial required documents uploaded. Proceed to outer part selection.",
"FileMaker documents complete. File sealed — awaiting FileReviewer.",
v3InPersonFlow: true,
},
});
@@ -8367,7 +8369,7 @@ export class ClaimRequestManagementService {
)
: allDocumentsUploaded
? options?.v3InPersonFlow
? "Initial required documents uploaded. Proceed to outer part selection."
? "All FileMaker documents uploaded. File is sealed and ready for FileReviewer pickup."
: "All documents uploaded successfully. Your claim is now ready for damage expert review."
: `Document uploaded successfully. ${remaining} documents remaining.`;
@@ -8382,7 +8384,7 @@ export class ClaimRequestManagementService {
: ClaimWorkflowStep.CAPTURE_PART_DAMAGES
: allDocumentsUploaded
? options?.v3InPersonFlow
? ClaimWorkflowStep.SELECT_OUTER_PARTS
? ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS
: ClaimWorkflowStep.USER_SUBMISSION_COMPLETE
: ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
message,
@@ -9964,11 +9966,6 @@ export class ClaimRequestManagementService {
}
}
private v3PartsFlowNotStarted(claimCase: any): boolean {
const parts = claimCase.damage?.selectedParts;
return !Array.isArray(parts) || parts.length === 0;
}
private async advanceV3ClaimToOuterPartsIfReady(
claimCase: any,
blame: any,
@@ -9980,12 +9977,16 @@ export class ClaimRequestManagementService {
}
const step = claimCase.workflow?.currentStep;
const partsNotStarted = this.v3PartsFlowNotStarted(claimCase);
const prematurelyCompleted =
step === ClaimWorkflowStep.USER_SUBMISSION_COMPLETE && partsNotStarted;
// UPLOAD_REQUIRED_DOCUMENTS: normal V3/V4 path after FileMaker docs.
// WAITING_FOR_FILE_REVIEWER (claim status): same DB step but with the new
// V4 claim status — FileReviewer is picking up the sealed file.
// USER_SUBMISSION_COMPLETE: can occur when documents were uploaded via a
// non-V3 path (e.g. generic v2 endpoint). Safe to advance as long as
// accident fields are set (checked just below).
const canAdvance =
step === ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS ||
prematurelyCompleted;
step === ClaimWorkflowStep.USER_SUBMISSION_COMPLETE ||
claimCase.status === ClaimCaseStatus.WAITING_FOR_FILE_REVIEWER;
if (!canAdvance) {
throw new BadRequestException(

View File

@@ -15,7 +15,15 @@ import {
UploadedFile,
} from "@nestjs/common";
import { readFile } from "node:fs/promises";
import { ApiBearerAuth, ApiParam, ApiTags, ApiOperation, ApiResponse, ApiBody, ApiConsumes } from "@nestjs/swagger";
import {
ApiBearerAuth,
ApiParam,
ApiTags,
ApiOperation,
ApiResponse,
ApiBody,
ApiConsumes,
} from "@nestjs/swagger";
import { FileInterceptor } from "@nestjs/platform-express";
import { diskStorage } from "multer";
import { extname } from "node:path";
@@ -33,9 +41,15 @@ import {
SelectOuterPartsV2Dto,
SelectOuterPartsV2ResponseDto,
} from "./dto/select-outer-parts-v2.dto";
import { SelectOtherPartsV2Dto, SelectOtherPartsV2ResponseDto } from "./dto/select-other-parts-v2.dto";
import {
SelectOtherPartsV2Dto,
SelectOtherPartsV2ResponseDto,
} from "./dto/select-other-parts-v2.dto";
import { GetCaptureRequirementsV2ResponseDto } from "./dto/capture-requirements-v2.dto";
import { UploadRequiredDocumentV2Dto, UploadRequiredDocumentV2ResponseDto } from "./dto/upload-document-v2.dto";
import {
UploadRequiredDocumentV2Dto,
UploadRequiredDocumentV2ResponseDto,
} from "./dto/upload-document-v2.dto";
import {
CapturePartV2Dto,
CapturePartV2ResponseDto,
@@ -52,7 +66,13 @@ import { ClaimVehicleTypeV2 } from "src/static/outer-car-parts-catalog";
@Controller("v2/claim-request-management")
@ApiBearerAuth()
@UseGuards(GlobalGuard, RolesGuard)
@Roles(RoleEnum.USER, RoleEnum.FIELD_EXPERT, RoleEnum.REGISTRAR)
@Roles(
RoleEnum.USER,
RoleEnum.FIELD_EXPERT,
RoleEnum.REGISTRAR,
RoleEnum.FILE_MAKER,
RoleEnum.FILE_REVIEWER,
)
export class ClaimRequestManagementV2Controller {
constructor(
private readonly claimRequestManagementService: ClaimRequestManagementService,
@@ -142,7 +162,10 @@ export class ClaimRequestManagementV2Controller {
})
@ApiParam({ name: "claimRequestId" })
@ApiResponse({ status: 200, description: "Claim returned to expert queue" })
@ApiResponse({ status: 400, description: "Resend requires uploads or wrong step" })
@ApiResponse({
status: 400,
description: "Resend requires uploads or wrong step",
})
async acknowledgeExpertResend(
@Param("claimRequestId") claimRequestId: string,
@CurrentUser() user: any,
@@ -156,7 +179,9 @@ export class ClaimRequestManagementV2Controller {
} catch (error) {
if (error instanceof HttpException) throw error;
throw new InternalServerErrorException(
error instanceof Error ? error.message : "Failed to acknowledge expert resend",
error instanceof Error
? error.message
: "Failed to acknowledge expert resend",
);
}
}
@@ -181,7 +206,10 @@ export class ClaimRequestManagementV2Controller {
})
@ApiBody({ type: UserObjectionV2Dto })
@ApiResponse({ status: 200, description: "Objection stored" })
@ApiResponse({ status: 400, description: "No active resend or empty payload" })
@ApiResponse({
status: 400,
description: "No active resend or empty payload",
})
@ApiResponse({ status: 403, description: "Not the claim owner" })
@ApiResponse({ status: 404, description: "Claim not found" })
@ApiResponse({ status: 409, description: "Objection already submitted" })
@@ -222,7 +250,10 @@ export class ClaimRequestManagementV2Controller {
})
@ApiBody({ type: UserRatingDto })
@ApiResponse({ status: 200, description: "Rating saved" })
@ApiResponse({ status: 400, description: "Claim not completed or invalid scores" })
@ApiResponse({
status: 400,
description: "Claim not completed or invalid scores",
})
@ApiResponse({ status: 403, description: "Not the claim owner" })
@ApiResponse({ status: 404, description: "Claim not found" })
@ApiResponse({ status: 409, description: "Rating already submitted" })
@@ -270,21 +301,32 @@ export class ClaimRequestManagementV2Controller {
type: "object",
required: ["sign", "agree", "branchId"],
properties: {
sign: { type: "string", format: "binary", description: "Signature image" },
sign: {
type: "string",
format: "binary",
description: "Signature image",
},
agree: {
type: "boolean",
description: "true to accept expert pricing and complete the claim",
},
branchId: {
type: "string",
description: "Insurer branch id (must belong to the claim owner's insurer; if pricing lists branch options, must match one of them)",
description:
"Insurer branch id (must belong to the claim owner's insurer; if pricing lists branch options, must match one of them)",
example: "507f1f77bcf86cd799439011",
},
},
},
})
@ApiResponse({ status: 200, description: "Signature stored; claim completed or rejected" })
@ApiResponse({ status: 400, description: "Wrong step/status or missing file" })
@ApiResponse({
status: 200,
description: "Signature stored; claim completed or rejected",
})
@ApiResponse({
status: 400,
description: "Wrong step/status or missing file",
})
@ApiResponse({ status: 403, description: "Not the claim owner" })
@ApiResponse({ status: 404, description: "Claim not found" })
@ApiResponse({ status: 409, description: "Already signed" })
@@ -314,7 +356,9 @@ export class ClaimRequestManagementV2Controller {
}
await this.mediaPolicyService.assertForClaim(sign, claimRequestId, "image");
const agreed =
typeof agree === "string" ? agree === "true" || agree === "1" : Boolean(agree);
typeof agree === "string"
? agree === "true" || agree === "1"
: Boolean(agree);
try {
return await this.claimRequestManagementService.submitOwnerInsurerApprovalSignV2(
claimRequestId,
@@ -453,8 +497,7 @@ export class ClaimRequestManagementV2Controller {
})
@ApiBody({
type: SelectOuterPartsV2Dto,
description:
"Selected vehicle type + selected outer part IDs from catalog",
description: "Selected vehicle type + selected outer part IDs from catalog",
examples: {
example1: {
summary: "Sedan - minor front damage",
@@ -522,9 +565,7 @@ export class ClaimRequestManagementV2Controller {
} catch (error) {
if (error instanceof HttpException) throw error;
throw new InternalServerErrorException(
error instanceof Error
? error.message
: "Failed to select outer parts",
error instanceof Error ? error.message : "Failed to select outer parts",
);
}
}
@@ -638,9 +679,7 @@ Optional: upload car green card file in the same step.
} catch (error) {
if (error instanceof HttpException) throw error;
throw new InternalServerErrorException(
error instanceof Error
? error.message
: "Failed to select other parts",
error instanceof Error ? error.message : "Failed to select other parts",
);
}
}
@@ -862,7 +901,7 @@ Returns status of each item (uploaded/captured or not).
type: "string",
example: "front",
description:
'For angle: front/back/left/right. For part: hood/front_bumper/etc.',
"For angle: front/back/left/right. For part: hood/front_bumper/etc.",
},
file: {
type: "string",
@@ -1005,7 +1044,10 @@ Returns status of each item (uploaded/captured or not).
description: "Video uploaded successfully",
type: VideoCaptureV2ResponseDto,
})
@ApiResponse({ status: 400, description: "Wrong workflow step or missing file" })
@ApiResponse({
status: 400,
description: "Wrong workflow step or missing file",
})
@ApiResponse({ status: 403, description: "Not the claim owner" })
@ApiResponse({ status: 404, description: "Claim not found" })
@ApiResponse({ status: 409, description: "Video already uploaded" })
@@ -1025,9 +1067,10 @@ Returns status of each item (uploaded/captured or not).
} catch (error) {
if (error instanceof HttpException) throw error;
throw new InternalServerErrorException(
error instanceof Error ? error.message : "Failed to upload car capture video",
error instanceof Error
? error.message
: "Failed to upload car capture video",
);
}
}
}