Files
cangku/raw_material_dialog.py
liangweihao 14e25ad03b 原料编辑功能改为独立窗口
- 新增 RawMaterialEditDialog 独立编辑对话框类
- 编辑原料时弹出新窗口,不再复用"新增原料"标签页
- 编辑窗口中型号字段设为只读,防止误修改
- 新增原料功能改为纯新增模式,检查重复型号
- 移除 current_edit_model 状态变量,简化代码逻辑

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-27 18:34:28 +08:00

827 lines
34 KiB
Python
Raw 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.

"""
原料库管理模块 - 处理面料和原材料的管理
"""
import os
from datetime import datetime
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QHBoxLayout, QGridLayout, QLabel, QLineEdit,
QPushButton, QComboBox, QTableWidget, QTableWidgetItem, QHeaderView,
QMessageBox, QTabWidget, QWidget, QDoubleSpinBox, QTextEdit
)
from PyQt5.QtCore import Qt
from database import get_db_connection
from stock_dialog import StockInDialog
class RawMaterialEditDialog(QDialog):
"""原料编辑对话框 - 独立窗口"""
def __init__(self, db_path, model, is_admin=False, parent=None):
super().__init__(parent)
self.db_path = db_path
self.model = model
self.is_admin = is_admin
self.setWindowTitle(f"编辑原料 - {model}")
self.resize(600, 500)
self.setup_ui()
self.load_material_data()
def setup_ui(self):
"""设置用户界面"""
layout = QGridLayout(self)
# 类目选择
layout.addWidget(QLabel("类目:"), 0, 0, Qt.AlignRight)
self.edit_major_category = QComboBox()
self.edit_major_category.setEditable(True)
layout.addWidget(self.edit_major_category, 0, 1)
layout.addWidget(QLabel("类型:"), 0, 2, Qt.AlignRight)
self.edit_sub_category = QLineEdit()
layout.addWidget(self.edit_sub_category, 0, 3)
# 基本信息
layout.addWidget(QLabel("型号:"), 1, 0, Qt.AlignRight)
self.edit_model = QLineEdit()
self.edit_model.setReadOnly(True) # 型号不可修改
self.edit_model.setStyleSheet("background-color: #f0f0f0;")
layout.addWidget(self.edit_model, 1, 1, 1, 3)
layout.addWidget(QLabel("供应商:"), 2, 0, Qt.AlignRight)
self.edit_supplier = QComboBox()
self.edit_supplier.setEditable(True)
layout.addWidget(self.edit_supplier, 2, 1, 1, 3)
layout.addWidget(QLabel("颜色:"), 3, 0, Qt.AlignRight)
self.edit_color = QLineEdit()
layout.addWidget(self.edit_color, 3, 1, 1, 3)
# 规格信息
layout.addWidget(QLabel("幅宽 (cm):"), 4, 0, Qt.AlignRight)
self.edit_width = QDoubleSpinBox()
self.edit_width.setRange(0, 300)
self.edit_width.setValue(0)
layout.addWidget(self.edit_width, 4, 1)
layout.addWidget(QLabel("克重 (g/m²):"), 5, 0, Qt.AlignRight)
self.edit_gsm = QDoubleSpinBox()
self.edit_gsm.setRange(0, 1000)
self.edit_gsm.setValue(0)
layout.addWidget(self.edit_gsm, 5, 1)
layout.addWidget(QLabel("单位:"), 6, 0, Qt.AlignRight)
self.edit_unit = QComboBox()
self.edit_unit.setEditable(True)
self.edit_unit.addItems(["", "", "公斤", "一对", "", ""])
layout.addWidget(self.edit_unit, 6, 1)
# 价格信息
layout.addWidget(QLabel("散剪价 (元/单位):"), 7, 0, Qt.AlignRight)
self.edit_retail = QDoubleSpinBox()
self.edit_retail.setRange(0, 10000)
self.edit_retail.setDecimals(2)
layout.addWidget(self.edit_retail, 7, 1)
layout.addWidget(QLabel("大货价 (元/单位):"), 8, 0, Qt.AlignRight)
self.edit_bulk = QDoubleSpinBox()
self.edit_bulk.setRange(0, 10000)
self.edit_bulk.setDecimals(2)
layout.addWidget(self.edit_bulk, 8, 1)
# 按钮
button_layout = QHBoxLayout()
save_btn = QPushButton("保存修改")
save_btn.clicked.connect(self.save_changes)
save_btn.setStyleSheet("background-color: #4CAF50; color: white; padding: 8px; font-weight: bold;")
button_layout.addWidget(save_btn)
cancel_btn = QPushButton("取消")
cancel_btn.clicked.connect(self.reject)
button_layout.addWidget(cancel_btn)
layout.addLayout(button_layout, 9, 0, 1, 4)
# 加载类目和供应商列表
self.load_categories_and_suppliers()
def get_conn(self):
"""获取数据库连接"""
return get_db_connection(self.db_path)
def load_categories_and_suppliers(self):
"""加载类目和供应商列表"""
try:
with self.get_conn() as conn:
# 加载类目
cursor = conn.execute("SELECT DISTINCT category FROM fabrics WHERE category IS NOT NULL AND category != ''")
majors = set(row[0] for row in cursor.fetchall() if row[0])
majors.update({"布料", "辅料", "其他"})
self.edit_major_category.addItems(sorted(majors))
# 加载供应商
cursor = conn.execute("SELECT DISTINCT supplier FROM fabrics WHERE supplier IS NOT NULL AND supplier != '' ORDER BY supplier")
suppliers = [row[0] for row in cursor.fetchall()]
self.edit_supplier.addItems(suppliers)
except:
pass
def load_material_data(self):
"""加载原料数据"""
try:
with self.get_conn() as conn:
cursor = conn.execute(
"SELECT category, fabric_type, supplier, color, width, gsm, unit, retail_price, bulk_price FROM fabrics WHERE model = ?",
(self.model,)
)
row = cursor.fetchone()
if not row:
QMessageBox.warning(self, "提示", "原料不存在或已被删除")
self.reject()
return
category, fabric_type, supplier, color, width, gsm, unit, retail, bulk = row
# 填充表单
self.edit_model.setText(self.model)
self.edit_major_category.setCurrentText(category or "")
self.edit_sub_category.setText(fabric_type or "")
self.edit_supplier.setCurrentText(supplier or "")
self.edit_color.setText(color or "")
self.edit_width.setValue(width or 0)
self.edit_gsm.setValue(gsm or 0)
self.edit_unit.setCurrentText(unit or "")
self.edit_retail.setValue(retail or 0)
self.edit_bulk.setValue(bulk or 0)
# 特殊处理胸杯类型
if fabric_type and "胸杯" in fabric_type:
self.edit_unit.setEnabled(False)
except Exception as e:
QMessageBox.critical(self, "错误", f"加载原料信息失败: {str(e)}")
self.reject()
def save_changes(self):
"""保存修改"""
major = self.edit_major_category.currentText().strip()
sub = self.edit_sub_category.text().strip()
# 特殊处理胸杯类型
if "胸杯" in sub:
major = "辅料"
category = major or ""
fabric_type = sub or ""
supplier = self.edit_supplier.currentText().strip()
color = self.edit_color.text().strip()
unit = self.edit_unit.currentText().strip() or ""
try:
with self.get_conn() as conn:
conn.execute('''
UPDATE fabrics
SET category=?, fabric_type=?, supplier=?, color=?, width=?, gsm=?,
retail_price=?, bulk_price=?, unit=?, timestamp=?
WHERE model=?
''', (
category, fabric_type, supplier, color,
self.edit_width.value() or None,
self.edit_gsm.value() or None,
self.edit_retail.value() or None,
self.edit_bulk.value() or None,
unit,
datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
self.model
))
conn.commit()
QMessageBox.information(self, "成功", f"已更新 '{self.model}'")
self.accept()
except Exception as e:
QMessageBox.critical(self, "错误", f"保存失败: {str(e)}")
class RawMaterialLibraryDialog(QDialog):
def __init__(self, db_path, is_admin=False):
super().__init__()
self.db_path = db_path
self.is_admin = is_admin
self.setWindowTitle("原料库管理")
self.resize(1400, 700)
self.setup_ui()
self.refresh_filters_and_table()
self.load_add_major_categories()
self.load_stock_table()
def setup_ui(self):
"""设置用户界面"""
layout = QVBoxLayout(self)
# 工具栏
toolbar = QHBoxLayout()
stock_in_btn = QPushButton("📥 原料入库管理(独立)")
stock_in_btn.clicked.connect(self.open_stock_in_dialog)
stock_in_btn.setStyleSheet("background-color: #ff5722; color: white; padding: 10px; font-weight: bold;")
toolbar.addWidget(stock_in_btn)
toolbar.addStretch()
layout.addLayout(toolbar)
# 标签页
tabs = QTabWidget()
layout.addWidget(tabs)
# 原料列表标签页
list_tab = self.create_list_tab()
tabs.addTab(list_tab, "原料列表")
# 新增/编辑标签页
add_tab = self.create_add_tab()
tabs.addTab(add_tab, "新增原料")
# 库存跟踪标签页
stock_tab = self.create_stock_tab()
tabs.addTab(stock_tab, "库存跟踪")
def create_list_tab(self):
"""创建原料列表标签页"""
list_tab = QWidget()
list_layout = QVBoxLayout(list_tab)
# 过滤器区域
filter_layout = QHBoxLayout()
filter_layout.addWidget(QLabel("类目筛选:"))
self.major_combo = QComboBox()
self.major_combo.addItem("全部类目")
self.major_combo.currentIndexChanged.connect(self.load_sub_categories)
filter_layout.addWidget(self.major_combo)
filter_layout.addWidget(QLabel("类型筛选:"))
self.sub_combo = QComboBox()
self.sub_combo.addItem("全部类型")
self.sub_combo.currentIndexChanged.connect(self.load_table)
filter_layout.addWidget(self.sub_combo)
filter_layout.addWidget(QLabel("供应商筛选:"))
self.supplier_combo = QComboBox()
self.supplier_combo.addItem("全部供应商")
self.supplier_combo.currentIndexChanged.connect(self.load_table)
filter_layout.addWidget(self.supplier_combo)
filter_layout.addWidget(QLabel("搜索型号:"))
self.search_input = QLineEdit()
self.search_input.textChanged.connect(self.load_table)
filter_layout.addWidget(self.search_input)
refresh_btn = QPushButton("刷新")
refresh_btn.clicked.connect(self.refresh_filters_and_table)
filter_layout.addWidget(refresh_btn)
list_layout.addLayout(filter_layout)
# 数据表格
headers = ["类目", "类型", "型号", "供应商", "颜色", "幅宽(cm)", "克重(g/m²)", "单位", "散剪价", "大货价(单位)", "米价", "码价", "操作"]
self.table = QTableWidget()
self.table.setColumnCount(len(headers))
self.table.setHorizontalHeaderLabels(headers)
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.table.setEditTriggers(QTableWidget.NoEditTriggers) # 禁止编辑表格内容
list_layout.addWidget(self.table)
# 隐藏价格列(非管理员)
if not self.is_admin:
self.table.setColumnHidden(9, True)
self.table.setColumnHidden(10, True)
self.table.setColumnHidden(11, True)
return list_tab
def create_add_tab(self):
"""创建新增/编辑标签页"""
add_tab = QWidget()
add_layout = QGridLayout(add_tab)
# 类目选择
add_layout.addWidget(QLabel("类目:"), 0, 0, Qt.AlignRight)
self.add_major_category = QComboBox()
self.add_major_category.setEditable(True)
add_layout.addWidget(self.add_major_category, 0, 1)
add_layout.addWidget(QLabel("类型:"), 0, 2, Qt.AlignRight)
self.add_sub_category = QLineEdit()
add_layout.addWidget(self.add_sub_category, 0, 3)
# 基本信息
add_layout.addWidget(QLabel("型号:"), 2, 0, Qt.AlignRight)
self.add_model = QLineEdit()
add_layout.addWidget(self.add_model, 2, 1, 1, 3)
add_layout.addWidget(QLabel("供应商:"), 3, 0, Qt.AlignRight)
self.add_supplier = QComboBox()
self.add_supplier.setEditable(True)
add_layout.addWidget(self.add_supplier, 3, 1, 1, 3)
add_layout.addWidget(QLabel("颜色:"), 4, 0, Qt.AlignRight)
self.add_color = QLineEdit()
add_layout.addWidget(self.add_color, 4, 1, 1, 3)
# 规格信息
add_layout.addWidget(QLabel("幅宽 (cm):"), 5, 0, Qt.AlignRight)
self.add_width = QDoubleSpinBox()
self.add_width.setRange(0, 300)
self.add_width.setValue(0)
add_layout.addWidget(self.add_width, 5, 1)
add_layout.addWidget(QLabel("克重 (g/m²):"), 6, 0, Qt.AlignRight)
self.add_gsm = QDoubleSpinBox()
self.add_gsm.setRange(0, 1000)
self.add_gsm.setValue(0)
add_layout.addWidget(self.add_gsm, 6, 1)
add_layout.addWidget(QLabel("单位:"), 7, 0, Qt.AlignRight)
self.add_unit = QComboBox()
self.add_unit.setEditable(True)
self.add_unit.addItems(["", "", "公斤", "一对", "", ""])
add_layout.addWidget(self.add_unit, 7, 1)
# 价格信息
add_layout.addWidget(QLabel("散剪价 (元/单位):"), 8, 0, Qt.AlignRight)
self.add_retail = QDoubleSpinBox()
self.add_retail.setRange(0, 10000)
self.add_retail.setDecimals(2)
add_layout.addWidget(self.add_retail, 8, 1)
add_layout.addWidget(QLabel("大货价 (元/单位):"), 9, 0, Qt.AlignRight)
self.add_bulk = QDoubleSpinBox()
self.add_bulk.setRange(0, 10000)
self.add_bulk.setDecimals(2)
add_layout.addWidget(self.add_bulk, 9, 1)
# 保存按钮
save_btn = QPushButton("保存原料")
save_btn.clicked.connect(self.save_raw_material)
add_layout.addWidget(save_btn, 10, 0, 1, 4)
return add_tab
def create_stock_tab(self):
"""创建库存跟踪标签页"""
stock_tab = QWidget()
stock_layout = QVBoxLayout(stock_tab)
stock_refresh = QPushButton("刷新库存")
stock_refresh.clicked.connect(self.load_stock_table)
stock_layout.addWidget(stock_refresh)
stock_headers = ["型号/名称", "颜色", "单位", "总采购量", "总消耗量", "当前剩余", "操作"]
self.stock_table = QTableWidget()
self.stock_table.setColumnCount(len(stock_headers))
self.stock_table.setHorizontalHeaderLabels(stock_headers)
self.stock_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
stock_layout.addWidget(self.stock_table)
return stock_tab
def get_conn(self):
"""获取数据库连接"""
return get_db_connection(self.db_path)
def open_stock_in_dialog(self):
"""打开入库对话框"""
dialog = StockInDialog(self.db_path)
dialog.exec_()
self.load_stock_table()
def refresh_filters_and_table(self):
"""刷新过滤器和表格"""
self.load_major_categories()
self.load_suppliers()
self.load_table()
def load_major_categories(self):
"""加载主类目"""
try:
with self.get_conn() as conn:
cursor = conn.execute("SELECT DISTINCT category FROM fabrics WHERE category IS NOT NULL AND category != ''")
majors = set(row[0] for row in cursor.fetchall() if row[0])
majors.update({"布料", "辅料", "其他"})
self.major_combo.blockSignals(True)
self.major_combo.clear()
self.major_combo.addItem("全部类目")
self.major_combo.addItems(sorted(majors))
self.major_combo.blockSignals(False)
except:
pass
self.load_sub_categories()
def load_add_major_categories(self):
"""加载添加界面的主类目"""
try:
with self.get_conn() as conn:
cursor = conn.execute("SELECT DISTINCT category FROM fabrics WHERE category IS NOT NULL AND category != ''")
majors = set(row[0] for row in cursor.fetchall() if row[0])
majors.update({"布料", "辅料", "其他"})
self.add_major_category.blockSignals(True)
current_text = self.add_major_category.currentText()
self.add_major_category.clear()
self.add_major_category.addItems(sorted(majors))
if current_text in majors:
self.add_major_category.setCurrentText(current_text)
else:
self.add_major_category.setCurrentText("布料")
self.add_major_category.blockSignals(False)
except:
self.add_major_category.clear()
self.add_major_category.addItems(["布料", "辅料", "其他"])
self.add_major_category.setCurrentText("布料")
def load_sub_categories(self):
"""加载子类型"""
major = self.major_combo.currentText()
self.sub_combo.blockSignals(True)
self.sub_combo.clear()
self.sub_combo.addItem("全部类型")
try:
with self.get_conn() as conn:
if major in ("全部类目", ""):
cursor = conn.execute("SELECT DISTINCT fabric_type FROM fabrics WHERE fabric_type IS NOT NULL AND fabric_type != ''")
subs = [row[0] for row in cursor.fetchall() if row[0]]
self.sub_combo.addItems(sorted(subs))
else:
cursor = conn.execute("SELECT DISTINCT fabric_type FROM fabrics WHERE category = ? AND fabric_type IS NOT NULL AND fabric_type != ''", (major,))
subs = [row[0] for row in cursor.fetchall() if row[0]]
self.sub_combo.addItems(sorted(subs))
except:
pass
self.sub_combo.blockSignals(False)
self.load_table()
def load_suppliers(self):
"""加载供应商列表"""
try:
with self.get_conn() as conn:
cursor = conn.execute("SELECT DISTINCT supplier FROM fabrics WHERE supplier IS NOT NULL AND supplier != '' ORDER BY supplier")
suppliers = [row[0] for row in cursor.fetchall()]
self.supplier_combo.blockSignals(True)
self.supplier_combo.clear()
self.supplier_combo.addItem("全部供应商")
self.supplier_combo.addItems(suppliers)
self.supplier_combo.blockSignals(False)
self.add_supplier.blockSignals(True)
self.add_supplier.clear()
self.add_supplier.addItems(suppliers)
self.add_supplier.blockSignals(False)
except:
pass
def load_table(self):
"""加载原料表格数据"""
try:
with self.get_conn() as conn:
query = "SELECT category, fabric_type, model, supplier, color, width, gsm, unit, retail_price, bulk_price FROM fabrics"
params = []
conditions = []
# 类目过滤
major = self.major_combo.currentText()
sub = self.sub_combo.currentText()
if major != "全部类目" and major:
if sub != "全部类型" and sub:
conditions.append("category = ? AND fabric_type = ?")
params.append(major)
params.append(sub)
else:
conditions.append("category = ?")
params.append(major)
elif sub != "全部类型" and sub:
# 只选择了类型,没有选择类目
conditions.append("fabric_type = ?")
params.append(sub)
# 供应商过滤
supplier = self.supplier_combo.currentText()
if supplier != "全部供应商" and supplier:
conditions.append("supplier = ?")
params.append(supplier)
# 关键词搜索
keyword = self.search_input.text().strip()
if keyword:
conditions.append("(model LIKE ? OR color LIKE ?)")
params.append("%" + keyword + "%")
params.append("%" + keyword + "%")
if conditions:
query += " WHERE " + " AND ".join(conditions)
query += " ORDER BY timestamp DESC"
cursor = conn.execute(query, params)
rows = cursor.fetchall()
# 填充表格
self.table.setRowCount(len(rows))
self.table.clearContents()
for row_idx, (category, fabric_type, model, supplier, color, width, gsm, unit, retail, bulk) in enumerate(rows):
major = category or ""
sub = fabric_type or ""
self.table.setItem(row_idx, 0, QTableWidgetItem(major))
self.table.setItem(row_idx, 1, QTableWidgetItem(sub))
self.table.setItem(row_idx, 2, QTableWidgetItem(model))
self.table.setItem(row_idx, 3, QTableWidgetItem(supplier or ""))
self.table.setItem(row_idx, 4, QTableWidgetItem(color or ""))
self.table.setItem(row_idx, 5, QTableWidgetItem("{:.1f}".format(width) if width else ""))
self.table.setItem(row_idx, 6, QTableWidgetItem("{:.0f}".format(gsm) if gsm else ""))
self.table.setItem(row_idx, 7, QTableWidgetItem(unit or ""))
self.table.setItem(row_idx, 8, QTableWidgetItem("{:.2f}".format(retail) if retail is not None else ""))
if self.is_admin:
unit_display = unit or ""
bulk_display = "{:.2f} ({})".format(bulk, unit_display) if bulk is not None else ""
self.table.setItem(row_idx, 9, QTableWidgetItem(bulk_display))
# 计算米价和码价
price_per_m = price_per_yard = 0.0
if bulk and width and gsm and width > 0 and gsm > 0:
if unit == "":
price_per_m = bulk
elif unit == "":
price_per_m = bulk / 0.9144
elif unit == "公斤":
price_per_m = bulk * (gsm / 1000.0) * (width / 100.0)
price_per_yard = price_per_m * 0.9144
self.table.setItem(row_idx, 10, QTableWidgetItem("{:.2f}".format(price_per_m)))
self.table.setItem(row_idx, 11, QTableWidgetItem("{:.2f}".format(price_per_yard)))
# 操作按钮
op_widget = QWidget()
op_layout = QHBoxLayout(op_widget)
op_layout.setContentsMargins(5, 2, 5, 2)
op_layout.setSpacing(10)
edit_btn = QPushButton("编辑")
edit_btn.clicked.connect(lambda _, m=model: self.edit_raw_material(m))
op_layout.addWidget(edit_btn)
del_btn = QPushButton("删除")
del_btn.clicked.connect(lambda _, m=model: self.delete_raw(m))
op_layout.addWidget(del_btn)
self.table.setCellWidget(row_idx, self.table.columnCount() - 1, op_widget)
except Exception as e:
QMessageBox.critical(self, "加载失败", str(e))
def edit_raw_material(self, model):
"""编辑原料 - 打开独立编辑窗口"""
dialog = RawMaterialEditDialog(self.db_path, model, self.is_admin, self)
if dialog.exec_() == QDialog.Accepted:
# 编辑成功后刷新列表
self.refresh_filters_and_table()
def delete_raw(self, model):
"""删除原料"""
reply = QMessageBox.question(self, "确认", f"删除 '{model}'")
if reply == QMessageBox.Yes:
try:
with self.get_conn() as conn:
conn.execute("DELETE FROM fabrics WHERE model=?", (model,))
conn.commit()
self.load_table()
QMessageBox.information(self, "成功", "删除完成")
except Exception as e:
QMessageBox.critical(self, "错误", "删除失败: " + str(e))
def save_raw_material(self):
"""保存新增原料"""
model = self.add_model.text().strip()
if not model:
QMessageBox.warning(self, "错误", "请输入型号/名称")
return
major = self.add_major_category.currentText().strip()
sub = self.add_sub_category.text().strip()
if "胸杯" in sub:
major = "辅料"
category = major or ""
fabric_type = sub or ""
supplier = self.add_supplier.currentText().strip()
color = self.add_color.text().strip()
unit = self.add_unit.currentText().strip() or ""
try:
with self.get_conn() as conn:
# 检查是否已存在
cursor = conn.execute("SELECT model FROM fabrics WHERE model = ?", (model,))
if cursor.fetchone():
QMessageBox.warning(self, "错误", f"型号 '{model}' 已存在,请使用编辑功能修改")
return
conn.execute('''
INSERT INTO fabrics
(model, category, fabric_type, supplier, color, width, gsm, retail_price, bulk_price, unit, timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (model, category, fabric_type, supplier, color,
self.add_width.value() or None, self.add_gsm.value() or None,
self.add_retail.value() or None, self.add_bulk.value() or None,
unit, datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
conn.commit()
QMessageBox.information(self, "成功", f"已保存 '{model}'")
# 清空表单
self.add_model.clear()
self.add_color.clear()
self.add_width.setValue(0)
self.add_gsm.setValue(0)
self.add_retail.setValue(0)
self.add_bulk.setValue(0)
self.add_sub_category.clear()
self.add_unit.setEnabled(True)
self.refresh_filters_and_table()
self.load_add_major_categories()
except Exception as e:
QMessageBox.critical(self, "错误", str(e))
def load_stock_table(self):
"""加载库存表格"""
try:
with self.get_conn() as conn:
cursor = conn.execute('''
SELECT f.model, f.color, f.unit,
COALESCE(SUM(si.quantity), 0) AS total_in,
COALESCE(SUM(c.consume_quantity), 0) AS total_out
FROM fabrics f
LEFT JOIN fabric_stock_in si ON f.model = si.model
LEFT JOIN fabric_consumption c ON f.model = c.model
GROUP BY f.model
ORDER BY f.timestamp DESC
''')
rows = cursor.fetchall()
self.stock_table.setRowCount(len(rows))
for row_idx, (model, color, unit, total_in, total_out) in enumerate(rows):
remaining = total_in - total_out
self.stock_table.setItem(row_idx, 0, QTableWidgetItem(model))
self.stock_table.setItem(row_idx, 1, QTableWidgetItem(color or ""))
self.stock_table.setItem(row_idx, 2, QTableWidgetItem(unit or ""))
self.stock_table.setItem(row_idx, 3, QTableWidgetItem("{:.3f}".format(total_in)))
self.stock_table.setItem(row_idx, 4, QTableWidgetItem("{:.3f}".format(total_out)))
self.stock_table.setItem(row_idx, 5, QTableWidgetItem("{:.3f}".format(remaining)))
# 操作按钮
op_widget = QWidget()
op_layout = QHBoxLayout(op_widget)
op_layout.setContentsMargins(5, 2, 5, 2)
op_layout.setSpacing(10)
detail_btn = QPushButton("查看明细")
detail_btn.clicked.connect(lambda _, m=model: self.show_stock_detail(m))
op_layout.addWidget(detail_btn)
clear_btn = QPushButton("一键清零剩余")
clear_btn.clicked.connect(lambda _, m=model: self.clear_remaining(m))
op_layout.addWidget(clear_btn)
self.stock_table.setCellWidget(row_idx, 6, op_widget)
except Exception as e:
QMessageBox.critical(self, "错误", str(e))
def show_stock_detail(self, model):
"""显示库存明细"""
try:
with self.get_conn() as conn:
# 查询入库记录
cursor_in = conn.execute("SELECT purchase_date, quantity, unit, note FROM fabric_stock_in WHERE model = ? ORDER BY purchase_date DESC", (model,))
in_rows = cursor_in.fetchall()
# 查询消耗记录
cursor_out = conn.execute('''
SELECT consume_date, style_number, quantity_made, loss_rate, consume_quantity, unit
FROM fabric_consumption WHERE model = ? ORDER BY consume_date DESC
''', (model,))
out_rows = cursor_out.fetchall()
text = f"{model}】库存明细\n\n"
text += "=== 采购入库记录 ===\n"
if in_rows:
for date, qty, unit, note in in_rows:
text += f"{date} +{qty} {unit} {note or ''}\n"
else:
text += "暂无入库记录\n"
text += "\n=== 生产消耗记录 ===\n"
if out_rows:
for date, style, qty_made, loss, consume, unit in out_rows:
text += f"{date} {style} {qty_made}件 (损耗{round(loss * 100, 1)}%) -{round(consume, 3)} {unit}\n"
else:
text += "暂无消耗记录\n"
# 显示明细对话框
dialog = QDialog(self)
dialog.setWindowTitle(model + " 库存明细")
dialog.resize(800, 600)
layout = QVBoxLayout(dialog)
text_edit = QTextEdit()
text_edit.setReadOnly(True)
text_edit.setText(text)
layout.addWidget(text_edit)
close_btn = QPushButton("关闭")
close_btn.clicked.connect(dialog.accept)
layout.addWidget(close_btn)
dialog.exec_()
except Exception as e:
QMessageBox.critical(self, "错误", str(e))
def clear_remaining(self, model):
"""清零剩余库存"""
reply = QMessageBox.question(
self,
"确认清零",
f"确定将 {model} 的剩余量清零?"
)
if reply != QMessageBox.Yes:
return
try:
with self.get_conn() as conn:
# 计算当前总入库量和总消耗量
cursor_in = conn.execute(
"SELECT COALESCE(SUM(quantity), 0) FROM fabric_stock_in WHERE model = ?",
(model,)
)
total_in = cursor_in.fetchone()[0] or 0.0
cursor_out = conn.execute(
"SELECT COALESCE(SUM(consume_quantity), 0) FROM fabric_consumption WHERE model = ?",
(model,)
)
total_out = cursor_out.fetchone()[0] or 0.0
remaining = round(float(total_in) - float(total_out), 6)
if remaining <= 0:
QMessageBox.information(self, "提示", f"{model} 当前没有可清零的剩余库存。")
return
# 获取用于记录此次清零的单位:优先使用最近一次入库单位,其次是原料表里的单位,最后默认“米”
unit = ""
cursor_unit = conn.execute(
"SELECT unit FROM fabric_stock_in WHERE model = ? ORDER BY purchase_date DESC LIMIT 1",
(model,)
)
row_unit = cursor_unit.fetchone()
if row_unit and row_unit[0]:
unit = row_unit[0]
else:
cursor_fabric = conn.execute(
"SELECT unit FROM fabrics WHERE model = ?",
(model,)
)
row_fabric = cursor_fabric.fetchone()
if row_fabric and row_fabric[0]:
unit = row_fabric[0]
# 以一条“清零”消耗记录的方式写入fabric_consumption
conn.execute(
"""
INSERT INTO fabric_consumption
(style_number, model, single_usage, quantity_made, loss_rate, consume_quantity, consume_date, unit)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""",
(
"库存清零", # 特殊标记,表示这是一次手工清零操作
model,
None, # 单件用量无意义置为NULL
0, # 生产件数为0
0.0, # 损耗率0
remaining, # 一次性消耗掉当前所有剩余
datetime.now().strftime('%Y-%m-%d'),
unit,
)
)
conn.commit()
QMessageBox.information(
self,
"完成",
f"{model} 剩余量 {remaining:.3f} {unit} 已清零。"
)
self.load_stock_table()
except Exception as e:
QMessageBox.critical(self, "错误", f"清零失败: {str(e)}")