Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic auto-accept for paid registrations #10381

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion app/models/competition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ class Competition < ApplicationRecord
event_change_deadline_date
competition_series_id
registration_version
auto_accept_registrations
auto_accept_disable_threshold
).freeze
VALID_NAME_RE = /\A([-&.:' [:alnum:]]+) (\d{4})\z/
VALID_ID_RE = /\A[a-zA-Z0-9]+\Z/
Expand Down Expand Up @@ -353,6 +355,17 @@ def advancement_condition_must_be_present_for_all_non_final_rounds
end
end

validate :auto_accept_validations
private def auto_accept_validations
errors.add(:auto_accept_registrations, I18n.t('competitions.errors.must_use_wca_registration')) if
auto_accept_registrations && !use_wca_registration

errors.add(:auto_accept_registrations, I18n.t('competitions.errors.auto_accept_limit')) if
auto_accept_disable_threshold > 0 && competitor_limit.present? && auto_accept_disable_threshold >= competitor_limit

errors.add(:auto_accept_registrations, I18n.t('competitions.errors.auto_accept_not_negative')) if auto_accept_disable_threshold < 0
end

def has_any_round_per_event?
competition_events.map(&:rounds).none?(&:empty?)
end
Expand Down Expand Up @@ -1850,7 +1863,7 @@ def to_competition_info
base_entry_fee_lowest_denomination currency_code allow_registration_edits allow_registration_self_delete_after_acceptance
allow_registration_without_qualification refund_policy_percent use_wca_registration guests_per_registration_limit venue contact
force_comment_in_registration use_wca_registration external_registration_page guests_entry_fee_lowest_denomination guest_entry_status
information events_per_registration_limit guests_enabled],
information events_per_registration_limit guests_enabled auto_accept_registration auto_accept_disable_threshold],
methods: %w[url website short_name city venue_address venue_details latitude_degrees longitude_degrees country_iso2 event_ids registration_currently_open?
main_event_id number_of_bookmarks using_payment_integrations? uses_qualification? uses_cutoff? competition_series_ids registration_full?
part_of_competition_series?],
Expand Down Expand Up @@ -2374,6 +2387,8 @@ def to_form_data
"guestsPerRegistration" => guests_per_registration_limit,
"extraRequirements" => extra_registration_requirements,
"forceComment" => force_comment_in_registration,
"autoAcceptEnabled" => auto_accept_registrations,
"autoAcceptDisableThreshold" => auto_accept_disable_threshold,
},
"eventRestrictions" => {
"forbidNewcomers" => {
Expand Down Expand Up @@ -2475,6 +2490,8 @@ def form_errors
"guestsPerRegistration" => errors[:guests_per_registration_limit],
"extraRequirements" => errors[:extra_registration_requirements],
"forceComment" => errors[:force_comment_in_registration],
"autoAcceptEnabled" => errors[:auto_accept_registrations],
"autoAcceptDisableThreshold" => errors[:auto_accept_disable_threshold],
},
"eventRestrictions" => {
"forbidNewcomers" => {
Expand Down Expand Up @@ -2606,6 +2623,8 @@ def self.form_data_to_attributes(form_data)
showAtAll: form_data.dig('admin', 'isVisible'),
being_cloned_from_id: form_data.dig('cloning', 'fromId'),
clone_tabs: form_data.dig('cloning', 'cloneTabs'),
auto_accept_registrations: form_data.dig('registration', 'autoAcceptEnabled'),
auto_accept_disable_threshold: form_data.dig('registration', 'autoAcceptDisableThreshold'),
}
end

Expand Down Expand Up @@ -2789,6 +2808,8 @@ def self.form_data_json_schema
"guestsPerRegistration" => { "type" => ["integer", "null"] },
"extraRequirements" => { "type" => ["string", "null"] },
"forceComment" => { "type" => ["boolean", "null"] },
"autoAcceptEnabled" => { "type" => ["boolean", "null"] },
"autoAcceptDisableThreshold" => { "type" => ["integer", "null"] },
},
},
"eventRestrictions" => {
Expand Down Expand Up @@ -2845,4 +2866,8 @@ def self.form_data_json_schema
},
}
end

def auto_accept_threshold_reached?
auto_accept_disable_threshold > 0 && auto_accept_disable_threshold <= registrations.competing_status_accepted.count
end
end
23 changes: 23 additions & 0 deletions app/models/registration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,13 @@ def self.accepted_and_paid_pending_count
end
end

validate :does_not_exceed_competitor_limit
private def does_not_exceed_competitor_limit
return unless competition&.competitor_limit.present?
errors.add(:competitor_limit, I18n.t('registrations.errors.competitor_limit_reached')) if
competition.registrations.competing_status_accepted.count >= competition.competitor_limit
end

# TODO: V3-REG cleanup. All these Validations can be used instead of the registration_checker checks
validate :cannot_be_undeleted_when_banned, if: :competing_status_changed?
private def cannot_be_undeleted_when_banned
Expand Down Expand Up @@ -428,4 +435,20 @@ def series_registration_info
def serializable_hash(options = nil)
super(DEFAULT_SERIALIZE_OPTIONS.merge(options || {}))
end

def auto_accept
return log_error('Auto-accept is not enabled for this competition.') unless competition.auto_accept_registrations
return log_error('Can only auto-accept pending registrations') unless competing_status_pending?
return log_error("Competition has reached auto_accept_disable_threshold of #{competition.auto_accept_disable_threshold} registrations") if
competition.auto_accept_threshold_reached?
return log_error('Competitor still has outstanding registration fees') if outstanding_entry_fees > 0
return log_error('Cant auto-accept while registration is not open') if !competition.registration_currently_open?

update(competing_status: Registrations::Helper::STATUS_ACCEPTED)
end

private def log_error(error)
Rails.logger.error(error)
false
end
end
6 changes: 6 additions & 0 deletions app/models/registration_payment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class RegistrationPayment < ApplicationRecord
belongs_to :refunded_registration_payment, class_name: 'RegistrationPayment', optional: true
has_many :refunding_registration_payments, class_name: 'RegistrationPayment', inverse_of: :refunded_registration_payment, foreign_key: :refunded_registration_payment_id, dependent: :destroy

after_create :attempt_auto_accept

monetize :amount_lowest_denomination,
as: "amount",
allow_nil: true,
Expand All @@ -26,4 +28,8 @@ def payment_status
receipt.determine_wca_status
end
end

private def attempt_auto_accept
registration&.auto_accept
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export default function RegistrationDetails() {

return (
<SubSection section="registration">
<InputBooleanSelect id="autoAcceptEnabled" required />
<ConditionalSection showIf={registration.autoAcceptEnabled}>
<InputNumber id="autoAcceptDisableThreshold" />
</ConditionalSection>
<InputDate id="waitingListDeadlineDate" dateTime required />
<InputDate id="eventChangeDeadlineDate" dateTime required />
<InputBooleanSelect id="allowOnTheSpot" required />
Expand Down
11 changes: 11 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,7 @@ en:
registrations:
#context: and when an error is found
errors:
competitor_limit_reached: "The competition is full."
need_name: "Need a name"
need_gender: "Need a gender"
need_dob: "Need a birthdate"
Expand Down Expand Up @@ -1657,6 +1658,8 @@ en:
refund_policy_percent: "Percent to be refunded"
refund_policy_limit_date: "Limit date for refunds"
registration:
auto_accept_enabled: "Automatic registration acceptance"
auto_accept_disable_threshold: "Limit for automatically accepting registrations"
opening_date_time: "Registration open"
closing_date_time: "Registration close"
waiting_list_deadline_date: "Deadline for accepting competitors from the waiting list"
Expand Down Expand Up @@ -1742,6 +1745,8 @@ en:
refund_policy_percent: "For now this number is informational only, refunds will have to be issued manually. If this is set to 0, a sentence will be displayed saying that registration fees won't be refunded under any circumstance."
refund_policy_limit_date: "Date after which no more refunds will be issued."
registration:
auto_accept_enabled: "This will automatically accept any user who registers and pays. Use at your own risk, and set a disable threshold to have control over the final registrations which get accepted."
auto_accept_disable_threshold: "When this number of accepted registrations is reached, auto-accept will disable itself - allowing organizers to manually approve the final registrations. Must be less than the competitor limit."
opening_date_time: "Note: Registration open and close are in UTC time"
closing_date_time: ""
waiting_list_deadline_date: "The date when waitlisted registrants will no longer be accepted. If you have a deadline for refunds, the deadline for being accepted should not be before the deadline for refunds."
Expand Down Expand Up @@ -1783,6 +1788,9 @@ en:
"true": "I want to specify a competitor limit"
"false": "I DON'T WANT to specify a competitor limit"
registration:
auto_accept_enabled:
"true": "Automatically accept users once they have paid"
"false": "Do not auto-accept users - registrations must be manually accepted"
allow_on_the_spot:
"true": "On the spot registrations will be accepted"
"false": "On the spot registrations WILL NOT be accepted"
Expand Down Expand Up @@ -1925,6 +1933,9 @@ en:
-6002: "You need to finish your registration before you can pay"
#context: and when an error occured
errors:
must_use_wca_registration: "Auto-accept can only be used if you are using the WCA website for registrations"
auto_accept_limit: "Limit for auto-accepted registrations must be less than the competitor limit"
auto_accept_not_negative: "Limit for auto-accepted registrations cannot be less than 0."
invalid_name_message: "must end with a year and must contain only alphanumeric characters, dashes(-), ampersands(&), periods(.), colons(:), apostrophes('), and spaces( )"
cannot_manage: "Cannot manage competition."
cannot_delete_public: "Cannot delete a competition that is publicly visible."
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

class AddAutoAcceptToCompetitionsTable < ActiveRecord::Migration[7.2]
def change
# TODO: Ask ChatGPT if these look fine
add_column :Competitions, :auto_accept_registrations, :boolean, default: false, null: false
add_column :Competitions, :auto_accept_disable_threshold, :integer, default: 0, null: false # TODO: Add validation that this can't be > competitor_limit
end
end
4 changes: 3 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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_07_210016) 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
Expand Down Expand Up @@ -82,6 +82,8 @@
t.boolean "forbid_newcomers", default: false, null: false
t.string "forbid_newcomers_reason"
t.integer "registration_version", default: 0, null: false
t.boolean "auto_accept_registrations", default: false, null: false
t.integer "auto_accept_disable_threshold", default: 0, null: false
t.index ["cancelled_at"], name: "index_Competitions_on_cancelled_at"
t.index ["countryId"], name: "index_Competitions_on_countryId"
t.index ["end_date"], name: "index_Competitions_on_end_date"
Expand Down
2 changes: 2 additions & 0 deletions lib/database_dumper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ def self.actions_to_column_sanitizers(columns_by_action)
registration_version
forbid_newcomers
forbid_newcomers_reason
auto_accept_registrations
auto_accept_disable_threshold
),
db_default: %w(
connected_stripe_account_id
Expand Down
5 changes: 5 additions & 0 deletions spec/factories/competitions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@

registration_version { :v3 }

trait :auto_accept do
use_wca_registration { true }
auto_accept_registrations { true }
end

trait :enforces_qualifications do
with_organizer
qualification_results { true }
Expand Down
23 changes: 22 additions & 1 deletion spec/factories/registration_payment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@

FactoryBot.define do
factory :registration_payment do
amount_lowest_denomination { 0 }
transient do
registration { nil }
competition { nil }
end

registration_id { registration&.id }
user_id { registration&.user_id }
amount_lowest_denomination { competition&.base_entry_fee_lowest_denomination }
currency_code { competition&.currency_code }

trait :refund do
amount_lowest_denomination { -competition.base_entry_fee_lowest_denomination }
end

trait :with_donation do
amount_lowest_denomination { competition.base_entry_fee_lowest_denomination*2 }
end

trait :skip_create_hook do
after(:build) { |payment| payment.class.skip_callback(:create, :after, :attempt_auto_accept) }
after(:create) { |payment| payment.class.set_callback(:create, :after, :attempt_auto_accept) }
end
end
end
2 changes: 1 addition & 1 deletion spec/lib/registrations/registration_checker_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1331,7 +1331,7 @@
it 'organizer cant accept a user when registration list is over full' do
competitor_limit = FactoryBot.create(:competition, :with_competitor_limit, :with_organizer, competitor_limit: 3)
limited_reg = FactoryBot.create(:registration, competition: competitor_limit)
FactoryBot.create_list(:registration, 4, :accepted, competition: competitor_limit)
FactoryBot.create_list(:registration, 4, :accepted, :skip_validations, competition: competitor_limit)

update_request = FactoryBot.build(
:update_request,
Expand Down
35 changes: 35 additions & 0 deletions spec/models/competition_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1583,4 +1583,39 @@ def change_and_check_activities(new_start_date, new_end_date)
expect(new_competition).not_to be_valid
end
end

describe "validate auto accept fields" do
let(:auto_accept_comp) { FactoryBot.build(:competition, :auto_accept) }

it 'cant enable auto-accept if not using WCA registration' do
auto_accept_comp.use_wca_registration = false
expect(auto_accept_comp).not_to be_valid
expect(auto_accept_comp.errors[:auto_accept_registrations]).to include("Auto-accept can only be used if you are using the WCA website for registrations")
end

it 'disable threshold cant exceed competitor limit' do
auto_accept_comp.competitor_limit = 100
auto_accept_comp.auto_accept_disable_threshold = 101
expect(auto_accept_comp).not_to be_valid
expect(auto_accept_comp.errors[:auto_accept_registrations]).to include("Limit for auto-accepted registrations must be less than the competitor limit")
end

it 'disable threshld must be less than competitor limit' do
auto_accept_comp.competitor_limit = 100
auto_accept_comp.auto_accept_disable_threshold = 100
expect(auto_accept_comp).not_to be_valid
expect(auto_accept_comp.errors[:auto_accept_registrations]).to include("Limit for auto-accepted registrations must be less than the competitor limit")
end

it 'disable threshold may be 0' do
auto_accept_comp.auto_accept_disable_threshold = 0
expect(auto_accept_comp).to be_valid
end

it 'disable threshold may not be be less than 0' do
auto_accept_comp.auto_accept_disable_threshold = -1
expect(auto_accept_comp).not_to be_valid
expect(auto_accept_comp.errors[:auto_accept_registrations]).to include("Limit for auto-accepted registrations cannot be less than 0.")
end
end
end
Loading
Loading