@@ -268,7 +268,7 @@
import Field from './field'
import FieldType from './field_type'
import FieldSubmitter from './field_submitter'
-import { IconLock, IconCirclePlus, IconInnerShadowTop, IconListSearch } from '@tabler/icons-vue'
+import { IconLock, IconCirclePlus, IconInnerShadowTop, IconSparkles } from '@tabler/icons-vue'
import IconDrag from './icon_drag'
export default {
@@ -277,7 +277,7 @@ export default {
Field,
FieldType,
IconCirclePlus,
- IconListSearch,
+ IconSparkles,
IconInnerShadowTop,
FieldSubmitter,
IconDrag,
@@ -470,9 +470,13 @@ export default {
const data = JSON.parse(jsonStr)
if (data.error) {
- alert(data.error)
+ if ((data.fields || fields).length) {
+ this.template.fields = data.fields || fields
- this.template.fields = data.fields || fields
+ this.save()
+ } else {
+ alert(data.error)
+ }
break
} else if (data.analyzing) {
diff --git a/app/javascript/template_builder/i18n.js b/app/javascript/template_builder/i18n.js
index 284989e4..ef97059d 100644
--- a/app/javascript/template_builder/i18n.js
+++ b/app/javascript/template_builder/i18n.js
@@ -188,6 +188,7 @@ const en = {
}
const es = {
+ autodetect_fields: 'Autodetectar campos',
analyzing_: 'Analizando...',
download: 'Descargar',
downloading_: 'Descargando...',
@@ -376,6 +377,7 @@ const es = {
}
const it = {
+ autodetect_fields: 'Rileva campi',
analyzing_: 'Analisi...',
download: 'Scarica',
downloading_: 'Download in corso...',
@@ -564,6 +566,7 @@ const it = {
}
const pt = {
+ autodetect_fields: 'Detectar campos',
analyzing_: 'Analisando...',
download: 'Baixar',
downloading_: 'Baixando...',
@@ -752,6 +755,7 @@ const pt = {
}
const fr = {
+ autodetect_fields: 'Détecter les champs',
analyzing_: 'Analyse...',
download: 'Télécharger',
downloading_: 'Téléchargement...',
@@ -940,6 +944,7 @@ const fr = {
}
const de = {
+ autodetect_fields: 'Felder erkennen',
analyzing_: 'Analysiere...',
download: 'Download',
downloading_: 'Download...',
@@ -1128,6 +1133,7 @@ const de = {
}
const nl = {
+ autodetect_fields: 'Velden detecteren',
analyzing_: 'Analyseren...',
download: 'Downloaden',
downloading_: 'Downloaden...',
diff --git a/app/jobs/send_submitter_invitation_email_job.rb b/app/jobs/send_submitter_invitation_email_job.rb
index 335e3665..49bf22bd 100644
--- a/app/jobs/send_submitter_invitation_email_job.rb
+++ b/app/jobs/send_submitter_invitation_email_job.rb
@@ -7,6 +7,8 @@ class SendSubmitterInvitationEmailJob
submitter = Submitter.find(params['submitter_id'])
return if submitter.completed_at?
+ return if submitter.submission.archived_at?
+ return if submitter.template&.archived_at?
return if submitter.submission.source == 'invite' && !Accounts.can_send_emails?(submitter.account, on_events: true)
unless Accounts.can_send_invitation_emails?(submitter.account)
diff --git a/app/models/account_config.rb b/app/models/account_config.rb
index 462cc725..c2893360 100644
--- a/app/models/account_config.rb
+++ b/app/models/account_config.rb
@@ -22,6 +22,7 @@
#
class AccountConfig < ApplicationRecord
SUBMITTER_INVITATION_EMAIL_KEY = 'submitter_invitation_email'
+ SUBMITTER_INVITATION_REMINDER_EMAIL_KEY = 'submitter_invitation_reminder_email'
SUBMITTER_COMPLETED_EMAIL_KEY = 'submitter_completed_email'
SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY = 'submitter_documents_copy_email'
BCC_EMAILS = 'bcc_emails'
@@ -59,6 +60,12 @@ class AccountConfig < ApplicationRecord
'body' => I18n.t(:submitter_invitation_email_sign_body)
}
},
+ SUBMITTER_INVITATION_REMINDER_EMAIL_KEY => lambda {
+ {
+ 'subject' => I18n.t(:you_are_invited_to_sign_a_document),
+ 'body' => I18n.t(:submitter_invitation_email_sign_body)
+ }
+ },
SUBMITTER_COMPLETED_EMAIL_KEY => lambda {
{
'subject' => I18n.t(:template_name_has_been_completed_by_submitters),
diff --git a/app/views/icons/_device_desktop.html.erb b/app/views/icons/_device_desktop.html.erb
new file mode 100644
index 00000000..3a6ba14f
--- /dev/null
+++ b/app/views/icons/_device_desktop.html.erb
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/app/views/icons/_device_mobile.html.erb b/app/views/icons/_device_mobile.html.erb
new file mode 100644
index 00000000..c820a773
--- /dev/null
+++ b/app/views/icons/_device_mobile.html.erb
@@ -0,0 +1,6 @@
+
diff --git a/app/views/icons/_device_tablet.html.erb b/app/views/icons/_device_tablet.html.erb
new file mode 100644
index 00000000..7dc1b8e4
--- /dev/null
+++ b/app/views/icons/_device_tablet.html.erb
@@ -0,0 +1,5 @@
+
diff --git a/app/views/submission_events/index.html.erb b/app/views/submission_events/index.html.erb
index a0a2b867..8dec75f3 100644
--- a/app/views/submission_events/index.html.erb
+++ b/app/views/submission_events/index.html.erb
@@ -28,9 +28,16 @@
<%= svg_icon(SubmissionEventsController::SUBMISSION_EVENT_ICONS.fetch(event.event_type, 'circle_dot'), class: 'w-4 h-4') %>
-
- <%= l(event.event_timestamp.in_time_zone(current_account.timezone), format: :long, locale: current_account.locale) %>
-
+
+
+ <%= l(event.event_timestamp.in_time_zone(current_account.timezone), format: :long, locale: current_account.locale) %>
+
+ <% if (device = DetectBrowserDevice.call(event.data['ua'])) %>
+
+ <%= svg_icon("device_#{device}", class: 'w-4 h-4') %>
+
+ <% end %>
+
<% if event.event_type == 'complete_verification' %>
<%= t('submission_event_names.complete_verification_by_html', provider: event.data['method'], submitter_name:) %>
diff --git a/app/views/submissions/_send_email.html.erb b/app/views/submissions/_send_email.html.erb
index eaf04b3a..18e03eda 100644
--- a/app/views/submissions/_send_email.html.erb
+++ b/app/views/submissions/_send_email.html.erb
@@ -1,116 +1 @@
-<% submitter_preferences_index = template&.preferences&.dig('submitters').to_a.index_by { |e| e['uuid'] } %>
-<% template_submitters = local_assigns[:submitter]&.submission&.template_submitters || template.submitters %>
-<% message_field_id = "message_field_#{SecureRandom.hex(3)}" %>
-
-<% config = AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY) %>
-
-
- <%= tag.input id: toggle_uuid = SecureRandom.uuid, value: '1', name: 'request_email_per_submitter', class: 'peer', type: 'checkbox', hidden: true, checked: local_assigns[:message_per_submitter] != false && template&.preferences&.dig('submitters').to_a.size > 1 %>
-
- <% if template_submitters.size > 1 && template_submitters.size < 5 && local_assigns[:message_per_submitter] != false %>
-
- <% end %>
-
-
+<%= render partial: 'submissions/send_email_base', locals: local_assigns %>
diff --git a/app/views/submissions/_send_email_base.html.erb b/app/views/submissions/_send_email_base.html.erb
new file mode 100644
index 00000000..eaf04b3a
--- /dev/null
+++ b/app/views/submissions/_send_email_base.html.erb
@@ -0,0 +1,116 @@
+<% submitter_preferences_index = template&.preferences&.dig('submitters').to_a.index_by { |e| e['uuid'] } %>
+<% template_submitters = local_assigns[:submitter]&.submission&.template_submitters || template.submitters %>
+<% message_field_id = "message_field_#{SecureRandom.hex(3)}" %>
+
+<% config = AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY) %>
+
+
+ <%= tag.input id: toggle_uuid = SecureRandom.uuid, value: '1', name: 'request_email_per_submitter', class: 'peer', type: 'checkbox', hidden: true, checked: local_assigns[:message_per_submitter] != false && template&.preferences&.dig('submitters').to_a.size > 1 %>
+
+ <% if template_submitters.size > 1 && template_submitters.size < 5 && local_assigns[:message_per_submitter] != false %>
+
+ <% end %>
+
+
diff --git a/app/views/templates_preferences/_submitter_completed_email_form.html.erb b/app/views/templates_preferences/_submitter_completed_email_form.html.erb
new file mode 100644
index 00000000..7025409d
--- /dev/null
+++ b/app/views/templates_preferences/_submitter_completed_email_form.html.erb
@@ -0,0 +1,70 @@
+
+ <% configs = AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY).value %>
+ <% template_email_preferences_values = @template.preferences.values_at('completed_notification_email_subject', 'completed_notification_email_body').compact_blank.presence %>
+ <% is_custom_template_email = template_email_preferences_values.present? %>
+ <% if is_custom_template_email %>
+ <%= button_to nil, template_preferences_path(@template), id: 'submitter_completed_email_reset_link', method: :delete, class: 'hidden', params: { config_key: AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY }, data: { turbo_confirm: t('are_you_sure_') }, form: { data: { close_on_submit: false } } %>
+ <% end %>
+ <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1', id: 'submitter_completed_email_template_form' }, data: { close_on_submit: false } do |f| %>
+
+ <%= f.fields_for :preferences, Struct.new(:completed_notification_email_subject, :completed_notification_email_body).new(@template.preferences['completed_notification_email_subject'].presence || configs['subject'], @template.preferences['completed_notification_email_body'].presence || configs['body']) do |ff| %>
+
+
+ <% end %>
+ <% end %>
+ <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' }, data: { close_on_submit: false } do |f| %>
+
+ <%= f.fields_for :preferences, Struct.new(:completed_notification_email_enabled, :completed_notification_email_attach_audit, :completed_notification_email_attach_documents).new(@template.preferences['completed_notification_email_enabled'], configs['attach_audit_log'] != false && @template.preferences['completed_notification_email_attach_audit'] != false, configs['attach_documents'] != false && @template.preferences['completed_notification_email_attach_documents'] != false) do |ff| %>
+
+
+ <%= t('attach_documents_to_the_email') %>
+
+
+ <%= ff.check_box :completed_notification_email_attach_documents, { checked: ff.object.completed_notification_email_attach_documents != false, class: 'toggle', disabled: configs['attach_documents'] == false }, 'true', 'false' %>
+
+
+
+
+ <%= t('attach_audit_log_pdf_to_the_email') %>
+
+
+ <%= ff.check_box :completed_notification_email_attach_audit, { checked: ff.object.completed_notification_email_attach_audit != false, class: 'toggle', disabled: configs['attach_audit_log'] == false }, 'true', 'false' %>
+
+
+
+
+ <%= t('send_emails_automatically_on_completion') %>
+
+
+ <%= ff.check_box :completed_notification_email_enabled, { checked: ff.object.completed_notification_email_enabled != false, class: 'toggle', disabled: configs['enabled'] == false }, 'true', 'false' %>
+
+
+ <% end %>
+ <% end %>
+
+
diff --git a/app/views/templates_preferences/_submitter_documents_copy_email_form.html.erb b/app/views/templates_preferences/_submitter_documents_copy_email_form.html.erb
new file mode 100644
index 00000000..8d178369
--- /dev/null
+++ b/app/views/templates_preferences/_submitter_documents_copy_email_form.html.erb
@@ -0,0 +1,75 @@
+
+ <% configs = AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY).value %>
+ <% template_email_preferences_values = @template.preferences.values_at('documents_copy_email_subject', 'documents_copy_email_body').compact_blank.presence %>
+ <% is_custom_template_email = template_email_preferences_values.present? %>
+ <% if is_custom_template_email %>
+ <%= button_to nil, template_preferences_path(@template), id: 'submitter_documents_copy_email_reset_link', method: :delete, class: 'hidden', params: { config_key: AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY }, data: { turbo_confirm: t('are_you_sure_') }, form: { data: { close_on_submit: false } } %>
+ <% end %>
+ <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1', id: 'submitter_documents_copy_email_template_form' }, data: { close_on_submit: false } do |f| %>
+
+ <%= f.fields_for :preferences, Struct.new(:documents_copy_email_reply_to, :documents_copy_email_subject, :documents_copy_email_body).new(@template.preferences['documents_copy_email_reply_to'].presence || configs['reply_to'], @template.preferences['documents_copy_email_subject'].presence || configs['subject'], @template.preferences['documents_copy_email_body'].presence || configs['body']) do |ff| %>
+
+
+ <% if can?(:manage, :reply_to) %>
+
+ <%= ff.label :documents_copy_email_reply_to, t('reply_to'), class: 'label' %>
+ <%= ff.email_field :documents_copy_email_reply_to, class: 'base-input', dir: 'auto', placeholder: t(:email) %>
+
+ <% end %>
+ <% end %>
+ <% end %>
+ <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1', id: 'submitter_documents_copy_email_template_form' }, data: { close_on_submit: false } do |f| %>
+ <%= f.fields_for :preferences, Struct.new(:documents_copy_email_enabled, :documents_copy_email_attach_audit, :documents_copy_email_attach_documents).new(@template.preferences['documents_copy_email_enabled'], configs['attach_audit_log'] != false && @template.preferences['documents_copy_email_attach_audit'] != false, configs['attach_documents'] != false && @template.preferences['documents_copy_email_attach_documents'] != false) do |ff| %>
+
+
+ <%= t('attach_documents_to_the_email') %>
+
+
+ <%= ff.check_box :documents_copy_email_attach_documents, { checked: ff.object.documents_copy_email_attach_documents != false, class: 'toggle', disabled: configs['attach_documents'] == false }, 'true', 'false' %>
+
+
+
+
+ <%= t('attach_audit_log_pdf_to_the_email') %>
+
+
+ <%= ff.check_box :documents_copy_email_attach_audit, { checked: ff.object.documents_copy_email_attach_audit != false, class: 'toggle', disabled: configs['attach_audit_log'] == false }, 'true', 'false' %>
+
+
+
+
+ <%= t('send_emails_automatically_on_completion') %>
+
+
+ <%= ff.check_box :documents_copy_email_enabled, { checked: ff.object.documents_copy_email_enabled != false && configs['enabled'] != false, class: 'toggle', disabled: configs['enabled'] == false }, 'true', 'false' %>
+
+
+ <% end %>
+ <% end %>
+
+
diff --git a/app/views/templates_preferences/_submitter_invitation_email_form.html.erb b/app/views/templates_preferences/_submitter_invitation_email_form.html.erb
new file mode 100644
index 00000000..0f87ddcd
--- /dev/null
+++ b/app/views/templates_preferences/_submitter_invitation_email_form.html.erb
@@ -0,0 +1,110 @@
+
+ <% template_email_preferences_values = @template.preferences.values_at('request_email_subject', 'request_email_body').compact_blank %>
+ <% default_template_email_preferences_values = AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY).value.values_at('subject', 'body') %>
+ <% is_custom_template_email = template_email_preferences_values.present? %>
+ <% multiple_submitters = @template.submitters.size > 1 && @template.submitters.size < 5 %>
+ <% if is_custom_template_email || @template.preferences['submitters'].to_a.any? %>
+ <%= button_to nil, template_preferences_path(@template), id: 'submitter_invitation_email_reset_link', method: :delete, class: 'hidden', params: { config_key: AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY }, data: { turbo_confirm: t('are_you_sure_') }, form: { data: { close_on_submit: false } } %>
+ <% end %>
+ <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1', id: 'submitter_invitation_email_template_form' }, data: { close_on_submit: false } do |f| %>
+
+ <%= tag.input id: 'request_email_per_submitter', value: '1', name: 'request_email_per_submitter', class: 'peer', type: 'checkbox', hidden: true, checked: @template.preferences['submitters'].to_a.size > 1 %>
+
+ <%= f.fields_for :preferences, Struct.new(:request_email_subject, :request_email_body).new(*(template_email_preferences_values.presence || default_template_email_preferences_values)) do |ff| %>
+
+
+ <% end %>
+
+ <% if multiple_submitters %>
+
+ <% options = @template.submitters.map { |e| [e['name'], "request_email_#{e['uuid']}"] } %>
+
+
+ <% options.each_with_index do |(label, val), index| %>
+
+ <%= f.radio_button :selected, val, checked: index.zero?, id: "#{val}_radio", data: { action: 'click:toggle-visible#trigger' }, class: 'hidden peer' %>
+ <%= f.label :selected, label, value: val, for: "#{val}_radio", class: 'tab w-full tab-lifted peer-checked:tab-active' %>
+
+ <% end %>
+
+
+ <%= f.fields_for :preferences do |ff| %>
+ <% @template.submitters.each_with_index do |submitter, index| %>
+
+ <% submitter_preferences = f.object.preferences['submitters'].to_a.find { |e| e['uuid'] == submitter['uuid'] } || {} %>
+ <% submitter_email_preferences_values = submitter_preferences.values_at('request_email_subject', 'request_email_body').compact_blank.presence %>
+ <%= ff.fields_for :submitters, Struct.new(:request_email_subject, :request_email_body).new(*(submitter_preferences.values_at('request_email_subject', 'request_email_body').compact_blank.presence || template_email_preferences_values.presence || default_template_email_preferences_values)), index: nil do |fff| %>
+ <%= fff.hidden_field :uuid, value: submitter['uuid'] %>
+
+
+ <% end %>
+
+ <% end %>
+ <% end %>
+
+ <% end %>
+ <% end %>
+ <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' }, data: { close_on_submit: false } do |f| %>
+
+ <%= f.fields_for :preferences, Struct.new(:request_email_enabled).new(@template.preferences['request_email_enabled']) do |ff| %>
+
+
+ <%= t('send_signature_request_email') %>
+
+
+ <%= ff.check_box :request_email_enabled, { checked: ff.object.request_email_enabled != false, class: 'toggle' }, 'true', 'false' %>
+
+
+ <% end %>
+ <% end %>
+
+
diff --git a/app/views/templates_preferences/_submitter_invitation_reminder_email_collapse.html.erb b/app/views/templates_preferences/_submitter_invitation_reminder_email_collapse.html.erb
new file mode 100644
index 00000000..e69de29b
diff --git a/app/views/templates_preferences/show.html.erb b/app/views/templates_preferences/show.html.erb
index 54f8d83f..290286cf 100644
--- a/app/views/templates_preferences/show.html.erb
+++ b/app/views/templates_preferences/show.html.erb
@@ -98,157 +98,17 @@
<%= t('signature_request_email') %>
- <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' }, data: { close_on_submit: false } do |f| %>
-
- <%= tag.input id: 'request_email_per_submitter', value: '1', name: 'request_email_per_submitter', class: 'peer', type: 'checkbox', hidden: true, checked: @template.preferences['submitters'].to_a.size > 1 %>
-
- <%= f.fields_for :preferences, Struct.new(:request_email_subject, :request_email_body).new(*(@template.preferences.values_at('request_email_subject', 'request_email_body').compact_blank.presence || AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY).value.values_at('subject', 'body'))) do |ff| %>
-
-
- <% end %>
-
- <% if @template.submitters.size > 1 && @template.submitters.size < 5 %>
-
- <% options = @template.submitters.map { |e| [e['name'], "request_email_#{e['uuid']}"] } %>
-
-
- <% options.each_with_index do |(label, val), index| %>
-
- <%= f.radio_button :selected, val, checked: index.zero?, id: "#{val}_radio", data: { action: 'click:toggle-visible#trigger' }, class: 'hidden peer' %>
- <%= f.label :selected, label, value: val, for: "#{val}_radio", class: 'tab w-full tab-lifted peer-checked:tab-active' %>
-
- <% end %>
-
-
- <%= f.fields_for :preferences do |ff| %>
- <% @template.submitters.each_with_index do |submitter, index| %>
-
- <% submitter_preferences = f.object.preferences['submitters'].to_a.find { |e| e['uuid'] == submitter['uuid'] } || {} %>
- <%= ff.fields_for :submitters, Struct.new(:request_email_subject, :request_email_body).new(*(submitter_preferences.values_at('request_email_subject', 'request_email_body').compact_blank.presence || @template.preferences.values_at('request_email_subject', 'request_email_body').compact_blank.presence || AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY).value.values_at('subject', 'body'))), index: nil do |fff| %>
- <%= fff.hidden_field :uuid, value: submitter['uuid'] %>
-
- <%= fff.label :request_email_subject, t('email_subject'), class: 'label' %>
- <%= fff.text_field :request_email_subject, required: true, class: 'base-input', dir: 'auto' %>
-
-
- <% end %>
-
- <% end %>
- <% end %>
-
- <% end %>
- <%= f.fields_for :preferences, Struct.new(:request_email_enabled).new(@template.preferences['request_email_enabled']) do |ff| %>
-
-
- <%= 'Send signature request email' %>
-
-
- <%= ff.check_box :request_email_enabled, { checked: ff.object.request_email_enabled != false, class: 'toggle' }, 'true', 'false' %>
-
-
- <% end %>
-
- <% end %>
+ <%= render 'templates_preferences/submitter_invitation_email_form' %>
+ <%= render 'templates_preferences/submitter_invitation_reminder_email_collapse' %>
<%= t('documents_copy_email') %>
- <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' }, data: { close_on_submit: false } do |f| %>
-
- <% configs = AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY).value %>
- <%= f.fields_for :preferences, Struct.new(:documents_copy_email_reply_to, :documents_copy_email_subject, :documents_copy_email_body, :documents_copy_email_enabled, :documents_copy_email_attach_audit, :documents_copy_email_attach_documents).new(@template.preferences['documents_copy_email_reply_to'].presence || configs['reply_to'], @template.preferences['documents_copy_email_subject'].presence || configs['subject'], @template.preferences['documents_copy_email_body'].presence || configs['body'], @template.preferences['documents_copy_email_enabled'], configs['attach_audit_log'] != false && @template.preferences['documents_copy_email_attach_audit'] != false, configs['attach_documents'] != false && @template.preferences['documents_copy_email_attach_documents'] != false) do |ff| %>
-
- <%= ff.label :documents_copy_email_subject, t('email_subject'), class: 'label' %>
- <%= ff.text_field :documents_copy_email_subject, required: true, class: 'base-input', dir: 'auto' %>
-
-
- <% if can?(:manage, :reply_to) %>
-
- <%= ff.label :documents_copy_email_reply_to, t('reply_to'), class: 'label' %>
- <%= ff.email_field :documents_copy_email_reply_to, class: 'base-input', dir: 'auto', placeholder: t(:email) %>
-
- <% end %>
-
-
- <%= t('attach_documents_to_the_email') %>
-
-
- <%= ff.check_box :documents_copy_email_attach_documents, { checked: ff.object.documents_copy_email_attach_documents != false, class: 'toggle', disabled: configs['attach_documents'] == false }, 'true', 'false' %>
-
-
-
-
- <%= t('attach_audit_log_pdf_to_the_email') %>
-
-
- <%= ff.check_box :documents_copy_email_attach_audit, { checked: ff.object.documents_copy_email_attach_audit != false, class: 'toggle', disabled: configs['attach_audit_log'] == false }, 'true', 'false' %>
-
-
-
-
- <%= t('send_emails_automatically_on_completion') %>
-
-
- <%= ff.check_box :documents_copy_email_enabled, { checked: ff.object.documents_copy_email_enabled != false && configs['enabled'] != false, class: 'toggle', disabled: configs['enabled'] == false }, 'true', 'false' %>
-
-
- <% end %>
-
- <% end %>
+ <%= render 'templates_preferences/submitter_documents_copy_email_form' %>
@@ -257,57 +117,7 @@
<%= t('completed_notification_email') %>
- <%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' }, data: { close_on_submit: false } do |f| %>
-
- <% configs = AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY).value %>
- <%= f.fields_for :preferences, Struct.new(:completed_notification_email_subject, :completed_notification_email_body, :completed_notification_email_enabled, :completed_notification_email_attach_audit, :completed_notification_email_attach_documents).new(@template.preferences['completed_notification_email_subject'].presence || configs['subject'], @template.preferences['completed_notification_email_body'].presence || configs['body'], @template.preferences['completed_notification_email_enabled'], configs['attach_audit_log'] != false && @template.preferences['completed_notification_email_attach_audit'] != false, configs['attach_documents'] != false && @template.preferences['completed_notification_email_attach_documents'] != false) do |ff| %>
-
- <%= ff.label :completed_notification_email_subject, t('email_subject'), class: 'label' %>
- <%= ff.text_field :completed_notification_email_subject, required: true, class: 'base-input', dir: 'auto' %>
-
-
-
-
- <%= t('attach_documents_to_the_email') %>
-
-
- <%= ff.check_box :completed_notification_email_attach_documents, { checked: ff.object.completed_notification_email_attach_documents != false, class: 'toggle', disabled: configs['attach_documents'] == false }, 'true', 'false' %>
-
-
-
-
- <%= t('attach_audit_log_pdf_to_the_email') %>
-
-
- <%= ff.check_box :completed_notification_email_attach_audit, { checked: ff.object.completed_notification_email_attach_audit != false, class: 'toggle', disabled: configs['attach_audit_log'] == false }, 'true', 'false' %>
-
-
-
-
- <%= t('send_emails_automatically_on_completion') %>
-
-
- <%= ff.check_box :completed_notification_email_enabled, { checked: ff.object.completed_notification_email_enabled != false, class: 'toggle', disabled: configs['enabled'] == false }, 'true', 'false' %>
-
-
- <% end %>
-
- <% end %>
+ <%= render 'templates_preferences/submitter_completed_email_form' %>
@@ -359,9 +169,9 @@
<%= t('share_template_with_test_mode') %>
-
- <%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: current_account.testing_accounts) %>
-
+
+ <%= f.check_box :value, class: 'toggle', checked: @template.template_sharings.exists?(account_id: current_account.testing_accounts) %>
+
<% end %>
diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml
index c08944eb..3b6d649f 100644
--- a/config/locales/i18n.yml
+++ b/config/locales/i18n.yml
@@ -290,6 +290,7 @@ en: &en
invalid_timeserver: Invalid Timeserver
email_templates: Email Templates
signature_request_email: Signature request email
+ signature_request_reminder_email: Signature request reminder email
signature_request_sms: Signature Request SMS
verification_code_sms: Verification Code SMS
completed_notification_email: Completed notification email
@@ -837,6 +838,11 @@ en: &en
connect_google_drive: Connect Google Drive
google_drive_has_been_connected: Google Drive has been connected
unable_to_identify_reset_your_password_to_sign_in: Unable to identify. Reset your password to sign in.
+ desktop: Desktop
+ mobile: Mobile
+ tablet: Tablet
+ reset_default: Reset default
+ send_signature_request_email: Send signature request email
submission_sources:
api: API
bulk: Bulk Send
@@ -1212,6 +1218,7 @@ es: &es
invalid_timeserver: Servidor de tiempo inválido
email_templates: Plantillas de correo electrónico
signature_request_email: Correo de solicitud de firma
+ signature_request_reminder_email: Correo de recordatorio de solicitud de firma
signature_request_sms: SMS de solicitud de firma
verification_code_sms: SMS de código de verificación
completed_notification_email: Correo de notificación de formulario completado
@@ -1758,6 +1765,11 @@ es: &es
connect_google_drive: Conectar Google Drive
google_drive_has_been_connected: Google Drive se ha conectado
unable_to_identify_reset_your_password_to_sign_in: No se pudo identificar. Restablece tu contraseña para iniciar sesión.
+ desktop: Escritorio
+ mobile: Móvil
+ tablet: Tableta
+ reset_default: Restablecer por defecto
+ send_signature_request_email: Enviar correo de solicitud de firma
submission_sources:
api: API
bulk: Envío masivo
@@ -2133,6 +2145,7 @@ it: &it
invalid_timeserver: Server di timestamp non valido
email_templates: Modelli email
signature_request_email: Email di richiesta di firma
+ signature_request_reminder_email: Email di promemoria di richiesta di firma
signature_request_sms: SMS di richiesta di firma
verification_code_sms: SMS con codice di verifica
completed_notification_email: Email di notifica di completamento
@@ -2680,6 +2693,11 @@ it: &it
connect_google_drive: Connetti Google Drive
google_drive_has_been_connected: Google Drive è stato connesso
unable_to_identify_reset_your_password_to_sign_in: Impossibile identificare. Reimposta la password per accedere.
+ desktop: Desktop
+ mobile: Mobile
+ tablet: Tablet
+ reset_default: Reimposta predefinito
+ send_signature_request_email: Invia email di richiesta firma
submission_sources:
api: API
bulk: Invio massivo
@@ -3056,6 +3074,7 @@ fr: &fr
invalid_timeserver: Serveur d’horodatage invalide
email_templates: Modèles d’e‑mail
signature_request_email: E‑mail de demande de signature
+ signature_request_reminder_email: E‑mail de rappel de demande de signature
signature_request_sms: SMS de demande de signature
verification_code_sms: SMS de code de vérification
completed_notification_email: E‑mail de notification de finalisation
@@ -3599,6 +3618,11 @@ fr: &fr
connect_google_drive: Connecter Google Drive
google_drive_has_been_connected: Google Drive a été connecté
unable_to_identify_reset_your_password_to_sign_in: Impossible d’identifier. Réinitialisez votre mot de passe pour vous connecter.
+ desktop: Bureau
+ mobile: Mobile
+ tablet: Tablette
+ reset_default: Réinitialiser par défaut
+ send_signature_request_email: Envoyer un e-mail de demande de signature
submission_sources:
api: API
bulk: Envoi en masse
@@ -3975,6 +3999,7 @@ pt: &pt
invalid_timeserver: Servidor de carimbo de tempo inválido
email_templates: Modelos de e-mail
signature_request_email: E-mail de solicitação de assinatura
+ signature_request_reminder_email: E-mail de lembrete de solicitação de assinatura
signature_request_sms: SMS de solicitação de assinatura
verification_code_sms: SMS com código de verificação
completed_notification_email: E-mail de notificação de submissão concluída
@@ -4522,6 +4547,11 @@ pt: &pt
connect_google_drive: Conectar Google Drive
google_drive_has_been_connected: O Google Drive foi conectado
unable_to_identify_reset_your_password_to_sign_in: Não foi possível identificar. Redefina sua senha para fazer login.
+ desktop: Computador
+ mobile: Celular
+ tablet: Tablet
+ reset_default: Redefinir para padrão
+ send_signature_request_email: Enviar e-mail de solicitação de assinatura
submission_sources:
api: API
bulk: Envio em massa
@@ -4898,6 +4928,7 @@ de: &de
invalid_timeserver: Ungültiger Zeitstempelserver
email_templates: E-Mail-Vorlagen
signature_request_email: E-Mail für Signaturanfrage
+ signature_request_reminder_email: E-Mail-Erinnerung für Signaturanfrage
signature_request_sms: SMS für Signaturanfrage
verification_code_sms: SMS mit Verifizierungscode
completed_notification_email: E-Mail-Benachrichtigung bei Abschluss
@@ -5445,6 +5476,11 @@ de: &de
connect_google_drive: Google Drive verbinden
google_drive_has_been_connected: Google Drive wurde verbunden
unable_to_identify_reset_your_password_to_sign_in: Identifizierung nicht möglich. Setzen Sie Ihr Passwort zurück, um sich anzumelden.
+ desktop: Desktop
+ mobile: Mobil
+ tablet: Tablet
+ reset_default: Standard zurücksetzen
+ send_signature_request_email: Signaturanfrage-E-Mail senden
submission_sources:
api: API
bulk: Massenversand
@@ -6186,6 +6222,7 @@ nl: &nl
invalid_timeserver: Ongeldige tijdserver
email_templates: E-mailsjablonen
signature_request_email: E-mail voor handtekeningverzoek
+ signature_request_reminder_email: E-mailherinnering voor handtekeningverzoek
signature_request_sms: SMS voor handtekeningverzoek
verification_code_sms: Verificatiecode-SMS
completed_notification_email: E-mailmelding voltooid
@@ -6729,6 +6766,11 @@ nl: &nl
connect_google_drive: Verbind Google Drive
google_drive_has_been_connected: Google Drive is verbonden
unable_to_identify_reset_your_password_to_sign_in: Kan niet worden geïdentificeerd. Stel je wachtwoord opnieuw in om in te loggen.
+ desktop: Desktop
+ mobile: Mobiel
+ tablet: Tablet
+ reset_default: Standaard herstellen
+ send_signature_request_email: E-mail met handtekeningaanvraag verzenden
submission_sources:
api: API
bulk: Bulkverzending
diff --git a/config/routes.rb b/config/routes.rb
index 20a3035d..05abbfc4 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -108,7 +108,7 @@ Rails.application.routes.draw do
resource :preview, only: %i[show], controller: 'templates_preview'
resource :form, only: %i[show], controller: 'templates_form_preview'
resource :code_modal, only: %i[show], controller: 'templates_code_modal'
- resource :preferences, only: %i[show create], controller: 'templates_preferences'
+ resource :preferences, only: %i[show create destroy], controller: 'templates_preferences'
resource :share_link, only: %i[show create], controller: 'templates_share_link'
resources :recipients, only: %i[create], controller: 'templates_recipients'
resources :prefillable_fields, only: %i[create], controller: 'templates_prefillable_fields'
diff --git a/lib/detect_browser_device.rb b/lib/detect_browser_device.rb
new file mode 100644
index 00000000..0240d278
--- /dev/null
+++ b/lib/detect_browser_device.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module DetectBrowserDevice
+ module_function
+
+ MOBILE_USER_AGENT_REGEXP = /
+ iPhone |
+ iPod |
+ Android.*Mobile|
+ Opera\ Mini |
+ Opera\ Mobi |
+ webOS |
+ IEMobile |
+ Windows\ Phone |
+ BlackBerry |
+ BB10 |
+ Mobile
+ /ix
+
+ TABLET_USER_AGENT_REGEXP = /
+ iPad |
+ Android(?!.*Mobile)|
+ Tablet |
+ Kindle |
+ PlayBook |
+ Silk
+ /ix
+
+ def call(user_agent)
+ return if user_agent.blank?
+
+ return 'mobile' if MOBILE_USER_AGENT_REGEXP.match?(user_agent)
+ return 'tablet' if TABLET_USER_AGENT_REGEXP.match?(user_agent)
+
+ 'desktop'
+ end
+end
diff --git a/lib/submissions/generate_audit_trail.rb b/lib/submissions/generate_audit_trail.rb
index b6505695..2619c949 100644
--- a/lib/submissions/generate_audit_trail.rb
+++ b/lib/submissions/generate_audit_trail.rb
@@ -23,6 +23,8 @@ module Submissions
RTL_REGEXP = TextUtils::RTL_REGEXP
MAX_IMAGE_HEIGHT = 100
+ CHECKSUM_LIMIT = 30
+
US_TIMEZONES = TimeUtils::US_TIMEZONES
module_function
@@ -216,7 +218,7 @@ module Submissions
composer.document.layout.formatted_text_box(
[
{ text: "#{I18n.t('original_sha256')}:\n", font: [FONT_NAME, { variant: :bold }] },
- original_documents.map { |d| d.metadata['sha256'] || d.checksum }.join("\n"),
+ original_documents.map { |d| d.metadata['sha256'] || d.checksum }.first(CHECKSUM_LIMIT).join("\n"),
"\n",
{ text: "#{I18n.t('result_sha256')}:\n", font: [FONT_NAME, { variant: :bold }] },
document.metadata['sha256'] || document.checksum,
diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb
index 934c86fc..e347d2ac 100644
--- a/lib/submissions/generate_result_attachments.rb
+++ b/lib/submissions/generate_result_attachments.rb
@@ -719,20 +719,30 @@ module Submissions
begin
pdf.sign(io, write_options: { validate: false }, **sign_params)
- rescue HexaPDF::MalformedPDFError, NoMethodError => e
+ rescue HexaPDF::Error, NoMethodError => e
Rollbar.error(e) if defined?(Rollbar)
- pdf.sign(io, write_options: { validate: false, incremental: false }, **sign_params)
+ begin
+ pdf.sign(io, write_options: { validate: false, incremental: false }, **sign_params)
+ rescue HexaPDF::Error
+ pdf.validate(auto_correct: true)
+ pdf.sign(io, write_options: { validate: false, incremental: false }, **sign_params)
+ end
end
maybe_enable_ltv(io, sign_params)
else
begin
pdf.write(io, incremental: true, validate: false)
- rescue HexaPDF::MalformedPDFError, NoMethodError => e
+ rescue HexaPDF::Error, NoMethodError => e
Rollbar.error(e) if defined?(Rollbar)
- pdf.write(io, incremental: false, validate: false)
+ begin
+ pdf.write(io, incremental: false, validate: false)
+ rescue HexaPDF::Error
+ pdf.validate(auto_correct: true)
+ pdf.write(io, incremental: false, validate: false)
+ end
end
end
diff --git a/lib/submitters/generate_font_image.rb b/lib/submitters/generate_font_image.rb
index b64580fc..b70d7450 100644
--- a/lib/submitters/generate_font_image.rb
+++ b/lib/submitters/generate_font_image.rb
@@ -20,6 +20,8 @@ module Submitters
def call(text, font: nil)
font = FONT_ALIASES[font] || font
+ text = ERB::Util.html_escape(text)
+
text_image = Vips::Image.text(text, font:, fontfile: FONTS[font],
width: WIDTH, height: HEIGHT, wrap: :none)
diff --git a/lib/templates/image_to_fields.rb b/lib/templates/image_to_fields.rb
index fa342f4e..786e9785 100755
--- a/lib/templates/image_to_fields.rb
+++ b/lib/templates/image_to_fields.rb
@@ -37,10 +37,10 @@ module Templates
transform_info[:trim_offset_x] = base_offset_x
transform_info[:trim_offset_y] = base_offset_y + r[:offset_y]
- outputs = model.predict({ 'input' => input_tensor })
+ outputs = model.predict({ 'input' => input_tensor }, output_type: :numo)
- boxes = Numo::SFloat.cast(outputs['dets'])[0, true, true]
- logits = Numo::SFloat.cast(outputs['labels'])[0, true, true]
+ boxes = outputs['dets'][0, true, true]
+ logits = outputs['labels'][0, true, true]
postprocess_outputs(boxes, logits, transform_info, acc, confidence:, temperature:, resolution:)
end
@@ -50,10 +50,10 @@ module Templates
transform_info[:trim_offset_x] = base_offset_x
transform_info[:trim_offset_y] = base_offset_y
- outputs = model.predict({ 'input' => input_tensor })
+ outputs = model.predict({ 'input' => input_tensor }, output_type: :numo)
- boxes = Numo::SFloat.cast(outputs['dets'])[0, true, true]
- logits = Numo::SFloat.cast(outputs['labels'])[0, true, true]
+ boxes = outputs['dets'][0, true, true]
+ logits = outputs['labels'][0, true, true]
detections = postprocess_outputs(boxes, logits, transform_info, confidence:, temperature:, resolution:)
end
@@ -77,7 +77,7 @@ module Templates
end
def build_fields_from_detections(detections, image)
- Array.new(detections[:xyxy].shape[0]) do |i|
+ detections[:xyxy].shape[0].times.filter_map do |i|
x1 = detections[:xyxy][i, 0]
y1 = detections[:xyxy][i, 1]
x2 = detections[:xyxy][i, 2]
@@ -92,6 +92,12 @@ module Templates
x1_norm = x2 / image.width.to_f
y1_norm = y2 / image.height.to_f
+ x1_norm = 1 if x1_norm > 1
+ y1_norm = 1 if y1_norm > 1
+
+ next if x0_norm < 0 || x0_norm > 1
+ next if y0_norm < 0 || y0_norm > 1
+
type_name = ID_TO_CLASS[class_id]
Field.new(