init
This commit is contained in:
323
internal/service/user_settings_service.go
Normal file
323
internal/service/user_settings_service.go
Normal file
@@ -0,0 +1,323 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"accounting-app/internal/models"
|
||||
)
|
||||
|
||||
// Service layer errors for user settings
|
||||
var (
|
||||
ErrInvalidIconLayout = errors.New("invalid icon layout, must be one of: four, five, six")
|
||||
ErrInvalidImageCompression = errors.New("invalid image compression, must be one of: low, medium, high")
|
||||
ErrDefaultAccountNotFound = errors.New("default account not found")
|
||||
ErrInvalidDefaultAccount = errors.New("invalid default account")
|
||||
)
|
||||
|
||||
// UserSettingsRepositoryInterface defines the interface for user settings repository operations
|
||||
type UserSettingsRepositoryInterface interface {
|
||||
GetOrCreate(userID uint) (*models.UserSettings, error)
|
||||
Update(settings *models.UserSettings) error
|
||||
GetWithDefaultAccounts(userID uint) (*models.UserSettings, error)
|
||||
}
|
||||
|
||||
// AccountRepositoryInterface defines the interface for account repository operations needed by settings service
|
||||
type AccountRepositoryInterface interface {
|
||||
GetByID(userID uint, id uint) (*models.Account, error)
|
||||
ExistsByID(userID uint, id uint) (bool, error)
|
||||
}
|
||||
|
||||
// UserSettingsInput represents the input data for updating user settings
|
||||
type UserSettingsInput struct {
|
||||
PreciseTimeEnabled *bool `json:"precise_time_enabled"`
|
||||
IconLayout *string `json:"icon_layout"`
|
||||
ImageCompression *string `json:"image_compression"`
|
||||
ShowReimbursementBtn *bool `json:"show_reimbursement_btn"`
|
||||
ShowRefundBtn *bool `json:"show_refund_btn"`
|
||||
CurrentLedgerID *uint `json:"current_ledger_id"`
|
||||
}
|
||||
|
||||
// DefaultAccountsInput represents the input data for updating default accounts
|
||||
// Feature: financial-core-upgrade
|
||||
// Validates: Requirements 5.1, 5.2
|
||||
type DefaultAccountsInput struct {
|
||||
DefaultExpenseAccountID *uint `json:"default_expense_account_id"`
|
||||
DefaultIncomeAccountID *uint `json:"default_income_account_id"`
|
||||
}
|
||||
|
||||
// DefaultAccountsResponse represents the response for default accounts
|
||||
// Feature: financial-core-upgrade
|
||||
// Validates: Requirements 5.1, 5.2
|
||||
type DefaultAccountsResponse struct {
|
||||
DefaultExpenseAccountID *uint `json:"default_expense_account_id,omitempty"`
|
||||
DefaultIncomeAccountID *uint `json:"default_income_account_id,omitempty"`
|
||||
DefaultExpenseAccount *models.Account `json:"default_expense_account,omitempty"`
|
||||
DefaultIncomeAccount *models.Account `json:"default_income_account,omitempty"`
|
||||
}
|
||||
|
||||
// UserSettingsServiceInterface defines the interface for user settings service operations
|
||||
type UserSettingsServiceInterface interface {
|
||||
GetSettings(userID uint) (*models.UserSettings, error)
|
||||
UpdateSettings(userID uint, input UserSettingsInput) (*models.UserSettings, error)
|
||||
GetDefaultAccounts(userID uint) (*DefaultAccountsResponse, error)
|
||||
UpdateDefaultAccounts(userID uint, input DefaultAccountsInput) (*DefaultAccountsResponse, error)
|
||||
ClearDefaultAccount(userID uint, accountID uint) error
|
||||
}
|
||||
|
||||
// UserSettingsService handles business logic for user settings
|
||||
type UserSettingsService struct {
|
||||
repo UserSettingsRepositoryInterface
|
||||
accountRepo AccountRepositoryInterface
|
||||
}
|
||||
|
||||
// NewUserSettingsService creates a new UserSettingsService instance
|
||||
func NewUserSettingsService(repo UserSettingsRepositoryInterface) *UserSettingsService {
|
||||
return &UserSettingsService{
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
// NewUserSettingsServiceWithAccountRepo creates a new UserSettingsService instance with account repository
|
||||
// Feature: financial-core-upgrade
|
||||
// Validates: Requirements 5.1, 5.2, 5.6, 5.7, 5.8
|
||||
func NewUserSettingsServiceWithAccountRepo(repo UserSettingsRepositoryInterface, accountRepo AccountRepositoryInterface) *UserSettingsService {
|
||||
return &UserSettingsService{
|
||||
repo: repo,
|
||||
accountRepo: accountRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// GetSettings retrieves user settings, creating default settings if not found
|
||||
// Feature: accounting-feature-upgrade
|
||||
// Validates: Requirements 5.4, 6.5, 8.25-8.27
|
||||
func (s *UserSettingsService) GetSettings(userID uint) (*models.UserSettings, error) {
|
||||
settings, err := s.repo.GetOrCreate(userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get settings: %w", err)
|
||||
}
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
// UpdateSettings updates user settings with validation
|
||||
// Feature: accounting-feature-upgrade
|
||||
// Validates: Requirements 5.4, 6.5, 8.25-8.27
|
||||
func (s *UserSettingsService) UpdateSettings(userID uint, input UserSettingsInput) (*models.UserSettings, error) {
|
||||
// Get existing settings
|
||||
settings, err := s.repo.GetOrCreate(userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get settings: %w", err)
|
||||
}
|
||||
|
||||
// Validate and update icon layout if provided
|
||||
if input.IconLayout != nil {
|
||||
layout := *input.IconLayout
|
||||
if layout != string(models.IconLayoutFour) &&
|
||||
layout != string(models.IconLayoutFive) &&
|
||||
layout != string(models.IconLayoutSix) {
|
||||
return nil, ErrInvalidIconLayout
|
||||
}
|
||||
settings.IconLayout = layout
|
||||
}
|
||||
|
||||
// Validate and update image compression if provided
|
||||
if input.ImageCompression != nil {
|
||||
compression := *input.ImageCompression
|
||||
if compression != string(models.ImageCompressionLow) &&
|
||||
compression != string(models.ImageCompressionMedium) &&
|
||||
compression != string(models.ImageCompressionHigh) {
|
||||
return nil, ErrInvalidImageCompression
|
||||
}
|
||||
settings.ImageCompression = compression
|
||||
}
|
||||
|
||||
// Update other fields if provided
|
||||
if input.PreciseTimeEnabled != nil {
|
||||
settings.PreciseTimeEnabled = *input.PreciseTimeEnabled
|
||||
}
|
||||
|
||||
if input.ShowReimbursementBtn != nil {
|
||||
settings.ShowReimbursementBtn = *input.ShowReimbursementBtn
|
||||
}
|
||||
|
||||
if input.ShowRefundBtn != nil {
|
||||
settings.ShowRefundBtn = *input.ShowRefundBtn
|
||||
}
|
||||
|
||||
if input.CurrentLedgerID != nil {
|
||||
settings.CurrentLedgerID = input.CurrentLedgerID
|
||||
}
|
||||
|
||||
// Save to database
|
||||
if err := s.repo.Update(settings); err != nil {
|
||||
return nil, fmt.Errorf("failed to update settings: %w", err)
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
// GetDefaultAccounts retrieves the current default account settings
|
||||
// Feature: financial-core-upgrade
|
||||
// Validates: Requirements 5.1, 5.2
|
||||
func (s *UserSettingsService) GetDefaultAccounts(userID uint) (*DefaultAccountsResponse, error) {
|
||||
settings, err := s.repo.GetOrCreate(userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get settings: %w", err)
|
||||
}
|
||||
|
||||
response := &DefaultAccountsResponse{
|
||||
DefaultExpenseAccountID: settings.DefaultExpenseAccountID,
|
||||
DefaultIncomeAccountID: settings.DefaultIncomeAccountID,
|
||||
}
|
||||
|
||||
// Load account details if account repo is available
|
||||
if s.accountRepo != nil {
|
||||
if settings.DefaultExpenseAccountID != nil {
|
||||
account, err := s.accountRepo.GetByID(userID, *settings.DefaultExpenseAccountID)
|
||||
if err == nil {
|
||||
response.DefaultExpenseAccount = account
|
||||
}
|
||||
}
|
||||
if settings.DefaultIncomeAccountID != nil {
|
||||
account, err := s.accountRepo.GetByID(userID, *settings.DefaultIncomeAccountID)
|
||||
if err == nil {
|
||||
response.DefaultIncomeAccount = account
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// UpdateDefaultAccounts updates the default account settings
|
||||
// Feature: financial-core-upgrade
|
||||
// Validates: Requirements 5.1, 5.2, 5.7, 5.8
|
||||
func (s *UserSettingsService) UpdateDefaultAccounts(userID uint, input DefaultAccountsInput) (*DefaultAccountsResponse, error) {
|
||||
// Get existing settings
|
||||
settings, err := s.repo.GetOrCreate(userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get settings: %w", err)
|
||||
}
|
||||
|
||||
// Validate and update default expense account if provided
|
||||
if input.DefaultExpenseAccountID != nil {
|
||||
if *input.DefaultExpenseAccountID == 0 {
|
||||
// Clear the default expense account
|
||||
settings.DefaultExpenseAccountID = nil
|
||||
} else {
|
||||
// Validate account exists
|
||||
if s.accountRepo != nil {
|
||||
exists, err := s.accountRepo.ExistsByID(userID, *input.DefaultExpenseAccountID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to validate expense account: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
return nil, ErrDefaultAccountNotFound
|
||||
}
|
||||
}
|
||||
settings.DefaultExpenseAccountID = input.DefaultExpenseAccountID
|
||||
}
|
||||
}
|
||||
|
||||
// Validate and update default income account if provided
|
||||
if input.DefaultIncomeAccountID != nil {
|
||||
if *input.DefaultIncomeAccountID == 0 {
|
||||
// Clear the default income account
|
||||
settings.DefaultIncomeAccountID = nil
|
||||
} else {
|
||||
// Validate account exists
|
||||
if s.accountRepo != nil {
|
||||
exists, err := s.accountRepo.ExistsByID(userID, *input.DefaultIncomeAccountID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to validate income account: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
return nil, ErrDefaultAccountNotFound
|
||||
}
|
||||
}
|
||||
settings.DefaultIncomeAccountID = input.DefaultIncomeAccountID
|
||||
}
|
||||
}
|
||||
|
||||
// Save to database
|
||||
if err := s.repo.Update(settings); err != nil {
|
||||
return nil, fmt.Errorf("failed to update settings: %w", err)
|
||||
}
|
||||
|
||||
// Build response with account details
|
||||
response := &DefaultAccountsResponse{
|
||||
DefaultExpenseAccountID: settings.DefaultExpenseAccountID,
|
||||
DefaultIncomeAccountID: settings.DefaultIncomeAccountID,
|
||||
}
|
||||
|
||||
// Load account details if account repo is available
|
||||
if s.accountRepo != nil {
|
||||
if settings.DefaultExpenseAccountID != nil {
|
||||
account, err := s.accountRepo.GetByID(userID, *settings.DefaultExpenseAccountID)
|
||||
if err == nil {
|
||||
response.DefaultExpenseAccount = account
|
||||
}
|
||||
}
|
||||
if settings.DefaultIncomeAccountID != nil {
|
||||
account, err := s.accountRepo.GetByID(userID, *settings.DefaultIncomeAccountID)
|
||||
if err == nil {
|
||||
response.DefaultIncomeAccount = account
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// ClearDefaultAccount clears the default account setting when an account is deleted
|
||||
// This should be called when an account is deleted to maintain data consistency
|
||||
// Feature: financial-core-upgrade
|
||||
// Validates: Requirements 5.6
|
||||
func (s *UserSettingsService) ClearDefaultAccount(userID uint, accountID uint) error {
|
||||
settings, err := s.repo.GetOrCreate(userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get settings: %w", err)
|
||||
}
|
||||
|
||||
updated := false
|
||||
|
||||
// Clear default expense account if it matches the deleted account
|
||||
if settings.DefaultExpenseAccountID != nil && *settings.DefaultExpenseAccountID == accountID {
|
||||
settings.DefaultExpenseAccountID = nil
|
||||
updated = true
|
||||
}
|
||||
|
||||
// Clear default income account if it matches the deleted account
|
||||
if settings.DefaultIncomeAccountID != nil && *settings.DefaultIncomeAccountID == accountID {
|
||||
settings.DefaultIncomeAccountID = nil
|
||||
updated = true
|
||||
}
|
||||
|
||||
// Only update if changes were made
|
||||
if updated {
|
||||
if err := s.repo.Update(settings); err != nil {
|
||||
return fmt.Errorf("failed to clear default account: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDefaultAccountForType returns the default account ID for a given transaction type
|
||||
// Feature: financial-core-upgrade
|
||||
// Validates: Requirements 5.3, 5.4, 5.5
|
||||
func (s *UserSettingsService) GetDefaultAccountForType(userID uint, transactionType string) (*uint, error) {
|
||||
settings, err := s.repo.GetOrCreate(userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get settings: %w", err)
|
||||
}
|
||||
|
||||
switch transactionType {
|
||||
case "expense":
|
||||
return settings.DefaultExpenseAccountID, nil
|
||||
case "income":
|
||||
return settings.DefaultIncomeAccountID, nil
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user