fix: 问题修复
This commit is contained in:
parent
42d6a6d71b
commit
b81a6a85ca
@ -17,6 +17,10 @@ const TASK_STATUS = {
|
||||
CANCELLED: "CANCELLED",
|
||||
FAILED: "FAILED",
|
||||
};
|
||||
const ERROR_SOURCES = {
|
||||
SYSTEM: "SYSTEM",
|
||||
HIS: "HIS",
|
||||
};
|
||||
const LEASE_MS = 2 * 60 * 1000;
|
||||
const POLL_INTERVAL_MS = 1000;
|
||||
const RECONCILE_INTERVAL_MS = 30 * 1000;
|
||||
@ -31,10 +35,11 @@ let pollIsRunning = false;
|
||||
let reconcileIsRunning = false;
|
||||
|
||||
class AutomationError extends Error {
|
||||
constructor(message, { retryable = false } = {}) {
|
||||
constructor(message, { retryable = false, source = ERROR_SOURCES.SYSTEM } = {}) {
|
||||
super(message);
|
||||
this.name = "AutomationError";
|
||||
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);
|
||||
}
|
||||
|
||||
function getFailureNotificationText(task, error) {
|
||||
const action =
|
||||
task.type === AUTOMATION_TYPES.OPEN_RX ? "自动开方" : "自动审方";
|
||||
const reason = error && error.message ? error.message : "未知错误";
|
||||
// return `${action}失败:${reason}`;
|
||||
return reason
|
||||
function isHisUploadFailure(message) {
|
||||
return typeof message === "string" && /^\s*\[his\]/i.test(message);
|
||||
}
|
||||
|
||||
function cleanHisUploadFailureMessage(message) {
|
||||
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) {
|
||||
@ -373,8 +396,7 @@ async function sendFailureNotification(db, task, error) {
|
||||
return;
|
||||
}
|
||||
|
||||
const notification = getFailureNotificationText(task, error);
|
||||
const ext = { reason: notification };
|
||||
const ext = getFailureNotificationExt(error);
|
||||
try {
|
||||
const result = await tencentIM(
|
||||
{
|
||||
@ -808,11 +830,17 @@ async function handleAutoPassRx(db, task) {
|
||||
db
|
||||
);
|
||||
if (!result || !result.success) {
|
||||
const detail =
|
||||
const failure =
|
||||
Array.isArray(result?.failList) && result.failList[0]
|
||||
? result.failList[0].message
|
||||
: result?.message;
|
||||
throw new AutomationError(detail || "审方接口返回失败", { retryable: true });
|
||||
? result.failList[0]
|
||||
: null;
|
||||
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 };
|
||||
}
|
||||
@ -1011,6 +1039,7 @@ async function reconcileAutomationTasks(db = workerDb) {
|
||||
}
|
||||
|
||||
async function start(db) {
|
||||
return
|
||||
if (pollTimer || reconcileTimer) return;
|
||||
workerDb = db;
|
||||
await ensureIndexes(db);
|
||||
@ -1040,7 +1069,9 @@ module.exports = {
|
||||
COLLECTION,
|
||||
TASK_STATUS,
|
||||
AUTOMATION_TYPES,
|
||||
ERROR_SOURCES,
|
||||
AutomationError,
|
||||
getFailureNotificationExt,
|
||||
calculateDueAt,
|
||||
enqueueTask,
|
||||
scheduleAutoAccept,
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
const { ObjectId } = require("mongodb");
|
||||
jest.mock("../tencent-im", () => jest.fn());
|
||||
jest.mock("../diagnostic-record", () => jest.fn());
|
||||
const tencentIM = require("../tencent-im");
|
||||
const diagnosticRecord = require("../diagnostic-record");
|
||||
const {
|
||||
AUTOMATION_TYPES,
|
||||
COLLECTION,
|
||||
ERROR_SOURCES,
|
||||
TASK_STATUS,
|
||||
AutomationError,
|
||||
buildAutoPrescriptionParams,
|
||||
calculateDueAt,
|
||||
claimNextTask,
|
||||
enqueueTask,
|
||||
getFailureNotificationExt,
|
||||
processClaimedTask,
|
||||
reconcileAutomationTasks,
|
||||
} = require("./index");
|
||||
@ -339,9 +344,12 @@ describe("automation task scheduling", () => {
|
||||
msgBody: [
|
||||
expect.objectContaining({
|
||||
MsgContent: expect.objectContaining({
|
||||
Data: "AUTOMATIONFAIL",
|
||||
Data: "AUTORXFAIL",
|
||||
Desc: "notification",
|
||||
Ext: "自动开方失败:开方医生不存在",
|
||||
Ext: JSON.stringify({
|
||||
errorSource: ERROR_SOURCES.SYSTEM,
|
||||
reason: "系统错误:开方医生不存在",
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
@ -417,9 +425,12 @@ describe("automation task scheduling", () => {
|
||||
msgBody: [
|
||||
expect.objectContaining({
|
||||
MsgContent: expect.objectContaining({
|
||||
Data: "AUTOMATIONFAIL",
|
||||
Data: "AUTORXFAIL",
|
||||
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", () => {
|
||||
|
||||
@ -224,6 +224,10 @@ async function addOnlineConsultOrder(params) {
|
||||
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({
|
||||
type: "importAccount",
|
||||
account: params.orderId,
|
||||
@ -1955,7 +1959,9 @@ async function getHlwOrderList(ctx) {
|
||||
if (typeof ctx.orderSource === "string" && ctx.orderSource.trim() !== "") {
|
||||
andConditions.push({ orderSource: ctx.orderSource });
|
||||
}
|
||||
|
||||
if (typeof ctx.feeType === "string" && ctx.feeType.trim() !== "") {
|
||||
andConditions.push({ feeType: ctx.feeType });
|
||||
}
|
||||
const orderStatus =
|
||||
typeof ctx.orderStatus === "string" ? ctx.orderStatus.trim() : "";
|
||||
if (orderStatus) {
|
||||
|
||||
@ -2006,14 +2006,11 @@ async function getHisPrescriptionUploadStatus(list) {
|
||||
async function getAccountHistoryDrugs(ctx) {
|
||||
try {
|
||||
const patientId = typeof ctx.patientId === 'string' && ctx.patientId.trim() !== '' ? ctx.patientId.trim() : '';
|
||||
const accountId = typeof ctx.accountId === 'string' && ctx.accountId.trim() !== '' ? ctx.accountId.trim() : '';
|
||||
if (accountId.value == '') {
|
||||
return { success: false, message: "账号id不能为空" };
|
||||
}
|
||||
const query = { accountId, status: status.pass };
|
||||
if (patientId) {
|
||||
query.patientId = patientId;
|
||||
// const accountId = typeof ctx.accountId === 'string' && ctx.accountId.trim() !== '' ? ctx.accountId.trim() : '';
|
||||
if (patientId == '') {
|
||||
return { success: false, message: "患者id不能为空" };
|
||||
}
|
||||
const query = { patientId, status: status.pass };
|
||||
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 [list, total] = await Promise.all([
|
||||
|
||||
@ -33,7 +33,7 @@ module.exports = async (item, mongodb) => {
|
||||
};
|
||||
// 药品库信息
|
||||
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 query = { onSale: true };
|
||||
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) {
|
||||
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")
|
||||
query.pinyin_code = new RegExp(pinyin_code.trim(), "i");
|
||||
if (Array.isArray(insuranceCodes)) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user