196 lines
4.6 KiB
Go
196 lines
4.6 KiB
Go
package service
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"git.juancwu.dev/juancwu/budgit/internal/model"
|
|
"git.juancwu.dev/juancwu/budgit/internal/repository"
|
|
"github.com/google/uuid"
|
|
"github.com/shopspring/decimal"
|
|
)
|
|
|
|
type CreateLoanDTO struct {
|
|
SpaceID string
|
|
UserID string
|
|
Name string
|
|
Description string
|
|
OriginalAmount decimal.Decimal
|
|
InterestRateBps int
|
|
StartDate time.Time
|
|
EndDate *time.Time
|
|
}
|
|
|
|
type UpdateLoanDTO struct {
|
|
ID string
|
|
Name string
|
|
Description string
|
|
OriginalAmount decimal.Decimal
|
|
InterestRateBps int
|
|
StartDate time.Time
|
|
EndDate *time.Time
|
|
}
|
|
|
|
const LoansPerPage = 25
|
|
|
|
type LoanService struct {
|
|
loanRepo repository.LoanRepository
|
|
receiptRepo repository.ReceiptRepository
|
|
}
|
|
|
|
func NewLoanService(loanRepo repository.LoanRepository, receiptRepo repository.ReceiptRepository) *LoanService {
|
|
return &LoanService{
|
|
loanRepo: loanRepo,
|
|
receiptRepo: receiptRepo,
|
|
}
|
|
}
|
|
|
|
func (s *LoanService) CreateLoan(dto CreateLoanDTO) (*model.Loan, error) {
|
|
if dto.Name == "" {
|
|
return nil, fmt.Errorf("loan name cannot be empty")
|
|
}
|
|
if dto.OriginalAmount.LessThanOrEqual(decimal.Zero) {
|
|
return nil, fmt.Errorf("amount must be positive")
|
|
}
|
|
|
|
now := time.Now()
|
|
loan := &model.Loan{
|
|
ID: uuid.NewString(),
|
|
SpaceID: dto.SpaceID,
|
|
Name: dto.Name,
|
|
Description: dto.Description,
|
|
OriginalAmount: dto.OriginalAmount,
|
|
InterestRateBps: dto.InterestRateBps,
|
|
StartDate: dto.StartDate,
|
|
EndDate: dto.EndDate,
|
|
IsPaidOff: false,
|
|
CreatedBy: dto.UserID,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
|
|
if err := s.loanRepo.Create(loan); err != nil {
|
|
return nil, err
|
|
}
|
|
return loan, nil
|
|
}
|
|
|
|
func (s *LoanService) GetLoan(id string) (*model.Loan, error) {
|
|
return s.loanRepo.GetByID(id)
|
|
}
|
|
|
|
func (s *LoanService) GetLoanWithSummary(id string) (*model.LoanWithPaymentSummary, error) {
|
|
loan, err := s.loanRepo.GetByID(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
totalPaid, err := s.loanRepo.GetTotalPaidForLoan(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
receiptCount, err := s.loanRepo.GetReceiptCountForLoan(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &model.LoanWithPaymentSummary{
|
|
Loan: *loan,
|
|
TotalPaid: totalPaid,
|
|
Remaining: loan.OriginalAmount.Sub(totalPaid),
|
|
ReceiptCount: receiptCount,
|
|
}, nil
|
|
}
|
|
|
|
func (s *LoanService) GetLoansWithSummaryForSpace(spaceID string) ([]*model.LoanWithPaymentSummary, error) {
|
|
loans, err := s.loanRepo.GetBySpaceID(spaceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return s.attachSummaries(loans)
|
|
}
|
|
|
|
func (s *LoanService) GetLoansWithSummaryForSpacePaginated(spaceID string, page int) ([]*model.LoanWithPaymentSummary, int, error) {
|
|
total, err := s.loanRepo.CountBySpaceID(spaceID)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
totalPages := (total + LoansPerPage - 1) / LoansPerPage
|
|
if totalPages < 1 {
|
|
totalPages = 1
|
|
}
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if page > totalPages {
|
|
page = totalPages
|
|
}
|
|
|
|
offset := (page - 1) * LoansPerPage
|
|
loans, err := s.loanRepo.GetBySpaceIDPaginated(spaceID, LoansPerPage, offset)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
result, err := s.attachSummaries(loans)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return result, totalPages, nil
|
|
}
|
|
|
|
func (s *LoanService) attachSummaries(loans []*model.Loan) ([]*model.LoanWithPaymentSummary, error) {
|
|
result := make([]*model.LoanWithPaymentSummary, len(loans))
|
|
for i, loan := range loans {
|
|
totalPaid, err := s.loanRepo.GetTotalPaidForLoan(loan.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
receiptCount, err := s.loanRepo.GetReceiptCountForLoan(loan.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result[i] = &model.LoanWithPaymentSummary{
|
|
Loan: *loan,
|
|
TotalPaid: totalPaid,
|
|
Remaining: loan.OriginalAmount.Sub(totalPaid),
|
|
ReceiptCount: receiptCount,
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (s *LoanService) UpdateLoan(dto UpdateLoanDTO) (*model.Loan, error) {
|
|
if dto.Name == "" {
|
|
return nil, fmt.Errorf("loan name cannot be empty")
|
|
}
|
|
if dto.OriginalAmount.LessThanOrEqual(decimal.Zero) {
|
|
return nil, fmt.Errorf("amount must be positive")
|
|
}
|
|
|
|
existing, err := s.loanRepo.GetByID(dto.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
existing.Name = dto.Name
|
|
existing.Description = dto.Description
|
|
existing.OriginalAmount = dto.OriginalAmount
|
|
existing.InterestRateBps = dto.InterestRateBps
|
|
existing.StartDate = dto.StartDate
|
|
existing.EndDate = dto.EndDate
|
|
existing.UpdatedAt = time.Now()
|
|
|
|
if err := s.loanRepo.Update(existing); err != nil {
|
|
return nil, err
|
|
}
|
|
return existing, nil
|
|
}
|
|
|
|
func (s *LoanService) DeleteLoan(id string) error {
|
|
return s.loanRepo.Delete(id)
|
|
}
|