Browse Source

Merge branch 'main' into CLDC-3311-bu-uprn-stress-testing

pull/2317/head
natdeanlewissoftwire 2 years ago committed by GitHub
parent
commit
7ec861cfa6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      app/components/create_log_actions_component.rb
  2. 17
      app/components/data_protection_confirmation_banner_component.rb
  3. 18
      app/components/lettings_log_summary_component.html.erb
  4. 17
      app/components/sales_log_summary_component.html.erb
  5. 12
      app/frontend/styles/_log.scss
  6. 8
      app/models/form/lettings/questions/declaration.rb
  7. 3
      app/models/form/question.rb
  8. 9
      app/models/form/sales/questions/privacy_notice.rb
  9. 9
      app/models/lettings_log.rb
  10. 8
      app/models/organisation.rb
  11. 6
      app/services/bulk_upload/lettings/year2024/row_parser.rb
  12. 8
      config/locales/en.yml
  13. 42
      spec/components/create_log_actions_component_spec.rb
  14. 82
      spec/components/data_protection_confirmation_banner_component_spec.rb
  15. 22
      spec/features/form/validations_spec.rb
  16. 10
      spec/models/form/lettings/questions/declaration_spec.rb
  17. 76
      spec/models/form/sales/questions/privacy_notice_spec.rb
  18. 40
      spec/models/lettings_log_spec.rb
  19. 16
      spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb

3
app/components/create_log_actions_component.rb

@ -14,9 +14,8 @@ class CreateLogActionsComponent < ViewComponent::Base
def display_actions?
return false if bulk_upload.present?
return true if user.support?
return false if !user.organisation.holds_own_stock? && user.organisation.stock_owners.empty? && user.organisation.absorbed_organisations.empty?
user.organisation.data_protection_confirmed?
user.organisation.data_protection_confirmed? && user.organisation.organisation_or_stock_owner_signed_dsa_and_holds_own_stock?
end
def create_button_href

17
app/components/data_protection_confirmation_banner_component.rb

@ -13,13 +13,16 @@ class DataProtectionConfirmationBannerComponent < ViewComponent::Base
def display_banner?
return false if user.support? && organisation.blank?
return true if org_without_dpo?
return false if !org_or_user_org.holds_own_stock? && org_or_user_org.stock_owners.empty? && org_or_user_org.absorbed_organisations.empty?
!org_or_user_org.data_protection_confirmed?
!org_or_user_org.data_protection_confirmed? || !org_or_user_org.organisation_or_stock_owner_signed_dsa_and_holds_own_stock?
end
def header_text
if org_without_dpo?
"To create logs your organisation must state a data protection officer. They must sign the Data Sharing Agreement."
elsif !org_or_user_org.holds_own_stock? && org_or_user_org.data_protection_confirmed?
"Your organisation does not own stock. To create logs your stock owner(s) must accept the Data Sharing Agreement on CORE."
elsif user.is_dpo?
"Your organisation must accept the Data Sharing Agreement before you can create any logs."
else
@ -28,7 +31,7 @@ class DataProtectionConfirmationBannerComponent < ViewComponent::Base
end
def banner_text
if org_without_dpo? || user.is_dpo?
if org_without_dpo? || user.is_dpo? || !org_or_user_org.holds_own_stock?
govuk_link_to(
link_text,
link_href,
@ -50,13 +53,21 @@ private
def link_text
if dpo_required?
"Contact helpdesk to assign a data protection officer"
elsif !org_or_user_org.holds_own_stock? && org_or_user_org.data_protection_confirmed?
"View or add stock owners"
else
"Read the Data Sharing Agreement"
end
end
def link_href
dpo_required? ? GlobalConstants::HELPDESK_URL : data_sharing_agreement_organisation_path(org_or_user_org)
if dpo_required?
GlobalConstants::HELPDESK_URL
elsif !org_or_user_org.holds_own_stock? && org_or_user_org.data_protection_confirmed?
stock_owners_organisation_path(org_or_user_org)
else
data_sharing_agreement_organisation_path(org_or_user_org)
end
end
def dpo_required?

18
app/components/lettings_log_summary_component.html.erb

@ -1,6 +1,6 @@
<article class="app-log-summary">
<div class="govuk-grid-row">
<div class="govuk-grid-column-one-half">
<div class="govuk-grid-column-two-thirds">
<header class="app-log-summary__header">
<h2 class="app-log-summary__title">
<%= govuk_link_to lettings_log_path(log) do %>
@ -8,20 +8,14 @@
<% end %>
</h2>
<% if log.tenancycode? or log.propcode? %>
<dl class="app-metadata app-metadata--inline">
<div class="app-metadata">
<% if log.tenancycode? %>
<div class="app-metadata__item">
<dt class="app-metadata__term">Tenant</dt>
<dd class="app-metadata__definition"><%= log.tenancycode %></dd>
</div>
<div class="app-log-summary__details">Tenant <%= log.tenancycode %></div>
<% end %>
<% if log.propcode? %>
<div class="app-metadata__item">
<dt class="app-metadata__term">Property</dt>
<dd class="app-metadata__definition"><%= log.propcode %></dd>
</div>
<div class="app-log-summary__details">Property <%= log.propcode %></div>
<% end %>
</dl>
</div>
<% end %>
</header>
<% if log.needstype? or log.startdate? %>
@ -51,7 +45,7 @@
</dl>
<% end %>
</div>
<footer class="govuk-grid-column-one-half app-log-summary__footer">
<footer class="govuk-grid-column-one-third app-log-summary__footer">
<p class="govuk-body govuk-!-margin-bottom-2">
<%= log_status %>
</p>

17
app/components/sales_log_summary_component.html.erb

@ -1,20 +1,17 @@
<article class="app-log-summary">
<div class="govuk-grid-row">
<div class="govuk-grid-column-one-half">
<div class="govuk-grid-column-two-thirds">
<header class="app-log-summary__header">
<h2 class="app-log-summary__title">
<%= govuk_link_to sales_log_path(log) do %>
Log <%= log.id %>
<% end %>
</h2>
<dl class="app-metadata app-metadata--inline">
<% if log.purchaser_code %>
<div class="app-metadata__item">
<dt class="app-metadata__term">Purchaser</dt>
<dd class="app-metadata__definition"><%= log.purchaser_code %></dd>
</div>
<% end %>
</dl>
<% if log.purchaser_code %>
<div class="app-metadata">
<div class="app-log-summary__details">Purchaser <%= log.purchaser_code %></div>
</div>
<% end %>
</header>
<p class="govuk-body govuk-!-margin-bottom-2">
<% if log.ownership_scheme %>
@ -41,7 +38,7 @@
</dl>
<% end %>
</div>
<footer class="govuk-grid-column-one-half app-log-summary__footer">
<footer class="govuk-grid-column-one-third app-log-summary__footer">
<p class="govuk-body govuk-!-margin-bottom-2">
<%= log_status %>
</p>

12
app/frontend/styles/_log.scss

@ -4,12 +4,14 @@
}
.app-log-summary__header {
align-items: baseline;
display: flex;
margin-bottom: govuk-spacing(2);
align-items: center;
}
.app-log-summary__title {
margin: 0 govuk-spacing(3) govuk-spacing(2) 0;
margin: auto govuk-spacing(3) auto 0;
white-space: nowrap;
}
@include govuk-media-query(tablet) {
@ -20,4 +22,10 @@
.app-log-summary__footer--actor {
display: block;
}
.app-log-summary__details {
word-break: break-all;
margin-top: auto;
margin-bottom: auto;
}
}

8
app/models/form/lettings/questions/declaration.rb

@ -20,5 +20,13 @@ class Form::Lettings::Questions::Declaration < ::Form::Question
{ "declaration" => { "value" => declaration_text } }.freeze
end
def unanswered_error_message
if form.start_year_after_2024?
I18n.t("validations.declaration.missing.post_2024")
else
I18n.t("validations.declaration.missing.pre_2024")
end
end
QUESTION_NUMBER_FROM_YEAR = { 2023 => 30, 2024 => 11 }.freeze
end

3
app/models/form/question.rb

@ -198,9 +198,6 @@ class Form::Question
end
def unanswered_error_message
return I18n.t("validations.declaration.missing") if id == "declaration"
return I18n.t("validations.privacynotice.missing") if id == "privacynotice"
I18n.t("validations.not_answered", question: error_display_label.downcase)
end

9
app/models/form/sales/questions/privacy_notice.rb

@ -20,6 +20,15 @@ class Form::Sales::Questions::PrivacyNotice < ::Form::Question
{ "privacynotice" => { "value" => declaration_text } }.freeze
end
def unanswered_error_message
buyer_or_buyers = @joint_purchase ? "buyers" : "buyer"
if form.start_year_after_2024?
I18n.t("validations.privacynotice.missing.post_2024", buyer_or_buyers:)
else
I18n.t("validations.privacynotice.missing.pre_2024", buyer_or_buyers:)
end
end
def guidance
if form.start_year_after_2024?
@joint_purchase ? "privacy_notice_buyer_2024_joint_purchase" : "privacy_notice_buyer_2024"

9
app/models/lettings_log.rb

@ -581,7 +581,14 @@ class LettingsLog < Log
end
def non_location_setup_questions_completed?
[needstype, renewal, rent_type, startdate, owning_organisation_id, created_by_id].all?(&:present?)
form.setup_sections.all? do |section|
section.subsections.all? do |subsection|
relevant_qs = subsection.applicable_questions(self).reject { |q| optional_fields.include?(q.id) || %w[scheme_id location].include?(q.id) }
relevant_qs.all? do |question|
question.completed?(self)
end
end
end
end
def resolve!

8
app/models/organisation.rb

@ -159,4 +159,12 @@ class Organisation < ApplicationRecord
def has_recent_absorbed_organisations?
absorbed_organisations&.merged_during_open_collection_period.present?
end
def organisation_or_stock_owner_signed_dsa_and_holds_own_stock?
return true if data_protection_confirmed? && holds_own_stock?
return true if stock_owners.any? { |stock_owner| stock_owner.data_protection_confirmed? && stock_owner.holds_own_stock? }
return true if absorbed_organisations.any? { |stock_owner| stock_owner.data_protection_confirmed? && stock_owner.holds_own_stock? }
false
end
end

6
app/services/bulk_upload/lettings/year2024/row_parser.rb

@ -1192,7 +1192,7 @@ private
attributes["layear"] = field_96
attributes["waityear"] = field_97
attributes["reason"] = field_98
attributes["reasonother"] = field_99
attributes["reasonother"] = field_99 if reason_is_other?
attributes["prevten"] = field_100
attributes["homeless"] = field_101
@ -1570,4 +1570,8 @@ private
def is_carehome
field_124.present? ? 1 : 0
end
def reason_is_other?
field_98 == 20
end
end

8
config/locales/en.yml

@ -567,10 +567,14 @@ en:
joint_more_than_one_member: "There must be more than one person in the household as you've told us this is a joint tenancy"
declaration:
missing: "You must show the DLUHC privacy notice to the tenant before you can submit this log."
missing:
pre_2024: "You must show the DLUHC privacy notice to the tenant before you can submit this log."
post_2024: "You must show or give access to the DLUHC privacy notice to the tenant before you can submit this log."
privacynotice:
missing: "You must show the DLUHC privacy notice to the buyer before you can submit this log."
missing:
pre_2024: "You must show the DLUHC privacy notice to the %{buyer_or_buyers} before you can submit this log."
post_2024: "You must show or give access to the DLUHC privacy notice to the %{buyer_or_buyers} before you can submit this log."
scheme:
toggle_date:

42
spec/components/create_log_actions_component_spec.rb

@ -74,7 +74,7 @@ RSpec.describe CreateLogActionsComponent, type: :component do
end
context "when has data sharing agremeent" do
let(:user) { create(:user, :support) }
let(:user) { create(:user) }
it "renders actions" do
expect(component.display_actions?).to eq(true)
@ -114,6 +114,46 @@ RSpec.describe CreateLogActionsComponent, type: :component do
expect(component.create_button_href).to eq("/sales-logs")
end
end
context "when organisation doesn't own stock" do
before do
user.organisation.update!(holds_own_stock: false)
end
context "and has signed DSA and stock owners have signed DSA" do
before do
parent_organisation = create(:organisation)
create(:organisation_relationship, child_organisation: user.organisation, parent_organisation:)
end
it "renders actions" do
expect(component.display_actions?).to eq(true)
end
end
context "and hasn't signed DSA and and stock owners have signed DSA" do
before do
user.organisation.data_protection_confirmation.update!(confirmed: false)
parent_organisation = create(:organisation)
create(:organisation_relationship, child_organisation: user.organisation, parent_organisation:)
end
it "renders actions" do
expect(component.display_actions?).to eq(false)
end
end
context "and no stock owners have signed data sharing agreement" do
before do
parent_organisation = create(:organisation, :without_dpc)
create(:organisation_relationship, child_organisation: user.organisation, parent_organisation:)
end
it "does not render actions" do
expect(component.display_actions?).to eq(false)
end
end
end
end
end
end

82
spec/components/data_protection_confirmation_banner_component_spec.rb

@ -61,6 +61,24 @@ RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do
expect(render).to have_selector("p", text: "Your organisation must accept the Data Sharing Agreement before you can create any logs.")
end
end
context "and org doesn't own stock and has a parent organisation that hasn't signed DSA" do
before do
organisation.data_protection_confirmation.update!(confirmed: false)
organisation.update!(holds_own_stock: false)
parent_organisation = create(:organisation, :without_dpc, holds_own_stock: true)
create(:organisation_relationship, child_organisation: organisation, parent_organisation:)
end
it "displays the banner and asks to sign" do
expect(component.display_banner?).to eq(true)
expect(render).to have_link(
"Read the Data Sharing Agreement",
href: "/organisations/#{organisation.id}/data-sharing-agreement",
)
expect(render).to have_selector("p", text: "Your data protection officer must accept the Data Sharing Agreement on CORE before you can create any logs.")
end
end
end
context "when org has a signed data sharing agremeent" do
@ -68,6 +86,40 @@ RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do
expect(component.display_banner?).to eq(false)
expect(render.content).to be_empty
end
context "and doesn't own stock" do
before do
organisation.update!(holds_own_stock: false)
end
context "and has a parent organisation that owns stock and has signed DSA" do
before do
parent_organisation = create(:organisation, holds_own_stock: true)
create(:organisation_relationship, child_organisation: organisation, parent_organisation:)
end
it "does not display banner" do
expect(component.display_banner?).to eq(false)
expect(render.content).to be_empty
end
end
context "and has a parent organisation that hasn't signed DSA" do
before do
parent_organisation = create(:organisation, :without_dpc, holds_own_stock: true)
create(:organisation_relationship, child_organisation: organisation, parent_organisation:)
end
it "displays the banner and asks to create stock owners" do
expect(component.display_banner?).to eq(true)
expect(render).to have_link(
"View or add stock owners",
href: "/organisations/#{organisation.id}/stock-owners",
)
expect(render).to have_selector("p", text: "Your organisation does not own stock. To create logs your stock owner(s) must accept the Data Sharing Agreement on CORE.")
end
end
end
end
context "when org does not have a DPO" do
@ -98,6 +150,20 @@ RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do
expect(render).to have_selector("p", text: "Your data protection officer must accept the Data Sharing Agreement on CORE before you can create any logs.")
expect(render).to have_selector("p", text: "You can ask: #{dpo.name}")
end
context "and has a parent organisation that owns stock and has signed DSA" do
before do
parent_organisation = create(:organisation, holds_own_stock: true)
create(:organisation_relationship, child_organisation: organisation, parent_organisation:)
end
it "displays the banner and shows DPOs" do
expect(component.display_banner?).to eq(true)
expect(render.css("a")).to be_empty
expect(render).to have_selector("p", text: "Your data protection officer must accept the Data Sharing Agreement on CORE before you can create any logs.")
expect(render).to have_selector("p", text: "You can ask: #{dpo.name}")
end
end
end
context "when user is a DPO" do
@ -112,6 +178,22 @@ RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do
)
expect(render).to have_selector("p", text: "Your organisation must accept the Data Sharing Agreement before you can create any logs.")
end
context "and has a parent organisation that owns stock and has signed DSA" do
before do
parent_organisation = create(:organisation, holds_own_stock: true)
create(:organisation_relationship, child_organisation: organisation, parent_organisation:)
end
it "displays the banner and asks to sign" do
expect(component.display_banner?).to eq(true)
expect(render).to have_link(
"Read the Data Sharing Agreement",
href: "/organisations/#{organisation.id}/data-sharing-agreement",
)
expect(render).to have_selector("p", text: "Your organisation must accept the Data Sharing Agreement before you can create any logs.")
end
end
end
end

22
spec/features/form/validations_spec.rb

@ -27,18 +27,6 @@ RSpec.describe "validations" do
created_by: user,
)
end
let(:completed_without_declaration) do
FactoryBot.create(
:lettings_log,
:completed,
created_by: user,
status: 1,
declaration: nil,
startdate: Time.zone.local(2021, 5, 1),
voiddate: Time.zone.local(2021, 5, 1),
mrcdate: Time.zone.local(2021, 5, 1),
)
end
let(:id) { lettings_log.id }
before do
@ -199,14 +187,4 @@ RSpec.describe "validations" do
end
end
end
describe "Submission validation" do
context "when tenant has not seen the privacy notice" do
it "shows a warning" do
visit("/lettings-logs/#{completed_without_declaration.id}/declaration")
click_button("Save and continue")
expect(page).to have_content("You must show the DLUHC privacy notice to the tenant")
end
end
end
end

10
spec/models/form/lettings/questions/declaration_spec.rb

@ -61,6 +61,10 @@ RSpec.describe Form::Lettings::Questions::Declaration, type: :model do
it "has check_answers_card_number = 0" do
expect(question.check_answers_card_number).to eq(0)
end
it "returns correct unanswered_error_message" do
expect(question.unanswered_error_message).to eq("You must show the DLUHC privacy notice to the tenant before you can submit this log.")
end
end
context "when the form year is >= 2024" do
@ -81,9 +85,9 @@ RSpec.describe Form::Lettings::Questions::Declaration, type: :model do
it "has check_answers_card_number nil" do
expect(question.check_answers_card_number).to be_nil
end
end
it "returns correct unanswered_error_message" do
expect(question.unanswered_error_message).to eq("You must show the DLUHC privacy notice to the tenant before you can submit this log.")
it "returns correct unanswered_error_message" do
expect(question.unanswered_error_message).to eq("You must show or give access to the DLUHC privacy notice to the tenant before you can submit this log.")
end
end
end

76
spec/models/form/sales/questions/privacy_notice_spec.rb

@ -48,14 +48,38 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do
allow(form).to receive(:start_year_after_2024?).and_return(false)
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"privacynotice" => { "value" => "The buyer has seen the DLUHC privacy notice" },
})
context "and there is a single buyer" do
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"privacynotice" => { "value" => "The buyer has seen the DLUHC privacy notice" },
})
end
it "uses the expected top guidance partial" do
expect(question.top_guidance_partial).to eq("privacy_notice_buyer")
end
it "returns correct unanswered_error_message" do
expect(question.unanswered_error_message).to eq("You must show the DLUHC privacy notice to the buyer before you can submit this log.")
end
end
it "uses the expected top guidance partial" do
expect(question.top_guidance_partial).to eq("privacy_notice_buyer")
context "and there are joint buyers" do
subject(:question) { described_class.new(question_id, question_definition, page, joint_purchase: true) }
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"privacynotice" => { "value" => "The buyers have seen the DLUHC privacy notice" },
})
end
it "uses the expected top guidance partial" do
expect(question.top_guidance_partial).to eq("privacy_notice_buyer_joint_purchase")
end
it "returns correct unanswered_error_message" do
expect(question.unanswered_error_message).to eq("You must show the DLUHC privacy notice to the buyers before you can submit this log.")
end
end
end
@ -64,18 +88,38 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do
allow(form).to receive(:start_year_after_2024?).and_return(true)
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"privacynotice" => { "value" => "The buyer has seen or been given access to the DLUHC privacy notice" },
})
end
context "and there is a single buyer" do
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"privacynotice" => { "value" => "The buyer has seen or been given access to the DLUHC privacy notice" },
})
end
it "uses the expected top guidance partial" do
expect(question.top_guidance_partial).to eq("privacy_notice_buyer_2024")
end
it "uses the expected top guidance partial" do
expect(question.top_guidance_partial).to eq("privacy_notice_buyer_2024")
it "returns correct unanswered_error_message" do
expect(question.unanswered_error_message).to eq("You must show or give access to the DLUHC privacy notice to the buyer before you can submit this log.")
end
end
end
it "returns correct unanswered_error_message" do
expect(question.unanswered_error_message).to eq("You must show the DLUHC privacy notice to the buyer before you can submit this log.")
context "and there are joint buyers" do
subject(:question) { described_class.new(question_id, question_definition, page, joint_purchase: true) }
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"privacynotice" => { "value" => "The buyers have seen or been given access to the DLUHC privacy notice" },
})
end
it "uses the expected top guidance partial" do
expect(question.top_guidance_partial).to eq("privacy_notice_buyer_2024_joint_purchase")
end
it "returns correct unanswered_error_message" do
expect(question.unanswered_error_message).to eq("You must show or give access to the DLUHC privacy notice to the buyers before you can submit this log.")
end
end
end
end

40
spec/models/lettings_log_spec.rb

@ -3507,5 +3507,45 @@ RSpec.describe LettingsLog do
end
end
end
describe "#non_location_setup_questions_completed" do
before do
Timecop.return
allow(FormHandler.instance).to receive(:current_lettings_form).and_call_original
Singleton.__init__(FormHandler)
end
context "when setup section has been completed" do
let(:lettings_log) { build(:lettings_log, :setup_completed) }
it "returns true" do
expect(lettings_log).to be_non_location_setup_questions_completed
end
end
context "when the declaration has not been completed for a 2024 log" do
let(:lettings_log) { build(:lettings_log, :setup_completed, startdate: Time.utc(2024, 10, 1), declaration: nil) }
it "returns false" do
expect(lettings_log).not_to be_non_location_setup_questions_completed
end
end
context "when an optional question has not been completed" do
let(:lettings_log) { build(:lettings_log, :setup_completed, propcode: nil) }
it "returns true" do
expect(lettings_log).to be_non_location_setup_questions_completed
end
end
context "when scheme and location have not been completed" do
let(:lettings_log) { build(:lettings_log, :setup_completed, :sh, scheme_id: nil, location: nil) }
it "returns true" do
expect(lettings_log).to be_non_location_setup_questions_completed
end
end
end
end
# rubocop:enable RSpec/MessageChain

16
spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb

@ -2223,10 +2223,20 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
end
describe "#reasonother" do
let(:attributes) { { bulk_upload:, field_99: "some other reason" } }
context "when reason is 'other'" do
let(:attributes) { { bulk_upload:, field_98: "20", field_99: "some other reason" } }
it "sets value to given free text string" do
expect(parser.log.reasonother).to eql("some other reason")
it "is set to given free text string" do
expect(parser.log.reasonother).to eql("some other reason")
end
end
context "when reason is not 'other'" do
let(:attributes) { { bulk_upload:, field_98: "50", field_99: "some other reason" } }
it "is set to nil" do
expect(parser.log.reasonother).to be_nil
end
end
end

Loading…
Cancel
Save