324 lines
11 KiB
Go
324 lines
11 KiB
Go
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
|
|
}
|
|
}
|