更新
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\logic\stats;
|
||||
|
||||
use app\common\service\tcm\RevisitVisitSlotService;
|
||||
|
||||
/** 指派日期队列:先圈定患者,再累计其持有期间的有效复诊订单;不改变全局诊次。 */
|
||||
final class RevisitCohortCalculator
|
||||
{
|
||||
public const TYPE_NEW = 'new';
|
||||
public const TYPE_INHERIT = 'inherit';
|
||||
|
||||
/**
|
||||
* 日志与订单分别按 (diagnosis_id, create_time, id) 升序。
|
||||
* 新指派保留全局诊次;继承每次从二诊重新起算,同一秒订单归最新指派。
|
||||
* 同一医助、诊单、诊次只计一次,避免反复继承把单诊接诊率推高至 100% 以上。
|
||||
*/
|
||||
public static function compute(
|
||||
array $logRows,
|
||||
iterable $orderRows,
|
||||
array $offsetMap,
|
||||
array $refundRejectSet,
|
||||
array $rollbackKeySet,
|
||||
int $startTs,
|
||||
int $endTs,
|
||||
int $observedAt,
|
||||
string $type
|
||||
): array {
|
||||
$type = $type === self::TYPE_INHERIT ? self::TYPE_INHERIT : self::TYPE_NEW;
|
||||
$timeline = [];
|
||||
$pairs = [];
|
||||
$lastTarget = [];
|
||||
foreach ($logRows as $r) {
|
||||
$did = (int) ($r['diagnosis_id'] ?? 0);
|
||||
$aid = (int) ($r['to_assistant_id'] ?? 0);
|
||||
$from = (int) ($r['from_assistant_id'] ?? 0);
|
||||
$time = (int) ($r['create_time'] ?? 0);
|
||||
if ($did <= 0 || $time > $observedAt) {
|
||||
continue;
|
||||
}
|
||||
// 从未分配重新指派时,写入侧可能把 from 回填为原医助,形成 A→A。
|
||||
// 以实际时间线判断持有人是否变化,保留释放后的重新承接。
|
||||
$previous = $lastTarget[$did] ?? null;
|
||||
if (($previous !== null && $previous === $aid) || ($previous === null && $from === $aid)) {
|
||||
continue;
|
||||
}
|
||||
$lastTarget[$did] = $aid;
|
||||
$isRollback = isset($rollbackKeySet[RevisitRateCalculator::rollbackKey(
|
||||
$did, $time, (int) ($r['operator_admin_id'] ?? 0)
|
||||
)]);
|
||||
$eventType = (int) ($r['is_inherit'] ?? 0) === 1 || $isRollback
|
||||
? self::TYPE_INHERIT : self::TYPE_NEW;
|
||||
$selected = $aid > 0 && $time >= $startTs && $time <= $endTs
|
||||
&& $eventType === $type && !isset($refundRejectSet[$did]);
|
||||
$timeline[$did][] = [
|
||||
'assistant_id' => $aid,
|
||||
'time' => $time,
|
||||
'selected' => $selected,
|
||||
'type' => $eventType,
|
||||
];
|
||||
if (!$selected) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($pairs[$aid][$did])) {
|
||||
$pairs[$aid][$did] = [
|
||||
'diagnosis_id' => $did,
|
||||
'assistant_id' => $aid,
|
||||
'assign_type' => $type,
|
||||
'assign_count' => 0,
|
||||
'first_assign_time' => $time,
|
||||
'last_assign_time' => $time,
|
||||
'is_rollback' => 0,
|
||||
];
|
||||
}
|
||||
$pairs[$aid][$did]['assign_count']++;
|
||||
$pairs[$aid][$did]['last_assign_time'] = $time;
|
||||
$pairs[$aid][$did]['is_rollback'] |= $isRollback ? 1 : 0;
|
||||
}
|
||||
|
||||
$orders = [];
|
||||
$curDid = 0;
|
||||
$globalSeq = 0;
|
||||
$segmentSeq = 0;
|
||||
$ptr = 0;
|
||||
$offset = 0;
|
||||
$event = null;
|
||||
foreach ($orderRows as $r) {
|
||||
$did = (int) ($r['diagnosis_id'] ?? 0);
|
||||
$ct = (int) ($r['create_time'] ?? 0);
|
||||
if ($did <= 0 || $ct > $observedAt) {
|
||||
continue;
|
||||
}
|
||||
if ($curDid !== $did) {
|
||||
$curDid = $did;
|
||||
$globalSeq = 0;
|
||||
$segmentSeq = 0;
|
||||
$ptr = 0;
|
||||
$event = null;
|
||||
$offset = RevisitVisitSlotService::resolveRevisitSlotStartOffset($did, $offsetMap);
|
||||
}
|
||||
$globalSeq++;
|
||||
$tl = $timeline[$did] ?? [];
|
||||
while ($ptr < \count($tl) && $tl[$ptr]['time'] <= $ct) {
|
||||
$event = $tl[$ptr++];
|
||||
$segmentSeq = 0;
|
||||
}
|
||||
if ($event === null) {
|
||||
continue;
|
||||
}
|
||||
$segmentSeq++;
|
||||
if (!$event['selected']) {
|
||||
continue;
|
||||
}
|
||||
$slot = $type === self::TYPE_INHERIT ? $segmentSeq + 1 : $globalSeq + $offset;
|
||||
if ($slot < 2 || $slot > RevisitRateCalculator::MAX_VISIT_SLOT) {
|
||||
continue;
|
||||
}
|
||||
$aid = $event['assistant_id'];
|
||||
// 重复承接同一患者时,明细保留首次达到该诊次的订单,与人数指标逐项对账。
|
||||
if (isset($orders[$aid][$slot][$did])) {
|
||||
continue;
|
||||
}
|
||||
$r['assistant_id'] = $aid;
|
||||
$r['visit_slot'] = $slot;
|
||||
$r['global_visit_slot'] = $globalSeq + $offset;
|
||||
$r['cohort_assign_time'] = $event['time'];
|
||||
$orders[$aid][$slot][$did] = $r;
|
||||
}
|
||||
|
||||
return ['pairsByAssistant' => $pairs, 'ordersByAssistant' => $orders, 'cohort_type' => $type];
|
||||
}
|
||||
|
||||
/** 部门与总计按诊单去重;每个诊次保留同一患者最早一笔订单。 */
|
||||
public static function scopedOrders(array $calc, array $assistantIds, int $slot = 0): array
|
||||
{
|
||||
$out = [];
|
||||
foreach ($assistantIds as $aid) {
|
||||
foreach ($calc['ordersByAssistant'][$aid] ?? [] as $s => $byDiag) {
|
||||
if ($slot > 0 && $s !== $slot) {
|
||||
continue;
|
||||
}
|
||||
foreach ($byDiag as $did => $r) {
|
||||
$previous = $out[$s][$did] ?? null;
|
||||
if ($previous === null || [(int) $r['create_time'], (int) $r['id']]
|
||||
< [(int) $previous['create_time'], (int) $previous['id']]) {
|
||||
$out[$s][$did] = $r;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public static function metricPack(array $calc, array $assistantIds, array $slots): array
|
||||
{
|
||||
$diagSet = [];
|
||||
foreach ($assistantIds as $aid) {
|
||||
foreach ($calc['pairsByAssistant'][$aid] ?? [] as $did => $_) {
|
||||
$diagSet[$did] = true;
|
||||
}
|
||||
}
|
||||
$count = \count($diagSet);
|
||||
$inherit = $calc['cohort_type'] === self::TYPE_INHERIT;
|
||||
$pack = [
|
||||
'assigned_count' => $inherit ? 0 : $count,
|
||||
'inherit_count' => $inherit ? $count : 0,
|
||||
'assigned_total_count' => $count,
|
||||
];
|
||||
$orders = self::scopedOrders($calc, $assistantIds);
|
||||
$all = array_sum(array_map('count', $orders));
|
||||
foreach ($slots as $slot) {
|
||||
$num = \count($orders[$slot] ?? []);
|
||||
$pack['visit' . $slot . '_count'] = $num;
|
||||
$pack['visit' . $slot . '_den'] = $count;
|
||||
$pack['visit' . $slot . '_rate'] = $count > 0 ? round($num / $count * 100, 2) : null;
|
||||
}
|
||||
$pack['visit_all_count'] = $all;
|
||||
$pack['visit_all_den'] = $count;
|
||||
$pack['visit_all_rate'] = $count > 0 ? round($all / $count * 100, 2) : null;
|
||||
|
||||
return $pack;
|
||||
}
|
||||
|
||||
/** 严格校验日期,避免非法日期被 strtotime 自动滚到次月。 */
|
||||
public static function dateRange(array $params, int $now): array
|
||||
{
|
||||
$start = trim((string) ($params['start_date'] ?? ''));
|
||||
$end = trim((string) ($params['end_date'] ?? ''));
|
||||
if ($start === '' && $end === '') {
|
||||
$month = trim((string) ($params['month'] ?? ''));
|
||||
if (preg_match('/^\d{4}-(0[1-9]|1[0-2])$/', $month) === 1) {
|
||||
$start = $month . '-01';
|
||||
$end = date('Y-m-t', (int) strtotime($start));
|
||||
} else {
|
||||
$start = date('Y-m-01', $now);
|
||||
$end = date('Y-m-d', $now);
|
||||
}
|
||||
}
|
||||
foreach ([$start, $end] as $date) {
|
||||
$parsed = \DateTimeImmutable::createFromFormat('!Y-m-d', $date);
|
||||
if ($parsed === false || $parsed->format('Y-m-d') !== $date) {
|
||||
throw new \InvalidArgumentException('请选择有效的开始日期和结束日期');
|
||||
}
|
||||
}
|
||||
if ($start > $end) {
|
||||
throw new \InvalidArgumentException('开始日期不能晚于结束日期');
|
||||
}
|
||||
|
||||
return [$start, $end, (int) strtotime($start . ' 00:00:00'), (int) strtotime($end . ' 23:59:59')];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\logic\stats;
|
||||
|
||||
use app\adminapi\logic\dept\DeptLogic;
|
||||
use app\common\service\tcm\RevisitVisitSlotService;
|
||||
use think\facade\Db;
|
||||
|
||||
/** 新报表独立读取方法,保持原复诊率 Logic 源码及调用链不变。 */
|
||||
trait RevisitCohortDataSource
|
||||
{
|
||||
private static function fetchRollbackKeySet(int $startTs, int $endTs): array
|
||||
{
|
||||
$rows = Db::name('tcm_diagnosis_auto_assign_log')
|
||||
->where('rollback_time', '>=', $startTs)
|
||||
->where('rollback_time', '<=', $endTs)
|
||||
->where('diagnosis_id', '>', 0)
|
||||
->field(['diagnosis_id', 'rollback_time', 'rollback_admin_id'])
|
||||
->select()
|
||||
->toArray();
|
||||
$out = [];
|
||||
foreach ($rows as $r) {
|
||||
$out[RevisitRateCalculator::rollbackKey(
|
||||
(int) ($r['diagnosis_id'] ?? 0),
|
||||
(int) ($r['rollback_time'] ?? 0),
|
||||
(int) ($r['rollback_admin_id'] ?? 0)
|
||||
)] = true;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
private static function iterateOrderSeqRows(array $diagIds, array $fields): \Generator
|
||||
{
|
||||
foreach (array_chunk($diagIds, 2000) as $chunk) {
|
||||
foreach (self::fetchOrderSeqRows($chunk, $fields) as $r) {
|
||||
yield $r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function buildAssistantDeptIndex(array $adminIds): array
|
||||
{
|
||||
if ($adminIds === []) {
|
||||
return [[], []];
|
||||
}
|
||||
// admin_dept 为 (admin_id, dept_id) 联合主键、无自增 id;取最小 dept_id 作为归属部门保证确定性
|
||||
$relRows = Db::name('admin_dept')
|
||||
->whereIn('admin_id', $adminIds)
|
||||
->order(['admin_id' => 'asc', 'dept_id' => 'asc'])
|
||||
->field(['admin_id', 'dept_id'])
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$deptNames = Db::name('dept')
|
||||
->whereNull('delete_time')
|
||||
->column('name', 'id');
|
||||
|
||||
$canonical = [];
|
||||
foreach ($relRows as $r) {
|
||||
$aid = (int) ($r['admin_id'] ?? 0);
|
||||
$deptId = (int) ($r['dept_id'] ?? 0);
|
||||
if ($aid <= 0 || $deptId <= 0 || isset($canonical[$aid])) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($deptNames[$deptId])) {
|
||||
continue;
|
||||
}
|
||||
$canonical[$aid] = $deptId;
|
||||
}
|
||||
|
||||
$names = [];
|
||||
foreach ($deptNames as $id => $name) {
|
||||
$names[(int) $id] = (string) $name;
|
||||
}
|
||||
|
||||
return [$canonical, $names];
|
||||
}
|
||||
|
||||
private static function resolveDeptFilterSet(mixed $raw): array
|
||||
{
|
||||
$erSet = DeptLogic::getErCenterSubtreeDeptIdSet();
|
||||
if ($erSet === []) {
|
||||
return [];
|
||||
}
|
||||
$deptFilterIds = self::parseDeptIds($raw);
|
||||
if ($deptFilterIds === []) {
|
||||
return $erSet;
|
||||
}
|
||||
$allowedRoots = [];
|
||||
foreach ($deptFilterIds as $id) {
|
||||
if (isset($erSet[$id])) {
|
||||
$allowedRoots[] = $id;
|
||||
}
|
||||
}
|
||||
if ($allowedRoots === []) {
|
||||
return [];
|
||||
}
|
||||
$expanded = self::expandDeptSubtreeSet($allowedRoots);
|
||||
$out = [];
|
||||
foreach ($expanded as $id => $_) {
|
||||
if (isset($erSet[$id])) {
|
||||
$out[$id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
private static function parseDeptIds(mixed $raw): array
|
||||
{
|
||||
if ($raw === null || $raw === '' || $raw === []) {
|
||||
return [];
|
||||
}
|
||||
$list = \is_array($raw) ? $raw : explode(',', (string) $raw);
|
||||
|
||||
return array_values(array_unique(array_filter(
|
||||
array_map('intval', $list),
|
||||
static fn (int $v): bool => $v > 0
|
||||
)));
|
||||
}
|
||||
|
||||
private static function expandDeptSubtreeSet(array $deptIds): array
|
||||
{
|
||||
$rows = Db::name('dept')
|
||||
->whereNull('delete_time')
|
||||
->field(['id', 'pid'])
|
||||
->select()
|
||||
->toArray();
|
||||
$childrenByPid = [];
|
||||
foreach ($rows as $r) {
|
||||
$childrenByPid[(int) $r['pid']][] = (int) $r['id'];
|
||||
}
|
||||
|
||||
$set = [];
|
||||
$queue = $deptIds;
|
||||
while ($queue !== []) {
|
||||
$id = (int) array_shift($queue);
|
||||
if ($id <= 0 || isset($set[$id])) {
|
||||
continue;
|
||||
}
|
||||
$set[$id] = true;
|
||||
foreach ($childrenByPid[$id] ?? [] as $childId) {
|
||||
$queue[] = $childId;
|
||||
}
|
||||
}
|
||||
|
||||
return $set;
|
||||
}
|
||||
|
||||
private static function fetchRefundOrRejectDiagnosisSet(array $diagIds): array
|
||||
{
|
||||
if ($diagIds === []) {
|
||||
return [];
|
||||
}
|
||||
$out = [];
|
||||
foreach (array_chunk($diagIds, 2000) as $chunk) {
|
||||
$ids = Db::name('tcm_prescription_order')
|
||||
->whereIn('diagnosis_id', $chunk)
|
||||
->whereNull('delete_time')
|
||||
->whereIn('fulfillment_status', [9, 10])
|
||||
->group('diagnosis_id')
|
||||
->column('diagnosis_id');
|
||||
foreach ($ids as $id) {
|
||||
$out[(int) $id] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
private static function fetchOrderSeqRows(array $diagIds, array $fields): array
|
||||
{
|
||||
// 实现已搬至 RevisitVisitSlotService(诊次口径单点维护),此处保留委托以减少 diff
|
||||
return RevisitVisitSlotService::fetchOrderSeqRows($diagIds, $fields);
|
||||
}
|
||||
|
||||
private static function fetchRevisitSlotStartOffsetMap(array $diagIds): array
|
||||
{
|
||||
// 实现已搬至 RevisitVisitSlotService(诊次口径单点维护),此处保留委托以减少 diff
|
||||
return RevisitVisitSlotService::fetchRevisitSlotStartOffsetMap($diagIds);
|
||||
}
|
||||
|
||||
private static function fetchDiagnosisInfo(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;
|
||||
}
|
||||
|
||||
private static function cohortDeptOptions(): array
|
||||
{
|
||||
$erSet = DeptLogic::getErCenterSubtreeDeptIdSet();
|
||||
if ($erSet === []) {
|
||||
return ['rows' => []];
|
||||
}
|
||||
$rows = Db::name('dept')
|
||||
->whereNull('delete_time')
|
||||
->whereIn('id', array_keys($erSet))
|
||||
->field(['id', 'pid', 'name'])
|
||||
->order('sort', 'desc')
|
||||
->order('id', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return [
|
||||
'rows' => array_map(static fn (array $r): array => [
|
||||
'id' => (int) $r['id'],
|
||||
'pid' => (int) $r['pid'],
|
||||
'name' => (string) $r['name'],
|
||||
], $rows),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\logic\stats;
|
||||
|
||||
use think\facade\Db;
|
||||
|
||||
/** 指派 / 继承独立报表。旧按月计算器继续服务自动分配与个人业绩。 */
|
||||
class RevisitCohortLogic
|
||||
{
|
||||
use RevisitCohortDataSource;
|
||||
|
||||
public static function overview(array $params, string $type = RevisitCohortCalculator::TYPE_NEW): array
|
||||
{
|
||||
$ctx = self::cohortContext($params, $type);
|
||||
$calc = $ctx['calc'];
|
||||
$aids = array_keys($calc['pairsByAssistant']);
|
||||
$names = $aids === [] ? [] : Db::name('admin')->whereIn('id', $aids)->column('name', 'id');
|
||||
$maxSlot = 4;
|
||||
foreach ($calc['ordersByAssistant'] as $bySlot) {
|
||||
foreach ($bySlot as $slot => $_) {
|
||||
$maxSlot = max($maxSlot, $slot);
|
||||
}
|
||||
}
|
||||
$slots = range(2, $maxSlot);
|
||||
$byDept = [];
|
||||
foreach ($aids as $aid) {
|
||||
$deptId = $ctx['assistantDept'][$aid];
|
||||
$byDept[$deptId][] = $aid;
|
||||
}
|
||||
$rows = [];
|
||||
foreach ($byDept as $deptId => $deptAids) {
|
||||
$children = [];
|
||||
$deptName = (string) ($ctx['deptNames'][$deptId] ?? ('#' . $deptId));
|
||||
foreach ($deptAids as $aid) {
|
||||
$children[] = [
|
||||
'row_key' => 'a' . $aid,
|
||||
'is_dept' => 0,
|
||||
'dept_id' => $deptId,
|
||||
'dept_name' => $deptName,
|
||||
'assistant_id' => $aid,
|
||||
'assistant_name' => (string) ($names[$aid] ?? ('#' . $aid)),
|
||||
] + RevisitCohortCalculator::metricPack($calc, [$aid], $slots);
|
||||
}
|
||||
usort($children, static fn (array $a, array $b): int =>
|
||||
($b['assigned_total_count'] <=> $a['assigned_total_count'])
|
||||
?: strcmp($a['assistant_name'], $b['assistant_name']));
|
||||
$rows[] = [
|
||||
'row_key' => 'd' . $deptId,
|
||||
'is_dept' => 1,
|
||||
'dept_id' => $deptId,
|
||||
'dept_name' => $deptName,
|
||||
'assistant_count' => \count($children),
|
||||
'children' => $children,
|
||||
] + RevisitCohortCalculator::metricPack($calc, $deptAids, $slots);
|
||||
}
|
||||
usort($rows, static fn (array $a, array $b): int =>
|
||||
($b['assigned_total_count'] <=> $a['assigned_total_count'])
|
||||
?: strcmp($a['dept_name'], $b['dept_name']));
|
||||
|
||||
return self::metadata($ctx) + [
|
||||
'slots' => $slots,
|
||||
'total' => RevisitCohortCalculator::metricPack($calc, $aids, $slots),
|
||||
'rows' => $rows,
|
||||
];
|
||||
}
|
||||
|
||||
public static function assignLines(array $params, string $type = RevisitCohortCalculator::TYPE_NEW): array
|
||||
{
|
||||
$ctx = self::cohortContext($params, $type);
|
||||
$aids = self::scopedAssistantIds($ctx, $params);
|
||||
$byDiag = [];
|
||||
foreach ($aids as $aid) {
|
||||
foreach ($ctx['calc']['pairsByAssistant'][$aid] ?? [] as $did => $pair) {
|
||||
if (!isset($byDiag[$did])) {
|
||||
$byDiag[$did] = $pair + ['assistants' => []];
|
||||
$byDiag[$did]['assign_count'] = 0;
|
||||
}
|
||||
$byDiag[$did]['assistants'][$aid] = true;
|
||||
$byDiag[$did]['assign_count'] += $pair['assign_count'];
|
||||
$byDiag[$did]['first_assign_time'] = min($byDiag[$did]['first_assign_time'], $pair['first_assign_time']);
|
||||
$byDiag[$did]['last_assign_time'] = max($byDiag[$did]['last_assign_time'], $pair['last_assign_time']);
|
||||
$byDiag[$did]['is_rollback'] |= $pair['is_rollback'];
|
||||
}
|
||||
}
|
||||
$diagInfo = self::fetchDiagnosisInfo(array_keys($byDiag));
|
||||
$names = $aids === [] ? [] : Db::name('admin')->whereIn('id', $aids)->column('name', 'id');
|
||||
$rows = [];
|
||||
foreach ($byDiag as $did => $pair) {
|
||||
$assistantNames = [];
|
||||
foreach ($pair['assistants'] as $aid => $_) {
|
||||
$assistantNames[] = (string) ($names[$aid] ?? ('#' . $aid));
|
||||
}
|
||||
$rows[] = [
|
||||
'diagnosis_id' => $did,
|
||||
'patient_name' => (string) ($diagInfo[$did]['patient_name'] ?? ''),
|
||||
'patient_phone' => (string) ($diagInfo[$did]['phone'] ?? ''),
|
||||
'assistant_names' => implode('、', $assistantNames),
|
||||
'assign_count' => $pair['assign_count'],
|
||||
'first_assign_time_text' => date('Y-m-d H:i:s', $pair['first_assign_time']),
|
||||
'last_assign_time' => $pair['last_assign_time'],
|
||||
'last_assign_time_text' => date('Y-m-d H:i:s', $pair['last_assign_time']),
|
||||
'assign_type' => $ctx['calc']['cohort_type'],
|
||||
'is_rollback' => $pair['is_rollback'],
|
||||
];
|
||||
}
|
||||
usort($rows, static fn (array $a, array $b): int => $b['last_assign_time'] <=> $a['last_assign_time']);
|
||||
|
||||
return self::metadata($ctx) + ['count' => \count($rows), 'rows' => $rows];
|
||||
}
|
||||
|
||||
public static function visitOrderLines(array $params, string $type = RevisitCohortCalculator::TYPE_NEW): array
|
||||
{
|
||||
$slot = (int) ($params['slot'] ?? 0);
|
||||
if ($slot < 0 || $slot === 1 || $slot > RevisitRateCalculator::MAX_VISIT_SLOT) {
|
||||
throw new \InvalidArgumentException('诊次须为 2~50,或 0(复诊合计)');
|
||||
}
|
||||
$ctx = self::cohortContext($params, $type);
|
||||
$orders = RevisitCohortCalculator::scopedOrders($ctx['calc'], self::scopedAssistantIds($ctx, $params), $slot);
|
||||
$flat = [];
|
||||
$adminIds = [];
|
||||
$diagIds = [];
|
||||
foreach ($orders as $byDiag) {
|
||||
foreach ($byDiag as $did => $r) {
|
||||
$flat[] = $r;
|
||||
$diagIds[$did] = true;
|
||||
$adminIds[(int) $r['assistant_id']] = true;
|
||||
if ((int) ($r['creator_id'] ?? 0) > 0) {
|
||||
$adminIds[(int) $r['creator_id']] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$diagInfo = self::fetchDiagnosisInfo(array_keys($diagIds));
|
||||
$names = $adminIds === [] ? [] : Db::name('admin')->whereIn('id', array_keys($adminIds))->column('name', 'id');
|
||||
$rows = [];
|
||||
foreach ($flat as $r) {
|
||||
$did = (int) $r['diagnosis_id'];
|
||||
$aid = (int) $r['assistant_id'];
|
||||
$cid = (int) ($r['creator_id'] ?? 0);
|
||||
$rows[] = [
|
||||
'order_id' => (int) $r['id'],
|
||||
'order_no' => (string) ($r['order_no'] ?? ''),
|
||||
'diagnosis_id' => $did,
|
||||
'patient_name' => (string) ($diagInfo[$did]['patient_name'] ?? ''),
|
||||
'patient_phone' => (string) ($diagInfo[$did]['phone'] ?? ''),
|
||||
'amount' => round((float) ($r['amount'] ?? 0), 2),
|
||||
'create_time' => (int) $r['create_time'],
|
||||
'create_time_text' => date('Y-m-d H:i:s', (int) $r['create_time']),
|
||||
'assistant_id' => $aid,
|
||||
'assistant_name' => (string) ($names[$aid] ?? ('#' . $aid)),
|
||||
'creator_name' => $cid > 0 ? (string) ($names[$cid] ?? ('#' . $cid)) : '—',
|
||||
'visit_slot' => (int) $r['visit_slot'],
|
||||
'global_visit_slot' => (int) $r['global_visit_slot'],
|
||||
'cohort_assign_time_text' => date('Y-m-d H:i:s', (int) $r['cohort_assign_time']),
|
||||
];
|
||||
}
|
||||
usort($rows, static fn (array $a, array $b): int => $b['create_time'] <=> $a['create_time']);
|
||||
|
||||
return self::metadata($ctx) + ['slot' => $slot, 'count' => \count($rows), 'rows' => $rows];
|
||||
}
|
||||
|
||||
/** 保留部门下拉结构,补充二中心内医助;不影响复诊到期页对父类 deptOptions 的复用。 */
|
||||
public static function deptOptions(): array
|
||||
{
|
||||
$options = self::cohortDeptOptions();
|
||||
$deptSet = self::resolveDeptFilterSet(null);
|
||||
if ($deptSet === []) {
|
||||
return $options + ['assistants' => []];
|
||||
}
|
||||
$ids = Db::name('admin_dept')->whereIn('dept_id', array_keys($deptSet))->group('admin_id')->column('admin_id');
|
||||
[$assistantDept] = self::buildAssistantDeptIndex($ids);
|
||||
$admins = $ids === [] ? [] : Db::name('admin')->whereIn('id', $ids)->whereNull('delete_time')
|
||||
->field(['id', 'name'])->order('name', 'asc')->select()->toArray();
|
||||
$assistants = [];
|
||||
foreach ($admins as $admin) {
|
||||
$deptId = (int) ($assistantDept[(int) $admin['id']] ?? 0);
|
||||
if (isset($deptSet[$deptId])) {
|
||||
$assistants[] = ['id' => (int) $admin['id'], 'name' => (string) $admin['name'], 'dept_id' => $deptId];
|
||||
}
|
||||
}
|
||||
|
||||
return $options + ['assistants' => $assistants];
|
||||
}
|
||||
|
||||
private static function cohortContext(array $params, string $type): array
|
||||
{
|
||||
$now = time();
|
||||
[$start, $end, $startTs, $endTs] = RevisitCohortCalculator::dateRange($params, $now);
|
||||
// 先按指派周期圈定诊单,再读取其完整时间线;避免小范围查询拉取全库历史日志。
|
||||
$candidateQuery = Db::name('tcm_diagnosis_assign_log')->alias('lg')
|
||||
->join('tcm_diagnosis dg', 'dg.id = lg.diagnosis_id AND dg.delete_time IS NULL', 'INNER')
|
||||
->where('lg.create_time', '>=', $startTs)->where('lg.create_time', '<=', min($endTs, $now))
|
||||
->where('lg.to_assistant_id', '>', 0)->where('lg.diagnosis_id', '>', 0);
|
||||
$filterAid = (int) ($params['assistant_id'] ?? 0);
|
||||
if ($filterAid > 0) {
|
||||
$candidateQuery->where('lg.to_assistant_id', $filterAid);
|
||||
}
|
||||
$candidateIds = array_map('intval', $candidateQuery->group('lg.diagnosis_id')->column('lg.diagnosis_id'));
|
||||
$logs = [];
|
||||
foreach (array_chunk($candidateIds, 2000) as $chunk) {
|
||||
$chunkLogs = Db::name('tcm_diagnosis_assign_log')->alias('lg')
|
||||
->whereIn('lg.diagnosis_id', $chunk)->where('lg.create_time', '<=', $now)
|
||||
->field(['lg.id', 'lg.diagnosis_id', 'lg.from_assistant_id', 'lg.to_assistant_id',
|
||||
'lg.operator_admin_id', 'lg.create_time', 'lg.is_inherit'])
|
||||
->order(['lg.diagnosis_id' => 'asc', 'lg.create_time' => 'asc', 'lg.id' => 'asc'])
|
||||
->select()->toArray();
|
||||
array_push($logs, ...$chunkLogs);
|
||||
}
|
||||
$calc = RevisitCohortCalculator::compute(
|
||||
$logs,
|
||||
self::iterateOrderSeqRows($candidateIds, ['o.id', 'o.order_no', 'o.diagnosis_id', 'o.create_time', 'o.amount', 'o.creator_id']),
|
||||
self::fetchRevisitSlotStartOffsetMap($candidateIds),
|
||||
self::fetchRefundOrRejectDiagnosisSet($candidateIds),
|
||||
self::fetchRollbackKeySet($startTs, min($endTs, $now)),
|
||||
$startTs, $endTs, $now, $type
|
||||
);
|
||||
[$assistantDept, $deptNames] = self::buildAssistantDeptIndex(array_keys($calc['pairsByAssistant']));
|
||||
$deptSet = self::resolveDeptFilterSet($params['dept_ids'] ?? null);
|
||||
foreach (array_keys($calc['pairsByAssistant']) as $aid) {
|
||||
if (!isset($deptSet[$assistantDept[$aid] ?? 0]) || ($filterAid > 0 && $filterAid !== $aid)) {
|
||||
unset($calc['pairsByAssistant'][$aid], $calc['ordersByAssistant'][$aid]);
|
||||
}
|
||||
}
|
||||
|
||||
return ['calc' => $calc, 'assistantDept' => $assistantDept, 'deptNames' => $deptNames,
|
||||
'start_date' => $start, 'end_date' => $end, 'observed_until' => date('Y-m-d H:i:s', $now)];
|
||||
}
|
||||
|
||||
private static function scopedAssistantIds(array $ctx, array $params): array
|
||||
{
|
||||
$aids = array_keys($ctx['calc']['pairsByAssistant']);
|
||||
$scopeAid = (int) ($params['scope_assistant_id'] ?? 0);
|
||||
if ($scopeAid > 0) {
|
||||
return \in_array($scopeAid, $aids, true) ? [$scopeAid] : [];
|
||||
}
|
||||
if (isset($params['dept_id']) && $params['dept_id'] !== '') {
|
||||
$deptId = (int) $params['dept_id'];
|
||||
$aids = array_values(array_filter($aids, static fn (int $aid): bool => ($ctx['assistantDept'][$aid] ?? 0) === $deptId));
|
||||
}
|
||||
|
||||
return $aids;
|
||||
}
|
||||
|
||||
private static function metadata(array $ctx): array
|
||||
{
|
||||
return ['start_date' => $ctx['start_date'], 'end_date' => $ctx['end_date'],
|
||||
'observed_until' => $ctx['observed_until'], 'cohort_type' => $ctx['calc']['cohort_type']];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user