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

519 lines
22 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 <QDoubleSpinBox>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QInputDialog>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QPushButton>
#include <QSpinBox>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QVBoxLayout>
#include <QWidget>
#include <string>
#include "core/his_core.h"
#include "core/medicine_service.h"
void MainWindow::setupMedicinesTab() {
medicinesTab_ = new QWidget(this);
auto* layout = new QVBoxLayout(medicinesTab_);
layout->setContentsMargins(20, 20, 20, 20);
layout->setSpacing(15);
auto* searchContainer = new QWidget(medicinesTab_);
searchContainer->setStyleSheet("background-color: #1f2937; border-radius: 8px; padding: 10px;");
auto* searchLayout = new QHBoxLayout(searchContainer);
searchLayout->setContentsMargins(10, 5, 10, 5);
medicineSearchBox_ = new QLineEdit(searchContainer);
medicineSearchBox_->setPlaceholderText(tr("🔍 搜索药品..."));
medicineSearchBox_->setStyleSheet("padding: 8px 15px; border-radius: 6px; border: 1px solid #374151; background-color: #111827; color: #f3f4f6;");
connect(medicineSearchBox_, &QLineEdit::textChanged, this, &MainWindow::onMedicineSearch);
searchLayout->addWidget(medicineSearchBox_);
layout->addWidget(searchContainer);
auto* buttonContainer = new QWidget(medicinesTab_);
buttonContainer->setStyleSheet("background-color: transparent;");
auto* buttonLayout = new QHBoxLayout(buttonContainer);
buttonLayout->setContentsMargins(0, 10, 0, 10);
medicineAddButton_ = new QPushButton(tr("+ 添加药品"), buttonContainer);
medicineUpdateButton_ = new QPushButton(tr("✎ 更新药品"), buttonContainer);
medicineRemoveButton_ = new QPushButton(tr("🗑 删除药品"), buttonContainer);
medicineIncreaseStockButton_ = new QPushButton(tr("📥 增加库存"), buttonContainer);
medicineDecreaseStockButton_ = new QPushButton(tr("📤 减少库存"), buttonContainer);
QString buttonStyle = R"(
QPushButton {
background-color: #374151;
color: #f3f4f6;
border: 1px solid #4b5563;
border-radius: 6px;
padding: 8px 16px;
font-size: 13px;
}
QPushButton:hover {
background-color: #4b5563;
border-color: #6b7280;
}
)";
medicineAddButton_->setStyleSheet(buttonStyle);
medicineUpdateButton_->setStyleSheet(buttonStyle);
medicineRemoveButton_->setStyleSheet(buttonStyle);
medicineIncreaseStockButton_->setStyleSheet(buttonStyle);
medicineDecreaseStockButton_->setStyleSheet(buttonStyle);
buttonLayout->addWidget(medicineAddButton_);
buttonLayout->addWidget(medicineUpdateButton_);
buttonLayout->addWidget(medicineRemoveButton_);
buttonLayout->addWidget(medicineIncreaseStockButton_);
buttonLayout->addWidget(medicineDecreaseStockButton_);
buttonLayout->addStretch(1);
layout->addWidget(buttonContainer);
medicineTable_ = new QTableWidget(0, 6, medicinesTab_);
medicineTable_->setHorizontalHeaderLabels({tr("药品ID"), tr("通用名称"), tr("品牌名称"), tr("归属科室"), tr("存量"), tr("价格")});
medicineTable_->setSelectionBehavior(QAbstractItemView::SelectRows);
medicineTable_->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed);
medicineTable_->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
medicineTable_->setSortingEnabled(true);
layout->addWidget(medicineTable_);
connect(medicineAddButton_, &QPushButton::clicked, this, &MainWindow::handleAddMedicine);
connect(medicineUpdateButton_, &QPushButton::clicked, this, &MainWindow::handleUpdateMedicine);
connect(medicineRemoveButton_, &QPushButton::clicked, this, &MainWindow::handleRemoveMedicine);
connect(medicineIncreaseStockButton_, &QPushButton::clicked, this, &MainWindow::handleIncreaseMedicineStock);
connect(medicineDecreaseStockButton_, &QPushButton::clicked, this, &MainWindow::handleDecreaseMedicineStock);
connect(medicineTable_, &QTableWidget::cellChanged, this, &MainWindow::onMedicineCellChanged);
}
void MainWindow::onMedicineCellChanged(int row, int column) {
QTableWidgetItem* idItem = medicineTable_->item(row, 0);
if (!idItem) return;
QString mid = idItem->text();
if (mid.isEmpty()) return;
auto* m = core_.medicineService.findMedicine(mid.toStdString());
if (!m) return;
QTableWidgetItem* genericItem = medicineTable_->item(row, 1);
QTableWidgetItem* brandItem = medicineTable_->item(row, 2);
QTableWidgetItem* deptItem = medicineTable_->item(row, 3);
QTableWidgetItem* stockItem = medicineTable_->item(row, 4);
QTableWidgetItem* priceItem = medicineTable_->item(row, 5);
std::string modifyDetails;
if (genericItem && column == 1) {
m->GenericName = genericItem->text().toStdString();
modifyDetails = "修改项: 通用名 | 新值: " + m->GenericName;
}
if (brandItem && column == 2) {
m->BrandName = brandItem->text().toStdString();
modifyDetails = "修改项: 商品名 | 新值: " + m->BrandName;
}
if (deptItem && column == 3) {
// 根据科室名称查找科室ID
QString deptName = deptItem->text();
std::string deptId = "";
core_.departmentService.for_eachDepartment([&deptName, &deptId](const std::string& id, const Department& d) {
if (QString::fromStdString(d.Name) == deptName) {
deptId = id;
}
});
if (!deptId.empty()) {
m->DepartmentID = deptId;
modifyDetails = "修改项: 科室 | 新值: " + deptName.toStdString();
} else {
QMessageBox::warning(this, tr("科室不存在"), tr("未找到科室: ") + deptName + tr(",请使用有效的科室名称"));
refreshMedicineTable();
}
}
if (stockItem && column == 4) {
int oldStock = m->StockQuantity;
m->StockQuantity = stockItem->text().toInt();
modifyDetails = "修改项: 库存 | 旧值: " + std::to_string(oldStock) + " | 新值: " + std::to_string(m->StockQuantity);
}
if (priceItem && column == 5) {
double oldPrice = m->UnitPrice;
m->UnitPrice = priceItem->text().toDouble();
modifyDetails = "修改项: 单价 | 旧值: " + std::to_string(oldPrice) + " | 新值: " + std::to_string(m->UnitPrice);
}
if (!modifyDetails.empty()) {
std::string details = "药品ID: " + mid.toStdString() + " | 药品名: " + m->GenericName + " | " + modifyDetails;
logOperation(LogEntryType::MEDICINE_OPERATION, "MODIFY_MEDICINE", details, mid.toStdString());
}
}
void MainWindow::refreshMedicineTable() {
medicineTable_->blockSignals(true);
medicineTable_->setSortingEnabled(false); // 【关键修复】
medicineTable_->setRowCount(0);
int row = 0;
core_.medicineService.for_eachMedicine([this, &row](const std::string&, const Medicine& med) {
medicineTable_->insertRow(row);
medicineTable_->setItem(row, 0, new QTableWidgetItem(QString::fromStdString(med.MedicineID)));
medicineTable_->setItem(row, 1, new QTableWidgetItem(QString::fromStdString(med.GenericName)));
medicineTable_->setItem(row, 2, new QTableWidgetItem(QString::fromStdString(med.BrandName)));
// 获取科室名称而不是ID
QString deptName = QString::fromStdString(med.DepartmentID);
auto* dept = core_.departmentService.findDepartment(med.DepartmentID);
if (dept) {
deptName = QString::fromStdString(dept->Name);
}
medicineTable_->setItem(row, 3, new QTableWidgetItem(deptName));
QTableWidgetItem* stockItem = new QTableWidgetItem();
stockItem->setData(Qt::DisplayRole, med.StockQuantity);
medicineTable_->setItem(row, 4, stockItem);
QTableWidgetItem* priceItem = new QTableWidgetItem();
// 修改使用QString::number设置固定两位小数格式确保显示后两位小数如11111.01
priceItem->setText(QString::number(med.UnitPrice, 'f', 2));
// 同时设置DisplayRole以支持按数字排序
priceItem->setData(Qt::DisplayRole, med.UnitPrice);
medicineTable_->setItem(row, 5, priceItem);
row++;
});
medicineTable_->setSortingEnabled(true); // 【关键修复】
medicineTable_->blockSignals(false);
}
void MainWindow::handleAddMedicine() {
QDialog* dialog = new QDialog(this);
dialog->setWindowTitle(tr("添加药品"));
dialog->setModal(true);
dialog->resize(450, 350);
dialog->setAttribute(Qt::WA_DeleteOnClose);
QFormLayout* form = new QFormLayout(dialog);
form->setSpacing(10);
form->setContentsMargins(20, 20, 20, 20);
QString newMedId = QString::fromStdString(Medicine::generateUniqueId());
QLabel* medIdDisplay = new QLabel(newMedId, dialog);
medIdDisplay->setEnabled(false);
QLineEdit* genericEdit = new QLineEdit(dialog);
QLineEdit* brandEdit = new QLineEdit(dialog);
QComboBox* deptCombo = new QComboBox(dialog);
deptCombo->addItem(tr("-- 请选择科室 --"));
core_.departmentService.for_eachDepartment([deptCombo](const std::string&, const Department& d) {
deptCombo->addItem(QString::fromStdString(d.Name), QString::fromStdString(d.DepartmentID));
});
QSpinBox* stockSpin = new QSpinBox(dialog);
stockSpin->setRange(0, MAX_MEDICINE_STOCK);
QDoubleSpinBox* priceSpin = new QDoubleSpinBox(dialog);
priceSpin->setRange(0.0, 100000.0);
priceSpin->setDecimals(2);
QLabel* infoLabel = new QLabel(tr("药品ID将自动生成"), dialog);
infoLabel->setStyleSheet("color: gray; font-size: 11px;");
form->addRow(tr("药品ID (自动生成):"), medIdDisplay);
form->addRow(tr("通用名:"), genericEdit);
form->addRow(tr("商品名:"), brandEdit);
form->addRow(tr("选择科室:"), deptCombo);
form->addRow(tr("库存:"), stockSpin);
form->addRow(tr("单价:"), priceSpin);
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, genericEdit, brandEdit, deptCombo, stockSpin, priceSpin, newMedId]() {
if (genericEdit->text().isEmpty()) {
QMessageBox::warning(this, tr("错误"), tr("请填写通用名"));
return;
}
if (deptCombo->currentIndex() == 0) {
QMessageBox::warning(this, tr("错误"), tr("请选择科室"));
return;
}
Medicine m(newMedId.toStdString(), genericEdit->text().toStdString(), brandEdit->text().toStdString(), {}, stockSpin->value(), deptCombo->currentData().toString().toStdString(), priceSpin->value());
if (!core_.medicineService.addOrUpdateMedicine(m)) {
QMessageBox::warning(this, tr("添加失败"), tr("药品 ID 已存在"));
return;
}
// 记录详细日志 - 药品的完整信息
std::string medDetails =
"药品ID: " + newMedId.toStdString() +
" | 通用名: " + genericEdit->text().toStdString() +
" | 商品名: " + brandEdit->text().toStdString() +
" | 科室: " + deptCombo->currentText().toStdString() +
" | 科室ID: " + deptCombo->currentData().toString().toStdString() +
" | 初始库存: " + std::to_string(stockSpin->value()) +
" | 单价: " + std::to_string(priceSpin->value());
logOperation(LogEntryType::MEDICINE_OPERATION, "ADD_MEDICINE", medDetails, newMedId.toStdString());
QMessageBox::information(this, tr("成功"), tr("已添加药品: %1 (%2)").arg(newMedId).arg(genericEdit->text()));
refreshMedicineTable();
refreshDashboard();
dialog->accept();
});
connect(cancelButton, &QPushButton::clicked, dialog, &QDialog::reject);
dialog->exec();
}
void MainWindow::handleUpdateMedicine() {
QString mid = safeCurrentTableId(medicineTable_);
if (mid.isEmpty()) {
QMessageBox::warning(this, tr("更新药品"), tr("请先选择药品"));
return;
}
auto* m = core_.medicineService.findMedicine(mid.toStdString());
if (!m) return;
QDialog* dialog = new QDialog(this);
dialog->setWindowTitle(tr("更新药品 - %1").arg(mid));
dialog->setModal(true);
dialog->resize(450, 350);
dialog->setAttribute(Qt::WA_DeleteOnClose);
QFormLayout* form = new QFormLayout(dialog);
QLabel* medIdDisplay = new QLabel(mid, dialog);
medIdDisplay->setEnabled(false);
QLineEdit* genericEdit = new QLineEdit(QString::fromStdString(m->GenericName), dialog);
QLineEdit* brandEdit = new QLineEdit(QString::fromStdString(m->BrandName), dialog);
QComboBox* deptCombo = new QComboBox(dialog);
deptCombo->addItem(tr("-- 请选择科室 --"));
core_.departmentService.for_eachDepartment([deptCombo](const std::string&, const Department& d) {
deptCombo->addItem(QString::fromStdString(d.Name), QString::fromStdString(d.DepartmentID));
});
// 设置当前选中的科室
for (int i = 1; i < deptCombo->count(); ++i) {
if (deptCombo->itemData(i).toString().toStdString() == m->DepartmentID) {
deptCombo->setCurrentIndex(i);
break;
}
}
QSpinBox* stockSpin = new QSpinBox(dialog);
stockSpin->setRange(0, MAX_MEDICINE_STOCK);
stockSpin->setValue(m->StockQuantity);
QDoubleSpinBox* priceSpin = new QDoubleSpinBox(dialog);
priceSpin->setRange(0.0, 100000.0);
priceSpin->setDecimals(2);
priceSpin->setValue(m->UnitPrice);
form->addRow(tr("药品ID:"), medIdDisplay);
form->addRow(tr("通用名:"), genericEdit);
form->addRow(tr("商品名:"), brandEdit);
form->addRow(tr("选择科室:"), deptCombo);
form->addRow(tr("库存:"), stockSpin);
form->addRow(tr("单价:"), priceSpin);
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, mid, dialog, m, genericEdit, brandEdit, deptCombo, stockSpin, priceSpin]() {
// 记录修改前的信息
std::string oldDetails =
"修改前 - 通用名: " + m->GenericName +
" | 商品名: " + m->BrandName +
" | 科室ID: " + m->DepartmentID +
" | 库存: " + std::to_string(m->StockQuantity) +
" | 单价: " + std::to_string(m->UnitPrice);
if (deptCombo->currentIndex() == 0) {
QMessageBox::warning(this, tr("错误"), tr("请选择科室"));
return;
}
if (!core_.medicineService.updateMedicine(mid.toStdString(), genericEdit->text().toStdString(), brandEdit->text().toStdString(), {}, stockSpin->value(), deptCombo->currentData().toString().toStdString(), priceSpin->value())) {
QMessageBox::warning(this, tr("更新失败"), tr("操作失败"));
return;
}
// 记录详细日志 - 修改后的信息
std::string newDetails =
"修改后 - 通用名: " + genericEdit->text().toStdString() +
" | 商品名: " + brandEdit->text().toStdString() +
" | 科室: " + deptCombo->currentText().toStdString() +
" | 库存: " + std::to_string(stockSpin->value()) +
" | 单价: " + std::to_string(priceSpin->value());
std::string fullDetails = "药品ID: " + mid.toStdString() + " | " + oldDetails + "\n" + newDetails;
logOperation(LogEntryType::MEDICINE_OPERATION, "MODIFY_MEDICINE", fullDetails, mid.toStdString());
QMessageBox::information(this, tr("成功"), tr("药品 %1 已更新").arg(mid));
refreshMedicineTable();
dialog->accept();
});
connect(cancelButton, &QPushButton::clicked, dialog, &QDialog::reject);
dialog->exec();
}
void MainWindow::handleRemoveMedicine() {
QString mid = safeCurrentTableId(medicineTable_);
if (mid.isEmpty()) {
QMessageBox::warning(this, tr("删除药品"), tr("请先选择药品"));
return;
}
// 获取药品完整信息用于日志记录和确认弹窗
auto* medicine = core_.medicineService.findMedicine(mid.toStdString());
std::string medDetails;
QString medName = tr("未知");
if (medicine) {
medName = QString::fromStdString(medicine->GenericName);
medDetails =
"药品ID: " + medicine->MedicineID +
" | 通用名: " + medicine->GenericName +
" | 商品名: " + medicine->BrandName +
" | 科室ID: " + medicine->DepartmentID +
" | 当前库存: " + std::to_string(medicine->StockQuantity) +
" | 单价: " + std::to_string(medicine->UnitPrice);
}
// 确认删除弹窗
QMessageBox::StandardButton reply = QMessageBox::question(
this, tr("确认删除"),
tr("确定要删除药品 \"%1\" (ID: %2) 吗?\n此操作无法撤销。").arg(medName).arg(mid),
QMessageBox::Yes | QMessageBox::No);
if (reply != QMessageBox::Yes) return;
std::string err;
if (!core_.medicineService.removeMedicine(mid.toStdString(), err)) {
QMessageBox::warning(this, tr("删除失败"), QString::fromStdString(err));
return;
}
// 记录详细日志 - 包含被删除药品的完整信息
if (!medDetails.empty()) {
logOperation(LogEntryType::MEDICINE_OPERATION, "REMOVE_MEDICINE", medDetails, mid.toStdString());
}
refreshMedicineTable();
refreshDashboard();
}
void MainWindow::handleIncreaseMedicineStock() {
QString mid = safeCurrentTableId(medicineTable_);
if (mid.isEmpty()) {
QMessageBox::warning(this, tr("增加库存"), tr("请先选择药品"));
return;
}
// 获取药品信息用于日志和检查
auto* medicine = core_.medicineService.findMedicine(mid.toStdString());
if (!medicine) {
QMessageBox::warning(this, tr("错误"), tr("药品不存在"));
return;
}
int oldStock = medicine->StockQuantity;
int maxAddable = MAX_MEDICINE_STOCK - oldStock;
if (maxAddable <= 0) {
QMessageBox::warning(this, tr("库存已达上限"),
tr("药品 %1 的库存已达上限 %2无法继续增加。")
.arg(QString::fromStdString(medicine->GenericName))
.arg(MAX_MEDICINE_STOCK));
return;
}
int amount = QInputDialog::getInt(this, tr("增加库存"),
tr("增加数量 (最大可增加 %1):").arg(maxAddable),
1, 1, maxAddable);
// 最终检查防止用户手动输入超过上限的值
if (oldStock + amount > MAX_MEDICINE_STOCK) {
QMessageBox::warning(this, tr("库存超限"),
tr("库存不能超过上限 %1。当前库存: %2增加数量: %3。")
.arg(MAX_MEDICINE_STOCK)
.arg(oldStock)
.arg(amount));
return;
}
if (!core_.medicineService.increaseStock(mid.toStdString(), amount)) {
QMessageBox::warning(this, tr("失败"), tr("库存增加失败"));
return;
}
// 记录详细日志
auto* newMedicine = core_.medicineService.findMedicine(mid.toStdString());
int newStock = newMedicine ? newMedicine->StockQuantity : 0;
std::string stockDetails =
"药品ID: " + mid.toStdString() +
" | 药品名: " + (medicine ? medicine->GenericName : "未知") +
" | 增加数量: " + std::to_string(amount) +
" | 库存变化: " + std::to_string(oldStock) + " -> " + std::to_string(newStock);
logOperation(LogEntryType::MEDICINE_OPERATION, "INCREASE_STOCK", stockDetails, mid.toStdString());
refreshMedicineTable();
}
void MainWindow::handleDecreaseMedicineStock() {
QString mid = safeCurrentTableId(medicineTable_);
if (mid.isEmpty()) {
QMessageBox::warning(this, tr("减少库存"), tr("请先选择药品"));
return;
}
// 获取药品信息用于日志
auto* medicine = core_.medicineService.findMedicine(mid.toStdString());
int oldStock = medicine ? medicine->StockQuantity : 0;
int amount = QInputDialog::getInt(this, tr("减少库存"), tr("减少数量:"), 1, 1, 100000);
if (!core_.medicineService.decreaseStock(mid.toStdString(), amount)) {
QMessageBox::warning(this, tr("失败"), tr("库存减少失败:可能库存不足"));
return;
}
// 记录详细日志
auto* newMedicine = core_.medicineService.findMedicine(mid.toStdString());
int newStock = newMedicine ? newMedicine->StockQuantity : 0;
std::string stockDetails =
"药品ID: " + mid.toStdString() +
" | 药品名: " + (medicine ? medicine->GenericName : "未知") +
" | 减少数量: " + std::to_string(amount) +
" | 库存变化: " + std::to_string(oldStock) + " -> " + std::to_string(newStock);
logOperation(LogEntryType::MEDICINE_OPERATION, "DECREASE_STOCK", stockDetails, mid.toStdString());
refreshMedicineTable();
}
void MainWindow::onMedicineSearch(const QString& text) {
for (int i = 0; i < medicineTable_->rowCount(); ++i) {
bool match = false;
for (int j = 0; j < medicineTable_->columnCount(); ++j) {
QTableWidgetItem* item = medicineTable_->item(i, j);
if (item && item->text().contains(text, Qt::CaseInsensitive)) {
match = true;
break;
}
}
medicineTable_->setRowHidden(i, !match);
}
}