Fixed bugs of car-body steps

This commit is contained in:
SepehrYahyaee
2026-04-18 10:10:19 +03:30
parent 9a65071276
commit 1a1d55bc2e
3 changed files with 177 additions and 84 deletions

View File

@@ -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,14 +3662,45 @@ 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 ??
(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, 'damage.otherParts': otherParts.length > 0 ? otherParts : undefined,
'money.sheba': shebaNumber, 'money.sheba': shebaNumber,
'money.nationalCodeOfInsurer': nationalCode, 'money.nationalCodeOfInsurer': nationalCode,
@@ -3674,11 +3722,48 @@ export class ClaimRequestManagementService {
otherParts: otherParts, otherParts: otherParts,
otherPartsCount: otherParts.length, otherPartsCount: otherParts.length,
hasBankInfo: true, hasBankInfo: true,
hasGreenCardUpload: !!file,
description: `User selected ${otherParts.length} other damaged parts and provided bank information`, 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 {

View File

@@ -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;

View File

@@ -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;
} }
/** /**