This commit is contained in:
SepehrYahyaee
2026-05-18 11:00:53 +03:30
parent 7ff3e9fd10
commit e1954cdb37
8 changed files with 327 additions and 34 deletions

View File

@@ -16,6 +16,11 @@ import {
blameCaseStatusToReportBucket,
initialBlameExpertReportBuckets,
} from "src/helpers/expert-panel-status-report";
import {
blameDocInExpertPortfolio,
expertPortfolioFileIdsFromActivityEvents,
objectIdsFromStringSet,
} from "src/helpers/expert-portfolio";
import { RequestManagementDbService } from "src/request-management/entities/db-service/request-management.db.service";
import { BlameRequestDbService } from "src/request-management/entities/db-service/blame-request.db.service";
import {
@@ -222,37 +227,54 @@ export class ExpertBlameService {
* Does NOT show requests decided by other experts.
*/
/**
* V2: Count blame cases for this experts tenant, grouped for dashboard:
* `IN_PROGRESS` = OPEN + WAITING_FOR_SECOND_PARTY; other {@link CaseStatus} keys unchanged.
* V2: Count blame cases in this field experts portfolio, grouped for dashboard.
* Portfolio = file-activity (checked/handled), lock, decision, or expert-initiated file.
*/
async getStatusReportBucketsV2(actor: any): Promise<Record<string, number>> {
requireActorClientKey(actor);
const rows = await this.blameRequestDbService.find({}, { lean: true });
const expertId = String(actor.sub);
const expertOid = new Types.ObjectId(expertId);
const activityEvents = await this.expertFileActivityDbService.findByExpert(
expertId,
ExpertFileKind.BLAME,
);
const activityFileIds =
expertPortfolioFileIdsFromActivityEvents(activityEvents);
const orClauses: Record<string, unknown>[] = [
{ initiatedByFieldExpertId: expertOid },
{ "expert.decision.decidedByExpertId": expertOid },
{ "expert.resend.requestedByExpertId": expertOid },
{ "workflow.lockedBy.actorId": expertOid },
];
const activityOids = objectIdsFromStringSet(activityFileIds);
if (activityOids.length > 0) {
orClauses.push({ _id: { $in: activityOids } });
}
const rows = await this.blameRequestDbService.find(
{
blameStatus: BlameStatus.DISAGREEMENT,
$or: orClauses,
},
{ lean: true },
);
const buckets = initialBlameExpertReportBuckets();
const seen = new Set<string>();
for (const doc of rows as Record<string, unknown>[]) {
if (!this.blameDocIncludedInExpertTenantReport(doc, actor)) continue;
const st = String(doc.status ?? "");
const key = blameCaseStatusToReportBucket(st);
const id = String(doc._id ?? "");
if (seen.has(id)) continue;
if (!blameDocInExpertPortfolio(doc, actor, activityFileIds)) continue;
seen.add(id);
const key = blameCaseStatusToReportBucket(String(doc.status ?? ""));
buckets.all++;
buckets[key] = (buckets[key] ?? 0) + 1;
}
return buckets;
}
/** Tenant + expert-initiated visibility (same as list, without queue filters). */
private blameDocIncludedInExpertTenantReport(
doc: Record<string, unknown>,
actor: { sub: string; clientKey?: string },
): boolean {
if (!blameCaseAccessibleToExpert(doc, actor)) {
return false;
}
if (doc.expertInitiated && doc.initiatedByFieldExpertId) {
return String(doc.initiatedByFieldExpertId) === String(actor.sub);
}
return true;
}
async findAllV2(actor: any): Promise<AllRequestDtoRsV2> {
try {
requireActorClientKey(actor);
@@ -1217,15 +1239,23 @@ export class ExpertBlameService {
);
}
const resendTenantId =
String(request.parties?.[0]?.person?.clientId ?? "") ||
String(request.parties?.[1]?.person?.clientId ?? "");
await this.recordBlameExpertActivity({
expertId: String(actorId),
requestId: String(requestId),
eventType: ExpertFileActivityType.UNCHECKED,
tenantId:
String(request.parties?.[0]?.person?.clientId ?? "") ||
String(request.parties?.[1]?.person?.clientId ?? ""),
tenantId: resendTenantId,
idempotencyKey: `blame:${requestId}:unchecked:resend:${actorId}`,
});
await this.recordBlameExpertActivity({
expertId: String(actorId),
requestId: String(requestId),
eventType: ExpertFileActivityType.HANDLED,
tenantId: resendTenantId,
idempotencyKey: `blame:${requestId}:handled:resend:${actorId}`,
});
await this.requestManagementService.applyLinkedClaimsBlameResendStarted(
requestId,