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 { CreateSuperAdminDto, CreateFieldExpertAdminDto, CreateRegistrarAdminDto, } from "./dto/super-admin.dto"; import { ClientDto } from "src/client/dto/create-client.dto"; import { InsurerRegisterDto, GenuineRegisterDto, LegalRegisterDto, } from "src/auth/dto/actor/register.actor.dto"; import { SystemSettingsService } from "src/system-settings/system-settings.service"; import { SystemSettingsResponseDto, 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() @UseGuards(SuperAdminGuard) @Controller("super-admin") export class SuperAdminController { constructor( private readonly superAdminService: SuperAdminService, private readonly systemSettingsService: SystemSettingsService, private readonly expertInsurerService: ExpertInsurerService, ) {} // ── Super-admin account management ─────────────────────────────────────── @Get("accounts") @ApiOperation({ summary: "List all super-admin accounts" }) listSuperAdmins() { return this.superAdminService.listSuperAdmins(); } @Post("accounts") @ApiOperation({ summary: "Create an additional super-admin account" }) @ApiBody({ type: CreateSuperAdminDto }) createSuperAdmin(@Body() body: CreateSuperAdminDto) { return this.superAdminService.createSuperAdmin(body); } // ── Client / insurer management ────────────────────────────────────────── @Post("clients") @ApiOperation({ summary: "Create a new insurer client", description: "Registers a new insurer (client) in the platform.", }) @ApiBody({ type: ClientDto }) createClient(@Body() body: ClientDto) { return this.superAdminService.createClient(body); } @Get("clients") @ApiOperation({ summary: "List all insurer clients" }) listClients() { return this.superAdminService.listClients(); } @Get("clients/names") @ApiOperation({ summary: "List insurer client names and IDs" }) listClientNames() { return this.superAdminService.listClientNames(); } // ── Actor registration ─────────────────────────────────────────────────── @Post("register/insurer") @ApiOperation({ summary: "Register a new insurer (company) actor" }) @ApiBody({ type: InsurerRegisterDto }) registerInsurer(@Body() body: InsurerRegisterDto) { return this.superAdminService.registerInsurer(body); } @Post("register/genuine") @ApiOperation({ deprecated: true, summary: "[DEPRECATED] Register a genuine actor", description: "Kept for legacy clients. Use the unified onboarding flow instead.", }) @ApiBody({ type: GenuineRegisterDto }) registerGenuine(@Body() body: GenuineRegisterDto) { return this.superAdminService.registerGenuine(body); } @Post("register/legal") @ApiOperation({ deprecated: true, summary: "[DEPRECATED] Register a legal actor", description: "Kept for legacy clients. Use the unified onboarding flow instead.", }) @ApiBody({ type: LegalRegisterDto }) registerLegal(@Body() body: LegalRegisterDto) { return this.superAdminService.registerLegal(body); } @Post("create-field-expert") @ApiOperation({ summary: "Create a field expert" }) @ApiBody({ type: CreateFieldExpertAdminDto }) createFieldExpert(@Body() body: CreateFieldExpertAdminDto) { return this.superAdminService.createFieldExpert(body); } @Post("create-registrar") @ApiOperation({ summary: "Create a registrar" }) @ApiBody({ type: CreateRegistrarAdminDto }) createRegistrar(@Body() body: CreateRegistrarAdminDto) { return this.superAdminService.createRegistrar(body); } // ── System settings ────────────────────────────────────────────────────── @Get("system-settings") @ApiOperation({ summary: "Get global system settings" }) @ApiResponse({ status: 200, type: SystemSettingsResponseDto }) getSystemSettings() { return this.systemSettingsService.getSettingsView(); } @Patch("system-settings") @ApiOperation({ summary: "Update global system settings" }) @ApiBody({ type: UpdateSystemSettingsDto }) @ApiResponse({ status: 200, type: SystemSettingsResponseDto }) updateSystemSettings(@Body() body: UpdateSystemSettingsDto) { return this.systemSettingsService.updateSettings(body); } @Patch("external-inquiries-live") @ApiOperation({ summary: "Enable or disable live external inquiries globally", description: "Updates `system_settings.externalApis.sandHubUseLiveApi`. When disabled, all inquiry flows use mocks.", }) @ApiBody({ type: SetExternalInquiriesLiveDto }) @ApiResponse({ status: 200, type: SystemSettingsResponseDto }) setExternalInquiriesLive(@Body() body: SetExternalInquiriesLiveDto) { return this.systemSettingsService.updateSettings({ 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); } }