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)
|
||||
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
|
||||
const updatedClaim = await this.claimCaseDbService.findByIdAndUpdate(
|
||||
@@ -3548,8 +3564,8 @@ export class ClaimRequestManagementService {
|
||||
metadata: {
|
||||
stepKey: ClaimWorkflowStep.SELECT_OUTER_PARTS,
|
||||
selectedParts: selectedParts,
|
||||
partsCount: selectedParts.length,
|
||||
description: `User selected ${selectedParts.length} damaged outer parts`,
|
||||
partsCount: selectedParts?.length,
|
||||
description: `User selected ${selectedParts?.length} damaged outer parts`,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -3561,7 +3577,7 @@ export class ClaimRequestManagementService {
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Outer parts selected for claim ${claimRequestId}: ${selectedParts.length} parts`,
|
||||
`Outer parts selected for claim ${claimRequestId}: ${selectedParts?.length} parts`,
|
||||
);
|
||||
|
||||
// 7. Return response
|
||||
@@ -3607,6 +3623,7 @@ export class ClaimRequestManagementService {
|
||||
body: SelectOtherPartsV2Dto,
|
||||
currentUserId: string,
|
||||
actor?: { sub: string; role?: string },
|
||||
file?: Express.Multer.File,
|
||||
): Promise<SelectOtherPartsV2ResponseDto> {
|
||||
try {
|
||||
// 1. Validate claim exists
|
||||
@@ -3645,40 +3662,108 @@ export class ClaimRequestManagementService {
|
||||
}
|
||||
|
||||
// 5. Prepare data
|
||||
const otherParts = body.otherParts || [];
|
||||
const shebaNumber = body.shebaNumber;
|
||||
const nationalCode = body.nationalCodeOfOwner;
|
||||
// Backward compatibility aliases:
|
||||
// - shebaNumber <- sheba
|
||||
// - 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 updatedClaim = await this.claimCaseDbService.findByIdAndUpdate(
|
||||
claimRequestId,
|
||||
{
|
||||
'damage.otherParts': otherParts.length > 0 ? otherParts : undefined,
|
||||
'money.sheba': shebaNumber,
|
||||
'money.nationalCodeOfInsurer': nationalCode,
|
||||
'status': ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS,
|
||||
'workflow.currentStep': ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
||||
'workflow.nextStep': ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
||||
$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,
|
||||
description: `User selected ${otherParts.length} other damaged parts and provided bank information`,
|
||||
},
|
||||
const shebaDigits = shebaInput.replace(/^IR/i, "");
|
||||
const shebaNumber = `IR${shebaDigits}`;
|
||||
const nationalCode = String(
|
||||
((body as any).nationalCodeOfOwner ??
|
||||
(body as any).nationalCodeOfInsurer ??
|
||||
"") as string,
|
||||
).replace(/\s/g, "");
|
||||
if (!/^[0-9]{24}$/.test(shebaDigits)) {
|
||||
throw new BadRequestException(
|
||||
"shebaNumber is required and must be valid (IR + 24 digits or only 24 digits)",
|
||||
);
|
||||
}
|
||||
if (!/^[0-9]{10}$/.test(nationalCode)) {
|
||||
throw new BadRequestException(
|
||||
"nationalCodeOfOwner is required and must be exactly 10 digits",
|
||||
);
|
||||
}
|
||||
|
||||
const updatePayload: any = {
|
||||
'damage.otherParts': otherParts.length > 0 ? otherParts : undefined,
|
||||
'money.sheba': shebaNumber,
|
||||
'money.nationalCodeOfInsurer': nationalCode,
|
||||
'status': ClaimCaseStatus.UPLOADING_REQUIRED_DOCUMENTS,
|
||||
'workflow.currentStep': ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
||||
'workflow.nextStep': ClaimWorkflowStep.CAPTURE_PART_DAMAGES,
|
||||
$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) {
|
||||
@@ -3690,8 +3775,8 @@ export class ClaimRequestManagementService {
|
||||
);
|
||||
|
||||
// 7. Mask sensitive data for response
|
||||
const maskedSheba = shebaNumber.replace(/^(.{4})(.*)(.{4})$/, 'IR$1************$3');
|
||||
const maskedNationalCode = nationalCode.replace(/^(.{2})(.*)(.{2})$/, '$1******$3');
|
||||
const maskedSheba = `IR${shebaDigits.slice(0, 4)}************${shebaDigits.slice(-4)}`;
|
||||
const maskedNationalCode = `${nationalCode.slice(0, 2)}******${nationalCode.slice(-2)}`;
|
||||
|
||||
// 8. Return response
|
||||
return {
|
||||
|
||||
@@ -254,13 +254,14 @@ export class ClaimRequestManagementV2Controller {
|
||||
**Workflow Step:** SELECT_OTHER_PARTS (Step 3 of Claim)
|
||||
|
||||
**Purpose:** User selects non-body damaged parts and provides bank information for payment.
|
||||
Optional: upload car green card file in the same step.
|
||||
|
||||
**Validations:**
|
||||
- Claim must exist
|
||||
- User must be the claim owner (damaged party from blame case)
|
||||
- Current workflow step must be SELECT_OTHER_PARTS
|
||||
- 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
|
||||
|
||||
**Valid Other Parts (Optional):**
|
||||
@@ -278,42 +279,38 @@ export class ClaimRequestManagementV2Controller {
|
||||
description: "The claim case ID (MongoDB ObjectId)",
|
||||
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({
|
||||
type: SelectOtherPartsV2Dto,
|
||||
description: "Other parts selection and bank information",
|
||||
examples: {
|
||||
example1: {
|
||||
summary: "Only bank info (no other parts damaged)",
|
||||
value: {
|
||||
otherParts: [],
|
||||
shebaNumber: "123456789012345678901234",
|
||||
nationalCodeOfOwner: "1234567890",
|
||||
},
|
||||
},
|
||||
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",
|
||||
description:
|
||||
"Other parts + bank information. Use `sheba` and `nationalCodeOfInsurer` like THIRD_PARTY flow. Optional file can be uploaded as car green card.",
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
otherParts: {
|
||||
oneOf: [
|
||||
{ type: "array", items: { type: "string" } },
|
||||
{ type: "string", description: "JSON string array for multipart" },
|
||||
],
|
||||
example: ["engine", "suspension"],
|
||||
},
|
||||
sheba: { type: "string", example: "IR123456789012345678901234" },
|
||||
nationalCodeOfInsurer: { type: "string", example: "1234567890" },
|
||||
file: { type: "string", format: "binary" },
|
||||
},
|
||||
required: ["sheba", "nationalCodeOfInsurer"],
|
||||
},
|
||||
})
|
||||
@ApiResponse({
|
||||
@@ -341,6 +338,7 @@ export class ClaimRequestManagementV2Controller {
|
||||
@Param("claimRequestId") claimRequestId: string,
|
||||
@Body() body: SelectOtherPartsV2Dto,
|
||||
@CurrentUser() user: any,
|
||||
@UploadedFile() file?: Express.Multer.File,
|
||||
): Promise<SelectOtherPartsV2ResponseDto> {
|
||||
try {
|
||||
return await this.claimRequestManagementService.selectOtherPartsV2(
|
||||
@@ -348,6 +346,7 @@ export class ClaimRequestManagementV2Controller {
|
||||
body,
|
||||
user.sub,
|
||||
user,
|
||||
file,
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) throw error;
|
||||
|
||||
@@ -41,34 +41,43 @@ export class SelectOtherPartsV2Dto {
|
||||
otherParts?: OtherCarPart[];
|
||||
|
||||
@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',
|
||||
pattern: '^[0-9]{24}$',
|
||||
minLength: 24,
|
||||
maxLength: 24,
|
||||
})
|
||||
@IsNotEmpty({ message: 'Sheba number is required' })
|
||||
@IsString({ message: 'Sheba number must be a string' })
|
||||
@Length(24, 24, { message: 'Sheba number must be exactly 24 digits' })
|
||||
@Matches(/^[0-9]{24}$/, {
|
||||
message: 'Sheba number must contain exactly 24 digits (without IR prefix)',
|
||||
})
|
||||
shebaNumber: string;
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
shebaNumber?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'National code of the owner - 10 digits',
|
||||
description: 'National code of insurer/owner - 10 digits',
|
||||
example: '1234567890',
|
||||
pattern: '^[0-9]{10}$',
|
||||
minLength: 10,
|
||||
maxLength: 10,
|
||||
})
|
||||
@IsNotEmpty({ message: 'National code of owner is required' })
|
||||
@IsNotEmpty({ message: 'nationalCodeOfInsurer is required' })
|
||||
@IsString({ message: 'National code must be a string' })
|
||||
@Length(10, 10, { message: 'National code must be exactly 10 digits' })
|
||||
@Matches(/^[0-9]{10}$/, {
|
||||
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