fix: 需求调整

This commit is contained in:
huxuejian 2026-08-24 16:41:11 +08:00
parent be51c7be1b
commit c9c262e3a1
6 changed files with 534 additions and 17 deletions

View File

@ -21,6 +21,11 @@ const ERROR_SOURCES = {
SYSTEM: "SYSTEM", SYSTEM: "SYSTEM",
HIS: "HIS", HIS: "HIS",
}; };
const TASK_CANCEL_REASONS = {
HIGH_RISK_DRUG: "HIGH_RISK_DRUG",
};
const MANUAL_OPEN_RX_MESSAGE =
"订单中含高风险药品,已停止自动开方,请医生人工开方。";
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;
@ -43,6 +48,14 @@ class AutomationError extends Error {
} }
} }
class ManualPrescriptionRequiredError extends AutomationError {
constructor(message = MANUAL_OPEN_RX_MESSAGE) {
super(message);
this.name = "ManualPrescriptionRequiredError";
this.cancelReason = TASK_CANCEL_REASONS.HIGH_RISK_DRUG;
}
}
function getTaskKey(type, orderId, rxId) { function getTaskKey(type, orderId, rxId) {
if (type === AUTOMATION_TYPES.PASS_RX) { if (type === AUTOMATION_TYPES.PASS_RX) {
return `${type}:${rxId}`; return `${type}:${rxId}`;
@ -83,7 +96,15 @@ function calculateDueAt(type, config, baseTime = Date.now(), random = Math.rando
async function enqueueTask( async function enqueueTask(
db, db,
{ type, corpId, orderId, rxId = "", baseTime = Date.now(), expiresAt } {
type,
corpId,
orderId,
rxId = "",
baseTime = Date.now(),
expiresAt,
config: providedConfig,
}
) { ) {
if (!corpId || !orderId || !Object.values(AUTOMATION_TYPES).includes(type)) { if (!corpId || !orderId || !Object.values(AUTOMATION_TYPES).includes(type)) {
return { success: false, message: "自动化任务参数错误" }; return { success: false, message: "自动化任务参数错误" };
@ -92,7 +113,7 @@ async function enqueueTask(
return { success: false, message: "自动审方任务缺少处方ID" }; return { success: false, message: "自动审方任务缺少处方ID" };
} }
const config = await getConfig(db, corpId); const config = providedConfig || (await getConfig(db, corpId));
const stageConfig = getTypeConfig(config, type); const stageConfig = getTypeConfig(config, type);
if (!stageConfig.enabled) { if (!stageConfig.enabled) {
return { success: false, skipped: true, message: "自动化配置未开启" }; return { success: false, skipped: true, message: "自动化配置未开启" };
@ -129,6 +150,7 @@ async function enqueueTask(
$unset: { $unset: {
completedAt: "", completedAt: "",
result: "", result: "",
cancelReason: "",
}, },
} }
); );
@ -163,6 +185,111 @@ async function enqueueTask(
}; };
} }
function getDrugCollectionName(order) {
return order && order.consultType === "onlineMedicinePurchase"
? "online-drug-info"
: "drug-info";
}
function getOrderDrugIds(order) {
const drugs = order && Array.isArray(order.drugs) ? order.drugs : [];
return drugs
.map((item) => (item && ObjectId.isValid(item._id) ? new ObjectId(item._id) : null))
.filter(Boolean);
}
async function findHighRiskDrug(db, order) {
const ids = getOrderDrugIds(order);
if (!ids.length) return null;
return db.collection(getDrugCollectionName(order)).findOne(
{ _id: { $in: ids }, riskLevel: "高风险" },
{ projection: { _id: 1, name: 1, riskLevel: 1 } }
);
}
async function cancelAutoOpenForHighRisk({
db,
orderId,
corpId,
doctorCode = "",
baseTime,
expiresAt,
}) {
const now = Date.now();
const taskKey = getTaskKey(AUTOMATION_TYPES.OPEN_RX, orderId, "");
const collection = db.collection(COLLECTION);
const cancelData = {
status: TASK_STATUS.CANCELLED,
leaseUntil: 0,
leaseOwner: "",
lastError: MANUAL_OPEN_RX_MESSAGE,
cancelReason: TASK_CANCEL_REASONS.HIGH_RISK_DRUG,
completedAt: now,
updatedAt: now,
};
const transitioned = await collection.updateOne(
{
taskKey,
$or: [
{ status: TASK_STATUS.PENDING },
{
status: TASK_STATUS.CANCELLED,
cancelReason: { $ne: TASK_CANCEL_REASONS.HIGH_RISK_DRUG },
},
],
},
{ $set: cancelData }
);
let shouldNotify = transitioned.modifiedCount === 1;
if (!shouldNotify) {
const cancelledTask = {
taskKey,
type: AUTOMATION_TYPES.OPEN_RX,
corpId,
orderId,
rxId: "",
status: TASK_STATUS.CANCELLED,
dueAt: Number.isFinite(baseTime) ? baseTime : now,
nextRunAt: Number.isFinite(baseTime) ? baseTime : now,
expiresAt: Number.isFinite(expiresAt) ? expiresAt : now + 30 * 60 * 1000,
attempts: 0,
leaseUntil: 0,
leaseOwner: "",
lastError: MANUAL_OPEN_RX_MESSAGE,
cancelReason: TASK_CANCEL_REASONS.HIGH_RISK_DRUG,
createdAt: now,
updatedAt: now,
completedAt: now,
};
try {
const inserted = await collection.updateOne(
{ taskKey },
{ $setOnInsert: cancelledTask },
{ upsert: true }
);
shouldNotify = inserted.upsertedCount === 1;
} catch (error) {
if (!error || error.code !== 11000) throw error;
}
}
if (shouldNotify) {
await sendManualOpenNotification(
db,
{ type: AUTOMATION_TYPES.OPEN_RX, corpId, orderId },
doctorCode
);
}
return {
success: false,
skipped: true,
manualRequired: true,
message: MANUAL_OPEN_RX_MESSAGE,
taskKey,
};
}
async function scheduleAutoAccept({ db, orderId, corpId, baseTime, expiresAt }) { async function scheduleAutoAccept({ db, orderId, corpId, baseTime, expiresAt }) {
const eligible = await db.collection("consult-order").findOne( const eligible = await db.collection("consult-order").findOne(
{ orderId, corpId, orderSource: "ALIPAY_MINI" }, { orderId, corpId, orderSource: "ALIPAY_MINI" },
@ -183,17 +310,33 @@ async function scheduleAutoAccept({ db, orderId, corpId, baseTime, expiresAt })
async function scheduleAutoOpenRx({ db, orderId, corpId, baseTime, expiresAt }) { async function scheduleAutoOpenRx({ db, orderId, corpId, baseTime, expiresAt }) {
const eligible = await db.collection("consult-order").findOne( const eligible = await db.collection("consult-order").findOne(
{ orderId, corpId, orderSource: "ALIPAY_MINI" }, { orderId, corpId, orderSource: "ALIPAY_MINI" },
{ projection: { _id: 1 } } { projection: { _id: 1, drugs: 1, consultType: 1, doctorCode: 1 } }
); );
if (!eligible) { if (!eligible) {
return { success: false, skipped: true, message: "非支付宝小程序订单" }; return { success: false, skipped: true, message: "非支付宝小程序订单" };
} }
const config = await getConfig(db, corpId);
if (!getTypeConfig(config, AUTOMATION_TYPES.OPEN_RX).enabled) {
return { success: false, skipped: true, message: "自动化配置未开启" };
}
const highRiskDrug = await findHighRiskDrug(db, eligible);
if (highRiskDrug) {
return cancelAutoOpenForHighRisk({
db,
orderId,
corpId,
doctorCode: eligible.doctorCode,
baseTime,
expiresAt,
});
}
return enqueueTask(db, { return enqueueTask(db, {
type: AUTOMATION_TYPES.OPEN_RX, type: AUTOMATION_TYPES.OPEN_RX,
orderId, orderId,
corpId, corpId,
baseTime, baseTime,
expiresAt, expiresAt,
config,
}); });
} }
@ -268,7 +411,7 @@ async function markTask(db, task, status, extra = {}) {
TASK_STATUS.CANCELLED, TASK_STATUS.CANCELLED,
TASK_STATUS.FAILED, TASK_STATUS.FAILED,
].includes(status); ].includes(status);
await db.collection(COLLECTION).updateOne( return db.collection(COLLECTION).updateOne(
{ {
_id: task._id, _id: task._id,
status: TASK_STATUS.RUNNING, status: TASK_STATUS.RUNNING,
@ -354,6 +497,67 @@ function getFailureNotificationExt(error) {
return { errorSource, reason }; return { errorSource, reason };
} }
async function sendManualOpenNotification(db, task, knownDoctorCode = "") {
let doctorCode = knownDoctorCode;
if (!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) {
console.error(
`[自动化任务] 未找到订单 ${task.orderId} 的医生,无法发送人工开方提示`
);
return false;
}
try {
const result = await tencentIM(
{
type: "sendSystemNotification",
corpId: task.corpId,
formAccount: task.orderId,
toAccount: doctorCode,
SyncOtherMachine: 1,
msgBody: [
{
MsgType: "TIMCustomElem",
MsgContent: {
Data: "AUTORXMANUAL",
Desc: "notification",
Ext: MANUAL_OPEN_RX_MESSAGE,
},
},
],
},
db
);
if (!result || !result.success) {
console.error(
`[自动化任务] 发送人工开方提示失败: ${result?.message || "未知错误"}`
);
return false;
}
return true;
} catch (notificationError) {
console.error(
"[自动化任务] 发送人工开方提示异常:",
notificationError.message
);
return false;
}
}
async function sendFailureNotification(db, task, error) { async function sendFailureNotification(db, task, error) {
let doctorCode = ""; let doctorCode = "";
@ -571,10 +775,16 @@ async function resolvePrescriptionDrugs(db, order) {
order.consultType === "onlineMedicinePurchase" ? "online-drug-info" : "drug-info"; order.consultType === "onlineMedicinePurchase" ? "online-drug-info" : "drug-info";
const masterDrugs = await db const masterDrugs = await db
.collection(collectionName) .collection(collectionName)
.find({ _id: { $in: ids }, onSale: true }) .find({ _id: { $in: ids } })
.toArray(); .toArray();
if (masterDrugs.length !== orderDrugs.length) { if (masterDrugs.length !== orderDrugs.length) {
throw new AutomationError("订单中存在已下架或不存在的药品"); throw new AutomationError("订单中存在不存在的药品");
}
if (masterDrugs.some((item) => item.riskLevel === "高风险")) {
throw new ManualPrescriptionRequiredError();
}
if (masterDrugs.some((item) => item.onSale !== true)) {
throw new AutomationError("订单中存在已下架的药品");
} }
const medicineConfig = await getMedicineConfig(db, order.corpId); const medicineConfig = await getMedicineConfig(db, order.corpId);
@ -884,7 +1094,15 @@ async function processClaimedTask(db, task) {
: new AutomationError(error.message || "自动化任务执行失败", { : new AutomationError(error.message || "自动化任务执行失败", {
retryable: true, retryable: true,
}); });
if (isSingleAttemptTask(task.type)) { if (automationError.cancelReason === TASK_CANCEL_REASONS.HIGH_RISK_DRUG) {
const marked = await markTask(db, task, TASK_STATUS.CANCELLED, {
lastError: automationError.message,
cancelReason: TASK_CANCEL_REASONS.HIGH_RISK_DRUG,
});
if (marked && (marked.modifiedCount === 1 || marked.matchedCount === 1)) {
await sendManualOpenNotification(db, task);
}
} else if (isSingleAttemptTask(task.type)) {
await markTask(db, task, TASK_STATUS.FAILED, { await markTask(db, task, TASK_STATUS.FAILED, {
lastError: automationError.message, lastError: automationError.message,
}); });
@ -1069,7 +1287,10 @@ module.exports = {
TASK_STATUS, TASK_STATUS,
AUTOMATION_TYPES, AUTOMATION_TYPES,
ERROR_SOURCES, ERROR_SOURCES,
TASK_CANCEL_REASONS,
MANUAL_OPEN_RX_MESSAGE,
AutomationError, AutomationError,
ManualPrescriptionRequiredError,
getFailureNotificationExt, getFailureNotificationExt,
calculateDueAt, calculateDueAt,
enqueueTask, enqueueTask,

View File

@ -7,6 +7,8 @@ const {
AUTOMATION_TYPES, AUTOMATION_TYPES,
COLLECTION, COLLECTION,
ERROR_SOURCES, ERROR_SOURCES,
MANUAL_OPEN_RX_MESSAGE,
TASK_CANCEL_REASONS,
TASK_STATUS, TASK_STATUS,
AutomationError, AutomationError,
buildAutoPrescriptionParams, buildAutoPrescriptionParams,
@ -16,6 +18,7 @@ const {
getFailureNotificationExt, getFailureNotificationExt,
processClaimedTask, processClaimedTask,
reconcileAutomationTasks, reconcileAutomationTasks,
scheduleAutoOpenRx,
} = require("./index"); } = require("./index");
function createCollectionDb(collections) { function createCollectionDb(collections) {
@ -126,11 +129,183 @@ describe("automation task scheduling", () => {
}, },
expect.objectContaining({ expect.objectContaining({
$set: expect.objectContaining({ status: TASK_STATUS.PENDING }), $set: expect.objectContaining({ status: TASK_STATUS.PENDING }),
$unset: { completedAt: "", result: "" }, $unset: { completedAt: "", result: "", cancelReason: "" },
}) })
); );
}); });
test("cancels store-drug auto open once when any ordered drug is high risk", async () => {
tencentIM.mockResolvedValue({ success: true });
const drugId = new ObjectId();
const lowRiskDrugId = new ObjectId();
const updateOne = jest
.fn()
.mockResolvedValueOnce({ modifiedCount: 0 })
.mockResolvedValueOnce({ upsertedCount: 1 })
.mockResolvedValueOnce({ modifiedCount: 0 })
.mockResolvedValueOnce({ upsertedCount: 0 });
const db = createCollectionDb({
"consult-order": {
findOne: jest.fn().mockResolvedValue({
_id: new ObjectId(),
orderId: "order-high-risk",
corpId: "corp-1",
doctorCode: "D001",
consultType: "storeMedicinePurchase",
drugs: [
{ _id: lowRiskDrugId.toString() },
{ _id: drugId.toString() },
],
}),
},
"hlw-config": {
findOne: jest.fn().mockResolvedValue({ autoOpenRx: true }),
},
"drug-info": {
findOne: jest.fn().mockResolvedValue({
_id: drugId,
name: "高风险药品",
riskLevel: "高风险",
}),
},
[COLLECTION]: { updateOne },
});
const first = await scheduleAutoOpenRx({
db,
orderId: "order-high-risk",
corpId: "corp-1",
});
const second = await scheduleAutoOpenRx({
db,
orderId: "order-high-risk",
corpId: "corp-1",
});
expect(first).toMatchObject({
success: false,
skipped: true,
manualRequired: true,
message: MANUAL_OPEN_RX_MESSAGE,
});
expect(second).toMatchObject({ manualRequired: true });
expect(updateOne).toHaveBeenNthCalledWith(
2,
{ taskKey: "AUTO_OPEN_RX:order-high-risk" },
{
$setOnInsert: expect.objectContaining({
status: TASK_STATUS.CANCELLED,
cancelReason: TASK_CANCEL_REASONS.HIGH_RISK_DRUG,
lastError: MANUAL_OPEN_RX_MESSAGE,
}),
},
{ upsert: true }
);
expect(tencentIM).toHaveBeenCalledTimes(1);
expect(tencentIM).toHaveBeenCalledWith(
expect.objectContaining({
formAccount: "order-high-risk",
toAccount: "D001",
msgBody: [
expect.objectContaining({
MsgContent: expect.objectContaining({
Data: "AUTORXMANUAL",
Ext: MANUAL_OPEN_RX_MESSAGE,
}),
}),
],
}),
db
);
});
test("checks the online drug collection and allows low-risk tasks to reactivate", async () => {
const drugId = new ObjectId();
const updateOne = jest.fn().mockResolvedValue({ modifiedCount: 1 });
const onlineFindOne = jest.fn().mockResolvedValue(null);
const db = createCollectionDb({
"consult-order": {
findOne: jest.fn().mockResolvedValue({
_id: new ObjectId(),
consultType: "onlineMedicinePurchase",
drugs: [{ _id: drugId.toString() }],
}),
},
"hlw-config": {
findOne: jest.fn().mockResolvedValue({ autoOpenRx: true }),
},
"online-drug-info": { findOne: onlineFindOne },
[COLLECTION]: { updateOne },
});
await expect(
scheduleAutoOpenRx({
db,
orderId: "order-online-low-risk",
corpId: "corp-1",
})
).resolves.toMatchObject({
success: true,
reactivated: true,
});
expect(onlineFindOne).toHaveBeenCalledWith(
{ _id: { $in: [expect.any(ObjectId)] }, riskLevel: "高风险" },
{ projection: { _id: 1, name: 1, riskLevel: 1 } }
);
expect(updateOne).toHaveBeenCalledWith(
{
taskKey: "AUTO_OPEN_RX:order-online-low-risk",
status: TASK_STATUS.CANCELLED,
},
expect.objectContaining({
$set: expect.objectContaining({ status: TASK_STATUS.PENDING }),
$unset: { completedAt: "", result: "", cancelReason: "" },
})
);
});
test("cancels auto open for a high-risk online drug", async () => {
tencentIM.mockResolvedValue({ success: true });
const drugId = new ObjectId();
const updateOne = jest
.fn()
.mockResolvedValueOnce({ modifiedCount: 0 })
.mockResolvedValueOnce({ upsertedCount: 1 });
const db = createCollectionDb({
"consult-order": {
findOne: jest.fn().mockResolvedValue({
_id: new ObjectId(),
doctorCode: "D002",
consultType: "onlineMedicinePurchase",
drugs: [{ _id: drugId.toString() }],
}),
},
"hlw-config": {
findOne: jest.fn().mockResolvedValue({ autoOpenRx: true }),
},
"online-drug-info": {
findOne: jest.fn().mockResolvedValue({
_id: drugId,
riskLevel: "高风险",
}),
},
[COLLECTION]: { updateOne },
});
await expect(
scheduleAutoOpenRx({
db,
orderId: "order-online-high-risk",
corpId: "corp-1",
})
).resolves.toMatchObject({
skipped: true,
manualRequired: true,
});
expect(tencentIM).toHaveBeenCalledTimes(1);
});
test("treats a concurrent unique-key insert as idempotent success", async () => { test("treats a concurrent unique-key insert as idempotent success", async () => {
const duplicateError = Object.assign(new Error("duplicate key"), { const duplicateError = Object.assign(new Error("duplicate key"), {
code: 11000, code: 11000,
@ -285,6 +460,91 @@ describe("automation task scheduling", () => {
); );
}); });
test("cancels a queued auto-open task when a drug becomes high risk before execution", async () => {
tencentIM.mockResolvedValue({ success: true });
const drugId = new ObjectId();
const taskUpdateOne = jest.fn().mockResolvedValue({ modifiedCount: 1 });
const order = {
orderId: "order-risk-changed",
corpId: "corp-1",
orderSource: "ALIPAY_MINI",
orderStatus: "processing",
doctorCode: "D001",
consultType: "storeMedicinePurchase",
drugs: [{ _id: drugId.toString() }],
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({
doctorNo: "D001",
onlineStatus: "online",
}),
},
"drug-info": {
find: jest.fn().mockReturnValue({
toArray: jest.fn().mockResolvedValue([
{
_id: drugId,
onSale: true,
name: "风险变更药品",
riskLevel: "高风险",
},
]),
}),
},
[COLLECTION]: { updateOne: taskUpdateOne },
});
const task = {
_id: new ObjectId(),
type: AUTOMATION_TYPES.OPEN_RX,
corpId: "corp-1",
orderId: "order-risk-changed",
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.CANCELLED,
cancelReason: TASK_CANCEL_REASONS.HIGH_RISK_DRUG,
lastError: MANUAL_OPEN_RX_MESSAGE,
}),
})
);
expect(diagnosticRecord).not.toHaveBeenCalled();
expect(tencentIM).toHaveBeenCalledWith(
expect.objectContaining({
toAccount: "D001",
msgBody: [
expect.objectContaining({
MsgContent: expect.objectContaining({
Data: "AUTORXMANUAL",
Ext: MANUAL_OPEN_RX_MESSAGE,
}),
}),
],
}),
db
);
});
test("fails automatic prescription immediately and sends the failure reason", async () => { test("fails automatic prescription immediately and sends the failure reason", async () => {
tencentIM.mockResolvedValue({ success: true }); tencentIM.mockResolvedValue({ success: true });
const taskUpdateOne = jest.fn().mockResolvedValue({ modifiedCount: 1 }); const taskUpdateOne = jest.fn().mockResolvedValue({ modifiedCount: 1 });

View File

@ -114,7 +114,8 @@ exports.formatUploadRxData = function (data) {
sin_doscnt: i.dosage, sin_doscnt: i.dosage,
sin_dosunt: i.dosage_unit, sin_dosunt: i.dosage_unit,
unit: i.unit, unit: i.unit,
appr_flag: i.forceSelfPay === 'Y' ? '2' : '1' appr_flag: i.forceSelfPay === 'Y' ? '2' : '1',
dose_time_name: i.dose_time_name,
} }
if (item.appr_flag === '2') { if (item.appr_flag === '2') {
//[his要求]当appr_flag=2时必填,如果这个药品维护了限制使用范围的,传1,否则就传2 //[his要求]当appr_flag=2时必填,如果这个药品维护了限制使用范围的,传1,否则就传2

View File

@ -227,9 +227,9 @@ async function addConsultDiagnosis(item) {
const chatCount = automated const chatCount = automated
? 3 ? 3
: await db.collection("im-chat-msg").countDocuments({ : await db.collection("im-chat-msg").countDocuments({
From_Account: doctorCode, From_Account: doctorCode,
To_Account: orderId, To_Account: orderId,
}); });
timeData.chatCountQueryTime = dayjs().format('YYYY-MM-DD HH:mm:ss'); timeData.chatCountQueryTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
if (!automated && chatCount < 3) { if (!automated && chatCount < 3) {
return { return {
@ -1143,6 +1143,9 @@ async function auditDiagnosis(item, db) {
async function uploadRx2His(rx, order) { async function uploadRx2His(rx, order) {
try { try {
const uploadData = formatUploadRxData(rx, order); const uploadData = formatUploadRxData(rx, order);
console.log('='.repeat(10))
console.log('='.repeat(10))
console.log(JSON.stringify(uploadData))
const { success, message } = await hnHis({ const { success, message } = await hnHis({
type: 'hlwuploadprescription', type: 'hlwuploadprescription',
data: uploadData data: uploadData

View File

@ -5,6 +5,31 @@ const { getDrugList } = require('./verify');
const { validateApplicableDiagnosisIds } = require('./applicable-diagnosis'); const { validateApplicableDiagnosisIds } = require('./applicable-diagnosis');
let db = ""; let db = "";
function getConfigOptionNames(list) {
return [...new Set((Array.isArray(list) ? list : [])
.map(item => typeof item === "string" ? item : item && item.name)
.filter(Boolean))];
}
async function getHlwDrugOptionLists(corpId) {
if (typeof corpId !== "string" || !corpId.trim()) return {};
const records = await db.collection("hlw-config").find(
{ group: `${corpId.trim()}-medicine-related` },
{ projection: { _id: 0, key: 1, list: 1 } }
).toArray();
const configMap = records.reduce((map, item) => {
if (item.key && Array.isArray(item.list)) map[item.key] = item.list;
return map;
}, {});
const optionLists = {
unit: getConfigOptionNames(configMap["medicine-package-unit"]),
frequency: getConfigOptionNames(configMap["store-medicine-frequence"]),
administration_method: getConfigOptionNames(configMap["store-medicine-administration"]),
dose_time_name: getConfigOptionNames(configMap["medicine-use-time"]),
};
return Object.fromEntries(Object.entries(optionLists).filter(([, list]) => list.length));
}
module.exports = async (item, mongodb) => { module.exports = async (item, mongodb) => {
db = mongodb; db = mongodb;
switch (item.type) { switch (item.type) {
@ -217,6 +242,7 @@ async function addHlwDrugInfo(ctx) {
return { success: false, message: "参数错误" } return { success: false, message: "参数错误" }
} }
try { try {
const optionLists = await getHlwDrugOptionLists(ctx.corpId);
const peopleInfo = { const peopleInfo = {
creator: typeof ctx.operator === "string" ? ctx.operator : "", creator: typeof ctx.operator === "string" ? ctx.operator : "",
creatorId: typeof ctx.operatorId === "string" ? ctx.operatorId : "", creatorId: typeof ctx.operatorId === "string" ? ctx.operatorId : "",
@ -226,7 +252,7 @@ async function addHlwDrugInfo(ctx) {
const { onSale, applicableDiagnosisIds, ...data } = params; const { onSale, applicableDiagnosisIds, ...data } = params;
let newDrug = {}; let newDrug = {};
if (Object.keys(data).length) { if (Object.keys(data).length) {
const [item] = getDrugList([data], true, peopleInfo); const [item] = getDrugList([data], true, peopleInfo, 'hlw', optionLists);
const { msgs, ...drug } = item; const { msgs, ...drug } = item;
if (msgs.length) { if (msgs.length) {
return { success: false, message: msgs.join(",") } return { success: false, message: msgs.join(",") }
@ -259,6 +285,7 @@ async function editHlwDrugInfo(ctx) {
return { success: false, message: "参数错误" } return { success: false, message: "参数错误" }
} }
try { try {
const optionLists = await getHlwDrugOptionLists(ctx.corpId);
const diagnosisIds = await validateApplicableDiagnosisIds(params, db); const diagnosisIds = await validateApplicableDiagnosisIds(params, db);
const { onSale, applicableDiagnosisIds, ...data } = params; const { onSale, applicableDiagnosisIds, ...data } = params;
const currentDrug = await db.collection("drug-info").findOne( const currentDrug = await db.collection("drug-info").findOne(
@ -282,7 +309,7 @@ async function editHlwDrugInfo(ctx) {
updaterId: typeof ctx.operatorId === "string" ? ctx.operatorId : "", updaterId: typeof ctx.operatorId === "string" ? ctx.operatorId : "",
updateTime: Date.now() updateTime: Date.now()
} }
const [item] = getDrugList([data], false, peopleInfo); const [item] = getDrugList([data], false, peopleInfo, 'hlw', optionLists);
const { msgs, ...drug } = item; const { msgs, ...drug } = item;
if (msgs.length) { if (msgs.length) {
return { success: false, message: msgs.join(",") } return { success: false, message: msgs.join(",") }
@ -332,12 +359,13 @@ async function importHlwDrugInfo(ctx) {
return { success: false, message: "参数错误" } return { success: false, message: "参数错误" }
} }
try { try {
const optionLists = await getHlwDrugOptionLists(ctx.corpId);
const peopleInfo = { const peopleInfo = {
creator: typeof ctx.operator === "string" ? ctx.operator : "", creator: typeof ctx.operator === "string" ? ctx.operator : "",
creatorId: typeof ctx.operatorId === "string" ? ctx.operatorId : "", creatorId: typeof ctx.operatorId === "string" ? ctx.operatorId : "",
createTime: Date.now(), createTime: Date.now(),
} }
const datalist = getDrugList(drugs, true, peopleInfo); const datalist = getDrugList(drugs, true, peopleInfo, 'hlw', optionLists);
const createTime = dayjs().valueOf(); const createTime = dayjs().valueOf();
const list = datalist.filter(i => i.msgs.length == 0).map(i => { const list = datalist.filter(i => i.msgs.length == 0).map(i => {
const { msgs, ...drug } = i; const { msgs, ...drug } = i;

View File

@ -22,6 +22,7 @@ const drugFieldDict = [
{ name: '通用名拼音码', field: 'generic_pinyin', verify: (val, label) => validator.verifyString(val, label) }, { name: '通用名拼音码', field: 'generic_pinyin', verify: (val, label) => validator.verifyString(val, label) },
{ name: '抗生素级别', field: 'antibiotic_level', verify: (val, label) => validator.verifyListItem(val, [0, 1, 2, 3], false, label, 0) }, { name: '抗生素级别', field: 'antibiotic_level', verify: (val, label) => validator.verifyListItem(val, [0, 1, 2, 3], false, label, 0) },
{ name: '肿瘤使用', field: 'tumor_use', verify: (val, label) => validator.verifyListItem(val, [0, 1, 2, 3], false, label, 0) }, { name: '肿瘤使用', field: 'tumor_use', verify: (val, label) => validator.verifyListItem(val, [0, 1, 2, 3], false, label, 0) },
{ name: '风险等级', field: 'riskLevel', verify: (val, label) => validator.verifyListItem(val, ['低风险', '高风险'], false, label) },
// 仅在线药品库使用:管理分类(处方药 / OTC)与剂型 // 仅在线药品库使用:管理分类(处方药 / OTC)与剂型
{ name: '管理分类', field: 'manage_category', verify: (val, label) => validator.verifyString(val, label) }, { name: '管理分类', field: 'manage_category', verify: (val, label) => validator.verifyString(val, label) },
{ name: '剂型', field: 'dosage_form', verify: (val, label) => validator.verifyString(val, label) }, { name: '剂型', field: 'dosage_form', verify: (val, label) => validator.verifyString(val, label) },
@ -32,6 +33,7 @@ const drugFieldDict = [
{ name: '给药标准方式', field: 'administration_method', verify: (val, label) => validator.verifyListItem(val, usageList.map(i => i.name), false, label) }, { name: '给药标准方式', field: 'administration_method', verify: (val, label) => validator.verifyListItem(val, usageList.map(i => i.name), false, label) },
{ name: '用药标准天数', field: 'days', verify: (val, label) => validateNumber(val, label) }, { name: '用药标准天数', field: 'days', verify: (val, label) => validateNumber(val, label) },
{ name: '发药标准数量', field: 'recommended_quantity', verify: (val, label) => validateNumber(val, label) }, { name: '发药标准数量', field: 'recommended_quantity', verify: (val, label) => validateNumber(val, label) },
{ name: '用药时机', field: 'dose_time_name', required: true, libType: 'hlw', verify: (val, label) => validator.verifyString(val, label, 1) },
{ name: '用药证明', field: 'medication_proof_desc', verify: (val, label) => validator.verifyString(val, label, 0, 500) }, { name: '用药证明', field: 'medication_proof_desc', verify: (val, label) => validator.verifyString(val, label, 0, 500) },
{ name: '使用限制说明', field: 'usage_restriction_desc', verify: (val, label) => validator.verifyString(val, label, 0, 500) }, { name: '使用限制说明', field: 'usage_restriction_desc', verify: (val, label) => validator.verifyString(val, label, 0, 500) },
{ name: '适用性别', field: 'suitableGender', verify: (val, label) => validator.verifyListItem(val, ['common', 'male', 'female'], false, label) }, { name: '适用性别', field: 'suitableGender', verify: (val, label) => validator.verifyListItem(val, ['common', 'male', 'female'], false, label) },
@ -50,7 +52,7 @@ function validateNumber(val, label, len = 0, required = false) {
return validator.verifyNumber(val, label, len) return validator.verifyNumber(val, label, len)
} }
function getDrugList(arr, verifyEveryField = true, extraInfo = {}, libType = 'hlw') { function getDrugList(arr, verifyEveryField = true, extraInfo = {}, libType = 'hlw', optionLists = {}) {
const fieldDict = drugFieldDict.filter(item => !item.libType || item.libType === libType); const fieldDict = drugFieldDict.filter(item => !item.libType || item.libType === libType);
return arr.map(drug => { return arr.map(drug => {
const res = fieldDict.reduce((obj, item) => { const res = fieldDict.reduce((obj, item) => {
@ -59,7 +61,9 @@ function getDrugList(arr, verifyEveryField = true, extraInfo = {}, libType = 'hl
return obj return obj
} }
const val = typeof drug[key] === 'string' ? drug[key].trim() : drug[key]; const val = typeof drug[key] === 'string' ? drug[key].trim() : drug[key];
const [pass, res] = item.verify(val, item.name); const [pass, res] = Array.isArray(optionLists[item.field])
? validator.verifyListItem(val, optionLists[item.field], Boolean(item.required), item.name)
: item.verify(val, item.name);
if (pass) { if (pass) {
obj[item.field] = res; obj[item.field] = res;
} else { } else {