This commit is contained in:
Your Name
2026-05-22 11:15:59 +08:00
parent 49c8c95966
commit 22ec53c070
6 changed files with 331 additions and 64 deletions
@@ -107,7 +107,10 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
$this->applyServiceChannelFilter($query);
$this->applySupplyModeFilter($query);
if (!$this->shouldBypassListVisibilityForDiagnosisEdit()) {
$this->applyCreatorOrOwnPrescriptionVisibility($query);
// 业绩看板按部门点「合计业绩」/复诊下钻:部门或数据域内医助筛选已收口,勿再叠「仅本人订单」
if (!$this->shouldSkipCreatorOnlyForYejiDrawer()) {
$this->applyCreatorOrOwnPrescriptionVisibility($query);
}
$this->applyDataScopeForPrescriptionOrder($query);
}
// 业绩看板侧栏等:不展示履约 4/9/10,与 stats 业绩口径一致
@@ -323,6 +326,50 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
return $diagnosisPatientId > 0 && $diagnosisPatientId === $patientId;
}
/**
* 业绩看板侧栏:按部门行点合计业绩/接诊诊单(assistant_dept_id),组长等应看到组内全员订单。
*/
private function isYejiDrawerDeptPerformanceScope(): bool
{
if ((int) ($this->params['yeji_order_drawer'] ?? 0) !== 1) {
return false;
}
if ((int) ($this->params['yeji_drawer_match_table_performance'] ?? 0) === 1) {
return true;
}
return isset($this->params['assistant_dept_id']) && (int) $this->params['assistant_dept_id'] > 0;
}
/**
* 业绩看板侧栏:部门业绩或复诊下钻查看数据域内其他医助订单时,跳过「仅本人可见」。
*/
private function shouldSkipCreatorOnlyForYejiDrawer(): bool
{
if ($this->isYejiDrawerDeptPerformanceScope()) {
return true;
}
if ((int) ($this->params['yeji_order_drawer'] ?? 0) !== 1) {
return false;
}
if ((int) ($this->params['yeji_er_center_revisit_only'] ?? 0) !== 1) {
return false;
}
$assistantId = (int) ($this->params['assistant_id'] ?? 0);
if ($assistantId <= 0 || $assistantId === (int) $this->adminId) {
return false;
}
if (!$this->dataScopeShouldApply()) {
return true;
}
$visibleIds = $this->getDataScopeVisibleAdminIds();
if ($visibleIds === null) {
return true;
}
return in_array($assistantId, $visibleIds, true);
}
/**
* 医助角色统计:仅「订单创建人=本人」计入
*/
@@ -341,7 +388,9 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
$query = PrescriptionOrder::where($this->searchWhereWithoutCreateTimeRange())->whereNull('delete_time');
$this->applyDoctorAssistantFilters($query);
$this->applyPatientKeywordFilter($query);
if (PrescriptionOrderLogic::canViewOrderListStatsAllScope($this->adminInfo)) {
if ($this->shouldSkipCreatorOnlyForYejiDrawer()) {
$this->applyDataScopeForPrescriptionOrder($query);
} elseif (PrescriptionOrderLogic::canViewOrderListStatsAllScope($this->adminInfo)) {
$this->applyDataScopeForPrescriptionOrder($query);
} else {
$assistantRid = (int) Config::get('project.prescription_order_stats_assistant_role_id', 2);
+37 -8
View File
@@ -243,7 +243,8 @@ class DeptLogic extends BaseLogic
private static function computeErCenterRevisitFullPack(
array $subtreeDeptIds,
?int $orderStartTs = null,
?int $orderEndTs = null
?int $orderEndTs = null,
?array $visibleCreatorIds = null
): array {
$emptySlots = [];
$emptyTotals = [];
@@ -284,6 +285,14 @@ class DeptLogic extends BaseLogic
/** 复用 buildAssistantCanonicalDeptInSubtree —— 该方法实际是 admin → 子树内 canonical 部门,与 admin 角色无关 */
$canonicalDept = self::buildAssistantCanonicalDeptInSubtree($creatorIds, $subtreeSet);
$creatorFlip = null;
if ($visibleCreatorIds !== null && $visibleCreatorIds !== []) {
$creatorFlip = array_flip(array_values(array_unique(array_filter(
array_map('intval', $visibleCreatorIds),
static fn (int $id): bool => $id > 0
))));
}
/** @var array<int, array<int, int>> $leafByDeptSlot dept_id => [ slot => cnt ]slot≥2 */
$leafByDeptSlot = [];
/** @var array<int, array<int, int>> $byCreatorSlot 复诊按订单创建人聚合(与诊金/接诊诊单口径一致) */
@@ -293,6 +302,9 @@ class DeptLogic extends BaseLogic
for ($i = 1; $i < $n; $i++) {
$slot = $i + 1;
$cid = (int) ($orders[$i]['creator_id'] ?? 0);
if ($creatorFlip !== null && ($cid <= 0 || !isset($creatorFlip[$cid]))) {
continue;
}
$deptId = $canonicalDept[$cid] ?? null;
if ($deptId === null || !isset($subtreeSet[$deptId])) {
continue;
@@ -337,14 +349,17 @@ class DeptLogic extends BaseLogic
*
* @return array{totals: array<int, int>, slots: array<int, array<int, int>>}
*/
public static function getErCenterRevisitRollupIndexedByDept(?int $orderStartTs = null, ?int $orderEndTs = null): array
{
public static function getErCenterRevisitRollupIndexedByDept(
?int $orderStartTs = null,
?int $orderEndTs = null,
?array $visibleCreatorIds = null
): array {
$erRoots = self::findErCenterRootDeptIds();
$subtreeIds = self::unionErCenterSubtreeDeptIds($erRoots);
if ($subtreeIds === []) {
return ['totals' => [], 'slots' => []];
}
$pack = self::computeErCenterRevisitFullPack($subtreeIds, $orderStartTs, $orderEndTs);
$pack = self::computeErCenterRevisitFullPack($subtreeIds, $orderStartTs, $orderEndTs, $visibleCreatorIds);
return ['totals' => $pack['totals'], 'slots' => $pack['slots']];
}
@@ -354,14 +369,17 @@ class DeptLogic extends BaseLogic
*
* @return array{totals: array<int, int>, slots: array<int, array<int, int>>}
*/
public static function getErCenterRevisitCountsByCreator(?int $orderStartTs = null, ?int $orderEndTs = null): array
{
public static function getErCenterRevisitCountsByCreator(
?int $orderStartTs = null,
?int $orderEndTs = null,
?array $visibleCreatorIds = null
): array {
$erRoots = self::findErCenterRootDeptIds();
$subtreeIds = self::unionErCenterSubtreeDeptIds($erRoots);
if ($subtreeIds === []) {
return ['totals' => [], 'slots' => []];
}
$pack = self::computeErCenterRevisitFullPack($subtreeIds, $orderStartTs, $orderEndTs);
$pack = self::computeErCenterRevisitFullPack($subtreeIds, $orderStartTs, $orderEndTs, $visibleCreatorIds);
$by = $pack['by_creator_slots'];
$totals = [];
$slots = [];
@@ -436,7 +454,8 @@ class DeptLogic extends BaseLogic
int $rootDeptId,
int $revisitSlot,
int $orderStartTs,
int $orderEndTs
int $orderEndTs,
?array $visibleCreatorIds = null
): array {
if ($rootDeptId <= 0 || $orderStartTs <= 0 || $orderEndTs < $orderStartTs) {
return ['rows' => []];
@@ -479,6 +498,13 @@ class DeptLogic extends BaseLogic
}
}
$canonicalDept = self::buildAssistantCanonicalDeptInSubtree(array_keys($creatorNeed), $erSubtreeSet);
$creatorFlip = null;
if ($visibleCreatorIds !== null && $visibleCreatorIds !== []) {
$creatorFlip = array_flip(array_values(array_unique(array_filter(
array_map('intval', $visibleCreatorIds),
static fn (int $id): bool => $id > 0
))));
}
/** @var array<int, int> $counts */
$counts = [];
foreach ($byPatient as $orders) {
@@ -492,6 +518,9 @@ class DeptLogic extends BaseLogic
if ($cid <= 0) {
continue;
}
if ($creatorFlip !== null && !isset($creatorFlip[$cid])) {
continue;
}
$canon = $canonicalDept[$cid] ?? null;
if ($canon === null || !isset($erSubtreeSet[$canon]) || !isset($descFlip[$canon])) {
continue;
@@ -157,6 +157,8 @@ class YejiStatsLogic
/**
* 按角色「数据范围」收窄业绩看板上下文(adminToPrimary、tableRowDeptIds)。
* DataScopeService / 列表 HasDataScopeFilter 一致:ALL 不处理;SELF/本部门/本部门及下级 仅保留可见 admin_id。
*
* 表格仍保持默认「中心」层级(图1);合计业绩等数值在聚合层按 dataScopeVisibleAdminIds 收窄。
*/
private static function applyYejiDataScope(array &$c, int $adminId, array $adminInfo): void
{
@@ -178,6 +180,17 @@ class YejiStatsLogic
return;
}
self::narrowYejiContextTableRowsByVisibleAdmins($c, $visibleIds);
}
/**
* 在现有展示行(默认中心层级)上按可见 admin 收窄表格行与台账归属。
*
* @param int[] $visibleIds
*/
private static function narrowYejiContextTableRowsByVisibleAdmins(array &$c, array $visibleIds): void
{
$flip = array_flip($visibleIds);
$filtered = [];
foreach ($c['adminToPrimary'] as $aid => $pid) {
@@ -402,7 +415,10 @@ class YejiStatsLogic
null,
null,
$tableRowDeptIds,
$deptById
$deptById,
null,
[],
$dataScopeAdminIds
)
: null;
// 业绩:部门行「合计业绩」与列表部门摊行一致;「{渠道}业绩」同列表摊行 + 渠道 EXISTS(与抽屉一致)
@@ -417,7 +433,8 @@ class YejiStatsLogic
$tableRowDeptIds,
$deptById,
$listAlignedPack !== null ? $listAlignedPack['amount'] : null,
$channelInfo
$channelInfo,
$dataScopeAdminIds
);
$performance = $perfBoth['all'];
$channelPerformance = $perfBoth['scoped'];
@@ -470,7 +487,8 @@ class YejiStatsLogic
}
@set_time_limit(120);
$revisitPack = DeptLogic::getErCenterRevisitRollupIndexedByDept($startTs, $endTs);
$revisitCreatorFilter = self::resolveYejiRevisitCreatorFilter($c);
$revisitPack = DeptLogic::getErCenterRevisitRollupIndexedByDept($startTs, $endTs, $revisitCreatorFilter);
$revisitTotals = $revisitPack['totals'];
$revisitSlotsByDept = $revisitPack['slots'];
@@ -807,7 +825,10 @@ class YejiStatsLogic
$erCenterDeptSet = DeptLogic::getErCenterSubtreeDeptIdSet();
/** 医助排行榜复诊:按订单创建人归属(与诊金 / 接诊诊单同口径),不再用 dg.assistant_id */
$revisitByAssistant = DeptLogic::getErCenterRevisitCountsByCreator($startTs, $endTs);
$revisitCreatorFilter = ($dataScopeRestricted && $dataScopeAdminIds !== null && $dataScopeAdminIds !== [])
? $dataScopeAdminIds
: null;
$revisitByAssistant = DeptLogic::getErCenterRevisitCountsByCreator($startTs, $endTs, $revisitCreatorFilter);
$assistantIdRows = Db::name('admin_role')->where('role_id', 2)->column('admin_id');
$assistantSet = [];
@@ -1938,7 +1959,8 @@ class YejiStatsLogic
$tableRowDeptIds,
$deptById,
$pack[0],
$pack[1]
$pack[1],
$dataScopeAdminIds
);
$scopedByDept = $scopedAligned['count'];
} else {
@@ -3011,25 +3033,16 @@ class YejiStatsLogic
return $out;
}
$revisitCreatorFilter = self::resolveYejiRevisitCreatorFilter($c);
$pack = DeptLogic::getErCenterRevisitAssistantBreakdownForDept(
$deptId,
$revisitSlot,
(int) $c['startTs'],
(int) $c['endTs']
(int) $c['endTs'],
$revisitCreatorFilter
);
$rows = $pack['rows'];
if (!empty($c['dataScopeRestricted']) && isset($c['dataScopeVisibleAdminIds'])) {
$flip = array_flip($c['dataScopeVisibleAdminIds']);
$filtered = [];
foreach ($rows as $rw) {
if (isset($flip[(int) ($rw['admin_id'] ?? 0)])) {
$filtered[] = $rw;
}
}
$rows = $filtered;
}
$out['rows'] = $rows;
if ($rows === []) {
$out['note'] = '当前部门与区间下无符合条件的复诊业务订单(口径与看板「二中心复诊」列一致)。';
@@ -3038,6 +3051,28 @@ class YejiStatsLogic
return $out;
}
/**
* 二中心复诊统计:数据范围下仅统计可见创建人的复诊笔数(与合计业绩一致)。
*
* @param array<string, mixed> $c
*
* @return int[]|null null=不限制;[]=无可见创建人
*/
private static function resolveYejiRevisitCreatorFilter(array $c): ?array
{
if (empty($c['dataScopeRestricted']) || !isset($c['dataScopeVisibleAdminIds'])) {
return null;
}
if (!\is_array($c['dataScopeVisibleAdminIds']) || $c['dataScopeVisibleAdminIds'] === []) {
return [];
}
return array_values(array_unique(array_filter(
array_map('intval', $c['dataScopeVisibleAdminIds']),
static fn (int $v): bool => $v > 0
)));
}
/**
* sumPerformance 对应,按 admin 维度返回全量/渠道业绩金额(不按部门折叠)。
* 归属固定为 sqlPerformanceAttributionAdminExpr()——按订单创建人。
@@ -3711,7 +3746,8 @@ class YejiStatsLogic
array $tableRowDeptIds = [],
array $deptById = [],
?array $precomputedListAlignedAmountByDept = null,
?array $channelInfo = null
?array $channelInfo = null,
?array $dataScopeAdminIds = null
): array {
if ($adminToPrimary === []) {
return ['all' => [], 'scoped' => []];
@@ -3730,7 +3766,10 @@ class YejiStatsLogic
null,
null,
$tableRowDeptIds,
$deptById
$deptById,
null,
[],
$dataScopeAdminIds
);
$allByDept = [];
foreach ($aligned['amount'] as $deptId => $v) {
@@ -3770,7 +3809,8 @@ class YejiStatsLogic
$tableRowDeptIds,
$deptById,
$pack[0],
$pack[1]
$pack[1],
$dataScopeAdminIds
);
$scopedByDept = [];
foreach ($scopedAligned['amount'] as $deptId => $v) {
@@ -4085,7 +4125,8 @@ class YejiStatsLogic
array $tableRowDeptIds,
array $deptById,
?string $channelScopedExistsSql = null,
array $channelScopedExistsBindings = []
array $channelScopedExistsBindings = [],
?array $dataScopeAdminIds = null
): array {
if ($tagDiagIds !== null && $tagDiagIds === []) {
return ['count' => [], 'amount' => []];
@@ -4115,6 +4156,9 @@ class YejiStatsLogic
$in = implode(',', $ids);
$query->whereRaw("o.creator_id IN ({$in})");
}
if ($dataScopeAdminIds !== null && $dataScopeAdminIds !== []) {
$query->whereIn('o.creator_id', $dataScopeAdminIds);
}
if ($channelScopedExistsSql !== null && $channelScopedExistsSql !== '') {
$query->whereRaw($channelScopedExistsSql, $channelScopedExistsBindings);
}
@@ -651,7 +651,7 @@ class PrescriptionOrderLogic
$rows = Order::whereIn('id', $ids)->whereNull('delete_time')
->field(['id', 'order_no', 'order_type', 'amount', 'status', 'create_time', 'creator_id', 'remark', 'is_exempt'])
->order('id', 'asc')
->whereIn('status', [2,5])
->whereIn('status', [2, 4, 5])
->select()
->toArray();
@@ -709,7 +709,10 @@ class PrescriptionOrderLogic
$arr['linked_pay_orders'] = self::linkedPayOrdersPayload($ids);
$sum = 0.0;
foreach ($arr['linked_pay_orders'] as $o) {
$sum += (float) ($o['amount'] ?? 0);
$st = (int) ($o['status'] ?? 0);
if ($st === 2 || $st === 5) {
$sum += (float) ($o['amount'] ?? 0);
}
}
$arr['linked_pay_paid_total'] = round($sum, 2);
$arr['deposit_min_amount'] = self::depositMinAmount();
@@ -2457,7 +2460,10 @@ class PrescriptionOrderLogic
$linkedPayOrders = self::linkedPayOrdersPayload($linkedPayOrderIds);
$linkedPayPaidTotal = 0.0;
foreach ($linkedPayOrders as $o) {
$linkedPayPaidTotal += (float) ($o['amount'] ?? 0);
$st = (int) ($o['status'] ?? 0);
if ($st === 2 || $st === 5) {
$linkedPayPaidTotal += (float) ($o['amount'] ?? 0);
}
}
$linkedPayPaidTotal = round($linkedPayPaidTotal, 2);
@@ -2468,7 +2474,13 @@ class PrescriptionOrderLogic
$order->fulfillment_status = $targetFulfillmentStatus;
}
$refundedPayCount = 0;
try {
if ($targetFulfillmentStatus === 10 && $linkedPayOrderIds !== []) {
$refundedPayCount = Order::whereIn('id', $linkedPayOrderIds)
->whereNull('delete_time')
->update(['status' => 4]);
}
$order->save();
} catch (\Throwable $e) {
self::$error = $e->getMessage();
@@ -2484,6 +2496,9 @@ class PrescriptionOrderLogic
$logLine = '订单完成,状态变更为「' . $label . '」,实付金额更新为 ¥' . $linkedPayPaidTotal . $unassignSummary;
} else {
$logLine = '订单处理完成,状态变更为「' . $label . '」' . $unassignSummary;
if ($targetFulfillmentStatus === 10 && $refundedPayCount > 0) {
$logLine .= ',关联支付单 ' . $refundedPayCount . ' 笔已标记为已退款';
}
}
self::writeLog(
$id,