Merge from docusealco/wip
This commit is contained in:
@@ -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?
|
||||
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']]
|
||||
|
||||
next unless sub
|
||||
|
||||
sub.completed_at.blank? && sub.sent_at.blank?
|
||||
end
|
||||
end
|
||||
|
||||
return unless next_submitter_item
|
||||
next_submitters = submitters_index.values_at(*Array.wrap(next_submitter_items).pluck('uuid'))
|
||||
|
||||
next_submitter = submitter.submission.submitters.find { |s| s.uuid == next_submitter_item['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)
|
||||
|
||||
+13
-5
@@ -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:,
|
||||
|
||||
+14
-5
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user