From 0165c2c064adcc07a5d97c9934381dd6d999b9e5 Mon Sep 17 00:00:00 2001 From: huxuejian Date: Mon, 21 Sep 2026 17:04:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8E=A5=E5=8F=A3=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hlw/drug-info/index.js | 29 +++++-- hlw/hlw-doctor/index.js | 3 + hlw/hlw-schedule/index.js | 163 ++++++++++++++++++++++++++++++++++++++ hlw/index.js | 5 ++ trigger.js | 17 +++- 5 files changed, 209 insertions(+), 8 deletions(-) create mode 100644 hlw/hlw-schedule/index.js diff --git a/hlw/drug-info/index.js b/hlw/drug-info/index.js index 0b0e859..083fb0e 100644 --- a/hlw/drug-info/index.js +++ b/hlw/drug-info/index.js @@ -261,6 +261,8 @@ async function addHlwDrugInfo(ctx) { } const diagnosisCodes = await validateApplicableDiagnosisCodes(params, db); const { onSale, applicableDiagnosisCodes, ...data } = params; + // ERP ID is allocated by the service so clients cannot supply or predict it. + data.erpId = await generateDrugErpId(db); let newDrug = {}; if (Object.keys(data).length) { const [item] = getDrugList([data], true, peopleInfo, 'hlw', optionLists); @@ -299,6 +301,9 @@ async function editHlwDrugInfo(ctx) { const optionLists = await getHlwDrugOptionLists(ctx.corpId); const diagnosisCodes = await validateApplicableDiagnosisCodes(params, db); const { onSale, applicableDiagnosisCodes, ...data } = params; + // ERP ID is immutable after creation. Ignore it even when sent by an older + // client or a manually constructed request. + delete data.erpId; const currentDrug = await db.collection("drug-info").findOne( { _id: new ObjectId(id) }, { projection: { erpId: 1, product_id: 1, product_id_str: 1 } } @@ -306,13 +311,6 @@ async function editHlwDrugInfo(ctx) { if (!currentDrug) { return { success: false, message: "未找到对应药品" } } - const legacyErpId = currentDrug.product_id_str || currentDrug.product_id; - const erpId = Object.prototype.hasOwnProperty.call(data, "erpId") - ? data.erpId - : (currentDrug.erpId || (legacyErpId || legacyErpId === 0 ? String(legacyErpId) : "")); - if (typeof erpId !== "string" || !erpId.trim()) { - return { success: false, message: "ERP ID不能为空" } - } let updateData = {}; if (Object.keys(data).length) { const peopleInfo = { @@ -349,6 +347,23 @@ async function editHlwDrugInfo(ctx) { } +async function generateDrugErpId(db) { + const key = 'drug-erp-id'; + const type = 'drug-erp-id'; + const result = await db.collection('sequence-counter').findOneAndUpdate( + { key, type }, + { + $inc: { sequence: 1 }, + $setOnInsert: { key, type } + }, + { + upsert: true, + returnDocument: 'after' + } + ); + return String(result.sequence).padStart(6, '0'); +} + async function setHlwDrugOnSale(ctx) { const { ids, onSale, updaterId, updater } = ctx; const _ids = Array.isArray(ids) ? ids.filter(i => typeof i === 'string' && i.trim()).map(i => new ObjectId(i.trim())) : [ids]; diff --git a/hlw/hlw-doctor/index.js b/hlw/hlw-doctor/index.js index 068ed17..3a27e88 100644 --- a/hlw/hlw-doctor/index.js +++ b/hlw/hlw-doctor/index.js @@ -558,6 +558,9 @@ async function getAllDoctorNos(ctx) { try { const jobs = Array.isArray(ctx.jobs) ? ctx.jobs : ['doctor']; const query = ctx.showAll === true ? {} : { job: { $in: jobs } }; + if (typeof ctx.corpId === 'string' && ctx.corpId.trim()) { + query.corpId = ctx.corpId.trim(); + } const res = await db .collection("hlw-doctor") .find(query, { projection: { doctorNo: 1, doctorName: 1, job: 1 } }) diff --git a/hlw/hlw-schedule/index.js b/hlw/hlw-schedule/index.js new file mode 100644 index 0000000..d025ded --- /dev/null +++ b/hlw/hlw-schedule/index.js @@ -0,0 +1,163 @@ +const dayjs = require('dayjs'); + +module.exports = async (item, db) => { + switch (item.type) { + case 'getHlwSchedule': + return getHlwSchedule(item, db); + case 'saveHlwSchedule': + return saveHlwSchedule(item, db); + case 'setTodayScheduledPeopleOnline': + return setTodayScheduledPeopleOnline(db); + } +}; + +function getDate(value) { + if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return ''; + const parsed = dayjs(value); + return parsed.isValid() && parsed.format('YYYY-MM-DD') === value ? value : ''; +} + +function getMonth(value) { + if (typeof value !== 'string' || !/^\d{4}-\d{2}$/.test(value)) return ''; + const parsed = dayjs(`${value}-01`); + return parsed.isValid() && parsed.format('YYYY-MM') === value ? value : ''; +} + +async function getHlwSchedule({ corpId, month }, db) { + const normalizedCorpId = typeof corpId === 'string' ? corpId.trim() : ''; + const normalizedMonth = getMonth(month); + if (!normalizedCorpId || !normalizedMonth) return { success: false, message: '机构或月份参数错误' }; + + try { + const startDate = `${normalizedMonth}-01`; + const endDate = dayjs(startDate).add(1, 'month').format('YYYY-MM-DD'); + const data = await db.collection('hlw-schedule') + .find({ corpId: normalizedCorpId, date: { $gte: startDate, $lt: endDate } }, { projection: { _id: 0 } }) + .sort({ date: 1 }) + .toArray(); + return { success: true, message: '查询成功', data }; + } catch (error) { + return { success: false, message: error.message || '查询排班失败' }; + } +} + +async function saveHlwSchedule({ corpId, date, doctorNos = [], pharmacistNos = [], operator, operatorId }, db) { + const normalizedCorpId = typeof corpId === 'string' ? corpId.trim() : ''; + const normalizedDate = getDate(date); + if (!normalizedCorpId || !normalizedDate) return { success: false, message: '机构或日期参数错误' }; + if (dayjs(normalizedDate).isBefore(dayjs(), 'day')) return { success: false, message: '过去日期的排班不可编辑' }; + + const normalizeNos = values => [...new Set((Array.isArray(values) ? values : []) + .filter(value => typeof value === 'string' && value.trim()) + .map(value => value.trim()))]; + const normalizedDoctorNos = normalizeNos(doctorNos); + const normalizedPharmacistNos = normalizeNos(pharmacistNos); + + try { + const personnel = await db.collection('hlw-doctor').find( + { + corpId: normalizedCorpId, + doctorNo: { $in: [...normalizedDoctorNos, ...normalizedPharmacistNos] }, + job: { $in: ['doctor', 'pharmacist'] } + }, + { projection: { doctorNo: 1, doctorName: 1, job: 1 } } + ).toArray(); + const peopleByNo = new Map(personnel.map(person => [person.doctorNo, person])); + const toPeople = (nos, job) => { + const people = nos.map(no => peopleByNo.get(no)); + return people.length === nos.length && people.every(person => person && person.job === job) + ? people.map(({ doctorNo, doctorName }) => ({ doctorNo, doctorName })) + : null; + }; + const doctors = toPeople(normalizedDoctorNos, 'doctor'); + const pharmacists = toPeople(normalizedPharmacistNos, 'pharmacist'); + if (!doctors || !pharmacists) return { success: false, message: '排班人员不存在或岗位不匹配' }; + + await db.collection('hlw-schedule').updateOne( + { corpId: normalizedCorpId, date: normalizedDate }, + { + $set: { + doctors, + pharmacists, + updater: typeof operator === 'string' ? operator : '', + updaterId: typeof operatorId === 'string' ? operatorId : '', + updateTime: Date.now() + }, + $setOnInsert: { corpId: normalizedCorpId, date: normalizedDate, createTime: Date.now() } + }, + { upsert: true } + ); + if (normalizedDate === dayjs().format('YYYY-MM-DD')) { + const onlineResult = await setTodayScheduledPeopleOnline(db); + if (!onlineResult.success) { + return { success: false, message: `排班已保存,但设置当天排班人员在线失败:${onlineResult.message}` }; + } + } + return { success: true, message: '排班保存成功' }; + } catch (error) { + return { success: false, message: error.message || '保存排班失败' }; + } +} + +async function setTodayScheduledPeopleOnline(db) { + const date = dayjs().format('YYYY-MM-DD'); + try { + const schedules = await db.collection('hlw-schedule') + .find({ date }, { projection: { corpId: 1, doctors: 1, pharmacists: 1 } }) + .toArray(); + const peopleByCorp = new Map(); + const addPeople = (corpId, people, job) => { + if (typeof corpId !== 'string' || !corpId.trim()) return; + if (!peopleByCorp.has(corpId)) { + peopleByCorp.set(corpId, { doctors: new Set(), pharmacists: new Set() }); + } + const target = job === 'doctor' + ? peopleByCorp.get(corpId).doctors + : peopleByCorp.get(corpId).pharmacists; + (Array.isArray(people) ? people : []).forEach((person) => { + if (typeof person?.doctorNo === 'string' && person.doctorNo.trim()) { + target.add(person.doctorNo.trim()); + } + }); + }; + + schedules.forEach(({ corpId, doctors, pharmacists }) => { + addPeople(corpId, doctors, 'doctor'); + addPeople(corpId, pharmacists, 'pharmacist'); + }); + + const updateTime = Date.now(); + const operations = []; + for (const [corpId, { doctors, pharmacists }] of peopleByCorp) { + if (doctors.size > 0) { + operations.push({ + updateMany: { + filter: { corpId, doctorNo: { $in: [...doctors] }, job: 'doctor' }, + update: { $set: { onlineStatus: 'online', offlineTime: null, updateTime } } + } + }); + } + if (pharmacists.size > 0) { + operations.push({ + updateMany: { + filter: { corpId, doctorNo: { $in: [...pharmacists] }, job: 'pharmacist' }, + update: { $set: { onlineStatus: 'online', offlineTime: null, updateTime } } + } + }); + } + } + + if (operations.length === 0) { + return { success: true, message: '当天无排班人员', data: { date, matchedCount: 0, modifiedCount: 0 } }; + } + + const result = await db.collection('hlw-doctor').bulkWrite(operations); + return { + success: true, + message: '当天排班人员已上线', + data: { date, matchedCount: result.matchedCount, modifiedCount: result.modifiedCount } + }; + } catch (error) { + return { success: false, message: error.message || '设置当天排班人员在线失败' }; + } +} diff --git a/hlw/index.js b/hlw/index.js index 45e6ddd..49832ad 100644 --- a/hlw/index.js +++ b/hlw/index.js @@ -29,6 +29,7 @@ const chineseMedicineOrder = require("./chinese-medicine-order"); const hlwPatient = require("./hlw-patient"); const scannedCardArchive = require("./scanned-card-archive"); const selfAuth = require("./self-auth"); +const hlwSchedule = require("./hlw-schedule"); module.exports = async (item, db, context) => { switch (item.type) { @@ -88,6 +89,10 @@ module.exports = async (item, db, context) => { case "autoSetPeopleOffline": case "getDoctorByNo": return await hlwDoctor(item, db); + case "getHlwSchedule": + case "saveHlwSchedule": + case "setTodayScheduledPeopleOnline": + return await hlwSchedule(item, db); case "getDiagnosisList": case "deleteHlwDiagnosis": case "updateHlwDiagnosis": diff --git a/trigger.js b/trigger.js index a336b10..1d96832 100644 --- a/trigger.js +++ b/trigger.js @@ -4,9 +4,24 @@ const dayjs = require("dayjs"); exports.initSchedule = () => { // triggerEveryMinute() - triggerAtNight() + triggerAtNight(); + triggerDailySchedulePeopleOnline(); }; +function triggerDailySchedulePeopleOnline() { + // node-schedule 格式:秒 分 时 日 月 周;每天 00:30 将当天排班的医生、药师上线。 + schedule.scheduleJob("0 30 0 * * *", async () => { + try { + const result = await api.getInternetHospitalApi({ type: "setTodayScheduledPeopleOnline" }); + if (!result?.success) { + console.log(`[${dayjs().format('YYYY-MM-DD HH:mm:ss:SSS')}] 排班人员自动上线失败: ${result?.message || '未知错误'}`); + } + } catch (error) { + console.log(`[${dayjs().format('YYYY-MM-DD HH:mm:ss:SSS')}] 排班人员自动上线失败: ${error.message}`); + } + }); +} + async function triggerEveryMinute() { schedule.scheduleJob("0 * * * * *", async () => { runHighFrequencyTask()