feat: 完成产线与仓储系统多模块迭代升级

本次迭代覆盖MES与WMS核心业务:
1. 新增接驳台托盘传感器读取与AGV对接能力
2. 完善工单排产、备料流程与权限体系拆分
3. 优化看板接口与前端路由、样式
4. 新增操作日志、库存盘点与角色保护逻辑
5. 修复代理地址、BOM保存等已知问题
This commit is contained in:
SunYF
2026-09-04 14:18:53 +08:00
parent c1fc6d32b2
commit b6799c9de4
138 changed files with 20613 additions and 2220 deletions
+72 -9
View File
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"time"
"bj_power_mes/ent"
@@ -13,16 +14,28 @@ import (
// GenerateMaterialRequest 按日排产自动算料生成备料单
// 数量 = 该日所有工单计划数量 × 产品BOM单台用量×(1+损耗率),材料来源=该工单产品的 BOM
func (s *Service) GenerateMaterialRequest(ctx context.Context, planDate string, operator string) (int, error) {
// 生成前校验:BOM 内所有物料编码须在 WMS 物料档案存在,有缺失返回 missing(不生成);
// WMS 不可达/异常时降级为"全部存在"并记 eventlog,避免 WMS 抖动阻塞产线。
// 返回值:count=本次生成条数;missing=WMS 中不存在的物料编码(非空时 count=0)。
func (s *Service) GenerateMaterialRequest(ctx context.Context, planDate string, operator string) (int, []string, error) {
plans, err := s.ListDailyPlans(ctx, "", planDate)
if err != nil {
return 0, err
return 0, nil, err
}
if len(plans) == 0 {
return 0, errors.New("该日期没有排产计划")
return 0, nil, errors.New("该日期没有排产计划")
}
created := 0
type planBom struct {
plan *ent.DailyPlan
wo *ent.WorkOrder
bom []*ent.BomItem
}
groups := make([]planBom, 0, len(plans))
codeSet := map[string]bool{}
for _, plan := range plans {
if plan.Status == "CANCELLED" || plan.Status == "DONE" {
continue // 已取消/已完成的排产不再算料
}
// 解析工单 → 产品编码 → 该产品 BOM
wo, err := s.ctx.EntClient.WorkOrder.Query().
Where(workorder.WorkOrderNo(plan.OrderNo)).Only(ctx)
@@ -33,23 +46,58 @@ func (s *Service) GenerateMaterialRequest(ctx context.Context, planDate string,
if err != nil {
continue
}
if len(bom) == 0 {
continue
}
for _, item := range bom {
reqQty := float64(plan.PlanQty) * item.UnitQty * (1 + item.LossRate/100)
if item.MaterialCode != "" {
codeSet[item.MaterialCode] = true
}
}
groups = append(groups, planBom{plan: plan, wo: wo, bom: bom})
}
if len(groups) == 0 {
return 0, nil, errors.New("该日期排产均无有效 BOM 物料,无法生成备料单")
}
// 阶段1:WMS 物料存在性校验(全部存在才生成)
codes := make([]string, 0, len(codeSet))
for c := range codeSet {
codes = append(codes, c)
}
missing, wmsErr := s.ctx.Wms.MaterialExists(ctx, codes)
if wmsErr != nil {
// 降级:WMS 不可达按"全部存在"放行,记日志供事后核查
s.ctx.EventLog.Write(ctx, "material.request.wms_skip", "", operator, "material_request", planDate,
"WMS 物料校验不可达,已降级放行", map[string]any{"planDate": planDate, "error": wmsErr.Error()})
} else if len(missing) > 0 {
return 0, missing, nil
}
// 阶段2:全部存在 → 算料生成
created := 0
// 备料单号基准:一次生成调用只取一次时间戳,序号全局递增,避免同一秒内多物料/多工单重复
tsBase := time.Now().Format("20060102150405")
seq := 1
for _, g := range groups {
for _, item := range g.bom {
reqQty := float64(g.plan.PlanQty) * item.UnitQty * (1 + item.LossRate/100)
if reqQty <= 0 {
continue
}
reqNo := fmt.Sprintf("MR%s%03d", time.Now().Format("20060102150405"), len(bom))
reqNo := fmt.Sprintf("MR%s%03d", tsBase, seq)
seq++
// 送达接驳台:取该排产确认的 dock_codes(可多选,逗号分隔),未确认则为空串待仓管下发时指定
target := strings.Join(g.plan.DockCodes, ",")
_ = s.ctx.EntClient.MaterialRequest.Create().
SetRequestNo(reqNo).SetOrderNo(plan.OrderNo).SetPlanDate(plan.PlanDate).
SetRequestNo(reqNo).SetOrderNo(g.plan.OrderNo).SetPlanDate(g.plan.PlanDate).
SetMaterialCode(item.MaterialCode).SetMaterialName(item.MaterialName).
SetUnit(item.Unit).SetManageMode(item.ManageMode).SetReqQty(reqQty).
SetStatus("PENDING").SetOperator(operator).
SetTargetDock("").Exec(ctx)
SetTargetDock(target).Exec(ctx)
created++
}
}
s.ctx.EventLog.Write(ctx, "material.request.generate", "", operator, "material_request", planDate, "按日排产自动生成备料单", map[string]any{"planDate": planDate, "count": created})
return created, nil
return created, nil, nil
}
// ListMaterialRequests 查询备料单
@@ -66,3 +114,18 @@ func (s *Service) ListMaterialRequests(ctx context.Context, orderNo, planDate, s
}
return q.Order(materialrequest.ByID()).All(ctx)
}
// SetMaterialRequestStatus 推进备料单状态(WMS 下发 AGV DELIVERING / 到位 DONE
func (s *Service) SetMaterialRequestStatus(ctx context.Context, requestNo, status string) error {
n, err := s.ctx.EntClient.MaterialRequest.Update().
Where(materialrequest.RequestNo(requestNo)).
SetStatus(status).
Save(ctx)
if err != nil {
return err
}
if n == 0 {
return errors.New("备料单不存在")
}
return nil
}