Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tobias: Issuing a Payout #15

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/furniture/tobias.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class Tobias
end
10 changes: 10 additions & 0 deletions app/furniture/tobias/beneficiary.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class Tobias
class Beneficiary < ApplicationRecord
self.table_name = "tobias_beneficiaries"

belongs_to :trust, inverse_of: :beneficiaries

has_many :payments, inverse_of: :beneficiary
has_many :payouts, through: :payments
end
end
10 changes: 10 additions & 0 deletions app/furniture/tobias/payment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class Tobias
class Payment < ApplicationRecord
self.table_name = "tobias_payments"

belongs_to :payout, inverse_of: :payments
belongs_to :beneficiary, inverse_of: :payments

monetize :amount_cents
end
end
20 changes: 20 additions & 0 deletions app/furniture/tobias/payout.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class Tobias
class Payout < ApplicationRecord
self.table_name = "tobias_payouts"

belongs_to :trust
has_many :beneficiaries, through: :trust
has_many :payments, inverse_of: :payout, dependent: :destroy

monetize :amount_cents

def issue
return if payments.present?

per_beneficiary_amount = (amount / beneficiaries.count)
beneficiaries.each do |beneficiary|
payments.create_with(amount: per_beneficiary_amount).find_or_create_by(beneficiary: beneficiary)
end
end
end
end
8 changes: 8 additions & 0 deletions app/furniture/tobias/trust.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class Tobias
class Trust < ApplicationRecord
self.table_name = "tobias_trusts"

has_many :beneficiaries, inverse_of: :trust, dependent: :destroy
has_many :payouts, inverse_of: :trust, dependent: :destroy
end
end
27 changes: 27 additions & 0 deletions db/migrate/20240127063826_create_tobias_payouts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class CreateTobiasPayouts < ActiveRecord::Migration[7.1]
def change
create_table :tobias_trusts, id: :uuid do |t|
t.timestamps
end

create_table :tobias_beneficiaries, id: :uuid do |t|
t.references :trust, type: :uuid, foreign_key: {to_table: :tobias_trusts}

t.timestamps
end

create_table :tobias_payouts, id: :uuid do |t|
t.monetize :amount
t.references :trust, type: :uuid, foreign_key: {to_table: :tobias_trusts}
t.timestamps
end

create_table :tobias_payments, id: :uuid do |t|
t.references :payout, type: :uuid, foreign_key: {to_table: :tobias_payouts}
t.references :beneficiary, type: :uuid, foreign_key: {to_table: :tobias_beneficiaries}
t.monetize :amount

t.timestamps
end
end
end
36 changes: 36 additions & 0 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,38 @@
t.index ["slug", "client_id"], name: "index_spaces_on_slug_and_client_id", unique: true
end

create_table "tobias_beneficiaries", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "trust_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["trust_id"], name: "index_tobias_beneficiaries_on_trust_id"
end

create_table "tobias_payments", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "payout_id"
t.uuid "beneficiary_id"
t.integer "amount_cents", default: 0, null: false
t.string "amount_currency", default: "USD", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["beneficiary_id"], name: "index_tobias_payments_on_beneficiary_id"
t.index ["payout_id"], name: "index_tobias_payments_on_payout_id"
end

create_table "tobias_payouts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.integer "amount_cents", default: 0, null: false
t.string "amount_currency", default: "USD", null: false
t.uuid "trust_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["trust_id"], name: "index_tobias_payouts_on_trust_id"
end

create_table "tobias_trusts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end

create_table "utility_hookups", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.uuid "space_id"
t.string "name", null: false
Expand Down Expand Up @@ -368,4 +400,8 @@
add_foreign_key "rooms", "media", column: "hero_image_id"
add_foreign_key "space_agreements", "spaces"
add_foreign_key "spaces", "rooms", column: "entrance_id"
add_foreign_key "tobias_beneficiaries", "tobias_trusts", column: "trust_id"
add_foreign_key "tobias_payments", "tobias_beneficiaries", column: "beneficiary_id"
add_foreign_key "tobias_payments", "tobias_payouts", column: "payout_id"
add_foreign_key "tobias_payouts", "tobias_trusts", column: "trust_id"
end
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require "faker"
require "pundit/rspec"
require "simplecov"
require "money-rails/test_helpers"

SimpleCov.start do
enable_coverage :branch
Expand Down
15 changes: 15 additions & 0 deletions spec/tobias/beneficiary_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require "rails_helper"

RSpec.describe Tobias::Beneficiary, type: :model do
describe "#trust" do
it { is_expected.to belong_to(:trust).inverse_of(:beneficiaries) }
end

describe "#payments" do
it { is_expected.to have_many(:payments).inverse_of(:beneficiary) }
end

describe "#payouts" do
it { is_expected.to have_many(:payouts).through(:payments) }
end
end
4 changes: 4 additions & 0 deletions spec/tobias/factories/beneficiary_factory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FactoryBot.define do
factory :tobias_beneficiary, class: "Tobias::Beneficiary" do
end
end
7 changes: 7 additions & 0 deletions spec/tobias/factories/payout_factory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require_relative "trust_factory"

FactoryBot.define do
factory :tobias_payout, class: "Tobias::Payout" do
association(:trust, factory: :tobias_trust)
end
end
4 changes: 4 additions & 0 deletions spec/tobias/factories/trust_factory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FactoryBot.define do
factory :tobias_trust, class: "Tobias::Trust" do
end
end
23 changes: 23 additions & 0 deletions spec/tobias/issuing_payouts_system_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require "rails_helper"
require_relative "factories/trust_factory"
require_relative "factories/beneficiary_factory"

# @see https://github.com/zinc-collective/tobias/issues/11
RSpec.describe "Tobias: Issuing a Payout", type: :system do
scenario "Issuing a `Payout` to multiple `Beneficiaries`" do # rubocop:disable RSpec/Capybara/FeatureMethods,RSpec/ExampleLength
trust = create(:tobias_trust)
beneficiaries = create_list(:tobias_beneficiary, 10, trust:)
visit(polymorphic_path(trust.location))
click_link("New Payout")
fill_in("Amount", with: 250_00)

click_button("Create Payout")

click_button("Issue Payout")

expect(page).to have_content("$25.00 Payments Issued to 10 Beneficiaries")
beneficiaries.each do |beneficiary|
expect(page).to have_content("#{beneficiary.name} $25.00 Pending")
end
end
end
11 changes: 11 additions & 0 deletions spec/tobias/payment_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require "rails_helper"

RSpec.describe Tobias::Payment, type: :model do
describe "#payout" do
it { is_expected.to belong_to(:payout).inverse_of(:payments) }
end

describe "#amount" do
it { is_expected.to monetize(:amount) }
end
end
63 changes: 63 additions & 0 deletions spec/tobias/payout_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
require "rails_helper"
require_relative "factories/payout_factory"
require_relative "factories/beneficiary_factory"

RSpec.describe Tobias::Payout, type: :model do
describe "#payments" do
it { is_expected.to have_many(:payments).inverse_of(:payout).dependent(:destroy) }
end

describe "#beneficiaries" do
it { is_expected.to have_many(:beneficiaries).through(:trust) }
end

describe "#amount" do
it { is_expected.to monetize(:amount) }
end

describe "#issue" do
it "issues a Payment to each Beneficiary for their share of the #amount" do
payout = create(:tobias_payout, amount_cents: 150_00)

beneficiaries = create_list(:tobias_beneficiary, 10, trust: payout.trust)

payout.issue

beneficiaries.each do |beneficiary|
expect(beneficiary.payments).to exist(amount_cents: 15_00)
end
end

context "when the Payout#amount does not divide evenly" do
it "rounds down so that it can" do
payout = create(:tobias_payout, amount_cents: 3_33)

beneficiaries = create_list(:tobias_beneficiary, 2, trust: payout.trust)

payout.issue

beneficiaries.each do |beneficiary|
expect(beneficiary.payments).to exist(amount_cents: 1_66)
end
end
end

context "when running twice" do
it "does not issue multiple payouts, even when beneficiaries are added" do
payout = create(:tobias_payout, amount_cents: 100_00)

create_list(:tobias_beneficiary, 2, trust: payout.trust)

payout.issue

create(:tobias_beneficiary, trust: payout.trust)

# ActiveRecord appears to be caching the `payout.beneficiaries` results
# Reload busts that cache.
payout.reload

expect { payout.issue }.not_to(change(payout.payments, :count))
end
end
end
end
11 changes: 11 additions & 0 deletions spec/tobias/trust_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require "rails_helper"

RSpec.describe Tobias::Trust, type: :model do
describe "#benificiaries" do
it { is_expected.to have_many(:beneficiaries).inverse_of(:trust).dependent(:destroy) }
end

describe "#payouts" do
it { is_expected.to have_many(:payouts).inverse_of(:trust).dependent(:destroy) }
end
end
Loading