Files
bj_power/bj_power_mes/internal/logic/product.go
T
SunYF 62e45b7379 feat: 完成附件存储重构与磁盘监控功能,同步物料档案与入库单规则更新
1.  新增跨平台磁盘空间监控能力,每日7点自动检测附件目录剩余空间,触发阈值告警
2.  重构附件存储方案为年/月/日/文件类型分层结构,统一MES与WMS的附件管理逻辑
3.  对齐物料档案与入库单的质量状态校验规则,仅合格品计入库存与出库
4.  实现入库单作废功能与区域库位的多级父子结构管理
5.  删除物料简称字段,补充规格型号与单位必填项,统一系统数据口径
6.  新增附件中心与磁盘状态查询接口,完善权限控制与操作日志
2026-09-19 10:54:15 +08:00

172 lines
5.9 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"
"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"`
Code string `json:"code"`
Spec string `json:"spec"`
Unit string `json:"unit"`
Category string `json:"category"`
IsActive bool `json:"isActive"`
}
func (s *Service) CreateProductType(ctx context.Context, req ProductTypeReq) error {
active := req.IsActive
if !req.IsActive {
active = true
}
if strings.TrimSpace(req.Name) == "" {
return errors.New("名称为必填项")
}
if strings.TrimSpace(req.Spec) == "" {
return errors.New("规格型号为必填项")
}
if strings.TrimSpace(req.Unit) == "" {
return errors.New("单位为必填项")
}
if err := s.ctx.Wms.CreateProductType(ctx, req.Code, req.Name, req.Spec, req.Unit, req.Category, 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")
}
if strings.TrimSpace(req.Name) == "" {
return errors.New("名称为必填项")
}
if strings.TrimSpace(req.Spec) == "" {
return errors.New("规格型号为必填项")
}
if strings.TrimSpace(req.Unit) == "" {
return errors.New("单位为必填项")
}
if err := s.ctx.Wms.UpdateProductType(ctx, req.Id, req.Name, req.Spec, req.Unit, req.Category, 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 {
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
}
// 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 分页+搜索(管理页真分页)。
// 2026-09-08 禁止关键字混搜:编码/名称/类别各自独立模糊。
func (s *Service) ListProductTypesPage(ctx context.Context, code, name, category, isActive string, page, pageSize int) (int64, []wmsclient.ProductTypeRow, bool, error) {
total, list, err := s.ctx.Wms.ProductTypePage(ctx, code, name, category, isActive, page, pageSize)
if err == nil {
return total, list, true, nil
}
fallback, ferr := s.localProductTypes(ctx, code, name, category)
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
}
// localProductTypes 本地只读缓存过滤(降级用):编码/名称/类别各自独立模糊匹配
func (s *Service) localProductTypes(ctx context.Context, code, name, category string) ([]wmsclient.ProductTypeRow, error) {
list, err := s.ctx.EntClient.ProductType.Query().Order(ent.Asc(producttype.FieldID)).All(ctx)
if err != nil {
return nil, err
}
codeKw := strings.ToLower(strings.TrimSpace(code))
nameKw := strings.ToLower(strings.TrimSpace(name))
catKw := strings.ToLower(strings.TrimSpace(category))
rows := make([]wmsclient.ProductTypeRow, 0, len(list))
for _, p := range list {
if codeKw != "" && !strings.Contains(strings.ToLower(p.Code), codeKw) {
continue
}
if nameKw != "" && !strings.Contains(strings.ToLower(p.Name), nameKw) {
continue
}
if catKw != "" && !strings.Contains(strings.ToLower(p.Category), catKw) {
continue
}
rows = append(rows, wmsclient.ProductTypeRow{
ID: p.ID, Code: p.Code, Name: p.Name, Spec: p.Spec, Unit: p.Unit,
Category: p.Category, IsActive: p.IsActive,
CreatedAt: p.CreatedAt.Unix(),
})
}
return rows, nil
}