Add ingredieints
This commit is contained in:
parent
93a3b29ae9
commit
fe4f17b34d
@ -40,7 +40,7 @@ func NewApp(db *gorm.DB) *App {
|
|||||||
func (a *App) Initialize(cfg *config.Config) error {
|
func (a *App) Initialize(cfg *config.Config) error {
|
||||||
repos := a.initRepositories()
|
repos := a.initRepositories()
|
||||||
processors := a.initProcessors(cfg, repos)
|
processors := a.initProcessors(cfg, repos)
|
||||||
services := a.initServices(processors, cfg)
|
services := a.initServices(processors, repos, cfg)
|
||||||
validators := a.initValidators()
|
validators := a.initValidators()
|
||||||
middleware := a.initMiddleware(services)
|
middleware := a.initMiddleware(services)
|
||||||
healthHandler := handler.NewHealthHandler()
|
healthHandler := handler.NewHealthHandler()
|
||||||
@ -140,7 +140,7 @@ type repositories struct {
|
|||||||
fileRepo *repository.FileRepositoryImpl
|
fileRepo *repository.FileRepositoryImpl
|
||||||
customerRepo *repository.CustomerRepository
|
customerRepo *repository.CustomerRepository
|
||||||
analyticsRepo *repository.AnalyticsRepositoryImpl
|
analyticsRepo *repository.AnalyticsRepositoryImpl
|
||||||
tableRepo *repository.TableRepository
|
tableRepo repository.TableRepositoryInterface
|
||||||
unitRepo *repository.UnitRepository
|
unitRepo *repository.UnitRepository
|
||||||
ingredientRepo *repository.IngredientRepository
|
ingredientRepo *repository.IngredientRepository
|
||||||
}
|
}
|
||||||
@ -232,7 +232,7 @@ type services struct {
|
|||||||
ingredientService *service.IngredientServiceImpl
|
ingredientService *service.IngredientServiceImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) initServices(processors *processors, cfg *config.Config) *services {
|
func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
|
||||||
authConfig := cfg.Auth()
|
authConfig := cfg.Auth()
|
||||||
jwtSecret := authConfig.AccessTokenSecret()
|
jwtSecret := authConfig.AccessTokenSecret()
|
||||||
authService := service.NewAuthService(processors.userProcessor, jwtSecret)
|
authService := service.NewAuthService(processors.userProcessor, jwtSecret)
|
||||||
@ -243,7 +243,7 @@ func (a *App) initServices(processors *processors, cfg *config.Config) *services
|
|||||||
productService := service.NewProductService(processors.productProcessor)
|
productService := service.NewProductService(processors.productProcessor)
|
||||||
productVariantService := service.NewProductVariantService(processors.productVariantProcessor)
|
productVariantService := service.NewProductVariantService(processors.productVariantProcessor)
|
||||||
inventoryService := service.NewInventoryService(processors.inventoryProcessor)
|
inventoryService := service.NewInventoryService(processors.inventoryProcessor)
|
||||||
orderService := service.NewOrderServiceImpl(processors.orderProcessor)
|
orderService := service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo)
|
||||||
paymentMethodService := service.NewPaymentMethodService(processors.paymentMethodProcessor)
|
paymentMethodService := service.NewPaymentMethodService(processors.paymentMethodProcessor)
|
||||||
fileService := service.NewFileServiceImpl(processors.fileProcessor)
|
fileService := service.NewFileServiceImpl(processors.fileProcessor)
|
||||||
var customerService service.CustomerService = service.NewCustomerService(processors.customerProcessor)
|
var customerService service.CustomerService = service.NewCustomerService(processors.customerProcessor)
|
||||||
|
|||||||
@ -10,6 +10,7 @@ type CreateOrderRequest struct {
|
|||||||
OutletID uuid.UUID `json:"outlet_id" validate:"required"`
|
OutletID uuid.UUID `json:"outlet_id" validate:"required"`
|
||||||
UserID uuid.UUID `json:"user_id" validate:"required"`
|
UserID uuid.UUID `json:"user_id" validate:"required"`
|
||||||
CustomerID *uuid.UUID `json:"customer_id"`
|
CustomerID *uuid.UUID `json:"customer_id"`
|
||||||
|
TableID *uuid.UUID `json:"table_id,omitempty" validate:"omitempty"`
|
||||||
TableNumber *string `json:"table_number,omitempty" validate:"omitempty,max=50"`
|
TableNumber *string `json:"table_number,omitempty" validate:"omitempty,max=50"`
|
||||||
OrderType string `json:"order_type" validate:"required,oneof=dine_in takeaway delivery"`
|
OrderType string `json:"order_type" validate:"required,oneof=dine_in takeaway delivery"`
|
||||||
Notes *string `json:"notes,omitempty" validate:"omitempty,max=1000"`
|
Notes *string `json:"notes,omitempty" validate:"omitempty,max=1000"`
|
||||||
|
|||||||
@ -7,17 +7,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Ingredient struct {
|
type Ingredient struct {
|
||||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
OrganizationID uuid.UUID `gorm:"type:uuid;not null;index" json:"organization_id"`
|
OrganizationID uuid.UUID `gorm:"type:uuid;not null;index" json:"organization_id"`
|
||||||
OutletID *uuid.UUID `gorm:"type:uuid;index" json:"outlet_id"`
|
OutletID *uuid.UUID `gorm:"type:uuid;index" json:"outlet_id"`
|
||||||
Name string `gorm:"not null;size:255" json:"name"`
|
Name string `gorm:"not null;size:255" json:"name"`
|
||||||
UnitID uuid.UUID `gorm:"type:uuid;not null;index" json:"unit_id"`
|
UnitID uuid.UUID `gorm:"type:uuid;not null;index" json:"unit_id"`
|
||||||
Cost float64 `gorm:"type:decimal(10,2);default:0.00" json:"cost"`
|
Cost float64 `gorm:"type:decimal(10,2);default:0.00" json:"cost"`
|
||||||
Stock float64 `gorm:"type:decimal(10,2);default:0.00" json:"stock"`
|
Stock float64 `gorm:"type:decimal(10,2);default:0.00" json:"stock"`
|
||||||
IsSemiFinished bool `gorm:"default:false" json:"is_semi_finished"`
|
IsSemiFinished bool `gorm:"default:false" json:"is_semi_finished"`
|
||||||
IsActive bool `gorm:"default:true" json:"is_active"`
|
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||||
Metadata map[string]any `gorm:"type:jsonb;default:'{}'" json:"metadata"`
|
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
Unit *Unit `gorm:"foreignKey:UnitID" json:"unit,omitempty"`
|
Unit *Unit `gorm:"foreignKey:UnitID" json:"unit,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,11 @@ type Unit struct {
|
|||||||
Name string `gorm:"not null;size:255" json:"name"`
|
Name string `gorm:"not null;size:255" json:"name"`
|
||||||
Abbreviation *string `gorm:"size:50" json:"abbreviation"`
|
Abbreviation *string `gorm:"size:50" json:"abbreviation"`
|
||||||
IsActive bool `gorm:"default:true" json:"is_active"`
|
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||||
|
DeletedAt *time.Time `gorm:"index" json:"deleted_at,omitempty"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (Unit) TableName() string {
|
||||||
|
return "units"
|
||||||
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ func MapUnitEntityToModel(entity *entities.Unit) *models.Unit {
|
|||||||
Name: entity.Name,
|
Name: entity.Name,
|
||||||
Abbreviation: entity.Abbreviation,
|
Abbreviation: entity.Abbreviation,
|
||||||
IsActive: entity.IsActive,
|
IsActive: entity.IsActive,
|
||||||
|
DeletedAt: entity.DeletedAt,
|
||||||
CreatedAt: entity.CreatedAt,
|
CreatedAt: entity.CreatedAt,
|
||||||
UpdatedAt: entity.UpdatedAt,
|
UpdatedAt: entity.UpdatedAt,
|
||||||
}
|
}
|
||||||
@ -34,6 +35,7 @@ func MapUnitModelToEntity(model *models.Unit) *entities.Unit {
|
|||||||
Name: model.Name,
|
Name: model.Name,
|
||||||
Abbreviation: model.Abbreviation,
|
Abbreviation: model.Abbreviation,
|
||||||
IsActive: model.IsActive,
|
IsActive: model.IsActive,
|
||||||
|
DeletedAt: model.DeletedAt,
|
||||||
CreatedAt: model.CreatedAt,
|
CreatedAt: model.CreatedAt,
|
||||||
UpdatedAt: model.UpdatedAt,
|
UpdatedAt: model.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,65 +1,66 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Ingredient struct {
|
type Ingredient struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
OrganizationID uuid.UUID `json:"organization_id"`
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
OutletID *uuid.UUID `json:"outlet_id"`
|
OutletID *uuid.UUID `json:"outlet_id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
UnitID uuid.UUID `json:"unit_id"`
|
UnitID uuid.UUID `json:"unit_id"`
|
||||||
Cost float64 `json:"cost"`
|
Cost float64 `json:"cost"`
|
||||||
Stock float64 `json:"stock"`
|
Stock float64 `json:"stock"`
|
||||||
IsSemiFinished bool `json:"is_semi_finished"`
|
IsSemiFinished bool `json:"is_semi_finished"`
|
||||||
IsActive bool `json:"is_active"`
|
IsActive bool `json:"is_active"`
|
||||||
Metadata map[string]any `json:"metadata"`
|
Metadata entities.Metadata `json:"metadata"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
Unit *Unit `json:"unit,omitempty"`
|
Unit *Unit `json:"unit,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateIngredientRequest struct {
|
type CreateIngredientRequest struct {
|
||||||
OrganizationID uuid.UUID `json:"organization_id"`
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
OutletID *uuid.UUID `json:"outlet_id"`
|
OutletID *uuid.UUID `json:"outlet_id"`
|
||||||
Name string `json:"name" validate:"required,min=1,max=255"`
|
Name string `json:"name" validate:"required,min=1,max=255"`
|
||||||
UnitID uuid.UUID `json:"unit_id" validate:"required"`
|
UnitID uuid.UUID `json:"unit_id" validate:"required"`
|
||||||
Cost float64 `json:"cost" validate:"min=0"`
|
Cost float64 `json:"cost" validate:"min=0"`
|
||||||
Stock float64 `json:"stock" validate:"min=0"`
|
Stock float64 `json:"stock" validate:"min=0"`
|
||||||
IsSemiFinished bool `json:"is_semi_finished"`
|
IsSemiFinished bool `json:"is_semi_finished"`
|
||||||
IsActive bool `json:"is_active"`
|
IsActive bool `json:"is_active"`
|
||||||
Metadata map[string]any `json:"metadata"`
|
Metadata entities.Metadata `json:"metadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateIngredientRequest struct {
|
type UpdateIngredientRequest struct {
|
||||||
OutletID *uuid.UUID `json:"outlet_id"`
|
OutletID *uuid.UUID `json:"outlet_id"`
|
||||||
Name string `json:"name" validate:"required,min=1,max=255"`
|
Name string `json:"name" validate:"required,min=1,max=255"`
|
||||||
UnitID uuid.UUID `json:"unit_id" validate:"required"`
|
UnitID uuid.UUID `json:"unit_id" validate:"required"`
|
||||||
Cost float64 `json:"cost" validate:"min=0"`
|
Cost float64 `json:"cost" validate:"min=0"`
|
||||||
Stock float64 `json:"stock" validate:"min=0"`
|
Stock float64 `json:"stock" validate:"min=0"`
|
||||||
IsSemiFinished bool `json:"is_semi_finished"`
|
IsSemiFinished bool `json:"is_semi_finished"`
|
||||||
IsActive bool `json:"is_active"`
|
IsActive bool `json:"is_active"`
|
||||||
Metadata map[string]any `json:"metadata"`
|
Metadata entities.Metadata `json:"metadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type IngredientResponse struct {
|
type IngredientResponse struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
OrganizationID uuid.UUID `json:"organization_id"`
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
OutletID *uuid.UUID `json:"outlet_id"`
|
OutletID *uuid.UUID `json:"outlet_id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
UnitID uuid.UUID `json:"unit_id"`
|
UnitID uuid.UUID `json:"unit_id"`
|
||||||
Cost float64 `json:"cost"`
|
Cost float64 `json:"cost"`
|
||||||
Stock float64 `json:"stock"`
|
Stock float64 `json:"stock"`
|
||||||
IsSemiFinished bool `json:"is_semi_finished"`
|
IsSemiFinished bool `json:"is_semi_finished"`
|
||||||
IsActive bool `json:"is_active"`
|
IsActive bool `json:"is_active"`
|
||||||
Metadata map[string]any `json:"metadata"`
|
Metadata entities.Metadata `json:"metadata"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
Unit *Unit `json:"unit,omitempty"`
|
Unit *Unit `json:"unit,omitempty"`
|
||||||
|
|||||||
@ -85,6 +85,7 @@ type CreateOrderRequest struct {
|
|||||||
OutletID uuid.UUID `validate:"required"`
|
OutletID uuid.UUID `validate:"required"`
|
||||||
UserID uuid.UUID `validate:"required"`
|
UserID uuid.UUID `validate:"required"`
|
||||||
CustomerID *uuid.UUID `validate:"omitempty"`
|
CustomerID *uuid.UUID `validate:"omitempty"`
|
||||||
|
TableID *uuid.UUID `validate:"omitempty"`
|
||||||
TableNumber *string `validate:"omitempty,max=50"`
|
TableNumber *string `validate:"omitempty,max=50"`
|
||||||
OrderType constants.OrderType `validate:"required"`
|
OrderType constants.OrderType `validate:"required"`
|
||||||
OrderItems []CreateOrderItemRequest `validate:"required,min=1,dive"`
|
OrderItems []CreateOrderItemRequest `validate:"required,min=1,dive"`
|
||||||
|
|||||||
@ -13,6 +13,7 @@ type Unit struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Abbreviation *string `json:"abbreviation"`
|
Abbreviation *string `json:"abbreviation"`
|
||||||
IsActive bool `json:"is_active"`
|
IsActive bool `json:"is_active"`
|
||||||
|
DeletedAt *time.Time `json:"deleted_at,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
@ -39,6 +40,7 @@ type UnitResponse struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Abbreviation *string `json:"abbreviation"`
|
Abbreviation *string `json:"abbreviation"`
|
||||||
IsActive bool `json:"is_active"`
|
IsActive bool `json:"is_active"`
|
||||||
|
DeletedAt *time.Time `json:"deleted_at,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,19 +43,16 @@ func (p *IngredientProcessorImpl) CreateIngredient(ctx context.Context, req *mod
|
|||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save to database
|
|
||||||
err = p.ingredientRepo.Create(ctx, ingredient)
|
err = p.ingredientRepo.Create(ctx, ingredient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get with relations
|
|
||||||
ingredientWithUnit, err := p.ingredientRepo.GetByID(ctx, ingredient.ID, req.OrganizationID)
|
ingredientWithUnit, err := p.ingredientRepo.GetByID(ctx, ingredient.ID, req.OrganizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map to response
|
|
||||||
ingredientModel := mappers.MapIngredientEntityToModel(ingredientWithUnit)
|
ingredientModel := mappers.MapIngredientEntityToModel(ingredientWithUnit)
|
||||||
response := &models.IngredientResponse{
|
response := &models.IngredientResponse{
|
||||||
ID: ingredientModel.ID,
|
ID: ingredientModel.ID,
|
||||||
|
|||||||
@ -82,10 +82,6 @@ type PaymentMethodRepository interface {
|
|||||||
GetByID(ctx context.Context, id uuid.UUID) (*entities.PaymentMethod, error)
|
GetByID(ctx context.Context, id uuid.UUID) (*entities.PaymentMethod, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProductVariantRepository interface {
|
|
||||||
GetByID(ctx context.Context, id uuid.UUID) (*entities.ProductVariant, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type CustomerRepository interface {
|
type CustomerRepository interface {
|
||||||
GetByIDAndOrganization(ctx context.Context, id, organizationID uuid.UUID) (*entities.Customer, error)
|
GetByIDAndOrganization(ctx context.Context, id, organizationID uuid.UUID) (*entities.Customer, error)
|
||||||
}
|
}
|
||||||
@ -111,7 +107,7 @@ type OrderProcessorImpl struct {
|
|||||||
paymentMethodRepo PaymentMethodRepository
|
paymentMethodRepo PaymentMethodRepository
|
||||||
inventoryRepo repository.InventoryRepository
|
inventoryRepo repository.InventoryRepository
|
||||||
inventoryMovementRepo repository.InventoryMovementRepository
|
inventoryMovementRepo repository.InventoryMovementRepository
|
||||||
productVariantRepo ProductVariantRepository
|
productVariantRepo repository.ProductVariantRepository
|
||||||
outletRepo OutletRepository
|
outletRepo OutletRepository
|
||||||
customerRepo CustomerRepository
|
customerRepo CustomerRepository
|
||||||
}
|
}
|
||||||
@ -125,7 +121,7 @@ func NewOrderProcessorImpl(
|
|||||||
paymentMethodRepo PaymentMethodRepository,
|
paymentMethodRepo PaymentMethodRepository,
|
||||||
inventoryRepo repository.InventoryRepository,
|
inventoryRepo repository.InventoryRepository,
|
||||||
inventoryMovementRepo repository.InventoryMovementRepository,
|
inventoryMovementRepo repository.InventoryMovementRepository,
|
||||||
productVariantRepo ProductVariantRepository,
|
productVariantRepo repository.ProductVariantRepository,
|
||||||
outletRepo OutletRepository,
|
outletRepo OutletRepository,
|
||||||
customerRepo CustomerRepository,
|
customerRepo CustomerRepository,
|
||||||
) *OrderProcessorImpl {
|
) *OrderProcessorImpl {
|
||||||
@ -193,7 +189,7 @@ func (p *OrderProcessorImpl) CreateOrder(ctx context.Context, req *models.Create
|
|||||||
ProductID: itemReq.ProductID,
|
ProductID: itemReq.ProductID,
|
||||||
ProductVariantID: itemReq.ProductVariantID,
|
ProductVariantID: itemReq.ProductVariantID,
|
||||||
Quantity: itemReq.Quantity,
|
Quantity: itemReq.Quantity,
|
||||||
UnitPrice: unitPrice, // Use price from database
|
UnitPrice: unitPrice,
|
||||||
TotalPrice: itemTotalPrice,
|
TotalPrice: itemTotalPrice,
|
||||||
UnitCost: unitCost,
|
UnitCost: unitCost,
|
||||||
TotalCost: itemTotalCost,
|
TotalCost: itemTotalCost,
|
||||||
|
|||||||
@ -13,11 +13,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type TableProcessor struct {
|
type TableProcessor struct {
|
||||||
tableRepo *repository.TableRepository
|
tableRepo repository.TableRepositoryInterface
|
||||||
orderRepo repository.OrderRepository
|
orderRepo repository.OrderRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTableProcessor(tableRepo *repository.TableRepository, orderRepo repository.OrderRepository) *TableProcessor {
|
func NewTableProcessor(tableRepo repository.TableRepositoryInterface, orderRepo repository.OrderRepository) *TableProcessor {
|
||||||
return &TableProcessor{
|
return &TableProcessor{
|
||||||
tableRepo: tableRepo,
|
tableRepo: tableRepo,
|
||||||
orderRepo: orderRepo,
|
orderRepo: orderRepo,
|
||||||
|
|||||||
@ -45,6 +45,7 @@ func (p *UnitProcessorImpl) CreateUnit(ctx context.Context, req *models.CreateUn
|
|||||||
Name: unitModel.Name,
|
Name: unitModel.Name,
|
||||||
Abbreviation: unitModel.Abbreviation,
|
Abbreviation: unitModel.Abbreviation,
|
||||||
IsActive: unitModel.IsActive,
|
IsActive: unitModel.IsActive,
|
||||||
|
DeletedAt: unitModel.DeletedAt,
|
||||||
CreatedAt: unitModel.CreatedAt,
|
CreatedAt: unitModel.CreatedAt,
|
||||||
UpdatedAt: unitModel.UpdatedAt,
|
UpdatedAt: unitModel.UpdatedAt,
|
||||||
}
|
}
|
||||||
@ -68,6 +69,7 @@ func (p *UnitProcessorImpl) GetUnitByID(ctx context.Context, id uuid.UUID) (*mod
|
|||||||
Name: unitModel.Name,
|
Name: unitModel.Name,
|
||||||
Abbreviation: unitModel.Abbreviation,
|
Abbreviation: unitModel.Abbreviation,
|
||||||
IsActive: unitModel.IsActive,
|
IsActive: unitModel.IsActive,
|
||||||
|
DeletedAt: unitModel.DeletedAt,
|
||||||
CreatedAt: unitModel.CreatedAt,
|
CreatedAt: unitModel.CreatedAt,
|
||||||
UpdatedAt: unitModel.UpdatedAt,
|
UpdatedAt: unitModel.UpdatedAt,
|
||||||
}
|
}
|
||||||
@ -102,6 +104,7 @@ func (p *UnitProcessorImpl) ListUnits(ctx context.Context, organizationID uuid.U
|
|||||||
Name: unitModel.Name,
|
Name: unitModel.Name,
|
||||||
Abbreviation: unitModel.Abbreviation,
|
Abbreviation: unitModel.Abbreviation,
|
||||||
IsActive: unitModel.IsActive,
|
IsActive: unitModel.IsActive,
|
||||||
|
DeletedAt: unitModel.DeletedAt,
|
||||||
CreatedAt: unitModel.CreatedAt,
|
CreatedAt: unitModel.CreatedAt,
|
||||||
UpdatedAt: unitModel.UpdatedAt,
|
UpdatedAt: unitModel.UpdatedAt,
|
||||||
}
|
}
|
||||||
@ -147,6 +150,7 @@ func (p *UnitProcessorImpl) UpdateUnit(ctx context.Context, id uuid.UUID, req *m
|
|||||||
Name: unitModel.Name,
|
Name: unitModel.Name,
|
||||||
Abbreviation: unitModel.Abbreviation,
|
Abbreviation: unitModel.Abbreviation,
|
||||||
IsActive: unitModel.IsActive,
|
IsActive: unitModel.IsActive,
|
||||||
|
DeletedAt: unitModel.DeletedAt,
|
||||||
CreatedAt: unitModel.CreatedAt,
|
CreatedAt: unitModel.CreatedAt,
|
||||||
UpdatedAt: unitModel.UpdatedAt,
|
UpdatedAt: unitModel.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,153 +0,0 @@
|
|||||||
package processor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"apskel-pos-be/internal/models"
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/mock"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MockUnitRepository is a mock implementation of the unit repository
|
|
||||||
type MockUnitRepository struct {
|
|
||||||
mock.Mock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockUnitRepository) Create(ctx context.Context, unit *models.Unit) error {
|
|
||||||
args := m.Called(ctx, unit)
|
|
||||||
return args.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockUnitRepository) GetByID(ctx context.Context, id, organizationID uuid.UUID) (*models.Unit, error) {
|
|
||||||
args := m.Called(ctx, id, organizationID)
|
|
||||||
if args.Get(0) == nil {
|
|
||||||
return nil, args.Error(1)
|
|
||||||
}
|
|
||||||
return args.Get(0).(*models.Unit), args.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockUnitRepository) GetAll(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, page, limit int, search string) ([]*models.Unit, int, error) {
|
|
||||||
args := m.Called(ctx, organizationID, outletID, page, limit, search)
|
|
||||||
if args.Get(0) == nil {
|
|
||||||
return nil, args.Int(1), args.Error(2)
|
|
||||||
}
|
|
||||||
return args.Get(0).([]*models.Unit), args.Int(1), args.Error(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockUnitRepository) Update(ctx context.Context, unit *models.Unit) error {
|
|
||||||
args := m.Called(ctx, unit)
|
|
||||||
return args.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockUnitRepository) Delete(ctx context.Context, id, organizationID uuid.UUID) error {
|
|
||||||
args := m.Called(ctx, id, organizationID)
|
|
||||||
return args.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnitProcessor_Create(t *testing.T) {
|
|
||||||
// Create mock repository
|
|
||||||
mockRepo := &MockUnitRepository{}
|
|
||||||
|
|
||||||
// Create processor
|
|
||||||
processor := NewUnitProcessor(mockRepo)
|
|
||||||
|
|
||||||
// Test data
|
|
||||||
organizationID := uuid.New()
|
|
||||||
request := &models.CreateUnitRequest{
|
|
||||||
Name: "Gram",
|
|
||||||
Abbreviation: "g",
|
|
||||||
IsActive: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mock expectations
|
|
||||||
mockRepo.On("Create", mock.Anything, mock.AnythingOfType("*models.Unit")).Return(nil)
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
result, err := processor.Create(request, organizationID)
|
|
||||||
|
|
||||||
// Assertions
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, result)
|
|
||||||
assert.Equal(t, request.Name, result.Name)
|
|
||||||
assert.Equal(t, request.Abbreviation, result.Abbreviation)
|
|
||||||
assert.Equal(t, request.IsActive, result.IsActive)
|
|
||||||
assert.Equal(t, organizationID, result.OrganizationID)
|
|
||||||
|
|
||||||
mockRepo.AssertExpectations(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnitProcessor_GetByID(t *testing.T) {
|
|
||||||
// Create mock repository
|
|
||||||
mockRepo := &MockUnitRepository{}
|
|
||||||
|
|
||||||
// Create processor
|
|
||||||
processor := NewUnitProcessor(mockRepo)
|
|
||||||
|
|
||||||
// Test data
|
|
||||||
unitID := uuid.New()
|
|
||||||
organizationID := uuid.New()
|
|
||||||
expectedUnit := &models.Unit{
|
|
||||||
ID: unitID,
|
|
||||||
OrganizationID: organizationID,
|
|
||||||
Name: "Gram",
|
|
||||||
Abbreviation: "g",
|
|
||||||
IsActive: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mock expectations
|
|
||||||
mockRepo.On("GetByID", mock.Anything, unitID, organizationID).Return(expectedUnit, nil)
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
result, err := processor.GetByID(unitID, organizationID)
|
|
||||||
|
|
||||||
// Assertions
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, result)
|
|
||||||
assert.Equal(t, expectedUnit.ID, result.ID)
|
|
||||||
assert.Equal(t, expectedUnit.Name, result.Name)
|
|
||||||
|
|
||||||
mockRepo.AssertExpectations(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnitProcessor_GetAll(t *testing.T) {
|
|
||||||
// Create mock repository
|
|
||||||
mockRepo := &MockUnitRepository{}
|
|
||||||
|
|
||||||
// Create processor
|
|
||||||
processor := NewUnitProcessor(mockRepo)
|
|
||||||
|
|
||||||
// Test data
|
|
||||||
organizationID := uuid.New()
|
|
||||||
expectedUnits := []*models.Unit{
|
|
||||||
{
|
|
||||||
ID: uuid.New(),
|
|
||||||
OrganizationID: organizationID,
|
|
||||||
Name: "Gram",
|
|
||||||
Abbreviation: "g",
|
|
||||||
IsActive: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: uuid.New(),
|
|
||||||
OrganizationID: organizationID,
|
|
||||||
Name: "Liter",
|
|
||||||
Abbreviation: "L",
|
|
||||||
IsActive: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mock expectations
|
|
||||||
mockRepo.On("GetAll", mock.Anything, organizationID, (*uuid.UUID)(nil), 1, 10, "").Return(expectedUnits, 2, nil)
|
|
||||||
|
|
||||||
// Execute
|
|
||||||
result, err := processor.GetAll(organizationID, nil, 1, 10, "")
|
|
||||||
|
|
||||||
// Assertions
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, result)
|
|
||||||
assert.Len(t, result.Data, 2)
|
|
||||||
assert.Equal(t, 2, result.Pagination.Total)
|
|
||||||
|
|
||||||
mockRepo.AssertExpectations(t)
|
|
||||||
}
|
|
||||||
26
internal/repository/table_repository_interface.go
Normal file
26
internal/repository/table_repository_interface.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TableRepositoryInterface defines the interface for table repository operations
|
||||||
|
type TableRepositoryInterface interface {
|
||||||
|
Create(ctx context.Context, table *entities.Table) error
|
||||||
|
GetByID(ctx context.Context, id uuid.UUID) (*entities.Table, error)
|
||||||
|
GetByOutletID(ctx context.Context, outletID uuid.UUID) ([]entities.Table, error)
|
||||||
|
GetByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]entities.Table, error)
|
||||||
|
Update(ctx context.Context, table *entities.Table) error
|
||||||
|
Delete(ctx context.Context, id uuid.UUID) error
|
||||||
|
List(ctx context.Context, organizationID, outletID *uuid.UUID, status *string, isActive *bool, search string, page, limit int) ([]entities.Table, int64, error)
|
||||||
|
GetAvailableTables(ctx context.Context, outletID uuid.UUID) ([]entities.Table, error)
|
||||||
|
GetOccupiedTables(ctx context.Context, outletID uuid.UUID) ([]entities.Table, error)
|
||||||
|
OccupyTable(ctx context.Context, tableID, orderID uuid.UUID, startTime *time.Time) error
|
||||||
|
ReleaseTable(ctx context.Context, tableID uuid.UUID, paymentAmount float64) error
|
||||||
|
GetByOrderID(ctx context.Context, orderID uuid.UUID) (*entities.Table, error)
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"apskel-pos-be/internal/entities"
|
"apskel-pos-be/internal/entities"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ func (r *UnitRepository) Create(ctx context.Context, unit *entities.Unit) error
|
|||||||
|
|
||||||
func (r *UnitRepository) GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.Unit, error) {
|
func (r *UnitRepository) GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.Unit, error) {
|
||||||
var unit entities.Unit
|
var unit entities.Unit
|
||||||
err := r.db.WithContext(ctx).Where("id = ? AND organization_id = ?", id, organizationID).First(&unit).Error
|
err := r.db.WithContext(ctx).Where("id = ? AND organization_id = ? AND deleted_at IS NULL", id, organizationID).First(&unit).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -35,7 +36,7 @@ func (r *UnitRepository) GetAll(ctx context.Context, organizationID uuid.UUID, o
|
|||||||
var units []*entities.Unit
|
var units []*entities.Unit
|
||||||
var total int64
|
var total int64
|
||||||
|
|
||||||
query := r.db.WithContext(ctx).Model(&entities.Unit{}).Where("organization_id = ?", organizationID)
|
query := r.db.WithContext(ctx).Model(&entities.Unit{}).Where("organization_id = ? AND deleted_at IS NULL", organizationID)
|
||||||
|
|
||||||
if outletID != nil {
|
if outletID != nil {
|
||||||
query = query.Where("outlet_id = ?", *outletID)
|
query = query.Where("outlet_id = ?", *outletID)
|
||||||
@ -62,7 +63,7 @@ func (r *UnitRepository) GetAll(ctx context.Context, organizationID uuid.UUID, o
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *UnitRepository) Update(ctx context.Context, unit *entities.Unit) error {
|
func (r *UnitRepository) Update(ctx context.Context, unit *entities.Unit) error {
|
||||||
result := r.db.WithContext(ctx).Where("id = ? AND organization_id = ?", unit.ID, unit.OrganizationID).Save(unit)
|
result := r.db.WithContext(ctx).Where("id = ? AND organization_id = ? AND deleted_at IS NULL", unit.ID, unit.OrganizationID).Save(unit)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return result.Error
|
return result.Error
|
||||||
}
|
}
|
||||||
@ -73,7 +74,11 @@ func (r *UnitRepository) Update(ctx context.Context, unit *entities.Unit) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *UnitRepository) Delete(ctx context.Context, id, organizationID uuid.UUID) error {
|
func (r *UnitRepository) Delete(ctx context.Context, id, organizationID uuid.UUID) error {
|
||||||
result := r.db.WithContext(ctx).Where("id = ? AND organization_id = ?", id, organizationID).Delete(&entities.Unit{})
|
now := time.Now()
|
||||||
|
result := r.db.WithContext(ctx).Where("id = ? AND organization_id = ? AND deleted_at IS NULL", id, organizationID).
|
||||||
|
Updates(map[string]interface{}{
|
||||||
|
"deleted_at": &now,
|
||||||
|
})
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return result.Error
|
return result.Error
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,11 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"apskel-pos-be/internal/models"
|
"apskel-pos-be/internal/models"
|
||||||
"apskel-pos-be/internal/processor"
|
"apskel-pos-be/internal/processor"
|
||||||
|
"apskel-pos-be/internal/repository"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
@ -26,11 +28,13 @@ type OrderService interface {
|
|||||||
|
|
||||||
type OrderServiceImpl struct {
|
type OrderServiceImpl struct {
|
||||||
orderProcessor processor.OrderProcessor
|
orderProcessor processor.OrderProcessor
|
||||||
|
tableRepo repository.TableRepositoryInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOrderServiceImpl(orderProcessor processor.OrderProcessor) *OrderServiceImpl {
|
func NewOrderServiceImpl(orderProcessor processor.OrderProcessor, tableRepo repository.TableRepositoryInterface) *OrderServiceImpl {
|
||||||
return &OrderServiceImpl{
|
return &OrderServiceImpl{
|
||||||
orderProcessor: orderProcessor,
|
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)
|
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)
|
response, err := s.orderProcessor.CreateOrder(ctx, req, organizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create order: %w", err)
|
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
|
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)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,12 +173,15 @@ func (s *OrderServiceImpl) CreatePayment(ctx context.Context, req *models.Create
|
|||||||
return nil, fmt.Errorf("validation error: %w", err)
|
return nil, fmt.Errorf("validation error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process payment creation
|
|
||||||
response, err := s.orderProcessor.CreatePayment(ctx, req)
|
response, err := s.orderProcessor.CreatePayment(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create payment: %w", err)
|
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
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,6 +272,11 @@ func (s *OrderServiceImpl) validateCreateOrderRequest(req *models.CreateOrderReq
|
|||||||
return fmt.Errorf("user ID is required")
|
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 {
|
if len(req.OrderItems) == 0 {
|
||||||
return fmt.Errorf("order must have at least one item")
|
return fmt.Errorf("order must have at least one item")
|
||||||
}
|
}
|
||||||
@ -445,3 +475,71 @@ func (s *OrderServiceImpl) validateSplitBillRequest(req *models.SplitBillRequest
|
|||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
308
internal/service/order_service_table_test.go
Normal file
308
internal/service/order_service_table_test.go
Normal file
@ -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")
|
||||||
|
}
|
||||||
@ -26,6 +26,7 @@ func CreateOrderContractToModel(req *contract.CreateOrderRequest) *models.Create
|
|||||||
return &models.CreateOrderRequest{
|
return &models.CreateOrderRequest{
|
||||||
OutletID: req.OutletID,
|
OutletID: req.OutletID,
|
||||||
UserID: req.UserID,
|
UserID: req.UserID,
|
||||||
|
TableID: req.TableID,
|
||||||
TableNumber: req.TableNumber,
|
TableNumber: req.TableNumber,
|
||||||
OrderType: constants.OrderType(req.OrderType),
|
OrderType: constants.OrderType(req.OrderType),
|
||||||
OrderItems: items,
|
OrderItems: items,
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
-- Revert payment status constraints to previous state
|
||||||
|
ALTER TABLE orders DROP CONSTRAINT IF EXISTS orders_payment_status_check;
|
||||||
|
ALTER TABLE orders ADD CONSTRAINT orders_payment_status_check CHECK (payment_status IN ('pending', 'completed', 'failed', 'refunded', 'partially_refunded'));
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
-- Update payment status constraints to include all defined statuses
|
||||||
|
ALTER TABLE orders DROP CONSTRAINT IF EXISTS orders_payment_status_check;
|
||||||
|
ALTER TABLE orders ADD CONSTRAINT orders_payment_status_check CHECK (payment_status IN ('pending', 'partial', 'completed', 'failed', 'refunded', 'partial-refunded'));
|
||||||
3
migrations/000036_add_deleted_at_to_units.down.sql
Normal file
3
migrations/000036_add_deleted_at_to_units.down.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
-- Remove deleted_at column from units table
|
||||||
|
DROP INDEX IF EXISTS idx_units_deleted_at;
|
||||||
|
ALTER TABLE units DROP COLUMN IF EXISTS deleted_at;
|
||||||
3
migrations/000036_add_deleted_at_to_units.up.sql
Normal file
3
migrations/000036_add_deleted_at_to_units.up.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
-- Add deleted_at column to units table for soft delete functionality
|
||||||
|
ALTER TABLE units ADD COLUMN deleted_at TIMESTAMP WITH TIME ZONE;
|
||||||
|
CREATE INDEX idx_units_deleted_at ON units(deleted_at);
|
||||||
Loading…
x
Reference in New Issue
Block a user