feat:CST-986

pull/624/head
pramodyallapu 3 months ago
parent a6bf8a9870
commit b0786580ad

@ -23,7 +23,8 @@ class AccountConfigsController < ApplicationController
AccountConfig::WITH_SIGNATURE_ID, AccountConfig::WITH_SIGNATURE_ID,
AccountConfig::COMBINE_PDF_RESULT_KEY, AccountConfig::COMBINE_PDF_RESULT_KEY,
AccountConfig::REQUIRE_SIGNING_REASON_KEY, AccountConfig::REQUIRE_SIGNING_REASON_KEY,
AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY AccountConfig::DOCUMENT_FILENAME_FORMAT_KEY,
AccountConfig::SIGNATURE_CALLBACK_URL_KEY
].freeze ].freeze
InvalidKey = Class.new(StandardError) InvalidKey = Class.new(StandardError)

@ -29,6 +29,8 @@ class ProcessSubmitterCompletionJob
end end
enqueue_completed_webhooks(submitter, is_all_completed:) enqueue_completed_webhooks(submitter, is_all_completed:)
SendSignatureCallbackJob.perform_async('submitter_id' => submitter.id)
end end
def create_completed_submitter!(submitter) def create_completed_submitter!(submitter)

@ -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

@ -54,6 +54,7 @@ class AccountConfig < ApplicationRecord
COMBINE_PDF_RESULT_KEY = 'combine_pdf_result_key' COMBINE_PDF_RESULT_KEY = 'combine_pdf_result_key'
DOCUMENT_FILENAME_FORMAT_KEY = 'document_filename_format' DOCUMENT_FILENAME_FORMAT_KEY = 'document_filename_format'
POLICY_LINKS_KEY = 'policy_links' POLICY_LINKS_KEY = 'policy_links'
SIGNATURE_CALLBACK_URL_KEY = 'signature_callback_url'
DEFAULT_VALUES = { DEFAULT_VALUES = {
SUBMITTER_INVITATION_EMAIL_KEY => lambda { SUBMITTER_INVITATION_EMAIL_KEY => lambda {

@ -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) %>
<div class="collapse collapse-plus bg-base-200 mt-4">
<input type="checkbox" <%= 'checked' if account_config.value.present? %>>
<div class="collapse-title text-xl font-medium">
Signature Callback URL
</div>
<div class="collapse-content">
<p class="text-sm text-base-content/60 mb-3">
When a Provider, Client/Caregiver, or Supervisor signature is collected, DocuSeal will POST
<code class="font-mono">{ template_id, form_id, email, signatures }</code> to this URL.
Leave blank to disable.
</p>
<%= form_for account_config, url: account_configs_path, method: :post,
html: { autocomplete: 'off', class: 'space-y-3' } do |f| %>
<%= f.hidden_field :key %>
<div class="form-control">
<%= f.url_field :value,
class: 'base-input w-full font-mono',
placeholder: 'https://your-app.example.com/api/signature-callback',
value: account_config.value.presence %>
</div>
<div class="form-control pt-1">
<%= f.button button_title(title: 'Save', disabled_with: 'Saving'), class: 'base-button' %>
</div>
<% end %>
</div>
</div>
<% end %>

@ -22,6 +22,10 @@
<%= render 'form_policy_links_form' %> <%= render 'form_policy_links_form' %>
</div> </div>
<%= render 'form_customization_settings' %> <%= render 'form_customization_settings' %>
<p class="text-4xl font-bold mb-4 mt-8">
Integrations
</p>
<%= render 'signature_callback_form' %>
</div> </div>
<div class="w-0 md:w-52"></div> <div class="w-0 md:w-52"></div>
</div> </div>

@ -29,6 +29,9 @@
{ name: 'patient_language', type: 'text', title: 'Patient Language' }, { name: 'patient_language', type: 'text', title: 'Patient Language' },
{ name: 'patient_parent_name', type: 'text', title: 'Patient Parent Name' }, { name: 'patient_parent_name', type: 'text', title: 'Patient Parent Name' },
{ name: 'patient_first_seen_date', type: 'date', title: 'Patient First Seen Date' }, { 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" }
] %> ] %>
<template-builder class="grid" data-template="<%= @template_data %>" data-with-sign-yourself-button="<%= !@template.archived_at? %>" data-with-fields-detection="true" data-with-send-button="<%= !@template.archived_at? && can?(:create, @template.submissions.new(account: current_account)) %>" data-locale="<%= I18n.locale %>" data-show-tour-start-form="<%= @show_tour_start_form %>" data-predefined-fields="<%= predefined_fields.to_json %>"></template-builder> <template-builder class="grid" data-template="<%= @template_data %>" data-with-sign-yourself-button="<%= !@template.archived_at? %>" data-with-fields-detection="true" data-with-send-button="<%= !@template.archived_at? && can?(:create, @template.submissions.new(account: current_account)) %>" data-locale="<%= I18n.locale %>" data-show-tour-start-form="<%= @show_tour_start_form %>" data-predefined-fields="<%= predefined_fields.to_json %>"></template-builder>
Loading…
Cancel
Save