diff --git a/app/controllers/country_bands_controller.rb b/app/controllers/country_bands_controller.rb
index 1ba7f09fc3..5307169b01 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 1e713a972a..43d10915a9 100644
--- a/app/models/country_band.rb
+++ b/app/models/country_band.rb
@@ -1,46 +1,9 @@
# 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
+ belongs_to :country_band_detail, foreign_key: :number, primary_key: :number
validates_inclusion_of :iso2, in: Country::WCA_COUNTRY_ISO_CODES
- validates_inclusion_of :number, in: BANDS.keys.freeze
def country
Country.find_by_iso2(self.iso2)
diff --git a/app/models/country_band_detail.rb b/app/models/country_band_detail.rb
new file mode 100644
index 0000000000..efd46e25a2
--- /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(inactive.invert_where) }
+ 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 ec25489639..ca800f0da2 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_in_cents.to_f / 100) %>)
<% 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 395439736a..3bb3eeb37d 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 0000000000..ad16e10257
--- /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_in_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 0000000000..9aec86245a
--- /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_in_cents: 0,
+ due_percent_registration_fee: 0,
+ )
+ CountryBandDetail.create!(
+ number: 1,
+ start_date: '2018-01-01',
+ due_amount_per_competitor_in_cents: 19,
+ due_percent_registration_fee: 5,
+ )
+ CountryBandDetail.create!(
+ number: 2,
+ start_date: '2018-01-01',
+ due_amount_per_competitor_in_cents: 32,
+ due_percent_registration_fee: 5,
+ )
+ CountryBandDetail.create!(
+ number: 3,
+ start_date: '2018-01-01',
+ due_amount_per_competitor_in_cents: 45,
+ due_percent_registration_fee: 15,
+ )
+ CountryBandDetail.create!(
+ number: 4,
+ start_date: '2018-01-01',
+ due_amount_per_competitor_in_cents: 228,
+ due_percent_registration_fee: 15,
+ )
+ CountryBandDetail.create!(
+ number: 5,
+ start_date: '2018-01-01',
+ due_amount_per_competitor_in_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 76cf2d624c..cde87543bf 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: 2024_11_24_050607) do
+ActiveRecord::Schema[7.2].define(version: 2024_12_25_031242) 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_in_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 0000000000..b36bc9813b
--- /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_in_cents: 0,
+ due_percent_registration_fee: 0,
+)
+CountryBandDetail.create!(
+ number: 1,
+ start_date: '2018-01-01',
+ due_amount_per_competitor_in_cents: 19,
+ due_percent_registration_fee: 5,
+)
+CountryBandDetail.create!(
+ number: 2,
+ start_date: '2018-01-01',
+ due_amount_per_competitor_in_cents: 32,
+ due_percent_registration_fee: 5,
+)
+CountryBandDetail.create!(
+ number: 3,
+ start_date: '2018-01-01',
+ due_amount_per_competitor_in_cents: 45,
+ due_percent_registration_fee: 15,
+)
+CountryBandDetail.create!(
+ number: 4,
+ start_date: '2018-01-01',
+ due_amount_per_competitor_in_cents: 228,
+ due_percent_registration_fee: 15,
+)
+CountryBandDetail.create!(
+ number: 5,
+ start_date: '2018-01-01',
+ due_amount_per_competitor_in_cents: 300,
+ due_percent_registration_fee: 15,
+)
diff --git a/lib/database_dumper.rb b/lib/database_dumper.rb
index 644cb64f34..17b59c5961 100644
--- a/lib/database_dumper.rb
+++ b/lib/database_dumper.rb
@@ -855,6 +855,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_in_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 536d807c6f..ce09592f2c 100644
--- a/lib/dues_calculator.rb
+++ b/lib/dues_calculator.rb
@@ -20,16 +20,19 @@ def self.dues_for_n_competitors(country_iso2, base_entry_fee_lowest_denomination
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_detail = CountryBandDetail.find_by(number: country_band)
+ registration_fees = Money.new(base_entry_fee_lowest_denomination, currency_code).exchange_to("USD")
DuesCalculator.update_exchange_rates_if_needed
- input_money_us_dollars = Money.new(base_entry_fee_lowest_denomination, currency_code).exchange_to("USD")
- 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_fee_dues = Money.new(registration_fees * (country_band_detail&.due_percent_registration_fee.to_f || 0) / 100, "USD")
- [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_in_cents || 0, "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 94507e454d..882a162874 100644
--- a/spec/models/country_band_spec.rb
+++ b/spec/models/country_band_spec.rb
@@ -15,6 +15,6 @@
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"])
+ expect(cb).to be_invalid_with_errors(country: ["must exist"])
end
end
diff --git a/spec/support/test_db_manager.rb b/spec/support/test_db_manager.rb
index 924cf1dacf..5bcb895c23 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