forked from Yara724/api
Merge pull request 'Added field expert support for insurer' (#133) from s.yahyaee/yara724-api:main into main
Reviewed-on: Yara724/api#133
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user