From 75b7e5ecadc7a61b2fdc5407a95cee9703876f7b Mon Sep 17 00:00:00 2001 From: "s.hajizadeh" Date: Tue, 21 Apr 2026 16:43:15 +0330 Subject: [PATCH] daghi problem fixed , 2 apis for reporting counts added --- .../schema/claim-case.evaluation.schema.ts | 11 +++-- src/expert-blame/expert-blame.service.ts | 36 ++++++++++++++ .../expert-blame.v2.controller.ts | 19 ++++++- src/expert-claim/expert-claim.service.ts | 23 +++++++++ .../expert-claim.v2.controller.ts | 10 ++++ src/helpers/expert-panel-status-report.ts | 49 +++++++++++++++++++ 6 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 src/helpers/expert-panel-status-report.ts diff --git a/src/claim-request-management/entites/schema/claim-case.evaluation.schema.ts b/src/claim-request-management/entites/schema/claim-case.evaluation.schema.ts index 9d4b28f..da6440b 100644 --- a/src/claim-request-management/entites/schema/claim-case.evaluation.schema.ts +++ b/src/claim-request-management/entites/schema/claim-case.evaluation.schema.ts @@ -12,8 +12,12 @@ export class ClaimPartPricing { @Prop({ type: String }) partId: string; - @Prop({ type: String }) - carPartDamage?: string; + /** + * Legacy string or structured `{ part, side }` from expert reply DTOs (V1/V2). + * Must be Mixed — objects cannot cast to String. + */ + @Prop({ type: MongooseSchema.Types.Mixed }) + carPartDamage?: string | { part?: string; side?: string }; @Prop({ type: String }) typeOfDamage?: string; @@ -27,8 +31,9 @@ export class ClaimPartPricing { @Prop({ type: MongooseSchema.Types.Mixed }) totalPayment?: string | number; + /** V1/V2: string/number legacy, or `{ option, price?, branchId? }` after expert normalization. */ @Prop({ type: MongooseSchema.Types.Mixed }) - daghi?: string | number; + daghi?: string | number | Record; @Prop({ type: Boolean, default: false }) factorNeeded?: boolean; diff --git a/src/expert-blame/expert-blame.service.ts b/src/expert-blame/expert-blame.service.ts index 027f015..639aca2 100644 --- a/src/expert-blame/expert-blame.service.ts +++ b/src/expert-blame/expert-blame.service.ts @@ -12,6 +12,10 @@ import { blameCaseAccessibleToExpert, requireActorClientKey, } from "src/helpers/tenant-scope"; +import { + blameCaseStatusToReportBucket, + initialBlameExpertReportBuckets, +} from "src/helpers/expert-panel-status-report"; import { RequestManagementDbService } from "src/request-management/entities/db-service/request-management.db.service"; import { BlameRequestDbService } from "src/request-management/entities/db-service/blame-request.db.service"; import { @@ -205,6 +209,38 @@ export class ExpertBlameService { * 2. Requests where current expert made the decision * Does NOT show requests decided by other experts. */ + /** + * V2: Count blame cases for this expert’s tenant, grouped for dashboard: + * `IN_PROGRESS` = OPEN + WAITING_FOR_SECOND_PARTY; other {@link CaseStatus} keys unchanged. + */ + async getStatusReportBucketsV2(actor: any): Promise> { + requireActorClientKey(actor); + const rows = await this.blameRequestDbService.find({}, { lean: true }); + const buckets = initialBlameExpertReportBuckets(); + for (const doc of rows as Record[]) { + if (!this.blameDocIncludedInExpertTenantReport(doc, actor)) continue; + const st = String(doc.status ?? ""); + const key = blameCaseStatusToReportBucket(st); + buckets.all++; + buckets[key] = (buckets[key] ?? 0) + 1; + } + return buckets; + } + + /** Tenant + expert-initiated visibility (same as list, without queue filters). */ + private blameDocIncludedInExpertTenantReport( + doc: Record, + actor: { sub: string; clientKey?: string }, + ): boolean { + if (!blameCaseAccessibleToExpert(doc, actor)) { + return false; + } + if (doc.expertInitiated && doc.initiatedByFieldExpertId) { + return String(doc.initiatedByFieldExpertId) === String(actor.sub); + } + return true; + } + async findAllV2(actor: any): Promise { try { requireActorClientKey(actor); diff --git a/src/expert-blame/expert-blame.v2.controller.ts b/src/expert-blame/expert-blame.v2.controller.ts index 708261a..2691b9f 100644 --- a/src/expert-blame/expert-blame.v2.controller.ts +++ b/src/expert-blame/expert-blame.v2.controller.ts @@ -8,7 +8,7 @@ import { Put, UseGuards, } from "@nestjs/common"; -import { ApiBearerAuth, ApiBody, ApiParam, ApiTags } from "@nestjs/swagger"; +import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiTags } from "@nestjs/swagger"; import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard"; import { RolesGuard } from "src/auth/guards/role.guard"; import { Roles } from "src/decorators/roles.decorator"; @@ -38,6 +38,23 @@ export class ExpertBlameV2Controller { } } + @Get("report/status-counts") + @ApiOperation({ + summary: "Count blame cases by grouped status bucket (tenant)", + description: + "IN_PROGRESS groups OPEN and WAITING_FOR_SECOND_PARTY. WAITING_FOR_EXPERT, WAITING_FOR_DOCUMENT_RESEND, WAITING_FOR_SIGNATURES, and terminal statuses are counted separately. Expert-initiated files count only for the initiating expert.", + }) + async getStatusReportBucketsV2(@CurrentUser() actor: any) { + try { + return await this.expertBlameService.getStatusReportBucketsV2(actor); + } catch (error) { + if (error instanceof HttpException) throw error; + throw new InternalServerErrorException( + error instanceof Error ? error.message : "Failed to load blame status report", + ); + } + } + @Get(":id") @ApiParam({ name: "id", description: "Blame case request id" }) async findOne(@Param("id") id: string, @CurrentUser() actor: any) { diff --git a/src/expert-claim/expert-claim.service.ts b/src/expert-claim/expert-claim.service.ts index ec7af51..8029fe4 100644 --- a/src/expert-claim/expert-claim.service.ts +++ b/src/expert-claim/expert-claim.service.ts @@ -39,6 +39,10 @@ import { claimCaseTouchesClient, requireActorClientKey, } from "src/helpers/tenant-scope"; +import { + claimCaseStatusToReportBucket, + initialClaimExpertReportBuckets, +} from "src/helpers/expert-panel-status-report"; import { ClaimWorkflowStep } from "src/Types&Enums/claim-request-management/claim-workflow-steps.enum"; import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum"; import { PartyRole } from "src/request-management/entities/schema/partyRole.enum"; @@ -2448,6 +2452,25 @@ export class ExpertClaimService { }; } + /** + * V2: Count claim cases for this damage expert’s tenant, grouped for dashboard: + * `IN_PROGRESS` = user flow before submission complete (CREATED … CAPTURING_PART_DAMAGES); + * other {@link ClaimCaseStatus} keys unchanged. + */ + async getStatusReportBucketsV2(actor: any): Promise> { + const clientKey = requireActorClientKey(actor); + const rows = await this.claimCaseDbService.find({}, { lean: true }); + const buckets = initialClaimExpertReportBuckets(); + for (const doc of rows as Record[]) { + if (!claimCaseTouchesClient(doc, clientKey)) continue; + const st = String(doc.status ?? ""); + const key = claimCaseStatusToReportBucket(st); + buckets.all++; + buckets[key] = (buckets[key] ?? 0) + 1; + } + return buckets; + } + /** * V2: Get claim list for damage expert * diff --git a/src/expert-claim/expert-claim.v2.controller.ts b/src/expert-claim/expert-claim.v2.controller.ts index c688120..e555de3 100644 --- a/src/expert-claim/expert-claim.v2.controller.ts +++ b/src/expert-claim/expert-claim.v2.controller.ts @@ -34,6 +34,16 @@ class InPersonVisitV2Dto { export class ExpertClaimV2Controller { constructor(private readonly expertClaimService: ExpertClaimService) { } + @Get("report/status-counts") + @ApiOperation({ + summary: "Count claim cases by grouped status bucket (tenant)", + description: + "IN_PROGRESS groups user-phase statuses before submission is complete (CREATED through CAPTURING_PART_DAMAGES). Other keys match ClaimCaseStatus. Scoped to the insurer in the JWT.", + }) + async getStatusReportBucketsV2(@CurrentUser() actor: any) { + return await this.expertClaimService.getStatusReportBucketsV2(actor); + } + @Get("requests") @ApiOperation({ summary: "List available claim requests for damage expert", diff --git a/src/helpers/expert-panel-status-report.ts b/src/helpers/expert-panel-status-report.ts new file mode 100644 index 0000000..aa13797 --- /dev/null +++ b/src/helpers/expert-panel-status-report.ts @@ -0,0 +1,49 @@ +import { CaseStatus } from "src/Types&Enums/blame-request-management/caseStatus.enum"; +import { ClaimCaseStatus } from "src/Types&Enums/claim-request-management/claim-case-status.enum"; + +/** Blame: early workflow before expert queue / resend / signatures / terminals. */ +const BLAME_IN_PROGRESS_STATUSES = new Set([ + CaseStatus.OPEN, + CaseStatus.WAITING_FOR_SECOND_PARTY, +]); + +/** + * Claim: user flow before submission is complete (analog to `ClaimWorkflowStep.USER_SUBMISSION_COMPLETE`). + */ +const CLAIM_IN_PROGRESS_STATUSES = new Set([ + ClaimCaseStatus.CREATED, + ClaimCaseStatus.SELECTING_OUTER_PARTS, + ClaimCaseStatus.SELECTING_OTHER_PARTS, + ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS, + ClaimCaseStatus.CAPTURING_PART_DAMAGES, +]); + +export function blameCaseStatusToReportBucket(status: string): string { + if (BLAME_IN_PROGRESS_STATUSES.has(status)) return "IN_PROGRESS"; + return status; +} + +export function claimCaseStatusToReportBucket(status: string): string { + if (CLAIM_IN_PROGRESS_STATUSES.has(status)) return "IN_PROGRESS"; + return status; +} + +/** Keys returned for blame V2 expert report (IN_PROGRESS + each native post-early status). */ +export function initialBlameExpertReportBuckets(): Record { + const out: Record = { all: 0, IN_PROGRESS: 0 }; + for (const s of Object.values(CaseStatus)) { + if (BLAME_IN_PROGRESS_STATUSES.has(s)) continue; + out[s] = 0; + } + return out; +} + +/** Keys returned for claim V2 expert report. */ +export function initialClaimExpertReportBuckets(): Record { + const out: Record = { all: 0, IN_PROGRESS: 0 }; + for (const s of Object.values(ClaimCaseStatus)) { + if (CLAIM_IN_PROGRESS_STATUSES.has(s)) continue; + out[s] = 0; + } + return out; +}