This commit is contained in:
Your Name
2026-05-07 15:27:57 +08:00
parent 3014a99db5
commit 49e7169630
10 changed files with 914 additions and 33 deletions
@@ -15,6 +15,7 @@ use app\adminapi\logic\stats\YejiStatsLogic;
* - GET stats.yejiStats/deptOptions 部门下拉
* - GET stats.yejiStats/leaderboard 医助排行榜(按展示部门分表)
* - GET stats.yejiStats/leadLines 进线数据明细(add_external_contact 逐条)
* - GET stats.yejiStats/appointmentLines 医助排行榜「预约诊单」挂号逐条明细
* - GET stats.yejiStats/revisitBreakdown 二中心复诊下钻:医助 × 业务订单笔数
*/
class YejiStatsController extends BaseAdminController
@@ -123,6 +124,15 @@ class YejiStatsController extends BaseAdminController
return $this->data(YejiStatsLogic::leadLineList($params, $this->adminId, $this->adminInfo));
}
/** 医助排行榜「预约诊单」:挂号记录逐条列表(与计数同口径) */
public function appointmentLines()
{
@set_time_limit(120);
$params = $this->request->get();
return $this->data(YejiStatsLogic::leaderboardAppointmentLines($params, $this->adminId, $this->adminInfo));
}
/** 二中心复诊:按部门行 + 复诊分项拆解医助与业务订单笔数 */
public function revisitBreakdown()
{
@@ -299,6 +299,14 @@ class YejiStatsLogic
);
$consults = $consultsBoth['all'];
$channelConsults = $consultsBoth['scoped'];
$bookedByDept = self::aggregateBookedAppointmentsByDept(
$startTs,
$endTs,
$adminToPrimary,
$tagDiagIds,
$tagAssistantIds,
$dataScopeAdminIds
);
// 业绩:与 /tcm.prescriptionOrder/lists 同口径(仅剔除已取消=4,含 NULL)
// - performance(全量) → "合计业绩" 列;不受所选渠道影响
// - channelPerformance(渠道) → "{渠道}业绩" 列 + ROI 分子;未选渠道时与 performance 相同
@@ -357,11 +365,13 @@ class YejiStatsLogic
$totalCost = 0.0;
$unmappedConsult = (int) ($consults[0] ?? 0);
$consultSumInTable = 0;
$bookedSumInTable = 0;
foreach ($tableRowDeptIds as $deptId) {
$lead = (int) ($leads[$deptId] ?? 0);
$cnt = (int) ($consults[$deptId] ?? 0);
$consultSumInTable += $cnt;
$bookedSumInTable += (int) ($bookedByDept[$deptId] ?? 0);
$cCnt = (int) ($channelConsults[$deptId] ?? 0);
// 合计业绩:部门下所有订单业绩(≠4),不限渠道
$perf = round((float) ($performance[$deptId] ?? 0), 1);
@@ -383,6 +393,7 @@ class YejiStatsLogic
'assign_count' => $assignCnt,
'revisit_count' => $rv,
'revisit_slots' => $rvSlots,
'appointment_booked_count' => (int) ($bookedByDept[$deptId] ?? 0),
'consult_count' => $cnt,
'deal_order_count' => $dDeal,
// {渠道}成交单:选定渠道时与 completed_performance 同源;未选渠道时与 consult_count 相同
@@ -428,6 +439,8 @@ class YejiStatsLogic
foreach ($tableRowDeptIds as $rid) {
$allocatedDealOrderSum += (int) ($dealOrderByDept[$rid] ?? 0);
}
$totalBooked = $narrowTotalsToTable ? $bookedSumInTable : array_sum($bookedByDept);
if ($narrowTotalsToTable) {
$totalConsult = $consultSumInTable;
$unmappedDealOrder = 0;
@@ -451,6 +464,7 @@ class YejiStatsLogic
'assign_count' => 0,
'revisit_count' => 0,
'revisit_slots' => [],
'appointment_booked_count' => (int) ($bookedByDept[0] ?? 0),
'consult_count' => $unmappedConsult,
'deal_order_count' => $unmappedDealOrder,
'channel_consult_count' => 0,
@@ -511,6 +525,7 @@ class YejiStatsLogic
'assign_count' => $totalAssign,
'revisit_count' => $totalRevisit,
'revisit_slots' => $totalRevisitSlots,
'appointment_booked_count' => $totalBooked,
'consult_count' => $totalConsult,
'deal_order_count' => $totalDealOrderCount,
'channel_consult_count' => $totalChannelConsult,
@@ -631,6 +646,13 @@ class YejiStatsLogic
$channelFilterActive,
$dataScopeAdminIds
);
$appointmentByAdmin = self::countBookedAppointmentsByAdminForLeaderboard(
$startTs,
$endTs,
$tagDiagIds,
$tagFallback ? $tagAssistantIds : null,
$dataScopeAdminIds
);
if ($channelFilterActive) {
self::excludeErCenterFromAdminLeaderboard(
@@ -671,8 +693,10 @@ class YejiStatsLogic
$rangeNote = $channelFilterActive
? ('排行诊金 = 选定渠道「' . $channelName . '」业绩口径(与部门表该渠道业绩列一致)。被指派数与部门表同口径。接诊率 = 诊金 ÷ 进线(元/进线)。'
. '「二中心」医助在该渠道下诊金、进线与被指派计 0。')
: '排行诊金 = 处方订单列表 assistant_id 口径(诊单医助业绩,与列表按医助筛选一致)。被指派数为指派至该医助的次数。接诊率 = 诊金 ÷ 进线(元/进线;进线为 0 时为 0)。';
. '「二中心」医助在该渠道下诊金、进线与被指派计 0。'
. ' 预约诊单:预约表按「预约日期」落在区间内、有效医助口径同接诊,状态含已预约/已完成/已过号(不含已取消);科室下医助全量展示。')
: '排行诊金 = 处方订单列表 assistant_id 口径(诊单医助业绩,与列表按医助筛选一致)。被指派数为指派至该医助的次数。接诊率 = 诊金 ÷ 进线(元/进线;进线为 0 时为 0)。'
. ' 预约诊单:预约表按「预约日期」落在区间内、有效医助口径同接诊,状态含已预约/已完成/已过号(不含已取消);科室下医助全量展示。';
$leaderboards = [];
foreach ($tableRowDeptIds as $deptId) {
@@ -690,18 +714,9 @@ class YejiStatsLogic
$consult = (int) ($consultsByAdmin[$aid] ?? 0);
$dealOrders = (int) ($dealOrderByAdmin[$aid] ?? 0);
$assignCnt = (int) ($assignsByAdmin[$aid] ?? 0);
$apptCnt = (int) ($appointmentByAdmin[$aid] ?? 0);
$rv = $isErCenterLb ? (int) ($revisitByAssistant['totals'][$aid] ?? 0) : 0;
$rvSlots = $isErCenterLb ? ($revisitByAssistant['slots'][$aid] ?? []) : [];
if (
$fee < 0.005
&& $lead === 0
&& $consult === 0
&& $dealOrders === 0
&& $assignCnt === 0
&& $rv === 0
) {
continue;
}
$rawFee = (float) ($feeByAdmin[$aid] ?? 0.0);
$rate = $lead > 0 ? round($rawFee / $lead, 1) : 0.0;
$row = [
@@ -712,6 +727,7 @@ class YejiStatsLogic
'assign_count' => $assignCnt,
'consult_count' => $consult,
'deal_order_count' => $dealOrders,
'appointment_count' => $apptCnt,
'consult_rate' => $rate,
'dept_abbr' => $deptAbbr,
];
@@ -731,7 +747,6 @@ class YejiStatsLogic
foreach ($rows as $i => $row) {
$rows[$i]['rank'] = $i + 1;
}
$rows = array_slice($rows, 0, 50);
$leaderboards[] = [
'dept_id' => $deptId,
@@ -1588,6 +1603,99 @@ class YejiStatsLogic
return $byDept;
}
/**
* 业绩表「预约诊单」列:status∈{1,3,4};部门归属规则与 {@see aggregateConsults} 一致。
*
* @return array<int, int> 展示部门 id => 条数(键 0 为无法归入展示「中心」)
*/
private static function aggregateBookedAppointmentsByDept(
int $startTs,
int $endTs,
array $adminToPrimary,
?array $tagDiagIds,
?array $tagAssistantIds,
?array $dataScopeAdminIds = null
): array {
if ($tagDiagIds !== null && $tagDiagIds === []) {
return [];
}
if ($tagAssistantIds !== null && $tagAssistantIds === []) {
return [];
}
$startDate = date('Y-m-d', $startTs);
$endDate = date('Y-m-d', $endTs);
$eff = self::consultEffectiveAssistantSql();
$query = Db::name('doctor_appointment')->alias('a')
->leftJoin('tcm_diagnosis u', 'a.patient_id = u.id')
->whereBetween('a.appointment_date', [$startDate, $endDate])
->whereIn('a.status', [1, 3, 4])
->whereRaw('(u.id IS NULL OR u.delete_time IS NULL)');
if ($tagDiagIds !== null) {
$query->whereIn('a.patient_id', $tagDiagIds);
}
if ($tagAssistantIds !== null) {
$ids = array_map('intval', array_keys($tagAssistantIds));
$query->whereRaw("({$eff}) IN (" . implode(',', $ids) . ')');
}
$query
->field([
Db::raw("({$eff}) AS eff_a"),
'a.doctor_id',
Db::raw('COUNT(*) AS cnt'),
])
->group([$eff, 'a.doctor_id']);
$rows = $query->select()->toArray();
if ($rows === []) {
return [];
}
$visFlip = $dataScopeAdminIds !== null && $dataScopeAdminIds !== []
? array_flip($dataScopeAdminIds)
: null;
$byDept = [];
foreach ($rows as $r) {
$effId = (int) ($r['eff_a'] ?? 0);
$docId = (int) ($r['doctor_id'] ?? 0);
$cnt = (int) ($r['cnt'] ?? 0);
if ($cnt <= 0) {
continue;
}
if ($visFlip !== null) {
if ($effId > 0) {
if (!isset($visFlip[$effId])) {
continue;
}
} elseif ($docId > 0) {
if (!isset($visFlip[$docId])) {
continue;
}
} else {
continue;
}
}
$primary = 0;
if ($effId > 0) {
$primary = (int) ($adminToPrimary[$effId] ?? 0);
}
if ($primary <= 0 && $docId > 0) {
$primary = (int) ($adminToPrimary[$docId] ?? 0);
}
if ($primary > 0) {
$byDept[$primary] = ($byDept[$primary] ?? 0) + $cnt;
} else {
$byDept[0] = ($byDept[0] ?? 0) + $cnt;
}
}
return $byDept;
}
/**
* 接诊按医助汇总(不按部门归并)。
*
@@ -1646,6 +1754,72 @@ class YejiStatsLogic
return 'COALESCE(NULLIF(a.assistant_id, 0), NULLIF(u.assistant_id, 0))';
}
/**
* 医助排行榜「预约诊单」:`doctor_appointment.appointment_date` 落在统计区间内;
* 状态为已预约(1)、已完成(3)、已过号(4),不含已取消(2);
* 有效医助与 {@see consultEffectiveAssistantSql()} 一致。
*
* @return array<int, int>
*/
private static function countBookedAppointmentsByAdminForLeaderboard(
int $startTs,
int $endTs,
?array $tagDiagIds,
?array $tagAssistantIds,
?array $dataScopeAdminIds
): array {
if ($tagDiagIds !== null && $tagDiagIds === []) {
return [];
}
if ($tagAssistantIds !== null && $tagAssistantIds === []) {
return [];
}
$startDate = date('Y-m-d', $startTs);
$endDate = date('Y-m-d', $endTs);
$eff = self::consultEffectiveAssistantSql();
$query = Db::name('doctor_appointment')->alias('a')
->leftJoin('tcm_diagnosis u', 'a.patient_id = u.id')
->whereBetween('a.appointment_date', [$startDate, $endDate])
->whereIn('a.status', [1, 3, 4])
->whereRaw('(u.id IS NULL OR u.delete_time IS NULL)');
if ($tagDiagIds !== null) {
$query->whereIn('a.patient_id', $tagDiagIds);
}
if ($tagAssistantIds !== null) {
$ids = array_map('intval', array_keys($tagAssistantIds));
$query->whereRaw("({$eff}) IN (" . implode(',', $ids) . ')');
}
$query->field([Db::raw("({$eff}) AS eff_a"), Db::raw('COUNT(*) AS cnt')])
->group($eff);
$rows = $query->select()->toArray();
if ($rows === []) {
return [];
}
$visFlip = $dataScopeAdminIds !== null && $dataScopeAdminIds !== []
? array_flip($dataScopeAdminIds)
: null;
$byAdmin = [];
foreach ($rows as $r) {
$aid = (int) ($r['eff_a'] ?? 0);
if ($aid <= 0) {
continue;
}
if ($visFlip !== null && !isset($visFlip[$aid])) {
continue;
}
$byAdmin[$aid] = ($byAdmin[$aid] ?? 0) + (int) ($r['cnt'] ?? 0);
}
return $byAdmin;
}
/**
* doctor_appointment 别名 a:与 AppointmentLists 已完成挂号 **计数** 对齐(诊单软删、appointment_date、status=3)。
* 不按医助角色或 assistant_id 再收窄(与列表 tab 总数一致)。
@@ -1907,6 +2081,401 @@ class YejiStatsLogic
];
}
/**
* admin → 展示「中心」映射:用于挂号明细按部门筛选(与 aggregateConsults 映射同源)。
*/
private static function buildYejiAdminPrimaryMapUnionSql(array $adminToPrimary): string
{
if ($adminToPrimary === []) {
return 'SELECT 0 AS map_aid, 0 AS map_pid WHERE 0 = 1';
}
$parts = [];
foreach ($adminToPrimary as $aid => $pid) {
$parts[] = 'SELECT ' . (int) $aid . ' AS map_aid,' . (int) $pid . ' AS map_pid';
}
return implode(' UNION ALL ', $parts);
}
/**
* @return list<array<string, mixed>>
*/
private static function formatYejiAppointmentLinePayloadRows(array $rawList, bool $hasChSrc): array
{
$statusMap = [
1 => '已预约',
2 => '已取消',
3 => '已完成',
4 => '已过号',
];
$typeMap = [
'video' => '视频问诊',
'text' => '图文问诊',
'phone' => '电话问诊',
];
$lists = [];
foreach ($rawList as $rw) {
$st = (int) ($rw['status'] ?? 0);
$tp = (string) ($rw['appointment_type'] ?? '');
$lists[] = [
'id' => (int) ($rw['id'] ?? 0),
'appointment_date' => (string) ($rw['appointment_date'] ?? ''),
'appointment_time' => (string) ($rw['appointment_time'] ?? ''),
'status' => $st,
'status_desc' => $statusMap[$st] ?? '未知',
'appointment_type' => $tp,
'appointment_type_desc' => $typeMap[$tp] ?? '未知',
'diagnosis_id' => (int) ($rw['patient_id'] ?? 0),
'patient_name' => (string) ($rw['patient_name'] ?? ''),
'patient_phone' => (string) ($rw['patient_phone'] ?? ''),
'doctor_name' => (string) ($rw['doctor_name'] ?? ''),
'channel_source' => $hasChSrc ? (string) ($rw['channel_source'] ?? '') : '',
];
}
return $lists;
}
/**
* 业绩表部门行「预约诊单」挂号明细(与 {@see aggregateBookedAppointmentsByDept} 同口径)。
*
* @param array<string, mixed> $c resolveYejiContext + applyYejiDataScope 后的上下文
*
* @return array<string, mixed>
*/
private static function yejiDeptAppointmentLinesInner(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 [
'start_date' => (string) $c['startDate'],
'end_date' => (string) $c['endDate'],
'assistant_id' => 0,
'assistant_name' => '',
'dept_id' => $deptIdParam,
'dept_name' => $deptName,
'channel_code' => $channelCode,
'count' => 0,
'lists' => [],
'note' => $note,
];
};
if ($deptIdParam < 0) {
return $empty('请指定有效部门。');
}
if ($deptIdParam > 0 && !in_array($deptIdParam, $c['tableRowDeptIds'], true)) {
return $empty('该部门不在当前展示部门筛选或数据权限范围内。');
}
$tagDiagIds = $c['tagDiagIds'];
$tagAssistantIds = $c['tagAssistantIds'];
if ($tagDiagIds !== null && $tagDiagIds === []) {
return $empty('当前渠道标签下无关联诊单,无挂号明细。');
}
if ($tagAssistantIds !== null && $tagAssistantIds === []) {
return $empty('当前渠道标签下无关联医助,无挂号明细。');
}
$startTs = (int) $c['startTs'];
$endTs = (int) $c['endTs'];
$startDate = date('Y-m-d', $startTs);
$endDate = date('Y-m-d', $endTs);
$eff = self::consultEffectiveAssistantSql();
$page = max(1, (int) ($params['page'] ?? 1));
$limit = min(100, max(1, (int) ($params['page_size'] ?? 20)));
$offset = ($page - 1) * $limit;
$hasChSrc = self::doctorAppointmentHasChannelSourceColumn();
$mapWrapped = '(' . self::buildYejiAdminPrimaryMapUnionSql($c['adminToPrimary']) . ')';
$dataScopeRestricted = !empty($c['dataScopeRestricted']);
$dataScopeAdminIds = $dataScopeRestricted && isset($c['dataScopeVisibleAdminIds'])
? $c['dataScopeVisibleAdminIds']
: null;
$buildBaseQuery = static function () use (
$eff,
$startDate,
$endDate,
$tagDiagIds,
$tagAssistantIds,
$mapWrapped,
$deptIdParam,
$dataScopeAdminIds
) {
$q = Db::name('doctor_appointment')->alias('a')
->leftJoin('tcm_diagnosis u', 'a.patient_id = u.id')
->leftJoin('admin doc', 'a.doctor_id = doc.id AND doc.delete_time IS NULL')
->whereBetween('a.appointment_date', [$startDate, $endDate])
->whereIn('a.status', [1, 3, 4])
->whereRaw('(u.id IS NULL OR u.delete_time IS NULL)');
if ($tagDiagIds !== null) {
$q->whereIn('a.patient_id', $tagDiagIds);
}
if ($tagAssistantIds !== null) {
$ids = array_map('intval', array_keys($tagAssistantIds));
if ($ids !== []) {
$q->whereRaw("({$eff}) IN (" . implode(',', $ids) . ')');
}
}
$primaryPred = 'COALESCE('
. "IF(({$eff}) > 0, (SELECT m.map_pid FROM {$mapWrapped} m WHERE m.map_aid = ({$eff}) LIMIT 1), NULL),"
. " IF(a.doctor_id > 0, (SELECT m2.map_pid FROM {$mapWrapped} m2 WHERE m2.map_aid = a.doctor_id LIMIT 1), NULL),"
. ' 0)';
$q->whereRaw("{$primaryPred} = ?", [$deptIdParam]);
if ($dataScopeAdminIds !== null && $dataScopeAdminIds !== []) {
$visIn = implode(',', array_map('intval', $dataScopeAdminIds));
$q->whereRaw(
"(({$eff}) > 0 AND ({$eff}) IN ({$visIn})) OR (({$eff}) <= 0 AND a.doctor_id > 0 AND a.doctor_id IN ({$visIn}))"
);
}
return $q;
};
$count = (int) $buildBaseQuery()->count();
$fieldParts = [
'a.id',
'a.appointment_date',
'a.appointment_time',
'a.status',
'a.appointment_type',
'a.patient_id',
'u.patient_name',
'u.phone AS patient_phone',
'doc.name AS doctor_name',
];
if ($hasChSrc) {
$fieldParts[] = 'a.channel_source';
}
$rawList = $buildBaseQuery()
->field(implode(',', $fieldParts))
->order('a.appointment_date', 'asc')
->order('a.appointment_time', 'asc')
->order('a.id', 'asc')
->limit($offset, $limit)
->select()
->toArray();
$lists = self::formatYejiAppointmentLinePayloadRows($rawList, $hasChSrc);
$note = '与业绩表「预约诊单」列同口径:预约日期在统计区间内,状态为已预约 / 已完成 / 已过号(不含已取消);部门归属医助优先,否则接诊医生。';
return [
'start_date' => (string) $c['startDate'],
'end_date' => (string) $c['endDate'],
'assistant_id' => 0,
'assistant_name' => '',
'dept_id' => $deptIdParam,
'dept_name' => $deptName,
'channel_code' => $channelCode,
'count' => $count,
'lists' => $lists,
'note' => $note,
];
}
/**
* 医助排行榜「预约诊单」逐条挂号明细(与 {@see countBookedAppointmentsByAdminForLeaderboard} 同口径)。
*
* @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{
* start_date: string,
* end_date: string,
* assistant_id: int,
* assistant_name: string,
* dept_id: int,
* dept_name: string,
* channel_code: string,
* count: int,
* lists: list<array<string, mixed>>,
* note: string
* }
*/
public static function leaderboardAppointmentLines(
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::yejiDeptAppointmentLinesInner($params, $c);
}
$channelCode = (string) $c['channelCode'];
$assistantName = '';
if ($assistantId > 0) {
$assistantName = (string) (Db::name('admin')->where('id', $assistantId)->whereNull('delete_time')->value('name') ?: '');
}
$baseOut = static function (array $c, string $note) use ($assistantId, $assistantName, $channelCode): array {
return [
'start_date' => (string) $c['startDate'],
'end_date' => (string) $c['endDate'],
'assistant_id' => $assistantId,
'assistant_name' => $assistantName,
'dept_id' => 0,
'dept_name' => '',
'channel_code' => $channelCode,
'count' => 0,
'lists' => [],
'note' => $note,
];
};
if ($assistantId <= 0) {
return $baseOut($c, '请指定有效医助,或传入 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 $baseOut($c, '当前账号数据权限下不可查看该医助挂号明细。');
}
}
if (!isset($c['adminToPrimary'][$assistantId])) {
return $baseOut($c, '该医助不在当前业绩看板展示范围内。');
}
$tagDiagIds = $c['tagDiagIds'];
$tagAssistantIds = $c['tagAssistantIds'];
$tagFallback = $c['tagFallback'];
$scopedAssistants = $tagFallback ? $tagAssistantIds : null;
if ($tagDiagIds !== null && $tagDiagIds === []) {
return $baseOut($c, '当前渠道标签下无关联诊单,无挂号明细。');
}
if ($scopedAssistants !== null && $scopedAssistants === []) {
return $baseOut($c, '当前渠道标签下无关联医助,无挂号明细。');
}
if ($scopedAssistants !== null && !isset($scopedAssistants[$assistantId])) {
return $baseOut($c, '当前渠道标签口径下该医助无归属挂号(与排行榜「预约诊单」一致)。');
}
$startTs = (int) $c['startTs'];
$endTs = (int) $c['endTs'];
$startDate = date('Y-m-d', $startTs);
$endDate = date('Y-m-d', $endTs);
$eff = self::consultEffectiveAssistantSql();
$page = max(1, (int) ($params['page'] ?? 1));
$limit = min(100, max(1, (int) ($params['page_size'] ?? 20)));
$offset = ($page - 1) * $limit;
$hasChSrc = self::doctorAppointmentHasChannelSourceColumn();
$buildBaseQuery = static function () use (
$eff,
$assistantId,
$startDate,
$endDate,
$tagDiagIds,
$scopedAssistants
) {
$q = Db::name('doctor_appointment')->alias('a')
->leftJoin('tcm_diagnosis u', 'a.patient_id = u.id')
->leftJoin('admin doc', 'a.doctor_id = doc.id AND doc.delete_time IS NULL')
->whereBetween('a.appointment_date', [$startDate, $endDate])
->whereIn('a.status', [1, 3, 4])
->whereRaw('(u.id IS NULL OR u.delete_time IS NULL)')
->whereRaw("({$eff}) = ?", [$assistantId]);
if ($tagDiagIds !== null) {
$q->whereIn('a.patient_id', $tagDiagIds);
}
if ($scopedAssistants !== null) {
$ids = array_map('intval', array_keys($scopedAssistants));
if ($ids !== []) {
$q->whereRaw("({$eff}) IN (" . implode(',', $ids) . ')');
}
}
return $q;
};
$count = (int) $buildBaseQuery()->count();
$fieldParts = [
'a.id',
'a.appointment_date',
'a.appointment_time',
'a.status',
'a.appointment_type',
'a.patient_id',
'u.patient_name',
'u.phone AS patient_phone',
'doc.name AS doctor_name',
];
if ($hasChSrc) {
$fieldParts[] = 'a.channel_source';
}
$rawList = $buildBaseQuery()
->field(implode(',', $fieldParts))
->order('a.appointment_date', 'asc')
->order('a.appointment_time', 'asc')
->order('a.id', 'asc')
->limit($offset, $limit)
->select()
->toArray();
$lists = self::formatYejiAppointmentLinePayloadRows($rawList, $hasChSrc);
$note = '与排行榜「预约诊单」同口径:预约日期在统计区间内,状态为已预约 / 已完成 / 已过号(不含已取消);'
. '有效医助优先取挂号表创建医助,否则取诊单医助。';
return [
'start_date' => (string) $c['startDate'],
'end_date' => (string) $c['endDate'],
'assistant_id' => $assistantId,
'assistant_name' => $assistantName,
'dept_id' => 0,
'dept_name' => '',
'channel_code' => $channelCode,
'count' => $count,
'lists' => $lists,
'note' => $note,
];
}
/**
* 业绩看板:二中心复诊按部门行下钻 —— 医助姓名与业务订单笔数(与表格「复诊」列同口径)。
*
@@ -545,6 +545,10 @@ class DiagnosisLogic extends BaseLogic
if (!empty($r['to_assistant_id'])) {
$adminIds[] = (int) $r['to_assistant_id'];
}
$rcp = (int) ($r['related_po_creator_id'] ?? 0);
if ($rcp > 0) {
$adminIds[] = $rcp;
}
}
$adminIds = array_values(array_unique(array_filter($adminIds)));
$nameMap = [];
@@ -564,6 +568,14 @@ class DiagnosisLogic extends BaseLogic
$r['create_time_text'] = !empty($r['create_time'])
? date('Y-m-d H:i:s', (int) $r['create_time'])
: '';
$rcpId = (int) ($r['related_po_creator_id'] ?? 0);
$r['related_po_creator_name'] = $rcpId > 0
? (string) ($nameMap[$rcpId] ?? ('ID:' . $rcpId))
: '';
$rpct = (int) ($r['related_po_create_time'] ?? 0);
$r['related_po_create_time_text'] = $rpct > 0 ? date('Y-m-d H:i:s', $rpct) : '';
$r['related_po_is_cleared_assistant_creator'] =
($rcpId > 0 && $fromId > 0 && $rcpId === $fromId) ? 1 : 0;
}
unset($r);