Added v3 of field-expert

This commit is contained in:
SepehrYahyaee
2026-06-22 13:05:11 +03:30
parent 89e715b0c9
commit 8f29bb564c
15 changed files with 2988 additions and 622 deletions

View File

@@ -23,6 +23,7 @@ import {
ApiQuery, ApiQuery,
ApiTags, ApiTags,
ApiOperation, ApiOperation,
ApiExcludeController,
} from "@nestjs/swagger"; } from "@nestjs/swagger";
import { diskStorage } from "multer"; import { diskStorage } from "multer";
import { ClaimAccessGuard } from "src/auth/guards/claim-access.guard"; import { ClaimAccessGuard } from "src/auth/guards/claim-access.guard";
@@ -39,8 +40,9 @@ import { UserObjectionDto } from "./dto/user-objection.dto";
import { InPersonVisitDto } from "./dto/in-person-visit.dto"; import { InPersonVisitDto } from "./dto/in-person-visit.dto";
import { UserRatingDto } from "./dto/user-rating.dto"; import { UserRatingDto } from "./dto/user-rating.dto";
@ApiExcludeController()
@Controller("claim-request-management") @Controller("claim-request-management")
@ApiTags("claim-request-management") // @ApiTags("claim-request-management")
@Roles(RoleEnum.USER, RoleEnum.EXPERT, RoleEnum.DAMAGE_EXPERT) @Roles(RoleEnum.USER, RoleEnum.EXPERT, RoleEnum.DAMAGE_EXPERT)
@UseGuards(ClaimAccessGuard, RolesGuard) @UseGuards(ClaimAccessGuard, RolesGuard)
@ApiBearerAuth() @ApiBearerAuth()
@@ -49,8 +51,8 @@ export class ClaimRequestManagementController {
private readonly claimRequestManagementService: ClaimRequestManagementService, private readonly claimRequestManagementService: ClaimRequestManagementService,
) {} ) {}
@ApiParam({ name: "blameId" }) // @ApiParam({ name: "blameId" })
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Post("/:blameId") @Post("/:blameId")
async createClaimRequest( async createClaimRequest(
@Param("blameId") requestId: string, @Param("blameId") requestId: string,
@@ -63,10 +65,10 @@ export class ClaimRequestManagementController {
); );
} }
@ApiBody({ type: CarDamagePartDto }) // @ApiBody({ type: CarDamagePartDto })
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Patch("/car-part-damage/:claimRequestID") @Patch("/car-part-damage/:claimRequestID")
@ApiParam({ name: "claimRequestID" }) // @ApiParam({ name: "claimRequestID" })
async carPartDamage( async carPartDamage(
@Param("claimRequestID") requestId: string, @Param("claimRequestID") requestId: string,
@Body() body: CarDamagePartDto, @Body() body: CarDamagePartDto,
@@ -79,7 +81,7 @@ export class ClaimRequestManagementController {
); );
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get("/car-other-part") @Get("/car-other-part")
async getCarOtherParts() { async getCarOtherParts() {
const carOtherPart = await readFile( const carOtherPart = await readFile(
@@ -89,8 +91,8 @@ export class ClaimRequestManagementController {
return carOtherPart; return carOtherPart;
} }
@ApiBody({ type: OtherCarDamagePartDto }) // @ApiBody({ type: OtherCarDamagePartDto })
@ApiParam({ name: "claimRequestID" }) // @ApiParam({ name: "claimRequestID" })
@UseInterceptors( @UseInterceptors(
FileInterceptor("file", { FileInterceptor("file", {
limits: { limits: {
@@ -108,7 +110,7 @@ export class ClaimRequestManagementController {
}), }),
) )
@ApiConsumes("multipart/form-data") @ApiConsumes("multipart/form-data")
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Patch("/car-other-part-damage/:claimRequestID") @Patch("/car-other-part-damage/:claimRequestID")
async carOtherPartDamage( async carOtherPartDamage(
@Param("claimRequestID") requestId: string, @Param("claimRequestID") requestId: string,
@@ -124,32 +126,32 @@ export class ClaimRequestManagementController {
); );
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get("required-documents-status/:claimRequestID") @Get("required-documents-status/:claimRequestID")
@ApiParam({ name: "claimRequestID" }) // @ApiParam({ name: "claimRequestID" })
async getRequiredDocumentsStatus(@Param("claimRequestID") requestId: string) { async getRequiredDocumentsStatus(@Param("claimRequestID") requestId: string) {
return await this.claimRequestManagementService.getRequiredDocumentsStatus( return await this.claimRequestManagementService.getRequiredDocumentsStatus(
requestId, requestId,
); );
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get("car-part-image-required/:claimRequestID") @Get("car-part-image-required/:claimRequestID")
@ApiParam({ name: "claimRequestID" }) // @ApiParam({ name: "claimRequestID" })
async getImageRequired(@Param("claimRequestID") requestId) { async getImageRequired(@Param("claimRequestID") requestId) {
return await this.claimRequestManagementService.getImageRequiredList( return await this.claimRequestManagementService.getImageRequiredList(
requestId, requestId,
); );
} }
@ApiBody({ // @ApiBody({
schema: { // schema: {
type: "object", // type: "object",
properties: { // properties: {
file: { type: "string", format: "binary" }, // file: { type: "string", format: "binary" },
}, // },
}, // },
}) // })
@UseInterceptors( @UseInterceptors(
FileInterceptor("file", { FileInterceptor("file", {
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES }, limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
@@ -169,14 +171,14 @@ export class ClaimRequestManagementController {
}), }),
}), }),
) )
@ApiConsumes("multipart/form-data") // @ApiConsumes("multipart/form-data")
@ApiParam({ name: "claimRequestID" }) // // @ApiParam({ name: "claimRequestID" })
@ApiQuery({ // @ApiQuery({
name: "documentType", // name: "documentType",
enum: ClaimRequiredDocumentType, // enum: ClaimRequiredDocumentType,
description: "Type of required document to upload", // description: "Type of required document to upload",
}) // })
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Patch("upload-required-document/:claimRequestID") @Patch("upload-required-document/:claimRequestID")
async uploadRequiredDocument( async uploadRequiredDocument(
@Param("claimRequestID") requestId: string, @Param("claimRequestID") requestId: string,
@@ -195,14 +197,14 @@ export class ClaimRequestManagementController {
); );
} }
@ApiBody({ // @ApiBody({
schema: { // schema: {
type: "object", // type: "object",
properties: { // properties: {
file: { type: "string", format: "binary" }, // file: { type: "string", format: "binary" },
}, // },
}, // },
}) // })
@UseInterceptors( @UseInterceptors(
FileInterceptor("file", { FileInterceptor("file", {
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES }, limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
@@ -223,13 +225,13 @@ export class ClaimRequestManagementController {
}), }),
}), }),
) )
@ApiConsumes("multipart/form-data") // @ApiConsumes("multipart/form-data")
@ApiParam({ name: "claimRequestID" }) // @ApiParam({ name: "claimRequestID" })
@ApiParam({ // @ApiParam({
name: "partId", // name: "partId",
description: "The ID of the specific car part being photographed.", // description: "The ID of the specific car part being photographed.",
}) // })
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Patch("capture-car-part-damage/:claimRequestID/:partId") @Patch("capture-car-part-damage/:claimRequestID/:partId")
async captureCarPartDamage( async captureCarPartDamage(
@Param("partId") partId: string, @Param("partId") partId: string,
@@ -246,14 +248,14 @@ export class ClaimRequestManagementController {
); );
} }
@ApiBody({ // @ApiBody({
schema: { // schema: {
type: "object", // type: "object",
properties: { // properties: {
file: { type: "string", format: "binary" }, // file: { type: "string", format: "binary" },
}, // },
}, // },
}) // })
@UseInterceptors( @UseInterceptors(
FileInterceptor("file", { FileInterceptor("file", {
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES }, limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
@@ -268,9 +270,9 @@ export class ClaimRequestManagementController {
}), }),
}), }),
) )
@ApiConsumes("multipart/form-data") // @ApiConsumes("multipart/form-data")
@ApiParam({ name: "claimRequestID" }) // @ApiParam({ name: "claimRequestID" })
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Patch("car-capture/:claimRequestID") @Patch("car-capture/:claimRequestID")
async captureVideoCapture( async captureVideoCapture(
@Param("claimRequestID") requestId: string, @Param("claimRequestID") requestId: string,
@@ -282,22 +284,22 @@ export class ClaimRequestManagementController {
); );
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get("requests/") @Get("requests/")
async getRequest(@CurrentUser() currentUser) { async getRequest(@CurrentUser() currentUser) {
return await this.claimRequestManagementService.myRequests(currentUser); return await this.claimRequestManagementService.myRequests(currentUser);
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get("request/:claimRequestId") @Get("request/:claimRequestId")
@ApiParam({ name: "claimRequestId" }) // @ApiParam({ name: "claimRequestId" })
myRequests(@Param("claimRequestId") requestId: string, @CurrentUser() user) { myRequests(@Param("claimRequestId") requestId: string, @CurrentUser() user) {
return this.claimRequestManagementService.requestDetails(requestId, user); return this.claimRequestManagementService.requestDetails(requestId, user);
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Put("request/reply/:claimRequestId") @Put("request/reply/:claimRequestId")
@ApiParam({ name: "claimRequestId" }) // @ApiParam({ name: "claimRequestId" })
@UseInterceptors( @UseInterceptors(
FileInterceptor("file", { FileInterceptor("file", {
limits: { limits: {
@@ -314,12 +316,12 @@ export class ClaimRequestManagementController {
}), }),
}), }),
) )
@ApiBody({ // @ApiBody({
type: UserCommentDto, // type: UserCommentDto,
description: "if partId null , you can upload video capture", // description: "if partId null , you can upload video capture",
}) // })
@ApiConsumes("multipart/form-data") // @ApiConsumes("multipart/form-data")
@ApiParam({ name: "claimRequestId" }) // @ApiParam({ name: "claimRequestId" })
async submitReply( async submitReply(
@Param("claimRequestId") requestId, @Param("claimRequestId") requestId,
@Body() body, @Body() body,
@@ -334,14 +336,14 @@ export class ClaimRequestManagementController {
// ); // );
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Put("request/resend/:claimRequestId/objection") @Put("request/resend/:claimRequestId/objection")
@ApiParam({ name: "claimRequestId" }) // @ApiParam({ name: "claimRequestId" })
@ApiConsumes("application/json") // @ApiConsumes("application/json")
@ApiBody({ // @ApiBody({
type: UserObjectionDto, // type: UserObjectionDto,
description: "Objection details with optional new parts", // description: "Objection details with optional new parts",
}) // })
async handleUserObjection( async handleUserObjection(
@Param("claimRequestId") claimRequestId: string, @Param("claimRequestId") claimRequestId: string,
@Body() userObjectionDto: UserObjectionDto, @Body() userObjectionDto: UserObjectionDto,
@@ -352,25 +354,25 @@ export class ClaimRequestManagementController {
); );
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Patch("request/resend/:claimRequestId") @Patch("request/resend/:claimRequestId")
@ApiConsumes("multipart/form-data") // @ApiConsumes("multipart/form-data")
@ApiParam({ name: "claimRequestId" }) // @ApiParam({ name: "claimRequestId" })
@ApiQuery({ name: "fields", enum: ["resendDocuments", "resendCarParts"] }) // @ApiQuery({ name: "fields", enum: ["resendDocuments", "resendCarParts"] })
@ApiQuery({ name: "partId", required: false }) // @ApiQuery({ name: "partId", required: false })
@ApiQuery({ name: "documentName", required: false }) // @ApiQuery({ name: "documentName", required: false })
@ApiQuery({ name: "side", required: false }) // @ApiQuery({ name: "side", required: false })
@ApiBody({ // @ApiBody({
schema: { // schema: {
type: "object", // type: "object",
properties: { // properties: {
file: { // file: {
type: "string", // type: "string",
format: "binary", // format: "binary",
}, // },
}, // },
}, // },
}) // })
@UseInterceptors( @UseInterceptors(
FileInterceptor("file", { FileInterceptor("file", {
storage: diskStorage({ storage: diskStorage({
@@ -403,10 +405,10 @@ export class ClaimRequestManagementController {
* User satisfaction rating for a completed claim file. * User satisfaction rating for a completed claim file.
* Only the damaged user (claim owner) can rate their claim after it is closed. * Only the damaged user (claim owner) can rate their claim after it is closed.
*/ */
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Put("request/:claimRequestId/user-rating") @Put("request/:claimRequestId/user-rating")
@ApiParam({ name: "claimRequestId" }) // @ApiParam({ name: "claimRequestId" })
@ApiBody({ type: UserRatingDto }) // @ApiBody({ type: UserRatingDto })
async addUserRating( async addUserRating(
@Param("claimRequestId") claimRequestId: string, @Param("claimRequestId") claimRequestId: string,
@Body() ratingDto: UserRatingDto, @Body() ratingDto: UserRatingDto,
@@ -419,22 +421,22 @@ export class ClaimRequestManagementController {
); );
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Patch("request/reply/:claimRequestId/:partId/upload-factor") @Patch("request/reply/:claimRequestId/:partId/upload-factor")
@ApiConsumes("multipart/form-data") // @ApiConsumes("multipart/form-data")
@ApiParam({ name: "claimRequestId" }) // @ApiParam({ name: "claimRequestId" })
@ApiParam({ name: "partId" }) // @ApiParam({ name: "partId" })
@ApiBody({ // @ApiBody({
schema: { // schema: {
type: "object", // type: "object",
properties: { // properties: {
file: { // file: {
type: "string", // type: "string",
format: "binary", // format: "binary",
}, // },
}, // },
}, // },
}) // })
@UseInterceptors( @UseInterceptors(
FileInterceptor("file", { FileInterceptor("file", {
storage: diskStorage({ storage: diskStorage({
@@ -462,9 +464,9 @@ export class ClaimRequestManagementController {
); );
} }
@ApiBody({ type: InPersonVisitDto }) // @ApiBody({ type: InPersonVisitDto })
@ApiParam({ name: "id" }) // @ApiParam({ name: "id" })
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Patch(":id/visit") @Patch(":id/visit")
async inPersonVisit( async inPersonVisit(
@Param("id") requestId: string, @Param("id") requestId: string,
@@ -479,26 +481,25 @@ export class ClaimRequestManagementController {
); );
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get("branches/:insuranceId") @Get("branches/:insuranceId")
// @ApiParam({ name: "insuranceId" })
async insuranceBranches(@Param("insuranceId") insuranceId: string) { async insuranceBranches(@Param("insuranceId") insuranceId: string) {
return await this.claimRequestManagementService.retrieveInsuranceBranches( return await this.claimRequestManagementService.retrieveInsuranceBranches(insuranceId);
insuranceId,
);
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get("fanavaran-submit/:claimRequestId") @Get("fanavaran-submit/:claimRequestId")
@ApiParam({ name: "claimRequestId" }) // @ApiParam({ name: "claimRequestId" })
async fanavaranSubmit(@Param("claimRequestId") claimRequestId: string) { async fanavaranSubmit(@Param("claimRequestId") claimRequestId: string) {
return await this.claimRequestManagementService.fanavaranSubmit( return await this.claimRequestManagementService.fanavaranSubmit(
claimRequestId, claimRequestId,
); );
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Post("fanavaran-submit/:claimRequestId") @Post("fanavaran-submit/:claimRequestId")
@ApiParam({ name: "claimRequestId" }) // @ApiParam({ name: "claimRequestId" })
async submitToFanavaran(@Param("claimRequestId") claimRequestId: string) { async submitToFanavaran(@Param("claimRequestId") claimRequestId: string) {
return await this.claimRequestManagementService.submitToFanavaran( return await this.claimRequestManagementService.submitToFanavaran(
claimRequestId, claimRequestId,

View File

@@ -43,6 +43,7 @@ import {
SelectOtherPartsV2Dto, SelectOtherPartsV2Dto,
SelectOtherPartsV2ResponseDto, SelectOtherPartsV2ResponseDto,
} from "./dto/select-other-parts-v2.dto"; } from "./dto/select-other-parts-v2.dto";
import { SelectOtherPartsV3Dto } from "./dto/select-other-parts-v3.dto";
import { GetCaptureRequirementsV2ResponseDto } from "./dto/capture-requirements-v2.dto"; import { GetCaptureRequirementsV2ResponseDto } from "./dto/capture-requirements-v2.dto";
import { import {
UploadRequiredDocumentV2Dto, UploadRequiredDocumentV2Dto,
@@ -82,6 +83,7 @@ import { ClaimRequiredDocumentType } from "src/Types&Enums/claim-request-managem
import { ClaimStatus } from "src/Types&Enums/claim-request-management/claimStatus.enum"; import { ClaimStatus } from "src/Types&Enums/claim-request-management/claimStatus.enum";
import { ClaimWorkflowStep } from "src/Types&Enums/claim-request-management/claim-workflow-steps.enum"; import { ClaimWorkflowStep } from "src/Types&Enums/claim-request-management/claim-workflow-steps.enum";
import { CaseStatus as BlameCaseStatus } from "src/Types&Enums/blame-request-management/caseStatus.enum"; import { CaseStatus as BlameCaseStatus } from "src/Types&Enums/blame-request-management/caseStatus.enum";
import { WorkflowStep } from "src/Types&Enums/blame-request-management/blameWorkflow-steps.enum";
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum"; import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
import { ClaimSignDbService } from "./entites/db-service/claim-sign.db.service"; import { ClaimSignDbService } from "./entites/db-service/claim-sign.db.service";
import { DamageImageDbService } from "./entites/db-service/damage-image.db.service"; import { DamageImageDbService } from "./entites/db-service/damage-image.db.service";
@@ -5060,6 +5062,101 @@ export class ClaimRequestManagementService {
}; };
} }
/**
* V2: Registrar creates a claim from a registrar-initiated IN_PERSON completed blame.
* Mirrors createClaimFromBlameForRegistrarV1 but uses V2 naming conventions.
*/
async createClaimFromBlameForRegistrarV2(
blameRequestId: string,
registrar: { sub: string; firstName?: string; lastName?: string },
): Promise<CreateClaimFromBlameResponseDto> {
const blameRequest =
await this.blameRequestDbService.findById(blameRequestId);
if (!blameRequest) throw new NotFoundException("Blame request not found");
if (blameRequest.status !== BlameCaseStatus.COMPLETED) {
throw new BadRequestException(
`Blame request must be COMPLETED. Current status: ${blameRequest.status}`,
);
}
if (
!blameRequest.registrarInitiated ||
blameRequest.creationMethod !== "IN_PERSON" ||
!blameRequest.initiatedByRegistrarId
) {
throw new BadRequestException(
"This endpoint is only for registrar-initiated IN_PERSON blame files.",
);
}
if (String(blameRequest.initiatedByRegistrarId) !== String(registrar.sub)) {
throw new ForbiddenException(
"Only the registrar who created this blame file can create the claim.",
);
}
const ownerFields = resolveClaimOwnerFieldsFromBlame(blameRequest);
if (!ownerFields) {
throw new BadRequestException(
"Could not resolve claim owner (damaged party) from blame",
);
}
const existingClaim = await this.claimCaseDbService.findOne({
blameRequestId: new Types.ObjectId(blameRequestId),
});
if (existingClaim)
throw new ConflictException("A claim for this blame case already exists");
const claimNo = await this.generateUniqueClaimNumber();
const newClaim = await this.claimCaseDbService.create({
requestNo: claimNo,
publicId: blameRequest.publicId,
blameRequestId: new Types.ObjectId(blameRequestId),
blameRequestNo: blameRequest.requestNo,
status: ClaimCaseStatus.SELECTING_OUTER_PARTS,
claimStatus: ClaimStatus.PENDING,
inquiries: (blameRequest as any).inquiries ?? {},
workflow: {
currentStep: ClaimWorkflowStep.SELECT_OUTER_PARTS,
nextStep: ClaimWorkflowStep.SELECT_OTHER_PARTS,
completedSteps: [ClaimWorkflowStep.CLAIM_CREATED],
locked: false,
},
damagedPartyUserId: new Types.ObjectId(ownerFields.userId),
owner: {
userId: new Types.ObjectId(ownerFields.userId),
userRole: ownerFields.userRole as any,
...(ownerFields.clientId
? {
clientId: new Types.ObjectId(ownerFields.clientId),
userClientKey: ownerFields.clientId,
}
: {}),
},
createdByRegistrarId: new Types.ObjectId(registrar.sub),
history: [
{
type: "CLAIM_CREATED",
actor: {
actorId: new Types.ObjectId(registrar.sub),
actorName:
`${registrar.firstName || ""} ${registrar.lastName || ""}`.trim(),
actorType: "registrar",
},
timestamp: new Date(),
metadata: {
blameRequestId,
blamePublicId: blameRequest.publicId,
createdByRegistrar: true,
},
},
],
} as any);
return {
claimRequestId: String(newClaim._id),
publicId: blameRequest.publicId,
status: ClaimCaseStatus.SELECTING_OUTER_PARTS,
message:
"Claim created successfully. Registrar can now fill claim data on behalf of the damaged party.",
};
}
/** /**
* V2 API: Select damaged outer car parts for claim (Step 2 of claim workflow) * V2 API: Select damaged outer car parts for claim (Step 2 of claim workflow)
* *
@@ -7660,4 +7757,405 @@ export class ClaimRequestManagementService {
const randomPart = Math.floor(10000 + Math.random() * 90000); // 5 digits const randomPart = Math.floor(10000 + Math.random() * 90000); // 5 digits
return `${prefix}${randomPart}`; return `${prefix}${randomPart}`;
} }
private async loadBlameForV3Claim(claimCase: any) {
if (!claimCase?.blameRequestId) return null;
return this.blameRequestDbService.findById(
claimCase.blameRequestId.toString(),
);
}
private hasPartyInquiriesOnBlame(blame: any, role: "FIRST" | "SECOND"): boolean {
const step =
role === "FIRST"
? WorkflowStep.FIRST_INITIAL_FORM
: WorkflowStep.SECOND_INITIAL_FORM;
return (blame.workflow?.completedSteps ?? []).includes(step);
}
private partyHasSignedOnBlame(blame: any, role: "FIRST" | "SECOND"): boolean {
const party = (blame.parties ?? []).find((p: any) => p?.role === role);
return !!party?.confirmation;
}
private assertV3ClaimDocumentsPhase(blame: any): void {
if (!(blame.expert?.decision as any)?.fields?.accidentWay) {
throw new BadRequestException(
"Submit accident fields before uploading required documents.",
);
}
const isCarBody =
blame?.type === BlameRequestType.CAR_BODY || blame?.type === "CAR_BODY";
if (isCarBody) {
if (!this.partyHasSignedOnBlame(blame, "FIRST")) {
throw new BadRequestException(
"Upload documents after the party has signed.",
);
}
return;
}
if (
!this.partyHasSignedOnBlame(blame, "FIRST") ||
!this.partyHasSignedOnBlame(blame, "SECOND")
) {
throw new BadRequestException(
"Both parties must sign before uploading required documents.",
);
}
}
private assertV3ClaimPartsPhase(blame: any): void {
if (!(blame.expert?.decision as any)?.fields?.accidentWay) {
throw new BadRequestException(
"Submit accident fields before part selection.",
);
}
const isCarBody =
blame?.type === BlameRequestType.CAR_BODY || blame?.type === "CAR_BODY";
if (isCarBody) {
if (!this.partyHasSignedOnBlame(blame, "FIRST")) {
throw new BadRequestException(
"Part selection requires the party signature.",
);
}
return;
}
if (
!this.partyHasSignedOnBlame(blame, "FIRST") ||
!this.partyHasSignedOnBlame(blame, "SECOND")
) {
throw new BadRequestException(
"Both parties must sign before part selection.",
);
}
}
private claimV3StepCompleted(
claimCase: any,
step: ClaimWorkflowStep,
): boolean {
return (claimCase.workflow?.completedSteps ?? []).includes(step);
}
private assertV3ClaimWorkflowStep(
claimCase: any,
expected: ClaimWorkflowStep | ClaimWorkflowStep[],
hint?: string,
): void {
const allowed = Array.isArray(expected) ? expected : [expected];
const current = claimCase.workflow?.currentStep;
if (!allowed.includes(current)) {
throw new BadRequestException(
hint ??
`Invalid workflow step. Expected ${allowed.join(" or ")}, but current step is ${current}`,
);
}
}
private async advanceV3ClaimToOuterPartsIfReady(
claimCase: any,
blame: any,
): Promise<void> {
if (claimCase.workflow?.currentStep === ClaimWorkflowStep.SELECT_OUTER_PARTS) {
return;
}
if (claimCase.workflow?.currentStep !== ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS) {
throw new BadRequestException(
`Complete required documents before selecting outer parts. Current step: ${claimCase.workflow?.currentStep}`,
);
}
if (!(blame.expert?.decision as any)?.fields?.accidentWay) {
throw new BadRequestException(
"Submit accident fields before selecting outer parts.",
);
}
await this.claimCaseDbService.findByIdAndUpdate(String(claimCase._id), {
$set: {
status: ClaimCaseStatus.SELECTING_OUTER_PARTS,
"workflow.currentStep": ClaimWorkflowStep.SELECT_OUTER_PARTS,
"workflow.nextStep": ClaimWorkflowStep.SELECT_OTHER_PARTS,
},
});
}
private async assertV3InPersonClaim(claimCase: any) {
const blame = await this.loadBlameForV3Claim(claimCase);
if (!blame) {
throw new BadRequestException("V3 claim flow requires a linked blame file.");
}
if (blame.creationMethod !== CreationMethod.IN_PERSON) {
throw new BadRequestException("V3 claim flow is only for IN_PERSON blame files.");
}
if (!blame.expertInitiated) {
throw new BadRequestException("V3 claim flow requires an expert-initiated blame file.");
}
return blame;
}
async uploadRequiredDocumentV3(
claimRequestId: string,
body: UploadRequiredDocumentV2Dto,
file: Express.Multer.File,
currentUserId: string,
actor?: { sub: string; role?: string },
): Promise<UploadRequiredDocumentV2ResponseDto> {
const claimCase = await this.claimCaseDbService.findById(claimRequestId);
if (!claimCase) {
throw new NotFoundException(`Claim case with ID ${claimRequestId} not found`);
}
const blame = await this.assertV3InPersonClaim(claimCase);
this.assertV3ClaimDocumentsPhase(blame);
const step = claimCase.workflow?.currentStep;
if (step !== ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS) {
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
$set: {
status: ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS,
"workflow.currentStep": ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
},
});
}
return this.uploadRequiredDocumentV2(
claimRequestId,
body,
file,
currentUserId,
actor,
);
}
async getCaptureRequirementsV3(
claimRequestId: string,
currentUserId: string,
actor?: { sub: string; role?: string },
): Promise<GetCaptureRequirementsV2ResponseDto> {
const claimCase = await this.claimCaseDbService.findById(claimRequestId);
if (!claimCase) {
throw new NotFoundException(`Claim case with ID ${claimRequestId} not found`);
}
const blame = await this.assertV3InPersonClaim(claimCase);
this.assertV3ClaimDocumentsPhase(blame);
return this.getCaptureRequirementsV2(claimRequestId, currentUserId, actor);
}
async selectOuterPartsV3(
claimRequestId: string,
body: SelectOuterPartsV2Dto,
currentUserId: string,
actor?: { sub: string; role?: string },
): Promise<SelectOuterPartsV2ResponseDto> {
const claimCase = await this.claimCaseDbService.findById(claimRequestId);
if (!claimCase) {
throw new NotFoundException(`Claim case with ID ${claimRequestId} not found`);
}
const blame = await this.assertV3InPersonClaim(claimCase);
this.assertV3ClaimPartsPhase(blame);
await this.advanceV3ClaimToOuterPartsIfReady(claimCase, blame);
const refreshed = await this.claimCaseDbService.findById(claimRequestId);
this.assertV3ClaimWorkflowStep(
refreshed,
ClaimWorkflowStep.SELECT_OUTER_PARTS,
"Select outer parts after accident fields and required documents.",
);
return this.selectOuterPartsV2(claimRequestId, body, currentUserId, actor);
}
async selectOtherPartsV3(
claimRequestId: string,
body: SelectOtherPartsV3Dto,
currentUserId: string,
actor?: { sub: string; role?: string },
file?: Express.Multer.File,
): Promise<SelectOtherPartsV2ResponseDto> {
const claimCase = await this.claimCaseDbService.findById(claimRequestId);
if (!claimCase) {
throw new NotFoundException(`Claim case with ID ${claimRequestId} not found`);
}
const blame = await this.assertV3InPersonClaim(claimCase);
this.assertV3ClaimPartsPhase(blame);
this.assertV3ClaimWorkflowStep(
claimCase,
ClaimWorkflowStep.SELECT_OTHER_PARTS,
"Select outer parts before other parts.",
);
if (!this.claimV3StepCompleted(claimCase, ClaimWorkflowStep.SELECT_OUTER_PARTS)) {
throw new BadRequestException(
"Complete outer part selection before selecting other parts.",
);
}
let otherParts = Array.isArray((body as any)?.otherParts)
? (body as any).otherParts
: [];
if (
!Array.isArray((body as any)?.otherParts) &&
typeof (body as any)?.otherParts === "string"
) {
try {
const parsed = JSON.parse((body as any).otherParts);
otherParts = Array.isArray(parsed) ? parsed : [];
} catch {
otherParts = [];
}
}
const shebaNumber = claimCase.money?.sheba;
const nationalCode = claimCase.money?.nationalCodeOfInsurer;
if (!shebaNumber || !nationalCode) {
throw new BadRequestException(
"Bank information is missing on the claim. Complete run-inquiries for both parties first.",
);
}
const updatePayload: any = {
"damage.otherParts": otherParts.length > 0 ? otherParts : undefined,
status: ClaimCaseStatus.CAPTURING_PART_DAMAGES,
"workflow.currentStep": ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
"workflow.nextStep": ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
$push: {
"workflow.completedSteps": ClaimWorkflowStep.SELECT_OTHER_PARTS,
history: {
type: "V3_STEP_COMPLETED",
actor: {
actorId: new Types.ObjectId(currentUserId),
actorName: claimCase.owner?.fullName || "Actor",
actorType: actor?.role === RoleEnum.REGISTRAR ? "registrar" : "field_expert",
},
timestamp: new Date(),
metadata: {
stepKey: ClaimWorkflowStep.SELECT_OTHER_PARTS,
otherParts,
v3SelectionOnly: true,
},
},
},
};
if (file) {
const docRef = await this.claimRequiredDocumentDbService.create({
path: file.path,
fileName: file.filename,
claimId: new Types.ObjectId(claimRequestId),
documentType: ClaimRequiredDocumentType.CAR_GREEN_CARD,
uploadedAt: new Date(),
});
updatePayload[`requiredDocuments.${ClaimRequiredDocumentType.CAR_GREEN_CARD}`] = {
fileId: docRef._id,
filePath: file.path,
fileName: file.filename,
uploaded: true,
uploadedAt: new Date(),
};
}
const updatedClaim = await this.claimCaseDbService.findByIdAndUpdate(
claimRequestId,
updatePayload,
);
if (!updatedClaim) {
throw new InternalServerErrorException("Failed to update claim case");
}
return {
claimRequestId: updatedClaim._id.toString(),
publicId: updatedClaim.publicId,
otherParts,
shebaNumber: String(shebaNumber).replace(/^(.{4})(.*)(.{4})$/, "IR$1************$3"),
nationalCodeOfOwner: String(nationalCode).replace(/^(.{2})(.*)(.{2})$/, "$1******$3"),
currentStep: ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
nextStep: ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
message:
"Other parts saved. Proceed to damaged-part photos and car angles (capture-part).",
};
}
async setVideoCaptureV3(
claimRequestId: string,
fileDetail: Express.Multer.File,
currentUserId: string,
actor?: { sub: string; role?: string },
): Promise<VideoCaptureV2ResponseDto> {
const claimCase = await this.claimCaseDbService.findById(claimRequestId);
if (!claimCase) {
throw new NotFoundException(`Claim case with ID ${claimRequestId} not found`);
}
await this.assertV3InPersonClaim(claimCase);
if (!fileDetail) {
throw new BadRequestException("Video file is required.");
}
if (claimCase.media?.videoCaptureId) {
throw new ConflictException(
"A walk-around video has already been uploaded for this claim.",
);
}
if (
claimCase.workflow?.currentStep !== ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS
) {
throw new BadRequestException(
`Complete all part/angle captures first. Expected ${ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS}, but current step is ${claimCase.workflow?.currentStep}`,
);
}
const captureProgress = getClaimCaptureProgress(claimCase);
if (!captureProgress.partsComplete) {
throw new BadRequestException(
"Upload photos for all selected damaged parts before the walk-around video.",
);
}
if (!captureProgress.anglesComplete) {
throw new BadRequestException(
"Capture all four car angles before the walk-around video.",
);
}
if (!isClaimCaptureStepComplete(claimCase)) {
throw new BadRequestException(
"Upload capture-phase vehicle documents (chassis, engine, metal plate) before the walk-around video.",
);
}
return this.setVideoCaptureV2(
claimRequestId,
fileDetail,
currentUserId,
actor,
);
}
async capturePartV3(
claimRequestId: string,
body: CapturePartV2Dto,
file: Express.Multer.File,
currentUserId: string,
actor?: { sub: string; role?: string },
): Promise<CapturePartV2ResponseDto> {
const claimCase = await this.claimCaseDbService.findById(claimRequestId);
if (!claimCase) {
throw new NotFoundException(`Claim case with ID ${claimRequestId} not found`);
}
const blame = await this.assertV3InPersonClaim(claimCase);
this.assertV3ClaimPartsPhase(blame);
this.assertV3ClaimWorkflowStep(
claimCase,
ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
"Select other parts before capturing damaged parts and angles.",
);
if (!this.claimV3StepCompleted(claimCase, ClaimWorkflowStep.SELECT_OTHER_PARTS)) {
throw new BadRequestException(
"Complete other part selection before capture.",
);
}
return this.capturePartV2(
claimRequestId,
body,
file,
currentUserId,
actor,
);
}
} }

View File

@@ -0,0 +1,29 @@
import { ApiPropertyOptional } from "@nestjs/swagger";
import {
ArrayMaxSize,
IsArray,
IsEnum,
IsOptional,
} from "class-validator";
import { OtherCarPart } from "./select-other-parts-v2.dto";
/**
* V3 in-person expert flow: other parts only (sheba/national code collected during run-inquiries).
*/
export class SelectOtherPartsV3Dto {
@ApiPropertyOptional({
description: "Array of selected other damaged parts (non-body parts)",
example: ["engine", "suspension", "headlight"],
enum: OtherCarPart,
isArray: true,
maxItems: 11,
})
@IsOptional()
@IsArray({ message: "otherParts must be an array" })
@ArrayMaxSize(11, { message: "Maximum 11 other parts can be selected" })
@IsEnum(OtherCarPart, {
each: true,
message: "Invalid part name. Must be one of the valid other car parts",
})
otherParts?: OtherCarPart[];
}

View File

@@ -2,7 +2,7 @@ import { Body, Controller, Get, Param, Patch, Post, UploadedFile, UseGuards, Use
import { FileInterceptor } from "@nestjs/platform-express"; import { FileInterceptor } from "@nestjs/platform-express";
import { diskStorage } from "multer"; import { diskStorage } from "multer";
import { extname } from "path"; import { extname } from "path";
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiOperation, ApiParam, ApiTags } from "@nestjs/swagger"; import { ApiBearerAuth, ApiConsumes, ApiExcludeController } from "@nestjs/swagger";
import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard"; import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard";
import { RolesGuard } from "src/auth/guards/role.guard"; import { RolesGuard } from "src/auth/guards/role.guard";
import { Roles } from "src/decorators/roles.decorator"; import { Roles } from "src/decorators/roles.decorator";
@@ -15,7 +15,8 @@ import { UploadRequiredDocumentV2Dto } from "./dto/upload-document-v2.dto";
import { CapturePartV2Dto } from "./dto/capture-part-v2.dto"; import { CapturePartV2Dto } from "./dto/capture-part-v2.dto";
import { ClaimRequestManagementService } from "./claim-request-management.service"; import { ClaimRequestManagementService } from "./claim-request-management.service";
@ApiTags("registrar-claim (v1)") @ApiExcludeController()
// @ApiTags("registrar-claim (v1)")
@Controller("registrar-claim") @Controller("registrar-claim")
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(LocalActorAuthGuard, RolesGuard) @UseGuards(LocalActorAuthGuard, RolesGuard)
@@ -24,7 +25,7 @@ export class RegistrarClaimV1Controller {
constructor(private readonly claimRequestManagementService: ClaimRequestManagementService) {} constructor(private readonly claimRequestManagementService: ClaimRequestManagementService) {}
@Post("create-from-blame/:blameRequestId") @Post("create-from-blame/:blameRequestId")
@ApiParam({ name: "blameRequestId" }) // @ApiParam({ name: "blameRequestId" })
createFromBlame(@Param("blameRequestId") blameRequestId: string, @CurrentUser() registrar: any) { createFromBlame(@Param("blameRequestId") blameRequestId: string, @CurrentUser() registrar: any) {
return this.claimRequestManagementService.createClaimFromBlameForRegistrarV1( return this.claimRequestManagementService.createClaimFromBlameForRegistrarV1(
blameRequestId, blameRequestId,
@@ -42,7 +43,7 @@ export class RegistrarClaimV1Controller {
} }
@Patch("select-outer-parts/:claimRequestId") @Patch("select-outer-parts/:claimRequestId")
@ApiBody({ type: SelectOuterPartsV2Dto }) // @ApiBody({ type: SelectOuterPartsV2Dto })
selectOuter( selectOuter(
@Param("claimRequestId") claimRequestId: string, @Param("claimRequestId") claimRequestId: string,
@Body() body: SelectOuterPartsV2Dto, @Body() body: SelectOuterPartsV2Dto,
@@ -57,7 +58,7 @@ export class RegistrarClaimV1Controller {
} }
@Patch("select-other-parts/:claimRequestId") @Patch("select-other-parts/:claimRequestId")
@ApiBody({ type: SelectOtherPartsV2Dto }) // @ApiBody({ type: SelectOtherPartsV2Dto })
selectOther( selectOther(
@Param("claimRequestId") claimRequestId: string, @Param("claimRequestId") claimRequestId: string,
@Body() body: SelectOtherPartsV2Dto, @Body() body: SelectOtherPartsV2Dto,
@@ -91,7 +92,7 @@ export class RegistrarClaimV1Controller {
}), }),
}), }),
) )
@ApiOperation({ summary: "Registrar uploads required claim document" }) // @ApiOperation({ summary: "Registrar uploads required claim document" })
uploadDoc( uploadDoc(
@Param("claimRequestId") claimRequestId: string, @Param("claimRequestId") claimRequestId: string,
@Body() body: UploadRequiredDocumentV2Dto, @Body() body: UploadRequiredDocumentV2Dto,
@@ -108,7 +109,7 @@ export class RegistrarClaimV1Controller {
} }
@Post("capture-part/:claimRequestId") @Post("capture-part/:claimRequestId")
@ApiConsumes("multipart/form-data") // @ApiConsumes("multipart/form-data")
@UseInterceptors( @UseInterceptors(
FileInterceptor("file", { FileInterceptor("file", {
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES }, limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },

View File

@@ -19,6 +19,7 @@ import {
ApiProduces, ApiProduces,
ApiTags, ApiTags,
ApiOperation, ApiOperation,
ApiExcludeController,
} from "@nestjs/swagger"; } from "@nestjs/swagger";
import { Response, Request } from "express"; import { Response, Request } from "express";
import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard"; import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard";
@@ -35,7 +36,8 @@ import {
} from "./dto/reply.dto"; } from "./dto/reply.dto";
import { ExpertBlameService } from "./expert-blame.service"; import { ExpertBlameService } from "./expert-blame.service";
@ApiTags("expert-blame-panel") @ApiExcludeController()
// @ApiTags("expert-blame-panel")
@Controller("expert-blame") @Controller("expert-blame")
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(LocalActorAuthGuard, RolesGuard) @UseGuards(LocalActorAuthGuard, RolesGuard)
@@ -45,35 +47,35 @@ export class ExpertBlameController {
// TODO role guard for expert fix // TODO role guard for expert fix
@Roles(RoleEnum.EXPERT) @Roles(RoleEnum.EXPERT)
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get() @Get()
async findAll(@CurrentUser() actor, @ClientKey() client) { async findAll(@CurrentUser() actor, @ClientKey() client) {
return await this.expertBlameService.findAll(actor); return await this.expertBlameService.findAll(actor);
} }
@Roles(RoleEnum.EXPERT) @Roles(RoleEnum.EXPERT)
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get(":id") @Get(":id")
async findOne(@Param("id") id: string, @CurrentUser() actor) { async findOne(@Param("id") id: string, @CurrentUser() actor) {
return await this.expertBlameService.findOne(id, actor.sub); return await this.expertBlameService.findOne(id, actor.sub);
} }
@Roles(RoleEnum.EXPERT) @Roles(RoleEnum.EXPERT)
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Put("lock/:id") @Put("lock/:id")
async lockRequest(@Param("id") id: string, @CurrentUser() actor) { async lockRequest(@Param("id") id: string, @CurrentUser() actor) {
return await this.expertBlameService.lockRequest(id, actor); return await this.expertBlameService.lockRequest(id, actor);
} }
@Roles(RoleEnum.EXPERT) @Roles(RoleEnum.EXPERT)
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get("request/accident-fields") @Get("request/accident-fields")
async getAccidentFields() { async getAccidentFields() {
return await this.expertBlameService.getAccidentField(); return await this.expertBlameService.getAccidentField();
} }
@Roles(RoleEnum.EXPERT) @Roles(RoleEnum.EXPERT)
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Put("reply/submit/:id") @Put("reply/submit/:id")
@ApiBody({ type: SubmitReplyDto }) @ApiBody({ type: SubmitReplyDto })
@ApiParam({ name: "id" }) @ApiParam({ name: "id" })
@@ -100,10 +102,10 @@ export class ExpertBlameController {
} }
@Roles(RoleEnum.EXPERT) @Roles(RoleEnum.EXPERT)
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Put("reply/resend/first/:id") @Put("reply/resend/first/:id")
@ApiBody({ type: ResendFirstPartyDto }) // @ApiBody({ type: ResendFirstPartyDto })
@ApiParam({ name: "id" }) // @ApiParam({ name: "id" })
async resendFirstParty( async resendFirstParty(
@Param("id") id: string, @Param("id") id: string,
@Body() body: SendAginIF, @Body() body: SendAginIF,
@@ -113,11 +115,11 @@ export class ExpertBlameController {
return this.handleResendRequest(id, body, actor.sub, req); return this.handleResendRequest(id, body, actor.sub, req);
} }
@Roles(RoleEnum.EXPERT) @Roles(RoleEnum.EXPERT)
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Put("reply/resend/second/:id") @Put("reply/resend/second/:id")
@Roles(RoleEnum.EXPERT) @Roles(RoleEnum.EXPERT)
@ApiBody({ type: ResendSecondPartyDto }) // @ApiBody({ type: ResendSecondPartyDto })
@ApiParam({ name: "id" }) // @ApiParam({ name: "id" })
async resendSecondParty( async resendSecondParty(
@Param("id") id: string, @Param("id") id: string,
@Body() body: SendAginIF, @Body() body: SendAginIF,
@@ -127,25 +129,25 @@ export class ExpertBlameController {
return this.handleResendRequest(id, body, actor.sub, req); return this.handleResendRequest(id, body, actor.sub, req);
} }
@Roles(RoleEnum.EXPERT) @Roles(RoleEnum.EXPERT)
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get("stream/:requestId") @Get("stream/:requestId")
@ApiParam({ name: "requestId" }) @ApiParam({ name: "requestId" })
async streamVideo(@Param("requestId") requestId: string) { async streamVideo(@Param("requestId") requestId: string) {
return this.expertBlameService.streamVideo(requestId); return this.expertBlameService.streamVideo(requestId);
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get("voice/:requestId/:voiceId") @Get("voice/:requestId/:voiceId")
@Roles(RoleEnum.EXPERT, RoleEnum.DAMAGE_EXPERT) @Roles(RoleEnum.EXPERT, RoleEnum.DAMAGE_EXPERT)
@ApiParam({ name: "requestId" }) // @ApiParam({ name: "requestId" })
@ApiParam({ name: "voiceId" }) // @ApiParam({ name: "voiceId" })
@ApiOkResponse({ // @ApiOkResponse({
schema: { // schema: {
type: "string", // type: "string",
format: "binary", // format: "binary",
}, // },
}) // })
@ApiProduces("mp3") // @ApiProduces("mp3")
async downloadVoice( async downloadVoice(
@Param("voiceId") voiceId, @Param("voiceId") voiceId,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@@ -156,8 +158,8 @@ export class ExpertBlameController {
return await this.expertBlameService.streamVoice(requestId, voiceId); return await this.expertBlameService.streamVoice(requestId, voiceId);
} }
@ApiParam({ name: "id" }) // @ApiParam({ name: "id" })
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Patch(":id/visit") @Patch(":id/visit")
async inPersonVisit(@Param("id") requestId: string, @CurrentUser() actor) { async inPersonVisit(@Param("id") requestId: string, @CurrentUser() actor) {
return await this.expertBlameService.inPersonVisit(requestId, actor); return await this.expertBlameService.inPersonVisit(requestId, actor);

View File

@@ -18,6 +18,7 @@ import {
ApiQuery, ApiQuery,
ApiTags, ApiTags,
ApiOperation, ApiOperation,
ApiExcludeController,
} from "@nestjs/swagger"; } from "@nestjs/swagger";
import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard"; import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard";
import { RolesGuard } from "src/auth/guards/role.guard"; import { RolesGuard } from "src/auth/guards/role.guard";
@@ -33,7 +34,8 @@ import { ParseJsonPipe } from "src/utils/pipes/parse-json.pipe";
import { ExpertClaimService } from "./expert-claim.service"; import { ExpertClaimService } from "./expert-claim.service";
import { FactorValidationDto } from "./dto/factor-validation.dto"; import { FactorValidationDto } from "./dto/factor-validation.dto";
@ApiTags("expert-claim-panel") @ApiExcludeController()
// @ApiTags("expert-claim-panel")
@Controller("expert-claim") @Controller("expert-claim")
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(LocalActorAuthGuard, RolesGuard) @UseGuards(LocalActorAuthGuard, RolesGuard)
@@ -41,20 +43,20 @@ import { FactorValidationDto } from "./dto/factor-validation.dto";
export class ExpertClaimController { export class ExpertClaimController {
constructor(private readonly expertClaimService: ExpertClaimService) {} constructor(private readonly expertClaimService: ExpertClaimService) {}
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get() @Get()
async getAllClaimRequests(@CurrentUser() actor, @ClientKey() client) { async getAllClaimRequests(@CurrentUser() actor, @ClientKey() client) {
return await this.expertClaimService.getClaimRequestsListForExpert(actor); return await this.expertClaimService.getClaimRequestsListForExpert(actor);
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get("/:id") @Get("/:id")
@ApiParam({ name: "id" }) // @ApiParam({ name: "id" })
@ApiQuery({ // @ApiQuery({
name: "updateDropPrice", // name: "updateDropPrice",
required: false, // required: false,
description: "A stringified JSON object with price drop override values.", // description: "A stringified JSON object with price drop override values.",
}) // })
async getClaimRequestPerId( async getClaimRequestPerId(
@Param("id") id: string, @Param("id") id: string,
@CurrentUser() currentUser, @CurrentUser() currentUser,
@@ -67,16 +69,16 @@ export class ExpertClaimController {
); );
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get("imageRequired/:id") @Get("imageRequired/:id")
@ApiParam({ name: "id" }) // @ApiParam({ name: "id" })
async getImageRequired(@Param("id") id: string, @CurrentUser() currentUser) { async getImageRequired(@Param("id") id: string, @CurrentUser() currentUser) {
return await this.expertClaimService.imageRequired(id, currentUser); return await this.expertClaimService.imageRequired(id, currentUser);
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get("required-documents/:id") @Get("required-documents/:id")
@ApiParam({ name: "id" }) // @ApiParam({ name: "id" })
async getRequiredDocuments( async getRequiredDocuments(
@Param("id") id: string, @Param("id") id: string,
@CurrentUser() currentUser, @CurrentUser() currentUser,
@@ -84,7 +86,7 @@ export class ExpertClaimController {
return await this.expertClaimService.getRequiredDocuments(id, currentUser); return await this.expertClaimService.getRequiredDocuments(id, currentUser);
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Put("lock/:id") @Put("lock/:id")
async lockClaimRequest(@Param("id") requestId: string, @CurrentUser() actor) { async lockClaimRequest(@Param("id") requestId: string, @CurrentUser() actor) {
return await this.expertClaimService.lockClaimRequest(requestId, actor); return await this.expertClaimService.lockClaimRequest(requestId, actor);
@@ -92,7 +94,7 @@ export class ExpertClaimController {
@ApiOperation({ deprecated: true }) @ApiOperation({ deprecated: true })
@Put("reply/submit/:id") @Put("reply/submit/:id")
@ApiParam({ name: "id" }) // @ApiParam({ name: "id" })
async submitReplyRequest( async submitReplyRequest(
@Param("id") requestId, @Param("id") requestId,
@Body() body: ClaimSubmitReplyDto, @Body() body: ClaimSubmitReplyDto,
@@ -105,9 +107,9 @@ export class ExpertClaimController {
); );
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Put("resend/submit/:id") @Put("resend/submit/:id")
@ApiParam({ name: "id" }) // @ApiParam({ name: "id" })
async submitResend( async submitResend(
@Param("id") requestId, @Param("id") requestId,
@Body() body: ClaimSubmitResendDto, @Body() body: ClaimSubmitResendDto,
@@ -120,12 +122,12 @@ export class ExpertClaimController {
); );
} }
@ApiQuery({ // @ApiQuery({
name: "query", // name: "query",
enum: ["car-capture", "accident"], //enum: ["car-capture", "accident"],
required: true, //required: true,
}) //})
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get("stream/:id/video") @Get("stream/:id/video")
@Header("Accept-Ranges", "bytes") @Header("Accept-Ranges", "bytes")
@Header("Content-Type", "video/mp4") @Header("Content-Type", "video/mp4")
@@ -143,7 +145,7 @@ export class ExpertClaimController {
enum: ["car-capture", "accident"], enum: ["car-capture", "accident"],
required: true, required: true,
}) })
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get("link/:id/video") @Get("link/:id/video")
@ApiParam({ name: "id" }) @ApiParam({ name: "id" })
async getClaimVideoLink( async getClaimVideoLink(
@@ -153,17 +155,17 @@ export class ExpertClaimController {
return this.expertClaimService.getVideoLink(id, query); return this.expertClaimService.getVideoLink(id, query);
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get("stream/:id/voice") @Get("stream/:id/voice")
@ApiParam({ name: "id" }) // @ApiParam({ name: "id" })
async getClaimVoiceLink(@Param("id") id: string) { async getClaimVoiceLink(@Param("id") id: string) {
return await this.expertClaimService.getVoiceLink(id); return await this.expertClaimService.getVoiceLink(id);
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Patch("validate-factors/:claimId") @Patch("validate-factors/:claimId")
@ApiParam({ name: "claimId" }) // @ApiParam({ name: "claimId" })
@ApiBody({ type: FactorValidationDto }) // @ApiBody({ type: FactorValidationDto })
@Roles(RoleEnum.DAMAGE_EXPERT) @Roles(RoleEnum.DAMAGE_EXPERT)
async validateFactors( async validateFactors(
@Param("claimId") claimId: string, @Param("claimId") claimId: string,
@@ -177,16 +179,16 @@ export class ExpertClaimController {
); );
} }
@ApiParam({ name: "id" }) // @ApiParam({ name: "id" })
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Patch(":id/visit") @Patch(":id/visit")
async inPersonVisit(@Param("id") requestId: string, @CurrentUser() actor) { async inPersonVisit(@Param("id") requestId: string, @CurrentUser() actor) {
return await this.expertClaimService.inPersonVisit(requestId, actor); return await this.expertClaimService.inPersonVisit(requestId, actor);
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get("branches/:insuranceId") @Get("branches/:insuranceId")
@ApiParam({ name: "insuranceId" }) // @ApiParam({ name: "insuranceId" })
async getInsuranceBranches(@Param("insuranceId") insuranceId: string) { async getInsuranceBranches(@Param("insuranceId") insuranceId: string) {
return await this.expertClaimService.retrieveInsuranceBranches(insuranceId); return await this.expertClaimService.retrieveInsuranceBranches(insuranceId);
} }

View File

@@ -1,8 +1,9 @@
import { Body, Controller, Post } from "@nestjs/common"; import { Body, Controller, Post } from "@nestjs/common";
import { ApiBearerAuth, ApiTags } from "@nestjs/swagger"; import { ApiBearerAuth, ApiExcludeController, ApiTags } from "@nestjs/swagger";
import { FieldExpertLoginDto } from "./dtos/login.dto"; import { FieldExpertLoginDto } from "./dtos/login.dto";
import { FieldExpertService } from "./expert-initiated.service"; import { FieldExpertService } from "./expert-initiated.service";
@ApiExcludeController()
@Controller("expert-initiated") @Controller("expert-initiated")
@ApiTags("Expert Initiated Flow (Field Expert)") @ApiTags("Expert Initiated Flow (Field Expert)")
@ApiBearerAuth() @ApiBearerAuth()

View File

@@ -0,0 +1,91 @@
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import {
IsBoolean,
IsNotEmpty,
IsOptional,
IsString,
ValidateNested,
} from "class-validator";
import { Type } from "class-transformer";
class PlateV3Dto {
@ApiProperty({ example: "44", description: "Left two digits" })
@IsString()
@IsNotEmpty()
leftDigits: string;
@ApiProperty({ example: "ب", description: "Center alphabet letter" })
@IsString()
@IsNotEmpty()
centerAlphabet: string;
@ApiProperty({ example: "111", description: "Center three digits" })
@IsString()
@IsNotEmpty()
centerDigits: string;
@ApiProperty({ example: "22", description: "Right two digits (Iran region code)" })
@IsString()
@IsNotEmpty()
ir: string;
}
/**
* Body for `POST run-inquiries/:requestId`.
* First call = guilty party (+ auto claim). Second call = damaged party (THIRD_PARTY only).
*/
export class RunInquiriesV3Dto {
@ApiProperty({
type: PlateV3Dto,
description: "Plate segments — Tejarat block / third-party inquiry.",
})
@ValidateNested()
@Type(() => PlateV3Dto)
plate: PlateV3Dto;
@ApiProperty({ example: "1234567890", description: "National code of the policyholder (insurer)" })
@IsString()
@IsNotEmpty()
nationalCodeOfInsurer: string;
@ApiProperty({ example: "1234567890", description: "National code of the driver" })
@IsString()
@IsNotEmpty()
nationalCodeOfDriver: string;
@ApiProperty({ example: true, description: "Whether the driver is the same person as the insurer" })
@IsBoolean()
driverIsInsurer: boolean;
@ApiProperty({ example: 13780624, description: "Insurer birth date (Jalali)" })
insurerBirthday: number | string;
@ApiPropertyOptional({ example: 13780624, description: "Driver birth date (Jalali). Required when driverIsInsurer is false." })
@IsOptional()
driverBirthday?: number | string | null;
@ApiPropertyOptional({
example: "IR123456789012345678901234",
description:
"Sheba (IBAN). Required on the damaged party call (CAR_BODY first call; THIRD_PARTY second call).",
})
@IsOptional()
@IsString()
sheba?: string;
@ApiPropertyOptional({
example: "123456789",
description: "Driver license (required when driverIsInsurer is false).",
})
@IsOptional()
@IsString()
driverLicense?: string;
@ApiPropertyOptional({
example: "123456789",
description: "Insurer license (required when driverIsInsurer is true).",
})
@IsOptional()
@IsString()
insurerLicense?: string;
}

View File

@@ -0,0 +1,584 @@
import { extname } from "node:path";
import {
Body,
Controller,
Get,
Param,
Patch,
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 { ExpertAccidentFieldsDto } from "./dto/expert-accident-fields.dto";
import { RequestManagementService } from "./request-management.service";
import { ClaimRequestManagementService } from "src/claim-request-management/claim-request-management.service";
import { PartyRole } from "./entities/schema/partyRole.enum";
import {
SelectOuterPartsV2Dto,
SelectOuterPartsV2ResponseDto,
} from "src/claim-request-management/dto/select-outer-parts-v2.dto";
import {
SelectOtherPartsV2ResponseDto,
} from "src/claim-request-management/dto/select-other-parts-v2.dto";
import { SelectOtherPartsV3Dto } from "src/claim-request-management/dto/select-other-parts-v3.dto";
import {
UploadRequiredDocumentV2Dto,
UploadRequiredDocumentV2ResponseDto,
} from "src/claim-request-management/dto/upload-document-v2.dto";
import {
CapturePartV2Dto,
CapturePartV2ResponseDto,
} from "src/claim-request-management/dto/capture-part-v2.dto";
import { GetCaptureRequirementsV2ResponseDto } from "src/claim-request-management/dto/capture-requirements-v2.dto";
/**
* V3 field-expert in-person flow (one party at a time, standard workflow steps).
*
* THIRD_PARTY:
* create → OTP/verify (guilty) → [car-body-form if CAR_BODY] → run-inquiries (guilty + claim)
* → location/description/voice → sign → OTP/verify (damaged) → run-inquiries (damaged)
* → location/description/voice → sign → accident-fields → docs → outer parts → other parts
* → capture-part → car-capture → upload-video (blame accident video, last) → WAITING_FOR_EXPERT
*
* CAR_BODY: same but single party — omit second-party OTP/inquiries/sign steps.
*/
@ApiTags("expert-initiated blame (mirror v3 in-person)")
@Controller("v3/expert-initiated/blame-request-management")
@ApiBearerAuth()
@UseGuards(LocalActorAuthGuard, RolesGuard)
@Roles(RoleEnum.FIELD_EXPERT)
export class ExpertInitiatedBlameV3MirrorController {
constructor(
private readonly requestManagementService: RequestManagementService,
private readonly claimRequestManagementService: ClaimRequestManagementService,
private readonly mediaPolicyService: MediaPolicyService,
) {}
@Post()
@ApiOperation({ summary: "Create IN_PERSON blame file" })
@ApiBody({ type: CreateExpertInitiatedFileDto })
async create(
@CurrentUser() expert: any,
@Body() dto: CreateExpertInitiatedFileDto,
) {
return this.requestManagementService.createExpertInitiatedBlameV2(expert, {
...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. Use this claim ID for capture-requirements, upload-document, and part-selection endpoints.",
})
async getLinkedClaimId(
@Param("requestId") requestId: string,
@CurrentUser() expert: any,
) {
return this.requestManagementService.getV3LinkedClaimForExpert(
expert,
requestId,
);
}
@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() expert: any,
@Param("requestId") requestId: string,
@Body() dto: SendPartyOtpDto,
) {
return this.requestManagementService.sendPartyOtpV2(expert, 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() expert: any,
@Param("requestId") requestId: string,
@Body() dto: VerifyPartyOtpDto,
) {
return this.requestManagementService.verifyPartyOtpV2(expert, requestId, dto);
}
@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() expert: any,
) {
return this.requestManagementService.carBodyAccidentTypeFormV3(
requestId,
body,
expert,
);
}
@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). " +
"Advances workflow to FIRST_INITIAL_FORM or SECOND_INITIAL_FORM.",
})
async runInquiries(
@CurrentUser() expert: any,
@Param("requestId") requestId: string,
@Body() dto: RunInquiriesV3Dto,
) {
return this.requestManagementService.runInquiriesV3(requestId, expert, dto);
}
@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() expert: any,
@Body("partyRole") partyRole?: string,
) {
return this.requestManagementService.addPartyLocationV3(
requestId,
expert,
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() expert: any,
@Body("partyRole") partyRole?: string,
) {
return this.requestManagementService.addPartyDescriptionV3(
requestId,
expert,
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, `v3-expert-voice-${unique}${ex}`);
},
}),
}),
)
@Post("upload-voice/:requestId")
@ApiParam({ name: "requestId" })
@ApiOperation({ summary: "Upload voice for current party" })
async uploadVoice(
@Param("requestId") requestId: string,
@CurrentUser() expert: any,
@UploadedFile() voice: Express.Multer.File,
@Body("partyRole") partyRole?: string,
) {
await this.mediaPolicyService.assertForBlame(voice, requestId, "voice");
return this.requestManagementService.addPartyVoiceV3(
requestId,
expert,
voice,
partyRole,
);
}
@Put("sign/:requestId")
@ApiParam({ name: "requestId" })
@ApiConsumes("multipart/form-data")
@ApiOperation({ summary: "Party on-site signature" })
@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, `v3-expert-party-${unique}${ex}`);
},
}),
}),
)
async sign(
@Param("requestId") requestId: string,
@Body() body: { partyRole?: string; isAccept?: string | boolean },
@CurrentUser() expert: 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(
expert,
requestId,
partyRole,
isAccept,
sign,
);
}
@Post("accident-fields/:requestId")
@ApiParam({ name: "requestId" })
@ApiBody({ type: ExpertAccidentFieldsDto })
@ApiOperation({
summary: "Accident type / fields (after all signatures)",
description:
"Saves accident fields only. Does not move to WAITING_FOR_EXPERT — that happens after the final blame accident video.",
})
async addAccidentFields(
@Param("requestId") requestId: string,
@Body() fields: ExpertAccidentFieldsDto,
@CurrentUser() expert: any,
) {
return this.requestManagementService.expertAddAccidentFieldsForBlameV3(
expert,
requestId,
fields,
);
}
@Get("capture-requirements/:claimRequestId")
@ApiParam({ name: "claimRequestId" })
@ApiOperation({ summary: "List required claim documents" })
async getCaptureRequirements(
@Param("claimRequestId") claimRequestId: string,
@CurrentUser() expert: any,
): Promise<GetCaptureRequirementsV2ResponseDto> {
return this.claimRequestManagementService.getCaptureRequirementsV3(
claimRequestId,
expert.sub,
expert,
);
}
@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" })
async uploadDocument(
@Param("claimRequestId") claimRequestId: string,
@Body() body: UploadRequiredDocumentV2Dto,
@UploadedFile() file: Express.Multer.File,
@CurrentUser() expert: any,
): Promise<UploadRequiredDocumentV2ResponseDto> {
await this.mediaPolicyService.assertForClaim(file, claimRequestId, "image");
return this.claimRequestManagementService.uploadRequiredDocumentV3(
claimRequestId,
body,
file,
expert.sub,
expert,
);
}
@Patch("select-outer-parts/:claimRequestId")
@ApiParam({ name: "claimRequestId" })
@ApiBody({ type: SelectOuterPartsV2Dto })
@ApiOperation({ summary: "Select damaged outer parts" })
async selectOuterParts(
@Param("claimRequestId") claimRequestId: string,
@Body() body: SelectOuterPartsV2Dto,
@CurrentUser() expert: any,
): Promise<SelectOuterPartsV2ResponseDto> {
return this.claimRequestManagementService.selectOuterPartsV3(
claimRequestId,
body,
expert.sub,
expert,
);
}
@Patch("select-other-parts/:claimRequestId")
@ApiParam({ name: "claimRequestId", example: "507f1f77bcf86cd799439011" })
@ApiConsumes("multipart/form-data")
@UseInterceptors(
FileInterceptor("file", {
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
storage: diskStorage({
destination: "./files/claim-required-document",
filename: (req, file, callback) => {
const unique = Date.now();
const ex = extname(file.originalname);
callback(null, `v3-other-parts-${unique}${ex}`);
},
}),
}),
)
@ApiOperation({
summary: "Select other damaged parts (V3)",
description:
"Other parts only — sheba and national code were collected during run-inquiries. Optional car green card file.",
})
@ApiBody({
description: "Other parts selection. Bank info is already on the claim from run-inquiries.",
schema: {
type: "object",
properties: {
otherParts: {
oneOf: [
{ type: "array", items: { type: "string", enum: ["engine", "suspension", "brake_system", "electrical", "radiator", "transmission", "exhaust", "headlight", "taillight", "mirror", "glass"] } },
{ type: "string", description: "JSON string array for multipart" },
],
example: ["engine", "suspension"],
},
file: { type: "string", format: "binary", description: "Optional car green card" },
},
},
})
async selectOtherParts(
@Param("claimRequestId") claimRequestId: string,
@Body() body: SelectOtherPartsV3Dto,
@CurrentUser() expert: any,
@UploadedFile() file?: Express.Multer.File,
): Promise<SelectOtherPartsV2ResponseDto> {
if (file) {
await this.mediaPolicyService.assertForClaim(file, claimRequestId, "image");
}
return this.claimRequestManagementService.selectOtherPartsV3(
claimRequestId,
body,
expert.sub,
expert,
file,
);
}
@Post("capture-part/:claimRequestId")
@ApiParam({ name: "claimRequestId", example: "507f1f77bcf86cd799439011" })
@ApiConsumes("multipart/form-data")
@UseInterceptors(
FileInterceptor("file", {
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
storage: diskStorage({
destination: "./files/claim-captures",
filename: (req, file, callback) => {
const unique = Date.now();
const ex = extname(file.originalname);
callback(null, `v3-capture-${unique}${ex}`);
},
}),
}),
)
@ApiOperation({
summary: "Field — car angles and damaged-part photos",
description:
"CAPTURE_PART_DAMAGES: (1) all damaged-part photos, (2) four car angles, (3) chassis/engine/metal-plate via upload-document. Then car-capture walk-around video.",
})
@ApiBody({
schema: {
type: "object",
required: ["captureType", "captureKey", "file"],
properties: {
captureType: {
type: "string",
enum: ["angle", "part"],
example: "angle",
},
captureKey: {
type: "string",
example: "front",
description:
"For angle: front/back/left/right. For part: hood/front_bumper/etc.",
},
file: { type: "string", format: "binary" },
},
},
})
async capturePart(
@Param("claimRequestId") claimRequestId: string,
@Body() body: CapturePartV2Dto,
@UploadedFile() file: Express.Multer.File,
@CurrentUser() expert: any,
): Promise<CapturePartV2ResponseDto> {
await this.mediaPolicyService.assertForClaim(file, claimRequestId, "image");
return this.claimRequestManagementService.capturePartV3(
claimRequestId,
body,
file,
expert.sub,
expert,
);
}
@Patch("car-capture/:claimRequestId")
@ApiParam({ name: "claimRequestId", example: "507f1f77bcf86cd799439011" })
@ApiConsumes("multipart/form-data")
@UseInterceptors(
FileInterceptor("file", {
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
storage: diskStorage({
destination: "./files/car-capture-videos/",
filename: (req, file, callback) => {
const unique = Date.now();
const ex = extname(file.originalname);
callback(null, `v3-claim-video-${unique}${ex}`);
},
}),
}),
)
@ApiBody({
schema: {
type: "object",
required: ["file"],
properties: { file: { type: "string", format: "binary" } },
},
})
@ApiOperation({
summary: "Field — walk-around video",
description:
"Upload after capture-part is complete (parts, angles, capture-phase docs). Required before the final blame accident video.",
})
async carCapture(
@Param("claimRequestId") claimRequestId: string,
@UploadedFile("file") file: Express.Multer.File,
@CurrentUser() expert: any,
) {
await this.mediaPolicyService.assertForClaim(file, claimRequestId, "video");
return this.claimRequestManagementService.setVideoCaptureV3(
claimRequestId,
file,
expert.sub,
expert,
);
}
@ApiBody({
schema: {
type: "object",
required: ["file"],
properties: { file: { type: "string", format: "binary" } },
},
})
@ApiConsumes("multipart/form-data")
@UseInterceptors(
FileInterceptor("file", {
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
storage: diskStorage({
destination: "./files/video",
filename: (req, file, callback) => {
const unique = Date.now();
const ex = extname(file.originalname);
callback(null, `v3-blame-accident-${unique}${ex}`);
},
}),
}),
)
@Post("upload-video/:requestId")
@ApiParam({ name: "requestId" })
@ApiOperation({
summary: "Blame accident video (final step)",
description:
"Last step of the V3 flow. Requires claim walk-around video and completed capture. Sets blame status to WAITING_FOR_EXPERT.",
})
async uploadBlameVideo(
@Param("requestId") requestId: string,
@CurrentUser() expert: any,
@UploadedFile() file: Express.Multer.File,
) {
await this.mediaPolicyService.assertForBlame(file, requestId, "video");
return this.requestManagementService.expertUploadBlameVideoV3(
expert,
requestId,
file,
);
}
}

View File

@@ -21,6 +21,7 @@ import {
ApiOperation, ApiOperation,
ApiResponse, ApiResponse,
ApiConsumes, ApiConsumes,
ApiExcludeController,
} from "@nestjs/swagger"; } from "@nestjs/swagger";
import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard"; import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard";
import { RolesGuard } from "src/auth/guards/role.guard"; import { RolesGuard } from "src/auth/guards/role.guard";
@@ -36,7 +37,8 @@ import { ExpertAccidentFieldsDto } from "./dto/expert-accident-fields.dto";
import { ExpertCompleteClaimDataDto } from "./dto/expert-complete-claim-data.dto"; import { ExpertCompleteClaimDataDto } from "./dto/expert-complete-claim-data.dto";
import { ClaimRequestManagementService } from "src/claim-request-management/claim-request-management.service"; import { ClaimRequestManagementService } from "src/claim-request-management/claim-request-management.service";
@ApiTags("expert-initiated-blame") @ApiExcludeController()
// @ApiTags("expert-initiated-blame")
@Controller("expert-initiated-blame") @Controller("expert-initiated-blame")
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(LocalActorAuthGuard, RolesGuard) @UseGuards(LocalActorAuthGuard, RolesGuard)
@@ -47,34 +49,26 @@ export class ExpertInitiatedController {
private readonly claimRequestManagementService: ClaimRequestManagementService, private readonly claimRequestManagementService: ClaimRequestManagementService,
) {} ) {}
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Get("my-files") @Get("my-files")
@ApiOperation({ // @ApiOperation({
summary: "List my expert-initiated files", // summary: "List my expert-initiated files",
description: // description:
"Returns all blame files created by the current field expert (their panel).", // "Returns all blame files created by the current field expert (their panel).",
}) // })
@ApiResponse({ status: 200, description: "List of expert-initiated files" }) // @ApiResponse({ status: 200, description: "List of expert-initiated files" })
async getMyFiles(@CurrentUser() expert: any) { async getMyFiles(@CurrentUser() expert: any) {
return await this.requestManagementService.getMyExpertInitiatedFiles( return await this.requestManagementService.getMyExpertInitiatedFiles(expert);
expert,
);
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Post("complete-blame-data/:requestId") @Post("complete-blame-data/:requestId")
@ApiOperation({ // @ApiParam({ name: "requestId", description: "ID of the expert-initiated file" })
summary: "Submit all blame-needed data", // @ApiBody({
description: // description:
"Single endpoint to complete blame form: send THIRD_PARTY or CAR_BODY form data according to file type. " + // "Send ExpertCompleteThirdPartyFormDto for THIRD_PARTY files or ExpertCompleteCarBodyFormDto for CAR_BODY files (according to file type).",
"Video and voice can be uploaded separately.", // })
}) // @ApiResponse({ status: 200, description: "Blame form completed successfully" })
@ApiParam({ name: "requestId", description: "ID of the expert-initiated file" })
@ApiBody({
description:
"Send ExpertCompleteThirdPartyFormDto for THIRD_PARTY files or ExpertCompleteCarBodyFormDto for CAR_BODY files (according to file type).",
})
@ApiResponse({ status: 200, description: "Blame form completed successfully" })
async completeBlameData( async completeBlameData(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@@ -87,20 +81,20 @@ export class ExpertInitiatedController {
); );
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Post("complete-claim-data/:claimRequestId") @Post("complete-claim-data/:claimRequestId")
@ApiOperation({ // @ApiOperation({
summary: "Submit claim-needed data", // summary: "Submit claim-needed data",
description: // description:
"Field expert fills claim data for expert-initiated IN_PERSON files (car part damage, sheba, national code, other parts). " + // "Field expert fills claim data for expert-initiated IN_PERSON files (car part damage, sheba, national code, other parts). " +
"Only the initiating expert can call this.", // "Only the initiating expert can call this.",
}) // })
@ApiParam({ // @ApiParam({
name: "claimRequestId", // name: "claimRequestId",
description: "ID of the claim file (created from the expert-initiated blame file)", // description: "ID of the claim file (created from the expert-initiated blame file)",
}) // })
@ApiBody({ type: ExpertCompleteClaimDataDto }) // @ApiBody({ type: ExpertCompleteClaimDataDto })
@ApiResponse({ status: 200, description: "Claim data updated successfully" }) // @ApiResponse({ status: 200, description: "Claim data updated successfully" })
async completeClaimData( async completeClaimData(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Param("claimRequestId") claimRequestId: string, @Param("claimRequestId") claimRequestId: string,
@@ -141,24 +135,11 @@ export class ExpertInitiatedController {
// ); // );
// } // }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Post("complete-form-third-party/:requestId") @Post("complete-form-third-party/:requestId")
@ApiOperation({ // @ApiParam({ name: "requestId" })
summary: "Expert completes all information for IN_PERSON THIRD_PARTY file", // @ApiBody({ type: ExpertCompleteThirdPartyFormDto })
description: // @ApiResponse({ status: 200, description: "Form completed successfully. File is now ready for user signatures." })
"Expert fills all information at once for IN_PERSON THIRD_PARTY files. " +
"Includes first party, second party, and guilty party selection. " +
"Video and voice uploads are handled separately.",
})
@ApiParam({
name: "requestId",
description: "ID of the expert-initiated IN_PERSON THIRD_PARTY file",
})
@ApiBody({ type: ExpertCompleteThirdPartyFormDto })
@ApiResponse({
status: 200,
description: "Form completed successfully. File is now ready for user signatures.",
})
async completeThirdPartyForm( async completeThirdPartyForm(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@@ -171,24 +152,15 @@ export class ExpertInitiatedController {
); );
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Post("complete-form-car-body/:requestId") @Post("complete-form-car-body/:requestId")
@ApiOperation({ // @ApiOperation({
summary: "Expert completes all information for IN_PERSON CAR_BODY file", // summary: "Expert completes all information for IN_PERSON CAR_BODY file",
description: // description:
"Expert fills all information at once for IN_PERSON CAR_BODY files. " + // "Expert fills all information at once for IN_PERSON CAR_BODY files. " +
"Includes first party data and carBodyForm. " + // "Includes first party data and carBodyForm. " +
"Video and voice uploads are handled separately.", // "Video and voice uploads are handled separately.",
}) // })
@ApiParam({
name: "requestId",
description: "ID of the expert-initiated IN_PERSON CAR_BODY file",
})
@ApiBody({ type: ExpertCompleteCarBodyFormDto })
@ApiResponse({
status: 200,
description: "Form completed successfully. File is now ready for user signature.",
})
async completeCarBodyForm( async completeCarBodyForm(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@@ -201,31 +173,16 @@ export class ExpertInitiatedController {
); );
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Post("upload-video/:requestId") @Post("upload-video/:requestId")
@ApiOperation({ // @ApiParam({ name: "requestId" })
summary: "Expert uploads video for expert-initiated file", // @ApiConsumes("multipart/form-data")
description: // @ApiBody({
"Expert uploads a single video file for the entire blame file. " + // schema: {
"Only one video is needed per file (not per party). " + // type: "object",
"Only works for expert-initiated files.", // properties: { file: { type: "string", format: "binary" } },
}) // },
@ApiParam({ // })
name: "requestId",
description: "ID of the expert-initiated file",
})
@ApiConsumes("multipart/form-data")
@ApiBody({
schema: {
type: "object",
properties: {
file: {
type: "string",
format: "binary",
},
},
},
})
@UseInterceptors( @UseInterceptors(
FileInterceptor("file", { FileInterceptor("file", {
limits: { limits: {
@@ -242,10 +199,10 @@ export class ExpertInitiatedController {
}), }),
}), }),
) )
@ApiResponse({ // @ApiResponse({
status: 200, // status: 200,
description: "Video uploaded successfully", // description: "Video uploaded successfully",
}) // })
async uploadVideo( async uploadVideo(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@@ -258,31 +215,28 @@ export class ExpertInitiatedController {
); );
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Post("upload-voice/:requestId") @Post("upload-voice/:requestId")
@ApiOperation({ // @ApiOperation({
summary: "Expert uploads voice for expert-initiated file", // summary: "Expert uploads voice for expert-initiated file",
description: // description:
"Expert uploads a single voice file for the entire blame file. " + // "Expert uploads a single voice file for the entire blame file. " +
"Only one voice is needed per file (not per party). " + // "Only one voice is needed per file (not per party). " +
"Only works for expert-initiated files.", // "Only works for expert-initiated files.",
}) // })
@ApiParam({ // @ApiParam({ name: "requestId" })
name: "requestId", // @ApiConsumes("multipart/form-data")
description: "ID of the expert-initiated file", // @ApiBody({
}) // schema: {
@ApiConsumes("multipart/form-data") // type: "object",
@ApiBody({ // properties: {
schema: { // voice: {
type: "object", // type: "string",
properties: { // format: "binary",
voice: { // },
type: "string", // },
format: "binary", // },
}, // })
},
},
})
@UseInterceptors( @UseInterceptors(
FileInterceptor("voice", { FileInterceptor("voice", {
limits: { limits: {
@@ -300,10 +254,7 @@ export class ExpertInitiatedController {
}), }),
}), }),
) )
@ApiResponse({ // @ApiResponse({ status: 200, description: "Voice uploaded successfully" })
status: 200,
description: "Voice uploaded successfully",
})
async uploadVoice( async uploadVoice(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@@ -316,24 +267,18 @@ export class ExpertInitiatedController {
); );
} }
@ApiOperation({ deprecated: true }) // @ApiOperation({ deprecated: true })
@Post("add-accident-fields/:requestId") @Post("add-accident-fields/:requestId")
@ApiOperation({ // @ApiOperation({
summary: "Expert adds accident fields to expert-initiated file", // summary: "Expert adds accident fields to expert-initiated file",
description: // description:
"Expert adds accident way, reason, and type fields to an expert-initiated file. " + // "Expert adds accident way, reason, and type fields to an expert-initiated file. " +
"Can be used for both IN_PERSON and LINK-based files. " + // "Can be used for both IN_PERSON and LINK-based files. " +
"If expertSubmitReply already exists, updates the fields. Otherwise, creates a new expertSubmitReply.", // "If expertSubmitReply already exists, updates the fields. Otherwise, creates a new expertSubmitReply.",
}) // })
@ApiParam({ // @ApiParam({ name: "requestId" })
name: "requestId", // @ApiBody({ type: ExpertAccidentFieldsDto })
description: "ID of the expert-initiated file", // @ApiResponse({ status: 200, description: "Accident fields added successfully" })
})
@ApiBody({ type: ExpertAccidentFieldsDto })
@ApiResponse({
status: 200,
description: "Accident fields added successfully",
})
async addAccidentFields( async addAccidentFields(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,

View File

@@ -46,7 +46,7 @@ import { PartyRole } from "./entities/schema/partyRole.enum";
* Field experts create files via LINK (send link to user) or IN_PERSON (expert fills on-site). * Field experts create files via LINK (send link to user) or IN_PERSON (expert fills on-site).
* Only the initiating field expert can see/review these files. * Only the initiating field expert can see/review these files.
*/ */
@ApiTags("expert-initiated-blame (v2)") // @ApiTags("expert-initiated-blame (v2)")
@Controller("v2/expert-initiated-blame") @Controller("v2/expert-initiated-blame")
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(LocalActorAuthGuard, RolesGuard) @UseGuards(LocalActorAuthGuard, RolesGuard)
@@ -59,23 +59,23 @@ export class ExpertInitiatedV2Controller {
) {} ) {}
@Get("my-files") @Get("my-files")
@ApiOperation({ // @ApiOperation({
summary: "[V2] List my expert-initiated blame files", // summary: "[V2] List my expert-initiated blame files",
description: // description:
"Returns all BlameRequest (workflow) files created by the current field expert.", // "Returns all BlameRequest (workflow) files created by the current field expert.",
}) // })
@ApiResponse({ status: 200, description: "List of expert-initiated files" }) // @ApiResponse({ status: 200, description: "List of expert-initiated files" })
async getMyFilesV2(@CurrentUser() expert: any) { async getMyFilesV2(@CurrentUser() expert: any) {
return this.requestManagementService.getMyExpertInitiatedFilesV2(expert); return this.requestManagementService.getMyExpertInitiatedFilesV2(expert);
} }
@Get("blame/:requestId") @Get("blame/:requestId")
@ApiOperation({ // @ApiOperation({
summary: "[V2] Get one expert-initiated blame request", // summary: "[V2] Get one expert-initiated blame request",
description: "Returns a single blame request. Only the initiating field expert can access.", // description: "Returns a single blame request. Only the initiating field expert can access.",
}) // })
@ApiParam({ name: "requestId", description: "Blame request ID" }) // @ApiParam({ name: "requestId", description: "Blame request ID" })
@ApiResponse({ status: 200, description: "Blame request" }) // @ApiResponse({ status: 200, description: "Blame request" })
async getBlameRequestV2( async getBlameRequestV2(
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@CurrentUser() expert: any, @CurrentUser() expert: any,
@@ -84,68 +84,45 @@ export class ExpertInitiatedV2Controller {
} }
@Post("create") @Post("create")
@ApiOperation({ // @ApiOperation({
summary: "[V2] Create expert-initiated blame file", // summary: "[V2] Create expert-initiated blame file",
description: // description:
"Creates a BlameRequest with workflow. LINK: parties identified by phone, expert sends link. IN_PERSON: expert fills form later.", // "Creates a BlameRequest with workflow. LINK: parties identified by phone, expert sends link. IN_PERSON: expert fills form later.",
}) // })
@ApiBody({ // @ApiBody({
type: CreateExpertInitiatedFileDto, // type: CreateExpertInitiatedFileDto,
examples: { // examples: {
thirdPartyInPerson: { // thirdPartyInPerson: {
summary: "Third-party, IN_PERSON (both parties)", // summary: "Third-party, IN_PERSON (both parties)",
description: // description:
"Expert fills form on-site for both parties.", // "Expert fills form on-site for both parties.",
value: { // value: {
type: "THIRD_PARTY", // type: "THIRD_PARTY",
creationMethod: "IN_PERSON", // creationMethod: "LINK",
firstPartyPhoneNumber: "09123456789", // firstPartyPhoneNumber: "09123456789",
secondPartyPhoneNumber: "09187654321", // },
}, // },
}, // carBodyInPerson: {
thirdPartyLink: { // summary: "Car-body, IN_PERSON",
summary: "Third-party, LINK", // description: "Expert fills form on-site for single party (car body).",
description: // value: {
"Expert sends link to first and second party by phone.", // type: "CAR_BODY",
value: { // creationMethod: "IN_PERSON",
type: "THIRD_PARTY", // firstPartyPhoneNumber: "09123456789",
creationMethod: "LINK", // },
firstPartyPhoneNumber: "09123456789", // },
}, // carBodyLink: {
}, // summary: "Car-body, LINK",
carBodyInPerson: { // description:
summary: "Car-body, IN_PERSON", // "Expert sends link to party by phone. Only first party phone needed.",
description: "Expert fills form on-site for single party (car body).", // value: {
value: { // type: "CAR_BODY",
type: "CAR_BODY", // creationMethod: "LINK",
creationMethod: "IN_PERSON", // firstPartyPhoneNumber: "09123456789",
firstPartyPhoneNumber: "09123456789", // },
}, // },
}, // },
carBodyLink: { // })
summary: "Car-body, LINK",
description:
"Expert sends link to party by phone. Only first party phone needed.",
value: {
type: "CAR_BODY",
creationMethod: "LINK",
firstPartyPhoneNumber: "09123456789",
},
},
},
})
@ApiResponse({
status: 201,
description: "File created",
schema: {
type: "object",
properties: {
requestId: { type: "string" },
publicId: { type: "string" },
linkUrl: { type: "string", description: "Present for LINK method" },
},
},
})
async createV2( async createV2(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Body() dto: CreateExpertInitiatedFileDto, @Body() dto: CreateExpertInitiatedFileDto,
@@ -157,13 +134,13 @@ export class ExpertInitiatedV2Controller {
} }
@Post("send-link/:requestId") @Post("send-link/:requestId")
@ApiOperation({ // @ApiOperation({
summary: "[V2] Send blame link to first party (LINK)", // summary: "[V2] Send blame link to first party (LINK)",
description: // description:
"For expert-initiated LINK files only. Provide the first party phone number; the service stores it, registers the user if needed, and sends the invite link via SMS.", // "For expert-initiated LINK files only. Provide the first party phone number; the service stores it, registers the user if needed, and sends the invite link via SMS.",
}) // })
@ApiParam({ name: "requestId", description: "Blame request ID" }) // @ApiParam({ name: "requestId", description: "Blame request ID" })
@ApiBody({ type: SendExpertInitiatedLinkV2Dto }) // @ApiBody({ type: SendExpertInitiatedLinkV2Dto })
async sendLinkV2( async sendLinkV2(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@@ -173,14 +150,14 @@ export class ExpertInitiatedV2Controller {
} }
@Post("send-party-otps/:requestId") @Post("send-party-otps/:requestId")
@ApiOperation({ // @ApiOperation({
summary: "[V2] Send OTP to party/parties (IN_PERSON)", // summary: "[V2] Send OTP to party/parties (IN_PERSON)",
description: // description:
"Sends OTP via SMS to first party (and second party for THIRD_PARTY) using the same flow as /user/send-otp. Parties receive the code; collect it from them and call verify-party-otps. Call this before filling the blame form.", // "Sends OTP via SMS to first party (and second party for THIRD_PARTY) using the same flow as /user/send-otp. Parties receive the code; collect it from them and call verify-party-otps. Call this before filling the blame form.",
}) // })
@ApiParam({ name: "requestId", description: "Blame request ID" }) // @ApiParam({ name: "requestId", description: "Blame request ID" })
@ApiBody({ type: SendPartyOtpsDto }) // @ApiBody({ type: SendPartyOtpsDto })
@ApiResponse({ status: 200, description: "OTP(s) sent; collect codes and call verify-party-otps" }) // @ApiResponse({ status: 200, description: "OTP(s) sent; collect codes and call verify-party-otps" })
async sendPartyOtpsV2( async sendPartyOtpsV2(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@@ -190,14 +167,14 @@ export class ExpertInitiatedV2Controller {
} }
@Post("verify-party-otps/:requestId") @Post("verify-party-otps/:requestId")
@ApiOperation({ // @ApiOperation({
summary: "[V2] Verify party OTPs (IN_PERSON)", // summary: "[V2] Verify party OTPs (IN_PERSON)",
description: // description:
"After send-party-otps, parties receive SMS. They tell you the code; submit it here. Required before complete-blame-data.", // "After send-party-otps, parties receive SMS. They tell you the code; submit it here. Required before complete-blame-data.",
}) // })
@ApiParam({ name: "requestId", description: "Blame request ID" }) // @ApiParam({ name: "requestId", description: "Blame request ID" })
@ApiBody({ type: VerifyPartyOtpsDto }) // @ApiBody({ type: VerifyPartyOtpsDto })
@ApiResponse({ status: 200, description: "OTPs verified; expert can proceed to complete-blame-data" }) // @ApiResponse({ status: 200, description: "OTPs verified; expert can proceed to complete-blame-data" })
async verifyPartyOtpsV2( async verifyPartyOtpsV2(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@@ -207,99 +184,99 @@ export class ExpertInitiatedV2Controller {
} }
@Post("complete-blame-data/:requestId") @Post("complete-blame-data/:requestId")
@ApiOperation({ // @ApiOperation({
summary: "[V2] Submit all blame data (IN_PERSON)", // summary: "[V2] Submit all blame data (IN_PERSON)",
description: // description:
"For IN_PERSON files only. Send THIRD_PARTY or CAR_BODY form EXCEPT location. Use one shared expertDescription (not per-party descriptions). After this, call add-locations once to submit scene lat/lon and move workflow to WAITING_FOR_SIGNATURES.", // "For IN_PERSON files only. Send THIRD_PARTY or CAR_BODY form EXCEPT location. Use one shared expertDescription (not per-party descriptions). After this, call add-locations once to submit scene lat/lon and move workflow to WAITING_FOR_SIGNATURES.",
}) // })
@ApiParam({ name: "requestId", description: "Blame request ID" }) // @ApiParam({ name: "requestId", description: "Blame request ID" })
@ApiBody({ // @ApiBody({
description: "Choose THIRD_PARTY or CAR_BODY example according to the file type. IN_PERSON v2 uses one shared expertDescription for the scene.", // description: "Choose THIRD_PARTY or CAR_BODY example according to the file type. IN_PERSON v2 uses one shared expertDescription for the scene.",
examples: { // examples: {
THIRD_PARTY: { // THIRD_PARTY: {
summary: "THIRD_PARTY", // summary: "THIRD_PARTY",
description: "Use when the blame file type is THIRD_PARTY", // description: "Use when the blame file type is THIRD_PARTY",
value: { // value: {
firstPartyPhoneNumber: "09123456789", // firstPartyPhoneNumber: "09123456789",
firstPartyInitialForm: { // firstPartyInitialForm: {
expertOpinion: false, // expertOpinion: false,
imDamaged: false, // imDamaged: false,
imGuilty: true, // imGuilty: true,
}, // },
firstPartyPlate: { // firstPartyPlate: {
nationalCodeOfInsurer: "", // nationalCodeOfInsurer: "",
nationalCodeOfDriver: "", // nationalCodeOfDriver: "",
insurerLicense: "", // insurerLicense: "",
driverLicense: "", // driverLicense: "",
plate: { leftDigits: 12, centerAlphabet: "الف", centerDigits: 345, ir: 22 }, // plate: { leftDigits: 12, centerAlphabet: "الف", centerDigits: 345, ir: 22 },
driverIsInsurer: true, // driverIsInsurer: true,
isNewCar: false, // isNewCar: false,
userNoCertificate: false, // userNoCertificate: false,
insurerBirthday: 13770624, // insurerBirthday: 13770624,
driverBirthday: "1370-01-01", // driverBirthday: "1370-01-01",
}, // },
expertDescription: { desc: "توضیح حادثه کارشناس (مشترک برای صحنه)" }, // expertDescription: { desc: "توضیح حادثه کارشناس (مشترک برای صحنه)" },
secondParty: { // secondParty: {
phoneNumber: "09187654321", // phoneNumber: "09187654321",
initialForm: { // initialForm: {
expertOpinion: false, // expertOpinion: false,
imDamaged: true, // imDamaged: true,
imGuilty: false, // imGuilty: false,
}, // },
plate: { // plate: {
nationalCodeOfInsurer: "", // nationalCodeOfInsurer: "",
nationalCodeOfDriver: "", // nationalCodeOfDriver: "",
insurerLicense: "", // insurerLicense: "",
driverLicense: "", // driverLicense: "",
plate: { leftDigits: 91, centerAlphabet: "ن", centerDigits: 174, ir: 79 }, // plate: { leftDigits: 91, centerAlphabet: "ن", centerDigits: 174, ir: 79 },
driverIsInsurer: true, // driverIsInsurer: true,
isNewCar: false, // isNewCar: false,
userNoCertificate: false, // userNoCertificate: false,
insurerBirthday: 13700720, // insurerBirthday: 13700720,
driverBirthday: "1370-01-01", // driverBirthday: "1370-01-01",
}, // },
}, // },
guiltyPartyPhoneNumber: "09123456789", // guiltyPartyPhoneNumber: "09123456789",
}, // },
}, // },
CAR_BODY: { // CAR_BODY: {
summary: "CAR_BODY", // summary: "CAR_BODY",
description: "Use when the blame file type is CAR_BODY", // description: "Use when the blame file type is CAR_BODY",
value: { // value: {
firstPartyPhoneNumber: "09123456789", // firstPartyPhoneNumber: "09123456789",
firstPartyInitialForm: { // firstPartyInitialForm: {
expertOpinion: false, // expertOpinion: false,
imDamaged: false, // imDamaged: false,
imGuilty: true, // imGuilty: true,
}, // },
carBodyForm: { car: true, object: false }, // carBodyForm: { car: true, object: false },
firstPartyPlate: { // firstPartyPlate: {
plateId: "", // plateId: "",
nationalCodeOfInsurer: "", // nationalCodeOfInsurer: "",
nationalCodeOfDriver: "", // nationalCodeOfDriver: "",
insurerLicense: "", // insurerLicense: "",
driverLicense: "", // driverLicense: "",
plate: { leftDigits: 12, centerAlphabet: "الف", centerDigits: 345, ir: 22 }, // plate: { leftDigits: 12, centerAlphabet: "الف", centerDigits: 345, ir: 22 },
driverIsInsurer: true, // driverIsInsurer: true,
isNewCar: false, // isNewCar: false,
userNoCertificate: false, // userNoCertificate: false,
insurerBirthday: 1370, // insurerBirthday: 1370,
driverBirthday: "1370-01-01", // driverBirthday: "1370-01-01",
}, // },
expertDescription: { // expertDescription: {
desc: "توضیح حادثه", // desc: "توضیح حادثه",
accidentDate: "2025-01-15", // accidentDate: "2025-01-15",
accidentTime: "14:30", // accidentTime: "14:30",
weatherCondition: "صاف", // weatherCondition: "صاف",
roadCondition: "خشک", // roadCondition: "خشک",
lightCondition: "روز", // lightCondition: "روز",
}, // },
}, // },
}, // },
}, // },
schema: { type: "object" }, // schema: { type: "object" },
}) // })
@ApiResponse({ status: 200, description: "Blame form completed; next: add-locations" }) // @ApiResponse({ status: 200, description: "Blame form completed; next: add-locations" })
async completeBlameDataV2( async completeBlameDataV2(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@@ -313,24 +290,24 @@ export class ExpertInitiatedV2Controller {
} }
@Post("add-locations/:requestId") @Post("add-locations/:requestId")
@ApiOperation({ // @ApiOperation({
summary: "[V2] Submit one scene location for expert-initiated IN_PERSON blame", // summary: "[V2] Submit one scene location for expert-initiated IN_PERSON blame",
description: // description:
"Submit one scene location after complete-blame-data. This transitions workflow to WAITING_FOR_SIGNATURES.", // "Submit one scene location after complete-blame-data. This transitions workflow to WAITING_FOR_SIGNATURES.",
}) // })
@ApiParam({ name: "requestId", description: "Blame request ID" }) // @ApiParam({ name: "requestId", description: "Blame request ID" })
@ApiBody({ // @ApiBody({
type: ExpertCompleteLocationV2Dto, // type: ExpertCompleteLocationV2Dto,
examples: { // examples: {
scene: { // scene: {
summary: "One scene location (all IN_PERSON types)", // summary: "One scene location (all IN_PERSON types)",
value: { // value: {
location: { lat: 35.6892, lon: 51.389 }, // location: { lat: 35.6892, lon: 51.389 },
}, // },
}, // },
}, // },
}) // })
@ApiResponse({ status: 200, description: "Locations saved; next: upload party signature(s)" }) // @ApiResponse({ status: 200, description: "Locations saved; next: upload party signature(s)" })
async addLocationsV2( async addLocationsV2(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@@ -344,17 +321,17 @@ export class ExpertInitiatedV2Controller {
} }
@Post("upload-video/:requestId") @Post("upload-video/:requestId")
@ApiOperation({ // @ApiOperation({
summary: "[V2] Expert uploads video for expert-initiated BlameRequest", // summary: "[V2] Expert uploads video for expert-initiated BlameRequest",
}) // })
@ApiParam({ name: "requestId", description: "Blame request ID" }) // @ApiParam({ name: "requestId", description: "Blame request ID" })
@ApiConsumes("multipart/form-data") // @ApiConsumes("multipart/form-data")
@ApiBody({ // @ApiBody({
schema: { // schema: {
type: "object", // type: "object",
properties: { file: { type: "string", format: "binary" } }, // properties: { file: { type: "string", format: "binary" } },
}, // },
}) // })
@UseInterceptors( @UseInterceptors(
FileInterceptor("file", { FileInterceptor("file", {
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES }, limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
@@ -368,7 +345,7 @@ export class ExpertInitiatedV2Controller {
}), }),
}), }),
) )
@ApiResponse({ status: 200, description: "Video uploaded" }) // @ApiResponse({ status: 200, description: "Video uploaded" })
async uploadVideoV2( async uploadVideoV2(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@@ -383,17 +360,17 @@ export class ExpertInitiatedV2Controller {
} }
@Post("upload-voice/:requestId") @Post("upload-voice/:requestId")
@ApiOperation({ // @ApiOperation({
summary: "[V2] Expert uploads voice for expert-initiated BlameRequest", // summary: "[V2] Expert uploads voice for expert-initiated BlameRequest",
}) // })
@ApiParam({ name: "requestId", description: "Blame request ID" }) // @ApiParam({ name: "requestId", description: "Blame request ID" })
@ApiConsumes("multipart/form-data") // @ApiConsumes("multipart/form-data")
@ApiBody({ // @ApiBody({
schema: { // schema: {
type: "object", // type: "object",
properties: { voice: { type: "string", format: "binary" } }, // properties: { voice: { type: "string", format: "binary" } },
}, // },
}) // })
@UseInterceptors( @UseInterceptors(
FileInterceptor("voice", { FileInterceptor("voice", {
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES }, limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
@@ -408,7 +385,7 @@ export class ExpertInitiatedV2Controller {
}), }),
}), }),
) )
@ApiResponse({ status: 200, description: "Voice uploaded" }) // @ApiResponse({ status: 200, description: "Voice uploaded" })
async uploadVoiceV2( async uploadVoiceV2(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@@ -423,12 +400,12 @@ export class ExpertInitiatedV2Controller {
} }
@Post("add-accident-fields/:requestId") @Post("add-accident-fields/:requestId")
@ApiOperation({ // @ApiOperation({
summary: "[V2] Expert adds accident fields to expert-initiated BlameRequest", // summary: "[V2] Expert adds accident fields to expert-initiated BlameRequest",
}) // })
@ApiParam({ name: "requestId", description: "Blame request ID" }) // @ApiParam({ name: "requestId", description: "Blame request ID" })
@ApiBody({ type: ExpertAccidentFieldsDto }) // @ApiBody({ type: ExpertAccidentFieldsDto })
@ApiResponse({ status: 200, description: "Accident fields added" }) // @ApiResponse({ status: 200, description: "Accident fields added" })
async addAccidentFieldsV2( async addAccidentFieldsV2(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@@ -442,24 +419,24 @@ export class ExpertInitiatedV2Controller {
} }
@Post("upload-party-signature/:requestId") @Post("upload-party-signature/:requestId")
@ApiOperation({ // @ApiOperation({
summary: "[V2] Expert uploads party signature (IN_PERSON)", // summary: "[V2] Expert uploads party signature (IN_PERSON)",
description: // description:
"For IN_PERSON only. Upload a party's signature collected on-site. CAR_BODY: use partyRole FIRST once. THIRD_PARTY: upload FIRST then SECOND. When all required parties have signed, blame case completes.", // "For IN_PERSON only. Upload a party's signature collected on-site. CAR_BODY: use partyRole FIRST once. THIRD_PARTY: upload FIRST then SECOND. When all required parties have signed, blame case completes.",
}) // })
@ApiParam({ name: "requestId", description: "Blame request ID" }) // @ApiParam({ name: "requestId", description: "Blame request ID" })
@ApiConsumes("multipart/form-data") // @ApiConsumes("multipart/form-data")
@ApiBody({ // @ApiBody({
schema: { // schema: {
type: "object", // type: "object",
required: ["partyRole", "sign"], // required: ["partyRole", "sign"],
properties: { // properties: {
partyRole: { type: "string", enum: ["FIRST", "SECOND"] }, // partyRole: { type: "string", enum: ["FIRST", "SECOND"] },
isAccept: { type: "boolean", default: true }, // isAccept: { type: "boolean", default: true },
sign: { type: "string", format: "binary" }, // sign: { type: "string", format: "binary" },
}, // },
}, // },
}) // })
@UseInterceptors( @UseInterceptors(
FileInterceptor("sign", { FileInterceptor("sign", {
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES }, limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
@@ -473,7 +450,7 @@ export class ExpertInitiatedV2Controller {
}), }),
}), }),
) )
@ApiResponse({ status: 200, description: "Signature recorded" }) // @ApiResponse({ status: 200, description: "Signature recorded" })
async uploadPartySignatureV2( async uploadPartySignatureV2(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@@ -496,13 +473,13 @@ export class ExpertInitiatedV2Controller {
} }
@Post("create-claim-from-blame/:blameRequestId") @Post("create-claim-from-blame/:blameRequestId")
@ApiOperation({ // @ApiOperation({
summary: "[V2] Create claim from expert-initiated IN_PERSON blame", // summary: "[V2] Create claim from expert-initiated IN_PERSON blame",
description: // description:
"Field expert creates a claim on behalf of the damaged party. Blame must be COMPLETED (signatures collected). Then use claim v2 endpoints to fill parts, documents, and captures.", // "Field expert creates a claim on behalf of the damaged party. Blame must be COMPLETED (signatures collected). Then use claim v2 endpoints to fill parts, documents, and captures.",
}) // })
@ApiParam({ name: "blameRequestId", description: "Completed blame request ID" }) // @ApiParam({ name: "blameRequestId", description: "Completed blame request ID" })
@ApiResponse({ status: 201, description: "Claim created" }) // @ApiResponse({ status: 201, description: "Claim created" })
async createClaimFromBlame( async createClaimFromBlame(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Param("blameRequestId") blameRequestId: string, @Param("blameRequestId") blameRequestId: string,
@@ -514,12 +491,12 @@ export class ExpertInitiatedV2Controller {
} }
@Post("complete-claim-data/:claimRequestId") @Post("complete-claim-data/:claimRequestId")
@ApiOperation({ // @ApiOperation({
summary: "Submit claim-needed data (expert-initiated IN_PERSON)", // summary: "Submit claim-needed data (expert-initiated IN_PERSON)",
}) // })
@ApiParam({ name: "claimRequestId", description: "Claim file ID" }) // @ApiParam({ name: "claimRequestId", description: "Claim file ID" })
@ApiBody({ type: ExpertCompleteClaimDataDto }) // @ApiBody({ type: ExpertCompleteClaimDataDto })
@ApiResponse({ status: 200, description: "Claim data updated" }) // @ApiResponse({ status: 200, description: "Claim data updated" })
async completeClaimData( async completeClaimData(
@CurrentUser() expert: any, @CurrentUser() expert: any,
@Param("claimRequestId") claimRequestId: string, @Param("claimRequestId") claimRequestId: string,

View File

@@ -2,7 +2,7 @@ import { extname } from "node:path";
import { Body, Controller, Get, Param, Post, UploadedFile, UseGuards, UseInterceptors } from "@nestjs/common"; import { Body, Controller, Get, Param, Post, UploadedFile, UseGuards, UseInterceptors } from "@nestjs/common";
import { FileInterceptor } from "@nestjs/platform-express"; import { FileInterceptor } from "@nestjs/platform-express";
import { diskStorage } from "multer"; import { diskStorage } from "multer";
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiOperation, ApiParam, ApiTags } from "@nestjs/swagger"; import { ApiBearerAuth, ApiBody, ApiConsumes, ApiOperation, ApiParam, ApiTags, ApiExcludeController } from "@nestjs/swagger";
import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard"; import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard";
import { RolesGuard } from "src/auth/guards/role.guard"; import { RolesGuard } from "src/auth/guards/role.guard";
import { CurrentUser } from "src/decorators/user.decorator"; import { CurrentUser } from "src/decorators/user.decorator";
@@ -16,7 +16,8 @@ import { VerifyPartyOtpsDto } from "./dto/verify-party-otps.dto";
import { ExpertAccidentFieldsDto } from "./dto/expert-accident-fields.dto"; import { ExpertAccidentFieldsDto } from "./dto/expert-accident-fields.dto";
import { PartyRole } from "./entities/schema/partyRole.enum"; import { PartyRole } from "./entities/schema/partyRole.enum";
@ApiTags("registrar-initiated-blame (v1)") @ApiExcludeController()
// @ApiTags("registrar-initiated-blame (v1)")
@Controller("registrar-initiated-blame") @Controller("registrar-initiated-blame")
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(LocalActorAuthGuard, RolesGuard) @UseGuards(LocalActorAuthGuard, RolesGuard)
@@ -25,8 +26,8 @@ export class RegistrarInitiatedController {
constructor(private readonly requestManagementService: RequestManagementService) {} constructor(private readonly requestManagementService: RequestManagementService) {}
@Post("create") @Post("create")
@ApiOperation({ summary: "Registrar creates IN_PERSON blame file" }) // @ApiOperation({ summary: "Registrar creates IN_PERSON blame file" })
@ApiBody({ type: CreateRegistrarInitiatedFileDto }) // @ApiBody({ type: CreateRegistrarInitiatedFileDto })
create(@CurrentUser() registrar: any, @Body() dto: CreateRegistrarInitiatedFileDto) { create(@CurrentUser() registrar: any, @Body() dto: CreateRegistrarInitiatedFileDto) {
return this.requestManagementService.createRegistrarInitiatedBlame(registrar, dto); return this.requestManagementService.createRegistrarInitiatedBlame(registrar, dto);
} }
@@ -42,8 +43,8 @@ export class RegistrarInitiatedController {
} }
@Post("send-party-otps/:requestId") @Post("send-party-otps/:requestId")
@ApiParam({ name: "requestId" }) // @ApiParam({ name: "requestId" })
@ApiBody({ type: SendPartyOtpsDto }) // @ApiBody({ type: SendPartyOtpsDto })
sendPartyOtps( sendPartyOtps(
@CurrentUser() registrar: any, @CurrentUser() registrar: any,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@@ -53,8 +54,8 @@ export class RegistrarInitiatedController {
} }
@Post("verify-party-otps/:requestId") @Post("verify-party-otps/:requestId")
@ApiParam({ name: "requestId" }) // @ApiParam({ name: "requestId" })
@ApiBody({ type: VerifyPartyOtpsDto }) // @ApiBody({ type: VerifyPartyOtpsDto })
verifyPartyOtps( verifyPartyOtps(
@CurrentUser() registrar: any, @CurrentUser() registrar: any,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@@ -73,7 +74,7 @@ export class RegistrarInitiatedController {
} }
@Post("upload-video/:requestId") @Post("upload-video/:requestId")
@ApiConsumes("multipart/form-data") // @ApiConsumes("multipart/form-data")
@UseInterceptors( @UseInterceptors(
FileInterceptor("file", { FileInterceptor("file", {
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES }, limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
@@ -92,7 +93,7 @@ export class RegistrarInitiatedController {
} }
@Post("upload-voice/:requestId") @Post("upload-voice/:requestId")
@ApiConsumes("multipart/form-data") // @ApiConsumes("multipart/form-data")
@UseInterceptors( @UseInterceptors(
FileInterceptor("voice", { FileInterceptor("voice", {
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES }, limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },
@@ -111,7 +112,7 @@ export class RegistrarInitiatedController {
} }
@Post("add-accident-fields/:requestId") @Post("add-accident-fields/:requestId")
@ApiBody({ type: ExpertAccidentFieldsDto }) // @ApiBody({ type: ExpertAccidentFieldsDto })
addAccidentFields( addAccidentFields(
@CurrentUser() registrar: any, @CurrentUser() registrar: any,
@Param("requestId") requestId: string, @Param("requestId") requestId: string,
@@ -125,7 +126,7 @@ export class RegistrarInitiatedController {
} }
@Post("upload-party-signature/:requestId") @Post("upload-party-signature/:requestId")
@ApiConsumes("multipart/form-data") // @ApiConsumes("multipart/form-data")
@UseInterceptors( @UseInterceptors(
FileInterceptor("sign", { FileInterceptor("sign", {
limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES }, limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES },

View File

@@ -23,6 +23,7 @@ import {
ApiParam, ApiParam,
ApiTags, ApiTags,
ApiOperation, ApiOperation,
ApiExcludeController,
} from "@nestjs/swagger"; } from "@nestjs/swagger";
import { Request } from "express"; import { Request } from "express";
import { diskStorage } from "multer"; import { diskStorage } from "multer";
@@ -43,8 +44,9 @@ import {
} from "./dto/create-request-management.dto"; } from "./dto/create-request-management.dto";
import { RequestManagementService } from "./request-management.service"; import { RequestManagementService } from "./request-management.service";
@ApiExcludeController()
@Controller("blame-request-management") @Controller("blame-request-management")
@ApiTags("blame-request-management") // @ApiTags("blame-request-management")
@ApiBearerAuth() @ApiBearerAuth()
@UseGuards(GlobalGuard, RolesGuard) @UseGuards(GlobalGuard, RolesGuard)
@Roles(RoleEnum.USER) @Roles(RoleEnum.USER)

View File

@@ -42,6 +42,7 @@ import { ExpertInitiatedV2Controller } from "./expert-initiated.v2.controller";
import { ExpertInitiatedBlameMirrorController } from "./expert-initiated-blame.mirror.controller"; import { ExpertInitiatedBlameMirrorController } from "./expert-initiated-blame.mirror.controller";
import { RegistrarInitiatedController } from "./registrar-initiated.controller"; import { RegistrarInitiatedController } from "./registrar-initiated.controller";
import { RegistrarBlameMirrorController } from "./registrar-blame.mirror.controller"; import { RegistrarBlameMirrorController } from "./registrar-blame.mirror.controller";
import { ExpertInitiatedBlameV3MirrorController } from "./expert-initiated-blame-v3.mirror.controller";
import { InquiryRefreshController } from "./inquiry-refresh.controller"; import { InquiryRefreshController } from "./inquiry-refresh.controller";
import { InquiryRefreshService } from "./inquiry-refresh.service"; import { InquiryRefreshService } from "./inquiry-refresh.service";
@@ -77,6 +78,7 @@ import { InquiryRefreshService } from "./inquiry-refresh.service";
ExpertInitiatedController, ExpertInitiatedController,
ExpertInitiatedV2Controller, ExpertInitiatedV2Controller,
ExpertInitiatedBlameMirrorController, ExpertInitiatedBlameMirrorController,
ExpertInitiatedBlameV3MirrorController,
RegistrarInitiatedController, RegistrarInitiatedController,
RegistrarBlameMirrorController, RegistrarBlameMirrorController,
InquiryRefreshController, InquiryRefreshController,

File diff suppressed because it is too large Load Diff