Merge pull request 'Fixed FileMaker and FileReviewer access to COMPLETED files' (#283) from s.yahyaee/yara724-api:main into main

Reviewed-on: Yara724/api#283
This commit is contained in:
2026-09-01 21:58:43 +03:30
7 changed files with 128 additions and 2 deletions

View File

@@ -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<string, unknown>) => {
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);
});
});

View File

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

View File

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

View File

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

View File

@@ -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. " +

View File

@@ -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"]

View File

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