From d3249e985499bc5cc9957f78327b53d1cbc256ae Mon Sep 17 00:00:00 2001 From: SepehrYahyaee <7heycallmegray@gmail.com> Date: Sun, 28 Jun 2026 14:21:08 +0330 Subject: [PATCH] Fix CAR_GREEN_CARD place to upload for v3 --- .../claim-request-management.service.ts | 225 ++++++++++-------- src/core/config/config.schema.ts | 2 - ...rt-initiated-blame-v3.mirror.controller.ts | 60 ++--- 3 files changed, 159 insertions(+), 128 deletions(-) diff --git a/src/claim-request-management/claim-request-management.service.ts b/src/claim-request-management/claim-request-management.service.ts index be7465b..a753530 100644 --- a/src/claim-request-management/claim-request-management.service.ts +++ b/src/claim-request-management/claim-request-management.service.ts @@ -3411,8 +3411,7 @@ export class ClaimRequestManagementService { CulpritTypeId: number; }; }): Promise> { - const lookupDefaults = - input.defaults ?? this.TEJARATNO_FANAVARAN_DEFAULTS; + const lookupDefaults = input.defaults ?? this.TEJARATNO_FANAVARAN_DEFAULTS; const result: Record = { ...lookupDefaults, AccidentCauseId: FANAVARAN_DEFAULT_ACCIDENT_CAUSE_ID, @@ -3844,7 +3843,11 @@ export class ClaimRequestManagementService { try { const appToken = await this.getAppToken(config, auditSession); - const authenticationToken = await this.login(appToken, config, auditSession); + const authenticationToken = await this.login( + appToken, + config, + auditSession, + ); this.logger.log( `${logPrefix} Calling policy inquiry API for nationalCode: ${nationalCodeOfInsurer}`, @@ -3866,7 +3869,8 @@ export class ClaimRequestManagementService { const policyCount = Array.isArray(response.data) ? response.data.length : 0; - const lastPolicy = policyCount > 0 ? response.data[policyCount - 1] : null; + const lastPolicy = + policyCount > 0 ? response.data[policyCount - 1] : null; this.logger.log( `${logPrefix} Policy inquiry response status=${response.status} count=${policyCount}`, @@ -3920,7 +3924,9 @@ export class ClaimRequestManagementService { httpStatus: response.status, responseMeta: { policyId: null, - policyCount: Array.isArray(response.data) ? response.data.length : 0, + policyCount: Array.isArray(response.data) + ? response.data.length + : 0, }, durationMs: Date.now() - startedAt, }); @@ -4234,7 +4240,10 @@ export class ClaimRequestManagementService { return payload; } catch (error) { - this.logger.error(`Error in previewFanavaranSubmitV2 (${clientKey})`, error); + this.logger.error( + `Error in previewFanavaranSubmitV2 (${clientKey})`, + error, + ); throw error; } } @@ -4986,9 +4995,7 @@ export class ClaimRequestManagementService { const parties: any[] = claim?.snapshot?.parties ?? []; if (!parties.length) return false; - const ownerIdStr = claim?.owner?.userId - ? String(claim.owner.userId) - : null; + const ownerIdStr = claim?.owner?.userId ? String(claim.owner.userId) : null; for (const party of parties) { const partyUserId = party?.person?.userId; @@ -5012,10 +5019,10 @@ export class ClaimRequestManagementService { actor?: { username?: string }, message = "Only the damaged party can perform this action on the claim", ): Promise { - const linkedUserIds = await resolveLinkedUserIdStrings( - this.userDbService, - { sub: effectiveUserId, username: actor?.username }, - ); + const linkedUserIds = await resolveLinkedUserIdStrings(this.userDbService, { + sub: effectiveUserId, + username: actor?.username, + }); const actorCtx = { sub: effectiveUserId, username: actor?.username }; // 0. damagedPartyUserId stored directly on the claim (fastest, most reliable) @@ -5044,7 +5051,9 @@ export class ClaimRequestManagementService { // 3. Snapshot-party fallback: claim.snapshot.parties is written at creation // and is reliable even when blame lookup fails or userId mapping differs. - if (this.claimSnapshotPartyMatchesAsDamagedParty(claimCase, linkedUserIds)) { + if ( + this.claimSnapshotPartyMatchesAsDamagedParty(claimCase, linkedUserIds) + ) { return; } @@ -5056,10 +5065,10 @@ export class ClaimRequestManagementService { currentUserId: string, actor?: { sub: string; role?: string; username?: string }, ): Promise { - const linkedUserIds = await resolveLinkedUserIdStrings( - this.userDbService, - { sub: currentUserId, username: actor?.username }, - ); + const linkedUserIds = await resolveLinkedUserIdStrings(this.userDbService, { + sub: currentUserId, + username: actor?.username, + }); if (ownerUserIdMatchesActor(claim.owner?.userId, linkedUserIds)) { return; @@ -5898,6 +5907,12 @@ export class ClaimRequestManagementService { label_en: "Damaged Metal Plate", category: "damaged_party", }, + { + key: "car_green_card", + label_fa: "کارت سبز خودرو", + label_en: "Car Green Card", + category: "damaged_party", + }, ...(isCarBody ? [] : [ @@ -6436,8 +6451,7 @@ export class ClaimRequestManagementService { $set["status"] = ClaimCaseStatus.SELECTING_OUTER_PARTS; $set["workflow.currentStep"] = ClaimWorkflowStep.SELECT_OUTER_PARTS; - $set["workflow.nextStep"] = - ClaimWorkflowStep.SELECT_OTHER_PARTS; + $set["workflow.nextStep"] = ClaimWorkflowStep.SELECT_OTHER_PARTS; completedStepsEntries.push( ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS, @@ -7588,8 +7602,7 @@ export class ClaimRequestManagementService { let message = "Your signature has been recorded. The claim is completed."; if (fanavaran.submitted) { - message += - " The claim was sent to Fanavaran successfully."; + message += " The claim was sent to Fanavaran successfully."; } else if (fanavaran.warning) { message += ` ${fanavaran.warning}`; } @@ -7698,31 +7711,35 @@ export class ClaimRequestManagementService { ? { "snapshot.parties.person.userId": { $in: idVariants } } : null; - const [ownerClaims, damagedDirectClaims, snapshotClaims, candidateBlames] = - await Promise.all([ - ownerOr - ? this.claimCaseDbService.find(ownerOr as any, { lean: true }) - : Promise.resolve([]), - damagedOr - ? this.claimCaseDbService.find(damagedOr as any, { lean: true }) - : Promise.resolve([]), - snapshotPartyFilter - ? this.claimCaseDbService.find(snapshotPartyFilter as any, { + const [ + ownerClaims, + damagedDirectClaims, + snapshotClaims, + candidateBlames, + ] = await Promise.all([ + ownerOr + ? this.claimCaseDbService.find(ownerOr as any, { lean: true }) + : Promise.resolve([]), + damagedOr + ? this.claimCaseDbService.find(damagedOr as any, { lean: true }) + : Promise.resolve([]), + snapshotPartyFilter + ? this.claimCaseDbService.find(snapshotPartyFilter as any, { + lean: true, + }) + : Promise.resolve([]), + partyBlameOr.length + ? this.blameRequestDbService.find( + partyBlameOr.length === 1 + ? (partyBlameOr[0] as any) + : ({ $or: partyBlameOr } as any), + { lean: true, - }) - : Promise.resolve([]), - partyBlameOr.length - ? this.blameRequestDbService.find( - partyBlameOr.length === 1 - ? (partyBlameOr[0] as any) - : ({ $or: partyBlameOr } as any), - { - lean: true, - select: "type parties expert.decision blameStatus", - }, - ) - : Promise.resolve([]), - ]); + select: "type parties expert.decision blameStatus", + }, + ) + : Promise.resolve([]), + ]); // Any blame where the user is a party (not just damaged) surfaces // the linked claim. Mutation guards protect against acting as the @@ -8033,7 +8050,10 @@ export class ClaimRequestManagementService { ); } - private hasPartyInquiriesOnBlame(blame: any, role: "FIRST" | "SECOND"): boolean { + private hasPartyInquiriesOnBlame( + blame: any, + role: "FIRST" | "SECOND", + ): boolean { const step = role === "FIRST" ? WorkflowStep.FIRST_INITIAL_FORM @@ -8129,7 +8149,9 @@ export class ClaimRequestManagementService { claimCase: any, blame: any, ): Promise { - if (claimCase.workflow?.currentStep === ClaimWorkflowStep.SELECT_OUTER_PARTS) { + if ( + claimCase.workflow?.currentStep === ClaimWorkflowStep.SELECT_OUTER_PARTS + ) { return; } @@ -8165,13 +8187,19 @@ export class ClaimRequestManagementService { private async assertV3InPersonClaim(claimCase: any) { const blame = await this.loadBlameForV3Claim(claimCase); if (!blame) { - throw new BadRequestException("V3 claim flow requires a linked blame file."); + throw new BadRequestException( + "V3 claim flow requires a linked blame file.", + ); } if (blame.creationMethod !== CreationMethod.IN_PERSON) { - throw new BadRequestException("V3 claim flow is only for IN_PERSON blame files."); + throw new BadRequestException( + "V3 claim flow is only for IN_PERSON blame files.", + ); } if (!blame.expertInitiated) { - throw new BadRequestException("V3 claim flow requires an expert-initiated blame file."); + throw new BadRequestException( + "V3 claim flow requires an expert-initiated blame file.", + ); } return blame; } @@ -8200,9 +8228,7 @@ export class ClaimRequestManagementService { if (this.isV3InitialDocumentsPhase(claimCase)) { const preCaptureDocs = base.requiredDocuments.filter( - (d) => - !d.preferUploadDuringCapture && - d.key !== ClaimRequiredDocumentType.CAR_GREEN_CARD, + (d) => !d.preferUploadDuringCapture, ); const remaining = preCaptureDocs.filter((d) => !d.uploaded).length; return { @@ -8288,15 +8314,11 @@ export class ClaimRequestManagementService { ): Promise { const claimCase = await this.claimCaseDbService.findById(claimRequestId); if (!claimCase) { - throw new NotFoundException(`Claim case with ID ${claimRequestId} not found`); - } - const blame = await this.assertV3InPersonClaim(claimCase); - - if (body.documentKey === ClaimRequiredDocumentType.CAR_GREEN_CARD) { - throw new BadRequestException( - "Car green card must be uploaded during other-parts selection, not here.", + throw new NotFoundException( + `Claim case with ID ${claimRequestId} not found`, ); } + const blame = await this.assertV3InPersonClaim(claimCase); const isCaptureDoc = isCapturePhaseDamagedPartyDocKey(body.documentKey); @@ -8308,7 +8330,10 @@ export class ClaimRequestManagementService { "Upload chassis, engine, and metal plate photos during capture after damaged-part photos and car angles.", ); if ( - !this.claimV3StepCompleted(claimCase, ClaimWorkflowStep.SELECT_OTHER_PARTS) + !this.claimV3StepCompleted( + claimCase, + ClaimWorkflowStep.SELECT_OTHER_PARTS, + ) ) { throw new BadRequestException( "Complete outer and other part selection before capture-phase vehicle documents.", @@ -8351,7 +8376,9 @@ export class ClaimRequestManagementService { ): Promise { const claimCase = await this.claimCaseDbService.findById(claimRequestId); if (!claimCase) { - throw new NotFoundException(`Claim case with ID ${claimRequestId} not found`); + throw new NotFoundException( + `Claim case with ID ${claimRequestId} not found`, + ); } const blame = await this.assertV3InPersonClaim(claimCase); @@ -8379,7 +8406,9 @@ export class ClaimRequestManagementService { ): Promise { const claimCase = await this.claimCaseDbService.findById(claimRequestId); if (!claimCase) { - throw new NotFoundException(`Claim case with ID ${claimRequestId} not found`); + throw new NotFoundException( + `Claim case with ID ${claimRequestId} not found`, + ); } const blame = await this.assertV3InPersonClaim(claimCase); this.assertV3ClaimPartsPhase(blame); @@ -8400,11 +8429,12 @@ export class ClaimRequestManagementService { body: SelectOtherPartsV3Dto, currentUserId: string, actor?: { sub: string; role?: string }, - file?: Express.Multer.File, ): Promise { const claimCase = await this.claimCaseDbService.findById(claimRequestId); if (!claimCase) { - throw new NotFoundException(`Claim case with ID ${claimRequestId} not found`); + throw new NotFoundException( + `Claim case with ID ${claimRequestId} not found`, + ); } const blame = await this.assertV3InPersonClaim(claimCase); this.assertV3ClaimPartsPhase(blame); @@ -8413,7 +8443,12 @@ export class ClaimRequestManagementService { ClaimWorkflowStep.SELECT_OTHER_PARTS, "Select outer parts before other parts.", ); - if (!this.claimV3StepCompleted(claimCase, ClaimWorkflowStep.SELECT_OUTER_PARTS)) { + if ( + !this.claimV3StepCompleted( + claimCase, + ClaimWorkflowStep.SELECT_OUTER_PARTS, + ) + ) { throw new BadRequestException( "Complete outer part selection before selecting other parts.", ); @@ -8454,7 +8489,8 @@ export class ClaimRequestManagementService { actor: { actorId: new Types.ObjectId(currentUserId), actorName: claimCase.owner?.fullName || "Actor", - actorType: actor?.role === RoleEnum.REGISTRAR ? "registrar" : "field_expert", + actorType: + actor?.role === RoleEnum.REGISTRAR ? "registrar" : "field_expert", }, timestamp: new Date(), metadata: { @@ -8466,23 +8502,6 @@ export class ClaimRequestManagementService { }, }; - if (file) { - 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(), - }; - } - const updatedClaim = await this.claimCaseDbService.findByIdAndUpdate( claimRequestId, updatePayload, @@ -8495,8 +8514,14 @@ export class ClaimRequestManagementService { claimRequestId: updatedClaim._id.toString(), publicId: updatedClaim.publicId, otherParts, - shebaNumber: String(shebaNumber).replace(/^(.{4})(.*)(.{4})$/, "IR$1************$3"), - nationalCodeOfOwner: String(nationalCode).replace(/^(.{2})(.*)(.{2})$/, "$1******$3"), + shebaNumber: String(shebaNumber).replace( + /^(.{4})(.*)(.{4})$/, + "IR$1************$3", + ), + nationalCodeOfOwner: String(nationalCode).replace( + /^(.{2})(.*)(.{2})$/, + "$1******$3", + ), currentStep: ClaimWorkflowStep.CAPTURE_PART_DAMAGES, nextStep: ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS, message: @@ -8512,7 +8537,9 @@ export class ClaimRequestManagementService { ): Promise { const claimCase = await this.claimCaseDbService.findById(claimRequestId); if (!claimCase) { - throw new NotFoundException(`Claim case with ID ${claimRequestId} not found`); + throw new NotFoundException( + `Claim case with ID ${claimRequestId} not found`, + ); } await this.assertV3InPersonClaim(claimCase); @@ -8526,7 +8553,8 @@ export class ClaimRequestManagementService { } if ( - claimCase.workflow?.currentStep !== ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS + claimCase.workflow?.currentStep !== + ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS ) { throw new BadRequestException( `Complete all part/angle captures first. Expected ${ClaimWorkflowStep.UPLOAD_REQUIRED_DOCUMENTS}, but current step is ${claimCase.workflow?.currentStep}`, @@ -8567,7 +8595,9 @@ export class ClaimRequestManagementService { ): Promise { const claimCase = await this.claimCaseDbService.findById(claimRequestId); if (!claimCase) { - throw new NotFoundException(`Claim case with ID ${claimRequestId} not found`); + throw new NotFoundException( + `Claim case with ID ${claimRequestId} not found`, + ); } const blame = await this.assertV3InPersonClaim(claimCase); this.assertV3ClaimPartsPhase(blame); @@ -8576,18 +8606,17 @@ export class ClaimRequestManagementService { ClaimWorkflowStep.CAPTURE_PART_DAMAGES, "Select other parts before capturing damaged parts and angles.", ); - if (!this.claimV3StepCompleted(claimCase, ClaimWorkflowStep.SELECT_OTHER_PARTS)) { + if ( + !this.claimV3StepCompleted( + claimCase, + ClaimWorkflowStep.SELECT_OTHER_PARTS, + ) + ) { throw new BadRequestException( "Complete other part selection before capture.", ); } - return this.capturePartV2( - claimRequestId, - body, - file, - currentUserId, - actor, - ); + return this.capturePartV2(claimRequestId, body, file, currentUserId, actor); } } diff --git a/src/core/config/config.schema.ts b/src/core/config/config.schema.ts index ee6c1bc..0d48824 100644 --- a/src/core/config/config.schema.ts +++ b/src/core/config/config.schema.ts @@ -2,12 +2,10 @@ import { IsBooleanString, IsEnum, IsNumber, - IsNumberString, IsOptional, IsPositive, IsString, IsUrl, - Length, Matches, Max, Min, diff --git a/src/request-management/expert-initiated-blame-v3.mirror.controller.ts b/src/request-management/expert-initiated-blame-v3.mirror.controller.ts index 0b9391c..c4d7728 100644 --- a/src/request-management/expert-initiated-blame-v3.mirror.controller.ts +++ b/src/request-management/expert-initiated-blame-v3.mirror.controller.ts @@ -45,9 +45,7 @@ import { SelectOuterPartsV2Dto, SelectOuterPartsV2ResponseDto, } from "src/claim-request-management/dto/select-outer-parts-v2.dto"; -import { - SelectOtherPartsV2ResponseDto, -} from "src/claim-request-management/dto/select-other-parts-v2.dto"; +import { SelectOtherPartsV2ResponseDto } from "src/claim-request-management/dto/select-other-parts-v2.dto"; import { SelectOtherPartsV3Dto } from "src/claim-request-management/dto/select-other-parts-v3.dto"; import { UploadRequiredDocumentV2Dto, @@ -117,7 +115,8 @@ export class ExpertInitiatedBlameV3MirrorController { @ApiBody({ type: SendPartyOtpDto }) @ApiOperation({ summary: "Send OTP to one party", - description: "Guilty party first; damaged party after guilty party has signed (THIRD_PARTY).", + description: + "Guilty party first; damaged party after guilty party has signed (THIRD_PARTY).", }) async sendPartyOtp( @CurrentUser() expert: any, @@ -139,7 +138,11 @@ export class ExpertInitiatedBlameV3MirrorController { @Param("requestId") requestId: string, @Body() dto: VerifyPartyOtpDto, ) { - return this.requestManagementService.verifyPartyOtpV2(expert, requestId, dto); + return this.requestManagementService.verifyPartyOtpV2( + expert, + requestId, + dto, + ); } @Post("car-body-form/:requestId") @@ -181,7 +184,9 @@ export class ExpertInitiatedBlameV3MirrorController { @Post("add-detail-location/:requestId") @ApiParam({ name: "requestId" }) @ApiBody({ type: LocationDto }) - @ApiOperation({ summary: "Add location for current party (partyRole FIRST or SECOND)" }) + @ApiOperation({ + summary: "Add location for current party (partyRole FIRST or SECOND)", + }) async addLocation( @Param("requestId") requestId: string, @Body() body: LocationDto, @@ -400,38 +405,42 @@ export class ExpertInitiatedBlameV3MirrorController { @Patch("select-other-parts/:claimRequestId") @ApiParam({ name: "claimRequestId", example: "507f1f77bcf86cd799439011" }) - @ApiConsumes("multipart/form-data") - @UseInterceptors( - FileInterceptor("file", { - limits: { fileSize: DEFAULT_MEDIA_MAX_BYTES }, - storage: diskStorage({ - destination: "./files/claim-required-document", - filename: (req, file, callback) => { - const unique = Date.now(); - const ex = extname(file.originalname); - callback(null, `v3-other-parts-${unique}${ex}`); - }, - }), - }), - ) @ApiOperation({ summary: "Select other damaged parts (V3)", description: "Other parts only — sheba and national code were collected during run-inquiries. Optional car green card file.", }) @ApiBody({ - description: "Other parts selection. Bank info is already on the claim from run-inquiries.", + description: + "Other parts selection. Bank info is already on the claim from run-inquiries.", schema: { type: "object", properties: { otherParts: { oneOf: [ - { type: "array", items: { type: "string", enum: ["engine", "suspension", "brake_system", "electrical", "radiator", "transmission", "exhaust", "headlight", "taillight", "mirror", "glass"] } }, + { + type: "array", + items: { + type: "string", + enum: [ + "engine", + "suspension", + "brake_system", + "electrical", + "radiator", + "transmission", + "exhaust", + "headlight", + "taillight", + "mirror", + "glass", + ], + }, + }, { type: "string", description: "JSON string array for multipart" }, ], example: ["engine", "suspension"], }, - file: { type: "string", format: "binary", description: "Optional car green card" }, }, }, }) @@ -439,17 +448,12 @@ export class ExpertInitiatedBlameV3MirrorController { @Param("claimRequestId") claimRequestId: string, @Body() body: SelectOtherPartsV3Dto, @CurrentUser() expert: any, - @UploadedFile() file?: Express.Multer.File, ): Promise { - if (file) { - await this.mediaPolicyService.assertForClaim(file, claimRequestId, "image"); - } return this.claimRequestManagementService.selectOtherPartsV3( claimRequestId, body, expert.sub, expert, - file, ); }