fix:问题修复

This commit is contained in:
huxuejian 2026-08-03 16:14:45 +08:00
parent cf095dba0c
commit 428b2cc050
2 changed files with 209 additions and 6 deletions

View File

@ -7,6 +7,7 @@ const {
randomInteger,
} = require("./config");
const { decryptOrderFields } = require("../consult-order/format");
const tencentIM = require("../tencent-im");
const COLLECTION = "hlw-automation-task";
const TASK_STATUS = {
@ -318,6 +319,94 @@ async function retryTask(db, task, error) {
});
}
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}`;
}
async function sendFailureNotification(db, task, error) {
let doctorCode = "";
try {
const order = await db.collection("consult-order").findOne(
{ orderId: task.orderId, corpId: task.corpId },
{ projection: { doctorCode: 1 } }
);
doctorCode = order && order.doctorCode ? order.doctorCode : "";
} catch (lookupError) {
console.error(
"[自动化任务] 查询失败通知接收人异常:",
lookupError.message
);
}
if (
!doctorCode &&
task.type === AUTOMATION_TYPES.PASS_RX &&
ObjectId.isValid(task.rxId)
) {
try {
const rx = await db.collection("diagnostic-record").findOne(
{ _id: new ObjectId(task.rxId), orderId: task.orderId },
{ projection: { doctorCode: 1 } }
);
doctorCode = rx && rx.doctorCode ? rx.doctorCode : "";
} catch (lookupError) {
console.error(
"[自动化任务] 查询处方失败通知接收人异常:",
lookupError.message
);
}
}
if (!doctorCode) {
console.error(
`[自动化任务] 未找到订单 ${task.orderId} 的医生,无法发送失败通知`
);
return;
}
const notification = getFailureNotificationText(task, error);
try {
const result = await tencentIM(
{
type: "sendSystemNotification",
corpId: task.corpId,
formAccount: task.orderId,
toAccount: doctorCode,
SyncOtherMachine: 1,
msgBody: [
{
MsgType: "TIMCustomElem",
MsgContent: {
Data: "AUTOMATIONFAIL",
Desc: "notification",
Ext: notification,
},
},
],
},
db
);
if (!result || !result.success) {
console.error(
`[自动化任务] 发送失败通知失败: ${result?.message || "未知错误"}`
);
}
} catch (notificationError) {
console.error(
"[自动化任务] 发送失败通知异常:",
notificationError.message
);
}
}
async function assertTaskEnabled(db, task) {
const config = await getConfig(db, task.corpId);
const { enabled } = getTypeConfig(config, task.type);
@ -648,7 +737,9 @@ async function handleAutoOpenRx(db, task) {
db
);
if (!result || !result.success) {
throw new AutomationError(result?.message || "自动开方失败", { retryable: true });
throw new AutomationError(result?.message || "开方接口返回失败", {
retryable: true,
});
}
const rx = await db.collection("diagnostic-record").findOne(
@ -719,7 +810,7 @@ async function handleAutoPassRx(db, task) {
Array.isArray(result?.failList) && result.failList[0]
? result.failList[0].message
: result?.message;
throw new AutomationError(detail || "自动审方失败", { retryable: true });
throw new AutomationError(detail || "审方接口返回失败", { retryable: true });
}
return { orderId: task.orderId, rxId: task.rxId };
}
@ -763,7 +854,12 @@ async function processClaimedTask(db, task) {
: new AutomationError(error.message || "自动化任务执行失败", {
retryable: true,
});
if (automationError.retryable) {
if (isSingleAttemptTask(task.type)) {
await markTask(db, task, TASK_STATUS.FAILED, {
lastError: automationError.message,
});
await sendFailureNotification(db, task, automationError);
} else if (automationError.retryable) {
await retryTask(db, task, automationError);
} else {
await markTask(db, task, TASK_STATUS.FAILED, {

View File

@ -1,4 +1,6 @@
const { ObjectId } = require("mongodb");
jest.mock("../tencent-im", () => jest.fn());
const tencentIM = require("../tencent-im");
const {
AUTOMATION_TYPES,
COLLECTION,
@ -25,6 +27,7 @@ function createCollectionDb(collections) {
describe("automation task scheduling", () => {
afterEach(() => {
jest.restoreAllMocks();
jest.clearAllMocks();
});
test("uses the configured delay and respects firstSubmitRxIntervel", () => {
@ -277,7 +280,84 @@ describe("automation task scheduling", () => {
);
});
test("retries automatic audit while the assigned pharmacist is offline", async () => {
test("fails automatic prescription immediately and sends the failure reason", async () => {
tencentIM.mockResolvedValue({ success: true });
const taskUpdateOne = jest.fn().mockResolvedValue({ modifiedCount: 1 });
const order = {
orderId: "order-1",
corpId: "corp-1",
orderSource: "ALIPAY_MINI",
orderStatus: "processing",
doctorCode: "D001",
expireTime: Date.now() + 60_000,
};
const db = createCollectionDb({
"hlw-config": {
findOne: jest.fn().mockResolvedValue({ autoOpenRx: true }),
},
"consult-order": {
findOne: jest.fn().mockResolvedValue(order),
},
"diagnostic-record": {
findOne: jest.fn().mockResolvedValue(null),
},
"hlw-doctor": {
findOne: jest.fn().mockResolvedValue(null),
},
[COLLECTION]: { updateOne: taskUpdateOne },
});
const task = {
_id: new ObjectId(),
type: AUTOMATION_TYPES.OPEN_RX,
corpId: "corp-1",
orderId: "order-1",
attempts: 1,
expiresAt: Date.now() + 60_000,
};
await processClaimedTask(db, task);
expect(taskUpdateOne).toHaveBeenCalledWith(
expect.objectContaining({
_id: task._id,
status: TASK_STATUS.RUNNING,
}),
expect.objectContaining({
$set: expect.objectContaining({
status: TASK_STATUS.FAILED,
completedAt: expect.any(Number),
lastError: "开方医生不存在",
}),
})
);
expect(tencentIM).toHaveBeenCalledWith(
expect.objectContaining({
type: "sendSystemNotification",
formAccount: "order-1",
toAccount: "D001",
SyncOtherMachine: 1,
msgBody: [
expect.objectContaining({
MsgContent: expect.objectContaining({
Data: "AUTOMATIONFAIL",
Desc: "notification",
Ext: "自动开方失败:开方医生不存在",
}),
}),
],
}),
db
);
expect(taskUpdateOne).not.toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
$set: expect.objectContaining({ status: TASK_STATUS.PENDING }),
})
);
});
test("fails automatic audit immediately and sends the failure reason", async () => {
tencentIM.mockResolvedValue({ success: true });
const rxId = new ObjectId();
const taskUpdateOne = jest.fn().mockResolvedValue({ modifiedCount: 1 });
const db = createCollectionDb({
@ -298,6 +378,9 @@ describe("automation task scheduling", () => {
onlineStatus: "offline",
}),
},
"consult-order": {
findOne: jest.fn().mockResolvedValue({ doctorCode: "D001" }),
},
[COLLECTION]: { updateOne: taskUpdateOne },
});
const task = {
@ -319,12 +402,36 @@ describe("automation task scheduling", () => {
}),
expect.objectContaining({
$set: expect.objectContaining({
status: TASK_STATUS.PENDING,
nextRunAt: expect.any(Number),
status: TASK_STATUS.FAILED,
completedAt: expect.any(Number),
lastError: "审方药师暂不在线",
}),
})
);
expect(tencentIM).toHaveBeenCalledWith(
expect.objectContaining({
type: "sendSystemNotification",
formAccount: "order-1",
toAccount: "D001",
SyncOtherMachine: 1,
msgBody: [
expect.objectContaining({
MsgContent: expect.objectContaining({
Data: "AUTOMATIONFAIL",
Desc: "notification",
Ext: "自动审方失败:审方药师暂不在线",
}),
}),
],
}),
db
);
expect(taskUpdateOne).not.toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
$set: expect.objectContaining({ status: TASK_STATUS.PENDING }),
})
);
});
});