Self Order - Feature

This commit is contained in:
ryan 2026-05-05 20:14:54 +07:00
parent b993da898f
commit d2296e5b13
4 changed files with 259 additions and 192 deletions

View File

@ -42,8 +42,16 @@ func (a *App) Initialize(cfg *config.Config) error {
processors := a.initProcessors(cfg, repos) processors := a.initProcessors(cfg, repos)
services := a.initServices(processors, repos, cfg) services := a.initServices(processors, repos, cfg)
validators := a.initValidators() validators := a.initValidators()
middleware := a.initMiddleware(services, cfg, repos) middleware := a.initMiddleware(services, cfg)
healthHandler := handler.NewHealthHandler() healthHandler := handler.NewHealthHandler()
selfOrderHandler := handler.NewSelfOrderHandler(
services.orderService,
services.categoryService,
services.productService,
repos.tableRepo,
repos.outletRepo,
repos.userRepo,
)
a.router = router.NewRouter( a.router = router.NewRouter(
cfg, cfg,
@ -105,14 +113,7 @@ func (a *App) Initialize(cfg *config.Config) error {
services.customerPointsService, services.customerPointsService,
services.spinGameService, services.spinGameService,
middleware.customerAuthMiddleware, middleware.customerAuthMiddleware,
handler.NewSelfOrderHandler( selfOrderHandler,
services.orderService,
services.productService,
repos.customerRepo,
repos.userRepo,
repos.outletRepo,
),
middleware.selfOrderMiddleware,
) )
return nil return nil
@ -449,14 +450,12 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
type middlewares struct { type middlewares struct {
authMiddleware *middleware.AuthMiddleware authMiddleware *middleware.AuthMiddleware
customerAuthMiddleware *middleware.CustomerAuthMiddleware customerAuthMiddleware *middleware.CustomerAuthMiddleware
selfOrderMiddleware *middleware.SelfOrderMiddleware
} }
func (a *App) initMiddleware(services *services, cfg *config.Config, repos *repositories) *middlewares { func (a *App) initMiddleware(services *services, cfg *config.Config) *middlewares {
return &middlewares{ return &middlewares{
authMiddleware: middleware.NewAuthMiddleware(services.authService), authMiddleware: middleware.NewAuthMiddleware(services.authService),
customerAuthMiddleware: middleware.NewCustomerAuthMiddleware(cfg.GetCustomerJWTSecret()), customerAuthMiddleware: middleware.NewCustomerAuthMiddleware(cfg.GetCustomerJWTSecret()),
selfOrderMiddleware: middleware.NewSelfOrderMiddleware(repos.tableRepo),
} }
} }

View File

@ -1,51 +1,54 @@
package contract package contract
import ( import (
"time"
"github.com/google/uuid" "github.com/google/uuid"
) )
type CreateSelfOrderRequest struct { type SelfOrderMenuRequest struct {
CustomerName string `json:"customer_name" validate:"required,min=1,max=255"` TableID uuid.UUID `json:"table_id" validate:"required"`
PhoneNumber *string `json:"phone_number,omitempty" validate:"omitempty"` CustomerName string `json:"customer_name" validate:"required"`
OrderItems []SelfOrderItemRequest `json:"order_items" validate:"required,min=1,dive"` Phone *string `json:"phone,omitempty"`
Notes *string `json:"notes,omitempty" validate:"omitempty,max=1000"`
}
type SelfOrderItemRequest struct {
ProductID uuid.UUID `json:"product_id" validate:"required"`
ProductVariantID *uuid.UUID `json:"product_variant_id,omitempty"`
Quantity int `json:"quantity" validate:"required,min=1"`
Notes *string `json:"notes,omitempty" validate:"omitempty,max=500"`
}
type SelfOrderResponse struct {
OrderID uuid.UUID `json:"order_id"`
OrderNumber string `json:"order_number"`
TableID uuid.UUID `json:"table_id"`
TableName string `json:"table_name"`
OutletID uuid.UUID `json:"outlet_id"`
OutletName string `json:"outlet_name"`
CustomerName string `json:"customer_name"`
OrderItems []OrderItemResponse `json:"order_items"`
Subtotal float64 `json:"subtotal"`
TaxAmount float64 `json:"tax_amount"`
TotalAmount float64 `json:"total_amount"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
} }
type SelfOrderMenuResponse struct { type SelfOrderMenuResponse struct {
OutletID uuid.UUID `json:"outlet_id"`
OutletName string `json:"outlet_name"` OutletName string `json:"outlet_name"`
TableID uuid.UUID `json:"table_id"`
TableName string `json:"table_name"` TableName string `json:"table_name"`
Organization OrganizationMenuInfo `json:"organization"` Categories []SelfOrderMenuCategory `json:"categories"`
Products ListProductsResponse `json:"products"`
} }
type OrganizationMenuInfo struct { type SelfOrderMenuCategory struct {
ID uuid.UUID `json:"id"` ID uuid.UUID `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Description *string `json:"description,omitempty"`
Order int `json:"order"`
Products []SelfOrderMenuItem `json:"products"`
}
type SelfOrderMenuItem struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Description *string `json:"description,omitempty"`
Price float64 `json:"price"`
ImageURL *string `json:"image_url,omitempty"`
Variants []SelfOrderMenuVariant `json:"variants,omitempty"`
}
type SelfOrderMenuVariant struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
PriceModifier float64 `json:"price_modifier"`
}
type SelfOrderCreateOrderRequest struct {
TableID uuid.UUID `json:"table_id" validate:"required"`
CustomerName string `json:"customer_name" validate:"required"`
Phone *string `json:"phone,omitempty"`
OrderItems []SelfOrderCreateOrderItem `json:"order_items" validate:"required,min=1,dive"`
}
type SelfOrderCreateOrderItem struct {
ProductID uuid.UUID `json:"product_id" validate:"required"`
ProductVariantID *uuid.UUID `json:"product_variant_id,omitempty"`
Quantity int `json:"quantity" validate:"required,min=1"`
Notes *string `json:"notes,omitempty"`
} }

View File

@ -3,11 +3,16 @@ package handler
import ( import (
"apskel-pos-be/internal/constants" "apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract" "apskel-pos-be/internal/contract"
"apskel-pos-be/internal/entities"
"apskel-pos-be/internal/logger" "apskel-pos-be/internal/logger"
"apskel-pos-be/internal/models" "apskel-pos-be/internal/models"
"apskel-pos-be/internal/processor"
"apskel-pos-be/internal/repository" "apskel-pos-be/internal/repository"
"apskel-pos-be/internal/service" "apskel-pos-be/internal/service"
"apskel-pos-be/internal/transformer"
"apskel-pos-be/internal/util" "apskel-pos-be/internal/util"
"context"
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
@ -15,102 +20,179 @@ import (
type SelfOrderHandler struct { type SelfOrderHandler struct {
orderService service.OrderService orderService service.OrderService
categoryService service.CategoryService
productService service.ProductService productService service.ProductService
customerRepo *repository.CustomerRepository tableRepo repository.TableRepositoryInterface
userRepo *repository.UserRepositoryImpl outletRepo processor.OutletRepository
outletRepo *repository.OutletRepositoryImpl userRepo processor.UserRepository
} }
func NewSelfOrderHandler( func NewSelfOrderHandler(
orderService service.OrderService, orderService service.OrderService,
categoryService service.CategoryService,
productService service.ProductService, productService service.ProductService,
customerRepo *repository.CustomerRepository, tableRepo repository.TableRepositoryInterface,
userRepo *repository.UserRepositoryImpl, outletRepo processor.OutletRepository,
outletRepo *repository.OutletRepositoryImpl, userRepo processor.UserRepository,
) *SelfOrderHandler { ) *SelfOrderHandler {
return &SelfOrderHandler{ return &SelfOrderHandler{
orderService: orderService, orderService: orderService,
categoryService: categoryService,
productService: productService, productService: productService,
customerRepo: customerRepo, tableRepo: tableRepo,
userRepo: userRepo,
outletRepo: outletRepo, outletRepo: outletRepo,
userRepo: userRepo,
} }
} }
func (h *SelfOrderHandler) GetMenu(c *gin.Context) { func (h *SelfOrderHandler) GetMenu(c *gin.Context) {
ctx := c.Request.Context() ctx := c.Request.Context()
organizationIDStr, _ := c.Get("self_order_organization_id") var req contract.SelfOrderMenuRequest
outletIDStr, _ := c.Get("self_order_outlet_id") if err := c.ShouldBindJSON(&req); err != nil {
tableIDStr, _ := c.Get("self_order_table_id") logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::GetMenu -> request binding failed")
tableName, _ := c.Get("self_order_table_name")
organizationID, err := uuid.Parse(organizationIDStr.(string))
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{ util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
contract.NewResponseError(constants.InternalServerErrorCode, constants.SelfOrderEntity, "invalid organization ID"), contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error()),
}), "SelfOrderHandler::GetMenu") }), "SelfOrderHandler::GetMenu")
return return
} }
outletID, err := uuid.Parse(outletIDStr.(string)) if req.TableID == uuid.Nil {
if err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{ util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
contract.NewResponseError(constants.InternalServerErrorCode, constants.SelfOrderEntity, "invalid outlet ID"), contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, "table_id is required"),
}), "SelfOrderHandler::GetMenu") }), "SelfOrderHandler::GetMenu")
return return
} }
tableID, _ := uuid.Parse(tableIDStr.(string)) if req.CustomerName == "" {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, "customer_name is required"),
}), "SelfOrderHandler::GetMenu")
return
}
table, err := h.tableRepo.GetByID(ctx, req.TableID)
if err != nil {
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::GetMenu -> table not found")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
contract.NewResponseError(constants.NotFoundErrorCode, constants.TableEntity, "table not found"),
}), "SelfOrderHandler::GetMenu")
return
}
if !table.IsActive {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
contract.NewResponseError(constants.ValidationErrorCode, constants.TableEntity, "table is not active"),
}), "SelfOrderHandler::GetMenu")
return
}
outlet, err := h.outletRepo.GetByID(ctx, table.OutletID)
if err != nil {
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::GetMenu -> outlet not found")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
contract.NewResponseError(constants.NotFoundErrorCode, constants.OrderServiceEntity, "outlet not found"),
}), "SelfOrderHandler::GetMenu")
return
}
isActive := true isActive := true
req := &contract.ListProductsRequest{ catResp := h.categoryService.ListCategories(ctx, &contract.ListCategoriesRequest{
OrganizationID: &organizationID, OrganizationID: &table.OrganizationID,
IsActive: &isActive,
Page: 1, Page: 1,
Limit: 100, Limit: 100,
})
if catResp.HasErrors() {
logger.FromContext(ctx).WithError(catResp.GetErrors()[0]).Error("SelfOrderHandler::GetMenu -> failed to list categories")
util.HandleResponse(c.Writer, c.Request, catResp, "SelfOrderHandler::GetMenu")
return
} }
productsResponse := h.productService.ListProducts(ctx, req) prodResp := h.productService.ListProducts(ctx, &contract.ListProductsRequest{
OrganizationID: &table.OrganizationID,
outlet, outletErr := h.outletRepo.GetByID(ctx, outletID) IsActive: &isActive,
outletName := "" Page: 1,
if outletErr == nil && outlet != nil { Limit: 1000,
outletName = outlet.Name })
if prodResp.HasErrors() {
logger.FromContext(ctx).WithError(prodResp.GetErrors()[0]).Error("SelfOrderHandler::GetMenu -> failed to list products")
util.HandleResponse(c.Writer, c.Request, prodResp, "SelfOrderHandler::GetMenu")
return
} }
menuResponse := &contract.SelfOrderMenuResponse{ catList, ok := catResp.Data.(*contract.ListCategoriesResponse)
OutletID: outletID, if !ok {
OutletName: outletName, util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
TableID: tableID, contract.NewResponseError(constants.InternalServerErrorCode, constants.CategoryServiceEntity, "unexpected categories response type"),
TableName: tableName.(string), }), "SelfOrderHandler::GetMenu")
Organization: contract.OrganizationMenuInfo{ return
ID: organizationID,
},
} }
if productsResponse != nil { prodList, ok := prodResp.Data.(*contract.ListProductsResponse)
if data, ok := productsResponse.Data.(*contract.ListProductsResponse); ok { if !ok {
menuResponse.Products = *data util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
} contract.NewResponseError(constants.InternalServerErrorCode, constants.ProductServiceEntity, "unexpected products response type"),
}), "SelfOrderHandler::GetMenu")
return
} }
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(menuResponse), "SelfOrderHandler::GetMenu") menu := h.buildMenuResponse(outlet, table, catList.Categories, prodList.Products)
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(menu), "SelfOrderHandler::GetMenu")
}
func (h *SelfOrderHandler) buildMenuResponse(
outlet *entities.Outlet,
table *entities.Table,
categories []contract.CategoryResponse,
products []contract.ProductResponse,
) *contract.SelfOrderMenuResponse {
productMap := make(map[uuid.UUID][]contract.ProductResponse)
for _, p := range products {
productMap[p.CategoryID] = append(productMap[p.CategoryID], p)
}
menuCategories := make([]contract.SelfOrderMenuCategory, 0, len(categories))
for _, cat := range categories {
menuItems := make([]contract.SelfOrderMenuItem, 0)
if prods, ok := productMap[cat.ID]; ok {
for _, p := range prods {
item := contract.SelfOrderMenuItem{
ID: p.ID,
Name: p.Name,
Description: p.Description,
Price: p.Price,
ImageURL: p.ImageURL,
}
for _, v := range p.Variants {
item.Variants = append(item.Variants, contract.SelfOrderMenuVariant{
ID: v.ID,
Name: v.Name,
PriceModifier: v.PriceModifier,
})
}
menuItems = append(menuItems, item)
}
}
menuCategories = append(menuCategories, contract.SelfOrderMenuCategory{
ID: cat.ID,
Name: cat.Name,
Description: cat.Description,
Order: cat.Order,
Products: menuItems,
})
}
return &contract.SelfOrderMenuResponse{
OutletName: outlet.Name,
TableName: table.TableName,
Categories: menuCategories,
}
} }
func (h *SelfOrderHandler) CreateOrder(c *gin.Context) { func (h *SelfOrderHandler) CreateOrder(c *gin.Context) {
ctx := c.Request.Context() ctx := c.Request.Context()
organizationIDStr, _ := c.Get("self_order_organization_id") var req contract.SelfOrderCreateOrderRequest
outletIDStr, _ := c.Get("self_order_outlet_id")
tableIDStr, _ := c.Get("self_order_table_id")
tableName, _ := c.Get("self_order_table_name")
organizationID, _ := uuid.Parse(organizationIDStr.(string))
outletID, _ := uuid.Parse(outletIDStr.(string))
tableID, _ := uuid.Parse(tableIDStr.(string))
var req contract.CreateSelfOrderRequest
if err := c.ShouldBindJSON(&req); err != nil { if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateOrder -> request binding failed") logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateOrder -> request binding failed")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{ util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
@ -119,121 +201,108 @@ func (h *SelfOrderHandler) CreateOrder(c *gin.Context) {
return return
} }
if req.CustomerName == "" { if err := h.validateCreateOrderRequest(&req); err != nil {
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{ util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
contract.NewResponseError(constants.ValidationErrorCode, constants.SelfOrderEntity, "customer_name is required"), contract.NewResponseError(constants.ValidationErrorCode, constants.RequestEntity, err.Error()),
}), "SelfOrderHandler::CreateOrder") }), "SelfOrderHandler::CreateOrder")
return return
} }
if len(req.OrderItems) == 0 { table, err := h.tableRepo.GetByID(ctx, req.TableID)
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
contract.NewResponseError(constants.ValidationErrorCode, constants.SelfOrderEntity, "at least one order item is required"),
}), "SelfOrderHandler::CreateOrder")
return
}
adminUser, err := h.userRepo.GetAdminByOrganizationID(ctx, organizationID)
if err != nil { if err != nil {
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateOrder -> failed to get admin user") logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateOrder -> table not found")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{ util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
contract.NewResponseError(constants.InternalServerErrorCode, constants.SelfOrderEntity, "failed to resolve system user"), contract.NewResponseError(constants.NotFoundErrorCode, constants.TableEntity, "table not found"),
}), "SelfOrderHandler::CreateOrder") }), "SelfOrderHandler::CreateOrder")
return return
} }
var customerID *uuid.UUID if !table.IsActive || !table.IsAvailable() {
if req.PhoneNumber != nil && *req.PhoneNumber != "" { util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
customer, err := h.customerRepo.GetByPhoneNumber(ctx, *req.PhoneNumber) contract.NewResponseError(constants.ValidationErrorCode, constants.TableEntity, "table is not available for ordering"),
}), "SelfOrderHandler::CreateOrder")
return
}
userID, err := h.resolveOrgUser(ctx, table.OrganizationID)
if err != nil { if err != nil {
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateOrder -> failed to lookup customer by phone") logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateOrder -> failed to resolve org user")
} util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
if customer != nil { contract.NewResponseError(constants.InternalServerErrorCode, constants.OrderServiceEntity, "failed to create self-order"),
customerID = &customer.ID }), "SelfOrderHandler::CreateOrder")
} return
} }
metadata := map[string]interface{}{ orderItems := make([]models.CreateOrderItemRequest, 0, len(req.OrderItems))
"source": string(constants.OrderSourceSelfOrder), for _, item := range req.OrderItems {
"customer_phone": "", orderItems = append(orderItems, models.CreateOrderItemRequest{
}
if req.PhoneNumber != nil {
metadata["customer_phone"] = *req.PhoneNumber
}
orderItems := make([]models.CreateOrderItemRequest, len(req.OrderItems))
for i, item := range req.OrderItems {
orderItems[i] = models.CreateOrderItemRequest{
ProductID: item.ProductID, ProductID: item.ProductID,
ProductVariantID: item.ProductVariantID, ProductVariantID: item.ProductVariantID,
Quantity: item.Quantity, Quantity: item.Quantity,
Notes: item.Notes, Notes: item.Notes,
} })
} }
tableNameStr := tableName.(string) metadata := make(map[string]interface{})
metadata["self_order"] = true
metadata["customer_name"] = req.CustomerName
if req.Phone != nil {
metadata["customer_phone"] = *req.Phone
}
tableID := req.TableID
modelReq := &models.CreateOrderRequest{ modelReq := &models.CreateOrderRequest{
OutletID: outletID, OutletID: table.OutletID,
UserID: adminUser.ID, UserID: userID,
CustomerID: customerID,
TableID: &tableID, TableID: &tableID,
TableNumber: &tableNameStr, TableNumber: &table.TableName,
OrderType: constants.OrderTypeDineIn, OrderType: constants.OrderTypeDineIn,
OrderItems: orderItems, OrderItems: orderItems,
Notes: req.Notes,
CustomerName: &req.CustomerName, CustomerName: &req.CustomerName,
Metadata: metadata, Metadata: metadata,
} }
response, err := h.orderService.CreateOrder(ctx, modelReq, organizationID) response, err := h.orderService.CreateOrder(ctx, modelReq, table.OrganizationID)
if err != nil { if err != nil {
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateOrder -> failed to create order") logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateOrder -> failed to create order")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{ util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
contract.NewResponseError(constants.InternalServerErrorCode, constants.SelfOrderEntity, err.Error()), contract.NewResponseError(constants.InternalServerErrorCode, constants.OrderServiceEntity, err.Error()),
}), "SelfOrderHandler::CreateOrder") }), "SelfOrderHandler::CreateOrder")
return return
} }
outlet, _ := h.outletRepo.GetByID(ctx, outletID) contractResp := transformer.OrderModelToContract(response)
outletName := "" util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "SelfOrderHandler::CreateOrder")
if outlet != nil {
outletName = outlet.Name
} }
orderItemsResp := make([]contract.OrderItemResponse, len(response.OrderItems)) func (h *SelfOrderHandler) validateCreateOrderRequest(req *contract.SelfOrderCreateOrderRequest) error {
for i, item := range response.OrderItems { if req.TableID == uuid.Nil {
orderItemsResp[i] = contract.OrderItemResponse{ return fmt.Errorf("table_id is required")
ID: item.ID,
OrderID: item.OrderID,
ProductID: item.ProductID,
ProductName: item.ProductName,
ProductVariantID: item.ProductVariantID,
ProductVariantName: item.ProductVariantName,
Quantity: item.Quantity,
UnitPrice: item.UnitPrice,
TotalPrice: item.TotalPrice,
Notes: item.Notes,
Status: string(item.Status),
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
} }
if req.CustomerName == "" {
return fmt.Errorf("customer_name is required")
}
if len(req.OrderItems) == 0 {
return fmt.Errorf("at least one order item is required")
}
for i, item := range req.OrderItems {
if item.ProductID == uuid.Nil {
return fmt.Errorf("product_id is required for item %d", i+1)
}
if item.Quantity <= 0 {
return fmt.Errorf("quantity must be greater than zero for item %d", i+1)
}
}
return nil
} }
selfOrderResp := &contract.SelfOrderResponse{ func (h *SelfOrderHandler) resolveOrgUser(ctx context.Context, organizationID uuid.UUID) (uuid.UUID, error) {
OrderID: response.ID, users, err := h.userRepo.GetByOrganizationID(ctx, organizationID)
OrderNumber: response.OrderNumber, if err != nil {
TableID: tableID, return uuid.Nil, fmt.Errorf("failed to get users for organization: %w", err)
TableName: tableNameStr,
OutletID: outletID,
OutletName: outletName,
CustomerName: req.CustomerName,
OrderItems: orderItemsResp,
Subtotal: response.Subtotal,
TaxAmount: response.TaxAmount,
TotalAmount: response.TotalAmount,
Status: string(response.Status),
CreatedAt: response.CreatedAt,
} }
if len(users) == 0 {
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(selfOrderResp), "SelfOrderHandler::CreateOrder") return uuid.Nil, fmt.Errorf("no users found for organization")
}
return users[0].ID, nil
} }

View File

@ -49,10 +49,9 @@ type Router struct {
selfOrderHandler *handler.SelfOrderHandler selfOrderHandler *handler.SelfOrderHandler
authMiddleware *middleware.AuthMiddleware authMiddleware *middleware.AuthMiddleware
customerAuthMiddleware *middleware.CustomerAuthMiddleware customerAuthMiddleware *middleware.CustomerAuthMiddleware
selfOrderMiddleware *middleware.SelfOrderMiddleware
} }
func NewRouter(cfg *config.Config, healthHandler *handler.HealthHandler, authService service.AuthService, authMiddleware *middleware.AuthMiddleware, userService *service.UserServiceImpl, userValidator *validator.UserValidatorImpl, organizationService service.OrganizationService, organizationValidator validator.OrganizationValidator, outletService service.OutletService, outletValidator validator.OutletValidator, outletSettingService service.OutletSettingService, categoryService service.CategoryService, categoryValidator validator.CategoryValidator, productService service.ProductService, productValidator validator.ProductValidator, productVariantService service.ProductVariantService, productVariantValidator validator.ProductVariantValidator, inventoryService service.InventoryService, inventoryValidator validator.InventoryValidator, orderService service.OrderService, orderValidator validator.OrderValidator, fileService service.FileService, fileValidator validator.FileValidator, customerService service.CustomerService, customerValidator validator.CustomerValidator, paymentMethodService service.PaymentMethodService, paymentMethodValidator validator.PaymentMethodValidator, analyticsService *service.AnalyticsServiceImpl, reportService service.ReportService, tableService *service.TableServiceImpl, tableValidator *validator.TableValidator, unitService handler.UnitService, ingredientService handler.IngredientService, productRecipeService service.ProductRecipeService, vendorService service.VendorService, vendorValidator validator.VendorValidator, purchaseOrderService service.PurchaseOrderService, purchaseOrderValidator validator.PurchaseOrderValidator, unitConverterService service.IngredientUnitConverterService, unitConverterValidator validator.IngredientUnitConverterValidator, chartOfAccountTypeService service.ChartOfAccountTypeService, chartOfAccountTypeValidator validator.ChartOfAccountTypeValidator, chartOfAccountService service.ChartOfAccountService, chartOfAccountValidator validator.ChartOfAccountValidator, accountService service.AccountService, accountValidator validator.AccountValidator, orderIngredientTransactionService service.OrderIngredientTransactionService, orderIngredientTransactionValidator validator.OrderIngredientTransactionValidator, gamificationService service.GamificationService, gamificationValidator validator.GamificationValidator, rewardService service.RewardService, rewardValidator validator.RewardValidator, campaignService service.CampaignService, campaignValidator validator.CampaignValidator, customerAuthService service.CustomerAuthService, customerAuthValidator validator.CustomerAuthValidator, customerPointsService service.CustomerPointsService, spinGameService service.SpinGameService, customerAuthMiddleware *middleware.CustomerAuthMiddleware, selfOrderHandler *handler.SelfOrderHandler, selfOrderMiddleware *middleware.SelfOrderMiddleware) *Router { func NewRouter(cfg *config.Config, healthHandler *handler.HealthHandler, authService service.AuthService, authMiddleware *middleware.AuthMiddleware, userService *service.UserServiceImpl, userValidator *validator.UserValidatorImpl, organizationService service.OrganizationService, organizationValidator validator.OrganizationValidator, outletService service.OutletService, outletValidator validator.OutletValidator, outletSettingService service.OutletSettingService, categoryService service.CategoryService, categoryValidator validator.CategoryValidator, productService service.ProductService, productValidator validator.ProductValidator, productVariantService service.ProductVariantService, productVariantValidator validator.ProductVariantValidator, inventoryService service.InventoryService, inventoryValidator validator.InventoryValidator, orderService service.OrderService, orderValidator validator.OrderValidator, fileService service.FileService, fileValidator validator.FileValidator, customerService service.CustomerService, customerValidator validator.CustomerValidator, paymentMethodService service.PaymentMethodService, paymentMethodValidator validator.PaymentMethodValidator, analyticsService *service.AnalyticsServiceImpl, reportService service.ReportService, tableService *service.TableServiceImpl, tableValidator *validator.TableValidator, unitService handler.UnitService, ingredientService handler.IngredientService, productRecipeService service.ProductRecipeService, vendorService service.VendorService, vendorValidator validator.VendorValidator, purchaseOrderService service.PurchaseOrderService, purchaseOrderValidator validator.PurchaseOrderValidator, unitConverterService service.IngredientUnitConverterService, unitConverterValidator validator.IngredientUnitConverterValidator, chartOfAccountTypeService service.ChartOfAccountTypeService, chartOfAccountTypeValidator validator.ChartOfAccountTypeValidator, chartOfAccountService service.ChartOfAccountService, chartOfAccountValidator validator.ChartOfAccountValidator, accountService service.AccountService, accountValidator validator.AccountValidator, orderIngredientTransactionService service.OrderIngredientTransactionService, orderIngredientTransactionValidator validator.OrderIngredientTransactionValidator, gamificationService service.GamificationService, gamificationValidator validator.GamificationValidator, rewardService service.RewardService, rewardValidator validator.RewardValidator, campaignService service.CampaignService, campaignValidator validator.CampaignValidator, customerAuthService service.CustomerAuthService, customerAuthValidator validator.CustomerAuthValidator, customerPointsService service.CustomerPointsService, spinGameService service.SpinGameService, customerAuthMiddleware *middleware.CustomerAuthMiddleware, selfOrderHandler *handler.SelfOrderHandler) *Router {
return &Router{ return &Router{
config: cfg, config: cfg,
@ -90,9 +89,8 @@ func NewRouter(cfg *config.Config, healthHandler *handler.HealthHandler, authSer
spinGameHandler: handler.NewSpinGameHandler(spinGameService), spinGameHandler: handler.NewSpinGameHandler(spinGameService),
authMiddleware: authMiddleware, authMiddleware: authMiddleware,
customerAuthMiddleware: customerAuthMiddleware, customerAuthMiddleware: customerAuthMiddleware,
selfOrderHandler: selfOrderHandler,
selfOrderMiddleware: selfOrderMiddleware,
productVariantHandler: handler.NewProductVariantHandler(productVariantService, productVariantValidator), productVariantHandler: handler.NewProductVariantHandler(productVariantService, productVariantValidator),
selfOrderHandler: selfOrderHandler,
} }
} }
@ -149,11 +147,9 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
customer.POST("/spin", r.spinGameHandler.PlaySpinGame) customer.POST("/spin", r.spinGameHandler.PlaySpinGame)
} }
// Self-order routes (public, token-based table identification)
selfOrder := v1.Group("/self-order") selfOrder := v1.Group("/self-order")
selfOrder.Use(r.selfOrderMiddleware.ResolveToken())
{ {
selfOrder.GET("/menu", r.selfOrderHandler.GetMenu) selfOrder.POST("/menu", r.selfOrderHandler.GetMenu)
selfOrder.POST("/order", r.selfOrderHandler.CreateOrder) selfOrder.POST("/order", r.selfOrderHandler.CreateOrder)
} }