forked from Yara724/api
YARA-877
This commit is contained in:
@@ -113,12 +113,24 @@ import {
|
|||||||
catalogItemToSelectedPart,
|
catalogItemToSelectedPart,
|
||||||
catalogLikeKeyFromPart,
|
catalogLikeKeyFromPart,
|
||||||
coerceDamagedPartsMediaToArray,
|
coerceDamagedPartsMediaToArray,
|
||||||
|
type DamageSelectedPartV2,
|
||||||
migrateExpertReplyCarPartDamageToUnified,
|
migrateExpertReplyCarPartDamageToUnified,
|
||||||
normalizeDamageSelectedParts,
|
normalizeDamageSelectedParts,
|
||||||
partIdentityKey,
|
partIdentityKey,
|
||||||
resolvePartCaptureIndex,
|
resolvePartCaptureIndex,
|
||||||
} from "src/helpers/outer-damage-parts";
|
} from "src/helpers/outer-damage-parts";
|
||||||
|
|
||||||
|
/** Same `requiredDocuments` keys as in getCaptureRequirementsV2; upload allowed during CAPTURE_PART_DAMAGES. */
|
||||||
|
const CAPTURE_PHASE_DAMAGED_PARTY_DOC_KEYS = [
|
||||||
|
"damaged_chassis_number",
|
||||||
|
"damaged_engine_photo",
|
||||||
|
"damaged_metal_plate",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
function isCapturePhaseDamagedPartyDocKey(key: string): boolean {
|
||||||
|
return (CAPTURE_PHASE_DAMAGED_PARTY_DOC_KEYS as readonly string[]).includes(key);
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ClaimRequestManagementService {
|
export class ClaimRequestManagementService {
|
||||||
private readonly logger = new Logger(ClaimRequestManagementService.name);
|
private readonly logger = new Logger(ClaimRequestManagementService.name);
|
||||||
@@ -163,6 +175,79 @@ export class ClaimRequestManagementService {
|
|||||||
private readonly sandHubService: SandHubService,
|
private readonly sandHubService: SandHubService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
private requiredDocumentKeysV2(isCarBody: boolean): string[] {
|
||||||
|
const damagedPartyKeys = [
|
||||||
|
"damaged_driving_license_front",
|
||||||
|
"damaged_driving_license_back",
|
||||||
|
"damaged_chassis_number",
|
||||||
|
"damaged_engine_photo",
|
||||||
|
"damaged_car_card_front",
|
||||||
|
"damaged_car_card_back",
|
||||||
|
"damaged_metal_plate",
|
||||||
|
];
|
||||||
|
if (isCarBody) return damagedPartyKeys;
|
||||||
|
return [
|
||||||
|
...damagedPartyKeys,
|
||||||
|
"guilty_driving_license_front",
|
||||||
|
"guilty_driving_license_back",
|
||||||
|
"guilty_car_card_front",
|
||||||
|
"guilty_car_card_back",
|
||||||
|
"guilty_metal_plate",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private isRequiredDocumentUploadedOnClaim(claimCase: any, key: string): boolean {
|
||||||
|
const doc =
|
||||||
|
(claimCase.requiredDocuments as any)?.get?.(key) ??
|
||||||
|
(claimCase.requiredDocuments as any)?.[key];
|
||||||
|
return !!doc?.uploaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private capturePhaseDamagedPartyDocsComplete(claimCase: any): boolean {
|
||||||
|
return CAPTURE_PHASE_DAMAGED_PARTY_DOC_KEYS.every((k) =>
|
||||||
|
this.isRequiredDocumentUploadedOnClaim(claimCase, k),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private allV2OwnerDocumentsComplete(
|
||||||
|
claimCase: any,
|
||||||
|
isCarBody: boolean,
|
||||||
|
assumeUploadedKey?: string,
|
||||||
|
): boolean {
|
||||||
|
for (const k of this.requiredDocumentKeysV2(isCarBody)) {
|
||||||
|
const ok =
|
||||||
|
k === assumeUploadedKey ? true : this.isRequiredDocumentUploadedOnClaim(claimCase, k);
|
||||||
|
if (!ok) return false;
|
||||||
|
}
|
||||||
|
if (isCarBody) {
|
||||||
|
const g = ClaimRequiredDocumentType.CAR_GREEN_CARD;
|
||||||
|
const okGreen =
|
||||||
|
assumeUploadedKey === g ? true : this.isRequiredDocumentUploadedOnClaim(claimCase, g);
|
||||||
|
if (!okGreen) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private countRemainingV2OwnerDocuments(
|
||||||
|
claimCase: any,
|
||||||
|
isCarBody: boolean,
|
||||||
|
assumeUploadedKey?: string,
|
||||||
|
): number {
|
||||||
|
let n = 0;
|
||||||
|
for (const k of this.requiredDocumentKeysV2(isCarBody)) {
|
||||||
|
const ok =
|
||||||
|
k === assumeUploadedKey ? true : this.isRequiredDocumentUploadedOnClaim(claimCase, k);
|
||||||
|
if (!ok) n++;
|
||||||
|
}
|
||||||
|
if (isCarBody) {
|
||||||
|
const g = ClaimRequiredDocumentType.CAR_GREEN_CARD;
|
||||||
|
const okGreen =
|
||||||
|
assumeUploadedKey === g ? true : this.isRequiredDocumentUploadedOnClaim(claimCase, g);
|
||||||
|
if (!okGreen) n++;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
private parsePlateFromCompactString(
|
private parsePlateFromCompactString(
|
||||||
plateId: string | undefined,
|
plateId: string | undefined,
|
||||||
): { leftDigits: number; centerAlphabet: string; centerDigits: number; ir: number } | null {
|
): { leftDigits: number; centerAlphabet: string; centerDigits: number; ir: number } | null {
|
||||||
@@ -219,7 +304,56 @@ export class ClaimRequestManagementService {
|
|||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
getOuterPartsCatalogV2(carType?: ClaimVehicleTypeV2): OuterPartCatalogItem[] {
|
/**
|
||||||
|
* Outer-parts catalog returned to clients (user app + expert panel).
|
||||||
|
*
|
||||||
|
* The shape intentionally mirrors how items are persisted under
|
||||||
|
* `damage.selectedParts` so the front-end can match catalog rows to the
|
||||||
|
* stored selection by `id` / `catalogKey` without renaming fields:
|
||||||
|
*
|
||||||
|
* { id, name, side, label_fa, catalogKey, carType }
|
||||||
|
*
|
||||||
|
* - `name` is side-agnostic (`backWheel`, not `left_backWheel`).
|
||||||
|
* - `label_fa` is disambiguated with the side in parentheses when multiple
|
||||||
|
* catalog rows share the same Farsi title (e.g. `چرخ عقب (چپ)`).
|
||||||
|
* - `catalogKey` keeps the original full key (`left_backWheel`) for clients
|
||||||
|
* that already address parts by it.
|
||||||
|
*/
|
||||||
|
getOuterPartsCatalogV2(
|
||||||
|
carType?: ClaimVehicleTypeV2,
|
||||||
|
): (DamageSelectedPartV2 & { carType: ClaimVehicleTypeV2 })[] {
|
||||||
|
const buildForType = (
|
||||||
|
type: ClaimVehicleTypeV2,
|
||||||
|
items: OuterPartCatalogItem[],
|
||||||
|
): (DamageSelectedPartV2 & { carType: ClaimVehicleTypeV2 })[] =>
|
||||||
|
items.map((p) => ({
|
||||||
|
...catalogItemToSelectedPart(p, items),
|
||||||
|
carType: type,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (carType) {
|
||||||
|
const items = OUTER_PARTS_BY_CAR_TYPE[carType] || [];
|
||||||
|
return buildForType(carType, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
const out: (DamageSelectedPartV2 & { carType: ClaimVehicleTypeV2 })[] = [];
|
||||||
|
for (const [type, items] of Object.entries(OUTER_PARTS_BY_CAR_TYPE)) {
|
||||||
|
out.push(...buildForType(type as ClaimVehicleTypeV2, items));
|
||||||
|
}
|
||||||
|
return out.sort((a, b) => {
|
||||||
|
if (a.carType === b.carType) return (a.id ?? 0) - (b.id ?? 0);
|
||||||
|
return String(a.carType).localeCompare(String(b.carType));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal raw catalog (kept in the original `OuterPartCatalogItem` shape so
|
||||||
|
* existing internal code that indexes by `key`/`titleFa` keeps working).
|
||||||
|
* Public API consumers should use `getOuterPartsCatalogV2` instead.
|
||||||
|
*/
|
||||||
|
private getOuterPartsRawCatalog(
|
||||||
|
carType?: ClaimVehicleTypeV2,
|
||||||
|
): OuterPartCatalogItem[] {
|
||||||
if (carType) {
|
if (carType) {
|
||||||
return (OUTER_PARTS_BY_CAR_TYPE[carType] || []).map((p) => ({
|
return (OUTER_PARTS_BY_CAR_TYPE[carType] || []).map((p) => ({
|
||||||
...p,
|
...p,
|
||||||
@@ -233,10 +367,7 @@ export class ClaimRequestManagementService {
|
|||||||
out.push({ ...p, carType: t });
|
out.push({ ...p, carType: t });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out.sort((a, b) => {
|
return out;
|
||||||
if (a.carType === b.carType) return a.id - b.id;
|
|
||||||
return String(a.carType).localeCompare(String(b.carType));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private userDamageDetail(blRequest: RequestManagementModel) {
|
private userDamageDetail(blRequest: RequestManagementModel) {
|
||||||
@@ -4396,12 +4527,14 @@ export class ClaimRequestManagementService {
|
|||||||
throw new ForbiddenException('Only the claim owner can view capture requirements');
|
throw new ForbiddenException('Only the claim owner can view capture requirements');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build car-type aware outer-parts lookup (complete source: static catalog)
|
// Build car-type aware outer-parts lookup (complete source: static catalog).
|
||||||
|
// Uses the raw catalog shape (`{id, key, titleFa, side, ...}`) because the
|
||||||
|
// map below is keyed on the full `key` (e.g. `left_backfender`).
|
||||||
const selectedCarType = claimCase.vehicle?.carType as ClaimVehicleTypeV2 | undefined;
|
const selectedCarType = claimCase.vehicle?.carType as ClaimVehicleTypeV2 | undefined;
|
||||||
const catalogForType =
|
const catalogForType =
|
||||||
selectedCarType && OUTER_PARTS_BY_CAR_TYPE[selectedCarType]
|
selectedCarType && OUTER_PARTS_BY_CAR_TYPE[selectedCarType]
|
||||||
? OUTER_PARTS_BY_CAR_TYPE[selectedCarType]
|
? OUTER_PARTS_BY_CAR_TYPE[selectedCarType]
|
||||||
: this.getOuterPartsCatalogV2();
|
: this.getOuterPartsRawCatalog();
|
||||||
const catalogByKey = new Map<string, OuterPartCatalogItem>();
|
const catalogByKey = new Map<string, OuterPartCatalogItem>();
|
||||||
for (const item of catalogForType) {
|
for (const item of catalogForType) {
|
||||||
if (!catalogByKey.has(item.key)) catalogByKey.set(item.key, item);
|
if (!catalogByKey.has(item.key)) catalogByKey.set(item.key, item);
|
||||||
@@ -4437,6 +4570,7 @@ export class ClaimRequestManagementService {
|
|||||||
label_en: doc.label_en,
|
label_en: doc.label_en,
|
||||||
category: doc.category,
|
category: doc.category,
|
||||||
uploaded: docData?.uploaded || false,
|
uploaded: docData?.uploaded || false,
|
||||||
|
preferUploadDuringCapture: isCapturePhaseDamagedPartyDocKey(doc.key),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -4655,11 +4789,24 @@ export class ClaimRequestManagementService {
|
|||||||
|
|
||||||
const step = claimCase.workflow?.currentStep;
|
const step = claimCase.workflow?.currentStep;
|
||||||
const isResendUpload = step === ClaimWorkflowStep.USER_EXPERT_RESEND;
|
const isResendUpload = step === ClaimWorkflowStep.USER_EXPERT_RESEND;
|
||||||
if (!isResendUpload && step !== ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS) {
|
const isCapturePhaseDocUpload =
|
||||||
|
step === ClaimWorkflowStep.CAPTURE_PART_DAMAGES &&
|
||||||
|
isCapturePhaseDamagedPartyDocKey(body.documentKey);
|
||||||
|
|
||||||
|
if (!isResendUpload) {
|
||||||
|
if (step === ClaimWorkflowStep.CAPTURE_PART_DAMAGES && !isCapturePhaseDocUpload) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
`Invalid workflow step. Expected ${ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS} or ${ClaimWorkflowStep.USER_EXPERT_RESEND}, but current step is ${claimCase.workflow?.currentStep}`,
|
`During ${ClaimWorkflowStep.CAPTURE_PART_DAMAGES} only chassis, engine, and damaged metal plate photos may be uploaded here (${CAPTURE_PHASE_DAMAGED_PARTY_DOC_KEYS.join(", ")}).`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const allowedInitialUploadStep =
|
||||||
|
step === ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS || isCapturePhaseDocUpload;
|
||||||
|
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}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (isResendUpload) {
|
if (isResendUpload) {
|
||||||
if (claimCase.status !== ClaimCaseStatus.WAITING_FOR_USER_RESEND) {
|
if (claimCase.status !== ClaimCaseStatus.WAITING_FOR_USER_RESEND) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
@@ -4734,15 +4881,23 @@ export class ClaimRequestManagementService {
|
|||||||
let remaining = 0;
|
let remaining = 0;
|
||||||
|
|
||||||
if (!isResendUpload) {
|
if (!isResendUpload) {
|
||||||
// Check if all documents are uploaded (8 for CAR_BODY, 13 for THIRD_PARTY)
|
if (isCapturePhaseDocUpload) {
|
||||||
const totalDocsRequired = isCarBodyUpload ? 8 : 13;
|
const afterThis = (k: string) =>
|
||||||
const currentDocs = claimCase.requiredDocuments || new Map();
|
k === body.documentKey || this.isRequiredDocumentUploadedOnClaim(claimCase, k);
|
||||||
const uploadedCount =
|
remaining = CAPTURE_PHASE_DAMAGED_PARTY_DOC_KEYS.filter((k) => !afterThis(k)).length;
|
||||||
(currentDocs instanceof Map ? currentDocs.size : Object.keys(currentDocs).length) + 1;
|
allDocumentsUploaded = false;
|
||||||
allDocumentsUploaded = uploadedCount >= totalDocsRequired;
|
} else {
|
||||||
remaining = totalDocsRequired - uploadedCount;
|
allDocumentsUploaded = this.allV2OwnerDocumentsComplete(
|
||||||
|
claimCase,
|
||||||
|
isCarBodyUpload,
|
||||||
|
body.documentKey,
|
||||||
|
);
|
||||||
|
remaining = this.countRemainingV2OwnerDocuments(
|
||||||
|
claimCase,
|
||||||
|
isCarBodyUpload,
|
||||||
|
body.documentKey,
|
||||||
|
);
|
||||||
|
|
||||||
// If all documents uploaded, user flow complete (captures were done earlier in v2)
|
|
||||||
if (allDocumentsUploaded) {
|
if (allDocumentsUploaded) {
|
||||||
updateData["status"] = ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT;
|
updateData["status"] = ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT;
|
||||||
updateData["claimStatus"] = ClaimStatus.PENDING;
|
updateData["claimStatus"] = ClaimStatus.PENDING;
|
||||||
@@ -4784,6 +4939,7 @@ export class ClaimRequestManagementService {
|
|||||||
ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS;
|
ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, updateData);
|
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, updateData);
|
||||||
|
|
||||||
@@ -4808,7 +4964,11 @@ export class ClaimRequestManagementService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = allDocumentsUploaded
|
const message = isCapturePhaseDocUpload
|
||||||
|
? remaining > 0
|
||||||
|
? `Document saved. ${remaining} capture-phase document(s) still required (chassis, engine, metal plate) before you can finish damage capture.`
|
||||||
|
: "Document saved. All capture-phase documents are uploaded; finish car angles and part photos to proceed."
|
||||||
|
: allDocumentsUploaded
|
||||||
? "All documents uploaded successfully. Your claim is now ready for damage expert review."
|
? "All documents uploaded successfully. Your claim is now ready for damage expert review."
|
||||||
: `Document uploaded successfully. ${remaining} documents remaining.`;
|
: `Document uploaded successfully. ${remaining} documents remaining.`;
|
||||||
|
|
||||||
@@ -4817,7 +4977,9 @@ export class ClaimRequestManagementService {
|
|||||||
documentKey: body.documentKey,
|
documentKey: body.documentKey,
|
||||||
fileUrl,
|
fileUrl,
|
||||||
allDocumentsUploaded,
|
allDocumentsUploaded,
|
||||||
currentStep: allDocumentsUploaded
|
currentStep: isCapturePhaseDocUpload
|
||||||
|
? ClaimWorkflowStep.CAPTURE_PART_DAMAGES
|
||||||
|
: allDocumentsUploaded
|
||||||
? ClaimWorkflowStep.USER_SUBMISSION_COMPLETE
|
? ClaimWorkflowStep.USER_SUBMISSION_COMPLETE
|
||||||
: ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
: ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
||||||
message,
|
message,
|
||||||
@@ -5069,8 +5231,11 @@ export class ClaimRequestManagementService {
|
|||||||
);
|
);
|
||||||
}).length;
|
}).length;
|
||||||
|
|
||||||
|
const capturePhaseDocsDone = this.capturePhaseDamagedPartyDocsComplete(updatedClaim);
|
||||||
const allCapturesComplete =
|
const allCapturesComplete =
|
||||||
anglesCaptured >= 4 && partsCaptured >= selectedNormAfter.length;
|
anglesCaptured >= 4 &&
|
||||||
|
partsCaptured >= selectedNormAfter.length &&
|
||||||
|
capturePhaseDocsDone;
|
||||||
|
|
||||||
if (allCapturesComplete) {
|
if (allCapturesComplete) {
|
||||||
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
||||||
@@ -5090,18 +5255,23 @@ export class ClaimRequestManagementService {
|
|||||||
metadata: {
|
metadata: {
|
||||||
stepKey: ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
stepKey: ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
||||||
description:
|
description:
|
||||||
'All car angles and damaged parts captured. Please upload required documents.',
|
'Angles, damaged parts, and capture-phase vehicle evidence (chassis, engine, metal plate) are complete. Please upload remaining required documents.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const captureDocsRemaining = CAPTURE_PHASE_DAMAGED_PARTY_DOC_KEYS.filter(
|
||||||
|
(k) => !this.isRequiredDocumentUploadedOnClaim(updatedClaim, k),
|
||||||
|
).length;
|
||||||
const remaining =
|
const remaining =
|
||||||
(4 - anglesCaptured) + (selectedNormAfter.length - partsCaptured);
|
(4 - anglesCaptured) +
|
||||||
|
(selectedNormAfter.length - partsCaptured) +
|
||||||
|
captureDocsRemaining;
|
||||||
const message = allCapturesComplete
|
const message = allCapturesComplete
|
||||||
? 'All captures complete. Please proceed to upload required documents.'
|
? 'All captures complete. Please proceed to upload required documents.'
|
||||||
: `${body.captureType === 'angle' ? 'Angle' : 'Part'} captured successfully. ${remaining} captures remaining.`;
|
: `${body.captureType === 'angle' ? 'Angle' : 'Part'} captured successfully. ${remaining} items remaining (angles, parts, and capture-phase documents).`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
claimRequestId: claimCase._id.toString(),
|
claimRequestId: claimCase._id.toString(),
|
||||||
|
|||||||
@@ -638,13 +638,13 @@ Optional: upload car green card file in the same step.
|
|||||||
summary: "Get Capture Requirements (V2)",
|
summary: "Get Capture Requirements (V2)",
|
||||||
description: `
|
description: `
|
||||||
**Get list of what needs to be captured:**
|
**Get list of what needs to be captured:**
|
||||||
- Required documents (13 items)
|
- Required documents (10 remaining at the documents step for third-party; 3 damaged-party items should be uploaded during capture — see \`preferUploadDuringCapture\` on each item)
|
||||||
- Car angles (4 items: front, back, left, right)
|
- Car angles (4 items: front, back, left, right)
|
||||||
- Damaged parts (based on selected outer parts)
|
- Damaged parts (based on selected outer parts)
|
||||||
|
|
||||||
Returns status of each item (uploaded/captured or not).
|
Returns status of each item (uploaded/captured or not).
|
||||||
|
|
||||||
**V2 order:** Complete angles and part photos first, then required documents.
|
**V2 order:** During \`CAPTURE_PART_DAMAGES\`, complete angles, part photos, walk-around video, and the three \`preferUploadDuringCapture\` documents (same upload-document API). Then complete the remaining documents in \`UPLOAD_REQUIRED_DOCUMENTS\`.
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
@ApiParam({
|
@ApiParam({
|
||||||
|
|||||||
@@ -35,6 +35,13 @@ export class RequiredDocumentItem {
|
|||||||
enum: ['general', 'damaged_party', 'guilty_party'],
|
enum: ['general', 'damaged_party', 'guilty_party'],
|
||||||
})
|
})
|
||||||
category: string;
|
category: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description:
|
||||||
|
'When true, the client should upload this file during CAPTURE_PART_DAMAGES (same POST upload-document endpoint and `requiredDocuments` keys). Capture cannot finish until these are uploaded.',
|
||||||
|
example: true,
|
||||||
|
})
|
||||||
|
preferUploadDuringCapture?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -140,19 +140,54 @@ export class SetClaimVehicleTypeV2Dto {
|
|||||||
carType: ClaimVehicleTypeV2;
|
carType: ClaimVehicleTypeV2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shape returned by `GET .../outer-parts-catalog` (both user and expert
|
||||||
|
* controllers). It mirrors how items are persisted under
|
||||||
|
* `damage.selectedParts` (see `DamageSelectedPartV2BodyDto`) so the front-end
|
||||||
|
* can match catalog rows to stored selections without any field renaming:
|
||||||
|
* `name` is side-agnostic, `label_fa` is disambiguated with the side in
|
||||||
|
* parentheses, and the original full catalog key (`left_backfender`) is
|
||||||
|
* exposed as `catalogKey`.
|
||||||
|
*/
|
||||||
export class OuterPartCatalogItemDto {
|
export class OuterPartCatalogItemDto {
|
||||||
@ApiProperty()
|
@ApiProperty({
|
||||||
|
description: "Static catalog id (unique across all car types)",
|
||||||
|
example: 102,
|
||||||
|
})
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({
|
||||||
key: string;
|
description: "Side-agnostic part name (matches stored part `name`)",
|
||||||
|
example: "backWheel",
|
||||||
|
})
|
||||||
|
name: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty({
|
||||||
titleFa: string;
|
description: "Vehicle side / region",
|
||||||
|
enum: OuterPartSideV2,
|
||||||
|
example: "left",
|
||||||
|
})
|
||||||
|
side: string;
|
||||||
|
|
||||||
@ApiProperty({ enum: OuterPartSideV2 })
|
@ApiProperty({
|
||||||
side: OuterPartSideV2;
|
description:
|
||||||
|
"Display label in Farsi, with side disambiguator in parentheses when needed",
|
||||||
|
example: "چرخ عقب (چپ)",
|
||||||
|
})
|
||||||
|
label_fa: string;
|
||||||
|
|
||||||
@ApiProperty({ enum: ClaimVehicleTypeV2, required: false })
|
@ApiProperty({
|
||||||
|
description: "Original full catalog key (matches stored `catalogKey`)",
|
||||||
|
example: "left_backWheel",
|
||||||
|
required: false,
|
||||||
|
})
|
||||||
|
catalogKey?: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: "Vehicle type this catalog row belongs to",
|
||||||
|
enum: ClaimVehicleTypeV2,
|
||||||
|
required: false,
|
||||||
|
example: "suv",
|
||||||
|
})
|
||||||
carType?: ClaimVehicleTypeV2;
|
carType?: ClaimVehicleTypeV2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2523,7 +2523,7 @@ export class ExpertClaimService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Price cap validation
|
// Price cap validation
|
||||||
const PRICE_CAP = 30_000_000;
|
const PRICE_CAP = 53_000_000;
|
||||||
let totalPrice = 0;
|
let totalPrice = 0;
|
||||||
for (const part of reply.parts || []) {
|
for (const part of reply.parts || []) {
|
||||||
const parsed = this.parsePersianNumber(String(part.totalPayment ?? '0'));
|
const parsed = this.parsePersianNumber(String(part.totalPayment ?? '0'));
|
||||||
|
|||||||
Reference in New Issue
Block a user