356 lines
12 KiB
JavaScript
Raw Permalink Normal View History

2026-07-27 11:28:33 +08:00
const dayjs = require("dayjs");
const ExcelJS = require('exceljs');
const Sm4Util = require("../../utils/sm4-util");
module.exports = async (item, db, res) => {
switch (item.downloadType) {
case 'downloadRx':
return await downloadRx(item, db, res);
case 'downloadOrder':
return await downloadOrder(item, db, res);
case 'downloadLackMedicineReg':
return await downloadLackMedicineReg(item, db, res);
default:
return '不支持的下载类型';
}
};
const rxStatusMap = {
INIT: "待审核",
PASS: "审核通过",
UNPASS: "审核未通过",
EXPIRED: "已取消",
PASS_INVALID: "已作废",
UNPASS_INVALID: "已作废",
INVALID: '已作废',
};
const orderStatusMap = {
pending: "待处理",
processing: "处理中",
cancelled: "已取消",
completed: "已完成",
finished: "已结束",
};
const orderSourceMap = {
ALIPAY_MINI: '支付宝小程序',
MATEPAD: '门店平板'
}
// 流式写入大数据量
async function downloadStream(db, res, options) {
try {
// 关闭 useStyles 提升性能
const workbook = new ExcelJS.stream.xlsx.WorkbookWriter({
stream: res,
useStyles: false
});
const worksheet = workbook.addWorksheet('Sheet1');
worksheet.columns = options.columns;
// 提前设置响应头
const safeFilename = options.filename.replace(/[<>:"/\\|?*]+/g, '_');
res.setHeader(
'Content-Type',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
);
res.setHeader(
'Content-Disposition',
`attachment; filename=${encodeURIComponent(safeFilename)}.xlsx`
);
// 加大批次
const BATCH_SIZE = 2000;
let skip = 0;
let hasMore = true;
while (hasMore) {
const batch = await db.collection(options.collection).aggregate([
...options.aggregateList,
{ $skip: skip },
{ $limit: BATCH_SIZE }
]).toArray();
batch.forEach(i => {
worksheet.addRow(options.format(i)).commit();
});
skip += batch.length;
hasMore = batch.length === BATCH_SIZE;
}
await worksheet.commit();
await workbook.commit();
// 不需要 res.end()ExcelJS 会自动关闭流
} catch (e) {
res.status(500).send('生成Excel文件时出错' + e.message);
}
}
async function downloadRx(ctx, db, res) {
const { startDate, endDate, startAuditDate, endAuditDate } = ctx;
const hasDates = startDate && dayjs(startDate).isValid() && endDate && dayjs(endDate).isValid();
const hasAuditDate = startAuditDate && dayjs(startAuditDate).isValid() && endAuditDate && dayjs(endAuditDate).isValid();
const query = {};
if (hasDates) {
query.createTime = { $gte: dayjs(startDate).startOf('day').valueOf(), $lte: dayjs(endDate).endOf('day').valueOf() };
}
if (hasAuditDate) {
query.auditTime = { $gte: dayjs(startAuditDate).startOf('day').valueOf(), $lte: dayjs(endAuditDate).endOf('day').valueOf() };
}
if (typeof ctx.name === 'string' && ctx.name.trim() !== "") {
query.name = new RegExp(ctx.name);
}
if (typeof ctx.doctorCode === 'string') {
query.doctorCode = ctx.doctorCode.trim();
}
if (typeof ctx.pharmacistNo === 'string' && ctx.pharmacistNo.trim() !== "") {
query.pharmacistNo = ctx.pharmacistNo.trim();
}
const columns = [
{ header: '状态', key: 'status', width: 10 },
{ header: '原因', key: 'reasons', width: 10 },
{ header: '提交时间', key: 'createTime', width: 20 },
{ header: '审核时间', key: 'auditTime', width: 20 },
{ header: '医生', key: 'doctorName', width: 10 },
{ header: '药师', key: 'pharmacist', width: 10 },
{ header: '患者', key: 'name', width: 10 },
{ header: '诊断', key: 'diagnosisList', width: 10 },
{ header: '药品', key: 'drugs', width: 10 },
];
let filename = '处方记录';
if (hasDates) {
filename += `_${dayjs(startDate).format('YYYY-MM-DD')}_${dayjs(endDate).format('YYYY-MM-DD')}`;
} else if (hasAuditDate) {
filename += `_${dayjs(startAuditDate).format('YYYY-MM-DD')}_${dayjs(endAuditDate).format('YYYY-MM-DD')}`;
}
try {
const total = await db.collection("diagnostic-record").countDocuments(query);
if (total > 120000) {
return { success: false, message: `数据量过大(${total}条数据),请分批次导出` };
}
if (total === 0) {
return { success: false, message: `没有符合条件的数据` };
}
const options = {
query,
columns,
filename,
collection: 'diagnostic-record',
aggregateList: [
{ $match: query },
{ $project: { _id: 0, status: 1, createTime: 1, reasons: 1, auditTime: 1, name: 1, doctorName: 1, pharmacist: 1, diagnosisList: 1, drugs: 1, orderId: 1, specification: 1 } },
{ $sort: { createTime: -1 } },
],
format: i => ({
status: rxStatusMap[i.status] || i.status,
reasons: Array.isArray(i.reasons) ? i.reasons.join(",") : "",
createTime: i.createTime ? dayjs(i.createTime).format("YYYY-MM-DD HH:mm:ss") : "",
auditTime: i.auditTime ? dayjs(i.auditTime).format("YYYY-MM-DD HH:mm:ss") : "",
doctorName: i.doctorName,
pharmacist: i.pharmacist,
name: Sm4Util.decryptDataForSm3(i.name)|| i.name,
diagnosisList: Array.isArray(i.diagnosisList) ? i.diagnosisList.map(d => d.name || '').join(",") : "",
drugs: Array.isArray(i.drugs) ? i.drugs.map(d => `${d.drugName} ${i.specification || ''} (${d.dosage}${d.dosage_unit}, ${d.frequencyName}, ${d.days}天)`).join(",") : "",
})
}
await downloadStream(db, res, options);
} catch (e) {
return { success: false, message: e.message || "查询失败" };
}
}
async function downloadOrder(ctx, db, res) {
const { startDate, endDate } = ctx;
const hasDates = startDate && dayjs(startDate).isValid() && endDate && dayjs(endDate).isValid();
const query = {};
if (hasDates) {
query.createTime = { $gte: dayjs(startDate).startOf('day').valueOf(), $lte: dayjs(endDate).endOf('day').valueOf() };
}
if (typeof ctx.name === 'string' && ctx.name.trim() !== "") {
query.name = new RegExp(ctx.name);
}
const doctorCodes = typeof ctx.doctorCodes === 'string' && ctx.doctorCodes.trim() ? ctx.doctorCodes.split(',').map(code => code.trim()).filter(Boolean) : [];
if (doctorCodes.length) {
query.doctorCode = { $in: doctorCodes };
}
const storeIds = typeof ctx.storeIds === 'string' && ctx.storeIds.trim() ? ctx.storeIds.split(',').map(code => code.trim()).filter(Boolean) : [];
if (storeIds.length) {
query.drugStoreId = { $in: storeIds };
}
if (typeof ctx.orderSource === 'string' && ctx.orderSource.trim() !== "") {
query.orderSource = ctx.orderSource.trim()
}
if (['true', 'false'].includes(ctx.isPres)) {
query.preOrderrId = { $exists: ctx.isPres === 'true' }
}
if (typeof ctx.orderStatus === 'string' && ctx.orderStatus.trim() !== "") {
query.orderStatus = ctx.orderStatus
}
const columns = [
{ header: '状态', key: 'status', width: 10 },
{ header: '患者', key: 'name', width: 10 },
{ header: '病历号', key: 'blhno', width: 10 },
{ header: '医生', key: 'doctorName', width: 20 },
{ header: '医生工号', key: 'doctorCode', width: 20 },
{ header: '科室', key: 'deptName', width: 10 },
{ header: '下单时间', key: 'orderTime', width: 10 },
{ header: '问诊类型', key: 'orderType', width: 10 },
{ header: '来源', key: 'drugStoreName', width: 10 },
{ header: '终端', key: 'orderSource', width: 10 },
{ header: '订单号', key: 'orderId', width: 10 },
];
let filename = '咨询订单';
if (hasDates) {
filename += `_${dayjs(startDate).format('YYYY-MM-DD')}_${dayjs(endDate).format('YYYY-MM-DD')}`;
}
try {
const total = await db.collection("consult-order").countDocuments(query);
if (total > 10000) {
return { success: false, message: `数据量过大(${total}条数据),请分批次导出` };
}
if (total === 0) {
return { success: false, message: `没有符合条件的数据` };
}
const options = {
query,
columns,
filename,
collection: 'consult-order',
aggregateList: [
{ $match: query },
{ $project: { _id: 0, orderStatus: 1, name: 1, blhno: 1, createTime: 1, doctorName: 1, doctorCode: 1, deptName: 1, preOrderrId: 1, drugStoreName: 1, orderSource: 1, orderId: 1 } },
{
$addFields: {
orderTime: {
$dateToString: {
format: "%Y-%m-%d",
date: { $toDate: "$createTime" },
timezone: "Asia/Shanghai" // 设置为中国标准时间
}
}
}
},
{ $sort: { createTime: -1 } },
],
format: i => ({
status: orderStatusMap[i.orderStatus] || i.orderStatus,
name: i.name,
blhno: i.blhno,
doctorName: i.doctorName,
doctorCode: i.doctorCode,
deptName: i.deptName,
orderTime: i.orderTime,
orderType: i.preOrderId ? '在线续方' : '在线复诊',
drugStoreName: i.drugStoreName,
orderSource: orderSourceMap[i.orderSource] || i.orderSource,
orderId: i.orderId,
})
}
await downloadStream(db, res, options);
} catch (e) {
return { success: false, message: e.message || "查询失败" };
}
}
// 缺货登记导出
async function downloadLackMedicineReg(ctx, db, res) {
const { startTime, endTime } = ctx;
const hasDates = startTime && dayjs(startTime).isValid() && endTime && dayjs(endTime).isValid();
const query = {};
if (hasDates) {
query.createTime = {
$gte: dayjs(startTime).startOf('day').valueOf(),
$lte: dayjs(endTime).endOf('day').valueOf(),
};
}
if (typeof ctx.corpId === 'string' && ctx.corpId.trim() !== "") {
query.corpId = ctx.corpId.trim();
}
if (typeof ctx.drugName === 'string' && ctx.drugName.trim() !== "") {
query.drugName = new RegExp(ctx.drugName.trim(), 'i');
}
if (typeof ctx.inventory === 'string') {
const inventory = ctx.inventory.trim();
if (['online', 'store'].includes(inventory)) {
query.inventory = inventory;
}
}
const columns = [
{ header: '登记时间', key: 'createTime', width: 20 },
{ header: '药品名称', key: 'drugName', width: 20 },
{ header: '规格', key: 'spec', width: 15 },
{ header: '厂家', key: 'manufacturer', width: 25 },
{ header: '医保编码', key: 'insuranceCode', width: 20 },
{ header: '附件数量', key: 'attachmentCount', width: 12 },
{ header: '备注说明', key: 'description', width: 30 },
{ header: '缺货药品库', key: 'storeName', width: 15 },
{ header: '患者姓名', key: 'patientName', width: 15 },
{ header: '联系电话', key: 'phone', width: 15 },
];
let filename = '缺货登记';
if (hasDates) {
filename += `_${dayjs(startTime).format('YYYY-MM-DD')}_${dayjs(endTime).format('YYYY-MM-DD')}`;
}
try {
const total = await db.collection("lack-medicine-reg").countDocuments(query);
if (total > 10000) {
return { success: false, message: `数据量过大(${total}条数据),请分批次导出` };
}
if (total === 0) {
return { success: false, message: `没有符合条件的数据` };
}
const options = {
query,
columns,
filename,
collection: 'lack-medicine-reg',
aggregateList: [
{ $match: query },
{
$project: {
_id: 0,
createTime: 1,
drugName: 1,
spec: 1,
manufacturer: 1,
insuranceCode: 1,
images: 1,
description: 1,
inventory: 1,
patientName: 1,
phone: 1,
}
},
{ $sort: { createTime: -1 } },
],
format: i => ({
createTime: i.createTime ? dayjs(i.createTime).format("YYYY-MM-DD HH:mm:ss") : "",
drugName: i.drugName || "",
spec: i.spec || "",
manufacturer: i.manufacturer || "",
insuranceCode: i.insuranceCode || "",
attachmentCount: Array.isArray(i.images) ? i.images.length : 0,
description: i.description || "",
storeName: i.inventory === 'store' ? '药店药品库' : '配送药品库',
patientName: i.patientName || "",
phone: i.phone || "",
})
};
await downloadStream(db, res, options);
} catch (e) {
return { success: false, message: e.message || "查询失败" };
}
}