From c746340a04a79549916321cba9fece713f3081da Mon Sep 17 00:00:00 2001 From: Daniel Dye Date: Thu, 19 Dec 2024 11:51:13 +0000 Subject: [PATCH] Add "Send claims to ESFA" flow --- .env.test | 1 + .../claims/payments/claims_controller.rb | 29 +++++++ .../claims/payments/claims_controller.rb | 4 +- .../support/claims/payments_controller.rb | 18 ++++- app/mailers/claims/payment_mailer.rb | 24 ++++++ app/models/claims/payment.rb | 4 +- .../claims/support/claims/payment_policy.rb | 9 +++ .../support/claims/payments/claim_policy.rb | 5 ++ .../claims/support/payments/claim_policy.rb | 2 - .../claims/payment/create_and_deliver.rb | 33 ++++++++ .../payments/claim/generate_csv_file.rb | 50 ++++++++++++ .../claims/payments/claims/error.html.erb | 11 +++ .../claims/payments/claims/index.html.erb | 13 +++ .../support/claims/payments/index.html.erb | 4 + .../support/claims/payments/new.html.erb | 31 +++++++ .../payments/new_not_permitted.html.erb | 19 +++++ config/application.rb | 6 ++ config/locales/en/claims/payment_mailer.yml | 30 +++++++ config/locales/en/claims/payments/claims.yml | 13 +++ .../en/claims/support/claims/payments.yml | 22 +++++ config/routes/claims.rb | 9 ++- spec/factories/claims/payments.rb | 2 + spec/mailers/claims/payment_mailer_spec.rb | 42 ++++++++++ .../previews/claims/payment_mailer_preview.rb | 11 +++ .../support/claims/payment_policy_spec.rb | 25 ++++++ .../claims/payments/claim_policy_spec.rb | 16 ++++ .../claims/payment/create_and_deliver_spec.rb | 28 +++++++ .../payments/claim/generate_csv_file_spec.rb | 32 ++++++++ spec/spec_helper.rb | 1 + .../payments/claims/download_claims_spec.rb | 56 +++++++++++++ .../payments/send_claims_to_esfa_spec.rb | 80 +++++++++++++++++++ 31 files changed, 624 insertions(+), 6 deletions(-) create mode 100644 app/controllers/claims/payments/claims_controller.rb create mode 100644 app/mailers/claims/payment_mailer.rb create mode 100644 app/policies/claims/support/claims/payment_policy.rb create mode 100644 app/policies/claims/support/claims/payments/claim_policy.rb delete mode 100644 app/policies/claims/support/payments/claim_policy.rb create mode 100644 app/services/claims/payment/create_and_deliver.rb create mode 100644 app/services/claims/payments/claim/generate_csv_file.rb create mode 100644 app/views/claims/payments/claims/error.html.erb create mode 100644 app/views/claims/payments/claims/index.html.erb create mode 100644 app/views/claims/support/claims/payments/new.html.erb create mode 100644 app/views/claims/support/claims/payments/new_not_permitted.html.erb create mode 100644 config/locales/en/claims/payment_mailer.yml create mode 100644 config/locales/en/claims/payments/claims.yml create mode 100644 spec/mailers/claims/payment_mailer_spec.rb create mode 100644 spec/mailers/previews/claims/payment_mailer_preview.rb create mode 100644 spec/policies/claims/support/claims/payment_policy_spec.rb create mode 100644 spec/policies/claims/support/claims/payments/claim_policy_spec.rb create mode 100644 spec/services/claims/payment/create_and_deliver_spec.rb create mode 100644 spec/services/claims/payments/claim/generate_csv_file_spec.rb create mode 100644 spec/system/claims/payments/claims/download_claims_spec.rb create mode 100644 spec/system/claims/support/claims/payments/send_claims_to_esfa_spec.rb diff --git a/.env.test b/.env.test index 5df3ed04a..edaead8f8 100644 --- a/.env.test +++ b/.env.test @@ -8,6 +8,7 @@ CLAIMS_HOST=claims.localhost CLAIMS_HOSTS=claims.localhost CLAIMS_DFE_SIGN_IN_CLIENT_ID=123 CLAIMS_DFE_SIGN_IN_SECRET=secret +CLAIMS_ESFA_EMAIL_ADDRESSES=esfa@example.com PLACEMENTS_HOST=placements.localhost PLACEMENTS_HOSTS=placements.localhost diff --git a/app/controllers/claims/payments/claims_controller.rb b/app/controllers/claims/payments/claims_controller.rb new file mode 100644 index 000000000..e12490c8f --- /dev/null +++ b/app/controllers/claims/payments/claims_controller.rb @@ -0,0 +1,29 @@ +class Claims::Payments::ClaimsController < Claims::ApplicationController + skip_before_action :authenticate_user! + + before_action :skip_authorization + before_action :validate_token + before_action :set_payment + + def download + send_data @payment.csv_file.download, filename: "payments-claims-#{Time.current.iso8601}.csv" + end + + private + + def validate_token + @payment_id = Rails.application.message_verifier(:payments).verify(token_param) + rescue ActiveSupport::MessageVerifier::InvalidSignature + render "error" + end + + def set_payment + @payment = Claims::Payment.find(@payment_id) + rescue ActiveRecord::RecordNotFound + render "error" + end + + def token_param + params.fetch(:token, nil) + end +end diff --git a/app/controllers/claims/support/claims/payments/claims_controller.rb b/app/controllers/claims/support/claims/payments/claims_controller.rb index 40fdb84dd..d5691554d 100644 --- a/app/controllers/claims/support/claims/payments/claims_controller.rb +++ b/app/controllers/claims/support/claims/payments/claims_controller.rb @@ -1,4 +1,6 @@ class Claims::Support::Claims::Payments::ClaimsController < Claims::Support::ApplicationController + append_pundit_namespace :claims, :payments + before_action :set_claim, only: %i[show] before_action :authorize_claim @@ -11,6 +13,6 @@ def set_claim end def authorize_claim - authorize [:payments, @claim || Claims::Claim] + authorize @claim || Claims::Claim end end diff --git a/app/controllers/claims/support/claims/payments_controller.rb b/app/controllers/claims/support/claims/payments_controller.rb index fa92c44e9..fc2588b18 100644 --- a/app/controllers/claims/support/claims/payments_controller.rb +++ b/app/controllers/claims/support/claims/payments_controller.rb @@ -1,4 +1,6 @@ class Claims::Support::Claims::PaymentsController < Claims::Support::ApplicationController + append_pundit_namespace :claims + before_action :authorize_claims helper_method :filter_form @@ -7,6 +9,20 @@ def index @pagy, @claims = pagy(filtered_claims) end + def new + @submitted_claims = Claims::Claim.submitted + + unless policy(Claims::Payment).create? + render "new_not_permitted" + end + end + + def create + Claims::Payment::CreateAndDeliver.call(current_user:) + + redirect_to claims_support_claims_payments_path, flash: { success: true, heading: "Claims sent to ESFA" } + end + private def filtered_claims @@ -40,6 +56,6 @@ def filter_params end def authorize_claims - authorize filtered_claims + authorize [:payments, filtered_claims] end end diff --git a/app/mailers/claims/payment_mailer.rb b/app/mailers/claims/payment_mailer.rb new file mode 100644 index 000000000..65710d41c --- /dev/null +++ b/app/mailers/claims/payment_mailer.rb @@ -0,0 +1,24 @@ +class Claims::PaymentMailer < Claims::ApplicationMailer + def payment_created_notification(payment) + @payment = payment + + notify_email to: esfa_email_addresses, + from: support_email, + subject: t(".subject"), + body: t( + ".body", + download_page_url: claims_payments_claims_url(token:), + support_email:, + ) + end + + private + + def esfa_email_addresses + ENV.fetch("CLAIMS_ESFA_EMAIL_ADDRESSES").split(",") + end + + def token + Rails.application.message_verifier(:payments).generate(@payment.id, expires_in: 7.days) + end +end diff --git a/app/models/claims/payment.rb b/app/models/claims/payment.rb index 36fd66cd8..783fcfefc 100644 --- a/app/models/claims/payment.rb +++ b/app/models/claims/payment.rb @@ -19,5 +19,7 @@ class Claims::Payment < ApplicationRecord belongs_to :sent_by, class_name: "Claims::SupportUser" has_many :payment_claims, dependent: :destroy - has_many :claims, through: :payment_claims + has_many :claims, through: :payment_claims, class_name: "Claims::Claim" + + has_one_attached :csv_file end diff --git a/app/policies/claims/support/claims/payment_policy.rb b/app/policies/claims/support/claims/payment_policy.rb new file mode 100644 index 000000000..b7ae94b5c --- /dev/null +++ b/app/policies/claims/support/claims/payment_policy.rb @@ -0,0 +1,9 @@ +class Claims::Support::Claims::PaymentPolicy < Claims::ApplicationPolicy + def new? + true + end + + def create? + Claims::Claim.submitted.any? + end +end diff --git a/app/policies/claims/support/claims/payments/claim_policy.rb b/app/policies/claims/support/claims/payments/claim_policy.rb new file mode 100644 index 000000000..6ebfe3477 --- /dev/null +++ b/app/policies/claims/support/claims/payments/claim_policy.rb @@ -0,0 +1,5 @@ +class Claims::Support::Claims::Payments::ClaimPolicy < Claims::ApplicationPolicy + def update? + false + end +end diff --git a/app/policies/claims/support/payments/claim_policy.rb b/app/policies/claims/support/payments/claim_policy.rb deleted file mode 100644 index 1659b7df7..000000000 --- a/app/policies/claims/support/payments/claim_policy.rb +++ /dev/null @@ -1,2 +0,0 @@ -class Claims::Support::Payments::ClaimPolicy < Claims::ApplicationPolicy -end diff --git a/app/services/claims/payment/create_and_deliver.rb b/app/services/claims/payment/create_and_deliver.rb new file mode 100644 index 000000000..d3887df5a --- /dev/null +++ b/app/services/claims/payment/create_and_deliver.rb @@ -0,0 +1,33 @@ +class Claims::Payment::CreateAndDeliver < ApplicationService + def initialize(current_user:) + @current_user = current_user + end + + def call + return if submitted_claims.none? + + ActiveRecord::Base.transaction do |transaction| + csv_file = Claims::Payments::Claim::GenerateCSVFile.call(claims: submitted_claims) + + payment = Claims::Payment.create!(sent_by: current_user, csv_file: File.open(csv_file.to_io), claims: submitted_claims) + + submitted_claims.find_each do |claim| + claim.update!(status: :payment_in_progress, payment_in_progress_at: Time.current) + end + + Claims::ClaimActivity.create!(action: :payment_delivered, user: current_user, record: payment) + + transaction.after_commit do + Claims::PaymentMailer.payment_created_notification(payment).deliver_later + end + end + end + + private + + attr_reader :current_user + + def submitted_claims + @submitted_claims ||= Claims::Claim.submitted + end +end diff --git a/app/services/claims/payments/claim/generate_csv_file.rb b/app/services/claims/payments/claim/generate_csv_file.rb new file mode 100644 index 000000000..80931b2f7 --- /dev/null +++ b/app/services/claims/payments/claim/generate_csv_file.rb @@ -0,0 +1,50 @@ +require "csv" + +class Claims::Payments::Claim::GenerateCSVFile < ApplicationService + HEADERS = %w[ + 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 + ].freeze + + def initialize(claims:) + @claims = claims + end + + def call + CSV.open(file_name, "w", headers: true) do |csv| + csv << HEADERS + + claims.each do |claim| + csv << [ + claim.reference, + claim.school.urn, + claim.school_name, + claim.school.local_authority_name, + claim.amount.format(symbol: false, decimal_mark: ".", no_cents: false), + claim.school.type_of_establishment, + claim.school.group, + claim.submitted_at&.iso8601, + claim.status, + ] + end + + csv + end + end + + private + + attr_reader :claims + + def file_name + Rails.root.join("tmp/payment-claims-#{Time.current}.csv") + end +end diff --git a/app/views/claims/payments/claims/error.html.erb b/app/views/claims/payments/claims/error.html.erb new file mode 100644 index 000000000..446d1209f --- /dev/null +++ b/app/views/claims/payments/claims/error.html.erb @@ -0,0 +1,11 @@ +
+
+
+

<%= t(".page_title") %>

+ +

<%= t(".description") %>

+ +

<%= t(".support_html", mail_to: govuk_mail_to(t("claims.support_email"), t("claims.support_email_html"))) %>

+
+
+
diff --git a/app/views/claims/payments/claims/index.html.erb b/app/views/claims/payments/claims/index.html.erb new file mode 100644 index 000000000..035244e16 --- /dev/null +++ b/app/views/claims/payments/claims/index.html.erb @@ -0,0 +1,13 @@ +
+
+
+

<%= t(".page_title") %>

+ +

<%= t(".description_html") %>

+ +

<%= t(".support_html", mail_to: govuk_mail_to(t("claims.support_email"), t("claims.support_email_html"))) %>

+ + <%= govuk_button_link_to t(".submit"), download_claims_payments_claims_path(token: params[:token]) %> +
+
+
diff --git a/app/views/claims/support/claims/payments/index.html.erb b/app/views/claims/support/claims/payments/index.html.erb index fc9d85b38..d20fa4780 100644 --- a/app/views/claims/support/claims/payments/index.html.erb +++ b/app/views/claims/support/claims/payments/index.html.erb @@ -8,6 +8,10 @@

<%= t(".sub_heading", count: @pagy.count) %>

+
+ <%= govuk_button_link_to t(".buttons.send_claims_to_esfa"), new_claims_support_claims_payment_path %> +
+ <% if @claims.any? %>

<%= t(".description", count: @pagy.count) %>

diff --git a/app/views/claims/support/claims/payments/new.html.erb b/app/views/claims/support/claims/payments/new.html.erb new file mode 100644 index 000000000..4463882ea --- /dev/null +++ b/app/views/claims/support/claims/payments/new.html.erb @@ -0,0 +1,31 @@ +<%= 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 %> + +
+
+
+

<%= t(".page_caption") %>

+

<%= t(".page_title") %>

+ +

<%= t(".description", count: @submitted_claims.count) %>

+ +

<%= t(".details.heading") %>

+ + <%= govuk_list type: :bullet do %> + <% t(".details.list").each do |item| %> +
  • <%= item %>
  • + <% end %> + <% end %> + + <%= govuk_warning_text text: t(".warning") %> + + <%= govuk_button_to t(".submit"), claims_support_claims_payments_path %> + +

    <%= govuk_link_to t(".cancel"), claims_support_claims_payments_path %>

    +
    +
    +
    diff --git a/app/views/claims/support/claims/payments/new_not_permitted.html.erb b/app/views/claims/support/claims/payments/new_not_permitted.html.erb new file mode 100644 index 000000000..4b7f5d5d0 --- /dev/null +++ b/app/views/claims/support/claims/payments/new_not_permitted.html.erb @@ -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 %> + +
    +
    +
    +

    <%= t(".page_caption") %>

    +

    <%= t(".page_title") %>

    + +

    <%= t(".description") %>

    + +

    <%= govuk_link_to t(".cancel"), claims_support_claims_payments_path %>

    +
    +
    +
    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..1caab91fb --- /dev/null +++ b/config/locales/en/claims/payment_mailer.yml @@ -0,0 +1,30 @@ +en: + claims: + payment_mailer: + payment_created_notification: + subject: Claims ready for payment - Claim funding for mentor training + body: | + To ESFA, + + These claims from the Claim funding for mentor training service (Claim) are ready for payment — the link to the latest CSV file is valid for 7 days: + + [%{download_page_url}](%{download_page_url}) + + What you need to do: + + 1. Check and validate the claims in the CSV file by marking them as ‘paid’ or ‘unpaid’ in the ‘claim_status’ column. + + 2. If you mark a claim as ‘unpaid’, add the reason in the ‘claim_unpaid_reason’ column. + + 3. Reply to this email and attach the updated CSV file. + + The Claim Support team will follow up on the reasons for unpaid claims and email back an updated version. + + If you have a problem opening the link or it has expired, reply to this email and request that it be sent again. + + # Give feedback or report a problem + + If you have any questions or feedback, please contact the team at [%{support_email}](%{support_email}). + + Regards + Claim funding for mentor training team diff --git a/config/locales/en/claims/payments/claims.yml b/config/locales/en/claims/payments/claims.yml new file mode 100644 index 000000000..c79f75f4f --- /dev/null +++ b/config/locales/en/claims/payments/claims.yml @@ -0,0 +1,13 @@ +en: + claims: + payments: + claims: + index: + page_title: Download payments file + description_html: Download the Claim funding for mentor training payments CSV file. + support_html: If you have any questions, email %{mail_to}. + submit: Download CSV file + error: + page_title: Sorry, there is a problem with the download link + description: You are seeing this page because the download link is not working. It may have timed out or contained an invalid security token. + support_html: Email %{mail_to} to request a new download link. diff --git a/config/locales/en/claims/support/claims/payments.yml b/config/locales/en/claims/support/claims/payments.yml index 27076ae04..69a6bacca 100644 --- a/config/locales/en/claims/support/claims/payments.yml +++ b/config/locales/en/claims/support/claims/payments.yml @@ -9,7 +9,29 @@ en: zero: Payments one: Payments (%{count}) other: Payments (%{count}) + buttons: + send_claims_to_esfa: Send claims to ESFA description: one: "%{count} claim needs processing" other: "%{count} claims need processing" no_claims: There are no claims waiting to be processed. + new: + page_caption: Payments + page_title: Send claims to ESFA + description: + one: There is %{count} claim included in this submission. + other: There are %{count} claims included in this submission. + details: + heading: "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 ‘Payment in progress’ + warning: This action cannot be undone. + submit: Send claims + cancel: Cancel + new_not_permitted: + page_caption: Payments + page_title: There are no claims to send for payment + description: You cannot send any claims to the ESFA because there are no claims pending payment. + cancel: Cancel diff --git a/config/routes/claims.rb b/config/routes/claims.rb index e5a8c93b0..a5c0b7df4 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: %i[index] do + get :download, on: :collection + end + end + resources :schools, only: %i[index show] do scope module: :schools do resources :claims do @@ -76,12 +82,13 @@ resources :claims, only: %i[show] end - resources :payments, only: %i[index] + resources :payments, only: %i[index new create] resources :samplings, path: "sampling/claims", only: %i[index show] do member do get :confirm_approval put :update end + collection do get "new", to: "samplings/upload_data#new", as: :new_upload_data get "new/:state_key/:step", to: "samplings/upload_data#edit", as: :upload_data diff --git a/spec/factories/claims/payments.rb b/spec/factories/claims/payments.rb index 415809f44..b4e85bee7 100644 --- a/spec/factories/claims/payments.rb +++ b/spec/factories/claims/payments.rb @@ -18,5 +18,7 @@ FactoryBot.define do factory :claims_payment, class: "Claims::Payment" do association :sent_by, factory: :claims_support_user + + claims { build_list(:claim, 3, :submitted) } end end diff --git a/spec/mailers/claims/payment_mailer_spec.rb b/spec/mailers/claims/payment_mailer_spec.rb new file mode 100644 index 000000000..b32fe4ee0 --- /dev/null +++ b/spec/mailers/claims/payment_mailer_spec.rb @@ -0,0 +1,42 @@ +require "rails_helper" + +RSpec.describe Claims::PaymentMailer, freeze: "20 December 2024", type: :mailer do + describe "#payment_created_notification" do + subject(:email) { described_class.payment_created_notification(payment) } + + let(:payment) { create(:claims_payment) } + let(:download_page_url) { claims_payments_claims_url(token:, host: "claims.localhost") } + let(:token) { Rails.application.message_verifier(:payments).generate(payment.id, expires_in: 7.days) } + + it "sends the sampling checks required email" do + expect(email.to).to contain_exactly("esfa@example.com") + expect(email.subject).to eq("Claims ready for payment - Claim funding for mentor training") + expect(email.body.to_s.squish).to eq(<<~EMAIL.squish) + To ESFA, + + These claims from the Claim funding for mentor training service (Claim) are ready for payment — the link to the latest CSV file is valid for 7 days: + + [#{download_page_url}](#{download_page_url}) + + What you need to do: + + 1. Check and validate the claims in the CSV file by marking them as ‘paid’ or ‘unpaid’ in the ‘claim_status’ column. + + 2. If you mark a claim as ‘unpaid’, add the reason in the ‘claim_unpaid_reason’ column. + + 3. Reply to this email and attach the updated CSV file. + + The Claim Support team will follow up on the reasons for unpaid claims and email back an updated version. + + If you have a problem opening the link or it has expired, reply to this email and request that it be sent again. + + # Give feedback or report a problem + + If you have any questions or feedback, please contact the team at [ittmentor.funding@education.gov.uk](ittmentor.funding@education.gov.uk). + + Regards + Claim funding for mentor training team + EMAIL + 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..da4c90f9c --- /dev/null +++ b/spec/mailers/previews/claims/payment_mailer_preview.rb @@ -0,0 +1,11 @@ +class Claims::PaymentMailerPreview < ActionMailer::Preview + def payment_created_notification + Claims::PaymentMailer.payment_created_notification(payment) + end + + private + + def payment + Claims::Payment.first_or_initialize(id: SecureRandom.uuid) + end +end diff --git a/spec/policies/claims/support/claims/payment_policy_spec.rb b/spec/policies/claims/support/claims/payment_policy_spec.rb new file mode 100644 index 000000000..c593560f6 --- /dev/null +++ b/spec/policies/claims/support/claims/payment_policy_spec.rb @@ -0,0 +1,25 @@ +require "rails_helper" + +describe Claims::Support::Claims::PaymentPolicy do + subject { described_class } + + let(:support_user) { build(:claims_support_user) } + + permissions :new? do + it { is_expected.to permit(support_user, Claims::Payment) } + end + + permissions :create? do + context "when there are submitted claims" do + before do + create(:claim, :submitted) + end + + it { is_expected.to permit(support_user, Claims::Payment) } + end + + context "when there are no submitted claims" do + it { is_expected.not_to permit(support_user, Claims::Payment) } + end + end +end diff --git a/spec/policies/claims/support/claims/payments/claim_policy_spec.rb b/spec/policies/claims/support/claims/payments/claim_policy_spec.rb new file mode 100644 index 000000000..4de6f2a68 --- /dev/null +++ b/spec/policies/claims/support/claims/payments/claim_policy_spec.rb @@ -0,0 +1,16 @@ +require "rails_helper" + +describe Claims::Support::Claims::Payments::ClaimPolicy do + subject { described_class } + + let(:support_user) { build(:claims_support_user) } + let(:claim) { build(:claim) } + + permissions :show? do + it { is_expected.to permit(support_user, claim) } + end + + permissions :edit?, :update? do + it { is_expected.not_to permit(support_user, claim) } + 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..a88e76efb --- /dev/null +++ b/spec/services/claims/payment/create_and_deliver_spec.rb @@ -0,0 +1,28 @@ +require "rails_helper" + +describe Claims::Payment::CreateAndDeliver do + subject(:create_and_deliver) { described_class.call(current_user:) } + + let(:current_user) { create(:claims_support_user) } + + describe "#call" do + it "does nothing when there are no submitted claims" do + expect { create_and_deliver }.to not_change(Claims::Payment, :count) + .and not_change(Claims::ClaimActivity, :count) + .and not_enqueue_mail(Claims::PaymentMailer, :payment_created_notification) + end + + context "when there are submitted claims" do + before do + create(:claim, :submitted) + end + + it "creates a payment, activity, updates claims statuses to 'payment_in_progress', and enqueues the deliver of an email to the ESFA" do + expect { create_and_deliver }.to change(Claims::Payment, :count).by(1) + .and change(Claims::ClaimActivity, :count).by(1) + .and change { Claims::Claim.pluck(:status).uniq }.from(%w[submitted]).to(%w[payment_in_progress]) + .and enqueue_mail(Claims::PaymentMailer, :payment_created_notification) + end + end + end +end diff --git a/spec/services/claims/payments/claim/generate_csv_file_spec.rb b/spec/services/claims/payments/claim/generate_csv_file_spec.rb new file mode 100644 index 000000000..ebd2b2cff --- /dev/null +++ b/spec/services/claims/payments/claim/generate_csv_file_spec.rb @@ -0,0 +1,32 @@ +require "rails_helper" + +describe Claims::Payments::Claim::GenerateCSVFile do + subject(:generate_csv_file) { described_class.call(claims:) } + + let(:claims) { create_list(:claim, 3) } + + describe "#call" do + it "generates a CSV file of claims" do + expect(generate_csv_file).to be_a(CSV) + expect(generate_csv_file).to be_closed + + csv = CSV.read(generate_csv_file.path) + + expect(csv.first).to eq(%w[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]) + + claims.each_with_index do |claim, index| + expect(csv[index + 1]).to eq([ + claim.reference, + claim.school.urn, + claim.school_name, + claim.school.local_authority_name, + claim.amount.format(symbol: false, decimal_mark: ".", no_cents: false), + claim.school.type_of_establishment, + claim.school.group, + claim.submitted_at&.iso8601, + claim.status, + ]) + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f4a34c4b6..2bfd1c737 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -45,6 +45,7 @@ # Instead, define negated versions of whatever matchers you wish to negate with # `RSpec::Matchers.define_negated_matcher` and use `expect(...).to matcher.and matcher`. RSpec::Matchers.define_negated_matcher :not_change, :change +RSpec::Matchers.define_negated_matcher :not_enqueue_mail, :enqueue_mail RSpec::Matchers.define_negated_matcher :not_have_enqueued_mail, :have_enqueued_mail RSpec::Matchers.define_negated_matcher :not_have_enqueued_job, :have_enqueued_job diff --git a/spec/system/claims/payments/claims/download_claims_spec.rb b/spec/system/claims/payments/claims/download_claims_spec.rb new file mode 100644 index 000000000..e435fa578 --- /dev/null +++ b/spec/system/claims/payments/claims/download_claims_spec.rb @@ -0,0 +1,56 @@ +require "rails_helper" + +RSpec.describe "Download payment claims", service: :claims, type: :system do + let(:payment) { create(:claims_payment) } + + let(:valid_token) { Rails.application.message_verifier(:payments).generate(payment.id, expires_in: 7.days) } + let(:expired_token) { Rails.application.message_verifier(:payments).generate(payment.id, expires_at: 10.days.ago) } + let(:random_token) { "random" } + + scenario "ESFA user clicks on a valid link in their email inbox" do + when_i_visit_the_download_url(token: valid_token) + then_i_see_a_page_with_download_button + + when_i_click_on_the_download_button + then_a_csv_file_is_downloaded + end + + scenario "ESFA user clicks on an expired link in their email inbox" do + when_i_visit_the_download_url(token: expired_token) + then_i_see_an_error_page + end + + scenario "ESFA user visits the url with a random token" do + when_i_visit_the_download_url(token: random_token) + then_i_see_an_error_page + end + + scenario "ESFA user visits the url without a token" do + when_i_visit_the_download_url(token: nil) + then_i_see_an_error_page + end + + private + + def when_i_visit_the_download_url(token:) + visit claims_payments_claims_path(token:) + end + + def then_i_see_a_page_with_download_button + expect(page).to have_css("h1.govuk-heading-l", text: "Download payments file") + end + + def then_i_see_an_error_page + expect(page).to have_css("h1.govuk-heading-l", text: "Sorry, there is a problem with the download link") + expect(page).to have_css("p.govuk-body", text: "You are seeing this page because the download link is not working. It may have timed out or contained an invalid security token.") + expect(page).to have_css("p.govuk-body", text: "Email ittmentor.funding@education.gov.uk to request a new download link.") + end + + def when_i_click_on_the_download_button + click_on "Download CSV file" + end + + def then_a_csv_file_is_downloaded + expect(response_headers["Content-Type"]).to eq "text/csv" + end +end diff --git a/spec/system/claims/support/claims/payments/send_claims_to_esfa_spec.rb b/spec/system/claims/support/claims/payments/send_claims_to_esfa_spec.rb new file mode 100644 index 000000000..8d6b88d09 --- /dev/null +++ b/spec/system/claims/support/claims/payments/send_claims_to_esfa_spec.rb @@ -0,0 +1,80 @@ +require "rails_helper" + +RSpec.describe "Send claims to ESFA", service: :claims, type: :system do + let(:support_user) { create(:claims_support_user) } + + before do + user_exists_in_dfe_sign_in(user: support_user) + end + + scenario "Support user attempts to send claims to ESFA, but there are no 'submitted' claims" do + given_i_sign_in + when_i_visit_claims_payments_index_page + when_i_click_on_send_claims_to_esfa + then_i_can_see_an_error_page + end + + context "when there are submitted claims" do + before do + create_list(:claim, 3, :submitted) + end + + scenario "Support user sends claims to ESFA" do + given_i_sign_in + when_i_visit_claims_payments_index_page + when_i_click_on_send_claims_to_esfa + then_i_can_see_a_confirmation_page + + when_i_click_on_send_claims + then_i_see_a_success_flash_message + and_claims_have_been_sent_to_esfa + end + end + + private + + def given_i_sign_in + visit sign_in_path + click_on "Sign in using DfE Sign In" + end + + def when_i_visit_claims_payments_index_page + click_on("Claims") + click_on("Payments") + end + + def when_i_click_on_send_claims_to_esfa + click_on("Send claims to ESFA") + end + + def then_i_can_see_an_error_page + expect(page).to have_css("p.govuk-caption-l", text: "Payments") + expect(page).to have_css("h1.govuk-heading-l", text: "There are no claims to send for payment") + expect(page).to have_css("p.govuk-body", text: "You cannot send any claims to the ESFA because there are no claims pending payment.") + expect(page).to have_link("Cancel", href: claims_support_claims_payments_path) + end + + def then_i_can_see_a_confirmation_page + expect(page).to have_css("p.govuk-caption-l", text: "Payments") + expect(page).to have_css("h1.govuk-heading-l", text: "Send claims to ESFA") + expect(page).to have_css("p.govuk-body", text: "There are 3 claims included in this submission.") + expect(page).to have_css("p.govuk-body", text: "Selecting ‘Send claims’ will:") + expect(page).to have_css("li", text: "create a CSV containing a list of all ‘Submitted’ claims") + expect(page).to have_css("li", text: "send an email to the ESFA containing a link to the generated CSV - this link expires after 7 days") + expect(page).to have_css("li", text: "update the claim status from ‘Submitted’ to ‘Payment in progress’") + expect(page).to have_css("div.govuk-warning-text", text: "This action cannot be undone.") + end + + def when_i_click_on_send_claims + click_on("Send claims") + end + + def then_i_see_a_success_flash_message + expect(page).to have_content("Claims sent to ESFA") + end + + def and_claims_have_been_sent_to_esfa + expect(Claims::Claim.submitted.count).to eq(0) + expect(Claims::Claim.payment_in_progress.count).to eq(3) + end +end