diff --git a/src/helpers/tenant-scope.ts b/src/helpers/tenant-scope.ts index 8f5eb9b..cf79fc3 100644 --- a/src/helpers/tenant-scope.ts +++ b/src/helpers/tenant-scope.ts @@ -13,6 +13,23 @@ export function requireActorClientKey(actor: { clientKey?: string }): string { export function blameCaseTouchesClient(doc: any, clientKey: string): boolean { const id = String(clientKey); + + // CAR_BODY: single party, always the first party's clientId + if (doc?.type === "CAR_BODY") { + const firstParty = (doc?.parties ?? []).find( + (p: any) => p?.role === "FIRST", + ); + return String(firstParty?.person?.clientId ?? "") === id; + } + + // THIRD_PARTY: only the guilty party's insurer sees this file + const guiltyClientId = resolveGuiltyPartyClientId(doc); + if (guiltyClientId) { + return guiltyClientId === id; + } + + // Fallback when guilt cannot be resolved yet — show to any party's insurer + // This should be rare since guilt is always known upfront in your system return (doc?.parties ?? []).some( (p: any) => String(p?.person?.clientId ?? "") === id, ); @@ -27,14 +44,11 @@ export function claimCaseTouchesClient(doc: any, clientKey: string): boolean { return String(tenantId ?? "") === id; } -/** - * Field expert–initiated cases may not yet have party clientId filled; the - * initiating expert is always scoped to their insurer via JWT. - */ export function blameCaseAccessibleToExpert( doc: any, actor: { sub: string; clientKey?: string }, ): boolean { + // Expert-initiated cases: only the initiating expert sees them if ( doc.expertInitiated && doc.initiatedByFieldExpertId && @@ -42,8 +56,11 @@ export function blameCaseAccessibleToExpert( ) { return true; } + const ck = actor?.clientKey; if (!ck) return false; + + // Uses the updated blameCaseTouchesClient — guilty party's clientId only return blameCaseTouchesClient(doc, ck); } @@ -69,3 +86,37 @@ export function assertClaimCaseForTenant( ); } } + +export function resolveGuiltyPartyClientId(doc: any): string | null { + const parties: any[] = doc?.parties ?? []; + + // Phase 1: expert decision exists — use it + const guiltyUserId = doc?.expert?.decision?.guiltyPartyId + ? String(doc.expert.decision.guiltyPartyId) + : null; + + if (guiltyUserId) { + const guiltyParty = parties.find( + (p) => String(p?.person?.userId ?? "") === guiltyUserId, + ); + return String(guiltyParty?.person?.clientId ?? "") || null; + } + + // Phase 2: no expert decision yet — use party self-statements + // The party who admitsGuilt=true is the guilty one + const guiltyByStatement = parties.find( + (p) => p?.statement?.admitsGuilt === true, + ); + if (guiltyByStatement) { + return String(guiltyByStatement?.person?.clientId ?? "") || null; + } + + // Phase 3: fallback — use blameStatus field if set + // AGREED means first party admitted guilt (convention in your system) + if (doc?.blameStatus === "AGREED") { + const firstParty = parties.find((p) => p?.role === "FIRST"); + return String(firstParty?.person?.clientId ?? "") || null; + } + + return null; +}