forked from Yara724/api
YARA-784
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { AddPlateDto } from "src/profile/dto/user/AddPlateDto";
|
||||||
|
import {
|
||||||
|
CarBodyFormDto,
|
||||||
|
DescriptionDto,
|
||||||
|
InitialFormDto,
|
||||||
|
} from "./create-request-management.dto";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* V2 (IN_PERSON): single expert description/location for the scene.
|
||||||
|
*/
|
||||||
|
export class ExpertCompleteCarBodyFormV2Dto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: "First party phone number",
|
||||||
|
example: "09123456789",
|
||||||
|
})
|
||||||
|
firstPartyPhoneNumber: string;
|
||||||
|
|
||||||
|
@ApiProperty({ type: InitialFormDto })
|
||||||
|
firstPartyInitialForm: InitialFormDto;
|
||||||
|
|
||||||
|
@ApiProperty({ type: CarBodyFormDto })
|
||||||
|
carBodyForm: CarBodyFormDto;
|
||||||
|
|
||||||
|
@ApiProperty({ type: AddPlateDto })
|
||||||
|
firstPartyPlate: AddPlateDto;
|
||||||
|
|
||||||
|
@ApiProperty({ type: DescriptionDto })
|
||||||
|
expertDescription: DescriptionDto;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { LocationDto } from "./create-request-management.dto";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* V2 (IN_PERSON): one scene location provided by expert.
|
||||||
|
*/
|
||||||
|
export class ExpertCompleteLocationV2Dto {
|
||||||
|
@ApiProperty({ type: LocationDto })
|
||||||
|
location: LocationDto;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { AddPlateDto } from "src/profile/dto/user/AddPlateDto";
|
||||||
|
import { DescriptionDto, InitialFormDto } from "./create-request-management.dto";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* V2 (IN_PERSON): second party inputs without separate description/location.
|
||||||
|
*/
|
||||||
|
export class ExpertSecondPartyInfoV2Dto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: "Second party phone number",
|
||||||
|
example: "09123456789",
|
||||||
|
})
|
||||||
|
phoneNumber: string;
|
||||||
|
|
||||||
|
@ApiProperty({ type: InitialFormDto })
|
||||||
|
initialForm: InitialFormDto;
|
||||||
|
|
||||||
|
@ApiProperty({ type: AddPlateDto })
|
||||||
|
plate: AddPlateDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* V2 (IN_PERSON): single expert description shared for the whole scene.
|
||||||
|
*/
|
||||||
|
export class ExpertCompleteThirdPartyFormV2Dto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: "First party phone number",
|
||||||
|
example: "09123456789",
|
||||||
|
})
|
||||||
|
firstPartyPhoneNumber: string;
|
||||||
|
|
||||||
|
@ApiProperty({ type: InitialFormDto })
|
||||||
|
firstPartyInitialForm: InitialFormDto;
|
||||||
|
|
||||||
|
@ApiProperty({ type: AddPlateDto })
|
||||||
|
firstPartyPlate: AddPlateDto;
|
||||||
|
|
||||||
|
@ApiProperty({ type: ExpertSecondPartyInfoV2Dto })
|
||||||
|
secondParty: ExpertSecondPartyInfoV2Dto;
|
||||||
|
|
||||||
|
@ApiProperty({ type: DescriptionDto })
|
||||||
|
expertDescription: DescriptionDto;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description:
|
||||||
|
"Phone number of the guilty party. Must match either first or second party phone number.",
|
||||||
|
example: "09123456789",
|
||||||
|
})
|
||||||
|
guiltyPartyPhoneNumber: string;
|
||||||
|
}
|
||||||
@@ -27,14 +27,14 @@ import { Roles } from "src/decorators/roles.decorator";
|
|||||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||||
import { RequestManagementService } from "./request-management.service";
|
import { RequestManagementService } from "./request-management.service";
|
||||||
import { CreateExpertInitiatedFileDto } from "./dto/expert-initiated.dto";
|
import { CreateExpertInitiatedFileDto } from "./dto/expert-initiated.dto";
|
||||||
import { ExpertCompleteThirdPartyFormDto } from "./dto/expert-complete-third-party-form.dto";
|
import { ExpertCompleteThirdPartyFormV2Dto } from "./dto/expert-complete-third-party-form.v2.dto";
|
||||||
import { ExpertCompleteCarBodyFormDto } from "./dto/expert-complete-car-body-form.dto";
|
import { ExpertCompleteCarBodyFormV2Dto } from "./dto/expert-complete-car-body-form.v2.dto";
|
||||||
import { ExpertAccidentFieldsDto } from "./dto/expert-accident-fields.dto";
|
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 { ExpertUploadPartySignatureDto } from "./dto/expert-upload-party-signature.dto";
|
import { ExpertUploadPartySignatureDto } from "./dto/expert-upload-party-signature.dto";
|
||||||
import { VerifyPartyOtpsDto } from "./dto/verify-party-otps.dto";
|
import { VerifyPartyOtpsDto } from "./dto/verify-party-otps.dto";
|
||||||
import { SendPartyOtpsDto } from "./dto/send-party-otps.dto";
|
import { SendPartyOtpsDto } from "./dto/send-party-otps.dto";
|
||||||
import { ExpertCompleteLocationsDto } from "./dto/expert-complete-locations.dto";
|
import { ExpertCompleteLocationV2Dto } from "./dto/expert-complete-location.v2.dto";
|
||||||
import { ClaimRequestManagementService } from "src/claim-request-management/claim-request-management.service";
|
import { ClaimRequestManagementService } from "src/claim-request-management/claim-request-management.service";
|
||||||
import { PartyRole } from "./entities/schema/partyRole.enum";
|
import { PartyRole } from "./entities/schema/partyRole.enum";
|
||||||
|
|
||||||
@@ -225,11 +225,11 @@ export class ExpertInitiatedV2Controller {
|
|||||||
@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 locations. After this, call add-locations to submit 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. All nested fields are listed so you can fill or test without guessing property names.",
|
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",
|
||||||
@@ -253,7 +253,7 @@ export class ExpertInitiatedV2Controller {
|
|||||||
insurerBirthday: 13770624,
|
insurerBirthday: 13770624,
|
||||||
driverBirthday: "1370-01-01",
|
driverBirthday: "1370-01-01",
|
||||||
},
|
},
|
||||||
firstPartyDescription: { desc: "توضیح حادثه طرف اول" },
|
expertDescription: { desc: "توضیح حادثه کارشناس (مشترک برای صحنه)" },
|
||||||
secondParty: {
|
secondParty: {
|
||||||
phoneNumber: "09187654321",
|
phoneNumber: "09187654321",
|
||||||
initialForm: {
|
initialForm: {
|
||||||
@@ -273,7 +273,6 @@ export class ExpertInitiatedV2Controller {
|
|||||||
insurerBirthday: 13700720,
|
insurerBirthday: 13700720,
|
||||||
driverBirthday: "1370-01-01",
|
driverBirthday: "1370-01-01",
|
||||||
},
|
},
|
||||||
description: { desc: "توضیح حادثه طرف دوم" },
|
|
||||||
},
|
},
|
||||||
guiltyPartyPhoneNumber: "09123456789",
|
guiltyPartyPhoneNumber: "09123456789",
|
||||||
},
|
},
|
||||||
@@ -302,7 +301,7 @@ export class ExpertInitiatedV2Controller {
|
|||||||
insurerBirthday: 1370,
|
insurerBirthday: 1370,
|
||||||
driverBirthday: "1370-01-01",
|
driverBirthday: "1370-01-01",
|
||||||
},
|
},
|
||||||
firstPartyDescription: {
|
expertDescription: {
|
||||||
desc: "توضیح حادثه",
|
desc: "توضیح حادثه",
|
||||||
accidentDate: "2025-01-15",
|
accidentDate: "2025-01-15",
|
||||||
accidentTime: "14:30",
|
accidentTime: "14:30",
|
||||||
@@ -319,7 +318,7 @@ export class ExpertInitiatedV2Controller {
|
|||||||
async completeBlameDataV2(
|
async completeBlameDataV2(
|
||||||
@CurrentUser() expert: any,
|
@CurrentUser() expert: any,
|
||||||
@Param("requestId") requestId: string,
|
@Param("requestId") requestId: string,
|
||||||
@Body() formData: any,
|
@Body() formData: ExpertCompleteThirdPartyFormV2Dto | ExpertCompleteCarBodyFormV2Dto,
|
||||||
) {
|
) {
|
||||||
return this.requestManagementService.expertCompleteBlameDataV2(
|
return this.requestManagementService.expertCompleteBlameDataV2(
|
||||||
expert,
|
expert,
|
||||||
@@ -330,25 +329,18 @@ export class ExpertInitiatedV2Controller {
|
|||||||
|
|
||||||
@Post("add-locations/:requestId")
|
@Post("add-locations/:requestId")
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "[V2] Submit location(s) for expert-initiated IN_PERSON blame",
|
summary: "[V2] Submit one scene location for expert-initiated IN_PERSON blame",
|
||||||
description:
|
description:
|
||||||
"Submit first party (and second party for THIRD_PARTY) 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: ExpertCompleteLocationsDto,
|
type: ExpertCompleteLocationV2Dto,
|
||||||
examples: {
|
examples: {
|
||||||
THIRD_PARTY: {
|
scene: {
|
||||||
summary: "THIRD_PARTY locations",
|
summary: "One scene location (all IN_PERSON types)",
|
||||||
value: {
|
value: {
|
||||||
firstPartyLocation: { lat: 35.6892, lon: 51.389 },
|
location: { lat: 35.6892, lon: 51.389 },
|
||||||
secondPartyLocation: { lat: 35.7001, lon: 51.4102 },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
CAR_BODY: {
|
|
||||||
summary: "CAR_BODY location",
|
|
||||||
value: {
|
|
||||||
firstPartyLocation: { lat: 35.6892, lon: 51.389 },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -357,12 +349,12 @@ export class ExpertInitiatedV2Controller {
|
|||||||
async addLocationsV2(
|
async addLocationsV2(
|
||||||
@CurrentUser() expert: any,
|
@CurrentUser() expert: any,
|
||||||
@Param("requestId") requestId: string,
|
@Param("requestId") requestId: string,
|
||||||
@Body() dto: ExpertCompleteLocationsDto,
|
@Body() dto: ExpertCompleteLocationV2Dto,
|
||||||
) {
|
) {
|
||||||
return this.requestManagementService.expertAddLocationsForBlameV2(
|
return this.requestManagementService.expertAddLocationsForBlameV2(
|
||||||
expert,
|
expert,
|
||||||
requestId,
|
requestId,
|
||||||
dto,
|
dto as any,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4039,6 +4039,9 @@ export class RequestManagementService {
|
|||||||
if (!formData.carBodyForm) {
|
if (!formData.carBodyForm) {
|
||||||
throw new BadRequestException("carBodyForm is required.");
|
throw new BadRequestException("carBodyForm is required.");
|
||||||
}
|
}
|
||||||
|
if (!formData?.expertDescription?.desc) {
|
||||||
|
throw new BadRequestException("expertDescription.desc is required.");
|
||||||
|
}
|
||||||
|
|
||||||
const firstPartyUserId = await this.getOrCreateUserByPhoneNumber(
|
const firstPartyUserId = await this.getOrCreateUserByPhoneNumber(
|
||||||
formData.firstPartyPhoneNumber,
|
formData.firstPartyPhoneNumber,
|
||||||
@@ -4110,12 +4113,12 @@ export class RequestManagementService {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
statement: {
|
statement: {
|
||||||
description: formData.firstPartyDescription?.desc,
|
description: formData.expertDescription?.desc,
|
||||||
accidentDate: formData.firstPartyDescription?.accidentDate,
|
accidentDate: formData.expertDescription?.accidentDate,
|
||||||
accidentTime: formData.firstPartyDescription?.accidentTime,
|
accidentTime: formData.expertDescription?.accidentTime,
|
||||||
weatherCondition: formData.firstPartyDescription?.weatherCondition,
|
weatherCondition: formData.expertDescription?.weatherCondition,
|
||||||
roadCondition: formData.firstPartyDescription?.roadCondition,
|
roadCondition: formData.expertDescription?.roadCondition,
|
||||||
lightCondition: formData.firstPartyDescription?.lightCondition,
|
lightCondition: formData.expertDescription?.lightCondition,
|
||||||
},
|
},
|
||||||
location: req.parties?.[0]?.location,
|
location: req.parties?.[0]?.location,
|
||||||
carBodyFirstForm: {
|
carBodyFirstForm: {
|
||||||
@@ -4184,6 +4187,9 @@ export class RequestManagementService {
|
|||||||
if (!formData.secondParty || !formData.guiltyPartyPhoneNumber) {
|
if (!formData.secondParty || !formData.guiltyPartyPhoneNumber) {
|
||||||
throw new BadRequestException("secondParty and guiltyPartyPhoneNumber are required.");
|
throw new BadRequestException("secondParty and guiltyPartyPhoneNumber are required.");
|
||||||
}
|
}
|
||||||
|
if (!formData?.expertDescription?.desc) {
|
||||||
|
throw new BadRequestException("expertDescription.desc is required.");
|
||||||
|
}
|
||||||
|
|
||||||
const firstPartyUserId = await this.getOrCreateUserByPhoneNumber(
|
const firstPartyUserId = await this.getOrCreateUserByPhoneNumber(
|
||||||
formData.firstPartyPhoneNumber,
|
formData.firstPartyPhoneNumber,
|
||||||
@@ -4254,7 +4260,7 @@ export class RequestManagementService {
|
|||||||
firstPartyUserId,
|
firstPartyUserId,
|
||||||
formData.firstPartyInitialForm,
|
formData.firstPartyInitialForm,
|
||||||
formData.firstPartyPlate,
|
formData.firstPartyPlate,
|
||||||
formData.firstPartyDescription?.desc || "",
|
formData.expertDescription?.desc || "",
|
||||||
PartyRole.FIRST,
|
PartyRole.FIRST,
|
||||||
);
|
);
|
||||||
const secondParty = await buildPartyFromForm(
|
const secondParty = await buildPartyFromForm(
|
||||||
@@ -4262,7 +4268,7 @@ export class RequestManagementService {
|
|||||||
secondPartyUserId,
|
secondPartyUserId,
|
||||||
formData.secondParty.initialForm,
|
formData.secondParty.initialForm,
|
||||||
formData.secondParty.plate,
|
formData.secondParty.plate,
|
||||||
formData.secondParty.description?.desc || "",
|
formData.expertDescription?.desc || "",
|
||||||
PartyRole.SECOND,
|
PartyRole.SECOND,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -4320,7 +4326,7 @@ export class RequestManagementService {
|
|||||||
async expertAddLocationsForBlameV2(
|
async expertAddLocationsForBlameV2(
|
||||||
expert: any,
|
expert: any,
|
||||||
requestId: string,
|
requestId: string,
|
||||||
dto: { firstPartyLocation: LocationDto; secondPartyLocation?: LocationDto },
|
dto: { location: LocationDto },
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const req = await this.blameRequestDbService.findById(requestId);
|
const req = await this.blameRequestDbService.findById(requestId);
|
||||||
if (!req) throw new NotFoundException("Request not found");
|
if (!req) throw new NotFoundException("Request not found");
|
||||||
@@ -4336,21 +4342,10 @@ export class RequestManagementService {
|
|||||||
|
|
||||||
const firstIdx = this.getPartyIndex(req, PartyRole.FIRST);
|
const firstIdx = this.getPartyIndex(req, PartyRole.FIRST);
|
||||||
if (firstIdx === -1) throw new BadRequestException("First party not found");
|
if (firstIdx === -1) throw new BadRequestException("First party not found");
|
||||||
if (!dto?.firstPartyLocation) {
|
if (!dto?.location) {
|
||||||
throw new BadRequestException("firstPartyLocation is required");
|
throw new BadRequestException("location is required");
|
||||||
}
|
|
||||||
req.parties[firstIdx].location = dto.firstPartyLocation as any;
|
|
||||||
|
|
||||||
if (req.type === BlameRequestType.THIRD_PARTY) {
|
|
||||||
const secondIdx = this.getPartyIndex(req, PartyRole.SECOND);
|
|
||||||
if (secondIdx === -1) throw new BadRequestException("Second party not found");
|
|
||||||
if (!dto?.secondPartyLocation) {
|
|
||||||
throw new BadRequestException(
|
|
||||||
"secondPartyLocation is required for THIRD_PARTY",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
req.parties[secondIdx].location = dto.secondPartyLocation as any;
|
|
||||||
}
|
}
|
||||||
|
req.parties[firstIdx].location = dto.location as any;
|
||||||
|
|
||||||
const completed = Array.isArray(req.workflow?.completedSteps)
|
const completed = Array.isArray(req.workflow?.completedSteps)
|
||||||
? req.workflow.completedSteps
|
? req.workflow.completedSteps
|
||||||
@@ -4358,12 +4353,6 @@ export class RequestManagementService {
|
|||||||
if (!completed.includes(WorkflowStep.FIRST_LOCATION as any)) {
|
if (!completed.includes(WorkflowStep.FIRST_LOCATION as any)) {
|
||||||
completed.push(WorkflowStep.FIRST_LOCATION as any);
|
completed.push(WorkflowStep.FIRST_LOCATION as any);
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
req.type === BlameRequestType.THIRD_PARTY &&
|
|
||||||
!completed.includes(WorkflowStep.SECOND_LOCATION as any)
|
|
||||||
) {
|
|
||||||
completed.push(WorkflowStep.SECOND_LOCATION as any);
|
|
||||||
}
|
|
||||||
|
|
||||||
req.workflow = {
|
req.workflow = {
|
||||||
...(req.workflow || {}),
|
...(req.workflow || {}),
|
||||||
@@ -4383,8 +4372,7 @@ export class RequestManagementService {
|
|||||||
actorType: expert?.role === RoleEnum.REGISTRAR ? "registrar" : "field_expert",
|
actorType: expert?.role === RoleEnum.REGISTRAR ? "registrar" : "field_expert",
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
hasFirstLocation: true,
|
hasLocation: true,
|
||||||
hasSecondLocation: req.type === BlameRequestType.THIRD_PARTY,
|
|
||||||
},
|
},
|
||||||
} as any);
|
} as any);
|
||||||
|
|
||||||
@@ -4467,6 +4455,12 @@ export class RequestManagementService {
|
|||||||
const firstPartyIndex = this.getPartyIndex(req, PartyRole.FIRST);
|
const firstPartyIndex = this.getPartyIndex(req, PartyRole.FIRST);
|
||||||
if (firstPartyIndex === -1) throw new BadRequestException("First party not found");
|
if (firstPartyIndex === -1) throw new BadRequestException("First party not found");
|
||||||
const firstParty = req.parties[firstPartyIndex];
|
const firstParty = req.parties[firstPartyIndex];
|
||||||
|
if (
|
||||||
|
Array.isArray(firstParty?.evidence?.voices) &&
|
||||||
|
firstParty.evidence.voices.length > 0
|
||||||
|
) {
|
||||||
|
throw new ConflictException("Voice already uploaded for this file");
|
||||||
|
}
|
||||||
|
|
||||||
const voiceDocument = await this.blameVoiceDbService.create({
|
const voiceDocument = await this.blameVoiceDbService.create({
|
||||||
fileName: voiceFile.filename,
|
fileName: voiceFile.filename,
|
||||||
|
|||||||
Reference in New Issue
Block a user