Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LUPEYALPHA-761] Early years provider import via Journey Configuration page #3050

Merged
merged 5 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions app/controllers/admin/eligible_ey_providers_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
module Admin
class EligibleEyProvidersController < BaseAdminController
before_action :ensure_service_operator

helper_method :journey_configuration

def create
@upload_form = EligibleEyProvidersForm.new(upload_params)

if @upload_form.invalid?
render "admin/journey_configurations/edit"
else
@upload_form.importer.run
flash[:notice] = @upload_form.importer.results_message

redirect_to edit_admin_journey_configuration_path(Journeys::EarlyYearsPayment::Provider::ROUTING_NAME)
end
end

def show
send_data EligibleEyProvider.csv,
type: "text/csv",
filename: "eligible_early_years_providers.csv"
end

private

def journey_configuration
@journey_configuration ||= Journeys::Configuration.find_by(
routing_name: Journeys::EarlyYearsPayment::Provider::ROUTING_NAME
)
end

def upload_params
params.require(:eligible_ey_providers_upload).permit(:file)
end
end
end
24 changes: 24 additions & 0 deletions app/forms/admin/eligible_ey_providers_form.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class Admin::EligibleEyProvidersForm
include ActiveModel::Model
include ActiveModel::Attributes

attribute :file

validates :file,
presence: {message: "Choose a CSV file of eligible EY providers to upload"}

validate :validate_importer_errors

def importer
@importer ||= EligibleEyProvidersImporter.new(file)
end

private

# importer is not activemodel::errors compliant
def validate_importer_errors
importer.errors.each do |error|
errors.add(:file, error)
end
end
end
26 changes: 26 additions & 0 deletions app/models/eligible_ey_provider.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class EligibleEyProvider < ApplicationRecord
belongs_to :local_authority

def local_authority_code
local_authority.try :code
end

def self.csv
csv_columns = {
"Nursery Name" => :nursery_name,
"EYURN / Ofsted URN" => :urn,
"LA Code" => :local_authority_code,
"Nursery Address" => :nursery_address,
"Primary Key Contact Email Address" => :primary_key_contact_email_address,
"Secondary Contact Email Address (Optional)" => :secondary_contact_email_address
}

CSV.generate(headers: true) do |csv|
csv << csv_columns.keys

all.each do |row|
csv << csv_columns.values.map { |attr| row.send(attr) }
end
end
end
end
31 changes: 31 additions & 0 deletions app/models/eligible_ey_providers_importer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class EligibleEyProvidersImporter < CsvImporter::Base
import_options(
target_data_model: EligibleEyProvider,
transform_rows_with: :row_to_hash,
mandatory_headers: [
"Nursery Name",
"EYURN / Ofsted URN",
"LA Code",
"Nursery Address",
"Primary Key Contact Email Address",
"Secondary Contact Email Address (Optional)"
]
)

def results_message
"Replaced #{deleted_row_count} existing providers with #{rows.count} new providers"
end

private

def row_to_hash(row)
{
nursery_name: row.fetch("Nursery Name"),
urn: row.fetch("EYURN / Ofsted URN"),
local_authority_id: LocalAuthority.find_by(code: row.fetch("LA Code")).try(:id),
nursery_address: row.fetch("Nursery Address"),
primary_key_contact_email_address: row.fetch("Primary Key Contact Email Address"),
secondary_contact_email_address: row.fetch("Secondary Contact Email Address (Optional)")
}
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<hr class="govuk-section-break govuk-section-break--m govuk-section-break--visible">

<h2 class="govuk-heading-m">
Download eligible EY providers
</h2>

<%= link_to "Download CSV", admin_eligible_ey_providers_path, class: "govuk-button" %>

<hr class="govuk-section-break govuk-section-break--m govuk-section-break--visible">

<h2 class="govuk-heading-m">
Upload eligible EY providers
</h2>

<%= form_with model: @upload_form, scope: :eligible_ey_providers_upload, url: admin_eligible_ey_providers_path, builder: GOVUKDesignSystemFormBuilder::FormBuilder do |f| %>
<%= f.govuk_error_summary %>

<%= f.govuk_file_field :file,
label: { text: "Eligible EY providers" },
hint: { text: "This file should be a CSV" } %>

<%= f.govuk_submit "Upload CSV" %>
<% end %>
10 changes: 10 additions & 0 deletions config/analytics_blocklist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@
- family_name
- email
- session_token
:eligible_ey_providers:
- id
- nursery_name
- urn
- local_authority_id
- nursery_address
- primary_key_contact_email_address
- secondary_contact_email_address
- created_at
- updated_at
:schools:
- postcode_sanitised
:file_uploads:
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ def matches?(request)

resources :journey_configurations, only: [:index, :edit, :update]
resources :levelling_up_premium_payments_awards, only: [:index, :create]
resource :eligible_ey_providers, only: [:create, :show], path: "eligible-early-years-providers"
resource :eligible_fe_providers, only: [:create, :show], path: "eligible-further-education-providers"

get "refresh-session", to: "sessions#refresh", as: :refresh_session
Expand Down
15 changes: 15 additions & 0 deletions db/migrate/20240731152713_create_eligible_ey_providers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class CreateEligibleEyProviders < ActiveRecord::Migration[7.0]
def change
create_table :eligible_ey_providers, id: :uuid do |t|
t.string :nursery_name
t.string :urn
t.references :local_authority, null: false, foreign_key: true, type: :uuid
t.string :nursery_address
t.string :primary_key_contact_email_address
t.string :secondary_contact_email_address

t.timestamps
end
add_index :eligible_ey_providers, :urn
end
end
16 changes: 15 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.0].define(version: 2024_07_24_092519) do
ActiveRecord::Schema[7.0].define(version: 2024_07_31_152713) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
enable_extension "pgcrypto"
Expand Down Expand Up @@ -180,6 +180,19 @@
t.datetime "updated_at", null: false
end

create_table "eligible_ey_providers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "nursery_name"
t.string "urn"
t.uuid "local_authority_id", null: false
t.string "nursery_address"
t.string "primary_key_contact_email_address"
t.string "secondary_contact_email_address"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["local_authority_id"], name: "index_eligible_ey_providers_on_local_authority_id"
t.index ["urn"], name: "index_eligible_ey_providers_on_urn"
end

create_table "eligible_fe_providers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.integer "ukprn", null: false
t.text "academic_year", null: false
Expand Down Expand Up @@ -502,6 +515,7 @@
add_foreign_key "claims", "journeys_sessions"
add_foreign_key "decisions", "dfe_sign_in_users", column: "created_by_id"
add_foreign_key "early_career_payments_eligibilities", "schools", column: "current_school_id"
add_foreign_key "eligible_ey_providers", "local_authorities"
add_foreign_key "international_relocation_payments_eligibilities", "schools", column: "current_school_id"
add_foreign_key "levelling_up_premium_payments_eligibilities", "schools", column: "current_school_id"
add_foreign_key "notes", "claims"
Expand Down
11 changes: 11 additions & 0 deletions spec/factories/eligible_ey_providers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FactoryBot.define do
factory :eligible_ey_provider do
association :local_authority

nursery_name { Faker::Company.name }
urn { rand(10_000_000..99_999_999) }
nursery_address { Faker::Address.full_address }
primary_key_contact_email_address { Faker::Internet.email }
secondary_contact_email_address { [Faker::Internet.email, nil].sample }
end
end
37 changes: 37 additions & 0 deletions spec/features/admin/eligible_ey_providers_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
require "rails_helper"

RSpec.feature "Admin of eligible ey providers" do
scenario "manage eligible ey providers" do
when_early_years_payment_provider_journey_configuration_exists
sign_in_as_service_operator

click_link "Manage services"
click_link "Change Claim an early years financial incentive payment - provider"

attach_file "eligible-ey-providers-upload-file-field", eligible_ey_providers_csv_file.path
click_button "Upload CSV"

click_link "Download CSV"

downloaded_csv = page.body

expect(downloaded_csv).to eql(eligible_ey_providers_csv_file.read)
end

def eligible_ey_providers_csv_file
return @eligible_ey_providers_csv_file if @eligible_ey_providers_csv_file

create(:local_authority, code: "101")

@eligible_ey_providers_csv_file = Tempfile.new
@eligible_ey_providers_csv_file.write <<~CSV
Nursery Name,EYURN / Ofsted URN,LA Code,Nursery Address,Primary Key Contact Email Address,Secondary Contact Email Address (Optional)
First Nursery,1000001,101,"1 Test Street, Test Town, TE1 1ST",primary@example.com,secondary@example.com
Second Nursery,1000002,101,"2 Test Street, Test Town, TE1 1ST",primary@example.com,
Third Nursery,1000003,101,"3 Test Street, Test Town, TE1 1ST",other@example.com,
CSV
@eligible_ey_providers_csv_file.rewind

@eligible_ey_providers_csv_file
end
end
91 changes: 91 additions & 0 deletions spec/models/eligible_ey_providers_importer_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
require "rails_helper"

RSpec.describe EligibleEyProvidersImporter do
subject { described_class.new(file) }

let(:file) { Tempfile.new }
let(:correct_headers) { described_class.mandatory_headers.join(",") + "\n" }
let(:local_authority) { create(:local_authority) }

def to_row(hash)
[
"\"#{hash[:nursery_name]}\"",
hash[:urn],
local_authority.code,
"\"#{hash[:nursery_address]}\"",
hash[:primary_key_contact_email_address],
hash[:secondary_contact_email_address]
].join(",") + "\n"
end

describe "#run" do
context "when incorrect headers" do
before do
file.write "incorrect,headers,here,here"
file.close
end

it "has errors" do
subject.run

expect(subject.errors).to be_present
expect(subject.errors).to include("The selected file is missing some expected columns: Nursery Name, EYURN / Ofsted URN, LA Code, Nursery Address, Primary Key Contact Email Address, Secondary Contact Email Address (Optional)")
end
end

context "when csv has no rows" do
before do
file.write correct_headers
file.close
end

it "has no errors" do
subject.run

expect(subject.errors).to be_empty
end

it "does not add any any records" do
expect { subject.run }.not_to change { EligibleEyProvider.count }
end

context "when there are existing records" do
before do
create(:eligible_ey_provider, local_authority:)
create(:eligible_ey_provider, local_authority:)
end

it "deletes existing records" do
expect { subject.run }.to change { EligibleEyProvider.count }.to(0)
end
end
end

context "with valid data" do
before do
file.write correct_headers

3.times do
file.write to_row(attributes_for(:eligible_ey_provider))
end

file.close
end

it "imports new records" do
expect { subject.run }.to change { EligibleEyProvider.count }.by(3)
end

context "when there are existing records" do
before do
create(:eligible_ey_provider, local_authority:)
create(:eligible_ey_provider, local_authority:)
end

it "deletes them with new records" do
expect { subject.run }.to change { EligibleEyProvider.count }.by(1)
end
end
end
end
end
Loading