This commit is contained in:
Your Name
2026-08-10 17:29:05 +08:00
parent 2199887c07
commit 9add23e019
129 changed files with 34157 additions and 59 deletions
@@ -0,0 +1,933 @@
"""Issued-prescription management matching the admin route contract."""
from __future__ import annotations
from collections.abc import Callable, Mapping
from datetime import datetime, timedelta
from typing import Any
from PySide6.QtCore import QDateTime, Qt
from PySide6.QtWidgets import (
QComboBox,
QDateTimeEdit,
QDialog,
QDialogButtonBox,
QFrame,
QGridLayout,
QHBoxLayout,
QLabel,
QLineEdit,
QListWidget,
QListWidgetItem,
QMessageBox,
QPushButton,
QStackedWidget,
QVBoxLayout,
QWidget,
)
from ..dialogs.prescription import (
AuditPrescriptionDialog,
DiagnosisDetailDialog,
PatchPatientDialog,
PrescriptionDetailDialog,
PrescriptionEditorDialog,
PrescriptionOrderDialog,
PrescriptionOrderListDialog,
)
from ..widgets import (
EmptyState,
MessageBanner,
PageHeader,
Pager,
SortableTable,
TableColumn,
display_text,
first_value,
friendly_error,
get_value,
has_permission,
invoke,
page_items,
page_total,
run_async,
show_toast,
)
def _int(value: Any, default: int = 0) -> int:
try:
return int(value)
except (TypeError, ValueError):
return default
def _truthy(value: Any) -> bool:
if isinstance(value, str):
return value.strip().lower() not in {"", "0", "false", "no", "off"}
return bool(value)
def prescription_status(row: Any) -> tuple[str, str]:
"""Return the admin-equivalent combined status label and visual kind."""
if _int(first_value(row, "void_status", "is_void"), 0) == 1:
return "已作废", "danger"
if _truthy(first_value(row, "business_prescription_audit_rejected", default=False)):
return "已驳回", "danger"
status = _int(first_value(row, "audit_status", "status", default=0), 0)
if status == 2:
return "已驳回", "danger"
if status == 1:
return "已通过", "success"
return "待审核", "warning"
def is_approved_active(row: Any) -> bool:
return (
_int(first_value(row, "audit_status", "status"), 0) == 1
and _int(first_value(row, "void_status", "is_void"), 0) != 1
)
def can_patch_patient(row: Any) -> bool:
return row is not None and _int(first_value(row, "void_status", "is_void"), 0) != 1
def can_create_order(row: Any) -> bool:
return can_patch_patient(row) and not _truthy(
first_value(row, "has_prescription_order", default=False)
)
def can_audit(row: Any) -> bool:
return (
row is not None
and _int(first_value(row, "audit_status", "status"), 0) == 0
and _int(first_value(row, "void_status", "is_void"), 0) != 1
)
def can_edit_or_delete(row: Any) -> bool:
return row is not None and not is_approved_active(row)
def _formula(value: Any) -> str:
text = str(value or "").strip().lower()
return "辅方" if text in {"2", "aux", "auxiliary", "辅方"} else "主方"
def _order_warnings(row: Any) -> list[str]:
if not _truthy(first_value(row, "has_prescription_order", default=False)):
return []
herbs = get_value(row, "herbs", None) or []
if not isinstance(herbs, (list, tuple)) or not herbs:
return ["请开方,当前处方药材为空白"]
seen: set[str] = set()
duplicate: list[str] = []
for herb in herbs:
name = str(first_value(herb, "name", "medicine_name", default="")).strip()
key = "".join(name.split()).lower()
if key and key in seen and name not in duplicate:
duplicate.append(name)
seen.add(key)
return [f"已有关联业务订单,存在重复药材:{'、'.join(duplicate)}"] if duplicate else []
def _sn_cell(_value: Any, row: Any) -> str:
sn = first_value(row, "sn", "prescription_no", "id", default="—")
prescription_id = first_value(row, "id", "prescription_id", default="—")
warnings = _order_warnings(row)
suffix = "\n" + "\n".join(warnings) if warnings else ""
return f"{sn}\nID: {prescription_id}{suffix}"
def _patient_cell(_value: Any, row: Any) -> str:
gender = first_value(row, "gender", default=None)
gender_text = "男" if gender in (1, "1") else "女" if gender in (0, "0") else "未知"
age = first_value(row, "age", default="—")
return f"{first_value(row, 'patient_name', default='—')}\n{gender_text} · {age}岁"
def _source_cell(_value: Any, row: Any) -> str:
return (
"空白处方"
if _truthy(first_value(row, "is_system_auto", "source", default=False))
else "手工"
)
def _audit_cell(_value: Any, row: Any) -> str:
label = prescription_status(row)[0]
reasons = []
if _truthy(first_value(row, "business_prescription_audit_rejected", default=False)):
reasons.append(
"业务订单审核:"
+ display_text(first_value(row, "business_prescription_audit_remark", default="—"))
)
if _int(first_value(row, "audit_status"), 0) == 2:
reasons.append(
"消费者处方审核:" + display_text(first_value(row, "audit_remark", default="—"))
)
return label + ("\n" + "\n".join(reasons) if reasons else "")
def _void_cell(_value: Any, row: Any) -> str:
return "作废" if _int(first_value(row, "void_status", "is_void"), 0) == 1 else "—"
def _doctor_cell(_value: Any, row: Any) -> str:
return (
f"{first_value(row, 'doctor_name', 'creator_name', default='—')}\n"
f"{first_value(row, 'prescription_date', default='—')}"
)
class DoctorMultiSelect(QWidget):
"""Compact checkable doctor selector fed by list rows/extend data."""
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self._options: dict[int, str] = {}
self._selected: set[int] = set()
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
self.button = QPushButton("全部医生")
self.button.clicked.connect(self._choose)
layout.addWidget(self.button)
def values(self) -> list[int]:
return sorted(self._selected)
def clear(self) -> None:
self._selected.clear()
self._update_text()
def update_options(self, rows: list[Any]) -> None:
for row in rows:
doctor_id = _int(first_value(row, "id", "creator_id", "doctor_id"), 0)
name = str(first_value(row, "name", "doctor_name", "creator_name", default="")).strip()
if doctor_id and name:
self._options[doctor_id] = name
self._update_text()
def _choose(self) -> None:
dialog = QDialog(self)
dialog.setWindowTitle("选择医生")
dialog.resize(360, 430)
layout = QVBoxLayout(dialog)
listing = QListWidget()
for doctor_id, name in sorted(self._options.items(), key=lambda item: item[1]):
item = QListWidgetItem(name)
item.setData(Qt.ItemDataRole.UserRole, doctor_id)
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
item.setCheckState(
Qt.CheckState.Checked if doctor_id in self._selected else Qt.CheckState.Unchecked
)
listing.addItem(item)
layout.addWidget(listing)
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Cancel | QDialogButtonBox.StandardButton.Ok
)
buttons.accepted.connect(dialog.accept)
buttons.rejected.connect(dialog.reject)
layout.addWidget(buttons)
if dialog.exec() != QDialog.DialogCode.Accepted:
return
self._selected = {
_int(listing.item(index).data(Qt.ItemDataRole.UserRole))
for index in range(listing.count())
if listing.item(index).checkState() == Qt.CheckState.Checked
}
self._selected.discard(0)
self._update_text()
def _update_text(self) -> None:
if not self._selected:
self.button.setText("全部医生")
return
names = [self._options.get(value, str(value)) for value in sorted(self._selected)]
self.button.setText("、".join(names[:2]) + (f" 等{len(names)}人" if len(names) > 2 else ""))
class PrescriptionsPage(QWidget):
"""Complete issued-prescription route: list, workflow actions, print, and orders."""
def __init__(
self,
repository: Any,
permissions: Any = None,
current_user: Any = None,
parent: QWidget | None = None,
) -> None:
super().__init__(parent)
self.repository = repository
self.permissions = permissions
self.current_user = current_user
self._page = 1
self._page_size = 15
self._generation = 0
self._loading = False
self._refresh_pending = False
self._detail_generation = 0
self._detail_target = 0
self._diagnosis_detail_generation = 0
self._diagnosis_detail_target = 0
self._mutation_pending = False
root = QVBoxLayout(self)
root.setContentsMargins(24, 20, 24, 24)
root.setSpacing(14)
header = PageHeader(
"已开处方",
"管理处方审核、患者修正与履约订单;已通过且未作废的处方只允许查看。",
)
self.orders_button = QPushButton("业务订单")
self.orders_button.setVisible(has_permission(permissions, "tcm.prescriptionOrder/lists"))
self.orders_button.clicked.connect(lambda: self._open_orders())
header.add_action(self.orders_button)
self.add_button = QPushButton("+ 新增处方")
self.add_button.setProperty("variant", "primary")
self.add_button.setVisible(has_permission(permissions, "cf.prescription/add"))
self.add_button.clicked.connect(self._add_prescription)
header.add_action(self.add_button)
root.addWidget(header)
root.addWidget(self._build_filters())
self.banner = MessageBanner()
root.addWidget(self.banner)
root.addWidget(self._build_table_card(), 1)
self._load_doctor_options()
def _build_filters(self) -> QWidget:
frame = QFrame()
frame.setObjectName("FilterBar")
grid = QGridLayout(frame)
grid.setContentsMargins(14, 12, 14, 12)
grid.setHorizontalSpacing(9)
grid.setVerticalSpacing(8)
self.quick_date = QComboBox()
self.quick_date.addItem("全部时间", "all")
self.quick_date.addItem("今日", "today")
self.quick_date.addItem("昨日", "yesterday")
self.quick_date.addItem("前天", "before_yesterday")
self.quick_date.addItem("自定义", "custom")
self.quick_date.currentIndexChanged.connect(self._quick_date_changed)
grid.addWidget(self.quick_date, 0, 0)
self.start_time = QDateTimeEdit(QDateTime.currentDateTime().addDays(-7))
self.start_time.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
self.start_time.setCalendarPopup(True)
self.start_time.setEnabled(False)
grid.addWidget(self.start_time, 0, 1)
self.end_time = QDateTimeEdit(QDateTime.currentDateTime())
self.end_time.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
self.end_time.setCalendarPopup(True)
self.end_time.setEnabled(False)
grid.addWidget(self.end_time, 0, 2)
self.audit_filter = QComboBox()
self.audit_filter.addItem("待审核", "pending")
self.audit_filter.addItem("全部审核状态", "all")
self.audit_filter.addItem("已通过", "passed")
self.audit_filter.addItem("未通过", "not_passed")
self.audit_filter.addItem("已驳回", "rejected")
self.audit_filter.setCurrentIndex(1)
grid.addWidget(self.audit_filter, 0, 3)
self.source_filter = QComboBox()
self.source_filter.addItem("全部来源", "all")
self.source_filter.addItem("手工", "manual")
self.source_filter.addItem("空白处方", "system")
grid.addWidget(self.source_filter, 0, 4)
self.sn_filter = QLineEdit()
self.sn_filter.setPlaceholderText("处方编号")
self.sn_filter.setClearButtonEnabled(True)
self.sn_filter.returnPressed.connect(self._search)
grid.addWidget(self.sn_filter, 1, 0)
self.patient_filter = QLineEdit()
self.patient_filter.setPlaceholderText("患者姓名")
self.patient_filter.setClearButtonEnabled(True)
self.patient_filter.returnPressed.connect(self._search)
grid.addWidget(self.patient_filter, 1, 1)
self.doctor_filter = DoctorMultiSelect()
current_id = _int(first_value(self.current_user, "id", "user_id"), 0)
current_name = str(first_value(self.current_user, "name", "real_name", default="")).strip()
if current_id and current_name:
self.doctor_filter.update_options([{"id": current_id, "name": current_name}])
grid.addWidget(self.doctor_filter, 1, 2)
query = QPushButton("查询")
query.setProperty("variant", "secondary")
query.clicked.connect(self._search)
grid.addWidget(query, 1, 3)
reset = QPushButton("重置")
reset.setProperty("variant", "ghost")
reset.clicked.connect(self._reset_filters)
grid.addWidget(reset, 1, 4)
grid.setColumnStretch(1, 1)
grid.setColumnStretch(2, 1)
return frame
def _build_table_card(self) -> QWidget:
card = QFrame()
card.setObjectName("Card")
layout = QVBoxLayout(card)
layout.setContentsMargins(14, 13, 14, 13)
layout.setSpacing(9)
toolbar = QHBoxLayout()
title = QLabel("处方列表")
title.setProperty("role", "sectionTitle")
toolbar.addWidget(title)
toolbar.addStretch(1)
self.view_button = self._action_button("查看", "cf.prescription/read", self._view_selected)
toolbar.addWidget(self.view_button)
self.patch_button = self._action_button(
"修正患者", "tcm.prescription/patchPatient", self._patch_selected
)
toolbar.addWidget(self.patch_button)
self.create_order_button = self._action_button(
"创建订单", "tcm.prescriptionOrder/create", self._create_order
)
toolbar.addWidget(self.create_order_button)
self.edit_button = self._action_button("编辑", "cf.prescription/edit", self._edit_selected)
toolbar.addWidget(self.edit_button)
self.audit_button = self._action_button(
"审核", "cf.prescription/audit", self._audit_selected
)
toolbar.addWidget(self.audit_button)
self.delete_button = self._action_button(
"删除", "cf.prescription/del", self._delete_selected, danger=True
)
toolbar.addWidget(self.delete_button)
refresh = QPushButton("刷新")
refresh.setProperty("variant", "ghost")
refresh.clicked.connect(self.refresh)
toolbar.addWidget(refresh)
layout.addLayout(toolbar)
self.stack = QStackedWidget()
table_host = QWidget()
table_layout = QVBoxLayout(table_host)
table_layout.setContentsMargins(0, 0, 0, 0)
self.table = SortableTable(
[
TableColumn("sn", "处方编号", 190, _sn_cell),
TableColumn("prescription_type", "处方类型", 95),
TableColumn("is_system_auto", "来源", 90, _source_cell),
TableColumn("patient_name", "患者信息", 120, _patient_cell),
TableColumn("audit_status", "审核状态", 220, _audit_cell),
TableColumn("void_status", "作废", 70, _void_cell),
TableColumn("doctor_name", "医生信息", 130, _doctor_cell),
TableColumn("assistant_name", "医助", 90),
TableColumn("create_time", "创建时间", 145),
]
)
self.table.itemSelectionChanged.connect(self._selection_changed)
self.table.itemDoubleClicked.connect(lambda _item: self._view_selected())
table_layout.addWidget(self.table, 1)
self.pager = Pager(self._page_size)
self.pager.page_changed.connect(self._change_page)
table_layout.addWidget(self.pager)
self.stack.addWidget(table_host)
self.stack.addWidget(
EmptyState("没有找到处方", "请调整创建时间、审核状态或患者筛选后重试。")
)
layout.addWidget(self.stack, 1)
return card
def _action_button(
self,
text: str,
permission: str,
handler: Callable[[], None],
*,
danger: bool = False,
) -> QPushButton:
button = QPushButton(text)
if danger:
button.setProperty("variant", "danger")
button.setVisible(has_permission(self.permissions, permission))
button.setEnabled(False)
button.clicked.connect(handler)
return button
def _load_doctor_options(self) -> None:
"""Populate the creator multi-select from the repository's doctor catalog."""
method = getattr(self.repository, "list_diagnosis_doctors", None)
if not callable(method):
return
run_async(
method,
on_success=lambda result: self.doctor_filter.update_options(page_items(result)),
)
def _quick_date_changed(self) -> None:
custom = self.quick_date.currentData() == "custom"
self.start_time.setEnabled(custom)
self.end_time.setEnabled(custom)
self._search()
def _date_filters(self) -> tuple[str, str]:
value = self.quick_date.currentData()
if value == "all":
return "", ""
if value == "custom":
return (
self.start_time.dateTime().toString("yyyy-MM-dd HH:mm:ss"),
self.end_time.dateTime().toString("yyyy-MM-dd HH:mm:ss"),
)
offset = {"today": 0, "yesterday": 1, "before_yesterday": 2}.get(value, 0)
target = datetime.now().date() - timedelta(days=offset)
return (
f"{target.isoformat()} 00:00:00",
f"{target.isoformat()} 23:59:59",
)
def _search(self) -> None:
self._page = 1
self.refresh()
def _reset_filters(self) -> None:
self.quick_date.blockSignals(True)
self.quick_date.setCurrentIndex(0)
self.quick_date.blockSignals(False)
self.start_time.setEnabled(False)
self.end_time.setEnabled(False)
self.audit_filter.setCurrentIndex(1)
self.source_filter.setCurrentIndex(0)
self.sn_filter.clear()
self.patient_filter.clear()
self.doctor_filter.clear()
self._search()
def _change_page(self, page: int) -> None:
self._page = page
self.refresh()
def refresh(self) -> None:
if self._loading:
self._refresh_pending = True
return
self._loading = True
self._refresh_pending = False
self._generation += 1
generation = self._generation
requested_page = self._page
page_size = self._page_size
start_time, end_time = self._date_filters()
filters: dict[str, Any] = {
"sn": self.sn_filter.text().strip(),
"patient_name": self.patient_filter.text().strip(),
"audit_filter": ""
if self.audit_filter.currentData() == "all"
else self.audit_filter.currentData(),
"source_filter": ""
if self.source_filter.currentData() == "all"
else self.source_filter.currentData(),
"start_time": start_time,
"end_time": end_time,
}
creator_ids = self.doctor_filter.values()
if creator_ids:
filters["creator_ids"] = creator_ids
self.banner.show_message("正在加载处方列表…", "info")
run_async(
lambda: invoke(
self.repository,
"prescriptions",
page=requested_page,
page_size=page_size,
**filters,
),
on_success=lambda result: self._apply_result(result, generation, requested_page),
on_error=lambda error: self._load_error(error, generation),
on_finished=lambda: self._load_finished(generation),
)
def _apply_result(self, result: Any, generation: int, requested_page: int) -> None:
if generation != self._generation:
return
rows = page_items(result)
self.table.set_rows(rows)
self.pager.update_state(requested_page, page_total(result, len(rows)))
self.stack.setCurrentIndex(0 if rows else 1)
doctor_rows = []
for row in rows:
doctor_rows.append(
{
"id": first_value(row, "creator_id", "doctor_id"),
"name": first_value(row, "doctor_name", "creator_name"),
}
)
extend_doctors = get_value(result, "extend.doctors", None) or get_value(
result, "doctors", None
)
if isinstance(extend_doctors, (list, tuple)):
doctor_rows.extend(extend_doctors)
self.doctor_filter.update_options(doctor_rows)
self.banner.clear()
if rows and self.table.currentRow() < 0:
self.table.selectRow(0)
self._selection_changed()
def _load_error(self, error: Exception, generation: int) -> None:
if generation == self._generation:
self.banner.show_message(friendly_error(error), "danger")
def _load_finished(self, generation: int) -> None:
if generation == self._generation:
self._loading = False
if self._refresh_pending:
self._refresh_pending = False
self.refresh()
def _selection_changed(self) -> None:
row = self.table.current_data()
active = not self._mutation_pending
self.view_button.setEnabled(active and row is not None)
self.patch_button.setEnabled(active and can_patch_patient(row))
self.create_order_button.setEnabled(active and can_create_order(row))
self.edit_button.setEnabled(active and can_edit_or_delete(row))
self.audit_button.setEnabled(active and can_audit(row))
self.delete_button.setEnabled(active and can_edit_or_delete(row))
def _set_mutation_pending(self, pending: bool) -> None:
self._mutation_pending = pending
self.add_button.setEnabled(not pending)
self.orders_button.setEnabled(not pending)
self._selection_changed()
def _selected(self) -> Any:
return self.table.current_data()
def _get_prescription(self, prescription_id: int) -> Any:
method = self.repository.get_prescription
return method(prescription_id)
def _load_detail(
self,
row: Any,
callback: Callable[[Any], None],
*,
message: str = "正在加载处方详情…",
) -> None:
prescription_id = _int(first_value(row, "id", "prescription_id"), 0)
if not prescription_id:
self.banner.show_message("处方 ID 无效。", "warning")
return
self._detail_generation += 1
generation = self._detail_generation
self._detail_target = prescription_id
self.banner.show_message(message, "info")
run_async(
lambda: self._get_prescription(prescription_id),
on_success=lambda detail: self._detail_success(
detail, prescription_id, generation, callback
),
on_error=lambda error: self._detail_error(error, prescription_id, generation),
)
def _detail_success(
self,
detail: Any,
prescription_id: int,
generation: int,
callback: Callable[[Any], None],
) -> None:
if generation != self._detail_generation or prescription_id != self._detail_target:
return
self.banner.clear()
callback(detail)
def _detail_error(self, error: Exception, prescription_id: int, generation: int) -> None:
if generation == self._detail_generation and prescription_id == self._detail_target:
self.banner.show_message(friendly_error(error), "danger")
def _view_selected(self) -> None:
row = self._selected()
if row is None or not has_permission(self.permissions, "cf.prescription/read"):
return
self._load_detail(row, self._show_detail)
def _show_detail(self, detail: Any) -> None:
if not has_permission(self.permissions, "cf.prescription/read"):
return
dialog = PrescriptionDetailDialog(
detail,
can_open_diagnosis=has_permission(self.permissions, "tcm.diagnosis/readonlyDetail"),
can_open_orders=has_permission(self.permissions, "tcm.prescriptionOrder/lists"),
parent=self,
)
dialog.diagnosis_requested.connect(self._open_diagnosis)
dialog.orders_requested.connect(lambda prescription_id: self._open_orders(prescription_id))
dialog.exec()
def _open_diagnosis(self, diagnosis_id: int) -> None:
if not has_permission(self.permissions, "tcm.diagnosis/readonlyDetail"):
self.banner.show_message("无权查看诊单详情。", "danger")
return
diagnosis_id = _int(diagnosis_id, 0)
if diagnosis_id <= 0:
self.banner.show_message("诊单 ID 无效。", "warning")
return
method = getattr(self.repository, "get_diagnosis_detail", None)
readonly_keyword = True
if not callable(method):
method = getattr(self.repository, "diagnosis_readonly_detail", None)
readonly_keyword = False
if not callable(method):
self.banner.show_message("当前 repository 不支持诊单详情。", "danger")
return
self._diagnosis_detail_generation += 1
generation = self._diagnosis_detail_generation
self._diagnosis_detail_target = diagnosis_id
self.banner.show_message("正在加载诊单详情…", "info")
run_async(
lambda: (
method(diagnosis_id, readonly=True) if readonly_keyword else method(diagnosis_id)
),
on_success=lambda detail: self._diagnosis_detail_success(
detail, diagnosis_id, generation
),
on_error=lambda error: self._diagnosis_detail_error(error, diagnosis_id, generation),
)
def _diagnosis_detail_success(self, detail: Any, diagnosis_id: int, generation: int) -> None:
if (
generation != self._diagnosis_detail_generation
or diagnosis_id != self._diagnosis_detail_target
):
return
self.banner.clear()
DiagnosisDetailDialog(detail, self).exec()
def _diagnosis_detail_error(self, error: Exception, diagnosis_id: int, generation: int) -> None:
if (
generation == self._diagnosis_detail_generation
and diagnosis_id == self._diagnosis_detail_target
):
self.banner.show_message(friendly_error(error), "danger")
def _add_prescription(self) -> None:
if not has_permission(self.permissions, "cf.prescription/add"):
return
dialog = PrescriptionEditorDialog(
self.repository,
mode="add",
current_user=self.current_user,
parent=self,
)
if dialog.exec() == QDialog.DialogCode.Accepted:
self._save_prescription(dialog.payload(), None)
def _edit_selected(self) -> None:
row = self._selected()
if (
row is None
or not has_permission(self.permissions, "cf.prescription/edit")
or not can_edit_or_delete(row)
):
return
self._load_detail(row, self._open_editor, message="正在准备编辑处方…")
def _open_editor(self, detail: Any) -> None:
dialog = PrescriptionEditorDialog(
self.repository,
detail,
mode="edit",
current_user=self.current_user,
parent=self,
)
if dialog.exec() == QDialog.DialogCode.Accepted:
self._save_prescription(
dialog.payload(),
_int(first_value(detail, "id", "prescription_id"), 0),
)
def _save_prescription(
self,
payload: dict[str, Any],
prescription_id: int | None,
) -> None:
permission = "cf.prescription/edit" if prescription_id else "cf.prescription/add"
if not has_permission(self.permissions, permission):
return
self._set_mutation_pending(True)
if prescription_id:
def operation() -> Any:
return self.repository.update_prescription(
prescription_id,
changes=payload,
)
message = "处方已保存并重新进入待审核。"
else:
def operation() -> Any:
return self.repository.create_prescription(payload)
message = "处方已新增并提交审核。"
run_async(
operation,
on_success=lambda _result: self._mutation_success(message),
on_error=self._mutation_error,
on_finished=lambda: self._set_mutation_pending(False),
)
def _delete_selected(self) -> None:
row = self._selected()
if (
row is None
or not has_permission(self.permissions, "cf.prescription/del")
or not can_edit_or_delete(row)
):
return
answer = QMessageBox.warning(
self,
"删除处方",
"确定删除该处方吗?此操作无法撤销。",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.Cancel,
QMessageBox.StandardButton.Cancel,
)
if answer != QMessageBox.StandardButton.Yes:
return
prescription_id = _int(first_value(row, "id", "prescription_id"), 0)
self._set_mutation_pending(True)
run_async(
lambda: self.repository.delete_prescription(prescription_id),
on_success=lambda _result: self._mutation_success("处方已删除。"),
on_error=self._mutation_error,
on_finished=lambda: self._set_mutation_pending(False),
)
def _patch_selected(self) -> None:
row = self._selected()
if (
row is None
or not has_permission(self.permissions, "tcm.prescription/patchPatient")
or not can_patch_patient(row)
):
return
dialog = PatchPatientDialog(row, self)
if dialog.exec() != QDialog.DialogCode.Accepted:
return
payload = dialog.payload()
self._set_mutation_pending(True)
run_async(
lambda: self.repository.patch_prescription_patient(
payload["id"],
patient_name=payload["patient_name"],
phone=payload["phone"],
gender=payload["gender"],
),
on_success=lambda _result: self._mutation_success("患者信息已修正。"),
on_error=self._mutation_error,
on_finished=lambda: self._set_mutation_pending(False),
)
def _audit_selected(self) -> None:
row = self._selected()
if (
row is None
or not has_permission(self.permissions, "cf.prescription/audit")
or not can_audit(row)
):
return
dialog = AuditPrescriptionDialog(row, self)
if dialog.exec() != QDialog.DialogCode.Accepted:
return
payload = dialog.payload()
self._set_mutation_pending(True)
run_async(
lambda: self.repository.audit_prescription(
payload["id"],
action=payload["action"],
remark=payload["remark"],
),
on_success=lambda result: self._audit_success(result, payload["action"]),
on_error=self._mutation_error,
on_finished=lambda: self._set_mutation_pending(False),
)
def _audit_success(self, result: Any, action: str) -> None:
message = "处方已驳回并作废。" if action == "reject" else "处方审核已通过。"
self._mutation_success(message)
if (
isinstance(result, Mapping)
and result.get("wecom_notify_ok") is False
and result.get("wecom_notify_hint")
):
show_toast(self, str(result["wecom_notify_hint"]), "warning", 5000)
def _create_order(self) -> None:
row = self._selected()
if (
row is None
or not has_permission(self.permissions, "tcm.prescriptionOrder/create")
or not can_create_order(row)
):
return
self._load_detail(row, self._open_order_editor, message="正在准备业务订单…")
def _open_order_editor(self, detail: Any) -> None:
if not has_permission(self.permissions, "tcm.prescriptionOrder/create"):
return
dialog = PrescriptionOrderDialog(
self.repository,
detail,
can_select_ship_mode=has_permission(
self.permissions, "tcm.prescriptionOrder/setShipMode"
),
can_view_internal_cost=has_permission(self.permissions, "finance.account_log/lists"),
can_edit_pharmacy_remark=has_permission(
self.permissions, "tcm.prescriptionOrder/editRemarkExtra"
),
parent=self,
)
if dialog.exec() != QDialog.DialogCode.Accepted:
return
payload = dialog.payload()
self._set_mutation_pending(True)
run_async(
lambda: self.repository.create_prescription_order(payload),
on_success=lambda result: self._order_created(result),
on_error=self._mutation_error,
on_finished=lambda: self._set_mutation_pending(False),
)
def _order_created(self, result: Any) -> None:
order_no = first_value(result, "order_no", "data.order_no", default="")
message = f"业务订单已创建:{order_no}" if order_no else "业务订单已创建。"
self._mutation_success(message)
def _open_orders(self, prescription_id: int | None = None) -> None:
if not has_permission(self.permissions, "tcm.prescriptionOrder/lists"):
return
PrescriptionOrderListDialog(
self.repository,
prescription_id=prescription_id,
parent=self,
).exec()
def _mutation_success(self, message: str) -> None:
self.banner.clear()
show_toast(self, message, "success", 3600)
self.refresh()
def _mutation_error(self, error: Exception) -> None:
message = friendly_error(error)
self.banner.show_message(message, "danger")
show_toast(self, message, "danger", 5000)
def showEvent(self, event: Any) -> None:
super().showEvent(event)
if self.table.rowCount() == 0 and not self._loading:
self.refresh()
__all__ = [
"PrescriptionsPage",
"can_audit",
"can_create_order",
"can_edit_or_delete",
"can_patch_patient",
"is_approved_active",
"prescription_status",
]