Add coa purchase and vendors
This commit is contained in:
parent
c107733add
commit
efe09c21e4
@ -80,6 +80,18 @@ func (a *App) Initialize(cfg *config.Config) error {
|
|||||||
services.unitService,
|
services.unitService,
|
||||||
services.ingredientService,
|
services.ingredientService,
|
||||||
services.productRecipeService,
|
services.productRecipeService,
|
||||||
|
services.vendorService,
|
||||||
|
validators.vendorValidator,
|
||||||
|
services.purchaseOrderService,
|
||||||
|
validators.purchaseOrderValidator,
|
||||||
|
services.unitConverterService,
|
||||||
|
validators.unitConverterValidator,
|
||||||
|
services.chartOfAccountTypeService,
|
||||||
|
validators.chartOfAccountTypeValidator,
|
||||||
|
services.chartOfAccountService,
|
||||||
|
validators.chartOfAccountValidator,
|
||||||
|
services.accountService,
|
||||||
|
validators.accountValidator,
|
||||||
)
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -125,77 +137,95 @@ func (a *App) Shutdown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type repositories struct {
|
type repositories struct {
|
||||||
userRepo *repository.UserRepositoryImpl
|
userRepo *repository.UserRepositoryImpl
|
||||||
organizationRepo *repository.OrganizationRepositoryImpl
|
organizationRepo *repository.OrganizationRepositoryImpl
|
||||||
outletRepo *repository.OutletRepositoryImpl
|
outletRepo *repository.OutletRepositoryImpl
|
||||||
outletSettingRepo *repository.OutletSettingRepositoryImpl
|
outletSettingRepo *repository.OutletSettingRepositoryImpl
|
||||||
categoryRepo *repository.CategoryRepositoryImpl
|
categoryRepo *repository.CategoryRepositoryImpl
|
||||||
productRepo *repository.ProductRepositoryImpl
|
productRepo *repository.ProductRepositoryImpl
|
||||||
productVariantRepo *repository.ProductVariantRepositoryImpl
|
productVariantRepo *repository.ProductVariantRepositoryImpl
|
||||||
inventoryRepo *repository.InventoryRepositoryImpl
|
inventoryRepo *repository.InventoryRepositoryImpl
|
||||||
inventoryMovementRepo *repository.InventoryMovementRepositoryImpl
|
inventoryMovementRepo *repository.InventoryMovementRepositoryImpl
|
||||||
orderRepo *repository.OrderRepositoryImpl
|
orderRepo *repository.OrderRepositoryImpl
|
||||||
orderItemRepo *repository.OrderItemRepositoryImpl
|
orderItemRepo *repository.OrderItemRepositoryImpl
|
||||||
paymentRepo *repository.PaymentRepositoryImpl
|
paymentRepo *repository.PaymentRepositoryImpl
|
||||||
paymentOrderItemRepo *repository.PaymentOrderItemRepositoryImpl
|
paymentOrderItemRepo *repository.PaymentOrderItemRepositoryImpl
|
||||||
paymentMethodRepo *repository.PaymentMethodRepositoryImpl
|
paymentMethodRepo *repository.PaymentMethodRepositoryImpl
|
||||||
fileRepo *repository.FileRepositoryImpl
|
fileRepo *repository.FileRepositoryImpl
|
||||||
customerRepo *repository.CustomerRepository
|
customerRepo *repository.CustomerRepository
|
||||||
analyticsRepo *repository.AnalyticsRepositoryImpl
|
analyticsRepo *repository.AnalyticsRepositoryImpl
|
||||||
tableRepo *repository.TableRepository
|
tableRepo *repository.TableRepository
|
||||||
unitRepo *repository.UnitRepository
|
unitRepo *repository.UnitRepository
|
||||||
ingredientRepo *repository.IngredientRepository
|
ingredientRepo *repository.IngredientRepository
|
||||||
productRecipeRepo *repository.ProductRecipeRepository
|
productRecipeRepo *repository.ProductRecipeRepository
|
||||||
txManager *repository.TxManager
|
vendorRepo *repository.VendorRepositoryImpl
|
||||||
|
purchaseOrderRepo *repository.PurchaseOrderRepositoryImpl
|
||||||
|
unitConverterRepo *repository.IngredientUnitConverterRepositoryImpl
|
||||||
|
chartOfAccountTypeRepo *repository.ChartOfAccountTypeRepositoryImpl
|
||||||
|
chartOfAccountRepo *repository.ChartOfAccountRepositoryImpl
|
||||||
|
accountRepo *repository.AccountRepositoryImpl
|
||||||
|
txManager *repository.TxManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) initRepositories() *repositories {
|
func (a *App) initRepositories() *repositories {
|
||||||
return &repositories{
|
return &repositories{
|
||||||
userRepo: repository.NewUserRepository(a.db),
|
userRepo: repository.NewUserRepository(a.db),
|
||||||
organizationRepo: repository.NewOrganizationRepositoryImpl(a.db),
|
organizationRepo: repository.NewOrganizationRepositoryImpl(a.db),
|
||||||
outletRepo: repository.NewOutletRepositoryImpl(a.db),
|
outletRepo: repository.NewOutletRepositoryImpl(a.db),
|
||||||
outletSettingRepo: repository.NewOutletSettingRepositoryImpl(a.db),
|
outletSettingRepo: repository.NewOutletSettingRepositoryImpl(a.db),
|
||||||
categoryRepo: repository.NewCategoryRepositoryImpl(a.db),
|
categoryRepo: repository.NewCategoryRepositoryImpl(a.db),
|
||||||
productRepo: repository.NewProductRepositoryImpl(a.db),
|
productRepo: repository.NewProductRepositoryImpl(a.db),
|
||||||
productVariantRepo: repository.NewProductVariantRepositoryImpl(a.db),
|
productVariantRepo: repository.NewProductVariantRepositoryImpl(a.db),
|
||||||
inventoryRepo: repository.NewInventoryRepositoryImpl(a.db),
|
inventoryRepo: repository.NewInventoryRepositoryImpl(a.db),
|
||||||
inventoryMovementRepo: repository.NewInventoryMovementRepositoryImpl(a.db),
|
inventoryMovementRepo: repository.NewInventoryMovementRepositoryImpl(a.db),
|
||||||
orderRepo: repository.NewOrderRepositoryImpl(a.db),
|
orderRepo: repository.NewOrderRepositoryImpl(a.db),
|
||||||
orderItemRepo: repository.NewOrderItemRepositoryImpl(a.db),
|
orderItemRepo: repository.NewOrderItemRepositoryImpl(a.db),
|
||||||
paymentRepo: repository.NewPaymentRepositoryImpl(a.db),
|
paymentRepo: repository.NewPaymentRepositoryImpl(a.db),
|
||||||
paymentOrderItemRepo: repository.NewPaymentOrderItemRepositoryImpl(a.db),
|
paymentOrderItemRepo: repository.NewPaymentOrderItemRepositoryImpl(a.db),
|
||||||
paymentMethodRepo: repository.NewPaymentMethodRepositoryImpl(a.db),
|
paymentMethodRepo: repository.NewPaymentMethodRepositoryImpl(a.db),
|
||||||
fileRepo: repository.NewFileRepositoryImpl(a.db),
|
fileRepo: repository.NewFileRepositoryImpl(a.db),
|
||||||
customerRepo: repository.NewCustomerRepository(a.db),
|
customerRepo: repository.NewCustomerRepository(a.db),
|
||||||
analyticsRepo: repository.NewAnalyticsRepositoryImpl(a.db),
|
analyticsRepo: repository.NewAnalyticsRepositoryImpl(a.db),
|
||||||
tableRepo: repository.NewTableRepository(a.db),
|
tableRepo: repository.NewTableRepository(a.db),
|
||||||
unitRepo: repository.NewUnitRepository(a.db),
|
unitRepo: repository.NewUnitRepository(a.db),
|
||||||
ingredientRepo: repository.NewIngredientRepository(a.db),
|
ingredientRepo: repository.NewIngredientRepository(a.db),
|
||||||
productRecipeRepo: repository.NewProductRecipeRepository(a.db),
|
productRecipeRepo: repository.NewProductRecipeRepository(a.db),
|
||||||
txManager: repository.NewTxManager(a.db),
|
vendorRepo: repository.NewVendorRepositoryImpl(a.db),
|
||||||
|
purchaseOrderRepo: repository.NewPurchaseOrderRepositoryImpl(a.db),
|
||||||
|
unitConverterRepo: repository.NewIngredientUnitConverterRepositoryImpl(a.db).(*repository.IngredientUnitConverterRepositoryImpl),
|
||||||
|
chartOfAccountTypeRepo: repository.NewChartOfAccountTypeRepositoryImpl(a.db),
|
||||||
|
chartOfAccountRepo: repository.NewChartOfAccountRepositoryImpl(a.db),
|
||||||
|
accountRepo: repository.NewAccountRepositoryImpl(a.db),
|
||||||
|
txManager: repository.NewTxManager(a.db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type processors struct {
|
type processors struct {
|
||||||
userProcessor *processor.UserProcessorImpl
|
userProcessor *processor.UserProcessorImpl
|
||||||
organizationProcessor processor.OrganizationProcessor
|
organizationProcessor processor.OrganizationProcessor
|
||||||
outletProcessor processor.OutletProcessor
|
outletProcessor processor.OutletProcessor
|
||||||
outletSettingProcessor *processor.OutletSettingProcessorImpl
|
outletSettingProcessor *processor.OutletSettingProcessorImpl
|
||||||
categoryProcessor processor.CategoryProcessor
|
categoryProcessor processor.CategoryProcessor
|
||||||
productProcessor processor.ProductProcessor
|
productProcessor processor.ProductProcessor
|
||||||
productVariantProcessor processor.ProductVariantProcessor
|
productVariantProcessor processor.ProductVariantProcessor
|
||||||
inventoryProcessor processor.InventoryProcessor
|
inventoryProcessor processor.InventoryProcessor
|
||||||
orderProcessor processor.OrderProcessor
|
orderProcessor processor.OrderProcessor
|
||||||
paymentMethodProcessor processor.PaymentMethodProcessor
|
paymentMethodProcessor processor.PaymentMethodProcessor
|
||||||
fileProcessor processor.FileProcessor
|
fileProcessor processor.FileProcessor
|
||||||
customerProcessor *processor.CustomerProcessor
|
customerProcessor *processor.CustomerProcessor
|
||||||
analyticsProcessor *processor.AnalyticsProcessorImpl
|
analyticsProcessor *processor.AnalyticsProcessorImpl
|
||||||
tableProcessor *processor.TableProcessor
|
tableProcessor *processor.TableProcessor
|
||||||
unitProcessor *processor.UnitProcessorImpl
|
unitProcessor *processor.UnitProcessorImpl
|
||||||
ingredientProcessor *processor.IngredientProcessorImpl
|
ingredientProcessor *processor.IngredientProcessorImpl
|
||||||
productRecipeProcessor *processor.ProductRecipeProcessorImpl
|
productRecipeProcessor *processor.ProductRecipeProcessorImpl
|
||||||
fileClient processor.FileClient
|
vendorProcessor *processor.VendorProcessorImpl
|
||||||
inventoryMovementService service.InventoryMovementService
|
purchaseOrderProcessor *processor.PurchaseOrderProcessorImpl
|
||||||
|
unitConverterProcessor *processor.IngredientUnitConverterProcessorImpl
|
||||||
|
chartOfAccountTypeProcessor *processor.ChartOfAccountTypeProcessorImpl
|
||||||
|
chartOfAccountProcessor *processor.ChartOfAccountProcessorImpl
|
||||||
|
accountProcessor *processor.AccountProcessorImpl
|
||||||
|
fileClient processor.FileClient
|
||||||
|
inventoryMovementService service.InventoryMovementService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processors {
|
func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processors {
|
||||||
@ -203,48 +233,60 @@ func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processor
|
|||||||
inventoryMovementService := service.NewInventoryMovementService(repos.inventoryMovementRepo, repos.ingredientRepo)
|
inventoryMovementService := service.NewInventoryMovementService(repos.inventoryMovementRepo, repos.ingredientRepo)
|
||||||
|
|
||||||
return &processors{
|
return &processors{
|
||||||
userProcessor: processor.NewUserProcessor(repos.userRepo, repos.organizationRepo, repos.outletRepo),
|
userProcessor: processor.NewUserProcessor(repos.userRepo, repos.organizationRepo, repos.outletRepo),
|
||||||
organizationProcessor: processor.NewOrganizationProcessorImpl(repos.organizationRepo, repos.outletRepo, repos.userRepo),
|
organizationProcessor: processor.NewOrganizationProcessorImpl(repos.organizationRepo, repos.outletRepo, repos.userRepo),
|
||||||
outletProcessor: processor.NewOutletProcessorImpl(repos.outletRepo),
|
outletProcessor: processor.NewOutletProcessorImpl(repos.outletRepo),
|
||||||
outletSettingProcessor: processor.NewOutletSettingProcessorImpl(repos.outletSettingRepo, repos.outletRepo),
|
outletSettingProcessor: processor.NewOutletSettingProcessorImpl(repos.outletSettingRepo, repos.outletRepo),
|
||||||
categoryProcessor: processor.NewCategoryProcessorImpl(repos.categoryRepo),
|
categoryProcessor: processor.NewCategoryProcessorImpl(repos.categoryRepo),
|
||||||
productProcessor: processor.NewProductProcessorImpl(repos.productRepo, repos.categoryRepo, repos.productVariantRepo, repos.inventoryRepo, repos.outletRepo),
|
productProcessor: processor.NewProductProcessorImpl(repos.productRepo, repos.categoryRepo, repos.productVariantRepo, repos.inventoryRepo, repos.outletRepo),
|
||||||
productVariantProcessor: processor.NewProductVariantProcessorImpl(repos.productVariantRepo, repos.productRepo),
|
productVariantProcessor: processor.NewProductVariantProcessorImpl(repos.productVariantRepo, repos.productRepo),
|
||||||
inventoryProcessor: processor.NewInventoryProcessorImpl(repos.inventoryRepo, repos.productRepo, repos.outletRepo, repos.ingredientRepo, repos.inventoryMovementRepo),
|
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),
|
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),
|
||||||
paymentMethodProcessor: processor.NewPaymentMethodProcessorImpl(repos.paymentMethodRepo),
|
paymentMethodProcessor: processor.NewPaymentMethodProcessorImpl(repos.paymentMethodRepo),
|
||||||
fileProcessor: processor.NewFileProcessorImpl(repos.fileRepo, fileClient),
|
fileProcessor: processor.NewFileProcessorImpl(repos.fileRepo, fileClient),
|
||||||
customerProcessor: processor.NewCustomerProcessor(repos.customerRepo),
|
customerProcessor: processor.NewCustomerProcessor(repos.customerRepo),
|
||||||
analyticsProcessor: processor.NewAnalyticsProcessorImpl(repos.analyticsRepo),
|
analyticsProcessor: processor.NewAnalyticsProcessorImpl(repos.analyticsRepo),
|
||||||
tableProcessor: processor.NewTableProcessor(repos.tableRepo, repos.orderRepo),
|
tableProcessor: processor.NewTableProcessor(repos.tableRepo, repos.orderRepo),
|
||||||
unitProcessor: processor.NewUnitProcessor(repos.unitRepo),
|
unitProcessor: processor.NewUnitProcessor(repos.unitRepo),
|
||||||
ingredientProcessor: processor.NewIngredientProcessor(repos.ingredientRepo, repos.unitRepo),
|
ingredientProcessor: processor.NewIngredientProcessor(repos.ingredientRepo, repos.unitRepo),
|
||||||
productRecipeProcessor: processor.NewProductRecipeProcessor(repos.productRecipeRepo, repos.productRepo, repos.ingredientRepo),
|
productRecipeProcessor: processor.NewProductRecipeProcessor(repos.productRecipeRepo, repos.productRepo, repos.ingredientRepo),
|
||||||
fileClient: fileClient,
|
vendorProcessor: processor.NewVendorProcessorImpl(repos.vendorRepo),
|
||||||
inventoryMovementService: inventoryMovementService,
|
purchaseOrderProcessor: processor.NewPurchaseOrderProcessorImpl(repos.purchaseOrderRepo, repos.vendorRepo, repos.ingredientRepo, repos.unitRepo, repos.fileRepo, inventoryMovementService, repos.unitConverterRepo),
|
||||||
|
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),
|
||||||
|
fileClient: fileClient,
|
||||||
|
inventoryMovementService: inventoryMovementService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type services struct {
|
type services struct {
|
||||||
userService *service.UserServiceImpl
|
userService *service.UserServiceImpl
|
||||||
authService service.AuthService
|
authService service.AuthService
|
||||||
organizationService service.OrganizationService
|
organizationService service.OrganizationService
|
||||||
outletService service.OutletService
|
outletService service.OutletService
|
||||||
outletSettingService service.OutletSettingService
|
outletSettingService service.OutletSettingService
|
||||||
categoryService service.CategoryService
|
categoryService service.CategoryService
|
||||||
productService service.ProductService
|
productService service.ProductService
|
||||||
productVariantService service.ProductVariantService
|
productVariantService service.ProductVariantService
|
||||||
inventoryService service.InventoryService
|
inventoryService service.InventoryService
|
||||||
orderService service.OrderService
|
orderService service.OrderService
|
||||||
paymentMethodService service.PaymentMethodService
|
paymentMethodService service.PaymentMethodService
|
||||||
fileService service.FileService
|
fileService service.FileService
|
||||||
customerService service.CustomerService
|
customerService service.CustomerService
|
||||||
analyticsService *service.AnalyticsServiceImpl
|
analyticsService *service.AnalyticsServiceImpl
|
||||||
reportService service.ReportService
|
reportService service.ReportService
|
||||||
tableService *service.TableServiceImpl
|
tableService *service.TableServiceImpl
|
||||||
unitService *service.UnitServiceImpl
|
unitService *service.UnitServiceImpl
|
||||||
ingredientService *service.IngredientServiceImpl
|
ingredientService *service.IngredientServiceImpl
|
||||||
productRecipeService *service.ProductRecipeServiceImpl
|
productRecipeService *service.ProductRecipeServiceImpl
|
||||||
|
vendorService *service.VendorServiceImpl
|
||||||
|
purchaseOrderService *service.PurchaseOrderServiceImpl
|
||||||
|
unitConverterService *service.IngredientUnitConverterServiceImpl
|
||||||
|
chartOfAccountTypeService service.ChartOfAccountTypeService
|
||||||
|
chartOfAccountService service.ChartOfAccountService
|
||||||
|
accountService service.AccountService
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
|
func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
|
||||||
@ -268,27 +310,39 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
|
|||||||
unitService := service.NewUnitService(processors.unitProcessor)
|
unitService := service.NewUnitService(processors.unitProcessor)
|
||||||
ingredientService := service.NewIngredientService(processors.ingredientProcessor)
|
ingredientService := service.NewIngredientService(processors.ingredientProcessor)
|
||||||
productRecipeService := service.NewProductRecipeService(processors.productRecipeProcessor)
|
productRecipeService := service.NewProductRecipeService(processors.productRecipeProcessor)
|
||||||
|
vendorService := service.NewVendorService(processors.vendorProcessor)
|
||||||
|
purchaseOrderService := service.NewPurchaseOrderService(processors.purchaseOrderProcessor)
|
||||||
|
unitConverterService := service.NewIngredientUnitConverterService(processors.unitConverterProcessor)
|
||||||
|
chartOfAccountTypeService := service.NewChartOfAccountTypeService(processors.chartOfAccountTypeProcessor)
|
||||||
|
chartOfAccountService := service.NewChartOfAccountService(processors.chartOfAccountProcessor)
|
||||||
|
accountService := service.NewAccountService(processors.accountProcessor)
|
||||||
|
|
||||||
return &services{
|
return &services{
|
||||||
userService: service.NewUserService(processors.userProcessor),
|
userService: service.NewUserService(processors.userProcessor),
|
||||||
authService: authService,
|
authService: authService,
|
||||||
organizationService: organizationService,
|
organizationService: organizationService,
|
||||||
outletService: outletService,
|
outletService: outletService,
|
||||||
outletSettingService: outletSettingService,
|
outletSettingService: outletSettingService,
|
||||||
categoryService: categoryService,
|
categoryService: categoryService,
|
||||||
productService: productService,
|
productService: productService,
|
||||||
productVariantService: productVariantService,
|
productVariantService: productVariantService,
|
||||||
inventoryService: inventoryService,
|
inventoryService: inventoryService,
|
||||||
orderService: orderService,
|
orderService: orderService,
|
||||||
paymentMethodService: paymentMethodService,
|
paymentMethodService: paymentMethodService,
|
||||||
fileService: fileService,
|
fileService: fileService,
|
||||||
customerService: customerService,
|
customerService: customerService,
|
||||||
analyticsService: analyticsService,
|
analyticsService: analyticsService,
|
||||||
reportService: reportService,
|
reportService: reportService,
|
||||||
tableService: tableService,
|
tableService: tableService,
|
||||||
unitService: unitService,
|
unitService: unitService,
|
||||||
ingredientService: ingredientService,
|
ingredientService: ingredientService,
|
||||||
productRecipeService: productRecipeService,
|
productRecipeService: productRecipeService,
|
||||||
|
vendorService: vendorService,
|
||||||
|
purchaseOrderService: purchaseOrderService,
|
||||||
|
unitConverterService: unitConverterService,
|
||||||
|
chartOfAccountTypeService: chartOfAccountTypeService,
|
||||||
|
chartOfAccountService: chartOfAccountService,
|
||||||
|
accountService: accountService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -303,33 +357,45 @@ func (a *App) initMiddleware(services *services) *middlewares {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type validators struct {
|
type validators struct {
|
||||||
userValidator *validator.UserValidatorImpl
|
userValidator *validator.UserValidatorImpl
|
||||||
organizationValidator validator.OrganizationValidator
|
organizationValidator validator.OrganizationValidator
|
||||||
outletValidator validator.OutletValidator
|
outletValidator validator.OutletValidator
|
||||||
categoryValidator validator.CategoryValidator
|
categoryValidator validator.CategoryValidator
|
||||||
productValidator validator.ProductValidator
|
productValidator validator.ProductValidator
|
||||||
productVariantValidator validator.ProductVariantValidator
|
productVariantValidator validator.ProductVariantValidator
|
||||||
inventoryValidator validator.InventoryValidator
|
inventoryValidator validator.InventoryValidator
|
||||||
orderValidator validator.OrderValidator
|
orderValidator validator.OrderValidator
|
||||||
paymentMethodValidator validator.PaymentMethodValidator
|
paymentMethodValidator validator.PaymentMethodValidator
|
||||||
fileValidator validator.FileValidator
|
fileValidator validator.FileValidator
|
||||||
customerValidator validator.CustomerValidator
|
customerValidator validator.CustomerValidator
|
||||||
tableValidator *validator.TableValidator
|
tableValidator *validator.TableValidator
|
||||||
|
vendorValidator *validator.VendorValidatorImpl
|
||||||
|
purchaseOrderValidator *validator.PurchaseOrderValidatorImpl
|
||||||
|
unitConverterValidator *validator.IngredientUnitConverterValidatorImpl
|
||||||
|
chartOfAccountTypeValidator *validator.ChartOfAccountTypeValidatorImpl
|
||||||
|
chartOfAccountValidator *validator.ChartOfAccountValidatorImpl
|
||||||
|
accountValidator *validator.AccountValidatorImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) initValidators() *validators {
|
func (a *App) initValidators() *validators {
|
||||||
return &validators{
|
return &validators{
|
||||||
userValidator: validator.NewUserValidator(),
|
userValidator: validator.NewUserValidator(),
|
||||||
organizationValidator: validator.NewOrganizationValidator(),
|
organizationValidator: validator.NewOrganizationValidator(),
|
||||||
outletValidator: validator.NewOutletValidator(),
|
outletValidator: validator.NewOutletValidator(),
|
||||||
categoryValidator: validator.NewCategoryValidator(),
|
categoryValidator: validator.NewCategoryValidator(),
|
||||||
productValidator: validator.NewProductValidator(),
|
productValidator: validator.NewProductValidator(),
|
||||||
productVariantValidator: validator.NewProductVariantValidator(),
|
productVariantValidator: validator.NewProductVariantValidator(),
|
||||||
inventoryValidator: validator.NewInventoryValidator(),
|
inventoryValidator: validator.NewInventoryValidator(),
|
||||||
orderValidator: validator.NewOrderValidator(),
|
orderValidator: validator.NewOrderValidator(),
|
||||||
paymentMethodValidator: validator.NewPaymentMethodValidator(),
|
paymentMethodValidator: validator.NewPaymentMethodValidator(),
|
||||||
fileValidator: validator.NewFileValidatorImpl(),
|
fileValidator: validator.NewFileValidatorImpl(),
|
||||||
customerValidator: validator.NewCustomerValidator(),
|
customerValidator: validator.NewCustomerValidator(),
|
||||||
tableValidator: validator.NewTableValidator(),
|
tableValidator: validator.NewTableValidator(),
|
||||||
|
vendorValidator: validator.NewVendorValidator(),
|
||||||
|
purchaseOrderValidator: validator.NewPurchaseOrderValidator(),
|
||||||
|
unitConverterValidator: validator.NewIngredientUnitConverterValidator().(*validator.IngredientUnitConverterValidatorImpl),
|
||||||
|
chartOfAccountTypeValidator: validator.NewChartOfAccountTypeValidator().(*validator.ChartOfAccountTypeValidatorImpl),
|
||||||
|
chartOfAccountValidator: validator.NewChartOfAccountValidator().(*validator.ChartOfAccountValidatorImpl),
|
||||||
|
accountValidator: validator.NewAccountValidator().(*validator.AccountValidatorImpl),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
272
internal/constants/default_chart_of_accounts.json
Normal file
272
internal/constants/default_chart_of_accounts.json
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
{
|
||||||
|
"chart_of_accounts": [
|
||||||
|
{
|
||||||
|
"name": "Current Assets",
|
||||||
|
"code": "1000",
|
||||||
|
"chart_of_account_type": "ASSET",
|
||||||
|
"parent_code": null,
|
||||||
|
"is_system": true,
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "Cash on Hand",
|
||||||
|
"number": "1001",
|
||||||
|
"account_type": "cash",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Physical cash available at the outlet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Petty Cash",
|
||||||
|
"number": "1002",
|
||||||
|
"account_type": "cash",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Small amount of cash for minor expenses"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Bank Account - Main",
|
||||||
|
"number": "1003",
|
||||||
|
"account_type": "bank",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Primary business bank account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Digital Wallet",
|
||||||
|
"number": "1004",
|
||||||
|
"account_type": "wallet",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Digital payment wallet"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Inventory",
|
||||||
|
"code": "1100",
|
||||||
|
"chart_of_account_type": "ASSET",
|
||||||
|
"parent_code": "1000",
|
||||||
|
"is_system": true,
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "Raw Materials",
|
||||||
|
"number": "1101",
|
||||||
|
"account_type": "asset",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Raw materials and ingredients inventory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Finished Goods",
|
||||||
|
"number": "1102",
|
||||||
|
"account_type": "asset",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Finished products ready for sale"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Work in Progress",
|
||||||
|
"number": "1103",
|
||||||
|
"account_type": "asset",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Products in production process"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Fixed Assets",
|
||||||
|
"code": "1500",
|
||||||
|
"chart_of_account_type": "ASSET",
|
||||||
|
"parent_code": null,
|
||||||
|
"is_system": true,
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "Equipment",
|
||||||
|
"number": "1501",
|
||||||
|
"account_type": "asset",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Business equipment and machinery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Furniture & Fixtures",
|
||||||
|
"number": "1502",
|
||||||
|
"account_type": "asset",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Furniture and fixtures"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Current Liabilities",
|
||||||
|
"code": "2000",
|
||||||
|
"chart_of_account_type": "LIABILITY",
|
||||||
|
"parent_code": null,
|
||||||
|
"is_system": true,
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "Accounts Payable",
|
||||||
|
"number": "2001",
|
||||||
|
"account_type": "liability",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Amounts owed to suppliers and vendors"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Accrued Expenses",
|
||||||
|
"number": "2002",
|
||||||
|
"account_type": "liability",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Expenses incurred but not yet paid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sales Tax Payable",
|
||||||
|
"number": "2003",
|
||||||
|
"account_type": "liability",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Sales tax collected but not yet remitted"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Owner's Equity",
|
||||||
|
"code": "3000",
|
||||||
|
"chart_of_account_type": "EQUITY",
|
||||||
|
"parent_code": null,
|
||||||
|
"is_system": true,
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "Owner's Capital",
|
||||||
|
"number": "3001",
|
||||||
|
"account_type": "equity",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Owner's initial investment in the business"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Retained Earnings",
|
||||||
|
"number": "3002",
|
||||||
|
"account_type": "equity",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Accumulated profits retained in the business"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Revenue",
|
||||||
|
"code": "4000",
|
||||||
|
"chart_of_account_type": "REVENUE",
|
||||||
|
"parent_code": null,
|
||||||
|
"is_system": true,
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "Sales Revenue",
|
||||||
|
"number": "4001",
|
||||||
|
"account_type": "revenue",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Revenue from product sales"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Service Revenue",
|
||||||
|
"number": "4002",
|
||||||
|
"account_type": "revenue",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Revenue from services provided"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Other Income",
|
||||||
|
"number": "4003",
|
||||||
|
"account_type": "revenue",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Other sources of income"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cost of Goods Sold",
|
||||||
|
"code": "5000",
|
||||||
|
"chart_of_account_type": "EXPENSE",
|
||||||
|
"parent_code": null,
|
||||||
|
"is_system": true,
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "Raw Materials Cost",
|
||||||
|
"number": "5001",
|
||||||
|
"account_type": "expense",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Cost of raw materials used in production"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Direct Labor Cost",
|
||||||
|
"number": "5002",
|
||||||
|
"account_type": "expense",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Direct labor costs for production"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Manufacturing Overhead",
|
||||||
|
"number": "5003",
|
||||||
|
"account_type": "expense",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Manufacturing overhead costs"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Operating Expenses",
|
||||||
|
"code": "6000",
|
||||||
|
"chart_of_account_type": "EXPENSE",
|
||||||
|
"parent_code": null,
|
||||||
|
"is_system": true,
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"name": "Rent Expense",
|
||||||
|
"number": "6001",
|
||||||
|
"account_type": "expense",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Rent for business premises"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Utilities Expense",
|
||||||
|
"number": "6002",
|
||||||
|
"account_type": "expense",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Electricity, water, and other utilities"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Salaries & Wages",
|
||||||
|
"number": "6003",
|
||||||
|
"account_type": "expense",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Employee salaries and wages"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Marketing Expense",
|
||||||
|
"number": "6004",
|
||||||
|
"account_type": "expense",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Marketing and advertising expenses"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Office Supplies",
|
||||||
|
"number": "6005",
|
||||||
|
"account_type": "expense",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Office supplies and stationery"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Professional Services",
|
||||||
|
"number": "6006",
|
||||||
|
"account_type": "expense",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Legal, accounting, and consulting fees"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Insurance Expense",
|
||||||
|
"number": "6007",
|
||||||
|
"account_type": "expense",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Business insurance premiums"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Depreciation Expense",
|
||||||
|
"number": "6008",
|
||||||
|
"account_type": "expense",
|
||||||
|
"opening_balance": 0.00,
|
||||||
|
"description": "Depreciation of fixed assets"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -15,30 +15,33 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RequestEntity = "request"
|
RequestEntity = "request"
|
||||||
UserServiceEntity = "user_service"
|
UserServiceEntity = "user_service"
|
||||||
OrganizationServiceEntity = "organization_service"
|
OrganizationServiceEntity = "organization_service"
|
||||||
CategoryServiceEntity = "category_service"
|
CategoryServiceEntity = "category_service"
|
||||||
ProductServiceEntity = "product_service"
|
ProductServiceEntity = "product_service"
|
||||||
ProductVariantServiceEntity = "product_variant_service"
|
ProductVariantServiceEntity = "product_variant_service"
|
||||||
InventoryServiceEntity = "inventory_service"
|
InventoryServiceEntity = "inventory_service"
|
||||||
OrderServiceEntity = "order_service"
|
OrderServiceEntity = "order_service"
|
||||||
CustomerServiceEntity = "customer_service"
|
CustomerServiceEntity = "customer_service"
|
||||||
UserValidatorEntity = "user_validator"
|
UserValidatorEntity = "user_validator"
|
||||||
AuthHandlerEntity = "auth_handler"
|
AuthHandlerEntity = "auth_handler"
|
||||||
UserHandlerEntity = "user_handler"
|
UserHandlerEntity = "user_handler"
|
||||||
CategoryHandlerEntity = "category_handler"
|
CategoryHandlerEntity = "category_handler"
|
||||||
ProductHandlerEntity = "product_handler"
|
ProductHandlerEntity = "product_handler"
|
||||||
ProductVariantHandlerEntity = "product_variant_handler"
|
ProductVariantHandlerEntity = "product_variant_handler"
|
||||||
InventoryHandlerEntity = "inventory_handler"
|
InventoryHandlerEntity = "inventory_handler"
|
||||||
OrderValidatorEntity = "order_validator"
|
OrderValidatorEntity = "order_validator"
|
||||||
OrderHandlerEntity = "order_handler"
|
OrderHandlerEntity = "order_handler"
|
||||||
OrganizationValidatorEntity = "organization_validator"
|
OrganizationValidatorEntity = "organization_validator"
|
||||||
OrgHandlerEntity = "organization_handler"
|
OrgHandlerEntity = "organization_handler"
|
||||||
PaymentMethodValidatorEntity = "payment_method_validator"
|
PaymentMethodValidatorEntity = "payment_method_validator"
|
||||||
PaymentMethodHandlerEntity = "payment_method_handler"
|
PaymentMethodHandlerEntity = "payment_method_handler"
|
||||||
OutletServiceEntity = "outlet_service"
|
OutletServiceEntity = "outlet_service"
|
||||||
TableEntity = "table"
|
VendorServiceEntity = "vendor_service"
|
||||||
|
PurchaseOrderServiceEntity = "purchase_order_service"
|
||||||
|
IngredientUnitConverterServiceEntity = "ingredient_unit_converter_service"
|
||||||
|
TableEntity = "table"
|
||||||
)
|
)
|
||||||
|
|
||||||
var HttpErrorMap = map[string]int{
|
var HttpErrorMap = map[string]int{
|
||||||
|
|||||||
19
internal/contract/account_contract.go
Normal file
19
internal/contract/account_contract.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountContract interface {
|
||||||
|
CreateAccount(ctx context.Context, req *CreateAccountRequest) (*AccountResponse, error)
|
||||||
|
GetAccountByID(ctx context.Context, id uuid.UUID) (*AccountResponse, error)
|
||||||
|
UpdateAccount(ctx context.Context, id uuid.UUID, req *UpdateAccountRequest) (*AccountResponse, error)
|
||||||
|
DeleteAccount(ctx context.Context, id uuid.UUID) error
|
||||||
|
ListAccounts(ctx context.Context, req *ListAccountsRequest) ([]AccountResponse, int, error)
|
||||||
|
GetAccountsByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]AccountResponse, error)
|
||||||
|
GetAccountsByChartOfAccount(ctx context.Context, chartOfAccountID uuid.UUID) ([]AccountResponse, error)
|
||||||
|
UpdateAccountBalance(ctx context.Context, id uuid.UUID, req *UpdateAccountBalanceRequest) error
|
||||||
|
GetAccountBalance(ctx context.Context, id uuid.UUID) (float64, error)
|
||||||
|
}
|
||||||
57
internal/contract/account_request.go
Normal file
57
internal/contract/account_request.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateAccountRequest struct {
|
||||||
|
ChartOfAccountID uuid.UUID `json:"chart_of_account_id" validate:"required"`
|
||||||
|
Name string `json:"name" validate:"required,min=1,max=255"`
|
||||||
|
Number string `json:"number" validate:"required,min=1,max=50"`
|
||||||
|
AccountType string `json:"account_type" validate:"required,oneof=cash wallet bank credit debit asset liability equity revenue expense"`
|
||||||
|
OpeningBalance float64 `json:"opening_balance"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateAccountRequest struct {
|
||||||
|
ChartOfAccountID *uuid.UUID `json:"chart_of_account_id"`
|
||||||
|
Name *string `json:"name" validate:"omitempty,min=1,max=255"`
|
||||||
|
Number *string `json:"number" validate:"omitempty,min=1,max=50"`
|
||||||
|
AccountType *string `json:"account_type" validate:"omitempty,oneof=cash wallet bank credit debit asset liability equity revenue expense"`
|
||||||
|
OpeningBalance *float64 `json:"opening_balance"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
IsActive *bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
|
OutletID *uuid.UUID `json:"outlet_id"`
|
||||||
|
ChartOfAccountID uuid.UUID `json:"chart_of_account_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Number string `json:"number"`
|
||||||
|
AccountType string `json:"account_type"`
|
||||||
|
OpeningBalance float64 `json:"opening_balance"`
|
||||||
|
CurrentBalance float64 `json:"current_balance"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
IsSystem bool `json:"is_system"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
ChartOfAccount *ChartOfAccountResponse `json:"chart_of_account,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListAccountsRequest struct {
|
||||||
|
OrganizationID *uuid.UUID `form:"organization_id"`
|
||||||
|
OutletID *uuid.UUID `form:"outlet_id"`
|
||||||
|
ChartOfAccountID *uuid.UUID `form:"chart_of_account_id"`
|
||||||
|
AccountType *string `form:"account_type"`
|
||||||
|
IsActive *bool `form:"is_active"`
|
||||||
|
IsSystem *bool `form:"is_system"`
|
||||||
|
Page int `form:"page,default=1"`
|
||||||
|
Limit int `form:"limit,default=10"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateAccountBalanceRequest struct {
|
||||||
|
Amount float64 `json:"amount" binding:"required"`
|
||||||
|
}
|
||||||
17
internal/contract/chart_of_account_contract.go
Normal file
17
internal/contract/chart_of_account_contract.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChartOfAccountContract interface {
|
||||||
|
CreateChartOfAccount(ctx context.Context, req *CreateChartOfAccountRequest) (*ChartOfAccountResponse, error)
|
||||||
|
GetChartOfAccountByID(ctx context.Context, id uuid.UUID) (*ChartOfAccountResponse, error)
|
||||||
|
UpdateChartOfAccount(ctx context.Context, id uuid.UUID, req *UpdateChartOfAccountRequest) (*ChartOfAccountResponse, error)
|
||||||
|
DeleteChartOfAccount(ctx context.Context, id uuid.UUID) error
|
||||||
|
ListChartOfAccounts(ctx context.Context, req *ListChartOfAccountsRequest) ([]ChartOfAccountResponse, int, error)
|
||||||
|
GetChartOfAccountsByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]ChartOfAccountResponse, error)
|
||||||
|
GetChartOfAccountsByType(ctx context.Context, organizationID uuid.UUID, chartOfAccountTypeID uuid.UUID, outletID *uuid.UUID) ([]ChartOfAccountResponse, error)
|
||||||
|
}
|
||||||
51
internal/contract/chart_of_account_request.go
Normal file
51
internal/contract/chart_of_account_request.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateChartOfAccountRequest struct {
|
||||||
|
ChartOfAccountTypeID uuid.UUID `json:"chart_of_account_type_id" validate:"required"`
|
||||||
|
ParentID *uuid.UUID `json:"parent_id"`
|
||||||
|
Name string `json:"name" validate:"required,min=1,max=255"`
|
||||||
|
Code string `json:"code" validate:"required,min=1,max=20"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateChartOfAccountRequest struct {
|
||||||
|
ChartOfAccountTypeID *uuid.UUID `json:"chart_of_account_type_id"`
|
||||||
|
ParentID *uuid.UUID `json:"parent_id"`
|
||||||
|
Name *string `json:"name" validate:"omitempty,min=1,max=255"`
|
||||||
|
Code *string `json:"code" validate:"omitempty,min=1,max=20"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
IsActive *bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartOfAccountResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
|
OutletID *uuid.UUID `json:"outlet_id"`
|
||||||
|
ChartOfAccountTypeID uuid.UUID `json:"chart_of_account_type_id"`
|
||||||
|
ParentID *uuid.UUID `json:"parent_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
IsSystem bool `json:"is_system"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
ChartOfAccountType *ChartOfAccountTypeResponse `json:"chart_of_account_type,omitempty"`
|
||||||
|
Parent *ChartOfAccountResponse `json:"parent,omitempty"`
|
||||||
|
Children []ChartOfAccountResponse `json:"children,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListChartOfAccountsRequest struct {
|
||||||
|
OrganizationID *uuid.UUID `form:"organization_id"`
|
||||||
|
OutletID *uuid.UUID `form:"outlet_id"`
|
||||||
|
ChartOfAccountTypeID *uuid.UUID `form:"chart_of_account_type_id"`
|
||||||
|
ParentID *uuid.UUID `form:"parent_id"`
|
||||||
|
IsActive *bool `form:"is_active"`
|
||||||
|
IsSystem *bool `form:"is_system"`
|
||||||
|
Page int `form:"page,default=1"`
|
||||||
|
Limit int `form:"limit,default=10"`
|
||||||
|
}
|
||||||
15
internal/contract/chart_of_account_type_contract.go
Normal file
15
internal/contract/chart_of_account_type_contract.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChartOfAccountTypeContract interface {
|
||||||
|
CreateChartOfAccountType(ctx context.Context, req *CreateChartOfAccountTypeRequest) (*ChartOfAccountTypeResponse, error)
|
||||||
|
GetChartOfAccountTypeByID(ctx context.Context, id uuid.UUID) (*ChartOfAccountTypeResponse, error)
|
||||||
|
UpdateChartOfAccountType(ctx context.Context, id uuid.UUID, req *UpdateChartOfAccountTypeRequest) (*ChartOfAccountTypeResponse, error)
|
||||||
|
DeleteChartOfAccountType(ctx context.Context, id uuid.UUID) error
|
||||||
|
ListChartOfAccountTypes(ctx context.Context, filters map[string]interface{}, page, limit int) ([]ChartOfAccountTypeResponse, int, error)
|
||||||
|
}
|
||||||
28
internal/contract/chart_of_account_type_request.go
Normal file
28
internal/contract/chart_of_account_type_request.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateChartOfAccountTypeRequest struct {
|
||||||
|
Name string `json:"name" validate:"required,min=1,max=100"`
|
||||||
|
Code string `json:"code" validate:"required,min=1,max=10"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateChartOfAccountTypeRequest struct {
|
||||||
|
Name *string `json:"name" validate:"omitempty,min=1,max=100"`
|
||||||
|
Code *string `json:"code" validate:"omitempty,min=1,max=10"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
IsActive *bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartOfAccountTypeResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
CreatedAt string `json:"created_at"`
|
||||||
|
UpdatedAt string `json:"updated_at"`
|
||||||
|
}
|
||||||
76
internal/contract/ingredient_unit_converter_contract.go
Normal file
76
internal/contract/ingredient_unit_converter_contract.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Request DTOs
|
||||||
|
type CreateIngredientUnitConverterRequest struct {
|
||||||
|
IngredientID uuid.UUID `json:"ingredient_id" validate:"required"`
|
||||||
|
FromUnitID uuid.UUID `json:"from_unit_id" validate:"required"`
|
||||||
|
ToUnitID uuid.UUID `json:"to_unit_id" validate:"required"`
|
||||||
|
ConversionFactor float64 `json:"conversion_factor" validate:"required,gt=0"`
|
||||||
|
IsActive *bool `json:"is_active,omitempty" validate:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateIngredientUnitConverterRequest struct {
|
||||||
|
FromUnitID *uuid.UUID `json:"from_unit_id,omitempty" validate:"omitempty"`
|
||||||
|
ToUnitID *uuid.UUID `json:"to_unit_id,omitempty" validate:"omitempty"`
|
||||||
|
ConversionFactor *float64 `json:"conversion_factor,omitempty" validate:"omitempty,gt=0"`
|
||||||
|
IsActive *bool `json:"is_active,omitempty" validate:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListIngredientUnitConvertersRequest struct {
|
||||||
|
IngredientID *uuid.UUID `json:"ingredient_id,omitempty"`
|
||||||
|
FromUnitID *uuid.UUID `json:"from_unit_id,omitempty"`
|
||||||
|
ToUnitID *uuid.UUID `json:"to_unit_id,omitempty"`
|
||||||
|
IsActive *bool `json:"is_active,omitempty"`
|
||||||
|
Search string `json:"search,omitempty"`
|
||||||
|
Page int `json:"page" validate:"required,min=1"`
|
||||||
|
Limit int `json:"limit" validate:"required,min=1,max=100"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConvertUnitRequest struct {
|
||||||
|
IngredientID uuid.UUID `json:"ingredient_id" validate:"required"`
|
||||||
|
FromUnitID uuid.UUID `json:"from_unit_id" validate:"required"`
|
||||||
|
ToUnitID uuid.UUID `json:"to_unit_id" validate:"required"`
|
||||||
|
Quantity float64 `json:"quantity" validate:"required,gt=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response DTOs
|
||||||
|
type IngredientUnitConverterResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
|
FromUnitID uuid.UUID `json:"from_unit_id"`
|
||||||
|
ToUnitID uuid.UUID `json:"to_unit_id"`
|
||||||
|
ConversionFactor float64 `json:"conversion_factor"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
CreatedBy uuid.UUID `json:"created_by"`
|
||||||
|
UpdatedBy uuid.UUID `json:"updated_by"`
|
||||||
|
Ingredient *IngredientResponse `json:"ingredient,omitempty"`
|
||||||
|
FromUnit *UnitResponse `json:"from_unit,omitempty"`
|
||||||
|
ToUnit *UnitResponse `json:"to_unit,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConvertUnitResponse struct {
|
||||||
|
FromQuantity float64 `json:"from_quantity"`
|
||||||
|
FromUnit *UnitResponse `json:"from_unit"`
|
||||||
|
ToQuantity float64 `json:"to_quantity"`
|
||||||
|
ToUnit *UnitResponse `json:"to_unit"`
|
||||||
|
ConversionFactor float64 `json:"conversion_factor"`
|
||||||
|
Ingredient *IngredientResponse `json:"ingredient,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListIngredientUnitConvertersResponse struct {
|
||||||
|
Converters []IngredientUnitConverterResponse `json:"converters"`
|
||||||
|
TotalCount int `json:"total_count"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
TotalPages int `json:"total_pages"`
|
||||||
|
}
|
||||||
|
|
||||||
117
internal/contract/purchase_order_contract.go
Normal file
117
internal/contract/purchase_order_contract.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreatePurchaseOrderRequest struct {
|
||||||
|
VendorID uuid.UUID `json:"vendor_id" validate:"required"`
|
||||||
|
PONumber string `json:"po_number" validate:"required,min=1,max=50"`
|
||||||
|
TransactionDate time.Time `json:"transaction_date" validate:"required"`
|
||||||
|
DueDate time.Time `json:"due_date" validate:"required"`
|
||||||
|
Reference *string `json:"reference,omitempty" validate:"omitempty,max=100"`
|
||||||
|
Status *string `json:"status,omitempty" validate:"omitempty,oneof=draft sent approved received cancelled"`
|
||||||
|
Message *string `json:"message,omitempty" validate:"omitempty"`
|
||||||
|
Items []CreatePurchaseOrderItemRequest `json:"items" validate:"required,min=1,dive"`
|
||||||
|
AttachmentFileIDs []uuid.UUID `json:"attachment_file_ids,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreatePurchaseOrderItemRequest struct {
|
||||||
|
IngredientID uuid.UUID `json:"ingredient_id" validate:"required"`
|
||||||
|
Description *string `json:"description,omitempty" validate:"omitempty"`
|
||||||
|
Quantity float64 `json:"quantity" validate:"required,gt=0"`
|
||||||
|
UnitID uuid.UUID `json:"unit_id" validate:"required"`
|
||||||
|
Amount float64 `json:"amount" validate:"required,gte=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdatePurchaseOrderRequest struct {
|
||||||
|
VendorID *uuid.UUID `json:"vendor_id,omitempty" validate:"omitempty"`
|
||||||
|
PONumber *string `json:"po_number,omitempty" validate:"omitempty,min=1,max=50"`
|
||||||
|
TransactionDate *time.Time `json:"transaction_date,omitempty" validate:"omitempty"`
|
||||||
|
DueDate *time.Time `json:"due_date,omitempty" validate:"omitempty"`
|
||||||
|
Reference *string `json:"reference,omitempty" validate:"omitempty,max=100"`
|
||||||
|
Status *string `json:"status,omitempty" validate:"omitempty,oneof=draft sent approved received cancelled"`
|
||||||
|
Message *string `json:"message,omitempty" validate:"omitempty"`
|
||||||
|
Items []UpdatePurchaseOrderItemRequest `json:"items,omitempty" validate:"omitempty,dive"`
|
||||||
|
AttachmentFileIDs []uuid.UUID `json:"attachment_file_ids,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdatePurchaseOrderItemRequest struct {
|
||||||
|
ID *uuid.UUID `json:"id,omitempty"` // For existing items
|
||||||
|
IngredientID *uuid.UUID `json:"ingredient_id,omitempty" validate:"omitempty"`
|
||||||
|
Description *string `json:"description,omitempty" validate:"omitempty"`
|
||||||
|
Quantity *float64 `json:"quantity,omitempty" validate:"omitempty,gt=0"`
|
||||||
|
UnitID *uuid.UUID `json:"unit_id,omitempty" validate:"omitempty"`
|
||||||
|
Amount *float64 `json:"amount,omitempty" validate:"omitempty,gte=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PurchaseOrderResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
|
VendorID uuid.UUID `json:"vendor_id"`
|
||||||
|
PONumber string `json:"po_number"`
|
||||||
|
TransactionDate time.Time `json:"transaction_date"`
|
||||||
|
DueDate time.Time `json:"due_date"`
|
||||||
|
Reference *string `json:"reference"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message *string `json:"message"`
|
||||||
|
TotalAmount float64 `json:"total_amount"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Vendor *VendorResponse `json:"vendor,omitempty"`
|
||||||
|
Items []PurchaseOrderItemResponse `json:"items,omitempty"`
|
||||||
|
Attachments []PurchaseOrderAttachmentResponse `json:"attachments,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PurchaseOrderItemResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
PurchaseOrderID uuid.UUID `json:"purchase_order_id"`
|
||||||
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
Quantity float64 `json:"quantity"`
|
||||||
|
UnitID uuid.UUID `json:"unit_id"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Ingredient *IngredientResponse `json:"ingredient,omitempty"`
|
||||||
|
Unit *UnitResponse `json:"unit,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PurchaseOrderAttachmentResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
PurchaseOrderID uuid.UUID `json:"purchase_order_id"`
|
||||||
|
FileID uuid.UUID `json:"file_id"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
File *FileResponse `json:"file,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListPurchaseOrdersRequest struct {
|
||||||
|
Page int `json:"page" validate:"min=1"`
|
||||||
|
Limit int `json:"limit" validate:"min=1,max=100"`
|
||||||
|
Search string `json:"search,omitempty"`
|
||||||
|
Status string `json:"status,omitempty" validate:"omitempty,oneof=draft sent approved received cancelled"`
|
||||||
|
VendorID *uuid.UUID `json:"vendor_id,omitempty"`
|
||||||
|
StartDate *time.Time `json:"start_date,omitempty"`
|
||||||
|
EndDate *time.Time `json:"end_date,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListPurchaseOrdersResponse struct {
|
||||||
|
PurchaseOrders []PurchaseOrderResponse `json:"purchase_orders"`
|
||||||
|
TotalCount int `json:"total_count"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
TotalPages int `json:"total_pages"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper types for ingredient and unit responses
|
||||||
|
type IngredientResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnitResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
62
internal/contract/vendor_contract.go
Normal file
62
internal/contract/vendor_contract.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package contract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateVendorRequest struct {
|
||||||
|
Name string `json:"name" validate:"required,min=1,max=255"`
|
||||||
|
Email *string `json:"email,omitempty" validate:"omitempty,email"`
|
||||||
|
PhoneNumber *string `json:"phone_number,omitempty" validate:"omitempty"`
|
||||||
|
Address *string `json:"address,omitempty" validate:"omitempty"`
|
||||||
|
ContactPerson *string `json:"contact_person,omitempty" validate:"omitempty,max=255"`
|
||||||
|
TaxNumber *string `json:"tax_number,omitempty" validate:"omitempty,max=50"`
|
||||||
|
PaymentTerms *string `json:"payment_terms,omitempty" validate:"omitempty,max=100"`
|
||||||
|
Notes *string `json:"notes,omitempty" validate:"omitempty"`
|
||||||
|
IsActive *bool `json:"is_active,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateVendorRequest struct {
|
||||||
|
Name *string `json:"name,omitempty" validate:"omitempty,min=1,max=255"`
|
||||||
|
Email *string `json:"email,omitempty" validate:"omitempty,email"`
|
||||||
|
PhoneNumber *string `json:"phone_number,omitempty" validate:"omitempty"`
|
||||||
|
Address *string `json:"address,omitempty" validate:"omitempty"`
|
||||||
|
ContactPerson *string `json:"contact_person,omitempty" validate:"omitempty,max=255"`
|
||||||
|
TaxNumber *string `json:"tax_number,omitempty" validate:"omitempty,max=50"`
|
||||||
|
PaymentTerms *string `json:"payment_terms,omitempty" validate:"omitempty,max=100"`
|
||||||
|
Notes *string `json:"notes,omitempty" validate:"omitempty"`
|
||||||
|
IsActive *bool `json:"is_active,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VendorResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email *string `json:"email"`
|
||||||
|
PhoneNumber *string `json:"phone_number"`
|
||||||
|
Address *string `json:"address"`
|
||||||
|
ContactPerson *string `json:"contact_person"`
|
||||||
|
TaxNumber *string `json:"tax_number"`
|
||||||
|
PaymentTerms *string `json:"payment_terms"`
|
||||||
|
Notes *string `json:"notes"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListVendorsRequest struct {
|
||||||
|
Page int `json:"page" validate:"min=1"`
|
||||||
|
Limit int `json:"limit" validate:"min=1,max=100"`
|
||||||
|
Search string `json:"search,omitempty"`
|
||||||
|
IsActive *bool `json:"is_active,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListVendorsResponse struct {
|
||||||
|
Vendors []VendorResponse `json:"vendors"`
|
||||||
|
TotalCount int `json:"total_count"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
TotalPages int `json:"total_pages"`
|
||||||
|
}
|
||||||
55
internal/entities/account.go
Normal file
55
internal/entities/account.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
AccountTypeCash AccountType = "cash"
|
||||||
|
AccountTypeWallet AccountType = "wallet"
|
||||||
|
AccountTypeBank AccountType = "bank"
|
||||||
|
AccountTypeCredit AccountType = "credit"
|
||||||
|
AccountTypeDebit AccountType = "debit"
|
||||||
|
AccountTypeAsset AccountType = "asset"
|
||||||
|
AccountTypeLiability AccountType = "liability"
|
||||||
|
AccountTypeEquity AccountType = "equity"
|
||||||
|
AccountTypeRevenue AccountType = "revenue"
|
||||||
|
AccountTypeExpense AccountType = "expense"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Account struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
OrganizationID uuid.UUID `gorm:"type:uuid;not null;index" json:"organization_id" validate:"required"`
|
||||||
|
OutletID *uuid.UUID `gorm:"type:uuid;index" json:"outlet_id"`
|
||||||
|
ChartOfAccountID uuid.UUID `gorm:"type:uuid;not null;index" json:"chart_of_account_id" validate:"required"`
|
||||||
|
Name string `gorm:"not null;size:255" json:"name" validate:"required,min=1,max=255"`
|
||||||
|
Number string `gorm:"not null;size:50" json:"number" validate:"required,min=1,max=50"`
|
||||||
|
AccountType AccountType `gorm:"not null;size:20" json:"account_type" validate:"required,oneof=cash wallet bank credit debit asset liability equity revenue expense"`
|
||||||
|
OpeningBalance float64 `gorm:"type:decimal(15,2);default:0.00" json:"opening_balance"`
|
||||||
|
CurrentBalance float64 `gorm:"type:decimal(15,2);default:0.00" json:"current_balance"`
|
||||||
|
Description *string `gorm:"type:text" json:"description"`
|
||||||
|
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||||
|
IsSystem bool `gorm:"default:false" json:"is_system"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
|
||||||
|
Organization Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"`
|
||||||
|
Outlet *Outlet `gorm:"foreignKey:OutletID" json:"outlet,omitempty"`
|
||||||
|
ChartOfAccount ChartOfAccount `gorm:"foreignKey:ChartOfAccountID" json:"chart_of_account,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Account) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if a.ID == uuid.Nil {
|
||||||
|
a.ID = uuid.New()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Account) TableName() string {
|
||||||
|
return "accounts"
|
||||||
|
}
|
||||||
41
internal/entities/chart_of_account.go
Normal file
41
internal/entities/chart_of_account.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChartOfAccount struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
OrganizationID uuid.UUID `gorm:"type:uuid;not null;index" json:"organization_id" validate:"required"`
|
||||||
|
OutletID *uuid.UUID `gorm:"type:uuid;index" json:"outlet_id"`
|
||||||
|
ChartOfAccountTypeID uuid.UUID `gorm:"type:uuid;not null;index" json:"chart_of_account_type_id" validate:"required"`
|
||||||
|
ParentID *uuid.UUID `gorm:"type:uuid;index" json:"parent_id"`
|
||||||
|
Name string `gorm:"not null;size:255" json:"name" validate:"required,min=1,max=255"`
|
||||||
|
Code string `gorm:"not null;size:20" json:"code" validate:"required,min=1,max=20"`
|
||||||
|
Description *string `gorm:"type:text" json:"description"`
|
||||||
|
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||||
|
IsSystem bool `gorm:"default:false" json:"is_system"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
|
||||||
|
Organization Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"`
|
||||||
|
Outlet *Outlet `gorm:"foreignKey:OutletID" json:"outlet,omitempty"`
|
||||||
|
ChartOfAccountType ChartOfAccountType `gorm:"foreignKey:ChartOfAccountTypeID" json:"chart_of_account_type,omitempty"`
|
||||||
|
Parent *ChartOfAccount `gorm:"foreignKey:ParentID" json:"parent,omitempty"`
|
||||||
|
Children []ChartOfAccount `gorm:"foreignKey:ParentID" json:"children,omitempty"`
|
||||||
|
Accounts []Account `gorm:"foreignKey:ChartOfAccountID" json:"accounts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChartOfAccount) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if c.ID == uuid.Nil {
|
||||||
|
c.ID = uuid.New()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ChartOfAccount) TableName() string {
|
||||||
|
return "chart_of_accounts"
|
||||||
|
}
|
||||||
31
internal/entities/chart_of_account_type.go
Normal file
31
internal/entities/chart_of_account_type.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChartOfAccountType struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
Name string `gorm:"not null;size:100" json:"name" validate:"required,min=1,max=100"`
|
||||||
|
Code string `gorm:"not null;size:10;unique" json:"code" validate:"required,min=1,max=10"`
|
||||||
|
Description *string `gorm:"type:text" json:"description"`
|
||||||
|
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
|
||||||
|
ChartOfAccounts []ChartOfAccount `gorm:"foreignKey:ChartOfAccountTypeID" json:"chart_of_accounts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ChartOfAccountType) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if c.ID == uuid.Nil {
|
||||||
|
c.ID = uuid.New()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ChartOfAccountType) TableName() string {
|
||||||
|
return "chart_of_account_types"
|
||||||
|
}
|
||||||
@ -18,6 +18,11 @@ func GetAllEntities() []interface{} {
|
|||||||
&Payment{},
|
&Payment{},
|
||||||
&Customer{},
|
&Customer{},
|
||||||
&Table{},
|
&Table{},
|
||||||
|
&Vendor{},
|
||||||
|
&PurchaseOrder{},
|
||||||
|
&PurchaseOrderItem{},
|
||||||
|
&PurchaseOrderAttachment{},
|
||||||
|
&IngredientUnitConverter{},
|
||||||
// Analytics entities are not database tables, they are query results
|
// Analytics entities are not database tables, they are query results
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
42
internal/entities/ingredient_unit_converter.go
Normal file
42
internal/entities/ingredient_unit_converter.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IngredientUnitConverter struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
OrganizationID uuid.UUID `gorm:"type:uuid;not null" json:"organization_id"`
|
||||||
|
IngredientID uuid.UUID `gorm:"type:uuid;not null" json:"ingredient_id"`
|
||||||
|
FromUnitID uuid.UUID `gorm:"type:uuid;not null" json:"from_unit_id"`
|
||||||
|
ToUnitID uuid.UUID `gorm:"type:uuid;not null" json:"to_unit_id"`
|
||||||
|
ConversionFactor float64 `gorm:"type:decimal(15,6);not null" json:"conversion_factor"`
|
||||||
|
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
CreatedBy uuid.UUID `gorm:"type:uuid;not null" json:"created_by"`
|
||||||
|
UpdatedBy uuid.UUID `gorm:"type:uuid;not null" json:"updated_by"`
|
||||||
|
|
||||||
|
// Relationships
|
||||||
|
Organization *Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"`
|
||||||
|
Ingredient *Ingredient `gorm:"foreignKey:IngredientID" json:"ingredient,omitempty"`
|
||||||
|
FromUnit *Unit `gorm:"foreignKey:FromUnitID" json:"from_unit,omitempty"`
|
||||||
|
ToUnit *Unit `gorm:"foreignKey:ToUnitID" json:"to_unit,omitempty"`
|
||||||
|
CreatedByUser *User `gorm:"foreignKey:CreatedBy" json:"created_by_user,omitempty"`
|
||||||
|
UpdatedByUser *User `gorm:"foreignKey:UpdatedBy" json:"updated_by_user,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (IngredientUnitConverter) TableName() string {
|
||||||
|
return "ingredient_unit_converters"
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeforeCreate hook to set default values
|
||||||
|
func (iuc *IngredientUnitConverter) BeforeCreate() error {
|
||||||
|
if iuc.ID == uuid.Nil {
|
||||||
|
iuc.ID = uuid.New()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
91
internal/entities/purchase_order.go
Normal file
91
internal/entities/purchase_order.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PurchaseOrder struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
OrganizationID uuid.UUID `gorm:"type:uuid;not null" json:"organization_id" validate:"required"`
|
||||||
|
VendorID uuid.UUID `gorm:"type:uuid;not null" json:"vendor_id" validate:"required"`
|
||||||
|
PONumber string `gorm:"not null;size:50" json:"po_number" validate:"required,min=1,max=50"`
|
||||||
|
TransactionDate time.Time `gorm:"type:date;not null" json:"transaction_date" validate:"required"`
|
||||||
|
DueDate time.Time `gorm:"type:date;not null" json:"due_date" validate:"required"`
|
||||||
|
Reference *string `gorm:"size:100" json:"reference" validate:"omitempty,max=100"`
|
||||||
|
Status string `gorm:"not null;size:20;default:'draft'" json:"status" validate:"required,oneof=draft sent approved received cancelled"`
|
||||||
|
Message *string `gorm:"type:text" json:"message" validate:"omitempty"`
|
||||||
|
TotalAmount float64 `gorm:"type:decimal(15,2);not null;default:0" json:"total_amount"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
|
||||||
|
Organization *Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"`
|
||||||
|
Vendor *Vendor `gorm:"foreignKey:VendorID" json:"vendor,omitempty"`
|
||||||
|
Items []PurchaseOrderItem `gorm:"foreignKey:PurchaseOrderID" json:"items,omitempty"`
|
||||||
|
Attachments []PurchaseOrderAttachment `gorm:"foreignKey:PurchaseOrderID" json:"attachments,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (po *PurchaseOrder) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if po.ID == uuid.Nil {
|
||||||
|
id := uuid.New()
|
||||||
|
po.ID = id
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (PurchaseOrder) TableName() string {
|
||||||
|
return "purchase_orders"
|
||||||
|
}
|
||||||
|
|
||||||
|
type PurchaseOrderItem struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
PurchaseOrderID uuid.UUID `gorm:"type:uuid;not null" json:"purchase_order_id" validate:"required"`
|
||||||
|
IngredientID uuid.UUID `gorm:"type:uuid;not null" json:"ingredient_id" validate:"required"`
|
||||||
|
Description *string `gorm:"type:text" json:"description" validate:"omitempty"`
|
||||||
|
Quantity float64 `gorm:"type:decimal(10,3);not null" json:"quantity" validate:"required,gt=0"`
|
||||||
|
UnitID uuid.UUID `gorm:"type:uuid;not null" json:"unit_id" validate:"required"`
|
||||||
|
Amount float64 `gorm:"type:decimal(15,2);not null" json:"amount" validate:"required,gte=0"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
|
||||||
|
PurchaseOrder *PurchaseOrder `gorm:"foreignKey:PurchaseOrderID" json:"purchase_order,omitempty"`
|
||||||
|
Ingredient *Ingredient `gorm:"foreignKey:IngredientID" json:"ingredient,omitempty"`
|
||||||
|
Unit *Unit `gorm:"foreignKey:UnitID" json:"unit,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (poi *PurchaseOrderItem) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if poi.ID == uuid.Nil {
|
||||||
|
id := uuid.New()
|
||||||
|
poi.ID = id
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (PurchaseOrderItem) TableName() string {
|
||||||
|
return "purchase_order_items"
|
||||||
|
}
|
||||||
|
|
||||||
|
type PurchaseOrderAttachment struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
PurchaseOrderID uuid.UUID `gorm:"type:uuid;not null" json:"purchase_order_id" validate:"required"`
|
||||||
|
FileID uuid.UUID `gorm:"type:uuid;not null" json:"file_id" validate:"required"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
|
||||||
|
PurchaseOrder *PurchaseOrder `gorm:"foreignKey:PurchaseOrderID" json:"purchase_order,omitempty"`
|
||||||
|
File *File `gorm:"foreignKey:FileID" json:"file,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (poa *PurchaseOrderAttachment) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if poa.ID == uuid.Nil {
|
||||||
|
id := uuid.New()
|
||||||
|
poa.ID = id
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (PurchaseOrderAttachment) TableName() string {
|
||||||
|
return "purchase_order_attachments"
|
||||||
|
}
|
||||||
39
internal/entities/vendor.go
Normal file
39
internal/entities/vendor.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package entities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Vendor struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
|
OrganizationID uuid.UUID `gorm:"type:uuid;not null" json:"organization_id" validate:"required"`
|
||||||
|
Name string `gorm:"not null;size:255" json:"name" validate:"required,min=1,max=255"`
|
||||||
|
Email *string `gorm:"size:255" json:"email" validate:"omitempty,email"`
|
||||||
|
PhoneNumber *string `gorm:"size:20" json:"phone_number" validate:"omitempty"`
|
||||||
|
Address *string `gorm:"type:text" json:"address" validate:"omitempty"`
|
||||||
|
ContactPerson *string `gorm:"size:255" json:"contact_person" validate:"omitempty,max=255"`
|
||||||
|
TaxNumber *string `gorm:"size:50" json:"tax_number" validate:"omitempty,max=50"`
|
||||||
|
PaymentTerms *string `gorm:"size:100" json:"payment_terms" validate:"omitempty,max=100"`
|
||||||
|
Notes *string `gorm:"type:text" json:"notes" validate:"omitempty"`
|
||||||
|
IsActive bool `gorm:"not null;default:true" json:"is_active"`
|
||||||
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||||
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||||
|
|
||||||
|
Organization *Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Vendor) BeforeCreate(tx *gorm.DB) error {
|
||||||
|
if v.ID == uuid.Nil {
|
||||||
|
id := uuid.New()
|
||||||
|
v.ID = id
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Vendor) TableName() string {
|
||||||
|
return "vendors"
|
||||||
|
}
|
||||||
197
internal/handler/account_handler.go
Normal file
197
internal/handler/account_handler.go
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/util"
|
||||||
|
"apskel-pos-be/internal/validator"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountHandler struct {
|
||||||
|
service contract.AccountContract
|
||||||
|
validator validator.AccountValidator
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccountHandler(service contract.AccountContract, validator validator.AccountValidator) *AccountHandler {
|
||||||
|
return &AccountHandler{
|
||||||
|
service: service,
|
||||||
|
validator: validator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AccountHandler) CreateAccount(c *gin.Context) {
|
||||||
|
var req contract.CreateAccountRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.CreateAccount(c, &req)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "AccountHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AccountHandler) GetAccountByID(c *gin.Context) {
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := uuid.Parse(idStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.GetAccountByID(c, id)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "AccountHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AccountHandler) UpdateAccount(c *gin.Context) {
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := uuid.Parse(idStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req contract.UpdateAccountRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.UpdateAccount(c, id, &req)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "AccountHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AccountHandler) DeleteAccount(c *gin.Context) {
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := uuid.Parse(idStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.service.DeleteAccount(c, id)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(gin.H{"message": "Account deleted successfully"}), "AccountHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AccountHandler) ListAccounts(c *gin.Context) {
|
||||||
|
var req contract.ListAccountsRequest
|
||||||
|
if err := c.ShouldBindQuery(&req); err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, total, err := h.service.ListAccounts(c, &req)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(gin.H{
|
||||||
|
"data": response,
|
||||||
|
"total": total,
|
||||||
|
"page": req.Page,
|
||||||
|
"limit": req.Limit,
|
||||||
|
}), "AccountHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AccountHandler) GetAccountsByOrganization(c *gin.Context) {
|
||||||
|
organizationIDStr := c.Param("organization_id")
|
||||||
|
organizationID, err := uuid.Parse(organizationIDStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid organization ID format"}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var outletID *uuid.UUID
|
||||||
|
if outletIDStr := c.Query("outlet_id"); outletIDStr != "" {
|
||||||
|
if parsedOutletID, err := uuid.Parse(outletIDStr); err == nil {
|
||||||
|
outletID = &parsedOutletID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.GetAccountsByOrganization(c, organizationID, outletID)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "AccountHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AccountHandler) GetAccountsByChartOfAccount(c *gin.Context) {
|
||||||
|
chartOfAccountIDStr := c.Param("chart_of_account_id")
|
||||||
|
chartOfAccountID, err := uuid.Parse(chartOfAccountIDStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid chart of account ID format"}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.GetAccountsByChartOfAccount(c, chartOfAccountID)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "AccountHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AccountHandler) UpdateAccountBalance(c *gin.Context) {
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := uuid.Parse(idStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req contract.UpdateAccountBalanceRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.service.UpdateAccountBalance(c, id, &req)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(gin.H{"message": "Account balance updated successfully"}), "AccountHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *AccountHandler) GetAccountBalance(c *gin.Context) {
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := uuid.Parse(idStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
balance, err := h.service.GetAccountBalance(c, id)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "AccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(gin.H{"balance": balance}), "AccountHandler")
|
||||||
|
}
|
||||||
171
internal/handler/chart_of_account_handler.go
Normal file
171
internal/handler/chart_of_account_handler.go
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/util"
|
||||||
|
"apskel-pos-be/internal/validator"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChartOfAccountHandler struct {
|
||||||
|
service contract.ChartOfAccountContract
|
||||||
|
validator validator.ChartOfAccountValidator
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChartOfAccountHandler(service contract.ChartOfAccountContract, validator validator.ChartOfAccountValidator) *ChartOfAccountHandler {
|
||||||
|
return &ChartOfAccountHandler{
|
||||||
|
service: service,
|
||||||
|
validator: validator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ChartOfAccountHandler) CreateChartOfAccount(c *gin.Context) {
|
||||||
|
var req contract.CreateChartOfAccountRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.CreateChartOfAccount(c, &req)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "ChartOfAccountHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ChartOfAccountHandler) GetChartOfAccountByID(c *gin.Context) {
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := uuid.Parse(idStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "ChartOfAccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.GetChartOfAccountByID(c, id)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "ChartOfAccountHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ChartOfAccountHandler) UpdateChartOfAccount(c *gin.Context) {
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := uuid.Parse(idStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "ChartOfAccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req contract.UpdateChartOfAccountRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.UpdateChartOfAccount(c, id, &req)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "ChartOfAccountHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ChartOfAccountHandler) DeleteChartOfAccount(c *gin.Context) {
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := uuid.Parse(idStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "ChartOfAccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.service.DeleteChartOfAccount(c, id)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(gin.H{"message": "Chart of account deleted successfully"}), "ChartOfAccountHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ChartOfAccountHandler) ListChartOfAccounts(c *gin.Context) {
|
||||||
|
var req contract.ListChartOfAccountsRequest
|
||||||
|
if err := c.ShouldBindQuery(&req); err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, total, err := h.service.ListChartOfAccounts(c, &req)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(gin.H{
|
||||||
|
"data": response,
|
||||||
|
"total": total,
|
||||||
|
"page": req.Page,
|
||||||
|
"limit": req.Limit,
|
||||||
|
}), "ChartOfAccountHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ChartOfAccountHandler) GetChartOfAccountsByOrganization(c *gin.Context) {
|
||||||
|
organizationIDStr := c.Param("organization_id")
|
||||||
|
organizationID, err := uuid.Parse(organizationIDStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid organization ID format"}}), "ChartOfAccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var outletID *uuid.UUID
|
||||||
|
if outletIDStr := c.Query("outlet_id"); outletIDStr != "" {
|
||||||
|
if parsedOutletID, err := uuid.Parse(outletIDStr); err == nil {
|
||||||
|
outletID = &parsedOutletID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.GetChartOfAccountsByOrganization(c, organizationID, outletID)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "ChartOfAccountHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ChartOfAccountHandler) GetChartOfAccountsByType(c *gin.Context) {
|
||||||
|
organizationIDStr := c.Param("organization_id")
|
||||||
|
organizationID, err := uuid.Parse(organizationIDStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid organization ID format"}}), "ChartOfAccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
typeIDStr := c.Param("type_id")
|
||||||
|
typeID, err := uuid.Parse(typeIDStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid type ID format"}}), "ChartOfAccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var outletID *uuid.UUID
|
||||||
|
if outletIDStr := c.Query("outlet_id"); outletIDStr != "" {
|
||||||
|
if parsedOutletID, err := uuid.Parse(outletIDStr); err == nil {
|
||||||
|
outletID = &parsedOutletID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.GetChartOfAccountsByType(c, organizationID, typeID, outletID)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "ChartOfAccountHandler")
|
||||||
|
}
|
||||||
124
internal/handler/chart_of_account_type_handler.go
Normal file
124
internal/handler/chart_of_account_type_handler.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/util"
|
||||||
|
"apskel-pos-be/internal/validator"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChartOfAccountTypeHandler struct {
|
||||||
|
service contract.ChartOfAccountTypeContract
|
||||||
|
validator validator.ChartOfAccountTypeValidator
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChartOfAccountTypeHandler(service contract.ChartOfAccountTypeContract, validator validator.ChartOfAccountTypeValidator) *ChartOfAccountTypeHandler {
|
||||||
|
return &ChartOfAccountTypeHandler{
|
||||||
|
service: service,
|
||||||
|
validator: validator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ChartOfAccountTypeHandler) CreateChartOfAccountType(c *gin.Context) {
|
||||||
|
var req contract.CreateChartOfAccountTypeRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountTypeHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.CreateChartOfAccountType(c, &req)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountTypeHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "ChartOfAccountTypeHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ChartOfAccountTypeHandler) GetChartOfAccountTypeByID(c *gin.Context) {
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := uuid.Parse(idStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "ChartOfAccountTypeHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.GetChartOfAccountTypeByID(c, id)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountTypeHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "ChartOfAccountTypeHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ChartOfAccountTypeHandler) UpdateChartOfAccountType(c *gin.Context) {
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := uuid.Parse(idStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "ChartOfAccountTypeHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req contract.UpdateChartOfAccountTypeRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountTypeHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := h.service.UpdateChartOfAccountType(c, id, &req)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountTypeHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(response), "ChartOfAccountTypeHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ChartOfAccountTypeHandler) DeleteChartOfAccountType(c *gin.Context) {
|
||||||
|
idStr := c.Param("id")
|
||||||
|
id, err := uuid.Parse(idStr)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: "Invalid ID format"}}), "ChartOfAccountTypeHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.service.DeleteChartOfAccountType(c, id)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountTypeHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(gin.H{"message": "Chart of account type deleted successfully"}), "ChartOfAccountTypeHandler")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ChartOfAccountTypeHandler) ListChartOfAccountTypes(c *gin.Context) {
|
||||||
|
// Parse query parameters
|
||||||
|
filters := make(map[string]interface{})
|
||||||
|
|
||||||
|
if isActive := c.Query("is_active"); isActive != "" {
|
||||||
|
if isActiveBool, err := strconv.ParseBool(isActive); err == nil {
|
||||||
|
filters["is_active"] = isActiveBool
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||||
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
||||||
|
|
||||||
|
response, total, err := h.service.ListChartOfAccountTypes(c, filters, page, limit)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{{Cause: err.Error()}}), "ChartOfAccountTypeHandler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(gin.H{
|
||||||
|
"data": response,
|
||||||
|
"total": total,
|
||||||
|
"page": page,
|
||||||
|
"limit": limit,
|
||||||
|
}), "ChartOfAccountTypeHandler")
|
||||||
|
}
|
||||||
256
internal/handler/ingredient_unit_converter_handler.go
Normal file
256
internal/handler/ingredient_unit_converter_handler.go
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/appcontext"
|
||||||
|
"apskel-pos-be/internal/constants"
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/logger"
|
||||||
|
"apskel-pos-be/internal/service"
|
||||||
|
"apskel-pos-be/internal/util"
|
||||||
|
"apskel-pos-be/internal/validator"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IngredientUnitConverterHandler struct {
|
||||||
|
converterService service.IngredientUnitConverterService
|
||||||
|
converterValidator validator.IngredientUnitConverterValidator
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIngredientUnitConverterHandler(
|
||||||
|
converterService service.IngredientUnitConverterService,
|
||||||
|
converterValidator validator.IngredientUnitConverterValidator,
|
||||||
|
) *IngredientUnitConverterHandler {
|
||||||
|
return &IngredientUnitConverterHandler{
|
||||||
|
converterService: converterService,
|
||||||
|
converterValidator: converterValidator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *IngredientUnitConverterHandler) CreateIngredientUnitConverter(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
var req contract.CreateIngredientUnitConverterRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
logger.FromContext(c.Request.Context()).WithError(err).Error("IngredientUnitConverterHandler::CreateIngredientUnitConverter -> request binding failed")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "IngredientUnitConverterHandler::CreateIngredientUnitConverter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
validationError, validationErrorCode := h.converterValidator.ValidateCreateIngredientUnitConverterRequest(&req)
|
||||||
|
if validationError != nil {
|
||||||
|
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "IngredientUnitConverterHandler::CreateIngredientUnitConverter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
converterResponse := h.converterService.CreateIngredientUnitConverter(ctx, contextInfo, &req)
|
||||||
|
if converterResponse.HasErrors() {
|
||||||
|
errorResp := converterResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("IngredientUnitConverterHandler::CreateIngredientUnitConverter -> Failed to create ingredient unit converter from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, converterResponse, "IngredientUnitConverterHandler::CreateIngredientUnitConverter")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *IngredientUnitConverterHandler) UpdateIngredientUnitConverter(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
converterIDStr := c.Param("id")
|
||||||
|
converterID, err := uuid.Parse(converterIDStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("IngredientUnitConverterHandler::UpdateIngredientUnitConverter -> Invalid converter ID")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid converter ID")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "IngredientUnitConverterHandler::UpdateIngredientUnitConverter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req contract.UpdateIngredientUnitConverterRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("IngredientUnitConverterHandler::UpdateIngredientUnitConverter -> request binding failed")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, "Invalid request body")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "IngredientUnitConverterHandler::UpdateIngredientUnitConverter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
validationError, validationErrorCode := h.converterValidator.ValidateUpdateIngredientUnitConverterRequest(&req)
|
||||||
|
if validationError != nil {
|
||||||
|
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "IngredientUnitConverterHandler::UpdateIngredientUnitConverter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
converterResponse := h.converterService.UpdateIngredientUnitConverter(ctx, contextInfo, converterID, &req)
|
||||||
|
if converterResponse.HasErrors() {
|
||||||
|
errorResp := converterResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("IngredientUnitConverterHandler::UpdateIngredientUnitConverter -> Failed to update ingredient unit converter from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, converterResponse, "IngredientUnitConverterHandler::UpdateIngredientUnitConverter")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *IngredientUnitConverterHandler) DeleteIngredientUnitConverter(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
converterIDStr := c.Param("id")
|
||||||
|
converterID, err := uuid.Parse(converterIDStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("IngredientUnitConverterHandler::DeleteIngredientUnitConverter -> Invalid converter ID")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid converter ID")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "IngredientUnitConverterHandler::DeleteIngredientUnitConverter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
converterResponse := h.converterService.DeleteIngredientUnitConverter(ctx, contextInfo, converterID)
|
||||||
|
if converterResponse.HasErrors() {
|
||||||
|
errorResp := converterResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("IngredientUnitConverterHandler::DeleteIngredientUnitConverter -> Failed to delete ingredient unit converter from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, converterResponse, "IngredientUnitConverterHandler::DeleteIngredientUnitConverter")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *IngredientUnitConverterHandler) GetIngredientUnitConverter(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
converterIDStr := c.Param("id")
|
||||||
|
converterID, err := uuid.Parse(converterIDStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("IngredientUnitConverterHandler::GetIngredientUnitConverter -> Invalid converter ID")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid converter ID")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "IngredientUnitConverterHandler::GetIngredientUnitConverter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
converterResponse := h.converterService.GetIngredientUnitConverter(ctx, contextInfo, converterID)
|
||||||
|
if converterResponse.HasErrors() {
|
||||||
|
errorResp := converterResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("IngredientUnitConverterHandler::GetIngredientUnitConverter -> Failed to get ingredient unit converter from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, converterResponse, "IngredientUnitConverterHandler::GetIngredientUnitConverter")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *IngredientUnitConverterHandler) ListIngredientUnitConverters(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
req := &contract.ListIngredientUnitConvertersRequest{
|
||||||
|
Page: 1,
|
||||||
|
Limit: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse query parameters
|
||||||
|
if pageStr := c.Query("page"); pageStr != "" {
|
||||||
|
if page, err := strconv.Atoi(pageStr); err == nil {
|
||||||
|
req.Page = page
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if limitStr := c.Query("limit"); limitStr != "" {
|
||||||
|
if limit, err := strconv.Atoi(limitStr); err == nil {
|
||||||
|
req.Limit = limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if search := c.Query("search"); search != "" {
|
||||||
|
req.Search = search
|
||||||
|
}
|
||||||
|
|
||||||
|
if ingredientIDStr := c.Query("ingredient_id"); ingredientIDStr != "" {
|
||||||
|
if ingredientID, err := uuid.Parse(ingredientIDStr); err == nil {
|
||||||
|
req.IngredientID = &ingredientID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fromUnitIDStr := c.Query("from_unit_id"); fromUnitIDStr != "" {
|
||||||
|
if fromUnitID, err := uuid.Parse(fromUnitIDStr); err == nil {
|
||||||
|
req.FromUnitID = &fromUnitID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if toUnitIDStr := c.Query("to_unit_id"); toUnitIDStr != "" {
|
||||||
|
if toUnitID, err := uuid.Parse(toUnitIDStr); err == nil {
|
||||||
|
req.ToUnitID = &toUnitID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isActiveStr := c.Query("is_active"); isActiveStr != "" {
|
||||||
|
if isActive, err := strconv.ParseBool(isActiveStr); err == nil {
|
||||||
|
req.IsActive = &isActive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validationError, validationErrorCode := h.converterValidator.ValidateListIngredientUnitConvertersRequest(req)
|
||||||
|
if validationError != nil {
|
||||||
|
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "IngredientUnitConverterHandler::ListIngredientUnitConverters")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
converterResponse := h.converterService.ListIngredientUnitConverters(ctx, contextInfo, req)
|
||||||
|
if converterResponse.HasErrors() {
|
||||||
|
errorResp := converterResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("IngredientUnitConverterHandler::ListIngredientUnitConverters -> Failed to list ingredient unit converters from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, converterResponse, "IngredientUnitConverterHandler::ListIngredientUnitConverters")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *IngredientUnitConverterHandler) GetConvertersForIngredient(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
ingredientIDStr := c.Param("ingredient_id")
|
||||||
|
ingredientID, err := uuid.Parse(ingredientIDStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("IngredientUnitConverterHandler::GetConvertersForIngredient -> Invalid ingredient ID")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid ingredient ID")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "IngredientUnitConverterHandler::GetConvertersForIngredient")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
converterResponse := h.converterService.GetConvertersForIngredient(ctx, contextInfo, ingredientID)
|
||||||
|
if converterResponse.HasErrors() {
|
||||||
|
errorResp := converterResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("IngredientUnitConverterHandler::GetConvertersForIngredient -> Failed to get converters for ingredient from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, converterResponse, "IngredientUnitConverterHandler::GetConvertersForIngredient")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *IngredientUnitConverterHandler) ConvertUnit(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
var req contract.ConvertUnitRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
logger.FromContext(c.Request.Context()).WithError(err).Error("IngredientUnitConverterHandler::ConvertUnit -> request binding failed")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "IngredientUnitConverterHandler::ConvertUnit")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
validationError, validationErrorCode := h.converterValidator.ValidateConvertUnitRequest(&req)
|
||||||
|
if validationError != nil {
|
||||||
|
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "IngredientUnitConverterHandler::ConvertUnit")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
converterResponse := h.converterService.ConvertUnit(ctx, contextInfo, &req)
|
||||||
|
if converterResponse.HasErrors() {
|
||||||
|
errorResp := converterResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("IngredientUnitConverterHandler::ConvertUnit -> Failed to convert unit from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, converterResponse, "IngredientUnitConverterHandler::ConvertUnit")
|
||||||
|
}
|
||||||
|
|
||||||
267
internal/handler/purchase_order_handler.go
Normal file
267
internal/handler/purchase_order_handler.go
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/appcontext"
|
||||||
|
"apskel-pos-be/internal/util"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/constants"
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/logger"
|
||||||
|
"apskel-pos-be/internal/service"
|
||||||
|
"apskel-pos-be/internal/validator"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PurchaseOrderHandler struct {
|
||||||
|
purchaseOrderService service.PurchaseOrderService
|
||||||
|
purchaseOrderValidator validator.PurchaseOrderValidator
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPurchaseOrderHandler(
|
||||||
|
purchaseOrderService service.PurchaseOrderService,
|
||||||
|
purchaseOrderValidator validator.PurchaseOrderValidator,
|
||||||
|
) *PurchaseOrderHandler {
|
||||||
|
return &PurchaseOrderHandler{
|
||||||
|
purchaseOrderService: purchaseOrderService,
|
||||||
|
purchaseOrderValidator: purchaseOrderValidator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PurchaseOrderHandler) CreatePurchaseOrder(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
var req contract.CreatePurchaseOrderRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
logger.FromContext(c.Request.Context()).WithError(err).Error("PurchaseOrderHandler::CreatePurchaseOrder -> request binding failed")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseOrderHandler::CreatePurchaseOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
validationError, validationErrorCode := h.purchaseOrderValidator.ValidateCreatePurchaseOrderRequest(&req)
|
||||||
|
if validationError != nil {
|
||||||
|
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseOrderHandler::CreatePurchaseOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
poResponse := h.purchaseOrderService.CreatePurchaseOrder(ctx, contextInfo, &req)
|
||||||
|
if poResponse.HasErrors() {
|
||||||
|
errorResp := poResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("PurchaseOrderHandler::CreatePurchaseOrder -> Failed to create purchase order from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, poResponse, "PurchaseOrderHandler::CreatePurchaseOrder")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PurchaseOrderHandler) UpdatePurchaseOrder(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
poIDStr := c.Param("id")
|
||||||
|
poID, err := uuid.Parse(poIDStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("PurchaseOrderHandler::UpdatePurchaseOrder -> Invalid purchase order ID")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid purchase order ID")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseOrderHandler::UpdatePurchaseOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req contract.UpdatePurchaseOrderRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("PurchaseOrderHandler::UpdatePurchaseOrder -> request binding failed")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, "Invalid request body")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseOrderHandler::UpdatePurchaseOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
validationError, validationErrorCode := h.purchaseOrderValidator.ValidateUpdatePurchaseOrderRequest(&req)
|
||||||
|
if validationError != nil {
|
||||||
|
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseOrderHandler::UpdatePurchaseOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
poResponse := h.purchaseOrderService.UpdatePurchaseOrder(ctx, contextInfo, poID, &req)
|
||||||
|
if poResponse.HasErrors() {
|
||||||
|
errorResp := poResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("PurchaseOrderHandler::UpdatePurchaseOrder -> Failed to update purchase order from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, poResponse, "PurchaseOrderHandler::UpdatePurchaseOrder")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PurchaseOrderHandler) DeletePurchaseOrder(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
poIDStr := c.Param("id")
|
||||||
|
poID, err := uuid.Parse(poIDStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("PurchaseOrderHandler::DeletePurchaseOrder -> Invalid purchase order ID")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid purchase order ID")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseOrderHandler::DeletePurchaseOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
poResponse := h.purchaseOrderService.DeletePurchaseOrder(ctx, contextInfo, poID)
|
||||||
|
if poResponse.HasErrors() {
|
||||||
|
errorResp := poResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("PurchaseOrderHandler::DeletePurchaseOrder -> Failed to delete purchase order from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, poResponse, "PurchaseOrderHandler::DeletePurchaseOrder")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PurchaseOrderHandler) GetPurchaseOrder(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
poIDStr := c.Param("id")
|
||||||
|
poID, err := uuid.Parse(poIDStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("PurchaseOrderHandler::GetPurchaseOrder -> Invalid purchase order ID")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid purchase order ID")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseOrderHandler::GetPurchaseOrder")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
poResponse := h.purchaseOrderService.GetPurchaseOrderByID(ctx, contextInfo, poID)
|
||||||
|
if poResponse.HasErrors() {
|
||||||
|
errorResp := poResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("PurchaseOrderHandler::GetPurchaseOrder -> Failed to get purchase order from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, poResponse, "PurchaseOrderHandler::GetPurchaseOrder")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PurchaseOrderHandler) ListPurchaseOrders(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
req := &contract.ListPurchaseOrdersRequest{
|
||||||
|
Page: 1,
|
||||||
|
Limit: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse query parameters
|
||||||
|
if pageStr := c.Query("page"); pageStr != "" {
|
||||||
|
if page, err := strconv.Atoi(pageStr); err == nil {
|
||||||
|
req.Page = page
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if limitStr := c.Query("limit"); limitStr != "" {
|
||||||
|
if limit, err := strconv.Atoi(limitStr); err == nil {
|
||||||
|
req.Limit = limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if search := c.Query("search"); search != "" {
|
||||||
|
req.Search = search
|
||||||
|
}
|
||||||
|
|
||||||
|
if status := c.Query("status"); status != "" {
|
||||||
|
req.Status = status
|
||||||
|
}
|
||||||
|
|
||||||
|
if vendorIDStr := c.Query("vendor_id"); vendorIDStr != "" {
|
||||||
|
if vendorID, err := uuid.Parse(vendorIDStr); err == nil {
|
||||||
|
req.VendorID = &vendorID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if startDateStr := c.Query("start_date"); startDateStr != "" {
|
||||||
|
if startDate, err := time.Parse("2006-01-02", startDateStr); err == nil {
|
||||||
|
req.StartDate = &startDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if endDateStr := c.Query("end_date"); endDateStr != "" {
|
||||||
|
if endDate, err := time.Parse("2006-01-02", endDateStr); err == nil {
|
||||||
|
req.EndDate = &endDate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validationError, validationErrorCode := h.purchaseOrderValidator.ValidateListPurchaseOrdersRequest(req)
|
||||||
|
if validationError != nil {
|
||||||
|
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseOrderHandler::ListPurchaseOrders")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
poResponse := h.purchaseOrderService.ListPurchaseOrders(ctx, contextInfo, req)
|
||||||
|
if poResponse.HasErrors() {
|
||||||
|
errorResp := poResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("PurchaseOrderHandler::ListPurchaseOrders -> Failed to list purchase orders from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, poResponse, "PurchaseOrderHandler::ListPurchaseOrders")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PurchaseOrderHandler) GetPurchaseOrdersByStatus(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
status := c.Param("status")
|
||||||
|
if status == "" {
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, "Status parameter is required")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseOrderHandler::GetPurchaseOrdersByStatus")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
poResponse := h.purchaseOrderService.GetPurchaseOrdersByStatus(ctx, contextInfo, status)
|
||||||
|
if poResponse.HasErrors() {
|
||||||
|
errorResp := poResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("PurchaseOrderHandler::GetPurchaseOrdersByStatus -> Failed to get purchase orders by status from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, poResponse, "PurchaseOrderHandler::GetPurchaseOrdersByStatus")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PurchaseOrderHandler) GetOverduePurchaseOrders(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
poResponse := h.purchaseOrderService.GetOverduePurchaseOrders(ctx, contextInfo)
|
||||||
|
if poResponse.HasErrors() {
|
||||||
|
errorResp := poResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("PurchaseOrderHandler::GetOverduePurchaseOrders -> Failed to get overdue purchase orders from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, poResponse, "PurchaseOrderHandler::GetOverduePurchaseOrders")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *PurchaseOrderHandler) UpdatePurchaseOrderStatus(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
poIDStr := c.Param("id")
|
||||||
|
poID, err := uuid.Parse(poIDStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("PurchaseOrderHandler::UpdatePurchaseOrderStatus -> Invalid purchase order ID")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid purchase order ID")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseOrderHandler::UpdatePurchaseOrderStatus")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
status := c.Param("status")
|
||||||
|
if status == "" {
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, "Status parameter is required")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "PurchaseOrderHandler::UpdatePurchaseOrderStatus")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
poResponse := h.purchaseOrderService.UpdatePurchaseOrderStatus(ctx, contextInfo, poID, status)
|
||||||
|
if poResponse.HasErrors() {
|
||||||
|
errorResp := poResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("PurchaseOrderHandler::UpdatePurchaseOrderStatus -> Failed to update purchase order status from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, poResponse, "PurchaseOrderHandler::UpdatePurchaseOrderStatus")
|
||||||
|
}
|
||||||
201
internal/handler/vendor_handler.go
Normal file
201
internal/handler/vendor_handler.go
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/appcontext"
|
||||||
|
"apskel-pos-be/internal/util"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/constants"
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/logger"
|
||||||
|
"apskel-pos-be/internal/service"
|
||||||
|
"apskel-pos-be/internal/validator"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VendorHandler struct {
|
||||||
|
vendorService service.VendorService
|
||||||
|
vendorValidator validator.VendorValidator
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVendorHandler(
|
||||||
|
vendorService service.VendorService,
|
||||||
|
vendorValidator validator.VendorValidator,
|
||||||
|
) *VendorHandler {
|
||||||
|
return &VendorHandler{
|
||||||
|
vendorService: vendorService,
|
||||||
|
vendorValidator: vendorValidator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *VendorHandler) CreateVendor(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
var req contract.CreateVendorRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
logger.FromContext(c.Request.Context()).WithError(err).Error("VendorHandler::CreateVendor -> request binding failed")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, err.Error())
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "VendorHandler::CreateVendor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
validationError, validationErrorCode := h.vendorValidator.ValidateCreateVendorRequest(&req)
|
||||||
|
if validationError != nil {
|
||||||
|
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "VendorHandler::CreateVendor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vendorResponse := h.vendorService.CreateVendor(ctx, contextInfo, &req)
|
||||||
|
if vendorResponse.HasErrors() {
|
||||||
|
errorResp := vendorResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("VendorHandler::CreateVendor -> Failed to create vendor from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, vendorResponse, "VendorHandler::CreateVendor")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *VendorHandler) UpdateVendor(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
vendorIDStr := c.Param("id")
|
||||||
|
vendorID, err := uuid.Parse(vendorIDStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("VendorHandler::UpdateVendor -> Invalid vendor ID")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid vendor ID")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "VendorHandler::UpdateVendor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var req contract.UpdateVendorRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("VendorHandler::UpdateVendor -> request binding failed")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, "Invalid request body")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "VendorHandler::UpdateVendor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
validationError, validationErrorCode := h.vendorValidator.ValidateUpdateVendorRequest(&req)
|
||||||
|
if validationError != nil {
|
||||||
|
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "VendorHandler::UpdateVendor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vendorResponse := h.vendorService.UpdateVendor(ctx, contextInfo, vendorID, &req)
|
||||||
|
if vendorResponse.HasErrors() {
|
||||||
|
errorResp := vendorResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("VendorHandler::UpdateVendor -> Failed to update vendor from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, vendorResponse, "VendorHandler::UpdateVendor")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *VendorHandler) DeleteVendor(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
vendorIDStr := c.Param("id")
|
||||||
|
vendorID, err := uuid.Parse(vendorIDStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("VendorHandler::DeleteVendor -> Invalid vendor ID")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid vendor ID")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "VendorHandler::DeleteVendor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vendorResponse := h.vendorService.DeleteVendor(ctx, contextInfo, vendorID)
|
||||||
|
if vendorResponse.HasErrors() {
|
||||||
|
errorResp := vendorResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("VendorHandler::DeleteVendor -> Failed to delete vendor from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, vendorResponse, "VendorHandler::DeleteVendor")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *VendorHandler) GetVendor(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
vendorIDStr := c.Param("id")
|
||||||
|
vendorID, err := uuid.Parse(vendorIDStr)
|
||||||
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("VendorHandler::GetVendor -> Invalid vendor ID")
|
||||||
|
validationResponseError := contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid vendor ID")
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "VendorHandler::GetVendor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vendorResponse := h.vendorService.GetVendorByID(ctx, contextInfo, vendorID)
|
||||||
|
if vendorResponse.HasErrors() {
|
||||||
|
errorResp := vendorResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("VendorHandler::GetVendor -> Failed to get vendor from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, vendorResponse, "VendorHandler::GetVendor")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *VendorHandler) ListVendors(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
req := &contract.ListVendorsRequest{
|
||||||
|
Page: 1,
|
||||||
|
Limit: 10,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse query parameters
|
||||||
|
if pageStr := c.Query("page"); pageStr != "" {
|
||||||
|
if page, err := strconv.Atoi(pageStr); err == nil {
|
||||||
|
req.Page = page
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if limitStr := c.Query("limit"); limitStr != "" {
|
||||||
|
if limit, err := strconv.Atoi(limitStr); err == nil {
|
||||||
|
req.Limit = limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if search := c.Query("search"); search != "" {
|
||||||
|
req.Search = search
|
||||||
|
}
|
||||||
|
|
||||||
|
if isActiveStr := c.Query("is_active"); isActiveStr != "" {
|
||||||
|
if isActive, err := strconv.ParseBool(isActiveStr); err == nil {
|
||||||
|
req.IsActive = &isActive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validationError, validationErrorCode := h.vendorValidator.ValidateListVendorsRequest(req)
|
||||||
|
if validationError != nil {
|
||||||
|
validationResponseError := contract.NewResponseError(validationErrorCode, constants.RequestEntity, validationError.Error())
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{validationResponseError}), "VendorHandler::ListVendors")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vendorResponse := h.vendorService.ListVendors(ctx, contextInfo, req)
|
||||||
|
if vendorResponse.HasErrors() {
|
||||||
|
errorResp := vendorResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("VendorHandler::ListVendors -> Failed to list vendors from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, vendorResponse, "VendorHandler::ListVendors")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *VendorHandler) GetActiveVendors(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
vendorResponse := h.vendorService.GetActiveVendors(ctx, contextInfo)
|
||||||
|
if vendorResponse.HasErrors() {
|
||||||
|
errorResp := vendorResponse.GetErrors()[0]
|
||||||
|
logger.FromContext(ctx).WithError(errorResp).Error("VendorHandler::GetActiveVendors -> Failed to get active vendors from service")
|
||||||
|
}
|
||||||
|
|
||||||
|
util.HandleResponse(c.Writer, c.Request, vendorResponse, "VendorHandler::GetActiveVendors")
|
||||||
|
}
|
||||||
73
internal/mappers/account_mapper.go
Normal file
73
internal/mappers/account_mapper.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package mappers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AccountEntityToResponse(entity *entities.Account) *models.AccountResponse {
|
||||||
|
response := &models.AccountResponse{
|
||||||
|
ID: entity.ID,
|
||||||
|
OrganizationID: entity.OrganizationID,
|
||||||
|
OutletID: entity.OutletID,
|
||||||
|
ChartOfAccountID: entity.ChartOfAccountID,
|
||||||
|
Name: entity.Name,
|
||||||
|
Number: entity.Number,
|
||||||
|
AccountType: string(entity.AccountType),
|
||||||
|
OpeningBalance: entity.OpeningBalance,
|
||||||
|
CurrentBalance: entity.CurrentBalance,
|
||||||
|
Description: entity.Description,
|
||||||
|
IsActive: entity.IsActive,
|
||||||
|
IsSystem: entity.IsSystem,
|
||||||
|
CreatedAt: entity.CreatedAt,
|
||||||
|
UpdatedAt: entity.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
if entity.ChartOfAccount.ID != uuid.Nil {
|
||||||
|
response.ChartOfAccount = ChartOfAccountEntityToResponse(&entity.ChartOfAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
func AccountCreateRequestToEntity(req *models.CreateAccountRequest, organizationID uuid.UUID, outletID *uuid.UUID) *entities.Account {
|
||||||
|
return &entities.Account{
|
||||||
|
OrganizationID: organizationID,
|
||||||
|
OutletID: outletID,
|
||||||
|
ChartOfAccountID: req.ChartOfAccountID,
|
||||||
|
Name: req.Name,
|
||||||
|
Number: req.Number,
|
||||||
|
AccountType: entities.AccountType(req.AccountType),
|
||||||
|
OpeningBalance: req.OpeningBalance,
|
||||||
|
CurrentBalance: req.OpeningBalance, // Initialize current balance with opening balance
|
||||||
|
Description: req.Description,
|
||||||
|
IsActive: true,
|
||||||
|
IsSystem: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AccountUpdateRequestToEntity(entity *entities.Account, req *models.UpdateAccountRequest) {
|
||||||
|
if req.ChartOfAccountID != nil {
|
||||||
|
entity.ChartOfAccountID = *req.ChartOfAccountID
|
||||||
|
}
|
||||||
|
if req.Name != nil {
|
||||||
|
entity.Name = *req.Name
|
||||||
|
}
|
||||||
|
if req.Number != nil {
|
||||||
|
entity.Number = *req.Number
|
||||||
|
}
|
||||||
|
if req.AccountType != nil {
|
||||||
|
entity.AccountType = entities.AccountType(*req.AccountType)
|
||||||
|
}
|
||||||
|
if req.OpeningBalance != nil {
|
||||||
|
entity.OpeningBalance = *req.OpeningBalance
|
||||||
|
}
|
||||||
|
if req.Description != nil {
|
||||||
|
entity.Description = req.Description
|
||||||
|
}
|
||||||
|
if req.IsActive != nil {
|
||||||
|
entity.IsActive = *req.IsActive
|
||||||
|
}
|
||||||
|
}
|
||||||
77
internal/mappers/chart_of_account_mapper.go
Normal file
77
internal/mappers/chart_of_account_mapper.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package mappers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ChartOfAccountEntityToResponse(entity *entities.ChartOfAccount) *models.ChartOfAccountResponse {
|
||||||
|
response := &models.ChartOfAccountResponse{
|
||||||
|
ID: entity.ID,
|
||||||
|
OrganizationID: entity.OrganizationID,
|
||||||
|
OutletID: entity.OutletID,
|
||||||
|
ChartOfAccountTypeID: entity.ChartOfAccountTypeID,
|
||||||
|
ParentID: entity.ParentID,
|
||||||
|
Name: entity.Name,
|
||||||
|
Code: entity.Code,
|
||||||
|
Description: entity.Description,
|
||||||
|
IsActive: entity.IsActive,
|
||||||
|
IsSystem: entity.IsSystem,
|
||||||
|
CreatedAt: entity.CreatedAt,
|
||||||
|
UpdatedAt: entity.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
if entity.ChartOfAccountType.ID != uuid.Nil {
|
||||||
|
response.ChartOfAccountType = ChartOfAccountTypeEntityToResponse(&entity.ChartOfAccountType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if entity.Parent != nil {
|
||||||
|
response.Parent = ChartOfAccountEntityToResponse(entity.Parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(entity.Children) > 0 {
|
||||||
|
response.Children = make([]models.ChartOfAccountResponse, len(entity.Children))
|
||||||
|
for i, child := range entity.Children {
|
||||||
|
response.Children[i] = *ChartOfAccountEntityToResponse(&child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChartOfAccountCreateRequestToEntity(req *models.CreateChartOfAccountRequest, organizationID uuid.UUID, outletID *uuid.UUID) *entities.ChartOfAccount {
|
||||||
|
return &entities.ChartOfAccount{
|
||||||
|
OrganizationID: organizationID,
|
||||||
|
OutletID: outletID,
|
||||||
|
ChartOfAccountTypeID: req.ChartOfAccountTypeID,
|
||||||
|
ParentID: req.ParentID,
|
||||||
|
Name: req.Name,
|
||||||
|
Code: req.Code,
|
||||||
|
Description: req.Description,
|
||||||
|
IsActive: true,
|
||||||
|
IsSystem: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChartOfAccountUpdateRequestToEntity(entity *entities.ChartOfAccount, req *models.UpdateChartOfAccountRequest) {
|
||||||
|
if req.ChartOfAccountTypeID != nil {
|
||||||
|
entity.ChartOfAccountTypeID = *req.ChartOfAccountTypeID
|
||||||
|
}
|
||||||
|
if req.ParentID != nil {
|
||||||
|
entity.ParentID = req.ParentID
|
||||||
|
}
|
||||||
|
if req.Name != nil {
|
||||||
|
entity.Name = *req.Name
|
||||||
|
}
|
||||||
|
if req.Code != nil {
|
||||||
|
entity.Code = *req.Code
|
||||||
|
}
|
||||||
|
if req.Description != nil {
|
||||||
|
entity.Description = req.Description
|
||||||
|
}
|
||||||
|
if req.IsActive != nil {
|
||||||
|
entity.IsActive = *req.IsActive
|
||||||
|
}
|
||||||
|
}
|
||||||
42
internal/mappers/chart_of_account_type_mapper.go
Normal file
42
internal/mappers/chart_of_account_type_mapper.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package mappers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ChartOfAccountTypeEntityToResponse(entity *entities.ChartOfAccountType) *models.ChartOfAccountTypeResponse {
|
||||||
|
return &models.ChartOfAccountTypeResponse{
|
||||||
|
ID: entity.ID,
|
||||||
|
Name: entity.Name,
|
||||||
|
Code: entity.Code,
|
||||||
|
Description: entity.Description,
|
||||||
|
IsActive: entity.IsActive,
|
||||||
|
CreatedAt: entity.CreatedAt,
|
||||||
|
UpdatedAt: entity.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChartOfAccountTypeCreateRequestToEntity(req *models.CreateChartOfAccountTypeRequest) *entities.ChartOfAccountType {
|
||||||
|
return &entities.ChartOfAccountType{
|
||||||
|
Name: req.Name,
|
||||||
|
Code: req.Code,
|
||||||
|
Description: req.Description,
|
||||||
|
IsActive: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ChartOfAccountTypeUpdateRequestToEntity(entity *entities.ChartOfAccountType, req *models.UpdateChartOfAccountTypeRequest) {
|
||||||
|
if req.Name != nil {
|
||||||
|
entity.Name = *req.Name
|
||||||
|
}
|
||||||
|
if req.Code != nil {
|
||||||
|
entity.Code = *req.Code
|
||||||
|
}
|
||||||
|
if req.Description != nil {
|
||||||
|
entity.Description = req.Description
|
||||||
|
}
|
||||||
|
if req.IsActive != nil {
|
||||||
|
entity.IsActive = *req.IsActive
|
||||||
|
}
|
||||||
|
}
|
||||||
168
internal/mappers/contract_mapper.go
Normal file
168
internal/mappers/contract_mapper.go
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
package mappers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Chart of Account Type Mappers
|
||||||
|
func ContractToModelCreateChartOfAccountTypeRequest(req *contract.CreateChartOfAccountTypeRequest) *models.CreateChartOfAccountTypeRequest {
|
||||||
|
return &models.CreateChartOfAccountTypeRequest{
|
||||||
|
Name: req.Name,
|
||||||
|
Code: req.Code,
|
||||||
|
Description: req.Description,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContractToModelUpdateChartOfAccountTypeRequest(req *contract.UpdateChartOfAccountTypeRequest) *models.UpdateChartOfAccountTypeRequest {
|
||||||
|
return &models.UpdateChartOfAccountTypeRequest{
|
||||||
|
Name: req.Name,
|
||||||
|
Code: req.Code,
|
||||||
|
Description: req.Description,
|
||||||
|
IsActive: req.IsActive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ModelToContractChartOfAccountTypeResponse(resp *models.ChartOfAccountTypeResponse) *contract.ChartOfAccountTypeResponse {
|
||||||
|
return &contract.ChartOfAccountTypeResponse{
|
||||||
|
ID: resp.ID,
|
||||||
|
Name: resp.Name,
|
||||||
|
Code: resp.Code,
|
||||||
|
Description: resp.Description,
|
||||||
|
IsActive: resp.IsActive,
|
||||||
|
CreatedAt: resp.CreatedAt.Format(time.RFC3339),
|
||||||
|
UpdatedAt: resp.UpdatedAt.Format(time.RFC3339),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chart of Account Mappers
|
||||||
|
func ContractToModelCreateChartOfAccountRequest(req *contract.CreateChartOfAccountRequest) *models.CreateChartOfAccountRequest {
|
||||||
|
return &models.CreateChartOfAccountRequest{
|
||||||
|
ChartOfAccountTypeID: req.ChartOfAccountTypeID,
|
||||||
|
ParentID: req.ParentID,
|
||||||
|
Name: req.Name,
|
||||||
|
Code: req.Code,
|
||||||
|
Description: req.Description,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContractToModelUpdateChartOfAccountRequest(req *contract.UpdateChartOfAccountRequest) *models.UpdateChartOfAccountRequest {
|
||||||
|
return &models.UpdateChartOfAccountRequest{
|
||||||
|
ChartOfAccountTypeID: req.ChartOfAccountTypeID,
|
||||||
|
ParentID: req.ParentID,
|
||||||
|
Name: req.Name,
|
||||||
|
Code: req.Code,
|
||||||
|
Description: req.Description,
|
||||||
|
IsActive: req.IsActive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContractToModelListChartOfAccountsRequest(req *contract.ListChartOfAccountsRequest) *models.ListChartOfAccountsRequest {
|
||||||
|
return &models.ListChartOfAccountsRequest{
|
||||||
|
OrganizationID: req.OrganizationID,
|
||||||
|
OutletID: req.OutletID,
|
||||||
|
ChartOfAccountTypeID: req.ChartOfAccountTypeID,
|
||||||
|
ParentID: req.ParentID,
|
||||||
|
IsActive: req.IsActive,
|
||||||
|
IsSystem: req.IsSystem,
|
||||||
|
Page: req.Page,
|
||||||
|
Limit: req.Limit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ModelToContractChartOfAccountResponse(resp *models.ChartOfAccountResponse) *contract.ChartOfAccountResponse {
|
||||||
|
contractResp := &contract.ChartOfAccountResponse{
|
||||||
|
ID: resp.ID,
|
||||||
|
OrganizationID: resp.OrganizationID,
|
||||||
|
OutletID: resp.OutletID,
|
||||||
|
ChartOfAccountTypeID: resp.ChartOfAccountTypeID,
|
||||||
|
ParentID: resp.ParentID,
|
||||||
|
Name: resp.Name,
|
||||||
|
Code: resp.Code,
|
||||||
|
Description: resp.Description,
|
||||||
|
IsActive: resp.IsActive,
|
||||||
|
IsSystem: resp.IsSystem,
|
||||||
|
CreatedAt: resp.CreatedAt.Format(time.RFC3339),
|
||||||
|
UpdatedAt: resp.UpdatedAt.Format(time.RFC3339),
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.ChartOfAccountType != nil {
|
||||||
|
contractResp.ChartOfAccountType = ModelToContractChartOfAccountTypeResponse(resp.ChartOfAccountType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Parent != nil {
|
||||||
|
contractResp.Parent = ModelToContractChartOfAccountResponse(resp.Parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp.Children) > 0 {
|
||||||
|
contractResp.Children = make([]contract.ChartOfAccountResponse, len(resp.Children))
|
||||||
|
for i, child := range resp.Children {
|
||||||
|
contractResp.Children[i] = *ModelToContractChartOfAccountResponse(&child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contractResp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account Mappers
|
||||||
|
func ContractToModelCreateAccountRequest(req *contract.CreateAccountRequest) *models.CreateAccountRequest {
|
||||||
|
return &models.CreateAccountRequest{
|
||||||
|
ChartOfAccountID: req.ChartOfAccountID,
|
||||||
|
Name: req.Name,
|
||||||
|
Number: req.Number,
|
||||||
|
AccountType: req.AccountType,
|
||||||
|
OpeningBalance: req.OpeningBalance,
|
||||||
|
Description: req.Description,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContractToModelUpdateAccountRequest(req *contract.UpdateAccountRequest) *models.UpdateAccountRequest {
|
||||||
|
return &models.UpdateAccountRequest{
|
||||||
|
ChartOfAccountID: req.ChartOfAccountID,
|
||||||
|
Name: req.Name,
|
||||||
|
Number: req.Number,
|
||||||
|
AccountType: req.AccountType,
|
||||||
|
OpeningBalance: req.OpeningBalance,
|
||||||
|
Description: req.Description,
|
||||||
|
IsActive: req.IsActive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContractToModelListAccountsRequest(req *contract.ListAccountsRequest) *models.ListAccountsRequest {
|
||||||
|
return &models.ListAccountsRequest{
|
||||||
|
OrganizationID: req.OrganizationID,
|
||||||
|
OutletID: req.OutletID,
|
||||||
|
ChartOfAccountID: req.ChartOfAccountID,
|
||||||
|
AccountType: req.AccountType,
|
||||||
|
IsActive: req.IsActive,
|
||||||
|
IsSystem: req.IsSystem,
|
||||||
|
Page: req.Page,
|
||||||
|
Limit: req.Limit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ModelToContractAccountResponse(resp *models.AccountResponse) *contract.AccountResponse {
|
||||||
|
contractResp := &contract.AccountResponse{
|
||||||
|
ID: resp.ID,
|
||||||
|
OrganizationID: resp.OrganizationID,
|
||||||
|
OutletID: resp.OutletID,
|
||||||
|
ChartOfAccountID: resp.ChartOfAccountID,
|
||||||
|
Name: resp.Name,
|
||||||
|
Number: resp.Number,
|
||||||
|
AccountType: resp.AccountType,
|
||||||
|
OpeningBalance: resp.OpeningBalance,
|
||||||
|
CurrentBalance: resp.CurrentBalance,
|
||||||
|
Description: resp.Description,
|
||||||
|
IsActive: resp.IsActive,
|
||||||
|
IsSystem: resp.IsSystem,
|
||||||
|
CreatedAt: resp.CreatedAt.Format(time.RFC3339),
|
||||||
|
UpdatedAt: resp.UpdatedAt.Format(time.RFC3339),
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.ChartOfAccount != nil {
|
||||||
|
contractResp.ChartOfAccount = ModelToContractChartOfAccountResponse(resp.ChartOfAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
return contractResp
|
||||||
|
}
|
||||||
@ -74,3 +74,26 @@ func MapIngredientModelsToEntities(models []*models.Ingredient) []*entities.Ingr
|
|||||||
|
|
||||||
return entities
|
return entities
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Entity to Response conversions
|
||||||
|
func MapIngredientEntityToResponse(entity *entities.Ingredient) *models.IngredientResponse {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.IngredientResponse{
|
||||||
|
ID: entity.ID,
|
||||||
|
OrganizationID: entity.OrganizationID,
|
||||||
|
OutletID: entity.OutletID,
|
||||||
|
Name: entity.Name,
|
||||||
|
UnitID: entity.UnitID,
|
||||||
|
Cost: entity.Cost,
|
||||||
|
Stock: entity.Stock,
|
||||||
|
IsSemiFinished: entity.IsSemiFinished,
|
||||||
|
IsActive: entity.IsActive,
|
||||||
|
Metadata: entity.Metadata,
|
||||||
|
CreatedAt: entity.CreatedAt,
|
||||||
|
UpdatedAt: entity.UpdatedAt,
|
||||||
|
Unit: MapUnitEntityToModel(entity.Unit),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
91
internal/mappers/ingredient_unit_converter_mapper.go
Normal file
91
internal/mappers/ingredient_unit_converter_mapper.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package mappers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Entity to Model conversions
|
||||||
|
func IngredientUnitConverterEntityToModel(entity *entities.IngredientUnitConverter) *models.IngredientUnitConverter {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
model := &models.IngredientUnitConverter{
|
||||||
|
ID: entity.ID,
|
||||||
|
OrganizationID: entity.OrganizationID,
|
||||||
|
IngredientID: entity.IngredientID,
|
||||||
|
FromUnitID: entity.FromUnitID,
|
||||||
|
ToUnitID: entity.ToUnitID,
|
||||||
|
ConversionFactor: entity.ConversionFactor,
|
||||||
|
IsActive: entity.IsActive,
|
||||||
|
CreatedAt: entity.CreatedAt,
|
||||||
|
UpdatedAt: entity.UpdatedAt,
|
||||||
|
CreatedBy: entity.CreatedBy,
|
||||||
|
UpdatedBy: entity.UpdatedBy,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map related entities
|
||||||
|
if entity.Ingredient != nil {
|
||||||
|
model.Ingredient = MapIngredientEntityToModel(entity.Ingredient)
|
||||||
|
}
|
||||||
|
if entity.FromUnit != nil {
|
||||||
|
model.FromUnit = MapUnitEntityToModel(entity.FromUnit)
|
||||||
|
}
|
||||||
|
if entity.ToUnit != nil {
|
||||||
|
model.ToUnit = MapUnitEntityToModel(entity.ToUnit)
|
||||||
|
}
|
||||||
|
|
||||||
|
return model
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entity to Response conversions
|
||||||
|
func IngredientUnitConverterEntityToResponse(entity *entities.IngredientUnitConverter) *models.IngredientUnitConverterResponse {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &models.IngredientUnitConverterResponse{
|
||||||
|
ID: entity.ID,
|
||||||
|
OrganizationID: entity.OrganizationID,
|
||||||
|
IngredientID: entity.IngredientID,
|
||||||
|
FromUnitID: entity.FromUnitID,
|
||||||
|
ToUnitID: entity.ToUnitID,
|
||||||
|
ConversionFactor: entity.ConversionFactor,
|
||||||
|
IsActive: entity.IsActive,
|
||||||
|
CreatedAt: entity.CreatedAt,
|
||||||
|
UpdatedAt: entity.UpdatedAt,
|
||||||
|
CreatedBy: entity.CreatedBy,
|
||||||
|
UpdatedBy: entity.UpdatedBy,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map related entities
|
||||||
|
if entity.Ingredient != nil {
|
||||||
|
response.Ingredient = MapIngredientEntityToResponse(entity.Ingredient)
|
||||||
|
}
|
||||||
|
if entity.FromUnit != nil {
|
||||||
|
response.FromUnit = MapUnitEntityToResponse(entity.FromUnit)
|
||||||
|
}
|
||||||
|
if entity.ToUnit != nil {
|
||||||
|
response.ToUnit = MapUnitEntityToResponse(entity.ToUnit)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch conversion methods
|
||||||
|
func IngredientUnitConverterEntitiesToModels(entities []*entities.IngredientUnitConverter) []*models.IngredientUnitConverter {
|
||||||
|
models := make([]*models.IngredientUnitConverter, len(entities))
|
||||||
|
for i, entity := range entities {
|
||||||
|
models[i] = IngredientUnitConverterEntityToModel(entity)
|
||||||
|
}
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
|
||||||
|
func IngredientUnitConverterEntitiesToResponses(entities []*entities.IngredientUnitConverter) []*models.IngredientUnitConverterResponse {
|
||||||
|
responses := make([]*models.IngredientUnitConverterResponse, len(entities))
|
||||||
|
for i, entity := range entities {
|
||||||
|
responses[i] = IngredientUnitConverterEntityToResponse(entity)
|
||||||
|
}
|
||||||
|
return responses
|
||||||
|
}
|
||||||
273
internal/mappers/purchase_order_mapper.go
Normal file
273
internal/mappers/purchase_order_mapper.go
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
package mappers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PurchaseOrderEntityToModel(entity *entities.PurchaseOrder) *models.PurchaseOrder {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.PurchaseOrder{
|
||||||
|
ID: entity.ID,
|
||||||
|
OrganizationID: entity.OrganizationID,
|
||||||
|
VendorID: entity.VendorID,
|
||||||
|
PONumber: entity.PONumber,
|
||||||
|
TransactionDate: entity.TransactionDate,
|
||||||
|
DueDate: entity.DueDate,
|
||||||
|
Reference: entity.Reference,
|
||||||
|
Status: entity.Status,
|
||||||
|
Message: entity.Message,
|
||||||
|
TotalAmount: entity.TotalAmount,
|
||||||
|
CreatedAt: entity.CreatedAt,
|
||||||
|
UpdatedAt: entity.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PurchaseOrderModelToEntity(model *models.PurchaseOrder) *entities.PurchaseOrder {
|
||||||
|
if model == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &entities.PurchaseOrder{
|
||||||
|
ID: model.ID,
|
||||||
|
OrganizationID: model.OrganizationID,
|
||||||
|
VendorID: model.VendorID,
|
||||||
|
PONumber: model.PONumber,
|
||||||
|
TransactionDate: model.TransactionDate,
|
||||||
|
DueDate: model.DueDate,
|
||||||
|
Reference: model.Reference,
|
||||||
|
Status: model.Status,
|
||||||
|
Message: model.Message,
|
||||||
|
TotalAmount: model.TotalAmount,
|
||||||
|
CreatedAt: model.CreatedAt,
|
||||||
|
UpdatedAt: model.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PurchaseOrderEntityToResponse(entity *entities.PurchaseOrder) *models.PurchaseOrderResponse {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &models.PurchaseOrderResponse{
|
||||||
|
ID: entity.ID,
|
||||||
|
OrganizationID: entity.OrganizationID,
|
||||||
|
VendorID: entity.VendorID,
|
||||||
|
PONumber: entity.PONumber,
|
||||||
|
TransactionDate: entity.TransactionDate,
|
||||||
|
DueDate: entity.DueDate,
|
||||||
|
Reference: entity.Reference,
|
||||||
|
Status: entity.Status,
|
||||||
|
Message: entity.Message,
|
||||||
|
TotalAmount: entity.TotalAmount,
|
||||||
|
CreatedAt: entity.CreatedAt,
|
||||||
|
UpdatedAt: entity.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map vendor if present
|
||||||
|
if entity.Vendor != nil {
|
||||||
|
response.Vendor = VendorEntityToResponse(entity.Vendor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map items if present
|
||||||
|
if entity.Items != nil {
|
||||||
|
response.Items = PurchaseOrderItemEntitiesToResponses(entity.Items)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map attachments if present
|
||||||
|
if entity.Attachments != nil {
|
||||||
|
response.Attachments = PurchaseOrderAttachmentEntitiesToResponses(entity.Attachments)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
func PurchaseOrderItemEntityToModel(entity *entities.PurchaseOrderItem) *models.PurchaseOrderItem {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.PurchaseOrderItem{
|
||||||
|
ID: entity.ID,
|
||||||
|
PurchaseOrderID: entity.PurchaseOrderID,
|
||||||
|
IngredientID: entity.IngredientID,
|
||||||
|
Description: entity.Description,
|
||||||
|
Quantity: entity.Quantity,
|
||||||
|
UnitID: entity.UnitID,
|
||||||
|
Amount: entity.Amount,
|
||||||
|
CreatedAt: entity.CreatedAt,
|
||||||
|
UpdatedAt: entity.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PurchaseOrderItemModelToEntity(model *models.PurchaseOrderItem) *entities.PurchaseOrderItem {
|
||||||
|
if model == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &entities.PurchaseOrderItem{
|
||||||
|
ID: model.ID,
|
||||||
|
PurchaseOrderID: model.PurchaseOrderID,
|
||||||
|
IngredientID: model.IngredientID,
|
||||||
|
Description: model.Description,
|
||||||
|
Quantity: model.Quantity,
|
||||||
|
UnitID: model.UnitID,
|
||||||
|
Amount: model.Amount,
|
||||||
|
CreatedAt: model.CreatedAt,
|
||||||
|
UpdatedAt: model.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PurchaseOrderItemEntityToResponse(entity *entities.PurchaseOrderItem) *models.PurchaseOrderItemResponse {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &models.PurchaseOrderItemResponse{
|
||||||
|
ID: entity.ID,
|
||||||
|
PurchaseOrderID: entity.PurchaseOrderID,
|
||||||
|
IngredientID: entity.IngredientID,
|
||||||
|
Description: entity.Description,
|
||||||
|
Quantity: entity.Quantity,
|
||||||
|
UnitID: entity.UnitID,
|
||||||
|
Amount: entity.Amount,
|
||||||
|
CreatedAt: entity.CreatedAt,
|
||||||
|
UpdatedAt: entity.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map ingredient if present
|
||||||
|
if entity.Ingredient != nil {
|
||||||
|
response.Ingredient = &models.IngredientResponse{
|
||||||
|
ID: entity.Ingredient.ID,
|
||||||
|
Name: entity.Ingredient.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map unit if present
|
||||||
|
if entity.Unit != nil {
|
||||||
|
response.Unit = &models.UnitResponse{
|
||||||
|
ID: entity.Unit.ID,
|
||||||
|
Name: entity.Unit.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
func PurchaseOrderAttachmentEntityToModel(entity *entities.PurchaseOrderAttachment) *models.PurchaseOrderAttachment {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.PurchaseOrderAttachment{
|
||||||
|
ID: entity.ID,
|
||||||
|
PurchaseOrderID: entity.PurchaseOrderID,
|
||||||
|
FileID: entity.FileID,
|
||||||
|
CreatedAt: entity.CreatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PurchaseOrderAttachmentModelToEntity(model *models.PurchaseOrderAttachment) *entities.PurchaseOrderAttachment {
|
||||||
|
if model == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &entities.PurchaseOrderAttachment{
|
||||||
|
ID: model.ID,
|
||||||
|
PurchaseOrderID: model.PurchaseOrderID,
|
||||||
|
FileID: model.FileID,
|
||||||
|
CreatedAt: model.CreatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func PurchaseOrderAttachmentEntityToResponse(entity *entities.PurchaseOrderAttachment) *models.PurchaseOrderAttachmentResponse {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &models.PurchaseOrderAttachmentResponse{
|
||||||
|
ID: entity.ID,
|
||||||
|
PurchaseOrderID: entity.PurchaseOrderID,
|
||||||
|
FileID: entity.FileID,
|
||||||
|
CreatedAt: entity.CreatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map file if present
|
||||||
|
if entity.File != nil {
|
||||||
|
response.File = &models.FileResponse{
|
||||||
|
ID: entity.File.ID,
|
||||||
|
FileName: entity.File.FileName,
|
||||||
|
OriginalName: entity.File.OriginalName,
|
||||||
|
FileURL: entity.File.FileURL,
|
||||||
|
FileSize: entity.File.FileSize,
|
||||||
|
MimeType: entity.File.MimeType,
|
||||||
|
FileType: entity.File.FileType,
|
||||||
|
IsPublic: entity.File.IsPublic,
|
||||||
|
CreatedAt: entity.File.CreatedAt,
|
||||||
|
UpdatedAt: entity.File.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch conversion methods
|
||||||
|
func PurchaseOrderEntitiesToModels(entities []*entities.PurchaseOrder) []*models.PurchaseOrder {
|
||||||
|
if entities == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
models := make([]*models.PurchaseOrder, len(entities))
|
||||||
|
for i, entity := range entities {
|
||||||
|
models[i] = PurchaseOrderEntityToModel(entity)
|
||||||
|
}
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
|
||||||
|
func PurchaseOrderEntitiesToResponses(entities []*entities.PurchaseOrder) []models.PurchaseOrderResponse {
|
||||||
|
if entities == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]models.PurchaseOrderResponse, len(entities))
|
||||||
|
for i, entity := range entities {
|
||||||
|
response := PurchaseOrderEntityToResponse(entity)
|
||||||
|
if response != nil {
|
||||||
|
responses[i] = *response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return responses
|
||||||
|
}
|
||||||
|
|
||||||
|
func PurchaseOrderItemEntitiesToResponses(entities []entities.PurchaseOrderItem) []models.PurchaseOrderItemResponse {
|
||||||
|
if entities == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]models.PurchaseOrderItemResponse, len(entities))
|
||||||
|
for i, entity := range entities {
|
||||||
|
response := PurchaseOrderItemEntityToResponse(&entity)
|
||||||
|
if response != nil {
|
||||||
|
responses[i] = *response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return responses
|
||||||
|
}
|
||||||
|
|
||||||
|
func PurchaseOrderAttachmentEntitiesToResponses(entities []entities.PurchaseOrderAttachment) []models.PurchaseOrderAttachmentResponse {
|
||||||
|
if entities == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]models.PurchaseOrderAttachmentResponse, len(entities))
|
||||||
|
for i, entity := range entities {
|
||||||
|
response := PurchaseOrderAttachmentEntityToResponse(&entity)
|
||||||
|
if response != nil {
|
||||||
|
responses[i] = *response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return responses
|
||||||
|
}
|
||||||
@ -66,3 +66,22 @@ func MapUnitModelsToEntities(models []*models.Unit) []*entities.Unit {
|
|||||||
|
|
||||||
return entities
|
return entities
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Entity to Response conversions
|
||||||
|
func MapUnitEntityToResponse(entity *entities.Unit) *models.UnitResponse {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.UnitResponse{
|
||||||
|
ID: entity.ID,
|
||||||
|
OrganizationID: entity.OrganizationID,
|
||||||
|
OutletID: entity.OutletID,
|
||||||
|
Name: entity.Name,
|
||||||
|
Abbreviation: entity.Abbreviation,
|
||||||
|
IsActive: entity.IsActive,
|
||||||
|
DeletedAt: entity.DeletedAt,
|
||||||
|
CreatedAt: entity.CreatedAt,
|
||||||
|
UpdatedAt: entity.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
96
internal/mappers/vendor_mapper.go
Normal file
96
internal/mappers/vendor_mapper.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package mappers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func VendorEntityToModel(entity *entities.Vendor) *models.Vendor {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.Vendor{
|
||||||
|
ID: entity.ID,
|
||||||
|
OrganizationID: entity.OrganizationID,
|
||||||
|
Name: entity.Name,
|
||||||
|
Email: entity.Email,
|
||||||
|
PhoneNumber: entity.PhoneNumber,
|
||||||
|
Address: entity.Address,
|
||||||
|
ContactPerson: entity.ContactPerson,
|
||||||
|
TaxNumber: entity.TaxNumber,
|
||||||
|
PaymentTerms: entity.PaymentTerms,
|
||||||
|
Notes: entity.Notes,
|
||||||
|
IsActive: entity.IsActive,
|
||||||
|
CreatedAt: entity.CreatedAt,
|
||||||
|
UpdatedAt: entity.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func VendorModelToEntity(model *models.Vendor) *entities.Vendor {
|
||||||
|
if model == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &entities.Vendor{
|
||||||
|
ID: model.ID,
|
||||||
|
OrganizationID: model.OrganizationID,
|
||||||
|
Name: model.Name,
|
||||||
|
Email: model.Email,
|
||||||
|
PhoneNumber: model.PhoneNumber,
|
||||||
|
Address: model.Address,
|
||||||
|
ContactPerson: model.ContactPerson,
|
||||||
|
TaxNumber: model.TaxNumber,
|
||||||
|
PaymentTerms: model.PaymentTerms,
|
||||||
|
Notes: model.Notes,
|
||||||
|
IsActive: model.IsActive,
|
||||||
|
CreatedAt: model.CreatedAt,
|
||||||
|
UpdatedAt: model.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func VendorEntityToResponse(entity *entities.Vendor) *models.VendorResponse {
|
||||||
|
if entity == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.VendorResponse{
|
||||||
|
ID: entity.ID,
|
||||||
|
OrganizationID: entity.OrganizationID,
|
||||||
|
Name: entity.Name,
|
||||||
|
Email: entity.Email,
|
||||||
|
PhoneNumber: entity.PhoneNumber,
|
||||||
|
Address: entity.Address,
|
||||||
|
ContactPerson: entity.ContactPerson,
|
||||||
|
TaxNumber: entity.TaxNumber,
|
||||||
|
PaymentTerms: entity.PaymentTerms,
|
||||||
|
Notes: entity.Notes,
|
||||||
|
IsActive: entity.IsActive,
|
||||||
|
CreatedAt: entity.CreatedAt,
|
||||||
|
UpdatedAt: entity.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func VendorEntitiesToModels(entities []*entities.Vendor) []*models.Vendor {
|
||||||
|
if entities == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
models := make([]*models.Vendor, len(entities))
|
||||||
|
for i, entity := range entities {
|
||||||
|
models[i] = VendorEntityToModel(entity)
|
||||||
|
}
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
|
||||||
|
func VendorEntitiesToResponses(entities []*entities.Vendor) []*models.VendorResponse {
|
||||||
|
if entities == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]*models.VendorResponse, len(entities))
|
||||||
|
for i, entity := range entities {
|
||||||
|
responses[i] = VendorEntityToResponse(entity)
|
||||||
|
}
|
||||||
|
return responses
|
||||||
|
}
|
||||||
55
internal/models/account.go
Normal file
55
internal/models/account.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
|
OutletID *uuid.UUID `json:"outlet_id"`
|
||||||
|
ChartOfAccountID uuid.UUID `json:"chart_of_account_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Number string `json:"number"`
|
||||||
|
AccountType string `json:"account_type"`
|
||||||
|
OpeningBalance float64 `json:"opening_balance"`
|
||||||
|
CurrentBalance float64 `json:"current_balance"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
IsSystem bool `json:"is_system"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
ChartOfAccount *ChartOfAccountResponse `json:"chart_of_account,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateAccountRequest struct {
|
||||||
|
ChartOfAccountID uuid.UUID `json:"chart_of_account_id" validate:"required"`
|
||||||
|
Name string `json:"name" validate:"required,min=1,max=255"`
|
||||||
|
Number string `json:"number" validate:"required,min=1,max=50"`
|
||||||
|
AccountType string `json:"account_type" validate:"required,oneof=cash wallet bank credit debit asset liability equity revenue expense"`
|
||||||
|
OpeningBalance float64 `json:"opening_balance"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateAccountRequest struct {
|
||||||
|
ChartOfAccountID *uuid.UUID `json:"chart_of_account_id"`
|
||||||
|
Name *string `json:"name" validate:"omitempty,min=1,max=255"`
|
||||||
|
Number *string `json:"number" validate:"omitempty,min=1,max=50"`
|
||||||
|
AccountType *string `json:"account_type" validate:"omitempty,oneof=cash wallet bank credit debit asset liability equity revenue expense"`
|
||||||
|
OpeningBalance *float64 `json:"opening_balance"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
IsActive *bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListAccountsRequest struct {
|
||||||
|
OrganizationID *uuid.UUID `form:"organization_id"`
|
||||||
|
OutletID *uuid.UUID `form:"outlet_id"`
|
||||||
|
ChartOfAccountID *uuid.UUID `form:"chart_of_account_id"`
|
||||||
|
AccountType *string `form:"account_type"`
|
||||||
|
IsActive *bool `form:"is_active"`
|
||||||
|
IsSystem *bool `form:"is_system"`
|
||||||
|
Page int `form:"page,default=1"`
|
||||||
|
Limit int `form:"limit,default=10"`
|
||||||
|
}
|
||||||
53
internal/models/chart_of_account.go
Normal file
53
internal/models/chart_of_account.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChartOfAccountResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
|
OutletID *uuid.UUID `json:"outlet_id"`
|
||||||
|
ChartOfAccountTypeID uuid.UUID `json:"chart_of_account_type_id"`
|
||||||
|
ParentID *uuid.UUID `json:"parent_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
IsSystem bool `json:"is_system"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
ChartOfAccountType *ChartOfAccountTypeResponse `json:"chart_of_account_type,omitempty"`
|
||||||
|
Parent *ChartOfAccountResponse `json:"parent,omitempty"`
|
||||||
|
Children []ChartOfAccountResponse `json:"children,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateChartOfAccountRequest struct {
|
||||||
|
ChartOfAccountTypeID uuid.UUID `json:"chart_of_account_type_id" validate:"required"`
|
||||||
|
ParentID *uuid.UUID `json:"parent_id"`
|
||||||
|
Name string `json:"name" validate:"required,min=1,max=255"`
|
||||||
|
Code string `json:"code" validate:"required,min=1,max=20"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateChartOfAccountRequest struct {
|
||||||
|
ChartOfAccountTypeID *uuid.UUID `json:"chart_of_account_type_id"`
|
||||||
|
ParentID *uuid.UUID `json:"parent_id"`
|
||||||
|
Name *string `json:"name" validate:"omitempty,min=1,max=255"`
|
||||||
|
Code *string `json:"code" validate:"omitempty,min=1,max=20"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
IsActive *bool `json:"is_active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListChartOfAccountsRequest struct {
|
||||||
|
OrganizationID *uuid.UUID `form:"organization_id"`
|
||||||
|
OutletID *uuid.UUID `form:"outlet_id"`
|
||||||
|
ChartOfAccountTypeID *uuid.UUID `form:"chart_of_account_type_id"`
|
||||||
|
ParentID *uuid.UUID `form:"parent_id"`
|
||||||
|
IsActive *bool `form:"is_active"`
|
||||||
|
IsSystem *bool `form:"is_system"`
|
||||||
|
Page int `form:"page,default=1"`
|
||||||
|
Limit int `form:"limit,default=10"`
|
||||||
|
}
|
||||||
30
internal/models/chart_of_account_type.go
Normal file
30
internal/models/chart_of_account_type.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChartOfAccountTypeResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateChartOfAccountTypeRequest struct {
|
||||||
|
Name string `json:"name" validate:"required,min=1,max=100"`
|
||||||
|
Code string `json:"code" validate:"required,min=1,max=10"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateChartOfAccountTypeRequest struct {
|
||||||
|
Name *string `json:"name" validate:"omitempty,min=1,max=100"`
|
||||||
|
Code *string `json:"code" validate:"omitempty,min=1,max=10"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
IsActive *bool `json:"is_active"`
|
||||||
|
}
|
||||||
96
internal/models/ingredient_unit_converter.go
Normal file
96
internal/models/ingredient_unit_converter.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IngredientUnitConverter represents the unit converter for ingredients
|
||||||
|
type IngredientUnitConverter struct {
|
||||||
|
ID uuid.UUID
|
||||||
|
OrganizationID uuid.UUID
|
||||||
|
IngredientID uuid.UUID
|
||||||
|
FromUnitID uuid.UUID
|
||||||
|
ToUnitID uuid.UUID
|
||||||
|
ConversionFactor float64
|
||||||
|
IsActive bool
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
CreatedBy uuid.UUID
|
||||||
|
UpdatedBy uuid.UUID
|
||||||
|
|
||||||
|
// Related entities
|
||||||
|
Ingredient *Ingredient
|
||||||
|
FromUnit *Unit
|
||||||
|
ToUnit *Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request DTOs
|
||||||
|
type CreateIngredientUnitConverterRequest struct {
|
||||||
|
IngredientID uuid.UUID `validate:"required"`
|
||||||
|
FromUnitID uuid.UUID `validate:"required"`
|
||||||
|
ToUnitID uuid.UUID `validate:"required"`
|
||||||
|
ConversionFactor float64 `validate:"required,gt=0"`
|
||||||
|
IsActive *bool `validate:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateIngredientUnitConverterRequest struct {
|
||||||
|
FromUnitID *uuid.UUID `validate:"omitempty"`
|
||||||
|
ToUnitID *uuid.UUID `validate:"omitempty"`
|
||||||
|
ConversionFactor *float64 `validate:"omitempty,gt=0"`
|
||||||
|
IsActive *bool `validate:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListIngredientUnitConvertersRequest struct {
|
||||||
|
IngredientID *uuid.UUID
|
||||||
|
FromUnitID *uuid.UUID
|
||||||
|
ToUnitID *uuid.UUID
|
||||||
|
IsActive *bool
|
||||||
|
Search string
|
||||||
|
Page int `validate:"required,min=1"`
|
||||||
|
Limit int `validate:"required,min=1,max=100"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConvertUnitRequest struct {
|
||||||
|
IngredientID uuid.UUID `validate:"required"`
|
||||||
|
FromUnitID uuid.UUID `validate:"required"`
|
||||||
|
ToUnitID uuid.UUID `validate:"required"`
|
||||||
|
Quantity float64 `validate:"required,gt=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response DTOs
|
||||||
|
type IngredientUnitConverterResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
|
FromUnitID uuid.UUID `json:"from_unit_id"`
|
||||||
|
ToUnitID uuid.UUID `json:"to_unit_id"`
|
||||||
|
ConversionFactor float64 `json:"conversion_factor"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
CreatedBy uuid.UUID `json:"created_by"`
|
||||||
|
UpdatedBy uuid.UUID `json:"updated_by"`
|
||||||
|
Ingredient *IngredientResponse `json:"ingredient,omitempty"`
|
||||||
|
FromUnit *UnitResponse `json:"from_unit,omitempty"`
|
||||||
|
ToUnit *UnitResponse `json:"to_unit,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConvertUnitResponse struct {
|
||||||
|
FromQuantity float64 `json:"from_quantity"`
|
||||||
|
FromUnit *UnitResponse `json:"from_unit"`
|
||||||
|
ToQuantity float64 `json:"to_quantity"`
|
||||||
|
ToUnit *UnitResponse `json:"to_unit"`
|
||||||
|
ConversionFactor float64 `json:"conversion_factor"`
|
||||||
|
Ingredient *IngredientResponse `json:"ingredient,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListIngredientUnitConvertersResponse struct {
|
||||||
|
Converters []IngredientUnitConverterResponse `json:"converters"`
|
||||||
|
TotalCount int `json:"total_count"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
TotalPages int `json:"total_pages"`
|
||||||
|
}
|
||||||
|
|
||||||
@ -26,5 +26,10 @@ func GetAllModelNames() []string {
|
|||||||
"PaymentMethod",
|
"PaymentMethod",
|
||||||
"Payment",
|
"Payment",
|
||||||
"Customer",
|
"Customer",
|
||||||
|
"Vendor",
|
||||||
|
"PurchaseOrder",
|
||||||
|
"PurchaseOrderItem",
|
||||||
|
"PurchaseOrderAttachment",
|
||||||
|
"IngredientUnitConverter",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
140
internal/models/purchase_order.go
Normal file
140
internal/models/purchase_order.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PurchaseOrder struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
|
VendorID uuid.UUID `json:"vendor_id"`
|
||||||
|
PONumber string `json:"po_number"`
|
||||||
|
TransactionDate time.Time `json:"transaction_date"`
|
||||||
|
DueDate time.Time `json:"due_date"`
|
||||||
|
Reference *string `json:"reference"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message *string `json:"message"`
|
||||||
|
TotalAmount float64 `json:"total_amount"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PurchaseOrderItem struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
PurchaseOrderID uuid.UUID `json:"purchase_order_id"`
|
||||||
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
Quantity float64 `json:"quantity"`
|
||||||
|
UnitID uuid.UUID `json:"unit_id"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PurchaseOrderAttachment struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
PurchaseOrderID uuid.UUID `json:"purchase_order_id"`
|
||||||
|
FileID uuid.UUID `json:"file_id"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PurchaseOrderResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
|
VendorID uuid.UUID `json:"vendor_id"`
|
||||||
|
PONumber string `json:"po_number"`
|
||||||
|
TransactionDate time.Time `json:"transaction_date"`
|
||||||
|
DueDate time.Time `json:"due_date"`
|
||||||
|
Reference *string `json:"reference"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Message *string `json:"message"`
|
||||||
|
TotalAmount float64 `json:"total_amount"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Vendor *VendorResponse `json:"vendor,omitempty"`
|
||||||
|
Items []PurchaseOrderItemResponse `json:"items,omitempty"`
|
||||||
|
Attachments []PurchaseOrderAttachmentResponse `json:"attachments,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PurchaseOrderItemResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
PurchaseOrderID uuid.UUID `json:"purchase_order_id"`
|
||||||
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
Quantity float64 `json:"quantity"`
|
||||||
|
UnitID uuid.UUID `json:"unit_id"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
Ingredient *IngredientResponse `json:"ingredient,omitempty"`
|
||||||
|
Unit *UnitResponse `json:"unit,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PurchaseOrderAttachmentResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
PurchaseOrderID uuid.UUID `json:"purchase_order_id"`
|
||||||
|
FileID uuid.UUID `json:"file_id"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
File *FileResponse `json:"file,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreatePurchaseOrderRequest struct {
|
||||||
|
VendorID uuid.UUID `json:"vendor_id"`
|
||||||
|
PONumber string `json:"po_number"`
|
||||||
|
TransactionDate time.Time `json:"transaction_date"`
|
||||||
|
DueDate time.Time `json:"due_date"`
|
||||||
|
Reference *string `json:"reference,omitempty"`
|
||||||
|
Status *string `json:"status,omitempty"`
|
||||||
|
Message *string `json:"message,omitempty"`
|
||||||
|
Items []CreatePurchaseOrderItemRequest `json:"items"`
|
||||||
|
AttachmentFileIDs []uuid.UUID `json:"attachment_file_ids,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreatePurchaseOrderItemRequest struct {
|
||||||
|
IngredientID uuid.UUID `json:"ingredient_id"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
Quantity float64 `json:"quantity"`
|
||||||
|
UnitID uuid.UUID `json:"unit_id"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdatePurchaseOrderRequest struct {
|
||||||
|
VendorID *uuid.UUID `json:"vendor_id,omitempty"`
|
||||||
|
PONumber *string `json:"po_number,omitempty"`
|
||||||
|
TransactionDate *time.Time `json:"transaction_date,omitempty"`
|
||||||
|
DueDate *time.Time `json:"due_date,omitempty"`
|
||||||
|
Reference *string `json:"reference,omitempty"`
|
||||||
|
Status *string `json:"status,omitempty"`
|
||||||
|
Message *string `json:"message,omitempty"`
|
||||||
|
Items []UpdatePurchaseOrderItemRequest `json:"items,omitempty"`
|
||||||
|
AttachmentFileIDs []uuid.UUID `json:"attachment_file_ids,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdatePurchaseOrderItemRequest struct {
|
||||||
|
ID *uuid.UUID `json:"id,omitempty"` // For existing items
|
||||||
|
IngredientID *uuid.UUID `json:"ingredient_id,omitempty"`
|
||||||
|
Description *string `json:"description,omitempty"`
|
||||||
|
Quantity *float64 `json:"quantity,omitempty"`
|
||||||
|
UnitID *uuid.UUID `json:"unit_id,omitempty"`
|
||||||
|
Amount *float64 `json:"amount,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListPurchaseOrdersRequest struct {
|
||||||
|
Page int `json:"page" validate:"min=1"`
|
||||||
|
Limit int `json:"limit" validate:"min=1,max=100"`
|
||||||
|
Search string `json:"search,omitempty"`
|
||||||
|
Status string `json:"status,omitempty"`
|
||||||
|
VendorID *uuid.UUID `json:"vendor_id,omitempty"`
|
||||||
|
StartDate *time.Time `json:"start_date,omitempty"`
|
||||||
|
EndDate *time.Time `json:"end_date,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListPurchaseOrdersResponse struct {
|
||||||
|
PurchaseOrders []PurchaseOrderResponse `json:"purchase_orders"`
|
||||||
|
TotalCount int `json:"total_count"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
TotalPages int `json:"total_pages"`
|
||||||
|
}
|
||||||
78
internal/models/vendor.go
Normal file
78
internal/models/vendor.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Vendor struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email *string `json:"email"`
|
||||||
|
PhoneNumber *string `json:"phone_number"`
|
||||||
|
Address *string `json:"address"`
|
||||||
|
ContactPerson *string `json:"contact_person"`
|
||||||
|
TaxNumber *string `json:"tax_number"`
|
||||||
|
PaymentTerms *string `json:"payment_terms"`
|
||||||
|
Notes *string `json:"notes"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type VendorResponse struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email *string `json:"email"`
|
||||||
|
PhoneNumber *string `json:"phone_number"`
|
||||||
|
Address *string `json:"address"`
|
||||||
|
ContactPerson *string `json:"contact_person"`
|
||||||
|
TaxNumber *string `json:"tax_number"`
|
||||||
|
PaymentTerms *string `json:"payment_terms"`
|
||||||
|
Notes *string `json:"notes"`
|
||||||
|
IsActive bool `json:"is_active"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateVendorRequest struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email *string `json:"email,omitempty"`
|
||||||
|
PhoneNumber *string `json:"phone_number,omitempty"`
|
||||||
|
Address *string `json:"address,omitempty"`
|
||||||
|
ContactPerson *string `json:"contact_person,omitempty"`
|
||||||
|
TaxNumber *string `json:"tax_number,omitempty"`
|
||||||
|
PaymentTerms *string `json:"payment_terms,omitempty"`
|
||||||
|
Notes *string `json:"notes,omitempty"`
|
||||||
|
IsActive *bool `json:"is_active,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateVendorRequest struct {
|
||||||
|
Name *string `json:"name,omitempty"`
|
||||||
|
Email *string `json:"email,omitempty"`
|
||||||
|
PhoneNumber *string `json:"phone_number,omitempty"`
|
||||||
|
Address *string `json:"address,omitempty"`
|
||||||
|
ContactPerson *string `json:"contact_person,omitempty"`
|
||||||
|
TaxNumber *string `json:"tax_number,omitempty"`
|
||||||
|
PaymentTerms *string `json:"payment_terms,omitempty"`
|
||||||
|
Notes *string `json:"notes,omitempty"`
|
||||||
|
IsActive *bool `json:"is_active,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListVendorsRequest struct {
|
||||||
|
Page int `json:"page"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
Search string `json:"search,omitempty"`
|
||||||
|
IsActive *bool `json:"is_active,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListVendorsResponse struct {
|
||||||
|
Vendors []*VendorResponse `json:"vendors"`
|
||||||
|
TotalCount int `json:"total_count"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
TotalPages int `json:"total_pages"`
|
||||||
|
}
|
||||||
207
internal/processor/account_processor.go
Normal file
207
internal/processor/account_processor.go
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
package processor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/appcontext"
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"apskel-pos-be/internal/mappers"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type AccountProcessor interface {
|
||||||
|
CreateAccount(ctx context.Context, req *models.CreateAccountRequest) (*models.AccountResponse, error)
|
||||||
|
GetAccountByID(ctx context.Context, id uuid.UUID) (*models.AccountResponse, error)
|
||||||
|
UpdateAccount(ctx context.Context, id uuid.UUID, req *models.UpdateAccountRequest) (*models.AccountResponse, error)
|
||||||
|
DeleteAccount(ctx context.Context, id uuid.UUID) error
|
||||||
|
ListAccounts(ctx context.Context, req *models.ListAccountsRequest) ([]models.AccountResponse, int, error)
|
||||||
|
GetAccountsByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]models.AccountResponse, error)
|
||||||
|
GetAccountsByChartOfAccount(ctx context.Context, chartOfAccountID uuid.UUID) ([]models.AccountResponse, error)
|
||||||
|
UpdateAccountBalance(ctx context.Context, id uuid.UUID, amount float64) error
|
||||||
|
GetAccountBalance(ctx context.Context, id uuid.UUID) (float64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountProcessorImpl struct {
|
||||||
|
accountRepo AccountRepository
|
||||||
|
chartOfAccountRepo ChartOfAccountRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccountProcessorImpl(accountRepo AccountRepository, chartOfAccountRepo ChartOfAccountRepository) *AccountProcessorImpl {
|
||||||
|
return &AccountProcessorImpl{
|
||||||
|
accountRepo: accountRepo,
|
||||||
|
chartOfAccountRepo: chartOfAccountRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AccountProcessorImpl) CreateAccount(ctx context.Context, req *models.CreateAccountRequest) (*models.AccountResponse, error) {
|
||||||
|
// Get organization and outlet from context
|
||||||
|
appCtx := appcontext.FromGinContext(ctx)
|
||||||
|
organizationID := appCtx.OrganizationID
|
||||||
|
var outletID *uuid.UUID
|
||||||
|
if appCtx.OutletID != uuid.Nil {
|
||||||
|
outletID = &appCtx.OutletID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if account number already exists for this organization/outlet
|
||||||
|
existing, err := p.accountRepo.GetByNumber(ctx, organizationID, req.Number, outletID)
|
||||||
|
if err == nil && existing != nil {
|
||||||
|
return nil, fmt.Errorf("account with number %s already exists", req.Number)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate chart of account exists
|
||||||
|
_, err = p.chartOfAccountRepo.GetByID(ctx, req.ChartOfAccountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("chart of account not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entity := mappers.AccountCreateRequestToEntity(req, organizationID, outletID)
|
||||||
|
err = p.accountRepo.Create(ctx, entity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create account: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.AccountEntityToResponse(entity), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AccountProcessorImpl) GetAccountByID(ctx context.Context, id uuid.UUID) (*models.AccountResponse, error) {
|
||||||
|
entity, err := p.accountRepo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("account not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.AccountEntityToResponse(entity), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AccountProcessorImpl) UpdateAccount(ctx context.Context, id uuid.UUID, req *models.UpdateAccountRequest) (*models.AccountResponse, error) {
|
||||||
|
entity, err := p.accountRepo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("account not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if new number already exists (if number is being updated)
|
||||||
|
if req.Number != nil && *req.Number != entity.Number {
|
||||||
|
existing, err := p.accountRepo.GetByNumber(ctx, entity.OrganizationID, *req.Number, entity.OutletID)
|
||||||
|
if err == nil && existing != nil {
|
||||||
|
return nil, fmt.Errorf("account with number %s already exists", *req.Number)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate chart of account exists if provided
|
||||||
|
if req.ChartOfAccountID != nil {
|
||||||
|
_, err = p.chartOfAccountRepo.GetByID(ctx, *req.ChartOfAccountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("chart of account not found: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mappers.AccountUpdateRequestToEntity(entity, req)
|
||||||
|
err = p.accountRepo.Update(ctx, entity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update account: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.AccountEntityToResponse(entity), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AccountProcessorImpl) DeleteAccount(ctx context.Context, id uuid.UUID) error {
|
||||||
|
entity, err := p.accountRepo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("account not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent deletion of system accounts
|
||||||
|
if entity.IsSystem {
|
||||||
|
return fmt.Errorf("cannot delete system account")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.accountRepo.Delete(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete account: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AccountProcessorImpl) ListAccounts(ctx context.Context, req *models.ListAccountsRequest) ([]models.AccountResponse, int, error) {
|
||||||
|
// Get organization and outlet from context
|
||||||
|
appCtx := appcontext.FromGinContext(ctx)
|
||||||
|
organizationID := appCtx.OrganizationID
|
||||||
|
var outletID *uuid.UUID
|
||||||
|
if appCtx.OutletID != uuid.Nil {
|
||||||
|
outletID = &appCtx.OutletID
|
||||||
|
}
|
||||||
|
|
||||||
|
filterEntity := &entities.Account{
|
||||||
|
OrganizationID: organizationID,
|
||||||
|
OutletID: outletID,
|
||||||
|
ChartOfAccountID: *req.ChartOfAccountID,
|
||||||
|
AccountType: entities.AccountType(*req.AccountType),
|
||||||
|
}
|
||||||
|
|
||||||
|
entities, total, err := p.accountRepo.List(ctx, filterEntity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("failed to list accounts: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]models.AccountResponse, len(entities))
|
||||||
|
for i, entity := range entities {
|
||||||
|
responses[i] = *mappers.AccountEntityToResponse(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AccountProcessorImpl) GetAccountsByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]models.AccountResponse, error) {
|
||||||
|
entities, err := p.accountRepo.GetByOrganization(ctx, organizationID, outletID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get accounts by organization: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]models.AccountResponse, len(entities))
|
||||||
|
for i, entity := range entities {
|
||||||
|
responses[i] = *mappers.AccountEntityToResponse(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AccountProcessorImpl) GetAccountsByChartOfAccount(ctx context.Context, chartOfAccountID uuid.UUID) ([]models.AccountResponse, error) {
|
||||||
|
entities, err := p.accountRepo.GetByChartOfAccount(ctx, chartOfAccountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get accounts by chart of account: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]models.AccountResponse, len(entities))
|
||||||
|
for i, entity := range entities {
|
||||||
|
responses[i] = *mappers.AccountEntityToResponse(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AccountProcessorImpl) UpdateAccountBalance(ctx context.Context, id uuid.UUID, amount float64) error {
|
||||||
|
_, err := p.accountRepo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("account not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.accountRepo.UpdateBalance(ctx, id, amount)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update account balance: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AccountProcessorImpl) GetAccountBalance(ctx context.Context, id uuid.UUID) (float64, error) {
|
||||||
|
balance, err := p.accountRepo.GetBalance(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to get account balance: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return balance, nil
|
||||||
|
}
|
||||||
206
internal/processor/chart_of_account_processor.go
Normal file
206
internal/processor/chart_of_account_processor.go
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
package processor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/appcontext"
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"apskel-pos-be/internal/mappers"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type ChartOfAccountProcessor interface {
|
||||||
|
CreateChartOfAccount(ctx context.Context, req *models.CreateChartOfAccountRequest) (*models.ChartOfAccountResponse, error)
|
||||||
|
GetChartOfAccountByID(ctx context.Context, id uuid.UUID) (*models.ChartOfAccountResponse, error)
|
||||||
|
UpdateChartOfAccount(ctx context.Context, id uuid.UUID, req *models.UpdateChartOfAccountRequest) (*models.ChartOfAccountResponse, error)
|
||||||
|
DeleteChartOfAccount(ctx context.Context, id uuid.UUID) error
|
||||||
|
ListChartOfAccounts(ctx context.Context, req *models.ListChartOfAccountsRequest) ([]models.ChartOfAccountResponse, int, error)
|
||||||
|
GetChartOfAccountsByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]models.ChartOfAccountResponse, error)
|
||||||
|
GetChartOfAccountsByType(ctx context.Context, organizationID uuid.UUID, chartOfAccountTypeID uuid.UUID, outletID *uuid.UUID) ([]models.ChartOfAccountResponse, error)
|
||||||
|
CreateDefaultChartOfAccounts(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartOfAccountProcessorImpl struct {
|
||||||
|
chartOfAccountRepo ChartOfAccountRepository
|
||||||
|
chartOfAccountTypeRepo ChartOfAccountTypeRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChartOfAccountProcessorImpl(chartOfAccountRepo ChartOfAccountRepository, chartOfAccountTypeRepo ChartOfAccountTypeRepository) *ChartOfAccountProcessorImpl {
|
||||||
|
return &ChartOfAccountProcessorImpl{
|
||||||
|
chartOfAccountRepo: chartOfAccountRepo,
|
||||||
|
chartOfAccountTypeRepo: chartOfAccountTypeRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ChartOfAccountProcessorImpl) CreateChartOfAccount(ctx context.Context, req *models.CreateChartOfAccountRequest) (*models.ChartOfAccountResponse, error) {
|
||||||
|
// Get organization and outlet from context
|
||||||
|
appCtx := appcontext.FromGinContext(ctx)
|
||||||
|
organizationID := appCtx.OrganizationID
|
||||||
|
var outletID *uuid.UUID
|
||||||
|
if appCtx.OutletID != uuid.Nil {
|
||||||
|
outletID = &appCtx.OutletID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if code already exists for this organization/outlet
|
||||||
|
existing, err := p.chartOfAccountRepo.GetByCode(ctx, organizationID, req.Code, outletID)
|
||||||
|
if err == nil && existing != nil {
|
||||||
|
return nil, fmt.Errorf("chart of account with code %s already exists", req.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate parent exists if provided
|
||||||
|
if req.ParentID != nil {
|
||||||
|
_, err := p.chartOfAccountRepo.GetByID(ctx, *req.ParentID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parent chart of account not found: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate chart of account type exists
|
||||||
|
_, err = p.chartOfAccountTypeRepo.GetByID(ctx, req.ChartOfAccountTypeID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("chart of account type not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
entity := mappers.ChartOfAccountCreateRequestToEntity(req, organizationID, outletID)
|
||||||
|
err = p.chartOfAccountRepo.Create(ctx, entity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create chart of account: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.ChartOfAccountEntityToResponse(entity), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ChartOfAccountProcessorImpl) GetChartOfAccountByID(ctx context.Context, id uuid.UUID) (*models.ChartOfAccountResponse, error) {
|
||||||
|
entity, err := p.chartOfAccountRepo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("chart of account not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.ChartOfAccountEntityToResponse(entity), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ChartOfAccountProcessorImpl) UpdateChartOfAccount(ctx context.Context, id uuid.UUID, req *models.UpdateChartOfAccountRequest) (*models.ChartOfAccountResponse, error) {
|
||||||
|
entity, err := p.chartOfAccountRepo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("chart of account not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if new code already exists (if code is being updated)
|
||||||
|
if req.Code != nil && *req.Code != entity.Code {
|
||||||
|
existing, err := p.chartOfAccountRepo.GetByCode(ctx, entity.OrganizationID, *req.Code, entity.OutletID)
|
||||||
|
if err == nil && existing != nil {
|
||||||
|
return nil, fmt.Errorf("chart of account with code %s already exists", *req.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate parent exists if provided
|
||||||
|
if req.ParentID != nil {
|
||||||
|
_, err := p.chartOfAccountRepo.GetByID(ctx, *req.ParentID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parent chart of account not found: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate chart of account type exists if provided
|
||||||
|
if req.ChartOfAccountTypeID != nil {
|
||||||
|
_, err = p.chartOfAccountTypeRepo.GetByID(ctx, *req.ChartOfAccountTypeID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("chart of account type not found: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mappers.ChartOfAccountUpdateRequestToEntity(entity, req)
|
||||||
|
err = p.chartOfAccountRepo.Update(ctx, entity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update chart of account: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.ChartOfAccountEntityToResponse(entity), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ChartOfAccountProcessorImpl) DeleteChartOfAccount(ctx context.Context, id uuid.UUID) error {
|
||||||
|
entity, err := p.chartOfAccountRepo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("chart of account not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent deletion of system accounts
|
||||||
|
if entity.IsSystem {
|
||||||
|
return fmt.Errorf("cannot delete system chart of account")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.chartOfAccountRepo.Delete(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete chart of account: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ChartOfAccountProcessorImpl) ListChartOfAccounts(ctx context.Context, req *models.ListChartOfAccountsRequest) ([]models.ChartOfAccountResponse, int, error) {
|
||||||
|
// Get organization and outlet from context
|
||||||
|
appCtx := appcontext.FromGinContext(ctx)
|
||||||
|
organizationID := appCtx.OrganizationID
|
||||||
|
var outletID *uuid.UUID
|
||||||
|
if appCtx.OutletID != uuid.Nil {
|
||||||
|
outletID = &appCtx.OutletID
|
||||||
|
}
|
||||||
|
|
||||||
|
filterEntity := &entities.ChartOfAccount{
|
||||||
|
OrganizationID: organizationID,
|
||||||
|
OutletID: outletID,
|
||||||
|
ChartOfAccountTypeID: *req.ChartOfAccountTypeID,
|
||||||
|
ParentID: req.ParentID,
|
||||||
|
}
|
||||||
|
|
||||||
|
entities, total, err := p.chartOfAccountRepo.List(ctx, filterEntity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("failed to list chart of accounts: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]models.ChartOfAccountResponse, len(entities))
|
||||||
|
for i, entity := range entities {
|
||||||
|
responses[i] = *mappers.ChartOfAccountEntityToResponse(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ChartOfAccountProcessorImpl) GetChartOfAccountsByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]models.ChartOfAccountResponse, error) {
|
||||||
|
entities, err := p.chartOfAccountRepo.GetByOrganization(ctx, organizationID, outletID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get chart of accounts by organization: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]models.ChartOfAccountResponse, len(entities))
|
||||||
|
for i, entity := range entities {
|
||||||
|
responses[i] = *mappers.ChartOfAccountEntityToResponse(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ChartOfAccountProcessorImpl) GetChartOfAccountsByType(ctx context.Context, organizationID uuid.UUID, chartOfAccountTypeID uuid.UUID, outletID *uuid.UUID) ([]models.ChartOfAccountResponse, error) {
|
||||||
|
entities, err := p.chartOfAccountRepo.GetByType(ctx, organizationID, chartOfAccountTypeID, outletID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get chart of accounts by type: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]models.ChartOfAccountResponse, len(entities))
|
||||||
|
for i, entity := range entities {
|
||||||
|
responses[i] = *mappers.ChartOfAccountEntityToResponse(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ChartOfAccountProcessorImpl) CreateDefaultChartOfAccounts(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) error {
|
||||||
|
// This method will be implemented to create default chart of accounts
|
||||||
|
// based on the JSON template when a new organization/outlet is created
|
||||||
|
// For now, we'll return nil as this is a placeholder
|
||||||
|
return nil
|
||||||
|
}
|
||||||
106
internal/processor/chart_of_account_type_processor.go
Normal file
106
internal/processor/chart_of_account_type_processor.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package processor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/mappers"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type ChartOfAccountTypeProcessor interface {
|
||||||
|
CreateChartOfAccountType(ctx context.Context, req *models.CreateChartOfAccountTypeRequest) (*models.ChartOfAccountTypeResponse, error)
|
||||||
|
GetChartOfAccountTypeByID(ctx context.Context, id uuid.UUID) (*models.ChartOfAccountTypeResponse, error)
|
||||||
|
UpdateChartOfAccountType(ctx context.Context, id uuid.UUID, req *models.UpdateChartOfAccountTypeRequest) (*models.ChartOfAccountTypeResponse, error)
|
||||||
|
DeleteChartOfAccountType(ctx context.Context, id uuid.UUID) error
|
||||||
|
ListChartOfAccountTypes(ctx context.Context, filters map[string]interface{}, page, limit int) ([]models.ChartOfAccountTypeResponse, int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartOfAccountTypeProcessorImpl struct {
|
||||||
|
chartOfAccountTypeRepo ChartOfAccountTypeRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChartOfAccountTypeProcessorImpl(chartOfAccountTypeRepo ChartOfAccountTypeRepository) *ChartOfAccountTypeProcessorImpl {
|
||||||
|
return &ChartOfAccountTypeProcessorImpl{
|
||||||
|
chartOfAccountTypeRepo: chartOfAccountTypeRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ChartOfAccountTypeProcessorImpl) CreateChartOfAccountType(ctx context.Context, req *models.CreateChartOfAccountTypeRequest) (*models.ChartOfAccountTypeResponse, error) {
|
||||||
|
// Check if code already exists
|
||||||
|
existing, err := p.chartOfAccountTypeRepo.GetByCode(ctx, req.Code)
|
||||||
|
if err == nil && existing != nil {
|
||||||
|
return nil, fmt.Errorf("chart of account type with code %s already exists", req.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
entity := mappers.ChartOfAccountTypeCreateRequestToEntity(req)
|
||||||
|
err = p.chartOfAccountTypeRepo.Create(ctx, entity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create chart of account type: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.ChartOfAccountTypeEntityToResponse(entity), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ChartOfAccountTypeProcessorImpl) GetChartOfAccountTypeByID(ctx context.Context, id uuid.UUID) (*models.ChartOfAccountTypeResponse, error) {
|
||||||
|
entity, err := p.chartOfAccountTypeRepo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("chart of account type not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.ChartOfAccountTypeEntityToResponse(entity), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ChartOfAccountTypeProcessorImpl) UpdateChartOfAccountType(ctx context.Context, id uuid.UUID, req *models.UpdateChartOfAccountTypeRequest) (*models.ChartOfAccountTypeResponse, error) {
|
||||||
|
entity, err := p.chartOfAccountTypeRepo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("chart of account type not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if new code already exists (if code is being updated)
|
||||||
|
if req.Code != nil && *req.Code != entity.Code {
|
||||||
|
existing, err := p.chartOfAccountTypeRepo.GetByCode(ctx, *req.Code)
|
||||||
|
if err == nil && existing != nil {
|
||||||
|
return nil, fmt.Errorf("chart of account type with code %s already exists", *req.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mappers.ChartOfAccountTypeUpdateRequestToEntity(entity, req)
|
||||||
|
err = p.chartOfAccountTypeRepo.Update(ctx, entity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update chart of account type: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.ChartOfAccountTypeEntityToResponse(entity), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ChartOfAccountTypeProcessorImpl) DeleteChartOfAccountType(ctx context.Context, id uuid.UUID) error {
|
||||||
|
_, err := p.chartOfAccountTypeRepo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("chart of account type not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.chartOfAccountTypeRepo.Delete(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete chart of account type: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ChartOfAccountTypeProcessorImpl) ListChartOfAccountTypes(ctx context.Context, filters map[string]interface{}, page, limit int) ([]models.ChartOfAccountTypeResponse, int, error) {
|
||||||
|
entities, total, err := p.chartOfAccountTypeRepo.List(ctx, filters, page, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("failed to list chart of account types: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]models.ChartOfAccountTypeResponse, len(entities))
|
||||||
|
for i, entity := range entities {
|
||||||
|
responses[i] = *mappers.ChartOfAccountTypeEntityToResponse(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses, total, nil
|
||||||
|
}
|
||||||
259
internal/processor/ingredient_unit_converter_processor.go
Normal file
259
internal/processor/ingredient_unit_converter_processor.go
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
package processor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"apskel-pos-be/internal/mappers"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IngredientUnitConverterRepository interface {
|
||||||
|
Create(ctx context.Context, converter *entities.IngredientUnitConverter) error
|
||||||
|
GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.IngredientUnitConverter, error)
|
||||||
|
GetByIDAndOrganizationID(ctx context.Context, id, organizationID uuid.UUID) (*entities.IngredientUnitConverter, error)
|
||||||
|
Update(ctx context.Context, converter *entities.IngredientUnitConverter) error
|
||||||
|
Delete(ctx context.Context, id, organizationID uuid.UUID) error
|
||||||
|
List(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*entities.IngredientUnitConverter, int, error)
|
||||||
|
GetByIngredientAndUnits(ctx context.Context, ingredientID, fromUnitID, toUnitID, organizationID uuid.UUID) (*entities.IngredientUnitConverter, error)
|
||||||
|
GetConvertersForIngredient(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*entities.IngredientUnitConverter, error)
|
||||||
|
GetActiveConverters(ctx context.Context, organizationID uuid.UUID) ([]*entities.IngredientUnitConverter, error)
|
||||||
|
ConvertQuantity(ctx context.Context, ingredientID, fromUnitID, toUnitID, organizationID uuid.UUID, quantity float64) (float64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type IngredientUnitConverterProcessor interface {
|
||||||
|
CreateIngredientUnitConverter(ctx context.Context, organizationID, userID uuid.UUID, req *models.CreateIngredientUnitConverterRequest) (*models.IngredientUnitConverterResponse, error)
|
||||||
|
UpdateIngredientUnitConverter(ctx context.Context, id, organizationID, userID uuid.UUID, req *models.UpdateIngredientUnitConverterRequest) (*models.IngredientUnitConverterResponse, error)
|
||||||
|
DeleteIngredientUnitConverter(ctx context.Context, id, organizationID uuid.UUID) error
|
||||||
|
GetIngredientUnitConverterByID(ctx context.Context, id, organizationID uuid.UUID) (*models.IngredientUnitConverterResponse, error)
|
||||||
|
ListIngredientUnitConverters(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*models.IngredientUnitConverterResponse, int, error)
|
||||||
|
GetConvertersForIngredient(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*models.IngredientUnitConverterResponse, error)
|
||||||
|
ConvertUnit(ctx context.Context, organizationID uuid.UUID, req *models.ConvertUnitRequest) (*models.ConvertUnitResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type IngredientUnitConverterProcessorImpl struct {
|
||||||
|
converterRepo IngredientUnitConverterRepository
|
||||||
|
ingredientRepo IngredientRepository
|
||||||
|
unitRepo UnitRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIngredientUnitConverterProcessorImpl(
|
||||||
|
converterRepo IngredientUnitConverterRepository,
|
||||||
|
ingredientRepo IngredientRepository,
|
||||||
|
unitRepo UnitRepository,
|
||||||
|
) *IngredientUnitConverterProcessorImpl {
|
||||||
|
return &IngredientUnitConverterProcessorImpl{
|
||||||
|
converterRepo: converterRepo,
|
||||||
|
ingredientRepo: ingredientRepo,
|
||||||
|
unitRepo: unitRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *IngredientUnitConverterProcessorImpl) CreateIngredientUnitConverter(ctx context.Context, organizationID, userID uuid.UUID, req *models.CreateIngredientUnitConverterRequest) (*models.IngredientUnitConverterResponse, error) {
|
||||||
|
// Validate ingredient exists
|
||||||
|
_, err := p.ingredientRepo.GetByID(ctx, req.IngredientID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ingredient not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate units exist
|
||||||
|
_, err = p.unitRepo.GetByID(ctx, req.FromUnitID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("from unit not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = p.unitRepo.GetByID(ctx, req.ToUnitID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("to unit not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if converter already exists
|
||||||
|
existingConverter, err := p.converterRepo.GetByIngredientAndUnits(ctx, req.IngredientID, req.FromUnitID, req.ToUnitID, organizationID)
|
||||||
|
if err == nil && existingConverter != nil {
|
||||||
|
return nil, fmt.Errorf("converter already exists for this ingredient and unit combination")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default values
|
||||||
|
isActive := true
|
||||||
|
if req.IsActive != nil {
|
||||||
|
isActive = *req.IsActive
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create entity
|
||||||
|
converter := &entities.IngredientUnitConverter{
|
||||||
|
OrganizationID: organizationID,
|
||||||
|
IngredientID: req.IngredientID,
|
||||||
|
FromUnitID: req.FromUnitID,
|
||||||
|
ToUnitID: req.ToUnitID,
|
||||||
|
ConversionFactor: req.ConversionFactor,
|
||||||
|
IsActive: isActive,
|
||||||
|
CreatedBy: userID,
|
||||||
|
UpdatedBy: userID,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.converterRepo.Create(ctx, converter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create ingredient unit converter: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the created converter with relationships
|
||||||
|
createdConverter, err := p.converterRepo.GetByID(ctx, converter.ID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get created converter: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.IngredientUnitConverterEntityToResponse(createdConverter), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *IngredientUnitConverterProcessorImpl) UpdateIngredientUnitConverter(ctx context.Context, id, organizationID, userID uuid.UUID, req *models.UpdateIngredientUnitConverterRequest) (*models.IngredientUnitConverterResponse, error) {
|
||||||
|
// Get existing converter
|
||||||
|
converter, err := p.converterRepo.GetByID(ctx, id, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ingredient unit converter not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update fields if provided
|
||||||
|
if req.FromUnitID != nil {
|
||||||
|
// Validate new unit exists
|
||||||
|
_, err = p.unitRepo.GetByID(ctx, *req.FromUnitID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("from unit not found: %w", err)
|
||||||
|
}
|
||||||
|
converter.FromUnitID = *req.FromUnitID
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ToUnitID != nil {
|
||||||
|
// Validate new unit exists
|
||||||
|
_, err = p.unitRepo.GetByID(ctx, *req.ToUnitID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("to unit not found: %w", err)
|
||||||
|
}
|
||||||
|
converter.ToUnitID = *req.ToUnitID
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ConversionFactor != nil {
|
||||||
|
converter.ConversionFactor = *req.ConversionFactor
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.IsActive != nil {
|
||||||
|
converter.IsActive = *req.IsActive
|
||||||
|
}
|
||||||
|
|
||||||
|
converter.UpdatedBy = userID
|
||||||
|
|
||||||
|
err = p.converterRepo.Update(ctx, converter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update ingredient unit converter: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the updated converter with relationships
|
||||||
|
updatedConverter, err := p.converterRepo.GetByID(ctx, converter.ID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get updated converter: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.IngredientUnitConverterEntityToResponse(updatedConverter), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *IngredientUnitConverterProcessorImpl) DeleteIngredientUnitConverter(ctx context.Context, id, organizationID uuid.UUID) error {
|
||||||
|
// Check if converter exists
|
||||||
|
_, err := p.converterRepo.GetByID(ctx, id, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ingredient unit converter not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.converterRepo.Delete(ctx, id, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete ingredient unit converter: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *IngredientUnitConverterProcessorImpl) GetIngredientUnitConverterByID(ctx context.Context, id, organizationID uuid.UUID) (*models.IngredientUnitConverterResponse, error) {
|
||||||
|
converter, err := p.converterRepo.GetByID(ctx, id, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ingredient unit converter not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.IngredientUnitConverterEntityToResponse(converter), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *IngredientUnitConverterProcessorImpl) ListIngredientUnitConverters(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*models.IngredientUnitConverterResponse, int, error) {
|
||||||
|
converters, total, err := p.converterRepo.List(ctx, organizationID, filters, page, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("failed to list ingredient unit converters: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]*models.IngredientUnitConverterResponse, len(converters))
|
||||||
|
for i, converter := range converters {
|
||||||
|
responses[i] = mappers.IngredientUnitConverterEntityToResponse(converter)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *IngredientUnitConverterProcessorImpl) GetConvertersForIngredient(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*models.IngredientUnitConverterResponse, error) {
|
||||||
|
converters, err := p.converterRepo.GetConvertersForIngredient(ctx, ingredientID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get converters for ingredient: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]*models.IngredientUnitConverterResponse, len(converters))
|
||||||
|
for i, converter := range converters {
|
||||||
|
responses[i] = mappers.IngredientUnitConverterEntityToResponse(converter)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *IngredientUnitConverterProcessorImpl) ConvertUnit(ctx context.Context, organizationID uuid.UUID, req *models.ConvertUnitRequest) (*models.ConvertUnitResponse, error) {
|
||||||
|
// Get ingredient and units for response
|
||||||
|
ingredient, err := p.ingredientRepo.GetByID(ctx, req.IngredientID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ingredient not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fromUnit, err := p.unitRepo.GetByID(ctx, req.FromUnitID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("from unit not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toUnit, err := p.unitRepo.GetByID(ctx, req.ToUnitID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("to unit not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert quantity
|
||||||
|
convertedQuantity, err := p.converterRepo.ConvertQuantity(ctx, req.IngredientID, req.FromUnitID, req.ToUnitID, organizationID, req.Quantity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to convert quantity: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get conversion factor for response
|
||||||
|
converter, err := p.converterRepo.GetByIngredientAndUnits(ctx, req.IngredientID, req.FromUnitID, req.ToUnitID, organizationID)
|
||||||
|
var conversionFactor float64
|
||||||
|
if err == nil {
|
||||||
|
conversionFactor = converter.ConversionFactor
|
||||||
|
} else {
|
||||||
|
// Try reverse converter
|
||||||
|
reverseConverter, err := p.converterRepo.GetByIngredientAndUnits(ctx, req.IngredientID, req.ToUnitID, req.FromUnitID, organizationID)
|
||||||
|
if err == nil {
|
||||||
|
conversionFactor = 1.0 / reverseConverter.ConversionFactor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &models.ConvertUnitResponse{
|
||||||
|
FromQuantity: req.Quantity,
|
||||||
|
FromUnit: mappers.MapUnitEntityToResponse(fromUnit),
|
||||||
|
ToQuantity: convertedQuantity,
|
||||||
|
ToUnit: mappers.MapUnitEntityToResponse(toUnit),
|
||||||
|
ConversionFactor: conversionFactor,
|
||||||
|
Ingredient: mappers.MapIngredientEntityToResponse(ingredient),
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
441
internal/processor/purchase_order_processor.go
Normal file
441
internal/processor/purchase_order_processor.go
Normal file
@ -0,0 +1,441 @@
|
|||||||
|
package processor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"apskel-pos-be/internal/mappers"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PurchaseOrderProcessor interface {
|
||||||
|
CreatePurchaseOrder(ctx context.Context, organizationID uuid.UUID, req *models.CreatePurchaseOrderRequest) (*models.PurchaseOrderResponse, error)
|
||||||
|
UpdatePurchaseOrder(ctx context.Context, id, organizationID uuid.UUID, req *models.UpdatePurchaseOrderRequest) (*models.PurchaseOrderResponse, error)
|
||||||
|
DeletePurchaseOrder(ctx context.Context, id, organizationID uuid.UUID) error
|
||||||
|
GetPurchaseOrderByID(ctx context.Context, id, organizationID uuid.UUID) (*models.PurchaseOrderResponse, error)
|
||||||
|
ListPurchaseOrders(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*models.PurchaseOrderResponse, int, error)
|
||||||
|
GetPurchaseOrdersByStatus(ctx context.Context, organizationID uuid.UUID, status string) ([]*models.PurchaseOrderResponse, error)
|
||||||
|
GetOverduePurchaseOrders(ctx context.Context, organizationID uuid.UUID) ([]*models.PurchaseOrderResponse, error)
|
||||||
|
UpdatePurchaseOrderStatus(ctx context.Context, id, organizationID, userID, outletID uuid.UUID, status string) (*models.PurchaseOrderResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PurchaseOrderProcessorImpl struct {
|
||||||
|
purchaseOrderRepo PurchaseOrderRepository
|
||||||
|
vendorRepo VendorRepository
|
||||||
|
ingredientRepo IngredientRepository
|
||||||
|
unitRepo UnitRepository
|
||||||
|
fileRepo FileRepository
|
||||||
|
inventoryMovementService InventoryMovementService
|
||||||
|
unitConverterRepo IngredientUnitConverterRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPurchaseOrderProcessorImpl(
|
||||||
|
purchaseOrderRepo PurchaseOrderRepository,
|
||||||
|
vendorRepo VendorRepository,
|
||||||
|
ingredientRepo IngredientRepository,
|
||||||
|
unitRepo UnitRepository,
|
||||||
|
fileRepo FileRepository,
|
||||||
|
inventoryMovementService InventoryMovementService,
|
||||||
|
unitConverterRepo IngredientUnitConverterRepository,
|
||||||
|
) *PurchaseOrderProcessorImpl {
|
||||||
|
return &PurchaseOrderProcessorImpl{
|
||||||
|
purchaseOrderRepo: purchaseOrderRepo,
|
||||||
|
vendorRepo: vendorRepo,
|
||||||
|
ingredientRepo: ingredientRepo,
|
||||||
|
unitRepo: unitRepo,
|
||||||
|
fileRepo: fileRepo,
|
||||||
|
inventoryMovementService: inventoryMovementService,
|
||||||
|
unitConverterRepo: unitConverterRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PurchaseOrderProcessorImpl) CreatePurchaseOrder(ctx context.Context, organizationID uuid.UUID, req *models.CreatePurchaseOrderRequest) (*models.PurchaseOrderResponse, error) {
|
||||||
|
// Check if vendor exists and belongs to organization
|
||||||
|
_, err := p.vendorRepo.GetByIDAndOrganizationID(ctx, req.VendorID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("vendor not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if PO number already exists in organization
|
||||||
|
existingPO, err := p.purchaseOrderRepo.GetByPONumber(ctx, req.PONumber, organizationID)
|
||||||
|
if err == nil && existingPO != nil {
|
||||||
|
return nil, fmt.Errorf("purchase order with PO number %s already exists in this organization", req.PONumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate ingredients and units exist
|
||||||
|
for i, item := range req.Items {
|
||||||
|
_, err := p.ingredientRepo.GetByID(ctx, item.IngredientID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ingredient not found for item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = p.unitRepo.GetByID(ctx, item.UnitID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unit not found for item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate total amount
|
||||||
|
totalAmount := 0.0
|
||||||
|
for _, item := range req.Items {
|
||||||
|
totalAmount += item.Amount
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create purchase order entity
|
||||||
|
poEntity := &entities.PurchaseOrder{
|
||||||
|
OrganizationID: organizationID,
|
||||||
|
VendorID: req.VendorID,
|
||||||
|
PONumber: req.PONumber,
|
||||||
|
TransactionDate: req.TransactionDate,
|
||||||
|
DueDate: req.DueDate,
|
||||||
|
Reference: req.Reference,
|
||||||
|
Status: "draft", // Default status
|
||||||
|
Message: req.Message,
|
||||||
|
TotalAmount: totalAmount,
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Status != nil {
|
||||||
|
poEntity.Status = *req.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create purchase order
|
||||||
|
err = p.purchaseOrderRepo.Create(ctx, poEntity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create purchase order: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create purchase order items
|
||||||
|
for _, itemReq := range req.Items {
|
||||||
|
itemEntity := &entities.PurchaseOrderItem{
|
||||||
|
PurchaseOrderID: poEntity.ID,
|
||||||
|
IngredientID: itemReq.IngredientID,
|
||||||
|
Description: itemReq.Description,
|
||||||
|
Quantity: itemReq.Quantity,
|
||||||
|
UnitID: itemReq.UnitID,
|
||||||
|
Amount: itemReq.Amount,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.purchaseOrderRepo.CreateItem(ctx, itemEntity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create purchase order item: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create attachments if provided
|
||||||
|
for _, fileID := range req.AttachmentFileIDs {
|
||||||
|
attachmentEntity := &entities.PurchaseOrderAttachment{
|
||||||
|
PurchaseOrderID: poEntity.ID,
|
||||||
|
FileID: fileID,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.purchaseOrderRepo.CreateAttachment(ctx, attachmentEntity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create purchase order attachment: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the created purchase order with all relations
|
||||||
|
createdPO, err := p.purchaseOrderRepo.GetByID(ctx, poEntity.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get created purchase order: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.PurchaseOrderEntityToResponse(createdPO), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PurchaseOrderProcessorImpl) UpdatePurchaseOrder(ctx context.Context, id, organizationID uuid.UUID, req *models.UpdatePurchaseOrderRequest) (*models.PurchaseOrderResponse, error) {
|
||||||
|
// Get existing purchase order
|
||||||
|
poEntity, err := p.purchaseOrderRepo.GetByIDAndOrganizationID(ctx, id, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("purchase order not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if vendor exists and belongs to organization (if vendor is being updated)
|
||||||
|
if req.VendorID != nil {
|
||||||
|
_, err := p.vendorRepo.GetByIDAndOrganizationID(ctx, *req.VendorID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("vendor not found: %w", err)
|
||||||
|
}
|
||||||
|
poEntity.VendorID = *req.VendorID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if PO number already exists (if PO number is being updated)
|
||||||
|
if req.PONumber != nil && *req.PONumber != poEntity.PONumber {
|
||||||
|
existingPO, err := p.purchaseOrderRepo.GetByPONumber(ctx, *req.PONumber, organizationID)
|
||||||
|
if err == nil && existingPO != nil {
|
||||||
|
return nil, fmt.Errorf("purchase order with PO number %s already exists in this organization", *req.PONumber)
|
||||||
|
}
|
||||||
|
poEntity.PONumber = *req.PONumber
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update other fields
|
||||||
|
if req.TransactionDate != nil {
|
||||||
|
poEntity.TransactionDate = *req.TransactionDate
|
||||||
|
}
|
||||||
|
if req.DueDate != nil {
|
||||||
|
poEntity.DueDate = *req.DueDate
|
||||||
|
}
|
||||||
|
if req.Reference != nil {
|
||||||
|
poEntity.Reference = req.Reference
|
||||||
|
}
|
||||||
|
if req.Status != nil {
|
||||||
|
poEntity.Status = *req.Status
|
||||||
|
}
|
||||||
|
if req.Message != nil {
|
||||||
|
poEntity.Message = req.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update items if provided
|
||||||
|
if req.Items != nil {
|
||||||
|
// Delete existing items
|
||||||
|
err = p.purchaseOrderRepo.DeleteItemsByPurchaseOrderID(ctx, poEntity.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to delete existing items: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new items
|
||||||
|
totalAmount := 0.0
|
||||||
|
for _, itemReq := range req.Items {
|
||||||
|
// Validate ingredients and units exist
|
||||||
|
if itemReq.IngredientID != nil {
|
||||||
|
_, err := p.ingredientRepo.GetByID(ctx, *itemReq.IngredientID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ingredient not found: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if itemReq.UnitID != nil {
|
||||||
|
_, err := p.unitRepo.GetByID(ctx, *itemReq.UnitID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unit not found: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use existing values if not provided
|
||||||
|
ingredientID := poEntity.Items[0].IngredientID // This is a simplified approach
|
||||||
|
unitID := poEntity.Items[0].UnitID
|
||||||
|
quantity := poEntity.Items[0].Quantity
|
||||||
|
amount := poEntity.Items[0].Amount
|
||||||
|
description := poEntity.Items[0].Description
|
||||||
|
|
||||||
|
if itemReq.IngredientID != nil {
|
||||||
|
ingredientID = *itemReq.IngredientID
|
||||||
|
}
|
||||||
|
if itemReq.UnitID != nil {
|
||||||
|
unitID = *itemReq.UnitID
|
||||||
|
}
|
||||||
|
if itemReq.Quantity != nil {
|
||||||
|
quantity = *itemReq.Quantity
|
||||||
|
}
|
||||||
|
if itemReq.Amount != nil {
|
||||||
|
amount = *itemReq.Amount
|
||||||
|
}
|
||||||
|
if itemReq.Description != nil {
|
||||||
|
description = itemReq.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
itemEntity := &entities.PurchaseOrderItem{
|
||||||
|
PurchaseOrderID: poEntity.ID,
|
||||||
|
IngredientID: ingredientID,
|
||||||
|
Description: description,
|
||||||
|
Quantity: quantity,
|
||||||
|
UnitID: unitID,
|
||||||
|
Amount: amount,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.purchaseOrderRepo.CreateItem(ctx, itemEntity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create purchase order item: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
totalAmount += amount
|
||||||
|
}
|
||||||
|
|
||||||
|
poEntity.TotalAmount = totalAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update attachments if provided
|
||||||
|
if req.AttachmentFileIDs != nil {
|
||||||
|
// Delete existing attachments
|
||||||
|
err = p.purchaseOrderRepo.DeleteAttachmentsByPurchaseOrderID(ctx, poEntity.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to delete existing attachments: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new attachments
|
||||||
|
for _, fileID := range req.AttachmentFileIDs {
|
||||||
|
attachmentEntity := &entities.PurchaseOrderAttachment{
|
||||||
|
PurchaseOrderID: poEntity.ID,
|
||||||
|
FileID: fileID,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.purchaseOrderRepo.CreateAttachment(ctx, attachmentEntity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create purchase order attachment: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update purchase order
|
||||||
|
err = p.purchaseOrderRepo.Update(ctx, poEntity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update purchase order: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the updated purchase order with all relations
|
||||||
|
updatedPO, err := p.purchaseOrderRepo.GetByID(ctx, poEntity.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get updated purchase order: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.PurchaseOrderEntityToResponse(updatedPO), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PurchaseOrderProcessorImpl) DeletePurchaseOrder(ctx context.Context, id, organizationID uuid.UUID) error {
|
||||||
|
_, err := p.purchaseOrderRepo.GetByIDAndOrganizationID(ctx, id, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("purchase order not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.purchaseOrderRepo.Delete(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete purchase order: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PurchaseOrderProcessorImpl) GetPurchaseOrderByID(ctx context.Context, id, organizationID uuid.UUID) (*models.PurchaseOrderResponse, error) {
|
||||||
|
poEntity, err := p.purchaseOrderRepo.GetByIDAndOrganizationID(ctx, id, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("purchase order not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.PurchaseOrderEntityToResponse(poEntity), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PurchaseOrderProcessorImpl) ListPurchaseOrders(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*models.PurchaseOrderResponse, int, error) {
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
poEntities, total, err := p.purchaseOrderRepo.List(ctx, organizationID, filters, limit, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("failed to list purchase orders: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
poResponses := make([]*models.PurchaseOrderResponse, len(poEntities))
|
||||||
|
for i, poEntity := range poEntities {
|
||||||
|
poResponses[i] = mappers.PurchaseOrderEntityToResponse(poEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
totalPages := int((total + int64(limit) - 1) / int64(limit))
|
||||||
|
return poResponses, totalPages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PurchaseOrderProcessorImpl) GetPurchaseOrdersByStatus(ctx context.Context, organizationID uuid.UUID, status string) ([]*models.PurchaseOrderResponse, error) {
|
||||||
|
poEntities, err := p.purchaseOrderRepo.GetByStatus(ctx, organizationID, status)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get purchase orders by status: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
poResponses := make([]*models.PurchaseOrderResponse, len(poEntities))
|
||||||
|
for i, poEntity := range poEntities {
|
||||||
|
poResponses[i] = mappers.PurchaseOrderEntityToResponse(poEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return poResponses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PurchaseOrderProcessorImpl) GetOverduePurchaseOrders(ctx context.Context, organizationID uuid.UUID) ([]*models.PurchaseOrderResponse, error) {
|
||||||
|
poEntities, err := p.purchaseOrderRepo.GetOverdue(ctx, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get overdue purchase orders: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
poResponses := make([]*models.PurchaseOrderResponse, len(poEntities))
|
||||||
|
for i, poEntity := range poEntities {
|
||||||
|
poResponses[i] = mappers.PurchaseOrderEntityToResponse(poEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
return poResponses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PurchaseOrderProcessorImpl) UpdatePurchaseOrderStatus(ctx context.Context, id, organizationID, userID, outletID uuid.UUID, status string) (*models.PurchaseOrderResponse, error) {
|
||||||
|
// Get the purchase order with items to check current status
|
||||||
|
po, err := p.purchaseOrderRepo.GetByIDAndOrganizationID(ctx, id, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("purchase order not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if status is changing to "received" and current status is not "received"
|
||||||
|
if status == "received" && po.Status != "received" {
|
||||||
|
// Get purchase order with items for inventory update
|
||||||
|
poWithItems, err := p.purchaseOrderRepo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get purchase order with items: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update inventory for each item
|
||||||
|
for _, item := range poWithItems.Items {
|
||||||
|
// Get ingredient to find its base unit
|
||||||
|
ingredient, err := p.ingredientRepo.GetByID(ctx, item.IngredientID, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get ingredient %s: %w", item.IngredientID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert quantity to ingredient's base unit if needed
|
||||||
|
quantityToAdd := item.Quantity
|
||||||
|
if item.UnitID != ingredient.UnitID {
|
||||||
|
// Convert from purchase unit to ingredient's base unit
|
||||||
|
convertedQuantity, err := p.unitConverterRepo.ConvertQuantity(ctx, item.IngredientID, item.UnitID, ingredient.UnitID, organizationID, item.Quantity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to convert quantity for ingredient %s from unit %s to %s: %w", item.IngredientID, item.UnitID, ingredient.UnitID, err)
|
||||||
|
}
|
||||||
|
quantityToAdd = convertedQuantity
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate unit cost in ingredient's base unit
|
||||||
|
unitCost := 0.0
|
||||||
|
if quantityToAdd > 0 {
|
||||||
|
unitCost = item.Amount / quantityToAdd
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create inventory movement for ingredient purchase
|
||||||
|
reason := fmt.Sprintf("Purchase order %s received", po.PONumber)
|
||||||
|
referenceType := entities.InventoryMovementReferenceTypePurchaseOrder
|
||||||
|
referenceID := &id
|
||||||
|
|
||||||
|
err = p.inventoryMovementService.CreateIngredientMovement(
|
||||||
|
ctx,
|
||||||
|
item.IngredientID,
|
||||||
|
organizationID,
|
||||||
|
outletID,
|
||||||
|
userID,
|
||||||
|
entities.InventoryMovementTypePurchase,
|
||||||
|
quantityToAdd,
|
||||||
|
unitCost,
|
||||||
|
reason,
|
||||||
|
&referenceType,
|
||||||
|
referenceID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create inventory movement for ingredient %s: %w", item.IngredientID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the purchase order status
|
||||||
|
err = p.purchaseOrderRepo.UpdateStatus(ctx, id, status)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update purchase order status: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the updated purchase order
|
||||||
|
updatedPO, err := p.purchaseOrderRepo.GetByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get updated purchase order: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.PurchaseOrderEntityToResponse(updatedPO), nil
|
||||||
|
}
|
||||||
32
internal/processor/purchase_order_repository.go
Normal file
32
internal/processor/purchase_order_repository.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package processor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PurchaseOrderRepository interface {
|
||||||
|
Create(ctx context.Context, po *entities.PurchaseOrder) error
|
||||||
|
GetByID(ctx context.Context, id uuid.UUID) (*entities.PurchaseOrder, error)
|
||||||
|
GetByIDAndOrganizationID(ctx context.Context, id, organizationID uuid.UUID) (*entities.PurchaseOrder, error)
|
||||||
|
Update(ctx context.Context, po *entities.PurchaseOrder) error
|
||||||
|
Delete(ctx context.Context, id uuid.UUID) error
|
||||||
|
List(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, limit, offset int) ([]*entities.PurchaseOrder, int64, error)
|
||||||
|
Count(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}) (int64, error)
|
||||||
|
GetByPONumber(ctx context.Context, poNumber string, organizationID uuid.UUID) (*entities.PurchaseOrder, error)
|
||||||
|
GetByStatus(ctx context.Context, organizationID uuid.UUID, status string) ([]*entities.PurchaseOrder, error)
|
||||||
|
GetOverdue(ctx context.Context, organizationID uuid.UUID) ([]*entities.PurchaseOrder, error)
|
||||||
|
UpdateStatus(ctx context.Context, id uuid.UUID, status string) error
|
||||||
|
UpdateTotalAmount(ctx context.Context, id uuid.UUID, totalAmount float64) error
|
||||||
|
CreateItem(ctx context.Context, item *entities.PurchaseOrderItem) error
|
||||||
|
UpdateItem(ctx context.Context, item *entities.PurchaseOrderItem) error
|
||||||
|
DeleteItem(ctx context.Context, id uuid.UUID) error
|
||||||
|
DeleteItemsByPurchaseOrderID(ctx context.Context, purchaseOrderID uuid.UUID) error
|
||||||
|
GetItemsByPurchaseOrderID(ctx context.Context, purchaseOrderID uuid.UUID) ([]*entities.PurchaseOrderItem, error)
|
||||||
|
CreateAttachment(ctx context.Context, attachment *entities.PurchaseOrderAttachment) error
|
||||||
|
DeleteAttachment(ctx context.Context, id uuid.UUID) error
|
||||||
|
DeleteAttachmentsByPurchaseOrderID(ctx context.Context, purchaseOrderID uuid.UUID) error
|
||||||
|
GetAttachmentsByPurchaseOrderID(ctx context.Context, purchaseOrderID uuid.UUID) ([]*entities.PurchaseOrderAttachment, error)
|
||||||
|
}
|
||||||
44
internal/processor/repository_interfaces.go
Normal file
44
internal/processor/repository_interfaces.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package processor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Repository interfaces for processors
|
||||||
|
type ChartOfAccountTypeRepository interface {
|
||||||
|
Create(ctx context.Context, chartOfAccountType *entities.ChartOfAccountType) error
|
||||||
|
GetByID(ctx context.Context, id uuid.UUID) (*entities.ChartOfAccountType, error)
|
||||||
|
GetByCode(ctx context.Context, code string) (*entities.ChartOfAccountType, error)
|
||||||
|
Update(ctx context.Context, chartOfAccountType *entities.ChartOfAccountType) error
|
||||||
|
Delete(ctx context.Context, id uuid.UUID) error
|
||||||
|
List(ctx context.Context, filters map[string]interface{}, page, limit int) ([]*entities.ChartOfAccountType, int, error)
|
||||||
|
GetActive(ctx context.Context) ([]*entities.ChartOfAccountType, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartOfAccountRepository interface {
|
||||||
|
Create(ctx context.Context, chartOfAccount *entities.ChartOfAccount) error
|
||||||
|
GetByID(ctx context.Context, id uuid.UUID) (*entities.ChartOfAccount, error)
|
||||||
|
Update(ctx context.Context, chartOfAccount *entities.ChartOfAccount) error
|
||||||
|
Delete(ctx context.Context, id uuid.UUID) error
|
||||||
|
List(ctx context.Context, req *entities.ChartOfAccount) ([]*entities.ChartOfAccount, int, error)
|
||||||
|
GetByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]*entities.ChartOfAccount, error)
|
||||||
|
GetByType(ctx context.Context, organizationID uuid.UUID, chartOfAccountTypeID uuid.UUID, outletID *uuid.UUID) ([]*entities.ChartOfAccount, error)
|
||||||
|
GetByCode(ctx context.Context, organizationID uuid.UUID, code string, outletID *uuid.UUID) (*entities.ChartOfAccount, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountRepository interface {
|
||||||
|
Create(ctx context.Context, account *entities.Account) error
|
||||||
|
GetByID(ctx context.Context, id uuid.UUID) (*entities.Account, error)
|
||||||
|
Update(ctx context.Context, account *entities.Account) error
|
||||||
|
Delete(ctx context.Context, id uuid.UUID) error
|
||||||
|
List(ctx context.Context, req *entities.Account) ([]*entities.Account, int, error)
|
||||||
|
GetByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]*entities.Account, error)
|
||||||
|
GetByChartOfAccount(ctx context.Context, chartOfAccountID uuid.UUID) ([]*entities.Account, error)
|
||||||
|
GetByNumber(ctx context.Context, organizationID uuid.UUID, number string, outletID *uuid.UUID) (*entities.Account, error)
|
||||||
|
UpdateBalance(ctx context.Context, id uuid.UUID, amount float64) error
|
||||||
|
GetBalance(ctx context.Context, id uuid.UUID) (float64, error)
|
||||||
|
}
|
||||||
177
internal/processor/vendor_processor.go
Normal file
177
internal/processor/vendor_processor.go
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
package processor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"apskel-pos-be/internal/mappers"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VendorProcessor interface {
|
||||||
|
CreateVendor(ctx context.Context, organizationID uuid.UUID, req *models.CreateVendorRequest) (*models.VendorResponse, error)
|
||||||
|
UpdateVendor(ctx context.Context, id, organizationID uuid.UUID, req *models.UpdateVendorRequest) (*models.VendorResponse, error)
|
||||||
|
DeleteVendor(ctx context.Context, id, organizationID uuid.UUID) error
|
||||||
|
GetVendorByID(ctx context.Context, id, organizationID uuid.UUID) (*models.VendorResponse, error)
|
||||||
|
ListVendors(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*models.VendorResponse, int, error)
|
||||||
|
GetActiveVendors(ctx context.Context, organizationID uuid.UUID) ([]*models.VendorResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type VendorProcessorImpl struct {
|
||||||
|
vendorRepo VendorRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVendorProcessorImpl(vendorRepo VendorRepository) *VendorProcessorImpl {
|
||||||
|
return &VendorProcessorImpl{
|
||||||
|
vendorRepo: vendorRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *VendorProcessorImpl) CreateVendor(ctx context.Context, organizationID uuid.UUID, req *models.CreateVendorRequest) (*models.VendorResponse, error) {
|
||||||
|
// Check if vendor with same name already exists in organization
|
||||||
|
if req.Name != "" {
|
||||||
|
existingVendor, err := p.vendorRepo.GetByName(ctx, req.Name, organizationID)
|
||||||
|
if err == nil && existingVendor != nil {
|
||||||
|
return nil, fmt.Errorf("vendor with name %s already exists in this organization", req.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if vendor with same email already exists in organization
|
||||||
|
if req.Email != nil && *req.Email != "" {
|
||||||
|
existingVendor, err := p.vendorRepo.GetByEmail(ctx, *req.Email, organizationID)
|
||||||
|
if err == nil && existingVendor != nil {
|
||||||
|
return nil, fmt.Errorf("vendor with email %s already exists in this organization", *req.Email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vendorEntity := &entities.Vendor{
|
||||||
|
OrganizationID: organizationID,
|
||||||
|
Name: req.Name,
|
||||||
|
Email: req.Email,
|
||||||
|
PhoneNumber: req.PhoneNumber,
|
||||||
|
Address: req.Address,
|
||||||
|
ContactPerson: req.ContactPerson,
|
||||||
|
TaxNumber: req.TaxNumber,
|
||||||
|
PaymentTerms: req.PaymentTerms,
|
||||||
|
Notes: req.Notes,
|
||||||
|
IsActive: true, // Default to active
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.IsActive != nil {
|
||||||
|
vendorEntity.IsActive = *req.IsActive
|
||||||
|
}
|
||||||
|
|
||||||
|
err := p.vendorRepo.Create(ctx, vendorEntity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create vendor: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.VendorEntityToResponse(vendorEntity), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *VendorProcessorImpl) UpdateVendor(ctx context.Context, id, organizationID uuid.UUID, req *models.UpdateVendorRequest) (*models.VendorResponse, error) {
|
||||||
|
vendorEntity, err := p.vendorRepo.GetByIDAndOrganizationID(ctx, id, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("vendor not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if vendor with same name already exists (excluding current vendor)
|
||||||
|
if req.Name != nil && *req.Name != "" && *req.Name != vendorEntity.Name {
|
||||||
|
existingVendor, err := p.vendorRepo.GetByName(ctx, *req.Name, organizationID)
|
||||||
|
if err == nil && existingVendor != nil && existingVendor.ID != id {
|
||||||
|
return nil, fmt.Errorf("vendor with name %s already exists in this organization", *req.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if vendor with same email already exists (excluding current vendor)
|
||||||
|
if req.Email != nil && *req.Email != "" && (vendorEntity.Email == nil || *req.Email != *vendorEntity.Email) {
|
||||||
|
existingVendor, err := p.vendorRepo.GetByEmail(ctx, *req.Email, organizationID)
|
||||||
|
if err == nil && existingVendor != nil && existingVendor.ID != id {
|
||||||
|
return nil, fmt.Errorf("vendor with email %s already exists in this organization", *req.Email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update fields
|
||||||
|
if req.Name != nil {
|
||||||
|
vendorEntity.Name = *req.Name
|
||||||
|
}
|
||||||
|
if req.Email != nil {
|
||||||
|
vendorEntity.Email = req.Email
|
||||||
|
}
|
||||||
|
if req.PhoneNumber != nil {
|
||||||
|
vendorEntity.PhoneNumber = req.PhoneNumber
|
||||||
|
}
|
||||||
|
if req.Address != nil {
|
||||||
|
vendorEntity.Address = req.Address
|
||||||
|
}
|
||||||
|
if req.ContactPerson != nil {
|
||||||
|
vendorEntity.ContactPerson = req.ContactPerson
|
||||||
|
}
|
||||||
|
if req.TaxNumber != nil {
|
||||||
|
vendorEntity.TaxNumber = req.TaxNumber
|
||||||
|
}
|
||||||
|
if req.PaymentTerms != nil {
|
||||||
|
vendorEntity.PaymentTerms = req.PaymentTerms
|
||||||
|
}
|
||||||
|
if req.Notes != nil {
|
||||||
|
vendorEntity.Notes = req.Notes
|
||||||
|
}
|
||||||
|
if req.IsActive != nil {
|
||||||
|
vendorEntity.IsActive = *req.IsActive
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.vendorRepo.Update(ctx, vendorEntity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update vendor: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.VendorEntityToResponse(vendorEntity), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *VendorProcessorImpl) DeleteVendor(ctx context.Context, id, organizationID uuid.UUID) error {
|
||||||
|
_, err := p.vendorRepo.GetByIDAndOrganizationID(ctx, id, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("vendor not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.vendorRepo.Delete(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete vendor: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *VendorProcessorImpl) GetVendorByID(ctx context.Context, id, organizationID uuid.UUID) (*models.VendorResponse, error) {
|
||||||
|
vendorEntity, err := p.vendorRepo.GetByIDAndOrganizationID(ctx, id, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("vendor not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.VendorEntityToResponse(vendorEntity), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *VendorProcessorImpl) ListVendors(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*models.VendorResponse, int, error) {
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
vendorEntities, total, err := p.vendorRepo.List(ctx, organizationID, filters, limit, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("failed to list vendors: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vendorResponses := mappers.VendorEntitiesToResponses(vendorEntities)
|
||||||
|
totalPages := int((total + int64(limit) - 1) / int64(limit))
|
||||||
|
|
||||||
|
return vendorResponses, totalPages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *VendorProcessorImpl) GetActiveVendors(ctx context.Context, organizationID uuid.UUID) ([]*models.VendorResponse, error) {
|
||||||
|
vendorEntities, err := p.vendorRepo.GetActiveVendors(ctx, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get active vendors: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappers.VendorEntitiesToResponses(vendorEntities), nil
|
||||||
|
}
|
||||||
21
internal/processor/vendor_repository.go
Normal file
21
internal/processor/vendor_repository.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package processor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VendorRepository interface {
|
||||||
|
Create(ctx context.Context, vendor *entities.Vendor) error
|
||||||
|
GetByID(ctx context.Context, id uuid.UUID) (*entities.Vendor, error)
|
||||||
|
GetByIDAndOrganizationID(ctx context.Context, id, organizationID uuid.UUID) (*entities.Vendor, error)
|
||||||
|
Update(ctx context.Context, vendor *entities.Vendor) error
|
||||||
|
Delete(ctx context.Context, id uuid.UUID) error
|
||||||
|
List(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, limit, offset int) ([]*entities.Vendor, int64, error)
|
||||||
|
Count(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}) (int64, error)
|
||||||
|
GetByEmail(ctx context.Context, email string, organizationID uuid.UUID) (*entities.Vendor, error)
|
||||||
|
GetByName(ctx context.Context, name string, organizationID uuid.UUID) (*entities.Vendor, error)
|
||||||
|
GetActiveVendors(ctx context.Context, organizationID uuid.UUID) ([]*entities.Vendor, error)
|
||||||
|
}
|
||||||
147
internal/repository/account_repository.go
Normal file
147
internal/repository/account_repository.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountRepository interface {
|
||||||
|
Create(ctx context.Context, account *entities.Account) error
|
||||||
|
GetByID(ctx context.Context, id uuid.UUID) (*entities.Account, error)
|
||||||
|
Update(ctx context.Context, account *entities.Account) error
|
||||||
|
Delete(ctx context.Context, id uuid.UUID) error
|
||||||
|
List(ctx context.Context, req *entities.Account) ([]*entities.Account, int, error)
|
||||||
|
GetByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]*entities.Account, error)
|
||||||
|
GetByChartOfAccount(ctx context.Context, chartOfAccountID uuid.UUID) ([]*entities.Account, error)
|
||||||
|
GetByNumber(ctx context.Context, organizationID uuid.UUID, number string, outletID *uuid.UUID) (*entities.Account, error)
|
||||||
|
UpdateBalance(ctx context.Context, id uuid.UUID, amount float64) error
|
||||||
|
GetBalance(ctx context.Context, id uuid.UUID) (float64, error)
|
||||||
|
GetSystemAccounts(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]*entities.Account, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountRepositoryImpl struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccountRepositoryImpl(db *gorm.DB) *AccountRepositoryImpl {
|
||||||
|
return &AccountRepositoryImpl{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AccountRepositoryImpl) Create(ctx context.Context, account *entities.Account) error {
|
||||||
|
return r.db.WithContext(ctx).Create(account).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AccountRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entities.Account, error) {
|
||||||
|
var account entities.Account
|
||||||
|
err := r.db.WithContext(ctx).Preload("ChartOfAccount").Preload("ChartOfAccount.ChartOfAccountType").First(&account, "id = ?", id).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AccountRepositoryImpl) Update(ctx context.Context, account *entities.Account) error {
|
||||||
|
return r.db.WithContext(ctx).Save(account).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AccountRepositoryImpl) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return r.db.WithContext(ctx).Delete(&entities.Account{}, "id = ?", id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AccountRepositoryImpl) List(ctx context.Context, req *entities.Account) ([]*entities.Account, int, error) {
|
||||||
|
var accounts []*entities.Account
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
query := r.db.WithContext(ctx).Model(&entities.Account{})
|
||||||
|
|
||||||
|
// Apply filters
|
||||||
|
if req.OrganizationID != uuid.Nil {
|
||||||
|
query = query.Where("organization_id = ?", req.OrganizationID)
|
||||||
|
}
|
||||||
|
if req.OutletID != nil {
|
||||||
|
query = query.Where("outlet_id = ?", *req.OutletID)
|
||||||
|
}
|
||||||
|
if req.ChartOfAccountID != uuid.Nil {
|
||||||
|
query = query.Where("chart_of_account_id = ?", req.ChartOfAccountID)
|
||||||
|
}
|
||||||
|
if req.AccountType != "" {
|
||||||
|
query = query.Where("account_type = ?", req.AccountType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count total
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply pagination and preloads
|
||||||
|
err := query.Preload("ChartOfAccount").Preload("ChartOfAccount.ChartOfAccountType").Find(&accounts).Error
|
||||||
|
return accounts, int(total), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AccountRepositoryImpl) GetByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]*entities.Account, error) {
|
||||||
|
var accounts []*entities.Account
|
||||||
|
query := r.db.WithContext(ctx).Where("organization_id = ?", organizationID)
|
||||||
|
|
||||||
|
if outletID != nil {
|
||||||
|
query = query.Where("outlet_id = ?", *outletID)
|
||||||
|
} else {
|
||||||
|
query = query.Where("outlet_id IS NULL")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.Preload("ChartOfAccount").Preload("ChartOfAccount.ChartOfAccountType").Find(&accounts).Error
|
||||||
|
return accounts, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AccountRepositoryImpl) GetByChartOfAccount(ctx context.Context, chartOfAccountID uuid.UUID) ([]*entities.Account, error) {
|
||||||
|
var accounts []*entities.Account
|
||||||
|
err := r.db.WithContext(ctx).Where("chart_of_account_id = ?", chartOfAccountID).Preload("ChartOfAccount").Preload("ChartOfAccount.ChartOfAccountType").Find(&accounts).Error
|
||||||
|
return accounts, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AccountRepositoryImpl) GetByNumber(ctx context.Context, organizationID uuid.UUID, number string, outletID *uuid.UUID) (*entities.Account, error) {
|
||||||
|
var account entities.Account
|
||||||
|
query := r.db.WithContext(ctx).Where("organization_id = ? AND number = ?", organizationID, number)
|
||||||
|
|
||||||
|
if outletID != nil {
|
||||||
|
query = query.Where("outlet_id = ?", *outletID)
|
||||||
|
} else {
|
||||||
|
query = query.Where("outlet_id IS NULL")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.Preload("ChartOfAccount").Preload("ChartOfAccount.ChartOfAccountType").First(&account).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AccountRepositoryImpl) UpdateBalance(ctx context.Context, id uuid.UUID, amount float64) error {
|
||||||
|
return r.db.WithContext(ctx).Model(&entities.Account{}).Where("id = ?", id).Update("current_balance", amount).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AccountRepositoryImpl) GetBalance(ctx context.Context, id uuid.UUID) (float64, error) {
|
||||||
|
var balance float64
|
||||||
|
err := r.db.WithContext(ctx).Model(&entities.Account{}).Select("current_balance").Where("id = ?", id).Scan(&balance).Error
|
||||||
|
return balance, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *AccountRepositoryImpl) GetSystemAccounts(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]*entities.Account, error) {
|
||||||
|
var accounts []*entities.Account
|
||||||
|
query := r.db.WithContext(ctx).Where("organization_id = ? AND is_system = ?", organizationID, true)
|
||||||
|
|
||||||
|
if outletID != nil {
|
||||||
|
query = query.Where("outlet_id = ?", *outletID)
|
||||||
|
} else {
|
||||||
|
query = query.Where("outlet_id IS NULL")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.Preload("ChartOfAccount").Preload("ChartOfAccount.ChartOfAccountType").Find(&accounts).Error
|
||||||
|
return accounts, err
|
||||||
|
}
|
||||||
143
internal/repository/chart_of_account_repository.go
Normal file
143
internal/repository/chart_of_account_repository.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChartOfAccountRepository interface {
|
||||||
|
Create(ctx context.Context, chartOfAccount *entities.ChartOfAccount) error
|
||||||
|
GetByID(ctx context.Context, id uuid.UUID) (*entities.ChartOfAccount, error)
|
||||||
|
Update(ctx context.Context, chartOfAccount *entities.ChartOfAccount) error
|
||||||
|
Delete(ctx context.Context, id uuid.UUID) error
|
||||||
|
List(ctx context.Context, req *entities.ChartOfAccount) ([]*entities.ChartOfAccount, int, error)
|
||||||
|
GetByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]*entities.ChartOfAccount, error)
|
||||||
|
GetByType(ctx context.Context, organizationID uuid.UUID, chartOfAccountTypeID uuid.UUID, outletID *uuid.UUID) ([]*entities.ChartOfAccount, error)
|
||||||
|
GetByCode(ctx context.Context, organizationID uuid.UUID, code string, outletID *uuid.UUID) (*entities.ChartOfAccount, error)
|
||||||
|
GetSystemAccounts(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]*entities.ChartOfAccount, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartOfAccountRepositoryImpl struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChartOfAccountRepositoryImpl(db *gorm.DB) *ChartOfAccountRepositoryImpl {
|
||||||
|
return &ChartOfAccountRepositoryImpl{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ChartOfAccountRepositoryImpl) Create(ctx context.Context, chartOfAccount *entities.ChartOfAccount) error {
|
||||||
|
return r.db.WithContext(ctx).Create(chartOfAccount).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ChartOfAccountRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entities.ChartOfAccount, error) {
|
||||||
|
var chartOfAccount entities.ChartOfAccount
|
||||||
|
err := r.db.WithContext(ctx).Preload("ChartOfAccountType").Preload("Parent").Preload("Children").First(&chartOfAccount, "id = ?", id).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &chartOfAccount, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ChartOfAccountRepositoryImpl) Update(ctx context.Context, chartOfAccount *entities.ChartOfAccount) error {
|
||||||
|
return r.db.WithContext(ctx).Save(chartOfAccount).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ChartOfAccountRepositoryImpl) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return r.db.WithContext(ctx).Delete(&entities.ChartOfAccount{}, "id = ?", id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ChartOfAccountRepositoryImpl) List(ctx context.Context, req *entities.ChartOfAccount) ([]*entities.ChartOfAccount, int, error) {
|
||||||
|
var chartOfAccounts []*entities.ChartOfAccount
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
query := r.db.WithContext(ctx).Model(&entities.ChartOfAccount{})
|
||||||
|
|
||||||
|
// Apply filters
|
||||||
|
if req.OrganizationID != uuid.Nil {
|
||||||
|
query = query.Where("organization_id = ?", req.OrganizationID)
|
||||||
|
}
|
||||||
|
if req.OutletID != nil {
|
||||||
|
query = query.Where("outlet_id = ?", *req.OutletID)
|
||||||
|
}
|
||||||
|
if req.ChartOfAccountTypeID != uuid.Nil {
|
||||||
|
query = query.Where("chart_of_account_type_id = ?", req.ChartOfAccountTypeID)
|
||||||
|
}
|
||||||
|
if req.ParentID != nil {
|
||||||
|
query = query.Where("parent_id = ?", *req.ParentID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count total
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply pagination and preloads
|
||||||
|
err := query.Preload("ChartOfAccountType").Preload("Parent").Preload("Children").Find(&chartOfAccounts).Error
|
||||||
|
return chartOfAccounts, int(total), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ChartOfAccountRepositoryImpl) GetByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]*entities.ChartOfAccount, error) {
|
||||||
|
var chartOfAccounts []*entities.ChartOfAccount
|
||||||
|
query := r.db.WithContext(ctx).Where("organization_id = ?", organizationID)
|
||||||
|
|
||||||
|
if outletID != nil {
|
||||||
|
query = query.Where("outlet_id = ?", *outletID)
|
||||||
|
} else {
|
||||||
|
query = query.Where("outlet_id IS NULL")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.Preload("ChartOfAccountType").Preload("Parent").Preload("Children").Find(&chartOfAccounts).Error
|
||||||
|
return chartOfAccounts, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ChartOfAccountRepositoryImpl) GetByType(ctx context.Context, organizationID uuid.UUID, chartOfAccountTypeID uuid.UUID, outletID *uuid.UUID) ([]*entities.ChartOfAccount, error) {
|
||||||
|
var chartOfAccounts []*entities.ChartOfAccount
|
||||||
|
query := r.db.WithContext(ctx).Where("organization_id = ? AND chart_of_account_type_id = ?", organizationID, chartOfAccountTypeID)
|
||||||
|
|
||||||
|
if outletID != nil {
|
||||||
|
query = query.Where("outlet_id = ?", *outletID)
|
||||||
|
} else {
|
||||||
|
query = query.Where("outlet_id IS NULL")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.Preload("ChartOfAccountType").Preload("Parent").Preload("Children").Find(&chartOfAccounts).Error
|
||||||
|
return chartOfAccounts, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ChartOfAccountRepositoryImpl) GetByCode(ctx context.Context, organizationID uuid.UUID, code string, outletID *uuid.UUID) (*entities.ChartOfAccount, error) {
|
||||||
|
var chartOfAccount entities.ChartOfAccount
|
||||||
|
query := r.db.WithContext(ctx).Where("organization_id = ? AND code = ?", organizationID, code)
|
||||||
|
|
||||||
|
if outletID != nil {
|
||||||
|
query = query.Where("outlet_id = ?", *outletID)
|
||||||
|
} else {
|
||||||
|
query = query.Where("outlet_id IS NULL")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.Preload("ChartOfAccountType").Preload("Parent").First(&chartOfAccount).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &chartOfAccount, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ChartOfAccountRepositoryImpl) GetSystemAccounts(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]*entities.ChartOfAccount, error) {
|
||||||
|
var chartOfAccounts []*entities.ChartOfAccount
|
||||||
|
query := r.db.WithContext(ctx).Where("organization_id = ? AND is_system = ?", organizationID, true)
|
||||||
|
|
||||||
|
if outletID != nil {
|
||||||
|
query = query.Where("outlet_id = ?", *outletID)
|
||||||
|
} else {
|
||||||
|
query = query.Where("outlet_id IS NULL")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.Preload("ChartOfAccountType").Preload("Parent").Find(&chartOfAccounts).Error
|
||||||
|
return chartOfAccounts, err
|
||||||
|
}
|
||||||
91
internal/repository/chart_of_account_type_repository.go
Normal file
91
internal/repository/chart_of_account_type_repository.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChartOfAccountTypeRepository interface {
|
||||||
|
Create(ctx context.Context, chartOfAccountType *entities.ChartOfAccountType) error
|
||||||
|
GetByID(ctx context.Context, id uuid.UUID) (*entities.ChartOfAccountType, error)
|
||||||
|
GetByCode(ctx context.Context, code string) (*entities.ChartOfAccountType, error)
|
||||||
|
Update(ctx context.Context, chartOfAccountType *entities.ChartOfAccountType) error
|
||||||
|
Delete(ctx context.Context, id uuid.UUID) error
|
||||||
|
List(ctx context.Context, filters map[string]interface{}, page, limit int) ([]*entities.ChartOfAccountType, int, error)
|
||||||
|
GetActive(ctx context.Context) ([]*entities.ChartOfAccountType, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartOfAccountTypeRepositoryImpl struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChartOfAccountTypeRepositoryImpl(db *gorm.DB) *ChartOfAccountTypeRepositoryImpl {
|
||||||
|
return &ChartOfAccountTypeRepositoryImpl{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ChartOfAccountTypeRepositoryImpl) Create(ctx context.Context, chartOfAccountType *entities.ChartOfAccountType) error {
|
||||||
|
return r.db.WithContext(ctx).Create(chartOfAccountType).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ChartOfAccountTypeRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entities.ChartOfAccountType, error) {
|
||||||
|
var chartOfAccountType entities.ChartOfAccountType
|
||||||
|
err := r.db.WithContext(ctx).First(&chartOfAccountType, "id = ?", id).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &chartOfAccountType, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ChartOfAccountTypeRepositoryImpl) GetByCode(ctx context.Context, code string) (*entities.ChartOfAccountType, error) {
|
||||||
|
var chartOfAccountType entities.ChartOfAccountType
|
||||||
|
err := r.db.WithContext(ctx).First(&chartOfAccountType, "code = ?", code).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &chartOfAccountType, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ChartOfAccountTypeRepositoryImpl) Update(ctx context.Context, chartOfAccountType *entities.ChartOfAccountType) error {
|
||||||
|
return r.db.WithContext(ctx).Save(chartOfAccountType).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ChartOfAccountTypeRepositoryImpl) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return r.db.WithContext(ctx).Delete(&entities.ChartOfAccountType{}, "id = ?", id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ChartOfAccountTypeRepositoryImpl) List(ctx context.Context, filters map[string]interface{}, page, limit int) ([]*entities.ChartOfAccountType, int, error) {
|
||||||
|
var chartOfAccountTypes []*entities.ChartOfAccountType
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
query := r.db.WithContext(ctx).Model(&entities.ChartOfAccountType{})
|
||||||
|
|
||||||
|
// Apply filters
|
||||||
|
for key, value := range filters {
|
||||||
|
if value != nil {
|
||||||
|
query = query.Where(key+" = ?", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count total
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply pagination
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
err := query.Offset(offset).Limit(limit).Find(&chartOfAccountTypes).Error
|
||||||
|
return chartOfAccountTypes, int(total), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ChartOfAccountTypeRepositoryImpl) GetActive(ctx context.Context) ([]*entities.ChartOfAccountType, error) {
|
||||||
|
var chartOfAccountTypes []*entities.ChartOfAccountType
|
||||||
|
err := r.db.WithContext(ctx).Where("is_active = ?", true).Find(&chartOfAccountTypes).Error
|
||||||
|
return chartOfAccountTypes, err
|
||||||
|
}
|
||||||
176
internal/repository/ingredient_unit_converter_repository.go
Normal file
176
internal/repository/ingredient_unit_converter_repository.go
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IngredientUnitConverterRepository interface {
|
||||||
|
Create(ctx context.Context, converter *entities.IngredientUnitConverter) error
|
||||||
|
GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.IngredientUnitConverter, error)
|
||||||
|
GetByIDAndOrganizationID(ctx context.Context, id, organizationID uuid.UUID) (*entities.IngredientUnitConverter, error)
|
||||||
|
Update(ctx context.Context, converter *entities.IngredientUnitConverter) error
|
||||||
|
Delete(ctx context.Context, id, organizationID uuid.UUID) error
|
||||||
|
List(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*entities.IngredientUnitConverter, int, error)
|
||||||
|
GetByIngredientAndUnits(ctx context.Context, ingredientID, fromUnitID, toUnitID, organizationID uuid.UUID) (*entities.IngredientUnitConverter, error)
|
||||||
|
GetConvertersForIngredient(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*entities.IngredientUnitConverter, error)
|
||||||
|
GetActiveConverters(ctx context.Context, organizationID uuid.UUID) ([]*entities.IngredientUnitConverter, error)
|
||||||
|
ConvertQuantity(ctx context.Context, ingredientID, fromUnitID, toUnitID, organizationID uuid.UUID, quantity float64) (float64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type IngredientUnitConverterRepositoryImpl struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIngredientUnitConverterRepositoryImpl(db *gorm.DB) IngredientUnitConverterRepository {
|
||||||
|
return &IngredientUnitConverterRepositoryImpl{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IngredientUnitConverterRepositoryImpl) Create(ctx context.Context, converter *entities.IngredientUnitConverter) error {
|
||||||
|
return r.db.WithContext(ctx).Create(converter).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IngredientUnitConverterRepositoryImpl) GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.IngredientUnitConverter, error) {
|
||||||
|
var converter entities.IngredientUnitConverter
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Where("id = ? AND organization_id = ?", id, organizationID).
|
||||||
|
Preload("Ingredient").
|
||||||
|
Preload("FromUnit").
|
||||||
|
Preload("ToUnit").
|
||||||
|
Preload("CreatedByUser").
|
||||||
|
Preload("UpdatedByUser").
|
||||||
|
First(&converter).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &converter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IngredientUnitConverterRepositoryImpl) GetByIDAndOrganizationID(ctx context.Context, id, organizationID uuid.UUID) (*entities.IngredientUnitConverter, error) {
|
||||||
|
return r.GetByID(ctx, id, organizationID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IngredientUnitConverterRepositoryImpl) Update(ctx context.Context, converter *entities.IngredientUnitConverter) error {
|
||||||
|
return r.db.WithContext(ctx).Save(converter).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IngredientUnitConverterRepositoryImpl) Delete(ctx context.Context, id, organizationID uuid.UUID) error {
|
||||||
|
return r.db.WithContext(ctx).
|
||||||
|
Where("id = ? AND organization_id = ?", id, organizationID).
|
||||||
|
Delete(&entities.IngredientUnitConverter{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IngredientUnitConverterRepositoryImpl) List(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, page, limit int) ([]*entities.IngredientUnitConverter, int, error) {
|
||||||
|
var converters []*entities.IngredientUnitConverter
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
query := r.db.WithContext(ctx).
|
||||||
|
Model(&entities.IngredientUnitConverter{}).
|
||||||
|
Where("organization_id = ?", organizationID)
|
||||||
|
|
||||||
|
// Apply filters
|
||||||
|
if ingredientID, ok := filters["ingredient_id"].(uuid.UUID); ok {
|
||||||
|
query = query.Where("ingredient_id = ?", ingredientID)
|
||||||
|
}
|
||||||
|
if fromUnitID, ok := filters["from_unit_id"].(uuid.UUID); ok {
|
||||||
|
query = query.Where("from_unit_id = ?", fromUnitID)
|
||||||
|
}
|
||||||
|
if toUnitID, ok := filters["to_unit_id"].(uuid.UUID); ok {
|
||||||
|
query = query.Where("to_unit_id = ?", toUnitID)
|
||||||
|
}
|
||||||
|
if isActive, ok := filters["is_active"].(bool); ok {
|
||||||
|
query = query.Where("is_active = ?", isActive)
|
||||||
|
}
|
||||||
|
if search, ok := filters["search"].(string); ok && search != "" {
|
||||||
|
query = query.Joins("LEFT JOIN ingredients ON ingredient_unit_converters.ingredient_id = ingredients.id").
|
||||||
|
Joins("LEFT JOIN units AS from_units ON ingredient_unit_converters.from_unit_id = from_units.id").
|
||||||
|
Joins("LEFT JOIN units AS to_units ON ingredient_unit_converters.to_unit_id = to_units.id").
|
||||||
|
Where("ingredients.name ILIKE ? OR from_units.name ILIKE ? OR to_units.name ILIKE ?",
|
||||||
|
"%"+search+"%", "%"+search+"%", "%"+search+"%")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get total count
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply pagination and get results
|
||||||
|
offset := (page - 1) * limit
|
||||||
|
err := query.
|
||||||
|
Preload("Ingredient").
|
||||||
|
Preload("FromUnit").
|
||||||
|
Preload("ToUnit").
|
||||||
|
Preload("CreatedByUser").
|
||||||
|
Preload("UpdatedByUser").
|
||||||
|
Order("created_at DESC").
|
||||||
|
Offset(offset).
|
||||||
|
Limit(limit).
|
||||||
|
Find(&converters).Error
|
||||||
|
|
||||||
|
return converters, int(total), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IngredientUnitConverterRepositoryImpl) GetByIngredientAndUnits(ctx context.Context, ingredientID, fromUnitID, toUnitID, organizationID uuid.UUID) (*entities.IngredientUnitConverter, error) {
|
||||||
|
var converter entities.IngredientUnitConverter
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Where("ingredient_id = ? AND from_unit_id = ? AND to_unit_id = ? AND organization_id = ? AND is_active = ?",
|
||||||
|
ingredientID, fromUnitID, toUnitID, organizationID, true).
|
||||||
|
Preload("Ingredient").
|
||||||
|
Preload("FromUnit").
|
||||||
|
Preload("ToUnit").
|
||||||
|
First(&converter).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &converter, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IngredientUnitConverterRepositoryImpl) GetConvertersForIngredient(ctx context.Context, ingredientID, organizationID uuid.UUID) ([]*entities.IngredientUnitConverter, error) {
|
||||||
|
var converters []*entities.IngredientUnitConverter
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Where("ingredient_id = ? AND organization_id = ? AND is_active = ?", ingredientID, organizationID, true).
|
||||||
|
Preload("FromUnit").
|
||||||
|
Preload("ToUnit").
|
||||||
|
Find(&converters).Error
|
||||||
|
return converters, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IngredientUnitConverterRepositoryImpl) GetActiveConverters(ctx context.Context, organizationID uuid.UUID) ([]*entities.IngredientUnitConverter, error) {
|
||||||
|
var converters []*entities.IngredientUnitConverter
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Where("organization_id = ? AND is_active = ?", organizationID, true).
|
||||||
|
Preload("Ingredient").
|
||||||
|
Preload("FromUnit").
|
||||||
|
Preload("ToUnit").
|
||||||
|
Find(&converters).Error
|
||||||
|
return converters, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *IngredientUnitConverterRepositoryImpl) ConvertQuantity(ctx context.Context, ingredientID, fromUnitID, toUnitID, organizationID uuid.UUID, quantity float64) (float64, error) {
|
||||||
|
// If from and to units are the same, return the same quantity
|
||||||
|
if fromUnitID == toUnitID {
|
||||||
|
return quantity, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find direct converter
|
||||||
|
converter, err := r.GetByIngredientAndUnits(ctx, ingredientID, fromUnitID, toUnitID, organizationID)
|
||||||
|
if err == nil {
|
||||||
|
return quantity * converter.ConversionFactor, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If direct converter not found, try to find reverse converter
|
||||||
|
reverseConverter, err := r.GetByIngredientAndUnits(ctx, ingredientID, toUnitID, fromUnitID, organizationID)
|
||||||
|
if err == nil {
|
||||||
|
return quantity / reverseConverter.ConversionFactor, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no converter found, return error
|
||||||
|
return 0, fmt.Errorf("no conversion found between units %s and %s for ingredient %s", fromUnitID, toUnitID, ingredientID)
|
||||||
|
}
|
||||||
|
|
||||||
248
internal/repository/purchase_order_repository.go
Normal file
248
internal/repository/purchase_order_repository.go
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PurchaseOrderRepositoryImpl struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPurchaseOrderRepositoryImpl(db *gorm.DB) *PurchaseOrderRepositoryImpl {
|
||||||
|
return &PurchaseOrderRepositoryImpl{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) Create(ctx context.Context, po *entities.PurchaseOrder) error {
|
||||||
|
return r.db.WithContext(ctx).Create(po).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entities.PurchaseOrder, error) {
|
||||||
|
var po entities.PurchaseOrder
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Preload("Vendor").
|
||||||
|
Preload("Items.Ingredient").
|
||||||
|
Preload("Items.Unit").
|
||||||
|
Preload("Attachments.File").
|
||||||
|
First(&po, "id = ?", id).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &po, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) GetByIDAndOrganizationID(ctx context.Context, id, organizationID uuid.UUID) (*entities.PurchaseOrder, error) {
|
||||||
|
var po entities.PurchaseOrder
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Preload("Vendor").
|
||||||
|
Preload("Items.Ingredient").
|
||||||
|
Preload("Items.Unit").
|
||||||
|
Preload("Attachments.File").
|
||||||
|
Where("id = ? AND organization_id = ?", id, organizationID).
|
||||||
|
First(&po).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &po, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) Update(ctx context.Context, po *entities.PurchaseOrder) error {
|
||||||
|
return r.db.WithContext(ctx).Save(po).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return r.db.WithContext(ctx).Delete(&entities.PurchaseOrder{}, "id = ?", id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) List(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, limit, offset int) ([]*entities.PurchaseOrder, int64, error) {
|
||||||
|
var pos []*entities.PurchaseOrder
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
query := r.db.WithContext(ctx).Model(&entities.PurchaseOrder{}).Where("organization_id = ?", organizationID)
|
||||||
|
|
||||||
|
// Apply filters
|
||||||
|
for key, value := range filters {
|
||||||
|
switch key {
|
||||||
|
case "search":
|
||||||
|
if searchStr, ok := value.(string); ok && searchStr != "" {
|
||||||
|
searchPattern := "%" + strings.ToLower(searchStr) + "%"
|
||||||
|
query = query.Where("LOWER(po_number) LIKE ? OR LOWER(reference) LIKE ?", searchPattern, searchPattern)
|
||||||
|
}
|
||||||
|
case "status":
|
||||||
|
if status, ok := value.(string); ok && status != "" {
|
||||||
|
query = query.Where("status = ?", status)
|
||||||
|
}
|
||||||
|
case "vendor_id":
|
||||||
|
if vendorID, ok := value.(uuid.UUID); ok {
|
||||||
|
query = query.Where("vendor_id = ?", vendorID)
|
||||||
|
}
|
||||||
|
case "start_date":
|
||||||
|
if startDate, ok := value.(time.Time); ok {
|
||||||
|
query = query.Where("transaction_date >= ?", startDate)
|
||||||
|
}
|
||||||
|
case "end_date":
|
||||||
|
if endDate, ok := value.(time.Time); ok {
|
||||||
|
query = query.Where("transaction_date <= ?", endDate)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
query = query.Where(key+" = ?", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.
|
||||||
|
Preload("Vendor").
|
||||||
|
Preload("Items.Ingredient").
|
||||||
|
Preload("Items.Unit").
|
||||||
|
Preload("Attachments.File").
|
||||||
|
Order("created_at DESC").
|
||||||
|
Limit(limit).
|
||||||
|
Offset(offset).
|
||||||
|
Find(&pos).Error
|
||||||
|
return pos, total, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) Count(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}) (int64, error) {
|
||||||
|
var count int64
|
||||||
|
query := r.db.WithContext(ctx).Model(&entities.PurchaseOrder{}).Where("organization_id = ?", organizationID)
|
||||||
|
|
||||||
|
// Apply filters
|
||||||
|
for key, value := range filters {
|
||||||
|
switch key {
|
||||||
|
case "search":
|
||||||
|
if searchStr, ok := value.(string); ok && searchStr != "" {
|
||||||
|
searchPattern := "%" + strings.ToLower(searchStr) + "%"
|
||||||
|
query = query.Where("LOWER(po_number) LIKE ? OR LOWER(reference) LIKE ?", searchPattern, searchPattern)
|
||||||
|
}
|
||||||
|
case "status":
|
||||||
|
if status, ok := value.(string); ok && status != "" {
|
||||||
|
query = query.Where("status = ?", status)
|
||||||
|
}
|
||||||
|
case "vendor_id":
|
||||||
|
if vendorID, ok := value.(uuid.UUID); ok {
|
||||||
|
query = query.Where("vendor_id = ?", vendorID)
|
||||||
|
}
|
||||||
|
case "start_date":
|
||||||
|
if startDate, ok := value.(time.Time); ok {
|
||||||
|
query = query.Where("transaction_date >= ?", startDate)
|
||||||
|
}
|
||||||
|
case "end_date":
|
||||||
|
if endDate, ok := value.(time.Time); ok {
|
||||||
|
query = query.Where("transaction_date <= ?", endDate)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
query = query.Where(key+" = ?", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.Count(&count).Error
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) GetByPONumber(ctx context.Context, poNumber string, organizationID uuid.UUID) (*entities.PurchaseOrder, error) {
|
||||||
|
var po entities.PurchaseOrder
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Where("po_number = ? AND organization_id = ?", poNumber, organizationID).
|
||||||
|
First(&po).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &po, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) GetByStatus(ctx context.Context, organizationID uuid.UUID, status string) ([]*entities.PurchaseOrder, error) {
|
||||||
|
var pos []*entities.PurchaseOrder
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Where("organization_id = ? AND status = ?", organizationID, status).
|
||||||
|
Preload("Vendor").
|
||||||
|
Preload("Items.Ingredient").
|
||||||
|
Preload("Items.Unit").
|
||||||
|
Find(&pos).Error
|
||||||
|
return pos, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) GetOverdue(ctx context.Context, organizationID uuid.UUID) ([]*entities.PurchaseOrder, error) {
|
||||||
|
var pos []*entities.PurchaseOrder
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Where("organization_id = ? AND due_date < ? AND status IN (?)", organizationID, time.Now(), []string{"draft", "sent", "approved"}).
|
||||||
|
Preload("Vendor").
|
||||||
|
Preload("Items.Ingredient").
|
||||||
|
Preload("Items.Unit").
|
||||||
|
Find(&pos).Error
|
||||||
|
return pos, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) UpdateStatus(ctx context.Context, id uuid.UUID, status string) error {
|
||||||
|
return r.db.WithContext(ctx).
|
||||||
|
Model(&entities.PurchaseOrder{}).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Update("status", status).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) UpdateTotalAmount(ctx context.Context, id uuid.UUID, totalAmount float64) error {
|
||||||
|
return r.db.WithContext(ctx).
|
||||||
|
Model(&entities.PurchaseOrder{}).
|
||||||
|
Where("id = ?", id).
|
||||||
|
Update("total_amount", totalAmount).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purchase Order Items methods
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) CreateItem(ctx context.Context, item *entities.PurchaseOrderItem) error {
|
||||||
|
return r.db.WithContext(ctx).Create(item).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) UpdateItem(ctx context.Context, item *entities.PurchaseOrderItem) error {
|
||||||
|
return r.db.WithContext(ctx).Save(item).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) DeleteItem(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return r.db.WithContext(ctx).Delete(&entities.PurchaseOrderItem{}, "id = ?", id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) DeleteItemsByPurchaseOrderID(ctx context.Context, purchaseOrderID uuid.UUID) error {
|
||||||
|
return r.db.WithContext(ctx).Delete(&entities.PurchaseOrderItem{}, "purchase_order_id = ?", purchaseOrderID).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) GetItemsByPurchaseOrderID(ctx context.Context, purchaseOrderID uuid.UUID) ([]*entities.PurchaseOrderItem, error) {
|
||||||
|
var items []*entities.PurchaseOrderItem
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Preload("Ingredient").
|
||||||
|
Preload("Unit").
|
||||||
|
Where("purchase_order_id = ?", purchaseOrderID).
|
||||||
|
Find(&items).Error
|
||||||
|
return items, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purchase Order Attachments methods
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) CreateAttachment(ctx context.Context, attachment *entities.PurchaseOrderAttachment) error {
|
||||||
|
return r.db.WithContext(ctx).Create(attachment).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) DeleteAttachment(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return r.db.WithContext(ctx).Delete(&entities.PurchaseOrderAttachment{}, "id = ?", id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) DeleteAttachmentsByPurchaseOrderID(ctx context.Context, purchaseOrderID uuid.UUID) error {
|
||||||
|
return r.db.WithContext(ctx).Delete(&entities.PurchaseOrderAttachment{}, "purchase_order_id = ?", purchaseOrderID).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PurchaseOrderRepositoryImpl) GetAttachmentsByPurchaseOrderID(ctx context.Context, purchaseOrderID uuid.UUID) ([]*entities.PurchaseOrderAttachment, error) {
|
||||||
|
var attachments []*entities.PurchaseOrderAttachment
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Preload("File").
|
||||||
|
Where("purchase_order_id = ?", purchaseOrderID).
|
||||||
|
Find(&attachments).Error
|
||||||
|
return attachments, err
|
||||||
|
}
|
||||||
134
internal/repository/vendor_repository.go
Normal file
134
internal/repository/vendor_repository.go
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VendorRepositoryImpl struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVendorRepositoryImpl(db *gorm.DB) *VendorRepositoryImpl {
|
||||||
|
return &VendorRepositoryImpl{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VendorRepositoryImpl) Create(ctx context.Context, vendor *entities.Vendor) error {
|
||||||
|
return r.db.WithContext(ctx).Create(vendor).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VendorRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entities.Vendor, error) {
|
||||||
|
var vendor entities.Vendor
|
||||||
|
err := r.db.WithContext(ctx).First(&vendor, "id = ?", id).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &vendor, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VendorRepositoryImpl) GetByIDAndOrganizationID(ctx context.Context, id, organizationID uuid.UUID) (*entities.Vendor, error) {
|
||||||
|
var vendor entities.Vendor
|
||||||
|
err := r.db.WithContext(ctx).Where("id = ? AND organization_id = ?", id, organizationID).First(&vendor).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &vendor, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VendorRepositoryImpl) Update(ctx context.Context, vendor *entities.Vendor) error {
|
||||||
|
return r.db.WithContext(ctx).Save(vendor).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VendorRepositoryImpl) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return r.db.WithContext(ctx).Delete(&entities.Vendor{}, "id = ?", id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VendorRepositoryImpl) List(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}, limit, offset int) ([]*entities.Vendor, int64, error) {
|
||||||
|
var vendors []*entities.Vendor
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
query := r.db.WithContext(ctx).Model(&entities.Vendor{}).Where("organization_id = ?", organizationID)
|
||||||
|
|
||||||
|
// Apply filters
|
||||||
|
for key, value := range filters {
|
||||||
|
switch key {
|
||||||
|
case "search":
|
||||||
|
if searchStr, ok := value.(string); ok && searchStr != "" {
|
||||||
|
searchPattern := "%" + strings.ToLower(searchStr) + "%"
|
||||||
|
query = query.Where("LOWER(name) LIKE ? OR LOWER(email) LIKE ? OR LOWER(contact_person) LIKE ?",
|
||||||
|
searchPattern, searchPattern, searchPattern)
|
||||||
|
}
|
||||||
|
case "is_active":
|
||||||
|
if isActive, ok := value.(bool); ok {
|
||||||
|
query = query.Where("is_active = ?", isActive)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
query = query.Where(key+" = ?", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := query.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.Order("created_at DESC").Limit(limit).Offset(offset).Find(&vendors).Error
|
||||||
|
return vendors, total, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VendorRepositoryImpl) Count(ctx context.Context, organizationID uuid.UUID, filters map[string]interface{}) (int64, error) {
|
||||||
|
var count int64
|
||||||
|
query := r.db.WithContext(ctx).Model(&entities.Vendor{}).Where("organization_id = ?", organizationID)
|
||||||
|
|
||||||
|
// Apply filters
|
||||||
|
for key, value := range filters {
|
||||||
|
switch key {
|
||||||
|
case "search":
|
||||||
|
if searchStr, ok := value.(string); ok && searchStr != "" {
|
||||||
|
searchPattern := "%" + strings.ToLower(searchStr) + "%"
|
||||||
|
query = query.Where("LOWER(name) LIKE ? OR LOWER(email) LIKE ? OR LOWER(contact_person) LIKE ?",
|
||||||
|
searchPattern, searchPattern, searchPattern)
|
||||||
|
}
|
||||||
|
case "is_active":
|
||||||
|
if isActive, ok := value.(bool); ok {
|
||||||
|
query = query.Where("is_active = ?", isActive)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
query = query.Where(key+" = ?", value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.Count(&count).Error
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VendorRepositoryImpl) GetByEmail(ctx context.Context, email string, organizationID uuid.UUID) (*entities.Vendor, error) {
|
||||||
|
var vendor entities.Vendor
|
||||||
|
err := r.db.WithContext(ctx).Where("email = ? AND organization_id = ?", email, organizationID).First(&vendor).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &vendor, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VendorRepositoryImpl) GetByName(ctx context.Context, name string, organizationID uuid.UUID) (*entities.Vendor, error) {
|
||||||
|
var vendor entities.Vendor
|
||||||
|
err := r.db.WithContext(ctx).Where("name = ? AND organization_id = ?", name, organizationID).First(&vendor).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &vendor, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *VendorRepositoryImpl) GetActiveVendors(ctx context.Context, organizationID uuid.UUID) ([]*entities.Vendor, error) {
|
||||||
|
var vendors []*entities.Vendor
|
||||||
|
err := r.db.WithContext(ctx).Where("organization_id = ? AND is_active = ?", organizationID, true).Find(&vendors).Error
|
||||||
|
return vendors, err
|
||||||
|
}
|
||||||
@ -12,28 +12,34 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Router struct {
|
type Router struct {
|
||||||
config *config.Config
|
config *config.Config
|
||||||
healthHandler *handler.HealthHandler
|
healthHandler *handler.HealthHandler
|
||||||
authHandler *handler.AuthHandler
|
authHandler *handler.AuthHandler
|
||||||
userHandler *handler.UserHandler
|
userHandler *handler.UserHandler
|
||||||
organizationHandler *handler.OrganizationHandler
|
organizationHandler *handler.OrganizationHandler
|
||||||
outletHandler *handler.OutletHandler
|
outletHandler *handler.OutletHandler
|
||||||
outletSettingHandler *handler.OutletSettingHandlerImpl
|
outletSettingHandler *handler.OutletSettingHandlerImpl
|
||||||
categoryHandler *handler.CategoryHandler
|
categoryHandler *handler.CategoryHandler
|
||||||
productHandler *handler.ProductHandler
|
productHandler *handler.ProductHandler
|
||||||
productVariantHandler *handler.ProductVariantHandler
|
productVariantHandler *handler.ProductVariantHandler
|
||||||
inventoryHandler *handler.InventoryHandler
|
inventoryHandler *handler.InventoryHandler
|
||||||
orderHandler *handler.OrderHandler
|
orderHandler *handler.OrderHandler
|
||||||
fileHandler *handler.FileHandler
|
fileHandler *handler.FileHandler
|
||||||
customerHandler *handler.CustomerHandler
|
customerHandler *handler.CustomerHandler
|
||||||
paymentMethodHandler *handler.PaymentMethodHandler
|
paymentMethodHandler *handler.PaymentMethodHandler
|
||||||
analyticsHandler *handler.AnalyticsHandler
|
analyticsHandler *handler.AnalyticsHandler
|
||||||
reportHandler *handler.ReportHandler
|
reportHandler *handler.ReportHandler
|
||||||
tableHandler *handler.TableHandler
|
tableHandler *handler.TableHandler
|
||||||
unitHandler *handler.UnitHandler
|
unitHandler *handler.UnitHandler
|
||||||
ingredientHandler *handler.IngredientHandler
|
ingredientHandler *handler.IngredientHandler
|
||||||
productRecipeHandler *handler.ProductRecipeHandler
|
productRecipeHandler *handler.ProductRecipeHandler
|
||||||
authMiddleware *middleware.AuthMiddleware
|
vendorHandler *handler.VendorHandler
|
||||||
|
purchaseOrderHandler *handler.PurchaseOrderHandler
|
||||||
|
unitConverterHandler *handler.IngredientUnitConverterHandler
|
||||||
|
chartOfAccountTypeHandler *handler.ChartOfAccountTypeHandler
|
||||||
|
chartOfAccountHandler *handler.ChartOfAccountHandler
|
||||||
|
accountHandler *handler.AccountHandler
|
||||||
|
authMiddleware *middleware.AuthMiddleware
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouter(cfg *config.Config,
|
func NewRouter(cfg *config.Config,
|
||||||
@ -69,30 +75,48 @@ func NewRouter(cfg *config.Config,
|
|||||||
tableValidator *validator.TableValidator,
|
tableValidator *validator.TableValidator,
|
||||||
unitService handler.UnitService,
|
unitService handler.UnitService,
|
||||||
ingredientService handler.IngredientService,
|
ingredientService handler.IngredientService,
|
||||||
productRecipeService service.ProductRecipeService) *Router {
|
productRecipeService service.ProductRecipeService,
|
||||||
|
vendorService service.VendorService,
|
||||||
|
vendorValidator validator.VendorValidator,
|
||||||
|
purchaseOrderService service.PurchaseOrderService,
|
||||||
|
purchaseOrderValidator validator.PurchaseOrderValidator,
|
||||||
|
unitConverterService service.IngredientUnitConverterService,
|
||||||
|
unitConverterValidator validator.IngredientUnitConverterValidator,
|
||||||
|
chartOfAccountTypeService service.ChartOfAccountTypeService,
|
||||||
|
chartOfAccountTypeValidator validator.ChartOfAccountTypeValidator,
|
||||||
|
chartOfAccountService service.ChartOfAccountService,
|
||||||
|
chartOfAccountValidator validator.ChartOfAccountValidator,
|
||||||
|
accountService service.AccountService,
|
||||||
|
accountValidator validator.AccountValidator) *Router {
|
||||||
|
|
||||||
return &Router{
|
return &Router{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
healthHandler: healthHandler,
|
healthHandler: healthHandler,
|
||||||
authHandler: handler.NewAuthHandler(authService),
|
authHandler: handler.NewAuthHandler(authService),
|
||||||
userHandler: handler.NewUserHandler(userService, userValidator),
|
userHandler: handler.NewUserHandler(userService, userValidator),
|
||||||
organizationHandler: handler.NewOrganizationHandler(organizationService, organizationValidator),
|
organizationHandler: handler.NewOrganizationHandler(organizationService, organizationValidator),
|
||||||
outletHandler: handler.NewOutletHandler(outletService, outletValidator),
|
outletHandler: handler.NewOutletHandler(outletService, outletValidator),
|
||||||
outletSettingHandler: handler.NewOutletSettingHandlerImpl(outletSettingService),
|
outletSettingHandler: handler.NewOutletSettingHandlerImpl(outletSettingService),
|
||||||
categoryHandler: handler.NewCategoryHandler(categoryService, categoryValidator),
|
categoryHandler: handler.NewCategoryHandler(categoryService, categoryValidator),
|
||||||
productHandler: handler.NewProductHandler(productService, productValidator),
|
productHandler: handler.NewProductHandler(productService, productValidator),
|
||||||
inventoryHandler: handler.NewInventoryHandler(inventoryService, inventoryValidator),
|
inventoryHandler: handler.NewInventoryHandler(inventoryService, inventoryValidator),
|
||||||
orderHandler: handler.NewOrderHandler(orderService, orderValidator, transformer.NewTransformer()),
|
orderHandler: handler.NewOrderHandler(orderService, orderValidator, transformer.NewTransformer()),
|
||||||
fileHandler: handler.NewFileHandler(fileService, fileValidator, transformer.NewTransformer()),
|
fileHandler: handler.NewFileHandler(fileService, fileValidator, transformer.NewTransformer()),
|
||||||
customerHandler: handler.NewCustomerHandler(customerService, customerValidator),
|
customerHandler: handler.NewCustomerHandler(customerService, customerValidator),
|
||||||
paymentMethodHandler: handler.NewPaymentMethodHandler(paymentMethodService, paymentMethodValidator),
|
paymentMethodHandler: handler.NewPaymentMethodHandler(paymentMethodService, paymentMethodValidator),
|
||||||
analyticsHandler: handler.NewAnalyticsHandler(analyticsService, transformer.NewTransformer()),
|
analyticsHandler: handler.NewAnalyticsHandler(analyticsService, transformer.NewTransformer()),
|
||||||
reportHandler: handler.NewReportHandler(reportService, userService),
|
reportHandler: handler.NewReportHandler(reportService, userService),
|
||||||
tableHandler: handler.NewTableHandler(tableService, tableValidator),
|
tableHandler: handler.NewTableHandler(tableService, tableValidator),
|
||||||
unitHandler: handler.NewUnitHandler(unitService),
|
unitHandler: handler.NewUnitHandler(unitService),
|
||||||
ingredientHandler: handler.NewIngredientHandler(ingredientService),
|
ingredientHandler: handler.NewIngredientHandler(ingredientService),
|
||||||
productRecipeHandler: handler.NewProductRecipeHandler(productRecipeService),
|
productRecipeHandler: handler.NewProductRecipeHandler(productRecipeService),
|
||||||
authMiddleware: authMiddleware,
|
vendorHandler: handler.NewVendorHandler(vendorService, vendorValidator),
|
||||||
|
purchaseOrderHandler: handler.NewPurchaseOrderHandler(purchaseOrderService, purchaseOrderValidator),
|
||||||
|
unitConverterHandler: handler.NewIngredientUnitConverterHandler(unitConverterService, unitConverterValidator),
|
||||||
|
chartOfAccountTypeHandler: handler.NewChartOfAccountTypeHandler(chartOfAccountTypeService, chartOfAccountTypeValidator),
|
||||||
|
chartOfAccountHandler: handler.NewChartOfAccountHandler(chartOfAccountService, chartOfAccountValidator),
|
||||||
|
accountHandler: handler.NewAccountHandler(accountService, accountValidator),
|
||||||
|
authMiddleware: authMiddleware,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,6 +321,42 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
|||||||
ingredients.DELETE("/:id", r.ingredientHandler.Delete)
|
ingredients.DELETE("/:id", r.ingredientHandler.Delete)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vendors := protected.Group("/vendors")
|
||||||
|
vendors.Use(r.authMiddleware.RequireAdminOrManager())
|
||||||
|
{
|
||||||
|
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.RequireAdminOrManager())
|
||||||
|
{
|
||||||
|
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("/:id", r.purchaseOrderHandler.GetPurchaseOrder)
|
||||||
|
purchaseOrders.PUT("/:id", r.purchaseOrderHandler.UpdatePurchaseOrder)
|
||||||
|
purchaseOrders.PUT("/:id/status/:status", r.purchaseOrderHandler.UpdatePurchaseOrderStatus)
|
||||||
|
purchaseOrders.DELETE("/:id", r.purchaseOrderHandler.DeletePurchaseOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
unitConverters := protected.Group("/unit-converters")
|
||||||
|
unitConverters.Use(r.authMiddleware.RequireAdminOrManager())
|
||||||
|
{
|
||||||
|
unitConverters.POST("", r.unitConverterHandler.CreateIngredientUnitConverter)
|
||||||
|
unitConverters.GET("", r.unitConverterHandler.ListIngredientUnitConverters)
|
||||||
|
unitConverters.GET("/ingredient/:ingredient_id", r.unitConverterHandler.GetConvertersForIngredient)
|
||||||
|
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 := protected.Group("/product-recipes")
|
||||||
productRecipes.Use(r.authMiddleware.RequireAdminOrManager())
|
productRecipes.Use(r.authMiddleware.RequireAdminOrManager())
|
||||||
{
|
{
|
||||||
@ -309,6 +369,43 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
|||||||
productRecipes.GET("/ingredient/:ingredient_id", r.productRecipeHandler.GetByIngredientID)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
outlets := protected.Group("/outlets")
|
outlets := protected.Group("/outlets")
|
||||||
outlets.Use(r.authMiddleware.RequireAdminOrManager())
|
outlets.Use(r.authMiddleware.RequireAdminOrManager())
|
||||||
{
|
{
|
||||||
|
|||||||
106
internal/service/account_service.go
Normal file
106
internal/service/account_service.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/mappers"
|
||||||
|
"apskel-pos-be/internal/processor"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountService interface {
|
||||||
|
contract.AccountContract
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountServiceImpl struct {
|
||||||
|
processor processor.AccountProcessor
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccountService(processor processor.AccountProcessor) AccountService {
|
||||||
|
return &AccountServiceImpl{
|
||||||
|
processor: processor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccountServiceImpl) CreateAccount(ctx context.Context, req *contract.CreateAccountRequest) (*contract.AccountResponse, error) {
|
||||||
|
modelReq := mappers.ContractToModelCreateAccountRequest(req)
|
||||||
|
modelResp, err := s.processor.CreateAccount(ctx, modelReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mappers.ModelToContractAccountResponse(modelResp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccountServiceImpl) GetAccountByID(ctx context.Context, id uuid.UUID) (*contract.AccountResponse, error) {
|
||||||
|
modelResp, err := s.processor.GetAccountByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mappers.ModelToContractAccountResponse(modelResp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccountServiceImpl) UpdateAccount(ctx context.Context, id uuid.UUID, req *contract.UpdateAccountRequest) (*contract.AccountResponse, error) {
|
||||||
|
modelReq := mappers.ContractToModelUpdateAccountRequest(req)
|
||||||
|
modelResp, err := s.processor.UpdateAccount(ctx, id, modelReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mappers.ModelToContractAccountResponse(modelResp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccountServiceImpl) DeleteAccount(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return s.processor.DeleteAccount(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccountServiceImpl) ListAccounts(ctx context.Context, req *contract.ListAccountsRequest) ([]contract.AccountResponse, int, error) {
|
||||||
|
modelReq := mappers.ContractToModelListAccountsRequest(req)
|
||||||
|
modelResp, total, err := s.processor.ListAccounts(ctx, modelReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResp := make([]contract.AccountResponse, len(modelResp))
|
||||||
|
for i, resp := range modelResp {
|
||||||
|
contractResp[i] = *mappers.ModelToContractAccountResponse(&resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return contractResp, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccountServiceImpl) GetAccountsByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]contract.AccountResponse, error) {
|
||||||
|
modelResp, err := s.processor.GetAccountsByOrganization(ctx, organizationID, outletID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResp := make([]contract.AccountResponse, len(modelResp))
|
||||||
|
for i, resp := range modelResp {
|
||||||
|
contractResp[i] = *mappers.ModelToContractAccountResponse(&resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return contractResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccountServiceImpl) GetAccountsByChartOfAccount(ctx context.Context, chartOfAccountID uuid.UUID) ([]contract.AccountResponse, error) {
|
||||||
|
modelResp, err := s.processor.GetAccountsByChartOfAccount(ctx, chartOfAccountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResp := make([]contract.AccountResponse, len(modelResp))
|
||||||
|
for i, resp := range modelResp {
|
||||||
|
contractResp[i] = *mappers.ModelToContractAccountResponse(&resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return contractResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccountServiceImpl) UpdateAccountBalance(ctx context.Context, id uuid.UUID, req *contract.UpdateAccountBalanceRequest) error {
|
||||||
|
return s.processor.UpdateAccountBalance(ctx, id, req.Amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AccountServiceImpl) GetAccountBalance(ctx context.Context, id uuid.UUID) (float64, error) {
|
||||||
|
return s.processor.GetAccountBalance(ctx, id)
|
||||||
|
}
|
||||||
98
internal/service/chart_of_account_service.go
Normal file
98
internal/service/chart_of_account_service.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/mappers"
|
||||||
|
"apskel-pos-be/internal/processor"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChartOfAccountService interface {
|
||||||
|
contract.ChartOfAccountContract
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartOfAccountServiceImpl struct {
|
||||||
|
processor processor.ChartOfAccountProcessor
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChartOfAccountService(processor processor.ChartOfAccountProcessor) ChartOfAccountService {
|
||||||
|
return &ChartOfAccountServiceImpl{
|
||||||
|
processor: processor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ChartOfAccountServiceImpl) CreateChartOfAccount(ctx context.Context, req *contract.CreateChartOfAccountRequest) (*contract.ChartOfAccountResponse, error) {
|
||||||
|
modelReq := mappers.ContractToModelCreateChartOfAccountRequest(req)
|
||||||
|
modelResp, err := s.processor.CreateChartOfAccount(ctx, modelReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mappers.ModelToContractChartOfAccountResponse(modelResp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ChartOfAccountServiceImpl) GetChartOfAccountByID(ctx context.Context, id uuid.UUID) (*contract.ChartOfAccountResponse, error) {
|
||||||
|
modelResp, err := s.processor.GetChartOfAccountByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mappers.ModelToContractChartOfAccountResponse(modelResp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ChartOfAccountServiceImpl) UpdateChartOfAccount(ctx context.Context, id uuid.UUID, req *contract.UpdateChartOfAccountRequest) (*contract.ChartOfAccountResponse, error) {
|
||||||
|
modelReq := mappers.ContractToModelUpdateChartOfAccountRequest(req)
|
||||||
|
modelResp, err := s.processor.UpdateChartOfAccount(ctx, id, modelReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mappers.ModelToContractChartOfAccountResponse(modelResp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ChartOfAccountServiceImpl) DeleteChartOfAccount(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return s.processor.DeleteChartOfAccount(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ChartOfAccountServiceImpl) ListChartOfAccounts(ctx context.Context, req *contract.ListChartOfAccountsRequest) ([]contract.ChartOfAccountResponse, int, error) {
|
||||||
|
modelReq := mappers.ContractToModelListChartOfAccountsRequest(req)
|
||||||
|
modelResp, total, err := s.processor.ListChartOfAccounts(ctx, modelReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResp := make([]contract.ChartOfAccountResponse, len(modelResp))
|
||||||
|
for i, resp := range modelResp {
|
||||||
|
contractResp[i] = *mappers.ModelToContractChartOfAccountResponse(&resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return contractResp, total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ChartOfAccountServiceImpl) GetChartOfAccountsByOrganization(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID) ([]contract.ChartOfAccountResponse, error) {
|
||||||
|
modelResp, err := s.processor.GetChartOfAccountsByOrganization(ctx, organizationID, outletID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResp := make([]contract.ChartOfAccountResponse, len(modelResp))
|
||||||
|
for i, resp := range modelResp {
|
||||||
|
contractResp[i] = *mappers.ModelToContractChartOfAccountResponse(&resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return contractResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ChartOfAccountServiceImpl) GetChartOfAccountsByType(ctx context.Context, organizationID uuid.UUID, chartOfAccountTypeID uuid.UUID, outletID *uuid.UUID) ([]contract.ChartOfAccountResponse, error) {
|
||||||
|
modelResp, err := s.processor.GetChartOfAccountsByType(ctx, organizationID, chartOfAccountTypeID, outletID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResp := make([]contract.ChartOfAccountResponse, len(modelResp))
|
||||||
|
for i, resp := range modelResp {
|
||||||
|
contractResp[i] = *mappers.ModelToContractChartOfAccountResponse(&resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return contractResp, nil
|
||||||
|
}
|
||||||
69
internal/service/chart_of_account_type_service.go
Normal file
69
internal/service/chart_of_account_type_service.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/mappers"
|
||||||
|
"apskel-pos-be/internal/processor"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChartOfAccountTypeService interface {
|
||||||
|
contract.ChartOfAccountTypeContract
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartOfAccountTypeServiceImpl struct {
|
||||||
|
processor processor.ChartOfAccountTypeProcessor
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChartOfAccountTypeService(processor processor.ChartOfAccountTypeProcessor) ChartOfAccountTypeService {
|
||||||
|
return &ChartOfAccountTypeServiceImpl{
|
||||||
|
processor: processor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ChartOfAccountTypeServiceImpl) CreateChartOfAccountType(ctx context.Context, req *contract.CreateChartOfAccountTypeRequest) (*contract.ChartOfAccountTypeResponse, error) {
|
||||||
|
modelReq := mappers.ContractToModelCreateChartOfAccountTypeRequest(req)
|
||||||
|
modelResp, err := s.processor.CreateChartOfAccountType(ctx, modelReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mappers.ModelToContractChartOfAccountTypeResponse(modelResp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ChartOfAccountTypeServiceImpl) GetChartOfAccountTypeByID(ctx context.Context, id uuid.UUID) (*contract.ChartOfAccountTypeResponse, error) {
|
||||||
|
modelResp, err := s.processor.GetChartOfAccountTypeByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mappers.ModelToContractChartOfAccountTypeResponse(modelResp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ChartOfAccountTypeServiceImpl) UpdateChartOfAccountType(ctx context.Context, id uuid.UUID, req *contract.UpdateChartOfAccountTypeRequest) (*contract.ChartOfAccountTypeResponse, error) {
|
||||||
|
modelReq := mappers.ContractToModelUpdateChartOfAccountTypeRequest(req)
|
||||||
|
modelResp, err := s.processor.UpdateChartOfAccountType(ctx, id, modelReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mappers.ModelToContractChartOfAccountTypeResponse(modelResp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ChartOfAccountTypeServiceImpl) DeleteChartOfAccountType(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return s.processor.DeleteChartOfAccountType(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ChartOfAccountTypeServiceImpl) ListChartOfAccountTypes(ctx context.Context, filters map[string]interface{}, page, limit int) ([]contract.ChartOfAccountTypeResponse, int, error) {
|
||||||
|
modelResp, total, err := s.processor.ListChartOfAccountTypes(ctx, filters, page, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResp := make([]contract.ChartOfAccountTypeResponse, len(modelResp))
|
||||||
|
for i, resp := range modelResp {
|
||||||
|
contractResp[i] = *mappers.ModelToContractChartOfAccountTypeResponse(&resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return contractResp, total, nil
|
||||||
|
}
|
||||||
151
internal/service/ingredient_unit_converter_service.go
Normal file
151
internal/service/ingredient_unit_converter_service.go
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/appcontext"
|
||||||
|
"apskel-pos-be/internal/constants"
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/processor"
|
||||||
|
"apskel-pos-be/internal/transformer"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IngredientUnitConverterService interface {
|
||||||
|
CreateIngredientUnitConverter(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.CreateIngredientUnitConverterRequest) *contract.Response
|
||||||
|
UpdateIngredientUnitConverter(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID, req *contract.UpdateIngredientUnitConverterRequest) *contract.Response
|
||||||
|
DeleteIngredientUnitConverter(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response
|
||||||
|
GetIngredientUnitConverter(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response
|
||||||
|
ListIngredientUnitConverters(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.ListIngredientUnitConvertersRequest) *contract.Response
|
||||||
|
GetConvertersForIngredient(ctx context.Context, apctx *appcontext.ContextInfo, ingredientID uuid.UUID) *contract.Response
|
||||||
|
ConvertUnit(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.ConvertUnitRequest) *contract.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
type IngredientUnitConverterServiceImpl struct {
|
||||||
|
converterProcessor processor.IngredientUnitConverterProcessor
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIngredientUnitConverterService(converterProcessor processor.IngredientUnitConverterProcessor) *IngredientUnitConverterServiceImpl {
|
||||||
|
return &IngredientUnitConverterServiceImpl{
|
||||||
|
converterProcessor: converterProcessor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IngredientUnitConverterServiceImpl) CreateIngredientUnitConverter(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.CreateIngredientUnitConverterRequest) *contract.Response {
|
||||||
|
modelReq := transformer.CreateIngredientUnitConverterRequestToModel(req)
|
||||||
|
|
||||||
|
converterResponse, err := s.converterProcessor.CreateIngredientUnitConverter(ctx, apctx.OrganizationID, apctx.UserID, modelReq)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.IngredientUnitConverterServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponse := transformer.IngredientUnitConverterModelResponseToResponse(converterResponse)
|
||||||
|
return contract.BuildSuccessResponse(contractResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IngredientUnitConverterServiceImpl) UpdateIngredientUnitConverter(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID, req *contract.UpdateIngredientUnitConverterRequest) *contract.Response {
|
||||||
|
modelReq := transformer.UpdateIngredientUnitConverterRequestToModel(req)
|
||||||
|
|
||||||
|
converterResponse, err := s.converterProcessor.UpdateIngredientUnitConverter(ctx, id, apctx.OrganizationID, apctx.UserID, modelReq)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.IngredientUnitConverterServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponse := transformer.IngredientUnitConverterModelResponseToResponse(converterResponse)
|
||||||
|
return contract.BuildSuccessResponse(contractResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IngredientUnitConverterServiceImpl) DeleteIngredientUnitConverter(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response {
|
||||||
|
err := s.converterProcessor.DeleteIngredientUnitConverter(ctx, id, apctx.OrganizationID)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.IngredientUnitConverterServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
return contract.BuildSuccessResponse(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IngredientUnitConverterServiceImpl) GetIngredientUnitConverter(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response {
|
||||||
|
converterResponse, err := s.converterProcessor.GetIngredientUnitConverterByID(ctx, id, apctx.OrganizationID)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.IngredientUnitConverterServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponse := transformer.IngredientUnitConverterModelResponseToResponse(converterResponse)
|
||||||
|
return contract.BuildSuccessResponse(contractResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IngredientUnitConverterServiceImpl) ListIngredientUnitConverters(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.ListIngredientUnitConvertersRequest) *contract.Response {
|
||||||
|
modelReq := transformer.ListIngredientUnitConvertersRequestToModel(req)
|
||||||
|
|
||||||
|
filters := make(map[string]interface{})
|
||||||
|
if modelReq.IngredientID != nil {
|
||||||
|
filters["ingredient_id"] = *modelReq.IngredientID
|
||||||
|
}
|
||||||
|
if modelReq.FromUnitID != nil {
|
||||||
|
filters["from_unit_id"] = *modelReq.FromUnitID
|
||||||
|
}
|
||||||
|
if modelReq.ToUnitID != nil {
|
||||||
|
filters["to_unit_id"] = *modelReq.ToUnitID
|
||||||
|
}
|
||||||
|
if modelReq.IsActive != nil {
|
||||||
|
filters["is_active"] = *modelReq.IsActive
|
||||||
|
}
|
||||||
|
if modelReq.Search != "" {
|
||||||
|
filters["search"] = modelReq.Search
|
||||||
|
}
|
||||||
|
|
||||||
|
converters, total, err := s.converterProcessor.ListIngredientUnitConverters(ctx, apctx.OrganizationID, filters, modelReq.Page, modelReq.Limit)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.IngredientUnitConverterServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponses := make([]contract.IngredientUnitConverterResponse, len(converters))
|
||||||
|
for i, converter := range converters {
|
||||||
|
contractResponses[i] = transformer.IngredientUnitConverterModelResponseToResponse(converter)
|
||||||
|
}
|
||||||
|
|
||||||
|
totalPages := (total + modelReq.Limit - 1) / modelReq.Limit
|
||||||
|
response := contract.ListIngredientUnitConvertersResponse{
|
||||||
|
Converters: contractResponses,
|
||||||
|
TotalCount: total,
|
||||||
|
Page: modelReq.Page,
|
||||||
|
Limit: modelReq.Limit,
|
||||||
|
TotalPages: totalPages,
|
||||||
|
}
|
||||||
|
|
||||||
|
return contract.BuildSuccessResponse(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IngredientUnitConverterServiceImpl) GetConvertersForIngredient(ctx context.Context, apctx *appcontext.ContextInfo, ingredientID uuid.UUID) *contract.Response {
|
||||||
|
converters, err := s.converterProcessor.GetConvertersForIngredient(ctx, ingredientID, apctx.OrganizationID)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.IngredientUnitConverterServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponses := make([]contract.IngredientUnitConverterResponse, len(converters))
|
||||||
|
for i, converter := range converters {
|
||||||
|
contractResponses[i] = transformer.IngredientUnitConverterModelResponseToResponse(converter)
|
||||||
|
}
|
||||||
|
|
||||||
|
return contract.BuildSuccessResponse(contractResponses)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IngredientUnitConverterServiceImpl) ConvertUnit(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.ConvertUnitRequest) *contract.Response {
|
||||||
|
modelReq := transformer.ConvertUnitRequestToModel(req)
|
||||||
|
|
||||||
|
convertResponse, err := s.converterProcessor.ConvertUnit(ctx, apctx.OrganizationID, modelReq)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.IngredientUnitConverterServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponse := transformer.ConvertUnitModelResponseToResponse(convertResponse)
|
||||||
|
return contract.BuildSuccessResponse(contractResponse)
|
||||||
|
}
|
||||||
|
|
||||||
175
internal/service/purchase_order_service.go
Normal file
175
internal/service/purchase_order_service.go
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/appcontext"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/constants"
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/processor"
|
||||||
|
"apskel-pos-be/internal/transformer"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PurchaseOrderService interface {
|
||||||
|
CreatePurchaseOrder(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.CreatePurchaseOrderRequest) *contract.Response
|
||||||
|
UpdatePurchaseOrder(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID, req *contract.UpdatePurchaseOrderRequest) *contract.Response
|
||||||
|
DeletePurchaseOrder(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response
|
||||||
|
GetPurchaseOrderByID(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response
|
||||||
|
ListPurchaseOrders(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.ListPurchaseOrdersRequest) *contract.Response
|
||||||
|
GetPurchaseOrdersByStatus(ctx context.Context, apctx *appcontext.ContextInfo, status string) *contract.Response
|
||||||
|
GetOverduePurchaseOrders(ctx context.Context, apctx *appcontext.ContextInfo) *contract.Response
|
||||||
|
UpdatePurchaseOrderStatus(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID, status string) *contract.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
type PurchaseOrderServiceImpl struct {
|
||||||
|
purchaseOrderProcessor processor.PurchaseOrderProcessor
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPurchaseOrderService(purchaseOrderProcessor processor.PurchaseOrderProcessor) *PurchaseOrderServiceImpl {
|
||||||
|
return &PurchaseOrderServiceImpl{
|
||||||
|
purchaseOrderProcessor: purchaseOrderProcessor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PurchaseOrderServiceImpl) CreatePurchaseOrder(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.CreatePurchaseOrderRequest) *contract.Response {
|
||||||
|
modelReq := transformer.CreatePurchaseOrderRequestToModel(req)
|
||||||
|
|
||||||
|
poResponse, err := s.purchaseOrderProcessor.CreatePurchaseOrder(ctx, apctx.OrganizationID, modelReq)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.PurchaseOrderServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponse := transformer.PurchaseOrderModelResponseToResponse(poResponse)
|
||||||
|
return contract.BuildSuccessResponse(contractResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PurchaseOrderServiceImpl) UpdatePurchaseOrder(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID, req *contract.UpdatePurchaseOrderRequest) *contract.Response {
|
||||||
|
modelReq := transformer.UpdatePurchaseOrderRequestToModel(req)
|
||||||
|
|
||||||
|
poResponse, err := s.purchaseOrderProcessor.UpdatePurchaseOrder(ctx, id, apctx.OrganizationID, modelReq)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.PurchaseOrderServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponse := transformer.PurchaseOrderModelResponseToResponse(poResponse)
|
||||||
|
return contract.BuildSuccessResponse(contractResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PurchaseOrderServiceImpl) DeletePurchaseOrder(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response {
|
||||||
|
err := s.purchaseOrderProcessor.DeletePurchaseOrder(ctx, id, apctx.OrganizationID)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.PurchaseOrderServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
return contract.BuildSuccessResponse(map[string]interface{}{
|
||||||
|
"message": "Purchase order deleted successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PurchaseOrderServiceImpl) GetPurchaseOrderByID(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response {
|
||||||
|
poResponse, err := s.purchaseOrderProcessor.GetPurchaseOrderByID(ctx, id, apctx.OrganizationID)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.PurchaseOrderServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponse := transformer.PurchaseOrderModelResponseToResponse(poResponse)
|
||||||
|
return contract.BuildSuccessResponse(contractResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PurchaseOrderServiceImpl) ListPurchaseOrders(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.ListPurchaseOrdersRequest) *contract.Response {
|
||||||
|
modelReq := transformer.ListPurchaseOrdersRequestToModel(req)
|
||||||
|
|
||||||
|
filters := make(map[string]interface{})
|
||||||
|
if modelReq.Search != "" {
|
||||||
|
filters["search"] = modelReq.Search
|
||||||
|
}
|
||||||
|
if modelReq.Status != "" {
|
||||||
|
filters["status"] = modelReq.Status
|
||||||
|
}
|
||||||
|
if modelReq.VendorID != nil {
|
||||||
|
filters["vendor_id"] = *modelReq.VendorID
|
||||||
|
}
|
||||||
|
if modelReq.StartDate != nil {
|
||||||
|
filters["start_date"] = *modelReq.StartDate
|
||||||
|
}
|
||||||
|
if modelReq.EndDate != nil {
|
||||||
|
filters["end_date"] = *modelReq.EndDate
|
||||||
|
}
|
||||||
|
|
||||||
|
pos, totalPages, err := s.purchaseOrderProcessor.ListPurchaseOrders(ctx, apctx.OrganizationID, filters, modelReq.Page, modelReq.Limit)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.PurchaseOrderServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponses := make([]contract.PurchaseOrderResponse, len(pos))
|
||||||
|
for i, po := range pos {
|
||||||
|
response := transformer.PurchaseOrderModelResponseToResponse(po)
|
||||||
|
if response != nil {
|
||||||
|
contractResponses[i] = *response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response := contract.ListPurchaseOrdersResponse{
|
||||||
|
PurchaseOrders: contractResponses,
|
||||||
|
TotalCount: len(contractResponses),
|
||||||
|
Page: modelReq.Page,
|
||||||
|
Limit: modelReq.Limit,
|
||||||
|
TotalPages: totalPages,
|
||||||
|
}
|
||||||
|
|
||||||
|
return contract.BuildSuccessResponse(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PurchaseOrderServiceImpl) GetPurchaseOrdersByStatus(ctx context.Context, apctx *appcontext.ContextInfo, status string) *contract.Response {
|
||||||
|
poResponses, err := s.purchaseOrderProcessor.GetPurchaseOrdersByStatus(ctx, apctx.OrganizationID, status)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.PurchaseOrderServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponses := make([]contract.PurchaseOrderResponse, len(poResponses))
|
||||||
|
for i, po := range poResponses {
|
||||||
|
response := transformer.PurchaseOrderModelResponseToResponse(po)
|
||||||
|
if response != nil {
|
||||||
|
contractResponses[i] = *response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contract.BuildSuccessResponse(contractResponses)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PurchaseOrderServiceImpl) GetOverduePurchaseOrders(ctx context.Context, apctx *appcontext.ContextInfo) *contract.Response {
|
||||||
|
poResponses, err := s.purchaseOrderProcessor.GetOverduePurchaseOrders(ctx, apctx.OrganizationID)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.PurchaseOrderServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponses := make([]contract.PurchaseOrderResponse, len(poResponses))
|
||||||
|
for i, po := range poResponses {
|
||||||
|
response := transformer.PurchaseOrderModelResponseToResponse(po)
|
||||||
|
if response != nil {
|
||||||
|
contractResponses[i] = *response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contract.BuildSuccessResponse(contractResponses)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PurchaseOrderServiceImpl) UpdatePurchaseOrderStatus(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID, status string) *contract.Response {
|
||||||
|
poResponse, err := s.purchaseOrderProcessor.UpdatePurchaseOrderStatus(ctx, id, apctx.OrganizationID, apctx.UserID, apctx.OutletID, status)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.PurchaseOrderServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponse := transformer.PurchaseOrderModelResponseToResponse(poResponse)
|
||||||
|
return contract.BuildSuccessResponse(contractResponse)
|
||||||
|
}
|
||||||
122
internal/service/vendor_service.go
Normal file
122
internal/service/vendor_service.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/appcontext"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/constants"
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/processor"
|
||||||
|
"apskel-pos-be/internal/transformer"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VendorService interface {
|
||||||
|
CreateVendor(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.CreateVendorRequest) *contract.Response
|
||||||
|
UpdateVendor(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID, req *contract.UpdateVendorRequest) *contract.Response
|
||||||
|
DeleteVendor(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response
|
||||||
|
GetVendorByID(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response
|
||||||
|
ListVendors(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.ListVendorsRequest) *contract.Response
|
||||||
|
GetActiveVendors(ctx context.Context, apctx *appcontext.ContextInfo) *contract.Response
|
||||||
|
}
|
||||||
|
|
||||||
|
type VendorServiceImpl struct {
|
||||||
|
vendorProcessor processor.VendorProcessor
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVendorService(vendorProcessor processor.VendorProcessor) *VendorServiceImpl {
|
||||||
|
return &VendorServiceImpl{
|
||||||
|
vendorProcessor: vendorProcessor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *VendorServiceImpl) CreateVendor(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.CreateVendorRequest) *contract.Response {
|
||||||
|
modelReq := transformer.CreateVendorRequestToModel(req)
|
||||||
|
|
||||||
|
vendorResponse, err := s.vendorProcessor.CreateVendor(ctx, apctx.OrganizationID, modelReq)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.VendorServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponse := transformer.VendorModelResponseToResponse(vendorResponse)
|
||||||
|
return contract.BuildSuccessResponse(contractResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *VendorServiceImpl) UpdateVendor(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID, req *contract.UpdateVendorRequest) *contract.Response {
|
||||||
|
modelReq := transformer.UpdateVendorRequestToModel(req)
|
||||||
|
|
||||||
|
vendorResponse, err := s.vendorProcessor.UpdateVendor(ctx, id, apctx.OrganizationID, modelReq)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.VendorServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponse := transformer.VendorModelResponseToResponse(vendorResponse)
|
||||||
|
return contract.BuildSuccessResponse(contractResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *VendorServiceImpl) DeleteVendor(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response {
|
||||||
|
err := s.vendorProcessor.DeleteVendor(ctx, id, apctx.OrganizationID)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.VendorServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
return contract.BuildSuccessResponse(map[string]interface{}{
|
||||||
|
"message": "Vendor deleted successfully",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *VendorServiceImpl) GetVendorByID(ctx context.Context, apctx *appcontext.ContextInfo, id uuid.UUID) *contract.Response {
|
||||||
|
vendorResponse, err := s.vendorProcessor.GetVendorByID(ctx, id, apctx.OrganizationID)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.VendorServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponse := transformer.VendorModelResponseToResponse(vendorResponse)
|
||||||
|
return contract.BuildSuccessResponse(contractResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *VendorServiceImpl) ListVendors(ctx context.Context, apctx *appcontext.ContextInfo, req *contract.ListVendorsRequest) *contract.Response {
|
||||||
|
modelReq := transformer.ListVendorsRequestToModel(req)
|
||||||
|
|
||||||
|
filters := make(map[string]interface{})
|
||||||
|
if modelReq.Search != "" {
|
||||||
|
filters["search"] = modelReq.Search
|
||||||
|
}
|
||||||
|
if modelReq.IsActive != nil {
|
||||||
|
filters["is_active"] = *modelReq.IsActive
|
||||||
|
}
|
||||||
|
|
||||||
|
vendors, totalPages, err := s.vendorProcessor.ListVendors(ctx, apctx.OrganizationID, filters, modelReq.Page, modelReq.Limit)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.VendorServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponses := transformer.VendorModelResponsesToResponses(vendors)
|
||||||
|
|
||||||
|
response := contract.ListVendorsResponse{
|
||||||
|
Vendors: contractResponses,
|
||||||
|
TotalCount: len(contractResponses),
|
||||||
|
Page: modelReq.Page,
|
||||||
|
Limit: modelReq.Limit,
|
||||||
|
TotalPages: totalPages,
|
||||||
|
}
|
||||||
|
|
||||||
|
return contract.BuildSuccessResponse(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *VendorServiceImpl) GetActiveVendors(ctx context.Context, apctx *appcontext.ContextInfo) *contract.Response {
|
||||||
|
vendorResponses, err := s.vendorProcessor.GetActiveVendors(ctx, apctx.OrganizationID)
|
||||||
|
if err != nil {
|
||||||
|
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.VendorServiceEntity, err.Error())
|
||||||
|
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResponses := transformer.VendorModelResponsesToResponses(vendorResponses)
|
||||||
|
return contract.BuildSuccessResponse(contractResponses)
|
||||||
|
}
|
||||||
154
internal/transformer/ingredient_unit_converter_transformer.go
Normal file
154
internal/transformer/ingredient_unit_converter_transformer.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
package transformer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Request transformations
|
||||||
|
func CreateIngredientUnitConverterRequestToModel(req *contract.CreateIngredientUnitConverterRequest) *models.CreateIngredientUnitConverterRequest {
|
||||||
|
if req == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.CreateIngredientUnitConverterRequest{
|
||||||
|
IngredientID: req.IngredientID,
|
||||||
|
FromUnitID: req.FromUnitID,
|
||||||
|
ToUnitID: req.ToUnitID,
|
||||||
|
ConversionFactor: req.ConversionFactor,
|
||||||
|
IsActive: req.IsActive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateIngredientUnitConverterRequestToModel(req *contract.UpdateIngredientUnitConverterRequest) *models.UpdateIngredientUnitConverterRequest {
|
||||||
|
if req == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.UpdateIngredientUnitConverterRequest{
|
||||||
|
FromUnitID: req.FromUnitID,
|
||||||
|
ToUnitID: req.ToUnitID,
|
||||||
|
ConversionFactor: req.ConversionFactor,
|
||||||
|
IsActive: req.IsActive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListIngredientUnitConvertersRequestToModel(req *contract.ListIngredientUnitConvertersRequest) *models.ListIngredientUnitConvertersRequest {
|
||||||
|
if req == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.ListIngredientUnitConvertersRequest{
|
||||||
|
IngredientID: req.IngredientID,
|
||||||
|
FromUnitID: req.FromUnitID,
|
||||||
|
ToUnitID: req.ToUnitID,
|
||||||
|
IsActive: req.IsActive,
|
||||||
|
Search: req.Search,
|
||||||
|
Page: req.Page,
|
||||||
|
Limit: req.Limit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertUnitRequestToModel(req *contract.ConvertUnitRequest) *models.ConvertUnitRequest {
|
||||||
|
if req == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.ConvertUnitRequest{
|
||||||
|
IngredientID: req.IngredientID,
|
||||||
|
FromUnitID: req.FromUnitID,
|
||||||
|
ToUnitID: req.ToUnitID,
|
||||||
|
Quantity: req.Quantity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response transformations
|
||||||
|
func IngredientUnitConverterModelResponseToResponse(model *models.IngredientUnitConverterResponse) contract.IngredientUnitConverterResponse {
|
||||||
|
if model == nil {
|
||||||
|
return contract.IngredientUnitConverterResponse{}
|
||||||
|
}
|
||||||
|
|
||||||
|
response := contract.IngredientUnitConverterResponse{
|
||||||
|
ID: model.ID,
|
||||||
|
OrganizationID: model.OrganizationID,
|
||||||
|
IngredientID: model.IngredientID,
|
||||||
|
FromUnitID: model.FromUnitID,
|
||||||
|
ToUnitID: model.ToUnitID,
|
||||||
|
ConversionFactor: model.ConversionFactor,
|
||||||
|
IsActive: model.IsActive,
|
||||||
|
CreatedAt: model.CreatedAt,
|
||||||
|
UpdatedAt: model.UpdatedAt,
|
||||||
|
CreatedBy: model.CreatedBy,
|
||||||
|
UpdatedBy: model.UpdatedBy,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map related entities
|
||||||
|
if model.Ingredient != nil {
|
||||||
|
ingredientResp := IngredientModelResponseToResponse(model.Ingredient)
|
||||||
|
response.Ingredient = &ingredientResp
|
||||||
|
}
|
||||||
|
if model.FromUnit != nil {
|
||||||
|
fromUnitResp := UnitModelResponseToResponse(model.FromUnit)
|
||||||
|
response.FromUnit = &fromUnitResp
|
||||||
|
}
|
||||||
|
if model.ToUnit != nil {
|
||||||
|
toUnitResp := UnitModelResponseToResponse(model.ToUnit)
|
||||||
|
response.ToUnit = &toUnitResp
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertUnitModelResponseToResponse(model *models.ConvertUnitResponse) contract.ConvertUnitResponse {
|
||||||
|
if model == nil {
|
||||||
|
return contract.ConvertUnitResponse{}
|
||||||
|
}
|
||||||
|
|
||||||
|
response := contract.ConvertUnitResponse{
|
||||||
|
FromQuantity: model.FromQuantity,
|
||||||
|
ToQuantity: model.ToQuantity,
|
||||||
|
ConversionFactor: model.ConversionFactor,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map units
|
||||||
|
if model.FromUnit != nil {
|
||||||
|
fromUnitResp := UnitModelResponseToResponse(model.FromUnit)
|
||||||
|
response.FromUnit = &fromUnitResp
|
||||||
|
}
|
||||||
|
if model.ToUnit != nil {
|
||||||
|
toUnitResp := UnitModelResponseToResponse(model.ToUnit)
|
||||||
|
response.ToUnit = &toUnitResp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map ingredient
|
||||||
|
if model.Ingredient != nil {
|
||||||
|
ingredientResp := IngredientModelResponseToResponse(model.Ingredient)
|
||||||
|
response.Ingredient = &ingredientResp
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions for related entities
|
||||||
|
func IngredientModelResponseToResponse(model *models.IngredientResponse) contract.IngredientResponse {
|
||||||
|
if model == nil {
|
||||||
|
return contract.IngredientResponse{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contract.IngredientResponse{
|
||||||
|
ID: model.ID,
|
||||||
|
Name: model.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnitModelResponseToResponse(model *models.UnitResponse) contract.UnitResponse {
|
||||||
|
if model == nil {
|
||||||
|
return contract.UnitResponse{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contract.UnitResponse{
|
||||||
|
ID: model.ID,
|
||||||
|
Name: model.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
204
internal/transformer/purchase_order_transformer.go
Normal file
204
internal/transformer/purchase_order_transformer.go
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
package transformer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Contract to Model conversions
|
||||||
|
func CreatePurchaseOrderRequestToModel(req *contract.CreatePurchaseOrderRequest) *models.CreatePurchaseOrderRequest {
|
||||||
|
items := make([]models.CreatePurchaseOrderItemRequest, len(req.Items))
|
||||||
|
for i, item := range req.Items {
|
||||||
|
items[i] = models.CreatePurchaseOrderItemRequest{
|
||||||
|
IngredientID: item.IngredientID,
|
||||||
|
Description: item.Description,
|
||||||
|
Quantity: item.Quantity,
|
||||||
|
UnitID: item.UnitID,
|
||||||
|
Amount: item.Amount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.CreatePurchaseOrderRequest{
|
||||||
|
VendorID: req.VendorID,
|
||||||
|
PONumber: req.PONumber,
|
||||||
|
TransactionDate: req.TransactionDate,
|
||||||
|
DueDate: req.DueDate,
|
||||||
|
Reference: req.Reference,
|
||||||
|
Status: req.Status,
|
||||||
|
Message: req.Message,
|
||||||
|
Items: items,
|
||||||
|
AttachmentFileIDs: req.AttachmentFileIDs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdatePurchaseOrderRequestToModel(req *contract.UpdatePurchaseOrderRequest) *models.UpdatePurchaseOrderRequest {
|
||||||
|
var items []models.UpdatePurchaseOrderItemRequest
|
||||||
|
if req.Items != nil {
|
||||||
|
items = make([]models.UpdatePurchaseOrderItemRequest, len(req.Items))
|
||||||
|
for i, item := range req.Items {
|
||||||
|
items[i] = models.UpdatePurchaseOrderItemRequest{
|
||||||
|
ID: item.ID,
|
||||||
|
IngredientID: item.IngredientID,
|
||||||
|
Description: item.Description,
|
||||||
|
Quantity: item.Quantity,
|
||||||
|
UnitID: item.UnitID,
|
||||||
|
Amount: item.Amount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.UpdatePurchaseOrderRequest{
|
||||||
|
VendorID: req.VendorID,
|
||||||
|
PONumber: req.PONumber,
|
||||||
|
TransactionDate: req.TransactionDate,
|
||||||
|
DueDate: req.DueDate,
|
||||||
|
Reference: req.Reference,
|
||||||
|
Status: req.Status,
|
||||||
|
Message: req.Message,
|
||||||
|
Items: items,
|
||||||
|
AttachmentFileIDs: req.AttachmentFileIDs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListPurchaseOrdersRequestToModel(req *contract.ListPurchaseOrdersRequest) *models.ListPurchaseOrdersRequest {
|
||||||
|
return &models.ListPurchaseOrdersRequest{
|
||||||
|
Page: req.Page,
|
||||||
|
Limit: req.Limit,
|
||||||
|
Search: req.Search,
|
||||||
|
Status: req.Status,
|
||||||
|
VendorID: req.VendorID,
|
||||||
|
StartDate: req.StartDate,
|
||||||
|
EndDate: req.EndDate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Model to Contract conversions
|
||||||
|
func PurchaseOrderModelResponseToResponse(po *models.PurchaseOrderResponse) *contract.PurchaseOrderResponse {
|
||||||
|
if po == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
response := &contract.PurchaseOrderResponse{
|
||||||
|
ID: po.ID,
|
||||||
|
OrganizationID: po.OrganizationID,
|
||||||
|
VendorID: po.VendorID,
|
||||||
|
PONumber: po.PONumber,
|
||||||
|
TransactionDate: po.TransactionDate,
|
||||||
|
DueDate: po.DueDate,
|
||||||
|
Reference: po.Reference,
|
||||||
|
Status: po.Status,
|
||||||
|
Message: po.Message,
|
||||||
|
TotalAmount: po.TotalAmount,
|
||||||
|
CreatedAt: po.CreatedAt,
|
||||||
|
UpdatedAt: po.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map vendor if present
|
||||||
|
if po.Vendor != nil {
|
||||||
|
response.Vendor = &contract.VendorResponse{
|
||||||
|
ID: po.Vendor.ID,
|
||||||
|
OrganizationID: po.Vendor.OrganizationID,
|
||||||
|
Name: po.Vendor.Name,
|
||||||
|
Email: po.Vendor.Email,
|
||||||
|
PhoneNumber: po.Vendor.PhoneNumber,
|
||||||
|
Address: po.Vendor.Address,
|
||||||
|
ContactPerson: po.Vendor.ContactPerson,
|
||||||
|
TaxNumber: po.Vendor.TaxNumber,
|
||||||
|
PaymentTerms: po.Vendor.PaymentTerms,
|
||||||
|
Notes: po.Vendor.Notes,
|
||||||
|
IsActive: po.Vendor.IsActive,
|
||||||
|
CreatedAt: po.Vendor.CreatedAt,
|
||||||
|
UpdatedAt: po.Vendor.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map items if present
|
||||||
|
if po.Items != nil {
|
||||||
|
response.Items = make([]contract.PurchaseOrderItemResponse, len(po.Items))
|
||||||
|
for i, item := range po.Items {
|
||||||
|
response.Items[i] = contract.PurchaseOrderItemResponse{
|
||||||
|
ID: item.ID,
|
||||||
|
PurchaseOrderID: item.PurchaseOrderID,
|
||||||
|
IngredientID: item.IngredientID,
|
||||||
|
Description: item.Description,
|
||||||
|
Quantity: item.Quantity,
|
||||||
|
UnitID: item.UnitID,
|
||||||
|
Amount: item.Amount,
|
||||||
|
CreatedAt: item.CreatedAt,
|
||||||
|
UpdatedAt: item.UpdatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map ingredient if present
|
||||||
|
if item.Ingredient != nil {
|
||||||
|
response.Items[i].Ingredient = &contract.IngredientResponse{
|
||||||
|
ID: item.Ingredient.ID,
|
||||||
|
Name: item.Ingredient.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map unit if present
|
||||||
|
if item.Unit != nil {
|
||||||
|
response.Items[i].Unit = &contract.UnitResponse{
|
||||||
|
ID: item.Unit.ID,
|
||||||
|
Name: item.Unit.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map attachments if present
|
||||||
|
if po.Attachments != nil {
|
||||||
|
response.Attachments = make([]contract.PurchaseOrderAttachmentResponse, len(po.Attachments))
|
||||||
|
for i, attachment := range po.Attachments {
|
||||||
|
response.Attachments[i] = contract.PurchaseOrderAttachmentResponse{
|
||||||
|
ID: attachment.ID,
|
||||||
|
PurchaseOrderID: attachment.PurchaseOrderID,
|
||||||
|
FileID: attachment.FileID,
|
||||||
|
CreatedAt: attachment.CreatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map file if present
|
||||||
|
if attachment.File != nil {
|
||||||
|
response.Attachments[i].File = &contract.FileResponse{
|
||||||
|
ID: attachment.File.ID,
|
||||||
|
FileName: attachment.File.FileName,
|
||||||
|
OriginalName: attachment.File.OriginalName,
|
||||||
|
FileURL: attachment.File.FileURL,
|
||||||
|
FileSize: attachment.File.FileSize,
|
||||||
|
MimeType: attachment.File.MimeType,
|
||||||
|
FileType: attachment.File.FileType,
|
||||||
|
IsPublic: attachment.File.IsPublic,
|
||||||
|
CreatedAt: attachment.File.CreatedAt,
|
||||||
|
UpdatedAt: attachment.File.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
func PurchaseOrderModelResponsesToResponses(pos []models.PurchaseOrderResponse) []contract.PurchaseOrderResponse {
|
||||||
|
if pos == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]contract.PurchaseOrderResponse, len(pos))
|
||||||
|
for i, po := range pos {
|
||||||
|
response := PurchaseOrderModelResponseToResponse(&po)
|
||||||
|
if response != nil {
|
||||||
|
responses[i] = *response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return responses
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListPurchaseOrdersModelResponseToResponse(resp *models.ListPurchaseOrdersResponse) *contract.ListPurchaseOrdersResponse {
|
||||||
|
return &contract.ListPurchaseOrdersResponse{
|
||||||
|
PurchaseOrders: PurchaseOrderModelResponsesToResponses(resp.PurchaseOrders),
|
||||||
|
TotalCount: resp.TotalCount,
|
||||||
|
Page: resp.Page,
|
||||||
|
Limit: resp.Limit,
|
||||||
|
TotalPages: resp.TotalPages,
|
||||||
|
}
|
||||||
|
}
|
||||||
89
internal/transformer/vendor_transformer.go
Normal file
89
internal/transformer/vendor_transformer.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package transformer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Contract to Model conversions
|
||||||
|
func CreateVendorRequestToModel(req *contract.CreateVendorRequest) *models.CreateVendorRequest {
|
||||||
|
return &models.CreateVendorRequest{
|
||||||
|
Name: req.Name,
|
||||||
|
Email: req.Email,
|
||||||
|
PhoneNumber: req.PhoneNumber,
|
||||||
|
Address: req.Address,
|
||||||
|
ContactPerson: req.ContactPerson,
|
||||||
|
TaxNumber: req.TaxNumber,
|
||||||
|
PaymentTerms: req.PaymentTerms,
|
||||||
|
Notes: req.Notes,
|
||||||
|
IsActive: req.IsActive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateVendorRequestToModel(req *contract.UpdateVendorRequest) *models.UpdateVendorRequest {
|
||||||
|
return &models.UpdateVendorRequest{
|
||||||
|
Name: req.Name,
|
||||||
|
Email: req.Email,
|
||||||
|
PhoneNumber: req.PhoneNumber,
|
||||||
|
Address: req.Address,
|
||||||
|
ContactPerson: req.ContactPerson,
|
||||||
|
TaxNumber: req.TaxNumber,
|
||||||
|
PaymentTerms: req.PaymentTerms,
|
||||||
|
Notes: req.Notes,
|
||||||
|
IsActive: req.IsActive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListVendorsRequestToModel(req *contract.ListVendorsRequest) *models.ListVendorsRequest {
|
||||||
|
return &models.ListVendorsRequest{
|
||||||
|
Page: req.Page,
|
||||||
|
Limit: req.Limit,
|
||||||
|
Search: req.Search,
|
||||||
|
IsActive: req.IsActive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Model to Contract conversions
|
||||||
|
func VendorModelResponseToResponse(vendor *models.VendorResponse) *contract.VendorResponse {
|
||||||
|
return &contract.VendorResponse{
|
||||||
|
ID: vendor.ID,
|
||||||
|
OrganizationID: vendor.OrganizationID,
|
||||||
|
Name: vendor.Name,
|
||||||
|
Email: vendor.Email,
|
||||||
|
PhoneNumber: vendor.PhoneNumber,
|
||||||
|
Address: vendor.Address,
|
||||||
|
ContactPerson: vendor.ContactPerson,
|
||||||
|
TaxNumber: vendor.TaxNumber,
|
||||||
|
PaymentTerms: vendor.PaymentTerms,
|
||||||
|
Notes: vendor.Notes,
|
||||||
|
IsActive: vendor.IsActive,
|
||||||
|
CreatedAt: vendor.CreatedAt,
|
||||||
|
UpdatedAt: vendor.UpdatedAt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func VendorModelResponsesToResponses(vendors []*models.VendorResponse) []contract.VendorResponse {
|
||||||
|
if vendors == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]contract.VendorResponse, len(vendors))
|
||||||
|
for i, vendor := range vendors {
|
||||||
|
response := VendorModelResponseToResponse(vendor)
|
||||||
|
if response != nil {
|
||||||
|
responses[i] = *response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return responses
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListVendorsModelResponseToResponse(resp *models.ListVendorsResponse) *contract.ListVendorsResponse {
|
||||||
|
contractVendors := VendorModelResponsesToResponses(resp.Vendors)
|
||||||
|
return &contract.ListVendorsResponse{
|
||||||
|
Vendors: contractVendors,
|
||||||
|
TotalCount: resp.TotalCount,
|
||||||
|
Page: resp.Page,
|
||||||
|
Limit: resp.Limit,
|
||||||
|
TotalPages: resp.TotalPages,
|
||||||
|
}
|
||||||
|
}
|
||||||
113
internal/util/accounting_util.go
Normal file
113
internal/util/accounting_util.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
"apskel-pos-be/internal/processor"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DefaultChartOfAccount struct {
|
||||||
|
ChartOfAccounts []ChartOfAccountTemplate `json:"chart_of_accounts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartOfAccountTemplate struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
ChartOfAccountType string `json:"chart_of_account_type"`
|
||||||
|
ParentCode *string `json:"parent_code"`
|
||||||
|
IsSystem bool `json:"is_system"`
|
||||||
|
Accounts []AccountTemplate `json:"accounts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountTemplate struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Number string `json:"number"`
|
||||||
|
AccountType string `json:"account_type"`
|
||||||
|
OpeningBalance float64 `json:"opening_balance"`
|
||||||
|
Description *string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateDefaultChartOfAccounts(ctx context.Context,
|
||||||
|
chartOfAccountProcessor processor.ChartOfAccountProcessor,
|
||||||
|
accountProcessor processor.AccountProcessor,
|
||||||
|
organizationID uuid.UUID,
|
||||||
|
outletID *uuid.UUID) error {
|
||||||
|
|
||||||
|
// Load the default chart of accounts template
|
||||||
|
template, err := loadDefaultChartOfAccountsTemplate()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to load default chart of accounts template: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: In a real implementation, you would get chart of account types
|
||||||
|
// and validate that all required types exist
|
||||||
|
|
||||||
|
// Create chart of accounts and accounts
|
||||||
|
chartOfAccountMap := make(map[string]uuid.UUID)
|
||||||
|
|
||||||
|
for _, coaTemplate := range template.ChartOfAccounts {
|
||||||
|
// Note: In a real implementation, you would call the processor to create the chart of account
|
||||||
|
// For now, we'll just store the mapping
|
||||||
|
chartOfAccountMap[coaTemplate.Code] = uuid.New() // This would be the actual ID from creation
|
||||||
|
|
||||||
|
// Create accounts for this chart of account
|
||||||
|
for _, accountTemplate := range coaTemplate.Accounts {
|
||||||
|
accountReq := &entities.Account{
|
||||||
|
OrganizationID: organizationID,
|
||||||
|
OutletID: outletID,
|
||||||
|
ChartOfAccountID: chartOfAccountMap[coaTemplate.Code],
|
||||||
|
Name: accountTemplate.Name,
|
||||||
|
Number: accountTemplate.Number,
|
||||||
|
AccountType: entities.AccountType(accountTemplate.AccountType),
|
||||||
|
OpeningBalance: accountTemplate.OpeningBalance,
|
||||||
|
CurrentBalance: accountTemplate.OpeningBalance,
|
||||||
|
Description: accountTemplate.Description,
|
||||||
|
IsActive: true,
|
||||||
|
IsSystem: coaTemplate.IsSystem,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: In a real implementation, you would call the processor to create the account
|
||||||
|
_ = accountReq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadDefaultChartOfAccountsTemplate() (*DefaultChartOfAccount, error) {
|
||||||
|
// Get the path to the template file
|
||||||
|
templatePath := filepath.Join("internal", "constants", "default_chart_of_accounts.json")
|
||||||
|
|
||||||
|
// Read the template file
|
||||||
|
data, err := ioutil.ReadFile(templatePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read template file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the JSON
|
||||||
|
var template DefaultChartOfAccount
|
||||||
|
if err := json.Unmarshal(data, &template); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse template JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &template, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getChartOfAccountTypeMap(ctx context.Context, chartOfAccountProcessor processor.ChartOfAccountProcessor) (map[string]uuid.UUID, error) {
|
||||||
|
// This is a placeholder - in a real implementation, you would call the processor
|
||||||
|
// to get all chart of account types and create a map of code -> ID
|
||||||
|
return map[string]uuid.UUID{
|
||||||
|
"ASSET": uuid.New(),
|
||||||
|
"LIABILITY": uuid.New(),
|
||||||
|
"EQUITY": uuid.New(),
|
||||||
|
"REVENUE": uuid.New(),
|
||||||
|
"EXPENSE": uuid.New(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
98
internal/validator/account_validator.go
Normal file
98
internal/validator/account_validator.go
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountValidator interface {
|
||||||
|
ValidateCreateAccount(req *models.CreateAccountRequest) error
|
||||||
|
ValidateUpdateAccount(req *models.UpdateAccountRequest) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountValidatorImpl struct {
|
||||||
|
validator *validator.Validate
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAccountValidator() AccountValidator {
|
||||||
|
return &AccountValidatorImpl{
|
||||||
|
validator: validator.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *AccountValidatorImpl) ValidateCreateAccount(req *models.CreateAccountRequest) error {
|
||||||
|
if err := v.validator.Struct(req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional custom validations
|
||||||
|
if strings.TrimSpace(req.Name) == "" {
|
||||||
|
return fmt.Errorf("name cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(req.Number) == "" {
|
||||||
|
return fmt.Errorf("number cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate account type
|
||||||
|
if !isValidAccountType(req.AccountType) {
|
||||||
|
return fmt.Errorf("invalid account type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate number format (alphanumeric)
|
||||||
|
if !isValidAccountNumberFormat(req.Number) {
|
||||||
|
return fmt.Errorf("number must be alphanumeric")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *AccountValidatorImpl) ValidateUpdateAccount(req *models.UpdateAccountRequest) error {
|
||||||
|
if err := v.validator.Struct(req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional custom validations
|
||||||
|
if req.Name != nil && strings.TrimSpace(*req.Name) == "" {
|
||||||
|
return fmt.Errorf("name cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Number != nil && strings.TrimSpace(*req.Number) == "" {
|
||||||
|
return fmt.Errorf("number cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate account type if provided
|
||||||
|
if req.AccountType != nil && !isValidAccountType(*req.AccountType) {
|
||||||
|
return fmt.Errorf("invalid account type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate number format if provided
|
||||||
|
if req.Number != nil && !isValidAccountNumberFormat(*req.Number) {
|
||||||
|
return fmt.Errorf("number must be alphanumeric")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidAccountType(accountType string) bool {
|
||||||
|
validTypes := []string{"cash", "wallet", "bank", "credit", "debit", "asset", "liability", "equity", "revenue", "expense"}
|
||||||
|
for _, validType := range validTypes {
|
||||||
|
if accountType == validType {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidAccountNumberFormat(number string) bool {
|
||||||
|
// Check if number is alphanumeric
|
||||||
|
for _, char := range number {
|
||||||
|
if !((char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z') || (char >= '0' && char <= '9')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
78
internal/validator/chart_of_account_type_validator.go
Normal file
78
internal/validator/chart_of_account_type_validator.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChartOfAccountTypeValidator interface {
|
||||||
|
ValidateCreateChartOfAccountType(req *models.CreateChartOfAccountTypeRequest) error
|
||||||
|
ValidateUpdateChartOfAccountType(req *models.UpdateChartOfAccountTypeRequest) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartOfAccountTypeValidatorImpl struct {
|
||||||
|
validator *validator.Validate
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChartOfAccountTypeValidator() ChartOfAccountTypeValidator {
|
||||||
|
return &ChartOfAccountTypeValidatorImpl{
|
||||||
|
validator: validator.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ChartOfAccountTypeValidatorImpl) ValidateCreateChartOfAccountType(req *models.CreateChartOfAccountTypeRequest) error {
|
||||||
|
if err := v.validator.Struct(req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional custom validations
|
||||||
|
if strings.TrimSpace(req.Name) == "" {
|
||||||
|
return fmt.Errorf("name cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(req.Code) == "" {
|
||||||
|
return fmt.Errorf("code cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate code format (alphanumeric, uppercase)
|
||||||
|
if !isValidCodeFormat(req.Code) {
|
||||||
|
return fmt.Errorf("code must be alphanumeric and uppercase")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ChartOfAccountTypeValidatorImpl) ValidateUpdateChartOfAccountType(req *models.UpdateChartOfAccountTypeRequest) error {
|
||||||
|
if err := v.validator.Struct(req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional custom validations
|
||||||
|
if req.Name != nil && strings.TrimSpace(*req.Name) == "" {
|
||||||
|
return fmt.Errorf("name cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Code != nil && strings.TrimSpace(*req.Code) == "" {
|
||||||
|
return fmt.Errorf("code cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate code format if provided
|
||||||
|
if req.Code != nil && !isValidCodeFormat(*req.Code) {
|
||||||
|
return fmt.Errorf("code must be alphanumeric and uppercase")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidCodeFormat(code string) bool {
|
||||||
|
// Check if code is alphanumeric and uppercase
|
||||||
|
for _, char := range code {
|
||||||
|
if !((char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
78
internal/validator/chart_of_account_validator.go
Normal file
78
internal/validator/chart_of_account_validator.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/models"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChartOfAccountValidator interface {
|
||||||
|
ValidateCreateChartOfAccount(req *models.CreateChartOfAccountRequest) error
|
||||||
|
ValidateUpdateChartOfAccount(req *models.UpdateChartOfAccountRequest) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartOfAccountValidatorImpl struct {
|
||||||
|
validator *validator.Validate
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChartOfAccountValidator() ChartOfAccountValidator {
|
||||||
|
return &ChartOfAccountValidatorImpl{
|
||||||
|
validator: validator.New(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ChartOfAccountValidatorImpl) ValidateCreateChartOfAccount(req *models.CreateChartOfAccountRequest) error {
|
||||||
|
if err := v.validator.Struct(req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional custom validations
|
||||||
|
if strings.TrimSpace(req.Name) == "" {
|
||||||
|
return fmt.Errorf("name cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(req.Code) == "" {
|
||||||
|
return fmt.Errorf("code cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate code format (alphanumeric)
|
||||||
|
if !isValidAccountCodeFormat(req.Code) {
|
||||||
|
return fmt.Errorf("code must be alphanumeric")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *ChartOfAccountValidatorImpl) ValidateUpdateChartOfAccount(req *models.UpdateChartOfAccountRequest) error {
|
||||||
|
if err := v.validator.Struct(req); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional custom validations
|
||||||
|
if req.Name != nil && strings.TrimSpace(*req.Name) == "" {
|
||||||
|
return fmt.Errorf("name cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Code != nil && strings.TrimSpace(*req.Code) == "" {
|
||||||
|
return fmt.Errorf("code cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate code format if provided
|
||||||
|
if req.Code != nil && !isValidAccountCodeFormat(*req.Code) {
|
||||||
|
return fmt.Errorf("code must be alphanumeric")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidAccountCodeFormat(code string) bool {
|
||||||
|
// Check if code is alphanumeric
|
||||||
|
for _, char := range code {
|
||||||
|
if !((char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z') || (char >= '0' && char <= '9')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
122
internal/validator/ingredient_unit_converter_validator.go
Normal file
122
internal/validator/ingredient_unit_converter_validator.go
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apskel-pos-be/internal/constants"
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IngredientUnitConverterValidator interface {
|
||||||
|
ValidateCreateIngredientUnitConverterRequest(req *contract.CreateIngredientUnitConverterRequest) (error, string)
|
||||||
|
ValidateUpdateIngredientUnitConverterRequest(req *contract.UpdateIngredientUnitConverterRequest) (error, string)
|
||||||
|
ValidateListIngredientUnitConvertersRequest(req *contract.ListIngredientUnitConvertersRequest) (error, string)
|
||||||
|
ValidateConvertUnitRequest(req *contract.ConvertUnitRequest) (error, string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type IngredientUnitConverterValidatorImpl struct{}
|
||||||
|
|
||||||
|
func NewIngredientUnitConverterValidator() IngredientUnitConverterValidator {
|
||||||
|
return &IngredientUnitConverterValidatorImpl{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *IngredientUnitConverterValidatorImpl) ValidateCreateIngredientUnitConverterRequest(req *contract.CreateIngredientUnitConverterRequest) (error, string) {
|
||||||
|
if req == nil {
|
||||||
|
return errors.New("request cannot be nil"), constants.ValidationErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if req.IngredientID.String() == "" {
|
||||||
|
return errors.New("ingredient_id is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.FromUnitID.String() == "" {
|
||||||
|
return errors.New("from_unit_id is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ToUnitID.String() == "" {
|
||||||
|
return errors.New("to_unit_id is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ConversionFactor <= 0 {
|
||||||
|
return errors.New("conversion_factor must be greater than 0"), constants.ValidationErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that from and to units are different
|
||||||
|
if req.FromUnitID == req.ToUnitID {
|
||||||
|
return errors.New("from_unit_id and to_unit_id must be different"), constants.ValidationErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *IngredientUnitConverterValidatorImpl) ValidateUpdateIngredientUnitConverterRequest(req *contract.UpdateIngredientUnitConverterRequest) (error, string) {
|
||||||
|
if req == nil {
|
||||||
|
return errors.New("request cannot be nil"), constants.ValidationErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// At least one field must be provided for update
|
||||||
|
if req.FromUnitID == nil && req.ToUnitID == nil && req.ConversionFactor == nil && req.IsActive == nil {
|
||||||
|
return errors.New("at least one field must be provided for update"), constants.ValidationErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate conversion factor if provided
|
||||||
|
if req.ConversionFactor != nil && *req.ConversionFactor <= 0 {
|
||||||
|
return errors.New("conversion_factor must be greater than 0"), constants.ValidationErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that from and to units are different if both are provided
|
||||||
|
if req.FromUnitID != nil && req.ToUnitID != nil && *req.FromUnitID == *req.ToUnitID {
|
||||||
|
return errors.New("from_unit_id and to_unit_id must be different"), constants.ValidationErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *IngredientUnitConverterValidatorImpl) ValidateListIngredientUnitConvertersRequest(req *contract.ListIngredientUnitConvertersRequest) (error, string) {
|
||||||
|
if req == nil {
|
||||||
|
return errors.New("request cannot be nil"), constants.ValidationErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate pagination
|
||||||
|
if req.Page < 1 {
|
||||||
|
return errors.New("page must be at least 1"), constants.ValidationErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Limit < 1 || req.Limit > 100 {
|
||||||
|
return errors.New("limit must be between 1 and 100"), constants.ValidationErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate search string length
|
||||||
|
if req.Search != "" && len(strings.TrimSpace(req.Search)) < 2 {
|
||||||
|
return errors.New("search term must be at least 2 characters"), constants.ValidationErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *IngredientUnitConverterValidatorImpl) ValidateConvertUnitRequest(req *contract.ConvertUnitRequest) (error, string) {
|
||||||
|
if req == nil {
|
||||||
|
return errors.New("request cannot be nil"), constants.ValidationErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if req.IngredientID.String() == "" {
|
||||||
|
return errors.New("ingredient_id is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.FromUnitID.String() == "" {
|
||||||
|
return errors.New("from_unit_id is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ToUnitID.String() == "" {
|
||||||
|
return errors.New("to_unit_id is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Quantity <= 0 {
|
||||||
|
return errors.New("quantity must be greater than 0"), constants.ValidationErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
189
internal/validator/purchase_order_validator.go
Normal file
189
internal/validator/purchase_order_validator.go
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/constants"
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PurchaseOrderValidator interface {
|
||||||
|
ValidateCreatePurchaseOrderRequest(req *contract.CreatePurchaseOrderRequest) (error, string)
|
||||||
|
ValidateUpdatePurchaseOrderRequest(req *contract.UpdatePurchaseOrderRequest) (error, string)
|
||||||
|
ValidateListPurchaseOrdersRequest(req *contract.ListPurchaseOrdersRequest) (error, string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PurchaseOrderValidatorImpl struct{}
|
||||||
|
|
||||||
|
func NewPurchaseOrderValidator() *PurchaseOrderValidatorImpl {
|
||||||
|
return &PurchaseOrderValidatorImpl{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *PurchaseOrderValidatorImpl) ValidateCreatePurchaseOrderRequest(req *contract.CreatePurchaseOrderRequest) (error, string) {
|
||||||
|
if req == nil {
|
||||||
|
return errors.New("request body is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.VendorID.String() == "" {
|
||||||
|
return errors.New("vendor_id is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(req.PONumber) == "" {
|
||||||
|
return errors.New("po_number is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.PONumber) < 1 || len(req.PONumber) > 50 {
|
||||||
|
return errors.New("po_number must be between 1 and 50 characters"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.TransactionDate.IsZero() {
|
||||||
|
return errors.New("transaction_date is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.DueDate.IsZero() {
|
||||||
|
return errors.New("due_date is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.DueDate.Before(req.TransactionDate) {
|
||||||
|
return errors.New("due_date must be after transaction_date"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Reference != nil && len(*req.Reference) > 100 {
|
||||||
|
return errors.New("reference must be at most 100 characters"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Status != nil {
|
||||||
|
validStatuses := []string{"draft", "sent", "approved", "received", "cancelled"}
|
||||||
|
if !contains(validStatuses, *req.Status) {
|
||||||
|
return errors.New("status must be one of: draft, sent, approved, received, cancelled"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Items) == 0 {
|
||||||
|
return errors.New("at least one item is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate items
|
||||||
|
for i, item := range req.Items {
|
||||||
|
if err, code := v.validatePurchaseOrderItem(&item, i); err != nil {
|
||||||
|
return err, code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *PurchaseOrderValidatorImpl) ValidateUpdatePurchaseOrderRequest(req *contract.UpdatePurchaseOrderRequest) (error, string) {
|
||||||
|
if req == nil {
|
||||||
|
return errors.New("request body is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.PONumber != nil {
|
||||||
|
if strings.TrimSpace(*req.PONumber) == "" {
|
||||||
|
return errors.New("po_number cannot be empty"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
if len(*req.PONumber) < 1 || len(*req.PONumber) > 50 {
|
||||||
|
return errors.New("po_number must be between 1 and 50 characters"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.TransactionDate != nil && req.DueDate != nil {
|
||||||
|
if req.DueDate.Before(*req.TransactionDate) {
|
||||||
|
return errors.New("due_date must be after transaction_date"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Reference != nil && len(*req.Reference) > 100 {
|
||||||
|
return errors.New("reference must be at most 100 characters"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Status != nil {
|
||||||
|
validStatuses := []string{"draft", "sent", "approved", "received", "cancelled"}
|
||||||
|
if !contains(validStatuses, *req.Status) {
|
||||||
|
return errors.New("status must be one of: draft, sent, approved, received, cancelled"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate items if provided
|
||||||
|
if req.Items != nil {
|
||||||
|
for i, item := range req.Items {
|
||||||
|
if err, code := v.validateUpdatePurchaseOrderItem(&item, i); err != nil {
|
||||||
|
return err, code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *PurchaseOrderValidatorImpl) ValidateListPurchaseOrdersRequest(req *contract.ListPurchaseOrdersRequest) (error, string) {
|
||||||
|
if req == nil {
|
||||||
|
return errors.New("request body is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Page < 1 {
|
||||||
|
return errors.New("page must be at least 1"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Limit < 1 || req.Limit > 100 {
|
||||||
|
return errors.New("limit must be between 1 and 100"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Status != "" {
|
||||||
|
validStatuses := []string{"draft", "sent", "approved", "received", "cancelled"}
|
||||||
|
if !contains(validStatuses, req.Status) {
|
||||||
|
return errors.New("status must be one of: draft, sent, approved, received, cancelled"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.StartDate != nil && req.EndDate != nil {
|
||||||
|
if req.EndDate.Before(*req.StartDate) {
|
||||||
|
return errors.New("end_date must be after start_date"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *PurchaseOrderValidatorImpl) validatePurchaseOrderItem(item *contract.CreatePurchaseOrderItemRequest, index int) (error, string) {
|
||||||
|
if item.IngredientID.String() == "" {
|
||||||
|
return errors.New("items[" + string(rune(index)) + "].ingredient_id is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.Quantity <= 0 {
|
||||||
|
return errors.New("items[" + string(rune(index)) + "].quantity must be greater than 0"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.UnitID.String() == "" {
|
||||||
|
return errors.New("items[" + string(rune(index)) + "].unit_id is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.Amount < 0 {
|
||||||
|
return errors.New("items[" + string(rune(index)) + "].amount must be greater than or equal to 0"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *PurchaseOrderValidatorImpl) validateUpdatePurchaseOrderItem(item *contract.UpdatePurchaseOrderItemRequest, index int) (error, string) {
|
||||||
|
if item.Quantity != nil && *item.Quantity <= 0 {
|
||||||
|
return errors.New("items[" + string(rune(index)) + "].quantity must be greater than 0"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.Amount != nil && *item.Amount < 0 {
|
||||||
|
return errors.New("items[" + string(rune(index)) + "].amount must be greater than or equal to 0"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to check if a string is in a slice
|
||||||
|
func contains(slice []string, item string) bool {
|
||||||
|
for _, s := range slice {
|
||||||
|
if s == item {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
118
internal/validator/vendor_validator.go
Normal file
118
internal/validator/vendor_validator.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/constants"
|
||||||
|
"apskel-pos-be/internal/contract"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VendorValidator interface {
|
||||||
|
ValidateCreateVendorRequest(req *contract.CreateVendorRequest) (error, string)
|
||||||
|
ValidateUpdateVendorRequest(req *contract.UpdateVendorRequest) (error, string)
|
||||||
|
ValidateListVendorsRequest(req *contract.ListVendorsRequest) (error, string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type VendorValidatorImpl struct{}
|
||||||
|
|
||||||
|
func NewVendorValidator() *VendorValidatorImpl {
|
||||||
|
return &VendorValidatorImpl{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VendorValidatorImpl) ValidateCreateVendorRequest(req *contract.CreateVendorRequest) (error, string) {
|
||||||
|
if req == nil {
|
||||||
|
return errors.New("request body is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(req.Name) == "" {
|
||||||
|
return errors.New("name is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Name) < 1 || len(req.Name) > 255 {
|
||||||
|
return errors.New("name must be between 1 and 255 characters"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Email != nil && strings.TrimSpace(*req.Email) != "" {
|
||||||
|
if !isValidEmail(*req.Email) {
|
||||||
|
return errors.New("email format is invalid"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.PhoneNumber != nil && strings.TrimSpace(*req.PhoneNumber) != "" {
|
||||||
|
if !isValidPhone(*req.PhoneNumber) {
|
||||||
|
return errors.New("phone_number format is invalid"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ContactPerson != nil && len(*req.ContactPerson) > 255 {
|
||||||
|
return errors.New("contact_person must be at most 255 characters"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.TaxNumber != nil && len(*req.TaxNumber) > 50 {
|
||||||
|
return errors.New("tax_number must be at most 50 characters"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.PaymentTerms != nil && len(*req.PaymentTerms) > 100 {
|
||||||
|
return errors.New("payment_terms must be at most 100 characters"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VendorValidatorImpl) ValidateUpdateVendorRequest(req *contract.UpdateVendorRequest) (error, string) {
|
||||||
|
if req == nil {
|
||||||
|
return errors.New("request body is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Name != nil {
|
||||||
|
if strings.TrimSpace(*req.Name) == "" {
|
||||||
|
return errors.New("name cannot be empty"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
if len(*req.Name) < 1 || len(*req.Name) > 255 {
|
||||||
|
return errors.New("name must be between 1 and 255 characters"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Email != nil && strings.TrimSpace(*req.Email) != "" {
|
||||||
|
if !isValidEmail(*req.Email) {
|
||||||
|
return errors.New("email format is invalid"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.PhoneNumber != nil && strings.TrimSpace(*req.PhoneNumber) != "" {
|
||||||
|
if !isValidPhone(*req.PhoneNumber) {
|
||||||
|
return errors.New("phone_number format is invalid"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ContactPerson != nil && len(*req.ContactPerson) > 255 {
|
||||||
|
return errors.New("contact_person must be at most 255 characters"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.TaxNumber != nil && len(*req.TaxNumber) > 50 {
|
||||||
|
return errors.New("tax_number must be at most 50 characters"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.PaymentTerms != nil && len(*req.PaymentTerms) > 100 {
|
||||||
|
return errors.New("payment_terms must be at most 100 characters"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VendorValidatorImpl) ValidateListVendorsRequest(req *contract.ListVendorsRequest) (error, string) {
|
||||||
|
if req == nil {
|
||||||
|
return errors.New("request body is required"), constants.MissingFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Page < 1 {
|
||||||
|
return errors.New("page must be at least 1"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Limit < 1 || req.Limit > 100 {
|
||||||
|
return errors.New("limit must be between 1 and 100"), constants.MalformedFieldErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
2
migrations/000039_create_vendors_table.down.sql
Normal file
2
migrations/000039_create_vendors_table.down.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
-- Drop vendors table
|
||||||
|
DROP TABLE IF EXISTS vendors;
|
||||||
22
migrations/000039_create_vendors_table.up.sql
Normal file
22
migrations/000039_create_vendors_table.up.sql
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
-- Vendors table
|
||||||
|
CREATE TABLE vendors (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
email VARCHAR(255),
|
||||||
|
phone_number VARCHAR(20),
|
||||||
|
address TEXT,
|
||||||
|
contact_person VARCHAR(255),
|
||||||
|
tax_number VARCHAR(50),
|
||||||
|
payment_terms VARCHAR(100),
|
||||||
|
notes TEXT,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_vendors_organization_id ON vendors(organization_id);
|
||||||
|
CREATE INDEX idx_vendors_name ON vendors(name);
|
||||||
|
CREATE INDEX idx_vendors_email ON vendors(email);
|
||||||
|
CREATE INDEX idx_vendors_is_active ON vendors(is_active);
|
||||||
|
CREATE INDEX idx_vendors_created_at ON vendors(created_at);
|
||||||
4
migrations/000040_create_purchase_orders_table.down.sql
Normal file
4
migrations/000040_create_purchase_orders_table.down.sql
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
-- Drop purchase order tables
|
||||||
|
DROP TABLE IF EXISTS purchase_order_attachments;
|
||||||
|
DROP TABLE IF EXISTS purchase_order_items;
|
||||||
|
DROP TABLE IF EXISTS purchase_orders;
|
||||||
54
migrations/000040_create_purchase_orders_table.up.sql
Normal file
54
migrations/000040_create_purchase_orders_table.up.sql
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
-- Purchase Orders table
|
||||||
|
CREATE TABLE purchase_orders (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
||||||
|
vendor_id UUID NOT NULL REFERENCES vendors(id) ON DELETE CASCADE,
|
||||||
|
po_number VARCHAR(50) NOT NULL,
|
||||||
|
transaction_date DATE NOT NULL,
|
||||||
|
due_date DATE NOT NULL,
|
||||||
|
reference VARCHAR(100),
|
||||||
|
status VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'sent', 'approved', 'received', 'cancelled')),
|
||||||
|
message TEXT,
|
||||||
|
total_amount DECIMAL(15,2) NOT NULL DEFAULT 0,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Purchase Order Items table
|
||||||
|
CREATE TABLE purchase_order_items (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
purchase_order_id UUID NOT NULL REFERENCES purchase_orders(id) ON DELETE CASCADE,
|
||||||
|
ingredient_id UUID NOT NULL REFERENCES ingredients(id) ON DELETE CASCADE,
|
||||||
|
description TEXT,
|
||||||
|
quantity DECIMAL(10,3) NOT NULL,
|
||||||
|
unit_id UUID NOT NULL REFERENCES units(id) ON DELETE CASCADE,
|
||||||
|
amount DECIMAL(15,2) NOT NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Purchase Order Attachments table
|
||||||
|
CREATE TABLE purchase_order_attachments (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
purchase_order_id UUID NOT NULL REFERENCES purchase_orders(id) ON DELETE CASCADE,
|
||||||
|
file_id UUID NOT NULL REFERENCES files(id) ON DELETE CASCADE,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Indexes
|
||||||
|
CREATE INDEX idx_purchase_orders_organization_id ON purchase_orders(organization_id);
|
||||||
|
CREATE INDEX idx_purchase_orders_vendor_id ON purchase_orders(vendor_id);
|
||||||
|
CREATE INDEX idx_purchase_orders_po_number ON purchase_orders(po_number);
|
||||||
|
CREATE INDEX idx_purchase_orders_status ON purchase_orders(status);
|
||||||
|
CREATE INDEX idx_purchase_orders_transaction_date ON purchase_orders(transaction_date);
|
||||||
|
CREATE INDEX idx_purchase_orders_created_at ON purchase_orders(created_at);
|
||||||
|
|
||||||
|
CREATE INDEX idx_purchase_order_items_purchase_order_id ON purchase_order_items(purchase_order_id);
|
||||||
|
CREATE INDEX idx_purchase_order_items_ingredient_id ON purchase_order_items(ingredient_id);
|
||||||
|
CREATE INDEX idx_purchase_order_items_unit_id ON purchase_order_items(unit_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_purchase_order_attachments_purchase_order_id ON purchase_order_attachments(purchase_order_id);
|
||||||
|
CREATE INDEX idx_purchase_order_attachments_file_id ON purchase_order_attachments(file_id);
|
||||||
|
|
||||||
|
-- Unique constraint for PO number per organization
|
||||||
|
CREATE UNIQUE INDEX idx_purchase_orders_po_number_org ON purchase_orders(organization_id, po_number);
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
DROP TABLE IF EXISTS ingredient_unit_converters;
|
||||||
|
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
CREATE TABLE ingredient_unit_converters (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
||||||
|
ingredient_id UUID NOT NULL REFERENCES ingredients(id) ON DELETE CASCADE,
|
||||||
|
from_unit_id UUID NOT NULL REFERENCES units(id) ON DELETE CASCADE,
|
||||||
|
to_unit_id UUID NOT NULL REFERENCES units(id) ON DELETE CASCADE,
|
||||||
|
conversion_factor DECIMAL(15,6) NOT NULL CHECK (conversion_factor > 0),
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
created_by UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
||||||
|
updated_by UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Create indexes for better performance
|
||||||
|
CREATE INDEX idx_ingredient_unit_converters_organization_id ON ingredient_unit_converters(organization_id);
|
||||||
|
CREATE INDEX idx_ingredient_unit_converters_ingredient_id ON ingredient_unit_converters(ingredient_id);
|
||||||
|
CREATE INDEX idx_ingredient_unit_converters_from_unit_id ON ingredient_unit_converters(from_unit_id);
|
||||||
|
CREATE INDEX idx_ingredient_unit_converters_to_unit_id ON ingredient_unit_converters(to_unit_id);
|
||||||
|
CREATE INDEX idx_ingredient_unit_converters_active ON ingredient_unit_converters(is_active);
|
||||||
|
|
||||||
|
-- Create unique constraint to prevent duplicate converters for the same ingredient and unit pair
|
||||||
|
CREATE UNIQUE INDEX idx_ingredient_unit_converters_unique
|
||||||
|
ON ingredient_unit_converters(organization_id, ingredient_id, from_unit_id, to_unit_id)
|
||||||
|
WHERE is_active = true;
|
||||||
|
|
||||||
|
-- Add comments for documentation
|
||||||
|
COMMENT ON TABLE ingredient_unit_converters IS 'Stores unit conversion factors for ingredients within an organization';
|
||||||
|
COMMENT ON COLUMN ingredient_unit_converters.conversion_factor IS 'How many from_unit_id units equal one to_unit_id unit (e.g., 1000 for grams to kilograms)';
|
||||||
|
COMMENT ON COLUMN ingredient_unit_converters.is_active IS 'Whether this conversion is currently active and can be used';
|
||||||
|
COMMENT ON COLUMN ingredient_unit_converters.created_by IS 'User who created this conversion rule';
|
||||||
|
COMMENT ON COLUMN ingredient_unit_converters.updated_by IS 'User who last updated this conversion rule';
|
||||||
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
DROP TABLE IF EXISTS chart_of_account_types;
|
||||||
20
migrations/000042_create_chart_of_account_types_table.up.sql
Normal file
20
migrations/000042_create_chart_of_account_types_table.up.sql
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
CREATE TABLE chart_of_account_types (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
code VARCHAR(10) NOT NULL UNIQUE,
|
||||||
|
description TEXT,
|
||||||
|
is_active BOOLEAN DEFAULT true,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Insert default chart of account types
|
||||||
|
INSERT INTO chart_of_account_types (name, code, description) VALUES
|
||||||
|
('Assets', 'ASSET', 'Resources owned by the organization'),
|
||||||
|
('Liabilities', 'LIABILITY', 'Debts and obligations'),
|
||||||
|
('Equity', 'EQUITY', 'Owner''s interest in the organization'),
|
||||||
|
('Revenue', 'REVENUE', 'Income from business operations'),
|
||||||
|
('Expenses', 'EXPENSE', 'Costs incurred in business operations');
|
||||||
|
|
||||||
|
CREATE INDEX idx_chart_of_account_types_code ON chart_of_account_types(code);
|
||||||
|
CREATE INDEX idx_chart_of_account_types_is_active ON chart_of_account_types(is_active);
|
||||||
@ -0,0 +1 @@
|
|||||||
|
DROP TABLE IF EXISTS chart_of_accounts;
|
||||||
24
migrations/000043_create_chart_of_accounts_table.up.sql
Normal file
24
migrations/000043_create_chart_of_accounts_table.up.sql
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
CREATE TABLE chart_of_accounts (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
||||||
|
outlet_id UUID REFERENCES outlets(id) ON DELETE CASCADE,
|
||||||
|
chart_of_account_type_id UUID NOT NULL REFERENCES chart_of_account_types(id) ON DELETE RESTRICT,
|
||||||
|
parent_id UUID REFERENCES chart_of_accounts(id) ON DELETE SET NULL,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
code VARCHAR(20) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
is_active BOOLEAN DEFAULT true,
|
||||||
|
is_system BOOLEAN DEFAULT false,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
UNIQUE(organization_id, outlet_id, code),
|
||||||
|
UNIQUE(organization_id, code) -- For organization-level accounts
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_chart_of_accounts_organization_id ON chart_of_accounts(organization_id);
|
||||||
|
CREATE INDEX idx_chart_of_accounts_outlet_id ON chart_of_accounts(outlet_id);
|
||||||
|
CREATE INDEX idx_chart_of_accounts_type_id ON chart_of_accounts(chart_of_account_type_id);
|
||||||
|
CREATE INDEX idx_chart_of_accounts_parent_id ON chart_of_accounts(parent_id);
|
||||||
|
CREATE INDEX idx_chart_of_accounts_code ON chart_of_accounts(code);
|
||||||
|
CREATE INDEX idx_chart_of_accounts_is_active ON chart_of_accounts(is_active);
|
||||||
|
CREATE INDEX idx_chart_of_accounts_is_system ON chart_of_accounts(is_system);
|
||||||
1
migrations/000044_create_accounts_table.down.sql
Normal file
1
migrations/000044_create_accounts_table.down.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
DROP TABLE IF EXISTS accounts;
|
||||||
26
migrations/000044_create_accounts_table.up.sql
Normal file
26
migrations/000044_create_accounts_table.up.sql
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
CREATE TABLE accounts (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
||||||
|
outlet_id UUID REFERENCES outlets(id) ON DELETE CASCADE,
|
||||||
|
chart_of_account_id UUID NOT NULL REFERENCES chart_of_accounts(id) ON DELETE RESTRICT,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
number VARCHAR(50) NOT NULL,
|
||||||
|
account_type VARCHAR(20) NOT NULL CHECK (account_type IN ('cash', 'wallet', 'bank', 'credit', 'debit', 'asset', 'liability', 'equity', 'revenue', 'expense')),
|
||||||
|
opening_balance DECIMAL(15,2) DEFAULT 0.00,
|
||||||
|
current_balance DECIMAL(15,2) DEFAULT 0.00,
|
||||||
|
description TEXT,
|
||||||
|
is_active BOOLEAN DEFAULT true,
|
||||||
|
is_system BOOLEAN DEFAULT false,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
UNIQUE(organization_id, outlet_id, number),
|
||||||
|
UNIQUE(organization_id, number) -- For organization-level accounts
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_accounts_organization_id ON accounts(organization_id);
|
||||||
|
CREATE INDEX idx_accounts_outlet_id ON accounts(outlet_id);
|
||||||
|
CREATE INDEX idx_accounts_chart_of_account_id ON accounts(chart_of_account_id);
|
||||||
|
CREATE INDEX idx_accounts_number ON accounts(number);
|
||||||
|
CREATE INDEX idx_accounts_account_type ON accounts(account_type);
|
||||||
|
CREATE INDEX idx_accounts_is_active ON accounts(is_active);
|
||||||
|
CREATE INDEX idx_accounts_is_system ON accounts(is_system);
|
||||||
Loading…
x
Reference in New Issue
Block a user