This commit is contained in:
Aditya Siregar
2025-08-03 00:34:25 +07:00
parent a759e0f57c
commit 96743cf50b
25 changed files with 2049 additions and 2141 deletions
+3 -1
View File
@@ -2,6 +2,7 @@ package app
import (
"apskel-pos-be/internal/client"
"apskel-pos-be/internal/transformer"
"context"
"log"
"net/http"
@@ -74,6 +75,7 @@ func (a *App) Initialize(cfg *config.Config) error {
validators.paymentMethodValidator,
services.analyticsService,
services.tableService,
validators.tableValidator,
)
return nil
@@ -213,7 +215,7 @@ type services struct {
fileService service.FileService
customerService service.CustomerService
analyticsService *service.AnalyticsServiceImpl
tableService *service.TableService
tableService *service.TableServiceImpl
}
func (a *App) initServices(processors *processors, cfg *config.Config) *services {
+3
View File
@@ -11,6 +11,7 @@ const (
MalformedFieldErrorCode = "310"
ValidationErrorCode = "304"
InvalidFieldErrorCode = "305"
NotFoundErrorCode = "404"
)
const (
@@ -37,6 +38,7 @@ const (
PaymentMethodValidatorEntity = "payment_method_validator"
PaymentMethodHandlerEntity = "payment_method_handler"
OutletServiceEntity = "outlet_service"
TableEntity = "table"
)
var HttpErrorMap = map[string]int{
@@ -45,6 +47,7 @@ var HttpErrorMap = map[string]int{
MalformedFieldErrorCode: http.StatusBadRequest,
ValidationErrorCode: http.StatusBadRequest,
InvalidFieldErrorCode: http.StatusBadRequest,
NotFoundErrorCode: http.StatusNotFound,
}
// Error messages
+1 -4
View File
@@ -1,12 +1,10 @@
package contract
import (
"time"
"github.com/google/uuid"
"time"
)
// Inventory Request DTOs
type CreateInventoryRequest struct {
OutletID uuid.UUID `json:"outlet_id" validate:"required"`
ProductID uuid.UUID `json:"product_id" validate:"required"`
@@ -37,7 +35,6 @@ type ListInventoryRequest struct {
Limit int `json:"limit" validate:"required,min=1,max=100"`
}
// Inventory Response DTOs
type InventoryResponse struct {
ID uuid.UUID `json:"id"`
OutletID uuid.UUID `json:"outlet_id"`
+1 -1
View File
@@ -36,7 +36,7 @@ func (t *Table) BeforeCreate(tx *gorm.DB) error {
return nil
}
func (Table) TableName() string {
func (Table) GetTableName() string {
return "tables"
}
+135 -233
View File
@@ -1,9 +1,12 @@
package handler
import (
"apskel-pos-be/internal/appcontext"
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
"apskel-pos-be/internal/service"
"net/http"
"apskel-pos-be/internal/logger"
"apskel-pos-be/internal/util"
"apskel-pos-be/internal/validator"
"strconv"
"github.com/gin-gonic/gin"
@@ -11,189 +14,126 @@ import (
)
type TableHandler struct {
tableService *service.TableService
tableService TableService
tableValidator *validator.TableValidator
}
func NewTableHandler(tableService *service.TableService) *TableHandler {
func NewTableHandler(tableService TableService, tableValidator *validator.TableValidator) *TableHandler {
return &TableHandler{
tableService: tableService,
tableService: tableService,
tableValidator: tableValidator,
}
}
// CreateTable godoc
// @Summary Create a new table
// @Description Create a new table for the organization
// @Tags tables
// @Accept json
// @Produce json
// @Param table body contract.CreateTableRequest true "Table data"
// @Success 201 {object} contract.TableResponse
// @Failure 400 {object} contract.ResponseError
// @Failure 401 {object} contract.ResponseError
// @Failure 500 {object} contract.ResponseError
// @Router /tables [post]
func (h *TableHandler) Create(c *gin.Context) {
ctx := c.Request.Context()
contextInfo := appcontext.FromGinContext(ctx)
var req contract.CreateTableRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, contract.ResponseError{
Error: "Invalid request body",
Message: err.Error(),
})
logger.FromContext(ctx).WithError(err).Error("TableHandler::Create -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "TableHandler::Create")
return
}
organizationID := c.GetString("organization_id")
orgID, err := uuid.Parse(organizationID)
if err != nil {
c.JSON(http.StatusBadRequest, contract.ResponseError{
Error: "Invalid organization ID",
Message: err.Error(),
})
if err := h.tableValidator.ValidateCreateTableRequest(req); err != nil {
logger.FromContext(ctx).WithError(err).Error("TableHandler::Create -> validation failed")
validationResponseError := contract.NewResponseError(constants.ValidationErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "TableHandler::Create")
return
}
response, err := h.tableService.Create(c.Request.Context(), req, orgID)
if err != nil {
c.JSON(http.StatusInternalServerError, contract.ResponseError{
Error: "Failed to create table",
Message: err.Error(),
})
return
response := h.tableService.CreateTable(ctx, contextInfo, &req)
if response.HasErrors() {
errorResp := response.GetErrors()[0]
logger.FromContext(ctx).WithError(errorResp).Error("TableHandler::Create -> service failed")
}
c.JSON(http.StatusCreated, response)
util.HandleResponse(c.Writer, c.Request, response, "TableHandler::Create")
}
// GetTable godoc
// @Summary Get table by ID
// @Description Get table details by ID
// @Tags tables
// @Produce json
// @Param id path string true "Table ID"
// @Success 200 {object} contract.TableResponse
// @Failure 400 {object} contract.ResponseError
// @Failure 404 {object} contract.ResponseError
// @Failure 500 {object} contract.ResponseError
// @Router /tables/{id} [get]
func (h *TableHandler) GetByID(c *gin.Context) {
ctx := c.Request.Context()
id := c.Param("id")
tableID, err := uuid.Parse(id)
if err != nil {
c.JSON(http.StatusBadRequest, contract.ResponseError{
Error: "Invalid table ID",
Message: err.Error(),
})
logger.FromContext(ctx).WithError(err).Error("TableHandler::GetByID -> Invalid table ID")
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid table ID")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "TableHandler::GetByID")
return
}
response, err := h.tableService.GetByID(c.Request.Context(), tableID)
if err != nil {
c.JSON(http.StatusNotFound, contract.ResponseError{
Error: "Table not found",
Message: err.Error(),
})
return
response := h.tableService.GetTableByID(ctx, tableID)
if response.HasErrors() {
errorResp := response.GetErrors()[0]
logger.FromContext(ctx).WithError(errorResp).Error("TableHandler::GetByID -> service failed")
}
c.JSON(http.StatusOK, response)
util.HandleResponse(c.Writer, c.Request, response, "TableHandler::GetByID")
}
// UpdateTable godoc
// @Summary Update table
// @Description Update table details
// @Tags tables
// @Accept json
// @Produce json
// @Param id path string true "Table ID"
// @Param table body contract.UpdateTableRequest true "Table update data"
// @Success 200 {object} contract.TableResponse
// @Failure 400 {object} contract.ResponseError
// @Failure 404 {object} contract.ResponseError
// @Failure 500 {object} contract.ResponseError
// @Router /tables/{id} [put]
func (h *TableHandler) Update(c *gin.Context) {
ctx := c.Request.Context()
id := c.Param("id")
tableID, err := uuid.Parse(id)
if err != nil {
c.JSON(http.StatusBadRequest, contract.ResponseError{
Error: "Invalid table ID",
Message: err.Error(),
})
logger.FromContext(ctx).WithError(err).Error("TableHandler::Update -> Invalid table ID")
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid table ID")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "TableHandler::Update")
return
}
var req contract.UpdateTableRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, contract.ResponseError{
Error: "Invalid request body",
Message: err.Error(),
})
logger.FromContext(ctx).WithError(err).Error("TableHandler::Update -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "TableHandler::Update")
return
}
response, err := h.tableService.Update(c.Request.Context(), tableID, req)
if err != nil {
c.JSON(http.StatusInternalServerError, contract.ResponseError{
Error: "Failed to update table",
Message: err.Error(),
})
if err := h.tableValidator.ValidateUpdateTableRequest(req); err != nil {
logger.FromContext(ctx).WithError(err).Error("TableHandler::Update -> validation failed")
validationResponseError := contract.NewResponseError(constants.ValidationErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "TableHandler::Update")
return
}
c.JSON(http.StatusOK, response)
response := h.tableService.UpdateTable(ctx, tableID, &req)
if response.HasErrors() {
errorResp := response.GetErrors()[0]
logger.FromContext(ctx).WithError(errorResp).Error("TableHandler::Update -> service failed")
}
util.HandleResponse(c.Writer, c.Request, response, "TableHandler::Update")
}
// DeleteTable godoc
// @Summary Delete table
// @Description Delete table by ID
// @Tags tables
// @Produce json
// @Param id path string true "Table ID"
// @Success 204 "No Content"
// @Failure 400 {object} contract.ResponseError
// @Failure 404 {object} contract.ResponseError
// @Failure 500 {object} contract.ResponseError
// @Router /tables/{id} [delete]
func (h *TableHandler) Delete(c *gin.Context) {
ctx := c.Request.Context()
id := c.Param("id")
tableID, err := uuid.Parse(id)
if err != nil {
c.JSON(http.StatusBadRequest, contract.ResponseError{
Error: "Invalid table ID",
Message: err.Error(),
})
logger.FromContext(ctx).WithError(err).Error("TableHandler::Delete -> Invalid table ID")
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid table ID")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "TableHandler::Delete")
return
}
err = h.tableService.Delete(c.Request.Context(), tableID)
if err != nil {
c.JSON(http.StatusInternalServerError, contract.ResponseError{
Error: "Failed to delete table",
Message: err.Error(),
})
return
response := h.tableService.DeleteTable(ctx, tableID)
if response.HasErrors() {
errorResp := response.GetErrors()[0]
logger.FromContext(ctx).WithError(errorResp).Error("TableHandler::Delete -> service failed")
}
c.Status(http.StatusNoContent)
util.HandleResponse(c.Writer, c.Request, response, "TableHandler::Delete")
}
// ListTables godoc
// @Summary List tables
// @Description Get paginated list of tables
// @Tags tables
// @Produce json
// @Param organization_id query string false "Organization ID"
// @Param outlet_id query string false "Outlet ID"
// @Param status query string false "Table status"
// @Param is_active query string false "Is active"
// @Param search query string false "Search term"
// @Param page query int false "Page number" default(1)
// @Param limit query int false "Page size" default(10)
// @Success 200 {object} contract.ListTablesResponse
// @Failure 400 {object} contract.ResponseError
// @Failure 500 {object} contract.ResponseError
// @Router /tables [get]
func (h *TableHandler) List(c *gin.Context) {
ctx := c.Request.Context()
query := contract.ListTablesQuery{
OrganizationID: c.Query("organization_id"),
OutletID: c.Query("outlet_id"),
@@ -216,170 +156,132 @@ func (h *TableHandler) List(c *gin.Context) {
}
}
response, err := h.tableService.List(c.Request.Context(), query)
if err != nil {
c.JSON(http.StatusInternalServerError, contract.ResponseError{
Error: "Failed to list tables",
Message: err.Error(),
})
if err := h.tableValidator.ValidateListTablesQuery(query); err != nil {
logger.FromContext(ctx).WithError(err).Error("TableHandler::List -> validation failed")
validationResponseError := contract.NewResponseError(constants.ValidationErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "TableHandler::List")
return
}
c.JSON(http.StatusOK, response)
response := h.tableService.ListTables(ctx, &query)
if response.HasErrors() {
errorResp := response.GetErrors()[0]
logger.FromContext(ctx).WithError(errorResp).Error("TableHandler::List -> service failed")
}
util.HandleResponse(c.Writer, c.Request, response, "TableHandler::List")
}
// OccupyTable godoc
// @Summary Occupy table
// @Description Occupy a table with an order
// @Tags tables
// @Accept json
// @Produce json
// @Param id path string true "Table ID"
// @Param request body contract.OccupyTableRequest true "Occupy table data"
// @Success 200 {object} contract.TableResponse
// @Failure 400 {object} contract.ResponseError
// @Failure 404 {object} contract.ResponseError
// @Failure 500 {object} contract.ResponseError
// @Router /tables/{id}/occupy [post]
func (h *TableHandler) OccupyTable(c *gin.Context) {
ctx := c.Request.Context()
id := c.Param("id")
tableID, err := uuid.Parse(id)
if err != nil {
c.JSON(http.StatusBadRequest, contract.ResponseError{
Error: "Invalid table ID",
Message: err.Error(),
})
logger.FromContext(ctx).WithError(err).Error("TableHandler::OccupyTable -> Invalid table ID")
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid table ID")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "TableHandler::OccupyTable")
return
}
var req contract.OccupyTableRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, contract.ResponseError{
Error: "Invalid request body",
Message: err.Error(),
})
logger.FromContext(ctx).WithError(err).Error("TableHandler::OccupyTable -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "TableHandler::OccupyTable")
return
}
response, err := h.tableService.OccupyTable(c.Request.Context(), tableID, req)
if err != nil {
c.JSON(http.StatusInternalServerError, contract.ResponseError{
Error: "Failed to occupy table",
Message: err.Error(),
})
if err := h.tableValidator.ValidateOccupyTableRequest(req); err != nil {
logger.FromContext(ctx).WithError(err).Error("TableHandler::OccupyTable -> validation failed")
validationResponseError := contract.NewResponseError(constants.ValidationErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "TableHandler::OccupyTable")
return
}
c.JSON(http.StatusOK, response)
response := h.tableService.OccupyTable(ctx, tableID, &req)
if response.HasErrors() {
errorResp := response.GetErrors()[0]
logger.FromContext(ctx).WithError(errorResp).Error("TableHandler::OccupyTable -> service failed")
}
util.HandleResponse(c.Writer, c.Request, response, "TableHandler::OccupyTable")
}
// ReleaseTable godoc
// @Summary Release table
// @Description Release a table and record payment amount
// @Tags tables
// @Accept json
// @Produce json
// @Param id path string true "Table ID"
// @Param request body contract.ReleaseTableRequest true "Release table data"
// @Success 200 {object} contract.TableResponse
// @Failure 400 {object} contract.ResponseError
// @Failure 404 {object} contract.ResponseError
// @Failure 500 {object} contract.ResponseError
// @Router /tables/{id}/release [post]
func (h *TableHandler) ReleaseTable(c *gin.Context) {
ctx := c.Request.Context()
id := c.Param("id")
tableID, err := uuid.Parse(id)
if err != nil {
c.JSON(http.StatusBadRequest, contract.ResponseError{
Error: "Invalid table ID",
Message: err.Error(),
})
logger.FromContext(ctx).WithError(err).Error("TableHandler::ReleaseTable -> Invalid table ID")
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid table ID")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "TableHandler::ReleaseTable")
return
}
var req contract.ReleaseTableRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, contract.ResponseError{
Error: "Invalid request body",
Message: err.Error(),
})
logger.FromContext(ctx).WithError(err).Error("TableHandler::ReleaseTable -> request binding failed")
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "TableHandler::ReleaseTable")
return
}
response, err := h.tableService.ReleaseTable(c.Request.Context(), tableID, req)
if err != nil {
c.JSON(http.StatusInternalServerError, contract.ResponseError{
Error: "Failed to release table",
Message: err.Error(),
})
if err := h.tableValidator.ValidateReleaseTableRequest(req); err != nil {
logger.FromContext(ctx).WithError(err).Error("TableHandler::ReleaseTable -> validation failed")
validationResponseError := contract.NewResponseError(constants.ValidationErrorCode, constants.RequestEntity, err.Error())
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "TableHandler::ReleaseTable")
return
}
c.JSON(http.StatusOK, response)
response := h.tableService.ReleaseTable(ctx, tableID, &req)
if response.HasErrors() {
errorResp := response.GetErrors()[0]
logger.FromContext(ctx).WithError(errorResp).Error("TableHandler::ReleaseTable -> service failed")
}
util.HandleResponse(c.Writer, c.Request, response, "TableHandler::ReleaseTable")
}
// GetAvailableTables godoc
// @Summary Get available tables
// @Description Get list of available tables for an outlet
// @Tags tables
// @Produce json
// @Param outlet_id path string true "Outlet ID"
// @Success 200 {array} contract.TableResponse
// @Failure 400 {object} contract.ResponseError
// @Failure 500 {object} contract.ResponseError
// @Router /outlets/{outlet_id}/tables/available [get]
func (h *TableHandler) GetAvailableTables(c *gin.Context) {
ctx := c.Request.Context()
outletIDStr := c.Param("outlet_id")
outletID, err := uuid.Parse(outletIDStr)
if err != nil {
c.JSON(http.StatusBadRequest, contract.ResponseError{
Error: "Invalid outlet ID",
Message: err.Error(),
})
logger.FromContext(ctx).WithError(err).Error("TableHandler::GetAvailableTables -> Invalid outlet ID")
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid outlet ID")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "TableHandler::GetAvailableTables")
return
}
tables, err := h.tableService.GetAvailableTables(c.Request.Context(), outletID)
if err != nil {
c.JSON(http.StatusInternalServerError, contract.ResponseError{
Error: "Failed to get available tables",
Message: err.Error(),
})
return
response := h.tableService.GetAvailableTables(ctx, outletID)
if response.HasErrors() {
errorResp := response.GetErrors()[0]
logger.FromContext(ctx).WithError(errorResp).Error("TableHandler::GetAvailableTables -> service failed")
}
c.JSON(http.StatusOK, tables)
util.HandleResponse(c.Writer, c.Request, response, "TableHandler::GetAvailableTables")
}
// GetOccupiedTables godoc
// @Summary Get occupied tables
// @Description Get list of occupied tables for an outlet
// @Tags tables
// @Produce json
// @Param outlet_id path string true "Outlet ID"
// @Success 200 {array} contract.TableResponse
// @Failure 400 {object} contract.ResponseError
// @Failure 500 {object} contract.ResponseError
// @Router /outlets/{outlet_id}/tables/occupied [get]
func (h *TableHandler) GetOccupiedTables(c *gin.Context) {
ctx := c.Request.Context()
outletIDStr := c.Param("outlet_id")
outletID, err := uuid.Parse(outletIDStr)
if err != nil {
c.JSON(http.StatusBadRequest, contract.ResponseError{
Error: "Invalid outlet ID",
Message: err.Error(),
})
logger.FromContext(ctx).WithError(err).Error("TableHandler::GetOccupiedTables -> Invalid outlet ID")
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid outlet ID")
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "TableHandler::GetOccupiedTables")
return
}
tables, err := h.tableService.GetOccupiedTables(c.Request.Context(), outletID)
if err != nil {
c.JSON(http.StatusInternalServerError, contract.ResponseError{
Error: "Failed to get occupied tables",
Message: err.Error(),
})
return
response := h.tableService.GetOccupiedTables(ctx, outletID)
if response.HasErrors() {
errorResp := response.GetErrors()[0]
logger.FromContext(ctx).WithError(errorResp).Error("TableHandler::GetOccupiedTables -> service failed")
}
c.JSON(http.StatusOK, tables)
util.HandleResponse(c.Writer, c.Request, response, "TableHandler::GetOccupiedTables")
}
+20
View File
@@ -0,0 +1,20 @@
package handler
import (
"apskel-pos-be/internal/appcontext"
"apskel-pos-be/internal/contract"
"context"
"github.com/google/uuid"
)
type TableService interface {
CreateTable(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.CreateTableRequest) *contract.Response
UpdateTable(ctx context.Context, id uuid.UUID, req *contract.UpdateTableRequest) *contract.Response
DeleteTable(ctx context.Context, id uuid.UUID) *contract.Response
GetTableByID(ctx context.Context, id uuid.UUID) *contract.Response
ListTables(ctx context.Context, req *contract.ListTablesQuery) *contract.Response
OccupyTable(ctx context.Context, tableID uuid.UUID, req *contract.OccupyTableRequest) *contract.Response
ReleaseTable(ctx context.Context, tableID uuid.UUID, req *contract.ReleaseTableRequest) *contract.Response
GetAvailableTables(ctx context.Context, outletID uuid.UUID) *contract.Response
GetOccupiedTables(ctx context.Context, outletID uuid.UUID) *contract.Response
}
+3 -3
View File
@@ -14,10 +14,10 @@ import (
type TableProcessor struct {
tableRepo *repository.TableRepository
orderRepo *repository.OrderRepository
orderRepo repository.OrderRepository
}
func NewTableProcessor(tableRepo *repository.TableRepository, orderRepo *repository.OrderRepository) *TableProcessor {
func NewTableProcessor(tableRepo *repository.TableRepository, orderRepo repository.OrderRepository) *TableProcessor {
return &TableProcessor{
tableRepo: tableRepo,
orderRepo: orderRepo,
@@ -136,7 +136,7 @@ func (p *TableProcessor) OccupyTable(ctx context.Context, tableID uuid.UUID, req
}
// Verify order exists
order, err := p.orderRepo.GetByID(ctx, req.OrderID)
_, err = p.orderRepo.GetByID(ctx, req.OrderID)
if err != nil {
return nil, errors.New("order not found")
}
+5 -10
View File
@@ -60,7 +60,8 @@ func NewRouter(cfg *config.Config,
paymentMethodService service.PaymentMethodService,
paymentMethodValidator validator.PaymentMethodValidator,
analyticsService *service.AnalyticsServiceImpl,
tableService *service.TableService) *Router {
tableService *service.TableServiceImpl,
tableValidator *validator.TableValidator) *Router {
return &Router{
config: cfg,
@@ -78,7 +79,7 @@ func NewRouter(cfg *config.Config,
customerHandler: handler.NewCustomerHandler(customerService, customerValidator),
paymentMethodHandler: handler.NewPaymentMethodHandler(paymentMethodService, paymentMethodValidator),
analyticsHandler: handler.NewAnalyticsHandler(analyticsService, transformer.NewTransformer()),
tableHandler: handler.NewTableHandler(tableService),
tableHandler: handler.NewTableHandler(tableService, tableValidator),
authMiddleware: authMiddleware,
}
}
@@ -261,19 +262,13 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
outlets.Use(r.authMiddleware.RequireAdminOrManager())
{
outlets.GET("/list", r.outletHandler.ListOutlets)
outlets.GET("/:id", r.outletHandler.GetOutlet)
outlets.PUT("/:id", r.outletHandler.UpdateOutlet)
outlets.GET("/detail/:id", r.outletHandler.GetOutlet)
outlets.PUT("/detail/:id", r.outletHandler.UpdateOutlet)
outlets.GET("/printer-setting/:outlet_id", r.outletSettingHandler.GetPrinterSettings)
outlets.PUT("/printer-setting/:outlet_id", r.outletSettingHandler.UpdatePrinterSettings)
outlets.GET("/:outlet_id/tables/available", r.tableHandler.GetAvailableTables)
outlets.GET("/:outlet_id/tables/occupied", r.tableHandler.GetOccupiedTables)
}
//outletPrinterSettings := protected.Group("/outlets/:outlet_id/settings")
//outletPrinterSettings.Use(r.authMiddleware.RequireAdminOrManager())
//{
//
//}
}
}
}
+71 -99
View File
@@ -1,115 +1,84 @@
package service
import (
"apskel-pos-be/internal/appcontext"
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
"apskel-pos-be/internal/models"
"apskel-pos-be/internal/processor"
"apskel-pos-be/internal/transformer"
"context"
"strconv"
"github.com/google/uuid"
)
type TableService struct {
type TableServiceImpl struct {
tableProcessor *processor.TableProcessor
tableTransformer *transformer.TableTransformer
}
func NewTableService(tableProcessor *processor.TableProcessor, tableTransformer *transformer.TableTransformer) *TableService {
return &TableService{
func NewTableService(tableProcessor *processor.TableProcessor, tableTransformer *transformer.TableTransformer) *TableServiceImpl {
return &TableServiceImpl{
tableProcessor: tableProcessor,
tableTransformer: tableTransformer,
}
}
func (s *TableService) Create(ctx context.Context, req contract.CreateTableRequest, organizationID uuid.UUID) (*contract.TableResponse, error) {
modelReq := models.CreateTableRequest{
OutletID: req.OutletID,
TableName: req.TableName,
PositionX: req.PositionX,
PositionY: req.PositionY,
Capacity: req.Capacity,
Metadata: req.Metadata,
}
func (s *TableServiceImpl) CreateTable(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.CreateTableRequest) *contract.Response {
modelReq := transformer.CreateTableRequestToModel(apctx, req)
response, err := s.tableProcessor.Create(ctx, modelReq, organizationID)
response, err := s.tableProcessor.Create(ctx, modelReq, apctx.OrganizationID)
if err != nil {
return nil, err
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.TableEntity, err.Error())
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
}
return s.tableTransformer.ToContract(*response), nil
contractResponse := s.tableTransformer.ToContract(*response)
return contract.BuildSuccessResponse(contractResponse)
}
func (s *TableService) GetByID(ctx context.Context, id uuid.UUID) (*contract.TableResponse, error) {
response, err := s.tableProcessor.GetByID(ctx, id)
if err != nil {
return nil, err
}
return s.tableTransformer.ToContract(*response), nil
}
func (s *TableService) Update(ctx context.Context, id uuid.UUID, req contract.UpdateTableRequest) (*contract.TableResponse, error) {
modelReq := models.UpdateTableRequest{
TableName: req.TableName,
PositionX: req.PositionX,
PositionY: req.PositionY,
Capacity: req.Capacity,
IsActive: req.IsActive,
Metadata: req.Metadata,
}
if req.Status != nil {
status := models.TableStatus(*req.Status)
modelReq.Status = &status
}
func (s *TableServiceImpl) UpdateTable(ctx context.Context, id uuid.UUID, req *contract.UpdateTableRequest) *contract.Response {
modelReq := transformer.UpdateTableRequestToModel(req)
response, err := s.tableProcessor.Update(ctx, id, modelReq)
if err != nil {
return nil, err
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.TableEntity, err.Error())
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
}
return s.tableTransformer.ToContract(*response), nil
contractResponse := s.tableTransformer.ToContract(*response)
return contract.BuildSuccessResponse(contractResponse)
}
func (s *TableService) Delete(ctx context.Context, id uuid.UUID) error {
return s.tableProcessor.Delete(ctx, id)
}
func (s *TableService) List(ctx context.Context, query contract.ListTablesQuery) (*contract.ListTablesResponse, error) {
req := models.ListTablesRequest{
Page: query.Page,
Limit: query.Limit,
Search: query.Search,
}
if query.OrganizationID != "" {
if orgID, err := uuid.Parse(query.OrganizationID); err == nil {
req.OrganizationID = &orgID
}
}
if query.OutletID != "" {
if outletID, err := uuid.Parse(query.OutletID); err == nil {
req.OutletID = &outletID
}
}
if query.Status != "" {
status := models.TableStatus(query.Status)
req.Status = &status
}
if query.IsActive != "" {
if isActive, err := strconv.ParseBool(query.IsActive); err == nil {
req.IsActive = &isActive
}
}
response, err := s.tableProcessor.List(ctx, req)
func (s *TableServiceImpl) DeleteTable(ctx context.Context, id uuid.UUID) *contract.Response {
err := s.tableProcessor.Delete(ctx, id)
if err != nil {
return nil, err
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.TableEntity, err.Error())
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
}
return contract.BuildSuccessResponse(map[string]interface{}{
"message": "Table deleted successfully",
})
}
func (s *TableServiceImpl) GetTableByID(ctx context.Context, id uuid.UUID) *contract.Response {
response, err := s.tableProcessor.GetByID(ctx, id)
if err != nil {
errorResp := contract.NewResponseError(constants.NotFoundErrorCode, constants.TableEntity, "Table not found")
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
}
contractResponse := s.tableTransformer.ToContract(*response)
return contract.BuildSuccessResponse(contractResponse)
}
func (s *TableServiceImpl) ListTables(ctx context.Context, req *contract.ListTablesQuery) *contract.Response {
modelReq := transformer.ListTablesQueryToModel(req)
response, err := s.tableProcessor.List(ctx, modelReq)
if err != nil {
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.TableEntity, err.Error())
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
}
contractTables := make([]contract.TableResponse, len(response.Tables))
@@ -117,46 +86,48 @@ func (s *TableService) List(ctx context.Context, query contract.ListTablesQuery)
contractTables[i] = *s.tableTransformer.ToContract(table)
}
return &contract.ListTablesResponse{
contractResponse := &contract.ListTablesResponse{
Tables: contractTables,
TotalCount: response.TotalCount,
Page: response.Page,
Limit: response.Limit,
TotalPages: response.TotalPages,
}, nil
}
return contract.BuildSuccessResponse(contractResponse)
}
func (s *TableService) OccupyTable(ctx context.Context, tableID uuid.UUID, req contract.OccupyTableRequest) (*contract.TableResponse, error) {
modelReq := models.OccupyTableRequest{
OrderID: req.OrderID,
StartTime: req.StartTime,
}
func (s *TableServiceImpl) OccupyTable(ctx context.Context, tableID uuid.UUID, req *contract.OccupyTableRequest) *contract.Response {
modelReq := transformer.OccupyTableRequestToModel(req)
response, err := s.tableProcessor.OccupyTable(ctx, tableID, modelReq)
if err != nil {
return nil, err
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.TableEntity, err.Error())
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
}
return s.tableTransformer.ToContract(*response), nil
contractResponse := s.tableTransformer.ToContract(*response)
return contract.BuildSuccessResponse(contractResponse)
}
func (s *TableService) ReleaseTable(ctx context.Context, tableID uuid.UUID, req contract.ReleaseTableRequest) (*contract.TableResponse, error) {
modelReq := models.ReleaseTableRequest{
PaymentAmount: req.PaymentAmount,
}
func (s *TableServiceImpl) ReleaseTable(ctx context.Context, tableID uuid.UUID, req *contract.ReleaseTableRequest) *contract.Response {
modelReq := transformer.ReleaseTableRequestToModel(req)
response, err := s.tableProcessor.ReleaseTable(ctx, tableID, modelReq)
if err != nil {
return nil, err
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.TableEntity, err.Error())
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
}
return s.tableTransformer.ToContract(*response), nil
contractResponse := s.tableTransformer.ToContract(*response)
return contract.BuildSuccessResponse(contractResponse)
}
func (s *TableService) GetAvailableTables(ctx context.Context, outletID uuid.UUID) ([]contract.TableResponse, error) {
func (s *TableServiceImpl) GetAvailableTables(ctx context.Context, outletID uuid.UUID) *contract.Response {
tables, err := s.tableProcessor.GetAvailableTables(ctx, outletID)
if err != nil {
return nil, err
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.TableEntity, err.Error())
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
}
responses := make([]contract.TableResponse, len(tables))
@@ -164,13 +135,14 @@ func (s *TableService) GetAvailableTables(ctx context.Context, outletID uuid.UUI
responses[i] = *s.tableTransformer.ToContract(table)
}
return responses, nil
return contract.BuildSuccessResponse(responses)
}
func (s *TableService) GetOccupiedTables(ctx context.Context, outletID uuid.UUID) ([]contract.TableResponse, error) {
func (s *TableServiceImpl) GetOccupiedTables(ctx context.Context, outletID uuid.UUID) *contract.Response {
tables, err := s.tableProcessor.GetOccupiedTables(ctx, outletID)
if err != nil {
return nil, err
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.TableEntity, err.Error())
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
}
responses := make([]contract.TableResponse, len(tables))
@@ -178,5 +150,5 @@ func (s *TableService) GetOccupiedTables(ctx context.Context, outletID uuid.UUID
responses[i] = *s.tableTransformer.ToContract(table)
}
return responses, nil
return contract.BuildSuccessResponse(responses)
}
+73 -60
View File
@@ -1,8 +1,13 @@
package transformer
import (
"apskel-pos-be/internal/appcontext"
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
"apskel-pos-be/internal/models"
"strconv"
"github.com/google/uuid"
)
type TableTransformer struct{}
@@ -33,10 +38,8 @@ func (t *TableTransformer) ToContract(model models.TableResponse) *contract.Tabl
if model.Order != nil {
response.Order = &contract.OrderResponse{
ID: model.Order.ID,
OrganizationID: model.Order.OrganizationID,
OutletID: model.Order.OutletID,
UserID: model.Order.UserID,
CustomerID: model.Order.CustomerID,
OrderNumber: model.Order.OrderNumber,
TableNumber: model.Order.TableNumber,
OrderType: string(model.Order.OrderType),
@@ -45,17 +48,6 @@ func (t *TableTransformer) ToContract(model models.TableResponse) *contract.Tabl
TaxAmount: model.Order.TaxAmount,
DiscountAmount: model.Order.DiscountAmount,
TotalAmount: model.Order.TotalAmount,
TotalCost: model.Order.TotalCost,
PaymentStatus: string(model.Order.PaymentStatus),
RefundAmount: model.Order.RefundAmount,
IsVoid: model.Order.IsVoid,
IsRefund: model.Order.IsRefund,
VoidReason: model.Order.VoidReason,
VoidedAt: model.Order.VoidedAt,
VoidedBy: model.Order.VoidedBy,
RefundReason: model.Order.RefundReason,
RefundedAt: model.Order.RefundedAt,
RefundedBy: model.Order.RefundedBy,
Metadata: model.Order.Metadata,
CreatedAt: model.Order.CreatedAt,
UpdatedAt: model.Order.UpdatedAt,
@@ -65,56 +57,77 @@ func (t *TableTransformer) ToContract(model models.TableResponse) *contract.Tabl
return response
}
func (t *TableTransformer) ToModel(contract contract.TableResponse) *models.TableResponse {
response := &models.TableResponse{
ID: contract.ID,
OrganizationID: contract.OrganizationID,
OutletID: contract.OutletID,
TableName: contract.TableName,
StartTime: contract.StartTime,
Status: models.TableStatus(contract.Status),
OrderID: contract.OrderID,
PaymentAmount: contract.PaymentAmount,
PositionX: contract.PositionX,
PositionY: contract.PositionY,
Capacity: contract.Capacity,
IsActive: contract.IsActive,
Metadata: contract.Metadata,
CreatedAt: contract.CreatedAt,
UpdatedAt: contract.UpdatedAt,
func CreateTableRequestToModel(apctx *appcontext.ContextInfo, req *contract.CreateTableRequest) models.CreateTableRequest {
return models.CreateTableRequest{
OutletID: req.OutletID,
TableName: req.TableName,
PositionX: req.PositionX,
PositionY: req.PositionY,
Capacity: req.Capacity,
Metadata: req.Metadata,
}
}
func UpdateTableRequestToModel(req *contract.UpdateTableRequest) models.UpdateTableRequest {
modelReq := models.UpdateTableRequest{
TableName: req.TableName,
PositionX: req.PositionX,
PositionY: req.PositionY,
Capacity: req.Capacity,
IsActive: req.IsActive,
Metadata: req.Metadata,
}
if contract.Order != nil {
response.Order = &models.OrderResponse{
ID: contract.Order.ID,
OrganizationID: contract.Order.OrganizationID,
OutletID: contract.Order.OutletID,
UserID: contract.Order.UserID,
CustomerID: contract.Order.CustomerID,
OrderNumber: contract.Order.OrderNumber,
TableNumber: contract.Order.TableNumber,
OrderType: models.OrderType(contract.Order.OrderType),
Status: models.OrderStatus(contract.Order.Status),
Subtotal: contract.Order.Subtotal,
TaxAmount: contract.Order.TaxAmount,
DiscountAmount: contract.Order.DiscountAmount,
TotalAmount: contract.Order.TotalAmount,
TotalCost: contract.Order.TotalCost,
PaymentStatus: models.PaymentStatus(contract.Order.PaymentStatus),
RefundAmount: contract.Order.RefundAmount,
IsVoid: contract.Order.IsVoid,
IsRefund: contract.Order.IsRefund,
VoidReason: contract.Order.VoidReason,
VoidedAt: contract.Order.VoidedAt,
VoidedBy: contract.Order.VoidedBy,
RefundReason: contract.Order.RefundReason,
RefundedAt: contract.Order.RefundedAt,
RefundedBy: contract.Order.RefundedBy,
Metadata: contract.Order.Metadata,
CreatedAt: contract.Order.CreatedAt,
UpdatedAt: contract.Order.UpdatedAt,
if req.Status != nil {
status := constants.TableStatus(*req.Status)
modelReq.Status = &status
}
return modelReq
}
func OccupyTableRequestToModel(req *contract.OccupyTableRequest) models.OccupyTableRequest {
return models.OccupyTableRequest{
OrderID: req.OrderID,
StartTime: req.StartTime,
}
}
func ReleaseTableRequestToModel(req *contract.ReleaseTableRequest) models.ReleaseTableRequest {
return models.ReleaseTableRequest{
PaymentAmount: req.PaymentAmount,
}
}
func ListTablesQueryToModel(req *contract.ListTablesQuery) models.ListTablesRequest {
modelReq := models.ListTablesRequest{
Page: req.Page,
Limit: req.Limit,
Search: req.Search,
}
if req.OrganizationID != "" {
if orgID, err := uuid.Parse(req.OrganizationID); err == nil {
modelReq.OrganizationID = &orgID
}
}
return response
if req.OutletID != "" {
if outletID, err := uuid.Parse(req.OutletID); err == nil {
modelReq.OutletID = &outletID
}
}
if req.Status != "" {
status := constants.TableStatus(req.Status)
modelReq.Status = &status
}
if req.IsActive != "" {
if isActive, err := strconv.ParseBool(req.IsActive); err == nil {
modelReq.IsActive = &isActive
}
}
return modelReq
}
+31 -1
View File
@@ -1,6 +1,12 @@
package validator
import "regexp"
import (
"errors"
"regexp"
"strings"
"github.com/go-playground/validator/v10"
)
// Shared helper functions for validators
func isValidEmail(email string) bool {
@@ -30,3 +36,27 @@ func isValidPlanType(planType string) bool {
}
return validPlanTypes[planType]
}
func formatValidationError(err error) error {
if validationErrors, ok := err.(validator.ValidationErrors); ok {
var errorMessages []string
for _, fieldError := range validationErrors {
switch fieldError.Tag() {
case "required":
errorMessages = append(errorMessages, fieldError.Field()+" is required")
case "email":
errorMessages = append(errorMessages, fieldError.Field()+" must be a valid email")
case "min":
errorMessages = append(errorMessages, fieldError.Field()+" must be at least "+fieldError.Param())
case "max":
errorMessages = append(errorMessages, fieldError.Field()+" must be at most "+fieldError.Param())
case "oneof":
errorMessages = append(errorMessages, fieldError.Field()+" must be one of: "+fieldError.Param())
default:
errorMessages = append(errorMessages, fieldError.Field()+" is invalid")
}
}
return errors.New(strings.Join(errorMessages, "; "))
}
return err
}