forked from Yara724/api
Merge pull request 'FAKE SMS + SEARCH APIs' (#148) from s.yahyaee/yara724-api:main into main
Reviewed-on: Yara724/api#148
This commit is contained in:
@@ -60,6 +60,7 @@ SMS_PROVIDER =
|
||||
SMS_API_KEY =
|
||||
AUTH_SMS_TEMPLATE =
|
||||
EXP_OTP_TIME =
|
||||
FAKE_OTP =
|
||||
|
||||
# ---------------------------------------------
|
||||
# ⚙️ Application Features / Flags
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
} from "src/helpers/iran-mobile";
|
||||
import {
|
||||
computeOtpExpireMs,
|
||||
FAKE_OTP_CODE,
|
||||
isFakeOtpEnabled,
|
||||
isOtpExpiryActive,
|
||||
readOtpExpireMinutesFromEnv,
|
||||
} from "src/helpers/user-otp-expiry";
|
||||
@@ -109,7 +111,7 @@ export class UserAuthService {
|
||||
const userExist = await this.userDbService.findOne(
|
||||
buildUserLookupByPhone(canonicalMobile),
|
||||
);
|
||||
const otp = this.otpCreator.create();
|
||||
const otp = this.createOtpForRequest();
|
||||
const hashOtp = await this.hashService.hash(otp);
|
||||
const expireMinutes = readOtpExpireMinutesFromEnv();
|
||||
const nowMs = Date.now();
|
||||
@@ -158,7 +160,23 @@ export class UserAuthService {
|
||||
return new LoginDtoRs(userExist);
|
||||
}
|
||||
|
||||
private createOtpForRequest(): string {
|
||||
if (isFakeOtpEnabled()) {
|
||||
this.logger.warn(
|
||||
"FAKE_OTP=true — using fixed dev OTP; SMS provider is not called",
|
||||
);
|
||||
return FAKE_OTP_CODE;
|
||||
}
|
||||
return this.otpCreator.create();
|
||||
}
|
||||
|
||||
private async smsSender(otp: string, mobile: string) {
|
||||
if (isFakeOtpEnabled()) {
|
||||
this.logger.log(
|
||||
`FAKE_OTP=true — skipped SMS for phone=${mobile} (use OTP ${FAKE_OTP_CODE})`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const ok = await this.smsOrchestrationService.sendAuthOtp(
|
||||
mobile,
|
||||
otp,
|
||||
|
||||
@@ -9,9 +9,10 @@ import {
|
||||
MaxLength,
|
||||
Min,
|
||||
} from "class-validator";
|
||||
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
||||
import { UNIFIED_FILE_STATUS_KEYS } from "src/helpers/unified-file-status";
|
||||
|
||||
/** Allowed sort keys for V2 list endpoints (claims / blame). */
|
||||
/** Allowed sort keys for V2 list endpoints (claims / blame / insurer files). */
|
||||
export const LIST_SORT_BY_V2 = [
|
||||
"publicId",
|
||||
"createdAt",
|
||||
@@ -21,9 +22,16 @@ export const LIST_SORT_BY_V2 = [
|
||||
|
||||
export type ListSortByV2 = (typeof LIST_SORT_BY_V2)[number];
|
||||
|
||||
export const LIST_FILE_TYPE_V2 = [
|
||||
BlameRequestType.THIRD_PARTY,
|
||||
BlameRequestType.CAR_BODY,
|
||||
] as const;
|
||||
|
||||
export type ListFileTypeV2 = (typeof LIST_FILE_TYPE_V2)[number];
|
||||
|
||||
/**
|
||||
* Optional query params for V2 lists. If neither `page` nor `limit` is sent,
|
||||
* the full filtered+sorted list is returned (backward compatible).
|
||||
* Optional query params for V2 expert/insurer file lists.
|
||||
* If neither `page` nor `limit` is sent, the full filtered+sorted list is returned.
|
||||
*/
|
||||
export class ListQueryV2Dto {
|
||||
@ApiPropertyOptional({
|
||||
@@ -68,7 +76,7 @@ export class ListQueryV2Dto {
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description:
|
||||
"Case-insensitive substring match on `publicId` and `requestNo` (and other endpoint-specific fields).",
|
||||
"Case-insensitive substring match on `publicId`, `requestNo`, status, and other endpoint-specific fields.",
|
||||
example: "A142",
|
||||
maxLength: 64,
|
||||
})
|
||||
@@ -85,4 +93,13 @@ export class ListQueryV2Dto {
|
||||
@IsOptional()
|
||||
@IsIn([...UNIFIED_FILE_STATUS_KEYS])
|
||||
unifiedStatus?: (typeof UNIFIED_FILE_STATUS_KEYS)[number];
|
||||
|
||||
@ApiPropertyOptional({
|
||||
enum: LIST_FILE_TYPE_V2,
|
||||
description:
|
||||
"Filter by blame file type: third-party liability (`THIRD_PARTY`) or car body (`CAR_BODY`).",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsIn([...LIST_FILE_TYPE_V2])
|
||||
fileType?: ListFileTypeV2;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,42 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { IsIn, IsISO8601, IsOptional } from "class-validator";
|
||||
import {
|
||||
LIST_FILE_TYPE_V2,
|
||||
ListFileTypeV2,
|
||||
} from "src/common/dto/list-query-v2.dto";
|
||||
import {
|
||||
UNIFIED_FILE_STATUS_KEYS,
|
||||
UnifiedFileStatusDefinition,
|
||||
UnifiedFileStatusKey,
|
||||
} from "src/helpers/unified-file-status";
|
||||
|
||||
export class UnifiedFileStatusReportQueryDto {
|
||||
@ApiPropertyOptional({
|
||||
description: "Optional start datetime (ISO 8601) for portfolio date filter.",
|
||||
example: "2026-01-01T00:00:00.000Z",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsISO8601({ strict: false })
|
||||
from?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
description: "Optional end datetime (ISO 8601) for portfolio date filter.",
|
||||
example: "2026-12-31T23:59:59.999Z",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsISO8601({ strict: false })
|
||||
to?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
enum: LIST_FILE_TYPE_V2,
|
||||
description:
|
||||
"Count only files of this blame type (`THIRD_PARTY` or `CAR_BODY`).",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsIn([...LIST_FILE_TYPE_V2])
|
||||
fileType?: ListFileTypeV2;
|
||||
}
|
||||
|
||||
export class UnifiedFileStatusDefinitionDto implements UnifiedFileStatusDefinition {
|
||||
@ApiProperty({ enum: UNIFIED_FILE_STATUS_KEYS })
|
||||
key: UnifiedFileStatusKey;
|
||||
|
||||
@@ -28,9 +28,13 @@ import {
|
||||
getUnifiedFileStatusCatalog,
|
||||
resolveUnifiedFileStatus,
|
||||
} from "src/helpers/unified-file-status";
|
||||
import { applyListQueryV2 } from "src/helpers/list-query-v2";
|
||||
import { applyListQueryV2, isInListDateRange, parseListDateRange } from "src/helpers/list-query-v2";
|
||||
import { ClaimCaseDbService } from "src/claim-request-management/entites/db-service/claim-case.db.service";
|
||||
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
||||
import {
|
||||
UnifiedFileStatusReportDto,
|
||||
UnifiedFileStatusReportQueryDto,
|
||||
} from "src/common/dto/unified-file-status-report.dto";
|
||||
import {
|
||||
ExpertFileAssignResultDto,
|
||||
ExpertFileAssignStatus,
|
||||
@@ -156,8 +160,25 @@ export class ExpertBlameService {
|
||||
});
|
||||
}
|
||||
|
||||
async getUnifiedFileStatusReportV2(actor: any) {
|
||||
const rows = await this.collectBlamePortfolioDocs(actor);
|
||||
async getUnifiedFileStatusReportV2(
|
||||
actor: any,
|
||||
query: UnifiedFileStatusReportQueryDto = {},
|
||||
): Promise<UnifiedFileStatusReportDto> {
|
||||
let rows = await this.collectBlamePortfolioDocs(actor);
|
||||
const { fromDate, toDate } = parseListDateRange(query.from, query.to);
|
||||
rows = rows.filter((doc) =>
|
||||
isInListDateRange(
|
||||
(doc as { createdAt?: Date }).createdAt,
|
||||
fromDate,
|
||||
toDate,
|
||||
),
|
||||
);
|
||||
if (query.fileType) {
|
||||
rows = rows.filter(
|
||||
(doc) =>
|
||||
String((doc as { type?: string }).type ?? "") === query.fileType,
|
||||
);
|
||||
}
|
||||
const claimByPublicId = await this.loadClaimsByPublicIds(
|
||||
rows.map((d) => String((d as { publicId?: string }).publicId ?? "")),
|
||||
);
|
||||
@@ -590,6 +611,8 @@ export class ExpertBlameService {
|
||||
claimStatus: claim?.status,
|
||||
});
|
||||
},
|
||||
fileType: (doc) =>
|
||||
String((doc as { type?: string }).type ?? "") || undefined,
|
||||
searchExtras: (doc) => {
|
||||
const d = doc as {
|
||||
_id?: unknown;
|
||||
|
||||
@@ -26,6 +26,10 @@ import { CurrentUser } from "src/decorators/user.decorator";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { ExpertBlameService } from "./expert-blame.service";
|
||||
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
||||
import {
|
||||
UnifiedFileStatusReportDto,
|
||||
UnifiedFileStatusReportQueryDto,
|
||||
} from "src/common/dto/unified-file-status-report.dto";
|
||||
import { AllRequestDtoRsV2 } from "./dto/all-request.dto";
|
||||
import { SubmitReplyDto } from "./dto/reply.dto";
|
||||
import { ResendRequestDto } from "./dto/resend.dto";
|
||||
@@ -44,8 +48,9 @@ export class ExpertBlameV2Controller {
|
||||
description:
|
||||
"Damage experts (`expert`): tenant-scoped **DISAGREEMENT** queue (available, locked, or decided by you). " +
|
||||
"Field experts (`field_expert`): all expert-initiated blame files they created (LINK + IN_PERSON). Use expert-claim for claims after blame completion. " +
|
||||
"Optional query: `search`, `sortBy` (publicId | createdAt | requestNo | status), `sortOrder`, `page`, `limit`, `unifiedStatus` (calculated blame+claim status).",
|
||||
"Optional query: `search`, `sortBy` (publicId | createdAt | requestNo | status), `sortOrder`, `page`, `limit`, `unifiedStatus`, `fileType` (THIRD_PARTY | CAR_BODY).",
|
||||
})
|
||||
@ApiResponse({ status: 200, type: AllRequestDtoRsV2 })
|
||||
async findAll(
|
||||
@CurrentUser() actor: any,
|
||||
@Query() query: ListQueryV2Dto,
|
||||
@@ -64,11 +69,18 @@ export class ExpertBlameV2Controller {
|
||||
@ApiOperation({
|
||||
summary: "Unified file status catalog + counts (blame + linked claim)",
|
||||
description:
|
||||
"Calculatable statuses for this expert's blame portfolio. Filter lists with `unifiedStatus` query param.",
|
||||
"Calculatable statuses for this expert's blame portfolio. Filter lists with `unifiedStatus` and `fileType` query params. Optional `from` / `to` ISO dates narrow the counted portfolio.",
|
||||
})
|
||||
async getUnifiedFileStatusReportV2(@CurrentUser() actor: any) {
|
||||
@ApiResponse({ status: 200, type: UnifiedFileStatusReportDto })
|
||||
async getUnifiedFileStatusReportV2(
|
||||
@CurrentUser() actor: any,
|
||||
@Query() query: UnifiedFileStatusReportQueryDto,
|
||||
): Promise<UnifiedFileStatusReportDto> {
|
||||
try {
|
||||
return await this.expertBlameService.getUnifiedFileStatusReportV2(actor);
|
||||
return await this.expertBlameService.getUnifiedFileStatusReportV2(
|
||||
actor,
|
||||
query,
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) throw error;
|
||||
throw new InternalServerErrorException(
|
||||
|
||||
@@ -141,7 +141,15 @@ import {
|
||||
/** Maximum sum of line `totalPayment` across the claim (Toman; priced parts + factor lines after validation). */
|
||||
import { getClaimV2TotalPaymentCapToman } from "src/constants/repair-amount-limits";
|
||||
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
||||
import { applyListQueryV2 } from "src/helpers/list-query-v2";
|
||||
import {
|
||||
UnifiedFileStatusReportDto,
|
||||
UnifiedFileStatusReportQueryDto,
|
||||
} from "src/common/dto/unified-file-status-report.dto";
|
||||
import {
|
||||
applyListQueryV2,
|
||||
isInListDateRange,
|
||||
parseListDateRange,
|
||||
} from "src/helpers/list-query-v2";
|
||||
import {
|
||||
countUnifiedFileStatuses,
|
||||
getUnifiedFileStatusCatalog,
|
||||
@@ -3416,13 +3424,37 @@ export class ExpertClaimService {
|
||||
return buckets;
|
||||
}
|
||||
|
||||
async getUnifiedFileStatusReportV2(actor: any) {
|
||||
async getUnifiedFileStatusReportV2(
|
||||
actor: any,
|
||||
query: UnifiedFileStatusReportQueryDto = {},
|
||||
): Promise<UnifiedFileStatusReportDto> {
|
||||
const { claims, blameById } =
|
||||
await this.collectClaimPortfolioWithBlame(actor);
|
||||
const { fromDate, toDate } = parseListDateRange(query.from, query.to);
|
||||
const filteredClaims = claims.filter((c) => {
|
||||
if (
|
||||
!isInListDateRange(
|
||||
c.createdAt as Date | string | undefined,
|
||||
fromDate,
|
||||
toDate,
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (!query.fileType) return true;
|
||||
const blame = c.blameRequestId
|
||||
? blameById.get(String(c.blameRequestId))
|
||||
: undefined;
|
||||
const type =
|
||||
blame?.type ??
|
||||
(c as { snapshot?: { accident?: { type?: string } } })?.snapshot
|
||||
?.accident?.type;
|
||||
return type === query.fileType;
|
||||
});
|
||||
return {
|
||||
statuses: getUnifiedFileStatusCatalog(),
|
||||
counts: countUnifiedFileStatuses(
|
||||
claims.map((c) => {
|
||||
filteredClaims.map((c) => {
|
||||
const blame = c.blameRequestId
|
||||
? blameById.get(String(c.blameRequestId))
|
||||
: undefined;
|
||||
@@ -3532,6 +3564,7 @@ export class ExpertClaimService {
|
||||
createdAt: (r) => r.createdAt,
|
||||
requestNo: (r) => r.publicId,
|
||||
status: (r) => r.unifiedFileStatus ?? r.status,
|
||||
fileType: (r) => r.blameRequestType,
|
||||
searchExtras: (r) =>
|
||||
[
|
||||
r.claimRequestId,
|
||||
|
||||
@@ -31,6 +31,10 @@ import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { ExpertFileAssignResultDto } from "src/common/dto/expert-file-assign-result.dto";
|
||||
import { ExpertClaimService } from "./expert-claim.service";
|
||||
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
||||
import {
|
||||
UnifiedFileStatusReportDto,
|
||||
UnifiedFileStatusReportQueryDto,
|
||||
} from "src/common/dto/unified-file-status-report.dto";
|
||||
import { GetClaimListV2ResponseDto } from "./dto/claim-list-v2.dto";
|
||||
import {
|
||||
ClaimSubmitResendV2Dto,
|
||||
@@ -69,10 +73,17 @@ export class ExpertClaimV2Controller {
|
||||
@ApiOperation({
|
||||
summary: "Unified file status catalog + counts (claim + linked blame)",
|
||||
description:
|
||||
"Calculatable statuses for this expert's claim portfolio. Filter lists with `unifiedStatus` query param.",
|
||||
"Calculatable statuses for this expert's claim portfolio. Filter lists with `unifiedStatus` and `fileType` query params. Optional `from` / `to` ISO dates narrow the counted portfolio.",
|
||||
})
|
||||
async getUnifiedFileStatusReportV2(@CurrentUser() actor: any) {
|
||||
return await this.expertClaimService.getUnifiedFileStatusReportV2(actor);
|
||||
@ApiResponse({ status: 200, type: UnifiedFileStatusReportDto })
|
||||
async getUnifiedFileStatusReportV2(
|
||||
@CurrentUser() actor: any,
|
||||
@Query() query: UnifiedFileStatusReportQueryDto,
|
||||
): Promise<UnifiedFileStatusReportDto> {
|
||||
return await this.expertClaimService.getUnifiedFileStatusReportV2(
|
||||
actor,
|
||||
query,
|
||||
);
|
||||
}
|
||||
|
||||
@Get("report/status-counts")
|
||||
@@ -127,7 +138,8 @@ export class ExpertClaimV2Controller {
|
||||
summary: "List available claim requests for damage expert",
|
||||
description:
|
||||
"Damage experts: tenant queue (`WAITING_FOR_DAMAGE_EXPERT`), locked/in-progress, factor validation. " +
|
||||
"Field experts: all claims linked to blame files they initiated (`initiatedByFieldExpertId` or matching `blameRequestId`). Optional query: `search`, `sortBy`, `sortOrder`, `page`, `limit`, `unifiedStatus`.",
|
||||
"Field experts: all claims linked to blame files they initiated (`initiatedByFieldExpertId` or matching `blameRequestId`). " +
|
||||
"Optional query: `search`, `sortBy`, `sortOrder`, `page`, `limit`, `unifiedStatus`, `fileType` (THIRD_PARTY | CAR_BODY).",
|
||||
})
|
||||
@ApiResponse({ status: 200, type: GetClaimListV2ResponseDto })
|
||||
async getClaimListV2(
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
ApiOperation,
|
||||
ApiParam,
|
||||
ApiQuery,
|
||||
ApiResponse,
|
||||
ApiTags,
|
||||
} from "@nestjs/swagger";
|
||||
import { Types } from "mongoose";
|
||||
@@ -27,6 +28,10 @@ import { FileRating } from "src/request-management/entities/schema/request-manag
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { ExpertInsurerService } from "./expert-insurer.service";
|
||||
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
||||
import {
|
||||
UnifiedFileStatusReportDto,
|
||||
UnifiedFileStatusReportQueryDto,
|
||||
} from "src/common/dto/unified-file-status-report.dto";
|
||||
import { CreateBranchDto } from "src/client/dto/create-branch.dto";
|
||||
import {
|
||||
CreateBlameExpertByInsurerDto,
|
||||
@@ -166,7 +171,7 @@ export class ExpertInsurerController {
|
||||
@ApiOperation({
|
||||
summary: "List insurer files (blame + claim merged by publicId)",
|
||||
description:
|
||||
"Optional query: `search`, `sortBy` (publicId | createdAt | requestNo | status), `sortOrder`, `page`, `limit`, `unifiedStatus`.",
|
||||
"Optional query: `search`, `sortBy` (publicId | createdAt | requestNo | status), `sortOrder`, `page`, `limit`, `unifiedStatus`, `fileType` (THIRD_PARTY | CAR_BODY).",
|
||||
})
|
||||
async getAllFiles(
|
||||
@CurrentUser() insurer,
|
||||
@@ -182,27 +187,16 @@ export class ExpertInsurerController {
|
||||
@ApiOperation({
|
||||
summary: "Unified file status catalog + counts (blame + claim)",
|
||||
description:
|
||||
"Returns calculatable unified statuses and per-status counts for this insurer's files. Use `unifiedStatus` on GET files to filter.",
|
||||
})
|
||||
@ApiQuery({
|
||||
name: "from",
|
||||
required: false,
|
||||
description: "Optional start datetime (ISO string)",
|
||||
})
|
||||
@ApiQuery({
|
||||
name: "to",
|
||||
required: false,
|
||||
description: "Optional end datetime (ISO string)",
|
||||
"Calculatable unified statuses and per-status counts for this insurer's files. Filter lists with `unifiedStatus` and `fileType`. Optional `from` / `to` ISO dates narrow the counted portfolio.",
|
||||
})
|
||||
@ApiResponse({ status: 200, type: UnifiedFileStatusReportDto })
|
||||
async getUnifiedFileStatusReport(
|
||||
@CurrentUser() actor,
|
||||
@Query("from") from?: string,
|
||||
@Query("to") to?: string,
|
||||
) {
|
||||
@Query() query: UnifiedFileStatusReportQueryDto,
|
||||
): Promise<UnifiedFileStatusReportDto> {
|
||||
return await this.expertInsurerService.getInsurerUnifiedFileStatusReport(
|
||||
actor,
|
||||
from,
|
||||
to,
|
||||
query,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,15 @@ import { enrichBlamePartiesForAgreementView } from "src/helpers/blame-party-agre
|
||||
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
||||
import { PartyRole } from "src/request-management/entities/schema/partyRole.enum";
|
||||
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
||||
import { applyListQueryV2 } from "src/helpers/list-query-v2";
|
||||
import {
|
||||
UnifiedFileStatusReportDto,
|
||||
UnifiedFileStatusReportQueryDto,
|
||||
} from "src/common/dto/unified-file-status-report.dto";
|
||||
import {
|
||||
applyListQueryV2,
|
||||
isInListDateRange,
|
||||
parseListDateRange,
|
||||
} from "src/helpers/list-query-v2";
|
||||
import {
|
||||
countUnifiedFileStatuses,
|
||||
getUnifiedFileStatusCatalog,
|
||||
@@ -1328,6 +1336,7 @@ export class ExpertInsurerService {
|
||||
createdAt: (f) => f.updatedAt ?? f.createdAt,
|
||||
requestNo: (f) => f.publicId,
|
||||
status: (f) => f.unifiedFileStatus,
|
||||
fileType: (f) => f.fileType,
|
||||
searchExtras: (f) =>
|
||||
[
|
||||
f.fileType,
|
||||
@@ -1352,16 +1361,18 @@ export class ExpertInsurerService {
|
||||
|
||||
async getInsurerUnifiedFileStatusReport(
|
||||
actor: any,
|
||||
from?: string,
|
||||
to?: string,
|
||||
) {
|
||||
query: UnifiedFileStatusReportQueryDto = {},
|
||||
): Promise<UnifiedFileStatusReportDto> {
|
||||
const clientObjectId = this.getClientId(actor);
|
||||
const { fromDate, toDate } = this.parseDateRange(from, to);
|
||||
const { fromDate, toDate } = parseListDateRange(query.from, query.to);
|
||||
|
||||
const files = await this.buildMergedInsurerFiles(clientObjectId);
|
||||
const inRange = files.filter((f) =>
|
||||
this.isInDateRange(f.createdAt, fromDate, toDate),
|
||||
let inRange = files.filter((f) =>
|
||||
isInListDateRange(f.createdAt, fromDate, toDate),
|
||||
);
|
||||
if (query.fileType) {
|
||||
inRange = inRange.filter((f) => f.fileType === query.fileType);
|
||||
}
|
||||
|
||||
return {
|
||||
statuses: getUnifiedFileStatusCatalog(),
|
||||
@@ -1946,7 +1957,10 @@ export class ExpertInsurerService {
|
||||
under_review: number;
|
||||
waiting_for_documents_resend: number;
|
||||
}> {
|
||||
const report = await this.getInsurerUnifiedFileStatusReport(actor, from, to);
|
||||
const report = await this.getInsurerUnifiedFileStatusReport(actor, {
|
||||
from,
|
||||
to,
|
||||
});
|
||||
const counts = report.counts;
|
||||
return {
|
||||
all: counts.all ?? 0,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
||||
import { BadRequestException } from "@nestjs/common";
|
||||
|
||||
const MAX_LIMIT = 100;
|
||||
const DEFAULT_LIMIT_WHEN_PAGE_SET = 20;
|
||||
@@ -8,10 +9,45 @@ export type ListItemGetters<T> = {
|
||||
createdAt: (row: T) => Date | string | number | undefined;
|
||||
requestNo?: (row: T) => string | undefined;
|
||||
status: (row: T) => string | undefined;
|
||||
/** Blame file type (`THIRD_PARTY` | `CAR_BODY`) when available. */
|
||||
fileType?: (row: T) => string | undefined;
|
||||
/** Extra strings to match against `search` (e.g. claimRequestId, vehicle). */
|
||||
searchExtras?: (row: T) => string[];
|
||||
};
|
||||
|
||||
export function parseListDateRange(
|
||||
from?: string,
|
||||
to?: string,
|
||||
): { fromDate?: Date; toDate?: Date } {
|
||||
const fromDate = from ? new Date(from) : undefined;
|
||||
const toDate = to ? new Date(to) : undefined;
|
||||
|
||||
if (fromDate && Number.isNaN(fromDate.getTime())) {
|
||||
throw new BadRequestException("Invalid 'from' date");
|
||||
}
|
||||
if (toDate && Number.isNaN(toDate.getTime())) {
|
||||
throw new BadRequestException("Invalid 'to' date");
|
||||
}
|
||||
if (fromDate && toDate && fromDate > toDate) {
|
||||
throw new BadRequestException("'from' must be before or equal to 'to'");
|
||||
}
|
||||
return { fromDate, toDate };
|
||||
}
|
||||
|
||||
export function isInListDateRange(
|
||||
value: Date | string | number | undefined,
|
||||
fromDate?: Date,
|
||||
toDate?: Date,
|
||||
): boolean {
|
||||
if (!fromDate && !toDate) return true;
|
||||
if (value == null) return false;
|
||||
const d = value instanceof Date ? value : new Date(value);
|
||||
if (Number.isNaN(d.getTime())) return false;
|
||||
if (fromDate && d < fromDate) return false;
|
||||
if (toDate && d > toDate) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function normalizeSearch(raw?: string): string | undefined {
|
||||
const t = raw?.trim().toLowerCase();
|
||||
return t || undefined;
|
||||
@@ -40,8 +76,13 @@ export function applyListQueryV2<T>(
|
||||
} {
|
||||
const search = normalizeSearch(query.search);
|
||||
let filtered = rows;
|
||||
if (query.fileType && getters.fileType) {
|
||||
filtered = filtered.filter(
|
||||
(row) => getters.fileType!(row) === query.fileType,
|
||||
);
|
||||
}
|
||||
if (search) {
|
||||
filtered = rows.filter((row) => {
|
||||
filtered = filtered.filter((row) => {
|
||||
const chunks: string[] = [];
|
||||
const pid = getters.publicId(row);
|
||||
if (pid) chunks.push(pid);
|
||||
|
||||
@@ -41,3 +41,10 @@ export function readOtpExpireMinutesFromEnv(): number {
|
||||
const raw = Number(process.env.EXP_OTP_TIME ?? "2");
|
||||
return Number.isFinite(raw) && raw > 0 ? raw : 2;
|
||||
}
|
||||
|
||||
/** Dev-only: skip SMS provider and accept a fixed OTP for any mobile. */
|
||||
export const FAKE_OTP_CODE = "12345";
|
||||
|
||||
export function isFakeOtpEnabled(): boolean {
|
||||
return process.env.FAKE_OTP === "true";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user