From 1b68a81204041886c3369c14bb021a1132df0866 Mon Sep 17 00:00:00 2001 From: "s.yahyaee" Date: Mon, 6 Apr 2026 14:15:31 +0330 Subject: [PATCH] YARA-763 --- .../dto/expert-complete-car-body-form.dto.ts | 4 - .../dto/expert-complete-locations.dto.ts | 11 ++ .../expert-complete-third-party-form.dto.ts | 7 -- .../expert-initiated.v2.controller.ts | 46 +++++++- .../request-management.service.ts | 106 +++++++++++++++--- 5 files changed, 144 insertions(+), 30 deletions(-) create mode 100644 src/request-management/dto/expert-complete-locations.dto.ts diff --git a/src/request-management/dto/expert-complete-car-body-form.dto.ts b/src/request-management/dto/expert-complete-car-body-form.dto.ts index 9bebef5..a05bf36 100644 --- a/src/request-management/dto/expert-complete-car-body-form.dto.ts +++ b/src/request-management/dto/expert-complete-car-body-form.dto.ts @@ -1,6 +1,5 @@ import { ApiProperty } from "@nestjs/swagger"; import { AddPlateDto } from "src/profile/dto/user/AddPlateDto"; -import { LocationDto } from "./create-request-management.dto"; import { DescriptionDto } from "./create-request-management.dto"; import { InitialFormDto, CarBodyFormDto } from "./create-request-management.dto"; @@ -24,9 +23,6 @@ export class ExpertCompleteCarBodyFormDto { @ApiProperty({ type: AddPlateDto }) firstPartyPlate: AddPlateDto; - @ApiProperty({ type: LocationDto }) - firstPartyLocation: LocationDto; - @ApiProperty({ type: DescriptionDto }) firstPartyDescription: DescriptionDto; } diff --git a/src/request-management/dto/expert-complete-locations.dto.ts b/src/request-management/dto/expert-complete-locations.dto.ts new file mode 100644 index 0000000..6dcd28c --- /dev/null +++ b/src/request-management/dto/expert-complete-locations.dto.ts @@ -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; +} + diff --git a/src/request-management/dto/expert-complete-third-party-form.dto.ts b/src/request-management/dto/expert-complete-third-party-form.dto.ts index 8957f82..1c5ad02 100644 --- a/src/request-management/dto/expert-complete-third-party-form.dto.ts +++ b/src/request-management/dto/expert-complete-third-party-form.dto.ts @@ -1,6 +1,5 @@ import { ApiProperty } from "@nestjs/swagger"; import { AddPlateDto } from "src/profile/dto/user/AddPlateDto"; -import { LocationDto } from "./create-request-management.dto"; import { DescriptionDto } from "./create-request-management.dto"; import { InitialFormDto } from "./create-request-management.dto"; @@ -20,9 +19,6 @@ export class ExpertSecondPartyInfoDto { @ApiProperty({ type: AddPlateDto }) plate: AddPlateDto; - @ApiProperty({ type: LocationDto }) - location: LocationDto; - @ApiProperty({ type: DescriptionDto }) description: DescriptionDto; } @@ -44,9 +40,6 @@ export class ExpertCompleteThirdPartyFormDto { @ApiProperty({ type: AddPlateDto }) firstPartyPlate: AddPlateDto; - @ApiProperty({ type: LocationDto }) - firstPartyLocation: LocationDto; - @ApiProperty({ type: DescriptionDto }) firstPartyDescription: DescriptionDto; diff --git a/src/request-management/expert-initiated.v2.controller.ts b/src/request-management/expert-initiated.v2.controller.ts index 4b7a392..060915b 100644 --- a/src/request-management/expert-initiated.v2.controller.ts +++ b/src/request-management/expert-initiated.v2.controller.ts @@ -34,6 +34,7 @@ import { ExpertCompleteClaimDataDto } from "./dto/expert-complete-claim-data.dto import { ExpertUploadPartySignatureDto } from "./dto/expert-upload-party-signature.dto"; import { VerifyPartyOtpsDto } from "./dto/verify-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 { PartyRole } from "./entities/schema/partyRole.enum"; @@ -224,7 +225,7 @@ export class ExpertInitiatedV2Controller { @ApiOperation({ summary: "[V2] Submit all blame data (IN_PERSON)", 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" }) @ApiBody({ @@ -252,7 +253,6 @@ export class ExpertInitiatedV2Controller { insurerBirthday: 13770624, driverBirthday: "1370-01-01", }, - firstPartyLocation: { lat: 35.6892, lon: 51.389 }, firstPartyDescription: { desc: "توضیح حادثه طرف اول" }, secondParty: { phoneNumber: "09187654321", @@ -273,7 +273,6 @@ export class ExpertInitiatedV2Controller { insurerBirthday: 13700720, driverBirthday: "1370-01-01", }, - location: { lat: 35.6892, lon: 51.389 }, description: { desc: "توضیح حادثه طرف دوم" }, }, guiltyPartyPhoneNumber: "09123456789", @@ -303,7 +302,6 @@ export class ExpertInitiatedV2Controller { insurerBirthday: 1370, driverBirthday: "1370-01-01", }, - firstPartyLocation: { lat: 35.6892, lon: 51.389 }, firstPartyDescription: { desc: "توضیح حادثه", accidentDate: "2025-01-15", @@ -317,7 +315,7 @@ export class ExpertInitiatedV2Controller { }, 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( @CurrentUser() expert: any, @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") @ApiOperation({ summary: "[V2] Expert uploads video for expert-initiated BlameRequest", diff --git a/src/request-management/request-management.service.ts b/src/request-management/request-management.service.ts index bb59f06..e046f91 100644 --- a/src/request-management/request-management.service.ts +++ b/src/request-management/request-management.service.ts @@ -4100,7 +4100,7 @@ export class RequestManagementService { roadCondition: formData.firstPartyDescription?.roadCondition, lightCondition: formData.firstPartyDescription?.lightCondition, }, - location: formData.firstPartyLocation, + location: req.parties?.[0]?.location, carBodyFirstForm: { car: !!formData.carBodyForm.car, object: !!formData.carBodyForm.object, @@ -4110,20 +4110,19 @@ export class RequestManagementService { req.parties = [firstParty]; req.workflow = { - currentStep: WorkflowStep.WAITING_FOR_SIGNATURES as any, - nextStep: undefined, + currentStep: WorkflowStep.FIRST_LOCATION as any, + nextStep: WorkflowStep.WAITING_FOR_SIGNATURES as any, completedSteps: [ WorkflowStep.CREATED, WorkflowStep.CAR_BODY_ACCIDENT_TYPE, WorkflowStep.FIRST_VIDEO, WorkflowStep.FIRST_INITIAL_FORM, - WorkflowStep.FIRST_LOCATION, WorkflowStep.FIRST_VOICE, WorkflowStep.FIRST_DESCRIPTION, ].filter((s) => s), locked: false, }; - req.status = CaseStatus.WAITING_FOR_SIGNATURES; + req.status = CaseStatus.OPEN; req.blameStatus = BlameStatus.AGREED; (req as any).filledBy = FilledBy.EXPERT; @@ -4181,7 +4180,6 @@ export class RequestManagementService { userId: Types.ObjectId, initialForm: any, plateDto: any, - locationDto: any, desc: string, role: PartyRole, ) => { @@ -4229,7 +4227,7 @@ export class RequestManagementService { financialCeiling: sandHubReport?.FinancialCvrCptl || sandHubReport?.FinancialCommitment, }, statement: { description: desc }, - location: locationDto, + location: undefined, evidence: {}, }; }; @@ -4239,7 +4237,6 @@ export class RequestManagementService { firstPartyUserId, formData.firstPartyInitialForm, formData.firstPartyPlate, - formData.firstPartyLocation, formData.firstPartyDescription?.desc || "", PartyRole.FIRST, ); @@ -4248,7 +4245,6 @@ export class RequestManagementService { secondPartyUserId, formData.secondParty.initialForm, formData.secondParty.plate, - formData.secondParty.location, formData.secondParty.description?.desc || "", PartyRole.SECOND, ); @@ -4261,24 +4257,22 @@ export class RequestManagementService { if (!req.expert) req.expert = {} as any; req.expert.decision = { guiltyPartyId: new Types.ObjectId(guiltyPartyId) } as any; req.workflow = { - currentStep: WorkflowStep.WAITING_FOR_SIGNATURES as any, - nextStep: undefined, + currentStep: WorkflowStep.FIRST_LOCATION as any, + nextStep: WorkflowStep.SECOND_LOCATION as any, completedSteps: [ WorkflowStep.CREATED, WorkflowStep.FIRST_BLAME_CONFESSION, WorkflowStep.FIRST_VIDEO, WorkflowStep.FIRST_INITIAL_FORM, - WorkflowStep.FIRST_LOCATION, WorkflowStep.FIRST_VOICE, WorkflowStep.FIRST_DESCRIPTION, WorkflowStep.SECOND_INITIAL_FORM, - WorkflowStep.SECOND_LOCATION, WorkflowStep.SECOND_VOICE, WorkflowStep.SECOND_DESCRIPTION, ].filter((s) => s), locked: false, }; - req.status = CaseStatus.WAITING_FOR_SIGNATURES; + req.status = CaseStatus.OPEN; req.blameStatus = BlameStatus.AGREED; (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 { + 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). */