diff --git a/src/claim-request-management/claim-details-access.v2.spec.ts b/src/claim-request-management/claim-details-access.v2.spec.ts new file mode 100644 index 0000000..8966a5b --- /dev/null +++ b/src/claim-request-management/claim-details-access.v2.spec.ts @@ -0,0 +1,59 @@ +import { ForbiddenException } from "@nestjs/common"; +import { ClaimRequestManagementService } from "./claim-request-management.service"; +import { RoleEnum } from "src/Types&Enums/role.enum"; + +describe("V2 claim-detail access for split file roles", () => { + const makerId = "maker-id"; + const reviewerId = "reviewer-id"; + + const createService = (blame: Record) => { + const service = Object.create( + ClaimRequestManagementService.prototype, + ) as ClaimRequestManagementService; + (service as any).blameRequestDbService = { + findById: jest.fn().mockResolvedValue(blame), + }; + return service; + }; + + const claim = { blameRequestId: "blame-id" }; + + it("allows the FileMaker who created a completed V4/V5 file", async () => { + const service = createService({ + isMadeByFileMaker: true, + expertInitiated: true, + creationMethod: "IN_PERSON", + initiatedByFieldExpertId: makerId, + }); + + await expect( + (service as any).assertActorCanViewClaimV2(claim, makerId, { + sub: makerId, + role: RoleEnum.FILE_MAKER, + }), + ).resolves.toBeUndefined(); + }); + + it("allows only the assigned FileReviewer", async () => { + const service = createService({ + isMadeByFileMaker: true, + expertInitiated: true, + creationMethod: "IN_PERSON", + assignedFileReviewerId: reviewerId, + }); + + await expect( + (service as any).assertActorCanViewClaimV2(claim, reviewerId, { + sub: reviewerId, + role: RoleEnum.FILE_REVIEWER, + }), + ).resolves.toBeUndefined(); + + await expect( + (service as any).assertActorCanViewClaimV2(claim, "other-reviewer", { + sub: "other-reviewer", + role: RoleEnum.FILE_REVIEWER, + }), + ).rejects.toBeInstanceOf(ForbiddenException); + }); +}); diff --git a/src/claim-request-management/claim-request-management.service.ts b/src/claim-request-management/claim-request-management.service.ts index cf8af08..025665c 100644 --- a/src/claim-request-management/claim-request-management.service.ts +++ b/src/claim-request-management/claim-request-management.service.ts @@ -8005,6 +8005,37 @@ export class ClaimRequestManagementService { } } + if ( + (actor?.role === RoleEnum.FILE_MAKER || + actor?.role === RoleEnum.FILE_REVIEWER) && + claim.blameRequestId + ) { + const blame = await this.blameRequestDbService.findById( + claim.blameRequestId.toString(), + ); + const isV4OrV5File = + !!(blame as any)?.isMadeByFileMaker && + blame?.expertInitiated && + blame?.creationMethod === "IN_PERSON"; + + if ( + actor.role === RoleEnum.FILE_MAKER && + isV4OrV5File && + claimCaseInitiatedByFieldExpert(claim, { sub: currentUserId }, blame) + ) { + return; + } + + if ( + actor.role === RoleEnum.FILE_REVIEWER && + isV4OrV5File && + String((blame as any)?.assignedFileReviewerId ?? "") === + String(currentUserId) + ) { + return; + } + } + if (actor?.role === RoleEnum.REGISTRAR && claim.blameRequestId) { const blame = await this.blameRequestDbService.findById( claim.blameRequestId.toString(), @@ -11059,6 +11090,14 @@ export class ClaimRequestManagementService { carAngles, damagedParts, expertResend, + fanavaran: + claim.status === ClaimCaseStatus.COMPLETED && + (claim.claimNo != null || claim.claimId != null) + ? { + claimNo: claim.claimNo, + claimId: claim.claimId, + } + : undefined, evaluation: mappedEvaluation ? { damageExpertReply: mappedEvaluation.damageExpertReply, diff --git a/src/claim-request-management/claim-request-management.v2.controller.ts b/src/claim-request-management/claim-request-management.v2.controller.ts index 823cc37..fba39a9 100644 --- a/src/claim-request-management/claim-request-management.v2.controller.ts +++ b/src/claim-request-management/claim-request-management.v2.controller.ts @@ -117,7 +117,7 @@ export class ClaimRequestManagementV2Controller { @ApiOperation({ summary: "Get Claim Details (V2)", description: - "Returns the claim snapshot for **USER** (owner), **FIELD_EXPERT**, or **REGISTRAR** when permitted. Initiating experts/registrars see unmasked money fields; owners get `ownerGuidance`.", + "Returns the claim snapshot for an authorized **USER**, **FIELD_EXPERT**, **REGISTRAR**, **FILE_MAKER**, or assigned **FILE_REVIEWER**. Initiating experts/registrars see unmasked money fields; owners get `ownerGuidance`. Completed claims include Fanavaran `claimNo` / `claimId` when available.", }) @ApiResponse({ status: 200, diff --git a/src/claim-request-management/dto/claim-details-v2.dto.ts b/src/claim-request-management/dto/claim-details-v2.dto.ts index 26f84f6..6a13ab3 100644 --- a/src/claim-request-management/dto/claim-details-v2.dto.ts +++ b/src/claim-request-management/dto/claim-details-v2.dto.ts @@ -210,6 +210,16 @@ export class ClaimDetailsV2ResponseDto { }) expertResend?: ExpertResendDetailsV2Dto; + @ApiPropertyOptional({ + description: + 'Fanavaran claim reference. Returned only after the local claim reaches COMPLETED and Fanavaran has supplied at least one reference.', + example: { claimNo: 123456, claimId: 987654 }, + }) + fanavaran?: { + claimNo?: number; + claimId?: number; + }; + @ApiPropertyOptional({ description: "Damage expert opinion(s): initial and final (after objection).", type: Object, diff --git a/src/expert-claim/dto/claim-detail-v2.dto.ts b/src/expert-claim/dto/claim-detail-v2.dto.ts index 2679fe9..62a4d97 100644 --- a/src/expert-claim/dto/claim-detail-v2.dto.ts +++ b/src/expert-claim/dto/claim-detail-v2.dto.ts @@ -163,6 +163,16 @@ export class ClaimDetailV2ResponseDto { }) fileMakerApprovalActorId?: string; + @ApiPropertyOptional({ + description: + "Fanavaran claim reference. Returned only after the local claim reaches COMPLETED and Fanavaran has supplied at least one reference.", + example: { claimNo: 123456, claimId: 987654 }, + }) + fanavaran?: { + claimNo?: number; + claimId?: number; + }; + @ApiPropertyOptional({ description: "Slice of `claim.evaluation` exposed to the damage expert. " + diff --git a/src/expert-claim/expert-claim.service.ts b/src/expert-claim/expert-claim.service.ts index bfe9f60..61e5ba4 100644 --- a/src/expert-claim/expert-claim.service.ts +++ b/src/expert-claim/expert-claim.service.ts @@ -5141,6 +5141,14 @@ export class ExpertClaimService { fileMakerApprovalActorId: (claim as any).fileMakerApprovalActorId ? String((claim as any).fileMakerApprovalActorId) : undefined, + fanavaran: + claim.status === ClaimCaseStatus.COMPLETED && + (claim.claimNo != null || claim.claimId != null) + ? { + claimNo: claim.claimNo, + claimId: claim.claimId, + } + : undefined, evaluation: (evaluationForApi as | ClaimDetailV2ResponseDto["evaluation"] diff --git a/src/expert-claim/expert-claim.v2.controller.ts b/src/expert-claim/expert-claim.v2.controller.ts index 85f6a59..56b2032 100644 --- a/src/expert-claim/expert-claim.v2.controller.ts +++ b/src/expert-claim/expert-claim.v2.controller.ts @@ -166,7 +166,7 @@ export class ExpertClaimV2Controller { @ApiOperation({ summary: "Get claim request detail for damage expert", description: - "Returns full claim details including captured images, required documents, damage selections, `evaluation.priceDrop` (included whenever saved, regardless of current status), `videoCapture` (from claim-video-capture via media.videoCaptureId), and `blameCase` (linked blameCases document with party video/voice URLs like expert-blame detail). `evaluation.damageExpertReply` / `damageExpertReplyFinal` are always returned once submitted. Allowed when status is WAITING_FOR_DAMAGE_EXPERT (if locked, only the locking expert) or when awaiting factor validation.", + "Returns full claim details including captured images, required documents, damage selections, `evaluation.priceDrop` (included whenever saved, regardless of current status), `videoCapture` (from claim-video-capture via media.videoCaptureId), and `blameCase` (linked blameCases document with party video/voice URLs like expert-blame detail). `evaluation.damageExpertReply` / `damageExpertReplyFinal` are always returned once submitted. Completed claims include Fanavaran `claimNo` / `claimId` when available. Allowed when status is WAITING_FOR_DAMAGE_EXPERT (if locked, only the locking expert) or when awaiting factor validation.", }) @ApiParam({ name: "claimRequestId" }) async getClaimDetailV2(