pull/349/head
Pete Matsyburka 1 year ago
parent 91d44a3e31
commit 94d5ab4ace

@ -14,7 +14,8 @@ 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::WITH_SIGNATURE_ID AccountConfig::WITH_SIGNATURE_ID,
AccountConfig::REQUIRE_SIGNING_REASON_KEY
].freeze ].freeze
InvalidKey = Class.new(StandardError) InvalidKey = Class.new(StandardError)

@ -23,6 +23,7 @@ safeRegisterElement('submission-form', class extends HTMLElement {
dryRun: this.dataset.dryRun === 'true', dryRun: this.dataset.dryRun === 'true',
expand: ['true', 'false'].includes(this.dataset.expand) ? this.dataset.expand === 'true' : null, expand: ['true', 'false'].includes(this.dataset.expand) ? this.dataset.expand === 'true' : null,
withSignatureId: this.dataset.withSignatureId === 'true', withSignatureId: this.dataset.withSignatureId === 'true',
requireSigningReason: this.dataset.requireSigningReason === 'true',
withConfetti: this.dataset.withConfetti !== 'false', withConfetti: this.dataset.withConfetti !== 'false',
withDisclosure: this.dataset.withDisclosure === 'true', withDisclosure: this.dataset.withDisclosure === 'true',
withTypedSignature: this.dataset.withTypedSignature !== 'false', withTypedSignature: this.dataset.withTypedSignature !== 'false',

@ -75,7 +75,7 @@
ID: {{ signature.uuid }} ID: {{ signature.uuid }}
</div> </div>
<div> <div>
{{ t('reason') }}: {{ t('digitally_signed_by') }} {{ submitter.name }} {{ t('reason') }}: {{ values[field.preferences?.reason_field_uuid] || t('digitally_signed_by') }} {{ submitter.name }}
<template v-if="submitter.email"> <template v-if="submitter.email">
&lt;{{ submitter.email }}&gt; &lt;{{ submitter.email }}&gt;
</template> </template>
@ -258,6 +258,11 @@ export default {
required: false, required: false,
default: '' default: ''
}, },
values: {
type: Object,
required: false,
default: () => ({})
},
isActive: { isActive: {
type: Boolean, type: Boolean,
required: false, required: false,

@ -18,6 +18,7 @@
<FieldArea <FieldArea
:ref="setAreaRef" :ref="setAreaRef"
v-model="values[field.uuid]" v-model="values[field.uuid]"
:values="values"
:field="field" :field="field"
:area="area" :area="area"
:submittable="true" :submittable="true"

@ -327,17 +327,20 @@
ref="currentStep" ref="currentStep"
:key="currentField.uuid" :key="currentField.uuid"
v-model="values[currentField.uuid]" v-model="values[currentField.uuid]"
:reason="values[currentField.preferences?.reason_field_uuid]"
:field="currentField" :field="currentField"
:previous-value="previousSignatureValueFor(currentField) || previousSignatureValue" :previous-value="previousSignatureValueFor(currentField) || previousSignatureValue"
:with-typed-signature="withTypedSignature" :with-typed-signature="withTypedSignature"
:remember-signature="rememberSignature" :remember-signature="rememberSignature"
:attachments-index="attachmentsIndex" :attachments-index="attachmentsIndex"
:require-signing-reason="requireSigningReason"
:button-text="buttonText" :button-text="buttonText"
:dry-run="dryRun" :dry-run="dryRun"
:with-disclosure="withDisclosure" :with-disclosure="withDisclosure"
:with-qr-button="withQrButton" :with-qr-button="withQrButton"
:submitter="submitter" :submitter="submitter"
:show-field-names="showFieldNames" :show-field-names="showFieldNames"
@update:reason="values[currentField.preferences?.reason_field_uuid] = $event"
@attached="attachments.push($event)" @attached="attachments.push($event)"
@start="scrollIntoField(currentField)" @start="scrollIntoField(currentField)"
@minimize="isFormVisible = false" @minimize="isFormVisible = false"
@ -549,6 +552,11 @@ export default {
required: false, required: false,
default: '-80px' default: '-80px'
}, },
requireSigningReason: {
type: Boolean,
required: false,
default: false
},
canSendEmail: { canSendEmail: {
type: Boolean, type: Boolean,
required: false, required: false,

@ -6,6 +6,14 @@ const en = {
signature: 'Signature', signature: 'Signature',
initials: 'Initials', initials: 'Initials',
drawn_signature_on_a_touchscreen_device: 'Drawn signature on a touchscreen device', drawn_signature_on_a_touchscreen_device: 'Drawn signature on a touchscreen device',
approved: 'Approved',
reviewed: 'Reviewed',
other: 'Other',
authored_by_me: 'Authored by me',
approved_by: 'Approved by',
reviewed_by: 'Reviewed by',
authored_by: 'Authored by',
select_a_reason: 'Select a reason',
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Scan the QR code with the camera app to open the form on mobile and draw your signature', scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: 'Scan the QR code with the camera app to open the form on mobile and draw your signature',
date: 'Date', date: 'Date',
number: 'Number', number: 'Number',
@ -77,6 +85,14 @@ const en = {
} }
const es = { const es = {
approved: 'Aprobado',
reviewed: 'Revisado',
other: 'Otro',
authored_by_me: 'Escrito por mí',
approved_by: 'Aprobado por',
reviewed_by: 'Revisado por',
authored_by: 'Escrito por',
select_a_reason: 'Selecciona una razón',
value_is_invalid: 'El valor no es válido', value_is_invalid: 'El valor no es válido',
verification_code_is_invalid: 'El código de verificación no es válido', verification_code_is_invalid: 'El código de verificación no es válido',
drawn_signature_on_a_touchscreen_device: 'Firma dibujada en un dispositivo con pantalla táctil', drawn_signature_on_a_touchscreen_device: 'Firma dibujada en un dispositivo con pantalla táctil',
@ -154,6 +170,14 @@ const es = {
} }
const it = { const it = {
approved: 'Approvato',
reviewed: 'Revisionato',
other: 'Altro',
authored_by_me: 'Creato da me',
approved_by: 'Approvato da',
reviewed_by: 'Revisionato da',
authored_by: 'Creato da',
select_a_reason: 'Seleziona una ragione',
value_is_invalid: 'Il valore non è valido', value_is_invalid: 'Il valore non è valido',
verification_code_is_invalid: 'Il codice di verifica non è valido', verification_code_is_invalid: 'Il codice di verifica non è valido',
drawn_signature_on_a_touchscreen_device: 'Firma disegnata su un dispositivo con schermo tattile', drawn_signature_on_a_touchscreen_device: 'Firma disegnata su un dispositivo con schermo tattile',
@ -231,6 +255,14 @@ const it = {
} }
const de = { const de = {
approved: 'Genehmigt',
reviewed: 'Überprüft',
other: 'Andere',
authored_by_me: 'Von mir verfasst',
approved_by: 'Genehmigt von',
reviewed_by: 'Überprüft von',
authored_by: 'Verfasst von',
select_a_reason: 'Grund auswählen',
value_is_invalid: 'Wert ist ungültig', value_is_invalid: 'Wert ist ungültig',
verification_code_is_invalid: 'Bestätigungscode ist ungültig', verification_code_is_invalid: 'Bestätigungscode ist ungültig',
drawn_signature_on_a_touchscreen_device: 'Gezeichnete Unterschrift auf einem Touchscreen-Gerät', drawn_signature_on_a_touchscreen_device: 'Gezeichnete Unterschrift auf einem Touchscreen-Gerät',
@ -308,6 +340,14 @@ const de = {
} }
const fr = { const fr = {
approved: 'Approuvé',
reviewed: 'Révisé',
other: 'Autre',
authored_by_me: 'Rédigé par moi',
approved_by: 'Approuvé par',
reviewed_by: 'Révisé par',
authored_by: 'Rédigé par',
select_a_reason: 'Sélectionnez une raison',
value_is_invalid: 'La valeur est invalide', value_is_invalid: 'La valeur est invalide',
verification_code_is_invalid: 'Le code de vérification est invalide', verification_code_is_invalid: 'Le code de vérification est invalide',
drawn_signature_on_a_touchscreen_device: 'Signature dessinée sur un appareil à écran tactile', drawn_signature_on_a_touchscreen_device: 'Signature dessinée sur un appareil à écran tactile',
@ -385,6 +425,14 @@ const fr = {
} }
const pl = { const pl = {
approved: 'Zaakceptowany',
reviewed: 'Przejrzany',
other: 'Inny',
authored_by_me: 'Napisane przeze mnie',
approved_by: 'Zaakceptowany przez',
reviewed_by: 'Przejrzany przez',
authored_by: 'Napisane przez',
select_a_reason: 'Wybierz powód',
value_is_invalid: 'Wartość jest nieprawidłowa', value_is_invalid: 'Wartość jest nieprawidłowa',
verification_code_is_invalid: 'Kod weryfikacyjny jest nieprawidłowy', verification_code_is_invalid: 'Kod weryfikacyjny jest nieprawidłowy',
drawn_signature_on_a_touchscreen_device: 'Podpis odręczny na urządzeniu z ekranem dotykowym', drawn_signature_on_a_touchscreen_device: 'Podpis odręczny na urządzeniu z ekranem dotykowym',
@ -462,6 +510,14 @@ const pl = {
} }
const uk = { const uk = {
approved: 'Затверджено',
reviewed: 'Переглянуто',
other: 'Інше',
authored_by_me: 'Авторство моє',
approved_by: 'Затверджено',
reviewed_by: 'Переглянуто',
authored_by: 'Автор',
select_a_reason: 'Виберіть причину',
value_is_invalid: 'Значення є неправильним', value_is_invalid: 'Значення є неправильним',
verification_code_is_invalid: 'Код підтвердження є неправильним', verification_code_is_invalid: 'Код підтвердження є неправильним',
drawn_signature_on_a_touchscreen_device: 'Підпис на сенсорному пристрої', drawn_signature_on_a_touchscreen_device: 'Підпис на сенсорному пристрої',
@ -539,6 +595,14 @@ const uk = {
} }
const cs = { const cs = {
approved: 'Schváleno',
reviewed: 'Zkontrolováno',
other: 'Jiné',
authored_by_me: 'Autorem jsem já',
approved_by: 'Schváleno kým',
reviewed_by: 'Zkontrolováno kým',
authored_by: 'Autorem',
select_a_reason: 'Vyberte důvod',
value_is_invalid: 'Hodnota je neplatná', value_is_invalid: 'Hodnota je neplatná',
verification_code_is_invalid: 'Ověřovací kód je neplatný', verification_code_is_invalid: 'Ověřovací kód je neplatný',
drawn_signature_on_a_touchscreen_device: 'Namalovaný podpis na dotykovém zařízení', drawn_signature_on_a_touchscreen_device: 'Namalovaný podpis na dotykovém zařízení',
@ -616,6 +680,14 @@ const cs = {
} }
const pt = { const pt = {
approved: 'Aprovado',
reviewed: 'Revisado',
other: 'Outro',
authored_by_me: 'Autorizado por mim',
approved_by: 'Aprovado por',
reviewed_by: 'Revisado por',
authored_by: 'Autorizado por',
select_a_reason: 'Selecione um motivo',
value_is_invalid: 'Valor é inválido', value_is_invalid: 'Valor é inválido',
verification_code_is_invalid: 'Código de verificação é inválido', verification_code_is_invalid: 'Código de verificação é inválido',
drawn_signature_on_a_touchscreen_device: 'Assinatura desenhada em um dispositivo com tela sensível ao toque', drawn_signature_on_a_touchscreen_device: 'Assinatura desenhada em um dispositivo com tela sensível ao toque',
@ -693,6 +765,14 @@ const pt = {
} }
const he = { const he = {
approved: 'מאושר',
reviewed: 'נסקר',
other: 'אחר',
authored_by_me: 'נכתב על ידי',
approved_by: 'אושר על ידי',
reviewed_by: 'נסקר על ידי',
authored_by: 'נכתב על ידי',
select_a_reason: 'בחר סיבה',
value_is_invalid: 'ערך לא תקין', value_is_invalid: 'ערך לא תקין',
verification_code_is_invalid: 'קוד האימות אינו תקין', verification_code_is_invalid: 'קוד האימות אינו תקין',
drawn_signature_on_a_touchscreen_device: 'חתימה שנוצרה במכשיר עם מסך מגע', drawn_signature_on_a_touchscreen_device: 'חתימה שנוצרה במכשיר עם מסך מגע',
@ -771,6 +851,14 @@ const he = {
} }
const nl = { const nl = {
approved: 'Goedgekeurd',
reviewed: 'Beoordeeld',
other: 'Anders',
authored_by_me: 'Door mij geschreven',
approved_by: 'Goedgekeurd door',
reviewed_by: 'Beoordeeld door',
authored_by: 'Geschreven door',
select_a_reason: 'Selecteer een reden',
value_is_invalid: 'Waarde is ongeldig', value_is_invalid: 'Waarde is ongeldig',
verification_code_is_invalid: 'Verificatiecode is ongeldig', verification_code_is_invalid: 'Verificatiecode is ongeldig',
drawn_signature_on_a_touchscreen_device: 'Getekende handtekening op een apparaat met een touchscreen', drawn_signature_on_a_touchscreen_device: 'Getekende handtekening op een apparaat met een touchscreen',
@ -849,6 +937,14 @@ const nl = {
} }
const ar = { const ar = {
approved: 'موافق عليه',
reviewed: 'تمت مراجعته',
other: 'آخر',
authored_by_me: 'مؤلف بواسطتي',
approved_by: 'موافق عليه من قبل',
reviewed_by: 'مراجع من قبل',
authored_by: 'مؤلف من قبل',
select_a_reason: 'اختر سببًا',
value_is_invalid: 'القيمة غير صالحة', value_is_invalid: 'القيمة غير صالحة',
verification_code_is_invalid: 'رمز التحقق غير صالح', verification_code_is_invalid: 'رمز التحقق غير صالح',
drawn_signature_on_a_touchscreen_device: 'توقيع مرسوم على جهاز بشاشة تعمل باللمس', drawn_signature_on_a_touchscreen_device: 'توقيع مرسوم على جهاز بشاشة تعمل باللمس',
@ -926,6 +1022,14 @@ const ar = {
} }
const ko = { const ko = {
approved: '승인됨',
reviewed: '검토됨',
other: '기타',
authored_by_me: '내가 작성한',
approved_by: '승인자',
reviewed_by: '검토자',
authored_by: '작성자',
select_a_reason: '이유 선택',
drawn_signature_on_a_touchscreen_device: '터치스크린 장치에서 그린 서명', drawn_signature_on_a_touchscreen_device: '터치스크린 장치에서 그린 서명',
scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: '카메라 앱으로 QR 코드를 스캔하여 모바일에서 양식을 열고 서명을 그리세요', scan_the_qr_code_with_the_camera_app_to_open_the_form_on_mobile_and_draw_your_signature: '카메라 앱으로 QR 코드를 스캔하여 모바일에서 양식을 열고 서명을 그리세요',
by_clicking_you_agree_to_the: '"{button}"를 클릭함으로써, 다음에 동의하게 됩니다', by_clicking_you_agree_to_the: '"{button}"를 클릭함으로써, 다음에 동의하게 됩니다',

@ -194,6 +194,48 @@
type="text" type="text"
@input="updateWrittenSignature" @input="updateWrittenSignature"
> >
<select
v-if="requireSigningReason && !isOtherReason"
class="select base-input !text-2xl w-full mt-6 text-center"
required
:name="`values[${field.preferences.reason_field_uuid}]`"
@change="$event.target.value === 'other' ? [reason = '', isOtherReason = true] : $emit('update:reason', $event.target.value)"
>
<option
value=""
disabled
:selected="!reason"
>
{{ t('select_a_reason') }}
</option>
<option
v-for="(label, option) in defaultReasons"
:key="option"
:value="option"
:selected="reason === option"
>
{{ label }}
</option>
<option value="other">
{{ t('other') }}
</option>
</select>
<input
v-if="requireSigningReason && isOtherReason"
class="base-input !text-2xl w-full mt-6"
required
:name="`values[${field.preferences.reason_field_uuid}]`"
:placeholder="t('type_here_')"
:value="reason"
type="text"
@input="$emit('update:reason', $event.target.value)"
>
<input
v-if="requireSigningReason"
hidden
name="with_reason"
:value="field.preferences.reason_field_uuid"
>
<div <div
v-if="isShowQr" v-if="isShowQr"
dir="auto" dir="auto"
@ -231,6 +273,7 @@ import { cropCanvasAndExportToPNG } from './crop_canvas'
import SignaturePad from 'signature_pad' import SignaturePad from 'signature_pad'
import AppearsOn from './appears_on' import AppearsOn from './appears_on'
import MarkdownContent from './markdown_content' import MarkdownContent from './markdown_content'
import { v4 } from 'uuid'
let isFontLoaded = false let isFontLoaded = false
@ -255,6 +298,11 @@ export default {
type: Object, type: Object,
required: true required: true
}, },
requireSigningReason: {
type: Boolean,
required: false,
default: false
},
submitter: { submitter: {
type: Object, type: Object,
required: true required: true
@ -304,17 +352,23 @@ export default {
required: false, required: false,
default: '' default: ''
}, },
reason: {
type: String,
required: false,
default: ''
},
modelValue: { modelValue: {
type: String, type: String,
required: false, required: false,
default: '' default: ''
} }
}, },
emits: ['attached', 'update:model-value', 'start', 'minimize'], emits: ['attached', 'update:model-value', 'start', 'minimize', 'update:reason'],
data () { data () {
return { return {
isSignatureStarted: !!this.previousValue, isSignatureStarted: !!this.previousValue,
isShowQr: false, isShowQr: false,
isOtherReason: false,
isUsePreviousValue: true, isUsePreviousValue: true,
isTextSignature: this.field.preferences?.format === 'typed', isTextSignature: this.field.preferences?.format === 'typed',
uploadImageInputKey: Math.random().toString() uploadImageInputKey: Math.random().toString()
@ -324,6 +378,13 @@ export default {
submitterSlug () { submitterSlug () {
return this.submitter.slug return this.submitter.slug
}, },
defaultReasons () {
return {
[this.t('approved_by')]: this.t('approved'),
[this.t('reviewed_by')]: this.t('reviewed'),
[this.t('authored_by')]: this.t('authored_by_me')
}
},
computedPreviousValue () { computedPreviousValue () {
if (this.isUsePreviousValue) { if (this.isUsePreviousValue) {
return this.previousValue return this.previousValue
@ -332,6 +393,13 @@ export default {
} }
} }
}, },
created () {
if (this.requireSigningReason) {
this.field.preferences ||= {}
this.field.preferences.reason_field_uuid ||= v4()
this.isOtherReason = this.reason && !this.defaultReasons[this.reason]
}
},
async mounted () { async mounted () {
this.$nextTick(() => { this.$nextTick(() => {
if (this.$refs.canvas) { if (this.$refs.canvas) {

@ -39,6 +39,7 @@ class AccountConfig < ApplicationRecord
FORCE_SSO_AUTH_KEY = 'force_sso_auth' FORCE_SSO_AUTH_KEY = 'force_sso_auth'
FLATTEN_RESULT_PDF_KEY = 'flatten_result_pdf' FLATTEN_RESULT_PDF_KEY = 'flatten_result_pdf'
WITH_SIGNATURE_ID = 'with_signature_id' WITH_SIGNATURE_ID = 'with_signature_id'
REQUIRE_SIGNING_REASON_KEY = 'require_signing_reason'
DEFAULT_VALUES = { DEFAULT_VALUES = {
SUBMITTER_INVITATION_EMAIL_KEY => { SUBMITTER_INVITATION_EMAIL_KEY => {

@ -63,6 +63,18 @@
</div> </div>
<% end %> <% end %>
<% end %> <% end %>
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::REQUIRE_SIGNING_REASON_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>
Require signing reason
</span>
<%= f.check_box :value, class: 'toggle', checked: account_config.value, onchange: 'this.form.requestSubmit()' %>
</div>
<% end %>
<% end %>
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ALLOW_TYPED_SIGNATURE) %> <% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ALLOW_TYPED_SIGNATURE) %>
<% if can?(:manage, account_config) %> <% if can?(:manage, account_config) %>
<%= form_for account_config, url: account_configs_path, method: :post do |f| %> <%= form_for account_config, url: account_configs_path, method: :post do |f| %>

@ -11,7 +11,7 @@
ID: <%= attachment.uuid %> ID: <%= attachment.uuid %>
</div> </div>
<div> <div>
<%= t('reason') %>: <%= t('digitally_signed_by') %> <%= submitter.name %> <%= t('reason') %>: <%= submitter.values[field.dig('preferences', 'reason_field_uuid')].presence || t('digitally_signed_by') %> <%= submitter.name %>
<% if submitter.email %> <% if submitter.email %>
&lt;<%= submitter.email %>&gt; &lt;<%= submitter.email %>&gt;
<% end %> <% end %>

@ -1,3 +1,3 @@
<% data_attachments = attachments_index.values.select { |e| e.record_id == submitter.id }.to_json(only: %i[uuid created_at], methods: %i[url filename content_type]) %> <% data_attachments = attachments_index.values.select { |e| e.record_id == submitter.id }.to_json(only: %i[uuid created_at], methods: %i[url filename content_type]) %>
<% data_fields = (submitter.submission.template_fields || submitter.submission.template.fields).select { |f| f['submitter_uuid'] == submitter.uuid }.to_json %> <% data_fields = (submitter.submission.template_fields || submitter.submission.template.fields).select { |f| f['submitter_uuid'] == submitter.uuid }.to_json %>
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-with-signature-id="<%= configs[:with_signature_id] %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'] %>" data-completed-message="<%= configs[:completed_message].to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(submitter.submission.account) %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>" data-previous-signature-value="<%= local_assigns[:signature_attachment]&.uuid %>" data-remember-signature="<%= configs[:prefill_signature] %>" data-dry-run="<%= local_assigns[:dry_run] %>" data-expand="<%= local_assigns[:expand] %>" data-scroll-padding="<%= local_assigns[:scroll_padding] %>"></submission-form> <submission-form data-is-demo="<%= Docuseal.demo? %>" data-require-signing-reason="<%= configs[:require_signing_reason] %>" data-with-signature-id="<%= configs[:with_signature_id] %>" data-with-confetti="<%= configs[:with_confetti] %>" data-completed-redirect-url="<%= submitter.preferences['completed_redirect_url'] %>" data-completed-message="<%= configs[:completed_message].to_json %>" data-completed-button="<%= configs[:completed_button].to_json %>" data-go-to-last="<%= submitter.preferences.key?('go_to_last') ? submitter.preferences['go_to_last'] : submitter.opened_at? %>" data-submitter="<%= submitter.to_json(only: %i[uuid slug name phone email]) %>" data-can-send-email="<%= Accounts.can_send_emails?(submitter.submission.account) %>" data-attachments="<%= data_attachments %>" data-fields="<%= data_fields %>" data-values="<%= submitter.values.to_json %>" data-with-typed-signature="<%= configs[:with_typed_signature] %>" data-previous-signature-value="<%= local_assigns[:signature_attachment]&.uuid %>" data-remember-signature="<%= configs[:prefill_signature] %>" data-dry-run="<%= local_assigns[:dry_run] %>" data-expand="<%= local_assigns[:expand] %>" data-scroll-padding="<%= local_assigns[:scroll_padding] %>"></submission-form>

@ -2,6 +2,7 @@
<% content_for(:html_description, "#{@submitter.account.name} has invited you to fill and sign documents online effortlessly with a secure, fast, and user-friendly digital document signing solution.") %> <% content_for(:html_description, "#{@submitter.account.name} has invited you to fill and sign documents online effortlessly with a secure, fast, and user-friendly digital document signing solution.") %>
<% fields_index = Templates.build_field_areas_index(@submitter.submission.template_fields || @submitter.submission.template.fields) %> <% fields_index = Templates.build_field_areas_index(@submitter.submission.template_fields || @submitter.submission.template.fields) %>
<% values = @submitter.submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %> <% values = @submitter.submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
<% submitters_index = @submitter.submission.submitters.index_by(&:uuid) %>
<% page_blob_struct = Struct.new(:url, :metadata, keyword_init: true) %> <% page_blob_struct = Struct.new(:url, :metadata, keyword_init: true) %>
<div style="max-height: -webkit-fill-available;"> <div style="max-height: -webkit-fill-available;">
<div id="scrollbox"> <div id="scrollbox">
@ -28,7 +29,7 @@
<% next if field['redacted'] && field['submitter_uuid'] != @submitter.uuid %> <% next if field['redacted'] && field['submitter_uuid'] != @submitter.uuid %>
<% next if value == '{{date}}' && field['submitter_uuid'] != @submitter.uuid %> <% next if value == '{{date}}' && field['submitter_uuid'] != @submitter.uuid %>
<% next if field.dig('preferences', 'formula').present? && field['submitter_uuid'] == @submitter.uuid %> <% next if field.dig('preferences', 'formula').present? && field['submitter_uuid'] == @submitter.uuid %>
<%= render 'submissions/value', area:, field:, attachments_index: @attachments_index, value:, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: @submitter, with_signature_id: @form_configs[:with_signature_id] %> <%= render 'submissions/value', area:, field:, attachments_index: @attachments_index, value:, locale: @submitter.account.locale, timezone: @submitter.account.timezone, submitter: submitters_index[field['submitter_uuid']], with_signature_id: @form_configs[:with_signature_id] %>
<% end %> <% end %>
</div> </div>
</div> </div>

@ -184,7 +184,7 @@ module Submissions
canvas.font(FONT_NAME, size: font_size) canvas.font(FONT_NAME, size: font_size)
case field['type'] case field['type']
when ->(type) { type == 'signature' && with_signature_id } when ->(type) { type == 'signature' && (with_signature_id || field.dig('preferences', 'reason_field_uuid')) }
attachment = submitter.attachments.find { |a| a.uuid == value } attachment = submitter.attachments.find { |a| a.uuid == value }
attachments_data_cache[attachment.uuid] ||= attachment.download attachments_data_cache[attachment.uuid] ||= attachment.download
@ -207,8 +207,10 @@ module Submissions
break if id_string.length < 8 break if id_string.length < 8
end end
reason_value = submitter.values[field.dig('preferences', 'reason_field_uuid')].presence
reason_string = reason_string =
"#{I18n.t('reason')}: #{I18n.t('digitally_signed_by')} " \ "#{I18n.t('reason')}: #{reason_value || I18n.t('digitally_signed_by')} " \
"#{submitter.name}#{submitter.email.present? ? " <#{submitter.email}>" : ''}\n" \ "#{submitter.name}#{submitter.email.present? ? " <#{submitter.email}>" : ''}\n" \
"#{I18n.l(attachment.created_at.in_time_zone(submitter.account.timezone), "#{I18n.l(attachment.created_at.in_time_zone(submitter.account.timezone),
format: :long, locale: submitter.account.locale)} " \ format: :long, locale: submitter.account.locale)} " \

@ -7,6 +7,7 @@ module Submitters
AccountConfig::FORM_WITH_CONFETTI_KEY, AccountConfig::FORM_WITH_CONFETTI_KEY,
AccountConfig::FORM_PREFILL_SIGNATURE_KEY, AccountConfig::FORM_PREFILL_SIGNATURE_KEY,
AccountConfig::WITH_SIGNATURE_ID, AccountConfig::WITH_SIGNATURE_ID,
AccountConfig::REQUIRE_SIGNING_REASON_KEY,
AccountConfig::ALLOW_TYPED_SIGNATURE].freeze AccountConfig::ALLOW_TYPED_SIGNATURE].freeze
module_function module_function
@ -20,11 +21,13 @@ module Submitters
with_confetti = find_safe_value(configs, AccountConfig::FORM_WITH_CONFETTI_KEY) != false with_confetti = find_safe_value(configs, AccountConfig::FORM_WITH_CONFETTI_KEY) != false
prefill_signature = find_safe_value(configs, AccountConfig::FORM_PREFILL_SIGNATURE_KEY) != false prefill_signature = find_safe_value(configs, AccountConfig::FORM_PREFILL_SIGNATURE_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
attrs = { completed_button:, attrs = { completed_button:,
with_typed_signature:, with_typed_signature:,
with_confetti:, with_confetti:,
completed_message:, completed_message:,
require_signing_reason:,
prefill_signature:, prefill_signature:,
with_signature_id: } with_signature_id: }

@ -35,6 +35,7 @@ module Submitters
assign_completed_attributes(submitter, request) if params[:completed] == 'true' assign_completed_attributes(submitter, request) if params[:completed] == 'true'
ApplicationRecord.transaction do ApplicationRecord.transaction do
maybe_set_signature_reason!(values, submitter, params)
validate_values!(values, submitter, params, request) validate_values!(values, submitter, params, request)
SubmissionEvents.create_with_tracking_data(submitter, 'complete_form', request) if params[:completed] == 'true' SubmissionEvents.create_with_tracking_data(submitter, 'complete_form', request) if params[:completed] == 'true'
@ -59,6 +60,31 @@ module Submitters
submitter submitter
end end
def maybe_set_signature_reason!(values, submitter, params)
return if params[:with_reason].blank?
reason_field_uuid = params[:with_reason]
signature_field_uuid = values.except(reason_field_uuid).keys.first
signature_field = submitter.submission.template_fields.find { |e| e['uuid'] == signature_field_uuid }
signature_field['preferences'] ||= {}
signature_field['preferences']['reason_field_uuid'] = reason_field_uuid
unless submitter.submission.template_fields.find { |e| e['uuid'] == reason_field_uuid }
reason_field = { 'type' => 'text',
'uuid' => reason_field_uuid,
'name' => I18n.t(:reason),
'readonly' => true,
'submitter_uuid' => submitter.uuid }
submitter.submission.template_fields.insert(submitter.submission.template_fields.index(signature_field) + 1,
reason_field)
end
submitter.submission.save!
end
def normalized_values(params) def normalized_values(params)
params.fetch(:values, {}).to_unsafe_h.transform_values do |v| params.fetch(:values, {}).to_unsafe_h.transform_values do |v|
if params[:cast_boolean] == 'true' if params[:cast_boolean] == 'true'

Loading…
Cancel
Save