fix:问题修复
This commit is contained in:
parent
cf095dba0c
commit
428b2cc050
@ -7,6 +7,7 @@ const {
|
|||||||
randomInteger,
|
randomInteger,
|
||||||
} = require("./config");
|
} = require("./config");
|
||||||
const { decryptOrderFields } = require("../consult-order/format");
|
const { decryptOrderFields } = require("../consult-order/format");
|
||||||
|
const tencentIM = require("../tencent-im");
|
||||||
|
|
||||||
const COLLECTION = "hlw-automation-task";
|
const COLLECTION = "hlw-automation-task";
|
||||||
const TASK_STATUS = {
|
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) {
|
async function assertTaskEnabled(db, task) {
|
||||||
const config = await getConfig(db, task.corpId);
|
const config = await getConfig(db, task.corpId);
|
||||||
const { enabled } = getTypeConfig(config, task.type);
|
const { enabled } = getTypeConfig(config, task.type);
|
||||||
@ -648,7 +737,9 @@ async function handleAutoOpenRx(db, task) {
|
|||||||
db
|
db
|
||||||
);
|
);
|
||||||
if (!result || !result.success) {
|
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(
|
const rx = await db.collection("diagnostic-record").findOne(
|
||||||
@ -719,7 +810,7 @@ async function handleAutoPassRx(db, task) {
|
|||||||
Array.isArray(result?.failList) && result.failList[0]
|
Array.isArray(result?.failList) && result.failList[0]
|
||||||
? result.failList[0].message
|
? result.failList[0].message
|
||||||
: result?.message;
|
: result?.message;
|
||||||
throw new AutomationError(detail || "自动审方失败", { retryable: true });
|
throw new AutomationError(detail || "审方接口返回失败", { retryable: true });
|
||||||
}
|
}
|
||||||
return { orderId: task.orderId, rxId: task.rxId };
|
return { orderId: task.orderId, rxId: task.rxId };
|
||||||
}
|
}
|
||||||
@ -763,7 +854,12 @@ async function processClaimedTask(db, task) {
|
|||||||
: new AutomationError(error.message || "自动化任务执行失败", {
|
: new AutomationError(error.message || "自动化任务执行失败", {
|
||||||
retryable: true,
|
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);
|
await retryTask(db, task, automationError);
|
||||||
} else {
|
} else {
|
||||||
await markTask(db, task, TASK_STATUS.FAILED, {
|
await markTask(db, task, TASK_STATUS.FAILED, {
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
const { ObjectId } = require("mongodb");
|
const { ObjectId } = require("mongodb");
|
||||||
|
jest.mock("../tencent-im", () => jest.fn());
|
||||||
|
const tencentIM = require("../tencent-im");
|
||||||
const {
|
const {
|
||||||
AUTOMATION_TYPES,
|
AUTOMATION_TYPES,
|
||||||
COLLECTION,
|
COLLECTION,
|
||||||
@ -25,6 +27,7 @@ function createCollectionDb(collections) {
|
|||||||
describe("automation task scheduling", () => {
|
describe("automation task scheduling", () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.restoreAllMocks();
|
jest.restoreAllMocks();
|
||||||
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("uses the configured delay and respects firstSubmitRxIntervel", () => {
|
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 rxId = new ObjectId();
|
||||||
const taskUpdateOne = jest.fn().mockResolvedValue({ modifiedCount: 1 });
|
const taskUpdateOne = jest.fn().mockResolvedValue({ modifiedCount: 1 });
|
||||||
const db = createCollectionDb({
|
const db = createCollectionDb({
|
||||||
@ -298,6 +378,9 @@ describe("automation task scheduling", () => {
|
|||||||
onlineStatus: "offline",
|
onlineStatus: "offline",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
"consult-order": {
|
||||||
|
findOne: jest.fn().mockResolvedValue({ doctorCode: "D001" }),
|
||||||
|
},
|
||||||
[COLLECTION]: { updateOne: taskUpdateOne },
|
[COLLECTION]: { updateOne: taskUpdateOne },
|
||||||
});
|
});
|
||||||
const task = {
|
const task = {
|
||||||
@ -319,12 +402,36 @@ describe("automation task scheduling", () => {
|
|||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
$set: expect.objectContaining({
|
$set: expect.objectContaining({
|
||||||
status: TASK_STATUS.PENDING,
|
status: TASK_STATUS.FAILED,
|
||||||
nextRunAt: expect.any(Number),
|
completedAt: expect.any(Number),
|
||||||
lastError: "审方药师暂不在线",
|
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 }),
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user