Files
zyt/app/tests/test_prescription_ui.py
T
2026-08-10 17:29:05 +08:00

425 lines
13 KiB
Python

from __future__ import annotations
import os
from types import SimpleNamespace
from typing import Any
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
import pytest
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QApplication
from doctor_workstation.services import DemoDoctorRepository
from doctor_workstation.ui.dialogs import prescription as dialog_module
from doctor_workstation.ui.dialogs.prescription import (
AuditPrescriptionDialog,
PrescriptionDetailDialog,
PrescriptionEditorDialog,
PrescriptionOrderDialog,
PrescriptionTemplateDialog,
RemoteMedicineComboBox,
parse_pasted_herbs,
render_prescription_html,
)
from doctor_workstation.ui.pages import prescription_library as library_module
from doctor_workstation.ui.pages import prescriptions as prescription_module
from doctor_workstation.ui.pages.prescription_library import PrescriptionLibraryPage
from doctor_workstation.ui.pages.prescriptions import (
PrescriptionsPage,
can_audit,
can_create_order,
can_edit_or_delete,
can_patch_patient,
prescription_status,
)
@pytest.fixture(scope="module")
def application() -> QApplication:
return QApplication.instance() or QApplication([])
@pytest.fixture
def immediate_async(monkeypatch: pytest.MonkeyPatch) -> None:
def run_immediately(
function: Any,
*args: Any,
on_success: Any = None,
on_error: Any = None,
on_finished: Any = None,
**kwargs: Any,
) -> object:
try:
result = function(*args, **kwargs)
except Exception as error:
if on_error:
on_error(error)
else:
if on_success:
on_success(result)
finally:
if on_finished:
on_finished()
return object()
monkeypatch.setattr(dialog_module, "run_async", run_immediately)
monkeypatch.setattr(library_module, "run_async", run_immediately)
monkeypatch.setattr(prescription_module, "run_async", run_immediately)
def test_admin_status_and_action_guards_are_exact() -> None:
pending = {
"id": 1,
"audit_status": 0,
"void_status": 0,
"has_prescription_order": 0,
}
approved = {**pending, "audit_status": 1}
rejected = {
**approved,
"business_prescription_audit_rejected": 1,
"business_prescription_audit_remark": "剂量需调整",
}
voided = {**approved, "void_status": 1}
assert prescription_status(pending) == ("待审核", "warning")
assert prescription_status(approved) == ("已通过", "success")
assert prescription_status(rejected) == ("已驳回", "danger")
assert prescription_status(voided) == ("已作废", "danger")
assert can_patch_patient(pending)
assert can_create_order(pending)
assert can_audit(pending)
assert can_edit_or_delete(pending)
assert not can_audit(approved)
assert not can_edit_or_delete(approved)
assert can_edit_or_delete(voided)
assert not can_patch_patient(voided)
def test_paste_parser_matches_common_admin_recipe_forms() -> None:
parsed = parse_pasted_herbs("Rp: 黄芪15 党参12、茯苓10g\n柴胡、白术各6克\n饭后温服")
assert parsed == [
{"name": "黄芪", "dosage": 15.0},
{"name": "党参", "dosage": 12.0},
{"name": "茯苓", "dosage": 10.0},
{"name": "柴胡", "dosage": 6.0},
{"name": "白术", "dosage": 6.0},
]
def test_remote_medicine_selector_rejects_new_free_text(
application: QApplication,
) -> None:
repository = SimpleNamespace(list_medicines=lambda **_kwargs: {"lists": [], "count": 0})
selector = RemoteMedicineComboBox(repository, name="历史药名")
assert selector.has_valid_selection
selector._queue_search("随意输入")
selector._timer.stop()
selector.setEditText("随意输入")
assert not selector.has_valid_selection
selector.set_value(11, "黄芪")
assert selector.has_valid_selection
selector.close()
application.processEvents()
def test_template_disable_edit_controls_import_not_template_maintenance(
application: QApplication,
immediate_async: None,
) -> None:
repository = SimpleNamespace(
list_medicines=lambda **_kwargs: {
"lists": [{"id": 11, "name": "黄芪"}],
"count": 1,
}
)
template = {
"id": 8,
"prescription_name": "益气方",
"formula_type": "主方",
"is_public": 1,
"disable_edit": 1,
"herbs": [{"medicine_id": 11, "name": "黄芪", "dosage": 15}],
}
dialog = PrescriptionTemplateDialog(repository, template, mode="edit")
assert dialog.disable_edit_check.isChecked()
assert dialog.herbs.rows[0].medicine.isEnabled()
assert dialog.herbs.rows[0].dosage.isEnabled()
assert dialog.payload() == {
"id": 8,
"prescription_name": "益气方",
"formula_type": "主方",
"is_public": 1,
"disable_edit": 1,
"herbs": [{"medicine_id": 11, "name": "黄芪", "dosage": 15.0}],
}
dialog.close()
application.processEvents()
def test_library_page_uses_canonical_permissions_and_full_columns(
application: QApplication,
immediate_async: None,
) -> None:
class Repository:
def list_prescription_templates(self, **_filters: Any) -> dict[str, Any]:
return {
"lists": [
{
"id": 3,
"prescription_name": "安神方",
"formula_type": "辅方",
"herbs": [{"name": "酸枣仁", "dosage": 12}],
"is_public": 0,
"disable_edit": 1,
"creator_id": 9,
"creator_name": "张医生",
"create_time": "2026-08-10 12:00:00",
}
],
"count": 1,
}
permissions = {
"wcf.prescription/add",
"wcf.prescription/read",
"wcf.prescription/edit",
"wcf.prescription/delete",
}
page = PrescriptionLibraryPage(
Repository(),
permissions,
SimpleNamespace(id=9, root=0, role_ids=[]),
)
page.refresh()
page.table.selectRow(0)
page._selection_changed()
assert page.table.columnCount() == 9
assert not page.view_button.isHidden() and page.view_button.isEnabled()
assert not page.edit_button.isHidden() and page.edit_button.isEnabled()
assert not page.delete_button.isHidden() and page.delete_button.isEnabled()
assert page.pager.page_size == 15
page.close()
application.processEvents()
def test_issued_page_sends_exact_filter_dto_and_row_guards(
application: QApplication,
immediate_async: None,
) -> None:
calls: list[dict[str, Any]] = []
pending = {
"id": 21,
"sn": "CF-21",
"patient_name": "林晓岚",
"gender": 0,
"age": 33,
"audit_status": 0,
"void_status": 0,
"has_prescription_order": 0,
"creator_id": 7,
"doctor_name": "周医生",
"prescription_date": "2026-08-10",
"create_time": "2026-08-10 12:00:00",
"herbs": [{"name": "黄芪", "dosage": 15}],
}
class Repository:
def list_diagnosis_doctors(self) -> list[dict[str, Any]]:
return [{"id": 9, "name": "孙医生"}]
def list_prescriptions(self, **filters: Any) -> dict[str, Any]:
calls.append(filters)
return {"lists": [pending], "count": 1}
permissions = {
"cf.prescription/add",
"cf.prescription/read",
"cf.prescription/edit",
"cf.prescription/audit",
"cf.prescription/del",
"tcm.prescription/patchPatient",
"tcm.prescriptionOrder/create",
"tcm.prescriptionOrder/lists",
}
page = PrescriptionsPage(
Repository(),
permissions,
SimpleNamespace(id=7, name="周医生"),
)
page.refresh()
page.table.selectRow(0)
page._selection_changed()
assert calls == [
{
"page_no": 1,
"page_size": 15,
"sn": "",
"patient_name": "",
"audit_filter": "",
"source_filter": "",
"start_time": "",
"end_time": "",
}
]
page.quick_date.setCurrentIndex(1)
assert len(calls) == 2
assert calls[-1]["start_time"].endswith("00:00:00")
assert calls[-1]["end_time"].endswith("23:59:59")
assert page.audit_button.isEnabled()
assert page.doctor_filter._options[9] == "孙医生"
assert page.patch_button.isEnabled()
assert page.create_order_button.isEnabled()
assert page.edit_button.isEnabled()
assert page.delete_button.isEnabled()
page.table.set_rows([{**pending, "audit_status": 1}])
page.table.selectRow(0)
page._selection_changed()
assert not page.audit_button.isEnabled()
assert not page.edit_button.isEnabled()
assert not page.delete_button.isEnabled()
assert page.patch_button.isEnabled()
assert page.create_order_button.isEnabled()
page.close()
application.processEvents()
def test_editor_builds_complete_add_payload(
application: QApplication,
immediate_async: None,
) -> None:
repository = SimpleNamespace(
list_medicines=lambda **_kwargs: {
"lists": [{"id": 31, "name": "黄芪"}],
"count": 1,
}
)
user = SimpleNamespace(id=7, name="周医生")
editor = PrescriptionEditorDialog(repository, mode="add", current_user=user)
editor.patient_name.setText("林晓岚")
editor.clinical_diagnosis.setPlainText("脾气虚")
editor.herbs.rows[0].medicine.set_value(31, "黄芪")
editor.herbs.rows[0].dosage.setValue(15)
editor.signature._has_strokes = True
payload = editor.payload()
assert payload["creator_id"] == 7
assert payload["audit_status"] == 0
assert payload["patient_name"] == "林晓岚"
assert payload["clinical_diagnosis"] == "脾气虚"
assert payload["herbs"] == [
{
"medicine_id": 31,
"name": "黄芪",
"dosage": 15.0,
"formula_type": "主方",
}
]
assert payload["doctor_name"] == "周医生"
assert payload["doctor_signature"].startswith("data:image/png;base64,")
assert isinstance(payload["aux_usage"], dict)
editor.close()
application.processEvents()
def test_audit_reject_requires_remark(
application: QApplication,
) -> None:
dialog = AuditPrescriptionDialog({"id": 4})
dialog._choose("reject")
assert dialog.action == ""
assert not dialog.banner.isHidden()
dialog.remark.setPlainText("剂量需调整")
dialog._choose("reject")
assert dialog.action == "reject"
assert dialog.payload() == {
"id": 4,
"action": "reject",
"remark": "剂量需调整",
}
dialog.close()
application.processEvents()
def test_order_payload_and_a4_print_document(
application: QApplication,
immediate_async: None,
) -> None:
repository = SimpleNamespace(
list_paid_prescription_orders=lambda diagnosis_id: {
"lists": [{"id": 88, "order_no": "PAY-88", "amount": 100}],
"deposit_min_amount": 100,
}
)
prescription = {
"id": 12,
"diagnosis_id": 6,
"sn": "CF-12",
"patient_name": "林晓岚",
"phone": "13800000000",
"gender": 0,
"age": 33,
"clinical_diagnosis": "脾气虚",
"doctor_name": "周医生",
"prescription_date": "2026-08-10",
"audit_status": 1,
"dose_count": 7,
"dose_unit": "剂",
"usage_days": 7,
"times_per_day": 2,
"herbs": [
{"name": "黄芪", "dosage": 15, "formula_type": "主方"},
{"name": "酸枣仁", "dosage": 12, "formula_type": "辅方"},
],
}
order = PrescriptionOrderDialog(repository, prescription)
order._load_paid_orders()
order.shipping_province.setText("四川省")
order.shipping_city.setText("成都市")
order.shipping_district.setText("双流区")
order.shipping_address.setText("黄龙大道 280 号")
order.amount.setValue(100)
order.paid_orders.item(0).setCheckState(Qt.CheckState.Checked)
payload = order.payload()
assert payload["prescription_id"] == 12
assert payload["diagnosis_id"] == 6
assert payload["pay_order_ids"] == [88]
assert payload["amount"] == 100
assert payload["ship_mode"] == "gancao"
rendered = render_prescription_html(prescription)
assert "林晓岚" in rendered
assert "黄芪" in rendered
assert "酸枣仁" in rendered
viewer = PrescriptionDetailDialog(prescription)
assert "中医处方笺" in viewer.document.toHtml()
viewer.close()
order.close()
application.processEvents()
def test_demo_repository_pages_render_offscreen(
application: QApplication,
immediate_async: None,
) -> None:
repository = DemoDoctorRepository()
user = SimpleNamespace(id=1, name="演示医生", root=1, role_ids=[0])
library = PrescriptionLibraryPage(repository, None, user)
issued = PrescriptionsPage(repository, None, user)
library.refresh()
issued.refresh()
assert library.table.rowCount() > 0
assert issued.table.rowCount() > 0
library.close()
issued.close()
application.processEvents()