From edf027acd3dab79b45de21126ed4432a4c5d376d Mon Sep 17 00:00:00 2001 From: SepehrYahyaee <7heycallmegray@gmail.com> Date: Wed, 1 Jul 2026 12:00:29 +0330 Subject: [PATCH 1/5] YARA-1076 --- .../claim-request-management.service.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/claim-request-management/claim-request-management.service.ts b/src/claim-request-management/claim-request-management.service.ts index e971684..5630d20 100644 --- a/src/claim-request-management/claim-request-management.service.ts +++ b/src/claim-request-management/claim-request-management.service.ts @@ -346,6 +346,14 @@ export class ClaimRequestManagementService { : this.isRequiredDocumentUploadedOnClaim(claimCase, k); if (!ok) return false; } + if (isCarBody) { + const g = ClaimRequiredDocumentType.CAR_GREEN_CARD; + const ok = + assumeUploadedKey === g + ? true + : this.isRequiredDocumentUploadedOnClaim(claimCase, g); + if (!ok) return false; + } return true; } @@ -362,6 +370,14 @@ export class ClaimRequestManagementService { : this.isRequiredDocumentUploadedOnClaim(claimCase, k); if (!ok) n++; } + if (isCarBody) { + const g = ClaimRequiredDocumentType.CAR_GREEN_CARD; + const ok = + assumeUploadedKey === g + ? true + : this.isRequiredDocumentUploadedOnClaim(claimCase, g); + if (!ok) n++; + } return n; } From 493be68b80af7ad8b3636b963542a5ab88e8c5c4 Mon Sep 17 00:00:00 2001 From: SepehrYahyaee <7heycallmegray@gmail.com> Date: Wed, 1 Jul 2026 12:04:39 +0330 Subject: [PATCH 2/5] YARA-1025 --- .../request-management.service.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/request-management/request-management.service.ts b/src/request-management/request-management.service.ts index bcb4bef..a4b8168 100644 --- a/src/request-management/request-management.service.ts +++ b/src/request-management/request-management.service.ts @@ -47,6 +47,7 @@ import { ReqBlameStatus } from "src/Types&Enums/blame-request-management/status. import { StepsEnum } from "src/Types&Enums/blame-request-management/steps.enum"; import { ExpertDbService } from "src/users/entities/db-service/expert.db.service"; import { UserDbService } from "src/users/entities/db-service/user.db.service"; +import { isOtpExpiryActive } from "src/helpers/user-otp-expiry"; import { parseIranLocalDateTime } from "src/helpers/iran-datetime"; import { applyListQueryV2 } from "src/helpers/list-query-v2"; import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto"; @@ -4735,6 +4736,7 @@ export class RequestManagementService { const phone = (dto?.phoneNumber || "").trim(); if (!phone) throw new BadRequestException("phoneNumber is required"); + // --- Cooldown guard (in-memory history, catches already-persisted sends) --- const twoMinutesAgo = new Date(Date.now() - 2 * 60 * 1000); const recentlySentToPhone = (req.history || []) .slice() @@ -4752,6 +4754,20 @@ export class RequestManagementService { ); } + // --- DB-level cooldown guard (catches burst/concurrent requests before history is saved) --- + // Read the user's otpExpire directly from the users collection. If a live + // OTP already exists we return 400 immediately — no SMS call, no DB writes. + const canonicalPhone = + normalizeIranMobile(phone) ?? phone; + const existingUser = await this.userDbService.findOne( + buildUserLookupByPhone(canonicalPhone), + ); + if (existingUser && isOtpExpiryActive(existingUser.otpExpire)) { + throw new BadRequestException( + `(${phone}): OTP was sent recently. Please wait before requesting again.`, + ); + } + try { await this.userAuthService.sendOtpRequest(phone); } catch (e: any) { From f92cee0575d8574d559212f0837ec1ab412ffd5d Mon Sep 17 00:00:00 2001 From: SepehrYahyaee <7heycallmegray@gmail.com> Date: Wed, 1 Jul 2026 12:11:19 +0330 Subject: [PATCH 3/5] YARA-1075 --- .../request-management.service.ts | 85 ++++++++++++------- 1 file changed, 54 insertions(+), 31 deletions(-) diff --git a/src/request-management/request-management.service.ts b/src/request-management/request-management.service.ts index a4b8168..49572d0 100644 --- a/src/request-management/request-management.service.ts +++ b/src/request-management/request-management.service.ts @@ -2092,14 +2092,32 @@ export class RequestManagementService { (req.parties[secondIdx].person as any).phoneNumber = phoneNumber; } + // If a valid OTP is already active for this number, return 200 immediately — + // no duplicate SMS, no unnecessary write, no error shown to the expert. + const canonicalPhoneAdv = normalizeIranMobile(phoneNumber) ?? phoneNumber; + const existingUserAdv = await this.userDbService.findOne( + buildUserLookupByPhone(canonicalPhoneAdv), + ); + if (existingUserAdv && isOtpExpiryActive(existingUserAdv.otpExpire)) { + return { + requestId: req._id, + publicId: req.publicId, + workflow: req.workflow, + message: `OTP already sent to ${phoneNumber} and is still valid. Collect the code and call verify-party-otp.`, + }; + } + // Send OTP to second party — expert collects it from them in person try { await this.userAuthService.sendOtpRequest(phoneNumber); } catch (e: any) { if (e?.message?.includes("Wait for expiry")) { - throw new BadRequestException( - `(${phoneNumber}): OTP was recently sent. Wait for the expiry time before requesting again.`, - ); + return { + requestId: req._id, + publicId: req.publicId, + workflow: req.workflow, + message: `OTP already sent to ${phoneNumber} and is still valid. Collect the code and call verify-party-otp.`, + }; } throw e; } @@ -4548,29 +4566,32 @@ export class RequestManagementService { ); } const sent: string[] = []; - try { - await this.userAuthService.sendOtpRequest(dto.firstPartyPhoneNumber); - sent.push(dto.firstPartyPhoneNumber); - } catch (e: any) { - if (e?.message?.includes("Wait for expiry")) { - throw new BadRequestException( - `First party (${dto.firstPartyPhoneNumber}): OTP was recently sent. Wait for the expiry time before requesting again.`, - ); + + // Helper: send OTP or silently succeed when a valid OTP already exists. + const sendOrSkipIfActive = async (phone: string): Promise => { + const canonical = normalizeIranMobile(phone) ?? phone; + const existing = await this.userDbService.findOne( + buildUserLookupByPhone(canonical), + ); + if (existing && isOtpExpiryActive(existing.otpExpire)) { + sent.push(phone); // already has a live OTP — treat as sent + return; } - throw e; - } - if (dto.secondPartyPhoneNumber) { try { - await this.userAuthService.sendOtpRequest(dto.secondPartyPhoneNumber); - sent.push(dto.secondPartyPhoneNumber); + await this.userAuthService.sendOtpRequest(phone); + sent.push(phone); } catch (e: any) { if (e?.message?.includes("Wait for expiry")) { - throw new BadRequestException( - `Second party (${dto.secondPartyPhoneNumber}): OTP was recently sent. Wait for the expiry time before requesting again.`, - ); + sent.push(phone); // race: sendOtpRequest found it active too — treat as sent + return; } throw e; } + }; + + await sendOrSkipIfActive(dto.firstPartyPhoneNumber); + if (dto.secondPartyPhoneNumber) { + await sendOrSkipIfActive(dto.secondPartyPhoneNumber); } return { sent: true, @@ -4749,32 +4770,34 @@ export class RequestManagementService { new Date(event.timestamp) > twoMinutesAgo, ); if (recentlySentToPhone) { - throw new BadRequestException( - `(${phone}): OTP was sent recently. Please wait 2 minutes before requesting again.`, - ); + return { + sent: true, + message: `OTP already sent to ${phone} and is still valid. Collect the code from the party, then call verify-party-otp.`, + }; } // --- DB-level cooldown guard (catches burst/concurrent requests before history is saved) --- // Read the user's otpExpire directly from the users collection. If a live - // OTP already exists we return 400 immediately — no SMS call, no DB writes. - const canonicalPhone = - normalizeIranMobile(phone) ?? phone; + // OTP already exists return 200 immediately — no duplicate SMS, no DB writes. + const canonicalPhone = normalizeIranMobile(phone) ?? phone; const existingUser = await this.userDbService.findOne( buildUserLookupByPhone(canonicalPhone), ); if (existingUser && isOtpExpiryActive(existingUser.otpExpire)) { - throw new BadRequestException( - `(${phone}): OTP was sent recently. Please wait before requesting again.`, - ); + return { + sent: true, + message: `OTP already sent to ${phone} and is still valid. Collect the code from the party, then call verify-party-otp.`, + }; } try { await this.userAuthService.sendOtpRequest(phone); } catch (e: any) { if (e?.message?.includes("Wait for expiry")) { - throw new BadRequestException( - `(${phone}): OTP was recently sent. Wait for the expiry time before requesting again.`, - ); + return { + sent: true, + message: `OTP already sent to ${phone} and is still valid. Collect the code from the party, then call verify-party-otp.`, + }; } throw e; } From dc006735ba3fc3129914fbc5315a73bf7c67ccd6 Mon Sep 17 00:00:00 2001 From: SepehrYahyaee <7heycallmegray@gmail.com> Date: Wed, 1 Jul 2026 12:15:22 +0330 Subject: [PATCH 4/5] Duplicated capture-requirements endpoint for file-maker --- .../file-maker-blame-v4.controller.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/request-management/file-maker-blame-v4.controller.ts b/src/request-management/file-maker-blame-v4.controller.ts index bdb2227..d1bb5eb 100644 --- a/src/request-management/file-maker-blame-v4.controller.ts +++ b/src/request-management/file-maker-blame-v4.controller.ts @@ -43,6 +43,7 @@ import { UploadRequiredDocumentV2Dto, UploadRequiredDocumentV2ResponseDto, } from "src/claim-request-management/dto/upload-document-v2.dto"; +import { GetCaptureRequirementsV2ResponseDto } from "src/claim-request-management/dto/capture-requirements-v2.dto"; /** * V4 FileMaker flow — first half of the split blame workflow. @@ -365,4 +366,24 @@ export class FileMakerBlameV4Controller { fileMaker, ); } + + @Get("capture-requirements/:claimRequestId") + @ApiParam({ name: "claimRequestId" }) + @ApiOperation({ + summary: "Capture requirements (step-aware)", + description: + "Initial documents phase: pre-capture docs only (no chassis/engine/metal plate). " + + "CAPTURE_PART_DAMAGES phase: damaged parts + angles via capture-part, then chassis/engine/metal plate via upload-document. " + + "Use `captureSequencePhase` and `captureSequenceHint` to drive the UI.", + }) + async getCaptureRequirements( + @Param("claimRequestId") claimRequestId: string, + @CurrentUser() fileMaker: any, + ): Promise { + return this.claimRequestManagementService.getCaptureRequirementsV3( + claimRequestId, + fileMaker.sub, + fileMaker, + ); + } } From e2a9232523a7e3bc44b8422f07528ca123621fca Mon Sep 17 00:00:00 2001 From: SepehrYahyaee <7heycallmegray@gmail.com> Date: Wed, 1 Jul 2026 12:23:34 +0330 Subject: [PATCH 5/5] YARA-1058 --- .../claim-request-management.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/claim-request-management/claim-request-management.service.ts b/src/claim-request-management/claim-request-management.service.ts index 5630d20..b85e157 100644 --- a/src/claim-request-management/claim-request-management.service.ts +++ b/src/claim-request-management/claim-request-management.service.ts @@ -6297,8 +6297,8 @@ export class ClaimRequestManagementService { ); } - // Initial flow: each document once. Expert resend: allow replacement. - if (!isResendUpload) { + // Initial flow: each document once. Expert resend and v3 in-person: allow replacement. + if (!isResendUpload && !options?.v3InPersonFlow) { const existingDoc = (claimCase.requiredDocuments as any)?.get?.(body.documentKey) ?? (claimCase.requiredDocuments as any)?.[body.documentKey];