Files
apskel-pos-backend/internal/app/app.go
T
efrilmandClaude Opus 5.5 39e47ff0e6 feat(loyalty): typed loyalty settings with change history
Adds LoyaltySettingsProcessor (docs/prd-point-coin.md F1, F2, F12, PC-109).

Reading returns typed settings for an outlet (earning per currency, paying
with EnakPoint) and for an organization (point value, exchange rate,
transfers, and the expiry settings awaiting note N4). A key that was never
set takes the PRD default. A stored value that is unusable, such as an
earn_per_amount of 0 that would divide by zero, also falls back to the
default and is logged, so a bad row never reaches a calculation.

Writing takes the whole settings struct, validates every rule in the PRD
before touching the database, and stores and records in
loyalty_setting_changes only the keys whose effective value changes: old
value (NULL while it was on its default), new value, and who changed it.
Clearing a limit deletes the stored value. Each save runs in one
transaction under an advisory lock per outlet or organization, so two saves
at once cannot both compute their change from the same old value. The
outlet must belong to the caller's organization.

Every key is described once (key, default, valid range, bound field), and
reading, validating and diffing all use that description.

GET /customer/wallet now reads the point value through this processor; the
minimal organization settings repository from PC-106 is removed.

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

664 lines
38 KiB
Go

package app
import (
"apskel-pos-be/internal/client"
"apskel-pos-be/internal/transformer"
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"apskel-pos-be/config"
"apskel-pos-be/internal/handler"
"apskel-pos-be/internal/middleware"
"apskel-pos-be/internal/processor"
"apskel-pos-be/internal/repository"
"apskel-pos-be/internal/router"
"apskel-pos-be/internal/service"
"apskel-pos-be/internal/validator"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
type App struct {
server *http.Server
db *gorm.DB
redisClient *redis.Client
router *router.Router
shutdown chan os.Signal
omsetScheduler *service.OmsetMilestoneScheduler
walletRecon *service.WalletReconciliationJob
}
func NewApp(db *gorm.DB, redisClient *redis.Client) *App {
return &App{
db: db,
redisClient: redisClient,
shutdown: make(chan os.Signal, 1),
}
}
func (a *App) Initialize(cfg *config.Config) error {
repos := a.initRepositories()
processors := a.initProcessors(cfg, repos)
// Initialize omset milestone scheduler
a.omsetScheduler = service.NewOmsetMilestoneScheduler(
repos.organizationRepo,
repos.outletRepo,
repos.userRepo,
processors.notificationProcessor,
)
// Checks that wallet balances, ledger and lots agree (docs/prd-point-coin.md ยง7.5)
a.walletRecon = service.NewWalletReconciliationJob(
repository.NewWalletReconciliationRepository(a.db),
repos.userRepo,
processors.notificationProcessor,
)
services := a.initServices(processors, repos, cfg)
validators := a.initValidators()
middleware := a.initMiddleware(services, cfg)
healthHandler := handler.NewHealthHandler()
selfOrderHandler := handler.NewSelfOrderHandler(
services.orderService,
services.categoryService,
services.productService,
repos.tableRepo,
repos.outletRepo,
repos.userRepo,
repos.sessionRepo,
repos.orderRepo,
services.productOutletPriceService,
)
a.router = router.NewRouter(
cfg,
healthHandler,
services.authService,
middleware.authMiddleware,
services.userService,
validators.userValidator,
services.organizationService,
validators.organizationValidator,
services.outletService,
validators.outletValidator,
services.outletSettingService,
services.categoryService,
validators.categoryValidator,
services.productService,
validators.productValidator,
services.productVariantService,
validators.productVariantValidator,
services.inventoryService,
validators.inventoryValidator,
services.orderService,
validators.orderValidator,
services.fileService,
validators.fileValidator,
services.customerService,
validators.customerValidator,
services.paymentMethodService,
validators.paymentMethodValidator,
services.analyticsService,
services.reportService,
services.tableService,
validators.tableValidator,
services.unitService,
services.ingredientService,
services.productRecipeService,
services.vendorService,
validators.vendorValidator,
services.purchaseOrderService,
validators.purchaseOrderValidator,
services.purchaseCategoryService,
validators.purchaseCategoryValidator,
services.unitConverterService,
validators.unitConverterValidator,
services.chartOfAccountTypeService,
validators.chartOfAccountTypeValidator,
services.chartOfAccountService,
validators.chartOfAccountValidator,
services.accountService,
validators.accountValidator,
*services.orderIngredientTransactionService,
validators.orderIngredientTransactionValidator,
services.gamificationService,
validators.gamificationValidator,
services.rewardService,
validators.rewardValidator,
services.campaignService,
validators.campaignValidator,
services.customerAuthService,
validators.customerAuthValidator,
services.customerPointsService,
services.spinGameService,
middleware.customerAuthMiddleware,
services.userDeviceService,
validators.userDeviceValidator,
services.notificationService,
validators.notificationValidator,
services.productOutletPriceService,
validators.productOutletPriceValidator,
selfOrderHandler,
services.expenseService,
validators.expenseValidator,
services.cashAdvanceService,
validators.cashAdvanceValidator,
services.walletAdminService,
validators.walletValidator,
a.redisClient,
)
return nil
}
func (a *App) Start(port string) error {
// Start the omset milestone scheduler (checks every 5 minutes for daily omset milestones)
if a.omsetScheduler != nil {
a.omsetScheduler.Start(5 * time.Minute)
}
if a.walletRecon != nil {
a.walletRecon.Start(6 * time.Hour)
}
engine := a.router.Init()
a.server = &http.Server{
Addr: ":" + port,
Handler: engine,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
signal.Notify(a.shutdown, os.Interrupt, syscall.SIGTERM)
go func() {
log.Printf("Server starting on port %s", port)
if err := a.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Failed to start server: %v", err)
}
}()
<-a.shutdown
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := a.server.Shutdown(ctx); err != nil {
log.Printf("Server forced to shutdown: %v", err)
return err
}
log.Println("Server exited gracefully")
return nil
}
func (a *App) Shutdown() {
if a.omsetScheduler != nil {
a.omsetScheduler.Stop()
}
if a.walletRecon != nil {
a.walletRecon.Stop()
}
close(a.shutdown)
}
type repositories struct {
userRepo *repository.UserRepositoryImpl
organizationRepo *repository.OrganizationRepositoryImpl
outletRepo *repository.OutletRepositoryImpl
outletSettingRepo *repository.OutletSettingRepositoryImpl
categoryRepo *repository.CategoryRepositoryImpl
productRepo *repository.ProductRepositoryImpl
productVariantRepo *repository.ProductVariantRepositoryImpl
inventoryRepo *repository.InventoryRepositoryImpl
inventoryMovementRepo *repository.InventoryMovementRepositoryImpl
orderRepo *repository.OrderRepositoryImpl
orderItemRepo *repository.OrderItemRepositoryImpl
paymentRepo *repository.PaymentRepositoryImpl
paymentOrderItemRepo *repository.PaymentOrderItemRepositoryImpl
paymentMethodRepo *repository.PaymentMethodRepositoryImpl
fileRepo *repository.FileRepositoryImpl
customerRepo *repository.CustomerRepository
analyticsRepo *repository.AnalyticsRepositoryImpl
tableRepo *repository.TableRepository
unitRepo *repository.UnitRepository
ingredientRepo *repository.IngredientRepository
ingredientCompositionRepo *repository.IngredientCompositionRepository
productRecipeRepo *repository.ProductRecipeRepository
vendorRepo *repository.VendorRepositoryImpl
purchaseOrderRepo *repository.PurchaseOrderRepositoryImpl
purchaseCategoryRepo *repository.PurchaseCategoryRepositoryImpl
unitConverterRepo *repository.IngredientUnitConverterRepositoryImpl
chartOfAccountTypeRepo *repository.ChartOfAccountTypeRepositoryImpl
chartOfAccountRepo *repository.ChartOfAccountRepositoryImpl
accountRepo *repository.AccountRepositoryImpl
orderIngredientTransactionRepo *repository.OrderIngredientTransactionRepositoryImpl
customerTokensRepo *repository.CustomerTokensRepository
tierRepo *repository.TierRepository
gameRepo *repository.GameRepository
gamePrizeRepo *repository.GamePrizeRepository
gamePlayRepo repository.GamePlayRepository
omsetTrackerRepo *repository.OmsetTrackerRepository
rewardRepo repository.RewardRepository
campaignRepo repository.CampaignRepository
campaignRuleRepo repository.CampaignRuleRepository
customerAuthRepo repository.CustomerAuthRepository
customerPointsRepo repository.CustomerPointsRepository
otpRepo repository.OtpRepository
sessionRepo repository.SessionRepository
txManager *repository.TxManager
userDeviceRepo *repository.UserDeviceRepositoryImpl
notificationRepo *repository.NotificationRepositoryImpl
notificationReceiverRepo *repository.NotificationReceiverRepositoryImpl
notificationDeliveryRepo *repository.NotificationDeliveryRepositoryImpl
productOutletPriceRepo *repository.ProductOutletPriceRepositoryImpl
expenseRepo *repository.ExpenseRepositoryImpl
cashAdvanceRepo *repository.CashAdvanceRepositoryImpl
walletRepo repository.WalletRepository
walletQueryRepo repository.WalletQueryRepository
loyaltySettingsRepo repository.LoyaltySettingsRepository
}
func (a *App) initRepositories() *repositories {
return &repositories{
userRepo: repository.NewUserRepository(a.db),
organizationRepo: repository.NewOrganizationRepositoryImpl(a.db),
outletRepo: repository.NewOutletRepositoryImpl(a.db),
outletSettingRepo: repository.NewOutletSettingRepositoryImpl(a.db),
categoryRepo: repository.NewCategoryRepositoryImpl(a.db),
productRepo: repository.NewProductRepositoryImpl(a.db),
productVariantRepo: repository.NewProductVariantRepositoryImpl(a.db),
inventoryRepo: repository.NewInventoryRepositoryImpl(a.db),
inventoryMovementRepo: repository.NewInventoryMovementRepositoryImpl(a.db),
orderRepo: repository.NewOrderRepositoryImpl(a.db),
orderItemRepo: repository.NewOrderItemRepositoryImpl(a.db),
paymentRepo: repository.NewPaymentRepositoryImpl(a.db),
paymentOrderItemRepo: repository.NewPaymentOrderItemRepositoryImpl(a.db),
paymentMethodRepo: repository.NewPaymentMethodRepositoryImpl(a.db),
fileRepo: repository.NewFileRepositoryImpl(a.db),
customerRepo: repository.NewCustomerRepository(a.db),
analyticsRepo: repository.NewAnalyticsRepositoryImpl(a.db),
tableRepo: repository.NewTableRepository(a.db),
unitRepo: repository.NewUnitRepository(a.db),
ingredientRepo: repository.NewIngredientRepository(a.db),
ingredientCompositionRepo: repository.NewIngredientCompositionRepository(a.db),
productRecipeRepo: repository.NewProductRecipeRepository(a.db),
vendorRepo: repository.NewVendorRepositoryImpl(a.db),
purchaseOrderRepo: repository.NewPurchaseOrderRepositoryImpl(a.db),
purchaseCategoryRepo: repository.NewPurchaseCategoryRepositoryImpl(a.db),
unitConverterRepo: repository.NewIngredientUnitConverterRepositoryImpl(a.db).(*repository.IngredientUnitConverterRepositoryImpl),
chartOfAccountTypeRepo: repository.NewChartOfAccountTypeRepositoryImpl(a.db),
chartOfAccountRepo: repository.NewChartOfAccountRepositoryImpl(a.db),
accountRepo: repository.NewAccountRepositoryImpl(a.db),
orderIngredientTransactionRepo: repository.NewOrderIngredientTransactionRepositoryImpl(a.db).(*repository.OrderIngredientTransactionRepositoryImpl),
customerTokensRepo: repository.NewCustomerTokensRepository(a.db),
tierRepo: repository.NewTierRepository(a.db),
gameRepo: repository.NewGameRepository(a.db),
gamePrizeRepo: repository.NewGamePrizeRepository(a.db),
gamePlayRepo: repository.NewGamePlayRepository(a.db),
omsetTrackerRepo: repository.NewOmsetTrackerRepository(a.db),
rewardRepo: repository.NewRewardRepository(a.db),
campaignRepo: repository.NewCampaignRepository(a.db),
campaignRuleRepo: repository.NewCampaignRuleRepository(a.db),
customerAuthRepo: repository.NewCustomerAuthRepository(a.db),
customerPointsRepo: repository.NewCustomerPointsRepository(a.db),
otpRepo: repository.NewOtpRepository(a.db),
sessionRepo: repository.NewSessionRepository(a.redisClient),
txManager: repository.NewTxManager(a.db),
userDeviceRepo: repository.NewUserDeviceRepositoryImpl(a.db),
notificationRepo: repository.NewNotificationRepository(a.db),
notificationReceiverRepo: repository.NewNotificationReceiverRepository(a.db),
notificationDeliveryRepo: repository.NewNotificationDeliveryRepository(a.db),
productOutletPriceRepo: repository.NewProductOutletPriceRepositoryImpl(a.db),
expenseRepo: repository.NewExpenseRepositoryImpl(a.db),
cashAdvanceRepo: repository.NewCashAdvanceRepositoryImpl(a.db),
walletRepo: repository.NewWalletRepository(a.db),
walletQueryRepo: repository.NewWalletQueryRepository(a.db),
loyaltySettingsRepo: repository.NewLoyaltySettingsRepository(a.db),
}
}
type processors struct {
userProcessor *processor.UserProcessorImpl
organizationProcessor processor.OrganizationProcessor
outletProcessor processor.OutletProcessor
outletSettingProcessor *processor.OutletSettingProcessorImpl
categoryProcessor processor.CategoryProcessor
productProcessor processor.ProductProcessor
productVariantProcessor processor.ProductVariantProcessor
inventoryProcessor processor.InventoryProcessor
orderProcessor processor.OrderProcessor
paymentMethodProcessor processor.PaymentMethodProcessor
fileProcessor processor.FileProcessor
customerProcessor *processor.CustomerProcessor
analyticsProcessor *processor.AnalyticsProcessorImpl
tableProcessor *processor.TableProcessor
unitProcessor *processor.UnitProcessorImpl
ingredientProcessor *processor.IngredientProcessorImpl
productRecipeProcessor *processor.ProductRecipeProcessorImpl
vendorProcessor *processor.VendorProcessorImpl
purchaseOrderProcessor *processor.PurchaseOrderProcessorImpl
purchaseCategoryProcessor *processor.PurchaseCategoryProcessorImpl
unitConverterProcessor *processor.IngredientUnitConverterProcessorImpl
chartOfAccountTypeProcessor *processor.ChartOfAccountTypeProcessorImpl
chartOfAccountProcessor *processor.ChartOfAccountProcessorImpl
accountProcessor *processor.AccountProcessorImpl
orderIngredientTransactionProcessor *processor.OrderIngredientTransactionProcessorImpl
customerTokensProcessor *processor.CustomerTokensProcessor
tierProcessor *processor.TierProcessor
gameProcessor *processor.GameProcessor
gamePrizeProcessor *processor.GamePrizeProcessor
gamePlayProcessor *processor.GamePlayProcessor
omsetTrackerProcessor *processor.OmsetTrackerProcessor
rewardProcessor processor.RewardProcessor
campaignProcessor processor.CampaignProcessor
campaignRuleProcessor processor.CampaignRuleProcessor
customerAuthProcessor processor.CustomerAuthProcessor
customerPointsProcessor *processor.CustomerPointsProcessor
otpProcessor processor.OtpProcessor
fileClient processor.FileClient
inventoryMovementService service.InventoryMovementService
userDeviceProcessor *processor.UserDeviceProcessorImpl
notificationProcessor *processor.NotificationProcessorImpl
productOutletPriceProcessor processor.ProductOutletPriceProcessor
expenseProcessor *processor.ExpenseProcessorImpl
cashAdvanceProcessor *processor.CashAdvanceProcessorImpl
walletProcessor *processor.WalletProcessor
walletAdminProcessor *processor.WalletAdminProcessor
loyaltySettingsProcessor *processor.LoyaltySettingsProcessor
}
func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processors {
fileClient := client.NewFileClient(cfg.S3Config)
fonnteClient := client.NewFonnteClient(cfg.GetFonnte())
otpProcessor := processor.NewOtpProcessor(fonnteClient, repos.otpRepo)
inventoryMovementService := service.NewInventoryMovementService(repos.inventoryMovementRepo, repos.ingredientRepo)
return &processors{
userProcessor: processor.NewUserProcessor(repos.userRepo, repos.organizationRepo, repos.outletRepo),
organizationProcessor: processor.NewOrganizationProcessorImpl(repos.organizationRepo, repos.outletRepo, repos.userRepo),
outletProcessor: processor.NewOutletProcessorImpl(repos.outletRepo),
outletSettingProcessor: processor.NewOutletSettingProcessorImpl(repos.outletSettingRepo, repos.outletRepo),
categoryProcessor: processor.NewCategoryProcessorImpl(repos.categoryRepo),
productProcessor: processor.NewProductProcessorImpl(repos.productRepo, repos.categoryRepo, repos.productVariantRepo, repos.inventoryRepo, repos.outletRepo, repos.productOutletPriceRepo),
productVariantProcessor: processor.NewProductVariantProcessorImpl(repos.productVariantRepo, repos.productRepo),
inventoryProcessor: processor.NewInventoryProcessorImpl(repos.inventoryRepo, repos.productRepo, repos.outletRepo, repos.ingredientRepo, repos.inventoryMovementRepo),
orderProcessor: processor.NewOrderProcessorImpl(repos.orderRepo, repos.orderItemRepo, repos.paymentRepo, repos.paymentOrderItemRepo, repos.productRepo, repos.paymentMethodRepo, repos.inventoryRepo, repos.inventoryMovementRepo, repos.productVariantRepo, repos.outletRepo, repos.customerRepo, repos.txManager, repos.productRecipeRepo, repos.ingredientRepo, inventoryMovementService, repos.productOutletPriceRepo),
paymentMethodProcessor: processor.NewPaymentMethodProcessorImpl(repos.paymentMethodRepo),
fileProcessor: processor.NewFileProcessorImpl(repos.fileRepo, fileClient),
customerProcessor: processor.NewCustomerProcessor(repos.customerRepo),
analyticsProcessor: processor.NewAnalyticsProcessorImpl(repos.analyticsRepo, repos.expenseRepo),
tableProcessor: processor.NewTableProcessor(repos.tableRepo, repos.orderRepo),
unitProcessor: processor.NewUnitProcessor(repos.unitRepo),
ingredientProcessor: processor.NewIngredientProcessor(repos.ingredientRepo, repos.unitRepo, repos.ingredientCompositionRepo),
productRecipeProcessor: processor.NewProductRecipeProcessor(repos.productRecipeRepo, repos.productRepo, repos.ingredientRepo),
vendorProcessor: processor.NewVendorProcessorImpl(repos.vendorRepo),
purchaseOrderProcessor: processor.NewPurchaseOrderProcessorImpl(repos.purchaseOrderRepo, repos.vendorRepo, repos.ingredientRepo, repos.purchaseCategoryRepo, repos.categoryRepo, repos.cashAdvanceRepo, repos.unitRepo, repos.fileRepo, inventoryMovementService, repos.unitConverterRepo),
purchaseCategoryProcessor: processor.NewPurchaseCategoryProcessorImpl(repos.purchaseCategoryRepo),
unitConverterProcessor: processor.NewIngredientUnitConverterProcessorImpl(repos.unitConverterRepo, repos.ingredientRepo, repos.unitRepo),
chartOfAccountTypeProcessor: processor.NewChartOfAccountTypeProcessorImpl(repos.chartOfAccountTypeRepo),
chartOfAccountProcessor: processor.NewChartOfAccountProcessorImpl(repos.chartOfAccountRepo, repos.chartOfAccountTypeRepo),
accountProcessor: processor.NewAccountProcessorImpl(repos.accountRepo, repos.chartOfAccountRepo),
orderIngredientTransactionProcessor: processor.NewOrderIngredientTransactionProcessorImpl(repos.orderIngredientTransactionRepo, repos.productRecipeRepo, repos.ingredientRepo, repos.unitRepo).(*processor.OrderIngredientTransactionProcessorImpl),
customerTokensProcessor: processor.NewCustomerTokensProcessor(repos.customerTokensRepo),
tierProcessor: processor.NewTierProcessor(repos.tierRepo),
gameProcessor: processor.NewGameProcessor(repos.gameRepo),
gamePrizeProcessor: processor.NewGamePrizeProcessor(repos.gamePrizeRepo),
gamePlayProcessor: processor.NewGamePlayProcessor(repos.gamePlayRepo, repos.gameRepo, repos.gamePrizeRepo, repos.customerTokensRepo, repos.customerPointsRepo),
omsetTrackerProcessor: processor.NewOmsetTrackerProcessor(repos.omsetTrackerRepo),
rewardProcessor: processor.NewRewardProcessor(repos.rewardRepo),
campaignProcessor: processor.NewCampaignProcessor(repos.campaignRepo),
campaignRuleProcessor: processor.NewCampaignRuleProcessor(repos.campaignRuleRepo),
customerAuthProcessor: processor.NewCustomerAuthProcessor(repos.customerAuthRepo, otpProcessor, repos.otpRepo, cfg.GetCustomerJWTSecret(), cfg.GetCustomerJWTExpiresTTL()),
customerPointsProcessor: processor.NewCustomerPointsProcessor(processor.NewWalletQueryProcessor(repos.walletQueryRepo, processor.NewLoyaltySettingsProcessor(repos.loyaltySettingsRepo, repos.txManager)), repos.gameRepo),
otpProcessor: otpProcessor,
fileClient: fileClient,
inventoryMovementService: inventoryMovementService,
userDeviceProcessor: processor.NewUserDeviceProcessorImpl(repos.userDeviceRepo),
notificationProcessor: buildNotificationProcessor(cfg, repos),
productOutletPriceProcessor: processor.NewProductOutletPriceProcessorImpl(repos.productOutletPriceRepo, repos.productRepo, repos.outletRepo),
expenseProcessor: processor.NewExpenseProcessorImpl(repos.expenseRepo, repos.purchaseCategoryRepo, repos.cashAdvanceRepo),
cashAdvanceProcessor: processor.NewCashAdvanceProcessorImpl(repos.cashAdvanceRepo, repos.categoryRepo),
walletProcessor: processor.NewWalletProcessor(repos.walletRepo),
loyaltySettingsProcessor: processor.NewLoyaltySettingsProcessor(repos.loyaltySettingsRepo, repos.txManager),
walletAdminProcessor: processor.NewWalletAdminProcessor(repository.NewWalletAdminRepository(a.db), repos.walletQueryRepo, processor.NewWalletProcessor(repos.walletRepo), repos.txManager),
}
}
type services struct {
userService *service.UserServiceImpl
authService service.AuthService
organizationService service.OrganizationService
outletService service.OutletService
outletSettingService service.OutletSettingService
categoryService service.CategoryService
productService service.ProductService
productVariantService service.ProductVariantService
inventoryService service.InventoryService
orderService service.OrderService
paymentMethodService service.PaymentMethodService
fileService service.FileService
customerService service.CustomerService
analyticsService *service.AnalyticsServiceImpl
reportService service.ReportService
tableService *service.TableServiceImpl
unitService *service.UnitServiceImpl
ingredientService *service.IngredientServiceImpl
productRecipeService *service.ProductRecipeServiceImpl
vendorService *service.VendorServiceImpl
purchaseOrderService *service.PurchaseOrderServiceImpl
purchaseCategoryService service.PurchaseCategoryService
unitConverterService *service.IngredientUnitConverterServiceImpl
chartOfAccountTypeService service.ChartOfAccountTypeService
chartOfAccountService service.ChartOfAccountService
accountService service.AccountService
orderIngredientTransactionService *service.OrderIngredientTransactionService
gamificationService service.GamificationService
rewardService service.RewardService
campaignService service.CampaignService
customerAuthService service.CustomerAuthService
customerPointsService service.CustomerPointsService
spinGameService service.SpinGameService
userDeviceService service.UserDeviceService
notificationService service.NotificationService
productOutletPriceService service.ProductOutletPriceService
expenseService *service.ExpenseServiceImpl
cashAdvanceService *service.CashAdvanceServiceImpl
walletAdminService *service.WalletAdminServiceImpl
}
func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
authConfig := cfg.Auth()
authService := service.NewAuthService(processors.userProcessor, processors.userDeviceProcessor, authConfig)
organizationService := service.NewOrganizationService(processors.organizationProcessor)
outletService := service.NewOutletService(processors.outletProcessor)
outletSettingService := service.NewOutletSettingService(processors.outletSettingProcessor)
categoryService := service.NewCategoryService(processors.categoryProcessor)
productService := service.NewProductService(processors.productProcessor)
productVariantService := service.NewProductVariantService(processors.productVariantProcessor)
inventoryService := service.NewInventoryService(processors.inventoryProcessor)
orderService := service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, nil, processors.orderIngredientTransactionProcessor, *repos.productRecipeRepo, repos.txManager, repos.sessionRepo, processors.notificationProcessor, repos.userRepo) // Will be updated after orderIngredientTransactionService is created
paymentMethodService := service.NewPaymentMethodService(processors.paymentMethodProcessor)
fileService := service.NewFileServiceImpl(processors.fileProcessor)
var customerService service.CustomerService = service.NewCustomerService(processors.customerProcessor)
analyticsService := service.NewAnalyticsServiceImpl(processors.analyticsProcessor)
reportService := service.NewReportService(analyticsService, repos.organizationRepo, repos.outletRepo, processors.fileClient)
tableService := service.NewTableService(processors.tableProcessor, transformer.NewTableTransformer())
unitService := service.NewUnitService(processors.unitProcessor)
ingredientService := service.NewIngredientService(processors.ingredientProcessor)
productRecipeService := service.NewProductRecipeService(processors.productRecipeProcessor)
vendorService := service.NewVendorService(processors.vendorProcessor)
purchaseOrderService := service.NewPurchaseOrderService(processors.purchaseOrderProcessor)
purchaseCategoryService := service.NewPurchaseCategoryService(processors.purchaseCategoryProcessor)
unitConverterService := service.NewIngredientUnitConverterService(processors.unitConverterProcessor)
chartOfAccountTypeService := service.NewChartOfAccountTypeService(processors.chartOfAccountTypeProcessor)
chartOfAccountService := service.NewChartOfAccountService(processors.chartOfAccountProcessor)
accountService := service.NewAccountService(processors.accountProcessor)
orderIngredientTransactionService := service.NewOrderIngredientTransactionService(processors.orderIngredientTransactionProcessor, repos.txManager)
gamificationService := service.NewGamificationService(processors.customerPointsProcessor, processors.customerTokensProcessor, processors.tierProcessor, processors.gameProcessor, processors.gamePrizeProcessor, processors.gamePlayProcessor, processors.omsetTrackerProcessor)
rewardService := service.NewRewardService(processors.rewardProcessor)
campaignService := service.NewCampaignService(processors.campaignProcessor, processors.campaignRuleProcessor)
customerAuthService := service.NewCustomerAuthService(processors.customerAuthProcessor)
customerPointsService := service.NewCustomerPointsService(processors.customerPointsProcessor)
spinGameService := service.NewSpinGameService(processors.gamePlayProcessor, repos.txManager)
userDeviceService := service.NewUserDeviceService(processors.userDeviceProcessor)
notificationService := service.NewNotificationService(processors.notificationProcessor)
// Update order service with order ingredient transaction service
orderService = service.NewOrderServiceImpl(processors.orderProcessor, repos.tableRepo, orderIngredientTransactionService, processors.orderIngredientTransactionProcessor, *repos.productRecipeRepo, repos.txManager, repos.sessionRepo, processors.notificationProcessor, repos.userRepo)
return &services{
userService: service.NewUserService(processors.userProcessor),
authService: authService,
organizationService: organizationService,
outletService: outletService,
outletSettingService: outletSettingService,
categoryService: categoryService,
productService: productService,
productVariantService: productVariantService,
inventoryService: inventoryService,
orderService: orderService,
paymentMethodService: paymentMethodService,
fileService: fileService,
customerService: customerService,
analyticsService: analyticsService,
reportService: reportService,
tableService: tableService,
unitService: unitService,
ingredientService: ingredientService,
productRecipeService: productRecipeService,
vendorService: vendorService,
purchaseOrderService: purchaseOrderService,
purchaseCategoryService: purchaseCategoryService,
unitConverterService: unitConverterService,
chartOfAccountTypeService: chartOfAccountTypeService,
chartOfAccountService: chartOfAccountService,
accountService: accountService,
orderIngredientTransactionService: orderIngredientTransactionService,
gamificationService: gamificationService,
rewardService: rewardService,
campaignService: campaignService,
customerAuthService: customerAuthService,
customerPointsService: customerPointsService,
spinGameService: spinGameService,
userDeviceService: userDeviceService,
notificationService: notificationService,
productOutletPriceService: service.NewProductOutletPriceService(processors.productOutletPriceProcessor),
expenseService: service.NewExpenseService(processors.expenseProcessor),
cashAdvanceService: service.NewCashAdvanceService(processors.cashAdvanceProcessor),
walletAdminService: service.NewWalletAdminService(processors.walletAdminProcessor),
}
}
type middlewares struct {
authMiddleware *middleware.AuthMiddleware
customerAuthMiddleware *middleware.CustomerAuthMiddleware
}
func (a *App) initMiddleware(services *services, cfg *config.Config) *middlewares {
return &middlewares{
authMiddleware: middleware.NewAuthMiddleware(services.authService),
customerAuthMiddleware: middleware.NewCustomerAuthMiddleware(cfg.GetCustomerJWTSecret()),
}
}
type validators struct {
userValidator *validator.UserValidatorImpl
organizationValidator validator.OrganizationValidator
outletValidator validator.OutletValidator
categoryValidator validator.CategoryValidator
productValidator validator.ProductValidator
productVariantValidator validator.ProductVariantValidator
inventoryValidator validator.InventoryValidator
orderValidator validator.OrderValidator
paymentMethodValidator validator.PaymentMethodValidator
fileValidator validator.FileValidator
customerValidator validator.CustomerValidator
tableValidator *validator.TableValidator
vendorValidator *validator.VendorValidatorImpl
purchaseOrderValidator *validator.PurchaseOrderValidatorImpl
purchaseCategoryValidator *validator.PurchaseCategoryValidatorImpl
unitConverterValidator *validator.IngredientUnitConverterValidatorImpl
chartOfAccountTypeValidator *validator.ChartOfAccountTypeValidatorImpl
chartOfAccountValidator *validator.ChartOfAccountValidatorImpl
accountValidator *validator.AccountValidatorImpl
orderIngredientTransactionValidator *validator.OrderIngredientTransactionValidatorImpl
gamificationValidator *validator.GamificationValidatorImpl
rewardValidator validator.RewardValidator
campaignValidator validator.CampaignValidator
customerAuthValidator validator.CustomerAuthValidator
userDeviceValidator *validator.UserDeviceValidatorImpl
notificationValidator *validator.NotificationValidatorImpl
productOutletPriceValidator *validator.ProductOutletPriceValidatorImpl
expenseValidator *validator.ExpenseValidatorImpl
cashAdvanceValidator *validator.CashAdvanceValidatorImpl
walletValidator *validator.WalletValidatorImpl
}
func (a *App) initValidators() *validators {
return &validators{
userValidator: validator.NewUserValidator(),
organizationValidator: validator.NewOrganizationValidator(),
outletValidator: validator.NewOutletValidator(),
categoryValidator: validator.NewCategoryValidator(),
productValidator: validator.NewProductValidator(),
productVariantValidator: validator.NewProductVariantValidator(),
inventoryValidator: validator.NewInventoryValidator(),
orderValidator: validator.NewOrderValidator(),
paymentMethodValidator: validator.NewPaymentMethodValidator(),
fileValidator: validator.NewFileValidatorImpl(),
customerValidator: validator.NewCustomerValidator(),
tableValidator: validator.NewTableValidator(),
vendorValidator: validator.NewVendorValidator(),
purchaseOrderValidator: validator.NewPurchaseOrderValidator(),
purchaseCategoryValidator: validator.NewPurchaseCategoryValidator(),
unitConverterValidator: validator.NewIngredientUnitConverterValidator().(*validator.IngredientUnitConverterValidatorImpl),
chartOfAccountTypeValidator: validator.NewChartOfAccountTypeValidator().(*validator.ChartOfAccountTypeValidatorImpl),
chartOfAccountValidator: validator.NewChartOfAccountValidator().(*validator.ChartOfAccountValidatorImpl),
accountValidator: validator.NewAccountValidator().(*validator.AccountValidatorImpl),
orderIngredientTransactionValidator: validator.NewOrderIngredientTransactionValidator().(*validator.OrderIngredientTransactionValidatorImpl),
gamificationValidator: validator.NewGamificationValidator(),
rewardValidator: validator.NewRewardValidator(),
campaignValidator: validator.NewCampaignValidator(),
customerAuthValidator: validator.NewCustomerAuthValidator(),
userDeviceValidator: validator.NewUserDeviceValidator(),
notificationValidator: validator.NewNotificationValidator(),
productOutletPriceValidator: validator.NewProductOutletPriceValidator(),
expenseValidator: validator.NewExpenseValidator(),
cashAdvanceValidator: validator.NewCashAdvanceValidator(),
walletValidator: validator.NewWalletValidator(),
}
}
// buildNotificationProcessor creates the notification processor with FCM integration.
// If FCM is not configured, it returns a processor with a nil FCM client (FCM dispatch will be skipped).
func buildNotificationProcessor(cfg *config.Config, repos *repositories) *processor.NotificationProcessorImpl {
var fcmClient client.FCMClient
if cfg.FCM.CredentialsFile != "" {
var err error
fcmClient, err = client.NewFCMClient(&cfg.FCM)
if err != nil {
// FCM init failure is non-fatal; notifications will still be persisted.
fcmClient = nil
}
}
return processor.NewNotificationProcessor(
repos.notificationRepo,
repos.notificationReceiverRepo,
repos.notificationDeliveryRepo,
repos.userDeviceRepo,
repos.userRepo,
fcmClient,
)
}