Fix v3 mirror flow

This commit is contained in:
SepehrYahyaee
2026-06-22 17:30:58 +03:30
parent 83e82ea68e
commit db569db9d1
15 changed files with 1004 additions and 248 deletions

View File

@@ -15,6 +15,12 @@ export class ClaimListItemV2Dto {
})
status: string;
@ApiPropertyOptional({
description: "Calculated unified blame+claim lifecycle status",
example: "WAITING_FOR_DAMAGE_EXPERT",
})
unifiedFileStatus?: string;
@ApiProperty({ description: 'Workflow step', example: 'USER_SUBMISSION_COMPLETE' })
currentStep: string;

View File

@@ -142,6 +142,11 @@ import {
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 {
countUnifiedFileStatuses,
getUnifiedFileStatusCatalog,
resolveUnifiedFileStatus,
} from "src/helpers/unified-file-status";
import { buildEnrichedDamagedParts } from "./dto/claim-damaged-part.enricher";
import { canonicalizeResendDocumentKey } from "src/helpers/claim-resend-document-keys";
@@ -3411,6 +3416,144 @@ export class ExpertClaimService {
return buckets;
}
async getUnifiedFileStatusReportV2(actor: any) {
const { claims, blameById } =
await this.collectClaimPortfolioWithBlame(actor);
return {
statuses: getUnifiedFileStatusCatalog(),
counts: countUnifiedFileStatuses(
claims.map((c) => {
const blame = c.blameRequestId
? blameById.get(String(c.blameRequestId))
: undefined;
return {
blameStatus: blame?.status,
claimStatus: c.status,
};
}),
),
};
}
private async collectClaimPortfolioWithBlame(actor: any): Promise<{
claims: any[];
blameById: Map<string, any>;
}> {
let claims: any[] = [];
if (actor.role === RoleEnum.FIELD_EXPERT) {
const expertOid = new Types.ObjectId(actor.sub);
const expertBlameIds = await this.blameRequestDbService
.find(
{ expertInitiated: true, initiatedByFieldExpertId: expertOid },
{ select: "_id", lean: true },
)
.then((docs) => docs.map((d) => (d as { _id: unknown })._id));
const claimOr: Record<string, unknown>[] = [
{ initiatedByFieldExpertId: expertOid },
];
if (expertBlameIds.length > 0) {
claimOr.push({ blameRequestId: { $in: expertBlameIds } });
}
claims = (await this.claimCaseDbService.find({ $or: claimOr })) as any[];
} else {
const clientKey = requireActorClientKey(actor);
const expertId = String(actor.sub);
const expertOid = new Types.ObjectId(expertId);
const activityEvents = await this.expertFileActivityDbService.findByExpert(
expertId,
ExpertFileKind.CLAIM,
);
const activityFileIds =
expertPortfolioFileIdsFromActivityEvents(activityEvents);
const orClauses: Record<string, unknown>[] = [
{ "workflow.lockedBy.actorId": expertOid },
{ "evaluation.damageExpertReply.actorDetail.actorId": expertOid },
{ "evaluation.damageExpertReplyFinal.actorDetail.actorId": expertOid },
{ "evaluation.damageExpertResend.requestedByExpertId": expertOid },
];
const activityOids = objectIdsFromStringSet(activityFileIds);
if (activityOids.length > 0) {
orClauses.push({ _id: { $in: activityOids } });
}
const rows =
orClauses.length === 0
? []
: await this.claimCaseDbService.find({ $or: orClauses }, { lean: true });
const seen = new Set<string>();
for (const doc of rows as Record<string, unknown>[]) {
const id = String(doc._id ?? "");
if (seen.has(id)) continue;
if (
!claimDocInExpertPortfolio(doc, expertId, activityFileIds, clientKey)
) {
continue;
}
seen.add(id);
claims.push(doc);
}
}
const blameIds = [
...new Set(
claims
.map((c) => c.blameRequestId?.toString())
.filter((id): id is string => !!id),
),
];
const blames =
blameIds.length > 0
? ((await this.blameRequestDbService.find(
{
_id: { $in: blameIds.map((id) => new Types.ObjectId(id)) },
},
{ lean: true, select: "status publicId" },
)) as any[])
: [];
const blameById = new Map<string, any>(
blames.map((b) => [String(b._id), b]),
);
return { claims, blameById };
}
private paginateClaimListV2(
list: ClaimListItemV2Dto[],
query: ListQueryV2Dto,
): GetClaimListV2ResponseDto {
let filtered = list;
if (query.unifiedStatus) {
filtered = list.filter(
(item) => item.unifiedFileStatus === query.unifiedStatus,
);
}
const paged = applyListQueryV2(
filtered,
{
publicId: (r) => r.publicId,
createdAt: (r) => r.createdAt,
requestNo: (r) => r.publicId,
status: (r) => r.unifiedFileStatus ?? r.status,
searchExtras: (r) =>
[
r.claimRequestId,
r.currentStep,
r.vehicle?.carName,
r.vehicle?.carModel,
r.blameRequestType,
r.unifiedFileStatus,
r.status,
].filter(Boolean) as string[],
},
query,
);
return {
list: paged.list,
total: paged.total,
page: paged.page,
limit: paged.limit,
totalPages: paged.totalPages,
};
}
/**
* V2: Get claim list for damage expert
*
@@ -3581,6 +3724,10 @@ export class ExpertClaimService {
claimRequestId: c._id.toString(),
publicId: c.publicId,
status: statusForList,
unifiedFileStatus: resolveUnifiedFileStatus({
blameStatus: blame?.status,
claimStatus: c.status,
}),
currentStep: c.workflow?.currentStep || "",
locked: lockActive,
lockedBy:
@@ -3601,32 +3748,7 @@ export class ExpertClaimService {
};
}) as ClaimListItemV2Dto[];
const paged = applyListQueryV2(
list,
{
publicId: (r) => r.publicId,
createdAt: (r) => r.createdAt,
requestNo: (r) => r.publicId,
status: (r) => r.status,
searchExtras: (r) =>
[
r.claimRequestId,
r.currentStep,
r.vehicle?.carName,
r.vehicle?.carModel,
r.blameRequestType,
].filter(Boolean) as string[],
},
query,
);
return {
list: paged.list,
total: paged.total,
page: paged.page,
limit: paged.limit,
totalPages: paged.totalPages,
};
return this.paginateClaimListV2(list, query);
}
/**
@@ -3687,6 +3809,10 @@ export class ExpertClaimService {
claimRequestId: c._id.toString(),
publicId: c.publicId,
status: c.status,
unifiedFileStatus: resolveUnifiedFileStatus({
blameStatus: blame?.status,
claimStatus: c.status,
}),
currentStep: c.workflow?.currentStep || "",
locked: lockActive,
lockedBy:
@@ -3707,31 +3833,7 @@ export class ExpertClaimService {
};
}) as ClaimListItemV2Dto[];
const paged = applyListQueryV2(
list,
{
publicId: (r) => r.publicId,
createdAt: (r) => r.createdAt,
requestNo: (r) => r.publicId,
status: (r) => r.status,
searchExtras: (r) =>
[
r.claimRequestId,
r.currentStep,
r.vehicle?.carName,
r.vehicle?.carModel,
].filter(Boolean) as string[],
},
query,
);
return {
list: paged.list,
total: paged.total,
page: paged.page,
limit: paged.limit,
totalPages: paged.totalPages,
};
return this.paginateClaimListV2(list, query);
}
/**

View File

@@ -65,11 +65,22 @@ export class ExpertClaimV2Controller {
private readonly claimRequestManagementService: ClaimRequestManagementService,
) {}
@Get("report/unified-file-statuses")
@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.",
})
async getUnifiedFileStatusReportV2(@CurrentUser() actor: any) {
return await this.expertClaimService.getUnifiedFileStatusReportV2(actor);
}
@Get("report/status-counts")
@ApiOperation({
summary: "Count claim cases by grouped status bucket (this damage expert)",
deprecated: true,
description:
"Counts only files in the acting experts portfolio: expertFileActivities (checked or handled), current workflow lock, or a prior damageExpertReply / damageExpertReplyFinal by this expert. IN_PROGRESS groups user-phase statuses before submission is complete (CREATED through CAPTURING_PART_DAMAGES). Other keys match ClaimCaseStatus. Does not include the open tenant queue unless this expert has touched the file.",
"Legacy claim-only buckets. Prefer GET report/unified-file-statuses for blame+claim calculatable statuses.",
})
async getStatusReportBucketsV2(@CurrentUser() actor: any) {
return await this.expertClaimService.getStatusReportBucketsV2(actor);
@@ -116,7 +127,7 @@ 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`.",
"Field experts: all claims linked to blame files they initiated (`initiatedByFieldExpertId` or matching `blameRequestId`). Optional query: `search`, `sortBy`, `sortOrder`, `page`, `limit`, `unifiedStatus`.",
})
@ApiResponse({ status: 200, type: GetClaimListV2ResponseDto })
async getClaimListV2(