diff --git a/src/claim-request-management/expert-initiated-claim.mirror.controller.ts b/src/claim-request-management/expert-initiated-claim.mirror.controller.ts index 21e244d..9045b22 100644 --- a/src/claim-request-management/expert-initiated-claim.mirror.controller.ts +++ b/src/claim-request-management/expert-initiated-claim.mirror.controller.ts @@ -4,9 +4,12 @@ import { Body, Controller, Get, + HttpException, + InternalServerErrorException, Param, Patch, Post, + Put, Query, UploadedFile, UseGuards, @@ -516,6 +519,73 @@ Returns status of each item (uploaded/captured or not). ); } + // ─── Owner signature on expert pricing ─────────────────────────────────────── + + @Put("claim-sign/:claimRequestId") + @ApiParam({ name: "claimRequestId" }) + @ApiConsumes("multipart/form-data") + @ApiBody({ + description: "Signature file, agreement, and branch", + schema: { + type: "object", + required: ["sign", "agree", "branchId"], + properties: { + sign: { type: "string", format: "binary", description: "Signature image" }, + agree: { type: "boolean", description: "true to accept, false to reject" }, + branchId: { type: "string", description: "Insurer branch ID" }, + }, + }, + }) + @ApiOperation({ + summary: "Owner signature on expert pricing (Flow 3 — expert acts on behalf of user)", + description: + "Field expert submits the damaged party's signature during the final approval stage. " + + "Delegates to the same service method as the user sign endpoint; the expert's " + + "identity is resolved to the claim owner via `resolveClaimEffectiveUserId`.", + }) + @UseInterceptors( + FileInterceptor("sign", { + limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES }, + storage: diskStorage({ + destination: "./files/claim-sign", + filename: (req, file, callback) => { + const unique = Date.now(); + const ex = extname(file.originalname); + const base = file.originalname.split(/[.,\s-]/)[0] || "sign"; + callback(null, `${base}-${unique}${ex}`); + }, + }), + }), + ) + async submitOwnerSign( + @Param("claimRequestId") claimRequestId: string, + @Body("agree") agree: string | boolean, + @Body("branchId") branchId: string, + @CurrentUser() expert: any, + @UploadedFile() sign: Express.Multer.File, + ) { + await this.mediaPolicyService.assertForClaim(sign, claimRequestId, "image"); + const agreed = + typeof agree === "string" + ? agree === "true" || agree === "1" + : Boolean(agree); + try { + return await this.claimRequestManagementService.submitOwnerInsurerApprovalSignV2( + claimRequestId, + agreed, + typeof branchId === "string" ? branchId : "", + sign, + expert.sub, + expert, + ); + } catch (error) { + if (error instanceof HttpException) throw error; + throw new InternalServerErrorException( + error instanceof Error ? error.message : "Failed to submit signature", + ); + } + } + @Patch("car-capture/:claimRequestId") @ApiConsumes("multipart/form-data") @UseInterceptors( diff --git a/src/request-management/file-reviewer-blame-v4.controller.ts b/src/request-management/file-reviewer-blame-v4.controller.ts index 9eefc09..a95a7be 100644 --- a/src/request-management/file-reviewer-blame-v4.controller.ts +++ b/src/request-management/file-reviewer-blame-v4.controller.ts @@ -1,11 +1,15 @@ import { extname } from "node:path"; import { + BadRequestException, Body, Controller, Get, + HttpException, + InternalServerErrorException, Param, Patch, Post, + Put, UploadedFile, UseGuards, UseInterceptors, @@ -316,6 +320,73 @@ export class FileReviewerBlameV4Controller { ); } + // ─── Owner signature on expert pricing ─────────────────────────────────────── + + @Put("claim-sign/:claimRequestId") + @ApiParam({ name: "claimRequestId" }) + @ApiConsumes("multipart/form-data") + @ApiBody({ + description: "Signature file, agreement, and branch", + schema: { + type: "object", + required: ["sign", "agree", "branchId"], + properties: { + sign: { type: "string", format: "binary", description: "Signature image" }, + agree: { type: "boolean", description: "true to accept, false to reject" }, + branchId: { type: "string", description: "Insurer branch ID" }, + }, + }, + }) + @ApiOperation({ + summary: "Owner signature on expert pricing (V4 — FileReviewer acts on behalf of user)", + description: + "FileReviewer submits the damaged party's signature during the final approval stage. " + + "Delegates to the same service method as the user sign endpoint; the FileReviewer's " + + "identity is resolved to the claim owner via `resolveClaimEffectiveUserId`.", + }) + @UseInterceptors( + FileInterceptor("sign", { + limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES }, + storage: diskStorage({ + destination: "./files/claim-sign", + filename: (req, file, callback) => { + const unique = Date.now(); + const ex = extname(file.originalname); + const base = file.originalname.split(/[.,\s-]/)[0] || "sign"; + callback(null, `${base}-${unique}${ex}`); + }, + }), + }), + ) + async submitOwnerSign( + @Param("claimRequestId") claimRequestId: string, + @Body("agree") agree: string | boolean, + @Body("branchId") branchId: string, + @CurrentUser() fileReviewer: any, + @UploadedFile() sign: Express.Multer.File, + ) { + await this.mediaPolicyService.assertForClaim(sign, claimRequestId, "image"); + const agreed = + typeof agree === "string" + ? agree === "true" || agree === "1" + : Boolean(agree); + try { + return await this.claimRequestManagementService.submitOwnerInsurerApprovalSignV2( + claimRequestId, + agreed, + typeof branchId === "string" ? branchId : "", + sign, + fileReviewer.sub, + fileReviewer, + ); + } catch (error) { + if (error instanceof HttpException) throw error; + throw new InternalServerErrorException( + error instanceof Error ? error.message : "Failed to submit signature", + ); + } + } + // ─── Walk-around video ──────────────────────────────────────────────────────── @Patch("car-capture/:claimRequestId") diff --git a/src/request-management/file-reviewer-blame-v5.controller.ts b/src/request-management/file-reviewer-blame-v5.controller.ts index 8fc401a..2c4f522 100644 --- a/src/request-management/file-reviewer-blame-v5.controller.ts +++ b/src/request-management/file-reviewer-blame-v5.controller.ts @@ -3,9 +3,12 @@ import { Body, Controller, Get, + HttpException, + InternalServerErrorException, Param, Patch, Post, + Put, UploadedFile, UseGuards, UseInterceptors, @@ -315,6 +318,73 @@ export class FileReviewerBlameV5Controller { ); } + // ─── Owner signature on expert pricing ─────────────────────────────────────── + + @Put("claim-sign/:claimRequestId") + @ApiParam({ name: "claimRequestId" }) + @ApiConsumes("multipart/form-data") + @ApiBody({ + description: "Signature file, agreement, and branch", + schema: { + type: "object", + required: ["sign", "agree", "branchId"], + properties: { + sign: { type: "string", format: "binary", description: "Signature image" }, + agree: { type: "boolean", description: "true to accept, false to reject" }, + branchId: { type: "string", description: "Insurer branch ID" }, + }, + }, + }) + @ApiOperation({ + summary: "Owner signature on expert pricing (V5 — FileReviewer acts on behalf of user)", + description: + "FileReviewer submits the damaged party's signature during the final approval stage. " + + "Delegates to the same service method as the user sign endpoint; the FileReviewer's " + + "identity is resolved to the claim owner via `resolveClaimEffectiveUserId`.", + }) + @UseInterceptors( + FileInterceptor("sign", { + limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES }, + storage: diskStorage({ + destination: "./files/claim-sign", + filename: (req, file, callback) => { + const unique = Date.now(); + const ex = extname(file.originalname); + const base = file.originalname.split(/[.,\s-]/)[0] || "sign"; + callback(null, `${base}-${unique}${ex}`); + }, + }), + }), + ) + async submitOwnerSign( + @Param("claimRequestId") claimRequestId: string, + @Body("agree") agree: string | boolean, + @Body("branchId") branchId: string, + @CurrentUser() fileReviewer: any, + @UploadedFile() sign: Express.Multer.File, + ) { + await this.mediaPolicyService.assertForClaim(sign, claimRequestId, "image"); + const agreed = + typeof agree === "string" + ? agree === "true" || agree === "1" + : Boolean(agree); + try { + return await this.claimRequestManagementService.submitOwnerInsurerApprovalSignV2( + claimRequestId, + agreed, + typeof branchId === "string" ? branchId : "", + sign, + fileReviewer.sub, + fileReviewer, + ); + } catch (error) { + if (error instanceof HttpException) throw error; + throw new InternalServerErrorException( + error instanceof Error ? error.message : "Failed to submit signature", + ); + } + } + // ─── Walk-around video ──────────────────────────────────────────────────────── @Patch("car-capture/:claimRequestId")