Fixed registrar and field expert

This commit is contained in:
SepehrYahyaee
2026-06-17 16:39:30 +03:30
parent a4eb98258b
commit bc5be99b59
19 changed files with 942 additions and 161 deletions

View File

@@ -4169,6 +4169,50 @@ export class ClaimRequestManagementService {
};
}
private async assertActorCanViewClaimV2(
claim: any,
currentUserId: string,
actor?: { sub: string; role?: string },
): Promise<void> {
const ownerId = claim.owner?.userId?.toString();
if (ownerId && ownerId === currentUserId) {
return;
}
if (actor?.role === RoleEnum.FIELD_EXPERT) {
if (claim.initiatedByFieldExpertId?.toString() === currentUserId) {
return;
}
if (claim.blameRequestId) {
const blame = await this.blameRequestDbService.findById(
claim.blameRequestId.toString(),
);
if (
blame?.expertInitiated &&
blame.initiatedByFieldExpertId &&
String(blame.initiatedByFieldExpertId) === currentUserId
) {
return;
}
}
}
if (actor?.role === RoleEnum.REGISTRAR && claim.blameRequestId) {
const blame = await this.blameRequestDbService.findById(
claim.blameRequestId.toString(),
);
if (
blame?.registrarInitiated &&
blame.initiatedByRegistrarId &&
String(blame.initiatedByRegistrarId) === currentUserId
) {
return;
}
}
throw new ForbiddenException("You do not have access to this claim");
}
/**
* Resolve effective user id for claim operations.
* For FIELD_EXPERT acting on expert-initiated IN_PERSON claim, returns the claim owner's id; otherwise returns currentUserId.
@@ -6519,7 +6563,6 @@ export class ClaimRequestManagementService {
.find(
{
expertInitiated: true,
creationMethod: "IN_PERSON",
initiatedByFieldExpertId: new Types.ObjectId(currentUserId),
},
{ select: "_id", lean: true },
@@ -6532,6 +6575,7 @@ export class ClaimRequestManagementService {
...(expertBlameIds.length
? [{ blameRequestId: { $in: expertBlameIds } }]
: []),
{ initiatedByFieldExpertId: new Types.ObjectId(currentUserId) },
],
},
{ lean: true },
@@ -6621,17 +6665,7 @@ export class ClaimRequestManagementService {
if (!claim) {
throw new NotFoundException("Claim request not found");
}
const effectiveUserId = await this.resolveClaimEffectiveUserId(
claim,
currentUserId,
actor?.role,
);
if (
!claim.owner?.userId ||
claim.owner.userId.toString() !== effectiveUserId
) {
throw new ForbiddenException("You do not have access to this claim");
}
await this.assertActorCanViewClaimV2(claim, currentUserId, actor);
const hasCapture = (data: any, key: string) =>
data && (data instanceof Map ? data.get(key) : data[key]);
@@ -6745,7 +6779,9 @@ export class ClaimRequestManagementService {
s ? s.replace(/^(.{4})(.*)(.{4})$/, "IR$1************$3") : undefined;
const maskNationalCode = (s?: string) =>
s ? s.replace(/^(.{2})(.*)(.{2})$/, "$1******$3") : undefined;
const isExpertViewer = actor?.role === RoleEnum.FIELD_EXPERT;
const isExpertViewer =
actor?.role === RoleEnum.FIELD_EXPERT ||
actor?.role === RoleEnum.REGISTRAR;
const ownerData = claim.owner
? {
userId: claim.owner.userId?.toString(),

View File

@@ -52,7 +52,7 @@ import { ClaimVehicleTypeV2 } from "src/static/outer-car-parts-catalog";
@Controller("v2/claim-request-management")
@ApiBearerAuth()
@UseGuards(GlobalGuard, RolesGuard)
@Roles(RoleEnum.USER, RoleEnum.FIELD_EXPERT)
@Roles(RoleEnum.USER, RoleEnum.FIELD_EXPERT, RoleEnum.REGISTRAR)
export class ClaimRequestManagementV2Controller {
constructor(
private readonly claimRequestManagementService: ClaimRequestManagementService,
@@ -63,7 +63,7 @@ export class ClaimRequestManagementV2Controller {
@ApiOperation({
summary: "Get My Claims (V2)",
description:
"Claims for the current user (or field-expert in-person files). Optional query: `search`, `sortBy` (publicId | createdAt | requestNo | status), `sortOrder`, `page`, `limit`. Without `page`/`limit`, returns the full filtered list.",
"Claims for the current user, or claims from blame files initiated by the current FIELD_EXPERT / REGISTRAR (LINK and IN_PERSON). Optional query: `search`, `sortBy`, `sortOrder`, `page`, `limit`.",
})
@ApiResponse({
status: 200,
@@ -97,7 +97,7 @@ export class ClaimRequestManagementV2Controller {
@ApiOperation({
summary: "Get Claim Details (V2)",
description:
"Returns the claim snapshot for **USER** (owner) or **FIELD_EXPERT** when permitted. Owners get `ownerGuidance`: `{ phaseKey, headline, nextActions[] (method + pathTemplate), objectionAllowed }` mapped from `status` / `claimStatus` / workflow so the client can show the correct screen without duplicating orchestration logic. FIELD_EXPERT does not receive `ownerGuidance`. Core payload includes documents, captures, evaluation replies, optional expert resend, and masked bank info.",
"Returns the claim snapshot for **USER** (owner), **FIELD_EXPERT**, or **REGISTRAR** when permitted. Initiating experts/registrars see unmasked money fields; owners get `ownerGuidance`.",
})
@ApiResponse({
status: 200,

View File

@@ -30,7 +30,6 @@ import { CurrentUser } from "src/decorators/user.decorator";
import { MediaPolicyService } from "src/media-policy/media-policy.service";
import { DEFAULT_MEDIA_MAX_BYTES } from "src/client/client.service";
import { RoleEnum } from "src/Types&Enums/role.enum";
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
import { ClaimVehicleTypeV2 } from "src/static/outer-car-parts-catalog";
import { ClaimRequestManagementService } from "./claim-request-management.service";
import {
@@ -93,19 +92,6 @@ export class ExpertInitiatedClaimMirrorController {
);
}
@Get("requests")
@ApiOperation({ summary: "[Expert mirror] List my (in-person) claims" })
async getMyClaims(
@CurrentUser() expert: any,
@Query() query: ListQueryV2Dto,
) {
return this.claimRequestManagementService.getMyClaimsV2(
expert.sub,
expert,
query,
);
}
@Get("outer-parts-catalog")
@ApiOperation({
summary: "Get outer parts catalog (V2)",
@@ -158,24 +144,6 @@ export class ExpertInitiatedClaimMirrorController {
);
}
@Get("request/:claimRequestId")
@ApiParam({
name: "claimRequestId",
description: "The claim case ID (MongoDB ObjectId)",
example: "507f1f77bcf86cd799439011",
})
@ApiOperation({ summary: "[Expert mirror] Get claim details" })
async getClaimDetails(
@Param("claimRequestId") claimRequestId: string,
@CurrentUser() expert: any,
) {
return this.claimRequestManagementService.getClaimDetailsV2(
claimRequestId,
expert.sub,
expert,
);
}
@Patch("select-outer-parts/:claimRequestId")
@ApiOperation({
summary: "Select Damaged Outer Car Parts (V2 - Step 2)",

View File

@@ -30,7 +30,6 @@ import { CurrentUser } from "src/decorators/user.decorator";
import { MediaPolicyService } from "src/media-policy/media-policy.service";
import { DEFAULT_MEDIA_MAX_BYTES } from "src/client/client.service";
import { RoleEnum } from "src/Types&Enums/role.enum";
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
import { ClaimVehicleTypeV2 } from "src/static/outer-car-parts-catalog";
import { ClaimRequestManagementService } from "./claim-request-management.service";
import {
@@ -93,19 +92,6 @@ export class RegistrarClaimMirrorController {
);
}
@Get("requests")
@ApiOperation({ summary: "[Registrar mirror] List my (in-person) claims" })
async getMyClaims(
@CurrentUser() registrar: any,
@Query() query: ListQueryV2Dto,
) {
return this.claimRequestManagementService.getMyClaimsV2(
registrar.sub,
registrar,
query,
);
}
@Get("outer-parts-catalog")
@ApiOperation({
summary: "Get outer parts catalog (V2)",
@@ -158,24 +144,6 @@ export class RegistrarClaimMirrorController {
);
}
@Get("request/:claimRequestId")
@ApiParam({
name: "claimRequestId",
description: "The claim case ID (MongoDB ObjectId)",
example: "507f1f77bcf86cd799439011",
})
@ApiOperation({ summary: "[Registrar mirror] Get claim details" })
async getClaimDetails(
@Param("claimRequestId") claimRequestId: string,
@CurrentUser() registrar: any,
) {
return this.claimRequestManagementService.getClaimDetailsV2(
claimRequestId,
registrar.sub,
registrar,
);
}
@Patch("select-outer-parts/:claimRequestId")
@ApiOperation({
summary: "Select Damaged Outer Car Parts (V2 - Step 2)",