From 8d7834a5e36d310a37c1e3c7adf072a36ed39b99 Mon Sep 17 00:00:00 2001 From: Mae Beale Date: Sun, 30 Aug 2026 16:50:43 -0400 Subject: [PATCH 1/4] Add payment-method filter + status chips to the event bulk-payments dashboard Facilitators reconciling a "Pay for Others" payer couldn't tell at a glance who still had loose ends, or narrow the list to one payment method. Now each payer row shows how many of their attendees aren't registered or aren't paid, and the list filters by the method the payer chose. - FormSubmission.payment_method scope + PAYMENT_METHOD_FILTER_OPTIONS - Event bulk-payments dashboard: payment-method filter in a turbo frame so only the list swaps on change - Per-row chips ("N not registered" / "N unpaid"), query-free from the already-preloaded matches/allocation totals Co-Authored-By: Claude Opus 4.8 (1M context) --- .../events/bulk_payments_controller.rb | 5 +- app/models/form_submission.rb | 11 ++++ app/views/events/_bulk_payment_card.html.erb | 24 ++++++++ app/views/events/bulk_payments/index.html.erb | 57 +++++++++++++------ spec/requests/events/bulk_payments_spec.rb | 56 ++++++++++++++++++ 5 files changed, 134 insertions(+), 19 deletions(-) diff --git a/app/controllers/events/bulk_payments_controller.rb b/app/controllers/events/bulk_payments_controller.rb index 360f8168bc..f065f6d69b 100644 --- a/app/controllers/events/bulk_payments_controller.rb +++ b/app/controllers/events/bulk_payments_controller.rb @@ -5,11 +5,14 @@ class BulkPaymentsController < ApplicationController def index authorize! @event - track_view("events.bulk_payments", { event_id: @event.id }) + # Skip on the filter turbo-frame reload so filtering doesn't double-count views. + track_view("events.bulk_payments", { event_id: @event.id }) unless turbo_frame_request? + @payment_method = params[:payment_method].presence @event_registrations = @event.event_registrations.active.not_transferred_in.includes(:registrant) @submissions = @event.form_submissions .where(role: "bulk_payment") + .payment_method(@payment_method) .includes(:person, form_answers: :form_field, payment: :allocations) .order(created_at: :desc) @allocated_by_registration = allocated_cents_by_registration(@event_registrations) diff --git a/app/models/form_submission.rb b/app/models/form_submission.rb index f1cd964e25..9339076afa 100644 --- a/app/models/form_submission.rb +++ b/app/models/form_submission.rb @@ -58,6 +58,17 @@ class FormSubmission < ApplicationRecord end } + # The bulk payments "Payment method" filter vocabulary — the method the payer + # chose on the form (stored as the `payment_method` answer). Same options the + # form offers (FormBuilderService::PAYMENT_METHOD_OPTIONS), value == label. + PAYMENT_METHOD_FILTER_OPTIONS = FormBuilderService::PAYMENT_METHOD_OPTIONS.map { |method| [ method, method ] }.freeze + + scope :payment_method, ->(value) { + next all if value.blank? + joins(form_answers: :form_field) + .where(form_fields: { field_identifier: "payment_method" }, form_answers: { submitted_answer: value }) + } + # Submitted on `created_at` — there is no separate submitted_at column. scope :submitted_between, ->(start_date, end_date) { scope = all diff --git a/app/views/events/_bulk_payment_card.html.erb b/app/views/events/_bulk_payment_card.html.erb index 3f3251f96c..165e19da51 100644 --- a/app/views/events/_bulk_payment_card.html.erb +++ b/app/views/events/_bulk_payment_card.html.erb @@ -75,6 +75,30 @@ + <%# At-a-glance status of who this payer covers: attendees not matched to any + registration, and matched registrations not yet paid in full (allocated from + the preloaded totals so this stays query-free). %> + <% unregistered_count = attendee_matches.count { |attendee| attendee[:matches].empty? } + unpaid_count = attendee_matches.count { |attendee| + attendee[:matches].present? && attendee[:matches].none? { |reg| allocated_by_registration.fetch(reg.id, 0) >= event_cost_cents } + } %> + <% if unregistered_count.positive? || unpaid_count.positive? %> +
+ <% if unregistered_count.positive? %> + + + <%= pluralize(unregistered_count, "attendee") %> not registered + + <% end %> + <% if unpaid_count.positive? %> + + + <%= pluralize(unpaid_count, "attendee") %> unpaid + + <% end %> +
+ <% end %> +