Self Order - Feature
This commit is contained in:
@@ -3,114 +3,196 @@ package handler
|
||||
import (
|
||||
"apskel-pos-be/internal/constants"
|
||||
"apskel-pos-be/internal/contract"
|
||||
"apskel-pos-be/internal/entities"
|
||||
"apskel-pos-be/internal/logger"
|
||||
"apskel-pos-be/internal/models"
|
||||
"apskel-pos-be/internal/processor"
|
||||
"apskel-pos-be/internal/repository"
|
||||
"apskel-pos-be/internal/service"
|
||||
"apskel-pos-be/internal/transformer"
|
||||
"apskel-pos-be/internal/util"
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type SelfOrderHandler struct {
|
||||
orderService service.OrderService
|
||||
productService service.ProductService
|
||||
customerRepo *repository.CustomerRepository
|
||||
userRepo *repository.UserRepositoryImpl
|
||||
outletRepo *repository.OutletRepositoryImpl
|
||||
orderService service.OrderService
|
||||
categoryService service.CategoryService
|
||||
productService service.ProductService
|
||||
tableRepo repository.TableRepositoryInterface
|
||||
outletRepo processor.OutletRepository
|
||||
userRepo processor.UserRepository
|
||||
}
|
||||
|
||||
func NewSelfOrderHandler(
|
||||
orderService service.OrderService,
|
||||
categoryService service.CategoryService,
|
||||
productService service.ProductService,
|
||||
customerRepo *repository.CustomerRepository,
|
||||
userRepo *repository.UserRepositoryImpl,
|
||||
outletRepo *repository.OutletRepositoryImpl,
|
||||
tableRepo repository.TableRepositoryInterface,
|
||||
outletRepo processor.OutletRepository,
|
||||
userRepo processor.UserRepository,
|
||||
) *SelfOrderHandler {
|
||||
return &SelfOrderHandler{
|
||||
orderService: orderService,
|
||||
productService: productService,
|
||||
customerRepo: customerRepo,
|
||||
userRepo: userRepo,
|
||||
outletRepo: outletRepo,
|
||||
orderService: orderService,
|
||||
categoryService: categoryService,
|
||||
productService: productService,
|
||||
tableRepo: tableRepo,
|
||||
outletRepo: outletRepo,
|
||||
userRepo: userRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SelfOrderHandler) GetMenu(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
organizationIDStr, _ := c.Get("self_order_organization_id")
|
||||
outletIDStr, _ := c.Get("self_order_outlet_id")
|
||||
tableIDStr, _ := c.Get("self_order_table_id")
|
||||
tableName, _ := c.Get("self_order_table_name")
|
||||
|
||||
organizationID, err := uuid.Parse(organizationIDStr.(string))
|
||||
if err != nil {
|
||||
var req contract.SelfOrderMenuRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::GetMenu -> request binding failed")
|
||||
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")
|
||||
return
|
||||
}
|
||||
|
||||
outletID, err := uuid.Parse(outletIDStr.(string))
|
||||
if err != nil {
|
||||
if req.TableID == uuid.Nil {
|
||||
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")
|
||||
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
|
||||
req := &contract.ListProductsRequest{
|
||||
OrganizationID: &organizationID,
|
||||
IsActive: &isActive,
|
||||
catResp := h.categoryService.ListCategories(ctx, &contract.ListCategoriesRequest{
|
||||
OrganizationID: &table.OrganizationID,
|
||||
Page: 1,
|
||||
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)
|
||||
|
||||
outlet, outletErr := h.outletRepo.GetByID(ctx, outletID)
|
||||
outletName := ""
|
||||
if outletErr == nil && outlet != nil {
|
||||
outletName = outlet.Name
|
||||
prodResp := h.productService.ListProducts(ctx, &contract.ListProductsRequest{
|
||||
OrganizationID: &table.OrganizationID,
|
||||
IsActive: &isActive,
|
||||
Page: 1,
|
||||
Limit: 1000,
|
||||
})
|
||||
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{
|
||||
OutletID: outletID,
|
||||
OutletName: outletName,
|
||||
TableID: tableID,
|
||||
TableName: tableName.(string),
|
||||
Organization: contract.OrganizationMenuInfo{
|
||||
ID: organizationID,
|
||||
},
|
||||
catList, ok := catResp.Data.(*contract.ListCategoriesResponse)
|
||||
if !ok {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.InternalServerErrorCode, constants.CategoryServiceEntity, "unexpected categories response type"),
|
||||
}), "SelfOrderHandler::GetMenu")
|
||||
return
|
||||
}
|
||||
|
||||
if productsResponse != nil {
|
||||
if data, ok := productsResponse.Data.(*contract.ListProductsResponse); ok {
|
||||
menuResponse.Products = *data
|
||||
prodList, ok := prodResp.Data.(*contract.ListProductsResponse)
|
||||
if !ok {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.InternalServerErrorCode, constants.ProductServiceEntity, "unexpected products response type"),
|
||||
}), "SelfOrderHandler::GetMenu")
|
||||
return
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(menuResponse), "SelfOrderHandler::GetMenu")
|
||||
return &contract.SelfOrderMenuResponse{
|
||||
OutletName: outlet.Name,
|
||||
TableName: table.TableName,
|
||||
Categories: menuCategories,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SelfOrderHandler) CreateOrder(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
organizationIDStr, _ := c.Get("self_order_organization_id")
|
||||
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
|
||||
var req contract.SelfOrderCreateOrderRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateOrder -> request binding failed")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
@@ -119,121 +201,108 @@ func (h *SelfOrderHandler) CreateOrder(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.CustomerName == "" {
|
||||
if err := h.validateCreateOrderRequest(&req); err != nil {
|
||||
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")
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.OrderItems) == 0 {
|
||||
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)
|
||||
table, err := h.tableRepo.GetByID(ctx, req.TableID)
|
||||
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{
|
||||
contract.NewResponseError(constants.InternalServerErrorCode, constants.SelfOrderEntity, "failed to resolve system user"),
|
||||
contract.NewResponseError(constants.NotFoundErrorCode, constants.TableEntity, "table not found"),
|
||||
}), "SelfOrderHandler::CreateOrder")
|
||||
return
|
||||
}
|
||||
|
||||
var customerID *uuid.UUID
|
||||
if req.PhoneNumber != nil && *req.PhoneNumber != "" {
|
||||
customer, err := h.customerRepo.GetByPhoneNumber(ctx, *req.PhoneNumber)
|
||||
if err != nil {
|
||||
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateOrder -> failed to lookup customer by phone")
|
||||
}
|
||||
if customer != nil {
|
||||
customerID = &customer.ID
|
||||
}
|
||||
if !table.IsActive || !table.IsAvailable() {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.ValidationErrorCode, constants.TableEntity, "table is not available for ordering"),
|
||||
}), "SelfOrderHandler::CreateOrder")
|
||||
return
|
||||
}
|
||||
|
||||
metadata := map[string]interface{}{
|
||||
"source": string(constants.OrderSourceSelfOrder),
|
||||
"customer_phone": "",
|
||||
}
|
||||
if req.PhoneNumber != nil {
|
||||
metadata["customer_phone"] = *req.PhoneNumber
|
||||
userID, err := h.resolveOrgUser(ctx, table.OrganizationID)
|
||||
if err != nil {
|
||||
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateOrder -> failed to resolve org user")
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{
|
||||
contract.NewResponseError(constants.InternalServerErrorCode, constants.OrderServiceEntity, "failed to create self-order"),
|
||||
}), "SelfOrderHandler::CreateOrder")
|
||||
return
|
||||
}
|
||||
|
||||
orderItems := make([]models.CreateOrderItemRequest, len(req.OrderItems))
|
||||
for i, item := range req.OrderItems {
|
||||
orderItems[i] = models.CreateOrderItemRequest{
|
||||
orderItems := make([]models.CreateOrderItemRequest, 0, len(req.OrderItems))
|
||||
for _, item := range req.OrderItems {
|
||||
orderItems = append(orderItems, models.CreateOrderItemRequest{
|
||||
ProductID: item.ProductID,
|
||||
ProductVariantID: item.ProductVariantID,
|
||||
Quantity: item.Quantity,
|
||||
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{
|
||||
OutletID: outletID,
|
||||
UserID: adminUser.ID,
|
||||
CustomerID: customerID,
|
||||
OutletID: table.OutletID,
|
||||
UserID: userID,
|
||||
TableID: &tableID,
|
||||
TableNumber: &tableNameStr,
|
||||
TableNumber: &table.TableName,
|
||||
OrderType: constants.OrderTypeDineIn,
|
||||
OrderItems: orderItems,
|
||||
Notes: req.Notes,
|
||||
CustomerName: &req.CustomerName,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
response, err := h.orderService.CreateOrder(ctx, modelReq, organizationID)
|
||||
response, err := h.orderService.CreateOrder(ctx, modelReq, table.OrganizationID)
|
||||
if err != nil {
|
||||
logger.FromContext(ctx).WithError(err).Error("SelfOrderHandler::CreateOrder -> failed to create order")
|
||||
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")
|
||||
return
|
||||
}
|
||||
|
||||
outlet, _ := h.outletRepo.GetByID(ctx, outletID)
|
||||
outletName := ""
|
||||
if outlet != nil {
|
||||
outletName = outlet.Name
|
||||
}
|
||||
contractResp := transformer.OrderModelToContract(response)
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "SelfOrderHandler::CreateOrder")
|
||||
}
|
||||
|
||||
orderItemsResp := make([]contract.OrderItemResponse, len(response.OrderItems))
|
||||
for i, item := range response.OrderItems {
|
||||
orderItemsResp[i] = contract.OrderItemResponse{
|
||||
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,
|
||||
func (h *SelfOrderHandler) validateCreateOrderRequest(req *contract.SelfOrderCreateOrderRequest) error {
|
||||
if req.TableID == uuid.Nil {
|
||||
return fmt.Errorf("table_id is required")
|
||||
}
|
||||
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{
|
||||
OrderID: response.ID,
|
||||
OrderNumber: response.OrderNumber,
|
||||
TableID: tableID,
|
||||
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,
|
||||
func (h *SelfOrderHandler) resolveOrgUser(ctx context.Context, organizationID uuid.UUID) (uuid.UUID, error) {
|
||||
users, err := h.userRepo.GetByOrganizationID(ctx, organizationID)
|
||||
if err != nil {
|
||||
return uuid.Nil, fmt.Errorf("failed to get users for organization: %w", err)
|
||||
}
|
||||
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(selfOrderResp), "SelfOrderHandler::CreateOrder")
|
||||
if len(users) == 0 {
|
||||
return uuid.Nil, fmt.Errorf("no users found for organization")
|
||||
}
|
||||
return users[0].ID, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user