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

181 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}