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)。有此记录则已发货自动释放医助不执行。
*/