1
0
forked from Yara724/api

Added field expert support for insurer

This commit is contained in:
2026-06-19 10:52:47 +03:30
parent 3c863bb90c
commit 3c61e4397a

View File

@@ -24,6 +24,7 @@ import { CaseStatus } from "src/Types&Enums/blame-request-management/caseStatus.
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum"; import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service"; import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service";
import { ExpertDbService } from "src/users/entities/db-service/expert.db.service"; import { ExpertDbService } from "src/users/entities/db-service/expert.db.service";
import { FieldExpertDbService } from "src/users/entities/db-service/field-expert.db.service";
import { ExpertFileActivityDbService } from "src/users/entities/db-service/expert-file-activity.db.service"; import { ExpertFileActivityDbService } from "src/users/entities/db-service/expert-file-activity.db.service";
import { ExpertFileActivityType } from "src/users/entities/schema/expert-file-activity.schema"; import { ExpertFileActivityType } from "src/users/entities/schema/expert-file-activity.schema";
import { HashService } from "src/utils/hash/hash.service"; import { HashService } from "src/utils/hash/hash.service";
@@ -62,6 +63,7 @@ export class ExpertInsurerService {
constructor( constructor(
private readonly expertDbService: ExpertDbService, private readonly expertDbService: ExpertDbService,
private readonly damageExpertDbService: DamageExpertDbService, private readonly damageExpertDbService: DamageExpertDbService,
private readonly fieldExpertDbService: FieldExpertDbService,
private readonly expertFileActivityDbService: ExpertFileActivityDbService, private readonly expertFileActivityDbService: ExpertFileActivityDbService,
private readonly blameRequestDbService: BlameRequestDbService, private readonly blameRequestDbService: BlameRequestDbService,
private readonly claimCaseDbService: ClaimCaseDbService, private readonly claimCaseDbService: ClaimCaseDbService,
@@ -74,6 +76,26 @@ export class ExpertInsurerService {
private readonly claimSignDbService: ClaimSignDbService, private readonly claimSignDbService: ClaimSignDbService,
) {} ) {}
/**
* Returns all expert IDs (blame-expert, damage-expert, field-expert) that
* belong to this insurer's clientKey in a single parallel fetch.
*/
private async getAllRosterExpertIds(
clientObjectId: Types.ObjectId,
): Promise<Set<string>> {
const ckFilter = this.clientKeyScopeFilter(clientObjectId);
const [experts, damageExperts, fieldExperts] = await Promise.all([
this.expertDbService.findAll(ckFilter as never),
this.damageExpertDbService.findAll(ckFilter as never),
this.fieldExpertDbService.findAll(ckFilter as never),
]);
return new Set<string>(
[...experts, ...damageExperts, ...fieldExperts].map((e: any) =>
String(e._id),
),
);
}
private getClientId(actorOrId: any): Types.ObjectId { private getClientId(actorOrId: any): Types.ObjectId {
const raw = const raw =
typeof actorOrId === "string" ? actorOrId : actorOrId?.clientKey; typeof actorOrId === "string" ? actorOrId : actorOrId?.clientKey;
@@ -786,13 +808,26 @@ export class ExpertInsurerService {
private async getClientClaimFiles( private async getClientClaimFiles(
clientObjectId: Types.ObjectId, clientObjectId: Types.ObjectId,
): Promise<any[]> { ): Promise<any[]> {
const all = (await this.claimCaseDbService.find( const [all, rosterExpertIds] = await Promise.all([
{}, this.claimCaseDbService.find({}, { lean: true }) as Promise<any[]>,
{ lean: true }, this.getAllRosterExpertIds(clientObjectId),
)) as any[]; ]);
const idStr = String(clientObjectId); const idStr = String(clientObjectId);
return all return all
.filter((f) => claimCaseTouchesClient(f, idStr)) .filter((f) => {
// Primary: owner.clientId matches this insurer (guilty party's insurance)
if (claimCaseTouchesClient(f, idStr)) return true;
// Secondary: initiated or evaluated by one of this insurer's experts
// (covers field-expert IN_PERSON flow where owner.clientId ≠ this insurer)
const fieldExpertId = f?.initiatedByFieldExpertId
? String(f.initiatedByFieldExpertId)
: null;
if (fieldExpertId && rosterExpertIds.has(fieldExpertId)) return true;
const damageExpertId = this.claimDamageExpertActorId(f);
if (damageExpertId && rosterExpertIds.has(damageExpertId)) return true;
return false;
})
.map((f) => this.normalizeClaimCase(f)); .map((f) => this.normalizeClaimCase(f));
} }
@@ -803,14 +838,16 @@ export class ExpertInsurerService {
) { ) {
const clientObjectId = this.getClientId(actor); const clientObjectId = this.getClientId(actor);
const ckFilter = this.clientKeyScopeFilter(clientObjectId); const ckFilter = this.clientKeyScopeFilter(clientObjectId);
const [experts, damageExperts, blameFiles, claimFiles] = await Promise.all([ const [experts, damageExperts, fieldExperts, blameFiles, claimFiles] =
await Promise.all([
this.expertDbService.findAll(ckFilter as never), this.expertDbService.findAll(ckFilter as never),
this.damageExpertDbService.findAll(ckFilter as never), this.damageExpertDbService.findAll(ckFilter as never),
this.fieldExpertDbService.findAll(ckFilter as never),
this.getClientBlameFiles(clientObjectId), this.getClientBlameFiles(clientObjectId),
this.getClientClaimFiles(clientObjectId), this.getClientClaimFiles(clientObjectId),
]); ]);
const allExpertsRaw = [...experts, ...damageExperts]; const allExpertsRaw = [...experts, ...damageExperts, ...fieldExperts];
const expertIds = allExpertsRaw.map((e) => String(e._id)); const expertIds = allExpertsRaw.map((e) => String(e._id));
const expertActivityStatsMap = await this.buildExpertActivityStatsMap( const expertActivityStatsMap = await this.buildExpertActivityStatsMap(
clientObjectId, clientObjectId,
@@ -890,6 +927,7 @@ export class ExpertInsurerService {
const allExperts = [ const allExperts = [
...experts.map((e) => mapExpertRow(e, "blame")), ...experts.map((e) => mapExpertRow(e, "blame")),
...damageExperts.map((e) => mapExpertRow(e, "claim")), ...damageExperts.map((e) => mapExpertRow(e, "claim")),
...fieldExperts.map((e) => mapExpertRow(e, "blame")),
]; ];
const page = Number(currentPage) > 0 ? Number(currentPage) : 1; const page = Number(currentPage) > 0 ? Number(currentPage) : 1;
@@ -960,12 +998,15 @@ export class ExpertInsurerService {
const expertObjectId = this.parseObjectId(expertId, "expert id"); const expertObjectId = this.parseObjectId(expertId, "expert id");
const clientOid = this.getClientId(insurerClientKey); const clientOid = this.getClientId(insurerClientKey);
const ckFilter = this.clientKeyScopeFilter(clientOid); const ckFilter = this.clientKeyScopeFilter(clientOid);
const [experts, damageExperts] = await Promise.all([ const [experts, damageExperts, fieldExperts] = await Promise.all([
this.expertDbService.findAll(ckFilter as never), this.expertDbService.findAll(ckFilter as never),
this.damageExpertDbService.findAll(ckFilter as never), this.damageExpertDbService.findAll(ckFilter as never),
this.fieldExpertDbService.findAll(ckFilter as never),
]); ]);
const id = String(expertObjectId); const id = String(expertObjectId);
const onBlameRoster = experts.some((e) => String(e._id) === id); const onBlameRoster =
experts.some((e) => String(e._id) === id) ||
fieldExperts.some((e) => String(e._id) === id);
const onClaimRoster = damageExperts.some((e) => String(e._id) === id); const onClaimRoster = damageExperts.some((e) => String(e._id) === id);
if (!onBlameRoster && !onClaimRoster) { if (!onBlameRoster && !onClaimRoster) {
throw new ForbiddenException( throw new ForbiddenException(
@@ -973,34 +1014,41 @@ export class ExpertInsurerService {
); );
} }
const clientKeyStr = String(clientOid); // Gather all files this expert touched — blame and claim — regardless of
// which insurance company's party appears on the file. The roster check
// above already ensures the expert belongs to THIS insurer, so showing all
// files they handled is the correct scope.
const [allBlames, allClaims] = await Promise.all([
onBlameRoster
? (this.blameRequestDbService.find({}, { lean: true }) as Promise<any[]>)
: Promise.resolve([] as any[]),
(this.claimCaseDbService.find({}, { lean: true }) as Promise<any[]>),
]);
const result: any[] = [];
// Blame files handled by a field/blame expert
if (onBlameRoster) { if (onBlameRoster) {
const blames = (await this.blameRequestDbService.find( for (const b of allBlames) {
{}, if (this.blameFieldExpertIdCandidates(b).includes(id)) {
{ lean: true }, result.push(this.mapBlameFileSummaryForInsurerExpert(b));
)) as any[];
return blames
.filter(
(b) =>
blameCaseTouchesClient(b, clientKeyStr) &&
this.blameFieldExpertIdCandidates(b).includes(id),
)
.map((b) => this.mapBlameFileSummaryForInsurerExpert(b));
} }
if (onClaimRoster) {
const claims = (await this.claimCaseDbService.find(
{},
{ lean: true },
)) as any[];
return claims
.filter(
(c) =>
claimCaseTouchesClient(c, clientKeyStr) &&
this.claimDamageExpertActorId(c) === id,
)
.map((c) => this.mapClaimFileSummaryForInsurerExpert(c));
} }
return []; }
// Claim files: initiated by this field expert OR evaluated by this damage expert
for (const c of allClaims) {
const isFieldExpertClaim =
c?.initiatedByFieldExpertId &&
String(c.initiatedByFieldExpertId) === id;
const isDamageExpertClaim =
onClaimRoster && this.claimDamageExpertActorId(c) === id;
if (isFieldExpertClaim || isDamageExpertClaim) {
result.push(this.mapClaimFileSummaryForInsurerExpert(c));
}
}
return result;
} }
private validateInsurerFileRating(rating: FileRating) { private validateInsurerFileRating(rating: FileRating) {
@@ -1697,9 +1745,11 @@ export class ExpertInsurerService {
const branchIdSet = new Set(branches.map((b: any) => String(b._id))); const branchIdSet = new Set(branches.map((b: any) => String(b._id)));
const ckFilter = this.clientKeyScopeFilter(clientObjectId); const ckFilter = this.clientKeyScopeFilter(clientObjectId);
const [experts, damageExperts, blameFiles, claimFiles] = await Promise.all([ const [experts, damageExperts, fieldExperts, blameFiles, claimFiles] =
await Promise.all([
this.expertDbService.findAll(ckFilter as never), this.expertDbService.findAll(ckFilter as never),
this.damageExpertDbService.findAll(ckFilter as never), this.damageExpertDbService.findAll(ckFilter as never),
this.fieldExpertDbService.findAll(ckFilter as never),
this.getClientBlameFiles(clientObjectId), this.getClientBlameFiles(clientObjectId),
this.getClientClaimFiles(clientObjectId), this.getClientClaimFiles(clientObjectId),
]); ]);
@@ -1707,7 +1757,8 @@ export class ExpertInsurerService {
const activeExpertsByBranch = new Map<string, Set<string>>(); const activeExpertsByBranch = new Map<string, Set<string>>();
const expertBranchById = new Map<string, string>(); const expertBranchById = new Map<string, string>();
const claimExpertBranchById = new Map<string, string>(); const claimExpertBranchById = new Map<string, string>();
for (const e of experts as any[]) { // blame-expert + field-expert both handle blame/IN_PERSON files
for (const e of [...(experts as any[]), ...(fieldExperts as any[])]) {
const bid = e?.branchId ? String(e.branchId) : ""; const bid = e?.branchId ? String(e.branchId) : "";
if (!bid || !branchIdSet.has(bid)) continue; if (!bid || !branchIdSet.has(bid)) continue;
const eid = String(e?._id || ""); const eid = String(e?._id || "");
@@ -1742,9 +1793,11 @@ export class ExpertInsurerService {
}; };
for (const b of blameFiles as any[]) { for (const b of blameFiles as any[]) {
const expertId = String(b?.expert?.assignedExpertId || ""); // Resolve expert via assignment OR field-expert initiation
if (!expertId) continue; const candidateIds = this.blameFieldExpertIdCandidates(b);
const bid = expertBranchById.get(expertId); const bid = candidateIds
.map((eid) => expertBranchById.get(eid))
.find(Boolean);
if (!bid) continue; if (!bid) continue;
const fileKey = String(b?.publicId || b?._id || ""); const fileKey = String(b?.publicId || b?._id || "");
if (!fileKey) continue; if (!fileKey) continue;
@@ -1756,9 +1809,17 @@ export class ExpertInsurerService {
} }
for (const c of claimFiles as any[]) { for (const c of claimFiles as any[]) {
const expertId = String(this.claimDamageExpertActorId(c) || ""); // Field-expert-initiated claims
const fieldExpertId = c?.initiatedByFieldExpertId
? String(c.initiatedByFieldExpertId)
: null;
const damageExpertId = this.claimDamageExpertActorId(c);
const expertId = fieldExpertId ?? damageExpertId ?? "";
if (!expertId) continue; if (!expertId) continue;
const bid = claimExpertBranchById.get(expertId); const bid =
expertBranchById.get(expertId) ??
claimExpertBranchById.get(expertId) ??
undefined;
if (!bid) continue; if (!bid) continue;
const fileKey = String(c?.publicId || c?._id || ""); const fileKey = String(c?.publicId || c?._id || "");
if (!fileKey) continue; if (!fileKey) continue;