forked from Yara724/api
Merge pull request 'Fixed bugs of car-body steps' (#19) from s.yahyaee/yara724-api:main into main
Reviewed-on: Yara724/api#19
This commit is contained in:
@@ -3525,7 +3525,23 @@ export class ClaimRequestManagementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 5. Convert selected parts to storage format (simple array of strings)
|
// 5. Convert selected parts to storage format (simple array of strings)
|
||||||
const selectedParts = body.selectedParts;
|
// Backward compatibility: some clients may still send legacy `carPartDamage` object.
|
||||||
|
let selectedParts = Array.isArray((body as any)?.selectedParts)
|
||||||
|
? ((body as any).selectedParts as string[])
|
||||||
|
: [];
|
||||||
|
if (selectedParts.length === 0 && (body as any)?.carPartDamage) {
|
||||||
|
this.assertCarPartDamageAtMostTwoOfFourSides(
|
||||||
|
(body as any).carPartDamage as CarDamagePartDto,
|
||||||
|
);
|
||||||
|
selectedParts = this.carDamagePartDtoToOuterPartSlugs(
|
||||||
|
(body as any).carPartDamage as CarDamagePartDto,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (selectedParts.length === 0) {
|
||||||
|
throw new BadRequestException(
|
||||||
|
"selectedParts is required and must contain at least one item",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 6. Update claim case with selected parts and move to next step
|
// 6. Update claim case with selected parts and move to next step
|
||||||
const updatedClaim = await this.claimCaseDbService.findByIdAndUpdate(
|
const updatedClaim = await this.claimCaseDbService.findByIdAndUpdate(
|
||||||
@@ -3548,8 +3564,8 @@ export class ClaimRequestManagementService {
|
|||||||
metadata: {
|
metadata: {
|
||||||
stepKey: ClaimWorkflowStep.SELECT_OUTER_PARTS,
|
stepKey: ClaimWorkflowStep.SELECT_OUTER_PARTS,
|
||||||
selectedParts: selectedParts,
|
selectedParts: selectedParts,
|
||||||
partsCount: selectedParts.length,
|
partsCount: selectedParts?.length,
|
||||||
description: `User selected ${selectedParts.length} damaged outer parts`,
|
description: `User selected ${selectedParts?.length} damaged outer parts`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -3561,7 +3577,7 @@ export class ClaimRequestManagementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`Outer parts selected for claim ${claimRequestId}: ${selectedParts.length} parts`,
|
`Outer parts selected for claim ${claimRequestId}: ${selectedParts?.length} parts`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 7. Return response
|
// 7. Return response
|
||||||
@@ -3607,6 +3623,7 @@ export class ClaimRequestManagementService {
|
|||||||
body: SelectOtherPartsV2Dto,
|
body: SelectOtherPartsV2Dto,
|
||||||
currentUserId: string,
|
currentUserId: string,
|
||||||
actor?: { sub: string; role?: string },
|
actor?: { sub: string; role?: string },
|
||||||
|
file?: Express.Multer.File,
|
||||||
): Promise<SelectOtherPartsV2ResponseDto> {
|
): Promise<SelectOtherPartsV2ResponseDto> {
|
||||||
try {
|
try {
|
||||||
// 1. Validate claim exists
|
// 1. Validate claim exists
|
||||||
@@ -3645,40 +3662,108 @@ export class ClaimRequestManagementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 5. Prepare data
|
// 5. Prepare data
|
||||||
const otherParts = body.otherParts || [];
|
// Backward compatibility aliases:
|
||||||
const shebaNumber = body.shebaNumber;
|
// - shebaNumber <- sheba
|
||||||
const nationalCode = body.nationalCodeOfOwner;
|
// - nationalCodeOfOwner <- nationalCodeOfInsurer
|
||||||
|
let otherParts = Array.isArray((body as any)?.otherParts)
|
||||||
|
? (body as any).otherParts
|
||||||
|
: [];
|
||||||
|
// Multipart clients may send otherParts as JSON string
|
||||||
|
if (!Array.isArray((body as any)?.otherParts) && typeof (body as any)?.otherParts === "string") {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse((body as any).otherParts);
|
||||||
|
otherParts = Array.isArray(parsed) ? parsed : [];
|
||||||
|
} catch {
|
||||||
|
otherParts = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const shebaInput = String(
|
||||||
|
((body as any).shebaNumber ?? (body as any).sheba ?? "") as string,
|
||||||
|
)
|
||||||
|
.replace(/\s/g, "");
|
||||||
|
|
||||||
// 6. Update claim case with other parts, bank info and move to next step
|
const shebaDigits = shebaInput.replace(/^IR/i, "");
|
||||||
const updatedClaim = await this.claimCaseDbService.findByIdAndUpdate(
|
const shebaNumber = `IR${shebaDigits}`;
|
||||||
claimRequestId,
|
const nationalCode = String(
|
||||||
{
|
((body as any).nationalCodeOfOwner ??
|
||||||
'damage.otherParts': otherParts.length > 0 ? otherParts : undefined,
|
(body as any).nationalCodeOfInsurer ??
|
||||||
'money.sheba': shebaNumber,
|
"") as string,
|
||||||
'money.nationalCodeOfInsurer': nationalCode,
|
).replace(/\s/g, "");
|
||||||
'status': ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS,
|
if (!/^[0-9]{24}$/.test(shebaDigits)) {
|
||||||
'workflow.currentStep': ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
throw new BadRequestException(
|
||||||
'workflow.nextStep': ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
"shebaNumber is required and must be valid (IR + 24 digits or only 24 digits)",
|
||||||
$push: {
|
);
|
||||||
'workflow.completedSteps': ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
}
|
||||||
history: {
|
if (!/^[0-9]{10}$/.test(nationalCode)) {
|
||||||
type: 'STEP_COMPLETED',
|
throw new BadRequestException(
|
||||||
actor: {
|
"nationalCodeOfOwner is required and must be exactly 10 digits",
|
||||||
actorId: new Types.ObjectId(currentUserId),
|
);
|
||||||
actorName: claimCase.owner?.fullName || 'User',
|
}
|
||||||
actorType: 'user',
|
|
||||||
},
|
const updatePayload: any = {
|
||||||
timestamp: new Date(),
|
'damage.otherParts': otherParts.length > 0 ? otherParts : undefined,
|
||||||
metadata: {
|
'money.sheba': shebaNumber,
|
||||||
stepKey: ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
'money.nationalCodeOfInsurer': nationalCode,
|
||||||
otherParts: otherParts,
|
'status': ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS,
|
||||||
otherPartsCount: otherParts.length,
|
'workflow.currentStep': ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
||||||
hasBankInfo: true,
|
'workflow.nextStep': ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
||||||
description: `User selected ${otherParts.length} other damaged parts and provided bank information`,
|
$push: {
|
||||||
},
|
'workflow.completedSteps': ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
||||||
|
history: {
|
||||||
|
type: 'STEP_COMPLETED',
|
||||||
|
actor: {
|
||||||
|
actorId: new Types.ObjectId(currentUserId),
|
||||||
|
actorName: claimCase.owner?.fullName || 'User',
|
||||||
|
actorType: 'user',
|
||||||
|
},
|
||||||
|
timestamp: new Date(),
|
||||||
|
metadata: {
|
||||||
|
stepKey: ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
||||||
|
otherParts: otherParts,
|
||||||
|
otherPartsCount: otherParts.length,
|
||||||
|
hasBankInfo: true,
|
||||||
|
hasGreenCardUpload: !!file,
|
||||||
|
description: `User selected ${otherParts.length} other damaged parts and provided bank information`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
const existingGreen =
|
||||||
|
(claimCase.requiredDocuments as any)?.get?.(
|
||||||
|
ClaimRequiredDocumentType.CAR_GREEN_CARD,
|
||||||
|
) ||
|
||||||
|
(claimCase.requiredDocuments as any)?.[
|
||||||
|
ClaimRequiredDocumentType.CAR_GREEN_CARD
|
||||||
|
];
|
||||||
|
if (existingGreen?.uploaded) {
|
||||||
|
throw new ConflictException(
|
||||||
|
"car_green_card has already been uploaded for this claim",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const docRef = await this.claimRequiredDocumentDbService.create({
|
||||||
|
path: file.path,
|
||||||
|
fileName: file.filename,
|
||||||
|
claimId: new Types.ObjectId(claimRequestId),
|
||||||
|
documentType: ClaimRequiredDocumentType.CAR_GREEN_CARD,
|
||||||
|
uploadedAt: new Date(),
|
||||||
|
});
|
||||||
|
updatePayload[
|
||||||
|
`requiredDocuments.${ClaimRequiredDocumentType.CAR_GREEN_CARD}`
|
||||||
|
] = {
|
||||||
|
fileId: docRef._id,
|
||||||
|
filePath: file.path,
|
||||||
|
fileName: file.filename,
|
||||||
|
uploaded: true,
|
||||||
|
uploadedAt: new Date(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Update claim case with other parts, bank info and optional green card file
|
||||||
|
const updatedClaim = await this.claimCaseDbService.findByIdAndUpdate(
|
||||||
|
claimRequestId,
|
||||||
|
updatePayload,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!updatedClaim) {
|
if (!updatedClaim) {
|
||||||
@@ -3690,8 +3775,8 @@ export class ClaimRequestManagementService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 7. Mask sensitive data for response
|
// 7. Mask sensitive data for response
|
||||||
const maskedSheba = shebaNumber.replace(/^(.{4})(.*)(.{4})$/, 'IR$1************$3');
|
const maskedSheba = `IR${shebaDigits.slice(0, 4)}************${shebaDigits.slice(-4)}`;
|
||||||
const maskedNationalCode = nationalCode.replace(/^(.{2})(.*)(.{2})$/, '$1******$3');
|
const maskedNationalCode = `${nationalCode.slice(0, 2)}******${nationalCode.slice(-2)}`;
|
||||||
|
|
||||||
// 8. Return response
|
// 8. Return response
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -254,13 +254,14 @@ export class ClaimRequestManagementV2Controller {
|
|||||||
**Workflow Step:** SELECT_OTHER_PARTS (Step 3 of Claim)
|
**Workflow Step:** SELECT_OTHER_PARTS (Step 3 of Claim)
|
||||||
|
|
||||||
**Purpose:** User selects non-body damaged parts and provides bank information for payment.
|
**Purpose:** User selects non-body damaged parts and provides bank information for payment.
|
||||||
|
Optional: upload car green card file in the same step.
|
||||||
|
|
||||||
**Validations:**
|
**Validations:**
|
||||||
- Claim must exist
|
- Claim must exist
|
||||||
- User must be the claim owner (damaged party from blame case)
|
- User must be the claim owner (damaged party from blame case)
|
||||||
- Current workflow step must be SELECT_OTHER_PARTS
|
- Current workflow step must be SELECT_OTHER_PARTS
|
||||||
- Bank information must not have been submitted previously
|
- Bank information must not have been submitted previously
|
||||||
- Sheba number must be exactly 24 digits
|
- Sheba (sheba) accepted as IR + 24 digits or only 24 digits
|
||||||
- National code must be exactly 10 digits
|
- National code must be exactly 10 digits
|
||||||
|
|
||||||
**Valid Other Parts (Optional):**
|
**Valid Other Parts (Optional):**
|
||||||
@@ -278,42 +279,38 @@ export class ClaimRequestManagementV2Controller {
|
|||||||
description: "The claim case ID (MongoDB ObjectId)",
|
description: "The claim case ID (MongoDB ObjectId)",
|
||||||
example: "507f1f77bcf86cd799439011",
|
example: "507f1f77bcf86cd799439011",
|
||||||
})
|
})
|
||||||
|
@ApiConsumes("multipart/form-data")
|
||||||
|
@UseInterceptors(
|
||||||
|
FileInterceptor("file", {
|
||||||
|
limits: { fileSize: 10 * 1024 * 1024 },
|
||||||
|
storage: diskStorage({
|
||||||
|
destination: "./files/claim-required-document",
|
||||||
|
filename: (req, file, callback) => {
|
||||||
|
const unique = Date.now();
|
||||||
|
const ex = extname(file.originalname);
|
||||||
|
callback(null, `other-parts-${unique}${ex}`);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
)
|
||||||
@ApiBody({
|
@ApiBody({
|
||||||
type: SelectOtherPartsV2Dto,
|
description:
|
||||||
description: "Other parts selection and bank information",
|
"Other parts + bank information. Use `sheba` and `nationalCodeOfInsurer` like THIRD_PARTY flow. Optional file can be uploaded as car green card.",
|
||||||
examples: {
|
schema: {
|
||||||
example1: {
|
type: "object",
|
||||||
summary: "Only bank info (no other parts damaged)",
|
properties: {
|
||||||
value: {
|
otherParts: {
|
||||||
otherParts: [],
|
oneOf: [
|
||||||
shebaNumber: "123456789012345678901234",
|
{ type: "array", items: { type: "string" } },
|
||||||
nationalCodeOfOwner: "1234567890",
|
{ type: "string", description: "JSON string array for multipart" },
|
||||||
},
|
],
|
||||||
},
|
example: ["engine", "suspension"],
|
||||||
example2: {
|
|
||||||
summary: "Engine and suspension damage",
|
|
||||||
value: {
|
|
||||||
otherParts: ["engine", "suspension"],
|
|
||||||
shebaNumber: "123456789012345678901234",
|
|
||||||
nationalCodeOfOwner: "1234567890",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
example3: {
|
|
||||||
summary: "Multiple systems damaged",
|
|
||||||
value: {
|
|
||||||
otherParts: ["engine", "brake_system", "electrical", "headlight"],
|
|
||||||
shebaNumber: "123456789012345678901234",
|
|
||||||
nationalCodeOfOwner: "1234567890",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
example4: {
|
|
||||||
summary: "Lighting and glass damage",
|
|
||||||
value: {
|
|
||||||
otherParts: ["headlight", "taillight", "mirror", "glass"],
|
|
||||||
shebaNumber: "123456789012345678901234",
|
|
||||||
nationalCodeOfOwner: "1234567890",
|
|
||||||
},
|
},
|
||||||
|
sheba: { type: "string", example: "IR123456789012345678901234" },
|
||||||
|
nationalCodeOfInsurer: { type: "string", example: "1234567890" },
|
||||||
|
file: { type: "string", format: "binary" },
|
||||||
},
|
},
|
||||||
|
required: ["sheba", "nationalCodeOfInsurer"],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ApiResponse({
|
@ApiResponse({
|
||||||
@@ -341,6 +338,7 @@ export class ClaimRequestManagementV2Controller {
|
|||||||
@Param("claimRequestId") claimRequestId: string,
|
@Param("claimRequestId") claimRequestId: string,
|
||||||
@Body() body: SelectOtherPartsV2Dto,
|
@Body() body: SelectOtherPartsV2Dto,
|
||||||
@CurrentUser() user: any,
|
@CurrentUser() user: any,
|
||||||
|
@UploadedFile() file?: Express.Multer.File,
|
||||||
): Promise<SelectOtherPartsV2ResponseDto> {
|
): Promise<SelectOtherPartsV2ResponseDto> {
|
||||||
try {
|
try {
|
||||||
return await this.claimRequestManagementService.selectOtherPartsV2(
|
return await this.claimRequestManagementService.selectOtherPartsV2(
|
||||||
@@ -348,6 +346,7 @@ export class ClaimRequestManagementV2Controller {
|
|||||||
body,
|
body,
|
||||||
user.sub,
|
user.sub,
|
||||||
user,
|
user,
|
||||||
|
file,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof HttpException) throw error;
|
if (error instanceof HttpException) throw error;
|
||||||
|
|||||||
@@ -41,34 +41,43 @@ export class SelectOtherPartsV2Dto {
|
|||||||
otherParts?: OtherCarPart[];
|
otherParts?: OtherCarPart[];
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'Sheba number (IBAN) for payment - 24 digits without IR prefix',
|
description: 'Sheba number (IBAN). Accepted: IR + 24 digits OR only 24 digits',
|
||||||
|
example: 'IR123456789012345678901234',
|
||||||
|
})
|
||||||
|
@IsNotEmpty({ message: 'sheba is required' })
|
||||||
|
@IsString({ message: 'sheba must be a string' })
|
||||||
|
sheba: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Legacy alias of sheba for backward compatibility',
|
||||||
example: '123456789012345678901234',
|
example: '123456789012345678901234',
|
||||||
pattern: '^[0-9]{24}$',
|
|
||||||
minLength: 24,
|
|
||||||
maxLength: 24,
|
|
||||||
})
|
})
|
||||||
@IsNotEmpty({ message: 'Sheba number is required' })
|
@IsOptional()
|
||||||
@IsString({ message: 'Sheba number must be a string' })
|
@IsString()
|
||||||
@Length(24, 24, { message: 'Sheba number must be exactly 24 digits' })
|
shebaNumber?: string;
|
||||||
@Matches(/^[0-9]{24}$/, {
|
|
||||||
message: 'Sheba number must contain exactly 24 digits (without IR prefix)',
|
|
||||||
})
|
|
||||||
shebaNumber: string;
|
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
description: 'National code of the owner - 10 digits',
|
description: 'National code of insurer/owner - 10 digits',
|
||||||
example: '1234567890',
|
example: '1234567890',
|
||||||
pattern: '^[0-9]{10}$',
|
pattern: '^[0-9]{10}$',
|
||||||
minLength: 10,
|
minLength: 10,
|
||||||
maxLength: 10,
|
maxLength: 10,
|
||||||
})
|
})
|
||||||
@IsNotEmpty({ message: 'National code of owner is required' })
|
@IsNotEmpty({ message: 'nationalCodeOfInsurer is required' })
|
||||||
@IsString({ message: 'National code must be a string' })
|
@IsString({ message: 'National code must be a string' })
|
||||||
@Length(10, 10, { message: 'National code must be exactly 10 digits' })
|
@Length(10, 10, { message: 'National code must be exactly 10 digits' })
|
||||||
@Matches(/^[0-9]{10}$/, {
|
@Matches(/^[0-9]{10}$/, {
|
||||||
message: 'National code must contain exactly 10 digits',
|
message: 'National code must contain exactly 10 digits',
|
||||||
})
|
})
|
||||||
nationalCodeOfOwner: string;
|
nationalCodeOfInsurer: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
description: 'Legacy alias for backward compatibility',
|
||||||
|
example: '1234567890',
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
nationalCodeOfOwner?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user