1
0
forked from Yara724/api

Rework the reports and insurer part

This commit is contained in:
2026-04-11 00:12:10 +03:30
parent 7ef057dc1f
commit 9afb079786
10 changed files with 564 additions and 1052 deletions

View File

@@ -1,413 +1,243 @@
import { Injectable, Logger } from "@nestjs/common";
import { BadRequestException, Injectable } from "@nestjs/common";
import { Types } from "mongoose";
import { ClaimRequestManagementDbService } from "src/claim-request-management/entites/db-service/claim-request-management.db.service";
import { RequestManagementDbService } from "src/request-management/entities/db-service/request-management.db.service";
import { ClientDbService } from "src/client/entities/db-service/client.db.service";
import { ReqBlameStatus } from "src/Types&Enums/blame-request-management/status.enum";
import { ReqClaimStatus } from "src/Types&Enums/claim-request-management/status.enum";
import { UserType } from "src/Types&Enums/userType.enum";
import { ClaimCaseDbService } from "src/claim-request-management/entites/db-service/claim-case.db.service";
import { BlameRequestDbService } from "src/request-management/entities/db-service/blame-request.db.service";
import { CaseStatus } from "src/Types&Enums/blame-request-management/caseStatus.enum";
import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum";
import { requireActorClientKey } from "src/helpers/tenant-scope";
import {
BlameCaseStatusCountReportDtoRs,
ClaimCaseStatusCountReportDtoRs,
CompanyAllRequestsCountReportDtoRs,
DamageExpertAllRequestsCountReportDtoRs,
ExpertAllRequestsCountReportDtoRs,
} from "./dto/reports.dto";
@Injectable()
export class ReportsService {
private readonly logger = new Logger(ReportsService.name);
constructor(
private readonly requestManagementDbService: RequestManagementDbService,
private readonly claimRequestManagementDbService: ClaimRequestManagementDbService,
private readonly clientDbService: ClientDbService,
private readonly blameRequestDbService: BlameRequestDbService,
private readonly claimCaseDbService: ClaimCaseDbService,
) {}
async getAllCheckedRequestsCountFn(role: string, client: string) {
const statuses = ["UnChecked", "CheckedRequest"];
const data: Record<string, number> = { all: 0 };
const clientId = new Types.ObjectId(client);
for (const status of statuses) {
let count = 0;
if (role === "damage_expert") {
count = await this.claimRequestManagementDbService.countByFilter({
claimStatus: status,
userClientKey: clientId,
});
} else {
count = await this.requestManagementDbService.countByFilter({
blameStatus: status,
$or: [
{ "firstPartyDetails.firstPartyClient.clientId": clientId },
{ "secondPartyDetails.secondPartyClient.clientId": clientId },
],
});
}
data[status] = count;
data.all += count;
}
return data;
private clientObjectId(client: string | undefined): Types.ObjectId {
const ck = requireActorClientKey({ clientKey: client });
return new Types.ObjectId(ck);
}
async getDateFilteredRequestByStatus(
role: string,
client: string,
start: Date,
end: Date,
) {
const statuses = ["UnChecked", "CheckedRequest", "CheckAgain"];
const data: Record<string, number> = {};
const clientId = new Types.ObjectId(client);
for (const status of statuses) {
let blameCount = 0;
let claimCount = 0;
if (role === "expert" || role === "company") {
blameCount = await this.requestManagementDbService.countByFilter({
blameStatus: status,
createdAt: { $gte: start, $lte: end },
$or: [
{ "firstPartyDetails.firstPartyClient.clientId": clientId },
{ "secondPartyDetails.secondPartyClient.clientId": clientId },
],
});
}
if (role === "damage_expert" || role === "company") {
claimCount = await this.claimRequestManagementDbService.countByFilter({
claimStatus: status,
userClientKey: clientId,
createdAt: { $gte: start, $lte: end },
});
}
data[status] = blameCount + claimCount;
}
return data;
}
private isVisibleToClientType(client: any, actor: any): boolean {
if (actor.userType === UserType.GENUINE) {
return true;
}
if (
actor.userType === UserType.LEGAL &&
String(client._id) === actor.clientKey
) {
return true;
}
return false;
}
private wasHandledByActor(request: any, actorSub: string): boolean {
type ActorCheckerEntry = { CheckedRequest?: { actorId: string } };
const actorChecker = request.actorsChecker as ActorCheckerEntry[];
if (!Array.isArray(actorChecker)) {
return false;
}
const matchingEntry = actorChecker.find(
(entry) => String(entry?.CheckedRequest?.actorId) === actorSub,
private isBlameForClient(r: any, client: Types.ObjectId): boolean {
const idStr = String(client);
return (r?.parties || []).some(
(p) => String(p?.person?.clientId || "") === idStr,
);
return !!matchingEntry;
}
/**
* Filters claim requests using the same logic as expert-claim service
* to ensure consistency between endpoints
*/
private async filterClaimRequestsForExpert(
requests: any[],
actor: any,
): Promise<any[]> {
const filteredRequests = [];
private isClaimForClient(r: any, client: Types.ObjectId): boolean {
return String(r?.owner?.userClientKey || "") === String(client);
}
for (const r of requests) {
// For expert-initiated blame files, only show to the initiating expert
if (r.blameFile?.expertInitiated && r.blameFile?.initiatedBy) {
if (String(r.blameFile.initiatedBy) !== actor.sub) {
continue; // Skip if not the initiating expert
}
// Expert-initiated claim files are always visible to the initiating expert
filteredRequests.push(r);
continue;
}
const client = await this.clientDbService.findOne({
_id: r.userClientKey,
});
if (!client) {
this.logger.warn(
`Client not found for claim request with ID: ${r._id}. Skipping.`,
);
continue;
}
const specialHandlingStatuses = [
ReqClaimStatus.CheckAgain,
ReqClaimStatus.ReviewRequest,
ReqClaimStatus.PendingFactorValidation,
];
const requiresSpecificActorCheck = specialHandlingStatuses.includes(
r.claimStatus,
);
if (requiresSpecificActorCheck) {
if (this.wasHandledByActor(r, actor.sub)) {
filteredRequests.push(r);
}
} else {
if (this.isVisibleToClientType(client, actor)) {
filteredRequests.push(r);
}
}
private countBlameByCaseStatus(
blames: any[],
clientId: Types.ObjectId,
): Record<string, number> {
const out: Record<string, number> = { all: 0 };
for (const s of Object.values(CaseStatus)) {
out[s] = 0;
}
return filteredRequests;
for (const r of blames) {
if (!this.isBlameForClient(r, clientId)) continue;
const st = r?.status as string;
if (st in out) out[st]++;
else out[st] = (out[st] ?? 0) + 1;
out.all++;
}
return out;
}
async getAllRequestsCountByRole(role: string, client: string, actor?: any) {
private countClaimByCaseStatus(
claims: any[],
clientId: Types.ObjectId,
): Record<string, number> {
const out: Record<string, number> = { all: 0 };
for (const s of Object.values(ClaimCaseStatus)) {
out[s] = 0;
}
for (const r of claims) {
if (!this.isClaimForClient(r, clientId)) continue;
const st = r?.status as string;
if (st in out) out[st]++;
else out[st] = (out[st] ?? 0) + 1;
out.all++;
}
return out;
}
async getAllRequestsCountByRole(role: string, client: string | undefined) {
const clientId = this.clientObjectId(client);
const [blames, claims] = await Promise.all([
this.blameRequestDbService.find({}, { lean: true }),
this.claimCaseDbService.find({}, { lean: true }),
]);
if (role === "expert") {
const statuses = Object.values(ReqBlameStatus);
const data: Record<string, number> = { all: 0 };
for (const status of statuses) {
const filter = {
blameStatus: status,
$or: [
{
"firstPartyDetails.firstPartyClient.clientId": new Types.ObjectId(
client,
),
},
// {
// "secondPartyDetails.secondPartyClient.clientId":
// new Types.ObjectId(client),
// },
],
};
const count =
await this.requestManagementDbService.countByFilter(filter);
data[status] = count;
data.all += count;
}
return data;
return this.countBlameByCaseStatus(blames as any[], clientId);
}
if (role === "damage_expert") {
const statuses = Object.values(ReqClaimStatus);
const data: Record<string, number> = { all: 0 };
// For damage_expert, we need to apply the same filtering as expert-claim service
if (actor) {
// Fetch all requests with the statuses that expert-claim service shows
// This matches the statuses in getClaimRequestsListForExpert
const relevantStatuses = [
ReqClaimStatus.UnChecked,
ReqClaimStatus.ReviewRequest,
ReqClaimStatus.CheckAgain,
ReqClaimStatus.CloseRequest,
ReqClaimStatus.InPersonVisit,
ReqClaimStatus.CheckedRequest,
ReqClaimStatus.PendingFactorValidation,
];
// Fetch all requests with relevant statuses (matching expert-claim query)
const allRequests =
await this.claimRequestManagementDbService.findAllByStatus({
claimStatus: { $in: relevantStatuses },
});
// Filter requests using the same logic as expert-claim service
const filteredRequests =
await this.filterClaimRequestsForExpert(allRequests, actor);
// Count by status from filtered results
for (const status of statuses) {
const count = filteredRequests.filter(
(r) => r.claimStatus === status,
).length;
data[status] = count;
data.all += count;
}
} else {
// Fallback to simple count if actor not provided (shouldn't happen for damage_expert)
for (const status of statuses) {
const filter = {
claimStatus: status,
userClientKey: new Types.ObjectId(client),
};
const count =
await this.claimRequestManagementDbService.countByFilter(filter);
data[status] = count;
data.all += count;
}
}
return data;
return this.countClaimByCaseStatus(claims as any[], clientId);
}
// ✅ Company logic
if (role === "company") {
const blameStatuses = Object.values(ReqBlameStatus);
const claimStatuses = Object.values(ReqClaimStatus);
const blameData: Record<string, number> = { all: 0 };
const claimData: Record<string, number> = { all: 0 };
// Only count files where this company is FIRST party in Blame
for (const status of blameStatuses) {
const filter = {
blameStatus: status,
"firstPartyDetails.firstPartyClient.clientId": new Types.ObjectId(
client,
),
};
const count =
await this.requestManagementDbService.countByFilter(filter);
blameData[status] = count;
blameData.all += count;
}
// Claims always use `userClientKey`
for (const status of claimStatuses) {
const filter = {
claimStatus: status,
userClientKey: new Types.ObjectId(client),
};
const count =
await this.claimRequestManagementDbService.countByFilter(filter);
claimData[status] = count;
claimData.all += count;
}
return { blameData, claimData };
return {
blame: this.countBlameByCaseStatus(blames as any[], clientId),
claim: this.countClaimByCaseStatus(claims as any[], clientId),
};
}
return null;
}
async getAllRequestsReportCount(actor, client) {
if (actor.role === "damage_expert") {
// Pass actor to apply filtering logic for damage_expert
const data = await this.getAllRequestsCountByRole(
actor.role,
client,
actor,
);
return new DamageExpertAllRequestsCountReportDtoRs(data);
} else if (actor.role === "expert") {
const data = await this.getAllRequestsCountByRole(actor.role, client);
return new ExpertAllRequestsCountReportDtoRs(data);
} else {
// company
const damageExpertData = await this.getAllRequestsCountByRole(
"damage_expert",
client,
);
const expertData = await this.getAllRequestsCountByRole("expert", client);
return new CompanyAllRequestsCountReportDtoRs(
expertData,
damageExpertData,
);
/**
* Per calendar month, counts by native `CaseStatus` / `ClaimCaseStatus`.
* Company gets both blame and claim maps; expert / damage_expert get one map.
*/
async getDateFilteredRequestByStatus(
role: string,
client: string | undefined,
start: Date,
end: Date,
) {
const clientId = this.clientObjectId(client);
const [blames, claims] = await Promise.all([
this.blameRequestDbService.find({}, { lean: true }),
this.claimCaseDbService.find({}, { lean: true }),
]);
const inRange = (r: any) => {
const createdAt = new Date(r.createdAt);
return createdAt >= start && createdAt <= end;
};
if (role === "company") {
const blameOut: Record<string, number> = { all: 0 };
const claimOut: Record<string, number> = { all: 0 };
for (const s of Object.values(CaseStatus)) blameOut[s] = 0;
for (const s of Object.values(ClaimCaseStatus)) claimOut[s] = 0;
for (const r of blames as any[]) {
if (!this.isBlameForClient(r, clientId) || !inRange(r)) continue;
const st = r?.status as string;
if (st in blameOut) blameOut[st]++;
else blameOut[st] = (blameOut[st] ?? 0) + 1;
blameOut.all++;
}
for (const r of claims as any[]) {
if (!this.isClaimForClient(r, clientId) || !inRange(r)) continue;
const st = r?.status as string;
if (st in claimOut) claimOut[st]++;
else claimOut[st] = (claimOut[st] ?? 0) + 1;
claimOut.all++;
}
return { blame: blameOut, claim: claimOut };
}
if (role === "expert") {
const out: Record<string, number> = { all: 0 };
for (const s of Object.values(CaseStatus)) out[s] = 0;
for (const r of blames as any[]) {
if (!this.isBlameForClient(r, clientId) || !inRange(r)) continue;
const st = r?.status as string;
if (st in out) out[st]++;
else out[st] = (out[st] ?? 0) + 1;
out.all++;
}
return out;
}
if (role === "damage_expert") {
const out: Record<string, number> = { all: 0 };
for (const s of Object.values(ClaimCaseStatus)) out[s] = 0;
for (const r of claims as any[]) {
if (!this.isClaimForClient(r, clientId) || !inRange(r)) continue;
const st = r?.status as string;
if (st in out) out[st]++;
else out[st] = (out[st] ?? 0) + 1;
out.all++;
}
return out;
}
throw new BadRequestException("Unsupported role for reports");
}
async getAllRequestsReportPerMonth(actor, client) {
const result = [];
async getAllRequestsReportCount(actor: any, client: string | undefined) {
requireActorClientKey(actor);
if (actor.role === "damage_expert") {
const data = await this.getAllRequestsCountByRole(actor.role, client);
return new ClaimCaseStatusCountReportDtoRs(data as Record<string, number>);
}
if (actor.role === "expert") {
const data = await this.getAllRequestsCountByRole(actor.role, client);
return new BlameCaseStatusCountReportDtoRs(data as Record<string, number>);
}
const companyPayload = await this.getAllRequestsCountByRole(
"company",
client,
);
return new CompanyAllRequestsCountReportDtoRs(
(companyPayload as { blame: Record<string, number> }).blame,
(companyPayload as { claim: Record<string, number> }).claim,
);
}
async getAllRequestsReportPerMonth(actor: any, client: string | undefined) {
requireActorClientKey(actor);
const result: any[] = [];
const now = new Date();
if (actor.role === "company") {
for (let i = 0; i < 5; i++) {
const monthStart = new Date(now.getFullYear(), now.getMonth() - i, 1);
const monthEnd = new Date(
now.getFullYear(),
now.getMonth() - i + 1,
0,
23,
59,
59,
);
const label = monthStart.toLocaleDateString("fa-IR", {
month: "long",
year: "numeric",
});
for (let i = 0; i < 5; i++) {
const monthStart = new Date(now.getFullYear(), now.getMonth() - i, 1);
const monthEnd = new Date(
now.getFullYear(),
now.getMonth() - i + 1,
0,
23,
59,
59,
);
const label = monthStart.toLocaleDateString("fa-IR", {
month: "long",
year: "numeric",
});
const blameData = await this.getDateFilteredRequestByStatus(
"expert",
let data: any;
if (actor.role === "company") {
data = await this.getDateFilteredRequestByStatus(
"company",
client,
monthStart,
monthEnd,
);
const claimData = await this.getDateFilteredRequestByStatus(
"damage_expert",
client,
monthStart,
monthEnd,
);
result.unshift({
stDate: monthStart,
enDate: monthEnd,
faLabel: label,
data: {
blame: blameData,
claim: claimData,
},
});
}
return result;
} else {
for (let i = 0; i < 5; i++) {
const monthStart = new Date(now.getFullYear(), now.getMonth() - i, 1);
const monthEnd = new Date(
now.getFullYear(),
now.getMonth() - i + 1,
0,
23,
59,
59,
);
const label = monthStart.toLocaleDateString("fa-IR", {
month: "long",
year: "numeric",
});
const data = await this.getDateFilteredRequestByStatus(
} else {
data = await this.getDateFilteredRequestByStatus(
actor.role,
client,
monthStart,
monthEnd,
);
result.unshift({
stDate: monthStart,
enDate: monthEnd,
faLabel: label,
data,
});
}
return result;
result.unshift({
stDate: monthStart,
enDate: monthEnd,
faLabel: label,
data,
});
}
return result;
}
async getAllCheckedRequestsCount(actor, client) {
if (actor.role === "damage_expert" || actor.role === "expert") {
return this.getAllCheckedRequestsCountFn(actor.role, client);
} else {
const blame = await this.getAllCheckedRequestsCountFn("expert", client);
const claim = await this.getAllCheckedRequestsCountFn(
"damage_expert",
client,
);
return { blame, claim };
}
/** Same aggregates as `GET /reports/report/requests` (native status keys). */
async getAllCheckedRequestsCount(actor: any, client: string | undefined) {
return this.getAllRequestsReportCount(actor, client);
}
}