preserve submitter preferences

pull/150/merge
Pete Matsyburka 2 years ago
parent 514987138a
commit 01333e5b2e

@ -50,35 +50,14 @@ module Api
end end
def create def create
is_send_email = !params[:send_email].in?(['false', false]) params[:send_email] = true unless params.key?(:send_email)
params[:send_sms] = false unless params.key?(:send_sms)
submissions =
if (emails = (params[:emails] || params[:email]).presence) && params[:submission].blank?
Submissions.create_from_emails(template: @template,
user: current_user,
source: :api,
mark_as_sent: is_send_email,
emails:)
else
submissions_attrs, attachments = normalize_submissions_params!(submissions_params[:submission], @template)
Submissions.create_from_submitters(
template: @template,
user: current_user,
source: :api,
mark_as_sent: is_send_email,
submitters_order: params[:submitters_order] || params[:order] || 'preserved',
submissions_attrs:
)
end
Submissions.send_signature_requests(submissions, send_email: is_send_email) submissions = create_submissions(@template, params)
submitters = submissions.flat_map(&:submitters) Submissions.send_signature_requests(submissions)
save_default_value_attachments!(attachments, submitters) render json: submissions.flat_map(&:submitters)
render json: submitters
rescue Submitters::NormalizeValues::UnknownFieldName, Submitters::NormalizeValues::UnknownSubmitterName => e rescue Submitters::NormalizeValues::UnknownFieldName, Submitters::NormalizeValues::UnknownSubmitterName => e
render json: { error: e.message }, status: :unprocessable_entity render json: { error: e.message }, status: :unprocessable_entity
end end
@ -91,6 +70,35 @@ module Api
private private
def create_submissions(template, params)
is_send_email = !params[:send_email].in?(['false', false])
if (emails = (params[:emails] || params[:email]).presence) && params[:submission].blank?
Submissions.create_from_emails(template:,
user: current_user,
source: :api,
mark_as_sent: is_send_email,
emails:,
params:)
else
submissions_attrs, attachments = normalize_submissions_params!(submissions_params, template)
submissions = Submissions.create_from_submitters(
template:,
user: current_user,
source: :api,
mark_as_sent: is_send_email,
submitters_order: params[:submitters_order] || params[:order] || 'preserved',
submissions_attrs:,
params:
)
save_default_value_attachments!(attachments, submissions.flat_map(&:submitters))
submissions
end
end
def serialize_params def serialize_params
{ {
only: %i[id source submitters_order created_at updated_at], only: %i[id source submitters_order created_at updated_at],
@ -107,11 +115,19 @@ module Api
end end
def submissions_params def submissions_params
params.permit(submission: [{ key = params.key?(:submission) ? :submission : :submissions
submitters: [[:uuid, :name, :email, :role, :completed, :phone, :application_key,
{ values: {}, readonly_fields: [], params.permit(
fields: [%i[name default_value readonly validation_pattern invalid_message]] }]] key => [
}]) [:send_email, :send_sms, {
message: %i[subject body],
submitters: [[:send_email, :send_sms, :uuid, :name, :email, :role,
:completed, :phone, :application_key,
{ values: {}, readonly_fields: [], message: %i[subject body],
fields: [%i[name default_value readonly validation_pattern invalid_message]] }]]
}]
]
).fetch(key, [])
end end
def normalize_submissions_params!(submissions_params, template) def normalize_submissions_params!(submissions_params, template)

@ -23,7 +23,8 @@ class StartFormController < ApplicationController
@submitter.assign_attributes( @submitter.assign_attributes(
uuid: @template.submitters.first['uuid'], uuid: @template.submitters.first['uuid'],
ip: request.remote_ip, ip: request.remote_ip,
ua: request.user_agent ua: request.user_agent,
preferences: { 'send_email' => true }
) )
@submitter.submission ||= Submission.new(template: @template, @submitter.submission ||= Submission.new(template: @template,

@ -34,23 +34,30 @@ class SubmissionsController < ApplicationController
def create def create
authorize!(:create, Submission) authorize!(:create, Submission)
if params[:is_custom_message] != '1'
params.delete(:subject)
params.delete(:body)
end
submissions = submissions =
if params[:emails].present? if params[:emails].present?
Submissions.create_from_emails(template: @template, Submissions.create_from_emails(template: @template,
user: current_user, user: current_user,
source: :invite, source: :invite,
mark_as_sent: params[:send_email] == '1', mark_as_sent: params[:send_email] == '1',
emails: params[:emails]) emails: params[:emails],
params:)
else else
Submissions.create_from_submitters(template: @template, Submissions.create_from_submitters(template: @template,
user: current_user, user: current_user,
source: :invite, source: :invite,
submitters_order: params[:preserve_order] == '1' ? 'preserved' : 'random', submitters_order: params[:preserve_order] == '1' ? 'preserved' : 'random',
mark_as_sent: params[:send_email] == '1', mark_as_sent: params[:send_email] == '1',
submissions_attrs: submissions_params[:submission].to_h.values) submissions_attrs: submissions_params[:submission].to_h.values,
params:)
end end
Submissions.send_signature_requests(submissions, params) Submissions.send_signature_requests(submissions)
redirect_to template_path(@template), notice: 'New recipients have been added' redirect_to template_path(@template), notice: 'New recipients have been added'
end end

@ -31,7 +31,8 @@ class ProcessSubmitterCompletionJob < ApplicationJob
SubmitterMailer.completed_email(submitter, user, bcc:).deliver_later! SubmitterMailer.completed_email(submitter, user, bcc:).deliver_later!
end end
to = submitter.submission.submitters.sort_by(&:completed_at).select(&:email?).map(&:friendly_name).join(', ') to = submitter.submission.submitters.reject { |e| e.preferences['send_email'] == false }
.sort_by(&:completed_at).select(&:email?).map(&:friendly_name).join(', ')
SubmitterMailer.documents_copy_email(submitter, to:).deliver_later! if to.present? SubmitterMailer.documents_copy_email(submitter, to:).deliver_later! if to.present?
end end
@ -48,6 +49,6 @@ class ProcessSubmitterCompletionJob < ApplicationJob
next_submitter = submitter.submission.submitters.find { |s| s.uuid == next_submitter_item['uuid'] } next_submitter = submitter.submission.submitters.find { |s| s.uuid == next_submitter_item['uuid'] }
Submitters.send_signature_requests([next_submitter], send_email: true) Submitters.send_signature_requests([next_submitter])
end end
end end

@ -4,7 +4,7 @@ class SendSubmitterInvitationEmailJob < ApplicationJob
def perform(params = {}) def perform(params = {})
submitter = Submitter.find(params['submitter_id']) submitter = Submitter.find(params['submitter_id'])
SubmitterMailer.invitation_email(submitter, subject: params['subject'], body: params['body']).deliver_now! SubmitterMailer.invitation_email(submitter).deliver_now!
SubmissionEvent.create!(submitter:, event_type: 'send_email') SubmissionEvent.create!(submitter:, event_type: 'send_email')

@ -3,18 +3,26 @@
class SubmitterMailer < ApplicationMailer class SubmitterMailer < ApplicationMailer
MAX_ATTACHMENTS_SIZE = 10.megabytes MAX_ATTACHMENTS_SIZE = 10.megabytes
def invitation_email(submitter, body: nil, subject: nil) DEFAULT_INVITATION_SUBJECT = 'You are invited to submit a form'
def invitation_email(submitter)
@current_account = submitter.submission.template.account @current_account = submitter.submission.template.account
@submitter = submitter @submitter = submitter
@body = body.presence
if submitter.preferences['email_message_uuid']
@email_message = submitter.account.email_messages.find_by(uuid: submitter.preferences['email_message_uuid'])
end
@body = @email_message&.body.presence
@subject = @email_message&.subject.presence
@email_config = AccountConfigs.find_for_account(@current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY) @email_config = AccountConfigs.find_for_account(@current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY)
subject = subject =
if @email_config || subject.present? if @email_config || @subject
ReplaceEmailVariables.call(subject.presence || @email_config.value['subject'], submitter:) ReplaceEmailVariables.call(@subject || @email_config.value['subject'], submitter:)
else else
'You are invited to submit a form' DEFAULT_INVITATION_SUBJECT
end end
mail(to: @submitter.friendly_name, mail(to: @submitter.friendly_name,

@ -15,6 +15,7 @@ class Account < ApplicationRecord
has_many :users, dependent: :destroy has_many :users, dependent: :destroy
has_many :encrypted_configs, dependent: :destroy has_many :encrypted_configs, dependent: :destroy
has_many :account_configs, dependent: :destroy has_many :account_configs, dependent: :destroy
has_many :email_messages, dependent: :destroy
has_many :templates, dependent: :destroy has_many :templates, dependent: :destroy
has_many :template_folders, dependent: :destroy has_many :template_folders, dependent: :destroy
has_one :default_template_folder, -> { where(name: TemplateFolder::DEFAULT_NAME) }, has_one :default_template_folder, -> { where(name: TemplateFolder::DEFAULT_NAME) },

@ -0,0 +1,39 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: email_messages
#
# id :bigint not null, primary key
# body :text not null
# sha1 :string not null
# subject :text not null
# uuid :string not null
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
# author_id :bigint not null
#
# Indexes
#
# index_email_messages_on_account_id (account_id)
# index_email_messages_on_sha1 (sha1)
# index_email_messages_on_uuid (uuid)
#
# Foreign Keys
#
# fk_rails_... (account_id => accounts.id)
# fk_rails_... (author_id => users.id)
#
class EmailMessage < ApplicationRecord
belongs_to :author, class_name: 'User'
belongs_to :account
attribute :uuid, :string, default: -> { SecureRandom.uuid }
before_validation :set_sha1, on: :create
def set_sha1
self.sha1 = Digest::SHA1.hexdigest({ subject:, body: }.to_json)
end
end

@ -6,6 +6,7 @@
# #
# id :bigint not null, primary key # id :bigint not null, primary key
# deleted_at :datetime # deleted_at :datetime
# preferences :text not null
# slug :string not null # slug :string not null
# source :text not null # source :text not null
# submitters_order :string not null # submitters_order :string not null
@ -36,9 +37,12 @@ class Submission < ApplicationRecord
has_many :submitters, dependent: :destroy has_many :submitters, dependent: :destroy
has_many :submission_events, dependent: :destroy has_many :submission_events, dependent: :destroy
attribute :preferences, :string, default: -> { {} }
serialize :template_fields, JSON serialize :template_fields, JSON
serialize :template_schema, JSON serialize :template_schema, JSON
serialize :template_submitters, JSON serialize :template_submitters, JSON
serialize :preferences, JSON
attribute :source, :string, default: 'link' attribute :source, :string, default: 'link'
attribute :submitters_order, :string, default: 'random' attribute :submitters_order, :string, default: 'random'

@ -12,6 +12,7 @@
# name :string # name :string
# opened_at :datetime # opened_at :datetime
# phone :string # phone :string
# preferences :text not null
# sent_at :datetime # sent_at :datetime
# slug :string not null # slug :string not null
# ua :string # ua :string
@ -37,9 +38,11 @@ class Submitter < ApplicationRecord
has_one :account, through: :template has_one :account, through: :template
attribute :values, :string, default: -> { {} } attribute :values, :string, default: -> { {} }
attribute :preferences, :string, default: -> { {} }
attribute :slug, :string, default: -> { SecureRandom.base58(14) } attribute :slug, :string, default: -> { SecureRandom.base58(14) }
serialize :values, JSON serialize :values, JSON
serialize :preferences, JSON
has_many_attached :documents has_many_attached :documents
has_many_attached :attachments has_many_attached :attachments

@ -55,6 +55,7 @@ class User < ApplicationRecord
has_many :template_folders, dependent: :destroy, foreign_key: :author_id, inverse_of: :author has_many :template_folders, dependent: :destroy, foreign_key: :author_id, inverse_of: :author
has_many :user_configs, dependent: :destroy has_many :user_configs, dependent: :destroy
has_many :encrypted_configs, dependent: :destroy, class_name: 'EncryptedUserConfig' has_many :encrypted_configs, dependent: :destroy, class_name: 'EncryptedUserConfig'
has_many :email_messages, dependent: :destroy
devise :two_factor_authenticatable, :recoverable, :rememberable, :validatable, :trackable devise :two_factor_authenticatable, :recoverable, :rememberable, :validatable, :trackable
devise :registerable, :omniauthable, omniauth_providers: [:google_oauth2] if Docuseal.multitenant? devise :registerable, :omniauthable, omniauth_providers: [:google_oauth2] if Docuseal.multitenant?

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddPreferencesToSubmitters < ActiveRecord::Migration[7.0]
class MigrationSubmitter < ApplicationRecord
self.table_name = 'submitters'
end
def change
add_column :submitters, :preferences, :text
MigrationSubmitter.where(preferences: nil).update_all(preferences: '{}')
change_column_null :submitters, :preferences, false
end
end

@ -0,0 +1,15 @@
# frozen_string_literal: true
class AddPreferencesToSubmissions < ActiveRecord::Migration[7.0]
class MigrationSubmission < ApplicationRecord
self.table_name = 'submissions'
end
def change
add_column :submissions, :preferences, :text
MigrationSubmission.where(preferences: nil).update_all(preferences: '{}')
change_column_null :submissions, :preferences, false
end
end

@ -0,0 +1,16 @@
# frozen_string_literal: true
class CreateEmailMessages < ActiveRecord::Migration[7.0]
def change
create_table :email_messages do |t|
t.string :uuid, null: false, index: true
t.references :author, null: false, foreign_key: { to_table: :users }, index: false
t.references :account, null: false, foreign_key: true, index: true
t.text :subject, null: false
t.text :body, null: false
t.string :sha1, null: false, index: true
t.timestamps
end
end
end

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_11_19_222105) do ActiveRecord::Schema[7.0].define(version: 2023_11_22_212612) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -81,6 +81,20 @@ ActiveRecord::Schema[7.0].define(version: 2023_11_19_222105) do
t.index ["submitter_id"], name: "index_document_generation_events_on_submitter_id" t.index ["submitter_id"], name: "index_document_generation_events_on_submitter_id"
end end
create_table "email_messages", force: :cascade do |t|
t.string "uuid", null: false
t.bigint "author_id", null: false
t.bigint "account_id", null: false
t.text "subject", null: false
t.text "body", null: false
t.string "sha1", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_email_messages_on_account_id"
t.index ["sha1"], name: "index_email_messages_on_sha1"
t.index ["uuid"], name: "index_email_messages_on_uuid"
end
create_table "encrypted_configs", force: :cascade do |t| create_table "encrypted_configs", force: :cascade do |t|
t.bigint "account_id", null: false t.bigint "account_id", null: false
t.string "key", null: false t.string "key", null: false
@ -125,6 +139,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_11_19_222105) do
t.text "source", null: false t.text "source", null: false
t.string "submitters_order", null: false t.string "submitters_order", null: false
t.string "slug", null: false t.string "slug", null: false
t.text "preferences", null: false
t.index ["created_by_user_id"], name: "index_submissions_on_created_by_user_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 t.index ["slug"], name: "index_submissions_on_slug", unique: true
t.index ["template_id"], name: "index_submissions_on_template_id" t.index ["template_id"], name: "index_submissions_on_template_id"
@ -146,6 +161,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_11_19_222105) do
t.string "name" t.string "name"
t.string "phone" t.string "phone"
t.string "application_key" t.string "application_key"
t.text "preferences", null: false
t.index ["email"], name: "index_submitters_on_email" t.index ["email"], name: "index_submitters_on_email"
t.index ["slug"], name: "index_submitters_on_slug", unique: true t.index ["slug"], name: "index_submitters_on_slug", unique: true
t.index ["submission_id"], name: "index_submitters_on_submission_id" t.index ["submission_id"], name: "index_submitters_on_submission_id"
@ -229,6 +245,8 @@ ActiveRecord::Schema[7.0].define(version: 2023_11_19_222105) do
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "document_generation_events", "submitters" add_foreign_key "document_generation_events", "submitters"
add_foreign_key "email_messages", "accounts"
add_foreign_key "email_messages", "users", column: "author_id"
add_foreign_key "encrypted_configs", "accounts" add_foreign_key "encrypted_configs", "accounts"
add_foreign_key "encrypted_user_configs", "users" add_foreign_key "encrypted_user_configs", "users"
add_foreign_key "submission_events", "submissions" add_foreign_key "submission_events", "submissions"

@ -0,0 +1,17 @@
# frozen_string_literal: true
module EmailMessages
module_function
def find_or_create_for_account_user(account, user, subject, body)
subject = SubmitterMailer::DEFAULT_INVITATION_SUBJECT if subject.blank?
sha1 = Digest::SHA1.hexdigest({ subject:, body: }.to_json)
message = account.email_messages.find_by(sha1:)
message ||= account.email_messages.create!(author: user, subject:, body:)
message
end
end

@ -27,11 +27,17 @@ module Submissions
submission.save! submission.save!
end end
def create_from_emails(template:, user:, emails:, source:, mark_as_sent: false) def create_from_emails(template:, user:, emails:, source:, mark_as_sent: false, params: {})
preferences = Submitters.normalize_preferences(template.account, user, params)
parse_emails(emails).uniq.map do |email| parse_emails(emails).uniq.map do |email|
submission = template.submissions.new(created_by_user: user, source:, template_submitters: template.submitters) submission = template.submissions.new(created_by_user: user,
source:,
template_submitters: template.submitters)
submission.submitters.new(email: normalize_email(email), submission.submitters.new(email: normalize_email(email),
uuid: template.submitters.first['uuid'], uuid: template.submitters.first['uuid'],
preferences:,
sent_at: mark_as_sent ? Time.current : nil) sent_at: mark_as_sent ? Time.current : nil)
submission.tap(&:save!) submission.tap(&:save!)
@ -45,13 +51,13 @@ module Submissions
end end
def create_from_submitters(template:, user:, submissions_attrs:, source:, mark_as_sent: false, def create_from_submitters(template:, user:, submissions_attrs:, source:, mark_as_sent: false,
submitters_order: DEFAULT_SUBMITTERS_ORDER) submitters_order: DEFAULT_SUBMITTERS_ORDER, params: {})
Submissions::CreateFromSubmitters.call( Submissions::CreateFromSubmitters.call(
template:, user:, submissions_attrs:, source:, mark_as_sent:, submitters_order: template:, user:, submissions_attrs:, source:, mark_as_sent:, submitters_order:, params:
) )
end end
def send_signature_requests(submissions, params) def send_signature_requests(submissions)
submissions.each do |submission| submissions.each do |submission|
submitters = submission.submitters.reject(&:completed_at?) submitters = submission.submitters.reject(&:completed_at?)
@ -59,9 +65,9 @@ module Submissions
first_submitter = first_submitter =
submission.template_submitters.filter_map { |s| submitters.find { |e| e.uuid == s['uuid'] } }.first submission.template_submitters.filter_map { |s| submitters.find { |e| e.uuid == s['uuid'] } }.first
Submitters.send_signature_requests([first_submitter], params) Submitters.send_signature_requests([first_submitter])
else else
Submitters.send_signature_requests(submitters, params) Submitters.send_signature_requests(submitters)
end end
end end
end end

@ -4,7 +4,9 @@ module Submissions
module CreateFromSubmitters module CreateFromSubmitters
module_function module_function
def call(template:, user:, submissions_attrs:, source:, submitters_order:, mark_as_sent: false) def call(template:, user:, submissions_attrs:, source:, submitters_order:, mark_as_sent: false, params: {})
preferences = Submitters.normalize_preferences(template.account, user, params)
Array.wrap(submissions_attrs).map do |attrs| Array.wrap(submissions_attrs).map do |attrs|
submission = template.submissions.new(created_by_user: user, source:, submission = template.submissions.new(created_by_user: user, source:,
template_submitters: template.submitters, submitters_order:) template_submitters: template.submitters, submitters_order:)
@ -18,7 +20,11 @@ module Submissions
is_order_sent = submitters_order == 'random' || index.zero? is_order_sent = submitters_order == 'random' || index.zero?
build_submitter(submission:, attrs: submitter_attrs, uuid:, is_order_sent:, mark_as_sent:) submission_preferences = Submitters.normalize_preferences(template.account, user, attrs)
build_submitter(submission:, attrs: submitter_attrs, uuid:,
is_order_sent:, mark_as_sent:, user:,
preferences: preferences.merge(submission_preferences))
end end
submission.tap(&:save!) submission.tap(&:save!)
@ -85,8 +91,9 @@ module Submissions
template.submitters[index]&.dig('uuid') template.submitters[index]&.dig('uuid')
end end
def build_submitter(submission:, attrs:, uuid:, is_order_sent:, mark_as_sent:) def build_submitter(submission:, attrs:, uuid:, is_order_sent:, mark_as_sent:, user:, preferences:)
email = Submissions.normalize_email(attrs[:email]) email = Submissions.normalize_email(attrs[:email])
submitter_preferences = Submitters.normalize_preferences(submission.account, user, attrs)
submission.submitters.new( submission.submitters.new(
email:, email:,
@ -96,6 +103,7 @@ module Submissions
completed_at: attrs[:completed] ? Time.current : nil, completed_at: attrs[:completed] ? Time.current : nil,
sent_at: mark_as_sent && email.present? && is_order_sent ? Time.current : nil, sent_at: mark_as_sent && email.present? && is_order_sent ? Time.current : nil,
values: attrs[:values] || {}, values: attrs[:values] || {},
preferences: preferences.merge(submitter_preferences),
uuid: uuid:
) )
end end

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
module Submitters module Submitters
TRUE_VALUES = ['1', 'true', true].freeze
module_function module_function
def search(submitters, keyword) def search(submitters, keyword)
@ -43,21 +45,30 @@ module Submitters
) )
end end
def send_signature_requests(submitters, params) def normalize_preferences(account, user, params)
return if params[:send_email] != true && params[:send_email] != '1' preferences = {}
submitters.each do |submitter| message_params = params['message'].presence || params.slice('subject', 'body').presence
next if submitter.email.blank?
enqueue_invitation_email(submitter, params) if message_params.present?
email_message = EmailMessages.find_or_create_for_account_user(account, user,
message_params['subject'],
message_params['body'])
end end
preferences['email_message_uuid'] = email_message.uuid if email_message
preferences['send_email'] = params['send_email'].in?(TRUE_VALUES) if params.key?('send_email')
preferences['send_sms'] = params['send_sms'].in?(TRUE_VALUES) if params.key?('send_sms')
preferences
end end
def enqueue_invitation_email(submitter, params) def send_signature_requests(submitters)
subject, body = params.values_at(:subject, :body) if params[:is_custom_message] == '1' submitters.each do |submitter|
next if submitter.email.blank?
next if submitter.preferences['send_email'] == false
SendSubmitterInvitationEmailJob.perform_later('submitter_id' => submitter.id, SendSubmitterInvitationEmailJob.perform_later('submitter_id' => submitter.id)
'body' => body, end
'subject' => subject)
end end
end end

Loading…
Cancel
Save