Skip to content

Commit

Permalink
Move payroll run to background job
Browse files Browse the repository at this point in the history
Creating the November payroll run timed out (October's too).
This commit moves creating the payment records into a background job.

We need to pass the claim ids and topup ids to the background job, this
is because once the admin has reviewed the claims that will be part of
this payroll run (shown in the `new` action) we can't include any claims
created in the interim. We may want to consider someother method of
indicating what claims should be part of this payroll run (create
pending payments? flag claims / topups as reviewed for payroll?) so we
don't have to pass such a large amount of information to the background
job.
  • Loading branch information
rjlynch committed Nov 11, 2024
1 parent f9a97a2 commit a02bfc9
Show file tree
Hide file tree
Showing 18 changed files with 349 additions and 229 deletions.
4 changes: 3 additions & 1 deletion app/controllers/admin/payroll_runs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ def create
return
end

payroll_run = PayrollRun.create_with_claims!(claims, topups, created_by: admin_user)
payroll_run = PayrollRun.create!(created_by: admin_user)

PayrollRunJob.perform_later(payroll_run, claims.ids, topups.ids)

redirect_to [:admin, payroll_run], notice: "Payroll run created"
rescue ActiveRecord::RecordInvalid => e
Expand Down
24 changes: 24 additions & 0 deletions app/jobs/payroll_run_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class PayrollRunJob < ApplicationJob
def perform(payroll_run, claim_ids, topup_ids)
claims = Claim.where(id: claim_ids)
topups = Topup.where(id: topup_ids)

ActiveRecord::Base.transaction do
[claims, topups].reduce([], :concat).group_by(&:national_insurance_number).each_value do |grouped_items|
# associates the claim to the payment, for Topup that's its associated claim
grouped_claims = grouped_items.map { |i| i.is_a?(Topup) ? i.claim : i }

# associates the payment to the Topup, so we know it's payrolled
group_topups = grouped_items.select { |i| i.is_a?(Topup) }

award_amount = grouped_items.map(&:award_amount).compact.sum(0)
Payment.create!(payroll_run: payroll_run, claims: grouped_claims, topups: group_topups, award_amount: award_amount)
end

payroll_run.complete!
end
rescue => e
payroll_run.failed!
raise e
end
end
23 changes: 2 additions & 21 deletions app/models/payroll_run.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class PayrollRun < ApplicationRecord
# backfill existing payroll runs and payments with a payment confirmation
belongs_to :confirmation_report_uploaded_by, class_name: "DfeSignIn::User", optional: true

enum :status, %w[pending complete failed].index_with(&:itself)

validate :ensure_no_payroll_run_this_month, on: :create

scope :this_month, -> { where(created_at: DateTime.now.all_month) }
Expand Down Expand Up @@ -41,27 +43,6 @@ def all_payments_confirmed?
@all_payments_confirmed = payment_confirmations.any? && total_confirmed_payments == payments_count
end

def self.create_with_claims!(claims, topups, attrs = {})
ActiveRecord::Base.transaction do
PayrollRun.create!(attrs).tap do |payroll_run|
[claims, topups].reduce([], :concat).group_by { |obj| group_by_field(obj) }.each_value do |grouped_items|
# associates the claim to the payment, for Topup that's its associated claim
grouped_claims = grouped_items.map { |i| i.is_a?(Topup) ? i.claim : i }

# associates the payment to the Topup, so we know it's payrolled
group_topups = grouped_items.select { |i| i.is_a?(Topup) }

award_amount = grouped_items.map(&:award_amount).compact.sum(0)
Payment.create!(payroll_run: payroll_run, claims: grouped_claims, topups: group_topups, award_amount: award_amount)
end
end
end
end

def self.group_by_field(obj)
obj.national_insurance_number
end

def download_triggered?
downloaded_at.present? && downloaded_by.present?
end
Expand Down
158 changes: 158 additions & 0 deletions app/views/admin/payroll_runs/_complete.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-xl">
<%= @payroll_run.created_at.strftime("%B") %> payroll run
</h1>

<dl class="govuk-summary-list govuk-!-margin-bottom-9">
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">
Approved claims
</dt>

<dd class="govuk-summary-list__value">
<%= @payroll_run.number_of_claims_for_policy(:all, filter: :claims) %>
</dd>
</div>
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">
Top ups
</dt>

<dd class="govuk-summary-list__value">
<%= @payroll_run.number_of_claims_for_policy(Policies::LevellingUpPremiumPayments, filter: :topups) %>
</dd>
</div>
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">
Total award amount
</dt>

<dd class="govuk-summary-list__value">
<%= number_to_currency(@payroll_run.total_award_amount) %>
</dd>
</div>
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">
Created by
</dt>

<dd class="govuk-summary-list__value">
<%= user_details(@payroll_run.created_by) %>
</dd>
</div>
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">
Downloaded
</dt>

<dd class="govuk-summary-list__value">
<%= @payroll_run.download_triggered? ? l(@payroll_run.downloaded_at) : "No" %>
</dd>
</div>
<% if @payroll_run.download_triggered? %>
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">
Downloaded by
</dt>

<dd class="govuk-summary-list__value">
<%= user_details(@payroll_run.downloaded_by) %>
</dd>
</div>
<% end %>
</dl>
</div>

<div class="govuk-grid-column-full">
<h2 class="govuk-heading-m">Summary of claim amounts by service</h2>

<table class="govuk-table">
<thead class="govuk-table__head">
<tr class="govuk-table__row">
<th scope="col" class="govuk-table__header">Service</th>
<th scope="col" class="govuk-table__header">Number of claims</th>
<th scope="col" class="govuk-table__header">Total claimed amount</th>
</tr>
<% Policies.all.each do |policy| %>
<tr class="govuk-table__row">
<th scope="row" class="govuk-table__header"><%= policy.short_name %></th>
<td class="govuk-table__cell"><%= @payroll_run.number_of_claims_for_policy(policy, filter: :claims) %></td>
<td class="govuk-table__cell"><%= number_to_currency(@payroll_run.total_claim_amount_for_policy(policy, filter: :claims)) %></td>
</tr>
<% end %>
<tr class="govuk-table__row">
<th scope="row" class="govuk-table__header"><%= I18n.t("levelling_up_premium_payments.policy_short_name") %> Top Ups</th>
<td class="govuk-table__cell"><%= @payroll_run.number_of_claims_for_policy(Policies::LevellingUpPremiumPayments, filter: :topups) %></td>
<td class="govuk-table__cell"><%= number_to_currency(@payroll_run.total_claim_amount_for_policy(Policies::LevellingUpPremiumPayments, filter: :topups)) %></td>
</tr>
</thead>
<tbody class="govuk-table__body">
</tbody>
</table>
</div>

<% unless @payroll_run.all_payments_confirmed? %>
<div class="govuk-grid-column-full">
<div class="govuk-form-group">
<label class="govuk-label" for="payroll_run_download_link">
You can now send this link to DfE Payroll for processing.
</label>
<%= text_field_tag "payroll_run_download_link", new_admin_payroll_run_download_url(@payroll_run), data: {"copy-to-clipboard": :true}, readonly: true, class: ["govuk-input"] %>
</div>
</div>
<% end %>

<div class="govuk-grid-column-full">
<h2 class="govuk-heading-l">Payments</h2>

<table class="govuk-table">
<thead class="govuk-table__head">
<tr class="govuk-table__row">
<th scope="col" class="govuk-table__header">Payment ID</th>
<th scope="col" class="govuk-table__header">Payee Name</th>
<th scope="col" class="govuk-table__header">Claim Reference</th>
<th scope="col" class="govuk-table__header">Service</th>
<th scope="col" class="govuk-table__header">Claim Amount</th>
<th scope="col" class="govuk-table__header">Payment Amount</th>
<% unless @payroll_run.all_payments_confirmed? %>
<th scope="col" class="govuk-table__header"><span class="govuk-visually-hidden">Actions</span></th>
<% end %>
</tr>
</thead>
<tbody class="govuk-table__body">
<% @payments.each do |payment| %>
<% payment.claims.each_with_index do |claim, index| %>
<% number_of_claims = payment.claims.size %>
<% topup_claim_ids = payment.topups.pluck(:claim_id) %>
<% line_item = topup_claim_ids.include?(claim.id) ? payment.topups.select { |t| t.claim_id == claim.id }.first : claim %>

<tr class="govuk-table__row">
<% if index == 0 %>
<th scope="row" rowspan="<%= number_of_claims %>" class="govuk-table__header"><%= payment.id %></th>
<td class="govuk-table__cell" rowspan="<%= number_of_claims %>"><%= payment.banking_name %></td>
<% end %>
<td class="govuk-table__cell"><%= link_to claim.reference, admin_claim_path(claim), class: "govuk-link" %></td>
<td class="govuk-table__cell"><%= line_item.is_a?(Topup) ? "#{I18n.t("levelling_up_premium_payments.policy_short_name")} (top up)" : claim.policy.short_name %></td>
<td class="govuk-table__cell"><%= number_to_currency(line_item.award_amount) %></td>
<% if index == 0 %>
<td class="govuk-table__cell" rowspan="<%= number_of_claims %>"><%= number_to_currency(payment.award_amount) %></td>
<% unless @payroll_run.all_payments_confirmed? %>
<td class="govuk-table__cell" rowspan="<%= number_of_claims %>">
<% unless payment.confirmation.present? %>
<%= link_to remove_admin_payroll_run_payment_path(id: payment.id, payroll_run_id: payment.payroll_run.id), class: "govuk-link" do %>
Remove <span class="govuk-visually-hidden">payment row</span>
<% end %>
<% end %>
</td>
<% end %>
<% end %>
</tr>
<% end %>
<% end %>
</tbody>
</table>

<%== render partial: 'pagination', locals: { pagy: @pagy } %>
</div>
</div>
15 changes: 15 additions & 0 deletions app/views/admin/payroll_runs/_failed.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-xl">
<%= @payroll_run.created_at.strftime("%B") %> payroll run
</h1>

<div class="govuk-warning-text">
<span class="govuk-warning-text__icon" aria-hidden="true">!</span>
<strong class="govuk-warning-text__text">
<span class="govuk-visually-hidden">Warning</span>
This payroll run errored, please contact tech support
</strong>
</div>
</div>
</div>
14 changes: 14 additions & 0 deletions app/views/admin/payroll_runs/_pending.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-xl">
<%= @payroll_run.created_at.strftime("%B") %> payroll run
</h1>

<p class="govuk-body">
This payroll run is in progress
</p>

<%= govuk_button_link_to "Refresh", admin_payroll_run_path(@payroll_run) %>
</div>
</div>

Loading

0 comments on commit a02bfc9

Please sign in to comment.