通用仓库管理系统开发书 Pyside6 + Sqlite3
通用仓库管理系统开发说明书(包含供应商和客户管理)
1. 项目概述
1.1 项目背景
随着企业规模的扩大和业务的复杂化,仓库管理变得越来越重要。为了提高仓库管理的效率和准确性,开发一个通用的仓库管理系统显得尤为重要。该系统将帮助企业实现库存的自动化管理,减少人为错误,提高工作效率。
1.2 项目目标
本项目旨在开发一个基于PySide6和SQLite3的通用仓库管理系统,主要功能包括:
- 商品信息管理
- 库存管理
- 入库和出库操作
- 供应商管理
- 客户管理
- 报表生成
- 用户权限管理
2. 技术选型
2.1 前端框架
- PySide6: PySide6是Qt for Python的官方库,提供了丰富的GUI组件和工具,适合开发跨平台的桌面应用程序。
2.2 数据库
- SQLite3: SQLite3是一个轻量级的嵌入式数据库,适合小型应用程序,无需单独的数据库服务器。
2.3 开发语言
- Python: Python是一种简单易学、功能强大的编程语言,适合快速开发。
3. 系统设计
3.1 数据库设计
3.1.1 数据库表结构
- 商品表 (Products)
-
id
(INTEGER, PRIMARY KEY, AUTOINCREMENT)name
(TEXT, NOT NULL)description
(TEXT)price
(REAL, NOT NULL)quantity
(INTEGER, NOT NULL)
- 供应商表 (Suppliers)
-
id
(INTEGER, PRIMARY KEY, AUTOINCREMENT)name
(TEXT, NOT NULL)contact
(TEXT)phone
(TEXT)address
(TEXT)
- 客户表 (Customers)
-
id
(INTEGER, PRIMARY KEY, AUTOINCREMENT)name
(TEXT, NOT NULL)contact
(TEXT)phone
(TEXT)address
(TEXT)
- 入库表 (StockIn)
-
id
(INTEGER, PRIMARY KEY, AUTOINCREMENT)product_id
(INTEGER, FOREIGN KEY REFERENCES Products(id))supplier_id
(INTEGER, FOREIGN KEY REFERENCES Suppliers(id))quantity
(INTEGER, NOT NULL)date
(TEXT, NOT NULL)
- 出库表 (StockOut)
-
id
(INTEGER, PRIMARY KEY, AUTOINCREMENT)product_id
(INTEGER, FOREIGN KEY REFERENCES Products(id))customer_id
(INTEGER, FOREIGN KEY REFERENCES Customers(id))quantity
(INTEGER, NOT NULL)date
(TEXT, NOT NULL)
- 用户表 (Users)
-
id
(INTEGER, PRIMARY KEY, AUTOINCREMENT)username
(TEXT, NOT NULL, UNIQUE)password
(TEXT, NOT NULL)role
(TEXT, NOT NULL)
3.2 功能模块设计
3.2.1 商品管理模块
- 添加商品
- 编辑商品信息
- 删除商品
- 查询商品
3.2.2 库存管理模块
- 查看库存
- 库存预警
- 库存调整
3.2.3 入库管理模块
- 添加入库记录
- 查看入库历史
3.2.4 出库管理模块
- 添加出库记录
- 查看出库历史
3.2.5 供应商管理模块
- 添加供应商
- 编辑供应商信息
- 删除供应商
- 查询供应商
3.2.6 客户管理模块
- 添加客户
- 编辑客户信息
- 删除客户
- 查询客户
3.2.7 报表生成模块
- 生成库存报表
- 生成出入库报表
3.2.8 用户管理模块
- 用户登录
- 用户注册
- 权限管理
4. 系统实现
4.1 环境搭建
4.1.1 安装依赖
pip install PySide6
4.1.2 创建数据库
import sqlite3
def create_database():
conn = sqlite3.connect('warehouse.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS Products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
price REAL NOT NULL,
quantity INTEGER NOT NULL
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS Suppliers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
contact TEXT,
phone TEXT,
address TEXT
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS Customers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
contact TEXT,
phone TEXT,
address TEXT
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS StockIn (
id INTEGER PRIMARY KEY AUTOINCREMENT,
product_id INTEGER NOT NULL,
supplier_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
date TEXT NOT NULL,
FOREIGN KEY (product_id) REFERENCES Products(id),
FOREIGN KEY (supplier_id) REFERENCES Suppliers(id)
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS StockOut (
id INTEGER PRIMARY KEY AUTOINCREMENT,
product_id INTEGER NOT NULL,
customer_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
date TEXT NOT NULL,
FOREIGN KEY (product_id) REFERENCES Products(id),
FOREIGN KEY (customer_id) REFERENCES Customers(id)
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS Users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
role TEXT NOT NULL
)
''')
conn.commit()
conn.close()
create_database()
4.2 前端界面设计
4.2.1 主界面
- 使用PySide6的
QMainWindow
作为主窗口,包含菜单栏、工具栏和状态栏。 - 主界面包含多个选项卡,分别对应不同的功能模块。
4.2.2 商品管理界面
- 使用
QTableWidget
显示商品列表。 - 提供添加、编辑、删除按钮。
4.2.3 库存管理界面
- 使用
QTableWidget
显示库存信息。 - 提供库存预警功能。
4.2.4 入库管理界面
- 提供表单用于添加入库记录。
- 使用
QTableWidget
显示入库历史。
4.2.5 出库管理界面
- 提供表单用于添加出库记录。
- 使用
QTableWidget
显示出库历史。
4.2.6 供应商管理界面
- 使用
QTableWidget
显示供应商列表。 - 提供添加、编辑、删除按钮。
4.2.7 客户管理界面
- 使用
QTableWidget
显示客户列表。 - 提供添加、编辑、删除按钮。
4.2.8 报表生成界面
- 提供按钮生成库存报表和出入库报表。
- 使用
QTextEdit
显示报表内容。
4.2.9 用户管理界面
- 提供登录和注册表单。
- 使用
QTableWidget
显示用户列表。
4.3 后端逻辑实现
4.3.1 商品管理逻辑
- 添加商品:将商品信息插入到
Products
表中。 - 编辑商品:更新
Products
表中的记录。 - 删除商品:从
Products
表中删除记录。 - 查询商品:根据条件查询
Products
表中的记录。
4.3.2 库存管理逻辑
- 查看库存:查询
Products
表中的库存信息。 - 库存预警:检查库存数量是否低于预设阈值。
- 库存调整:更新
Products
表中的库存数量。
4.3.3 入库管理逻辑
- 添加入库记录:将入库信息插入到
StockIn
表中,并更新Products
表中的库存数量。 - 查看入库历史:查询
StockIn
表中的记录。
4.3.4 出库管理逻辑
- 添加出库记录:将出库信息插入到
StockOut
表中,并更新Products
表中的库存数量。 - 查看出库历史:查询
StockOut
表中的记录。
4.3.5 供应商管理逻辑
- 添加供应商:将供应商信息插入到
Suppliers
表中。 - 编辑供应商:更新
Suppliers
表中的记录。 - 删除供应商:从
Suppliers
表中删除记录。 - 查询供应商:根据条件查询
Suppliers
表中的记录。
4.3.6 客户管理逻辑
- 添加客户:将客户信息插入到
Customers
表中。 - 编辑客户:更新
Customers
表中的记录。 - 删除客户:从
Customers
表中删除记录。 - 查询客户:根据条件查询
Customers
表中的记录。
4.3.7 报表生成逻辑
- 生成库存报表:查询
Products
表中的库存信息并生成报表。 - 生成出入库报表:查询
StockIn
和StockOut
表中的记录并生成报表。
4.3.8 用户管理逻辑
- 用户登录:验证用户名和密码。
- 用户注册:将用户信息插入到
Users
表中。 - 权限管理:根据用户角色控制功能访问权限。
5. 系统测试
5.1 单元测试
- 对每个功能模块进行单元测试,确保每个功能都能正常工作。
5.2 集成测试
- 对整个系统进行集成测试,确保各个模块之间的协作没有问题。
5.3 用户验收测试
- 邀请最终用户进行验收测试,确保系统满足用户需求。
6. 部署与维护
6.1 部署
- 将系统打包为可执行文件,方便用户安装和使用。
6.2 维护
- 定期备份数据库,防止数据丢失。
- 根据用户反馈进行系统优化和功能更新。
7. 总结
本项目通过使用PySide6和SQLite3,成功开发了一个通用的仓库管理系统。该系统功能完善,操作简便,能够有效提高仓库管理的效率和准确性。未来可以根据实际需求进一步扩展系统功能,如增加多仓库管理、支持更多报表类型等。
以下是 通用仓库管理系统 的开发文件目录结构,基于 PySide6 和 SQLite3 实现。该目录结构清晰划分了前端、后端、数据库、资源文件等模块,便于开发和维护。
开发文件目录结构
warehouse_management_system/
├── main.py # 程序入口文件
├── requirements.txt # 项目依赖文件
├── README.md # 项目说明文档
├── database/ # 数据库相关文件
│ ├── db.py # 数据库连接与初始化
│ ├── models.py # 数据库表结构定义
│ └── queries.py # 数据库查询语句封装
├── ui/ # 前端界面文件
│ ├── main_window.py # 主窗口界面
│ ├── product_ui.py # 商品管理界面
│ ├── stock_ui.py # 库存管理界面
│ ├── supplier_ui.py # 供应商管理界面
│ ├── customer_ui.py # 客户管理界面
│ ├── report_ui.py # 报表生成界面
│ └── user_ui.py # 用户管理界面
├── logic/ # 后端业务逻辑
│ ├── product_logic.py # 商品管理逻辑
│ ├── stock_logic.py # 库存管理逻辑
│ ├── supplier_logic.py # 供应商管理逻辑
│ ├── customer_logic.py # 客户管理逻辑
│ ├── report_logic.py # 报表生成逻辑
│ └── user_logic.py # 用户管理逻辑
├── resources/ # 资源文件
│ ├── icons/ # 图标资源
│ └── styles/ # 样式表文件
├── tests/ # 测试文件
│ ├── test_product.py # 商品管理测试
│ ├── test_stock.py # 库存管理测试
│ ├── test_supplier.py # 供应商管理测试
│ ├── test_customer.py # 客户管理测试
│ ├── test_report.py # 报表生成测试
│ └── test_user.py # 用户管理测试
└── utils/ # 工具类
├── logger.py # 日志工具
└── helpers.py # 通用工具函数
目录说明
main.py
-
- 程序入口文件,负责启动应用程序并加载主窗口。
requirements.txt
-
- 项目依赖文件,列出所有需要安装的 Python 库,例如:
PySide6==6.5.0
sqlite3
database/
-
db.py
: 数据库连接与初始化,包括创建数据库和表的逻辑。models.py
: 定义数据库表结构(如商品、供应商、客户等)。queries.py
: 封装常用的 SQL 查询语句,便于复用。
ui/
-
- 存放所有前端界面文件,每个界面文件对应一个功能模块。
- 使用 PySide6 的
QMainWindow
、QDialog
、QWidget
等组件构建界面。
logic/
-
- 存放后端业务逻辑文件,每个文件对应一个功能模块的逻辑处理。
- 例如:商品管理逻辑、库存管理逻辑、供应商管理逻辑等。
resources/
-
- 存放项目所需的资源文件,如图标、样式表等。
tests/
-
- 存放单元测试文件,用于测试各个功能模块的正确性。
utils/
-
- 存放工具类文件,如日志工具、通用工具函数等。
示例代码片段
main.py
import sys
from PySide6.QtWidgets import QApplication
from ui.main_window import MainWindow
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
database/db.py
import sqlite3
def create_database():
conn = sqlite3.connect('warehouse.db')
cursor = conn.cursor()
# 创建商品表
cursor.execute('''
CREATE TABLE IF NOT EXISTS Products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
price REAL NOT NULL,
quantity INTEGER NOT NULL
)
''')
# 创建供应商表
cursor.execute('''
CREATE TABLE IF NOT EXISTS Suppliers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
contact TEXT,
phone TEXT,
address TEXT
)
''')
conn.commit()
conn.close()
if __name__ == "__main__":
create_database()
ui/main_window.py
from PySide6.QtWidgets import QMainWindow, QTabWidget
from ui.product_ui import ProductUI
from ui.stock_ui import StockUI
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("仓库管理系统")
self.setGeometry(100, 100, 800, 600)
# 创建选项卡
self.tabs = QTabWidget()
self.setCentralWidget(self.tabs)
# 添加商品管理界面
self.product_ui = ProductUI()
self.tabs.addTab(self.product_ui, "商品管理")
# 添加库存管理界面
self.stock_ui = StockUI()
self.tabs.addTab(self.stock_ui, "库存管理")
总结
以上目录结构清晰划分了项目的各个模块,便于团队协作开发和维护。通过模块化的设计,可以快速扩展功能(如添加新的管理模块)或修复问题。
为了支持通过 CSS 文档 来动态调整 UI 样式,我们可以将 PySide6 的界面设计与 CSS 样式分离。PySide6 支持使用 QSS(Qt Style Sheets),这是一种类似于 CSS 的样式表语言,可以通过加载外部 CSS 文件或直接在代码中设置样式来实现 UI 样式的动态调整。
以下是实现方案和目录结构调整:
调整后的目录结构
warehouse_management_system/
├── main.py # 程序入口文件
├── requirements.txt # 项目依赖文件
├── README.md # 项目说明文档
├── database/ # 数据库相关文件
│ ├── db.py # 数据库连接与初始化
│ ├── models.py # 数据库表结构定义
│ └── queries.py # 数据库查询语句封装
├── ui/ # 前端界面文件
│ ├── main_window.py # 主窗口界面
│ ├── product_ui.py # 商品管理界面
│ ├── stock_ui.py # 库存管理界面
│ ├── supplier_ui.py # 供应商管理界面
│ ├── customer_ui.py # 客户管理界面
│ ├── report_ui.py # 报表生成界面
│ └── user_ui.py # 用户管理界面
├── logic/ # 后端业务逻辑
│ ├── product_logic.py # 商品管理逻辑
│ ├── stock_logic.py # 库存管理逻辑
│ ├── supplier_logic.py # 供应商管理逻辑
│ ├── customer_logic.py # 客户管理逻辑
│ ├── report_logic.py # 报表生成逻辑
│ └── user_logic.py # 用户管理逻辑
├── resources/ # 资源文件
│ ├── icons/ # 图标资源
│ └── styles/ # 样式表文件
│ ├── main_style.qss # 主窗口样式表
│ ├── product_style.qss # 商品管理界面样式表
│ └── button_style.qss # 按钮通用样式表
├── tests/ # 测试文件
│ ├── test_product.py # 商品管理测试
│ ├── test_stock.py # 库存管理测试
│ ├── test_supplier.py # 供应商管理测试
│ ├── test_customer.py # 客户管理测试
│ ├── test_report.py # 报表生成测试
│ └── test_user.py # 用户管理测试
└── utils/ # 工具类
├── logger.py # 日志工具
├── helpers.py # 通用工具函数
└── style_loader.py # 样式加载工具
实现步骤
1. 创建样式表文件
在 resources/styles/
目录下创建 .qss
文件,例如:
main_style.qss
/* 主窗口样式 */
QMainWindow {
background-color: #f0f0f0;
font-family: "Arial";
font-size: 14px;
}
/* 选项卡样式 */
QTabWidget::pane {
border: 1px solid #ccc;
background-color: #fff;
}
QTabBar::tab {
padding: 10px;
background-color: #e0e0e0;
border: 1px solid #ccc;
}
QTabBar::tab:selected {
background-color: #fff;
border-bottom-color: #fff;
}
button_style.qss
/* 按钮通用样式 */
QPushButton {
background-color: #4CAF50;
color: white;
border: none;
padding: 10px;
border-radius: 5px;
}
QPushButton:hover {
background-color: #45a049;
}
QPushButton:pressed {
background-color: #3d8b40;
}
2. 加载样式表
在 utils/style_loader.py
中编写样式加载工具:
from PySide6.QtWidgets import QApplication
def load_style(file_path):
"""加载指定的 QSS 样式表文件"""
try:
with open(file_path, "r", encoding="utf-8") as f:
style = f.read()
QApplication.instance().setStyleSheet(style)
except Exception as e:
print(f"加载样式表失败: {e}")
3. 在界面中应用样式
在 main.py
中加载全局样式:
import sys
from PySide6.QtWidgets import QApplication
from ui.main_window import MainWindow
from utils.style_loader import load_style
if __name__ == "__main__":
app = QApplication(sys.argv)
# 加载全局样式
load_style("resources/styles/main_style.qss")
window = MainWindow()
window.show()
sys.exit(app.exec())
在具体的界面文件中,可以加载局部样式。例如,在 ui/product_ui.py
中:
from PySide6.QtWidgets import QWidget, QVBoxLayout, QPushButton
from utils.style_loader import load_style
class ProductUI(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
self.load_styles()
def init_ui(self):
self.layout = QVBoxLayout()
self.button = QPushButton("添加商品")
self.layout.addWidget(self.button)
self.setLayout(self.layout)
def load_styles(self):
# 加载按钮样式
load_style("resources/styles/button_style.qss")
4. 动态切换样式
可以通过配置文件或用户设置动态切换样式。例如:
def change_theme(theme_name):
if theme_name == "light":
load_style("resources/styles/light_theme.qss")
elif theme_name == "dark":
load_style("resources/styles/dark_theme.qss")
5. 示例样式表
dark_theme.qss
/* 深色主题 */
QMainWindow {
background-color: #333;
color: #fff;
}
QPushButton {
background-color: #555;
color: #fff;
border: 1px solid #777;
}
QPushButton:hover {
background-color: #666;
}
QPushButton:pressed {
background-color: #444;
}
总结
通过将样式表与界面逻辑分离,可以实现以下优势:
- 样式与逻辑分离:便于维护和扩展。
- 动态切换主题:通过加载不同的
.qss
文件,实现主题切换。 - 统一管理样式:将所有样式集中存放在
resources/styles/
目录下,便于管理。
这种方法非常适合需要频繁调整 UI 样式的项目,同时也能提高开发效率。
为了支持将打印功能改为基于 HTML 模板,并且允许用户通过修改外部 HTML 文件来自定义打印内容,我们可以将 HTML 模板文件与代码分离。通过加载外部 HTML 文件并动态填充数据,可以实现灵活的打印功能。
以下是实现方案和目录结构调整:
调整后的目录结构
warehouse_management_system/
├── main.py # 程序入口文件
├── requirements.txt # 项目依赖文件
├── README.md # 项目说明文档
├── database/ # 数据库相关文件
│ ├── db.py # 数据库连接与初始化
│ ├── models.py # 数据库表结构定义
│ └── queries.py # 数据库查询语句封装
├── ui/ # 前端界面文件
│ ├── main_window.py # 主窗口界面
│ ├── product_ui.py # 商品管理界面
│ ├── stock_ui.py # 库存管理界面
│ ├── supplier_ui.py # 供应商管理界面
│ ├── customer_ui.py # 客户管理界面
│ ├── report_ui.py # 报表生成界面
│ └── user_ui.py # 用户管理界面
├── logic/ # 后端业务逻辑
│ ├── product_logic.py # 商品管理逻辑
│ ├── stock_logic.py # 库存管理逻辑
│ ├── supplier_logic.py # 供应商管理逻辑
│ ├── customer_logic.py # 客户管理逻辑
│ ├── report_logic.py # 报表生成逻辑
│ └── user_logic.py # 用户管理逻辑
├── resources/ # 资源文件
│ ├── icons/ # 图标资源
│ ├── styles/ # 样式表文件
│ └── templates/ # HTML 模板文件
│ ├── product_report.html # 商品报表模板
│ ├── stock_report.html # 库存报表模板
│ └── invoice_template.html# 发票模板
├── tests/ # 测试文件
│ ├── test_product.py # 商品管理测试
│ ├── test_stock.py # 库存管理测试
│ ├── test_supplier.py # 供应商管理测试
│ ├── test_customer.py # 客户管理测试
│ ├── test_report.py # 报表生成测试
│ └── test_user.py # 用户管理测试
└── utils/ # 工具类
├── logger.py # 日志工具
├── helpers.py # 通用工具函数
├── style_loader.py # 样式加载工具
└── template_loader.py # HTML 模板加载工具
实现步骤
1. 创建 HTML 模板文件
在 resources/templates/
目录下创建 HTML 模板文件,例如:
product_report.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>商品报表</title>
<style>
body {
font-family: Arial, sans-serif;
}
h1 {
text-align: center;
color: #333;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
</style>
</head>
<body>
<h1>商品报表</h1>
<table>
<thead>
<tr>
<th>商品名称</th>
<th>描述</th>
<th>价格</th>
<th>库存数量</th>
</tr>
</thead>
<tbody>
{{ products }}
</tbody>
</table>
</body>
</html>
2. 编写模板加载工具
在 utils/template_loader.py
中编写 HTML 模板加载工具:
def load_template(file_path, data):
"""
加载 HTML 模板并填充数据
:param file_path: 模板文件路径
:param data: 填充的数据(字典格式)
:return: 渲染后的 HTML 内容
"""
try:
with open(file_path, "r", encoding="utf-8") as f:
template = f.read()
for key, value in data.items():
template = template.replace("{{ " + key + " }}", value)
return template
except Exception as e:
print(f"加载模板失败: {e}")
return None
3. 在报表生成逻辑中使用模板
在 logic/report_logic.py
中编写报表生成逻辑:
from utils.template_loader import load_template
from database.queries import get_all_products
def generate_product_report():
"""
生成商品报表
:return: 渲染后的 HTML 内容
"""
# 从数据库获取商品数据
products = get_all_products()
# 将数据格式化为 HTML 表格行
product_rows = ""
for product in products:
product_rows += f"""
<tr>
<td>{product['name']}</td>
<td>{product['description']}</td>
<td>{product['price']}</td>
<td>{product['quantity']}</td>
</tr>
"""
# 加载模板并填充数据
template_data = {
"products": product_rows
}
html_content = load_template("resources/templates/product_report.html", template_data)
return html_content
4. 打印 HTML 内容
在 ui/report_ui.py
中实现打印功能:
from PySide6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QTextEdit, QPrintDialog, QPrinter
from PySide6.QtPrintSupport import QPrintDialog
from PySide6.QtWebEngineWidgets import QWebEngineView
from logic.report_logic import generate_product_report
class ReportUI(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
self.layout = QVBoxLayout()
# 生成报表按钮
self.generate_button = QPushButton("生成商品报表")
self.generate_button.clicked.connect(self.generate_report)
self.layout.addWidget(self.generate_button)
# 显示 HTML 内容
self.web_view = QWebEngineView()
self.layout.addWidget(self.web_view)
# 打印按钮
self.print_button = QPushButton("打印报表")
self.print_button.clicked.connect(self.print_report)
self.layout.addWidget(self.print_button)
self.setLayout(self.layout)
def generate_report(self):
"""生成并显示商品报表"""
html_content = generate_product_report()
if html_content:
self.web_view.setHtml(html_content)
def print_report(self):
"""打印报表"""
printer = QPrinter()
dialog = QPrintDialog(printer, self)
if dialog.exec() == QPrintDialog.Accepted:
self.web_view.page().print(printer, lambda success: print("打印成功" if success else "打印失败"))
5. 动态修改模板
用户可以直接修改 resources/templates/
目录下的 HTML 文件,例如调整样式或添加新的字段。系统会在每次生成报表时加载最新的模板。
总结
通过将打印功能改为基于 HTML 模板,并允许用户通过修改外部 HTML 文件来自定义打印内容,可以实现以下优势:
- 灵活定制:用户可以直接修改 HTML 文件,无需修改代码。
- 样式与数据分离:HTML 模板负责样式,Python 代码负责数据填充。
- 支持复杂布局:HTML 和 CSS 可以轻松实现复杂的报表布局。
这种方法非常适合需要频繁调整打印格式的项目,同时也能提高开发效率。
为了支持 添加入库记录 时能够记录 多个明细商品,我们需要对数据库设计和表单逻辑进行调整。以下是详细的实现方案:
1. 数据库设计调整
1.1 数据库表结构
入库主表 (StockIn)
id
(INTEGER, PRIMARY KEY, AUTOINCREMENT): 入库记录的唯一标识。supplier_id
(INTEGER, FOREIGN KEY REFERENCES Suppliers(id)): 供应商 ID。date
(TEXT, NOT NULL): 入库日期。total_quantity
(INTEGER, NOT NULL): 总入库数量。total_amount
(REAL, NOT NULL): 总入库金额。
入库明细表 (StockInDetails)
id
(INTEGER, PRIMARY KEY, AUTOINCREMENT): 明细记录的唯一标识。stock_in_id
(INTEGER, FOREIGN KEY REFERENCES StockIn(id)): 关联的入库记录 ID。product_id
(INTEGER, FOREIGN KEY REFERENCES Products(id)): 商品 ID。quantity
(INTEGER, NOT NULL): 入库数量。price
(REAL, NOT NULL): 商品单价。amount
(REAL, NOT NULL): 明细金额(quantity * price)。
1.2 数据库初始化脚本
在 database/db.py
中更新数据库初始化逻辑:
def create_database():
conn = sqlite3.connect('warehouse.db')
cursor = conn.cursor()
# 创建入库主表
cursor.execute('''
CREATE TABLE IF NOT EXISTS StockIn (
id INTEGER PRIMARY KEY AUTOINCREMENT,
supplier_id INTEGER NOT NULL,
date TEXT NOT NULL,
total_quantity INTEGER NOT NULL,
total_amount REAL NOT NULL,
FOREIGN KEY (supplier_id) REFERENCES Suppliers(id)
)
''')
# 创建入库明细表
cursor.execute('''
CREATE TABLE IF NOT EXISTS StockInDetails (
id INTEGER PRIMARY KEY AUTOINCREMENT,
stock_in_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
price REAL NOT NULL,
amount REAL NOT NULL,
FOREIGN KEY (stock_in_id) REFERENCES StockIn(id),
FOREIGN KEY (product_id) REFERENCES Products(id)
)
''')
conn.commit()
conn.close()
2. 表单设计
2.1 添加入库记录表单
在 ui/stock_ui.py
中设计一个表单,支持添加多个明细商品:
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QTableWidget, QTableWidgetItem, QLineEdit, QDateEdit, QComboBox
from PySide6.QtCore import QDate
from database.queries import get_all_suppliers, get_all_products
class StockInUI(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
self.details = [] # 存储明细商品
def init_ui(self):
self.layout = QVBoxLayout()
# 供应商选择
self.supplier_combo = QComboBox()
self.supplier_combo.addItems([s['name'] for s in get_all_suppliers()])
self.layout.addWidget(self.supplier_combo)
# 入库日期
self.date_edit = QDateEdit(QDate.currentDate())
self.layout.addWidget(self.date_edit)
# 明细商品表格
self.details_table = QTableWidget()
self.details_table.setColumnCount(4)
self.details_table.setHorizontalHeaderLabels(["商品", "数量", "单价", "金额"])
self.layout.addWidget(self.details_table)
# 添加明细按钮
self.add_detail_button = QPushButton("添加明细")
self.add_detail_button.clicked.connect(self.add_detail)
self.layout.addWidget(self.add_detail_button)
# 提交按钮
self.submit_button = QPushButton("提交入库")
self.submit_button.clicked.connect(self.submit_stock_in)
self.layout.addWidget(self.submit_button)
self.setLayout(self.layout)
def add_detail(self):
"""添加明细商品"""
# 弹出对话框选择商品和输入数量、单价
dialog = QDialog(self)
dialog.setWindowTitle("添加明细")
layout = QVBoxLayout()
# 商品选择
product_combo = QComboBox()
product_combo.addItems([p['name'] for p in get_all_products()])
layout.addWidget(product_combo)
# 数量输入
quantity_edit = QLineEdit()
quantity_edit.setPlaceholderText("数量")
layout.addWidget(quantity_edit)
# 单价输入
price_edit = QLineEdit()
price_edit.setPlaceholderText("单价")
layout.addWidget(price_edit)
# 确认按钮
confirm_button = QPushButton("确认")
confirm_button.clicked.connect(lambda: self.confirm_detail(
dialog, product_combo.currentText(), quantity_edit.text(), price_edit.text()
))
layout.addWidget(confirm_button)
dialog.setLayout(layout)
dialog.exec()
def confirm_detail(self, dialog, product_name, quantity, price):
"""确认添加明细"""
try:
quantity = int(quantity)
price = float(price)
amount = quantity * price
# 添加到明细列表
self.details.append({
"product_name": product_name,
"quantity": quantity,
"price": price,
"amount": amount
})
# 更新表格
row = self.details_table.rowCount()
self.details_table.insertRow(row)
self.details_table.setItem(row, 0, QTableWidgetItem(product_name))
self.details_table.setItem(row, 1, QTableWidgetItem(str(quantity)))
self.details_table.setItem(row, 2, QTableWidgetItem(str(price)))
self.details_table.setItem(row, 3, QTableWidgetItem(str(amount)))
dialog.close()
except ValueError:
print("请输入有效的数量和单价")
def submit_stock_in(self):
"""提交入库记录"""
if not self.details:
print("请添加至少一个明细商品")
return
# 计算总数量和总金额
total_quantity = sum(d['quantity'] for d in self.details)
total_amount = sum(d['amount'] for d in self.details)
# 获取供应商 ID
supplier_name = self.supplier_combo.currentText()
supplier_id = next(s['id'] for s in get_all_suppliers() if s['name'] == supplier_name)
# 插入入库主表
from database.queries import insert_stock_in
stock_in_id = insert_stock_in(supplier_id, self.date_edit.date().toString("yyyy-MM-dd"), total_quantity, total_amount)
# 插入入库明细表
from database.queries import insert_stock_in_detail
for detail in self.details:
product_id = next(p['id'] for p in get_all_products() if p['name'] == detail['product_name'])
insert_stock_in_detail(stock_in_id, product_id, detail['quantity'], detail['price'], detail['amount'])
print("入库记录提交成功")
self.details.clear()
self.details_table.setRowCount(0)
3. 数据库操作
在 database/queries.py
中编写数据库操作函数:
def insert_stock_in(supplier_id, date, total_quantity, total_amount):
"""插入入库主表记录"""
conn = sqlite3.connect('warehouse.db')
cursor = conn.cursor()
cursor.execute('''
INSERT INTO StockIn (supplier_id, date, total_quantity, total_amount)
VALUES (?, ?, ?, ?)
''', (supplier_id, date, total_quantity, total_amount))
conn.commit()
stock_in_id = cursor.lastrowid
conn.close()
return stock_in_id
def insert_stock_in_detail(stock_in_id, product_id, quantity, price, amount):
"""插入入库明细表记录"""
conn = sqlite3.connect('warehouse.db')
cursor = conn.cursor()
cursor.execute('''
INSERT INTO StockInDetails (stock_in_id, product_id, quantity, price, amount)
VALUES (?, ?, ?, ?, ?)
''', (stock_in_id, product_id, quantity, price, amount))
conn.commit()
conn.close()
4. 总结
通过以上设计,系统可以支持:
- 多个明细商品:每次入库可以添加多个商品。
- 数据一致性:入库主表和明细表通过外键关联,确保数据完整性。
- 灵活扩展:可以轻松扩展功能,如支持修改明细、删除明细等。
这种方法非常适合需要记录复杂入库场景的仓库管理系统。
为了支持 Excel 导出和导入 功能,我们可以使用 Python 的 openpyxl
库来处理 Excel 文件。以下是实现方案,涵盖 商品管理、供应商管理、客户管理 和 入库记录 等模块的 Excel 导出和导入功能。
1. 安装依赖
首先,安装 openpyxl
库:
pip install openpyxl
2. 导出功能实现
2.1 导出商品数据到 Excel
在 utils/excel_utils.py
中编写导出工具函数:
from openpyxl import Workbook
def export_to_excel(data, headers, file_path):
"""
将数据导出到 Excel 文件
:param data: 数据列表(每行是一个字典)
:param headers: 表头列表(例如 ["ID", "名称", "描述", "价格", "库存"])
:param file_path: 导出的文件路径(例如 "products.xlsx")
"""
wb = Workbook()
ws = wb.active
# 写入表头
ws.append(headers)
# 写入数据
for row in data:
ws.append([row.get(header.lower(), "") for header in headers])
# 保存文件
wb.save(file_path)
print(f"数据已导出到 {file_path}")
在 logic/product_logic.py
中调用导出函数:
from utils.excel_utils import export_to_excel
from database.queries import get_all_products
def export_products_to_excel(file_path):
"""导出商品数据到 Excel"""
products = get_all_products()
headers = ["ID", "名称", "描述", "价格", "库存"]
export_to_excel(products, headers, file_path)
2.2 在界面中添加导出按钮
在 ui/product_ui.py
中添加导出按钮:
from PySide6.QtWidgets import QFileDialog
from logic.product_logic import export_products_to_excel
class ProductUI(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
self.layout = QVBoxLayout()
# 导出按钮
self.export_button = QPushButton("导出到 Excel")
self.export_button.clicked.connect(self.export_products)
self.layout.addWidget(self.export_button)
self.setLayout(self.layout)
def export_products(self):
"""导出商品数据"""
file_path, _ = QFileDialog.getSaveFileName(self, "保存文件", "", "Excel 文件 (*.xlsx)")
if file_path:
export_products_to_excel(file_path)
3. 导入功能实现
3.1 从 Excel 导入商品数据
在 utils/excel_utils.py
中编写导入工具函数:
from openpyxl import load_workbook
def import_from_excel(file_path):
"""
从 Excel 文件导入数据
:param file_path: Excel 文件路径
:return: 数据列表(每行是一个字典)
"""
wb = load_workbook(file_path)
ws = wb.active
# 读取表头
headers = [cell.value for cell in ws[1]]
# 读取数据
data = []
for row in ws.iter_rows(min_row=2, values_only=True):
row_data = {headers[i].lower(): row[i] for i in range(len(headers))}
data.append(row_data)
return data
在 logic/product_logic.py
中调用导入函数:
from utils.excel_utils import import_from_excel
from database.queries import insert_product
def import_products_from_excel(file_path):
"""从 Excel 导入商品数据"""
data = import_from_excel(file_path)
for item in data:
insert_product(
name=item.get("名称", ""),
description=item.get("描述", ""),
price=item.get("价格", 0),
quantity=item.get("库存", 0)
)
print(f"成功导入 {len(data)} 条商品数据")
3.2 在界面中添加导入按钮
在 ui/product_ui.py
中添加导入按钮:
from logic.product_logic import import_products_from_excel
class ProductUI(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
self.layout = QVBoxLayout()
# 导入按钮
self.import_button = QPushButton("从 Excel 导入")
self.import_button.clicked.connect(self.import_products)
self.layout.addWidget(self.import_button)
self.setLayout(self_layout)
def import_products(self):
"""导入商品数据"""
file_path, _ = QFileDialog.getOpenFileName(self, "选择文件", "", "Excel 文件 (*.xlsx)")
if file_path:
import_products_from_excel(file_path)
4. 支持其他模块
4.1 供应商管理
导出供应商数据
from database.queries import get_all_suppliers
def export_suppliers_to_excel(file_path):
"""导出供应商数据到 Excel"""
suppliers = get_all_suppliers()
headers = ["ID", "名称", "联系人", "电话", "地址"]
export_to_excel(suppliers, headers, file_path)
导入供应商数据
from database.queries import insert_supplier
def import_suppliers_from_excel(file_path):
"""从 Excel 导入供应商数据"""
data = import_from_excel(file_path)
for item in data:
insert_supplier(
name=item.get("名称", ""),
contact=item.get("联系人", ""),
phone=item.get("电话", ""),
address=item.get("地址", "")
)
print(f"成功导入 {len(data)} 条供应商数据")
4.2 客户管理
导出客户数据
from database.queries import get_all_customers
def export_customers_to_excel(file_path):
"""导出客户数据到 Excel"""
customers = get_all_customers()
headers = ["ID", "名称", "联系人", "电话", "地址"]
export_to_excel(customers, headers, file_path)
导入客户数据
from database.queries import insert_customer
def import_customers_from_excel(file_path):
"""从 Excel 导入客户数据"""
data = import_from_excel(file_path)
for item in data:
insert_customer(
name=item.get("名称", ""),
contact=item.get("联系人", ""),
phone=item.get("电话", ""),
address=item.get("地址", "")
)
print(f"成功导入 {len(data)} 条客户数据")
4.3 入库记录
导出入库记录
from database.queries import get_all_stock_in
def export_stock_in_to_excel(file_path):
"""导出入库记录到 Excel"""
stock_in_records = get_all_stock_in()
headers = ["ID", "供应商", "日期", "总数量", "总金额"]
export_to_excel(stock_in_records, headers, file_path)
导入入库记录
from database.queries import insert_stock_in
def import_stock_in_from_excel(file_path):
"""从 Excel 导入入库记录"""
data = import_from_excel(file_path)
for item in data:
insert_stock_in(
supplier_id=item.get("供应商ID", 0),
date=item.get("日期", ""),
total_quantity=item.get("总数量", 0),
total_amount=item.get("总金额", 0)
)
print(f"成功导入 {len(data)} 条入库记录")
5. 总结
通过以上实现,系统可以支持:
- Excel 导出:将商品、供应商、客户、入库记录等数据导出为 Excel 文件。
- Excel 导入:从 Excel 文件导入数据到系统中。
- 灵活扩展:可以轻松扩展其他模块的导入导出功能。
这种方法非常适合需要与 Excel 交互的仓库管理系统,同时也能提高数据管理的效率。
要实现 模糊录入 功能,系统需要能够从用户粘贴的文本中自动识别 产品名称、单价 和 数量。这可以通过以下步骤实现:
1. 功能需求分析
- 输入:用户粘贴一段文本(例如从文档或聊天记录中复制的内容)。
- 处理:
-
- 识别文本中的产品名称。
- 提取单价和数量。
- 输出:将识别出的产品名称、单价和数量填充到表单中。
2. 实现方案
2.1 文本预处理
- 去除多余的空格、换行符等。
- 将文本按行或按特定分隔符(如逗号、分号)拆分为多个条目。
2.2 识别产品名称
- 使用正则表达式匹配已知的产品名称。
- 如果没有匹配到,可以提示用户手动选择。
2.3 提取单价和数量
- 使用正则表达式提取数字和单位(如“元”、“个”等)。
- 根据上下文判断数字是单价还是数量。
2.4 填充表单
- 将识别出的数据填充到表单中,供用户确认或修改。
3. 代码实现
3.1 文本预处理
import re
def preprocess_text(text):
"""
预处理文本
:param text: 用户输入的文本
:return: 处理后的文本列表(每行一个条目)
"""
# 去除多余的空格和换行符
text = re.sub(r'\s+', ' ', text).strip()
# 按行或分号拆分
lines = re.split(r'[\n;]', text)
return [line.strip() for line in lines if line.strip()]
3.2 识别产品名称
假设系统中已有商品列表,可以从数据库中获取:
from database.queries import get_all_products
def extract_product_name(text, products):
"""
从文本中提取产品名称
:param text: 单行文本
:param products: 商品列表(从数据库获取)
:return: 匹配到的产品名称,如果没有匹配到则返回 None
"""
for product in products:
if product['name'] in text:
return product['name']
return None
3.3 提取单价和数量
def extract_price_and_quantity(text):
"""
从文本中提取单价和数量
:param text: 单行文本
:return: 单价和数量(如果未提取到则返回 None)
"""
# 匹配数字(支持小数)
numbers = re.findall(r'\d+\.?\d*', text)
if len(numbers) >= 2:
return float(numbers[0]), int(numbers[1]) # 第一个数字是单价,第二个是数量
return None, None
3.4 综合处理
def parse_input_text(text):
"""
解析用户输入的文本
:param text: 用户输入的文本
:return: 解析后的数据列表(每个条目是一个字典)
"""
products = get_all_products()
lines = preprocess_text(text)
result = []
for line in lines:
product_name = extract_product_name(line, products)
price, quantity = extract_price_and_quantity(line)
if product_name and price and quantity:
result.append({
"product_name": product_name,
"price": price,
"quantity": quantity
})
return result
3.5 在界面中实现模糊录入
在 ui/stock_ui.py
中添加模糊录入功能:
from PySide6.QtWidgets import QTextEdit, QMessageBox
class StockInUI(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
self.layout = QVBoxLayout()
# 模糊录入文本框
self.fuzzy_input = QTextEdit()
self.fuzzy_input.setPlaceholderText("粘贴文本内容(例如:苹果 5.5元 3个)")
self.layout.addWidget(self.fuzzy_input)
# 解析按钮
self.parse_button = QPushButton("解析文本")
self.parse_button.clicked.connect(self.parse_input)
self.layout.addWidget(self.parse_button)
self.setLayout(self.layout)
def parse_input(self):
"""解析用户输入的文本"""
text = self.fuzzy_input.toPlainText()
if not text:
QMessageBox.warning(self, "错误", "请输入文本内容")
return
parsed_data = parse_input_text(text)
if not parsed_data:
QMessageBox.warning(self, "错误", "未识别到有效数据")
return
# 将解析结果填充到表单中
for item in parsed_data:
self.add_detail(item['product_name'], item['quantity'], item['price'])
QMessageBox.information(self, "成功", f"成功解析 {len(parsed_data)} 条数据")
def add_detail(self, product_name, quantity, price):
"""将解析结果添加到明细表格"""
row = self.details_table.rowCount()
self.details_table.insertRow(row)
self.details_table.setItem(row, 0, QTableWidgetItem(product_name))
self.details_table.setItem(row, 1, QTableWidgetItem(str(quantity)))
self.details_table.setItem(row, 2, QTableWidgetItem(str(price)))
self.details_table.setItem(row, 3, QTableWidgetItem(str(quantity * price)))
4. 示例
输入文本
苹果 5.5元 3个
香蕉 3.2元 5个
橙子 4.0元 2个
解析结果
[
{"product_name": "苹果", "price": 5.5, "quantity": 3},
{"product_name": "香蕉", "price": 3.2, "quantity": 5},
{"product_name": "橙子", "price": 4.0, "quantity": 2}
]
5. 总结
通过以上实现,系统可以:
- 模糊录入:从用户粘贴的文本中自动识别产品名称、单价和数量。
- 智能填充:将解析结果自动填充到表单中,减少用户手动输入的工作量。
- 灵活扩展:可以根据需求调整正则表达式或匹配逻辑,支持更多格式的文本。
这种方法非常适合需要快速录入数据的场景,能够显著提高工作效率。
为了实现 每周五自动发送邮件 给采购人员,通知库存低的商品需要补货,我们可以结合以下技术:
- 定时任务:使用 Python 的
schedule
库或操作系统的定时任务(如cron
)来定期执行脚本。 - 邮件发送:使用 Python 的
smtplib
库发送邮件。 - 库存检查:从数据库中查询库存低的商品。
以下是详细实现方案:
1. 安装依赖
安装所需的 Python 库:
pip install schedule
2. 数据库查询
在 database/queries.py
中编写查询库存低商品的函数:
def get_low_stock_products(threshold=10):
"""
获取库存低于阈值的商品
:param threshold: 库存阈值,默认 10
:return: 库存低的商品列表
"""
conn = sqlite3.connect('warehouse.db')
cursor = conn.cursor()
cursor.execute('''
SELECT name, quantity FROM Products WHERE quantity < ?
''', (threshold,))
products = cursor.fetchall()
conn.close()
return products
3. 邮件发送
在 utils/email_utils.py
中编写邮件发送工具:
import smtplib
from email.mime.text import MIMEText
from email.header import Header
def send_email(subject, body, to_emails, smtp_server, smtp_port, sender_email, sender_password):
"""
发送邮件
:param subject: 邮件主题
:param body: 邮件正文
:param to_emails: 收件人邮箱列表
:param smtp_server: SMTP 服务器地址
:param smtp_port: SMTP 服务器端口
:param sender_email: 发件人邮箱
:param sender_password: 发件人邮箱密码或授权码
"""
try:
# 创建邮件内容
msg = MIMEText(body, 'plain', 'utf-8')
msg['Subject'] = Header(subject, 'utf-8')
msg['From'] = sender_email
msg['To'] = ', '.join(to_emails)
# 连接 SMTP 服务器并发送邮件
with smtplib.SMTP(smtp_server, smtp_port) as server:
server.starttls() # 启用 TLS 加密
server.login(sender_email, sender_password)
server.sendmail(sender_email, to_emails, msg.as_string())
print("邮件发送成功")
except Exception as e:
print(f"邮件发送失败: {e}")
4. 定时任务
在 scripts/low_stock_notification.py
中编写定时任务脚本:
import schedule
import time
from database.queries import get_low_stock_products
from utils.email_utils import send_email
def check_low_stock_and_notify():
"""
检查库存低的商品并发送邮件通知
"""
# 获取库存低的商品
low_stock_products = get_low_stock_products(threshold=10)
if not low_stock_products:
print("没有库存低的商品")
return
# 生成邮件正文
body = "以下商品库存较低,请及时补货:\n\n"
for product in low_stock_products:
body += f"商品名称: {product[0]}, 当前库存: {product[1]}\n"
# 邮件配置
subject = "库存补货通知"
to_emails = ["purchaser1@example.com", "purchaser2@example.com"] # 采购人员邮箱
smtp_server = "smtp.example.com" # SMTP 服务器地址
smtp_port = 587 # SMTP 服务器端口
sender_email = "noreply@example.com" # 发件人邮箱
sender_password = "your_password" # 发件人邮箱密码或授权码
# 发送邮件
send_email(subject, body, to_emails, smtp_server, smtp_port, sender_email, sender_password)
# 每周五上午 10:00 执行任务
schedule.every().friday.at("10:00").do(check_low_stock_and_notify)
# 保持脚本运行
while True:
schedule.run_pending()
time.sleep(1)
5. 部署定时任务
5.1 使用 Python 脚本运行
直接运行 low_stock_notification.py
脚本:
python scripts/low_stock_notification.py
5.2 使用操作系统的定时任务
如果希望脚本在后台运行,可以使用操作系统的定时任务工具。
在 Linux 上使用 cron
- 打开 crontab 编辑器:
crontab -e
- 添加以下行(每周五上午 10:00 执行):
0 10 * * 5 python /path/to/your/scripts/low_stock_notification.py
在 Windows 上使用任务计划程序
- 打开任务计划程序。
- 创建一个新任务,设置触发器为每周五上午 10:00。
- 设置操作为运行
python
并指定脚本路径。
6. 示例邮件内容
邮件主题
库存补货通知
邮件正文
以下商品库存较低,请及时补货:
商品名称: 苹果, 当前库存: 5
商品名称: 香蕉, 当前库存: 8
商品名称: 橙子, 当前库存: 3
7. 总结
通过以上实现,系统可以:
- 自动检查库存:每周五自动检查库存低的商品。
- 发送邮件通知:将库存低的商品列表通过邮件发送给采购人员。
- 灵活配置:可以调整库存阈值、邮件接收人、发送时间等。
这种方法非常适合需要定期补货的场景,能够显著提高仓库管理的效率。