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

@@ -26,6 +26,7 @@ import { CurrentUser } from "src/decorators/user.decorator";
import { FileRating } from "src/request-management/entities/schema/request-management.schema";
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 { CreateBranchDto } from "src/client/dto/create-branch.dto";
import {
CreateBlameExpertByInsurerDto,
@@ -162,9 +163,46 @@ export class ExpertInsurerController {
}
@Get("files")
async getAllFiles(@CurrentUser() insurer) {
@ApiOperation({
summary: "List insurer files (blame + claim merged by publicId)",
description:
"Optional query: `search`, `sortBy` (publicId | createdAt | requestNo | status), `sortOrder`, `page`, `limit`, `unifiedStatus`.",
})
async getAllFiles(
@CurrentUser() insurer,
@Query() query: ListQueryV2Dto,
) {
return await this.expertInsurerService.retrieveAllFilesOfClient(
insurer.clientKey,
query,
);
}
@Get("report/unified-file-statuses")
@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)",
})
async getUnifiedFileStatusReport(
@CurrentUser() actor,
@Query("from") from?: string,
@Query("to") to?: string,
) {
return await this.expertInsurerService.getInsurerUnifiedFileStatusReport(
actor,
from,
to,
);
}
@@ -265,6 +303,11 @@ export class ExpertInsurerController {
}
@Get("report/status-counts")
@ApiOperation({
summary: "Legacy status counts (mapped from unified statuses)",
deprecated: true,
description: "Prefer GET report/unified-file-statuses for full calculatable blame+claim statuses.",
})
@ApiQuery({
name: "from",
required: false,

View File

@@ -39,6 +39,13 @@ import { toJalaliDateAndTime } from "src/helpers/date-jalali";
import { enrichBlamePartiesForAgreementView } from "src/helpers/blame-party-agreement-decision";
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 {
countUnifiedFileStatuses,
getUnifiedFileStatusCatalog,
resolveUnifiedFileStatus,
} from "src/helpers/unified-file-status";
import {
getClaimCarAngleCaptureBlob,
getDamagedPartCaptureBlob,
@@ -1145,13 +1152,10 @@ export class ExpertInsurerService {
};
}
async retrieveAllFilesOfClient(insurerId: string) {
const id = this.getClientId(insurerId);
const idStr = String(id);
private async buildMergedInsurerFiles(clientObjectId: Types.ObjectId) {
const [blameFiles, claimFiles] = await Promise.all([
this.getClientBlameFiles(id),
this.getClientClaimFiles(id),
this.getClientBlameFiles(clientObjectId),
this.getClientClaimFiles(clientObjectId),
]);
const byPublicId = new Map<
@@ -1192,7 +1196,6 @@ export class ExpertInsurerService {
}
>();
// Index blame files first
for (const b of blameFiles) {
const key = String((b as any).publicId || (b as any)._id || "");
if (!key) continue;
@@ -1216,8 +1219,6 @@ export class ExpertInsurerService {
byPublicId.set(key, prev);
}
// For claim files whose blame wasn't in blameFiles (different insurer's blame),
// fetch the linked blame directly so we can get vehicle + parties data
const claimOnlyBlameIds = claimFiles
.filter((c) => {
const key = String((c as any).publicId || (c as any)._id || "");
@@ -1243,13 +1244,11 @@ export class ExpertInsurerService {
linkedBlames.map((b) => [String(b.publicId), b]),
);
// Index claim files
for (const c of claimFiles) {
const key = String((c as any).publicId || (c as any)._id || "");
if (!key) continue;
const prev = byPublicId.get(key) ?? { publicId: key };
// If no blame entry yet, use the linked blame for parties/vehicle/fileType
if (!prev.blame) {
const linkedBlame = linkedBlameByPublicId.get(key);
if (linkedBlame) {
@@ -1293,26 +1292,85 @@ export class ExpertInsurerService {
byPublicId.set(key, prev);
}
const files = Array.from(byPublicId.values())
.map((f) => ({
publicId: f.publicId,
fileType: f.fileType,
hasClaim: !!f.claim,
parties: f.parties,
blame: f.blame,
claim: f.claim,
createdAt: f.createdAt,
updatedAt: f.updatedAt,
}))
.sort((a, b) => {
const at = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
const bt = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
return bt - at;
});
return Array.from(byPublicId.values()).map((f) => ({
publicId: f.publicId,
fileType: f.fileType,
hasClaim: !!f.claim,
unifiedFileStatus: resolveUnifiedFileStatus({
blameStatus: f.blame?.status,
claimStatus: f.claim?.status,
}),
parties: f.parties,
blame: f.blame,
claim: f.claim,
createdAt: f.createdAt,
updatedAt: f.updatedAt,
}));
}
async retrieveAllFilesOfClient(
insurerId: string,
query: ListQueryV2Dto = {},
) {
const id = this.getClientId(insurerId);
let files = await this.buildMergedInsurerFiles(id);
if (query.unifiedStatus) {
files = files.filter(
(f) => f.unifiedFileStatus === query.unifiedStatus,
);
}
const paged = applyListQueryV2(
files,
{
publicId: (f) => f.publicId,
createdAt: (f) => f.updatedAt ?? f.createdAt,
requestNo: (f) => f.publicId,
status: (f) => f.unifiedFileStatus,
searchExtras: (f) =>
[
f.fileType,
f.blame?.requestNo,
f.claim?.requestNo,
f.blame?.status,
f.claim?.status,
f.unifiedFileStatus,
].filter(Boolean) as string[],
},
query,
);
return {
total: files.length,
files,
total: paged.total,
page: paged.page,
limit: paged.limit,
totalPages: paged.totalPages,
files: paged.list,
};
}
async getInsurerUnifiedFileStatusReport(
actor: any,
from?: string,
to?: string,
) {
const clientObjectId = this.getClientId(actor);
const { fromDate, toDate } = this.parseDateRange(from, to);
const files = await this.buildMergedInsurerFiles(clientObjectId);
const inRange = files.filter((f) =>
this.isInDateRange(f.createdAt, fromDate, toDate),
);
return {
statuses: getUnifiedFileStatusCatalog(),
counts: countUnifiedFileStatuses(
inRange.map((f) => ({
blameStatus: f.blame?.status,
claimStatus: f.claim?.status,
})),
),
};
}
@@ -1888,72 +1946,17 @@ export class ExpertInsurerService {
under_review: number;
waiting_for_documents_resend: number;
}> {
const clientObjectId = this.getClientId(actor);
const { fromDate, toDate } = this.parseDateRange(from, to);
const [blameFilesRaw, claimFilesRaw] = await Promise.all([
this.getClientBlameFiles(clientObjectId),
this.getClientClaimFiles(clientObjectId),
]);
const blameFiles = blameFilesRaw.filter((f) =>
this.isInDateRange((f as any)?.createdAt, fromDate, toDate),
);
const claimFiles = claimFilesRaw.filter((f) =>
this.isInDateRange((f as any)?.createdAt, fromDate, toDate),
);
const fileMap = new Map<string, { blame?: any; claim?: any }>();
for (const b of blameFiles) {
const key = String((b as any).publicId || (b as any)._id || "");
if (!key) continue;
const prev = fileMap.get(key) ?? {};
prev.blame = b;
fileMap.set(key, prev);
}
for (const c of claimFiles) {
const key = String((c as any).publicId || (c as any)._id || "");
if (!key) continue;
const prev = fileMap.get(key) ?? {};
prev.claim = c;
fileMap.set(key, prev);
}
let completed = 0;
let underReview = 0;
let waitingForDocumentsResend = 0;
for (const entry of fileMap.values()) {
const blameStatus = entry.blame?.status;
const claimStatus = entry.claim?.status;
if (claimStatus === ClaimCaseStatus.COMPLETED) {
completed += 1;
}
const blameUnderReview = blameStatus === CaseStatus.WAITING_FOR_EXPERT;
const claimUnderReview =
claimStatus === ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT ||
claimStatus === ClaimCaseStatus.EXPERT_REVIEWING;
if (blameUnderReview || claimUnderReview) {
underReview += 1;
}
const blameResend =
blameStatus === CaseStatus.WAITING_FOR_DOCUMENT_RESEND;
const claimResend =
claimStatus === ClaimCaseStatus.WAITING_FOR_USER_RESEND;
if (blameResend || claimResend) {
waitingForDocumentsResend += 1;
}
}
const report = await this.getInsurerUnifiedFileStatusReport(actor, from, to);
const counts = report.counts;
return {
all: fileMap.size,
completed,
under_review: underReview,
waiting_for_documents_resend: waitingForDocumentsResend,
all: counts.all ?? 0,
completed: counts.COMPLETED ?? 0,
under_review:
(counts.WAITING_FOR_BLAME_EXPERT ?? 0) +
(counts.WAITING_FOR_DAMAGE_EXPERT ?? 0) +
(counts.EXPERT_REVIEWING ?? 0) +
(counts.EXPERT_VALIDATING_FACTORS ?? 0),
waiting_for_documents_resend: counts.WAITING_FOR_DOCUMENT_RESEND ?? 0,
};
}
}