package repository import ( "context" "github.com/jackc/pgx/v5/pgxpool" "financeiro-carvalho/internal/middleware" "financeiro-carvalho/internal/model" ) // AccountRepoWithYield extends the basic account repo with CDI-related operations. type AccountRepoWithYield interface { List(ctx context.Context) ([]model.Account, error) GetByID(ctx context.Context, id int) (*model.Account, error) Create(ctx context.Context, in model.AccountInput) (*model.Account, error) Update(ctx context.Context, id int, in model.AccountInput) (*model.Account, error) Delete(ctx context.Context, id int) error TotalPatrimony(ctx context.Context) (float64, error) UpdateLastYieldDate(ctx context.Context, id int, date string) error } type AccountRepository struct { pool *pgxpool.Pool } func NewAccountRepository(pool *pgxpool.Pool) *AccountRepository { return &AccountRepository{pool: pool} } func (r *AccountRepository) List(ctx context.Context) ([]model.Account, error) { pid := middleware.ProfileIDFromCtx(ctx) rows, err := r.pool.Query(ctx, ` SELECT a.id, a.name, a.type, a.initial_balance, a.yield_type, a.cdi_percentage, a.last_yield_date::text, a.closing_day, a.due_day, a.created_at::text, a.updated_at::text, a.initial_balance + COALESCE(SUM(CASE WHEN t.type = 'income' THEN t.amount ELSE 0 END), 0) - COALESCE(SUM(CASE WHEN t.type = 'expense' THEN t.amount ELSE 0 END), 0) AS balance FROM accounts a LEFT JOIN transactions t ON t.account_id = a.id WHERE a.profile_id = $1 GROUP BY a.id ORDER BY a.created_at `, pid) if err != nil { return nil, err } defer rows.Close() var out []model.Account for rows.Next() { var a model.Account if err := rows.Scan(&a.ID, &a.Name, &a.Type, &a.InitialBalance, &a.YieldType, &a.CDIPercentage, &a.LastYieldDate, &a.ClosingDay, &a.DueDay, &a.CreatedAt, &a.UpdatedAt, &a.Balance); err != nil { return nil, err } out = append(out, a) } return out, rows.Err() } func (r *AccountRepository) GetByID(ctx context.Context, id int) (*model.Account, error) { pid := middleware.ProfileIDFromCtx(ctx) var a model.Account err := r.pool.QueryRow(ctx, ` SELECT a.id, a.name, a.type, a.initial_balance, a.yield_type, a.cdi_percentage, a.last_yield_date::text, a.closing_day, a.due_day, a.created_at::text, a.updated_at::text, a.initial_balance + COALESCE(SUM(CASE WHEN t.type = 'income' THEN t.amount ELSE 0 END), 0) - COALESCE(SUM(CASE WHEN t.type = 'expense' THEN t.amount ELSE 0 END), 0) AS balance FROM accounts a LEFT JOIN transactions t ON t.account_id = a.id WHERE a.id = $1 AND a.profile_id = $2 GROUP BY a.id `, id, pid).Scan(&a.ID, &a.Name, &a.Type, &a.InitialBalance, &a.YieldType, &a.CDIPercentage, &a.LastYieldDate, &a.ClosingDay, &a.DueDay, &a.CreatedAt, &a.UpdatedAt, &a.Balance) if err != nil { return nil, err } return &a, nil } func (r *AccountRepository) Create(ctx context.Context, in model.AccountInput) (*model.Account, error) { pid := middleware.ProfileIDFromCtx(ctx) yt := in.YieldType if yt == "" { yt = "none" } cdiPct := in.CDIPercentage if cdiPct <= 0 { cdiPct = 100 } var a model.Account err := r.pool.QueryRow(ctx, ` INSERT INTO accounts (name, type, initial_balance, yield_type, cdi_percentage, closing_day, due_day, profile_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, name, type, initial_balance, yield_type, cdi_percentage, last_yield_date::text, closing_day, due_day, created_at::text, updated_at::text `, in.Name, in.Type, in.InitialBalance, yt, cdiPct, in.ClosingDay, in.DueDay, pid). Scan(&a.ID, &a.Name, &a.Type, &a.InitialBalance, &a.YieldType, &a.CDIPercentage, &a.LastYieldDate, &a.ClosingDay, &a.DueDay, &a.CreatedAt, &a.UpdatedAt) if err != nil { return nil, err } a.Balance = a.InitialBalance return &a, nil } func (r *AccountRepository) Update(ctx context.Context, id int, in model.AccountInput) (*model.Account, error) { pid := middleware.ProfileIDFromCtx(ctx) yt := in.YieldType if yt == "" { yt = "none" } cdiPct := in.CDIPercentage if cdiPct <= 0 { cdiPct = 100 } row := r.pool.QueryRow(ctx, ` UPDATE accounts SET name=$1, type=$2, initial_balance=$3, yield_type=$4, cdi_percentage=$5, closing_day=$6, due_day=$7, updated_at=NOW() WHERE id=$8 AND profile_id=$9 RETURNING id, name, type, initial_balance, yield_type, cdi_percentage, last_yield_date::text, closing_day, due_day, created_at::text, updated_at::text `, in.Name, in.Type, in.InitialBalance, yt, cdiPct, in.ClosingDay, in.DueDay, id, pid) var a model.Account if err := row.Scan(&a.ID, &a.Name, &a.Type, &a.InitialBalance, &a.YieldType, &a.CDIPercentage, &a.LastYieldDate, &a.ClosingDay, &a.DueDay, &a.CreatedAt, &a.UpdatedAt); err != nil { return nil, err } full, err := r.GetByID(ctx, a.ID) if err != nil { return nil, err } return full, nil } func (r *AccountRepository) Delete(ctx context.Context, id int) error { pid := middleware.ProfileIDFromCtx(ctx) _, err := r.pool.Exec(ctx, `DELETE FROM accounts WHERE id = $1 AND profile_id = $2`, id, pid) return err } func (r *AccountRepository) TotalPatrimony(ctx context.Context) (float64, error) { pid := middleware.ProfileIDFromCtx(ctx) var total float64 err := r.pool.QueryRow(ctx, ` SELECT COALESCE(SUM( a.initial_balance + COALESCE(( SELECT SUM(CASE WHEN type='income' THEN amount ELSE -amount END) FROM transactions WHERE account_id = a.id ), 0) ), 0) FROM accounts a WHERE a.profile_id = $1 `, pid).Scan(&total) return total, err } func (r *AccountRepository) UpdateLastYieldDate(ctx context.Context, id int, date string) error { pid := middleware.ProfileIDFromCtx(ctx) _, err := r.pool.Exec(ctx, `UPDATE accounts SET last_yield_date = $1 WHERE id = $2 AND profile_id = $3`, date, id, pid) return err }