diff --git a/app/controllers/account_configs_controller.rb b/app/controllers/account_configs_controller.rb index 66d0967b..079866d7 100644 --- a/app/controllers/account_configs_controller.rb +++ b/app/controllers/account_configs_controller.rb @@ -11,6 +11,7 @@ class AccountConfigsController < ApplicationController AccountConfig::FORCE_MFA, AccountConfig::ALLOW_TO_RESUBMIT, AccountConfig::ALLOW_TO_DECLINE_KEY, + AccountConfig::ALLOW_TO_DELEGATE_KEY, AccountConfig::FORM_PREFILL_SIGNATURE_KEY, AccountConfig::ESIGNING_PREFERENCE_KEY, AccountConfig::FORM_WITH_CONFETTI_KEY, @@ -36,7 +37,7 @@ class AccountConfigsController < ApplicationController end def destroy - raise InvalidKey unless ALLOWED_KEYS.include?(@account_config.key) + raise InvalidKey unless allowed_keys.include?(@account_config.key) @account_config.destroy! @@ -45,8 +46,12 @@ class AccountConfigsController < ApplicationController private + def allowed_keys + ALLOWED_KEYS + end + def load_account_config - raise InvalidKey unless ALLOWED_KEYS.include?(account_config_params[:key]) + raise InvalidKey unless allowed_keys.include?(account_config_params[:key]) @account_config = AccountConfig.find_or_initialize_by(account: current_account, key: account_config_params[:key]) diff --git a/app/controllers/submission_events_controller.rb b/app/controllers/submission_events_controller.rb index 28011799..0dad2336 100644 --- a/app/controllers/submission_events_controller.rb +++ b/app/controllers/submission_events_controller.rb @@ -16,6 +16,7 @@ class SubmissionEventsController < ApplicationController 'email_verified' => 'email_check', 'click_sms' => 'hand_click', 'decline_form' => 'x', + 'delegate_form' => 'user_share', 'start_verification' => 'player_play', 'complete_verification' => 'check', 'invite_party' => 'user_plus' diff --git a/app/controllers/submit_form_controller.rb b/app/controllers/submit_form_controller.rb index 5a6a0ae5..96f95f08 100644 --- a/app/controllers/submit_form_controller.rb +++ b/app/controllers/submit_form_controller.rb @@ -3,11 +3,12 @@ class SubmitFormController < ApplicationController layout 'form' - around_action :with_browser_locale, only: %i[show completed success] + around_action :with_browser_locale, only: %i[show completed success delegated] skip_before_action :authenticate_user! skip_authorization_check before_action :load_submitter, only: %i[show update completed] + before_action :maybe_redirect_delegated, only: :show before_action :maybe_render_locked_page, only: :show before_action :maybe_require_link_2fa, only: %i[show] @@ -91,6 +92,12 @@ class SubmitFormController < ApplicationController def success; end + def delegated + submitter_version = SubmitterVersion.find_by!(slug: params[:slug] || params[:submit_form_slug]) + + @submitter = submitter_version.submitter + end + private def maybe_require_link_2fa @@ -108,8 +115,18 @@ class SubmitFormController < ApplicationController render :declined if @submitter.declined_at? end + def maybe_redirect_delegated + return if @submitter + + submitter_version = SubmitterVersion.find_by!(slug: params[:slug] || params[:submit_form_slug]) + + submitter_version.submitter.submission_events.find_by!(event_type: :delegate_form) + + redirect_to submit_form_delegated_path(submitter_version.slug) + end + def load_submitter - @submitter = Submitter.find_by!(slug: params[:slug] || params[:submit_form_slug]) + @submitter = Submitter.find_by(slug: params[:slug] || params[:submit_form_slug]) end def build_attachments_index(submission) diff --git a/app/controllers/submit_form_decline_controller.rb b/app/controllers/submit_form_decline_controller.rb index a8f969c3..a55590f1 100644 --- a/app/controllers/submit_form_decline_controller.rb +++ b/app/controllers/submit_form_decline_controller.rb @@ -4,31 +4,38 @@ class SubmitFormDeclineController < ApplicationController skip_before_action :authenticate_user! skip_authorization_check - def create - submitter = Submitter.find_by!(slug: params[:submit_form_slug]) + before_action :load_submitter - return redirect_to submit_form_path(submitter.slug) if submitter.declined_at? || - submitter.completed_at? || - submitter.submission.archived_at? || - submitter.submission.expired? || - submitter.submission.template&.archived_at? || - !Submitters::AuthorizedForForm.call(submitter, current_user, - request) + def create + return redirect_to submit_form_path(@submitter.slug) if @submitter.declined_at? || + @submitter.completed_at? || + @submitter.submission.archived_at? || + @submitter.submission.expired? || + @submitter.submission.template&.archived_at? || + !Submitters::AuthorizedForForm.call(@submitter, + current_user, + request) ApplicationRecord.transaction do - submitter.update!(declined_at: Time.current) + @submitter.update!(declined_at: Time.current) - SubmissionEvents.create_with_tracking_data(submitter, 'decline_form', request, { reason: params[:reason] }) + SubmissionEvents.create_with_tracking_data(@submitter, 'decline_form', request, { reason: params[:reason] }) end - user = submitter.submission.created_by_user || submitter.template.author + user = @submitter.submission.created_by_user || @submitter.template.author if user.user_configs.find_by(key: UserConfig::RECEIVE_DECLINED_EMAIL)&.value != false - SubmitterMailer.declined_email(submitter, user).deliver_later! + SubmitterMailer.declined_email(@submitter, user).deliver_later! end - WebhookUrls.enqueue_events(submitter, 'form.declined') + WebhookUrls.enqueue_events(@submitter, 'form.declined') + + redirect_to submit_form_path(@submitter.slug) + end + + private - redirect_to submit_form_path(submitter.slug) + def load_submitter + @submitter = Submitter.find_by!(slug: params[:submit_form_slug]) end end diff --git a/app/controllers/submit_form_delegate_controller.rb b/app/controllers/submit_form_delegate_controller.rb new file mode 100644 index 00000000..0a516509 --- /dev/null +++ b/app/controllers/submit_form_delegate_controller.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class SubmitFormDelegateController < ApplicationController + skip_before_action :authenticate_user! + skip_authorization_check + + before_action :load_submitter + + def create + return redirect_to submit_form_path(@submitter.slug) if @submitter.declined_at? || + @submitter.completed_at? || + @submitter.submission.archived_at? || + @submitter.submission.expired? || + @submitter.submission.template&.archived_at? || + !Submitters::AuthorizedForForm.call(@submitter, + current_user, + request) + + @submitter.account.account_configs.find_by!(key: AccountConfig::ALLOW_TO_DELEGATE_KEY, value: true) + + email = Submissions.normalize_email(params[:email]) + + return redirect_to submit_form_path(@submitter.slug) if email.blank? + + old_slug = @submitter.slug + + ApplicationRecord.transaction do + @submitter.submitter_versions.create!(slug: old_slug, email: @submitter.email, + name: @submitter.name, phone: @submitter.phone) + + SubmissionEvents.create_with_tracking_data(@submitter, 'delegate_form', request, + { old_email: @submitter.email, email: }) + + @submitter.update!(email:, phone: nil, name: nil, slug: SecureRandom.base58(14)) + end + + SendSubmitterInvitationEmailJob.perform_async('submitter_id' => @submitter.id) + + redirect_to submit_form_delegated_path(old_slug) + end + + private + + def load_submitter + @submitter = Submitter.find_by!(slug: params[:submit_form_slug]) + end +end diff --git a/app/models/account_config.rb b/app/models/account_config.rb index 7781e293..8d41bca4 100644 --- a/app/models/account_config.rb +++ b/app/models/account_config.rb @@ -30,6 +30,7 @@ class AccountConfig < ApplicationRecord ALLOW_TYPED_SIGNATURE = 'allow_typed_signature' ALLOW_TO_RESUBMIT = 'allow_to_resubmit' ALLOW_TO_DECLINE_KEY = 'allow_to_decline' + ALLOW_TO_DELEGATE_KEY = 'allow_to_delegate' ALLOW_TO_PARTIAL_DOWNLOAD_KEY = 'allow_to_partial_download' SUBMITTER_REMINDERS = 'submitter_reminders' ENFORCE_SIGNING_ORDER_KEY = 'enforce_signing_order' diff --git a/app/models/submission_event.rb b/app/models/submission_event.rb index 8aece806..e88d192f 100644 --- a/app/models/submission_event.rb +++ b/app/models/submission_event.rb @@ -64,6 +64,7 @@ class SubmissionEvent < ApplicationRecord invite_party: 'invite_party', complete_form: 'complete_form', decline_form: 'decline_form', + delegate_form: 'delegate_form', api_complete_form: 'api_complete_form' }, scope: false diff --git a/app/models/submitter.rb b/app/models/submitter.rb index 205a3b0c..0e4bc6c2 100644 --- a/app/models/submitter.rb +++ b/app/models/submitter.rb @@ -44,6 +44,7 @@ class Submitter < ApplicationRecord belongs_to :account has_one :template, through: :submission has_one :search_entry, as: :record, inverse_of: :record, dependent: :destroy if SearchEntry.table_exists? + has_many :submitter_versions, dependent: :destroy attribute :values, :string, default: -> { {} } attribute :preferences, :string, default: -> { {} } diff --git a/app/models/submitter_version.rb b/app/models/submitter_version.rb new file mode 100644 index 00000000..ea67a745 --- /dev/null +++ b/app/models/submitter_version.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: submitter_versions +# +# id :bigint not null, primary key +# email :string +# name :string +# phone :string +# slug :string not null +# created_at :datetime not null +# updated_at :datetime not null +# submitter_id :bigint not null +# +# Indexes +# +# index_submitter_versions_on_slug (slug) +# index_submitter_versions_on_submitter_id (submitter_id) +# +# Foreign Keys +# +# fk_rails_... (submitter_id => submitters.id) +# +class SubmitterVersion < ApplicationRecord + belongs_to :submitter +end diff --git a/app/views/accounts/show.html.erb b/app/views/accounts/show.html.erb index 8ee0e45f..ba9a9c49 100644 --- a/app/views/accounts/show.html.erb +++ b/app/views/accounts/show.html.erb @@ -131,23 +131,50 @@ <% end %> <% end %> - <% if !Docuseal.multitenant? || can?(:manage, :disable_decline) %> - <% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ALLOW_TO_DECLINE_KEY) %> - <% if can?(:manage, account_config) %> - <%= form_for account_config, url: account_configs_path, method: :post do |f| %> - <%= f.hidden_field :key %> -
-
- <%= t('allow_to_decline_documents') %> - - <%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %> - -
+ <% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ALLOW_TO_DECLINE_KEY) %> + <% if can?(:manage, account_config) %> + <%= form_for account_config, url: account_configs_path, method: :post do |f| %> + <%= f.hidden_field :key %> +
+
+ <%= t('allow_to_decline_documents') %> + + <%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %> + +
+ <% if !Docuseal.multitenant? || can?(:manage, :disable_decline) %> <%= f.check_box :value, class: 'toggle', checked: account_config.value != false %> + <% else %> + " data-turbo="false" data-tip="<%= I18n.t('unlock_with_docuseal_pro') %>" data-on="change" class="flex tooltip"> + <%= f.check_box :value, class: 'toggle pointer-events-none', checked: account_config.value != false, disabled: true %> + + <% end %> +
+ <% end %> + <% end %> + <% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ALLOW_TO_DELEGATE_KEY) %> + <% if can?(:manage, account_config) %> + <%= form_for account_config, url: account_configs_path, method: :post do |f| %> + <%= f.hidden_field :key %> +
+
+ <%= t('allow_to_delegate_documents') %> + + <%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %> +
- <% end %> + <% if !Docuseal.multitenant? || can?(:manage, :delegate_form) %> + + <%= f.check_box :value, class: 'toggle', checked: account_config.value == true %> + + <% else %> + " data-turbo="false" data-tip="<%= I18n.t('unlock_with_docuseal_pro') %>" class="flex tooltip"> + <%= f.check_box :value, class: 'toggle pointer-events-none', checked: account_config.value == true, disabled: true %> + + <% end %> +
<% end %> <% end %> <% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::FORM_PREFILL_SIGNATURE_KEY) %> diff --git a/app/views/icons/_user_share.html.erb b/app/views/icons/_user_share.html.erb new file mode 100644 index 00000000..ecb37945 --- /dev/null +++ b/app/views/icons/_user_share.html.erb @@ -0,0 +1 @@ + diff --git a/app/views/submission_events/index.html.erb b/app/views/submission_events/index.html.erb index 8dec75f3..5c454288 100644 --- a/app/views/submission_events/index.html.erb +++ b/app/views/submission_events/index.html.erb @@ -20,10 +20,13 @@ <% end %>

+ <% submitter_versions_index = @submission.submitters.each_with_object({}) { |s, h| h[s.id] = s.submitter_versions.to_a.sort_by(&:created_at) } %> <% @submission.submission_events.order(:event_timestamp).each do |event| %> <% submitter = @submission.submitters.find { |e| e.id == event.submitter_id } %> <% bg_class = event_colors[submitters_uuids.index(submitter.uuid) % event_colors.length] %> - <% submitter_name = event.event_type.include?('sms') || event.event_type.include?('phone') ? (event.data['phone'] || submitter.phone) : (submitter.name || submitter.email || submitter.phone) %> + <% versions = submitter_versions_index[submitter.id] || [] %> + <% active_version = versions.find { |v| v.created_at > event.event_timestamp } %> + <% submitter_name = event.event_type.include?('sms') || event.event_type.include?('phone') ? (event.data['phone'] || active_version&.phone || submitter.phone) : (active_version&.name || active_version&.email || active_version&.phone || submitter.name || submitter.email || submitter.phone) %>
  • <%= svg_icon(SubmissionEventsController::SUBMISSION_EVENT_ICONS.fetch(event.event_type, 'circle_dot'), class: 'w-4 h-4') %> @@ -43,6 +46,9 @@ <%= t('submission_event_names.complete_verification_by_html', provider: event.data['method'], submitter_name:) %> <% elsif event.event_type == 'invite_party' && (invited_submitter = @submission.submitters.find { |e| e.uuid == event.data['uuid'] }) && (name = @submission.template_submitters.find { |e| e['uuid'] == event.data['uuid'] }&.dig('name')) %> <%= t('submission_event_names.invite_party_by_html', invited_submitter_name: [invited_submitter.name || invited_submitter.email || invited_submitter.phone, name].join(' '), submitter_name:) %> + <% elsif event.event_type == 'delegate_form' %> + <% delegate_from = event.data['old_email'].presence || (versions.reverse.find { |v| v.created_at <= event.event_timestamp }&.then { |v| v.name || v.phone }) %> + <%= t('submission_event_names.delegate_form_by_html', from: delegate_from, to: event.data['email']) %> <% elsif event.event_type.include?('send_') %> <%= t("submission_event_names.#{event.event_type}_to_html", submitter_name:) %> <% elsif event.event_type.start_with?('bounce_') || event.event_type.start_with?('complaint_') %> diff --git a/app/views/submit_form/_delegate_form.html.erb b/app/views/submit_form/_delegate_form.html.erb new file mode 100644 index 00000000..7e708984 --- /dev/null +++ b/app/views/submit_form/_delegate_form.html.erb @@ -0,0 +1,9 @@ +<%= form_for '', url: submit_form_delegate_index_path(submitter.slug), method: :post do |f| %> +
    + <%= f.label :email, t(:enter_the_email_address_of_the_person_you_want_to_delegate_to), class: 'label' %> + <%= f.email_field :email, required: true, class: 'base-input w-full', dir: 'auto', placeholder: t('email') %> +
    + + <%= f.button button_title(title: t(:delegate)), class: 'base-button' %> + +<% end %> diff --git a/app/views/submit_form/delegated.html.erb b/app/views/submit_form/delegated.html.erb new file mode 100644 index 00000000..de96b574 --- /dev/null +++ b/app/views/submit_form/delegated.html.erb @@ -0,0 +1,21 @@ +
    +
    +
    +
    + <%= render 'start_form/banner' %> +
    +
    +
    +
    + <%= svg_icon('user_share', class: 'w-10 h-10') %> +
    +
    +

    <%= @submitter.submission.name || @submitter.submission.template.name %>

    +

    <%= t('document_has_been_delegated_on_html', time: l(@submitter.submission_events.order(event_timestamp: :desc).find_by!(event_type: :delegate_form).event_timestamp, format: :long)) %>

    +
    +
    +
    +
    +
    +
    +<%= render 'shared/attribution', link_path: '/start', account: @submitter.account %> diff --git a/app/views/submit_form/show.html.erb b/app/views/submit_form/show.html.erb index 54fc3e78..85db38ef 100644 --- a/app/views/submit_form/show.html.erb +++ b/app/views/submit_form/show.html.erb @@ -9,6 +9,7 @@ <% schema = Submissions.filtered_conditions_schema(@submitter.submission, values:, include_submitter_uuid: @submitter.uuid) %> <% font_scale = 1000.0 / PdfUtils::US_LETTER_W %> <% decline_modal_checkbox_uuid = nil %> +<% delegate_modal_checkbox_uuid = nil %>
    @@ -20,42 +21,85 @@ <%= @submitter.submission.name || @submitter.submission.template.name %>
    - <% if @form_configs[:with_decline] %> - - <% end %> - <% if @form_configs[:with_partial_download] %> - - - <%= svg_icon('download', class: 'w-6 h-6 inline md:hidden') %> - - - - + <% if @form_configs[:with_delegate] %> + + <% if @form_configs[:with_decline] %> + + <% end %> + <% if @form_configs[:with_partial_download] %> + + + <%= svg_icon('download', class: 'w-5 h-5') %> + + + + <% end %> + <% else %> + <% if @form_configs[:with_decline] %> + + <% end %> + <% if @form_configs[:with_partial_download] %> + + + <%= svg_icon('download', class: 'w-6 h-6 inline md:hidden') %> + + + + + <% end %> <% end %>
    <% end %> <% schema.each do |item| %> @@ -110,4 +154,9 @@ <%= render 'submit_form/decline_form', submitter: @submitter %> <% end %> <% end %> +<% if @form_configs[:with_delegate] %> + <%= render 'shared/html_modal', title: t(:delegate), uuid: delegate_modal_checkbox_uuid do %> + <%= render 'submit_form/delegate_form', submitter: @submitter %> + <% end %> +<% end %> <%= render 'scripts/autosize_field' %> diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 0b91d8fb..22ec12d5 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -163,10 +163,13 @@ en: &en download: Download decline: Decline declined: Declined + delegate: Delegate + enter_the_email_address_of_the_person_you_want_to_delegate_to: Enter the email address of the person you want to delegate to decline_reason: Decline reason provide_a_reason: Provide a reason notify_the_sender_with_the_reason_you_declined: Notify the sender with the reason you declined form_has_been_declined_on_html: 'Form has been declined on %{time}' + document_has_been_delegated_on_html: 'Document has been delegated on %{time}' name_declined_by_submitter: '"%{name}" declined by %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" has been declined by %{submitter} with the following reason:' completed_successfully: Completed Successfully @@ -201,6 +204,7 @@ en: &en allow_typed_text_signatures: Allow typed text signatures allow_to_resubmit_completed_forms: Allow to resubmit completed forms allow_to_decline_documents: Allow to decline documents + allow_to_delegate_documents: Allow to delegate documents remember_and_pre_fill_signatures: Remember and pre-fill signatures require_authentication_for_file_download_links: Require authentication for file download links combine_completed_documents_and_audit_log: Combine completed documents and Audit Log @@ -841,6 +845,7 @@ en: &en allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Allow signers to create signatures by typing their name instead of drawing or uploading one. allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Allow signers to resubmit forms after completion, useful when corrections or multiple submissions are needed. allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Allow recipients to decline signing a document. The decline reason notification will be sent to the signature requester. + allow_recipients_to_delegate_documents: Allow recipients to delegate documents to a different person via email. This can be useful when the original recipient needs to pass the document to the authorized signer. save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: "Save a user's signature and automatically pre-fill it in future signing sessions." make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: Make document download links expire after 40 minutes to prevent long-term access and enhance security. require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: Require authentication with user login or API key to access the document download links. @@ -954,6 +959,8 @@ en: &en complete_kba_by_html: 'KBA completed by %{submitter_name}' fail_kba_by_html: 'KBA failed by %{submitter_name}' api_complete_form_by_html: 'Submission completed via API by %{submitter_name}' + decline_form_by_html: 'Form declined by %{submitter_name}' + delegate_form_by_html: 'Delegated from %{from} to %{to}' import_list: select_worksheet: Select Worksheet open: Open @@ -1192,11 +1199,14 @@ es: &es downloading: Descargando download: Descargar decline: Rechazar + delegate: Delegar + enter_the_email_address_of_the_person_you_want_to_delegate_to: Ingrese la dirección de correo electrónico de la persona a quien desea delegar declined: Rechazado decline_reason: Motivo de rechazo provide_a_reason: Proporcione un motivo notify_the_sender_with_the_reason_you_declined: Notifique al remitente con el motivo por el que rechazó form_has_been_declined_on_html: 'El formulario ha sido rechazado el %{time}' + document_has_been_delegated_on_html: 'El documento ha sido delegado el %{time}' name_declined_by_submitter: '"%{name}" rechazado por %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" ha sido rechazado por %{submitter} con el siguiente motivo:' completed_successfully: Completado exitosamente @@ -1231,6 +1241,7 @@ es: &es allow_typed_text_signatures: Permitir firmas de texto mecanografiadas allow_to_resubmit_completed_forms: Permitir reenviar formularios completados allow_to_decline_documents: Permitir rechazar documentos + allow_to_delegate_documents: Permitir delegar documentos remember_and_pre_fill_signatures: Recordar y prellenar firmas require_authentication_for_file_download_links: Requerir autenticación para enlaces de descarga de archivos combine_completed_documents_and_audit_log: Combinar documentos completados y Registro de Auditoría @@ -1868,6 +1879,7 @@ es: &es allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Permitir que los firmantes creen firmas escribiendo su nombre en lugar de dibujar o subir una. allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Permitir que los firmantes vuelvan a enviar formularios después de completarlos, útil cuando se necesitan correcciones o múltiples envíos. allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Permitir que los destinatarios rechacen firmar un documento. La notificación del motivo del rechazo se enviará al solicitante de la firma. + allow_recipients_to_delegate_documents: Permitir que los destinatarios deleguen documentos a otra persona por correo electrónico. Esto puede ser útil cuando el destinatario original necesita pasar el documento al firmante autorizado. save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: Guardar la firma de un usuario y rellenarla automáticamente en futuras sesiones de firma. make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: Hacer que los enlaces de descarga de documentos caduquen después de 40 minutos para evitar el acceso a largo plazo y mejorar la seguridad. require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: Requerir autenticación con inicio de sesión de usuario o clave API para acceder a los enlaces de descarga de documentos. @@ -1976,6 +1988,7 @@ es: &es invite_party_by_html: 'Invitado %{invited_submitter_name} por %{submitter_name}' complete_form_by_html: 'Envío completado por %{submitter_name}' api_complete_form_by_html: 'Envío completado vía API por %{submitter_name}' + delegate_form_by_html: 'Delegado de %{from} a %{to}' start_verification_by_html: 'Verificación de identidad iniciada por %{submitter_name}' complete_verification_by_html: 'Verificación de identidad completada por %{submitter_name} con %{provider}' start_kba_by_html: 'KBA iniciado por %{submitter_name}' @@ -2219,11 +2232,14 @@ it: &it downloading: Scaricamento download: Scarica decline: Rifiuta + delegate: Delega + enter_the_email_address_of_the_person_you_want_to_delegate_to: Inserisci l'indirizzo email della persona a cui vuoi delegare declined: Rifiutato decline_reason: Motivo del rifiuto provide_a_reason: Fornisci un motivo notify_the_sender_with_the_reason_you_declined: Informare il mittente del motivo per cui hai rifiutato form_has_been_declined_on_html: 'Il modulo è stato rifiutato il %{time}' + document_has_been_delegated_on_html: 'Il documento è stato delegato il %{time}' name_declined_by_submitter: '"%{name}" rifiutato da %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" è stato rifiutato da %{submitter} per il seguente motivo:' completed_successfully: Completato con successo @@ -2258,6 +2274,7 @@ it: &it allow_typed_text_signatures: Permettere firme con testo digitato allow_to_resubmit_completed_forms: Permettere di reinviare i moduli completati allow_to_decline_documents: Permettere di rifiutare i documenti + allow_to_delegate_documents: Permettere di delegare i documenti remember_and_pre_fill_signatures: Ricordare e precompilare le firme require_authentication_for_file_download_links: "Richiedere l'autenticazione per i link di download dei file" combine_completed_documents_and_audit_log: Combinare i documenti completati e il Registro di Audit @@ -2896,6 +2913,7 @@ it: &it allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Consentire ai firmatari di creare firme digitando il proprio nome invece di disegnarle o caricarle. allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Consentire ai firmatari di reinviare i moduli dopo il completamento, utile quando sono necessarie correzioni o più invii. allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Consentire ai destinatari di rifiutare di firmare un documento. La notifica del motivo del rifiuto verrà inviata al richiedente della firma. + allow_recipients_to_delegate_documents: Consentire ai destinatari di delegare i documenti a un'altra persona tramite e-mail. Ciò può essere utile quando il destinatario originale deve passare il documento al firmatario autorizzato. save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: Salvare la firma di un utente e compilarla automaticamente nelle future sessioni di firma. make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: "Fare in modo che i link di download dei documenti scadano dopo 40 minuti per prevenire l'accesso a lungo termine e migliorare la sicurezza." require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: "Richiedere l'autenticazione con login utente o chiave API per accedere ai link di download dei documenti." @@ -3004,6 +3022,7 @@ it: &it invite_party_by_html: 'Invitato %{invited_submitter_name} da %{submitter_name}' complete_form_by_html: 'Invio completato da %{submitter_name}' api_complete_form_by_html: 'Invio completato tramite API da %{submitter_name}' + delegate_form_by_html: 'Delegato da %{from} a %{to}' start_verification_by_html: "Verifica dell'identità iniziata da %{submitter_name}" complete_verification_by_html: "Verifica dell'identità completata da %{submitter_name} con %{provider}" start_kba_by_html: "KBA avviata da %{submitter_name}" @@ -3247,11 +3266,14 @@ fr: &fr downloading: Téléchargement download: Télécharger decline: Refuser + delegate: Déléguer + enter_the_email_address_of_the_person_you_want_to_delegate_to: Saisissez l'adresse e-mail de la personne à qui vous souhaitez déléguer declined: Refusé decline_reason: Motif du refus provide_a_reason: Fournissez un motif notify_the_sender_with_the_reason_you_declined: Notifier l’expéditeur avec le motif du refus form_has_been_declined_on_html: Le formulaire a été refusé le %{time} + document_has_been_delegated_on_html: Le document a été délégué le %{time} name_declined_by_submitter: '"%{name}" refusé par %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" a été refusé par %{submitter} pour le motif suivant :' completed_successfully: Terminé avec succès @@ -3286,6 +3308,7 @@ fr: &fr allow_typed_text_signatures: Autoriser les signatures tapées allow_to_resubmit_completed_forms: Autoriser la nouvelle soumission des formulaires complétés allow_to_decline_documents: Autoriser le refus de documents + allow_to_delegate_documents: Autoriser la délégation de documents remember_and_pre_fill_signatures: Mémoriser et pré-remplir les signatures require_authentication_for_file_download_links: Exiger une authentification pour les liens de téléchargement combine_completed_documents_and_audit_log: Combiner les documents complétés et le journal d’audit @@ -3919,6 +3942,7 @@ fr: &fr allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Autoriser les signataires à créer des signatures en tapant leur nom au lieu de dessiner ou téléverser une signature. allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Autoriser les signataires à soumettre à nouveau les formulaires après finalisation, utile pour des corrections ou soumissions multiples. allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Autoriser les destinataires à refuser de signer un document. Le motif du refus sera notifié au demandeur de signature. + allow_recipients_to_delegate_documents: Autoriser les destinataires à déléguer des documents à une autre personne par e-mail. Cela peut être utile lorsque le destinataire d'origine doit transmettre le document au signataire autorisé. save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: Mémoriser la signature d’un utilisateur et la pré‑remplir automatiquement lors de futures sessions de signature. make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: Faire expirer les liens de téléchargement des documents après 40 minutes pour éviter un accès à long terme et renforcer la sécurité. require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: Exiger une authentification par connexion utilisateur ou clé API pour accéder aux liens de téléchargement des documents. @@ -4030,6 +4054,7 @@ fr: &fr start_verification_by_html: "Vérification d’identité démarrée par %{submitter_name}" complete_verification_by_html: "Vérification d’identité terminée par %{submitter_name} avec %{provider}" api_complete_form_by_html: "Soumission terminée via API par %{submitter_name}" + delegate_form_by_html: "Délégué de %{from} à %{to}" start_kba_by_html: "KBA démarré par %{submitter_name}" complete_kba_by_html: "KBA terminé par %{submitter_name}" fail_kba_by_html: "KBA échoué par %{submitter_name}" @@ -4271,11 +4296,14 @@ pt: &pt downloading: Baixando download: Baixar decline: Recusar + delegate: Delegar + enter_the_email_address_of_the_person_you_want_to_delegate_to: Insira o endereço de e-mail da pessoa para quem deseja delegar declined: Recusado decline_reason: Motivo da recusa provide_a_reason: Forneça um motivo notify_the_sender_with_the_reason_you_declined: Notifique o remetente com o motivo da sua recusa form_has_been_declined_on_html: "O formulário foi recusado em %{time}" + document_has_been_delegated_on_html: "O documento foi delegado em %{time}" name_declined_by_submitter: '"%{name}" foi recusado por %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" foi recusado por %{submitter} com o seguinte motivo:' completed_successfully: Concluído com sucesso @@ -4310,6 +4338,7 @@ pt: &pt allow_typed_text_signatures: Permitir assinaturas digitadas allow_to_resubmit_completed_forms: Permitir reenviar formulários concluídos allow_to_decline_documents: Permitir recusar documentos + allow_to_delegate_documents: Permitir delegar documentos remember_and_pre_fill_signatures: Lembrar e preencher assinaturas automaticamente require_authentication_for_file_download_links: Requerer autenticação para links de download de arquivos combine_completed_documents_and_audit_log: Combinar documentos concluídos e log de auditoria @@ -4947,6 +4976,7 @@ pt: &pt allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Permitir que os signatários criem assinaturas digitando seu nome em vez de desenhá-las ou carregá-las. allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Permitir que os signatários reenviem formulários após o término, útil quando são necessárias correções ou múltiplos envios. allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Permitir que os destinatários recusem assinar um documento. A notificação do motivo da recusa será enviada ao solicitante da assinatura. + allow_recipients_to_delegate_documents: Permitir que os destinatários deleguem documentos a outra pessoa por e-mail. Isso pode ser útil quando o destinatário original precisa passar o documento ao signatário autorizado. save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: Salvar a assinatura de um usuário e preenchê-la automaticamente em futuras sessões de assinatura. make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: Fazer os links de download de documentos expirarem após 40 minutos para evitar o acesso prolongado e aumentar a segurança. require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: Exigir autenticação com login do usuário ou chave API para acessar os links de download de documentos. @@ -5057,6 +5087,7 @@ pt: &pt start_verification_by_html: 'Verificação de identidade iniciada por %{submitter_name}' complete_verification_by_html: 'Verificação de identidade concluída por %{submitter_name} com %{provider}' api_complete_form_by_html: 'Submissão concluída via API por %{submitter_name}' + delegate_form_by_html: 'Delegado de %{from} para %{to}' start_kba_by_html: 'KBA iniciada por %{submitter_name}' complete_kba_by_html: 'KBA concluída por %{submitter_name}' fail_kba_by_html: 'KBA reprovada por %{submitter_name}' @@ -5298,11 +5329,14 @@ de: &de downloading: Wird heruntergeladen download: Download decline: Ablehnen + delegate: Delegieren + enter_the_email_address_of_the_person_you_want_to_delegate_to: Geben Sie die E-Mail-Adresse der Person ein, an die Sie delegieren möchten declined: Abgelehnt decline_reason: Ablehnungsgrund provide_a_reason: Geben Sie einen Grund an notify_the_sender_with_the_reason_you_declined: Den Absender über den Grund Ihrer Ablehnung benachrichtigen form_has_been_declined_on_html: "Das Formular wurde am %{time} abgelehnt" + document_has_been_delegated_on_html: "Das Dokument wurde am %{time} delegiert" name_declined_by_submitter: '"%{name}" wurde von %{submitter} abgelehnt' name_declined_by_submitter_with_the_following_reason: '"%{name}" wurde von %{submitter} mit folgendem Grund abgelehnt:' completed_successfully: Erfolgreich abgeschlossen @@ -5337,6 +5371,7 @@ de: &de allow_typed_text_signatures: Getippte Unterschriften zulassen allow_to_resubmit_completed_forms: Erneutes Einreichen abgeschlossener Formulare zulassen allow_to_decline_documents: Ablehnen von Dokumenten erlauben + allow_to_delegate_documents: Delegieren von Dokumenten erlauben remember_and_pre_fill_signatures: Signaturen merken und vorab ausfüllen require_authentication_for_file_download_links: Authentifizierung für Dateidownload-Links erforderlich combine_completed_documents_and_audit_log: Abgeschlossene Dokumente und Prüfprotokoll kombinieren @@ -5974,6 +6009,7 @@ de: &de allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Unterzeichner dürfen Signaturen erstellen, indem sie ihren Namen eingeben, statt eine Signatur zu zeichnen oder hochzuladen. allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Unterzeichner dürfen Formulare nach Abschluss erneut einreichen – nützlich für Korrekturen oder mehrere Einreichungen. allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Empfänger dürfen die Unterzeichnung eines Dokuments ablehnen. Die Benachrichtigung mit dem Ablehnungsgrund wird an den Anforderer der Signatur gesendet. + allow_recipients_to_delegate_documents: Empfängern erlauben, Dokumente per E-Mail an eine andere Person weiterzuleiten. Dies kann nützlich sein, wenn der ursprüngliche Empfänger das Dokument an den autorisierten Unterzeichner weiterleiten muss. save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: Die Signatur eines Benutzers speichern und sie in zukünftigen Signatursitzungen automatisch vorausfüllen. make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: Download-Links für Dokumente nach 40 Minuten ablaufen lassen, um langfristigen Zugriff zu verhindern und die Sicherheit zu erhöhen. require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: Authentifizierung mit Benutzeranmeldung oder API-Schlüssel erforderlich, um auf die Download-Links für Dokumente zuzugreifen. @@ -6084,6 +6120,7 @@ de: &de start_verification_by_html: 'Identitätsüberprüfung gestartet von %{submitter_name}' complete_verification_by_html: 'Identitätsüberprüfung abgeschlossen von %{submitter_name} mit %{provider}' api_complete_form_by_html: 'Einreichung über API abgeschlossen von %{submitter_name}' + delegate_form_by_html: 'Delegiert von %{from} an %{to}' start_kba_by_html: 'KBA gestartet von %{submitter_name}' complete_kba_by_html: 'KBA abgeschlossen von %{submitter_name}' fail_kba_by_html: 'KBA fehlgeschlagen von %{submitter_name}' @@ -6190,11 +6227,14 @@ pl: hi_there: Cześć, download: Pobierz decline: Odrzuć + delegate: Deleguj + enter_the_email_address_of_the_person_you_want_to_delegate_to: Wprowadź adres e-mail osoby, do której chcesz delegować declined: Odrzucono decline_reason: Powód odrzucenia provide_a_reason: Podaj powód notify_the_sender_with_the_reason_you_declined: Powiadom nadawcę o powodzie, dla którego odrzuciłeś form_has_been_declined_on_html: 'Formularz został odrzucony o %{time}' + document_has_been_delegated_on_html: 'Dokument został oddelegowany o %{time}' name_declined_by_submitter: '"%{name}" odrzucono przez %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" został odrzucony przez %{submitter} z następującym powodem:' email: E-mail @@ -6287,11 +6327,14 @@ uk: hi_there: Привіт, download: Завантажити decline: Відхилити + delegate: Делегувати + enter_the_email_address_of_the_person_you_want_to_delegate_to: Введіть адресу електронної пошти особи, якій ви хочете делегувати declined: Відхилено decline_reason: Причина відхилення provide_a_reason: Надайте причину notify_the_sender_with_the_reason_you_declined: Повідомте відправника про причину, з якої ви відхилили form_has_been_declined_on_html: 'Форму було відхилено о %{time}' + document_has_been_delegated_on_html: 'Документ було делеговано о %{time}' name_declined_by_submitter: '"%{name}" відхилено %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" було відхилено %{submitter} з такою причиною:' email: E-mail @@ -6384,11 +6427,14 @@ cs: hi_there: Ahoj, download: Stáhnout decline: Odmítnout + delegate: Delegovat + enter_the_email_address_of_the_person_you_want_to_delegate_to: Zadejte e-mailovou adresu osoby, na kterou chcete delegovat declined: Odmítnuto decline_reason: Důvod odmítnutí provide_a_reason: Uveďte důvod notify_the_sender_with_the_reason_you_declined: Informujte odesílatele o důvodu odmítnutí form_has_been_declined_on_html: 'Formulář byl odmítnut dne %{time}' + document_has_been_delegated_on_html: 'Dokument byl delegován dne %{time}' name_declined_by_submitter: '"%{name}" odmítnuto %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" bylo odmítnuto %{submitter} s následujícím důvodem:' email: E-mail @@ -6481,11 +6527,14 @@ he: hi_there: שלום, download: הורד decline: דחייה + delegate: הואלה + enter_the_email_address_of_the_person_you_want_to_delegate_to: הזן את כתובת הדוא"ל של האדם שברצונך להאציל אליו declined: דחוי decline_reason: סיבת דחייה provide_a_reason: אנא ספק סיבה notify_the_sender_with_the_reason_you_declined: הודע לשולח על הסיבה לדחייה form_has_been_declined_on_html: 'הטופס נדחה בתאריך %{time}' + document_has_been_delegated_on_html: 'המסמך הועבר בתאריך %{time}' name_declined_by_submitter: '"%{name}" נדחה על ידי %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" נדחה על ידי %{submitter} עם הסיבה הבאה:' email: דוא"ל @@ -6714,11 +6763,14 @@ nl: &nl downloading: Downloaden download: Downloaden decline: Weigeren + delegate: Delegeren + enter_the_email_address_of_the_person_you_want_to_delegate_to: Voer het e-mailadres in van de persoon aan wie u wilt delegeren declined: Geweigerd decline_reason: Reden van weigering provide_a_reason: Geef een reden op notify_the_sender_with_the_reason_you_declined: Informeer de verzender over de reden van uw weigering form_has_been_declined_on_html: Formulier is geweigerd op %{time} + document_has_been_delegated_on_html: Document is gedelegeerd op %{time} name_declined_by_submitter: '"%{name}" geweigerd door %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" is geweigerd door %{submitter} met de volgende reden:' completed_successfully: Succesvol voltooid @@ -6753,6 +6805,7 @@ nl: &nl allow_typed_text_signatures: Handtekeningen met getypte tekst toestaan allow_to_resubmit_completed_forms: Opnieuw indienen van voltooide formulieren toestaan allow_to_decline_documents: Weigeren van documenten toestaan + allow_to_delegate_documents: Delegeren van documenten toestaan remember_and_pre_fill_signatures: Handtekeningen onthouden en vooraf invullen require_authentication_for_file_download_links: Authenticatie vereisen voor links naar bestandsdownloads combine_completed_documents_and_audit_log: Voltooide documenten en auditlogboek combineren @@ -7386,6 +7439,7 @@ nl: &nl allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Sta ondertekenaars toe handtekeningen te maken door hun naam te typen in plaats van te tekenen of te uploaden. allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Sta ondertekenaars toe formulieren na voltooiing opnieuw in te dienen; nuttig wanneer correcties of meerdere inzendingen nodig zijn. allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Sta ontvangers toe het ondertekenen van een document te weigeren. De melding met de reden van weigering wordt naar de aanvrager van de handtekening gestuurd. + allow_recipients_to_delegate_documents: Sta ontvangers toe documenten per e-mail te delegeren aan een andere persoon. Dit kan handig zijn wanneer de oorspronkelijke ontvanger het document moet doorgeven aan de gemachtigde ondertekenaar. save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: Sla de handtekening van een gebruiker op en vul deze automatisch vooraf in bij toekomstige ondertekeningssessies. make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: Laat links voor het downloaden van documenten na 40 minuten verlopen om langdurige toegang te voorkomen en de beveiliging te verbeteren. require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: Authenticatie met gebruikerslogin of API-sleutel vereisen om toegang te krijgen tot de documentdownloadlinks. @@ -7496,6 +7550,7 @@ nl: &nl start_verification_by_html: "Identiteitsverificatie gestart door %{submitter_name}" complete_verification_by_html: "Identiteitsverificatie voltooid door %{submitter_name} met %{provider}" api_complete_form_by_html: "Inzending via API voltooid door %{submitter_name}" + delegate_form_by_html: "Gedelegeerd van %{from} naar %{to}" start_kba_by_html: "KBA gestart door %{submitter_name}" complete_kba_by_html: "KBA voltooid door %{submitter_name}" fail_kba_by_html: "KBA mislukt door %{submitter_name}" @@ -7602,11 +7657,14 @@ ar: hi_there: مرحبا, download: تحميل decline: رفض + delegate: تفويض + enter_the_email_address_of_the_person_you_want_to_delegate_to: أدخل عنوان البريد الإلكتروني للشخص الذي تريد التفويض إليه declined: مرفوض decline_reason: سبب الرفض provide_a_reason: قدم سببًا notify_the_sender_with_the_reason_you_declined: أخطر المرسل بسبب الرفض form_has_been_declined_on_html: 'تم رفض النموذج في %{time}' + document_has_been_delegated_on_html: 'تم تفويض المستند في %{time}' name_declined_by_submitter: '"%{name}" مرفوض بواسطة %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" تم رفضه بواسطة %{submitter} مع السبب التالي:' email: البريد الإلكتروني @@ -7699,11 +7757,14 @@ ko: hi_there: 안녕하세요, download: 다운로드 decline: 거절 + delegate: 위임 + enter_the_email_address_of_the_person_you_want_to_delegate_to: 위임할 사람의 이메일 주소를 입력하세요 declined: 거절됨 decline_reason: 거절 사유 provide_a_reason: 사유를 제공하세요 notify_the_sender_with_the_reason_you_declined: 거절한 이유로 발신자에게 알리기 form_has_been_declined_on_html: '양식이 %{time}에 거절되었습니다.' + document_has_been_delegated_on_html: '문서가 %{time}에 위임되었습니다.' name_declined_by_submitter: '"%{name}"이(가) %{submitter}에 의해 거절되었습니다.' name_declined_by_submitter_with_the_following_reason: '"%{name}"이(가) %{submitter}에 의해 다음과 같은 사유로 거절되었습니다:' email: 이메일 @@ -7796,11 +7857,14 @@ ja: hi_there: こんにちは download: ダウンロード decline: 辞退 + delegate: 委任 + enter_the_email_address_of_the_person_you_want_to_delegate_to: 委任したい人のメールアドレスを入力してください declined: 辞退済み decline_reason: 辞退の理由 provide_a_reason: 理由を入力してください notify_the_sender_with_the_reason_you_declined: 辞退理由を送信者に通知してください form_has_been_declined_on_html: '%{time} にフォームが辞退されました' + document_has_been_delegated_on_html: '%{time} にドキュメントが委任されました' name_declined_by_submitter: '"%{name}" は %{submitter} により辞退されました' name_declined_by_submitter_with_the_following_reason: '"%{name}" は %{submitter} により次の理由で辞退されました:' email: メール diff --git a/config/routes.rb b/config/routes.rb index ff7e1089..ee700493 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -145,8 +145,10 @@ Rails.application.routes.draw do resources :values, only: %i[index], controller: 'submit_form_values' resources :download, only: %i[index], controller: 'submit_form_download' resources :decline, only: %i[create], controller: 'submit_form_decline' + resources :delegate, only: %i[create], controller: 'submit_form_delegate' resources :invite, only: %i[create], controller: 'submit_form_invite' get :completed + get :delegated end resources :submit_form_draw_signature, only: %i[show], path: 'p', param: 'slug' diff --git a/db/migrate/20260327100000_create_submitter_versions.rb b/db/migrate/20260327100000_create_submitter_versions.rb new file mode 100644 index 00000000..40ae11f9 --- /dev/null +++ b/db/migrate/20260327100000_create_submitter_versions.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class CreateSubmitterVersions < ActiveRecord::Migration[8.1] + def change + create_table :submitter_versions do |t| + t.references :submitter, null: false, foreign_key: true + t.string :slug, null: false + t.string :email + t.string :name + t.string :phone + + t.timestamps + end + + add_index :submitter_versions, :slug + end +end diff --git a/db/schema.rb b/db/schema.rb index c2c39bc1..2615a0ff 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2026_02_26_193537) do +ActiveRecord::Schema[8.1].define(version: 2026_03_27_100000) do # These are extensions that must be enabled in order to support this database enable_extension "btree_gin" enable_extension "plpgsql" @@ -364,6 +364,18 @@ ActiveRecord::Schema[8.1].define(version: 2026_02_26_193537) do t.index ["template_id"], name: "index_submissions_on_template_id" end + create_table "submitter_versions", force: :cascade do |t| + t.datetime "created_at", null: false + t.string "email" + t.string "name" + t.string "phone" + t.string "slug", null: false + t.bigint "submitter_id", null: false + t.datetime "updated_at", null: false + t.index ["slug"], name: "index_submitter_versions_on_slug" + t.index ["submitter_id"], name: "index_submitter_versions_on_submitter_id" + end + create_table "submitters", force: :cascade do |t| t.bigint "submission_id", null: false t.string "uuid", null: false @@ -541,12 +553,14 @@ ActiveRecord::Schema[8.1].define(version: 2026_02_26_193537) do add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" add_foreign_key "document_generation_events", "submitters" + add_foreign_key "dynamic_document_versions", "dynamic_documents" add_foreign_key "dynamic_documents", "templates" add_foreign_key "email_events", "accounts" add_foreign_key "email_messages", "accounts" add_foreign_key "email_messages", "users", column: "author_id" add_foreign_key "encrypted_configs", "accounts" add_foreign_key "encrypted_user_configs", "users" + add_foreign_key "mcp_tokens", "users" add_foreign_key "oauth_access_grants", "oauth_applications", column: "application_id" add_foreign_key "oauth_access_grants", "users", column: "resource_owner_id" add_foreign_key "oauth_access_tokens", "oauth_applications", column: "application_id" @@ -556,6 +570,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_02_26_193537) do add_foreign_key "submission_events", "submitters" add_foreign_key "submissions", "templates" add_foreign_key "submissions", "users", column: "created_by_user_id" + add_foreign_key "submitter_versions", "submitters" add_foreign_key "submitters", "submissions" add_foreign_key "template_accesses", "templates" add_foreign_key "template_folders", "accounts" diff --git a/lib/accounts.rb b/lib/accounts.rb index fbbf688c..41b7d761 100644 --- a/lib/accounts.rb +++ b/lib/accounts.rb @@ -175,6 +175,7 @@ module Accounts def can_send_emails?(_account, **_params) return true if Docuseal.multitenant? + return true if Rails.env.development? return true if ENV['SMTP_ADDRESS'].present? EncryptedConfig.exists?(key: EncryptedConfig::EMAIL_SMTP_KEY) diff --git a/lib/submissions/generate_audit_trail.rb b/lib/submissions/generate_audit_trail.rb index ed80a86d..0c7660db 100644 --- a/lib/submissions/generate_audit_trail.rb +++ b/lib/submissions/generate_audit_trail.rb @@ -246,33 +246,25 @@ module Submissions next if submitter.blank? - completed_event = - submission.submission_events.find { |e| e.submitter_id == submitter.id && e.complete_form? } || - SubmissionEvent.new + submission_events = submission.submission_events.select { |e| e.submitter_id == submitter.id } - click_email_event = - submission.submission_events.find { |e| e.submitter_id == submitter.id && e.click_email? } + delegated_event = submission_events.select(&:delegate_form?).max_by(&:event_timestamp) - verify_email_event = - submission.submission_events.find { |e| e.submitter_id == submitter.id && e.email_verified? } + if delegated_event + submission_events = submission_events.select { |e| e.event_timestamp > delegated_event.event_timestamp } + end - is_phone_verified = - submission.template_fields.any? do |e| - e['type'] == 'phone' && e['submitter_uuid'] == submitter.uuid && submitter.values[e['uuid']].present? - end + completed_event = submission_events.find(&:complete_form?) || SubmissionEvent.new - verify_phone_event = - submission.submission_events.find { |e| e.submitter_id == submitter.id && e.phone_verified? } + click_email_event = submission_events.find(&:click_email?) - is_id_verified = - submission.template_fields.any? do |e| - e['type'] == 'verification' && e['submitter_uuid'] == submitter.uuid && submitter.values[e['uuid']].present? - end + verify_email_event = submission_events.find(&:email_verified?) - is_kba_passed = - submission.template_fields.any? do |e| - e['type'] == 'kba' && e['submitter_uuid'] == submitter.uuid && submitter.values[e['uuid']].present? - end + verify_phone_event = submission_events.find(&:phone_verified?) + + is_id_verified = submission_events.any?(&:complete_verification?) + + is_kba_passed = submission_events.any?(&:complete_kba?) info_rows = [ [ @@ -291,7 +283,7 @@ module Submissions submitter.email && (click_email_event || verify_email_event) && { text: "#{I18n.t('email_verification')}: #{I18n.t('verified')}\n" }, - submitter.phone && (is_phone_verified || verify_phone_event) && { + submitter.phone && verify_phone_event && { text: "#{I18n.t('phone_verification')}: #{I18n.t('verified')}\n" }, is_id_verified && { @@ -446,15 +438,23 @@ module Submissions composer.text(I18n.t('event_log'), font_size: 12, padding: [10, 0, 20, 0]) + submitter_versions_index = submission.submitters.each_with_object({}) do |s, h| + h[s.id] = s.submitter_versions.to_a.sort_by(&:created_at) + end + events_data = submission.submission_events.sort_by(&:event_timestamp).filter_map do |event| next if event.event_type.in?(%w[bounce_email complaint_email]) submitter = submission.submitters.find { |e| e.id == event.submitter_id } + versions = submitter_versions_index[submitter.id] || [] + active_version = versions.find { |v| v.created_at > event.event_timestamp } + submitter_name = if event.event_type.include?('sms') || event.event_type.include?('phone') - event.data['phone'] || submitter.phone + event.data['phone'] || active_version&.phone || submitter.phone else - submitter.name || submitter.email || submitter.phone + active_version&.name || active_version&.email || active_version&.phone || + submitter.name || submitter.email || submitter.phone end text = @@ -473,6 +473,10 @@ module Submissions I18n.t("submission_event_names.#{event.event_type}_to_html", submitter_name:), "#{I18n.t(:from)} #{submission.created_by_user.full_name} #{submission.created_by_user.email}" ].join("\n") + elsif event.event_type == 'delegate_form' + from = event.data['old_email'].presence || + versions.reverse.find { |v| v.created_at <= event.event_timestamp }&.then { |v| v.name || v.phone } + I18n.t('submission_event_names.delegate_form_by_html', from:, to: event.data['email']) elsif event.event_type.include?('send_') I18n.t("submission_event_names.#{event.event_type}_to_html", submitter_name:) else diff --git a/lib/submitters/form_configs.rb b/lib/submitters/form_configs.rb index 4db19b32..3ebf9118 100644 --- a/lib/submitters/form_configs.rb +++ b/lib/submitters/form_configs.rb @@ -8,6 +8,7 @@ module Submitters AccountConfig::FORM_PREFILL_SIGNATURE_KEY, AccountConfig::WITH_SIGNATURE_ID, AccountConfig::ALLOW_TO_DECLINE_KEY, + AccountConfig::ALLOW_TO_DELEGATE_KEY, AccountConfig::ENFORCE_SIGNING_ORDER_KEY, AccountConfig::REQUIRE_SIGNING_REASON_KEY, AccountConfig::REUSE_SIGNATURE_KEY, @@ -31,6 +32,7 @@ module Submitters prefill_signature = find_safe_value(configs, AccountConfig::FORM_PREFILL_SIGNATURE_KEY) != false reuse_signature = find_safe_value(configs, AccountConfig::REUSE_SIGNATURE_KEY) != false with_decline = find_safe_value(configs, AccountConfig::ALLOW_TO_DECLINE_KEY) != false + with_delegate = find_safe_value(configs, AccountConfig::ALLOW_TO_DELEGATE_KEY) == true with_partial_download = find_safe_value(configs, AccountConfig::ALLOW_TO_PARTIAL_DOWNLOAD_KEY) != false with_signature_id = find_safe_value(configs, AccountConfig::WITH_SIGNATURE_ID) == true require_signing_reason = find_safe_value(configs, AccountConfig::REQUIRE_SIGNING_REASON_KEY) == true @@ -42,7 +44,7 @@ module Submitters policy_links = find_safe_value(configs, AccountConfig::POLICY_LINKS_KEY) attrs = { completed_button:, with_typed_signature:, with_confetti:, - reuse_signature:, with_decline:, with_partial_download:, + reuse_signature:, with_decline:, with_delegate:, with_partial_download:, policy_links:, enforce_signing_order:, completed_message:, require_signing_reason:, prefill_signature:, with_submitter_timezone:, with_signature_id_reason:, with_signature_id:, with_field_labels:, with_timestamp_seconds: } diff --git a/spec/system/signing_form_spec.rb b/spec/system/signing_form_spec.rb index 7af76c2f..c5ebb4dc 100644 --- a/spec/system/signing_form_spec.rb +++ b/spec/system/signing_form_spec.rb @@ -1159,6 +1159,50 @@ RSpec.describe 'Signing Form' do end end + context 'when decline is enabled' do + let(:template) { create(:template, account:, author:, only_field_types: %w[text]) } + let(:submission) { create(:submission, template:) } + let(:submitter) { create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:) } + + it 'declines the form and shows the declined page' do + visit submit_form_path(slug: submitter.slug) + + find('#decline_button').click + fill_in 'reason', with: 'I do not agree with the terms' + click_button 'Decline' + + expect(page).to have_content('Form has been declined') + + submitter.reload + + expect(submitter.declined_at).to be_present + end + end + + context 'when delegate is enabled' do + let(:template) { create(:template, account:, author:, only_field_types: %w[text]) } + let(:submission) { create(:submission, template:) } + let(:submitter) { create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:) } + + before do + create(:account_config, account:, key: AccountConfig::ALLOW_TO_DELEGATE_KEY, value: true) + end + + it 'delegates the form to another email and shows the delegated page' do + visit submit_form_path(slug: submitter.slug) + + find('#delegate_button').click + fill_in 'email', with: 'delegate@example.com' + click_button 'Delegate' + + expect(page).to have_content('Document has been delegated') + + submitter.reload + + expect(submitter.email).to eq('delegate@example.com') + end + end + context 'when the 2FA email verification is enabled', sidekiq: :inline do let(:template) { create(:template, account:, author:, only_field_types: %w[text]) } let(:submission) { create(:submission, template:) }