diff --git a/app/models/validations/sales/financial_validations.rb b/app/models/validations/sales/financial_validations.rb index f66618e16..9e30d49f3 100644 --- a/app/models/validations/sales/financial_validations.rb +++ b/app/models/validations/sales/financial_validations.rb @@ -119,6 +119,18 @@ module Validations::Sales::FinancialValidations end end + def validate_equity_less_than_staircase_difference(record) + return unless record.equity && record.stairbought && record.stairowned + return unless record.saledate && record.form.start_year_after_2024? + + if record.equity > record.stairowned - record.stairbought + formatted_equity = sprintf("%g", record.equity) + record.errors.add :equity, I18n.t("validations.financial.equity.over_stairowned_minus_stairbought", equity: formatted_equity, staircase_difference: record.stairowned - record.stairbought) + record.errors.add :stairowned, I18n.t("validations.financial.equity.over_stairowned_minus_stairbought", equity: formatted_equity, staircase_difference: record.stairowned - record.stairbought) + record.errors.add :stairbought, I18n.t("validations.financial.equity.over_stairowned_minus_stairbought", equity: formatted_equity, staircase_difference: record.stairowned - record.stairbought) + end + end + private def is_relationship_child?(relationship) diff --git a/app/models/validations/sales/household_validations.rb b/app/models/validations/sales/household_validations.rb index c597cb225..6e10b89b5 100644 --- a/app/models/validations/sales/household_validations.rb +++ b/app/models/validations/sales/household_validations.rb @@ -30,6 +30,16 @@ module Validations::Sales::HouseholdValidations end end + def validate_buyer1_previous_tenure(record) + return unless record.saledate && record.form.start_year_after_2024? + return unless record.discounted_ownership_sale? && record.prevten + + if [3, 4, 5, 6, 7, 9, 0].include?(record.prevten) + record.errors.add :prevten, I18n.t("validations.household.prevten.invalid_for_discounted_sale") + record.errors.add :ownershipsch, I18n.t("validations.household.prevten.invalid_for_discounted_sale") + end + end + private def validate_person_age_matches_relationship(record, person_num) diff --git a/config/locales/en.yml b/config/locales/en.yml index e04d9465b..b9638ba94 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -433,6 +433,7 @@ en: equity: under_min: "The minimum initial equity stake for this type of shared ownership sale is %{min_equity}%" over_max: "The maximum initial equity stake is %{max_equity}%" + over_stairowned_minus_stairbought: "The initial equity stake is %{equity}% and the percentage owned in total minus the percentage bought is %{staircase_difference}%. In a staircasing transaction, the equity stake purchased cannot be larger than the percentage the buyer owns minus the percentage bought." mortgage: "Mortgage value cannot be £0 if a mortgage was used for the purchase of this property" shared_ownership_deposit: "The %{mortgage_deposit_and_discount_error_fields} added together is %{mortgage_deposit_and_discount_total}. The value times the equity percentage is %{value_times_equity}. These figures should be the same" @@ -503,6 +504,7 @@ en: internal_transfer: "Answer cannot be %{prevten} as this tenancy is an internal transfer" la_general_needs: internal_transfer: "Answer cannot be a fixed-term or lifetime local authority general needs tenancy as it’s an internal transfer and a private registered provider is on the tenancy agreement" + invalid_for_discounted_sale: "Buyer 1’s previous tenure should be “local authority tenant” or “private registered provider or housing association tenant” for discounted sales" referral: secure_tenancy: "Answer must be internal transfer as this is a secure tenancy" rsnvac_non_temp: "Answer cannot be this source of referral as this is a re-let to tenant who occupied the same property as temporary accommodation" diff --git a/spec/fixtures/files/sales_logs_csv_export_codes.csv b/spec/fixtures/files/sales_logs_csv_export_codes.csv index fac4199a0..2bfcb8fc7 100644 --- a/spec/fixtures/files/sales_logs_csv_export_codes.csv +++ b/spec/fixtures/files/sales_logs_csv_export_codes.csv @@ -1,2 +1,2 @@ -id,status,duplicate_set_id,created_at,updated_at,old_form_id,collection_start_year,creation_method,is_dpo,owning_organisation_name,managing_organisation_name,created_by,day,month,year,purchid,ownershipsch,type,othtype,companybuy,buylivein,jointpur,jointmore,uprn,uprn_confirmed,address_line1,address_line2,town_or_city,county,pcode1,pcode2,la_known,la,la_label,beds,proptype,builtype,pcodenk,wchair,noint,privacynotice,age1,sex1,ethnic_group,ethnic,national,ecstat1,buy1livein,relat2,age2,sex2,ethnic_group2,ethnicbuy2,nationalbuy2,ecstat2,buy2livein,hholdcount,relat3,age3,sex3,ecstat3,relat4,age4,sex4,ecstat4,relat5,age5,sex5,ecstat5,relat6,age6,sex6,ecstat6,prevten,ppcodenk,ppostc1,ppostc2,previous_la_known,prevloc,prevloc_label,pregyrha,pregother,pregla,pregghb,pregblank,buy2living,prevtenbuy2,hhregres,hhregresstill,armedforcesspouse,disabled,wheel,income1nk,income1,inc1mort,income2nk,income2,inc2mort,hb,savingsnk,savings,prevown,prevshared,proplen,staircase,stairbought,stairowned,staircasesale,resale,exday,exmonth,exyear,hoday,homonth,hoyear,lanomagr,soctenant,frombeds,fromprop,socprevten,value,equity,mortgageused,mortgage,mortgagelender,mortgagelenderother,mortlen,extrabor,deposit,cashdis,mrent,has_mscharge,mscharge,discount,grant -,completed,,2023-12-08T00:00:00+00:00,2023-12-08T00:00:00+00:00,,2023,1,false,DLUHC,DLUHC,billyboy@eyeklaud.com,8,12,2023,,2,8,,,,1,1,,,Address line 1,,Town or city,,SW1A,1AA,1,E09000003,Barnet,2,1,1,0,1,2,1,30,X,17,17,18,1,1,P,35,X,17,,13,1,1,3,C,14,X,9,X,-9,X,3,R,-9,R,10,,,,,1,1,,,0,,,1,1,1,1,,3,,1,4,5,1,1,0,10000,1,0,10000,1,4,1,,1,2,10,,,,,,,,,,,,,,,,,110000.0,,1,20000.0,5,,10,1,80000.0,,,1,100.0,,10000.0 +id,status,duplicate_set_id,created_at,updated_at,old_form_id,collection_start_year,creation_method,is_dpo,owning_organisation_name,managing_organisation_name,created_by,day,month,year,purchid,ownershipsch,type,othtype,companybuy,buylivein,jointpur,jointmore,noint,uprn,uprn_confirmed,address_line1,address_line2,town_or_city,county,pcode1,pcode2,la_known,la,la_label,beds,proptype,builtype,pcodenk,wchair,privacynotice,age1,sex1,ethnic_group,ethnic,national,ecstat1,buy1livein,relat2,age2,sex2,ethnic_group2,ethnicbuy2,nationalbuy2,ecstat2,buy2livein,hholdcount,relat3,age3,sex3,ecstat3,relat4,age4,sex4,ecstat4,relat5,age5,sex5,ecstat5,relat6,age6,sex6,ecstat6,prevten,ppcodenk,ppostc1,ppostc2,previous_la_known,prevloc,prevloc_label,pregyrha,pregother,pregla,pregghb,pregblank,buy2living,prevtenbuy2,hhregres,hhregresstill,armedforcesspouse,disabled,wheel,income1nk,income1,inc1mort,income2nk,income2,inc2mort,hb,savingsnk,savings,prevown,prevshared,proplen,staircase,stairbought,stairowned,staircasesale,resale,exday,exmonth,exyear,hoday,homonth,hoyear,lanomagr,soctenant,frombeds,fromprop,socprevten,value,equity,mortgageused,mortgage,mortgagelender,mortgagelenderother,mortlen,extrabor,deposit,cashdis,mrent,has_mscharge,mscharge,discount,grant +,completed,,2023-12-08T00:00:00+00:00,2023-12-08T00:00:00+00:00,,2023,1,false,DLUHC,DLUHC,billyboy@eyeklaud.com,8,12,2023,,2,8,,,,1,1,2,,,Address line 1,,Town or city,,SW1A,1AA,1,E09000003,Barnet,2,1,1,0,1,1,30,X,17,17,18,1,1,P,35,X,17,,13,1,1,3,C,14,X,9,X,-9,X,3,R,-9,R,10,,,,,1,1,,,0,,,1,1,1,1,,3,,1,4,5,1,1,0,10000,1,0,10000,1,4,1,,1,2,10,,,,,,,,,,,,,,,,,110000.0,,1,20000.0,5,,10,1,80000.0,,,1,100.0,,10000.0 diff --git a/spec/fixtures/files/sales_logs_csv_export_labels.csv b/spec/fixtures/files/sales_logs_csv_export_labels.csv index 7c8f3c887..6d8660776 100644 --- a/spec/fixtures/files/sales_logs_csv_export_labels.csv +++ b/spec/fixtures/files/sales_logs_csv_export_labels.csv @@ -1,2 +1,2 @@ -id,status,duplicate_set_id,created_at,updated_at,old_form_id,collection_start_year,creation_method,is_dpo,owning_organisation_name,managing_organisation_name,created_by,day,month,year,purchid,ownershipsch,type,othtype,companybuy,buylivein,jointpur,jointmore,uprn,uprn_confirmed,address_line1,address_line2,town_or_city,county,pcode1,pcode2,la_known,la,la_label,beds,proptype,builtype,pcodenk,wchair,noint,privacynotice,age1,sex1,ethnic_group,ethnic,national,ecstat1,buy1livein,relat2,age2,sex2,ethnic_group2,ethnicbuy2,nationalbuy2,ecstat2,buy2livein,hholdcount,relat3,age3,sex3,ecstat3,relat4,age4,sex4,ecstat4,relat5,age5,sex5,ecstat5,relat6,age6,sex6,ecstat6,prevten,ppcodenk,ppostc1,ppostc2,previous_la_known,prevloc,prevloc_label,pregyrha,pregother,pregla,pregghb,pregblank,buy2living,prevtenbuy2,hhregres,hhregresstill,armedforcesspouse,disabled,wheel,income1nk,income1,inc1mort,income2nk,income2,inc2mort,hb,savingsnk,savings,prevown,prevshared,proplen,staircase,stairbought,stairowned,staircasesale,resale,exday,exmonth,exyear,hoday,homonth,hoyear,lanomagr,soctenant,frombeds,fromprop,socprevten,value,equity,mortgageused,mortgage,mortgagelender,mortgagelenderother,mortlen,extrabor,deposit,cashdis,mrent,has_mscharge,mscharge,discount,grant -,completed,,2023-12-08T00:00:00+00:00,2023-12-08T00:00:00+00:00,,2023,single log,false,DLUHC,DLUHC,billyboy@eyeklaud.com,8,12,2023,,Yes - a discounted ownership scheme,Right to Acquire (RTA),,,,Yes,Yes,,,Address line 1,,Town or city,,SW1A,1AA,1,E09000003,Barnet,2,Flat or maisonette,Purpose built,0,Yes,Yes,1,30,Non-binary,Buyer 1 prefers not to say,17,United Kingdom,Full-time - 30 hours or more,Yes,Partner,35,Non-binary,Buyer 2 prefers not to say,,Buyer prefers not to say,Full-time - 30 hours or more,Yes,3,Child,14,Non-binary,Child under 16,Other,Not known,Non-binary,"In government training into work, such as New Deal",Prefers not to say,Not known,Prefers not to say,Prefers not to say,,,,,Local authority tenant,No,,,No,,,1,1,1,1,,Don't know,,Yes,Yes,No,Yes,Yes,Yes,10000,Yes,Yes,10000,Yes,"Don’t know ",No,,Yes,No,10,,,,,,,,,,,,,,,,,110000.0,,Yes,20000.0,Cambridge Building Society,,10,Yes,80000.0,,,Yes,100.0,,10000.0 +id,status,duplicate_set_id,created_at,updated_at,old_form_id,collection_start_year,creation_method,is_dpo,owning_organisation_name,managing_organisation_name,created_by,day,month,year,purchid,ownershipsch,type,othtype,companybuy,buylivein,jointpur,jointmore,noint,uprn,uprn_confirmed,address_line1,address_line2,town_or_city,county,pcode1,pcode2,la_known,la,la_label,beds,proptype,builtype,pcodenk,wchair,privacynotice,age1,sex1,ethnic_group,ethnic,national,ecstat1,buy1livein,relat2,age2,sex2,ethnic_group2,ethnicbuy2,nationalbuy2,ecstat2,buy2livein,hholdcount,relat3,age3,sex3,ecstat3,relat4,age4,sex4,ecstat4,relat5,age5,sex5,ecstat5,relat6,age6,sex6,ecstat6,prevten,ppcodenk,ppostc1,ppostc2,previous_la_known,prevloc,prevloc_label,pregyrha,pregother,pregla,pregghb,pregblank,buy2living,prevtenbuy2,hhregres,hhregresstill,armedforcesspouse,disabled,wheel,income1nk,income1,inc1mort,income2nk,income2,inc2mort,hb,savingsnk,savings,prevown,prevshared,proplen,staircase,stairbought,stairowned,staircasesale,resale,exday,exmonth,exyear,hoday,homonth,hoyear,lanomagr,soctenant,frombeds,fromprop,socprevten,value,equity,mortgageused,mortgage,mortgagelender,mortgagelenderother,mortlen,extrabor,deposit,cashdis,mrent,has_mscharge,mscharge,discount,grant +,completed,,2023-12-08T00:00:00+00:00,2023-12-08T00:00:00+00:00,,2023,single log,false,DLUHC,DLUHC,billyboy@eyeklaud.com,8,12,2023,,Yes - a discounted ownership scheme,Right to Acquire (RTA),,,,Yes,Yes,Yes,,,Address line 1,,Town or city,,SW1A,1AA,1,E09000003,Barnet,2,Flat or maisonette,Purpose built,0,Yes,1,30,Non-binary,Buyer 1 prefers not to say,17,United Kingdom,Full-time - 30 hours or more,Yes,Partner,35,Non-binary,Buyer 2 prefers not to say,,Buyer prefers not to say,Full-time - 30 hours or more,Yes,3,Child,14,Non-binary,Child under 16,Other,Not known,Non-binary,"In government training into work, such as New Deal",Prefers not to say,Not known,Prefers not to say,Prefers not to say,,,,,Local authority tenant,No,,,No,,,1,1,1,1,,Don't know,,Yes,Yes,No,Yes,Yes,Yes,10000,Yes,Yes,10000,Yes,"Don’t know ",No,,Yes,No,10,,,,,,,,,,,,,,,,,110000.0,,Yes,20000.0,Cambridge Building Society,,10,Yes,80000.0,,,Yes,100.0,,10000.0 diff --git a/spec/models/validations/sales/financial_validations_spec.rb b/spec/models/validations/sales/financial_validations_spec.rb index 00dc9f850..8a57df6b6 100644 --- a/spec/models/validations/sales/financial_validations_spec.rb +++ b/spec/models/validations/sales/financial_validations_spec.rb @@ -561,4 +561,83 @@ RSpec.describe Validations::Sales::FinancialValidations do end end end + + describe "#validate_equity_less_than_staircase_difference" do + let(:record) { FactoryBot.create(:sales_log, saledate: now) } + + around do |example| + Timecop.freeze(now) do + Singleton.__init__(FormHandler) + example.run + end + Timecop.return + Singleton.__init__(FormHandler) + end + + context "with a log in the 23/24 collection year" do + let(:now) { Time.zone.local(2023, 4, 1) } + + it "does not add an error" do + record.stairbought = 2 + record.stairowned = 3 + record.equity = 2 + financial_validator.validate_equity_less_than_staircase_difference(record) + expect(record.errors).to be_empty + end + end + + context "with a log in 24/25 collection year" do + let(:now) { Time.zone.local(2024, 4, 1) } + + it "adds errors if equity is more than stairowned - stairbought" do + record.stairbought = 2 + record.stairowned = 3 + record.equity = 2 + financial_validator.validate_equity_less_than_staircase_difference(record) + expect(record.errors["equity"]).to include("The initial equity stake is 2% and the percentage owned in total minus the percentage bought is 1%. In a staircasing transaction, the equity stake purchased cannot be larger than the percentage the buyer owns minus the percentage bought.") + expect(record.errors["stairowned"]).to include("The initial equity stake is 2% and the percentage owned in total minus the percentage bought is 1%. In a staircasing transaction, the equity stake purchased cannot be larger than the percentage the buyer owns minus the percentage bought.") + expect(record.errors["stairbought"]).to include("The initial equity stake is 2% and the percentage owned in total minus the percentage bought is 1%. In a staircasing transaction, the equity stake purchased cannot be larger than the percentage the buyer owns minus the percentage bought.") + end + + it "does not add errors if equity is less than stairowned - stairbought" do + record.stairbought = 2 + record.stairowned = 10 + record.equity = 2 + financial_validator.validate_equity_less_than_staircase_difference(record) + expect(record.errors).to be_empty + end + + it "does not add errors if equity is equal stairowned - stairbought" do + record.stairbought = 2 + record.stairowned = 10 + record.equity = 8 + financial_validator.validate_equity_less_than_staircase_difference(record) + expect(record.errors).to be_empty + end + + it "does not add errors if stairbought is not given" do + record.stairbought = nil + record.stairowned = 10 + record.equity = 2 + financial_validator.validate_equity_less_than_staircase_difference(record) + expect(record.errors).to be_empty + end + + it "does not add errors if stairowned is not given" do + record.stairbought = 2 + record.stairowned = nil + record.equity = 2 + financial_validator.validate_equity_less_than_staircase_difference(record) + expect(record.errors).to be_empty + end + + it "does not add errors if equity is not given" do + record.stairbought = 2 + record.stairowned = 10 + record.equity = 0 + financial_validator.validate_equity_less_than_staircase_difference(record) + expect(record.errors).to be_empty + end + end + end end diff --git a/spec/models/validations/sales/household_validations_spec.rb b/spec/models/validations/sales/household_validations_spec.rb index 21d205e03..898a47fcc 100644 --- a/spec/models/validations/sales/household_validations_spec.rb +++ b/spec/models/validations/sales/household_validations_spec.rb @@ -268,4 +268,79 @@ RSpec.describe Validations::Sales::HouseholdValidations do end end end + + describe "#validate_buyer1_previous_tenure" do + let(:record) { build(:sales_log) } + + let(:now) { Time.zone.local(2024, 4, 4) } + + before do + Timecop.freeze(now) + Singleton.__init__(FormHandler) + record.ownershipsch = 2 + record.saledate = now + end + + after do + Timecop.return + Singleton.__init__(FormHandler) + end + + it "adds an error when previous tenure is not valid" do + [3, 4, 5, 6, 7, 9, 0].each do |prevten| + record.prevten = prevten + household_validator.validate_buyer1_previous_tenure(record) + expect(record.errors["prevten"]).to include("Buyer 1’s previous tenure should be “local authority tenant” or “private registered provider or housing association tenant” for discounted sales") + expect(record.errors["ownershipsch"]).to include("Buyer 1’s previous tenure should be “local authority tenant” or “private registered provider or housing association tenant” for discounted sales") + end + end + + it "does not add an error when previous tenure is allowed" do + [1, 2].each do |prevten| + record.prevten = prevten + household_validator.validate_buyer1_previous_tenure(record) + expect(record.errors).to be_empty + end + end + + it "does not add an error if previous tenure is not given" do + record.prevten = nil + household_validator.validate_buyer1_previous_tenure(record) + expect(record.errors).to be_empty + end + + it "does not add an error for shared ownership sale" do + record.ownershipsch = 1 + + [1, 2, 3, 4, 5, 6, 7, 9, 0].each do |prevten| + record.prevten = prevten + household_validator.validate_buyer1_previous_tenure(record) + expect(record.errors).to be_empty + end + end + + it "does not add an error for outright sale" do + record.ownershipsch = 3 + + [1, 2, 3, 4, 5, 6, 7, 9, 0].each do |prevten| + record.prevten = prevten + household_validator.validate_buyer1_previous_tenure(record) + expect(record.errors).to be_empty + end + end + + context "with 23/24 logs" do + let(:now) { Time.zone.local(2023, 4, 4) } + + it "does not add an error for outright sale" do + record.ownershipsch = 2 + + [1, 2, 3, 4, 5, 6, 7, 9, 0].each do |prevten| + record.prevten = prevten + household_validator.validate_buyer1_previous_tenure(record) + expect(record.errors).to be_empty + end + end + end + end end diff --git a/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb b/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb index 0f506ca28..c0d8c8245 100644 --- a/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb +++ b/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb @@ -88,7 +88,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do field_85: "5", field_86: "1", field_87: "10", - field_88: "11", + field_88: "40", field_89: "1", field_91: "30", field_92: "3",