From 1902cdaa5500a971921f2c1a33e9a3ea2d89d4d1 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 1 Jul 2026 18:10:08 +0300 Subject: [PATCH] submission completed_at --- app/controllers/api/submissions_controller.rb | 8 ++++++- app/controllers/api/submitters_controller.rb | 4 +++- app/jobs/process_submitter_completion_job.rb | 23 ++++++++++++------- app/models/submission.rb | 1 + ...60701123007_add_submission_completed_at.rb | 7 ++++++ db/schema.rb | 3 ++- lib/mcp/tools/send_documents.rb | 9 -------- lib/submissions.rb | 16 +++++++++++++ lib/submitters/submit_values.rb | 6 ++++- 9 files changed, 56 insertions(+), 21 deletions(-) create mode 100644 db/migrate/20260701123007_add_submission_completed_at.rb diff --git a/app/controllers/api/submissions_controller.rb b/app/controllers/api/submissions_controller.rb index ba64cbc6..2a1154f0 100644 --- a/app/controllers/api/submissions_controller.rb +++ b/app/controllers/api/submissions_controller.rb @@ -92,10 +92,16 @@ module Api Submissions.send_signature_requests(submissions) submissions.each do |submission| + if submission.submitters.all?(&:completed_at?) && Submissions.maybe_update_completed_at(submission) + last_submitter = submission.submitters.max_by(&:completed_at) + end + submission.submitters.each do |submitter| next unless submitter.completed_at? - ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id, 'send_invitation_email' => false) + ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id, + 'is_last' => submitter == last_submitter, + 'send_invitation_email' => false) end end diff --git a/app/controllers/api/submitters_controller.rb b/app/controllers/api/submitters_controller.rb index 15b3e119..2827ba47 100644 --- a/app/controllers/api/submitters_controller.rb +++ b/app/controllers/api/submitters_controller.rb @@ -73,7 +73,9 @@ module Api end if @submitter.completed_at? - ProcessSubmitterCompletionJob.perform_async('submitter_id' => @submitter.id) + is_last = Submissions.maybe_update_completed_at(@submitter.submission) + + ProcessSubmitterCompletionJob.perform_async('submitter_id' => @submitter.id, 'is_last' => is_last) elsif normalized_params[:send_email] || normalized_params[:send_sms] Submitters.send_signature_requests([@submitter]) end diff --git a/app/jobs/process_submitter_completion_job.rb b/app/jobs/process_submitter_completion_job.rb index 135c0c38..84eca9b7 100644 --- a/app/jobs/process_submitter_completion_job.rb +++ b/app/jobs/process_submitter_completion_job.rb @@ -5,30 +5,37 @@ class ProcessSubmitterCompletionJob def perform(params = {}) submitter = Submitter.find(params['submitter_id']) + submission = submitter.submission create_completed_submitter!(submitter) - is_all_completed = !submitter.submission.submitters.exists?(completed_at: nil) + is_last = + if params.key?('is_last') + params['is_last'] + else + !submission.submitters.exists?(completed_at: nil) && + submitter.completed_at == submission.submitters.maximum(:completed_at) + end Submissions::EnsureResultGenerated.call(submitter) - if is_all_completed && submitter.completed_at == submitter.submission.submitters.maximum(:completed_at) - if submitter.submission.account.account_configs.exists?(key: AccountConfig::COMBINE_PDF_RESULT_KEY, value: true) + if is_last + if submission.account.account_configs.exists?(key: AccountConfig::COMBINE_PDF_RESULT_KEY, value: true) Submissions::EnsureCombinedGenerated.call(submitter) end - Submissions::EnsureAuditGenerated.call(submitter.submission) + Submissions::EnsureAuditGenerated.call(submission) enqueue_completed_emails(submitter) end create_completed_documents!(submitter) - if !is_all_completed && submitter.submission.submitters_order_preserved? && params['send_invitation_email'] != false + if !submission.completed_at && submission.submitters_order_preserved? && params['send_invitation_email'] != false enqueue_next_submitter_request_notification(submitter) end - enqueue_completed_webhooks(submitter, is_all_completed:) + enqueue_completed_webhooks(submitter, is_last:) end def create_completed_submitter!(submitter) @@ -77,7 +84,7 @@ class ProcessSubmitterCompletionJob end end - def enqueue_completed_webhooks(submitter, is_all_completed: false) + def enqueue_completed_webhooks(submitter, is_last: false) event_uuids = {} WebhookUrls.for_account_id(submitter.account_id, %w[form.completed submission.completed]).each do |webhook| @@ -89,7 +96,7 @@ class ProcessSubmitterCompletionJob 'webhook_url_id' => webhook.id) end - next unless webhook.events.include?('submission.completed') && is_all_completed + next unless webhook.events.include?('submission.completed') && is_last event_uuids['submission.completed'] ||= SecureRandom.uuid diff --git a/app/models/submission.rb b/app/models/submission.rb index efd82fd1..41cc6b8b 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -6,6 +6,7 @@ # # id :bigint not null, primary key # archived_at :datetime +# completed_at :datetime # expire_at :datetime # name :text # preferences :text not null diff --git a/db/migrate/20260701123007_add_submission_completed_at.rb b/db/migrate/20260701123007_add_submission_completed_at.rb new file mode 100644 index 00000000..42d76225 --- /dev/null +++ b/db/migrate/20260701123007_add_submission_completed_at.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddSubmissionCompletedAt < ActiveRecord::Migration[8.1] + def change + add_column :submissions, :completed_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 3d2012a4..57a5927f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2026_06_28_120000) do +ActiveRecord::Schema[8.1].define(version: 2026_07_01_123007) do # These are extensions that must be enabled in order to support this database enable_extension "btree_gin" enable_extension "pg_catalog.plpgsql" @@ -358,6 +358,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_06_28_120000) do create_table "submissions", force: :cascade do |t| t.bigint "account_id", null: false t.datetime "archived_at" + t.datetime "completed_at" t.datetime "created_at", null: false t.bigint "created_by_user_id" t.datetime "expire_at" diff --git a/lib/mcp/tools/send_documents.rb b/lib/mcp/tools/send_documents.rb index c5aac794..04894798 100644 --- a/lib/mcp/tools/send_documents.rb +++ b/lib/mcp/tools/send_documents.rb @@ -116,15 +116,6 @@ module Mcp Submissions.send_signature_requests(submissions) - submissions.each do |submission| - submission.submitters.each do |submitter| - next unless submitter.completed_at? - - ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id, - 'send_invitation_email' => false) - end - end - SearchEntries.enqueue_reindex(submissions) submission = submissions.first diff --git a/lib/submissions.rb b/lib/submissions.rb index c7ba677b..4e7f1abd 100644 --- a/lib/submissions.rb +++ b/lib/submissions.rb @@ -5,6 +5,22 @@ module Submissions module_function + def maybe_update_completed_at(submission) + incomplete_submitter = Submitter.where(submission_id: submission.id, completed_at: nil).select(1) + + max_completed_at = + Arel::Nodes::Grouping.new( + Submitter.arel_table.project(Submitter.arel_table[:completed_at].maximum) + .where(Submitter.arel_table[:submission_id].eq(Submission.arel_table[:id])) + .ast + ) + + Submission.where(id: submission.id, completed_at: nil) + .where.not(incomplete_submitter.arel.exists) + .update_all(completed_at: max_completed_at) + .positive? + end + def search(current_user, submissions, keyword, search_values: false, search_template: false) if Docuseal.fulltext_search? fulltext_search(current_user, submissions, keyword, search_template:) diff --git a/lib/submitters/submit_values.rb b/lib/submitters/submit_values.rb index 31d956c1..513bb711 100644 --- a/lib/submitters/submit_values.rb +++ b/lib/submitters/submit_values.rb @@ -32,7 +32,11 @@ module Submitters submitter.submission.save! - ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id) if submitter.completed_at? + if submitter.completed_at? + is_last = Submissions.maybe_update_completed_at(submitter.submission) + + ProcessSubmitterCompletionJob.perform_async('submitter_id' => submitter.id, 'is_last' => is_last) + end submitter end