one-off submission

pull/493/head
Pete Matsyburka 5 months ago committed by Pete Matsyburka
parent c59d948d41
commit f03166b4ea

@ -183,7 +183,7 @@ module Api
def submissions_params
permitted_attrs = [
:send_email, :send_sms, :bcc_completed, :completed_redirect_url, :reply_to, :go_to_last,
:expire_at,
:expire_at, :name,
{
message: %i[subject body],
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,

@ -21,12 +21,13 @@ module Api
)
cloned_template.source = :api
cloned_template.save!
schema_documents = Templates::CloneAttachments.call(template: cloned_template,
original_template: @template,
documents: params[:documents])
cloned_template.save!
WebhookUrls.for_account_id(cloned_template.account_id, 'template.created').each do |webhook_url|
SendTemplateCreatedWebhookRequestJob.perform_async('template_id' => cloned_template.id,
'webhook_url_id' => webhook_url.id)

@ -142,7 +142,7 @@ class StartFormController < ApplicationController
end
def filter_undefined_submitters(template)
Templates.filter_undefined_submitters(template)
Templates.filter_undefined_submitters(template.submitters)
end
def submitter_params

@ -4,7 +4,7 @@ class SubmissionsArchivedController < ApplicationController
load_and_authorize_resource :submission, parent: false
def index
@submissions = @submissions.joins(:template)
@submissions = @submissions.left_joins(:template)
@submissions = @submissions.where.not(archived_at: nil)
.or(@submissions.where.not(templates: { archived_at: nil }))
.preload(:template_accesses, :created_by_user, template: :author)

@ -4,7 +4,7 @@ class SubmissionsDashboardController < ApplicationController
load_and_authorize_resource :submission, parent: false
def index
@submissions = @submissions.joins(:template)
@submissions = @submissions.left_joins(:template)
@submissions = @submissions.where(archived_at: nil)
.where(templates: { archived_at: nil })

@ -8,6 +8,7 @@ class SubmitFormController < ApplicationController
skip_authorization_check
before_action :load_submitter, only: %i[show update completed]
before_action :maybe_render_locked_page, only: :show
CONFIG_KEYS = [].freeze
@ -15,19 +16,14 @@ class SubmitFormController < ApplicationController
submission = @submitter.submission
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
return render :archived if submission.template.archived_at? ||
submission.archived_at? ||
@submitter.account.archived_at?
return render :expired if submission.expired?
return render :declined if @submitter.declined_at?
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
return render :awaiting if (@form_configs[:enforce_signing_order] ||
submission.template.preferences['submitters_order'] == 'preserved') &&
submission.template&.preferences&.dig('submitters_order') == 'preserved') &&
!Submitters.current_submitter_order?(@submitter)
Submitters.preload_with_pages(@submitter)
Submissions.preload_with_pages(submission)
Submitters::MaybeUpdateDefaultValues.call(@submitter, current_user)
@ -54,7 +50,7 @@ class SubmitFormController < ApplicationController
return render json: { error: I18n.t('form_has_been_completed_already') }, status: :unprocessable_entity
end
if @submitter.template.archived_at? || @submitter.submission.archived_at?
if @submitter.template&.archived_at? || @submitter.submission.archived_at?
return render json: { error: I18n.t('form_has_been_archived') }, status: :unprocessable_entity
end
@ -84,6 +80,15 @@ class SubmitFormController < ApplicationController
private
def maybe_render_locked_page
return render :archived if @submitter.submission.template&.archived_at? ||
@submitter.submission.archived_at? ||
@submitter.account.archived_at?
return render :expired if @submitter.submission.expired?
render :declined if @submitter.declined_at?
end
def load_submitter
@submitter = Submitter.find_by!(slug: params[:slug] || params[:submit_form_slug])
end

@ -11,7 +11,7 @@ class SubmitFormDeclineController < ApplicationController
submitter.completed_at? ||
submitter.submission.archived_at? ||
submitter.submission.expired? ||
submitter.submission.template.archived_at?
submitter.submission.template&.archived_at?
ApplicationRecord.transaction do
submitter.update!(declined_at: Time.current)

@ -14,7 +14,7 @@ class SubmitFormDownloadController < ApplicationController
return head :unprocessable_entity if @submitter.declined_at? ||
@submitter.submission.archived_at? ||
@submitter.submission.expired? ||
@submitter.submission.template.archived_at?
@submitter.submission.template&.archived_at?
last_completed_submitter = @submitter.submission.submitters
.where.not(id: @submitter.id)
@ -25,7 +25,7 @@ class SubmitFormDownloadController < ApplicationController
if last_completed_submitter
Submitters.select_attachments_for_download(last_completed_submitter)
else
@submitter.submission.template.schema_documents.preload(:blob)
@submitter.submission.schema_documents.preload(:blob)
end
urls = attachments.map do |attachment|

@ -6,13 +6,15 @@ class SubmittersResubmitController < ApplicationController
def update
return redirect_to submit_form_path(slug: @submitter.slug) if @submitter.email != current_user.email
submission = @submitter.template.submissions.new(created_by_user: current_user,
submitters_order: :preserved,
**@submitter.submission.slice(:template_fields,
:account_id,
:template_schema,
:template_submitters,
:preferences))
submission = @submitter.account.submissions.new(created_by_user: current_user,
submitters_order: :preserved,
**@submitter.submission.slice(:template_fields,
:account_id,
:name,
:template_id,
:template_schema,
:template_submitters,
:preferences))
@submitter.submission.submitters.each do |submitter|
new_submitter = submission.submitters.new(submitter.slice(:uuid, :email, :phone, :name,
@ -27,6 +29,10 @@ class SubmittersResubmitController < ApplicationController
submission.save!
@submitter.submission.documents_attachments.each do |attachment|
submission.documents_attachments.create!(uuid: attachment.uuid, blob_id: attachment.blob_id)
end
redirect_to submit_form_path(slug: @new_submitter.slug)
end

@ -13,7 +13,7 @@ class TemplatesFormPreviewController < ApplicationController
@submitter.submission.submitters = @template.submitters.map { |item| Submitter.new(uuid: item['uuid']) }
Submitters.preload_with_pages(@submitter)
Submissions.preload_with_pages(@submitter.submission)
@attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments)
.preload(:blob).index_by(&:uuid)

@ -0,0 +1,21 @@
# frozen_string_literal: true
class GenerateAttachmentPreviewJob
include Sidekiq::Job
InvalidFormat = Class.new(StandardError)
sidekiq_options queue: :images
def perform(params = {})
attachment = ActiveStorage::Attachment.find(params['attachment_id'])
if attachment.content_type == Templates::ProcessDocument::PDF_CONTENT_TYPE
Templates::ProcessDocument.generate_pdf_preview_images(attachment, attachment.download)
elsif attachment.image?
Templates::ProcessDocument.generate_preview_image(attachment, attachment.download)
else
raise InvalidFormat, attachment.id
end
end
end

@ -7,7 +7,7 @@ class ProcessSubmissionExpiredJob
submission = Submission.find(params['submission_id'])
return if submission.archived_at?
return if submission.template.archived_at?
return if submission.template&.archived_at?
return if submission.submitters.where.not(declined_at: nil).exists?
return unless submission.submitters.exists?(completed_at: nil)

@ -82,7 +82,7 @@ class ProcessSubmitterCompletionJob
user = submission.created_by_user || submitter.template.author
if submitter.account.users.exists?(id: user.id) && submission.preferences['send_email'] != false &&
submitter.template.preferences['completed_notification_email_enabled'] != false
submitter.template&.preferences&.dig('completed_notification_email_enabled') != false
if submission.submitters.map(&:email).exclude?(user.email) &&
user.user_configs.find_by(key: UserConfig::RECEIVE_COMPLETED_EMAIL)&.value != false &&
user.role != 'integration'
@ -98,7 +98,7 @@ class ProcessSubmitterCompletionJob
end
def maybe_enqueue_copy_emails(submitter)
return if submitter.template.preferences['documents_copy_email_enabled'] == false
return if submitter.template&.preferences&.dig('documents_copy_email_enabled') == false
configs = AccountConfigs.find_or_initialize_for_key(submitter.account,
AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY)
@ -119,7 +119,7 @@ class ProcessSubmitterCompletionJob
def build_bcc_addresses(submission)
bcc = submission.preferences['bcc_completed'].presence ||
submission.template.preferences['bcc_completed'].presence ||
submission.template&.preferences&.dig('bcc_completed').presence ||
submission.account.account_configs
.find_by(key: AccountConfig::BCC_EMAILS)&.value

@ -23,11 +23,11 @@ class SubmitterMailer < ApplicationMailer
@body = @email_message&.body.presence ||
template_submitters_index.dig(@submitter.uuid, 'request_email_body').presence ||
@submitter.template.preferences['request_email_body'].presence
@submitter.template&.preferences&.dig('request_email_body').presence
@subject = @email_message&.subject.presence ||
template_submitters_index.dig(@submitter.uuid, 'request_email_subject').presence ||
@submitter.template.preferences['request_email_subject'].presence
@submitter.template&.preferences&.dig('request_email_subject').presence
@email_config = AccountConfigs.find_for_account(@current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY)
@ -53,6 +53,8 @@ class SubmitterMailer < ApplicationMailer
@submission = submitter.submission
@user = user
template_preferences = @submission.template&.preferences || {}
Submissions::EnsureResultGenerated.call(submitter)
@email_config = AccountConfigs.find_for_account(@current_account, AccountConfig::SUBMITTER_COMPLETED_EMAIL_KEY)
@ -60,15 +62,15 @@ class SubmitterMailer < ApplicationMailer
add_completed_email_attachments!(
submitter,
with_documents: @email_config&.value&.dig('attach_documents') != false &&
@submitter.template.preferences['completed_notification_email_attach_documents'] != false,
template_preferences['completed_notification_email_attach_documents'] != false,
with_audit_log: @email_config&.value&.dig('attach_audit_log') != false &&
@submitter.template.preferences['completed_notification_email_attach_audit'] != false
template_preferences['completed_notification_email_attach_audit'] != false
)
@subject = @submitter.template.preferences['completed_notification_email_subject'].presence
@subject = template_preferences['completed_notification_email_subject'].presence
@subject ||= @email_config.value['subject'] if @email_config
@body = @submitter.template.preferences['completed_notification_email_body'].presence
@body = template_preferences['completed_notification_email_body'].presence
@body ||= @email_config.value['body'] if @email_config
assign_message_metadata('submitter_completed', @submitter)
@ -97,7 +99,7 @@ class SubmitterMailer < ApplicationMailer
to: user.role == 'integration' ? user.friendly_name.sub(/\+\w+@/, '@') : user.friendly_name,
reply_to: @submitter.friendly_name,
subject: I18n.t(:name_declined_by_submitter,
name: @submission.template.name.truncate(20),
name: (@submission.name || @submission.template.name).truncate(20),
submitter: @submitter.name || @submitter.email || @submitter.phone))
end
end
@ -107,22 +109,24 @@ class SubmitterMailer < ApplicationMailer
@submitter = submitter
@sig = submitter.signed_id(expires_in: SIGN_TTL, purpose: :download_completed) if sig
template_preferences = @submitter.template&.preferences || {}
Submissions::EnsureResultGenerated.call(@submitter)
@email_config = AccountConfigs.find_for_account(@current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY)
add_completed_email_attachments!(
submitter,
with_documents: @submitter.template.preferences['documents_copy_email_attach_documents'] != false &&
(@email_config.nil? || @email_config.value['attach_documents'] != false),
with_audit_log: @submitter.template.preferences['documents_copy_email_attach_audit'] != false &&
(@email_config.nil? || @email_config.value['attach_audit_log'] != false)
with_documents: template_preferences['documents_copy_email_attach_documents'] != false &&
(@email_config.nil? || @email_config.value['attach_documents'] != false),
with_audit_log: template_preferences['documents_copy_email_attach_audit'] != false &&
(@email_config.nil? || @email_config.value['attach_audit_log'] != false)
)
@subject = @submitter.template.preferences['documents_copy_email_subject'].presence
@subject = template_preferences['documents_copy_email_subject'].presence
@subject ||= @email_config.value['subject'] if @email_config
@body = @submitter.template.preferences['documents_copy_email_body'].presence
@body = template_preferences['documents_copy_email_body'].presence
@body ||= @email_config.value['body'] if @email_config
assign_message_metadata('submitter_documents_copy', @submitter)
@ -130,11 +134,7 @@ class SubmitterMailer < ApplicationMailer
I18n.with_locale(@current_account.locale) do
subject =
if @subject.present?
ReplaceEmailVariables.call(@subject, submitter:)
else
I18n.t(:your_document_copy)
end
@subject.present? ? ReplaceEmailVariables.call(@subject, submitter:) : I18n.t(:your_document_copy)
mail(from: from_address_for_submitter(submitter),
to: to || @submitter.friendly_name,
@ -147,7 +147,7 @@ class SubmitterMailer < ApplicationMailer
def build_submitter_reply_to(submitter, email_config: nil, documents_copy_email: nil)
reply_to = submitter.preferences['reply_to'].presence
reply_to ||= submitter.template.preferences['documents_copy_email_reply_to'].presence if documents_copy_email
reply_to ||= submitter.template&.preferences&.dig('documents_copy_email_reply_to').presence if documents_copy_email
reply_to ||= email_config.value['reply_to'].presence if email_config
if reply_to.blank? && (submitter.submission.created_by_user || submitter.template.author)&.email != submitter.email
@ -212,7 +212,7 @@ class SubmitterMailer < ApplicationMailer
end
def build_submitter_preferences_index(submitter)
submitter.template.preferences['submitters'].to_a.index_by { |e| e['uuid'] }
submitter.template&.preferences&.dig('submitters').to_a.index_by { |e| e['uuid'] }
end
def add_attachments_with_size_limit(submitter, storage_attachments, current_size, filename_format = nil)

@ -13,7 +13,7 @@
# account_id :bigint not null
# submission_id :bigint not null
# submitter_id :bigint not null
# template_id :bigint not null
# template_id :bigint
#
# Indexes
#
@ -24,7 +24,7 @@ class CompletedSubmitter < ApplicationRecord
belongs_to :submitter
belongs_to :submission
belongs_to :account
belongs_to :template
belongs_to :template, optional: true
has_many :completed_documents, dependent: :destroy,
primary_key: :submitter_id,

@ -7,6 +7,7 @@
# id :bigint not null, primary key
# archived_at :datetime
# expire_at :datetime
# name :text
# preferences :text not null
# slug :string not null
# source :text not null
@ -18,7 +19,7 @@
# updated_at :datetime not null
# account_id :bigint not null
# created_by_user_id :bigint
# template_id :bigint not null
# template_id :bigint
#
# Indexes
#
@ -33,7 +34,7 @@
# fk_rails_... (template_id => templates.id)
#
class Submission < ApplicationRecord
belongs_to :template
belongs_to :template, optional: true
belongs_to :account
belongs_to :created_by_user, class_name: 'User', optional: true
@ -56,6 +57,7 @@ class Submission < ApplicationRecord
has_one_attached :combined_document
has_many_attached :preview_documents
has_many_attached :documents
has_many :template_accesses, primary_key: :template_id, foreign_key: :template_id, dependent: nil, inverse_of: false
@ -96,6 +98,14 @@ class Submission < ApplicationRecord
expire_at && expire_at <= Time.current
end
def schema_documents
if template_id?
template_schema_documents
else
documents_attachments
end
end
def fields_uuid_index
@fields_uuid_index ||= (template_fields || template.fields).index_by { |f| f['uuid'] }
end

@ -26,7 +26,7 @@
<%= button_to button_title(title: t('send_copy_to_email'), disabled_with: t('sending'), icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { template_slug: @template.slug, email: params[:email] }, class: 'base-button w-full' %>
</toggle-submit>
<% end %>
<% if Templates.filter_undefined_submitters(@template).size == 1 && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && @template.shared_link? %>
<% if Templates.filter_undefined_submitters(@template.submitters).size == 1 && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && @template.shared_link? %>
<toggle-submit class="block">
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), start_form_path(@template.slug), params: { submitter: { email: params[:email] }, resubmit: true }, method: :put, class: 'white-button w-full' %>
</toggle-submit>

@ -4,9 +4,9 @@
<% with_signature_id, is_combined_enabled = AccountConfig.where(account_id: @submission.account_id, key: [AccountConfig::COMBINE_PDF_RESULT_KEY, AccountConfig::WITH_SIGNATURE_ID], value: true).then { |configs| [configs.any? { |e| e.key == AccountConfig::WITH_SIGNATURE_ID }, configs.any? { |e| e.key == AccountConfig::COMBINE_PDF_RESULT_KEY }] } %>
<div style="max-width: 1600px" class="mx-auto pl-4">
<div class="flex justify-between py-1.5 items-center pr-4 sticky top-0 md:relative z-10 bg-base-100">
<a href="<%= signed_in? && @submission.account_id == current_account&.id ? template_path(@submission.template) : '/' %>" class="flex items-center space-x-3 py-1">
<a href="<%= signed_in? && @submission.account_id == current_account&.id && @submission.template ? template_path(@submission.template) : '/' %>" class="flex items-center space-x-3 py-1">
<span><%= render 'submissions/logo' %></span>
<span class="text-xl md:text-3xl font-semibold focus:text-clip" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;"><% @submission.template.name.split(/(_)/).each do |item| %><%= item %><wbr><% end %></span>
<span class="text-xl md:text-3xl font-semibold focus:text-clip" style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;"><% (@submission.name || @submission.template.name).split(/(_)/).each do |item| %><%= item %><wbr><% end %></span>
</a>
<div class="space-x-3 flex items-center">
<% last_submitter = @submission.submitters.to_a.select(&:completed_at?).max_by(&:completed_at) %>
@ -73,7 +73,7 @@
<% values = @submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
<% schema = Submissions.filtered_conditions_schema(@submission, values:) %>
<% schema.each do |item| %>
<% document = @submission.template_schema_documents.find { |a| item['attachment_uuid'] == a.uuid } %>
<% document = @submission.schema_documents.find { |a| item['attachment_uuid'] == a.uuid } %>
<a href="#<%= "page-#{document.uuid}-0" %>" onclick="[event.preventDefault(), window[event.target.closest('a').href.split('#')[1]].scrollIntoView({ behavior: 'smooth', block: 'start' })]" class="block cursor-pointer">
<img src="<%= Docuseal::URL_CACHE.fetch([document.id, document.uuid, 0].join(':'), expires_in: 10.minutes) { document.preview_images.first.url } %>" width="<%= document.preview_images.first.metadata['width'] %>" height="<%= document.preview_images.first.metadata['height'] %>" class="rounded border" loading="lazy">
<div class="pb-2 pt-1.5 text-center" dir="auto">
@ -89,7 +89,7 @@
<% attachments_index = ActiveStorage::Attachment.where(record: @submission.submitters, name: :attachments).preload(:blob).index_by(&:uuid) %>
<% page_blob_struct = Struct.new(:url, :metadata, keyword_init: true) %>
<% schema.each do |item| %>
<% document = @submission.template_schema_documents.find { |e| e.uuid == item['attachment_uuid'] } %>
<% document = @submission.schema_documents.find { |e| e.uuid == item['attachment_uuid'] } %>
<% document_annots_index = document.metadata.dig('pdf', 'annotations')&.group_by { |e| e['page'] } || {} %>
<% preview_images_index = document.preview_images.loaded? ? document.preview_images.index_by { |e| e.filename.base.to_i } : {} %>
<% lazyload_metadata = document.preview_images.first.metadata %>

@ -11,7 +11,7 @@
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
</div>
<div>
<p dir="auto" class="text-lg font-bold mb-1"><%= @submission.template.name %></p>
<p dir="auto" class="text-lg font-bold mb-1"><%= @submission.name || @submission.template.name %></p>
<% last_submitter = @submission.submitters.completed.order(:completed_at).last %>
<% if last_submitter %>
<p dir="auto" class="text-sm">

@ -2,4 +2,4 @@
<% data_fields = Submissions.filtered_conditions_fields(submitter).to_json %>
<% invite_submitters = (submitter.submission.template_submitters || submitter.submission.template.submitters).select { |s| s['invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } }.to_json %>
<% optional_invite_submitters = (submitter.submission.template_submitters || submitter.submission.template.submitters).select { |s| s['optional_invite_by_uuid'] == submitter.uuid && submitter.submission.submitters.none? { |e| e.uuid == s['uuid'] } }.to_json %>
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-schema="<%= schema.to_json %>" data-reuse-signature="<%= configs[:reuse_signature] %>" data-require-signing-reason="<%= configs[:require_signing_reason] %>" data-with-signature-id="<%= configs[:with_signature_id] %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'].presence || submitter.submission.template.preferences['completed_redirect_url'] %>" data-completed-message="<%= (configs[:completed_message]&.compact_blank.presence || submitter.submission.template.preferences['completed_message'] || {}).to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(submitter.submission.account) %>" data-optional-invite-submitters="<%= optional_invite_submitters %>" data-invite-submitters="<%= invite_submitters %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>" data-previous-signature-value="<%= local_assigns[:signature_attachment]&.uuid %>" data-remember-signature="<%= configs[:prefill_signature] %>" data-dry-run="<%= local_assigns[:dry_run] %>" data-expand="<%= local_assigns[:expand] %>" data-scroll-padding="<%= local_assigns[:scroll_padding] %>" data-language="<%= I18n.locale.to_s.split('-').first %>"></submission-form>
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-schema="<%= schema.to_json %>" data-reuse-signature="<%= configs[:reuse_signature] %>" data-require-signing-reason="<%= configs[:require_signing_reason] %>" data-with-signature-id="<%= configs[:with_signature_id] %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'].presence || submitter.submission.template&.preferences&.dig('completed_redirect_url') %>" data-completed-message="<%= (configs[:completed_message]&.compact_blank.presence || submitter.submission.template&.preferences&.dig('completed_message') || {}).to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(submitter.submission.account) %>" data-optional-invite-submitters="<%= optional_invite_submitters %>" data-invite-submitters="<%= invite_submitters %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>" data-previous-signature-value="<%= local_assigns[:signature_attachment]&.uuid %>" data-remember-signature="<%= configs[:prefill_signature] %>" data-dry-run="<%= local_assigns[:dry_run] %>" data-expand="<%= local_assigns[:expand] %>" data-scroll-padding="<%= local_assigns[:scroll_padding] %>" data-language="<%= I18n.locale.to_s.split('-').first %>"></submission-form>

@ -10,7 +10,7 @@
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
</div>
<div dir="auto">
<p class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template.name %></p>
<p class="text-sm"><%= t('form_has_been_deleted_by_html', name: @submitter.account.name) %></p>
</div>
</div>

@ -10,7 +10,7 @@
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
</div>
<div dir="auto">
<p class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template.name %></p>
<p class="text-sm"><%= t('awaiting_completion_by_the_other_party') %></p>
</div>
</div>

@ -10,7 +10,7 @@
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
</div>
<div>
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
<p dir="auto" class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template.name %></p>
<p dir="auto" class="text-sm">
<%= t(@submitter.with_signature_fields? ? 'signed_on_time' : 'completed_on_time', time: l(@submitter.completed_at.to_date, format: :long)) %>
</p>
@ -23,7 +23,7 @@
<toggle-submit>
<%= button_to button_title(title: t('send_copy_to_email'), disabled_with: t('sending'), icon: svg_icon('mail_forward', class: 'w-6 h-6')), send_submission_email_index_path, params: { submitter_slug: @submitter.slug }, class: 'white-button w-full' %>
</toggle-submit>
<% if Templates.filter_undefined_submitters(@submitter.submission.template).size != 1 %>
<% if Templates.filter_undefined_submitters(@submitter.submission.template_submitters).size != 1 %>
<div class="divider uppercase"><%= t('or') %></div>
<% else %>
<div class="py-2"></div>
@ -42,8 +42,8 @@
</download-button>
<% end %>
</div>
<% undefined_submitters = Templates.filter_undefined_submitters(@submitter.submission.template) %>
<% if undefined_submitters.size == 1 && undefined_submitters.first['uuid'] == @submitter.uuid && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false %>
<% undefined_submitters = Templates.filter_undefined_submitters(@submitter.submission.template_submitters) %>
<% if undefined_submitters.size == 1 && undefined_submitters.first['uuid'] == @submitter.uuid && %w[api embed].exclude?(@submitter.submission.source) && @submitter.account.account_configs.find_or_initialize_by(key: AccountConfig::ALLOW_TO_RESUBMIT).value != false && @submitter.template && !@submitter.template.archived_at? %>
<div class="divider uppercase"><%= t('or') %></div>
<toggle-submit class="block">
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), resubmit_form_path, params: { resubmit: @submitter.slug }, method: :put, class: 'white-button w-full' %>

@ -10,7 +10,7 @@
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
</div>
<div dir="auto">
<p class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template.name %></p>
<p class="text-sm"><%= t('form_has_been_declined_on_html', time: l(@submitter.declined_at, format: :long)) %></p>
</div>
</div>

@ -10,7 +10,7 @@
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
</div>
<div dir="auto">
<p class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
<p class="text-lg font-bold mb-1"><%= @submitter.submission.name || @submitter.submission.template.name %></p>
<p class="text-sm"><%= t('form_expired_at_html', time: l(@submitter.submission.expire_at, format: :long)) %></p>
</div>
</div>

@ -1,4 +1,4 @@
<% content_for(:html_title, "#{@submitter.submission.template.name} | DocuSeal") %>
<% content_for(:html_title, "#{@submitter.submission.name || @submitter.submission.template.name} | DocuSeal") %>
<% content_for(:html_description, "#{@submitter.account.name} has invited you to fill and sign documents online effortlessly with a secure, fast, and user-friendly digital document signing solution.") %>
<% fields_index = Templates.build_field_areas_index(@submitter.submission.template_fields || @submitter.submission.template.fields) %>
<% values = @submitter.submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
@ -13,7 +13,7 @@
<%= render('submit_form/banner') %>
<div id="signing_form_header" class="sticky min-[1230px]:static top-0 z-50 bg-base-100 py-2 px-2 flex items-center md:-mx-[8px]" style="margin-bottom: -16px">
<div class="text-xl md:text-2xl font-medium focus:text-clip" style="width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
<%= @submitter.submission.template.name %>
<%= @submitter.submission.name || @submitter.submission.template.name %>
</div>
<div class="flex items-center space-x-2" style="margin-left: 20px; flex-shrink: 0">
<% if @form_configs[:with_decline] %>
@ -58,11 +58,11 @@
</scroll-buttons>
<% end %>
<% schema.each do |item| %>
<% document = @submitter.submission.template_schema_documents.find { |a| a.uuid == item['attachment_uuid'] } %>
<% document = @submitter.submission.schema_documents.find { |a| a.uuid == item['attachment_uuid'] } %>
<div id="document-<%= document.uuid %>">
<% document_annots_index = document.metadata.dig('pdf', 'annotations')&.group_by { |e| e['page'] } || {} %>
<% preview_images_index = document.preview_images.loaded? ? document.preview_images.index_by { |e| e.filename.base.to_i } : {} %>
<% lazyload_metadata = document.preview_images.last.metadata %>
<% lazyload_metadata = document.preview_images.last&.metadata || {} %>
<% (document.metadata.dig('pdf', 'number_of_pages') || (document.preview_images.loaded? ? preview_images_index.size : document.preview_images.size)).times do |index| %>
<% page = preview_images_index[index] || page_blob_struct.new(metadata: lazyload_metadata, url: preview_document_page_path(document.signed_uuid, "#{index}.jpg")) %>
<div class="relative my-4 shadow-md">

@ -2,6 +2,6 @@
<%= render 'custom_content', content: @body, submitter: @submitter %>
<% else %>
<p><%= t('hi_there') %>,</p>
<p><%= I18n.t(:name_has_been_completed_by_submitters, name: @submitter.submission.template.name, submitters: @submitter.submission.submitters.order(:completed_at).map { |e| e.name || e.email || e.phone }.uniq.join(', ')) %></p>
<p><%= I18n.t(:name_has_been_completed_by_submitters, name: @submitter.submission.name || @submitter.submission.template.name, submitters: @submitter.submission.submitters.order(:completed_at).map { |e| e.name || e.email || e.phone }.uniq.join(', ')) %></p>
<p><%= link_to submission_url(@submitter.submission), submission_url(@submitter.submission) %></p>
<% end %>

@ -1,4 +1,4 @@
<p><%= t('hi_there') %>,</p>
<p><%= t('name_declined_by_submitter_with_the_following_reason', name: @submitter.submission.template.name, submitter: @submitter.name || @submitter.email || @submitter.phone) %></p>
<p><%= t('name_declined_by_submitter_with_the_following_reason', name: @submitter.submission.name || @submitter.submission.template.name, submitter: @submitter.name || @submitter.email || @submitter.phone) %></p>
<%= simple_format(h(@submitter.submission_events.find_by(event_type: :decline_form).data['reason'])) %>
<p><%= link_to submission_url(@submitter.submission), submission_url(@submitter.submission) %></p>

@ -2,10 +2,10 @@
<%= render 'custom_content', content: @body, submitter: @submitter, sig: @sig %>
<% else %>
<p><%= t('hi_there') %>,</p>
<p><%= t('please_check_the_copy_of_your_name_in_the_email_attachments', name: @submitter.submission.template.name) %>
<p><%= t('please_check_the_copy_of_your_name_in_the_email_attachments', name: @submitter.submission.name || @submitter.submission.template.name) %>
<p><%= t('alternatively_you_can_review_and_download_your_copy_using_the_link_below') %></p>
<p>
<%= link_to @submitter.template.name, submissions_preview_url(@submitter.submission.slug, { sig: @sig }.compact) %>
<%= link_to @submitter.submission.name || @submitter.submission.template.name, submissions_preview_url(@submitter.submission.slug, { sig: @sig }.compact) %>
</p>
<p>
<%= t('thanks') %>,<br><%= @current_account.name %>

@ -6,7 +6,7 @@
<% end %>
<% else %>
<p><%= t('hi_there') %>,</p>
<p><%= I18n.t(@submitter.with_signature_fields? ? :you_have_been_invited_to_sign_the_name : :you_have_been_invited_to_submit_the_name_form, name: @submitter.submission.template.name) %></p>
<p><%= I18n.t(@submitter.with_signature_fields? ? :you_have_been_invited_to_sign_the_name : :you_have_been_invited_to_submit_the_name_form, name: @submitter.submission.name || @submitter.submission.template.name) %></p>
<p><%= link_to I18n.t(@submitter.with_signature_fields? ? :review_and_sign : :review_and_submit), submit_form_url(slug: @submitter.slug, t: SubmissionEvents.build_tracking_param(@submitter, 'click_email'), host: ENV.fetch('EMAIL_HOST', Docuseal.default_url_options[:host])) %></p>
<p><%= t('please_contact_us_by_replying_to_this_email_if_you_didn_t_request_this') %></p>
<p>

@ -2,12 +2,12 @@
<div class="bg-base-200 rounded-2xl flex flex-col sm:flex-row items-strech">
<% if local_assigns[:with_template] %>
<% template = submission.template %>
<a href="<%= template_path(template) %>" class="px-5 sm:pr-3 py-3 group sm:rounded-l-2xl sm:rounded-tr-none rounded-t-2xl flex sm:flex-col justify-between sm:w-52 w-full flex-shrink-0 bg-base-300/60 space-x-2 sm:space-x-0">
<a href="<%= template ? template_path(template) : submission_path(submission) %>" class="px-5 sm:pr-3 py-3 group sm:rounded-l-2xl sm:rounded-tr-none rounded-t-2xl flex sm:flex-col justify-between sm:w-52 w-full flex-shrink-0 bg-base-300/60 space-x-2 sm:space-x-0">
<div>
<div class="font-medium items-start w-full group-hover:link text-sm flex space-x-1">
<%= svg_icon('file_text', class: 'w-4 h-4 mt-0.5 flex-shrink-0') %>
<span style="overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;">
<% template.name.split(/(_)/).each do |item| %><%= item %><wbr><% end %>
<% (submission.name || template.name).split(/(_)/).each do |item| %><%= item %><wbr><% end %>
<%= svg_icon('arrow_right', class: 'w-4 h-4 sm:inline group-hover:visible invisible hidden') %>
</span>
</div>
@ -78,7 +78,7 @@
</download-button>
</div>
</div>
<% elsif !submission.archived_at? && !template.archived_at? && !submission.expired? && !submitter.declined_at? %>
<% elsif !submission.archived_at? && !template&.archived_at? && !submission.expired? && !submitter.declined_at? %>
<% if current_user.email == submitter.email %>
<div class="flex-1 md:flex-none md:w-36 flex">
<a href="<%= submit_form_url(slug: submitter.slug) %>" data-turbo="false" target="_blank" id="sign_yourself_button" class="btn btn-sm btn-neutral btn-outline bg-white w-full md:w-36 flex z-[1]">
@ -101,7 +101,7 @@
<%= t('view') %>
</a>
</div>
<% if !submission.archived_at? && !template.archived_at? && can?(:destroy, submission) %>
<% if !submission.archived_at? && !template&.archived_at? && can?(:destroy, submission) %>
<span data-tip="<%= t('archive') %>" class="sm:tooltip tooltip-top">
<%= button_to button_title(title: nil, disabled_with: t(:archive).first(4), icon: svg_icon('archive', class: 'w-6 h-6')), submission_path(submission), class: 'btn btn-outline btn-sm w-full md:w-fit', form: { class: 'flex' }, title: t('archive'), method: :delete %>
</span>
@ -164,7 +164,7 @@
<span class="inline"><%= t('download')[..-2] %>...</span>
</span>
</download-button>
<% elsif !template.archived_at? && !submission.archived_at? && !is_submission_completed && !submission.expired? && !submitter.declined_at? %>
<% elsif !template&.archived_at? && !submission.archived_at? && !is_submission_completed && !submission.expired? && !submitter.declined_at? %>
<div class="relative flex items-center space-x-3">
<% if current_user.email == submitter.email %>
<a href="<%= submit_form_url(slug: submitter.slug) %>" data-turbo="false" target="_blank" id="sign_yourself_button" class="absolute md:relative top-0 right-0 btn btn-xs btn-outline btn-neutral bg-white w-28 md:w-36 z-[1]">

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddNameToSubmissions < ActiveRecord::Migration[8.0]
def change
add_column :submissions, :name, :text
end
end

@ -0,0 +1,7 @@
# frozen_string_literal: true
class RemoveNotNullTemplateId < ActiveRecord::Migration[8.0]
def change
change_column_null :submissions, :template_id, true
end
end

@ -0,0 +1,7 @@
# frozen_string_literal: true
class RemoveCompletedSubmitterTemplateNotNull < ActiveRecord::Migration[8.0]
def change
change_column_null :completed_submitters, :template_id, true
end
end

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_05_30_080846) do
ActiveRecord::Schema[8.0].define(version: 2025_05_31_085328) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -110,7 +110,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_30_080846) do
t.bigint "submitter_id", null: false
t.bigint "submission_id", null: false
t.bigint "account_id", null: false
t.bigint "template_id", null: false
t.bigint "template_id"
t.string "source", null: false
t.integer "sms_count", null: false
t.datetime "completed_at", null: false
@ -270,7 +270,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_30_080846) do
end
create_table "submissions", force: :cascade do |t|
t.bigint "template_id", null: false
t.bigint "template_id"
t.bigint "created_by_user_id"
t.datetime "archived_at"
t.datetime "created_at", null: false
@ -284,6 +284,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_30_080846) do
t.text "preferences", null: false
t.bigint "account_id", null: false
t.datetime "expire_at"
t.text "name"
t.index ["account_id", "id"], name: "index_submissions_on_account_id_and_id"
t.index ["created_by_user_id"], name: "index_submissions_on_created_by_user_id"
t.index ["slug"], name: "index_submissions_on_slug", unique: true

@ -2,6 +2,7 @@
module ReplaceEmailVariables
TEMPLATE_NAME = /\{+template\.name\}+/i
SUBMISSION_NAME = /\{+submission\.name\}+/i
TEMPLATE_ID = /\{+template\.id\}+/i
SUBMITTER_LINK = /\{+submitter\.link\}+/i
ACCOUNT_NAME = /\{+account\.name\}+/i
@ -31,7 +32,10 @@ module ReplaceEmailVariables
# rubocop:disable Metrics
def call(text, submitter:, tracking_event_type: 'click_email', html_escape: false, sig: nil)
text = replace(text, TEMPLATE_NAME, html_escape:) { (submitter.template || submitter.submission.template).name }
text = replace(text, TEMPLATE_NAME, html_escape:) do
(submitter.template || submitter.submission.template || submitter.submission).name
end
text = replace(text, SUBMISSION_NAME, html_escape:) { submitter.submission.name }
text = replace(text, TEMPLATE_ID, html_escape:) { submitter.template.id }
text = replace(text, SUBMITTER_ID, html_escape:) { submitter.id }
text = replace(text, SUBMITTER_SLUG, html_escape:) { submitter.slug }

@ -21,7 +21,7 @@ module Submissions
arel = arel.or(Arel::Table.new(:submitters)[:values].matches(term)) if search_values
if search_template
submissions = submissions.joins(:template)
submissions = submissions.left_joins(:template)
arel = arel.or(Template.arel_table[:name].lower.matches("%#{keyword.downcase}%"))
end
@ -40,15 +40,17 @@ module Submissions
def preload_with_pages(submission)
ActiveRecord::Associations::Preloader.new(
records: [submission],
associations: [:template, { template_schema_documents: :blob }]
associations: [
submission.template_id? ? { template_schema_documents: :blob } : { documents_attachments: :blob }
]
).call
total_pages =
submission.template_schema_documents.sum { |e| e.metadata.dig('pdf', 'number_of_pages').to_i }
submission.schema_documents.sum { |e| e.metadata.dig('pdf', 'number_of_pages').to_i }
if total_pages < PRELOAD_ALL_PAGES_AMOUNT
ActiveRecord::Associations::Preloader.new(
records: submission.template_schema_documents,
records: submission.schema_documents,
associations: [:blob, { preview_images_attachments: :blob }]
).call
end
@ -90,10 +92,10 @@ module Submissions
emails
end
def create_from_submitters(template:, user:, submissions_attrs:, source:,
def create_from_submitters(template:, user:, submissions_attrs:, source:, with_template: true,
submitters_order: DEFAULT_SUBMITTERS_ORDER, params: {})
Submissions::CreateFromSubmitters.call(
template:, user:, submissions_attrs:, source:, submitters_order:, params:
template:, user:, submissions_attrs:, source:, submitters_order:, params:, with_template:
)
end

@ -7,7 +7,7 @@ module Submissions
module_function
# rubocop:disable Metrics
def call(template:, user:, submissions_attrs:, source:, submitters_order:, params: {})
def call(template:, user:, submissions_attrs:, source:, submitters_order:, params: {}, with_template: true)
preferences = Submitters.normalize_preferences(user.account, user, params)
submissions = Array.wrap(submissions_attrs).filter_map do |attrs|
@ -21,6 +21,7 @@ module Submissions
submission = template.submissions.new(created_by_user: user, source:,
account_id: user.account_id,
preferences: set_submission_preferences,
name: with_template ? attrs[:name] : (attrs[:name] || template.name),
expire_at:,
template_submitters: [], submitters_order:)
@ -60,7 +61,7 @@ module Submissions
preferences: preferences.merge(submission_preferences))
end
maybe_set_template_fields(submission, attrs[:submitters])
maybe_set_template_fields(submission, attrs[:submitters], with_template:)
if submission.submitters.size > template.submitters.size
raise BaseError, 'Defined more signing parties than in template'
@ -76,6 +77,8 @@ module Submissions
maybe_add_invite_submitters(submission, template)
submission.template = nil unless with_template
submission.tap(&:save!)
end
@ -118,7 +121,7 @@ module Submissions
}.compact_blank
end
def maybe_set_template_fields(submission, submitters_attrs, default_submitter_uuid: nil)
def maybe_set_template_fields(submission, submitters_attrs, default_submitter_uuid: nil, with_template: true)
template_fields = (submission.template_fields || submission.template.fields).deep_dup
submitters = submission.template_submitters || submission.template.submitters
@ -133,7 +136,7 @@ module Submissions
end
if template_fields != (submission.template_fields || submission.template.fields) ||
submitters_attrs.any? { |e| e[:completed].present? }
submitters_attrs.any? { |e| e[:completed].present? } || !with_template
submission.template_fields = template_fields
submission.template_schema = submission.template.schema if submission.template_schema.blank?
end

@ -58,7 +58,8 @@ module Submissions
ActiveStorage::Attachment.create!(
blob: ActiveStorage::Blob.create_and_upload!(
io: io.tap(&:rewind), filename: "#{I18n.t('audit_log')} - #{submission.template.name}.pdf"
io: io.tap(&:rewind), filename: "#{I18n.t('audit_log')} - " \
"#{submission.name || submission.template.name}.pdf"
),
name: 'audit_trail',
record: submission
@ -186,7 +187,7 @@ module Submissions
last_submitter = submission.submitters.select(&:completed_at).max_by(&:completed_at)
documents_data = Submitters.select_attachments_for_download(last_submitter).map do |document|
original_documents = submission.template.documents.select do |e|
original_documents = submission.schema_documents.select do |e|
e.uuid == (document.metadata['original_uuid'] || document.uuid)
end.presence

@ -32,7 +32,7 @@ module Submissions
ActiveStorage::Attachment.create!(
blob: ActiveStorage::Blob.create_and_upload!(
io: io.tap(&:rewind), filename: "#{submission.template.name}.pdf"
io: io.tap(&:rewind), filename: "#{submission.name || submission.template.name}.pdf"
),
name: 'combined_document',
record: submission

@ -70,7 +70,7 @@ module Submissions
submitter:,
uuid: GenerateResultAttachments.images_pdf_uuid(original_documents.select(&:image?)),
values_hash:,
name: template.name
name: submission.name || template.name
)
ApplicationRecord.no_touching do

@ -72,17 +72,17 @@ module Submissions
pdfs_index = generate_pdfs(submitter)
template = submitter.submission.template
account = submitter.account
submission = submitter.submission
pkcs = Accounts.load_signing_pkcs(account)
tsa_url = Accounts.load_timeserver_url(account)
image_pdfs = []
original_documents = template.documents.preload(:blob)
original_documents = submission.schema_documents.preload(:blob)
result_attachments =
submitter.submission.template_schema.filter_map do |item|
submission.template_schema.filter_map do |item|
pdf = pdfs_index[item['attachment_uuid']]
next if pdf.nil?
@ -114,7 +114,7 @@ module Submissions
tsa_url:,
pkcs:,
uuid: images_pdf_uuid(original_documents.select(&:image?)),
name: template.name
name: submission.name || template.name
)
ApplicationRecord.no_touching do
@ -656,14 +656,14 @@ module Submissions
Submissions::EnsureResultGenerated.call(latest_submitter) if latest_submitter
documents = latest_submitter&.documents&.preload(:blob).to_a.presence
documents ||= submission.template_schema_documents.preload(:blob)
documents ||= submission.schema_documents.preload(:blob)
attachment_uuids = Submissions.filtered_conditions_schema(submission).pluck('attachment_uuid')
attachments_index = documents.index_by { |a| a.metadata['original_uuid'] || a.uuid }
attachment_uuids.each_with_object({}) do |uuid, acc|
attachment = attachments_index[uuid]
attachment ||= submission.template_schema_documents.preload(:blob).find { |a| a.uuid == uuid }
attachment ||= submission.schema_documents.preload(:blob).find { |a| a.uuid == uuid }
next unless attachment

@ -3,7 +3,7 @@
module Submissions
module SerializeForApi
SERIALIZE_PARAMS = {
only: %i[id slug source submitters_order expire_at created_at updated_at archived_at],
only: %i[id name slug source submitters_order expire_at created_at updated_at archived_at],
methods: %i[audit_log_url combined_document_url],
include: {
submitters: { only: %i[id] },

@ -27,7 +27,7 @@ module Submitters
return [submitter.submission.combined_document_attachment]
end
original_documents = submitter.submission.template_schema_documents.preload(:blob)
original_documents = submitter.submission.schema_documents.preload(:blob)
is_more_than_two_images = original_documents.count(&:image?) > 1
submitter.documents.preload(:blob).reject do |attachment|
@ -36,25 +36,6 @@ module Submitters
end
end
def preload_with_pages(submitter)
ActiveRecord::Associations::Preloader.new(
records: [submitter],
associations: [submission: [:template, { template_schema_documents: :blob }]]
).call
total_pages =
submitter.submission.template_schema_documents.sum { |e| e.metadata.dig('pdf', 'number_of_pages').to_i }
if total_pages < PRELOAD_ALL_PAGES_AMOUNT
ActiveRecord::Associations::Preloader.new(
records: submitter.submission.template_schema_documents,
associations: [:blob, { preview_images_attachments: :blob }]
).call
end
submitter
end
def create_attachment!(submitter, params)
blob =
if (file = params[:file])

@ -43,8 +43,8 @@ module Templates
templates.where(Template.arel_table[:name].lower.matches("%#{keyword.downcase}%"))
end
def filter_undefined_submitters(template)
template.submitters.to_a.select do |item|
def filter_undefined_submitters(template_submitters)
template_submitters.to_a.select do |item|
item['invite_by_uuid'].blank? && item['optional_invite_by_uuid'].blank? &&
item['linked_to_uuid'].blank? && item['is_requester'].blank? && item['email'].blank?
end

@ -4,7 +4,7 @@ module Templates
module CloneAttachments
module_function
def call(template:, original_template:, documents: [], excluded_attachment_uuids: [])
def call(template:, original_template:, documents: [], excluded_attachment_uuids: [], save: true)
schema_uuids_replacements = {}
template.schema.each_with_index do |schema_item, index|
@ -29,32 +29,31 @@ module Templates
end
end
template.save!
attachments =
original_template.schema_documents.filter_map do |document|
new_attachment_uuid = schema_uuids_replacements[document.uuid]
original_template.schema_documents.filter_map do |document|
new_attachment_uuid = schema_uuids_replacements[document.uuid]
next unless new_attachment_uuid
next unless new_attachment_uuid
new_document =
ApplicationRecord.no_touching do
template.documents_attachments.create!(
new_document =
template.documents_attachments.new(
uuid: new_attachment_uuid,
blob_id: document.blob_id
)
end
clone_document_preview_images_attachments(document:, new_document:)
clone_document_preview_images_attachments(document:, new_document:)
new_document
end
new_document
end
template.save! if save
attachments
end
def clone_document_preview_images_attachments(document:, new_document:)
ApplicationRecord.no_touching do
document.preview_images_attachments.each do |preview_image|
new_document.preview_images_attachments.create!(blob_id: preview_image.blob_id)
end
document.preview_images_attachments.each do |preview_image|
new_document.preview_images_attachments.new(blob_id: preview_image.blob_id)
end
end
end

@ -36,6 +36,24 @@ module Templates
attachment
end
def process(attachment, data, extract_fields: false)
if attachment.content_type == PDF_CONTENT_TYPE && extract_fields && data.size < MAX_FLATTEN_FILE_SIZE
pdf = HexaPDF::Document.new(io: StringIO.new(data))
fields = Templates::FindAcroFields.call(pdf, attachment, data)
end
pdf ||= HexaPDF::Document.new(io: StringIO.new(data))
number_of_pages = pdf.pages.size
attachment.metadata['pdf'] ||= {}
attachment.metadata['pdf']['number_of_pages'] = number_of_pages
attachment.metadata['pdf']['fields'] = fields if fields
attachment
end
def generate_preview_image(attachment, data)
ActiveStorage::Attachment.where(name: ATTACHMENT_NAME, record: attachment).destroy_all

Loading…
Cancel
Save