From dddc4d202abcb4502560f5f22eb03dbeaff68664 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 6 May 2026 11:17:31 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/src/views/tcm/diagnosis/index.vue | 69 +++++++++++++++++-- .../app/adminapi/lists/tcm/DiagnosisLists.php | 45 ++++++++++++ .../common/service/ExpressTrackingService.php | 63 +++++++++++++++++ 3 files changed, 170 insertions(+), 7 deletions(-) diff --git a/admin/src/views/tcm/diagnosis/index.vue b/admin/src/views/tcm/diagnosis/index.vue index 7f7a3444..dca0e1e5 100644 --- a/admin/src/views/tcm/diagnosis/index.vue +++ b/admin/src/views/tcm/diagnosis/index.vue @@ -37,12 +37,26 @@ {{ completedVisitCount }} - 待分配医助 - {{ pendingAssignCount }} + + 待分配医助 + {{ pendingAssignCount }} + +
@@ -705,7 +719,9 @@ const formData = reactive({ /** 顶部「待预约」Tab:仅展示已建诊单且尚未挂号(无有效预约) */ pending_booking: '' as '' | '1', /** 顶部「已完成」Tab:至少有一条挂号记录为已完成 status=3 */ - completed_appointment: '' as '' | '1' + completed_appointment: '' as '' | '1', + /** 待分配医助:按业务订单创建月份筛选 YYYY-MM,空表示不限月份 */ + pending_assign_order_month: '' as string }) const activeTab = ref('all') @@ -763,6 +779,7 @@ const pendingAssignCount = ref(undefined) const handleDateTabClick = async (value: string) => { formData.pending_assign = '' + formData.pending_assign_order_month = '' formData.pending_booking = '' formData.completed_appointment = '' formData.has_appointment = '' @@ -775,6 +792,7 @@ const handleDateTabClick = async (value: string) => { /** 已创建诊单、尚未挂号的记录(与第二行「未挂号」同条件,便于从 Tab 一键进入) */ const handlePendingBookingTabClick = async () => { formData.pending_assign = '' + formData.pending_assign_order_month = '' formData.pending_booking = '1' formData.completed_appointment = '' formData.appointment_date = '' @@ -787,6 +805,7 @@ const handlePendingBookingTabClick = async () => { /** 至少有一条挂号为「已完成」(后端 appointment.status=3) */ const handleCompletedVisitTabClick = async () => { formData.pending_assign = '' + formData.pending_assign_order_month = '' formData.pending_booking = '' formData.completed_appointment = '1' formData.has_appointment = '' @@ -802,6 +821,18 @@ const handlePendingAssignTabClick = async () => { formData.completed_appointment = '' formData.has_appointment = '' formData.appointment_date = '' + if (!formData.pending_assign_order_month) { + formData.pending_assign_order_month = dayjs().format('YYYY-MM') + } + pager.page = 1 + await getLists() + fetchDateCounts() +} + +/** 待分配:切换订单月份后刷新列表与角标 */ +const onPendingAssignOrderMonthChange = async (val: string | null) => { + formData.pending_assign_order_month = val || '' + if (formData.pending_assign !== '1') return pager.page = 1 await getLists() fetchDateCounts() @@ -819,7 +850,14 @@ const fetchDateCounts = async () => { tcmDiagnosisLists({ page_no: 1, page_size: 1 }), tcmDiagnosisLists({ has_appointment: 0, page_no: 1, page_size: 1 }), tcmDiagnosisLists({ completed_appointment: 1, page_no: 1, page_size: 1 }), - tcmDiagnosisLists({ pending_assign: 1, page_no: 1, page_size: 1 }) + tcmDiagnosisLists({ + pending_assign: 1, + page_no: 1, + page_size: 1, + ...(formData.pending_assign === '1' && formData.pending_assign_order_month + ? { pending_assign_order_month: formData.pending_assign_order_month } + : {}) + }) ]) dateCounts.value = { [yesterdayStr.value]: yesterday?.count ?? 0, @@ -971,6 +1009,7 @@ const handleReset = () => { formData.appointment_date = '' formData.has_appointment = '' formData.pending_assign = '' + formData.pending_assign_order_month = '' formData.pending_booking = '' formData.completed_appointment = '' pager.page = 1 @@ -1939,6 +1978,22 @@ onUnmounted(() => { background: var(--el-color-warning); } } + + .pending-assign-wrap { + display: inline-flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + margin-left: 8px; + + .chip-pending { + margin-left: 0; + } + } + + .pending-assign-month { + width: 128px; + } } .search-row { diff --git a/server/app/adminapi/lists/tcm/DiagnosisLists.php b/server/app/adminapi/lists/tcm/DiagnosisLists.php index 846218b3..5488fbe9 100644 --- a/server/app/adminapi/lists/tcm/DiagnosisLists.php +++ b/server/app/adminapi/lists/tcm/DiagnosisLists.php @@ -19,6 +19,7 @@ use app\adminapi\logic\dept\DeptLogic; use app\common\model\tcm\Diagnosis; use app\common\model\tcm\BloodRecord; use app\common\model\DiagnosisViewRecord; +use app\common\model\Order; use app\common\model\doctor\Appointment; use app\common\model\tcm\Prescription; use app\common\model\tcm\CallRecord; @@ -130,6 +131,8 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface $query->whereExists("SELECT 1 FROM {$aptTbl} apt WHERE apt.patient_id = {$diagTbl}.id AND apt.status = 3"); } + $this->applyPendingAssignBusinessOrderMonthFilter($query); + // 仅已开方(有处方)的诊单:随访列表传 only_has_prescription=1;问诊等页面不传则不过滤 if (isset($this->params['only_has_prescription']) && (string) $this->params['only_has_prescription'] === '1') { $rxTbl = (new Prescription())->getTable(); @@ -541,7 +544,49 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface $diagTbl = (new Diagnosis())->getTable(); $query->whereExists("SELECT 1 FROM {$aptTbl} apt WHERE apt.patient_id = {$diagTbl}.id AND apt.status = 3"); } + + $this->applyPendingAssignBusinessOrderMonthFilter($query); return $query->count(); } + + /** + * 「待分配医助」Tab:按月份筛选——诊单在指定自然月内需存在业务订单(order.patient_id = 诊单 id) + * create_time 兼容整型时间戳与 datetime 字符串 + */ + private function applyPendingAssignBusinessOrderMonthFilter($query): void + { + $pendingAssign = isset($this->params['pending_assign']) && (string) $this->params['pending_assign'] === '1'; + if (!$pendingAssign || empty($this->params['pending_assign_order_month'])) { + return; + } + $month = trim((string) $this->params['pending_assign_order_month']); + if (!preg_match('/^(\d{4})-(\d{2})$/', $month, $m)) { + return; + } + $y = (int) $m[1]; + $mo = (int) $m[2]; + if ($y < 1970 || $y > 2100 || $mo < 1 || $mo > 12) { + return; + } + $tStart = strtotime(sprintf('%04d-%02d-01 00:00:00', $y, $mo)); + if ($tStart === false) { + return; + } + $tEnd = strtotime(date('Y-m-t 23:59:59', $tStart)); + if ($tEnd === false) { + return; + } + $dsStart = date('Y-m-d H:i:s', $tStart); + $dsEnd = date('Y-m-d H:i:s', $tEnd); + + $orderTbl = (new Order())->getTable(); + $diagTbl = (new Diagnosis())->getTable(); + $sql = "SELECT 1 FROM `{$orderTbl}` o WHERE o.patient_id = {$diagTbl}.id " + . 'AND o.delete_time IS NULL AND (' + . "(o.create_time >= {$tStart} AND o.create_time <= {$tEnd}) OR " + . "(o.create_time >= '{$dsStart}' AND o.create_time <= '{$dsEnd}')" + . ')'; + $query->whereExists($sql); + } } diff --git a/server/app/common/service/ExpressTrackingService.php b/server/app/common/service/ExpressTrackingService.php index 048e22aa..b2458e70 100644 --- a/server/app/common/service/ExpressTrackingService.php +++ b/server/app/common/service/ExpressTrackingService.php @@ -4,6 +4,9 @@ declare(strict_types=1); namespace app\common\service; +use app\adminapi\logic\dept\DeptLogic; +use app\common\model\auth\AdminDept; +use app\common\model\dept\Dept; use app\common\model\ExpressTracking; use app\common\model\ExpressTrace; use app\common\model\ExpressStateLog; @@ -246,6 +249,7 @@ class ExpressTrackingService * 处方业务订单履约「已发货(5) / 已签收(6)」时:清空关联诊单(患者)医助,并写入 tcm_diagnosis_assign_log。 * 快递 100 定时拉轨迹、甘草路由、甘草回调与履约核对共用同一规则。 * 例外:指派日志中若存在「从某医助改派到另一医助」(原、新医助 ID 均大于 0)的记录,视为人工指派链,自动释放跳过。 + * 例外:患者当前医助在「二中心」及其全部子部门(与后台部门树一致)下任职时,不自动清空医助(按 admin_dept 与 dept 树判定)。 * * @param array{tracking_id?:int,tracking_number?:string,source?:string} $meta * @return array{ @@ -300,6 +304,17 @@ class ExpressTrackingService return null; } + if (self::currentAssistantBelongsToErzhongxinDeptTree($fromAssistantId)) { + Log::info('Skipped auto assistant release (shipped/signed): assistant under 二中心 dept tree', [ + 'diagnosis_id' => $diagnosisId, + 'prescription_order_id' => $orderId, + 'source' => $source, + 'current_assistant_id' => $fromAssistantId, + ]); + + return null; + } + $operatorName = match ($source) { 'express_auto_update' => '系统·物流自动同步', 'gancao_route', 'gancao_route_sync' => '系统·甘草路由同步', @@ -358,6 +373,54 @@ class ExpressTrackingService } } + /** + * 部门名称为「二中心」的根节点 id(启用、未删除的首条),无则 0。 + * 与后台 adminapi 部门数据源一致(表 zyt_dept,树由 pid 构成)。 + */ + private static function getErzhongxinRootDeptId(): int + { + static $cached = null; + if ($cached !== null) { + return $cached; + } + $id = (int) Dept::where('name', '二中心') + ->where('status', 1) + ->whereNull('delete_time') + ->order('id', 'asc') + ->value('id'); + $cached = $id > 0 ? $id : 0; + + return $cached; + } + + /** + * 诊单当前医助(管理员)是否归属「二中心」或其任意下级部门(admin_dept.dept_id 落在该子树内)。 + * 与 DeptLogic::getSelfAndDescendantIds 行为对齐于部门列表接口的树展开。 + */ + private static function currentAssistantBelongsToErzhongxinDeptTree(int $assistantAdminId): bool + { + if ($assistantAdminId <= 0) { + return false; + } + $rootId = self::getErzhongxinRootDeptId(); + if ($rootId <= 0) { + return false; + } + $treeIds = DeptLogic::getSelfAndDescendantIds($rootId); + if ($treeIds === []) { + return false; + } + $allowed = array_flip(array_map('intval', $treeIds)); + $deptIds = AdminDept::where('admin_id', $assistantAdminId)->column('dept_id'); + foreach ($deptIds as $did) { + if (isset($allowed[(int) $did])) { + return true; + } + } + + return false; + } + /** * 是否存在「由原医助改派到新医助」的指派记录(两端 admin 均大于 0)。有此记录则已发货自动释放医助不执行。 */