forked from Yara724/api
Merge pull request 'main' (#101) from s.yahyaee/yara724-api:main into main
Reviewed-on: Yara724/api#101
This commit is contained in:
@@ -303,66 +303,58 @@ export class ExpertBlameService {
|
||||
requireActorClientKey(actor);
|
||||
const expertId = actor.sub;
|
||||
|
||||
// Fetch all DISAGREEMENT cases
|
||||
const allCases = await this.blameRequestDbService.find(
|
||||
{
|
||||
blameStatus: BlameStatus.DISAGREEMENT,
|
||||
},
|
||||
{ blameStatus: BlameStatus.DISAGREEMENT },
|
||||
{ lean: true },
|
||||
);
|
||||
|
||||
// Filter to show only:
|
||||
// 1. Same insurance tenant (party clientId or expert-initiated by this actor)
|
||||
// 2. Fresh requests (WAITING_FOR_EXPERT and no decision)
|
||||
// 3. Requests decided by current expert
|
||||
// 4. Expert-initiated: only the initiating field expert sees them
|
||||
const visibleCases = (allCases as Record<string, unknown>[]).filter(
|
||||
(doc) => {
|
||||
if (!blameCaseAccessibleToExpert(doc, actor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const expertInitiated = doc.expertInitiated === true;
|
||||
const initiatedByFieldExpertId = doc.initiatedByFieldExpertId;
|
||||
|
||||
if (expertInitiated && initiatedByFieldExpertId) {
|
||||
if (String(initiatedByFieldExpertId) !== expertId) {
|
||||
return false; // Only the initiating field expert can see this file
|
||||
}
|
||||
return true; // Initiating expert can see their expert-initiated file
|
||||
}
|
||||
// Always filter by tenant first
|
||||
if (!blameCaseAccessibleToExpert(doc, actor)) return false;
|
||||
|
||||
const status = doc.status as string;
|
||||
const decision = doc.expert as any;
|
||||
const decidedByExpertId = decision?.decision?.decidedByExpertId;
|
||||
const hasDecision = !!decision?.decision;
|
||||
const decision = (doc.expert as any)?.decision;
|
||||
const decidedByExpertId = decision?.decidedByExpertId
|
||||
? String(decision.decidedByExpertId)
|
||||
: null;
|
||||
const lockedById = String(
|
||||
(doc.workflow as any)?.lockedBy?.actorId ?? "",
|
||||
);
|
||||
const lockEnforced =
|
||||
(doc.workflow as any)?.locked &&
|
||||
this.isBlameV2WorkflowLockCurrentlyEnforced(doc as any);
|
||||
|
||||
// Fresh request (no decision yet)
|
||||
if (status === CaseStatus.WAITING_FOR_EXPERT && !hasDecision) {
|
||||
return true;
|
||||
// Expert-initiated files: only the initiating expert sees them
|
||||
if (doc.expertInitiated === true && doc.initiatedByFieldExpertId) {
|
||||
return String(doc.initiatedByFieldExpertId) === expertId;
|
||||
}
|
||||
|
||||
// Request decided by current expert
|
||||
if (decidedByExpertId && String(decidedByExpertId) === expertId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Locked by current expert but no decision yet
|
||||
const lockedBy =
|
||||
decision?.resend?.requestedByExpertId ||
|
||||
(doc.workflow as any)?.lockedBy?.actorId;
|
||||
if (
|
||||
// Bucket 1: Available — waiting, no decision, no active lock by someone else
|
||||
const isAvailable =
|
||||
status === CaseStatus.WAITING_FOR_EXPERT &&
|
||||
lockedBy &&
|
||||
String(lockedBy) === expertId
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
!decidedByExpertId &&
|
||||
(!lockEnforced || lockedById === expertId);
|
||||
|
||||
return false;
|
||||
// Bucket 2: Mine — decided by me
|
||||
const isDecidedByMe =
|
||||
!!decidedByExpertId && decidedByExpertId === expertId;
|
||||
|
||||
// Bucket 3: Mine — currently locked/assigned to me (in progress)
|
||||
const isLockedByMe = lockEnforced && lockedById === expertId;
|
||||
|
||||
// Bucket 4: Mine — persistently assigned to me for review
|
||||
const assignedForReviewById = String(
|
||||
(doc.workflow as any)?.assignedForReviewBy?.actorId ?? "",
|
||||
);
|
||||
const isAssignedToMe =
|
||||
!!assignedForReviewById && assignedForReviewById === expertId;
|
||||
|
||||
return isAvailable || isDecidedByMe || isLockedByMe || isAssignedToMe;
|
||||
},
|
||||
);
|
||||
|
||||
// Reconcile stale locks in-memory (same as before)
|
||||
const staleIds = new Set<string>();
|
||||
for (const doc of visibleCases) {
|
||||
const w = doc.workflow as Record<string, unknown> | undefined;
|
||||
@@ -564,6 +556,7 @@ export class ExpertBlameService {
|
||||
|
||||
const assigneeToPersist = blameWorkflowAssigneeToPersistOnLockExpiry(
|
||||
request.workflow,
|
||||
request,
|
||||
);
|
||||
const expireSet: Record<string, unknown> = { "workflow.locked": false };
|
||||
if (assigneeToPersist) {
|
||||
@@ -854,75 +847,79 @@ export class ExpertBlameService {
|
||||
actor: any,
|
||||
): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
// requireActorClientKey(actor);
|
||||
const actorId = actor.sub;
|
||||
await this.expireBlameCaseWorkflowLockV2IfStale(requestId);
|
||||
|
||||
const doc =
|
||||
await this.blameRequestDbService.findByIdWithoutHistory(requestId);
|
||||
if (!doc) {
|
||||
throw new NotFoundException("Request not found");
|
||||
}
|
||||
// assertBlameCaseForExpertTenant(doc, actor);
|
||||
const type = doc.type as string;
|
||||
if (type === BlameRequestType.CAR_BODY) {
|
||||
|
||||
// Tenant check
|
||||
assertBlameCaseForExpertTenant(doc, actor);
|
||||
|
||||
if (doc.type === BlameRequestType.CAR_BODY) {
|
||||
throw new ForbiddenException(
|
||||
"CAR_BODY type requests are automatically handled and do not require expert review.",
|
||||
);
|
||||
}
|
||||
|
||||
// Access control
|
||||
const expertInitiated = doc.expertInitiated === true;
|
||||
const initiatedByFieldExpertId = doc.initiatedByFieldExpertId;
|
||||
// Expert-initiated: only the initiating expert
|
||||
if (
|
||||
expertInitiated &&
|
||||
initiatedByFieldExpertId &&
|
||||
String(initiatedByFieldExpertId) !== actorId
|
||||
doc.expertInitiated === true &&
|
||||
doc.initiatedByFieldExpertId &&
|
||||
String(doc.initiatedByFieldExpertId) !== actorId
|
||||
) {
|
||||
throw new ForbiddenException(
|
||||
"Only the field expert who created this file can view and review it.",
|
||||
);
|
||||
}
|
||||
|
||||
if (this.isBlameV2WorkflowLockCurrentlyEnforced(doc)) {
|
||||
const w = doc.workflow as
|
||||
| { lockedBy?: { actorId?: unknown } }
|
||||
| undefined;
|
||||
const lockerId = String(w?.lockedBy?.actorId ?? "");
|
||||
if (lockerId && lockerId !== actorId) {
|
||||
const decision = (doc.expert as any)?.decision;
|
||||
const decidedByExpertId = decision?.decidedByExpertId
|
||||
? String(decision.decidedByExpertId)
|
||||
: null;
|
||||
const lockedById = String((doc.workflow as any)?.lockedBy?.actorId ?? "");
|
||||
const lockEnforced =
|
||||
(doc.workflow as any)?.locked &&
|
||||
this.isBlameV2WorkflowLockCurrentlyEnforced(doc);
|
||||
const assignedForReviewById = String(
|
||||
(doc.workflow as any)?.assignedForReviewBy?.actorId ?? "",
|
||||
);
|
||||
|
||||
// Access gates — must satisfy at least one bucket
|
||||
const isAvailable =
|
||||
doc.status === CaseStatus.WAITING_FOR_EXPERT && !decidedByExpertId;
|
||||
const isDecidedByMe = decidedByExpertId === actorId;
|
||||
const isLockedByMe = lockEnforced && lockedById === actorId;
|
||||
const isAssignedToMe =
|
||||
!!assignedForReviewById && assignedForReviewById === actorId;
|
||||
|
||||
if (!isAvailable && !isDecidedByMe && !isLockedByMe && !isAssignedToMe) {
|
||||
// Give a specific reason if possible
|
||||
if (lockEnforced && lockedById && lockedById !== actorId) {
|
||||
throw new ForbiddenException(
|
||||
"This request is locked by another expert.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const decision = (doc.expert as any)?.decision;
|
||||
const decidedByExpertId = decision?.decidedByExpertId;
|
||||
if (decidedByExpertId && String(decidedByExpertId) !== actorId) {
|
||||
if (decidedByExpertId && decidedByExpertId !== actorId) {
|
||||
throw new ForbiddenException(
|
||||
"You do not have permission to view this request. It has been handled by another expert.",
|
||||
);
|
||||
}
|
||||
throw new ForbiddenException(
|
||||
"You do not have permission to view this request.",
|
||||
);
|
||||
}
|
||||
|
||||
// Build evidence URLs
|
||||
const parties = Array.isArray(doc.parties) ? doc.parties : [];
|
||||
|
||||
const typedParties = parties as Array<{
|
||||
vehicle?: {
|
||||
inquiry?: {
|
||||
mapped?: any;
|
||||
};
|
||||
};
|
||||
evidence?: {
|
||||
videoId?: string | number;
|
||||
voices?: (string | number)[];
|
||||
videoUrl?: string;
|
||||
voiceUrls?: string[];
|
||||
};
|
||||
}>;
|
||||
|
||||
// Evidence (videos + voices)
|
||||
for (const party of typedParties) {
|
||||
for (const party of parties as Array<{
|
||||
evidence?: Record<string, unknown>;
|
||||
}>) {
|
||||
if (!party.evidence) continue;
|
||||
const evidence = party.evidence as Record<string, unknown>;
|
||||
const evidence = party.evidence;
|
||||
|
||||
if (evidence.videoId) {
|
||||
const videoDoc = await this.blameVideoDbService.findById(
|
||||
@@ -931,7 +928,7 @@ export class ExpertBlameService {
|
||||
if (videoDoc?.path) evidence.videoUrl = buildFileLink(videoDoc.path);
|
||||
}
|
||||
|
||||
if (evidence.voices && Array.isArray(evidence.voices)) {
|
||||
if (Array.isArray(evidence.voices)) {
|
||||
const voiceUrls: string[] = [];
|
||||
for (const voiceId of evidence.voices) {
|
||||
const voiceDoc = await this.blameVoiceDbService.findById(
|
||||
@@ -950,31 +947,31 @@ export class ExpertBlameService {
|
||||
const updatedAt = doc.updatedAt
|
||||
? new Date(doc.updatedAt as string | number)
|
||||
: new Date();
|
||||
|
||||
const [createdDate, createdTime] = toJalaliDateAndTime(createdAt);
|
||||
const [updatedDate, updatedTime] = toJalaliDateAndTime(updatedAt);
|
||||
|
||||
doc.createdAtFormatted = `${createdDate} ${createdTime}`;
|
||||
doc.updatedAtFormatted = `${updatedDate} ${updatedTime}`;
|
||||
|
||||
// Omit heavy SandHub inquiry blob from expert response (keep other vehicle fields)
|
||||
for (const party of typedParties) {
|
||||
const veh = party?.vehicle as Record<string, unknown> | undefined;
|
||||
if (veh && Object.prototype.hasOwnProperty.call(veh, "inquiry")) {
|
||||
delete veh.inquiry;
|
||||
// Strip heavy SandHub inquiry blob
|
||||
for (const party of parties as Array<{
|
||||
vehicle?: Record<string, unknown>;
|
||||
}>) {
|
||||
if (
|
||||
party.vehicle &&
|
||||
Object.prototype.hasOwnProperty.call(party.vehicle, "inquiry")
|
||||
) {
|
||||
delete party.vehicle.inquiry;
|
||||
}
|
||||
}
|
||||
|
||||
return doc;
|
||||
} catch (error) {
|
||||
if (error instanceof HttpException) throw error;
|
||||
|
||||
this.logger.error(
|
||||
"findOneV2 failed",
|
||||
requestId,
|
||||
error instanceof Error ? error.stack : String(error),
|
||||
);
|
||||
|
||||
throw new InternalServerErrorException(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
|
||||
@@ -3294,22 +3294,26 @@ export class ExpertClaimService {
|
||||
|
||||
const claims = await this.claimCaseDbService.find({
|
||||
$or: [
|
||||
// Available claims: waiting for expert, not locked
|
||||
// Available: waiting, not locked
|
||||
{
|
||||
status: ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT,
|
||||
"workflow.locked": { $ne: true },
|
||||
// No persistent assignee (or expired without decision)
|
||||
$or: [
|
||||
{ "workflow.assignedForReviewBy": { $exists: false } },
|
||||
{ "workflow.assignedForReviewBy": null },
|
||||
],
|
||||
},
|
||||
// Expert reviewing but lock cleared (e.g. TTL expiry) — still list until status is reconciled
|
||||
{
|
||||
status: ClaimCaseStatus.EXPERT_REVIEWING,
|
||||
"workflow.locked": { $ne: true },
|
||||
},
|
||||
// This expert's own locked/in-progress claims
|
||||
// This expert's locked/in-progress claims
|
||||
{
|
||||
"workflow.locked": true,
|
||||
"workflow.lockedBy.actorId": new Types.ObjectId(actorId),
|
||||
},
|
||||
// User uploaded all factors; expert must approve/reject (unlocked queue)
|
||||
// This expert's persistently assigned claims (decided or resend/objection follow-up)
|
||||
{
|
||||
"workflow.assignedForReviewBy.actorId": new Types.ObjectId(actorId),
|
||||
},
|
||||
// Factor validation queue (open to all tenant experts — no lock needed)
|
||||
{
|
||||
$or: [
|
||||
{
|
||||
@@ -3324,6 +3328,16 @@ export class ExpertClaimService {
|
||||
},
|
||||
],
|
||||
},
|
||||
// WAITING_FOR_USER_RESEND assigned to this expert
|
||||
{
|
||||
status: ClaimCaseStatus.WAITING_FOR_USER_RESEND,
|
||||
"workflow.assignedForReviewBy.actorId": new Types.ObjectId(actorId),
|
||||
},
|
||||
// EXPERT_REVIEWING with stale lock (reconciliation will fix status, show in meantime)
|
||||
{
|
||||
status: ClaimCaseStatus.EXPERT_REVIEWING,
|
||||
"workflow.locked": { $ne: true },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -3331,10 +3345,12 @@ export class ExpertClaimService {
|
||||
claimCaseTouchesClient(c, clientKey),
|
||||
);
|
||||
|
||||
// Reconcile stale locks — if no decision was made, also clear assignedForReviewBy
|
||||
const staleLockToReconcile = filtered.filter(
|
||||
(c) =>
|
||||
c.workflow?.locked && !this.isClaimV2WorkflowLockCurrentlyEnforced(c),
|
||||
);
|
||||
|
||||
const touchedIds = (
|
||||
await Promise.all(
|
||||
staleLockToReconcile.map(async (c) => {
|
||||
@@ -3357,9 +3373,39 @@ export class ExpertClaimService {
|
||||
}
|
||||
}
|
||||
|
||||
// Post-filter: remove files that belong to another expert after reconciliation
|
||||
const finalFiltered = filtered.filter((c) => {
|
||||
const assignedId = String(c.workflow?.assignedForReviewBy?.actorId ?? "");
|
||||
const lockEnforced =
|
||||
c.workflow?.locked && this.isClaimV2WorkflowLockCurrentlyEnforced(c);
|
||||
const lockedById = String(c.workflow?.lockedBy?.actorId ?? "");
|
||||
const isFactorValidation = claimIsAwaitingExpertFactorValidationV2(c);
|
||||
|
||||
// Factor validation is open to all tenant experts
|
||||
if (isFactorValidation) return true;
|
||||
|
||||
// Available: no assignee, not locked by someone else
|
||||
const isAvailable =
|
||||
c.status === ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT &&
|
||||
!assignedId &&
|
||||
(!lockEnforced || lockedById === actorId);
|
||||
|
||||
// Mine: assigned to me (covers decided, resend, objection follow-up)
|
||||
const isAssignedToMe = !!assignedId && assignedId === actorId;
|
||||
|
||||
// Mine: currently locked by me
|
||||
const isLockedByMe = lockEnforced && lockedById === actorId;
|
||||
|
||||
// Mine: resend pending and I'm the assigned expert
|
||||
const isResendMine =
|
||||
c.status === ClaimCaseStatus.WAITING_FOR_USER_RESEND && isAssignedToMe;
|
||||
|
||||
return isAvailable || isAssignedToMe || isLockedByMe || isResendMine;
|
||||
});
|
||||
|
||||
const blameIds = [
|
||||
...new Set(
|
||||
filtered
|
||||
finalFiltered
|
||||
.map((c) => c.blameRequestId?.toString())
|
||||
.filter((id): id is string => !!id),
|
||||
),
|
||||
@@ -3375,7 +3421,7 @@ export class ExpertClaimService {
|
||||
blames.map((b) => [String(b._id), b]),
|
||||
);
|
||||
|
||||
const list = filtered.map((c) => {
|
||||
const list = finalFiltered.map((c) => {
|
||||
const awaitingFactorValidation =
|
||||
claimIsAwaitingExpertFactorValidationV2(c);
|
||||
const v = this.vehicleForExpertFromClaimAndBlameMap(c, blameById);
|
||||
@@ -3390,6 +3436,7 @@ export class ExpertClaimService {
|
||||
c.status === ClaimCaseStatus.EXPERT_REVIEWING
|
||||
? ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT
|
||||
: c.status;
|
||||
|
||||
return {
|
||||
claimRequestId: c._id.toString(),
|
||||
publicId: c.publicId,
|
||||
@@ -3406,11 +3453,7 @@ export class ExpertClaimService {
|
||||
}
|
||||
: undefined,
|
||||
vehicle: v
|
||||
? {
|
||||
carName: v.carName,
|
||||
carModel: v.carModel,
|
||||
carType: v.carType,
|
||||
}
|
||||
? { carName: v.carName, carModel: v.carModel, carType: v.carType }
|
||||
: undefined,
|
||||
...fileCtx,
|
||||
createdAt: c.createdAt,
|
||||
@@ -3727,6 +3770,7 @@ export class ExpertClaimService {
|
||||
|
||||
const assigneeToPersist = claimWorkflowAssigneeToPersistOnLockExpiry(
|
||||
claim.workflow,
|
||||
claim,
|
||||
);
|
||||
const expireLockSet: Record<string, unknown> = { "workflow.locked": false };
|
||||
if (assigneeToPersist) {
|
||||
@@ -3795,21 +3839,53 @@ export class ExpertClaimService {
|
||||
claim.status === ClaimCaseStatus.EXPERT_REVIEWING;
|
||||
const isFactorValidationPending =
|
||||
claimIsAwaitingExpertFactorValidationV2(claim);
|
||||
const isResendPending =
|
||||
claim.status === ClaimCaseStatus.WAITING_FOR_USER_RESEND;
|
||||
|
||||
if (!isDamageExpertPhase && !isFactorValidationPending) {
|
||||
if (
|
||||
!isDamageExpertPhase &&
|
||||
!isFactorValidationPending &&
|
||||
!isResendPending
|
||||
) {
|
||||
throw new ForbiddenException(
|
||||
`This claim is not available for expert review. Current status: ${claim.status}`,
|
||||
);
|
||||
}
|
||||
|
||||
const lockBlocksOthers = this.isClaimV2WorkflowLockCurrentlyEnforced(claim);
|
||||
if (lockBlocksOthers) {
|
||||
const lockerId = claim.workflow?.lockedBy?.actorId?.toString();
|
||||
if (lockerId && lockerId !== actorId) {
|
||||
// Ownership access control — same 4-bucket logic as list
|
||||
const assignedForReviewById = String(
|
||||
(claim.workflow as any)?.assignedForReviewBy?.actorId ?? "",
|
||||
);
|
||||
const lockEnforced = this.isClaimV2WorkflowLockCurrentlyEnforced(claim);
|
||||
const lockedById = String(claim.workflow?.lockedBy?.actorId ?? "");
|
||||
|
||||
const isAvailable =
|
||||
isDamageExpertPhase && !assignedForReviewById && !lockEnforced;
|
||||
const isAssignedToMe =
|
||||
!!assignedForReviewById && assignedForReviewById === actorId;
|
||||
const isLockedByMe = lockEnforced && lockedById === actorId;
|
||||
// Factor validation is open to all tenant experts (no individual ownership)
|
||||
const isFactorValidationOpen = isFactorValidationPending;
|
||||
|
||||
if (
|
||||
!isAvailable &&
|
||||
!isAssignedToMe &&
|
||||
!isLockedByMe &&
|
||||
!isFactorValidationOpen
|
||||
) {
|
||||
if (lockEnforced && lockedById && lockedById !== actorId) {
|
||||
throw new ForbiddenException(
|
||||
`This claim is locked by another expert: ${claim.workflow.lockedBy?.actorName}`,
|
||||
`This claim is locked by another expert: ${claim.workflow?.lockedBy?.actorName}`,
|
||||
);
|
||||
}
|
||||
if (assignedForReviewById && assignedForReviewById !== actorId) {
|
||||
throw new ForbiddenException(
|
||||
"This claim has been assigned to another expert.",
|
||||
);
|
||||
}
|
||||
throw new ForbiddenException(
|
||||
"You do not have permission to view this claim.",
|
||||
);
|
||||
}
|
||||
|
||||
const hasCapture = (data: any, key: string) =>
|
||||
|
||||
@@ -25,9 +25,8 @@ export function resolvePersistentReviewAssigneeId(
|
||||
if (fromField != null && String(fromField).length > 0) {
|
||||
return String(fromField);
|
||||
}
|
||||
const fromHistory = history?.find(
|
||||
(h) => h.type === assignedHistoryType,
|
||||
)?.actor?.actorId;
|
||||
const fromHistory = history?.find((h) => h.type === assignedHistoryType)
|
||||
?.actor?.actorId;
|
||||
if (fromHistory != null && String(fromHistory).length > 0) {
|
||||
return String(fromHistory);
|
||||
}
|
||||
@@ -43,8 +42,7 @@ export function buildExpertAssignConflictForOtherReviewer(
|
||||
message: string;
|
||||
lockedBy: { actorId: string; actorName?: string; lockedAt?: string };
|
||||
} {
|
||||
const assignee =
|
||||
workflow?.assignedForReviewBy ?? workflow?.lockedBy;
|
||||
const assignee = workflow?.assignedForReviewBy ?? workflow?.lockedBy;
|
||||
const lockedAt = workflow?.lockedAt;
|
||||
return {
|
||||
success: false,
|
||||
@@ -53,25 +51,28 @@ export function buildExpertAssignConflictForOtherReviewer(
|
||||
lockedBy: {
|
||||
actorId: assigneeId,
|
||||
actorName: assignee?.actorName,
|
||||
lockedAt: lockedAt
|
||||
? new Date(lockedAt as Date).toISOString()
|
||||
: undefined,
|
||||
lockedAt: lockedAt ? new Date(lockedAt as Date).toISOString() : undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Copy lock holder into `assignedForReviewBy` when persisting a TTL unlock. */
|
||||
export function blameWorkflowAssigneeToPersistOnLockExpiry(
|
||||
workflow: WorkflowAssigneeRef | undefined,
|
||||
): WorkflowAssigneeRef["assignedForReviewBy"] | undefined {
|
||||
if (workflow?.assignedForReviewBy) {
|
||||
return undefined;
|
||||
}
|
||||
return workflow?.lockedBy;
|
||||
workflow: any,
|
||||
request: any,
|
||||
) {
|
||||
const hasDecision = !!request?.expert?.decision;
|
||||
return hasDecision ? (workflow?.assignedForReviewBy ?? null) : null;
|
||||
}
|
||||
|
||||
export function claimWorkflowAssigneeToPersistOnLockExpiry(
|
||||
workflow: WorkflowAssigneeRef | undefined,
|
||||
): WorkflowAssigneeRef["assignedForReviewBy"] | undefined {
|
||||
return blameWorkflowAssigneeToPersistOnLockExpiry(workflow);
|
||||
workflow: any,
|
||||
claim: any,
|
||||
): typeof workflow.assignedForReviewBy | null {
|
||||
// If a decision or resend was already submitted, preserve ownership
|
||||
const hasDecision =
|
||||
!!claim?.evaluation?.damageExpertReply ||
|
||||
!!claim?.evaluation?.damageExpertReplyFinal ||
|
||||
!!claim?.evaluation?.damageExpertResend;
|
||||
|
||||
return hasDecision ? (workflow?.assignedForReviewBy ?? null) : null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user