From 3c61e4397a38883001517a1b5f300c2e80e6e277 Mon Sep 17 00:00:00 2001 From: Sepehr Yahyaee Date: Fri, 19 Jun 2026 10:52:47 +0330 Subject: [PATCH] Added field expert support for insurer --- src/expert-insurer/expert-insurer.service.ts | 163 +++++++++++++------ 1 file changed, 112 insertions(+), 51 deletions(-) diff --git a/src/expert-insurer/expert-insurer.service.ts b/src/expert-insurer/expert-insurer.service.ts index 016386c..fbeb66d 100644 --- a/src/expert-insurer/expert-insurer.service.ts +++ b/src/expert-insurer/expert-insurer.service.ts @@ -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 { DamageExpertDbService } from "src/users/entities/db-service/damage-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 { ExpertFileActivityType } from "src/users/entities/schema/expert-file-activity.schema"; import { HashService } from "src/utils/hash/hash.service"; @@ -62,6 +63,7 @@ export class ExpertInsurerService { constructor( private readonly expertDbService: ExpertDbService, private readonly damageExpertDbService: DamageExpertDbService, + private readonly fieldExpertDbService: FieldExpertDbService, private readonly expertFileActivityDbService: ExpertFileActivityDbService, private readonly blameRequestDbService: BlameRequestDbService, private readonly claimCaseDbService: ClaimCaseDbService, @@ -74,6 +76,26 @@ export class ExpertInsurerService { 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> { + 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( + [...experts, ...damageExperts, ...fieldExperts].map((e: any) => + String(e._id), + ), + ); + } + private getClientId(actorOrId: any): Types.ObjectId { const raw = typeof actorOrId === "string" ? actorOrId : actorOrId?.clientKey; @@ -786,13 +808,26 @@ export class ExpertInsurerService { private async getClientClaimFiles( clientObjectId: Types.ObjectId, ): Promise { - const all = (await this.claimCaseDbService.find( - {}, - { lean: true }, - )) as any[]; + const [all, rosterExpertIds] = await Promise.all([ + this.claimCaseDbService.find({}, { lean: true }) as Promise, + this.getAllRosterExpertIds(clientObjectId), + ]); const idStr = String(clientObjectId); + 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)); } @@ -803,14 +838,16 @@ export class ExpertInsurerService { ) { const clientObjectId = this.getClientId(actor); const ckFilter = this.clientKeyScopeFilter(clientObjectId); - const [experts, damageExperts, blameFiles, claimFiles] = await Promise.all([ - this.expertDbService.findAll(ckFilter as never), - this.damageExpertDbService.findAll(ckFilter as never), - this.getClientBlameFiles(clientObjectId), - this.getClientClaimFiles(clientObjectId), - ]); + const [experts, damageExperts, fieldExperts, blameFiles, claimFiles] = + await Promise.all([ + this.expertDbService.findAll(ckFilter as never), + this.damageExpertDbService.findAll(ckFilter as never), + this.fieldExpertDbService.findAll(ckFilter as never), + this.getClientBlameFiles(clientObjectId), + this.getClientClaimFiles(clientObjectId), + ]); - const allExpertsRaw = [...experts, ...damageExperts]; + const allExpertsRaw = [...experts, ...damageExperts, ...fieldExperts]; const expertIds = allExpertsRaw.map((e) => String(e._id)); const expertActivityStatsMap = await this.buildExpertActivityStatsMap( clientObjectId, @@ -890,6 +927,7 @@ export class ExpertInsurerService { const allExperts = [ ...experts.map((e) => mapExpertRow(e, "blame")), ...damageExperts.map((e) => mapExpertRow(e, "claim")), + ...fieldExperts.map((e) => mapExpertRow(e, "blame")), ]; const page = Number(currentPage) > 0 ? Number(currentPage) : 1; @@ -960,12 +998,15 @@ export class ExpertInsurerService { const expertObjectId = this.parseObjectId(expertId, "expert id"); const clientOid = this.getClientId(insurerClientKey); 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.damageExpertDbService.findAll(ckFilter as never), + this.fieldExpertDbService.findAll(ckFilter as never), ]); 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); if (!onBlameRoster && !onClaimRoster) { 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) + : Promise.resolve([] as any[]), + (this.claimCaseDbService.find({}, { lean: true }) as Promise), + ]); + + const result: any[] = []; + + // Blame files handled by a field/blame expert if (onBlameRoster) { - const blames = (await this.blameRequestDbService.find( - {}, - { lean: true }, - )) as any[]; - return blames - .filter( - (b) => - blameCaseTouchesClient(b, clientKeyStr) && - this.blameFieldExpertIdCandidates(b).includes(id), - ) - .map((b) => this.mapBlameFileSummaryForInsurerExpert(b)); + for (const b of allBlames) { + if (this.blameFieldExpertIdCandidates(b).includes(id)) { + result.push(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)); + + // 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 []; + + return result; } private validateInsurerFileRating(rating: FileRating) { @@ -1697,17 +1745,20 @@ export class ExpertInsurerService { const branchIdSet = new Set(branches.map((b: any) => String(b._id))); const ckFilter = this.clientKeyScopeFilter(clientObjectId); - const [experts, damageExperts, blameFiles, claimFiles] = await Promise.all([ - this.expertDbService.findAll(ckFilter as never), - this.damageExpertDbService.findAll(ckFilter as never), - this.getClientBlameFiles(clientObjectId), - this.getClientClaimFiles(clientObjectId), - ]); + const [experts, damageExperts, fieldExperts, blameFiles, claimFiles] = + await Promise.all([ + this.expertDbService.findAll(ckFilter as never), + this.damageExpertDbService.findAll(ckFilter as never), + this.fieldExpertDbService.findAll(ckFilter as never), + this.getClientBlameFiles(clientObjectId), + this.getClientClaimFiles(clientObjectId), + ]); const activeExpertsByBranch = new Map>(); const expertBranchById = new Map(); const claimExpertBranchById = new Map(); - 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) : ""; if (!bid || !branchIdSet.has(bid)) continue; const eid = String(e?._id || ""); @@ -1742,9 +1793,11 @@ export class ExpertInsurerService { }; for (const b of blameFiles as any[]) { - const expertId = String(b?.expert?.assignedExpertId || ""); - if (!expertId) continue; - const bid = expertBranchById.get(expertId); + // Resolve expert via assignment OR field-expert initiation + const candidateIds = this.blameFieldExpertIdCandidates(b); + const bid = candidateIds + .map((eid) => expertBranchById.get(eid)) + .find(Boolean); if (!bid) continue; const fileKey = String(b?.publicId || b?._id || ""); if (!fileKey) continue; @@ -1756,9 +1809,17 @@ export class ExpertInsurerService { } 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; - const bid = claimExpertBranchById.get(expertId); + const bid = + expertBranchById.get(expertId) ?? + claimExpertBranchById.get(expertId) ?? + undefined; if (!bid) continue; const fileKey = String(c?.publicId || c?._id || ""); if (!fileKey) continue;