187 lines
6.0 KiB
Go
187 lines
6.0 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"accounting-app/internal/models"
|
|
"accounting-app/internal/repository"
|
|
)
|
|
|
|
// Common exchange rate service errors
|
|
var (
|
|
ErrInvalidRate = errors.New("exchange rate must be positive")
|
|
ErrInvalidEffectiveDate = errors.New("effective date cannot be in the future")
|
|
)
|
|
|
|
// ExchangeRateService handles business logic for exchange rates
|
|
type ExchangeRateService struct {
|
|
repo *repository.ExchangeRateRepository
|
|
}
|
|
|
|
// NewExchangeRateService creates a new ExchangeRateService instance
|
|
func NewExchangeRateService(repo *repository.ExchangeRateRepository) *ExchangeRateService {
|
|
return &ExchangeRateService{repo: repo}
|
|
}
|
|
|
|
// CreateExchangeRate creates a new exchange rate
|
|
func (s *ExchangeRateService) CreateExchangeRate(rate *models.ExchangeRate) error {
|
|
// Validate rate value
|
|
if rate.Rate <= 0 {
|
|
return ErrInvalidRate
|
|
}
|
|
|
|
// Validate effective date (should not be in the future)
|
|
if rate.EffectiveDate.After(time.Now()) {
|
|
return ErrInvalidEffectiveDate
|
|
}
|
|
|
|
// Validate currencies are different
|
|
if rate.FromCurrency == rate.ToCurrency {
|
|
return repository.ErrSameCurrency
|
|
}
|
|
|
|
return s.repo.Create(rate)
|
|
}
|
|
|
|
// GetExchangeRateByID retrieves an exchange rate by its ID
|
|
func (s *ExchangeRateService) GetExchangeRateByID(id uint) (*models.ExchangeRate, error) {
|
|
return s.repo.GetByID(id)
|
|
}
|
|
|
|
// GetAllExchangeRates retrieves all exchange rates
|
|
func (s *ExchangeRateService) GetAllExchangeRates() ([]models.ExchangeRate, error) {
|
|
return s.repo.GetAll()
|
|
}
|
|
|
|
// UpdateExchangeRate updates an existing exchange rate
|
|
func (s *ExchangeRateService) UpdateExchangeRate(rate *models.ExchangeRate) error {
|
|
// Validate rate value
|
|
if rate.Rate <= 0 {
|
|
return ErrInvalidRate
|
|
}
|
|
|
|
// Validate effective date (should not be in the future)
|
|
if rate.EffectiveDate.After(time.Now()) {
|
|
return ErrInvalidEffectiveDate
|
|
}
|
|
|
|
// Validate currencies are different
|
|
if rate.FromCurrency == rate.ToCurrency {
|
|
return repository.ErrSameCurrency
|
|
}
|
|
|
|
return s.repo.Update(rate)
|
|
}
|
|
|
|
// DeleteExchangeRate deletes an exchange rate by its ID
|
|
func (s *ExchangeRateService) DeleteExchangeRate(id uint) error {
|
|
return s.repo.Delete(id)
|
|
}
|
|
|
|
// GetExchangeRateByCurrencyPair retrieves the most recent exchange rate for a currency pair
|
|
func (s *ExchangeRateService) GetExchangeRateByCurrencyPair(fromCurrency, toCurrency models.Currency) (*models.ExchangeRate, error) {
|
|
return s.repo.GetByCurrencyPair(fromCurrency, toCurrency)
|
|
}
|
|
|
|
// GetExchangeRateByCurrencyPairAndDate retrieves the exchange rate for a currency pair on a specific date
|
|
func (s *ExchangeRateService) GetExchangeRateByCurrencyPairAndDate(fromCurrency, toCurrency models.Currency, date time.Time) (*models.ExchangeRate, error) {
|
|
return s.repo.GetByCurrencyPairAndDate(fromCurrency, toCurrency, date)
|
|
}
|
|
|
|
// GetLatestExchangeRates retrieves the most recent exchange rate for each currency pair
|
|
func (s *ExchangeRateService) GetLatestExchangeRates() ([]models.ExchangeRate, error) {
|
|
return s.repo.GetLatestRates()
|
|
}
|
|
|
|
// ConvertCurrency converts an amount from one currency to another using the most recent exchange rate
|
|
func (s *ExchangeRateService) ConvertCurrency(amount float64, fromCurrency, toCurrency models.Currency) (float64, error) {
|
|
// If currencies are the same, return the original amount
|
|
if fromCurrency == toCurrency {
|
|
return amount, nil
|
|
}
|
|
|
|
// Get the exchange rate
|
|
rate, err := s.repo.GetByCurrencyPair(fromCurrency, toCurrency)
|
|
if err != nil {
|
|
// If direct rate not found, try inverse rate
|
|
if errors.Is(err, repository.ErrExchangeRateNotFound) {
|
|
inverseRate, inverseErr := s.repo.GetByCurrencyPair(toCurrency, fromCurrency)
|
|
if inverseErr != nil {
|
|
return 0, fmt.Errorf("no exchange rate found for %s to %s: %w", fromCurrency, toCurrency, err)
|
|
}
|
|
// Use inverse rate: 1 / rate
|
|
if inverseRate.Rate == 0 {
|
|
return 0, errors.New("invalid inverse exchange rate (zero)")
|
|
}
|
|
return amount / inverseRate.Rate, nil
|
|
}
|
|
return 0, err
|
|
}
|
|
|
|
return amount * rate.Rate, nil
|
|
}
|
|
|
|
// ConvertCurrencyOnDate converts an amount from one currency to another using the exchange rate on a specific date
|
|
func (s *ExchangeRateService) ConvertCurrencyOnDate(amount float64, fromCurrency, toCurrency models.Currency, date time.Time) (float64, error) {
|
|
// If currencies are the same, return the original amount
|
|
if fromCurrency == toCurrency {
|
|
return amount, nil
|
|
}
|
|
|
|
// Get the exchange rate for the specific date
|
|
rate, err := s.repo.GetByCurrencyPairAndDate(fromCurrency, toCurrency, date)
|
|
if err != nil {
|
|
// If direct rate not found, try inverse rate
|
|
if errors.Is(err, repository.ErrExchangeRateNotFound) {
|
|
inverseRate, inverseErr := s.repo.GetByCurrencyPairAndDate(toCurrency, fromCurrency, date)
|
|
if inverseErr != nil {
|
|
return 0, fmt.Errorf("no exchange rate found for %s to %s on %s: %w", fromCurrency, toCurrency, date.Format("2006-01-02"), err)
|
|
}
|
|
// Use inverse rate: 1 / rate
|
|
if inverseRate.Rate == 0 {
|
|
return 0, errors.New("invalid inverse exchange rate (zero)")
|
|
}
|
|
return amount / inverseRate.Rate, nil
|
|
}
|
|
return 0, err
|
|
}
|
|
|
|
return amount * rate.Rate, nil
|
|
}
|
|
|
|
// GetExchangeRateByCurrency retrieves all exchange rates involving a specific currency
|
|
func (s *ExchangeRateService) GetExchangeRateByCurrency(currency models.Currency) ([]models.ExchangeRate, error) {
|
|
return s.repo.GetByCurrency(currency)
|
|
}
|
|
|
|
// SetExchangeRate creates or updates an exchange rate for a currency pair
|
|
// This is a convenience method for users to set rates without worrying about create vs update
|
|
func (s *ExchangeRateService) SetExchangeRate(fromCurrency, toCurrency models.Currency, rate float64, effectiveDate time.Time) error {
|
|
// Validate rate value
|
|
if rate <= 0 {
|
|
return ErrInvalidRate
|
|
}
|
|
|
|
// Validate effective date
|
|
if effectiveDate.After(time.Now()) {
|
|
return ErrInvalidEffectiveDate
|
|
}
|
|
|
|
// Validate currencies are different
|
|
if fromCurrency == toCurrency {
|
|
return repository.ErrSameCurrency
|
|
}
|
|
|
|
// Create new exchange rate entry
|
|
exchangeRate := &models.ExchangeRate{
|
|
FromCurrency: fromCurrency,
|
|
ToCurrency: toCurrency,
|
|
Rate: rate,
|
|
EffectiveDate: effectiveDate,
|
|
}
|
|
|
|
return s.repo.Create(exchangeRate)
|
|
}
|