package services import ( "FamilyHub/src/domain" "FamilyHub/src/integrations/receiptProvider" "FamilyHub/src/repositories" "FamilyHub/src/utils" "context" "fmt" "log" "strings" ) type ReceiptService interface { AddReceipt(ctx context.Context, req domain.AddReceiptRequest) (*domain.Receipt, error) } type receiptService struct { provider receiptProvider.ReceiptProvider repo repositories.ReceiptsRepository transactionRepo repositories.TransactionRepository } func NewReceiptService( provider receiptProvider.ReceiptProvider, repo repositories.ReceiptsRepository, transactionRepo repositories.TransactionRepository, ) ReceiptService { return &receiptService{ provider: provider, repo: repo, transactionRepo: transactionRepo, } } func (s *receiptService) AddReceipt( ctx context.Context, req domain.AddReceiptRequest, ) (*domain.Receipt, error) { log.Printf("receipt add request: payload=%s", utils.ToLogJSON(req)) receipt, err := s.provider.GetReceipt(ctx, req.Date, req.Number) if err != nil { log.Printf("receipt add failed: err=%v payload=%s", err, utils.ToLogJSON(req)) return nil, err } receiptID, err := s.repo.Create(ctx, receipt) if err != nil { log.Printf("receipt persist failed: err=%v receipt=%s", err, utils.ToLogJSON(receipt)) return nil, err } receipt.ID = int(receiptID) if !s.shouldCreateTransaction(req) { log.Printf("receipt add response: payload=%s", utils.ToLogJSON(receipt)) return receipt, nil } transaction, err := s.createTransactionForReceipt(ctx, receipt, req, receiptID) if err != nil { if rollbackErr := s.repo.Delete(ctx, receiptID); rollbackErr != nil { return nil, fmt.Errorf("create receipt transaction: %w (rollback receipt %d: %v)", err, receiptID, rollbackErr) } return nil, err } receipt.TransactionID = &transaction.ID log.Printf("receipt add response: payload=%s", utils.ToLogJSON(receipt)) return receipt, nil } func (s *receiptService) shouldCreateTransaction(req domain.AddReceiptRequest) bool { return s.transactionRepo != nil && req.FamilyID != nil && req.CreatedBy != nil } func (s *receiptService) createTransactionForReceipt( ctx context.Context, receipt *domain.Receipt, req domain.AddReceiptRequest, receiptID int64, ) (*domain.Transaction, error) { transactionType := "expense" if req.Type != nil && strings.TrimSpace(*req.Type) != "" { transactionType = strings.TrimSpace(*req.Type) } category := "receipt" if req.Category != nil && strings.TrimSpace(*req.Category) != "" { category = strings.TrimSpace(*req.Category) } description := buildReceiptTransactionDescription(receipt, req.Description) transaction := &domain.Transaction{ FamilyID: *req.FamilyID, Description: description, Type: transactionType, DateTime: receipt.IssuedAt, Category: category, Amount: receipt.TotalAmount, CreatedBy: *req.CreatedBy, ReceiptID: &receiptID, } log.Printf("%+v\n", transaction) log.Printf("receipt transaction create request: payload=%s", utils.ToLogJSON(transaction)) if err := s.transactionRepo.Create(ctx, transaction); err != nil { log.Printf("receipt transaction create failed: err=%v payload=%s", err, utils.ToLogJSON(transaction)) return nil, err } log.Printf("receipt transaction create response: payload=%s", utils.ToLogJSON(transaction)) return transaction, nil } func buildReceiptTransactionDescription(receipt *domain.Receipt, explicit *string) *string { if explicit != nil && strings.TrimSpace(*explicit) != "" { value := strings.TrimSpace(*explicit) return &value } if name := strings.TrimSpace(receipt.NameTO); name != "" { return &name } if number := strings.TrimSpace(receipt.ReceiptNumber); number != "" { value := fmt.Sprintf("Receipt %s", number) return &value } return nil }