From bd132ce477ecfe272d1415112e3b0b66a5a68e99 Mon Sep 17 00:00:00 2001 From: Daniel James Date: Sat, 4 Jan 2025 12:01:34 +0530 Subject: [PATCH 1/2] Store country band details in database --- app/controllers/country_bands_controller.rb | 2 +- app/models/country_band.rb | 39 +---------------- app/models/country_band_detail.rb | 6 +++ app/views/country_bands/_show_band.html.erb | 8 ++-- app/views/country_bands/index.html.erb | 4 +- ...41225030959_create_country_band_details.rb | 14 +++++++ ...225031242_populate_country_band_details.rb | 42 +++++++++++++++++++ db/schema.rb | 12 +++++- lib/dues_calculator.rb | 10 +++-- 9 files changed, 87 insertions(+), 50 deletions(-) create mode 100644 app/models/country_band_detail.rb create mode 100644 db/migrate/20241225030959_create_country_band_details.rb create mode 100644 db/migrate/20241225031242_populate_country_band_details.rb diff --git a/app/controllers/country_bands_controller.rb b/app/controllers/country_bands_controller.rb index 1ba7f09fc3b..6ea3937743b 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.distinct.pluck(:number).include?(@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..43d10915a97 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 00000000000..efd46e25a22 --- /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 ec254896396..ca800f0da2b 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 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..ad16e102575 --- /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 00000000000..f87ce794eb4 --- /dev/null +++ b/db/migrate/20241225031242_populate_country_band_details.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +class PopulateCountryBandDetails < ActiveRecord::Migration[7.2] + def change + 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 +end diff --git a/db/schema.rb b/db/schema.rb index 76cf2d624c5..cde87543bfd 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/lib/dues_calculator.rb b/lib/dues_calculator.rb index 536d807c6fe..5d4b381be78 100644 --- a/lib/dues_calculator.rb +++ b/lib/dues_calculator.rb @@ -24,10 +24,12 @@ def self.dues_per_competitor_in_usd(country_iso2, base_entry_fee_lowest_denomina 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") + country_band_detail = CountryBandDetail.find_by(number: country_band) + return nil unless country_band_detail + + registration_fee_dues_us_dollars = input_money_us_dollars * country_band_detail.due_percent_registration_fee.to_f/100 + # cent is given directly because Money require lowest currency subunit, which is cents for USD + country_band_dues_us_dollars_money = Money.new(country_band_detail.due_amount_per_competitor_in_cents, "USD") [registration_fee_dues_us_dollars, country_band_dues_us_dollars_money].max rescue Money::Currency::UnknownCurrency, CurrencyUnavailable From e28d6cce7b428305069dc72fefbe0d2cece86f20 Mon Sep 17 00:00:00 2001 From: Daniel James Date: Wed, 8 Jan 2025 07:23:31 +0530 Subject: [PATCH 2/2] Review changes --- app/controllers/country_bands_controller.rb | 2 +- ...225031242_populate_country_band_details.rb | 6 ++- db/seeds/country_band_details.seeds.rb | 38 +++++++++++++++++++ lib/database_dumper.rb | 14 +++++++ lib/dues_calculator.rb | 15 ++++---- spec/models/country_band_spec.rb | 2 +- spec/support/test_db_manager.rb | 1 + 7 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 db/seeds/country_band_details.seeds.rb diff --git a/app/controllers/country_bands_controller.rb b/app/controllers/country_bands_controller.rb index 6ea3937743b..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 CountryBandDetail.distinct.pluck(:number).include?(@number) + unless CountryBandDetail.exists?(number: @number) flash[:danger] = "Unknown band number" return redirect_to country_bands_path end diff --git a/db/migrate/20241225031242_populate_country_band_details.rb b/db/migrate/20241225031242_populate_country_band_details.rb index f87ce794eb4..9aec86245af 100644 --- a/db/migrate/20241225031242_populate_country_band_details.rb +++ b/db/migrate/20241225031242_populate_country_band_details.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class PopulateCountryBandDetails < ActiveRecord::Migration[7.2] - def change + def up CountryBandDetail.create!( number: 0, start_date: '2018-01-01', @@ -39,4 +39,8 @@ def change due_percent_registration_fee: 15, ) end + + def down + CountryBandDetail.delete_all + end end diff --git a/db/seeds/country_band_details.seeds.rb b/db/seeds/country_band_details.seeds.rb new file mode 100644 index 00000000000..b36bc9813b9 --- /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 644cb64f34b..17b59c5961f 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 5d4b381be78..ce09592f2c2 100644 --- a/lib/dues_calculator.rb +++ b/lib/dues_calculator.rb @@ -20,18 +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") - country_band_detail = CountryBandDetail.find_by(number: country_band) - return nil unless country_band_detail + # 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 = input_money_us_dollars * country_band_detail.due_percent_registration_fee.to_f/100 - # cent is given directly because Money require lowest currency subunit, which is cents for USD - country_band_dues_us_dollars_money = Money.new(country_band_detail.due_amount_per_competitor_in_cents, "USD") + # Calculation of 'country band dues' + country_band_dues = Money.new(country_band_detail&.due_amount_per_competitor_in_cents || 0, "USD") - [registration_fee_dues_us_dollars, country_band_dues_us_dollars_money].max + # 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..882a162874b 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 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