From ec816dfdd6110cbb5580e75bec2a25f9fd280ff2 Mon Sep 17 00:00:00 2001 From: huxuejian Date: Thu, 24 Sep 2026 18:22:05 +0800 Subject: [PATCH] =?UTF-8?q?fix:=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 --- README.md | 2 +- hlw/diagnostic-record/document.ts | 2 + hlw/diagnostic-record/format.js | 8 +- hlw/diagnostic-record/index.js | 152 +++--- hlw/hn-his/index.js | 747 ++---------------------------- hlw/index.js | 2 +- hlw/prescription-pdf/index.js | 92 ++-- index.js | 219 +-------- request.js | 4 + 9 files changed, 198 insertions(+), 1030 deletions(-) diff --git a/README.md b/README.md index 5b71b60..00f39f2 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ GET /im-chat/image/:msgId 生产使用前建议补充问诊订单权限验证。 ## 处方 PDF -处方笺下载接口为 `GET /hlw/prescriptions/:id/pdf`;默认输出 A5,传 `paperSize=A4` 可输出 A4。 +处方笺下载接口为 `GET /hlw/prescriptions/:id/pdf`;默认输出 A5,传 `paperSize=A4` 可输出 A4。获取 PDF Base64 内容可调用 `GET /hlw/prescriptions/:id/base64`,响应中的 `data` 字段为 Base64 字符串,也支持 `paperSize=A4`。 服务运行环境需要预装 Chromium/Chrome,并配置其可执行文件路径: diff --git a/hlw/diagnostic-record/document.ts b/hlw/diagnostic-record/document.ts index f7ee1ef..5bfbf02 100644 --- a/hlw/diagnostic-record/document.ts +++ b/hlw/diagnostic-record/document.ts @@ -18,6 +18,7 @@ enum DiagnosticStatus { EXPIRED = 'EXPIRED', // 订单过期 PASS_INVALID = 'PASS_INVALID', // 通过的处方作废 UNPASS_INVALID = 'UNPASS_INVALID', // 未通过的处方作废 + CANCELLED = 'CANCELLED', // 门店主动撤销 INVALID = 'INVALID', // 处方作废 } @@ -122,4 +123,5 @@ interface DiagnosticRecord { needReUpload?: boolean // 门店处方 是否需要重新上传(可能存在的场景 调用his接口上传成功了 但是his未上传医保, 需要药师手动去his的上传处理页面上传) printCount?: number; // 门店自费处方打印次数 lastPrintTime?: number; // 门店自费处方最近一次打印时间 + cancelTime?: number; // 门店主动撤销时间 } diff --git a/hlw/diagnostic-record/format.js b/hlw/diagnostic-record/format.js index 43eab8c..458b0bf 100644 --- a/hlw/diagnostic-record/format.js +++ b/hlw/diagnostic-record/format.js @@ -90,8 +90,8 @@ function decryptDiagnosticRecordList(list) { return list.map((item) => decryptDiagnosticRecordFields(item)); } -exports.formatUploadRxData = function (data) { - return { +exports.formatUploadRxData = function (data, opt) { + const result = { // pdfUrl: getPrescriptionPdfUrl(data), pay_type: data.feeType === 'YB' ? '1' : '0', patientid: data.patientId, @@ -141,6 +141,10 @@ exports.formatUploadRxData = function (data) { icd_name: i.name, })), }; + if (opt && opt.pdf) { + result.pdf = opt.pdf + } + return result }; diff --git a/hlw/diagnostic-record/index.js b/hlw/diagnostic-record/index.js index 28f9ce8..8b3c335 100644 --- a/hlw/diagnostic-record/index.js +++ b/hlw/diagnostic-record/index.js @@ -1,5 +1,6 @@ let db = ""; const { ObjectId } = require("mongodb"); +const fs = require("fs/promises"); const { getDatabase } = require("../../mongodb"); const dayjs = require("dayjs"); const { decryptOrderList } = require("../consult-order/format"); @@ -20,6 +21,7 @@ const beijingCa = require("../beijing-ca"); const BigNumber = require("bignumber.js"); // const timeApi = require("../hlw-time-duration"); const types = require("./types"); +const { getOrCreatePrescriptionPdf } = require("../prescription-pdf"); /** * 性能优化建议: @@ -75,6 +77,7 @@ const status = { passInvalid: "PASS_INVALID", //通过的处方作废 (目前就订单退费会导致处方作废) unpassInvalid: "UNPASS_INVALID", //未通过的处方作废 (目前就订单退费会导致处方作废) invalid: "INVALID", //处方作废 (目前就订单退费会导致处方作废) + cancelled: "CANCELLED", //门店主动撤销 }; module.exports = async (item, mongodb, user) => { @@ -90,6 +93,8 @@ module.exports = async (item, mongodb, user) => { return await getStoreDiagnosisRecord(item, db, user); case "markStoreRxPrinted": return await markStoreRxPrinted(item, db, user); + case "cancelStoreDiagnosisRecord": + return await cancelStoreDiagnosisRecord(item, db, user); case "getAuditDiagnosisList": return await getAuditDiagnosisList(item, db); case "getAuditedDiagnosisList": @@ -100,8 +105,6 @@ module.exports = async (item, mongodb, user) => { return await getPatientPassDiagnosisRecord(item, db); case "auditDiagnosis": return await auditDiagnosis(item, db); - case "uploadDiagnosis": - return await uploadDiagnosis(item, db); case "orderHasDiagnosis": return await orderHasDiagnosis(item, db); case "getLatestSubmitTime": @@ -140,11 +143,28 @@ module.exports = async (item, mongodb, user) => { return await getPatientWineBuyCount(item, db); case "getYbReviewResult": return await getYbReviewResult(item, db); + case "getpdfbase64": + return { + success: true, + message: "查询成功", + data: await getpdfbase64(item, db), + }; } }; let scheduleTaskIsRunning = false; +async function getpdfbase64(ctx = {}, db) { + const prescriptionId = String(ctx.id || ctx._id || "").trim(); + if (!ObjectId.isValid(prescriptionId)) { + throw new Error("处方 id 格式错误"); + } + + const paperSize = ctx.paperSize === "A4" ? "A4" : "A5"; + const { pdfPath } = await getOrCreatePrescriptionPdf(db, prescriptionId, { paperSize }); + return (await fs.readFile(pdfPath)).toString("base64"); +} + async function scheduleAutoPassAfterPrescription({ rxId, orderId, @@ -554,7 +574,7 @@ async function getStoreDiagnosisRecord(item, db, user) { } const query = { drugStoreNo: { $in: [...new Set(storeIds)] }, - status: status.pass, + status: { $in: [status.pass, status.cancelled] }, }; const page = item.page > 0 && Number.isInteger(item.page) ? item.page : 1; const pageSize = @@ -619,6 +639,56 @@ async function getStoreDiagnosisRecord(item, db, user) { } } +async function cancelStoreDiagnosisRecord(item, db, user) { + const storeIds = await resolveStoreIds(item, db, user); + if ( + typeof item.id !== "string" || + !ObjectId.isValid(item.id.trim()) || + storeIds.length === 0 + ) { + return { success: false, message: "参数错误" }; + } + + try { + const record = await db.collection("diagnostic-record").findOne({ + _id: new ObjectId(item.id.trim()), + drugStoreNo: { $in: [...new Set(storeIds)] }, + status: status.pass, + }); + if (!record) { + return { success: false, message: "处方不存在或当前状态不可撤销" }; + } + + // 医保处方必须先由 HIS 撤销成功,才允许修改本地状态。 + if (record.feeType === "YB") { + const hisResult = await hnHis({ + type: "cancelInsuranceRx", + patientId: record.patientId, + orderno: record.medOrgOrderNo, + }); + if (!hisResult || !hisResult.success) { + return { success: false, message: (hisResult && hisResult.message) || "医保处方撤销失败" }; + } + } + + const cancelTime = Date.now(); + const result = await db.collection("diagnostic-record").updateOne( + { + _id: record._id, + drugStoreNo: { $in: [...new Set(storeIds)] }, + status: status.pass, + }, + { $set: { status: status.cancelled, cancelTime } } + ); + if (result.modifiedCount !== 1) { + return { success: false, message: "处方状态已变更,请刷新后重试" }; + } + return { success: true, message: "处方撤销成功", data: { cancelTime } }; + } catch (e) { + return { success: false, message: e.message || "处方撤销失败" }; + } +} + async function markStoreRxPrinted(item, db, user) { const storeIds = await resolveStoreIds(item, db, user); if ( @@ -1143,7 +1213,9 @@ async function auditDiagnosis(item, db) { async function uploadRx2His(rx, order) { try { - const uploadData = formatUploadRxData(rx, order); + // 获取处方笺pdf + const base64 = await getpdfbase64({ id: rx._id.toString() }, db); + const uploadData = await formatUploadRxData(rx, { pdf: base64 }); const { success, message } = await hnHis({ type: 'hlwuploadprescription', data: uploadData @@ -1281,52 +1353,6 @@ function getOriData(data) { return `医生${doctorName}向患者${patientName}开具的诊断是${diagnosis},开具的药物是${drugs}。`; } -async function uploadDiagnosis(ids) { - if (!Array.isArray(ids) || ids.length == 0) - return { - success: false, - message: "参数错误", - }; - try { - const res = await db - .collection("diagnostic-record") - .find({ _id: { $in: ids }, status: status.pass }) - .limit(ids.length) - .toArray(); - const list = res.map((item) => ({ - payload: formatUploadRxData(item), - _id: item._id, - })); - const res1 = await Promise.all( - list.map((item) => - zytHis({ - type: "hlwuploadprescription", - payload: item.payload, - _id: item._id, - }) - ) - ); - const successIds = res1.filter((i) => i.success).map((i) => i._id); - await db - .collection("diagnostic-record") - .updateMany( - { _id: { $in: successIds } }, - { $set: { uploadStatus: "uploaded" } } - ); - const successCount = successIds.length; - return { - success: true, - message: `成功上传${successCount}条数据`, - }; - } catch (err) { - console.log(err, err.message); - return { - success: false, - message: err.message || "上传失败", - }; - } -} - /** * * orderStatus completed(已完成) processing(处理中) pending(待处理) cancelled(已取消) finished(已结束) @@ -1435,30 +1461,16 @@ async function orderHasDiagnosis(item) { async function reUploadDiagnosticRecord({ _id }) { try { + return const data = await db .collection("diagnostic-record") .findOne({ _id: new ObjectId(_id) }); + console.log(data) if (!data) return { success: false, message: "处方不存在" }; - if (data.prescriptionType !== "storeMedicinePurchase") { - return { success: false, message: "暂只支持门店处方重新上传" }; - } - if (data && data.uploadStatus === "uploaded") - return { success: false, message: "处方已上传" }; - const { uploaded } = await zytHis({ - type: "hlwPrescriptionOrderStatus", - orderno: data.orderId, - patientId: data.patientId, - }); - if (uploaded) { - await db - .collection("diagnostic-record") - .updateOne( - { _id: new ObjectId(_id) }, - { $set: { uploadStatus: "uploaded", hisUploadStatus: "uploaded" } } - ); - return { success: true, message: "处方上传成功" }; - } - const payload = formatUploadRxData(data); + const base64 = await getpdfbase64({ id: data._id.toString() }, db); + const payload = formatUploadRxData(data, { pdf: base64 }); + console.log(payload) + return { success: false, message: '打住' } const { success, message } = await zytHis({ type: "hlwuploadprescription", payload, diff --git a/hlw/hn-his/index.js b/hlw/hn-his/index.js index ee0b8c4..7e1a93c 100644 --- a/hlw/hn-his/index.js +++ b/hlw/hn-his/index.js @@ -19,56 +19,16 @@ module.exports = async (item) => { return await getHisCustomer(item); case "addHisCustomer": return await addHisCustomer(item); - case "registration": - return await registration(item); - case "getPayStatus": - return await getPayStatus(item); - case "hlwRefund": - return await hlwRefund(item); case "hlwuploadprescription": return await hlwuploadprescription(item); - case "hlwPrescriptionOrderStatus": - return await hlwPrescriptionOrderStatus(item); - case "getHisPrescriptionUploadStatus": - return await getHisPrescriptionUploadStatus(item); - case "getHisStoreRegList": - return await getHisStoreRegList(item); - case "insurancePreSettle": - return await insurancePreSettle(item); - case "insuranceSettle": - return await insuranceSettle(item); - case "insuranceRefund": - return await insuranceRefund(item); - case "getRecent90daysDrugRecord": - return await getRecent90daysDrugRecord(item); - case "uploadMedicinePurchaseRx": - return await uploadMedicinePurchaseRx(item); - case "uploadWinePurchaseRx": - return await uploadWinePurchaseRx(item); - case "getHisDrugInfo": - return await getHisDrugInfo(item); - case "getHisMedicinePurchase": - return await getHisMedicinePurchase(item); - case "cancelMedicinePurchase": - return await cancelMedicinePurchase(item); - case "refundMedicinePurchase": - return await refundMedicinePurchase(item); - case "getHisInvoice": - return await getHisInvoice(item); - case "updateMedicinePurchaseDelivery": - return await updateMedicinePurchaseDelivery(item); - case 'syncHisChargeStatus': - return await syncHisChargeStatus(item); case "hisTest": return await hisTest(item); case "getHisEmployeeInfo": return await getHisEmployeeInfo(item); - case "syncHisRefundStatus": - return await syncHisRefundStatus(item); - case "getWineDrugStockInfo": - return await getWineDrugStockInfo(item); case "ybReviewPrescription": return await ybReviewPrescription(item); + case "cancelInsuranceRx": + return await cancelInsuranceRx(item); } }; @@ -199,490 +159,6 @@ async function hlwuploadprescription(item) { } } -// 预约挂号 -async function registration(item) { - const { patientId, doctorCode, appwin = 'line', unitCode, registerId, chargecode, reg_fee, shopid, shopname, op_id = '', channel } = item; - if (AppWin[appwin] == undefined) { - return { - success: false, - message: "appwin参数错误" - } - } - if (AppWin[appwin] == '线下挂号' && (typeof op_id !== 'string' || op_id.trim() === '')) { - return { - success: false, - message: "op_id参数错误" - } - } - try { - const url = `${baseUrl}/his/hlw/registration`; - // 根据dayjs 判断是上午 还是下午 - const ampm = dayjs().hour() < 12 ? "a" : "p"; - const requestday = dayjs().format("YYYY-MM-DD HH:mm:ss"); - // registerId 为 H开头 15位随机数 - const payload = { - patientId, - doctorCode, - unitCode, - requestday, - ampm, - registerId, - chargecode, - reg_fee, - shopid, - shopname, - chargetype: "普通", - appwin - }; - if (AppWin[appwin] == '线下挂号') { - payload.op_id = op_id; - } - if (channel === 1) { - payload.channel = 1 - } - const { data, status_code, message } = await request.main( - url, - payload, - "POST" - ); - if (data && data.register && data.register.medorg_order_no) { - return { - success: true, - data, - message: message || "挂号成功", - }; - } else { - return { - success: false, - message: message || "挂号失败", - }; - } - } catch (err) { - return { - success: false, - message: err.message || "挂号失败" - }; - } -} - -//订单支付状态查询 -async function getPayStatus(item) { - const { patientId, registerId, medorgOrderNo } = item; - try { - const url = `${baseUrl}/his/hlw/settlement`; - const { status, status_code, message, ...rest } = await request.main( - url, - { - patientid: patientId, - registerid: registerId, - medorg_order_no: medorgOrderNo, - }, - "POST" - ); - const settlement = - rest.data && rest.data.settlement ? rest.data.settlement : null; - //settlement.status 0 已收费 1 已退费 5 未收费 x 已作废 - if (status_code == 200) { - return { - success: true, - status: settlement ? settlement.status : '', - settlement, - message: "订单查询成功", - }; - } else { - return { - success: false, - message: message || "获取失败", - }; - } - } catch (err) { - return { - success: false, - message: "获取失败", - }; - } -} - -//医保患者退费 -async function hlwRefund(item) { - const { patientId, registerId, medorgOrderNo } = item; - try { - const url = `${baseUrl}/his/hlw/refund`; - const { data, status_code, message, ...rest } = await request.main( - url, - { - patientid: patientId, - registerid: registerId, - medorg_order_no: medorgOrderNo, - }, - "POST" - ); - if (status_code == 200 && !message) { - return { - success: true, - message: "退费成功", - }; - } else { - return { - success: false, - message: message || "退费失败", - }; - } - } catch (err) { - return { - success: false, - message: "退费失败", - }; - } -} - - - -// 外配处方状态查询 -async function hlwPrescriptionOrderStatus(item) { - const { patientId, orderno } = item; - try { - const url = `${baseUrl}/his/hlw/orderstatus`; - const { status_code, message, data } = await request.main( - url, - { - patientid: patientId, - orderno, - }, - "POST" - ); - if (status_code == 200 && data && data.orderstatus) { - return { - success: true, - uploaded: data.orderstatus.status == '1', - status: data.orderstatus.status, - message: "查询成功", - }; - } else { - return { - success: false, - message: message || "查询失败", - }; - } - } catch (err) { - return { - success: false, - message: "查询失败", - }; - } -} - -async function getHisPrescriptionUploadStatus(item) { - const { patientId, orderno, _id } = item; - try { - const url = `${baseUrl}/his/hlw/orderstatus`; - const { status_code, message, data } = await request.main( - url, - { - patientid: patientId, - orderno, - }, - "POST" - ); - if (status_code == 200 && data && data.orderstatus && data.orderstatus.status == 1) { - return { orderno, hisUploadStatus: 'uploaded', _id } - } - if (status_code == 200 && data && data.orderstatus && data.orderstatus.status == -1) { - return { orderno, hisUploadStatus: 'error', _id } - } - if (status_code == 200 && data && data.orderstatus && data.orderstatus.status == 0) { - return { orderno, hisUploadStatus: 'notUploaded', _id } - } - return { - success: false, - message: message || "查询失败", - - }; - } catch (err) { - return { - success: false, - message: "查询失败", - }; - } - -} - -async function getHisStoreRegList(item) { - const { storeId: shopid } = item; - if (typeof shopid !== 'string' || shopid.trim() === '') { - return { - success: false, - message: "店铺id无效", - }; - } - try { - const url = `${baseUrl}/his/hlw/registerquery`; - const { status_code, message, data } = await request.main( - url, - { - shopid, - requestday: dayjs().format('YYYY-MM-DD HH:mm:ss'), - status: "0", - starttime: dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'), - endtime: dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss'), - hospid: "108", - }, - "POST" - ); - if (status_code == 200 && Array.isArray(data)) { - return { - success: true, - list: data, - message: "查询成功", - }; - } else { - return { - success: false, - message: message || "查询失败", - }; - } - } catch (err) { - return { - success: false, - message: "查询失败", - }; - } -} - -async function insurancePreSettle(ctx) { - const { - medorg_order_no: orderno, - patientId: patientid, - mdtrtCertNo: mdtrt_cert_no, - mdtrtCertType: mdtrt_cert_type, - cardSn: card_sn, - operator_code, - operator_name, - shopid, - hospid = "108" - } = ctx; - try { - const url = `${baseUrl}/his/hlw/presettle`; - const payload = { orderno, patientid, mdtrt_cert_no, mdtrt_cert_type, card_sn, operator_code, operator_name, hospid, shopid }; - const { data, status_code, message } = await request.main(url, payload, "POST"); - - if (status_code == 200 && !message) { - return { success: true, data, message: '预结算成功' } - } - return { success: false, message: message || '预结算失败' } - } catch (e) { - return { success: false, message: e.message || '预结算失败' } - } -} - -async function insuranceSettle(ctx) { - const { - patientid, - orderno, - mdtrt_id, - mdtrt_cert_no, - outparm_1101, - outparm_2206A, - inparm_2207A, - outparm_2204, - totalamount, - selfpay, - operator_code, - operator_name - } = ctx; - const payload = { - patientid, - orderno, - settle_method: '7', - paymethod: '1', // 1: 现金 w: 支付宝 z: 微信 - paysn: '', - payrequestsn: '', - mdtrt_id, - mdtrt_cert_no, - outparm_1101, - outparm_2206A, - inparm_2207A, - outparm_2204, - totalamount, - selfpay, - operator_code, - operator_name - } - try { - const url = `${baseUrl}/his/hlw/ybsettle`; - // const url = `${baseUrl}/his/hlw/presettle`; - const { data, status_code, message } = await request.main(url, payload, "POST"); - if (status_code == 200 && !message) { - return { success: true, data, message: '结算成功' } - } - return { success: false, message: message || '结算失败' } - } catch (e) { - return { success: false, message: e.message || '结算失败' } - } -} - -async function insuranceRefund(ctx) { - const { type, ...payload } = ctx; - try { - const url = `${baseUrl}/his/hlw/ybrefund`; - // const url = `${baseUrl}/his/hlw/presettle`; - const { data, status_code, message } = await request.main(url, payload, "POST"); - if (status_code == 200 && !message) { - return { success: true, data, message: '退费成功' } - } - return { success: false, message: message || '退费失败' } - } catch (e) { - return { success: false, message: e.message || '退费失败' } - } -} - -async function getRecent90daysDrugRecord(ctx) { - const patientid = typeof ctx.patientId === 'string' ? ctx.patientId.trim() : ''; - if (patientid === '') { - return { success: false, message: '患者ID不能为空' } - } - try { - const url = `${baseUrl}/his/hlw/patientzj3100`; - const payload = { patientid, hospid: "108", doctorCode: '10021' }; - if (typeof ctx.doctorcode === 'string' && ctx.doctorcode.trim() !== '') { - payload.doctorcode = ctx.doctorcode.trim(); - } - const { data, status_code, message } = await request.main(url, payload, "POST"); - - - if (status_code == 200 && !message && data && data.outparm_zj3100) { - // 解析医保用药数据列表 - const decodedBuffer = Buffer.from(data.outparm_zj3100, 'base64'); - const gbkDecodedStr = decode(decodedBuffer, 'gbk'); - const res = JSON.parse(gbkDecodedStr); - const drugList = res && res.data && Array.isArray(res.data.lists) ? res.data.lists.map(i => ({ - hiListName: i.hiListName, - insuranceCode: i.hilistCode, - mdtrtId: i.mdtrtId, - quantity: i.cnt, - spec: i.spec && i.spec !== 'null' ? i.spec : '', - unit: i.unit && i.unit !== 'null' ? i.unit : '' - })) : []; - - // 解析医保记录 - const decodedBuffer1 = Buffer.from(data.outparm_zj3101, 'base64'); - const gbkDecodedStr1 = decode(decodedBuffer1, 'gbk'); - const res1 = JSON.parse(gbkDecodedStr1); - const recordList = res1 && res1.data && Array.isArray(res1.data.lists) ? res1.data.lists.map(i => ({ - mdtrtId: i.mdtrtId, - date: i.admDate, - medinsName: i.medinsName, - })) : []; - - return { success: true, drugList, message: '查询成功', recordList } - } - return { success: false, message: message || '查询失败' } - } catch (e) { - return { success: false, message: e.message || '查询失败' } - } -} - -async function getHisDrugInfo(item) { - try { - const ignoreAreaCode = typeof item.ignoreAreaCode === 'boolean' ? item.ignoreAreaCode : false; - if (!ignoreAreaCode && (typeof item.areaCode !== 'string' || item.areaCode.trim() === '')) { - return { success: false, message: '区域代码不能为空' } - } - const showAll = typeof item.showAll === 'boolean' ? item.showAll : false; - const gjbm = Array.isArray(item.insuranceCodes) ? item.insuranceCodes.filter(i => typeof i === 'string' && i.trim() !== '') : []; - console.log('gjbm: ', gjbm) - if (gjbm.length === 0 && !showAll) { - return { success: false, message: '医保编码不能为空' } - } - const url = `${baseUrl}/his/hlw/drug`; - const payload = { - hospid: '108', - drug_type: 'xy', - area_code: typeof item.areaCode === 'string' ? item.areaCode.trim() : '', - gjbm: gjbm.join(',') - } - const { code, status_code, message, data } = await request.main( - url, - payload, - "POST" - ); - if (status_code == 200 && code == '1' && !message) { - return { - success: true, - message: "[his]查询药品信息成功", - data: data && Array.isArray(data.drugs) ? data.drugs : [] - }; - } else { - return { - success: false, - message: `[his]查询药品信息传失败: ${message || '未知错误'}`, - }; - } - } catch (err) { - return { - success: false, - message: `[his]查询药品信息传错误: ${err.message || '未知'}`, - }; - } -} - -async function uploadMedicinePurchaseRx(item) { - try { - const { data } = item; - const url = `${baseUrl}/his/hlw/mzordersave`; - const { code, status_code, message } = await request.main( - url, - data, - "POST" - ); - if (status_code == 200 && code == '1' && !message) { - return { - success: true, - message: "[his]购药订单处上传成功", - }; - } else { - return { - success: false, - message: `[his]购药订单处方上传失败: ${message || '未知错误'}`, - }; - } - } catch (err) { - return { - success: false, - message: `[his]购药订单处方上传错误: ${err.message || '未知'}`, - }; - } -} - -async function uploadWinePurchaseRx(item) { - try { - const { data } = item; - const url = `${baseUrl}/his/hlw/tcsave`; - const { code, status_code, message } = await request.main( - url, - data, - "POST" - ); - console.log('data', code, status_code, message); - if (status_code == 200 && code == '1' && !message) { - return { - success: true, - message: "[his]浸酒处上传成功", - }; - } else { - return { - success: false, - message: `[his]浸酒处方上传失败: ${message || '未知错误'}`, - }; - } - } catch (err) { - return { - success: false, - message: `[his]浸酒处方上传错误: ${err.message || '未知'}`, - }; - } -} - async function ybReviewPrescription(item) { try { const params = { type: "rationalDrugReview", data: item.data }; @@ -706,155 +182,6 @@ async function ybReviewPrescription(item) { } } - -async function getHisMedicinePurchase({ patientId, orderNo }) { - try { - const url = `${baseUrl}/his/hlw/orderquery`; - const { code, status_code, message, data } = await request.main(url, { patientid: patientId, orderno: orderNo }, "POST"); - const order = data && Array.isArray(data.orders) ? data.orders[0] : null; - if (status_code == 200 && code == '1' && !message) { - if (order) { - return { success: true, data: order, message: '查询成功' } - } - return { success: false, message: `[his]处方订单查询失败: 未找到订单` } - } - return { - success: false, - message: `[his]处方订单查询失败: ${message || '未知错误'}`, - }; - } catch (err) { - return { success: false, message: `[his]处方订单查询错误: ${err.message || '未知错误'}` }; - } -} - -async function cancelMedicinePurchase({ patientId: patientid, orderNo: orderno }) { - try { - const url = `${baseUrl}/his/hlw/ordercancel`; - const { code, status_code, message } = await request.main(url, { patientid, orderno }, "POST"); - if (status_code == 200 && code == '1' && !message) { - return { success: true, message: '[his]取消购药订单处方成功' } - } - return { success: false, message: `[his]取消购药订单处方失败: ${message || '未知错误'}` } - } catch (err) { - return { success: false, message: `[his]取消购药订单处方错误: ${err.message || '未知错误'}` }; - } -} - -async function refundMedicinePurchase({ patientId: patientid, orderNo: orderno }) { - try { - const url = `${baseUrl}/his/hlw/orderrefund`; - const { code, status_code, message } = await request.main(url, { patientid, orderno }, "POST"); - if (status_code == 200 && code == '1' && !message) { - return { success: true, message: '[his]退费购药订单处方成功' } - } - return { success: false, message: `[his]退费购药订单处方失败: ${message || '未知错误'}` } - } catch (err) { - return { success: false, message: `[his]退费购药订单处方错误: ${err.message || '未知错误'}` }; - } -} - -async function getHisInvoice(item) { - try { - const { patientId, orderNo } = item; - const url = `${baseUrl}/his/hlw/dzfp`; - let { code, status_code, message, data } = await request.main(url, { patientid: patientId, orderno: orderNo }, "POST"); - if (status_code == 200 && code == '1' && !message) { - const dzfp = data && Array.isArray(data.dzfp) ? data.dzfp[0] : null; - return { success: true, data: dzfp && dzfp.dzfp_url ? dzfp.dzfp_url : '', message: '发票查询成功' } - } - return { success: false, message: `[his]发票查询失败: ${message || '未知错误'}` } - } catch (err) { - return { success: false, message: `[his]发票查询错误: ${err.message || '未知错误'}` }; - } -} - -async function updateMedicinePurchaseDelivery(item) { - try { - const { - patientId: patientid, - orderNo: orderno, - backFlag: back_flag = 0, - shippingType: deliv_type, - shippingNo: deliv_no, - shippingFee: deliv_fee, - pickupCode: tracking_no, - payTime: pay_dt, - tradeNo: pay_orderno, - receiveName: shipp_name, - receivePhone: shipp_tel, - fullAddress: shipp_addr, - invoice - } = item; - const payload = { - patientid, - orderno, - back_flag, // 0 收取运费 | 1 退还运费 - deliv_type, - deliv_no, - deliv_fee, - pay_dt, - pay_orderno, - shipp_name, - shipp_tel, - shipp_addr, - tracking_no, - invoice - } - const url = `${baseUrl}/his/hlw/delivery`; - let { code, status_code, message, data } = await request.main(url, payload, "POST"); - if (status_code == 200 && code == '1' && !message) { - return { success: true, data, message: '[his]同步配药订单物流信息成功' } - } - return { success: false, message: `[his]同步配药订单物流信息失败: ${message || '未知错误'}` } - } catch (err) { - return { success: false, message: `[his]同步配药订单物流信息错误: ${err.message || '未知错误'}` }; - } -} - -async function syncHisChargeStatus(ctx) { - try { - const payload = { - patientid: ctx.patientId, - orderno: ctx.orderNo, - settle_method: 1, - paymethod: 'h', - paysn: ctx.tradeNo, - payrequestsn: ctx.outTradeNo, - totalamount: ctx.totalAmount, - selfpay: ctx.totalAmount, - operator_code: 'hlw', - operator_name: '互联网医院' - } - const url = `${baseUrl}/his/hlw/ybsettle`; - let { code, status_code, message, data } = await request.main(url, payload, "POST"); - if (status_code == 200 && code == '1' && !message) { - return { success: true, data, message: '[his]同步浸酒订单收费成功' } - } - return { success: false, message: `[his]同步浸酒订单收费失败: ${message || '未知错误'}` } - } catch (err) { - return { success: false, message: `[his]同步浸酒订单收费错误: ${err.message || '未知错误'}` }; - } -} - -async function syncHisRefundStatus(ctx) { - try { - const payload = { - patientid: ctx.patientId, - orderno: ctx.orderNo, - ptlsh: ctx.refundSerialNo - } - const url = `${baseUrl}/his/hlw/orderrefund`; - let { code, status_code, message, data } = await request.main(url, payload, "POST"); - if (status_code == 200 && code == '1' && !message) { - return { success: true, data, message: '[his]同步浸酒订单退费成功' } - } - return { success: false, message: `[his]同步浸酒订单退费失败: ${message || '未知错误'}` } - } catch (err) { - return { success: false, message: `[his]同步浸酒订单退费错误: ${err.message || '未知错误'}` }; - } -} - - async function getHisEmployeeInfo(item) { const { code } = item; try { @@ -877,46 +204,6 @@ async function getHisEmployeeInfo(item) { } } -async function getWineDrugStockInfo(item) { - try { - if (typeof item.code !== 'string' || item.code.trim() === '') { - return { success: false, message: '中药饮片代码不能为空' } - } - if (!(Number.isInteger(item.num) && item.num > 0)) { - return { success: false, message: '中药饮片数量必须是整数且大于0' } - } - const url = `${baseUrl}/his/hlw/drug`; - const payload = { - hospid: '108', - drug_type: 'zcy', - tc_code: item.code.trim(), - tc_number: item.num - } - const { code, status_code, message, data } = await request.main( - url, - payload, - "POST" - ); - if (status_code == 200 && code == '1') { - return { - success: true, - message: "[his]查询药品信息成功", - data: data - }; - } - return { - success: false, - message: `中药饮品查询失败: ${message || '未知错误'}`, - }; - - } catch (err) { - return { - success: false, - message: `中药饮品查询失败错误: ${err.message || '未知'}`, - }; - } -} - async function hisTest() { // const data = { // patientId: '10428622', @@ -951,4 +238,32 @@ async function updateHisDrug({ data: drugArr }) { message: err.message || "同步his药品错误", }; } -} \ No newline at end of file +} + +async function cancelInsuranceRx(item) { + const patientId = typeof item.patientId === "string" ? item.patientId.trim() : ""; + const orderno = typeof item.orderno === "string" ? item.orderno.trim() : ""; + if (!patientId || !orderno) { + return { success: false, message: "医保处方缺少患者编号或处方编号" }; + } + try { + const params = { type: "hlwPrescriptionOrderCancel", patientId, orderno }; + const { code, status_code, message } = await request.main(url, params, "POST"); + if (status_code == 200 && code == "1") { + return { + success: true, + message: `处方撤销成功`, + }; + } else { + return { + success: false, + message: message || "处方撤销失败", + }; + } + } catch (err) { + return { + success: false, + message: err.message || "处方撤销错误", + }; + } +} diff --git a/hlw/index.js b/hlw/index.js index 49832ad..0c1d9c4 100644 --- a/hlw/index.js +++ b/hlw/index.js @@ -131,7 +131,6 @@ module.exports = async (item, db, context) => { case "getAuditDiagnosisList": case "getAuditedDiagnosisList": case "auditDiagnosis": - case "uploadDiagnosis": case "reUploadDiagnosticRecord": case "getPatientDiagnosisRecord": case "getPatientPassDiagnosisRecord": @@ -139,6 +138,7 @@ module.exports = async (item, db, context) => { case "orderHasDiagnosis": case "getStoreDiagnosisRecord": case "markStoreRxPrinted": + case "cancelStoreDiagnosisRecord": case "getAllDiagnosisRecord": case "getDoctorRxStats": case "getPharmacistRxStats": diff --git a/hlw/prescription-pdf/index.js b/hlw/prescription-pdf/index.js index 0b63356..f4a35bc 100644 --- a/hlw/prescription-pdf/index.js +++ b/hlw/prescription-pdf/index.js @@ -12,9 +12,11 @@ let browserPromise; let activeJobs = 0; const waiters = []; const cachedPdfJobs = new Map(); -const PDF_TEMPLATE_VERSION = "v5"; +const PDF_TEMPLATE_VERSION = "v7"; const MAX_SIGNATURE_IMAGE_BYTES = 1024 * 1024; const SIGNATURE_DOWNLOAD_TIMEOUT_MS = 5000; +const PRESCRIPTION_SEAL_IMAGE_URL = process.env.CONFIG_PRESCRIPTION_SEAL_IMAGE_URL + || "https://hlwyy.hnkhhospital.com/upload-files/other/stamp.png"; const signatureMimeTypes = { ".png": "image/png", @@ -235,14 +237,28 @@ function signature(value, fallback) { : `${escapeHtml(fallback)}`; } +async function addDrugStoreName(db, record) { + if (typeof record.drugStoreName === "string" || typeof record.orderId !== "string" || !record.orderId.trim()) { + return record; + } + try { + const order = await db.collection("consult-order").findOne( + { orderId: record.orderId }, + { projection: { drugStoreName: 1 } } + ); + return { ...record, drugStoreName: order?.drugStoreName || "" }; + } catch (_error) { + // 门店名称仅用于处方提示语;查询异常不能阻止 PDF 的生成。 + return record; + } +} + function buildPrescriptionHtml(record, paperSize = "A5", signatureImages = {}) { const size = paperSize === "A4" ? "A4" : "A5"; const diagnosis = Array.isArray(record.diagnosisList) ? record.diagnosisList : []; const drugs = Array.isArray(record.drugs) ? record.drugs : []; const prescriptionTime = dayjs(record.prescriptionEndTime || record.createTime); - const diagnosisHtml = diagnosis.length - ? diagnosis.map((item, index) => `
${index + 1}. ${escapeHtml(item.name)}
`).join("") - : "
—
"; + const diagnosisText = diagnosis.map((item) => item?.name).filter(Boolean).join("+"); const drugHtml = drugs.length ? drugs.map((drug, index) => `
@@ -255,34 +271,37 @@ function buildPrescriptionHtml(record, paperSize = "A5", signatureImages = {}) { return `
@@ -292,14 +311,14 @@ function buildPrescriptionHtml(record, paperSize = "A5", signatureImages = {}) {
NO:${escapeHtml(record.medOrgOrderNo || record._id)}
-
姓名:${escapeHtml(record.name)}
性别:${escapeHtml(getSex(record))}
费别:${record.feeType === "YB" ? "医保" : "自费"}
年龄:${escapeHtml(getAge(record))}
+
姓名:${escapeHtml(record.name)}
性别:${escapeHtml(getSex(record))}
年龄:${escapeHtml(getAge(record))}
费别:${record.feeType === "YB" ? "医保" : "自费"}
门诊病历号:${escapeHtml(record.blhno)}
科别:${escapeHtml(record.deptName)}
地址:${escapeHtml(record.address)}
电话号:${escapeHtml(record.mobile)}
-
诊断:
开方日期:${prescriptionTime.isValid() ? prescriptionTime.format("YYYY-MM-DD HH:mm") : ""}
${diagnosisHtml}
-
Rp
${drugHtml}
-
医生:${signature(signatureImages.doctor || record.doctorSignImg, record.doctorName)}
审方药师:${signature(signatureImages.pharmacist || record.pharmacistSignImg, record.pharmacist)}
调配:
核对:
发药:
注意:请勿丢失,处方当日有效,因特殊情况,该处方有效为天(签名:)
+
诊断:${escapeHtml(diagnosisText)}
开方日期:${prescriptionTime.isValid() ? prescriptionTime.format("YYYY-MM-DD HH:mm") : ""}
+
Rp
${signatureImages.seal ? `处方印章` : ""}
${drugHtml}
+
`; } @@ -310,12 +329,13 @@ async function generatePrescriptionPdf(record, options = {}) { try { const browser = await getBrowser(); page = await browser.newPage(); - const [doctor, pharmacist] = await Promise.all([ + const [doctor, pharmacist, seal] = await Promise.all([ loadSignatureDataImage(record.doctorSignImg), loadSignatureDataImage(record.pharmacistSignImg), + loadSignatureDataImage(PRESCRIPTION_SEAL_IMAGE_URL), ]); await page.setContent( - buildPrescriptionHtml(record, options.paperSize, { doctor, pharmacist }), + buildPrescriptionHtml(record, options.paperSize, { doctor, pharmacist, seal }), { waitUntil: "load" } ); await page.evaluate(() => document.fonts && document.fonts.ready); @@ -343,7 +363,10 @@ async function createPrescriptionPdf(db, id, options) { error.statusCode = 404; throw error; } - return generatePrescriptionPdf(decryptDiagnosticRecordFields(record), options); + return generatePrescriptionPdf( + await addDrugStoreName(db, decryptDiagnosticRecordFields(record)), + options + ); } function getPdfPath(id, paperSize) { @@ -394,7 +417,10 @@ async function getOrCreatePrescriptionPdf(db, id, options = {}) { error.statusCode = 404; throw error; } - const pdf = await generatePrescriptionPdf(decryptDiagnosticRecordFields(record), { paperSize }); + const pdf = await generatePrescriptionPdf( + await addDrugStoreName(db, decryptDiagnosticRecordFields(record)), + { paperSize } + ); const temporaryPath = `${pdfPath}.${crypto.randomUUID()}.tmp`; try { await fsp.writeFile(temporaryPath, pdf, { flag: "wx" }); diff --git a/index.js b/index.js index e442119..2bf10c0 100644 --- a/index.js +++ b/index.js @@ -52,6 +52,7 @@ const { jwtAuth, optionalJwtAuth } = require("./middleware/jwt-auth"); const { smartAuth } = require("./middleware/smart-auth"); const sms = require("./sms"); const path = require("path"); +const fs = require("fs/promises"); const { ObjectId } = require("mongodb"); const { useUploadFileParse } = require("./upload-file-parse"); const app = express(); @@ -301,6 +302,17 @@ app.get( }) ); +// 返回处方笺 PDF 的 Base64 内容,供客户端上传或嵌入预览。 +app.get( + "/hlw/prescriptions/:id/base64", + asyncHandler(async (req, res) => { + const db = await getDatabase("Internet-hospital"); + const paperSize = req.query.paperSize === "A4" ? "A4" : "A5"; + const { pdfPath } = await getOrCreatePrescriptionPdf(db, req.params.id, { paperSize }); + sendSuccess(res, (await fs.readFile(pdfPath)).toString("base64"), "查询成功"); + }) +); + // POST请求处理器 app.post( "/getYoucanData/member", @@ -317,28 +329,6 @@ app.post( }) ); -app.post( - "/getYoucanData/todo", - jwtAuth, - asyncHandler(async (req, res) => { - const requestData = req.body; - const db = await getDatabase("admin"); - let item = await todo.main(requestData, db); - safeSend(res, item); - }) -); - -app.post( - "/getYoucanData/groupmsg", - jwtAuth, - asyncHandler(async (req, res) => { - const requestData = req.body; - const db = await getDatabase("admin"); - let item = await groupmsg.main(requestData, db); - safeSend(res, item); - }) -); - app.post( "/getYoucanData/corp", smartAuth, // 使用智能鉴权,支持白名单 type 跳过鉴权 @@ -385,37 +375,6 @@ app.post( }) ); -app.post( - "/getYoucanData/sessionArchive", - jwtAuth, - asyncHandler(async (req, res) => { - const requestData = req.body; - const db = await getDatabase("corp"); - const item = await sessionArchive.main(requestData, db); - safeSend(res, item); - }) -); - -app.post( - "/getYoucanData/weCom", - jwtAuth, - asyncHandler(async (req, res) => { - const requestData = req.body; - const item = await weCom.main(requestData); - safeSend(res, item); - }) -); - -app.post( - "/getYoucanData/customerHisSync", - jwtAuth, - asyncHandler(async (req, res) => { - const requestData = req.body; - const item = await customerHisSync.main(requestData); - safeSend(res, item); - }) -); - app.post( "/getYoucanData/sms", asyncHandler(async (req, res) => { @@ -457,42 +416,6 @@ app.post( }) ); -app.post( - "/union-pay-notify", - smartAuth, // 使用智能鉴权,支持白名单 type 跳过鉴权 - asyncHandler(async (req, res) => { - await trade.handleUnionPayNotify(req, res); - }) -); - -app.post( - "/fengniao-auth", - smartAuth, // 使用智能鉴权,支持白名单 type 跳过鉴权 - asyncHandler(async (req, res) => { - await fengniao.handleFengNiaoAuth(req, res); - }) -); - -app.post( - "/fengniao-notify", - smartAuth, // 使用智能鉴权,支持白名单 type 跳过鉴权 - asyncHandler(async (req, res) => { - await fengniao.notify(req, res); - }) -); - -app.post( - "/zyt-his-notify", - smartAuth, // 使用智能鉴权,支持白名单 type 跳过鉴权 - asyncHandler(async (req, res) => { - const notifyHandler = require('./hlw/zyt-his/notify'); - const requestData = req.body; - const result = await notifyHandler.handleNotify(requestData); - safeSend(res, result) - }) -); - - app.post( "/IMCallBack", @@ -529,124 +452,6 @@ app.get( }) ); -/** - * mPaaS 音视频通话:云端自动录制回调 - * - 控制台配置:通话应用 -> 功能配置 -> 配置云端自动录制及录制回调地址 - * - 回调方式:POST(application/json) - * - 业务侧响应:{ bizRequestId, code: 0 } - */ -app.post( - "/mrtc/record/callback", - asyncHandler(async (req, res) => { - const payload = req.body && typeof req.body === "object" ? req.body : {}; - const { - bizRequestId, - bizName, - appId, - workspaceId, - roomId, - recordId, - eventCode, - recordResult, - } = payload; - - // 按文档要求回包,避免云端重试;落库失败也不影响回包 - res.json({ bizRequestId: bizRequestId || "", code: 0 }); - - try { - const db = await getDatabase("Internet-hospital"); - const now = Date.now(); - - let orderId = ""; - let doctorCode = ""; - if (typeof roomId === "string" && roomId) { - const order = await db.collection("consult-order").findOne( - { mrtcRoomId: roomId }, - { projection: { orderId: 1, doctorCode: 1 } } - ); - if (order && order.orderId) orderId = order.orderId; - if (order && order.doctorCode) doctorCode = order.doctorCode; - } - - const normalizedEventCode = - typeof eventCode === "number" - ? eventCode - : typeof eventCode === "string" && eventCode !== "" - ? Number(eventCode) - : undefined; - - const rr = recordResult && typeof recordResult === "object" ? recordResult : null; - const status = - rr && rr.status !== undefined && rr.status !== null && rr.status !== "" ? Number(rr.status) : undefined; - - const $set = { - updatedAt: now, - source: "AUTO_RECORD_CALLBACK", - }; - if (orderId) $set.orderId = orderId; - if (doctorCode) $set.doctorCode = doctorCode; - if (typeof roomId === "string" && roomId) $set.roomId = roomId; - if (typeof recordId === "string" && recordId) $set.recordId = recordId; - if (typeof bizRequestId === "string" && bizRequestId) $set.bizRequestId = bizRequestId; - if (typeof bizName === "string" && bizName) $set.bizName = bizName; - if (typeof appId === "string" && appId) $set.appId = appId; - if (typeof workspaceId === "string" && workspaceId) $set.workspaceId = workspaceId; - if (normalizedEventCode !== undefined && Number.isFinite(normalizedEventCode)) $set.eventCode = normalizedEventCode; - - // eventCode=99 时传 recordResult:status/fileType/filePath/recordStartTime/mediaType - if (normalizedEventCode === 99 && rr) { - if (status !== undefined && Number.isFinite(status)) $set.status = status; - if (rr.fileType !== undefined && rr.fileType !== null && rr.fileType !== "") $set.fileType = Number(rr.fileType); - if (typeof rr.filePath === "string") $set.filePath = rr.filePath; - if (rr.recordStartTime !== undefined && rr.recordStartTime !== null && rr.recordStartTime !== "") - $set.startAt = Number(rr.recordStartTime); - if (rr.mediaType !== undefined && rr.mediaType !== null && rr.mediaType !== "") $set.mediaType = Number(rr.mediaType); - } - - // eventCode=11:录制结束(结束时间以回调到达时间为准) - if (normalizedEventCode === 11) { - $set.endAt = now; - } - - // 以 recordId 为主键幂等(回调不会携带 recordThirdId) - const filter = recordId ? { recordId } : { bizRequestId: bizRequestId || `AUTO_${now}` }; - - await db.collection("mrtc-video-record").updateOne( - filter, - { - // 注意:MongoDB 不允许在同一次 update 中同时用 $setOnInsert 与 $push 修改同一路径(会报 conflict) - // events 字段由 $push 在首次写入时自动创建为数组 - $setOnInsert: { createdAt: now }, - $set, - $push: { - events: { - event: "AUTO_RECORD_CALLBACK", - at: now, - eventCode: normalizedEventCode, - status, - payload, - }, - }, - }, - { upsert: true } - ); - - logger.info("[MRTC][AUTO_RECORD] callback stored", { - bizRequestId, - roomId, - recordId, - eventCode: normalizedEventCode, - orderId, - status, - }); - } catch (e) { - logger.error("[MRTC][AUTO_RECORD] callback store failed", { - message: e && e.message ? e.message : String(e), - }); - } - }) -); - app.get( "/callback/data", asyncHandler(async (req, res) => { diff --git a/request.js b/request.js index ad11133..bf984f6 100644 --- a/request.js +++ b/request.js @@ -28,11 +28,15 @@ function get(url) { } function post(url, data, config) { + const hasPdf = data && data.data && typeof data.data.pdf == 'string' ? data.data.pdf.length > 50 : false; const info = { url, method: "POST", data, start: dayjs().format('YYYY-MM-DD HH:mm:ss:SSS') }; return new Promise(async (resolve, reject) => { try { const response = await axios.post(url, data, config); resolve(response.data); + if (hasPdf) { + info.data.data.pdf = 'pdfbase64' + } writeLog(info, response); } catch (error) { info.errMsg = error?.message || error;