This commit is contained in:
Your Name
2026-06-16 15:47:44 +08:00
parent c9c2b4608f
commit 37b2945c44
3 changed files with 325 additions and 1 deletions
@@ -292,7 +292,7 @@
:fetch-fun="prescriptionOrderExport"
:params="prescriptionOrderExportParams"
:page-size="pager.size"
export-hint="导出范围与上方筛选一致履约状态创建时间及其他条件均会生效自媒体渠道挂号渠道来源优先取该单关联处方登记的挂号无则诊单下同患者挂号取 id 最大的一条与前台挂号选择的记录一致业绩侧栏带渠道筛选导出时与同页列表高亮挂号同源服务套餐按字典 server_order 解析展示签收日期与详情/业绩看板同源仅读物流库轨迹/签收时间导出不再实时查快递100速度只取决于数据库签收时间由 `tcm:backfill-sign-time` 命令与物流自动更新定时任务落库刚发货尚未同步的单子会暂时为空待下次回填/定时任务刷新后显示"
export-hint="导出范围与上方筛选一致履约状态创建时间及其他条件均会生效自媒体渠道挂号渠道来源优先取该单关联处方登记的挂号无则诊单下同患者挂号取 id 最大的一条与前台挂号选择的记录一致业绩侧栏带渠道筛选导出时与同页列表高亮挂号同源服务套餐按字典 server_order 解析展示关联收款记录与详情侧栏同源已支付/已退款/待审核每笔两行展示摘要行+明细行多笔空行分隔单元格自动换行签收日期与详情/业绩看板同源仅读物流库轨迹/签收时间导出不再实时查快递100速度只取决于数据库签收时间由 `tcm:backfill-sign-time` 命令与物流自动更新定时任务落库刚发货尚未同步的单子会暂时为空待下次回填/定时任务刷新后显示"
/>
</el-form-item>
</el-form>
@@ -9,6 +9,7 @@ use app\adminapi\logic\dept\DeptLogic;
use app\adminapi\logic\stats\YejiStatsLogic;
use app\adminapi\logic\tcm\PrescriptionOrderLogic;
use app\common\enum\ExportEnum;
use app\common\cache\ExportCache;
use app\common\lists\ListsExcelInterface;
use app\common\lists\ListsExtendInterface;
use app\common\lists\ListsSearchInterface;
@@ -26,6 +27,12 @@ use app\common\service\gancao\GancaoScmRecipelService;
use think\facade\Config;
use think\facade\Db;
use think\db\Query;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Fill;
class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface, ListsExcelInterface
{
@@ -783,6 +790,7 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
'export_medication_days' => '天数',
'export_amount' => '总金额',
'export_paid_amount' => '已付金额',
'export_linked_pay_records' => '关联收款记录',
'export_refund_amount' => '退款金额',
'export_agency_collect' => '代收金额',
'export_tracking_number' => '快递单号',
@@ -795,6 +803,133 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
];
}
/**
* 处方业务订单导出:深色表头、斑马纹、长文本自动换行(覆盖默认亮蓝表头)
*
* @param array<string, string> $excelFields
* @param array<int, array<string, mixed>> $lists
*/
public function createExcel($excelFields, $lists): string
{
$title = array_values($excelFields);
$fieldKeys = array_keys($excelFields);
$data = [];
foreach ($lists as $row) {
$temp = [];
foreach ($excelFields as $key => $excelField) {
$fieldData = $row[$key] ?? '';
if (is_numeric($fieldData) && strlen((string) $fieldData) >= 12) {
$fieldData .= "\t";
}
$temp[$key] = $fieldData;
}
$data[] = $temp;
}
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setTitle('处方业务订单');
foreach ($title as $key => $value) {
$sheet->setCellValueByColumnAndRow($key + 1, 1, $value);
}
$rowNum = 2;
foreach ($data as $item) {
$column = 1;
foreach ($item as $value) {
$sheet->setCellValueByColumnAndRow($column, $rowNum, $value);
++$column;
}
++$rowNum;
}
$highest = $sheet->getHighestRowAndColumn();
$highestRow = (int) $highest['row'];
$lastColumn = (string) $highest['column'];
$titleScope = 'A1:' . $lastColumn . '1';
$allScope = 'A1:' . $lastColumn . $highestRow;
$bodyScope = 'A2:' . $lastColumn . $highestRow;
$sheet->freezePane('A2');
$sheet->getRowDimension(1)->setRowHeight(26);
$sheet->getStyle($titleScope)->getFill()
->setFillType(Fill::FILL_SOLID)
->getStartColor()->setARGB('FF334155');
$sheet->getStyle($titleScope)->getFont()->getColor()->setARGB('FFFFFFFF');
$sheet->getStyle($titleScope)->getFont()->setBold(true);
$sheet->getStyle($titleScope)->getAlignment()
->setHorizontal(Alignment::HORIZONTAL_CENTER)
->setVertical(Alignment::VERTICAL_CENTER);
$sheet->getStyle($allScope)->getBorders()->getAllBorders()
->setBorderStyle(Border::BORDER_THIN)
->getColor()->setARGB('FFE2E8F0');
$sheet->getStyle($bodyScope)->getAlignment()
->setVertical(Alignment::VERTICAL_TOP)
->setWrapText(true);
$wrapWideKeys = [
'export_linked_pay_records',
'export_prescription_name',
'export_guahao_channel_source',
'export_assistant_dept',
'export_service_package',
];
$fixedWidths = [
'export_fulfillment_status_text' => 12,
'export_order_time' => 18,
'export_patient_gender' => 6,
'export_patient_age' => 6,
'export_medication_days' => 6,
'export_amount' => 10,
'export_paid_amount' => 10,
'export_refund_amount' => 10,
'export_agency_collect' => 10,
'export_sign_time' => 12,
'export_supply_mode' => 10,
'export_linked_pay_records' => 52,
'export_prescription_name' => 34,
'export_guahao_channel_source' => 22,
'export_assistant_dept' => 24,
'export_service_package' => 18,
'export_tracking_number' => 18,
];
foreach ($fieldKeys as $idx => $fieldKey) {
$colLetter = Coordinate::stringFromColumnIndex($idx + 1);
if (isset($fixedWidths[$fieldKey])) {
$sheet->getColumnDimension($colLetter)->setWidth($fixedWidths[$fieldKey]);
} elseif (!in_array($fieldKey, $wrapWideKeys, true)) {
$sheet->getColumnDimension($colLetter)->setAutoSize(true);
} else {
$sheet->getColumnDimension($colLetter)->setWidth(28);
}
}
for ($r = 2; $r <= $highestRow; ++$r) {
if ($r % 2 === 0) {
$sheet->getStyle('A' . $r . ':' . $lastColumn . $r)->getFill()
->setFillType(Fill::FILL_SOLID)
->getStartColor()->setARGB('FFF8FAFC');
}
}
$writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
$exportCache = new ExportCache();
$src = $exportCache->getSrc();
if (!file_exists($src)) {
mkdir($src, 0775, true);
}
$writer->save($src . $this->fileName);
$vars = ['file' => $exportCache->setFile($this->fileName)];
return (string) url('adminapi/download/export', $vars, true, true);
}
/**
* 业绩看板侧栏专用:约诊/接诊条数,与 YejiStatsLogic::applyConsultFiltersAlignedWithAppointmentLists 同口径
* doctor_appointment.status=3appointment_date 落入区间,有效医助 COALESCE(挂号.assistant_id,诊单.assistant_id))。
@@ -3161,6 +3161,193 @@ class PrescriptionOrderLogic
return $out;
}
/**
* 导出用:统一解析时间(支持 Unix 秒/毫秒与 Y-m-d H:i:s 字符串,与前端 formatOrderTime 同口径)
*
* @param mixed $value
*/
private static function formatDateTimeForExport($value, string $format = 'Y-m-d H:i'): string
{
if ($value === null || $value === '' || $value === false) {
return '';
}
if (\is_int($value) || \is_float($value)) {
$ts = (int) $value;
if ($ts > 9999999999) {
$ts = (int) floor($ts / 1000);
}
return $ts > 0 ? date($format, $ts) : '';
}
$str = trim((string) $value);
if ($str === '') {
return '';
}
if (preg_match('/^\d+$/', $str)) {
$ts = (int) $str;
if ($ts > 9999999999) {
$ts = (int) floor($ts / 1000);
}
return $ts > 0 ? date($format, $ts) : '';
}
if (str_contains($str, '-') || str_contains($str, '/')) {
$ts = strtotime($str);
return $ts !== false ? date($format, $ts) : $str;
}
return $str;
}
/**
* 导出用:支付单来源/方式(与前端 formatPayOrderSource 同口径)
*
* @param array<string, mixed> $row
*/
private static function formatPayOrderSourceForExport(array $row): string
{
$createType = trim((string) ($row['create_type'] ?? ''));
if ($createType === 'wechat_work') {
return '企业微信对外收款';
}
if ($createType === 'fubei') {
return '付呗';
}
$paymentMethod = trim((string) ($row['payment_method'] ?? ''));
$methodMap = [
'alipay' => '支付宝',
'wechat' => '微信',
'wechat_work' => '企业微信',
'fubei' => '付呗',
'manual' => '手动确认到账',
];
if ($paymentMethod !== '' && isset($methodMap[$paymentMethod])) {
return $methodMap[$paymentMethod];
}
return '普通订单';
}
/**
* 导出用:单条关联收款可读文本(两行结构,便于 Excel 自动换行阅读)
*
* @param array<string, mixed> $pay
* @param array<int|string, string> $creatorNames
*/
private static function formatLinkedPayRecordLineForExport(array $pay, array $creatorNames, int $index = 1): string
{
$typeMap = [
1 => '挂号费',
2 => '问诊费',
3 => '药品费用',
4 => '首付费用',
5 => '尾款费用',
6 => '其他费用',
7 => '全部费用',
8 => '驼奶费用',
];
$statusMap = [
1 => '待支付',
2 => '已支付',
3 => '已取消',
4 => '已退款',
5 => '待审核',
];
$orderNo = trim((string) ($pay['order_no'] ?? ''));
$amount = number_format(round((float) ($pay['amount'] ?? 0), 2), 2, '.', '');
$typeDesc = $typeMap[(int) ($pay['order_type'] ?? 0)] ?? '—';
$statusDesc = $statusMap[(int) ($pay['status'] ?? 0)] ?? '—';
$source = self::formatPayOrderSourceForExport($pay);
$cid = (int) ($pay['creator_id'] ?? 0);
$creator = $cid > 0 ? (string) ($creatorNames[$cid] ?? '') : '—';
$timeStr = self::formatDateTimeForExport($pay['create_time'] ?? '') ?: '—';
$line1 = sprintf('[%d] %s ¥%s %s %s', $index, $orderNo, $amount, $typeDesc, $statusDesc);
$line2 = sprintf(' %s %s %s', $source, $creator, $timeStr);
return $line1 . "\n" . $line2;
}
/**
* 导出用:批量解析业务订单关联收款记录(与详情 linked_pay_orders 同口径:status ∈ {2,4,5}
*
* @param int[] $poIds
*
* @return array<int, string> prescription_order_id => 多笔以空行分隔的可读文本
*/
private static function batchLinkedPayRecordsExportTextByPoIds(array $poIds): array
{
$poIds = array_values(array_filter(array_unique(array_map('intval', $poIds)), static fn (int $id): bool => $id > 0));
if ($poIds === []) {
return [];
}
$linksByPo = [];
$allPayIds = [];
$linkRows = PrescriptionOrderPayOrder::whereIn('prescription_order_id', $poIds)
->field(['prescription_order_id', 'pay_order_id'])
->order('id', 'asc')
->select()
->toArray();
foreach ($linkRows as $l) {
$poid = (int) ($l['prescription_order_id'] ?? 0);
$payId = (int) ($l['pay_order_id'] ?? 0);
if ($poid <= 0 || $payId <= 0) {
continue;
}
$linksByPo[$poid][] = $payId;
$allPayIds[$payId] = true;
}
$out = [];
foreach ($poIds as $poid) {
$out[$poid] = '';
}
if ($allPayIds === []) {
return $out;
}
$payRows = Order::whereIn('id', array_keys($allPayIds))
->whereNull('delete_time')
->whereIn('status', [2, 4, 5])
->field(['id', 'order_no', 'order_type', 'amount', 'status', 'create_time', 'creator_id', 'payment_method', 'create_type'])
->select()
->toArray();
$payById = [];
$creatorIds = [];
foreach ($payRows as $r) {
$id = (int) ($r['id'] ?? 0);
if ($id <= 0) {
continue;
}
$payById[$id] = $r;
$cid = (int) ($r['creator_id'] ?? 0);
if ($cid > 0) {
$creatorIds[$cid] = true;
}
}
$creatorNames = $creatorIds !== []
? Admin::whereIn('id', array_keys($creatorIds))->column('name', 'id')
: [];
foreach ($poIds as $poid) {
$lines = [];
$seq = 0;
foreach ($linksByPo[$poid] ?? [] as $payId) {
$pay = $payById[$payId] ?? null;
if (!\is_array($pay)) {
continue;
}
++$seq;
$lines[] = self::formatLinkedPayRecordLineForExport($pay, $creatorNames, $seq);
}
$out[$poid] = implode("\n\n", $lines);
}
return $out;
}
/**
* 业务订单列表导出:写入与 PrescriptionOrderLists::setExcelFields 对应的字段(export=2 时由列表类调用)
* 「挂号渠道来源」取值与详情/列表解析一致:处方 appointment_id → 否则诊单下 MAX(挂号.id);业绩抽屉带渠道时与列表同源高亮。
@@ -3392,6 +3579,7 @@ class PrescriptionOrderLogic
$firstVisitAssistantByDiag = self::batchFirstVisitAssistantNameByDiagnosis($diagIdList, $diagById);
$assistantDeptCache = [];
$linkedPayExportByPo = self::batchLinkedPayRecordsExportTextByPoIds($poIds);
foreach ($lists as &$item) {
$poId = (int) ($item['id'] ?? 0);
@@ -3464,6 +3652,7 @@ class PrescriptionOrderLogic
$paid = round((float) ($item['linked_pay_paid_total'] ?? 0), 2);
$item['export_amount'] = number_format($amt, 2, '.', '');
$item['export_paid_amount'] = number_format($paid, 2, '.', '');
$item['export_linked_pay_records'] = $linkedPayExportByPo[$poId] ?? '';
$refundStored = round((float) ($item['refund_amount'] ?? 0), 2);
$item['export_refund_amount'] = $refundStored > 0
? number_format($refundStored, 2, '.', '')