260 lines
7.5 KiB
Go
260 lines
7.5 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"accounting-app/internal/models"
|
|
"accounting-app/internal/repository"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// Service layer errors for ledgers
|
|
var (
|
|
ErrLedgerNotFound = errors.New("ledger not found")
|
|
ErrLedgerLimitExceeded = errors.New("maximum number of ledgers exceeded")
|
|
ErrCannotDeleteLastLedger = errors.New("cannot delete the last ledger")
|
|
ErrInvalidTheme = errors.New("invalid theme, must be one of: pink, beige, brown")
|
|
)
|
|
|
|
// LedgerInput represents the input data for creating or updating a ledger
|
|
type LedgerInput struct {
|
|
Name string `json:"name" binding:"required,max=100"`
|
|
Theme string `json:"theme" binding:"omitempty,oneof=pink beige brown"`
|
|
CoverImage string `json:"cover_image"`
|
|
IsDefault bool `json:"is_default"`
|
|
SortOrder int `json:"sort_order"`
|
|
}
|
|
|
|
// LedgerServiceInterface defines the interface for ledger service operations
|
|
type LedgerServiceInterface interface {
|
|
CreateLedger(userID uint, input LedgerInput) (*models.Ledger, error)
|
|
GetLedger(userID uint, id uint) (*models.Ledger, error)
|
|
GetAllLedgers(userID uint) ([]models.Ledger, error)
|
|
UpdateLedger(userID uint, id uint, input LedgerInput) (*models.Ledger, error)
|
|
DeleteLedger(userID uint, id uint) error
|
|
GetDefaultLedger(userID uint) (*models.Ledger, error)
|
|
GetDeletedLedgers(userID uint) ([]models.Ledger, error)
|
|
RestoreLedger(userID uint, id uint) error
|
|
}
|
|
|
|
// LedgerService handles business logic for ledgers
|
|
type LedgerService struct {
|
|
repo *repository.LedgerRepository
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewLedgerService creates a new LedgerService instance
|
|
func NewLedgerService(repo *repository.LedgerRepository, db *gorm.DB) *LedgerService {
|
|
return &LedgerService{
|
|
repo: repo,
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
// CreateLedger creates a new ledger with business logic validation
|
|
// Feature: accounting-feature-upgrade
|
|
// Validates: Requirements 3.1-3.6, 3.12
|
|
func (s *LedgerService) CreateLedger(userID uint, input LedgerInput) (*models.Ledger, error) {
|
|
// Check if the ledger limit has been reached
|
|
count, err := s.repo.Count(userID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to count ledgers: %w", err)
|
|
}
|
|
if count >= models.MaxLedgersPerUser {
|
|
return nil, ErrLedgerLimitExceeded
|
|
}
|
|
|
|
// Validate theme if provided
|
|
if input.Theme != "" && input.Theme != "pink" && input.Theme != "beige" && input.Theme != "brown" {
|
|
return nil, ErrInvalidTheme
|
|
}
|
|
|
|
// Create the ledger model
|
|
ledger := &models.Ledger{
|
|
UserID: userID,
|
|
Name: input.Name,
|
|
Theme: input.Theme,
|
|
CoverImage: input.CoverImage,
|
|
IsDefault: input.IsDefault,
|
|
SortOrder: input.SortOrder,
|
|
}
|
|
|
|
// If this is set as default, we need to unset other defaults
|
|
if input.IsDefault {
|
|
if err := s.repo.SetDefault(userID, 0); err != nil {
|
|
return nil, fmt.Errorf("failed to unset default ledgers: %w", err)
|
|
}
|
|
}
|
|
|
|
// Save to database
|
|
if err := s.repo.Create(ledger); err != nil {
|
|
return nil, fmt.Errorf("failed to create ledger: %w", err)
|
|
}
|
|
|
|
// If this is the first ledger, set it as default
|
|
if count == 0 {
|
|
ledger.IsDefault = true
|
|
if err := s.repo.Update(userID, ledger); err != nil {
|
|
return nil, fmt.Errorf("failed to set first ledger as default: %w", err)
|
|
}
|
|
}
|
|
|
|
return ledger, nil
|
|
}
|
|
|
|
// GetLedger retrieves a ledger by ID
|
|
func (s *LedgerService) GetLedger(userID uint, id uint) (*models.Ledger, error) {
|
|
ledger, err := s.repo.GetByID(userID, id)
|
|
if err != nil {
|
|
if errors.Is(err, repository.ErrLedgerNotFound) {
|
|
return nil, ErrLedgerNotFound
|
|
}
|
|
return nil, fmt.Errorf("failed to get ledger: %w", err)
|
|
}
|
|
return ledger, nil
|
|
}
|
|
|
|
// GetAllLedgers retrieves all ledgers
|
|
func (s *LedgerService) GetAllLedgers(userID uint) ([]models.Ledger, error) {
|
|
ledgers, err := s.repo.GetAll(userID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get ledgers: %w", err)
|
|
}
|
|
return ledgers, nil
|
|
}
|
|
|
|
// UpdateLedger updates an existing ledger
|
|
// Feature: accounting-feature-upgrade
|
|
// Validates: Requirements 3.6
|
|
func (s *LedgerService) UpdateLedger(userID uint, id uint, input LedgerInput) (*models.Ledger, error) {
|
|
// Get existing ledger
|
|
ledger, err := s.repo.GetByID(userID, id)
|
|
if err != nil {
|
|
if errors.Is(err, repository.ErrLedgerNotFound) {
|
|
return nil, ErrLedgerNotFound
|
|
}
|
|
return nil, fmt.Errorf("failed to get ledger: %w", err)
|
|
}
|
|
|
|
// Validate theme if provided
|
|
if input.Theme != "" && input.Theme != "pink" && input.Theme != "beige" && input.Theme != "brown" {
|
|
return nil, ErrInvalidTheme
|
|
}
|
|
|
|
// Update fields
|
|
ledger.Name = input.Name
|
|
if input.Theme != "" {
|
|
ledger.Theme = input.Theme
|
|
}
|
|
ledger.CoverImage = input.CoverImage
|
|
ledger.SortOrder = input.SortOrder
|
|
|
|
// Handle default status change
|
|
if input.IsDefault && !ledger.IsDefault {
|
|
// Setting this ledger as default
|
|
if err := s.repo.SetDefault(userID, id); err != nil {
|
|
return nil, fmt.Errorf("failed to set default ledger: %w", err)
|
|
}
|
|
ledger.IsDefault = true
|
|
}
|
|
|
|
// Save to database
|
|
if err := s.repo.Update(userID, ledger); err != nil {
|
|
return nil, fmt.Errorf("failed to update ledger: %w", err)
|
|
}
|
|
|
|
return ledger, nil
|
|
}
|
|
|
|
// DeleteLedger soft-deletes a ledger by ID
|
|
// Feature: accounting-feature-upgrade
|
|
// Validates: Requirements 3.7, 3.8, 3.15
|
|
func (s *LedgerService) DeleteLedger(userID uint, id uint) error {
|
|
// Check if this is the last ledger
|
|
count, err := s.repo.Count(userID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to count ledgers: %w", err)
|
|
}
|
|
if count <= 1 {
|
|
return ErrCannotDeleteLastLedger
|
|
}
|
|
|
|
// Get the ledger to check if it's the default
|
|
ledger, err := s.repo.GetByID(userID, id)
|
|
if err != nil {
|
|
if errors.Is(err, repository.ErrLedgerNotFound) {
|
|
return ErrLedgerNotFound
|
|
}
|
|
return fmt.Errorf("failed to get ledger: %w", err)
|
|
}
|
|
|
|
// Delete the ledger
|
|
if err := s.repo.Delete(userID, id); err != nil {
|
|
return fmt.Errorf("failed to delete ledger: %w", err)
|
|
}
|
|
|
|
// If this was the default ledger, set the first remaining ledger as default
|
|
if ledger.IsDefault {
|
|
ledgers, err := s.repo.GetAll(userID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get ledgers after deletion: %w", err)
|
|
}
|
|
if len(ledgers) > 0 {
|
|
if err := s.repo.SetDefault(userID, ledgers[0].ID); err != nil {
|
|
return fmt.Errorf("failed to set new default ledger: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetDefaultLedger retrieves the default ledger
|
|
func (s *LedgerService) GetDefaultLedger(userID uint) (*models.Ledger, error) {
|
|
ledger, err := s.repo.GetDefault(userID)
|
|
if err != nil {
|
|
if errors.Is(err, repository.ErrLedgerNotFound) {
|
|
return nil, ErrLedgerNotFound
|
|
}
|
|
return nil, fmt.Errorf("failed to get default ledger: %w", err)
|
|
}
|
|
return ledger, nil
|
|
}
|
|
|
|
// GetDeletedLedgers retrieves all soft-deleted ledgers
|
|
// Feature: accounting-feature-upgrade
|
|
// Validates: Requirements 3.9
|
|
func (s *LedgerService) GetDeletedLedgers(userID uint) ([]models.Ledger, error) {
|
|
ledgers, err := s.repo.GetDeleted(userID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get deleted ledgers: %w", err)
|
|
}
|
|
return ledgers, nil
|
|
}
|
|
|
|
// RestoreLedger restores a soft-deleted ledger by ID
|
|
// Feature: accounting-feature-upgrade
|
|
// Validates: Requirements 3.9
|
|
func (s *LedgerService) RestoreLedger(userID uint, id uint) error {
|
|
// Check if restoring would exceed the ledger limit
|
|
count, err := s.repo.Count(userID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to count ledgers: %w", err)
|
|
}
|
|
if count >= models.MaxLedgersPerUser {
|
|
return ErrLedgerLimitExceeded
|
|
}
|
|
|
|
// Restore the ledger
|
|
if err := s.repo.Restore(userID, id); err != nil {
|
|
if errors.Is(err, repository.ErrLedgerNotFound) {
|
|
return ErrLedgerNotFound
|
|
}
|
|
return fmt.Errorf("failed to restore ledger: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|