chore: 批量完成项目多模块迭代优化

1. 废弃半成品库存表并统一库存主表
2. 修复列表排序与搜索大小写不敏感问题
3. 新增用户最近登录时间、工单BOM名称字段
4. 完善角色管理、工位选择器功能
5. 增加拧紧数据补录、成品自动回流WMS能力
6. 统一导出时间范围校验规则
7. 丰富物料/备料单筛选条件
8. 调整菜单与权限命名适配产品型号业务
This commit is contained in:
SunYF
2026-09-08 11:44:04 +08:00
parent 06689970e2
commit e852c7cd37
96 changed files with 4528 additions and 542 deletions
+110 -11
View File
@@ -3,11 +3,20 @@ package logic
import (
"context"
"errors"
"fmt"
"strings"
"bj_power_mes/ent"
"bj_power_mes/ent/bomitem"
"bj_power_mes/ent/producttype"
"bj_power_mes/ent/workorder"
"bj_power_mes/internal/wmsclient"
)
// 产品型号(成品档案):编码主数据单一来源 = WMSmaterials item_type=3)。
// MES 本页改为实时代理 WMS;WMS 不可达时降级读本地 product_types 只读缓存(仅查询,
// 写操作必须 WMS 可用),前端以 wmsOnline 标识提示。
type ProductTypeReq struct {
Id int `json:"id"`
Name string `json:"name"`
@@ -22,25 +31,115 @@ func (s *Service) CreateProductType(ctx context.Context, req ProductTypeReq) err
if !req.IsActive {
active = true
}
return s.ctx.EntClient.ProductType.Create().
SetName(req.Name).SetCode(req.Code).SetCategory(req.Category).
SetRemark(req.Remark).SetIsActive(active).Exec(ctx)
if err := s.ctx.Wms.CreateProductType(ctx, req.Code, req.Name, req.Category, req.Remark, active); err != nil {
return errors.New("WMS 不可达或拒绝写入(编码主数据以 WMS 为单一来源):" + err.Error())
}
return nil
}
func (s *Service) UpdateProductType(ctx context.Context, req ProductTypeReq) error {
if req.Id == 0 {
return errors.New("缺少 id")
}
return s.ctx.EntClient.ProductType.UpdateOneID(req.Id).
SetName(req.Name).SetCode(req.Code).SetCategory(req.Category).
SetRemark(req.Remark).SetIsActive(req.IsActive).Exec(ctx)
if err := s.ctx.Wms.UpdateProductType(ctx, req.Id, req.Name, req.Category, req.Remark, req.IsActive); err != nil {
return errors.New("WMS 不可达或拒绝写入:" + err.Error())
}
return nil
}
// DeleteProductType 删除保护:被工单/BOM 引用过的型号禁删(MES 侧引用校验,
// WMS 侧库存引用由 WMS 内部接口再校验一层)
func (s *Service) DeleteProductType(ctx context.Context, id int) error {
return s.ctx.EntClient.ProductType.DeleteOneID(id).Exec(ctx)
row, err := s.productTypeByID(ctx, id)
if err != nil {
return err
}
woCnt, _ := s.ctx.EntClient.WorkOrder.Query().Where(workorder.ProductCode(row.Code)).Count(ctx)
if woCnt > 0 {
return fmt.Errorf("该产品型号已被 %d 张工单引用,不可删除;建议停用(下架)", woCnt)
}
bomCnt, _ := s.ctx.EntClient.BomItem.Query().Where(bomitem.ProductCode(row.Code)).Count(ctx)
if bomCnt > 0 {
return fmt.Errorf("该产品型号在物料清单中有 %d 条配置,不可删除;建议停用(下架)", bomCnt)
}
if err := s.ctx.Wms.DeleteProductType(ctx, id); err != nil {
return errors.New("WMS 不可达或拒绝删除:" + err.Error())
}
return nil
}
func (s *Service) ListProductTypes(ctx context.Context) ([]*ent.ProductType, error) {
return s.ctx.EntClient.ProductType.Query().
Order(ent.Desc(producttype.FieldID)).All(ctx)
}
// ListProductTypes 全量列表(下拉/兼容形态,id asc 新建在底部)。
// degraded=false 表示 WMS 不可达、数据来自本地只读缓存。
func (s *Service) ListProductTypes(ctx context.Context) ([]wmsclient.ProductTypeRow, bool, error) {
list, err := s.ctx.Wms.ProductTypes(ctx, "", "")
if err == nil {
return list, true, nil
}
fallback, ferr := s.localProductTypes(ctx, "")
if ferr != nil {
return nil, false, errors.New("WMS 不可达且本地缓存读取失败")
}
return fallback, false, nil
}
// ListProductTypesPage 分页+搜索(管理页真分页)
func (s *Service) ListProductTypesPage(ctx context.Context, keyword, isActive string, page, pageSize int) (int64, []wmsclient.ProductTypeRow, bool, error) {
total, list, err := s.ctx.Wms.ProductTypePage(ctx, keyword, isActive, page, pageSize)
if err == nil {
return total, list, true, nil
}
fallback, ferr := s.localProductTypes(ctx, keyword)
if ferr != nil {
return 0, nil, false, errors.New("WMS 不可达且本地缓存读取失败")
}
start := (page - 1) * pageSize
if start > len(fallback) {
start = len(fallback)
}
end := start + pageSize
if end > len(fallback) {
end = len(fallback)
}
return int64(len(fallback)), fallback[start:end], false, nil
}
func (s *Service) productTypeByID(ctx context.Context, id int) (*wmsclient.ProductTypeRow, error) {
list, err := s.ctx.Wms.ProductTypes(ctx, "", "")
if err == nil {
for i := range list {
if list[i].ID == id {
return &list[i], nil
}
}
return nil, errors.New("产品型号不存在")
}
// 降级:本地缓存
pt, err := s.ctx.EntClient.ProductType.Get(ctx, id)
if err != nil {
return nil, errors.New("产品型号不存在(WMS 不可达,无法校验引用)")
}
return &wmsclient.ProductTypeRow{ID: pt.ID, Code: pt.Code, Name: pt.Name}, nil
}
func (s *Service) localProductTypes(ctx context.Context, keyword string) ([]wmsclient.ProductTypeRow, error) {
list, err := s.ctx.EntClient.ProductType.Query().Order(ent.Asc(producttype.FieldID)).All(ctx)
if err != nil {
return nil, err
}
rows := make([]wmsclient.ProductTypeRow, 0, len(list))
kw := strings.ToLower(strings.TrimSpace(keyword))
for _, p := range list {
if kw != "" &&
!strings.Contains(strings.ToLower(p.Code), kw) &&
!strings.Contains(strings.ToLower(p.Name), kw) &&
!strings.Contains(strings.ToLower(p.Category), kw) {
continue
}
rows = append(rows, wmsclient.ProductTypeRow{
ID: p.ID, Code: p.Code, Name: p.Name, Category: p.Category,
Remark: p.Remark, IsActive: p.IsActive,
CreatedAt: p.CreatedAt.Unix(),
})
}
return rows, nil
}