refactor(wms): 合并库存表为统一inventory,重构业务逻辑

1. 合并inventory_batch与serial_number为统一inventory表,用manage_mode区分结构件批次/精密件SN
2. 移除冗余的material_type字段与相关逻辑
3. 重构库存查询、检验、入库出库等业务逻辑适配新表结构
4. 调整前端路由与菜单,拆分基础数据为区域维护和物料档案
5. 优化入库校验逻辑,避免空单问题
6. 调整Vite构建配置,关闭自动清空输出目录
7. 为各模块添加详细中文注释,统一业务术语
8. 生成并更新Ent自动代码文件
This commit is contained in:
SunYF
2026-09-02 08:21:11 +08:00
parent 47573e44b0
commit 68f50ee46e
70 changed files with 5895 additions and 9818 deletions
+3 -1
View File
@@ -6,7 +6,9 @@ import (
"entgo.io/ent/schema/index"
)
// InboundDetail 入库明细(SN 逐条
// InboundDetail 入库明细(一张入库单可含多行
// 结构件记录 batch_no,精密件记录 sn_code(逐件一行),quantity 冗余记录该行数量。
// 用于入库单正向追溯(查某入库单具体入了哪些批次/SN)。
type InboundDetail struct {
ent.Schema
}
+4 -4
View File
@@ -18,14 +18,14 @@ func (InboundOrder) Fields() []ent.Field {
field.String("inbound_type").Default("purchase").Comment("入库类型"),
field.String("material_code").Comment("物料编码"),
field.String("material_name").Optional().Comment("物料名称(冗余)"),
// 管理粒度 1批次/2序列号
field.Int("manage_mode").Default(1),
// 管理粒度 1批次(结构件)/2序列号(精密件),冗余自物料档案,便于单单据直接判断
field.Int("manage_mode").Default(1).Comment("管理粒度 1批次/2序列号"),
field.String("batch_no").Optional().Comment("批次号(结构件)"),
field.String("zone_code").Optional().Comment("入库区域"),
field.Int("quantity").Default(0).Comment("数量"),
field.Int("quantity").Default(0).Comment("本次入库总数量(批次合计或SN条数)"),
field.String("operator").Optional().Comment("操作人"),
field.String("remark").Optional().Comment("备注"),
field.Int64("created_at").DefaultFunc(nowUnix),
field.Int64("created_at").DefaultFunc(nowUnix).Comment("创建时间"),
}
}
+83
View File
@@ -0,0 +1,83 @@
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// Inventory 统一库存台账
//
// 业务背景:原系统把库存拆成「结构件批次(inventory_batch)」与「精密件序列号(serial_number)」
// 两张表,导致 锁库存 / 导出 / 汇总 都要分别写两套逻辑,运维成本高且容易不一致。
// 现合并为一张表,用 manage_mode 区分管理粒度,对外提供统一的 锁/查询/汇总/盘点 能力。
//
// 两种管理粒度的存储约定:
// - manage_mode = 1 结构件(批次管理):
// 一条记录 = 一个批次,quantity 表示该批次当前余量,locked_qty 表示已锁定件数。
// 可用量 = quantity - locked_qty。出库/盘点都按"数量"增减。
// - manage_mode = 2 精密件(SN 管理):
// 一条记录 = 一个单件(SN)quantity 恒为 1locked_qty 为 0 或 1(锁定时=1)。
// 生命周期用 status 字段跟踪:在库/锁定/出库/使用/报废。
// 可用量 = quantity - locked_qty(锁定时为 0)。
//
// 区域(zone_code)只到 Z01~Z04 大分区级别,不引入货架/层/格(业务决策:小货架高频取货场景无需精确货位索引)。
// 半成品(semifinished)是独立的固定业务类型,不进本表(由 MES 报工闭环单独管理)。
type Inventory struct {
ent.Schema
}
func (Inventory) Fields() []ent.Field {
return []ent.Field{
// 管理粒度:1=结构件(批次) 2=精密件(SN)。决定本行按"数量"还是按"单件"解释。
field.Int("manage_mode").Comment("管理粒度 1=结构件(批次) 2=精密件(SN)"),
// 物料编码,关联 materials 主数据。
field.String("material_code").Comment("物料编码(关联 materials 表)"),
// 物料名称冗余存储,查询时无需再 join 主数据。
field.String("material_name").Optional().Comment("物料名称(冗余,便于直接展示)"),
// 批次号:结构件必填(系统生成);精密件可填所属批次用于分组追溯。
field.String("batch_no").Optional().Comment("批次号(系统生成);精密件可关联所属批次"),
// 序列号:仅精密件填写,作为单件唯一追溯锚点。空字符串不保证唯一,由代码层查重。
field.String("sn_code").Optional().Comment("序列号(SN,仅精密件);空值表示非 SN 行"),
// 数量:结构件=批次余量;精密件恒为 1。
field.Int("quantity").Default(0).Comment("数量:结构件=批次余量;精密件=1"),
// 已锁定数量:结构件=锁定件数;精密件=0或1。可用量 = quantity - locked_qty。
field.Int("locked_qty").Default(0).Comment("已锁定数量(库存占用):结构件=件数;精密件=0/1"),
// 检验状态,由质量检验模块翻转。
field.String("quality_status").Default("未检").Comment("检验状态 未检/合格/不合格"),
// 所在区域(大分区)。
field.String("zone_code").Optional().Comment("所在区域(Z01~Z04 大分区)"),
// 生命周期状态:
// 结构件一律"在库"(锁定看 locked_qty,不靠本字段);
// 精密件随业务流转:在库/锁定/出库/使用/报废。
field.String("status").Default("在库").Comment("生命周期:在库/锁定/出库/使用/报废(精密件用;结构件恒在库)"),
// 生产日期(结构件批次用)。
field.String("production_date").Optional().Comment("生产日期"),
// 供应商(结构件批次用)。
field.String("supplier").Optional().Comment("供应商"),
// 关联入库单号,便于正向追溯。
field.String("inbound_no").Optional().Comment("关联入库单号"),
// 备注。
field.String("remark").Optional().Comment("备注"),
field.Int64("created_at").DefaultFunc(nowUnix),
field.Int64("updated_at").DefaultFunc(nowUnix),
}
}
func (Inventory) Indexes() []ent.Index {
return []ent.Index{
index.Fields("manage_mode"),
index.Fields("material_code"),
index.Fields("quality_status"),
index.Fields("zone_code"),
index.Fields("batch_no"),
index.Fields("sn_code"),
}
}
-37
View File
@@ -1,37 +0,0 @@
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// InventoryBatch 结构件批次台账
type InventoryBatch struct {
ent.Schema
}
func (InventoryBatch) Fields() []ent.Field {
return []ent.Field{
field.String("batch_no").Unique().Comment("批次号(系统自动生成:日期+自增)"),
field.String("material_code").Comment("物料编码"),
field.String("material_name").Optional().Comment("物料名称(冗余)"),
field.Int("quantity").Default(0).Comment("当前可用数量"),
field.Int("locked_qty").Default(0).Comment("已锁定数量"),
field.String("production_date").Optional().Comment("生产日期"),
field.String("supplier").Optional().Comment("供应商"),
field.String("quality_status").Default("未检").Comment("检验状态 未检/合格/不合格"),
field.String("zone_code").Optional().Comment("所在区域"),
field.String("remark").Optional().Comment("备注"),
field.Int64("created_at").DefaultFunc(nowUnix),
field.Int64("updated_at").DefaultFunc(nowUnix),
}
}
func (InventoryBatch) Indexes() []ent.Index {
return []ent.Index{
index.Fields("material_code"),
index.Fields("quality_status"),
index.Fields("zone_code"),
}
}
+7 -4
View File
@@ -6,8 +6,13 @@ import (
"entgo.io/ent/schema/index"
)
// Material 物料档案
// 精密件(manage_mode=2) 逐件 SN 管理;结构件(manage_mode=1) 批次管理
// Material 物料档案(主数据)
// 全系统物料的唯一定义来源,入库/出库/库存/检验都通过 material_code 关联本表。
// 管理粒度由 manage_mode 决定,贯穿整个库存链路:
// - manage_mode=1 结构件:按"批次"管理,一次到货入一批,库存以 quantity 计数;
// - manage_mode=2 精密件:按"SN 序列号"逐件管理,库存以单件行计数,可单件追溯。
// 注意:物料档案只描述"这个物料是什么、怎么管",不承载生命周期状态
// (原材料→半成品→成品的阶段由各自库存实体/inventory 表承载,不在此预判)。
type Material struct {
ent.Schema
}
@@ -24,8 +29,6 @@ func (Material) Fields() []ent.Field {
field.Int("manage_mode").Default(1).Comment("管理粒度 1批次/2序列号"),
field.Bool("is_batch_managed").Default(false).Comment("是否批次管理"),
field.Bool("is_serial_managed").Default(false).Comment("是否序列号管理"),
// 物料类型:raw(原材料) semi(半成品) finished(成品)
field.String("material_type").Default("raw").Comment("物料总状态 raw/semi/finished"),
// 所属物料模板(种类)
field.String("template_code").Optional().Comment("物料模板编码(物料种类)"),
field.Int64("created_at").DefaultFunc(nowUnix).Comment("创建时间"),
+3 -1
View File
@@ -6,7 +6,9 @@ import (
"entgo.io/ent/schema/index"
)
// OutboundDetail 出库明细
// OutboundDetail 出库明细(一张出库单可含多行)
// 与 InboundDetail 对称,记录本次出库涉及的具体批次/SN 与数量,
// 是库存账与单据账闭环核对的关键凭证。
type OutboundDetail struct {
ent.Schema
}
+3 -3
View File
@@ -20,15 +20,15 @@ func (OutboundOrder) Fields() []ent.Field {
field.String("order_no").Optional().Comment("关联工单号(工单为唯一挂靠载体)"),
field.String("material_code").Comment("物料编码"),
field.String("material_name").Optional().Comment("物料名称(冗余)"),
field.Int("manage_mode").Default(1),
field.Int("manage_mode").Default(1).Comment("管理粒度 1批次/2序列号"),
field.String("batch_no").Optional().Comment("批次号(结构件)"),
field.String("zone_code").Optional().Comment("出库区域"),
field.Int("quantity").Default(0).Comment("数量"),
field.Int("quantity").Default(0).Comment("本次出库总数量"),
field.String("operator").Optional().Comment("操作人"),
field.String("reviewer").Optional().Comment("复核人"),
field.String("target_dock").Optional().Comment("目标接驳台(AGV配送)"),
field.String("remark").Optional().Comment("备注"),
field.Int64("created_at").DefaultFunc(nowUnix),
field.Int64("created_at").DefaultFunc(nowUnix).Comment("创建时间"),
}
}
+4 -1
View File
@@ -6,7 +6,10 @@ import (
"entgo.io/ent/schema/index"
)
// PackageBox 包装箱 ↔ SN 关联
// PackageBox 包装箱 ↔ SN 关联(装箱管理)
// 将一个或多个精密件 SN 绑定到一个箱号,便于整箱出入库与追溯。
// sn_list 以字符串存多个 SN(逗号分隔 / JSON),不强制外键关联 inventory,
// 因为装箱是业务封装动作,箱内 SN 仍各自在 inventory 表中独立流转。
type PackageBox struct {
ent.Schema
}
-37
View File
@@ -1,37 +0,0 @@
package schema
import (
"entgo.io/ent"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
// SerialNumber 精密件/成品 单件管理
type SerialNumber struct {
ent.Schema
}
func (SerialNumber) Fields() []ent.Field {
return []ent.Field{
field.String("sn_code").Unique().Comment("序列号"),
field.String("material_code").Optional().Comment("物料编码(可为空)"),
field.String("batch_no").Optional().Comment("所属批次"),
// 状态:在库/锁定/出库/使用/报废
field.String("status").Default("在库").Comment("在库/锁定/出库/使用/报废"),
field.String("current_zone").Optional().Comment("当前区域"),
field.String("quality_status").Default("未检").Comment("检验状态 未检/合格/不合格"),
field.String("last_inbound_at").Optional().Comment("最近入库时间"),
field.String("last_outbound_at").Optional().Comment("最近出库时间"),
field.String("remark").Optional().Comment("备注"),
field.Int64("created_at").DefaultFunc(nowUnix),
field.Int64("updated_at").DefaultFunc(nowUnix),
}
}
func (SerialNumber) Indexes() []ent.Index {
return []ent.Index{
index.Fields("material_code"),
index.Fields("status"),
index.Fields("batch_no"),
}
}
+1 -1
View File
@@ -38,7 +38,7 @@ func (StocktakeItem) Fields() []ent.Field {
field.String("stocktake_no").Comment("所属盘点单号"),
field.String("target_type").Comment("BATCH/SN"),
field.String("target_id").Comment("批次号或SN码"),
field.String("material_code").Optional(),
field.String("material_code").Optional().Comment("物料编码(冗余,便于盘点差异按物料归类)"),
field.String("zone_code").Optional().Comment("所在区域"),
field.Int("book_qty").Default(0).Comment("账面数量"),
field.Int("scanned_qty").Default(-1).Comment("实盘数量,-1=未盘"),
+3 -1
View File
@@ -6,7 +6,9 @@ import (
"entgo.io/ent/schema/index"
)
// User 用户
// User 系统用户(账号与权限)
// 角色 role 控制功能权限:admin(管理员) / operator(库房保管员) / inspector(质检员)。
// 密码以 bcrypt 哈希存储,登录走 JWT。
type User struct {
ent.Schema
}
+4 -2
View File
@@ -5,8 +5,10 @@ import (
"entgo.io/ent/schema/field"
)
// Zone 库房区域
// 待检/合格/不合格/退货区 等
// Zone 库房区域(大分区)
// 仅作为"大分区"维度(如 待检区/标准存储区/暂存区/退货区),编码 Z01~Z04。
// 业务决策:小货架高频取货场景无需"货架-层-格"精确货位,系统管到区域即可,
// 货架内部由仓管经验定位,不给现场增加录入负担。库存(inventory)只关联 zone_code。
type Zone struct {
ent.Schema
}