更新
This commit is contained in:
@@ -18,6 +18,10 @@ class PerformanceDashboardController extends BaseAdminController
|
||||
{
|
||||
@set_time_limit(120);
|
||||
|
||||
return $this->data(PerformanceDashboardLogic::overview($this->adminId, $this->adminInfo));
|
||||
return $this->data(PerformanceDashboardLogic::overview(
|
||||
$this->adminId,
|
||||
$this->adminInfo,
|
||||
$this->request->get()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,18 +89,17 @@ class MyPatientLists extends BaseAdminDataLists implements ListsSearchInterface,
|
||||
$this->applyKeyword($query);
|
||||
|
||||
$statusFilter = $applyStatusFilter ? trim((string) ($this->params['status_filter'] ?? '')) : '';
|
||||
if ($statusFilter === 'unconfirmed') {
|
||||
$viewTable = (new DiagnosisViewRecord())->getTable();
|
||||
$appointmentTable = (new Appointment())->getTable();
|
||||
if ($statusFilter === 'unbooked') {
|
||||
$query->whereNotExists(
|
||||
"SELECT 1 FROM {$viewTable} confirm_row"
|
||||
. ' WHERE confirm_row.diagnosis_id = d.id'
|
||||
. ' AND confirm_row.is_confirmed = 1'
|
||||
. ' AND confirm_row.delete_time IS NULL'
|
||||
"SELECT 1 FROM {$appointmentTable} unbooked_apt"
|
||||
. ' WHERE unbooked_apt.patient_id = d.id'
|
||||
. ' AND unbooked_apt.status IN (' . implode(',', self::EFFECTIVE_APPOINTMENT_STATUSES) . ')'
|
||||
);
|
||||
}
|
||||
|
||||
$appointmentStatuses = self::EFFECTIVE_APPOINTMENT_STATUSES;
|
||||
if ($statusFilter === 'booked') {
|
||||
if (in_array($statusFilter, ['pending_interview', 'booked'], true)) {
|
||||
$appointmentStatuses = [1];
|
||||
} elseif ($statusFilter === 'completed') {
|
||||
$appointmentStatuses = [3];
|
||||
@@ -108,14 +107,17 @@ class MyPatientLists extends BaseAdminDataLists implements ListsSearchInterface,
|
||||
$appointmentStatuses = [4];
|
||||
}
|
||||
|
||||
$needsAppointmentFilter = in_array($statusFilter, ['booked', 'completed', 'missed'], true);
|
||||
$needsAppointmentFilter = in_array(
|
||||
$statusFilter,
|
||||
['pending_interview', 'booked', 'completed', 'missed'],
|
||||
true
|
||||
);
|
||||
[$startDate, $endDate] = $applyDateFilter ? $this->dateRange() : ['', ''];
|
||||
if ($startDate !== '' || $endDate !== '') {
|
||||
$needsAppointmentFilter = true;
|
||||
}
|
||||
|
||||
if ($needsAppointmentFilter) {
|
||||
$appointmentTable = (new Appointment())->getTable();
|
||||
$conditions = [
|
||||
'filter_apt.patient_id = d.id',
|
||||
'filter_apt.status IN (' . implode(',', $appointmentStatuses) . ')',
|
||||
@@ -245,6 +247,7 @@ class MyPatientLists extends BaseAdminDataLists implements ListsSearchInterface,
|
||||
$today = date('Y-m-d');
|
||||
$statusFilter = trim((string) ($this->params['status_filter'] ?? ''));
|
||||
$preferredStatuses = [
|
||||
'pending_interview' => [1],
|
||||
'booked' => [1],
|
||||
'completed' => [3],
|
||||
'missed' => [4],
|
||||
@@ -364,6 +367,6 @@ class MyPatientLists extends BaseAdminDataLists implements ListsSearchInterface,
|
||||
|
||||
private function appointmentStatusText(int $status): string
|
||||
{
|
||||
return [1 => '已挂号', 3 => '已完成', 4 => '已过号'][$status] ?? '未挂号';
|
||||
return [1 => '待面诊', 3 => '已完成', 4 => '已过号'][$status] ?? '未预约';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,9 +65,18 @@ class MyPatientOrderLists extends BaseAdminDataLists implements ListsSearchInter
|
||||
$effectiveAmountQuery = clone $query;
|
||||
YejiStatsLogic::applyPrescriptionOrderEffectiveAmountQuery($effectiveAmountQuery, 'po');
|
||||
|
||||
// 拒收指标保留关键词、审核和日期条件,但不受当前履约状态按钮影响,
|
||||
// 避免点击“拒收订单”后分母被收窄为拒收状态而固定显示 100%。
|
||||
$rejectionScopeQuery = $this->buildQuery(true);
|
||||
$rejectionScopeOrderCount = (int) (clone $rejectionScopeQuery)->count('po.id');
|
||||
$rejectedCount = (int) (clone $rejectionScopeQuery)
|
||||
->where('po.fulfillment_status', 9)
|
||||
->count('po.id');
|
||||
$orderCount = (int) (clone $query)->count('po.id');
|
||||
|
||||
return [
|
||||
'summary' => [
|
||||
'orders' => (int) (clone $query)->count('po.id'),
|
||||
'orders' => $orderCount,
|
||||
'amount' => round((float) $effectiveAmountQuery->sum('po.amount'), 2),
|
||||
'pending' => (int) $pendingQuery
|
||||
->where(function ($q) {
|
||||
@@ -76,12 +85,16 @@ class MyPatientOrderLists extends BaseAdminDataLists implements ListsSearchInter
|
||||
})
|
||||
->count('po.id'),
|
||||
'completed' => (int) (clone $query)->whereIn('po.fulfillment_status', [3, 6])->count('po.id'),
|
||||
'rejected' => $rejectedCount,
|
||||
'rejection_rate' => $rejectionScopeOrderCount > 0
|
||||
? round($rejectedCount / $rejectionScopeOrderCount * 100, 2)
|
||||
: 0.0,
|
||||
],
|
||||
'scope' => MyPatientLogic::scopeMeta($this->adminId, $this->adminInfo),
|
||||
];
|
||||
}
|
||||
|
||||
private function buildQuery(): Query
|
||||
private function buildQuery(bool $ignoreFulfillmentStatus = false): Query
|
||||
{
|
||||
$query = PrescriptionOrder::alias('po')
|
||||
->join('tcm_diagnosis d', 'po.diagnosis_id = d.id')
|
||||
@@ -91,7 +104,7 @@ class MyPatientOrderLists extends BaseAdminDataLists implements ListsSearchInter
|
||||
|
||||
MyPatientLogic::applyScope($query, $this->adminId, $this->adminInfo);
|
||||
$this->applyKeyword($query);
|
||||
$this->applyStatusFilters($query);
|
||||
$this->applyStatusFilters($query, $ignoreFulfillmentStatus);
|
||||
$this->applyDateFilter($query);
|
||||
|
||||
return $query;
|
||||
@@ -122,9 +135,12 @@ class MyPatientOrderLists extends BaseAdminDataLists implements ListsSearchInter
|
||||
});
|
||||
}
|
||||
|
||||
private function applyStatusFilters(Query $query): void
|
||||
private function applyStatusFilters(Query $query, bool $ignoreFulfillmentStatus = false): void
|
||||
{
|
||||
foreach (['prescription_audit_status', 'payment_slip_audit_status', 'fulfillment_status'] as $field) {
|
||||
if ($ignoreFulfillmentStatus && $field === 'fulfillment_status') {
|
||||
continue;
|
||||
}
|
||||
$raw = $this->params[$field] ?? '';
|
||||
if ($raw === '' || $raw === null) {
|
||||
continue;
|
||||
|
||||
@@ -11,6 +11,7 @@ use app\common\model\auth\Admin;
|
||||
use app\common\model\auth\AdminDept;
|
||||
use app\common\model\stats\PersonalYeji;
|
||||
use app\common\service\DataScope\DataScopeService;
|
||||
use app\common\service\qywx\MediaChannelService;
|
||||
use think\facade\Db;
|
||||
|
||||
/**
|
||||
@@ -32,6 +33,12 @@ class FirstVisitConversionLogic
|
||||
$scopeValue = DataScopeService::getEffectiveScope($adminInfo);
|
||||
$selectedDeptId = max(0, (int) ($params['dept_id'] ?? 0));
|
||||
$selectedAssistantId = max(0, (int) ($params['assistant_id'] ?? 0));
|
||||
$selectedMediaChannelCode = MediaChannelService::normalizeStatsCode(
|
||||
trim((string) ($params['media_channel_code'] ?? ''))
|
||||
);
|
||||
$selectedMediaChannel = $selectedMediaChannelCode !== ''
|
||||
? MediaChannelService::getChannelByCode($selectedMediaChannelCode)
|
||||
: null;
|
||||
|
||||
$deptSelectionValid = $selectedDeptId <= 0
|
||||
|| $allowedDeptSet === null
|
||||
@@ -73,7 +80,7 @@ class FirstVisitConversionLogic
|
||||
'time_type' => 'custom',
|
||||
'start_date' => $startDate,
|
||||
'end_date' => $endDate,
|
||||
'include_filters' => 0,
|
||||
'include_filters' => 1,
|
||||
'include_members' => 0,
|
||||
'exclude_cancelled_appointments' => 1,
|
||||
'order_metric_mode' => 'performance',
|
||||
@@ -83,6 +90,9 @@ class FirstVisitConversionLogic
|
||||
if ($selectedDeptId > 0 && $deptSelectionValid) {
|
||||
$conversionParams['dept_id'] = $selectedDeptId;
|
||||
}
|
||||
if ($selectedMediaChannelCode !== '') {
|
||||
$conversionParams['media_channel_code'] = $selectedMediaChannelCode;
|
||||
}
|
||||
|
||||
$conversion = ConversionLogic::overview(
|
||||
$conversionParams,
|
||||
@@ -103,12 +113,21 @@ class FirstVisitConversionLogic
|
||||
$startDate,
|
||||
$endDate,
|
||||
$effectiveAdminIds,
|
||||
array_fill_keys(array_keys($rowDeptIdSet), true)
|
||||
array_fill_keys(array_keys($rowDeptIdSet), true),
|
||||
self::personalYejiMediaSources($selectedMediaChannelCode, $selectedMediaChannel)
|
||||
);
|
||||
self::applyOpenCounts($rows, $openDirect);
|
||||
|
||||
$summary = is_array($conversion['summary'] ?? null) ? $conversion['summary'] : [];
|
||||
$summary['total_open_count'] = array_sum($openDirect);
|
||||
$summary['total_open_rate'] = self::percent(
|
||||
(int) $summary['total_open_count'],
|
||||
(int) ($summary['add_fans_count'] ?? 0)
|
||||
);
|
||||
$summary['open_appointment_rate'] = self::percent(
|
||||
(int) ($summary['paid_appointment_count'] ?? 0),
|
||||
(int) $summary['total_open_count']
|
||||
);
|
||||
$summary['open_receive_rate'] = self::percent(
|
||||
(int) ($summary['completed_order_count'] ?? 0),
|
||||
(int) $summary['total_open_count']
|
||||
@@ -127,6 +146,12 @@ class FirstVisitConversionLogic
|
||||
$selectedAssistantName = $selectedAssistantId > 0
|
||||
? (string) (Admin::where('id', $selectedAssistantId)->whereNull('delete_time')->value('name') ?? '')
|
||||
: '';
|
||||
$selectedMediaChannelName = $selectedMediaChannelCode !== ''
|
||||
? (string) ($selectedMediaChannel['channel_name'] ?? $selectedMediaChannelCode)
|
||||
: '';
|
||||
$conversionFilters = is_array($conversion['extend']['filters'] ?? null)
|
||||
? $conversion['extend']['filters']
|
||||
: [];
|
||||
|
||||
return [
|
||||
'meta' => [
|
||||
@@ -139,13 +164,20 @@ class FirstVisitConversionLogic
|
||||
'scope_label' => DataScopeService::scopeLabel($scopeValue),
|
||||
'selected_dept_name' => $selectedDeptName,
|
||||
'selected_assistant_name' => $selectedAssistantName,
|
||||
'open_count_source' => '个人业绩录入',
|
||||
'selected_media_channel_code' => $selectedMediaChannelCode,
|
||||
'selected_media_channel_name' => $selectedMediaChannelName,
|
||||
'open_count_source' => $selectedMediaChannelCode === ''
|
||||
? '个人业绩录入'
|
||||
: '个人业绩录入(按渠道名称匹配)',
|
||||
'appointment_rule' => '按预约日期统计,归属优先挂号医助、再回退诊单医助;仅含已预约、已完成和已过号',
|
||||
'performance_rule' => '按业务订单创建时间和创建人统计,排除取消、拒收、退款及已发生退款的订单',
|
||||
],
|
||||
'filters' => [
|
||||
'departments' => DeptLogic::getAllDataScoped($adminId, $adminInfo),
|
||||
'assistants' => self::assistantOptions($baseVisibleAdminIds, $selectedDeptIds, $selectedDeptId),
|
||||
'media_channels' => is_array($conversionFilters['media_channels'] ?? null)
|
||||
? $conversionFilters['media_channels']
|
||||
: [],
|
||||
],
|
||||
'summary' => $summary,
|
||||
'rankings' => [
|
||||
@@ -292,16 +324,30 @@ class FirstVisitConversionLogic
|
||||
}
|
||||
}
|
||||
|
||||
/** @param int[]|null $effectiveAdminIds @param array<int,true> $rowDeptSet @return array<int,int> */
|
||||
private static function loadOpenCountByDept(string $startDate, string $endDate, ?array $effectiveAdminIds, array $rowDeptSet): array
|
||||
/**
|
||||
* @param int[]|null $effectiveAdminIds
|
||||
* @param array<int,true> $rowDeptSet
|
||||
* @param string[]|null $mediaSources null=全部渠道;空数组=所选渠道没有可匹配的手工来源
|
||||
* @return array<int,int>
|
||||
*/
|
||||
private static function loadOpenCountByDept(
|
||||
string $startDate,
|
||||
string $endDate,
|
||||
?array $effectiveAdminIds,
|
||||
array $rowDeptSet,
|
||||
?array $mediaSources = null
|
||||
): array
|
||||
{
|
||||
if ($effectiveAdminIds === [] || $rowDeptSet === []) {
|
||||
if ($effectiveAdminIds === [] || $rowDeptSet === [] || $mediaSources === []) {
|
||||
return [];
|
||||
}
|
||||
$query = PersonalYeji::whereBetween('yeji_date', [$startDate, $endDate]);
|
||||
if ($effectiveAdminIds !== null) {
|
||||
$query->whereIn('creator_id', $effectiveAdminIds);
|
||||
}
|
||||
if ($mediaSources !== null) {
|
||||
$query->whereIn('media_source', $mediaSources);
|
||||
}
|
||||
$rows = $query
|
||||
->fieldRaw('creator_id, SUM(total_open_count) AS open_count')
|
||||
->group('creator_id')
|
||||
@@ -401,6 +447,11 @@ class FirstVisitConversionLogic
|
||||
}
|
||||
$count = (int) ($direct[(int) ($row['id'] ?? 0)] ?? 0) + $childTotal;
|
||||
$row['total_open_count'] = $count;
|
||||
$row['total_open_rate'] = self::percent($count, (int) ($row['add_fans_count'] ?? 0));
|
||||
$row['open_appointment_rate'] = self::percent(
|
||||
(int) ($row['paid_appointment_count'] ?? 0),
|
||||
$count
|
||||
);
|
||||
$row['open_receive_rate'] = self::percent((int) ($row['completed_order_count'] ?? 0), $count);
|
||||
$sum += (int) ($direct[(int) ($row['id'] ?? 0)] ?? 0) + $childTotal;
|
||||
}
|
||||
@@ -409,6 +460,32 @@ class FirstVisitConversionLogic
|
||||
return $sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 手工开口按 personal_yeji.media_source 保存;渠道筛选时仅匹配该渠道自身的稳定标识和名称。
|
||||
* 不使用 source_group_name,避免同组多个渠道的开口数被重复计入每个渠道。
|
||||
*
|
||||
* @param array<string,mixed>|null $channel
|
||||
* @return string[]|null
|
||||
*/
|
||||
private static function personalYejiMediaSources(string $channelCode, ?array $channel): ?array
|
||||
{
|
||||
if ($channelCode === '') {
|
||||
return null;
|
||||
}
|
||||
if ($channel === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values(array_unique(array_filter(array_map(
|
||||
static fn ($value): string => trim((string) $value),
|
||||
[
|
||||
$channelCode,
|
||||
$channel['channel_name'] ?? '',
|
||||
$channel['source_tag_name'] ?? '',
|
||||
]
|
||||
), static fn (string $value): bool => $value !== '')));
|
||||
}
|
||||
|
||||
/** @param array<int,array<string,mixed>> $rows @return array<int,array<string,mixed>> */
|
||||
private static function rankingRows(array $rows): array
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@ class PerformanceDashboardLogic
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function overview(int $adminId, array $adminInfo): array
|
||||
public static function overview(int $adminId, array $adminInfo, array $params = []): array
|
||||
{
|
||||
$today = date('Y-m-d');
|
||||
$yesterday = date('Y-m-d', strtotime('-1 day'));
|
||||
@@ -49,9 +49,15 @@ class PerformanceDashboardLogic
|
||||
/** @var array<int>|null $visibleAdminIds */
|
||||
$visibleAdminIds = $scope['_visible_admin_ids'];
|
||||
unset($scope['_visible_admin_ids']);
|
||||
$rankingDeptId = self::resolveRankingDeptId(
|
||||
max(0, (int) ($params['ranking_dept_id'] ?? 0)),
|
||||
$adminId,
|
||||
$adminInfo
|
||||
);
|
||||
|
||||
$orderDaily = self::loadPerformanceOrderDaily($previousMonthStart, $today, $visibleAdminIds);
|
||||
$personalOrderDaily = self::loadPerformanceOrderDaily($monthStart, $today, [$adminId]);
|
||||
$lowAmountPaymentDaily = self::loadLowAmountPaymentDaily($yesterday, $today, $visibleAdminIds);
|
||||
|
||||
$monthAmount = self::sumDailyMetric($orderDaily, $monthStart, $today, 'amount');
|
||||
$previousMonthAmount = self::sumDailyMetric(
|
||||
@@ -92,7 +98,13 @@ class PerformanceDashboardLogic
|
||||
'start_date' => $today,
|
||||
'end_date' => $today,
|
||||
], $adminId, $adminInfo);
|
||||
$appointmentRanking = self::buildAppointmentRanking($adminId, $adminInfo, $scope);
|
||||
$appointmentRanking = self::buildAppointmentRanking(
|
||||
$adminId,
|
||||
$adminInfo,
|
||||
$scope,
|
||||
$visibleAdminIds,
|
||||
$rankingDeptId
|
||||
);
|
||||
$performanceRanking = self::buildPerformanceRanking(
|
||||
is_array($todayPerformanceOverview['rows'] ?? null) ? $todayPerformanceOverview['rows'] : []
|
||||
);
|
||||
@@ -114,6 +126,8 @@ class PerformanceDashboardLogic
|
||||
$yesterdayOrderCount = (int) self::dailyMetric($orderDaily, $yesterday, 'count');
|
||||
$todayOrderAmount = self::dailyMetric($orderDaily, $today, 'amount');
|
||||
$yesterdayOrderAmount = self::dailyMetric($orderDaily, $yesterday, 'amount');
|
||||
$todayLowAmountPaymentCount = (int) self::dailyMetric($lowAmountPaymentDaily, $today, 'count');
|
||||
$yesterdayLowAmountPaymentCount = (int) self::dailyMetric($lowAmountPaymentDaily, $yesterday, 'count');
|
||||
$todayPaidAppointmentCount = self::paidAppointmentCountWithCenterRule($todayOverview);
|
||||
$yesterdayPaidAppointmentCount = self::paidAppointmentCountWithCenterRule($yesterdayOverview);
|
||||
$todayPaidAppointmentRate = self::percent($todayPaidAppointmentCount, $todayAddFansCount);
|
||||
@@ -147,6 +161,7 @@ class PerformanceDashboardLogic
|
||||
'today' => [
|
||||
'add_fans_count' => $todayAddFansCount,
|
||||
'appointment_total_count' => $todayAppointmentCount,
|
||||
'low_amount_payment_count' => $todayLowAmountPaymentCount,
|
||||
'interview_count' => $todayInterviewCount,
|
||||
// 保留原响应字段名以兼容已发布前端,数值含义已统一为“计入业绩的业务订单”。
|
||||
'completed_order_count' => $todayOrderCount,
|
||||
@@ -160,6 +175,10 @@ class PerformanceDashboardLogic
|
||||
$todayAppointmentCount,
|
||||
$yesterdayAppointmentCount
|
||||
),
|
||||
'low_amount_payment_count' => self::buildComparison(
|
||||
$todayLowAmountPaymentCount,
|
||||
$yesterdayLowAmountPaymentCount
|
||||
),
|
||||
'interview_count' => self::buildComparison($todayInterviewCount, $yesterdayInterviewCount),
|
||||
'completed_order_count' => self::buildComparison($todayOrderCount, $yesterdayOrderCount),
|
||||
'completed_order_amount' => self::buildComparison($todayOrderAmount, $yesterdayOrderAmount),
|
||||
@@ -181,13 +200,17 @@ class PerformanceDashboardLogic
|
||||
'items' => $performanceRanking,
|
||||
],
|
||||
],
|
||||
'filters' => [
|
||||
'ranking_departments' => self::rankingDepartmentOptions($adminId, $adminInfo),
|
||||
'ranking_dept_id' => $rankingDeptId,
|
||||
],
|
||||
'trend' => $trend,
|
||||
'target' => $target,
|
||||
'meta' => [
|
||||
'generated_at' => date('Y-m-d H:i:s'),
|
||||
'timezone' => date_default_timezone_get(),
|
||||
'commission_note' => '本人业绩按当前账号创建的业务订单统计,排除已取消、拒收和退款订单。',
|
||||
'rate_note' => '付费挂号率:一中心员工的有效挂号全部按付费挂号,其他部门按 5 元实付挂号;面诊接诊率:有效业务诊单数 / 已完成面诊数。近 7 天挂号排除已取消记录,诊单按订单创建时间统计并排除履约 4/9/10。',
|
||||
'rate_note' => '付费挂号率:一中心员工的有效挂号全部按付费挂号,其他部门按 5 元实付挂号;接诊率:有效业务诊单数 / 已完成面诊数。近 7 天挂号排除已取消记录,诊单按订单创建时间统计并排除履约 4/9/10。',
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -300,6 +323,30 @@ class PerformanceDashboardLogic
|
||||
];
|
||||
}
|
||||
|
||||
private static function resolveRankingDeptId(int $requestedDeptId, int $adminId, array $adminInfo): int
|
||||
{
|
||||
if ($requestedDeptId <= 0) {
|
||||
return 0;
|
||||
}
|
||||
$exists = Dept::where('id', $requestedDeptId)->whereNull('delete_time')->count() > 0;
|
||||
if (!$exists) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$allowedDeptSet = DataScopeService::getAllowedDeptIdSet($adminId, $adminInfo);
|
||||
if ($allowedDeptSet !== null && !isset($allowedDeptSet[$requestedDeptId])) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $requestedDeptId;
|
||||
}
|
||||
|
||||
/** @return array<int, array<string, mixed>> */
|
||||
private static function rankingDepartmentOptions(int $adminId, array $adminInfo): array
|
||||
{
|
||||
return DeptLogic::getAllDataScoped($adminId, $adminInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int>|null $visibleAdminIds
|
||||
* @return array<string, array{amount: float, count: int}>
|
||||
@@ -345,6 +392,53 @@ class PerformanceDashboardLogic
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当日 0 < 实收金额 <= 10 元的已支付订单笔数;退款订单状态为 4,不会进入统计。
|
||||
*
|
||||
* @param array<int>|null $visibleAdminIds
|
||||
* @return array<string, array{count:int}>
|
||||
*/
|
||||
private static function loadLowAmountPaymentDaily(
|
||||
string $startDate,
|
||||
string $endDate,
|
||||
?array $visibleAdminIds
|
||||
): array {
|
||||
if ($visibleAdminIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = Db::name('order')
|
||||
->whereNull('delete_time')
|
||||
->where('status', 2)
|
||||
->where('amount', '>', 0)
|
||||
->where('amount', '<=', 10)
|
||||
->whereNotNull('payment_time')
|
||||
->where('payment_time', '<>', '')
|
||||
->whereBetweenTime(
|
||||
'payment_time',
|
||||
$startDate . ' 00:00:00',
|
||||
$endDate . ' 23:59:59'
|
||||
);
|
||||
if ($visibleAdminIds !== null) {
|
||||
$query->whereIn('creator_id', $visibleAdminIds);
|
||||
}
|
||||
|
||||
$rows = $query
|
||||
->fieldRaw("DATE(payment_time) AS date_label, COUNT(*) AS item_count")
|
||||
->group('date_label')
|
||||
->select()
|
||||
->toArray();
|
||||
$out = [];
|
||||
foreach ($rows as $row) {
|
||||
$date = (string) ($row['date_label'] ?? '');
|
||||
if ($date !== '') {
|
||||
$out[$date] = ['count' => (int) ($row['item_count'] ?? 0)];
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array{amount: float, count: int}> $daily
|
||||
*/
|
||||
@@ -408,7 +502,13 @@ class PerformanceDashboardLogic
|
||||
* @param array<string, mixed> $scope
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function buildAppointmentRanking(int $adminId, array $adminInfo, array $scope): array
|
||||
private static function buildAppointmentRanking(
|
||||
int $adminId,
|
||||
array $adminInfo,
|
||||
array $scope,
|
||||
?array $baseVisibleAdminIds,
|
||||
int $rankingDeptId
|
||||
): array
|
||||
{
|
||||
$roleIds = array_map('intval', $scope['role_ids'] ?? []);
|
||||
$isDoctorSelf = ($scope['key'] ?? '') === 'self'
|
||||
@@ -442,7 +542,7 @@ class PerformanceDashboardLogic
|
||||
];
|
||||
}
|
||||
|
||||
$rankingVisibleAdminIds = null;
|
||||
$rankingVisibleAdminIds = $baseVisibleAdminIds;
|
||||
$rankingScopeLabel = (string) ($scope['label'] ?? '');
|
||||
if (
|
||||
(int) ($scope['scope_value'] ?? DataScopeService::SCOPE_SELF) === DataScopeService::SCOPE_SELF
|
||||
@@ -454,6 +554,13 @@ class PerformanceDashboardLogic
|
||||
$rankingScopeLabel = '本人所属部门';
|
||||
}
|
||||
}
|
||||
if ($rankingDeptId > 0) {
|
||||
$deptAdminIds = self::departmentAdminIds($rankingDeptId);
|
||||
$rankingVisibleAdminIds = $rankingVisibleAdminIds === null
|
||||
? $deptAdminIds
|
||||
: array_values(array_intersect($rankingVisibleAdminIds, $deptAdminIds));
|
||||
$rankingScopeLabel = (string) (Dept::where('id', $rankingDeptId)->value('name') ?? $rankingScopeLabel);
|
||||
}
|
||||
|
||||
$assistantStats = ConversionLogic::overview([
|
||||
'dimension' => 'assistant',
|
||||
@@ -496,6 +603,23 @@ class PerformanceDashboardLogic
|
||||
];
|
||||
}
|
||||
|
||||
/** @return int[] */
|
||||
private static function departmentAdminIds(int $deptId): array
|
||||
{
|
||||
$deptIds = array_values(array_unique(array_filter(
|
||||
array_map('intval', DeptLogic::getSelfAndDescendantIds($deptId)),
|
||||
static fn (int $id): bool => $id > 0
|
||||
)));
|
||||
if ($deptIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values(array_unique(array_filter(
|
||||
array_map('intval', AdminDept::whereIn('dept_id', $deptIds)->column('admin_id')),
|
||||
static fn (int $id): bool => $id > 0
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* SELF 医助排行的卡片级例外:只扩展到当前账号所有有效直接部门内的有效医助。
|
||||
* 不展开子部门,也不改变驾驶舱其它指标的数据范围。
|
||||
|
||||
Reference in New Issue
Block a user