Files
apskel-pos-backend/internal/router/router.go
T
efrilmandClaude Opus 5.5 ab3425070b feat(loyalty): exchange EnakCoin into EnakPoint
Adds GET /customer/wallet/exchange/preview?coins= and
POST /customer/wallet/exchange (docs/prd-point-coin.md F4, K3, PC-401).

The customer exchanges a multiple of the organization's coin_amount and
gets (coins / coin_amount) x point_amount EnakPoint, approved by their PIN
(K8). A malformed amount is refused before the PIN is checked, so it costs
no attempt. In one transaction the wallet is locked, EXCHANGE_OUT takes the
EnakCoin in K9 order and EXCHANGE_IN adds the EnakPoint; the two rows share
a group, point at each other and both freeze the rate in their metadata.

The EnakPoint are split over the EnakCoin lots they came from, each part
keeping its lot's expiry and pointing back at it, so exchanging cannot
extend a balance's life. The split takes floor(coins so far x rate) per
lot, which adds up exactly because the total is a multiple of coin_amount.
EnakPoint have no validity of their own until the expiry model is decided
(N4), so the EnakCoin lot is for now the only bound.

The Idempotency-Key header (or X-Idempotency-Key) is required. A retry
with the same key is recognised under the wallet lock and replayed with
the ids and rate the first attempt froze, even if the rate has changed
since; the same key for another amount is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:09:53 +07:00

723 lines
41 KiB
Go

package router
import (
"apskel-pos-be/config"
"apskel-pos-be/internal/handler"
"apskel-pos-be/internal/middleware"
"apskel-pos-be/internal/service"
"apskel-pos-be/internal/transformer"
"apskel-pos-be/internal/validator"
"github.com/gin-gonic/gin"
"github.com/redis/go-redis/v9"
)
type Router struct {
config *config.Config
healthHandler *handler.HealthHandler
authHandler *handler.AuthHandler
userHandler *handler.UserHandler
organizationHandler *handler.OrganizationHandler
outletHandler *handler.OutletHandler
outletSettingHandler *handler.OutletSettingHandlerImpl
categoryHandler *handler.CategoryHandler
productHandler *handler.ProductHandler
productVariantHandler *handler.ProductVariantHandler
inventoryHandler *handler.InventoryHandler
orderHandler *handler.OrderHandler
fileHandler *handler.FileHandler
customerHandler *handler.CustomerHandler
paymentMethodHandler *handler.PaymentMethodHandler
analyticsHandler *handler.AnalyticsHandler
reportHandler *handler.ReportHandler
tableHandler *handler.TableHandler
unitHandler *handler.UnitHandler
ingredientHandler *handler.IngredientHandler
productRecipeHandler *handler.ProductRecipeHandler
vendorHandler *handler.VendorHandler
purchaseOrderHandler *handler.PurchaseOrderHandler
purchaseCategoryHandler *handler.PurchaseCategoryHandler
unitConverterHandler *handler.IngredientUnitConverterHandler
chartOfAccountTypeHandler *handler.ChartOfAccountTypeHandler
chartOfAccountHandler *handler.ChartOfAccountHandler
accountHandler *handler.AccountHandler
orderIngredientTransactionHandler *handler.OrderIngredientTransactionHandler
gamificationHandler *handler.GamificationHandler
rewardHandler *handler.RewardHandler
campaignHandler *handler.CampaignHandler
customerAuthHandler *handler.CustomerAuthHandler
customerPointsHandler *handler.CustomerPointsHandler
spinGameHandler *handler.SpinGameHandler
userDeviceHandler *handler.UserDeviceHandler
notificationHandler *handler.NotificationHandler
selfOrderHandler *handler.SelfOrderHandler
productOutletPriceHandler *handler.ProductOutletPriceHandler
expenseHandler *handler.ExpenseHandler
cashAdvanceHandler *handler.CashAdvanceHandler
walletAdminHandler *handler.WalletAdminHandler
loyaltySettingsHandler *handler.LoyaltySettingsHandler
customerPinHandler *handler.CustomerPinHandler
pointPaymentHandler *handler.PointPaymentHandler
customerOrderPaymentHandler *handler.CustomerOrderPaymentHandler
customerWalletHandler *handler.CustomerWalletHandler
authMiddleware *middleware.AuthMiddleware
customerAuthMiddleware *middleware.CustomerAuthMiddleware
redisClient *redis.Client
}
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, purchaseCategoryService service.PurchaseCategoryService, purchaseCategoryValidator validator.PurchaseCategoryValidator, 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, userDeviceService service.UserDeviceService, userDeviceValidator validator.UserDeviceValidator, notificationService service.NotificationService, notificationValidator validator.NotificationValidator, productOutletPriceService service.ProductOutletPriceService, productOutletPriceValidator validator.ProductOutletPriceValidator, selfOrderHandler *handler.SelfOrderHandler, expenseService *service.ExpenseServiceImpl, expenseValidator *validator.ExpenseValidatorImpl, cashAdvanceService service.CashAdvanceService, cashAdvanceValidator validator.CashAdvanceValidator, walletAdminService service.WalletAdminService, walletValidator validator.WalletValidator, loyaltySettingsService service.LoyaltySettingsService, customerPinService service.CustomerPinService, pointPaymentService service.PointPaymentService, customerOrderPaymentService service.CustomerOrderPaymentService, customerWalletService service.CustomerWalletService, redisClient *redis.Client) *Router {
return &Router{
config: cfg,
healthHandler: healthHandler,
authHandler: handler.NewAuthHandler(authService),
userHandler: handler.NewUserHandler(userService, userValidator),
organizationHandler: handler.NewOrganizationHandler(organizationService, organizationValidator),
outletHandler: handler.NewOutletHandler(outletService, outletValidator),
outletSettingHandler: handler.NewOutletSettingHandlerImpl(outletSettingService),
categoryHandler: handler.NewCategoryHandler(categoryService, categoryValidator),
productHandler: handler.NewProductHandler(productService, productValidator),
inventoryHandler: handler.NewInventoryHandler(inventoryService, inventoryValidator),
orderHandler: handler.NewOrderHandler(orderService, orderValidator, transformer.NewTransformer()),
fileHandler: handler.NewFileHandler(fileService, fileValidator, transformer.NewTransformer()),
customerHandler: handler.NewCustomerHandler(customerService, customerValidator),
paymentMethodHandler: handler.NewPaymentMethodHandler(paymentMethodService, paymentMethodValidator),
analyticsHandler: handler.NewAnalyticsHandler(analyticsService, transformer.NewTransformer()),
reportHandler: handler.NewReportHandler(reportService, userService),
tableHandler: handler.NewTableHandler(tableService, tableValidator, cfg.Server.SelfOrderUrl),
unitHandler: handler.NewUnitHandler(unitService),
ingredientHandler: handler.NewIngredientHandler(ingredientService),
productRecipeHandler: handler.NewProductRecipeHandler(productRecipeService),
vendorHandler: handler.NewVendorHandler(vendorService, vendorValidator),
purchaseOrderHandler: handler.NewPurchaseOrderHandler(purchaseOrderService, purchaseOrderValidator),
purchaseCategoryHandler: handler.NewPurchaseCategoryHandler(purchaseCategoryService, purchaseCategoryValidator),
unitConverterHandler: handler.NewIngredientUnitConverterHandler(unitConverterService, unitConverterValidator),
chartOfAccountTypeHandler: handler.NewChartOfAccountTypeHandler(chartOfAccountTypeService, chartOfAccountTypeValidator),
chartOfAccountHandler: handler.NewChartOfAccountHandler(chartOfAccountService, chartOfAccountValidator),
accountHandler: handler.NewAccountHandler(accountService, accountValidator),
orderIngredientTransactionHandler: handler.NewOrderIngredientTransactionHandler(&orderIngredientTransactionService, orderIngredientTransactionValidator),
gamificationHandler: handler.NewGamificationHandler(gamificationService, gamificationValidator),
rewardHandler: handler.NewRewardHandler(rewardService, rewardValidator),
campaignHandler: handler.NewCampaignHandler(campaignService, campaignValidator),
customerAuthHandler: handler.NewCustomerAuthHandler(customerAuthService, customerAuthValidator),
customerPointsHandler: handler.NewCustomerPointsHandler(customerPointsService),
spinGameHandler: handler.NewSpinGameHandler(spinGameService),
authMiddleware: authMiddleware,
customerAuthMiddleware: customerAuthMiddleware,
productVariantHandler: handler.NewProductVariantHandler(productVariantService, productVariantValidator),
userDeviceHandler: handler.NewUserDeviceHandler(userDeviceService, userDeviceValidator),
notificationHandler: handler.NewNotificationHandler(notificationService, notificationValidator),
selfOrderHandler: selfOrderHandler,
productOutletPriceHandler: handler.NewProductOutletPriceHandler(productOutletPriceService, productOutletPriceValidator),
expenseHandler: handler.NewExpenseHandler(expenseService, expenseValidator),
cashAdvanceHandler: handler.NewCashAdvanceHandler(cashAdvanceService, cashAdvanceValidator),
walletAdminHandler: handler.NewWalletAdminHandler(walletAdminService, walletValidator),
loyaltySettingsHandler: handler.NewLoyaltySettingsHandler(loyaltySettingsService),
customerPinHandler: handler.NewCustomerPinHandler(customerPinService),
pointPaymentHandler: handler.NewPointPaymentHandler(pointPaymentService),
customerOrderPaymentHandler: handler.NewCustomerOrderPaymentHandler(customerOrderPaymentService),
customerWalletHandler: handler.NewCustomerWalletHandler(customerWalletService),
redisClient: redisClient,
}
}
func (r *Router) Init() *gin.Engine {
gin.SetMode(gin.ReleaseMode)
engine := gin.New()
engine.Use(
middleware.JsonAPI(),
middleware.CORS(),
middleware.CorrelationID(),
middleware.Recover(),
middleware.HTTPStatLogger(),
middleware.PopulateContext(),
)
r.addAppRoutes(engine)
return engine
}
func (r *Router) addAppRoutes(rg *gin.Engine) {
rg.GET("/health", r.healthHandler.HealthCheck)
v1 := rg.Group("/api/v1")
{
auth := v1.Group("/auth")
{
auth.POST("/login", r.authHandler.Login)
auth.POST("/logout", r.authHandler.Logout)
auth.POST("/refresh", r.authHandler.RefreshToken)
auth.GET("/validate", r.authHandler.ValidateToken)
auth.GET("/profile", r.authHandler.GetProfile)
}
// Customer authentication routes
customerAuth := v1.Group("/customer-auth")
{
customerAuth.POST("/check-phone", r.customerAuthHandler.CheckPhone)
customerAuth.POST("/register/start", r.customerAuthHandler.RegisterStart)
customerAuth.POST("/register/verify-otp", r.customerAuthHandler.RegisterVerifyOtp)
customerAuth.POST("/register/set-password", r.customerAuthHandler.RegisterSetPassword)
customerAuth.POST("/login", r.customerAuthHandler.Login)
customerAuth.POST("/resend-otp", r.customerAuthHandler.ResendOtp)
}
// Customer authenticated routes
customer := v1.Group("/customer")
customer.Use(r.customerAuthMiddleware.ValidateCustomerToken())
{
customer.GET("/points", r.customerPointsHandler.GetCustomerPoints)
customer.GET("/tokens", r.customerPointsHandler.GetCustomerTokens)
customer.GET("/wallet", r.customerPointsHandler.GetCustomerWallet)
customer.GET("/wallet/transactions", r.customerPointsHandler.GetCustomerWalletTransactions)
customer.POST("/wallet/payment-code", r.customerPinHandler.IssuePaymentCode)
customer.GET("/wallet/exchange/preview", r.customerWalletHandler.PreviewExchange)
customer.POST("/wallet/exchange", r.customerWalletHandler.Exchange)
customer.POST("/orders/:id/pay-with-points", r.customerOrderPaymentHandler.PayWithPoints)
// PIN that approves moving EnakPoint and EnakCoin (docs/prd-point-coin.md F11)
customer.GET("/pin/status", r.customerPinHandler.Status)
customer.POST("/pin/otp", r.customerPinHandler.RequestOtp)
customer.POST("/pin", r.customerPinHandler.CreatePin)
customer.PUT("/pin", r.customerPinHandler.ChangePin)
customer.POST("/pin/reset", r.customerPinHandler.ResetPin)
customer.GET("/games", r.customerPointsHandler.GetCustomerGames)
customer.GET("/ferris-wheel", r.customerPointsHandler.GetFerrisWheelGame)
customer.POST("/spin", r.spinGameHandler.PlaySpinGame)
}
selfOrder := v1.Group("/self-order")
{
selfOrder.GET("/table/:token", r.selfOrderHandler.ValidateToken)
selfOrder.GET("/categories", r.selfOrderHandler.ListCategories)
selfOrder.GET("/menu", r.selfOrderHandler.GetMenu)
selfOrder.POST("/orders", r.selfOrderHandler.CreateOrder)
selfOrder.GET("/orders/:session_id", r.selfOrderHandler.GetOrdersBySession)
}
organizations := v1.Group("/organizations")
{
organizations.POST("", r.organizationHandler.CreateOrganization)
}
protected := v1.Group("")
protected.Use(r.authMiddleware.RequireAuth())
{
users := protected.Group("/users")
{
adminUsers := users.Group("")
adminUsers.Use(r.authMiddleware.RequireAdminOrManager())
{
adminUsers.POST("", r.userHandler.CreateUser)
adminUsers.GET("", r.userHandler.ListUsers)
adminUsers.GET("/:id", r.userHandler.GetUser)
adminUsers.PUT("/:id", r.userHandler.UpdateUser)
adminUsers.DELETE("/:id", r.userHandler.DeleteUser)
adminUsers.PUT("/:id/activate", r.userHandler.ActivateUser)
adminUsers.PUT("/:id/deactivate", r.userHandler.DeactivateUser)
adminUsers.POST("/select-outlet", r.userHandler.UpdateUserOutlet)
}
users.PUT("/:id/password", r.userHandler.ChangePassword)
}
protectedOrganizations := protected.Group("/organizations")
{
adminOrgRoutes := protectedOrganizations.Group("")
adminOrgRoutes.Use(r.authMiddleware.RequireSuperAdmin())
{
adminOrgRoutes.GET("", r.organizationHandler.ListOrganizations)
adminOrgRoutes.GET("/:id", r.organizationHandler.GetOrganization)
adminOrgRoutes.PUT("/:id", r.organizationHandler.UpdateOrganization)
adminOrgRoutes.DELETE("/:id", r.organizationHandler.DeleteOrganization)
}
}
categories := protected.Group("/categories")
categories.Use(r.authMiddleware.RequireAdminOrManager())
{
categories.POST("", r.categoryHandler.CreateCategory)
categories.GET("", r.categoryHandler.ListCategories)
categories.GET("/:id", r.categoryHandler.GetCategory)
categories.PUT("/:id", r.categoryHandler.UpdateCategory)
categories.DELETE("/:id", r.categoryHandler.DeleteCategory)
}
units := protected.Group("/units")
units.Use(r.authMiddleware.RequireAdminOrManager())
{
units.POST("", r.unitHandler.Create)
units.GET("", r.unitHandler.GetAll)
units.GET("/:id", r.unitHandler.GetByID)
units.PUT("/:id", r.unitHandler.Update)
units.DELETE("/:id", r.unitHandler.Delete)
}
products := protected.Group("/products")
products.Use(r.authMiddleware.RequireAdminOrManager())
{
products.POST("", r.productHandler.CreateProduct)
products.GET("", r.productHandler.ListProducts)
products.GET("/all", r.productHandler.ListProductAll)
products.GET("/:id", r.productHandler.GetProduct)
products.PUT("/:id", r.productHandler.UpdateProduct)
products.DELETE("/:id", r.productHandler.DeleteProduct)
}
productOutletPrices := protected.Group("/product-outlet-prices")
productOutletPrices.Use(r.authMiddleware.RequireAdminOrManager())
{
productOutletPrices.POST("", r.productOutletPriceHandler.Upsert)
productOutletPrices.POST("/bulk", r.productOutletPriceHandler.BulkUpsert)
productOutletPrices.GET("/product/:product_id", r.productOutletPriceHandler.GetByProduct)
productOutletPrices.GET("/outlet/:outlet_id", r.productOutletPriceHandler.GetByOutlet)
productOutletPrices.GET("/product/:product_id/outlet/:outlet_id", r.productOutletPriceHandler.GetByProductAndOutlet)
productOutletPrices.DELETE("/:id", r.productOutletPriceHandler.Delete)
}
productVariants := protected.Group("/product-variants")
{
productVariants.POST("", r.productVariantHandler.CreateProductVariant)
productVariants.PUT("/:id", r.productVariantHandler.UpdateProductVariant)
productVariants.DELETE("/:id", r.productVariantHandler.DeleteProductVariant)
productVariants.GET("/:id", r.productVariantHandler.GetProductVariant)
}
inventory := protected.Group("/inventory")
inventory.Use(r.authMiddleware.RequireAdminOrManager())
{
inventory.POST("", r.inventoryHandler.CreateInventory)
inventory.GET("", r.inventoryHandler.ListInventory)
inventory.GET("/:id", r.inventoryHandler.GetInventory)
inventory.PUT("/:id", r.inventoryHandler.UpdateInventory)
inventory.DELETE("/:id", r.inventoryHandler.DeleteInventory)
inventory.POST("/adjust", r.inventoryHandler.AdjustInventory)
inventory.POST("/restock", r.inventoryHandler.RestockInventory)
inventory.GET("/low-stock/:outlet_id", r.inventoryHandler.GetLowStockItems)
inventory.GET("/zero-stock/:outlet_id", r.inventoryHandler.GetZeroStockItems)
inventory.GET("/report/summary/:outlet_id", r.inventoryHandler.GetInventoryReportSummary)
inventory.GET("/report/details/:outlet_id", r.inventoryHandler.GetInventoryReportDetails)
}
orders := protected.Group("/orders")
orders.Use(r.authMiddleware.RequireAdminOrManager())
{
orders.GET("", r.orderHandler.ListOrders)
orders.GET("/:id", r.orderHandler.GetOrderByID)
orders.GET("/:id/point-payment/preview", r.pointPaymentHandler.Preview)
orders.POST("", r.orderHandler.CreateOrder)
orders.POST("/:id/add-items", middleware.IdempotencyMiddleware(r.redisClient), r.orderHandler.AddToOrder)
orders.PUT("/:id", r.orderHandler.UpdateOrder)
orders.PUT("/:id/customer", r.orderHandler.SetOrderCustomer)
orders.POST("/void", middleware.IdempotencyMiddleware(r.redisClient), r.orderHandler.VoidOrder)
orders.POST("/:id/refund", middleware.IdempotencyMiddleware(r.redisClient), r.orderHandler.RefundOrder)
orders.POST("/split-bill", r.orderHandler.SplitBill)
}
payments := protected.Group("/payments")
payments.Use(r.authMiddleware.RequireAdminOrManager())
{
payments.POST("", middleware.IdempotencyMiddleware(r.redisClient), r.orderHandler.CreatePayment)
payments.POST("/:id/refund", middleware.IdempotencyMiddleware(r.redisClient), r.orderHandler.RefundPayment)
}
paymentMethods := protected.Group("/payment-methods")
paymentMethods.Use(r.authMiddleware.RequireAdminOrManager())
{
paymentMethods.POST("", r.paymentMethodHandler.CreatePaymentMethod)
paymentMethods.GET("", r.paymentMethodHandler.ListPaymentMethods)
paymentMethods.GET("/:id", r.paymentMethodHandler.GetPaymentMethod)
paymentMethods.PUT("/:id", r.paymentMethodHandler.UpdatePaymentMethod)
paymentMethods.DELETE("/:id", r.paymentMethodHandler.DeletePaymentMethod)
paymentMethods.GET("/organization/:organization_id/active", r.paymentMethodHandler.GetActivePaymentMethodsByOrganization)
}
files := protected.Group("/files")
files.Use(r.authMiddleware.RequireAdminOrManager())
{
files.GET("/organization", r.fileHandler.GetFilesByOrganization)
files.GET("/user", r.fileHandler.GetFilesByUser)
files.GET("/:id", r.fileHandler.GetFileByID)
files.POST("/upload", r.fileHandler.UploadFile)
files.PUT("/:id", r.fileHandler.UpdateFile)
}
customers := protected.Group("/customers")
customers.Use(r.authMiddleware.RequireAdminOrManager())
{
customers.POST("", r.customerHandler.CreateCustomer)
customers.GET("", r.customerHandler.ListCustomers)
customers.GET("/:id", r.customerHandler.GetCustomer)
customers.PUT("/:id", r.customerHandler.UpdateCustomer)
customers.DELETE("/:id", r.customerHandler.DeleteCustomer)
customers.POST("/set-default", r.customerHandler.SetDefaultCustomer)
customers.GET("/default", r.customerHandler.GetDefaultCustomer)
}
analytics := protected.Group("/analytics")
analytics.Use(r.authMiddleware.RequireAdminOrManager())
{
analytics.GET("/payment-methods", r.analyticsHandler.GetPaymentMethodAnalytics)
analytics.GET("/sales", r.analyticsHandler.GetSalesAnalytics)
analytics.GET("/purchasing", r.analyticsHandler.GetPurchasingAnalytics)
analytics.GET("/products", r.analyticsHandler.GetProductAnalytics)
analytics.GET("/categories", r.analyticsHandler.GetProductAnalyticsPerCategory)
analytics.GET("/parent-categories", r.analyticsHandler.GetProductAnalyticsPerParentCategory)
analytics.GET("/parent-categories/:parent_category_id", r.analyticsHandler.GetParentCategoryAnalyticsDetail)
analytics.GET("/dashboard", r.analyticsHandler.GetDashboardAnalytics)
analytics.GET("/profit-loss", r.analyticsHandler.GetProfitLossAnalytics)
analytics.GET("/exclusive-summary/period", r.analyticsHandler.GetExclusiveSummaryPeriod)
analytics.GET("/exclusive-summary/monthly", r.analyticsHandler.GetExclusiveSummaryMonthly)
analytics.GET("/exclusive-summary/mtd", r.analyticsHandler.GetExclusiveSummaryMTD)
}
tables := protected.Group("/tables")
tables.Use(r.authMiddleware.RequireAdminOrManager())
{
tables.POST("", r.tableHandler.Create)
tables.GET("", r.tableHandler.List)
tables.GET("/:id", r.tableHandler.GetByID)
tables.PUT("/:id", r.tableHandler.Update)
tables.DELETE("/:id", r.tableHandler.Delete)
tables.POST("/:id/occupy", r.tableHandler.OccupyTable)
tables.POST("/:id/release", r.tableHandler.ReleaseTable)
tables.GET("/:id/qr", r.tableHandler.GenerateQRCode)
}
ingredients := protected.Group("/ingredients")
ingredients.Use(r.authMiddleware.RequireAdminOrManagerOrPurchasing())
{
ingredients.POST("", r.ingredientHandler.Create)
ingredients.GET("", r.ingredientHandler.GetAll)
ingredients.GET("/:id", r.ingredientHandler.GetByID)
ingredients.PUT("/:id", r.ingredientHandler.Update)
ingredients.DELETE("/:id", r.ingredientHandler.Delete)
ingredients.POST("/:id/compositions", r.ingredientHandler.AddCompositions)
ingredients.PUT("/compositions/:composition_id", r.ingredientHandler.UpdateComposition)
ingredients.DELETE("/compositions/:composition_id", r.ingredientHandler.DeleteComposition)
}
vendors := protected.Group("/vendors")
vendors.Use(r.authMiddleware.RequireAdminOrManagerOrPurchasing())
{
vendors.POST("", r.vendorHandler.CreateVendor)
vendors.GET("", r.vendorHandler.ListVendors)
vendors.GET("/active", r.vendorHandler.GetActiveVendors)
vendors.GET("/:id", r.vendorHandler.GetVendor)
vendors.PUT("/:id", r.vendorHandler.UpdateVendor)
vendors.DELETE("/:id", r.vendorHandler.DeleteVendor)
}
purchaseOrders := protected.Group("/purchase-orders")
purchaseOrders.Use(r.authMiddleware.RequireAdminOrManagerOrPurchasing())
{
purchaseOrders.POST("", r.purchaseOrderHandler.CreatePurchaseOrder)
purchaseOrders.GET("", r.purchaseOrderHandler.ListPurchaseOrders)
purchaseOrders.GET("/status/:status", r.purchaseOrderHandler.GetPurchaseOrdersByStatus)
purchaseOrders.GET("/overdue", r.purchaseOrderHandler.GetOverduePurchaseOrders)
purchaseOrders.GET("/teams", r.purchaseOrderHandler.ListPurchaseTeams)
purchaseOrders.GET("/:id", r.purchaseOrderHandler.GetPurchaseOrder)
purchaseOrders.PUT("/:id", r.purchaseOrderHandler.UpdatePurchaseOrder)
purchaseOrders.PUT("/:id/status/:status", r.purchaseOrderHandler.UpdatePurchaseOrderStatus)
purchaseOrders.DELETE("/:id", r.purchaseOrderHandler.DeletePurchaseOrder)
}
purchaseCategories := protected.Group("/purchase-categories")
purchaseCategories.Use(r.authMiddleware.RequireAdminOrManagerOrPurchasing())
{
purchaseCategories.POST("", r.purchaseCategoryHandler.CreatePurchaseCategory)
purchaseCategories.GET("", r.purchaseCategoryHandler.ListPurchaseCategories)
purchaseCategories.GET("/:id", r.purchaseCategoryHandler.GetPurchaseCategory)
purchaseCategories.PUT("/:id", r.purchaseCategoryHandler.UpdatePurchaseCategory)
purchaseCategories.DELETE("/:id", r.purchaseCategoryHandler.DeletePurchaseCategory)
}
unitConverters := protected.Group("/unit-converters")
unitConverters.Use(r.authMiddleware.RequireAdminOrManagerOrPurchasing())
{
unitConverters.POST("", r.unitConverterHandler.CreateIngredientUnitConverter)
unitConverters.GET("", r.unitConverterHandler.ListIngredientUnitConverters)
unitConverters.GET("/ingredient/:ingredient_id", r.unitConverterHandler.GetConvertersForIngredient)
unitConverters.GET("/ingredient/:ingredient_id/units", r.unitConverterHandler.GetUnitsByIngredientID)
unitConverters.POST("/convert", r.unitConverterHandler.ConvertUnit)
unitConverters.GET("/:id", r.unitConverterHandler.GetIngredientUnitConverter)
unitConverters.PUT("/:id", r.unitConverterHandler.UpdateIngredientUnitConverter)
unitConverters.DELETE("/:id", r.unitConverterHandler.DeleteIngredientUnitConverter)
}
productRecipes := protected.Group("/product-recipes")
productRecipes.Use(r.authMiddleware.RequireAdminOrManager())
{
productRecipes.POST("", r.productRecipeHandler.Create)
productRecipes.POST("/bulk", r.productRecipeHandler.BulkCreate)
productRecipes.GET("/:id", r.productRecipeHandler.GetByID)
productRecipes.PUT("/:id", r.productRecipeHandler.Update)
productRecipes.DELETE("/:id", r.productRecipeHandler.Delete)
productRecipes.GET("/product/:product_id", r.productRecipeHandler.GetByProductID)
productRecipes.GET("/ingredient/:ingredient_id", r.productRecipeHandler.GetByIngredientID)
}
// Accounting routes
chartOfAccountTypes := protected.Group("/chart-of-account-types")
chartOfAccountTypes.Use(r.authMiddleware.RequireAdminOrManager())
{
chartOfAccountTypes.POST("", r.chartOfAccountTypeHandler.CreateChartOfAccountType)
chartOfAccountTypes.GET("", r.chartOfAccountTypeHandler.ListChartOfAccountTypes)
chartOfAccountTypes.GET("/:id", r.chartOfAccountTypeHandler.GetChartOfAccountTypeByID)
chartOfAccountTypes.PUT("/:id", r.chartOfAccountTypeHandler.UpdateChartOfAccountType)
chartOfAccountTypes.DELETE("/:id", r.chartOfAccountTypeHandler.DeleteChartOfAccountType)
}
chartOfAccounts := protected.Group("/chart-of-accounts")
chartOfAccounts.Use(r.authMiddleware.RequireAdminOrManager())
{
chartOfAccounts.POST("", r.chartOfAccountHandler.CreateChartOfAccount)
chartOfAccounts.GET("", r.chartOfAccountHandler.ListChartOfAccounts)
chartOfAccounts.GET("/:id", r.chartOfAccountHandler.GetChartOfAccountByID)
chartOfAccounts.PUT("/:id", r.chartOfAccountHandler.UpdateChartOfAccount)
chartOfAccounts.DELETE("/:id", r.chartOfAccountHandler.DeleteChartOfAccount)
chartOfAccounts.GET("/organization/:organization_id", r.chartOfAccountHandler.GetChartOfAccountsByOrganization)
chartOfAccounts.GET("/organization/:organization_id/type/:type_id", r.chartOfAccountHandler.GetChartOfAccountsByType)
}
accounts := protected.Group("/accounts")
accounts.Use(r.authMiddleware.RequireAdminOrManager())
{
accounts.POST("", r.accountHandler.CreateAccount)
accounts.GET("", r.accountHandler.ListAccounts)
accounts.GET("/:id", r.accountHandler.GetAccountByID)
accounts.PUT("/:id", r.accountHandler.UpdateAccount)
accounts.DELETE("/:id", r.accountHandler.DeleteAccount)
accounts.GET("/organization/:organization_id", r.accountHandler.GetAccountsByOrganization)
accounts.GET("/chart-of-account/:chart_of_account_id", r.accountHandler.GetAccountsByChartOfAccount)
accounts.PUT("/:id/balance", r.accountHandler.UpdateAccountBalance)
accounts.GET("/:id/balance", r.accountHandler.GetAccountBalance)
}
expenses := protected.Group("/expenses")
expenses.Use(r.authMiddleware.RequireAdminOrManagerOrPurchasing())
{
expenses.POST("", r.expenseHandler.CreateExpense)
expenses.GET("", r.expenseHandler.ListExpenses)
expenses.GET("/analytics", r.expenseHandler.GetExpenseAnalytics)
expenses.GET("/:id", r.expenseHandler.GetExpense)
expenses.PUT("/:id", r.expenseHandler.UpdateExpense)
expenses.DELETE("/:id", r.expenseHandler.DeleteExpense)
}
cashAdvances := protected.Group("/cash-advances")
cashAdvances.Use(r.authMiddleware.RequireAdminOrManagerOrPurchasing())
{
cashAdvances.POST("", r.cashAdvanceHandler.CreateCashAdvance)
cashAdvances.GET("", r.cashAdvanceHandler.ListCashAdvances)
// Registered ahead of /:id so the picker path is not read as an id.
cashAdvances.GET("/teams", r.cashAdvanceHandler.ListCashAdvanceTeams)
cashAdvances.GET("/:id", r.cashAdvanceHandler.GetCashAdvance)
cashAdvances.PUT("/:id", r.cashAdvanceHandler.UpdateCashAdvance)
cashAdvances.PUT("/:id/status/:status", r.cashAdvanceHandler.UpdateCashAdvanceStatus)
cashAdvances.DELETE("/:id", r.cashAdvanceHandler.DeleteCashAdvance)
}
orderIngredientTransactions := protected.Group("/order-ingredient-transactions")
orderIngredientTransactions.Use(r.authMiddleware.RequireAdminOrManager())
{
orderIngredientTransactions.POST("", r.orderIngredientTransactionHandler.CreateOrderIngredientTransaction)
orderIngredientTransactions.GET("", r.orderIngredientTransactionHandler.ListOrderIngredientTransactions)
orderIngredientTransactions.GET("/:id", r.orderIngredientTransactionHandler.GetOrderIngredientTransactionByID)
orderIngredientTransactions.PUT("/:id", r.orderIngredientTransactionHandler.UpdateOrderIngredientTransaction)
orderIngredientTransactions.DELETE("/:id", r.orderIngredientTransactionHandler.DeleteOrderIngredientTransaction)
orderIngredientTransactions.GET("/order/:order_id", r.orderIngredientTransactionHandler.GetOrderIngredientTransactionsByOrder)
orderIngredientTransactions.GET("/order-item/:order_item_id", r.orderIngredientTransactionHandler.GetOrderIngredientTransactionsByOrderItem)
orderIngredientTransactions.GET("/ingredient/:ingredient_id", r.orderIngredientTransactionHandler.GetOrderIngredientTransactionsByIngredient)
orderIngredientTransactions.GET("/summary", r.orderIngredientTransactionHandler.GetOrderIngredientTransactionSummary)
orderIngredientTransactions.POST("/bulk", r.orderIngredientTransactionHandler.BulkCreateOrderIngredientTransactions)
}
gamification := protected.Group("/marketing")
gamification.Use(r.authMiddleware.RequireAdminOrManager())
{
//customerPoints := gamification.Group("/customer-points")
//{
// customerPoints.POST("", r.gamificationHandler.CreateCustomerPoints)
// customerPoints.GET("", r.gamificationHandler.ListCustomerPoints)
// customerPoints.GET("/:id", r.gamificationHandler.GetCustomerPoints)
// customerPoints.PUT("/:id", r.gamificationHandler.UpdateCustomerPoints)
// customerPoints.DELETE("/:id", r.gamificationHandler.DeleteCustomerPoints)
// customerPoints.GET("/customer/:customer_id", r.gamificationHandler.GetCustomerPointsByCustomerID)
// customerPoints.POST("/customer/:customer_id/add", r.gamificationHandler.AddCustomerPoints)
// customerPoints.POST("/customer/:customer_id/deduct", r.gamificationHandler.DeductCustomerPoints)
//}
// Customer Tokens
//customerTokens := gamification.Group("/customer-tokens")
//{
// customerTokens.POST("", r.gamificationHandler.CreateCustomerTokens)
// customerTokens.GET("", r.gamificationHandler.ListCustomerTokens)
// customerTokens.GET("/:id", r.gamificationHandler.GetCustomerTokens)
// customerTokens.PUT("/:id", r.gamificationHandler.UpdateCustomerTokens)
// customerTokens.DELETE("/:id", r.gamificationHandler.DeleteCustomerTokens)
// customerTokens.GET("/customer/:customer_id/type/:token_type", r.gamificationHandler.GetCustomerTokensByCustomerIDAndType)
// customerTokens.POST("/customer/:customer_id/type/:token_type/add", r.gamificationHandler.AddCustomerTokens)
// customerTokens.POST("/customer/:customer_id/type/:token_type/deduct", r.gamificationHandler.DeductCustomerTokens)
//}
// Tiers
tiers := gamification.Group("/tiers")
{
tiers.POST("", r.gamificationHandler.CreateTier)
tiers.GET("", r.gamificationHandler.ListTiers)
tiers.GET("/:id", r.gamificationHandler.GetTier)
tiers.PUT("/:id", r.gamificationHandler.UpdateTier)
tiers.DELETE("/:id", r.gamificationHandler.DeleteTier)
tiers.GET("/by-points/:points", r.gamificationHandler.GetTierByPoints)
}
// Games
games := gamification.Group("/games")
{
games.POST("", r.gamificationHandler.CreateGame)
games.GET("", r.gamificationHandler.ListGames)
games.GET("/active", r.gamificationHandler.GetActiveGames)
games.GET("/:id", r.gamificationHandler.GetGame)
games.PUT("/:id", r.gamificationHandler.UpdateGame)
games.DELETE("/:id", r.gamificationHandler.DeleteGame)
}
// Game Prizes
gamePrizes := gamification.Group("/game-prizes")
{
gamePrizes.POST("", r.gamificationHandler.CreateGamePrize)
gamePrizes.GET("", r.gamificationHandler.ListGamePrizes)
gamePrizes.GET("/:id", r.gamificationHandler.GetGamePrize)
gamePrizes.PUT("/:id", r.gamificationHandler.UpdateGamePrize)
gamePrizes.DELETE("/:id", r.gamificationHandler.DeleteGamePrize)
gamePrizes.GET("/game/:game_id", r.gamificationHandler.GetGamePrizesByGameID)
gamePrizes.GET("/game/:game_id/available", r.gamificationHandler.GetAvailablePrizes)
}
//// Game Plays
//gamePlays := gamification.Group("/game-plays")
//{
// gamePlays.POST("", r.gamificationHandler.CreateGamePlay)
// gamePlays.GET("", r.gamificationHandler.ListGamePlays)
// gamePlays.GET("/:id", r.gamificationHandler.GetGamePlay)
// gamePlays.POST("/play", r.gamificationHandler.PlayGame)
//}
// Omset Tracker
//omsetTracker := gamification.Group("/omset-tracker")
//{
// omsetTracker.POST("", r.gamificationHandler.CreateOmsetTracker)
// omsetTracker.GET("", r.gamificationHandler.ListOmsetTrackers)
// omsetTracker.GET("/:id", r.gamificationHandler.GetOmsetTracker)
// omsetTracker.PUT("/:id", r.gamificationHandler.UpdateOmsetTracker)
// omsetTracker.DELETE("/:id", r.gamificationHandler.DeleteOmsetTracker)
//}
// Rewards
rewards := gamification.Group("/rewards")
{
rewards.POST("", r.rewardHandler.CreateReward)
rewards.GET("", r.rewardHandler.ListRewards)
rewards.GET("/:id", r.rewardHandler.GetReward)
rewards.PUT("/:id", r.rewardHandler.UpdateReward)
rewards.DELETE("/:id", r.rewardHandler.DeleteReward)
rewards.PUT("/:id/stock/:stock", r.rewardHandler.UpdateRewardStock)
rewards.GET("/type/:type", r.rewardHandler.GetRewardsByType)
}
// Campaigns
campaigns := gamification.Group("/campaigns")
{
campaigns.POST("", r.campaignHandler.CreateCampaign)
campaigns.GET("", r.campaignHandler.ListCampaigns)
campaigns.GET("/active", r.campaignHandler.GetActiveCampaigns)
campaigns.GET("/app", r.campaignHandler.GetCampaignsForApp)
campaigns.GET("/:id", r.campaignHandler.GetCampaign)
campaigns.PUT("/:id", r.campaignHandler.UpdateCampaign)
campaigns.DELETE("/:id", r.campaignHandler.DeleteCampaign)
}
// Campaign Rules
// EnakPoint & EnakCoin settings of the organization (docs/prd-point-coin.md F2)
gamification.GET("/loyalty-settings", r.loyaltySettingsHandler.GetOrganizationSettings)
gamification.PUT("/loyalty-settings", r.authMiddleware.RequireLoyaltyManager(), r.loyaltySettingsHandler.UpdateOrganizationSettings)
gamification.GET("/loyalty-settings/history", r.loyaltySettingsHandler.ListHistory)
// EnakPoint & EnakCoin wallet of one customer (docs/prd-point-coin.md F7)
marketingCustomers := gamification.Group("/customers")
{
marketingCustomers.GET("/:id/wallet", r.walletAdminHandler.GetCustomerWallet)
marketingCustomers.POST("/:id/wallet/adjust", r.authMiddleware.RequireLoyaltyManager(), r.walletAdminHandler.AdjustCustomerWallet)
marketingCustomers.DELETE("/:id/pin", r.authMiddleware.RequireLoyaltyManager(), r.customerPinHandler.RemovePin)
marketingCustomers.GET("/:id/security-events", r.customerPinHandler.ListSecurityEvents)
}
campaignRules := gamification.Group("/campaign-rules")
{
campaignRules.POST("", r.campaignHandler.CreateCampaignRule)
campaignRules.GET("", r.campaignHandler.ListCampaignRules)
campaignRules.GET("/:id", r.campaignHandler.GetCampaignRule)
campaignRules.PUT("/:id", r.campaignHandler.UpdateCampaignRule)
campaignRules.DELETE("/:id", r.campaignHandler.DeleteCampaignRule)
campaignRules.GET("/campaign/:campaign_id", r.campaignHandler.GetCampaignRulesByCampaignID)
}
}
outlets := protected.Group("/outlets")
outlets.Use(r.authMiddleware.RequireAdminOrManager())
{
outlets.POST("", r.outletHandler.CreateOutlet)
outlets.GET("/list", r.outletHandler.ListOutlets)
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)
// EnakPoint & EnakCoin settings of the outlet (docs/prd-point-coin.md F1)
outlets.GET("/:outlet_id/loyalty-settings", r.loyaltySettingsHandler.GetOutletSettings)
outlets.PUT("/:outlet_id/loyalty-settings", r.authMiddleware.RequireLoyaltyManager(), r.loyaltySettingsHandler.UpdateOutletSettings)
outlets.GET("/:outlet_id/tables/available", r.tableHandler.GetAvailableTables)
outlets.GET("/:outlet_id/tables/occupied", r.tableHandler.GetOccupiedTables)
// Reports
outlets.GET("/:outlet_id/reports/daily-transaction.pdf", r.reportHandler.GetDailyTransactionReportPDF)
outlets.GET("/:outlet_id/reports/profit-loss.pdf", r.reportHandler.GetProfitLossReportPDF)
}
// User device routes - accessible by authenticated users for their own devices
userDevices := protected.Group("/user-devices")
{
userDevices.POST("/register", r.userDeviceHandler.RegisterDevice)
userDevices.GET("/me", r.userDeviceHandler.GetMyDevices)
userDevices.GET("/:id", r.userDeviceHandler.GetDevice)
userDevices.PUT("/:id", r.userDeviceHandler.UpdateDevice)
userDevices.DELETE("/:id", r.userDeviceHandler.DeleteDevice)
}
// Admin-only user device routes
adminUserDevices := protected.Group("/user-devices")
adminUserDevices.Use(r.authMiddleware.RequireAdminOrManager())
{
adminUserDevices.GET("", r.userDeviceHandler.ListDevices)
adminUserDevices.GET("/user/:user_id", r.userDeviceHandler.GetDevicesByUser)
}
// Notification routes - authenticated users manage their own notifications
notifications := protected.Group("/notifications")
{
notifications.GET("", r.notificationHandler.List)
notifications.GET("/:id", r.notificationHandler.GetByID)
notifications.PUT("/:id/read", r.notificationHandler.MarkAsRead)
notifications.PUT("/read-all", r.notificationHandler.MarkAllAsRead)
notifications.DELETE("/:id", r.notificationHandler.Delete)
}
// Admin notification routes - send and broadcast
adminNotifications := protected.Group("/notifications")
adminNotifications.Use(r.authMiddleware.RequireAdminOrManager())
{
adminNotifications.POST("/send", r.notificationHandler.Send)
adminNotifications.POST("/broadcast", r.notificationHandler.Broadcast)
}
}
}
}