add download combined pdf button
This commit is contained in:
@@ -16,13 +16,16 @@ class SubmissionsDebugController < ApplicationController
|
|||||||
render 'submit_form/show'
|
render 'submit_form/show'
|
||||||
end
|
end
|
||||||
f.pdf do
|
f.pdf do
|
||||||
if params[:audit]
|
result =
|
||||||
Submissions::GenerateAuditTrail.call(@submitter.submission)
|
if params[:audit]
|
||||||
else
|
Submissions::GenerateAuditTrail.call(@submitter.submission)
|
||||||
Submissions::GenerateResultAttachments.call(@submitter)
|
elsif params[:combined]
|
||||||
end
|
Submissions::GenerateCombinedAttachment.call(@submitter)
|
||||||
|
else
|
||||||
|
Submissions::GenerateResultAttachments.call(@submitter)
|
||||||
|
end
|
||||||
|
|
||||||
send_data ActiveStorage::Attachment.where(name: params[:audit] ? :audit_trail : :documents).last.download,
|
send_data Array.wrap(result).first.download,
|
||||||
filename: 'debug.pdf',
|
filename: 'debug.pdf',
|
||||||
disposition: 'inline',
|
disposition: 'inline',
|
||||||
type: 'application/pdf'
|
type: 'application/pdf'
|
||||||
|
|||||||
@@ -33,7 +33,17 @@ class SubmissionsDownloadController < ApplicationController
|
|||||||
return head :not_found
|
return head :not_found
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: build_urls(last_submitter)
|
if params[:combined]
|
||||||
|
url = build_combined_url(submitter)
|
||||||
|
|
||||||
|
if url
|
||||||
|
render json: [url]
|
||||||
|
else
|
||||||
|
head :not_found
|
||||||
|
end
|
||||||
|
else
|
||||||
|
render json: build_urls(last_submitter)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -47,4 +57,14 @@ class SubmissionsDownloadController < ApplicationController
|
|||||||
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: FILES_TTL.from_now.to_i)
|
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: FILES_TTL.from_now.to_i)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def build_combined_url(submitter)
|
||||||
|
return if submitter.submission.submitters.exists?(completed_at: nil)
|
||||||
|
return if submitter.submission.submitters.order(:completed_at).last != submitter
|
||||||
|
|
||||||
|
attachment = submitter.submission.combined_document_attachment
|
||||||
|
attachment ||= Submissions::GenerateCombinedAttachment.call(submitter)
|
||||||
|
|
||||||
|
ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: FILES_TTL.from_now.to_i)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class Submission < ApplicationRecord
|
|||||||
attribute :slug, :string, default: -> { SecureRandom.base58(14) }
|
attribute :slug, :string, default: -> { SecureRandom.base58(14) }
|
||||||
|
|
||||||
has_one_attached :audit_trail
|
has_one_attached :audit_trail
|
||||||
|
has_one_attached :combined_document
|
||||||
|
|
||||||
has_many :template_schema_documents,
|
has_many :template_schema_documents,
|
||||||
->(e) { where(uuid: (e.template_schema.presence || e.template.schema).pluck('attachment_uuid')) },
|
->(e) { where(uuid: (e.template_schema.presence || e.template.schema).pluck('attachment_uuid')) },
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M6 9l6 6l6 -6" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 312 B |
@@ -14,16 +14,42 @@
|
|||||||
</a>
|
</a>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if last_submitter = @submission.submitters.to_a.select(&:completed_at?).max_by(&:completed_at) %>
|
<% if last_submitter = @submission.submitters.to_a.select(&:completed_at?).max_by(&:completed_at) %>
|
||||||
<download-button data-src="<%= submitter_download_index_path(last_submitter.slug, { sig: params[:sig] }.compact) %>" class="base-button">
|
<% is_all_completed = @submission.submitters.to_a.all?(&:completed_at?) %>
|
||||||
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
<div class="join relative">
|
||||||
<%= svg_icon('download', class: 'w-6 h-6') %>
|
<download-button data-src="<%= submitter_download_index_path(last_submitter.slug, { sig: params[:sig] }.compact) %>" class="base-button <%= '!rounded-r-none !pr-2' if is_all_completed %>">
|
||||||
<span class="hidden md:inline">Download</span>
|
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||||
</span>
|
<%= svg_icon('download', class: 'w-6 h-6') %>
|
||||||
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
<span class="hidden md:inline">Download</span>
|
||||||
<%= svg_icon('loader', class: 'w-6 h-6 animate-spin') %>
|
</span>
|
||||||
<span class="hidden md:inline">Downloading</span>
|
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
||||||
</span>
|
<%= svg_icon('loader', class: 'w-6 h-6 animate-spin') %>
|
||||||
</download-button>
|
<span class="hidden md:inline">Downloading</span>
|
||||||
|
</span>
|
||||||
|
</download-button>
|
||||||
|
<% if is_all_completed %>
|
||||||
|
<div class="dropdown dropdown-end">
|
||||||
|
<label tabindex="0" class="base-button !rounded-l-none !pl-1 !pr-2 !border-l-neutral-500">
|
||||||
|
<span class="text-sm align-text-top">
|
||||||
|
<%= svg_icon('chevron_down', class: 'w-6 h-6 flex-shrink-0 stroke-2') %>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<ul tabindex="0" class="z-10 dropdown-content p-2 mt-2 shadow menu text-base bg-base-100 rounded-box text-right">
|
||||||
|
<li>
|
||||||
|
<download-button data-src="<%= submitter_download_index_path(last_submitter.slug, { sig: params[:sig], combined: true }.compact) %>" class="flex items-center">
|
||||||
|
<span class="flex items-center justify-center space-x-2" data-target="download-button.defaultButton">
|
||||||
|
<%= svg_icon('download', class: 'w-6 h-6 flex-shrink-0') %>
|
||||||
|
<span class="whitespace-nowrap">Download combined PDF</span>
|
||||||
|
</span>
|
||||||
|
<span class="flex items-center justify-center space-x-2 hidden" data-target="download-button.loadingButton">
|
||||||
|
<%= svg_icon('loader', class: 'w-6 h-6 animate-spin') %>
|
||||||
|
<span>Downloading</span>
|
||||||
|
</span>
|
||||||
|
</download-button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
<% elsif @submission.submitters.to_a.size == 1 %>
|
<% elsif @submission.submitters.to_a.size == 1 %>
|
||||||
<%= render 'shared/clipboard_copy', text: start_form_url(slug: @submission.template.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Share Link', copied_title: 'Copied to Clipboard' %>
|
<%= render 'shared/clipboard_copy', text: start_form_url(slug: @submission.template.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Share Link', copied_title: 'Copied to Clipboard' %>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -77,7 +103,7 @@
|
|||||||
<% submitter_field_counters = Hash.new { 0 } %>
|
<% submitter_field_counters = Hash.new { 0 } %>
|
||||||
<% (@submission.template_submitters || @submission.template.submitters).each_with_index do |item, index| %>
|
<% (@submission.template_submitters || @submission.template.submitters).each_with_index do |item, index| %>
|
||||||
<% submitter = @submission.submitters.find { |e| e.uuid == item['uuid'] } %>
|
<% submitter = @submission.submitters.find { |e| e.uuid == item['uuid'] } %>
|
||||||
<div class="sticky -top-1 bg-base-100 pt-1 -mt-1 z-10">
|
<div class="sticky -top-1 bg-base-100 pt-1 -mt-1">
|
||||||
<div class="border border-base-300 rounded-md px-2 py-1 mb-1">
|
<div class="border border-base-300 rounded-md px-2 py-1 mb-1">
|
||||||
<div class="flex items-center space-x-1">
|
<div class="flex items-center space-x-1">
|
||||||
<span class="mx-1 w-3 h-3 rounded-full <%= colors[index] %>"></span>
|
<span class="mx-1 w-3 h-3 rounded-full <%= colors[index] %>"></span>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ module Submissions
|
|||||||
'Helvetica'
|
'Helvetica'
|
||||||
end
|
end
|
||||||
|
|
||||||
SIGN_REASON = 'Signed with DocuSeal.co'
|
|
||||||
VERIFIED_TEXT = 'Verified'
|
VERIFIED_TEXT = 'Verified'
|
||||||
UNVERIFIED_TEXT = 'Unverified'
|
UNVERIFIED_TEXT = 'Unverified'
|
||||||
|
|
||||||
@@ -31,9 +30,35 @@ module Submissions
|
|||||||
|
|
||||||
# rubocop:disable Metrics
|
# rubocop:disable Metrics
|
||||||
def call(submission)
|
def call(submission)
|
||||||
|
document = build_audit_trail(submission)
|
||||||
|
|
||||||
account = submission.account
|
account = submission.account
|
||||||
|
|
||||||
pkcs = Accounts.load_signing_pkcs(account)
|
pkcs = Accounts.load_signing_pkcs(account)
|
||||||
tsa_url = Accounts.load_timeserver_url(account)
|
tsa_url = Accounts.load_timeserver_url(account)
|
||||||
|
|
||||||
|
io = StringIO.new
|
||||||
|
|
||||||
|
document.trailer.info[:Creator] = "#{Docuseal.product_name} (#{Docuseal::PRODUCT_URL})"
|
||||||
|
|
||||||
|
sign_params = {
|
||||||
|
reason: sign_reason,
|
||||||
|
**Submissions::GenerateResultAttachments.build_signing_params(pkcs, tsa_url)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.sign(io, **sign_params)
|
||||||
|
|
||||||
|
ActiveStorage::Attachment.create!(
|
||||||
|
blob: ActiveStorage::Blob.create_and_upload!(
|
||||||
|
io: StringIO.new(io.string), filename: "Audit Log - #{submission.template.name}.pdf"
|
||||||
|
),
|
||||||
|
name: 'audit_trail',
|
||||||
|
record: submission
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_audit_trail(submission)
|
||||||
|
account = submission.account
|
||||||
verify_url = Rails.application.routes.url_helpers.settings_esign_url(**Docuseal.default_url_options)
|
verify_url = Rails.application.routes.url_helpers.settings_esign_url(**Docuseal.default_url_options)
|
||||||
page_size =
|
page_size =
|
||||||
if TimeUtils.timezone_abbr(account.timezone, Time.current.beginning_of_year).in?(US_TIMEZONES)
|
if TimeUtils.timezone_abbr(account.timezone, Time.current.beginning_of_year).in?(US_TIMEZONES)
|
||||||
@@ -299,24 +324,11 @@ module Submissions
|
|||||||
|
|
||||||
composer.table(events_data, cell_style: { padding: [0, 0, 12, 0], border: { width: 0 } }) if events_data.present?
|
composer.table(events_data, cell_style: { padding: [0, 0, 12, 0], border: { width: 0 } }) if events_data.present?
|
||||||
|
|
||||||
io = StringIO.new
|
composer.document
|
||||||
|
end
|
||||||
|
|
||||||
composer.document.trailer.info[:Creator] = "#{Docuseal.product_name} (#{Docuseal::PRODUCT_URL})"
|
def sign_reason
|
||||||
|
'Signed with DocuSeal.co'
|
||||||
sign_params = {
|
|
||||||
reason: SIGN_REASON,
|
|
||||||
**Submissions::GenerateResultAttachments.build_signing_params(pkcs, tsa_url)
|
|
||||||
}
|
|
||||||
|
|
||||||
composer.document.sign(io, **sign_params)
|
|
||||||
|
|
||||||
ActiveStorage::Attachment.create!(
|
|
||||||
blob: ActiveStorage::Blob.create_and_upload!(
|
|
||||||
io: StringIO.new(io.string), filename: "Audit Log - #{submission.template.name}.pdf"
|
|
||||||
),
|
|
||||||
name: 'audit_trail',
|
|
||||||
record: submission
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_logo(column, _submission = nil)
|
def add_logo(column, _submission = nil)
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Submissions
|
||||||
|
module GenerateCombinedAttachment
|
||||||
|
module_function
|
||||||
|
|
||||||
|
def call(submitter)
|
||||||
|
pdf = build_combined_pdf(submitter)
|
||||||
|
|
||||||
|
submission = submitter.submission
|
||||||
|
account = submission.account
|
||||||
|
|
||||||
|
pkcs = Accounts.load_signing_pkcs(account)
|
||||||
|
tsa_url = Accounts.load_timeserver_url(account)
|
||||||
|
|
||||||
|
io = StringIO.new
|
||||||
|
|
||||||
|
pdf.trailer.info[:Creator] = "#{Docuseal.product_name} (#{Docuseal::PRODUCT_URL})"
|
||||||
|
|
||||||
|
sign_params = {
|
||||||
|
reason: sign_reason,
|
||||||
|
**Submissions::GenerateResultAttachments.build_signing_params(pkcs, tsa_url)
|
||||||
|
}
|
||||||
|
|
||||||
|
pdf.sign(io, **sign_params)
|
||||||
|
|
||||||
|
ActiveStorage::Attachment.create!(
|
||||||
|
blob: ActiveStorage::Blob.create_and_upload!(
|
||||||
|
io: StringIO.new(io.string), filename: "#{submission.template.name}.pdf"
|
||||||
|
),
|
||||||
|
name: 'combined_document',
|
||||||
|
record: submission
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_combined_pdf(submitter)
|
||||||
|
pdfs_index = Submissions::GenerateResultAttachments.generate_pdfs(submitter)
|
||||||
|
|
||||||
|
audit_trail = Submissions::GenerateAuditTrail.build_audit_trail(submitter.submission)
|
||||||
|
|
||||||
|
audit_trail.dispatch_message(:complete_objects)
|
||||||
|
|
||||||
|
result = HexaPDF::Document.new
|
||||||
|
|
||||||
|
submitter.submission.template_schema.each do |item|
|
||||||
|
pdf = pdfs_index[item['attachment_uuid']]
|
||||||
|
|
||||||
|
pdf.dispatch_message(:complete_objects)
|
||||||
|
|
||||||
|
pdf.pages.each { |page| result.pages << result.import(page) }
|
||||||
|
end
|
||||||
|
|
||||||
|
audit_trail.pages.each { |page| result.pages << result.import(page) }
|
||||||
|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
def sign_reason
|
||||||
|
'Signed with DocuSeal.co'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -39,17 +39,58 @@ module Submissions
|
|||||||
|
|
||||||
# rubocop:disable Metrics
|
# rubocop:disable Metrics
|
||||||
def call(submitter)
|
def call(submitter)
|
||||||
cell_layouter = HexaPDF::Layout::TextLayouter.new(text_valign: :center, text_align: :center)
|
pdfs_index = generate_pdfs(submitter)
|
||||||
|
|
||||||
template = submitter.submission.template
|
template = submitter.submission.template
|
||||||
|
account = submitter.account
|
||||||
|
|
||||||
|
pkcs = Accounts.load_signing_pkcs(account)
|
||||||
|
tsa_url = Accounts.load_timeserver_url(account)
|
||||||
|
|
||||||
|
image_pdfs = []
|
||||||
|
original_documents = template.documents.preload(:blob)
|
||||||
|
|
||||||
|
result_attachments =
|
||||||
|
submitter.submission.template_schema.map do |item|
|
||||||
|
pdf = pdfs_index[item['attachment_uuid']]
|
||||||
|
|
||||||
|
attachment = build_pdf_attachment(pdf:, submitter:, pkcs:, tsa_url:,
|
||||||
|
uuid: item['attachment_uuid'],
|
||||||
|
name: item['name'])
|
||||||
|
|
||||||
|
image_pdfs << pdf if original_documents.find { |a| a.uuid == item['attachment_uuid'] }.image?
|
||||||
|
|
||||||
|
attachment
|
||||||
|
end
|
||||||
|
|
||||||
|
return result_attachments.map { |e| e.tap(&:save!) } if image_pdfs.size < 2
|
||||||
|
|
||||||
|
images_pdf =
|
||||||
|
image_pdfs.each_with_object(HexaPDF::Document.new) do |pdf, doc|
|
||||||
|
pdf.pages.each { |page| doc.pages << doc.import(page) }
|
||||||
|
end
|
||||||
|
|
||||||
|
images_pdf_attachment =
|
||||||
|
build_pdf_attachment(
|
||||||
|
pdf: images_pdf,
|
||||||
|
submitter:,
|
||||||
|
tsa_url:,
|
||||||
|
pkcs:,
|
||||||
|
uuid: images_pdf_uuid(original_documents.select(&:image?)),
|
||||||
|
name: template.name
|
||||||
|
)
|
||||||
|
|
||||||
|
(result_attachments + [images_pdf_attachment]).map { |e| e.tap(&:save!) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_pdfs(submitter)
|
||||||
|
cell_layouter = HexaPDF::Layout::TextLayouter.new(text_valign: :center, text_align: :center)
|
||||||
|
|
||||||
is_flatten =
|
is_flatten =
|
||||||
submitter.account.account_configs
|
submitter.account.account_configs
|
||||||
.find_or_initialize_by(key: AccountConfig::FLATTEN_RESULT_PDF_KEY).value != false
|
.find_or_initialize_by(key: AccountConfig::FLATTEN_RESULT_PDF_KEY).value != false
|
||||||
|
|
||||||
account = submitter.account
|
account = submitter.account
|
||||||
pkcs = Accounts.load_signing_pkcs(account)
|
|
||||||
tsa_url = Accounts.load_timeserver_url(account)
|
|
||||||
attachments_data_cache = {}
|
attachments_data_cache = {}
|
||||||
|
|
||||||
pdfs_index = build_pdfs_index(submitter, flatten: is_flatten)
|
pdfs_index = build_pdfs_index(submitter, flatten: is_flatten)
|
||||||
@@ -259,40 +300,7 @@ module Submissions
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
image_pdfs = []
|
pdfs_index
|
||||||
original_documents = template.documents.preload(:blob)
|
|
||||||
|
|
||||||
result_attachments =
|
|
||||||
submitter.submission.template_schema.map do |item|
|
|
||||||
pdf = pdfs_index[item['attachment_uuid']]
|
|
||||||
|
|
||||||
attachment = build_pdf_attachment(pdf:, submitter:, pkcs:, tsa_url:,
|
|
||||||
uuid: item['attachment_uuid'],
|
|
||||||
name: item['name'])
|
|
||||||
|
|
||||||
image_pdfs << pdf if original_documents.find { |a| a.uuid == item['attachment_uuid'] }.image?
|
|
||||||
|
|
||||||
attachment
|
|
||||||
end
|
|
||||||
|
|
||||||
return result_attachments.map { |e| e.tap(&:save!) } if image_pdfs.size < 2
|
|
||||||
|
|
||||||
images_pdf =
|
|
||||||
image_pdfs.each_with_object(HexaPDF::Document.new) do |pdf, doc|
|
|
||||||
pdf.pages.each { |page| doc.pages << doc.import(page) }
|
|
||||||
end
|
|
||||||
|
|
||||||
images_pdf_attachment =
|
|
||||||
build_pdf_attachment(
|
|
||||||
pdf: images_pdf,
|
|
||||||
submitter:,
|
|
||||||
tsa_url:,
|
|
||||||
pkcs:,
|
|
||||||
uuid: images_pdf_uuid(original_documents.select(&:image?)),
|
|
||||||
name: template.name
|
|
||||||
)
|
|
||||||
|
|
||||||
(result_attachments + [images_pdf_attachment]).map { |e| e.tap(&:save!) }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_pdf_attachment(pdf:, submitter:, pkcs:, tsa_url:, uuid:, name:)
|
def build_pdf_attachment(pdf:, submitter:, pkcs:, tsa_url:, uuid:, name:)
|
||||||
|
|||||||
Reference in New Issue
Block a user