diff --git a/AGENTS.md b/AGENTS.md index e194adbac3..25548a9fdf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -273,7 +273,8 @@ action, or `authorize! :workshop, to: :summary?`). - `EventRegistrationServices::CalloutFormSubmission` — Records a registrant's answers to one of the forms a registration-ticket callout delivers inline (`call(registration:, callout:, form:, form_params:, clarity_params:)`, returns the service instance with `#submission`/`#profile_changes`). Find-or-creates one `FormSubmission` per `(registrant, form, event)` carrying the form's own role — a re-submit edits the answers in place via `FormSubmission#persist_answer`. That a callout collected it is recorded separately in `FormSubmission#metadata` (`collected_via: "callout"`, `collected_via_callout_id`) via `#record_callout_collection!` — the flag (`#collected_via_callout?`), not the role, tells a callout-collected submission apart. There is no survey-role flag — the extra side effects self-gate on the form's own fields, so they no-op on a form that doesn't ask those questions: a choice field with linked `FormFieldResource`s (`FormField#per_resource?`) fans out to one answer per resource (nil `form_field`, `FormField#per_resource_question` snapshotted in `question_name_when_answered` — subtitle-led, so two fan-out questions sharing a prompt can't collide on one answer row); fields carrying the `anonymous_contributions` / `display_name_preference` identifiers write through to the `Person` via `PersonServices::SyncSharingPreferences` (the same write-through the registration form runs), exposing `#profile_changes` so the controller Ahoy-tracks real edits; and submitting any form on the **`scholarship`** callout stamps a recipient's `post_survey_completed_at`. Drives `Events::CalloutsController#callout`/`#submit_callout` (the person-first `registration/:slug/forms/:callout_id` page and its `.../:form_id` submit) - `SurveyFormSeeder` — Creates the four standalone survey template forms from the form-builder presets (the Day 1 / Day 2 evaluations, the overall post-event survey, and the scholarship recipients survey), idempotent on form name and returning the names created. `#link_fanout_resources` then links each fan-out question to its training-topic `Resource`s by title (skipping any that aren't present). Shared by `db/seeds.rb` and the `data:seed_survey_forms` task so prod can create them without the full seed - `CloseProgramFormSeeder` — Creates the standalone "Close Program" agreement form (role `close_program`, sections `person_identifier` + `close_program`) from the form-builder presets, idempotent on form name, returning the name created (or nil). Built as an unpublished template for staff to slug + publish. Shared by `db/seeds.rb` and the `data:seed_close_program_form` task -- `EventRegistrationServices::PublicRegistration` — Public registration handling. An affirmative `communication_consent` answer captures a News (mailing-list) `TopicSubscription` via `NewsSubscriptionCapture`, sourced to the event +- `EventRegistrationServices::PublicRegistration` — Public registration handling. An affirmative `communication_consent` answer captures a News (mailing-list) `TopicSubscription` via `NewsSubscriptionCapture`, sourced to the event. A resubmission matched to an active registration sends the registrant and admins DUPLICATE emails (`event_registration_duplicate` / `event_registration_duplicate_fyi`) +- `EventRegistrationServices::ResubmissionChanges` — Snapshots a registration's resubmittable state (scholarship, CE, W-9, invoice, payment method, who's paying, linked organizations) before and after a resubmission and turns the difference into plain-language notes, plus one note per registration-form answer that differs from the person's previous registration submission for the event; the notes ride in the duplicate notifications' `custom_message` (one per line) and drive the "(scholarship now requested)" / "(registration changed)" subject suffix - `NewsSubscriptionCapture` — Successor to the retired person-level mailing-list consent flag: given a `person` + `source`, idempotently creates an active **News** `TopicSubscription` (skips when one is already active, no-op when the News topic isn't seeded). Called from `PublicRegistration` (source = event) and `PublicFormSubmission` (source = form) when the `communication_consent` question is answered - `OrganizationServices::CaptureFromSubmission` — The shared org-capture for **both** the event registration (`PublicRegistration`) and standalone (`PublicFormSubmission`) flows: resolves the submitted organization by name (`FormField::ORGANIZATION_NAME_FIELD_IDENTIFIER`, case-insensitive per the column collation; never creates one — an unmatched name is left for an admin), syncs its website/type (`SyncProfile`, latest-wins) and work address (`UpsertAddress`, which adds another work address rather than rewriting a different one). `AffiliationServices::CreateFromRegistration` then mints the person's job affiliation from the typed position, plus the standing Facilitator one when `facilitator_training:` — which only an event supplies (`@event.facilitator_training`), so a standalone form gets the job affiliation alone, the same as registering for a non-training event. **Agreement-role submissions never come through here** — they run `LinkSubmittedOrganization`, which owns scenario end-dating and the new-job start date. Returns the org + the autofill changes, which the event flow records on its registration↔org link - `PersonServices::CaptureFromSubmission` — The shared person-capture for **both** the event registration (`PublicRegistration`) and standalone (`PublicFormSubmission`) flows, run for a newly-created **and** an existing person, so the person smart fields on the Smart form settings page behave identically across both forms. Fills identity extras (pronouns, name `pronunciation`, `nickname` → first name / legal first name, primary email type, secondary email + type), the profile (`racial_ethnic_identity` + `SyncSharingPreferences`), the mailing address, the phone contact method, and sector/age tags (mirrored onto any organizations passed in). A non-blank answer overwrites what's on file (newest submission wins); a blank never clobbers. Sector and age tags both run through `SubmissionTagging` and follow one rule on the **person**: submitting both a primary and additional set **overwrites** the person's tags with the latest (`replace: true`), submitting only a primary **reassigns** the primary and leaves the rest (additive); the org mirror always stays additive (orgs aggregate their members' tags). Self-gates on each field identifier, so a form that doesn't ask a question no-ops for it diff --git a/app/jobs/notification_mailer_job.rb b/app/jobs/notification_mailer_job.rb index 12aa63862f..9e26c9ecb5 100644 --- a/app/jobs/notification_mailer_job.rb +++ b/app/jobs/notification_mailer_job.rb @@ -20,6 +20,8 @@ def perform(notification_id, persist_delivered_email: true) "reset_password_fyi" => ->(n) { NotificationMailer.reset_password_fyi(n) }, "event_registration_confirmation" => ->(n) { EventMailer.event_registration_confirmation(n.noticeable) }, "event_registration_confirmation_fyi" => ->(n) { NotificationMailer.event_registration_confirmation_fyi(n) }, + "event_registration_duplicate" => ->(n) { EventMailer.event_registration_confirmation(n.noticeable, duplicate: true, changes_message: n.custom_message) }, + "event_registration_duplicate_fyi" => ->(n) { NotificationMailer.event_registration_duplicate_fyi(n) }, "event_registration_cancelled" => ->(n) { EventMailer.event_registration_cancelled(n.noticeable) }, "event_registration_cancelled_fyi" => ->(n) { NotificationMailer.event_registration_cancelled_fyi(n) }, "event_registration_reminder" => ->(n) { EventMailer.event_registration_reminder(n.noticeable, custom_message: n.custom_message, custom_subject: n.custom_subject, hide_event_card: n.hide_event_card, hide_ticket_button: n.hide_ticket_button) }, diff --git a/app/mailers/event_mailer.rb b/app/mailers/event_mailer.rb index 33a2c91027..f55006dfd3 100644 --- a/app/mailers/event_mailer.rb +++ b/app/mailers/event_mailer.rb @@ -1,8 +1,13 @@ class EventMailer < ApplicationMailer - def event_registration_confirmation(event_registration) + # `duplicate:` marks a resubmission merged into an existing registration; its + # `changes_message` lists what that resubmission changed (see ResubmissionChanges). + def event_registration_confirmation(event_registration, duplicate: false, changes_message: nil) @event_registration = event_registration @event = event_registration.event.decorate @person = event_registration.registrant + @duplicate = duplicate + changes = EventRegistrationServices::ResubmissionChanges.from_message(changes_message) + @change_notes = changes.notes @notification_type = "Event registration confirmation" @@ -14,7 +19,7 @@ def event_registration_confirmation(event_registration) to: @person.preferred_email, from: self.class.sender(Organization.no_reply_email), reply_to: Organization.programs_email, - subject: "AWBW Portal: #{@event_registration.registration_subject_noun.capitalize} received for #{@event.title}" + subject: confirmation_subject(changes) ) end @@ -125,4 +130,13 @@ def event_registration_cancelled(event_registration) subject: "AWBW Portal: #{@event_registration.registration_subject_noun.capitalize} cancelled for #{@event.title}" ) end + + private + + def confirmation_subject(changes) + noun = @event_registration.registration_subject_noun + return "AWBW Portal: #{noun.capitalize} received for #{@event.title}" unless @duplicate + + "AWBW Portal: DUPLICATE #{noun} received for #{@event.title}#{changes.subject_suffix}" + end end diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb index aa31d30700..427702ae72 100644 --- a/app/mailers/notification_mailer.rb +++ b/app/mailers/notification_mailer.rb @@ -18,6 +18,20 @@ def event_registration_confirmation_fyi(notification) ) end + def event_registration_duplicate_fyi(notification) + @event_registration = notification.noticeable + @event = @event_registration.event.decorate + @person = @event_registration.registrant + @resubmitted_at = notification.created_at + changes = EventRegistrationServices::ResubmissionChanges.from_message(notification.custom_message) + @change_notes = changes.notes + @notification_type = "Duplicate event registration" + + mail( + subject: "#{FYI_PREFIX} DUPLICATE #{@event_registration.registration_subject_noun} by #{@person.full_name} to #{@event.title}#{changes.subject_suffix}" + ) + end + # Takes the FormSubmission directly (not a Notification record, unlike the others). # `updated:` distinguishes an edit to an existing submission from a first one. def callout_form_submitted_fyi(form_submission, updated: false) diff --git a/app/models/notification.rb b/app/models/notification.rb index 173a0c56bc..b65b56a5ce 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -27,6 +27,8 @@ class Notification < ApplicationRecord event_registration_confirmation event_registration_confirmation_fyi + event_registration_duplicate + event_registration_duplicate_fyi event_registration_cancelled event_registration_cancelled_fyi event_registration_reminder @@ -102,6 +104,7 @@ class Notification < ApplicationRecord [ "Admin FYI (all)", "[FYI]" ], [ "Admin FYI: event registration confirmed", "[FYI] New event registration" ], [ "Admin FYI: event scholarship registration confirmed", "[FYI] New event scholarship registration" ], + [ "Admin FYI: duplicate event registration", "[FYI] DUPLICATE event" ], [ "Admin FYI: event registration cancelled", "[FYI] Event registration cancelled" ], [ "Admin FYI: event scholarship registration cancelled", "[FYI] Event scholarship registration cancelled" ], [ "Admin FYI: bulk payment", "[FYI] New bulk payment" ], @@ -118,6 +121,7 @@ class Notification < ApplicationRecord [ "Event scholarship registration cancelled", "Event scholarship registration cancelled" ], [ "Event registration received", "Event registration received" ], [ "Event scholarship registration received", "Event scholarship registration received" ], + [ "Duplicate event registration received", "AWBW Portal: DUPLICATE event" ], [ "Bulk payment: confirmation", "Bulk payment received" ], [ "Idea: confirmation (all)", "has been received" ], [ "Story: promotion confirmation", "is now a story" ], diff --git a/app/services/event_registration_services/public_registration.rb b/app/services/event_registration_services/public_registration.rb index 96070da29c..1343cbef77 100644 --- a/app/services/event_registration_services/public_registration.rb +++ b/app/services/event_registration_services/public_registration.rb @@ -92,6 +92,7 @@ def call ) if existing + before_resubmission = ResubmissionChanges.snapshot(existing) existing.update!(scholarship_requested: true) if @scholarship_requested create_ce_registration(existing, person) existing.update!(w9_requested: true) if w9_requested? @@ -100,15 +101,19 @@ def call existing.update!(expected_payment_method: payment_method) if payment_method.present? someone_else = someone_else_will_pay_answer existing.update!(someone_else_will_pay: someone_else) unless someone_else.nil? - if existing.status == "cancelled" - existing.update!(status: "registered") - send_notifications(existing) - end + reactivated = existing.status == "cancelled" + existing.update!(status: "registered") if reactivated organization_link = connect_organization(existing, organization) submission = create_form_submission(person) organization_link&.record_form_submission(submission) save_scholarship_submission(person) save_continuing_education_submission(person) + if reactivated + send_notifications(existing) + else + changes = ResubmissionChanges.between(before_resubmission, ResubmissionChanges.snapshot(existing), submission:) + send_duplicate_notifications(existing, changes) + end return Result.new(success?: true, event_registration: existing, form_submission: submission, errors: []) end @@ -401,5 +406,26 @@ def send_notifications(event_registration) notification_type: 0 ) end + + def send_duplicate_notifications(event_registration, changes) + NotificationServices::CreateNotification.call( + noticeable: event_registration, + person: event_registration.registrant, + kind: :event_registration_duplicate, + recipient_role: :person, + recipient_email: event_registration.registrant.preferred_email, + custom_message: changes.to_message, + notification_type: 1 + ) + + NotificationServices::CreateNotification.call( + noticeable: event_registration, + kind: :event_registration_duplicate_fyi, + recipient_role: :admin, + recipient_email: Organization.programs_email, + custom_message: changes.to_message, + notification_type: 1 + ) + end end end diff --git a/app/services/event_registration_services/resubmission_changes.rb b/app/services/event_registration_services/resubmission_changes.rb new file mode 100644 index 0000000000..28405e5dd8 --- /dev/null +++ b/app/services/event_registration_services/resubmission_changes.rb @@ -0,0 +1,89 @@ +module EventRegistrationServices + # Plain-language notes on what a resubmission changed on an existing registration. + # They travel in the duplicate notifications' custom_message, one note per line. + class ResubmissionChanges + SCHOLARSHIP_REQUESTED = "Scholarship now requested".freeze + + NEWLY_REQUESTED_NOTES = { + scholarship_requested: SCHOLARSHIP_REQUESTED, + ce_requested: "Continuing education credit now requested", + w9_requested: "W-9 now requested", + invoice_requested: "Invoice now requested" + }.freeze + + # Answers already reported through the registration settings above. + SETTINGS_FIELD_IDENTIFIERS = [ + "payment_method", "confirm_email", + PublicRegistration::ADDITIONAL_FORMS_IDENTIFIER, PublicRegistration::SOMEONE_ELSE_WILL_PAY_IDENTIFIER + ].freeze + + attr_reader :notes + + def self.snapshot(registration) + { + scholarship_requested: registration.scholarship_requested?, + ce_requested: registration.continuing_education_registrations.exists?, + w9_requested: registration.w9_requested?, + invoice_requested: registration.invoice_requested?, + expected_payment_method: registration.expected_payment_method.presence, + someone_else_will_pay: registration.someone_else_will_pay?, + organization_ids: registration.event_registration_organizations.pluck(:organization_id) + } + end + + def self.between(before, after, submission: nil) + notes = NEWLY_REQUESTED_NOTES.filter_map { |key, note| note if after[key] && !before[key] } + if before[:expected_payment_method] != after[:expected_payment_method] + notes << "Payment method changed from #{before[:expected_payment_method] || "none"} to #{after[:expected_payment_method] || "none"}" + end + if before[:someone_else_will_pay] != after[:someone_else_will_pay] + notes << (after[:someone_else_will_pay] ? "Someone else will now pay" : "Someone else is no longer paying") + end + linked = Organization.where(id: after[:organization_ids] - before[:organization_ids]).order(:name).pluck(:name) + notes.concat(linked.map { |name| "Organization linked: #{name}" }) + notes.concat(answer_notes(submission)) if submission + new(notes) + end + + # Each answer that differs from the person's previous submission of this form + # for the event, squished to one line so it fits the newline-separated message. + # A missing answer is skipped: hidden "answers on file" fields aren't posted. + def self.answer_notes(submission) + previous = FormSubmission.where(person_id: submission.person_id, event_id: submission.event_id, role: submission.role) + .where.not(id: submission.id).order(:created_at, :id).last + return [] unless previous + + previous_answers = previous.form_answers.index_by(&:form_field_id) + submission.form_answers.includes(:form_field).order(:id).filter_map do |answer| + next if answer.form_field.nil? || answer.form_field.field_identifier.in?(SETTINGS_FIELD_IDENTIFIERS) + + now = answer.submitted_answer.to_s.squish + was = previous_answers[answer.form_field_id]&.submitted_answer.to_s.squish + next if now.blank? || now == was + + question = (answer.question_name_when_answered.presence || answer.form_field.name).squish.truncate(60) + next "#{question}: added #{now.truncate(80)}" if was.blank? + + "#{question}: changed from #{was.truncate(80)} to #{now.truncate(80)}" + end + end + + def self.from_message(message) + new(message.to_s.split("\n").map(&:strip).compact_blank) + end + + def initialize(notes) + @notes = notes + end + + def to_message + @notes.join("\n").presence + end + + def subject_suffix + return " (scholarship now requested)" if @notes.include?(SCHOLARSHIP_REQUESTED) + + " (registration changed)" if @notes.any? + end + end +end diff --git a/app/views/event_mailer/event_registration_confirmation.html.erb b/app/views/event_mailer/event_registration_confirmation.html.erb index 26ca4dec49..4a8faecbe7 100644 --- a/app/views/event_mailer/event_registration_confirmation.html.erb +++ b/app/views/event_mailer/event_registration_confirmation.html.erb @@ -1,10 +1,19 @@ -
Hello <%= @person.first_name %>,
+ <% if @duplicate %> ++ We received another registration from you for this event. Your new answers + were added to your existing registration, so you're still registered just once. +
+ + <%= render "shared/resubmission_changes", notes: @change_notes %> + <% end %> +This message confirms your registration for the following <%= @organization_name %> event:
diff --git a/app/views/event_mailer/event_registration_confirmation.text.erb b/app/views/event_mailer/event_registration_confirmation.text.erb index 64c52b87ea..f0f8a8c9e5 100644 --- a/app/views/event_mailer/event_registration_confirmation.text.erb +++ b/app/views/event_mailer/event_registration_confirmation.text.erb @@ -1,6 +1,12 @@ -Event registration received +<%= @duplicate ? "DUPLICATE event registration received" : "Event registration received" %> Hello <%= @person.first_name %>, +<% if @duplicate %> + +We received another registration from you for this event. Your new answers were added to your existing registration, so you're still registered just once. + +<%= render "shared/resubmission_changes", notes: @change_notes %> +<% end %> This message confirms your registration for the following event: diff --git a/app/views/notification_mailer/_event_registration_fyi_details.html.erb b/app/views/notification_mailer/_event_registration_fyi_details.html.erb new file mode 100644 index 0000000000..003fb907e5 --- /dev/null +++ b/app/views/notification_mailer/_event_registration_fyi_details.html.erb @@ -0,0 +1,62 @@ ++ <%= @event.pre_title %> +
+ <% end %> + ++ <%= @event.times(display_day: true, display_date: true) %> +
+ + <% if @event.labelled_cost.present? %> ++ <%= @event.labelled_cost %> +
+ <% end %> + + <% if @event.location.present? %> ++ <%= @event.location.name %> +
+ <% end %> + + <%# Virtual events show only the platform label (e.g. "Zoom") — the join link, + meeting ID, and passcode are withheld and live on the ticket pages. %> + <% if event_platform_label(@event).present? %> ++ <%= event_platform_label(@event) %> +
+ <% end %> ++ <%= link_to "View ticket", + registration_ticket_url(@event_registration.slug), + style: "display:inline-block; + padding:10px 16px; + background:#2563eb; + color:#ffffff; + text-decoration:none; + border-radius:6px; + font-weight:bold;" if @event_registration.present? %> + + + View profile + + + + + View event + +
diff --git a/app/views/notification_mailer/_event_registration_fyi_details.text.erb b/app/views/notification_mailer/_event_registration_fyi_details.text.erb new file mode 100644 index 0000000000..204f43274f --- /dev/null +++ b/app/views/notification_mailer/_event_registration_fyi_details.text.erb @@ -0,0 +1,32 @@ +Event +<%= @event.title %> + +Event date +<%= @event.times(display_day: true, display_date: true) %> + +<% if @event.location.present? %> + Location + <%= @event.location.name %> +<% end %> + +<% if event_platform_label(@event).present? %> + Videoconference + <%= event_platform_label(@event) %> +<% end %> + +<% if @event.labelled_cost.present? %> + Cost + <%= @event.labelled_cost %> +<% end %> + +------------------------------------------------------------ + +View ticket: +<%= registration_ticket_url(@event_registration.slug) if @event_registration.present? %> + +View profile: +<%= person_url(@person) %> + +View event: +<%= event_url(@event, reg: @event_registration.slug) %> + diff --git a/app/views/notification_mailer/event_registration_confirmation_fyi.html.erb b/app/views/notification_mailer/event_registration_confirmation_fyi.html.erb index 30c2863d9d..d972844b7f 100644 --- a/app/views/notification_mailer/event_registration_confirmation_fyi.html.erb +++ b/app/views/notification_mailer/event_registration_confirmation_fyi.html.erb @@ -1,5 +1,3 @@ -<% profile_url = person_url(@person) %> -New Event Registration
@@ -16,65 +14,4 @@ .strftime("%B %-d, %Y at %-l:%M %p %Z") %> -- <%= @event.pre_title %> -
- <% end %> - -- <%= @event.times(display_day: true, display_date: true) %> -
- - <% if @event.labelled_cost.present? %> -- <%= @event.labelled_cost %> -
- <% end %> - - <% if @event.location.present? %> -- <%= @event.location.name %> -
- <% end %> - - <%# Virtual events show only the platform label (e.g. "Zoom") — the join link, - meeting ID, and passcode are withheld and live on the ticket pages. %> - <% if event_platform_label(@event).present? %> -- <%= event_platform_label(@event) %> -
- <% end %> -- <%= link_to "View ticket", - registration_ticket_url(@event_registration.slug), - style: "display:inline-block; - padding:10px 16px; - background:#2563eb; - color:#ffffff; - text-decoration:none; - border-radius:6px; - font-weight:bold;" if @event_registration.present? %> - - - View profile - - - - - View event - -
+<%= render "event_registration_fyi_details" %> diff --git a/app/views/notification_mailer/event_registration_confirmation_fyi.text.erb b/app/views/notification_mailer/event_registration_confirmation_fyi.text.erb index 14e6d15077..cbcf264ca8 100644 --- a/app/views/notification_mailer/event_registration_confirmation_fyi.text.erb +++ b/app/views/notification_mailer/event_registration_confirmation_fyi.text.erb @@ -1,5 +1,3 @@ -<% profile_url = person_url(@person) %> - New Event Registration ========================== @@ -12,35 +10,4 @@ Registered on ------------------------------------------------------------ -Event -<%= @event.title %> - -Event date -<%= @event.times(display_day: true, display_date: true) %> - -<% if @event.location.present? %> - Location - <%= @event.location.name %> -<% end %> - -<% if event_platform_label(@event).present? %> - Videoconference - <%= event_platform_label(@event) %> -<% end %> - -<% if @event.labelled_cost.present? %> - Cost - <%= @event.labelled_cost %> -<% end %> - ------------------------------------------------------------- - -View ticket: -<%= registration_ticket_url(@event_registration.slug) if @event_registration.present? %> - -View profile: -<%= profile_url %> - -View event: -<%= event_url(@event, reg: @event_registration.slug) %> - +<%= render "event_registration_fyi_details" %> diff --git a/app/views/notification_mailer/event_registration_duplicate_fyi.html.erb b/app/views/notification_mailer/event_registration_duplicate_fyi.html.erb new file mode 100644 index 0000000000..5aca0bbec9 --- /dev/null +++ b/app/views/notification_mailer/event_registration_duplicate_fyi.html.erb @@ -0,0 +1,20 @@ ++ DUPLICATE <%= @event_registration.registration_subject_noun %> +
+ ++ Resubmitted on + <%= @resubmitted_at.in_time_zone("Pacific Time (US & Canada)").strftime("%B %-d, %Y at %-l:%M %p %Z") %>. + Originally registered on + <%= @event_registration.created_at.in_time_zone("Pacific Time (US & Canada)").strftime("%B %-d, %Y at %-l:%M %p %Z") %>. + The answers were added to the existing registration. +
+ +<%= render "shared/resubmission_changes", notes: @change_notes %> + +<%= render "event_registration_fyi_details" %> diff --git a/app/views/notification_mailer/event_registration_duplicate_fyi.text.erb b/app/views/notification_mailer/event_registration_duplicate_fyi.text.erb new file mode 100644 index 0000000000..de582811ef --- /dev/null +++ b/app/views/notification_mailer/event_registration_duplicate_fyi.text.erb @@ -0,0 +1,18 @@ +DUPLICATE <%= @event_registration.registration_subject_noun %> +========================== + +<%= @person.full_name %> (<%= @person.preferred_email %>) + +Resubmitted on +<%= @resubmitted_at.in_time_zone("Pacific Time (US & Canada)").strftime("%B %-d, %Y at %-l:%M %p %Z") %> + +Originally registered on +<%= @event_registration.created_at.in_time_zone("Pacific Time (US & Canada)").strftime("%B %-d, %Y at %-l:%M %p %Z") %> + +The answers were added to the existing registration. + +<%= render "shared/resubmission_changes", notes: @change_notes %> + +------------------------------------------------------------ + +<%= render "event_registration_fyi_details" %> diff --git a/app/views/shared/_resubmission_changes.html.erb b/app/views/shared/_resubmission_changes.html.erb new file mode 100644 index 0000000000..ede7cce2cf --- /dev/null +++ b/app/views/shared/_resubmission_changes.html.erb @@ -0,0 +1,10 @@ +<% if notes.any? %> +This submission changed the registration:
+