diff --git a/app/controllers/account_configs_controller.rb b/app/controllers/account_configs_controller.rb index cf128ef9..e12d5c26 100644 --- a/app/controllers/account_configs_controller.rb +++ b/app/controllers/account_configs_controller.rb @@ -23,7 +23,8 @@ class AccountConfigsController < ApplicationController AccountConfig::WITH_SIGNATURE_ID, AccountConfig::COMBINE_PDF_RESULT_KEY, AccountConfig::REQUIRE_SIGNING_REASON_KEY, - AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY + AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY, + AccountConfig::SIGNATURE_CALLBACK_URL_KEY ].freeze InvalidKey = Class.new(StandardError) diff --git a/app/jobs/process_submitter_completion_job.rb b/app/jobs/process_submitter_completion_job.rb index 0976b3bf..2ada975a 100644 --- a/app/jobs/process_submitter_completion_job.rb +++ b/app/jobs/process_submitter_completion_job.rb @@ -29,6 +29,8 @@ class ProcessSubmitterCompletionJob end enqueue_completed_webhooks(submitter, is_all_completed:) + + SendSignatureCallbackJob.perform_async('submitter_id' => submitter.id) end def create_completed_submitter!(submitter) diff --git a/app/jobs/send_signature_callback_job.rb b/app/jobs/send_signature_callback_job.rb new file mode 100644 index 00000000..886d6db2 --- /dev/null +++ b/app/jobs/send_signature_callback_job.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +class SendSignatureCallbackJob + include Sidekiq::Job + + sidekiq_options queue: :webhooks + + TRACKED_FIELD_NAMES = %w[provider_signature client_caregiver_signature supervisor_signature].freeze + CALLBACK_ENDPOINT = 'https://app.therapypms.com/v1/note/signed'.freeze + + def perform(params = {}) + submitter = Submitter.find_by(id: params['submitter_id']) + + return unless submitter&.completed_at? + + callback_url = AccountConfig.find_by( + account_id: submitter.account_id, + key: AccountConfig::SIGNATURE_CALLBACK_URL_KEY + )&.value.presence || CALLBACK_ENDPOINT + + fields = submitter.submission.template_fields || submitter.submission.template&.fields + + return unless fields + + tracked_fields = fields.select do |f| + f['submitter_uuid'] == submitter.uuid && + f['type'] == 'signature' && + TRACKED_FIELD_NAMES.include?(f['name']) + end + + return if tracked_fields.empty? + + attachments_index = submitter.attachments.preload(:blob).index_by(&:uuid) + + signatures = tracked_fields.filter_map do |field| + attachment_uuid = submitter.values[field['uuid']] + next unless attachment_uuid.present? + + attachment = attachments_index[attachment_uuid] + next unless attachment + + { + field_name: field['name'], + field_uuid: field['uuid'], + signature: Base64.strict_encode64(attachment.download) + } + end + + return if signatures.empty? + + payload = { + admin_id: submitter.submission.created_by_user_id, + template_id: submitter.submission.template_id, + submission_id: submitter.submission_id, + embed_src: build_embed_src(submitter), + email: submitter.email, + signatures: signatures + } + + Faraday.post(callback_url) do |req| + req.headers['Content-Type'] = 'application/json' + req.body = payload.to_json + req.options.read_timeout = 10 + req.options.open_timeout = 10 + end + rescue Faraday::Error => e + Rails.logger.error("SendSignatureCallbackJob error for submitter #{params['submitter_id']}: #{e.message}") + end + + private + + def build_embed_src(submitter) + opts = Docuseal.default_url_options + port = opts[:port] ? ":#{opts[:port]}" : '' + + "#{opts[:protocol]}://#{opts[:host]}#{port}/submissions/#{submitter.submission_id}" + end +end diff --git a/app/models/account_config.rb b/app/models/account_config.rb index 3964e973..4d41eba6 100644 --- a/app/models/account_config.rb +++ b/app/models/account_config.rb @@ -54,6 +54,7 @@ class AccountConfig < ApplicationRecord COMBINE_PDF_RESULT_KEY = 'combine_pdf_result_key' DOCUMENT_FILENAME_FORMAT_KEY = 'document_filename_format' POLICY_LINKS_KEY = 'policy_links' + SIGNATURE_CALLBACK_URL_KEY = 'signature_callback_url' DEFAULT_VALUES = { SUBMITTER_INVITATION_EMAIL_KEY => lambda { diff --git a/app/views/personalization_settings/_signature_callback_form.html.erb b/app/views/personalization_settings/_signature_callback_form.html.erb new file mode 100644 index 00000000..8b4c21aa --- /dev/null +++ b/app/views/personalization_settings/_signature_callback_form.html.erb @@ -0,0 +1,29 @@ +<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::SIGNATURE_CALLBACK_URL_KEY) %> +<% if can?(:manage, account_config) %> +
+ When a Provider, Client/Caregiver, or Supervisor signature is collected, DocuSeal will POST
+ { template_id, form_id, email, signatures } to this URL.
+ Leave blank to disable.
+
+ Integrations +
+ <%= render 'signature_callback_form' %> diff --git a/app/views/templates/edit.html.erb b/app/views/templates/edit.html.erb index a41a0ad6..4f492797 100644 --- a/app/views/templates/edit.html.erb +++ b/app/views/templates/edit.html.erb @@ -29,6 +29,9 @@ { name: 'patient_language', type: 'text', title: 'Patient Language' }, { name: 'patient_parent_name', type: 'text', title: 'Patient Parent Name' }, { name: 'patient_first_seen_date', type: 'date', title: 'Patient First Seen Date' }, - { name: 'payor_name', type: 'text', title: 'Payor Name' } + { name: 'payor_name', type: 'text', title: 'Payor Name' }, + { name: 'provider_signature', type: 'signature', title: 'Provider Signature' }, + { name: 'client_caregiver_signature', type: 'signature', title: 'Client/Caregiver Signature' }, + { name: 'supervisor_signature', type: 'signature', title: "Supervisor's Signature" } ] %>