forked from Yara724/api
Fix searching on GET APIs
This commit is contained in:
@@ -9,9 +9,10 @@ import {
|
|||||||
MaxLength,
|
MaxLength,
|
||||||
Min,
|
Min,
|
||||||
} from "class-validator";
|
} 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";
|
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 = [
|
export const LIST_SORT_BY_V2 = [
|
||||||
"publicId",
|
"publicId",
|
||||||
"createdAt",
|
"createdAt",
|
||||||
@@ -21,9 +22,16 @@ export const LIST_SORT_BY_V2 = [
|
|||||||
|
|
||||||
export type ListSortByV2 = (typeof LIST_SORT_BY_V2)[number];
|
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,
|
* Optional query params for V2 expert/insurer file lists.
|
||||||
* the full filtered+sorted list is returned (backward compatible).
|
* If neither `page` nor `limit` is sent, the full filtered+sorted list is returned.
|
||||||
*/
|
*/
|
||||||
export class ListQueryV2Dto {
|
export class ListQueryV2Dto {
|
||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
@@ -68,7 +76,7 @@ export class ListQueryV2Dto {
|
|||||||
|
|
||||||
@ApiPropertyOptional({
|
@ApiPropertyOptional({
|
||||||
description:
|
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",
|
example: "A142",
|
||||||
maxLength: 64,
|
maxLength: 64,
|
||||||
})
|
})
|
||||||
@@ -85,4 +93,13 @@ export class ListQueryV2Dto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsIn([...UNIFIED_FILE_STATUS_KEYS])
|
@IsIn([...UNIFIED_FILE_STATUS_KEYS])
|
||||||
unifiedStatus?: (typeof UNIFIED_FILE_STATUS_KEYS)[number];
|
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 {
|
import {
|
||||||
UNIFIED_FILE_STATUS_KEYS,
|
UNIFIED_FILE_STATUS_KEYS,
|
||||||
UnifiedFileStatusDefinition,
|
UnifiedFileStatusDefinition,
|
||||||
UnifiedFileStatusKey,
|
UnifiedFileStatusKey,
|
||||||
} from "src/helpers/unified-file-status";
|
} 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 {
|
export class UnifiedFileStatusDefinitionDto implements UnifiedFileStatusDefinition {
|
||||||
@ApiProperty({ enum: UNIFIED_FILE_STATUS_KEYS })
|
@ApiProperty({ enum: UNIFIED_FILE_STATUS_KEYS })
|
||||||
key: UnifiedFileStatusKey;
|
key: UnifiedFileStatusKey;
|
||||||
|
|||||||
@@ -28,9 +28,13 @@ import {
|
|||||||
getUnifiedFileStatusCatalog,
|
getUnifiedFileStatusCatalog,
|
||||||
resolveUnifiedFileStatus,
|
resolveUnifiedFileStatus,
|
||||||
} from "src/helpers/unified-file-status";
|
} 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 { ClaimCaseDbService } from "src/claim-request-management/entites/db-service/claim-case.db.service";
|
||||||
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
||||||
|
import {
|
||||||
|
UnifiedFileStatusReportDto,
|
||||||
|
UnifiedFileStatusReportQueryDto,
|
||||||
|
} from "src/common/dto/unified-file-status-report.dto";
|
||||||
import {
|
import {
|
||||||
ExpertFileAssignResultDto,
|
ExpertFileAssignResultDto,
|
||||||
ExpertFileAssignStatus,
|
ExpertFileAssignStatus,
|
||||||
@@ -156,8 +160,25 @@ export class ExpertBlameService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUnifiedFileStatusReportV2(actor: any) {
|
async getUnifiedFileStatusReportV2(
|
||||||
const rows = await this.collectBlamePortfolioDocs(actor);
|
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(
|
const claimByPublicId = await this.loadClaimsByPublicIds(
|
||||||
rows.map((d) => String((d as { publicId?: string }).publicId ?? "")),
|
rows.map((d) => String((d as { publicId?: string }).publicId ?? "")),
|
||||||
);
|
);
|
||||||
@@ -590,6 +611,8 @@ export class ExpertBlameService {
|
|||||||
claimStatus: claim?.status,
|
claimStatus: claim?.status,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
fileType: (doc) =>
|
||||||
|
String((doc as { type?: string }).type ?? "") || undefined,
|
||||||
searchExtras: (doc) => {
|
searchExtras: (doc) => {
|
||||||
const d = doc as {
|
const d = doc as {
|
||||||
_id?: unknown;
|
_id?: unknown;
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ import { CurrentUser } from "src/decorators/user.decorator";
|
|||||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||||
import { ExpertBlameService } from "./expert-blame.service";
|
import { ExpertBlameService } from "./expert-blame.service";
|
||||||
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
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 { AllRequestDtoRsV2 } from "./dto/all-request.dto";
|
||||||
import { SubmitReplyDto } from "./dto/reply.dto";
|
import { SubmitReplyDto } from "./dto/reply.dto";
|
||||||
import { ResendRequestDto } from "./dto/resend.dto";
|
import { ResendRequestDto } from "./dto/resend.dto";
|
||||||
@@ -44,8 +48,9 @@ export class ExpertBlameV2Controller {
|
|||||||
description:
|
description:
|
||||||
"Damage experts (`expert`): tenant-scoped **DISAGREEMENT** queue (available, locked, or decided by you). " +
|
"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. " +
|
"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(
|
async findAll(
|
||||||
@CurrentUser() actor: any,
|
@CurrentUser() actor: any,
|
||||||
@Query() query: ListQueryV2Dto,
|
@Query() query: ListQueryV2Dto,
|
||||||
@@ -64,11 +69,18 @@ export class ExpertBlameV2Controller {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Unified file status catalog + counts (blame + linked claim)",
|
summary: "Unified file status catalog + counts (blame + linked claim)",
|
||||||
description:
|
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 {
|
try {
|
||||||
return await this.expertBlameService.getUnifiedFileStatusReportV2(actor);
|
return await this.expertBlameService.getUnifiedFileStatusReportV2(
|
||||||
|
actor,
|
||||||
|
query,
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof HttpException) throw error;
|
if (error instanceof HttpException) throw error;
|
||||||
throw new InternalServerErrorException(
|
throw new InternalServerErrorException(
|
||||||
|
|||||||
@@ -141,7 +141,15 @@ import {
|
|||||||
/** Maximum sum of line `totalPayment` across the claim (Toman; priced parts + factor lines after validation). */
|
/** Maximum sum of line `totalPayment` across the claim (Toman; priced parts + factor lines after validation). */
|
||||||
import { getClaimV2TotalPaymentCapToman } from "src/constants/repair-amount-limits";
|
import { getClaimV2TotalPaymentCapToman } from "src/constants/repair-amount-limits";
|
||||||
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
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 {
|
import {
|
||||||
countUnifiedFileStatuses,
|
countUnifiedFileStatuses,
|
||||||
getUnifiedFileStatusCatalog,
|
getUnifiedFileStatusCatalog,
|
||||||
@@ -3416,13 +3424,37 @@ export class ExpertClaimService {
|
|||||||
return buckets;
|
return buckets;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUnifiedFileStatusReportV2(actor: any) {
|
async getUnifiedFileStatusReportV2(
|
||||||
|
actor: any,
|
||||||
|
query: UnifiedFileStatusReportQueryDto = {},
|
||||||
|
): Promise<UnifiedFileStatusReportDto> {
|
||||||
const { claims, blameById } =
|
const { claims, blameById } =
|
||||||
await this.collectClaimPortfolioWithBlame(actor);
|
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 {
|
return {
|
||||||
statuses: getUnifiedFileStatusCatalog(),
|
statuses: getUnifiedFileStatusCatalog(),
|
||||||
counts: countUnifiedFileStatuses(
|
counts: countUnifiedFileStatuses(
|
||||||
claims.map((c) => {
|
filteredClaims.map((c) => {
|
||||||
const blame = c.blameRequestId
|
const blame = c.blameRequestId
|
||||||
? blameById.get(String(c.blameRequestId))
|
? blameById.get(String(c.blameRequestId))
|
||||||
: undefined;
|
: undefined;
|
||||||
@@ -3532,6 +3564,7 @@ export class ExpertClaimService {
|
|||||||
createdAt: (r) => r.createdAt,
|
createdAt: (r) => r.createdAt,
|
||||||
requestNo: (r) => r.publicId,
|
requestNo: (r) => r.publicId,
|
||||||
status: (r) => r.unifiedFileStatus ?? r.status,
|
status: (r) => r.unifiedFileStatus ?? r.status,
|
||||||
|
fileType: (r) => r.blameRequestType,
|
||||||
searchExtras: (r) =>
|
searchExtras: (r) =>
|
||||||
[
|
[
|
||||||
r.claimRequestId,
|
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 { ExpertFileAssignResultDto } from "src/common/dto/expert-file-assign-result.dto";
|
||||||
import { ExpertClaimService } from "./expert-claim.service";
|
import { ExpertClaimService } from "./expert-claim.service";
|
||||||
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
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 { GetClaimListV2ResponseDto } from "./dto/claim-list-v2.dto";
|
||||||
import {
|
import {
|
||||||
ClaimSubmitResendV2Dto,
|
ClaimSubmitResendV2Dto,
|
||||||
@@ -69,10 +73,17 @@ export class ExpertClaimV2Controller {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Unified file status catalog + counts (claim + linked blame)",
|
summary: "Unified file status catalog + counts (claim + linked blame)",
|
||||||
description:
|
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) {
|
@ApiResponse({ status: 200, type: UnifiedFileStatusReportDto })
|
||||||
return await this.expertClaimService.getUnifiedFileStatusReportV2(actor);
|
async getUnifiedFileStatusReportV2(
|
||||||
|
@CurrentUser() actor: any,
|
||||||
|
@Query() query: UnifiedFileStatusReportQueryDto,
|
||||||
|
): Promise<UnifiedFileStatusReportDto> {
|
||||||
|
return await this.expertClaimService.getUnifiedFileStatusReportV2(
|
||||||
|
actor,
|
||||||
|
query,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("report/status-counts")
|
@Get("report/status-counts")
|
||||||
@@ -127,7 +138,8 @@ export class ExpertClaimV2Controller {
|
|||||||
summary: "List available claim requests for damage expert",
|
summary: "List available claim requests for damage expert",
|
||||||
description:
|
description:
|
||||||
"Damage experts: tenant queue (`WAITING_FOR_DAMAGE_EXPERT`), locked/in-progress, factor validation. " +
|
"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 })
|
@ApiResponse({ status: 200, type: GetClaimListV2ResponseDto })
|
||||||
async getClaimListV2(
|
async getClaimListV2(
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
ApiOperation,
|
ApiOperation,
|
||||||
ApiParam,
|
ApiParam,
|
||||||
ApiQuery,
|
ApiQuery,
|
||||||
|
ApiResponse,
|
||||||
ApiTags,
|
ApiTags,
|
||||||
} from "@nestjs/swagger";
|
} from "@nestjs/swagger";
|
||||||
import { Types } from "mongoose";
|
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 { RoleEnum } from "src/Types&Enums/role.enum";
|
||||||
import { ExpertInsurerService } from "./expert-insurer.service";
|
import { ExpertInsurerService } from "./expert-insurer.service";
|
||||||
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
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 { CreateBranchDto } from "src/client/dto/create-branch.dto";
|
||||||
import {
|
import {
|
||||||
CreateBlameExpertByInsurerDto,
|
CreateBlameExpertByInsurerDto,
|
||||||
@@ -166,7 +171,7 @@ export class ExpertInsurerController {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "List insurer files (blame + claim merged by publicId)",
|
summary: "List insurer files (blame + claim merged by publicId)",
|
||||||
description:
|
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(
|
async getAllFiles(
|
||||||
@CurrentUser() insurer,
|
@CurrentUser() insurer,
|
||||||
@@ -182,27 +187,16 @@ export class ExpertInsurerController {
|
|||||||
@ApiOperation({
|
@ApiOperation({
|
||||||
summary: "Unified file status catalog + counts (blame + claim)",
|
summary: "Unified file status catalog + counts (blame + claim)",
|
||||||
description:
|
description:
|
||||||
"Returns calculatable unified statuses and per-status counts for this insurer's files. Use `unifiedStatus` on GET files to filter.",
|
"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.",
|
||||||
})
|
|
||||||
@ApiQuery({
|
|
||||||
name: "from",
|
|
||||||
required: false,
|
|
||||||
description: "Optional start datetime (ISO string)",
|
|
||||||
})
|
|
||||||
@ApiQuery({
|
|
||||||
name: "to",
|
|
||||||
required: false,
|
|
||||||
description: "Optional end datetime (ISO string)",
|
|
||||||
})
|
})
|
||||||
|
@ApiResponse({ status: 200, type: UnifiedFileStatusReportDto })
|
||||||
async getUnifiedFileStatusReport(
|
async getUnifiedFileStatusReport(
|
||||||
@CurrentUser() actor,
|
@CurrentUser() actor,
|
||||||
@Query("from") from?: string,
|
@Query() query: UnifiedFileStatusReportQueryDto,
|
||||||
@Query("to") to?: string,
|
): Promise<UnifiedFileStatusReportDto> {
|
||||||
) {
|
|
||||||
return await this.expertInsurerService.getInsurerUnifiedFileStatusReport(
|
return await this.expertInsurerService.getInsurerUnifiedFileStatusReport(
|
||||||
actor,
|
actor,
|
||||||
from,
|
query,
|
||||||
to,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,15 @@ import { enrichBlamePartiesForAgreementView } from "src/helpers/blame-party-agre
|
|||||||
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
import { BlameRequestType } from "src/Types&Enums/blame-request-management/blameRequestType.enum";
|
||||||
import { PartyRole } from "src/request-management/entities/schema/partyRole.enum";
|
import { PartyRole } from "src/request-management/entities/schema/partyRole.enum";
|
||||||
import { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
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 {
|
import {
|
||||||
countUnifiedFileStatuses,
|
countUnifiedFileStatuses,
|
||||||
getUnifiedFileStatusCatalog,
|
getUnifiedFileStatusCatalog,
|
||||||
@@ -1328,6 +1336,7 @@ export class ExpertInsurerService {
|
|||||||
createdAt: (f) => f.updatedAt ?? f.createdAt,
|
createdAt: (f) => f.updatedAt ?? f.createdAt,
|
||||||
requestNo: (f) => f.publicId,
|
requestNo: (f) => f.publicId,
|
||||||
status: (f) => f.unifiedFileStatus,
|
status: (f) => f.unifiedFileStatus,
|
||||||
|
fileType: (f) => f.fileType,
|
||||||
searchExtras: (f) =>
|
searchExtras: (f) =>
|
||||||
[
|
[
|
||||||
f.fileType,
|
f.fileType,
|
||||||
@@ -1352,16 +1361,18 @@ export class ExpertInsurerService {
|
|||||||
|
|
||||||
async getInsurerUnifiedFileStatusReport(
|
async getInsurerUnifiedFileStatusReport(
|
||||||
actor: any,
|
actor: any,
|
||||||
from?: string,
|
query: UnifiedFileStatusReportQueryDto = {},
|
||||||
to?: string,
|
): Promise<UnifiedFileStatusReportDto> {
|
||||||
) {
|
|
||||||
const clientObjectId = this.getClientId(actor);
|
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 files = await this.buildMergedInsurerFiles(clientObjectId);
|
||||||
const inRange = files.filter((f) =>
|
let inRange = files.filter((f) =>
|
||||||
this.isInDateRange(f.createdAt, fromDate, toDate),
|
isInListDateRange(f.createdAt, fromDate, toDate),
|
||||||
);
|
);
|
||||||
|
if (query.fileType) {
|
||||||
|
inRange = inRange.filter((f) => f.fileType === query.fileType);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statuses: getUnifiedFileStatusCatalog(),
|
statuses: getUnifiedFileStatusCatalog(),
|
||||||
@@ -1946,7 +1957,10 @@ export class ExpertInsurerService {
|
|||||||
under_review: number;
|
under_review: number;
|
||||||
waiting_for_documents_resend: 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;
|
const counts = report.counts;
|
||||||
return {
|
return {
|
||||||
all: counts.all ?? 0,
|
all: counts.all ?? 0,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
import type { ListQueryV2Dto } from "src/common/dto/list-query-v2.dto";
|
||||||
|
import { BadRequestException } from "@nestjs/common";
|
||||||
|
|
||||||
const MAX_LIMIT = 100;
|
const MAX_LIMIT = 100;
|
||||||
const DEFAULT_LIMIT_WHEN_PAGE_SET = 20;
|
const DEFAULT_LIMIT_WHEN_PAGE_SET = 20;
|
||||||
@@ -8,10 +9,45 @@ export type ListItemGetters<T> = {
|
|||||||
createdAt: (row: T) => Date | string | number | undefined;
|
createdAt: (row: T) => Date | string | number | undefined;
|
||||||
requestNo?: (row: T) => string | undefined;
|
requestNo?: (row: T) => string | undefined;
|
||||||
status: (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). */
|
/** Extra strings to match against `search` (e.g. claimRequestId, vehicle). */
|
||||||
searchExtras?: (row: T) => string[];
|
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 {
|
function normalizeSearch(raw?: string): string | undefined {
|
||||||
const t = raw?.trim().toLowerCase();
|
const t = raw?.trim().toLowerCase();
|
||||||
return t || undefined;
|
return t || undefined;
|
||||||
@@ -40,8 +76,13 @@ export function applyListQueryV2<T>(
|
|||||||
} {
|
} {
|
||||||
const search = normalizeSearch(query.search);
|
const search = normalizeSearch(query.search);
|
||||||
let filtered = rows;
|
let filtered = rows;
|
||||||
|
if (query.fileType && getters.fileType) {
|
||||||
|
filtered = filtered.filter(
|
||||||
|
(row) => getters.fileType!(row) === query.fileType,
|
||||||
|
);
|
||||||
|
}
|
||||||
if (search) {
|
if (search) {
|
||||||
filtered = rows.filter((row) => {
|
filtered = filtered.filter((row) => {
|
||||||
const chunks: string[] = [];
|
const chunks: string[] = [];
|
||||||
const pid = getters.publicId(row);
|
const pid = getters.publicId(row);
|
||||||
if (pid) chunks.push(pid);
|
if (pid) chunks.push(pid);
|
||||||
|
|||||||
Reference in New Issue
Block a user