1223
This commit is contained in:
504
main.py
Normal file
504
main.py
Normal file
@@ -0,0 +1,504 @@
|
||||
"""
|
||||
服装布料计算管理器 - 专业版(重构版主程序)
|
||||
- 模块化设计,代码分离
|
||||
- 所有功能完整可用
|
||||
"""
|
||||
|
||||
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 DatabaseManager, 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.db_manager = DatabaseManager(self.db_path)
|
||||
|
||||
# 设置窗口
|
||||
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; }
|
||||
""")
|
||||
|
||||
self.init_ui()
|
||||
self.load_garment_list()
|
||||
|
||||
def get_conn(self):
|
||||
"""获取数据库连接"""
|
||||
return get_db_connection(self.db_path)
|
||||
|
||||
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()
|
||||
|
||||
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 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_())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user