Files
HIS-GUI/gui/pages/patients_page.cpp
2026-04-07 21:30:39 +08:00

1685 lines
70 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "mainwindow.h"
#include <QComboBox>
#include <QDialog>
#include <QFormLayout>
#include <QGroupBox>
#include <QHeaderView>
#include <QHBoxLayout>
#include <QInputDialog>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QSpinBox>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QTextEdit>
#include <QVBoxLayout>
#include <vector>
#include <ctime>
#include <algorithm>
#include "core/his_core.h"
#include "models/patient.h"
#include "utils/logger.h"
#include "utils/input_validator.h"
#include "dialogs/patient_dialog.h"
#include "dialogs/payment_dialog.h"
#include "dialogs/settlement_dialog.h"
void MainWindow::setupPatientsTab() {
patientsTab_ = new QWidget(this);
auto* layout = new QVBoxLayout(patientsTab_);
patientSearchBox_ = new QLineEdit(patientsTab_);
patientSearchBox_->setPlaceholderText(tr("搜索患者..."));
connect(patientSearchBox_, &QLineEdit::textChanged, this, &MainWindow::onPatientSearch);
auto* toolBar1 = new QHBoxLayout();
toolBar1->addWidget(patientSearchBox_);
patientAddButton_ = new QPushButton(tr("添加患者"), patientsTab_);
patientEditButton_ = new QPushButton(tr("编辑患者"), patientsTab_);
patientRemoveButton_ = new QPushButton(tr("删除患者"), patientsTab_);
patientViewCaseButton_ = new QPushButton(tr("查看病例信息"), patientsTab_);
toolBar1->addWidget(patientAddButton_);
toolBar1->addWidget(patientEditButton_);
toolBar1->addWidget(patientRemoveButton_);
toolBar1->addWidget(patientViewCaseButton_);
toolBar1->addStretch(1);
layout->addLayout(toolBar1);
auto* toolBar2 = new QHBoxLayout();
patientRegistrationButton_ = new QPushButton(tr("挂号"), patientsTab_);
patientAddCheckRecordButton_ = new QPushButton(tr("添加检查记录"), patientsTab_);
patientAddDiagnosisButton_ = new QPushButton(tr("添加诊断记录"), patientsTab_);
patientAddMedicineRecordButton_ = new QPushButton(tr("添加用药记录"), patientsTab_);
patientAdmitButton_ = new QPushButton(tr("入院"), patientsTab_);
patientAddSurgeryRecordButton_ = new QPushButton(tr("添加手术记录"), patientsTab_);
patientDischargeButton_ = new QPushButton(tr("出院"), patientsTab_);
patientPaymentButton_ = new QPushButton(tr("缴费"), patientsTab_);
toolBar2->addWidget(patientRegistrationButton_);
toolBar2->addWidget(patientAddCheckRecordButton_);
toolBar2->addWidget(patientAddDiagnosisButton_);
toolBar2->addWidget(patientAddMedicineRecordButton_);
toolBar2->addWidget(patientAdmitButton_);
toolBar2->addWidget(patientAddSurgeryRecordButton_);
toolBar2->addWidget(patientDischargeButton_);
toolBar2->addWidget(patientPaymentButton_);
toolBar2->addStretch(1);
layout->addLayout(toolBar2);
auto* rowLayout = new QHBoxLayout();
patientTable_ = new QTableWidget(0, 6, patientsTab_);
patientTable_->setHorizontalHeaderLabels({tr("患者ID"), tr("姓名"), tr("性别"), tr("年龄"), tr("手机号"), tr("状态")});
patientTable_->setSelectionBehavior(QAbstractItemView::SelectRows);
patientTable_->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed);
patientTable_->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
patientTable_->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
patientTable_->horizontalHeader()->setStretchLastSection(false);
patientTable_->setSortingEnabled(true);
patientTable_->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
patientCaseDetail_ = new QTextEdit(patientsTab_);
patientCaseDetail_->setReadOnly(true);
patientCaseDetail_->setPlaceholderText(tr("选择患者以查看详细病例记录..."));
rowLayout->addWidget(patientTable_, 3);
rowLayout->addWidget(patientCaseDetail_, 2);
layout->addLayout(rowLayout);
connect(patientAddButton_, &QPushButton::clicked, this, &MainWindow::handleAddPatient);
connect(patientEditButton_, &QPushButton::clicked, this, &MainWindow::handleEditPatient);
connect(patientRemoveButton_, &QPushButton::clicked, this, &MainWindow::handleRemovePatient);
connect(patientViewCaseButton_, &QPushButton::clicked, this, &MainWindow::handleViewPatientCase);
connect(patientAdmitButton_, &QPushButton::clicked, this, &MainWindow::handleAdmitPatient);
connect(patientDischargeButton_, &QPushButton::clicked, this, &MainWindow::handleDischargePatient);
connect(patientPaymentButton_, &QPushButton::clicked, this, &MainWindow::handlePatientPayment);
connect(patientAddDiagnosisButton_, &QPushButton::clicked, this, &MainWindow::handleAddDiagnosisRecord);
connect(patientAddSurgeryRecordButton_, &QPushButton::clicked, this, &MainWindow::handleAddSurgeryRecord);
connect(patientAddMedicineRecordButton_, &QPushButton::clicked, this, &MainWindow::handleAddMedicineRecord);
connect(patientAddCheckRecordButton_, &QPushButton::clicked, this, &MainWindow::handleAddCheckRecord);
connect(patientRegistrationButton_, &QPushButton::clicked, this, &MainWindow::handleRegistration);
connect(patientTable_, &QTableWidget::itemSelectionChanged, this, &MainWindow::updatePatientCaseDetail);
connect(patientTable_, &QTableWidget::cellChanged, this, &MainWindow::onPatientCellChanged);
}
void MainWindow::onPatientCellChanged(int row, int column) {
QTableWidgetItem* idItem = patientTable_->item(row, 0);
if (!idItem) return;
QString pid = idItem->text();
if (pid.isEmpty()) return;
auto* p = core_.patientService.findPatient(pid.toStdString());
if (!p) return;
QTableWidgetItem* nameItem = patientTable_->item(row, 1);
QTableWidgetItem* genderItem = patientTable_->item(row, 2);
QTableWidgetItem* ageItem = patientTable_->item(row, 3);
QTableWidgetItem* contactItem = patientTable_->item(row, 4);
QTableWidgetItem* statusItem = patientTable_->item(row, 5);
std::string modifyDetails;
if (nameItem && column == 1) {
p->Name = nameItem->text().toStdString();
modifyDetails = "修改项: 姓名 | 新值: " + p->Name;
}
if (genderItem && column == 2) {
p->Gender = genderItem->text().toStdString();
modifyDetails = "修改项: 性别 | 新值: " + p->Gender;
}
if (ageItem && column == 3) {
p->Age = ageItem->text().toInt();
modifyDetails = "修改项: 年龄 | 新值: " + std::to_string(p->Age);
}
if (contactItem && column == 4) {
p->Contact = contactItem->text().toStdString();
modifyDetails = "修改项: 手机号 | 新值: " + p->Contact;
}
if (statusItem && column == 5) {
p->Status = parsePatientStatus(statusItem->text());
modifyDetails = "修改项: 状态 | 新值: " + patientStatusToText(p->Status).toStdString();
}
if (!modifyDetails.empty()) {
std::string details = "患者ID: " + pid.toStdString() + " | 患者名: " + p->Name + " | " + modifyDetails;
logOperation(LogEntryType::PATIENT_OPERATION, "MODIFY_PATIENT", details, pid.toStdString());
}
}
void MainWindow::refreshPatientTable() {
patientTable_->blockSignals(true);
patientTable_->setSortingEnabled(false);
patientTable_->setRowCount(0);
int row = 0;
core_.patientService.for_eachPatient([this, &row](const std::string&, const Patient& patient) {
patientTable_->insertRow(row);
patientTable_->setItem(row, 0, new QTableWidgetItem(QString::fromStdString(patient.PatientID)));
patientTable_->setItem(row, 1, new QTableWidgetItem(QString::fromStdString(patient.Name)));
patientTable_->setItem(row, 2, new QTableWidgetItem(QString::fromStdString(patient.Gender)));
QTableWidgetItem* ageItem = new QTableWidgetItem();
ageItem->setData(Qt::DisplayRole, patient.Age);
patientTable_->setItem(row, 3, ageItem);
patientTable_->setItem(row, 4, new QTableWidgetItem(QString::fromStdString(patient.Contact)));
patientTable_->setItem(row, 5, new QTableWidgetItem(patientStatusToText(patient.Status)));
row++;
});
patientTable_->setSortingEnabled(true);
patientTable_->blockSignals(false);
}
void MainWindow::updatePatientCaseDetail() {
QString patientId = safeCurrentTableId(patientTable_);
if (patientId.isEmpty()) {
patientCaseDetail_->setText(tr("请选择患者以查看病例。"));
return;
}
PatientCase* pc = core_.patientCaseService.getCase(patientId.toStdString());
if (!pc) {
patientCaseDetail_->setText(tr("当前患者没有病例记录。"));
return;
}
QString text;
text += tr("PatientID: %1\n").arg(patientId);
text += tr("预约记录数量: %1\n").arg((int)pc->getAppointmentRecordCount());
text += tr("检查记录数量: %1\n").arg((int)pc->getCheckRecordCount());
text += tr("诊断记录数量: %1\n").arg((int)pc->getDiagnosisRecordCount());
text += tr("用药记录数量: %1\n").arg((int)pc->getMedicineRecordCount());
text += tr("手术记录数量: %1\n").arg((int)pc->getSurgeryRecordCount());
text += tr("住院记录数量: %1\n\n").arg((int)pc->getAdmissionRecordCount());
text += tr("--- 预约记录 ---\n");
for (const auto& r : pc->AppointmentRecords) {
text += tr("医生: %1, 患者: %2, 日期: %3, 备注: %4, 时间: %5\n")
.arg(QString::fromStdString(r.DoctorID))
.arg(QString::fromStdString(r.PatientID))
.arg(QString::fromStdString(r.AppointmentDate))
.arg(QString::fromStdString(r.Notes))
.arg(formatDate(r.Timestamp));
}
text += tr("\n--- 检查记录 ---\n");
for (const auto& r : pc->CheckRecords) {
text += tr("检查: %1, 价格: %2, 医生: %3, 时间: %4\n")
.arg(QString::fromStdString(r.CheckName))
.arg(r.Price)
.arg(QString::fromStdString(r.DoctorID))
.arg(formatDate(r.Timestamp));
}
text += tr("\n--- 诊断记录 ---\n");
for (const auto& r : pc->DiagnosisRecords) {
text += tr("医生: %1, 诊断: %2, 处方: %3, 备注: %4, 时间: %5\n")
.arg(QString::fromStdString(r.DoctorID))
.arg(QString::fromStdString(r.Diagnosis))
.arg(QString::fromStdString(r.Prescription))
.arg(QString::fromStdString(r.Remarks))
.arg(formatDate(r.Timestamp));
}
text += tr("\n--- 用药记录 ---\n");
for (const auto& r : pc->MedicineRecords) {
text += tr("药品: %1, 数量: %2, 用法: %3, 单价: %4, 医生: %5, 时间: %6\n")
.arg(QString::fromStdString(r.MedicineName))
.arg(r.Quantity)
.arg(QString::fromStdString(r.Usage))
.arg(r.UnitPrice)
.arg(QString::fromStdString(r.DoctorID))
.arg(formatDate(r.Timestamp));
}
text += tr("\n--- 手术记录 ---\n");
for (const auto& r : pc->SurgeryRecords) {
text += tr("手术名称: %1, 类型: %2, 主刀医生: %3\n")
.arg(QString::fromStdString(r.SurgeryName))
.arg(QString::fromStdString(r.SurgeryType))
.arg(QString::fromStdString(r.SurgeonID));
text += tr(" 麻醉方式: %1, 麻醉医生: %2\n")
.arg(QString::fromStdString(r.AnesthesiaType))
.arg(QString::fromStdString(r.AnesthesiologistID));
text += tr(" 术前诊断: %1\n").arg(QString::fromStdString(r.Diagnosis));
text += tr(" 手术时长: %1分钟, 出血量: %2\n").arg(r.Duration).arg(QString::fromStdString(r.BloodLoss));
if (!r.Complications.empty()) {
text += tr(" 并发症: %1\n").arg(QString::fromStdString(r.Complications));
}
text += tr(" 手术时间: %1\n").arg(formatDate(r.SurgeryTime));
if (!r.Remarks.empty()) {
text += tr(" 备注: %1\n").arg(QString::fromStdString(r.Remarks));
}
}
text += tr("\n--- 住院记录 ---\n");
for (const auto& r : pc->AdmissionRecords) {
text += tr("病房: %1, 床位: %2, 原因: %3\n").arg(QString::fromStdString(r.WardID)).arg(QString::fromStdString(r.BedID)).arg(QString::fromStdString(r.Reason));
text += tr(" 入院时间: %1, 出院时间: %2\n").arg(formatDate(r.AdmissionTime)).arg(formatDate(r.DischargeTime));
if (!r.DischargeSummary.empty()) {
text += tr(" 出院小结: %1\n").arg(QString::fromStdString(r.DischargeSummary));
}
}
patientCaseDetail_->setText(text);
}
void MainWindow::handleAddPatient() {
QDialog* dialog = new QDialog(this);
dialog->setWindowTitle(tr("添加患者"));
dialog->setModal(true);
dialog->resize(450, 300);
dialog->setAttribute(Qt::WA_DeleteOnClose);
QFormLayout* form = new QFormLayout(dialog);
form->setSpacing(10);
form->setContentsMargins(20, 20, 20, 20);
QString newPatientId = QString::fromStdString(Patient::generateUniqueId());
QLabel* patientIdDisplay = new QLabel(newPatientId, dialog);
patientIdDisplay->setEnabled(false);
QLineEdit* nameEdit = new QLineEdit(dialog);
QSpinBox* ageSpin = new QSpinBox(dialog);
ageSpin->setRange(0, 200);
ageSpin->setValue(0);
QComboBox* genderCombo = new QComboBox(dialog);
genderCombo->addItems({tr("Male"), tr("Female")});
QLineEdit* contactEdit = new QLineEdit(dialog);
QLabel* infoLabel = new QLabel(tr("患者ID将自动生成"), dialog);
infoLabel->setStyleSheet("color: gray; font-size: 11px;");
form->addRow(tr("患者ID (自动生成):"), patientIdDisplay);
form->addRow(tr("姓名:"), nameEdit);
form->addRow(tr("年龄:"), ageSpin);
form->addRow(tr("性别:"), genderCombo);
form->addRow(tr("联系方式:"), contactEdit);
form->addRow(infoLabel);
auto* buttonLayout = new QHBoxLayout();
auto* okButton = new QPushButton(tr("添加"), dialog);
auto* cancelButton = new QPushButton(tr("取消"), dialog);
buttonLayout->addWidget(okButton);
buttonLayout->addWidget(cancelButton);
buttonLayout->addStretch();
form->addRow(buttonLayout);
connect(okButton, &QPushButton::clicked, [this, dialog, nameEdit, ageSpin, genderCombo, contactEdit, newPatientId]() {
if (nameEdit->text().isEmpty()) {
QMessageBox::warning(this, tr("错误"), tr("请填写姓名"));
return;
}
// 验证手机号格式
QString contact = contactEdit->text().trimmed();
if (!contact.isEmpty() && !input_validator::validatePhoneNumber(contact)) {
QMessageBox::warning(this, tr("手机号格式错误"),
tr("手机号格式不正确!\n\n要求:\n- 只能包含数字和 \"-\"\n- \"-\" 不能放在开头或结尾\n- 总长度不超过24个字符\n\n例如010-88889999 或 13812345678"));
return;
}
Patient p(newPatientId.toStdString(), nameEdit->text().toStdString(), ageSpin->value(),
genderCombo->currentText().toStdString(), contactEdit->text().toStdString(),
PatientStatus::Unregistered);
if (!core_.patientService.addPatient(p)) {
QMessageBox::warning(this, tr("添加失败"), tr("患者 ID 已存在"));
return;
}
if (!core_.patientCaseService.addPatientCase(newPatientId.toStdString())) {
std::string err;
core_.patientService.removePatient(newPatientId.toStdString(), err);
QMessageBox::warning(this, tr("添加失败"), tr("病例创建失败"));
return;
}
std::string patientDetails =
"患者ID: " + newPatientId.toStdString() +
" | 姓名: " + nameEdit->text().toStdString() +
" | 年龄: " + std::to_string(ageSpin->value()) +
" | 性别: " + genderCombo->currentText().toStdString() +
" | 联系方式: " + contactEdit->text().toStdString() +
" | 状态: 未挂号";
logOperation(LogEntryType::PATIENT_OPERATION, "ADD_PATIENT", patientDetails, newPatientId.toStdString());
QMessageBox::information(this, tr("成功"), tr("已添加患者: %1 (%2)").arg(newPatientId).arg(nameEdit->text()));
refreshPatientTable();
refreshDashboard();
dialog->accept();
});
connect(cancelButton, &QPushButton::clicked, dialog, &QDialog::reject);
dialog->exec();
}
void MainWindow::handleEditPatient() {
QString pid = safeCurrentTableId(patientTable_);
if (pid.isEmpty()) {
QMessageBox::warning(this, tr("编辑患者"), tr("请先选择患者"));
return;
}
auto* p = core_.patientService.findPatient(pid.toStdString());
if (!p) return;
QDialog* dialog = new QDialog(this);
dialog->setWindowTitle(tr("编辑患者 - %1").arg(pid));
dialog->setModal(true);
dialog->resize(450, 300);
dialog->setAttribute(Qt::WA_DeleteOnClose);
QFormLayout* form = new QFormLayout(dialog);
QLabel* patientIdLabel = new QLabel(pid, dialog);
patientIdLabel->setEnabled(false);
QLineEdit* nameEdit = new QLineEdit(QString::fromStdString(p->Name), dialog);
QSpinBox* ageSpin = new QSpinBox(dialog);
ageSpin->setRange(0, 200);
ageSpin->setValue(p->Age);
QComboBox* genderCombo = new QComboBox(dialog);
genderCombo->addItems({tr("Male"), tr("Female")});
int genderIdx = (p->Gender == "Female") ? 1 : 0;
genderCombo->setCurrentIndex(genderIdx);
QLineEdit* contactEdit = new QLineEdit(QString::fromStdString(p->Contact), dialog);
QComboBox* statusCombo = new QComboBox(dialog);
statusCombo->addItems({tr("Unregistered"), tr("Outpatient"), tr("Emergency"), tr("Visited"), tr("Inpatient"), tr("Discharged")});
int statusIdx = 0;
switch (p->Status) {
case PatientStatus::Unregistered: statusIdx = 0; break;
case PatientStatus::Outpatient: statusIdx = 1; break;
case PatientStatus::Emergency: statusIdx = 2; break;
case PatientStatus::Visited: statusIdx = 3; break;
case PatientStatus::Inpatient: statusIdx = 4; break;
case PatientStatus::Discharged: statusIdx = 5; break;
}
statusCombo->setCurrentIndex(statusIdx);
form->addRow(tr("患者ID:"), patientIdLabel);
form->addRow(tr("姓名:"), nameEdit);
form->addRow(tr("年龄:"), ageSpin);
form->addRow(tr("性别:"), genderCombo);
form->addRow(tr("联系方式:"), contactEdit);
form->addRow(tr("状态:"), statusCombo);
auto* buttonLayout = new QHBoxLayout();
auto* okButton = new QPushButton(tr("保存"), dialog);
auto* cancelButton = new QPushButton(tr("取消"), dialog);
buttonLayout->addWidget(okButton);
buttonLayout->addWidget(cancelButton);
buttonLayout->addStretch();
form->addRow(buttonLayout);
connect(okButton, &QPushButton::clicked, [this, pid, dialog, p, nameEdit, ageSpin, genderCombo, contactEdit, statusCombo]() {
// 验证手机号格式
QString contact = contactEdit->text().trimmed();
if (!contact.isEmpty() && !input_validator::validatePhoneNumber(contact)) {
QMessageBox::warning(this, tr("手机号格式错误"),
tr("手机号格式不正确!\n\n要求:\n- 只能包含数字和 \"-\"\n- \"-\" 不能放在开头或结尾\n- 总长度不超过24个字符\n\n例如010-88889999 或 13812345678"));
return;
}
std::string oldDetails =
"修改前 - 姓名: " + p->Name +
" | 年龄: " + std::to_string(p->Age) +
" | 性别: " + p->Gender +
" | 联系方式: " + p->Contact +
" | 状态: " + patientStatusToText(p->Status).toStdString();
if (!core_.patientService.updatePatient(pid.toStdString(), nameEdit->text().toStdString(), ageSpin->value(),
genderCombo->currentText().toStdString(), contactEdit->text().toStdString(),
parsePatientStatus(statusCombo->currentText()))) {
QMessageBox::warning(this, tr("编辑失败"), tr("更新失败"));
return;
}
std::string newDetails =
"修改后 - 姓名: " + nameEdit->text().toStdString() +
" | 年龄: " + std::to_string(ageSpin->value()) +
" | 性别: " + genderCombo->currentText().toStdString() +
" | 联系方式: " + contactEdit->text().toStdString() +
" | 状态: " + statusCombo->currentText().toStdString();
std::string fullDetails = "患者ID: " + pid.toStdString() + " | " + oldDetails + "\n" + newDetails;
logOperation(LogEntryType::PATIENT_OPERATION, "MODIFY_PATIENT", fullDetails, pid.toStdString());
QMessageBox::information(this, tr("成功"), tr("已成功更新患者 %1").arg(pid));
refreshPatientTable();
dialog->accept();
});
connect(cancelButton, &QPushButton::clicked, dialog, &QDialog::reject);
dialog->exec();
}
void MainWindow::handleRemovePatient() {
QString pid = safeCurrentTableId(patientTable_);
if (pid.isEmpty()) {
QMessageBox::warning(this, tr("删除患者"), tr("请先选择患者"));
return;
}
auto* patient = core_.patientService.findPatient(pid.toStdString());
std::string patientDetails;
QString patientName = tr("未知");
if (patient) {
patientName = QString::fromStdString(patient->Name);
patientDetails =
"患者ID: " + patient->PatientID +
" | 姓名: " + patient->Name +
" | 年龄: " + std::to_string(patient->Age) +
" | 性别: " + patient->Gender +
" | 联系方式: " + patient->Contact +
" | 状态: " + patientStatusToText(patient->Status).toStdString();
}
QMessageBox::StandardButton reply = QMessageBox::question(
this, tr("确认删除"),
tr("确定要删除患者 \"%1\" (ID: %2) 吗?\n此操作无法撤销,将同时删除该患者的所有病例记录。").arg(patientName).arg(pid),
QMessageBox::Ok | QMessageBox::Cancel,
QMessageBox::Cancel);
if (reply != QMessageBox::Ok) return;
std::string err;
if (!core_.patientService.removePatient(pid.toStdString(), err)) {
QMessageBox::warning(this, tr("删除失败"), QString::fromStdString(err));
return;
}
core_.patientCaseService.removeCase(pid.toStdString());
if (!patientDetails.empty()) {
logOperation(LogEntryType::PATIENT_OPERATION, "REMOVE_PATIENT", patientDetails, pid.toStdString());
}
refreshPatientTable();
refreshDashboard();
}
void MainWindow::handleViewPatientCase() {
QString pid = safeCurrentTableId(patientTable_);
if (pid.isEmpty()) {
QMessageBox::warning(this, tr("查看病例信息"), tr("请先选择患者"));
return;
}
auto* dialog = new PatientCaseViewDialog(core_, pid, this);
dialog->exec();
}
void MainWindow::handleAdmitPatient() {
QString pid = safeCurrentTableId(patientTable_);
if (pid.isEmpty()) {
QMessageBox::warning(this, tr("入院"), tr("请先选择患者"));
return;
}
auto* patient = core_.patientService.findPatient(pid.toStdString());
if (patient && patient->Status == PatientStatus::Inpatient) {
QMessageBox::warning(this, tr("入院"), tr("该患者 %1 已经入院,不能重复入院。").arg(pid));
return;
}
QDialog* dialog = new QDialog(this);
dialog->setWindowTitle(tr("入院 - %1").arg(pid));
dialog->setModal(true);
dialog->resize(550, 450);
dialog->setAttribute(Qt::WA_DeleteOnClose);
QFormLayout* form = new QFormLayout(dialog);
form->setSpacing(10);
form->setContentsMargins(20, 20, 20, 20);
QLabel* patientIdLabel = new QLabel(pid, dialog);
patientIdLabel->setEnabled(false);
QString currentDate = formatCurrentDate();
QLabel* dateLabel = new QLabel(currentDate, dialog);
dateLabel->setEnabled(false);
QComboBox* wardCombo = new QComboBox(dialog);
wardCombo->addItem(tr("-- 选择病房 --"));
core_.wardService.for_eachWard([&wardCombo](const std::string&, const Ward& ward) {
QString display = QString("%1 (%2) - 空床: %3/%4")
.arg(QString::fromStdString(ward.WardID))
.arg(QString::fromStdString(ward.DepartmentID))
.arg(ward.freeBedCount())
.arg(ward.MaxBeds);
wardCombo->addItem(display, QString::fromStdString(ward.WardID));
});
QComboBox* bedCombo = new QComboBox(dialog);
bedCombo->addItem(tr("-- 先选择病房 --"));
QTextEdit* reasonEdit = new QTextEdit(dialog);
reasonEdit->setMaximumHeight(80);
reasonEdit->setPlaceholderText(tr("入院原因"));
form->addRow(tr("患者ID:"), patientIdLabel);
form->addRow(tr("入院日期:"), dateLabel);
form->addRow(tr("选择病房:"), wardCombo);
form->addRow(tr("选择床位:"), bedCombo);
form->addRow(tr("入院原因:"), reasonEdit);
auto* buttonLayout = new QHBoxLayout();
auto* okButton = new QPushButton(tr("确认入院"), dialog);
auto* cancelButton = new QPushButton(tr("取消"), dialog);
buttonLayout->addWidget(okButton);
buttonLayout->addWidget(cancelButton);
buttonLayout->addStretch();
form->addRow(buttonLayout);
connect(wardCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), [wardCombo, bedCombo, &core = core_](int index) {
bedCombo->clear();
if (index == 0) {
bedCombo->addItem(tr("-- 先选择病房 --"));
return;
}
QString wardId = wardCombo->currentData().toString();
auto* ward = core.wardService.findWard(wardId.toStdString());
if (!ward) return;
bedCombo->addItem(tr("-- 选择床位 --"));
const auto* wardPtr = ward;
for (const auto& bed : wardPtr->Beds) {
if (bed.Status == BedStatus::Free) {
bedCombo->addItem(QString::fromStdString(bed.BedID), QString::fromStdString(bed.BedID));
}
}
});
connect(okButton, &QPushButton::clicked, [this, pid, wardCombo, bedCombo, dateLabel, reasonEdit, dialog]() {
if (wardCombo->currentIndex() == 0 || bedCombo->currentIndex() == 0) {
QMessageBox::warning(this, tr("错误"), tr("请选择病房和床位"));
return;
}
QString wardId = wardCombo->currentData().toString();
QString bedId = bedCombo->currentData().toString();
std::string actualBedId;
if (!core_.patientService.admitPatient(wardId.toStdString(), pid.toStdString(), actualBedId)) {
QMessageBox::warning(this, tr("入院失败"), tr("无法分配床位,请确认病房和床位是否正确"));
return;
}
auto* patient = core_.patientService.findPatient(pid.toStdString());
if (patient) {
core_.patientService.updatePatient(pid.toStdString(), patient->Name, patient->Age, patient->Gender, patient->Contact, PatientStatus::Inpatient);
}
AdmissionRecord ar(wardId.toStdString(), actualBedId, reasonEdit->toPlainText().toStdString());
core_.patientCaseService.addAdmissionRecord(pid.toStdString(), ar);
std::string paymentId;
double admissionFee = 1000.0;
if (core_.paymentService.createPayment(pid.toStdString(), admissionFee,
PaymentMethod::Cash, "入院", wardId.toStdString(),
"入院押金 - 病房: " + wardId.toStdString() + ", 床位: " + actualBedId, paymentId)) {
if (!core_.paymentService.completePayment(paymentId)) {
core_.paymentService.removePayment(paymentId);
QMessageBox::warning(this, tr("支付失败"), tr("入院押金支付失败,请重试"));
return;
}
}
std::string admitDetails =
"患者ID: " + pid.toStdString() +
" | 患者姓名: " + (patient ? patient->Name : "未知") +
" | 病房ID: " + wardId.toStdString() +
" | 床位ID: " + actualBedId +
" | 入院原因: " + reasonEdit->toPlainText().toStdString() +
" | 入院日期: " + dateLabel->text().toStdString() +
" | 患者状态: 住院" +
" | 入院押金: " + std::to_string(admissionFee) +
" | 支付ID: " + paymentId +
" | 支付状态: 已完成";
logOperation(LogEntryType::ADMISSION_RECORD, "ADMIT", admitDetails, pid.toStdString());
// 获取患者姓名和科室名称用于显示
auto* patientPtr = core_.patientService.findPatient(pid.toStdString());
QString patientName = patientPtr ? QString::fromStdString(patientPtr->Name) : pid;
auto* ward = core_.wardService.findWard(wardId.toStdString());
QString deptName = ward ? QString::fromStdString(ward->DepartmentID) : wardId;
if (ward) {
auto* dept = core_.departmentService.findDepartment(ward->DepartmentID);
if (dept) {
deptName = QString::fromStdString(dept->Name);
}
}
QMessageBox::information(this, tr("成功"),
tr("%1(%2) 已成功入院到病房 %3%4\n床位: %5\n入院押金: %6 元 (已自动完成支付)")
.arg(patientName)
.arg(pid)
.arg(wardId)
.arg(deptName)
.arg(QString::fromStdString(actualBedId))
.arg(QString::number(admissionFee, 'f', 2)));
refreshWardTable();
refreshPatientTable();
refreshDashboard();
dialog->accept();
});
connect(cancelButton, &QPushButton::clicked, dialog, &QDialog::reject);
dialog->exec();
}
void MainWindow::handleDischargePatient() {
QString pid = safeCurrentTableId(patientTable_);
if (pid.isEmpty()) {
QMessageBox::warning(this, tr("出院"), tr("请先选择患者"));
return;
}
auto* p = core_.patientService.findPatient(pid.toStdString());
if (!p || p->Status != PatientStatus::Inpatient) {
QMessageBox::warning(this, tr("出院"), tr("该患者不是住院状态"));
return;
}
Ward* patientWard = nullptr;
std::string bedName;
core_.wardService.for_eachWard([&](const std::string&, const Ward& ward) {
for (const auto& bed : ward.Beds) {
if (bed.PatientID == pid.toStdString()) {
patientWard = const_cast<Ward*>(&ward);
bedName = bed.BedID;
return;
}
}
});
if (!patientWard) {
QMessageBox::warning(this, tr("出院"), tr("未找到该患者的床位信息"));
return;
}
QString currentDate = formatCurrentDate();
if (!core_.patientService.releasePatient(patientWard->WardID, pid.toStdString())) {
QMessageBox::warning(this, tr("出院失败"), tr("无法释放床位"));
return;
}
core_.patientService.updatePatient(pid.toStdString(), p->Name, p->Age, p->Gender, p->Contact, PatientStatus::Discharged);
auto* pc = core_.patientCaseService.getCase(pid.toStdString());
if (pc) {
for (auto& ar : pc->AdmissionRecords) {
if (ar.isCurrentlyAdmitted() && ar.BedID == bedName) {
ar.DischargeTime = time(nullptr);
ar.DischargeSummary = tr("自动出院").toStdString();
break;
}
}
}
std::string dischargeDetails =
"患者ID: " + pid.toStdString() +
" | 患者姓名: " + (p ? p->Name : "未知") +
" | 病房ID: " + patientWard->WardID +
" | 床位ID: " + bedName +
" | 入院日期: " + formatDate(pc && !pc->AdmissionRecords.empty() ? pc->AdmissionRecords.back().AdmissionTime : 0).toStdString() +
" | 出院日期: " + currentDate.toStdString() +
" | 患者状态: 出院" +
" | 出院原因: 自动出院";
logOperation(LogEntryType::DISCHARGE_RECORD, "DISCHARGE", dischargeDetails, pid.toStdString());
QMessageBox::information(this, tr("出院成功"),
tr("患者 %1 已出院,入院日期: %2\n请使用\"缴费\"按钮进行结算。")
.arg(pid).arg(formatDate(pc && !pc->AdmissionRecords.empty() ? pc->AdmissionRecords.back().AdmissionTime : 0)));
refreshWardTable();
refreshPatientTable();
refreshDashboard();
updatePatientCaseDetail();
}
void MainWindow::handleAddDiagnosisRecord() {
QString pid = safeCurrentTableId(patientTable_);
if (pid.isEmpty()) {
QMessageBox::warning(this, tr("新增诊断"), tr("请先选择患者"));
return;
}
// 检查患者状态:未挂号或已出院禁止添加诊断记录
auto* patient = core_.patientService.findPatient(pid.toStdString());
if (!patient) {
QMessageBox::warning(this, tr("新增诊断"), tr("患者不存在"));
return;
}
if (patient->Status == PatientStatus::Unregistered) {
QMessageBox::warning(this, tr("新增诊断"),
tr("该患者当前状态为\"未挂号\",禁止添加诊断记录。\n请先为该患者进行挂号操作。"));
return;
}
if (patient->Status == PatientStatus::Discharged) {
QMessageBox::warning(this, tr("新增诊断"),
tr("该患者当前状态为\"已出院\",禁止添加诊断记录。"));
return;
}
struct DoctorInfo {
std::string id;
std::string name;
std::string dept;
};
std::vector<DoctorInfo> doctors;
core_.doctorService.for_eachDoctor([&doctors](const std::string&, const Doctor& d) {
doctors.push_back({d.DoctorID, d.Name, d.DepartmentID});
});
if (doctors.empty()) {
QMessageBox::warning(this, tr("错误"), tr("系统中没有医生,请先添加医生"));
return;
}
QDialog* dialog = new QDialog(this);
dialog->setWindowTitle(tr("添加诊断记录 - %1").arg(pid));
dialog->setModal(true);
dialog->resize(500, 400);
dialog->setAttribute(Qt::WA_DeleteOnClose);
QFormLayout* form = new QFormLayout(dialog);
form->setSpacing(10);
form->setContentsMargins(20, 20, 20, 20);
QComboBox* doctorCombo = new QComboBox(dialog);
doctorCombo->addItem(tr("-- 请选择医生 --"), QVariant());
for (const auto& doc : doctors) {
QString deptName = QString::fromStdString(doc.dept);
auto* dept = core_.departmentService.findDepartment(doc.dept);
if (dept) {
deptName = QString::fromStdString(dept->Name);
}
QString display = QString("%1 (%2) - %3").arg(QString::fromStdString(doc.name)).arg(deptName).arg(QString::fromStdString(doc.id));
doctorCombo->addItem(display, QString::fromStdString(doc.id));
}
QString currentDate = formatCurrentDate();
QLabel* dateLabel = new QLabel(currentDate, dialog);
dateLabel->setEnabled(false);
QTextEdit* diagnosisEdit = new QTextEdit(dialog);
diagnosisEdit->setMinimumHeight(60);
QTextEdit* prescriptionEdit = new QTextEdit(dialog);
prescriptionEdit->setMinimumHeight(60);
QLineEdit* remarksEdit = new QLineEdit(dialog);
form->addRow(tr("选择医生:"), doctorCombo);
form->addRow(tr("诊断日期:"), dateLabel);
form->addRow(tr("诊断:"), diagnosisEdit);
form->addRow(tr("处方:"), prescriptionEdit);
form->addRow(tr("备注:"), remarksEdit);
auto* buttonLayout = new QHBoxLayout();
auto* okButton = new QPushButton(tr("保存"), dialog);
auto* cancelButton = new QPushButton(tr("取消"), dialog);
buttonLayout->addWidget(okButton);
buttonLayout->addWidget(cancelButton);
buttonLayout->addStretch();
form->addRow(buttonLayout);
connect(okButton, &QPushButton::clicked, [this, pid, doctorCombo, diagnosisEdit, prescriptionEdit, remarksEdit, dialog]() {
if (doctorCombo->currentIndex() <= 0) {
QMessageBox::warning(this, tr("错误"), tr("请选择医生"));
return;
}
if (diagnosisEdit->toPlainText().isEmpty()) {
QMessageBox::warning(this, tr("错误"), tr("请填写诊断内容"));
return;
}
QString doctorId = doctorCombo->itemData(doctorCombo->currentIndex()).toString();
DiagnosisRecord record(
doctorId.toStdString(),
diagnosisEdit->toPlainText().toStdString(),
prescriptionEdit->toPlainText().toStdString(),
remarksEdit->text().toStdString()
);
if (!core_.patientCaseService.addDiagnosisRecord(pid.toStdString(), record)) {
QMessageBox::warning(this, tr("新增失败"), tr("诊断记录添加失败"));
return;
}
std::string diagDetails =
"患者ID: " + pid.toStdString() +
" | 医生ID: " + doctorId.toStdString() +
" | 诊断内容: " + diagnosisEdit->toPlainText().toStdString() +
" | 处方: " + prescriptionEdit->toPlainText().toStdString() +
" | 备注: " + remarksEdit->text().toStdString();
logOperation(LogEntryType::DIAGNOSIS_RECORD, "ADD_DIAGNOSIS", diagDetails, pid.toStdString());
// 获取患者和医生信息用于显示
auto* patient = core_.patientService.findPatient(pid.toStdString());
QString patientName = patient ? QString::fromStdString(patient->Name) : pid;
auto* doctor = core_.doctorService.findDoctor(doctorId.toStdString());
QString doctorName = doctor ? QString::fromStdString(doctor->Name) : doctorId;
QMessageBox::information(this, tr("成功"),
tr("%1(%2) 已成功诊断\n医生: %3(%4)\n诊断内容: %5")
.arg(patientName)
.arg(pid)
.arg(doctorName)
.arg(doctorId)
.arg(diagnosisEdit->toPlainText()));
if (patient && (patient->Status == PatientStatus::Outpatient ||
patient->Status == PatientStatus::Emergency ||
patient->Status == PatientStatus::Visited)) {
core_.patientService.updatePatient(pid.toStdString(), patient->Name, patient->Age,
patient->Gender, patient->Contact, PatientStatus::Unregistered);
refreshPatientTable();
QMessageBox::information(this, tr("诊断完成"),
tr("诊断已完成,患者 %1(%2) 已回到\"未挂号\"状态。\n如需继续就诊,请重新挂号。")
.arg(patientName)
.arg(pid));
}
updatePatientCaseDetail();
dialog->accept();
});
connect(cancelButton, &QPushButton::clicked, dialog, &QDialog::reject);
dialog->exec();
}
void MainWindow::handleAddCheckRecord() {
QString pid = safeCurrentTableId(patientTable_);
if (pid.isEmpty()) {
QMessageBox::warning(this, tr("添加检查记录"), tr("请先选择患者"));
return;
}
// 检查患者状态:未挂号或已出院禁止添加检查记录
auto* patient = core_.patientService.findPatient(pid.toStdString());
if (!patient) {
QMessageBox::warning(this, tr("添加检查记录"), tr("患者不存在"));
return;
}
if (patient->Status == PatientStatus::Unregistered) {
QMessageBox::warning(this, tr("添加检查记录"),
tr("该患者当前状态为\"未挂号\",禁止添加检查记录。\n请先为该患者进行挂号操作。"));
return;
}
if (patient->Status == PatientStatus::Discharged) {
QMessageBox::warning(this, tr("添加检查记录"),
tr("该患者当前状态为\"已出院\",禁止添加检查记录。"));
return;
}
auto* pc = core_.patientCaseService.getCase(pid.toStdString());
if (!pc) {
QMessageBox::warning(this, tr("添加检查记录"),
tr("该患者没有病例记录。\n请先为该患者创建病例后再添加检查记录。"));
return;
}
auto* dialog = new CheckRecordAddDialog(core_, pid, this);
int result = dialog->exec();
QString checkId;
QString doctorId;
if (result == QDialog::Accepted) {
checkId = dialog->getCheckId();
doctorId = dialog->getDoctorId();
}
delete dialog;
if (result != QDialog::Accepted) {
return;
}
if (checkId.isEmpty()) {
QMessageBox::warning(this, tr("错误"), tr("请选择检查项目"));
return;
}
if (doctorId.isEmpty()) {
QMessageBox::warning(this, tr("错误"), tr("请选择医生"));
return;
}
auto* check = core_.checkService.findCheck(checkId.toStdString());
if (!check) {
QMessageBox::warning(this, tr("错误"), tr("检查项目不存在"));
return;
}
CheckRecord record(
checkId.toStdString(),
check->Name,
check->DepartmentID,
check->Price,
doctorId.toStdString()
);
if (!core_.patientCaseService.addCheckRecord(pid.toStdString(), record)) {
QMessageBox::warning(this, tr("错误"), tr("检查记录添加失败"));
return;
}
std::string paymentId;
if (core_.paymentService.createPayment(pid.toStdString(), check->Price,
PaymentMethod::Cash, "检查", checkId.toStdString(),
"检查: " + check->Name, paymentId)) {
if (!core_.paymentService.completePayment(paymentId)) {
core_.paymentService.removePayment(paymentId);
QMessageBox::warning(this, tr("支付失败"), tr("检查费用支付失败,请重试"));
return;
}
}
std::string checkDetails =
"患者ID: " + pid.toStdString() +
" | 检查ID: " + checkId.toStdString() +
" | 检查名: " + check->Name +
" | 科室ID: " + check->DepartmentID +
" | 价格: " + std::to_string(check->Price) +
" | 医生ID: " + doctorId.toStdString() +
" | 支付ID: " + paymentId +
" | 支付状态: 已完成";
logOperation(LogEntryType::CHECK_RECORD, "ADD_CHECK_RECORD", checkDetails, pid.toStdString());
QMessageBox::information(this, tr("成功"),
tr("检查记录已添加\n检查: %1\n科室: %2\n价格: %3\n已自动完成支付")
.arg(QString::fromStdString(check->Name))
.arg(QString::fromStdString(check->DepartmentID))
.arg(QString::number(check->Price, 'f', 2)));
updatePatientCaseDetail();
}
void MainWindow::handleRegistration() {
QString pid = safeCurrentTableId(patientTable_);
if (pid.isEmpty()) {
QMessageBox::warning(this, tr("挂号"), tr("请先选择患者"));
return;
}
const auto* patient = core_.patientService.findPatient(pid.toStdString());
if (!patient) {
QMessageBox::warning(this, tr("挂号"), tr("患者不存在"));
return;
}
RegistrationDialog dialog(core_, pid, this);
int result = dialog.exec();
if (result == QDialog::Accepted) {
QString deptId = dialog.getDepartmentId();
QString doctorId = dialog.getDoctorId();
int regType = dialog.getRegistrationType();
if (!deptId.isEmpty() && !doctorId.isEmpty()) {
const auto* doctor = core_.doctorService.findDoctor(doctorId.toStdString());
const auto* dept = core_.departmentService.findDepartment(deptId.toStdString());
if (doctor && dept) {
double registrationFee = (regType == 1) ? 50.0 : 10.0;
QString regTypeName = (regType == 1) ? tr("急诊") : tr("门诊");
PatientStatus newStatus = (regType == 1) ? PatientStatus::Emergency : PatientStatus::Outpatient;
auto* patientPtr = core_.patientService.findPatient(pid.toStdString());
if (patientPtr) {
core_.patientService.updatePatient(pid.toStdString(), patientPtr->Name, patientPtr->Age,
patientPtr->Gender, patientPtr->Contact, newStatus);
}
std::string regDetails =
"患者ID: " + pid.toStdString() +
" | 患者姓名: " + patient->Name +
" | 挂号类型: " + regTypeName.toStdString() +
" | 科室: " + dept->Name +
" | 医生ID: " + doctorId.toStdString() +
" | 医生姓名: " + doctor->Name +
" | 医生职称: " +
(doctor->Title == DoctorTitle::Chief ? std::string("主任医师") :
doctor->Title == DoctorTitle::AssociateChief ? std::string("副主任医师") :
doctor->Title == DoctorTitle::Attending ? std::string("主治医师") :
std::string("住院医师"));
std::string paymentId;
if (core_.paymentService.createPayment(pid.toStdString(), registrationFee,
PaymentMethod::Cash, "挂号", doctorId.toStdString(),
"挂号 - 类型: " + regTypeName.toStdString() + ", 科室: " + dept->Name + ", 医生: " + doctor->Name, paymentId)) {
if (!core_.paymentService.completePayment(paymentId)) {
core_.paymentService.removePayment(paymentId);
QMessageBox::warning(this, tr("支付失败"), tr("挂号费支付失败,请重试"));
return;
}
}
regDetails += " | 挂号费: " + std::to_string(registrationFee) +
" | 支付ID: " + paymentId + " | 支付状态: 已完成";
logOperation(LogEntryType::PATIENT_OPERATION, "REGISTRATION", regDetails, pid.toStdString());
// 获取患者姓名用于显示
QString patientName = patient ? QString::fromStdString(patient->Name) : pid;
QMessageBox::information(this, tr("挂号成功"),
tr("%1(%2) 已成功挂号\n挂号类型: %3\n医生: %4(%5)\n科室: %6\n挂号费: %7 元 (已自动完成支付)")
.arg(patientName)
.arg(pid)
.arg(regTypeName)
.arg(QString::fromStdString(doctor->Name))
.arg(QString::fromStdString(doctorId.toStdString()))
.arg(QString::fromStdString(dept->Name))
.arg(QString::number(registrationFee, 'f', 2)));
refreshPatientTable();
updatePatientCaseDetail();
}
}
}
}
void MainWindow::handleAddMedicineRecord() {
QString pid = safeCurrentTableId(patientTable_);
if (pid.isEmpty()) {
QMessageBox::warning(this, tr("添加用药记录"), tr("请先选择患者"));
return;
}
auto* pc = core_.patientCaseService.getCase(pid.toStdString());
if (!pc || pc->AppointmentRecords.empty()) {
QMessageBox::warning(this, tr("添加用药记录"),
tr("该患者尚未挂号(没有预约记录)。\n请先为该患者进行挂号操作后再添加用药记录。"));
return;
}
auto* dialog = new MedicineRecordAddDialog(core_, pid, this);
int result = dialog->exec();
QString medicineId;
int quantity;
QString usage;
QString doctorId;
if (result == QDialog::Accepted) {
medicineId = dialog->getMedicineId();
quantity = dialog->getQuantity();
usage = dialog->getUsage();
doctorId = dialog->getDoctorId();
}
delete dialog;
if (result != QDialog::Accepted) {
return;
}
if (medicineId.isEmpty()) {
QMessageBox::warning(this, tr("错误"), tr("请选择药品"));
return;
}
if (doctorId.isEmpty()) {
QMessageBox::warning(this, tr("错误"), tr("请选择医生"));
return;
}
auto* med = core_.medicineService.findMedicine(medicineId.toStdString());
if (!med) {
QMessageBox::warning(this, tr("错误"), tr("药品不存在"));
return;
}
if (med->StockQuantity < quantity) {
QMessageBox::warning(this, tr("库存不足"),
tr("药品 %1 库存不足,当前库存: %2需要: %3")
.arg(QString::fromStdString(med->GenericName))
.arg(med->StockQuantity)
.arg(quantity));
return;
}
MedicineRecord record(
medicineId.toStdString(),
med->GenericName,
quantity,
usage.toStdString(),
med->UnitPrice,
doctorId.toStdString()
);
std::string paymentId;
double totalPrice = record.getTotalPrice();
if (!core_.paymentService.createPayment(pid.toStdString(), totalPrice,
PaymentMethod::Cash, "用药", medicineId.toStdString(),
"药品: " + med->GenericName + ", 数量: " + std::to_string(quantity), paymentId)) {
QMessageBox::warning(this, tr("支付失败"), tr("创建支付记录失败"));
return;
}
if (!core_.paymentService.completePayment(paymentId)) {
core_.paymentService.removePayment(paymentId);
QMessageBox::warning(this, tr("支付失败"), tr("药品费用支付失败,请重试"));
return;
}
if (!core_.medicineService.decreaseStock(medicineId.toStdString(), quantity)) {
core_.paymentService.refundPayment(paymentId);
QMessageBox::warning(this, tr("错误"), tr("库存扣除失败,已退款"));
return;
}
if (!core_.patientCaseService.addMedicineRecord(pid.toStdString(), record)) {
core_.medicineService.increaseStock(medicineId.toStdString(), quantity);
QMessageBox::warning(this, tr("错误"), tr("用药记录添加失败"));
return;
}
std::string medDetails =
"患者ID: " + pid.toStdString() +
" | 药品ID: " + medicineId.toStdString() +
" | 药品名: " + std::string(med->GenericName.c_str()) +
" | 数量: " + std::to_string(quantity) +
" | 用法: " + usage.toStdString() +
" | 单价: " + std::to_string(med->UnitPrice) +
" | 总价: " + std::to_string(totalPrice) +
" | 医生ID: " + doctorId.toStdString() +
" | 支付ID: " + paymentId +
" | 支付状态: 已完成" +
" | 变更库存: " + std::to_string(quantity) + "份 (扣除)";
logOperation(LogEntryType::MEDICINE_RECORD, "ADD_MEDICINE", medDetails, pid.toStdString());
QMessageBox::information(this, tr("成功"),
tr("用药记录已添加\n药品: %1\n数量: %2\n总价: %3\n已自动完成支付")
.arg(QString::fromStdString(med->GenericName))
.arg(quantity)
.arg(QString::number(totalPrice, 'f', 2)));
if (pc && pc->getDiagnosisRecordCount() > 0 && pc->getMedicineRecordCount() > 0) {
auto* patient = core_.patientService.findPatient(pid.toStdString());
if (patient && patient->Status == PatientStatus::Outpatient) {
core_.patientService.updatePatient(pid.toStdString(), patient->Name, patient->Age,
patient->Gender, patient->Contact, PatientStatus::Visited);
refreshPatientTable();
}
}
refreshMedicineTable();
updatePatientCaseDetail();
}
void MainWindow::handlePatientPayment() {
QString pid = safeCurrentTableId(patientTable_);
if (pid.isEmpty()) {
QMessageBox::warning(this, tr("缴费"), tr("请先选择患者"));
return;
}
auto* p = core_.patientService.findPatient(pid.toStdString());
if (!p) {
QMessageBox::warning(this, tr("缴费"), tr("患者不存在"));
return;
}
// 检查患者状态:只有出院或门诊状态才能缴费
if (p->Status != PatientStatus::Discharged && p->Status != PatientStatus::Outpatient &&
p->Status != PatientStatus::Visited && p->Status != PatientStatus::Emergency) {
QString statusText = patientStatusToText(p->Status);
QString message;
if (p->Status == PatientStatus::Inpatient) {
message = tr("该患者当前状态为\"%1\",请先办理出院后再缴费。").arg(statusText);
} else {
message = tr("该患者当前状态为\"%1\",请先进行挂号后再缴费。").arg(statusText);
}
QMessageBox::warning(this, tr("缴费"), message);
return;
}
auto* pc = core_.patientCaseService.getCase(pid.toStdString());
if (!pc) {
QMessageBox::warning(this, tr("缴费"), tr("该患者没有病例记录"));
return;
}
QString currentDate = formatCurrentDate();
QString patientName = p ? QString::fromStdString(p->Name) : QString();
double totalAmount = 0.0;
std::vector<std::pair<QString, double>> feeItems;
for (const auto& record : pc->AppointmentRecords) {
bool hasPayment = false;
core_.paymentManagementService.getAllPayments([&](const Payment& payment) {
if (payment.PatientID == pid.toStdString() &&
payment.OperationID == record.DoctorID &&
payment.PaymentType == "挂号") {
hasPayment = true;
}
});
if (!hasPayment) {
double regFee = 10.0;
totalAmount += regFee;
feeItems.push_back({tr("挂号费"), regFee});
}
}
for (const auto& record : pc->CheckRecords) {
bool hasPayment = false;
core_.paymentManagementService.getAllPayments([&](const Payment& payment) {
if (payment.PatientID == pid.toStdString() &&
payment.OperationID == record.CheckID) {
hasPayment = true;
}
});
if (!hasPayment) {
totalAmount += record.Price;
feeItems.push_back({QString::fromStdString(record.CheckName), record.Price});
}
}
for (const auto& record : pc->MedicineRecords) {
double itemTotal = record.UnitPrice * record.Quantity;
bool hasPayment = false;
core_.paymentManagementService.getAllPayments([&](const Payment& payment) {
if (payment.PatientID == pid.toStdString() &&
payment.OperationID == record.MedicineID) {
hasPayment = true;
}
});
if (!hasPayment) {
totalAmount += itemTotal;
feeItems.push_back({QString::fromStdString(record.MedicineName), itemTotal});
}
}
std::vector<std::string> depositPaymentIds;
for (const auto& record : pc->AdmissionRecords) {
double days = 1.0;
if (record.DischargeTime > 0 && record.AdmissionTime > 0) {
days = std::max(1.0, std::difftime(record.DischargeTime, record.AdmissionTime) / 86400.0);
}
double hospitalFee = days * 100.0;
double admissionDeposit = 0.0;
std::string depositPaymentId;
core_.paymentManagementService.getAllPayments([&](const Payment& payment) {
if (payment.PatientID == pid.toStdString() &&
payment.PaymentType == "入院" &&
payment.OperationID == record.WardID &&
payment.Status == "Completed") {
admissionDeposit = payment.Amount;
depositPaymentId = payment.PaymentID;
}
});
double netFee = hospitalFee - admissionDeposit;
totalAmount += netFee;
feeItems.push_back({tr("住院费"), hospitalFee});
if (admissionDeposit > 0) {
feeItems.push_back({tr("押金退回"), -admissionDeposit});
if (!depositPaymentId.empty()) {
depositPaymentIds.push_back(depositPaymentId);
}
}
}
// 处理手术费用(从手术记录中读取费用)
for (size_t i = 0; i < pc->SurgeryRecords.size(); ++i) {
const auto& record = pc->SurgeryRecords[i];
if (record.Fee > 0) {
totalAmount += record.Fee;
feeItems.push_back({QString::fromStdString(record.SurgeryName), record.Fee});
}
}
if (feeItems.empty()) {
QMessageBox::information(this, tr("缴费"), tr("患者 %1 当前没有需要结算的费用。").arg(pid));
return;
}
std::string settlementId;
if (!core_.settlementService.generateSettlement(pid.toStdString(), patientName.toStdString(), currentDate.toStdString(), settlementId)) {
QMessageBox::warning(this, tr("缴费"), tr("结算单生成失败"));
return;
}
for (const auto& item : feeItems) {
SettlementItem settlementItem(item.first.toStdString(), "费用", "", 1, item.second, "");
core_.settlementService.addItemToSettlement(settlementId, settlementItem);
}
core_.settlementService.calculateSettlement(settlementId);
const Settlement* finalSettlement = core_.settlementService.findSettlement(settlementId);
double finalAmount = finalSettlement ? finalSettlement->TotalAmount : totalAmount;
std::string paymentId;
if (core_.paymentService.createPayment(pid.toStdString(), finalAmount,
PaymentMethod::Cash, "结算", settlementId,
"综合结算 - 患者: " + patientName.toStdString(), paymentId)) {
if (!core_.paymentService.completePayment(paymentId)) {
core_.paymentService.removePayment(paymentId);
QMessageBox::warning(this, tr("支付失败"), tr("结算支付失败,请重试"));
return;
}
}
core_.settlementService.completeSettlement(settlementId);
for (const auto& depositPaymentId : depositPaymentIds) {
core_.paymentManagementService.processRefund(depositPaymentId, "入院押金退款-出院结算");
}
std::string paymentDetails =
"患者ID: " + pid.toStdString() +
" | 患者姓名: " + patientName.toStdString() +
" | 结算单ID: " + settlementId +
" | 支付ID: " + paymentId +
" | 总金额: " + std::to_string(totalAmount) +
" | 费用项数量: " + std::to_string(feeItems.size()) +
" | 押金退款记录数: " + std::to_string(depositPaymentIds.size()) +
" | 操作: 缴费结算";
logOperation(LogEntryType::SYSTEM_EVENT, "SETTLEMENT", paymentDetails, pid.toStdString());
QMessageBox::StandardButton reply = QMessageBox::question(
this, tr("缴费成功"),
tr("患者 %1 缴费成功\n总金额: ¥%2\n费用项: %3项\n是否查看结算单?")
.arg(pid)
.arg(QString::number(totalAmount, 'f', 2))
.arg(feeItems.size()),
QMessageBox::Yes | QMessageBox::No);
if (reply == QMessageBox::Yes) {
SettlementDialog dialog(core_, this);
dialog.displaySettlement(QString::fromStdString(settlementId));
dialog.exec();
}
core_.patientService.updatePatient(pid.toStdString(), p->Name, p->Age,
p->Gender, p->Contact, PatientStatus::Unregistered);
refreshPatientTable();
refreshDashboard();
}
void MainWindow::handleAddAdmissionRecord() {
QString pid = safeCurrentTableId(patientTable_);
if (pid.isEmpty()) {
QMessageBox::warning(this, tr("添加入院记录"), tr("请先选择患者"));
return;
}
QMessageBox::information(this, tr("添加入院记录"), tr("功能开发中..."));
}
void MainWindow::handleAddSurgeryRecord() {
QString pid = safeCurrentTableId(patientTable_);
if (pid.isEmpty()) {
QMessageBox::warning(this, tr("添加手术记录"), tr("请先选择患者"));
return;
}
// 检查患者状态:只有入院状态才允许添加手术记录
auto* patient = core_.patientService.findPatient(pid.toStdString());
if (!patient) {
QMessageBox::warning(this, tr("添加手术记录"), tr("患者不存在"));
return;
}
if (patient->Status != PatientStatus::Inpatient) {
QMessageBox::warning(this, tr("添加手术记录"),
tr("该患者当前状态为\"%1\",禁止添加手术记录。\n只有入院状态的患者才能添加手术记录。")
.arg(patientStatusToText(patient->Status)));
return;
}
struct DoctorInfo {
std::string id;
std::string name;
std::string dept;
};
std::vector<DoctorInfo> doctors;
core_.doctorService.for_eachDoctor([&doctors](const std::string&, const Doctor& d) {
doctors.push_back({d.DoctorID, d.Name, d.DepartmentID});
});
if (doctors.empty()) {
QMessageBox::warning(this, tr("错误"), tr("系统中没有医生,请先添加医生"));
return;
}
QDialog* dialog = new QDialog(this);
dialog->setWindowTitle(tr("添加手术记录 - %1").arg(pid));
dialog->setModal(true);
dialog->resize(600, 500);
dialog->setAttribute(Qt::WA_DeleteOnClose);
QFormLayout* form = new QFormLayout(dialog);
form->setSpacing(10);
form->setContentsMargins(20, 20, 20, 20);
// 主刀医生选择
QComboBox* surgeonCombo = new QComboBox(dialog);
surgeonCombo->addItem(tr("-- 请选择主刀医生 --"), QVariant());
for (const auto& doc : doctors) {
QString deptName = QString::fromStdString(doc.dept);
auto* dept = core_.departmentService.findDepartment(doc.dept);
if (dept) {
deptName = QString::fromStdString(dept->Name);
}
QString display = QString("%1 (%2) - %3").arg(QString::fromStdString(doc.name)).arg(deptName).arg(QString::fromStdString(doc.id));
surgeonCombo->addItem(display, QString::fromStdString(doc.id));
}
// 助手医生选择(可选)
QComboBox* assistantCombo = new QComboBox(dialog);
assistantCombo->addItem(tr("-- 助手医生(可选) --"), QVariant());
for (const auto& doc : doctors) {
QString deptName = QString::fromStdString(doc.dept);
auto* dept = core_.departmentService.findDepartment(doc.dept);
if (dept) {
deptName = QString::fromStdString(dept->Name);
}
QString display = QString("%1 (%2) - %3").arg(QString::fromStdString(doc.name)).arg(deptName).arg(QString::fromStdString(doc.id));
assistantCombo->addItem(display, QString::fromStdString(doc.id));
}
// 麻醉医生选择
QComboBox* anesthesiologistCombo = new QComboBox(dialog);
anesthesiologistCombo->addItem(tr("-- 请选择麻醉医生 --"), QVariant());
for (const auto& doc : doctors) {
QString deptName = QString::fromStdString(doc.dept);
auto* dept = core_.departmentService.findDepartment(doc.dept);
if (dept) {
deptName = QString::fromStdString(dept->Name);
}
QString display = QString("%1 (%2) - %3").arg(QString::fromStdString(doc.name)).arg(deptName).arg(QString::fromStdString(doc.id));
anesthesiologistCombo->addItem(display, QString::fromStdString(doc.id));
}
QString currentDate = formatCurrentDate();
QLabel* dateLabel = new QLabel(currentDate, dialog);
dateLabel->setEnabled(false);
// 手术名称
QLineEdit* surgeryNameEdit = new QLineEdit(dialog);
surgeryNameEdit->setPlaceholderText(tr("如:阑尾切除术"));
// 手术类型
QComboBox* surgeryTypeCombo = new QComboBox(dialog);
surgeryTypeCombo->addItems({tr("开放手术"), tr("微创手术"), tr("腔镜手术"), tr("介入手术"), tr("其他")});
// 麻醉方式
QComboBox* anesthesiaTypeCombo = new QComboBox(dialog);
anesthesiaTypeCombo->addItems({tr("全身麻醉"), tr("局部麻醉"), tr("椎管内麻醉"), tr("神经阻滞麻醉"), tr("复合麻醉"), tr("其他")});
// 术前诊断
QTextEdit* diagnosisEdit = new QTextEdit(dialog);
diagnosisEdit->setMinimumHeight(50);
diagnosisEdit->setPlaceholderText(tr("术前诊断"));
// 手术过程
QTextEdit* procedureEdit = new QTextEdit(dialog);
procedureEdit->setMinimumHeight(80);
procedureEdit->setPlaceholderText(tr("详细描述手术过程"));
// 术中并发症
QLineEdit* complicationsEdit = new QLineEdit(dialog);
complicationsEdit->setPlaceholderText(tr("如无并发症请填写'无'"));
// 出血量
QLineEdit* bloodLossEdit = new QLineEdit(dialog);
bloodLossEdit->setPlaceholderText(tr("200ml"));
// 手术时长
QSpinBox* durationSpin = new QSpinBox(dialog);
durationSpin->setRange(0, 1440);
durationSpin->setSuffix(tr(" 分钟"));
durationSpin->setValue(60);
// 备注
QLineEdit* remarksEdit = new QLineEdit(dialog);
remarksEdit->setPlaceholderText(tr("其他备注"));
// 手术金额
QLineEdit* surgeryFeeEdit = new QLineEdit(dialog);
surgeryFeeEdit->setPlaceholderText(tr("手术费用(元)"));
surgeryFeeEdit->setValidator(new QDoubleValidator(0, 999999, 2, dialog));
surgeryFeeEdit->setText("0.00");
form->addRow(tr("主刀医生:"), surgeonCombo);
form->addRow(tr("助手医生:"), assistantCombo);
form->addRow(tr("麻醉医生:"), anesthesiologistCombo);
form->addRow(tr("手术日期:"), dateLabel);
form->addRow(tr("手术名称:"), surgeryNameEdit);
form->addRow(tr("手术类型:"), surgeryTypeCombo);
form->addRow(tr("麻醉方式:"), anesthesiaTypeCombo);
form->addRow(tr("术前诊断:"), diagnosisEdit);
form->addRow(tr("手术过程:"), procedureEdit);
form->addRow(tr("术中并发症:"), complicationsEdit);
form->addRow(tr("出血量:"), bloodLossEdit);
form->addRow(tr("手术时长:"), durationSpin);
form->addRow(tr("手术金额:"), surgeryFeeEdit);
form->addRow(tr("备注:"), remarksEdit);
auto* buttonLayout = new QHBoxLayout();
auto* okButton = new QPushButton(tr("保存"), dialog);
auto* cancelButton = new QPushButton(tr("取消"), dialog);
buttonLayout->addWidget(okButton);
buttonLayout->addWidget(cancelButton);
buttonLayout->addStretch();
form->addRow(buttonLayout);
connect(okButton, &QPushButton::clicked, [this, pid, dialog, surgeonCombo, assistantCombo, anesthesiologistCombo,
surgeryNameEdit, surgeryTypeCombo, anesthesiaTypeCombo, diagnosisEdit, procedureEdit,
complicationsEdit, bloodLossEdit, durationSpin, remarksEdit, surgeryFeeEdit]() {
if (surgeonCombo->currentIndex() <= 0) {
QMessageBox::warning(this, tr("错误"), tr("请选择主刀医生"));
return;
}
if (surgeryNameEdit->text().isEmpty()) {
QMessageBox::warning(this, tr("错误"), tr("请填写手术名称"));
return;
}
if (diagnosisEdit->toPlainText().isEmpty()) {
QMessageBox::warning(this, tr("错误"), tr("请填写术前诊断"));
return;
}
if (procedureEdit->toPlainText().isEmpty()) {
QMessageBox::warning(this, tr("错误"), tr("请填写手术过程"));
return;
}
if (anesthesiologistCombo->currentIndex() <= 0) {
QMessageBox::warning(this, tr("错误"), tr("请选择麻醉医生"));
return;
}
QString surgeonId = surgeonCombo->itemData(surgeonCombo->currentIndex()).toString();
QString assistantId = assistantCombo->currentIndex() > 0 ? assistantCombo->itemData(assistantCombo->currentIndex()).toString() : "";
QString anesthesiologistId = anesthesiologistCombo->itemData(anesthesiologistCombo->currentIndex()).toString();
// 获取手术费用
double surgeryFee = surgeryFeeEdit->text().toDouble();
SurgeryRecord record;
record.SurgeryName = surgeryNameEdit->text().toStdString();
record.SurgeryType = surgeryTypeCombo->currentText().toStdString();
record.SurgeonID = surgeonId.toStdString();
record.AssistantDoctorID = assistantId.toStdString();
record.AnesthesiaType = anesthesiaTypeCombo->currentText().toStdString();
record.AnesthesiologistID = anesthesiologistId.toStdString();
record.Diagnosis = diagnosisEdit->toPlainText().toStdString();
record.Procedure = procedureEdit->toPlainText().toStdString();
record.Complications = complicationsEdit->text().isEmpty() ? "" : complicationsEdit->text().toStdString();
record.BloodLoss = bloodLossEdit->text().isEmpty() ? "未知" : bloodLossEdit->text().toStdString();
record.Duration = durationSpin->value();
record.Remarks = remarksEdit->text().toStdString();
record.SurgeryTime = std::time(nullptr);
record.Fee = surgeryFee; // 保存手术费用到记录中
if (!core_.patientCaseService.addSurgeryRecord(pid.toStdString(), record)) {
QMessageBox::warning(this, tr("新增失败"), tr("手术记录添加失败"));
return;
}
std::string surgeryDetails =
"患者ID: " + pid.toStdString() +
" | 手术名称: " + record.SurgeryName +
" | 手术类型: " + record.SurgeryType +
" | 主刀医生ID: " + record.SurgeonID +
" | 助手医生ID: " + record.AssistantDoctorID +
" | 麻醉方式: " + record.AnesthesiaType +
" | 麻醉医生ID: " + record.AnesthesiologistID +
" | 术前诊断: " + record.Diagnosis +
" | 手术时长: " + std::to_string(record.Duration) + "分钟" +
" | 出血量: " + record.BloodLoss +
" | 并发症: " + record.Complications +
" | 手术费用: " + std::to_string(surgeryFee) +
" | 备注: " + record.Remarks;
logOperation(LogEntryType::SURGERY_RECORD, "ADD_SURGERY", surgeryDetails, pid.toStdString());
QString successMsg = tr("手术记录已添加\n手术名称: %1\n手术时长: %2分钟\n手术费用: ¥%3")
.arg(surgeryNameEdit->text())
.arg(durationSpin->value())
.arg(QString::number(surgeryFee, 'f', 2));
if (surgeryFee > 0) {
successMsg += tr("\n(手术费用将在\"缴费\"时一并结算)");
}
QMessageBox::information(this, tr("成功"), successMsg);
updatePatientCaseDetail();
dialog->accept();
});
connect(cancelButton, &QPushButton::clicked, dialog, &QDialog::reject);
dialog->exec();
}
void MainWindow::onPatientSearch(const QString& text) {
for (int i = 0; i < patientTable_->rowCount(); ++i) {
bool match = false;
for (int j = 0; j < patientTable_->columnCount(); ++j) {
QTableWidgetItem* item = patientTable_->item(i, j);
if (item && item->text().contains(text, Qt::CaseInsensitive)) {
match = true;
break;
}
}
patientTable_->setRowHidden(i, !match);
}
}