39 changed files with 871 additions and 560 deletions
@ -1,16 +0,0 @@ |
|||||||
class Form::Common::Pages::CreatedBy < ::Form::Page |
|
||||||
def initialize(id, hsh, subsection) |
|
||||||
super |
|
||||||
@id = "created_by" |
|
||||||
end |
|
||||||
|
|
||||||
def questions |
|
||||||
@questions ||= [ |
|
||||||
Form::Common::Questions::CreatedById.new(nil, nil, self), |
|
||||||
] |
|
||||||
end |
|
||||||
|
|
||||||
def routed_to?(_log, current_user) |
|
||||||
!!current_user&.support? |
|
||||||
end |
|
||||||
end |
|
||||||
@ -1,46 +0,0 @@ |
|||||||
class Form::Common::Questions::CreatedById < ::Form::Question |
|
||||||
def initialize(id, hsh, page) |
|
||||||
super |
|
||||||
@id = "created_by_id" |
|
||||||
@check_answer_label = "User" |
|
||||||
@header = "Which user are you creating this log for?" |
|
||||||
@type = "select" |
|
||||||
end |
|
||||||
|
|
||||||
def answer_options |
|
||||||
answer_opts = { "" => "Select an option" } |
|
||||||
return answer_opts unless ActiveRecord::Base.connected? |
|
||||||
|
|
||||||
User.select(:id, :name).each_with_object(answer_opts) do |user, hsh| |
|
||||||
hsh[user.id] = user.name |
|
||||||
hsh |
|
||||||
end |
|
||||||
end |
|
||||||
|
|
||||||
def displayed_answer_options(log, _user = nil) |
|
||||||
return answer_options unless log.owning_organisation |
|
||||||
|
|
||||||
user_ids = log.owning_organisation.users.pluck(:id) + [""] |
|
||||||
answer_options.select { |k, _v| user_ids.include?(k) } |
|
||||||
end |
|
||||||
|
|
||||||
def label_from_value(value, _log = nil, _user = nil) |
|
||||||
return unless value |
|
||||||
|
|
||||||
answer_options[value] |
|
||||||
end |
|
||||||
|
|
||||||
def hidden_in_check_answers?(_log, current_user) |
|
||||||
!current_user.support? |
|
||||||
end |
|
||||||
|
|
||||||
def derived? |
|
||||||
true |
|
||||||
end |
|
||||||
|
|
||||||
private |
|
||||||
|
|
||||||
def selected_answer_option_is_derived?(_log) |
|
||||||
false |
|
||||||
end |
|
||||||
end |
|
||||||
@ -0,0 +1,19 @@ |
|||||||
|
class Form::Sales::Pages::CreatedBy < ::Form::Page |
||||||
|
def initialize(id, hsh, subsection) |
||||||
|
super |
||||||
|
@id = "created_by" |
||||||
|
end |
||||||
|
|
||||||
|
def questions |
||||||
|
@questions ||= [ |
||||||
|
Form::Sales::Questions::CreatedById.new(nil, nil, self), |
||||||
|
] |
||||||
|
end |
||||||
|
|
||||||
|
def routed_to?(_log, current_user) |
||||||
|
return true if current_user&.support? |
||||||
|
return true if current_user&.data_coordinator? |
||||||
|
|
||||||
|
false |
||||||
|
end |
||||||
|
end |
||||||
@ -0,0 +1,54 @@ |
|||||||
|
class Form::Sales::Questions::CreatedById < ::Form::Question |
||||||
|
ANSWER_OPTS = { "" => "Select an option" }.freeze |
||||||
|
|
||||||
|
def initialize(id, hsh, page) |
||||||
|
super |
||||||
|
@id = "created_by_id" |
||||||
|
@check_answer_label = "User" |
||||||
|
@header = "Which user are you creating this log for?" |
||||||
|
@type = "select" |
||||||
|
end |
||||||
|
|
||||||
|
def answer_options |
||||||
|
ANSWER_OPTS |
||||||
|
end |
||||||
|
|
||||||
|
def displayed_answer_options(log, current_user = nil) |
||||||
|
return ANSWER_OPTS unless log.owning_organisation |
||||||
|
return ANSWER_OPTS unless current_user |
||||||
|
|
||||||
|
users = current_user.support? ? log.owning_organisation.users : current_user.organisation.users |
||||||
|
|
||||||
|
users.each_with_object(ANSWER_OPTS.dup) do |user, hsh| |
||||||
|
hsh[user.id] = present_user(user) |
||||||
|
hsh |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
def label_from_value(value, _log = nil, _user = nil) |
||||||
|
return unless value |
||||||
|
|
||||||
|
present_user(User.find(value)) |
||||||
|
end |
||||||
|
|
||||||
|
def hidden_in_check_answers?(_log, current_user) |
||||||
|
return false if current_user.support? |
||||||
|
return false if current_user.data_coordinator? |
||||||
|
|
||||||
|
true |
||||||
|
end |
||||||
|
|
||||||
|
def derived? |
||||||
|
true |
||||||
|
end |
||||||
|
|
||||||
|
private |
||||||
|
|
||||||
|
def present_user(user) |
||||||
|
"#{user.name} (#{user.email})" |
||||||
|
end |
||||||
|
|
||||||
|
def selected_answer_option_is_derived?(_log) |
||||||
|
false |
||||||
|
end |
||||||
|
end |
||||||
@ -0,0 +1,45 @@ |
|||||||
|
<% content_for :before_content do %> |
||||||
|
<%= govuk_back_link href: @form.back_path %> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<div class="govuk-grid-row"> |
||||||
|
<div class="govuk-grid-column-two-thirds"> |
||||||
|
<%= form_with model: @form, scope: :form, url: bulk_upload_lettings_log_path(id: "prepare-your-file"), method: :patch do |f| %> |
||||||
|
<%= f.hidden_field :year %> |
||||||
|
|
||||||
|
<span class="govuk-caption-l">Upload lettings logs in bulk (<%= @form.year_combo %>)</span> |
||||||
|
<h1 class="govuk-heading-l">Prepare your file</h1> |
||||||
|
|
||||||
|
<h2 class="govuk-heading-s">Download template</h2> |
||||||
|
|
||||||
|
<ul class="govuk-list govuk-list--bullet"> |
||||||
|
<li> |
||||||
|
If your organisation is new to using bulk upload or you have updated your HMS export, use <%= govuk_link_to "this template (improved question ordering)", @form.template_path %>. |
||||||
|
</li> |
||||||
|
|
||||||
|
<li> |
||||||
|
If your organisation was using bulk upload on the previous CORE website and hasn't changed its HMS to be compatible with the new CORE service, use <%= govuk_link_to "this template", @form.old_template_path %>. |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
|
||||||
|
<h2 class="govuk-heading-s">Create your file</h2> |
||||||
|
|
||||||
|
<ul class="govuk-list govuk-list--bullet"> |
||||||
|
<li>Fill in the template with CORE data from your housing management system according to the <%= govuk_link_to "Lettings #{@form.year_combo} Bulk Upload Specification", @form.specification_path %></li> |
||||||
|
<li><strong>Username field:</strong> To assign a log to someone else, enter the email address they use to log into CORE</li> |
||||||
|
<li>If you have to manually enter large volumes of data into the bulk upload template, we recommend creating logs directly in the service instead. <%= govuk_link_to "Find out more about exporting your data", bulk_upload_lettings_log_path(id: "guidance", form: { year: @form.year }) %></li> |
||||||
|
</ul> |
||||||
|
|
||||||
|
<%= govuk_inset_text(text: "You can upload both general needs and supported housing logs in the same file for 2023/24 data.") %> |
||||||
|
|
||||||
|
<h2 class="govuk-heading-s">Save your file</h2> |
||||||
|
|
||||||
|
<ul class="govuk-list govuk-list--bullet"> |
||||||
|
<li>Save your file as a CSV</li> |
||||||
|
<li>Your file should now be ready to upload</li> |
||||||
|
</ul> |
||||||
|
|
||||||
|
<%= f.govuk_submit class: "govuk-!-margin-top-7" %> |
||||||
|
<% end %> |
||||||
|
</div> |
||||||
|
</div> |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
<% content_for :before_content do %> |
||||||
|
<% title = "Tell us if your organisation is merging" %> |
||||||
|
<% content_for :title, title %> |
||||||
|
<%# TODO: Update this backlink to also work with the create org flow %> |
||||||
|
<%= govuk_back_link href: confirm_telephone_number_merge_request_path(@merge_request) %> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<h2 class="govuk-heading-l">What is the merge date?</h2> |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
class AddPhoneNumberToMergeRequest < ActiveRecord::Migration[7.0] |
||||||
|
change_table :merge_requests, bulk: true do |t| |
||||||
|
t.column :telephone_number_correct, :boolean |
||||||
|
t.column :new_telephone_number, :string |
||||||
|
end |
||||||
|
end |
||||||
@ -1,82 +0,0 @@ |
|||||||
require "rails_helper" |
|
||||||
|
|
||||||
RSpec.describe Form::Common::Questions::CreatedById, type: :model do |
|
||||||
subject(:question) { described_class.new(question_id, question_definition, page) } |
|
||||||
|
|
||||||
let(:question_id) { nil } |
|
||||||
let(:question_definition) { nil } |
|
||||||
let(:page) { instance_double(Form::Page) } |
|
||||||
let(:subsection) { instance_double(Form::Subsection) } |
|
||||||
let(:form) { instance_double(Form) } |
|
||||||
let(:user_1) { FactoryBot.create(:user, name: "first user") } |
|
||||||
let(:user_2) { FactoryBot.create(:user, name: "second user") } |
|
||||||
let!(:expected_answer_options) do |
|
||||||
{ |
|
||||||
"" => "Select an option", |
|
||||||
user_1.id => user_1.name, |
|
||||||
user_2.id => user_2.name, |
|
||||||
} |
|
||||||
end |
|
||||||
|
|
||||||
it "has correct page" do |
|
||||||
expect(question.page).to eq(page) |
|
||||||
end |
|
||||||
|
|
||||||
it "has the correct id" do |
|
||||||
expect(question.id).to eq("created_by_id") |
|
||||||
end |
|
||||||
|
|
||||||
it "has the correct header" do |
|
||||||
expect(question.header).to eq("Which user are you creating this log for?") |
|
||||||
end |
|
||||||
|
|
||||||
it "has the correct check_answer_label" do |
|
||||||
expect(question.check_answer_label).to eq("User") |
|
||||||
end |
|
||||||
|
|
||||||
it "has the correct type" do |
|
||||||
expect(question.type).to eq("select") |
|
||||||
end |
|
||||||
|
|
||||||
it "has the correct hint_text" do |
|
||||||
expect(question.hint_text).to be_nil |
|
||||||
end |
|
||||||
|
|
||||||
it "has the correct answer options" do |
|
||||||
expect(question.answer_options).to eq(expected_answer_options) |
|
||||||
end |
|
||||||
|
|
||||||
it "is marked as derived" do |
|
||||||
expect(question.derived?).to be true |
|
||||||
end |
|
||||||
|
|
||||||
context "when the current user is support" do |
|
||||||
let(:support_user) { FactoryBot.build(:user, :support) } |
|
||||||
|
|
||||||
it "is shown in check answers" do |
|
||||||
expect(question.hidden_in_check_answers?(nil, support_user)).to be false |
|
||||||
end |
|
||||||
end |
|
||||||
|
|
||||||
context "when the current user is not support" do |
|
||||||
let(:user) { FactoryBot.build(:user) } |
|
||||||
|
|
||||||
it "is not shown in check answers" do |
|
||||||
expect(question.hidden_in_check_answers?(nil, user)).to be true |
|
||||||
end |
|
||||||
end |
|
||||||
|
|
||||||
context "when the owning organisation is already set" do |
|
||||||
let(:lettings_log) { FactoryBot.create(:lettings_log, owning_organisation: user_2.organisation) } |
|
||||||
let(:expected_answer_options) do |
|
||||||
{ |
|
||||||
"" => "Select an option", |
|
||||||
user_2.id => user_2.name, |
|
||||||
} |
|
||||||
end |
|
||||||
|
|
||||||
it "only displays users that belong to that organisation" do |
|
||||||
expect(question.displayed_answer_options(lettings_log)).to eq(expected_answer_options) |
|
||||||
end |
|
||||||
end |
|
||||||
end |
|
||||||
@ -0,0 +1,88 @@ |
|||||||
|
require "rails_helper" |
||||||
|
|
||||||
|
RSpec.describe Form::Sales::Questions::CreatedById, type: :model do |
||||||
|
subject(:question) { described_class.new(question_id, question_definition, page) } |
||||||
|
|
||||||
|
let(:question_id) { nil } |
||||||
|
let(:question_definition) { nil } |
||||||
|
let(:page) { instance_double(Form::Page) } |
||||||
|
let(:subsection) { instance_double(Form::Subsection) } |
||||||
|
let(:form) { instance_double(Form) } |
||||||
|
|
||||||
|
it "has correct page" do |
||||||
|
expect(question.page).to eq(page) |
||||||
|
end |
||||||
|
|
||||||
|
it "has the correct id" do |
||||||
|
expect(question.id).to eq("created_by_id") |
||||||
|
end |
||||||
|
|
||||||
|
it "has the correct header" do |
||||||
|
expect(question.header).to eq("Which user are you creating this log for?") |
||||||
|
end |
||||||
|
|
||||||
|
it "has the correct check_answer_label" do |
||||||
|
expect(question.check_answer_label).to eq("User") |
||||||
|
end |
||||||
|
|
||||||
|
it "has the correct type" do |
||||||
|
expect(question.type).to eq("select") |
||||||
|
end |
||||||
|
|
||||||
|
it "has the correct hint_text" do |
||||||
|
expect(question.hint_text).to be_nil |
||||||
|
end |
||||||
|
|
||||||
|
it "is marked as derived" do |
||||||
|
expect(question.derived?).to be true |
||||||
|
end |
||||||
|
|
||||||
|
context "when the current user is support" do |
||||||
|
let(:support_user) { build(:user, :support) } |
||||||
|
|
||||||
|
it "is shown in check answers" do |
||||||
|
expect(question.hidden_in_check_answers?(nil, support_user)).to be false |
||||||
|
end |
||||||
|
|
||||||
|
describe "#displayed_answer_options" do |
||||||
|
let(:owning_org_user) { create(:user) } |
||||||
|
let(:sales_log) { create(:sales_log, owning_organisation: owning_org_user.organisation) } |
||||||
|
let(:expected_answer_options) do |
||||||
|
{ |
||||||
|
"" => "Select an option", |
||||||
|
owning_org_user.id => "#{owning_org_user.name} (#{owning_org_user.email})", |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
it "only displays users that belong to the owning organisation" do |
||||||
|
expect(question.displayed_answer_options(sales_log, support_user)).to eq(expected_answer_options) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
context "when the current user is data_coordinator" do |
||||||
|
let(:data_coordinator) { create(:user, :data_coordinator) } |
||||||
|
|
||||||
|
it "is shown in check answers" do |
||||||
|
expect(question.hidden_in_check_answers?(nil, data_coordinator)).to be false |
||||||
|
end |
||||||
|
|
||||||
|
describe "#displayed_answer_options" do |
||||||
|
let(:owning_org_user) { create(:user) } |
||||||
|
let(:sales_log) { create(:sales_log, owning_organisation: owning_org_user.organisation) } |
||||||
|
let!(:user_in_same_org) { create(:user, organisation: data_coordinator.organisation) } |
||||||
|
|
||||||
|
let(:expected_answer_options) do |
||||||
|
{ |
||||||
|
"" => "Select an option", |
||||||
|
user_in_same_org.id => "#{user_in_same_org.name} (#{user_in_same_org.email})", |
||||||
|
data_coordinator.id => "#{data_coordinator.name} (#{data_coordinator.email})", |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
it "only displays users that belong user's org" do |
||||||
|
expect(question.displayed_answer_options(sales_log, data_coordinator)).to eq(expected_answer_options) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
@ -0,0 +1,187 @@ |
|||||||
|
class BulkUpload::SalesLogToCsv |
||||||
|
attr_reader :log, :line_ending, :col_offset, :overrides |
||||||
|
|
||||||
|
def initialize(log:, line_ending: "\n", col_offset: 1, overrides: {}) |
||||||
|
@log = log |
||||||
|
@line_ending = line_ending |
||||||
|
@col_offset = col_offset |
||||||
|
@overrides = overrides |
||||||
|
end |
||||||
|
|
||||||
|
def row_prefix |
||||||
|
[nil] * col_offset |
||||||
|
end |
||||||
|
|
||||||
|
def to_2022_csv_row |
||||||
|
(row_prefix + to_2022_row).flatten.join(",") + line_ending |
||||||
|
end |
||||||
|
|
||||||
|
def default_2022_field_numbers |
||||||
|
(1..125).to_a |
||||||
|
end |
||||||
|
|
||||||
|
def default_2022_field_numbers_row(seed: nil) |
||||||
|
if seed |
||||||
|
["Bulk upload field number"] + default_2022_field_numbers.shuffle(random: Random.new(seed)) |
||||||
|
else |
||||||
|
["Bulk upload field number"] + default_2022_field_numbers |
||||||
|
end.flatten.join(",") + line_ending |
||||||
|
end |
||||||
|
|
||||||
|
def to_2022_row |
||||||
|
[ |
||||||
|
log.purchid, # 1 |
||||||
|
log.saledate&.day, |
||||||
|
log.saledate&.month, |
||||||
|
log.saledate&.strftime("%y"), |
||||||
|
nil, |
||||||
|
log.noint, |
||||||
|
log.age1, |
||||||
|
log.age2, |
||||||
|
log.age3, |
||||||
|
log.age4, |
||||||
|
log.age5, |
||||||
|
log.age6, |
||||||
|
|
||||||
|
log.sex1, |
||||||
|
log.sex2, |
||||||
|
log.sex3, |
||||||
|
log.sex4, |
||||||
|
log.sex5, |
||||||
|
log.sex6, |
||||||
|
|
||||||
|
log.relat2, |
||||||
|
log.relat3, # 20 |
||||||
|
log.relat4, |
||||||
|
log.relat5, |
||||||
|
log.relat6, |
||||||
|
|
||||||
|
log.ecstat1, |
||||||
|
log.ecstat2, |
||||||
|
log.ecstat3, |
||||||
|
log.ecstat4, |
||||||
|
log.ecstat5, |
||||||
|
log.ecstat6, |
||||||
|
|
||||||
|
log.ethnic, # 30 |
||||||
|
log.national, |
||||||
|
log.income1, |
||||||
|
log.income2, |
||||||
|
log.inc1mort, |
||||||
|
log.inc2mort, |
||||||
|
log.savings, |
||||||
|
log.prevown, |
||||||
|
nil, |
||||||
|
|
||||||
|
log.prevten, |
||||||
|
log.prevloc, # 40 |
||||||
|
((log.ppostcode_full || "").split(" ") || [""]).first, |
||||||
|
((log.ppostcode_full || "").split(" ") || [""]).last, |
||||||
|
log.ppcodenk == 0 ? 1 : nil, |
||||||
|
|
||||||
|
log.pregyrha, |
||||||
|
log.pregla, |
||||||
|
log.pregghb, |
||||||
|
log.pregother, |
||||||
|
|
||||||
|
log.disabled, |
||||||
|
log.wheel, |
||||||
|
log.beds, # 50 |
||||||
|
log.proptype, |
||||||
|
log.builtype, |
||||||
|
log.la, |
||||||
|
((log.postcode_full || "").split(" ") || [""]).first, |
||||||
|
((log.postcode_full || "").split(" ") || [""]).last, |
||||||
|
log.wchair, |
||||||
|
|
||||||
|
log.type, # shared ownership |
||||||
|
log.resale, |
||||||
|
log.hodate&.day, |
||||||
|
log.hodate&.month, # 60 |
||||||
|
log.hodate&.strftime("%y"), |
||||||
|
log.exdate&.day, |
||||||
|
log.exdate&.month, |
||||||
|
log.exdate&.strftime("%y"), |
||||||
|
log.lanomagr, |
||||||
|
|
||||||
|
log.frombeds, |
||||||
|
log.fromprop, |
||||||
|
|
||||||
|
log.value, |
||||||
|
log.equity, |
||||||
|
log.mortgage, # 70 |
||||||
|
log.extrabor, |
||||||
|
log.deposit, |
||||||
|
log.cashdis, |
||||||
|
|
||||||
|
log.mrent, |
||||||
|
log.mscharge, |
||||||
|
|
||||||
|
log.type, # discounted ownership |
||||||
|
log.value, |
||||||
|
log.grant, |
||||||
|
log.discount, |
||||||
|
log.mortgage, # 80 |
||||||
|
log.extrabor, |
||||||
|
log.deposit, |
||||||
|
log.mscharge, |
||||||
|
|
||||||
|
log.type, # outright sale |
||||||
|
log.othtype, |
||||||
|
nil, |
||||||
|
|
||||||
|
log.value, |
||||||
|
log.mortgage, |
||||||
|
log.extrabor, |
||||||
|
log.deposit, # 90 |
||||||
|
log.mscharge, |
||||||
|
|
||||||
|
overrides[:organisation_id] || log.owning_organisation&.old_visible_id, |
||||||
|
log.created_by&.email, |
||||||
|
nil, |
||||||
|
hhregres, |
||||||
|
nil, |
||||||
|
log.armedforcesspouse, |
||||||
|
log.mortgagelender, # shared ownership |
||||||
|
log.mortgagelenderother, |
||||||
|
log.mortgagelender, # discounted ownership 100 |
||||||
|
log.mortgagelenderother, |
||||||
|
log.mortgagelender, # outright ownership |
||||||
|
log.mortgagelenderother, |
||||||
|
|
||||||
|
log.hb, |
||||||
|
log.mortlen, # shared ownership |
||||||
|
log.mortlen, # discounted ownership |
||||||
|
log.mortlen, # outright ownership |
||||||
|
|
||||||
|
log.proplen, # discounted ownership |
||||||
|
log.jointmore, |
||||||
|
log.proplen, # shared ownership 110 |
||||||
|
log.staircase, |
||||||
|
log.privacynotice, |
||||||
|
log.ownershipsch, |
||||||
|
log.companybuy, # outright sale |
||||||
|
log.buylivein, |
||||||
|
log.jointpur, |
||||||
|
log.buy1livein, |
||||||
|
log.buy2livein, |
||||||
|
log.hholdcount, |
||||||
|
log.stairbought, # 120 |
||||||
|
log.stairowned, |
||||||
|
log.socprevten, |
||||||
|
log.mortgageused, # shared ownership |
||||||
|
log.mortgageused, # discounted ownership |
||||||
|
log.mortgageused, # outright ownership |
||||||
|
] |
||||||
|
end |
||||||
|
|
||||||
|
private |
||||||
|
|
||||||
|
def hhregres |
||||||
|
if log.hhregres == 1 |
||||||
|
log.hhregresstill |
||||||
|
else |
||||||
|
log.hhregres |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
Loading…
Reference in new issue