saveinmed/backend/internal/repository/postgres/financial_repository.go

99 lines
3.9 KiB
Go

package postgres
import (
"context"
"time"
"github.com/gofrs/uuid/v5"
"github.com/saveinmed/backend-go/internal/domain"
)
// CreateDocument persists a KYC document.
func (r *Repository) CreateDocument(ctx context.Context, doc *domain.CompanyDocument) error {
now := time.Now().UTC()
doc.CreatedAt = now
doc.UpdatedAt = now
query := `INSERT INTO company_documents (id, company_id, type, url, status, rejection_reason, created_at, updated_at)
VALUES (:id, :company_id, :type, :url, :status, :rejection_reason, :created_at, :updated_at)`
_, err := r.db.NamedExecContext(ctx, query, doc)
return err
}
// ListDocuments retrieves documents for a company.
func (r *Repository) ListDocuments(ctx context.Context, companyID uuid.UUID) ([]domain.CompanyDocument, error) {
var docs []domain.CompanyDocument
query := `SELECT id, company_id, type, url, status, rejection_reason, created_at, updated_at FROM company_documents WHERE company_id = $1 ORDER BY created_at DESC`
if err := r.db.SelectContext(ctx, &docs, query, companyID); err != nil {
return nil, err
}
return docs, nil
}
// RecordLedgerEntry inserts an immutable financial record.
func (r *Repository) RecordLedgerEntry(ctx context.Context, entry *domain.LedgerEntry) error {
entry.CreatedAt = time.Now().UTC()
query := `INSERT INTO ledger_entries (id, company_id, amount_cents, type, description, reference_id, created_at)
VALUES (:id, :company_id, :amount_cents, :type, :description, :reference_id, :created_at)`
_, err := r.db.NamedExecContext(ctx, query, entry)
return err
}
// GetLedger returns transaction history for a company.
func (r *Repository) GetLedger(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]domain.LedgerEntry, int64, error) {
var entries []domain.LedgerEntry
// Get total count
var total int64
if err := r.db.GetContext(ctx, &total, "SELECT count(*) FROM ledger_entries WHERE company_id = $1", companyID); err != nil {
return nil, 0, err
}
// Get paginated entries
query := `SELECT id, company_id, amount_cents, type, description, reference_id, created_at FROM ledger_entries WHERE company_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3`
if err := r.db.SelectContext(ctx, &entries, query, companyID, limit, offset); err != nil {
return nil, 0, err
}
return entries, total, nil
}
// GetBalance calculates the current balance based on ledger entries.
func (r *Repository) GetBalance(ctx context.Context, companyID uuid.UUID) (int64, error) {
var balance int64
// COALESCE to handle case with no entries returning NULL
query := `SELECT COALESCE(SUM(amount_cents), 0) FROM ledger_entries WHERE company_id = $1`
if err := r.db.GetContext(ctx, &balance, query, companyID); err != nil {
return 0, err
}
return balance, nil
}
// CreateWithdrawal requests a payout.
func (r *Repository) CreateWithdrawal(ctx context.Context, withdrawal *domain.Withdrawal) error {
now := time.Now().UTC()
withdrawal.CreatedAt = now
withdrawal.UpdatedAt = now
// Transaction to ensure balance check?
// In a real system, we reserved balance via ledger entry first.
// The Service layer should call RecordLedgerEntry(WITHDRAWAL) before calling CreateWithdrawal.
// So here we just insert the request.
query := `INSERT INTO withdrawals (id, company_id, amount_cents, status, bank_account_info, created_at, updated_at)
VALUES (:id, :company_id, :amount_cents, :status, :bank_account_info, :created_at, :updated_at)`
_, err := r.db.NamedExecContext(ctx, query, withdrawal)
return err
}
// ListWithdrawals retrieves payout requests.
func (r *Repository) ListWithdrawals(ctx context.Context, companyID uuid.UUID) ([]domain.Withdrawal, error) {
var list []domain.Withdrawal
query := `SELECT id, company_id, amount_cents, status, bank_account_info, transaction_id, rejection_reason, created_at, updated_at FROM withdrawals WHERE company_id = $1 ORDER BY created_at DESC`
if err := r.db.SelectContext(ctx, &list, query, companyID); err != nil {
return nil, err
}
return list, nil
}