refactor: 重构产线工位与备料流程,移除冗余字段
1. 移除工单、工件、日排产中的冗余工位组合/接驳台字段 2. 新增工位接驳台编码字段与唯一约束,关联WMS接驳台主数据 3. 重构日排产为工位产量分配模式,替代原接驳台列表 4. 新增备料单工位级字段与分批出库支持 5. 移除定时同步可生产数量,改为WMS实时拉取接口 6. 过滤操作日志,排除登录/退出相关日志 7. 调整仪表盘工序展示逻辑为今日派工路线 8. 新增接驳台维护菜单与基础数据
This commit is contained in:
@@ -3,6 +3,7 @@ package logic
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -17,6 +18,7 @@ import (
|
||||
"bj_power_mes/ent/workorder"
|
||||
"bj_power_mes/ent/workpiece"
|
||||
"bj_power_mes/ent/workpieceprocess"
|
||||
"bj_power_mes/internal/wmsclient"
|
||||
)
|
||||
|
||||
func itoa(n int) string { return strconv.Itoa(n) }
|
||||
@@ -68,30 +70,22 @@ type OnlineReq struct {
|
||||
Sn string `json:"sn"`
|
||||
OrderNo string `json:"orderNo"`
|
||||
WorkOrderId int `json:"workOrderId"`
|
||||
ProcessSeq string `json:"processSeq"`
|
||||
}
|
||||
|
||||
// OnlineWorkpiece 工件进线登记
|
||||
// 路线由「工位派工(station_process)」按日派生,工件不再冗余存储路线串
|
||||
func (s *Service) OnlineWorkpiece(ctx context.Context, req OnlineReq, operator string) error {
|
||||
if req.Sn == "" {
|
||||
return errors.New("sn 不能为空")
|
||||
}
|
||||
seq := req.ProcessSeq
|
||||
if seq == "" {
|
||||
seq = FullProcessSeq
|
||||
}
|
||||
if req.WorkOrderId == 0 {
|
||||
if wo, err := s.ctx.EntClient.WorkOrder.Query().Where(workorder.WorkOrderNo(req.OrderNo)).First(ctx); err == nil {
|
||||
req.WorkOrderId = wo.ID
|
||||
if req.ProcessSeq == "" && wo.ProcessSeq != "" {
|
||||
seq = wo.ProcessSeq
|
||||
}
|
||||
}
|
||||
}
|
||||
seq = NormalizeProcessSeq(seq)
|
||||
err := s.ctx.EntClient.Workpiece.Create().
|
||||
SetSn(req.Sn).SetOrderNo(req.OrderNo).SetWorkOrderId(req.WorkOrderId).
|
||||
SetProcessSeq(seq).SetStatus("ONLINE").SetOnlineAt(time.Now()).Exec(ctx)
|
||||
SetStatus("ONLINE").SetOnlineAt(time.Now()).Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -328,8 +322,9 @@ func (s *Service) DoneWorkpiece(ctx context.Context, req DoneReq, operator strin
|
||||
_, _ = s.ctx.EntClient.AssociationTrace.UpdateOneID(existID).
|
||||
SetBatchItems(req.BatchItems).SetSerialItems(req.SerialItems).SetOperator(operator).Save(ctx)
|
||||
} else {
|
||||
routeStr, _ := RouteStationsStr(ctx, s.ctx.EntClient, TodayStr(), "")
|
||||
_, err = s.ctx.EntClient.AssociationTrace.Create().
|
||||
SetFinishedSn(req.Sn).SetOrderNo(wp.OrderNo).SetProcessCode(wp.ProcessSeq).
|
||||
SetFinishedSn(req.Sn).SetOrderNo(wp.OrderNo).SetProcessCode(routeStr).
|
||||
SetBatchItems(req.BatchItems).SetSerialItems(req.SerialItems).
|
||||
SetOperator(operator).Save(ctx)
|
||||
if err != nil {
|
||||
@@ -343,10 +338,53 @@ func (s *Service) DoneWorkpiece(ctx context.Context, req DoneReq, operator strin
|
||||
// 成品 SN = 工件 SN(进线登记即系统发号,全程唯一,保证对号可追溯)。
|
||||
// WMS 不可达/校验失败时降级:记 eventlog 不阻塞完工,与备料 WMS 校验降级策略同模式。
|
||||
s.retainFinishedToWms(ctx, wp, operator)
|
||||
// 反冲扣账:物料在当道工序被消耗,完工即按整份 BOM 反冲扣 WMS 库存。
|
||||
// 失败仅记 eventlog 不阻塞完工(与成品回流同降级策略)。
|
||||
s.backflushWorkpiece(ctx, wp, operator)
|
||||
s.notifyDashboard()
|
||||
return nil
|
||||
}
|
||||
|
||||
// backflushWorkpiece 反冲扣账:按工单产品整份 BOM 算「单件消耗(含损耗)」,
|
||||
// 调 WMS 内部反冲接口 FIFO 扣库存 + 生成 backflush 出库单。
|
||||
// 整份 BOM 一次性下发(完工即全部物料已消耗),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 {
|
||||
s.ctx.EventLog.Write(ctx, "wms.backflush.failed", wp.OrderNo, operator, "workpiece", wp.Sn,
|
||||
"反冲失败:工单不存在", map[string]any{"workOrderId": wp.WorkOrderId})
|
||||
return
|
||||
}
|
||||
bom, err := s.ListBom(ctx, wo.ProductCode, bomNameOrDefault(""))
|
||||
if err != nil || len(bom) == 0 {
|
||||
s.ctx.EventLog.Write(ctx, "wms.backflush.skip", wp.OrderNo, operator, "workpiece", wp.Sn,
|
||||
"无BOM物料,跳过反冲", map[string]any{"productCode": wo.ProductCode})
|
||||
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)))
|
||||
if qty <= 0 {
|
||||
continue
|
||||
}
|
||||
items = append(items, wmsclient.BackflushItem{MaterialCode: it.MaterialCode, Qty: qty})
|
||||
}
|
||||
if len(items) == 0 {
|
||||
return
|
||||
}
|
||||
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})
|
||||
return
|
||||
}
|
||||
s.ctx.EventLog.Write(ctx, "wms.backflush", wp.OrderNo, operator, "workpiece", wp.Sn,
|
||||
"反冲扣账已下发WMS", map[string]any{"productCode": wo.ProductCode, "items": len(items)})
|
||||
}
|
||||
|
||||
// retainFinishedToWms 完工品回流 WMS 库存。失败仅记日志(wms.finished.retain_failed),
|
||||
// 不回滚完工状态——仓管可在 WMS 侧人工补录,避免 WMS 抖动卡死产线。
|
||||
func (s *Service) retainFinishedToWms(ctx context.Context, wp *ent.Workpiece, operator string) {
|
||||
@@ -433,10 +471,8 @@ func (s *Service) WorkpiecesInProcess(ctx context.Context, stationNo int) ([]InP
|
||||
}
|
||||
out := make([]InProcessItem, 0, len(list))
|
||||
for _, wp := range list {
|
||||
seq := ParseProcessSeq(wp.ProcessSeq)
|
||||
if len(seq) == 0 {
|
||||
seq = ParseProcessSeq(FullProcessSeq)
|
||||
}
|
||||
// 路线由今日工位派工(station_process)派生;未派工则无路线(maxStation=0)
|
||||
seq, _ := RouteStations(ctx, s.ctx.EntClient, TodayStr(), "")
|
||||
maxStation := 0
|
||||
for _, c := range seq {
|
||||
if c > maxStation {
|
||||
|
||||
Reference in New Issue
Block a user