From b19ea730849c2a2b549b4be263ffe60cd60ef5cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C5=82awosz=20S=C5=82awi=C5=84ski?= Date: Wed, 11 Dec 2024 09:05:38 +0000 Subject: [PATCH] Updating contract data, fixes 2350 data request (#2066) --- app/models/contract_template.rb | 16 ++++ app/services/one_off/update_contracts.rb | 30 ++++++ lib/tasks/one_off/update_contracts_2350.rake | 6 ++ spec/models/contract_template_spec.rb | 19 ++++ .../services/one_off/update_contracts_spec.rb | 94 +++++++++++++++++++ 5 files changed, 165 insertions(+) create mode 100644 app/services/one_off/update_contracts.rb create mode 100644 lib/tasks/one_off/update_contracts_2350.rake create mode 100644 spec/services/one_off/update_contracts_spec.rb diff --git a/app/models/contract_template.rb b/app/models/contract_template.rb index be8855f23b..3b788848f5 100644 --- a/app/models/contract_template.rb +++ b/app/models/contract_template.rb @@ -19,4 +19,20 @@ class ContractTemplate < ApplicationRecord greater_than: 0, } validates :ecf_id, uniqueness: { case_sensitive: false }, allow_nil: true + + def new_from_existing(attributes_to_override) + new_attributes = { + special_course: special_course, + per_participant: per_participant, + output_payment_percentage: output_payment_percentage, + number_of_payment_periods: number_of_payment_periods, + service_fee_percentage: service_fee_percentage, + service_fee_installments: service_fee_installments, + recruitment_target: recruitment_target, + monthly_service_fee: monthly_service_fee, + targeted_delivery_funding_per_participant: targeted_delivery_funding_per_participant, + }.merge(attributes_to_override) + + ContractTemplate.new(new_attributes) + end end diff --git a/app/services/one_off/update_contracts.rb b/app/services/one_off/update_contracts.rb new file mode 100644 index 0000000000..3c41997709 --- /dev/null +++ b/app/services/one_off/update_contracts.rb @@ -0,0 +1,30 @@ +module OneOff + class UpdateContracts + def self.call(year:, month:, cohort_year:, csv_path:) + csv_file = CSV.read(csv_path, headers: true) + + ActiveRecord::Base.transaction do + csv_file.each do |row| + lead_provider = LeadProvider.find_by!(name: row["provider_name"]) + cohort = Cohort.find_by!(start_year: cohort_year) + course = Course.find_by!(identifier: row["course_identifier"]) + + statements = Statement.where(year:, month:, cohort: cohort, lead_provider: lead_provider) + raise "There should be only one statement present (#{row.to_h})" if statements.count != 1 + + contracts = statements.first.contracts.where(course: course) + raise "There should be only one contract present (#{row.to_h})" if contracts.count != 1 + + contract = contracts.first + old_template = contract.contract_template + new_template = old_template.new_from_existing(per_participant: row["per_participant"]) + new_template.save! + + contract.contract_template = new_template + contract.save! + Rails.logger.info("[UpdateContract] Contract #{contract.id} got template updated: #{old_template.id} to #{new_template.id}") + end + end + end + end +end diff --git a/lib/tasks/one_off/update_contracts_2350.rake b/lib/tasks/one_off/update_contracts_2350.rake new file mode 100644 index 0000000000..ea38b7566e --- /dev/null +++ b/lib/tasks/one_off/update_contracts_2350.rake @@ -0,0 +1,6 @@ +namespace :one_off do + desc "One off task for ticket 2350 to update contracts" + task :update_contracts, %i[file_path] => :environment do |_t, args| + OneOff::UpdateContracts.call(year: 2024, month: 12, cohort_year: 2024, csv_path: args[:file_path]) + end +end diff --git a/spec/models/contract_template_spec.rb b/spec/models/contract_template_spec.rb index 0449bdb390..360cc440b9 100644 --- a/spec/models/contract_template_spec.rb +++ b/spec/models/contract_template_spec.rb @@ -16,4 +16,23 @@ it { is_expected.to validate_numericality_of(:recruitment_target).only_integer.is_greater_than(0).with_message("Must be an integer greater than zero") } it { is_expected.to validate_uniqueness_of(:ecf_id).case_insensitive.with_message("ECF ID must be unique").allow_nil } end + + describe "#new_from_existing" do + let(:contract_template) { create(:contract_template, per_participant: 123.0) } + + let(:new_contract_template) { contract_template.new_from_existing(per_participant: 321.0) } + + it "is not persisted" do + expect(new_contract_template).not_to be_persisted + end + + it "overrides attributes" do + expect(new_contract_template.per_participant).to eq(321.0) + end + + it "is copying attributes if not specified" do + new_attributes = contract_template.new_from_existing({}) + expect(new_attributes.attributes.except("created_at", "updated_at", "id", "ecf_id")).to eq(contract_template.attributes.except("created_at", "updated_at", "id", "ecf_id")) + end + end end diff --git a/spec/services/one_off/update_contracts_spec.rb b/spec/services/one_off/update_contracts_spec.rb new file mode 100644 index 0000000000..d4920fccde --- /dev/null +++ b/spec/services/one_off/update_contracts_spec.rb @@ -0,0 +1,94 @@ +require "rails_helper" + +RSpec.describe OneOff::UpdateContracts do + describe ".call" do + let(:year) { 2024 } + let(:month) { 12 } + let(:cohort_year) { 2024 } + let(:csv_file) { Tempfile.new } + let(:csv_path) { csv_file.path } + let(:csv_content) do + <<~CSV + provider_name,course_identifier,per_participant + Provider 1,#{Course::NPQ_SENCO},1000 + Provider 2,#{Course::NPQ_HEADSHIP},2000 + CSV + end + + let(:lead_provider_1) { create(:lead_provider, name: "Provider 1") } + let(:lead_provider_2) { create(:lead_provider, name: "Provider 2") } + let(:cohort) { create(:cohort, start_year: cohort_year) } + let(:course_1) { create(:course, :senco) } + let(:course_2) { create(:course, :headship) } + let(:statement_1) { create(:statement, year: year, month: month, cohort: cohort, lead_provider: lead_provider_1) } + let(:statement_2) { create(:statement, year: year, month: month, cohort: cohort, lead_provider: lead_provider_2) } + let(:contract_template_1) { create(:contract_template, per_participant: 100) } + let(:contract_template_2) { create(:contract_template, per_participant: 200) } + let!(:contract_1) { create(:contract, contract_template: contract_template_1, statement: statement_1, course: course_1) } + let!(:contract_2) { create(:contract, contract_template: contract_template_2, statement: statement_2, course: course_2) } + + before do + csv_file.write(csv_content) + csv_file.rewind + end + + context "when operation is successful" do + it "changes the contract templates" do + OneOff::UpdateContracts.call(year: 2024, month: 12, cohort_year: 2024, csv_path:) + + expect(contract_1.reload.contract_template).not_to eq(contract_template_1) + expect(contract_2.reload.contract_template).not_to eq(contract_template_2) + + expect(contract_1.reload.contract_template.per_participant).to eq(1000.0) + expect(contract_2.reload.contract_template.per_participant).to eq(2000.0) + end + + describe "logs" do + let(:csv_content) do + <<~CSV + provider_name,course_identifier,per_participant + Provider 1,#{Course::NPQ_SENCO},1000 + CSV + end + + let(:expected_message) do + from_id = contract_template_1.id + to_id = contract_1.reload.contract_template.id + + "[UpdateContract] Contract #{contract_1.id} got template updated: #{from_id} to #{to_id}" + end + + it "has proper output" do + allow(Rails.logger).to receive(:info) + + OneOff::UpdateContracts.call(year: 2024, month: 12, cohort_year: 2024, csv_path:) + + expect(Rails.logger) + .to have_received(:info).with(expected_message) + end + end + end + + context "when record is missing from the file" do + let(:csv_content) do + <<~CSV + provider_name,course_identifier,per_participant + Provider 1,#{Course::NPQ_SENCO},1000 + Provider 2,#{Course::NPQ_HEADSHIP},2000 + Provider 3,#{Course::NPQ_HEADSHIP},2000 + CSV + end + + it "makes the whole process unsuccessful" do + expect(contract_1.reload.contract_template.per_participant).not_to eq(1000.0) + expect(contract_2.reload.contract_template.per_participant).not_to eq(2000.0) + end + + it "raises the exception" do + expect { + OneOff::UpdateContracts.call(year: 2024, month: 12, cohort_year: 2024, csv_path:) + }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end +end