forked from Yara724/api
Merge remote-tracking branch 'upstream/main' | Merge upstream into main and reapply local changes
This commit is contained in:
@@ -68,7 +68,9 @@ import { PublicIdService } from "src/utils/public-id/public-id.service";
|
||||
import { ImageRequiredModel } from "./entites/schema/image-required.schema";
|
||||
import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service";
|
||||
import { FactorStatus } from "src/Types&Enums/claim-request-management/factor-status.enum";
|
||||
import { BranchDbService } from "src/client/entities/db-service/branch.db.service";import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { BranchDbService } from "src/client/entities/db-service/branch.db.service";
|
||||
import { CreationMethod } from "src/request-management/entities/schema/request-management.schema";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { UserRatingDto } from "./dto/user-rating.dto";
|
||||
|
||||
@Injectable()
|
||||
@@ -341,6 +343,166 @@ export class ClaimRequestManagementService {
|
||||
return trueItems;
|
||||
}
|
||||
|
||||
/** One section of CarDamagePartDto: either a single object or an array of objects (legacy). */
|
||||
private carPartDamageSectionToRows(section: unknown): Record<string, unknown>[] {
|
||||
if (section == null) return [];
|
||||
if (Array.isArray(section)) {
|
||||
return section.filter(
|
||||
(row): row is Record<string, unknown> =>
|
||||
!!row && typeof row === "object",
|
||||
);
|
||||
}
|
||||
if (typeof section === "object") {
|
||||
return [section as Record<string, unknown>];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
private carPartDamageSectionHasTrue(section: unknown): boolean {
|
||||
for (const row of this.carPartDamageSectionToRows(section)) {
|
||||
for (const val of Object.values(row)) {
|
||||
if (val === true) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* At most two of left, right, front, back may contain any damaged part (true).
|
||||
* `top` and claim `otherParts` are not subject to this rule.
|
||||
*/
|
||||
private assertCarPartDamageAtMostTwoOfFourSides(dto: CarDamagePartDto): void {
|
||||
const d = dto as unknown as Record<string, unknown>;
|
||||
const quadrants = [
|
||||
{ key: "left", section: d.left },
|
||||
{ key: "right", section: d.right },
|
||||
{ key: "front", section: d.front },
|
||||
{ key: "back", section: d.back },
|
||||
];
|
||||
const used = quadrants.filter((q) =>
|
||||
this.carPartDamageSectionHasTrue(q.section),
|
||||
);
|
||||
if (used.length > 2) {
|
||||
throw new BadRequestException(
|
||||
`At most two of the four sides (left, right, front, back) may include damaged parts. ` +
|
||||
`Sides with selections: ${used.map((u) => u.key).join(", ")}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps legacy expert CarDamagePartDto (nested booleans) to v2 outer part slugs (ClaimCase.damage.selectedParts).
|
||||
*/
|
||||
private carDamagePartDtoToOuterPartSlugs(dto: CarDamagePartDto): string[] {
|
||||
const out = new Set<string>();
|
||||
const add = (slug: string | undefined) => {
|
||||
if (slug) out.add(slug);
|
||||
};
|
||||
const walkMainOnSide = (
|
||||
side: "left" | "right",
|
||||
items: unknown,
|
||||
map: Record<string, string>,
|
||||
) => {
|
||||
for (const row of this.carPartDamageSectionToRows(items)) {
|
||||
for (const [key, val] of Object.entries(row)) {
|
||||
if (val !== true) continue;
|
||||
const slug = map[`${side}_${key}`] ?? map[key];
|
||||
add(slug);
|
||||
}
|
||||
}
|
||||
};
|
||||
// MainParts on left / right (side-specific door / fender)
|
||||
walkMainOnSide("left", (dto as any).left, {
|
||||
left_frontDoor: "front_left_door",
|
||||
left_backDoor: "rear_left_door",
|
||||
left_frontFender: "front_left_fender",
|
||||
left_backFender: "rear_left_fender",
|
||||
frontDoor: "front_left_door",
|
||||
backDoor: "rear_left_door",
|
||||
frontFender: "front_left_fender",
|
||||
backFender: "rear_left_fender",
|
||||
});
|
||||
walkMainOnSide("right", (dto as any).right, {
|
||||
right_frontDoor: "front_right_door",
|
||||
right_backDoor: "rear_right_door",
|
||||
right_frontFender: "front_right_fender",
|
||||
right_backFender: "rear_right_fender",
|
||||
frontDoor: "front_right_door",
|
||||
backDoor: "rear_right_door",
|
||||
frontFender: "front_right_fender",
|
||||
backFender: "rear_right_fender",
|
||||
});
|
||||
const walkSimple = (items: unknown, keyToSlug: Record<string, string>) => {
|
||||
for (const row of this.carPartDamageSectionToRows(items)) {
|
||||
for (const [key, val] of Object.entries(row)) {
|
||||
if (val === true) add(keyToSlug[key]);
|
||||
}
|
||||
}
|
||||
};
|
||||
walkSimple((dto as any).front, {
|
||||
frontBumper: "front_bumper",
|
||||
carHood: "hood",
|
||||
});
|
||||
walkSimple((dto as any).back, {
|
||||
backBumper: "rear_bumper",
|
||||
carTrunk: "trunk",
|
||||
});
|
||||
walkSimple((dto as any).top, {
|
||||
roof: "roof",
|
||||
});
|
||||
return Array.from(out);
|
||||
}
|
||||
|
||||
/** Normalize expert DTO otherParts (strings or legacy { label: true }[]) to string[]. */
|
||||
private normalizeExpertOtherPartsInput(raw: any[] | undefined): string[] {
|
||||
if (!raw || !Array.isArray(raw)) return [];
|
||||
const out: string[] = [];
|
||||
for (const item of raw) {
|
||||
if (typeof item === "string" && item.trim()) {
|
||||
out.push(item.trim());
|
||||
continue;
|
||||
}
|
||||
if (item && typeof item === "object") {
|
||||
for (const [k, v] of Object.entries(item)) {
|
||||
if (v === true && k) out.push(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private async assertFieldExpertInPersonBlameForClaimCase(
|
||||
claimCase: any,
|
||||
expert: any,
|
||||
): Promise<void> {
|
||||
const blameRequestId = claimCase?.blameRequestId?.toString?.();
|
||||
if (!blameRequestId) {
|
||||
throw new BadRequestException("Claim case has no linked blame request.");
|
||||
}
|
||||
const blameRequest = await this.blameRequestDbService.findById(blameRequestId);
|
||||
if (!blameRequest) {
|
||||
throw new NotFoundException("Blame request not found for this claim.");
|
||||
}
|
||||
if (
|
||||
!blameRequest.expertInitiated ||
|
||||
blameRequest.creationMethod !== CreationMethod.IN_PERSON
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
"This endpoint is only for expert-initiated IN_PERSON claim files.",
|
||||
);
|
||||
}
|
||||
if (!blameRequest.initiatedByFieldExpertId) {
|
||||
throw new BadRequestException(
|
||||
"Blame request is missing initiating field expert.",
|
||||
);
|
||||
}
|
||||
if (String(blameRequest.initiatedByFieldExpertId) !== String(expert.sub)) {
|
||||
throw new ForbiddenException(
|
||||
"Only the initiating field expert can complete claim data for this file.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async setCarPartDamageAndImage(cl, trueItems) {
|
||||
cl.carPartDamage = trueItems;
|
||||
const jsonTrueItems = JSON.parse(JSON.stringify(trueItems));
|
||||
@@ -394,7 +556,9 @@ export class ClaimRequestManagementService {
|
||||
|
||||
/**
|
||||
* Field expert completes claim-needed data for expert-initiated IN_PERSON files.
|
||||
* Only the initiating expert can call this. Accepts car part damage and/or sheba, nationalCodeOfInsurer, otherParts.
|
||||
* Only the initiating field expert can call this. Accepts car part damage and/or sheba, nationalCodeOfInsurer, otherParts.
|
||||
*
|
||||
* V2: Resolves `ClaimCase` in `claimCases` collection (workflow). Legacy v1 claim documents are still supported.
|
||||
*/
|
||||
async expertCompleteClaimData(
|
||||
claimRequestId: string,
|
||||
@@ -406,6 +570,16 @@ export class ClaimRequestManagementService {
|
||||
otherParts?: any[];
|
||||
},
|
||||
): Promise<{ success: boolean; claimRequestId: string }> {
|
||||
const claimCase = await this.claimCaseDbService.findById(claimRequestId);
|
||||
if (claimCase) {
|
||||
return this.expertCompleteClaimDataForClaimCaseV2(
|
||||
claimRequestId,
|
||||
expert,
|
||||
dto,
|
||||
claimCase,
|
||||
);
|
||||
}
|
||||
|
||||
const claim = await this.claimDbService.findOne(claimRequestId);
|
||||
if (!claim) {
|
||||
throw new NotFoundException("Claim file not found");
|
||||
@@ -427,6 +601,7 @@ export class ClaimRequestManagementService {
|
||||
if (claim.carPartDamage && (claim.carPartDamage as any[]).length > 0) {
|
||||
throw new BadRequestException("Car part damage is already set.");
|
||||
}
|
||||
this.assertCarPartDamageAtMostTwoOfFourSides(dto.carPartDamage);
|
||||
const trueItems = this.findTrueItems(dto.carPartDamage);
|
||||
const claimDoc = (await this.claimDbService.findOne(
|
||||
claimRequestId,
|
||||
@@ -455,6 +630,178 @@ export class ClaimRequestManagementService {
|
||||
return { success: true, claimRequestId };
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 ClaimCase: outer parts + optional bank/other parts (expert-initiated IN_PERSON only).
|
||||
*/
|
||||
private async expertCompleteClaimDataForClaimCaseV2(
|
||||
claimRequestId: string,
|
||||
expert: any,
|
||||
dto: {
|
||||
carPartDamage?: CarDamagePartDto;
|
||||
sheba?: string;
|
||||
nationalCodeOfInsurer?: string;
|
||||
otherParts?: any[];
|
||||
},
|
||||
claimCase: any,
|
||||
): Promise<{ success: boolean; claimRequestId: string }> {
|
||||
await this.assertFieldExpertInPersonBlameForClaimCase(claimCase, expert);
|
||||
|
||||
let working = claimCase;
|
||||
|
||||
if (dto.carPartDamage) {
|
||||
if (working.workflow?.currentStep !== ClaimWorkflowStep.SELECT_OUTER_PARTS) {
|
||||
throw new BadRequestException(
|
||||
`Cannot set outer parts: workflow step must be ${ClaimWorkflowStep.SELECT_OUTER_PARTS}. Current: ${working.workflow?.currentStep}`,
|
||||
);
|
||||
}
|
||||
if (
|
||||
working.damage?.selectedParts &&
|
||||
working.damage.selectedParts.length > 0
|
||||
) {
|
||||
throw new BadRequestException("Car part damage is already set.");
|
||||
}
|
||||
this.assertCarPartDamageAtMostTwoOfFourSides(dto.carPartDamage);
|
||||
const selectedParts = this.carDamagePartDtoToOuterPartSlugs(dto.carPartDamage);
|
||||
if (!selectedParts.length) {
|
||||
throw new BadRequestException(
|
||||
"No outer damaged parts could be derived from carPartDamage. Check part selections.",
|
||||
);
|
||||
}
|
||||
|
||||
const updated = await this.claimCaseDbService.findByIdAndUpdate(
|
||||
claimRequestId,
|
||||
{
|
||||
"damage.selectedParts": selectedParts,
|
||||
status: ClaimCaseStatus.SELECTING_OTHER_PARTS,
|
||||
"workflow.currentStep": ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
||||
"workflow.nextStep": ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS,
|
||||
$push: {
|
||||
"workflow.completedSteps": ClaimWorkflowStep.SELECT_OUTER_PARTS,
|
||||
history: {
|
||||
type: "STEP_COMPLETED",
|
||||
actor: {
|
||||
actorId: new Types.ObjectId(expert.sub),
|
||||
actorName:
|
||||
`${expert.firstName || ""} ${expert.lastName || ""}`.trim() ||
|
||||
"field_expert",
|
||||
actorType: "field_expert",
|
||||
},
|
||||
timestamp: new Date(),
|
||||
metadata: {
|
||||
stepKey: ClaimWorkflowStep.SELECT_OUTER_PARTS,
|
||||
selectedParts,
|
||||
description: "Field expert submitted outer damage parts",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
if (!updated) {
|
||||
throw new InternalServerErrorException("Failed to update claim case");
|
||||
}
|
||||
working = updated;
|
||||
}
|
||||
|
||||
const hasBankOrOther =
|
||||
dto.sheba != null ||
|
||||
dto.nationalCodeOfInsurer != null ||
|
||||
dto.otherParts != null;
|
||||
if (hasBankOrOther) {
|
||||
const fresh = await this.claimCaseDbService.findById(claimRequestId);
|
||||
if (!fresh) throw new NotFoundException("Claim case not found");
|
||||
if (fresh.workflow?.currentStep !== ClaimWorkflowStep.SELECT_OTHER_PARTS) {
|
||||
throw new BadRequestException(
|
||||
`Bank/other parts can only be set at ${ClaimWorkflowStep.SELECT_OTHER_PARTS}. Current: ${fresh.workflow?.currentStep}`,
|
||||
);
|
||||
}
|
||||
|
||||
const mergedSheba =
|
||||
dto.sheba != null ? dto.sheba : fresh.money?.sheba;
|
||||
const mergedNational =
|
||||
dto.nationalCodeOfInsurer != null
|
||||
? dto.nationalCodeOfInsurer
|
||||
: fresh.money?.nationalCodeOfInsurer;
|
||||
|
||||
if (fresh.money?.sheba && dto.sheba != null && dto.sheba !== fresh.money.sheba) {
|
||||
throw new ConflictException("Sheba is already set for this claim.");
|
||||
}
|
||||
if (
|
||||
fresh.money?.nationalCodeOfInsurer &&
|
||||
dto.nationalCodeOfInsurer != null &&
|
||||
dto.nationalCodeOfInsurer !== fresh.money.nationalCodeOfInsurer
|
||||
) {
|
||||
throw new ConflictException(
|
||||
"National code of insurer is already set for this claim.",
|
||||
);
|
||||
}
|
||||
|
||||
const otherPartsArr = this.normalizeExpertOtherPartsInput(dto.otherParts);
|
||||
const setPatch: Record<string, unknown> = {};
|
||||
if (dto.sheba != null) setPatch["money.sheba"] = dto.sheba;
|
||||
if (dto.nationalCodeOfInsurer != null) {
|
||||
setPatch["money.nationalCodeOfInsurer"] = dto.nationalCodeOfInsurer;
|
||||
}
|
||||
if (dto.otherParts != null) {
|
||||
setPatch["damage.otherParts"] =
|
||||
otherPartsArr.length > 0 ? otherPartsArr : [];
|
||||
}
|
||||
|
||||
const bankComplete =
|
||||
mergedSheba &&
|
||||
mergedNational &&
|
||||
/^[0-9]{24}$/.test(String(mergedSheba).replace(/\s/g, "")) &&
|
||||
/^[0-9]{10}$/.test(String(mergedNational).replace(/\s/g, ""));
|
||||
|
||||
if (bankComplete) {
|
||||
const shebaNorm = String(mergedSheba).replace(/\s/g, "");
|
||||
const nationalNorm = String(mergedNational).replace(/\s/g, "");
|
||||
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
||||
"money.sheba": shebaNorm,
|
||||
"money.nationalCodeOfInsurer": nationalNorm,
|
||||
...(dto.otherParts != null
|
||||
? {
|
||||
"damage.otherParts":
|
||||
otherPartsArr.length > 0 ? otherPartsArr : [],
|
||||
}
|
||||
: {}),
|
||||
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(expert.sub),
|
||||
actorName:
|
||||
`${expert.firstName || ""} ${expert.lastName || ""}`.trim() ||
|
||||
"field_expert",
|
||||
actorType: "field_expert",
|
||||
},
|
||||
timestamp: new Date(),
|
||||
metadata: {
|
||||
stepKey: ClaimWorkflowStep.SELECT_OTHER_PARTS,
|
||||
description:
|
||||
"Field expert submitted bank info and optional other parts",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
if (Object.keys(setPatch).length === 0) {
|
||||
throw new BadRequestException(
|
||||
"Provide sheba (24 digits) and nationalCodeOfInsurer (10 digits) together to finish this step, or supply partial fields incrementally.",
|
||||
);
|
||||
}
|
||||
await this.claimCaseDbService.findByIdAndUpdate(claimRequestId, {
|
||||
$set: setPatch,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true, claimRequestId };
|
||||
}
|
||||
|
||||
async selectCarOtherPartDamage(
|
||||
requestId: string,
|
||||
body: any, // Use your DTO for better type safety
|
||||
@@ -3349,7 +3696,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(
|
||||
@@ -3372,8 +3735,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`,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -3385,7 +3748,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
|
||||
@@ -3431,6 +3794,7 @@ export class ClaimRequestManagementService {
|
||||
body: SelectOtherPartsV2Dto,
|
||||
currentUserId: string,
|
||||
actor?: { sub: string; role?: string },
|
||||
file?: Express.Multer.File,
|
||||
): Promise<SelectOtherPartsV2ResponseDto> {
|
||||
try {
|
||||
// 1. Validate claim exists
|
||||
@@ -3469,40 +3833,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) {
|
||||
@@ -3514,8 +3946,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 {
|
||||
|
||||
Reference in New Issue
Block a user