package logic import ( "context" "database/sql" "strconv" "strings" "muyu-apiserver/model" "muyu-apiserver/pkg/metrics" "muyu-apiserver/pkg/tenantctx" "muyu-apiserver/pkg/uid" "muyu-apiserver/rpc/inventory/internal/svc" "muyu-apiserver/rpc/inventory/pb" "github.com/zeromicro/go-zero/core/logx" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) type CreateProductLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewCreateProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateProductLogic { return &CreateProductLogic{ ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx), } } func (l *CreateProductLogic) CreateProduct(in *pb.CreateProductReq) (*pb.IdResp, error) { tenantId := tenantctx.ExtractTenantId(l.ctx) productId := uid.Generate() salesPrice, _ := strconv.ParseFloat(in.SalesPrice, 64) productName := strings.TrimSpace(in.ProductName) if productName == "" { return nil, status.Error(codes.InvalidArgument, "产品名称不能为空") } color := normalizeProductColor(in.Color) colorNo, err := resolveColorNo(l.ctx, l.svcCtx.ProductModel, tenantId, productName, in.ColorNo) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } productCode := strings.ToUpper(strings.TrimSpace(in.ProductCode)) if productCode == "" { productCode = generateProductCode(productName, colorNo) } if err := ensureProductCodeAvailable(l.ctx, l.svcCtx.ProductModel, tenantId, productCode); err != nil { return nil, status.Error(codes.AlreadyExists, err.Error()) } if err := validateProductBatchInput(in.InitialBatch); err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } product := &model.InvProduct{ ProductId: productId, TenantId: tenantId, ProductName: productName, ImageUrl: in.ImageUrl, Spec: in.Spec, Color: color, ColorNo: colorNo, ProductCode: productCode, SalesPrice: salesPrice, Remark: sql.NullString{String: in.Remark, Valid: in.Remark != ""}, Status: 1, } _, err = l.svcCtx.ProductModel.Insert(l.ctx, product) if err != nil { return nil, status.Error(codes.Internal, err.Error()) } firstBatchNo := productCode + "-B001" if batchNo := strings.TrimSpace(in.InitialBatch.GetBatchNo()); batchNo != "" { firstBatchNo = batchNo } firstBatch := &model.InvProductBatch{ BatchId: uid.Generate(), TenantId: tenantId, ProductId: productId, BatchNo: firstBatchNo, Status: 1, } if err := batchInputToModelFields(in.InitialBatch, firstBatch); err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } if _, err := l.svcCtx.ProductBatchModel.Insert(l.ctx, firstBatch); err != nil { return nil, status.Error(codes.Internal, err.Error()) } var totalLengthM float64 var totalBolts int if len(in.Pans) > 0 { pans := make([]*model.InvProductPan, 0, len(in.Pans)) for i, panInput := range in.Pans { panId := uid.Generate() batchId := panInput.BatchId if batchId == "" { batchId = firstBatch.BatchId } if _, err := l.svcCtx.ProductBatchModel.FindOneByProductAndBatchId(l.ctx, tenantId, productId, batchId); err != nil { return nil, status.Error(codes.InvalidArgument, "批次不存在或不属于该产品") } pans = append(pans, &model.InvProductPan{ PanId: panId, ProductId: productId, BatchId: batchId, Name: panInput.Name, Position: panInput.Position, SortOrder: int64(i + 1), TenantId: tenantId, }) if len(panInput.BoltLengths) > 0 { bolts := make([]*model.InvProductBolt, 0, len(panInput.BoltLengths)) for j, lengthStr := range panInput.BoltLengths { lengthM, _ := strconv.ParseFloat(lengthStr, 64) totalLengthM += lengthM bolts = append(bolts, &model.InvProductBolt{ BoltId: uid.Generate(), PanId: panId, LengthM: lengthM, Color: color, SortOrder: int64(j + 1), TenantId: tenantId, }) } totalBolts += len(panInput.BoltLengths) if err := l.svcCtx.BoltModel.BulkReplace(l.ctx, panId, tenantId, bolts); err != nil { l.Errorf("create bolts for pan %s failed: %v", panId, err) } } } if err := l.svcCtx.PanModel.BulkReplace(l.ctx, productId, tenantId, pans); err != nil { l.Errorf("create pans for product %s failed: %v", productId, err) } } metrics.ProductCreatedTotal.WithLabelValues(tenantId, productName, in.Spec, color).Inc() if totalLengthM > 0 { metrics.InboundLengthMetersTotal.WithLabelValues(tenantId, productName, color, "create").Add(totalLengthM) } if totalBolts > 0 { metrics.InboundBoltsTotal.WithLabelValues(tenantId, "create").Add(float64(totalBolts)) } return &pb.IdResp{Id: productId}, nil }