forked from Yara724/api
Merge pull request 'YARA-720, Yara-721 and others' (#1) from s.yahyaee/yara724-api:main into main
Reviewed-on: Yara724/api#1
This commit is contained in:
@@ -263,14 +263,30 @@ export class ExpertBlameService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Initial validation to ensure the expert has access
|
// 2. Initial validation to ensure the expert has access
|
||||||
if (String(request?.actorLocked?.actorId) === actorId && request.lockFile) {
|
// Check if locked by current expert and lock is still active
|
||||||
|
const isLockedByCurrentExpert =
|
||||||
|
String(request?.actorLocked?.actorId) === actorId && request.lockFile;
|
||||||
|
|
||||||
|
// Check if lock has expired
|
||||||
|
let isLockExpired = false;
|
||||||
|
if (request.unlockTime) {
|
||||||
|
const unlockTime = new Date(request.unlockTime).getTime();
|
||||||
|
const now = Date.now();
|
||||||
|
isLockExpired = now >= unlockTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLockedByCurrentExpert && !isLockExpired) {
|
||||||
// This is the correct expert, and the file is locked to them, which is fine.
|
// This is the correct expert, and the file is locked to them, which is fine.
|
||||||
|
// They can access it even if they closed the browser and came back.
|
||||||
} else if (
|
} else if (
|
||||||
request.lockFile ||
|
(request.lockFile && !isLockExpired) ||
|
||||||
request.blameStatus === ReqBlameStatus.ReviewRequest
|
request.blameStatus === ReqBlameStatus.ReviewRequest
|
||||||
) {
|
) {
|
||||||
// The file is locked, but not by the current expert.
|
// The file is locked by someone else, or lock expired but status hasn't updated yet
|
||||||
throw new BadRequestException("Request is locked by another expert");
|
// Only block if lock is still active and not by current expert
|
||||||
|
if (request.lockFile && !isLockExpired && !isLockedByCurrentExpert) {
|
||||||
|
throw new BadRequestException("Request is locked by another expert");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Populate the resend links if the data exists
|
// 3. Populate the resend links if the data exists
|
||||||
@@ -474,9 +490,18 @@ export class ExpertBlameService {
|
|||||||
"Access denied to this request. You are not the locked expert.",
|
"Access denied to this request. You are not the locked expert.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (request.unlockTime == null) {
|
|
||||||
|
// Check if lock has expired (unlockTime has passed)
|
||||||
|
if (request.unlockTime) {
|
||||||
|
const unlockTime = new Date(request.unlockTime).getTime();
|
||||||
|
const now = Date.now();
|
||||||
|
if (now >= unlockTime) {
|
||||||
|
throw new ForbiddenException("Your lock time has expired.");
|
||||||
|
}
|
||||||
|
} else if (request.unlockTime == null) {
|
||||||
throw new ForbiddenException("Your lock time has expired.");
|
throw new ForbiddenException("Your lock time has expired.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!request.lockFile) {
|
if (!request.lockFile) {
|
||||||
throw new ForbiddenException(
|
throw new ForbiddenException(
|
||||||
"You must lock the request before submitting a reply.",
|
"You must lock the request before submitting a reply.",
|
||||||
|
|||||||
@@ -818,7 +818,8 @@ export class ExpertClaimService {
|
|||||||
return new ClaimPerIdRs(requestUpdated);
|
return new ClaimPerIdRs(requestUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isRequestLocked(requestUpdated)) {
|
// Check if locked by someone else (allow if locked by current user)
|
||||||
|
if (this.isRequestLocked(requestUpdated, currentUser)) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
{ responseCode: 1007, message: "Request is locked" },
|
{ responseCode: 1007, message: "Request is locked" },
|
||||||
HttpStatus.FORBIDDEN,
|
HttpStatus.FORBIDDEN,
|
||||||
@@ -846,7 +847,7 @@ export class ExpertClaimService {
|
|||||||
if (this.isCurrentUserAllowed(request, currentUser)) {
|
if (this.isCurrentUserAllowed(request, currentUser)) {
|
||||||
return (await this.claimRequestManagementDbService.findOne(requestId))
|
return (await this.claimRequestManagementDbService.findOne(requestId))
|
||||||
.imageRequired;
|
.imageRequired;
|
||||||
} else if (this.isRequestLocked(requestId)) {
|
} else if (this.isRequestLocked(request, currentUser)) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
{ responseCode: 1007, message: "Request is locked" },
|
{ responseCode: 1007, message: "Request is locked" },
|
||||||
HttpStatus.FORBIDDEN,
|
HttpStatus.FORBIDDEN,
|
||||||
@@ -889,8 +890,8 @@ export class ExpertClaimService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform authorization checks
|
// Perform authorization checks - allow if locked by current user
|
||||||
if (this.isRequestLocked(request) && !this.isCurrentUserAllowed(request, currentUser)) {
|
if (this.isRequestLocked(request, currentUser)) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
{ responseCode: 1007, message: "Request is locked" },
|
{ responseCode: 1007, message: "Request is locked" },
|
||||||
HttpStatus.FORBIDDEN,
|
HttpStatus.FORBIDDEN,
|
||||||
@@ -933,16 +934,54 @@ export class ExpertClaimService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isCurrentUserAllowed(request: any, currentUser: any): boolean {
|
private isCurrentUserAllowed(request: any, currentUser: any): boolean {
|
||||||
return (
|
// Check if locked by current user and lock is still active
|
||||||
|
const isLockedByCurrentUser =
|
||||||
String(request?.actorLocked?.actorId) === currentUser.sub &&
|
String(request?.actorLocked?.actorId) === currentUser.sub &&
|
||||||
request.lockFile
|
request.lockFile;
|
||||||
);
|
|
||||||
|
if (!isLockedByCurrentUser) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check if lock has expired (unlockTime has passed)
|
||||||
|
if (request.unlockTime) {
|
||||||
|
const unlockTime = new Date(request.unlockTime).getTime();
|
||||||
|
const now = Date.now();
|
||||||
|
if (now >= unlockTime) {
|
||||||
|
// Lock has expired, but still allow access if they were the one who locked it
|
||||||
|
// The unlockApi will handle the cleanup
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isRequestLocked(request: any): boolean {
|
private isRequestLocked(request: any, currentUser?: any): boolean {
|
||||||
return (
|
if (!request.lockFile || request.claimStatus !== ReqClaimStatus.ReviewRequest) {
|
||||||
request.lockFile && request.claimStatus === ReqClaimStatus.ReviewRequest
|
return false;
|
||||||
);
|
}
|
||||||
|
|
||||||
|
// Check if lock has expired
|
||||||
|
if (request.unlockTime) {
|
||||||
|
const unlockTime = new Date(request.unlockTime).getTime();
|
||||||
|
const now = Date.now();
|
||||||
|
if (now >= unlockTime) {
|
||||||
|
// Lock has expired, treat as not locked
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If currentUser is provided, allow access if they are the one who locked it
|
||||||
|
if (currentUser) {
|
||||||
|
const isLockedByCurrentUser =
|
||||||
|
String(request?.actorLocked?.actorId) === currentUser.sub;
|
||||||
|
// Return false (not locked) if locked by current user, true if locked by someone else
|
||||||
|
return !isLockedByCurrentUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no currentUser provided, treat as locked
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async submitReplyRequest(
|
async submitReplyRequest(
|
||||||
@@ -963,9 +1002,18 @@ export class ExpertClaimService {
|
|||||||
) {
|
) {
|
||||||
throw new ForbiddenException("Access denied to this request");
|
throw new ForbiddenException("Access denied to this request");
|
||||||
}
|
}
|
||||||
if (request?.unlockTime == null && !request?.objection) {
|
|
||||||
|
// Check if lock has expired (unlockTime has passed)
|
||||||
|
if (request?.unlockTime && !request?.objection) {
|
||||||
|
const unlockTime = new Date(request.unlockTime).getTime();
|
||||||
|
const now = Date.now();
|
||||||
|
if (now >= unlockTime) {
|
||||||
|
throw new ForbiddenException("Your time has expired");
|
||||||
|
}
|
||||||
|
} else if (request?.unlockTime == null && !request?.objection) {
|
||||||
throw new ForbiddenException("Your time has expired");
|
throw new ForbiddenException("Your time has expired");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!request.lockFile && !request?.objection) {
|
if (!request.lockFile && !request?.objection) {
|
||||||
throw new ForbiddenException(
|
throw new ForbiddenException(
|
||||||
"For submit reply you must lock the request",
|
"For submit reply you must lock the request",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { InjectModel } from "@nestjs/mongoose";
|
|||||||
import { Model, Types } from "mongoose";
|
import { Model, Types } from "mongoose";
|
||||||
import { ClaimRequiredDocumentDbService } from "src/claim-request-management/entites/db-service/claim-required-document.db.service";
|
import { ClaimRequiredDocumentDbService } from "src/claim-request-management/entites/db-service/claim-required-document.db.service";
|
||||||
import { VideoCaptureDbService } from "src/claim-request-management/entites/db-service/video-capture.db.service";
|
import { VideoCaptureDbService } from "src/claim-request-management/entites/db-service/video-capture.db.service";
|
||||||
|
import { DamageImageDbService } from "src/claim-request-management/entites/db-service/damage-image.db.service";
|
||||||
import { ClaimRequestManagementModel } from "src/claim-request-management/entites/schema/claim-request-management.schema";
|
import { ClaimRequestManagementModel } from "src/claim-request-management/entites/schema/claim-request-management.schema";
|
||||||
import { CreateBranchDto } from "src/client/dto/create-branch.dto";
|
import { CreateBranchDto } from "src/client/dto/create-branch.dto";
|
||||||
import { BranchDbService } from "src/client/entities/db-service/branch.db.service";
|
import { BranchDbService } from "src/client/entities/db-service/branch.db.service";
|
||||||
@@ -44,6 +45,7 @@ export class ExpertInsurerService {
|
|||||||
private readonly claimVideoCaptureDbService: VideoCaptureDbService,
|
private readonly claimVideoCaptureDbService: VideoCaptureDbService,
|
||||||
private readonly branchDbService: BranchDbService,
|
private readonly branchDbService: BranchDbService,
|
||||||
private readonly claimRequiredDocumentDbService: ClaimRequiredDocumentDbService,
|
private readonly claimRequiredDocumentDbService: ClaimRequiredDocumentDbService,
|
||||||
|
private readonly damageImageDbService: DamageImageDbService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async retrieveAllExpertsOfClient(
|
async retrieveAllExpertsOfClient(
|
||||||
@@ -189,7 +191,7 @@ export class ExpertInsurerService {
|
|||||||
: this.requestManagementModel;
|
: this.requestManagementModel;
|
||||||
const expertCollection = role === "claim" ? "damage-expert" : "expert";
|
const expertCollection = role === "claim" ? "damage-expert" : "expert";
|
||||||
|
|
||||||
return await model.aggregate([
|
const results = await model.aggregate([
|
||||||
{ $match: { "actorLocked.actorId": expertObjectId } },
|
{ $match: { "actorLocked.actorId": expertObjectId } },
|
||||||
{
|
{
|
||||||
$lookup: {
|
$lookup: {
|
||||||
@@ -232,6 +234,7 @@ export class ExpertInsurerService {
|
|||||||
currentStep: 1,
|
currentStep: 1,
|
||||||
rating: 1,
|
rating: 1,
|
||||||
averageRating: 1,
|
averageRating: 1,
|
||||||
|
imageRequired: 1,
|
||||||
expertInfo: {
|
expertInfo: {
|
||||||
_id: 1,
|
_id: 1,
|
||||||
fullName: {
|
fullName: {
|
||||||
@@ -244,6 +247,15 @@ export class ExpertInsurerService {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Process imageRequired for claim files
|
||||||
|
if (role === "claim") {
|
||||||
|
return await Promise.all(
|
||||||
|
results.map((file) => this.processImageRequired(file)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
async rateExpertOnFile(
|
async rateExpertOnFile(
|
||||||
@@ -334,6 +346,7 @@ export class ExpertInsurerService {
|
|||||||
nationalCodeOfInsurer: 1,
|
nationalCodeOfInsurer: 1,
|
||||||
carGreenCard: 1,
|
carGreenCard: 1,
|
||||||
aiImages: 1,
|
aiImages: 1,
|
||||||
|
imageRequired: 1,
|
||||||
videoCaptureId: 1,
|
videoCaptureId: 1,
|
||||||
damageExpertReply: 1,
|
damageExpertReply: 1,
|
||||||
damageExpertReplyFinal: 1,
|
damageExpertReplyFinal: 1,
|
||||||
@@ -353,7 +366,10 @@ export class ExpertInsurerService {
|
|||||||
blameFilesRaw.map((file) => this.populateBlameFileLinks(file)),
|
blameFilesRaw.map((file) => this.populateBlameFileLinks(file)),
|
||||||
);
|
);
|
||||||
const populatedClaimFiles = await Promise.all(
|
const populatedClaimFiles = await Promise.all(
|
||||||
claimFilesRaw.map((file) => this.populateClaimFileLinks(file)),
|
claimFilesRaw.map(async (file) => {
|
||||||
|
const populated = await this.populateClaimFileLinks(file);
|
||||||
|
return await this.processImageRequired(populated);
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return { blameFiles: populatedBlameFiles, claimFiles: populatedClaimFiles };
|
return { blameFiles: populatedBlameFiles, claimFiles: populatedClaimFiles };
|
||||||
@@ -442,6 +458,99 @@ export class ExpertInsurerService {
|
|||||||
return claimFile;
|
return claimFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes imageRequired field:
|
||||||
|
* 1. Removes part_segments from aiReport.distinct_damaged_parts_report.parts[]
|
||||||
|
* 2. Populates imageId fields with file links
|
||||||
|
*/
|
||||||
|
private async processImageRequired(claimFile: any): Promise<any> {
|
||||||
|
if (!claimFile || !claimFile.imageRequired) {
|
||||||
|
return claimFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageRequired = claimFile.imageRequired;
|
||||||
|
|
||||||
|
// Process aroundTheCar array
|
||||||
|
if (Array.isArray(imageRequired.aroundTheCar)) {
|
||||||
|
imageRequired.aroundTheCar = await Promise.all(
|
||||||
|
imageRequired.aroundTheCar.map(async (item: any) => {
|
||||||
|
// Remove part_segments from aiReport.distinct_damaged_parts_report.parts[]
|
||||||
|
if (
|
||||||
|
item?.aiReport?.distinct_damaged_parts_report?.parts &&
|
||||||
|
Array.isArray(item.aiReport.distinct_damaged_parts_report.parts)
|
||||||
|
) {
|
||||||
|
item.aiReport.distinct_damaged_parts_report.parts =
|
||||||
|
item.aiReport.distinct_damaged_parts_report.parts.map(
|
||||||
|
(part: any) => {
|
||||||
|
const { part_segments, ...partWithoutSegments } = part;
|
||||||
|
return partWithoutSegments;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate imageId with file link
|
||||||
|
if (item?.imageId) {
|
||||||
|
try {
|
||||||
|
const imageDoc = await this.damageImageDbService.findOne(
|
||||||
|
item.imageId.toString(),
|
||||||
|
);
|
||||||
|
if (imageDoc && imageDoc.path) {
|
||||||
|
item.imageId = buildFileLink(imageDoc.path);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn(
|
||||||
|
`Failed to populate imageId for aroundTheCar item: ${error.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process selectPartOfCar array
|
||||||
|
if (Array.isArray(imageRequired.selectPartOfCar)) {
|
||||||
|
imageRequired.selectPartOfCar = await Promise.all(
|
||||||
|
imageRequired.selectPartOfCar.map(async (item: any) => {
|
||||||
|
// Remove part_segments from aiReport.distinct_damaged_parts_report.parts[]
|
||||||
|
if (
|
||||||
|
item?.aiReport?.distinct_damaged_parts_report?.parts &&
|
||||||
|
Array.isArray(item.aiReport.distinct_damaged_parts_report.parts)
|
||||||
|
) {
|
||||||
|
item.aiReport.distinct_damaged_parts_report.parts =
|
||||||
|
item.aiReport.distinct_damaged_parts_report.parts.map(
|
||||||
|
(part: any) => {
|
||||||
|
const { part_segments, ...partWithoutSegments } = part;
|
||||||
|
return partWithoutSegments;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate imageId with file link
|
||||||
|
if (item?.imageId) {
|
||||||
|
try {
|
||||||
|
const imageDoc = await this.damageImageDbService.findOne(
|
||||||
|
item.imageId.toString(),
|
||||||
|
);
|
||||||
|
if (imageDoc && imageDoc.path) {
|
||||||
|
item.imageId = buildFileLink(imageDoc.path);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn(
|
||||||
|
`Failed to populate imageId for selectPartOfCar item: ${error.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return claimFile;
|
||||||
|
}
|
||||||
|
|
||||||
private async populatePartyReplyLinks(partyReply: any) {
|
private async populatePartyReplyLinks(partyReply: any) {
|
||||||
if (!partyReply) return;
|
if (!partyReply) return;
|
||||||
// --- FIX: Consistently use findById ---
|
// --- FIX: Consistently use findById ---
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
import { ClaimRequestManagementModule } from "src/claim-request-management/claim-request-management.module";
|
import { ClaimRequestManagementModule } from "src/claim-request-management/claim-request-management.module";
|
||||||
import { RequestManagementModule } from "src/request-management/request-management.module";
|
import { RequestManagementModule } from "src/request-management/request-management.module";
|
||||||
|
import { ClientModule } from "src/client/client.module";
|
||||||
import { ReportsController } from "./reports.controller";
|
import { ReportsController } from "./reports.controller";
|
||||||
import { ReportsService } from "./reports.service";
|
import { ReportsService } from "./reports.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [RequestManagementModule, ClaimRequestManagementModule],
|
imports: [
|
||||||
|
RequestManagementModule,
|
||||||
|
ClaimRequestManagementModule,
|
||||||
|
ClientModule,
|
||||||
|
],
|
||||||
controllers: [ReportsController],
|
controllers: [ReportsController],
|
||||||
providers: [ReportsService],
|
providers: [ReportsService],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { ClaimRequestManagementDbService } from "src/claim-request-management/entites/db-service/claim-request-management.db.service";
|
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 { 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 { ReqBlameStatus } from "src/Types&Enums/blame-request-management/status.enum";
|
||||||
import { ReqClaimStatus } from "src/Types&Enums/claim-request-management/status.enum";
|
import { ReqClaimStatus } from "src/Types&Enums/claim-request-management/status.enum";
|
||||||
|
import { UserType } from "src/Types&Enums/userType.enum";
|
||||||
import {
|
import {
|
||||||
CompanyAllRequestsCountReportDtoRs,
|
CompanyAllRequestsCountReportDtoRs,
|
||||||
DamageExpertAllRequestsCountReportDtoRs,
|
DamageExpertAllRequestsCountReportDtoRs,
|
||||||
@@ -12,9 +14,12 @@ import {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ReportsService {
|
export class ReportsService {
|
||||||
|
private readonly logger = new Logger(ReportsService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly requestManagementDbService: RequestManagementDbService,
|
private readonly requestManagementDbService: RequestManagementDbService,
|
||||||
private readonly claimRequestManagementDbService: ClaimRequestManagementDbService,
|
private readonly claimRequestManagementDbService: ClaimRequestManagementDbService,
|
||||||
|
private readonly clientDbService: ClientDbService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getAllCheckedRequestsCountFn(role: string, client: string) {
|
async getAllCheckedRequestsCountFn(role: string, client: string) {
|
||||||
@@ -81,7 +86,91 @@ export class ReportsService {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllRequestsCountByRole(role: string, client: string) {
|
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") {
|
if (role === "expert") {
|
||||||
const statuses = Object.values(ReqBlameStatus);
|
const statuses = Object.values(ReqBlameStatus);
|
||||||
const data: Record<string, number> = { all: 0 };
|
const data: Record<string, number> = { all: 0 };
|
||||||
@@ -115,16 +204,51 @@ export class ReportsService {
|
|||||||
const statuses = Object.values(ReqClaimStatus);
|
const statuses = Object.values(ReqClaimStatus);
|
||||||
const data: Record<string, number> = { all: 0 };
|
const data: Record<string, number> = { all: 0 };
|
||||||
|
|
||||||
for (const status of statuses) {
|
// For damage_expert, we need to apply the same filtering as expert-claim service
|
||||||
const filter = {
|
if (actor) {
|
||||||
claimStatus: status,
|
// Fetch all requests with the statuses that expert-claim service shows
|
||||||
userClientKey: new Types.ObjectId(client),
|
// This matches the statuses in getClaimRequestsListForExpert
|
||||||
};
|
const relevantStatuses = [
|
||||||
|
ReqClaimStatus.UnChecked,
|
||||||
|
ReqClaimStatus.ReviewRequest,
|
||||||
|
ReqClaimStatus.CheckAgain,
|
||||||
|
ReqClaimStatus.CloseRequest,
|
||||||
|
ReqClaimStatus.InPersonVisit,
|
||||||
|
ReqClaimStatus.CheckedRequest,
|
||||||
|
ReqClaimStatus.PendingFactorValidation,
|
||||||
|
];
|
||||||
|
|
||||||
const count =
|
// Fetch all requests with relevant statuses (matching expert-claim query)
|
||||||
await this.claimRequestManagementDbService.countByFilter(filter);
|
const allRequests =
|
||||||
data[status] = count;
|
await this.claimRequestManagementDbService.findAllByStatus({
|
||||||
data.all += count;
|
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 data;
|
||||||
@@ -174,7 +298,12 @@ export class ReportsService {
|
|||||||
|
|
||||||
async getAllRequestsReportCount(actor, client) {
|
async getAllRequestsReportCount(actor, client) {
|
||||||
if (actor.role === "damage_expert") {
|
if (actor.role === "damage_expert") {
|
||||||
const data = await this.getAllRequestsCountByRole(actor.role, client);
|
// Pass actor to apply filtering logic for damage_expert
|
||||||
|
const data = await this.getAllRequestsCountByRole(
|
||||||
|
actor.role,
|
||||||
|
client,
|
||||||
|
actor,
|
||||||
|
);
|
||||||
return new DamageExpertAllRequestsCountReportDtoRs(data);
|
return new DamageExpertAllRequestsCountReportDtoRs(data);
|
||||||
} else if (actor.role === "expert") {
|
} else if (actor.role === "expert") {
|
||||||
const data = await this.getAllRequestsCountByRole(actor.role, client);
|
const data = await this.getAllRequestsCountByRole(actor.role, client);
|
||||||
|
|||||||
Reference in New Issue
Block a user