feat: 重构BOM架构,新增基础设置与虚拟工位管理功能

本次重构删除原有BOM物料清单表,改用工单工艺组合×工艺物料清单作为唯一用料来源;新增系统基础设置表支持WMS地址、日志保留天数等配置,新增虚拟工位作业管理后台接口与前端页签控制功能,同时优化工位号校验逻辑、事件日志自动清理与工单用料查询能力。
This commit is contained in:
SunYF
2026-09-23 11:41:28 +08:00
parent d3eda6b588
commit f274da044c
74 changed files with 3637 additions and 5586 deletions
+11 -14
View File
@@ -446,9 +446,9 @@ func (s *Service) DoneWorkpiece(ctx context.Context, req DoneReq, operator strin
return nil
}
// backflushWorkpiece 反冲扣账:按工单产品整份 BOM 算「单件消耗(含损耗)」,
// backflushWorkpiece 反冲扣账:按工单工艺组合的单台用料(工艺物料清单汇总,含损耗)算「单件消耗」,
// 调 WMS 内部反冲接口 FIFO 扣库存 + 生成 backflush 出库单。
// 整份 BOM 一次性下发(完工即全部物料已消耗),WMS 不可达/校验失败仅记日志。
// 用料唯一来源=工单组合×flow_materialBOM 已删除);WMS 不可达/校验失败仅记日志。
func (s *Service) backflushWorkpiece(ctx context.Context, wp *ent.Workpiece, operator string) {
wo, err := s.ctx.EntClient.WorkOrder.Get(ctx, wp.WorkOrderId)
if err != nil || wo == nil {
@@ -456,19 +456,16 @@ func (s *Service) backflushWorkpiece(ctx context.Context, wp *ent.Workpiece, ope
"反冲失败:工单不存在", map[string]any{"workOrderId": wp.WorkOrderId})
return
}
bom, err := s.ListBom(ctx, wo.ProductCode, bomNameOrDefault(""))
if err != nil || len(bom) == 0 {
usage := s.WorkOrderUnitUsage(ctx, wo)
if len(usage) == 0 {
s.ctx.EventLog.Write(ctx, "wms.backflush.skip", wp.OrderNo, operator, "workpiece", wp.Sn,
"无BOM物料,跳过反冲", map[string]any{"productCode": wo.ProductCode})
"工单工艺组合无物料清单,跳过反冲", map[string]any{"orderNo": wp.OrderNo})
return
}
items := make([]wmsclient.BackflushItem, 0, len(bom))
for _, it := range bom {
if it.MaterialCode == "" {
continue
}
// 单件消耗 = 单台用量 ×(1+损耗率),四舍五入;≤0 跳过
qty := int(math.Round(it.UnitQty * (1 + it.LossRate/100)))
items := make([]wmsclient.BackflushItem, 0, len(usage))
for _, it := range usage {
// 单件消耗 = 单台含损耗用量,四舍五入;≤0 跳过
qty := int(math.Round(it.EffQty))
if qty <= 0 {
continue
}
@@ -479,11 +476,11 @@ func (s *Service) backflushWorkpiece(ctx context.Context, wp *ent.Workpiece, ope
}
if wmsErr := s.ctx.Wms.Backflush(ctx, wp.OrderNo, 0, 0, operator, items); wmsErr != nil {
s.ctx.EventLog.Write(ctx, "wms.backflush.failed", wp.OrderNo, operator, "workpiece", wp.Sn,
"反冲扣账WMS失败(库存未扣,请人工核查)", map[string]any{"error": wmsErr.Error(), "productCode": wo.ProductCode})
"反冲扣账WMS失败(库存未扣,请人工核查)", map[string]any{"error": wmsErr.Error(), "orderNo": wp.OrderNo})
return
}
s.ctx.EventLog.Write(ctx, "wms.backflush", wp.OrderNo, operator, "workpiece", wp.Sn,
"反冲扣账已下发WMS", map[string]any{"productCode": wo.ProductCode, "items": len(items)})
"反冲扣账已下发WMS", map[string]any{"orderNo": wp.OrderNo, "items": len(items)})
}
// retainFinishedToWms 完工品回流 WMS 库存。失败仅记日志(wms.finished.retain_failed),