Merge pull request 'Fixed YARA-1217' (#262) from s.yahyaee/yara724-api:main into main

Reviewed-on: Yara724/api#262
This commit is contained in:
2026-08-10 10:35:14 +03:30
4 changed files with 60 additions and 101 deletions

View File

@@ -20,8 +20,6 @@ export enum ClaimRequiredDocumentType {
GUILTY_CAR_CARD_FRONT = "guilty_car_card_front", GUILTY_CAR_CARD_FRONT = "guilty_car_card_front",
GUILTY_CAR_CARD_BACK = "guilty_car_card_back", GUILTY_CAR_CARD_BACK = "guilty_car_card_back",
GUILTY_METAL_PLATE = "guilty_metal_plate", GUILTY_METAL_PLATE = "guilty_metal_plate",
/** V4 FileMaker flow only: one photo of the guilty party's car. */
GUILTY_CAR_PHOTO = "guilty_car_photo",
} }
export enum CarAngle { export enum CarAngle {

View File

@@ -460,7 +460,6 @@ export class ClaimRequestManagementService {
isCarBody: boolean, isCarBody: boolean,
assumeUploadedKey?: string, assumeUploadedKey?: string,
skipMetalPlate?: boolean, skipMetalPlate?: boolean,
requiresGuiltyCarPhoto?: boolean,
): boolean { ): boolean {
for (const k of this.v3PreCaptureDocumentKeys(isCarBody)) { for (const k of this.v3PreCaptureDocumentKeys(isCarBody)) {
if (skipMetalPlate && OPTIONAL_CAPTURE_PHASE_DOC_KEYS_V4V5.includes(k as any)) continue; if (skipMetalPlate && OPTIONAL_CAPTURE_PHASE_DOC_KEYS_V4V5.includes(k as any)) continue;
@@ -478,15 +477,6 @@ export class ClaimRequestManagementService {
: this.isRequiredDocumentUploadedOnClaim(claimCase, g); : this.isRequiredDocumentUploadedOnClaim(claimCase, g);
if (!ok) return false; if (!ok) return false;
} }
// V4 THIRD_PARTY only: require one photo of the guilty party's car.
if (requiresGuiltyCarPhoto && !isCarBody) {
const k = ClaimRequiredDocumentType.GUILTY_CAR_PHOTO;
const ok =
k === assumeUploadedKey
? true
: this.isRequiredDocumentUploadedOnClaim(claimCase, k);
if (!ok) return false;
}
return true; return true;
} }
@@ -495,7 +485,6 @@ export class ClaimRequestManagementService {
isCarBody: boolean, isCarBody: boolean,
assumeUploadedKey?: string, assumeUploadedKey?: string,
skipMetalPlate?: boolean, skipMetalPlate?: boolean,
requiresGuiltyCarPhoto?: boolean,
): number { ): number {
let n = 0; let n = 0;
for (const k of this.v3PreCaptureDocumentKeys(isCarBody)) { for (const k of this.v3PreCaptureDocumentKeys(isCarBody)) {
@@ -514,15 +503,6 @@ export class ClaimRequestManagementService {
: this.isRequiredDocumentUploadedOnClaim(claimCase, g); : this.isRequiredDocumentUploadedOnClaim(claimCase, g);
if (!ok) n++; if (!ok) n++;
} }
// V4 THIRD_PARTY only: one photo of the guilty party's car.
if (requiresGuiltyCarPhoto && !isCarBody) {
const k = ClaimRequiredDocumentType.GUILTY_CAR_PHOTO;
const ok =
k === assumeUploadedKey
? true
: this.isRequiredDocumentUploadedOnClaim(claimCase, k);
if (!ok) n++;
}
return n; return n;
} }
@@ -9093,7 +9073,7 @@ export class ClaimRequestManagementService {
file: Express.Multer.File, file: Express.Multer.File,
currentUserId: string, currentUserId: string,
actor?: { sub: string; role?: string }, actor?: { sub: string; role?: string },
options?: { v3InPersonFlow?: boolean; skipMetalPlate?: boolean; requiresFileMakerApproval?: boolean; requiresGuiltyCarPhoto?: boolean }, options?: { v3InPersonFlow?: boolean; skipMetalPlate?: boolean; requiresFileMakerApproval?: boolean },
): Promise<UploadRequiredDocumentV2ResponseDto> { ): Promise<UploadRequiredDocumentV2ResponseDto> {
try { try {
const claimCase = await this.claimCaseDbService.findById(claimRequestId); const claimCase = await this.claimCaseDbService.findById(claimRequestId);
@@ -9326,7 +9306,6 @@ export class ClaimRequestManagementService {
isCarBodyUpload, isCarBodyUpload,
body.documentKey, body.documentKey,
options?.skipMetalPlate, options?.skipMetalPlate,
options?.requiresGuiltyCarPhoto,
); );
allDocumentsUploaded = options?.v3InPersonFlow allDocumentsUploaded = options?.v3InPersonFlow
@@ -9342,7 +9321,6 @@ export class ClaimRequestManagementService {
isCarBodyUpload, isCarBodyUpload,
body.documentKey, body.documentKey,
options?.skipMetalPlate, options?.skipMetalPlate,
options?.requiresGuiltyCarPhoto,
) )
: this.countRemainingV2OwnerDocuments( : this.countRemainingV2OwnerDocuments(
claimCase, claimCase,
@@ -9439,14 +9417,10 @@ export class ClaimRequestManagementService {
: null; : null;
const isCarBodyRecheck = blameForRecheck?.type === BlameRequestType.CAR_BODY; const isCarBodyRecheck = blameForRecheck?.type === BlameRequestType.CAR_BODY;
const skipMetalPlateRecheck = !!(blameForRecheck as any)?.isMadeByFileMaker; const skipMetalPlateRecheck = !!(blameForRecheck as any)?.isMadeByFileMaker;
// V4 = isMadeByFileMaker + requiresFileMakerApproval is false on the claim.
const requiresGuiltyCarPhotoRecheck =
!!(blameForRecheck as any)?.isMadeByFileMaker &&
!(afterWrite as any).requiresFileMakerApproval;
const allDoneNow = const allDoneNow =
afterWrite.status === ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS && afterWrite.status === ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS &&
afterWrite.workflow?.currentStep === ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS && afterWrite.workflow?.currentStep === ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS &&
this.allV3PreCaptureDocumentsComplete(afterWrite, isCarBodyRecheck, undefined, skipMetalPlateRecheck, requiresGuiltyCarPhotoRecheck); this.allV3PreCaptureDocumentsComplete(afterWrite, isCarBodyRecheck, undefined, skipMetalPlateRecheck);
if (allDoneNow) { if (allDoneNow) {
// Atomic conditional update: only succeeds when status is still // Atomic conditional update: only succeeds when status is still
@@ -11279,37 +11253,6 @@ export class ClaimRequestManagementService {
}; };
} }
// V4 only (isMadeByFileMaker + NOT requiresFileMakerApproval) and THIRD_PARTY:
// inject one photo of the guilty party's car into the pre-capture document list.
const isV4 =
!!(blame as any)?.isMadeByFileMaker &&
!(claimCase as any)?.requiresFileMakerApproval;
const isCarBodyBlame = blame?.type === BlameRequestType.CAR_BODY || (blame as any)?.type === "CAR_BODY";
if (isV4 && !isCarBodyBlame) {
const guiltyCarPhotoKey = ClaimRequiredDocumentType.GUILTY_CAR_PHOTO;
const alreadyPresent = base.requiredDocuments.some(
(d) => d.key === guiltyCarPhotoKey,
);
if (!alreadyPresent) {
const docData = (claimCase.requiredDocuments as any)?.get?.(guiltyCarPhotoKey)
?? (claimCase.requiredDocuments as any)?.[guiltyCarPhotoKey];
base = {
...base,
requiredDocuments: [
...base.requiredDocuments,
{
key: guiltyCarPhotoKey,
label_fa: "عکس خودرو مقصر",
label_en: "Guilty Party Car Photo",
category: "guilty_party",
uploaded: !!docData?.uploaded,
preferUploadDuringCapture: false,
},
],
};
}
}
const step = claimCase.workflow?.currentStep; const step = claimCase.workflow?.currentStep;
const capturePartDone = this.claimV3StepCompleted( const capturePartDone = this.claimV3StepCompleted(
claimCase, claimCase,
@@ -11454,23 +11397,13 @@ export class ClaimRequestManagementService {
} }
} }
// V4 = isMadeByFileMaker + requiresFileMakerApproval is false.
const requiresGuiltyCarPhoto =
!!(blame as any).isMadeByFileMaker &&
!(claimCase as any).requiresFileMakerApproval;
return this.uploadRequiredDocumentV2( return this.uploadRequiredDocumentV2(
claimRequestId, claimRequestId,
body, body,
file, file,
currentUserId, currentUserId,
actor, actor,
{ { v3InPersonFlow: true, skipMetalPlate, requiresFileMakerApproval: !!(claimCase as any).requiresFileMakerApproval },
v3InPersonFlow: true,
skipMetalPlate,
requiresFileMakerApproval: !!(claimCase as any).requiresFileMakerApproval,
requiresGuiltyCarPhoto,
},
); );
} }
@@ -11528,7 +11461,33 @@ export class ClaimRequestManagementService {
"Select outer parts after accident fields and required documents.", "Select outer parts after accident fields and required documents.",
); );
return this.selectOuterPartsV2(claimRequestId, body, currentUserId, actor); const result = await this.selectOuterPartsV2(claimRequestId, body, currentUserId, actor);
// V4 THIRD_PARTY only: append a synthetic "guilty damage area" entry to
// damage.selectedParts so the FileReviewer must capture one photo of the
// guilty party's damaged spot during the CAPTURE_PART_DAMAGES phase.
// selectOuterPartsV2 uses $set on damage.selectedParts, so we append here
// with a separate $push after the V2 write completes.
const isV4 =
!!(blame as any)?.isMadeByFileMaker &&
!(refreshed as any)?.requiresFileMakerApproval;
const isCarBodyBlame =
blame?.type === BlameRequestType.CAR_BODY ||
(blame as any)?.type === "CAR_BODY";
if (isV4 && !isCarBodyBlame) {
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
$push: {
"damage.selectedParts": {
id: null,
name: "guilty_damage_area",
side: "",
label_fa: "تصویر نقطه آسیب‌دیده مقصر",
},
},
});
}
return result;
} }
async selectOtherPartsV3( async selectOtherPartsV3(

View File

@@ -1,5 +1,5 @@
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
import { IsNotEmpty, IsString, MaxLength } from "class-validator"; import { IsNotEmpty, IsOptional, IsString, MaxLength } from "class-validator";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { AddPlateDto } from "src/profile/dto/user/AddPlateDto"; import { AddPlateDto } from "src/profile/dto/user/AddPlateDto";
import { StepsEnum } from "src/Types&Enums/blame-request-management/steps.enum"; import { StepsEnum } from "src/Types&Enums/blame-request-management/steps.enum";
@@ -98,20 +98,24 @@ export class DescriptionV4Dto {
@IsNotEmpty() @IsNotEmpty()
desc: string; desc: string;
@ApiProperty({ @ApiPropertyOptional({
description: "Date of the accident (required for all V4 files)", description:
"Date of the accident. Required for the first (guilty) party; " +
"optional for the second (damaged) party.",
example: "2025-12-08", example: "2025-12-08",
}) })
@IsNotEmpty() @IsOptional()
accidentDate: Date; accidentDate?: Date;
@ApiProperty({ @ApiPropertyOptional({
description: "Time of the accident (required for all V4 files)", description:
"Time of the accident. Required for the first (guilty) party; " +
"optional for the second (damaged) party.",
example: "14:30", example: "14:30",
}) })
@IsOptional()
@IsString() @IsString()
@IsNotEmpty() accidentTime?: string;
accidentTime: string;
} }
/** /**

View File

@@ -10280,37 +10280,35 @@ export class RequestManagementService {
body: DescriptionV4Dto, body: DescriptionV4Dto,
partyRole?: string, partyRole?: string,
) { ) {
if (
body.accidentDate == null ||
body.accidentDate === ("" as any)
) {
throw new BadRequestException(
"accidentDate is required for V4 files.",
);
}
if (
!body.accidentTime ||
!String(body.accidentTime).trim()
) {
throw new BadRequestException(
"accidentTime is required for V4 files.",
);
}
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");
await this.verifyExpertAccessForBlameV2(req, actor); await this.verifyExpertAccessForBlameV2(req, actor);
const role = this.resolvePartyRoleV3(req, partyRole); const role = this.resolvePartyRoleV3(req, partyRole);
this.assertBlameV3PartyDetailPhase(req, role); this.assertBlameV3PartyDetailPhase(req, role);
// accidentDate and accidentTime are only required for the first (guilty)
// party — the second (damaged) party can skip them.
if (role === PartyRole.FIRST) {
if (body.accidentDate == null || body.accidentDate === ("" as any)) {
throw new BadRequestException(
"accidentDate is required for the first party in V4 files.",
);
}
if (!body.accidentTime || !String(body.accidentTime).trim()) {
throw new BadRequestException(
"accidentTime is required for the first party in V4 files.",
);
}
}
const idx = this.getPartyIndex(req, role); const idx = this.getPartyIndex(req, role);
if (idx === -1) throw new BadRequestException(`${role} party not found`); if (idx === -1) throw new BadRequestException(`${role} party not found`);
const party = req.parties[idx]; const party = req.parties[idx];
if (!party.statement) party.statement = {} as any; if (!party.statement) party.statement = {} as any;
party.statement.description = body.desc; party.statement.description = body.desc;
party.statement.accidentDate = body.accidentDate as any; if (body.accidentDate != null) party.statement.accidentDate = body.accidentDate as any;
party.statement.accidentTime = body.accidentTime; if (body.accidentTime) party.statement.accidentTime = body.accidentTime;
if (!Array.isArray(req.history)) req.history = []; if (!Array.isArray(req.history)) req.history = [];
req.history.push({ req.history.push({