我们是东莞妥达信息(tudait.com),做精细化库存管理(WMS)与企业管理软件。下面这套做法来自我们在客户现场跑出来的规则,不是 PPT 上的制度。
先给结论
库存混乱,九成不是「账没记」,而是账记错了形状。
大多数公司的库存表长这样:一个产品一行、一个 quantity 字段,采购入库就 +,领用出库就 −,盘点再手工把数字改成实际值。这个形状在低值快消里凑合能用,但在高值物料上天生有三个缺陷:
- 批次混在一起——同一个产品十批货,账上只有一个总数;
- 可用与在库不分——被预订、被冻结、能动的,全是同一个数字;
- 出入库不绑单据和人——谁领的、为哪张单领的,事后只能靠猜。
单价高意味着两件事:一笔出库算错就是真金白银;一批货追溯不到源头就是质量事故。我们最后落地的模型可以概括成一句话:库存数量不是一个被反复修改的字段,而是三种状态的事件行累加出来的结果;每一批入库有自己的身份(inID),每一笔冻结和出库都带着这个身份走。
下面把我们自己的做法摊开讲。
一、我们现在的做法
1. 按采购单入库,入库时就带采购价。 不是入库后再补价、更不是月底估价。采购申请 → 报价 → 采购单 → 入库单,价格在前面的环节就确定好了,入库这一步直接跟着入库明细落进库存行。单价永远和那批货绑死,不会因为后面行情变了而「漂移」。
2. 每一次入库,有一个 inID。 inID 就是这一批货的身份:入库明细行的 _id。同一批货的入库、冻结、出库,全都带这个 inID。产品相同、批号相同,只要不是同一次入库,就是不同的 inID、不同的行——账上不合并。
3. 入库完成后,形成一条「平衡数」(余额)。 我们叫 stockbalance:按「产品 + 仓库 + 仓管 + 供应商 + inID」汇总出来的余额行,实时可查、可供申领。它不是一张需要人去维护的表,而是从原始行聚合出来的视图——所以它永远和明细对得上,不存在「余额表忘了更新」。
4. 出库时按 inID 分别扣。 某个产品有十批入库,就是十个 inID、十个价格。谁动哪一批,就从哪一批扣。先进先出、指定批次,都是在 inID 这一层做的选择,而不是在「产品总数」上做减法。每一批扣减都带着那一批的价格,所以每笔出库天然带有成本口径。
5. 销售 / 领用前先冻结库存。 确认领用的那一刻先把这批货冻结掉:冻结之后这部分数量从「可用」里移出,别人就抢不走了。这一条同时解决两个问题:不会超售(可用数不够就冻不上,直接报错),也不会出现「A 的货被 B 领走、甚至被 B 卖掉」(冻结行绑定了领用人和领用单,出库时只认这条绑定关系)。
二、补充一:核心是「三态事件账」,不是「余额字段」
这是我们和传统库存表最大的分歧点,也是上面一切做法的地基。
库存表里不允许出现「修改数量」这个动作。数量只有三种状态的行:
| 行状态 | 含义 | 何时产生 |
|---|---|---|
status = 1 | 入库 | 入库单生效时新增一行 |
status = 0 | 冻结(预留) | 领用确认时新增一行 |
status = -1 | 出库 | 出库时新增一行 |
然后余额完全由聚合得出:
可用数量 = Σ(status=1) − Σ(status=0) − Σ(status=-1)
冻结数量 = Σ(status=0)
实 数 = Σ(status=1) − Σ(status=-1) ← 真正躺在仓库里的
三个数字并存、分开显示,而不是一个数字承担三种含义。这一点非常关键:「在库 40、冻结 20、可用 20」和「在库 20」是完全不同的两件事,前者能解释货去哪了,后者只会让人以为货丢了。
配套还有两条纪律:
- 撤销、报损、盘点、拆分、转移,全都是「再追加一行」,而不是改旧行。 我们给每行一个方式字段区分:采购入库 / 返仓入库 / 拆分入库 / 盘点入库 / 正常出库 / 报损 / 拆分出库 / 盘点出库 / 撤销 / 拒绝,再加一个有效性标记(有效 / 盘点 / 失效 / 撤销历史)。历史行永远在,只是被标记为不参与核算。
- 出错必须可见。 库存不足就报错,不做「兜底到 0」、不做默认值、不静默跳过。这个原则听着像废话,但它决定了你到底是「账是准的、异常看得见」,还是「账面平了、问题藏起来了」。
好处很直接:任何一天的账都能往回倒推,任何一行都能解释它从哪来、到哪去;要核对时不需要「相信系统」,拿明细重算一遍就行——而且这里没有第二本账要核,结余就是这些流水当场算出来的结果。
三、补充二:批次(inID)是整个模型的脊柱
没有批次身份,前面说的都是空话。inID 带来的能力:
- 批次级追溯:出库单能查到入库单、采购单,反查供应商、批号、入库时间、经手仓管;
- 批次级价格:每批一个价,库存金额 = Σ(数量 × 该批价格),出库成本天然可算;
- 批次级隔离:同产品不同批次互不影响,冻结和扣减都精确到批次;
- 先进先出 / 指定批次:排序规则作用在批次行上,而不是在混账上「假装先进先出」。
我们的物料本来就批次敏感(效期、批号),所以这条要求不算额外成本;但对做电子、耗材、零售的人来说,逻辑是一样的——想做清楚成本,就必须先能分清批次。
四、补充三:冻结不是「锁一个数字」,是「复制一行并声明归属」
这是防超售、防串货的关键实现,展开说:
- 冻结时先校验,再落行。 系统去查该批次(inID + 产品 + 仓库 + 仓管 + 供应商)当前可用数,够才允许冻结;不够就明确报「此位置没有足够库存冻结」,绝不悄悄少冻一点。
- 冻结行的数量 = 本次需求数量,并从原入库行复制出批次信息(inID、价格、批号、仓库……),状态置 0。
- 冻结行绑定归属:领用人、领用单明细。这条绑定是后面出库的「钥匙」——出库时只删绑定这张单的冻结行,别人的冻结碰都不碰。
也正因为冻结行是独立的一行而不是给原行打个标记,才做得到「部分冻结」:一批 100,张三冻 30、李四冻 20,原行不动,可用自动变成 50。解冻也只是把冻结行扣掉 / 失效,原行始终完好。
出库则是在冻结之上再走一遍校验:删除属于该单据的冻结行 → 追加一条状态 −1 的出库行(数量是实领数量,同样带着 inID 和价格)→ 回写单据状态与实领数量。所以有两道闸:冻结时防超售,出库时防负库存。两次校验都不过就是两次报错机会,库存精度就是这么攒出来的。
五、补充四:我们真踩过的坑——并发重复冻结
写这篇不是为了让方案显得完美,这个坑值得单独说,因为它是「设计对了但落地没做够」的典型。
冻结在实现上是三步:读余额视图 → 判断够不够 → 插入冻结行。三步之间没有原子性。结果就是:两个并发的冻结请求都会读到旧的可用数,各插一行,冻结总量超过入库总量,可用数量变成负数(我们实测出现过 −20:入库 20、被冻了两次共 40)。
证据也好认:两条冻结行字段完全一样、创建时间只差几十毫秒。
修法是两道防线:
- 后端:同组串行。 把同一批次(inID + 产品 + 仓库 + 仓管 + 供应商)的冻结操作串起来,后到的请求等前一个跑完再查视图,看到的就是最新可用数;不够就按原逻辑报错——宁可不做,也不做错。
- 前端:确认防重。 提交中禁用重复触发,避免「确认弹窗」和「执行」两路并发。
顺带说一句:这类问题的排查顺序永远是「先看数据特征(时间戳、字段是否完全一致),再怀疑并发」,比盯着代码猜快得多。
六、补充五:这样记账之后,「销售数据不清晰」自己就好了
很多老板抱怨的「销售数据不清晰」,本质是出库记录里没有成本口径:只记了「出了 30 个」,没记「这 30 个是多少钱、从哪一批出的、给谁出的」。批次账天然把这些补上:
- 库存金额:每批剩余数量 × 该批采购价,随时可算,不用等盘点;
- 出库 / 领用金额:按批次价格计,能按产品、分类、组别、人、时间段拆;
- 链路完整:申购 → 报价 → 采购单 → 入库 → 领用 / 销售 → 出库 → 发票,全链路一条 inID 串起来,看结余不需要人工拼表;
- 异常可见:负数、悬空引用、找不到库存,系统直接报错而不是显示 0——我们甚至专门写过脚本去扫描历史脏数据(缺价格、缺引用、把同一批货拆成两条账),因为这个层次的错误不主动找是看不见的。
对高值、批次敏感的业务来说,这几项才是「数据清晰」的真实含义:不是报表好看,而是任何一笔钱都能指认到某一行货。
七、如果你要照做,顺序别搞错
- 先立批次身份:每次入库一个唯一 ID,之后所有动作都带上它。没有这一步,后面的全免谈。
- 把余额字段换成三态事件账:入库 / 冻结 / 出库都是追加行,余额用视图算(可用 / 冻结 / 实数三个数)。
- 加冻结(预留):先校验再落行;同一批次的冻结要串行,防并发重复冻结。
- 绑定单据与人:每个动作记录「为哪张单据、经谁的手」,出库只认自己的绑定。
- 撤销和盘点用标记,不删行,历史永远保留。
- 金额口径统一到批次价,报表按批次汇总,别按「平均价」拍脑袋。
- 出错必须可见:不兜底、不用默认值、不静默跳过。脏数据定期扫描,别等它变成负数才被发现。
这七步里,最容易被跳过、但最不能跳的是第 1 步和第 7 步。批次身份决定了这个体系能走多远;「出错可见」决定了它会不会在半年后悄悄烂掉。
一句话总结
库存混乱的解药不是更勤快的盘点,而是换一种记账形状:每批货有身份(inID),每笔变动追加一行(入库 / 冻结 / 出库三态),余额靠聚合而不是靠修改,冻结把货和单据绑在一起。 账的形状对了,超售、串货、成本不清、追溯不了,这些都是同一个问题的不同症状,会一起消失。
关于我们:东莞妥达信息(tudait.com),做精细化库存管理(WMS)与企业管理软件 —— 国产自研、私有化部署,数据留在企业自己手里。
相关:精细化库存管理(WMS) · 常见问题 · 关于我们
更多库存管理文章:如何处理呆滞库存? · 如何协调多平台的库存和发货? · 如何从零建立完善的库存管理政策? · 如何实现实时动态库存管理? · 小企业做批发,进销存财务一体软件怎么选? · 小工厂仓库怎么管控? · 库存异常反映什么问题? · 第三方仓储计费:库存管理软件怎么选? · 预测准确率 90%,为什么库存还是乱?
你的库存,卡在「管不清」还是「改不动」?
把场景(品类、批次 / 效期要求、现在怎么管)发给我们,评估后给方案与周期。