diff --git a/app/controllers/event_registrations_controller.rb b/app/controllers/event_registrations_controller.rb index 8bba54a91c..fa8013af25 100644 --- a/app/controllers/event_registrations_controller.rb +++ b/app/controllers/event_registrations_controller.rb @@ -273,7 +273,9 @@ def unlink_organization def destroy authorize! @event_registration event = @event_registration.event - if @event_registration.destroy + if !@event_registration.deletable? + flash[:alert] = "This registration can't be deleted because it has payments, scholarships, or attendance on record." + elsif @event_registration.destroy flash[:notice] = "Registration deleted." else flash[:alert] = @event_registration.errors.full_messages.to_sentence diff --git a/app/controllers/events/registrations_controller.rb b/app/controllers/events/registrations_controller.rb index 1d62daecf0..a60fa45320 100644 --- a/app/controllers/events/registrations_controller.rb +++ b/app/controllers/events/registrations_controller.rb @@ -125,6 +125,15 @@ def destroy authorize! @event_registration + unless @event_registration.deletable? + alert = "You can't de-register because there are payments or attendance on record. Cancel your registration instead." + respond_to do |format| + format.turbo_stream { flash.now[:alert] = alert } + format.html { redirect_to @event, alert: alert } + end + return + end + if @event_registration.destroy success = "You are no longer registered." respond_to do |format| diff --git a/app/models/event_registration.rb b/app/models/event_registration.rb index 63e0ca6ebb..029ee37fbf 100644 --- a/app/models/event_registration.rb +++ b/app/models/event_registration.rb @@ -195,6 +195,14 @@ def active? status.in?(ACTIVE_STATUSES) end + # Safe to delete only when removing the record would not orphan financial + # data or erase attendance history. Allocations (payments and scholarships) + # have no dependent: :destroy, so a registration with any allocation must be + # kept, as must one with attendance (attended or incomplete) on record. + def deletable? + !allocations.exists? && !status.in?(%w[ attended incomplete_attendance ]) + end + def checked_in? # checked_in_at.present? end diff --git a/app/views/event_registrations/_form.html.erb b/app/views/event_registrations/_form.html.erb index 13a1091ad2..da55f3db5b 100644 --- a/app/views/event_registrations/_form.html.erb +++ b/app/views/event_registrations/_form.html.erb @@ -432,7 +432,7 @@ <%# ---- Footer actions ---- %>
- <% if allowed_to?(:destroy?, f.object) %> + <% if allowed_to?(:destroy?, f.object) && f.object.deletable? %> <%= link_to event_registration_path(@event_registration, return_to: params[:return_to].presence), class: "btn btn-danger-outline", data: { turbo: true, turbo_method: :delete, turbo_confirm: "Are you sure you want to delete?" } do %> diff --git a/app/views/events/_registration_button.html.erb b/app/views/events/_registration_button.html.erb index 1fe235fe6f..f3c1636eca 100644 --- a/app/views/events/_registration_button.html.erb +++ b/app/views/events/_registration_button.html.erb @@ -7,11 +7,13 @@ No event <% elsif Current.user.nil? %> Login to register - <% elsif event.actively_registered?(Current.user.person) %> - <%= link_to "De-register", - event_registrant_registration_path(event_id: event), - data: { turbo_method: :delete, turbo_confirm: "Are you sure?"}, - class: "btn btn-secondary-outline text-sm" %> + <% elsif (registration = event.active_registration_for(Current.user.person)) %> + <% if registration.deletable? %> + <%= link_to "De-register", + event_registrant_registration_path(event_id: event), + data: { turbo_method: :delete, turbo_confirm: "Are you sure?"}, + class: "btn btn-secondary-outline text-sm" %> + <% end %> Registered <% elsif event.ended? %> Event ended diff --git a/spec/models/event_registration_spec.rb b/spec/models/event_registration_spec.rb index 9fdc1ef1b8..1b92cc6724 100644 --- a/spec/models/event_registration_spec.rb +++ b/spec/models/event_registration_spec.rb @@ -50,6 +50,36 @@ end end + describe "#deletable?" do + it "returns true for a plain registration with no allocations or attendance" do + reg = create(:event_registration, status: "registered") + expect(reg).to be_deletable + end + + it "returns false when the registration has a payment allocation" do + reg = create(:event_registration, status: "registered") + payment = create(:payment, person: reg.registrant, amount_cents: 1000, amount_cents_remaining: nil) + create(:allocation, source: payment, allocatable: reg, amount: 1000) + expect(reg).not_to be_deletable + end + + it "returns false when the registration has a scholarship allocation" do + reg = create(:event_registration, status: "registered") + scholarship = create(:scholarship, recipient: reg.registrant, amount_cents: 1000) + create(:allocation, source: scholarship, allocatable: reg, amount: 1000) + expect(reg).not_to be_deletable + end + + it "returns false when the registration has attendance on record" do + expect(create(:event_registration, status: "attended")).not_to be_deletable + expect(create(:event_registration, status: "incomplete_attendance")).not_to be_deletable + end + + it "returns true for a cancelled registration with no allocations" do + expect(create(:event_registration, status: "cancelled")).to be_deletable + end + end + describe ".registrant_ids" do it "returns registrations for the registrants in a hyphenated id list" do person_a = create(:person) diff --git a/spec/requests/event_registrations_spec.rb b/spec/requests/event_registrations_spec.rb index cbbf84ab23..bb8ae52abc 100644 --- a/spec/requests/event_registrations_spec.rb +++ b/spec/requests/event_registrations_spec.rb @@ -328,6 +328,17 @@ def unrequest(registration) delete event_registration_path(existing_registration) }.to change(EventRegistration, :count).by(-1) end + + it "refuses to delete a registration with payments on record" do + payment = create(:payment, person: regular_user.person, amount_cents: 1000, amount_cents_remaining: nil) + create(:allocation, source: payment, allocatable: existing_registration, amount: 1000) + + expect { + delete event_registration_path(existing_registration) + }.not_to change(EventRegistration, :count) + + expect(flash[:alert]).to include("can't be deleted") + end end describe "organization linking" do diff --git a/spec/requests/events/registrations_spec.rb b/spec/requests/events/registrations_spec.rb index a425630c06..12b07e58ea 100644 --- a/spec/requests/events/registrations_spec.rb +++ b/spec/requests/events/registrations_spec.rb @@ -586,6 +586,24 @@ end end + context "when the registration has payments on record" do + let!(:registration) { create(:event_registration, event: event, registrant: user.person) } + + before do + payment = create(:payment, person: user.person, amount_cents: 1000, amount_cents_remaining: nil) + create(:allocation, source: payment, allocatable: registration, amount: 1000) + end + + it "refuses to destroy and returns an alert" do + expect { + delete event_registrant_registration_path(event_id: event.id), + headers: turbo_headers + }.not_to change(EventRegistration, :count) + + expect(flash.now[:alert]).to include("Cancel your registration instead") + end + end + context "when destroy fails" do let!(:registration) { create(:event_registration, event: event, registrant: user.person) }