import { Body, Controller, Get, HttpException, InternalServerErrorException, Param, Post, Put, Query, UseGuards, } from "@nestjs/common"; import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags, } from "@nestjs/swagger"; import { ExpertFileAssignResultDto } from "src/common/dto/expert-file-assign-result.dto"; 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 { RoleEnum } from "src/Types&Enums/role.enum"; import { ExpertBlameService } from "./expert-blame.service"; import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto"; import { AllRequestDtoRsV2 } from "./dto/all-request.dto"; import { SubmitReplyDto } from "./dto/reply.dto"; import { ResendRequestDto } from "./dto/resend.dto"; @ApiTags("expert-blame-panel (v2)") @Controller("v2/expert-blame") @ApiBearerAuth() @UseGuards(LocalActorAuthGuard, RolesGuard) @Roles(RoleEnum.EXPERT, RoleEnum.FIELD_EXPERT) export class ExpertBlameV2Controller { constructor(private readonly expertBlameService: ExpertBlameService) {} @Get() @ApiOperation({ summary: "List blame cases for field expert (V2)", description: "Optional query: `search`, `sortBy` (publicId | createdAt | requestNo | status), `sortOrder`, `page`, `limit`. Without `page`/`limit`, returns the full filtered list.", }) async findAll( @CurrentUser() actor: any, @Query() query: ListQueryV2Dto, ): Promise { try { return await this.expertBlameService.findAllV2(actor, query); } catch (error) { if (error instanceof HttpException) throw error; throw new InternalServerErrorException( error instanceof Error ? error.message : "Failed to list blame cases", ); } } @Get("report/status-counts") @ApiOperation({ summary: "Count blame cases by grouped status bucket (this field expert)", description: "Counts only files in the acting expert’s portfolio: expertFileActivities (checked or handled), workflow lock, expert decision, or expert-initiated blame. IN_PROGRESS groups OPEN and WAITING_FOR_SECOND_PARTY. Other keys match CaseStatus. Does not include the open WAITING_FOR_EXPERT queue unless this expert has touched the file.", }) 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) { try { return await this.expertBlameService.findOneV2(id, actor); } catch (error) { if (error instanceof HttpException) throw error; throw new InternalServerErrorException( error instanceof Error ? error.message : "Failed to get blame case details", ); } } @Post(":id/assign") @ApiOperation({ summary: "Check availability and assign blame case to this expert", description: "Call before opening a case from the list. Returns `assigned` when the case was free and is now locked to you, `already_assigned_to_you` when you already hold the lock, or **409** with `status=locked` when another expert is reviewing. Does not replace PUT lock — use this for list UI availability.", }) @ApiParam({ name: "id", description: "Blame case request id" }) @ApiResponse({ status: 200, type: ExpertFileAssignResultDto }) @ApiResponse({ status: 409, description: "Another expert is processing this case", type: ExpertFileAssignResultDto, }) async assignForReview(@Param("id") id: string, @CurrentUser() actor: any) { try { return await this.expertBlameService.assignBlameCaseForReviewV2( id, actor, ); } catch (error) { if (error instanceof HttpException) throw error; throw new InternalServerErrorException( error instanceof Error ? error.message : "Failed to assign blame case", ); } } @Put("lock/:id") @ApiOperation({ summary: "Lock blame case for review (legacy)", description: "Same assignment logic as POST `:id/assign`, but returns the legacy `{ _id, lock }` shape and maps conflicts to 400 instead of 409.", deprecated: true, }) @ApiParam({ name: "id", description: "Blame case request id" }) async lockRequest(@Param("id") id: string, @CurrentUser() actor: any) { try { return await this.expertBlameService.lockRequestV2(id, actor); } catch (error) { if (error instanceof HttpException) throw error; throw new InternalServerErrorException( error instanceof Error ? error.message : "Failed to lock blame case", ); } } @Put("reply/resend/:id") @ApiParam({ name: "id", description: "Blame case request id" }) @ApiBody({ type: ResendRequestDto }) async resendRequest( @Param("id") id: string, @Body() body: ResendRequestDto, @CurrentUser() actor: any, ) { try { return await this.expertBlameService.resendRequestV2(id, body, actor); } catch (error) { if (error instanceof HttpException) throw error; throw new InternalServerErrorException( error instanceof Error ? error.message : "Failed to request document resend", ); } } @Put("reply/submit/:id") @ApiParam({ name: "id", description: "Blame case request id" }) @ApiBody({ type: SubmitReplyDto }) async submitReply( @Param("id") id: string, @Body() body: SubmitReplyDto, @CurrentUser() actor: any, ) { try { return await this.expertBlameService.replyRequestV2(id, body, actor); } catch (error) { if (error instanceof HttpException) throw error; throw new InternalServerErrorException( error instanceof Error ? error.message : "Failed to submit expert reply", ); } } @Put("reply/inPerson/:id") @ApiParam({ name: "id", description: "Blame case request id" }) @ApiBody({ type: SubmitReplyDto }) async submitInPerson( @Param("id") id: string, @Body() body: SubmitReplyDto, @CurrentUser() actor: any, ) { try { return await this.expertBlameService.submitInPerson(id, actor.sub); } catch (error) { if (error instanceof HttpException) throw error; throw new InternalServerErrorException( error instanceof Error ? error.message : "Failed to submit expert reply for in person", ); } } }