更新
This commit is contained in:
@@ -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",
|
||||
]
|
||||
Reference in New Issue
Block a user