saveinmed/backend/internal/usecase/financial_service.go
Gabbriiel 90467db1ec refactor: substitui backend Medusa por backend Go e corrige testes do marketplace
- Remove backend Medusa.js (TypeScript) e substitui pelo backend Go (saveinmed-performance-core)
- Corrige testes auth.test.ts: alinha paths de API (v1/ sem barra inicial) e campo access_token
- Corrige GroupedProductCard.test.tsx: ajusta distância formatada (toFixed) e troca userEvent por fireEvent com fakeTimers
- Corrige AuthContext.test.tsx: usa vi.hoisted() para mocks e corrige parênteses no waitFor

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 04:56:37 -06:00

116 lines
3.4 KiB
Go

package usecase
import (
"context"
"errors"
"github.com/gofrs/uuid/v5"
"github.com/saveinmed/backend-go/internal/domain"
)
// UploadDocument registers a new KYC document.
func (s *Service) UploadDocument(ctx context.Context, companyID uuid.UUID, docType, url string) (*domain.CompanyDocument, error) {
doc := &domain.CompanyDocument{
ID: uuid.Must(uuid.NewV7()),
CompanyID: companyID,
Type: docType,
URL: url,
Status: "PENDING",
}
if err := s.repo.CreateDocument(ctx, doc); err != nil {
return nil, err
}
return doc, nil
}
// GetCompanyDocuments retrieves all documents for a company.
func (s *Service) GetCompanyDocuments(ctx context.Context, companyID uuid.UUID) ([]domain.CompanyDocument, error) {
return s.repo.ListDocuments(ctx, companyID)
}
// GetFormattedLedger retrieves the financial statement.
func (s *Service) GetFormattedLedger(ctx context.Context, companyID uuid.UUID, page, pageSize int) (*domain.PaginationResponse[domain.LedgerEntry], error) {
if page < 1 {
page = 1
}
if pageSize <= 0 {
pageSize = 20
}
offset := (page - 1) * pageSize
entries, total, err := s.repo.GetLedger(ctx, companyID, pageSize, offset)
if err != nil {
return nil, err
}
// Calculate total pages
totalPages := 0
if total > 0 {
totalPages = int((total + int64(pageSize) - 1) / int64(pageSize))
}
return &domain.PaginationResponse[domain.LedgerEntry]{
Items: entries,
TotalCount: total,
CurrentPage: page,
TotalPages: totalPages,
}, nil
}
// GetBalance returns the current net balance.
func (s *Service) GetBalance(ctx context.Context, companyID uuid.UUID) (int64, error) {
return s.repo.GetBalance(ctx, companyID)
}
// RequestWithdrawal initiates a payout if balance is sufficient.
func (s *Service) RequestWithdrawal(ctx context.Context, companyID uuid.UUID, amountCents int64, bankInfo string) (*domain.Withdrawal, error) {
if amountCents <= 0 {
return nil, errors.New("amount must be positive")
}
// 1. Check Balance
balance, err := s.repo.GetBalance(ctx, companyID)
if err != nil {
return nil, err
}
if balance < amountCents {
return nil, errors.New("insufficient balance")
}
// 2. Create Ledger Entry first (Debit)
entry := &domain.LedgerEntry{
ID: uuid.Must(uuid.NewV7()),
CompanyID: companyID,
AmountCents: -amountCents,
Type: "WITHDRAWAL",
Description: "Withdrawal Request",
}
if err := s.repo.RecordLedgerEntry(ctx, entry); err != nil {
return nil, err
}
// 3. Create Withdrawal Record
withdrawal := &domain.Withdrawal{
ID: uuid.Must(uuid.NewV7()),
CompanyID: companyID,
AmountCents: amountCents,
Status: "PENDING",
BankAccountInfo: bankInfo,
}
if err := s.repo.CreateWithdrawal(ctx, withdrawal); err != nil {
// ROLLBACK LEDGER? In a real system we need a TX across both repository calls.
// Since we handle tx inside repository usually, we should expose a method "RequestWithdrawalTx" in Repo.
// For MVP, we risk valid ledger invalid withdrawal state.
// Or we can just call it done.
// Alternatively, we can assume the balance check is "soft" and we re-verify later.
// Let's rely on Repo abstraction in future iterations.
return nil, err
}
return withdrawal, nil
}
// ListWithdrawals returns all withdrawal requests for a company.
func (s *Service) ListWithdrawals(ctx context.Context, companyID uuid.UUID) ([]domain.Withdrawal, error) {
return s.repo.ListWithdrawals(ctx, companyID)
}