Files
zyt/app/tests/test_prescription_ui.py
T
2026-08-11 17:39:41 +08:00

486 lines
16 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
build_prescription_clinical_diagnosis,
build_prescription_visit_no,
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_prescription_seed_helpers_match_admin_visit_no_and_clinical() -> None:
assert build_prescription_visit_no(diagnosis_id=15534) == "1K00015534"
assert build_prescription_visit_no(appointment_id=401, diagnosis_id=15534) == "1K00000401"
assert build_prescription_visit_no() == ""
assert build_prescription_clinical_diagnosis({"diagnosis_type": "follow_up"}) == "复诊"
assert build_prescription_clinical_diagnosis({"clinical_diagnosis": "follow_up"}) == "复诊"
assert (
build_prescription_clinical_diagnosis(
{"symptoms": "肝郁脾虚", "diagnosis_type": "follow_up"}
)
== "肝郁脾虚"
)
assert build_prescription_clinical_diagnosis({"clinical_diagnosis": "气阴两虚"}) == "气阴两虚"
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,
"dosage_amount": 5,
"dosage_bag_count": 2,
"dosage_unit": "g",
"usage_way": "温水送服",
"usage_time": "饭后",
"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
assert "服药前请核对姓名、电话、医生等信息" in rendered
assert "Rp." in rendered
assert "药房联" in rendered
assert "主方" in rendered
assert "辅方" in rendered
assert "105克" in rendered
assert "84克" in rendered
assert "单剂量:</span> 27克" in rendered
assert "每天2次, 一次2袋, 每袋5g, 温水送服, 饭后" in rendered
viewer = PrescriptionDetailDialog(prescription)
document_html = viewer.document.toHtml()
assert "药房联" in document_html
assert "用药 (单剂)" in document_html
viewer.close()
order.close()
application.processEvents()
def test_prescription_detail_can_open_immutable_case_record_tab(
application: QApplication,
) -> None:
prescription = {
"id": 12,
"diagnosis_id": 6,
"patient_name": "林晓岚",
"case_record": {
"patient_name": "林晓岚",
"phone": "13800138000",
"local_hospital_name": "市中医院",
"symptoms": "口渴乏力",
"tongue_coating": "舌红少苔",
},
}
viewer = PrescriptionDetailDialog(prescription, initial_tab="case")
assert viewer.tabs.count() == 2
assert viewer.tabs.tabText(viewer.tabs.currentIndex()) == "详细病历"
assert viewer.case_document is not None
case_html = viewer.case_document.toHtml()
assert "市中医院" in case_html
assert "口渴乏力" in case_html
viewer.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()