diff --git a/admin/src/views/tcm/diagnosis/edit.vue b/admin/src/views/tcm/diagnosis/edit.vue
index 3582f2b4..f405eb33 100644
--- a/admin/src/views/tcm/diagnosis/edit.vue
+++ b/admin/src/views/tcm/diagnosis/edit.vue
@@ -67,17 +67,37 @@
>
基本信息
+
+
+
+ 最近业务订单 #{{ latestPrescriptionOrder.id }} 状态为「{{ latestPrescriptionOrder.fulfillment_status_text }}」,患者基本信息不可修改
+
+
+ 存在未完成的业务订单,患者基本信息不可修改
+
+
+
@@ -778,8 +803,24 @@ const submitting = ref(false)
/** 拥有后手机号可正常编辑,失焦不再强制脱敏 */
const hasPhonePlainPermission = computed(() => hasPermission(['tcm.diagnosis/phonePlain']))
const hasDailyRecordPermission = computed(() => hasPermission(['tcm.diagnosis/dailyRecord']))
-/** 编辑且无明文权限:只读脱敏,点击切换查看完整号 */
-const showPhoneMaskedEdit = computed(() => mode.value === 'edit' && !hasPhonePlainPermission.value)
+const patientBasicLocked = ref(false)
+const canEditPatientBasicFromApi = ref(true)
+const latestPrescriptionOrder = ref<{
+ id: number
+ fulfillment_status: number
+ fulfillment_status_text: string
+} | null>(null)
+const canEditPatientBasicFields = computed(() => {
+ if (viewOnly.value) return false
+ if (mode.value === 'add') return true
+ return canEditPatientBasicFromApi.value
+})
+/** 编辑且无明文权限或基本信息锁定:只读脱敏,点击切换查看完整号 */
+const showPhoneMaskedEdit = computed(() => {
+ if (mode.value !== 'edit') return false
+ if (!canEditPatientBasicFields.value) return true
+ return !hasPhonePlainPermission.value
+})
const phoneRevealUnlocked = ref(false)
const activeTab = ref('basic')
const drawerTitle = computed(() => {
@@ -1167,6 +1208,9 @@ async function loadDiagnosisDetailIntoForm(id: number): Promise {
originalPhone.value = data.phone || ''
originalIdCard.value = data.id_card || ''
+ patientBasicLocked.value = !!data.patient_basic_locked
+ canEditPatientBasicFromApi.value = data.can_edit_patient_basic !== false
+ latestPrescriptionOrder.value = data.latest_prescription_order ?? null
data.phone = hasPhonePlainPermission.value ? data.phone || '' : maskPhone(data.phone || '')
data.id_card = maskIdCard(data.id_card || '')
@@ -1178,12 +1222,19 @@ async function loadDiagnosisDetailIntoForm(id: number): Promise {
formData.value.diabetes_discovery_year = y == null || y === '' ? '' : String(y)
}
+const resetPatientBasicLockState = () => {
+ patientBasicLocked.value = false
+ canEditPatientBasicFromApi.value = true
+ latestPrescriptionOrder.value = null
+}
+
const open = async (type: string, id?: number) => {
viewOnly.value = false
mode.value = type
visible.value = true
activeTab.value = 'basic' // 重置到基本信息标签页
phoneRevealUnlocked.value = false
+ resetPatientBasicLockState()
// 加载字典数据
await getDictOptions()
@@ -1206,6 +1257,7 @@ const openViewOnly = async (id: number) => {
visible.value = true
activeTab.value = 'basic'
phoneRevealUnlocked.value = false
+ resetPatientBasicLockState()
await getDictOptions()
await loadDiagnosisDetailIntoForm(id)
}
@@ -1370,6 +1422,19 @@ defineExpose({
}
}
+ .diagnosis-patient-basic-fieldset:disabled {
+ opacity: 1;
+ :deep(.el-input__wrapper),
+ :deep(.el-input-number),
+ :deep(.el-input-number__decrease),
+ :deep(.el-input-number__increase),
+ :deep(.el-radio),
+ :deep(.el-radio__input) {
+ cursor: not-allowed;
+ pointer-events: none;
+ }
+ }
+
:deep(.el-form) {
padding-bottom: 20px;
font-size: 12px;
diff --git a/server/app/adminapi/controller/tcm/DiagnosisController.php b/server/app/adminapi/controller/tcm/DiagnosisController.php
index d1eb592f..2d1fe24a 100755
--- a/server/app/adminapi/controller/tcm/DiagnosisController.php
+++ b/server/app/adminapi/controller/tcm/DiagnosisController.php
@@ -87,7 +87,7 @@ class DiagnosisController extends BaseAdminController
public function edit()
{
$params = (new DiagnosisValidate())->post()->goCheck('edit');
- $result = DiagnosisLogic::edit($params);
+ $result = DiagnosisLogic::edit($params, $this->adminInfo);
if ($result) {
return $this->success('编辑成功', [], 1, 1);
}
@@ -116,7 +116,7 @@ class DiagnosisController extends BaseAdminController
{
$params = (new DiagnosisValidate())->goCheck('id');
- $result = DiagnosisLogic::detail($params);
+ $result = DiagnosisLogic::detail($params, $this->adminInfo);
DiagnosisLogic::markAssignRead((int) ($params['id'] ?? 0), $this->adminId);
return $this->data($result);
}
diff --git a/server/app/adminapi/logic/tcm/DiagnosisLogic.php b/server/app/adminapi/logic/tcm/DiagnosisLogic.php
index dbe8e5e1..dfdb4d12 100755
--- a/server/app/adminapi/logic/tcm/DiagnosisLogic.php
+++ b/server/app/adminapi/logic/tcm/DiagnosisLogic.php
@@ -28,6 +28,7 @@ use app\common\model\DiagnosisViewRecord;
use app\common\model\doctor\Appointment;
use app\common\model\auth\Admin;
use app\common\model\auth\AdminRole;
+use app\adminapi\logic\auth\AuthLogic;
use app\adminapi\logic\doctor\DoctorNoteLogic;
use app\adminapi\logic\doctor\AppointmentLogic;
use app\adminapi\logic\tcm\TrackingNoteLogic;
@@ -73,8 +74,8 @@ class DiagnosisLogic extends BaseLogic
}
}
- // 生成患者ID
- $params['patient_id'] = self::generatePatientId();
+ // 新患者先占位 0,写入后 patient_id 与自增 id 对齐
+ $params['patient_id'] = 0;
// 处理既往史数组
if (isset($params['past_history']) && is_array($params['past_history'])) {
@@ -119,6 +120,7 @@ class DiagnosisLogic extends BaseLogic
}
$model = Diagnosis::create($params);
+ $model = self::syncPatientIdWithDiagnosisId($model);
// 图片写入 doctor_note 表
if (!empty($newTongueImages) || !empty($newReportFiles)) {
@@ -130,7 +132,7 @@ class DiagnosisLogic extends BaseLogic
}
// 自动为患者创建 TRTC 账号
- self::createPatientTrtcAccount($model->id);
+ self::createPatientTrtcAccount($model->patient_id);
return $model->id;
} catch (\Exception $e) {
@@ -140,16 +142,20 @@ class DiagnosisLogic extends BaseLogic
}
/**
- * @notes 生成患者ID
- * @return int
+ * @notes 新患者首张诊单:patient_id 与自增 id 对齐
+ * @param Diagnosis $model
+ * @return Diagnosis
*/
- private static function generatePatientId(): int
+ private static function syncPatientIdWithDiagnosisId(Diagnosis $model): Diagnosis
{
- // 获取当前最大的患者ID
- $maxPatientId = Diagnosis::max('id') ?? 10000000;
-
- // 返回下一个患者ID
- return $maxPatientId + 1;
+ if ((int) $model->patient_id === (int) $model->id) {
+ return $model;
+ }
+
+ Diagnosis::where('id', $model->id)->update(['patient_id' => $model->id]);
+ $model->patient_id = (int) $model->id;
+
+ return $model;
}
/**
@@ -157,9 +163,23 @@ class DiagnosisLogic extends BaseLogic
* @param array $params
* @return bool
*/
- public static function edit(array $params): bool
+ /** 诊单编辑:患者基本信息字段(姓名/身份证/手机/性别/年龄) */
+ private const PATIENT_BASIC_FIELDS = ['patient_name', 'id_card', 'phone', 'gender', 'age'];
+
+ public static function edit(array $params, array $adminInfo = []): bool
{
try {
+ if (!empty($params['id']) && $adminInfo !== []) {
+ $existing = Diagnosis::find((int) $params['id']);
+ if ($existing && !self::canEditPatientBasicInfo((int) $params['id'], $adminInfo)) {
+ if (self::patientBasicFieldsChanged($existing, $params)) {
+ self::setError('最近业务订单未完成,无法修改患者基本信息');
+
+ return false;
+ }
+ }
+ }
+
// 检查手机号是否重复(排除当前记录)
if (!empty($params['phone'])) {
$exists = Diagnosis::where('phone', $params['phone'])
@@ -250,7 +270,7 @@ class DiagnosisLogic extends BaseLogic
* @param $params
* @return array
*/
- public static function detail($params): array
+ public static function detail($params, array $adminInfo = []): array
{
$diagnosis = Diagnosis::findOrEmpty($params['id'])->toArray();
@@ -317,7 +337,17 @@ class DiagnosisLogic extends BaseLogic
$diagnosis['local_hospital_visit_date'] = $diagnosis['diagnosis_date'];
}
$diagnosis['is_view'] =DiagnosisViewRecord::where(['diagnosis_id'=> $diagnosis['id'], 'user_id' =>$params['user_id']??0])->find() ? 1 : 0;
-
+
+ $latestPo = self::getLatestPrescriptionOrderRowForDiagnosis((int) ($diagnosis['id'] ?? 0));
+ $patientBasicLocked = $latestPo !== null && (int) ($latestPo['fulfillment_status'] ?? 0) !== 3;
+ $diagnosis['patient_basic_locked'] = $patientBasicLocked;
+ $diagnosis['can_edit_patient_basic'] = self::canEditPatientBasicInfo((int) ($diagnosis['id'] ?? 0), $adminInfo);
+ $diagnosis['latest_prescription_order'] = $latestPo !== null ? [
+ 'id' => (int) ($latestPo['id'] ?? 0),
+ 'fulfillment_status' => (int) ($latestPo['fulfillment_status'] ?? 0),
+ 'fulfillment_status_text'=> PrescriptionOrderLogic::fulfillmentStatusLabel((int) ($latestPo['fulfillment_status'] ?? 0)),
+ ] : null;
+
return $diagnosis;
}
@@ -486,11 +516,11 @@ class DiagnosisLogic extends BaseLogic
}
/**
- * 诊单下用于指派日志快照的「最新」业务单:按 create_time 最新,其次 id(避免误用更早下单但 id 更大的记录)。
+ * 诊单下「最新」业务单:按 create_time 最新,其次 id(避免误用更早下单但 id 更大的记录)。
*
- * @return array{creator_id: int, create_time: int}|null
+ * @return array|null
*/
- private static function getLatestPrescriptionOrderSnapshotForDiagnosis(int $diagnosisId): ?array
+ private static function getLatestPrescriptionOrderRowForDiagnosis(int $diagnosisId): ?array
{
if ($diagnosisId <= 0) {
return null;
@@ -500,19 +530,88 @@ class DiagnosisLogic extends BaseLogic
->whereNull('delete_time')
->order('create_time', 'desc')
->order('id', 'desc')
- ->field(['creator_id', 'create_time'])
+ ->field(['id', 'creator_id', 'create_time', 'fulfillment_status'])
->find();
if ($row === null || $row === []) {
return null;
}
+ return $row;
+ }
+
+ /**
+ * 诊单下用于指派日志快照的「最新」业务单:按 create_time 最新,其次 id(避免误用更早下单但 id 更大的记录)。
+ *
+ * @return array{creator_id: int, create_time: int}|null
+ */
+ private static function getLatestPrescriptionOrderSnapshotForDiagnosis(int $diagnosisId): ?array
+ {
+ $row = self::getLatestPrescriptionOrderRowForDiagnosis($diagnosisId);
+ if ($row === null) {
+ return null;
+ }
+
return [
'creator_id' => (int) ($row['creator_id'] ?? 0),
'create_time' => (int) ($row['create_time'] ?? 0),
];
}
+ /**
+ * 菜单权限 tcm.diagnosis/editPatientBasic:最近业务订单未完成时仍可改患者基本信息
+ */
+ public static function hasEditPatientBasicPermission(array $adminInfo): bool
+ {
+ if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
+ return true;
+ }
+ $perms = AuthLogic::getAuthByAdminId((int) ($adminInfo['admin_id'] ?? 0));
+
+ return in_array('tcm.diagnosis/editPatientBasic', $perms, true);
+ }
+
+ /**
+ * 是否允许编辑诊单患者基本信息(无业务单 / 最近一单已完成 / 具备 editPatientBasic 权限)
+ */
+ public static function canEditPatientBasicInfo(int $diagnosisId, array $adminInfo): bool
+ {
+ $latest = self::getLatestPrescriptionOrderRowForDiagnosis($diagnosisId);
+ if ($latest === null) {
+ return true;
+ }
+ if ((int) ($latest['fulfillment_status'] ?? 0) === 3) {
+ return true;
+ }
+
+ return self::hasEditPatientBasicPermission($adminInfo);
+ }
+
+ /**
+ * @param Diagnosis $existing
+ */
+ private static function patientBasicFieldsChanged($existing, array $params): bool
+ {
+ foreach (self::PATIENT_BASIC_FIELDS as $field) {
+ if (!array_key_exists($field, $params)) {
+ continue;
+ }
+ $old = $existing->{$field};
+ $new = $params[$field];
+ if ($field === 'age' || $field === 'gender') {
+ if ((int) $old !== (int) $new) {
+ return true;
+ }
+ continue;
+ }
+ if ((string) $old !== (string) $new) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/**
* 诊单 current assistant_id 为 0 时,从最近一条指派日志推断「原医助」(如发货释放后:上一条多为 from=X、to=0)。
* - 最近一条 to_assistant_id > 0:视为上一任持有人(库未同步时的兜底)
@@ -3092,8 +3191,9 @@ class DiagnosisLogic extends BaseLogic
unset($params['user_id']); // 诊单表无此字段,仅用于创建 view_record
$patientId = $params['patient_id'] ?? 0;
- if (!$patientId) {
- $params['patient_id'] = self::generatePatientId();
+ $isNewPatient = !$patientId;
+ if ($isNewPatient) {
+ $params['patient_id'] = 0;
}
$params['status'] = 1;
@@ -3123,6 +3223,9 @@ class DiagnosisLogic extends BaseLogic
}
$model = Diagnosis::create($params);
+ if ($isNewPatient) {
+ $model = self::syncPatientIdWithDiagnosisId($model);
+ }
self::createPatientTrtcAccount($model->patient_id);
// 图片写入 doctor_note
diff --git a/server/sql/1.9.20260613/add_diagnosis_edit_patient_basic_menu.sql b/server/sql/1.9.20260613/add_diagnosis_edit_patient_basic_menu.sql
new file mode 100644
index 00000000..33f1e666
--- /dev/null
+++ b/server/sql/1.9.20260613/add_diagnosis_edit_patient_basic_menu.sql
@@ -0,0 +1,15 @@
+-- 诊单编辑页:最近业务订单未完成时仍可修改患者基本信息(姓名/身份证/手机/性别/年龄)
+-- 接口:tcm.diagnosis/edit(后端校验);前端 edit.vue 同 perms 控制表单可编辑
+SET @diag_menu_id := (SELECT id FROM zyt_system_menu WHERE perms = 'tcm.diagnosis/lists' LIMIT 1);
+
+INSERT INTO zyt_system_menu (
+ pid, type, name, icon, sort, perms, paths, component,
+ selected, params, is_cache, is_show, is_disable, create_time, update_time
+)
+SELECT
+ @diag_menu_id, 'A', '未完成订单改患者信息', '', 22,
+ 'tcm.diagnosis/editPatientBasic', '', '',
+ '', '', 0, 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
+FROM DUAL
+WHERE @diag_menu_id IS NOT NULL
+ AND NOT EXISTS (SELECT 1 FROM zyt_system_menu WHERE perms = 'tcm.diagnosis/editPatientBasic');