mirror of
https://github.com/docusealco/docuseal
synced 2026-11-04 14:42:32 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
182f593d86 | ||
|
|
7f4548e5c7 | ||
|
|
5e4f207844 | ||
|
|
1f29e624e7 | ||
|
|
658e8a7bd6 | ||
|
|
08c3b04511 | ||
|
|
7779eb880d | ||
|
|
21b9ca8ea5 | ||
|
|
db7e3794d9 | ||
|
|
df1d78897a | ||
|
|
e1eb2da6f9 | ||
|
|
a92d1d82c1 | ||
|
|
2dcfc41365 |
No files matched your search
@@ -61,5 +61,8 @@ RSpec/ExampleLength:
|
||||
Rails/I18nLocaleTexts:
|
||||
Enabled: false
|
||||
|
||||
Rails/SkipsModelValidations:
|
||||
Enabled: false
|
||||
|
||||
Rails/ApplicationController:
|
||||
Enabled: false
|
||||
@@ -44,13 +44,6 @@ DocuSeal is an open source platform that provides secure and efficient digital d
|
||||
- [x] Mobile-optimized
|
||||
- [x] Easy to deploy in minutes
|
||||
|
||||
## For Companies
|
||||
### Integrate seamless document signing into your web or mobile apps with DocuSeal!
|
||||
|
||||
At DocuSeal we have expertise and technologies to make documents creation, filling, signing and processing seamlessly integrated with your product. We specialize in working with various industries, including **Banking, Healthcare, Transport, eCommerce, KYC, CRM, and other software products** that require bulk document signing. By leveraging DocuSeal, we can assist in reducing the overall cost of developing and processing electronic documents while ensuring security and compliance with local electronic document laws.
|
||||
|
||||
[](https://cal.com/docuseal)
|
||||
|
||||
## Deploy
|
||||
|
||||
|Heroku|Railway|
|
||||
@@ -80,6 +73,13 @@ Run the app under a custom domain over https using docker compose (make sure you
|
||||
HOST=your-domain-name.com docker-compose up
|
||||
```
|
||||
|
||||
## For Companies
|
||||
### Integrate seamless document signing into your web or mobile apps with DocuSeal!
|
||||
|
||||
At DocuSeal we have expertise and technologies to make documents creation, filling, signing and processing seamlessly integrated with your product. We specialize in working with various industries, including **Banking, Healthcare, Transport, eCommerce, KYC, CRM, and other software products** that require bulk document signing. By leveraging DocuSeal, we can assist in reducing the overall cost of developing and processing electronic documents while ensuring security and compliance with local electronic document laws.
|
||||
|
||||
[](https://cal.com/docuseal)
|
||||
|
||||
## License
|
||||
|
||||
DocuSeal is released under the GNU Affero General Public License v3.0.
|
||||
@@ -23,6 +23,10 @@ class SubmitFormController < ApplicationController
|
||||
|
||||
submitter.save!
|
||||
|
||||
Submissions.update_template_fields!(submitter.submission) if submitter.submission.template_fields.blank?
|
||||
|
||||
submitter.submission.save!
|
||||
|
||||
if submitter.completed_at?
|
||||
GenerateSubmitterResultAttachmentsJob.perform_later(submitter)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export default actionable(class extends HTMLElement {
|
||||
document.body.classList.remove('overflow-hidden')
|
||||
|
||||
document.removeEventListener('keyup', this.onEscKey)
|
||||
document.removeEventListener('turbo:submit-end', this.handleSubmit)
|
||||
document.removeEventListener('turbo:submit-end', this.onSubmit)
|
||||
document.removeEventListener('turbo:before-cache', this.close)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="isActive"
|
||||
v-if="isActive && withLabel"
|
||||
class="absolute -top-7 rounded bg-base-content text-base-100 px-2 text-sm whitespace-nowrap"
|
||||
>
|
||||
{{ field.name || fieldNames[field.type] }}
|
||||
@@ -146,6 +146,11 @@ export default {
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
withLabel: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
fieldIndex: {
|
||||
type: Number,
|
||||
required: false,
|
||||
@@ -207,7 +212,7 @@ export default {
|
||||
},
|
||||
formattedDate () {
|
||||
if (this.field.type === 'date' && this.modelValue) {
|
||||
return new Intl.DateTimeFormat([], { year: 'numeric', month: 'long', day: 'numeric' }).format(new Date(this.modelValue))
|
||||
return new Intl.DateTimeFormat([], { year: 'numeric', month: 'long', day: 'numeric', timeZone: 'UTC' }).format(new Date(this.modelValue))
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
v-for="(area, areaIndex) in field.areas"
|
||||
:key="areaIndex"
|
||||
>
|
||||
<Teleport :to="`#page-${area.attachment_uuid}-${area.page}`">
|
||||
<Teleport
|
||||
:to="`#page-${area.attachment_uuid}-${area.page}`"
|
||||
>
|
||||
<FieldArea
|
||||
:ref="setAreaRef"
|
||||
v-model="values[field.uuid]"
|
||||
@@ -20,6 +22,7 @@
|
||||
:submittable="true"
|
||||
:field-index="fieldIndex"
|
||||
:is-active="currentStep === step"
|
||||
:with-label="withLabel"
|
||||
:is-value-set="step.some((f) => f.uuid in values)"
|
||||
:attachments-index="attachmentsIndex"
|
||||
@click="$emit('focus-step', step)"
|
||||
@@ -54,6 +57,11 @@ export default {
|
||||
required: false,
|
||||
default: () => ({})
|
||||
},
|
||||
withLabel: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
currentStep: {
|
||||
type: Array,
|
||||
required: false,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
:steps="stepFields"
|
||||
:values="values"
|
||||
:attachments-index="attachmentsIndex"
|
||||
:with-label="!isAnonymousChecboxes"
|
||||
:current-step="currentStepFields"
|
||||
@focus-step="[saveStep(), goToStep($event, false, true), currentField.type !== 'checkbox' ? isFormVisible = true : '']"
|
||||
/>
|
||||
@@ -182,7 +183,7 @@
|
||||
/>
|
||||
<div
|
||||
v-else-if="currentField.type === 'checkbox'"
|
||||
class="flex w-full"
|
||||
class="flex w-full max-h-44 overflow-y-auto"
|
||||
>
|
||||
<input
|
||||
type="hidden"
|
||||
@@ -192,31 +193,36 @@
|
||||
<div
|
||||
class="space-y-3.5 mx-auto"
|
||||
>
|
||||
<div
|
||||
v-for="(field, index) in currentStepFields"
|
||||
:key="field.uuid"
|
||||
>
|
||||
<label
|
||||
:for="field.uuid"
|
||||
class="flex items-center space-x-3"
|
||||
<template v-if="isAnonymousChecboxes">
|
||||
Complete hightlighted checkboxes and click <span class="font-semibold">{{ stepFields.length === currentStep + 1 ? 'submit' : 'next' }}</span>.
|
||||
</template>
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="(field, index) in currentStepFields"
|
||||
:key="field.uuid"
|
||||
>
|
||||
<input
|
||||
type="hidden"
|
||||
:name="`values[${field.uuid}]`"
|
||||
:value="!!values[field.uuid]"
|
||||
<label
|
||||
:for="field.uuid"
|
||||
class="flex items-center space-x-3"
|
||||
>
|
||||
<input
|
||||
:id="field.uuid"
|
||||
type="checkbox"
|
||||
class="base-checkbox !h-7 !w-7"
|
||||
:checked="!!values[field.uuid]"
|
||||
@click="[$refs.areas.scrollIntoField(field), values[field.uuid] = !values[field.uuid]]"
|
||||
>
|
||||
<span class="text-xl">
|
||||
{{ field.name || field.type + ' ' + (index + 1) }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<input
|
||||
type="hidden"
|
||||
:name="`values[${field.uuid}]`"
|
||||
:value="!!values[field.uuid]"
|
||||
>
|
||||
<input
|
||||
:id="field.uuid"
|
||||
type="checkbox"
|
||||
class="base-checkbox !h-7 !w-7"
|
||||
:checked="!!values[field.uuid]"
|
||||
@click="[$refs.areas.scrollIntoField(field), values[field.uuid] = !values[field.uuid]]"
|
||||
>
|
||||
<span class="text-xl">
|
||||
{{ field.name || field.type + ' ' + (index + 1) }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<ImageStep
|
||||
@@ -374,6 +380,9 @@ export default {
|
||||
currentStepFields () {
|
||||
return this.stepFields[this.currentStep]
|
||||
},
|
||||
isAnonymousChecboxes () {
|
||||
return this.currentField.type === 'checkbox' && this.currentStepFields.every((e) => !e.name) && this.currentStepFields.length > 4
|
||||
},
|
||||
isButtonDisabled () {
|
||||
return this.isSubmitting ||
|
||||
(this.currentField.required && ['image', 'file'].includes(this.currentField.type) && !this.values[this.currentField.uuid]?.length) ||
|
||||
|
||||
@@ -64,8 +64,11 @@
|
||||
:with-arrows="template.schema.length > 1"
|
||||
:item="item"
|
||||
:document="sortedDocuments[index]"
|
||||
:template="template"
|
||||
:is-direct-upload="isDirectUpload"
|
||||
@scroll-to="scrollIntoDocument(item)"
|
||||
@remove="onDocumentRemove"
|
||||
@replace="onDocumentReplace"
|
||||
@up="moveDocument(item, -1)"
|
||||
@down="moveDocument(item, 1)"
|
||||
@change="save"
|
||||
@@ -133,7 +136,7 @@
|
||||
<p>
|
||||
<button
|
||||
class="base-button"
|
||||
@click="drawField = false"
|
||||
@click="drawField = null"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -353,7 +356,7 @@ export default {
|
||||
|
||||
if (this.selectedField?.type === this.dragFieldType) {
|
||||
baseArea = this.selectedAreaRef.value
|
||||
} else if (previousField?.areas) {
|
||||
} else if (previousField?.areas?.length) {
|
||||
baseArea = previousField.areas[previousField.areas.length - 1]
|
||||
} else {
|
||||
if (['checkbox'].includes(this.dragFieldType)) {
|
||||
@@ -417,6 +420,27 @@ export default {
|
||||
this.template.schema.splice(this.template.schema.indexOf(item), 1)
|
||||
}
|
||||
|
||||
this.template.fields.forEach((field) => {
|
||||
[...(field.areas || [])].forEach((area) => {
|
||||
if (area.attachment_uuid === item.attachment_uuid) {
|
||||
field.areas.splice(field.areas.indexOf(area), 1)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.save()
|
||||
},
|
||||
onDocumentReplace ({ replaceSchemaItem, schema, documents }) {
|
||||
this.template.schema.splice(this.template.schema.indexOf(replaceSchemaItem), 1, schema[0])
|
||||
this.template.documents.push(...documents)
|
||||
this.template.fields.forEach((field) => {
|
||||
(field.areas || []).forEach((area) => {
|
||||
if (area.attachment_uuid === replaceSchemaItem.attachment_uuid) {
|
||||
area.attachment_uuid = schema[0].attachment_uuid
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.save()
|
||||
},
|
||||
moveDocument (item, direction) {
|
||||
|
||||
@@ -93,6 +93,11 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.isDirectUpload) {
|
||||
import('@rails/activestorage')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
upload: Upload.methods.upload,
|
||||
onDropFiles (e) {
|
||||
|
||||
@@ -172,6 +172,7 @@ export default {
|
||||
name: '',
|
||||
uuid: v4(),
|
||||
required: type !== 'checkbox',
|
||||
areas: [],
|
||||
submitter_uuid: this.selectedSubmitter.uuid,
|
||||
type
|
||||
}
|
||||
|
||||
@@ -9,39 +9,51 @@
|
||||
loading="lazy"
|
||||
>
|
||||
<div
|
||||
class="group flex justify-end cursor-pointer top-0 bottom-0 left-0 right-0 absolute"
|
||||
class="group flex justify-end cursor-pointer top-0 bottom-0 left-0 right-0 absolute p-1"
|
||||
@click="$emit('scroll-to', item)"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col justify-between opacity-0 group-hover:opacity-100"
|
||||
>
|
||||
<div class="rounded-bl rounded-tr bg-white border">
|
||||
<button
|
||||
class="rounded-bl rounded-tr hover:text-base-100 hover:bg-base-content w-full transition-colors"
|
||||
style="width: 24px"
|
||||
@click.stop="$emit('remove', item)"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<div class="flex justify-between w-full">
|
||||
<div style="width: 26px" />
|
||||
<div class="">
|
||||
<ReplaceButton
|
||||
:is-direct-upload="isDirectUpload"
|
||||
:template-id="template.id"
|
||||
class="opacity-0 group-hover:opacity-100"
|
||||
@click.stop
|
||||
@success="$emit('replace', { replaceSchemaItem: item, ...$event })"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="withArrows"
|
||||
class="flex flex-col border rounded-br rounded-tl bg-white divide-y"
|
||||
class="flex flex-col justify-between opacity-0 group-hover:opacity-100"
|
||||
>
|
||||
<button
|
||||
class="rounded-tl hover:text-base-100 hover:bg-base-content w-full transition-colors"
|
||||
style="width: 24px"
|
||||
@click.stop="$emit('up', item)"
|
||||
<div>
|
||||
<button
|
||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
||||
style="width: 24px; height: 24px"
|
||||
@click.stop="$emit('remove', item)"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="withArrows"
|
||||
class="flex flex-col space-y-1"
|
||||
>
|
||||
↑
|
||||
</button>
|
||||
<button
|
||||
class="rounded-br hover:text-base-100 hover:bg-base-content w-full transition-colors"
|
||||
style="width: 24px"
|
||||
@click.stop="$emit('down', item)"
|
||||
>
|
||||
↓
|
||||
</button>
|
||||
<button
|
||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
||||
style="width: 24px; height: 24px"
|
||||
@click.stop="$emit('up', item)"
|
||||
>
|
||||
↑
|
||||
</button>
|
||||
<button
|
||||
class="btn border-base-200 bg-white text-base-content btn-xs rounded hover:text-base-100 hover:bg-base-content hover:border-base-content w-full transition-colors"
|
||||
style="width: 24px; height: 24px"
|
||||
@click.stop="$emit('down', item)"
|
||||
>
|
||||
↓
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,34 +72,52 @@
|
||||
|
||||
<script>
|
||||
import Contenteditable from './contenteditable'
|
||||
import Upload from './upload'
|
||||
import ReplaceButton from './replace'
|
||||
|
||||
export default {
|
||||
name: 'DocumentPreview',
|
||||
components: {
|
||||
Contenteditable
|
||||
Contenteditable,
|
||||
ReplaceButton
|
||||
},
|
||||
props: {
|
||||
item: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
template: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
document: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isDirectUpload: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false
|
||||
},
|
||||
withArrows: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
emits: ['scroll-to', 'change', 'remove', 'up', 'down'],
|
||||
emits: ['scroll-to', 'change', 'remove', 'up', 'down', 'replace'],
|
||||
computed: {
|
||||
previewImage () {
|
||||
return this.document.preview_images[0]
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.isDirectUpload) {
|
||||
import('@rails/activestorage')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
upload: Upload.methods.upload,
|
||||
onUpdateName (value) {
|
||||
this.item.name = value
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<label
|
||||
:for="inputId"
|
||||
class="btn btn-neutral btn-xs text-white transition-none"
|
||||
:class="{ 'opacity-100': isLoading || isProcessing }"
|
||||
>
|
||||
{{ message }}
|
||||
<form
|
||||
ref="form"
|
||||
class="hidden"
|
||||
>
|
||||
<input
|
||||
:id="inputId"
|
||||
ref="input"
|
||||
name="files[]"
|
||||
type="file"
|
||||
accept="image/*, application/pdf"
|
||||
@change="upload"
|
||||
>
|
||||
</form>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Upload from './upload'
|
||||
|
||||
export default {
|
||||
name: 'ReplaceDocument',
|
||||
props: {
|
||||
templateId: {
|
||||
type: [Number, String],
|
||||
required: true
|
||||
},
|
||||
isDirectUpload: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['success'],
|
||||
data () {
|
||||
return {
|
||||
isLoading: false,
|
||||
isProcessing: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
inputId () {
|
||||
return 'el' + Math.random().toString(32).split('.')[1]
|
||||
},
|
||||
message () {
|
||||
if (this.isLoading) {
|
||||
return 'Uploading...'
|
||||
} else if (this.isProcessing) {
|
||||
return 'Processing...'
|
||||
} else {
|
||||
return 'Replace'
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.isDirectUpload) {
|
||||
import('@rails/activestorage')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
upload: Upload.methods.upload
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -4,12 +4,15 @@
|
||||
#
|
||||
# Table name: submissions
|
||||
#
|
||||
# id :bigint not null, primary key
|
||||
# deleted_at :datetime
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# created_by_user_id :bigint
|
||||
# template_id :bigint not null
|
||||
# id :bigint not null, primary key
|
||||
# deleted_at :datetime
|
||||
# template_fields :text
|
||||
# template_schema :text
|
||||
# template_submitters :text
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# created_by_user_id :bigint
|
||||
# template_id :bigint not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
@@ -27,5 +30,9 @@ class Submission < ApplicationRecord
|
||||
|
||||
has_many :submitters, dependent: :destroy
|
||||
|
||||
serialize :template_fields, JSON
|
||||
serialize :template_schema, JSON
|
||||
serialize :template_submitters, JSON
|
||||
|
||||
scope :active, -> { where(deleted_at: nil) }
|
||||
end
|
||||
@@ -44,6 +44,9 @@ class Template < ApplicationRecord
|
||||
|
||||
has_many_attached :documents
|
||||
|
||||
has_many :schema_documents, ->(e) { where(uuid: e.schema.pluck('attachment_uuid')) },
|
||||
class_name: 'ActiveStorage::Attachment', dependent: :destroy, as: :record, inverse_of: :record
|
||||
|
||||
has_many :submissions, dependent: :destroy
|
||||
|
||||
scope :active, -> { where(deleted_at: nil) }
|
||||
|
||||
@@ -56,6 +56,10 @@ class User < ApplicationRecord
|
||||
!deleted_at?
|
||||
end
|
||||
|
||||
def remember_me
|
||||
true
|
||||
end
|
||||
|
||||
def initials
|
||||
[first_name.first, last_name.first].join.upcase
|
||||
end
|
||||
|
||||
@@ -10,14 +10,6 @@
|
||||
<%= f.label :password, class: 'label' %>
|
||||
<%= f.password_field :password, autocomplete: 'current-password', class: 'base-input' %>
|
||||
</div>
|
||||
<% if devise_mapping.rememberable? %>
|
||||
<div class="form-control">
|
||||
<%= f.label :remember_me, class: 'flex items-center cursor-pointer' do %>
|
||||
<%= f.check_box :remember_me, checked: true, class: 'base-checkbox' %>
|
||||
<span class="label">Remember me</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<%= f.button button_title(title: 'Log In', disabled_with: 'Logging In'), class: 'base-button' %>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</div>
|
||||
<div class="flex md:max-h-[calc(100vh-60px)]">
|
||||
<div class="overflow-y-auto overflow-x-hidden hidden lg:block w-52 flex-none pr-3 mt-0.5 pt-0.5">
|
||||
<% @submission.template.schema.each do |item| %>
|
||||
<% (@submission.template_schema || @submission.template.schema).each do |item| %>
|
||||
<% document = @submission.template.documents.find { |a| item['attachment_uuid'] == a.uuid } %>
|
||||
<a href="#<%= "page-#{document.uuid}-0" %>" onclick="[event.preventDefault(), window[event.target.closest('a').href.split('#')[1]].scrollIntoView({ behavior: 'smooth', block: 'start' })]" class="block cursor-pointer">
|
||||
<img src="<%= document.preview_images.first.url %>" width="<%= document.preview_images.first.metadata['width'] %>" height="<%= document.preview_images.first.metadata['height'] %>" class="rounded border" loading="lazy">
|
||||
@@ -35,10 +35,10 @@
|
||||
</div>
|
||||
<div class="w-full overflow-y-auto overflow-x-hidden mt-0.5 pt-0.5">
|
||||
<div class="pr-3.5 pl-0.5">
|
||||
<% fields_index = Templates.build_field_areas_index(@submission.template) %>
|
||||
<% fields_index = Templates.build_field_areas_index(@submission.template_fields || @submission.template.fields) %>
|
||||
<% values = @submission.submitters.reduce({}) { |acc, sub| acc.merge(sub.values) } %>
|
||||
<% attachments_index = ActiveStorage::Attachment.where(record: @submission.submitters, name: :attachments).preload(:blob).index_by(&:uuid) %>
|
||||
<% @submission.template.schema.each do |item| %>
|
||||
<% (@submission.template_schema || @submission.template.schema).each do |item| %>
|
||||
<% document = @submission.template.documents.find { |e| e.uuid == item['attachment_uuid'] } %>
|
||||
<% document.preview_images.sort_by { |a| a.filename.base.to_i }.each_with_index do |page, index| %>
|
||||
<div id="<%= "page-#{document.uuid}-#{index}" %>" class="relative">
|
||||
@@ -57,15 +57,15 @@
|
||||
</div>
|
||||
<div class="hidden md:block relative w-80 flex-none pt-0.5 pr-4 pl-0.5 overflow-auto space">
|
||||
<% colors = %w[bg-red-500 bg-sky-500 bg-emerald-500 bg-yellow-300 bg-purple-600] %>
|
||||
<% submitter_fields_index = @submission.template.fields.group_by { |f| f['submitter_uuid'] } %>
|
||||
<% @submission.template.submitters.each_with_index do |item, index| %>
|
||||
<% submitter_fields_index = (@submission.template_fields || @submission.template.fields).group_by { |f| f['submitter_uuid'] } %>
|
||||
<% (@submission.template_submitters || @submission.template.submitters).each_with_index do |item, index| %>
|
||||
<% submitter = @submission.submitters.find { |e| e.uuid == item['uuid'] } %>
|
||||
<div class="sticky -top-1 bg-base-100 pt-1 -mt-1 z-10">
|
||||
<div class="border border-base-300 rounded-md px-2 py-1 mb-1">
|
||||
<div class="flex items-center space-x-1">
|
||||
<span class="mx-1 w-3 h-3 rounded-full <%= colors[index] %>"></span>
|
||||
<span class="text-lg">
|
||||
<%= @submission.template.submitters.find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %>
|
||||
<%= (@submission.template_submitters || @submission.template.submitters).find { |e| e['uuid'] == submitter&.uuid }&.dig('name') || "#{(index + 1).ordinalize} Submitter" %>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center space-x-1 mt-1">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<% fields_index = Templates.build_field_areas_index(@submitter.submission.template) %>
|
||||
<% fields_index = Templates.build_field_areas_index(@submitter.submission.template_fields || @submitter.submission.template.fields) %>
|
||||
<% values = @submitter.submission.submitters.where.not(id: @submitter.id).reduce({}) { |acc, sub| acc.merge(sub.values) } %>
|
||||
<% attachments_index = ActiveStorage::Attachment.where(record: @submitter.submission.submitters, name: :attachments).preload(:blob).index_by(&:uuid) %>
|
||||
<div style="max-height: -webkit-fill-available;">
|
||||
@@ -10,7 +10,7 @@
|
||||
<span>DocuSeal</span>
|
||||
</a>
|
||||
</div>
|
||||
<% @submitter.submission.template.schema.each do |item| %>
|
||||
<% (@submitter.submission.template_schema || @submitter.submission.template.schema).each do |item| %>
|
||||
<% document = @submitter.submission.template.documents.find { |a| a.uuid == item['attachment_uuid'] } %>
|
||||
<% document.preview_images.sort_by { |a| a.filename.base.to_i }.each_with_index do |page, index| %>
|
||||
<div class="relative my-4 shadow-md">
|
||||
@@ -32,7 +32,7 @@
|
||||
<div class="fixed bottom-0 w-full h-0 z-20">
|
||||
<div class="mx-auto" style="max-width: 1000px">
|
||||
<div class="relative md:mx-32">
|
||||
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-submitter-uuid="<%= @submitter.uuid %>" data-submitter-slug="<%= @submitter.slug %>" data-can-send-email="<%= Accounts.can_send_emails?(Struct.new(:id).new(@submitter.submission.template.account_id)) %>" data-attachments="<%= attachments_index.values.select { |e| e.record_id == @submitter.id }.to_json(only: %i[uuid], methods: %i[url filename content_type]) %>" data-fields="<%= @submitter.submission.template.fields.select { |f| f['submitter_uuid'] == @submitter.uuid }.to_json %>" data-values="<%= @submitter.values.to_json %>" data-authenticity-token="<%= form_authenticity_token %>"></submission-form>
|
||||
<submission-form data-is-demo="<%= Docuseal.demo? %>" data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-submitter-uuid="<%= @submitter.uuid %>" data-submitter-slug="<%= @submitter.slug %>" data-can-send-email="<%= Accounts.can_send_emails?(Struct.new(:id).new(@submitter.submission.template.account_id)) %>" data-attachments="<%= attachments_index.values.select { |e| e.record_id == @submitter.id }.to_json(only: %i[uuid], methods: %i[url filename content_type]) %>" data-fields="<%= (@submitter.submission.template_fields || @submitter.submission.template.fields).select { |f| f['submitter_uuid'] == @submitter.uuid }.to_json %>" data-values="<%= @submitter.values.to_json %>" data-authenticity-token="<%= form_authenticity_token %>"></submission-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1 +1 @@
|
||||
<template-builder data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-template="<%= @template.to_json(include: { documents: { include: { preview_images: { methods: %i[url metadata filename] } } } }) %>"></template-builder>
|
||||
<template-builder data-is-direct-upload="<%= Docuseal.active_storage_public? %>" data-template="<%= @template.as_json.merge(documents: @template.schema_documents.as_json(include: { preview_images: { methods: %i[url metadata filename] } })).to_json %>"></template-builder>
|
||||
@@ -37,7 +37,7 @@
|
||||
<div class="space-y-4">
|
||||
<% @submissions.each do |submission| %>
|
||||
<a href="<%= submission_path(submission) %>" class="bg-base-200 w-full flex flex-col md:flex-row space-y-4 md:space-y-0 md:justify-between rounded-2xl px-6 py-5 md:items-center">
|
||||
<% submitters = submission.template.submitters.filter_map { |item| submission.submitters.find { |e| e.uuid == item['uuid'] } } %>
|
||||
<% submitters = (submission.template_submitters || submission.template.submitters).filter_map { |item| submission.submitters.find { |e| e.uuid == item['uuid'] } } %>
|
||||
<% is_submission_complated = submitters.all?(&:completed_at?) %>
|
||||
<% if submitters.size == 1 %>
|
||||
<div>
|
||||
|
||||
@@ -76,7 +76,7 @@ Rails.application.configure do
|
||||
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
|
||||
config.action_mailer.raise_delivery_errors = false
|
||||
|
||||
if ENV['SMTP_USERNAME']
|
||||
if ENV['SMTP_ADDRESS']
|
||||
config.action_mailer.delivery_method = :smtp
|
||||
config.action_mailer.smtp_settings = {
|
||||
address: ENV.fetch('SMTP_ADDRESS', nil),
|
||||
@@ -86,7 +86,7 @@ Rails.application.configure do
|
||||
password: ENV.fetch('SMTP_PASSWORD', nil),
|
||||
authentication: ENV.fetch('SMTP_AUTHENTICATION', 'plain'),
|
||||
enable_starttls_auto: ENV['SMTP_ENABLE_STARTTLS_AUTO'] == 'true'
|
||||
}
|
||||
}.compact
|
||||
end
|
||||
|
||||
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
|
||||
|
||||
@@ -159,7 +159,7 @@ Devise.setup do |config|
|
||||
|
||||
# ==> Configuration for :rememberable
|
||||
# The time the user will be remembered without asking for credentials again.
|
||||
# config.remember_for = 2.weeks
|
||||
config.remember_for = 2.years
|
||||
|
||||
# Invalidates all the remember me tokens when the user signs out.
|
||||
config.expire_all_remember_me_on_sign_out = true
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
ActiveRecord::Tasks::DatabaseTasks.migrate if ENV['RAILS_ENV'] == 'production'
|
||||
Rails.configuration.to_prepare do
|
||||
ActiveRecord::Tasks::DatabaseTasks.migrate if ENV['RAILS_ENV'] == 'production'
|
||||
end
|
||||
@@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddTemplateFieldsToSubmission < ActiveRecord::Migration[7.0]
|
||||
class MigrationTemplate < ApplicationRecord
|
||||
self.table_name = 'templates'
|
||||
end
|
||||
|
||||
class MigrationSubmission < ApplicationRecord
|
||||
self.table_name = 'submissions'
|
||||
end
|
||||
|
||||
def up
|
||||
add_column :submissions, :template_fields, :text
|
||||
add_column :submissions, :template_schema, :text
|
||||
add_column :submissions, :template_submitters, :text
|
||||
|
||||
MigrationTemplate.all.each do |template|
|
||||
MigrationSubmission.where(template_id: template.id).each do |submission|
|
||||
submission.update_columns(template_fields: template.fields,
|
||||
template_schema: template.schema,
|
||||
template_submitters: template.submitters)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :submissions, :template_fields
|
||||
remove_column :submissions, :template_schema
|
||||
remove_column :submissions, :template_submitters
|
||||
end
|
||||
end
|
||||
+4
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_07_01_075115) do
|
||||
ActiveRecord::Schema[7.0].define(version: 2023_07_26_062428) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
@@ -77,6 +77,9 @@ ActiveRecord::Schema[7.0].define(version: 2023_07_01_075115) do
|
||||
t.datetime "deleted_at"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.text "template_fields"
|
||||
t.text "template_schema"
|
||||
t.text "template_submitters"
|
||||
t.index ["created_by_user_id"], name: "index_submissions_on_created_by_user_id"
|
||||
t.index ["template_id"], name: "index_submissions_on_template_id"
|
||||
end
|
||||
|
||||
@@ -25,7 +25,7 @@ module ActionMailerConfigsInterceptor
|
||||
port: email_configs.value['port'],
|
||||
tls: email_configs.value['port'].to_s == '465')
|
||||
|
||||
message.from = email_configs.value['from_email']
|
||||
message.from = "#{email_configs.account.name} <#{email_configs.value['from_email']}>"
|
||||
else
|
||||
message.delivery_method(:test)
|
||||
end
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Submissions
|
||||
module_function
|
||||
|
||||
def update_template_fields!(submission)
|
||||
submission.template_fields = submission.template.fields
|
||||
submission.template_schema = submission.template.schema
|
||||
submission.template_submitters = submission.template.submitters
|
||||
|
||||
submission.save!
|
||||
end
|
||||
end
|
||||
@@ -27,7 +27,7 @@ module Submissions
|
||||
|
||||
pdfs_index = build_pdfs_index(submitter)
|
||||
|
||||
template.fields.each do |field|
|
||||
submitter.submission.template_fields.each do |field|
|
||||
next if field['submitter_uuid'] != submitter.uuid
|
||||
|
||||
field.fetch('areas', []).each do |area|
|
||||
@@ -159,7 +159,7 @@ module Submissions
|
||||
original_documents = template.documents.preload(:blob)
|
||||
|
||||
results =
|
||||
template.schema.map do |item|
|
||||
submitter.submission.template_schema.map do |item|
|
||||
pdf = pdfs_index[item['attachment_uuid']]
|
||||
|
||||
attachment = save_signed_pdf(pdf:, submitter:, certs:, uuid: item['attachment_uuid'], name: item['name'])
|
||||
|
||||
+2
-2
@@ -3,10 +3,10 @@
|
||||
module Templates
|
||||
module_function
|
||||
|
||||
def build_field_areas_index(template)
|
||||
def build_field_areas_index(fields)
|
||||
hash = {}
|
||||
|
||||
template.fields.each do |field|
|
||||
fields.each do |field|
|
||||
(field['areas'] || []).each do |area|
|
||||
hash[area['attachment_uuid']] ||= {}
|
||||
acc = (hash[area['attachment_uuid']][area['page']] ||= [])
|
||||
|
||||
Reference in new issue
Block a user