All checks were successful
CI / ci (push) Successful in 12s
- model/finance.go: FinanceCategory, FinanceTransaction, Summary, Analytics - repository/finance.go: CRUD + summary/analytics queries - service/finance.go: business logic with auto-seed default categories - handler/finance.go: REST endpoints with owner-only check (user_id=1) - db.go: finance_categories + finance_transactions migrations - main.go: register /finance/* routes Endpoints: GET/POST/PUT/DELETE /finance/categories, /finance/transactions GET /finance/summary, /finance/analytics
168 lines
4.1 KiB
Go
168 lines
4.1 KiB
Go
package service
|
|
|
|
import (
|
|
"database/sql"
|
|
"time"
|
|
|
|
"github.com/daniil/homelab-api/internal/model"
|
|
"github.com/daniil/homelab-api/internal/repository"
|
|
)
|
|
|
|
type FinanceService struct {
|
|
repo *repository.FinanceRepository
|
|
}
|
|
|
|
func NewFinanceService(repo *repository.FinanceRepository) *FinanceService {
|
|
return &FinanceService{repo: repo}
|
|
}
|
|
|
|
// --- Categories ---
|
|
|
|
func (s *FinanceService) ListCategories(userID int64) ([]model.FinanceCategory, error) {
|
|
// Auto-seed if user has no categories
|
|
has, err := s.repo.HasCategories(userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !has {
|
|
if err := s.repo.SeedDefaultCategories(userID); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
cats, err := s.repo.ListCategories(userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cats == nil {
|
|
cats = []model.FinanceCategory{}
|
|
}
|
|
return cats, nil
|
|
}
|
|
|
|
func (s *FinanceService) CreateCategory(userID int64, req *model.CreateFinanceCategoryRequest) (*model.FinanceCategory, error) {
|
|
cat := &model.FinanceCategory{
|
|
UserID: userID,
|
|
Name: req.Name,
|
|
Emoji: req.Emoji,
|
|
Type: req.Type,
|
|
Color: defaultString(req.Color, "#6366f1"),
|
|
SortOrder: req.SortOrder,
|
|
}
|
|
if req.Budget != nil {
|
|
cat.Budget = sql.NullFloat64{Float64: *req.Budget, Valid: true}
|
|
}
|
|
if err := s.repo.CreateCategory(cat); err != nil {
|
|
return nil, err
|
|
}
|
|
cat.ProcessForJSON()
|
|
return cat, nil
|
|
}
|
|
|
|
func (s *FinanceService) UpdateCategory(id, userID int64, req *model.UpdateFinanceCategoryRequest) (*model.FinanceCategory, error) {
|
|
cat, err := s.repo.GetCategory(id, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if req.Name != nil {
|
|
cat.Name = *req.Name
|
|
}
|
|
if req.Emoji != nil {
|
|
cat.Emoji = *req.Emoji
|
|
}
|
|
if req.Type != nil {
|
|
cat.Type = *req.Type
|
|
}
|
|
if req.Budget != nil {
|
|
cat.Budget = sql.NullFloat64{Float64: *req.Budget, Valid: true}
|
|
}
|
|
if req.Color != nil {
|
|
cat.Color = *req.Color
|
|
}
|
|
if req.SortOrder != nil {
|
|
cat.SortOrder = *req.SortOrder
|
|
}
|
|
if err := s.repo.UpdateCategory(cat); err != nil {
|
|
return nil, err
|
|
}
|
|
cat.ProcessForJSON()
|
|
return cat, nil
|
|
}
|
|
|
|
func (s *FinanceService) DeleteCategory(id, userID int64) error {
|
|
return s.repo.DeleteCategory(id, userID)
|
|
}
|
|
|
|
// --- Transactions ---
|
|
|
|
func (s *FinanceService) ListTransactions(userID int64, month, year int, categoryID *int64, txType, search string, limit, offset int) ([]model.FinanceTransaction, error) {
|
|
txs, err := s.repo.ListTransactions(userID, month, year, categoryID, txType, search, limit, offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if txs == nil {
|
|
txs = []model.FinanceTransaction{}
|
|
}
|
|
return txs, nil
|
|
}
|
|
|
|
func (s *FinanceService) CreateTransaction(userID int64, req *model.CreateFinanceTransactionRequest) (*model.FinanceTransaction, error) {
|
|
date, err := time.Parse("2006-01-02", req.Date)
|
|
if err != nil {
|
|
date = time.Now()
|
|
}
|
|
tx := &model.FinanceTransaction{
|
|
UserID: userID,
|
|
CategoryID: req.CategoryID,
|
|
Type: req.Type,
|
|
Amount: req.Amount,
|
|
Description: req.Description,
|
|
Date: date,
|
|
}
|
|
if err := s.repo.CreateTransaction(tx); err != nil {
|
|
return nil, err
|
|
}
|
|
return tx, nil
|
|
}
|
|
|
|
func (s *FinanceService) UpdateTransaction(id, userID int64, req *model.UpdateFinanceTransactionRequest) (*model.FinanceTransaction, error) {
|
|
tx, err := s.repo.GetTransaction(id, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if req.CategoryID != nil {
|
|
tx.CategoryID = *req.CategoryID
|
|
}
|
|
if req.Type != nil {
|
|
tx.Type = *req.Type
|
|
}
|
|
if req.Amount != nil {
|
|
tx.Amount = *req.Amount
|
|
}
|
|
if req.Description != nil {
|
|
tx.Description = *req.Description
|
|
}
|
|
if req.Date != nil {
|
|
parsed, err := time.Parse("2006-01-02", *req.Date)
|
|
if err == nil {
|
|
tx.Date = parsed
|
|
}
|
|
}
|
|
if err := s.repo.UpdateTransaction(tx); err != nil {
|
|
return nil, err
|
|
}
|
|
return tx, nil
|
|
}
|
|
|
|
func (s *FinanceService) DeleteTransaction(id, userID int64) error {
|
|
return s.repo.DeleteTransaction(id, userID)
|
|
}
|
|
|
|
func (s *FinanceService) GetSummary(userID int64, month, year int) (*model.FinanceSummary, error) {
|
|
return s.repo.GetSummary(userID, month, year)
|
|
}
|
|
|
|
func (s *FinanceService) GetAnalytics(userID int64, months int) (*model.FinanceAnalytics, error) {
|
|
return s.repo.GetAnalytics(userID, months)
|
|
}
|