Files
bj_power/bj_power_mes/internal/processor/replenisher.go
T

181 lines
4.8 KiB
Go
Raw Normal View History

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
}