Files
apskel-pos-backend/internal/router/router.go
T
efrilmandClaude Opus 5.5 3af16a1fbc feat(enakgame): weekly leaderboard
GET /customer/enakgame/leaderboard ranks the customers of an organization by
the EnakCoin they won from games in one week, Monday to Sunday in
Asia/Jakarta. Only completed, unflagged sessions count, by the week they
started; entry cost is not subtracted. Equal totals rank by who reached
theirs first, so no two customers share a rank.

Optional date (any day of the week, this week by default), game_id and limit
(10, at most 100). The response holds the top ranks and, under me, the
customer asking wherever they rank. Other customers are shown by a masked
name: the first word whole, the others by their first letter; a word with a
digit keeps only its first character.

The EnakGame web game shows it: integration-enakgame.md §10 (checklist now
§11) and RFC §11. The DB test needs TEST_DATABASE_URL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:15:02 +07:00

724 lines
42 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
campaignHandler *handler.CampaignHandler
customerAuthHandler *handler.CustomerAuthHandler
customerPointsHandler *handler.CustomerPointsHandler
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
customerWalletHandler *handler.CustomerWalletHandler
customerDeviceHandler *handler.CustomerDeviceHandler
customerOutletHandler *handler.CustomerOutletHandler
customerOrderHandler *handler.CustomerOrderHandler
enakGameAdminHandler *handler.EnakGameAdminHandler
enakGameCustomerHandler *handler.EnakGameCustomerHandler
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, campaignService service.CampaignService, campaignValidator validator.CampaignValidator, customerAuthService service.CustomerAuthService, customerAuthValidator validator.CustomerAuthValidator, customerPointsService service.CustomerPointsService, 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, customerWalletService service.CustomerWalletService, customerDeviceService service.CustomerDeviceService, customerOutletService service.CustomerOutletService, customerOrderService service.CustomerOrderService, enakGameAdminService service.EnakGameAdminService, enakGameCustomerService service.EnakGameCustomerService, 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),
campaignHandler: handler.NewCampaignHandler(campaignService, campaignValidator),
customerAuthHandler: handler.NewCustomerAuthHandler(customerAuthService, customerAuthValidator),
customerPointsHandler: handler.NewCustomerPointsHandler(customerPointsService),
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),
customerWalletHandler: handler.NewCustomerWalletHandler(customerWalletService),
customerDeviceHandler: handler.NewCustomerDeviceHandler(customerDeviceService),
customerOutletHandler: handler.NewCustomerOutletHandler(customerOutletService),
customerOrderHandler: handler.NewCustomerOrderHandler(customerOrderService),
enakGameAdminHandler: handler.NewEnakGameAdminHandler(enakGameAdminService),
enakGameCustomerHandler: handler.NewEnakGameCustomerHandler(enakGameCustomerService),
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("/wallet", r.customerPointsHandler.GetCustomerWallet)
customer.GET("/wallet/transactions", r.customerPointsHandler.GetCustomerWalletTransactions)
customer.GET("/wallet/expiring", r.customerPointsHandler.GetCustomerWalletExpiring)
customer.GET("/wallet/exchange/preview", r.customerWalletHandler.PreviewExchange)
customer.POST("/wallet/exchange", r.customerWalletHandler.Exchange)
customer.GET("/wallet/transfer/recipient", r.customerWalletHandler.TransferRecipient)
customer.POST("/wallet/transfer", r.customerWalletHandler.Transfer)
customer.PUT("/devices", r.customerDeviceHandler.Register)
customer.DELETE("/devices/:device_id", r.customerDeviceHandler.Unregister)
customer.GET("/outlets", r.customerOutletHandler.List)
customer.GET("/orders", r.customerOrderHandler.List)
customer.GET("/orders/:id", r.customerOrderHandler.Detail)
// 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)
// EnakGame (docs/rfc-enakgame.md §11). /enakgame because /customer/games was
// the old spin.
enakGame := customer.Group("/enakgame")
{
enakGame.GET("/games", r.enakGameCustomerHandler.ListGames)
enakGame.POST("/sessions", r.enakGameCustomerHandler.StartSession)
enakGame.GET("/sessions", r.enakGameCustomerHandler.ListSessions)
enakGame.GET("/sessions/:id", r.enakGameCustomerHandler.GetSession)
enakGame.POST("/sessions/:id/complete", r.enakGameCustomerHandler.CompleteSession)
enakGame.GET("/leaderboard", r.enakGameCustomerHandler.Leaderboard)
}
// Vouchers: what EnakPoint is spent on, whatever it came from (docs/rfc-enakgame.md
// §5.7, §7.4), so not under /enakgame.
customer.GET("/vouchers", r.enakGameCustomerHandler.ListVouchers)
customer.GET("/vouchers/redemptions", r.enakGameCustomerHandler.ListRedemptions)
customer.POST("/vouchers/:id/redeem", r.enakGameCustomerHandler.RedeemVoucher)
}
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.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("/profit-sharing", r.analyticsHandler.GetProductAnalyticsPerParentCategory)
analytics.GET("/profit-sharing/: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())
{
// 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)
}
// 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)
//}
// 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)
}
// Trace one ledger row lot by lot back to where its balance came from (F7, §8.1)
gamification.GET("/wallet-transactions/:id/trace", r.walletAdminHandler.TraceTransaction)
// EnakGame (docs/rfc-enakgame.md §11). Reward configurations and budgets
// change what rewards cost, so writing them takes a loyalty manager.
enakGame := gamification.Group("/enakgame")
{
enakGame.POST("/games", r.enakGameAdminHandler.CreateGame)
enakGame.GET("/games", r.enakGameAdminHandler.ListGames)
enakGame.GET("/games/:id", r.enakGameAdminHandler.GetGame)
enakGame.PUT("/games/:id", r.enakGameAdminHandler.UpdateGame)
enakGame.PUT("/games/:id/status", r.enakGameAdminHandler.SetGameStatus)
enakGame.GET("/games/:id/reward-configs", r.enakGameAdminHandler.ListRewardConfigs)
enakGame.POST("/games/:id/reward-configs", r.authMiddleware.RequireLoyaltyManager(), r.enakGameAdminHandler.CreateRewardConfig)
enakGame.POST("/reward-configs/:id/activate", r.authMiddleware.RequireLoyaltyManager(), r.enakGameAdminHandler.ActivateRewardConfig)
enakGame.GET("/budgets", r.enakGameAdminHandler.ListBudgets)
enakGame.GET("/budgets/:id", r.enakGameAdminHandler.GetBudget)
enakGame.GET("/budgets/:id/metrics", r.enakGameAdminHandler.BudgetMetrics)
enakGame.POST("/budgets", r.authMiddleware.RequireLoyaltyManager(), r.enakGameAdminHandler.CreateBudget)
enakGame.PUT("/budgets/:id", r.authMiddleware.RequireLoyaltyManager(), r.enakGameAdminHandler.UpdateBudget)
enakGame.DELETE("/budgets/:id", r.authMiddleware.RequireLoyaltyManager(), r.enakGameAdminHandler.DeleteBudget)
// Accepting writes new reward configuration versions.
enakGame.GET("/budgets/:id/recommendation", r.enakGameAdminHandler.BudgetRecommendation)
enakGame.POST("/budgets/:id/recommendation/accept", r.authMiddleware.RequireLoyaltyManager(), r.enakGameAdminHandler.AcceptBudgetRecommendation)
enakGame.GET("/analytics/games", r.enakGameAdminHandler.GameAnalytics)
enakGame.GET("/analytics/economy", r.enakGameAdminHandler.EconomyAnalytics)
// Events change what rewards cost, like budgets.
enakGame.GET("/events", r.enakGameAdminHandler.ListEvents)
enakGame.GET("/events/:id", r.enakGameAdminHandler.GetEvent)
enakGame.POST("/events", r.authMiddleware.RequireLoyaltyManager(), r.enakGameAdminHandler.CreateEvent)
enakGame.PUT("/events/:id", r.authMiddleware.RequireLoyaltyManager(), r.enakGameAdminHandler.UpdateEvent)
enakGame.PUT("/events/:id/status", r.authMiddleware.RequireLoyaltyManager(), r.enakGameAdminHandler.SetEventStatus)
}
// Vouchers EnakPoint is redeemed for, whatever it came from; not part of
// EnakGame. Writing them takes a loyalty manager.
vouchers := gamification.Group("/vouchers")
{
vouchers.GET("", r.enakGameAdminHandler.ListVouchers)
vouchers.GET("/:id", r.enakGameAdminHandler.GetVoucher)
vouchers.GET("/:id/codes", r.enakGameAdminHandler.ListVoucherCodes)
vouchers.POST("", r.authMiddleware.RequireLoyaltyManager(), r.enakGameAdminHandler.CreateVoucher)
vouchers.PUT("/:id", r.authMiddleware.RequireLoyaltyManager(), r.enakGameAdminHandler.UpdateVoucher)
vouchers.PUT("/:id/status", r.authMiddleware.RequireLoyaltyManager(), r.enakGameAdminHandler.SetVoucherStatus)
vouchers.POST("/:id/codes", r.authMiddleware.RequireLoyaltyManager(), r.enakGameAdminHandler.ImportVoucherCodes)
}
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)
}
}
}
}