Added transaction feature, fixed some mistakes

This commit is contained in:
2026-04-11 11:12:54 +03:00
parent 6872563c62
commit 545b05d5a0
37 changed files with 2509 additions and 115 deletions
+177
View File
@@ -0,0 +1,177 @@
package services
import (
"FamilyHub/src/domain"
"FamilyHub/src/repositories"
"context"
"database/sql"
"errors"
"strings"
)
type TransactionService interface {
Create(ctx context.Context, req domain.CreateTransactionRequest) (*domain.Transaction, error)
GetByID(ctx context.Context, id int64) (*domain.Transaction, error)
List(ctx context.Context, filter domain.TransactionListFilter) ([]*domain.Transaction, error)
Update(ctx context.Context, id int64, req domain.UpdateTransactionRequest) (*domain.Transaction, error)
Delete(ctx context.Context, id int64) error
}
type transactionService struct {
repo repositories.TransactionRepository
}
func NewTransactionService(repo repositories.TransactionRepository) TransactionService {
return &transactionService{repo: repo}
}
var (
ErrTransactionNotFound = errors.New("transaction not found")
ErrTransactionPatch = errors.New("empty update payload")
ErrReceiptLinkConflict = errors.New("receipt_id and detach_receipt cannot be used together")
ErrInvalidTransaction = errors.New("type and category are required")
ErrReceiptNotFound = errors.New("receipt not found")
)
func (s *transactionService) Create(ctx context.Context, req domain.CreateTransactionRequest) (*domain.Transaction, error) {
if strings.TrimSpace(req.Type) == "" || strings.TrimSpace(req.Category) == "" {
return nil, ErrInvalidTransaction
}
transaction := &domain.Transaction{
FamilyID: req.FamilyID,
Description: req.Description,
Type: req.Type,
DateTime: req.DateTime,
Category: req.Category,
Amount: req.Amount,
CreatedBy: req.CreatedBy,
ReceiptID: req.ReceiptID,
}
if err := s.repo.Create(ctx, transaction); err != nil {
if errors.Is(err, repositories.ErrReceiptNotFound) {
return nil, ErrReceiptNotFound
}
return nil, err
}
return transaction, nil
}
func (s *transactionService) GetByID(ctx context.Context, id int64) (*domain.Transaction, error) {
transaction, err := s.repo.GetByID(ctx, id)
if err != nil {
return nil, err
}
if transaction == nil {
return nil, ErrTransactionNotFound
}
return transaction, nil
}
func (s *transactionService) List(ctx context.Context, filter domain.TransactionListFilter) ([]*domain.Transaction, error) {
if filter.Limit <= 0 {
filter.Limit = 50
}
if filter.Limit > 200 {
filter.Limit = 200
}
if filter.Offset < 0 {
filter.Offset = 0
}
return s.repo.List(ctx, filter)
}
func (s *transactionService) Update(ctx context.Context, id int64, req domain.UpdateTransactionRequest) (*domain.Transaction, error) {
if req.ReceiptID != nil && req.DetachReceipt {
return nil, ErrReceiptLinkConflict
}
existing, err := s.repo.GetByID(ctx, id)
if err != nil {
return nil, err
}
if existing == nil {
return nil, ErrTransactionNotFound
}
if req.Description == nil &&
req.Type == nil &&
req.DateTime == nil &&
req.Category == nil &&
req.Amount == nil &&
req.ReceiptID == nil &&
!req.DetachReceipt {
return nil, ErrTransactionPatch
}
updated := &domain.Transaction{
ID: existing.ID,
FamilyID: existing.FamilyID,
Description: existing.Description,
Type: existing.Type,
DateTime: existing.DateTime,
Category: existing.Category,
Amount: existing.Amount,
CreatedAt: existing.CreatedAt,
CreatedBy: existing.CreatedBy,
ReceiptID: existing.ReceiptID,
}
if req.Description != nil {
updated.Description = req.Description
}
if req.Type != nil {
updated.Type = *req.Type
}
if req.DateTime != nil {
updated.DateTime = *req.DateTime
}
if req.Category != nil {
updated.Category = *req.Category
}
if req.Amount != nil {
updated.Amount = *req.Amount
}
syncReceipt := false
if req.DetachReceipt {
updated.ReceiptID = nil
syncReceipt = true
}
if req.ReceiptID != nil {
updated.ReceiptID = req.ReceiptID
syncReceipt = true
}
if strings.TrimSpace(updated.Type) == "" || strings.TrimSpace(updated.Category) == "" {
return nil, ErrInvalidTransaction
}
if err := s.repo.Update(ctx, updated, syncReceipt); err != nil {
if errors.Is(err, repositories.ErrReceiptNotFound) {
return nil, ErrReceiptNotFound
}
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrTransactionNotFound
}
return nil, err
}
return s.repo.GetByID(ctx, id)
}
func (s *transactionService) Delete(ctx context.Context, id int64) error {
transaction, err := s.repo.GetByID(ctx, id)
if err != nil {
return err
}
if transaction == nil {
return ErrTransactionNotFound
}
return s.repo.Delete(ctx, id)
}