diff --git a/app/assets/stylesheets/components/_heading.scss b/app/assets/stylesheets/components/_heading.scss index 3582a9233..9cc4c2ab5 100644 --- a/app/assets/stylesheets/components/_heading.scss +++ b/app/assets/stylesheets/components/_heading.scss @@ -1,8 +1,11 @@ -.govuk-heading-l { - .govuk-heading-group { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: govuk-spacing(2); +.govuk-heading-group { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: govuk-spacing(2); + margin-bottom: govuk-spacing(6); + + .govuk-heading-l { + margin: 0; } } diff --git a/app/assets/stylesheets/components/_tag.scss b/app/assets/stylesheets/components/_tag.scss index 044c4a668..c53ee2ac7 100644 --- a/app/assets/stylesheets/components/_tag.scss +++ b/app/assets/stylesheets/components/_tag.scss @@ -1,3 +1,7 @@ .govuk-tag { max-width: unset; } + +h1 .govuk-tag { + vertical-align: middle; +} diff --git a/app/components/claim/card_component.html.erb b/app/components/claim/card_component.html.erb index c7eb3ba8e..e5f630f56 100644 --- a/app/components/claim/card_component.html.erb +++ b/app/components/claim/card_component.html.erb @@ -1,7 +1,7 @@
- <%= govuk_link_to t(".claim_title", reference: claim.reference, school_name: claim.school.name), claims_support_claim_path(claim), no_visited_state: true %> + <%= govuk_link_to t(".claim_title", reference: claim.reference, school_name: claim.school.name), href, no_visited_state: true %>
<%= render Claim::StatusTagComponent.new(claim:, classes: %w[govuk-body-s]) %>
diff --git a/app/components/claim/card_component.rb b/app/components/claim/card_component.rb index c17b3f28e..ea131bf25 100644 --- a/app/components/claim/card_component.rb +++ b/app/components/claim/card_component.rb @@ -1,9 +1,10 @@ class Claim::CardComponent < ApplicationComponent - attr_reader :claim + attr_reader :claim, :href - def initialize(claim:, classes: [], html_attributes: {}) + def initialize(claim:, href:, classes: [], html_attributes: {}) super(classes:, html_attributes:) @claim = claim + @href = href end end diff --git a/app/components/claim/status_tag_component.rb b/app/components/claim/status_tag_component.rb index 5d93841cf..862269539 100644 --- a/app/components/claim/status_tag_component.rb +++ b/app/components/claim/status_tag_component.rb @@ -1,6 +1,4 @@ class Claim::StatusTagComponent < ApplicationComponent - attr_reader :claim - def initialize(claim:, classes: [], html_attributes: {}) super(classes:, html_attributes:) @@ -8,11 +6,13 @@ def initialize(claim:, classes: [], html_attributes: {}) end def call - govuk_tag(text: claim.status.humanize, colour:) + govuk_tag(text: t(".#{claim.status}"), colour:) end private + attr_reader :claim + def default_attributes super.merge!(class: super.class) end @@ -26,7 +26,11 @@ def status_colours internal_draft: "grey", draft: "grey", submitted: "blue", - payment_in_progress: "light-blue", + payment_in_progress: "turquoise", + paid: "green", + payment_information_requested: "light-blue", + payment_information_sent: "yellow", + payment_not_approved: "red", }.with_indifferent_access end end diff --git a/app/controllers/claims/payments/claims_controller.rb b/app/controllers/claims/payments/claims_controller.rb new file mode 100644 index 000000000..8a43ba040 --- /dev/null +++ b/app/controllers/claims/payments/claims_controller.rb @@ -0,0 +1,27 @@ +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 Claims::Claim::GenerateCSV.call(claims: @payment.claims), filename: "claims-#{Date.current}.csv" + end + + private + + def validate_token + @payment_id = Rails.application.message_verifier(:payment).verify(token_param) + rescue ActiveSupport::MessageVerifier::InvalidSignature, ActionController::ParameterMissing + render "errors/link_expired" + end + + def token_param + params.require(:token) + end + + def set_payment + @payment = Claims::Payment.find(@payment_id) + end +end diff --git a/app/controllers/claims/support/claims_controller.rb b/app/controllers/claims/support/claims_controller.rb index 3fdfb4ea5..1034af5d6 100644 --- a/app/controllers/claims/support/claims_controller.rb +++ b/app/controllers/claims/support/claims_controller.rb @@ -16,13 +16,13 @@ def show; end def download_csv csv_data = Claims::Claim::GenerateCSV.call(claims: @filtered_claims) - send_data csv_data, filename: "claims-#{Date.current}.csv", type: "text/csv", disposition: "attachment" + send_data csv_data, filename: "claims-#{Time.current.iso8601}.csv", type: "text/csv", disposition: "attachment" end private def set_filtered_claims - @filtered_claims = Claims::ClaimsQuery.call(params: filter_form.query_params) + @filtered_claims = Claims::ClaimsQuery.call(params: filter_form.query_params.merge(query_params)) end def filter_form @@ -49,6 +49,10 @@ def filter_params ) end + def query_params + request.query_parameters.symbolize_keys + end + def set_claim @claim = Claims::Claim.find(params.require(:id)) end diff --git a/app/controllers/claims/support/payments/claims_controller.rb b/app/controllers/claims/support/payments/claims_controller.rb new file mode 100644 index 000000000..9c0b6c300 --- /dev/null +++ b/app/controllers/claims/support/payments/claims_controller.rb @@ -0,0 +1,26 @@ +class Claims::Support::Payments::ClaimsController < Claims::Support::ApplicationController + before_action :set_claim + before_action :authorize_claim + + def information_sent + @claim.update!(status: :payment_information_sent) + + redirect_to claims_support_claim_path(@claim) + end + + def reject + @claim.update!(status: :payment_not_approved) + + redirect_to claims_support_claim_path(@claim) + end + + private + + def set_claim + @claim = policy_scope(Claims::Claim, policy_scope_class: Claims::Support::Payments::ClaimPolicy::Scope).find(params[:id]) + end + + def authorize_claim + authorize @claim, policy_class: Claims::Support::Payments::ClaimPolicy + end +end diff --git a/app/controllers/claims/support/payments/confirmations_controller.rb b/app/controllers/claims/support/payments/confirmations_controller.rb new file mode 100644 index 000000000..ad19a3adf --- /dev/null +++ b/app/controllers/claims/support/payments/confirmations_controller.rb @@ -0,0 +1,23 @@ +class Claims::Support::Payments::ConfirmationsController < Claims::Support::ApplicationController + before_action :authorize_confirmation + + def create + Claims::Payment::ParseConfirmation.call(file: file_param) + + redirect_to claims_support_payments_path, flash: { heading: t(".success"), success: true } + end + + private + + def file_param + params.require(:file) + end + + def authorize_confirmation + authorize :confirmation, policy_class: Claims::Support::Payments::ConfirmationPolicy + end + + def policy(record = nil) + Claims::Support::Payments::ConfirmationPolicy.new(current_user, record) + end +end diff --git a/app/controllers/claims/support/payments_controller.rb b/app/controllers/claims/support/payments_controller.rb new file mode 100644 index 000000000..cd4301fe7 --- /dev/null +++ b/app/controllers/claims/support/payments_controller.rb @@ -0,0 +1,22 @@ +class Claims::Support::PaymentsController < Claims::ApplicationController + before_action :set_claims, only: %i[index] + before_action :authorize_payment + + def index; end + + def create + Claims::Payment::CreateAndDeliver.call(current_user:) + + redirect_to claims_support_payments_path, flash: { heading: t(".success"), success: true } + end + + private + + def set_claims + @pagy, @claims = pagy(Claims::Claim.where(status: %i[payment_information_requested payment_information_sent])) + end + + def authorize_payment + authorize Claims::Payment + end +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index 53dedffb5..d75299c38 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -4,8 +4,13 @@ class ApplicationMailer < Mail::Notify::Mailer default from: "no-reply@education.gov.uk" def notify_email(subject:, **headers) - headers.merge!(rails_mailer: mailer_name, rails_mail_template: action_name) - view_mail(GENERIC_NOTIFY_TEMPLATE, subject: environment_prefix + subject, **headers) + headers.merge!( + rails_mailer: mailer_name, + rails_mail_template: action_name, + subject: environment_prefix + subject, + ) + + view_mail(GENERIC_NOTIFY_TEMPLATE, **headers) end private diff --git a/app/mailers/claims/payment_mailer.rb b/app/mailers/claims/payment_mailer.rb new file mode 100644 index 000000000..f2f1744d6 --- /dev/null +++ b/app/mailers/claims/payment_mailer.rb @@ -0,0 +1,26 @@ +class Claims::PaymentMailer < ApplicationMailer + def payment_created_notification(payment) + notify_email to: esfa_email_addresses, + from: t("claims.support_email"), + subject: t(".subject"), + body: t( + ".body", + link_to: download_claims_payments_claims_url(token: message_verifier.generate(payment.id, expires_at:)), + service_name:, + ) + end + + private + + def esfa_email_addresses + ENV["CLAIMS_ESFA_EMAIL_ADDRESSES"].split(",") + end + + def message_verifier + Rails.application.message_verifier(:payment) + end + + def expires_at + 30.days.from_now + end +end diff --git a/app/models/claims/claim.rb b/app/models/claims/claim.rb index e2da0ade6..75008aba7 100644 --- a/app/models/claims/claim.rb +++ b/app/models/claims/claim.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 @@ -71,6 +72,10 @@ class Claims::Claim < ApplicationRecord 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", }, validate: true diff --git a/app/models/claims/payment.rb b/app/models/claims/payment.rb new file mode 100644 index 000000000..593f5b9e2 --- /dev/null +++ b/app/models/claims/payment.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) +# +class Claims::Payment < ApplicationRecord + belongs_to :sent_by, class_name: "Claims::SupportUser" + + def claims + @claims ||= Claims::Claim.where(id: claim_ids) + end +end diff --git a/app/policies/claims/claim_policy.rb b/app/policies/claims/claim_policy.rb index 492af658e..957947a6f 100644 --- a/app/policies/claims/claim_policy.rb +++ b/app/policies/claims/claim_policy.rb @@ -4,7 +4,7 @@ def create? end def edit? - claim_claim_window_current? && !record.submitted? + claim_claim_window_current? && (record.internal_draft? || record.draft?) end def update? diff --git a/app/policies/claims/payment_policy.rb b/app/policies/claims/payment_policy.rb new file mode 100644 index 000000000..0967149bb --- /dev/null +++ b/app/policies/claims/payment_policy.rb @@ -0,0 +1,17 @@ +class Claims::PaymentPolicy < Claims::ApplicationPolicy + def new? + true + end + + def create? + Claims::Claim.submitted.any? + end + + def instructions? + true + end + + def summary? + true + end +end diff --git a/app/policies/claims/support/payments/claim_policy.rb b/app/policies/claims/support/payments/claim_policy.rb new file mode 100644 index 000000000..09f46af11 --- /dev/null +++ b/app/policies/claims/support/payments/claim_policy.rb @@ -0,0 +1,17 @@ +class Claims::Support::Payments::ClaimPolicy < Claims::ApplicationPolicy + def information_sent? + user.support_user? && record.payment_information_requested? + end + alias_method :check_information_sent?, :information_sent? + + def reject? + user.support_user? && record.payment_information_requested? + end + alias_method :check_reject?, :reject? + + class Scope < ApplicationPolicy::Scope + def resolve + scope.where(status: %i[payment_information_requested payment_information_sent]) + end + end +end diff --git a/app/policies/claims/support/payments/confirmation_policy.rb b/app/policies/claims/support/payments/confirmation_policy.rb new file mode 100644 index 000000000..9a6852275 --- /dev/null +++ b/app/policies/claims/support/payments/confirmation_policy.rb @@ -0,0 +1,9 @@ +class Claims::Support::Payments::ConfirmationPolicy < Claims::ApplicationPolicy + def new? + true + end + + def create? + Claims::Claim.where(status: %i[payment_in_progress payment_information_sent payment_information_requested]).any? + end +end diff --git a/app/queries/claims/claims_query.rb b/app/queries/claims/claims_query.rb index df924338f..6ae7a90f3 100644 --- a/app/queries/claims/claims_query.rb +++ b/app/queries/claims/claims_query.rb @@ -8,6 +8,7 @@ def call scope = submitted_before(scope) scope = status_condition(scope) scope = academic_year_condition(scope) + scope = payment_condition(scope) scope.order_created_at_desc end @@ -55,4 +56,10 @@ def academic_year_condition(scope) scope.where(claim_window_id: Claims::ClaimWindow.select(:id).where(academic_year_id: params[:academic_year_ids])) end + + def payment_condition(scope) + return scope if params[:payment_id].blank? + + scope.where(id: Claims::Payment.find_by(id: params[:payment_id]).claim_ids) + end 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..a91fd7cb9 --- /dev/null +++ b/app/services/claims/payment/create_and_deliver.rb @@ -0,0 +1,29 @@ +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| + payment = Claims::Payment.create!(sent_by: current_user, claim_ids: submitted_claims.pluck(:id)) + + submitted_claims.find_each do |claim| + claim.update!(status: :payment_in_progress) + end + + transaction.after_commit do + Claims::PaymentMailer.with(service: :claims).payment_created_notification(payment).deliver_later + end + end + end + + private + + attr_reader :payment_params, :current_user + + def submitted_claims + @submitted_claims ||= Claims::Claim.submitted + end +end diff --git a/app/services/claims/payment/parse_confirmation.rb b/app/services/claims/payment/parse_confirmation.rb new file mode 100644 index 000000000..60ee7bf5a --- /dev/null +++ b/app/services/claims/payment/parse_confirmation.rb @@ -0,0 +1,25 @@ +class Claims::Payment::ParseConfirmation < ApplicationService + def initialize(file:) + @file = file + end + + def call + CSV.foreach(file, headers: true) do |row| + next unless row["claim_status"].in? %w[paid unpaid] + + claim = Claims::Claim.find_by(reference: row["claim_reference"]) + + next unless claim.status.in? %w[payment_in_progress payment_information_sent payment_information_requested] + + if row["claim_status"] == "paid" + claim.update!(status: :paid) + elsif row["claim_status"] == "unpaid" + claim.update!(status: :payment_information_requested, unpaid_reason: row["claim_unpaid_reason"]) + end + end + end + + private + + attr_reader :file +end diff --git a/app/views/claims/support/claims/_secondary_navigation.html.erb b/app/views/claims/support/claims/_secondary_navigation.html.erb new file mode 100644 index 000000000..95c8d15c6 --- /dev/null +++ b/app/views/claims/support/claims/_secondary_navigation.html.erb @@ -0,0 +1,4 @@ +<%= render SecondaryNavigationComponent.new do |component| %> + <% component.with_navigation_item t(".claims"), claims_support_claims_path, current: local_assigns[:current] == :claims %> + <% component.with_navigation_item t(".payments"), claims_support_payments_path, current: local_assigns[:current] == :payments %> +<% end %> diff --git a/app/views/claims/support/claims/index.html.erb b/app/views/claims/support/claims/index.html.erb index b1e3a5ea7..e8a737d76 100644 --- a/app/views/claims/support/claims/index.html.erb +++ b/app/views/claims/support/claims/index.html.erb @@ -1,10 +1,15 @@ <%= render "claims/support/primary_navigation", current: :claims %> -<%= content_for :page_title, t(".heading", count: @pagy.count) %> +<%= content_for :page_title, t(".heading") %>
-

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

+

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

+ + <%= render "claims/support/claims/secondary_navigation" %> + +

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

+ + <%= govuk_button_link_to t(".download_csv"), download_csv_claims_support_claims_path(**request.query_parameters) %> - <%= govuk_link_to t(".download_csv"), download_csv_claims_support_claims_path(**request.query_parameters), class: "govuk-button", method: :get %>
@@ -51,7 +56,7 @@ <% if @claims.any? %>
<% @claims.each do |claim| %> - <%= render Claim::CardComponent.new(claim:) %> + <%= render Claim::CardComponent.new(claim:, href: claims_support_claim_path(claim)) %> <% end %>
<%= render PaginationComponent.new(pagy: @pagy) %> diff --git a/app/views/claims/support/claims/show.html.erb b/app/views/claims/support/claims/show.html.erb index 37349aaad..65e13b945 100644 --- a/app/views/claims/support/claims/show.html.erb +++ b/app/views/claims/support/claims/show.html.erb @@ -1,5 +1,5 @@ <%= render "claims/support/primary_navigation", current: :claims %> -<% content_for(:page_title) { t(".page_title", school_name: @claim.school.name) } %> +<% content_for(:page_title) { @claim.school.name } %> <% content_for(:before_content) do %> <%= govuk_back_link href: claims_support_claims_path %> @@ -8,13 +8,15 @@
- <%= t(".page_caption", reference: @claim.reference) %> -
-

<%= t(".page_title", school_name: @claim.school.name) %>

- <%= render Claim::StatusTagComponent.new(claim: @claim) %> -
+
+
<%= t(".page_caption", reference: @claim.reference) %>
+

+ <%= @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 %> + +
+
+
+
+
<%= t(".page_caption", reference: @claim.reference) %>
+

+ <%= @claim.school.name %> + <%= render Claim::StatusTagComponent.new(claim: @claim) %> +

+
+ + <% 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