@peopl-health/nexus 5.52.0-dev.7696 → 5.52.0-dev.7705
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -4,6 +4,7 @@ const { redactDirectIdentifiers } = require('../utils/sanitizerUtils');
|
|
|
4
4
|
const {
|
|
5
5
|
addCallLog,
|
|
6
6
|
addDraft,
|
|
7
|
+
cancelEscalationReview,
|
|
7
8
|
closeEscalationReview,
|
|
8
9
|
getEscalationReviewById,
|
|
9
10
|
handOffToDoctor,
|
|
@@ -135,6 +136,16 @@ const closeEscalationReviewController = async (req, res) => {
|
|
|
135
136
|
}
|
|
136
137
|
};
|
|
137
138
|
|
|
139
|
+
const cancelEscalationReviewController = async (req, res) => {
|
|
140
|
+
try {
|
|
141
|
+
const { triggeredBy, reason = null } = req.body || {};
|
|
142
|
+
const review = await cancelEscalationReview({ id: req.params.id, cancelledBy: triggeredBy, reason, requestId: req.requestId });
|
|
143
|
+
res.status(200).json({ success: true, review });
|
|
144
|
+
} catch (error) {
|
|
145
|
+
respondWithError(res, error, 'cancelling escalation review', { reviewId: req.params?.id });
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
138
149
|
const syncEscalationReviewCaseController = async (req, res) => {
|
|
139
150
|
try {
|
|
140
151
|
const { triggeredBy } = req.body || {};
|
|
@@ -152,6 +163,7 @@ const syncEscalationReviewCaseController = async (req, res) => {
|
|
|
152
163
|
module.exports = {
|
|
153
164
|
addEscalationReviewCallLogController,
|
|
154
165
|
addEscalationReviewDraftController,
|
|
166
|
+
cancelEscalationReviewController,
|
|
155
167
|
closeEscalationReviewController,
|
|
156
168
|
getEscalationReviewController,
|
|
157
169
|
handOffEscalationReviewController,
|
|
@@ -7,6 +7,7 @@ const { logger } = require('../utils/logger');
|
|
|
7
7
|
const CALL_OUTCOMES = ['contacted', 'no_answer', 'voicemail'];
|
|
8
8
|
const HANDOFF_MODES = ['sent_whatsapp', 'copied'];
|
|
9
9
|
const INTERRUPTED_STATUSES = ['handing_off', 'sending'];
|
|
10
|
+
const CANCELLABLE_STATUSES = ['open', 'documenting', 'draft_ready'];
|
|
10
11
|
const DOCTOR_DECISIONS = ['approved', 'edited_approved', 'needs_intervention', 'returned'];
|
|
11
12
|
const DECISION_SOURCES = ['platform', 'registered_by_navigator'];
|
|
12
13
|
|
|
@@ -83,6 +84,7 @@ const escalationReviewSchema = new mongoose.Schema({
|
|
|
83
84
|
'indications_queued',
|
|
84
85
|
'indications_sent',
|
|
85
86
|
'closed',
|
|
87
|
+
'cancelled',
|
|
86
88
|
],
|
|
87
89
|
default: 'open',
|
|
88
90
|
},
|
|
@@ -100,6 +102,7 @@ const escalationReviewSchema = new mongoose.Schema({
|
|
|
100
102
|
|
|
101
103
|
escalationReviewSchema.index({ patientCode: 1, createdAt: -1 });
|
|
102
104
|
escalationReviewSchema.index({ activeCaseKey: 1 }, { unique: true, sparse: true });
|
|
105
|
+
escalationReviewSchema.index({ patientCode: 1 }, { unique: true, partialFilterExpression: { closedAt: null } });
|
|
103
106
|
|
|
104
107
|
const getEscalationReview = () => {
|
|
105
108
|
const dbName = getModelDatabase('EscalationReview');
|
|
@@ -110,6 +113,7 @@ const getEscalationReview = () => {
|
|
|
110
113
|
|
|
111
114
|
module.exports = {
|
|
112
115
|
CALL_OUTCOMES,
|
|
116
|
+
CANCELLABLE_STATUSES,
|
|
113
117
|
DECISION_SOURCES,
|
|
114
118
|
DOCTOR_DECISIONS,
|
|
115
119
|
HANDOFF_MODES,
|
package/lib/routes/index.js
CHANGED
|
@@ -126,6 +126,7 @@ const escalationReviewRouteDefinitions = {
|
|
|
126
126
|
'POST /:id/decisions': 'recordEscalationReviewDecisionController',
|
|
127
127
|
'POST /:id/patient-deliveries': 'sendEscalationReviewIndicationsController',
|
|
128
128
|
'POST /:id/close': 'closeEscalationReviewController',
|
|
129
|
+
'POST /:id/cancel': 'cancelEscalationReviewController',
|
|
129
130
|
'POST /:id/case-sync': 'syncEscalationReviewCaseController'
|
|
130
131
|
};
|
|
131
132
|
|
|
@@ -285,6 +286,7 @@ const builtInControllers = {
|
|
|
285
286
|
recordEscalationReviewDecisionController: escalationReviewController.recordEscalationReviewDecisionController,
|
|
286
287
|
sendEscalationReviewIndicationsController: escalationReviewController.sendEscalationReviewIndicationsController,
|
|
287
288
|
closeEscalationReviewController: escalationReviewController.closeEscalationReviewController,
|
|
289
|
+
cancelEscalationReviewController: escalationReviewController.cancelEscalationReviewController,
|
|
288
290
|
syncEscalationReviewCaseController: escalationReviewController.syncEscalationReviewCaseController
|
|
289
291
|
};
|
|
290
292
|
|
|
@@ -6,6 +6,7 @@ const { logger } = require('../utils/logger');
|
|
|
6
6
|
const { PATIENT_CODE, redactDirectIdentifiers } = require('../utils/sanitizerUtils');
|
|
7
7
|
const {
|
|
8
8
|
CALL_OUTCOMES,
|
|
9
|
+
CANCELLABLE_STATUSES,
|
|
9
10
|
DECISION_SOURCES,
|
|
10
11
|
DOCTOR_DECISIONS,
|
|
11
12
|
HANDOFF_MODES,
|
|
@@ -248,6 +249,24 @@ const deliveryTransport = (message, result) => {
|
|
|
248
249
|
return result.deferred ? 'template_recovery' : 'text';
|
|
249
250
|
};
|
|
250
251
|
|
|
252
|
+
let indexesReady = new WeakMap();
|
|
253
|
+
|
|
254
|
+
function ensureActiveReviewIndex(EscalationReview) {
|
|
255
|
+
const pending = indexesReady.get(EscalationReview);
|
|
256
|
+
if (pending) return pending;
|
|
257
|
+
|
|
258
|
+
const building = EscalationReview.ensureIndexes().catch((error) => {
|
|
259
|
+
indexesReady.delete(EscalationReview);
|
|
260
|
+
if (error?.code !== DUPLICATE_KEY_ERROR) throw error;
|
|
261
|
+
logger.error('[EscalationReview] A uniqueness index could not be built', { error: redactedError(error) });
|
|
262
|
+
throw httpError(409, 'Escalation reviews cannot be opened: a uniqueness index could not be built. The server log names the index and the conflict; resolve it and retry.');
|
|
263
|
+
});
|
|
264
|
+
indexesReady.set(EscalationReview, building);
|
|
265
|
+
return building;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const _resetEscalationReviewIndexes = () => { indexesReady = new WeakMap(); };
|
|
269
|
+
|
|
251
270
|
async function openEscalationReview({ code, patientCaseRecordId = null, openedBy, requestId = null } = {}) {
|
|
252
271
|
const patientCode = patientCodeOf(code);
|
|
253
272
|
const operator = requiredText(openedBy, 'openedBy');
|
|
@@ -264,14 +283,14 @@ async function openEscalationReview({ code, patientCaseRecordId = null, openedBy
|
|
|
264
283
|
}
|
|
265
284
|
|
|
266
285
|
const EscalationReview = getEscalationReview();
|
|
267
|
-
await EscalationReview
|
|
286
|
+
await ensureActiveReviewIndex(EscalationReview);
|
|
268
287
|
let review;
|
|
269
288
|
try {
|
|
270
289
|
review = (await EscalationReview.create(fields)).toObject();
|
|
271
290
|
} catch (error) {
|
|
272
|
-
if (error?.code !== DUPLICATE_KEY_ERROR
|
|
273
|
-
const existing = await EscalationReview.findOne({
|
|
274
|
-
if (!existing) throw httpError(409, '
|
|
291
|
+
if (error?.code !== DUPLICATE_KEY_ERROR) throw error;
|
|
292
|
+
const existing = await EscalationReview.findOne({ patientCode, closedAt: null }).lean();
|
|
293
|
+
if (!existing) throw httpError(409, 'A closed review still holds this patient case; try again in a moment');
|
|
275
294
|
return { review: existing, created: false };
|
|
276
295
|
}
|
|
277
296
|
await audit(review, operator, requestId, { action: 'opened', patientCaseRecordId });
|
|
@@ -524,6 +543,17 @@ async function closeEscalationReview({ id, closedBy, deliveryConfirmed = false,
|
|
|
524
543
|
return syncCase(review, operator, requestId);
|
|
525
544
|
}
|
|
526
545
|
|
|
546
|
+
async function cancelEscalationReview({ id, cancelledBy, reason = null, requestId = null } = {}) {
|
|
547
|
+
const reviewId = reviewIdOf(id);
|
|
548
|
+
const operator = requiredText(cancelledBy, 'cancelledBy');
|
|
549
|
+
const review = await updateReview(reviewId, CANCELLABLE_STATUSES, {
|
|
550
|
+
$set: { status: 'cancelled', closedBy: operator, closedAt: new Date() },
|
|
551
|
+
$unset: { activeCaseKey: '' },
|
|
552
|
+
});
|
|
553
|
+
await audit(review, operator, requestId, { action: 'cancelled', reason: optionalText(reason) });
|
|
554
|
+
return review;
|
|
555
|
+
}
|
|
556
|
+
|
|
527
557
|
async function syncEscalationReviewCase({ id, syncedBy, requestId = null } = {}) {
|
|
528
558
|
const reviewId = reviewIdOf(id);
|
|
529
559
|
const operator = requiredText(syncedBy, 'syncedBy');
|
|
@@ -531,8 +561,10 @@ async function syncEscalationReviewCase({ id, syncedBy, requestId = null } = {})
|
|
|
531
561
|
}
|
|
532
562
|
|
|
533
563
|
module.exports = {
|
|
564
|
+
_resetEscalationReviewIndexes,
|
|
534
565
|
addCallLog,
|
|
535
566
|
addDraft,
|
|
567
|
+
cancelEscalationReview,
|
|
536
568
|
closeEscalationReview,
|
|
537
569
|
getEscalationReviewById,
|
|
538
570
|
handOffToDoctor,
|