From 9866e5468c1f221ba81db9dcc24083e06379aa55 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 25 May 2024 20:57:36 +0300 Subject: [PATCH] add download combined pdf button --- .../submissions_debug_controller.rb | 15 ++-- .../submissions_download_controller.rb | 22 ++++- app/models/submission.rb | 1 + app/views/icons/_chevron_down.html.erb | 4 + app/views/submissions/show.html.erb | 48 ++++++++--- lib/submissions/generate_audit_trail.rb | 48 +++++++---- .../generate_combined_attachment.rb | 62 ++++++++++++++ .../generate_result_attachments.rb | 82 ++++++++++--------- 8 files changed, 209 insertions(+), 73 deletions(-) create mode 100644 app/views/icons/_chevron_down.html.erb create mode 100644 lib/submissions/generate_combined_attachment.rb diff --git a/app/controllers/submissions_debug_controller.rb b/app/controllers/submissions_debug_controller.rb index e411db15..4a6e8b9d 100644 --- a/app/controllers/submissions_debug_controller.rb +++ b/app/controllers/submissions_debug_controller.rb @@ -16,13 +16,16 @@ class SubmissionsDebugController < ApplicationController render 'submit_form/show' end f.pdf do - if params[:audit] - Submissions::GenerateAuditTrail.call(@submitter.submission) - else - Submissions::GenerateResultAttachments.call(@submitter) - end + result = + if params[:audit] + Submissions::GenerateAuditTrail.call(@submitter.submission) + elsif params[:combined] + 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', disposition: 'inline', type: 'application/pdf' diff --git a/app/controllers/submissions_download_controller.rb b/app/controllers/submissions_download_controller.rb index 6207a5c7..815e7702 100644 --- a/app/controllers/submissions_download_controller.rb +++ b/app/controllers/submissions_download_controller.rb @@ -33,7 +33,17 @@ class SubmissionsDownloadController < ApplicationController return head :not_found 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 private @@ -47,4 +57,14 @@ class SubmissionsDownloadController < ApplicationController ActiveStorage::Blob.proxy_url(attachment.blob, expires_at: FILES_TTL.from_now.to_i) 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 diff --git a/app/models/submission.rb b/app/models/submission.rb index 309010d0..55bb8fcd 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -52,6 +52,7 @@ class Submission < ApplicationRecord attribute :slug, :string, default: -> { SecureRandom.base58(14) } has_one_attached :audit_trail + has_one_attached :combined_document has_many :template_schema_documents, ->(e) { where(uuid: (e.template_schema.presence || e.template.schema).pluck('attachment_uuid')) }, diff --git a/app/views/icons/_chevron_down.html.erb b/app/views/icons/_chevron_down.html.erb new file mode 100644 index 00000000..a7fed86c --- /dev/null +++ b/app/views/icons/_chevron_down.html.erb @@ -0,0 +1,4 @@ + + + + diff --git a/app/views/submissions/show.html.erb b/app/views/submissions/show.html.erb index ce41cc0d..e9dee1a9 100644 --- a/app/views/submissions/show.html.erb +++ b/app/views/submissions/show.html.erb @@ -14,16 +14,42 @@ <% end %> <% if last_submitter = @submission.submitters.to_a.select(&:completed_at?).max_by(&:completed_at) %> - - - <%= svg_icon('download', class: 'w-6 h-6') %> - - - - + <% is_all_completed = @submission.submitters.to_a.all?(&:completed_at?) %> +
+ + + <%= svg_icon('download', class: 'w-6 h-6') %> + + + + + <% if is_all_completed %> + + <% end %> +
<% 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' %> <% end %> @@ -77,7 +103,7 @@ <% submitter_field_counters = Hash.new { 0 } %> <% (@submission.template_submitters || @submission.template.submitters).each_with_index do |item, index| %> <% submitter = @submission.submitters.find { |e| e.uuid == item['uuid'] } %> -
+
diff --git a/lib/submissions/generate_audit_trail.rb b/lib/submissions/generate_audit_trail.rb index e4abcc33..7828e2d4 100644 --- a/lib/submissions/generate_audit_trail.rb +++ b/lib/submissions/generate_audit_trail.rb @@ -12,7 +12,6 @@ module Submissions 'Helvetica' end - SIGN_REASON = 'Signed with DocuSeal.co' VERIFIED_TEXT = 'Verified' UNVERIFIED_TEXT = 'Unverified' @@ -31,9 +30,35 @@ module Submissions # rubocop:disable Metrics def call(submission) + document = build_audit_trail(submission) + account = submission.account + pkcs = Accounts.load_signing_pkcs(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) page_size = 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? - io = StringIO.new - - composer.document.trailer.info[:Creator] = "#{Docuseal.product_name} (#{Docuseal::PRODUCT_URL})" - - sign_params = { - reason: SIGN_REASON, - **Submissions::GenerateResultAttachments.build_signing_params(pkcs, tsa_url) - } - - composer.document.sign(io, **sign_params) + composer.document + end - 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 - ) + def sign_reason + 'Signed with DocuSeal.co' end def add_logo(column, _submission = nil) diff --git a/lib/submissions/generate_combined_attachment.rb b/lib/submissions/generate_combined_attachment.rb new file mode 100644 index 00000000..c04c267d --- /dev/null +++ b/lib/submissions/generate_combined_attachment.rb @@ -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 diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index 5ff6aef9..4e37347c 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -39,17 +39,58 @@ module Submissions # rubocop:disable Metrics def call(submitter) - cell_layouter = HexaPDF::Layout::TextLayouter.new(text_valign: :center, text_align: :center) + pdfs_index = generate_pdfs(submitter) 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 = submitter.account.account_configs .find_or_initialize_by(key: AccountConfig::FLATTEN_RESULT_PDF_KEY).value != false account = submitter.account - pkcs = Accounts.load_signing_pkcs(account) - tsa_url = Accounts.load_timeserver_url(account) attachments_data_cache = {} pdfs_index = build_pdfs_index(submitter, flatten: is_flatten) @@ -259,40 +300,7 @@ module Submissions end end - 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!) } + pdfs_index end def build_pdf_attachment(pdf:, submitter:, pkcs:, tsa_url:, uuid:, name:)