diff --git a/app/controllers/api/submissions_controller.rb b/app/controllers/api/submissions_controller.rb index 22af24d9..77b4e615 100644 --- a/app/controllers/api/submissions_controller.rb +++ b/app/controllers/api/submissions_controller.rb @@ -172,7 +172,10 @@ module Api Submissions::NormalizeParamUtils.save_default_value_attachments!(attachments, submitters) submitters.each do |submitter| - SubmissionEvents.create_with_tracking_data(submitter, 'api_complete_form', request) if submitter.completed_at? + if submitter.completed_at? + Submitters::SubmitValues.maybe_invite_via_field(submitter, request) + SubmissionEvents.create_with_tracking_data(submitter, 'api_complete_form', request) + end end submissions diff --git a/app/controllers/api/submitters_controller.rb b/app/controllers/api/submitters_controller.rb index e56eb8b8..f28bf5e1 100644 --- a/app/controllers/api/submitters_controller.rb +++ b/app/controllers/api/submitters_controller.rb @@ -34,6 +34,7 @@ module Api render json: Submitters::SerializeForApi.call(@submitter, with_template: true, with_events: true, params:) end + # rubocop:disable Metrics/MethodLength def update if @submitter.completed_at? return render json: { error: 'Submitter has already completed the submission.' }, status: :unprocessable_content @@ -60,7 +61,10 @@ module Api @submitter.submission.save! - SubmissionEvents.create_with_tracking_data(@submitter, 'api_complete_form', request) if @submitter.completed_at? + if @submitter.completed_at? + Submitters::SubmitValues.maybe_invite_via_field(@submitter, request) + SubmissionEvents.create_with_tracking_data(@submitter, 'api_complete_form', request) + end end if @submitter.completed_at? @@ -78,6 +82,7 @@ module Api render json: { error: e.message }, status: :unprocessable_content end + # rubocop:enable Metrics/MethodLength def submitter_params submitter_params = params.key?(:submitter) ? params.require(:submitter) : params diff --git a/app/controllers/api/templates_controller.rb b/app/controllers/api/templates_controller.rb index c8211b7f..c3f1dd42 100644 --- a/app/controllers/api/templates_controller.rb +++ b/app/controllers/api/templates_controller.rb @@ -107,7 +107,8 @@ module Api :external_id, :shared_link, { - submitters: [%i[name uuid is_requester invite_by_uuid optional_invite_by_uuid linked_to_uuid email order]], + submitters: [%i[name uuid is_requester invite_by_uuid invite_via_field_uuid + optional_invite_by_uuid linked_to_uuid email order]], fields: [[:uuid, :submitter_uuid, :name, :type, :required, :readonly, :default_value, :title, :description, :prefillable, diff --git a/app/controllers/submit_form_invite_controller.rb b/app/controllers/submit_form_invite_controller.rb index bcc848ae..413e2b9a 100644 --- a/app/controllers/submit_form_invite_controller.rb +++ b/app/controllers/submit_form_invite_controller.rb @@ -19,7 +19,9 @@ class SubmitFormInviteController < ApplicationController next unless attrs next if attrs[:email].blank? - submitter.submission.submitters.create!(**attrs, account_id: submitter.account_id) + email = Submissions.normalize_email(attrs[:email]) + + submitter.submission.submitters.create!(uuid: attrs[:uuid], email:, account_id: submitter.account_id) SubmissionEvents.create_with_tracking_data(submitter, 'invite_party', request, { uuid: submitter.uuid }) end diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb index 39b45044..0d6db6f7 100644 --- a/app/controllers/templates_controller.rb +++ b/app/controllers/templates_controller.rb @@ -97,7 +97,8 @@ class TemplatesController < ApplicationController :name, { schema: [[:attachment_uuid, :google_drive_file_id, :name, { conditions: [%i[field_uuid value action operation]] }]], - submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid optional_invite_by_uuid email order]], + submitters: [%i[name uuid is_requester linked_to_uuid invite_via_field_uuid + invite_by_uuid optional_invite_by_uuid email order]], fields: [[:uuid, :submitter_uuid, :name, :type, :required, :readonly, :default_value, :title, :description, :prefillable, diff --git a/app/controllers/templates_recipients_controller.rb b/app/controllers/templates_recipients_controller.rb index 17a4bbb8..64fa58b2 100644 --- a/app/controllers/templates_recipients_controller.rb +++ b/app/controllers/templates_recipients_controller.rb @@ -22,7 +22,7 @@ class TemplatesRecipientsController < ApplicationController def submitters_params permit_params = { submitters: [%i[name uuid is_requester optional_invite_by_uuid - invite_by_uuid linked_to_uuid email option order]] } + invite_by_uuid invite_via_field_uuid linked_to_uuid email option order]] } params.require(:template).permit(permit_params).fetch(:submitters, {}).values.filter_map do |s| next if s[:uuid].blank? @@ -36,6 +36,7 @@ class TemplatesRecipientsController < ApplicationController s[:order] = s[:order].to_i if s[:order].present? s.delete(:invite_by_uuid) if s[:invite_by_uuid].blank? s.delete(:optional_invite_by_uuid) if s[:optional_invite_by_uuid].blank? + s.delete(:invite_via_field_uuid) if s[:invite_via_field_uuid].blank? normalize_option_value(s) end @@ -53,6 +54,7 @@ class TemplatesRecipientsController < ApplicationController attrs.delete(:email) attrs.delete(:linked_to_uuid) attrs.delete(:invite_by_uuid) + attrs.delete(:invite_via_field_uuid) attrs.delete(:optional_invite_by_uuid) when /\Alinked_to_(.*)\z/ attrs[:linked_to_uuid] = ::Regexp.last_match(-1) diff --git a/app/views/submissions/_detailed_form.html.erb b/app/views/submissions/_detailed_form.html.erb index ac4c2607..7e38fecb 100644 --- a/app/views/submissions/_detailed_form.html.erb +++ b/app/views/submissions/_detailed_form.html.erb @@ -1,6 +1,6 @@ <% has_phone_field = false %> <%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %> - <% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? } %> + <% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %>
diff --git a/app/views/submissions/_email_form.html.erb b/app/views/submissions/_email_form.html.erb index a88e3a0b..2fee6706 100644 --- a/app/views/submissions/_email_form.html.erb +++ b/app/views/submissions/_email_form.html.erb @@ -1,5 +1,5 @@ <%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %> - <% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? } %> + <% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %> <% if submitters.size == 1 %> diff --git a/app/views/submissions/_phone_form.html.erb b/app/views/submissions/_phone_form.html.erb index 68bf6d45..02900a53 100644 --- a/app/views/submissions/_phone_form.html.erb +++ b/app/views/submissions/_phone_form.html.erb @@ -1,5 +1,5 @@ <%= form_for '', url: template_submissions_path(template), html: { class: 'space-y-4', autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %> - <% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? } %> + <% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %>
diff --git a/app/views/templates_preferences/_recipients.html.erb b/app/views/templates_preferences/_recipients.html.erb index a8547669..4d45e38f 100644 --- a/app/views/templates_preferences/_recipients.html.erb +++ b/app/views/templates_preferences/_recipients.html.erb @@ -7,8 +7,8 @@
<% template.submitters.each_with_index do |submitter, index| %>
- <%= f.fields_for :submitters, item = Struct.new(:name, :uuid, :is_requester, :email, :invite_by_uuid, :optional_invite_by_uuid, :linked_to_uuid, :order, :option).new(*submitter.values_at('name', 'uuid', 'is_requester', 'email', 'invite_by_uuid', 'optional_invite_by_uuid', 'linked_to_uuid', 'order')), index: do |ff| %> - <% item.option = item.is_requester.present? ? 'is_requester' : (item.email.present? ? 'email' : (item.linked_to_uuid.present? ? "linked_to_#{item.linked_to_uuid}" : (item.invite_by_uuid.present? ? "invite_by_#{item.invite_by_uuid}" : (item.optional_invite_by_uuid.present? ? "optional_invite_by_#{item.optional_invite_by_uuid}" : '')))) %> + <%= f.fields_for :submitters, item = Struct.new(:name, :uuid, :is_requester, :email, :invite_by_uuid, :invite_via_field_uuid, :optional_invite_by_uuid, :linked_to_uuid, :order, :option).new(*submitter.values_at('name', 'uuid', 'is_requester', 'email', 'invite_by_uuid', 'invite_via_field_uuid', 'optional_invite_by_uuid', 'linked_to_uuid', 'order')), index: do |ff| %> + <% item.option = item.is_requester.present? ? 'is_requester' : (item.email.present? ? 'email' : (item.linked_to_uuid.present? ? "linked_to_#{item.linked_to_uuid}" : (item.invite_by_uuid.present? ? "invite_by_#{item.invite_by_uuid}" : (item.optional_invite_by_uuid.present? ? "optional_invite_by_#{item.optional_invite_by_uuid}" : (item.invite_via_field_uuid.present? ? 'invite_via_field' : ''))))) %> <%= ff.hidden_field :uuid %>
@@ -28,10 +28,16 @@ <% if template.submitters.size == 2 %> <%= tag.input name: ff.field_name(:email), value: ff.object.email, type: :email, class: 'base-input', multiple: true, autocomplete: 'off', placeholder: t('default_email'), disabled: ff.object.is_requester || ff.object.invite_by_uuid.present? || ff.object.optional_invite_by_uuid.present?, id: field_uuid = SecureRandom.uuid %> <% else %> + <% invite_fields = template.fields.select { |field| field['name'].present? && field['submitter_uuid'] != submitter['uuid'] } %> - <%= ff.select :option, [[t('not_specified'), 'not_set'], (local_assigns[:with_submission_requester] == false ? nil : [t('submission_requester'), 'is_requester']), [t('specified_email'), 'email'], *(template.submitters - [submitter]).flat_map { |e| [[t('invite_by_name', name: e['name']), "invite_by_#{e['uuid']}"], [t('invite_by_name', name: e['name']) + " (#{t(:optional).capitalize})", "optional_invite_by_#{e['uuid']}"]] }, *(template.submitters - [submitter]).map { |e| [t('same_as_name', name: e['name']), "linked_to_#{e['uuid']}"] }].compact, {}, class: 'base-select mb-3' %> + + <%= ff.select :option, [[t('not_specified'), 'not_set'], (local_assigns[:with_submission_requester] == false ? nil : [t('submission_requester'), 'is_requester']), [t('specified_email'), 'email'], *(template.submitters - [submitter]).flat_map { |e| [[t('invite_by_name', name: e['name']), "invite_by_#{e['uuid']}"], [t('invite_by_name', name: e['name']) + " (#{t(:optional).capitalize})", "optional_invite_by_#{e['uuid']}"]] }, *(template.submitters - [submitter]).map { |e| [t('same_as_name', name: e['name']), "linked_to_#{e['uuid']}"] }, (invite_fields.present? ? [t('invite_via_form_field'), 'invite_via_field'] : nil)].compact, {}, class: 'base-select mb-3' %> + <%= tag.input name: ff.field_name(:email), type: :email, value: ff.object.email, multiple: true, class: "base-input #{'hidden' if item.option != 'email'}", autocomplete: 'off', placeholder: t('default_email'), id: email_field_uuid %> + + <%= select_tag ff.field_name(:invite_via_field_uuid), options_for_select(invite_fields.map { |field| [field['name'], field['uuid']] }, item.invite_via_field_uuid), prompt: t(:select_field), class: 'base-select',required: true %> + <% end %>
<% if template.submitters.size == 2 %> diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 4b3f7777..1a5312c6 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -500,6 +500,7 @@ en: &en submission_requester: Submission requester specified_email: Specified email invite_by_name: 'Invite by %{name}' + invite_via_form_field: Invite via Form Field same_as_name: 'Same as %{name}' default_email: Default Email processing: Processing @@ -1490,6 +1491,7 @@ es: &es submission_requester: Solicitante del envío specified_email: Correo electrónico especificado invite_by_name: 'Invitar por %{name}' + invite_via_form_field: Invitar a través de campo del formulario same_as_name: 'Igual que %{name}' default_email: Correo electrónico predeterminado processing: Procesando @@ -2477,6 +2479,7 @@ it: &it submission_requester: "Richiedente dell'invio" specified_email: Email specificata invite_by_name: 'Invito da %{name}' + invite_via_form_field: Invito tramite campo del modulo same_as_name: 'Uguale a %{name}' default_email: Email predefinita processing: Elaborazione in corso @@ -3465,6 +3468,7 @@ fr: &fr submission_requester: Demandeur de soumission specified_email: E‑mail spécifié invite_by_name: Inviter par %{name} + invite_via_form_field: Inviter via champ du formulaire same_as_name: Identique à %{name} default_email: E‑mail par défaut processing: Traitement en cours @@ -4449,6 +4453,7 @@ pt: &pt submission_requester: Solicitante de submissão specified_email: E-mail especificado invite_by_name: 'Convidado por %{name}' + invite_via_form_field: Convidar via campo do formulário same_as_name: 'Igual a %{name}' default_email: E-mail padrão processing: Processando @@ -5436,6 +5441,7 @@ de: &de submission_requester: Anfragende Person specified_email: Angegebene E-Mail invite_by_name: 'Einladung von %{name}' + invite_via_form_field: Einladung über Formularfeld same_as_name: 'Gleich wie %{name}' default_email: Standard-E-Mail processing: Verarbeitung @@ -6812,6 +6818,7 @@ nl: &nl submission_requester: Aanvrager van inzending specified_email: Opgegeven e-mail invite_by_name: Uitnodigen door %{name} + invite_via_form_field: Uitnodigen via formulierveld same_as_name: Zelfde als %{name} default_email: Standaard e-mail processing: Verwerken diff --git a/lib/submissions/create_from_submitters.rb b/lib/submissions/create_from_submitters.rb index b1e318cf..b367403c 100644 --- a/lib/submissions/create_from_submitters.rb +++ b/lib/submissions/create_from_submitters.rb @@ -56,7 +56,9 @@ module Submissions template_submitter = template_submitters.find { |e| e['uuid'] == uuid } end - template_submitter = template_submitter.except('optional_invite_by_uuid', 'invite_by_uuid') + template_submitter = template_submitter.except('optional_invite_by_uuid', 'invite_by_uuid', + 'invite_via_field_uuid') + template_submitter['order'] = submitter_attrs['order'] if submitter_attrs['order'].present? submission.template_submitters << template_submitter @@ -113,7 +115,10 @@ module Submissions item = item.merge('invite_by_uuid' => invite_by_uuid) if invite_by_uuid end - next if item['invite_by_uuid'].blank? && item['optional_invite_by_uuid'].blank? + next if item['invite_by_uuid'].blank? && + item['optional_invite_by_uuid'].blank? && + item['invite_via_field_uuid'].blank? + next if submission.template_submitters.any? { |e| e['uuid'] == item['uuid'] } item = item.merge('order' => submitter_attr['order']) if submitter_attr && submitter_attr['order'].present? diff --git a/lib/submitters/submit_values.rb b/lib/submitters/submit_values.rb index 4b7f4dcb..a2992957 100644 --- a/lib/submitters/submit_values.rb +++ b/lib/submitters/submit_values.rb @@ -6,6 +6,7 @@ module Submitters RequiredFieldError = Class.new(StandardError) VARIABLE_REGEXP = /\{\{?(\w+)\}\}?/ + PHONE_REGEXP = /[+\d()\s-]+/ NONEDITABLE_FIELD_TYPES = %w[stamp heading strikethrough].freeze STRFTIME_MAP = { @@ -52,7 +53,11 @@ module Submitters ActiveStorage::Attachment.where(uuid: touch_attachment_uuid, record: submitter).touch_all(:created_at) end - SubmissionEvents.create_with_tracking_data(submitter, 'complete_form', request) if params[:completed] == 'true' + if params[:completed] == 'true' + maybe_invite_via_field(submitter, request) + + SubmissionEvents.create_with_tracking_data(submitter, 'complete_form', request) + end submitter.save! end @@ -405,6 +410,47 @@ module Submitters end end + def maybe_invite_via_field(submitter, request) + submission = submitter.submission + + is_invited = false + + submission.template_submitters.each do |s| + field_uuid = s['invite_via_field_uuid'] + + next if field_uuid.blank? + + field = submission.template_fields.find { |e| e['uuid'] == field_uuid } + + next unless field + next unless field['submitter_uuid'] == submitter.uuid + + next if submission.submitters.exists?(uuid: s['uuid']) + + value = submitter.values[field_uuid] + + next if value.blank? + + if value.include?('@') + email = Submissions.normalize_email(value) + elsif value.match?(PHONE_REGEXP) + phone = value.gsub(/[^+\d]/, '') + end + + next if email.blank? && phone.blank? + + submission.submitters.create!(uuid: s['uuid'], email:, phone:, account_id: submitter.account_id) + + SubmissionEvents.create_with_tracking_data(submitter, 'invite_party', request, { uuid: submitter.uuid }) + + is_invited = true + end + + submission.update!(submitters_order: :preserved) if is_invited + + submitter + end + def validate_value!(_value, field, _params, submitter, _request) if field['readonly'] == true Rollbar.warning("Readonly field #{submitter.id}: #{field['uuid']}") if defined?(Rollbar) diff --git a/lib/templates.rb b/lib/templates.rb index 2abd93dd..ad63302b 100644 --- a/lib/templates.rb +++ b/lib/templates.rb @@ -72,6 +72,7 @@ module Templates def filter_undefined_submitters(template_submitters) template_submitters.to_a.select do |item| item['invite_by_uuid'].blank? && item['optional_invite_by_uuid'].blank? && + item['invite_via_field_uuid'].blank? && item['linked_to_uuid'].blank? && item['is_requester'].blank? && item['email'].blank? end end diff --git a/lib/templates/clone.rb b/lib/templates/clone.rb index e4b5fc60..1d213152 100644 --- a/lib/templates/clone.rb +++ b/lib/templates/clone.rb @@ -4,7 +4,7 @@ module Templates module Clone module_function - # rubocop:disable Metrics, Style/CombinableLoops + # rubocop:disable Metrics def call(original_template, author:, external_id: nil, name: nil, folder_name: nil) template = original_template.account.templates.new @@ -49,20 +49,6 @@ module Templates submitter['uuid'] = new_submitter_uuid end - cloned_submitters.each do |submitter| - if submitter['optional_invite_by_uuid'].present? - submitter['optional_invite_by_uuid'] = submitter_uuids_replacements[submitter['optional_invite_by_uuid']] - end - - if submitter['invite_by_uuid'].present? - submitter['invite_by_uuid'] = submitter_uuids_replacements[submitter['invite_by_uuid']] - end - - if submitter['linked_to_uuid'].present? - submitter['linked_to_uuid'] = submitter_uuids_replacements[submitter['linked_to_uuid']] - end - end - cloned_preferences['submitters'].to_a.each do |submitter| submitter['uuid'] = submitter_uuids_replacements[submitter['uuid']] end @@ -97,8 +83,26 @@ module Templates end end + cloned_submitters.each do |submitter| + if submitter['optional_invite_by_uuid'].present? + submitter['optional_invite_by_uuid'] = submitter_uuids_replacements[submitter['optional_invite_by_uuid']] + end + + if submitter['invite_by_uuid'].present? + submitter['invite_by_uuid'] = submitter_uuids_replacements[submitter['invite_by_uuid']] + end + + if submitter['linked_to_uuid'].present? + submitter['linked_to_uuid'] = submitter_uuids_replacements[submitter['linked_to_uuid']] + end + + if submitter['invite_via_field_uuid'].present? + submitter['invite_via_field_uuid'] = field_uuids_replacements[submitter['invite_via_field_uuid']] + end + end + [cloned_submitters, cloned_fields, cloned_schema, cloned_preferences] end - # rubocop:enable Metrics, Style/CombinableLoops + # rubocop:enable Metrics end end