feat: 完成产线与仓储系统多模块迭代升级
本次迭代覆盖MES与WMS核心业务: 1. 新增接驳台托盘传感器读取与AGV对接能力 2. 完善工单排产、备料流程与权限体系拆分 3. 优化看板接口与前端路由、样式 4. 新增操作日志、库存盘点与角色保护逻辑 5. 修复代理地址、BOM保存等已知问题
This commit is contained in:
@@ -3,6 +3,8 @@ package logic
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"bj_power_mes/ent"
|
||||
@@ -19,15 +21,17 @@ func parseDate(v string) (time.Time, error) {
|
||||
}
|
||||
|
||||
type DailyPlanReq struct {
|
||||
Id int `json:"id"`
|
||||
OrderNo string `json:"orderNo"`
|
||||
PlanDate string `json:"planDate"`
|
||||
PlanQty int `json:"planQty"`
|
||||
CompletedQty int `json:"completedQty"`
|
||||
Status string `json:"status"`
|
||||
Id int `json:"id"`
|
||||
OrderNo string `json:"orderNo"`
|
||||
PlanDate string `json:"planDate"`
|
||||
PlanQty int `json:"planQty"`
|
||||
CompletedQty int `json:"completedQty"`
|
||||
Status string `json:"status"`
|
||||
DockCodes []string `json:"dockCodes"`
|
||||
}
|
||||
|
||||
// SaveDailyPlan 创建/更新日排产(同一 工单+日期 唯一)
|
||||
// 排产状态枚举统一为:PENDING(待执行)/PROCESSING(执行中)/DONE(已完成)/CANCELLED(已取消)
|
||||
func (s *Service) SaveDailyPlan(ctx context.Context, req DailyPlanReq, operator string) error {
|
||||
if req.OrderNo == "" || req.PlanDate == "" {
|
||||
return errors.New("工单号和排产日期必填")
|
||||
@@ -37,22 +41,24 @@ func (s *Service) SaveDailyPlan(ctx context.Context, req DailyPlanReq, operator
|
||||
if err != nil {
|
||||
return errors.New("工单不存在")
|
||||
}
|
||||
if wo.Status == "DONE" || wo.Status == "CLOSED" {
|
||||
return errors.New("该工单已完成/关闭,不能排产")
|
||||
if wo.Status == "DONE" || wo.Status == "CANCELLED" {
|
||||
return errors.New("该工单已完成或已取消,不能排产")
|
||||
}
|
||||
if wo.Status == "PAUSED" {
|
||||
return errors.New("该工单已暂停,恢复执行前不能新增排产")
|
||||
}
|
||||
cur, err := s.ctx.EntClient.DailyPlan.Query().
|
||||
Where(dailyplan.OrderNo(req.OrderNo)).All(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 计算合计:同「工单+日期」的记录无论原状态如何,都以本次新值替换(只算一次),避免旧值+新值重复累计
|
||||
total := 0
|
||||
// 计算合计:本次 req.PlanQty 恒计入;若「工单+日期」已有记录,则原值作废只算新值(避免旧值+新值重复累计)
|
||||
total := req.PlanQty
|
||||
for _, p := range cur {
|
||||
if p.PlanDate == req.PlanDate {
|
||||
if p.CompletedQty > 0 {
|
||||
return errors.New("该排产日已有完工数量,不能修改计划数量")
|
||||
}
|
||||
total += req.PlanQty
|
||||
continue
|
||||
}
|
||||
total += p.PlanQty
|
||||
@@ -63,27 +69,139 @@ func (s *Service) SaveDailyPlan(ctx context.Context, req DailyPlanReq, operator
|
||||
exist, err := s.ctx.EntClient.DailyPlan.Query().
|
||||
Where(dailyplan.OrderNo(req.OrderNo), dailyplan.PlanDate(req.PlanDate)).First(ctx)
|
||||
status := req.Status
|
||||
if status == "" {
|
||||
status = "PENDING"
|
||||
switch status {
|
||||
case "", "PENDING", "PROCESSING", "DONE", "CANCELLED":
|
||||
if status == "" {
|
||||
status = "PENDING"
|
||||
}
|
||||
default:
|
||||
return errors.New("非法的排产状态")
|
||||
}
|
||||
if err == nil && exist != nil {
|
||||
_, err = s.ctx.EntClient.DailyPlan.UpdateOneID(exist.ID).
|
||||
SetOrderNo(req.OrderNo).SetPlanDate(req.PlanDate).SetPlanQty(req.PlanQty).SetStatus(status).Save(ctx)
|
||||
SetOrderNo(req.OrderNo).SetPlanDate(req.PlanDate).SetPlanQty(req.PlanQty).SetStatus(status).
|
||||
SetDockCodes(dockCodes(req.DockCodes)).Save(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
_, err = s.ctx.EntClient.DailyPlan.Create().
|
||||
SetOrderNo(req.OrderNo).SetPlanDate(req.PlanDate).SetPlanQty(req.PlanQty).
|
||||
SetStatus(status).SetOperator(operator).Save(ctx)
|
||||
SetStatus(status).SetOperator(operator).SetDockCodes(dockCodes(req.DockCodes)).Save(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
s.ctx.EventLog.Write(ctx, "daily.plan.save", req.OrderNo, operator, "daily_plan", req.OrderNo, "保存日排产", map[string]any{"planDate": req.PlanDate, "planQty": req.PlanQty})
|
||||
// 记录该工单本次使用的接驳台到工单 context,供下次排产默认推荐
|
||||
if len(dockCodes(req.DockCodes)) > 0 {
|
||||
s.rememberOrderDocks(ctx, req.OrderNo, dockCodes(req.DockCodes))
|
||||
}
|
||||
s.ctx.EventLog.Write(ctx, "daily.plan.save", req.OrderNo, operator, "daily_plan", req.OrderNo, "保存日排产", map[string]any{"planDate": req.PlanDate, "planQty": req.PlanQty, "dockCodes": req.DockCodes})
|
||||
return nil
|
||||
}
|
||||
|
||||
// dockCodes 规范化接驳台列表:仅保留 DOCK01..DOCK20 / DOCK21,去重、保持顺序
|
||||
func dockCodes(codes []string) []string {
|
||||
seen := map[string]bool{}
|
||||
out := []string{}
|
||||
for _, c := range codes {
|
||||
c = strings.TrimSpace(c)
|
||||
if !validDockCode(c) || seen[c] {
|
||||
continue
|
||||
}
|
||||
seen[c] = true
|
||||
out = append(out, c)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// validDockCode DOCK01..DOCK21
|
||||
func validDockCode(c string) bool {
|
||||
if len(c) != 6 || !strings.HasPrefix(c, "DOCK") {
|
||||
return false
|
||||
}
|
||||
n, err := strconv.Atoi(c[4:])
|
||||
return err == nil && n >= 1 && n <= 21
|
||||
}
|
||||
|
||||
// rememberOrderDocks 把工单「上次使用的接驳台」写入 work_order.context,供下次排产默认推荐
|
||||
func (s *Service) rememberOrderDocks(ctx context.Context, orderNo string, docks []string) {
|
||||
if len(docks) == 0 {
|
||||
return
|
||||
}
|
||||
wo, err := s.ctx.EntClient.WorkOrder.Query().Where(workorder.WorkOrderNo(orderNo)).Only(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c := wo.Context
|
||||
if c == nil {
|
||||
c = map[string]any{}
|
||||
}
|
||||
c["lastDockCodes"] = docks
|
||||
_ = s.ctx.EntClient.WorkOrder.UpdateOneID(wo.ID).SetContext(c).Exec(ctx)
|
||||
}
|
||||
|
||||
// SuggestedDockCodes 查某工单「上次使用的接驳台」,供排产默认推荐
|
||||
func (s *Service) SuggestedDockCodes(ctx context.Context, orderNo string) []string {
|
||||
wo, err := s.ctx.EntClient.WorkOrder.Query().Where(workorder.WorkOrderNo(orderNo)).Only(ctx)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if v, ok := wo.Context["lastDockCodes"].([]any); ok {
|
||||
out := []string{}
|
||||
for _, x := range v {
|
||||
if s2, ok := x.(string); ok {
|
||||
out = append(out, s2)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// syncPlansByOrderStatus 工单状态联动日排产:
|
||||
// - 工单 → IN_PROGRESS:该工单 PENDING 排产置 PROCESSING(开始执行)
|
||||
// - 工单 → DONE:已足额完工(completedQty>=planQty)的排产置 DONE;其余未完成排产置 CANCELLED
|
||||
// - 工单 → CANCELLED:该工单未完成(PENDING/PROCESSING)排产全部置 CANCELLED
|
||||
func (s *Service) syncPlansByOrderStatus(ctx context.Context, orderNo, woStatus, operator string) {
|
||||
if orderNo == "" {
|
||||
return
|
||||
}
|
||||
plans, err := s.ctx.EntClient.DailyPlan.Query().
|
||||
Where(dailyplan.OrderNo(orderNo), dailyplan.StatusIn("PENDING", "PROCESSING")).All(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
changed := 0
|
||||
to := ""
|
||||
for _, p := range plans {
|
||||
switch woStatus {
|
||||
case "IN_PROGRESS":
|
||||
to = "PROCESSING"
|
||||
case "DONE":
|
||||
if p.CompletedQty >= p.PlanQty {
|
||||
to = "DONE"
|
||||
} else {
|
||||
to = "CANCELLED"
|
||||
}
|
||||
case "CANCELLED":
|
||||
to = "CANCELLED"
|
||||
default:
|
||||
return
|
||||
}
|
||||
if p.Status == to {
|
||||
continue
|
||||
}
|
||||
if _, err := s.ctx.EntClient.DailyPlan.UpdateOneID(p.ID).SetStatus(to).Save(ctx); err == nil {
|
||||
changed++
|
||||
}
|
||||
}
|
||||
if changed > 0 {
|
||||
s.ctx.EventLog.Write(ctx, "daily.plan.status_sync", orderNo, operator, "daily_plan", orderNo,
|
||||
"工单状态联动排产", map[string]any{"woStatus": woStatus, "changed": changed})
|
||||
}
|
||||
}
|
||||
|
||||
// ListDailyPlans 按工单/日期查询日排产
|
||||
func (s *Service) ListDailyPlans(ctx context.Context, orderNo, planDate string) ([]*ent.DailyPlan, error) {
|
||||
q := s.ctx.EntClient.DailyPlan.Query()
|
||||
|
||||
Reference in New Issue
Block a user