""" 原料库管理模块 - 处理面料和原材料的管理 """ 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 != ''") 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): """保存修改""" 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 != ''") 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(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)}")