This commit is contained in:
Your Name
2026-09-03 16:03:41 +08:00
parent b4c11881b4
commit 928f72ec3d
25 changed files with 1926 additions and 394 deletions
+2 -2
View File
@@ -3,9 +3,9 @@
__all__ = ["DEBUG_MODE", "ONLINE_API_BASE_URL", "__version__"]
# Single source of truth for runtime, package, installer, and executable versions.
__version__ = "1.4.0"
__version__ = "1.4.1"
# 调试模式开启时,登录页显示“演示模式”和“服务器设置”。
# 正式发布请保持 False;此时程序只使用下面配置的线上域名。
DEBUG_MODE = True
DEBUG_MODE = False
ONLINE_API_BASE_URL = "https://admin.zhenyangtang.com.cn"
@@ -758,9 +758,11 @@ class DiagnosisDialog(QDialog):
parent: QWidget | None = None,
*,
permissions: Any = None,
embedded: bool = False,
) -> None:
super().__init__(parent)
self.repository = repository
self._embedded = bool(embedded)
self.permissions = (
permissions
if permissions is not None
@@ -882,11 +884,17 @@ class DiagnosisDialog(QDialog):
)
self.setObjectName("DiagnosisDialogRoot")
self.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.FramelessWindowHint)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
if self._embedded:
self.setWindowFlags(Qt.WindowType.Widget)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, False)
self.setMinimumSize(0, 0)
self.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Expanding)
else:
self.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.FramelessWindowHint)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.setMinimumSize(760, 520)
self.resize(1024, 640)
self.setWindowTitle("患者信息详情")
self.setMinimumSize(760, 520)
self.resize(1024, 640)
self.setStyleSheet(DIAGNOSIS_QSS)
self.view_stack = QStackedLayout(self)
@@ -911,16 +919,25 @@ class DiagnosisDialog(QDialog):
root = QVBoxLayout(page)
root.setContentsMargins(0, 0, 0, 0)
root.setSpacing(0)
self.readonly_header = QWidget(page)
self.readonly_header.setObjectName("DiagnosisReadonlyHeader")
header_layout = QVBoxLayout(self.readonly_header)
header_layout.setContentsMargins(16, 16, 16, 0)
header_layout.setSpacing(0)
self.readonly_hero = self._build_readonly_hero()
header_layout.addWidget(self.readonly_hero)
root.addWidget(self.readonly_header)
self.readonly_scroll = QScrollArea()
self.readonly_scroll.setObjectName("DiagnosisReadonlyScroll")
self.readonly_scroll.setWidgetResizable(True)
self.readonly_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.readonly_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.readonly_scroll.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.readonly_scroll.verticalScrollBar().setSingleStep(28)
content = QWidget()
self.readonly_content_layout = QVBoxLayout(content)
self.readonly_content_layout.setContentsMargins(16, 16, 16, 16)
self.readonly_content_layout.setSpacing(16)
self.readonly_hero = self._build_readonly_hero()
self.readonly_content_layout.addWidget(self.readonly_hero)
self.readonly_error = QFrame()
self.readonly_error.setObjectName("DiagnosisReadonlyErrorCard")
self.readonly_error.setProperty("diagnosisReadonlyCard", True)
@@ -981,16 +998,16 @@ class DiagnosisDialog(QDialog):
left_layout = QHBoxLayout(self.readonly_hero_left)
left_layout.setContentsMargins(0, 0, 0, 0)
left_layout.setSpacing(12)
back = QPushButton("← 返回")
back.setObjectName("DiagnosisReadonlyBack")
back.setCursor(Qt.CursorShape.PointingHandCursor)
back.setStyleSheet(
self.readonly_back_button = QPushButton(" 收起资料" if self._embedded else " 返回")
self.readonly_back_button.setObjectName("DiagnosisReadonlyBack")
self.readonly_back_button.setCursor(Qt.CursorShape.PointingHandCursor)
self.readonly_back_button.setStyleSheet(
"QPushButton{height:32px;padding:0 8px;border:0;background:transparent;"
"color:#5265F6;font-size:13px;font-weight:500;}"
"QPushButton:hover,QPushButton:focus{background:#F0F2FF;border-radius:6px;}"
)
back.clicked.connect(self.reject)
left_layout.addWidget(back)
self.readonly_back_button.clicked.connect(self.reject)
left_layout.addWidget(self.readonly_back_button)
title = QLabel("患者信息详情")
title.setObjectName("DiagnosisReadonlyTitle")
left_layout.addWidget(title)
@@ -1009,6 +1026,13 @@ class DiagnosisDialog(QDialog):
self.readonly_status.setObjectName("DiagnosisReadonlyStatus")
self.readonly_status.setProperty("severity", "neutral")
right_layout.addWidget(self.readonly_status)
self.readonly_close_button = QPushButton("×")
self.readonly_close_button.setObjectName("DiagnosisCloseButton")
self.readonly_close_button.setToolTip("关闭诊单详情")
self.readonly_close_button.setAccessibleName("关闭诊单详情")
self.readonly_close_button.setCursor(Qt.CursorShape.PointingHandCursor)
self.readonly_close_button.clicked.connect(self.reject)
right_layout.addWidget(self.readonly_close_button)
layout.addWidget(self.readonly_hero_left, 0, 0)
layout.addWidget(self.readonly_hero_right, 0, 1, Qt.AlignmentFlag.AlignRight)
layout.setColumnStretch(0, 1)
@@ -1790,6 +1814,8 @@ class DiagnosisDialog(QDialog):
label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
def _sync_host_geometry(self) -> None:
if self._embedded:
return
owner = self._owner
if owner is None:
if self.width() < 760 or self.height() < 520:
@@ -1823,6 +1849,8 @@ class DiagnosisDialog(QDialog):
layout.addWidget(self.readonly_hero_right, 0, 1, Qt.AlignmentFlag.AlignRight)
def _install_owner_filter(self) -> None:
if self._embedded:
return
if self._owner is not None and not self._owner_filter_installed:
self._owner.installEventFilter(self)
self._owner_filter_installed = True
@@ -1830,6 +1858,10 @@ class DiagnosisDialog(QDialog):
def _rebind_owner(self) -> None:
"""Resolve the live Shell window for every open/show cycle."""
if self._embedded:
self._owner = None
self._owner_filter_installed = False
return
parent = self.parentWidget()
candidate = parent.window() if parent is not None else None
if candidate is self:
@@ -1852,8 +1884,9 @@ class DiagnosisDialog(QDialog):
super().resizeEvent(event)
def showEvent(self, event: Any) -> None:
self._rebind_owner()
self._sync_host_geometry()
if not self._embedded:
self._rebind_owner()
self._sync_host_geometry()
self._update_drawer_geometry()
self._reflow_readonly_hero()
super().showEvent(event)
@@ -1938,10 +1971,12 @@ class DiagnosisDialog(QDialog):
*,
editable: bool = False,
seed: Any = None,
authoritative_detail: Any = None,
view_only: bool = False,
modeless: bool = False,
auto_show: bool = True,
) -> None:
"""Open immediately, then replace the seed with authoritative server data."""
"""Prepare a diagnosis view, optionally showing it immediately."""
self._rebind_owner()
for player in list(self._recording_players):
@@ -1986,13 +2021,13 @@ class DiagnosisDialog(QDialog):
self._daily_todo_status = None
self._orders_page = 1
self._orders_total = 0
self._detail = seed
self._detail = authoritative_detail if authoritative_detail is not None else seed
self.save_button.set_state("idle")
self.refresh_permissions()
self.view_stack.setCurrentWidget(
self.readonly_page if self._standalone_readonly else self.drawer_overlay
)
self.setModal(not modeless and not self._standalone_readonly)
self.setModal(False if self._embedded else not modeless and not self._standalone_readonly)
self.setWindowTitle(
"患者信息详情"
if self._standalone_readonly
@@ -2018,15 +2053,38 @@ class DiagnosisDialog(QDialog):
)
self._clear_tables()
self._clear_message()
if seed is not None:
self._render(seed, [], [])
if self._detail is not None:
self._render(self._detail, [], [])
self._sync_form_interactivity()
self._sync_save_button()
self._sync_host_geometry()
if auto_show:
self.show()
if not self._embedded:
self.raise_()
if authoritative_detail is not None:
diagnosis = get_value(authoritative_detail, "diagnosis", None) or authoritative_detail
patient = get_value(authoritative_detail, "patient", None) or {}
self._patient_id = _int(
first_value(
diagnosis,
"patient_id",
"source_patient_id",
default=first_value(patient, "patient_id", "id", default=0),
),
0,
)
self._authoritative_detail_loaded = True
self._show_authoritative_content(True)
self._clear_message()
self._set_loading(False)
if self._standalone_readonly:
self._load_visible_readonly_sections()
else:
self._ensure_tab_loaded(self._current_tab_key())
return
self._show_message("正在加载权威诊单详情…", "info")
self._set_loading(True)
self._sync_host_geometry()
self.show()
self.raise_()
self._start_detail_load()
def _start_detail_load(self) -> None:
@@ -16,7 +16,7 @@ import re
import sys
import tempfile
from collections.abc import Iterable, Mapping, Sequence
from datetime import date, datetime
from datetime import date
from pathlib import Path
from time import monotonic
from typing import Any
@@ -80,6 +80,7 @@ from PySide6.QtWidgets import (
QScrollArea,
QSizePolicy,
QSpinBox,
QSplitter,
QTableWidget,
QTableWidgetItem,
QTabWidget,
@@ -104,6 +105,7 @@ from ..widgets import (
run_async,
show_toast,
)
from .diagnosis import DiagnosisDialog as _StructuredDiagnosisDialog
PRESCRIPTION_DRAWER_QSS = r"""
QFrame#PrescriptionDrawerSurface {
@@ -2697,6 +2699,7 @@ class PrescriptionEditorDialog(QDialog):
self._source = _mapping(prescription)
self._loading_data = False
self._linked_order_generation = 0
self._diagnosis_view: _StructuredDiagnosisDialog | None = None
self._prescribing_creator_id = _int(
first_value(
prescription,
@@ -2709,17 +2712,37 @@ class PrescriptionEditorDialog(QDialog):
self.setModal(True)
self.setMinimumSize(420, 600)
self.resize(self.DRAWER_WIDTH, 900)
root = QVBoxLayout(self)
root = QHBoxLayout(self)
root.setContentsMargins(0, 0, 0, 0)
root.setSpacing(0)
self.workspace_splitter = QSplitter(Qt.Orientation.Horizontal, self)
self.workspace_splitter.setObjectName("PrescriptionWorkspaceSplitter")
self.workspace_splitter.setChildrenCollapsible(False)
self.workspace_splitter.setHandleWidth(1)
root.addWidget(self.workspace_splitter)
self.diagnosis_host = QFrame(self.workspace_splitter)
self.diagnosis_host.setObjectName("PrescriptionDiagnosisPane")
self.diagnosis_host.setMinimumWidth(300)
diagnosis_layout = QVBoxLayout(self.diagnosis_host)
diagnosis_layout.setContentsMargins(0, 0, 0, 0)
diagnosis_layout.setSpacing(0)
self.diagnosis_layout = diagnosis_layout
self.workspace_splitter.addWidget(self.diagnosis_host)
self.diagnosis_host.hide()
self.drawer_surface = QFrame()
self.drawer_surface.setObjectName("PrescriptionDrawerSurface")
self.drawer_surface.setStyleSheet(_prescription_drawer_qss())
self.drawer_surface.setMinimumWidth(self.minimumWidth())
self.drawer_surface.setMaximumWidth(self.DRAWER_WIDTH)
surface_layout = QVBoxLayout(self.drawer_surface)
surface_layout.setContentsMargins(0, 0, 0, 0)
surface_layout.setSpacing(0)
root.addWidget(self.drawer_surface)
self.workspace_splitter.addWidget(self.drawer_surface)
self.workspace_splitter.setStretchFactor(0, 1)
self.workspace_splitter.setStretchFactor(1, 0)
self.header = self._build_header()
surface_layout.addWidget(self.header)
@@ -2920,7 +2943,7 @@ class PrescriptionEditorDialog(QDialog):
self.diagnosis_button.setProperty("size", "small")
self.diagnosis_button.setVisible(diagnosis_id > 0)
self.diagnosis_button.clicked.connect(
lambda _checked=False, value=diagnosis_id: self.diagnosis_requested.emit(value)
lambda _checked=False, value=diagnosis_id: self._toggle_diagnosis_view(value)
)
diagnosis_layout.addWidget(self.diagnosis_button)
self.diagnosis_id_hint = QLabel(f"关联诊单 #{diagnosis_id}" if diagnosis_id > 0 else "")
@@ -3237,15 +3260,58 @@ class PrescriptionEditorDialog(QDialog):
target = targets[max(0, min(len(targets) - 1, index))]
self.body_scroll.ensureWidgetVisible(target, 0, 24)
def _toggle_diagnosis_view(self, diagnosis_id: int) -> None:
if self.diagnosis_host.isVisible():
if self._diagnosis_view is not None:
self._diagnosis_view.reject()
return
if diagnosis_id <= 0:
return
if not has_permission(self.permissions, "tcm.diagnosis/readonlyDetail", default=True):
self.context_banner.show_message("无权查看诊单详情。", "danger")
return
if self._diagnosis_view is None:
self._diagnosis_view = _StructuredDiagnosisDialog(
self.repository,
self.diagnosis_host,
permissions=self.permissions,
embedded=True,
)
self._diagnosis_view.finished.connect(
lambda _result, view=self._diagnosis_view: self._diagnosis_view_finished(view)
)
self.diagnosis_layout.addWidget(self._diagnosis_view)
self.diagnosis_host.show()
self.diagnosis_button.setText("收起患者诊单")
self._fit_drawer_geometry()
self._diagnosis_view.open_for(diagnosis_id, editable=False)
def _diagnosis_view_finished(self, view: _StructuredDiagnosisDialog) -> None:
if view is not self._diagnosis_view:
return
self.diagnosis_host.hide()
self.diagnosis_button.setText("查看患者诊单详情")
self._fit_drawer_geometry()
def _fit_drawer_geometry(self) -> None:
parent = self.parentWidget()
if parent is None:
return
anchor = parent.window()
origin = anchor.mapToGlobal(QPoint(0, 0))
width = min(self.DRAWER_WIDTH, max(self.minimumWidth(), anchor.width()))
expanded = self.diagnosis_host.isVisible()
width = (
anchor.width()
if expanded
else min(self.DRAWER_WIDTH, max(self.minimumWidth(), anchor.width()))
)
height = max(self.minimumHeight(), anchor.height())
self.setGeometry(origin.x() + anchor.width() - width, origin.y(), width, height)
if expanded:
editor_width = min(self.DRAWER_WIDTH, max(520, round(width * 0.55)))
self.workspace_splitter.setSizes([max(300, width - editor_width - 1), editor_width])
else:
self.workspace_splitter.setSizes([0, width])
def showEvent(self, event: Any) -> None:
super().showEvent(event)
@@ -3836,6 +3902,11 @@ class PrescriptionEditorDialog(QDialog):
self.validation.clear()
super().accept()
def done(self, result: int) -> None:
if self._diagnosis_view is not None and self._diagnosis_view.isVisible():
self._diagnosis_view.reject()
super().done(result)
class PatchPatientDialog(QDialog):
"""Narrow patient identity correction that preserves audit state."""
@@ -5102,8 +5173,8 @@ class PrescriptionDetailDialog(QDialog):
painter.end()
class DiagnosisDetailDialog(QDialog):
"""Read-only diagnosis view preserving the important admin tab boundaries."""
class DiagnosisDetailDialog(_StructuredDiagnosisDialog):
"""Compatibility entry that renders prescription-linked diagnoses with the shared UI."""
def __init__(
self,
@@ -5113,180 +5184,29 @@ class DiagnosisDetailDialog(QDialog):
repository: Any = None,
permissions: Any = None,
) -> None:
super().__init__(parent)
source = _mapping(diagnosis)
self.repository = repository
self.permissions = permissions
self._order_detail_generation = 0
self._order_detail_order_id = 0
self._order_detail_table: QTableWidget | None = None
self._order_detail_button: QPushButton | None = None
if repository is None:
raise ValueError("repository is required to show diagnosis details")
super().__init__(repository, parent, permissions=permissions)
self.setWindowTitle("诊单详情(只读)")
self.resize(880, 700)
root = QVBoxLayout(self)
tabs = QTabWidget()
groups = (
(
"病历",
(
"id",
"patient_id",
"patient_name",
"gender",
"age",
"phone",
"chief_complaint",
"present_illness",
"past_history",
"diagnosis",
"syndrome",
"treatment",
),
source = _mapping(diagnosis)
nested = _mapping(source.get("diagnosis"))
diagnosis_id = _int(
first_value(
nested,
"id",
"diagnosis_id",
default=first_value(source, "id", "diagnosis_id"),
),
("医生备注", ("doctor_notes", "doctor_note", "notes")),
("日常记录", ("daily_records", "blood_records")),
("处方", ("prescriptions", "case_records")),
("沟通与指派", ("call_records", "chat_records", "assign_logs", "appointments")),
0,
)
for title, keys in groups:
browser = QTextBrowser()
rows = []
for key in keys:
value = source.get(key)
if value in (None, "", [], {}):
continue
rendered = (
json.dumps(value, ensure_ascii=False, indent=2, default=str)
if isinstance(value, (Mapping, list, tuple))
else str(value)
)
rows.append(f"<h3>{html.escape(key)}</h3><pre>{html.escape(rendered)}</pre>")
browser.setHtml("".join(rows) or "<p>暂无数据</p>")
tabs.addTab(browser, title)
tabs.addTab(self._build_orders_tab(source), "业务订单")
root.addWidget(tabs, 1)
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
buttons.rejected.connect(self.reject)
root.addWidget(buttons)
def _build_orders_tab(self, source: Mapping[str, Any]) -> QWidget:
host = QWidget()
layout = QVBoxLayout(host)
layout.setContentsMargins(8, 8, 8, 8)
layout.setSpacing(8)
rows: list[Any] = []
for key in ("prescription_orders", "orders"):
value = source.get(key)
if isinstance(value, list):
rows.extend(value)
latest = source.get("latest_prescription_order")
if isinstance(latest, Mapping) and latest:
latest_id = _int(first_value(latest, "id", "order_id"), 0)
if latest_id and not any(
_int(first_value(row, "id", "order_id"), 0) == latest_id for row in rows
):
rows.insert(0, latest)
if not rows:
empty = QLabel("暂无关联业务订单")
empty.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(empty, 1)
return host
table = QTableWidget(len(rows), 6)
table.setHorizontalHeaderLabels(
["订单号", "金额", "履约状态", "收货人", "手机", "创建时间"]
)
table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
table.verticalHeader().hide()
table.horizontalHeader().setStretchLastSection(True)
self._order_detail_table = table
for row_index, row in enumerate(rows):
values = (
first_value(row, "order_no", "sn", "id"),
first_value(row, "amount", "effective_amount"),
first_value(row, "fulfillment_status_text", "status_text", "status"),
first_value(row, "recipient_name", "patient_name"),
first_value(row, "recipient_phone", "phone"),
first_value(row, "create_time_text", "create_time"),
)
for column, value in enumerate(values):
item = QTableWidgetItem(display_text(value))
item.setData(Qt.ItemDataRole.UserRole, row)
table.setItem(row_index, column, item)
layout.addWidget(table, 1)
actions = QHBoxLayout()
view = QPushButton("查看订单详情")
view.setProperty("variant", "primary")
self._order_detail_button = view
view.clicked.connect(self._open_selected_order)
table.itemDoubleClicked.connect(lambda _item: self._open_selected_order())
actions.addWidget(view)
actions.addStretch(1)
layout.addLayout(actions)
return host
def _set_order_detail_loading(self, loading: bool) -> None:
if self._order_detail_table is not None:
self._order_detail_table.setEnabled(not loading)
if self._order_detail_button is not None:
self._order_detail_button.setEnabled(not loading)
def _open_selected_order(self) -> None:
table = self._order_detail_table
if table is None:
return
row = table.currentRow()
item = table.item(row, 0) if row >= 0 else None
order = item.data(Qt.ItemDataRole.UserRole) if item is not None else None
if order is None:
return
order_id = _int(first_value(order, "id", "order_id"), 0)
self._order_detail_generation += 1
generation = self._order_detail_generation
self._order_detail_order_id = order_id
getter = getattr(self.repository, "get_prescription_order", None)
if order_id <= 0 or not callable(getter):
self._set_order_detail_loading(False)
self._present_order_detail(order, order_id)
return
self._set_order_detail_loading(True)
run_async(
lambda: getter(order_id),
on_success=lambda result: self._order_detail_success(result, order_id, generation),
on_error=lambda error: self._order_detail_error(error, order, order_id, generation),
on_finished=lambda: self._order_detail_finished(order_id, generation),
)
def _order_detail_success(self, order: Any, order_id: int, generation: int) -> None:
if generation != self._order_detail_generation or order_id != self._order_detail_order_id:
return
self._present_order_detail(order, order_id)
def _order_detail_error(
self,
_error: Exception,
fallback_order: Any,
order_id: int,
generation: int,
) -> None:
if generation != self._order_detail_generation or order_id != self._order_detail_order_id:
return
self._present_order_detail(fallback_order, order_id)
def _order_detail_finished(self, order_id: int, generation: int) -> None:
if generation == self._order_detail_generation and order_id == self._order_detail_order_id:
self._set_order_detail_loading(False)
def _present_order_detail(self, order: Any, order_id: int) -> None:
from .diagnosis import present_order_detail
present_order_detail(
self.window() if self.window() is not None else self,
order,
order_id=order_id,
permissions=self.permissions,
exec_=True,
if diagnosis_id <= 0:
raise ValueError("diagnosis detail is missing a valid diagnosis id")
self.open_for(
diagnosis_id,
editable=False,
seed=source,
authoritative_detail=source,
auto_show=False,
)
+17
View File
@@ -639,6 +639,23 @@ def test_readonly_is_an_independent_vertical_page_flow(
application.processEvents()
def test_readonly_scroll_keeps_close_controls_reachable(application: QApplication) -> None:
dialog = _open_dialog(application, (760, 520), mode="readonly")
header_before = dialog.readonly_header.geometry()
scroll_bar = dialog.readonly_scroll.verticalScrollBar()
assert scroll_bar.maximum() > 0
assert dialog.readonly_close_button.isVisibleTo(dialog)
scroll_bar.setValue(scroll_bar.maximum())
application.processEvents()
assert dialog.readonly_header.geometry() == header_before
assert dialog.readonly_close_button.isVisibleTo(dialog)
dialog.readonly_close_button.click()
application.processEvents()
assert not dialog.isVisible()
@pytest.mark.parametrize("size", [(1024, 640), (1440, 900)])
@pytest.mark.parametrize("mode", ["edit", "viewOnly"])
def test_drawer_is_full_height_rtl_and_sixty_percent_wide(
+93 -94
View File
@@ -288,122 +288,121 @@ def test_paid_order_response_is_bound_to_active_diagnosis_and_blocks_save(
application.processEvents()
def test_diagnosis_order_detail_lookup_is_queued_before_repository_call(
def test_prescription_diagnosis_detail_uses_shared_structured_readonly_ui(
application: QApplication,
monkeypatch: pytest.MonkeyPatch,
) -> None:
queued: list[tuple[Any, dict[str, Any]]] = []
requested: list[int] = []
shown: list[tuple[int, str]] = []
class Repository:
def get_prescription_order(self, order_id: int) -> dict[str, Any]:
requested.append(order_id)
return {"id": order_id, "order_no": f"DETAIL-{order_id}"}
def get_doctor_notes(self, diagnosis_id: int) -> list[dict[str, Any]]:
assert diagnosis_id == 745
return []
def queue_async(function: Any, **options: Any) -> object:
queued.append((function, options))
return object()
def present_order_detail(
_host: Any,
order: dict[str, Any],
*,
order_id: int,
permissions: Any,
exec_: bool,
) -> None:
del permissions, exec_
shown.append((order_id, order["order_no"]))
monkeypatch.setattr(dialog_module, "run_async", queue_async)
monkeypatch.setattr(diagnosis_module, "present_order_detail", present_order_detail)
monkeypatch.setattr(diagnosis_module, "run_async", _immediate_async)
dialog = DiagnosisDetailDialog(
{"orders": [{"id": 17, "order_no": "ROW-17"}]},
{
"diagnosis": {
"id": 745,
"patient_id": 745,
"patient_name": "庄志芳",
"phone": "13823549442",
"id_card": "440305196701011234",
"gender": 0,
"age": 59,
"chief_complaint": "睡眠不好、出汗多",
"past_history": ["高血压", "高脂血症"],
},
"patient": {"id": 745},
},
repository=Repository(),
permissions=PermissionSet(["tcm.diagnosis/readonlyDetail"]),
)
table = dialog._order_detail_table
button = dialog._order_detail_button
assert table is not None
assert button is not None
table.setCurrentCell(0, 0)
button.click()
assert len(queued) == 1
assert requested == []
assert shown == []
assert not table.isEnabled()
assert not button.isEnabled()
function, options = queued[0]
options["on_success"](function())
options["on_finished"]()
assert requested == [17]
assert shown == [(17, "DETAIL-17")]
assert table.isEnabled()
assert button.isEnabled()
assert isinstance(dialog, DiagnosisDialog)
assert dialog.view_stack.currentWidget() is dialog.readonly_page
assert dialog.edit_fields["chief_complaint"].toPlainText() == "睡眠不好、出汗多"
assert dialog.summary_fields["phone"].text() == "138****9442"
assert dialog.case_grid.isVisibleTo(dialog)
assert not dialog.findChildren(dialog_module.QTextBrowser)
dialog.close()
application.processEvents()
def test_diagnosis_order_detail_ignores_stale_result_and_keeps_row_fallback(
@pytest.mark.parametrize("owner_width", [1024, 1440, 1710])
def test_prescription_editor_embeds_scrollable_diagnosis_beside_editable_form(
application: QApplication,
monkeypatch: pytest.MonkeyPatch,
owner_width: int,
) -> None:
queued: list[dict[str, Any]] = []
shown: list[tuple[int, str]] = []
class Repository:
def get_diagnosis_detail(
self, diagnosis_id: int, *, readonly: bool = False
) -> dict[str, Any]:
assert readonly
return {
"diagnosis": {
"id": diagnosis_id,
"patient_id": 745,
"patient_name": "庄志芳",
"chief_complaint": "睡眠不好、出汗多",
}
}
def queue_async(_function: Any, **options: Any) -> object:
queued.append(options)
return object()
def get_doctor_notes(self, _diagnosis_id: int) -> list[dict[str, Any]]:
return []
def present_order_detail(
_host: Any,
order: dict[str, Any],
*,
order_id: int,
permissions: Any,
exec_: bool,
) -> None:
del permissions, exec_
shown.append((order_id, order["order_no"]))
repository = SimpleNamespace(get_prescription_order=lambda order_id: {"id": order_id})
monkeypatch.setattr(dialog_module, "run_async", queue_async)
monkeypatch.setattr(diagnosis_module, "present_order_detail", present_order_detail)
dialog = DiagnosisDetailDialog(
{
"orders": [
{"id": 21, "order_no": "ROW-21"},
{"id": 22, "order_no": "ROW-22"},
]
},
repository=repository,
monkeypatch.setattr(diagnosis_module, "run_async", _immediate_async)
owner = QDialog()
owner.resize(owner_width, 720)
owner.show()
repository = Repository()
editor = PrescriptionEditorDialog(
repository,
{"diagnosis_id": 745, "patient_name": "庄志芳"},
mode="edit",
current_user=SimpleNamespace(id=9, name="周医生"),
permissions=PermissionSet(["tcm.diagnosis/readonlyDetail"]),
parent=owner,
)
table = dialog._order_detail_table
button = dialog._order_detail_button
assert table is not None
assert button is not None
editor.show()
application.processEvents()
assert QApplication.activeModalWidget() is editor
assert editor.width() == editor.DRAWER_WIDTH
table.setCurrentCell(0, 0)
dialog._open_selected_order()
table.setCurrentCell(1, 0)
dialog._open_selected_order()
assert len(queued) == 2
editor.diagnosis_button.click()
application.processEvents()
queued[0]["on_success"]({"id": 21, "order_no": "STALE-21"})
queued[0]["on_finished"]()
assert shown == []
assert not table.isEnabled()
assert not button.isEnabled()
detail = editor._diagnosis_view
assert detail is not None
assert QApplication.activeModalWidget() is editor
assert not detail.isWindow()
assert detail.parentWidget() is editor.diagnosis_host
assert editor.diagnosis_host.isVisibleTo(editor)
assert editor.drawer_surface.isVisibleTo(editor)
assert editor.width() == owner.width()
assert editor.diagnosis_host.geometry().right() < editor.drawer_surface.geometry().left()
scroll_bar = detail.readonly_scroll.verticalScrollBar()
assert scroll_bar.maximum() > 0
scroll_bar.setValue(scroll_bar.maximum())
assert scroll_bar.value() == scroll_bar.maximum()
queued[1]["on_error"](RuntimeError("detail unavailable"))
queued[1]["on_finished"]()
assert shown == [(22, "ROW-22")]
assert table.isEnabled()
assert button.isEnabled()
dialog.close()
editor.patient_name.setText("庄志芳(已核对)")
assert editor.patient_name.isEnabled()
assert editor.payload()["patient_name"] == "庄志芳(已核对)"
detail.readonly_close_button.click()
application.processEvents()
assert editor.isVisible()
assert not editor.diagnosis_host.isVisible()
assert editor.width() == editor.DRAWER_WIDTH
assert editor.patient_name.text() == "庄志芳(已核对)"
editor.diagnosis_button.click()
application.processEvents()
assert editor.diagnosis_host.isVisibleTo(editor)
editor.reject()
application.processEvents()
assert not editor.isVisible()
assert not detail.isVisible()
owner.close()
application.processEvents()