feat(claims): support optional accident sketch

This commit is contained in:
SepehrYahyaee
2026-09-22 11:37:21 +03:30
parent f07e510c5f
commit 8ac185c861
9 changed files with 151 additions and 16 deletions

View File

@@ -1639,7 +1639,9 @@ export class ClaimRequestManagementService {
private getRequiredDocumentTypes(
claimRequest: any,
): ClaimRequiredDocumentType[] {
const allTypes = Object.values(ClaimRequiredDocumentType);
const allTypes = Object.values(ClaimRequiredDocumentType).filter(
(type) => type !== ClaimRequiredDocumentType.ACCIDENT_SKETCH,
);
// Check if it's CAR_BODY type
const isCarBody = claimRequest.blameFile?.type === "CAR_BODY";
@@ -10332,7 +10334,8 @@ export class ClaimRequestManagementService {
if (!catalogByKey.has(item.key)) catalogByKey.set(item.key, item);
}
// Required documents: CAR_BODY needs 7 (damaged + green card); THIRD_PARTY needs 13 (add guilty_*)
// Required documents vary by claim type. The accident sketch is offered
// in every flow, but is excluded from every completion calculation.
const blameRequest = claimCase.blameRequestId
? await this.blameRequestDbService.findById(
claimCase.blameRequestId.toString(),
@@ -10340,53 +10343,68 @@ export class ClaimRequestManagementService {
: null;
const isCarBody = blameRequest?.type === BlameRequestType.CAR_BODY;
const requiredDocsDefinition = [
{
key: ClaimRequiredDocumentType.ACCIDENT_SKETCH,
label_fa: "کروکی",
label_en: "Accident Sketch",
category: "general",
required: false,
},
{
key: "damaged_driving_license_front",
label_fa: "گواهینامه طرف آسیب‌دیده - جلو",
label_en: "Damaged Party License - Front",
category: "damaged_party",
required: true,
},
{
key: "damaged_driving_license_back",
label_fa: "گواهینامه طرف آسیب‌دیده - پشت",
label_en: "Damaged Party License - Back",
category: "damaged_party",
required: true,
},
{
key: "damaged_chassis_number",
label_fa: "شماره شاسی",
label_en: "Chassis Number",
category: "damaged_party",
required: true,
},
{
key: "damaged_engine_photo",
label_fa: "عکس موتور",
label_en: "Engine Photo",
category: "damaged_party",
required: true,
},
{
key: "damaged_car_card_front",
label_fa: "کارت خودرو آسیب‌دیده - جلو",
label_en: "Damaged Car Card - Front",
category: "damaged_party",
required: true,
},
{
key: "damaged_car_card_back",
label_fa: "کارت خودرو آسیب‌دیده - پشت",
label_en: "Damaged Car Card - Back",
category: "damaged_party",
required: true,
},
{
key: "damaged_metal_plate",
label_fa: "پلاک فلزی آسیب‌دیده",
label_en: "Damaged Metal Plate",
category: "damaged_party",
required: true,
},
{
key: "car_green_card",
label_fa: "کارت سبز خودرو",
label_en: "Car Green Card",
category: "damaged_party",
required: true,
},
...(isCarBody
? []
@@ -10396,30 +10414,35 @@ export class ClaimRequestManagementService {
label_fa: "گواهینامه طرف مقصر - جلو",
label_en: "Guilty Party License - Front",
category: "guilty_party",
required: true,
},
{
key: "guilty_driving_license_back",
label_fa: "گواهینامه طرف مقصر - پشت",
label_en: "Guilty Party License - Back",
category: "guilty_party",
required: true,
},
{
key: "guilty_car_card_front",
label_fa: "کارت خودرو مقصر - جلو",
label_en: "Guilty Car Card - Front",
category: "guilty_party",
required: true,
},
{
key: "guilty_car_card_back",
label_fa: "کارت خودرو مقصر - پشت",
label_en: "Guilty Car Card - Back",
category: "guilty_party",
required: true,
},
{
key: "guilty_metal_plate",
label_fa: "پلاک فلزی مقصر",
label_en: "Guilty Metal Plate",
category: "guilty_party",
required: true,
},
]),
];
@@ -10431,6 +10454,7 @@ export class ClaimRequestManagementService {
label_fa: doc.label_fa,
label_en: doc.label_en,
category: doc.category,
required: doc.required,
uploaded: docData?.uploaded || false,
preferUploadDuringCapture: isCapturePhaseDamagedPartyDocKey(doc.key),
};
@@ -10493,7 +10517,10 @@ export class ClaimRequestManagementService {
});
// Calculate progress
const documentsUploaded = requiredDocuments.filter(
const requiredDocumentItems = requiredDocuments.filter(
(d) => d.required,
);
const documentsUploaded = requiredDocumentItems.filter(
(d) => d.uploaded,
).length;
const anglesCaptured = carAngles.filter((a) => a.captured).length;
@@ -10511,7 +10538,7 @@ export class ClaimRequestManagementService {
captureProgress.anglesTotal - captureProgress.anglesCaptured,
);
const postCaptureDocsRemaining = requiredDocuments.filter(
(d) => !d.preferUploadDuringCapture && !d.uploaded,
(d) => d.required && !d.preferUploadDuringCapture && !d.uploaded,
).length;
const totalRemaining =
@@ -10531,7 +10558,7 @@ export class ClaimRequestManagementService {
totalRemaining,
progress: {
documentsUploaded,
documentsTotal: requiredDocuments.length,
documentsTotal: requiredDocumentItems.length,
anglesCaptured,
anglesTotal: carAngles.length,
partsCaptured,
@@ -10694,6 +10721,8 @@ export class ClaimRequestManagementService {
const step = claimCase.workflow?.currentStep;
const isResendUpload = step === ClaimWorkflowStep.USER_EXPERT_RESEND;
const isOptionalDocument =
body.documentKey === ClaimRequiredDocumentType.ACCIDENT_SKETCH;
const isCapturePhaseDocUpload =
step === ClaimWorkflowStep.CAPTURE_PART_DAMAGES &&
(isCapturePhaseDamagedPartyDocKey(body.documentKey) ||
@@ -10724,7 +10753,9 @@ export class ClaimRequestManagementService {
}
const allowedInitialUploadStep =
step === ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS ||
isCapturePhaseDocUpload;
isCapturePhaseDocUpload ||
(isOptionalDocument &&
step === ClaimWorkflowStep.USER_SUBMISSION_COMPLETE);
if (!allowedInitialUploadStep) {
throw new BadRequestException(
`Invalid workflow step. Expected ${ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS}, ${ClaimWorkflowStep.CAPTURE_PART_DAMAGES} (capture-phase documents only), or ${ClaimWorkflowStep.USER_EXPERT_RESEND}, but current step is ${claimCase.workflow?.currentStep}`,
@@ -10843,7 +10874,7 @@ export class ClaimRequestManagementService {
let remaining = 0;
let captureStepCompletedOnThisUpload = false;
if (!isResendUpload) {
if (!isResendUpload && !isOptionalDocument) {
if (isCapturePhaseDocUpload) {
const afterThis = (k: string) =>
k === body.documentKey ||
@@ -10981,6 +11012,28 @@ export class ClaimRequestManagementService {
}
}
if (!isResendUpload && isOptionalDocument) {
allDocumentsUploaded = options?.v3InPersonFlow
? this.allV3PreCaptureDocumentsComplete(
claimCase,
isCarBodyUpload,
undefined,
options?.skipMetalPlate,
)
: this.allV2OwnerDocumentsComplete(claimCase, isCarBodyUpload);
remaining = options?.v3InPersonFlow
? this.countRemainingV3PreCaptureDocuments(
claimCase,
isCarBodyUpload,
undefined,
options?.skipMetalPlate,
)
: this.countRemainingV2OwnerDocuments(
claimCase,
isCarBodyUpload,
);
}
// Build the final MongoDB update — all operators explicit and clean
const updatePayload: Record<string, unknown> = { $set };
@@ -11085,7 +11138,12 @@ export class ClaimRequestManagementService {
}
// Auto-submit Fanavaran attachments when all documents are uploaded
if (allDocumentsUploaded && !isResendUpload && !options?.v3InPersonFlow) {
if (
allDocumentsUploaded &&
!isOptionalDocument &&
!isResendUpload &&
!options?.v3InPersonFlow
) {
this.submitFanavaranAttachmentsV2(claimRequestId, resolveFanavaranClientKey()).catch(
(err) => this.logger.warn(`Fanavaran attachments auto-submit failed: ${err?.message}`),
);
@@ -11125,7 +11183,9 @@ export class ClaimRequestManagementService {
};
}
const message = isCapturePhaseDocUpload
const message = isOptionalDocument
? "Optional accident sketch uploaded successfully."
: isCapturePhaseDocUpload
? captureStepCompletedOnThisUpload
? options?.v3InPersonFlow
? "Capture-phase documents and all damage captures are complete. Upload the walk-around video (car-capture) to finalise."
@@ -11148,7 +11208,9 @@ export class ClaimRequestManagementService {
documentKey: body.documentKey,
fileUrl,
allDocumentsUploaded,
currentStep: isCapturePhaseDocUpload
currentStep: isOptionalDocument
? step || ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS
: isCapturePhaseDocUpload
? captureStepCompletedOnThisUpload
? ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS
: ClaimWorkflowStep.CAPTURE_PART_DAMAGES
@@ -12905,7 +12967,10 @@ export class ClaimRequestManagementService {
const preCaptureDocs = base.requiredDocuments.filter(
(d) => !d.preferUploadDuringCapture,
);
const remaining = preCaptureDocs.filter((d) => !d.uploaded).length;
const requiredPreCaptureDocs = preCaptureDocs.filter((d) => d.required);
const remaining = requiredPreCaptureDocs.filter(
(d) => !d.uploaded,
).length;
return {
...base,
requiredDocuments: preCaptureDocs,
@@ -12917,8 +12982,9 @@ export class ClaimRequestManagementService {
totalRemaining: remaining,
progress: {
...base.progress,
documentsTotal: preCaptureDocs.length,
documentsUploaded: preCaptureDocs.filter((d) => d.uploaded).length,
documentsTotal: requiredPreCaptureDocs.length,
documentsUploaded: requiredPreCaptureDocs.filter((d) => d.uploaded)
.length,
partsCaptured: 0,
partsTotal: 0,
anglesCaptured: 0,
@@ -12973,6 +13039,7 @@ export class ClaimRequestManagementService {
label_fa: "تصویر نقطه آسیب‌دیده مقصر",
label_en: "Guilty Car Damaged Area",
category: "guilty_party",
required: true,
uploaded: guiltyAreaUploaded,
preferUploadDuringCapture: true,
};