资讯动态

Python进销存系统源码解析:从Excel台账到数据库驱动

发布时间:2026/10/17 16:23:47 来源:尧图企业网站定制
简介这份Python进销存系统源码面向希望掌握企业级业务系统开发的中初级Python开发者与计算机专业学生可用于课程设计、毕业设计或二次开发练手。系统围绕进货、销售、库存三大核心流程展开涉及Web框架、数据库交互、模板渲染、路由处理、表单验证、会话管理、权限认证、日志记录、库存管理算法、数据分析报表及API集成等知识点帮助读者理解从需求到落地的完整实现思路。资源以zip压缩包形式提供整体约91.29MB文件总数与类型明细上游暂未提供可下载后自行查看目录结构。目前已有709人学习下载适合对照源码梳理模块划分、学习业务逻辑组织方式并在此基础上定制符合自身需求的进销存功能。1. 从一张Excel台账到一套能跑的系统这份python进销存源码到底能省多少事很多做小生意或者管仓库的朋友最开始都是拿Excel记进出货。刚开始几十条数据还行一旦SKU过百、每天几十单进出Excel就开始卡公式一多还容易崩最要命的是两个人同时改一个文件版本冲突能把人逼疯。我见过一个做五金批发的朋友月底盘库对不上账硬是熬了两个通宵翻聊天记录找单据。这份python进销存系统源码解决的就是从“手工台账”到“数据库驱动”的跨越——它把商品、采购、销售、库存、往来账这些环节用代码串起来数据落在本地数据库里查询、汇总、预警都是自动的。适合谁一是刚学完python基础、想找个完整项目练手的开发者二是小团队里懂点技术的运营或仓管想搭一套自己能改的轻量系统三是接私活需要快速交付进销存模块的人。它不是什么大厂ERP但胜在结构清晰、依赖少、能跑起来改起来也不费劲。2. 拆开源码包先看骨架模块划分与数据库表怎么对应业务拿到一份源码别急着pip install。我一般先花二十分钟把目录结构和建表语句过一遍这比直接跑起来更能判断它值不值得改。进销存系统的核心就三件事货怎么进来、怎么出去、还剩多少。源码包通常按这个逻辑切模块下面按常见结构拆。2.1 目录结构与职责边界典型的python进销存源码包根目录下会有这么几块inventory_system/ ├── main.py # 入口启动主窗口或Web服务 ├── config.py # 数据库连接、路径、常量配置 ├── models/ # 数据模型层 │ ├── product.py # 商品、分类 │ ├── purchase.py # 采购单、供应商 │ ├── sale.py # 销售单、客户 │ └── stock.py # 库存流水、盘点 ├── views/ # 界面层Tkinter/PyQt或路由Flask ├── services/ # 业务逻辑单据审核、库存增减 ├── utils/ # 导出Excel、打印、校验工具 └── requirements.txt # 依赖清单这个划分不是摆设。models只管字段和关系services管“采购单审核后库存加多少”这类规则views只负责展示和收集输入。新手最容易犯的错是把库存加减逻辑写在界面按钮的回调里结果换个界面就得重写一遍。你拿到源码后先确认库存变动是不是集中在services层如果是这套代码的可维护性就及格了。2.2 核心表结构与字段含义进销存的数据模型绕不开这几张表。下面按常见设计列一下关键字段你对照源码里的建表语句看字段名可能不同但逻辑一致。表名关键字段说明productid, name, sku, category_id, unit, purchase_price, sale_price, stock_qty, warn_qty商品主数据stock_qty是当前库存warn_qty是预警下限categoryid, name, parent_id支持二级分类parent_id为0表示顶级supplierid, name, contact, phone, address供应商信息customerid, name, contact, phone, address客户信息purchase_orderid, order_no, supplier_id, total_amount, status, create_time采购主单status区分草稿/已入库purchase_itemid, order_id, product_id, qty, price, amount采购明细一单多品sale_orderid, order_no, customer_id, total_amount, status, create_time销售主单sale_itemid, order_id, product_id, qty, price, amount销售明细stock_logid, product_id, change_qty, type, ref_order, create_time库存流水type区分采购入库/销售出库/盘点调整这里有个设计点值得注意库存数量到底存在product表里还是每次从stock_log累加算出来两种做法各有取舍。存字段查询快但一旦流水和字段对不上就麻烦纯流水算准确但商品多了列表页会慢。常见做法是两者都有——product.stock_qty作为冗余字段每次单据审核时在同一个事务里更新它并写一条stock_log盘点时用流水核对字段。你看源码里是不是这么处理的如果是说明作者踩过坑。2.3 依赖清单与运行环境确认requirements.txt里通常就几个包数据库驱动pymysql或sqlite3内置、界面库PyQt5或tkinter内置、Excel导出openpyxl。如果用了Flask做Web版还会多出flask、flask-sqlalchemy。先确认版本python 3.8以上基本都能跑。我习惯先建虚拟环境再装依赖避免污染全局。python -m venv venv # Windows venv\Scripts\activate # macOS/Linux source venv/bin/activate pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple装完先别急着跑main.py用python -c import flask这类命令逐个验证关键包是否导入成功。有次我遇到PyQt5装上了但缺平台插件界面死活起不来报错信息还特别隐晦后来发现是系统缺libxcb相关库。这种环境问题在Linux上尤其常见Windows相对省心。3. 让系统跑起来数据库初始化与首单录入的完整链路骨架看明白了接下来把它跑起来。这一章按“建库→初始化→录商品→做采购→做销售→查库存”的顺序走一遍每一步都对应源码里的具体操作。你跟着走完这套系统能不能用、哪里要改心里就有数了。3.1 数据库初始化与配置修改源码默认可能用SQLite文件型数据库零配置适合单机。如果要多人用得换MySQL。先看config.py里的连接串# config.py 常见写法 import os # SQLite 配置 DB_PATH os.path.join(os.path.dirname(__file__), data, inventory.db) SQLALCHEMY_DATABASE_URI fsqlite:///{DB_PATH} # 如果换 MySQL改成下面这样 # SQLALCHEMY_DATABASE_URI mysqlpymysql://root:password127.0.0.1:3306/inventory?charsetutf8mb4换MySQL时注意三点一是提前建好数据库CREATE DATABASE inventory DEFAULT CHARSET utf8mb4;二是密码里如果有特殊字符要转义三是确保pymysql已安装。改完配置后找初始化脚本通常叫init_db.py或create_tables.pypython init_db.py如果源码没有独立初始化脚本而是在main.py启动时自动建表那就直接跑main。跑完用数据库工具连上去看表建出来没有、字段类型对不对。我遇到过datetime字段默认值写成CURRENT_TIMESTAMP但在SQLite下不生效的情况导致创建时间全是空后来在代码层用datetime.now()兜底。3.2 基础数据录入商品与往来单位系统跑起来第一件事不是开单是录基础数据。商品、供应商、客户这三样没有单据开不出来。界面操作各版本不同但底层调用的service方法大同小异。以商品新增为例核心逻辑是# services/product_service.py 常见实现 def add_product(name, sku, category_id, unit, purchase_price, sale_price, warn_qty0): # 校验SKU唯一性 if Product.query.filter_by(skusku).first(): raise ValueError(fSKU {sku} 已存在) # 校验价格非负 if purchase_price 0 or sale_price 0: raise ValueError(价格不能为负) product Product( namename, skusku, category_idcategory_id, unitunit, purchase_pricepurchase_price, sale_pricesale_price, stock_qty0, warn_qtywarn_qty ) db.session.add(product) db.session.commit() return product这段代码里SKU唯一性校验和价格非负校验是必须的少了这两个后面库存和金额全乱。warn_qty是库存预警线设0表示不预警。批量导入商品时常见做法是提供一个Excel模板用openpyxl读进来循环调用add_product每行包在try里失败的行记下来最后统一提示别一行报错就整个中断。3.3 采购入库单据审核与库存联动采购流程是“建采购单→加明细→审核入库”。审核这一步是关键它要在一个事务里完成三件事改采购单状态、增加商品库存、写库存流水。看源码里审核方法怎么写的# services/purchase_service.py def approve_purchase(order_id): order PurchaseOrder.query.get(order_id) if order.status ! draft: raise ValueError(只有草稿状态可审核) try: for item in order.items: product Product.query.get(item.product_id) # 增加库存 product.stock_qty item.qty # 写流水 log StockLog( product_iditem.product_id, change_qtyitem.qty, typepurchase_in, ref_orderorder.order_no ) db.session.add(log) order.status approved db.session.commit() except Exception as e: db.session.rollback() raise e注意db.session.rollback()这行。如果循环到第三个商品时数据库报错前两个商品的库存已经加了不回滚就造成数据不一致。这是进销存系统最典型的坑审核逻辑必须包在事务里。另外order.status ! draft这个前置判断也不能少防止重复审核导致库存加两次。有些源码为了省事审核按钮点一次就禁用但网络延迟或误操作仍可能触发两次请求后端校验才是最后一道防线。3.4 销售出库与库存预警销售流程和采购对称但多一个库存充足性检查。卖出去之前得确认库存够不然就超卖了。# services/sale_service.py def approve_sale(order_id): order SaleOrder.query.get(order_id) if order.status ! draft: raise ValueError(只有草稿状态可审核) try: for item in order.items: product Product.query.get(item.product_id) if product.stock_qty item.qty: raise ValueError(f{product.name} 库存不足当前{product.stock_qty}需要{item.qty}) product.stock_qty - item.qty log StockLog( product_iditem.product_id, change_qty-item.qty, typesale_out, ref_orderorder.order_no ) db.session.add(log) order.status approved db.session.commit() except Exception as e: db.session.rollback() raise e库存不足时抛异常整个事务回滚一张单里只要有一个商品不够整单都出不去。这个策略适合严格管理的场景。如果业务允许部分出库就得改成拆单逻辑复杂度上一个台阶。库存预警则是在商品列表或首页看板里查stock_qty warn_qty的商品列出来。这个查询简单但要注意warn_qty为0的商品不应该被预警所以条件得写成warn_qty 0 AND stock_qty warn_qty。4. 避坑与排查源码跑通前最容易翻车的五个地方这套系统我前后搭过几次也帮人远程排查过下面五个问题出现频率最高。每条按“现象→原因→解决”写你遇到了直接对号入座。4.1 中文乱码商品名存进去变成问号现象录入“螺丝刀”保存后列表里显示“???”或者乱码。原因数据库字符集不是utf8mb4或者连接串没指定charset。SQLite默认支持UTF-8问题多出在MySQL。解决建库时指定DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci连接串加?charsetutf8mb4。已经建好的库用ALTER DATABASE inventory CHARACTER SET utf8mb4;改但已有表的字段字符集可能没跟着变保险做法是重建表。4.2 库存对不上流水和字段不一致现象商品列表显示的库存和流水累加算出来的对不上。原因某次操作直接改了product.stock_qty但没写stock_log或者审核中途报错没回滚。解决先写个对账脚本把每个商品的stock_log按product_id分组求和和product.stock_qty比对找出差异商品。然后查这些商品的操作记录定位是哪次单据出的问题。根治办法是加数据库触发器或者在service层强制“改库存必写流水”代码评审时重点看这块。4.3 界面卡死大数据量列表加载慢现象商品上千条后打开商品列表要等十几秒界面无响应。原因一次性SELECT * FROM product全查出来渲染或者用了ORM的N1查询列表里每个商品又去查分类名。解决列表加分页每页50条分类名用join一次查出来别循环查。如果源码用的是Tkinter的Treeview插入几千行本身就慢可以改成先插100行滚动到底再加载更多。这个改动不大但体验提升明显。4.4 单据号重复并发下order_no撞车现象两个人同时开单生成的单据号一样保存时报唯一约束冲突。原因单据号用“日期自增”生成但自增是在内存里算的没加锁。解决单据号生成放到数据库层用序列表或者SELECT MAX(order_no) FOR UPDATE加行锁。简单点可以用UUID但可读性差。常见折中方案是“日期时分秒随机两位”撞车概率极低真撞了有唯一索引兜底提示用户重试即可。4.5 依赖版本冲突跑起来报ImportError现象pip install -r requirements.txt装完运行报某个模块找不到或者版本不兼容。原因requirements.txt里版本锁得太死或者和当前python版本不匹配。解决先看报错是哪个包用pip show 包名看实际装的版本。如果是SQLAlchemy 2.x和1.x的API差异源码里用了query.get()在2.x里会警告改成db.session.get(Model, id)。实在搞不定就按源码注释里写的python版本重建环境别硬扛。5. 二次开发与数据核对把系统改成自己顺手的样子跑通只是开始真正省事的是按自己业务改。这一章说两个方向一是加一个库存对账的校验脚本二是把常用查询做成快捷入口。最后分享一个我自己的习惯。5.1 写一个库存对账脚本系统用久了难免有对不上的时候。与其等月底盘库发现不如每天跑一次对账。下面这个脚本直接连数据库把差异列出来# check_stock.py 库存对账脚本 import sqlite3 from collections import defaultdict conn sqlite3.connect(data/inventory.db) cursor conn.cursor() # 从流水累加算库存 cursor.execute( SELECT product_id, SUM(change_qty) as calc_qty FROM stock_log GROUP BY product_id ) calc {row[0]: row[1] for row in cursor.fetchall()} # 从商品表读库存 cursor.execute(SELECT id, name, stock_qty FROM product) mismatch [] for pid, name, stock_qty in cursor.fetchall(): calc_qty calc.get(pid, 0) if calc_qty ! stock_qty: mismatch.append((pid, name, stock_qty, calc_qty)) if mismatch: print(f发现 {len(mismatch)} 个商品库存不一致) for pid, name, real, calc in mismatch: print(f {name}(id{pid}): 字段{real}, 流水{calc}, 差异{real-calc}) else: print(库存全部一致) conn.close()这个脚本的逻辑是stock_log里每条记录代表一次库存变动按商品累加就是理论库存product.stock_qty是系统当前认为的库存。两者不等就说明中间有操作没写流水或者事务没回滚干净。差异为正说明字段多了为负说明字段少了。找到差异商品后去查它的stock_log和单据记录一般能定位到具体哪次操作。我一般把这个脚本挂到定时任务里每天早上跑一次有差异就发邮件提醒别等出事了才查。5.2 把高频查询做成快捷入口进销存用起来后有几个查询是天天要看的今日销售汇总、库存预警列表、某客户的历史订单。与其每次点好几层菜单不如在主界面加几个按钮直接跳。以库存预警为例service层加个方法# services/stock_service.py def get_warning_products(): return Product.query.filter( Product.warn_qty 0, Product.stock_qty Product.warn_qty ).order_by(Product.stock_qty.asc()).all()然后在主界面加个“库存预警”按钮点击后弹窗展示这个列表支持导出Excel。导出用openpyxl几行代码就行from openpyxl import Workbook def export_warning_to_excel(products, filepath): wb Workbook() ws wb.active ws.title 库存预警 ws.append([商品名称, SKU, 当前库存, 预警线, 建议采购量]) for p in products: ws.append([p.name, p.sku, p.stock_qty, p.warn_qty, p.warn_qty * 2 - p.stock_qty]) wb.save(filepath)建议采购量按“预警线×2减当前库存”算是个粗略补货参考实际还得看采购周期和起订量。这种小功能加起来不费劲但用起来顺手很多。5.3 一个习惯改任何涉及库存的代码先跑对账最后说个血泪经验。有次我为了图快在销售出库的逻辑里加了个“赠品不扣库存”的分支结果赠品对应的商品库存没减流水也没写月底对账差了十几件。从那以后我每次改完库存相关的代码都强制走一遍流程建测试商品→开采购单入库→开销售单出库→跑对账脚本→确认流水和字段一致。这个习惯帮我拦住了至少三次类似问题。进销存系统的库存就是钱代码里每一行stock_qty 或-都值得多看一眼。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑