Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 42371d7ce3 | |||
| 4116746be3 | |||
| b2b2856ac0 | |||
| b65bca213f | |||
| 7a72e2cbb7 | |||
| 3a73398a1e | |||
| cce20d9230 | |||
| 8b72c91951 | |||
| b0d8e47a75 | |||
| 85824faa2b | |||
| 8a507339af | |||
| 4636849118 | |||
| cc52cae9fc | |||
| b6bb4d2c8b | |||
| 5032676bd2 | |||
| 146b37f8cd | |||
| 47b3a2d09e | |||
| f2306b334b | |||
| 3affa65571 | |||
| 6f3be16826 | |||
| 07ce76537b | |||
| a1fb38b8cb | |||
| 1b52a99662 | |||
| f5facfa6b9 | |||
| 21c6144dc6 | |||
| 2c155f0e42 | |||
| 33cc0a8ab5 | |||
| 1e65a7e52b | |||
| 14e2dbe1aa | |||
| a823a6ac56 | |||
| cfc4e001ba | |||
| 496cc855f6 | |||
| bfb2db07a5 | |||
| 9f4f868246 | |||
| f91f02f786 | |||
| 1da72e3e7a | |||
| 2e2c0e087d | |||
| 0c72f58ab0 | |||
| d9d969cb79 | |||
| 8d685d1f73 | |||
| e406620044 | |||
| a315674421 | |||
| 8d953f210f | |||
| 7439ef07ad | |||
| fc4b4de6ed | |||
| 96e4733f87 | |||
| 8b6731a289 | |||
| f93ac68e9c | |||
| 439df4879c | |||
| 83b6ce8574 | |||
| 6ea07bc13b | |||
| a15624690b | |||
| 974bb85fb7 | |||
| 5dc80601b2 | |||
| df2e53bc40 |
@@ -0,0 +1,58 @@
|
||||
---
|
||||
:position: before
|
||||
:position_in_additional_file_patterns: before
|
||||
:position_in_class: before
|
||||
:position_in_factory: before
|
||||
:position_in_fixture: before
|
||||
:position_in_routes: before
|
||||
:position_in_serializer: before
|
||||
:position_in_test: before
|
||||
:classified_sort: true
|
||||
:exclude_controllers: true
|
||||
:exclude_factories: true
|
||||
:exclude_fixtures: false
|
||||
:exclude_helpers: true
|
||||
:exclude_scaffolds: true
|
||||
:exclude_serializers: false
|
||||
:exclude_sti_subclasses: false
|
||||
:exclude_tests: false
|
||||
:force: false
|
||||
:format_markdown: false
|
||||
:format_rdoc: false
|
||||
:format_yard: false
|
||||
:frozen: false
|
||||
:ignore_model_sub_dir: false
|
||||
:ignore_unknown_models: false
|
||||
:include_version: false
|
||||
:show_check_constraints: false
|
||||
:show_complete_foreign_keys: false
|
||||
:show_foreign_keys: true
|
||||
:show_indexes: true
|
||||
:simple_indexes: false
|
||||
:sort: false
|
||||
:timestamp: false
|
||||
:trace: false
|
||||
:with_comment: true
|
||||
:with_column_comments: true
|
||||
:with_table_comments: true
|
||||
:active_admin: false
|
||||
:command:
|
||||
:debug: false
|
||||
:hide_default_column_types: ''
|
||||
:hide_limit_column_types: ''
|
||||
:ignore_columns:
|
||||
:ignore_routes:
|
||||
:models: true
|
||||
:routes: false
|
||||
:skip_on_db_migrate: false
|
||||
:target_action: :do_annotations
|
||||
:wrapper:
|
||||
:wrapper_close:
|
||||
:wrapper_open:
|
||||
:classes_default_to_s: []
|
||||
:additional_file_patterns: []
|
||||
:model_dir:
|
||||
- app/models
|
||||
:require: []
|
||||
:root_dir:
|
||||
- ''
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 4
|
||||
- name: Run Erblint
|
||||
run: bundle exec erblint ./app
|
||||
run: bundle exec erb_lint ./app
|
||||
|
||||
eslint:
|
||||
name: ESLint
|
||||
@@ -77,6 +77,33 @@ jobs:
|
||||
run: |
|
||||
./node_modules/eslint/bin/eslint.js "app/javascript/**/*.js"
|
||||
|
||||
brakeman:
|
||||
name: Brakeman
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.4.1
|
||||
- name: Cache gems
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: vendor/bundle
|
||||
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gem-
|
||||
- name: Install gems
|
||||
run: |
|
||||
gem install bundler
|
||||
bundle config path vendor/bundle
|
||||
bundle install --jobs 4 --retry 4
|
||||
yarn install
|
||||
sudo apt-get update
|
||||
sudo apt-get install libvips
|
||||
- name: Run Brakeman
|
||||
run: bundle exec brakeman -q --exit-on-warn
|
||||
|
||||
rspec:
|
||||
name: RSpec
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ruby:3.4.1-alpine as fonts
|
||||
FROM ruby:3.4.1-alpine AS fonts
|
||||
|
||||
WORKDIR /fonts
|
||||
|
||||
@@ -6,7 +6,7 @@ RUN apk --no-cache add fontforge wget && wget https://github.com/satbyy/go-noto-
|
||||
|
||||
RUN fontforge -lang=py -c 'font1 = fontforge.open("FreeSans.ttf"); font2 = fontforge.open("NotoSansSymbols2-Regular.ttf"); font1.mergeFonts(font2); font1.generate("FreeSans.ttf")'
|
||||
|
||||
FROM ruby:3.4.1-alpine as webpack
|
||||
FROM ruby:3.4.1-alpine AS webpack
|
||||
|
||||
ENV RAILS_ENV=production
|
||||
ENV NODE_ENV=production
|
||||
@@ -32,7 +32,7 @@ COPY ./app/views ./app/views
|
||||
|
||||
RUN echo "gem 'shakapacker'" > Gemfile && ./bin/shakapacker
|
||||
|
||||
FROM ruby:3.4.1-alpine as app
|
||||
FROM ruby:3.4.1-alpine AS app
|
||||
|
||||
ENV RAILS_ENV=production
|
||||
ENV BUNDLE_WITHOUT="development:test"
|
||||
|
||||
@@ -46,7 +46,6 @@ gem 'twitter_cldr', require: false
|
||||
gem 'tzinfo-data'
|
||||
|
||||
group :development, :test do
|
||||
gem 'annotate'
|
||||
gem 'better_html'
|
||||
gem 'bullet'
|
||||
gem 'debug'
|
||||
@@ -63,6 +62,9 @@ group :development, :test do
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem 'annotaterb'
|
||||
gem 'brakeman', require: false
|
||||
gem 'foreman', require: false
|
||||
gem 'letter_opener_web'
|
||||
gem 'web-console'
|
||||
end
|
||||
|
||||
@@ -74,9 +74,7 @@ GEM
|
||||
uri (>= 0.13.1)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
annotate (2.6.5)
|
||||
activerecord (>= 2.3.0)
|
||||
rake (>= 0.8.7)
|
||||
annotaterb (4.14.0)
|
||||
arabic-letter-connector (0.1.1)
|
||||
ast (2.4.2)
|
||||
aws-eventstream (1.3.0)
|
||||
@@ -120,6 +118,8 @@ GEM
|
||||
bindex (0.8.1)
|
||||
bootsnap (1.18.4)
|
||||
msgpack (~> 1.2)
|
||||
brakeman (7.0.0)
|
||||
racc
|
||||
builder (3.3.0)
|
||||
bullet (8.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
@@ -229,6 +229,7 @@ GEM
|
||||
ffi (1.17.1-arm64-darwin)
|
||||
ffi (1.17.1-x86_64-linux-gnu)
|
||||
ffi (1.17.1-x86_64-linux-musl)
|
||||
foreman (0.88.1)
|
||||
geom2d (0.4.1)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
@@ -327,7 +328,7 @@ GEM
|
||||
mysql2 (0.5.6)
|
||||
net-http-persistent (4.0.5)
|
||||
connection_pool (~> 2.2)
|
||||
net-imap (0.5.3)
|
||||
net-imap (0.5.6)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
@@ -337,18 +338,18 @@ GEM
|
||||
net-smtp (0.5.0)
|
||||
net-protocol
|
||||
nio4r (2.7.4)
|
||||
nokogiri (1.18.2)
|
||||
nokogiri (1.18.3)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.2-aarch64-linux-gnu)
|
||||
nokogiri (1.18.3-aarch64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.2-aarch64-linux-musl)
|
||||
nokogiri (1.18.3-aarch64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.2-arm64-darwin)
|
||||
nokogiri (1.18.3-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.2-x86_64-linux-gnu)
|
||||
nokogiri (1.18.3-x86_64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.2-x86_64-linux-musl)
|
||||
nokogiri (1.18.3-x86_64-linux-musl)
|
||||
racc (~> 1.4)
|
||||
oj (3.16.8)
|
||||
bigdecimal (>= 3.0)
|
||||
@@ -386,7 +387,7 @@ GEM
|
||||
puma (6.5.0)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.8.1)
|
||||
rack (3.1.8)
|
||||
rack (3.1.10)
|
||||
rack-proxy (0.7.7)
|
||||
rack
|
||||
rack-session (2.0.0)
|
||||
@@ -588,13 +589,14 @@ PLATFORMS
|
||||
x86_64-linux-musl
|
||||
|
||||
DEPENDENCIES
|
||||
annotate
|
||||
annotaterb
|
||||
arabic-letter-connector
|
||||
aws-sdk-s3
|
||||
aws-sdk-secretsmanager
|
||||
azure-storage-blob
|
||||
better_html
|
||||
bootsnap
|
||||
brakeman
|
||||
bullet
|
||||
cancancan
|
||||
capybara
|
||||
@@ -610,6 +612,7 @@ DEPENDENCIES
|
||||
faker
|
||||
faraday
|
||||
faraday-follow_redirects
|
||||
foreman
|
||||
google-cloud-storage
|
||||
hexapdf
|
||||
image_processing
|
||||
|
||||
@@ -39,7 +39,7 @@ class AccountConfigsController < ApplicationController
|
||||
end
|
||||
|
||||
def account_config_params
|
||||
params.required(:account_config).permit!.tap do |attrs|
|
||||
params.required(:account_config).permit(:key, :value, { value: {} }, { value: [] }).tap do |attrs|
|
||||
attrs[:value] = attrs[:value] == '1' if attrs[:value].in?(%w[1 0])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,7 +10,7 @@ module Api
|
||||
last_submitter = @submission.submitters.max_by(&:completed_at)
|
||||
|
||||
if last_submitter.documents_attachments.blank?
|
||||
last_submitter.documents_attachments = Submissions::EnsureResultGenerated.call(submitter)
|
||||
last_submitter.documents_attachments = Submissions::EnsureResultGenerated.call(last_submitter)
|
||||
end
|
||||
|
||||
last_submitter.documents_attachments
|
||||
|
||||
@@ -146,9 +146,15 @@ module Api
|
||||
|
||||
if attrs[:completed]
|
||||
submitter.values = Submitters::SubmitValues.merge_default_values(submitter)
|
||||
submitter.values = Submitters::SubmitValues.merge_formula_values(submitter)
|
||||
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
||||
|
||||
formula_values = Submitters::SubmitValues.build_formula_values(submitter)
|
||||
|
||||
if formula_values.present?
|
||||
submitter.values = submitter.values.merge(formula_values)
|
||||
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
||||
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
|
||||
|
||||
@@ -34,6 +34,8 @@ module Api
|
||||
}
|
||||
end
|
||||
}
|
||||
rescue HexaPDF::MalformedPDFError
|
||||
render json: { error: 'Malformed PDF' }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,7 +30,7 @@ class ApplicationController < ActionController::Base
|
||||
redirect_to request.referer, alert: 'Too many requests', status: :too_many_requests
|
||||
end
|
||||
|
||||
if Rails.env.production?
|
||||
if Rails.env.production? || Rails.env.test?
|
||||
rescue_from CanCan::AccessDenied do |e|
|
||||
Rollbar.warning(e) if defined?(Rollbar)
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class NotificationsSettingsController < ApplicationController
|
||||
end
|
||||
|
||||
def email_config_params
|
||||
params.require(:account_config).permit!.tap do |attrs|
|
||||
params.require(:account_config).permit(:key, :value, { value: {} }, { value: [] }).tap do |attrs|
|
||||
attrs[:key] = nil unless attrs[:key].in?([AccountConfig::BCC_EMAILS, AccountConfig::SUBMITTER_REMINDERS])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -50,7 +50,7 @@ class PersonalizationSettingsController < ApplicationController
|
||||
end
|
||||
|
||||
def account_config_params
|
||||
attrs = params.require(:account_config).permit!
|
||||
attrs = params.require(:account_config).permit(:key, :value, { value: {} }, { value: [] })
|
||||
|
||||
return attrs if attrs[:value].is_a?(String)
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ class SendSubmissionEmailController < ApplicationController
|
||||
|
||||
SEND_DURATION = 30.minutes
|
||||
|
||||
def success; end
|
||||
|
||||
def create
|
||||
@submitter =
|
||||
if params[:template_slug]
|
||||
@@ -31,7 +29,7 @@ class SendSubmissionEmailController < ApplicationController
|
||||
end
|
||||
|
||||
respond_to do |f|
|
||||
f.html { redirect_to success_send_submission_email_index_path }
|
||||
f.html { render :success }
|
||||
f.json { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -34,6 +34,8 @@ class StartFormController < ApplicationController
|
||||
assign_submission_attributes(@submitter, @template)
|
||||
|
||||
Submissions::AssignDefinedSubmitters.call(@submitter.submission)
|
||||
else
|
||||
@submitter.assign_attributes(ip: request.remote_ip, ua: request.user_agent)
|
||||
end
|
||||
|
||||
if @submitter.save
|
||||
@@ -65,8 +67,9 @@ class StartFormController < ApplicationController
|
||||
.order(id: :desc)
|
||||
.where(declined_at: nil)
|
||||
.where(external_id: nil)
|
||||
.where(ip: [nil, request.remote_ip])
|
||||
.then { |rel| params[:resubmit].present? ? rel.where(completed_at: nil) : rel }
|
||||
.find_or_initialize_by(**submitter_params.compact_blank)
|
||||
.find_or_initialize_by(email: submitter_params[:email], **submitter_params.compact_blank)
|
||||
end
|
||||
|
||||
def assign_submission_attributes(submitter, template)
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SubmissionEventsController < ApplicationController
|
||||
SUBMISSION_EVENT_ICONS = {
|
||||
'view_form' => 'eye',
|
||||
'start_form' => 'player_play',
|
||||
'complete_form' => 'check',
|
||||
'send_email' => 'mail_forward',
|
||||
'click_email' => 'hand_click',
|
||||
'api_complete_form' => 'check',
|
||||
'send_reminder_email' => 'mail_forward',
|
||||
'send_2fa_sms' => '2fa',
|
||||
'send_sms' => 'send',
|
||||
'phone_verified' => 'phone_check',
|
||||
'click_sms' => 'hand_click',
|
||||
'decline_form' => 'x',
|
||||
'start_verification' => 'player_play',
|
||||
'complete_verification' => 'check',
|
||||
'invite_party' => 'user_plus'
|
||||
}.freeze
|
||||
|
||||
load_and_authorize_resource :submission
|
||||
|
||||
def index; end
|
||||
end
|
||||
@@ -20,7 +20,7 @@ class SubmissionsPreviewController < ApplicationController
|
||||
|
||||
@submission ||= Submission.find_by!(slug: params[:slug])
|
||||
|
||||
raise ActionController::RoutingError if @submission.account.archived_at?
|
||||
raise ActionController::RoutingError, I18n.t('not_found') if @submission.account.archived_at?
|
||||
|
||||
if !@submission.submitters.all?(&:completed_at?) && !signature_valid &&
|
||||
(!current_user || !current_ability.can?(:read, @submission))
|
||||
@@ -40,6 +40,7 @@ class SubmissionsPreviewController < ApplicationController
|
||||
|
||||
def completed
|
||||
@submission = Submission.find_by!(slug: params[:submissions_preview_slug])
|
||||
@template = @submission.template
|
||||
|
||||
render :completed, layout: 'form'
|
||||
end
|
||||
|
||||
@@ -28,8 +28,10 @@ class TemplatesPreferencesController < ApplicationController
|
||||
submitters_order
|
||||
completed_notification_email_subject completed_notification_email_body
|
||||
completed_notification_email_enabled completed_notification_email_attach_audit] +
|
||||
[completed_message: %i[title body]]
|
||||
[completed_message: %i[title body],
|
||||
submitters: [%i[uuid request_email_subject request_email_body]]]
|
||||
).tap do |attrs|
|
||||
attrs[:preferences].delete(:submitters) if params[:request_email_per_submitter] != '1'
|
||||
attrs[:preferences] = attrs[:preferences].transform_values do |value|
|
||||
if %w[true false].include?(value)
|
||||
value == 'true'
|
||||
|
||||
@@ -26,7 +26,7 @@ class UserConfigsController < ApplicationController
|
||||
end
|
||||
|
||||
def user_config_params
|
||||
params.required(:user_config).permit!.tap do |attrs|
|
||||
params.required(:user_config).permit(:key, :value, { value: {} }, { value: [] }).tap do |attrs|
|
||||
attrs[:value] = attrs[:value] == '1' if attrs[:value].in?(%w[1 0])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -45,17 +45,16 @@ class UsersController < ApplicationController
|
||||
return redirect_to settings_users_path, notice: I18n.t('unable_to_update_user') if Docuseal.demo?
|
||||
|
||||
attrs = user_params.compact_blank.merge(user_params.slice(:archived_at))
|
||||
attrs.delete(:role) if !role_valid?(attrs[:role]) || current_user == @user
|
||||
|
||||
if params.dig(:user, :account_id).present?
|
||||
account = Account.accessible_by(current_ability).find(params[:user][:account_id])
|
||||
account = Account.accessible_by(current_ability).find(params.dig(:user, :account_id))
|
||||
|
||||
authorize!(:manage, account)
|
||||
|
||||
@user.account = account
|
||||
end
|
||||
|
||||
if @user.update(attrs)
|
||||
if @user.update(attrs.except(current_user == @user ? :role : nil))
|
||||
redirect_back fallback_location: settings_users_path, notice: I18n.t('user_has_been_updated')
|
||||
else
|
||||
render turbo_stream: turbo_stream.replace(:modal, template: 'users/edit'), status: :unprocessable_entity
|
||||
@@ -84,8 +83,11 @@ class UsersController < ApplicationController
|
||||
|
||||
def user_params
|
||||
if params.key?(:user)
|
||||
params.require(:user).permit(:email, :first_name, :last_name, :password,
|
||||
:role, :archived_at, :account_id)
|
||||
permitted_params = %i[email first_name last_name password archived_at]
|
||||
|
||||
permitted_params << :role if role_valid?(params.dig(:user, :role))
|
||||
|
||||
params.require(:user).permit(permitted_params)
|
||||
else
|
||||
{}
|
||||
end
|
||||
|
||||
@@ -3,6 +3,8 @@ export default class extends HTMLElement {
|
||||
this.resize()
|
||||
|
||||
this.textarea.addEventListener('input', () => this.resize())
|
||||
|
||||
this.observeVisibility()
|
||||
}
|
||||
|
||||
resize () {
|
||||
@@ -11,6 +13,28 @@ export default class extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
observeVisibility () {
|
||||
this.observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
this.resize()
|
||||
this.observer.unobserve(this.textarea)
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
threshold: 0.1
|
||||
}
|
||||
)
|
||||
|
||||
this.observer.observe(this.textarea)
|
||||
}
|
||||
|
||||
disconnectedCallback () {
|
||||
this.observer.unobserve(this.textarea)
|
||||
}
|
||||
|
||||
get textarea () {
|
||||
return this.querySelector('textarea')
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
class="field-area flex absolute lg:text-base -outline-offset-1"
|
||||
dir="auto"
|
||||
:style="computedStyle"
|
||||
:class="{ 'font-serif': field.preferences?.font === 'Times', 'text-[1.6vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'border border-red-100 bg-red-100 cursor-pointer': submittable, 'border border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'field-area-active outline-red-500 outline-dashed outline-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
||||
:class="{ 'font-mono': field.preferences?.font === 'Courier', 'font-serif': field.preferences?.font === 'Times', 'text-[1.6vw] lg:text-base': !textOverflowChars, 'text-[1.0vw] lg:text-xs': textOverflowChars, 'cursor-default': !submittable, 'border border-red-100 bg-red-100 cursor-pointer': submittable, 'border border-red-100': !isActive && submittable, 'bg-opacity-80': !isActive && !isValueSet && submittable, 'field-area-active outline-red-500 outline-dashed outline-2 z-10': isActive && submittable, 'bg-opacity-40': (isActive || isValueSet) && submittable }"
|
||||
>
|
||||
<div
|
||||
v-if="(!withFieldPlaceholder || !field.name || field.type === 'cells') && !isActive && !isValueSet && field.type !== 'checkbox' && submittable && !area.option_uuid"
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
<button
|
||||
v-if="!isCompleted"
|
||||
id="minimize_form_button"
|
||||
class="absolute right-0 mr-2 mt-2 top-0 hidden md:block"
|
||||
class="absolute right-0 top-0"
|
||||
:class="currentField?.description?.length > 100 ? 'mr-1 mt-1 md:mr-2 md:mt-2': 'mr-2 mt-2 hidden md:block'"
|
||||
:title="t('minimize')"
|
||||
@click.prevent="minimizeForm"
|
||||
>
|
||||
@@ -1036,7 +1037,7 @@ export default {
|
||||
}, [])
|
||||
},
|
||||
formulaFields () {
|
||||
return this.fields.filter((f) => f.preferences?.formula && f.type !== 'payment')
|
||||
return this.fields.filter((f) => f.preferences?.formula && f.type !== 'payment' && this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f))
|
||||
},
|
||||
attachmentsIndex () {
|
||||
return this.attachments.reduce((acc, a) => {
|
||||
|
||||
@@ -1401,7 +1401,6 @@ export default {
|
||||
const lastArea = field.areas[field.areas.length - 1]
|
||||
|
||||
if (lastArea) {
|
||||
fieldArea.x -= lastArea.w / 2
|
||||
fieldArea.w = lastArea.w
|
||||
fieldArea.h = lastArea.h
|
||||
}
|
||||
|
||||
@@ -14,8 +14,20 @@ class SubmitterMailer < ApplicationMailer
|
||||
@email_message = submitter.account.email_messages.find_by(uuid: submitter.preferences['email_message_uuid'])
|
||||
end
|
||||
|
||||
@body = @email_message&.body.presence || @submitter.template.preferences['request_email_body'].presence
|
||||
@subject = @email_message&.subject.presence || @submitter.template.preferences['request_email_subject'].presence
|
||||
template_submitters_index =
|
||||
if @email_message.blank?
|
||||
build_submitter_preferences_index(@submitter)
|
||||
else
|
||||
{}
|
||||
end
|
||||
|
||||
@body = @email_message&.body.presence ||
|
||||
template_submitters_index.dig(@submitter.uuid, 'request_email_body').presence ||
|
||||
@submitter.template.preferences['request_email_body'].presence
|
||||
|
||||
@subject = @email_message&.subject.presence ||
|
||||
template_submitters_index.dig(@submitter.uuid, 'request_email_subject').presence ||
|
||||
@submitter.template.preferences['request_email_subject'].presence
|
||||
|
||||
@email_config = AccountConfigs.find_for_account(@current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY)
|
||||
|
||||
@@ -24,14 +36,7 @@ class SubmitterMailer < ApplicationMailer
|
||||
reply_to = build_submitter_reply_to(@submitter)
|
||||
|
||||
I18n.with_locale(@current_account.locale) do
|
||||
subject =
|
||||
if @email_config || @subject
|
||||
ReplaceEmailVariables.call(@subject || @email_config.value['subject'], submitter:)
|
||||
elsif @submitter.with_signature_fields?
|
||||
I18n.t(:you_are_invited_to_sign_a_document)
|
||||
else
|
||||
I18n.t(:you_are_invited_to_submit_a_form)
|
||||
end
|
||||
subject = build_invite_subject(@subject, @email_config, submitter)
|
||||
|
||||
mail(
|
||||
to: @submitter.friendly_name,
|
||||
@@ -196,6 +201,20 @@ class SubmitterMailer < ApplicationMailer
|
||||
user.role == 'integration' ? user.friendly_name.sub(/\+\w+@/, '@') : user.friendly_name
|
||||
end
|
||||
|
||||
def build_invite_subject(subject, email_config, submitter)
|
||||
if email_config || subject
|
||||
ReplaceEmailVariables.call(subject || email_config.value['subject'], submitter:)
|
||||
elsif submitter.with_signature_fields?
|
||||
I18n.t(:you_are_invited_to_sign_a_document)
|
||||
else
|
||||
I18n.t(:you_are_invited_to_submit_a_form)
|
||||
end
|
||||
end
|
||||
|
||||
def build_submitter_preferences_index(submitter)
|
||||
submitter.template.preferences['submitters'].to_a.index_by { |e| e['uuid'] }
|
||||
end
|
||||
|
||||
def add_attachments_with_size_limit(submitter, storage_attachments, current_size, filename_format = nil)
|
||||
total_size = current_size
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ class Submitter < ApplicationRecord
|
||||
has_many_attached :attachments
|
||||
has_many_attached :preview_documents
|
||||
has_many :template_accesses, through: :template
|
||||
has_many :email_events, as: :emailable, dependent: (Docuseal.multitenant? ? nil : :destroy)
|
||||
|
||||
has_many :document_generation_events, dependent: :destroy
|
||||
has_many :submission_events, dependent: :destroy
|
||||
@@ -63,6 +64,8 @@ class Submitter < ApplicationRecord
|
||||
|
||||
scope :completed, -> { where.not(completed_at: nil) }
|
||||
|
||||
after_destroy :anonymize_email_events, if: -> { Docuseal.multitenant? }
|
||||
|
||||
def status
|
||||
if declined_at?
|
||||
'declined'
|
||||
@@ -108,4 +111,12 @@ class Submitter < ApplicationRecord
|
||||
fields.any? { |f| f['submitter_uuid'] == uuid && signature_field_types.include?(f['type']) }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def anonymize_email_events
|
||||
email_events.each do |event|
|
||||
event.update!(email: Digest::MD5.base64digest(event.email))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" width="24" height="24" stroke-width="2">
|
||||
<path d="M7 16h-4l3.47 -4.66a2 2 0 1 0 -3.47 -1.54"></path>
|
||||
<path d="M10 16v-8h4"></path> <path d="M10 12l3 0"></path>
|
||||
<path d="M17 16v-6a2 2 0 0 1 4 0v6"></path>
|
||||
<path d="M17 13l4 0"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 426 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" width="44" height="44" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" />
|
||||
<path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 397 B |
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" width="24" height="24" stroke-width="2">
|
||||
<path d="M8 13v-8.5a1.5 1.5 0 0 1 3 0v7.5"></path>
|
||||
<path d="M11 11.5v-2a1.5 1.5 0 0 1 3 0v2.5"></path>
|
||||
<path d="M14 10.5a1.5 1.5 0 0 1 3 0v1.5"></path>
|
||||
<path d="M17 11.5a1.5 1.5 0 0 1 3 0v4.5a6 6 0 0 1 -6 6h-2h.208a6 6 0 0 1 -5.012 -2.7l-.196 -.3c-.312 -.479 -1.407 -2.388 -3.286 -5.728a1.5 1.5 0 0 1 .536 -2.022a1.867 1.867 0 0 1 2.28 .28l1.47 1.47"></path>
|
||||
<path d="M5 3l-1 -1"></path>
|
||||
<path d="M4 7h-1"></path>
|
||||
<path d="M14 3l1 -1"></path>
|
||||
<path d="M15 6h1"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 711 B |
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" width="24" height="24" stroke-width="2">
|
||||
<path d="M4 12h.01"></path>
|
||||
<path d="M4 6h.01"></path>
|
||||
<path d="M4 18h.01"></path>
|
||||
<path d="M8 18h2"></path>
|
||||
<path d="M8 12h2"></path>
|
||||
<path d="M8 6h2"></path>
|
||||
<path d="M14 6h6"></path>
|
||||
<path d="M14 12h6"></path>
|
||||
<path d="M14 18h6"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 484 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" width="24" height="24" stroke-width="2">
|
||||
<path d="M3 9l9 6l9 -6l-9 -6l-9 6"></path>
|
||||
<path d="M21 9v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-10"></path>
|
||||
<path d="M3 19l6 -6"></path>
|
||||
<path d="M15 13l6 6"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 401 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" width="24" height="24" stroke-width="2">
|
||||
<path d="M5 4h4l2 5l-2.5 1.5a11 11 0 0 0 5 5l1.5 -2.5l5 2v4a2 2 0 0 1 -2 2a16 16 0 0 1 -15 -15a2 2 0 0 1 2 -2"></path>
|
||||
<path d="M15 6l2 2l4 -4"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 382 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="<%= local_assigns[:class] %>" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" width="24" height="24" stroke-width="2">
|
||||
<path d="M7 4v16l13 -8z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 261 B |
@@ -2,7 +2,7 @@
|
||||
<div class="space-y-6 mx-auto">
|
||||
<div class="space-y-6">
|
||||
<div class="flex items-center justify-center">
|
||||
<%= render 'start_form/docuseal_logo' %>
|
||||
<%= render 'start_form/banner' %>
|
||||
</div>
|
||||
<div class="text-center text-4xl font-bold">
|
||||
<%= t('email_has_been_sent') %>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<% event_colors = %w[bg-red-200 bg-sky-200 bg-emerald-200 bg-yellow-200 bg-purple-200 bg-pink-200 bg-cyan-200 bg-orange-200 bg-lime-200 bg-indigo-200] %>
|
||||
<% submitters_uuids = (@submission.template_submitters || @submission.template.submitters).pluck('uuid') %>
|
||||
<%= render 'shared/turbo_modal_large', title: t('event_log') do %>
|
||||
<div class="pl-8 pr-4 py-4">
|
||||
<ol class="relative border-s border-base-300 space-y-6">
|
||||
<li class="ml-7">
|
||||
<span class="absolute flex items-center justify-center w-7 h-7 rounded-full -start-3.5 ring-8 ring-base-100 text-base-content bg-gray-200">
|
||||
<%= svg_icon('file_text', class: 'w-4 h-4') %>
|
||||
</span>
|
||||
<p class="text-sm leading-none text-base-content/60 pt-1.5">
|
||||
<%= l(@submission.created_at.in_time_zone(current_account.timezone), format: :long, locale: current_account.locale) %>
|
||||
</p>
|
||||
<p class="text-base-content/80 mt-1">
|
||||
<% if @submission.source == 'invite' %>
|
||||
<%= t('submission_created_by_email_html', email: @submission.created_by_user.email) %>
|
||||
<% elsif @submission.created_by_user %>
|
||||
<%= t('submission_created_by_email_via_source_html', email: @submission.created_by_user.email, source: t("submission_sources.#{@submission.source}")) %>
|
||||
<% else %>
|
||||
<%= t('submission_created_via_source_html', source: t("submission_sources.#{@submission.source}")) %>
|
||||
<% end %>
|
||||
</p>
|
||||
</li>
|
||||
<% @submission.submission_events.order(:event_timestamp).each do |event| %>
|
||||
<% submitter = @submission.submitters.find { |e| e.id == event.submitter_id } %>
|
||||
<% bg_class = event_colors[submitters_uuids.index(submitter.uuid) % event_colors.length] %>
|
||||
<% submitter_name = event.event_type.include?('sms') || event.event_type.include?('phone') ? (event.data['phone'] || submitter.phone) : (submitter.name || submitter.email || submitter.phone) %>
|
||||
<li class="ml-7">
|
||||
<span class="absolute flex items-center justify-center w-7 h-7 rounded-full -start-3.5 ring-8 ring-base-100 text-base-content <%= bg_class %>">
|
||||
<%= svg_icon(SubmissionEventsController::SUBMISSION_EVENT_ICONS.fetch(event.event_type, 'circle_dot'), class: 'w-4 h-4') %>
|
||||
</span>
|
||||
<p class="text-sm leading-none text-base-content/60 pt-1.5">
|
||||
<%= l(event.event_timestamp.in_time_zone(current_account.timezone), format: :long, locale: current_account.locale) %>
|
||||
</p>
|
||||
<p class="text-base-content/80 mt-1">
|
||||
<% if event.event_type == 'complete_verification' %>
|
||||
<%= t('submission_event_names.complete_verification_by_html', provider: event.data['method'], submitter_name:) %>
|
||||
<% elsif event.event_type == 'invite_party' && (invited_submitter = @submission.submitters.find { |e| e.uuid == event.data['uuid'] }) && (name = @submission.template_submitters.find { |e| e['uuid'] == event.data['uuid'] }&.dig('name')) %>
|
||||
<%= t('submission_event_names.invite_party_by_html', invited_submitter_name: [invited_submitter.name || invited_submitter.email || invited_submitter.phone, name].join(' '), submitter_name:) %>
|
||||
<% elsif event.event_type.include?('send_') %>
|
||||
<%= t("submission_event_names.#{event.event_type}_to_html", submitter_name:) %>
|
||||
<% else %>
|
||||
<%= t("submission_event_names.#{event.event_type}_by_html", submitter_name:) %>
|
||||
<% end %>
|
||||
</p>
|
||||
</li>
|
||||
<% end %>
|
||||
</ol>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -1,3 +1,4 @@
|
||||
<% submitter_preferences_index = template.preferences['submitters'].to_a.index_by { |e| e['uuid'] } %>
|
||||
<div class="form-control">
|
||||
<% can_send_emails = Accounts.can_send_emails?(current_account) %>
|
||||
<div class="flex justify-between items-center">
|
||||
@@ -36,10 +37,18 @@
|
||||
<% config = AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY) %>
|
||||
<div id="message_field" class="card card-compact bg-base-300/40 hidden">
|
||||
<div class="card-body">
|
||||
<div class="form-control space-y-2">
|
||||
<%= tag.input id: toggle_uuid = SecureRandom.uuid, value: '1', name: 'request_email_per_submitter', class: 'peer', type: 'checkbox', hidden: true, checked: local_assigns[:message_per_submitter] != false && template.preferences['submitters'].to_a.size > 1 %>
|
||||
<div class="peer-checked:hidden form-control space-y-2">
|
||||
<div class="form-control">
|
||||
<%= f.label :subject, t('subject'), class: 'label' %>
|
||||
<%= f.text_field :subject, value: local_assigns[:submitter_email_message]&.subject.presence || template.preferences['request_email_subject'].presence || config.value['subject'], required: true, class: '!text-sm base-input w-full', dir: 'auto' %>
|
||||
<div class="flex justify-between">
|
||||
<%= f.label :subject, t('subject'), class: 'label' %>
|
||||
<% if template.submitters.size > 1 && template.submitters.size < 5 && local_assigns[:message_per_submitter] != false %>
|
||||
<label for="<%= toggle_uuid %>" class="label underline">
|
||||
<%= t('edit_per_party') %>
|
||||
</label>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= f.text_field :subject, value: local_assigns[:submitter_email_message]&.subject.presence || submitter_preferences_index.dig(local_assigns[:submitter]&.uuid, 'request_email_subject').presence || template.preferences['request_email_subject'].presence || config.value['subject'], required: true, class: '!text-sm base-input w-full', dir: 'auto' %>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<div class="flex items-center">
|
||||
@@ -49,7 +58,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<autoresize-textarea>
|
||||
<%= f.text_area :body, value: local_assigns[:submitter_email_message]&.body.presence || template.preferences['request_email_body'].presence || config.value['body'], required: true, class: 'base-textarea w-full', rows: 10, dir: 'auto' %>
|
||||
<%= f.text_area :body, value: local_assigns[:submitter_email_message]&.body.presence || submitter_preferences_index.dig(local_assigns[:submitter]&.uuid, 'request_email_body').presence || template.preferences['request_email_body'].presence || config.value['body'], required: true, class: 'base-textarea w-full', rows: 10, dir: 'auto' %>
|
||||
</autoresize-textarea>
|
||||
<% unless local_assigns.fetch(:disable_save_as_default_template_option, false) %>
|
||||
<label for="<%= uuid = SecureRandom.uuid %>" class="flex items-center cursor-pointer">
|
||||
@@ -60,5 +69,44 @@
|
||||
</div>
|
||||
<%= render 'submissions/message_fields' %>
|
||||
</div>
|
||||
<% if template.submitters.size > 1 && template.submitters.size < 5 && local_assigns[:message_per_submitter] != false %>
|
||||
<div class="hidden peer-checked:block form-control space-y-2">
|
||||
<% uuid = SecureRandom.uuid %>
|
||||
<% options = template.submitters.map { |e| [e['name'], "request_email_#{uuid}_#{e['uuid']}"] } %>
|
||||
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="flex relative px-1">
|
||||
<ul class="tabs w-full flex flex-nowrap">
|
||||
<% options.each_with_index do |(label, val), index| %>
|
||||
<div class="w-full">
|
||||
<%= f.radio_button :selected, val, checked: index.zero?, id: "#{val}_radio", data: { action: 'click:toggle-visible#trigger' }, class: 'hidden peer' %>
|
||||
<%= f.label :selected, label, value: val, for: "#{val}_radio", class: 'tab w-full tab-lifted peer-checked:tab-active !bg-transparent' %>
|
||||
</div>
|
||||
<% end %>
|
||||
</ul>
|
||||
</toggle-visible>
|
||||
<% template.submitters.each_with_index do |submitter, index| %>
|
||||
<%= fields_for :submitter_preferences, nil, index: submitter['uuid'] do |ff| %>
|
||||
<div id="request_email_<%= uuid %>_<%= submitter['uuid'] %>" class="<%= 'hidden' if index != 0 %>">
|
||||
<div class="form-control">
|
||||
<div class="flex justify-between">
|
||||
<%= ff.label :subject, t('subject'), class: 'label' %>
|
||||
</div>
|
||||
<%= ff.text_field :subject, value: local_assigns[:submitter_email_message]&.subject.presence || submitter_preferences_index.dig(submitter['uuid'], 'request_email_subject').presence || template.preferences['request_email_subject'].presence || config.value['subject'], required: true, class: '!text-sm base-input w-full', dir: 'auto' %>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<div class="flex items-center">
|
||||
<%= ff.label :message, t('body'), class: 'label' %>
|
||||
<span class="tooltip tooltip-right" data-tip="<%= t('use_following_placeholders_text_') %> <%= AccountConfig::DEFAULT_VALUES[AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY].call['body'].scan(/{.*?}/).join(', ') %>">
|
||||
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||
</span>
|
||||
</div>
|
||||
<autoresize-textarea>
|
||||
<%= ff.text_area :body, value: local_assigns[:submitter_email_message]&.body.presence || submitter_preferences_index.dig(submitter['uuid'], 'request_email_body').presence || template.preferences['request_email_body'].presence || config.value['body'], required: true, class: 'base-textarea w-full', rows: 10, dir: 'auto' %>
|
||||
</autoresize-textarea>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<% align = field.dig('preferences', 'align') %>
|
||||
<% color = field.dig('preferences', 'color') %>
|
||||
<field-value dir="auto" class="flex absolute text-[1.6vw] lg:text-base <%= 'font-serif' if field.dig('preferences', 'font') == 'Times' %> <%= align == 'right' ? 'justify-end' : (align == 'center' ? 'justify-center' : '') %>" style="<%= "color: #{color}; " if color.present? %>width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size'].to_i * 1.23}pt); line-height: `clamp(6pt, 2.0vw, #{(field['preferences']['font_size'].to_i * 1.23) + 3}pt)`" if field.dig('preferences', 'font_size') %>">
|
||||
<% font = field.dig('preferences', 'font') %>
|
||||
<field-value dir="auto" class="flex absolute text-[1.6vw] lg:text-base <%= 'font-mono' if font == 'Courier' %> <%= 'font-serif' if font == 'Times' %> <%= align == 'right' ? 'justify-end' : (align == 'center' ? 'justify-center' : '') %>" style="<%= "color: #{color}; " if color.present? %>width: <%= area['w'] * 100 %>%; height: <%= area['h'] * 100 %>%; left: <%= area['x'] * 100 %>%; top: <%= area['y'] * 100 %>%; <%= "font-size: clamp(4pt, 1.6vw, #{field['preferences']['font_size'].to_i * 1.23}pt); line-height: `clamp(6pt, 2.0vw, #{(field['preferences']['font_size'].to_i * 1.23) + 3}pt)`" if field.dig('preferences', 'font_size') %>">
|
||||
<% if field['type'] == 'signature' %>
|
||||
<div class="flex flex-col justify-between h-full overflow-hidden">
|
||||
<div class="flex-grow flex overflow-hidden" style="min-height: 50%">
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
<%= svg_icon('external_link', class: 'w-6 h-6') %>
|
||||
<span class="hidden md:inline"><%= t('audit_log') %></span>
|
||||
</a>
|
||||
<% else %>
|
||||
<%= link_to submission_events_path(@submission), class: 'white-button', data: { turbo_frame: :modal } do %>
|
||||
<%= svg_icon('logs', class: 'w-6 h-6') %>
|
||||
<span class="hidden md:block"><%= t('event_log') %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if last_submitter %>
|
||||
<% if is_all_completed || !is_combined_enabled %>
|
||||
@@ -232,7 +237,7 @@
|
||||
<% elsif field['type'] == 'date' %>
|
||||
<%= TimeUtils.format_date_string(value, field.dig('preferences', 'format'), @submission.account.locale) %>
|
||||
<% else %>
|
||||
<%= Array.wrap(value).join(', ') %>
|
||||
<div class="whitespace-pre-wrap"><%= Array.wrap(value).join(', ') %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<% query_params = params.permit(:q).merge(filter_params) %>
|
||||
<% if icon = { 'declined' => 'x_circle', 'expired' => 'clock_cancel', 'partially_completed' => 'clock_edit' }[params[:status]] %>
|
||||
<% if icon = { 'declined' => 'x_circle', 'expired' => 'clock_cancel', 'partially_completed' => 'clock_edit', 'sent' => 'send', 'opened' => 'mail_opened' }[params[:status]] %>
|
||||
<div class="flex h-10 px-2 py-1 text-lg items-center justify-between border text-center text-neutral font-semibold rounded-xl w-full md:w-34 border-neutral-700">
|
||||
<%= link_to submissions_filter_path('status', query_params.merge(path: url_for, with_remove: true)), data: { turbo_frame: 'modal' }, class: 'flex items-center space-x-1 w-full pr-1 md:max-w-[140px]' do %>
|
||||
<%= svg_icon(icon, class: 'w-5 h-5 shrink-0') %>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="flex flex-col md:flex-row gap-2 mt-5">
|
||||
<div class="form-control w-full">
|
||||
<div id="status" class="radio-select grid grid-cols-2 gap-2 px-1">
|
||||
<% ['', 'pending', 'completed', 'partially_completed', 'declined', 'expired'].each do |status| %>
|
||||
<% ['', 'pending', 'completed', 'partially_completed', 'sent', 'opened', 'declined', 'expired'].each do |status| %>
|
||||
<label class="radio-label cursor-pointer inline-flex items-center space-x-2">
|
||||
<%= radio_button_tag 'status', status, params[:status] == status || (status == '' && params[:status].blank?), class: 'base-radio' %>
|
||||
<span><%= t(status.presence || 'all') %></span>
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
<div class="max-w-md mx-auto px-2 mt-12 mb-4">
|
||||
<div class="space-y-6 mx-auto">
|
||||
<div class="space-y-6">
|
||||
<% if Docuseal.multitenant? %>
|
||||
<div class="flex items-center justify-center">
|
||||
<%= render 'start_form/docuseal_logo' %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex items-center justify-center">
|
||||
<%= render 'start_form/banner' %>
|
||||
</div>
|
||||
<div class="flex items-center bg-base-200 rounded-xl p-4 mb-4">
|
||||
<div class="flex items-center">
|
||||
<div class="mr-3">
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<%= render partial: 'submissions/annotation', collection: annots, as: :annot %>
|
||||
@@ -47,8 +47,8 @@
|
||||
<div class="relative my-4 shadow-md">
|
||||
<img loading="lazy" src="<%= page.url %>" width="<%= page.metadata['width'] %>" height="<%= page.metadata['height'] %>">
|
||||
<div id="page-<%= [document.uuid, index].join('-') %>" class="top-0 bottom-0 left-0 right-0 absolute">
|
||||
<% document_annots_index[index]&.each do |annot| %>
|
||||
<%= render 'submissions/annotation', annot: %>
|
||||
<% if annots = document_annots_index[index] %>
|
||||
<%= render 'submit_form/annotations', annots: %>
|
||||
<% end %>
|
||||
<% fields_index.dig(document.uuid, index)&.each do |(area, field)| %>
|
||||
<% value = values[field['uuid']].presence || (field['default_value'].present? ? Submitters::SubmitValues.template_default_value_for_submitter(field['default_value'], @submitter.submission.submitters.find { |e| e.uuid == field['submitter_uuid'] }, with_time: false) : nil) %>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</submitter-item>
|
||||
</div>
|
||||
<div>
|
||||
<%= render 'submissions/send_email', f:, template: @submitter.template, submitter: @submitter, resend_email: @submitter.sent_at?, submitter_email_message: @submitter_email_message, disable_save_as_default_template_option: true %>
|
||||
<%= render 'submissions/send_email', f:, template: @submitter.template, submitter: @submitter, resend_email: @submitter.sent_at?, submitter_email_message: @submitter_email_message, disable_save_as_default_template_option: true, message_per_submitter: false %>
|
||||
<%= render 'submissions/send_sms', f:, resend_sms: @submitter.sent_at? %>
|
||||
</div>
|
||||
<div class="form-control mt-4">
|
||||
|
||||
@@ -83,21 +83,71 @@
|
||||
<div class="collapse-content">
|
||||
<%= form_for @template, url: template_preferences_path(@template), method: :post, html: { autocomplete: 'off', class: 'mt-1' }, data: { close_on_submit: false } do |f| %>
|
||||
<toggle-on-submit data-element-id="email_saved_alert1"></toggle-on-submit>
|
||||
<%= f.fields_for :preferences, Struct.new(:request_email_subject, :request_email_body).new(*(@template.preferences.values_at('request_email_subject', 'request_email_body').compact_blank.presence || AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY).value.values_at('subject', 'body'))) do |ff| %>
|
||||
<div class="form-control">
|
||||
<%= ff.label :request_email_subject, t('email_subject'), class: 'label' %>
|
||||
<%= ff.text_field :request_email_subject, required: true, class: 'base-input', dir: 'auto' %>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<div class="flex items-center">
|
||||
<%= ff.label :request_email_body, t('email_body'), class: 'label' %>
|
||||
<span class="tooltip tooltip-right" data-tip="<%= t('use_following_placeholders_text_') %> <%= AccountConfig::DEFAULT_VALUES[AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY].call['body'].scan(/{.*?}/).join(', ') %>">
|
||||
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||
</span>
|
||||
<%= tag.input id: 'request_email_per_submitter', value: '1', name: 'request_email_per_submitter', class: 'peer', type: 'checkbox', hidden: true, checked: @template.preferences['submitters'].to_a.size > 1 %>
|
||||
<div class="peer-checked:hidden">
|
||||
<%= f.fields_for :preferences, Struct.new(:request_email_subject, :request_email_body).new(*(@template.preferences.values_at('request_email_subject', 'request_email_body').compact_blank.presence || AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY).value.values_at('subject', 'body'))) do |ff| %>
|
||||
<div class="form-control">
|
||||
<div class="flex justify-between">
|
||||
<%= ff.label :request_email_subject, t('email_subject'), class: 'label' %>
|
||||
<% if @template.submitters.size > 1 && @template.submitters.size < 5 %>
|
||||
<label for="request_email_per_submitter" class="label underline">
|
||||
<%= t('edit_per_party') %>
|
||||
</label>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= ff.text_field :request_email_subject, required: true, class: 'base-input', dir: 'auto' %>
|
||||
</div>
|
||||
<autoresize-textarea>
|
||||
<%= ff.text_area :request_email_body, required: true, class: 'base-input w-full py-2', dir: 'auto' %>
|
||||
</autoresize-textarea>
|
||||
<div class="form-control">
|
||||
<div class="flex items-center">
|
||||
<%= ff.label :request_email_body, t('email_body'), class: 'label' %>
|
||||
<span class="tooltip tooltip-right" data-tip="<%= t('use_following_placeholders_text_') %> <%= AccountConfig::DEFAULT_VALUES[AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY].call['body'].scan(/{.*?}/).join(', ') %>">
|
||||
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||
</span>
|
||||
</div>
|
||||
<autoresize-textarea>
|
||||
<%= ff.text_area :request_email_body, required: true, class: 'base-input w-full py-2', dir: 'auto' %>
|
||||
</autoresize-textarea>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if @template.submitters.size > 1 && @template.submitters.size < 5 %>
|
||||
<div class="hidden peer-checked:block">
|
||||
<% options = @template.submitters.map { |e| [e['name'], "request_email_#{e['uuid']}"] } %>
|
||||
<toggle-visible data-element-ids="<%= options.map(&:last).to_json %>" class="flex relative px-1">
|
||||
<ul class="tabs w-full flex flex-nowrap mb-2">
|
||||
<% options.each_with_index do |(label, val), index| %>
|
||||
<div class="w-full">
|
||||
<%= f.radio_button :selected, val, checked: index.zero?, id: "#{val}_radio", data: { action: 'click:toggle-visible#trigger' }, class: 'hidden peer' %>
|
||||
<%= f.label :selected, label, value: val, for: "#{val}_radio", class: 'tab w-full tab-lifted peer-checked:tab-active' %>
|
||||
</div>
|
||||
<% end %>
|
||||
</ul>
|
||||
</toggle-visible>
|
||||
<%= f.fields_for :preferences do |ff| %>
|
||||
<% @template.submitters.each_with_index do |submitter, index| %>
|
||||
<div id="request_email_<%= submitter['uuid'] %>" class="<%= 'hidden' if index != 0 %>">
|
||||
<% submitter_preferences = f.object.preferences['submitters'].to_a.find { |e| e['uuid'] == submitter['uuid'] } || {} %>
|
||||
<%= ff.fields_for :submitters, Struct.new(:request_email_subject, :request_email_body).new(*(submitter_preferences.values_at('request_email_subject', 'request_email_body').compact_blank.presence || @template.preferences.values_at('request_email_subject', 'request_email_body').compact_blank.presence || AccountConfigs.find_or_initialize_for_key(current_account, AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY).value.values_at('subject', 'body'))), index: nil do |fff| %>
|
||||
<%= fff.hidden_field :uuid, value: submitter['uuid'] %>
|
||||
<div class="form-control">
|
||||
<%= fff.label :request_email_subject, t('email_subject'), class: 'label' %>
|
||||
<%= fff.text_field :request_email_subject, required: true, class: 'base-input', dir: 'auto' %>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<div class="flex items-center">
|
||||
<%= fff.label :request_email_body, t('email_body'), class: 'label' %>
|
||||
<span class="tooltip tooltip-right" data-tip="<%= t('use_following_placeholders_text_') %> <%= AccountConfig::DEFAULT_VALUES[AccountConfig::SUBMITTER_INVITATION_EMAIL_KEY].call['body'].scan(/{.*?}/).join(', ') %>">
|
||||
<%= svg_icon('info_circle', class: 'w-4 h-4') %>
|
||||
</span>
|
||||
</div>
|
||||
<autoresize-textarea>
|
||||
<%= fff.text_area :request_email_body, required: true, class: 'base-input w-full py-2', dir: 'auto' %>
|
||||
</autoresize-textarea>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="form-control pt-2">
|
||||
@@ -235,12 +285,12 @@
|
||||
<div class="form-control">
|
||||
<%= ff.text_field :name, class: 'w-full outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1 peer mb-2', autocomplete: 'off', placeholder: "#{index + 1}#{(index + 1).ordinal} Party", required: true %>
|
||||
<% if @template.submitters.size == 2 %>
|
||||
<%= ff.email_field :email, class: 'base-input', autocomplete: 'off', placeholder: t('default_email'), disabled: ff.object.is_requester || ff.object.invite_by_uuid.present? || ff.object.optional_invite_by_uuid.present?, id: field_uuid = SecureRandom.uuid %>
|
||||
<%= tag.input name: ff.field_name(:email), value: ff.object.email, type: :email, class: 'base-input', multiple: true, autocomplete: 'off', placeholder: t('default_email'), disabled: ff.object.is_requester || ff.object.invite_by_uuid.present? || ff.object.optional_invite_by_uuid.present?, id: field_uuid = SecureRandom.uuid %>
|
||||
<% else %>
|
||||
<toggle-attribute data-target-id="<%= email_field_uuid = SecureRandom.uuid %>" data-class-name="hidden" data-value="email">
|
||||
<%= ff.select :option, [[t('not_specified'), 'not_set'], [t('submission_requester'), 'is_requester'], [t('specified_email'), 'email'], *(@template.submitters - [submitter]).flat_map { |e| [[t('invite_by_name', name: e['name']), "invite_by_#{e['uuid']}"], [t('invite_by_name', name: e['name']) + " (#{t(:optional).capitalize})", "optional_invite_by_#{e['uuid']}"]] }, *(@template.submitters - [submitter]).map { |e| [t('same_as_name', name: e['name']), "linked_to_#{e['uuid']}"] }], {}, class: 'base-select mb-3' %>
|
||||
</toggle-attribute>
|
||||
<%= ff.email_field :email, class: "base-input #{'hidden' if item.option != 'email'}", autocomplete: 'off', placeholder: t('default_email'), id: email_field_uuid %>
|
||||
<%= tag.input name: ff.field_name(:email), type: :email, value: ff.object.email, multiple: true, class: "base-input #{'hidden' if item.option != 'email'}", autocomplete: 'off', placeholder: t('default_email'), id: email_field_uuid %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if @template.submitters.size == 2 %>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"ignored_warnings": [
|
||||
{
|
||||
"fingerprint": "25f4ce5fee1e1180fa1919dc4ee78db3ab3457a956e4679503aa745771a43836",
|
||||
"note": "Permitted parameters are necessary for creating submitters via API"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -20,6 +20,7 @@ en: &en
|
||||
language_ko: 한국어
|
||||
hi_there: Hi there
|
||||
thanks: Thanks
|
||||
edit_per_party: Edit per party
|
||||
reply_to: Reply to
|
||||
pending_by_me: Pending by me
|
||||
partially_completed: Partially completed
|
||||
@@ -694,6 +695,17 @@ en: &en
|
||||
overdue_payment: Overdue Payment
|
||||
your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Your Pro Plan has been suspended due to unpaid invoices. You can update your payment details to settle the invoice and continue using DocuSeal or cancel your subscription.
|
||||
manage_subscription: Manage Subscription
|
||||
submission_created_by_email_html: '<b>Submission created</b> by %{email}'
|
||||
submission_created_by_email_via_source_html: '<b>Submission created</b> by %{email} via %{source}'
|
||||
submission_created_via_source_html: '<b>Submission created</b> via %{source}'
|
||||
pro_user_seats_used: Pro user seats used
|
||||
manage_plan: Manage plan
|
||||
submission_sources:
|
||||
api: API
|
||||
bulk: Bulk Send
|
||||
embed: Embedding
|
||||
invie: Invite
|
||||
link: Link
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>Email sent</b> to %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>Reminder email sent</b> to %{submitter_name}'
|
||||
@@ -732,6 +744,7 @@ en: &en
|
||||
read: Read your data
|
||||
|
||||
es: &es
|
||||
edit_per_party: Editar por parte
|
||||
signed: Firmado
|
||||
reply_to: Responder a
|
||||
partially_completed: Parcialmente completado
|
||||
@@ -1408,6 +1421,17 @@ es: &es
|
||||
overdue_payment: Pago Atrasado
|
||||
your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Tu plan Pro ha sido suspendido debido a facturas impagas. Puedes actualizar tus datos de pago para liquidar la factura y seguir usando DocuSeal o cancelar tu suscripción.
|
||||
manage_subscription: Gestionar Suscripción
|
||||
submission_created_by_email_html: '<b>Envío creado</b> por %{email}'
|
||||
submission_created_by_email_via_source_html: '<b>Envío creado</b> por %{email} a través de %{source}'
|
||||
submission_created_via_source_html: '<b>Envío creado</b> a través de %{source}'
|
||||
pro_user_seats_used: Plazas de usuario Pro en uso
|
||||
manage_plan: Gestionar plan
|
||||
submission_sources:
|
||||
api: API
|
||||
bulk: Envío masivo
|
||||
embed: Integración
|
||||
invite: Invitación
|
||||
link: Enlace
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>Correo electrónico enviado</b> a %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>Correo de recordatorio enviado</b> a %{submitter_name}'
|
||||
@@ -1446,6 +1470,7 @@ es: &es
|
||||
read: Leer tus datos
|
||||
|
||||
it: &it
|
||||
edit_per_party: Modifica per partito
|
||||
signed: Firmato
|
||||
reply_to: Rispondi a
|
||||
pending_by_me: In sospeso da me
|
||||
@@ -2121,6 +2146,17 @@ it: &it
|
||||
overdue_payment: Pagamento Scaduto
|
||||
your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Il tuo piano Pro è stato sospeso a causa di fatture non pagate. Puoi aggiornare i tuoi dati di pagamento per saldare la fattura e continuare a utilizzare DocuSeal o annullare l'abbonamento.
|
||||
manage_subscription: Gestisci Abbonamento
|
||||
submission_created_by_email_html: '<b>Invio creato</b> da %{email}'
|
||||
submission_created_by_email_via_source_html: '<b>Invio creato</b> da %{email} tramite %{source}'
|
||||
submission_created_via_source_html: '<b>Invio creato</b> tramite %{source}'
|
||||
pro_user_seats_used: Posti utente Pro in uso
|
||||
manage_plan: Gestisci piano
|
||||
submission_sources:
|
||||
api: API
|
||||
bulk: Invio massivo
|
||||
embed: Incorporamento
|
||||
invite: Invito
|
||||
link: Link
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>E-mail inviato</b> a %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>E-mail di promemoria inviato</b> a %{submitter_name}'
|
||||
@@ -2159,6 +2195,7 @@ it: &it
|
||||
read: Leggi i tuoi dati
|
||||
|
||||
fr: &fr
|
||||
edit_per_party: Éditer par partie
|
||||
signed: Signé
|
||||
reply_to: Répondre à
|
||||
partially_completed: Partiellement complété
|
||||
@@ -2836,6 +2873,17 @@ fr: &fr
|
||||
overdue_payment: Paiement En Retard
|
||||
your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Votre plan Pro a été suspendu en raison de factures impayées. Vous pouvez mettre à jour vos informations de paiement pour régler la facture et continuer à utiliser DocuSeal ou annuler votre abonnement.
|
||||
manage_subscription: Gérer l'Abonnement
|
||||
submission_created_by_email_html: '<b>Soumission créée</b> par %{email}'
|
||||
submission_created_by_email_via_source_html: '<b>Soumission créée</b> par %{email} via %{source}'
|
||||
submission_created_via_source_html: '<b>Soumission créée</b> via %{source}'
|
||||
pro_user_seats_used: Places utilisateur Pro en cours d'utilisation
|
||||
manage_plan: Gérer le plan
|
||||
submission_sources:
|
||||
api: API
|
||||
bulk: Envoi en masse
|
||||
embed: Intégration
|
||||
invite: Invitation
|
||||
link: Lien
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>E-mail envoyé</b> à %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>E-mail de rappel envoyé</b> à %{submitter_name}'
|
||||
@@ -2874,6 +2922,7 @@ fr: &fr
|
||||
read: Lire vos données
|
||||
|
||||
pt: &pt
|
||||
edit_per_party: Edita por festa
|
||||
signed: Assinado
|
||||
reply_to: Responder a
|
||||
partially_completed: Parcialmente concluído
|
||||
@@ -3550,6 +3599,17 @@ pt: &pt
|
||||
overdue_payment: Pagamento Atrasado
|
||||
your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Seu plano Pro foi suspenso devido a faturas não pagas. Você pode atualizar seus dados de pagamento para quitar a fatura e continuar usando o DocuSeal ou cancelar sua assinatura.
|
||||
manage_subscription: Gerenciar Assinatura
|
||||
submission_created_by_email_html: '<b>Envio criado</b> por %{email}'
|
||||
submission_created_by_email_via_source_html: '<b>Envio criado</b> por %{email} via %{source}'
|
||||
submission_created_via_source_html: '<b>Envio criado</b> via %{source}'
|
||||
pro_user_seats_used: Lugares de usuário Pro em uso
|
||||
manage_plan: Gerenciar plano
|
||||
submission_sources:
|
||||
api: API
|
||||
bulk: Envio em massa
|
||||
embed: Incorporação
|
||||
invite: Convite
|
||||
link: Link
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>E-mail enviado</b> para %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>E-mail de lembrete enviado</b> para %{submitter_name}'
|
||||
@@ -3588,6 +3648,7 @@ pt: &pt
|
||||
read: Ler seus dados
|
||||
|
||||
de: &de
|
||||
edit_per_party: Bearbeiten pro Partei
|
||||
signed: Unterschrieben
|
||||
reply_to: Antworten auf
|
||||
partially_completed: Teilweise abgeschlossen
|
||||
@@ -4264,6 +4325,17 @@ de: &de
|
||||
overdue_payment: Überfällige Zahlung
|
||||
your_pro_plan_has_been_suspended_due_to_unpaid_invoices_you_can_update_your_payment_details_to_settle_the_invoice_and_continue_using_docuseal_or_cancel_your_subscription: Ihr Pro-Plan wurde aufgrund unbezahlter Rechnungen ausgesetzt. Sie können Ihre Zahlungsdaten aktualisieren, um die Rechnung zu begleichen und DocuSeal weiterhin zu nutzen, oder Ihr Abonnement kündigen.
|
||||
manage_subscription: Abonnement Verwalten
|
||||
submission_created_by_email_html: '<b>Übermittlung erstellt</b> von %{email}'
|
||||
submission_created_by_email_via_source_html: '<b>Übermittlung erstellt</b> durch %{email} über %{source}'
|
||||
submission_created_via_source_html: '<b>Übermittlung erstellt</b> über %{source}'
|
||||
pro_user_seats_used: Verwendete Pro-Benutzerplätze
|
||||
manage_plan: Plan verwalten
|
||||
submission_sources:
|
||||
api: API
|
||||
bulk: Massenversand
|
||||
embed: Einbettung
|
||||
invite: Einladung
|
||||
link: Link
|
||||
submission_event_names:
|
||||
send_email_to_html: '<b>E-Mail gesendet</b> an %{submitter_name}'
|
||||
send_reminder_email_to_html: '<b>Erinnerungs-E-Mail gesendet</b> an %{submitter_name}'
|
||||
|
||||
@@ -72,6 +72,7 @@ Rails.application.routes.draw do
|
||||
resources :submissions, only: %i[index], controller: 'submissions_dashboard'
|
||||
resources :submissions, only: %i[show destroy] do
|
||||
resources :unarchive, only: %i[create], controller: 'submissions_unarchive'
|
||||
resources :events, only: %i[index], controller: 'submission_events'
|
||||
end
|
||||
resources :submitters, only: %i[edit update]
|
||||
resources :console_redirect, only: %i[index]
|
||||
@@ -145,9 +146,7 @@ Rails.application.routes.draw do
|
||||
get :completed
|
||||
end
|
||||
|
||||
resources :send_submission_email, only: %i[create] do
|
||||
get :success, on: :collection
|
||||
end
|
||||
resources :send_submission_email, only: %i[create]
|
||||
|
||||
resources :submitters, only: %i[], param: 'slug' do
|
||||
resources :download, only: %i[index], controller: 'submissions_download'
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class CreateConsole1984Tables < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
create_table :console1984_sessions do |t|
|
||||
t.text :reason
|
||||
t.references :user, null: false, index: false
|
||||
t.timestamps
|
||||
|
||||
t.index :created_at
|
||||
t.index %i[user_id created_at]
|
||||
end
|
||||
|
||||
create_table :console1984_users do |t|
|
||||
t.string :username, null: false
|
||||
t.timestamps
|
||||
|
||||
t.index [:username]
|
||||
end
|
||||
|
||||
create_table :console1984_commands do |t|
|
||||
t.text :statements
|
||||
t.references :sensitive_access
|
||||
t.references :session, null: false, index: false
|
||||
t.timestamps
|
||||
|
||||
t.index %i[session_id created_at sensitive_access_id], name: 'on_session_and_sensitive_chronologically'
|
||||
end
|
||||
|
||||
create_table :console1984_sensitive_accesses do |t|
|
||||
t.text :justification
|
||||
t.references :session, null: false
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.2].define(version: 2024_12_07_172237) do
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_02_25_111255) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
@@ -112,6 +112,40 @@ ActiveRecord::Schema[7.2].define(version: 2024_12_07_172237) do
|
||||
t.index ["submitter_id"], name: "index_completed_submitters_on_submitter_id", unique: true
|
||||
end
|
||||
|
||||
create_table "console1984_commands", force: :cascade do |t|
|
||||
t.text "statements"
|
||||
t.bigint "sensitive_access_id"
|
||||
t.bigint "session_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["sensitive_access_id"], name: "index_console1984_commands_on_sensitive_access_id"
|
||||
t.index ["session_id", "created_at", "sensitive_access_id"], name: "on_session_and_sensitive_chronologically"
|
||||
end
|
||||
|
||||
create_table "console1984_sensitive_accesses", force: :cascade do |t|
|
||||
t.text "justification"
|
||||
t.bigint "session_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["session_id"], name: "index_console1984_sensitive_accesses_on_session_id"
|
||||
end
|
||||
|
||||
create_table "console1984_sessions", force: :cascade do |t|
|
||||
t.text "reason"
|
||||
t.bigint "user_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["created_at"], name: "index_console1984_sessions_on_created_at"
|
||||
t.index ["user_id", "created_at"], name: "index_console1984_sessions_on_user_id_and_created_at"
|
||||
end
|
||||
|
||||
create_table "console1984_users", force: :cascade do |t|
|
||||
t.string "username", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["username"], name: "index_console1984_users_on_username"
|
||||
end
|
||||
|
||||
create_table "document_generation_events", force: :cascade do |t|
|
||||
t.bigint "submitter_id", null: false
|
||||
t.string "event_name", null: false
|
||||
|
||||
@@ -8,19 +8,20 @@ module DownloadUtils
|
||||
module_function
|
||||
|
||||
def call(url)
|
||||
uri = Addressable::URI.parse(url)
|
||||
|
||||
if Docuseal.multitenant?
|
||||
raise UnableToDownload, "Error loading: #{uri.display_uri}. Only HTTPS is allowed." if uri.scheme != 'https'
|
||||
|
||||
if uri.host.in?(LOCALHOSTS)
|
||||
raise UnableToDownload, "Error loading: #{uri.display_uri}. Can't download from localhost."
|
||||
end
|
||||
uri = begin
|
||||
URI(url)
|
||||
rescue URI::Error
|
||||
Addressable::URI.parse(url).normalize
|
||||
end
|
||||
|
||||
resp = conn.get(uri.display_uri.to_s)
|
||||
if Docuseal.multitenant?
|
||||
raise UnableToDownload, "Error loading: #{uri}. Only HTTPS is allowed." if uri.scheme != 'https'
|
||||
raise UnableToDownload, "Error loading: #{uri}. Can't download from localhost." if uri.host.in?(LOCALHOSTS)
|
||||
end
|
||||
|
||||
raise UnableToDownload, "Error loading: #{uri.display_uri}" if resp.status >= 400
|
||||
resp = conn.get(uri)
|
||||
|
||||
raise UnableToDownload, "Error loading: #{uri}" if resp.status >= 400
|
||||
|
||||
resp
|
||||
end
|
||||
|
||||
@@ -24,6 +24,10 @@ module PdfIcons
|
||||
StringIO.new(logo_new_data)
|
||||
end
|
||||
|
||||
def stamp_logo_io
|
||||
StringIO.new(stamp_logo_data)
|
||||
end
|
||||
|
||||
def check_data
|
||||
@check_data ||= PATH.join('check.png').read
|
||||
end
|
||||
@@ -39,4 +43,8 @@ module PdfIcons
|
||||
def logo_new_data
|
||||
@logo_new_data ||= PATH.join('logo_new.png').read
|
||||
end
|
||||
|
||||
def stamp_logo_data
|
||||
@stamp_logo_data ||= PATH.join('stamp-logo.png').read
|
||||
end
|
||||
end
|
||||
|
||||
|
After Width: | Height: | Size: 26 KiB |
@@ -15,7 +15,10 @@ module ReplaceEmailVariables
|
||||
SUBMITTER_SLUG = /\{+submitter\.slug\}+/i
|
||||
SUBMISSION_LINK = /\{+submission\.link\}+/i
|
||||
SUBMISSION_ID = /\{+submission\.id\}+/i
|
||||
SUBMISSION_SUBMITTERS = /\{+submission\.submitters\}+/i
|
||||
SUBMITTERS = /\{+(?:submission\.)?submitters\}+/i
|
||||
SUBMITTERS_N_EMAIL = /\{+submitters\[(?<index>\d+)\]\.email\}+/i
|
||||
SUBMITTERS_N_NAME = /\{+submitters\[(?<index>\d+)\]\.name\}+/i
|
||||
SUBMITTERS_N_FIRST_NAME = /\{+submitters\[(?<index>\d+)\]\.first_name\}+/i
|
||||
DOCUMENTS_LINKS = /\{+documents\.links\}+/i
|
||||
DOCUMENTS_LINK = /\{+documents\.link\}+/i
|
||||
|
||||
@@ -37,13 +40,25 @@ module ReplaceEmailVariables
|
||||
text = replace(text, SUBMISSION_LINK, html_escape:) do
|
||||
submitter.submission ? build_submission_link(submitter.submission) : ''
|
||||
end
|
||||
text = replace(text, SUBMISSION_SUBMITTERS, html_escape:) { build_submission_submitters(submitter.submission) }
|
||||
text = replace(text, SUBMITTERS, html_escape:) { build_submission_submitters(submitter.submission) }
|
||||
text = replace(text, DOCUMENTS_LINKS, html_escape:) { build_documents_links_text(submitter, sig) }
|
||||
text = replace(text, DOCUMENTS_LINK, html_escape:) { build_documents_links_text(submitter, sig) }
|
||||
text = replace(text, ACCOUNT_NAME, html_escape:) { submitter.submission.account.name }
|
||||
text = replace(text, SENDER_NAME, html_escape:) { submitter.submission.created_by_user&.full_name }
|
||||
text = replace(text, SENDER_FIRST_NAME, html_escape:) { submitter.submission.created_by_user&.first_name }
|
||||
|
||||
text = replace(text, SUBMITTERS_N_NAME, html_escape:) do |match|
|
||||
build_submitters_n_field(submitter.submission, match[:index].to_i - 1, :name)
|
||||
end
|
||||
|
||||
text = replace(text, SUBMITTERS_N_EMAIL, html_escape:) do |match|
|
||||
build_submitters_n_field(submitter.submission, match[:index].to_i - 1, :email)
|
||||
end
|
||||
|
||||
text = replace(text, SUBMITTERS_N_FIRST_NAME, html_escape:) do |match|
|
||||
build_submitters_n_field(submitter.submission, match[:index].to_i - 1, :first_name)
|
||||
end
|
||||
|
||||
replace(text, SENDER_EMAIL, html_escape:) { submitter.submission.created_by_user&.email.to_s.sub(/\+\w+@/, '@') }
|
||||
end
|
||||
# rubocop:enable Metrics
|
||||
@@ -54,12 +69,18 @@ module ReplaceEmailVariables
|
||||
)
|
||||
end
|
||||
|
||||
def build_submitters_n_field(submission, index, field_name)
|
||||
uuid = (submission.template_submitters || submission.template.submitters).dig(index, 'uuid')
|
||||
|
||||
submission.submitters.find { |s| s.uuid == uuid }.try(field_name)
|
||||
end
|
||||
|
||||
def replace(text, var, html_escape: false)
|
||||
text.gsub(var) do
|
||||
if html_escape
|
||||
ERB::Util.html_escape(yield)
|
||||
ERB::Util.html_escape(yield(Regexp.last_match))
|
||||
else
|
||||
yield
|
||||
yield(Regexp.last_match)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,10 +3,28 @@
|
||||
module SendWebhookRequest
|
||||
USER_AGENT = 'DocuSeal.com Webhook'
|
||||
|
||||
LOCALHOSTS = %w[0.0.0.0 127.0.0.1 localhost].freeze
|
||||
|
||||
HttpsError = Class.new(StandardError)
|
||||
LocalhostError = Class.new(StandardError)
|
||||
|
||||
module_function
|
||||
|
||||
def call(webhook_url, event_type:, data:)
|
||||
Faraday.post(webhook_url.url) do |req|
|
||||
uri = begin
|
||||
URI(webhook_url.url)
|
||||
rescue URI::Error
|
||||
Addressable::URI.parse(webhook_url.url).normalize
|
||||
end
|
||||
|
||||
if Docuseal.multitenant?
|
||||
raise HttpsError, 'Only HTTPS is allowed.' if uri.scheme != 'https' &&
|
||||
!AccountConfig.exists?(key: :allow_http,
|
||||
account_id: webhook_url.account_id)
|
||||
raise LocalhostError, "Can't send to localhost." if uri.host.in?(LOCALHOSTS)
|
||||
end
|
||||
|
||||
Faraday.post(uri) do |req|
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
req.headers['User-Agent'] = USER_AGENT
|
||||
req.headers.merge!(webhook_url.secret.to_h) if webhook_url.secret.present?
|
||||
|
||||
@@ -107,6 +107,7 @@ module Submissions
|
||||
|
||||
def normalize_email(email)
|
||||
return if email.blank?
|
||||
return if email.is_a?(Numeric)
|
||||
|
||||
return email.downcase if email.to_s.include?(',') ||
|
||||
email.to_s.match?(/\.(?:gob|om|mm|cm|et|mo|nz|za|ie)\z/) ||
|
||||
|
||||
@@ -36,8 +36,8 @@ module Submissions
|
||||
|
||||
is_order_sent = submitters_order == 'random' || index.zero?
|
||||
|
||||
build_submitter(submission:, attrs: submitter_attrs, uuid:,
|
||||
is_order_sent:, user:,
|
||||
build_submitter(submission:, attrs: submitter_attrs,
|
||||
uuid:, is_order_sent:, user:, params:,
|
||||
preferences: preferences.merge(submission_preferences))
|
||||
end
|
||||
|
||||
@@ -69,6 +69,16 @@ module Submissions
|
||||
end
|
||||
end
|
||||
|
||||
def submitter_message_preferences(uuid, params)
|
||||
return {} if params[:request_email_per_submitter] != '1'
|
||||
return {} if params[:is_custom_message] != '1'
|
||||
|
||||
{
|
||||
'subject' => params.dig('submitter_preferences', uuid, 'subject'),
|
||||
'body' => params.dig('submitter_preferences', uuid, 'body')
|
||||
}.compact_blank
|
||||
end
|
||||
|
||||
def maybe_set_template_fields(submission, submitters_attrs, default_submitter_uuid: nil)
|
||||
template_fields = (submission.template_fields || submission.template.fields).deep_dup
|
||||
|
||||
@@ -175,9 +185,10 @@ module Submissions
|
||||
uuid || template.submitters[index]&.dig('uuid')
|
||||
end
|
||||
|
||||
def build_submitter(submission:, attrs:, uuid:, is_order_sent:, user:, preferences:)
|
||||
def build_submitter(submission:, attrs:, uuid:, is_order_sent:, user:, preferences:, params:)
|
||||
email = Submissions.normalize_email(attrs[:email])
|
||||
submitter_preferences = Submitters.normalize_preferences(submission.account, user, attrs)
|
||||
submitter_preferences = Submitters.normalize_preferences(submission.account, user,
|
||||
attrs.merge(submitter_message_preferences(uuid, params)))
|
||||
values = attrs[:values] || {}
|
||||
|
||||
phone_field_uuid = find_phone_field(submission, values)&.dig('uuid')
|
||||
@@ -214,9 +225,15 @@ module Submissions
|
||||
|
||||
def assign_completed_attributes(submitter)
|
||||
submitter.values = Submitters::SubmitValues.merge_default_values(submitter)
|
||||
submitter.values = Submitters::SubmitValues.merge_formula_values(submitter)
|
||||
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
||||
|
||||
formula_values = Submitters::SubmitValues.build_formula_values(submitter)
|
||||
|
||||
if formula_values.present?
|
||||
submitter.values = submitter.values.merge(formula_values)
|
||||
submitter.values = Submitters::SubmitValues.maybe_remove_condition_values(submitter)
|
||||
end
|
||||
|
||||
submitter.values = submitter.values.transform_values do |v|
|
||||
v == '{{date}}' ? Time.current.in_time_zone(submitter.submission.account.timezone).to_date.to_s : v
|
||||
end
|
||||
|
||||
@@ -37,30 +37,45 @@ module Submissions
|
||||
submissions.where(created_by_user_id: user&.id || -1)
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def filter_by_status(submissions, filters)
|
||||
submissions = submissions.pending if filters[:status] == 'pending'
|
||||
submissions = submissions.completed if filters[:status] == 'completed'
|
||||
submissions = submissions.declined if filters[:status] == 'declined'
|
||||
submissions = submissions.expired if filters[:status] == 'expired'
|
||||
|
||||
if filters[:status] == 'partially_completed'
|
||||
submissions =
|
||||
submissions.joins(:submitters)
|
||||
.group(:id)
|
||||
.having(Arel::Nodes::NamedFunction.new(
|
||||
'COUNT', [Arel::Nodes::NamedFunction.new('NULLIF',
|
||||
[Submitter.arel_table[:completed_at].eq(nil),
|
||||
Arel::Nodes.build_quoted(false)])]
|
||||
).gt(0))
|
||||
.having(Arel::Nodes::NamedFunction.new(
|
||||
'COUNT', [Arel::Nodes::NamedFunction.new('NULLIF',
|
||||
[Submitter.arel_table[:completed_at].not_eq(nil),
|
||||
Arel::Nodes.build_quoted(false)])]
|
||||
).gt(0))
|
||||
case filters[:status]
|
||||
when 'pending'
|
||||
submissions.pending
|
||||
when 'completed'
|
||||
submissions.completed
|
||||
when 'declined'
|
||||
submissions.declined
|
||||
when 'expired'
|
||||
submissions.expired
|
||||
when 'sent'
|
||||
submissions.joins(:submitters)
|
||||
.where(submitters: { opened_at: nil, completed_at: nil, declined_at: nil })
|
||||
.where.not(submitters: { sent_at: nil })
|
||||
.group(:id)
|
||||
when 'opened'
|
||||
submissions.joins(:submitters)
|
||||
.where(submitters: { completed_at: nil, declined_at: nil })
|
||||
.where.not(submitters: { opened_at: nil })
|
||||
.group(:id)
|
||||
when 'partially_completed'
|
||||
submissions.joins(:submitters)
|
||||
.group(:id)
|
||||
.having(Arel::Nodes::NamedFunction.new(
|
||||
'COUNT', [Arel::Nodes::NamedFunction.new('NULLIF',
|
||||
[Submitter.arel_table[:completed_at].eq(nil),
|
||||
Arel::Nodes.build_quoted(false)])]
|
||||
).gt(0))
|
||||
.having(Arel::Nodes::NamedFunction.new(
|
||||
'COUNT', [Arel::Nodes::NamedFunction.new('NULLIF',
|
||||
[Submitter.arel_table[:completed_at].not_eq(nil),
|
||||
Arel::Nodes.build_quoted(false)])]
|
||||
).gt(0))
|
||||
else
|
||||
submissions
|
||||
end
|
||||
|
||||
submissions
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
def filter_by_created_at(submissions, filters)
|
||||
submissions = submissions.where(created_at: filters[:created_at_from]..) if filters[:created_at_from].present?
|
||||
|
||||
@@ -23,9 +23,15 @@ module Submissions
|
||||
**Submissions::GenerateResultAttachments.build_signing_params(submitter, pkcs, tsa_url)
|
||||
}
|
||||
|
||||
pdf.sign(io, **sign_params)
|
||||
begin
|
||||
pdf.sign(io, **sign_params)
|
||||
rescue HexaPDF::MalformedPDFError => e
|
||||
Rollbar.error(e) if defined?(Rollbar)
|
||||
|
||||
pdf.sign(io, write_options: { incremental: false }, **sign_params)
|
||||
end
|
||||
else
|
||||
pdf.write(io)
|
||||
pdf.write(io, incremental: true, validate: false)
|
||||
end
|
||||
|
||||
Submissions::GenerateResultAttachments.maybe_enable_ltv(io, sign_params)
|
||||
|
||||
@@ -18,9 +18,12 @@ module Submissions
|
||||
TEXT_TOP_MARGIN = 1
|
||||
MAX_PAGE_ROTATE = 20
|
||||
|
||||
COURIER_FONT = 'Courier'
|
||||
|
||||
A4_SIZE = [595, 842].freeze
|
||||
|
||||
TESTING_FOOTER = 'Testing Document - NOT LEGALLY BINDING'
|
||||
DEFAULT_FONTS = %w[Times Helvetica Courier].freeze
|
||||
|
||||
MISSING_GLYPH_REPLACE = {
|
||||
'▪' => '-',
|
||||
@@ -188,7 +191,9 @@ module Submissions
|
||||
|
||||
fill_color = field.dig('preferences', 'color').presence
|
||||
|
||||
font = pdf.fonts.add(field.dig('preferences', 'font').presence || FONT_NAME)
|
||||
font_name = field.dig('preferences', 'font')
|
||||
font_name = FONT_NAME unless font_name.in?(DEFAULT_FONTS)
|
||||
font = pdf.fonts.add(font_name)
|
||||
|
||||
value = submitter.values[field['uuid']]
|
||||
value = field['default_value'] if field['type'] == 'heading'
|
||||
@@ -435,18 +440,19 @@ module Submissions
|
||||
|
||||
value = TextUtils.maybe_rtl_reverse(Array.wrap(value).join(', '))
|
||||
|
||||
text = HexaPDF::Layout::TextFragment.create(value, font:,
|
||||
fill_color:,
|
||||
font_size:)
|
||||
text_params = { font:, fill_color:, font_size: }
|
||||
text_params[:line_height] = text_params[:font_size] * 1.6 if font_name == COURIER_FONT
|
||||
|
||||
text = HexaPDF::Layout::TextFragment.create(value, **text_params)
|
||||
|
||||
lines = layouter.fit([text], area['w'] * width, height).lines
|
||||
box_height = lines.sum(&:height)
|
||||
|
||||
if preferences_font_size.blank? && box_height > (area['h'] * height) + 1
|
||||
text = HexaPDF::Layout::TextFragment.create(value,
|
||||
font:,
|
||||
fill_color:,
|
||||
font_size: (font_size / 1.4).to_i)
|
||||
text_params[:font_size] = (font_size / 1.4).to_i
|
||||
text_params[:line_height] = text_params[:font_size] * 1.6 if font_name == COURIER_FONT
|
||||
|
||||
text = HexaPDF::Layout::TextFragment.create(value, **text_params)
|
||||
|
||||
lines = layouter.fit([text], field['type'].in?(%w[date number]) ? width : area['w'] * width, height).lines
|
||||
|
||||
@@ -454,10 +460,10 @@ module Submissions
|
||||
end
|
||||
|
||||
if preferences_font_size.blank? && box_height > (area['h'] * height) + 1
|
||||
text = HexaPDF::Layout::TextFragment.create(value,
|
||||
font:,
|
||||
fill_color:,
|
||||
font_size: (font_size / 1.9).to_i)
|
||||
text_params[:font_size] = (font_size / 1.9).to_i
|
||||
text_params[:line_height] = text_params[:font_size] * 1.6 if font_name == COURIER_FONT
|
||||
|
||||
text = HexaPDF::Layout::TextFragment.create(value, **text_params)
|
||||
|
||||
lines = layouter.fit([text], field['type'].in?(%w[date number]) ? width : area['w'] * width, height).lines
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ module Submitters
|
||||
end
|
||||
|
||||
def load_logo(_submitter)
|
||||
PdfIcons.logo_io
|
||||
PdfIcons.stamp_logo_io
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -53,9 +53,17 @@ module Submitters
|
||||
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)
|
||||
submitter.values = merge_formula_values(submitter)
|
||||
|
||||
formula_values = build_formula_values(submitter)
|
||||
|
||||
if formula_values.present?
|
||||
submitter.values = submitter.values.merge(formula_values)
|
||||
submitter.values = maybe_remove_condition_values(submitter)
|
||||
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
|
||||
@@ -149,7 +157,7 @@ module Submitters
|
||||
default_values.compact_blank.merge(submitter.values)
|
||||
end
|
||||
|
||||
def merge_formula_values(submitter)
|
||||
def build_formula_values(submitter)
|
||||
computed_values = submitter.submission.template_fields.each_with_object({}) do |field, acc|
|
||||
next if field['submitter_uuid'] != submitter.uuid
|
||||
next if field['type'] == 'payment'
|
||||
@@ -161,7 +169,7 @@ module Submitters
|
||||
acc[field['uuid']] = calculate_formula_value(formula, submitter.values.merge(acc.compact_blank))
|
||||
end
|
||||
|
||||
submitter.values.merge(computed_values.compact_blank)
|
||||
computed_values.compact_blank
|
||||
end
|
||||
|
||||
def calculate_formula_value(_formula, _values)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# This rake task was added by annotate_rb gem.
|
||||
|
||||
# Can set `ANNOTATERB_SKIP_ON_DB_TASKS` to be anything to skip this
|
||||
if Rails.env.development? && ENV['ANNOTATERB_SKIP_ON_DB_TASKS'].nil?
|
||||
require 'annotate_rb'
|
||||
|
||||
AnnotateRb::Core.load_rake_tasks
|
||||
end
|
||||
@@ -1,57 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
if Rails.env.development?
|
||||
require 'annotate'
|
||||
|
||||
task set_annotation_options: :environment do
|
||||
Annotate.set_defaults(
|
||||
'active_admin' => 'false',
|
||||
'additional_file_patterns' => [],
|
||||
'routes' => 'false',
|
||||
'models' => 'true',
|
||||
'position_in_routes' => 'before',
|
||||
'position_in_class' => 'before',
|
||||
'position_in_test' => 'before',
|
||||
'position_in_fixture' => 'before',
|
||||
'position_in_factory' => 'before',
|
||||
'position_in_serializer' => 'before',
|
||||
'show_foreign_keys' => 'true',
|
||||
'show_complete_foreign_keys' => 'false',
|
||||
'show_indexes' => 'true',
|
||||
'simple_indexes' => 'false',
|
||||
'model_dir' => 'app/models',
|
||||
'root_dir' => '',
|
||||
'include_version' => 'false',
|
||||
'require' => '',
|
||||
'exclude_tests' => 'true',
|
||||
'exclude_fixtures' => 'true',
|
||||
'exclude_factories' => 'true',
|
||||
'exclude_serializers' => 'false',
|
||||
'exclude_scaffolds' => 'true',
|
||||
'exclude_controllers' => 'true',
|
||||
'exclude_helpers' => 'true',
|
||||
'exclude_sti_subclasses' => 'false',
|
||||
'ignore_model_sub_dir' => 'false',
|
||||
'ignore_columns' => nil,
|
||||
'ignore_routes' => nil,
|
||||
'ignore_unknown_models' => 'false',
|
||||
'hide_limit_column_types' => 'integer,bigint,boolean',
|
||||
'hide_default_column_types' => 'json,jsonb,hstore',
|
||||
'skip_on_db_migrate' => 'false',
|
||||
'format_bare' => 'true',
|
||||
'format_rdoc' => 'false',
|
||||
'format_yard' => 'false',
|
||||
'format_markdown' => 'false',
|
||||
'sort' => 'false',
|
||||
'force' => 'false',
|
||||
'frozen' => 'false',
|
||||
'classified_sort' => 'true',
|
||||
'trace' => 'false',
|
||||
'wrapper_open' => nil,
|
||||
'wrapper_close' => nil,
|
||||
'with_comment' => 'true'
|
||||
)
|
||||
end
|
||||
|
||||
Annotate.load_tasks
|
||||
end
|
||||
@@ -9,7 +9,6 @@ module Templates
|
||||
|
||||
template.external_id = external_id
|
||||
template.author = author
|
||||
template.preferences = original_template.preferences.deep_dup
|
||||
template.name = name.presence || "#{original_template.name} (#{I18n.t('clone')})"
|
||||
|
||||
if folder_name.present?
|
||||
@@ -18,10 +17,11 @@ module Templates
|
||||
template.folder_id = original_template.folder_id
|
||||
end
|
||||
|
||||
template.submitters, template.fields, template.schema =
|
||||
template.submitters, template.fields, template.schema, template.preferences =
|
||||
update_submitters_and_fields_and_schema(original_template.submitters.deep_dup,
|
||||
original_template.fields.deep_dup,
|
||||
original_template.schema.deep_dup)
|
||||
original_template.schema.deep_dup,
|
||||
original_template.preferences.deep_dup)
|
||||
|
||||
if name.present? && template.schema.size == 1 &&
|
||||
original_template.schema.first['name'] == original_template.name &&
|
||||
@@ -32,7 +32,8 @@ module Templates
|
||||
template
|
||||
end
|
||||
|
||||
def update_submitters_and_fields_and_schema(cloned_submitters, cloned_fields, cloned_schema)
|
||||
# rubocop:disable Metrics, Style/CombinableLoops
|
||||
def update_submitters_and_fields_and_schema(cloned_submitters, cloned_fields, cloned_schema, cloned_preferences)
|
||||
submitter_uuids_replacements = {}
|
||||
field_uuids_replacements = {}
|
||||
|
||||
@@ -43,6 +44,24 @@ module Templates
|
||||
submitter['uuid'] = new_submitter_uuid
|
||||
end
|
||||
|
||||
cloned_submitters.each do |submitter|
|
||||
if submitter['optional_invite_by_uuid'].present?
|
||||
submitter['optional_invite_by_uuid'] = submitter_uuids_replacements[submitter['optional_invite_by_uuid']]
|
||||
end
|
||||
|
||||
if submitter['invite_by_uuid'].present?
|
||||
submitter['invite_by_uuid'] = submitter_uuids_replacements[submitter['invite_by_uuid']]
|
||||
end
|
||||
|
||||
if submitter['linked_to_uuid'].present?
|
||||
submitter['linked_to_uuid'] = submitter_uuids_replacements[submitter['linked_to_uuid']]
|
||||
end
|
||||
end
|
||||
|
||||
cloned_preferences['submitters'].to_a.each do |submitter|
|
||||
submitter['uuid'] = submitter_uuids_replacements[submitter['uuid']]
|
||||
end
|
||||
|
||||
cloned_fields.each do |field|
|
||||
new_field_uuid = SecureRandom.uuid
|
||||
|
||||
@@ -73,7 +92,8 @@ module Templates
|
||||
end
|
||||
end
|
||||
|
||||
[cloned_submitters, cloned_fields, cloned_schema]
|
||||
[cloned_submitters, cloned_fields, cloned_schema, cloned_preferences]
|
||||
end
|
||||
# rubocop:enable Metrics, Style/CombinableLoops
|
||||
end
|
||||
end
|
||||
|
||||
@@ -101,7 +101,7 @@ module Templates
|
||||
|
||||
{
|
||||
uuid: SecureRandom.uuid,
|
||||
required: false,
|
||||
required: field.flags.include?(:required),
|
||||
preferences: {},
|
||||
areas:,
|
||||
**field_properties
|
||||
|
||||
@@ -9,6 +9,7 @@ FactoryBot.define do
|
||||
|
||||
transient do
|
||||
submitter_count { 1 }
|
||||
attachment_count { 1 }
|
||||
only_field_types do
|
||||
%w[text date checkbox radio signature number multiple select initials image file stamp cells phone payment]
|
||||
end
|
||||
@@ -16,20 +17,6 @@ FactoryBot.define do
|
||||
end
|
||||
|
||||
after(:create) do |template, ev|
|
||||
blob = ActiveStorage::Blob.create_and_upload!(
|
||||
io: Rails.root.join('spec/fixtures/sample-document.pdf').open,
|
||||
filename: 'sample-document.pdf',
|
||||
content_type: 'application/pdf'
|
||||
)
|
||||
attachment = ActiveStorage::Attachment.create!(
|
||||
blob:,
|
||||
name: :documents,
|
||||
record: template
|
||||
)
|
||||
|
||||
Templates::ProcessDocument.call(attachment, attachment.download)
|
||||
|
||||
template.schema = [{ attachment_uuid: attachment.uuid, name: 'sample-document' }]
|
||||
number_words = %w[first second third fourth fifth sixth seventh eighth ninth tenth]
|
||||
|
||||
template.submitters = Array.new(ev.submitter_count) do |i|
|
||||
@@ -39,296 +26,319 @@ FactoryBot.define do
|
||||
}
|
||||
end
|
||||
|
||||
template.fields = template.submitters.reduce([]) do |fields, submitter|
|
||||
fields += [
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'First Name',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.09273546006944444,
|
||||
'y' => 0.1099851117387033,
|
||||
'w' => 0.2701497395833333,
|
||||
'h' => 0.0372705365913556,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Birthday',
|
||||
'type' => 'date',
|
||||
'required' => true,
|
||||
'preferences' => { 'format' => 'DD/MM/YYYY' },
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.09166666666666666,
|
||||
'y' => 0.1762778204144282,
|
||||
'w' => 0.2763888888888889,
|
||||
'h' => 0.0359029261474578,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Do you agree?',
|
||||
'type' => 'checkbox',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.09051106770833334,
|
||||
'y' => 0.227587027259332,
|
||||
'w' => 0.2784450954861111,
|
||||
'h' => 0.04113074042239687,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'First child',
|
||||
'type' => 'radio',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'options' => [
|
||||
{ 'value' => 'Girl', 'uuid' => SecureRandom.uuid },
|
||||
{ 'value' => 'Boy', 'uuid' => SecureRandom.uuid }
|
||||
],
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.09027777777777778,
|
||||
'y' => 0.3020184190330008,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Signature',
|
||||
'type' => 'signature',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.08611111111111111,
|
||||
'y' => 0.3487183422870299,
|
||||
'w' => 0.2,
|
||||
'h' => 0.0707269155206287,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'House number',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.08333333333333333,
|
||||
'y' => 0.4582041442824252,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Colors',
|
||||
'type' => 'multiple',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'options' => [
|
||||
{ 'value' => 'Red', 'uuid' => SecureRandom.uuid },
|
||||
{ 'value' => 'Green', 'uuid' => SecureRandom.uuid },
|
||||
{ 'value' => 'Blue', 'uuid' => SecureRandom.uuid }
|
||||
],
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.45,
|
||||
'y' => 0.1133998465080583,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Gender',
|
||||
'type' => 'select',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'options' => [
|
||||
{ 'value' => 'Male', 'uuid' => SecureRandom.uuid },
|
||||
{ 'value' => 'Female', 'uuid' => SecureRandom.uuid }
|
||||
],
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.4513888888888889,
|
||||
'y' => 0.1752954719877206,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Initials',
|
||||
'type' => 'initials',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.4486111111111111,
|
||||
'y' => 0.2273599386032233,
|
||||
'w' => 0.1,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Avatar',
|
||||
'type' => 'image',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.7180555555555556,
|
||||
'y' => 0.1129547198772064,
|
||||
'w' => 0.2,
|
||||
'h' => 0.1414538310412574,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Attachment',
|
||||
'type' => 'file',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.7166666666666667,
|
||||
'y' => 0.3020107444359171,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Stamp',
|
||||
'type' => 'stamp',
|
||||
'required' => true,
|
||||
'readonly' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.7166666666666667,
|
||||
'y' => 0.3771910974673829,
|
||||
'w' => 0.2,
|
||||
'h' => 0.0707269155206287,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Cell code',
|
||||
'type' => 'cells',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.4472222222222222,
|
||||
'y' => 0.3530851880276286,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'cell_w' => 0.04,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Payment',
|
||||
'type' => 'payment',
|
||||
'required' => true,
|
||||
'preferences' => { 'currency' => 'EUR', 'price' => 1000 },
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.4486111111111111,
|
||||
'y' => 0.43168073676132,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => 'Mobile Phone',
|
||||
'type' => 'phone',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.44443359375,
|
||||
'y' => 0.3010283960092095,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
}
|
||||
].select { |f| ev.only_field_types.include?(f['type']) && ev.except_field_types.exclude?(f['type']) }
|
||||
ev.attachment_count.times do |i|
|
||||
attachment_index = i + 1 if i > 0
|
||||
field_index = "(#{attachment_index})" if attachment_index
|
||||
|
||||
fields
|
||||
blob = ActiveStorage::Blob.create_and_upload!(
|
||||
io: Rails.root.join('spec/fixtures/sample-document.pdf').open,
|
||||
filename: 'sample-document.pdf',
|
||||
content_type: 'application/pdf'
|
||||
)
|
||||
attachment = ActiveStorage::Attachment.create!(
|
||||
blob:,
|
||||
name: :documents,
|
||||
record: template
|
||||
)
|
||||
|
||||
Templates::ProcessDocument.call(attachment, attachment.download)
|
||||
|
||||
template.schema << {
|
||||
attachment_uuid: attachment.uuid,
|
||||
name: ['sample-document', attachment_index].compact.join('-')
|
||||
}
|
||||
|
||||
template.fields += template.submitters.reduce([]) do |fields, submitter|
|
||||
fields += [
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => ['First Name', field_index].compact.join(' '),
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.09273546006944444,
|
||||
'y' => 0.1099851117387033,
|
||||
'w' => 0.2701497395833333,
|
||||
'h' => 0.0372705365913556,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => ['Birthday', field_index].compact.join(' '),
|
||||
'type' => 'date',
|
||||
'required' => true,
|
||||
'preferences' => { 'format' => 'DD/MM/YYYY' },
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.09166666666666666,
|
||||
'y' => 0.1762778204144282,
|
||||
'w' => 0.2763888888888889,
|
||||
'h' => 0.0359029261474578,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => ['Do you agree?', field_index].compact.join(' '),
|
||||
'type' => 'checkbox',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.09051106770833334,
|
||||
'y' => 0.227587027259332,
|
||||
'w' => 0.2784450954861111,
|
||||
'h' => 0.04113074042239687,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => ['First child', field_index].compact.join(' '),
|
||||
'type' => 'radio',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'options' => [
|
||||
{ 'value' => 'Girl', 'uuid' => SecureRandom.uuid },
|
||||
{ 'value' => 'Boy', 'uuid' => SecureRandom.uuid }
|
||||
],
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.09027777777777778,
|
||||
'y' => 0.3020184190330008,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => ['Signature', field_index].compact.join(' '),
|
||||
'type' => 'signature',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.08611111111111111,
|
||||
'y' => 0.3487183422870299,
|
||||
'w' => 0.2,
|
||||
'h' => 0.0707269155206287,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => ['House number', field_index].compact.join(' '),
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.08333333333333333,
|
||||
'y' => 0.4582041442824252,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => ['Colors', field_index].compact.join(' '),
|
||||
'type' => 'multiple',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'options' => [
|
||||
{ 'value' => 'Red', 'uuid' => SecureRandom.uuid },
|
||||
{ 'value' => 'Green', 'uuid' => SecureRandom.uuid },
|
||||
{ 'value' => 'Blue', 'uuid' => SecureRandom.uuid }
|
||||
],
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.45,
|
||||
'y' => 0.1133998465080583,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => ['Gender', field_index].compact.join(' '),
|
||||
'type' => 'select',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'options' => [
|
||||
{ 'value' => 'Male', 'uuid' => SecureRandom.uuid },
|
||||
{ 'value' => 'Female', 'uuid' => SecureRandom.uuid }
|
||||
],
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.4513888888888889,
|
||||
'y' => 0.1752954719877206,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => ['Initials', field_index].compact.join(' '),
|
||||
'type' => 'initials',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.4486111111111111,
|
||||
'y' => 0.2273599386032233,
|
||||
'w' => 0.1,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => ['Avatar', field_index].compact.join(' '),
|
||||
'type' => 'image',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.7180555555555556,
|
||||
'y' => 0.1129547198772064,
|
||||
'w' => 0.2,
|
||||
'h' => 0.1414538310412574,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => ['Attachment', field_index].compact.join(' '),
|
||||
'type' => 'file',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.7166666666666667,
|
||||
'y' => 0.3020107444359171,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => ['Stamp', field_index].compact.join(' '),
|
||||
'type' => 'stamp',
|
||||
'required' => true,
|
||||
'readonly' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.7166666666666667,
|
||||
'y' => 0.3771910974673829,
|
||||
'w' => 0.2,
|
||||
'h' => 0.0707269155206287,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => ['Cell code', field_index].compact.join(' '),
|
||||
'type' => 'cells',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.4472222222222222,
|
||||
'y' => 0.3530851880276286,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'cell_w' => 0.04,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => ['Payment', field_index].compact.join(' '),
|
||||
'type' => 'payment',
|
||||
'required' => true,
|
||||
'preferences' => { 'currency' => 'EUR', 'price' => 1000 },
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.4486111111111111,
|
||||
'y' => 0.43168073676132,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'uuid' => SecureRandom.uuid,
|
||||
'submitter_uuid' => submitter['uuid'],
|
||||
'name' => ['Mobile Phone', field_index].compact.join(' '),
|
||||
'type' => 'phone',
|
||||
'required' => true,
|
||||
'preferences' => {},
|
||||
'areas' => [
|
||||
{
|
||||
'x' => 0.44443359375,
|
||||
'y' => 0.3010283960092095,
|
||||
'w' => 0.2,
|
||||
'h' => 0.02857142857142857,
|
||||
'attachment_uuid' => attachment.uuid,
|
||||
'page' => 0
|
||||
}
|
||||
]
|
||||
}
|
||||
].select { |f| ev.only_field_types.include?(f['type']) && ev.except_field_types.exclude?(f['type']) }
|
||||
|
||||
fields
|
||||
end
|
||||
end
|
||||
|
||||
template.save!
|
||||
|
||||