diff --git a/app/models/grant.rb b/app/models/grant.rb index 330d600f2..489aab424 100644 --- a/app/models/grant.rb +++ b/app/models/grant.rb @@ -10,6 +10,7 @@ class Grant < ApplicationRecord validates :name, presence: true validates :amount_cents, numericality: { greater_than_or_equal_to: 0 } validates :donor_type, inclusion: { in: DONOR_TYPES } + validate :amount_covers_scholarships_already_issued scope :by_deadline, -> { order(Arel.sql("application_deadline IS NULL, application_deadline ASC")) } @@ -94,6 +95,18 @@ def task_list private + # A grant's amount can't be lowered below what has already been awarded against + # it — the scholarships are committed, so the grant must at least cover them. + # Mirrors Scholarship#within_grant_budget from the other side of the ledger. + def amount_covers_scholarships_already_issued + return unless amount_cents + + issued = scholarships_total_cents + if amount_cents < issued + errors.add(:amount_cents, "can't be less than the #{MoneyFormatter.dollars_from_cents(issued)} already awarded in scholarships") + end + end + def text_to_list(text) text.to_s.split("\n").map(&:strip).reject(&:blank?) end diff --git a/spec/models/grant_spec.rb b/spec/models/grant_spec.rb index 68888adb9..66eae6a3c 100644 --- a/spec/models/grant_spec.rb +++ b/spec/models/grant_spec.rb @@ -19,6 +19,37 @@ it "is valid with a person donor" do expect(build(:grant, :donated_by_person)).to be_valid end + + describe "amount cannot drop below scholarships already issued" do + it "is invalid when the amount is reduced below the total awarded" do + grant = create(:grant, amount_cents: 100_000) + create(:scholarship, grant:, amount_cents: 60_000) + + grant.amount_cents = 50_000 + expect(grant).not_to be_valid + expect(grant.errors[:amount_cents]).to include("can't be less than the $600 already awarded in scholarships") + end + + it "is valid when the amount equals the total awarded" do + grant = create(:grant, amount_cents: 100_000) + create(:scholarship, grant:, amount_cents: 60_000) + + grant.amount_cents = 60_000 + expect(grant).to be_valid + end + + it "is valid when the amount stays above the total awarded" do + grant = create(:grant, amount_cents: 100_000) + create(:scholarship, grant:, amount_cents: 60_000) + + grant.amount_cents = 70_000 + expect(grant).to be_valid + end + + it "is valid for a new grant with no scholarships" do + expect(build(:grant, amount_cents: 0)).to be_valid + end + end end describe "money accessors" do