refactor: 重构工艺工序相关命名与数据模型
1. 全局替换"工序"为"工艺"统一术语,包括页面文案、枚举、注释
2. 重构工单与工件工艺数据模型:
- 新增工艺组合表flow_material作为备料/齐套唯一源头
- 新增工位状态表station_state支持自主停单/恢复接单
- 移除station_process派工表,改用工单工艺组合作为路线唯一源头
- 替换processCode为flowId作为工艺关联标识
- 重构工件当前进度字段为currentStationNo
3. 删除冗余的静态备份资源文件
4. 调整物料选择组件默认启用仅显示激活物料
5. 优化工单创建/编辑逻辑,新增工艺组合校验与保存
This commit is contained in:
@@ -11,23 +11,24 @@ import (
|
||||
"time"
|
||||
|
||||
"bj_power_mes/ent"
|
||||
"bj_power_mes/ent/flowmaterial"
|
||||
"bj_power_mes/ent/materialrequest"
|
||||
"bj_power_mes/ent/station"
|
||||
"bj_power_mes/ent/workorder"
|
||||
"bj_power_mes/internal/wmsclient"
|
||||
)
|
||||
|
||||
// GenerateMaterialRequest 按日排产 + 今日工位派工(station_process) 自动算料,生成工位级备料单。
|
||||
// GenerateMaterialRequest 按日排产 + 工单工艺组合 + 工艺物料清单(flow_material) 自动算料,生成工位级备料单。
|
||||
//
|
||||
// 算法:
|
||||
// - 路线来源:今日 station_process 派工(工位号→工序编号)。某工序(1~12)今日派工到哪些工位,
|
||||
// 该工序的 BOM 物料就拆到这些工位各生成一行备料单(物料随工序上料)。
|
||||
// - 数量:单工位量 = 该工位分配产量(日排产 stationPlanQty,未配置则日总量平均到各派工工位)
|
||||
// × BOM 单台用量 ×(1+损耗率);同工序多工位时日总量按分配/平均拆分,不重复放大。
|
||||
// - 目标接驳台:取该派工工位的 station.dockCode(无接驳台则留空待仓管指定)。
|
||||
// - processCode=0 的 BOM 物料(不参与工位绑定):按日总量生成一行,工位/接驳台留空。
|
||||
// 算法(2026-09-22 生产模型定稿):
|
||||
// - 路线来源:工单 flow_combination([{flowId,stationNo}],工位号即顺序号)。无组合的工单跳过并记日志。
|
||||
// - 物料来源:组合条目涉及工艺的 flow_material(工艺物料清单 ⊆ 产品 BOM)。
|
||||
// - 数量:备料 = 组合条目 × flow_material × 分产量——各工位量取日排产 stationPlanQty
|
||||
// (全有值且和>0 时采用,否则同工艺工位平均拆、余数补前位);reqQty = 分产量 × 单台用量 ×(1+损耗率)。
|
||||
// - 目标接驳台:取 station.dock_code(无接驳台则留空待仓管指定)。
|
||||
// - BOM 中未被任何涉及工艺引用的物料:记 eventlog(bom.unassigned) 告警,不阻断生成。
|
||||
//
|
||||
// 生成前校验:BOM 内所有物料编码须在 WMS 物料档案存在,有缺失返回 missing(不生成);
|
||||
// 生成前校验:涉及物料编码须在 WMS 物料档案存在,有缺失返回 missing(不生成);
|
||||
// WMS 不可达/异常时降级为"全部存在"并记 eventlog,避免 WMS 抖动阻塞产线。
|
||||
// 返回值:count=本次生成条数;missing=WMS 中不存在的物料编码(非空时 count=0)。
|
||||
func (s *Service) GenerateMaterialRequest(ctx context.Context, planDate string, operator string) (int, []string, error) {
|
||||
@@ -38,35 +39,13 @@ func (s *Service) GenerateMaterialRequest(ctx context.Context, planDate string,
|
||||
if len(plans) == 0 {
|
||||
return 0, nil, errors.New("该日期没有排产计划")
|
||||
}
|
||||
// 今日工位派工:工位号 → 工序编号
|
||||
procMap, err := RouteStationProcessCodeMap(ctx, s.ctx.EntClient, TodayStr(), "")
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
type planGroup struct {
|
||||
plan *ent.DailyPlan
|
||||
wo *ent.WorkOrder
|
||||
items []FlowItem // 工单工艺组合(工位号升序)
|
||||
fmByFlow map[int][]*ent.FlowMaterial // 工艺ID → 工艺物料清单
|
||||
}
|
||||
if len(procMap) == 0 {
|
||||
return 0, nil, errors.New("今日未配置工位派工(station_process),无法生成备料单")
|
||||
}
|
||||
// 工序编号 → 工位号列表(反查,供 BOM 物料按工序拆到工位)
|
||||
stationsByProcess := map[int][]int{}
|
||||
stNos := make([]int, 0, len(procMap))
|
||||
for stNo, pc := range procMap {
|
||||
stationsByProcess[pc] = append(stationsByProcess[pc], stNo)
|
||||
stNos = append(stNos, stNo)
|
||||
}
|
||||
// 工位号 → 接驳台编码
|
||||
dockByStation := map[int]string{}
|
||||
if sts, e := s.ctx.EntClient.Station.Query().Where(station.StationNoIn(stNos...)).All(ctx); e == nil {
|
||||
for _, st := range sts {
|
||||
dockByStation[st.StationNo] = st.DockCode
|
||||
}
|
||||
}
|
||||
|
||||
type planBom struct {
|
||||
plan *ent.DailyPlan
|
||||
wo *ent.WorkOrder
|
||||
bom []*ent.BomItem
|
||||
}
|
||||
groups := make([]planBom, 0, len(plans))
|
||||
groups := make([]planGroup, 0, len(plans))
|
||||
codeSet := map[string]bool{}
|
||||
for _, plan := range plans {
|
||||
if plan.Status == "CANCELLED" || plan.Status == "DONE" {
|
||||
@@ -77,22 +56,53 @@ func (s *Service) GenerateMaterialRequest(ctx context.Context, planDate string,
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
bom, err := s.ListBom(ctx, wo.ProductCode, bomNameOrDefault(""))
|
||||
if err != nil {
|
||||
items, _ := RouteStationsFromWO(ctx, s.ctx.EntClient, wo)
|
||||
if len(items) == 0 {
|
||||
s.ctx.EventLog.Write(ctx, "material.request.skip", plan.OrderNo, operator, "material_request", plan.OrderNo,
|
||||
"工单未配置工艺组合,本日跳过备料", map[string]any{"planDate": plan.PlanDate})
|
||||
continue
|
||||
}
|
||||
if len(bom) == 0 {
|
||||
continue
|
||||
}
|
||||
for _, item := range bom {
|
||||
if item.MaterialCode != "" {
|
||||
codeSet[item.MaterialCode] = true
|
||||
g := planGroup{plan: plan, wo: wo, items: items, fmByFlow: map[int][]*ent.FlowMaterial{}}
|
||||
for _, it := range items {
|
||||
if _, done := g.fmByFlow[it.FlowId]; done {
|
||||
continue
|
||||
}
|
||||
fms, err := s.ctx.EntClient.FlowMaterial.Query().
|
||||
Where(flowmaterial.FlowId(it.FlowId)).All(ctx)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
g.fmByFlow[it.FlowId] = fms
|
||||
for _, fm := range fms {
|
||||
if fm.MaterialCode != "" {
|
||||
codeSet[fm.MaterialCode] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
groups = append(groups, planBom{plan: plan, wo: wo, bom: bom})
|
||||
// BOM 物料未被任何涉及工艺引用 → 告警不阻断(工艺物料清单 ⊆ 产品 BOM 的兜底提醒)
|
||||
if bom, e := s.ListBom(ctx, wo.ProductCode, bomNameOrDefault("")); e == nil {
|
||||
covered := map[string]bool{}
|
||||
for _, fms := range g.fmByFlow {
|
||||
for _, fm := range fms {
|
||||
covered[fm.MaterialCode] = true
|
||||
}
|
||||
}
|
||||
un := []string{}
|
||||
for _, b := range bom {
|
||||
if b.MaterialCode != "" && !covered[b.MaterialCode] {
|
||||
un = append(un, b.MaterialCode)
|
||||
}
|
||||
}
|
||||
if len(un) > 0 {
|
||||
s.ctx.EventLog.Write(ctx, "bom.unassigned", plan.OrderNo, operator, "product_bom", wo.ProductCode,
|
||||
"BOM 物料未配置在任何涉及工艺的物料清单,本次备料未覆盖",
|
||||
map[string]any{"materials": un})
|
||||
}
|
||||
}
|
||||
groups = append(groups, g)
|
||||
}
|
||||
if len(groups) == 0 {
|
||||
return 0, nil, errors.New("该日期排产均无有效 BOM 物料,无法生成备料单")
|
||||
return 0, nil, errors.New("该日期排产均无有效工单工艺组合,无法生成备料单")
|
||||
}
|
||||
// 阶段1:WMS 物料存在性校验(全部存在才生成)
|
||||
codes := make([]string, 0, len(codeSet))
|
||||
@@ -107,7 +117,7 @@ func (s *Service) GenerateMaterialRequest(ctx context.Context, planDate string,
|
||||
} else if len(missing) > 0 {
|
||||
return 0, missing, nil
|
||||
}
|
||||
// 阶段2:全部存在 → 按工位拆料生成
|
||||
// 阶段2:全部存在 → 按 组合条目 × flow_material × 分产量 拆料生成
|
||||
created := 0
|
||||
tsBase := time.Now().Format("20060102150405")
|
||||
seq := 1
|
||||
@@ -119,53 +129,53 @@ func (s *Service) GenerateMaterialRequest(ctx context.Context, planDate string,
|
||||
stationQty[n] = v
|
||||
}
|
||||
}
|
||||
// 成品可作零部件:BOM 子项若为成品,递归展开到叶子(原材料/外购件)再算料
|
||||
leaves := s.expandBomToLeaf(ctx, g.wo.ProductCode, bomNameOrDefault(""), 1, 0, map[string]bool{})
|
||||
for _, item := range leaves {
|
||||
pc := item.processCode // 该物料所属装配工序(1~12),0=不参与工位绑定
|
||||
if pc == 0 {
|
||||
reqQty := float64(g.plan.PlanQty) * item.unitQty * (1 + item.lossRate/100)
|
||||
if reqQty <= 0 {
|
||||
continue
|
||||
}
|
||||
reqNo := fmt.Sprintf("MR%s%03d", tsBase, seq)
|
||||
seq++
|
||||
_ = s.ctx.EntClient.MaterialRequest.Create().
|
||||
SetRequestNo(reqNo).SetOrderNo(g.plan.OrderNo).SetPlanDate(g.plan.PlanDate).
|
||||
SetMaterialCode(item.materialCode).SetMaterialName(item.materialName).
|
||||
SetUnit(item.unit).SetManageMode(item.manageMode).SetReqQty(reqQty).
|
||||
SetStatus("PENDING").SetOperator(operator).Exec(ctx)
|
||||
created++
|
||||
continue
|
||||
// 工艺ID → 该工艺涉及的全部工位(组合内升序)
|
||||
stationsByFlow := map[int][]int{}
|
||||
for _, it := range g.items {
|
||||
stationsByFlow[it.FlowId] = append(stationsByFlow[it.FlowId], it.StationNo)
|
||||
}
|
||||
// 工位号 → 接驳台编码
|
||||
dockByStation := map[int]string{}
|
||||
if sts, e := s.ctx.EntClient.Station.Query().
|
||||
Where(station.StationNoIn(RouteStationsOfWO(g.items)...)).All(ctx); e == nil {
|
||||
for _, st := range sts {
|
||||
dockByStation[st.StationNo] = st.DockCode
|
||||
}
|
||||
stations := stationsByProcess[pc]
|
||||
if len(stations) == 0 {
|
||||
// 该工序今日未派工到任何工位,本日无需上料(非缺料)
|
||||
continue
|
||||
}
|
||||
for flowId, stations := range stationsByFlow {
|
||||
fms := g.fmByFlow[flowId]
|
||||
if len(fms) == 0 {
|
||||
continue // 该工艺未配置物料清单 → 无料可备
|
||||
}
|
||||
// 分产量:stationPlanQty 全有值且和>0 直接采用,否则平均拆、余数补前位
|
||||
qtys := splitQtyToStations(stations, g.plan.PlanQty, stationQty)
|
||||
for i, stNo := range stations {
|
||||
q := qtys[i]
|
||||
if q <= 0 {
|
||||
for _, fm := range fms {
|
||||
if fm.MaterialCode == "" {
|
||||
continue
|
||||
}
|
||||
reqQty := float64(q) * item.unitQty * (1 + item.lossRate/100)
|
||||
if reqQty <= 0 {
|
||||
continue
|
||||
for i, stNo := range stations {
|
||||
q := qtys[i]
|
||||
if q <= 0 {
|
||||
continue
|
||||
}
|
||||
reqQty := float64(q) * fm.UnitQty * (1 + fm.LossRate/100)
|
||||
if reqQty <= 0 {
|
||||
continue
|
||||
}
|
||||
reqNo := fmt.Sprintf("MR%s%03d", tsBase, seq)
|
||||
seq++
|
||||
_ = s.ctx.EntClient.MaterialRequest.Create().
|
||||
SetRequestNo(reqNo).SetOrderNo(g.plan.OrderNo).SetPlanDate(g.plan.PlanDate).
|
||||
SetMaterialCode(fm.MaterialCode).SetMaterialName(fm.MaterialName).
|
||||
SetUnit(fm.Unit).SetManageMode(fm.ManageMode).SetReqQty(reqQty).
|
||||
SetStatus("PENDING").SetOperator(operator).
|
||||
SetStationNo(stNo).SetFlowId(flowId).SetTargetDock(dockByStation[stNo]).Exec(ctx)
|
||||
created++
|
||||
}
|
||||
reqNo := fmt.Sprintf("MR%s%03d", tsBase, seq)
|
||||
seq++
|
||||
_ = s.ctx.EntClient.MaterialRequest.Create().
|
||||
SetRequestNo(reqNo).SetOrderNo(g.plan.OrderNo).SetPlanDate(g.plan.PlanDate).
|
||||
SetMaterialCode(item.materialCode).SetMaterialName(item.materialName).
|
||||
SetUnit(item.unit).SetManageMode(item.manageMode).SetReqQty(reqQty).
|
||||
SetStatus("PENDING").SetOperator(operator).
|
||||
SetStationNo(stNo).SetProcessCode(pc).SetTargetDock(dockByStation[stNo]).Exec(ctx)
|
||||
created++
|
||||
}
|
||||
}
|
||||
}
|
||||
s.ctx.EventLog.Write(ctx, "material.request.generate", "", operator, "material_request", planDate, "按日排产+工位派工自动生成工位级备料单", map[string]any{"planDate": planDate, "count": created})
|
||||
s.ctx.EventLog.Write(ctx, "material.request.generate", "", operator, "material_request", planDate, "按日排产+工单工艺组合+工艺物料清单生成工位级备料单", map[string]any{"planDate": planDate, "count": created})
|
||||
// 需求10:把每个工单的「工单级需求总量」同步给 WMS 台账(幂等,失败降级不阻塞)。
|
||||
// 必须先同步 total_qty,后续自动出库才能在台账上累加 out_qty 并判定缺口/领料完结。
|
||||
synced := map[string]bool{}
|
||||
@@ -360,7 +370,6 @@ type bomLeaf struct {
|
||||
manageMode string
|
||||
unitQty float64 // 已折算父级倍率的单台用量
|
||||
lossRate float64
|
||||
processCode int
|
||||
}
|
||||
|
||||
// expandBomToLeaf 递归展开产品物料清单到叶子(原材料/外购件),供备料算料。
|
||||
|
||||
Reference in New Issue
Block a user