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 = { 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 = {}; 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 { 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 = { 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 = { 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 = { all: 0 }; const claimData: Record = { 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 }; } } }