Add ingredieints
This commit is contained in:
@@ -3,9 +3,11 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"apskel-pos-be/internal/models"
|
||||
"apskel-pos-be/internal/processor"
|
||||
"apskel-pos-be/internal/repository"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
@@ -26,11 +28,13 @@ type OrderService interface {
|
||||
|
||||
type OrderServiceImpl struct {
|
||||
orderProcessor processor.OrderProcessor
|
||||
tableRepo repository.TableRepositoryInterface
|
||||
}
|
||||
|
||||
func NewOrderServiceImpl(orderProcessor processor.OrderProcessor) *OrderServiceImpl {
|
||||
func NewOrderServiceImpl(orderProcessor processor.OrderProcessor, tableRepo repository.TableRepositoryInterface) *OrderServiceImpl {
|
||||
return &OrderServiceImpl{
|
||||
orderProcessor: orderProcessor,
|
||||
tableRepo: tableRepo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,11 +43,23 @@ func (s *OrderServiceImpl) CreateOrder(ctx context.Context, req *models.CreateOr
|
||||
return nil, fmt.Errorf("validation error: %w", err)
|
||||
}
|
||||
|
||||
if req.TableID != nil {
|
||||
if err := s.validateTable(ctx, req); err != nil {
|
||||
return nil, fmt.Errorf("table validation failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
response, err := s.orderProcessor.CreateOrder(ctx, req, organizationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create order: %w", err)
|
||||
}
|
||||
|
||||
if req.TableID != nil {
|
||||
if err := s.occupyTableWithOrder(ctx, *req.TableID, response.ID); err != nil {
|
||||
fmt.Printf("Warning: failed to occupy table %s with order %s: %v\n", *req.TableID, response.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
@@ -121,6 +137,12 @@ func (s *OrderServiceImpl) VoidOrder(ctx context.Context, req *models.VoidOrderR
|
||||
return fmt.Errorf("failed to void order: %w", err)
|
||||
}
|
||||
|
||||
// Release table if order is voided
|
||||
if err := s.handleTableReleaseOnVoid(ctx, req.OrderID); err != nil {
|
||||
// Log the error but don't fail the void operation
|
||||
fmt.Printf("Warning: failed to handle table release for voided order %s: %v\n", req.OrderID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -151,12 +173,15 @@ func (s *OrderServiceImpl) CreatePayment(ctx context.Context, req *models.Create
|
||||
return nil, fmt.Errorf("validation error: %w", err)
|
||||
}
|
||||
|
||||
// Process payment creation
|
||||
response, err := s.orderProcessor.CreatePayment(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create payment: %w", err)
|
||||
}
|
||||
|
||||
if err := s.handleTableReleaseOnPayment(ctx, req.OrderID); err != nil {
|
||||
fmt.Printf("Warning: failed to handle table release for order %s: %v\n", req.OrderID, err)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
@@ -247,6 +272,11 @@ func (s *OrderServiceImpl) validateCreateOrderRequest(req *models.CreateOrderReq
|
||||
return fmt.Errorf("user ID is required")
|
||||
}
|
||||
|
||||
// Validate table ID if provided
|
||||
if req.TableID != nil && *req.TableID == uuid.Nil {
|
||||
return fmt.Errorf("table ID cannot be nil if provided")
|
||||
}
|
||||
|
||||
if len(req.OrderItems) == 0 {
|
||||
return fmt.Errorf("order must have at least one item")
|
||||
}
|
||||
@@ -445,3 +475,71 @@ func (s *OrderServiceImpl) validateSplitBillRequest(req *models.SplitBillRequest
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateTable validates that the table exists and is available for occupation
|
||||
func (s *OrderServiceImpl) validateTable(ctx context.Context, req *models.CreateOrderRequest) error {
|
||||
// Validate table exists and is available
|
||||
table, err := s.tableRepo.GetByID(ctx, *req.TableID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("table not found: %w", err)
|
||||
}
|
||||
|
||||
// Check if table belongs to the same outlet
|
||||
if table.OutletID != req.OutletID {
|
||||
return fmt.Errorf("table does not belong to the specified outlet")
|
||||
}
|
||||
|
||||
// Check if table is available for occupation
|
||||
if !table.CanBeOccupied() {
|
||||
return fmt.Errorf("table is not available for occupation (current status: %s)", table.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *OrderServiceImpl) occupyTableWithOrder(ctx context.Context, tableID, orderID uuid.UUID) error {
|
||||
startTime := time.Now()
|
||||
if err := s.tableRepo.OccupyTable(ctx, tableID, orderID, &startTime); err != nil {
|
||||
return fmt.Errorf("failed to occupy table: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *OrderServiceImpl) handleTableReleaseOnPayment(ctx context.Context, orderID uuid.UUID) error {
|
||||
order, err := s.orderProcessor.GetOrderByID(ctx, orderID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get order: %w", err)
|
||||
}
|
||||
|
||||
if order.PaymentStatus == "completed" {
|
||||
table, err := s.tableRepo.GetByOrderID(ctx, orderID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if table != nil {
|
||||
if err := s.tableRepo.ReleaseTable(ctx, table.ID, order.TotalAmount); err != nil {
|
||||
return fmt.Errorf("failed to release table: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleTableReleaseOnVoid releases the table when an order is voided
|
||||
func (s *OrderServiceImpl) handleTableReleaseOnVoid(ctx context.Context, orderID uuid.UUID) error {
|
||||
table, err := s.tableRepo.GetByOrderID(ctx, orderID)
|
||||
if err != nil {
|
||||
// Table might not exist or not be occupied, which is fine
|
||||
return nil
|
||||
}
|
||||
|
||||
if table != nil {
|
||||
if err := s.tableRepo.ReleaseTable(ctx, table.ID, 0); err != nil {
|
||||
return fmt.Errorf("failed to release table: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"apskel-pos-be/internal/entities"
|
||||
"apskel-pos-be/internal/models"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// Mock implementations for testing
|
||||
type MockOrderProcessor struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockOrderProcessor) CreateOrder(ctx context.Context, req *models.CreateOrderRequest, organizationID uuid.UUID) (*models.OrderResponse, error) {
|
||||
args := m.Called(ctx, req, organizationID)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*models.OrderResponse), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockOrderProcessor) AddToOrder(ctx context.Context, orderID uuid.UUID, req *models.AddToOrderRequest) (*models.AddToOrderResponse, error) {
|
||||
args := m.Called(ctx, orderID, req)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*models.AddToOrderResponse), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockOrderProcessor) UpdateOrder(ctx context.Context, id uuid.UUID, req *models.UpdateOrderRequest) (*models.OrderResponse, error) {
|
||||
args := m.Called(ctx, id, req)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*models.OrderResponse), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockOrderProcessor) GetOrderByID(ctx context.Context, id uuid.UUID) (*models.OrderResponse, error) {
|
||||
args := m.Called(ctx, id)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*models.OrderResponse), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockOrderProcessor) ListOrders(ctx context.Context, req *models.ListOrdersRequest) (*models.ListOrdersResponse, error) {
|
||||
args := m.Called(ctx, req)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*models.ListOrdersResponse), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockOrderProcessor) VoidOrder(ctx context.Context, req *models.VoidOrderRequest, voidedBy uuid.UUID) error {
|
||||
args := m.Called(ctx, req, voidedBy)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockOrderProcessor) RefundOrder(ctx context.Context, id uuid.UUID, req *models.RefundOrderRequest, refundedBy uuid.UUID) error {
|
||||
args := m.Called(ctx, id, req, refundedBy)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockOrderProcessor) CreatePayment(ctx context.Context, req *models.CreatePaymentRequest) (*models.PaymentResponse, error) {
|
||||
args := m.Called(ctx, req)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*models.PaymentResponse), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockOrderProcessor) RefundPayment(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID) error {
|
||||
args := m.Called(ctx, paymentID, refundAmount, reason, refundedBy)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockOrderProcessor) SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID) (*models.SetOrderCustomerResponse, error) {
|
||||
args := m.Called(ctx, orderID, req, organizationID)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*models.SetOrderCustomerResponse), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockOrderProcessor) SplitBill(ctx context.Context, req *models.SplitBillRequest) (*models.SplitBillResponse, error) {
|
||||
args := m.Called(ctx, req)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*models.SplitBillResponse), args.Error(1)
|
||||
}
|
||||
|
||||
type MockTableRepository struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockTableRepository) Create(ctx context.Context, table *entities.Table) error {
|
||||
args := m.Called(ctx, table)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockTableRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.Table, error) {
|
||||
args := m.Called(ctx, id)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*entities.Table), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockTableRepository) GetByOutletID(ctx context.Context, outletID uuid.UUID) ([]entities.Table, error) {
|
||||
args := m.Called(ctx, outletID)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]entities.Table), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockTableRepository) GetByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]entities.Table, error) {
|
||||
args := m.Called(ctx, organizationID)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]entities.Table), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockTableRepository) Update(ctx context.Context, table *entities.Table) error {
|
||||
args := m.Called(ctx, table)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockTableRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
args := m.Called(ctx, id)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockTableRepository) List(ctx context.Context, organizationID, outletID *uuid.UUID, status *string, isActive *bool, search string, page, limit int) ([]entities.Table, int64, error) {
|
||||
args := m.Called(ctx, organizationID, outletID, status, isActive, search, page, limit)
|
||||
if args.Get(0) == nil {
|
||||
return nil, 0, args.Error(2)
|
||||
}
|
||||
return args.Get(0).([]entities.Table), args.Get(1).(int64), args.Error(2)
|
||||
}
|
||||
|
||||
func (m *MockTableRepository) GetAvailableTables(ctx context.Context, outletID uuid.UUID) ([]entities.Table, error) {
|
||||
args := m.Called(ctx, outletID)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]entities.Table), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockTableRepository) GetOccupiedTables(ctx context.Context, outletID uuid.UUID) ([]entities.Table, error) {
|
||||
args := m.Called(ctx, outletID)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).([]entities.Table), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockTableRepository) OccupyTable(ctx context.Context, tableID, orderID uuid.UUID, startTime *time.Time) error {
|
||||
args := m.Called(ctx, tableID, orderID, startTime)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockTableRepository) ReleaseTable(ctx context.Context, tableID uuid.UUID, paymentAmount float64) error {
|
||||
args := m.Called(ctx, tableID, paymentAmount)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *MockTableRepository) GetByOrderID(ctx context.Context, orderID uuid.UUID) (*entities.Table, error) {
|
||||
args := m.Called(ctx, orderID)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*entities.Table), args.Error(1)
|
||||
}
|
||||
|
||||
func TestCreateOrderWithTableOccupation(t *testing.T) {
|
||||
// Setup
|
||||
ctx := context.Background()
|
||||
organizationID := uuid.New()
|
||||
outletID := uuid.New()
|
||||
userID := uuid.New()
|
||||
tableID := uuid.New()
|
||||
orderID := uuid.New()
|
||||
|
||||
// Create mock table
|
||||
mockTable := &entities.Table{
|
||||
ID: tableID,
|
||||
OrganizationID: organizationID,
|
||||
OutletID: outletID,
|
||||
TableName: "Table 1",
|
||||
Status: "available",
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
// Create mock order response
|
||||
mockOrderResponse := &models.OrderResponse{
|
||||
ID: orderID,
|
||||
// Add other required fields
|
||||
}
|
||||
|
||||
// Create mock repositories
|
||||
mockTableRepo := &MockTableRepository{}
|
||||
mockOrderProcessor := &MockOrderProcessor{}
|
||||
|
||||
// Set up expectations
|
||||
mockTableRepo.On("GetByID", ctx, tableID).Return(mockTable, nil)
|
||||
mockOrderProcessor.On("CreateOrder", ctx, mock.AnythingOfType("*models.CreateOrderRequest"), organizationID).Return(mockOrderResponse, nil)
|
||||
mockTableRepo.On("OccupyTable", ctx, tableID, orderID, mock.AnythingOfType("*time.Time")).Return(nil)
|
||||
|
||||
// Create service with mock dependencies
|
||||
service := &OrderServiceImpl{
|
||||
orderProcessor: mockOrderProcessor,
|
||||
tableRepo: mockTableRepo,
|
||||
}
|
||||
|
||||
// Create test request
|
||||
req := &models.CreateOrderRequest{
|
||||
OutletID: outletID,
|
||||
UserID: userID,
|
||||
TableID: &tableID,
|
||||
OrderType: "dine_in",
|
||||
OrderItems: []models.CreateOrderItemRequest{
|
||||
{
|
||||
ProductID: uuid.New(),
|
||||
Quantity: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Test order creation with table occupation
|
||||
response, err := service.CreateOrder(ctx, req, organizationID)
|
||||
|
||||
// Assertions
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, response)
|
||||
assert.Equal(t, orderID, response.ID)
|
||||
|
||||
// Verify mock calls
|
||||
mockTableRepo.AssertExpectations(t)
|
||||
mockOrderProcessor.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestCreateOrderTableValidationFailure(t *testing.T) {
|
||||
// Setup
|
||||
ctx := context.Background()
|
||||
organizationID := uuid.New()
|
||||
outletID := uuid.New()
|
||||
userID := uuid.New()
|
||||
tableID := uuid.New()
|
||||
|
||||
// Create mock table that's already occupied
|
||||
mockTable := &entities.Table{
|
||||
ID: tableID,
|
||||
OrganizationID: organizationID,
|
||||
OutletID: outletID,
|
||||
TableName: "Table 1",
|
||||
Status: "occupied", // Already occupied
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
// Create mock repositories
|
||||
mockTableRepo := &MockTableRepository{}
|
||||
mockOrderProcessor := &MockOrderProcessor{}
|
||||
|
||||
// Set up expectations
|
||||
mockTableRepo.On("GetByID", ctx, tableID).Return(mockTable, nil)
|
||||
|
||||
// Create service with mock dependencies
|
||||
service := &OrderServiceImpl{
|
||||
orderProcessor: mockOrderProcessor,
|
||||
tableRepo: mockTableRepo,
|
||||
}
|
||||
|
||||
// Create test request
|
||||
req := &models.CreateOrderRequest{
|
||||
OutletID: outletID,
|
||||
UserID: userID,
|
||||
TableID: &tableID,
|
||||
OrderType: "dine_in",
|
||||
OrderItems: []models.CreateOrderItemRequest{
|
||||
{
|
||||
ProductID: uuid.New(),
|
||||
Quantity: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Test order creation with occupied table
|
||||
response, err := service.CreateOrder(ctx, req, organizationID)
|
||||
|
||||
// Assertions
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, response)
|
||||
assert.Contains(t, err.Error(), "table is not available for occupation")
|
||||
|
||||
// Verify mock calls
|
||||
mockTableRepo.AssertExpectations(t)
|
||||
mockOrderProcessor.AssertNotCalled(t, "CreateOrder")
|
||||
}
|
||||
Reference in New Issue
Block a user