""" 服装布料计算管理器 - 专业版(最终完整修复版) - 修复所有 "name not defined" 错误 - 衣服库和原料库正常打开 - 所有功能完整可用 """ import sys import os from datetime import datetime from PyQt5.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QComboBox, QTextEdit, QMessageBox, QGroupBox, QDoubleSpinBox, QSpinBox, QDialog, QScrollArea ) from PyQt5.QtCore import Qt from PyQt5.QtGui import QFont from database import get_db_connection from login_dialog import LoginDialog from stock_dialog import StockInDialog from raw_material_dialog import RawMaterialLibraryDialog from garment_dialogs import GarmentLibraryDialog from purchase_order_dialog import PurchaseOrderDialog class FabricManager(QMainWindow): def __init__(self, is_admin=False): super().__init__() self.is_admin = is_admin exe_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(__file__) self.db_path = os.path.join(exe_dir, "fabric_library.db") self.init_db() mode_text = "(管理员模式)" if is_admin else "(普通模式)" self.setWindowTitle("服装布料计算管理器 - 专业版 " + mode_text) self.resize(1300, 800) self.setStyleSheet(""" QMainWindow { background-color: #f0fff8; } QGroupBox { font-weight: bold; color: #2e8b57; border: 2px solid #90ee90; border-radius: 10px; margin-top: 10px; padding-top: 8px; background-color: #ffffff; } QPushButton { background-color: #4caf50; color: white; padding: 10px; border-radius: 8px; font-size: 13px; font-weight: bold; } QTextEdit { background-color: #e8f5e9; border: 2px solid #a5d6a7; border-radius: 8px; padding: 10px; font-size: 13px; } QLineEdit, QDoubleSpinBox, QSpinBox, QComboBox { padding: 6px; border: 2px solid #a5d6a7; border-radius: 6px; font-size: 13px; } QLabel { font-size: 13px; color: #2e8b57; } """) # 提前绑定方法,避免 AttributeError self.quick_stock_in = self._quick_stock_in self.generate_purchase_order = self._generate_purchase_order self.record_current_consumption = self._record_current_consumption self.open_library = self._open_library self.open_garment_library = self._open_garment_library self.load_garment_list = self._load_garment_list self.load_garment_materials = self._load_garment_materials self.show_guide = self._show_guide self.convert_units = self._convert_units self.init_ui() self.load_garment_list() def _quick_stock_in(self): dialog = StockInDialog(self.db_path) dialog.exec_() def _generate_purchase_order(self): style_number = self.garment_combo.currentText().strip() if not style_number: QMessageBox.warning(self, "提示", "请先选择或计算一款号!") return quantity = self.quantity_input.value() loss_rate = self.loss_input.value() / 100 dialog = PurchaseOrderDialog(self.db_path, style_number, quantity, loss_rate) dialog.exec_() def _record_current_consumption(self): style_number = self.garment_combo.currentText().strip() if not style_number: QMessageBox.warning(self, "提示", "请先选择一款号并计算用量!") return quantity = self.quantity_input.value() loss_rate = self.loss_input.value() / 100 try: with self.get_conn() as conn: cursor = conn.execute(''' SELECT category, fabric_type, usage_per_piece, unit FROM garment_materials WHERE style_number = ? ''', (style_number,)) rows = cursor.fetchall() inserted = 0 for category, fabric_type, usage_per_piece, unit in rows: if usage_per_piece == 0: continue # 获取该原料在入库时使用的单位 stock_cursor = conn.execute(''' SELECT unit FROM fabric_stock_in WHERE model = ? ORDER BY purchase_date DESC LIMIT 1 ''', (category,)) stock_unit_row = stock_cursor.fetchone() # 如果有入库记录,使用入库单位;否则使用原来的单位 if stock_unit_row: stock_unit = stock_unit_row[0] # 如果单位不同,需要转换 if unit != stock_unit: consume_qty = self.convert_unit_value(usage_per_piece * quantity * (1 + loss_rate), unit, stock_unit, category) final_unit = stock_unit else: consume_qty = usage_per_piece * quantity * (1 + loss_rate) final_unit = unit else: # 没有入库记录,使用原单位 consume_qty = usage_per_piece * quantity * (1 + loss_rate) final_unit = unit conn.execute(''' INSERT INTO fabric_consumption (style_number, model, single_usage, quantity_made, loss_rate, consume_quantity, consume_date, unit) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ''', (style_number, category, usage_per_piece, quantity, loss_rate, consume_qty, datetime.now().strftime('%Y-%m-%d'), final_unit)) inserted += 1 conn.commit() if inserted > 0: QMessageBox.information(self, "成功", "本次生产消耗已记录到库存!") else: QMessageBox.information(self, "提示", "本次没有可记录的原料消耗") except Exception as e: QMessageBox.critical(self, "错误", str(e)) def _open_library(self): try: dialog = RawMaterialLibraryDialog(self.db_path, self.is_admin) dialog.exec_() except Exception as e: QMessageBox.critical(self, "错误", f"打开原料库失败: {str(e)}") def _open_garment_library(self): try: dialog = GarmentLibraryDialog(self.db_path) dialog.exec_() self.load_garment_list() except Exception as e: QMessageBox.critical(self, "错误", f"打开衣服库失败: {str(e)}") def _load_garment_list(self): current = self.garment_combo.currentText() self.garment_combo.blockSignals(True) self.garment_combo.clear() self.garment_combo.addItem("") try: with self.get_conn() as conn: cursor = conn.execute("SELECT style_number FROM garments ORDER BY style_number") for row in cursor.fetchall(): self.garment_combo.addItem(row[0]) except: pass self.garment_combo.blockSignals(False) if current: self.garment_combo.setCurrentText(current) def _load_garment_materials(self): style_number = self.garment_combo.currentText().strip() if not style_number: self.result_text.clear() return qty = self.quantity_input.value() loss = self.loss_input.value() / 100 text = f"款号: {style_number}\n生产件数: {qty}\n损耗率: {self.loss_input.value()}%\n\n" try: with self.get_conn() as conn: cursor = conn.execute("SELECT category, fabric_type, usage_per_piece, unit FROM garment_materials WHERE style_number = ? ORDER BY id", (style_number,)) for category, fabric_type, usage, unit in cursor.fetchall(): if usage: total = usage * qty * (1 + loss) # 显示材料名称(如果有类型则显示类目-类型,否则只显示类目) material_name = f"{category}-{fabric_type}" if fabric_type else category text += f"{material_name}\n单件: {usage:.3f} {unit}\n总用量: {total:.3f} {unit}\n\n" except Exception as e: text += "计算失败: " + str(e) self.result_text.setText(text) def _show_guide(self): guide_text = """ 【服装布料计算管理器 - 专业版 详细使用说明】 • 启动时弹出登录界面,默认密码均为 123456 • 管理员可修改密码 • 原料库支持大类/子类/供应商多条件筛选 • 胸杯自动归类到“辅料”,单位固定“一对” • 衣服编辑支持从原料库选择具体型号(可选),自动填充单位 • 支持材料行上移/下移/删除 • 主界面支持: - 批量计算用量(含损耗) - 生成采购单(复制或保存TXT) - “记录本次消耗到库存”:自动记录生产消耗 • 原料库“库存跟踪”Tab: - 显示每种原料的总采购、总消耗、当前剩余 - 查看明细(入库和消耗历史) - 一键清零剩余(盘点用完时使用) • 在“原料入库管理”或原料库可多次记录采购入库 祝使用愉快! """ dialog = QDialog(self) dialog.setWindowTitle("详细使用说明") dialog.resize(800, 700) layout = QVBoxLayout(dialog) text_area = QTextEdit() text_area.setReadOnly(True) text_area.setFont(QFont("Microsoft YaHei", 12)) text_area.setText(guide_text) scroll = QScrollArea() scroll.setWidgetResizable(True) scroll.setWidget(text_area) layout.addWidget(scroll) close_btn = QPushButton("关闭") close_btn.clicked.connect(dialog.accept) layout.addWidget(close_btn) dialog.exec_() def convert_unit_value(self, value, from_unit, to_unit, fabric_model=None): """单位转换函数:将数值从一个单位转换为另一个单位""" if from_unit == to_unit: return value # 米 <-> 码 转换 if from_unit == "米" and to_unit == "码": return value / 0.9144 elif from_unit == "码" and to_unit == "米": return value * 0.9144 # 长度单位转换为重量单位(需要面料的幅宽和克重信息) elif (from_unit in ["米", "码"] and to_unit == "公斤") or (from_unit == "公斤" and to_unit in ["米", "码"]): if fabric_model: try: with self.get_conn() as conn: cursor = conn.execute("SELECT width, gsm FROM fabrics WHERE model = ?", (fabric_model,)) fabric_info = cursor.fetchone() if fabric_info and fabric_info[0] and fabric_info[1]: width, gsm = fabric_info if width > 0 and gsm > 0: if from_unit == "米" and to_unit == "公斤": # 米转公斤:米数 * 幅宽(m) * 克重(kg/m²) return value * (width / 100) * (gsm / 1000) elif from_unit == "码" and to_unit == "公斤": # 码转公斤:先转米,再转公斤 meters = value * 0.9144 return meters * (width / 100) * (gsm / 1000) elif from_unit == "公斤" and to_unit == "米": # 公斤转米:公斤 / (幅宽(m) * 克重(kg/m²)) return value / ((width / 100) * (gsm / 1000)) elif from_unit == "公斤" and to_unit == "码": # 公斤转码:先转米,再转码 meters = value / ((width / 100) * (gsm / 1000)) return meters / 0.9144 except Exception: pass # 如果无法转换,返回原值 return value def _convert_units(self): sender = self.sender() try: if sender == self.calc_m: m = self.calc_m.value() self.calc_yard.blockSignals(True) self.calc_yard.setValue(m / 0.9144) self.calc_yard.blockSignals(False) weight = (m * self.calc_width.value() / 100 * self.calc_gsm.value()) / 1000 self.calc_kg.blockSignals(True) self.calc_kg.setValue(weight) self.calc_kg.blockSignals(False) elif sender == self.calc_yard: yard = self.calc_yard.value() m = yard * 0.9144 self.calc_m.blockSignals(True) self.calc_m.setValue(m) self.calc_m.blockSignals(False) weight = (m * self.calc_width.value() / 100 * self.calc_gsm.value()) / 1000 self.calc_kg.blockSignals(True) self.calc_kg.setValue(weight) self.calc_kg.blockSignals(False) elif sender == self.calc_kg: kg = self.calc_kg.value() if self.calc_width.value() > 0 and self.calc_gsm.value() > 0: m = (kg * 1000 * 100) / (self.calc_width.value() * self.calc_gsm.value()) self.calc_m.blockSignals(True) self.calc_m.setValue(m) self.calc_m.blockSignals(False) self.calc_yard.blockSignals(True) self.calc_yard.setValue(m / 0.9144) self.calc_yard.blockSignals(False) elif sender == self.calc_width or sender == self.calc_gsm: # 当幅宽或克重改变时,重新计算公斤数(基于当前米数) m = self.calc_m.value() if m > 0: weight = (m * self.calc_width.value() / 100 * self.calc_gsm.value()) / 1000 self.calc_kg.blockSignals(True) self.calc_kg.setValue(weight) self.calc_kg.blockSignals(False) except Exception: pass def get_conn(self): return get_db_connection(self.db_path) def init_db(self): try: with self.get_conn() as conn: conn.execute(''' CREATE TABLE IF NOT EXISTS fabrics ( model TEXT PRIMARY KEY, category TEXT DEFAULT '未分类', supplier TEXT, color TEXT, width REAL, gsm REAL, retail_price REAL, bulk_price REAL, unit TEXT DEFAULT '米', timestamp TEXT ) ''') conn.execute(''' CREATE TABLE IF NOT EXISTS garments ( style_number TEXT PRIMARY KEY, image_path TEXT ) ''') conn.execute(''' CREATE TABLE IF NOT EXISTS garment_materials ( id INTEGER PRIMARY KEY AUTOINCREMENT, style_number TEXT, category TEXT, fabric_type TEXT, usage_per_piece REAL, unit TEXT DEFAULT '米' ) ''') # 添加fabric_type列(如果不存在) try: conn.execute("ALTER TABLE garment_materials ADD COLUMN fabric_type TEXT") except: pass # 列已存在 conn.execute(''' CREATE TABLE IF NOT EXISTS admin_settings ( key TEXT PRIMARY KEY, value TEXT ) ''') conn.execute(''' CREATE TABLE IF NOT EXISTS fabric_stock_in ( id INTEGER PRIMARY KEY AUTOINCREMENT, model TEXT, quantity REAL, unit TEXT, purchase_date TEXT, note TEXT ) ''') conn.execute(''' CREATE TABLE IF NOT EXISTS fabric_consumption ( id INTEGER PRIMARY KEY AUTOINCREMENT, style_number TEXT, model TEXT, single_usage REAL, quantity_made INTEGER, loss_rate REAL, consume_quantity REAL, consume_date TEXT, unit TEXT ) ''') if not self.get_setting("admin_password"): conn.execute("INSERT INTO admin_settings (key, value) VALUES ('admin_password', ?)", ("123456",)) if not self.get_setting("user_password"): conn.execute("INSERT INTO admin_settings (key, value) VALUES ('user_password', ?)", ("123456",)) conn.commit() except Exception as e: QMessageBox.critical(self, "数据库错误", "无法初始化数据库:" + str(e)) def get_setting(self, key): try: with self.get_conn() as conn: cursor = conn.execute("SELECT value FROM admin_settings WHERE key = ?", (key,)) row = cursor.fetchone() return row[0] if row else None except: return None def init_ui(self): scroll = QScrollArea() scroll.setWidgetResizable(True) central_widget = QWidget() scroll.setWidget(central_widget) self.setCentralWidget(scroll) main_layout = QVBoxLayout(central_widget) title = QLabel("服装布料计算管理器 - 专业版") title.setFont(QFont("Microsoft YaHei", 18, QFont.Bold)) title.setAlignment(Qt.AlignCenter) title.setStyleSheet("color: #228b22; padding: 15px;") main_layout.addWidget(title) top_buttons = QHBoxLayout() guide_btn = QPushButton("📖 查看使用说明") guide_btn.clicked.connect(self.show_guide) top_buttons.addWidget(guide_btn) garment_btn = QPushButton("👔 衣服库管理") garment_btn.clicked.connect(self.open_garment_library) top_buttons.addWidget(garment_btn) library_btn = QPushButton("🗄️ 原料库管理") library_btn.clicked.connect(self.open_library) top_buttons.addWidget(library_btn) stock_in_btn = QPushButton("📦 快速原料入库") stock_in_btn.clicked.connect(self.quick_stock_in) top_buttons.addWidget(stock_in_btn) top_buttons.addStretch() main_layout.addLayout(top_buttons) content_layout = QHBoxLayout() left_widget = QWidget() left_layout = QVBoxLayout(left_widget) calc_group = QGroupBox("批量计算(按衣服款号)") calc_layout = QGridLayout() calc_layout.setVerticalSpacing(12) calc_layout.addWidget(QLabel("选择衣服款号:"), 0, 0, Qt.AlignRight) self.garment_combo = QComboBox() self.garment_combo.setEditable(True) self.garment_combo.currentIndexChanged.connect(self.load_garment_materials) calc_layout.addWidget(self.garment_combo, 0, 1, 1, 2) calc_layout.addWidget(QLabel("生产件数:"), 1, 0, Qt.AlignRight) self.quantity_input = QSpinBox() self.quantity_input.setRange(1, 1000000) self.quantity_input.setValue(1000) calc_layout.addWidget(self.quantity_input, 1, 1) calc_layout.addWidget(QLabel("损耗率 (%):"), 2, 0, Qt.AlignRight) self.loss_input = QDoubleSpinBox() self.loss_input.setRange(0, 50) self.loss_input.setValue(5) calc_layout.addWidget(self.loss_input, 2, 1) calc_btn = QPushButton("计算本次总用量") calc_btn.clicked.connect(self.load_garment_materials) calc_layout.addWidget(calc_btn, 3, 0, 1, 3) calc_group.setLayout(calc_layout) left_layout.addWidget(calc_group) result_group = QGroupBox("本次生产用量明细") result_layout = QVBoxLayout() self.result_text = QTextEdit() self.result_text.setReadOnly(True) self.result_text.setMinimumHeight(400) result_layout.addWidget(self.result_text) btn_layout = QHBoxLayout() po_btn = QPushButton("生成采购单") po_btn.clicked.connect(self.generate_purchase_order) po_btn.setStyleSheet("background-color: #ff9800; font-weight: bold; padding: 12px;") btn_layout.addWidget(po_btn) record_btn = QPushButton("记录本次消耗到库存") record_btn.clicked.connect(self.record_current_consumption) record_btn.setStyleSheet("background-color: #e91e63; color: white; font-weight: bold; padding: 12px;") btn_layout.addWidget(record_btn) result_layout.addLayout(btn_layout) result_group.setLayout(result_layout) left_layout.addWidget(result_group) content_layout.addWidget(left_widget, stretch=3) right_group = QGroupBox("单位换算计算器") right_layout = QGridLayout() right_layout.setVerticalSpacing(10) right_layout.addWidget(QLabel("米数:"), 0, 0, Qt.AlignRight) self.calc_m = QDoubleSpinBox() self.calc_m.setRange(0, 100000) self.calc_m.setDecimals(3) self.calc_m.valueChanged.connect(self.convert_units) right_layout.addWidget(self.calc_m, 0, 1) right_layout.addWidget(QLabel("码数:"), 1, 0, Qt.AlignRight) self.calc_yard = QDoubleSpinBox() self.calc_yard.setRange(0, 100000) self.calc_yard.setDecimals(3) self.calc_yard.valueChanged.connect(self.convert_units) right_layout.addWidget(self.calc_yard, 1, 1) right_layout.addWidget(QLabel("公斤:"), 2, 0, Qt.AlignRight) self.calc_kg = QDoubleSpinBox() self.calc_kg.setRange(0, 100000) self.calc_kg.setDecimals(6) self.calc_kg.valueChanged.connect(self.convert_units) right_layout.addWidget(self.calc_kg, 2, 1) right_layout.addWidget(QLabel("幅宽 (cm):"), 3, 0, Qt.AlignRight) self.calc_width = QDoubleSpinBox() self.calc_width.setRange(50, 300) self.calc_width.setValue(150) self.calc_width.valueChanged.connect(self.convert_units) right_layout.addWidget(self.calc_width, 3, 1) right_layout.addWidget(QLabel("克重 (g/m²):"), 4, 0, Qt.AlignRight) self.calc_gsm = QDoubleSpinBox() self.calc_gsm.setRange(50, 1000) self.calc_gsm.setValue(200) self.calc_gsm.valueChanged.connect(self.convert_units) right_layout.addWidget(self.calc_gsm, 4, 1) right_group.setLayout(right_layout) content_layout.addWidget(right_group, stretch=1) main_layout.addLayout(content_layout) main_layout.addStretch() if __name__ == "__main__": app = QApplication(sys.argv) exe_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(__file__) db_path = os.path.join(exe_dir, "fabric_library.db") login = LoginDialog(db_path) if login.exec_() == QDialog.Accepted: window = FabricManager(login.is_admin) window.show() sys.exit(app.exec_())