Merge from docusealco/wip
This commit is contained in:
@@ -15,6 +15,7 @@ class AccountConfigsController < ApplicationController
|
|||||||
AccountConfig::DOWNLOAD_LINKS_AUTH_KEY,
|
AccountConfig::DOWNLOAD_LINKS_AUTH_KEY,
|
||||||
AccountConfig::FORCE_SSO_AUTH_KEY,
|
AccountConfig::FORCE_SSO_AUTH_KEY,
|
||||||
AccountConfig::FLATTEN_RESULT_PDF_KEY,
|
AccountConfig::FLATTEN_RESULT_PDF_KEY,
|
||||||
|
AccountConfig::ENFORCE_SIGNING_ORDER_KEY,
|
||||||
AccountConfig::WITH_SIGNATURE_ID,
|
AccountConfig::WITH_SIGNATURE_ID,
|
||||||
AccountConfig::COMBINE_PDF_RESULT_KEY,
|
AccountConfig::COMBINE_PDF_RESULT_KEY,
|
||||||
AccountConfig::REQUIRE_SIGNING_REASON_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
|
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)
|
if last_submitter.completed_at < TTL.ago && !signature_valid && !current_user_submitter?(last_submitter)
|
||||||
Rollbar.info("TTL: #{last_submitter.id}") if defined?(Rollbar)
|
Rollbar.info("TTL: #{last_submitter.id}") if defined?(Rollbar)
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ class SubmitFormController < ApplicationController
|
|||||||
@submitter.account.archived_at?
|
@submitter.account.archived_at?
|
||||||
return render :expired if submission.expired?
|
return render :expired if submission.expired?
|
||||||
return render :declined if @submitter.declined_at?
|
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.current_submitter_order?(@submitter)
|
||||||
|
|
||||||
Submitters.preload_with_pages(@submitter)
|
Submitters.preload_with_pages(@submitter)
|
||||||
@@ -29,8 +33,6 @@ class SubmitFormController < ApplicationController
|
|||||||
|
|
||||||
@attachments_index = build_attachments_index(submission)
|
@attachments_index = build_attachments_index(submission)
|
||||||
|
|
||||||
@form_configs = Submitters::FormConfigs.call(@submitter, CONFIG_KEYS)
|
|
||||||
|
|
||||||
return unless @form_configs[:prefill_signature]
|
return unless @form_configs[:prefill_signature]
|
||||||
|
|
||||||
if (user_signature = UserConfigs.load_signature(current_user))
|
if (user_signature = UserConfigs.load_signature(current_user))
|
||||||
@@ -70,6 +72,10 @@ class SubmitFormController < ApplicationController
|
|||||||
Submitters::SubmitValues.call(submitter, params, request)
|
Submitters::SubmitValues.call(submitter, params, request)
|
||||||
|
|
||||||
head :ok
|
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
|
rescue Submitters::SubmitValues::ValidationError => e
|
||||||
render json: { error: e.message }, status: :unprocessable_entity
|
render json: { error: e.message }, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ import IndeterminateCheckbox from './elements/indeterminate_checkbox'
|
|||||||
|
|
||||||
import * as TurboInstantClick from './lib/turbo_instant_click'
|
import * as TurboInstantClick from './lib/turbo_instant_click'
|
||||||
|
|
||||||
import './images/preview.png'
|
|
||||||
|
|
||||||
TurboInstantClick.start()
|
TurboInstantClick.start()
|
||||||
|
|
||||||
document.addEventListener('turbo:before-cache', () => {
|
document.addEventListener('turbo:before-cache', () => {
|
||||||
|
|||||||
+20
-17
@@ -1,5 +1,6 @@
|
|||||||
import SignaturePad from 'signature_pad'
|
import SignaturePad from 'signature_pad'
|
||||||
import { cropCanvasAndExportToPNG } from './submission_form/crop_canvas'
|
import { cropCanvasAndExportToPNG } from './submission_form/crop_canvas'
|
||||||
|
import { isValidSignatureCanvas } from './submission_form/validate_signature'
|
||||||
|
|
||||||
window.customElements.define('draw-signature', class extends HTMLElement {
|
window.customElements.define('draw-signature', class extends HTMLElement {
|
||||||
connectedCallback () {
|
connectedCallback () {
|
||||||
@@ -43,6 +44,8 @@ window.customElements.define('draw-signature', class extends HTMLElement {
|
|||||||
|
|
||||||
return response
|
return response
|
||||||
})
|
})
|
||||||
|
}).catch(error => {
|
||||||
|
console.log(error)
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.submitButton.disabled = false
|
this.submitButton.disabled = false
|
||||||
})
|
})
|
||||||
@@ -65,26 +68,26 @@ window.customElements.define('draw-signature', class extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async submitImage () {
|
async submitImage () {
|
||||||
return new Promise((resolve, reject) => {
|
if (!isValidSignatureCanvas(this.pad.toData())) {
|
||||||
cropCanvasAndExportToPNG(this.canvas, { errorOnTooSmall: true }).then(async (blob) => {
|
alert('Signature is too small or simple. Please redraw.')
|
||||||
const file = new File([blob], 'signature.png', { type: 'image/png' })
|
|
||||||
|
|
||||||
const formData = new FormData()
|
return Promise.reject(new Error('Image too small or simple'))
|
||||||
|
}
|
||||||
|
|
||||||
formData.append('file', file)
|
return cropCanvasAndExportToPNG(this.canvas).then(async (blob) => {
|
||||||
formData.append('submitter_slug', this.dataset.slug)
|
const file = new File([blob], 'signature.png', { type: 'image/png' })
|
||||||
formData.append('name', 'attachments')
|
|
||||||
formData.append('remember_signature', 'true')
|
|
||||||
|
|
||||||
return fetch('/api/attachments', {
|
const formData = new FormData()
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
formData.append('file', file)
|
||||||
}).then((resp) => resp.json()).then((attachment) => {
|
formData.append('submitter_slug', this.dataset.slug)
|
||||||
return resolve(attachment)
|
formData.append('name', 'attachments')
|
||||||
})
|
formData.append('remember_signature', 'true')
|
||||||
}).catch((error) => {
|
|
||||||
return reject(error)
|
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 ctx = canvas.getContext('2d')
|
||||||
|
|
||||||
const width = canvas.width
|
const width = canvas.width
|
||||||
@@ -33,10 +33,6 @@ function cropCanvasAndExportToPNG (canvas, { errorOnTooSmall } = { errorOnTooSma
|
|||||||
croppedCanvas.height = croppedHeight
|
croppedCanvas.height = croppedHeight
|
||||||
const croppedCtx = croppedCanvas.getContext('2d')
|
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)
|
croppedCtx.drawImage(canvas, leftmost, topmost, croppedWidth, croppedHeight, 0, 0, croppedWidth, croppedHeight)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|||||||
@@ -1404,7 +1404,21 @@ export default {
|
|||||||
if (response.status === 422 || response.status === 500) {
|
if (response.status === 422 || response.status === 500) {
|
||||||
const data = await response.json()
|
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()
|
const i18nKey = data.error.replace(/\s+/g, '_').toLowerCase()
|
||||||
|
|
||||||
alert(this.t(i18nKey) !== i18nKey ? this.t(i18nKey) : data.error)
|
alert(this.t(i18nKey) !== i18nKey ? this.t(i18nKey) : data.error)
|
||||||
@@ -1439,11 +1453,7 @@ export default {
|
|||||||
this.isSubmittingComplete = false
|
this.isSubmittingComplete = false
|
||||||
})
|
})
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
if (error?.message === 'Image too small') {
|
console.log(error)
|
||||||
alert(this.t('signature_is_too_small_please_redraw'))
|
|
||||||
} else {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.isSubmitting = false
|
this.isSubmitting = false
|
||||||
this.isSubmittingComplete = false
|
this.isSubmittingComplete = false
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ const en = {
|
|||||||
reupload: 'Reupload',
|
reupload: 'Reupload',
|
||||||
upload: 'Upload',
|
upload: 'Upload',
|
||||||
files: 'Files',
|
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'
|
wait_countdown_seconds: 'Wait {countdown} seconds'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@ const es = {
|
|||||||
reupload: 'Volver a subir',
|
reupload: 'Volver a subir',
|
||||||
upload: 'Subir',
|
upload: 'Subir',
|
||||||
files: 'Archivos',
|
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'
|
wait_countdown_seconds: 'Espera {countdown} segundos'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,7 +289,7 @@ const it = {
|
|||||||
reupload: 'Ricarica',
|
reupload: 'Ricarica',
|
||||||
upload: 'Carica',
|
upload: 'Carica',
|
||||||
files: 'File',
|
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'
|
wait_countdown_seconds: 'Attendi {countdown} secondi'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,7 +387,7 @@ const de = {
|
|||||||
reupload: 'Erneut hochladen',
|
reupload: 'Erneut hochladen',
|
||||||
upload: 'Hochladen',
|
upload: 'Hochladen',
|
||||||
files: 'Dateien',
|
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'
|
wait_countdown_seconds: 'Warte {countdown} Sekunden'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,7 +485,7 @@ const fr = {
|
|||||||
reupload: 'Recharger',
|
reupload: 'Recharger',
|
||||||
upload: 'Télécharger',
|
upload: 'Télécharger',
|
||||||
files: 'Fichiers',
|
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'
|
wait_countdown_seconds: 'Attendez {countdown} secondes'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,7 +583,7 @@ const pl = {
|
|||||||
reupload: 'Ponowne przesłanie',
|
reupload: 'Ponowne przesłanie',
|
||||||
upload: 'Przesyłanie',
|
upload: 'Przesyłanie',
|
||||||
files: 'Pliki',
|
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 = {
|
const uk = {
|
||||||
@@ -680,7 +680,7 @@ const uk = {
|
|||||||
reupload: 'Перезавантажити',
|
reupload: 'Перезавантажити',
|
||||||
upload: 'Завантажити',
|
upload: 'Завантажити',
|
||||||
files: 'Файли',
|
files: 'Файли',
|
||||||
signature_is_too_small_please_redraw: 'Підпис занадто малий. Будь ласка, перемалюйте його.',
|
signature_is_too_small_or_simple_please_redraw: 'Підпис занадто маленький або надто простий. Будь ласка, перемалюйте.',
|
||||||
wait_countdown_seconds: 'Зачекайте {countdown} секунд'
|
wait_countdown_seconds: 'Зачекайте {countdown} секунд'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -778,7 +778,7 @@ const cs = {
|
|||||||
reupload: 'Znovu nahrát',
|
reupload: 'Znovu nahrát',
|
||||||
upload: 'Nahrát',
|
upload: 'Nahrát',
|
||||||
files: 'Soubory',
|
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'
|
wait_countdown_seconds: 'Počkejte {countdown} sekund'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -876,7 +876,7 @@ const pt = {
|
|||||||
reupload: 'Reenviar',
|
reupload: 'Reenviar',
|
||||||
upload: 'Carregar',
|
upload: 'Carregar',
|
||||||
files: 'Arquivos',
|
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'
|
wait_countdown_seconds: 'Aguarde {countdown} segundos'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -975,7 +975,7 @@ const he = {
|
|||||||
reupload: 'העלה שוב',
|
reupload: 'העלה שוב',
|
||||||
upload: 'העלאה',
|
upload: 'העלאה',
|
||||||
files: 'קבצים',
|
files: 'קבצים',
|
||||||
signature_is_too_small_please_redraw: 'החתימה קטנה מדי. אנא צייר מחדש.',
|
signature_is_too_small_or_simple_please_redraw: 'החתימה קטנה או פשוטה מדי. אנא חתום מחדש.',
|
||||||
wait_countdown_seconds: 'המתן {countdown} שניות'
|
wait_countdown_seconds: 'המתן {countdown} שניות'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1074,7 +1074,7 @@ const nl = {
|
|||||||
reupload: 'Opnieuw uploaden',
|
reupload: 'Opnieuw uploaden',
|
||||||
upload: 'Uploaden',
|
upload: 'Uploaden',
|
||||||
files: 'Bestanden',
|
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'
|
wait_countdown_seconds: 'Wacht {countdown} seconden'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1172,7 +1172,7 @@ const ar = {
|
|||||||
reupload: 'إعادة التحميل',
|
reupload: 'إعادة التحميل',
|
||||||
upload: 'تحميل',
|
upload: 'تحميل',
|
||||||
files: 'الملفات',
|
files: 'الملفات',
|
||||||
signature_is_too_small_please_redraw: 'التوقيع صغير جدًا. يرجى إعادة الرسم.',
|
signature_is_too_small_or_simple_please_redraw: 'التوقيع صغير جدًا أو بسيط جدًا. يرجى إعادة رسمه.',
|
||||||
wait_countdown_seconds: 'انتظر {countdown} ثانية'
|
wait_countdown_seconds: 'انتظر {countdown} ثانية'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1269,7 +1269,7 @@ const ko = {
|
|||||||
reupload: '다시 업로드',
|
reupload: '다시 업로드',
|
||||||
upload: '업로드',
|
upload: '업로드',
|
||||||
files: '파일',
|
files: '파일',
|
||||||
signature_is_too_small_please_redraw: '서명이 너무 작습니다. 다시 그려주세요.',
|
signature_is_too_small_or_simple_please_redraw: '서명이 너무 작거나 단순합니다. 다시 그려주세요.',
|
||||||
wait_countdown_seconds: '{countdown}초 기다리세요'
|
wait_countdown_seconds: '{countdown}초 기다리세요'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -118,11 +118,17 @@
|
|||||||
:src="attachmentsIndex[modelValue || computedPreviousValue].url"
|
:src="attachmentsIndex[modelValue || computedPreviousValue].url"
|
||||||
class="mx-auto bg-white border border-base-300 rounded max-h-44"
|
class="mx-auto bg-white border border-base-300 rounded max-h-44"
|
||||||
>
|
>
|
||||||
<canvas
|
<div class="relative">
|
||||||
v-show="!modelValue && !computedPreviousValue"
|
<div
|
||||||
ref="canvas"
|
v-if="!isDrawInitials"
|
||||||
class="bg-white border border-base-300 rounded-2xl w-full draw-canvas"
|
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
|
<input
|
||||||
v-if="!isDrawInitials && !modelValue && !computedPreviousValue"
|
v-if="!isDrawInitials && !modelValue && !computedPreviousValue"
|
||||||
id="initials_text_input"
|
id="initials_text_input"
|
||||||
|
|||||||
@@ -292,6 +292,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { IconReload, IconCamera, IconSignature, IconTextSize, IconArrowsDiagonalMinimize2, IconQrcode, IconX } from '@tabler/icons-vue'
|
import { IconReload, IconCamera, IconSignature, IconTextSize, IconArrowsDiagonalMinimize2, IconQrcode, IconX } from '@tabler/icons-vue'
|
||||||
import { cropCanvasAndExportToPNG } from './crop_canvas'
|
import { cropCanvasAndExportToPNG } from './crop_canvas'
|
||||||
|
import { isValidSignatureCanvas } from './validate_signature'
|
||||||
import SignaturePad from 'signature_pad'
|
import SignaturePad from 'signature_pad'
|
||||||
import AppearsOn from './appears_on'
|
import AppearsOn from './appears_on'
|
||||||
import FileDropzone from './dropzone'
|
import FileDropzone from './dropzone'
|
||||||
@@ -598,6 +599,7 @@ export default {
|
|||||||
},
|
},
|
||||||
drawImage (event) {
|
drawImage (event) {
|
||||||
this.remove()
|
this.remove()
|
||||||
|
this.clear()
|
||||||
this.isSignatureStarted = true
|
this.isSignatureStarted = true
|
||||||
|
|
||||||
this.drawOnCanvas(event.target.files[0], this.$refs.canvas)
|
this.drawOnCanvas(event.target.files[0], this.$refs.canvas)
|
||||||
@@ -680,8 +682,14 @@ export default {
|
|||||||
return Promise.resolve({})
|
return Promise.resolve({})
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
if (this.isSignatureStarted && this.pad.toData().length > 0 && !isValidSignatureCanvas(this.pad.toData())) {
|
||||||
cropCanvasAndExportToPNG(this.$refs.canvas, { errorOnTooSmall: true }).then(async (blob) => {
|
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' })
|
const file = new File([blob], 'signature.png', { type: 'image/png' })
|
||||||
|
|
||||||
if (this.dryRun) {
|
if (this.dryRun) {
|
||||||
@@ -717,12 +725,6 @@ export default {
|
|||||||
return resolve(attachment)
|
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
|
||||||
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
|
end
|
||||||
|
|
||||||
def build_bcc_addresses(submission)
|
def build_bcc_addresses(submission)
|
||||||
@@ -110,11 +126,6 @@ class ProcessSubmitterCompletionJob
|
|||||||
bcc.to_s.scan(User::EMAIL_REGEXP)
|
bcc.to_s.scan(User::EMAIL_REGEXP)
|
||||||
end
|
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)
|
def enqueue_next_submitter_request_notification(submitter)
|
||||||
next_submitter_item =
|
next_submitter_item =
|
||||||
submitter.submission.template_submitters.find do |e|
|
submitter.submission.template_submitters.find do |e|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class AccountConfig < ApplicationRecord
|
|||||||
ALLOW_TO_RESUBMIT = 'allow_to_resubmit'
|
ALLOW_TO_RESUBMIT = 'allow_to_resubmit'
|
||||||
ALLOW_TO_DECLINE_KEY = 'allow_to_decline'
|
ALLOW_TO_DECLINE_KEY = 'allow_to_decline'
|
||||||
SUBMITTER_REMINDERS = 'submitter_reminders'
|
SUBMITTER_REMINDERS = 'submitter_reminders'
|
||||||
|
ENFORCE_SIGNING_ORDER_KEY = 'enforce_signing_order'
|
||||||
FORM_COMPLETED_BUTTON_KEY = 'form_completed_button'
|
FORM_COMPLETED_BUTTON_KEY = 'form_completed_button'
|
||||||
FORM_COMPLETED_MESSAGE_KEY = 'form_completed_message'
|
FORM_COMPLETED_MESSAGE_KEY = 'form_completed_message'
|
||||||
FORM_WITH_CONFETTI_KEY = 'form_with_confetti'
|
FORM_WITH_CONFETTI_KEY = 'form_with_confetti'
|
||||||
|
|||||||
@@ -151,6 +151,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% 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>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= render 'compliances' %>
|
<%= render 'compliances' %>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<div class="collapse-content">
|
<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| %>
|
<%= 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.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">
|
<div class="form-control">
|
||||||
<%= ff.label :subject, t('subject'), class: 'label' %>
|
<%= ff.label :subject, t('subject'), class: 'label' %>
|
||||||
<%= ff.text_field :subject, required: true, class: 'base-input', dir: 'auto' %>
|
<%= 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' %>
|
<%= ff.text_area :body, required: true, class: 'base-input w-full py-2', dir: 'auto' %>
|
||||||
</autoresize-textarea>
|
</autoresize-textarea>
|
||||||
</div>
|
</div>
|
||||||
<% if can?(:manage, :reply_to) %>
|
<% if can?(:manage, :reply_to) || can?(:manage, :personalization_advanced) %>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= ff.label :reply_to, t('reply_to'), class: 'label' %>
|
<%= ff.label :reply_to, t('reply_to'), class: 'label' %>
|
||||||
<%= ff.email_field :reply_to, class: 'base-input', dir: 'auto', placeholder: t(:email) %>
|
<%= ff.email_field :reply_to, class: 'base-input', dir: 'auto', placeholder: t(:email) %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="flex items-center justify-between pt-2.5 mx-1">
|
<div class="space-y-3.5">
|
||||||
<span>
|
<div class="flex items-center justify-between mx-1">
|
||||||
<%= t('attach_documents') %>
|
<span>
|
||||||
</span>
|
<%= t('attach_documents') %>
|
||||||
<%= ff.check_box :attach_documents, { checked: ff.object.attach_documents != false, class: 'toggle' }, 'true', 'false' %>
|
</span>
|
||||||
</div>
|
<%= ff.check_box :attach_documents, { checked: ff.object.attach_documents != false, class: 'toggle' }, 'true', 'false' %>
|
||||||
<div class="flex items-center justify-between pb-2.5 mx-1">
|
</div>
|
||||||
<span>
|
<div class="flex items-center justify-between mx-1">
|
||||||
<%= t('attach_audit_log_pdf') %>
|
<span>
|
||||||
</span>
|
<%= t('attach_audit_log_pdf') %>
|
||||||
<%= ff.check_box :attach_audit_log, { checked: ff.object.attach_audit_log != false, class: 'toggle' }, 'true', 'false' %>
|
</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>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="form-control pt-2">
|
<div class="form-control pt-2">
|
||||||
|
|||||||
@@ -13,10 +13,10 @@
|
|||||||
<meta property="og:image" content="">
|
<meta property="og:image" content="">
|
||||||
<meta name="twitter:image" content="">
|
<meta name="twitter:image" content="">
|
||||||
<% else %>
|
<% 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:width" content="800">
|
||||||
<meta property="og:image:height" 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 %>
|
<% end %>
|
||||||
<meta name="twitter:card" content="summary">
|
<meta name="twitter:card" content="summary">
|
||||||
<meta name="twitter:creator" content="@docusealco">
|
<meta name="twitter:creator" content="@docusealco">
|
||||||
|
|||||||
@@ -206,7 +206,7 @@
|
|||||||
<span>
|
<span>
|
||||||
<%= t('send_emails_automatically_on_completion') %>
|
<%= t('send_emails_automatically_on_completion') %>
|
||||||
</span>
|
</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>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="form-control pt-2">
|
<div class="form-control pt-2">
|
||||||
@@ -322,15 +322,17 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-2' }, data: { close_on_submit: false } do |f| %>
|
<% unless current_account.account_configs.exists?(key: AccountConfig::ENFORCE_SIGNING_ORDER_KEY, value: true) %>
|
||||||
<div class="flex items-center pt-4 mt-4 justify-between border-t w-full">
|
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-2' }, data: { close_on_submit: false } do |f| %>
|
||||||
<span>
|
<div class="flex items-center pt-4 mt-4 justify-between border-t w-full">
|
||||||
<%= t('enforce_recipients_order') %>
|
<span>
|
||||||
</span>
|
<%= t('enforce_recipients_order') %>
|
||||||
<%= f.fields_for :preferences, Struct.new(:submitters_order).new(@template.preferences['submitters_order']) do |ff| %>
|
</span>
|
||||||
<%= ff.check_box :submitters_order, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'preserved', '' %>
|
<%= f.fields_for :preferences, Struct.new(:submitters_order).new(@template.preferences['submitters_order']) do |ff| %>
|
||||||
<% end %>
|
<%= ff.check_box :submitters_order, { class: 'toggle', onchange: 'this.form.requestSubmit()' }, 'preserved', '' %>
|
||||||
</div>
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="form-control mt-5 pb-2">
|
<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 %>
|
<%= 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">
|
<div class="flex-wrap space-y-4 md:flex md:flex-nowrap md:space-y-0 md:space-x-10">
|
||||||
<%= render 'shared/settings_nav' %>
|
<%= render 'shared/settings_nav' %>
|
||||||
<div class="md:flex-grow">
|
<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">
|
<h1 class="text-4xl font-bold">
|
||||||
<% if params[:status] == 'archived' %>
|
<% if params[:status] == 'archived' %>
|
||||||
<%= t('archived_users') %>
|
<%= t('archived_users') %>
|
||||||
|
|||||||
+18
-18
@@ -20,6 +20,8 @@ en: &en
|
|||||||
language_ko: 한국어
|
language_ko: 한국어
|
||||||
hi_there: Hi there
|
hi_there: Hi there
|
||||||
thanks: Thanks
|
thanks: Thanks
|
||||||
|
bcc_recipients: BCC recipients
|
||||||
|
always_enforce_signing_order: Always enforce the signing order
|
||||||
edit_per_party: Edit per party
|
edit_per_party: Edit per party
|
||||||
reply_to: Reply to
|
reply_to: Reply to
|
||||||
pending_by_me: Pending by me
|
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_test_mode: Share template with Test mode
|
||||||
share_template_with_all_tenants: Share template with all Tenants
|
share_template_with_all_tenants: Share template with all Tenants
|
||||||
use_following_placeholders_text_: 'Use following placeholders text:'
|
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
|
move_into_folder: Move Into Folder
|
||||||
new_folder_name: New Folder Name
|
new_folder_name: New Folder Name
|
||||||
exit_preview: Exit Preview
|
exit_preview: Exit Preview
|
||||||
@@ -473,8 +475,6 @@ en: &en
|
|||||||
team_accounts: Team Accounts
|
team_accounts: Team Accounts
|
||||||
tenant_account: Tenant account
|
tenant_account: Tenant account
|
||||||
tenant_accounts: Tenant Accounts
|
tenant_accounts: Tenant Accounts
|
||||||
upgrade_plan: Upgrade Plan
|
|
||||||
add_user: Add User
|
|
||||||
impersonate: Impersonate
|
impersonate: Impersonate
|
||||||
loading: Loading
|
loading: Loading
|
||||||
documents: Documents
|
documents: Documents
|
||||||
@@ -745,6 +745,8 @@ en: &en
|
|||||||
read: Read your data
|
read: Read your data
|
||||||
|
|
||||||
es: &es
|
es: &es
|
||||||
|
always_enforce_signing_order: Siempre imponer el orden de firma
|
||||||
|
bcc_recipients: Destinatarios CCO
|
||||||
edit_per_party: Editar por parte
|
edit_per_party: Editar por parte
|
||||||
signed: Firmado
|
signed: Firmado
|
||||||
reply_to: Responder a
|
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_test_mode: Compartir plantilla con el modo de prueba
|
||||||
share_template_with_all_tenants: Compartir plantilla con todos los inquilinos
|
share_template_with_all_tenants: Compartir plantilla con todos los inquilinos
|
||||||
use_following_placeholders_text_: 'Usa los siguientes marcadores de posición:'
|
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
|
move_into_folder: Mover a la carpeta
|
||||||
new_folder_name: Nuevo nombre de la carpeta
|
new_folder_name: Nuevo nombre de la carpeta
|
||||||
exit_preview: Salir de la vista previa
|
exit_preview: Salir de la vista previa
|
||||||
@@ -1200,8 +1202,6 @@ es: &es
|
|||||||
team_accounts: Cuentas de equipo
|
team_accounts: Cuentas de equipo
|
||||||
tenant_account: Cuenta de inquilino
|
tenant_account: Cuenta de inquilino
|
||||||
tenant_accounts: Cuentas de inquilino
|
tenant_accounts: Cuentas de inquilino
|
||||||
upgrade_plan: Actualizar plan
|
|
||||||
add_user: Agregar usuario
|
|
||||||
impersonate: Suplantar
|
impersonate: Suplantar
|
||||||
loading: Cargando
|
loading: Cargando
|
||||||
documents: Documentos
|
documents: Documentos
|
||||||
@@ -1472,6 +1472,8 @@ es: &es
|
|||||||
read: Leer tus datos
|
read: Leer tus datos
|
||||||
|
|
||||||
it: &it
|
it: &it
|
||||||
|
always_enforce_signing_order: Applicare sempre l'ordine di firma
|
||||||
|
bcc_recipients: Destinatari BCC
|
||||||
edit_per_party: Modifica per partito
|
edit_per_party: Modifica per partito
|
||||||
signed: Firmato
|
signed: Firmato
|
||||||
reply_to: Rispondi a
|
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_test_mode: Condividi modello con la modalità di test
|
||||||
share_template_with_all_tenants: Condividi il modello con tutti i tenant
|
share_template_with_all_tenants: Condividi il modello con tutti i tenant
|
||||||
use_following_placeholders_text_: 'Usa i seguenti segnaposto:'
|
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
|
move_into_folder: Sposta nella cartella
|
||||||
new_folder_name: Nuovo nome della cartella
|
new_folder_name: Nuovo nome della cartella
|
||||||
exit_preview: "Esci dall'anteprima"
|
exit_preview: "Esci dall'anteprima"
|
||||||
@@ -1926,8 +1928,6 @@ it: &it
|
|||||||
team_accounts: Account di squadra
|
team_accounts: Account di squadra
|
||||||
tenant_account: Account tenant
|
tenant_account: Account tenant
|
||||||
tenant_accounts: Account tenant
|
tenant_accounts: Account tenant
|
||||||
upgrade_plan: Aggiorna piano
|
|
||||||
add_user: Aggiungi utente
|
|
||||||
impersonate: Impersona
|
impersonate: Impersona
|
||||||
loading: Caricamento in corso
|
loading: Caricamento in corso
|
||||||
documents: Documenti
|
documents: Documenti
|
||||||
@@ -2198,6 +2198,8 @@ it: &it
|
|||||||
read: Leggi i tuoi dati
|
read: Leggi i tuoi dati
|
||||||
|
|
||||||
fr: &fr
|
fr: &fr
|
||||||
|
always_enforce_signing_order: Toujours appliquer l'ordre de signature
|
||||||
|
bcc_recipients: Destinataires en CCI
|
||||||
edit_per_party: Éditer par partie
|
edit_per_party: Éditer par partie
|
||||||
signed: Signé
|
signed: Signé
|
||||||
reply_to: Répondre à
|
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_test_mode: Partager le modèle avec le mode test
|
||||||
share_template_with_all_tenants: Partager le modèle avec tous les locataires
|
share_template_with_all_tenants: Partager le modèle avec tous les locataires
|
||||||
use_following_placeholders_text_: 'Utilisez les espaces réservés suivants:'
|
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
|
move_into_folder: Déplacer dans le dossier
|
||||||
new_folder_name: Nouveau nom du dossier
|
new_folder_name: Nouveau nom du dossier
|
||||||
exit_preview: "Quitter l'aperçu"
|
exit_preview: "Quitter l'aperçu"
|
||||||
@@ -2654,8 +2656,6 @@ fr: &fr
|
|||||||
team_accounts: "Comptes d'équipe"
|
team_accounts: "Comptes d'équipe"
|
||||||
tenant_account: Compte locataire
|
tenant_account: Compte locataire
|
||||||
tenant_accounts: Comptes locataires
|
tenant_accounts: Comptes locataires
|
||||||
upgrade_plan: Mettre à jour le plan
|
|
||||||
add_user: Ajouter un utilisateur
|
|
||||||
impersonate: Usurper
|
impersonate: Usurper
|
||||||
loading: Chargement en cours
|
loading: Chargement en cours
|
||||||
documents: Documents
|
documents: Documents
|
||||||
@@ -2926,6 +2926,8 @@ fr: &fr
|
|||||||
read: Lire vos données
|
read: Lire vos données
|
||||||
|
|
||||||
pt: &pt
|
pt: &pt
|
||||||
|
always_enforce_signing_order: Sempre impor a ordem de assinatura
|
||||||
|
bcc_recipients: Destinatários BCC
|
||||||
edit_per_party: Edita por festa
|
edit_per_party: Edita por festa
|
||||||
signed: Assinado
|
signed: Assinado
|
||||||
reply_to: Responder a
|
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_test_mode: Compartilhar modelo com o modo de teste
|
||||||
share_template_with_all_tenants: Compartilhar modelo com todos os locatários
|
share_template_with_all_tenants: Compartilhar modelo com todos os locatários
|
||||||
use_following_placeholders_text_: 'Use os seguintes textos de substituição:'
|
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
|
move_into_folder: Mover para pasta
|
||||||
new_folder_name: Novo nome da pasta
|
new_folder_name: Novo nome da pasta
|
||||||
exit_preview: Sair da pré-visualização
|
exit_preview: Sair da pré-visualização
|
||||||
@@ -3381,8 +3383,6 @@ pt: &pt
|
|||||||
team_accounts: Contas de equipe
|
team_accounts: Contas de equipe
|
||||||
tenant_account: Conta de locatário
|
tenant_account: Conta de locatário
|
||||||
tenant_accounts: Contas de locatário
|
tenant_accounts: Contas de locatário
|
||||||
upgrade_plan: Fazer upgrade do plano
|
|
||||||
add_user: Adicionar usuário
|
|
||||||
impersonate: Usurpar
|
impersonate: Usurpar
|
||||||
loading: Carregando
|
loading: Carregando
|
||||||
documents: Documentos
|
documents: Documentos
|
||||||
@@ -3653,6 +3653,8 @@ pt: &pt
|
|||||||
read: Ler seus dados
|
read: Ler seus dados
|
||||||
|
|
||||||
de: &de
|
de: &de
|
||||||
|
always_enforce_signing_order: Immer die Reihenfolge der Unterschriften erzwingen
|
||||||
|
bcc_recipients: BCC-Empfänger
|
||||||
edit_per_party: Bearbeiten pro Partei
|
edit_per_party: Bearbeiten pro Partei
|
||||||
signed: Unterschrieben
|
signed: Unterschrieben
|
||||||
reply_to: Antworten auf
|
reply_to: Antworten auf
|
||||||
@@ -4067,7 +4069,7 @@ de: &de
|
|||||||
share_template_with_test_mode: Vorlage mit dem Testmodus teilen
|
share_template_with_test_mode: Vorlage mit dem Testmodus teilen
|
||||||
share_template_with_all_tenants: Vorlage mit allen Mietern teilen
|
share_template_with_all_tenants: Vorlage mit allen Mietern teilen
|
||||||
use_following_placeholders_text_: 'Verwende die folgenden Platzhaltertexte:'
|
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
|
move_into_folder: In Ordner verschieben
|
||||||
new_folder_name: Neuer Ordnername
|
new_folder_name: Neuer Ordnername
|
||||||
exit_preview: Vorschau beenden
|
exit_preview: Vorschau beenden
|
||||||
@@ -4108,8 +4110,6 @@ de: &de
|
|||||||
team_accounts: Teamkonten
|
team_accounts: Teamkonten
|
||||||
tenant_account: Mieterkonto
|
tenant_account: Mieterkonto
|
||||||
tenant_accounts: Mieterkonten
|
tenant_accounts: Mieterkonten
|
||||||
upgrade_plan: Plan upgraden
|
|
||||||
add_user: Benutzer hinzufügen
|
|
||||||
impersonate: Nachahmen
|
impersonate: Nachahmen
|
||||||
loading: Wird geladen
|
loading: Wird geladen
|
||||||
documents: Dokumente
|
documents: Dokumente
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ module ActionMailerConfigsInterceptor
|
|||||||
end
|
end
|
||||||
|
|
||||||
unless Docuseal.multitenant?
|
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
|
if email_configs
|
||||||
message.delivery_method(:smtp, build_smtp_configs_hash(email_configs))
|
message.delivery_method(:smtp, build_smtp_configs_hash(email_configs))
|
||||||
|
|||||||
@@ -196,4 +196,20 @@ module Submissions
|
|||||||
end
|
end
|
||||||
end.exclude?(false)
|
end.exclude?(false)
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
@@ -7,11 +7,15 @@ module Submissions
|
|||||||
CHECK_COMPLETE_TIMEOUT = 90.seconds
|
CHECK_COMPLETE_TIMEOUT = 90.seconds
|
||||||
|
|
||||||
WaitForCompleteTimeout = Class.new(StandardError)
|
WaitForCompleteTimeout = Class.new(StandardError)
|
||||||
|
NotCompletedYet = Class.new(StandardError)
|
||||||
|
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
def call(submitter)
|
def call(submitter)
|
||||||
return [] unless submitter
|
return [] unless submitter
|
||||||
|
|
||||||
|
raise NotCompletedYet unless submitter.completed_at?
|
||||||
|
|
||||||
return submitter.documents if ApplicationRecord.uncached { submitter.document_generation_events.complete.exists? }
|
return submitter.documents if ApplicationRecord.uncached { submitter.document_generation_events.complete.exists? }
|
||||||
|
|
||||||
events =
|
events =
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ module Submitters
|
|||||||
AccountConfig::FORM_PREFILL_SIGNATURE_KEY,
|
AccountConfig::FORM_PREFILL_SIGNATURE_KEY,
|
||||||
AccountConfig::WITH_SIGNATURE_ID,
|
AccountConfig::WITH_SIGNATURE_ID,
|
||||||
AccountConfig::ALLOW_TO_DECLINE_KEY,
|
AccountConfig::ALLOW_TO_DECLINE_KEY,
|
||||||
|
AccountConfig::ENFORCE_SIGNING_ORDER_KEY,
|
||||||
AccountConfig::REQUIRE_SIGNING_REASON_KEY,
|
AccountConfig::REQUIRE_SIGNING_REASON_KEY,
|
||||||
AccountConfig::REUSE_SIGNATURE_KEY,
|
AccountConfig::REUSE_SIGNATURE_KEY,
|
||||||
AccountConfig::ALLOW_TYPED_SIGNATURE,
|
AccountConfig::ALLOW_TYPED_SIGNATURE,
|
||||||
@@ -27,6 +28,7 @@ module Submitters
|
|||||||
with_decline = find_safe_value(configs, AccountConfig::ALLOW_TO_DECLINE_KEY) != false
|
with_decline = find_safe_value(configs, AccountConfig::ALLOW_TO_DECLINE_KEY) != false
|
||||||
with_signature_id = find_safe_value(configs, AccountConfig::WITH_SIGNATURE_ID) == true
|
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
|
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)
|
policy_links = find_safe_value(configs, AccountConfig::POLICY_LINKS_KEY)
|
||||||
|
|
||||||
attrs = {
|
attrs = {
|
||||||
@@ -36,6 +38,7 @@ module Submitters
|
|||||||
reuse_signature:,
|
reuse_signature:,
|
||||||
with_decline:,
|
with_decline:,
|
||||||
policy_links:,
|
policy_links:,
|
||||||
|
enforce_signing_order:,
|
||||||
completed_message:,
|
completed_message:,
|
||||||
require_signing_reason:,
|
require_signing_reason:,
|
||||||
prefill_signature:,
|
prefill_signature:,
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
module Submitters
|
module Submitters
|
||||||
module SubmitValues
|
module SubmitValues
|
||||||
ValidationError = Class.new(StandardError)
|
ValidationError = Class.new(StandardError)
|
||||||
|
RequiredFieldError = Class.new(StandardError)
|
||||||
|
|
||||||
VARIABLE_REGEXP = /\{\{?(\w+)\}\}?/
|
VARIABLE_REGEXP = /\{\{?(\w+)\}\}?/
|
||||||
|
NONEDITABLE_FIELD_TYPES = %w[stamp heading].freeze
|
||||||
|
|
||||||
module_function
|
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?
|
Submissions.update_template_fields!(submitter.submission) if submitter.submission.template_fields.blank?
|
||||||
|
|
||||||
unless submitter.submission_events.exists?(event_type: 'start_form')
|
unless submitter.submission_events.exists?(event_type: 'start_form')
|
||||||
@@ -20,7 +22,7 @@ module Submitters
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
update_submitter!(submitter, params, request)
|
update_submitter!(submitter, params, request, validate_required:)
|
||||||
|
|
||||||
submitter.submission.save!
|
submitter.submission.save!
|
||||||
|
|
||||||
@@ -29,13 +31,13 @@ module Submitters
|
|||||||
submitter
|
submitter
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_submitter!(submitter, params, request)
|
def update_submitter!(submitter, params, request, validate_required: true)
|
||||||
values = normalized_values(params)
|
values = normalized_values(params)
|
||||||
|
|
||||||
submitter.values.merge!(values)
|
submitter.values.merge!(values)
|
||||||
submitter.opened_at ||= Time.current
|
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
|
ApplicationRecord.transaction do
|
||||||
maybe_set_signature_reason!(values, submitter, params)
|
maybe_set_signature_reason!(values, submitter, params)
|
||||||
@@ -49,25 +51,36 @@ module Submitters
|
|||||||
submitter
|
submitter
|
||||||
end
|
end
|
||||||
|
|
||||||
def assign_completed_attributes(submitter, request)
|
def assign_completed_attributes(submitter, request, validate_required: true)
|
||||||
submitter.completed_at = Time.current
|
submitter.completed_at = Time.current
|
||||||
submitter.ip = request.remote_ip
|
submitter.ip = request.remote_ip
|
||||||
submitter.ua = request.user_agent
|
submitter.ua = request.user_agent
|
||||||
|
|
||||||
submitter.values = merge_default_values(submitter)
|
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)
|
formula_values = build_formula_values(submitter)
|
||||||
|
|
||||||
if formula_values.present?
|
if formula_values.present?
|
||||||
submitter.values = submitter.values.merge(formula_values)
|
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
|
end
|
||||||
|
|
||||||
submitter.values = submitter.values.transform_values do |v|
|
submitter.values = submitter.values.transform_values do |v|
|
||||||
v == '{{date}}' ? Time.current.in_time_zone(submitter.account.timezone).to_date.to_s : v
|
v == '{{date}}' ? Time.current.in_time_zone(submitter.account.timezone).to_date.to_s : v
|
||||||
end
|
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
|
submitter
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -188,37 +201,67 @@ module Submitters
|
|||||||
with_time:)
|
with_time:)
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_remove_condition_values(submitter)
|
def maybe_remove_condition_values(submitter, required_field_uuids_acc: nil)
|
||||||
fields_uuid_index = submitter.submission.template_fields.index_by { |e| e['uuid'] }
|
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 =
|
attachments_index =
|
||||||
Submissions.filtered_conditions_schema(submitter.submission).index_by { |i| i['attachment_uuid'] }
|
if has_document_conditions
|
||||||
|
Submissions.filtered_conditions_schema(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) }
|
|
||||||
end
|
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'])
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
submitter.values
|
submitter.values
|
||||||
end
|
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)
|
def field_conditions_other_submitter?(submitter, field, fields_uuid_index)
|
||||||
|
return false if field['conditions'].blank?
|
||||||
|
|
||||||
field['conditions'].to_a.any? do |c|
|
field['conditions'].to_a.any? do |c|
|
||||||
fields_uuid_index.dig(c['field_uuid'], 'submitter_uuid') != submitter.uuid
|
fields_uuid_index.dig(c['field_uuid'], 'submitter_uuid') != submitter.uuid
|
||||||
end
|
end
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
+38
-10
@@ -4,21 +4,49 @@ module SigningFormHelper
|
|||||||
module_function
|
module_function
|
||||||
|
|
||||||
def draw_canvas
|
def draw_canvas
|
||||||
page.find('canvas').click([], { x: 150, y: 100 })
|
|
||||||
page.execute_script <<~JS
|
page.execute_script <<~JS
|
||||||
const canvas = document.getElementsByTagName('canvas')[0];
|
const canvas = document.getElementsByTagName('canvas')[0];
|
||||||
const ctx = canvas.getContext('2d');
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
|
||||||
ctx.beginPath();
|
const startX = rect.left + 50;
|
||||||
ctx.moveTo(150, 100);
|
const startY = rect.top + 100;
|
||||||
ctx.lineTo(450, 100);
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
ctx.beginPath();
|
const amplitude = 20;
|
||||||
ctx.moveTo(150, 100);
|
const wavelength = 30;
|
||||||
ctx.lineTo(150, 150);
|
const length = 300;
|
||||||
ctx.stroke();
|
|
||||||
|
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
|
JS
|
||||||
|
|
||||||
sleep 0.5
|
sleep 0.5
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -360,6 +360,19 @@ RSpec.describe 'Signing Form', type: :system do
|
|||||||
expect(field_value(submitter, 'Signature')).to be_present
|
expect(field_value(submitter, 'Signature')).to be_present
|
||||||
end
|
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
|
it 'completes the form if the canvas is typed' do
|
||||||
visit submit_form_path(slug: submitter.slug)
|
visit submit_form_path(slug: submitter.slug)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user