Files
docuseal/lib/submissions/create_from_submitters.rb
T

454 lines
17 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
module Submissions
module CreateFromSubmitters
2024-10-22 22:26:08 +03:00
BaseError = Class.new(StandardError)
module_function
2025-03-10 18:27:35 +02:00
# rubocop:disable Metrics
2025-12-19 11:55:25 +02:00
def call(template:, user:, submissions_attrs:, source:, submitters_order:, params: {}, with_template: true,
new_fields: nil)
2024-02-13 23:05:31 +02:00
preferences = Submitters.normalize_preferences(user.account, user, params)
2023-11-23 20:03:52 +02:00
2025-03-10 18:27:35 +02:00
submissions = Array.wrap(submissions_attrs).filter_map do |attrs|
2024-02-13 23:05:31 +02:00
submission_preferences = Submitters.normalize_preferences(user.account, user, attrs)
2023-12-02 23:16:38 +02:00
submission_preferences = preferences.merge(submission_preferences)
2024-01-10 02:06:31 +02:00
set_submission_preferences = submission_preferences.slice('send_email', 'bcc_completed')
2023-12-02 23:16:38 +02:00
set_submission_preferences['send_email'] = true if params['send_completed_email']
2025-05-10 13:03:52 +03:00
expire_at = attrs[:expire_at] || Templates.build_default_expire_at(template)
2023-12-02 23:16:38 +02:00
2025-09-19 21:36:05 +03:00
submission = template.submissions.new(
created_by_user: user, source:,
account_id: user.account_id,
preferences: set_submission_preferences,
name: with_template ? attrs[:name] : (attrs[:name].presence || template.name),
variables: attrs[:variables] || {},
expire_at:,
template_submitters: [], submitters_order:
)
2025-03-16 21:56:30 +02:00
template_submitters = template.submitters.deep_dup
2023-10-12 00:06:33 +03:00
attrs[:submitters].each_with_index do |submitter_attrs, index|
2025-03-16 21:56:30 +02:00
if submitter_attrs[:roles].present? && submitter_attrs[:roles].size > 1
template_submitter, template_submitters, submission.template_fields =
merge_submitters_and_fields(submitter_attrs, template_submitters,
submission.template_fields || submission.template.fields)
submission.template_schema = submission.template.schema if submission.template_schema.blank?
uuid = template_submitter['uuid']
else
if submitter_attrs[:roles].present? && submitter_attrs[:roles].size == 1
submitter_attrs[:role] = submitter_attrs[:roles].first
end
uuid = find_submitter_uuid(template_submitters, submitter_attrs, index)
2025-03-16 21:56:30 +02:00
next if uuid.blank?
next if submitter_attrs.slice('email', 'phone', 'name').compact_blank.blank?
submission.template_fields = submission.template.fields if submitter_attrs[:completed].present? &&
submission.template_fields.blank?
template_submitter = template_submitters.find { |e| e['uuid'] == uuid }
end
2026-04-14 10:51:12 +03:00
raise BaseError, 'Invalid submitter params' unless template_submitter
2026-02-20 12:27:39 +02:00
template_submitter = template_submitter.except('optional_invite_by_uuid', 'invite_by_uuid',
'invite_via_field_uuid')
2025-07-29 16:15:55 +03:00
template_submitter['order'] = submitter_attrs['order'] if submitter_attrs['order'].present?
2023-11-30 12:31:37 +02:00
2025-07-29 16:15:55 +03:00
submission.template_submitters << template_submitter
2026-01-29 12:57:26 +02:00
is_order_sent = submitters_order == 'random' ||
(template_submitter['order'] || submitter_attrs[:index] || index).zero?
2025-02-09 00:45:16 +02:00
build_submitter(submission:, attrs: submitter_attrs,
uuid:, is_order_sent:, user:, params:,
2023-11-23 20:03:52 +02:00
preferences: preferences.merge(submission_preferences))
end
2026-02-12 09:22:08 +02:00
maybe_set_dynamic_documents(submission)
2025-12-19 11:55:25 +02:00
maybe_set_template_fields(submission, attrs[:submitters], with_template:, new_fields:)
2025-03-16 21:56:30 +02:00
2024-10-22 22:26:08 +03:00
if submission.submitters.size > template.submitters.size
raise BaseError, 'Defined more signing parties than in template'
end
2025-04-10 00:56:16 +03:00
if template.preferences['validate_unique_submitters'] == true
submission_emails = submission.submitters.filter_map(&:email)
raise BaseError, 'Recipient emails should differ' if submission_emails.uniq.size != submission_emails.size
end
2024-03-30 19:37:48 +02:00
next if submission.submitters.blank?
2025-12-09 11:35:30 +02:00
maybe_add_invite_submitters(submission, template, attrs[:submitters])
2024-09-12 17:31:37 +03:00
2026-07-04 15:47:40 +03:00
assign_submitters_is_viewer(submission)
2025-05-31 15:15:25 +03:00
submission.template = nil unless with_template
submission.tap(&:save!)
end
2025-03-10 18:27:35 +02:00
maybe_enqueue_expire_at(submissions)
submissions
end
2026-07-04 15:47:40 +03:00
def assign_submitters_is_viewer(submission)
template_fields = submission.template_fields || submission.template.fields
field_submitter_uuids = Set.new(template_fields.pluck('submitter_uuid'))
viewer_template_submitters =
submission.template_submitters.select { |s| field_submitter_uuids.exclude?(s['uuid']) }
return submission if viewer_template_submitters.blank?
viewer_template_submitters.each { |s| s['is_viewer'] = true }
return submission if submission.template_submitters.any? { |s| s['order'] }
first_submitter = submission.submitters.find(&:sent_at)
return submission unless first_submitter
submitters_index = submission.submitters.reject(&:completed_at?).index_by(&:uuid)
Submissions.find_first_viewers(first_submitter, submitters_index).each do |viewer|
next if viewer.preferences['send_email'] == false || viewer.email.blank?
viewer.sent_at ||= first_submitter.sent_at
end
submission
end
2026-02-12 09:22:08 +02:00
def maybe_set_dynamic_documents(submission)
return submission unless submission.template_id?
template = submission.template
return submission if template.variables_schema.present? ||
submission.variables_schema.present?
2026-04-01 16:08:49 +03:00
return submission if template.schema.none? { |e| e['dynamic'] }
2026-02-12 09:22:08 +02:00
areas_index = {}
submission.template_schema = []
template.schema.each do |item|
if item['dynamic']
dynamic_document = template.schema_dynamic_documents.find { |e| e.uuid == item['attachment_uuid'] }
dynamic_document_version = DynamicDocuments::EnsureVersionGenerated.call(dynamic_document)
dynamic_document_version.areas.each { |area| areas_index[area['uuid']] = area }
submission.template_schema << item.deep_dup.merge('dynamic_document_sha1' => dynamic_document.sha1)
else
submission.template_schema << item.deep_dup
end
end
2026-06-30 17:47:42 +03:00
submission.template_fields = template.fields.deep_dup.filter_map do |field|
next field if field['areas'].blank?
2026-02-12 09:22:08 +02:00
2026-06-30 17:47:42 +03:00
field['areas'] = field['areas'].filter_map do |area|
2026-02-12 09:22:08 +02:00
dynamic_area = areas_index[area['uuid']]
2026-06-30 17:47:42 +03:00
next area.merge(dynamic_area) if dynamic_area
area if area.key?('page')
2026-02-12 09:22:08 +02:00
end
2026-06-30 17:47:42 +03:00
field if field['areas'].present?
2026-02-12 09:22:08 +02:00
end
submission
end
2025-03-10 18:27:35 +02:00
def maybe_enqueue_expire_at(submissions)
submissions.each do |submission|
next unless submission.expire_at?
2026-07-08 10:20:42 +03:00
ProcessSubmissionExpiredJob.perform_at(submission.expire_at, 'submission_id' => submission.id,
'expire_at' => submission.expire_at.to_i)
2025-03-10 18:27:35 +02:00
end
end
2025-12-09 11:35:30 +02:00
def maybe_add_invite_submitters(submission, template, submitter_attrs)
2025-01-09 01:09:26 +02:00
template.submitters.each_with_index do |item, index|
2025-12-09 11:35:30 +02:00
submitter_attr = submitter_attrs.find { |e| e['role'].to_s.casecmp?(item['name'].to_s) }
if submitter_attr && submitter_attr['invite_by'].present?
invite_by_uuid = template.submitters.find { |s| s['name'] == submitter_attr['invite_by'] }&.dig('uuid')
item = item.merge('invite_by_uuid' => invite_by_uuid) if invite_by_uuid
end
2026-02-20 12:27:39 +02:00
next if item['invite_by_uuid'].blank? &&
item['optional_invite_by_uuid'].blank? &&
item['invite_via_field_uuid'].blank?
2024-12-19 17:17:34 +02:00
next if submission.template_submitters.any? { |e| e['uuid'] == item['uuid'] }
2024-09-12 17:31:37 +03:00
2026-01-22 20:33:21 +02:00
item = item.merge('order' => submitter_attr['order']) if submitter_attr && submitter_attr['order'].present?
2025-01-09 01:09:26 +02:00
if index.zero?
submission.template_submitters.insert(1, item)
elsif submission.template_submitters.size > index
submission.template_submitters.insert(index, item)
else
submission.template_submitters << item
end
2024-09-12 17:31:37 +03:00
end
end
2025-02-09 00:45:16 +02:00
def submitter_message_preferences(uuid, params)
return {} if params[:request_email_per_submitter] != '1'
return {} if params[:is_custom_message] != '1'
{
'subject' => params.dig('submitter_preferences', uuid, 'subject'),
'body' => params.dig('submitter_preferences', uuid, 'body')
}.compact_blank
end
2025-12-19 11:55:25 +02:00
def maybe_set_template_fields(submission, submitters_attrs, default_submitter_uuid: nil, with_template: true,
new_fields: nil)
2023-12-17 18:39:10 +02:00
template_fields = (submission.template_fields || submission.template.fields).deep_dup
2023-10-12 00:06:33 +03:00
2025-03-16 21:56:30 +02:00
submitters = submission.template_submitters || submission.template.submitters
2023-10-12 00:06:33 +03:00
submitters_attrs.each_with_index do |submitter_attrs, index|
2025-03-16 21:56:30 +02:00
submitter_uuid = default_submitter_uuid || find_submitter_uuid(submitters, submitter_attrs, index)
2023-10-12 00:06:33 +03:00
2023-10-12 22:13:46 +03:00
process_readonly_fields_param(submitter_attrs[:readonly_fields], template_fields, submitter_uuid)
2023-12-29 18:54:48 +02:00
process_field_values_param(submitter_attrs[:values], template_fields, submitter_uuid)
2023-10-12 00:06:33 +03:00
2023-10-12 22:13:46 +03:00
process_fields_param(submitter_attrs[:fields], template_fields, submitter_uuid)
2023-10-12 00:06:33 +03:00
end
2025-12-19 11:55:25 +02:00
if template_fields != (submission.template_fields || submission.template.fields) || new_fields.present? ||
2026-02-12 09:22:08 +02:00
submitters_attrs.any? { |e| e[:completed].present? } || !with_template || submission.variables.present? ||
submission.template&.variables_schema.present?
2025-12-19 11:55:25 +02:00
submission.template_fields = new_fields ? new_fields + template_fields : template_fields
2024-12-18 16:53:35 +02:00
submission.template_schema = submission.template.schema if submission.template_schema.blank?
2025-09-24 19:51:37 +03:00
submission.variables_schema = submission.template.variables_schema if submission.template &&
2025-09-15 08:57:54 +03:00
submission.variables_schema.blank?
2023-10-12 00:06:33 +03:00
end
submission
end
2025-12-19 11:55:25 +02:00
# rubocop:enable Metrics
2023-10-12 00:06:33 +03:00
2025-03-16 21:56:30 +02:00
def merge_submitters_and_fields(submitter_attrs, template_submitters, template_fields)
selected_submitters = submitter_attrs[:roles].map do |role|
template_submitters.find { |e| e['name'].to_s.casecmp(role).zero? } ||
raise(BaseError, "#{role} role doesn't exist")
end
merge_role_uuids = selected_submitters.pluck('uuid')
old_role_uuids = template_submitters.pluck('uuid')
name = submitter_attrs[:role].presence || selected_submitters.pluck('name').join(' / ')
merged_submitter, template_submitters =
build_merged_submitter(template_submitters, role_uuids: merge_role_uuids, name:)
field_names_index = {}
sorted_fields = template_fields.sort_by { |e| old_role_uuids.index(e['submitter_uuid']) }
sorted_fields.each do |field|
next unless merge_role_uuids.include?(field['submitter_uuid'])
if (existing_field = field_names_index[field['name']])
existing_field['areas'] ||= []
existing_field['areas'].push(*field['areas'])
template_fields.delete(field)
else
field['submitter_uuid'] = merged_submitter['uuid']
field_names_index[field['name']] = field if field['name'].present?
end
end
[merged_submitter, template_submitters, template_fields]
end
def build_merged_submitter(submitters, role_uuids:, name:)
new_uuid = Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, role_uuids.sort.join(':'))
merged_submitter = nil
submitters =
submitters.filter_map do |submitter|
submitter['optional_invite_by_uuid'] = new_uuid if role_uuids.include?(submitter['optional_invite_by_uuid'])
submitter['invite_by_uuid'] = new_uuid if role_uuids.include?(submitter['invite_by_uuid'])
submitter['linked_to_uuid'] = new_uuid if role_uuids.include?(submitter['linked_to_uuid'])
if role_uuids.include?(submitter['uuid'])
next if merged_submitter
merged_submitter = submitter.deep_dup
merged_submitter['uuid'] = new_uuid
merged_submitter['name'] = name
merged_submitter.delete('linked_to_uuid')
2025-06-16 18:55:23 +03:00
next merged_submitter
2025-03-16 21:56:30 +02:00
end
submitter
end
[merged_submitter, submitters]
end
2023-10-12 22:13:46 +03:00
def process_readonly_fields_param(readonly_fields, template_fields, submitter_uuid)
return if readonly_fields.blank?
template_fields.each do |f|
next if f['submitter_uuid'] != submitter_uuid ||
(!f['name'].in?(readonly_fields) &&
2024-02-01 13:16:30 +02:00
!f['name'].to_s.downcase.in?(readonly_fields) &&
2023-10-13 14:17:28 +03:00
!f['name'].to_s.parameterize.underscore.in?(readonly_fields))
2023-10-12 22:13:46 +03:00
f['readonly'] = true
end
end
2023-12-29 18:54:48 +02:00
def process_field_values_param(values, template_fields, submitter_uuid)
return if values.blank?
template_fields.each do |f|
next if f['type'].in?(%w[signature image initials file])
next if f['submitter_uuid'] != submitter_uuid
2024-03-20 23:42:54 +02:00
next unless values.key?(f['uuid'])
2023-12-29 18:54:48 +02:00
2024-03-20 23:42:54 +02:00
value = values[f['uuid']]
2023-12-29 18:54:48 +02:00
2024-03-20 23:42:54 +02:00
if value.present?
f['default_value'] = value
else
f.delete('default_value')
end
2023-12-29 18:54:48 +02:00
end
end
2023-10-12 22:13:46 +03:00
def process_fields_param(fields, template_fields, submitter_uuid)
return if fields.blank?
template_fields.each do |f|
next if f['submitter_uuid'] != submitter_uuid
2023-10-13 14:17:28 +03:00
field_configs = fields.find do |e|
2024-04-10 13:30:27 +03:00
if e['name'].present?
e['name'].to_s.casecmp(f['name'].to_s).zero? || e['name'] == f['name'].to_s.parameterize.underscore
else
e['uuid'] == f['uuid']
end
2023-10-13 14:17:28 +03:00
end
2023-10-12 22:13:46 +03:00
next if field_configs.blank?
2024-01-13 13:15:08 +02:00
assign_field_attrs(f, field_configs)
end
end
2023-10-12 22:13:46 +03:00
2024-01-13 13:15:08 +02:00
def assign_field_attrs(field, attrs)
field['title'] = attrs['title'] if attrs['title'].present?
field['description'] = attrs['description'] if attrs['description'].present?
field['readonly'] = attrs['readonly'] if attrs.key?('readonly')
field['required'] = attrs['required'] if attrs.key?('required')
2024-02-08 20:12:08 +02:00
2024-03-20 23:42:54 +02:00
if attrs.key?('default_value') && !field['type'].in?(%w[signature image initials file])
if attrs['default_value'].present?
field['default_value'] = Submitters::NormalizeValues.normalize_value(field, attrs['default_value'])
else
field.delete('default_value')
end
2024-02-08 20:12:08 +02:00
end
2023-10-12 22:13:46 +03:00
2024-06-09 20:25:19 +03:00
field['preferences'] = (field['preferences'] || {}).merge(attrs['preferences']) if attrs['preferences'].present?
2025-08-20 18:19:23 +03:00
field['validation'] = (field['validation'] || {}).merge(attrs['validation']) if attrs['validation'].present?
2024-06-09 20:25:19 +03:00
2024-01-13 13:15:08 +02:00
return field if attrs['validation_pattern'].blank?
field['validation'] = {
'pattern' => attrs['validation_pattern'],
'message' => attrs['invalid_message']
}.compact_blank
field
2023-10-12 22:13:46 +03:00
end
2025-03-16 21:56:30 +02:00
def find_submitter_uuid(submitters, attrs, index)
2024-03-14 01:21:19 +02:00
uuid = attrs[:uuid].presence
2025-03-16 21:56:30 +02:00
uuid ||= submitters.find { |e| e['name'].to_s.casecmp(attrs[:role].to_s).zero? }&.dig('uuid')
2024-03-14 01:21:19 +02:00
2026-01-29 12:57:26 +02:00
uuid || submitters[attrs[:index] || index]&.dig('uuid')
2023-10-12 00:06:33 +03:00
end
2025-02-09 00:45:16 +02:00
def build_submitter(submission:, attrs:, uuid:, is_order_sent:, user:, preferences:, params:)
email = Submissions.normalize_email(attrs[:email])
2025-02-09 00:45:16 +02:00
submitter_preferences = Submitters.normalize_preferences(submission.account, user,
attrs.merge(submitter_message_preferences(uuid, params)))
2024-04-28 21:43:51 +03:00
values = attrs[:values] || {}
2024-08-16 10:19:00 +03:00
phone_field_uuid = find_phone_field(submission, values)&.dig('uuid')
2024-06-13 22:57:29 +03:00
submitter =
submission.submitters.new(
email:,
phone: (attrs[:phone] || values[phone_field_uuid]).to_s.gsub(/[^0-9+]/, ''),
name: attrs[:name],
2024-08-16 10:19:00 +03:00
account_id: user.account_id,
2024-06-13 22:57:29 +03:00
external_id: attrs[:external_id].presence || attrs[:application_key],
completed_at: attrs[:completed].present? ? Time.current : nil,
values: values.except(phone_field_uuid),
metadata: attrs[:metadata] || {},
preferences: preferences.merge(submitter_preferences)
.merge({ default_values: attrs[:values] }.compact_blank)
.except('bcc_completed'),
uuid:
)
2024-07-11 13:09:35 +03:00
submitter.sent_at =
submitter.preferences['send_email'] != false && email.present? && is_order_sent ? Time.current : nil
2024-06-13 22:57:29 +03:00
assign_completed_attributes(submitter) if submitter.completed_at?
submitter
end
2024-08-16 10:19:00 +03:00
def find_phone_field(submission, values)
(submission.template_fields || submission.template.fields).find do |f|
values[f['uuid']].present? && f['type'] == 'phone'
end
end
2025-08-19 12:25:23 +03:00
def assign_completed_attributes(submitter, with_verification: true)
submitter.values = Submitters::SubmitValues.merge_default_values(submitter, with_verification:)
2024-06-13 22:57:29 +03:00
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
2025-02-20 11:59:29 +02:00
formula_values = Submitters::SubmitValues.build_formula_values(submitter)
if formula_values.present?
submitter.values = submitter.values.merge(formula_values)
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
end
2026-04-23 20:25:41 +03:00
submitter.values = Submitters::SubmitValues.replace_current_date_placeholders(submitter)
2024-06-13 22:57:29 +03:00
submitter
end
end
end