forked from Yara724/api
YARA-977
This commit is contained in:
@@ -51,6 +51,10 @@ import {
|
||||
} from "src/helpers/outer-damage-parts";
|
||||
import { ClaimVehicleTypeV2 } from "src/static/outer-car-parts-catalog";
|
||||
import { ClaimSignDbService } from "src/claim-request-management/entites/db-service/claim-sign.db.service";
|
||||
import {
|
||||
extractExpertNamesFromBlame,
|
||||
extractExpertNamesFromClaim,
|
||||
} from "./helper/insurer.helper";
|
||||
|
||||
@Injectable()
|
||||
export class ExpertInsurerService {
|
||||
@@ -70,7 +74,8 @@ export class ExpertInsurerService {
|
||||
) {}
|
||||
|
||||
private getClientId(actorOrId: any): Types.ObjectId {
|
||||
const raw = typeof actorOrId === "string" ? actorOrId : actorOrId?.clientKey;
|
||||
const raw =
|
||||
typeof actorOrId === "string" ? actorOrId : actorOrId?.clientKey;
|
||||
if (!raw || !Types.ObjectId.isValid(raw)) {
|
||||
throw new BadRequestException("Client key is required");
|
||||
}
|
||||
@@ -91,15 +96,22 @@ export class ExpertInsurerService {
|
||||
tenantId: Types.ObjectId,
|
||||
expertIds: string[],
|
||||
): Promise<Record<string, { totalHandled: number; totalChecked: number }>> {
|
||||
const events = await this.expertFileActivityDbService.findByTenantAndExperts(
|
||||
const events =
|
||||
await this.expertFileActivityDbService.findByTenantAndExperts(
|
||||
tenantId,
|
||||
expertIds,
|
||||
);
|
||||
const stateByExpertFile = new Map<string, { checked: boolean; handled: boolean }>();
|
||||
const stateByExpertFile = new Map<
|
||||
string,
|
||||
{ checked: boolean; handled: boolean }
|
||||
>();
|
||||
|
||||
for (const e of events) {
|
||||
const key = `${String(e.expertId)}:${String(e.fileId)}`;
|
||||
const prev = stateByExpertFile.get(key) ?? { checked: false, handled: false };
|
||||
const prev = stateByExpertFile.get(key) ?? {
|
||||
checked: false,
|
||||
handled: false,
|
||||
};
|
||||
if (e.eventType === ExpertFileActivityType.CHECKED) {
|
||||
if (!prev.handled) prev.checked = true;
|
||||
} else if (e.eventType === ExpertFileActivityType.UNCHECKED) {
|
||||
@@ -111,13 +123,17 @@ export class ExpertInsurerService {
|
||||
stateByExpertFile.set(key, prev);
|
||||
}
|
||||
|
||||
const result: Record<string, { totalHandled: number; totalChecked: number }> = {};
|
||||
const result: Record<
|
||||
string,
|
||||
{ totalHandled: number; totalChecked: number }
|
||||
> = {};
|
||||
for (const expertId of expertIds) {
|
||||
result[expertId] = { totalHandled: 0, totalChecked: 0 };
|
||||
}
|
||||
for (const [key, st] of stateByExpertFile.entries()) {
|
||||
const [expertId] = key.split(":");
|
||||
if (!result[expertId]) result[expertId] = { totalHandled: 0, totalChecked: 0 };
|
||||
if (!result[expertId])
|
||||
result[expertId] = { totalHandled: 0, totalChecked: 0 };
|
||||
if (st.handled) result[expertId].totalHandled += 1;
|
||||
else if (st.checked) result[expertId].totalChecked += 1;
|
||||
}
|
||||
@@ -265,18 +281,18 @@ export class ExpertInsurerService {
|
||||
return {
|
||||
...d,
|
||||
guiltyPartyId:
|
||||
guilty != null && typeof (guilty as { toString?: () => string }).toString === "function"
|
||||
guilty != null &&
|
||||
typeof (guilty as { toString?: () => string }).toString === "function"
|
||||
? String(guilty)
|
||||
: guilty,
|
||||
decidedByExpertId:
|
||||
decidedBy != null &&
|
||||
typeof (decidedBy as { toString?: () => string }).toString === "function"
|
||||
typeof (decidedBy as { toString?: () => string }).toString ===
|
||||
"function"
|
||||
? String(decidedBy)
|
||||
: decidedBy,
|
||||
decidedAt:
|
||||
decidedAt instanceof Date
|
||||
? decidedAt.toISOString()
|
||||
: decidedAt,
|
||||
decidedAt instanceof Date ? decidedAt.toISOString() : decidedAt,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -300,7 +316,8 @@ export class ExpertInsurerService {
|
||||
if (!Array.isArray(parties) || parties.length === 0) return out;
|
||||
|
||||
const first =
|
||||
(parties as any[]).find((p: any) => p?.role === PartyRole.FIRST) ?? (parties as any[])[0];
|
||||
(parties as any[]).find((p: any) => p?.role === PartyRole.FIRST) ??
|
||||
(parties as any[])[0];
|
||||
const cbf = first?.carBodyFirstForm;
|
||||
delete out.blameStatus;
|
||||
if (cbf && typeof cbf === "object") {
|
||||
@@ -340,7 +357,8 @@ export class ExpertInsurerService {
|
||||
) {
|
||||
if (!person || typeof person !== "object") return person;
|
||||
const cid =
|
||||
person.clientId?.toString?.() ?? (person.clientId != null ? String(person.clientId) : undefined);
|
||||
person.clientId?.toString?.() ??
|
||||
(person.clientId != null ? String(person.clientId) : undefined);
|
||||
return {
|
||||
...person,
|
||||
userId: person.userId?.toString?.() ?? undefined,
|
||||
@@ -412,7 +430,11 @@ export class ExpertInsurerService {
|
||||
side: sp.side,
|
||||
label_fa: sp.label_fa,
|
||||
catalogKey: sp.catalogKey,
|
||||
captured: hasDamagedPartCapture(damagedPartsDataExpert, ck, selectedNormExpert),
|
||||
captured: hasDamagedPartCapture(
|
||||
damagedPartsDataExpert,
|
||||
ck,
|
||||
selectedNormExpert,
|
||||
),
|
||||
path: cap?.path,
|
||||
fileName: cap?.fileName,
|
||||
url,
|
||||
@@ -439,7 +461,9 @@ export class ExpertInsurerService {
|
||||
});
|
||||
}
|
||||
|
||||
private async claimSignLinkFromId(signDetailId: unknown): Promise<string | undefined> {
|
||||
private async claimSignLinkFromId(
|
||||
signDetailId: unknown,
|
||||
): Promise<string | undefined> {
|
||||
if (signDetailId == null || signDetailId === "") return undefined;
|
||||
const id = String(signDetailId);
|
||||
if (!Types.ObjectId.isValid(id)) return undefined;
|
||||
@@ -454,7 +478,10 @@ export class ExpertInsurerService {
|
||||
evaluation: Record<string, unknown> | undefined,
|
||||
): Promise<Record<string, unknown> | undefined> {
|
||||
if (!evaluation || typeof evaluation !== "object") return evaluation;
|
||||
const ev = JSON.parse(JSON.stringify(evaluation)) as Record<string, unknown>;
|
||||
const ev = JSON.parse(JSON.stringify(evaluation)) as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
|
||||
const enrichReply = async (reply: any) => {
|
||||
if (!reply || typeof reply !== "object") return;
|
||||
@@ -487,14 +514,18 @@ export class ExpertInsurerService {
|
||||
const evidence = party.evidence as Record<string, unknown>;
|
||||
|
||||
if (evidence.videoId) {
|
||||
const videoDoc = await this.blameVideoDbService.findById(String(evidence.videoId));
|
||||
const videoDoc = await this.blameVideoDbService.findById(
|
||||
String(evidence.videoId),
|
||||
);
|
||||
if (videoDoc?.path) evidence.videoUrl = buildFileLink(videoDoc.path);
|
||||
}
|
||||
|
||||
if (evidence.voices && Array.isArray(evidence.voices)) {
|
||||
const voiceUrls: string[] = [];
|
||||
for (const voiceId of evidence.voices) {
|
||||
const voiceDoc = await this.blameVoiceDbService.findById(String(voiceId));
|
||||
const voiceDoc = await this.blameVoiceDbService.findById(
|
||||
String(voiceId),
|
||||
);
|
||||
if (voiceDoc?.path) voiceUrls.push(buildFileLink(voiceDoc.path));
|
||||
}
|
||||
evidence.voiceUrls = voiceUrls;
|
||||
@@ -511,14 +542,20 @@ export class ExpertInsurerService {
|
||||
doc.parties = enrichBlamePartiesForAgreementView(doc);
|
||||
|
||||
const mergedParties = doc.parties as any[];
|
||||
const clientIds = mergedParties.map((p) => p?.person?.clientId?.toString?.()).filter(Boolean);
|
||||
const clientIds = mergedParties
|
||||
.map((p) => p?.person?.clientId?.toString?.())
|
||||
.filter(Boolean);
|
||||
const clientNames = await this.clientNamesByClientIds(clientIds);
|
||||
doc.parties = mergedParties.map((p) => this.mapPartyForInsurerFull(p, clientNames));
|
||||
doc.parties = mergedParties.map((p) =>
|
||||
this.mapPartyForInsurerFull(p, clientNames),
|
||||
);
|
||||
this.enrichPartiesConfirmationSignatures(doc.parties as any[]);
|
||||
|
||||
if (doc.expert && typeof doc.expert === "object") {
|
||||
const ex = { ...(doc.expert as Record<string, unknown>) };
|
||||
const serialized = this.serializeBlameExpertDecisionForInsurer(ex.decision);
|
||||
const serialized = this.serializeBlameExpertDecisionForInsurer(
|
||||
ex.decision,
|
||||
);
|
||||
if (serialized !== undefined) ex.decision = serialized;
|
||||
doc.expert = ex;
|
||||
}
|
||||
@@ -551,7 +588,8 @@ export class ExpertInsurerService {
|
||||
? Array.from(requiredDocs.keys())
|
||||
: Object.keys(requiredDocs);
|
||||
for (const k of keys as string[]) {
|
||||
const row = requiredDocs instanceof Map ? requiredDocs.get(k) : requiredDocs[k];
|
||||
const row =
|
||||
requiredDocs instanceof Map ? requiredDocs.get(k) : requiredDocs[k];
|
||||
requiredDocumentsStatus[k] = {
|
||||
uploaded: !!row?.uploaded,
|
||||
fileId: row?.fileId?.toString?.(),
|
||||
@@ -576,7 +614,9 @@ export class ExpertInsurerService {
|
||||
};
|
||||
}
|
||||
|
||||
const carTypeExpert = (claim.vehicle as any)?.carType as ClaimVehicleTypeV2 | undefined;
|
||||
const carTypeExpert = (claim.vehicle as any)?.carType as
|
||||
| ClaimVehicleTypeV2
|
||||
| undefined;
|
||||
const selectedNormExpert = normalizeDamageSelectedParts(
|
||||
(claim.damage as any)?.selectedParts,
|
||||
carTypeExpert,
|
||||
@@ -646,13 +686,17 @@ export class ExpertInsurerService {
|
||||
};
|
||||
}
|
||||
|
||||
const evaluationEnriched = await this.enrichClaimEvaluationForInsurer(evaluation);
|
||||
const evaluationEnriched =
|
||||
await this.enrichClaimEvaluationForInsurer(evaluation);
|
||||
const vehicleSanitized = claim.vehicle
|
||||
? this.sanitizeVehicleInquiry(claim.vehicle as any)
|
||||
: undefined;
|
||||
const vehicleOut = vehicleSanitized
|
||||
? (() => {
|
||||
const { carType: _omit, ...rest } = vehicleSanitized as Record<string, unknown>;
|
||||
const { carType: _omit, ...rest } = vehicleSanitized as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
return rest;
|
||||
})()
|
||||
: undefined;
|
||||
@@ -705,9 +749,11 @@ export class ExpertInsurerService {
|
||||
).filter((v): v is number => typeof v === "number" && !isNaN(v))
|
||||
: [];
|
||||
const userValues = userRating
|
||||
? [userRating.progressSpeed, userRating.registrationEase, userRating.overallEvaluation].filter(
|
||||
(v) => typeof v === "number" && !isNaN(v),
|
||||
)
|
||||
? [
|
||||
userRating.progressSpeed,
|
||||
userRating.registrationEase,
|
||||
userRating.overallEvaluation,
|
||||
].filter((v) => typeof v === "number" && !isNaN(v))
|
||||
: [];
|
||||
const insurerAvg = insurerValues.length
|
||||
? insurerValues.reduce((a, b) => a + b, 0) / insurerValues.length
|
||||
@@ -715,30 +761,50 @@ export class ExpertInsurerService {
|
||||
const userAvg = userValues.length
|
||||
? userValues.reduce((a, b) => a + b, 0) / userValues.length
|
||||
: null;
|
||||
const scores = [insurerAvg, userAvg].filter((v): v is number => typeof v === "number");
|
||||
const scores = [insurerAvg, userAvg].filter(
|
||||
(v): v is number => typeof v === "number",
|
||||
);
|
||||
if (!scores.length) return null;
|
||||
return parseFloat((scores.reduce((a, b) => a + b, 0) / scores.length).toFixed(2));
|
||||
return parseFloat(
|
||||
(scores.reduce((a, b) => a + b, 0) / scores.length).toFixed(2),
|
||||
);
|
||||
}
|
||||
|
||||
private async getClientBlameFiles(clientObjectId: Types.ObjectId): Promise<any[]> {
|
||||
const all = (await this.blameRequestDbService.find({}, { lean: true })) as any[];
|
||||
private async getClientBlameFiles(
|
||||
clientObjectId: Types.ObjectId,
|
||||
): Promise<any[]> {
|
||||
const all = (await this.blameRequestDbService.find(
|
||||
{},
|
||||
{ lean: true },
|
||||
)) as any[];
|
||||
const idStr = String(clientObjectId);
|
||||
return all
|
||||
.filter((f) =>
|
||||
(f?.parties || []).some((p) => String(p?.person?.clientId || "") === idStr),
|
||||
(f?.parties || []).some(
|
||||
(p) => String(p?.person?.clientId || "") === idStr,
|
||||
),
|
||||
)
|
||||
.map((f) => this.normalizeBlameCase(f));
|
||||
}
|
||||
|
||||
private async getClientClaimFiles(clientObjectId: Types.ObjectId): Promise<any[]> {
|
||||
const all = (await this.claimCaseDbService.find({}, { lean: true })) as any[];
|
||||
private async getClientClaimFiles(
|
||||
clientObjectId: Types.ObjectId,
|
||||
): Promise<any[]> {
|
||||
const all = (await this.claimCaseDbService.find(
|
||||
{},
|
||||
{ lean: true },
|
||||
)) as any[];
|
||||
const idStr = String(clientObjectId);
|
||||
return all
|
||||
.filter((f) => claimCaseTouchesClient(f, idStr))
|
||||
.map((f) => this.normalizeClaimCase(f));
|
||||
}
|
||||
|
||||
async retrieveAllExpertsOfClient(actor, currentPage: number, countPerPage: number) {
|
||||
async retrieveAllExpertsOfClient(
|
||||
actor,
|
||||
currentPage: number,
|
||||
countPerPage: number,
|
||||
) {
|
||||
const clientObjectId = this.getClientId(actor);
|
||||
const ckFilter = this.clientKeyScopeFilter(clientObjectId);
|
||||
const [experts, damageExperts, blameFiles, claimFiles] = await Promise.all([
|
||||
@@ -755,18 +821,23 @@ export class ExpertInsurerService {
|
||||
expertIds,
|
||||
);
|
||||
const expertTotalRatingsMap: Record<string, number[]> = {};
|
||||
const expertRatingsByCategoryMap: Record<string, Record<string, number[]>> = {};
|
||||
const expertRatingsByCategoryMap: Record<
|
||||
string,
|
||||
Record<string, number[]>
|
||||
> = {};
|
||||
|
||||
const processRatings = (expertId: string | undefined, file: any) => {
|
||||
if (!expertId) return;
|
||||
const rating = file?.rating;
|
||||
const combinedScore = this.getCombinedFileScore(file);
|
||||
if (combinedScore !== null) {
|
||||
if (!expertTotalRatingsMap[expertId]) expertTotalRatingsMap[expertId] = [];
|
||||
if (!expertTotalRatingsMap[expertId])
|
||||
expertTotalRatingsMap[expertId] = [];
|
||||
expertTotalRatingsMap[expertId].push(combinedScore);
|
||||
}
|
||||
if (!rating || typeof rating !== "object") return;
|
||||
if (!expertRatingsByCategoryMap[expertId]) expertRatingsByCategoryMap[expertId] = {};
|
||||
if (!expertRatingsByCategoryMap[expertId])
|
||||
expertRatingsByCategoryMap[expertId] = {};
|
||||
for (const [category, value] of Object.entries(rating)) {
|
||||
if (category === "botRating") continue;
|
||||
if (typeof value === "number" && !isNaN(value)) {
|
||||
@@ -790,7 +861,9 @@ export class ExpertInsurerService {
|
||||
const totalRatings = expertTotalRatingsMap[expertIdStr] || [];
|
||||
const overallAverageRating = totalRatings.length
|
||||
? parseFloat(
|
||||
(totalRatings.reduce((a, b) => a + b, 0) / totalRatings.length).toFixed(2),
|
||||
(
|
||||
totalRatings.reduce((a, b) => a + b, 0) / totalRatings.length
|
||||
).toFixed(2),
|
||||
)
|
||||
: null;
|
||||
const averageRatingsByCategory: Record<string, number> = {};
|
||||
@@ -869,7 +942,9 @@ export class ExpertInsurerService {
|
||||
* combined insurer + user ratings.
|
||||
*/
|
||||
async getTopFilesForClient(insurerId: string): Promise<any[]> {
|
||||
const claimFiles = await this.getClientClaimFiles(this.getClientId(insurerId));
|
||||
const claimFiles = await this.getClientClaimFiles(
|
||||
this.getClientId(insurerId),
|
||||
);
|
||||
const scored = claimFiles
|
||||
.map((file) => {
|
||||
const combinedScore = this.getCombinedFileScore(file);
|
||||
@@ -877,10 +952,15 @@ export class ExpertInsurerService {
|
||||
return { ...file, combinedScore };
|
||||
})
|
||||
.filter((f) => f !== null);
|
||||
return scored.sort((a, b) => b.combinedScore - a.combinedScore).slice(0, 10);
|
||||
return scored
|
||||
.sort((a, b) => b.combinedScore - a.combinedScore)
|
||||
.slice(0, 10);
|
||||
}
|
||||
|
||||
async getAllFilesForInsurerExpert(expertId: string, insurerClientKey: string) {
|
||||
async getAllFilesForInsurerExpert(
|
||||
expertId: string,
|
||||
insurerClientKey: string,
|
||||
) {
|
||||
const expertObjectId = this.parseObjectId(expertId, "expert id");
|
||||
const clientOid = this.getClientId(insurerClientKey);
|
||||
const ckFilter = this.clientKeyScopeFilter(clientOid);
|
||||
@@ -899,7 +979,10 @@ export class ExpertInsurerService {
|
||||
|
||||
const clientKeyStr = String(clientOid);
|
||||
if (onBlameRoster) {
|
||||
const blames = (await this.blameRequestDbService.find({}, { lean: true })) as any[];
|
||||
const blames = (await this.blameRequestDbService.find(
|
||||
{},
|
||||
{ lean: true },
|
||||
)) as any[];
|
||||
return blames
|
||||
.filter(
|
||||
(b) =>
|
||||
@@ -909,7 +992,10 @@ export class ExpertInsurerService {
|
||||
.map((b) => this.mapBlameFileSummaryForInsurerExpert(b));
|
||||
}
|
||||
if (onClaimRoster) {
|
||||
const claims = (await this.claimCaseDbService.find({}, { lean: true })) as any[];
|
||||
const claims = (await this.claimCaseDbService.find(
|
||||
{},
|
||||
{ lean: true },
|
||||
)) as any[];
|
||||
return claims
|
||||
.filter(
|
||||
(c) =>
|
||||
@@ -932,7 +1018,9 @@ export class ExpertInsurerService {
|
||||
for (const key of required) {
|
||||
const value = rating?.[key];
|
||||
if (typeof value !== "number" || value < 0 || value > 5) {
|
||||
throw new BadRequestException(`${key} must be a number between 0 and 5`);
|
||||
throw new BadRequestException(
|
||||
`${key} must be a number between 0 and 5`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -957,8 +1045,12 @@ export class ExpertInsurerService {
|
||||
this.getClientClaimFiles(id),
|
||||
]);
|
||||
|
||||
const blame = blameFiles.find((b) => String((b as any).publicId) === publicId);
|
||||
const claim = claimFiles.find((c) => String((c as any).publicId) === publicId);
|
||||
const blame = blameFiles.find(
|
||||
(b) => String((b as any).publicId) === publicId,
|
||||
);
|
||||
const claim = claimFiles.find(
|
||||
(c) => String((c as any).publicId) === publicId,
|
||||
);
|
||||
|
||||
if (!blame && !claim) {
|
||||
throw new NotFoundException("File not found for this publicId");
|
||||
@@ -971,7 +1063,10 @@ export class ExpertInsurerService {
|
||||
} = { publicId };
|
||||
|
||||
if (claim) {
|
||||
const claimId = this.parseObjectId(String((claim as any)._id), "claim id");
|
||||
const claimId = this.parseObjectId(
|
||||
String((claim as any)._id),
|
||||
"claim id",
|
||||
);
|
||||
const updated = await this.claimCaseDbService.findByIdAndUpdate(claimId, {
|
||||
$set: { "evaluation.rating": rating },
|
||||
});
|
||||
@@ -983,10 +1078,16 @@ export class ExpertInsurerService {
|
||||
}
|
||||
|
||||
if (blame) {
|
||||
const blameId = this.parseObjectId(String((blame as any)._id), "blame id");
|
||||
const updated = await this.blameRequestDbService.findByIdAndUpdate(blameId, {
|
||||
const blameId = this.parseObjectId(
|
||||
String((blame as any)._id),
|
||||
"blame id",
|
||||
);
|
||||
const updated = await this.blameRequestDbService.findByIdAndUpdate(
|
||||
blameId,
|
||||
{
|
||||
$set: { "expert.rating": rating },
|
||||
});
|
||||
},
|
||||
);
|
||||
if (!updated) throw new NotFoundException("Blame file not found");
|
||||
out.blame = {
|
||||
requestId: String((updated as any)._id),
|
||||
@@ -1034,6 +1135,7 @@ export class ExpertInsurerService {
|
||||
plate?: string;
|
||||
};
|
||||
}>;
|
||||
expertNames?: string[];
|
||||
createdAt?: Date | string;
|
||||
updatedAt?: Date | string;
|
||||
};
|
||||
@@ -1043,6 +1145,7 @@ export class ExpertInsurerService {
|
||||
status?: string;
|
||||
claimStatus?: string;
|
||||
currentStep?: string;
|
||||
expertNames?: string[];
|
||||
createdAt?: Date | string;
|
||||
updatedAt?: Date | string;
|
||||
};
|
||||
@@ -1058,10 +1161,13 @@ export class ExpertInsurerService {
|
||||
const partiesPreview = this.buildPartiesPreview((b as any).parties);
|
||||
prev.fileType = prev.fileType ?? (b as any).type;
|
||||
prev.blame = {
|
||||
requestId: (b as any)?._id?.toString?.() || String((b as any)?._id || ""),
|
||||
requestNo: (b as any).requestNo || String((b as any).requestNumber || ""),
|
||||
requestId:
|
||||
(b as any)?._id?.toString?.() || String((b as any)?._id || ""),
|
||||
requestNo:
|
||||
(b as any).requestNo || String((b as any).requestNumber || ""),
|
||||
status: (b as any).status,
|
||||
parties: partiesPreview,
|
||||
expertNames: extractExpertNamesFromBlame(b), // ← add
|
||||
createdAt: (b as any).createdAt,
|
||||
updatedAt: (b as any).updatedAt,
|
||||
};
|
||||
@@ -1075,14 +1181,19 @@ export class ExpertInsurerService {
|
||||
const key = String((c as any).publicId || (c as any)._id || "");
|
||||
if (!key) continue;
|
||||
const prev = byPublicId.get(key) ?? { publicId: key };
|
||||
const claimPartiesPreview = this.buildPartiesPreview((c as any)?.snapshot?.parties);
|
||||
const claimPartiesPreview = this.buildPartiesPreview(
|
||||
(c as any)?.snapshot?.parties,
|
||||
);
|
||||
prev.fileType = prev.fileType ?? (c as any)?.snapshot?.accident?.type;
|
||||
prev.claim = {
|
||||
requestId: (c as any)?._id?.toString?.() || String((c as any)?._id || ""),
|
||||
requestNo: (c as any).requestNo || String((c as any).requestNumber || ""),
|
||||
requestId:
|
||||
(c as any)?._id?.toString?.() || String((c as any)?._id || ""),
|
||||
requestNo:
|
||||
(c as any).requestNo || String((c as any).requestNumber || ""),
|
||||
status: (c as any).status,
|
||||
claimStatus: (c as any).claimStatus,
|
||||
currentStep: (c as any).currentStep,
|
||||
expertNames: extractExpertNamesFromClaim(c), // ← add
|
||||
createdAt: (c as any).createdAt,
|
||||
updatedAt: (c as any).updatedAt,
|
||||
};
|
||||
@@ -1127,8 +1238,12 @@ export class ExpertInsurerService {
|
||||
this.getClientClaimFiles(id),
|
||||
]);
|
||||
|
||||
const blame = blameFiles.find((b) => String((b as any).publicId) === publicId);
|
||||
const claim = claimFiles.find((c) => String((c as any).publicId) === publicId);
|
||||
const blame = blameFiles.find(
|
||||
(b) => String((b as any).publicId) === publicId,
|
||||
);
|
||||
const claim = claimFiles.find(
|
||||
(c) => String((c as any).publicId) === publicId,
|
||||
);
|
||||
|
||||
if (!blame && !claim) {
|
||||
throw new NotFoundException("File not found for this publicId");
|
||||
@@ -1303,13 +1418,17 @@ export class ExpertInsurerService {
|
||||
|
||||
const email = payload.email.trim().toLowerCase();
|
||||
const existingByEmail = await this.expertDbService.findOne({ email });
|
||||
const existingDamageByEmail = await this.damageExpertDbService.findOne({ email });
|
||||
const existingDamageByEmail = await this.damageExpertDbService.findOne({
|
||||
email,
|
||||
});
|
||||
if (existingByEmail || existingDamageByEmail) {
|
||||
throw new ConflictException("An expert with this email already exists.");
|
||||
}
|
||||
|
||||
const nationalCode = payload.nationalCode.trim();
|
||||
const existingByNational = await this.expertDbService.findOne({ nationalCode });
|
||||
const existingByNational = await this.expertDbService.findOne({
|
||||
nationalCode,
|
||||
});
|
||||
const existingDamageByNational = await this.damageExpertDbService.findOne({
|
||||
nationalCode,
|
||||
});
|
||||
@@ -1388,7 +1507,14 @@ export class ExpertInsurerService {
|
||||
// Calculate current month date range
|
||||
const now = new Date();
|
||||
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
const monthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59);
|
||||
const monthEnd = new Date(
|
||||
now.getFullYear(),
|
||||
now.getMonth() + 1,
|
||||
0,
|
||||
23,
|
||||
59,
|
||||
59,
|
||||
);
|
||||
|
||||
// Filter files created this month
|
||||
const filesThisMonth = claimFiles.filter((file) => {
|
||||
@@ -1416,7 +1542,9 @@ export class ExpertInsurerService {
|
||||
insurerRating.evaluationTimeliness,
|
||||
insurerRating.accidentCauseAccuracy,
|
||||
insurerRating.guiltyVehicleIdentification,
|
||||
].filter((val): val is number => typeof val === "number" && !isNaN(val));
|
||||
].filter(
|
||||
(val): val is number => typeof val === "number" && !isNaN(val),
|
||||
);
|
||||
|
||||
if (insurerValues.length > 0) {
|
||||
filesWithInsurerRating++;
|
||||
@@ -1428,7 +1556,11 @@ export class ExpertInsurerService {
|
||||
|
||||
// Check for bot rating (if botRating field exists in rating object)
|
||||
const botRating = (insurerRating as any)?.botRating;
|
||||
if (botRating !== undefined && !isNaN(botRating) && typeof botRating === "number") {
|
||||
if (
|
||||
botRating !== undefined &&
|
||||
!isNaN(botRating) &&
|
||||
typeof botRating === "number"
|
||||
) {
|
||||
filesWithBotRating++;
|
||||
totalBotRatings += botRating;
|
||||
}
|
||||
@@ -1511,12 +1643,15 @@ export class ExpertInsurerService {
|
||||
const clientObjectId = this.getClientId(insuranceId);
|
||||
const { fromDate, toDate } = this.parseDateRange(opts?.from, opts?.to);
|
||||
const isActiveFilter = this.parseOptionalBoolean(opts?.isActive);
|
||||
const branches = await this.branchDbService.findAllWithFilters(String(clientObjectId), {
|
||||
const branches = await this.branchDbService.findAllWithFilters(
|
||||
String(clientObjectId),
|
||||
{
|
||||
search: opts?.search,
|
||||
from: fromDate,
|
||||
to: toDate,
|
||||
isActive: isActiveFilter,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const branchIdSet = new Set(branches.map((b: any) => String(b._id)));
|
||||
const ckFilter = this.clientKeyScopeFilter(clientObjectId);
|
||||
@@ -1536,7 +1671,8 @@ export class ExpertInsurerService {
|
||||
const eid = String(e?._id || "");
|
||||
if (!eid) continue;
|
||||
expertBranchById.set(eid, bid);
|
||||
if (!activeExpertsByBranch.has(bid)) activeExpertsByBranch.set(bid, new Set());
|
||||
if (!activeExpertsByBranch.has(bid))
|
||||
activeExpertsByBranch.set(bid, new Set());
|
||||
activeExpertsByBranch.get(bid)!.add(eid);
|
||||
}
|
||||
for (const e of damageExperts as any[]) {
|
||||
@@ -1545,14 +1681,20 @@ export class ExpertInsurerService {
|
||||
const eid = String(e?._id || "");
|
||||
if (!eid) continue;
|
||||
claimExpertBranchById.set(eid, bid);
|
||||
if (!activeExpertsByBranch.has(bid)) activeExpertsByBranch.set(bid, new Set());
|
||||
if (!activeExpertsByBranch.has(bid))
|
||||
activeExpertsByBranch.set(bid, new Set());
|
||||
activeExpertsByBranch.get(bid)!.add(eid);
|
||||
}
|
||||
|
||||
const completedByBranch = new Map<string, Set<string>>();
|
||||
const handlingByBranch = new Map<string, Set<string>>();
|
||||
const { blameInHandling, claimInHandling } = this.buildHandlingBranchStatusSets();
|
||||
const addPublicId = (map: Map<string, Set<string>>, branchId: string, fileKey: string) => {
|
||||
const { blameInHandling, claimInHandling } =
|
||||
this.buildHandlingBranchStatusSets();
|
||||
const addPublicId = (
|
||||
map: Map<string, Set<string>>,
|
||||
branchId: string,
|
||||
fileKey: string,
|
||||
) => {
|
||||
if (!map.has(branchId)) map.set(branchId, new Set());
|
||||
map.get(branchId)!.add(fileKey);
|
||||
};
|
||||
@@ -1565,8 +1707,10 @@ export class ExpertInsurerService {
|
||||
const fileKey = String(b?.publicId || b?._id || "");
|
||||
if (!fileKey) continue;
|
||||
const status = String(b?.status || "");
|
||||
if (status === CaseStatus.COMPLETED) addPublicId(completedByBranch, bid, fileKey);
|
||||
if (blameInHandling.has(status)) addPublicId(handlingByBranch, bid, fileKey);
|
||||
if (status === CaseStatus.COMPLETED)
|
||||
addPublicId(completedByBranch, bid, fileKey);
|
||||
if (blameInHandling.has(status))
|
||||
addPublicId(handlingByBranch, bid, fileKey);
|
||||
}
|
||||
|
||||
for (const c of claimFiles as any[]) {
|
||||
@@ -1577,8 +1721,10 @@ export class ExpertInsurerService {
|
||||
const fileKey = String(c?.publicId || c?._id || "");
|
||||
if (!fileKey) continue;
|
||||
const status = String(c?.status || "");
|
||||
if (status === ClaimCaseStatus.COMPLETED) addPublicId(completedByBranch, bid, fileKey);
|
||||
if (claimInHandling.has(status)) addPublicId(handlingByBranch, bid, fileKey);
|
||||
if (status === ClaimCaseStatus.COMPLETED)
|
||||
addPublicId(completedByBranch, bid, fileKey);
|
||||
if (claimInHandling.has(status))
|
||||
addPublicId(handlingByBranch, bid, fileKey);
|
||||
}
|
||||
|
||||
const list = branches.map((branch: any) => {
|
||||
@@ -1654,10 +1800,7 @@ export class ExpertInsurerService {
|
||||
this.isInDateRange((f as any)?.createdAt, fromDate, toDate),
|
||||
);
|
||||
|
||||
const fileMap = new Map<
|
||||
string,
|
||||
{ blame?: any; claim?: any }
|
||||
>();
|
||||
const fileMap = new Map<string, { blame?: any; claim?: any }>();
|
||||
|
||||
for (const b of blameFiles) {
|
||||
const key = String((b as any).publicId || (b as any)._id || "");
|
||||
@@ -1686,8 +1829,7 @@ export class ExpertInsurerService {
|
||||
completed += 1;
|
||||
}
|
||||
|
||||
const blameUnderReview =
|
||||
blameStatus === CaseStatus.WAITING_FOR_EXPERT;
|
||||
const blameUnderReview = blameStatus === CaseStatus.WAITING_FOR_EXPERT;
|
||||
const claimUnderReview =
|
||||
claimStatus === ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT ||
|
||||
claimStatus === ClaimCaseStatus.EXPERT_REVIEWING;
|
||||
|
||||
42
src/expert-insurer/helper/insurer.helper.ts
Normal file
42
src/expert-insurer/helper/insurer.helper.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
export function extractExpertNamesFromBlame(b: any): string[] {
|
||||
const names = new Set<string>();
|
||||
|
||||
const add = (v: unknown) => {
|
||||
if (typeof v === "string" && v.trim()) names.add(v.trim());
|
||||
};
|
||||
|
||||
add(b?.workflow?.assignedForReviewBy?.actorName);
|
||||
add(b?.workflow?.lockedBy?.actorName);
|
||||
|
||||
// History entries from expert actions
|
||||
for (const h of b?.history ?? []) {
|
||||
if (
|
||||
h?.actor?.actorType === "field_expert" ||
|
||||
h?.actor?.actorType === "damage_expert"
|
||||
) {
|
||||
add(h?.actor?.actorName);
|
||||
}
|
||||
}
|
||||
|
||||
return [...names];
|
||||
}
|
||||
|
||||
export function extractExpertNamesFromClaim(c: any): string[] {
|
||||
const names = new Set<string>();
|
||||
|
||||
const add = (v: unknown) => {
|
||||
if (typeof v === "string" && v.trim()) names.add(v.trim());
|
||||
};
|
||||
|
||||
add(c?.workflow?.assignedForReviewBy?.actorName);
|
||||
add(c?.workflow?.lockedBy?.actorName);
|
||||
|
||||
// Evaluation snapshots
|
||||
add(c?.evaluation?.damageExpertResend?.expertProfileSnapshot?.fullName);
|
||||
add(c?.evaluation?.damageExpertReply?.expertProfileSnapshot?.fullName);
|
||||
add(c?.evaluation?.damageExpertReplyFinal?.expertProfileSnapshot?.fullName);
|
||||
add(c?.evaluation?.factorValidationExpertProfileSnapshot?.fullName);
|
||||
add(c?.evaluation?.inPersonVisitExpertProfileSnapshot?.fullName);
|
||||
|
||||
return [...names];
|
||||
}
|
||||
Reference in New Issue
Block a user