diff --git a/Gemfile b/Gemfile
index 863d16f..21e78cf 100644
--- a/Gemfile
+++ b/Gemfile
@@ -88,3 +88,5 @@ group :test do
# Code coverage; started in test/test_helper.rb only when COVERAGE=1
gem "simplecov", require: false
end
+
+gem "money-rails", "~> 3.0"
diff --git a/Gemfile.lock b/Gemfile.lock
index ecea679..2a19f5b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -226,6 +226,16 @@ GEM
minitest (6.0.6)
drb (~> 2.0)
prism (~> 1.5)
+ monetize (2.0.0)
+ money (~> 7.0)
+ money (7.1.1)
+ bigdecimal
+ i18n (~> 1.9)
+ money-rails (3.0.0)
+ activesupport (>= 7.0)
+ monetize (~> 2.0)
+ money (~> 7.0)
+ railties (>= 7.0)
msgpack (1.8.4)
net-imap (0.6.6)
date
@@ -489,6 +499,7 @@ DEPENDENCIES
json (>= 2.21.2)
kamal
letter_opener
+ money-rails (~> 3.0)
nokogiri (>= 1.19.4)
postmark-rails (~> 0.22)
propshaft
@@ -597,6 +608,9 @@ CHECKSUMS
mini_magick (5.3.1) sha256=29395dfd76badcabb6403ee5aff6f681e867074f8f28ce08d78661e9e4a351c4
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
+ monetize (2.0.0) sha256=152980cadff9fd11a95817d56e3d7d69b07998c4506f15d971edf05851f82aba
+ money (7.1.1) sha256=8ed5cd99dfbc45e82688ea711cb59ea3c396c2608cd88876502b164c90269d4e
+ money-rails (3.0.0) sha256=f4bd1c8e1c98d221e7bf452eeb52e240f41224e43da2a477a5659a87c0c95bae
msgpack (1.8.4) sha256=4411c22d350dd1c20250f7eada3cca2695438c2f769cf0782f0cd065d90a3e7b
net-imap (0.6.6) sha256=96aa4ee50df3060203e649efc341f53480b791d49e150f2fdebf68beb141a8df
net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3
diff --git a/app/models/allocation.rb b/app/models/allocation.rb
index e5cc0f8..c449ec6 100644
--- a/app/models/allocation.rb
+++ b/app/models/allocation.rb
@@ -1,4 +1,6 @@
class Allocation < ApplicationRecord
+ monetize :amount_cents, allow_nil: true
+
belongs_to :scenario
belongs_to :allocation_category, optional: true
diff --git a/app/models/allocation/one_time.rb b/app/models/allocation/one_time.rb
index d81eee1..386dafa 100644
--- a/app/models/allocation/one_time.rb
+++ b/app/models/allocation/one_time.rb
@@ -1,5 +1,5 @@
class Allocation::OneTime < Allocation
- validates :amount,
+ validates :amount_cents,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
validate :within_total_giving_amount
@@ -16,7 +16,7 @@ def share_percentage
total = scenario.one_time_giving_amount
return 0 if total.zero?
- (amount.to_i / total.to_f * 100).round
+ (amount.cents / total.cents.to_f * 100).round
end
# The most this allocation can be set to while staying within the scenario's
@@ -34,21 +34,18 @@ def max_amount(scenario = self.scenario)
private
def within_total_giving_amount
- return if amount.blank? || scenario&.total_giving_amount.blank?
+ return if amount_cents.blank? || scenario&.total_giving_amount_cents.blank?
- remaining = budget_remaining(scenario)
- return if remaining.nil?
-
- if amount > remaining
- others = scenario.total_giving_amount - remaining
- errors.add(:amount, "would bring one-time giving to #{others + amount}, over the total giving amount of #{scenario.total_giving_amount.to_i}")
+ others = scenario.one_time_allocations.where.not(id: id).sum(:amount_cents)
+ if others + amount_cents > scenario.total_giving_amount_cents
+ errors.add(:amount, "would bring one-time giving to #{(others + amount_cents) / 100.0}, over the total giving amount of #{(scenario.total_giving_amount_cents / 100.0).to_i}")
end
end
def budget_remaining(scenario = self.scenario)
return nil if scenario.blank? || scenario.total_giving_amount.blank?
- others = scenario.one_time_allocations.where.not(id: id).sum(:amount)
- scenario.total_giving_amount - others
+ others = scenario.one_time_allocations.where.not(id: id).sum(:amount_cents)
+ (scenario.total_giving_amount_cents - others) / 100
end
end
diff --git a/app/models/allocation/ongoing.rb b/app/models/allocation/ongoing.rb
index 35335bd..c44789a 100644
--- a/app/models/allocation/ongoing.rb
+++ b/app/models/allocation/ongoing.rb
@@ -7,11 +7,11 @@ class Allocation::Ongoing < Allocation
validate :within_ongoing_percentage_total
def dollar_amount
- (percentage.to_i / 100.0 * scenario.ongoing_giving_amount).round
+ scenario.ongoing_giving_amount * (percentage.to_i / 100.0)
end
def perpetuity_annual_amount
- (dollar_amount * PERPETUITY_PAYOUT_RATE).round
+ dollar_amount * PERPETUITY_PAYOUT_RATE
end
def ongoing?
diff --git a/app/models/scenario.rb b/app/models/scenario.rb
index 1c69ede..300d8a7 100644
--- a/app/models/scenario.rb
+++ b/app/models/scenario.rb
@@ -1,6 +1,8 @@
class Scenario < ApplicationRecord
include UserSearchable
+ monetize :total_giving_amount_cents, allow_nil: true
+
belongs_to :organization
belongs_to :user
has_many :allocations, dependent: :destroy
@@ -33,11 +35,11 @@ def ongoing_percentage_total
end
def one_time_giving_amount
- one_time_allocations.sum { |allocation| allocation.amount.to_i }
+ one_time_allocations.sum(Money.new(0)) { |allocation| allocation.amount }
end
def ongoing_giving_amount
- total_giving_amount.to_i - one_time_giving_amount
+ (total_giving_amount || Money.new(0)) - one_time_giving_amount
end
private
diff --git a/app/views/admin/scenarios/index.html.erb b/app/views/admin/scenarios/index.html.erb
index 92b7394..38affea 100644
--- a/app/views/admin/scenarios/index.html.erb
+++ b/app/views/admin/scenarios/index.html.erb
@@ -41,7 +41,7 @@
<%= link_to scenario_path(scenario), data: { turbo_frame: "_top" }, class: "block" do %>
- <%= scenario.total_giving_amount.present? ? number_to_currency(scenario.total_giving_amount, precision: 0) : content_tag(:span, "—", class: "text-ink-faint") %>
+ <%= scenario.total_giving_amount.present? ? number_to_currency(scenario.total_giving_amount) : content_tag(:span, "—", class: "text-ink-faint") %>
<% end %>
|
diff --git a/app/views/public/scenarios/show.html.erb b/app/views/public/scenarios/show.html.erb
index aa1237a..35b9026 100644
--- a/app/views/public/scenarios/show.html.erb
+++ b/app/views/public/scenarios/show.html.erb
@@ -6,9 +6,9 @@
<%= @scenario.name %>
Shared by <%= @scenario.user.display_name %>
- <% if @scenario.total_giving_amount.to_i.positive? %>
+ <% if @scenario.total_giving_amount&.positive? %>
- Total giving <%= number_to_currency(@scenario.total_giving_amount, precision: 0) %>
+ Total giving <%= number_to_currency(@scenario.total_giving_amount) %>
<% end %>
diff --git a/app/views/scenarios/_allocation.html.erb b/app/views/scenarios/_allocation.html.erb
index d3e8082..b09fa82 100644
--- a/app/views/scenarios/_allocation.html.erb
+++ b/app/views/scenarios/_allocation.html.erb
@@ -4,7 +4,7 @@
<% slider_limit = remaining_ongoing_percentage(scenario, allocation) %>
- <%= number_to_currency(allocation.amount, precision: 0) %>
+ <%= number_to_currency(allocation.amount) %>
diff --git a/app/views/scenarios/index.html.erb b/app/views/scenarios/index.html.erb
index 946b7d3..19c2e58 100644
--- a/app/views/scenarios/index.html.erb
+++ b/app/views/scenarios/index.html.erb
@@ -19,7 +19,7 @@
<%= link_to scenario.name, scenario_path(scenario), class: "font-serif font-medium text-lg text-ink hover:text-brand" %>
- <%= scenario.total_giving_amount.present? ? number_to_currency(scenario.total_giving_amount, precision: 0) : "No total set" %>
+ <%= scenario.total_giving_amount.present? ? number_to_currency(scenario.total_giving_amount) : "No total set" %>
<%= link_to "Open", scenario_path(scenario), class: "text-brand hover:underline" %>
diff --git a/app/views/scenarios/total_giving_amounts/_form.html.erb b/app/views/scenarios/total_giving_amounts/_form.html.erb
index 103723a..51fe458 100644
--- a/app/views/scenarios/total_giving_amounts/_form.html.erb
+++ b/app/views/scenarios/total_giving_amounts/_form.html.erb
@@ -6,9 +6,8 @@
<%= form.label :total_giving_amount, "Total giving amount", class: "w-40 text-ink-soft" %>
$
- <%= form.number_field :total_giving_amount, step: "1", min: 0, autofocus: true, placeholder: "0",
- inputmode: "numeric",
- data: { controller: "integer-input", action: "input->integer-input#transform" },
+ <%= form.number_field :total_giving_amount, value: scenario.total_giving_amount&.to_i, step: "1", min: 0, autofocus: true, placeholder: "0",
+ inputmode: "decimal",
class: "block w-full rounded-md border border-line bg-surface-soft pl-7 pr-3 py-2 shadow-sm focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/10" %>
diff --git a/app/views/scenarios/total_giving_amounts/_total_giving_amount.html.erb b/app/views/scenarios/total_giving_amounts/_total_giving_amount.html.erb
index 6f59843..5463890 100644
--- a/app/views/scenarios/total_giving_amounts/_total_giving_amount.html.erb
+++ b/app/views/scenarios/total_giving_amounts/_total_giving_amount.html.erb
@@ -5,7 +5,7 @@
Total giving amount
<% if scenario.total_giving_amount.present? %>
<%= link_to edit_scenario_total_giving_amount_path(scenario), class: "group inline-flex items-center gap-2" do %>
-
<%= number_to_currency(scenario.total_giving_amount, precision: 0) %>
+
<%= number_to_currency(scenario.total_giving_amount) %>