Merge from docusealco/wip
This commit is contained in:
@@ -15,6 +15,7 @@ class AccountConfigsController < ApplicationController
|
||||
AccountConfig::DOWNLOAD_LINKS_AUTH_KEY,
|
||||
AccountConfig::FORCE_SSO_AUTH_KEY,
|
||||
AccountConfig::FLATTEN_RESULT_PDF_KEY,
|
||||
AccountConfig::ENFORCE_SIGNING_ORDER_KEY,
|
||||
AccountConfig::WITH_SIGNATURE_ID,
|
||||
AccountConfig::COMBINE_PDF_RESULT_KEY,
|
||||
AccountConfig::REQUIRE_SIGNING_REASON_KEY,
|
||||
|
||||
@@ -23,9 +23,9 @@ class SubmissionsDownloadController < ApplicationController
|
||||
|
||||
last_submitter = submitter.submission.submitters.where.not(completed_at: nil).order(:completed_at).last
|
||||
|
||||
Submissions::EnsureResultGenerated.call(last_submitter)
|
||||
return head :not_found unless last_submitter
|
||||
|
||||
return head :not_found unless last_submitter.completed_at?
|
||||
Submissions::EnsureResultGenerated.call(last_submitter)
|
||||
|
||||
if last_submitter.completed_at < TTL.ago && !signature_valid && !current_user_submitter?(last_submitter)
|
||||
Rollbar.info("TTL: #{last_submitter.id}") if defined?(Rollbar)
|
||||
|
||||
@@ -20,7 +20,11 @@ class SubmitFormController < ApplicationController
|
||||
@submitter.account.archived_at?
|
||||
return render :expired if submission.expired?
|
||||
return render :declined if @submitter.declined_at?
|
||||
return render :awaiting if submission.template.preferences['submitters_order'] == 'preserved' &&
|
||||
|
||||
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
|
||||
|
||||
return render :awaiting if (@form_configs[:enforce_signing_order] ||
|
||||
submission.template.preferences['submitters_order'] == 'preserved') &&
|
||||
!Submitters.current_submitter_order?(@submitter)
|
||||
|
||||
Submitters.preload_with_pages(@submitter)
|
||||
@@ -29,8 +33,6 @@ class SubmitFormController < ApplicationController
|
||||
|
||||
@attachments_index = build_attachments_index(submission)
|
||||
|
||||
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
|
||||
|
||||
return unless @form_configs[:prefill_signature]
|
||||
|
||||
if (user_signature = UserConfigs.load_signature(current_user))
|
||||
@@ -70,6 +72,10 @@ class SubmitFormController < ApplicationController
|
||||
Submitters::SubmitValues.call(submitter, params, request)
|
||||
|
||||
head :ok
|
||||
rescue Submitters::SubmitValues::RequiredFieldError => e
|
||||
Rollbar.warning("Required field #{submitter.id}: #{e.message}") if defined?(Rollbar)
|
||||
|
||||
render json: { field_uuid: e.message }, status: :unprocessable_entity
|
||||
rescue Submitters::SubmitValues::ValidationError => e
|
||||
render json: { error: e.message }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
@@ -35,8 +35,6 @@ import IndeterminateCheckbox from './elements/indeterminate_checkbox'
|
||||
|
||||
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||
|
||||
import './images/preview.png'
|
||||
|
||||
TurboInstantClick.start()
|
||||
|
||||
document.addEventListener('turbo:before-cache', () => {
|
||||
|
||||
+20
-17
@@ -1,5 +1,6 @@
|
||||
import SignaturePad from 'signature_pad'
|
||||
import { cropCanvasAndExportToPNG } from './submission_form/crop_canvas'
|
||||
import { isValidSignatureCanvas } from './submission_form/validate_signature'
|
||||
|
||||
window.customElements.define('draw-signature', class extends HTMLElement {
|
||||
connectedCallback () {
|
||||
@@ -43,6 +44,8 @@ window.customElements.define('draw-signature', class extends HTMLElement {
|
||||
|
||||
return response
|
||||
})
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
}).finally(() => {
|
||||
this.submitButton.disabled = false
|
||||
})
|
||||
@@ -65,26 +68,26 @@ window.customElements.define('draw-signature', class extends HTMLElement {
|
||||
}
|
||||
|
||||
async submitImage () {
|
||||
return new Promise((resolve, reject) => {
|
||||
cropCanvasAndExportToPNG(this.canvas, { errorOnTooSmall: true }).then(async (blob) => {
|
||||
const file = new File([blob], 'signature.png', { type: 'image/png' })
|
||||
if (!isValidSignatureCanvas(this.pad.toData())) {
|
||||
alert('Signature is too small or simple. Please redraw.')
|
||||
|
||||
const formData = new FormData()
|
||||
return Promise.reject(new Error('Image too small or simple'))
|
||||
}
|
||||
|
||||
formData.append('file', file)
|
||||
formData.append('submitter_slug', this.dataset.slug)
|
||||
formData.append('name', 'attachments')
|
||||
formData.append('remember_signature', 'true')
|
||||
return cropCanvasAndExportToPNG(this.canvas).then(async (blob) => {
|
||||
const file = new File([blob], 'signature.png', { type: 'image/png' })
|
||||
|
||||
return fetch('/api/attachments', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then((resp) => resp.json()).then((attachment) => {
|
||||
return resolve(attachment)
|
||||
})
|
||||
}).catch((error) => {
|
||||
return reject(error)
|
||||
})
|
||||
const formData = new FormData()
|
||||
|
||||
formData.append('file', file)
|
||||
formData.append('submitter_slug', this.dataset.slug)
|
||||
formData.append('name', 'attachments')
|
||||
formData.append('remember_signature', 'true')
|
||||
|
||||
return fetch('/api/attachments', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(resp => resp.json())
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 41 KiB |
@@ -1,4 +1,4 @@
|
||||
function cropCanvasAndExportToPNG (canvas, { errorOnTooSmall } = { errorOnTooSmall: false }) {
|
||||
function cropCanvasAndExportToPNG (canvas) {
|
||||
const ctx = canvas.getContext('2d')
|
||||
|
||||
const width = canvas.width
|
||||
@@ -33,10 +33,6 @@ function cropCanvasAndExportToPNG (canvas, { errorOnTooSmall } = { errorOnTooSma
|
||||
croppedCanvas.height = croppedHeight
|
||||
const croppedCtx = croppedCanvas.getContext('2d')
|
||||
|
||||
if (errorOnTooSmall && (croppedWidth < 20 || croppedHeight < 20)) {
|
||||
return Promise.reject(new Error('Image too small'))
|
||||
}
|
||||
|
||||
croppedCtx.drawImage(canvas, leftmost, topmost, croppedWidth, croppedHeight, 0, 0, croppedWidth, croppedHeight)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
@@ -1404,7 +1404,21 @@ export default {
|
||||
if (response.status === 422 || response.status === 500) {
|
||||
const data = await response.json()
|
||||
|
||||
if (data.error) {
|
||||
if (data.field_uuid) {
|
||||
const field = this.fieldsUuidIndex[data.field_uuid]
|
||||
|
||||
if (field) {
|
||||
const step = this.stepFields.findIndex((fields) => fields.includes(field))
|
||||
|
||||
if (step !== -1) {
|
||||
this.goToStep(step, this.autoscrollFields)
|
||||
|
||||
this.showFillAllRequiredFields = true
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('Required field: ' + data.field_uuid))
|
||||
} else if (data.error) {
|
||||
const i18nKey = data.error.replace(/\s+/g, '_').toLowerCase()
|
||||
|
||||
alert(this.t(i18nKey) !== i18nKey ? this.t(i18nKey) : data.error)
|
||||
@@ -1439,11 +1453,7 @@ export default {
|
||||
this.isSubmittingComplete = false
|
||||
})
|
||||
}).catch(error => {
|
||||
if (error?.message === 'Image too small') {
|
||||
alert(this.t('signature_is_too_small_please_redraw'))
|
||||
} else {
|
||||
console.log(error)
|
||||
}
|
||||
console.log(error)
|
||||
}).finally(() => {
|
||||
this.isSubmitting = false
|
||||
this.isSubmittingComplete = false
|
||||
|
||||
@@ -93,7 +93,7 @@ const en = {
|
||||
reupload: 'Reupload',
|
||||
upload: 'Upload',
|
||||
files: 'Files',
|
||||
signature_is_too_small_please_redraw: 'Signature is too small. Please redraw.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'Signature is too small or simple. Please redraw.',
|
||||
wait_countdown_seconds: 'Wait {countdown} seconds'
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ const es = {
|
||||
reupload: 'Volver a subir',
|
||||
upload: 'Subir',
|
||||
files: 'Archivos',
|
||||
signature_is_too_small_please_redraw: 'La firma es demasiado pequeña. Por favor, dibújala de nuevo.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'La firma es demasiado pequeña o simple. Por favor, vuelve a dibujarla.',
|
||||
wait_countdown_seconds: 'Espera {countdown} segundos'
|
||||
}
|
||||
|
||||
@@ -289,7 +289,7 @@ const it = {
|
||||
reupload: 'Ricarica',
|
||||
upload: 'Carica',
|
||||
files: 'File',
|
||||
signature_is_too_small_please_redraw: 'La firma è troppo piccola. Ridisegnala per favore.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'La firma è troppo piccola o semplice. Ridisegnala, per favore.',
|
||||
wait_countdown_seconds: 'Attendi {countdown} secondi'
|
||||
}
|
||||
|
||||
@@ -387,7 +387,7 @@ const de = {
|
||||
reupload: 'Erneut hochladen',
|
||||
upload: 'Hochladen',
|
||||
files: 'Dateien',
|
||||
signature_is_too_small_please_redraw: 'Die Unterschrift ist zu klein. Bitte erneut zeichnen.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'Die Unterschrift ist zu klein oder zu einfach. Bitte erneut zeichnen.',
|
||||
wait_countdown_seconds: 'Warte {countdown} Sekunden'
|
||||
}
|
||||
|
||||
@@ -485,7 +485,7 @@ const fr = {
|
||||
reupload: 'Recharger',
|
||||
upload: 'Télécharger',
|
||||
files: 'Fichiers',
|
||||
signature_is_too_small_please_redraw: 'La signature est trop petite. Veuillez la redessiner.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'La signature est trop petite ou trop simple. Veuillez la redessiner.',
|
||||
wait_countdown_seconds: 'Attendez {countdown} secondes'
|
||||
}
|
||||
|
||||
@@ -583,7 +583,7 @@ const pl = {
|
||||
reupload: 'Ponowne przesłanie',
|
||||
upload: 'Przesyłanie',
|
||||
files: 'Pliki',
|
||||
signature_is_too_small_please_redraw: 'Podpis jest zbyt mały. Proszę narysować go ponownie.'
|
||||
signature_is_too_small_or_simple_please_redraw: 'Podpis jest zbyt mały lub zbyt prosty. Proszę narysować go ponownie.'
|
||||
}
|
||||
|
||||
const uk = {
|
||||
@@ -680,7 +680,7 @@ const uk = {
|
||||
reupload: 'Перезавантажити',
|
||||
upload: 'Завантажити',
|
||||
files: 'Файли',
|
||||
signature_is_too_small_please_redraw: 'Підпис занадто малий. Будь ласка, перемалюйте його.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'Підпис занадто маленький або надто простий. Будь ласка, перемалюйте.',
|
||||
wait_countdown_seconds: 'Зачекайте {countdown} секунд'
|
||||
}
|
||||
|
||||
@@ -778,7 +778,7 @@ const cs = {
|
||||
reupload: 'Znovu nahrát',
|
||||
upload: 'Nahrát',
|
||||
files: 'Soubory',
|
||||
signature_is_too_small_please_redraw: 'Podpis je příliš malý. Prosím, překreslete ho.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'Podpis je příliš malý nebo jednoduchý. Nakreslete jej prosím znovu.',
|
||||
wait_countdown_seconds: 'Počkejte {countdown} sekund'
|
||||
}
|
||||
|
||||
@@ -876,7 +876,7 @@ const pt = {
|
||||
reupload: 'Reenviar',
|
||||
upload: 'Carregar',
|
||||
files: 'Arquivos',
|
||||
signature_is_too_small_please_redraw: 'A assinatura é muito pequena. Por favor, redesenhe-a.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'A assinatura é muito pequena ou simples. Por favor, redesenhe.',
|
||||
wait_countdown_seconds: 'Aguarde {countdown} segundos'
|
||||
}
|
||||
|
||||
@@ -975,7 +975,7 @@ const he = {
|
||||
reupload: 'העלה שוב',
|
||||
upload: 'העלאה',
|
||||
files: 'קבצים',
|
||||
signature_is_too_small_please_redraw: 'החתימה קטנה מדי. אנא צייר מחדש.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'החתימה קטנה או פשוטה מדי. אנא חתום מחדש.',
|
||||
wait_countdown_seconds: 'המתן {countdown} שניות'
|
||||
}
|
||||
|
||||
@@ -1074,7 +1074,7 @@ const nl = {
|
||||
reupload: 'Opnieuw uploaden',
|
||||
upload: 'Uploaden',
|
||||
files: 'Bestanden',
|
||||
signature_is_too_small_please_redraw: 'De handtekening is te klein. Teken deze opnieuw, alstublieft.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'De handtekening is te klein of te eenvoudig. Teken opnieuw.',
|
||||
wait_countdown_seconds: 'Wacht {countdown} seconden'
|
||||
}
|
||||
|
||||
@@ -1172,7 +1172,7 @@ const ar = {
|
||||
reupload: 'إعادة التحميل',
|
||||
upload: 'تحميل',
|
||||
files: 'الملفات',
|
||||
signature_is_too_small_please_redraw: 'التوقيع صغير جدًا. يرجى إعادة الرسم.',
|
||||
signature_is_too_small_or_simple_please_redraw: 'التوقيع صغير جدًا أو بسيط جدًا. يرجى إعادة رسمه.',
|
||||
wait_countdown_seconds: 'انتظر {countdown} ثانية'
|
||||
}
|
||||
|
||||
@@ -1269,7 +1269,7 @@ const ko = {
|
||||
reupload: '다시 업로드',
|
||||
upload: '업로드',
|
||||
files: '파일',
|
||||
signature_is_too_small_please_redraw: '서명이 너무 작습니다. 다시 그려주세요.',
|
||||
signature_is_too_small_or_simple_please_redraw: '서명이 너무 작거나 단순합니다. 다시 그려주세요.',
|
||||
wait_countdown_seconds: '{countdown}초 기다리세요'
|
||||
}
|
||||
|
||||
|
||||
@@ -118,11 +118,17 @@
|
||||
:src="attachmentsIndex[modelValue || computedPreviousValue].url"
|
||||
class="mx-auto bg-white border border-base-300 rounded max-h-44"
|
||||
>
|
||||
<canvas
|
||||
v-show="!modelValue && !computedPreviousValue"
|
||||
ref="canvas"
|
||||
class="bg-white border border-base-300 rounded-2xl w-full draw-canvas"
|
||||
/>
|
||||
<div class="relative">
|
||||
<div
|
||||
v-if="!isDrawInitials"
|
||||
class="absolute top-0 right-0 left-0 bottom-0"
|
||||
/>
|
||||
<canvas
|
||||
v-show="!modelValue && !computedPreviousValue"
|
||||
ref="canvas"
|
||||
class="bg-white border border-base-300 rounded-2xl w-full draw-canvas"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
v-if="!isDrawInitials && !modelValue && !computedPreviousValue"
|
||||
id="initials_text_input"
|
||||
|
||||
@@ -292,6 +292,7 @@
|
||||
<script>
|
||||
import { IconReload, IconCamera, IconSignature, IconTextSize, IconArrowsDiagonalMinimize2, IconQrcode, IconX } from '@tabler/icons-vue'
|
||||
import { cropCanvasAndExportToPNG } from './crop_canvas'
|
||||
import { isValidSignatureCanvas } from './validate_signature'
|
||||
import SignaturePad from 'signature_pad'
|
||||
import AppearsOn from './appears_on'
|
||||
import FileDropzone from './dropzone'
|
||||
@@ -598,6 +599,7 @@ export default {
|
||||
},
|
||||
drawImage (event) {
|
||||
this.remove()
|
||||
this.clear()
|
||||
this.isSignatureStarted = true
|
||||
|
||||
this.drawOnCanvas(event.target.files[0], this.$refs.canvas)
|
||||
@@ -680,8 +682,14 @@ export default {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
cropCanvasAndExportToPNG(this.$refs.canvas, { errorOnTooSmall: true }).then(async (blob) => {
|
||||
if (this.isSignatureStarted && this.pad.toData().length > 0 && !isValidSignatureCanvas(this.pad.toData())) {
|
||||
alert(this.t('signature_is_too_small_or_simple_please_redraw'))
|
||||
|
||||
return Promise.reject(new Error('Image too small or simple'))
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
cropCanvasAndExportToPNG(this.$refs.canvas).then(async (blob) => {
|
||||
const file = new File([blob], 'signature.png', { type: 'image/png' })
|
||||
|
||||
if (this.dryRun) {
|
||||
@@ -717,12 +725,6 @@ export default {
|
||||
return resolve(attachment)
|
||||
})
|
||||
}
|
||||
}).catch((error) => {
|
||||
if (error.message === 'Image too small' && this.field.required === false) {
|
||||
return resolve({})
|
||||
} else {
|
||||
return reject(error)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
function isValidSignatureCanvas (data) {
|
||||
if (data.length === 0) return false
|
||||
|
||||
const strokes = data.filter(stroke => Array.isArray(stroke.points) && stroke.points.length > 2)
|
||||
|
||||
if (strokes.length === 0) return false
|
||||
|
||||
let skippedStraightLine = 0
|
||||
|
||||
const validStrokes = strokes.filter(stroke => {
|
||||
const points = stroke.points
|
||||
const first = points[0]
|
||||
const last = points[points.length - 1]
|
||||
const A = last.y - first.y
|
||||
const B = first.x - last.x
|
||||
const C = last.x * first.y - first.x * last.y
|
||||
const lineLength = Math.sqrt(A * A + B * B)
|
||||
|
||||
const totalDeviation = points.reduce((sum, p) => {
|
||||
const distanceToLine = Math.abs(A * p.x + B * p.y + C) / lineLength
|
||||
return sum + distanceToLine
|
||||
}, 0)
|
||||
|
||||
const avgDeviation = totalDeviation / points.length
|
||||
|
||||
if (avgDeviation < 3 && skippedStraightLine < 2) {
|
||||
skippedStraightLine++
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return validStrokes.length > 0
|
||||
}
|
||||
|
||||
export { isValidSignatureCanvas }
|
||||
@@ -94,11 +94,27 @@ class ProcessSubmitterCompletionJob
|
||||
end
|
||||
end
|
||||
|
||||
to = build_to_addresses(submitter)
|
||||
maybe_enqueue_copy_emails(submitter)
|
||||
end
|
||||
|
||||
return if to.blank? || submitter.template.preferences['documents_copy_email_enabled'] == false
|
||||
def maybe_enqueue_copy_emails(submitter)
|
||||
return if submitter.template.preferences['documents_copy_email_enabled'] == false
|
||||
|
||||
SubmitterMailer.documents_copy_email(submitter, to:).deliver_later!
|
||||
configs = AccountConfigs.find_or_initialize_for_key(submitter.account,
|
||||
AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY)
|
||||
|
||||
return if configs.value['enabled'] == false
|
||||
|
||||
to = submitter.submission.submitters.reject { |e| e.preferences['send_email'] == false }
|
||||
.sort_by(&:completed_at).select(&:email?).map(&:friendly_name)
|
||||
|
||||
return if to.blank?
|
||||
|
||||
if configs.value['bcc_recipients'] == true
|
||||
to.each { |to| SubmitterMailer.documents_copy_email(submitter, to:).deliver_later! }
|
||||
else
|
||||
SubmitterMailer.documents_copy_email(submitter, to: to.join(', ')).deliver_later!
|
||||
end
|
||||
end
|
||||
|
||||
def build_bcc_addresses(submission)
|
||||
@@ -110,11 +126,6 @@ class ProcessSubmitterCompletionJob
|
||||
bcc.to_s.scan(User::EMAIL_REGEXP)
|
||||
end
|
||||
|
||||
def build_to_addresses(submitter)
|
||||
submitter.submission.submitters.reject { |e| e.preferences['send_email'] == false }
|
||||
.sort_by(&:completed_at).select(&:email?).map(&:friendly_name).join(', ')
|
||||
end
|
||||
|
||||
def enqueue_next_submitter_request_notification(submitter)
|
||||
next_submitter_item =
|
||||
submitter.submission.template_submitters.find do |e|
|
||||
|
||||
@@ -30,6 +30,7 @@ class AccountConfig < ApplicationRecord
|
||||
ALLOW_TO_RESUBMIT = 'allow_to_resubmit'
|
||||
ALLOW_TO_DECLINE_KEY = 'allow_to_decline'
|
||||
SUBMITTER_REMINDERS = 'submitter_reminders'
|
||||
ENFORCE_SIGNING_ORDER_KEY = 'enforce_signing_order'
|
||||
FORM_COMPLETED_BUTTON_KEY = 'form_completed_button'
|
||||
FORM_COMPLETED_MESSAGE_KEY = 'form_completed_message'
|
||||
FORM_WITH_CONFETTI_KEY = 'form_with_confetti'
|
||||
|
||||
@@ -151,6 +151,20 @@
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if !Docuseal.multitenant? || can?(:manage, :personalization_advanced) %>
|
||||
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ENFORCE_SIGNING_ORDER_KEY) %>
|
||||
<% if can?(:manage, account_config) %>
|
||||
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||
<%= f.hidden_field :key %>
|
||||
<div class="flex items-center justify-between py-2.5">
|
||||
<span>
|
||||
<%= t('always_enforce_signing_order') %>
|
||||
</span>
|
||||
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render 'compliances' %>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="collapse-content">
|
||||
<%= form_for AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_DOCUMENTS_COPY_EMAIL_KEY), url: settings_personalization_path, method: :post, html: { autocomplete: 'off', class: 'space-y-4' } do |f| %>
|
||||
<%= f.hidden_field :key %>
|
||||
<%= f.fields_for :value, Struct.new(:subject, :body, :reply_to, :attach_audit_log, :attach_documents).new(*f.object.value.values_at('subject', 'body', 'reply_to', 'attach_audit_log', 'attach_documents')) do |ff| %>
|
||||
<%= f.fields_for :value, Struct.new(:subject, :body, :reply_to, :attach_audit_log, :attach_documents, :bcc_recipients, :enabled).new(*f.object.value.values_at('subject', 'body', 'reply_to', 'attach_audit_log', 'attach_documents', 'bcc_recipients', 'enabled')) do |ff| %>
|
||||
<div class="form-control">
|
||||
<%= ff.label :subject, t('subject'), class: 'label' %>
|
||||
<%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
|
||||
@@ -24,23 +24,41 @@
|
||||
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2', dir: 'auto' %>
|
||||
</autoresize-textarea>
|
||||
</div>
|
||||
<% if can?(:manage, :reply_to) %>
|
||||
<% if can?(:manage, :reply_to) || can?(:manage, :personalization_advanced) %>
|
||||
<div class="form-control">
|
||||
<%= ff.label :reply_to, t('reply_to'), class: 'label' %>
|
||||
<%= ff.email_field :reply_to, class: 'base-input', dir: 'auto', placeholder: t(:email) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex items-center justify-between pt-2.5 mx-1">
|
||||
<span>
|
||||
<%= t('attach_documents') %>
|
||||
</span>
|
||||
<%= ff.check_box :attach_documents, { checked: ff.object.attach_documents != false, class: 'toggle' }, 'true', 'false' %>
|
||||
</div>
|
||||
<div class="flex items-center justify-between pb-2.5 mx-1">
|
||||
<span>
|
||||
<%= t('attach_audit_log_pdf') %>
|
||||
</span>
|
||||
<%= ff.check_box :attach_audit_log, { checked: ff.object.attach_audit_log != false, class: 'toggle' }, 'true', 'false' %>
|
||||
<div class="space-y-3.5">
|
||||
<div class="flex items-center justify-between mx-1">
|
||||
<span>
|
||||
<%= t('attach_documents') %>
|
||||
</span>
|
||||
<%= ff.check_box :attach_documents, { checked: ff.object.attach_documents != false, class: 'toggle' }, 'true', 'false' %>
|
||||
</div>
|
||||
<div class="flex items-center justify-between mx-1">
|
||||
<span>
|
||||
<%= t('attach_audit_log_pdf') %>
|
||||
</span>
|
||||
<%= ff.check_box :attach_audit_log, { checked: ff.object.attach_audit_log != false, class: 'toggle' }, 'true', 'false' %>
|
||||
</div>
|
||||
<% unless Docuseal.multitenant? %>
|
||||
<div class="flex items-center justify-between mx-1">
|
||||
<span>
|
||||
<%= t('bcc_recipients') %>
|
||||
</span>
|
||||
<%= ff.check_box :bcc_recipients, { checked: ff.object.bcc_recipients == true, class: 'toggle' }, 'true', 'false' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if !Docuseal.multitenant? || can?(:manage, :personalization_advanced) %>
|
||||
<div class="flex items-center justify-between mx-1">
|
||||
<span>
|
||||
<%= t('send_emails_automatically_on_completion') %>
|
||||
</span>
|
||||
<%= ff.check_box :enabled, { checked: ff.object.enabled != false, class: 'toggle' }, 'true', 'false' %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="form-control pt-2">
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
<meta property="og:image" content="">
|
||||
<meta name="twitter:image" content="">
|
||||
<% else %>
|
||||
<meta property="og:image" content="<%= image_pack_url('images/preview.png') %>">
|
||||
<meta property="og:image" content="<%= root_url %>preview.png">
|
||||
<meta property="og:image:width" content="800">
|
||||
<meta property="og:image:height" content="800">
|
||||
<meta name="twitter:image" content="<%= image_pack_url('images/preview.png') %>">
|
||||
<meta name="twitter:image" content="<%= root_url %>preview.png">
|
||||
<% end %>
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:creator" content="@docusealco">
|
||||
|
||||
@@ -206,7 +206,7 @@
|
||||
<span>
|
||||
<%= t('send_emails_automatically_on_completion') %>
|
||||
</span>
|
||||
<%= ff.check_box :documents_copy_email_enabled, { checked: ff.object.documents_copy_email_enabled != false, class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'true', 'false' %>
|
||||
<%= ff.check_box :documents_copy_email_enabled, { checked: ff.object.documents_copy_email_enabled != false && configs['enabled'] != false, class: 'toggle', onchange: 'this.form.requestSubmit()', disabled: configs['enabled'] == false }, 'true', 'false' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="form-control pt-2">
|
||||
@@ -322,15 +322,17 @@
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-2' }, data: { close_on_submit: false } do |f| %>
|
||||
<div class="flex items-center pt-4 mt-4 justify-between border-t w-full">
|
||||
<span>
|
||||
<%= t('enforce_recipients_order') %>
|
||||
</span>
|
||||
<%= f.fields_for :preferences, Struct.new(:submitters_order).new(@template.preferences['submitters_order']) do |ff| %>
|
||||
<%= ff.check_box :submitters_order, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'preserved', '' %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% unless current_account.account_configs.exists?(key: AccountConfig::ENFORCE_SIGNING_ORDER_KEY, value: true) %>
|
||||
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-2' }, data: { close_on_submit: false } do |f| %>
|
||||
<div class="flex items-center pt-4 mt-4 justify-between border-t w-full">
|
||||
<span>
|
||||
<%= t('enforce_recipients_order') %>
|
||||
</span>
|
||||
<%= f.fields_for :preferences, Struct.new(:submitters_order).new(@template.preferences['submitters_order']) do |ff| %>
|
||||
<%= ff.check_box :submitters_order, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'preserved', '' %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<div class="form-control mt-5 pb-2">
|
||||
<%= button_tag button_title(title: t('save'), disabled_with: t('updating')), class: 'base-button', form: :submitters_form %>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="flex-wrap space-y-4 md:flex md:flex-nowrap md:space-y-0 md:space-x-10">
|
||||
<%= render 'shared/settings_nav' %>
|
||||
<div class="md:flex-grow">
|
||||
<div class="flex flex-col md:flex-row gap-2 md:justify-between md:items-end mb-4 min-h-12">
|
||||
<div class="flex flex-col md:flex-row md:flex-wrap gap-2 md:justify-between md:items-end mb-4 min-h-12">
|
||||
<h1 class="text-4xl font-bold">
|
||||
<% if params[:status] == 'archived' %>
|
||||
<%= t('archived_users') %>
|
||||
|
||||
+18
-18
@@ -20,6 +20,8 @@ en: &en
|
||||
language_ko: 한국어
|
||||
hi_there: Hi there
|
||||
thanks: Thanks
|
||||
bcc_recipients: BCC recipients
|
||||
always_enforce_signing_order: Always enforce the signing order
|
||||
edit_per_party: Edit per party
|
||||
reply_to: Reply to
|
||||
pending_by_me: Pending by me
|
||||
@@ -432,7 +434,7 @@ en: &en
|
||||
share_template_with_test_mode: Share template with Test mode
|
||||
share_template_with_all_tenants: Share template with all Tenants
|
||||
use_following_placeholders_text_: 'Use following placeholders text:'
|
||||
upgrade_plan_to_add_more_users: Upgrade plan to add more users
|
||||
users_count_total_users_count_pro_users_limit_was_reached_to_invite_additional_users_please_purchase_more_pro_user_seats_via_the_manage_plan_button: '%{users_count}/%{total_users_count} Pro users limit was reached. To invite additional users, please purchase more Pro user seats via the "Manage plan" button.'
|
||||
move_into_folder: Move Into Folder
|
||||
new_folder_name: New Folder Name
|
||||
exit_preview: Exit Preview
|
||||
@@ -473,8 +475,6 @@ en: &en
|
||||
team_accounts: Team Accounts
|
||||
tenant_account: Tenant account
|
||||
tenant_accounts: Tenant Accounts
|
||||
upgrade_plan: Upgrade Plan
|
||||
add_user: Add User
|
||||
impersonate: Impersonate
|
||||
loading: Loading
|
||||
documents: Documents
|
||||
@@ -745,6 +745,8 @@ en: &en
|
||||
read: Read your data
|
||||
|
||||
es: &es
|
||||
always_enforce_signing_order: Siempre imponer el orden de firma
|
||||
bcc_recipients: Destinatarios CCO
|
||||
edit_per_party: Editar por parte
|
||||
signed: Firmado
|
||||
reply_to: Responder a
|
||||
@@ -1159,7 +1161,7 @@ es: &es
|
||||
share_template_with_test_mode: Compartir plantilla con el modo de prueba
|
||||
share_template_with_all_tenants: Compartir plantilla con todos los inquilinos
|
||||
use_following_placeholders_text_: 'Usa los siguientes marcadores de posición:'
|
||||
upgrade_plan_to_add_more_users: Actualiza el plan para agregar más usuarios
|
||||
users_count_total_users_count_pro_users_limit_was_reached_to_invite_additional_users_please_purchase_more_pro_user_seats_via_the_manage_plan_button: 'Se alcanzó el límite de %{users_count}/%{total_users_count} usuarios Pro. Para invitar a más usuarios, compra más plazas Pro usando el botón "Gestionar plan".'
|
||||
move_into_folder: Mover a la carpeta
|
||||
new_folder_name: Nuevo nombre de la carpeta
|
||||
exit_preview: Salir de la vista previa
|
||||
@@ -1200,8 +1202,6 @@ es: &es
|
||||
team_accounts: Cuentas de equipo
|
||||
tenant_account: Cuenta de inquilino
|
||||
tenant_accounts: Cuentas de inquilino
|
||||
upgrade_plan: Actualizar plan
|
||||
add_user: Agregar usuario
|
||||
impersonate: Suplantar
|
||||
loading: Cargando
|
||||
documents: Documentos
|
||||
@@ -1472,6 +1472,8 @@ es: &es
|
||||
read: Leer tus datos
|
||||
|
||||
it: &it
|
||||
always_enforce_signing_order: Applicare sempre l'ordine di firma
|
||||
bcc_recipients: Destinatari BCC
|
||||
edit_per_party: Modifica per partito
|
||||
signed: Firmato
|
||||
reply_to: Rispondi a
|
||||
@@ -1885,7 +1887,7 @@ it: &it
|
||||
share_template_with_test_mode: Condividi modello con la modalità di test
|
||||
share_template_with_all_tenants: Condividi il modello con tutti i tenant
|
||||
use_following_placeholders_text_: 'Usa i seguenti segnaposto:'
|
||||
upgrade_plan_to_add_more_users: Aggiorna il piano per aggiungere più utenti
|
||||
users_count_total_users_count_pro_users_limit_was_reached_to_invite_additional_users_please_purchase_more_pro_user_seats_via_the_manage_plan_button: 'È stato raggiunto il limite di %{users_count}/%{total_users_count} utenti Pro. Per invitare altri utenti, acquista più posti Pro tramite il pulsante "Gestisci piano".'
|
||||
move_into_folder: Sposta nella cartella
|
||||
new_folder_name: Nuovo nome della cartella
|
||||
exit_preview: "Esci dall'anteprima"
|
||||
@@ -1926,8 +1928,6 @@ it: &it
|
||||
team_accounts: Account di squadra
|
||||
tenant_account: Account tenant
|
||||
tenant_accounts: Account tenant
|
||||
upgrade_plan: Aggiorna piano
|
||||
add_user: Aggiungi utente
|
||||
impersonate: Impersona
|
||||
loading: Caricamento in corso
|
||||
documents: Documenti
|
||||
@@ -2198,6 +2198,8 @@ it: &it
|
||||
read: Leggi i tuoi dati
|
||||
|
||||
fr: &fr
|
||||
always_enforce_signing_order: Toujours appliquer l'ordre de signature
|
||||
bcc_recipients: Destinataires en CCI
|
||||
edit_per_party: Éditer par partie
|
||||
signed: Signé
|
||||
reply_to: Répondre à
|
||||
@@ -2613,7 +2615,7 @@ fr: &fr
|
||||
share_template_with_test_mode: Partager le modèle avec le mode test
|
||||
share_template_with_all_tenants: Partager le modèle avec tous les locataires
|
||||
use_following_placeholders_text_: 'Utilisez les espaces réservés suivants:'
|
||||
upgrade_plan_to_add_more_users: "Mettez à jour le plan pour ajouter plus d'utilisateurs"
|
||||
users_count_total_users_count_pro_users_limit_was_reached_to_invite_additional_users_please_purchase_more_pro_user_seats_via_the_manage_plan_button: 'La limite de %{users_count}/%{total_users_count} utilisateurs Pro a été atteinte. Pour inviter d''autres utilisateurs, veuillez acheter plus de places Pro via le bouton "Gérer le plan".'
|
||||
move_into_folder: Déplacer dans le dossier
|
||||
new_folder_name: Nouveau nom du dossier
|
||||
exit_preview: "Quitter l'aperçu"
|
||||
@@ -2654,8 +2656,6 @@ fr: &fr
|
||||
team_accounts: "Comptes d'équipe"
|
||||
tenant_account: Compte locataire
|
||||
tenant_accounts: Comptes locataires
|
||||
upgrade_plan: Mettre à jour le plan
|
||||
add_user: Ajouter un utilisateur
|
||||
impersonate: Usurper
|
||||
loading: Chargement en cours
|
||||
documents: Documents
|
||||
@@ -2926,6 +2926,8 @@ fr: &fr
|
||||
read: Lire vos données
|
||||
|
||||
pt: &pt
|
||||
always_enforce_signing_order: Sempre impor a ordem de assinatura
|
||||
bcc_recipients: Destinatários BCC
|
||||
edit_per_party: Edita por festa
|
||||
signed: Assinado
|
||||
reply_to: Responder a
|
||||
@@ -3340,7 +3342,7 @@ pt: &pt
|
||||
share_template_with_test_mode: Compartilhar modelo com o modo de teste
|
||||
share_template_with_all_tenants: Compartilhar modelo com todos os locatários
|
||||
use_following_placeholders_text_: 'Use os seguintes textos de substituição:'
|
||||
upgrade_plan_to_add_more_users: Faça upgrade do plano para adicionar mais usuários
|
||||
users_count_total_users_count_pro_users_limit_was_reached_to_invite_additional_users_please_purchase_more_pro_user_seats_via_the_manage_plan_button: 'O limite de %{users_count}/%{total_users_count} usuários Pro foi atingido. Para convidar mais usuários, adquira mais licenças Pro através do botão "Gerenciar plano".'
|
||||
move_into_folder: Mover para pasta
|
||||
new_folder_name: Novo nome da pasta
|
||||
exit_preview: Sair da pré-visualização
|
||||
@@ -3381,8 +3383,6 @@ pt: &pt
|
||||
team_accounts: Contas de equipe
|
||||
tenant_account: Conta de locatário
|
||||
tenant_accounts: Contas de locatário
|
||||
upgrade_plan: Fazer upgrade do plano
|
||||
add_user: Adicionar usuário
|
||||
impersonate: Usurpar
|
||||
loading: Carregando
|
||||
documents: Documentos
|
||||
@@ -3653,6 +3653,8 @@ pt: &pt
|
||||
read: Ler seus dados
|
||||
|
||||
de: &de
|
||||
always_enforce_signing_order: Immer die Reihenfolge der Unterschriften erzwingen
|
||||
bcc_recipients: BCC-Empfänger
|
||||
edit_per_party: Bearbeiten pro Partei
|
||||
signed: Unterschrieben
|
||||
reply_to: Antworten auf
|
||||
@@ -4067,7 +4069,7 @@ de: &de
|
||||
share_template_with_test_mode: Vorlage mit dem Testmodus teilen
|
||||
share_template_with_all_tenants: Vorlage mit allen Mietern teilen
|
||||
use_following_placeholders_text_: 'Verwende die folgenden Platzhaltertexte:'
|
||||
upgrade_plan_to_add_more_users: Upgrade des Plans, um weitere Benutzer hinzuzufügen
|
||||
users_count_total_users_count_pro_users_limit_was_reached_to_invite_additional_users_please_purchase_more_pro_user_seats_via_the_manage_plan_button: 'Das Limit von %{users_count}/%{total_users_count} Pro-Benutzern wurde erreicht. Um weitere Benutzer einzuladen, kaufen Sie bitte zusätzliche Pro-Benutzerplätze über die Schaltfläche "Plan verwalten".'
|
||||
move_into_folder: In Ordner verschieben
|
||||
new_folder_name: Neuer Ordnername
|
||||
exit_preview: Vorschau beenden
|
||||
@@ -4108,8 +4110,6 @@ de: &de
|
||||
team_accounts: Teamkonten
|
||||
tenant_account: Mieterkonto
|
||||
tenant_accounts: Mieterkonten
|
||||
upgrade_plan: Plan upgraden
|
||||
add_user: Benutzer hinzufügen
|
||||
impersonate: Nachahmen
|
||||
loading: Wird geladen
|
||||
documents: Dokumente
|
||||
|
||||
@@ -28,7 +28,7 @@ module ActionMailerConfigsInterceptor
|
||||
end
|
||||
|
||||
unless Docuseal.multitenant?
|
||||
email_configs = EncryptedConfig.find_by(key: EncryptedConfig::EMAIL_SMTP_KEY)
|
||||
email_configs = EncryptedConfig.order(:account_id).find_by(key: EncryptedConfig::EMAIL_SMTP_KEY)
|
||||
|
||||
if email_configs
|
||||
message.delivery_method(:smtp, build_smtp_configs_hash(email_configs))
|
||||
|
||||
@@ -196,4 +196,20 @@ module Submissions
|
||||
end
|
||||
end.exclude?(false)
|
||||
end
|
||||
|
||||
def regenerate_documents(submission)
|
||||
submitters = submission.submitters.where.not(completed_at: nil).preload(:documents_attachments)
|
||||
|
||||
submitters.each { |submitter| submitter.documents.each(&:destroy!) }
|
||||
|
||||
submission.submitters.where.not(completed_at: nil).order(:completed_at).each do |submitter|
|
||||
GenerateResultAttachments.call(submitter)
|
||||
end
|
||||
|
||||
return if submission.combined_document_attachment.blank?
|
||||
|
||||
submission.combined_document_attachment.destroy!
|
||||
|
||||
Submissions::GenerateCombinedAttachment.call(submission.submitters.completed.order(:completed_at).last)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,11 +7,15 @@ module Submissions
|
||||
CHECK_COMPLETE_TIMEOUT = 90.seconds
|
||||
|
||||
WaitForCompleteTimeout = Class.new(StandardError)
|
||||
NotCompletedYet = Class.new(StandardError)
|
||||
|
||||
module_function
|
||||
|
||||
def call(submitter)
|
||||
return [] unless submitter
|
||||
|
||||
raise NotCompletedYet unless submitter.completed_at?
|
||||
|
||||
return submitter.documents if ApplicationRecord.uncached { submitter.document_generation_events.complete.exists? }
|
||||
|
||||
events =
|
||||
|
||||
@@ -8,6 +8,7 @@ module Submitters
|
||||
AccountConfig::FORM_PREFILL_SIGNATURE_KEY,
|
||||
AccountConfig::WITH_SIGNATURE_ID,
|
||||
AccountConfig::ALLOW_TO_DECLINE_KEY,
|
||||
AccountConfig::ENFORCE_SIGNING_ORDER_KEY,
|
||||
AccountConfig::REQUIRE_SIGNING_REASON_KEY,
|
||||
AccountConfig::REUSE_SIGNATURE_KEY,
|
||||
AccountConfig::ALLOW_TYPED_SIGNATURE,
|
||||
@@ -27,6 +28,7 @@ module Submitters
|
||||
with_decline = find_safe_value(configs, AccountConfig::ALLOW_TO_DECLINE_KEY) != false
|
||||
with_signature_id = find_safe_value(configs, AccountConfig::WITH_SIGNATURE_ID) == true
|
||||
require_signing_reason = find_safe_value(configs, AccountConfig::REQUIRE_SIGNING_REASON_KEY) == true
|
||||
enforce_signing_order = find_safe_value(configs, AccountConfig::ENFORCE_SIGNING_ORDER_KEY) == true
|
||||
policy_links = find_safe_value(configs, AccountConfig::POLICY_LINKS_KEY)
|
||||
|
||||
attrs = {
|
||||
@@ -36,6 +38,7 @@ module Submitters
|
||||
reuse_signature:,
|
||||
with_decline:,
|
||||
policy_links:,
|
||||
enforce_signing_order:,
|
||||
completed_message:,
|
||||
require_signing_reason:,
|
||||
prefill_signature:,
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
module Submitters
|
||||
module SubmitValues
|
||||
ValidationError = Class.new(StandardError)
|
||||
RequiredFieldError = Class.new(StandardError)
|
||||
|
||||
VARIABLE_REGEXP = /\{\{?(\w+)\}\}?/
|
||||
NONEDITABLE_FIELD_TYPES = %w[stamp heading].freeze
|
||||
|
||||
module_function
|
||||
|
||||
def call(submitter, params, request)
|
||||
def call(submitter, params, request, validate_required: true)
|
||||
Submissions.update_template_fields!(submitter.submission) if submitter.submission.template_fields.blank?
|
||||
|
||||
unless submitter.submission_events.exists?(event_type: 'start_form')
|
||||
@@ -20,7 +22,7 @@ module Submitters
|
||||
end
|
||||
end
|
||||
|
||||
update_submitter!(submitter, params, request)
|
||||
update_submitter!(submitter, params, request, validate_required:)
|
||||
|
||||
submitter.submission.save!
|
||||
|
||||
@@ -29,13 +31,13 @@ module Submitters
|
||||
submitter
|
||||
end
|
||||
|
||||
def update_submitter!(submitter, params, request)
|
||||
def update_submitter!(submitter, params, request, validate_required: true)
|
||||
values = normalized_values(params)
|
||||
|
||||
submitter.values.merge!(values)
|
||||
submitter.opened_at ||= Time.current
|
||||
|
||||
assign_completed_attributes(submitter, request) if params[:completed] == 'true'
|
||||
assign_completed_attributes(submitter, request, validate_required:) if params[:completed] == 'true'
|
||||
|
||||
ApplicationRecord.transaction do
|
||||
maybe_set_signature_reason!(values, submitter, params)
|
||||
@@ -49,25 +51,36 @@ module Submitters
|
||||
submitter
|
||||
end
|
||||
|
||||
def assign_completed_attributes(submitter, request)
|
||||
def assign_completed_attributes(submitter, request, validate_required: true)
|
||||
submitter.completed_at = Time.current
|
||||
submitter.ip = request.remote_ip
|
||||
submitter.ua = request.user_agent
|
||||
|
||||
submitter.values = merge_default_values(submitter)
|
||||
submitter.values = maybe_remove_condition_values(submitter)
|
||||
|
||||
required_field_uuids_acc = Set.new
|
||||
|
||||
submitter.values = maybe_remove_condition_values(submitter, required_field_uuids_acc:)
|
||||
|
||||
formula_values = build_formula_values(submitter)
|
||||
|
||||
if formula_values.present?
|
||||
submitter.values = submitter.values.merge(formula_values)
|
||||
submitter.values = maybe_remove_condition_values(submitter)
|
||||
submitter.values = maybe_remove_condition_values(submitter, required_field_uuids_acc:)
|
||||
end
|
||||
|
||||
submitter.values = submitter.values.transform_values do |v|
|
||||
v == '{{date}}' ? Time.current.in_time_zone(submitter.account.timezone).to_date.to_s : v
|
||||
end
|
||||
|
||||
required_field_uuids_acc.each do |uuid|
|
||||
next if submitter.values[uuid].present?
|
||||
|
||||
raise RequiredFieldError, uuid if validate_required
|
||||
|
||||
Rollbar.warning("Required field #{submitter.id}: #{uuid}") if defined?(Rollbar)
|
||||
end
|
||||
|
||||
submitter
|
||||
end
|
||||
|
||||
@@ -188,37 +201,67 @@ module Submitters
|
||||
with_time:)
|
||||
end
|
||||
|
||||
def maybe_remove_condition_values(submitter)
|
||||
fields_uuid_index = submitter.submission.template_fields.index_by { |e| e['uuid'] }
|
||||
def maybe_remove_condition_values(submitter, required_field_uuids_acc: nil)
|
||||
submission = submitter.submission
|
||||
|
||||
fields_uuid_index = submission.template_fields.index_by { |e| e['uuid'] }
|
||||
|
||||
submitters_values = nil
|
||||
has_other_submitters = submission.template_submitters.size > 1
|
||||
|
||||
has_document_conditions =
|
||||
(submission.template_schema || submission.template.schema).any? { |e| e['conditions'].present? }
|
||||
|
||||
attachments_index =
|
||||
Submissions.filtered_conditions_schema(submitter.submission).index_by { |i| i['attachment_uuid'] }
|
||||
|
||||
submitter_values = nil
|
||||
is_other_submitter_conditions = submitter.submission.template_submitters.size > 1
|
||||
|
||||
submitter.submission.template_fields.each do |field|
|
||||
next if field['submitter_uuid'] != submitter.uuid
|
||||
|
||||
submitter_values ||= submitter.values
|
||||
|
||||
is_other_submitter_conditions &&= field_conditions_other_submitter?(submitter, field, fields_uuid_index)
|
||||
|
||||
if is_other_submitter_conditions
|
||||
submitter_values = submitter.submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) }
|
||||
if has_document_conditions
|
||||
Submissions.filtered_conditions_schema(submission).index_by { |i| i['attachment_uuid'] }
|
||||
end
|
||||
|
||||
submitter.values.delete(field['uuid']) unless check_field_conditions(submitter_values, field, fields_uuid_index)
|
||||
submission.template_fields.each do |field|
|
||||
next if field['submitter_uuid'] != submitter.uuid
|
||||
|
||||
if field['areas'].present? && field['areas'].none? { |area| attachments_index[area['attachment_uuid']] }
|
||||
required_field_uuids_acc.add(field['uuid']) if required_field_uuids_acc && required_editable_field?(field)
|
||||
|
||||
if has_document_conditions && !check_field_areas_attachments(field, attachments_index)
|
||||
submitter.values.delete(field['uuid'])
|
||||
required_field_uuids_acc.delete(field['uuid'])
|
||||
end
|
||||
|
||||
if has_other_submitters && !submitters_values &&
|
||||
field_conditions_other_submitter?(submitter, field, fields_uuid_index)
|
||||
submitters_values = merge_submitters_values(submitter)
|
||||
end
|
||||
|
||||
unless check_field_conditions(submitters_values || submitter.values, field, fields_uuid_index)
|
||||
submitter.values.delete(field['uuid'])
|
||||
required_field_uuids_acc.delete(field['uuid'])
|
||||
end
|
||||
end
|
||||
|
||||
submitter.values
|
||||
end
|
||||
|
||||
def required_editable_field?(field)
|
||||
return false if NONEDITABLE_FIELD_TYPES.include?(field['type'])
|
||||
|
||||
field['required'].present? && field['readonly'].blank?
|
||||
end
|
||||
|
||||
def check_field_areas_attachments(field, attachments_index)
|
||||
return true if field['areas'].blank?
|
||||
|
||||
field['areas'].any? { |area| attachments_index[area['attachment_uuid']] }
|
||||
end
|
||||
|
||||
def merge_submitters_values(submitter)
|
||||
submitter.submission.submitters
|
||||
.reduce({}) { |acc, sub| acc.merge(sub.values) }
|
||||
.merge(submitter.values)
|
||||
end
|
||||
|
||||
def field_conditions_other_submitter?(submitter, field, fields_uuid_index)
|
||||
return false if field['conditions'].blank?
|
||||
|
||||
field['conditions'].to_a.any? do |c|
|
||||
fields_uuid_index.dig(c['field_uuid'], 'submitter_uuid') != submitter.uuid
|
||||
end
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
+38
-10
@@ -4,21 +4,49 @@ module SigningFormHelper
|
||||
module_function
|
||||
|
||||
def draw_canvas
|
||||
page.find('canvas').click([], { x: 150, y: 100 })
|
||||
page.execute_script <<~JS
|
||||
const canvas = document.getElementsByTagName('canvas')[0];
|
||||
const ctx = canvas.getContext('2d');
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(150, 100);
|
||||
ctx.lineTo(450, 100);
|
||||
ctx.stroke();
|
||||
const startX = rect.left + 50;
|
||||
const startY = rect.top + 100;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(150, 100);
|
||||
ctx.lineTo(150, 150);
|
||||
ctx.stroke();
|
||||
const amplitude = 20;
|
||||
const wavelength = 30;
|
||||
const length = 300;
|
||||
|
||||
function dispatchPointerEvent(type, x, y) {
|
||||
const event = new PointerEvent(type, {
|
||||
pointerId: 1,
|
||||
pointerType: 'pen',
|
||||
isPrimary: true,
|
||||
clientX: x,
|
||||
clientY: y,
|
||||
bubbles: true,
|
||||
pressure: 0.5
|
||||
});
|
||||
|
||||
canvas.dispatchEvent(event);
|
||||
}
|
||||
|
||||
dispatchPointerEvent('pointerdown', startX, startY);
|
||||
|
||||
let x = 0;
|
||||
function drawStep() {
|
||||
if (x > length) {
|
||||
dispatchPointerEvent('pointerup', startX + x, startY);
|
||||
return;
|
||||
}
|
||||
|
||||
const y = startY + amplitude * Math.sin((x / wavelength) * 2 * Math.PI);
|
||||
dispatchPointerEvent('pointermove', startX + x, y);
|
||||
x += 5;
|
||||
requestAnimationFrame(drawStep);
|
||||
}
|
||||
|
||||
drawStep();
|
||||
JS
|
||||
|
||||
sleep 0.5
|
||||
end
|
||||
|
||||
|
||||
@@ -360,6 +360,19 @@ RSpec.describe 'Signing Form', type: :system do
|
||||
expect(field_value(submitter, 'Signature')).to be_present
|
||||
end
|
||||
|
||||
it 'shows an error message if the canvas is not drawn or too simple' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
find('#expand_form_button').click
|
||||
page.find('canvas').click([], { x: 150, y: 100 })
|
||||
|
||||
alert_text = page.accept_alert do
|
||||
click_button 'Sign and Complete'
|
||||
end
|
||||
|
||||
expect(alert_text).to eq 'Signature is too small or simple. Please redraw.'
|
||||
end
|
||||
|
||||
it 'completes the form if the canvas is typed' do
|
||||
visit submit_form_path(slug: submitter.slug)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user