发布于2026-07-04 阅读(0)
扫一扫,手机访问
先说核心结论:用 ThinkPHP 实现 ERP 进销存库存联动,关键不是框架本身有多强,而是把“业务动作触发库存实时变更”这个逻辑拆清楚。说白了,采购入库、销售出库、退货、调拨……这些操作不能简单地在商品表里加加减减,必须设计一套统一的库存事务模型——每笔操作都记成一条库存流水(stock_log),同时驱动当前库存表(stock_current)自动更新。真正的难点在于:如何让业务逻辑和库存逻辑解耦,又能保证数据一致性。

别犯最常见的错误:直接在商品表里存一个 quantity 字段。并发一上来,写冲突、状态不一致全来了。正确的做法是拆成“流水 + 快照”双表:
in_purchase、销售出库 out_sale、退货入库 in_return),还要带上业务单据 ID(business_id)、操作人 ID(operator_id)、备注。主键自增,记得给 goods_id + created_at 建索引。goods_id、warehouse_id、quantity、freeze_quantity(冻结量,用于已下单但未发货的场景)。唯一约束就是 goods_id + warehouse_id。所有涉及库存变动的地方,必须走同一个服务方法,而且全程用数据库事务包住。ThinkPHP 8.x 推荐直接用 Db::transaction(),别指望模型事件偷偷帮你扣减——一旦出问题,查都查不出来。
stock_current.quantity - stock_current.freeze_quantity 是否 ≥ 要扣的数量。stock_log 记录,把 type、delta(变动量,正号入库负号出库)、business_id 等字段填全。stock_current。推荐用 MySQL 的 ON DUPLICATE KEY UPDATE,或者 ThinkPHP 里写原生 UPDATE 加 Db::raw('quantity + '.$delta)。举个例子:Db::table('stock_current')->update(['quantity' => Db::raw('quantity + '.$delta)])->where(['goods_id'=>$gid, 'warehouse_id'=>$wid]);采购单、销售单、调拨单各自独立建模,只负责自己的业务逻辑:审批流、客户/供应商关联、金额计算。它们提交成功后,再调用统一的库存服务去处理库存变动。
StockService::recordOutbound($saleOrderId, $items)。$items 里的每个商品,独立执行一次库存事务——这里不要批量一条 SQL 搞定,要支持部分失败回滚。如果某个商品库存不够,整个事务回滚,前面的也不生效。StockService::revertOutbound($saleOrderId),根据原流水记录,生成一条 type=in_sale_refund 的新流水,反向更新库存。整个过程也是事务包裹。高并发场景下,光靠数据库事务还不够,必须加行锁或者状态锁。
Db::execute("UPDATE stock_current SET quantity = quantity - ? WHERE goods_id = ? AND warehouse_id = ? AND quantity >= ?", [$num, $gid, $wid, $num]);stock_lock:{$gid}:{$wid}),但只作为兜底,不能替代数据库行锁。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8