mirror of
https://github.com/3ybactuk/marketplace-go-service-project.git
synced 2025-10-30 22:13:44 +03:00
212 lines
5.6 KiB
Go
212 lines
5.6 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
|
|
"route256/loms/internal/domain/entity"
|
|
"route256/loms/internal/domain/model"
|
|
|
|
pb "route256/pkg/api/loms/v1"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
//go:generate minimock -i OrderRepository -o ./mock -s _mock.go
|
|
type OrderRepository interface {
|
|
OrderCreate(ctx context.Context, order *entity.Order) (entity.ID, error)
|
|
OrderSetStatus(ctx context.Context, orderID entity.ID, newStatus string) error
|
|
OrderGetByID(ctx context.Context, orderID entity.ID) (*entity.Order, error)
|
|
}
|
|
|
|
//go:generate minimock -i StockRepository -o ./mock -s _mock.go
|
|
type StockRepository interface {
|
|
StockReserve(ctx context.Context, stock *entity.Stock) error
|
|
StockReserveRemove(ctx context.Context, stock *entity.Stock) error
|
|
StockCancel(ctx context.Context, stock *entity.Stock) error
|
|
StockGetByID(ctx context.Context, sku entity.Sku) (*entity.Stock, error)
|
|
}
|
|
|
|
type txManager interface {
|
|
WriteWithTransaction(ctx context.Context, fn func(ctx context.Context) error) (err error)
|
|
ReadWithTransaction(ctx context.Context, fn func(ctx context.Context) error) (err error)
|
|
WriteWithRepeatableRead(ctx context.Context, fn func(ctx context.Context) error) (err error)
|
|
ReadWithRepeatableRead(ctx context.Context, fn func(ctx context.Context) error) (err error)
|
|
}
|
|
|
|
type LomsService struct {
|
|
orders OrderRepository
|
|
stocks StockRepository
|
|
txManager txManager
|
|
}
|
|
|
|
func NewLomsService(orderRepo OrderRepository, stockRepo StockRepository, txManager txManager) *LomsService {
|
|
return &LomsService{
|
|
orders: orderRepo,
|
|
stocks: stockRepo,
|
|
txManager: txManager,
|
|
}
|
|
}
|
|
|
|
func (s *LomsService) rollbackStocks(ctx context.Context, stocks []*entity.Stock) {
|
|
for _, stock := range stocks {
|
|
if err := s.stocks.StockCancel(ctx, stock); err != nil {
|
|
log.Error().Err(err).Msg("failed to rollback stock")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *LomsService) OrderCreate(ctx context.Context, orderReq *pb.OrderCreateRequest) (entity.ID, error) {
|
|
if orderReq == nil || orderReq.UserId <= 0 || len(orderReq.Items) == 0 {
|
|
return 0, model.ErrInvalidInput
|
|
}
|
|
|
|
for _, item := range orderReq.Items {
|
|
if item.Sku <= 0 || item.Count == 0 {
|
|
return 0, model.ErrInvalidInput
|
|
}
|
|
}
|
|
|
|
order := &entity.Order{
|
|
OrderID: 0,
|
|
Status: pb.OrderStatus_ORDER_STATUS_NEW.String(),
|
|
UserID: entity.ID(orderReq.UserId),
|
|
Items: make([]entity.OrderItem, len(orderReq.Items)),
|
|
}
|
|
|
|
for i, item := range orderReq.Items {
|
|
order.Items[i] = entity.OrderItem{
|
|
ID: entity.Sku(item.Sku),
|
|
Count: item.Count,
|
|
}
|
|
}
|
|
|
|
slices.SortStableFunc(order.Items, func(a, b entity.OrderItem) int {
|
|
return int(a.ID - b.ID)
|
|
})
|
|
|
|
var (
|
|
orderID entity.ID
|
|
resErr error
|
|
)
|
|
|
|
err := s.txManager.WriteWithTransaction(ctx, func(txCtx context.Context) error {
|
|
id, err := s.orders.OrderCreate(txCtx, order)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
order.OrderID = id
|
|
orderID = id
|
|
|
|
committed := make([]*entity.Stock, 0, len(order.Items))
|
|
for _, it := range order.Items {
|
|
st := &entity.Stock{Item: it, Reserved: it.Count}
|
|
if err := s.stocks.StockReserve(txCtx, st); err != nil {
|
|
s.rollbackStocks(txCtx, committed)
|
|
|
|
_ = s.orders.OrderSetStatus(txCtx, id,
|
|
pb.OrderStatus_ORDER_STATUS_FAILED.String())
|
|
|
|
resErr = fmt.Errorf("stocks.StockReserve: %w", err)
|
|
return nil
|
|
}
|
|
committed = append(committed, st)
|
|
}
|
|
|
|
return s.orders.OrderSetStatus(txCtx, id,
|
|
pb.OrderStatus_ORDER_STATUS_AWAITING_PAYMENT.String())
|
|
})
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if resErr != nil {
|
|
return 0, resErr
|
|
}
|
|
return orderID, nil
|
|
}
|
|
|
|
func (s *LomsService) OrderInfo(ctx context.Context, orderID entity.ID) (*entity.Order, error) {
|
|
if orderID <= 0 {
|
|
return nil, model.ErrInvalidInput
|
|
}
|
|
|
|
return s.orders.OrderGetByID(ctx, orderID)
|
|
}
|
|
|
|
func (s *LomsService) OrderPay(ctx context.Context, orderID entity.ID) error {
|
|
if orderID <= 0 {
|
|
return model.ErrInvalidInput
|
|
}
|
|
|
|
return s.txManager.WriteWithTransaction(ctx, func(txCtx context.Context) error {
|
|
order, err := s.orders.OrderGetByID(txCtx, orderID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch order.Status {
|
|
case pb.OrderStatus_ORDER_STATUS_PAYED.String():
|
|
return nil
|
|
case pb.OrderStatus_ORDER_STATUS_AWAITING_PAYMENT.String():
|
|
for _, it := range order.Items {
|
|
if err := s.stocks.StockReserveRemove(txCtx, &entity.Stock{
|
|
Item: it,
|
|
Reserved: it.Count,
|
|
}); err != nil {
|
|
log.Error().Err(err).Msg("failed to free stock reservation")
|
|
}
|
|
}
|
|
return s.orders.OrderSetStatus(txCtx, orderID,
|
|
pb.OrderStatus_ORDER_STATUS_PAYED.String())
|
|
default:
|
|
return model.ErrOrderInvalidStatus
|
|
}
|
|
})
|
|
}
|
|
|
|
func (s *LomsService) OrderCancel(ctx context.Context, orderID entity.ID) error {
|
|
if orderID <= 0 {
|
|
return model.ErrInvalidInput
|
|
}
|
|
|
|
return s.txManager.WriteWithTransaction(ctx, func(txCtx context.Context) error {
|
|
order, err := s.orders.OrderGetByID(txCtx, orderID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch order.Status {
|
|
case pb.OrderStatus_ORDER_STATUS_CANCELLED.String():
|
|
return nil
|
|
case pb.OrderStatus_ORDER_STATUS_FAILED.String(),
|
|
pb.OrderStatus_ORDER_STATUS_PAYED.String():
|
|
return model.ErrOrderInvalidStatus
|
|
}
|
|
|
|
for _, it := range order.Items {
|
|
if err := s.stocks.StockCancel(txCtx, &entity.Stock{
|
|
Item: it,
|
|
Reserved: it.Count,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return s.orders.OrderSetStatus(txCtx, orderID,
|
|
pb.OrderStatus_ORDER_STATUS_CANCELLED.String())
|
|
})
|
|
}
|
|
|
|
func (s *LomsService) StocksInfo(ctx context.Context, sku entity.Sku) (uint32, error) {
|
|
if sku <= 0 {
|
|
return 0, model.ErrInvalidInput
|
|
}
|
|
|
|
stock, err := s.stocks.StockGetByID(ctx, sku)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return stock.Item.Count - stock.Reserved, nil
|
|
}
|