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) } // 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) } } } }