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_BACK = "guilty_car_card_back",
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 {

View File

@@ -460,7 +460,6 @@ export class ClaimRequestManagementService {
isCarBody: boolean,
assumeUploadedKey?: string,
skipMetalPlate?: boolean,
requiresGuiltyCarPhoto?: boolean,
): boolean {
for (const k of this.v3PreCaptureDocumentKeys(isCarBody)) {
if (skipMetalPlate && OPTIONAL_CAPTURE_PHASE_DOC_KEYS_V4V5.includes(k as any)) continue;
@@ -478,15 +477,6 @@ export class ClaimRequestManagementService {
: this.isRequiredDocumentUploadedOnClaim(claimCase, g);
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;
}
@@ -495,7 +485,6 @@ export class ClaimRequestManagementService {
isCarBody: boolean,
assumeUploadedKey?: string,
skipMetalPlate?: boolean,
requiresGuiltyCarPhoto?: boolean,
): number {
let n = 0;
for (const k of this.v3PreCaptureDocumentKeys(isCarBody)) {
@@ -514,15 +503,6 @@ export class ClaimRequestManagementService {
: this.isRequiredDocumentUploadedOnClaim(claimCase, g);
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;
}
@@ -9093,7 +9073,7 @@ export class ClaimRequestManagementService {
file: Express.Multer.File,
currentUserId: string,
actor?: { sub: string; role?: string },
options?: { v3InPersonFlow?: boolean; skipMetalPlate?: boolean; requiresFileMakerApproval?: boolean; requiresGuiltyCarPhoto?: boolean },
options?: { v3InPersonFlow?: boolean; skipMetalPlate?: boolean; requiresFileMakerApproval?: boolean },
): Promise<UploadRequiredDocumentV2ResponseDto> {
try {
const claimCase = await this.claimCaseDbService.findById(claimRequestId);
@@ -9326,7 +9306,6 @@ export class ClaimRequestManagementService {
isCarBodyUpload,
body.documentKey,
options?.skipMetalPlate,
options?.requiresGuiltyCarPhoto,
);
allDocumentsUploaded = options?.v3InPersonFlow
@@ -9342,7 +9321,6 @@ export class ClaimRequestManagementService {
isCarBodyUpload,
body.documentKey,
options?.skipMetalPlate,
options?.requiresGuiltyCarPhoto,
)
: this.countRemainingV2OwnerDocuments(
claimCase,
@@ -9439,14 +9417,10 @@ export class ClaimRequestManagementService {
: null;
const isCarBodyRecheck = blameForRecheck?.type === BlameRequestType.CAR_BODY;
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 =
afterWrite.status === ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS &&
afterWrite.workflow?.currentStep === ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS &&
this.allV3PreCaptureDocumentsComplete(afterWrite, isCarBodyRecheck, undefined, skipMetalPlateRecheck, requiresGuiltyCarPhotoRecheck);
this.allV3PreCaptureDocumentsComplete(afterWrite, isCarBodyRecheck, undefined, skipMetalPlateRecheck);
if (allDoneNow) {
// 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 capturePartDone = this.claimV3StepCompleted(
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(
claimRequestId,
body,
file,
currentUserId,
actor,
{
v3InPersonFlow: true,
skipMetalPlate,
requiresFileMakerApproval: !!(claimCase as any).requiresFileMakerApproval,
requiresGuiltyCarPhoto,
},
{ v3InPersonFlow: true, skipMetalPlate, requiresFileMakerApproval: !!(claimCase as any).requiresFileMakerApproval },
);
}
@@ -11528,7 +11461,33 @@ export class ClaimRequestManagementService {
"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(

View File

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

View File

@@ -10280,37 +10280,35 @@ export class RequestManagementService {
body: DescriptionV4Dto,
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);
if (!req) throw new NotFoundException("Request not found");
await this.verifyExpertAccessForBlameV2(req, actor);
const role = this.resolvePartyRoleV3(req, partyRole);
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);
if (idx === -1) throw new BadRequestException(`${role} party not found`);
const party = req.parties[idx];
if (!party.statement) party.statement = {} as any;
party.statement.description = body.desc;
party.statement.accidentDate = body.accidentDate as any;
party.statement.accidentTime = body.accidentTime;
if (body.accidentDate != null) party.statement.accidentDate = body.accidentDate as any;
if (body.accidentTime) party.statement.accidentTime = body.accidentTime;
if (!Array.isArray(req.history)) req.history = [];
req.history.push({