forked from Yara724/api
YARA-951 case-4
This commit is contained in:
@@ -66,6 +66,13 @@ export class ClaimWorkflow {
|
|||||||
|
|
||||||
@Prop({ type: ClaimPreLockQueueSnapshotSchema })
|
@Prop({ type: ClaimPreLockQueueSnapshotSchema })
|
||||||
preLockQueueSnapshot?: ClaimPreLockQueueSnapshot;
|
preLockQueueSnapshot?: ClaimPreLockQueueSnapshot;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First damage expert who called review assign; kept after the 15m lock expires
|
||||||
|
* so no other expert can take the case while it remains in the expert queue.
|
||||||
|
*/
|
||||||
|
@Prop({ type: ClaimActorLockSchema })
|
||||||
|
assignedForReviewBy?: ClaimActorLock;
|
||||||
}
|
}
|
||||||
export const ClaimWorkflowSchema = SchemaFactory.createForClass(ClaimWorkflow);
|
export const ClaimWorkflowSchema = SchemaFactory.createForClass(ClaimWorkflow);
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ import {
|
|||||||
ExpertFileAssignResultDto,
|
ExpertFileAssignResultDto,
|
||||||
ExpertFileAssignStatus,
|
ExpertFileAssignStatus,
|
||||||
} from "src/common/dto/expert-file-assign-result.dto";
|
} from "src/common/dto/expert-file-assign-result.dto";
|
||||||
|
import {
|
||||||
|
BLAME_REVIEW_ASSIGNED_HISTORY_TYPE,
|
||||||
|
blameWorkflowAssigneeToPersistOnLockExpiry,
|
||||||
|
buildExpertAssignConflictForOtherReviewer,
|
||||||
|
resolvePersistentReviewAssigneeId,
|
||||||
|
} from "src/helpers/expert-workflow-review-assignee";
|
||||||
import { RequestManagementDbService } from "src/request-management/entities/db-service/request-management.db.service";
|
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 { BlameRequestDbService } from "src/request-management/entities/db-service/blame-request.db.service";
|
||||||
import {
|
import {
|
||||||
@@ -541,10 +547,18 @@ export class ExpertBlameService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const assigneeToPersist = blameWorkflowAssigneeToPersistOnLockExpiry(
|
||||||
|
request.workflow,
|
||||||
|
);
|
||||||
|
const expireSet: Record<string, unknown> = { "workflow.locked": false };
|
||||||
|
if (assigneeToPersist) {
|
||||||
|
expireSet["workflow.assignedForReviewBy"] = assigneeToPersist;
|
||||||
|
}
|
||||||
|
|
||||||
const cleared = await this.blameRequestDbService.findOneAndUpdate(
|
const cleared = await this.blameRequestDbService.findOneAndUpdate(
|
||||||
filter as any,
|
filter as any,
|
||||||
{
|
{
|
||||||
$set: { "workflow.locked": false },
|
$set: expireSet,
|
||||||
$unset: {
|
$unset: {
|
||||||
"workflow.lockedAt": "",
|
"workflow.lockedAt": "",
|
||||||
"workflow.expiredAt": "",
|
"workflow.expiredAt": "",
|
||||||
@@ -1056,24 +1070,29 @@ export class ExpertBlameService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const expertOid = new Types.ObjectId(actor.sub);
|
const expertOid = new Types.ObjectId(actor.sub);
|
||||||
|
const reviewAssigneeId = resolvePersistentReviewAssigneeId(
|
||||||
|
request.workflow,
|
||||||
|
request.history,
|
||||||
|
BLAME_REVIEW_ASSIGNED_HISTORY_TYPE,
|
||||||
|
);
|
||||||
|
if (reviewAssigneeId && reviewAssigneeId !== actor.sub) {
|
||||||
|
throw new ConflictException(
|
||||||
|
buildExpertAssignConflictForOtherReviewer(
|
||||||
|
reviewAssigneeId,
|
||||||
|
request.workflow,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const lockedById = String(request.workflow?.lockedBy?.actorId ?? "");
|
const lockedById = String(request.workflow?.lockedBy?.actorId ?? "");
|
||||||
const lockEnforced =
|
const lockEnforced =
|
||||||
request.workflow?.locked &&
|
request.workflow?.locked &&
|
||||||
this.isBlameV2WorkflowLockCurrentlyEnforced(request);
|
this.isBlameV2WorkflowLockCurrentlyEnforced(request);
|
||||||
|
|
||||||
if (lockEnforced && lockedById && lockedById !== actor.sub) {
|
if (lockEnforced && lockedById && lockedById !== actor.sub) {
|
||||||
throw new ConflictException({
|
throw new ConflictException(
|
||||||
success: false,
|
buildExpertAssignConflictForOtherReviewer(lockedById, request.workflow),
|
||||||
status: "locked" satisfies ExpertFileAssignStatus,
|
);
|
||||||
message: "Request is already being processed by another expert",
|
|
||||||
lockedBy: {
|
|
||||||
actorId: lockedById,
|
|
||||||
actorName: request.workflow?.lockedBy?.actorName,
|
|
||||||
lockedAt: request.workflow?.lockedAt
|
|
||||||
? new Date(request.workflow.lockedAt as Date).toISOString()
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lockEnforced && lockedById === actor.sub) {
|
if (lockEnforced && lockedById === actor.sub) {
|
||||||
@@ -1089,17 +1108,21 @@ export class ExpertBlameService {
|
|||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const lockSnapshot = await this.snapshotFieldExpert(actor.sub);
|
const lockSnapshot = await this.snapshotFieldExpert(actor.sub);
|
||||||
const lockUpdate = {
|
const lockedByPayload = {
|
||||||
|
actorId: expertOid,
|
||||||
|
actorName: actor.fullName || "Unknown Expert",
|
||||||
|
actorRole: "expert" as const,
|
||||||
|
...(lockSnapshot && { expertProfileSnapshot: lockSnapshot }),
|
||||||
|
};
|
||||||
|
const lockUpdate: {
|
||||||
|
$set: Record<string, unknown>;
|
||||||
|
$push: Record<string, unknown>;
|
||||||
|
} = {
|
||||||
$set: {
|
$set: {
|
||||||
"workflow.locked": true,
|
"workflow.locked": true,
|
||||||
"workflow.lockedAt": now,
|
"workflow.lockedAt": now,
|
||||||
"workflow.expiredAt": new Date(now.getTime() + this.blameV2LockTtlMs),
|
"workflow.expiredAt": new Date(now.getTime() + this.blameV2LockTtlMs),
|
||||||
"workflow.lockedBy": {
|
"workflow.lockedBy": lockedByPayload,
|
||||||
actorId: expertOid,
|
|
||||||
actorName: actor.fullName || "Unknown Expert",
|
|
||||||
actorRole: "expert",
|
|
||||||
...(lockSnapshot && { expertProfileSnapshot: lockSnapshot }),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
$push: {
|
$push: {
|
||||||
history: {
|
history: {
|
||||||
@@ -1114,16 +1137,30 @@ export class ExpertBlameService {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
if (!request.workflow?.assignedForReviewBy) {
|
||||||
|
lockUpdate.$set["workflow.assignedForReviewBy"] = lockedByPayload;
|
||||||
|
}
|
||||||
|
|
||||||
const assignFilter: Record<string, unknown> = {
|
const assignFilter: Record<string, unknown> = {
|
||||||
_id: new Types.ObjectId(requestId),
|
_id: new Types.ObjectId(requestId),
|
||||||
status: CaseStatus.WAITING_FOR_EXPERT,
|
status: CaseStatus.WAITING_FOR_EXPERT,
|
||||||
blameStatus: BlameStatus.DISAGREEMENT,
|
blameStatus: BlameStatus.DISAGREEMENT,
|
||||||
$or: [
|
$and: [
|
||||||
{ "workflow.locked": { $ne: true } },
|
{
|
||||||
{ "workflow.locked": false },
|
$or: [
|
||||||
{ "workflow.expiredAt": { $lte: now } },
|
{ "workflow.locked": { $ne: true } },
|
||||||
{ "workflow.lockedBy.actorId": expertOid },
|
{ "workflow.locked": false },
|
||||||
|
{ "workflow.expiredAt": { $lte: now } },
|
||||||
|
{ "workflow.lockedBy.actorId": expertOid },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$or: [
|
||||||
|
{ "workflow.assignedForReviewBy": { $exists: false } },
|
||||||
|
{ "workflow.assignedForReviewBy": null },
|
||||||
|
{ "workflow.assignedForReviewBy.actorId": expertOid },
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1140,6 +1177,19 @@ export class ExpertBlameService {
|
|||||||
if (!updated) {
|
if (!updated) {
|
||||||
await this.expireBlameCaseWorkflowLockV2IfStale(requestId);
|
await this.expireBlameCaseWorkflowLockV2IfStale(requestId);
|
||||||
const latest = await this.blameRequestDbService.findById(requestId);
|
const latest = await this.blameRequestDbService.findById(requestId);
|
||||||
|
const otherAssigneeId = resolvePersistentReviewAssigneeId(
|
||||||
|
latest?.workflow,
|
||||||
|
latest?.history,
|
||||||
|
BLAME_REVIEW_ASSIGNED_HISTORY_TYPE,
|
||||||
|
);
|
||||||
|
if (otherAssigneeId && otherAssigneeId !== actor.sub) {
|
||||||
|
throw new ConflictException(
|
||||||
|
buildExpertAssignConflictForOtherReviewer(
|
||||||
|
otherAssigneeId,
|
||||||
|
latest?.workflow,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
const otherId = String(latest?.workflow?.lockedBy?.actorId ?? "");
|
const otherId = String(latest?.workflow?.lockedBy?.actorId ?? "");
|
||||||
if (
|
if (
|
||||||
latest?.workflow?.locked &&
|
latest?.workflow?.locked &&
|
||||||
@@ -1147,18 +1197,9 @@ export class ExpertBlameService {
|
|||||||
otherId &&
|
otherId &&
|
||||||
otherId !== actor.sub
|
otherId !== actor.sub
|
||||||
) {
|
) {
|
||||||
throw new ConflictException({
|
throw new ConflictException(
|
||||||
success: false,
|
buildExpertAssignConflictForOtherReviewer(otherId, latest?.workflow),
|
||||||
status: "locked" satisfies ExpertFileAssignStatus,
|
);
|
||||||
message: "Request is already being processed by another expert",
|
|
||||||
lockedBy: {
|
|
||||||
actorId: otherId,
|
|
||||||
actorName: latest.workflow?.lockedBy?.actorName,
|
|
||||||
lockedAt: latest.workflow?.lockedAt
|
|
||||||
? new Date(latest.workflow.lockedAt as Date).toISOString()
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
throw new BadRequestException({
|
throw new BadRequestException({
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ import {
|
|||||||
ExpertFileAssignResultDto,
|
ExpertFileAssignResultDto,
|
||||||
ExpertFileAssignStatus,
|
ExpertFileAssignStatus,
|
||||||
} from "src/common/dto/expert-file-assign-result.dto";
|
} from "src/common/dto/expert-file-assign-result.dto";
|
||||||
|
import {
|
||||||
|
CLAIM_REVIEW_ASSIGNED_HISTORY_TYPE,
|
||||||
|
claimWorkflowAssigneeToPersistOnLockExpiry,
|
||||||
|
buildExpertAssignConflictForOtherReviewer,
|
||||||
|
resolvePersistentReviewAssigneeId,
|
||||||
|
} from "src/helpers/expert-workflow-review-assignee";
|
||||||
import { ClaimPerIdRs } from "./dto/claim-list-perId-rs.dto";
|
import { ClaimPerIdRs } from "./dto/claim-list-perId-rs.dto";
|
||||||
import { ClaimListDtoRs } from "./dto/claim-list-rs.dto";
|
import { ClaimListDtoRs } from "./dto/claim-list-rs.dto";
|
||||||
import { ClaimCaseDbService } from "src/claim-request-management/entites/db-service/claim-case.db.service";
|
import { ClaimCaseDbService } from "src/claim-request-management/entites/db-service/claim-case.db.service";
|
||||||
@@ -2375,24 +2381,30 @@ export class ExpertClaimService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const expertOid = new Types.ObjectId(actor.sub);
|
||||||
|
const reviewAssigneeId = resolvePersistentReviewAssigneeId(
|
||||||
|
claim.workflow,
|
||||||
|
claim.history,
|
||||||
|
CLAIM_REVIEW_ASSIGNED_HISTORY_TYPE,
|
||||||
|
);
|
||||||
|
if (reviewAssigneeId && reviewAssigneeId !== actor.sub) {
|
||||||
|
throw new ConflictException(
|
||||||
|
buildExpertAssignConflictForOtherReviewer(
|
||||||
|
reviewAssigneeId,
|
||||||
|
claim.workflow,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const lockedById = String(claim.workflow?.lockedBy?.actorId ?? "");
|
const lockedById = String(claim.workflow?.lockedBy?.actorId ?? "");
|
||||||
const lockEnforced =
|
const lockEnforced =
|
||||||
claim.workflow?.locked &&
|
claim.workflow?.locked &&
|
||||||
this.isClaimV2WorkflowLockCurrentlyEnforced(claim);
|
this.isClaimV2WorkflowLockCurrentlyEnforced(claim);
|
||||||
|
|
||||||
if (lockEnforced && lockedById && lockedById !== actor.sub) {
|
if (lockEnforced && lockedById && lockedById !== actor.sub) {
|
||||||
throw new ConflictException({
|
throw new ConflictException(
|
||||||
success: false,
|
buildExpertAssignConflictForOtherReviewer(lockedById, claim.workflow),
|
||||||
status: "locked" satisfies ExpertFileAssignStatus,
|
);
|
||||||
message: "Request is already being processed by another expert",
|
|
||||||
lockedBy: {
|
|
||||||
actorId: lockedById,
|
|
||||||
actorName: claim.workflow?.lockedBy?.actorName,
|
|
||||||
lockedAt: claim.workflow?.lockedAt
|
|
||||||
? new Date(claim.workflow.lockedAt as Date).toISOString()
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lockEnforced && lockedById === actor.sub) {
|
if (lockEnforced && lockedById === actor.sub) {
|
||||||
@@ -2406,21 +2418,22 @@ export class ExpertClaimService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const expertOid = new Types.ObjectId(actor.sub);
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const lockSnapshot = await this.snapshotDamageExpert(actor.sub);
|
const lockSnapshot = await this.snapshotDamageExpert(actor.sub);
|
||||||
const expiredAt = new Date(now.getTime() + this.claimV2WorkflowLockTtlMs);
|
const expiredAt = new Date(now.getTime() + this.claimV2WorkflowLockTtlMs);
|
||||||
|
|
||||||
|
const lockedByPayload = {
|
||||||
|
actorId: expertOid,
|
||||||
|
actorName: actor.fullName,
|
||||||
|
actorRole: "damage_expert" as const,
|
||||||
|
...(lockSnapshot && { expertProfileSnapshot: lockSnapshot }),
|
||||||
|
};
|
||||||
|
|
||||||
const baseLockUpdate: Record<string, unknown> = {
|
const baseLockUpdate: Record<string, unknown> = {
|
||||||
"workflow.locked": true,
|
"workflow.locked": true,
|
||||||
"workflow.lockedAt": now,
|
"workflow.lockedAt": now,
|
||||||
"workflow.expiredAt": expiredAt,
|
"workflow.expiredAt": expiredAt,
|
||||||
"workflow.lockedBy": {
|
"workflow.lockedBy": lockedByPayload,
|
||||||
actorId: expertOid,
|
|
||||||
actorName: actor.fullName,
|
|
||||||
actorRole: "damage_expert",
|
|
||||||
...(lockSnapshot && { expertProfileSnapshot: lockSnapshot }),
|
|
||||||
},
|
|
||||||
"workflow.preLockQueueSnapshot": {
|
"workflow.preLockQueueSnapshot": {
|
||||||
claimStatus: claim.claimStatus,
|
claimStatus: claim.claimStatus,
|
||||||
currentStep: this.normalizeClaimWorkflowStepForSnapshot(
|
currentStep: this.normalizeClaimWorkflowStepForSnapshot(
|
||||||
@@ -2455,6 +2468,10 @@ export class ExpertClaimService {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!claim.workflow?.assignedForReviewBy) {
|
||||||
|
baseLockUpdate["workflow.assignedForReviewBy"] = lockedByPayload;
|
||||||
|
}
|
||||||
|
|
||||||
const lockUpdate: Record<string, unknown> = isFactorValidationLock
|
const lockUpdate: Record<string, unknown> = isFactorValidationLock
|
||||||
? baseLockUpdate
|
? baseLockUpdate
|
||||||
: {
|
: {
|
||||||
@@ -2466,11 +2483,22 @@ export class ExpertClaimService {
|
|||||||
|
|
||||||
const assignFilter: Record<string, unknown> = {
|
const assignFilter: Record<string, unknown> = {
|
||||||
_id: new Types.ObjectId(claimRequestId),
|
_id: new Types.ObjectId(claimRequestId),
|
||||||
$or: [
|
$and: [
|
||||||
{ "workflow.locked": { $ne: true } },
|
{
|
||||||
{ "workflow.locked": false },
|
$or: [
|
||||||
{ "workflow.expiredAt": { $lte: now } },
|
{ "workflow.locked": { $ne: true } },
|
||||||
{ "workflow.lockedBy.actorId": expertOid },
|
{ "workflow.locked": false },
|
||||||
|
{ "workflow.expiredAt": { $lte: now } },
|
||||||
|
{ "workflow.lockedBy.actorId": expertOid },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$or: [
|
||||||
|
{ "workflow.assignedForReviewBy": { $exists: false } },
|
||||||
|
{ "workflow.assignedForReviewBy": null },
|
||||||
|
{ "workflow.assignedForReviewBy.actorId": expertOid },
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2497,6 +2525,19 @@ export class ExpertClaimService {
|
|||||||
if (!updated) {
|
if (!updated) {
|
||||||
await this.expireClaimWorkflowLockV2IfStale(claimRequestId);
|
await this.expireClaimWorkflowLockV2IfStale(claimRequestId);
|
||||||
const latest = await this.claimCaseDbService.findById(claimRequestId);
|
const latest = await this.claimCaseDbService.findById(claimRequestId);
|
||||||
|
const otherAssigneeId = resolvePersistentReviewAssigneeId(
|
||||||
|
latest?.workflow,
|
||||||
|
latest?.history,
|
||||||
|
CLAIM_REVIEW_ASSIGNED_HISTORY_TYPE,
|
||||||
|
);
|
||||||
|
if (otherAssigneeId && otherAssigneeId !== actor.sub) {
|
||||||
|
throw new ConflictException(
|
||||||
|
buildExpertAssignConflictForOtherReviewer(
|
||||||
|
otherAssigneeId,
|
||||||
|
latest?.workflow,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
const otherId = String(latest?.workflow?.lockedBy?.actorId ?? "");
|
const otherId = String(latest?.workflow?.lockedBy?.actorId ?? "");
|
||||||
if (
|
if (
|
||||||
latest?.workflow?.locked &&
|
latest?.workflow?.locked &&
|
||||||
@@ -2504,18 +2545,9 @@ export class ExpertClaimService {
|
|||||||
otherId &&
|
otherId &&
|
||||||
otherId !== actor.sub
|
otherId !== actor.sub
|
||||||
) {
|
) {
|
||||||
throw new ConflictException({
|
throw new ConflictException(
|
||||||
success: false,
|
buildExpertAssignConflictForOtherReviewer(otherId, latest?.workflow),
|
||||||
status: "locked" satisfies ExpertFileAssignStatus,
|
);
|
||||||
message: "Request is already being processed by another expert",
|
|
||||||
lockedBy: {
|
|
||||||
actorId: otherId,
|
|
||||||
actorName: latest.workflow?.lockedBy?.actorName,
|
|
||||||
lockedAt: latest.workflow?.lockedAt
|
|
||||||
? new Date(latest.workflow.lockedAt as Date).toISOString()
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
throw new BadRequestException({
|
throw new BadRequestException({
|
||||||
success: false,
|
success: false,
|
||||||
@@ -3657,10 +3689,18 @@ export class ExpertClaimService {
|
|||||||
"workflow.preLockQueueSnapshot": "",
|
"workflow.preLockQueueSnapshot": "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const assigneeToPersist = claimWorkflowAssigneeToPersistOnLockExpiry(
|
||||||
|
claim.workflow,
|
||||||
|
);
|
||||||
|
const expireLockSet: Record<string, unknown> = { "workflow.locked": false };
|
||||||
|
if (assigneeToPersist) {
|
||||||
|
expireLockSet["workflow.assignedForReviewBy"] = assigneeToPersist;
|
||||||
|
}
|
||||||
|
|
||||||
const update = needsQueueRestore
|
const update = needsQueueRestore
|
||||||
? {
|
? {
|
||||||
$set: {
|
$set: {
|
||||||
"workflow.locked": false,
|
...expireLockSet,
|
||||||
status: ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT,
|
status: ClaimCaseStatus.WAITING_FOR_DAMAGE_EXPERT,
|
||||||
claimStatus: restoreClaimStatus,
|
claimStatus: restoreClaimStatus,
|
||||||
"workflow.currentStep": restoreCurrent,
|
"workflow.currentStep": restoreCurrent,
|
||||||
@@ -3669,7 +3709,7 @@ export class ExpertClaimService {
|
|||||||
$unset: lockFieldsUnset,
|
$unset: lockFieldsUnset,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
$set: { "workflow.locked": false },
|
$set: expireLockSet,
|
||||||
$unset: lockFieldsUnset,
|
$unset: lockFieldsUnset,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
77
src/helpers/expert-workflow-review-assignee.ts
Normal file
77
src/helpers/expert-workflow-review-assignee.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import type { ExpertFileAssignStatus } from "src/common/dto/expert-file-assign-result.dto";
|
||||||
|
|
||||||
|
export const BLAME_REVIEW_ASSIGNED_HISTORY_TYPE = "BLAME_ASSIGNED";
|
||||||
|
export const CLAIM_REVIEW_ASSIGNED_HISTORY_TYPE = "CLAIM_ASSIGNED";
|
||||||
|
|
||||||
|
type WorkflowAssigneeRef = {
|
||||||
|
assignedForReviewBy?: { actorId?: unknown; actorName?: string };
|
||||||
|
lockedBy?: { actorId?: unknown; actorName?: string };
|
||||||
|
lockedAt?: Date | string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type HistoryAssigneeRef = {
|
||||||
|
type?: string;
|
||||||
|
actor?: { actorId?: unknown };
|
||||||
|
timestamp?: Date | string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Expert who first assigned via the review assign endpoint (survives lock TTL expiry). */
|
||||||
|
export function resolvePersistentReviewAssigneeId(
|
||||||
|
workflow: WorkflowAssigneeRef | undefined,
|
||||||
|
history: HistoryAssigneeRef[] | undefined,
|
||||||
|
assignedHistoryType: string,
|
||||||
|
): string {
|
||||||
|
const fromField = workflow?.assignedForReviewBy?.actorId;
|
||||||
|
if (fromField != null && String(fromField).length > 0) {
|
||||||
|
return String(fromField);
|
||||||
|
}
|
||||||
|
const fromHistory = history?.find(
|
||||||
|
(h) => h.type === assignedHistoryType,
|
||||||
|
)?.actor?.actorId;
|
||||||
|
if (fromHistory != null && String(fromHistory).length > 0) {
|
||||||
|
return String(fromHistory);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildExpertAssignConflictForOtherReviewer(
|
||||||
|
assigneeId: string,
|
||||||
|
workflow?: WorkflowAssigneeRef,
|
||||||
|
): {
|
||||||
|
success: false;
|
||||||
|
status: ExpertFileAssignStatus;
|
||||||
|
message: string;
|
||||||
|
lockedBy: { actorId: string; actorName?: string; lockedAt?: string };
|
||||||
|
} {
|
||||||
|
const assignee =
|
||||||
|
workflow?.assignedForReviewBy ?? workflow?.lockedBy;
|
||||||
|
const lockedAt = workflow?.lockedAt;
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
status: "locked",
|
||||||
|
message: "Request is already being answered by another expert",
|
||||||
|
lockedBy: {
|
||||||
|
actorId: assigneeId,
|
||||||
|
actorName: assignee?.actorName,
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function claimWorkflowAssigneeToPersistOnLockExpiry(
|
||||||
|
workflow: WorkflowAssigneeRef | undefined,
|
||||||
|
): WorkflowAssigneeRef["assignedForReviewBy"] | undefined {
|
||||||
|
return blameWorkflowAssigneeToPersistOnLockExpiry(workflow);
|
||||||
|
}
|
||||||
@@ -48,5 +48,12 @@ export class Workflow {
|
|||||||
|
|
||||||
@Prop({ type: ActorLockSchema })
|
@Prop({ type: ActorLockSchema })
|
||||||
lockedBy?: ActorLock;
|
lockedBy?: ActorLock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* First expert who called review assign; kept after the 15m workflow lock expires
|
||||||
|
* so no other expert can take the case while it remains in the expert queue.
|
||||||
|
*/
|
||||||
|
@Prop({ type: ActorLockSchema })
|
||||||
|
assignedForReviewBy?: ActorLock;
|
||||||
}
|
}
|
||||||
export const WorkflowSchema = SchemaFactory.createForClass(Workflow);
|
export const WorkflowSchema = SchemaFactory.createForClass(Workflow);
|
||||||
Reference in New Issue
Block a user