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 QDate, QEvent, QObject, QSize, Qt from PySide6.QtGui import QColor, QImage, QPainter from PySide6.QtPdf import QPdfDocument from PySide6.QtWidgets import QApplication, QComboBox, QDialog, QPushButton, QWidget from doctor_workstation import app as app_module from doctor_workstation.core import PermissionSet, Prescription 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) == ("已通过", "success") 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() == 10 assert [ page.table.horizontalHeaderItem(index).text() for index in range(page.table.columnCount()) ] == [ "ID", "处方名称", "处方类型", "药材数量", "药材组成(部分)", "功效主治", "公开范围", "创建人", "创建时间", "操作", ] assert page.table.cellWidget(0, 2) is not None assert page.table.cellWidget(0, 6) is not None assert page.table.cellWidget(0, 9) is not None assert not page.view_button.isHidden() and page.view_button.isEnabled() assert not page.ai_button.isHidden() and page.ai_button.isEnabled() assert page.ai_button.text() == "AI解释" 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 assert page.metric_cards["total"].value_label.text() == "1" assert page.metric_cards["private"].value_label.text() == "1" assert page.metric_cards["public"].value_label.text() == "0" assert page.metric_cards["month"].value_label.text() == "1" 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 page.table.columnCount() == 11 assert page.table.horizontalHeaderItem(2).text() == "操作" assert page.table.cellWidget(0, 2) is not None assert page.table.cellWidget(0, 3) is not None assert page.table.cellWidget(0, 6) is not None row_edit = next( button for button in page.table.cellWidget(0, 2).findChildren(QPushButton) if button.accessibleName() == "编辑处方" ) assert row_edit.text() == "编辑" assert row_edit.isEnabled() 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_issued_row_edit_targets_clicked_prescription_without_checkbox( application: QApplication, immediate_async: None, ) -> None: requested: list[int] = [] opened: list[dict[str, Any]] = [] callbacks: list[str] = [] rows = [ { "id": 11, "sn": "CF-11", "patient_name": "患者甲", "audit_status": 0, "void_status": 0, "creator_id": 7, }, { "id": 22, "sn": "CF-22", "patient_name": "患者乙", "audit_status": 0, "void_status": 0, "creator_id": 7, }, ] class Repository: def list_diagnosis_doctors(self) -> list[dict[str, Any]]: return [] def list_prescriptions(self, **_filters: Any) -> dict[str, Any]: return {"lists": rows, "count": len(rows)} def get_prescription(self, prescription_id: int) -> dict[str, Any]: requested.append(prescription_id) return { **next(row for row in rows if row["id"] == prescription_id), "clinical_diagnosis": "脾气虚", } page = PrescriptionsPage( Repository(), PermissionSet(["cf.prescription/edit"]), SimpleNamespace(id=7, name="周医生"), ) page.refresh() page._open_editor = lambda detail: opened.append(detail) # type: ignore[method-assign] assert page.table.current_data()["id"] == 11 assert all( page.table.item(row_index, 0).checkState() == Qt.CheckState.Unchecked for row_index in range(page.table.rowCount()) ) second_actions = page.table.cellWidget(1, 2) assert second_actions is not None second_edit = next( button for button in second_actions.findChildren(QPushButton) if button.accessibleName() == "编辑处方" ) second_edit.click() application.processEvents() assert requested == [22] assert [detail["id"] for detail in opened] == [22] assert page.table.current_data()["id"] == 22 assert all( page.table.item(row_index, 0).checkState() == Qt.CheckState.Unchecked for row_index in range(page.table.rowCount()) ) page._run_row_action({"id": 999}, lambda: callbacks.append("stale")) assert callbacks == [] page._set_mutation_pending(True) assert not second_edit.isEnabled() page._set_mutation_pending(False) assert second_edit.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) # The admin editor starts with an empty table; adding a herb is explicit. editor.herbs.add_row(formula_type="主方") editor.patient_name.setText("林晓岚") editor.clinical_diagnosis.setPlainText("脾气虚") editor.herbs.rows[0].medicine.set_value(31, "黄芪") editor.herbs.rows[0].dosage.setValue(15) editor.dietary_taboo.set_values(["辛辣食物", "浓茶"]) assert editor.dietary_taboo.lineEdit().text() == "辛辣食物、浓茶" 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["dietary_taboo"] == ["辛辣食物", "浓茶"] assert payload["doctor_signature"].startswith("data:image/png;base64,") assert isinstance(payload["aux_usage"], dict) editor.close() application.processEvents() def test_prescription_workflows_never_show_orphan_child_controls( application: QApplication, immediate_async: None, ) -> None: shown: list[tuple[str, str]] = [] class OrphanShowRecorder(QObject): def eventFilter(self, watched: QObject, event: QEvent) -> bool: # noqa: N802 if ( event.type() == QEvent.Type.Show and isinstance(watched, QWidget) and not isinstance(watched, QDialog) and watched.parentWidget() is None ): text = getattr(watched, "text", lambda: "")() shown.append((type(watched).__name__, str(text))) return False repository = SimpleNamespace( list_medicines=lambda **_kwargs: { "lists": [{"id": 31, "name": "黄芪"}], "count": 1, } ) prescription = { "id": 12, "diagnosis_id": 6, "patient_name": "林晓岚", "phone": "13800000000", "audit_status": 1, "herbs": [{"medicine_id": 31, "name": "黄芪", "dosage": 15}], } recorder = OrphanShowRecorder(application) application.installEventFilter(recorder) widgets: list[QWidget] = [] try: widgets.extend( [ dialog_module.HerbRowWidget( repository, prescription["herbs"][0], show_formula=True, locked=False, ), dialog_module.HerbEditor( repository, show_formula=True, show_actions=True, ), PrescriptionEditorDialog( repository, prescription, mode="edit", permissions=PermissionSet( ["cf.prescription/edit", "tcm.prescriptionLibrary/lists"] ), ), PrescriptionDetailDialog( prescription, can_open_diagnosis=True, can_open_orders=True, ), PrescriptionOrderDialog( repository, prescription, can_view_internal_cost=True, can_edit_pharmacy_remark=True, ), ] ) finally: application.removeEventFilter(recorder) assert shown == [] for widget in widgets: widget.close() application.processEvents() def test_editor_matches_admin_four_observation_fields_and_edit_context( application: QApplication, immediate_async: None, ) -> None: calls: list[dict[str, Any]] = [] class Repository: def list_prescription_orders(self, **filters: Any) -> dict[str, Any]: calls.append(filters) return { "lists": [ { "order_no": "PO-901", "medication_days": 14, "remark_assistant": "按两周疗程复核", } ], "count": 1, } source = { "id": 91, "diagnosis_id": 501, "patient_name": "林晓岚", "visit_no": "1K00000501", "tongue": "面象哨兵", "tongue_image": "舌象哨兵", "pulse": "脉象哨兵", "pulse_condition": "脉象详情哨兵", "clinical_diagnosis": "气阴两虚", "doctor_name": "周医生", "audit_status": 2, "audit_remark": "请补充辨证依据", "void_status": 1, "is_system_auto": 1, "business_prescription_audit_rejected": 1, "business_prescription_audit_remark": "订单审核意见", "herbs": [{"medicine_id": 31, "name": "黄芪", "dosage": 15}], } editor = PrescriptionEditorDialog(Repository(), source, mode="edit") assert editor.windowTitle() == "编辑处方" assert editor.patient_name.isReadOnly() assert editor.visit_no.isReadOnly() assert not hasattr(editor, "phone") assert editor.gender_male.isChecked() assert editor.payload()["gender"] == 1 assert editor.tongue.text() == "面象哨兵" assert editor.tongue_image.text() == "舌象哨兵" assert editor.pulse.text() == "脉象哨兵" assert editor.pulse_condition.text() == "脉象详情哨兵" payload = editor.payload() assert payload["tongue"] == "面象哨兵" assert payload["tongue_image"] == "舌象哨兵" assert payload["pulse"] == "脉象哨兵" assert payload["pulse_condition"] == "脉象详情哨兵" assert "取消作废、清除驳回" in editor.context_banner.label.text() assert "系统自动生成" in editor.context_banner.label.text() assert "请补充辨证依据" in editor.context_banner.label.text() assert "业务订单侧" not in editor.context_banner.label.text() assert calls == [{"page_no": 1, "page_size": 5, "prescription_id": 91}] assert "PO-901" in editor.linked_order_banner.label.text() assert "14 天" in editor.linked_order_banner.label.text() assert "按两周疗程复核" in editor.linked_order_banner.label.text() assert editor.linked_order_banner.objectName() == "MessageBanner" assert editor.linked_order_banner.property("kind") == "warning" assert "#FDF6EC" in editor.drawer_surface.styleSheet() assert 'QFrame#MessageBanner[kind="warning"]' in editor.drawer_surface.styleSheet() editor.close() application.processEvents() def test_editor_preserves_global_lock_and_admin_type_constraints( application: QApplication, ) -> None: editor = PrescriptionEditorDialog( SimpleNamespace(), mode="add", current_user=SimpleNamespace(id=7, name="周医生"), permissions=PermissionSet([]), ) editor.herbs.set_rows( [ { "medicine_id": 31, "name": "酸枣仁", "dosage": 12, "formula_type": "辅方", "locked": True, }, {"medicine_id": 32, "name": "黄芪", "dosage": 15, "formula_type": "主方"}, ], locked=False, ) assert editor.herbs.locked assert all(row.locked for row in editor.herbs.rows) locked_payload = editor.payload()["herbs"] assert locked_payload[0]["locked"] is True assert "locked" not in locked_payload[1] assert not editor.add_main_button.isEnabled() assert editor.import_library_button.isHidden() assert editor._aux_name_field.isHidden() editor.prescription_type.setCurrentIndex(editor.prescription_type.findData("饮片")) assert editor.dosage_amount.minimum() == 50 assert editor.dosage_amount.maximum() == 250 assert editor.dosage_amount.value() == 50 assert editor.bags_per_dose.maximum() == 9 assert editor.aux_dosage_amount.value() == 50 editor.prescription_type.setCurrentIndex(editor.prescription_type.findData("浓缩水丸")) assert editor.dosage_amount.minimum() == 1 assert editor.dosage_amount.maximum() == 10 assert editor.dosage_amount.value() == 1 assert editor.dosage_bag_count.maximum() == 5 assert editor.aux_dosage_amount.value() == 5 editor.close() application.processEvents() def test_editor_usage_fields_are_dropdowns_and_date_opens_calendar( application: QApplication, ) -> None: editor = PrescriptionEditorDialog( SimpleNamespace(), { "prescription_type": "饮片", "prescription_date": "2026-08-01", "herbs": [{"medicine_id": 31, "name": "黄芪", "dosage": 15}], }, mode="edit", ) assert isinstance(editor.dosage_amount, QComboBox) assert not editor.dosage_amount.isEditable() assert editor.dosage_amount.itemText(0).endswith("ml") assert isinstance(editor.bags_per_dose, QComboBox) assert editor.bags_per_dose.itemText(0) == "1包" assert editor.bags_per_dose.maximum() == 9 assert isinstance(editor.dosage_bag_count, QComboBox) assert editor.dosage_bag_count.itemText(0) == "1袋" assert isinstance(editor.prescription_type, QComboBox) assert isinstance(editor.usage_time, QComboBox) assert isinstance(editor.usage_way, QComboBox) assert "down-arrow" in editor.drawer_surface.styleSheet() assert "__CHEVRON_URL__" not in editor.drawer_surface.styleSheet() assert editor.prescription_type.cursor().shape() == Qt.CursorShape.PointingHandCursor assert editor.date_edit.cursor().shape() == Qt.CursorShape.PointingHandCursor assert editor.date_edit.calendarPopup() calendar_button = editor.date_edit.findChild(QPushButton, "PrescriptionDateButton") assert calendar_button is not None assert not calendar_button.icon().isNull() assert editor.date_edit.date() == QDate(2026, 8, 1) editor.show() application.processEvents() editor.date_edit.open_calendar() application.processEvents() assert editor.date_edit.calendarWidget().isVisible() editor.date_edit.calendarWidget().clicked.emit(QDate(2026, 8, 13)) application.processEvents() assert not editor.date_edit.calendarWidget().isVisible() assert editor.payload()["prescription_date"] == "2026-08-13" editor.bags_per_dose.setValue(4) assert editor.payload()["bags_per_dose"] == 4 editor.close() application.processEvents() def test_editor_matches_admin_empty_rows_dosage_choices_and_detail_merge( application: QApplication, ) -> None: editor = PrescriptionEditorDialog( SimpleNamespace(), mode="add", current_user=SimpleNamespace(id=7, name="周医生"), ) assert editor.herbs.rows == [] editor.herbs.add_row(formula_type="主方") assert editor.payload()["herbs"] == [{"name": "", "dosage": 0.0, "formula_type": "主方"}] editor.need_decoction.setChecked(True) editor.prescription_type.setCurrentIndex(editor.prescription_type.findData("饮片")) assert editor.need_decoction.isChecked() assert [ editor.dosage_amount.itemData(index) for index in range(editor.dosage_amount.count()) ] == [50.0, 100.0, 120.0, 150.0, 180.0, 200.0, 250.0] editor.prescription_type.setCurrentIndex(editor.prescription_type.findData("颗粒")) assert editor.dosage_amount.optional_value() is None assert "dosage_amount" not in editor.payload() merged = prescription_module.merge_prescription_detail( { "id": 91, "audit_status": 1, "void_status": 1, "business_prescription_audit_rejected": 1, "business_prescription_audit_remark": "订单驳回", }, {"id": 91, "patient_name": "林晓岚", "herbs": []}, ) assert merged["patient_name"] == "林晓岚" assert merged["void_status"] == 1 assert merged["business_prescription_audit_rejected"] == 1 assert merged["business_prescription_audit_remark"] == "订单驳回" model_detail = Prescription.from_dict( {"id": 91, "patient_phone": "13800138000", "patient_name": "林晓岚"} ) model_merged = prescription_module.merge_prescription_detail( {"id": 91, "void_status": 1}, model_detail, ) assert model_merged["phone"] == "13800138000" assert model_merged["void_status"] == 1 editor.close() application.processEvents() def test_paste_parser_matches_admin_zero_and_note_rules() -> None: assert parse_pasted_herbs("黄芪0g") == [] assert parse_pasted_herbs("黄芪(炙)、党参各6g") == [ {"name": "黄芪", "dosage": 6.0}, {"name": "党参", "dosage": 6.0}, ] def test_editor_only_shows_business_rejection_for_consumer_approved_rows( application: QApplication, ) -> None: editor = PrescriptionEditorDialog( SimpleNamespace(), { "id": 92, "audit_status": 1, "business_prescription_audit_rejected": 1, "business_prescription_audit_remark": "重新核对剂量", "patient_name": "林晓岚", "clinical_diagnosis": "气阴两虚", "doctor_name": "周医生", "herbs": [{"name": "黄芪", "dosage": 15, "formula_type": "主方"}], }, mode="edit", ) message = editor.context_banner.label.text() assert "业务订单侧「处方审核」已驳回" in message assert "重新核对剂量" in message 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 '