127 lines
3.9 KiB
Go
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
|
|
}
|