forked from Yara724/api
Fixed users not being able to view their files
This commit is contained in:
@@ -62,8 +62,18 @@ import { applyListQueryV2 } from "src/helpers/list-query-v2";
|
||||
import { partyPersonMatchesUser } from "src/helpers/iran-mobile";
|
||||
import {
|
||||
buildBlamePartyAccessOrConditions,
|
||||
mongoUserIdEqualityFilter,
|
||||
collectUserIdVariants,
|
||||
} from "src/helpers/party-access-queries";
|
||||
import {
|
||||
buildOwnerUserIdAccessFilter,
|
||||
ownerUserIdMatchesActor,
|
||||
resolveLinkedUserIdStrings,
|
||||
} from "src/helpers/user-access-resolver";
|
||||
import {
|
||||
blameDamagedPartyMatchesUser,
|
||||
resolveClaimOwnerParty,
|
||||
resolveDamagedPartyUserId,
|
||||
} from "src/helpers/blame-damaged-party";
|
||||
import { claimCaseInitiatedByFieldExpert } from "src/helpers/tenant-scope";
|
||||
import { ClaimDetailsV2ResponseDto } from "./dto/claim-details-v2.dto";
|
||||
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
|
||||
@@ -2719,14 +2729,12 @@ export class ClaimRequestManagementService {
|
||||
currentUserId,
|
||||
actor?.role,
|
||||
);
|
||||
if (
|
||||
!claim.owner?.userId ||
|
||||
claim.owner.userId.toString() !== effectiveUserId
|
||||
) {
|
||||
throw new ForbiddenException(
|
||||
"You do not have access to this claim file",
|
||||
);
|
||||
}
|
||||
await this.assertEffectiveUserIsDamagedPartyOnClaim(
|
||||
claim,
|
||||
effectiveUserId,
|
||||
actor as { username?: string },
|
||||
"You do not have access to this claim file",
|
||||
);
|
||||
|
||||
const replyKey = claim.evaluation?.damageExpertReplyFinal
|
||||
? "damageExpertReplyFinal"
|
||||
@@ -4580,6 +4588,19 @@ export class ClaimRequestManagementService {
|
||||
const claimNo = await this.generateUniqueClaimNumber();
|
||||
|
||||
// 8. Create claim case
|
||||
const ownerParty = resolveClaimOwnerParty(blameRequest);
|
||||
if (!ownerParty?.person?.userId) {
|
||||
throw new BadRequestException(
|
||||
"Could not resolve claim owner from blame parties",
|
||||
);
|
||||
}
|
||||
const damagedUserId = (() => {
|
||||
const oid = String(ownerParty.person.userId);
|
||||
const dp = (blameRequest.parties || []).find(
|
||||
(p: any) => p.person?.userId && String(p.person.userId) !== oid,
|
||||
);
|
||||
return dp?.person?.userId ? String(dp.person.userId) : null;
|
||||
})();
|
||||
const newClaim = await this.claimCaseDbService.create({
|
||||
requestNo: claimNo,
|
||||
publicId: blameRequest.publicId,
|
||||
@@ -4594,32 +4615,19 @@ export class ClaimRequestManagementService {
|
||||
completedSteps: [ClaimWorkflowStep.CLAIM_CREATED],
|
||||
locked: false,
|
||||
},
|
||||
owner: {
|
||||
userId: new Types.ObjectId(currentUserId),
|
||||
userRole: currentUserParty.role as any,
|
||||
fullName: currentUserParty.person?.fullName,
|
||||
userClientKey: currentUserParty.person?.clientId
|
||||
? String(currentUserParty.person.clientId)
|
||||
: undefined,
|
||||
// clientId must be the GUILTY party's clientId — their insurer pays the claim
|
||||
...(() => {
|
||||
if (isCarBody) {
|
||||
// CAR_BODY has no guilty party — use the first party's own clientId
|
||||
const firstParty = parties.find((p) => p.role === "FIRST");
|
||||
const cid = firstParty?.person?.clientId;
|
||||
return cid ? { clientId: new Types.ObjectId(String(cid)) } : {};
|
||||
}
|
||||
|
||||
const guiltyPartyId = String(
|
||||
blameRequest?.expert?.decision?.guiltyPartyId ?? "",
|
||||
);
|
||||
const guiltyParty = parties.find(
|
||||
(p) => String(p.person?.userId) === guiltyPartyId,
|
||||
);
|
||||
const cid = guiltyParty?.person?.clientId;
|
||||
return cid ? { clientId: new Types.ObjectId(String(cid)) } : {};
|
||||
})(),
|
||||
},
|
||||
...(damagedUserId
|
||||
? { damagedPartyUserId: new Types.ObjectId(damagedUserId) }
|
||||
: {}),
|
||||
owner: (() => {
|
||||
const cid = ownerParty.person.clientId;
|
||||
return {
|
||||
userId: new Types.ObjectId(String(ownerParty.person.userId)),
|
||||
userRole: ownerParty.role as any,
|
||||
fullName: currentUserParty.person?.fullName,
|
||||
userClientKey: cid ? String(cid) : undefined,
|
||||
...(cid ? { clientId: new Types.ObjectId(String(cid)) } : {}),
|
||||
};
|
||||
})(),
|
||||
history: [
|
||||
{
|
||||
type: "CLAIM_CREATED",
|
||||
@@ -4725,9 +4733,12 @@ export class ClaimRequestManagementService {
|
||||
throw new ConflictException("A claim for this blame case already exists");
|
||||
}
|
||||
const claimNo = await this.generateUniqueClaimNumber();
|
||||
const damagedParty = parties.find(
|
||||
(p) => p.person?.userId && String(p.person.userId) === damagedUserId,
|
||||
);
|
||||
const ownerParty = resolveClaimOwnerParty(blameRequest);
|
||||
if (!ownerParty?.person?.userId) {
|
||||
throw new BadRequestException(
|
||||
"Could not resolve claim owner (guilty party) from blame",
|
||||
);
|
||||
}
|
||||
const newClaim = await this.claimCaseDbService.create({
|
||||
requestNo: claimNo,
|
||||
publicId: blameRequest.publicId,
|
||||
@@ -4743,12 +4754,15 @@ export class ClaimRequestManagementService {
|
||||
completedSteps: [ClaimWorkflowStep.CLAIM_CREATED],
|
||||
locked: false,
|
||||
},
|
||||
damagedPartyUserId: new Types.ObjectId(damagedUserId),
|
||||
owner: {
|
||||
userId: new Types.ObjectId(damagedUserId),
|
||||
userRole: damagedParty?.role as any,
|
||||
...(damagedParty?.person?.clientId
|
||||
userId: new Types.ObjectId(String(ownerParty.person.userId)),
|
||||
userRole: ownerParty.role as any,
|
||||
...(ownerParty.person.clientId
|
||||
? {
|
||||
clientId: new Types.ObjectId(damagedParty.person.clientId as any),
|
||||
clientId: new Types.ObjectId(
|
||||
String(ownerParty.person.clientId),
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
@@ -4782,32 +4796,128 @@ export class ClaimRequestManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
private async assertActorCanViewClaimV2(
|
||||
/**
|
||||
* True when the actor appears as a non-owner (damaged) party on the claim's
|
||||
* snapshot.parties. This is the fastest, blame-free fallback: the snapshot is
|
||||
* written at claim-creation time and always holds the correct user IDs.
|
||||
*
|
||||
* Logic:
|
||||
* - Any party whose person.userId is in linkedUserIds and is NOT the claim
|
||||
* owner (guilty party) is considered the damaged party.
|
||||
* - If owner.userId is unknown we accept any matching party (CAR_BODY flows).
|
||||
*/
|
||||
private claimSnapshotPartyMatchesAsDamagedParty(
|
||||
claim: any,
|
||||
currentUserId: string,
|
||||
actor?: { sub: string; role?: string },
|
||||
linkedUserIdStrings: string[],
|
||||
): boolean {
|
||||
const parties: any[] = claim?.snapshot?.parties ?? [];
|
||||
if (!parties.length) return false;
|
||||
|
||||
const ownerIdStr = claim?.owner?.userId
|
||||
? String(claim.owner.userId)
|
||||
: null;
|
||||
|
||||
for (const party of parties) {
|
||||
const partyUserId = party?.person?.userId;
|
||||
if (partyUserId == null) continue;
|
||||
const partyStr = String(partyUserId);
|
||||
|
||||
// Must be this actor
|
||||
if (!linkedUserIdStrings.some((id) => id === partyStr)) continue;
|
||||
|
||||
// Must NOT be the guilty-owner (unless owner is unknown — CAR_BODY)
|
||||
if (ownerIdStr && partyStr === ownerIdStr) continue;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private async assertEffectiveUserIsDamagedPartyOnClaim(
|
||||
claimCase: any,
|
||||
effectiveUserId: string,
|
||||
actor?: { username?: string },
|
||||
message = "Only the damaged party can perform this action on the claim",
|
||||
): Promise<void> {
|
||||
const ownerId = claim.owner?.userId?.toString();
|
||||
if (ownerId && ownerId === currentUserId) {
|
||||
const linkedUserIds = await resolveLinkedUserIdStrings(
|
||||
this.userDbService,
|
||||
{ sub: effectiveUserId, username: actor?.username },
|
||||
);
|
||||
const actorCtx = { sub: effectiveUserId, username: actor?.username };
|
||||
|
||||
// 0. damagedPartyUserId stored directly on the claim (fastest, most reliable)
|
||||
if (ownerUserIdMatchesActor(claimCase?.damagedPartyUserId, linkedUserIds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
(!actor?.role || actor.role === RoleEnum.USER) &&
|
||||
claim.blameRequestId
|
||||
) {
|
||||
// 1. Owner (= guilty party for THIRD_PARTY) — kept for backward compat
|
||||
// on CAR_BODY where owner IS the damaged party.
|
||||
if (ownerUserIdMatchesActor(claimCase?.owner?.userId, linkedUserIds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Blame-based resolution (primary path for THIRD_PARTY)
|
||||
if (claimCase?.blameRequestId) {
|
||||
const blame = await this.blameRequestDbService.findById(
|
||||
claim.blameRequestId.toString(),
|
||||
claimCase.blameRequestId.toString(),
|
||||
);
|
||||
const isParty =
|
||||
Array.isArray(blame?.parties) &&
|
||||
blame.parties.some((p: any) =>
|
||||
partyPersonMatchesUser(p?.person, {
|
||||
sub: currentUserId,
|
||||
username: (actor as { username?: string })?.username,
|
||||
}),
|
||||
if (
|
||||
blame &&
|
||||
blameDamagedPartyMatchesUser(blame, actorCtx, linkedUserIds)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Snapshot-party fallback: claim.snapshot.parties is written at creation
|
||||
// and is reliable even when blame lookup fails or userId mapping differs.
|
||||
if (this.claimSnapshotPartyMatchesAsDamagedParty(claimCase, linkedUserIds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ForbiddenException(message);
|
||||
}
|
||||
|
||||
private async assertActorCanViewClaimV2(
|
||||
claim: any,
|
||||
currentUserId: string,
|
||||
actor?: { sub: string; role?: string; username?: string },
|
||||
): Promise<void> {
|
||||
const linkedUserIds = await resolveLinkedUserIdStrings(
|
||||
this.userDbService,
|
||||
{ sub: currentUserId, username: actor?.username },
|
||||
);
|
||||
|
||||
if (ownerUserIdMatchesActor(claim.owner?.userId, linkedUserIds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// damagedPartyUserId is set at claim-creation time — the most direct check.
|
||||
if (ownerUserIdMatchesActor(claim.damagedPartyUserId, linkedUserIds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!actor?.role || actor.role === RoleEnum.USER) {
|
||||
const userCtx = { sub: currentUserId, username: actor?.username };
|
||||
|
||||
// Any party on the linked blame can VIEW the claim.
|
||||
// (Mutation guards use assertEffectiveUserIsDamagedPartyOnClaim which is
|
||||
// stricter and only allows the damaged party to act.)
|
||||
if (claim.blameRequestId) {
|
||||
const blame = await this.blameRequestDbService.findById(
|
||||
claim.blameRequestId.toString(),
|
||||
);
|
||||
if (isParty) {
|
||||
if (blame) {
|
||||
const parties: any[] = (blame as any).parties ?? [];
|
||||
const isParty = parties.some((p: any) =>
|
||||
partyPersonMatchesUser(p?.person, userCtx, linkedUserIds),
|
||||
);
|
||||
if (isParty) return;
|
||||
}
|
||||
}
|
||||
|
||||
// Snapshot-party fallback (reliable for claims with snapshot written).
|
||||
if (this.claimSnapshotPartyMatchesAsDamagedParty(claim, linkedUserIds)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -4878,9 +4988,10 @@ export class ClaimRequestManagementService {
|
||||
return currentUserId;
|
||||
}
|
||||
}
|
||||
return claimCase.owner?.userId
|
||||
? String(claimCase.owner.userId)
|
||||
: currentUserId;
|
||||
return (
|
||||
resolveDamagedPartyUserId(blameRequest) ??
|
||||
(claimCase.owner?.userId ? String(claimCase.owner.userId) : currentUserId)
|
||||
);
|
||||
}
|
||||
|
||||
async createClaimFromBlameForRegistrarV1(
|
||||
@@ -4936,9 +5047,12 @@ export class ClaimRequestManagementService {
|
||||
if (existingClaim)
|
||||
throw new ConflictException("A claim for this blame case already exists");
|
||||
const claimNo = await this.generateUniqueClaimNumber();
|
||||
const damagedParty = parties.find(
|
||||
(p) => p.person?.userId && String(p.person.userId) === damagedUserId,
|
||||
);
|
||||
const ownerParty = resolveClaimOwnerParty(blameRequest);
|
||||
if (!ownerParty?.person?.userId) {
|
||||
throw new BadRequestException(
|
||||
"Could not resolve claim owner (guilty party) from blame",
|
||||
);
|
||||
}
|
||||
const newClaim = await this.claimCaseDbService.create({
|
||||
requestNo: claimNo,
|
||||
publicId: blameRequest.publicId,
|
||||
@@ -4953,12 +5067,15 @@ export class ClaimRequestManagementService {
|
||||
completedSteps: [ClaimWorkflowStep.CLAIM_CREATED],
|
||||
locked: false,
|
||||
},
|
||||
damagedPartyUserId: new Types.ObjectId(damagedUserId),
|
||||
owner: {
|
||||
userId: new Types.ObjectId(damagedUserId),
|
||||
userRole: damagedParty?.role as any,
|
||||
...(damagedParty?.person?.clientId
|
||||
userId: new Types.ObjectId(String(ownerParty.person.userId)),
|
||||
userRole: ownerParty.role as any,
|
||||
...(ownerParty.person.clientId
|
||||
? {
|
||||
clientId: new Types.ObjectId(damagedParty.person.clientId as any),
|
||||
clientId: new Types.ObjectId(
|
||||
String(ownerParty.person.clientId),
|
||||
),
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
@@ -5025,15 +5142,12 @@ export class ClaimRequestManagementService {
|
||||
currentUserId,
|
||||
actor?.role,
|
||||
);
|
||||
// 2. Validate user is the claim owner (or field expert acting for IN_PERSON claim)
|
||||
if (
|
||||
!claimCase.owner?.userId ||
|
||||
claimCase.owner.userId.toString() !== effectiveUserId
|
||||
) {
|
||||
throw new ForbiddenException(
|
||||
"Only the claim owner (damaged party) can select damaged parts",
|
||||
);
|
||||
}
|
||||
await this.assertEffectiveUserIsDamagedPartyOnClaim(
|
||||
claimCase,
|
||||
effectiveUserId,
|
||||
actor as { username?: string },
|
||||
"Only the claim owner (damaged party) can select damaged parts",
|
||||
);
|
||||
|
||||
// 3. Validate workflow step is correct (should be at SELECT_OUTER_PARTS)
|
||||
if (
|
||||
@@ -5245,15 +5359,13 @@ export class ClaimRequestManagementService {
|
||||
currentUserId,
|
||||
actor?.role,
|
||||
);
|
||||
// 2. Validate user is the claim owner (or field expert for IN_PERSON claim)
|
||||
if (
|
||||
!claimCase.owner?.userId ||
|
||||
claimCase.owner.userId.toString() !== effectiveUserId
|
||||
) {
|
||||
throw new ForbiddenException(
|
||||
"Only the claim owner (damaged party) can submit other parts and bank information",
|
||||
);
|
||||
}
|
||||
// 2. Validate user is the damaged party (or field expert for IN_PERSON claim)
|
||||
await this.assertEffectiveUserIsDamagedPartyOnClaim(
|
||||
claimCase,
|
||||
effectiveUserId,
|
||||
actor as { username?: string },
|
||||
"Only the claim owner (damaged party) can submit other parts and bank information",
|
||||
);
|
||||
|
||||
// 3. Validate workflow step is correct (should be at SELECT_OTHER_PARTS)
|
||||
if (
|
||||
@@ -5468,14 +5580,12 @@ export class ClaimRequestManagementService {
|
||||
currentUserId,
|
||||
actor?.role,
|
||||
);
|
||||
if (
|
||||
!claimCase.owner?.userId ||
|
||||
claimCase.owner.userId.toString() !== effectiveUserId
|
||||
) {
|
||||
throw new ForbiddenException(
|
||||
"Only the claim owner can view capture requirements",
|
||||
);
|
||||
}
|
||||
await this.assertEffectiveUserIsDamagedPartyOnClaim(
|
||||
claimCase,
|
||||
effectiveUserId,
|
||||
actor as { username?: string },
|
||||
"Only the claim owner can view capture requirements",
|
||||
);
|
||||
|
||||
// Build car-type aware outer-parts lookup (complete source: static catalog).
|
||||
// Uses the raw catalog shape (`{id, key, titleFa, side, ...}`) because the
|
||||
@@ -5764,14 +5874,12 @@ export class ClaimRequestManagementService {
|
||||
currentUserId,
|
||||
actor?.role,
|
||||
);
|
||||
if (
|
||||
!claimCase.owner?.userId ||
|
||||
claimCase.owner.userId.toString() !== effectiveUserId
|
||||
) {
|
||||
throw new ForbiddenException(
|
||||
"Only the claim owner can complete this step",
|
||||
);
|
||||
}
|
||||
await this.assertEffectiveUserIsDamagedPartyOnClaim(
|
||||
claimCase,
|
||||
effectiveUserId,
|
||||
actor as { username?: string },
|
||||
"Only the claim owner can complete this step",
|
||||
);
|
||||
if (
|
||||
claimCase.workflow?.currentStep !== ClaimWorkflowStep.USER_EXPERT_RESEND
|
||||
) {
|
||||
@@ -5840,14 +5948,12 @@ export class ClaimRequestManagementService {
|
||||
currentUserId,
|
||||
actor?.role,
|
||||
);
|
||||
if (
|
||||
!claimCase.owner?.userId ||
|
||||
claimCase.owner.userId.toString() !== effectiveUserId
|
||||
) {
|
||||
throw new ForbiddenException(
|
||||
"Only the claim owner can upload documents",
|
||||
);
|
||||
}
|
||||
await this.assertEffectiveUserIsDamagedPartyOnClaim(
|
||||
claimCase,
|
||||
effectiveUserId,
|
||||
actor as { username?: string },
|
||||
"Only the claim owner can upload documents",
|
||||
);
|
||||
|
||||
const step = claimCase.workflow?.currentStep;
|
||||
const isResendUpload = step === ClaimWorkflowStep.USER_EXPERT_RESEND;
|
||||
@@ -6186,12 +6292,12 @@ export class ClaimRequestManagementService {
|
||||
currentUserId,
|
||||
actor?.role,
|
||||
);
|
||||
if (
|
||||
!claimCase.owner?.userId ||
|
||||
claimCase.owner.userId.toString() !== effectiveUserId
|
||||
) {
|
||||
throw new ForbiddenException("Only the claim owner can capture parts");
|
||||
}
|
||||
await this.assertEffectiveUserIsDamagedPartyOnClaim(
|
||||
claimCase,
|
||||
effectiveUserId,
|
||||
actor as { username?: string },
|
||||
"Only the claim owner can capture parts",
|
||||
);
|
||||
|
||||
const capStep = claimCase.workflow?.currentStep;
|
||||
const isResendCapture = capStep === ClaimWorkflowStep.USER_EXPERT_RESEND;
|
||||
@@ -6483,14 +6589,12 @@ export class ClaimRequestManagementService {
|
||||
currentUserId,
|
||||
actor?.role,
|
||||
);
|
||||
if (
|
||||
!claimCase.owner?.userId ||
|
||||
claimCase.owner.userId.toString() !== effectiveUserId
|
||||
) {
|
||||
throw new ForbiddenException(
|
||||
"Only the claim owner can upload car capture video",
|
||||
);
|
||||
}
|
||||
await this.assertEffectiveUserIsDamagedPartyOnClaim(
|
||||
claimCase,
|
||||
effectiveUserId,
|
||||
actor as { username?: string },
|
||||
"Only the claim owner can upload car capture video",
|
||||
);
|
||||
|
||||
if (!fileDetail) {
|
||||
throw new BadRequestException("Video file is required.");
|
||||
@@ -6606,14 +6710,12 @@ export class ClaimRequestManagementService {
|
||||
currentUserId,
|
||||
actor?.role,
|
||||
);
|
||||
if (
|
||||
!claimCase.owner?.userId ||
|
||||
claimCase.owner.userId.toString() !== effectiveUserId
|
||||
) {
|
||||
throw new ForbiddenException(
|
||||
"Only the claim owner can submit an objection",
|
||||
);
|
||||
}
|
||||
await this.assertEffectiveUserIsDamagedPartyOnClaim(
|
||||
claimCase,
|
||||
effectiveUserId,
|
||||
actor as { username?: string },
|
||||
"Only the claim owner can submit an objection",
|
||||
);
|
||||
|
||||
if (claimCase.evaluation?.objection?.submittedAt) {
|
||||
throw new ConflictException(
|
||||
@@ -6827,14 +6929,12 @@ export class ClaimRequestManagementService {
|
||||
currentUserId,
|
||||
actor?.role,
|
||||
);
|
||||
if (
|
||||
!claimCase.owner?.userId ||
|
||||
claimCase.owner.userId.toString() !== effectiveUserId
|
||||
) {
|
||||
throw new ForbiddenException(
|
||||
"Only the claim owner can submit a satisfaction rating.",
|
||||
);
|
||||
}
|
||||
await this.assertEffectiveUserIsDamagedPartyOnClaim(
|
||||
claimCase,
|
||||
effectiveUserId,
|
||||
actor as { username?: string },
|
||||
"Only the claim owner can submit a satisfaction rating.",
|
||||
);
|
||||
|
||||
if (claimCase.status !== ClaimCaseStatus.COMPLETED) {
|
||||
throw new BadRequestException(
|
||||
@@ -6932,14 +7032,12 @@ export class ClaimRequestManagementService {
|
||||
currentUserId,
|
||||
actor?.role,
|
||||
);
|
||||
if (
|
||||
!claimCase.owner?.userId ||
|
||||
claimCase.owner.userId.toString() !== effectiveUserId
|
||||
) {
|
||||
throw new ForbiddenException(
|
||||
"Only the claim owner (damaged party) can sign at this step.",
|
||||
);
|
||||
}
|
||||
await this.assertEffectiveUserIsDamagedPartyOnClaim(
|
||||
claimCase,
|
||||
effectiveUserId,
|
||||
actor as { username?: string },
|
||||
"Only the claim owner (damaged party) can sign at this step.",
|
||||
);
|
||||
|
||||
if (!claimCaseStatusAllowsOwnerInsurerSignStep(claimCase.status)) {
|
||||
throw new BadRequestException(
|
||||
@@ -7243,38 +7341,102 @@ export class ClaimRequestManagementService {
|
||||
{ lean: true },
|
||||
);
|
||||
} else {
|
||||
const partyBlameOr = buildBlamePartyAccessOrConditions({
|
||||
const linkedUserIds = await resolveLinkedUserIdStrings(
|
||||
this.userDbService,
|
||||
{
|
||||
sub: currentUserId,
|
||||
username: (actor as { username?: string })?.username,
|
||||
},
|
||||
);
|
||||
const actorCtx = {
|
||||
sub: currentUserId,
|
||||
username: (actor as { username?: string })?.username,
|
||||
});
|
||||
const partyBlameIds = await this.blameRequestDbService
|
||||
.find(
|
||||
partyBlameOr.length === 1
|
||||
? (partyBlameOr[0] as any)
|
||||
: ({ $or: partyBlameOr } as any),
|
||||
{ select: "_id", lean: true },
|
||||
)
|
||||
.then((docs) => docs.map((d) => (d as any)._id));
|
||||
|
||||
const ownerOr = mongoUserIdEqualityFilter(
|
||||
"owner.userId",
|
||||
currentUserId,
|
||||
};
|
||||
const partyBlameOr = buildBlamePartyAccessOrConditions(
|
||||
actorCtx,
|
||||
linkedUserIds,
|
||||
);
|
||||
const claimOr: Record<string, unknown>[] = [];
|
||||
if (ownerOr) claimOr.push(ownerOr);
|
||||
if (partyBlameIds.length) {
|
||||
claimOr.push({ blameRequestId: { $in: partyBlameIds } });
|
||||
}
|
||||
|
||||
claims =
|
||||
claimOr.length === 0
|
||||
? []
|
||||
: await this.claimCaseDbService.find(
|
||||
claimOr.length === 1
|
||||
? (claimOr[0] as any)
|
||||
: ({ $or: claimOr } as any),
|
||||
const idVariants = collectUserIdVariants(...linkedUserIds);
|
||||
|
||||
const ownerOr = buildOwnerUserIdAccessFilter(idVariants);
|
||||
|
||||
// damagedPartyUserId filter — direct match without blame lookup
|
||||
const damagedOr =
|
||||
idVariants.length === 1
|
||||
? { damagedPartyUserId: idVariants[0] }
|
||||
: idVariants.length > 1
|
||||
? { damagedPartyUserId: { $in: idVariants } }
|
||||
: null;
|
||||
|
||||
// Build snapshot-party query: claim.snapshot.parties.person.userId
|
||||
// is the most direct and reliable path — it's copied from the blame
|
||||
// at claim-creation time and always has the correct damaged-party userId.
|
||||
const snapshotPartyFilter =
|
||||
idVariants.length === 1
|
||||
? { "snapshot.parties.person.userId": idVariants[0] }
|
||||
: idVariants.length > 1
|
||||
? { "snapshot.parties.person.userId": { $in: idVariants } }
|
||||
: null;
|
||||
|
||||
const [ownerClaims, damagedDirectClaims, snapshotClaims, candidateBlames] =
|
||||
await Promise.all([
|
||||
ownerOr
|
||||
? this.claimCaseDbService.find(ownerOr as any, { lean: true })
|
||||
: Promise.resolve([]),
|
||||
damagedOr
|
||||
? this.claimCaseDbService.find(damagedOr as any, { lean: true })
|
||||
: Promise.resolve([]),
|
||||
snapshotPartyFilter
|
||||
? this.claimCaseDbService.find(snapshotPartyFilter as any, {
|
||||
lean: true,
|
||||
})
|
||||
: Promise.resolve([]),
|
||||
partyBlameOr.length
|
||||
? this.blameRequestDbService.find(
|
||||
partyBlameOr.length === 1
|
||||
? (partyBlameOr[0] as any)
|
||||
: ({ $or: partyBlameOr } as any),
|
||||
{
|
||||
lean: true,
|
||||
select: "type parties expert.decision blameStatus",
|
||||
},
|
||||
)
|
||||
: Promise.resolve([]),
|
||||
]);
|
||||
|
||||
// Any blame where the user is a party (not just damaged) surfaces
|
||||
// the linked claim. Mutation guards protect against acting as the
|
||||
// wrong party; viewing is intentionally open to all parties.
|
||||
const partyBlameIds = (candidateBlames as any[]).map((b) => b._id);
|
||||
|
||||
const damagedClaims =
|
||||
partyBlameIds.length > 0
|
||||
? await this.claimCaseDbService.find(
|
||||
{ blameRequestId: { $in: partyBlameIds } },
|
||||
{ lean: true },
|
||||
);
|
||||
)
|
||||
: [];
|
||||
|
||||
// Merge: owner claims + blame-resolved damaged claims + snapshot-party claims
|
||||
// snapshotClaims may include guilty-owner claims too — filter them out here.
|
||||
const snapshotDamagedClaims = (snapshotClaims as any[]).filter((c) => {
|
||||
if (!c?.owner?.userId) return true; // unknown owner: allow
|
||||
const ownerStr = String(c.owner.userId);
|
||||
// Exclude if the user IS the owner (already in ownerClaims)
|
||||
return !linkedUserIds.some((id) => id === ownerStr);
|
||||
});
|
||||
|
||||
const byId = new Map<string, any>();
|
||||
for (const c of [
|
||||
...(ownerClaims as any[]),
|
||||
...(damagedDirectClaims as any[]),
|
||||
...(damagedClaims as any[]),
|
||||
...snapshotDamagedClaims,
|
||||
]) {
|
||||
byId.set(String(c._id), c);
|
||||
}
|
||||
claims = [...byId.values()];
|
||||
}
|
||||
const list = (claims as any[]).map((c) => ({
|
||||
claimRequestId: c._id.toString(),
|
||||
|
||||
@@ -116,6 +116,14 @@ export class ClaimCase {
|
||||
@Prop({ type: Types.ObjectId, index: true })
|
||||
initiatedByFieldExpertId?: Types.ObjectId;
|
||||
|
||||
/**
|
||||
* The damaged party's userId, resolved from the blame at claim-creation time.
|
||||
* Stored here so view/list access does not require an extra blame lookup.
|
||||
* For CAR_BODY this is the FIRST party; for THIRD_PARTY it is the non-guilty party.
|
||||
*/
|
||||
@Prop({ type: Types.ObjectId, index: true })
|
||||
damagedPartyUserId?: Types.ObjectId;
|
||||
|
||||
@Prop({ type: ClaimWorkflowSchema, default: () => ({}) })
|
||||
workflow?: ClaimWorkflow;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user