diff --git a/database.py b/database.py index 05f95aa..962cdde 100644 --- a/database.py +++ b/database.py @@ -143,7 +143,17 @@ class DatabaseManager: conn.execute("ALTER TABLE fabric_consumption ADD COLUMN updated_at DATETIME DEFAULT CURRENT_TIMESTAMP") except: pass - + + # 添加逻辑删除字段 + try: + conn.execute("ALTER TABLE fabric_stock_in ADD COLUMN is_deleted INTEGER DEFAULT 0") + except: + pass + try: + conn.execute("ALTER TABLE fabric_consumption ADD COLUMN is_deleted INTEGER DEFAULT 0") + except: + pass + # 数据迁移:将fabrics表中category字段的"类目-类型"格式拆分成两个字段 try: cursor = conn.execute("SELECT model, category FROM fabrics WHERE category LIKE '%-%' AND (fabric_type IS NULL OR fabric_type = '')") diff --git a/fabric_library.db b/fabric_library.db index 7451bea..016e8ba 100644 Binary files a/fabric_library.db and b/fabric_library.db differ diff --git a/raw_material_dialog.py b/raw_material_dialog.py index 09f6ab7..d40ca94 100644 --- a/raw_material_dialog.py +++ b/raw_material_dialog.py @@ -44,8 +44,6 @@ class RawMaterialEditDialog(QDialog): # 基本信息 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) @@ -164,6 +162,13 @@ class RawMaterialEditDialog(QDialog): 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() @@ -179,24 +184,64 @@ class RawMaterialEditDialog(QDialog): 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 - )) + # 如果型号被修改了,检查新型号是否已存在 + 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() - QMessageBox.information(self, "成功", f"已更新 '{self.model}'") + 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: @@ -380,7 +425,13 @@ class RawMaterialLibraryDialog(QDialog): self.stock_table = QTableWidget() self.stock_table.setColumnCount(len(stock_headers)) self.stock_table.setHorizontalHeaderLabels(stock_headers) - self.stock_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + 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 @@ -664,8 +715,8 @@ class RawMaterialLibraryDialog(QDialog): 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 + 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 @@ -694,6 +745,10 @@ class RawMaterialLibraryDialog(QDialog): 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) @@ -707,14 +762,17 @@ class RawMaterialLibraryDialog(QDialog): """显示库存明细""" 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,)) + # 查询入库记录(只显示未删除的) + 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 = ? ORDER BY consume_date DESC + FROM fabric_consumption WHERE model = ? AND is_deleted = 0 ORDER BY consume_date DESC ''', (model,)) out_rows = cursor_out.fetchall() @@ -750,78 +808,152 @@ class RawMaterialLibraryDialog(QDialog): QMessageBox.critical(self, "错误", str(e)) def clear_remaining(self, model): - """清零剩余库存""" - reply = QMessageBox.question( + """清零剩余库存(逻辑删除)""" + reply = QMessageBox.warning( self, "确认清零", - f"确定将 {model} 的剩余量清零?" + f"确定将 {model} 的剩余量清零吗?\n\n" + "此操作将标记删除该原料的所有历史采购和消耗记录。", + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No ) 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, - ) + "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} 剩余量 {remaining:.3f} {unit} 已清零。" + f"{model} 的剩余库存已清零。" ) self.load_stock_table() except Exception as e: - QMessageBox.critical(self, "错误", f"清零失败: {str(e)}") \ No newline at end of file + 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)}") \ No newline at end of file