ARTICLE DETAIL

资讯详情

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

电商进销存系统设计:库存流水、幂等与状态机是关键

电商进销存系统设计:库存流水、幂等与状态机是关键 电商进销存系统听上去简单管商品、管采购、管出库、管库存。可一旦把“库存准确”和“对账清楚”真正放进系统里很多方案就露馅了。页面做得再好看库存对不上、单据重复提交就重复入账、采购和财务对账对到崩溃这个系统在业务眼里就是“不高级”。高级感不是换一套皮肤而是系统对复杂业务有预判库存按流水算账单据只能流转不能硬改并发扣减不会超卖重复请求不会重复入账任何一步操作都有迹可查。本文会从业务域拆分、数据库表结构、库存扣减与幂等设计、交互体验升级、成本与报表、技术架构落地六个角度拆解并给出可直接套用的 DDL、扣减 SQL、接口设计和排查清单。适合正在做 B 端后台管理系统、电商后台、ERP/WMS 相关项目的产品经理、后端开发和技术负责人。1. 核心能力速览普通进销存与高级进销存差在哪先给出一张对比表后面所有展开都围绕这张表来讲。所谓“高级”不是设计一个更复杂的界面而是让系统在业务建模、数据模型、交互反馈、工程能力四个层面都提前考虑复杂场景。设计维度普通进销存高级感进销存页面组织功能菜单平铺堆砌按业务域组织商品、库存、采购、销售、财务库存数据只存一个“库存数量”字段可用库存、锁定库存、在途库存、批次库存分离单据处理增删改直接改库存单据审核后生成不可变流水可冲红、可追溯并发场景直接 update 库存原子更新 乐观锁 幂等控制成本核算手工录入成本价移动加权平均 / FIFO 自动计算异常处理弹一个 error toast状态机 失败重试 操作日志 审计留痕扩展性一个库一张表走天下预留多仓、多组织、多币种核心表可水平拆分如果只记住一句话高级感的进销存系统库存永远不是被“改”出来的而是被“流水”算出来的。单据是驱动流水的唯一入口权限控制谁能发起单据状态机控制单据怎么流转幂等控制同一笔业务不会被执行两次。2. 业务建模先拆清楚进销存到底在管什么2.1 进销存系统的三个核心域进销存表面上在管“进货、销售、库存”实际上管的是三件事单据流、库存账、资金账。这三个账互相咬合但绝不能混在一张表里。更专业的拆法是把电商进销存系统拆成这几个业务域业务域核心职责主要实体商品中心管理商品档案、SKU、单位、条码、上下架状态商品、SKU、类目、品牌、单位库存中心管理仓库、库位、批次、库存账、库存流水仓库、库位、库存账、库存流水、盘点单采购中心管理采购需求、采购订单、采购入库、退货采购订单、采购入库单、采购退货单销售中心管理销售订单、发货出库、退换货销售订单、销售出库单、退换货单财务结算成本核算、往来对账、应收应付成本账、供应商账单、客户账单很多进销存系统做得很乱根本原因是把商品、库存、采购、销售、财务全塞进一个“库存管理”模块里。界面上看起来功能都有实际上业务实体之间没有清晰边界后续加一个多仓、加一个批次、加一个成本核算就要改表结构甚至重写逻辑。2.2 关键业务模型SKU、仓库、批次、库存账电商进销存的核心不是“单据”而是“SKU 仓库 批次”组成的库存维度。一个比较完整的库存模型至少包含下列维度SKU最小库存管理单元同一件商品的不同颜色、不同规格就是不同 SKU。仓库库存的物理归属电商多仓模式还需要区分发货仓和虚拟仓。库位仓库内的物理位置精细化仓储才需要初期可以不做。批次同一 SKU 不同采购时间、不同保质期需要区分时就引入批次。库存账记录可用库存、锁定库存、在途库存是库存查询的“快照表”。库存流水记录每一次库存变动的来源单据、变动数量、变动前后值是库存的“事实表”。普通系统只做“SKU 库存数量”高级系统会从一开始就把“锁定库存”和“在途库存”拆出来因为电商订单提交后需要预占库存采购单下了但还没到货时也需要体现“在途”否则业务人员看到的数据永远是滞后的。2.3 单据与库存必须解耦进销存系统最容易踩的坑是在页面里直接改库存数量。比如按一个“手动调整”按钮把库存从 10 改成 8。这看起来方便但之后没人知道为什么从 10 变成了 8是谁改的哪笔业务触发的。正确的做法是库存数量只能由库存流水驱动而库存流水只能由经过审核的单据生成。采购入库单审核通过生成“入库流水”库存增加。销售出库单审核通过生成“出库流水”库存减少。盘点单审核通过生成“盘盈/盘亏流水”库存调整。单据作废不能直接删掉原流水而是生成一条反向冲红流水。这样设计之后任何一条库存数据都可以从流水追溯到业务单据再从单据追溯到操作人。对账时不用猜直接查流水。3. 数据库表设计进销存系统表结构怎么设计才合理3.1 SKU 商品表商品表是所有业务单据的基础字段不需要太复杂但唯一编码必须做严。CREATE TABLE sku ( id bigint NOT NULL AUTO_INCREMENT, spu_id bigint DEFAULT NULL COMMENT 商品SPU ID, sku_code varchar(64) NOT NULL COMMENT SKU编码, sku_name varchar(128) NOT NULL COMMENT SKU名称, barcode varchar(64) DEFAULT NULL COMMENT 条码, unit varchar(20) DEFAULT NULL COMMENT 单位, status tinyint NOT NULL DEFAULT 1 COMMENT 状态1启用 0停用, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_sku_code (sku_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENTSKU商品表;这里有两个设计点一是sku_code加唯一索引防止重复创建二是把spu_id单独拆出来方便后续做多规格商品聚合而不是把所有信息塞在一张表里。3.2 库存表库存表的核心是“快照”它不负责记录历史只负责回答“现在还有多少可用”。CREATE TABLE inventory ( id bigint NOT NULL AUTO_INCREMENT, warehouse_id bigint NOT NULL COMMENT 仓库ID, sku_id bigint NOT NULL COMMENT SKU ID, batch_no varchar(64) DEFAULT NULL COMMENT 批次号无批次可空, available_qty int NOT NULL DEFAULT 0 COMMENT 可用库存, locked_qty int NOT NULL DEFAULT 0 COMMENT 锁定库存, on_way_qty int NOT NULL DEFAULT 0 COMMENT 在途库存, version int NOT NULL DEFAULT 0 COMMENT 乐观锁版本号, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_wh_sku_batch (warehouse_id,sku_id,batch_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT库存快照表;关键点available_qty、locked_qty、on_way_qty三分法是电商进销存和传统进销存的明显分水岭。version字段用于乐观锁后面并发扣减时会用。唯一索引warehouse_id sku_id batch_no保证同一仓库同一 SKU 同一批次只有一条库存记录这是防重复数据的基础。3.3 库存流水表库存流水表是整个系统最不该被删改的表。它记录每一次库存变动的原始凭证设计上要保证“只增不改”。CREATE TABLE inventory_flow ( id bigint NOT NULL AUTO_INCREMENT, flow_no varchar(64) NOT NULL COMMENT 流水号, sku_id bigint NOT NULL COMMENT SKU ID, warehouse_id bigint NOT NULL COMMENT 仓库ID, batch_no varchar(64) DEFAULT NULL COMMENT 批次号, biz_type varchar(32) NOT NULL COMMENT 业务类型INBOUND/OUTBOUND/STOCK_TAKE/LOCK/UNLOCK, biz_no varchar(64) NOT NULL COMMENT 来源单据号, change_qty int NOT NULL COMMENT 变动数量正数入库负数出库, before_qty int NOT NULL COMMENT 变动前可用库存, after_qty int NOT NULL COMMENT 变动后可用库存, operator_id bigint NOT NULL COMMENT 操作人ID, created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_biz_type_no (biz_type,biz_no), KEY idx_sku_warehouse_time (sku_id,warehouse_id,created_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT库存流水表;这里最容易忽略的是biz_type biz_no的唯一索引。它保证了同一张单据的同一类业务只能生成一条流水是重复提交的数据库层兜底。普通系统只在“库存表”上做文章高级系统会优先保证“流水表”的完整性和不可变性。流水在库存就能重建库存表丢了流水重放一遍也能恢复。3.4 单据主从表设计采购订单、销售订单、出入库单都建议采用主从表结构主表存单据编号、单据状态、往来单位、金额汇总明细表存具体商品、数量、单价。以采购入库单为例CREATE TABLE inbound_order ( id bigint NOT NULL AUTO_INCREMENT, inbound_no varchar(64) NOT NULL COMMENT 入库单号, supplier_id bigint NOT NULL COMMENT 供应商ID, warehouse_id bigint NOT NULL COMMENT 入库仓库, status tinyint NOT NULL DEFAULT 0 COMMENT 状态0草稿 1已提交 2已审核 3已入库 4已作废, total_amount decimal(18,2) NOT NULL DEFAULT 0.00, create_by bigint NOT NULL, audit_by bigint DEFAULT NULL, audit_time datetime DEFAULT NULL, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_inbound_no (inbound_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT采购入库单主表; CREATE TABLE inbound_order_item ( id bigint NOT NULL AUTO_INCREMENT, inbound_id bigint NOT NULL COMMENT 主表ID, sku_id bigint NOT NULL, batch_no varchar(64) DEFAULT NULL, qty int NOT NULL COMMENT 入库数量, cost_price decimal(18,2) NOT NULL COMMENT 入库成本价, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_inbound_id (inbound_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT采购入库单明细表;主从表的优势是一张入库单可以管理多条 SKU 明细单据状态只需要在主表维护明细表只做数据记录。查询时先聚合并行展示再进入详情展开明细这也是 B 端列表页和详情页标准的数据来源模式。4. 库存扣减与幂等设计防止超卖和重复入账4.1 库存扣减的三种方案电商场景下库存扣减是最容易出现并发问题的地方。多个订单同时下单同一 SKU 扣减时不能被“搞超卖”。比较常见的方案有三种方案实现方式适用场景风险SQL 原子更新UPDATE inventory SET available_qty available_qty - ? WHERE available_qty ?中小规模库存简单行锁竞争数据库压力大乐观锁版本号扣减时校验version失败重试冲突不频繁的常规业务高冲突下重试多Redis 预扣 MQ 异步落库用 Lua 脚本预扣 Redis 库存MQ 异步更新数据库高并发电商大促需要处理 Redis 与 DB 最终一致先看最简单的原子更新方案UPDATE inventory SET available_qty available_qty - #{qty}, locked_qty locked_qty #{qty}, version version 1 WHERE sku_id #{skuId} AND warehouse_id #{warehouseId} AND available_qty #{qty};如果UPDATE影响行数为 0说明库存不足或者记录不存在直接返回失败。这个 SQL 利用数据库行锁保证同一时刻只有一个事务能改这条库存记录是最基础也是最可靠的扣减方式。如果不想让业务直接扣减可用库存更规范的做法是先“锁定库存”支付完成后真正“扣减”。这样订单提交但未支付时可用库存不会变少但也不会被其他订单抢走。锁定和解锁同样要走流水避免锁了之后没人释放。4.2 幂等设计为什么单据不能重复入账幂等要解决的问题是同一个请求因为网络重试、前端重复点击、消息队列重发到了后端会发生两次。比如采购入库单审核接口被重复调用两次如果没有幂等控制库存会被加两次供应商的应付账款也会被记两次整个账就崩了。数据库层面的兜底已经在流水表上做了uk_biz_type_no唯一索引保证同一张单据的同一业务类型只能插入一条流水。代码层面的控制同样要加流程是根据单据号查询单据当前状态。状态不允许当前操作就抛异常。尝试插入库存流水用唯一索引兜底。插入成功才更新库存。最后更新单据状态。Transactional public void confirmInbound(Long inboundOrderId, Long operatorId) { // 1. 检查单据状态防止重复审核 InboundOrder order inboundOrderMapper.selectById(inboundOrderId); if (order null || order.getStatus() ! STATUS_SUBMITTED) { throw new BusinessException(单据状态不允许入库审核); } // 2. 写库存流水数据库唯一索引兜底防重 for (InboundOrderItem item : getItems(inboundOrderId)) { int inserted inventoryFlowMapper.insertIgnore( item.getSkuId(), order.getWarehouseId(), item.getBatchNo(), INBOUND, order.getInboundNo(), item.getQty(), operatorId ); if (inserted 0) { throw new BusinessException(入库单重复提交请刷新后查看); } } // 3. 更新库存快照 inventoryMapper.increaseAvailable( order.getWarehouseId(), item.getSkuId(), item.getBatchNo(), item.getQty() ); // 4. 更新单据状态 inboundOrderMapper.updateStatus(inboundOrderId, STATUS_INBOUND, operatorId); }这里要注意insertIgnore只有在唯一索引冲突时才不会报错如果数据库不支持insert ignore可以先查流水是否存在存在就直接抛异常。两种方式结合才能在接口层和数据库层双重防重。4.3 高并发场景Redis 预扣与最终一致如果电商业务有大促、秒杀等极端并发场景直接把所有扣减压到数据库行锁上数据库会成为瓶颈。更常见的做法是库存热点数据预热到 Redis。下单时用 Lua 脚本扣减 Redis 库存保证原子性。扣减成功发 MQ 消息异步更新数据库库存。订单超时未支付时Redis 和数据库同时回滚库存。Redis 预扣只负责“限流”和“快速失败”数据库最终还是要通过流水账保持一致。Redis 库存和数据库库存可能出现短暂不一致需要定时对账任务用流水表重建数据库库存再和 Redis 对比修正。5. 交互体验设计B 端“高级感”的面子工程5.1 导航和页面组织B 端后台管理系统的“高级感”首先看一级导航。很多系统把所有功能平铺在左侧菜单商品、库存、订单、往来单位、报表全在一层用户找个功能要翻半天。更好的组织方式是按业务域分两层一级导航商品中心、库存中心、采购中心、销售中心、财务中心、报表分析。二级导航库存中心下拆“库存查询”“入库管理”“出库管理”“盘点管理”“调拨管理”。跨域功能如“全部订单”可以放在全局搜索里而不是堆到一级菜单。导航的规则很简单让业务人员按照“我要做一件什么事”找到入口而不是按照开发者的模块划分找入口。5.2 列表页设计进销存系统大部分使用场景在列表页查库存、审单据、导出报表。列表页设计有几个高性价比的优化点筛选区和表格数据区域分开常用筛选条件默认展示高级筛选折叠。表格默认展示关键列单号、往来单位、数量、金额、状态、操作时间、操作人。列展示可以用“列设置”控制不同角色保存不同列方案这个功能成本不高但非常提升感觉。行内操作放高频动作如“查看”“审核”“作废”批量操作放表格工具栏。空状态必须说明下一步比如“暂无采购入库单”下面跟一个“新建入库单”按钮。列表页最忌讳的是“把所有按钮都放在每一行”一眼看去全是按钮不知道哪个是主操作。更合理的做法是主操作在行内批量操作和低频操作在表格上方或详情抽屉里。5.3 详情页与单据流转进销存里大量页面是单据详情。很多系统一遇到详情就跳到新页面来回切换非常累。更好的方式列表点“查看”打开右侧抽屉展示单据信息、商品明细、操作记录和状态流转。详情链路相关的上下游单据比如销售出库单关联的销售订单、物流单、退换货单用关联 Tab 展示。审核操作在详情抽屉内直接完成不需要来回跳转。关键字段如“库存数量”“成本价”“供应商应付”要有明确的单位、格式和变动说明。5.4 状态可视化进销存单据是一套状态机。高级系统会把状态机显性展示出来而不是只给用户一个状态字段。例如采购入库单状态流转草稿 - 已提交 - 已审核 - 已入库 - 已作废用户看到这个流转就知道当前单子处在哪个环节、下一步可以做什么。状态操作按钮也按状态动态显示草稿状态显示“提交”“编辑”“删除”。已提交状态显示“审核通过”“审核驳回”。已审核状态显示“生成入库单”。已入库状态只显示“查看”。这套逻辑在实现上就是给每个状态配置“可用操作”避免用户点了按钮才发现“当前状态不允许”。B 端的体验升级很多不是新功能而是提前拦截错误操作。5.5 批量操作和导入电商进销存经常遇到几百条 SKU 的采购入库、批量审核、批量导入商品。批量任务设计要注意批量审核要给明确的确认弹窗展示将处理多少条是否有异常项。批量导入必须提供模板下载导入后生成校验结果文件逐行说明错误原因。耗时任务不能阻塞浏览器要进入任务中心展示进度、成功数、失败数、失败原因。失败任务支持“重新执行失败项”而不是让用户重新整理整个文件。这一块是普通系统和“能用的大系统”之间最明显的差距之一。6. 业务规则与自动化让系统自己会算账6.1 成本核算电商进销存涉及采购成本、销售毛利、库存价值的计算。最常用的成本核算方式是移动加权平均法。公式新成本单价 (原库存数量 × 原成本单价 本次入库数量 × 本次入库单价) / (原库存数量 本次入库数量)def moving_average_cost(prev_qty, prev_cost, in_qty, in_cost): total_qty prev_qty in_qty if total_qty 0: return 0 return (prev_qty * prev_cost in_qty * in_cost) / total_qty成本计算必须在入库审核时触发并且要记录计算当时的前置库存和前置成本方便追溯。如果一张采购入库单审核后又作废需要用冲红流水回滚成本不能直接改历史成本记录。6.2 库存预警与补货建议库存预警不是简单写一个“低于安全库存就弹红字”而是要根据实际业务算日均销量按最近 30 天或者 90 天的销售出库流水统计。安全库存日均销量 × 采购提前期 安全缓冲。建议补货量安全库存 - 当前可用库存 - 在途库存 未发货订单占用。这个计算可以放在定时任务里每天生成补货建议列表采购人员直接基于建议单生成采购订单。这一块自动化之后进销存系统才真正从“记录工具”变成“决策工具”。6.3 报表与库存周转进销存系统的报表建议单独设计不要把报表查询直接压到业务库。库存汇总报表按商品、类目、仓库维度汇总库存数量和库存金额。库存周转报表库存周转天数 平均库存 / 日均出库数量。采购分析报表供应商准时到货率、采购均价趋势。销售毛利报表按商品、订单、客户维度拆分毛利。报表层的核心设计原则是“只读不写”独立 schema 或者独立数据库通过定时任务或消息队列同步数据避免业务高峰期报表查询拖垮业务库。7. 技术架构落地从模块化单体到服务化演进7.1 模块化单体起步大多数电商进销存项目的业务量还没有到必须上微服务的程度。上来就拆几十个服务反而会增加分布式事务和运维复杂度。更稳的方案是“模块化单体”一个应用多个业务模块模块之间通过领域接口交互。典型模块划分base-center 基础档案仓库、往来单位、用户、角色 product-center 商品与SKU inventory-center 库存账、库存流水、盘点 purchase-center 采购订单、采购入库、采购退货 sale-center 销售订单、销售出库、退换货 finance-center 成本、应收应付、对账 report-center 报表与数据分析在代码层面这些模块可以是一个 Maven/Gradle 多模块工程数据库可以先用同一个实例但按 schema 或表前缀隔离。当某个模块的数据量和访问量真正上来时再把对应模块拆成独立服务。7.2 API 设计与幂等接口接口设计层面所有写操作接口最好都支持幂等。推荐做法是客户端每次请求生成一个requestId服务端用requestId做去重。例如库存扣减接口POST /api/v1/inventory/deduct { requestId: 0a1b2c3d-1234-5678-9abc-def012345678, warehouseId: 101, skuId: 88, batchNo: B20240801, qty: 2, bizType: SALE_OUT, bizNo: SO20240801001 }响应{ code: 0, message: success, data: { flowId: 1024001, afterQty: 18 } }服务端实现时先根据requestId查操作日志如果已经成功处理过直接返回上次结果避免重复扣库存。这个方案成本低但对业务稳定性提升非常明显。7.3 架构层面的其他建议数据库读写分离报表查询走只读从库。库存热点数据用 Redis 缓存Lua 脚本保证原子扣减。库存变更、单据状态变更通过消息队列发布领域事件下游系统订阅。操作日志必须记录谁在什么时间对哪张单做了什么操作进销存系统的审计要求比普通系统高很多。定期做库存快照防止流水重放过慢。8. 常见设计问题和排查思路进销存系统上线后最常出现的问题集中在下面几张表里。问题现象可能原因排查思路解决建议库存出现负数扣减前未校验库存或并发扣减查库存流水确认扣减顺序扣减 SQL 增加available_qty qty条件检查并发控制同一单据重复入库接口无幂等重复点击或 MQ 重放查流水表是否有两条相同biz_no的入库记录加biz_no唯一索引代码层增加状态校验对账不平单据状态和流水状态不同步成本计算时机不对对比销售出库、入库流水和财务应收应付保证成本计算在审核事务中完成用流水重建账单列表页查询很慢多表 JOIN 无索引报表和业务共用库EXPLAIN 看执行计划增加联合索引报表独立库单据状态错乱状态机缺少校验审核和作废并发执行查操作日志看时间线状态更新必须带条件WHERE status ?校验前置状态批量导入卡死同步任务处理大数据量查看任务日志和数据库连接大批量导入改异步任务增加进度和失败重跑库存和实际仓库对不上线下出库未走系统流程盘点不规范对比出入库流水和盘点记录规范业务流程定期盘点盘盈盘亏走审核流水这里需要单独强调一个点库存异常永远不要直接“改库存”要通过盘点单或者冲红单来调整。虽然多了一步审核流程但每一步都可追查这是进销存系统数据可信的基础。9. 最佳实践与合规建议进销存系统是典型的 B 端核心业务系统设计阶段多花心思后期能少踩很多坑。先做业务建模再画原型。导航怎么分、状态机怎么流转、数据从哪来到哪去先在文档里理清楚。核心主流程优先跑通从建商品、建仓库、做采购入库、销售出库、查看库存再到财务对账串起来再扩展。库存流水只增不改不要把删除和 update 的能力开放给普通用户。所有金额字段用 decimal 存储禁止用 float 和 double避免金额精度问题。关键操作全部留痕审核人、审核时间、操作来源、IP、请求 ID缺一不可。权限控制要做到数据行级隔离比如供应商账号只能看自己的订单仓库人员只能操作本仓库单据。涉及平台销售数据、客户数据时只采集业务必需字段避免过度采集个人隐私信息导出和查看敏感数据要有独立权限和日志。跨地域、跨境电商多主体业务提前考虑多租户隔离、汇率、多币种和不同税率配置而不是后期打补丁。发布或测试时使用模拟数据和隔离环境不要拿真实业务库做演示或联调。如果团队刚起步建议先用一个最小闭环版本跑通一仓、一组织、单币种把单据流、库存流水、成本核算做扎实等业务要求多仓、多组织、多币种时再逐步扩展。数据库表设计从一开始就预留warehouse_id、organization_id、currency字段可以显著降低后续扩展成本。10. 总结回到最初的问题电商进销存系统怎么设计更高级答案不在界面皮肤而在四个层面业务层面把进销存拆成商品、库存、采购、销售、财务几个清晰的业务域。数据层面库存是流水算出来的不是页面改出来的。过程层面库存扣减、幂等、状态机、操作日志都要提前设计。交互层面用列表、筛选、抽屉、状态流转和批处理让用户少犯错误、少走回头路。最先应该做的是把库存流水表和单据状态机建起来给所有业务单据加唯一索引和幂等控制。最容易踩的坑是直接改库存数量以及不做状态校验就允许任意操作。这套设计做完后面无论是接电商平台、接物流系统、加智能补货还是接财务系统底子都在。建议先把“库存流水 幂等 状态机”这条主线落地再逐步扩展多仓、多币种和报表分析。收藏本文下次设计进销存系统时可以直接对照这份清单做需求和评审。
返回列表