Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37b2945c44 | ||
|
|
c9c2b4608f |
@@ -96,6 +96,21 @@ export function yejiStatsRevisitBreakdown(params: {
|
||||
return request.get({ url: '/stats.yejiStats/revisitBreakdown', params })
|
||||
}
|
||||
|
||||
/** 被指派数明细:与看板「被指派数」同口径;部门行传 dept_id,医助排行榜传 assistant_id */
|
||||
export function yejiStatsAssignLines(params: {
|
||||
start_date: string
|
||||
end_date: string
|
||||
dept_id?: number
|
||||
assistant_id?: number
|
||||
dept_ids?: string
|
||||
channel_code?: string
|
||||
tag_id?: string
|
||||
page?: number
|
||||
page_size?: number
|
||||
}) {
|
||||
return request.get({ url: '/stats.yejiStats/assignLines', params })
|
||||
}
|
||||
|
||||
export function doctorDailyStatsOverview(params: {
|
||||
start_date?: string
|
||||
end_date?: string
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -283,7 +283,15 @@
|
||||
<td class="col-name">{{ r.name }}</td>
|
||||
<td class="col-consult">{{ formatLeaderboardInt(r.consult_count) }}</td>
|
||||
<td class="col-deal">{{ formatLeaderboardInt(r.deal_order_count) }}</td>
|
||||
<td class="col-assign">{{ formatLeaderboardInt(r.assign_count ?? 0) }}</td>
|
||||
<td class="col-assign" @click.stop="onLeaderboardAssignCellClick(r)">
|
||||
<span
|
||||
v-if="Number(r.assign_count ?? 0) > 0"
|
||||
class="yeji-lead-cell--link"
|
||||
>{{ formatLeaderboardInt(r.assign_count ?? 0) }}</span>
|
||||
<template v-else>{{
|
||||
formatLeaderboardInt(r.assign_count ?? 0)
|
||||
}}</template>
|
||||
</td>
|
||||
<td class="col-appointment" @click.stop="onLeaderboardAppointmentCellClick(r)">
|
||||
<span
|
||||
v-if="Number(r.appointment_count ?? 0) > 0"
|
||||
@@ -561,7 +569,13 @@
|
||||
>{{ formatInt(r.lead_count) }}</span>
|
||||
<template v-else>{{ formatInt(r.lead_count) }}</template>
|
||||
</td>
|
||||
<td>{{ formatInt(r.assign_count ?? 0) }}</td>
|
||||
<td @click.stop="onAssignCountCellClick($event, tb, r)">
|
||||
<span
|
||||
v-if="yejiAssignCountCellClickable(r)"
|
||||
class="yeji-lead-cell--link"
|
||||
>{{ formatInt(r.assign_count ?? 0) }}</span>
|
||||
<template v-else>{{ formatInt(r.assign_count ?? 0) }}</template>
|
||||
</td>
|
||||
<td @click.stop="onYejiRevisitTotalCellClick(tb, r)">
|
||||
<span
|
||||
v-if="r.dept_id > 0 && Number(r.revisit_count ?? 0) > 0"
|
||||
@@ -1356,6 +1370,59 @@
|
||||
/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="assignLinesDialogVisible"
|
||||
width="min(960px, 96vw)"
|
||||
destroy-on-close
|
||||
class="yeji-leadlines-dialog"
|
||||
>
|
||||
<template #header>
|
||||
<div class="yeji-unassigned-dialog__head">
|
||||
<span class="yeji-unassigned-dialog__title">被指派明细</span>
|
||||
<p class="yeji-unassigned-dialog__sub">{{ assignLinesSubtitle }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<p v-if="assignLinesApiNote" class="yeji-unassigned-dialog__note">{{ assignLinesApiNote }}</p>
|
||||
<div v-loading="assignLinesLoading" class="yeji-unassigned-dialog__body">
|
||||
<el-table
|
||||
v-if="assignLinesRows.length > 0 || assignLinesLoading"
|
||||
:data="assignLinesRows"
|
||||
size="small"
|
||||
stripe
|
||||
border
|
||||
max-height="440"
|
||||
class="yeji-unassigned-dialog__table"
|
||||
:empty-text="assignLinesLoading ? '加载中…' : '暂无数据'"
|
||||
>
|
||||
<el-table-column type="index" label="#" width="46" :index="assignLinesIndexMethod" />
|
||||
<el-table-column prop="diagnosis_id" label="诊单ID" width="88" />
|
||||
<el-table-column prop="patient_name" label="患者" min-width="100" show-overflow-tooltip />
|
||||
<el-table-column prop="patient_phone" label="联系电话" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column prop="assistant_name" label="被指派医助" min-width="108" show-overflow-tooltip />
|
||||
<el-table-column prop="assign_count" label="指派次数" width="88" align="right" />
|
||||
<el-table-column prop="last_assign_time_text" label="最近指派时间" min-width="156" show-overflow-tooltip />
|
||||
</el-table>
|
||||
<el-empty
|
||||
v-if="!assignLinesLoading && assignLinesRows.length === 0"
|
||||
description="暂无被指派明细"
|
||||
/>
|
||||
<div v-if="assignLinesCount > 0" class="yeji-leadlines-dialog__pager">
|
||||
<el-pagination
|
||||
:current-page="assignLinesPage"
|
||||
:page-size="assignLinesPageSize"
|
||||
background
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="assignLinesCount"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:disabled="assignLinesLoading"
|
||||
:hide-on-single-page="false"
|
||||
@current-change="onAssignLinesPageChange"
|
||||
@size-change="onAssignLinesPageSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1376,6 +1443,7 @@ import {
|
||||
yejiStatsLeadLines,
|
||||
yejiStatsAppointmentLines,
|
||||
yejiStatsRevisitBreakdown,
|
||||
yejiStatsAssignLines,
|
||||
} from '@/api/stats'
|
||||
import { deptPerformanceTargetMonthMatrix } from '@/api/finance'
|
||||
import { prescriptionOrderLists } from '@/api/tcm'
|
||||
@@ -1491,6 +1559,17 @@ interface YejiLeadLineRow {
|
||||
external_contact_name: string
|
||||
}
|
||||
|
||||
interface YejiAssignLineRow {
|
||||
diagnosis_id: number
|
||||
patient_name: string
|
||||
patient_phone: string
|
||||
assistant_id: number
|
||||
assistant_name: string
|
||||
assign_count: number
|
||||
last_assign_time: number
|
||||
last_assign_time_text: string
|
||||
}
|
||||
|
||||
interface YejiRow {
|
||||
dept_id: number
|
||||
dept_name: string
|
||||
@@ -1709,6 +1788,31 @@ type AppointmentLinesCtx =
|
||||
}
|
||||
const appointmentLinesContext = ref<AppointmentLinesCtx | null>(null)
|
||||
|
||||
const assignLinesDialogVisible = ref(false)
|
||||
const assignLinesLoading = ref(false)
|
||||
const assignLinesSubtitle = ref('')
|
||||
const assignLinesApiNote = ref('')
|
||||
const assignLinesRows = ref<YejiAssignLineRow[]>([])
|
||||
const assignLinesCount = ref(0)
|
||||
const assignLinesPage = ref(1)
|
||||
const assignLinesPageSize = ref(20)
|
||||
type AssignLinesCtx =
|
||||
| {
|
||||
mode: 'assistant'
|
||||
assistant_id: number
|
||||
name: string
|
||||
start_date: string
|
||||
end_date: string
|
||||
}
|
||||
| {
|
||||
mode: 'dept'
|
||||
dept_id: number
|
||||
dept_name: string
|
||||
start_date: string
|
||||
end_date: string
|
||||
}
|
||||
const assignLinesContext = ref<AssignLinesCtx | null>(null)
|
||||
|
||||
const appointmentLinesShowChannelColumn = computed(() =>
|
||||
appointmentLinesRows.value.some(r => String(r.channel_source ?? '') !== '')
|
||||
)
|
||||
@@ -3193,6 +3297,124 @@ function onLeadCountCellClick(ev: MouseEvent, tb: YejiTable, row: YejiRow) {
|
||||
void openLeadLinesDialog(tb, row)
|
||||
}
|
||||
|
||||
function yejiAssignCountCellClickable(r: YejiRow): boolean {
|
||||
return r.dept_id > 0 && Number(r.assign_count ?? 0) > 0
|
||||
}
|
||||
|
||||
function onAssignCountCellClick(ev: MouseEvent, tb: YejiTable, row: YejiRow) {
|
||||
if (!yejiAssignCountCellClickable(row)) {
|
||||
return
|
||||
}
|
||||
ev.stopPropagation()
|
||||
void openAssignLinesDialogForDept(tb, row)
|
||||
}
|
||||
|
||||
function assignLinesIndexMethod(index: number) {
|
||||
return (assignLinesPage.value - 1) * assignLinesPageSize.value + index + 1
|
||||
}
|
||||
|
||||
function buildAssignLinesRequestParams(page: number, pageSize: number): Record<string, string | number> | null {
|
||||
const ctx = assignLinesContext.value
|
||||
if (!ctx) {
|
||||
return null
|
||||
}
|
||||
const p: Record<string, string | number> = {
|
||||
start_date: ctx.start_date,
|
||||
end_date: ctx.end_date,
|
||||
page,
|
||||
page_size: pageSize,
|
||||
}
|
||||
if (ctx.mode === 'dept') {
|
||||
p.dept_id = ctx.dept_id
|
||||
} else {
|
||||
p.assistant_id = ctx.assistant_id
|
||||
}
|
||||
if (selectedDeptIds.value.length > 0) {
|
||||
p.dept_ids = selectedDeptIds.value.join(',')
|
||||
}
|
||||
if (selectedChannel.value) {
|
||||
p.channel_code = selectedChannel.value
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
async function fetchAssignLinesPage() {
|
||||
const params = buildAssignLinesRequestParams(assignLinesPage.value, assignLinesPageSize.value)
|
||||
if (!params) {
|
||||
return
|
||||
}
|
||||
assignLinesLoading.value = true
|
||||
try {
|
||||
const res: any = await yejiStatsAssignLines(params as any)
|
||||
assignLinesRows.value = Array.isArray(res?.lists) ? res.lists : []
|
||||
assignLinesCount.value = Number(res?.count ?? 0)
|
||||
assignLinesApiNote.value = typeof res?.note === 'string' ? res.note : ''
|
||||
} catch (e: unknown) {
|
||||
if (axios.isCancel(e)) return
|
||||
const any = e as any
|
||||
ElMessage.error(any?.msg || any?.message || '加载被指派明细失败')
|
||||
assignLinesRows.value = []
|
||||
assignLinesCount.value = 0
|
||||
assignLinesApiNote.value = ''
|
||||
} finally {
|
||||
assignLinesLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function openAssignLinesDialogForDept(tb: YejiTable, row: YejiRow) {
|
||||
assignLinesContext.value = {
|
||||
mode: 'dept',
|
||||
dept_id: row.dept_id,
|
||||
dept_name: row.dept_name,
|
||||
start_date: tb.start_date,
|
||||
end_date: tb.end_date,
|
||||
}
|
||||
assignLinesSubtitle.value = `${row.dept_name} · ${tb.start_date} ~ ${tb.end_date}`
|
||||
assignLinesApiNote.value = ''
|
||||
assignLinesRows.value = []
|
||||
assignLinesCount.value = 0
|
||||
assignLinesPage.value = 1
|
||||
assignLinesPageSize.value = 20
|
||||
assignLinesDialogVisible.value = true
|
||||
await fetchAssignLinesPage()
|
||||
}
|
||||
|
||||
function onLeaderboardAssignCellClick(r: LeaderboardPack['leaderboards'][0]['rows'][0]) {
|
||||
if (!r || r.admin_id <= 0 || !leaderboardBlock.value) {
|
||||
return
|
||||
}
|
||||
if (Number(r.assign_count ?? 0) <= 0) {
|
||||
return
|
||||
}
|
||||
const lb = leaderboardBlock.value
|
||||
assignLinesContext.value = {
|
||||
mode: 'assistant',
|
||||
assistant_id: r.admin_id,
|
||||
name: r.name,
|
||||
start_date: lb.start_date,
|
||||
end_date: lb.end_date,
|
||||
}
|
||||
assignLinesSubtitle.value = `${r.name} · 医助 · ${lb.start_date} ~ ${lb.end_date}`
|
||||
assignLinesApiNote.value = ''
|
||||
assignLinesRows.value = []
|
||||
assignLinesCount.value = 0
|
||||
assignLinesPage.value = 1
|
||||
assignLinesPageSize.value = 20
|
||||
assignLinesDialogVisible.value = true
|
||||
void fetchAssignLinesPage()
|
||||
}
|
||||
|
||||
function onAssignLinesPageChange(p: number) {
|
||||
assignLinesPage.value = p
|
||||
void fetchAssignLinesPage()
|
||||
}
|
||||
|
||||
function onAssignLinesPageSizeChange(size: number) {
|
||||
assignLinesPageSize.value = size
|
||||
assignLinesPage.value = 1
|
||||
void fetchAssignLinesPage()
|
||||
}
|
||||
|
||||
const LEAD_LINES_EXPORT_COLUMNS: { key: keyof YejiLeadLineRow; label: string }[] = [
|
||||
{ key: 'event_time_text', label: '进线时间' },
|
||||
{ key: 'reception_admin_name', label: '接待' },
|
||||
|
||||
@@ -17,6 +17,7 @@ use app\adminapi\logic\stats\YejiStatsLogic;
|
||||
* - GET stats.yejiStats/leadLines 进线数据明细(add_external_contact 逐条)
|
||||
* - GET stats.yejiStats/appointmentLines 医助排行榜「预约诊单」挂号逐条明细
|
||||
* - GET stats.yejiStats/revisitBreakdown 二中心复诊下钻:医助 × 业务订单笔数
|
||||
* - GET stats.yejiStats/assignLines 被指派数明细(医助×诊单去重,与看板同口径)
|
||||
*/
|
||||
class YejiStatsController extends BaseAdminController
|
||||
{
|
||||
@@ -141,4 +142,13 @@ class YejiStatsController extends BaseAdminController
|
||||
|
||||
return $this->data(YejiStatsLogic::revisitDeptAssistantBreakdown($params, $this->adminId, $this->adminInfo));
|
||||
}
|
||||
|
||||
/** 被指派数明细:与看板「被指派数」同口径(区间内非继承指派,医助×诊单去重) */
|
||||
public function assignLines()
|
||||
{
|
||||
@set_time_limit(120);
|
||||
$params = $this->request->get();
|
||||
|
||||
return $this->data(YejiStatsLogic::assignLines($params, $this->adminId, $this->adminInfo));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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=3,appointment_date 落入区间,有效医助 COALESCE(挂号.assistant_id,诊单.assistant_id))。
|
||||
|
||||
@@ -2616,6 +2616,420 @@ class YejiStatsLogic
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 被指派明细:与看板「被指派数」同口径(区间内非继承指派,医助×诊单去重)。
|
||||
*
|
||||
* @param array{
|
||||
* start_date?:string,end_date?:string,dept_ids?:int[]|string,channel_code?:string,tag_id?:string,
|
||||
* assistant_id?:int|string,admin_id?:int|string,dept_id?:int|string,
|
||||
* page?:int|string,page_size?:int|string
|
||||
* } $params
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function assignLines(array $params, int $viewerAdminId = 0, array $viewerAdminInfo = []): array
|
||||
{
|
||||
$c = self::resolveYejiContext($params);
|
||||
if ($viewerAdminId > 0) {
|
||||
self::applyYejiDataScope($c, $viewerAdminId, $viewerAdminInfo);
|
||||
}
|
||||
|
||||
$assistantId = (int) ($params['assistant_id'] ?? $params['admin_id'] ?? 0);
|
||||
|
||||
if ($assistantId <= 0 && array_key_exists('dept_id', $params)) {
|
||||
return self::yejiDeptAssignLinesInner($params, $c);
|
||||
}
|
||||
|
||||
return self::yejiAssistantAssignLinesInner($params, $c, $assistantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
* @param array<string, mixed> $c
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function yejiDeptAssignLinesInner(array $params, array $c): array
|
||||
{
|
||||
$deptIdParam = (int) ($params['dept_id'] ?? 0);
|
||||
$channelCode = (string) $c['channelCode'];
|
||||
$deptName = $deptIdParam === 0
|
||||
? '未归属中心'
|
||||
: self::formatYejiDeptRowDisplayName($deptIdParam, $c['deptById']);
|
||||
|
||||
$empty = static function (string $note) use ($c, $channelCode, $deptIdParam, $deptName): array {
|
||||
return self::yejiAssignLinesEmptyPayload($c, $note, 0, '', $deptIdParam, $deptName, $channelCode);
|
||||
};
|
||||
|
||||
if ($deptIdParam <= 0) {
|
||||
return $empty('未归属中心无被指派明细(看板该行为 0)。');
|
||||
}
|
||||
if (!in_array($deptIdParam, $c['tableRowDeptIds'], true)) {
|
||||
return $empty('该部门不在当前展示部门筛选或数据权限范围内。');
|
||||
}
|
||||
|
||||
$erCenterDeptSet = DeptLogic::getErCenterSubtreeDeptIdSet();
|
||||
if ($channelCode !== '' && isset($erCenterDeptSet[$deptIdParam])) {
|
||||
return $empty('与看板一致:二中心及其组织下级在选定渠道下被指派数计 0,无明细。');
|
||||
}
|
||||
|
||||
$targetAssistantIds = [];
|
||||
foreach ($c['adminToPrimary'] as $aid => $primary) {
|
||||
if ((int) $primary === $deptIdParam) {
|
||||
$targetAssistantIds[] = (int) $aid;
|
||||
}
|
||||
}
|
||||
if ($targetAssistantIds === []) {
|
||||
return $empty('当前部门下没有可映射的被指派医助,无明细。');
|
||||
}
|
||||
|
||||
return self::yejiAssignLinesFetch($c, $params, $targetAssistantIds, 0, '', $deptIdParam, $deptName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
* @param array<string, mixed> $c
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function yejiAssistantAssignLinesInner(array $params, array $c, int $assistantId): array
|
||||
{
|
||||
$channelCode = (string) $c['channelCode'];
|
||||
|
||||
$assistantName = '';
|
||||
if ($assistantId > 0) {
|
||||
$assistantName = (string) (Db::name('admin')->where('id', $assistantId)->whereNull('delete_time')->value('name') ?: '');
|
||||
}
|
||||
|
||||
$empty = static function (string $note) use ($c, $assistantId, $assistantName, $channelCode): array {
|
||||
return self::yejiAssignLinesEmptyPayload($c, $note, $assistantId, $assistantName, 0, '', $channelCode);
|
||||
};
|
||||
|
||||
if ($assistantId <= 0) {
|
||||
return $empty('请指定有效医助,或传入 dept_id 查看部门汇总明细。');
|
||||
}
|
||||
|
||||
$dataScopeRestricted = !empty($c['dataScopeRestricted']);
|
||||
$dataScopeAdminIds = $dataScopeRestricted && isset($c['dataScopeVisibleAdminIds'])
|
||||
? $c['dataScopeVisibleAdminIds']
|
||||
: null;
|
||||
if ($dataScopeRestricted && $dataScopeAdminIds !== null) {
|
||||
$flip = array_flip($dataScopeAdminIds);
|
||||
if (!isset($flip[$assistantId])) {
|
||||
return $empty('当前账号数据权限下不可查看该医助被指派明细。');
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($c['adminToPrimary'][$assistantId])) {
|
||||
return $empty('该医助不在当前业绩看板展示范围内。');
|
||||
}
|
||||
|
||||
$tagDiagIds = $c['tagDiagIds'];
|
||||
$tagAssistantIds = $c['tagAssistantIds'];
|
||||
$tagFallback = $c['tagFallback'];
|
||||
$scopedAssistants = $tagFallback ? $tagAssistantIds : null;
|
||||
|
||||
if ($tagDiagIds !== null && $tagDiagIds === []) {
|
||||
return $empty('当前渠道标签下无关联诊单,无被指派明细。');
|
||||
}
|
||||
if ($scopedAssistants !== null && $scopedAssistants === []) {
|
||||
return $empty('当前渠道标签下无关联医助,无被指派明细。');
|
||||
}
|
||||
if ($scopedAssistants !== null && !isset($scopedAssistants[$assistantId])) {
|
||||
return $empty('当前渠道标签口径下该医助无被指派记录(与排行榜「被指派数」一致)。');
|
||||
}
|
||||
|
||||
$erCenterDeptSet = DeptLogic::getErCenterSubtreeDeptIdSet();
|
||||
$primaryDept = (int) ($c['adminToPrimary'][$assistantId] ?? 0);
|
||||
if ($channelCode !== '' && $primaryDept > 0 && isset($erCenterDeptSet[$primaryDept])) {
|
||||
return $empty('与看板一致:二中心及其组织下级医助在选定渠道下被指派数计 0,无明细。');
|
||||
}
|
||||
|
||||
return self::yejiAssignLinesFetch(
|
||||
$c,
|
||||
$params,
|
||||
[$assistantId],
|
||||
$assistantId,
|
||||
$assistantName,
|
||||
0,
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $c
|
||||
* @param array<string, mixed> $params
|
||||
* @param list<int> $targetAssistantIds
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function yejiAssignLinesFetch(
|
||||
array $c,
|
||||
array $params,
|
||||
array $targetAssistantIds,
|
||||
int $assistantIdOut,
|
||||
string $assistantNameOut,
|
||||
int $deptIdOut,
|
||||
string $deptNameOut
|
||||
): array {
|
||||
$channelCode = (string) $c['channelCode'];
|
||||
$startTs = (int) $c['startTs'];
|
||||
$endTs = (int) $c['endTs'];
|
||||
$page = max(1, (int) ($params['page'] ?? 1));
|
||||
$limit = min(100, max(1, (int) ($params['page_size'] ?? 20)));
|
||||
$offset = ($page - 1) * $limit;
|
||||
|
||||
$dataScopeRestricted = !empty($c['dataScopeRestricted']);
|
||||
$dataScopeAdminIds = $dataScopeRestricted && isset($c['dataScopeVisibleAdminIds'])
|
||||
? $c['dataScopeVisibleAdminIds']
|
||||
: null;
|
||||
if ($dataScopeAdminIds !== null && $dataScopeAdminIds !== []) {
|
||||
$visFlip = array_flip($dataScopeAdminIds);
|
||||
$targetAssistantIds = array_values(array_filter(
|
||||
$targetAssistantIds,
|
||||
static fn (int $aid): bool => isset($visFlip[$aid])
|
||||
));
|
||||
}
|
||||
|
||||
if ($targetAssistantIds === []) {
|
||||
return self::yejiAssignLinesEmptyPayload(
|
||||
$c,
|
||||
'当前数据权限下无可查看的被指派医助。',
|
||||
$assistantIdOut,
|
||||
$assistantNameOut,
|
||||
$deptIdOut,
|
||||
$deptNameOut,
|
||||
$channelCode
|
||||
);
|
||||
}
|
||||
|
||||
$tagDiagIds = $c['tagDiagIds'];
|
||||
$tagAssistantIds = $c['tagAssistantIds'];
|
||||
$tagFallback = $c['tagFallback'];
|
||||
$scopedAssistants = $tagFallback ? $tagAssistantIds : null;
|
||||
|
||||
if ($tagDiagIds !== null && $tagDiagIds === []) {
|
||||
return self::yejiAssignLinesEmptyPayload(
|
||||
$c,
|
||||
'当前渠道标签下无关联诊单,无被指派明细。',
|
||||
$assistantIdOut,
|
||||
$assistantNameOut,
|
||||
$deptIdOut,
|
||||
$deptNameOut,
|
||||
$channelCode
|
||||
);
|
||||
}
|
||||
if ($scopedAssistants !== null && $scopedAssistants === []) {
|
||||
return self::yejiAssignLinesEmptyPayload(
|
||||
$c,
|
||||
'当前渠道标签下无关联医助,无被指派明细。',
|
||||
$assistantIdOut,
|
||||
$assistantNameOut,
|
||||
$deptIdOut,
|
||||
$deptNameOut,
|
||||
$channelCode
|
||||
);
|
||||
}
|
||||
|
||||
$buildBaseQuery = static function () use (
|
||||
$c,
|
||||
$targetAssistantIds,
|
||||
$startTs,
|
||||
$endTs,
|
||||
$tagDiagIds,
|
||||
$scopedAssistants
|
||||
) {
|
||||
$diagTable = self::tableWithPrefix('tcm_diagnosis');
|
||||
$q = Db::name('tcm_diagnosis_assign_log')
|
||||
->alias('lg')
|
||||
->join("{$diagTable} dg", 'dg.id = lg.diagnosis_id AND dg.delete_time IS NULL', 'INNER')
|
||||
->where('lg.create_time', 'between', [$startTs, $endTs])
|
||||
->where('lg.to_assistant_id', '>', 0)
|
||||
->where('lg.is_inherit', 0)
|
||||
->whereIn('lg.to_assistant_id', $targetAssistantIds);
|
||||
|
||||
self::applyYejiAssignLogChannelFilters(
|
||||
$q,
|
||||
$tagDiagIds,
|
||||
$scopedAssistants,
|
||||
$c['appointmentChannelValues'],
|
||||
$c['channelFilterActive'],
|
||||
$c['channelInfo']
|
||||
);
|
||||
|
||||
return $q;
|
||||
};
|
||||
|
||||
$countRows = $buildBaseQuery()
|
||||
->field(['lg.to_assistant_id', 'lg.diagnosis_id'])
|
||||
->group('lg.to_assistant_id,lg.diagnosis_id')
|
||||
->select()
|
||||
->toArray();
|
||||
$count = \count($countRows);
|
||||
|
||||
$rawList = $buildBaseQuery()
|
||||
->field([
|
||||
'lg.to_assistant_id',
|
||||
'lg.diagnosis_id',
|
||||
Db::raw('COUNT(*) AS assign_count'),
|
||||
Db::raw('MAX(lg.create_time) AS last_assign_time'),
|
||||
])
|
||||
->group('lg.to_assistant_id,lg.diagnosis_id')
|
||||
->order('last_assign_time', 'desc')
|
||||
->order('lg.diagnosis_id', 'desc')
|
||||
->limit($offset, $limit)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$diagIds = [];
|
||||
$assistantIds = [];
|
||||
foreach ($rawList as $rw) {
|
||||
$diagIds[(int) ($rw['diagnosis_id'] ?? 0)] = true;
|
||||
$assistantIds[(int) ($rw['to_assistant_id'] ?? 0)] = true;
|
||||
}
|
||||
$diagInfo = self::fetchYejiAssignDiagnosisInfo(array_keys($diagIds));
|
||||
$nameMap = $assistantIds !== []
|
||||
? Db::name('admin')->whereIn('id', array_keys($assistantIds))->column('name', 'id')
|
||||
: [];
|
||||
|
||||
$lists = [];
|
||||
foreach ($rawList as $rw) {
|
||||
$did = (int) ($rw['diagnosis_id'] ?? 0);
|
||||
$aid = (int) ($rw['to_assistant_id'] ?? 0);
|
||||
$lastTs = (int) ($rw['last_assign_time'] ?? 0);
|
||||
$lists[] = [
|
||||
'diagnosis_id' => $did,
|
||||
'patient_name' => (string) ($diagInfo[$did]['patient_name'] ?? ''),
|
||||
'patient_phone' => (string) ($diagInfo[$did]['phone'] ?? ''),
|
||||
'assistant_id' => $aid,
|
||||
'assistant_name' => $aid > 0 ? (string) ($nameMap[$aid] ?? ('#' . $aid)) : '—',
|
||||
'assign_count' => (int) ($rw['assign_count'] ?? 0),
|
||||
'last_assign_time' => $lastTs,
|
||||
'last_assign_time_text' => $lastTs > 0 ? date('Y-m-d H:i:s', $lastTs) : '',
|
||||
];
|
||||
}
|
||||
|
||||
$note = '与看板「被指派数」同口径:区间内 `tcm_diagnosis_assign_log` 成功指派(to_assistant_id>0,剔除勾选「继承」),'
|
||||
. '按指派操作时间落区间,「医助 × 诊单」去重、剔除已删诊单。';
|
||||
|
||||
return [
|
||||
'start_date' => (string) $c['startDate'],
|
||||
'end_date' => (string) $c['endDate'],
|
||||
'assistant_id' => $assistantIdOut,
|
||||
'assistant_name' => $assistantNameOut,
|
||||
'dept_id' => $deptIdOut,
|
||||
'dept_name' => $deptNameOut,
|
||||
'channel_code' => $channelCode,
|
||||
'count' => $count,
|
||||
'lists' => $lists,
|
||||
'note' => $note,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $c
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function yejiAssignLinesEmptyPayload(
|
||||
array $c,
|
||||
string $note,
|
||||
int $assistantId,
|
||||
string $assistantName,
|
||||
int $deptId,
|
||||
string $deptName,
|
||||
string $channelCode
|
||||
): array {
|
||||
return [
|
||||
'start_date' => (string) $c['startDate'],
|
||||
'end_date' => (string) $c['endDate'],
|
||||
'assistant_id' => $assistantId,
|
||||
'assistant_name' => $assistantName,
|
||||
'dept_id' => $deptId,
|
||||
'dept_name' => $deptName,
|
||||
'channel_code' => $channelCode,
|
||||
'count' => 0,
|
||||
'lists' => [],
|
||||
'note' => $note,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \think\db\Query $query
|
||||
*/
|
||||
private static function applyYejiAssignLogChannelFilters(
|
||||
$query,
|
||||
?array $tagDiagIds,
|
||||
?array $tagAssistantIds,
|
||||
array $appointmentChannelValues,
|
||||
bool $channelFilterActive,
|
||||
?array $channelInfo
|
||||
): void {
|
||||
if (!$channelFilterActive) {
|
||||
return;
|
||||
}
|
||||
if ($appointmentChannelValues !== []) {
|
||||
$norm = self::normalizeAppointmentChannelValues($appointmentChannelValues);
|
||||
if ($norm === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return;
|
||||
}
|
||||
$pack = self::buildChannelScopedAppointmentExistsSqlAndBindings('dg.id', $norm, $channelInfo);
|
||||
if ($pack === null) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return;
|
||||
}
|
||||
$query->whereRaw($pack[0], $pack[1]);
|
||||
} elseif ($tagDiagIds !== null || $tagAssistantIds !== null) {
|
||||
if ($tagDiagIds !== null && $tagDiagIds === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return;
|
||||
}
|
||||
if ($tagAssistantIds !== null && $tagAssistantIds === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return;
|
||||
}
|
||||
if ($tagDiagIds !== null) {
|
||||
$query->whereIn('lg.diagnosis_id', $tagDiagIds);
|
||||
}
|
||||
if ($tagAssistantIds !== null) {
|
||||
$query->whereIn('lg.to_assistant_id', array_map('intval', array_keys($tagAssistantIds)));
|
||||
}
|
||||
} else {
|
||||
$query->whereRaw('0 = 1');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<int> $diagIds
|
||||
*
|
||||
* @return array<int, array{patient_name:string,phone:string}>
|
||||
*/
|
||||
private static function fetchYejiAssignDiagnosisInfo(array $diagIds): array
|
||||
{
|
||||
if ($diagIds === []) {
|
||||
return [];
|
||||
}
|
||||
$rows = Db::name('tcm_diagnosis')
|
||||
->whereIn('id', $diagIds)
|
||||
->field(['id', 'patient_name', 'phone'])
|
||||
->select()
|
||||
->toArray();
|
||||
$out = [];
|
||||
foreach ($rows as $r) {
|
||||
$out[(int) $r['id']] = [
|
||||
'patient_name' => trim((string) ($r['patient_name'] ?? '')),
|
||||
'phone' => trim((string) ($r['phone'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* admin → 展示「中心」映射:用于挂号明细按部门筛选(与 aggregateConsults 映射同源)。
|
||||
*/
|
||||
|
||||
@@ -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, '.', '')
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
-- 业绩看板「被指派数」明细接口(fans/yeji · pid=293)
|
||||
-- 幂等:按 perms 判重
|
||||
|
||||
INSERT INTO `zyt_system_menu`
|
||||
(`pid`, `type`, `name`, `icon`, `sort`, `perms`, `paths`, `component`, `selected`, `params`, `is_show`, `is_disable`, `create_time`, `update_time`)
|
||||
SELECT
|
||||
293, 'A', '业绩-被指派明细', '', 12,
|
||||
'stats.yejiStats/assignLines', '', '', '', '',
|
||||
1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
|
||||
FROM DUAL
|
||||
WHERE EXISTS (SELECT 1 FROM `zyt_system_menu` p WHERE p.id = 293)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM `zyt_system_menu` m
|
||||
WHERE m.perms = 'stats.yejiStats/assignLines'
|
||||
);
|
||||
Reference in New Issue
Block a user