feat: loans
This commit is contained in:
parent
f05c36e44f
commit
ac7296b06e
20 changed files with 3191 additions and 4 deletions
107
internal/repository/loan.go
Normal file
107
internal/repository/loan.go
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.juancwu.dev/juancwu/budgit/internal/model"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrLoanNotFound = errors.New("loan not found")
|
||||
)
|
||||
|
||||
type LoanRepository interface {
|
||||
Create(loan *model.Loan) error
|
||||
GetByID(id string) (*model.Loan, error)
|
||||
GetBySpaceID(spaceID string) ([]*model.Loan, error)
|
||||
GetBySpaceIDPaginated(spaceID string, limit, offset int) ([]*model.Loan, error)
|
||||
CountBySpaceID(spaceID string) (int, error)
|
||||
Update(loan *model.Loan) error
|
||||
Delete(id string) error
|
||||
SetPaidOff(id string, paidOff bool) error
|
||||
GetTotalPaidForLoan(loanID string) (int, error)
|
||||
GetReceiptCountForLoan(loanID string) (int, error)
|
||||
}
|
||||
|
||||
type loanRepository struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewLoanRepository(db *sqlx.DB) LoanRepository {
|
||||
return &loanRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *loanRepository) Create(loan *model.Loan) error {
|
||||
query := `INSERT INTO loans (id, space_id, name, description, original_amount_cents, interest_rate_bps, start_date, end_date, is_paid_off, created_by, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12);`
|
||||
_, err := r.db.Exec(query, loan.ID, loan.SpaceID, loan.Name, loan.Description, loan.OriginalAmountCents, loan.InterestRateBps, loan.StartDate, loan.EndDate, loan.IsPaidOff, loan.CreatedBy, loan.CreatedAt, loan.UpdatedAt)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *loanRepository) GetByID(id string) (*model.Loan, error) {
|
||||
loan := &model.Loan{}
|
||||
query := `SELECT * FROM loans WHERE id = $1;`
|
||||
err := r.db.Get(loan, query, id)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ErrLoanNotFound
|
||||
}
|
||||
return loan, err
|
||||
}
|
||||
|
||||
func (r *loanRepository) GetBySpaceID(spaceID string) ([]*model.Loan, error) {
|
||||
var loans []*model.Loan
|
||||
query := `SELECT * FROM loans WHERE space_id = $1 ORDER BY is_paid_off ASC, created_at DESC;`
|
||||
err := r.db.Select(&loans, query, spaceID)
|
||||
return loans, err
|
||||
}
|
||||
|
||||
func (r *loanRepository) GetBySpaceIDPaginated(spaceID string, limit, offset int) ([]*model.Loan, error) {
|
||||
var loans []*model.Loan
|
||||
query := `SELECT * FROM loans WHERE space_id = $1 ORDER BY is_paid_off ASC, created_at DESC LIMIT $2 OFFSET $3;`
|
||||
err := r.db.Select(&loans, query, spaceID, limit, offset)
|
||||
return loans, err
|
||||
}
|
||||
|
||||
func (r *loanRepository) CountBySpaceID(spaceID string) (int, error) {
|
||||
var count int
|
||||
err := r.db.Get(&count, `SELECT COUNT(*) FROM loans WHERE space_id = $1;`, spaceID)
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *loanRepository) Update(loan *model.Loan) error {
|
||||
query := `UPDATE loans SET name = $1, description = $2, original_amount_cents = $3, interest_rate_bps = $4, start_date = $5, end_date = $6, updated_at = $7 WHERE id = $8;`
|
||||
result, err := r.db.Exec(query, loan.Name, loan.Description, loan.OriginalAmountCents, loan.InterestRateBps, loan.StartDate, loan.EndDate, loan.UpdatedAt, loan.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rows, err := result.RowsAffected()
|
||||
if err == nil && rows == 0 {
|
||||
return ErrLoanNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *loanRepository) Delete(id string) error {
|
||||
_, err := r.db.Exec(`DELETE FROM loans WHERE id = $1;`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *loanRepository) SetPaidOff(id string, paidOff bool) error {
|
||||
_, err := r.db.Exec(`UPDATE loans SET is_paid_off = $1, updated_at = $2 WHERE id = $3;`, paidOff, time.Now(), id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *loanRepository) GetTotalPaidForLoan(loanID string) (int, error) {
|
||||
var total int
|
||||
err := r.db.Get(&total, `SELECT COALESCE(SUM(total_amount_cents), 0) FROM receipts WHERE loan_id = $1;`, loanID)
|
||||
return total, err
|
||||
}
|
||||
|
||||
func (r *loanRepository) GetReceiptCountForLoan(loanID string) (int, error) {
|
||||
var count int
|
||||
err := r.db.Get(&count, `SELECT COUNT(*) FROM receipts WHERE loan_id = $1;`, loanID)
|
||||
return count, err
|
||||
}
|
||||
326
internal/repository/receipt.go
Normal file
326
internal/repository/receipt.go
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
"git.juancwu.dev/juancwu/budgit/internal/model"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrReceiptNotFound = errors.New("receipt not found")
|
||||
)
|
||||
|
||||
type ReceiptRepository interface {
|
||||
CreateWithSources(
|
||||
receipt *model.Receipt,
|
||||
sources []model.ReceiptFundingSource,
|
||||
balanceExpense *model.Expense,
|
||||
accountTransfers []*model.AccountTransfer,
|
||||
) error
|
||||
GetByID(id string) (*model.Receipt, error)
|
||||
GetByLoanIDPaginated(loanID string, limit, offset int) ([]*model.Receipt, error)
|
||||
CountByLoanID(loanID string) (int, error)
|
||||
GetBySpaceIDPaginated(spaceID string, limit, offset int) ([]*model.Receipt, error)
|
||||
CountBySpaceID(spaceID string) (int, error)
|
||||
GetFundingSourcesByReceiptID(receiptID string) ([]model.ReceiptFundingSource, error)
|
||||
GetFundingSourcesWithAccountsByReceiptIDs(receiptIDs []string) (map[string][]model.ReceiptFundingSourceWithAccount, error)
|
||||
DeleteWithReversal(receiptID string) error
|
||||
UpdateWithSources(
|
||||
receipt *model.Receipt,
|
||||
sources []model.ReceiptFundingSource,
|
||||
balanceExpense *model.Expense,
|
||||
accountTransfers []*model.AccountTransfer,
|
||||
) error
|
||||
}
|
||||
|
||||
type receiptRepository struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewReceiptRepository(db *sqlx.DB) ReceiptRepository {
|
||||
return &receiptRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *receiptRepository) CreateWithSources(
|
||||
receipt *model.Receipt,
|
||||
sources []model.ReceiptFundingSource,
|
||||
balanceExpense *model.Expense,
|
||||
accountTransfers []*model.AccountTransfer,
|
||||
) error {
|
||||
tx, err := r.db.Beginx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// Insert receipt
|
||||
_, err = tx.Exec(
|
||||
`INSERT INTO receipts (id, loan_id, space_id, description, total_amount_cents, date, recurring_receipt_id, created_by, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);`,
|
||||
receipt.ID, receipt.LoanID, receipt.SpaceID, receipt.Description, receipt.TotalAmountCents, receipt.Date, receipt.RecurringReceiptID, receipt.CreatedBy, receipt.CreatedAt, receipt.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Insert balance expense if present
|
||||
if balanceExpense != nil {
|
||||
_, err = tx.Exec(
|
||||
`INSERT INTO expenses (id, space_id, created_by, description, amount_cents, type, date, payment_method_id, recurring_expense_id, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);`,
|
||||
balanceExpense.ID, balanceExpense.SpaceID, balanceExpense.CreatedBy, balanceExpense.Description, balanceExpense.AmountCents, balanceExpense.Type, balanceExpense.Date, balanceExpense.PaymentMethodID, balanceExpense.RecurringExpenseID, balanceExpense.CreatedAt, balanceExpense.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Insert account transfers
|
||||
for _, transfer := range accountTransfers {
|
||||
_, err = tx.Exec(
|
||||
`INSERT INTO account_transfers (id, account_id, amount_cents, direction, note, recurring_deposit_id, created_by, created_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8);`,
|
||||
transfer.ID, transfer.AccountID, transfer.AmountCents, transfer.Direction, transfer.Note, transfer.RecurringDepositID, transfer.CreatedBy, transfer.CreatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Insert funding sources
|
||||
for _, src := range sources {
|
||||
_, err = tx.Exec(
|
||||
`INSERT INTO receipt_funding_sources (id, receipt_id, source_type, account_id, amount_cents, linked_expense_id, linked_transfer_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7);`,
|
||||
src.ID, src.ReceiptID, src.SourceType, src.AccountID, src.AmountCents, src.LinkedExpenseID, src.LinkedTransferID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (r *receiptRepository) GetByID(id string) (*model.Receipt, error) {
|
||||
receipt := &model.Receipt{}
|
||||
query := `SELECT * FROM receipts WHERE id = $1;`
|
||||
err := r.db.Get(receipt, query, id)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ErrReceiptNotFound
|
||||
}
|
||||
return receipt, err
|
||||
}
|
||||
|
||||
func (r *receiptRepository) GetByLoanIDPaginated(loanID string, limit, offset int) ([]*model.Receipt, error) {
|
||||
var receipts []*model.Receipt
|
||||
query := `SELECT * FROM receipts WHERE loan_id = $1 ORDER BY date DESC, created_at DESC LIMIT $2 OFFSET $3;`
|
||||
err := r.db.Select(&receipts, query, loanID, limit, offset)
|
||||
return receipts, err
|
||||
}
|
||||
|
||||
func (r *receiptRepository) CountByLoanID(loanID string) (int, error) {
|
||||
var count int
|
||||
err := r.db.Get(&count, `SELECT COUNT(*) FROM receipts WHERE loan_id = $1;`, loanID)
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *receiptRepository) GetBySpaceIDPaginated(spaceID string, limit, offset int) ([]*model.Receipt, error) {
|
||||
var receipts []*model.Receipt
|
||||
query := `SELECT * FROM receipts WHERE space_id = $1 ORDER BY date DESC, created_at DESC LIMIT $2 OFFSET $3;`
|
||||
err := r.db.Select(&receipts, query, spaceID, limit, offset)
|
||||
return receipts, err
|
||||
}
|
||||
|
||||
func (r *receiptRepository) CountBySpaceID(spaceID string) (int, error) {
|
||||
var count int
|
||||
err := r.db.Get(&count, `SELECT COUNT(*) FROM receipts WHERE space_id = $1;`, spaceID)
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *receiptRepository) GetFundingSourcesByReceiptID(receiptID string) ([]model.ReceiptFundingSource, error) {
|
||||
var sources []model.ReceiptFundingSource
|
||||
query := `SELECT * FROM receipt_funding_sources WHERE receipt_id = $1;`
|
||||
err := r.db.Select(&sources, query, receiptID)
|
||||
return sources, err
|
||||
}
|
||||
|
||||
func (r *receiptRepository) GetFundingSourcesWithAccountsByReceiptIDs(receiptIDs []string) (map[string][]model.ReceiptFundingSourceWithAccount, error) {
|
||||
if len(receiptIDs) == 0 {
|
||||
return make(map[string][]model.ReceiptFundingSourceWithAccount), nil
|
||||
}
|
||||
|
||||
type row struct {
|
||||
ID string `db:"id"`
|
||||
ReceiptID string `db:"receipt_id"`
|
||||
SourceType model.FundingSourceType `db:"source_type"`
|
||||
AccountID *string `db:"account_id"`
|
||||
AmountCents int `db:"amount_cents"`
|
||||
LinkedExpenseID *string `db:"linked_expense_id"`
|
||||
LinkedTransferID *string `db:"linked_transfer_id"`
|
||||
AccountName *string `db:"account_name"`
|
||||
}
|
||||
|
||||
query, args, err := sqlx.In(`
|
||||
SELECT rfs.id, rfs.receipt_id, rfs.source_type, rfs.account_id, rfs.amount_cents,
|
||||
rfs.linked_expense_id, rfs.linked_transfer_id,
|
||||
ma.name AS account_name
|
||||
FROM receipt_funding_sources rfs
|
||||
LEFT JOIN money_accounts ma ON rfs.account_id = ma.id
|
||||
WHERE rfs.receipt_id IN (?)
|
||||
ORDER BY rfs.source_type ASC;
|
||||
`, receiptIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query = r.db.Rebind(query)
|
||||
|
||||
var rows []row
|
||||
if err := r.db.Select(&rows, query, args...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[string][]model.ReceiptFundingSourceWithAccount)
|
||||
for _, rw := range rows {
|
||||
accountName := ""
|
||||
if rw.AccountName != nil {
|
||||
accountName = *rw.AccountName
|
||||
}
|
||||
result[rw.ReceiptID] = append(result[rw.ReceiptID], model.ReceiptFundingSourceWithAccount{
|
||||
ReceiptFundingSource: model.ReceiptFundingSource{
|
||||
ID: rw.ID,
|
||||
ReceiptID: rw.ReceiptID,
|
||||
SourceType: rw.SourceType,
|
||||
AccountID: rw.AccountID,
|
||||
AmountCents: rw.AmountCents,
|
||||
LinkedExpenseID: rw.LinkedExpenseID,
|
||||
LinkedTransferID: rw.LinkedTransferID,
|
||||
},
|
||||
AccountName: accountName,
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *receiptRepository) DeleteWithReversal(receiptID string) error {
|
||||
tx, err := r.db.Beginx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// Get all funding sources for this receipt
|
||||
var sources []model.ReceiptFundingSource
|
||||
if err := tx.Select(&sources, `SELECT * FROM receipt_funding_sources WHERE receipt_id = $1;`, receiptID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete linked expenses and transfers
|
||||
for _, src := range sources {
|
||||
if src.LinkedExpenseID != nil {
|
||||
if _, err := tx.Exec(`DELETE FROM expenses WHERE id = $1;`, *src.LinkedExpenseID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if src.LinkedTransferID != nil {
|
||||
if _, err := tx.Exec(`DELETE FROM account_transfers WHERE id = $1;`, *src.LinkedTransferID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete funding sources (cascade would handle this, but be explicit)
|
||||
if _, err := tx.Exec(`DELETE FROM receipt_funding_sources WHERE receipt_id = $1;`, receiptID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete the receipt
|
||||
if _, err := tx.Exec(`DELETE FROM receipts WHERE id = $1;`, receiptID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (r *receiptRepository) UpdateWithSources(
|
||||
receipt *model.Receipt,
|
||||
sources []model.ReceiptFundingSource,
|
||||
balanceExpense *model.Expense,
|
||||
accountTransfers []*model.AccountTransfer,
|
||||
) error {
|
||||
tx, err := r.db.Beginx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// Delete old linked records
|
||||
var oldSources []model.ReceiptFundingSource
|
||||
if err := tx.Select(&oldSources, `SELECT * FROM receipt_funding_sources WHERE receipt_id = $1;`, receipt.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, src := range oldSources {
|
||||
if src.LinkedExpenseID != nil {
|
||||
if _, err := tx.Exec(`DELETE FROM expenses WHERE id = $1;`, *src.LinkedExpenseID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if src.LinkedTransferID != nil {
|
||||
if _, err := tx.Exec(`DELETE FROM account_transfers WHERE id = $1;`, *src.LinkedTransferID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, err := tx.Exec(`DELETE FROM receipt_funding_sources WHERE receipt_id = $1;`, receipt.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update receipt
|
||||
_, err = tx.Exec(
|
||||
`UPDATE receipts SET description = $1, total_amount_cents = $2, date = $3, updated_at = $4 WHERE id = $5;`,
|
||||
receipt.Description, receipt.TotalAmountCents, receipt.Date, receipt.UpdatedAt, receipt.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Insert new balance expense
|
||||
if balanceExpense != nil {
|
||||
_, err = tx.Exec(
|
||||
`INSERT INTO expenses (id, space_id, created_by, description, amount_cents, type, date, payment_method_id, recurring_expense_id, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);`,
|
||||
balanceExpense.ID, balanceExpense.SpaceID, balanceExpense.CreatedBy, balanceExpense.Description, balanceExpense.AmountCents, balanceExpense.Type, balanceExpense.Date, balanceExpense.PaymentMethodID, balanceExpense.RecurringExpenseID, balanceExpense.CreatedAt, balanceExpense.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Insert new account transfers
|
||||
for _, transfer := range accountTransfers {
|
||||
_, err = tx.Exec(
|
||||
`INSERT INTO account_transfers (id, account_id, amount_cents, direction, note, recurring_deposit_id, created_by, created_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8);`,
|
||||
transfer.ID, transfer.AccountID, transfer.AmountCents, transfer.Direction, transfer.Note, transfer.RecurringDepositID, transfer.CreatedBy, transfer.CreatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Insert new funding sources
|
||||
for _, src := range sources {
|
||||
_, err = tx.Exec(
|
||||
`INSERT INTO receipt_funding_sources (id, receipt_id, source_type, account_id, amount_cents, linked_expense_id, linked_transfer_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7);`,
|
||||
src.ID, src.ReceiptID, src.SourceType, src.AccountID, src.AmountCents, src.LinkedExpenseID, src.LinkedTransferID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
165
internal/repository/recurring_receipt.go
Normal file
165
internal/repository/recurring_receipt.go
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.juancwu.dev/juancwu/budgit/internal/model"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrRecurringReceiptNotFound = errors.New("recurring receipt not found")
|
||||
)
|
||||
|
||||
type RecurringReceiptRepository interface {
|
||||
Create(rr *model.RecurringReceipt, sources []model.RecurringReceiptSource) error
|
||||
GetByID(id string) (*model.RecurringReceipt, error)
|
||||
GetByLoanID(loanID string) ([]*model.RecurringReceipt, error)
|
||||
GetBySpaceID(spaceID string) ([]*model.RecurringReceipt, error)
|
||||
GetSourcesByRecurringReceiptID(id string) ([]model.RecurringReceiptSource, error)
|
||||
Update(rr *model.RecurringReceipt, sources []model.RecurringReceiptSource) error
|
||||
Delete(id string) error
|
||||
SetActive(id string, active bool) error
|
||||
Deactivate(id string) error
|
||||
GetDueRecurrences(now time.Time) ([]*model.RecurringReceipt, error)
|
||||
GetDueRecurrencesForSpace(spaceID string, now time.Time) ([]*model.RecurringReceipt, error)
|
||||
UpdateNextOccurrence(id string, next time.Time) error
|
||||
}
|
||||
|
||||
type recurringReceiptRepository struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewRecurringReceiptRepository(db *sqlx.DB) RecurringReceiptRepository {
|
||||
return &recurringReceiptRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *recurringReceiptRepository) Create(rr *model.RecurringReceipt, sources []model.RecurringReceiptSource) error {
|
||||
tx, err := r.db.Beginx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
_, err = tx.Exec(
|
||||
`INSERT INTO recurring_receipts (id, loan_id, space_id, description, total_amount_cents, frequency, start_date, end_date, next_occurrence, is_active, created_by, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13);`,
|
||||
rr.ID, rr.LoanID, rr.SpaceID, rr.Description, rr.TotalAmountCents, rr.Frequency, rr.StartDate, rr.EndDate, rr.NextOccurrence, rr.IsActive, rr.CreatedBy, rr.CreatedAt, rr.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, src := range sources {
|
||||
_, err = tx.Exec(
|
||||
`INSERT INTO recurring_receipt_sources (id, recurring_receipt_id, source_type, account_id, amount_cents)
|
||||
VALUES ($1, $2, $3, $4, $5);`,
|
||||
src.ID, src.RecurringReceiptID, src.SourceType, src.AccountID, src.AmountCents,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (r *recurringReceiptRepository) GetByID(id string) (*model.RecurringReceipt, error) {
|
||||
rr := &model.RecurringReceipt{}
|
||||
query := `SELECT * FROM recurring_receipts WHERE id = $1;`
|
||||
err := r.db.Get(rr, query, id)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ErrRecurringReceiptNotFound
|
||||
}
|
||||
return rr, err
|
||||
}
|
||||
|
||||
func (r *recurringReceiptRepository) GetByLoanID(loanID string) ([]*model.RecurringReceipt, error) {
|
||||
var results []*model.RecurringReceipt
|
||||
query := `SELECT * FROM recurring_receipts WHERE loan_id = $1 ORDER BY is_active DESC, next_occurrence ASC;`
|
||||
err := r.db.Select(&results, query, loanID)
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (r *recurringReceiptRepository) GetBySpaceID(spaceID string) ([]*model.RecurringReceipt, error) {
|
||||
var results []*model.RecurringReceipt
|
||||
query := `SELECT * FROM recurring_receipts WHERE space_id = $1 ORDER BY is_active DESC, next_occurrence ASC;`
|
||||
err := r.db.Select(&results, query, spaceID)
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (r *recurringReceiptRepository) GetSourcesByRecurringReceiptID(id string) ([]model.RecurringReceiptSource, error) {
|
||||
var sources []model.RecurringReceiptSource
|
||||
query := `SELECT * FROM recurring_receipt_sources WHERE recurring_receipt_id = $1;`
|
||||
err := r.db.Select(&sources, query, id)
|
||||
return sources, err
|
||||
}
|
||||
|
||||
func (r *recurringReceiptRepository) Update(rr *model.RecurringReceipt, sources []model.RecurringReceiptSource) error {
|
||||
tx, err := r.db.Beginx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
_, err = tx.Exec(
|
||||
`UPDATE recurring_receipts SET description = $1, total_amount_cents = $2, frequency = $3, start_date = $4, end_date = $5, next_occurrence = $6, updated_at = $7 WHERE id = $8;`,
|
||||
rr.Description, rr.TotalAmountCents, rr.Frequency, rr.StartDate, rr.EndDate, rr.NextOccurrence, rr.UpdatedAt, rr.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Replace sources
|
||||
if _, err := tx.Exec(`DELETE FROM recurring_receipt_sources WHERE recurring_receipt_id = $1;`, rr.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, src := range sources {
|
||||
_, err = tx.Exec(
|
||||
`INSERT INTO recurring_receipt_sources (id, recurring_receipt_id, source_type, account_id, amount_cents)
|
||||
VALUES ($1, $2, $3, $4, $5);`,
|
||||
src.ID, src.RecurringReceiptID, src.SourceType, src.AccountID, src.AmountCents,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (r *recurringReceiptRepository) Delete(id string) error {
|
||||
_, err := r.db.Exec(`DELETE FROM recurring_receipts WHERE id = $1;`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *recurringReceiptRepository) SetActive(id string, active bool) error {
|
||||
_, err := r.db.Exec(`UPDATE recurring_receipts SET is_active = $1, updated_at = $2 WHERE id = $3;`, active, time.Now(), id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *recurringReceiptRepository) Deactivate(id string) error {
|
||||
return r.SetActive(id, false)
|
||||
}
|
||||
|
||||
func (r *recurringReceiptRepository) GetDueRecurrences(now time.Time) ([]*model.RecurringReceipt, error) {
|
||||
var results []*model.RecurringReceipt
|
||||
query := `SELECT * FROM recurring_receipts WHERE is_active = true AND next_occurrence <= $1;`
|
||||
err := r.db.Select(&results, query, now)
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (r *recurringReceiptRepository) GetDueRecurrencesForSpace(spaceID string, now time.Time) ([]*model.RecurringReceipt, error) {
|
||||
var results []*model.RecurringReceipt
|
||||
query := `SELECT * FROM recurring_receipts WHERE is_active = true AND space_id = $1 AND next_occurrence <= $2;`
|
||||
err := r.db.Select(&results, query, spaceID, now)
|
||||
return results, err
|
||||
}
|
||||
|
||||
func (r *recurringReceiptRepository) UpdateNextOccurrence(id string, next time.Time) error {
|
||||
_, err := r.db.Exec(`UPDATE recurring_receipts SET next_occurrence = $1, updated_at = $2 WHERE id = $3;`, next, time.Now(), id)
|
||||
return err
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue