Debts MVP Database Schema And Rails File List
Database Schema
This schema follows the conventions already used by the finance module:
- multi-tenant by
organization_id - ownership by
membership_id - decimal precision for financial amounts
- enums stored as strings
- optional
account_idfor linking payment source
Migration 1: Create Debts
Suggested file: db/migrate/TIMESTAMP_create_debts.rb
class CreateDebts < ActiveRecord::Migration[8.1]
def change
create_table :debts do |t|
t.references :organization, null: false, foreign_key: true
t.references :membership, null: true, foreign_key: true
t.references :account, null: true, foreign_key: true
t.string :name, null: false
t.string :lender_name
t.string :debt_type, null: false, default: "personal_loan"
t.string :status, null: false, default: "active"
t.string :currency, null: false, default: "BRL"
t.decimal :original_amount, precision: 15, scale: 2, null: false, default: 0
t.decimal :current_balance, precision: 15, scale: 2, null: false, default: 0
t.decimal :interest_rate, precision: 8, scale: 4, null: false, default: 0
t.string :interest_rate_period, null: false, default: "monthly"
t.decimal :minimum_payment, precision: 15, scale: 2
t.integer :due_day
t.date :started_on
t.date :ends_on
t.integer :installment_total
t.integer :installments_paid, null: false, default: 0
t.text :notes
t.timestamps
end
add_index :debts, [ :organization_id, :name ]
add_index :debts, [ :organization_id, :status ]
add_index :debts, [ :organization_id, :debt_type ]
add_index :debts, [ :organization_id, :membership_id ]
add_index :debts, [ :organization_id, :due_day ]
end
end
Column Notes
membership_idnullable allows shared family debt.account_idis optional because not every debt needs a source account.original_amountandcurrent_balanceboth remain in the record because users may start tracking a debt after it has already been partially repaid.interest_rateuses scale4to support values like1.9900.due_dayis better than storing a recurring due date string for loans and cards in the MVP.
Migration 2: Create Debt Payments
Suggested file: db/migrate/TIMESTAMP_create_debt_payments.rb
class CreateDebtPayments < ActiveRecord::Migration[8.1]
def change
create_table :debt_payments do |t|
t.references :debt, null: false, foreign_key: true
t.references :organization, null: false, foreign_key: true
t.references :membership, null: false, foreign_key: true
t.references :account, null: true, foreign_key: true
t.date :paid_on, null: false
t.decimal :amount, precision: 15, scale: 2, null: false, default: 0
t.decimal :principal_amount, precision: 15, scale: 2
t.decimal :interest_amount, precision: 15, scale: 2
t.decimal :fee_amount, precision: 15, scale: 2
t.text :notes
t.timestamps
end
add_index :debt_payments, [ :debt_id, :paid_on ]
add_index :debt_payments, [ :organization_id, :paid_on ]
end
end
Schema Rules
Validation rules expected at model level:
original_amount > 0current_balance >= 0interest_rate >= 0minimum_payment >= 0when presentamount > 0due_daymust be in1..31when presentinstallments_paid <= installment_totalwhen total exists
Rails File List For MVP
This file list is the exact recommended MVP footprint.
New Models
app/models/debt.rbapp/models/debt_payment.rbapp/models/debt/summary_calculator.rb
New Controllers
app/controllers/organizations/debts_controller.rbapp/controllers/organizations/debt_payments_controller.rb
New Policies
app/policies/debt_policy.rb
New Views
app/views/organizations/debts/index.html.erbapp/views/organizations/debts/show.html.erbapp/views/organizations/debts/_form.html.erbapp/views/organizations/debts/new.html.erbapp/views/organizations/debts/edit.html.erbapp/views/organizations/debt_payments/_form.html.erb
Existing Files To Update
config/routes.rbapp/views/shared/_sidebar_links.html.erbapp/helpers/finance_helper.rbconfig/locales/en.ymlconfig/locales/pt-BR.ymldb/seeds.rb
Potentially, if the repo already centralizes nav translations elsewhere:
- locale file containing
navlabels
New Tests
test/models/debt_test.rbtest/models/debt_payment_test.rbtest/models/debt/summary_calculator_test.rbtest/controllers/organizations/debts_controller_test.rbtest/controllers/organizations/debt_payments_controller_test.rbtest/system/debts_flow_test.rb- fixture updates in
test/fixtures/debts.yml - fixture updates in
test/fixtures/debt_payments.yml
New Fixtures
test/fixtures/debts.ymltest/fixtures/debt_payments.yml
New Migrations
db/migrate/TIMESTAMP_create_debts.rbdb/migrate/TIMESTAMP_create_debt_payments.rb
Optional But Reasonable Additions
These are still compatible with the MVP if implementation needs them.
app/helpers/debts_helper.rbapp/views/organizations/debts/_debt_row.html.erbapp/views/organizations/debts/_kpi_cards.html.erbapp/views/organizations/debts/_upcoming_payments.html.erb
These partials are useful if the main index page becomes too large.
Missing MVP Coverage To Add
- full debt copy in
pt-BRso modal labels, placeholders, nav items, and empty states never render missing-translation output - realistic seeded debt examples for
[email protected]so the module is immediately reviewable afterdb:seed - a simplified debt index and form flow that prioritizes balance, next payment, and payoff progress before secondary metadata
Minimal Parameter Contract
Debt params
params.expect(
debt: [
:name,
:lender_name,
:debt_type,
:status,
:currency,
:original_amount,
:current_balance,
:interest_rate,
:interest_rate_period,
:minimum_payment,
:due_day,
:started_on,
:ends_on,
:installment_total,
:installments_paid,
:account_id,
:notes,
:shared
]
)
Implementation note:
sharedcan be a virtual form field that determines whethermembership_idis set tonilorCurrent.membership.id.
DebtPayment params
params.expect(
debt_payment: [
:paid_on,
:amount,
:principal_amount,
:interest_amount,
:fee_amount,
:account_id,
:notes
]
)
Suggested Associations Summary
Debt
belongs_to :organization
belongs_to :membership, optional: true
belongs_to :account, optional: true
has_many :debt_payments, dependent: :destroy
DebtPayment
belongs_to :debt
belongs_to :organization
belongs_to :membership
belongs_to :account, optional: true
Suggested Enums Summary
Debt
enum :debt_type, {
credit_card: "credit_card",
personal_loan: "personal_loan",
mortgage: "mortgage",
vehicle_financing: "vehicle_financing",
installment_plan: "installment_plan",
tax_debt: "tax_debt",
family_loan: "family_loan",
other: "other"
}, default: :personal_loan
enum :status, {
active: "active",
paid_off: "paid_off",
overdue: "overdue",
renegotiated: "renegotiated",
paused: "paused"
}, default: :active
enum :interest_rate_period, {
monthly: "monthly",
yearly: "yearly"
}, default: :monthly
Fixture Strategy
Recommended fixture coverage:
- one shared family debt
- one personal debt for the main signed-in member
- one overdue debt
- one paid-off debt
- two payment records for one active debt
That is enough to exercise the full MVP surface in tests.