package logic import ( "context" "database/sql" "strconv" "time" "muyu-apiserver/pkg/tenantctx" "muyu-apiserver/pkg/uid" "muyu-apiserver/rpc/production/internal/svc" "muyu-apiserver/rpc/production/pb" "github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/stores/sqlx" ) type CreateInboundLogic struct { ctx context.Context svcCtx *svc.ServiceContext logx.Logger } func NewCreateInboundLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateInboundLogic { return &CreateInboundLogic{ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx)} } func (l *CreateInboundLogic) CreateInbound(in *pb.CreateInboundReq) (*pb.IdResp, error) { tenantId := tenantctx.ExtractTenantId(l.ctx) qty, _ := strconv.ParseFloat(in.Quantity, 64) if qty <= 0 { return nil, ErrInvalidQuantity } plan, err := l.svcCtx.PlanModel.FindOneByPlanId(l.ctx, in.PlanId) if err != nil { return nil, ErrPlanNotFound } // Tenant isolation: allow plan owner OR an associated factory tenant if plan.TenantId != tenantId { factories, fErr := l.svcCtx.PlanFactoryModel.FindByPlanId(l.ctx, in.PlanId) if fErr != nil { return nil, ErrPermissionDenied } authorized := false for _, f := range factories { if f.FactoryTenantId == tenantId { authorized = true break } } if !authorized { return nil, ErrPermissionDenied } } if plan.Status != 2 { // must be producing return nil, ErrPlanNotProducing } // Check if adding this quantity would exceed target if plan.CompletedQuantity+qty > plan.TargetQuantity { return nil, ErrExceedTarget } price, _ := strconv.ParseFloat(in.PricePerMeter, 64) recordId := uid.Generate() // Transactional: insert record + recalculate completed_quantity + check completion + write inventory log err = l.svcCtx.Conn.TransactCtx(l.ctx, func(ctx context.Context, session sqlx.Session) error { // 1. Insert inbound record _, err := session.ExecCtx(ctx, "INSERT INTO pro_inbound_record (record_id, tenant_id, plan_id, product_id, batch_no, quantity, rolls, price_per_meter, operator_id, remark) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", recordId, tenantId, in.PlanId, plan.ProductId, in.BatchNo, qty, in.Rolls, price, tenantId, sql.NullString{String: in.Remark, Valid: in.Remark != ""}) if err != nil { return err } // 2. Recalculate completed_quantity via SUM (not increment — prevents drift) _, err = session.ExecCtx(ctx, "UPDATE pro_production_plan SET completed_quantity = COALESCE((SELECT SUM(quantity) FROM pro_inbound_record WHERE plan_id = ?), 0), operator = ? WHERE plan_id = ?", in.PlanId, tenantId, in.PlanId) if err != nil { return err } // 3. Check if plan is now complete var completedQty float64 err = session.QueryRowCtx(ctx, &completedQty, "SELECT completed_quantity FROM pro_production_plan WHERE plan_id = ?", in.PlanId) if err != nil { return err } if completedQty >= plan.TargetQuantity { // Mark plan as completed _, err = session.ExecCtx(ctx, "UPDATE pro_production_plan SET status = 3 WHERE plan_id = ?", in.PlanId) if err != nil { return err } // Mark fabric_warehouse step as completed _, err = session.ExecCtx(ctx, "UPDATE pro_plan_process_step SET status = 2, completed_at = NOW(), operator_id = ? WHERE plan_id = ? AND step_type = 'fabric_warehouse'", tenantId, in.PlanId) if err != nil { return err } } // 4. Write inventory log (inside transaction via raw SQL) logId := uid.Generate() _, err = session.ExecCtx(ctx, "INSERT INTO `inv_inventory_log` (`log_id`, `tenant_id`, `product_id`, `change_qty`, `balance_after`, `change_type`, `ref_type`, `ref_id`, `contact_id`, `log_date`, `operator`, `remark`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", logId, plan.TenantId, plan.ProductId, qty, float64(0), int64(2), "PRODUCTION", recordId, in.PlanId, time.Now(), tenantId, "生产入库 "+in.BatchNo) return err }) if err != nil { return nil, err } return &pb.IdResp{Id: recordId}, nil }