fix: rebuild MES as compilable go-zero+ent backend (renamed bj_power_mes), restore 3 workstation projects from pristine original, align naming; all Go projects go build clean
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
|
||||
"bj_power_mes/constants"
|
||||
"bj_power_mes/ent"
|
||||
"bj_power_mes/ent/job"
|
||||
"bj_power_mes/ent/producttype"
|
||||
"bj_power_mes/ent/workorder"
|
||||
"bj_power_mes/internal/processor/actor"
|
||||
)
|
||||
|
||||
// Replenisher 暂存台补料编排器。配额计算 + 选料,物理搬运由 RobotWorker 执行。
|
||||
type Replenisher struct {
|
||||
entClient *ent.Client
|
||||
tempStore *actor.TempStoreActor
|
||||
|
||||
replenishing atomic.Bool
|
||||
}
|
||||
|
||||
// NewReplenisher 创建暂存台补料编排器
|
||||
func NewReplenisher(entClient *ent.Client, tempStore *actor.TempStoreActor) *Replenisher {
|
||||
return &Replenisher{
|
||||
entClient: entClient,
|
||||
tempStore: tempStore,
|
||||
}
|
||||
}
|
||||
|
||||
// IsReplenishing 返回当前是否正在执行补料操作
|
||||
func (r *Replenisher) IsReplenishing() bool {
|
||||
return r.replenishing.Load()
|
||||
}
|
||||
|
||||
// SetReplenishing 设置补料互斥标志
|
||||
func (r *Replenisher) SetReplenishing(on bool) {
|
||||
r.replenishing.Store(on)
|
||||
}
|
||||
|
||||
// --- 硬编码暂存台配额规则 ---
|
||||
|
||||
// activeCategories 返回当前活跃工单(有 Created job 在 dock 上)的 category 列表
|
||||
func (r *Replenisher) activeCategories(ctx context.Context) []string {
|
||||
cats, _ := r.dockCategories(ctx)
|
||||
return cats
|
||||
}
|
||||
|
||||
// computeQuotas 根据活跃 category 组合计算配额。8 个暂存台位置按以下规则分配:
|
||||
//
|
||||
// A+B+C: 3:3:2 A+B: 4:4 A+C: 5:3 B+C: 5:3 仅一种: 8
|
||||
func computeQuotas(activeCats []string) map[string]int {
|
||||
sort.Strings(activeCats)
|
||||
key := ""
|
||||
for i, c := range activeCats {
|
||||
if i > 0 {
|
||||
key += "+"
|
||||
}
|
||||
key += c
|
||||
}
|
||||
switch key {
|
||||
case "A6VM107+A6VM160+A6VM200":
|
||||
return map[string]int{"A6VM107": 3, "A6VM160": 3, "A6VM200": 2}
|
||||
case "A6VM107+A6VM160":
|
||||
return map[string]int{"A6VM107": 4, "A6VM160": 4}
|
||||
case "A6VM107+A6VM200":
|
||||
return map[string]int{"A6VM107": 5, "A6VM200": 3}
|
||||
case "A6VM160+A6VM200":
|
||||
return map[string]int{"A6VM160": 5, "A6VM200": 3}
|
||||
default:
|
||||
if len(activeCats) == 1 {
|
||||
return map[string]int{activeCats[0]: 8}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// BuildReplenishCandidate 检查是否需要补料,返回一个 Dock 上的待补料工件。
|
||||
// 触发条件:某 category 暂存台占用 ≤ quota/2。
|
||||
// 返回 nil 表示无需补料或暂存台已满。
|
||||
func (r *Replenisher) BuildReplenishCandidate(ctx context.Context) *ReplenishItem {
|
||||
if r == nil || r.entClient == nil {
|
||||
return nil
|
||||
}
|
||||
activeCats := r.activeCategories(ctx)
|
||||
if len(activeCats) == 0 {
|
||||
return nil
|
||||
}
|
||||
quotas := computeQuotas(activeCats)
|
||||
if quotas == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 找出需要补料的 category
|
||||
// 条件:1) 未满(occ < quota) 2) 已半满或正在补料中(occ ≤ quota/2 或 replenishing)
|
||||
var needRefill string
|
||||
for cat, quota := range quotas {
|
||||
occ := r.categoryOccupied(ctx, cat)
|
||||
if occ >= quota {
|
||||
continue // 已满
|
||||
}
|
||||
if !r.replenishing.Load() && occ > quota/2 {
|
||||
continue // 有空位但未到半满,等消耗更多再补
|
||||
}
|
||||
needRefill = cat
|
||||
break
|
||||
}
|
||||
if needRefill == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 从 Dock 上找该 category 的 Created 工件(仅已启动的工单)
|
||||
jbs, err := r.entClient.Job.Query().
|
||||
Where(
|
||||
job.StatusEQ(constants.JobStatus_Created),
|
||||
job.PositionTypeEQ(constants.PositionType_OnDock),
|
||||
job.HasProductTypeWith(producttype.CategoryEQ(producttype.Category(needRefill))),
|
||||
job.HasWorkOrderWith(workorder.StatusIn(
|
||||
constants.WorkOrderStatus_InProgress,
|
||||
constants.WorkOrderStatus_Paused,
|
||||
)),
|
||||
).
|
||||
Order(job.ByDockNo(), job.ByDockSlotNo()).
|
||||
Limit(1).
|
||||
All(ctx)
|
||||
if err != nil || len(jbs) == 0 {
|
||||
return nil
|
||||
}
|
||||
j := jbs[0]
|
||||
if j.DockNo == nil || j.DockSlotNo == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ReplenishItem{
|
||||
JobID: j.ID,
|
||||
DockNo: *j.DockNo,
|
||||
DockSlotNo: *j.DockSlotNo,
|
||||
ProductTypeID: j.ProductTypeId,
|
||||
}
|
||||
}
|
||||
|
||||
// dockCategories 返回 dock 上 Created 工件所属的 category 列表(去重)
|
||||
func (r *Replenisher) dockCategories(ctx context.Context) ([]string, error) {
|
||||
pts, err := r.entClient.ProductType.Query().
|
||||
Where(producttype.HasJobsWith(
|
||||
job.StatusIn(constants.JobStatus_Created, constants.JobStatus_Suspended),
|
||||
job.PositionTypeEQ(constants.PositionType_OnDock),
|
||||
job.HasWorkOrderWith(workorder.StatusIn(
|
||||
constants.WorkOrderStatus_InProgress,
|
||||
constants.WorkOrderStatus_Paused,
|
||||
)),
|
||||
)).
|
||||
All(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seen := make(map[string]bool)
|
||||
var cats []string
|
||||
for _, pt := range pts {
|
||||
cat := string(pt.Category)
|
||||
if !seen[cat] {
|
||||
seen[cat] = true
|
||||
cats = append(cats, cat)
|
||||
}
|
||||
}
|
||||
return cats, nil
|
||||
}
|
||||
|
||||
// categoryOccupied 返回指定 category 当前占用暂存台的槽位数
|
||||
func (r *Replenisher) categoryOccupied(ctx context.Context, category string) int {
|
||||
count, _ := r.entClient.Job.Query().
|
||||
Where(
|
||||
job.StatusNotIn(constants.JobStatus_Completed, constants.JobStatus_Scrapped),
|
||||
job.TempSlotNoNotNil(),
|
||||
job.HasProductTypeWith(producttype.CategoryEQ(producttype.Category(category))),
|
||||
).
|
||||
Count(ctx)
|
||||
return count
|
||||
}
|
||||
Reference in New Issue
Block a user