feat: 完成产线与仓储系统多模块迭代升级
本次迭代覆盖MES与WMS核心业务: 1. 新增接驳台托盘传感器读取与AGV对接能力 2. 完善工单排产、备料流程与权限体系拆分 3. 优化看板接口与前端路由、样式 4. 新增操作日志、库存盘点与角色保护逻辑 5. 修复代理地址、BOM保存等已知问题
This commit is contained in:
@@ -3,6 +3,8 @@ package logic
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"bj_power_mes/ent"
|
||||
"bj_power_mes/ent/dailyplan"
|
||||
@@ -24,7 +26,7 @@ type WorkOrderReq struct {
|
||||
PlanEnd string `json:"planEnd"`
|
||||
}
|
||||
|
||||
// CreateWorkOrder 创建工单
|
||||
// CreateWorkOrder 创建工单(状态恒 CREATED,后续用状态流转按钮推进)
|
||||
func (s *Service) CreateWorkOrder(ctx context.Context, req WorkOrderReq, operator string) error {
|
||||
if req.WorkOrderNo == "" {
|
||||
return errors.New("工单号不能为空")
|
||||
@@ -39,6 +41,9 @@ func (s *Service) CreateWorkOrder(ctx context.Context, req WorkOrderReq, operato
|
||||
if status == "" {
|
||||
status = "CREATED"
|
||||
}
|
||||
if status != "CREATED" {
|
||||
status = "CREATED" // 创建恒为已创建;不允许直接创建到后续状态
|
||||
}
|
||||
if req.ProcessSeq == "" {
|
||||
req.ProcessSeq = FullProcessSeq
|
||||
} else if err := ValidateProcessSeq(req.ProcessSeq); err != nil {
|
||||
@@ -70,10 +75,18 @@ func (s *Service) CreateWorkOrder(ctx context.Context, req WorkOrderReq, operato
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateWorkOrder 编辑工单。仅「已创建」状态允许编辑(已下发/执行中/已暂停只能流转,已完成/已取消只读)。
|
||||
func (s *Service) UpdateWorkOrder(ctx context.Context, req WorkOrderReq, operator string) error {
|
||||
if req.Id == 0 {
|
||||
return errors.New("缺少 id")
|
||||
}
|
||||
old, err := s.ctx.EntClient.WorkOrder.Get(ctx, req.Id)
|
||||
if err != nil {
|
||||
return errors.New("工单不存在")
|
||||
}
|
||||
if old.Status != "CREATED" && old.Status != "PENDING" {
|
||||
return errors.New("仅「已创建」状态的工单可编辑;生产中或已结束的工单只能做状态流转")
|
||||
}
|
||||
if req.ProcessSeq != "" {
|
||||
if err := ValidateProcessSeq(req.ProcessSeq); err != nil {
|
||||
return err
|
||||
@@ -81,9 +94,6 @@ func (s *Service) UpdateWorkOrder(ctx context.Context, req WorkOrderReq, operato
|
||||
req.ProcessSeq = NormalizeProcessSeq(req.ProcessSeq)
|
||||
}
|
||||
u := s.ctx.EntClient.WorkOrder.UpdateOneID(req.Id).SetProcessSeq(req.ProcessSeq)
|
||||
if req.Status != "" {
|
||||
u.SetStatus(req.Status)
|
||||
}
|
||||
if req.Quantity > 0 {
|
||||
u.SetQuantity(req.Quantity)
|
||||
}
|
||||
@@ -108,7 +118,7 @@ func (s *Service) UpdateWorkOrder(ctx context.Context, req WorkOrderReq, operato
|
||||
}
|
||||
wo, err := s.ctx.EntClient.WorkOrder.Get(ctx, req.Id)
|
||||
if err == nil {
|
||||
s.ctx.EventLog.Write(ctx, "work.order.update", wo.WorkOrderNo, operator, "work_order", wo.WorkOrderNo, "更新工单", map[string]any{"status": req.Status})
|
||||
s.ctx.EventLog.Write(ctx, "work.order.update", wo.WorkOrderNo, operator, "work_order", wo.WorkOrderNo, "更新工单", nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -138,32 +148,77 @@ func (s *Service) GetWorkOrder(ctx context.Context, id int) (*ent.WorkOrder, err
|
||||
return s.ctx.EntClient.WorkOrder.Query().Order(ent.Desc(workorder.FieldID)).First(ctx)
|
||||
}
|
||||
|
||||
// 工单状态白名单
|
||||
// 工单状态白名单(统一枚举:已创建/已下发/执行中/已暂停/已完成/已取消)
|
||||
var workOrderStatuses = map[string]bool{
|
||||
"CREATED": true, "RELEASED": true, "IN_PROGRESS": true, "DONE": true,
|
||||
"PENDING": true, "CONFIRMED": true,
|
||||
"CREATED": true, "RELEASED": true, "IN_PROGRESS": true,
|
||||
"PAUSED": true, "DONE": true, "CANCELLED": true,
|
||||
}
|
||||
|
||||
// SetWorkOrderStatus 工单状态流转(发布/开工/完工等),操作列快捷操作。
|
||||
func (s *Service) SetWorkOrderStatus(ctx context.Context, id int, status, operator string) error {
|
||||
// workOrderTransitions 工单状态流转表:from → {to...}
|
||||
// CREATED 可下发/取消;RELEASED 可开工/取消;IN_PROGRESS 可暂停/完工;
|
||||
// PAUSED 可恢复/取消;DONE/CANCELLED 为终态,不再流转。
|
||||
var workOrderTransitions = map[string]map[string]bool{
|
||||
"CREATED": {"RELEASED": true, "CANCELLED": true},
|
||||
"RELEASED": {"IN_PROGRESS": true, "CANCELLED": true},
|
||||
"IN_PROGRESS": {"PAUSED": true, "DONE": true},
|
||||
"PAUSED": {"IN_PROGRESS": true, "CANCELLED": true},
|
||||
"DONE": {},
|
||||
"CANCELLED": {},
|
||||
// 兼容存量旧枚举:PENDING(待排产)≈已创建可发布,CONFIRMED(已确认)≈已下发可开工
|
||||
"PENDING": {"RELEASED": true, "CANCELLED": true},
|
||||
"CONFIRMED": {"IN_PROGRESS": true, "CANCELLED": true},
|
||||
}
|
||||
|
||||
// SetWorkOrderStatus 工单状态流转(发布/开工/暂停/恢复/完工/取消)。
|
||||
// 校验流转合法性;写 eventlog 记录 原状态/新状态/原因;终态联动处理(见 cancelPlansOfOrder)。
|
||||
func (s *Service) SetWorkOrderStatus(ctx context.Context, id int, status, reason, operator string) error {
|
||||
if id <= 0 {
|
||||
return errors.New("缺少 id")
|
||||
}
|
||||
if !workOrderStatuses[status] {
|
||||
return errors.New("非法的工单状态")
|
||||
}
|
||||
if err := s.ctx.EntClient.WorkOrder.UpdateOneID(id).SetStatus(status).Exec(ctx); err != nil {
|
||||
wo, err := s.ctx.EntClient.WorkOrder.Get(ctx, id)
|
||||
if err != nil {
|
||||
return errors.New("工单不存在")
|
||||
}
|
||||
if wo.Status == status {
|
||||
return nil // 幂等:状态相同直接成功
|
||||
}
|
||||
if !workOrderTransitions[wo.Status][status] {
|
||||
return fmt.Errorf("不允许从「%s」变更为「%s」", statusTextCN(wo.Status), statusTextCN(status))
|
||||
}
|
||||
upd := s.ctx.EntClient.WorkOrder.UpdateOneID(id).SetStatus(status)
|
||||
if status == "DONE" {
|
||||
now := time.Now()
|
||||
upd.SetCompletedAt(now)
|
||||
}
|
||||
if err := upd.Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if wo, err := s.ctx.EntClient.WorkOrder.Get(ctx, id); err == nil {
|
||||
s.ctx.EventLog.Write(ctx, "work.order.status", wo.WorkOrderNo, operator, "work_order", wo.WorkOrderNo, "变更工单状态", map[string]any{"status": status})
|
||||
}
|
||||
// 状态变更操作日志:原状态/新状态/原因
|
||||
s.ctx.EventLog.Write(ctx, "work.order.status", wo.WorkOrderNo, operator, "work_order", wo.WorkOrderNo, "变更工单状态",
|
||||
map[string]any{"from": wo.Status, "to": status, "reason": reason})
|
||||
// 联动:DONE/CANCELLED → 该工单未完成排产批量取消;IN_PROGRESS → 排产置执行中
|
||||
s.syncPlansByOrderStatus(ctx, wo.WorkOrderNo, status, operator)
|
||||
return nil
|
||||
}
|
||||
|
||||
// statusTextCN 状态枚举中文文案
|
||||
func statusTextCN(s string) string {
|
||||
m := map[string]string{
|
||||
"CREATED": "已创建", "RELEASED": "已下发", "IN_PROGRESS": "执行中",
|
||||
"PAUSED": "已暂停", "DONE": "已完成", "CANCELLED": "已取消",
|
||||
}
|
||||
if v, ok := m[s]; ok {
|
||||
return v
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// DeleteWorkOrder 删除工单。
|
||||
// 安全条件:① 状态仅限 已创建/待排产/已发布(未开始生产);② 无日排产记录;
|
||||
// ③ 未生成备料单;④ 无任何工件生产数据。
|
||||
// 安全条件:① 状态仅限 已创建(CREATED,兼容存量 PENDING/CONFIRMED 未开始生产);② 无日排产记录;
|
||||
// ③ 未生成备料单;④ 无任何工件生产数据。已下发/执行中/已暂停/已完成/已取消一律不可删除。
|
||||
func (s *Service) DeleteWorkOrder(ctx context.Context, id int, operator string) error {
|
||||
if id <= 0 {
|
||||
return errors.New("缺少 id")
|
||||
@@ -172,8 +227,8 @@ func (s *Service) DeleteWorkOrder(ctx context.Context, id int, operator string)
|
||||
if err != nil {
|
||||
return errors.New("工单不存在")
|
||||
}
|
||||
if wo.Status != "CREATED" && wo.Status != "PENDING" && wo.Status != "RELEASED" {
|
||||
return errors.New("仅「已创建 / 待排产 / 已发布」状态的工单可删除,生产中或已完成的工单不能删除")
|
||||
if wo.Status != "CREATED" && wo.Status != "PENDING" && wo.Status != "CONFIRMED" {
|
||||
return errors.New("仅「已创建」状态的工单可删除;已下发、执行中或已结束的工单不能删除")
|
||||
}
|
||||
if n, err := s.ctx.EntClient.DailyPlan.Query().Where(dailyplan.OrderNo(wo.WorkOrderNo)).Count(ctx); err == nil && n > 0 {
|
||||
return errors.New("该工单已有日排产记录,请先删除对应排产后再删除工单")
|
||||
|
||||
Reference in New Issue
Block a user