127 lines
3.9 KiB
Go

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
}