fix:接口调整

This commit is contained in:
huxuejian 2026-09-24 18:22:05 +08:00
parent 0165c2c064
commit ec816dfdd6
9 changed files with 198 additions and 1030 deletions

View File

@ -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,并配置其可执行文件路径:

View File

@ -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; // 门店主动撤销时间
}

View File

@ -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
};

View File

@ -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,

View File

@ -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药品错误",
};
}
}
}
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 || "处方撤销错误",
};
}
}

View File

@ -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":

View File

@ -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) {
: `<span>${escapeHtml(fallback)}</span>`;
}
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) => `<div>${index + 1}. ${escapeHtml(item.name)}</div>`).join("")
: "<div>—</div>";
const diagnosisText = diagnosis.map((item) => item?.name).filter(Boolean).join("+");
const drugHtml = drugs.length
? drugs.map((drug, index) => `
<section class="medicine-item">
@ -255,34 +271,37 @@ function buildPrescriptionHtml(record, paperSize = "A5", signatureImages = {}) {
return `<!doctype html>
<html lang="zh-CN"><head><meta charset="utf-8" />
<style>
@page { size: ${size} portrait; margin: 6mm; }
@page { size: ${size} portrait; margin: 8mm; }
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; color: #111; font-family: "Noto Sans CJK SC", "Noto Sans SC", SimSun, "Songti SC", serif; font-size: 12px; }
html, body { margin: 0; padding: 0; color: #111; font-family: "Noto Sans CJK SC", "Noto Sans SC", SimSun, "Songti SC", serif; font-size: 14px; }
body { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.prescription { min-height: ${size === "A5" ? "198mm" : "285mm"}; display: flex; flex-direction: column; line-height: 1.5; }
.header { position: relative; border-bottom: 1px solid #111; padding: 1px 0 5px; }
.title { font-weight: 700; font-size: 16px; text-align: center; line-height: 1.75; }
.rx-type { position: absolute; top: 0; right: 0; width: 38px; border: 1px solid #555; text-align: center; color: #555; line-height: 1.55; }
.rx-number { margin-top: 4px; font-size: 12px; }
.prescription { min-height: ${size === "A5" ? "194mm" : "281mm"}; display: flex; flex-direction: column; line-height: 1.28; overflow: hidden; }
.header { position: relative; border-bottom: 1px solid #111; padding: 3px 0 4px; }
.title { font-weight: 700; font-size: 17px; text-align: center; line-height: 1.4; }
.rx-type { position: absolute; top: 0; right: 0; width: 40px; border: 1px solid #333; text-align: center; color: #666; font-size: 12px; line-height: 1.3; }
.rx-number { margin-top: 5px; }
.patient-info { padding-top: 5px; }
.info-row { display: flex; padding: 2px 0; gap: 5px; }
.info-row { display: flex; padding: 2px; }
.info-cell { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.info-half { width: 50%; overflow-wrap: anywhere; }
.diagnosis { margin-top: 5px; }
.diagnosis-head { display: flex; justify-content: space-between; gap: 8px; }
.diagnosis-list { padding-top: 2px; min-height: 18px; }
.date { white-space: nowrap; color: #555; }
.medicine { margin-top: 4px; }
.rp { font-size: 16px; font-weight: 700; margin-bottom: 3px; }
.medicine-item { break-inside: avoid; margin: 0 0 11px; padding-left: 2px; }
.medicine-name { font-weight: 700; font-size: 13px; }
.medicine-detail { color: #444; margin-top: 2px; }
.footer { margin-top: auto; border-top: 1px dashed #999; padding-top: 6px; break-inside: avoid; }
.signature-row, .action-row { display: flex; padding: 2px 0; }
.signature-cell, .action-cell { width: 33.333%; min-height: 20px; }
.signature-image { display: inline-block; width: 82px; height: 28px; object-fit: contain; vertical-align: middle; }
.notice { margin-top: 4px; color: #b33; font-size: 10px; line-height: 1.4; }
.blank-line { display: inline-block; width: 35px; border-bottom: 1px solid currentColor; }
.diagnosis-text { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.date { flex: none; white-space: nowrap; }
.medicine { margin-top: 5px; }
.rp-line { position: relative; display: flex; align-items: center; min-height: 30px; margin-bottom: 6px; }
.rp-line::after { content: ""; flex: 1; margin-left: 8px; border-top: 1px dashed #6b7280; }
.rp { font-size: 20px; font-weight: 700; }
.seal-image { position: absolute; z-index: 1; top: -4px; right: 0; width: 120px; height: 120px; object-fit: contain; }
.medicine-item { break-inside: avoid; margin: 0 0 6px; }
.medicine-name { font-weight: 700; }
.medicine-detail { color: #666; margin-top: 1px; }
.footer { margin-top: auto; border-top: 1px dashed #999; padding-top: 5px; break-inside: avoid; }
.signature-row, .action-row { display: flex; padding-top: 4px; }
.signature-cell, .action-cell { width: 33.333%; min-height: 24px; }
.signature-image { display: inline-block; width: 76px; height: 30px; object-fit: contain; vertical-align: middle; }
.notice { margin-top: 0; font-size: 12px; line-height: 1.25; white-space: nowrap; }
.notice-first { margin-top: 7px; }
@media print { .medicine-item, .footer { page-break-inside: avoid; } }
</style></head><body>
<main class="prescription">
@ -292,14 +311,14 @@ function buildPrescriptionHtml(record, paperSize = "A5", signatureImages = {}) {
<div class="rx-number">NO:${escapeHtml(record.medOrgOrderNo || record._id)}</div>
</header>
<section class="patient-info">
<div class="info-row"><div class="info-cell">姓名:${escapeHtml(record.name)}</div><div class="info-cell">性别:${escapeHtml(getSex(record))}</div><div class="info-cell">费别:${record.feeType === "YB" ? "医保" : "自费"}</div><div class="info-cell">年龄:${escapeHtml(getAge(record))}</div></div>
<div class="info-row"><div class="info-cell">姓名:${escapeHtml(record.name)}</div><div class="info-cell">性别:${escapeHtml(getSex(record))}</div><div class="info-cell">年龄:${escapeHtml(getAge(record))}</div><div class="info-cell">费别:${record.feeType === "YB" ? "医保" : "自费"}</div></div>
<div class="info-row"><div class="info-half">门诊病历号:${escapeHtml(record.blhno)}</div><div class="info-half">科别:${escapeHtml(record.deptName)}</div></div>
<div class="info-row"><div class="info-cell">地址:${escapeHtml(record.address)}</div></div>
<div class="info-row"><div class="info-cell">电话号:${escapeHtml(record.mobile)}</div></div>
</section>
<section class="diagnosis"><div class="diagnosis-head"><div>诊断:</div><div class="date">开方日期:${prescriptionTime.isValid() ? prescriptionTime.format("YYYY-MM-DD HH:mm") : ""}</div></div><div class="diagnosis-list">${diagnosisHtml}</div></section>
<section class="medicine"><div class="rp">Rp</div>${drugHtml}</section>
<footer class="footer"><div class="signature-row"><div class="signature-cell">医生:${signature(signatureImages.doctor || record.doctorSignImg, record.doctorName)}</div><div class="signature-cell">审方药师:${signature(signatureImages.pharmacist || record.pharmacistSignImg, record.pharmacist)}</div></div><div class="action-row"><div class="action-cell">调配:</div><div class="action-cell">核对:</div><div class="action-cell">发药:</div></div><div class="notice">注意:请勿丢失,处方当日有效,因特殊情况,该处方有效为<span class="blank-line"></span>天(签名:<span class="blank-line"></span>)</div></footer>
<section class="diagnosis"><div class="diagnosis-head"><div class="diagnosis-text">诊断:${escapeHtml(diagnosisText)}</div><div class="date">开方日期:${prescriptionTime.isValid() ? prescriptionTime.format("YYYY-MM-DD HH:mm") : ""}</div></div></section>
<section class="medicine"><div class="rp-line"><div class="rp">Rp</div>${signatureImages.seal ? `<img class="seal-image" src="${signatureImages.seal}" alt="处方印章" />` : ""}</div>${drugHtml}</section>
<footer class="footer"><div class="signature-row"><div class="signature-cell">医生:${signature(signatureImages.doctor || record.doctorSignImg, record.doctorName)}</div><div class="signature-cell">审方药师:${signature(signatureImages.pharmacist || record.pharmacistSignImg, record.pharmacist)}</div><div class="signature-cell"></div></div><div class="action-row"><div class="action-cell">审核:</div><div class="action-cell">调配:</div><div class="action-cell">核对/发药:</div></div><div class="notice notice-first">1.根据《处方管理办法》规定,本处方开具三天有效;</div><div class="notice">2.本处方仅供患者${escapeHtml(record.drugStoreName)}使用,本处方金额以实际药店成交金额为准;</div><div class="notice">3.请仔细阅读药品使用说明,服药谨遵医嘱,如有不适请立即停药并前往医院就诊。</div></footer>
</main>
</body></html>`;
}
@ -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" });

219
index.js
View File

@ -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) => {

View File

@ -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;