Files
bj_power/bj_power_mes/internal/logic/material.go
T
SunYF e852c7cd37 chore: 批量完成项目多模块迭代优化
1. 废弃半成品库存表并统一库存主表
2. 修复列表排序与搜索大小写不敏感问题
3. 新增用户最近登录时间、工单BOM名称字段
4. 完善角色管理、工位选择器功能
5. 增加拧紧数据补录、成品自动回流WMS能力
6. 统一导出时间范围校验规则
7. 丰富物料/备料单筛选条件
8. 调整菜单与权限命名适配产品型号业务
2026-09-08 11:44:04 +08:00

155 lines
5.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 logic
import (
"context"
"errors"
"fmt"
"strings"
"time"
"bj_power_mes/ent"
"bj_power_mes/ent/materialrequest"
"bj_power_mes/ent/workorder"
)
// GenerateMaterialRequest 按日排产自动算料生成备料单
// 数量 = 该日所有工单计划数量 × 产品BOM单台用量×(1+损耗率),材料来源=该工单产品的 BOM
// 生成前校验:BOM 内所有物料编码须在 WMS 物料档案存在,有缺失返回 missing(不生成);
// WMS 不可达/异常时降级为"全部存在"并记 eventlog,避免 WMS 抖动阻塞产线。
// 返回值:count=本次生成条数;missing=WMS 中不存在的物料编码(非空时 count=0)。
func (s *Service) GenerateMaterialRequest(ctx context.Context, planDate string, operator string) (int, []string, error) {
plans, err := s.ListDailyPlans(ctx, "", planDate)
if err != nil {
return 0, nil, err
}
if len(plans) == 0 {
return 0, nil, errors.New("该日期没有排产计划")
}
type planBom struct {
plan *ent.DailyPlan
wo *ent.WorkOrder
bom []*ent.BomItem
}
groups := make([]planBom, 0, len(plans))
codeSet := map[string]bool{}
for _, plan := range plans {
if plan.Status == "CANCELLED" || plan.Status == "DONE" {
continue // 已取消/已完成的排产不再算料
}
// 解析工单 → 产品编码 → 该工单锁定的 BOM(建单时选定的 bomName
wo, err := s.ctx.EntClient.WorkOrder.Query().
Where(workorder.WorkOrderNo(plan.OrderNo)).Only(ctx)
if err != nil {
continue
}
bom, err := s.ListBom(ctx, wo.ProductCode, bomNameOrDefault(wo.BomName))
if err != nil {
continue
}
if len(bom) == 0 {
continue
}
for _, item := range bom {
if item.MaterialCode != "" {
codeSet[item.MaterialCode] = true
}
}
groups = append(groups, planBom{plan: plan, wo: wo, bom: bom})
}
if len(groups) == 0 {
return 0, nil, errors.New("该日期排产均无有效 BOM 物料,无法生成备料单")
}
// 阶段1:WMS 物料存在性校验(全部存在才生成)
codes := make([]string, 0, len(codeSet))
for c := range codeSet {
codes = append(codes, c)
}
missing, wmsErr := s.ctx.Wms.MaterialExists(ctx, codes)
if wmsErr != nil {
// 降级:WMS 不可达按"全部存在"放行,记日志供事后核查
s.ctx.EventLog.Write(ctx, "material.request.wms_skip", "", operator, "material_request", planDate,
"WMS 物料校验不可达,已降级放行", map[string]any{"planDate": planDate, "error": wmsErr.Error()})
} else if len(missing) > 0 {
return 0, missing, nil
}
// 阶段2:全部存在 → 算料生成
created := 0
// 备料单号基准:一次生成调用只取一次时间戳,序号全局递增,避免同一秒内多物料/多工单重复
tsBase := time.Now().Format("20060102150405")
seq := 1
for _, g := range groups {
for _, item := range g.bom {
reqQty := float64(g.plan.PlanQty) * item.UnitQty * (1 + item.LossRate/100)
if reqQty <= 0 {
continue
}
reqNo := fmt.Sprintf("MR%s%03d", tsBase, seq)
seq++
// 送达接驳台:取该排产确认的 dock_codes(可多选,逗号分隔),未确认则为空串待仓管下发时指定
target := strings.Join(g.plan.DockCodes, ",")
_ = 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).
SetTargetDock(target).Exec(ctx)
created++
}
}
s.ctx.EventLog.Write(ctx, "material.request.generate", "", operator, "material_request", planDate, "按日排产自动生成备料单", map[string]any{"planDate": planDate, "count": created})
s.notifyDashboard()
return created, nil, nil
}
// ListMaterialRequests 查询备料单
func (s *Service) ListMaterialRequests(ctx context.Context, orderNo, planDate, status string) ([]*ent.MaterialRequest, error) {
return s.listMaterialRequests(ctx, orderNo, planDate, status, "", "", "")
}
// ListMaterialRequestsFiltered 管理页丰富筛选入口
func (s *Service) ListMaterialRequestsFiltered(ctx context.Context, orderNo, planDate, status, materialCode, materialName, targetDock string) ([]*ent.MaterialRequest, error) {
return s.listMaterialRequests(ctx, orderNo, planDate, status, materialCode, materialName, targetDock)
}
// listMaterialRequests 备料单查询(2026-09-08 丰富筛选+大小写不敏感)
// materialCode/materialName/targetDock 可空;keyword 兜底匹配 备料单号/工单号/物料编码
func (s *Service) listMaterialRequests(ctx context.Context, orderNo, planDate, status, materialCode, materialName, targetDock string) ([]*ent.MaterialRequest, error) {
q := s.ctx.EntClient.MaterialRequest.Query()
if orderNo != "" {
q = q.Where(materialrequest.OrderNoEqualFold(orderNo))
}
if planDate != "" {
q = q.Where(materialrequest.PlanDate(planDate))
}
if status != "" {
q = q.Where(materialrequest.Status(status))
}
if materialCode != "" {
q = q.Where(materialrequest.MaterialCodeContainsFold(materialCode))
}
if materialName != "" {
q = q.Where(materialrequest.MaterialNameContainsFold(materialName))
}
if targetDock != "" {
q = q.Where(materialrequest.TargetDockEqualFold(targetDock))
}
return q.Order(ent.Desc(materialrequest.FieldCreatedAt), ent.Desc(materialrequest.FieldID)).All(ctx)
}
// SetMaterialRequestStatus 推进备料单状态(WMS 下发 AGV DELIVERING / 到位 DONE
func (s *Service) SetMaterialRequestStatus(ctx context.Context, requestNo, status string) error {
n, err := s.ctx.EntClient.MaterialRequest.Update().
Where(materialrequest.RequestNo(requestNo)).
SetStatus(status).
Save(ctx)
if err != nil {
return err
}
if n == 0 {
return errors.New("备料单不存在")
}
s.notifyDashboard()
return nil
}