forked from Yara724/api
403 lines
12 KiB
TypeScript
403 lines
12 KiB
TypeScript
import {
|
|
BadRequestException,
|
|
Body,
|
|
Controller,
|
|
Get,
|
|
Param,
|
|
Post,
|
|
Put,
|
|
Query,
|
|
UnauthorizedException,
|
|
UseGuards,
|
|
} from "@nestjs/common";
|
|
import {
|
|
ApiBearerAuth,
|
|
ApiBody,
|
|
ApiOperation,
|
|
ApiParam,
|
|
ApiQuery,
|
|
ApiResponse,
|
|
ApiTags,
|
|
} from "@nestjs/swagger";
|
|
import { Types } from "mongoose";
|
|
import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard";
|
|
import { RolesGuard } from "src/auth/guards/role.guard";
|
|
import { Roles } from "src/decorators/roles.decorator";
|
|
import { CurrentUser } from "src/decorators/user.decorator";
|
|
import { FileRating } from "src/request-management/entities/schema/request-management.schema";
|
|
import { RoleEnum } from "src/Types&Enums/role.enum";
|
|
import { ExpertInsurerService } from "./expert-insurer.service";
|
|
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
|
import {
|
|
UnifiedFileStatusReportDto,
|
|
UnifiedFileStatusReportQueryDto,
|
|
} from "src/common/dto/unified-file-status-report.dto";
|
|
import { CreateBranchDto } from "src/client/dto/create-branch.dto";
|
|
import {
|
|
CreateBlameExpertByInsurerDto,
|
|
CreateClaimExpertByInsurerDto,
|
|
CreateFileMakerByInsurerDto,
|
|
CreateFileReviewerByInsurerDto,
|
|
CreateFinancialExpertByInsurerDto,
|
|
} from "./dto/create-insurer-expert.dto";
|
|
|
|
@Controller("expert-insurer")
|
|
@ApiTags("expert-insurer-panel")
|
|
@ApiBearerAuth()
|
|
@UseGuards(LocalActorAuthGuard, RolesGuard)
|
|
@Roles(RoleEnum.COMPANY)
|
|
export class ExpertInsurerController {
|
|
constructor(private readonly expertInsurerService: ExpertInsurerService) {}
|
|
|
|
@Get("branches")
|
|
@ApiQuery({ name: "search", required: false, type: String })
|
|
@ApiQuery({
|
|
name: "from",
|
|
required: false,
|
|
description: "Optional start datetime (ISO string)",
|
|
})
|
|
@ApiQuery({
|
|
name: "to",
|
|
required: false,
|
|
description: "Optional end datetime (ISO string)",
|
|
})
|
|
@ApiQuery({
|
|
name: "isActive",
|
|
required: false,
|
|
description: "Filter active state (true/false)",
|
|
})
|
|
async getInsuranceBranches(
|
|
@CurrentUser() insurer,
|
|
@Query("search") search?: string,
|
|
@Query("from") from?: string,
|
|
@Query("to") to?: string,
|
|
@Query("isActive") isActive?: string,
|
|
) {
|
|
if (!insurer) {
|
|
throw new UnauthorizedException("Could not identify the current user.");
|
|
}
|
|
return await this.expertInsurerService.retrieveInsuranceBranches(
|
|
insurer.clientKey,
|
|
{ search, from, to, isActive },
|
|
);
|
|
}
|
|
|
|
@Post("branches")
|
|
@ApiBody({ type: CreateBranchDto })
|
|
async addBranch(
|
|
@CurrentUser() insurer,
|
|
@Body() createBranchDto: CreateBranchDto,
|
|
) {
|
|
if (!insurer) {
|
|
throw new UnauthorizedException("Could not identify the current user.");
|
|
}
|
|
|
|
return await this.expertInsurerService.addBranch(
|
|
insurer.clientKey,
|
|
createBranchDto,
|
|
);
|
|
}
|
|
|
|
@Put("branches/:branchId/status")
|
|
@ApiParam({ name: "branchId" })
|
|
@ApiBody({
|
|
schema: {
|
|
type: "object",
|
|
properties: { isActive: { type: "boolean" } },
|
|
required: ["isActive"],
|
|
},
|
|
})
|
|
async setBranchStatus(
|
|
@CurrentUser() insurer,
|
|
@Param("branchId") branchId: string,
|
|
@Body("isActive") isActive: unknown,
|
|
) {
|
|
if (!insurer) {
|
|
throw new UnauthorizedException("Could not identify the current user.");
|
|
}
|
|
// Accept native boolean (JSON body) or string coercion (legacy query/form usage)
|
|
let active: boolean;
|
|
if (typeof isActive === "boolean") {
|
|
active = isActive;
|
|
} else {
|
|
const normalized = String(isActive ?? "").trim().toLowerCase();
|
|
if (!["true", "false", "1", "0", "yes", "no"].includes(normalized)) {
|
|
throw new BadRequestException("isActive must be a boolean");
|
|
}
|
|
active = ["true", "1", "yes"].includes(normalized);
|
|
}
|
|
return this.expertInsurerService.setBranchActive(
|
|
insurer.clientKey,
|
|
branchId,
|
|
active,
|
|
);
|
|
}
|
|
|
|
@Post("experts/blame")
|
|
@ApiBody({ type: CreateBlameExpertByInsurerDto })
|
|
async addBlameExpert(
|
|
@CurrentUser() insurer,
|
|
@Body() body: CreateBlameExpertByInsurerDto,
|
|
) {
|
|
if (!insurer) {
|
|
throw new UnauthorizedException("Could not identify the current user.");
|
|
}
|
|
return this.expertInsurerService.addBlameExpert(insurer.clientKey, body);
|
|
}
|
|
|
|
@Post("experts/claim")
|
|
@ApiBody({ type: CreateClaimExpertByInsurerDto })
|
|
async addClaimExpert(
|
|
@CurrentUser() insurer,
|
|
@Body() body: CreateClaimExpertByInsurerDto,
|
|
) {
|
|
if (!insurer) {
|
|
throw new UnauthorizedException("Could not identify the current user.");
|
|
}
|
|
return this.expertInsurerService.addClaimExpert(insurer.clientKey, body);
|
|
}
|
|
|
|
@Post("experts/file-maker")
|
|
@ApiBody({ type: CreateFileMakerByInsurerDto })
|
|
@ApiOperation({ summary: "Create a FileMaker account under this insurer" })
|
|
async addFileMaker(
|
|
@CurrentUser() insurer,
|
|
@Body() body: CreateFileMakerByInsurerDto,
|
|
) {
|
|
if (!insurer) {
|
|
throw new UnauthorizedException("Could not identify the current user.");
|
|
}
|
|
return this.expertInsurerService.addFileMaker(insurer.clientKey, body);
|
|
}
|
|
|
|
@Post("experts/file-reviewer")
|
|
@ApiBody({ type: CreateFileReviewerByInsurerDto })
|
|
@ApiOperation({ summary: "Create a FileReviewer account under this insurer" })
|
|
async addFileReviewer(
|
|
@CurrentUser() insurer,
|
|
@Body() body: CreateFileReviewerByInsurerDto,
|
|
) {
|
|
if (!insurer) {
|
|
throw new UnauthorizedException("Could not identify the current user.");
|
|
}
|
|
return this.expertInsurerService.addFileReviewer(insurer.clientKey, body);
|
|
}
|
|
|
|
@Post("experts/financial-expert")
|
|
@ApiBody({ type: CreateFinancialExpertByInsurerDto })
|
|
@ApiOperation({
|
|
summary: "Create a FinancialExpert account under this insurer (V5 flow)",
|
|
description:
|
|
"FinancialExperts are the final approvers in the V5 blame flow before fanavaran submission.",
|
|
})
|
|
async addFinancialExpert(
|
|
@CurrentUser() insurer,
|
|
@Body() body: CreateFinancialExpertByInsurerDto,
|
|
) {
|
|
if (!insurer) {
|
|
throw new UnauthorizedException("Could not identify the current user.");
|
|
}
|
|
return this.expertInsurerService.addFinancialExpert(insurer.clientKey, body);
|
|
}
|
|
|
|
@ApiQuery({ name: "page", type: Number })
|
|
@ApiQuery({ name: "response_count", type: Number })
|
|
@Get("experts/list")
|
|
async getAllExperts(
|
|
@Query("page") page: number,
|
|
@Query("response_count") count: number,
|
|
@CurrentUser() actor,
|
|
) {
|
|
return await this.expertInsurerService.retrieveAllExpertsOfClient(
|
|
actor,
|
|
page,
|
|
count,
|
|
);
|
|
}
|
|
|
|
@Get("experts/top")
|
|
@ApiOperation({
|
|
summary: "Top blame vs claim experts for this insurer",
|
|
description:
|
|
"Response has two arrays: `blameExperts` (roster from expert / blame files) and `claimExperts` (damage-expert roster / claim files). Each item includes `overallAverageRating` derived from ratings stored on those files plus user ratings where present.",
|
|
})
|
|
async getTopExperts(@CurrentUser() actor) {
|
|
return await this.expertInsurerService.getTopExpertsForClient(actor);
|
|
}
|
|
|
|
@Get("files")
|
|
@ApiOperation({
|
|
summary: "List insurer files (blame + claim merged by publicId)",
|
|
description:
|
|
"Optional query: `search`, `sortBy` (publicId | createdAt | requestNo | status), `sortOrder`, `page`, `limit`, `unifiedStatus`, `fileType` (THIRD_PARTY | CAR_BODY).",
|
|
})
|
|
async getAllFiles(
|
|
@CurrentUser() insurer,
|
|
@Query() query: ListQueryV2Dto,
|
|
) {
|
|
return await this.expertInsurerService.retrieveAllFilesOfClient(
|
|
insurer.clientKey,
|
|
query,
|
|
);
|
|
}
|
|
|
|
@Get("report/unified-file-statuses")
|
|
@ApiOperation({
|
|
summary: "Unified file status catalog + counts (blame + claim)",
|
|
description:
|
|
"Calculatable unified statuses and per-status counts for this insurer's files. Filter lists with `unifiedStatus` and `fileType`. Optional `from` / `to` ISO dates narrow the counted portfolio.",
|
|
})
|
|
@ApiResponse({ status: 200, type: UnifiedFileStatusReportDto })
|
|
async getUnifiedFileStatusReport(
|
|
@CurrentUser() actor,
|
|
@Query() query: UnifiedFileStatusReportQueryDto,
|
|
): Promise<UnifiedFileStatusReportDto> {
|
|
return await this.expertInsurerService.getInsurerUnifiedFileStatusReport(
|
|
actor,
|
|
query,
|
|
);
|
|
}
|
|
|
|
@Get("files/:publicId")
|
|
@ApiParam({ name: "publicId" })
|
|
async getFileDetailsByPublicId(
|
|
@CurrentUser() insurer,
|
|
@Param("publicId") publicId: string,
|
|
) {
|
|
return await this.expertInsurerService.retrieveFileDetailsByPublicId(
|
|
insurer.clientKey,
|
|
publicId,
|
|
);
|
|
}
|
|
|
|
@ApiBody({
|
|
description:
|
|
"One insurer rating for the shared publicId; persisted on claim and/or blame case documents when both exist.",
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
collisionMethodAccuracy: {
|
|
type: "number",
|
|
minimum: 0,
|
|
maximum: 5,
|
|
example: 4,
|
|
description: "تشخیص درست نحوه برخورد",
|
|
},
|
|
evaluationTimeliness: {
|
|
type: "number",
|
|
minimum: 0,
|
|
maximum: 5,
|
|
example: 3,
|
|
description: "زمان ارزیابی",
|
|
},
|
|
accidentCauseAccuracy: {
|
|
type: "number",
|
|
minimum: 0,
|
|
maximum: 5,
|
|
example: 5,
|
|
description: "تشخیص درست علت تصادف",
|
|
},
|
|
guiltyVehicleIdentification: {
|
|
type: "number",
|
|
minimum: 0,
|
|
maximum: 5,
|
|
example: 4,
|
|
description: "تشخیص درست وسیله نقلیه مقصر",
|
|
},
|
|
botRating: {
|
|
type: "number",
|
|
minimum: 0,
|
|
maximum: 5,
|
|
example: 4,
|
|
description: "برای امتیاز دادن به عملکرد بات",
|
|
},
|
|
},
|
|
required: [
|
|
"collisionMethodAccuracy",
|
|
"evaluationTimeliness",
|
|
"accidentCauseAccuracy",
|
|
"guiltyVehicleIdentification",
|
|
"botRating",
|
|
],
|
|
example: {
|
|
collisionMethodAccuracy: 4,
|
|
evaluationTimeliness: 3,
|
|
accidentCauseAccuracy: 5,
|
|
guiltyVehicleIdentification: 4,
|
|
botRating: 4,
|
|
},
|
|
},
|
|
})
|
|
@ApiParam({ name: "publicId" })
|
|
@Put("files/:publicId/rating")
|
|
async rateExpertsByPublicId(
|
|
@CurrentUser() insurer,
|
|
@Param("publicId") publicId: string,
|
|
@Body() rating: FileRating,
|
|
) {
|
|
return await this.expertInsurerService.rateExpertByPublicId(
|
|
publicId,
|
|
rating,
|
|
insurer.clientKey,
|
|
);
|
|
}
|
|
|
|
@Get("top-files")
|
|
async getTopFiles(@CurrentUser() insurer) {
|
|
return await this.expertInsurerService.getTopFilesForClient(
|
|
insurer.clientKey,
|
|
);
|
|
}
|
|
|
|
@Get("statistics")
|
|
async getExpertStatistics(@CurrentUser() actor) {
|
|
return await this.expertInsurerService.getExpertStatisticsReport(actor);
|
|
}
|
|
|
|
@Get("report/status-counts")
|
|
@ApiOperation({
|
|
summary: "Legacy status counts (mapped from unified statuses)",
|
|
deprecated: true,
|
|
description: "Prefer GET report/unified-file-statuses for full calculatable blame+claim statuses.",
|
|
})
|
|
@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" })
|
|
@ApiOperation({
|
|
summary: "Files handled by one roster expert (summary rows)",
|
|
description:
|
|
"Resolves id against this insurer's `expert` then `damage-expert` roster. Blame branch runs when the id is on the expert roster (field experts). Each item is a small summary (`kind`, ids, statuses, dates)—not full file payloads. Claim matches use final or draft damage-expert reply.",
|
|
})
|
|
@Get("/:expertId")
|
|
async requestDetail(@CurrentUser() insurer, @Param("expertId") id: string) {
|
|
if (!Types.ObjectId.isValid(id)) {
|
|
throw new BadRequestException("Invalid expert ID");
|
|
}
|
|
|
|
return await this.expertInsurerService.getAllFilesForInsurerExpert(
|
|
id,
|
|
insurer.clientKey,
|
|
);
|
|
}
|
|
}
|