更新
This commit is contained in:
@@ -898,6 +898,24 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
|
||||
return;
|
||||
}
|
||||
$yejiTableRowDeptIds = [];
|
||||
if (isset($this->params['yeji_table_row_dept_ids']) && trim((string) $this->params['yeji_table_row_dept_ids']) !== '') {
|
||||
foreach (explode(',', (string) $this->params['yeji_table_row_dept_ids']) as $p) {
|
||||
$v = (int) trim((string) $p);
|
||||
if ($v > 0) {
|
||||
$yejiTableRowDeptIds[] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((int) ($this->params['yeji_order_drawer'] ?? 0) === 1 && $yejiTableRowDeptIds !== []) {
|
||||
YejiStatsLogic::applyPrescriptionOrderListAlignedWinnerDeptFilter(
|
||||
$query,
|
||||
(int) $this->params['assistant_dept_id'],
|
||||
$yejiTableRowDeptIds
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
$rootDeptId = (int) $this->params['assistant_dept_id'];
|
||||
$deptIds = DeptLogic::getSelfAndDescendantIds($rootDeptId);
|
||||
$deptIds = array_values(array_filter(array_map('intval', $deptIds), static function (int $id): bool {
|
||||
@@ -911,16 +929,9 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
|
||||
$inList = implode(',', $deptIds);
|
||||
$diagTbl = (new Diagnosis())->getTable();
|
||||
$adTbl = (new AdminDept())->getTable();
|
||||
$query->where(function ($q) use ($poTbl, $diagTbl, $adTbl, $inList) {
|
||||
$q->whereExists(
|
||||
"SELECT 1 FROM `{$adTbl}` ad INNER JOIN `{$diagTbl}` dg ON ad.admin_id = dg.assistant_id AND dg.delete_time IS NULL "
|
||||
. "WHERE dg.`id` = `{$poTbl}`.`diagnosis_id` AND ad.dept_id IN ({$inList})"
|
||||
);
|
||||
$q->whereExists(
|
||||
"SELECT 1 FROM `{$adTbl}` adc WHERE adc.`admin_id` = `{$poTbl}`.`creator_id` AND adc.`dept_id` IN ({$inList})",
|
||||
'OR'
|
||||
);
|
||||
});
|
||||
$query->whereRaw(
|
||||
YejiStatsLogic::sqlPrescriptionOrderListDeptSubtreeMatchCreatorFirst($poTbl, $diagTbl, $adTbl, $inList)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ declare(strict_types=1);
|
||||
namespace app\adminapi\logic\stats;
|
||||
|
||||
use app\adminapi\logic\dept\DeptLogic;
|
||||
use app\common\model\auth\AdminDept;
|
||||
use app\common\model\tcm\Diagnosis;
|
||||
use app\common\model\tcm\PrescriptionOrder;
|
||||
use app\common\service\DataScope\DataScopeService;
|
||||
use app\common\service\qywx\MediaChannelService;
|
||||
@@ -20,9 +22,9 @@ use think\facade\Db;
|
||||
* · **二中心及其组织下级在选定渠道下计 0**(与进线一致)
|
||||
* - 复诊统计 = 仅**名称含「二中心」的部门及其组织架构下级**:区间内业务单按患者 create_time 排序,第 1 笔不计,第 2 笔起分列「复诊2」「复诊3」…;口径同业绩单条件;按诊单医助在组织树内归属 rollup(含下级),与 dept 模块 ErCenter 复诊算法一致
|
||||
* - 已完成挂号 = status=3 挂号条数(appointment_date),列「已完成挂号」;部门归属:有效医助→接诊医生→展示行映射。
|
||||
* - 接诊诊单 = 列名对应**业务订单条数**(非取消、create_time 区间),与处方列表按 **assistant_dept_id** 筛选时一致:诊单 **assistant_id** 或订单 **creator_id** 任一人事部门落在该行子树即计入;多行命中时取**最深的展示部门行**,与「业绩归属医助」分组可能不同。
|
||||
* - 接诊诊单 = 列名对应**业务订单条数**(非取消、create_time 区间),与处方列表按 **assistant_dept_id** 筛选时一致:优先按订单 **creator_id** 人事部门;无创建人时回退诊单 **assistant_id**;多行命中时取**最深的展示部门行**,与「业绩归属医助」分组可能不同。
|
||||
* - {渠道}成交单 = 选定渠道时与 {渠道}业绩同源:区间内 **非取消业务订单条数**(仅剔除 fulfillment_status=4;NULL 与其它状态计入,按 create_time 归日);dict(channels) 与 tag 口径均按订单计(tag 时按诊单医助收窄),非挂号条数。
|
||||
* - 合计业绩 = 区间内业务订单 amount 求和(排除已取消(4) 与软删),**部门列**与「接诊诊单」一致:诊单医助或订单创建人的人事部门落在该展示行子树则计入,多行命中取最深展示行;与处方列表按 assistant_dept_id 筛选后的金额合计一致。
|
||||
* - 合计业绩 = 区间内业务订单 amount 求和(排除已取消(4) 与软删),**部门列**与「接诊诊单」一致;与处方列表按 assistant_dept_id 筛选后的金额合计一致。
|
||||
* 无法归入任一展示部门的金额单列「未归属中心」行;**全表合计行**仍为同时间/业绩条件的全表 SUM(不限部门)。
|
||||
* · 时间窗与 stats_order_amount_performance 一致;列表若另有患者筛选或账号数据域收窄,金额可能仍不同
|
||||
* · 选定渠道时「{渠道}业绩」列仍按比例展示;合计列全量与渠道列含未归属补差;**二中心及其组织下级展示部门不参与该渠道进线/业绩/成交单/ROI 分子**(与同模块 DeptLogic::getErCenterSubtreeDeptIdSet 一致)
|
||||
@@ -566,12 +568,12 @@ class YejiStatsLogic
|
||||
}
|
||||
|
||||
$scopedTableNote = ($explicitDeptFilter
|
||||
? ' 已筛选展示部门时,表格「合计业绩」「接诊诊单」各行为同一套列表部门口径(诊单医助或创建人∪子树、最深展示行);底栏合计业绩/成交单与处方订单列表 extend(所选部门树+数据域)一致,可能与各行相加不同。'
|
||||
? ' 已筛选展示部门时,表格「合计业绩」「接诊诊单」各行为同一套列表部门口径(**创建人人事部门优先**,无创建人则诊单医助;∪子树、最深展示行);底栏合计业绩/成交单与处方订单列表 extend(所选部门树+数据域)一致,可能与各行相加不同。'
|
||||
: '')
|
||||
. ($dataScopeRestricted
|
||||
? ' 当前账号受数据范围限制时,底栏合计业绩与列表同域。'
|
||||
: '');
|
||||
$baseNoteNoChannel = '业绩口径:除履约已取消(4)外全部计入。部门行「合计业绩」与「接诊诊单」均为列表 assistant_dept_id 口径(诊单医助或订单创建人人事部门∪子树,多行命中取最深展示行)。多表「本月」区间为当月1日至服务器当日。**投放成本**按进线占比分摊,**二中心子树展示部门进线在分摊中计 0**(与部门 ErCenter 子树一致)。';
|
||||
$baseNoteNoChannel = '业绩口径:除履约已取消(4)外全部计入。部门行「合计业绩」与「接诊诊单」均为列表 assistant_dept_id 口径(**创建人优先**,否则诊单医助;人事部门∪子树,多行命中取最深展示行)。多表「本月」区间为当月1日至服务器当日。**投放成本**按进线占比分摊,**二中心子树展示部门进线在分摊中计 0**(与部门 ErCenter 子树一致)。';
|
||||
|
||||
return [
|
||||
'start_date' => $startDate,
|
||||
@@ -583,7 +585,7 @@ class YejiStatsLogic
|
||||
'channel_filter_note' => $channelCode !== ''
|
||||
? ('{渠道}业绩:与「处方订单列表」业绩同口径,按订单 create_time 归日、仅剔除履约已取消(4)(NULL 与其它状态均计入);渠道判定用"该患者是否有挂号 channels 命中 dict + status=3"(不限挂号时点)。'
|
||||
. ' {渠道}成交单数:与{渠道}业绩同源,仅剔除已取消(4)的订单条数(非挂号条数)。'
|
||||
. ' 「已完成挂号」列同 admin 挂号已完成列表;「接诊诊单」列为业务订单条数(非取消),与列表按 assistant_dept_id 筛选时一致(诊单医助或创建人∪子树)。**二中心及其组织下级**(与部门模块 ErCenter 子树一致)**不计入**该渠道进线、业绩与成交单。**投放成本**按各部门进线条数占展示部门进线合计的比例分摊,**二中心子树进线在分摊中计 0**(未选渠道时仍显示进线数,仅不参与均摊)。ROI = 渠道业绩(不含二中心子树)÷ 总投放成本。进线按所选标签客户精确过滤。'
|
||||
. ' 「已完成挂号」列同 admin 挂号已完成列表;「接诊诊单」列为业务订单条数(非取消),与列表按 assistant_dept_id 筛选时一致(**创建人优先**,否则诊单医助;∪子树)。**二中心及其组织下级**(与部门模块 ErCenter 子树一致)**不计入**该渠道进线、业绩与成交单。**投放成本**按各部门进线条数占展示部门进线合计的比例分摊,**二中心子树进线在分摊中计 0**(未选渠道时仍显示进线数,仅不参与均摊)。ROI = 渠道业绩(不含二中心子树)÷ 总投放成本。进线按所选标签客户精确过滤。'
|
||||
. ($tagFallback
|
||||
? ' ⚠ 当前业务尚未回填 tcm_diagnosis.external_userid 与 user_auth.unionid,已自动退化为「该标签客户的跟进医助 → 区间内接诊/业绩」口径,仅用于趋势观察。'
|
||||
: '')
|
||||
@@ -3934,7 +3936,7 @@ class YejiStatsLogic
|
||||
|
||||
/**
|
||||
* 与 {@see PrescriptionOrderLists::applyDoctorAssistantFilters} 中 assistant_dept_id(未开抽屉对齐开关)一致:
|
||||
* 诊单医助或订单创建人的人事部门落在该行子树即命中;命中多行时取组织深度最大的展示行。
|
||||
* 有创建人则仅用创建人人事部门落在该行子树即命中;无创建人则诊单医助;命中多行时取组织深度最大的展示行。
|
||||
*
|
||||
* @param array<int, true> $candidates admin_id => true
|
||||
* @param array<int, int[]> $adminDeptIdList
|
||||
@@ -4008,7 +4010,7 @@ class YejiStatsLogic
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表部门口径(诊单医助 ∪ 创建人 ⇄ 展示行子树,最深行):同时聚单笔数与金额,供「接诊诊单」与「合计业绩」列共用。
|
||||
* 列表部门口径(有创建人则仅用创建人、否则诊单医助 ⇄ 展示行子树,最深行):聚单笔数与金额,供「接诊诊单」与「合计业绩」列共用。
|
||||
*
|
||||
* @return array{count: array<int, int>, amount: array<int, float>}
|
||||
*/
|
||||
@@ -4079,11 +4081,10 @@ class YejiStatsLogic
|
||||
foreach ($orderRows as $r) {
|
||||
$a = (int) ($r['diag_assistant_id'] ?? 0);
|
||||
$c = (int) ($r['creator_id'] ?? 0);
|
||||
if ($a > 0) {
|
||||
$adminIds[] = $a;
|
||||
}
|
||||
if ($c > 0) {
|
||||
$adminIds[] = $c;
|
||||
} elseif ($a > 0) {
|
||||
$adminIds[] = $a;
|
||||
}
|
||||
}
|
||||
$adminIds = array_values(array_unique(array_filter($adminIds, static fn (int $x): bool => $x > 0)));
|
||||
@@ -4106,11 +4107,10 @@ class YejiStatsLogic
|
||||
$asst = (int) ($r['diag_assistant_id'] ?? 0);
|
||||
$cr = (int) ($r['creator_id'] ?? 0);
|
||||
$cand = [];
|
||||
if ($asst > 0) {
|
||||
$cand[$asst] = true;
|
||||
}
|
||||
if ($cr > 0) {
|
||||
$cand[$cr] = true;
|
||||
} elseif ($asst > 0) {
|
||||
$cand[$asst] = true;
|
||||
}
|
||||
$deptKey = self::resolveDeepestYejiTableRowForListDeptOr(
|
||||
$cand,
|
||||
@@ -4132,7 +4132,7 @@ class YejiStatsLogic
|
||||
}
|
||||
|
||||
/**
|
||||
* 成交单数按部门:**处方订单列表**按 assistant_dept_id 筛选时的业务单条数(诊单医助或创建人 ∪ 部门子树),
|
||||
* 成交单数按部门:**处方订单列表**按 assistant_dept_id 筛选时的业务单条数(创建人优先、无创建人则诊单医助 ∪ 部门子树),
|
||||
* 与 {@see PrescriptionOrderLists::applyDoctorAssistantFilters} 在 <b>未</b> 传 yeji_drawer_match_table_performance 时一致。
|
||||
* 选定医助 tag 时收窄为(业绩归属医助 ∈ 集 **或** 诊单医助 ∈ 集 **或** 创建人 ∈ 集)。
|
||||
*
|
||||
@@ -4920,4 +4920,102 @@ class YejiStatsLogic
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 业务订单列表 WHERE 片段:有创建人则仅按创建人人事部门落在子树;无创建人则按诊单医助(与
|
||||
* {@see aggregateListAlignedOrderMetricsByDept} 及
|
||||
* {@see PrescriptionOrderLists::applyDoctorAssistantFilters} 中 assistant_dept_id 一致)。
|
||||
*
|
||||
* @param string $inListComma 已净化的 id 列表(仅数字与逗号)
|
||||
*/
|
||||
public static function sqlPrescriptionOrderListDeptSubtreeMatchCreatorFirst(
|
||||
string $poTbl,
|
||||
string $diagTbl,
|
||||
string $adTbl,
|
||||
string $inListComma
|
||||
): string {
|
||||
$creatorInSubtree = "EXISTS (SELECT 1 FROM `{$adTbl}` adc WHERE adc.`admin_id` = `{$poTbl}`.`creator_id` AND adc.`dept_id` IN ({$inListComma}))";
|
||||
$assistantInSubtree = "EXISTS (SELECT 1 FROM `{$adTbl}` ad INNER JOIN `{$diagTbl}` dg ON ad.admin_id = dg.assistant_id AND dg.delete_time IS NULL "
|
||||
. "WHERE dg.`id` = `{$poTbl}`.`diagnosis_id` AND ad.dept_id IN ({$inListComma}))";
|
||||
|
||||
return '('
|
||||
. '((IFNULL(`' . $poTbl . '`.`creator_id`,0) > 0) AND (' . $creatorInSubtree . '))'
|
||||
. ' OR '
|
||||
. '((IFNULL(`' . $poTbl . '`.`creator_id`,0) <= 0) AND (' . $assistantInSubtree . '))'
|
||||
. ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* 业绩看板侧栏:与「接诊诊单」列 {@see aggregateListAlignedOrderMetricsByDept} 同源——在**同一表格**
|
||||
* 展示行集合内,创建人优先归属;无创建人则医助;多行命中时取组织深度最大的展示行(同深取较小 dept id)。
|
||||
* 传入当前表全部展示行 dept id。
|
||||
*
|
||||
* @param int[] $tableRowDeptIds 当前业绩表 body 行 dept_id(含点击行及兄弟行)
|
||||
*/
|
||||
public static function applyPrescriptionOrderListAlignedWinnerDeptFilter(Query $query, int $targetRowDeptId, array $tableRowDeptIds): void
|
||||
{
|
||||
$targetRowDeptId = (int) $targetRowDeptId;
|
||||
if ($targetRowDeptId <= 0) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$uniq = [];
|
||||
foreach ($tableRowDeptIds as $id) {
|
||||
$id = (int) $id;
|
||||
if ($id > 0) {
|
||||
$uniq[$id] = true;
|
||||
}
|
||||
}
|
||||
$tableRowDeptIds = array_keys($uniq);
|
||||
if ($tableRowDeptIds === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$deptRows = Db::name('dept')->whereNull('delete_time')->field(['id', 'pid'])->select()->toArray();
|
||||
$deptById = [];
|
||||
foreach ($deptRows as $row) {
|
||||
$deptById[(int) $row['id']] = $row;
|
||||
}
|
||||
|
||||
$dT = self::deptDepthFromRoot($targetRowDeptId, $deptById);
|
||||
$beaters = [];
|
||||
foreach ($tableRowDeptIds as $r) {
|
||||
$r = (int) $r;
|
||||
if ($r <= 0 || $r === $targetRowDeptId) {
|
||||
continue;
|
||||
}
|
||||
$dR = self::deptDepthFromRoot($r, $deptById);
|
||||
if ($dR > $dT || ($dR === $dT && $r < $targetRowDeptId)) {
|
||||
$beaters[] = $r;
|
||||
}
|
||||
}
|
||||
|
||||
$poTbl = (new PrescriptionOrder())->getTable();
|
||||
$diagTbl = (new Diagnosis())->getTable();
|
||||
$adTbl = (new AdminDept())->getTable();
|
||||
|
||||
$tSubtree = DeptLogic::getSelfAndDescendantIds($targetRowDeptId);
|
||||
$tSubtree = array_values(array_filter(array_map('intval', $tSubtree), static fn (int $id): bool => $id > 0));
|
||||
if ($tSubtree === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
|
||||
return;
|
||||
}
|
||||
$tIn = implode(',', $tSubtree);
|
||||
$clauses = [self::sqlPrescriptionOrderListDeptSubtreeMatchCreatorFirst($poTbl, $diagTbl, $adTbl, $tIn)];
|
||||
foreach ($beaters as $b) {
|
||||
$bSubtree = DeptLogic::getSelfAndDescendantIds((int) $b);
|
||||
$bSubtree = array_values(array_filter(array_map('intval', $bSubtree), static fn (int $id): bool => $id > 0));
|
||||
if ($bSubtree === []) {
|
||||
continue;
|
||||
}
|
||||
$bIn = implode(',', $bSubtree);
|
||||
$clauses[] = 'NOT (' . self::sqlPrescriptionOrderListDeptSubtreeMatchCreatorFirst($poTbl, $diagTbl, $adTbl, $bIn) . ')';
|
||||
}
|
||||
$query->whereRaw('(' . implode(' AND ', $clauses) . ')');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user