Merge from docusealco/wip

pull/493/merge 2.1.0
Alex Turchyn 3 months ago committed by GitHub
commit f627277b45
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -12,10 +12,15 @@ on:
type: string
required: false
default: tonistiigi/binfmt:latest
os:
description: OS
type: string
required: false
default: ubuntu-24.04-arm
jobs:
build:
runs-on: ubuntu-24.04-arm
runs-on: ${{ inputs.os }}
timeout-minutes: 20
steps:
@ -29,7 +34,7 @@ jobs:
uses: docker/metadata-action@v4
with:
images: docuseal/docuseal
tags: type=semver,pattern={{version}}
tags: latest,${{ inputs.version }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3

@ -10,10 +10,21 @@ module Api
def create
submitter = Submitter.find_by!(slug: params[:submitter_slug])
if params[:type].in?(%w[initials signature]) && ImageUtils.blank?(Vips::Image.new_from_file(params[:file].path))
Rollbar.error("Empty signature: #{submitter.id}") if defined?(Rollbar)
if params[:type].in?(%w[initials signature])
image = Vips::Image.new_from_file(params[:file].path)
return render json: { error: "#{params[:type]} is empty" }, status: :unprocessable_entity
if ImageUtils.blank?(image)
Rollbar.error("Empty signature: #{submitter.id}") if defined?(Rollbar)
return render json: { error: "#{params[:type]} is empty" }, status: :unprocessable_entity
end
if ImageUtils.error?(image)
Rollbar.error("Error signature: #{submitter.id}") if defined?(Rollbar)
return render json: { error: "#{params[:type]} error, try to sign on another device" },
status: :unprocessable_entity
end
end
attachment = Submitters.create_attachment!(submitter, params)

@ -185,7 +185,7 @@ module Api
message: %i[subject body],
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,
:completed, :phone, :application_key, :external_id, :reply_to, :go_to_last,
:require_phone_2fa,
:require_phone_2fa, :order,
{ metadata: {}, values: {}, roles: [], readonly_fields: [], message: %i[subject body],
fields: [:name, :uuid, :default_value, :value, :title, :description,
:readonly, :required, :validation_pattern, :invalid_message,

@ -106,7 +106,7 @@ module Api
:external_id,
:shared_link,
{
submitters: [%i[name uuid is_requester invite_by_uuid optional_invite_by_uuid linked_to_uuid email]],
submitters: [%i[name uuid is_requester invite_by_uuid optional_invite_by_uuid linked_to_uuid email order]],
fields: [[:uuid, :submitter_uuid, :name, :type,
:required, :readonly, :default_value,
:title, :description, :prefillable,

@ -117,7 +117,7 @@ class TemplatesController < ApplicationController
params.require(:template).permit(
:name,
{ schema: [[:attachment_uuid, :name, { conditions: [%i[field_uuid value action operation]] }]],
submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid optional_invite_by_uuid email]],
submitters: [%i[name uuid is_requester linked_to_uuid invite_by_uuid optional_invite_by_uuid email order]],
fields: [[:uuid, :submitter_uuid, :name, :type,
:required, :readonly, :default_value,
:title, :description, :prefillable,

@ -9,6 +9,10 @@ class TemplatesRecipientsController < ApplicationController
@template.submitters =
submitters_params.map { |s| s.reject { |_, v| v.is_a?(String) && v.blank? } }
if @template.submitters.each_with_index.all? { |s, index| s['order'] == index }
@template.submitters.each { |s| s.delete('order') }
end
@template.save!
render json: { submitters: @template.submitters }
@ -18,7 +22,7 @@ class TemplatesRecipientsController < ApplicationController
def submitters_params
permit_params = { submitters: [%i[name uuid is_requester optional_invite_by_uuid
invite_by_uuid linked_to_uuid email option]] }
invite_by_uuid linked_to_uuid email option order]] }
params.require(:template).permit(permit_params).fetch(:submitters, {}).values.filter_map do |s|
next if s[:uuid].blank?
@ -29,6 +33,7 @@ class TemplatesRecipientsController < ApplicationController
s.delete(:is_requester)
end
s[:order] = s[:order].to_i if s[:order].present?
s.delete(:invite_by_uuid) if s[:invite_by_uuid].blank?
s.delete(:optional_invite_by_uuid) if s[:optional_invite_by_uuid].blank?

@ -38,6 +38,7 @@ import DashboardDropzone from './elements/dashboard_dropzone'
import RequiredCheckboxGroup from './elements/required_checkbox_group'
import PageContainer from './elements/page_container'
import EmailEditor from './elements/email_editor'
import MountOnClick from './elements/mount_on_click'
import * as TurboInstantClick from './lib/turbo_instant_click'
@ -111,6 +112,7 @@ safeRegisterElement('check-on-click', CheckOnClick)
safeRegisterElement('required-checkbox-group', RequiredCheckboxGroup)
safeRegisterElement('page-container', PageContainer)
safeRegisterElement('email-editor', EmailEditor)
safeRegisterElement('mount-on-click', MountOnClick)
safeRegisterElement('template-builder', class extends HTMLElement {
connectedCallback () {

@ -0,0 +1,11 @@
export default class extends HTMLElement {
connectedCallback () {
this.addEventListener('click', () => {
document.body.append(this.template.content)
})
}
get template () {
return document.getElementById(this.dataset.templateId)
}
}

@ -21,7 +21,7 @@
</label>
<div class="space-x-2 flex flex-none">
<span
v-if="isTextSignature && format !== 'typed' && format !== 'upload'"
v-if="isTextSignature && format !== 'typed_or_upload' && format !== 'typed' && format !== 'upload'"
class="tooltip"
:data-tip="t('draw_signature')"
>
@ -38,7 +38,7 @@
</a>
</span>
<span
v-else-if="withTypedSignature && format !== 'typed' && format !== 'drawn' && format !== 'upload'"
v-else-if="withTypedSignature && format !== 'drawn_or_upload' && format !== 'typed_or_upload' && format !== 'typed' && format !== 'drawn' && format !== 'upload'"
class="tooltip ml-2"
:class="{ 'hidden sm:inline': modelValue || computedPreviousValue }"
:data-tip="t('type_text')"
@ -85,8 +85,8 @@
{{ t(format === 'upload' ? 'reupload' : 'redraw') }}
</a>
<span
v-if="withQrButton && !modelValue && !computedPreviousValue && format !== 'typed' && format !== 'upload'"
class=" tooltip"
v-if="withQrButton && !modelValue && !computedPreviousValue && format !== 'typed_or_upload' && format !== 'typed' && format !== 'upload'"
class="tooltip before:translate-x-[-90%]"
:data-tip="t('drawn_signature_on_a_touchscreen_device')"
>
<a
@ -395,7 +395,7 @@ export default {
isShowQr: false,
isOtherReason: false,
isUsePreviousValue: true,
isTextSignature: this.field.preferences?.format === 'typed',
isTextSignature: this.field.preferences?.format === 'typed' || this.field.preferences?.format === 'typed_or_upload',
uploadImageInputKey: Math.random().toString()
}
},

@ -304,7 +304,7 @@
{{ t('any') }}
</option>
<option
v-for="type in ['drawn', 'typed', 'drawn_or_typed', 'upload']"
v-for="type in ['drawn', 'typed', 'drawn_or_typed', 'drawn_or_upload', 'upload']"
:key="type"
:value="type"
:selected="field.preferences?.format === type"

@ -60,6 +60,7 @@ const en = {
any: 'Any',
drawn: 'Drawn',
drawn_or_typed: 'Drawn or Typed',
drawn_or_upload: 'Drawn or Upload',
upload: 'Upload',
formula: 'Formula',
typed: 'Typed',
@ -322,6 +323,7 @@ const es = {
any: 'Cualquier',
drawn: 'Dibujado',
drawn_or_typed: 'Dibujado o Escrito',
drawn_or_upload: 'Dibujado o Subido',
upload: 'Subir',
typed: 'Escrito',
none: 'Ninguno',
@ -410,6 +412,7 @@ const it = {
any: 'Qualsiasi',
drawn: 'Disegnato',
drawn_or_typed: 'Disegnato o Digitato',
drawn_or_upload: 'Disegnato o Caricato',
upload: 'Caricare',
formula: 'Formula',
typed: 'Digitato',
@ -672,6 +675,7 @@ const pt = {
any: 'Qualquer',
drawn: 'Desenhado',
drawn_or_typed: 'Desenhado ou Digitado',
drawn_or_upload: 'Desenhado ou Enviado',
upload: 'Carregar',
typed: 'Digitado',
none: 'Nenhum',
@ -847,6 +851,7 @@ const fr = {
any: 'Tout',
drawn: 'Dessiné',
drawn_or_typed: 'Dessiné ou Tapé',
drawn_or_upload: 'Dessiné ou Téléchargé',
upload: 'Téléverser',
typed: 'Tapé',
none: 'Aucun',
@ -1022,6 +1027,7 @@ const de = {
any: 'Jede',
drawn: 'Gezeichnet',
drawn_or_typed: 'Gezeichnet oder getippt',
drawn_or_upload: 'Gezeichnet oder hochgeladen',
upload: 'Upload',
typed: 'Getippt',
none: 'Keine',

@ -135,19 +135,32 @@ class ProcessSubmitterCompletionJob
end
def enqueue_next_submitter_request_notification(submitter)
next_submitter_item =
submitter.submission.template_submitters.find do |e|
sub = submitter.submission.submitters.find { |s| s.uuid == e['uuid'] }
submission = submitter.submission
submitters_index = submission.submitters.index_by(&:uuid)
next unless sub
next_submitter_items =
if submission.template_submitters.any? { |s| s['order'] }
submitter_groups =
submission.template_submitters.group_by.with_index { |s, index| s['order'] || index }
sub.completed_at.blank? && sub.sent_at.blank?
end
current_group_index = submitter_groups.find { |_, group| group.any? { |s| s['uuid'] == submitter.uuid } }&.first
if submitter_groups[current_group_index + 1] &&
submitters_index.values_at(*submitter_groups[current_group_index].pluck('uuid')).all?(&:completed_at?)
submitter_groups[current_group_index + 1]
end
else
submission.template_submitters.find do |e|
sub = submitters_index[e['uuid']]
return unless next_submitter_item
next unless sub
sub.completed_at.blank? && sub.sent_at.blank?
end
end
next_submitter = submitter.submission.submitters.find { |s| s.uuid == next_submitter_item['uuid'] }
next_submitters = submitters_index.values_at(*Array.wrap(next_submitter_items).pluck('uuid'))
Submitters.send_signature_requests([next_submitter])
Submitters.send_signature_requests(next_submitters)
end
end

@ -2,6 +2,7 @@
class TemplateMailer < ApplicationMailer
def otp_verification_email(template, email:)
@current_account = template.account
@template = template
@otp_code = EmailVerificationCodes.generate([email.downcase.strip, template.slug].join(':'))

@ -35,17 +35,19 @@
<%= ff.select :authentication, options_for_select([%w[Plain plain], %w[Login login], %w[CRAM-MD5 cram_md5]], value.fetch('authentication', 'plain')), { prompt: true }, required: true, class: 'base-select' %>
</div>
</div>
<div class="form-control">
<%= ff.label :security_label, 'SMTP Security', class: 'label' %>
<div class="flex items-center space-x-6">
<% [%w[Auto none], %w[SSL ssl], %w[TLS tls], %w[Noverify noverify]].each do |(label, val)| %>
<%= ff.label :security, value: val, for: "#{val}_radio", class: 'label' do %>
<%= ff.radio_button :security, val, checked: (value['security'].blank? && val == 'none') || value['security'] == val, id: "#{val}_radio", class: 'base-radio mr-2' %>
<%= label %>
<% if !Docuseal.multitenant? || can?(:manage, :personalization_advanced) %>
<div class="form-control">
<%= ff.label :security_label, 'SMTP Security', class: 'label' %>
<div class="flex items-center space-x-6">
<% [%w[Auto none], %w[SSL ssl], %w[TLS tls], %w[Noverify noverify]].each do |(label, val)| %>
<%= ff.label :security, value: val, for: "#{val}_radio", class: 'label' do %>
<%= ff.radio_button :security, val, checked: (value['security'].blank? && val == 'none') || value['security'] == val, id: "#{val}_radio", class: 'base-radio mr-2' %>
<%= label %>
<% end %>
<% end %>
<% end %>
</div>
</div>
</div>
<% end %>
<div class="form-control">
<%= ff.label :from_email, t('send_from_email'), class: 'label' %>
<%= ff.email_field :from_email, value: value['from_email'], required: !Docuseal.multitenant?, class: 'base-input' %>

@ -1,4 +1,4 @@
<% if template.preferences['submitters_order'] == 'preserved' %>
<% if template.preferences['submitters_order'] == 'preserved' || template.submitters.any? { |s| s['order'] } %>
<%= f.hidden_field :preserve_order, value: '1' %>
<% elsif template.submitters.size > 1 %>
<div class="form-control">

@ -1,4 +1,5 @@
<% close_on_submit = local_assigns.fetch(:close_on_submit, true) %>
<% is_order_set = template.submitters.any? { |s| s['order'] } %>
<%= form_for template, url: template_recipients_path(template), method: :post, html: { autocomplete: 'off', class: 'mt-1', id: :submitters_form }, data: { close_on_submit: } do |f| %>
<% unless close_on_submit %>
<toggle-on-submit data-element-id="form_saved_alert"></toggle-on-submit>
@ -6,11 +7,24 @@
<div class="space-y-3 divide-y">
<% template.submitters.each_with_index do |submitter, index| %>
<div class="<%= 'pt-3' if index.positive? %>">
<%= f.fields_for :submitters, item = Struct.new(:name, :uuid, :is_requester, :email, :invite_by_uuid, :optional_invite_by_uuid, :linked_to_uuid, :option).new(*submitter.values_at('name', 'uuid', 'is_requester', 'email', 'invite_by_uuid', 'optional_invite_by_uuid', 'linked_to_uuid')), index: do |ff| %>
<%= f.fields_for :submitters, item = Struct.new(:name, :uuid, :is_requester, :email, :invite_by_uuid, :optional_invite_by_uuid, :linked_to_uuid, :order, :option).new(*submitter.values_at('name', 'uuid', 'is_requester', 'email', 'invite_by_uuid', 'optional_invite_by_uuid', 'linked_to_uuid', 'order')), index: do |ff| %>
<% item.option = item.is_requester.present? ? 'is_requester' : (item.email.present? ? 'email' : (item.linked_to_uuid.present? ? "linked_to_#{item.linked_to_uuid}" : (item.invite_by_uuid.present? ? "invite_by_#{item.invite_by_uuid}" : (item.optional_invite_by_uuid.present? ? "optional_invite_by_#{item.optional_invite_by_uuid}" : '')))) %>
<%= ff.hidden_field :uuid %>
<div class="form-control">
<%= ff.text_field :name, class: 'w-full outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1 peer mb-2', autocomplete: 'off', placeholder: "#{index + 1}#{(index + 1).ordinal} Party", required: true %>
<div class="flex justify-between">
<%= ff.text_field :name, class: 'w-full outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1 peer mb-2', autocomplete: 'off', placeholder: "#{index + 1}#{(index + 1).ordinal} Party", required: true %>
<% if template.submitters.size > 2 %>
<div id="order_<%= submitter['uuid'] %>" class="mr-0.5">
<% if is_order_set %>
<%= ff.select :order, options_for_select(template.submitters.map.with_index { |_, i| [(i + 1).ordinalize, i] }, submitter['order'].presence || index), {}, class: 'select select-xs text-sm input-bordered bg-white pl-3.5' %>
<% elsif index == 0 %>
<mount-on-click data-template-id="order_fields" class="link whitespace-nowrap text-sm mt-1 mr-1 block">
<%= t('edit_order') %>
</mount-on-click>
<% end %>
</div>
<% end %>
</div>
<% if template.submitters.size == 2 %>
<%= tag.input name: ff.field_name(:email), value: ff.object.email, type: :email, class: 'base-input', multiple: true, autocomplete: 'off', placeholder: t('default_email'), disabled: ff.object.is_requester || ff.object.invite_by_uuid.present? || ff.object.optional_invite_by_uuid.present?, id: field_uuid = SecureRandom.uuid %>
<% else %>
@ -85,3 +99,16 @@
</div>
<% end %>
</div>
<% if template.submitters.size > 2 && !is_order_set %>
<template id="order_fields">
<% template.submitters.each_with_index do |submitter, index| %>
<turbo-stream action="replace" target="order_<%= submitter['uuid'] %>">
<template>
<div id="order_<%= submitter['uuid'] %>">
<%= select_tag "template[submitters][#{index}][order]", options_for_select(template.submitters.map.with_index { |_, i| [(i + 1).ordinalize, i] }, submitter['order'].presence || index), class: 'select select-xs text-sm input-bordered bg-white pl-3.5' %>
</div>
</template>
</turbo-stream>
<% end %>
</template>
<% end %>

@ -24,6 +24,7 @@ en: &en
thanks: Thanks
private: Private
select: Select
edit_order: Edit Order
invite_form_fields: Invite form fields
default_parties: Default parties
authenticate_embedded_form_preview_with_token: Authenticate embedded form preview with token
@ -898,6 +899,7 @@ en: &en
range_without_total: "%{from}-%{to} events"
es: &es
edit_order: Edita Pedido
select: Seleccionar
invite_form_fields: Invitar campos del formulario
pro: Pro
@ -1776,6 +1778,7 @@ es: &es
range_without_total: "%{from}-%{to} eventos"
it: &it
edit_order: Modifica Ordine
select: Seleziona
invite_form_fields: Invita campi modulo
pro: Pro
@ -2654,6 +2657,7 @@ it: &it
range_without_total: "%{from}-%{to} eventi"
fr: &fr
edit_order: Modifier la commande
select: Sélectionner
invite_form_fields: Inviter des champs de formulaire
pro: Pro
@ -3535,6 +3539,7 @@ fr: &fr
range_without_total: "%{from} à %{to} événements"
pt: &pt
edit_order: Edita Pedido
select: Selecionar
invite_form_fields: Convidar campos do formulário
pro: Pro
@ -4414,6 +4419,7 @@ pt: &pt
range_without_total: "%{from}-%{to} eventos"
de: &de
edit_order: Bestellung bearbeiten
select: Auswählen
invite_form_fields: Formularfelder einladen
pro: Pro

@ -1,7 +1,7 @@
# frozen_string_literal: true
class PopulateSubmitterAccountId < ActiveRecord::Migration[7.1]
disable_ddl_transaction
disable_ddl_transaction!
class MigrationSubmitter < ApplicationRecord
self.table_name = 'submitters'

@ -1,7 +1,7 @@
# frozen_string_literal: true
class PopulateCompletedSubmittersAndDocuments < ActiveRecord::Migration[7.2]
disable_ddl_transaction
disable_ddl_transaction!
class MigrationSubmitter < ApplicationRecord
self.table_name = 'submitters'

@ -1,7 +1,7 @@
# frozen_string_literal: true
class PopulateWebhookUrls < ActiveRecord::Migration[7.2]
disable_ddl_transaction
disable_ddl_transaction!
class MigrationWebhookUrl < ActiveRecord::Base
self.table_name = 'webhook_urls'

@ -1,7 +1,7 @@
# frozen_string_literal: true
class AddSharedLinkToTemplates < ActiveRecord::Migration[8.0]
disable_ddl_transaction
disable_ddl_transaction!
class MigrationTemplate < ActiveRecord::Base
self.table_name = 'templates'

@ -1,11 +1,11 @@
# frozen_string_literal: true
class CreateSearchEnties < ActiveRecord::Migration[8.0]
disable_ddl_transaction!
def up
return unless adapter_name == 'PostgreSQL'
enable_extension 'btree_gin'
create_table :search_entries do |t|
t.references :record, null: false, polymorphic: true, index: false
t.bigint :account_id, null: false
@ -14,12 +14,24 @@ class CreateSearchEnties < ActiveRecord::Migration[8.0]
t.timestamps
end
add_index :search_entries, %i[account_id tsvector], using: :gin, where: "record_type = 'Submitter'",
name: 'index_search_entries_on_account_id_tsvector_submitter'
add_index :search_entries, %i[account_id tsvector], using: :gin, where: "record_type = 'Submission'",
name: 'index_search_entries_on_account_id_tsvector_submission'
add_index :search_entries, %i[account_id tsvector], using: :gin, where: "record_type = 'Template'",
name: 'index_search_entries_on_account_id_tsvector_template'
begin
enable_extension 'btree_gin'
rescue StandardError
nil
end
btree_gin_enabled = extension_enabled?('btree_gin')
add_index :search_entries, btree_gin_enabled ? %i[account_id tsvector] : :tsvector,
using: :gin, where: "record_type = 'Submitter'",
name: 'index_search_entries_on_account_id_tsvector_submitter'
add_index :search_entries, btree_gin_enabled ? %i[account_id tsvector] : :tsvector,
using: :gin, where: "record_type = 'Submission'",
name: 'index_search_entries_on_account_id_tsvector_submission'
add_index :search_entries, btree_gin_enabled ? %i[account_id tsvector] : :tsvector,
using: :gin, where: "record_type = 'Template'",
name: 'index_search_entries_on_account_id_tsvector_template'
add_index :search_entries, %i[record_id record_type], unique: true
end
@ -28,6 +40,10 @@ class CreateSearchEnties < ActiveRecord::Migration[8.0]
drop_table :search_entries
disable_extension 'btree_gin'
begin
disable_extension 'btree_gin'
rescue StandardError
nil
end
end
end

@ -4,13 +4,18 @@ class AddNgramToSearchIndex < ActiveRecord::Migration[8.0]
def change
return unless adapter_name == 'PostgreSQL'
btree_gin_enabled = extension_enabled?('btree_gin')
add_column :search_entries, :ngram, :tsvector
add_index :search_entries, %i[account_id ngram], using: :gin, where: "record_type = 'Submitter'",
name: 'index_search_entries_on_account_id_ngram_submitter'
add_index :search_entries, %i[account_id ngram], using: :gin, where: "record_type = 'Submission'",
name: 'index_search_entries_on_account_id_ngram_submission'
add_index :search_entries, %i[account_id ngram], using: :gin, where: "record_type = 'Template'",
name: 'index_search_entries_on_account_id_ngram_template'
add_index :search_entries, btree_gin_enabled ? %i[account_id tsvector] : :tsvector,
using: :gin, where: "record_type = 'Submitter'",
name: 'index_search_entries_on_account_id_ngram_submitter'
add_index :search_entries, btree_gin_enabled ? %i[account_id tsvector] : :tsvector,
using: :gin, where: "record_type = 'Submission'",
name: 'index_search_entries_on_account_id_ngram_submission'
add_index :search_entries, btree_gin_enabled ? %i[account_id tsvector] : :tsvector,
using: :gin, where: "record_type = 'Template'",
name: 'index_search_entries_on_account_id_ngram_template'
end
end

@ -14,4 +14,12 @@ module ImageUtils
false
end
def error?(image)
image = image.crop(0, 0, image.width / 4, 2)
row1, row2 = image.to_a
row1[3..] == row2[..-4] && row1.each_cons(2).none? { |a, b| a == b }
end
end

@ -76,6 +76,7 @@ module Params
type(submitter_params, :name, String)
type(submitter_params, :reply_to, String)
type(submitter_params, :email, String)
type(submitter_params, :order, Integer)
email_format(submitter_params, :email, message: 'email is invalid')
email_format(submitter_params, :reply_to, message: 'reply_to email is invalid')
type(submitter_params, :phone, String)

@ -142,15 +142,23 @@ module Submissions
submissions.each_with_index do |submission, index|
delay_seconds = (delay + index).seconds if delay
submitters = submission.submitters.reject(&:completed_at?)
template_submitters = submission.template_submitters
submitters_index = submission.submitters.reject(&:completed_at?).index_by(&:uuid)
if submission.submitters_order_preserved?
first_submitter =
submission.template_submitters.filter_map { |s| submitters.find { |e| e.uuid == s['uuid'] } }.first
if template_submitters.any? { |s| s['order'] }
min_order = template_submitters.map.with_index { |s, i| s['order'] || i }.min
first_submitters = template_submitters.filter_map do |s|
submitters_index[s['uuid']] if s['order'] == min_order
end
Submitters.send_signature_requests(first_submitters, delay_seconds:)
elsif submission.submitters_order_preserved?
first_submitter = template_submitters.filter_map { |s| submitters_index[s['uuid']] }.first
Submitters.send_signature_requests([first_submitter], delay_seconds:) if first_submitter
else
Submitters.send_signature_requests(submitters, delay_seconds:)
Submitters.send_signature_requests(submitters_index.values, delay_seconds:)
end
end
end

@ -52,9 +52,12 @@ module Submissions
template_submitter = template_submitters.find { |e| e['uuid'] == uuid }
end
submission.template_submitters << template_submitter.except('optional_invite_by_uuid', 'invite_by_uuid')
template_submitter = template_submitter.except('optional_invite_by_uuid', 'invite_by_uuid')
template_submitter['order'] = submitter_attrs['order'] if submitter_attrs['order'].present?
is_order_sent = submitters_order == 'random' || index.zero?
submission.template_submitters << template_submitter
is_order_sent = submitters_order == 'random' || (template_submitter['order'] || index).zero?
build_submitter(submission:, attrs: submitter_attrs,
uuid:, is_order_sent:, user:, params:,

@ -162,13 +162,22 @@ module Submitters
end
def current_submitter_order?(submitter)
submitter_items = submitter.submission.template_submitters || submitter.submission.template.submitters
submission = submitter.submission
before_items = submitter_items[0...(submitter_items.find_index { |e| e['uuid'] == submitter.uuid })]
submitter_items = submission.template_submitters || submission.template.submitters
before_items.reduce(true) do |acc, item|
acc && submitter.submission.submitters.find { |e| e.uuid == item['uuid'] }&.completed_at?
end
before_items =
if submitter_items.any? { |s| s['order'] }
submitter_groups = submitter_items.group_by.with_index { |s, index| s['order'] || index }.sort_by(&:first)
current_group_index = submitter_groups.find_index { |_, group| group.any? { |s| s['uuid'] == submitter.uuid } }
submitter_groups.first(current_group_index).flat_map(&:last)
else
submitter_items.first(submitter_items.find_index { |e| e['uuid'] == submitter.uuid })
end
before_items.all? { |item| submission.submitters.find { |e| e.uuid == item['uuid'] }&.completed_at? }
end
def build_document_filename(submitter, blob, filename_format)

Loading…
Cancel
Save