-
<%= t(".page_caption", reference: @claim.reference) %>
-
-
<%= t(".page_title", school_name: @claim.school.name) %>
- <%= render Claim::StatusTagComponent.new(claim: @claim) %>
-
+
- <% if @claim.submitted? %>
+ <% if @claim.submitted_by_id? %>
<%= t(".submitted_by", name: @claim.submitted_by.full_name, date: l(@claim.submitted_on, format: :long)) %>
<% end %>
diff --git a/app/views/claims/support/payments/_new_payment.html.erb b/app/views/claims/support/payments/_new_payment.html.erb
new file mode 100644
index 000000000..e37fbf79c
--- /dev/null
+++ b/app/views/claims/support/payments/_new_payment.html.erb
@@ -0,0 +1,16 @@
+<%= content_for :page_title, t(".heading") %>
+
+
<%= t(".caption") %>
+
<%= t(".heading") %>
+
+
<%= t(".description") %>
+
+<%= govuk_list type: :bullet do |list| %>
+ <% t(".list").each do |item| %>
+ <%= tag.li item %>
+ <% end %>
+<% end %>
+
+<%= govuk_warning_text text: t(".disclaimer") %>
+
+<%= govuk_button_to t(".submit"), claims_support_payments_path %>
diff --git a/app/views/claims/support/payments/_no_submitted_claims.html.erb b/app/views/claims/support/payments/_no_submitted_claims.html.erb
new file mode 100644
index 000000000..bcab53598
--- /dev/null
+++ b/app/views/claims/support/payments/_no_submitted_claims.html.erb
@@ -0,0 +1,6 @@
+<%= content_for :page_title, t(".heading") %>
+
+
<%= t(".caption") %>
+
<%= t(".heading") %>
+
+
<%= t(".description") %>
diff --git a/app/views/claims/support/payments/claims/check_information_sent.html.erb b/app/views/claims/support/payments/claims/check_information_sent.html.erb
new file mode 100644
index 000000000..88e2a3133
--- /dev/null
+++ b/app/views/claims/support/payments/claims/check_information_sent.html.erb
@@ -0,0 +1,25 @@
+<% content_for :page_title, t(".page_title") %>
+<% render "claims/support/primary_navigation", current: :claims %>
+
+<%= content_for(:before_content) do %>
+ <%= govuk_back_link(href: claims_support_payments_claim_path(@claim)) %>
+<% end %>
+
+
+
+
+
<%= t(".page_caption", reference: @claim.reference) %>
+
<%= t(".page_title") %>
+
+
<%= t(".description") %>
+
+ <%= govuk_warning_text text: t(".warning") %>
+
+ <%= govuk_button_to t(".submit"), information_sent_claims_support_payments_claim_path(@claim), method: :put %>
+
+
+ <%= govuk_link_to t(".cancel"), claims_support_payments_claim_path(@claim), no_visited_state: true %>
+
+
+
+
diff --git a/app/views/claims/support/payments/claims/check_reject.html.erb b/app/views/claims/support/payments/claims/check_reject.html.erb
new file mode 100644
index 000000000..88304687a
--- /dev/null
+++ b/app/views/claims/support/payments/claims/check_reject.html.erb
@@ -0,0 +1,25 @@
+<% content_for :page_title, t(".page_title") %>
+<% render "claims/support/primary_navigation", current: :claims %>
+
+<%= content_for(:before_content) do %>
+ <%= govuk_back_link(href: claims_support_payments_claim_path(@claim)) %>
+<% end %>
+
+
+
+
+
<%= t(".page_caption", reference: @claim.reference) %>
+
<%= t(".page_title") %>
+
+
<%= t(".description") %>
+
+ <%= govuk_warning_text text: t(".warning") %>
+
+ <%= govuk_button_to t(".submit"), reject_claims_support_payments_claim_path(@claim), method: :put %>
+
+
+ <%= govuk_link_to t(".cancel"), claims_support_payments_claim_path(@claim), no_visited_state: true %>
+
+
+
+
diff --git a/app/views/claims/support/payments/claims/show.html.erb b/app/views/claims/support/payments/claims/show.html.erb
new file mode 100644
index 000000000..938b92662
--- /dev/null
+++ b/app/views/claims/support/payments/claims/show.html.erb
@@ -0,0 +1,37 @@
+<%= render "claims/support/primary_navigation", current: :claims %>
+<% content_for(:page_title) { @claim.school.name } %>
+
+<% content_for(:before_content) do %>
+ <%= govuk_back_link href: claims_support_payments_path %>
+<% end %>
+
+
+
+
+
+
+ <% if @claim.unpaid_reason.present? %>
+ <%= govuk_inset_text text: t(".unpaid_reason", reason: @claim.unpaid_reason) %>
+ <% end %>
+
+ <% if @claim.payment_information_requested? %>
+
+ <%= govuk_button_link_to t(".actions.information_sent"), information_sent_claims_support_payments_claim_path(@claim) %>
+ <%= govuk_button_link_to t(".actions.reject"), reject_claims_support_payments_claim_path(@claim), secondary: true %>
+
+ <% end %>
+
+ <% if @claim.submitted_by_id? %>
+
<%= t(".submitted_by", name: @claim.submitted_by.full_name, date: l(@claim.submitted_on, format: :long)) %>
+ <% end %>
+
+ <%= render "claims/support/claims/details", claim: @claim %>
+
+
+
diff --git a/app/views/claims/support/payments/confirmations/_new_confirmation.html.erb b/app/views/claims/support/payments/confirmations/_new_confirmation.html.erb
new file mode 100644
index 000000000..fd8a57b31
--- /dev/null
+++ b/app/views/claims/support/payments/confirmations/_new_confirmation.html.erb
@@ -0,0 +1,7 @@
+
<%= t(".caption") %>
+
<%= t(".heading") %>
+
+<%= form_with url: claims_support_payments_confirmations_path do |f| %>
+ <%= f.govuk_file_field :file, label: { text: t(".label") }, accept: "text/csv" %>
+ <%= f.govuk_submit t(".submit") %>
+<% end %>
diff --git a/app/views/claims/support/payments/confirmations/_no_claims_sent_to_esfa.html.erb b/app/views/claims/support/payments/confirmations/_no_claims_sent_to_esfa.html.erb
new file mode 100644
index 000000000..bcab53598
--- /dev/null
+++ b/app/views/claims/support/payments/confirmations/_no_claims_sent_to_esfa.html.erb
@@ -0,0 +1,6 @@
+<%= content_for :page_title, t(".heading") %>
+
+
<%= t(".caption") %>
+
<%= t(".heading") %>
+
+
<%= t(".description") %>
diff --git a/app/views/claims/support/payments/confirmations/new.html.erb b/app/views/claims/support/payments/confirmations/new.html.erb
new file mode 100644
index 000000000..a03d20445
--- /dev/null
+++ b/app/views/claims/support/payments/confirmations/new.html.erb
@@ -0,0 +1,21 @@
+<%= render "claims/support/primary_navigation", current: :claims %>
+
+<%= content_for(:before_content) do %>
+ <%= govuk_back_link(href: claims_support_payments_path) %>
+<% end %>
+
+
+
+
+ <% if policy.create? %>
+ <%= render "new_confirmation" %>
+ <% else %>
+ <%= render "no_claims_sent_to_esfa" %>
+ <% end %>
+
+
+ <%= govuk_link_to t(".cancel"), claims_support_payments_path %>
+
+
+
+
diff --git a/app/views/claims/support/payments/index.html.erb b/app/views/claims/support/payments/index.html.erb
new file mode 100644
index 000000000..88327bcf6
--- /dev/null
+++ b/app/views/claims/support/payments/index.html.erb
@@ -0,0 +1,29 @@
+<%= render "claims/support/primary_navigation", current: :claims %>
+<%= content_for :page_title, t(".heading") %>
+
+
+
<%= t(".heading") %>
+
+ <%= render "claims/support/claims/secondary_navigation" %>
+
+
<%= t(".sub_heading") %>
+
+
+ <%= govuk_button_link_to t(".send_to_esfa"), new_claims_support_payment_path %>
+ <%= govuk_button_link_to t(".upload_esfa_response"), new_claims_support_payments_confirmation_path, secondary: true %>
+
+
+ <% if @claims.any? %>
+
<%= t(".results", count: @pagy.count) %>
+
+
+ <% @claims.each do |claim| %>
+ <%= render Claim::CardComponent.new(claim:, href: claims_support_payments_claim_path(claim)) %>
+ <% end %>
+
+
+ <%= render PaginationComponent.new(pagy: @pagy) %>
+ <% else %>
+
<%= t(".no_claims") %>
+ <% end %>
+
diff --git a/app/views/claims/support/payments/new.html.erb b/app/views/claims/support/payments/new.html.erb
new file mode 100644
index 000000000..e7ffc4543
--- /dev/null
+++ b/app/views/claims/support/payments/new.html.erb
@@ -0,0 +1,21 @@
+<%= render "claims/support/primary_navigation", current: :claims %>
+
+<%= content_for(:before_content) do %>
+ <%= govuk_back_link(href: claims_support_payments_path) %>
+<% end %>
+
+
+
+
+ <% if policy(Claims::Payment).create? %>
+ <%= render "new_payment" %>
+ <% else %>
+ <%= render "no_submitted_claims" %>
+ <% end %>
+
+
+ <%= govuk_link_to t(".cancel"), claims_support_payments_path %>
+
+
+
+
diff --git a/app/views/claims/support/payments/summary.html.erb b/app/views/claims/support/payments/summary.html.erb
new file mode 100644
index 000000000..ab1accdaa
--- /dev/null
+++ b/app/views/claims/support/payments/summary.html.erb
@@ -0,0 +1,43 @@
+<%= render "claims/support/primary_navigation", current: :claims %>
+<%= content_for :page_title, t(".heading") %>
+
+<%= content_for(:before_content) do %>
+ <%= govuk_back_link(href: claims_support_payments_path) %>
+<% end %>
+
+
+
<%= t(".caption") %>
+
<%= t(".heading") %>
+
+ <%= app_table do |table| %>
+ <% table.with_head do |head| %>
+ <% head.with_row do |row| %>
+ <% row.with_cell header: true, text: "Date" %>
+ <% row.with_cell header: true, text: "Sent by" %>
+ <% row.with_cell header: true, text: "Actions" %>
+ <% end %>
+ <% end %>
+
+ <% table.with_body do |body| %>
+ <% @payments.each do |payment| %>
+ <% body.with_row do |row| %>
+ <% row.with_cell text: l(payment.created_at, format: :long) %>
+ <% row.with_cell text: payment.sent_by.full_name %>
+ <% row.with_cell text: govuk_link_to(t(".links.view_claims", count: payment.claim_ids.length), claims_support_claims_path(payment_id: payment.id)) %>
+ <% end %>
+ <% end %>
+ <% end %>
+ <% end %>
+
+
<%= t(".sub_heading") %>
+
+ <%= govuk_list type: :number do |list| %>
+ <% t(".steps").each do |step| %>
+ <%= tag.li step %>
+ <% end %>
+ <% end %>
+
+
+ <%= govuk_link_to t(".links.all_claims"), claims_support_claims_path %>
+
+
diff --git a/app/views/errors/link_expired.html.erb b/app/views/errors/link_expired.html.erb
new file mode 100644
index 000000000..e7b5ca641
--- /dev/null
+++ b/app/views/errors/link_expired.html.erb
@@ -0,0 +1,15 @@
+<%= content_for :page_title, t(".page_title") %>
+
+
+
+ <%= t(".page_title") %>
+
+
+
+ <%= t(".description") %>
+
+
+
+ <%== t(".support_message", email_link: mail_to(t("#{current_service}.support_email"), t("#{current_service}.support_email_html"), class: "govuk-link")) %>
+
+
diff --git a/config/analytics.yml b/config/analytics.yml
index 65dff686e..4bd834efa 100644
--- a/config/analytics.yml
+++ b/config/analytics.yml
@@ -81,6 +81,7 @@ shared:
- reviewed
- previous_revision_id
- claim_window_id
+ - unpaid_reason
:schools:
- id
- urn
@@ -191,3 +192,9 @@ shared:
- term_id
- created_at
- updated_at
+ :payments:
+ - id
+ - sent_by_id
+ - claim_ids
+ - created_at
+ - updated_at
diff --git a/config/application.rb b/config/application.rb
index d84457ce7..3c4c2a4c9 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -55,5 +55,11 @@ class Application < Rails::Application
# Store user sessions in the database
config.session_store :active_record_store
+
+ # Configure message verifiers to generate URL-safe tokens.
+ config.before_initialize do |app|
+ app.message_verifiers.clear_rotations
+ app.message_verifiers.rotate(url_safe: true)
+ end
end
end
diff --git a/config/locales/en/claims/payment_mailer.yml b/config/locales/en/claims/payment_mailer.yml
new file mode 100644
index 000000000..c1ed6291d
--- /dev/null
+++ b/config/locales/en/claims/payment_mailer.yml
@@ -0,0 +1,17 @@
+en:
+ claims:
+ payment_mailer:
+ payment_created_notification:
+ subject: Claims from funding mentors service - ready for payment
+ body: |
+ Linked below is the latest CSV file from the Claim funding for mentor training service:
+
+ [%{link_to}](%{link_to})
+
+ Please review these claims and mark them as 'paid' or 'unpaid.
+
+ If the claim is 'unpaid' please use the next column to give details. These details will help our support team follow-up on questions or any further information that’s required.
+
+ Regards
+
+ %{service_name} team
diff --git a/config/locales/en/claims/support/claims.yml b/config/locales/en/claims/support/claims.yml
index 648c263d9..ab4138bd7 100644
--- a/config/locales/en/claims/support/claims.yml
+++ b/config/locales/en/claims/support/claims.yml
@@ -2,8 +2,12 @@ en:
claims:
support:
claims:
+ secondary_navigation:
+ claims: All claims
+ payments: Payments
index:
- heading: Claims (%{count})
+ heading: Claims
+ sub_heading: All claims
download_csv: Download CSV
search_label: Search by claim reference
school: School
@@ -20,13 +24,13 @@ en:
clear: Clear search
status: Status
show:
- page_caption: Claim - %{reference}
- page_title: Claim - %{school_name}
- status: Status
+ page_caption: Claim %{reference}
provider: Accredited provider
- mentor_with_index: Mentor %{index}
- mentor: Mentor
+ unpaid_reason: "This payment was not approved because: %{reason}"
submitted_by: Submitted by %{name} on %{date}.
+ actions:
+ information_sent_to_esfa: Information sent to ESFA
+ payment_not_approved: Payment not approved
details:
hour: hour
hourly_rate: Hourly rate
diff --git a/config/locales/en/claims/support/payments.yml b/config/locales/en/claims/support/payments.yml
new file mode 100644
index 000000000..68885a4f4
--- /dev/null
+++ b/config/locales/en/claims/support/payments.yml
@@ -0,0 +1,31 @@
+en:
+ claims:
+ support:
+ payments:
+ index:
+ heading: Claims
+ sub_heading: Payments
+ results:
+ one: "%{count} claim needs processing"
+ other: "%{count} claims need processing"
+ send_to_esfa: Send claims to ESFA
+ upload_esfa_response: Upload ESFA response
+ no_claims: There are no claims waiting to be processed.
+ new:
+ cancel: Cancel
+ new_payment:
+ caption: Payments
+ heading: Send claims to ESFA
+ description: "Selecting ‘Send claims’ will:"
+ list:
+ - create a CSV containing a list of all ‘Submitted’ claims
+ - send an email to the ESFA containing a link to the generated CSV - this link expires after 7 days
+ - update the claim status from ‘Submitted’ to ‘Pending payment’
+ disclaimer: This action cannot be undone.
+ submit: Send claims
+ no_submitted_claims:
+ caption: Payments
+ heading: There are no claims to send for payment
+ description: You cannot send any claims to the ESFA because there are no claims pending payment.
+ create:
+ success: Claims sent to ESFA
diff --git a/config/locales/en/claims/support/payments/claims.yml b/config/locales/en/claims/support/payments/claims.yml
new file mode 100644
index 000000000..4a33b886c
--- /dev/null
+++ b/config/locales/en/claims/support/payments/claims.yml
@@ -0,0 +1,27 @@
+en:
+ claims:
+ support:
+ payments:
+ claims:
+ show:
+ page_caption: Payments - Claim %{reference}
+ provider: Accredited provider
+ unpaid_reason: "This payment was not approved because: %{reason}"
+ submitted_by: Submitted by %{name} on %{date}.
+ actions:
+ information_sent: Confirm information sent
+ reject: Reject claim
+ check_information_sent:
+ page_caption: Payments - Claim %{reference}
+ page_title: Are you sure you want to update the claim?
+ description: You confirm that you have sent the ESFA the information they requested so they can pay the claim.
+ warning: This action cannot be undone.
+ submit: Update claim
+ cancel: Cancel
+ check_reject:
+ page_caption: Payments - Claim %{reference}
+ page_title: Are you sure you want to reject the claim?
+ description: This will result in this claim not being paid.
+ warning: This action cannot be undone.
+ submit: Reject claim
+ cancel: Cancel
diff --git a/config/locales/en/claims/support/payments/confirmations.yml b/config/locales/en/claims/support/payments/confirmations.yml
new file mode 100644
index 000000000..5d7065eaf
--- /dev/null
+++ b/config/locales/en/claims/support/payments/confirmations.yml
@@ -0,0 +1,18 @@
+en:
+ claims:
+ support:
+ payments:
+ confirmations:
+ new:
+ cancel: Cancel
+ new_confirmation:
+ caption: Payments
+ heading: Upload response from ESFA
+ label: Upload a file
+ submit: Continue
+ no_claims_sent_to_esfa:
+ caption: Payments
+ heading: You cannot upload a response from the ESFA
+ description: You cannot upload a response from the ESFA as there are no claims pending payment.
+ create:
+ success: ESFA response uploaded
diff --git a/config/locales/en/components/claim/status_tag_component.yml b/config/locales/en/components/claim/status_tag_component.yml
index f5ae939c5..5ad216e9a 100644
--- a/config/locales/en/components/claim/status_tag_component.yml
+++ b/config/locales/en/components/claim/status_tag_component.yml
@@ -4,3 +4,8 @@ en:
status_tag_component:
draft: Draft
submitted: Submitted
+ payment_in_progress: Payment in progress
+ paid: Paid
+ payment_information_requested: Information requested
+ payment_information_sent: Information sent
+ payment_not_approved: Payment not approved
diff --git a/config/locales/en/errors.yml b/config/locales/en/errors.yml
index 3cd14b169..7cc18b5d6 100644
--- a/config/locales/en/errors.yml
+++ b/config/locales/en/errors.yml
@@ -23,5 +23,7 @@ en:
- Try again later.
- "You’re seeing this message as you’ve tried to access this page repeatedly, please wait a few moments before trying again."
support_message: If you have any questions, please email us at %{email_link}
-
-
+ link_expired:
+ page_title: You cannot download this file
+ description: This is because the download link has expired.
+ support_message: If you have any questions, please email us at %{email_link}
diff --git a/config/routes/claims.rb b/config/routes/claims.rb
index 299ad2bd7..3db5d152a 100644
--- a/config/routes/claims.rb
+++ b/config/routes/claims.rb
@@ -13,6 +13,12 @@
resources :service_updates, path: "service-updates", only: %i[index show]
+ namespace :payments do
+ resources :claims, only: [] do
+ get :download, on: :collection
+ end
+ end
+
resources :schools, only: %i[index show] do
scope module: :schools do
resources :claims do
@@ -64,6 +70,20 @@
get :download_csv, on: :collection
end
+ resources :payments, only: %i[index new create]
+
+ namespace :payments do
+ resources :confirmations, only: %i[new create]
+ resources :claims, only: %i[show] do
+ member do
+ get :check_information_sent, path: "information-sent"
+ put :information_sent, path: "information-sent"
+ get :check_reject, path: "reject"
+ put :reject
+ end
+ end
+ end
+
resources :support_users, path: "support-users" do
get :check, on: :collection
get :remove, on: :member
diff --git a/db/migrate/20240910124347_create_payments.rb b/db/migrate/20240910124347_create_payments.rb
new file mode 100644
index 000000000..33fb2d16c
--- /dev/null
+++ b/db/migrate/20240910124347_create_payments.rb
@@ -0,0 +1,10 @@
+class CreatePayments < ActiveRecord::Migration[7.1]
+ def change
+ create_table :payments, id: :uuid do |t|
+ t.references :sent_by, null: false, foreign_key: { to_table: :users }, type: :uuid
+ t.string :claim_ids, array: true, default: []
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20240918151025_add_paid_and_payment_information_requested_to_claim_status.rb b/db/migrate/20240918151025_add_paid_and_payment_information_requested_to_claim_status.rb
new file mode 100644
index 000000000..3529f1d1b
--- /dev/null
+++ b/db/migrate/20240918151025_add_paid_and_payment_information_requested_to_claim_status.rb
@@ -0,0 +1,6 @@
+class AddPaidAndPaymentInformationRequestedToClaimStatus < ActiveRecord::Migration[7.2]
+ def change
+ add_enum_value :claim_status, "paid"
+ add_enum_value :claim_status, "payment_information_requested"
+ end
+end
diff --git a/db/migrate/20240918154306_add_payment_information_sent_and_payment_not_approved_claim_statuses.rb b/db/migrate/20240918154306_add_payment_information_sent_and_payment_not_approved_claim_statuses.rb
new file mode 100644
index 000000000..7ca7780e2
--- /dev/null
+++ b/db/migrate/20240918154306_add_payment_information_sent_and_payment_not_approved_claim_statuses.rb
@@ -0,0 +1,6 @@
+class AddPaymentInformationSentAndPaymentNotApprovedClaimStatuses < ActiveRecord::Migration[7.2]
+ def change
+ add_enum_value :claim_status, "payment_information_sent"
+ add_enum_value :claim_status, "payment_not_approved"
+ end
+end
diff --git a/db/migrate/20240919150611_add_unpaid_reason_to_claims.rb b/db/migrate/20240919150611_add_unpaid_reason_to_claims.rb
new file mode 100644
index 000000000..0d3bce01d
--- /dev/null
+++ b/db/migrate/20240919150611_add_unpaid_reason_to_claims.rb
@@ -0,0 +1,5 @@
+class AddUnpaidReasonToClaims < ActiveRecord::Migration[7.2]
+ def change
+ add_column :claims, :unpaid_reason, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index c3eb7d020..22616024c 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -17,7 +17,7 @@
# Custom types defined in this database.
# Note that some types may not work with other database engines. Be careful if changing database.
- create_enum "claim_status", ["internal_draft", "draft", "submitted", "payment_in_progress"]
+ create_enum "claim_status", ["internal_draft", "draft", "submitted", "payment_in_progress", "paid", "payment_information_requested", "payment_information_sent", "payment_not_approved"]
create_enum "mentor_training_type", ["refresher", "initial"]
create_enum "placement_status", ["draft", "published"]
create_enum "placement_year_group", ["year_1", "year_2", "year_3", "year_4", "year_5", "year_6"]
@@ -81,6 +81,7 @@
t.uuid "previous_revision_id"
t.boolean "reviewed", default: false
t.uuid "claim_window_id"
+ t.string "unpaid_reason"
t.index ["claim_window_id"], name: "index_claims_on_claim_window_id"
t.index ["created_by_type", "created_by_id"], name: "index_claims_on_created_by"
t.index ["previous_revision_id"], name: "index_claims_on_previous_revision_id"
@@ -231,6 +232,14 @@
t.index ["school_id"], name: "index_partnerships_on_school_id"
end
+ create_table "payments", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
+ t.uuid "sent_by_id", null: false
+ t.string "claim_ids", default: [], array: true
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["sent_by_id"], name: "index_payments_on_sent_by_id"
+ end
+
create_table "pg_search_documents", force: :cascade do |t|
t.text "content"
t.text "organisation_type"
@@ -457,6 +466,7 @@
add_foreign_key "mentor_trainings", "providers"
add_foreign_key "partnerships", "providers"
add_foreign_key "partnerships", "schools"
+ add_foreign_key "payments", "users", column: "sent_by_id"
add_foreign_key "placement_additional_subjects", "placements"
add_foreign_key "placement_additional_subjects", "subjects"
add_foreign_key "placement_mentor_joins", "mentors"
diff --git a/spec/factories/claims.rb b/spec/factories/claims.rb
index f86ba87be..850eb5b52 100644
--- a/spec/factories/claims.rb
+++ b/spec/factories/claims.rb
@@ -9,6 +9,7 @@
# status :enum
# submitted_at :datetime
# submitted_by_type :string
+# unpaid_reason :string
# created_at :datetime not null
# updated_at :datetime not null
# claim_window_id :uuid
diff --git a/spec/factories/claims/payments.rb b/spec/factories/claims/payments.rb
new file mode 100644
index 000000000..9a3a9d21e
--- /dev/null
+++ b/spec/factories/claims/payments.rb
@@ -0,0 +1,23 @@
+# == Schema Information
+#
+# Table name: payments
+#
+# id :uuid not null, primary key
+# claim_ids :string default([]), is an Array
+# created_at :datetime not null
+# updated_at :datetime not null
+# sent_by_id :uuid not null
+#
+# Indexes
+#
+# index_payments_on_sent_by_id (sent_by_id)
+#
+# Foreign Keys
+#
+# fk_rails_... (sent_by_id => users.id)
+#
+FactoryBot.define do
+ factory :claims_payment, class: "Claims::Payment" do
+ association :sent_by, factory: :claims_support_user
+ end
+end
diff --git a/spec/helpers/claims/claim_helper_spec.rb b/spec/helpers/claims/claim_helper_spec.rb
index 83cd7f1df..510cf1892 100644
--- a/spec/helpers/claims/claim_helper_spec.rb
+++ b/spec/helpers/claims/claim_helper_spec.rb
@@ -3,7 +3,7 @@
RSpec.describe Claims::ClaimHelper do
describe "#claim_statuses_for_selection" do
it "returns an array of claims statuses, except draft statuses" do
- expect(claim_statuses_for_selection).to contain_exactly("submitted", "payment_in_progress")
+ expect(claim_statuses_for_selection).to contain_exactly("paid", "payment_not_approved", "payment_information_sent", "payment_information_requested", "payment_in_progress", "submitted")
end
end
end
diff --git a/spec/mailers/previews/claims/payment_mailer_preview.rb b/spec/mailers/previews/claims/payment_mailer_preview.rb
new file mode 100644
index 000000000..a42e52cbb
--- /dev/null
+++ b/spec/mailers/previews/claims/payment_mailer_preview.rb
@@ -0,0 +1,5 @@
+class Claims::PaymentMailerPreview < ActionMailer::Preview
+ def payment_created_notification
+ Claims::PaymentMailer.with(service: :claims).payment_created_notification(Claims::Payment.first)
+ end
+end
diff --git a/spec/models/claims/claim_spec.rb b/spec/models/claims/claim_spec.rb
index 9811f4f06..2c68fe119 100644
--- a/spec/models/claims/claim_spec.rb
+++ b/spec/models/claims/claim_spec.rb
@@ -9,6 +9,7 @@
# status :enum
# submitted_at :datetime
# submitted_by_type :string
+# unpaid_reason :string
# created_at :datetime not null
# updated_at :datetime not null
# claim_window_id :uuid
@@ -96,6 +97,10 @@
draft: "draft",
submitted: "submitted",
payment_in_progress: "payment_in_progress",
+ paid: "paid",
+ payment_information_requested: "payment_information_requested",
+ payment_information_sent: "payment_information_sent",
+ payment_not_approved: "payment_not_approved",
)
.backed_by_column_of_type(:enum)
end
diff --git a/spec/models/claims/payment_spec.rb b/spec/models/claims/payment_spec.rb
new file mode 100644
index 000000000..31d5dfdf6
--- /dev/null
+++ b/spec/models/claims/payment_spec.rb
@@ -0,0 +1,25 @@
+# == Schema Information
+#
+# Table name: payments
+#
+# id :uuid not null, primary key
+# claim_ids :string default([]), is an Array
+# created_at :datetime not null
+# updated_at :datetime not null
+# sent_by_id :uuid not null
+#
+# Indexes
+#
+# index_payments_on_sent_by_id (sent_by_id)
+#
+# Foreign Keys
+#
+# fk_rails_... (sent_by_id => users.id)
+#
+require "rails_helper"
+
+RSpec.describe Claims::Payment, type: :model do
+ context "with associations" do
+ it { is_expected.to belong_to(:sent_by).class_name("Claims::SupportUser") }
+ end
+end
diff --git a/spec/services/claims/payment/create_and_deliver_spec.rb b/spec/services/claims/payment/create_and_deliver_spec.rb
new file mode 100644
index 000000000..7506facf2
--- /dev/null
+++ b/spec/services/claims/payment/create_and_deliver_spec.rb
@@ -0,0 +1,23 @@
+require "rails_helper"
+
+describe Claims::Payment::CreateAndDeliver do
+ describe "#call" do
+ let(:current_user) { create(:claims_support_user) }
+
+ context "when there are no submitted claims" do
+ it "does not create a payment" do
+ expect { described_class.call(current_user:) }.not_to change(Claims::Payment, :count)
+ end
+ end
+
+ context "when there are submitted claims" do
+ let!(:submitted_claims) { create_list(:claim, 3, :submitted) }
+
+ it "creates a payment" do
+ expect { described_class.call(current_user:) }.to change(Claims::Payment, :count)
+ .and change { submitted_claims.map(&:reload).map(&:status).uniq }.to(%w[sent_to_esfa])
+ .and enqueue_mail(Claims::PaymentMailer, :payment_created_notification)
+ end
+ end
+ end
+end
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index be0ac861f..271f94ecf 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -17,6 +17,9 @@
opts.add_argument("--window-size=1400,1400")
end
+ # options.add_preference(:download, prompt_for_download: false, default_directory: "tmp/downloads")
+ # options.add_preference(:browser, set_download_behavior: { behavior: "allow" })
+
Capybara::Selenium::Driver.new(app, browser: :chrome, options:)
end
diff --git a/spec/system/claims/payments/download_claims_spec.rb b/spec/system/claims/payments/download_claims_spec.rb
new file mode 100644
index 000000000..50e5bf045
--- /dev/null
+++ b/spec/system/claims/payments/download_claims_spec.rb
@@ -0,0 +1,64 @@
+require "rails_helper"
+
+RSpec.describe "Download claims", freeze: "27 September 2024 13:00", service: :claims, type: :system do
+ let(:submitted_claims) { create_list(:claim, 3, :submitted) }
+
+ scenario "User visits download link" do
+ token = given_there_is_a_payment_with_claims
+ when_i_visit_the_payment_claims_download_link(token)
+ then_i_download_a_csv_of_claims
+ end
+
+ scenario "User visits download link with invalid token" do
+ when_i_visit_the_payment_claims_download_link("invalid-token")
+ then_i_am_see_an_error_page
+ end
+
+ scenario "User visits download link with an expired token" do
+ token = given_there_is_a_payment_with_claims(expires_at: 1.day.ago)
+ when_i_visit_the_payment_claims_download_link(token)
+ then_i_am_see_an_error_page
+ end
+
+ private
+
+ def given_there_is_a_payment_with_claims(expires_at: 30.days.from_now)
+ payment = create(:claims_payment, claim_ids: submitted_claims.pluck(:id))
+
+ Rails.application.message_verifier(:payment).generate(payment.id, expires_at:)
+ end
+
+ def when_i_visit_the_payment_claims_download_link(token)
+ visit "/payments/claims/download?token=#{token}"
+ end
+
+ def then_i_download_a_csv_of_claims
+ expect(response_headers["Content-Type"]).to eq("text/csv")
+ expect(response_headers["Content-Disposition"]).to eq("attachment; filename=\"claims-2024-09-27.csv\"; filename*=UTF-8''claims-2024-09-27.csv")
+
+ submitted_claims_csv = submitted_claims.map do |claim|
+ [
+ claim.reference,
+ claim.school.urn,
+ claim.school.name,
+ claim.school.local_authority_name,
+ claim.amount,
+ claim.school.type_of_establishment,
+ claim.school.group,
+ claim.submitted_at.iso8601,
+ claim.status,
+ ].join(",")
+ end
+
+ expect(page.body).to eq(<<~CSV)
+ claim_reference,school_urn,school_name,school_local_authority,claim_amount,school_type_of_establishment,school_group,claim_submission_date,claim_status,claim_unpaid_reason
+ #{submitted_claims_csv.join("\n")}
+ CSV
+ end
+
+ def then_i_am_see_an_error_page
+ expect(page).to have_css("h1", text: "You cannot download this file")
+ expect(page).to have_content("This is because the download link has expired.")
+ expect(page).to have_content("If you have any questions, please email us at ittmentor.funding@education.gov.uk")
+ end
+end
diff --git a/spec/system/claims/support/payments/create_a_payment_spec.rb b/spec/system/claims/support/payments/create_a_payment_spec.rb
new file mode 100644
index 000000000..8e8fd459d
--- /dev/null
+++ b/spec/system/claims/support/payments/create_a_payment_spec.rb
@@ -0,0 +1,64 @@
+require "rails_helper"
+
+RSpec.describe "Create a new payment", service: :claims, type: :system do
+ context "when there are no submitted claims" do
+ scenario "User cannot create a new payment" do
+ given_i_sign_in
+ when_i_attempt_to_send_claims_to_esfa
+ then_i_should_see_an_error_page
+ end
+ end
+
+ context "when there are submitted claims", freeze: "26 September 2024 13:11" do
+ before do
+ create_list(:claim, 3, :submitted)\
+ end
+
+ scenario "User creates a new payment" do
+ given_i_sign_in
+ when_i_attempt_to_send_claims_to_esfa
+ then_i_should_see_a_confirmation_page
+
+ when_i_confirm_sending_claims_to_esfa
+ then_i_see_a_success_message
+ end
+ end
+
+ private
+
+ def given_i_sign_in
+ user_exists_in_dfe_sign_in(user: create(:claims_support_user, first_name: "Colin", last_name: "Chapman"))
+ visit sign_in_path
+ click_on "Sign in using DfE Sign In"
+ end
+
+ def when_i_attempt_to_send_claims_to_esfa
+ click_on "Claims"
+ click_on "Payments"
+ click_on "Send claims to ESFA"
+ end
+
+ def then_i_should_see_an_error_page
+ expect(page).to have_css("h1", text: "There are no claims to send for payment")
+ expect(page).to have_content("You cannot send any claims to the ESFA because there are no claims pending payment.")
+ end
+
+ def then_i_should_see_a_confirmation_page
+ expect(page).to have_css("h1", text: "Send claims to ESFA")
+
+ expect(page).to have_content("Selecting ‘Send claims’ will:")
+ expect(page).to have_content("create a CSV containing a list of all ‘Submitted’ claims")
+ expect(page).to have_content("send an email to the ESFA containing a link to the generated CSV - this link expires after 7 days")
+ expect(page).to have_content("update the claim status from ‘Submitted’ to ‘Pending payment’")
+
+ expect(page).to have_content("This action cannot be undone.")
+ end
+
+ def when_i_confirm_sending_claims_to_esfa
+ click_on "Send claims"
+ end
+
+ def then_i_see_a_success_message
+ expect(page).to have_css("h3", text: "Claims sent to ESFA")
+ end
+end
diff --git a/spec/system/claims/support/payments/view_payments_tab_spec.rb b/spec/system/claims/support/payments/view_payments_tab_spec.rb
new file mode 100644
index 000000000..b5d331eb3
--- /dev/null
+++ b/spec/system/claims/support/payments/view_payments_tab_spec.rb
@@ -0,0 +1,30 @@
+require "rails_helper"
+
+RSpec.describe "View claims payments tab", service: :claims, type: :system do
+ scenario "User views the payments tab" do
+ given_i_sign_in
+ when_i_visit_the_claims_payments_tab
+ then_i_should_see_the_payment_actions
+ end
+
+ private
+
+ def given_i_sign_in
+ user_exists_in_dfe_sign_in(user: create(:claims_support_user))
+ visit sign_in_path
+ click_on "Sign in using DfE Sign In"
+ end
+
+ def when_i_visit_the_claims_payments_tab
+ click_on "Claims"
+ click_on "Payments"
+ end
+
+ def then_i_should_see_the_payment_actions
+ expect(page).to have_css("h1", text: "Claims")
+ expect(page).to have_css("h2", text: "Payments")
+
+ expect(page).to have_link("Send claims to ESFA", href: new_claims_support_payment_path)
+ expect(page).to have_link("Upload ESFA response", href: new_claims_support_payments_confirmation_path)
+ end
+end