forked from Yara724/api
391 lines
13 KiB
TypeScript
391 lines
13 KiB
TypeScript
import { extname } from "node:path";
|
|
import {
|
|
Body,
|
|
Controller,
|
|
Get,
|
|
Param,
|
|
Post,
|
|
Put,
|
|
UploadedFile,
|
|
UseGuards,
|
|
UseInterceptors,
|
|
} from "@nestjs/common";
|
|
import {
|
|
ApiBearerAuth,
|
|
ApiBody,
|
|
ApiConsumes,
|
|
ApiOperation,
|
|
ApiParam,
|
|
ApiTags,
|
|
} from "@nestjs/swagger";
|
|
import { FileInterceptor } from "@nestjs/platform-express";
|
|
import { diskStorage } from "multer";
|
|
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 { MediaPolicyService } from "src/media-policy/media-policy.service";
|
|
import { DEFAULT_MEDIA_MAX_BYTES } from "src/client/client.service";
|
|
import { RoleEnum } from "src/Types&Enums/role.enum";
|
|
import { CreationMethod } from "./entities/schema/request-management.schema";
|
|
import { CreateExpertInitiatedFileDto } from "./dto/expert-initiated.dto";
|
|
import { SendPartyOtpDto, VerifyPartyOtpDto } from "./dto/party-otp.dto";
|
|
import { RunInquiriesV3Dto } from "./dto/run-inquiries-v3.dto";
|
|
import {
|
|
CarBodyFormDto,
|
|
DescriptionDto,
|
|
LocationDto,
|
|
} from "./dto/create-request-management.dto";
|
|
import { RequestManagementService } from "./request-management.service";
|
|
import { PartyRole } from "./entities/schema/partyRole.enum";
|
|
import { ClaimRequestManagementService } from "src/claim-request-management/claim-request-management.service";
|
|
import {
|
|
UploadRequiredDocumentV2Dto,
|
|
UploadRequiredDocumentV2ResponseDto,
|
|
} from "src/claim-request-management/dto/upload-document-v2.dto";
|
|
import { GetCaptureRequirementsV2ResponseDto } from "src/claim-request-management/dto/capture-requirements-v2.dto";
|
|
|
|
/**
|
|
* V5 FileMaker flow — identical to V4 but under the /v5/ prefix.
|
|
*
|
|
* The only behavioural difference in the V5 flow is in the FileReviewer's final
|
|
* step: instead of moving directly to WAITING_FOR_EXPERT, the blame video moves
|
|
* the file to WAITING_FOR_FINANCIAL_EXPERT so a FinancialExpert can approve or
|
|
* reject before fanavaran submission.
|
|
*
|
|
* The FileMaker side is unchanged; all sequence and endpoints are the same.
|
|
*
|
|
* THIRD_PARTY sequence:
|
|
* create → send-party-otp (guilty) → verify-party-otp (guilty)
|
|
* → [car-body-form if CAR_BODY] → run-inquiries (guilty + auto claim)
|
|
* → add-detail-location / add-detail-description / upload-voice (guilty)
|
|
* → sign (guilty)
|
|
* → send-party-otp (damaged) → verify-party-otp (damaged)
|
|
* → run-inquiries (damaged)
|
|
* → add-detail-location / add-detail-description / upload-voice (damaged)
|
|
* → sign (damaged) ← FileMaker job done here
|
|
*/
|
|
@ApiTags("v5 FileMaker — blame file creation (with FinancialExpert approval)")
|
|
@Controller("v5/file-maker/blame-request-management")
|
|
@ApiBearerAuth()
|
|
@UseGuards(LocalActorAuthGuard, RolesGuard)
|
|
@Roles(RoleEnum.FILE_MAKER)
|
|
export class FileMakerBlameV5Controller {
|
|
constructor(
|
|
private readonly requestManagementService: RequestManagementService,
|
|
private readonly mediaPolicyService: MediaPolicyService,
|
|
private readonly claimRequestManagementService: ClaimRequestManagementService,
|
|
) {}
|
|
|
|
// ─── File creation ───────────────────────────────────────────────────────────
|
|
|
|
@Post()
|
|
@ApiOperation({ summary: "Create IN_PERSON blame file (FileMaker V5)" })
|
|
@ApiBody({ type: CreateExpertInitiatedFileDto })
|
|
async create(
|
|
@CurrentUser() fileMaker: any,
|
|
@Body() dto: CreateExpertInitiatedFileDto,
|
|
) {
|
|
return this.requestManagementService.createExpertInitiatedBlameV2(
|
|
fileMaker,
|
|
{ ...dto, creationMethod: CreationMethod.IN_PERSON },
|
|
);
|
|
}
|
|
|
|
@Get("claim-id/:requestId")
|
|
@ApiParam({ name: "requestId", description: "Blame request ID" })
|
|
@ApiOperation({
|
|
summary: "Get linked claim ID",
|
|
description:
|
|
"Returns the claim auto-created during guilty-party run-inquiries. " +
|
|
"Share this claim ID with the FileReviewer.",
|
|
})
|
|
async getLinkedClaimId(
|
|
@Param("requestId") requestId: string,
|
|
@CurrentUser() fileMaker: any,
|
|
) {
|
|
return this.requestManagementService.getV3LinkedClaimForExpert(
|
|
fileMaker,
|
|
requestId,
|
|
);
|
|
}
|
|
|
|
// ─── OTP ─────────────────────────────────────────────────────────────────────
|
|
|
|
@Post("send-party-otp/:requestId")
|
|
@ApiParam({ name: "requestId" })
|
|
@ApiBody({ type: SendPartyOtpDto })
|
|
@ApiOperation({
|
|
summary: "Send OTP to one party",
|
|
description:
|
|
"Guilty party first; damaged party after guilty party has signed (THIRD_PARTY).",
|
|
})
|
|
async sendPartyOtp(
|
|
@CurrentUser() fileMaker: any,
|
|
@Param("requestId") requestId: string,
|
|
@Body() dto: SendPartyOtpDto,
|
|
) {
|
|
return this.requestManagementService.sendPartyOtpV2(
|
|
fileMaker,
|
|
requestId,
|
|
dto,
|
|
);
|
|
}
|
|
|
|
@Post("verify-party-otp/:requestId")
|
|
@ApiParam({ name: "requestId" })
|
|
@ApiBody({ type: VerifyPartyOtpDto })
|
|
@ApiOperation({
|
|
summary: "Verify one party OTP",
|
|
description: "FIRST (guilty) then SECOND (damaged) on THIRD_PARTY files.",
|
|
})
|
|
async verifyPartyOtp(
|
|
@CurrentUser() fileMaker: any,
|
|
@Param("requestId") requestId: string,
|
|
@Body() dto: VerifyPartyOtpDto,
|
|
) {
|
|
return this.requestManagementService.verifyPartyOtpV2(
|
|
fileMaker,
|
|
requestId,
|
|
dto,
|
|
);
|
|
}
|
|
|
|
// ─── Pre-inquiry form ────────────────────────────────────────────────────────
|
|
|
|
@Post("car-body-form/:requestId")
|
|
@ApiParam({ name: "requestId" })
|
|
@ApiBody({ type: CarBodyFormDto })
|
|
@ApiOperation({
|
|
summary: "CAR_BODY only — accident type before inquiries",
|
|
description: "Submit after guilty-party OTP verify, before run-inquiries.",
|
|
})
|
|
async carBodyForm(
|
|
@Param("requestId") requestId: string,
|
|
@Body() body: CarBodyFormDto,
|
|
@CurrentUser() fileMaker: any,
|
|
) {
|
|
return this.requestManagementService.carBodyAccidentTypeFormV3(
|
|
requestId,
|
|
body,
|
|
fileMaker,
|
|
);
|
|
}
|
|
|
|
@Post("run-inquiries/:requestId")
|
|
@ApiParam({ name: "requestId" })
|
|
@ApiBody({ type: RunInquiriesV3Dto })
|
|
@ApiOperation({
|
|
summary: "Run external inquiries for the current party",
|
|
description:
|
|
"1st call = guilty party (+ auto claim). 2nd call = damaged party (THIRD_PARTY, after guilty sign).",
|
|
})
|
|
async runInquiries(
|
|
@CurrentUser() fileMaker: any,
|
|
@Param("requestId") requestId: string,
|
|
@Body() dto: RunInquiriesV3Dto,
|
|
) {
|
|
return this.requestManagementService.runInquiriesV3(
|
|
requestId,
|
|
fileMaker,
|
|
dto,
|
|
);
|
|
}
|
|
|
|
// ─── Party details ────────────────────────────────────────────────────────────
|
|
|
|
@Post("add-detail-location/:requestId")
|
|
@ApiParam({ name: "requestId" })
|
|
@ApiBody({ type: LocationDto })
|
|
@ApiOperation({
|
|
summary: "Add location for current party (partyRole FIRST or SECOND)",
|
|
})
|
|
async addLocation(
|
|
@Param("requestId") requestId: string,
|
|
@Body() body: LocationDto,
|
|
@CurrentUser() fileMaker: any,
|
|
@Body("partyRole") partyRole?: string,
|
|
) {
|
|
return this.requestManagementService.addPartyLocationV3(
|
|
requestId,
|
|
fileMaker,
|
|
body,
|
|
partyRole,
|
|
);
|
|
}
|
|
|
|
@Post("add-detail-description/:requestId")
|
|
@ApiParam({ name: "requestId" })
|
|
@ApiBody({ type: DescriptionDto })
|
|
@ApiOperation({ summary: "Add description for current party" })
|
|
async addDescription(
|
|
@Param("requestId") requestId: string,
|
|
@Body() body: DescriptionDto,
|
|
@CurrentUser() fileMaker: any,
|
|
@Body("partyRole") partyRole?: string,
|
|
) {
|
|
return this.requestManagementService.addPartyDescriptionV3(
|
|
requestId,
|
|
fileMaker,
|
|
body,
|
|
partyRole,
|
|
);
|
|
}
|
|
|
|
@ApiBody({
|
|
schema: {
|
|
type: "object",
|
|
properties: {
|
|
file: { type: "string", format: "binary" },
|
|
partyRole: { type: "string", enum: ["FIRST", "SECOND"] },
|
|
},
|
|
required: ["file"],
|
|
},
|
|
})
|
|
@ApiConsumes("multipart/form-data")
|
|
@UseInterceptors(
|
|
FileInterceptor("file", {
|
|
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
|
|
storage: diskStorage({
|
|
destination: "./files/voice",
|
|
filename: (req, file, callback) => {
|
|
const unique = Date.now();
|
|
const ex = extname(file.originalname);
|
|
callback(null, `v5-filemaker-voice-${unique}${ex}`);
|
|
},
|
|
}),
|
|
}),
|
|
)
|
|
@Post("upload-voice/:requestId")
|
|
@ApiParam({ name: "requestId" })
|
|
@ApiOperation({ summary: "Upload voice for current party" })
|
|
async uploadVoice(
|
|
@Param("requestId") requestId: string,
|
|
@CurrentUser() fileMaker: any,
|
|
@UploadedFile() voice: Express.Multer.File,
|
|
@Body("partyRole") partyRole?: string,
|
|
) {
|
|
await this.mediaPolicyService.assertForBlame(voice, requestId, "voice");
|
|
return this.requestManagementService.addPartyVoiceV3(
|
|
requestId,
|
|
fileMaker,
|
|
voice,
|
|
partyRole,
|
|
);
|
|
}
|
|
|
|
// ─── Signatures (final FileMaker step) ───────────────────────────────────────
|
|
|
|
@Put("sign/:requestId")
|
|
@ApiParam({ name: "requestId" })
|
|
@ApiConsumes("multipart/form-data")
|
|
@ApiOperation({
|
|
summary: "Party on-site signature (FileMaker final step)",
|
|
description:
|
|
"Collect FIRST (guilty) then SECOND (damaged) signatures. " +
|
|
"After the last signature the file is ready for FileReviewer pickup.",
|
|
})
|
|
@ApiBody({
|
|
schema: {
|
|
type: "object",
|
|
required: ["partyRole", "sign"],
|
|
properties: {
|
|
partyRole: { type: "string", enum: ["FIRST", "SECOND"] },
|
|
isAccept: { type: "boolean", default: true },
|
|
sign: { type: "string", format: "binary" },
|
|
},
|
|
},
|
|
})
|
|
@UseInterceptors(
|
|
FileInterceptor("sign", {
|
|
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
|
|
storage: diskStorage({
|
|
destination: "./files/signs",
|
|
filename: (req, file, callback) => {
|
|
const unique = Date.now();
|
|
const ex = extname(file.originalname);
|
|
callback(null, `v5-filemaker-party-${unique}${ex}`);
|
|
},
|
|
}),
|
|
}),
|
|
)
|
|
async sign(
|
|
@Param("requestId") requestId: string,
|
|
@Body() body: { partyRole?: string; isAccept?: string | boolean },
|
|
@CurrentUser() fileMaker: any,
|
|
@UploadedFile() sign: Express.Multer.File,
|
|
) {
|
|
await this.mediaPolicyService.assertForBlame(sign, requestId, "image");
|
|
const partyRole =
|
|
body?.partyRole === "SECOND" ? PartyRole.SECOND : PartyRole.FIRST;
|
|
const isAccept = !(body?.isAccept === false || body?.isAccept === "false");
|
|
return this.requestManagementService.expertUploadPartySignatureV3(
|
|
fileMaker,
|
|
requestId,
|
|
partyRole,
|
|
isAccept,
|
|
sign,
|
|
);
|
|
}
|
|
|
|
// ─── Claim document upload ────────────────────────────────────────────────────
|
|
|
|
@Post("upload-document/:claimRequestId")
|
|
@ApiParam({ name: "claimRequestId" })
|
|
@ApiConsumes("multipart/form-data")
|
|
@UseInterceptors(
|
|
FileInterceptor("file", {
|
|
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
|
|
storage: diskStorage({
|
|
destination: "./files/claim-documents",
|
|
filename: (req, file, callback) => {
|
|
const unique = Date.now();
|
|
const ex = extname(file.originalname);
|
|
callback(null, `${file.originalname.split(".")[0]}-${unique}${ex}`);
|
|
},
|
|
}),
|
|
}),
|
|
)
|
|
@ApiOperation({
|
|
summary: "Upload one required claim document (FileMaker)",
|
|
description:
|
|
"Upload licenses and car cards against the auto-created claim. " +
|
|
"Use claim-id/:requestId to obtain the claimRequestId after run-inquiries.",
|
|
})
|
|
async uploadDocument(
|
|
@Param("claimRequestId") claimRequestId: string,
|
|
@Body() body: UploadRequiredDocumentV2Dto,
|
|
@UploadedFile() file: Express.Multer.File,
|
|
@CurrentUser() fileMaker: any,
|
|
): Promise<UploadRequiredDocumentV2ResponseDto> {
|
|
await this.mediaPolicyService.assertForClaim(file, claimRequestId, "image");
|
|
return this.claimRequestManagementService.uploadRequiredDocumentV3(
|
|
claimRequestId,
|
|
body,
|
|
file,
|
|
fileMaker.sub,
|
|
fileMaker,
|
|
);
|
|
}
|
|
|
|
@Get("capture-requirements/:claimRequestId")
|
|
@ApiParam({ name: "claimRequestId" })
|
|
@ApiOperation({
|
|
summary: "Capture requirements (step-aware)",
|
|
description:
|
|
"Initial documents phase: pre-capture docs only (no chassis/engine/metal plate). " +
|
|
"CAPTURE_PART_DAMAGES phase: damaged parts + angles via capture-part, then chassis/engine/metal plate via upload-document. " +
|
|
"Use `captureSequencePhase` and `captureSequenceHint` to drive the UI.",
|
|
})
|
|
async getCaptureRequirements(
|
|
@Param("claimRequestId") claimRequestId: string,
|
|
@CurrentUser() fileMaker: any,
|
|
): Promise<GetCaptureRequirementsV2ResponseDto> {
|
|
return this.claimRequestManagementService.getCaptureRequirementsV3(
|
|
claimRequestId,
|
|
fileMaker.sub,
|
|
fileMaker,
|
|
);
|
|
}
|
|
}
|