ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Django+Flask双框架构建超市进销存系统:从库存设计到部署实战

Django+Flask双框架构建超市进销存系统:从库存设计到部署实战 先说结论这套系统要是真能顺顺当当跑起来超市连锁的库存账、采购账、门店销售账基本就全收进一个后台里了老板不用再靠Excel和微信群对账采购和仓管也不用天天扯皮“那批货到底到没到”。不管你是打算拿它做毕业设计、给自家小超市做信息化还是想入门 Python Web 开发练手这个项目的含金量都够。一条链路下来Django 的 ORM、Model 设计、Admin 后台、权限控制、模板渲染、静态资源处理Flask 的轻量接口、Excel 导入导出、报表服务再加 waitress 和 nginx 的部署几乎把一个 Web 管理系统从开发到上线的全部环节都覆盖到了。这篇文章我按自己实际做这类项目的经验来拆先讲整体架构和选型思路再讲数据库和核心业务逻辑怎么设计然后把采购、销售、库存、调拨这几个关键模块的代码和组织方式摆出来接着说说 Flask 在这个系统里到底扮演什么角色最后聊部署和踩坑。内容偏实战直接照着抄能省不少事。1. 系统设计与技术选型思路1.1 为什么是 Django Flask 双框架而不是只选一个很多第一次接触这个项目的人都会问既然都用 Python 了为什么还要搞两个 Web 框架Django 和 Flask 不是二选一的关系吗实际上在这个项目里它们各自承担的职责完全不同。Django 负责的是核心业务门店管理、商品档案、采购单、销售单、库存流水、员工权限这些东西。这类业务的特点是模型关联复杂、需要后台管理界面、需要权限体系Django 的 ORM、Model 层、Admin 后台、Form 验证、中间件机制天生就是为这种重业务系统准备的。Flask 在这个项目里更像是“辅助服务”。比如批量导入商品 Excel、导出库存报表、对接电子秤或者第三方 ERP 的小接口、给门店端提供一个简单的数据查询 API。这些场景不涉及复杂的 Model 关联也不需要 Admin 后台用 Flask 写反而更轻快不需要背着一整个 Django 项目跑来跑去。我实际做的时候还会把 Flask 服务单独跑在一个端口上比如 Django 跑 8000Flask 跑 8001两者通过 HTTP 接口通信。这样做的另外一个好处是如果某个接口被第三方高频调用或者需要做报表生成这种耗时操作可以直接把 Flask 服务拆出去独立部署不会拖垮主系统。1.2 核心需求拆解超市连锁进销存到底管什么超市连锁的进销存和普通单店进销存最大的区别就是多了“门店”和“仓库”这两个维度。单店系统只要管一个库存数就行但要管连锁就要能回答这些核心问题总部仓库里有哪些货各有多少各门店的货架和库房里分别还有多少门店缺货时是从总部调拨还是走采购流程调拨过去的货在途多少、到了多少、损耗多少每个门店的销售数据是多少退货了多少供应商的账期到了该付多少钱所以这个系统的核心数据模型必须围绕“门店—仓库—商品—库存流水”来设计。采购、销售、调拨、盘点本质上都是在改库存但每笔操作都要留下一条流水记录这样才能追溯“这个库存数字是怎么来的”。另外还要考虑多角色权限问题总部管理员能看所有门店数据能审核采购单门店店长只能看本门店的库存和销售只能发起调拨申请采购员只能维护商品档案和做采购单仓管员能做收货入库。权限不分清楚一个能看全部门店的账对连锁体系来说就是安全隐患。1.3 用 Django MTV 模式理解整条业务链Django 的 MTV 模式一直是新人最容易绕晕的地方但结合这个项目就很好理解。M 就是 Model对应数据库表。这个系统里最核心的是商品表、门店表、仓库表、库存表、库存流水表、采购单表、采购单明细表、销售单表、销售明细表、调拨单表。T 就是 Template对应前端页面。Django 的模板引擎负责把数据显示在页面上比如采购单列表页、库存明细页、门店销售报表页。V 就是 View对应业务逻辑。比如采购入库的视图函数要做的事是校验单据数据、更新采购单状态、增加库存、写入流水、更新应付账款。这一层的代码质量直接决定了系统靠不靠谱。MTV 模式的好处是每一个环节都能单独开发、单独测试。我们项目里三个同事同时分工一个写 Model 层和数据库迁移一个写 View 层的业务逻辑一个写模板页面互相之间只需要约定好变量名和数据格式不用互相等。2. 数据库模型设计要点与库存核心逻辑2.1 核心表结构与字段设计我直接把我用过的表结构抽象成通用的大概长这样你可以根据自己的需求扩展表名关键字段说明Storeid, name, address, manager, phone门店表Warehouseid, store(OneToOne), name, keeper仓库表一个门店一个仓库便于管理Categoryid, name, parent商品分类支持两级分类就够了Productid, sku_code, barcode, name, spec, unit, category, purchase_price, sale_price, low_stock, high_stock商品档案价格用 DecimalFieldSupplierid, name, contact, phone, address供应商表Stockid, warehouse, product, quantity, last_in_date, last_out_date实时库存表StockFlowid, warehouse, product, change_type, quantity, before_qty, after_qty, order_no, remark, created_at库存流水表任何变动都写这里PurchaseOrderid, order_no, supplier, warehouse, status, total_amount, paid_amount, operator, created_at采购单主表PurchaseOrderItemid, purchase_order, product, quantity, price, amount采购单明细SaleOrderid, order_no, store, status, total_amount, cashier, created_at销售单主表SaleOrderItemid, sale_order, product, quantity, price, amount销售单明细TransferOrderid, order_no, from_warehouse, to_warehouse, status, operator, created_at调拨单主表TransferOrderItemid, transfer_order, product, quantity调拨明细特别注意两点。第一所有金额字段用DecimalField绝对不要用 FloatField。超市商品单价几块钱几分钱Float 的浮点误差会在累计报表里被放大对账对不上时你会怀疑人生。max_digits10, decimal_places2基本够用。第二库存流水表是整个系统的“审计日志”它不存冗余的商品名称和仓库名称只用外键关联。每次改库存必须在同一个数据库事务里同时写流水两个操作要么都成功要么都失败。2.2 库存扣减的并发安全超市门店收银时段非常集中晚高峰 18 点到 21 点。如果销售出库时直接“读库存 → 算新库存 → 写库存”高并发下最容易出的问题就是超卖两个请求同时读到库存还剩 1各自减 1都写回 0但实际上卖掉了 2 件。解决办法其实不复杂后端一定要用行级锁。Django 的 ORM 提供了select_for_update()方法在事务内锁定库存行同一个商品的其他扣减操作必须等当前操作提交才能继续。from django.db import transaction from django.db.models import F transaction.atomic def sale_out_stock(product_id, warehouse_id, quantity): stock Stock.objects.select_for_update().get( product_idproduct_id, warehouse_idwarehouse_id, ) if stock.quantity quantity: raise ValueError(库存不足) stock.quantity - quantity stock.last_out_date timezone.now() stock.save() StockFlow.objects.create( warehouse_idwarehouse_id, product_idproduct_id, change_typesale_out, quantity-quantity, before_qtystock.quantity quantity, after_qtystock.quantity, order_nosale_order_no, )这里还有一个更快的写法是用F()表达式Stock.objects.filter( pkstock.pk, quantity__gtequantity, ).update(quantityF(quantity) - quantity)但用select_for_update()的好处是你先读到了数据库里真实的库存数可以在代码层做更灵活的业务判断比如库存不足时返回明确提示。2.3 库存流水设计的几个坑写流水时before_qty和after_qty这两个字段看着冗余但实际排查问题时非常有价值。有一次采购部说系统里某个商品库存多了 20 件我直接按商品和时间查流水发现是做了一笔红字采购入库单数量填错了后来做了退货。如果只有变动数量而没有变动前后的库存值这种问题根本没法定位。流水的change_type建议用字符串枚举不要用数字。purchase_in、purchase_return、sale_out、sale_return、transfer_out、transfer_in、check_loss、check_profit这些类型一眼就能看懂。数据库加索引在warehouse_id product_id created_at上查询历史明细时才不会越用越慢。3. 核心业务模块的 Django 实现3.1 采购入库流程从下单到入账采购流程我建议设计成三个状态待审核 → 已审核待入库 → 已完成。采购员创建采购单选择供应商和仓库添加商品明细系统自动算出总金额。提交后单据进入“待审核”状态只有有权限的管理员才能审核。审核通过后仓管员在“采购入库”页面看到待入库的单据核对实物后点击入库系统自动增加库存并写流水。Django 里实现这个流程关键在于把多个表的数据在一个事务里维护好。Model 层的设计要避免在视图函数里写大段重复逻辑我会把入库操作封装成 service 函数。from django.db import transaction from .models import PurchaseOrder, Stock, StockFlow transaction.atomic def complete_purchase_in(purchase_order_id, operator): po PurchaseOrder.objects.select_for_update().get( pkpurchase_order_id, statusapproved ) for item in po.items.select_related(product): stock, created Stock.objects.select_for_update().get_or_create( warehousepo.warehouse, productitem.product, defaults{quantity: 0}, ) before stock.quantity stock.quantity item.quantity stock.last_in_date timezone.now() stock.save() StockFlow.objects.create( warehousepo.warehouse, productitem.product, change_typepurchase_in, quantityitem.quantity, before_qtybefore, after_qtystock.quantity, order_nopo.order_no, remark采购入库, ) po.status completed po.save()采购入库这里最容易踩的坑是“重复入库”。如果仓管员手抖点了两次入库而代码没有做状态校验或者没有用事务锁库存会直接翻倍。所以complete_purchase_in开头的select_for_update()必须锁主单并且判断status approved否则直接抛异常。3.2 销售出库与销售退货连锁超市的销售数据一般来自收银系统但在一个进销存管理系统里也需要提供手工开单入口比如处理团购单、批发客户、内部领用。销售出库的逻辑和采购入库反着来核心也是锁库存。但还有一个容易忽略的点退货。顾客退货后商品回到门店仓库系统要自动恢复库存而且在报表里退货要有单独的显示列不能混在销售里否则月底毛利分析会出错。销售单的金额计算还有个细节会员折扣、满减活动是收银系统在前端完成的手工开单不需要重算促销直接把成交价写进明细里的price字段即可。这里保持灵活性让业务人员可以修改成交价但不能低于进价否则亏损了还看不出来。3.3 库存预警安全库存怎么设置才合理库存预警在超市连锁里是实用性非常高的功能。断货意味着损失销售额积压意味着占资金、占库位甚至过期。所以商品档案里我设计了low_stock和high_stock两个字段。安全库存的设置不能所有商品一刀切。卖得快的牛奶、面包最低库存可能要按 3 天的销量算空调、风扇这种季节性的旺季最低库存高淡季可以低甚至允许为 0。我的做法是系统初次上线时统一给每个商品一个默认安全库存跑一个月后根据实际销售数据用脚本调整。比如近 15 天平均日销量乘以备货天数就是合理的最低库存。预警逻辑不用实时扫描我一般用两种方式每个门店登录后的首页自动显示“本店低库存商品 Top 20”和“零库存商品”。每天定时任务扫一遍所有商品的库存数低于阈值就把 SKU 列表生成一张工作单推送给采购员。Django 里实现定时任务可以用 celery但小系统没必要上这么重的方案。直接用系统的 cron 或者 Windows 计划任务每天凌晨跑一个 Django management command 就行。from django.core.management.base import BaseCommand from product.models import Product, Stock class Command(BaseCommand): help 扫描低库存商品生成预警工作单 def handle(self, *args, **options): for stock in Stock.objects.select_related(product, warehouse).all(): if stock.quantity stock.product.low_stock: self.stdout.write( f告警: {stock.warehouse.name} - f{stock.product.name} 库存仅剩 {stock.quantity} )3.4 门店调拨在途库存的概念门店 A 缺货总部仓库有货这时候不是让门店 A 下单采购而是走调拨。调拨单流程是门店 A 发起申请 → 总部审核 → 总部仓管出库 → 门店 A 确认入库。这里有个“在途库存”的概念。调拨出库后货离开了总部仓库但还没到门店仓库这期间的货属于谁严格来说它应该算在途。很多小系统直接不处理在途导致调拨单还没完成两边账都对不上。我的处理方案是调拨单有两个状态节点statusout表示已从调出仓出库statusdone表示已到达调入仓。出库时调出仓扣库存写一条transfer_out流水入库时调入仓加库存写一条transfer_in流水。调拨明细单独统计在途量就是statusout且未 done 的单子里的数量。4. 角色权限与 Django Admin 后台配置4.1 系统权限控制的基本模型连锁体系下不控制好权限后果是很严重的。门店店长能看到全公司采购价可能就有动力去跟供应商私下谈回扣普通员工能随便修改商品售价超市就会面临“员工价”漏洞。Django 自带的auth应用提供了完整的用户、组、权限模型直接基于它二次开发最省事。用户表用User角色用Group权限用Permission。假设我们有三个角色角色权限范围总部管理员所有模块审核采购单、调拨单查看所有门店报表门店店长本门店库存、销售查询发起调拨申请采购员商品档案、供应商管理、采购单创建与修改创建组的时候在 Admin 后台给每个组勾选对应的权限再在前端视图函数或者模板里用permission_required装饰器控制访问。from django.contrib.auth.decorators import permission_required permission_required(store.can_transfer_out, raise_exceptionTrue) def transfer_out(request, transfer_id): # 调拨出库逻辑 pass4.2 Django Admin 在进销存系统里的妙用很多人把 Django Admin 当成一个鸡肋的自动后台但在进销存项目里它反而是快速上线的好帮手。采购单、销售单这些单据在 Admin 后台可以快速查看和筛选。供应商、商品分类、门店信息这些基础数据的维护直接在 Admin 里录入比自定义页面还要高效。只要在admin.py里注册 Model配置list_display、list_filter、search_fields即可。admin.register(PurchaseOrder) class PurchaseOrderAdmin(admin.ModelAdmin): list_display (order_no, supplier, warehouse, status, total_amount, created_at) list_filter (status, created_at) search_fields (order_no, supplier__name) date_hierarchy created_at但我一般不开放 Admin 给门店人员用因为它对权限的控制不如自定义页面灵活而且界面风格偏后台。总部管理员和采购员可以用 Admin门店端还是走自定义的前端页面界面更干净、操作更明确。4.3 数据隔离方案权限控制除了“能进哪个页面”还有一个更麻烦的问题数据隔离。同样是库存列表总部管理员要看到全部门店店长只能看到自己门店的。这个在查询层面处理获得当前登录用户对应的门店然后在所有查询里强制加上门店过滤条件。def get_visible_warehouses(request): if request.user.is_superuser: return Warehouse.objects.all() store request.user.staff_profile.store return Warehouse.objects.filter(storestore)写代码时不建议在视图里每次都手动写这个过滤逻辑我当时是封装成 mixin或者直接用 Django 的 queryset 封装到 Model Manager 里避免哪天漏写一个过滤条件导致数据泄露。5. Flask 在项目里承担的角色与实现5.1 用 Flask 做 Excel 导入导出的轻量服务超市商品档案动辄几千上万条逐条手工录入不现实。上线初期最大的需求就是把老系统的 Excel 商品表一次性导进来。这种场景用 Django 也能写但要用到 pandas、openpyxl 等等一堆依赖每次导入还可能要写日志、处理数据格式校验。我把这些逻辑单独做成一个 Flask 服务代码更干净。from flask import Flask, request, jsonify import openpyxl app Flask(__name__) app.route(/api/import_products, methods[POST]) def import_products(): file request.files[file] wb openpyxl.load_workbook(file) ws wb.active items [] for row in ws.iter_rows(min_row2, values_onlyTrue): sku_code, barcode, name, spec, unit, purchase_price, sale_price row[:7] items.append({ sku_code: sku_code, barcode: barcode, name: name, spec: spec, unit: unit, purchase_price: float(purchase_price), sale_price: float(sale_price), }) # 这里调用 Django 提供的 HTTP API 写入数据库 return jsonify({code: 0, count: len(items)})Flask 只负责文件的接收和解析拿到结构化数据后通过 HTTP 调 Django 的 API 写入主数据库。这样两个服务的数据落库逻辑都在 Django 这边避免两头直接连数据库造成脏数据。5.2 Flask 做报表生成与数据导出超市连锁运营最常看的是库存明细表、销售日报、采购汇总表、毛利分析表。这些表如果直接放 Django 页面里也能做但报表往往是按周、按月生成 PDF 或 Excel 然后发到管理群用 Flask 起一个轻量服务定时生成会更快。Flask 路由接收参数查询数据库用 openpyxl 生成 Excel 文件保存在临时目录返回下载地址。app.route(/api/stock_report) def stock_report(): warehouse_id request.args.get(warehouse_id, typeint) data query_stock_data(warehouse_id) # 从 Django 数据库读取 wb openpyxl.Workbook() ws wb.active ws.append([商品编码, 商品名称, 规格, 库存数量, 门店]) for item in data: ws.append([item[sku_code], item[name], item[spec], item[quantity], item[store]]) file_path os.path.join(reports, fstock_{warehouse_id}_{datetime.now():%Y%m%d%H%M%S}.xlsx) wb.save(file_path) return send_file(file_path, as_attachmentTrue)报表数据量大时还有防超时的考虑Flask 这边生成任务改为后台进程前端轮询下载状态避免长连接把 nginx 的 worker 占死。5.3 Flask 提供门店端轻量 API门店端如果要做一个运行在收银机旁边的小屏幕看库存或者对接外部供应链系统查库存、下订单直接开放 Django 的后台 URL 风险较大也容易把复杂的业务逻辑暴露出去。用 Flask 做一个薄薄的 API 网关就合适。Flask 只接受白名单 IP 的请求参数校验后调用 Django 的接口返回结果时按门店维度把数据裁剪好。这样外部系统接触不到 Django 的实际接口结构安全和逻辑都更可控。6. 部署与上线waitress nginx 实战6.1 为什么 Windows 上不用 gunicorn很多人习惯 Linux 上用 gunicorn 跑 Django但如果你是在 Windows 服务器上部署gunicorn 是跑不起来的它是基于 Unix fork 实现的。之前网络热词里也有一堆“windows10 waitressnginx 部署”的搜索说明这个坑大家没少踩。waitress 是纯 Python 实现的 WSGI 服务器Windows、Linux 都能跑性能应对中小型进销存系统完全够用。安装一行命令pip install waitress启动 Django 项目假设项目配置在 settings 里已经关了 DEBUGwaitress-serve --listen127.0.0.1:8000 myproject.wsgi:application这里监听 127.0.0.1 而不是 0.0.0.0 是有讲究的如果直接对外暴露 Django 端口静态文件请求也打到 Django 进程里所有并发请求都会由 waitress 的线程池处理ThreadedWSGIServer 默认线程数有限一旦到了瓶颈页面就卡死不响应。正确做法是让 nginx 监听公网 80/443转发动态请求给 waitress静态文件由 nginx 自己处理。6.2 nginx 配置示范我这里给一个前后端一体部署的 nginx 配置片段所有静态文件交给 nginx动态请求反向代理给 Django waitressFlask 服务代理给 8001 端口。server { listen 80; server_name your-domain.com; client_max_body_size 20m; location /static/ { alias C:/path/to/static/; } location /media/ { alias C:/path/to/media/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location /flask/ { proxy_pass http://127.0.0.1:8001/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }启动 Flask 服务类似waitress-serve --listen127.0.0.1:8001 app:app刷新 nginx 配置后访问http://your-domain.com/flask/api/import_products就能打到 Flask 了。注意 Flask 路由里的前缀要和location对应的路径匹配好Flask 里可以加一个url_prefix或者在路由里直接带上/flask前缀。6.3 Windows 下部署的路径和编码坑Windows 部署 Python Web 项目最常见的两个问题路径问题和编码问题。路径问题尤其容易出现在附件下载和静态文件上。Django 项目里如果用相对路径拼接文件目录在 Windows 下运行很容易踩中“反斜杠被转义”的坑。务必用os.path.join或者pathlib.Path而且配置里一定要用绝对路径。from pathlib import Path BASE_DIR Path(__file__).resolve().parent.parent MEDIA_ROOT BASE_DIR / media STATIC_ROOT BASE_DIR / static另一个坑是 Excel 导入时中文乱码。openpyxl 读写 xlsx 本身没问题但 Windows 下如果你用 csv 导出默认编码是 gbk 或者 ascii用 Excel 打开乱码。碰到 CSV 导出场景要么导出时编码指定utf-8-sig要么直接改用 xlsx 格式。6.4 静态文件收集与常见显示问题Django 在开发模式DEBUGTrue时能自己处理静态文件但生产部署时必须执行一次静态文件收集把所有 App 里的静态文件统一复制到STATIC_ROOT指向的目录。python manage.py collectstatic --noinput网络热词里有个“vscode 写 img 标签在 django 的 static 文件中显示不了”这个问题十有八九就是没搞清STATIC_URL和模板标签的配合。规范写法是在模板里用{% load static %}然后img src{% static images/logo.png %}而不是直接写img src/static/images/logo.png。后者在 DEBUG 模式下可能碰巧能显示生产环境一旦改了静态文件部署路径就直接 404。7. 常见问题与排查技巧实录7.1 “数据库表改了但代码跑起来没反应”这是新手最常问的问题。Django 的 ORM 不像某些框架每次启动自动同步表结构修改了 Model 字段后必须生成并执行迁移文件。python manage.py makemigrations python manage.py migrate如果migrate没生效先python manage.py showmigrations看迁移记录查一下是不是有某个迁移文件被标记成了未执行。不要手动去数据库里改表结构否则迁移记录和真实表结构对不上后面所有迁移都会报错。7.2 页面能开但接口写数据就 500这种问题先看日志Django 的 debug 页面会列出完整 traceback。如果生产环境关了 debug就看日志文件或者临时在环境变量里开一次DEBUGTrue复现问题。常见原因有三个数据库里的DateTimeField时区问题、DecimalField位数不够存超额数据会报 DataError、外键关联的对象被删了导致 IntegrityError。时区问题建议直接全局用USE_TZ True数据库连接时统一指定Asia/Shanghai。7.3 并发扣减库存后出现负库存负库存不是 bug而是业务漏洞。原因通常是库存扣减操作没有加锁或者加了锁但没有开启事务。在 Django 里select_for_update()必须在transaction.atomic()块内才有意义否则锁会自动释放根本锁不住。另外检查一下是不是所有改库存的入口都走了同一个 service 方法。如果采购入库走 service但盘点报损在视图里直接改了stock.quantity两条链路没有统一加锁逻辑照样会出现并发问题。我的原则是凡是可以改库存的地方只能调用同一个stock_change()函数不允许直接对Stock.quantity赋值。7.4 部署后访问页面样式全丢静态文件 404 最常见。第一步确认collectstatic执行了第二步确认 nginx 的location /static/里的alias路径和实际目录一致第三步检查模板里是不是硬编码了/static/而没有用模板标签。另外在 Windows 上部署时alias路径用正斜杠不用反斜杠。C:/path/to/static/这种写法 nginx 能正常解析C:\path\to\static\很容易出问题。7.5 常见问题速查表问题现象大概率原因排查顺序Admin 页面能进但自定义页面 500模板变量名不对或数据查询出错先看 traceback再查模板里变量采购单审核后库存没变service 函数没被调用或事务回滚看流水表有没有记录再看状态是否更新Flask 接口能通但收不到文件nginxclient_max_body_size太小改配置并重载 nginxExcel 导入中文乱码编码问题xlsx 用 openpyxl 没问题csv 要utf-8-sig导出时改编码定时任务没跑起来Windows 计划任务没配置或命令路径不对手动执行一次 management command 验证库存对不上账缺流水记录或手工改动过数据库按商品查流水定位第一笔异常变动8. 一些实操上的心得与扩展建议这个项目从头到尾做一遍比我预想的更花时间尤其是“库存流水”这一层一旦设计得不严谨后面对账和排查问题就会非常痛苦。我自己实际做完后的几点经验第一流水表一定要尽早设计宁可多写几个冗余字段不要图省事只记数量不记前后值。库存的每一次变动包括导入、盘点调整、报损都要有对应的流水类型。第二如果要从零开发建议顺序是先搭好商品、门店、仓库、库存、流水这几张基础表然后做采购入库再做销售出库最后加调拨和盘点。采购和销售跑通后系统就已经能用了后面每个模块都等于在原有库存逻辑上“加一种流水类型”不会伤筋动骨。第三数据库备份绝对不能省。进销存系统是业务的核心一天不备份都心虚。Windows 上可以用计划任务每天凌晨执行一次数据库 dump保留最近 7 天。后续想扩展的话可以加条码枪扫描录入、对接电子发票接口、上小程序让门店店长在手机上审批调拨单都是基于现有框架很自然的延伸。这套 Django Flask 的架构扛住一个几十家门店的连锁超市的日常进销存管理完全没有问题。
返回列表