forked from Yara724/api
Compare commits
5 Commits
f3c7f6a7e0
...
e2a9232523
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2a9232523 | ||
|
|
dc006735ba | ||
|
|
f92cee0575 | ||
|
|
493be68b80 | ||
|
|
edf027acd3 |
@@ -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;
|
||||
}
|
||||
|
||||
@@ -6281,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];
|
||||
|
||||
@@ -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<GetCaptureRequirementsV2ResponseDto> {
|
||||
return this.claimRequestManagementService.getCaptureRequirementsV3(
|
||||
claimRequestId,
|
||||
fileMaker.sub,
|
||||
fileMaker,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
@@ -2091,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;
|
||||
}
|
||||
@@ -4547,29 +4566,32 @@ export class RequestManagementService {
|
||||
);
|
||||
}
|
||||
const sent: string[] = [];
|
||||
|
||||
// Helper: send OTP or silently succeed when a valid OTP already exists.
|
||||
const sendOrSkipIfActive = async (phone: string): Promise<void> => {
|
||||
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;
|
||||
}
|
||||
try {
|
||||
await this.userAuthService.sendOtpRequest(dto.firstPartyPhoneNumber);
|
||||
sent.push(dto.firstPartyPhoneNumber);
|
||||
await this.userAuthService.sendOtpRequest(phone);
|
||||
sent.push(phone);
|
||||
} 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.`,
|
||||
);
|
||||
sent.push(phone); // race: sendOtpRequest found it active too — treat as sent
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
await sendOrSkipIfActive(dto.firstPartyPhoneNumber);
|
||||
if (dto.secondPartyPhoneNumber) {
|
||||
try {
|
||||
await this.userAuthService.sendOtpRequest(dto.secondPartyPhoneNumber);
|
||||
sent.push(dto.secondPartyPhoneNumber);
|
||||
} 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.`,
|
||||
);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
await sendOrSkipIfActive(dto.secondPartyPhoneNumber);
|
||||
}
|
||||
return {
|
||||
sent: true,
|
||||
@@ -4735,6 +4757,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()
|
||||
@@ -4747,18 +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 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)) {
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user