diff --git a/CHANGELOG.md b/CHANGELOG.md index 63f83d096..7e3dff14e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ - Display the budgets headings as "Activity budgets" for level C and D activities on the Financials tab - model a financial value from a csv file +- model a single row of csv data that contains actual, refund and activity + comments ## Release 141 - 2023-12-04 diff --git a/app/models/import/csv/activity_actual_refund_comment/row.rb b/app/models/import/csv/activity_actual_refund_comment/row.rb new file mode 100644 index 000000000..40eacf5de --- /dev/null +++ b/app/models/import/csv/activity_actual_refund_comment/row.rb @@ -0,0 +1,208 @@ +class Import::Csv::ActivityActualRefundComment::Row + attr_reader :errors + + def initialize(csv_row) + @row = csv_row + @errors = {} + @actual = Import::Csv::Financial.new(@row.field("Actual Value")) + @refund = Import::Csv::Financial.new(@row.field("Refund Value")) + end + + def valid? + validate_financial_quarter + validate_financial_year + validate_roda_identifier + validate_receiving_organisation_name + validate_receiving_organisation_type + + if validate_actual && validate_refund + validate_no_actual_and_refund + validate_refund_must_have_comment + end + + @errors.empty? + end + + def invalid? + !valid? + end + + def empty? + return nil unless valid? + + actual_value.zero? && refund_value.zero? && comment.nil? + end + + def actual_value + @actual.decimal_value + end + + def refund_value + @refund.decimal_value + end + + def comment + @row.field("Comment").tap do |comment| + return nil if comment.blank? + end + end + + def roda_identifier + @row.field("Activity RODA Identifier").tap do |identifier| + return nil if identifier.blank? + end + end + + def financial_quarter + @row.field("Financial Quarter").tap do |quarter| + return nil if quarter.blank? + end + end + + def financial_year + @row.field("Financial Year").tap do |year| + return nil if year.blank? + end + end + + def receiving_organisation_name + @row.field("Receiving Organisation Name").tap do |name| + return nil if name.blank? + end + end + + def receiving_organisation_type + @row.field("Receiving Organisation Type").tap do |type| + return nil if type.blank? + end + end + + def receiving_organisation_iati_reference + @row.field("Receiving Organisation IATI Reference").tap do |reference| + return nil if reference.blank? + end + end + + private def original_actual_value + @actual.original_value + end + + private def original_refund_value + @refund.original_value + end + + private def validate_roda_identifier + if roda_identifier.blank? + @errors["Activity RODA Identifier"] = [roda_identifier, I18n.t("import.csv.activity_actual_refund_comment.errors.default.required")] + return false + end + + true + end + + private def validate_financial_quarter + if financial_quarter.blank? + @errors["Financial Quarter"] = [financial_quarter, I18n.t("import.csv.activity_actual_refund_comment.errors.default.required")] + return false + end + + if ["1", "2", "3", "4"].none?(financial_quarter) + @errors["Financial Quarter"] = [financial_quarter, I18n.t("import.csv.activity_actual_refund_comment.errors.financial_quarter")] + return false + end + + true + end + + private def validate_financial_year + if financial_year.blank? + @errors["Financial Year"] = [financial_year, I18n.t("import.csv.activity_actual_refund_comment.errors.default.required")] + return false + end + + begin + FinancialYear.new(financial_year) + rescue ::FinancialYear::InvalidYear + @errors["Financial Year"] = [financial_year, I18n.t("import.csv.activity_actual_refund_comment.errors.financial_year")] + return false + end + + true + end + + private def validate_actual + if actual_value.nil? + @errors["Actual Value"] = [original_actual_value, I18n.t("import.csv.activity_actual_refund_comment.errors.financial_value")] + return false + end + true + end + + private def validate_refund + if refund_value.nil? + @errors["Refund Value"] = [original_refund_value, I18n.t("import.csv.activity_actual_refund_comment.errors.financial_value")] + return false + end + true + end + + private def validate_no_actual_and_refund + if actual_value.positive? && (refund_value.positive? || refund_value.negative?) + @errors["Actual Value"] = [original_actual_value, I18n.t("import.csv.activity_actual_refund_comment.errors.actual_value_with_refund")] + @errors["Refund Value"] = [original_refund_value, I18n.t("import.csv.activity_actual_refund_comment.errors.refund_value_with_actual")] + return false + end + true + end + + private def validate_refund_must_have_comment + if actual_value.zero? && !refund_value.zero? && comment.nil? + @errors["Comment"] = [comment, I18n.t("import.csv.activity_actual_refund_comment.errors.refund_requires_comment")] + return false + end + true + end + + private def validate_receiving_organisation_name + if receiving_organisation_name.blank? && receiving_organisation_type.present? + @errors["Receiving Organisation Name"] = [ + receiving_organisation_name, I18n.t("import.csv.activity_actual_refund_comment.errors.receiving_organisation_name.type") + ] + return false + end + + if receiving_organisation_name.blank? && receiving_organisation_iati_reference.present? + @errors["Receiving Organisation Name"] = [ + receiving_organisation_name, I18n.t("import.csv.activity_actual_refund_comment.errors.receiving_organisation_name.reference") + ] + return false + end + + true + end + + private def validate_receiving_organisation_type + return true if receiving_organisation_name.blank? && receiving_organisation_type.blank? + + if receiving_organisation_name.present? && receiving_organisation_type.blank? + @errors["Receiving Organisation Type"] = [ + receiving_organisation_type, I18n.t("import.csv.activity_actual_refund_comment.errors.receiving_organisation_type.blank_name") + ] + return false + end + + unless value_in_code_list?("organisation_type", receiving_organisation_type) + @errors["Receiving Organisation Type"] = [ + receiving_organisation_type, I18n.t("import.csv.activity_actual_refund_comment.errors.receiving_organisation_type.invalid_code") + ] + return false + end + + true + end + + private def value_in_code_list?(code_list, value) + code_list = Codelist.new(type: code_list) + code_list.find_item_by_code(value) ? true : false + end +end diff --git a/config/locales/import/csv/actiity_actual_refund_comment/errors.en.yml b/config/locales/import/csv/actiity_actual_refund_comment/errors.en.yml new file mode 100644 index 000000000..fe5830bcb --- /dev/null +++ b/config/locales/import/csv/actiity_actual_refund_comment/errors.en.yml @@ -0,0 +1,20 @@ +--- +en: + import: + csv: + activity_actual_refund_comment: + errors: + default: + required: Is required + financial_quarter: Must be 1, 2, 3 or 4 + financial_year: Must be a four digit year + financial_value: Must be a financial value + actual_value_with_refund: Actual and refund cannot be reported on the same row + refund_value_with_actual: Refund and actual cannot be reported on the same row + refund_requires_comment: Refund must have a comment + receiving_organisation_name: + type: Cannot be blank when Receiving Organisation Type is present + reference: Cannot be blank when Receiving Organisation IATI reference is present + receiving_organisation_type: + blank_name: Cannot be blank when Receiving Organisation Name is present + invalid_code: Is not a valid receiving organisation type code diff --git a/spec/models/import/csv/activity_actual_refund_comment/row_spec.rb b/spec/models/import/csv/activity_actual_refund_comment/row_spec.rb new file mode 100644 index 000000000..e2ef45958 --- /dev/null +++ b/spec/models/import/csv/activity_actual_refund_comment/row_spec.rb @@ -0,0 +1,621 @@ +require "rails_helper" + +RSpec.describe Import::Csv::ActivityActualRefundComment::Row do + subject { described_class.new(csv_row) } + + describe "#actual_value" do + context "when the row is valid" do + let(:csv_row) { valid_csv_row(actual: "10000", refund: "0", comment: "") } + + it "returns the converted value" do + expect(subject.actual_value).to eql BigDecimal("10000") + end + end + end + + describe "#refund_value" do + context "when the row is valid" do + let(:csv_row) { valid_csv_row(actual: "0", refund: "20000", comment: "This is a refund.") } + + it "returns the converted value" do + expect(subject.refund_value).to eql BigDecimal("20000") + end + end + end + + describe "#roda_identifier" do + let(:csv_row) { valid_csv_row } + + context "when the value is a string" do + it "returns the string" do + allow(csv_row).to receive(:field).with("Activity RODA Identifier").and_return("VALID-RODA-IDENTIFIER") + + expect(subject.roda_identifier).to eql "VALID-RODA-IDENTIFIER" + end + end + + context "when the value is nil" do + it "returns nil" do + allow(csv_row).to receive(:field).with("Activity RODA Identifier").and_return(nil) + + expect(subject.roda_identifier).to be_nil + end + end + + context "when the value is blank" do + it "returns nil" do + allow(csv_row).to receive(:field).with("Activity RODA Identifier").and_return("") + + expect(subject.roda_identifier).to be_nil + end + end + end + + describe "#financial_quarter" do + let(:csv_row) { valid_csv_row } + + context "when the value is a string" do + it "returns the string" do + allow(csv_row).to receive(:field).with("Financial Quarter").and_return("2") + + expect(subject.financial_quarter).to eql "2" + end + end + + context "when the value is nil" do + it "returns nil" do + allow(csv_row).to receive(:field).with("Financial Quarter").and_return(nil) + + expect(subject.financial_quarter).to be_nil + end + end + + context "when the value is blank" do + it "returns nil" do + allow(csv_row).to receive(:field).with("Financial Quarter").and_return("") + + expect(subject.financial_quarter).to be_nil + end + end + end + + describe "#financial_year" do + let(:csv_row) { valid_csv_row } + + context "when the value is a string" do + it "returns the string" do + allow(csv_row).to receive(:field).with("Financial Year").and_return("2023") + + expect(subject.financial_year).to eql "2023" + end + end + + context "when the value is nil" do + it "returns nil" do + allow(csv_row).to receive(:field).with("Financial Year").and_return(nil) + + expect(subject.financial_year).to be_nil + end + end + + context "when the value is blank" do + it "returns nil" do + allow(csv_row).to receive(:field).with("Financial Year").and_return("") + + expect(subject.financial_year).to be_nil + end + end + end + + describe "#receiving_organisation_name" do + let(:csv_row) { valid_csv_row } + + context "when the value is a string" do + it "returns the string" do + allow(csv_row).to receive(:field).with("Receiving Organisation Name").and_return("Organisation Name") + + expect(subject.receiving_organisation_name).to eql "Organisation Name" + end + end + + context "when the value is nil" do + it "returns nil" do + allow(csv_row).to receive(:field).with("Receiving Organisation Name").and_return(nil) + + expect(subject.receiving_organisation_name).to be_nil + end + end + + context "when the value is blank" do + it "returns nil" do + allow(csv_row).to receive(:field).with("Receiving Organisation Name").and_return(" ") + + expect(subject.receiving_organisation_name).to be_nil + end + end + end + + describe "#receiving_organisation_type" do + let(:csv_row) { valid_csv_row } + + context "when the value is a string" do + it "returns the string" do + allow(csv_row).to receive(:field).with("Receiving Organisation Type").and_return("10") + + expect(subject.receiving_organisation_type).to eql "10" + end + end + + context "when the value is nil" do + it "returns nil" do + allow(csv_row).to receive(:field).with("Receiving Organisation Type").and_return(nil) + + expect(subject.receiving_organisation_type).to be_nil + end + end + + context "when the value is blank" do + it "returns nil" do + allow(csv_row).to receive(:field).with("Receiving Organisation Type").and_return(" ") + + expect(subject.receiving_organisation_type).to be_nil + end + end + end + + describe "#receiving_organisation_iati_reference" do + let(:csv_row) { valid_csv_row } + + context "when there is a string" do + it "returns the value" do + allow(csv_row).to receive(:field).with("Receiving Organisation IATI Reference").and_return("IATI-REF-01") + + expect(subject.receiving_organisation_iati_reference).to eql "IATI-REF-01" + end + end + + context "when the value is nil" do + it "returns nil" do + allow(csv_row).to receive(:field).with("Receiving Organisation IATI Reference").and_return(nil) + + expect(subject.receiving_organisation_iati_reference).to be_nil + end + end + + context "when the value is blank" do + it "returns nil" do + allow(csv_row).to receive(:field).with("Receiving Organisation IATI Reference").and_return(" ") + + expect(subject.receiving_organisation_iati_reference).to be_nil + end + end + end + + describe "#empty?" do + context "when the row is valid" do + context "when there is an actual value" do + let(:csv_row) { valid_csv_row(actual: "30000", refund: "0", comment: "") } + + it "returns false" do + expect(subject.empty?).to be false + end + end + + context "when there is a refund value" do + let(:csv_row) { valid_csv_row(actual: "0", refund: "40000", comment: "This is a refund.") } + + it "returns false" do + expect(subject.empty?).to be false + end + end + + context "when there is a comment" do + let(:csv_row) { valid_csv_row(actual: "0", refund: "0", comment: "This is a comment.") } + + it "returns false" do + expect(subject.empty?).to be false + end + end + + context "when there are no values of interest" do + let(:csv_row) { valid_csv_row(actual: "0", refund: "0", comment: "") } + + it "returns true" do + expect(subject.empty?).to be true + end + end + end + + context "when the row is invalid" do + let(:csv_row) { valid_csv_row(actual: "ten thousand pounds", refund: "0", comment: "") } + + it "returns nil" do + expect(subject.empty?).to be_nil + end + end + end + + describe "#comment" do + context "when there is a comment" do + let(:csv_row) { valid_csv_row(actual: "", refund: "", comment: "This is a comment.") } + + it "returns the comment" do + expect(subject.comment).to eql "This is a comment." + end + end + + context "when the comment is blank" do + let(:csv_row) { valid_csv_row(actual: "", refund: "", comment: nil) } + + it "returns nil" do + expect(subject.comment).to be_nil + end + end + + context "when the comment is empty" do + let(:csv_row) { valid_csv_row(actual: "", refund: "", comment: nil) } + + it "returns nil" do + expect(subject.comment).to be_nil + end + end + + context "when the commnet is a single space" do + let(:csv_row) { valid_csv_row(actual: "", refund: "", comment: " ") } + + it "returns nil" do + expect(subject.comment).to be_nil + end + end + + context "when the comment is a multiple spaces" do + let(:csv_row) { valid_csv_row(actual: "", refund: "", comment: " ") } + + it "returns nil" do + expect(subject.comment).to be_nil + end + end + end + + describe "validations" do + context "when the actual value is zero and the refund value is zero" do + let(:csv_row) { valid_csv_row(actual: "0", refund: "0", comment: "") } + + it "is valid" do + expect(subject).to be_valid + end + end + + context "when the actual value is not a number" do + let(:csv_row) { valid_csv_row(actual: "ten thousand pounds", refund: "0", comment: "") } + + it "is invalid with an error message and the original value" do + expect(subject).to be_invalid + expect(error_for_column("Actual Value").message).to eql "Must be a financial value" + expect(error_for_column("Actual Value").value).to eql "ten thousand pounds" + end + end + + context "when the actual value is a number" do + context "and the refund value is zero" do + context "and there is no comment" do + let(:csv_row) { valid_csv_row(actual: "10000", refund: "0", comment: "") } + + it "is valid" do + expect(subject).to be_valid + end + end + + context "and there is a comment" do + let(:csv_row) { valid_csv_row(actual: "10000", refund: "0", comment: "This is a comment.") } + + it "is valid" do + expect(subject).to be_valid + end + end + end + + context "and the refund value is a positive number" do + let(:csv_row) { valid_csv_row(actual: "30000", refund: "40000", comment: "") } + + it "is invalid with an error message and the original value" do + expect(subject).to be_invalid + expect(error_for_column("Actual Value").message).to include "cannot be reported on the same row" + expect(error_for_column("Actual Value").value).to eql "30000" + expect(error_for_column("Refund Value").message).to include "cannot be reported on the same row" + expect(error_for_column("Refund Value").value).to eql "40000" + end + end + + context "and the refund value is a negative number" do + let(:csv_row) { valid_csv_row(actual: "30000", refund: "-40000", comment: "") } + + it "is invalid with an error message and the original value" do + expect(subject).to be_invalid + expect(error_for_column("Actual Value").message).to include "cannot be reported on the same row" + expect(error_for_column("Actual Value").value).to eql "30000" + expect(error_for_column("Refund Value").message).to include "cannot be reported on the same row" + expect(error_for_column("Refund Value").value).to eql "-40000" + end + end + end + + context "when the refund value is not a number" do + let(:csv_row) { valid_csv_row(actual: "30000", refund: "zero", comment: "") } + + it "is invalid with an error message and the original value" do + expect(subject).to be_invalid + expect(error_for_column("Refund Value").message).to eql "Must be a financial value" + expect(error_for_column("Refund Value").value).to eql "zero" + end + end + + context "when the refund value is a number" do + context "and the actual value is zero" do + context "and there is a comment" do + let(:csv_row) { valid_csv_row(actual: "0", refund: "10000", comment: "This is a refund comment.") } + + it "is valid" do + expect(subject).to be_valid + end + end + + context "and there is no comment" do + let(:csv_row) { valid_csv_row(actual: "0", refund: "10000", comment: "") } + + it "is invalid with an error message and the original value" do + expect(subject).to be_invalid + expect(error_for_column("Comment").message).to eql "Refund must have a comment" + expect(error_for_column("Comment").value).to be_nil + end + end + end + + context "and the actual value is a positive number" do + let(:csv_row) { valid_csv_row(actual: "50000", refund: "10000", comment: "This is a refund comment.") } + + it "is invalid with an error message and the original value" do + expect(subject).to be_invalid + expect(error_for_column("Actual Value").message).to include "cannot be reported on the same row" + expect(error_for_column("Actual Value").value).to eql "50000" + expect(error_for_column("Refund Value").message).to include "cannot be reported on the same row" + expect(error_for_column("Refund Value").value).to eql "10000" + end + end + end + + context "when the actual and refund are zero" do + context "and there is a comment" do + let(:csv_row) { valid_csv_row(actual: "0", refund: "0", comment: "This is a activity comment.") } + + it "is valid" do + expect(subject).to be_valid + end + end + + context "and there is not a comment" do + let(:csv_row) { valid_csv_row(actual: "0", refund: "0", comment: "") } + + it "is valid" do + expect(subject).to be_valid + end + end + end + + describe "financial quarter" do + let(:csv_row) { valid_csv_row } + + context "when the value is nil" do + it "is invalid with an error message and the original value" do + allow(csv_row).to receive(:field).with("Financial Quarter").and_return(nil) + + expect(subject).to be_invalid + expect(error_for_column("Financial Quarter").message).to eql "Is required" + expect(error_for_column("Financial Quarter").value).to eql nil + end + end + + context "when the value is blank" do + it "is invalid with an error message and the original value" do + allow(csv_row).to receive(:field).with("Financial Quarter").and_return("") + + expect(subject).to be_invalid + expect(error_for_column("Financial Quarter").message).to eql "Is required" + expect(error_for_column("Financial Quarter").value).to be_nil + end + end + + context "when the value is not 1, 2, 3 or 4" do + it "is invalid with an error message and the original value" do + allow(csv_row).to receive(:field).with("Financial Quarter").and_return("5") + + expect(subject).to be_invalid + expect(error_for_column("Financial Quarter").message).to eql "Must be 1, 2, 3 or 4" + expect(error_for_column("Financial Quarter").value).to eql "5" + end + end + end + + describe "financial year" do + let(:csv_row) { valid_csv_row } + + context "when the value is nil" do + it "is invalid with an error message and the original value" do + allow(csv_row).to receive(:field).with("Financial Year").and_return(nil) + + expect(subject).to be_invalid + expect(error_for_column("Financial Year").message).to eql "Is required" + expect(error_for_column("Financial Year").value).to eql nil + end + end + + context "when the value is blank" do + it "is invalid with an error message and the original value" do + allow(csv_row).to receive(:field).with("Financial Year").and_return("") + + expect(subject).to be_invalid + expect(error_for_column("Financial Year").message).to eql "Is required" + expect(error_for_column("Financial Year").value).to be_nil + end + end + + context "when the year is not valid" do + it "is invalid with an error message and the original value" do + allow(csv_row).to receive(:field).with("Financial Year").and_return("Twenty twenty three") + + expect(subject).to be_invalid + expect(error_for_column("Financial Year").message).to eql "Must be a four digit year" + expect(error_for_column("Financial Year").value).to eql "Twenty twenty three" + end + end + end + + describe "RODA identifier" do + let(:csv_row) { valid_csv_row } + + context "when the value is nil" do + it "is invalid with an error message and the original value" do + allow(csv_row).to receive(:field).with("Activity RODA Identifier").and_return(nil) + + expect(subject).to be_invalid + expect(error_for_column("Activity RODA Identifier").message).to eql "Is required" + expect(error_for_column("Activity RODA Identifier").value).to be_nil + end + end + + context "when the value is blank" do + it "is invalid with an error message and the original value" do + allow(csv_row).to receive(:field).with("Activity RODA Identifier").and_return("") + + expect(subject).to be_invalid + expect(error_for_column("Activity RODA Identifier").message).to eql "Is required" + expect(error_for_column("Activity RODA Identifier").value).to be_nil + end + end + end + + describe "Receiving Organisation" do + let(:csv_row) { valid_csv_row } + + context "when the name is blank" do + context "and the the type and IATI reference are also blank" do + it "is valid" do + allow(csv_row).to receive(:field).with("Receiving Organisation Name").and_return("") + allow(csv_row).to receive(:field).with("Receiving Organisation Type").and_return("") + allow(csv_row).to receive(:field).with("Receiving Organisation IATI Reference").and_return("") + + expect(subject).to be_valid + end + end + + context "and the type has a value but the IATI reference does not" do + it "is invalid with an error message and the original value" do + allow(csv_row).to receive(:field).with("Receiving Organisation Name").and_return("") + allow(csv_row).to receive(:field).with("Receiving Organisation Type").and_return("10") + allow(csv_row).to receive(:field).with("Receiving Organisation IATI Reference").and_return("") + + expect(subject).to be_invalid + expect(error_for_column("Receiving Organisation Name").message).to include("Cannot be blank when") + expect(error_for_column("Receiving Organisation Name").value).to be_nil + end + end + + context "and the IATI reference has a value but the type does not" do + it "is invalid an error message and the original value" do + allow(csv_row).to receive(:field).with("Receiving Organisation Name").and_return("") + allow(csv_row).to receive(:field).with("Receiving Organisation Type").and_return("") + allow(csv_row).to receive(:field).with("Receiving Organisation IATI Reference").and_return("IATI-REF") + + expect(subject).to be_invalid + expect(error_for_column("Receiving Organisation Name").message).to include("Cannot be blank when") + expect(error_for_column("Receiving Organisation Name").value).to be_nil + end + end + end + + context "when the name has a value" do + context "and the type and IATI reference are blank" do + it "is invalid with an error message and the original value" do + allow(csv_row).to receive(:field).with("Receiving Organisation Name").and_return("Test organisation") + allow(csv_row).to receive(:field).with("Receiving Organisation Type").and_return("") + allow(csv_row).to receive(:field).with("Receiving Organisation IATI Reference").and_return("") + + expect(subject).to be_invalid + expect(error_for_column("Receiving Organisation Type").message).to include("Cannot be blank when") + expect(error_for_column("Receiving Organisation Type").value).to be_nil + end + end + + context "and the type has a valid value but the IATI reference is blank" do + it "is valid" do + allow(csv_row).to receive(:field).with("Receiving Organisation Name").and_return("Test organisation") + allow(csv_row).to receive(:field).with("Receiving Organisation Type").and_return("10") + allow(csv_row).to receive(:field).with("Receiving Organisation IATI Reference").and_return("") + + expect(subject).to be_valid + end + end + + context "and the IATI reference has a value but the type does not" do + it "is invalid with an error message and the original value" do + allow(csv_row).to receive(:field).with("Receiving Organisation Name").and_return("Test organisation") + allow(csv_row).to receive(:field).with("Receiving Organisation Type").and_return("") + allow(csv_row).to receive(:field).with("Receiving Organisation IATI Reference").and_return("IATI-REF") + + expect(subject).to be_invalid + expect(error_for_column("Receiving Organisation Type").message).to include("Cannot be blank when") + expect(error_for_column("Receiving Organisation Type").value).to be_nil + end + end + end + end + + describe "Receiving Organisation Type" do + let(:csv_row) { valid_csv_row } + + context "when the value is on the code list" do + it "is valid" do + allow(csv_row).to receive(:field).with("Receiving Organisation Name").and_return("Test Organisation") + allow(csv_row).to receive(:field).with("Receiving Organisation Type").and_return("10") + + expect(subject).to be_valid + end + end + + context "when the value is not on the code list" do + it "is invalid with an error message and the original value" do + allow(csv_row).to receive(:field).with("Receiving Organisation Name").and_return("Test Organisation") + allow(csv_row).to receive(:field).with("Receiving Organisation Type").and_return("Not a code") + + expect(subject).to be_invalid + expect(error_for_column("Receiving Organisation Type").message).to include "valid receiving organisation type code" + expect(error_for_column("Receiving Organisation Type").value).to include "Not a code" + end + end + end + end + + def valid_csv_row(actual: "10000", refund: "0", comment: "This is a comment") + row = double(CSV::Row) + allow(row).to receive(:field).with("Activity RODA Identifier").and_return("GCRF-UKSA-DJ94DSK0-ID") + allow(row).to receive(:field).with("Financial Quarter").and_return("1") + allow(row).to receive(:field).with("Financial Year").and_return("2023") + allow(row).to receive(:field).with("Actual Value").and_return(actual) + allow(row).to receive(:field).with("Refund Value").and_return(refund) + allow(row).to receive(:field).with("Comment").and_return(comment) + allow(row).to receive(:field).with("Receiving Organisation Name").and_return(nil) + allow(row).to receive(:field).with("Receiving Organisation IATI Reference").and_return(nil) + allow(row).to receive(:field).with("Receiving Organisation Type").and_return(nil) + + row + end + + def error_for_column(column_header) + raise "No error for column #{column_header}" unless subject.errors[column_header] + + message = subject.errors[column_header][1] + value = subject.errors[column_header][0] + + OpenStruct.new(value: value, message: message) + end +end