- 为fabrics表添加is_deleted字段用于标记删除状态 - 修改delete_raw方法实现逻辑删除而非物理删除 - 更新所有查询语句过滤已删除的原料数据 - 更新库存视图过滤已删除的原料和相关记录 - 保留历史数据,支持数据恢复 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
963 lines
40 KiB
Python
963 lines
40 KiB
Python
"""
|
||
原料库管理模块 - 处理面料和原材料的管理
|
||
"""
|
||
|
||
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()
|
||
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 != '' AND (is_deleted IS NULL OR is_deleted = 0)")
|
||
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 != '' AND (is_deleted IS NULL OR is_deleted = 0) 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 = ? AND (is_deleted IS NULL OR is_deleted = 0)",
|
||
(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):
|
||
"""保存修改"""
|
||
new_model = self.edit_model.text().strip()
|
||
|
||
# 检查型号是否为空
|
||
if not new_model:
|
||
QMessageBox.warning(self, "错误", "型号不能为空")
|
||
return
|
||
|
||
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:
|
||
# 如果型号被修改了,检查新型号是否已存在
|
||
if new_model != self.model:
|
||
cursor = conn.execute("SELECT model FROM fabrics WHERE model = ?", (new_model,))
|
||
if cursor.fetchone():
|
||
QMessageBox.warning(self, "错误", f"型号 '{new_model}' 已存在,请使用其他型号")
|
||
return
|
||
|
||
# 型号被修改,需要更新所有相关表
|
||
# 更新原料表
|
||
conn.execute('''
|
||
UPDATE fabrics
|
||
SET model=?, category=?, fabric_type=?, supplier=?, color=?, width=?, gsm=?,
|
||
retail_price=?, bulk_price=?, unit=?, timestamp=?
|
||
WHERE model=?
|
||
''', (
|
||
new_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.execute("UPDATE fabric_stock_in SET model=? WHERE model=?", (new_model, self.model))
|
||
|
||
# 更新消耗记录表
|
||
conn.execute("UPDATE fabric_consumption SET model=? WHERE model=?", (new_model, self.model))
|
||
|
||
# 更新衣服材料用量表
|
||
conn.execute("UPDATE garment_materials SET model=? WHERE model=?", (new_model, self.model))
|
||
|
||
else:
|
||
# 型号未修改,只更新其他字段
|
||
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()
|
||
|
||
if new_model != self.model:
|
||
QMessageBox.information(self, "成功", f"已将 '{self.model}' 更新为 '{new_model}'")
|
||
else:
|
||
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.Interactive)
|
||
# 设置操作列固定宽度,其他列自动拉伸
|
||
for i in range(6):
|
||
self.stock_table.horizontalHeader().setSectionResizeMode(i, QHeaderView.Stretch)
|
||
self.stock_table.horizontalHeader().setSectionResizeMode(6, QHeaderView.Fixed)
|
||
self.stock_table.setColumnWidth(6, 400) # 操作列固定宽度400像素
|
||
self.stock_table.setEditTriggers(QTableWidget.NoEditTriggers) # 设置为只读模式
|
||
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 != '' AND (is_deleted IS NULL OR is_deleted = 0)")
|
||
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 != '' AND (is_deleted IS NULL OR is_deleted = 0)")
|
||
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 != '' AND (is_deleted IS NULL OR is_deleted = 0)")
|
||
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 != '' AND (is_deleted IS NULL OR is_deleted = 0)", (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 != '' AND (is_deleted IS NULL OR is_deleted = 0) 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 = []
|
||
|
||
# 过滤已删除的原料
|
||
conditions.append("(is_deleted IS NULL OR is_deleted = 0)")
|
||
|
||
# 类目过滤
|
||
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}' 吗?\n\n此操作将标记删除该原料,不会影响历史数据。")
|
||
if reply == QMessageBox.Yes:
|
||
try:
|
||
with self.get_conn() as conn:
|
||
# 逻辑删除:设置is_deleted=1
|
||
conn.execute("UPDATE fabrics SET is_deleted=1, updated_at=CURRENT_TIMESTAMP 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(CASE WHEN si.is_deleted = 0 THEN si.quantity ELSE 0 END), 0) AS total_in,
|
||
COALESCE(SUM(CASE WHEN c.is_deleted = 0 THEN c.consume_quantity ELSE 0 END), 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)
|
||
|
||
edit_btn = QPushButton("编辑剩余库存")
|
||
edit_btn.clicked.connect(lambda _, m=model, u=unit, r=remaining: self.edit_remaining_stock(m, u, r))
|
||
op_layout.addWidget(edit_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 = ? AND is_deleted = 0 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 = ? AND is_deleted = 0 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.warning(
|
||
self,
|
||
"确认清零",
|
||
f"确定将 {model} 的剩余量清零吗?\n\n"
|
||
"此操作将标记删除该原料的所有历史采购和消耗记录。",
|
||
QMessageBox.Yes | QMessageBox.No,
|
||
QMessageBox.No
|
||
)
|
||
if reply != QMessageBox.Yes:
|
||
return
|
||
|
||
try:
|
||
with self.get_conn() as conn:
|
||
# 逻辑删除历史采购记录
|
||
conn.execute(
|
||
"UPDATE fabric_stock_in SET is_deleted = 1, updated_at = CURRENT_TIMESTAMP WHERE model = ? AND is_deleted = 0",
|
||
(model,)
|
||
)
|
||
|
||
# 逻辑删除历史消耗记录
|
||
conn.execute(
|
||
"UPDATE fabric_consumption SET is_deleted = 1, updated_at = CURRENT_TIMESTAMP WHERE model = ? AND is_deleted = 0",
|
||
(model,)
|
||
)
|
||
|
||
conn.commit()
|
||
|
||
QMessageBox.information(
|
||
self,
|
||
"完成",
|
||
f"{model} 的剩余库存已清零。"
|
||
)
|
||
self.load_stock_table()
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "错误", f"清零失败: {str(e)}")
|
||
|
||
def edit_remaining_stock(self, model, unit, current_remaining):
|
||
"""编辑剩余库存"""
|
||
from PyQt5.QtWidgets import QDialogButtonBox
|
||
|
||
# 创建编辑对话框
|
||
dialog = QDialog(self)
|
||
dialog.setWindowTitle(f"编辑剩余库存 - {model}")
|
||
dialog.resize(400, 200)
|
||
|
||
layout = QVBoxLayout(dialog)
|
||
|
||
# 显示当前剩余量
|
||
info_label = QLabel(f"当前剩余量: {current_remaining:.3f} {unit}")
|
||
info_label.setStyleSheet("font-weight: bold; color: #2196F3; font-size: 14px;")
|
||
layout.addWidget(info_label)
|
||
|
||
# 新剩余量输入
|
||
input_layout = QHBoxLayout()
|
||
input_layout.addWidget(QLabel("新剩余量:"))
|
||
new_remaining_input = QDoubleSpinBox()
|
||
new_remaining_input.setRange(0, 999999)
|
||
new_remaining_input.setDecimals(3)
|
||
new_remaining_input.setValue(current_remaining)
|
||
input_layout.addWidget(new_remaining_input)
|
||
input_layout.addWidget(QLabel(unit))
|
||
layout.addLayout(input_layout)
|
||
|
||
# 警告提示
|
||
warning_label = QLabel(
|
||
"⚠️ 警告:保存后将删除该原料的所有历史采购和消耗记录!\n"
|
||
"系统将只保留新设置的剩余库存量。"
|
||
)
|
||
warning_label.setStyleSheet(
|
||
"background-color: #fff3cd; color: #856404; "
|
||
"padding: 10px; border: 1px solid #ffc107; border-radius: 5px;"
|
||
)
|
||
warning_label.setWordWrap(True)
|
||
layout.addWidget(warning_label)
|
||
|
||
# 按钮
|
||
button_box = QDialogButtonBox(
|
||
QDialogButtonBox.Save | QDialogButtonBox.Cancel
|
||
)
|
||
button_box.accepted.connect(dialog.accept)
|
||
button_box.rejected.connect(dialog.reject)
|
||
layout.addWidget(button_box)
|
||
|
||
# 显示对话框
|
||
if dialog.exec_() == QDialog.Accepted:
|
||
new_remaining = new_remaining_input.value()
|
||
self.save_new_remaining_stock(model, unit, new_remaining)
|
||
|
||
def save_new_remaining_stock(self, model, unit, new_remaining):
|
||
"""保存新的剩余库存量(逻辑删除历史记录)"""
|
||
# 二次确认
|
||
reply = QMessageBox.warning(
|
||
self,
|
||
"最后确认",
|
||
f"确定要将 {model} 的剩余库存设置为 {new_remaining:.3f} {unit} 吗?\n\n"
|
||
"此操作将:\n"
|
||
"1. 标记删除该原料的所有历史采购记录\n"
|
||
"2. 标记删除该原料的所有历史消耗记录\n"
|
||
"3. 创建一条新的入库记录作为当前库存",
|
||
QMessageBox.Yes | QMessageBox.No,
|
||
QMessageBox.No
|
||
)
|
||
|
||
if reply != QMessageBox.Yes:
|
||
return
|
||
|
||
try:
|
||
with self.get_conn() as conn:
|
||
# 逻辑删除历史采购记录
|
||
conn.execute(
|
||
"UPDATE fabric_stock_in SET is_deleted = 1, updated_at = CURRENT_TIMESTAMP WHERE model = ? AND is_deleted = 0",
|
||
(model,)
|
||
)
|
||
|
||
# 逻辑删除历史消耗记录
|
||
conn.execute(
|
||
"UPDATE fabric_consumption SET is_deleted = 1, updated_at = CURRENT_TIMESTAMP WHERE model = ? AND is_deleted = 0",
|
||
(model,)
|
||
)
|
||
|
||
# 如果新剩余量大于0,创建一条新的入库记录
|
||
if new_remaining > 0:
|
||
conn.execute(
|
||
"""
|
||
INSERT INTO fabric_stock_in
|
||
(model, quantity, unit, purchase_date, note, is_deleted)
|
||
VALUES (?, ?, ?, ?, ?, 0)
|
||
""",
|
||
(
|
||
model,
|
||
new_remaining,
|
||
unit,
|
||
datetime.now().strftime('%Y-%m-%d'),
|
||
"手动编辑库存"
|
||
)
|
||
)
|
||
|
||
conn.commit()
|
||
|
||
QMessageBox.information(
|
||
self,
|
||
"成功",
|
||
f"{model} 的剩余库存已更新为 {new_remaining:.3f} {unit}"
|
||
)
|
||
self.load_stock_table()
|
||
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "错误", f"保存失败: {str(e)}") |