forked from Yara724/api
114 lines
4.0 KiB
TypeScript
114 lines
4.0 KiB
TypeScript
import { Types } from "mongoose";
|
|
import { ExpertFileActivityType } from "src/users/entities/schema/expert-file-activity.schema";
|
|
import {
|
|
blameCaseAccessibleToExpert,
|
|
claimCaseTouchesClient,
|
|
} from "src/helpers/tenant-scope";
|
|
|
|
export interface ExpertFileActivityEventRow {
|
|
fileId: Types.ObjectId | string;
|
|
eventType: ExpertFileActivityType;
|
|
occurredAt: Date;
|
|
}
|
|
|
|
/**
|
|
* Distinct file ids where the expert is actively checking (CHECKED, not UNCHECKED)
|
|
* or has handled the file (HANDLED), replayed in chronological order.
|
|
*/
|
|
export function expertPortfolioFileIdsFromActivityEvents(
|
|
events: ExpertFileActivityEventRow[],
|
|
): Set<string> {
|
|
const sorted = [...events].sort(
|
|
(a, b) =>
|
|
a.occurredAt.getTime() - b.occurredAt.getTime() ||
|
|
String(a.fileId).localeCompare(String(b.fileId)),
|
|
);
|
|
const stateByFile = new Map<string, { checked: boolean; handled: boolean }>();
|
|
|
|
for (const e of sorted) {
|
|
const fid = String(e.fileId);
|
|
const prev = stateByFile.get(fid) ?? { checked: false, handled: false };
|
|
if (e.eventType === ExpertFileActivityType.CHECKED) {
|
|
if (!prev.handled) prev.checked = true;
|
|
} else if (e.eventType === ExpertFileActivityType.UNCHECKED) {
|
|
prev.checked = false;
|
|
} else if (e.eventType === ExpertFileActivityType.HANDLED) {
|
|
prev.handled = true;
|
|
prev.checked = false;
|
|
}
|
|
stateByFile.set(fid, prev);
|
|
}
|
|
|
|
const ids = new Set<string>();
|
|
for (const [fid, st] of stateByFile) {
|
|
if (st.handled || st.checked) ids.add(fid);
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
export function objectIdsFromStringSet(ids: Set<string>): Types.ObjectId[] {
|
|
return [...ids]
|
|
.filter((id) => Types.ObjectId.isValid(id))
|
|
.map((id) => new Types.ObjectId(id));
|
|
}
|
|
|
|
/** Damage-expert claim portfolio: assigned (lock/check) or substantive work on the file. */
|
|
export function claimDocInExpertPortfolio(
|
|
doc: Record<string, unknown>,
|
|
expertId: string,
|
|
activityFileIds: Set<string>,
|
|
clientKey: string,
|
|
): boolean {
|
|
if (!claimCaseTouchesClient(doc, clientKey)) return false;
|
|
const id = String(doc._id ?? "");
|
|
if (activityFileIds.has(id)) return true;
|
|
|
|
const lockedBy = (doc.workflow as { lockedBy?: { actorId?: unknown } } | undefined)
|
|
?.lockedBy?.actorId;
|
|
if (lockedBy != null && String(lockedBy) === String(expertId)) return true;
|
|
|
|
const evaluation = doc.evaluation as Record<string, unknown> | undefined;
|
|
for (const key of ["damageExpertReply", "damageExpertReplyFinal"] as const) {
|
|
const reply = evaluation?.[key] as
|
|
| { actorDetail?: { actorId?: unknown } }
|
|
| undefined;
|
|
const actorId = reply?.actorDetail?.actorId;
|
|
if (actorId != null && String(actorId) === String(expertId)) return true;
|
|
}
|
|
const resendBy = (
|
|
evaluation?.damageExpertResend as { requestedByExpertId?: unknown } | undefined
|
|
)?.requestedByExpertId;
|
|
if (resendBy != null && String(resendBy) === String(expertId)) return true;
|
|
return false;
|
|
}
|
|
|
|
/** Field-expert blame portfolio: initiated, decided, locked, or file-activity touch. */
|
|
export function blameDocInExpertPortfolio(
|
|
doc: Record<string, unknown>,
|
|
actor: { sub: string; clientKey?: string },
|
|
activityFileIds: Set<string>,
|
|
): boolean {
|
|
if (!blameCaseAccessibleToExpert(doc, actor)) return false;
|
|
const expertId = String(actor.sub);
|
|
const id = String(doc._id ?? "");
|
|
if (activityFileIds.has(id)) return true;
|
|
|
|
if (doc.expertInitiated && doc.initiatedByFieldExpertId) {
|
|
return String(doc.initiatedByFieldExpertId) === expertId;
|
|
}
|
|
|
|
const expert = doc.expert as Record<string, unknown> | undefined;
|
|
const decidedBy = (expert?.decision as { decidedByExpertId?: unknown } | undefined)
|
|
?.decidedByExpertId;
|
|
if (decidedBy != null && String(decidedBy) === expertId) return true;
|
|
|
|
const lockedBy =
|
|
(expert?.resend as { requestedByExpertId?: unknown } | undefined)
|
|
?.requestedByExpertId ??
|
|
(doc.workflow as { lockedBy?: { actorId?: unknown } } | undefined)?.lockedBy
|
|
?.actorId;
|
|
if (lockedBy != null && String(lockedBy) === expertId) return true;
|
|
|
|
return false;
|
|
}
|