diff --git a/app/src/doctor_workstation/ui/pages/prescriptions.py b/app/src/doctor_workstation/ui/pages/prescriptions.py index 742540346..776c1d17a 100644 --- a/app/src/doctor_workstation/ui/pages/prescriptions.py +++ b/app/src/doctor_workstation/ui/pages/prescriptions.py @@ -445,7 +445,22 @@ def _formula(value: Any) -> str: return "辅方" if text in {"2", "aux", "auxiliary", "辅方"} else "主方" -def _order_warnings(row: Any) -> list[str]: +def _is_blank_prescription(row: Any) -> bool: + if row is None or _truthy(get_value(row, "is_system_auto")): + return True + raw = getattr(row, "raw", None) + source = raw if isinstance(raw, Mapping) and raw else row + missing = object() + herbs = get_value(source, "herbs", missing) + # Compact historical rows may omit herbs; omission is not an empty prescription. + if herbs is missing: + return False + return not isinstance(herbs, (list, tuple)) or not any( + str(get_value(herb, "name", "") or "").strip() for herb in herbs + ) + + +def _order_warnings(row: Any) -> list[str]: """Match the PC list's linked-order checks, including blank herb rows.""" raw = getattr(row, "raw", None) @@ -1183,7 +1198,7 @@ class PrescriptionsPage(QWidget): self.view_button = self._action_button("查看", "cf.prescription/read", self._view_selected) toolbar.addWidget(self.view_button) self.ai_report_button = self._action_button("AI 报告", "tcm.prescriptionAi/reports", self._open_ai_report) - self.ai_report_button.setVisible(can_open_issued_ai(self.permissions) and callable(getattr(self.repository, "list_prescription_ai_reports", None))) + self.ai_report_button.hide() toolbar.addWidget(self.ai_report_button) self.patch_button = self._action_button( "修改患者", "tcm.prescription/patchPatient", self._patch_selected @@ -1565,7 +1580,7 @@ class PrescriptionsPage(QWidget): actions_host, ) ) - if self._ai_enabled and can_open_issued_ai(self.permissions): + if self._ai_enabled and can_open_issued_ai(self.permissions) and not _is_blank_prescription(row): actions.addWidget( _row_action_button( "eye", "AI 报告", @@ -1618,6 +1633,8 @@ class PrescriptionsPage(QWidget): if item is None or top + self.table.rowHeight(index) <= 0 or top >= viewport.height(): continue row = item.data(Qt.ItemDataRole.UserRole) + if _is_blank_prescription(row): + continue value = _int(first_value(row, "id", "prescription_id"), 0) if value > 0: ids.append(value) @@ -1628,6 +1645,15 @@ class PrescriptionsPage(QWidget): self._ai_scroll_timer.start() def _load_ai_statuses(self) -> None: + only_blank = self.table.rowCount() > 0 and all( + _is_blank_prescription(self.table.item(index, 0).data(Qt.ItemDataRole.UserRole)) + for index in range(self.table.rowCount()) + ) + self.ai_status_notice.setVisible(not only_blank) + if only_blank: + self._ai_timer.stop() + self._set_ai_columns(False) + return method = getattr(self.repository, "list_prescription_ai_statuses", None) if not has_permission(self.permissions, "tcm.prescriptionAi/statuses", default=False): self.ai_status_notice.setText("AI 分析:当前账号没有查看分析状态的权限,请联系管理员授权。") @@ -1711,15 +1737,17 @@ class PrescriptionsPage(QWidget): row = self.table.item(index, 0).data(Qt.ItemDataRole.UserRole) value = _int(first_value(row, "id", "prescription_id"), 0) batch = self._ai_statuses.get(value) - if batch is None: + blank = _is_blank_prescription(row) + if batch is None and not blank: continue - state = state_text(batch) if batch else ("尚未开方" if _truthy(get_value(row, "is_system_auto")) else "尚无分析记录") - for column, text in ((11, state), (12, agreement_text(batch))): + state = "" if blank else (state_text(batch) if batch else "尚无分析记录") + agreement = "" if blank else agreement_text(batch) + for column, text in ((11, state), (12, agreement)): item = self.table.item(index, column) if item.text() != text: item.setText(text) changed = True - item.setToolTip(status_tooltip(batch)) + item.setToolTip("" if blank else status_tooltip(batch)) item.setData(Qt.ItemDataRole.AccessibleTextRole, text) if changed: self.table.resizeRowsToContents() @@ -1736,6 +1764,8 @@ class PrescriptionsPage(QWidget): def _open_ai_report(self) -> None: row = self._selected() + if _is_blank_prescription(row): + return value = _int(first_value(row, "id", "prescription_id"), 0) if value > 0: present_issued_prescription_ai(self.repository, self.permissions, self, prescription_id=value) @@ -1748,6 +1778,8 @@ class PrescriptionsPage(QWidget): return self.table.selectRow(item.row()) row = self._selected() + if _is_blank_prescription(row): + return menu = QMenu(self.table) action = menu.addAction("AI 报告 / 逐味对照") action.triggered.connect(lambda: self._run_row_action(row, self._open_ai_report)) @@ -1795,7 +1827,13 @@ class PrescriptionsPage(QWidget): row = self.table.current_data() active = not self._mutation_pending self.view_button.setEnabled(active and row is not None) - self.ai_report_button.setEnabled(row is not None) + ai_available = ( + not _is_blank_prescription(row) + and can_open_issued_ai(self.permissions) + and callable(getattr(self.repository, "list_prescription_ai_reports", None)) + ) + self.ai_report_button.setVisible(ai_available) + self.ai_report_button.setEnabled(ai_available) self.patch_button.setEnabled(active and can_patch_patient(row)) self.create_order_button.setEnabled(active and can_create_order(row)) self.edit_button.setEnabled(active and can_edit_or_delete(row)) diff --git a/app/tests/test_issued_prescription_ai.py b/app/tests/test_issued_prescription_ai.py index 865c93b27..0736fb8f1 100644 --- a/app/tests/test_issued_prescription_ai.py +++ b/app/tests/test_issued_prescription_ai.py @@ -250,6 +250,69 @@ def test_list_batches_visible_ids_and_stops_on_hide(application: QApplication, i assert repository.calls == calls +def test_blank_list_row_has_no_ai_display_or_entry_points(application: QApplication, immediate: None, monkeypatch: pytest.MonkeyPatch) -> None: + repository = Repository() + opened = [] + monkeypatch.setattr(page_module, "present_issued_prescription_ai", lambda *args, **kwargs: opened.append(kwargs)) + page = page_module.PrescriptionsPage(repository, ["*"]) + page.resize(1366, 800) + page.show() + application.processEvents() + # Demo row 801 contains herbs; row 802 is an empty manual prescription. + assert all(call[1] == [801] for call in repository.calls if call[0] == "statuses") + assert not page.table.isColumnHidden(11) and not page.table.isColumnHidden(12) + page._ai_statuses[802] = batch(prescription_id=802) + page._render_ai_statuses() + for column in (11, 12): + item = page.table.item(1, column) + assert item.text() == item.toolTip() == item.data(Qt.ItemDataRole.AccessibleTextRole) == "" + assert not any(button.accessibleName() == "AI 报告" for button in page.table.cellWidget(1, 2).findChildren(QPushButton)) + page.table.selectRow(1) + assert page.ai_report_button.isHidden() and not page.ai_report_button.isEnabled() + page._open_ai_report() + page.table.itemClicked.emit(page.table.item(1, 11)) + page.table.itemClicked.emit(page.table.item(1, 12)) + page._open_ai_context_menu(page.table.visualItemRect(page.table.item(1, 1)).center()) + assert not hasattr(page, "_ai_context_menu") + assert opened == [] + page.table.selectRow(0) + assert page.ai_report_button.isVisible() and page.ai_report_button.isEnabled() + page.ai_report_button.click() + assert opened == [{"prescription_id": 801}] + page.close() + + +@pytest.mark.parametrize("blank_fields", [ + {"is_system_auto": "1", "herbs": []}, + {"is_system_auto": True, "herbs": [{"name": "黄芪"}]}, + {"is_system_auto": 0, "herbs": [{}, {"name": " "}]}, + {"is_system_auto": 0, "herbs": None}, +]) +def test_all_blank_list_hides_ai_until_prescription_is_saved(application: QApplication, immediate: None, monkeypatch: pytest.MonkeyPatch, blank_fields: dict[str, Any]) -> None: + repository = Repository() + row = {"id": 803, "sn": "RX-BLANK", "patient_name": "测试患者", **blank_fields} + monkeypatch.setattr(repository, "list_prescriptions", lambda **_filters: {"lists": [row], "count": 1}) + page = page_module.PrescriptionsPage(repository, ["*"]) + page.resize(1366, 800) + page.show() + application.processEvents() + assert page.table.isColumnHidden(11) and page.table.isColumnHidden(12) + assert page.ai_report_button.isHidden() and page.ai_status_notice.isHidden() + assert not page._ai_timer.isActive() + assert not any(call[0] == "statuses" for call in repository.calls) + assert not any(button.accessibleName() == "AI 报告" for button in page.table.cellWidget(0, 2).findChildren(QPushButton)) + + row.update(is_system_auto=0, herbs=[{"name": "黄芪", "dosage": 12}]) + page.refresh() + application.processEvents() + assert ("statuses", [803]) in repository.calls + assert not page.table.isColumnHidden(11) and not page.table.isColumnHidden(12) + assert page.ai_report_button.isVisible() and page.ai_status_notice.isVisible() + assert "千问 0%" in page.table.item(0, 12).text() + assert any(button.accessibleName() == "AI 报告" for button in page.table.cellWidget(0, 2).findChildren(QPushButton)) + page.close() + + def test_list_disabled_explains_availability_and_keeps_history(application: QApplication, immediate: None) -> None: repository = Repository() repository.enabled = False @@ -447,8 +510,9 @@ def test_context_menu_targets_clicked_prescription(application: QApplication, im page.resize(1366, 800) page.show() application.processEvents() - item = page.table.item(1, 1) - expected_id = page.table.item(1, 0).data(Qt.ItemDataRole.UserRole).id + page.table.selectRow(1) + item = page.table.item(0, 1) + expected_id = page.table.item(0, 0).data(Qt.ItemDataRole.UserRole).id page._open_ai_context_menu(page.table.visualItemRect(item).center()) page._ai_context_menu.actions()[0].trigger() assert opened == [{"prescription_id": expected_id}]