add expire at

pull/349/head
Pete Matsyburka 1 year ago
parent c4a91c2b34
commit 3a2910f717

@ -147,6 +147,7 @@ module Api
def submissions_params
permitted_attrs = [
:send_email, :send_sms, :bcc_completed, :completed_redirect_url, :reply_to, :go_to_last,
:expire_at,
{
message: %i[subject body],
submitters: [[:send_email, :send_sms, :completed_redirect_url, :uuid, :name, :email, :role,

@ -17,7 +17,8 @@ class StartFormController < ApplicationController
def update
return redirect_to start_form_path(@template.slug) if @template.archived_at?
@submitter = Submitter.where(submission: @template.submissions.where(archived_at: nil))
@submitter = Submitter.where(submission: @template.submissions.where(expire_at: Time.current..)
.or(@template.submissions.where(expire_at: nil)).where(archived_at: nil))
.order(id: :desc)
.then { |rel| params[:resubmit].present? ? rel.where(completed_at: nil) : rel }
.find_or_initialize_by(**submitter_params.compact_blank)

@ -14,6 +14,7 @@ class SubmitFormController < ApplicationController
return redirect_to submit_form_completed_path(@submitter.slug) if @submitter.completed_at?
return render :archived if @submitter.submission.template.archived_at? || @submitter.submission.archived_at?
return render :expired if @submitter.submission.expired?
Submitters.preload_with_pages(@submitter)
@ -51,6 +52,10 @@ class SubmitFormController < ApplicationController
return render json: { error: 'Form has been archived.' }, status: :unprocessable_entity
end
if submitter.submission.expired?
return render json: { error: 'Form has been expired.' }, status: :unprocessable_entity
end
Submitters::SubmitValues.call(submitter, params, request)
head :ok

@ -6,6 +6,7 @@
#
# id :bigint not null, primary key
# archived_at :datetime
# expire_at :datetime
# preferences :text not null
# slug :string not null
# source :text not null
@ -75,6 +76,10 @@ class Submission < ApplicationRecord
preserved: 'preserved'
}, scope: false, prefix: true
def expired?
expire_at && expire_at <= Time.current
end
def audit_trail_url
return if audit_trail.blank?

@ -54,7 +54,7 @@
</div>
<% end %>
</div>
<% elsif @submission.submitters.to_a.size == 1 %>
<% elsif @submission.submitters.to_a.size == 1 && !@submission.expired? %>
<%= render 'shared/clipboard_copy', text: start_form_url(slug: @submission.template.slug), class: 'base-button', icon_class: 'w-6 h-6 text-white', copy_title: 'Copy Share Link', copied_title: 'Copied to Clipboard' %>
<% end %>
</div>
@ -146,15 +146,15 @@
<%= submitter&.completed_at? ? l(submitter.completed_at.in_time_zone(@submission.account.timezone), format: :long, locale: @submission.account.locale) : 'Not completed yet' %>
</span>
</div>
<% if signed_in? && submitter && submitter.email && !submitter.completed_at && !@submission.archived_at? && can?(:update, submitter) && Accounts.can_send_emails?(current_account) %>
<% if signed_in? && submitter && submitter.email && !submitter.completed_at && !@submission.archived_at? && can?(:update, submitter) && Accounts.can_send_emails?(current_account) && !@submission.expired? %>
<div class="mt-2 mb-1">
<%= button_to button_title(title: submitter.sent_at? ? 'Re-send Email' : 'Send Email', disabled_with: 'Sending'), submitter_send_email_index_path(submitter_slug: submitter.slug), class: 'btn btn-sm btn-primary w-full' %>
</div>
<% end %>
<% if signed_in? && submitter && submitter.phone && !submitter.completed_at && !@submission.archived_at? && can?(:update, submitter) %>
<% if signed_in? && submitter && submitter.phone && !submitter.completed_at && !@submission.archived_at? && can?(:update, submitter) && !@submission.expired? %>
<%= render 'submissions/send_sms_button', submitter: %>
<% end %>
<% if signed_in? && submitter && !submitter.completed_at? && !@submission.archived_at? && can?(:create, submitter) %>
<% if signed_in? && submitter && !submitter.completed_at? && !@submission.archived_at? && can?(:create, submitter) && !@submission.expired? %>
<div class="mt-2 mb-1">
<a class="btn btn-sm btn-primary w-full" target="_blank" href="<%= submit_form_path(slug: submitter.slug) %>">
Sign In-person

@ -0,0 +1,21 @@
<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">
<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">
<%= svg_icon('writing_sign', class: 'w-10 h-10') %>
</div>
<div dir="auto">
<p class="text-lg font-bold mb-1"><%= @submitter.submission.template.name %></p>
<p class="text-sm"><%= t('form_expired_at_html', time: l(@submitter.submission.expire_at, format: :long)) %></p>
</div>
</div>
</div>
</div>
</div>
</div>
<%= render 'shared/attribution', link_path: '/start', account: @submitter.account %>

@ -34,11 +34,19 @@
<% submitter = submitters.first %>
<div class="flex items-center space-x-4">
<span class="flex flex-col md:flex-row md:items-center gap-3">
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge <%= status_badges[submitter.status] %> md:w-32 badge-lg bg-opacity-50 uppercase text-sm font-semibold">
<%= submitter.status %>
</span>
</div>
<% if submission.expired? %>
<div class="tooltip flex" data-tip="<%= l(submission.expire_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge badge-error md:w-32 bg-opacity-50 badge-lg uppercase text-sm font-semibold">
Expired
</span>
</div>
<% else %>
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge <%= status_badges[submitter.status] %> md:w-32 badge-lg bg-opacity-50 uppercase text-sm font-semibold">
<%= submitter.status %>
</span>
</div>
<% end %>
<span class="text-lg break-all flex items-center">
<%= submitter.name || submitter.email || submitter.phone %>
</span>
@ -61,7 +69,7 @@
</download-button>
</button>
</form>
<% elsif !submission.archived_at? && !template.archived_at? %>
<% elsif !submission.archived_at? && !template.archived_at? && !submission.expired? %>
<% if current_user.email == submitter.email %>
<form data-turbo="false" method="get" action="<%= submit_form_url(slug: submitter.slug) %>" class="flex-1 md:flex-none">
<button onclick="event.stopPropagation()" class="btn btn-sm btn-neutral btn-outline bg-white w-full md:w-36 flex">
@ -101,12 +109,18 @@
<%= latest_submitter.status %>
</span>
</div>
<% elsif submission.expired? %>
<div class="tooltip flex" data-tip="<%= l(submission.expire_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge badge-error md:w-32 bg-opacity-50 badge-lg uppercase text-sm font-semibold">
Expired
</span>
</div>
<% end %>
<div class="w-full <%= is_submission_completed ? 'space-y-1' : 'space-y-4' %> md:space-y-0">
<% submitters.each_with_index do |submitter, index| %>
<div class="relative flex justify-between items-start md:items-center space-x-3">
<span class="flex flex-col md:flex-row md:items-center gap-2">
<% unless is_submission_completed %>
<% if !is_submission_completed && !submission.expired? %>
<div class="tooltip flex" data-tip="<%= l(submitter.status_event_at.in_time_zone(current_account.timezone), format: :short, locale: current_account.locale) %>">
<span class="badge md:w-24 <%= status_badges[submitter.status] %> bg-opacity-50 uppercase text-xs font-semibold">
<%= submitter.status %>
@ -132,7 +146,7 @@
</download-button>
</button>
</form>
<% elsif !template.archived_at? && !submission.archived_at? && !is_submission_completed %>
<% elsif !template.archived_at? && !submission.archived_at? && !is_submission_completed && !submission.expired? %>
<div class="relative flex items-center space-x-3">
<% if current_user.email == submitter.email %>
<form data-turbo="false" method="get" action="<%= submit_form_url(slug: submitter.slug) %>">

@ -19,6 +19,7 @@ en: &en
language_ar: العربية
language_ko: 한국어
email: Email
form_expired_at_html: 'Form expired on <span class="font-semibold">%{time}</span>'
verification_code_code: 'Verification code: %{code}'
digitally_signed_by: Digitally signed by
role: Role
@ -66,6 +67,7 @@ en: &en
es: &es
role: Rol
reason: Razón
form_expired_at_html: 'El formulario expiró el <span class="font-semibold">%{time}</span>'
verification_code_code: 'Código de verificación: %{code}'
email: Correo electrónico
digitally_signed_by: Firmado digitalmente por
@ -111,6 +113,7 @@ it: &it
reason: Ragione
verification_code_code: 'Codice di verifica: %{code}'
email: Email
form_expired_at_html: 'Il modulo è scaduto il <span class="font-semibold">%{time}</span>'
digitally_signed_by: Firmato digitalmente da
role: Ruolo
provide_your_email_to_start: Fornisci la tua email per iniziare
@ -154,6 +157,7 @@ fr: &fr
digitally_signed_by: Signé numériquement par
reason: Raison
role: Rôle
form_expired_at_html: 'Le formulaire a expiré le <span class="font-semibold">%{time}</span>'
provide_your_email_to_start: Entrez votre adresse email pour commencer
start: Démarrer
starting: Démarrage
@ -196,6 +200,7 @@ pt: &pt
reason: Razão
verification_code_code: 'Código de verificação: %{code}'
digitally_signed_by: Assinado digitalmente por
form_expired_at_html: 'O formulário expirou em <span class="font-semibold">%{time}</span>'
role: Função
provide_your_email_to_start: Forneça o seu email para começar
start: Iniciar
@ -237,6 +242,7 @@ de: &de
verification_code_code: 'Verifizierungscode: %{code}'
reason: Grund
email: E-Mail
form_expired_at_html: 'Das Formular ist am <span class="font-semibold">%{time}</span> abgelaufen'
digitally_signed_by: Digital signiert von
role: Rolle
provide_your_email_to_start: Gib deine E-Mail-Adresse ein, um zu starten
@ -278,6 +284,7 @@ pl:
email: Email
verification_code_code: 'Kod weryfikacyjny: %{code}'
digitally_signed_by: Podpis cyfrowy przez
form_expired_at_html: 'Formularz wygasł o <span class="font-semibold">%{time}</span>'
role: Rola
provide_your_email_to_start: Podaj swój adres email, aby rozpocząć
start: Rozpocznij
@ -321,6 +328,7 @@ uk:
verification_code_code: 'Код підтвердження: %{code}'
role: Роль
provide_your_email_to_start: Введіть свій email, щоб почати
form_expired_at_html: 'Строк подачі завершився о <span class="font-semibold">%{time}</span>'
start: Почати
reason: Причина
starting: Початок
@ -363,6 +371,7 @@ cs:
role: Role
provide_your_email_to_start: Zadejte svůj email pro zahájení
reason: Důvod
form_expired_at_html: 'Formulář vypršel <span class="font-semibold">%{time}</span>'
start: Zahájit
starting: Zahajování
form_has_been_deleted_by_html: 'Formulář byl smazán uživatelem <span class="font-semibold">%{name}</span>.'
@ -406,6 +415,7 @@ he:
provide_your_email_to_start: ספק את כתובת הדוא"ל שלך כדי להתחיל
start: התחל
starting: מתחיל
form_expired_at_html: 'הטופס פג תוקף ב- <span class="font-semibold">%{time}</span>'
form_has_been_deleted_by_html: 'הטופס נמחק על ידי <span class="font-semibold">%{name}</span>.'
invited_by_html: 'הוזמן על ידי <span class="font-semibold">%{name}</span>'
you_have_been_invited_to_submit_a_form: הוזמנת להגיש טופס
@ -446,6 +456,7 @@ nl:
provide_your_email_to_start: Geef uw e-mailadres om te beginnen
start: Start
reason: Reden
form_expired_at_html: 'Formulier is verlopen op <span class="font-semibold">%{time}</span>'
starting: Starten
form_has_been_deleted_by_html: 'Formulier is verwijderd door <span class="font-semibold">%{name}</span>.'
invited_by_html: 'Uitgenodigd door <span class="font-semibold">%{name}</span>'
@ -491,6 +502,7 @@ ar:
form_has_been_deleted_by_html: 'تم حذف الاستمارة بواسطة <span class="font-semibold">%{name}</span>.'
reason: سبب
invited_by_html: 'تمت الدعوة بواسطة <span class="font-semibold">%{name}</span>'
form_expired_at_html: 'استمارة انتهت صلاحيتها في <span class="font-semibold">%{time}</span>'
you_have_been_invited_to_submit_a_form: تمت دعوتك لتقديم استمارة
signed_on_time: 'تم التوقيع في %{time}'
form_has_been_submitted_already: تم تقديم الاستمارة بالفعل
@ -530,6 +542,7 @@ ko:
start: 시작
reason: 이유
starting: 시작 중
form_expired_at_html: '양식이 만료되었습니다 <span class="font-semibold">%{time}</span>'
form_has_been_deleted_by_html: '<span class="font-semibold">%{name}</span>에 의해 양식이 삭제되었습니다.'
invited_by_html: '<span class="font-semibold">%{name}</span>에 의해 초대되었습니다.'
you_have_been_invited_to_submit_a_form: 양식을 제출하도록 초대되었습니다.

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddExpireAtToSubmissions < ActiveRecord::Migration[7.1]
def change
add_column :submissions, :expire_at, :datetime
end
end

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2024_07_20_063827) do
ActiveRecord::Schema[7.1].define(version: 2024_08_01_125558) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -218,6 +218,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_07_20_063827) do
t.string "slug", null: false
t.text "preferences", null: false
t.bigint "account_id", null: false
t.datetime "expire_at"
t.index ["account_id"], name: "index_submissions_on_account_id"
t.index ["created_by_user_id"], name: "index_submissions_on_created_by_user_id"
t.index ["slug"], name: "index_submissions_on_slug", unique: true

@ -17,6 +17,7 @@ module Submissions
submission = template.submissions.new(created_by_user: user, source:,
account_id: user.account_id,
preferences: set_submission_preferences,
expire_at: attrs[:expire_at],
template_submitters: [], submitters_order:)
maybe_set_template_fields(submission, attrs[:submitters])

@ -3,7 +3,7 @@
module Submissions
module SerializeForApi
SERIALIZE_PARAMS = {
only: %i[id slug source submitters_order created_at updated_at archived_at],
only: %i[id slug source submitters_order expire_at created_at updated_at archived_at],
methods: %i[audit_log_url],
include: {
submitters: { only: %i[id slug uuid name email phone
@ -41,7 +41,7 @@ module Submissions
json[:completed_at] = last_submitter.completed_at
else
json[:documents] = []
json[:status] = 'pending'
json[:status] = submission.expired? ? 'expired' : 'pending'
json[:completed_at] = nil
end

@ -32,6 +32,14 @@ module TimeUtils
tz_info.abbreviation(time)
end
def parse_time_value(value)
if value.is_a?(Integer)
Time.zone.at(value.to_s.first(10).to_i)
elsif value.present?
Time.zone.parse(value)
end
end
def parse_date_string(string, pattern)
pattern = pattern.sub(/Y+/, YEAR_FORMATS)
.sub(/M+/, MONTH_FORMATS)

Loading…
Cancel
Save