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 '