diff --git a/src/super-admin/super-admin.controller.ts b/src/super-admin/super-admin.controller.ts index d102c56..fc813d4 100644 --- a/src/super-admin/super-admin.controller.ts +++ b/src/super-admin/super-admin.controller.ts @@ -1,18 +1,25 @@ import { + BadRequestException, Body, Controller, Get, + Param, Patch, Post, + Put, + Query, UseGuards, } from "@nestjs/common"; import { ApiBearerAuth, ApiBody, ApiOperation, + ApiParam, + ApiQuery, ApiResponse, ApiTags, } from "@nestjs/swagger"; +import { Types } from "mongoose"; import { SuperAdminGuard } from "./guards/super-admin.guard"; import { SuperAdminService } from "./super-admin.service"; import { @@ -32,6 +39,20 @@ import { UpdateSystemSettingsDto, } from "src/system-settings/dto/system-settings.dto"; import { SetExternalInquiriesLiveDto } from "src/client/dto/external-inquiries-live.dto"; +import { ExpertInsurerService } from "src/expert-insurer/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 { FileRating } from "src/request-management/entities/schema/request-management.schema"; +import { + CreateBlameExpertByInsurerDto, + CreateClaimExpertByInsurerDto, + CreateFileMakerByInsurerDto, + CreateFileReviewerByInsurerDto, +} from "src/expert-insurer/dto/create-insurer-expert.dto"; @ApiTags("super-admin") @ApiBearerAuth() @@ -41,6 +62,7 @@ export class SuperAdminController { constructor( private readonly superAdminService: SuperAdminService, private readonly systemSettingsService: SystemSettingsService, + private readonly expertInsurerService: ExpertInsurerService, ) {} // ── Super-admin account management ─────────────────────────────────────── @@ -159,4 +181,313 @@ export class SuperAdminController { externalApis: { sandHubUseLiveApi: body?.enabled === true }, }); } + + // ── Insurer panel (super-admin proxy) ──────────────────────────────────── + + @Get("insurer/branches") + @ApiOperation({ summary: "List branches for a given client" }) + @ApiQuery({ name: "clientId", required: true, type: String }) + @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)" }) + getInsuranceBranches( + @Query("clientId") clientId: string, + @Query("search") search?: string, + @Query("from") from?: string, + @Query("to") to?: string, + @Query("isActive") isActive?: string, + ) { + return this.expertInsurerService.retrieveInsuranceBranches( + clientId, + { search, from, to, isActive }, + ); + } + + @Post("insurer/branches") + @ApiOperation({ summary: "Add a branch for a given client" }) + @ApiBody({ + schema: { + allOf: [ + { $ref: "#/components/schemas/CreateBranchDto" }, + { + type: "object", + required: ["clientId"], + properties: { clientId: { type: "string", description: "Client ObjectId" } }, + }, + ], + }, + }) + addBranch( + @Body("clientId") clientId: string, + @Body() createBranchDto: CreateBranchDto, + ) { + return this.expertInsurerService.addBranch(clientId, createBranchDto); + } + + @Put("insurer/branches/:branchId/status") + @ApiOperation({ summary: "Set branch active/inactive for a given client" }) + @ApiParam({ name: "branchId" }) + @ApiBody({ + schema: { + type: "object", + properties: { + clientId: { type: "string", description: "Client ObjectId" }, + isActive: { type: "boolean" }, + }, + required: ["clientId", "isActive"], + }, + }) + setBranchStatus( + @Param("branchId") branchId: string, + @Body("clientId") clientId: string, + @Body("isActive") isActive: unknown, + ) { + 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(clientId, branchId, active); + } + + @Post("insurer/experts/blame") + @ApiOperation({ summary: "Create a blame expert under a given client" }) + @ApiBody({ + schema: { + allOf: [ + { $ref: "#/components/schemas/CreateBlameExpertByInsurerDto" }, + { + type: "object", + required: ["clientId"], + properties: { clientId: { type: "string", description: "Client ObjectId" } }, + }, + ], + }, + }) + addBlameExpert( + @Body("clientId") clientId: string, + @Body() body: CreateBlameExpertByInsurerDto, + ) { + return this.expertInsurerService.addBlameExpert(clientId, body); + } + + @Post("insurer/experts/claim") + @ApiOperation({ summary: "Create a claim expert under a given client" }) + @ApiBody({ + schema: { + allOf: [ + { $ref: "#/components/schemas/CreateClaimExpertByInsurerDto" }, + { + type: "object", + required: ["clientId"], + properties: { clientId: { type: "string", description: "Client ObjectId" } }, + }, + ], + }, + }) + addClaimExpert( + @Body("clientId") clientId: string, + @Body() body: CreateClaimExpertByInsurerDto, + ) { + return this.expertInsurerService.addClaimExpert(clientId, body); + } + + @Post("insurer/experts/file-maker") + @ApiOperation({ summary: "Create a FileMaker account under a given client" }) + @ApiBody({ + schema: { + allOf: [ + { $ref: "#/components/schemas/CreateFileMakerByInsurerDto" }, + { + type: "object", + required: ["clientId"], + properties: { clientId: { type: "string", description: "Client ObjectId" } }, + }, + ], + }, + }) + addFileMaker( + @Body("clientId") clientId: string, + @Body() body: CreateFileMakerByInsurerDto, + ) { + return this.expertInsurerService.addFileMaker(clientId, body); + } + + @Post("insurer/experts/file-reviewer") + @ApiOperation({ summary: "Create a FileReviewer account under a given client" }) + @ApiBody({ + schema: { + allOf: [ + { $ref: "#/components/schemas/CreateFileReviewerByInsurerDto" }, + { + type: "object", + required: ["clientId"], + properties: { clientId: { type: "string", description: "Client ObjectId" } }, + }, + ], + }, + }) + addFileReviewer( + @Body("clientId") clientId: string, + @Body() body: CreateFileReviewerByInsurerDto, + ) { + return this.expertInsurerService.addFileReviewer(clientId, body); + } + + @Get("insurer/experts/list") + @ApiOperation({ summary: "List all experts of a given client" }) + @ApiQuery({ name: "clientId", required: true, type: String }) + @ApiQuery({ name: "page", type: Number }) + @ApiQuery({ name: "response_count", type: Number }) + getAllExperts( + @Query("clientId") clientId: string, + @Query("page") page: number, + @Query("response_count") count: number, + ) { + return this.expertInsurerService.retrieveAllExpertsOfClient( + { clientKey: clientId }, + page, + count, + ); + } + + @Get("insurer/experts/top") + @ApiOperation({ summary: "Top blame vs claim experts for a given client" }) + @ApiQuery({ name: "clientId", required: true, type: String }) + getTopExperts(@Query("clientId") clientId: string) { + return this.expertInsurerService.getTopExpertsForClient({ clientKey: clientId }); + } + + @Get("insurer/files") + @ApiOperation({ summary: "List files (blame + claim merged) for a given client" }) + @ApiQuery({ name: "clientId", required: true, type: String }) + getAllFiles( + @Query("clientId") clientId: string, + @Query() query: ListQueryV2Dto, + ) { + return this.expertInsurerService.retrieveAllFilesOfClient(clientId, query); + } + + @Get("insurer/report/unified-file-statuses") + @ApiOperation({ summary: "Unified file status catalog + counts for a given client" }) + @ApiQuery({ name: "clientId", required: true, type: String }) + @ApiResponse({ status: 200, type: UnifiedFileStatusReportDto }) + getUnifiedFileStatusReport( + @Query("clientId") clientId: string, + @Query() query: UnifiedFileStatusReportQueryDto, + ): Promise { + return this.expertInsurerService.getInsurerUnifiedFileStatusReport( + { clientKey: clientId }, + query, + ); + } + + @Get("insurer/report/status-counts") + @ApiOperation({ + summary: "Legacy status counts for a given client", + deprecated: true, + description: "Prefer GET insurer/report/unified-file-statuses.", + }) + @ApiQuery({ name: "clientId", required: true, type: String }) + @ApiQuery({ name: "from", required: false, description: "Optional start datetime (ISO string)" }) + @ApiQuery({ name: "to", required: false, description: "Optional end datetime (ISO string)" }) + getInsurerStatusReport( + @Query("clientId") clientId: string, + @Query("from") from?: string, + @Query("to") to?: string, + ) { + return this.expertInsurerService.getInsurerFileStatusCounts( + { clientKey: clientId }, + from, + to, + ); + } + + @Get("insurer/files/:publicId/timeline") + @ApiOperation({ summary: "Activity timeline for a case of a given client" }) + @ApiParam({ name: "publicId" }) + @ApiQuery({ name: "clientId", required: true, type: String }) + getFileTimeline( + @Query("clientId") clientId: string, + @Param("publicId") publicId: string, + ) { + return this.expertInsurerService.getFileTimeline(clientId, publicId); + } + + @Get("insurer/files/:publicId") + @ApiOperation({ summary: "File details by publicId for a given client" }) + @ApiParam({ name: "publicId" }) + @ApiQuery({ name: "clientId", required: true, type: String }) + getFileDetailsByPublicId( + @Query("clientId") clientId: string, + @Param("publicId") publicId: string, + ) { + return this.expertInsurerService.retrieveFileDetailsByPublicId(clientId, publicId); + } + + // @Put("insurer/files/:publicId/rating") + // @ApiOperation({ summary: "Rate experts by publicId for a given client" }) + // @ApiParam({ name: "publicId" }) + // @ApiBody({ + // schema: { + // type: "object", + // required: [ + // "clientId", + // "collisionMethodAccuracy", + // "evaluationTimeliness", + // "accidentCauseAccuracy", + // "guiltyVehicleIdentification", + // "botRating", + // ], + // properties: { + // clientId: { type: "string", description: "Client ObjectId" }, + // collisionMethodAccuracy: { type: "number", minimum: 0, maximum: 5 }, + // evaluationTimeliness: { type: "number", minimum: 0, maximum: 5 }, + // accidentCauseAccuracy: { type: "number", minimum: 0, maximum: 5 }, + // guiltyVehicleIdentification: { type: "number", minimum: 0, maximum: 5 }, + // botRating: { type: "number", minimum: 0, maximum: 5 }, + // }, + // }, + // }) + // rateExpertsByPublicId( + // @Param("publicId") publicId: string, + // @Body() body: FileRating & { clientId: string }, + // ) { + // const { clientId, ...rating } = body; + // return this.expertInsurerService.rateExpertByPublicId(publicId, rating as FileRating, clientId); + // } + + @Get("insurer/top-files") + @ApiOperation({ summary: "Top-rated files for a given client" }) + @ApiQuery({ name: "clientId", required: true, type: String }) + getTopFiles(@Query("clientId") clientId: string) { + return this.expertInsurerService.getTopFilesForClient(clientId); + } + + @Get("insurer/statistics") + @ApiOperation({ summary: "Expert statistics report for a given client" }) + @ApiQuery({ name: "clientId", required: true, type: String }) + getExpertStatistics(@Query("clientId") clientId: string) { + return this.expertInsurerService.getExpertStatisticsReport({ clientKey: clientId }); + } + + @Get("insurer/:expertId") + @ApiOperation({ summary: "Files handled by one roster expert for a given client" }) + @ApiParam({ name: "expertId" }) + @ApiQuery({ name: "clientId", required: true, type: String }) + getExpertFiles( + @Query("clientId") clientId: string, + @Param("expertId") expertId: string, + ) { + if (!Types.ObjectId.isValid(expertId)) { + throw new BadRequestException("Invalid expert ID"); + } + return this.expertInsurerService.getAllFilesForInsurerExpert(expertId, clientId); + } } diff --git a/src/super-admin/super-admin.module.ts b/src/super-admin/super-admin.module.ts index e175127..49a583b 100644 --- a/src/super-admin/super-admin.module.ts +++ b/src/super-admin/super-admin.module.ts @@ -3,6 +3,7 @@ import { ClientModule } from "src/client/client.module"; import { SystemSettingsModule } from "src/system-settings/system-settings.module"; import { HashModule } from "src/utils/hash/hash.module"; import { UsersModule } from "src/users/users.module"; +import { ExpertInsurerModule } from "src/expert-insurer/expert-insurer.module"; import { SuperAdminController } from "./super-admin.controller"; import { SuperAdminService } from "./super-admin.service"; import { SuperAdminGuard } from "./guards/super-admin.guard"; @@ -26,6 +27,7 @@ import { SuperAdminGuard } from "./guards/super-admin.guard"; SystemSettingsModule, HashModule, UsersModule, + ExpertInsurerModule, ], controllers: [SuperAdminController], providers: [SuperAdminService, SuperAdminGuard],