upodate system
This commit is contained in:
@@ -1,11 +1,17 @@
|
||||
package inprogress_order
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"enaklo-pos-be/internal/common/logger"
|
||||
"enaklo-pos-be/internal/common/mycontext"
|
||||
order2 "enaklo-pos-be/internal/constants/order"
|
||||
"enaklo-pos-be/internal/entity"
|
||||
"enaklo-pos-be/internal/services/v2/order"
|
||||
"fmt"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -19,56 +25,185 @@ type InProgressOrderService interface {
|
||||
|
||||
type OrderRepository interface {
|
||||
FindByID(ctx mycontext.Context, id int64) (*entity.Order, error)
|
||||
CreateOrUpdate(ctx mycontext.Context, order *entity.Order) (*entity.Order, error)
|
||||
CreateOrder(ctx mycontext.Context, order *entity.Order, tx *gorm.DB) (*entity.Order, error)
|
||||
CreateOrderItems(ctx mycontext.Context, orderID int64, items []entity.OrderItem, tx *gorm.DB) error
|
||||
GetListByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int, status string) ([]*entity.Order, error)
|
||||
FindByIDAndPartnerID(ctx mycontext.Context, id int64, partnerID int64) (*entity.Order, error)
|
||||
UpdateOrderTotalsWithTx(ctx mycontext.Context, trx *gorm.DB, orderID int64, amount, tax, total float64) error
|
||||
}
|
||||
|
||||
type OrderCalculator interface {
|
||||
CalculateOrderTotals(
|
||||
ctx mycontext.Context,
|
||||
items []entity.OrderItemRequest,
|
||||
productDetails *entity.ProductDetails,
|
||||
source string,
|
||||
partnerID int64,
|
||||
) (*entity.OrderCalculation, error)
|
||||
CalculateOrderTotals(ctx mycontext.Context, items []entity.OrderItemRequest, productDetails *entity.ProductDetails, source string, partnerID int64) (*entity.OrderCalculation, error)
|
||||
ValidateOrderItems(ctx mycontext.Context, items []entity.OrderItemRequest) ([]int64, []entity.OrderItemRequest, error)
|
||||
}
|
||||
|
||||
type TransactionManager interface {
|
||||
Begin(ctx context.Context, opts ...*sql.TxOptions) (*gorm.DB, error)
|
||||
Commit(session *gorm.DB) *gorm.DB
|
||||
Rollback(session *gorm.DB) *gorm.DB
|
||||
}
|
||||
|
||||
type inProgressOrderSvc struct {
|
||||
repo OrderRepository
|
||||
orderCalculator OrderCalculator
|
||||
product order.ProductService
|
||||
trx TransactionManager
|
||||
}
|
||||
|
||||
func NewInProgressOrderService(repo OrderRepository, calculator OrderCalculator, product order.ProductService) InProgressOrderService {
|
||||
func NewInProgressOrderService(repo OrderRepository,
|
||||
calculator OrderCalculator, product order.ProductService, trx TransactionManager) InProgressOrderService {
|
||||
return &inProgressOrderSvc{
|
||||
repo: repo,
|
||||
orderCalculator: calculator,
|
||||
product: product,
|
||||
trx: trx,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *inProgressOrderSvc) Save(ctx mycontext.Context, req *entity.OrderRequest) (*entity.Order, error) {
|
||||
productIDs, filteredItems, err := s.orderCalculator.ValidateOrderItems(ctx, req.OrderItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.OrderItems = filteredItems
|
||||
|
||||
productDetails, err := s.product.GetProductDetails(ctx, productIDs, req.PartnerID)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to get product details", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderCalculation, err := s.orderCalculator.CalculateOrderTotals(ctx, req.OrderItems, productDetails, req.Source, req.PartnerID)
|
||||
orderItems, err := s.prepareOrderItems(ctx, req.OrderItems, req.PartnerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderItems := make([]entity.OrderItem, len(req.OrderItems))
|
||||
for i, item := range req.OrderItems {
|
||||
orderCalculation, err := s.calculateOrderTotals(ctx, req.OrderItems, req.Source, req.PartnerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx, err := s.trx.Begin(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to begin transaction")
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
s.trx.Rollback(tx)
|
||||
}
|
||||
}()
|
||||
|
||||
orderToSave := s.createOrderEntity(req, nil, orderCalculation) // Save order without items first
|
||||
createdOrder, err := s.repo.CreateOrder(ctx, orderToSave, tx)
|
||||
if err != nil {
|
||||
s.trx.Rollback(tx)
|
||||
if logger.ContextLogger(ctx) != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to create in-progress order", zap.Error(err), zap.Int64("partnerID", orderToSave.PartnerID))
|
||||
}
|
||||
return nil, errors.Wrap(err, "failed to create in-progress order")
|
||||
}
|
||||
|
||||
err = s.repo.CreateOrderItems(ctx, createdOrder.ID, orderItems, tx)
|
||||
if err != nil {
|
||||
s.trx.Rollback(tx)
|
||||
if logger.ContextLogger(ctx) != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to create order items", zap.Error(err), zap.Int64("orderID", createdOrder.ID))
|
||||
}
|
||||
return nil, errors.Wrap(err, "failed to create order items")
|
||||
}
|
||||
|
||||
if err := s.trx.Commit(tx).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "failed to commit transaction")
|
||||
}
|
||||
|
||||
fullOrder, err := s.repo.FindByID(ctx, createdOrder.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to fetch created order")
|
||||
}
|
||||
return fullOrder, nil
|
||||
}
|
||||
|
||||
func (s *inProgressOrderSvc) AddItems(ctx mycontext.Context, orderID int64, newItems []entity.OrderItemRequest) (*entity.Order, error) {
|
||||
existingOrder, err := s.repo.FindByID(ctx, orderID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to fetch order %d", orderID)
|
||||
}
|
||||
|
||||
if existingOrder.Status != order2.Pending.String() {
|
||||
return nil, errors.Errorf("cannot add items to order with status %s", existingOrder.Status)
|
||||
}
|
||||
|
||||
newOrderItems, err := s.prepareOrderItems(ctx, newItems, existingOrder.PartnerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx, err := s.trx.Begin(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to begin transaction")
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
s.trx.Rollback(tx)
|
||||
}
|
||||
}()
|
||||
|
||||
err = s.repo.CreateOrderItems(ctx, existingOrder.ID, newOrderItems, tx)
|
||||
if err != nil {
|
||||
s.trx.Rollback(tx)
|
||||
if logger.ContextLogger(ctx) != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to add order items",
|
||||
zap.Error(err),
|
||||
zap.Int64("orderID", existingOrder.ID))
|
||||
}
|
||||
return nil, errors.Wrap(err, "failed to add order items")
|
||||
}
|
||||
|
||||
updatedOrder, err := s.repo.FindByID(ctx, existingOrder.ID)
|
||||
if err != nil {
|
||||
s.trx.Rollback(tx)
|
||||
return nil, errors.Wrap(err, "failed to fetch updated order")
|
||||
}
|
||||
|
||||
combinedItemRequests := s.convertToOrderItemRequests(updatedOrder.OrderItems)
|
||||
orderCalculation, err := s.calculateOrderTotals(ctx, combinedItemRequests, updatedOrder.Source, updatedOrder.PartnerID)
|
||||
if err != nil {
|
||||
s.trx.Rollback(tx)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updatedOrder.Total = orderCalculation.Total
|
||||
updatedOrder.Tax = orderCalculation.Tax
|
||||
updatedOrder.Amount = orderCalculation.Subtotal
|
||||
|
||||
err = s.repo.UpdateOrderTotalsWithTx(ctx,
|
||||
tx,
|
||||
updatedOrder.ID,
|
||||
orderCalculation.Subtotal,
|
||||
orderCalculation.Tax,
|
||||
orderCalculation.Total)
|
||||
|
||||
if err != nil {
|
||||
s.trx.Rollback(tx)
|
||||
if logger.ContextLogger(ctx) != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to update order totals", zap.Error(err), zap.Int64("orderID", updatedOrder.ID))
|
||||
}
|
||||
return nil, errors.Wrap(err, "failed to update order totals")
|
||||
}
|
||||
|
||||
if err := s.trx.Commit(tx).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "failed to commit transaction")
|
||||
}
|
||||
|
||||
updatedOrder.OrderItems = newOrderItems
|
||||
|
||||
return updatedOrder, nil
|
||||
}
|
||||
|
||||
func (s *inProgressOrderSvc) prepareOrderItems(ctx mycontext.Context, items []entity.OrderItemRequest, partnerID int64) ([]entity.OrderItem, error) {
|
||||
productIDs, filteredItems, err := s.orderCalculator.ValidateOrderItems(ctx, items)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
productDetails, err := s.product.GetProductDetails(ctx, productIDs, partnerID)
|
||||
if err != nil {
|
||||
if logger.ContextLogger(ctx) != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to get product details", zap.Error(err))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderItems := make([]entity.OrderItem, len(filteredItems))
|
||||
for i, item := range filteredItems {
|
||||
product, exists := productDetails.Products[item.ProductID]
|
||||
productName := ""
|
||||
if exists {
|
||||
@@ -76,17 +211,34 @@ func (s *inProgressOrderSvc) Save(ctx mycontext.Context, req *entity.OrderReques
|
||||
}
|
||||
|
||||
orderItems[i] = entity.OrderItem{
|
||||
ItemID: item.ProductID,
|
||||
ItemName: productName,
|
||||
Quantity: item.Quantity,
|
||||
Price: product.Price,
|
||||
ItemType: product.Type,
|
||||
Description: product.Description,
|
||||
Notes: item.Notes,
|
||||
ItemID: item.ProductID,
|
||||
ItemName: productName,
|
||||
Quantity: item.Quantity,
|
||||
Price: product.Price,
|
||||
ItemType: product.Type,
|
||||
Notes: item.Notes,
|
||||
}
|
||||
}
|
||||
|
||||
order := &entity.Order{
|
||||
return orderItems, nil
|
||||
}
|
||||
|
||||
func (s *inProgressOrderSvc) calculateOrderTotals(ctx mycontext.Context, items []entity.OrderItemRequest, source string, partnerID int64) (*entity.OrderCalculation, error) {
|
||||
productIDs, _, err := s.orderCalculator.ValidateOrderItems(ctx, items)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
productDetails, err := s.product.GetProductDetails(ctx, productIDs, partnerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s.orderCalculator.CalculateOrderTotals(ctx, items, productDetails, source, partnerID)
|
||||
}
|
||||
|
||||
func (s *inProgressOrderSvc) createOrderEntity(req *entity.OrderRequest, orderItems []entity.OrderItem, calculation *entity.OrderCalculation) *entity.Order {
|
||||
return &entity.Order{
|
||||
ID: req.ID,
|
||||
PartnerID: req.PartnerID,
|
||||
CustomerID: req.CustomerID,
|
||||
@@ -95,22 +247,50 @@ func (s *inProgressOrderSvc) Save(ctx mycontext.Context, req *entity.OrderReques
|
||||
OrderItems: orderItems,
|
||||
TableNumber: req.TableNumber,
|
||||
OrderType: req.OrderType,
|
||||
Total: orderCalculation.Total,
|
||||
Tax: orderCalculation.Tax,
|
||||
Amount: orderCalculation.Subtotal,
|
||||
Total: calculation.Total,
|
||||
Tax: calculation.Tax,
|
||||
Amount: calculation.Subtotal,
|
||||
Status: order2.Pending.String(),
|
||||
Source: req.Source,
|
||||
}
|
||||
}
|
||||
|
||||
createdOrder, err := s.repo.CreateOrUpdate(ctx, order)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to create in-progress order",
|
||||
zap.Error(err),
|
||||
zap.Int64("partnerID", order.PartnerID))
|
||||
return nil, errors.Wrap(err, "failed to create in-progress order")
|
||||
func (s *inProgressOrderSvc) convertToOrderItemRequests(items []entity.OrderItem) []entity.OrderItemRequest {
|
||||
requests := make([]entity.OrderItemRequest, len(items))
|
||||
for i, item := range items {
|
||||
requests[i] = entity.OrderItemRequest{
|
||||
ProductID: item.ItemID,
|
||||
Quantity: item.Quantity,
|
||||
Notes: item.Notes,
|
||||
}
|
||||
}
|
||||
return requests
|
||||
}
|
||||
|
||||
func (s *inProgressOrderSvc) extractNewlyAddedItems(updatedOrder *entity.Order, existingItems []entity.OrderItem) []entity.OrderItem {
|
||||
if len(existingItems) == 0 {
|
||||
return updatedOrder.OrderItems
|
||||
}
|
||||
|
||||
return createdOrder, nil
|
||||
existingItemMap := make(map[string]struct{})
|
||||
for _, item := range existingItems {
|
||||
key := s.createItemKey(item)
|
||||
existingItemMap[key] = struct{}{}
|
||||
}
|
||||
|
||||
newlyAdded := make([]entity.OrderItem, 0)
|
||||
for _, item := range updatedOrder.OrderItems {
|
||||
key := s.createItemKey(item)
|
||||
if _, exists := existingItemMap[key]; !exists {
|
||||
newlyAdded = append(newlyAdded, item)
|
||||
}
|
||||
}
|
||||
|
||||
return newlyAdded
|
||||
}
|
||||
|
||||
func (s *inProgressOrderSvc) createItemKey(item entity.OrderItem) string {
|
||||
return fmt.Sprintf("%d_%s", item.ItemID, item.Notes)
|
||||
}
|
||||
|
||||
func (s *inProgressOrderSvc) GetOrdersByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.Order, error) {
|
||||
@@ -127,79 +307,6 @@ func (s *inProgressOrderSvc) GetOrdersByPartnerID(ctx mycontext.Context, partner
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
func (s *inProgressOrderSvc) AddItems(ctx mycontext.Context, orderID int64, newItems []entity.OrderItemRequest) (*entity.Order, error) {
|
||||
existingOrder, err := s.repo.FindByID(ctx, orderID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to fetch order %d", orderID)
|
||||
}
|
||||
|
||||
type itemKey struct {
|
||||
ProductID int64
|
||||
Notes string
|
||||
}
|
||||
|
||||
itemMap := make(map[itemKey]entity.OrderItemRequest)
|
||||
existingKeys := make(map[itemKey]struct{})
|
||||
|
||||
// Collect existing items
|
||||
for _, oi := range existingOrder.OrderItems {
|
||||
key := itemKey{ProductID: oi.ItemID, Notes: oi.Notes}
|
||||
existingKeys[key] = struct{}{}
|
||||
itemMap[key] = entity.OrderItemRequest{
|
||||
ProductID: oi.ItemID,
|
||||
Quantity: oi.Quantity,
|
||||
Notes: oi.Notes,
|
||||
}
|
||||
}
|
||||
|
||||
// Merge new items into map
|
||||
for _, ni := range newItems {
|
||||
key := itemKey{ProductID: ni.ProductID, Notes: ni.Notes}
|
||||
if existing, found := itemMap[key]; found {
|
||||
existing.Quantity += ni.Quantity
|
||||
itemMap[key] = existing
|
||||
} else {
|
||||
itemMap[key] = ni
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare merged items
|
||||
mergedItems := make([]entity.OrderItemRequest, 0, len(itemMap))
|
||||
for _, item := range itemMap {
|
||||
mergedItems = append(mergedItems, item)
|
||||
}
|
||||
|
||||
// Save updated order
|
||||
req := &entity.OrderRequest{
|
||||
ID: existingOrder.ID,
|
||||
PartnerID: existingOrder.PartnerID,
|
||||
CustomerID: existingOrder.CustomerID,
|
||||
CustomerName: existingOrder.CustomerName,
|
||||
CreatedBy: existingOrder.CreatedBy,
|
||||
TableNumber: existingOrder.TableNumber,
|
||||
OrderType: existingOrder.OrderType,
|
||||
Source: existingOrder.Source,
|
||||
OrderItems: mergedItems,
|
||||
}
|
||||
|
||||
savedOrder, err := s.Save(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newlyAdded := make([]entity.OrderItem, 0)
|
||||
for _, item := range savedOrder.OrderItems {
|
||||
key := itemKey{ProductID: item.ItemID, Notes: item.Notes}
|
||||
if _, exists := existingKeys[key]; !exists {
|
||||
newlyAdded = append(newlyAdded, item)
|
||||
}
|
||||
}
|
||||
|
||||
savedOrder.OrderItems = newlyAdded
|
||||
|
||||
return savedOrder, nil
|
||||
}
|
||||
|
||||
func (s *inProgressOrderSvc) GetOrderByOrderAndPartnerID(ctx mycontext.Context, partnerID int64, orderID int64) (*entity.Order, error) {
|
||||
orders, err := s.repo.FindByIDAndPartnerID(ctx, orderID, partnerID)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,898 @@
|
||||
package inprogress_order
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"enaklo-pos-be/internal/common/mycontext"
|
||||
order2 "enaklo-pos-be/internal/constants/order"
|
||||
"enaklo-pos-be/internal/entity"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Mock implementations
|
||||
type MockOrderRepository struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockOrderRepository) FindByID(ctx mycontext.Context, id int64) (*entity.Order, error) {
|
||||
args := m.Called(ctx, id)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*entity.Order), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockOrderRepository) CreateOrder(ctx mycontext.Context, order *entity.Order, tx *gorm.DB) (*entity.Order, error) {
|
||||
args := m.Called(ctx, order, tx)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*entity.Order), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockOrderRepository) CreateOrderItems(ctx mycontext.Context, orderID int64, items []entity.OrderItem, tx *gorm.DB) error {
|
||||
args := m.Called(ctx, orderID, items, tx)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockOrderRepository) GetListByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int, status string) ([]*entity.Order, error) {
|
||||
args := m.Called(ctx, partnerID, limit, offset, status)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]*entity.Order), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockOrderRepository) FindByIDAndPartnerID(ctx mycontext.Context, id int64, partnerID int64) (*entity.Order, error) {
|
||||
args := m.Called(ctx, id, partnerID)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*entity.Order), args.Error(1)
|
||||
}
|
||||
|
||||
type MockOrderCalculator struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockOrderCalculator) CalculateOrderTotals(ctx mycontext.Context, items []entity.OrderItemRequest, productDetails *entity.ProductDetails, source string, partnerID int64) (*entity.OrderCalculation, error) {
|
||||
args := m.Called(ctx, items, productDetails, source, partnerID)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*entity.OrderCalculation), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockOrderCalculator) ValidateOrderItems(ctx mycontext.Context, items []entity.OrderItemRequest) ([]int64, []entity.OrderItemRequest, error) {
|
||||
args := m.Called(ctx, items)
|
||||
|
||||
// Handle nil values properly
|
||||
var productIDs []int64
|
||||
if args.Get(0) != nil {
|
||||
productIDs = args.Get(0).([]int64)
|
||||
}
|
||||
|
||||
var filteredItems []entity.OrderItemRequest
|
||||
if args.Get(1) != nil {
|
||||
filteredItems = args.Get(1).([]entity.OrderItemRequest)
|
||||
}
|
||||
|
||||
return productIDs, filteredItems, args.Error(2)
|
||||
}
|
||||
|
||||
type MockProductService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockProductService) GetProductDetails(ctx mycontext.Context, productIDs []int64, partnerID int64) (*entity.ProductDetails, error) {
|
||||
args := m.Called(ctx, productIDs, partnerID)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*entity.ProductDetails), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockProductService) GetProductsByIDs(ctx mycontext.Context, ids []int64, partnerID int64) ([]*entity.Product, error) {
|
||||
args := m.Called(ctx, ids, partnerID)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]*entity.Product), args.Error(1)
|
||||
}
|
||||
|
||||
type MockTransactionManager struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockTransactionManager) Begin(ctx context.Context, opts ...*sql.TxOptions) (*gorm.DB, error) {
|
||||
args := m.Called(ctx, opts)
|
||||
return args.Get(0).(*gorm.DB), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockTransactionManager) Commit(session *gorm.DB) *gorm.DB {
|
||||
args := m.Called(session)
|
||||
return args.Get(0).(*gorm.DB)
|
||||
}
|
||||
|
||||
func (m *MockTransactionManager) Rollback(session *gorm.DB) *gorm.DB {
|
||||
args := m.Called(session)
|
||||
return args.Get(0).(*gorm.DB)
|
||||
}
|
||||
|
||||
func TestInProgressOrderService_Save(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
request *entity.OrderRequest
|
||||
setupMocks func(*MockOrderRepository, *MockOrderCalculator, *MockProductService)
|
||||
expectedResult *entity.Order
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "successful order creation",
|
||||
request: &entity.OrderRequest{
|
||||
ID: 1,
|
||||
PartnerID: 100,
|
||||
CustomerID: func() *int64 { id := int64(200); return &id }(),
|
||||
CustomerName: "John Doe",
|
||||
CreatedBy: 300,
|
||||
OrderItems: []entity.OrderItemRequest{
|
||||
{ProductID: 1, Quantity: 2, Notes: "Extra spicy"},
|
||||
{ProductID: 2, Quantity: 1, Notes: ""},
|
||||
},
|
||||
TableNumber: "A1",
|
||||
OrderType: "DINE_IN",
|
||||
Source: "POS",
|
||||
},
|
||||
setupMocks: func(repo *MockOrderRepository, calc *MockOrderCalculator, prod *MockProductService) {
|
||||
// Mock ValidateOrderItems
|
||||
calc.On("ValidateOrderItems", mock.Anything, mock.Anything).Return(
|
||||
[]int64{1, 2},
|
||||
[]entity.OrderItemRequest{
|
||||
{ProductID: 1, Quantity: 2, Notes: "Extra spicy"},
|
||||
{ProductID: 2, Quantity: 1, Notes: ""},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
// Mock GetProductDetails
|
||||
productDetails := &entity.ProductDetails{
|
||||
Products: map[int64]*entity.Product{
|
||||
1: {ID: 1, Name: "Burger", Price: 10.0, Type: "PRODUCT"},
|
||||
2: {ID: 2, Name: "Fries", Price: 5.0, Type: "PRODUCT"},
|
||||
},
|
||||
}
|
||||
prod.On("GetProductDetails", mock.Anything, []int64{1, 2}, int64(100)).Return(productDetails, nil)
|
||||
|
||||
// Mock CalculateOrderTotals
|
||||
calc.On("CalculateOrderTotals", mock.Anything, mock.Anything, productDetails, "POS", int64(100)).Return(
|
||||
&entity.OrderCalculation{
|
||||
Subtotal: 25.0,
|
||||
Tax: 2.5,
|
||||
Total: 27.5,
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
// Mock CreateOrder (returns order without items)
|
||||
createdOrder := &entity.Order{
|
||||
ID: 1,
|
||||
PartnerID: 100,
|
||||
CustomerID: func() *int64 { id := int64(200); return &id }(),
|
||||
CustomerName: "John Doe",
|
||||
CreatedBy: 300,
|
||||
TableNumber: "A1",
|
||||
OrderType: "DINE_IN",
|
||||
Total: 27.5,
|
||||
Tax: 2.5,
|
||||
Amount: 25.0,
|
||||
Status: order2.Pending.String(),
|
||||
Source: "POS",
|
||||
}
|
||||
repo.On("CreateOrder", mock.Anything, mock.Anything).Return(createdOrder, nil)
|
||||
|
||||
// Mock CreateOrderItems
|
||||
repo.On("CreateOrderItems", mock.Anything, int64(1), mock.Anything).Return(nil)
|
||||
|
||||
// Mock FindByID (returns full order with items)
|
||||
expectedOrder := &entity.Order{
|
||||
ID: 1,
|
||||
PartnerID: 100,
|
||||
CustomerID: func() *int64 { id := int64(200); return &id }(),
|
||||
CustomerName: "John Doe",
|
||||
CreatedBy: 300,
|
||||
OrderItems: []entity.OrderItem{
|
||||
{ItemID: 1, ItemName: "Burger", Quantity: 2, Price: 10.0, ItemType: "PRODUCT", Notes: "Extra spicy"},
|
||||
{ItemID: 2, ItemName: "Fries", Quantity: 1, Price: 5.0, ItemType: "PRODUCT", Notes: ""},
|
||||
},
|
||||
TableNumber: "A1",
|
||||
OrderType: "DINE_IN",
|
||||
Total: 27.5,
|
||||
Tax: 2.5,
|
||||
Amount: 25.0,
|
||||
Status: order2.Pending.String(),
|
||||
Source: "POS",
|
||||
}
|
||||
repo.On("FindByID", mock.Anything, int64(1)).Return(expectedOrder, nil)
|
||||
},
|
||||
expectedResult: &entity.Order{
|
||||
ID: 1,
|
||||
PartnerID: 100,
|
||||
CustomerID: func() *int64 { id := int64(200); return &id }(),
|
||||
CustomerName: "John Doe",
|
||||
CreatedBy: 300,
|
||||
OrderItems: []entity.OrderItem{
|
||||
{ItemID: 1, ItemName: "Burger", Quantity: 2, Price: 10.0, ItemType: "PRODUCT", Notes: "Extra spicy"},
|
||||
{ItemID: 2, ItemName: "Fries", Quantity: 1, Price: 5.0, ItemType: "PRODUCT", Notes: ""},
|
||||
},
|
||||
TableNumber: "A1",
|
||||
OrderType: "DINE_IN",
|
||||
Total: 27.5,
|
||||
Tax: 2.5,
|
||||
Amount: 25.0,
|
||||
Status: order2.Pending.String(),
|
||||
Source: "POS",
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "validation error",
|
||||
request: &entity.OrderRequest{
|
||||
PartnerID: 100,
|
||||
OrderItems: []entity.OrderItemRequest{
|
||||
{ProductID: 1, Quantity: 0}, // Invalid quantity
|
||||
},
|
||||
},
|
||||
setupMocks: func(repo *MockOrderRepository, calc *MockOrderCalculator, prod *MockProductService) {
|
||||
calc.On("ValidateOrderItems", mock.Anything, mock.Anything).Return(
|
||||
nil, nil, errors.New("invalid quantity"),
|
||||
)
|
||||
},
|
||||
expectedResult: nil,
|
||||
expectedError: "invalid quantity",
|
||||
},
|
||||
{
|
||||
name: "product details error",
|
||||
request: &entity.OrderRequest{
|
||||
PartnerID: 100,
|
||||
OrderItems: []entity.OrderItemRequest{
|
||||
{ProductID: 1, Quantity: 1},
|
||||
},
|
||||
},
|
||||
setupMocks: func(repo *MockOrderRepository, calc *MockOrderCalculator, prod *MockProductService) {
|
||||
calc.On("ValidateOrderItems", mock.Anything, mock.Anything).Return(
|
||||
[]int64{1},
|
||||
[]entity.OrderItemRequest{{ProductID: 1, Quantity: 1}},
|
||||
nil,
|
||||
)
|
||||
prod.On("GetProductDetails", mock.Anything, []int64{1}, int64(100)).Return(nil, errors.New("product not found"))
|
||||
},
|
||||
expectedResult: nil,
|
||||
expectedError: "product not found",
|
||||
},
|
||||
{
|
||||
name: "calculation error",
|
||||
request: &entity.OrderRequest{
|
||||
PartnerID: 100,
|
||||
OrderItems: []entity.OrderItemRequest{
|
||||
{ProductID: 1, Quantity: 1},
|
||||
},
|
||||
},
|
||||
setupMocks: func(repo *MockOrderRepository, calc *MockOrderCalculator, prod *MockProductService) {
|
||||
calc.On("ValidateOrderItems", mock.Anything, mock.Anything).Return(
|
||||
[]int64{1},
|
||||
[]entity.OrderItemRequest{{ProductID: 1, Quantity: 1}},
|
||||
nil,
|
||||
)
|
||||
productDetails := &entity.ProductDetails{
|
||||
Products: map[int64]*entity.Product{
|
||||
1: {ID: 1, Name: "Burger", Price: 10.0, Type: "PRODUCT"},
|
||||
},
|
||||
}
|
||||
prod.On("GetProductDetails", mock.Anything, []int64{1}, int64(100)).Return(productDetails, nil)
|
||||
calc.On("CalculateOrderTotals", mock.Anything, mock.Anything, productDetails, "", int64(100)).Return(
|
||||
nil, errors.New("calculation failed"),
|
||||
)
|
||||
},
|
||||
expectedResult: nil,
|
||||
expectedError: "calculation failed",
|
||||
},
|
||||
{
|
||||
name: "repository error",
|
||||
request: &entity.OrderRequest{
|
||||
PartnerID: 100,
|
||||
OrderItems: []entity.OrderItemRequest{
|
||||
{ProductID: 1, Quantity: 1},
|
||||
},
|
||||
},
|
||||
setupMocks: func(repo *MockOrderRepository, calc *MockOrderCalculator, prod *MockProductService) {
|
||||
calc.On("ValidateOrderItems", mock.Anything, mock.Anything).Return(
|
||||
[]int64{1},
|
||||
[]entity.OrderItemRequest{{ProductID: 1, Quantity: 1}},
|
||||
nil,
|
||||
)
|
||||
productDetails := &entity.ProductDetails{
|
||||
Products: map[int64]*entity.Product{
|
||||
1: {ID: 1, Name: "Burger", Price: 10.0, Type: "PRODUCT"},
|
||||
},
|
||||
}
|
||||
prod.On("GetProductDetails", mock.Anything, []int64{1}, int64(100)).Return(productDetails, nil)
|
||||
calc.On("CalculateOrderTotals", mock.Anything, mock.Anything, productDetails, "", int64(100)).Return(
|
||||
&entity.OrderCalculation{Subtotal: 10.0, Tax: 1.0, Total: 11.0},
|
||||
nil,
|
||||
)
|
||||
repo.On("CreateOrder", mock.Anything, mock.Anything).Return(nil, errors.New("database error"))
|
||||
},
|
||||
expectedResult: nil,
|
||||
expectedError: "failed to create in-progress order: database error",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Setup mocks
|
||||
mockRepo := &MockOrderRepository{}
|
||||
mockCalc := &MockOrderCalculator{}
|
||||
mockProd := &MockProductService{}
|
||||
|
||||
if tt.setupMocks != nil {
|
||||
tt.setupMocks(mockRepo, mockCalc, mockProd)
|
||||
}
|
||||
|
||||
// Create service
|
||||
service := NewInProgressOrderService(mockRepo, mockCalc, mockProd)
|
||||
|
||||
// Execute
|
||||
ctx := mycontext.NewContext(context.Background())
|
||||
result, err := service.Save(ctx, tt.request)
|
||||
|
||||
// Assert
|
||||
if tt.expectedError != "" {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.expectedError)
|
||||
assert.Nil(t, result)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, tt.expectedResult.ID, result.ID)
|
||||
assert.Equal(t, tt.expectedResult.PartnerID, result.PartnerID)
|
||||
assert.Equal(t, tt.expectedResult.Status, result.Status)
|
||||
assert.Equal(t, tt.expectedResult.Total, result.Total)
|
||||
assert.Len(t, result.OrderItems, len(tt.expectedResult.OrderItems))
|
||||
}
|
||||
|
||||
// Verify all mocks were called as expected
|
||||
mockRepo.AssertExpectations(t)
|
||||
mockCalc.AssertExpectations(t)
|
||||
mockProd.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInProgressOrderService_AddItems(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
orderID int64
|
||||
newItems []entity.OrderItemRequest
|
||||
setupMocks func(*MockOrderRepository, *MockOrderCalculator, *MockProductService)
|
||||
expectedResult *entity.Order
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "successful add items to pending order",
|
||||
orderID: 1,
|
||||
newItems: []entity.OrderItemRequest{
|
||||
{ProductID: 3, Quantity: 1, Notes: "No onions"},
|
||||
{ProductID: 4, Quantity: 2, Notes: ""},
|
||||
},
|
||||
setupMocks: func(repo *MockOrderRepository, calc *MockOrderCalculator, prod *MockProductService) {
|
||||
// Mock existing order
|
||||
existingOrder := &entity.Order{
|
||||
ID: 1,
|
||||
PartnerID: 100,
|
||||
Status: order2.Pending.String(),
|
||||
OrderItems: []entity.OrderItem{
|
||||
{ItemID: 1, ItemName: "Burger", Quantity: 2, Price: 10.0, ItemType: "PRODUCT", Notes: "Extra spicy"},
|
||||
},
|
||||
Source: "POS",
|
||||
}
|
||||
repo.On("FindByID", mock.Anything, int64(1)).Return(existingOrder, nil).Once()
|
||||
|
||||
// Mock ValidateOrderItems for new items
|
||||
calc.On("ValidateOrderItems", mock.Anything, mock.Anything).Return(
|
||||
[]int64{3, 4},
|
||||
[]entity.OrderItemRequest{
|
||||
{ProductID: 3, Quantity: 1, Notes: "No onions"},
|
||||
{ProductID: 4, Quantity: 2, Notes: ""},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
// Mock GetProductDetails
|
||||
productDetails := &entity.ProductDetails{
|
||||
Products: map[int64]*entity.Product{
|
||||
3: {ID: 3, Name: "Salad", Price: 8.0, Type: "PRODUCT"},
|
||||
4: {ID: 4, Name: "Drink", Price: 3.0, Type: "PRODUCT"},
|
||||
},
|
||||
}
|
||||
prod.On("GetProductDetails", mock.Anything, []int64{3, 4}, int64(100)).Return(productDetails, nil)
|
||||
|
||||
// Mock CreateOrderItems
|
||||
repo.On("CreateOrderItems", mock.Anything, int64(1), mock.Anything).Return(nil)
|
||||
|
||||
// Mock FindByID (returns updated order with all items)
|
||||
updatedOrder := &entity.Order{
|
||||
ID: 1,
|
||||
PartnerID: 100,
|
||||
Status: order2.Pending.String(),
|
||||
OrderItems: []entity.OrderItem{
|
||||
{ItemID: 1, ItemName: "Burger", Quantity: 2, Price: 10.0, ItemType: "PRODUCT", Notes: "Extra spicy"},
|
||||
{ItemID: 3, ItemName: "Salad", Quantity: 1, Price: 8.0, ItemType: "PRODUCT", Notes: "No onions"},
|
||||
{ItemID: 4, ItemName: "Drink", Quantity: 2, Price: 3.0, ItemType: "PRODUCT", Notes: ""},
|
||||
},
|
||||
Total: 40.7,
|
||||
Tax: 3.7,
|
||||
Amount: 37.0,
|
||||
Source: "POS",
|
||||
}
|
||||
repo.On("FindByID", mock.Anything, int64(1)).Return(updatedOrder, nil).Once()
|
||||
|
||||
// Mock CalculateOrderTotals for combined items
|
||||
calc.On("CalculateOrderTotals", mock.Anything, mock.Anything, mock.Anything, "POS", int64(100)).Return(
|
||||
&entity.OrderCalculation{
|
||||
Subtotal: 37.0,
|
||||
Tax: 3.7,
|
||||
Total: 40.7,
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
// Mock CreateOrder for updating totals
|
||||
repo.On("CreateOrder", mock.Anything, mock.Anything).Return(updatedOrder, nil)
|
||||
},
|
||||
expectedResult: &entity.Order{
|
||||
ID: 1,
|
||||
PartnerID: 100,
|
||||
Status: order2.Pending.String(),
|
||||
OrderItems: []entity.OrderItem{
|
||||
{ItemID: 3, ItemName: "Salad", Quantity: 1, Price: 8.0, ItemType: "PRODUCT", Notes: "No onions"},
|
||||
{ItemID: 4, ItemName: "Drink", Quantity: 2, Price: 3.0, ItemType: "PRODUCT", Notes: ""},
|
||||
},
|
||||
Total: 40.7,
|
||||
Tax: 3.7,
|
||||
Amount: 37.0,
|
||||
Source: "POS",
|
||||
},
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
name: "order not found",
|
||||
orderID: 999,
|
||||
newItems: []entity.OrderItemRequest{
|
||||
{ProductID: 1, Quantity: 1},
|
||||
},
|
||||
setupMocks: func(repo *MockOrderRepository, calc *MockOrderCalculator, prod *MockProductService) {
|
||||
repo.On("FindByID", mock.Anything, int64(999)).Return(nil, errors.New("order not found"))
|
||||
},
|
||||
expectedResult: nil,
|
||||
expectedError: "failed to fetch order 999: order not found",
|
||||
},
|
||||
{
|
||||
name: "order not in pending status",
|
||||
orderID: 1,
|
||||
newItems: []entity.OrderItemRequest{
|
||||
{ProductID: 1, Quantity: 1},
|
||||
},
|
||||
setupMocks: func(repo *MockOrderRepository, calc *MockOrderCalculator, prod *MockProductService) {
|
||||
existingOrder := &entity.Order{
|
||||
ID: 1,
|
||||
Status: order2.Paid.String(),
|
||||
}
|
||||
repo.On("FindByID", mock.Anything, int64(1)).Return(existingOrder, nil)
|
||||
},
|
||||
expectedResult: nil,
|
||||
expectedError: "cannot add items to order with status PAID",
|
||||
},
|
||||
{
|
||||
name: "validation error for new items",
|
||||
orderID: 1,
|
||||
newItems: []entity.OrderItemRequest{
|
||||
{ProductID: 1, Quantity: 0}, // Invalid quantity
|
||||
},
|
||||
setupMocks: func(repo *MockOrderRepository, calc *MockOrderCalculator, prod *MockProductService) {
|
||||
existingOrder := &entity.Order{
|
||||
ID: 1,
|
||||
PartnerID: 100,
|
||||
Status: order2.Pending.String(),
|
||||
OrderItems: []entity.OrderItem{
|
||||
{ItemID: 1, ItemName: "Burger", Quantity: 2, Price: 10.0, ItemType: "PRODUCT", Notes: "Extra spicy"},
|
||||
},
|
||||
}
|
||||
repo.On("FindByID", mock.Anything, int64(1)).Return(existingOrder, nil)
|
||||
calc.On("ValidateOrderItems", mock.Anything, mock.Anything).Return(
|
||||
nil, nil, errors.New("invalid quantity"),
|
||||
)
|
||||
},
|
||||
expectedResult: nil,
|
||||
expectedError: "invalid quantity",
|
||||
},
|
||||
{
|
||||
name: "product details error",
|
||||
orderID: 1,
|
||||
newItems: []entity.OrderItemRequest{
|
||||
{ProductID: 1, Quantity: 1},
|
||||
},
|
||||
setupMocks: func(repo *MockOrderRepository, calc *MockOrderCalculator, prod *MockProductService) {
|
||||
existingOrder := &entity.Order{
|
||||
ID: 1,
|
||||
PartnerID: 100,
|
||||
Status: order2.Pending.String(),
|
||||
OrderItems: []entity.OrderItem{
|
||||
{ItemID: 1, ItemName: "Burger", Quantity: 2, Price: 10.0, ItemType: "PRODUCT", Notes: "Extra spicy"},
|
||||
},
|
||||
}
|
||||
repo.On("FindByID", mock.Anything, int64(1)).Return(existingOrder, nil)
|
||||
calc.On("ValidateOrderItems", mock.Anything, mock.Anything).Return(
|
||||
[]int64{1},
|
||||
[]entity.OrderItemRequest{{ProductID: 1, Quantity: 1}},
|
||||
nil,
|
||||
)
|
||||
prod.On("GetProductDetails", mock.Anything, []int64{1}, int64(100)).Return(nil, errors.New("product not found"))
|
||||
},
|
||||
expectedResult: nil,
|
||||
expectedError: "product not found",
|
||||
},
|
||||
{
|
||||
name: "calculation error",
|
||||
orderID: 1,
|
||||
newItems: []entity.OrderItemRequest{
|
||||
{ProductID: 1, Quantity: 1},
|
||||
},
|
||||
setupMocks: func(repo *MockOrderRepository, calc *MockOrderCalculator, prod *MockProductService) {
|
||||
existingOrder := &entity.Order{
|
||||
ID: 1,
|
||||
PartnerID: 100,
|
||||
Status: order2.Pending.String(),
|
||||
OrderItems: []entity.OrderItem{
|
||||
{ItemID: 1, ItemName: "Burger", Quantity: 2, Price: 10.0, ItemType: "PRODUCT", Notes: "Extra spicy"},
|
||||
},
|
||||
Source: "POS",
|
||||
}
|
||||
repo.On("FindByID", mock.Anything, int64(1)).Return(existingOrder, nil)
|
||||
calc.On("ValidateOrderItems", mock.Anything, mock.Anything).Return(
|
||||
[]int64{1},
|
||||
[]entity.OrderItemRequest{{ProductID: 1, Quantity: 1}},
|
||||
nil,
|
||||
)
|
||||
productDetails := &entity.ProductDetails{
|
||||
Products: map[int64]*entity.Product{
|
||||
1: {ID: 1, Name: "Burger", Price: 10.0, Type: "PRODUCT"},
|
||||
},
|
||||
}
|
||||
prod.On("GetProductDetails", mock.Anything, []int64{1}, int64(100)).Return(productDetails, nil)
|
||||
calc.On("CalculateOrderTotals", mock.Anything, mock.Anything, mock.Anything, "POS", int64(100)).Return(
|
||||
nil, errors.New("calculation failed"),
|
||||
)
|
||||
},
|
||||
expectedResult: nil,
|
||||
expectedError: "calculation failed",
|
||||
},
|
||||
{
|
||||
name: "repository update error",
|
||||
orderID: 1,
|
||||
newItems: []entity.OrderItemRequest{
|
||||
{ProductID: 1, Quantity: 1},
|
||||
},
|
||||
setupMocks: func(repo *MockOrderRepository, calc *MockOrderCalculator, prod *MockProductService) {
|
||||
existingOrder := &entity.Order{
|
||||
ID: 1,
|
||||
PartnerID: 100,
|
||||
Status: order2.Pending.String(),
|
||||
OrderItems: []entity.OrderItem{
|
||||
{ItemID: 1, ItemName: "Burger", Quantity: 2, Price: 10.0, ItemType: "PRODUCT", Notes: "Extra spicy"},
|
||||
},
|
||||
Source: "POS",
|
||||
}
|
||||
repo.On("FindByID", mock.Anything, int64(1)).Return(existingOrder, nil)
|
||||
calc.On("ValidateOrderItems", mock.Anything, mock.Anything).Return(
|
||||
[]int64{1},
|
||||
[]entity.OrderItemRequest{{ProductID: 1, Quantity: 1}},
|
||||
nil,
|
||||
)
|
||||
productDetails := &entity.ProductDetails{
|
||||
Products: map[int64]*entity.Product{
|
||||
1: {ID: 1, Name: "Burger", Price: 10.0, Type: "PRODUCT"},
|
||||
},
|
||||
}
|
||||
prod.On("GetProductDetails", mock.Anything, []int64{1}, int64(100)).Return(productDetails, nil)
|
||||
calc.On("CalculateOrderTotals", mock.Anything, mock.Anything, mock.Anything, "POS", int64(100)).Return(
|
||||
&entity.OrderCalculation{Subtotal: 30.0, Tax: 3.0, Total: 33.0},
|
||||
nil,
|
||||
)
|
||||
repo.On("CreateOrderItems", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("database error"))
|
||||
},
|
||||
expectedResult: nil,
|
||||
expectedError: "failed to add order items: database error",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Setup mocks
|
||||
mockRepo := &MockOrderRepository{}
|
||||
mockCalc := &MockOrderCalculator{}
|
||||
mockProd := &MockProductService{}
|
||||
|
||||
if tt.setupMocks != nil {
|
||||
tt.setupMocks(mockRepo, mockCalc, mockProd)
|
||||
}
|
||||
|
||||
// Create service
|
||||
service := NewInProgressOrderService(mockRepo, mockCalc, mockProd)
|
||||
|
||||
// Execute
|
||||
ctx := mycontext.NewContext(context.Background())
|
||||
result, err := service.AddItems(ctx, tt.orderID, tt.newItems)
|
||||
|
||||
// Assert
|
||||
if tt.expectedError != "" {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tt.expectedError)
|
||||
assert.Nil(t, result)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, tt.expectedResult.ID, result.ID)
|
||||
assert.Equal(t, tt.expectedResult.PartnerID, result.PartnerID)
|
||||
assert.Equal(t, tt.expectedResult.Status, result.Status)
|
||||
assert.Equal(t, tt.expectedResult.Total, result.Total)
|
||||
// Should only return newly added items
|
||||
assert.Len(t, result.OrderItems, len(tt.expectedResult.OrderItems))
|
||||
}
|
||||
|
||||
// Verify all mocks were called as expected
|
||||
mockRepo.AssertExpectations(t)
|
||||
mockCalc.AssertExpectations(t)
|
||||
mockProd.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInProgressOrderService_HelperMethods(t *testing.T) {
|
||||
service := &inProgressOrderSvc{}
|
||||
|
||||
t.Run("convertToOrderItemRequests", func(t *testing.T) {
|
||||
items := []entity.OrderItem{
|
||||
{ItemID: 1, Quantity: 2, Notes: "Extra spicy"},
|
||||
{ItemID: 2, Quantity: 1, Notes: ""},
|
||||
}
|
||||
|
||||
result := service.convertToOrderItemRequests(items)
|
||||
|
||||
assert.Len(t, result, 2)
|
||||
assert.Equal(t, int64(1), result[0].ProductID)
|
||||
assert.Equal(t, 2, result[0].Quantity)
|
||||
assert.Equal(t, "Extra spicy", result[0].Notes)
|
||||
assert.Equal(t, int64(2), result[1].ProductID)
|
||||
assert.Equal(t, 1, result[1].Quantity)
|
||||
assert.Equal(t, "", result[1].Notes)
|
||||
})
|
||||
|
||||
t.Run("createItemKey", func(t *testing.T) {
|
||||
item := entity.OrderItem{ItemID: 1, Notes: "Extra spicy"}
|
||||
key := service.createItemKey(item)
|
||||
assert.Equal(t, "1_Extra spicy", key)
|
||||
|
||||
item2 := entity.OrderItem{ItemID: 2, Notes: ""}
|
||||
key2 := service.createItemKey(item2)
|
||||
assert.Equal(t, "2_", key2)
|
||||
})
|
||||
|
||||
t.Run("extractNewlyAddedItems", func(t *testing.T) {
|
||||
existingItems := []entity.OrderItem{
|
||||
{ItemID: 1, Notes: "Extra spicy"},
|
||||
{ItemID: 2, Notes: ""},
|
||||
}
|
||||
|
||||
updatedOrder := &entity.Order{
|
||||
OrderItems: []entity.OrderItem{
|
||||
{ItemID: 1, Notes: "Extra spicy"},
|
||||
{ItemID: 2, Notes: ""},
|
||||
{ItemID: 3, Notes: "No onions"},
|
||||
{ItemID: 4, Notes: ""},
|
||||
},
|
||||
}
|
||||
|
||||
result := service.extractNewlyAddedItems(updatedOrder, existingItems)
|
||||
|
||||
assert.Len(t, result, 2)
|
||||
assert.Equal(t, int64(3), result[0].ItemID)
|
||||
assert.Equal(t, "No onions", result[0].Notes)
|
||||
assert.Equal(t, int64(4), result[1].ItemID)
|
||||
assert.Equal(t, "", result[1].Notes)
|
||||
})
|
||||
|
||||
t.Run("extractNewlyAddedItems with no existing items", func(t *testing.T) {
|
||||
updatedOrder := &entity.Order{
|
||||
OrderItems: []entity.OrderItem{
|
||||
{ItemID: 1, Notes: "Extra spicy"},
|
||||
{ItemID: 2, Notes: ""},
|
||||
},
|
||||
}
|
||||
|
||||
result := service.extractNewlyAddedItems(updatedOrder, []entity.OrderItem{})
|
||||
|
||||
assert.Len(t, result, 2)
|
||||
assert.Equal(t, int64(1), result[0].ItemID)
|
||||
assert.Equal(t, int64(2), result[1].ItemID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSave_WithTransaction(t *testing.T) {
|
||||
// Setup
|
||||
mockRepo := new(MockOrderRepository)
|
||||
mockCalculator := new(MockOrderCalculator)
|
||||
mockProduct := new(MockProductService)
|
||||
mockTrx := new(MockTransactionManager)
|
||||
|
||||
service := NewInProgressOrderService(mockRepo, mockCalculator, mockProduct, mockTrx)
|
||||
|
||||
ctx := mycontext.NewContext(context.Background())
|
||||
req := &entity.OrderRequest{
|
||||
PartnerID: 1,
|
||||
OrderItems: []entity.OrderItemRequest{
|
||||
{ProductID: 1, Quantity: 2},
|
||||
},
|
||||
Source: "pos",
|
||||
}
|
||||
|
||||
// Mock transaction
|
||||
mockTx := &gorm.DB{}
|
||||
mockTrx.On("Begin", ctx, mock.Anything).Return(mockTx, nil)
|
||||
mockTrx.On("Commit", mockTx).Return(mockTx)
|
||||
mockTrx.On("Rollback", mockTx).Return(mockTx)
|
||||
|
||||
// Mock calculator
|
||||
productIDs := []int64{1}
|
||||
filteredItems := []entity.OrderItemRequest{{ProductID: 1, Quantity: 2}}
|
||||
mockCalculator.On("ValidateOrderItems", ctx, req.OrderItems).Return(productIDs, filteredItems, nil)
|
||||
|
||||
// Mock product service
|
||||
productDetails := &entity.ProductDetails{
|
||||
Products: map[int64]*entity.Product{
|
||||
1: {ID: 1, Name: "Test Product", Price: 10.0},
|
||||
},
|
||||
}
|
||||
mockProduct.On("GetProductDetails", ctx, productIDs, req.PartnerID).Return(productDetails, nil)
|
||||
|
||||
// Mock calculation
|
||||
calculation := &entity.OrderCalculation{
|
||||
Subtotal: 20.0,
|
||||
Tax: 2.0,
|
||||
Total: 22.0,
|
||||
}
|
||||
mockCalculator.On("CalculateOrderTotals", ctx, req.OrderItems, productDetails, req.Source, req.PartnerID).Return(calculation, nil)
|
||||
|
||||
// Mock repository calls
|
||||
createdOrder := &entity.Order{ID: 1, PartnerID: 1}
|
||||
mockRepo.On("CreateOrder", ctx, mock.AnythingOfType("*entity.Order"), mockTx).Return(createdOrder, nil)
|
||||
mockRepo.On("CreateOrderItems", ctx, int64(1), mock.AnythingOfType("[]entity.OrderItem"), mockTx).Return(nil)
|
||||
|
||||
fullOrder := &entity.Order{ID: 1, PartnerID: 1, OrderItems: []entity.OrderItem{}}
|
||||
mockRepo.On("FindByID", ctx, int64(1)).Return(fullOrder, nil)
|
||||
|
||||
// Execute
|
||||
result, err := service.Save(ctx, req)
|
||||
|
||||
// Assert
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, int64(1), result.ID)
|
||||
|
||||
// Verify all mocks were called
|
||||
mockTrx.AssertExpectations(t)
|
||||
mockRepo.AssertExpectations(t)
|
||||
mockCalculator.AssertExpectations(t)
|
||||
mockProduct.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestAddItems_WithTransaction(t *testing.T) {
|
||||
// Setup
|
||||
mockRepo := new(MockOrderRepository)
|
||||
mockCalculator := new(MockOrderCalculator)
|
||||
mockProduct := new(MockProductService)
|
||||
mockTrx := new(MockTransactionManager)
|
||||
|
||||
service := NewInProgressOrderService(mockRepo, mockCalculator, mockProduct, mockTrx)
|
||||
|
||||
ctx := mycontext.NewContext(context.Background())
|
||||
orderID := int64(1)
|
||||
newItems := []entity.OrderItemRequest{
|
||||
{ProductID: 2, Quantity: 1},
|
||||
}
|
||||
|
||||
// Mock existing order
|
||||
existingOrder := &entity.Order{
|
||||
ID: orderID,
|
||||
Status: "pending",
|
||||
OrderItems: []entity.OrderItem{
|
||||
{ItemID: 1, Quantity: 2},
|
||||
},
|
||||
}
|
||||
mockRepo.On("FindByID", ctx, orderID).Return(existingOrder, nil)
|
||||
|
||||
// Mock transaction
|
||||
mockTx := &gorm.DB{}
|
||||
mockTrx.On("Begin", ctx, mock.Anything).Return(mockTx, nil)
|
||||
mockTrx.On("Commit", mockTx).Return(mockTx)
|
||||
mockTrx.On("Rollback", mockTx).Return(mockTx)
|
||||
|
||||
// Mock calculator
|
||||
productIDs := []int64{2}
|
||||
filteredItems := []entity.OrderItemRequest{{ProductID: 2, Quantity: 1}}
|
||||
mockCalculator.On("ValidateOrderItems", ctx, newItems).Return(productIDs, filteredItems, nil)
|
||||
|
||||
// Mock product service
|
||||
productDetails := &entity.ProductDetails{
|
||||
Products: map[int64]*entity.Product{
|
||||
2: {ID: 2, Name: "New Product", Price: 15.0},
|
||||
},
|
||||
}
|
||||
mockProduct.On("GetProductDetails", ctx, productIDs, existingOrder.PartnerID).Return(productDetails, nil)
|
||||
|
||||
// Mock repository calls
|
||||
mockRepo.On("CreateOrderItems", ctx, orderID, mock.AnythingOfType("[]entity.OrderItem"), mockTx).Return(nil)
|
||||
|
||||
updatedOrder := &entity.Order{
|
||||
ID: orderID,
|
||||
Status: "pending",
|
||||
OrderItems: []entity.OrderItem{
|
||||
{ItemID: 1, Quantity: 2},
|
||||
{ItemID: 2, Quantity: 1},
|
||||
},
|
||||
}
|
||||
mockRepo.On("FindByID", ctx, orderID).Return(updatedOrder, nil)
|
||||
|
||||
// Mock calculation for updated totals
|
||||
combinedItems := []entity.OrderItemRequest{
|
||||
{ProductID: 1, Quantity: 2},
|
||||
{ProductID: 2, Quantity: 1},
|
||||
}
|
||||
updatedCalculation := &entity.OrderCalculation{
|
||||
Subtotal: 35.0,
|
||||
Tax: 3.5,
|
||||
Total: 38.5,
|
||||
}
|
||||
mockCalculator.On("CalculateOrderTotals", ctx, combinedItems, productDetails, updatedOrder.Source, updatedOrder.PartnerID).Return(updatedCalculation, nil)
|
||||
|
||||
updatedOrderWithTotals := &entity.Order{
|
||||
ID: orderID,
|
||||
Status: "pending",
|
||||
Total: 38.5,
|
||||
Tax: 3.5,
|
||||
Amount: 35.0,
|
||||
OrderItems: []entity.OrderItem{
|
||||
{ItemID: 1, Quantity: 2},
|
||||
{ItemID: 2, Quantity: 1},
|
||||
},
|
||||
}
|
||||
mockRepo.On("CreateOrder", ctx, mock.AnythingOfType("*entity.Order"), mockTx).Return(updatedOrderWithTotals, nil)
|
||||
|
||||
// Execute
|
||||
result, err := service.AddItems(ctx, orderID, newItems)
|
||||
|
||||
// Assert
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, orderID, result.ID)
|
||||
|
||||
// Verify all mocks were called
|
||||
mockTrx.AssertExpectations(t)
|
||||
mockRepo.AssertExpectations(t)
|
||||
mockCalculator.AssertExpectations(t)
|
||||
mockProduct.AssertExpectations(t)
|
||||
}
|
||||
Reference in New Issue
Block a user