This commit is contained in:
2026-04-06 14:15:31 +03:30
parent f77da50d1f
commit 1b68a81204
5 changed files with 144 additions and 30 deletions

View File

@@ -1,6 +1,5 @@
import { ApiProperty } from "@nestjs/swagger"; import { ApiProperty } from "@nestjs/swagger";
import { AddPlateDto } from "src/profile/dto/user/AddPlateDto"; import { AddPlateDto } from "src/profile/dto/user/AddPlateDto";
import { LocationDto } from "./create-request-management.dto";
import { DescriptionDto } from "./create-request-management.dto"; import { DescriptionDto } from "./create-request-management.dto";
import { InitialFormDto, CarBodyFormDto } from "./create-request-management.dto"; import { InitialFormDto, CarBodyFormDto } from "./create-request-management.dto";
@@ -24,9 +23,6 @@ export class ExpertCompleteCarBodyFormDto {
@ApiProperty({ type: AddPlateDto }) @ApiProperty({ type: AddPlateDto })
firstPartyPlate: AddPlateDto; firstPartyPlate: AddPlateDto;
@ApiProperty({ type: LocationDto })
firstPartyLocation: LocationDto;
@ApiProperty({ type: DescriptionDto }) @ApiProperty({ type: DescriptionDto })
firstPartyDescription: DescriptionDto; firstPartyDescription: DescriptionDto;
} }

View File

@@ -0,0 +1,11 @@
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { LocationDto } from "./create-request-management.dto";
export class ExpertCompleteLocationsDto {
@ApiProperty({ type: LocationDto })
firstPartyLocation: LocationDto;
@ApiPropertyOptional({ type: LocationDto })
secondPartyLocation?: LocationDto;
}

View File

@@ -1,6 +1,5 @@
import { ApiProperty } from "@nestjs/swagger"; import { ApiProperty } from "@nestjs/swagger";
import { AddPlateDto } from "src/profile/dto/user/AddPlateDto"; import { AddPlateDto } from "src/profile/dto/user/AddPlateDto";
import { LocationDto } from "./create-request-management.dto";
import { DescriptionDto } from "./create-request-management.dto"; import { DescriptionDto } from "./create-request-management.dto";
import { InitialFormDto } from "./create-request-management.dto"; import { InitialFormDto } from "./create-request-management.dto";
@@ -20,9 +19,6 @@ export class ExpertSecondPartyInfoDto {
@ApiProperty({ type: AddPlateDto }) @ApiProperty({ type: AddPlateDto })
plate: AddPlateDto; plate: AddPlateDto;
@ApiProperty({ type: LocationDto })
location: LocationDto;
@ApiProperty({ type: DescriptionDto }) @ApiProperty({ type: DescriptionDto })
description: DescriptionDto; description: DescriptionDto;
} }
@@ -44,9 +40,6 @@ export class ExpertCompleteThirdPartyFormDto {
@ApiProperty({ type: AddPlateDto }) @ApiProperty({ type: AddPlateDto })
firstPartyPlate: AddPlateDto; firstPartyPlate: AddPlateDto;
@ApiProperty({ type: LocationDto })
firstPartyLocation: LocationDto;
@ApiProperty({ type: DescriptionDto }) @ApiProperty({ type: DescriptionDto })
firstPartyDescription: DescriptionDto; firstPartyDescription: DescriptionDto;

View File

@@ -34,6 +34,7 @@ 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 { 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";
@@ -224,7 +225,7 @@ 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. After this, status becomes WAITING_FOR_SIGNATURES; use upload-party-signature to collect party signatures.", "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.",
}) })
@ApiParam({ name: "requestId", description: "Blame request ID" }) @ApiParam({ name: "requestId", description: "Blame request ID" })
@ApiBody({ @ApiBody({
@@ -252,7 +253,6 @@ export class ExpertInitiatedV2Controller {
insurerBirthday: 13770624, insurerBirthday: 13770624,
driverBirthday: "1370-01-01", driverBirthday: "1370-01-01",
}, },
firstPartyLocation: { lat: 35.6892, lon: 51.389 },
firstPartyDescription: { desc: "توضیح حادثه طرف اول" }, firstPartyDescription: { desc: "توضیح حادثه طرف اول" },
secondParty: { secondParty: {
phoneNumber: "09187654321", phoneNumber: "09187654321",
@@ -273,7 +273,6 @@ export class ExpertInitiatedV2Controller {
insurerBirthday: 13700720, insurerBirthday: 13700720,
driverBirthday: "1370-01-01", driverBirthday: "1370-01-01",
}, },
location: { lat: 35.6892, lon: 51.389 },
description: { desc: "توضیح حادثه طرف دوم" }, description: { desc: "توضیح حادثه طرف دوم" },
}, },
guiltyPartyPhoneNumber: "09123456789", guiltyPartyPhoneNumber: "09123456789",
@@ -303,7 +302,6 @@ export class ExpertInitiatedV2Controller {
insurerBirthday: 1370, insurerBirthday: 1370,
driverBirthday: "1370-01-01", driverBirthday: "1370-01-01",
}, },
firstPartyLocation: { lat: 35.6892, lon: 51.389 },
firstPartyDescription: { firstPartyDescription: {
desc: "توضیح حادثه", desc: "توضیح حادثه",
accidentDate: "2025-01-15", accidentDate: "2025-01-15",
@@ -317,7 +315,7 @@ export class ExpertInitiatedV2Controller {
}, },
schema: { type: "object" }, schema: { type: "object" },
}) })
@ApiResponse({ status: 200, description: "Blame form completed; next: upload party signature(s)" }) @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,
@@ -330,6 +328,44 @@ export class ExpertInitiatedV2Controller {
); );
} }
@Post("add-locations/:requestId")
@ApiOperation({
summary: "[V2] Submit location(s) for expert-initiated IN_PERSON blame",
description:
"Submit first party (and second party for THIRD_PARTY) location after complete-blame-data. This transitions workflow to WAITING_FOR_SIGNATURES.",
})
@ApiParam({ name: "requestId", description: "Blame request ID" })
@ApiBody({
type: ExpertCompleteLocationsDto,
examples: {
THIRD_PARTY: {
summary: "THIRD_PARTY locations",
value: {
firstPartyLocation: { 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 },
},
},
},
})
@ApiResponse({ status: 200, description: "Locations saved; next: upload party signature(s)" })
async addLocationsV2(
@CurrentUser() expert: any,
@Param("requestId") requestId: string,
@Body() dto: ExpertCompleteLocationsDto,
) {
return this.requestManagementService.expertAddLocationsForBlameV2(
expert,
requestId,
dto,
);
}
@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",

View File

@@ -4100,7 +4100,7 @@ export class RequestManagementService {
roadCondition: formData.firstPartyDescription?.roadCondition, roadCondition: formData.firstPartyDescription?.roadCondition,
lightCondition: formData.firstPartyDescription?.lightCondition, lightCondition: formData.firstPartyDescription?.lightCondition,
}, },
location: formData.firstPartyLocation, location: req.parties?.[0]?.location,
carBodyFirstForm: { carBodyFirstForm: {
car: !!formData.carBodyForm.car, car: !!formData.carBodyForm.car,
object: !!formData.carBodyForm.object, object: !!formData.carBodyForm.object,
@@ -4110,20 +4110,19 @@ export class RequestManagementService {
req.parties = [firstParty]; req.parties = [firstParty];
req.workflow = { req.workflow = {
currentStep: WorkflowStep.WAITING_FOR_SIGNATURES as any, currentStep: WorkflowStep.FIRST_LOCATION as any,
nextStep: undefined, nextStep: WorkflowStep.WAITING_FOR_SIGNATURES as any,
completedSteps: [ completedSteps: [
WorkflowStep.CREATED, WorkflowStep.CREATED,
WorkflowStep.CAR_BODY_ACCIDENT_TYPE, WorkflowStep.CAR_BODY_ACCIDENT_TYPE,
WorkflowStep.FIRST_VIDEO, WorkflowStep.FIRST_VIDEO,
WorkflowStep.FIRST_INITIAL_FORM, WorkflowStep.FIRST_INITIAL_FORM,
WorkflowStep.FIRST_LOCATION,
WorkflowStep.FIRST_VOICE, WorkflowStep.FIRST_VOICE,
WorkflowStep.FIRST_DESCRIPTION, WorkflowStep.FIRST_DESCRIPTION,
].filter((s) => s), ].filter((s) => s),
locked: false, locked: false,
}; };
req.status = CaseStatus.WAITING_FOR_SIGNATURES; req.status = CaseStatus.OPEN;
req.blameStatus = BlameStatus.AGREED; req.blameStatus = BlameStatus.AGREED;
(req as any).filledBy = FilledBy.EXPERT; (req as any).filledBy = FilledBy.EXPERT;
@@ -4181,7 +4180,6 @@ export class RequestManagementService {
userId: Types.ObjectId, userId: Types.ObjectId,
initialForm: any, initialForm: any,
plateDto: any, plateDto: any,
locationDto: any,
desc: string, desc: string,
role: PartyRole, role: PartyRole,
) => { ) => {
@@ -4229,7 +4227,7 @@ export class RequestManagementService {
financialCeiling: sandHubReport?.FinancialCvrCptl || sandHubReport?.FinancialCommitment, financialCeiling: sandHubReport?.FinancialCvrCptl || sandHubReport?.FinancialCommitment,
}, },
statement: { description: desc }, statement: { description: desc },
location: locationDto, location: undefined,
evidence: {}, evidence: {},
}; };
}; };
@@ -4239,7 +4237,6 @@ export class RequestManagementService {
firstPartyUserId, firstPartyUserId,
formData.firstPartyInitialForm, formData.firstPartyInitialForm,
formData.firstPartyPlate, formData.firstPartyPlate,
formData.firstPartyLocation,
formData.firstPartyDescription?.desc || "", formData.firstPartyDescription?.desc || "",
PartyRole.FIRST, PartyRole.FIRST,
); );
@@ -4248,7 +4245,6 @@ export class RequestManagementService {
secondPartyUserId, secondPartyUserId,
formData.secondParty.initialForm, formData.secondParty.initialForm,
formData.secondParty.plate, formData.secondParty.plate,
formData.secondParty.location,
formData.secondParty.description?.desc || "", formData.secondParty.description?.desc || "",
PartyRole.SECOND, PartyRole.SECOND,
); );
@@ -4261,24 +4257,22 @@ export class RequestManagementService {
if (!req.expert) req.expert = {} as any; if (!req.expert) req.expert = {} as any;
req.expert.decision = { guiltyPartyId: new Types.ObjectId(guiltyPartyId) } as any; req.expert.decision = { guiltyPartyId: new Types.ObjectId(guiltyPartyId) } as any;
req.workflow = { req.workflow = {
currentStep: WorkflowStep.WAITING_FOR_SIGNATURES as any, currentStep: WorkflowStep.FIRST_LOCATION as any,
nextStep: undefined, nextStep: WorkflowStep.SECOND_LOCATION as any,
completedSteps: [ completedSteps: [
WorkflowStep.CREATED, WorkflowStep.CREATED,
WorkflowStep.FIRST_BLAME_CONFESSION, WorkflowStep.FIRST_BLAME_CONFESSION,
WorkflowStep.FIRST_VIDEO, WorkflowStep.FIRST_VIDEO,
WorkflowStep.FIRST_INITIAL_FORM, WorkflowStep.FIRST_INITIAL_FORM,
WorkflowStep.FIRST_LOCATION,
WorkflowStep.FIRST_VOICE, WorkflowStep.FIRST_VOICE,
WorkflowStep.FIRST_DESCRIPTION, WorkflowStep.FIRST_DESCRIPTION,
WorkflowStep.SECOND_INITIAL_FORM, WorkflowStep.SECOND_INITIAL_FORM,
WorkflowStep.SECOND_LOCATION,
WorkflowStep.SECOND_VOICE, WorkflowStep.SECOND_VOICE,
WorkflowStep.SECOND_DESCRIPTION, WorkflowStep.SECOND_DESCRIPTION,
].filter((s) => s), ].filter((s) => s),
locked: false, locked: false,
}; };
req.status = CaseStatus.WAITING_FOR_SIGNATURES; req.status = CaseStatus.OPEN;
req.blameStatus = BlameStatus.AGREED; req.blameStatus = BlameStatus.AGREED;
(req as any).filledBy = FilledBy.EXPERT; (req as any).filledBy = FilledBy.EXPERT;
@@ -4302,6 +4296,90 @@ export class RequestManagementService {
}; };
} }
/**
* V2: Expert submits location(s) for expert-initiated IN_PERSON blame.
* This is separated from complete-blame-data to support dedicated location UI step.
*/
async expertAddLocationsForBlameV2(
expert: any,
requestId: string,
dto: { firstPartyLocation: LocationDto; secondPartyLocation?: LocationDto },
): Promise<any> {
const req = await this.blameRequestDbService.findById(requestId);
if (!req) throw new NotFoundException("Request not found");
if (
(!req.expertInitiated && !req.registrarInitiated) ||
req.creationMethod !== CreationMethod.IN_PERSON
) {
throw new BadRequestException(
"This endpoint is only for expert-initiated IN_PERSON blame files.",
);
}
this.verifyExpertAccessForBlameV2(req, expert);
const firstIdx = this.getPartyIndex(req, PartyRole.FIRST);
if (firstIdx === -1) throw new BadRequestException("First party not found");
if (!dto?.firstPartyLocation) {
throw new BadRequestException("firstPartyLocation 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;
}
const completed = Array.isArray(req.workflow?.completedSteps)
? req.workflow.completedSteps
: [];
if (!completed.includes(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 || {}),
currentStep: WorkflowStep.WAITING_FOR_SIGNATURES as any,
nextStep: undefined,
completedSteps: completed,
locked: false,
} as any;
req.status = CaseStatus.WAITING_FOR_SIGNATURES;
if (!Array.isArray(req.history)) req.history = [];
req.history.push({
type: "EXPERT_ADDED_LOCATIONS_V2",
actor: {
actorId: new Types.ObjectId(expert.sub),
actorName: `${expert.firstName || ""} ${expert.lastName || ""}`.trim(),
actorType: expert?.role === RoleEnum.REGISTRAR ? "registrar" : "field_expert",
},
metadata: {
hasFirstLocation: true,
hasSecondLocation: req.type === BlameRequestType.THIRD_PARTY,
},
} as any);
await (req as any).save();
return {
requestId: req._id,
publicId: req.publicId,
workflow: req.workflow,
status: req.status,
};
}
/** /**
* V2: Expert uploads video for expert-initiated BlameRequest (first party). * V2: Expert uploads video for expert-initiated BlameRequest (first party).
*/ */