本次提交覆盖多个业务模块的功能完善与体验优化:
1. **鉴权与配置调整**:
- 统一JWT滑动续签逻辑,简化Token存储,移除RefreshToken相关冗余代码
- 调整多项目配置文件中JWT过期时间为3600秒,统一会话闲置窗口
- 工位配置放开1~12限制,改为仅校验大于0
2. **术语统一替换**:全链路将"精密件"替换为"电气件",修正物料管理描述
3. **功能新增**:
- 新增工位类型、工艺路线与产线点位台账模块
- 添加工艺PDF预览面板、工位终端代理转发接口
- 新增操作日志按操作人列表筛选、工位登出日志记录
- 新增PLC移料指令与产线点位状态管理
4. **业务流程优化**:
- 调整BOM物料删除校验逻辑,优化工单备料计算
- 补充物料图号、检测单号等追溯字段
- 完善工艺流程图与工位绑定关系说明
- 优化前端页面文案与交互细节
5. **代码规范与维护**:
- 新增通用工具函数与前端静态资源
- 整理路由权限与中间件逻辑
- 修复部分接口与配置的不兼容问题
148 lines
5.5 KiB
Go
148 lines
5.5 KiB
Go
package logic
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"bj_power_mes/ent"
|
|
"bj_power_mes/ent/bomitem"
|
|
"bj_power_mes/ent/workorder"
|
|
)
|
|
|
|
// 默认BOMName 同一型号多份 BOM 并存时的缺省名称(历史数据/未指定时统一回填此值)
|
|
const DefaultBOMName = "默认"
|
|
|
|
type BomItemReq struct {
|
|
ProductCode string `json:"productCode"`
|
|
BomName string `json:"bomName"`
|
|
MaterialCode string `json:"materialCode"`
|
|
MaterialName string `json:"materialName"`
|
|
Spec string `json:"spec"`
|
|
Unit string `json:"unit"`
|
|
ManageMode string `json:"manageMode"`
|
|
ProcessCode *int `json:"processCode"` // 装配工序 1~12;nil=不修改(兼容旧调用/移除时保留);0=显式清除
|
|
UnitQty float64 `json:"unitQty"`
|
|
LossRate float64 `json:"lossRate"`
|
|
RequiredQty float64 `json:"requiredQty"`
|
|
SerialSns []string `json:"serialSns"`
|
|
}
|
|
|
|
// bomNameOrDefault 空 BOM 名回退为「默认」(兼容旧前端/旧数据)
|
|
func bomNameOrDefault(name string) string {
|
|
if name == "" {
|
|
return DefaultBOMName
|
|
}
|
|
return name
|
|
}
|
|
|
|
// SaveBom 保存产品 BOM(覆盖式 upsert:唯一键 = 产品编码+BOM名称+物料编码)。
|
|
// 同一产品型号可并存多份 BOM(不同厂家供货/含半成品等不同料单结构),按 bomName 区分,
|
|
// 工单建单时选定一份;备料/装机绑定均按工单锁定的 bomName 计算。
|
|
func (s *Service) SaveBom(ctx context.Context, productCode, bomName string, items []BomItemReq, operator string) error {
|
|
bomName = bomNameOrDefault(bomName)
|
|
for _, it := range items {
|
|
if it.ManageMode == "" {
|
|
it.ManageMode = "2"
|
|
}
|
|
exist, err := s.ctx.EntClient.BomItem.Query().
|
|
Where(bomitem.ProductCode(productCode), bomitem.BomName(bomName), bomitem.MaterialCode(it.MaterialCode)).First(ctx)
|
|
if err == nil && exist != nil {
|
|
u := s.ctx.EntClient.BomItem.UpdateOneID(exist.ID).
|
|
SetMaterialName(it.MaterialName).SetSpec(it.Spec).SetUnit(it.Unit).
|
|
SetManageMode(it.ManageMode).SetUnitQty(it.UnitQty).SetLossRate(it.LossRate)
|
|
if it.ProcessCode != nil {
|
|
u = u.SetProcessCode(*it.ProcessCode) // nil 时不覆盖既有工序配置
|
|
}
|
|
if len(it.SerialSns) > 0 {
|
|
u.SetSerialSns(it.SerialSns)
|
|
}
|
|
if _, err := u.Save(ctx); err != nil {
|
|
return fmt.Errorf("更新BOM物料 %s 失败: %w", it.MaterialCode, err)
|
|
}
|
|
} else {
|
|
pc := 0
|
|
if it.ProcessCode != nil {
|
|
pc = *it.ProcessCode
|
|
}
|
|
b := s.ctx.EntClient.BomItem.Create().
|
|
SetProductCode(productCode).SetBomName(bomName).SetMaterialCode(it.MaterialCode).
|
|
SetMaterialName(it.MaterialName).SetSpec(it.Spec).SetUnit(it.Unit).
|
|
SetManageMode(it.ManageMode).SetProcessCode(pc).
|
|
SetUnitQty(it.UnitQty).SetLossRate(it.LossRate)
|
|
if len(it.SerialSns) > 0 {
|
|
b.SetSerialSns(it.SerialSns)
|
|
}
|
|
if _, err := b.Save(ctx); err != nil {
|
|
return fmt.Errorf("保存BOM物料 %s 失败: %w", it.MaterialCode, err)
|
|
}
|
|
}
|
|
}
|
|
s.ctx.EventLog.Write(ctx, "bom.save", "", operator, "product_bom", productCode, "保存产品BOM", map[string]any{"bomName": bomName, "count": len(items)})
|
|
return nil
|
|
}
|
|
|
|
// DeleteBomItem 移除 BOM 中的一条物料(按行主键)。
|
|
// 安全校验:该 (产品编码+BOM名称) 下任一工单已生成备料单时禁止移除,避免已锁定料单被改动。
|
|
func (s *Service) DeleteBomItem(ctx context.Context, id int, operator string) error {
|
|
item, err := s.ctx.EntClient.BomItem.Get(ctx, id)
|
|
if err != nil {
|
|
return errors.New("BOM 物料不存在")
|
|
}
|
|
used, err := s.ctx.EntClient.WorkOrder.Query().
|
|
Where(workorder.ProductCode(item.ProductCode)).Exist(ctx)
|
|
if err == nil && used {
|
|
// 该型号已有工单,谨慎起见禁止直接移除
|
|
return fmt.Errorf("该物料清单已被「%s」型号的工单选用,移除物料会影响备料/追溯,请先评估相关工单", item.ProductCode)
|
|
}
|
|
if err := s.ctx.EntClient.BomItem.DeleteOneID(id).Exec(ctx); err != nil {
|
|
return err
|
|
}
|
|
s.ctx.EventLog.Write(ctx, "bom.item.delete", "", operator, "product_bom", item.ProductCode,
|
|
"移除BOM物料 "+item.MaterialCode, map[string]any{"bomName": item.BomName})
|
|
return nil
|
|
}
|
|
|
|
// ListBom 查询 BOM(可按产品编码 + BOM 名称过滤;bomName 传 "ALL" 或空且指定了产品时返回全部)
|
|
func (s *Service) ListBom(ctx context.Context, productCode, bomName string) ([]*ent.BomItem, error) {
|
|
q := s.ctx.EntClient.BomItem.Query()
|
|
if productCode != "" {
|
|
q = q.Where(bomitem.ProductCode(productCode))
|
|
}
|
|
if bomName != "" && bomName != "ALL" {
|
|
q = q.Where(bomitem.BomName(bomName))
|
|
}
|
|
return q.Order(ent.Asc(bomitem.FieldProductCode), ent.Asc(bomitem.FieldBomName), ent.Asc(bomitem.FieldID)).All(ctx)
|
|
}
|
|
|
|
// BomNameInfo 型号下的一份 BOM 概要(名称 + 物料数)
|
|
type BomNameInfo struct {
|
|
BomName string `json:"bomName"`
|
|
ItemCount int `json:"itemCount"`
|
|
}
|
|
|
|
// ListBomNames 列出某产品型号下并存的所有 BOM 名称(工单建单选 BOM 下拉数据源)
|
|
func (s *Service) ListBomNames(ctx context.Context, productCode string) ([]*BomNameInfo, error) {
|
|
if productCode == "" {
|
|
return []*BomNameInfo{}, nil
|
|
}
|
|
items, err := s.ctx.EntClient.BomItem.Query().
|
|
Where(bomitem.ProductCode(productCode)).All(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
order := []string{}
|
|
count := map[string]int{}
|
|
for _, it := range items {
|
|
if _, ok := count[it.BomName]; !ok {
|
|
order = append(order, it.BomName)
|
|
}
|
|
count[it.BomName]++
|
|
}
|
|
out := make([]*BomNameInfo, 0, len(order))
|
|
for _, name := range order {
|
|
out = append(out, &BomNameInfo{BomName: name, ItemCount: count[name]})
|
|
}
|
|
return out, nil
|
|
}
|