fix: 问题修复
This commit is contained in:
parent
42d6a6d71b
commit
b81a6a85ca
@ -17,6 +17,10 @@ const TASK_STATUS = {
|
|||||||
CANCELLED: "CANCELLED",
|
CANCELLED: "CANCELLED",
|
||||||
FAILED: "FAILED",
|
FAILED: "FAILED",
|
||||||
};
|
};
|
||||||
|
const ERROR_SOURCES = {
|
||||||
|
SYSTEM: "SYSTEM",
|
||||||
|
HIS: "HIS",
|
||||||
|
};
|
||||||
const LEASE_MS = 2 * 60 * 1000;
|
const LEASE_MS = 2 * 60 * 1000;
|
||||||
const POLL_INTERVAL_MS = 1000;
|
const POLL_INTERVAL_MS = 1000;
|
||||||
const RECONCILE_INTERVAL_MS = 30 * 1000;
|
const RECONCILE_INTERVAL_MS = 30 * 1000;
|
||||||
@ -31,10 +35,11 @@ let pollIsRunning = false;
|
|||||||
let reconcileIsRunning = false;
|
let reconcileIsRunning = false;
|
||||||
|
|
||||||
class AutomationError extends Error {
|
class AutomationError extends Error {
|
||||||
constructor(message, { retryable = false } = {}) {
|
constructor(message, { retryable = false, source = ERROR_SOURCES.SYSTEM } = {}) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = "AutomationError";
|
this.name = "AutomationError";
|
||||||
this.retryable = retryable;
|
this.retryable = retryable;
|
||||||
|
this.source = source === ERROR_SOURCES.HIS ? ERROR_SOURCES.HIS : ERROR_SOURCES.SYSTEM;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,12 +328,30 @@ function isSingleAttemptTask(type) {
|
|||||||
return [AUTOMATION_TYPES.OPEN_RX, AUTOMATION_TYPES.PASS_RX].includes(type);
|
return [AUTOMATION_TYPES.OPEN_RX, AUTOMATION_TYPES.PASS_RX].includes(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFailureNotificationText(task, error) {
|
function isHisUploadFailure(message) {
|
||||||
const action =
|
return typeof message === "string" && /^\s*\[his\]/i.test(message);
|
||||||
task.type === AUTOMATION_TYPES.OPEN_RX ? "自动开方" : "自动审方";
|
}
|
||||||
const reason = error && error.message ? error.message : "未知错误";
|
|
||||||
// return `${action}失败:${reason}`;
|
function cleanHisUploadFailureMessage(message) {
|
||||||
return reason
|
if (typeof message !== "string") return "";
|
||||||
|
return message
|
||||||
|
.replace(/^\s*\[his\]\s*/i, "")
|
||||||
|
.replace(/^处方上传(?:失败|错误)\s*[::]\s*/, "")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFailureNotificationExt(error) {
|
||||||
|
const errorSource =
|
||||||
|
error && error.source === ERROR_SOURCES.HIS
|
||||||
|
? ERROR_SOURCES.HIS
|
||||||
|
: ERROR_SOURCES.SYSTEM;
|
||||||
|
const rawReason =
|
||||||
|
error && typeof error.message === "string" ? error.message.trim() : "";
|
||||||
|
const reason =
|
||||||
|
errorSource === ERROR_SOURCES.HIS
|
||||||
|
? `HIS上传错误:${cleanHisUploadFailureMessage(rawReason) || "未知错误"}`
|
||||||
|
: `系统错误:${rawReason || "未知错误"}`;
|
||||||
|
return { errorSource, reason };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendFailureNotification(db, task, error) {
|
async function sendFailureNotification(db, task, error) {
|
||||||
@ -373,8 +396,7 @@ async function sendFailureNotification(db, task, error) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const notification = getFailureNotificationText(task, error);
|
const ext = getFailureNotificationExt(error);
|
||||||
const ext = { reason: notification };
|
|
||||||
try {
|
try {
|
||||||
const result = await tencentIM(
|
const result = await tencentIM(
|
||||||
{
|
{
|
||||||
@ -808,11 +830,17 @@ async function handleAutoPassRx(db, task) {
|
|||||||
db
|
db
|
||||||
);
|
);
|
||||||
if (!result || !result.success) {
|
if (!result || !result.success) {
|
||||||
const detail =
|
const failure =
|
||||||
Array.isArray(result?.failList) && result.failList[0]
|
Array.isArray(result?.failList) && result.failList[0]
|
||||||
? result.failList[0].message
|
? result.failList[0]
|
||||||
: result?.message;
|
: null;
|
||||||
throw new AutomationError(detail || "审方接口返回失败", { retryable: true });
|
const detail = failure ? failure.message : result?.message;
|
||||||
|
throw new AutomationError(detail || "审方接口返回失败", {
|
||||||
|
retryable: true,
|
||||||
|
source: isHisUploadFailure(detail)
|
||||||
|
? ERROR_SOURCES.HIS
|
||||||
|
: ERROR_SOURCES.SYSTEM,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return { orderId: task.orderId, rxId: task.rxId };
|
return { orderId: task.orderId, rxId: task.rxId };
|
||||||
}
|
}
|
||||||
@ -1011,6 +1039,7 @@ async function reconcileAutomationTasks(db = workerDb) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function start(db) {
|
async function start(db) {
|
||||||
|
return
|
||||||
if (pollTimer || reconcileTimer) return;
|
if (pollTimer || reconcileTimer) return;
|
||||||
workerDb = db;
|
workerDb = db;
|
||||||
await ensureIndexes(db);
|
await ensureIndexes(db);
|
||||||
@ -1040,7 +1069,9 @@ module.exports = {
|
|||||||
COLLECTION,
|
COLLECTION,
|
||||||
TASK_STATUS,
|
TASK_STATUS,
|
||||||
AUTOMATION_TYPES,
|
AUTOMATION_TYPES,
|
||||||
|
ERROR_SOURCES,
|
||||||
AutomationError,
|
AutomationError,
|
||||||
|
getFailureNotificationExt,
|
||||||
calculateDueAt,
|
calculateDueAt,
|
||||||
enqueueTask,
|
enqueueTask,
|
||||||
scheduleAutoAccept,
|
scheduleAutoAccept,
|
||||||
|
|||||||
@ -1,14 +1,19 @@
|
|||||||
const { ObjectId } = require("mongodb");
|
const { ObjectId } = require("mongodb");
|
||||||
jest.mock("../tencent-im", () => jest.fn());
|
jest.mock("../tencent-im", () => jest.fn());
|
||||||
|
jest.mock("../diagnostic-record", () => jest.fn());
|
||||||
const tencentIM = require("../tencent-im");
|
const tencentIM = require("../tencent-im");
|
||||||
|
const diagnosticRecord = require("../diagnostic-record");
|
||||||
const {
|
const {
|
||||||
AUTOMATION_TYPES,
|
AUTOMATION_TYPES,
|
||||||
COLLECTION,
|
COLLECTION,
|
||||||
|
ERROR_SOURCES,
|
||||||
TASK_STATUS,
|
TASK_STATUS,
|
||||||
|
AutomationError,
|
||||||
buildAutoPrescriptionParams,
|
buildAutoPrescriptionParams,
|
||||||
calculateDueAt,
|
calculateDueAt,
|
||||||
claimNextTask,
|
claimNextTask,
|
||||||
enqueueTask,
|
enqueueTask,
|
||||||
|
getFailureNotificationExt,
|
||||||
processClaimedTask,
|
processClaimedTask,
|
||||||
reconcileAutomationTasks,
|
reconcileAutomationTasks,
|
||||||
} = require("./index");
|
} = require("./index");
|
||||||
@ -339,9 +344,12 @@ describe("automation task scheduling", () => {
|
|||||||
msgBody: [
|
msgBody: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
MsgContent: expect.objectContaining({
|
MsgContent: expect.objectContaining({
|
||||||
Data: "AUTOMATIONFAIL",
|
Data: "AUTORXFAIL",
|
||||||
Desc: "notification",
|
Desc: "notification",
|
||||||
Ext: "自动开方失败:开方医生不存在",
|
Ext: JSON.stringify({
|
||||||
|
errorSource: ERROR_SOURCES.SYSTEM,
|
||||||
|
reason: "系统错误:开方医生不存在",
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
@ -417,9 +425,12 @@ describe("automation task scheduling", () => {
|
|||||||
msgBody: [
|
msgBody: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
MsgContent: expect.objectContaining({
|
MsgContent: expect.objectContaining({
|
||||||
Data: "AUTOMATIONFAIL",
|
Data: "AUTORXFAIL",
|
||||||
Desc: "notification",
|
Desc: "notification",
|
||||||
Ext: "自动审方失败:审方药师暂不在线",
|
Ext: JSON.stringify({
|
||||||
|
errorSource: ERROR_SOURCES.SYSTEM,
|
||||||
|
reason: "系统错误:审方药师暂不在线",
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
@ -433,6 +444,152 @@ describe("automation task scheduling", () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("labels HIS prescription upload failures in the notification", async () => {
|
||||||
|
tencentIM.mockResolvedValue({ success: true });
|
||||||
|
diagnosticRecord.mockResolvedValue({
|
||||||
|
success: false,
|
||||||
|
failList: [{ message: "[his]处方上传失败: HIS拒绝接收处方" }],
|
||||||
|
});
|
||||||
|
const rxId = new ObjectId();
|
||||||
|
const taskUpdateOne = jest.fn().mockResolvedValue({ modifiedCount: 1 });
|
||||||
|
const db = createCollectionDb({
|
||||||
|
"hlw-config": {
|
||||||
|
findOne: jest.fn().mockResolvedValue({ autoPassRx: true }),
|
||||||
|
},
|
||||||
|
"diagnostic-record": {
|
||||||
|
findOne: jest.fn().mockResolvedValue({
|
||||||
|
_id: rxId,
|
||||||
|
orderId: "order-his-failure",
|
||||||
|
status: "INIT",
|
||||||
|
pharmacistNo: "P001",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"hlw-doctor": {
|
||||||
|
findOne: jest.fn().mockResolvedValue({
|
||||||
|
doctorNo: "P001",
|
||||||
|
onlineStatus: "online",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"consult-order": {
|
||||||
|
findOne: jest.fn().mockResolvedValue({ doctorCode: "D001" }),
|
||||||
|
},
|
||||||
|
[COLLECTION]: { updateOne: taskUpdateOne },
|
||||||
|
});
|
||||||
|
const task = {
|
||||||
|
_id: new ObjectId(),
|
||||||
|
type: AUTOMATION_TYPES.PASS_RX,
|
||||||
|
corpId: "corp-1",
|
||||||
|
orderId: "order-his-failure",
|
||||||
|
rxId: rxId.toString(),
|
||||||
|
attempts: 1,
|
||||||
|
expiresAt: Date.now() + 60_000,
|
||||||
|
};
|
||||||
|
|
||||||
|
await processClaimedTask(db, task);
|
||||||
|
|
||||||
|
expect(tencentIM).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
msgBody: [
|
||||||
|
expect.objectContaining({
|
||||||
|
MsgContent: expect.objectContaining({
|
||||||
|
Data: "AUTORXFAIL",
|
||||||
|
Ext: JSON.stringify({
|
||||||
|
errorSource: ERROR_SOURCES.HIS,
|
||||||
|
reason: "HIS上传错误:HIS拒绝接收处方",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
db
|
||||||
|
);
|
||||||
|
expect(taskUpdateOne).toHaveBeenCalledWith(
|
||||||
|
expect.anything(),
|
||||||
|
expect.objectContaining({
|
||||||
|
$set: expect.objectContaining({
|
||||||
|
status: TASK_STATUS.FAILED,
|
||||||
|
lastError: "[his]处方上传失败: HIS拒绝接收处方",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("keeps ordinary automatic audit failures classified as system errors", async () => {
|
||||||
|
tencentIM.mockResolvedValue({ success: true });
|
||||||
|
diagnosticRecord.mockResolvedValue({
|
||||||
|
success: false,
|
||||||
|
failList: [{ message: "处方数据更新失败" }],
|
||||||
|
});
|
||||||
|
const rxId = new ObjectId();
|
||||||
|
const db = createCollectionDb({
|
||||||
|
"hlw-config": {
|
||||||
|
findOne: jest.fn().mockResolvedValue({ autoPassRx: true }),
|
||||||
|
},
|
||||||
|
"diagnostic-record": {
|
||||||
|
findOne: jest.fn().mockResolvedValue({
|
||||||
|
_id: rxId,
|
||||||
|
orderId: "order-system-failure",
|
||||||
|
status: "INIT",
|
||||||
|
pharmacistNo: "P001",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"hlw-doctor": {
|
||||||
|
findOne: jest.fn().mockResolvedValue({
|
||||||
|
doctorNo: "P001",
|
||||||
|
onlineStatus: "online",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"consult-order": {
|
||||||
|
findOne: jest.fn().mockResolvedValue({ doctorCode: "D001" }),
|
||||||
|
},
|
||||||
|
[COLLECTION]: {
|
||||||
|
updateOne: jest.fn().mockResolvedValue({ modifiedCount: 1 }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const task = {
|
||||||
|
_id: new ObjectId(),
|
||||||
|
type: AUTOMATION_TYPES.PASS_RX,
|
||||||
|
corpId: "corp-1",
|
||||||
|
orderId: "order-system-failure",
|
||||||
|
rxId: rxId.toString(),
|
||||||
|
attempts: 1,
|
||||||
|
expiresAt: Date.now() + 60_000,
|
||||||
|
};
|
||||||
|
|
||||||
|
await processClaimedTask(db, task);
|
||||||
|
|
||||||
|
expect(tencentIM).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
msgBody: [
|
||||||
|
expect.objectContaining({
|
||||||
|
MsgContent: expect.objectContaining({
|
||||||
|
Ext: JSON.stringify({
|
||||||
|
errorSource: ERROR_SOURCES.SYSTEM,
|
||||||
|
reason: "系统错误:处方数据更新失败",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
db
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("uses the system unknown-error fallback when no error message exists", () => {
|
||||||
|
expect(getFailureNotificationExt()).toEqual({
|
||||||
|
errorSource: ERROR_SOURCES.SYSTEM,
|
||||||
|
reason: "系统错误:未知错误",
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
getFailureNotificationExt(
|
||||||
|
new AutomationError("", { source: ERROR_SOURCES.SYSTEM })
|
||||||
|
)
|
||||||
|
).toEqual({
|
||||||
|
errorSource: ERROR_SOURCES.SYSTEM,
|
||||||
|
reason: "系统错误:未知错误",
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("automatic prescription assembly", () => {
|
describe("automatic prescription assembly", () => {
|
||||||
|
|||||||
@ -224,6 +224,10 @@ async function addOnlineConsultOrder(params) {
|
|||||||
return { success: false, message: data.message };
|
return { success: false, message: data.message };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const onlineYaoshiCount = await db.collection("hlw-doctor").countDocuments({ corpId: params.corpId, onlineStatus: "online", job: "pharmacist" });
|
||||||
|
if (onlineYaoshiCount === 0) {
|
||||||
|
return { success: false, message: "当前医生或药师不在线,暂无法提交问诊单,请稍后再试" };
|
||||||
|
}
|
||||||
const imAccountResult = await tencentIM({
|
const imAccountResult = await tencentIM({
|
||||||
type: "importAccount",
|
type: "importAccount",
|
||||||
account: params.orderId,
|
account: params.orderId,
|
||||||
@ -1955,7 +1959,9 @@ async function getHlwOrderList(ctx) {
|
|||||||
if (typeof ctx.orderSource === "string" && ctx.orderSource.trim() !== "") {
|
if (typeof ctx.orderSource === "string" && ctx.orderSource.trim() !== "") {
|
||||||
andConditions.push({ orderSource: ctx.orderSource });
|
andConditions.push({ orderSource: ctx.orderSource });
|
||||||
}
|
}
|
||||||
|
if (typeof ctx.feeType === "string" && ctx.feeType.trim() !== "") {
|
||||||
|
andConditions.push({ feeType: ctx.feeType });
|
||||||
|
}
|
||||||
const orderStatus =
|
const orderStatus =
|
||||||
typeof ctx.orderStatus === "string" ? ctx.orderStatus.trim() : "";
|
typeof ctx.orderStatus === "string" ? ctx.orderStatus.trim() : "";
|
||||||
if (orderStatus) {
|
if (orderStatus) {
|
||||||
|
|||||||
@ -2006,14 +2006,11 @@ async function getHisPrescriptionUploadStatus(list) {
|
|||||||
async function getAccountHistoryDrugs(ctx) {
|
async function getAccountHistoryDrugs(ctx) {
|
||||||
try {
|
try {
|
||||||
const patientId = typeof ctx.patientId === 'string' && ctx.patientId.trim() !== '' ? ctx.patientId.trim() : '';
|
const patientId = typeof ctx.patientId === 'string' && ctx.patientId.trim() !== '' ? ctx.patientId.trim() : '';
|
||||||
const accountId = typeof ctx.accountId === 'string' && ctx.accountId.trim() !== '' ? ctx.accountId.trim() : '';
|
// const accountId = typeof ctx.accountId === 'string' && ctx.accountId.trim() !== '' ? ctx.accountId.trim() : '';
|
||||||
if (accountId.value == '') {
|
if (patientId == '') {
|
||||||
return { success: false, message: "账号id不能为空" };
|
return { success: false, message: "患者id不能为空" };
|
||||||
}
|
|
||||||
const query = { accountId, status: status.pass };
|
|
||||||
if (patientId) {
|
|
||||||
query.patientId = patientId;
|
|
||||||
}
|
}
|
||||||
|
const query = { patientId, status: status.pass };
|
||||||
const page = Number.isInteger(ctx.page) && ctx.page > 0 ? ctx.page : 1;
|
const page = Number.isInteger(ctx.page) && ctx.page > 0 ? ctx.page : 1;
|
||||||
const pageSize = Number.isInteger(ctx.pageSize) && ctx.pageSize > 0 ? ctx.pageSize : 10;
|
const pageSize = Number.isInteger(ctx.pageSize) && ctx.pageSize > 0 ? ctx.pageSize : 10;
|
||||||
const [list, total] = await Promise.all([
|
const [list, total] = await Promise.all([
|
||||||
|
|||||||
@ -33,7 +33,7 @@ module.exports = async (item, mongodb) => {
|
|||||||
};
|
};
|
||||||
// 药品库信息
|
// 药品库信息
|
||||||
async function getDrugInfo(item) {
|
async function getDrugInfo(item) {
|
||||||
let { name, _id, ids, pinyin_code, keyword, insuranceCodes } = item;
|
let { name, _id, ids, pinyin_code, keyword, insuranceCodes, barcode } = item;
|
||||||
let fuzzyQuery = null;
|
let fuzzyQuery = null;
|
||||||
let query = { onSale: true };
|
let query = { onSale: true };
|
||||||
if (typeof name === "string") query.name = new RegExp(name.trim(), "i");
|
if (typeof name === "string") query.name = new RegExp(name.trim(), "i");
|
||||||
@ -41,6 +41,9 @@ async function getDrugInfo(item) {
|
|||||||
if (Array.isArray(ids) && ids.length > 0) {
|
if (Array.isArray(ids) && ids.length > 0) {
|
||||||
query._id = { $in: ids.filter(id => ObjectId.isValid(id)).map(id => new ObjectId(id)) };
|
query._id = { $in: ids.filter(id => ObjectId.isValid(id)).map(id => new ObjectId(id)) };
|
||||||
}
|
}
|
||||||
|
if (typeof barcode === "string") {
|
||||||
|
query.barcode = barcode.trim();
|
||||||
|
}
|
||||||
if (typeof pinyin_code === "string")
|
if (typeof pinyin_code === "string")
|
||||||
query.pinyin_code = new RegExp(pinyin_code.trim(), "i");
|
query.pinyin_code = new RegExp(pinyin_code.trim(), "i");
|
||||||
if (Array.isArray(insuranceCodes)) {
|
if (Array.isArray(insuranceCodes)) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user