优化原料库存管理功能

- 库存跟踪表格设置为只读模式,防止误编辑
- 添加"编辑剩余库存"功能,支持直接修改库存数量
- 实现逻辑删除机制,删除操作不再物理删除数据
  - 在 fabric_stock_in 和 fabric_consumption 表添加 is_deleted 字段
  - 所有删除操作改为标记删除,保留历史数据
  - 查询时自动过滤已删除记录
- 原料编辑支持修改型号
  - 型号字段改为可编辑
  - 保存时检查型号重复并提示
  - 型号修改时级联更新所有关联表
- 优化操作列宽度,确保按钮文本完整显示
- 改进警告提示,明确说明操作影响

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-27 18:59:18 +08:00
parent 14e25ad03b
commit fa70f62099
3 changed files with 225 additions and 83 deletions

View File

@@ -144,6 +144,16 @@ class DatabaseManager:
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 = '')")

Binary file not shown.

View File

@@ -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,6 +184,42 @@ class RawMaterialEditDialog(QDialog):
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=?,
@@ -194,8 +235,12 @@ class RawMaterialEditDialog(QDialog):
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()
@@ -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)}")
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)}")