diff --git a/app/controllers/country_bands_controller.rb b/app/controllers/country_bands_controller.rb index 1ba7f09fc3b..5307169b01d 100644 --- a/app/controllers/country_bands_controller.rb +++ b/app/controllers/country_bands_controller.rb @@ -10,7 +10,7 @@ def index def edit @number = id_from_params - unless CountryBand::BANDS.keys.include?(@number) + unless CountryBandDetail.exists?(number: @number) flash[:danger] = "Unknown band number" return redirect_to country_bands_path end diff --git a/app/models/country_band.rb b/app/models/country_band.rb index 1e713a972a8..57813148ab6 100644 --- a/app/models/country_band.rb +++ b/app/models/country_band.rb @@ -1,48 +1,20 @@ # frozen_string_literal: true class CountryBand < ApplicationRecord - BANDS = { - 0 => { - value: 0.00, - }, - 1 => { - value: 0.19, - }, - 2 => { - value: 0.32, - }, - 3 => { - value: 0.45, - }, - 4 => { - value: 2.28, - }, - 5 => { - value: 3.00, - }, - }.freeze - - # According to WCA's current dues policy, the due amount per competitor is equivalent - # to this percent of registration fee. Only used if this due amount per competitor is - # larger than the due amount per competitor calculated from the competition's country band. - PERCENT_REGISTRATION_FEE_USED_FOR_DUE_AMOUNT = 0.15 - - def self.percent_registration_fee_used_for_due_amount(country_band) - return 0 if country_band.nil? - if country_band >= 3 - 0.15 - elsif country_band >= 1 - 0.05 - else - 0.00 - end - end - belongs_to :country, foreign_key: :iso2, primary_key: :iso2 + has_many :country_band_details, foreign_key: :number, primary_key: :number validates_inclusion_of :iso2, in: Country::WCA_COUNTRY_ISO_CODES - validates_inclusion_of :number, in: BANDS.keys.freeze + validates :number, numericality: { + only_integer: true, + greater_than_or_equal_to: 0, + less_than_or_equal_to: 5, + } def country Country.find_by_iso2(self.iso2) end + + def active_country_band_detail + country_band_details.active.first + end end diff --git a/app/models/country_band_detail.rb b/app/models/country_band_detail.rb new file mode 100644 index 00000000000..36b11f08769 --- /dev/null +++ b/app/models/country_band_detail.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class CountryBandDetail < ApplicationRecord + scope :active, -> { where(end_date: nil).or(where.not(end_date: ..Date.today)) } + scope :inactive, -> { where(end_date: ..Date.today) } +end diff --git a/app/views/country_bands/_show_band.html.erb b/app/views/country_bands/_show_band.html.erb index ec254896396..cf2c0070db1 100644 --- a/app/views/country_bands/_show_band.html.erb +++ b/app/views/country_bands/_show_band.html.erb @@ -1,12 +1,12 @@
- <%= t("country_bands.band_title", number: number) %> - (<%= t("country_bands.due_value", value: data[:value]) %>) + <%= t("country_bands.band_title", number: country_band_detail.number) %> + (<%= t("country_bands.due_value", value: country_band_detail.due_amount_per_competitor_us_cents / 100.0) %>) <% if current_user&.can_admin_finances? %> - <%= link_to(ui_icon("pencil alt"), edit_country_band_path(id: number)) %> + <%= link_to(ui_icon("pencil alt"), edit_country_band_path(id: country_band_detail.number)) %> <% end %>
- <% (@country_bands_by_number[number] || []).map(&:country).sort_by(&:name).each do |c| %> + <% (@country_bands_by_number[country_band_detail.number] || []).map(&:country).sort_by(&:name).each do |c| %>
<%= flag_icon c.iso2 %>
diff --git a/app/views/country_bands/index.html.erb b/app/views/country_bands/index.html.erb index 395439736ab..3bb3eeb37d2 100644 --- a/app/views/country_bands/index.html.erb +++ b/app/views/country_bands/index.html.erb @@ -14,8 +14,8 @@
- <% CountryBand::BANDS.each do |number, data| %> - <%= render "show_band", number: number, data: data %> + <% CountryBandDetail.active.order(:number).each do |country_band_detail| %> + <%= render "show_band", country_band_detail: country_band_detail %> <% end %>
diff --git a/db/migrate/20241225030959_create_country_band_details.rb b/db/migrate/20241225030959_create_country_band_details.rb new file mode 100644 index 00000000000..0814f793fc2 --- /dev/null +++ b/db/migrate/20241225030959_create_country_band_details.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class CreateCountryBandDetails < ActiveRecord::Migration[7.2] + def change + create_table :country_band_details do |t| + t.integer "number", null: false + t.date "start_date", null: false + t.date "end_date" + t.integer "due_amount_per_competitor_us_cents", null: false + t.integer "due_percent_registration_fee", null: false + t.timestamps + end + end +end diff --git a/db/migrate/20241225031242_populate_country_band_details.rb b/db/migrate/20241225031242_populate_country_band_details.rb new file mode 100644 index 00000000000..b747daa567e --- /dev/null +++ b/db/migrate/20241225031242_populate_country_band_details.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class PopulateCountryBandDetails < ActiveRecord::Migration[7.2] + def up + CountryBandDetail.create!( + number: 0, + start_date: '2018-01-01', + due_amount_per_competitor_us_cents: 0, + due_percent_registration_fee: 0, + ) + CountryBandDetail.create!( + number: 1, + start_date: '2018-01-01', + due_amount_per_competitor_us_cents: 19, + due_percent_registration_fee: 5, + ) + CountryBandDetail.create!( + number: 2, + start_date: '2018-01-01', + due_amount_per_competitor_us_cents: 32, + due_percent_registration_fee: 5, + ) + CountryBandDetail.create!( + number: 3, + start_date: '2018-01-01', + due_amount_per_competitor_us_cents: 45, + due_percent_registration_fee: 15, + ) + CountryBandDetail.create!( + number: 4, + start_date: '2018-01-01', + due_amount_per_competitor_us_cents: 228, + due_percent_registration_fee: 15, + ) + CountryBandDetail.create!( + number: 5, + start_date: '2018-01-01', + due_amount_per_competitor_us_cents: 300, + due_percent_registration_fee: 15, + ) + end + + def down + CountryBandDetail.delete_all + end +end diff --git a/db/schema.rb b/db/schema.rb index a6b0e93779f..96499d9a8aa 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2025_02_02_154917) do +ActiveRecord::Schema[7.2].define(version: 2025_01_24_154917) do create_table "Competitions", id: { type: :string, limit: 32, default: "" }, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.string "name", limit: 50, default: "", null: false t.string "cityName", limit: 50, default: "", null: false @@ -665,6 +665,16 @@ t.datetime "updated_at", null: false end + create_table "country_band_details", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| + t.integer "number", null: false + t.date "start_date", null: false + t.date "end_date" + t.integer "due_amount_per_competitor_us_cents", null: false + t.integer "due_percent_registration_fee", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "country_bands", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.integer "number", null: false t.string "iso2", limit: 2, null: false diff --git a/db/seeds/country_band_details.seeds.rb b/db/seeds/country_band_details.seeds.rb new file mode 100644 index 00000000000..394914873fe --- /dev/null +++ b/db/seeds/country_band_details.seeds.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +CountryBandDetail.create!( + number: 0, + start_date: '2018-01-01', + due_amount_per_competitor_us_cents: 0, + due_percent_registration_fee: 0, +) +CountryBandDetail.create!( + number: 1, + start_date: '2018-01-01', + due_amount_per_competitor_us_cents: 19, + due_percent_registration_fee: 5, +) +CountryBandDetail.create!( + number: 2, + start_date: '2018-01-01', + due_amount_per_competitor_us_cents: 32, + due_percent_registration_fee: 5, +) +CountryBandDetail.create!( + number: 3, + start_date: '2018-01-01', + due_amount_per_competitor_us_cents: 45, + due_percent_registration_fee: 15, +) +CountryBandDetail.create!( + number: 4, + start_date: '2018-01-01', + due_amount_per_competitor_us_cents: 228, + due_percent_registration_fee: 15, +) +CountryBandDetail.create!( + number: 5, + start_date: '2018-01-01', + due_amount_per_competitor_us_cents: 300, + due_percent_registration_fee: 15, +) diff --git a/lib/database_dumper.rb b/lib/database_dumper.rb index 0b438ba2e88..daa452540f1 100644 --- a/lib/database_dumper.rb +++ b/lib/database_dumper.rb @@ -857,6 +857,20 @@ def self.actions_to_column_sanitizers(columns_by_action) ), ), }.freeze, + "country_band_details" => { + column_sanitizers: actions_to_column_sanitizers( + copy: %w( + id + number + start_date + end_date + due_amount_per_competitor_us_cents + due_percent_registration_fee + created_at + updated_at + ), + ), + }.freeze, "user_roles" => { where_clause: "JOIN user_groups ON user_groups.id=group_id WHERE NOT user_groups.is_hidden", column_sanitizers: actions_to_column_sanitizers( diff --git a/lib/dues_calculator.rb b/lib/dues_calculator.rb index 85aaf1302ed..6bbf897d744 100644 --- a/lib/dues_calculator.rb +++ b/lib/dues_calculator.rb @@ -9,16 +9,20 @@ def self.dues_per_competitor(country_iso2, base_entry_fee_lowest_denomination, c end def self.dues_per_competitor_in_usd(country_iso2, base_entry_fee_lowest_denomination, currency_code) - country_band = CountryBand.find_by(iso2: country_iso2)&.number + country_band = CountryBand.find_by(iso2: country_iso2) + country_band_detail = country_band&.active_country_band_detail - input_money_us_dollars = Money.new(base_entry_fee_lowest_denomination, currency_code).exchange_to("USD") + return nil if country_band_detail.nil? - registration_fee_dues_us_dollars = input_money_us_dollars * CountryBand.percent_registration_fee_used_for_due_amount(country_band) - country_band_dues_us_dollars = country_band.present? && country_band > 0 ? CountryBand::BANDS[country_band][:value] : 0 - # times 100 because Money require lowest currency subunit, which is cents for USD - country_band_dues_us_dollars_money = Money.new(country_band_dues_us_dollars * 100, "USD") + # Calculation of 'registration fee dues' + registration_fees_in_usd = Money.new(base_entry_fee_lowest_denomination, currency_code).exchange_to("USD") + registration_fee_dues = registration_fees_in_usd * country_band_detail.due_percent_registration_fee / 100.0 - [registration_fee_dues_us_dollars, country_band_dues_us_dollars_money].max + # Calculation of 'country band dues' + country_band_dues = Money.new(country_band_detail.due_amount_per_competitor_us_cents, "USD") + + # The maximum of the two is the total dues per competitor + [registration_fee_dues, country_band_dues].max rescue Money::Currency::UnknownCurrency, CurrencyUnavailable nil end diff --git a/spec/models/country_band_spec.rb b/spec/models/country_band_spec.rb index 94507e454d3..9ef84d327a3 100644 --- a/spec/models/country_band_spec.rb +++ b/spec/models/country_band_spec.rb @@ -14,7 +14,7 @@ end it "invalidates band with invalid band id" do - cb = CountryBand.new(number: 6, iso2: "HELLO") - expect(cb).to be_invalid_with_errors(number: ["is not included in the list"]) + cb = CountryBand.new(number: 6, iso2: "IN") + expect(cb).to be_invalid_with_errors(number: ["must be less than or equal to 5"]) end end diff --git a/spec/support/test_db_manager.rb b/spec/support/test_db_manager.rb index 924cf1dacff..5bcb895c233 100644 --- a/spec/support/test_db_manager.rb +++ b/spec/support/test_db_manager.rb @@ -16,6 +16,7 @@ class TestDbManager groups_metadata_councils groups_metadata_teams_committees groups_metadata_translators + country_band_details ).freeze def self.fill_tables