forked from Yara724/api
Compare commits
4 Commits
2898897dd9
...
68b3b2000b
| Author | SHA1 | Date | |
|---|---|---|---|
| 68b3b2000b | |||
|
|
028ca899fe | ||
|
|
0086b8db4d | ||
|
|
7f5b64f2a6 |
@@ -7,12 +7,12 @@ export enum ClaimWorkflowStep {
|
|||||||
SELECT_OUTER_PARTS = "SELECT_OUTER_PARTS",
|
SELECT_OUTER_PARTS = "SELECT_OUTER_PARTS",
|
||||||
SELECT_OTHER_PARTS = "SELECT_OTHER_PARTS",
|
SELECT_OTHER_PARTS = "SELECT_OTHER_PARTS",
|
||||||
|
|
||||||
// User: Documentation phase
|
// User: Damage capture phase (angles + per-part photos) — before document upload in v2
|
||||||
UPLOAD_REQUIRED_DOCUMENTS = "UPLOAD_REQUIRED_DOCUMENTS",
|
|
||||||
|
|
||||||
// User: Damage capture phase (per part)
|
|
||||||
CAPTURE_PART_DAMAGES = "CAPTURE_PART_DAMAGES",
|
CAPTURE_PART_DAMAGES = "CAPTURE_PART_DAMAGES",
|
||||||
|
|
||||||
|
// User: Documentation phase — after captures in v2
|
||||||
|
UPLOAD_REQUIRED_DOCUMENTS = "UPLOAD_REQUIRED_DOCUMENTS",
|
||||||
|
|
||||||
// User submission complete
|
// User submission complete
|
||||||
USER_SUBMISSION_COMPLETE = "USER_SUBMISSION_COMPLETE",
|
USER_SUBMISSION_COMPLETE = "USER_SUBMISSION_COMPLETE",
|
||||||
|
|
||||||
|
|||||||
@@ -674,7 +674,7 @@ export class ClaimRequestManagementService {
|
|||||||
"damage.selectedParts": selectedParts,
|
"damage.selectedParts": selectedParts,
|
||||||
status: ClaimCaseStatus.SELECTING_OTHER_PARTS,
|
status: ClaimCaseStatus.SELECTING_OTHER_PARTS,
|
||||||
"workflow.currentStep": ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
"workflow.currentStep": ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
||||||
"workflow.nextStep": ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
"workflow.nextStep": ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
||||||
$push: {
|
$push: {
|
||||||
"workflow.completedSteps": ClaimWorkflowStep.SELECT_OUTER_PARTS,
|
"workflow.completedSteps": ClaimWorkflowStep.SELECT_OUTER_PARTS,
|
||||||
history: {
|
history: {
|
||||||
@@ -764,9 +764,9 @@ export class ClaimRequestManagementService {
|
|||||||
otherPartsArr.length > 0 ? otherPartsArr : [],
|
otherPartsArr.length > 0 ? otherPartsArr : [],
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
status: ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS,
|
status: ClaimCaseStatus.CAPTURING_PART_DAMAGES,
|
||||||
"workflow.currentStep": ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
"workflow.currentStep": ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
||||||
"workflow.nextStep": ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
"workflow.nextStep": ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
||||||
$push: {
|
$push: {
|
||||||
"workflow.completedSteps": ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
"workflow.completedSteps": ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
||||||
history: {
|
history: {
|
||||||
@@ -3355,6 +3355,13 @@ export class ClaimRequestManagementService {
|
|||||||
owner: {
|
owner: {
|
||||||
userId: new Types.ObjectId(currentUserId),
|
userId: new Types.ObjectId(currentUserId),
|
||||||
userRole: currentUserParty.role as any,
|
userRole: currentUserParty.role as any,
|
||||||
|
...(currentUserParty.person?.clientId
|
||||||
|
? {
|
||||||
|
clientId: new Types.ObjectId(
|
||||||
|
currentUserParty.person.clientId as any,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
},
|
},
|
||||||
history: [
|
history: [
|
||||||
{
|
{
|
||||||
@@ -3476,6 +3483,11 @@ export class ClaimRequestManagementService {
|
|||||||
owner: {
|
owner: {
|
||||||
userId: new Types.ObjectId(damagedUserId),
|
userId: new Types.ObjectId(damagedUserId),
|
||||||
userRole: damagedParty?.role as any,
|
userRole: damagedParty?.role as any,
|
||||||
|
...(damagedParty?.person?.clientId
|
||||||
|
? {
|
||||||
|
clientId: new Types.ObjectId(damagedParty.person.clientId as any),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
},
|
},
|
||||||
history: [
|
history: [
|
||||||
{
|
{
|
||||||
@@ -3611,6 +3623,11 @@ export class ClaimRequestManagementService {
|
|||||||
owner: {
|
owner: {
|
||||||
userId: new Types.ObjectId(damagedUserId),
|
userId: new Types.ObjectId(damagedUserId),
|
||||||
userRole: damagedParty?.role as any,
|
userRole: damagedParty?.role as any,
|
||||||
|
...(damagedParty?.person?.clientId
|
||||||
|
? {
|
||||||
|
clientId: new Types.ObjectId(damagedParty.person.clientId as any),
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
},
|
},
|
||||||
createdByRegistrarId: new Types.ObjectId(registrar.sub),
|
createdByRegistrarId: new Types.ObjectId(registrar.sub),
|
||||||
history: [
|
history: [
|
||||||
@@ -3721,7 +3738,7 @@ export class ClaimRequestManagementService {
|
|||||||
'damage.selectedParts': selectedParts,
|
'damage.selectedParts': selectedParts,
|
||||||
'status': ClaimCaseStatus.SELECTING_OTHER_PARTS,
|
'status': ClaimCaseStatus.SELECTING_OTHER_PARTS,
|
||||||
'workflow.currentStep': ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
'workflow.currentStep': ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
||||||
'workflow.nextStep': ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
'workflow.nextStep': ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
||||||
$push: {
|
$push: {
|
||||||
'workflow.completedSteps': ClaimWorkflowStep.SELECT_OUTER_PARTS,
|
'workflow.completedSteps': ClaimWorkflowStep.SELECT_OUTER_PARTS,
|
||||||
history: {
|
history: {
|
||||||
@@ -3757,7 +3774,7 @@ export class ClaimRequestManagementService {
|
|||||||
publicId: updatedClaim.publicId,
|
publicId: updatedClaim.publicId,
|
||||||
selectedParts: selectedParts,
|
selectedParts: selectedParts,
|
||||||
currentStep: ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
currentStep: ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
||||||
nextStep: ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
nextStep: ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
||||||
message: 'Outer parts selected successfully. Please proceed to select other parts and provide bank information.',
|
message: 'Outer parts selected successfully. Please proceed to select other parts and provide bank information.',
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -3875,9 +3892,9 @@ export class ClaimRequestManagementService {
|
|||||||
'damage.otherParts': otherParts.length > 0 ? otherParts : undefined,
|
'damage.otherParts': otherParts.length > 0 ? otherParts : undefined,
|
||||||
'money.sheba': shebaNumber,
|
'money.sheba': shebaNumber,
|
||||||
'money.nationalCodeOfInsurer': nationalCode,
|
'money.nationalCodeOfInsurer': nationalCode,
|
||||||
'status': ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS,
|
'status': ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS ,
|
||||||
'workflow.currentStep': ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
'workflow.currentStep': ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
||||||
'workflow.nextStep': ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
'workflow.nextStep': ClaimWorkflowStep.USER_SUBMISSION_COMPLETE,
|
||||||
$push: {
|
$push: {
|
||||||
'workflow.completedSteps': ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
'workflow.completedSteps': ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
||||||
history: {
|
history: {
|
||||||
@@ -3956,9 +3973,10 @@ export class ClaimRequestManagementService {
|
|||||||
otherParts: otherParts,
|
otherParts: otherParts,
|
||||||
shebaNumber: maskedSheba,
|
shebaNumber: maskedSheba,
|
||||||
nationalCodeOfOwner: maskedNationalCode,
|
nationalCodeOfOwner: maskedNationalCode,
|
||||||
currentStep: ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
currentStep: ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
||||||
nextStep: ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
nextStep: ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
||||||
message: 'Other parts and bank information saved successfully. Please proceed to upload required documents.',
|
message:
|
||||||
|
'Other parts and bank information saved successfully. Please capture car angles and damaged parts, then upload required documents.',
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof HttpException) throw error;
|
if (error instanceof HttpException) throw error;
|
||||||
@@ -4025,7 +4043,6 @@ export class ClaimRequestManagementService {
|
|||||||
: null;
|
: null;
|
||||||
const isCarBody = blameRequest?.type === BlameRequestType.CAR_BODY;
|
const isCarBody = blameRequest?.type === BlameRequestType.CAR_BODY;
|
||||||
const requiredDocsDefinition = [
|
const requiredDocsDefinition = [
|
||||||
{ key: 'car_green_card', label_fa: 'کارت سبز خودرو', label_en: 'Car Green Card', category: 'general' },
|
|
||||||
{ key: 'damaged_driving_license_front', label_fa: 'گواهینامه طرف آسیبدیده - جلو', label_en: 'Damaged Party License - Front', category: 'damaged_party' },
|
{ key: 'damaged_driving_license_front', label_fa: 'گواهینامه طرف آسیبدیده - جلو', label_en: 'Damaged Party License - Front', category: 'damaged_party' },
|
||||||
{ key: 'damaged_driving_license_back', label_fa: 'گواهینامه طرف آسیبدیده - پشت', label_en: 'Damaged Party License - Back', category: 'damaged_party' },
|
{ key: 'damaged_driving_license_back', label_fa: 'گواهینامه طرف آسیبدیده - پشت', label_en: 'Damaged Party License - Back', category: 'damaged_party' },
|
||||||
{ key: 'damaged_chassis_number', label_fa: 'شماره شاسی', label_en: 'Chassis Number', category: 'damaged_party' },
|
{ key: 'damaged_chassis_number', label_fa: 'شماره شاسی', label_en: 'Chassis Number', category: 'damaged_party' },
|
||||||
@@ -4207,11 +4224,14 @@ export class ClaimRequestManagementService {
|
|||||||
const uploadedCount = (currentDocs instanceof Map ? currentDocs.size : Object.keys(currentDocs).length) + 1;
|
const uploadedCount = (currentDocs instanceof Map ? currentDocs.size : Object.keys(currentDocs).length) + 1;
|
||||||
const allDocumentsUploaded = uploadedCount >= totalDocsRequired;
|
const allDocumentsUploaded = uploadedCount >= totalDocsRequired;
|
||||||
|
|
||||||
// If all documents uploaded, move to next step
|
// If all documents uploaded, user flow complete (captures were done earlier in v2)
|
||||||
if (allDocumentsUploaded) {
|
if (allDocumentsUploaded) {
|
||||||
updateData['status'] = ClaimCaseStatus.CAPTURING_PART_DAMAGES;
|
updateData['status'] = ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT;
|
||||||
updateData['workflow.currentStep'] = ClaimWorkflowStep.CAPTURE_PART_DAMAGES;
|
updateData['claimStatus'] = ClaimStatus.PENDING;
|
||||||
updateData['workflow.nextStep'] = ClaimWorkflowStep.USER_SUBMISSION_COMPLETE;
|
updateData['workflow.currentStep'] =
|
||||||
|
ClaimWorkflowStep.USER_SUBMISSION_COMPLETE;
|
||||||
|
updateData['workflow.nextStep'] =
|
||||||
|
ClaimWorkflowStep.EXPERT_DAMAGE_ASSESSMENT;
|
||||||
updateData.$push.history = [
|
updateData.$push.history = [
|
||||||
updateData.$push.history,
|
updateData.$push.history,
|
||||||
{
|
{
|
||||||
@@ -4227,15 +4247,30 @@ export class ClaimRequestManagementService {
|
|||||||
description: 'All required documents uploaded',
|
description: 'All required documents uploaded',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'STEP_COMPLETED',
|
||||||
|
actor: {
|
||||||
|
actorId: new Types.ObjectId(currentUserId),
|
||||||
|
actorName: claimCase.owner?.fullName || 'User',
|
||||||
|
actorType: 'user',
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
metadata: {
|
||||||
|
stepKey: ClaimWorkflowStep.USER_SUBMISSION_COMPLETE,
|
||||||
|
description:
|
||||||
|
'User submission complete. Claim ready for damage expert review.',
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
updateData.$push['workflow.completedSteps'] = ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS;
|
updateData.$push['workflow.completedSteps'] =
|
||||||
|
ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, updateData);
|
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, updateData);
|
||||||
|
|
||||||
const remaining = totalDocsRequired - uploadedCount;
|
const remaining = totalDocsRequired - uploadedCount;
|
||||||
const message = allDocumentsUploaded
|
const message = allDocumentsUploaded
|
||||||
? 'All documents uploaded successfully. Please proceed to capture car angles and damaged parts.'
|
? '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.`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -4243,7 +4278,9 @@ export class ClaimRequestManagementService {
|
|||||||
documentKey: body.documentKey,
|
documentKey: body.documentKey,
|
||||||
fileUrl,
|
fileUrl,
|
||||||
allDocumentsUploaded,
|
allDocumentsUploaded,
|
||||||
currentStep: allDocumentsUploaded ? ClaimWorkflowStep.CAPTURE_PART_DAMAGES : ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
currentStep: allDocumentsUploaded
|
||||||
|
? ClaimWorkflowStep.USER_SUBMISSION_COMPLETE
|
||||||
|
: ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
||||||
message,
|
message,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -4340,10 +4377,9 @@ export class ClaimRequestManagementService {
|
|||||||
|
|
||||||
if (allCapturesComplete) {
|
if (allCapturesComplete) {
|
||||||
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
||||||
status: ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT,
|
status: ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS,
|
||||||
claimStatus: ClaimStatus.PENDING,
|
'workflow.currentStep': ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
||||||
'workflow.currentStep': ClaimWorkflowStep.USER_SUBMISSION_COMPLETE,
|
'workflow.nextStep': ClaimWorkflowStep.USER_SUBMISSION_COMPLETE,
|
||||||
'workflow.nextStep': ClaimWorkflowStep.EXPERT_DAMAGE_ASSESSMENT,
|
|
||||||
$push: {
|
$push: {
|
||||||
'workflow.completedSteps': ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
'workflow.completedSteps': ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
||||||
history: {
|
history: {
|
||||||
@@ -4355,8 +4391,9 @@ export class ClaimRequestManagementService {
|
|||||||
},
|
},
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
metadata: {
|
metadata: {
|
||||||
stepKey: ClaimWorkflowStep.USER_SUBMISSION_COMPLETE,
|
stepKey: ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
||||||
description: 'User submission complete. Claim ready for damage expert review.',
|
description:
|
||||||
|
'All car angles and damaged parts captured. Please upload required documents.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -4365,7 +4402,7 @@ export class ClaimRequestManagementService {
|
|||||||
|
|
||||||
const remaining = (4 - anglesCaptured) + (selectedParts.length - partsCaptured);
|
const remaining = (4 - anglesCaptured) + (selectedParts.length - partsCaptured);
|
||||||
const message = allCapturesComplete
|
const message = allCapturesComplete
|
||||||
? 'All captures complete. Your claim is now ready for damage expert review.'
|
? '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} captures remaining.`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -4375,7 +4412,7 @@ export class ClaimRequestManagementService {
|
|||||||
fileUrl,
|
fileUrl,
|
||||||
allCapturesComplete,
|
allCapturesComplete,
|
||||||
currentStep: allCapturesComplete
|
currentStep: allCapturesComplete
|
||||||
? ClaimWorkflowStep.USER_SUBMISSION_COMPLETE
|
? ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS
|
||||||
: ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
: ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
||||||
message,
|
message,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -360,8 +360,8 @@ Optional: upload car green card file in the same step.
|
|||||||
- headlight, taillight, mirror, glass
|
- headlight, taillight, mirror, glass
|
||||||
|
|
||||||
**After Success:**
|
**After Success:**
|
||||||
- Workflow moves to: UPLOAD_REQUIRED_DOCUMENTS (Step 4)
|
- Workflow moves to: CAPTURE_PART_DAMAGES (Step 4)
|
||||||
- User can proceed to upload required documents
|
- User captures car angles and damaged-part photos, then uploads required documents (Step 5)
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
@ApiParam({
|
@ApiParam({
|
||||||
@@ -461,6 +461,8 @@ Optional: upload car green card file in the same step.
|
|||||||
- 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.
|
||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
@ApiParam({
|
@ApiParam({
|
||||||
@@ -523,9 +525,9 @@ Returns status of each item (uploaded/captured or not).
|
|||||||
)
|
)
|
||||||
@ApiConsumes("multipart/form-data")
|
@ApiConsumes("multipart/form-data")
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Upload Required Document (V2 - Step 4)",
|
summary: "Upload Required Document (V2 - Step 5)",
|
||||||
description: `
|
description: `
|
||||||
**Workflow Step:** UPLOAD_REQUIRED_DOCUMENTS (Step 4 of Claim)
|
**Workflow Step:** UPLOAD_REQUIRED_DOCUMENTS (Step 5 of Claim)
|
||||||
|
|
||||||
**Upload one of the required documents** (13 for THIRD_PARTY; CAR_BODY may require fewer — see capture-requirements):
|
**Upload one of the required documents** (13 for THIRD_PARTY; CAR_BODY may require fewer — see capture-requirements):
|
||||||
- car_green_card
|
- car_green_card
|
||||||
@@ -534,7 +536,7 @@ Returns status of each item (uploaded/captured or not).
|
|||||||
- damaged_car_card_front/back, damaged_metal_plate
|
- damaged_car_card_front/back, damaged_metal_plate
|
||||||
- guilty_driving_license_front/back, guilty_car_card_front/back, guilty_metal_plate (THIRD_PARTY)
|
- guilty_driving_license_front/back, guilty_car_card_front/back, guilty_metal_plate (THIRD_PARTY)
|
||||||
|
|
||||||
**When all required documents are uploaded:** Workflow moves to CAPTURE_PART_DAMAGES (Step 5).
|
**When all required documents are uploaded:** Workflow moves to USER_SUBMISSION_COMPLETE and the claim is ready for damage expert review.
|
||||||
|
|
||||||
**Field expert IN_PERSON:** Same endpoint; use with claim created from expert-initiated IN_PERSON blame to upload documents on behalf of the damaged party.
|
**Field expert IN_PERSON:** Same endpoint; use with claim created from expert-initiated IN_PERSON blame to upload documents on behalf of the damaged party.
|
||||||
`,
|
`,
|
||||||
@@ -552,7 +554,6 @@ Returns status of each item (uploaded/captured or not).
|
|||||||
documentKey: {
|
documentKey: {
|
||||||
type: "string",
|
type: "string",
|
||||||
enum: [
|
enum: [
|
||||||
"car_green_card",
|
|
||||||
"damaged_driving_license_front",
|
"damaged_driving_license_front",
|
||||||
"damaged_driving_license_back",
|
"damaged_driving_license_back",
|
||||||
"damaged_chassis_number",
|
"damaged_chassis_number",
|
||||||
@@ -566,7 +567,7 @@ Returns status of each item (uploaded/captured or not).
|
|||||||
"guilty_car_card_back",
|
"guilty_car_card_back",
|
||||||
"guilty_metal_plate",
|
"guilty_metal_plate",
|
||||||
],
|
],
|
||||||
example: "car_green_card",
|
example: "damaged_driving_license_front",
|
||||||
},
|
},
|
||||||
file: {
|
file: {
|
||||||
type: "string",
|
type: "string",
|
||||||
@@ -632,15 +633,15 @@ Returns status of each item (uploaded/captured or not).
|
|||||||
)
|
)
|
||||||
@ApiConsumes("multipart/form-data")
|
@ApiConsumes("multipart/form-data")
|
||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Capture Car Angle or Damaged Part (V2 - Step 5)",
|
summary: "Capture Car Angle or Damaged Part (V2 - Step 4)",
|
||||||
description: `
|
description: `
|
||||||
**Workflow Step:** CAPTURE_PART_DAMAGES (Step 5 of Claim)
|
**Workflow Step:** CAPTURE_PART_DAMAGES (Step 4 of Claim)
|
||||||
|
|
||||||
**Capture types:**
|
**Capture types:**
|
||||||
1. **angle**: Car angles (front, back, left, right) - 4 required
|
1. **angle**: Car angles (front, back, left, right) - 4 required
|
||||||
2. **part**: Damaged parts based on selectedParts from Step 2
|
2. **part**: Damaged parts based on selectedParts from Step 2
|
||||||
|
|
||||||
**All captures must be completed before user can submit claim.**
|
**When all captures are complete:** Workflow moves to UPLOAD_REQUIRED_DOCUMENTS (Step 5).
|
||||||
|
|
||||||
**Field expert IN_PERSON:** Same endpoint; use with claim created from expert-initiated IN_PERSON blame to capture photos on behalf of the damaged party.
|
**Field expert IN_PERSON:** Same endpoint; use with claim created from expert-initiated IN_PERSON blame to capture photos on behalf of the damaged party.
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -117,19 +117,20 @@ export class SelectOtherPartsV2ResponseDto {
|
|||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'Current workflow step',
|
description: 'Current workflow step',
|
||||||
example: 'UPLOAD_REQUIRED_DOCUMENTS',
|
example: 'CAPTURE_PART_DAMAGES',
|
||||||
})
|
})
|
||||||
currentStep: string;
|
currentStep: string;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'Next possible workflow step',
|
description: 'Next possible workflow step',
|
||||||
example: 'CAPTURE_PART_DAMAGES',
|
example: 'UPLOAD_REQUIRED_DOCUMENTS',
|
||||||
})
|
})
|
||||||
nextStep: string;
|
nextStep: string;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'Success message',
|
description: 'Success message',
|
||||||
example: 'Other parts and bank information saved successfully. Please proceed to upload required documents.',
|
example:
|
||||||
|
'Other parts and bank information saved successfully. Please capture car angles and damaged parts, then upload required documents.',
|
||||||
})
|
})
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ export class ClaimOwner {
|
|||||||
|
|
||||||
@Prop({ type: String })
|
@Prop({ type: String })
|
||||||
fullName?: string;
|
fullName?: string;
|
||||||
|
|
||||||
|
@Prop({ type: Types.ObjectId })
|
||||||
|
clientId: Types.ObjectId;
|
||||||
}
|
}
|
||||||
export const ClaimOwnerSchema = SchemaFactory.createForClass(ClaimOwner);
|
export const ClaimOwnerSchema = SchemaFactory.createForClass(ClaimOwner);
|
||||||
|
|
||||||
|
|||||||
@@ -669,33 +669,22 @@ export class ExpertBlameService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const parties = (doc.parties ?? []);
|
const parties = Array.isArray(doc.parties) ? doc.parties : [];
|
||||||
|
|
||||||
const typedParties = parties as Array<{
|
const typedParties = parties as Array<{
|
||||||
vehicle?: {
|
vehicle?: {
|
||||||
inquiry?: {
|
inquiry?: {
|
||||||
mapped?: any; // Use 'any' or a more specific type if known
|
mapped?: any;
|
||||||
// other inquiry properties
|
|
||||||
};
|
};
|
||||||
// other vehicle properties
|
|
||||||
};
|
};
|
||||||
evidence?: { // For the second part of your original error description
|
evidence?: {
|
||||||
videoId?: string | number;
|
videoId?: string | number;
|
||||||
voices?: (string | number)[];
|
voices?: (string | number)[];
|
||||||
videoUrl?: string;
|
videoUrl?: string;
|
||||||
voiceUrls?: string[];
|
voiceUrls?: string[];
|
||||||
};
|
};
|
||||||
// other party properties
|
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
// Extract inquiry.mapped for each party if needed, but we will keep the full vehicle object.
|
|
||||||
for (const party of typedParties) {
|
|
||||||
const mapped = party?.vehicle?.inquiry?.mapped;
|
|
||||||
// If you still want this field, you can add it like:
|
|
||||||
// party.inquiryMapped = mapped ?? null;
|
|
||||||
// But based on your last request, we are keeping the full vehicle object.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evidence (videos + voices)
|
// Evidence (videos + voices)
|
||||||
for (const party of typedParties) {
|
for (const party of typedParties) {
|
||||||
if (!party.evidence) continue;
|
if (!party.evidence) continue;
|
||||||
@@ -727,7 +716,13 @@ export class ExpertBlameService {
|
|||||||
doc.createdAtFormatted = `${createdDate} ${createdTime}`;
|
doc.createdAtFormatted = `${createdDate} ${createdTime}`;
|
||||||
doc.updatedAtFormatted = `${updatedDate} ${updatedTime}`;
|
doc.updatedAtFormatted = `${updatedDate} ${updatedTime}`;
|
||||||
|
|
||||||
// The line `delete party.vehicle;` has been removed, so vehicle will be included.
|
// Omit heavy SandHub inquiry blob from expert response (keep other vehicle fields)
|
||||||
|
for (const party of typedParties) {
|
||||||
|
const veh = party?.vehicle as Record<string, unknown> | undefined;
|
||||||
|
if (veh && Object.prototype.hasOwnProperty.call(veh, "inquiry")) {
|
||||||
|
delete veh.inquiry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return doc;
|
return doc;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { BlameRequestType } from 'src/Types&Enums/blame-request-management/blameRequestType.enum';
|
||||||
|
|
||||||
export class ClaimDetailV2ResponseDto {
|
export class ClaimDetailV2ResponseDto {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@@ -43,6 +44,19 @@ export class ClaimDetailV2ResponseDto {
|
|||||||
plate?: any;
|
plate?: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Blame file type from linked blame case',
|
||||||
|
enum: BlameRequestType,
|
||||||
|
})
|
||||||
|
blameRequestType?: BlameRequestType;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description:
|
||||||
|
'CAR_BODY only: first-step flags — another car (`car`) and/or object (`object`)',
|
||||||
|
example: { car: true, object: false },
|
||||||
|
})
|
||||||
|
carBodyFirstForm?: { car?: boolean; object?: boolean };
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: 'Blame request ID', example: '507f...' })
|
@ApiPropertyOptional({ description: 'Blame request ID', example: '507f...' })
|
||||||
blameRequestId?: string;
|
blameRequestId?: string;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { BlameRequestType } from 'src/Types&Enums/blame-request-management/blameRequestType.enum';
|
||||||
|
|
||||||
export class ClaimListItemV2Dto {
|
export class ClaimListItemV2Dto {
|
||||||
@ApiProperty({ description: 'Claim case ID' })
|
@ApiProperty({ description: 'Claim case ID' })
|
||||||
@@ -29,6 +30,20 @@ export class ClaimListItemV2Dto {
|
|||||||
carType?: string;
|
carType?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Blame file type from linked blame case',
|
||||||
|
enum: BlameRequestType,
|
||||||
|
example: BlameRequestType.THIRD_PARTY,
|
||||||
|
})
|
||||||
|
blameRequestType?: BlameRequestType;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description:
|
||||||
|
'CAR_BODY only: first-step flags — accident involved another car (`car`) and/or fixed object (`object`)',
|
||||||
|
example: { car: true, object: false },
|
||||||
|
})
|
||||||
|
carBodyFirstForm?: { car?: boolean; object?: boolean };
|
||||||
|
|
||||||
@ApiProperty({ description: 'Submission date', example: '2026-02-22T10:00:00.000Z' })
|
@ApiProperty({ description: 'Submission date', example: '2026-02-22T10:00:00.000Z' })
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ import {
|
|||||||
requireActorClientKey,
|
requireActorClientKey,
|
||||||
} from "src/helpers/tenant-scope";
|
} from "src/helpers/tenant-scope";
|
||||||
import { ClaimWorkflowStep } from "src/Types&Enums/claim-request-management/claim-workflow-steps.enum";
|
import { ClaimWorkflowStep } from "src/Types&Enums/claim-request-management/claim-workflow-steps.enum";
|
||||||
|
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
||||||
|
import { PartyRole } from "src/request-management/entities/schema/partyRole.enum";
|
||||||
import { GetClaimListV2ResponseDto, ClaimListItemV2Dto } from "./dto/claim-list-v2.dto";
|
import { GetClaimListV2ResponseDto, ClaimListItemV2Dto } from "./dto/claim-list-v2.dto";
|
||||||
import { ClaimDetailV2ResponseDto } from "./dto/claim-detail-v2.dto";
|
import { ClaimDetailV2ResponseDto } from "./dto/claim-detail-v2.dto";
|
||||||
import { ClaimSubmitReplyDto, ClaimSubmitResendDto } from "./dto/reply.dto";
|
import { ClaimSubmitReplyDto, ClaimSubmitResendDto } from "./dto/reply.dto";
|
||||||
@@ -172,6 +174,117 @@ export class ExpertClaimService {
|
|||||||
private readonly branchDbService: BranchDbService,
|
private readonly branchDbService: BranchDbService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
/** Map blame party `Vehicle` (name/model/type) to expert panel shape. */
|
||||||
|
private expertVehicleFromPartyVehicle(v: any): {
|
||||||
|
carName?: string;
|
||||||
|
carModel?: string;
|
||||||
|
carType?: string;
|
||||||
|
plate?: { plateId?: string };
|
||||||
|
} | undefined {
|
||||||
|
if (!v || typeof v !== "object") return undefined;
|
||||||
|
const carName = v.name ?? v.carName;
|
||||||
|
const carModel = v.model ?? v.carModel;
|
||||||
|
const carType = v.type ?? v.carType;
|
||||||
|
const plateId = v.plateId;
|
||||||
|
const plate =
|
||||||
|
plateId != null && String(plateId).length > 0
|
||||||
|
? { plateId: String(plateId) }
|
||||||
|
: undefined;
|
||||||
|
if (!carName && !carModel && !carType && !plate) return undefined;
|
||||||
|
return { carName, carModel, carType, ...(plate ? { plate } : {}) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Damaged party vehicle on blame (matches claim owner, or non-guilty party for THIRD_PARTY). */
|
||||||
|
private damagedPartyVehicleFromBlame(
|
||||||
|
blame: any,
|
||||||
|
claim: any,
|
||||||
|
):
|
||||||
|
| {
|
||||||
|
carName?: string;
|
||||||
|
carModel?: string;
|
||||||
|
carType?: string;
|
||||||
|
plate?: { plateId?: string };
|
||||||
|
}
|
||||||
|
| undefined {
|
||||||
|
const parties = blame?.parties;
|
||||||
|
if (!Array.isArray(parties) || parties.length === 0) return undefined;
|
||||||
|
|
||||||
|
const ownerId = claim?.owner?.userId ? String(claim.owner.userId) : "";
|
||||||
|
let party = ownerId
|
||||||
|
? parties.find(
|
||||||
|
(p: any) =>
|
||||||
|
p?.person?.userId && String(p.person.userId) === ownerId,
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (!party && blame?.expert?.decision?.guiltyPartyId) {
|
||||||
|
const guilty = String(blame.expert.decision.guiltyPartyId);
|
||||||
|
party = parties.find(
|
||||||
|
(p: any) =>
|
||||||
|
p?.person?.userId && String(p.person.userId) !== guilty,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!party && parties.length === 1) party = parties[0];
|
||||||
|
|
||||||
|
return this.expertVehicleFromPartyVehicle(party?.vehicle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private vehicleForExpertFromClaimAndBlameMap(
|
||||||
|
claim: any,
|
||||||
|
blameById: Map<string, any>,
|
||||||
|
):
|
||||||
|
| { carName?: string; carModel?: string; carType?: string; plate?: { plateId?: string } }
|
||||||
|
| undefined {
|
||||||
|
const cv = claim?.vehicle;
|
||||||
|
if (
|
||||||
|
cv &&
|
||||||
|
(cv.carName || cv.carModel || cv.carType || (cv as any).plate)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
carName: cv.carName,
|
||||||
|
carModel: cv.carModel,
|
||||||
|
carType: cv.carType,
|
||||||
|
...((cv as any).plate ? { plate: (cv as any).plate } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const bid = claim?.blameRequestId?.toString();
|
||||||
|
if (!bid) return undefined;
|
||||||
|
const blame = blameById.get(bid);
|
||||||
|
if (!blame) return undefined;
|
||||||
|
return this.damagedPartyVehicleFromBlame(blame, claim);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blame file kind for damage-expert UI: THIRD_PARTY vs CAR_BODY, and CAR_BODY first-step flags.
|
||||||
|
*/
|
||||||
|
private blameFileContextForExpert(blame: any): {
|
||||||
|
blameRequestType?: BlameRequestType;
|
||||||
|
carBodyFirstForm?: { car?: boolean; object?: boolean };
|
||||||
|
} {
|
||||||
|
if (!blame?.type) return {};
|
||||||
|
const blameRequestType = blame.type as BlameRequestType;
|
||||||
|
const out: {
|
||||||
|
blameRequestType?: BlameRequestType;
|
||||||
|
carBodyFirstForm?: { car?: boolean; object?: boolean };
|
||||||
|
} = { blameRequestType };
|
||||||
|
if (blameRequestType !== BlameRequestType.CAR_BODY) return out;
|
||||||
|
|
||||||
|
const parties = blame.parties;
|
||||||
|
if (!Array.isArray(parties) || parties.length === 0) return out;
|
||||||
|
|
||||||
|
const first =
|
||||||
|
parties.find((p: any) => p?.role === PartyRole.FIRST) ?? parties[0];
|
||||||
|
const cbf = first?.carBodyFirstForm;
|
||||||
|
if (cbf && typeof cbf === "object") {
|
||||||
|
out.carBodyFirstForm = {
|
||||||
|
...(typeof cbf.car === "boolean" ? { car: cbf.car } : {}),
|
||||||
|
...(typeof cbf.object === "boolean" ? { object: cbf.object } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
private isVisibleToClientType(client: any, actor: any): boolean {
|
private isVisibleToClientType(client: any, actor: any): boolean {
|
||||||
if (actor.userType === UserType.GENUINE) {
|
if (actor.userType === UserType.GENUINE) {
|
||||||
return true;
|
return true;
|
||||||
@@ -2237,36 +2350,62 @@ export class ExpertClaimService {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const list = (claims as any[])
|
const filtered = (claims as any[]).filter((c) =>
|
||||||
.filter((c) => claimCaseTouchesClient(c, clientKey))
|
claimCaseTouchesClient(c, clientKey),
|
||||||
.map((c) => {
|
);
|
||||||
const awaitingFactorValidation =
|
|
||||||
c.status === ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL &&
|
const blameIds = [
|
||||||
c.claimStatus === ClaimStatus.UNDER_REVIEW &&
|
...new Set(
|
||||||
c.workflow?.currentStep === ClaimWorkflowStep.EXPERT_COST_EVALUATION;
|
filtered
|
||||||
return {
|
.map((c) => c.blameRequestId?.toString())
|
||||||
claimRequestId: c._id.toString(),
|
.filter((id): id is string => !!id),
|
||||||
publicId: c.publicId,
|
),
|
||||||
status: c.status,
|
];
|
||||||
currentStep: c.workflow?.currentStep || '',
|
const blames =
|
||||||
locked: c.workflow?.locked || false,
|
blameIds.length > 0
|
||||||
lockedBy: c.workflow?.lockedBy
|
? ((await this.blameRequestDbService.find(
|
||||||
? {
|
{ _id: { $in: blameIds.map((id) => new Types.ObjectId(id)) } },
|
||||||
actorId: c.workflow.lockedBy.actorId?.toString(),
|
{ lean: true, select: "type parties expert.decision" },
|
||||||
actorName: c.workflow.lockedBy.actorName,
|
)) as any[])
|
||||||
}
|
: [];
|
||||||
: undefined,
|
const blameById = new Map<string, any>(
|
||||||
vehicle: c.vehicle
|
blames.map((b) => [String(b._id), b]),
|
||||||
? {
|
);
|
||||||
carName: c.vehicle.carName,
|
|
||||||
carModel: c.vehicle.carModel,
|
const list = filtered.map((c) => {
|
||||||
carType: c.vehicle.carType,
|
const awaitingFactorValidation =
|
||||||
}
|
c.status === ClaimCaseStatus.WAITING_FOR_INSURER_APPROVAL &&
|
||||||
: undefined,
|
c.claimStatus === ClaimStatus.UNDER_REVIEW &&
|
||||||
createdAt: c.createdAt,
|
c.workflow?.currentStep === ClaimWorkflowStep.EXPERT_COST_EVALUATION;
|
||||||
awaitingFactorValidation,
|
const v = this.vehicleForExpertFromClaimAndBlameMap(c, blameById);
|
||||||
};
|
const blame = c.blameRequestId
|
||||||
}) as ClaimListItemV2Dto[];
|
? blameById.get(c.blameRequestId.toString())
|
||||||
|
: undefined;
|
||||||
|
const fileCtx = blame ? this.blameFileContextForExpert(blame) : {};
|
||||||
|
return {
|
||||||
|
claimRequestId: c._id.toString(),
|
||||||
|
publicId: c.publicId,
|
||||||
|
status: c.status,
|
||||||
|
currentStep: c.workflow?.currentStep || "",
|
||||||
|
locked: c.workflow?.locked || false,
|
||||||
|
lockedBy: c.workflow?.lockedBy
|
||||||
|
? {
|
||||||
|
actorId: c.workflow.lockedBy.actorId?.toString(),
|
||||||
|
actorName: c.workflow.lockedBy.actorName,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
vehicle: v
|
||||||
|
? {
|
||||||
|
carName: v.carName,
|
||||||
|
carModel: v.carModel,
|
||||||
|
carType: v.carType,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
...fileCtx,
|
||||||
|
createdAt: c.createdAt,
|
||||||
|
awaitingFactorValidation,
|
||||||
|
};
|
||||||
|
}) as ClaimListItemV2Dto[];
|
||||||
|
|
||||||
return { list, total: list.length };
|
return { list, total: list.length };
|
||||||
}
|
}
|
||||||
@@ -2360,6 +2499,32 @@ export class ExpertClaimService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let vehiclePayload = claim.vehicle as any;
|
||||||
|
const hasClaimVehicle =
|
||||||
|
vehiclePayload &&
|
||||||
|
(vehiclePayload.carName ||
|
||||||
|
vehiclePayload.carModel ||
|
||||||
|
vehiclePayload.carType ||
|
||||||
|
vehiclePayload.plate);
|
||||||
|
|
||||||
|
let blameLean: any = null;
|
||||||
|
if (claim.blameRequestId) {
|
||||||
|
const rows = (await this.blameRequestDbService.find(
|
||||||
|
{ _id: new Types.ObjectId(claim.blameRequestId.toString()) },
|
||||||
|
{ lean: true, select: "type parties expert.decision" },
|
||||||
|
)) as any[];
|
||||||
|
blameLean = rows[0] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasClaimVehicle && blameLean) {
|
||||||
|
const fromBlame = this.damagedPartyVehicleFromBlame(blameLean, claim);
|
||||||
|
if (fromBlame) vehiclePayload = fromBlame;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blameFileContext = blameLean
|
||||||
|
? this.blameFileContextForExpert(blameLean)
|
||||||
|
: {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
claimRequestId: claim._id.toString(),
|
claimRequestId: claim._id.toString(),
|
||||||
publicId: claim.publicId,
|
publicId: claim.publicId,
|
||||||
@@ -2381,7 +2546,8 @@ export class ExpertClaimService {
|
|||||||
fullName: claim.owner.fullName,
|
fullName: claim.owner.fullName,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
vehicle: claim.vehicle,
|
vehicle: vehiclePayload,
|
||||||
|
...blameFileContext,
|
||||||
blameRequestId: claim.blameRequestId?.toString(),
|
blameRequestId: claim.blameRequestId?.toString(),
|
||||||
blameRequestNo: claim.blameRequestNo,
|
blameRequestNo: claim.blameRequestNo,
|
||||||
selectedParts: claim.damage?.selectedParts,
|
selectedParts: claim.damage?.selectedParts,
|
||||||
|
|||||||
@@ -19,7 +19,12 @@ export function blameCaseTouchesClient(doc: any, clientKey: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function claimCaseTouchesClient(doc: any, clientKey: string): boolean {
|
export function claimCaseTouchesClient(doc: any, clientKey: string): boolean {
|
||||||
return String(doc?.owner?.userClientKey ?? "") === String(clientKey);
|
const id = String(clientKey);
|
||||||
|
const owner = doc?.owner as
|
||||||
|
| { clientId?: unknown; userClientKey?: unknown }
|
||||||
|
| undefined;
|
||||||
|
const tenantId = owner?.clientId ?? owner?.userClientKey;
|
||||||
|
return String(tenantId ?? "") === id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
19
src/request-management/dto/claim-creation-hint.dto.ts
Normal file
19
src/request-management/dto/claim-creation-hint.dto.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computed on GET blame v2 for the current viewer — no DB field on BlameRequest.
|
||||||
|
*/
|
||||||
|
export class ClaimCreationHintDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description:
|
||||||
|
"True if a v2 claim case already exists for this blame (by blameRequestId).",
|
||||||
|
})
|
||||||
|
hasClaim: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description:
|
||||||
|
"True when this viewer is the damaged party, blame is COMPLETED, no claim exists yet, " +
|
||||||
|
"and for THIRD_PARTY all parties have signed and accepted the expert decision.",
|
||||||
|
})
|
||||||
|
shouldGuideToCreateClaim: boolean;
|
||||||
|
}
|
||||||
@@ -307,11 +307,11 @@ export class RequestManagementService {
|
|||||||
private readonly expertDbService: ExpertDbService,
|
private readonly expertDbService: ExpertDbService,
|
||||||
private readonly autoCloseRequestService: AutoCloseRequestService,
|
private readonly autoCloseRequestService: AutoCloseRequestService,
|
||||||
private readonly claimRequestManagementDbService: ClaimRequestManagementDbService,
|
private readonly claimRequestManagementDbService: ClaimRequestManagementDbService,
|
||||||
|
private readonly claimCaseDbService: ClaimCaseDbService,
|
||||||
private readonly publicIdService: PublicIdService,
|
private readonly publicIdService: PublicIdService,
|
||||||
private readonly workflowStepDbService: WorkflowStepDbService,
|
private readonly workflowStepDbService: WorkflowStepDbService,
|
||||||
private readonly hashService: HashService,
|
private readonly hashService: HashService,
|
||||||
private readonly userAuthService: UserAuthService,
|
private readonly userAuthService: UserAuthService,
|
||||||
private readonly claimCaseDbService: ClaimCaseDbService,
|
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -3982,6 +3982,81 @@ export class RequestManagementService {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* V2: Hint for UX — damaged user should open create-claim when blame is done and no claim exists.
|
||||||
|
* Mirrors eligibility rules in createClaimFromBlameV2 (without throwing).
|
||||||
|
*/
|
||||||
|
private async computeClaimCreationHintForViewer(
|
||||||
|
blame: any,
|
||||||
|
user: any,
|
||||||
|
): Promise<{ hasClaim: boolean; shouldGuideToCreateClaim: boolean }> {
|
||||||
|
const none = { hasClaim: false, shouldGuideToCreateClaim: false };
|
||||||
|
if (!user?.sub || !Types.ObjectId.isValid(String(user.sub))) {
|
||||||
|
return none;
|
||||||
|
}
|
||||||
|
const currentUserId = String(user.sub);
|
||||||
|
|
||||||
|
const existingClaim = await this.claimCaseDbService.findOne({
|
||||||
|
blameRequestId: blame._id,
|
||||||
|
});
|
||||||
|
const hasClaim = !!existingClaim;
|
||||||
|
if (hasClaim) {
|
||||||
|
return { hasClaim: true, shouldGuideToCreateClaim: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blame.status !== CaseStatus.COMPLETED) {
|
||||||
|
return { hasClaim: false, shouldGuideToCreateClaim: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
const parties = blame.parties || [];
|
||||||
|
|
||||||
|
if (blame.type === BlameRequestType.CAR_BODY) {
|
||||||
|
const first = parties.find(
|
||||||
|
(p: any) => p?.role === PartyRole.FIRST || p?.role === "FIRST",
|
||||||
|
);
|
||||||
|
const damagedId = first?.person?.userId
|
||||||
|
? String(first.person.userId)
|
||||||
|
: null;
|
||||||
|
if (!damagedId || damagedId !== currentUserId) {
|
||||||
|
return { hasClaim: false, shouldGuideToCreateClaim: false };
|
||||||
|
}
|
||||||
|
return { hasClaim: false, shouldGuideToCreateClaim: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blame.type === BlameRequestType.THIRD_PARTY) {
|
||||||
|
const guiltyRaw = blame.expert?.decision?.guiltyPartyId;
|
||||||
|
if (!guiltyRaw) {
|
||||||
|
return { hasClaim: false, shouldGuideToCreateClaim: false };
|
||||||
|
}
|
||||||
|
const guiltyId = String(guiltyRaw);
|
||||||
|
if (currentUserId === guiltyId) {
|
||||||
|
return { hasClaim: false, shouldGuideToCreateClaim: false };
|
||||||
|
}
|
||||||
|
const userParty = parties.find(
|
||||||
|
(p: any) =>
|
||||||
|
p?.person?.userId && String(p.person.userId) === currentUserId,
|
||||||
|
);
|
||||||
|
if (!userParty) {
|
||||||
|
return { hasClaim: false, shouldGuideToCreateClaim: false };
|
||||||
|
}
|
||||||
|
if (parties.length < 2) {
|
||||||
|
return { hasClaim: false, shouldGuideToCreateClaim: false };
|
||||||
|
}
|
||||||
|
const allSigned = parties.every(
|
||||||
|
(p: any) => p.confirmation !== undefined && p.confirmation !== null,
|
||||||
|
);
|
||||||
|
const allAccepted = parties.every(
|
||||||
|
(p: any) => p.confirmation?.accepted === true,
|
||||||
|
);
|
||||||
|
if (!allSigned || !allAccepted) {
|
||||||
|
return { hasClaim: false, shouldGuideToCreateClaim: false };
|
||||||
|
}
|
||||||
|
return { hasClaim: false, shouldGuideToCreateClaim: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
return none;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* V2: Get one blame request by id. Access allowed if current user is a party (by userId or phone)
|
* V2: Get one blame request by id. Access allowed if current user is a party (by userId or phone)
|
||||||
* or the initiating field expert. For expert-initiated LINK, party access by phone is sufficient.
|
* or the initiating field expert. For expert-initiated LINK, party access by phone is sufficient.
|
||||||
@@ -4024,7 +4099,13 @@ export class RequestManagementService {
|
|||||||
await (req as any).save();
|
await (req as any).save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return req;
|
|
||||||
|
const claimCreation = await this.computeClaimCreationHintForViewer(req, user);
|
||||||
|
const plain =
|
||||||
|
typeof (req as any).toObject === "function"
|
||||||
|
? (req as any).toObject({ versionKey: false })
|
||||||
|
: { ...(req as any) };
|
||||||
|
return { ...plain, claimCreation };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
ApiBearerAuth,
|
ApiBearerAuth,
|
||||||
ApiBody,
|
ApiBody,
|
||||||
ApiConsumes,
|
ApiConsumes,
|
||||||
|
ApiOperation,
|
||||||
ApiParam,
|
ApiParam,
|
||||||
ApiTags,
|
ApiTags,
|
||||||
} from "@nestjs/swagger";
|
} from "@nestjs/swagger";
|
||||||
@@ -77,6 +78,11 @@ export class RequestManagementV2Controller {
|
|||||||
* Get one blame request by id. Allowed for the request owner (party) or the initiating field expert.
|
* Get one blame request by id. Allowed for the request owner (party) or the initiating field expert.
|
||||||
*/
|
*/
|
||||||
@Get(":requestId")
|
@Get(":requestId")
|
||||||
|
@ApiOperation({
|
||||||
|
summary: "Get one blame request (v2)",
|
||||||
|
description:
|
||||||
|
"Response is the blame document plus **claimCreation** ({ hasClaim, shouldGuideToCreateClaim }) — computed for the current user so the app can route the damaged party to create-claim when the blame is COMPLETED and no v2 claim exists yet.",
|
||||||
|
})
|
||||||
@ApiParam({ name: "requestId", description: "Blame request ID" })
|
@ApiParam({ name: "requestId", description: "Blame request ID" })
|
||||||
@Roles(RoleEnum.USER, RoleEnum.FIELD_EXPERT)
|
@Roles(RoleEnum.USER, RoleEnum.FIELD_EXPERT)
|
||||||
async getBlameRequestV2(
|
async getBlameRequestV2(
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export const createClaimWorkflowStepExamples = {
|
|||||||
description_en: 'Select other damaged parts (non-body), Sheba number and insurer national code',
|
description_en: 'Select other damaged parts (non-body), Sheba number and insurer national code',
|
||||||
category: 'CLAIM',
|
category: 'CLAIM',
|
||||||
requiredPreviousSteps: ['SELECT_OUTER_PARTS'],
|
requiredPreviousSteps: ['SELECT_OUTER_PARTS'],
|
||||||
nextPossibleSteps: ['UPLOAD_REQUIRED_DOCUMENTS'],
|
nextPossibleSteps: ['CAPTURE_PART_DAMAGES'],
|
||||||
allowedRoles: ['user'],
|
allowedRoles: ['user'],
|
||||||
estimatedDuration: 4,
|
estimatedDuration: 4,
|
||||||
fields: [
|
fields: [
|
||||||
@@ -188,15 +188,15 @@ export const createClaimWorkflowStepExamples = {
|
|||||||
value: {
|
value: {
|
||||||
stepKey: 'UPLOAD_REQUIRED_DOCUMENTS',
|
stepKey: 'UPLOAD_REQUIRED_DOCUMENTS',
|
||||||
type: 'CLAIM',
|
type: 'CLAIM',
|
||||||
stepNumber: 4,
|
stepNumber: 5,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
stepName_fa: 'آپلود مدارک مورد نیاز',
|
stepName_fa: 'آپلود مدارک مورد نیاز',
|
||||||
stepName_en: 'Upload Required Documents',
|
stepName_en: 'Upload Required Documents',
|
||||||
description_fa: 'آپلود مدارک مورد نیاز برای بررسی خسارت (لیست داینامیک بر اساس قطعات انتخاب شده)',
|
description_fa: 'آپلود مدارک مورد نیاز برای بررسی خسارت (لیست داینامیک بر اساس قطعات انتخاب شده)',
|
||||||
description_en: 'Upload required documents for claim review (dynamic list based on selected parts)',
|
description_en: 'Upload required documents for claim review (dynamic list based on selected parts)',
|
||||||
category: 'CLAIM',
|
category: 'CLAIM',
|
||||||
requiredPreviousSteps: ['SELECT_OTHER_PARTS'],
|
requiredPreviousSteps: ['CAPTURE_PART_DAMAGES'],
|
||||||
nextPossibleSteps: ['CAPTURE_PART_DAMAGES'],
|
nextPossibleSteps: ['USER_SUBMISSION_COMPLETE'],
|
||||||
allowedRoles: ['user'],
|
allowedRoles: ['user'],
|
||||||
estimatedDuration: 5,
|
estimatedDuration: 5,
|
||||||
fields: [],
|
fields: [],
|
||||||
@@ -215,15 +215,15 @@ export const createClaimWorkflowStepExamples = {
|
|||||||
value: {
|
value: {
|
||||||
stepKey: 'CAPTURE_PART_DAMAGES',
|
stepKey: 'CAPTURE_PART_DAMAGES',
|
||||||
type: 'CLAIM',
|
type: 'CLAIM',
|
||||||
stepNumber: 5,
|
stepNumber: 4,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
stepName_fa: 'ثبت تصاویر آسیبهای قطعات',
|
stepName_fa: 'ثبت تصاویر آسیبهای قطعات',
|
||||||
stepName_en: 'Capture Part Damages',
|
stepName_en: 'Capture Part Damages',
|
||||||
description_fa: 'ثبت تصاویر آسیب برای هر قطعه (یک درخواست به ازای هر قطعه)',
|
description_fa: 'ثبت تصاویر آسیب برای هر قطعه (یک درخواست به ازای هر قطعه)',
|
||||||
description_en: 'Capture damage images for each part (one request per part)',
|
description_en: 'Capture damage images for each part (one request per part)',
|
||||||
category: 'CLAIM',
|
category: 'CLAIM',
|
||||||
requiredPreviousSteps: ['UPLOAD_REQUIRED_DOCUMENTS'],
|
requiredPreviousSteps: ['SELECT_OTHER_PARTS'],
|
||||||
nextPossibleSteps: ['USER_SUBMISSION_COMPLETE'],
|
nextPossibleSteps: ['UPLOAD_REQUIRED_DOCUMENTS'],
|
||||||
allowedRoles: ['user'],
|
allowedRoles: ['user'],
|
||||||
estimatedDuration: 10,
|
estimatedDuration: 10,
|
||||||
fields: [],
|
fields: [],
|
||||||
@@ -251,7 +251,7 @@ export const createClaimWorkflowStepExamples = {
|
|||||||
description_fa: 'اطلاعات کاربر به طور کامل ثبت شد و در انتظار بررسی کارشناس خسارت',
|
description_fa: 'اطلاعات کاربر به طور کامل ثبت شد و در انتظار بررسی کارشناس خسارت',
|
||||||
description_en: 'User information completed, waiting for damage expert review',
|
description_en: 'User information completed, waiting for damage expert review',
|
||||||
category: 'CLAIM',
|
category: 'CLAIM',
|
||||||
requiredPreviousSteps: ['CAPTURE_PART_DAMAGES'],
|
requiredPreviousSteps: ['UPLOAD_REQUIRED_DOCUMENTS'],
|
||||||
nextPossibleSteps: ['EXPERT_DAMAGE_ASSESSMENT'],
|
nextPossibleSteps: ['EXPERT_DAMAGE_ASSESSMENT'],
|
||||||
allowedRoles: ['user', 'damage_expert', 'admin'],
|
allowedRoles: ['user', 'damage_expert', 'admin'],
|
||||||
estimatedDuration: 0,
|
estimatedDuration: 0,
|
||||||
|
|||||||
@@ -1158,7 +1158,7 @@ export const claimWorkflowStepExamples = {
|
|||||||
"description_en": "Select other damaged parts (non-body), Sheba number and insurer national code",
|
"description_en": "Select other damaged parts (non-body), Sheba number and insurer national code",
|
||||||
"category": "CLAIM",
|
"category": "CLAIM",
|
||||||
"requiredPreviousSteps": ["SELECT_OUTER_PARTS"],
|
"requiredPreviousSteps": ["SELECT_OUTER_PARTS"],
|
||||||
"nextPossibleSteps": ["UPLOAD_REQUIRED_DOCUMENTS"],
|
"nextPossibleSteps": ["CAPTURE_PART_DAMAGES"],
|
||||||
"allowedRoles": ["user"],
|
"allowedRoles": ["user"],
|
||||||
"estimatedDuration": 4,
|
"estimatedDuration": 4,
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -1240,19 +1240,19 @@ export const claimWorkflowStepExamples = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
uploadRequiredDocuments: {
|
uploadRequiredDocuments: {
|
||||||
summary: 'Claim Step 4: UPLOAD_REQUIRED_DOCUMENTS',
|
summary: 'Claim Step 5: UPLOAD_REQUIRED_DOCUMENTS',
|
||||||
value: {
|
value: {
|
||||||
"stepKey": "UPLOAD_REQUIRED_DOCUMENTS",
|
"stepKey": "UPLOAD_REQUIRED_DOCUMENTS",
|
||||||
"type": "CLAIM",
|
"type": "CLAIM",
|
||||||
"stepNumber": 4,
|
"stepNumber": 5,
|
||||||
"isActive": true,
|
"isActive": true,
|
||||||
"stepName_fa": "آپلود مدارک مورد نیاز",
|
"stepName_fa": "آپلود مدارک مورد نیاز",
|
||||||
"stepName_en": "Upload Required Documents",
|
"stepName_en": "Upload Required Documents",
|
||||||
"description_fa": "آپلود مدارک مورد نیاز برای بررسی خسارت (لیست داینامیک بر اساس قطعات انتخاب شده)",
|
"description_fa": "آپلود مدارک مورد نیاز برای بررسی خسارت (لیست داینامیک بر اساس قطعات انتخاب شده)",
|
||||||
"description_en": "Upload required documents for claim review (dynamic list based on selected parts)",
|
"description_en": "Upload required documents for claim review (dynamic list based on selected parts)",
|
||||||
"category": "CLAIM",
|
"category": "CLAIM",
|
||||||
"requiredPreviousSteps": ["SELECT_OTHER_PARTS"],
|
"requiredPreviousSteps": ["CAPTURE_PART_DAMAGES"],
|
||||||
"nextPossibleSteps": ["CAPTURE_PART_DAMAGES"],
|
"nextPossibleSteps": ["USER_SUBMISSION_COMPLETE"],
|
||||||
"allowedRoles": ["user"],
|
"allowedRoles": ["user"],
|
||||||
"estimatedDuration": 5,
|
"estimatedDuration": 5,
|
||||||
"fields": [],
|
"fields": [],
|
||||||
@@ -1266,19 +1266,19 @@ export const claimWorkflowStepExamples = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
capturePartDamages: {
|
capturePartDamages: {
|
||||||
summary: 'Claim Step 5: CAPTURE_PART_DAMAGES',
|
summary: 'Claim Step 4: CAPTURE_PART_DAMAGES',
|
||||||
value: {
|
value: {
|
||||||
"stepKey": "CAPTURE_PART_DAMAGES",
|
"stepKey": "CAPTURE_PART_DAMAGES",
|
||||||
"type": "CLAIM",
|
"type": "CLAIM",
|
||||||
"stepNumber": 5,
|
"stepNumber": 4,
|
||||||
"isActive": true,
|
"isActive": true,
|
||||||
"stepName_fa": "ثبت تصاویر آسیبهای قطعات",
|
"stepName_fa": "ثبت تصاویر آسیبهای قطعات",
|
||||||
"stepName_en": "Capture Part Damages",
|
"stepName_en": "Capture Part Damages",
|
||||||
"description_fa": "ثبت تصاویر آسیب برای هر قطعه (یک درخواست به ازای هر قطعه)",
|
"description_fa": "ثبت تصاویر آسیب برای هر قطعه (یک درخواست به ازای هر قطعه)",
|
||||||
"description_en": "Capture damage images for each part (one request per part)",
|
"description_en": "Capture damage images for each part (one request per part)",
|
||||||
"category": "CLAIM",
|
"category": "CLAIM",
|
||||||
"requiredPreviousSteps": ["UPLOAD_REQUIRED_DOCUMENTS"],
|
"requiredPreviousSteps": ["SELECT_OTHER_PARTS"],
|
||||||
"nextPossibleSteps": ["USER_SUBMISSION_COMPLETE"],
|
"nextPossibleSteps": ["UPLOAD_REQUIRED_DOCUMENTS"],
|
||||||
"allowedRoles": ["user"],
|
"allowedRoles": ["user"],
|
||||||
"estimatedDuration": 10,
|
"estimatedDuration": 10,
|
||||||
"fields": [],
|
"fields": [],
|
||||||
@@ -1305,7 +1305,7 @@ export const claimWorkflowStepExamples = {
|
|||||||
"description_fa": "اطلاعات کاربر به طور کامل ثبت شد و در انتظار بررسی کارشناس خسارت",
|
"description_fa": "اطلاعات کاربر به طور کامل ثبت شد و در انتظار بررسی کارشناس خسارت",
|
||||||
"description_en": "User information completed, waiting for damage expert review",
|
"description_en": "User information completed, waiting for damage expert review",
|
||||||
"category": "CLAIM",
|
"category": "CLAIM",
|
||||||
"requiredPreviousSteps": ["CAPTURE_PART_DAMAGES"],
|
"requiredPreviousSteps": ["UPLOAD_REQUIRED_DOCUMENTS"],
|
||||||
"nextPossibleSteps": ["EXPERT_DAMAGE_ASSESSMENT"],
|
"nextPossibleSteps": ["EXPERT_DAMAGE_ASSESSMENT"],
|
||||||
"allowedRoles": ["user", "damage_expert", "admin"],
|
"allowedRoles": ["user", "damage_expert", "admin"],
|
||||||
"estimatedDuration": 0,
|
"estimatedDuration": 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user