forked from Yara724/api
Added v3 of field-expert
This commit is contained in:
@@ -23,6 +23,7 @@ import {
|
||||
ApiQuery,
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiExcludeController,
|
||||
} from "@nestjs/swagger";
|
||||
import { diskStorage } from "multer";
|
||||
import { ClaimAccessGuard } from "src/auth/guards/claim-access.guard";
|
||||
@@ -39,8 +40,9 @@ import { UserObjectionDto } from "./dto/user-objection.dto";
|
||||
import { InPersonVisitDto } from "./dto/in-person-visit.dto";
|
||||
import { UserRatingDto } from "./dto/user-rating.dto";
|
||||
|
||||
@ApiExcludeController()
|
||||
@Controller("claim-request-management")
|
||||
@ApiTags("claim-request-management")
|
||||
// @ApiTags("claim-request-management")
|
||||
@Roles(RoleEnum.USER, RoleEnum.EXPERT, RoleEnum.DAMAGE_EXPERT)
|
||||
@UseGuards(ClaimAccessGuard, RolesGuard)
|
||||
@ApiBearerAuth()
|
||||
@@ -49,8 +51,8 @@ export class ClaimRequestManagementController {
|
||||
private readonly claimRequestManagementService: ClaimRequestManagementService,
|
||||
) {}
|
||||
|
||||
@ApiParam({ name: "blameId" })
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiParam({ name: "blameId" })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Post("/:blameId")
|
||||
async createClaimRequest(
|
||||
@Param("blameId") requestId: string,
|
||||
@@ -63,10 +65,10 @@ export class ClaimRequestManagementController {
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBody({ type: CarDamagePartDto })
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiBody({ type: CarDamagePartDto })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Patch("/car-part-damage/:claimRequestID")
|
||||
@ApiParam({ name: "claimRequestID" })
|
||||
// @ApiParam({ name: "claimRequestID" })
|
||||
async carPartDamage(
|
||||
@Param("claimRequestID") requestId: string,
|
||||
@Body() body: CarDamagePartDto,
|
||||
@@ -79,7 +81,7 @@ export class ClaimRequestManagementController {
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Get("/car-other-part")
|
||||
async getCarOtherParts() {
|
||||
const carOtherPart = await readFile(
|
||||
@@ -89,8 +91,8 @@ export class ClaimRequestManagementController {
|
||||
return carOtherPart;
|
||||
}
|
||||
|
||||
@ApiBody({ type: OtherCarDamagePartDto })
|
||||
@ApiParam({ name: "claimRequestID" })
|
||||
// @ApiBody({ type: OtherCarDamagePartDto })
|
||||
// @ApiParam({ name: "claimRequestID" })
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
limits: {
|
||||
@@ -108,7 +110,7 @@ export class ClaimRequestManagementController {
|
||||
}),
|
||||
)
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Patch("/car-other-part-damage/:claimRequestID")
|
||||
async carOtherPartDamage(
|
||||
@Param("claimRequestID") requestId: string,
|
||||
@@ -124,32 +126,32 @@ export class ClaimRequestManagementController {
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Get("required-documents-status/:claimRequestID")
|
||||
@ApiParam({ name: "claimRequestID" })
|
||||
// @ApiParam({ name: "claimRequestID" })
|
||||
async getRequiredDocumentsStatus(@Param("claimRequestID") requestId: string) {
|
||||
return await this.claimRequestManagementService.getRequiredDocumentsStatus(
|
||||
requestId,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Get("car-part-image-required/:claimRequestID")
|
||||
@ApiParam({ name: "claimRequestID" })
|
||||
// @ApiParam({ name: "claimRequestID" })
|
||||
async getImageRequired(@Param("claimRequestID") requestId) {
|
||||
return await this.claimRequestManagementService.getImageRequiredList(
|
||||
requestId,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
file: { type: "string", format: "binary" },
|
||||
},
|
||||
},
|
||||
})
|
||||
// @ApiBody({
|
||||
// schema: {
|
||||
// type: "object",
|
||||
// properties: {
|
||||
// file: { type: "string", format: "binary" },
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
|
||||
@@ -169,14 +171,14 @@ export class ClaimRequestManagementController {
|
||||
}),
|
||||
}),
|
||||
)
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiParam({ name: "claimRequestID" })
|
||||
@ApiQuery({
|
||||
name: "documentType",
|
||||
enum: ClaimRequiredDocumentType,
|
||||
description: "Type of required document to upload",
|
||||
})
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiConsumes("multipart/form-data")
|
||||
// // @ApiParam({ name: "claimRequestID" })
|
||||
// @ApiQuery({
|
||||
// name: "documentType",
|
||||
// enum: ClaimRequiredDocumentType,
|
||||
// description: "Type of required document to upload",
|
||||
// })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Patch("upload-required-document/:claimRequestID")
|
||||
async uploadRequiredDocument(
|
||||
@Param("claimRequestID") requestId: string,
|
||||
@@ -195,14 +197,14 @@ export class ClaimRequestManagementController {
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
file: { type: "string", format: "binary" },
|
||||
},
|
||||
},
|
||||
})
|
||||
// @ApiBody({
|
||||
// schema: {
|
||||
// type: "object",
|
||||
// properties: {
|
||||
// file: { type: "string", format: "binary" },
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
|
||||
@@ -223,13 +225,13 @@ export class ClaimRequestManagementController {
|
||||
}),
|
||||
}),
|
||||
)
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiParam({ name: "claimRequestID" })
|
||||
@ApiParam({
|
||||
name: "partId",
|
||||
description: "The ID of the specific car part being photographed.",
|
||||
})
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiConsumes("multipart/form-data")
|
||||
// @ApiParam({ name: "claimRequestID" })
|
||||
// @ApiParam({
|
||||
// name: "partId",
|
||||
// description: "The ID of the specific car part being photographed.",
|
||||
// })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Patch("capture-car-part-damage/:claimRequestID/:partId")
|
||||
async captureCarPartDamage(
|
||||
@Param("partId") partId: string,
|
||||
@@ -246,14 +248,14 @@ export class ClaimRequestManagementController {
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
file: { type: "string", format: "binary" },
|
||||
},
|
||||
},
|
||||
})
|
||||
// @ApiBody({
|
||||
// schema: {
|
||||
// type: "object",
|
||||
// properties: {
|
||||
// file: { type: "string", format: "binary" },
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
|
||||
@@ -268,9 +270,9 @@ export class ClaimRequestManagementController {
|
||||
}),
|
||||
}),
|
||||
)
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiParam({ name: "claimRequestID" })
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiConsumes("multipart/form-data")
|
||||
// @ApiParam({ name: "claimRequestID" })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Patch("car-capture/:claimRequestID")
|
||||
async captureVideoCapture(
|
||||
@Param("claimRequestID") requestId: string,
|
||||
@@ -282,22 +284,22 @@ export class ClaimRequestManagementController {
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Get("requests/")
|
||||
async getRequest(@CurrentUser() currentUser) {
|
||||
return await this.claimRequestManagementService.myRequests(currentUser);
|
||||
}
|
||||
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Get("request/:claimRequestId")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
// @ApiParam({ name: "claimRequestId" })
|
||||
myRequests(@Param("claimRequestId") requestId: string, @CurrentUser() user) {
|
||||
return this.claimRequestManagementService.requestDetails(requestId, user);
|
||||
}
|
||||
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Put("request/reply/:claimRequestId")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
// @ApiParam({ name: "claimRequestId" })
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
limits: {
|
||||
@@ -314,12 +316,12 @@ export class ClaimRequestManagementController {
|
||||
}),
|
||||
}),
|
||||
)
|
||||
@ApiBody({
|
||||
type: UserCommentDto,
|
||||
description: "if partId null , you can upload video capture",
|
||||
})
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
// @ApiBody({
|
||||
// type: UserCommentDto,
|
||||
// description: "if partId null , you can upload video capture",
|
||||
// })
|
||||
// @ApiConsumes("multipart/form-data")
|
||||
// @ApiParam({ name: "claimRequestId" })
|
||||
async submitReply(
|
||||
@Param("claimRequestId") requestId,
|
||||
@Body() body,
|
||||
@@ -334,14 +336,14 @@ export class ClaimRequestManagementController {
|
||||
// );
|
||||
}
|
||||
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Put("request/resend/:claimRequestId/objection")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
@ApiConsumes("application/json")
|
||||
@ApiBody({
|
||||
type: UserObjectionDto,
|
||||
description: "Objection details with optional new parts",
|
||||
})
|
||||
// @ApiParam({ name: "claimRequestId" })
|
||||
// @ApiConsumes("application/json")
|
||||
// @ApiBody({
|
||||
// type: UserObjectionDto,
|
||||
// description: "Objection details with optional new parts",
|
||||
// })
|
||||
async handleUserObjection(
|
||||
@Param("claimRequestId") claimRequestId: string,
|
||||
@Body() userObjectionDto: UserObjectionDto,
|
||||
@@ -352,25 +354,25 @@ export class ClaimRequestManagementController {
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Patch("request/resend/:claimRequestId")
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
@ApiQuery({ name: "fields", enum: ["resendDocuments", "resendCarParts"] })
|
||||
@ApiQuery({ name: "partId", required: false })
|
||||
@ApiQuery({ name: "documentName", required: false })
|
||||
@ApiQuery({ name: "side", required: false })
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
file: {
|
||||
type: "string",
|
||||
format: "binary",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
// @ApiConsumes("multipart/form-data")
|
||||
// @ApiParam({ name: "claimRequestId" })
|
||||
// @ApiQuery({ name: "fields", enum: ["resendDocuments", "resendCarParts"] })
|
||||
// @ApiQuery({ name: "partId", required: false })
|
||||
// @ApiQuery({ name: "documentName", required: false })
|
||||
// @ApiQuery({ name: "side", required: false })
|
||||
// @ApiBody({
|
||||
// schema: {
|
||||
// type: "object",
|
||||
// properties: {
|
||||
// file: {
|
||||
// type: "string",
|
||||
// format: "binary",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
storage: diskStorage({
|
||||
@@ -403,10 +405,10 @@ export class ClaimRequestManagementController {
|
||||
* User satisfaction rating for a completed claim file.
|
||||
* Only the damaged user (claim owner) can rate their claim after it is closed.
|
||||
*/
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Put("request/:claimRequestId/user-rating")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
@ApiBody({ type: UserRatingDto })
|
||||
// @ApiParam({ name: "claimRequestId" })
|
||||
// @ApiBody({ type: UserRatingDto })
|
||||
async addUserRating(
|
||||
@Param("claimRequestId") claimRequestId: string,
|
||||
@Body() ratingDto: UserRatingDto,
|
||||
@@ -419,22 +421,22 @@ export class ClaimRequestManagementController {
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Patch("request/reply/:claimRequestId/:partId/upload-factor")
|
||||
@ApiConsumes("multipart/form-data")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
@ApiParam({ name: "partId" })
|
||||
@ApiBody({
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
file: {
|
||||
type: "string",
|
||||
format: "binary",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
// @ApiConsumes("multipart/form-data")
|
||||
// @ApiParam({ name: "claimRequestId" })
|
||||
// @ApiParam({ name: "partId" })
|
||||
// @ApiBody({
|
||||
// schema: {
|
||||
// type: "object",
|
||||
// properties: {
|
||||
// file: {
|
||||
// type: "string",
|
||||
// format: "binary",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
storage: diskStorage({
|
||||
@@ -462,9 +464,9 @@ export class ClaimRequestManagementController {
|
||||
);
|
||||
}
|
||||
|
||||
@ApiBody({ type: InPersonVisitDto })
|
||||
@ApiParam({ name: "id" })
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiBody({ type: InPersonVisitDto })
|
||||
// @ApiParam({ name: "id" })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Patch(":id/visit")
|
||||
async inPersonVisit(
|
||||
@Param("id") requestId: string,
|
||||
@@ -479,26 +481,25 @@ export class ClaimRequestManagementController {
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Get("branches/:insuranceId")
|
||||
// @ApiParam({ name: "insuranceId" })
|
||||
async insuranceBranches(@Param("insuranceId") insuranceId: string) {
|
||||
return await this.claimRequestManagementService.retrieveInsuranceBranches(
|
||||
insuranceId,
|
||||
);
|
||||
return await this.claimRequestManagementService.retrieveInsuranceBranches(insuranceId);
|
||||
}
|
||||
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Get("fanavaran-submit/:claimRequestId")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
// @ApiParam({ name: "claimRequestId" })
|
||||
async fanavaranSubmit(@Param("claimRequestId") claimRequestId: string) {
|
||||
return await this.claimRequestManagementService.fanavaranSubmit(
|
||||
claimRequestId,
|
||||
);
|
||||
}
|
||||
|
||||
@ApiOperation({ deprecated: true })
|
||||
// @ApiOperation({ deprecated: true })
|
||||
@Post("fanavaran-submit/:claimRequestId")
|
||||
@ApiParam({ name: "claimRequestId" })
|
||||
// @ApiParam({ name: "claimRequestId" })
|
||||
async submitToFanavaran(@Param("claimRequestId") claimRequestId: string) {
|
||||
return await this.claimRequestManagementService.submitToFanavaran(
|
||||
claimRequestId,
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
SelectOtherPartsV2Dto,
|
||||
SelectOtherPartsV2ResponseDto,
|
||||
} from "./dto/select-other-parts-v2.dto";
|
||||
import { SelectOtherPartsV3Dto } from "./dto/select-other-parts-v3.dto";
|
||||
import { GetCaptureRequirementsV2ResponseDto } from "./dto/capture-requirements-v2.dto";
|
||||
import {
|
||||
UploadRequiredDocumentV2Dto,
|
||||
@@ -82,6 +83,7 @@ import { ClaimRequiredDocumentType } from "src/Types&Enums/claim-request-managem
|
||||
import { ClaimStatus } from "src/Types&Enums/claim-request-management/claimStatus.enum";
|
||||
import { ClaimWorkflowStep } from "src/Types&Enums/claim-request-management/claim-workflow-steps.enum";
|
||||
import { CaseStatus as BlameCaseStatus } from "src/Types&Enums/blame-request-management/caseStatus.enum";
|
||||
import { WorkflowStep } from "src/Types&Enums/blame-request-management/blameWorkflow-steps.enum";
|
||||
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
||||
import { ClaimSignDbService } from "./entites/db-service/claim-sign.db.service";
|
||||
import { DamageImageDbService } from "./entites/db-service/damage-image.db.service";
|
||||
@@ -5060,6 +5062,101 @@ export class ClaimRequestManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* V2: Registrar creates a claim from a registrar-initiated IN_PERSON completed blame.
|
||||
* Mirrors createClaimFromBlameForRegistrarV1 but uses V2 naming conventions.
|
||||
*/
|
||||
async createClaimFromBlameForRegistrarV2(
|
||||
blameRequestId: string,
|
||||
registrar: { sub: string; firstName?: string; lastName?: string },
|
||||
): Promise<CreateClaimFromBlameResponseDto> {
|
||||
const blameRequest =
|
||||
await this.blameRequestDbService.findById(blameRequestId);
|
||||
if (!blameRequest) throw new NotFoundException("Blame request not found");
|
||||
if (blameRequest.status !== BlameCaseStatus.COMPLETED) {
|
||||
throw new BadRequestException(
|
||||
`Blame request must be COMPLETED. Current status: ${blameRequest.status}`,
|
||||
);
|
||||
}
|
||||
if (
|
||||
!blameRequest.registrarInitiated ||
|
||||
blameRequest.creationMethod !== "IN_PERSON" ||
|
||||
!blameRequest.initiatedByRegistrarId
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
"This endpoint is only for registrar-initiated IN_PERSON blame files.",
|
||||
);
|
||||
}
|
||||
if (String(blameRequest.initiatedByRegistrarId) !== String(registrar.sub)) {
|
||||
throw new ForbiddenException(
|
||||
"Only the registrar who created this blame file can create the claim.",
|
||||
);
|
||||
}
|
||||
const ownerFields = resolveClaimOwnerFieldsFromBlame(blameRequest);
|
||||
if (!ownerFields) {
|
||||
throw new BadRequestException(
|
||||
"Could not resolve claim owner (damaged party) from blame",
|
||||
);
|
||||
}
|
||||
const existingClaim = await this.claimCaseDbService.findOne({
|
||||
blameRequestId: new Types.ObjectId(blameRequestId),
|
||||
});
|
||||
if (existingClaim)
|
||||
throw new ConflictException("A claim for this blame case already exists");
|
||||
const claimNo = await this.generateUniqueClaimNumber();
|
||||
const newClaim = await this.claimCaseDbService.create({
|
||||
requestNo: claimNo,
|
||||
publicId: blameRequest.publicId,
|
||||
blameRequestId: new Types.ObjectId(blameRequestId),
|
||||
blameRequestNo: blameRequest.requestNo,
|
||||
status: ClaimCaseStatus.SELECTING_OUTER_PARTS,
|
||||
claimStatus: ClaimStatus.PENDING,
|
||||
inquiries: (blameRequest as any).inquiries ?? {},
|
||||
workflow: {
|
||||
currentStep: ClaimWorkflowStep.SELECT_OUTER_PARTS,
|
||||
nextStep: ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
||||
completedSteps: [ClaimWorkflowStep.CLAIM_CREATED],
|
||||
locked: false,
|
||||
},
|
||||
damagedPartyUserId: new Types.ObjectId(ownerFields.userId),
|
||||
owner: {
|
||||
userId: new Types.ObjectId(ownerFields.userId),
|
||||
userRole: ownerFields.userRole as any,
|
||||
...(ownerFields.clientId
|
||||
? {
|
||||
clientId: new Types.ObjectId(ownerFields.clientId),
|
||||
userClientKey: ownerFields.clientId,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
createdByRegistrarId: new Types.ObjectId(registrar.sub),
|
||||
history: [
|
||||
{
|
||||
type: "CLAIM_CREATED",
|
||||
actor: {
|
||||
actorId: new Types.ObjectId(registrar.sub),
|
||||
actorName:
|
||||
`${registrar.firstName || ""} ${registrar.lastName || ""}`.trim(),
|
||||
actorType: "registrar",
|
||||
},
|
||||
timestamp: new Date(),
|
||||
metadata: {
|
||||
blameRequestId,
|
||||
blamePublicId: blameRequest.publicId,
|
||||
createdByRegistrar: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
return {
|
||||
claimRequestId: String(newClaim._id),
|
||||
publicId: blameRequest.publicId,
|
||||
status: ClaimCaseStatus.SELECTING_OUTER_PARTS,
|
||||
message:
|
||||
"Claim created successfully. Registrar can now fill claim data on behalf of the damaged party.",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 API: Select damaged outer car parts for claim (Step 2 of claim workflow)
|
||||
*
|
||||
@@ -7660,4 +7757,405 @@ export class ClaimRequestManagementService {
|
||||
const randomPart = Math.floor(10000 + Math.random() * 90000); // 5 digits
|
||||
return `${prefix}${randomPart}`;
|
||||
}
|
||||
|
||||
private async loadBlameForV3Claim(claimCase: any) {
|
||||
if (!claimCase?.blameRequestId) return null;
|
||||
return this.blameRequestDbService.findById(
|
||||
claimCase.blameRequestId.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
private hasPartyInquiriesOnBlame(blame: any, role: "FIRST" | "SECOND"): boolean {
|
||||
const step =
|
||||
role === "FIRST"
|
||||
? WorkflowStep.FIRST_INITIAL_FORM
|
||||
: WorkflowStep.SECOND_INITIAL_FORM;
|
||||
return (blame.workflow?.completedSteps ?? []).includes(step);
|
||||
}
|
||||
|
||||
private partyHasSignedOnBlame(blame: any, role: "FIRST" | "SECOND"): boolean {
|
||||
const party = (blame.parties ?? []).find((p: any) => p?.role === role);
|
||||
return !!party?.confirmation;
|
||||
}
|
||||
|
||||
private assertV3ClaimDocumentsPhase(blame: any): void {
|
||||
if (!(blame.expert?.decision as any)?.fields?.accidentWay) {
|
||||
throw new BadRequestException(
|
||||
"Submit accident fields before uploading required documents.",
|
||||
);
|
||||
}
|
||||
const isCarBody =
|
||||
blame?.type === BlameRequestType.CAR_BODY || blame?.type === "CAR_BODY";
|
||||
if (isCarBody) {
|
||||
if (!this.partyHasSignedOnBlame(blame, "FIRST")) {
|
||||
throw new BadRequestException(
|
||||
"Upload documents after the party has signed.",
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!this.partyHasSignedOnBlame(blame, "FIRST") ||
|
||||
!this.partyHasSignedOnBlame(blame, "SECOND")
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
"Both parties must sign before uploading required documents.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private assertV3ClaimPartsPhase(blame: any): void {
|
||||
if (!(blame.expert?.decision as any)?.fields?.accidentWay) {
|
||||
throw new BadRequestException(
|
||||
"Submit accident fields before part selection.",
|
||||
);
|
||||
}
|
||||
const isCarBody =
|
||||
blame?.type === BlameRequestType.CAR_BODY || blame?.type === "CAR_BODY";
|
||||
if (isCarBody) {
|
||||
if (!this.partyHasSignedOnBlame(blame, "FIRST")) {
|
||||
throw new BadRequestException(
|
||||
"Part selection requires the party signature.",
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!this.partyHasSignedOnBlame(blame, "FIRST") ||
|
||||
!this.partyHasSignedOnBlame(blame, "SECOND")
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
"Both parties must sign before part selection.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private claimV3StepCompleted(
|
||||
claimCase: any,
|
||||
step: ClaimWorkflowStep,
|
||||
): boolean {
|
||||
return (claimCase.workflow?.completedSteps ?? []).includes(step);
|
||||
}
|
||||
|
||||
private assertV3ClaimWorkflowStep(
|
||||
claimCase: any,
|
||||
expected: ClaimWorkflowStep | ClaimWorkflowStep[],
|
||||
hint?: string,
|
||||
): void {
|
||||
const allowed = Array.isArray(expected) ? expected : [expected];
|
||||
const current = claimCase.workflow?.currentStep;
|
||||
if (!allowed.includes(current)) {
|
||||
throw new BadRequestException(
|
||||
hint ??
|
||||
`Invalid workflow step. Expected ${allowed.join(" or ")}, but current step is ${current}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async advanceV3ClaimToOuterPartsIfReady(
|
||||
claimCase: any,
|
||||
blame: any,
|
||||
): Promise<void> {
|
||||
if (claimCase.workflow?.currentStep === ClaimWorkflowStep.SELECT_OUTER_PARTS) {
|
||||
return;
|
||||
}
|
||||
if (claimCase.workflow?.currentStep !== ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS) {
|
||||
throw new BadRequestException(
|
||||
`Complete required documents before selecting outer parts. Current step: ${claimCase.workflow?.currentStep}`,
|
||||
);
|
||||
}
|
||||
if (!(blame.expert?.decision as any)?.fields?.accidentWay) {
|
||||
throw new BadRequestException(
|
||||
"Submit accident fields before selecting outer parts.",
|
||||
);
|
||||
}
|
||||
await this.claimCaseDbService.findByIdAndUpdate(String(claimCase._id), {
|
||||
$set: {
|
||||
status: ClaimCaseStatus.SELECTING_OUTER_PARTS,
|
||||
"workflow.currentStep": ClaimWorkflowStep.SELECT_OUTER_PARTS,
|
||||
"workflow.nextStep": ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async assertV3InPersonClaim(claimCase: any) {
|
||||
const blame = await this.loadBlameForV3Claim(claimCase);
|
||||
if (!blame) {
|
||||
throw new BadRequestException("V3 claim flow requires a linked blame file.");
|
||||
}
|
||||
if (blame.creationMethod !== CreationMethod.IN_PERSON) {
|
||||
throw new BadRequestException("V3 claim flow is only for IN_PERSON blame files.");
|
||||
}
|
||||
if (!blame.expertInitiated) {
|
||||
throw new BadRequestException("V3 claim flow requires an expert-initiated blame file.");
|
||||
}
|
||||
return blame;
|
||||
}
|
||||
|
||||
async uploadRequiredDocumentV3(
|
||||
claimRequestId: string,
|
||||
body: UploadRequiredDocumentV2Dto,
|
||||
file: Express.Multer.File,
|
||||
currentUserId: string,
|
||||
actor?: { sub: string; role?: string },
|
||||
): Promise<UploadRequiredDocumentV2ResponseDto> {
|
||||
const claimCase = await this.claimCaseDbService.findById(claimRequestId);
|
||||
if (!claimCase) {
|
||||
throw new NotFoundException(`Claim case with ID ${claimRequestId} not found`);
|
||||
}
|
||||
const blame = await this.assertV3InPersonClaim(claimCase);
|
||||
this.assertV3ClaimDocumentsPhase(blame);
|
||||
|
||||
const step = claimCase.workflow?.currentStep;
|
||||
if (step !== ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS) {
|
||||
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
||||
$set: {
|
||||
status: ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS,
|
||||
"workflow.currentStep": ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return this.uploadRequiredDocumentV2(
|
||||
claimRequestId,
|
||||
body,
|
||||
file,
|
||||
currentUserId,
|
||||
actor,
|
||||
);
|
||||
}
|
||||
|
||||
async getCaptureRequirementsV3(
|
||||
claimRequestId: string,
|
||||
currentUserId: string,
|
||||
actor?: { sub: string; role?: string },
|
||||
): Promise<GetCaptureRequirementsV2ResponseDto> {
|
||||
const claimCase = await this.claimCaseDbService.findById(claimRequestId);
|
||||
if (!claimCase) {
|
||||
throw new NotFoundException(`Claim case with ID ${claimRequestId} not found`);
|
||||
}
|
||||
const blame = await this.assertV3InPersonClaim(claimCase);
|
||||
this.assertV3ClaimDocumentsPhase(blame);
|
||||
return this.getCaptureRequirementsV2(claimRequestId, currentUserId, actor);
|
||||
}
|
||||
|
||||
async selectOuterPartsV3(
|
||||
claimRequestId: string,
|
||||
body: SelectOuterPartsV2Dto,
|
||||
currentUserId: string,
|
||||
actor?: { sub: string; role?: string },
|
||||
): Promise<SelectOuterPartsV2ResponseDto> {
|
||||
const claimCase = await this.claimCaseDbService.findById(claimRequestId);
|
||||
if (!claimCase) {
|
||||
throw new NotFoundException(`Claim case with ID ${claimRequestId} not found`);
|
||||
}
|
||||
const blame = await this.assertV3InPersonClaim(claimCase);
|
||||
this.assertV3ClaimPartsPhase(blame);
|
||||
await this.advanceV3ClaimToOuterPartsIfReady(claimCase, blame);
|
||||
|
||||
const refreshed = await this.claimCaseDbService.findById(claimRequestId);
|
||||
this.assertV3ClaimWorkflowStep(
|
||||
refreshed,
|
||||
ClaimWorkflowStep.SELECT_OUTER_PARTS,
|
||||
"Select outer parts after accident fields and required documents.",
|
||||
);
|
||||
|
||||
return this.selectOuterPartsV2(claimRequestId, body, currentUserId, actor);
|
||||
}
|
||||
|
||||
async selectOtherPartsV3(
|
||||
claimRequestId: string,
|
||||
body: SelectOtherPartsV3Dto,
|
||||
currentUserId: string,
|
||||
actor?: { sub: string; role?: string },
|
||||
file?: Express.Multer.File,
|
||||
): Promise<SelectOtherPartsV2ResponseDto> {
|
||||
const claimCase = await this.claimCaseDbService.findById(claimRequestId);
|
||||
if (!claimCase) {
|
||||
throw new NotFoundException(`Claim case with ID ${claimRequestId} not found`);
|
||||
}
|
||||
const blame = await this.assertV3InPersonClaim(claimCase);
|
||||
this.assertV3ClaimPartsPhase(blame);
|
||||
this.assertV3ClaimWorkflowStep(
|
||||
claimCase,
|
||||
ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
||||
"Select outer parts before other parts.",
|
||||
);
|
||||
if (!this.claimV3StepCompleted(claimCase, ClaimWorkflowStep.SELECT_OUTER_PARTS)) {
|
||||
throw new BadRequestException(
|
||||
"Complete outer part selection before selecting other parts.",
|
||||
);
|
||||
}
|
||||
|
||||
let otherParts = Array.isArray((body as any)?.otherParts)
|
||||
? (body as any).otherParts
|
||||
: [];
|
||||
if (
|
||||
!Array.isArray((body as any)?.otherParts) &&
|
||||
typeof (body as any)?.otherParts === "string"
|
||||
) {
|
||||
try {
|
||||
const parsed = JSON.parse((body as any).otherParts);
|
||||
otherParts = Array.isArray(parsed) ? parsed : [];
|
||||
} catch {
|
||||
otherParts = [];
|
||||
}
|
||||
}
|
||||
|
||||
const shebaNumber = claimCase.money?.sheba;
|
||||
const nationalCode = claimCase.money?.nationalCodeOfInsurer;
|
||||
if (!shebaNumber || !nationalCode) {
|
||||
throw new BadRequestException(
|
||||
"Bank information is missing on the claim. Complete run-inquiries for both parties first.",
|
||||
);
|
||||
}
|
||||
|
||||
const updatePayload: any = {
|
||||
"damage.otherParts": otherParts.length > 0 ? otherParts : undefined,
|
||||
status: ClaimCaseStatus.CAPTURING_PART_DAMAGES,
|
||||
"workflow.currentStep": ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
||||
"workflow.nextStep": ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
||||
$push: {
|
||||
"workflow.completedSteps": ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
||||
history: {
|
||||
type: "V3_STEP_COMPLETED",
|
||||
actor: {
|
||||
actorId: new Types.ObjectId(currentUserId),
|
||||
actorName: claimCase.owner?.fullName || "Actor",
|
||||
actorType: actor?.role === RoleEnum.REGISTRAR ? "registrar" : "field_expert",
|
||||
},
|
||||
timestamp: new Date(),
|
||||
metadata: {
|
||||
stepKey: ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
||||
otherParts,
|
||||
v3SelectionOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (file) {
|
||||
const docRef = await this.claimRequiredDocumentDbService.create({
|
||||
path: file.path,
|
||||
fileName: file.filename,
|
||||
claimId: new Types.ObjectId(claimRequestId),
|
||||
documentType: ClaimRequiredDocumentType.CAR_GREEN_CARD,
|
||||
uploadedAt: new Date(),
|
||||
});
|
||||
updatePayload[`requiredDocuments.${ClaimRequiredDocumentType.CAR_GREEN_CARD}`] = {
|
||||
fileId: docRef._id,
|
||||
filePath: file.path,
|
||||
fileName: file.filename,
|
||||
uploaded: true,
|
||||
uploadedAt: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
const updatedClaim = await this.claimCaseDbService.findByIdAndUpdate(
|
||||
claimRequestId,
|
||||
updatePayload,
|
||||
);
|
||||
if (!updatedClaim) {
|
||||
throw new InternalServerErrorException("Failed to update claim case");
|
||||
}
|
||||
|
||||
return {
|
||||
claimRequestId: updatedClaim._id.toString(),
|
||||
publicId: updatedClaim.publicId,
|
||||
otherParts,
|
||||
shebaNumber: String(shebaNumber).replace(/^(.{4})(.*)(.{4})$/, "IR$1************$3"),
|
||||
nationalCodeOfOwner: String(nationalCode).replace(/^(.{2})(.*)(.{2})$/, "$1******$3"),
|
||||
currentStep: ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
||||
nextStep: ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
||||
message:
|
||||
"Other parts saved. Proceed to damaged-part photos and car angles (capture-part).",
|
||||
};
|
||||
}
|
||||
|
||||
async setVideoCaptureV3(
|
||||
claimRequestId: string,
|
||||
fileDetail: Express.Multer.File,
|
||||
currentUserId: string,
|
||||
actor?: { sub: string; role?: string },
|
||||
): Promise<VideoCaptureV2ResponseDto> {
|
||||
const claimCase = await this.claimCaseDbService.findById(claimRequestId);
|
||||
if (!claimCase) {
|
||||
throw new NotFoundException(`Claim case with ID ${claimRequestId} not found`);
|
||||
}
|
||||
await this.assertV3InPersonClaim(claimCase);
|
||||
|
||||
if (!fileDetail) {
|
||||
throw new BadRequestException("Video file is required.");
|
||||
}
|
||||
if (claimCase.media?.videoCaptureId) {
|
||||
throw new ConflictException(
|
||||
"A walk-around video has already been uploaded for this claim.",
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
claimCase.workflow?.currentStep !== ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
`Complete all part/angle captures first. Expected ${ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS}, but current step is ${claimCase.workflow?.currentStep}`,
|
||||
);
|
||||
}
|
||||
|
||||
const captureProgress = getClaimCaptureProgress(claimCase);
|
||||
if (!captureProgress.partsComplete) {
|
||||
throw new BadRequestException(
|
||||
"Upload photos for all selected damaged parts before the walk-around video.",
|
||||
);
|
||||
}
|
||||
if (!captureProgress.anglesComplete) {
|
||||
throw new BadRequestException(
|
||||
"Capture all four car angles before the walk-around video.",
|
||||
);
|
||||
}
|
||||
if (!isClaimCaptureStepComplete(claimCase)) {
|
||||
throw new BadRequestException(
|
||||
"Upload capture-phase vehicle documents (chassis, engine, metal plate) before the walk-around video.",
|
||||
);
|
||||
}
|
||||
|
||||
return this.setVideoCaptureV2(
|
||||
claimRequestId,
|
||||
fileDetail,
|
||||
currentUserId,
|
||||
actor,
|
||||
);
|
||||
}
|
||||
|
||||
async capturePartV3(
|
||||
claimRequestId: string,
|
||||
body: CapturePartV2Dto,
|
||||
file: Express.Multer.File,
|
||||
currentUserId: string,
|
||||
actor?: { sub: string; role?: string },
|
||||
): Promise<CapturePartV2ResponseDto> {
|
||||
const claimCase = await this.claimCaseDbService.findById(claimRequestId);
|
||||
if (!claimCase) {
|
||||
throw new NotFoundException(`Claim case with ID ${claimRequestId} not found`);
|
||||
}
|
||||
const blame = await this.assertV3InPersonClaim(claimCase);
|
||||
this.assertV3ClaimPartsPhase(blame);
|
||||
this.assertV3ClaimWorkflowStep(
|
||||
claimCase,
|
||||
ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
||||
"Select other parts before capturing damaged parts and angles.",
|
||||
);
|
||||
if (!this.claimV3StepCompleted(claimCase, ClaimWorkflowStep.SELECT_OTHER_PARTS)) {
|
||||
throw new BadRequestException(
|
||||
"Complete other part selection before capture.",
|
||||
);
|
||||
}
|
||||
|
||||
return this.capturePartV2(
|
||||
claimRequestId,
|
||||
body,
|
||||
file,
|
||||
currentUserId,
|
||||
actor,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import {
|
||||
ArrayMaxSize,
|
||||
IsArray,
|
||||
IsEnum,
|
||||
IsOptional,
|
||||
} from "class-validator";
|
||||
import { OtherCarPart } from "./select-other-parts-v2.dto";
|
||||
|
||||
/**
|
||||
* V3 in-person expert flow: other parts only (sheba/national code collected during run-inquiries).
|
||||
*/
|
||||
export class SelectOtherPartsV3Dto {
|
||||
@ApiPropertyOptional({
|
||||
description: "Array of selected other damaged parts (non-body parts)",
|
||||
example: ["engine", "suspension", "headlight"],
|
||||
enum: OtherCarPart,
|
||||
isArray: true,
|
||||
maxItems: 11,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsArray({ message: "otherParts must be an array" })
|
||||
@ArrayMaxSize(11, { message: "Maximum 11 other parts can be selected" })
|
||||
@IsEnum(OtherCarPart, {
|
||||
each: true,
|
||||
message: "Invalid part name. Must be one of the valid other car parts",
|
||||
})
|
||||
otherParts?: OtherCarPart[];
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { Body, Controller, Get, Param, Patch, Post, UploadedFile, UseGuards, Use
|
||||
import { FileInterceptor } from "@nestjs/platform-express";
|
||||
import { diskStorage } from "multer";
|
||||
import { extname } from "path";
|
||||
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiOperation, ApiParam, ApiTags } from "@nestjs/swagger";
|
||||
import { ApiBearerAuth, ApiConsumes, ApiExcludeController } from "@nestjs/swagger";
|
||||
import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard";
|
||||
import { RolesGuard } from "src/auth/guards/role.guard";
|
||||
import { Roles } from "src/decorators/roles.decorator";
|
||||
@@ -15,7 +15,8 @@ import { UploadRequiredDocumentV2Dto } from "./dto/upload-document-v2.dto";
|
||||
import { CapturePartV2Dto } from "./dto/capture-part-v2.dto";
|
||||
import { ClaimRequestManagementService } from "./claim-request-management.service";
|
||||
|
||||
@ApiTags("registrar-claim (v1)")
|
||||
@ApiExcludeController()
|
||||
// @ApiTags("registrar-claim (v1)")
|
||||
@Controller("registrar-claim")
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(LocalActorAuthGuard, RolesGuard)
|
||||
@@ -24,7 +25,7 @@ export class RegistrarClaimV1Controller {
|
||||
constructor(private readonly claimRequestManagementService: ClaimRequestManagementService) {}
|
||||
|
||||
@Post("create-from-blame/:blameRequestId")
|
||||
@ApiParam({ name: "blameRequestId" })
|
||||
// @ApiParam({ name: "blameRequestId" })
|
||||
createFromBlame(@Param("blameRequestId") blameRequestId: string, @CurrentUser() registrar: any) {
|
||||
return this.claimRequestManagementService.createClaimFromBlameForRegistrarV1(
|
||||
blameRequestId,
|
||||
@@ -42,7 +43,7 @@ export class RegistrarClaimV1Controller {
|
||||
}
|
||||
|
||||
@Patch("select-outer-parts/:claimRequestId")
|
||||
@ApiBody({ type: SelectOuterPartsV2Dto })
|
||||
// @ApiBody({ type: SelectOuterPartsV2Dto })
|
||||
selectOuter(
|
||||
@Param("claimRequestId") claimRequestId: string,
|
||||
@Body() body: SelectOuterPartsV2Dto,
|
||||
@@ -57,7 +58,7 @@ export class RegistrarClaimV1Controller {
|
||||
}
|
||||
|
||||
@Patch("select-other-parts/:claimRequestId")
|
||||
@ApiBody({ type: SelectOtherPartsV2Dto })
|
||||
// @ApiBody({ type: SelectOtherPartsV2Dto })
|
||||
selectOther(
|
||||
@Param("claimRequestId") claimRequestId: string,
|
||||
@Body() body: SelectOtherPartsV2Dto,
|
||||
@@ -91,7 +92,7 @@ export class RegistrarClaimV1Controller {
|
||||
}),
|
||||
}),
|
||||
)
|
||||
@ApiOperation({ summary: "Registrar uploads required claim document" })
|
||||
// @ApiOperation({ summary: "Registrar uploads required claim document" })
|
||||
uploadDoc(
|
||||
@Param("claimRequestId") claimRequestId: string,
|
||||
@Body() body: UploadRequiredDocumentV2Dto,
|
||||
@@ -108,7 +109,7 @@ export class RegistrarClaimV1Controller {
|
||||
}
|
||||
|
||||
@Post("capture-part/:claimRequestId")
|
||||
@ApiConsumes("multipart/form-data")
|
||||
// @ApiConsumes("multipart/form-data")
|
||||
@UseInterceptors(
|
||||
FileInterceptor("file", {
|
||||
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
|
||||
|
||||
Reference in New Issue
Block a user