forked from Yara724/api
414 lines
12 KiB
TypeScript
414 lines
12 KiB
TypeScript
import { Injectable, Logger } 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 {
|
|
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,
|
|
) {}
|
|
|
|
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;
|
|
}
|
|
|
|
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,
|
|
);
|
|
|
|
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 = [];
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
return filteredRequests;
|
|
}
|
|
|
|
async getAllRequestsCountByRole(role: string, client: string, actor?: any) {
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// ✅ 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 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,
|
|
);
|
|
}
|
|
}
|
|
|
|
async getAllRequestsReportPerMonth(actor, client) {
|
|
const result = [];
|
|
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",
|
|
});
|
|
|
|
const blameData = await this.getDateFilteredRequestByStatus(
|
|
"expert",
|
|
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(
|
|
actor.role,
|
|
client,
|
|
monthStart,
|
|
monthEnd,
|
|
);
|
|
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 };
|
|
}
|
|
}
|
|
}
|