forked from Yara724/api
YARA-1110
This commit is contained in:
@@ -388,6 +388,12 @@ export class ExpertClaimService {
|
||||
claim: any,
|
||||
actor: any,
|
||||
): Promise<void> {
|
||||
// FILE_REVIEWER: by the time we reach this method the caller has already
|
||||
// verified that actor.sub === blame.assignedFileReviewerId (Phase 2 of
|
||||
// assignClaimForReviewV2). The generic tenant-scope check would incorrectly
|
||||
// reject them because initiatedByFieldExpertId belongs to the FileMaker, not
|
||||
// the reviewer. Skip it — the assignment check is the access proof.
|
||||
if ((actor as any).role === RoleEnum.FILE_REVIEWER) return;
|
||||
const blame = await this.loadBlameForClaim(claim);
|
||||
assertClaimCaseForExpertActor(claim, actor, blame);
|
||||
}
|
||||
@@ -2635,11 +2641,55 @@ export class ExpertClaimService {
|
||||
throw new NotFoundException("Claim request not found");
|
||||
}
|
||||
|
||||
// FILE_REVIEWER: assign themselves to the linked blame (V4 flow).
|
||||
// This is a different path from the damage-expert lock — it operates on the
|
||||
// blame document and is idempotent (same reviewer can re-assign to themselves).
|
||||
// FILE_REVIEWER: two-phase lock behaviour.
|
||||
// Phase 1 — blame is WAITING_FOR_FILE_REVIEWER: atomically set
|
||||
// assignedFileReviewerId on the blame document.
|
||||
// Phase 2 — blame is already past WAITING_FOR_FILE_REVIEWER (i.e. the
|
||||
// reviewer finished field work and blame moved to WAITING_FOR_EXPERT):
|
||||
// the reviewer now needs the standard damage-expert workflow lock
|
||||
// so they can perform damage assessment. Fall through to the
|
||||
// damage-expert path below; assertExpertActorOnClaim will verify
|
||||
// tenant scope via the reviewer's clientKey.
|
||||
if ((actor as any).role === RoleEnum.FILE_REVIEWER) {
|
||||
return this.assignFileReviewerToV4Blame(claimRequestId, claim, actor);
|
||||
if (!claim.blameRequestId) {
|
||||
throw new BadRequestException({
|
||||
success: false,
|
||||
status: "unavailable" satisfies ExpertFileAssignStatus,
|
||||
message: "This claim has no linked blame file.",
|
||||
});
|
||||
}
|
||||
const reviewerBlame = await this.blameRequestDbService.findById(
|
||||
String(claim.blameRequestId),
|
||||
);
|
||||
if (!reviewerBlame) {
|
||||
throw new NotFoundException("Linked blame file not found.");
|
||||
}
|
||||
const blameStatus = (reviewerBlame as any).status as string;
|
||||
if (blameStatus === "WAITING_FOR_FILE_REVIEWER") {
|
||||
// Phase 1: blame-assign path
|
||||
return this.assignFileReviewerToV4Blame(claimRequestId, claim, actor);
|
||||
}
|
||||
// Phase 2: blame is past the initial assignment step.
|
||||
// Only the reviewer who was assigned during Phase 1 may proceed.
|
||||
const assignedReviewerId = (reviewerBlame as any).assignedFileReviewerId
|
||||
? String((reviewerBlame as any).assignedFileReviewerId)
|
||||
: null;
|
||||
if (!assignedReviewerId) {
|
||||
throw new BadRequestException({
|
||||
success: false,
|
||||
status: "unavailable" satisfies ExpertFileAssignStatus,
|
||||
message: "No reviewer has been assigned to this file yet.",
|
||||
});
|
||||
}
|
||||
if (assignedReviewerId !== actor.sub) {
|
||||
throw new ConflictException({
|
||||
success: false,
|
||||
status: "locked" satisfies ExpertFileAssignStatus,
|
||||
message: "This file is assigned to another reviewer.",
|
||||
});
|
||||
}
|
||||
// Assigned reviewer — fall through to the damage-expert workflow lock below.
|
||||
// (actor.role stays FILE_REVIEWER; assertExpertActorOnClaim checks clientKey scope)
|
||||
}
|
||||
|
||||
await this.assertExpertActorOnClaim(claim, actor);
|
||||
@@ -4613,7 +4663,8 @@ export class ExpertClaimService {
|
||||
const actorId = actor.sub;
|
||||
if (
|
||||
actor.role !== RoleEnum.FIELD_EXPERT &&
|
||||
actor.role !== RoleEnum.FILE_MAKER
|
||||
actor.role !== RoleEnum.FILE_MAKER &&
|
||||
actor.role !== RoleEnum.FILE_REVIEWER
|
||||
) {
|
||||
await this.reconcileStaleExpertReviewingClaimLocksForTenant(actor);
|
||||
}
|
||||
@@ -4625,7 +4676,12 @@ export class ExpertClaimService {
|
||||
}
|
||||
|
||||
const linkedBlame = await this.loadBlameForClaim(claim);
|
||||
assertClaimCaseForExpertActor(claim, actor, linkedBlame);
|
||||
// FILE_REVIEWER access is validated by the role-specific gate below
|
||||
// (isAssignedToMe || isOpen). Skip the generic tenant-scope assert which
|
||||
// would incorrectly reject them via the initiatedByFieldExpertId path.
|
||||
if (actor.role !== RoleEnum.FILE_REVIEWER) {
|
||||
assertClaimCaseForExpertActor(claim, actor, linkedBlame);
|
||||
}
|
||||
|
||||
// Variables used both in the gate block and in the detail-build section below
|
||||
const isDamageExpertPhase =
|
||||
@@ -4650,15 +4706,15 @@ export class ExpertClaimService {
|
||||
actor.role === RoleEnum.FILE_REVIEWER
|
||||
) {
|
||||
if (actor.role === RoleEnum.FILE_REVIEWER) {
|
||||
// FILE_REVIEWER: must be a V4 file AND (they are the assigned reviewer OR
|
||||
// FILE_REVIEWER: must be a V4/V5 file AND (they are the assigned reviewer OR
|
||||
// the file is still open — WAITING_FOR_FILE_REVIEWER with no reviewer yet).
|
||||
const isV4Blame =
|
||||
const isV4V5Blame =
|
||||
(linkedBlame as any)?.isMadeByFileMaker &&
|
||||
linkedBlame?.expertInitiated &&
|
||||
linkedBlame?.creationMethod === "IN_PERSON";
|
||||
if (!isV4Blame) {
|
||||
if (!isV4V5Blame) {
|
||||
throw new ForbiddenException(
|
||||
"FileReviewers can only access V4 FileMaker files.",
|
||||
"FileReviewers can only access V4/V5 FileMaker files.",
|
||||
);
|
||||
}
|
||||
const assignedReviewerId = (linkedBlame as any)?.assignedFileReviewerId
|
||||
|
||||
Reference in New Issue
Block a user