Add admin delete, edit/reopen, and patient self-service edit features

pull/624/head
pramodyallapu 3 months ago
parent 2e5fe4c830
commit a4460cd98e

@ -79,6 +79,8 @@ class SubmissionsController < ApplicationController
end
def destroy
template = @submission.template
notice =
if params[:permanently].in?(['true', true])
@submission.destroy!
@ -92,7 +94,11 @@ class SubmissionsController < ApplicationController
I18n.t('submission_has_been_archived')
end
redirect_back(fallback_location: @submission.template_id ? template_path(@submission.template) : root_path, notice:)
if params[:permanently].in?(['true', true])
redirect_to(template ? template_path(template) : root_path, notice:)
else
redirect_back(fallback_location: template ? template_path(template) : root_path, notice:)
end
end
private

@ -0,0 +1,27 @@
# frozen_string_literal: true
class SubmitterEditFormController < ApplicationController
skip_before_action :authenticate_user!
skip_authorization_check
def update
@submitter = Submitter.find_by!(slug: params[:submitter_slug])
if @submitter.submission.archived_at? || @submitter.submission.expired? || @submitter.submission.template&.archived_at?
return redirect_to submit_form_completed_path(@submitter.slug),
alert: I18n.t('form_cannot_be_edited')
end
unless @submitter.completed_at?
return redirect_to submit_form_path(@submitter.slug)
end
ActiveRecord::Base.transaction do
@submitter.update!(completed_at: nil, opened_at: nil)
@submitter.submission_events.where(event_type: 'complete_form').destroy_all
@submitter.documents.each(&:purge)
end
redirect_to submit_form_path(@submitter.slug)
end
end

@ -0,0 +1,46 @@
# frozen_string_literal: true
class SubmitterEditValuesController < ApplicationController
NON_EDITABLE_TYPES = %w[signature initials image stamp file payment verification kba heading strikethrough].freeze
before_action :load_and_authorize_submitter
def edit
all_fields = @submitter.submission.template_fields || @submitter.submission.template.fields
@fields = all_fields.select { |f| f['submitter_uuid'] == @submitter.uuid }
.reject { |f| NON_EDITABLE_TYPES.include?(f['type']) }
end
def update
all_fields = @submitter.submission.template_fields || @submitter.submission.template.fields
editable_fields = all_fields.select { |f| f['submitter_uuid'] == @submitter.uuid }
.reject { |f| NON_EDITABLE_TYPES.include?(f['type']) }
editable_uuids = editable_fields.map { |f| f['uuid'] }
submitted_values = params[:values].to_h.slice(*editable_uuids)
ActiveRecord::Base.transaction do
@submitter.update!(values: @submitter.values.merge(submitted_values))
@submitter.documents.each(&:purge)
SubmissionEvent.create!(
submitter: @submitter,
event_type: :admin_edit_values,
data: { user_id: current_user.id, user_email: current_user.email, updated_uuids: editable_uuids }
)
end
Submissions::GenerateResultAttachments.call(@submitter)
redirect_to submission_path(@submitter.submission),
notice: I18n.t('submission_values_have_been_updated')
end
private
def load_and_authorize_submitter
@submitter = Submitter.find(params[:id])
authorize! :update, @submitter.submission
end
end

@ -0,0 +1,35 @@
# frozen_string_literal: true
class SubmittersReopenController < ApplicationController
before_action :load_and_authorize_submitter
def update
ActiveRecord::Base.transaction do
@submitter.update!(completed_at: nil, opened_at: nil)
@submitter.submission_events.where(event_type: 'complete_form').destroy_all
@submitter.documents.each(&:purge)
SubmissionEvent.create!(
submitter: @submitter,
event_type: :admin_reopen_form,
data: { user_id: current_user.id, user_email: current_user.email }
)
end
if @submitter.email.present?
SendSubmitterInvitationEmailJob.perform_async('submitter_id' => @submitter.id)
end
redirect_to submission_path(@submitter.submission),
notice: I18n.t('submission_has_been_reopened')
end
private
def load_and_authorize_submitter
@submitter = Submitter.find(params[:id])
authorize! :update, @submitter.submission
end
end

@ -64,7 +64,9 @@ class SubmissionEvent < ApplicationRecord
invite_party: 'invite_party',
complete_form: 'complete_form',
decline_form: 'decline_form',
api_complete_form: 'api_complete_form'
api_complete_form: 'api_complete_form',
admin_reopen_form: 'admin_reopen_form',
admin_edit_values: 'admin_edit_values'
}, scope: false
private

@ -19,6 +19,16 @@
<% if signed_in? && can?(:create, @submission) && @submission.archived_at? && !is_all_completed %>
<%= button_to button_title(title: t('unarchive'), disabled_with: t('unarchive')[0..-2], icon: svg_icon('rotate', class: 'w-6 h-6')), submission_unarchive_index_path(@submission), class: 'btn btn-primary btn-ghost text-base hidden md:flex' %>
<% end %>
<% if signed_in? && can?(:destroy, @submission) && !@submission.archived_at? %>
<span data-tip="<%= t('archive') %>" class="sm:tooltip tooltip-top">
<%= button_to button_title(title: t('archive'), disabled_with: t('archive')[0..-2], icon: svg_icon('archive', class: 'w-6 h-6')), submission_path(@submission), class: 'btn btn-ghost text-base hidden md:flex', method: :delete %>
</span>
<% end %>
<% if signed_in? && can?(:destroy, @submission) && @submission.archived_at? %>
<span data-tip="<%= t('remove') %>" class="sm:tooltip tooltip-top">
<%= button_to button_title(title: t('remove'), disabled_with: t('remove')[0..-2], icon: svg_icon('trash', class: 'w-6 h-6')), submission_path(@submission, permanently: true), class: 'btn btn-ghost btn-error text-base hidden md:flex', method: :delete, data: { turbo_confirm: t('submission_deletion_is_irreversible_and_will_permanently_remove_all_associated_signed_documents_with_it_are_you_sure_') } %>
</span>
<% end %>
<% if @submission.audit_trail.present? %>
<% elsif signed_in? %>
@ -242,6 +252,14 @@
<%= button_to t('resubmit'), submitters_resubmit_path(submitter), method: :put, class: 'btn btn-sm btn-primary w-full', form: { target: '_blank' }, data: { turbo: false } %>
</div>
<% end %>
<% if signed_in? && submitter && submitter.completed_at? && can?(:update, @submission) && !@submission.archived_at? %>
<div class="mt-2 mb-1">
<%= link_to t('edit_values'), edit_submitter_edit_value_path(submitter), class: 'btn btn-sm btn-outline w-full', data: { turbo_frame: 'modal' } %>
</div>
<div class="mt-1 mb-1">
<%= button_to t('reopen_and_resend'), submitters_reopen_path(submitter), method: :put, class: 'btn btn-sm btn-warning w-full', data: { turbo_confirm: 'This will clear the completion status and send a new invitation link to the patient. Are you sure?' } %>
</div>
<% end %>
</div>
</div>
<div class="px-1.5 mb-4">

@ -49,6 +49,17 @@
<%= button_to button_title(title: t('resubmit'), disabled_with: t('resubmit'), icon: svg_icon('reload', class: 'w-6 h-6')), resubmit_form_path, params: { resubmit: @submitter.slug }, method: :put, class: 'white-button w-full' %>
</toggle-submit>
<% end %>
<% unless @submitter.submission.archived_at? || @submitter.submission.expired? %>
<div class="divider uppercase"><%= t('or') %></div>
<toggle-submit class="block">
<%= button_to button_title(title: t('edit_my_response'), disabled_with: t('loading'), icon: svg_icon('pencil', class: 'w-6 h-6')),
submitter_edit_form_path,
params: { submitter_slug: @submitter.slug },
method: :put,
class: 'white-button w-full',
data: { turbo_confirm: t('edit_my_response_confirm') } %>
</toggle-submit>
<% end %>
</div>
</div>
<%= render 'shared/attribution', link_path: '/start', account: @submitter.account %>

@ -0,0 +1,57 @@
<%= render 'shared/turbo_modal_large', title: t('edit_values') do %>
<div class="px-5 mb-5 mt-4">
<%= form_for '', url: submitter_edit_value_path(@submitter), method: :patch,
html: { autocomplete: 'off' }, data: { turbo_frame: :_top } do |f| %>
<% if @fields.empty? %>
<p class="text-base-content/60 text-center py-6">No editable text fields found for this submitter.</p>
<% else %>
<% field_counters = Hash.new { 0 } %>
<% @fields.each do |field| %>
<% field_counters[field['type']] += 1 %>
<% current_value = @submitter.values[field['uuid']] %>
<div class="form-control mb-4">
<label class="label py-0.5">
<span class="label-text font-medium" dir="auto">
<%= field['name'].presence || "#{field['type'].capitalize} Field #{field_counters[field['type']]}" %>
</span>
<span class="label-text-alt text-base-content/40 uppercase text-xs"><%= field['type'] %></span>
</label>
<% if field['type'] == 'select' && field['options'].present? %>
<%= select_tag "values[#{field['uuid']}]",
options_for_select(field['options'], current_value),
include_blank: true,
class: 'select select-bordered w-full' %>
<% elsif field['type'] == 'checkbox' %>
<div class="flex items-center gap-2 mt-1">
<input type="hidden" name="values[<%= field['uuid'] %>]" value="">
<input type="checkbox" name="values[<%= field['uuid'] %>]" value="true"
<%= 'checked' if current_value.to_s == 'true' %>
class="checkbox checkbox-primary" />
<span class="text-sm text-base-content/60">Checked</span>
</div>
<% elsif field['type'] == 'radio' && field['options'].present? %>
<div class="flex flex-col gap-2 mt-1">
<% field['options'].each do |opt| %>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" name="values[<%= field['uuid'] %>]" value="<%= opt %>"
<%= 'checked' if current_value.to_s == opt.to_s %>
class="radio radio-primary radio-sm" />
<span class="text-sm"><%= opt %></span>
</label>
<% end %>
</div>
<% else %>
<%= text_field_tag "values[#{field['uuid']}]", current_value.to_s,
class: 'base-input w-full', dir: 'auto',
placeholder: field['name'].presence || field['type'] %>
<% end %>
</div>
<% end %>
<% end %>
<div class="form-control mt-5">
<%= f.button button_title(title: t('save_and_regenerate'), disabled_with: t('saving')),
class: 'base-button', disabled: @fields.empty? %>
</div>
<% end %>
</div>
<% end %>

@ -565,6 +565,14 @@ en: &en
unable_to_move_template_into_folder: Unable to move template into folder.
template_has_been_unarchived: Template has been unarchived.
submission_has_been_unarchived: Submission has been unarchived.
submission_has_been_reopened: Submission has been reopened and a new invitation link has been sent.
submission_values_have_been_updated: Submission values have been updated and document regenerated.
reopen_and_resend: Reopen & Resend
edit_values: Edit Values
save_and_regenerate: Save & Regenerate Document
edit_my_response: Edit My Response
edit_my_response_confirm: This will reopen the form so you can make changes. Your current submission will be cleared. Continue?
form_cannot_be_edited: This form can no longer be edited.
unable_to_update_file: Unable to upload file.
user_has_been_invited: User has been invited.
unable_to_update_user: Unable to update user.

@ -87,6 +87,8 @@ Rails.application.routes.draw do
resources :testing_api_settings, only: %i[index]
resources :submitters_autocomplete, only: %i[index]
resources :submitters_resubmit, only: %i[update]
resources :submitters_reopen, only: %i[update]
resources :submitter_edit_values, only: %i[edit update]
resources :template_folders_autocomplete, only: %i[index]
resources :webhook_secret, only: %i[show update]
resources :webhook_preferences, only: %i[update]
@ -141,6 +143,7 @@ Rails.application.routes.draw do
end
resource :resubmit_form, controller: 'start_form', only: :update
resource :submitter_edit_form, controller: 'submitter_edit_form', only: %i[update]
resource :submit_form_email_2fa, only: %i[create update]
resources :start_form_email_2fa_send, only: :create

Loading…
Cancel
Save