Skip to content

Commit

Permalink
Add ESFA payment response flow
Browse files Browse the repository at this point in the history
  • Loading branch information
Nitemaeric committed Jan 3, 2025
1 parent c674e50 commit 6a80c24
Show file tree
Hide file tree
Showing 39 changed files with 549 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class Claims::Support::Claims::PaymentResponsesController < Claims::Support::ApplicationController
append_pundit_namespace :claims

before_action :authorize_claims_payments_response

helper_method :claims_payment_response

def new
render "new_not_permitted" unless policy(claims_payment_response).update?
end

def check
render "new" unless claims_payment_response.save
end

def update
Claims::PaymentResponse::Process.call(payment_response: claims_payment_response, current_user:)

redirect_to claims_support_claims_payments_path, flash: { success: true, heading: t(".success") }
end

private

def authorize_claims_payments_response
authorize claims_payment_response
end

def claims_payment_response_params
params.fetch(:claims_payment_response, {}).permit(:csv_file).merge(user: current_user)
end

def claims_payment_response
@claims_payment_response ||= params[:id] ? Claims::PaymentResponse.find(params[:id]) : Claims::PaymentResponse.new(claims_payment_response_params)
end
end
7 changes: 7 additions & 0 deletions app/jobs/claims/remove_unprocessed_payment_responses_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Claims::RemoveUnprocessedPaymentResponsesJob < ApplicationJob
queue_as :default

def perform
Claims::PaymentResponse.where(processed: false, created_at: ..1.day.ago).find_each(&:destroy!)
end
end
1 change: 1 addition & 0 deletions app/models/claims/claim.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# status :enum
# submitted_at :datetime
# submitted_by_type :string
# unpaid_reason :text
# created_at :datetime not null
# updated_at :datetime not null
# claim_window_id :uuid
Expand Down
29 changes: 29 additions & 0 deletions app/models/claims/payment_response.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# == Schema Information
#
# Table name: payment_responses
#
# id :uuid not null, primary key
# processed :boolean default(FALSE)
# created_at :datetime not null
# updated_at :datetime not null
# user_id :uuid not null
#
# Indexes
#
# index_payment_responses_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (user_id => users.id)
#
class Claims::PaymentResponse < ApplicationRecord
belongs_to :user

has_one_attached :csv_file

validates :csv_file, presence: true

def row_count
@row_count ||= CSV.parse(csv_file.download, headers: true).length
end
end
9 changes: 9 additions & 0 deletions app/policies/claims/support/claims/payment_response_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class Claims::Support::Claims::PaymentResponsePolicy < Claims::ApplicationPolicy
def new?
true
end

def update?
Claims::Claim.payment_in_progress.any?
end
end
34 changes: 34 additions & 0 deletions app/services/claims/payment_response/process.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
class Claims::PaymentResponse::Process < ApplicationService
def initialize(payment_response:, current_user:)
@payment_response = payment_response
@current_user = current_user
end

def call
return if Claims::Claim.payment_in_progress.none?

ActiveRecord::Base.transaction do
CSV.parse(payment_response.csv_file.download, headers: true).each do |row|
claim = Claims::Claim.find_by(reference: row["claim_reference"])

next unless claim
next unless claim.payment_in_progress?

case row["claim_status"]
when "paid"
claim.update!(status: :paid)
when "unpaid"
claim.update!(status: :payment_information_requested, unpaid_reason: row["claim_unpaid_reason"])
end
end

Claims::ClaimActivity.create!(action: :payment_response_uploaded, user: current_user, record: payment_response)

payment_response
end
end

private

attr_reader :payment_response, :current_user
end
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@

<div class="app-timeline__description">
<% case claim_activity.action %>
<% when "payment_request_delivered", "clawback_request_delivered" %>
<% when "payment_request_delivered", "clawback_request_delivered", "payment_response_uploaded" %>
<ul class="app-timeline__documents">
<li class="app-timeline__document-item">
<%= govuk_link_to claim_activity.record.csv_file, class: "app-timeline__document-link" do %>
<%= image_tag "icon-document.svg", class: "app-timeline__document-link__icon" %> Claims sent to ESFA
<%= image_tag "icon-document.svg", class: "app-timeline__document-link__icon" %>

<%= Claims::ClaimActivity.human_attribute_name("document.#{claim_activity.action}") %>
<% end %>
</li>
</ul>
Expand Down
23 changes: 23 additions & 0 deletions app/views/claims/support/claims/payment_responses/check.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<%= render "claims/support/primary_navigation", current: :claims %>
<% content_for(:page_title) { sanitize t(".page_title") } %>

<% content_for(:before_content) do %>
<%= govuk_back_link href: claims_support_claims_payments_path %>
<% end %>

<div class="govuk-width-container">
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= form_with model: claims_payment_response, url: claims_support_claims_payment_response_path(claims_payment_response) do |f| %>
<p class="govuk-caption-l"><%= t(".page_caption") %></p>
<h1 class="govuk-heading-l"><%= t(".page_title") %></h1>

<p class="govuk-body"><%= t(".description", count: claims_payment_response.row_count) %></p>

<%= f.govuk_submit t(".submit") %>
<% end %>

<p class="govuk-body"><%= govuk_link_to t(".cancel"), claims_support_claims_payments_path %></p>
</div>
</div>
</div>
36 changes: 36 additions & 0 deletions app/views/claims/support/claims/payment_responses/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<%= render "claims/support/primary_navigation", current: :claims %>
<% content_for(:page_title) { sanitize t(".page_title") } %>

<% content_for(:before_content) do %>
<%= govuk_back_link href: claims_support_claims_payments_path %>
<% end %>

<div class="govuk-width-container">
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= form_with model: claims_payment_response, url: check_claims_support_claims_payment_responses_path do |f| %>
<%= f.govuk_error_summary %>

<p class="govuk-caption-l"><%= t(".page_caption") %></p>
<h1 class="govuk-heading-l"><%= t(".page_title") %></h1>

<%= f.govuk_file_field :csv_file, label: { text: t(".label") }, accept: "text/csv" %>

<%= govuk_details summary_text: t(".help.label") do %>
<p class="govuk-body"><%= t(".help.description") %></p>
<p class="govuk-body"><%= t(".help.csv_instructions") %></p>

<%= govuk_list type: :bullet do %>
<% t(".help.csv_headers").each do |header| %>
<li><%= header %></li>
<% end %>
<% end %>
<% end %>

<%= f.govuk_submit t(".submit") %>
<% end %>

<p class="govuk-body"><%= govuk_link_to t(".cancel"), claims_support_claims_payments_path %></p>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<%= render "claims/support/primary_navigation", current: :claims %>
<% content_for(:page_title) { sanitize t(".page_title") } %>

<% content_for(:before_content) do %>
<%= govuk_back_link href: claims_support_claims_payments_path %>
<% end %>

<div class="govuk-width-container">
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<p class="govuk-caption-l"><%= t(".page_caption") %></p>
<h1 class="govuk-heading-l"><%= t(".page_title") %></h1>

<p class="govuk-body"><%= t(".description") %></p>

<p class="govuk-body"><%= govuk_link_to t(".cancel"), claims_support_claims_payments_path %></p>
</div>
</div>
</div>
7 changes: 7 additions & 0 deletions app/views/claims/support/claims/payments/claims/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
<p class="govuk-caption-l"><%= t(".page_caption", reference: @claim.reference) %></p>
<h1 class="govuk-heading-l"><%= @claim.school_name %> <%= render Claim::StatusTagComponent.new(claim: @claim) %></h1>

<% if @claim.unpaid_reason.present? %>
<%= govuk_inset_text do %>
<h2 class="govuk-heading-s"><%= t(".unpaid_reason_header") %></h2>
<p class="govuk-body"><%= @claim.unpaid_reason %></p>
<% end %>
<% end %>

<% if @claim.payment_information_requested? %>
<div class="govuk-button-group">
<%= govuk_button_link_to t(".buttons.information_sent"), confirm_information_sent_claims_support_claims_payments_claim_path(@claim) %>
Expand Down
1 change: 1 addition & 0 deletions app/views/claims/support/claims/payments/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

<div class="govuk-button-group">
<%= govuk_button_link_to t(".buttons.send_claims_to_esfa"), new_claims_support_claims_payment_path %>
<%= govuk_button_link_to t(".buttons.upload_esfa_response"), new_claims_support_claims_payment_response_path, secondary: true %>
</div>

<% if @claims.any? %>
Expand Down
7 changes: 7 additions & 0 deletions config/analytics.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ shared:
- claim_window_id
- sampling_reason
- payment_in_progress_at
- unpaid_reason
:samplings:
- id
- created_at
Expand Down Expand Up @@ -236,6 +237,12 @@ shared:
- claim_id
- created_at
- updated_at
:payment_responses:
- id
- user_id
- processed
- created_at
- updated_at
:claim_activities:
- id
- action
Expand Down
4 changes: 4 additions & 0 deletions config/locales/en/activemodel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ en:
blank: Enter a window closing date
overlap: Select a date that is not within an existing claim window
greater_than_or_equal_to: Enter a window closing date that is after the opening date
claims/support/claims/payment_response_form:
attributes:
csv_file:
blank: Select a CSV file to upload
user_invite_form:
attributes:
email:
Expand Down
10 changes: 10 additions & 0 deletions config/locales/en/activerecord.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ en:
sampling_response_uploaded: Provider sampling response uploaded
clawback_request_delivered: Claims sent to ESFA for clawback
clawback_response_uploaded: ESFA clawback response uploaded
claims/claim_activity/document:
payment_request_delivered: Claims sent to ESFA
payment_response_uploaded: ESFA payment response
sampling_response_uploaded: Provider sampling response
clawback_request_delivered: Claims sent to ESFA
clawback_response_uploaded: ESFA clawback response
claims/claim_window:
academic_year: Academic year
window: Claim window
Expand Down Expand Up @@ -74,6 +80,10 @@ en:
greater_than: Enter the number of hours between 1 and 20
less_than_or_equal_to: Enter the number of hours between 1 and 20
not_an_integer: Enter whole numbers only
claims/payment_response:
attributes:
csv_file:
blank: Select a CSV file to upload
user:
attributes:
first_name:
Expand Down
41 changes: 41 additions & 0 deletions config/locales/en/claims/support/claims/payment_responses.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
en:
claims:
support:
claims:
payment_responses:
new:
page_caption: Payments
page_title: Upload ESFA response
label: Upload CSV file
help:
label: Help with the CSV file
description: Use this form to upload the CSV file sent by the ESFA.
csv_instructions: "The CSV file must contain the following headers in the first row:"
csv_headers:
- claim_reference
- school_urn
- school_name
- school_local_authority
- school_establishment_type
- school_establishment_type_group
- claim_amount
- claim_submission_date
- claim_status
- claim_unpaid_reason
submit: Upload CSV file
cancel: Cancel
new_not_permitted:
page_caption: Payments
page_title: You cannot upload a response from the ESFA
description: You cannot upload a response from the ESFA as there are no claims waiting for a response.
cancel: Cancel
check:
page_caption: Payments
page_title: Are you sure you want to upload the ESFA response?
description:
one: There is %{count} claim included in this upload.
other: There are %{count} claims included in this upload.
submit: Upload response
cancel: Cancel
update:
success: ESFA response uploaded
1 change: 1 addition & 0 deletions config/locales/en/claims/support/claims/payments.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ en:
other: Payments (%{count})
buttons:
send_claims_to_esfa: Send claims to ESFA
upload_esfa_response: Upload ESFA response
description:
one: "%{count} claim needs processing"
other: "%{count} claims need processing"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ en:
mentor_with_index: Mentor %{index}
mentor: Mentor
submitted_by: Submitted by %{name} on %{date}.
unpaid_reason_header: Reason claim was not paid
buttons:
information_sent: Confirm information sent
paid: Confirm claim paid
Expand Down
4 changes: 4 additions & 0 deletions config/routes/claims.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@
end

resources :payments, only: %i[index new create]
resources :payment_responses, only: %i[new update] do
post :check, on: :collection
end

resources :samplings, path: "sampling/claims", only: %i[index show] do
member do
get :confirm_approval
Expand Down
5 changes: 5 additions & 0 deletions config/scheduled_jobs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ remove_internal_draft_claims:
class: "Claims::RemoveInternalDraftClaimsJob"
description: "Remove all internal_draft claims"

remove_unprocessed_claims_payment_responses:
cron: "20 0 * * *" # Every day at 00:20.
class: "Claims::RemoveUnprocessedPaymentResponsesJob"
description: "Remove unprocessed claims payment responses"

send_entity_table_checks_to_bigquery:
cron: "30 0 * * *" # Every day at 00:30.
class: "DfE::Analytics::EntityTableCheckJob"
Expand Down
10 changes: 10 additions & 0 deletions db/migrate/20241224133333_create_claims_payment_responses.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class CreateClaimsPaymentResponses < ActiveRecord::Migration[7.2]
def change
create_table :payment_responses, id: :uuid do |t|
t.references :user, null: false, foreign_key: true, type: :uuid
t.boolean :processed, default: false

t.timestamps
end
end
end
5 changes: 5 additions & 0 deletions db/migrate/20250102102324_add_unpaid_reason_to_claims.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddUnpaidReasonToClaims < ActiveRecord::Migration[7.2]
def change
add_column :claims, :unpaid_reason, :text
end
end
Loading

0 comments on commit 6a80c24

Please sign in to comment.