From 98f1d2caf5993883b78925be581d950c47013e55 Mon Sep 17 00:00:00 2001 From: SepehrYahyaee <7heycallmegray@gmail.com> Date: Tue, 28 Apr 2026 14:41:47 +0330 Subject: [PATCH] YARA-857 --- .../expert-insurer.controller.ts | 27 ++++- src/expert-insurer/expert-insurer.service.ts | 110 ++++++++++++++++++ 2 files changed, 135 insertions(+), 2 deletions(-) diff --git a/src/expert-insurer/expert-insurer.controller.ts b/src/expert-insurer/expert-insurer.controller.ts index 5a7b81c..c8e4727 100644 --- a/src/expert-insurer/expert-insurer.controller.ts +++ b/src/expert-insurer/expert-insurer.controller.ts @@ -91,7 +91,7 @@ export class ExpertInsurerController { @ApiQuery({ name: "page", type: Number }) @ApiQuery({ name: "response_count", type: Number }) - @Get("list") + @Get("experts/list") async getAllExperts( @Query("page") page: number, @Query("response_count") count: number, @@ -104,7 +104,7 @@ export class ExpertInsurerController { ); } - @Get("top-experts") + @Get("experts/top") async getTopExperts(@CurrentUser() actor) { return await this.expertInsurerService.getTopExpertsForClient(actor); } @@ -128,6 +128,29 @@ export class ExpertInsurerController { return await this.expertInsurerService.getExpertStatisticsReport(actor); } + @Get("report/status-counts") + @ApiQuery({ + name: "from", + required: false, + description: "Optional start datetime (ISO string)", + }) + @ApiQuery({ + name: "to", + required: false, + description: "Optional end datetime (ISO string)", + }) + async getInsurerStatusReport( + @CurrentUser() actor, + @Query("from") from?: string, + @Query("to") to?: string, + ) { + return await this.expertInsurerService.getInsurerFileStatusCounts( + actor, + from, + to, + ); + } + @ApiParam({ name: "expertId" }) @ApiQuery({ name: "role", enum: ["claim", "blame"] }) @Get("/:expertId") diff --git a/src/expert-insurer/expert-insurer.service.ts b/src/expert-insurer/expert-insurer.service.ts index 98fc932..af702be 100644 --- a/src/expert-insurer/expert-insurer.service.ts +++ b/src/expert-insurer/expert-insurer.service.ts @@ -21,6 +21,7 @@ import { import { BlameRequestDbService } from "src/request-management/entities/db-service/blame-request.db.service"; import { FileRating } from "src/request-management/entities/schema/request-management.schema"; 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 { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service"; import { ExpertDbService } from "src/users/entities/db-service/expert.db.service"; import { ExpertFileActivityDbService } from "src/users/entities/db-service/expert-file-activity.db.service"; @@ -664,4 +665,113 @@ export class ExpertInsurerService { async retrieveInsuranceBranches(insuranceId: string) { return this.branchDbService.findAll(insuranceId); } + + private parseDateRange(from?: string, to?: string) { + const fromDate = from ? new Date(from) : undefined; + const toDate = to ? new Date(to) : undefined; + + if (fromDate && Number.isNaN(fromDate.getTime())) { + throw new BadRequestException("Invalid 'from' date"); + } + if (toDate && Number.isNaN(toDate.getTime())) { + throw new BadRequestException("Invalid 'to' date"); + } + if (fromDate && toDate && fromDate > toDate) { + throw new BadRequestException("'from' must be before or equal to 'to'"); + } + return { fromDate, toDate }; + } + + private isInDateRange(value: any, fromDate?: Date, toDate?: Date): boolean { + if (!fromDate && !toDate) return true; + if (!value) return false; + const d = new Date(value); + if (Number.isNaN(d.getTime())) return false; + if (fromDate && d < fromDate) return false; + if (toDate && d > toDate) return false; + return true; + } + + async getInsurerFileStatusCounts( + actor: any, + from?: string, + to?: string, + ): Promise<{ + all: number; + completed: number; + under_review: number; + waiting_for_documents_resend: number; + }> { + const clientObjectId = this.getClientId(actor); + const { fromDate, toDate } = this.parseDateRange(from, to); + + const [blameFilesRaw, claimFilesRaw] = await Promise.all([ + this.getClientBlameFiles(clientObjectId), + this.getClientClaimFiles(clientObjectId), + ]); + + const blameFiles = blameFilesRaw.filter((f) => + this.isInDateRange((f as any)?.createdAt, fromDate, toDate), + ); + const claimFiles = claimFilesRaw.filter((f) => + this.isInDateRange((f as any)?.createdAt, fromDate, toDate), + ); + + 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 || ""); + if (!key) continue; + const prev = fileMap.get(key) ?? {}; + prev.blame = b; + fileMap.set(key, prev); + } + for (const c of claimFiles) { + const key = String((c as any).publicId || (c as any)._id || ""); + if (!key) continue; + const prev = fileMap.get(key) ?? {}; + prev.claim = c; + fileMap.set(key, prev); + } + + let completed = 0; + let underReview = 0; + let waitingForDocumentsResend = 0; + + for (const entry of fileMap.values()) { + const blameStatus = entry.blame?.status; + const claimStatus = entry.claim?.status; + + if (claimStatus === ClaimCaseStatus.COMPLETED) { + completed += 1; + } + + const blameUnderReview = + blameStatus === CaseStatus.WAITING_FOR_EXPERT; + const claimUnderReview = + claimStatus === ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT || + claimStatus === ClaimCaseStatus.EXPERT_REVIEWING; + if (blameUnderReview || claimUnderReview) { + underReview += 1; + } + + const blameResend = + blameStatus === CaseStatus.WAITING_FOR_DOCUMENT_RESEND; + const claimResend = + claimStatus === ClaimCaseStatus.WAITING_FOR_USER_RESEND; + if (blameResend || claimResend) { + waitingForDocumentsResend += 1; + } + } + + return { + all: fileMap.size, + completed, + under_review: underReview, + waiting_for_documents_resend: waitingForDocumentsResend, + }; + } }