diff --git a/app/controllers/collection_resources_controller.rb b/app/controllers/collection_resources_controller.rb
index ed080c749..06988c88c 100644
--- a/app/controllers/collection_resources_controller.rb
+++ b/app/controllers/collection_resources_controller.rb
@@ -1,13 +1,15 @@
class CollectionResourcesController < ApplicationController
include CollectionResourcesHelper
- before_action :authenticate_user!, except: %i[download_mandatory_collection_resource]
+ before_action :authenticate_user!, except: %i[download_mandatory_collection_resource download_additional_collection_resource]
def index
render_not_found unless current_user.support?
@mandatory_lettings_collection_resources_per_year = MandatoryCollectionResourcesService.generate_resources("lettings", editable_collection_resource_years)
@mandatory_sales_collection_resources_per_year = MandatoryCollectionResourcesService.generate_resources("sales", editable_collection_resource_years)
+ @additional_lettings_collection_resources_per_year = CollectionResource.visible.where(log_type: "lettings", mandatory: false).group_by(&:year)
+ @additional_sales_collection_resources_per_year = CollectionResource.visible.where(log_type: "sales", mandatory: false).group_by(&:year)
end
def download_mandatory_collection_resource
@@ -23,8 +25,17 @@ class CollectionResourcesController < ApplicationController
download_resource(resource.download_filename)
end
- def edit
- return render_not_found unless current_user.support?
+ def download_additional_collection_resource
+ resource = CollectionResource.find_by(id: params[:collection_resource_id])
+
+ return render_not_found unless resource
+ return render_not_found unless resource_for_year_can_be_downloaded?(resource.year)
+
+ download_resource(resource.download_filename)
+ end
+
+ def edit_mandatory_collection_resource
+ return render_not_authorized unless current_user.support?
year = params[:year].to_i
resource_type = params[:resource_type]
@@ -39,8 +50,19 @@ class CollectionResourcesController < ApplicationController
render "collection_resources/edit"
end
- def update
- return render_not_found unless current_user.support?
+ def edit_additional_collection_resource
+ return render_not_authorized unless current_user.support?
+
+ @collection_resource = CollectionResource.find_by(id: params[:collection_resource_id])
+
+ return render_not_found unless @collection_resource
+ return render_not_found unless resource_for_year_can_be_updated?(@collection_resource.year)
+
+ render "collection_resources/edit"
+ end
+
+ def update_mandatory_collection_resource
+ return render_not_authorized unless current_user.support?
year = resource_params[:year].to_i
resource_type = resource_params[:resource_type]
@@ -52,7 +74,8 @@ class CollectionResourcesController < ApplicationController
@collection_resource = MandatoryCollectionResourcesService.generate_resource(log_type, year, resource_type)
render_not_found unless @collection_resource
- validate_file(file)
+ @collection_resource.file = file
+ @collection_resource.validate_attached_file
return render "collection_resources/edit" if @collection_resource.errors.any?
@@ -68,8 +91,38 @@ class CollectionResourcesController < ApplicationController
redirect_to collection_resources_path
end
+ def update_additional_collection_resource
+ return render_not_authorized unless current_user.support?
+
+ @collection_resource = CollectionResource.find_by(id: params[:collection_resource_id])
+
+ return render_not_found unless @collection_resource
+ return render_not_found unless resource_for_year_can_be_updated?(@collection_resource.year)
+
+ @collection_resource.file = resource_params[:file]
+ @collection_resource.validate_attached_file
+ @collection_resource.validate_short_display_name
+ return render "collection_resources/edit" if @collection_resource.errors.any?
+
+ @collection_resource.short_display_name = resource_params[:short_display_name]
+ @collection_resource.download_filename = @collection_resource.file&.original_filename
+ @collection_resource.display_name = "#{@collection_resource.log_type} #{@collection_resource.short_display_name} (#{text_year_range_format(@collection_resource.year)})"
+ if @collection_resource.save
+ begin
+ CollectionResourcesService.new.upload_collection_resource(@collection_resource.download_filename, @collection_resource.file)
+ flash[:notice] = "The #{@collection_resource.log_type} #{text_year_range_format(@collection_resource.year)} #{@collection_resource.short_display_name.downcase} has been updated."
+ redirect_to collection_resources_path
+ rescue StandardError
+ @collection_resource.errors.add(:file, :error_uploading)
+ render "collection_resources/edit"
+ end
+ else
+ render "collection_resources/edit"
+ end
+ end
+
def confirm_mandatory_collection_resources_release
- return render_not_found unless current_user.support?
+ return render_not_authorized unless current_user.support?
@year = params[:year].to_i
@@ -79,7 +132,7 @@ class CollectionResourcesController < ApplicationController
end
def release_mandatory_collection_resources
- return render_not_found unless current_user.support?
+ return render_not_authorized unless current_user.support?
year = params[:year].to_i
@@ -91,10 +144,73 @@ class CollectionResourcesController < ApplicationController
redirect_to collection_resources_path
end
+ def new
+ return render_not_authorized unless current_user.support?
+
+ year = params[:year].to_i
+ log_type = params[:log_type]
+
+ return render_not_found unless editable_collection_resource_years.include?(year)
+
+ @collection_resource = CollectionResource.new(year:, log_type:)
+ end
+
+ def create
+ return render_not_authorized unless current_user.support? && editable_collection_resource_years.include?(resource_params[:year].to_i)
+
+ @collection_resource = CollectionResource.new(resource_params)
+ @collection_resource.download_filename ||= @collection_resource.file&.original_filename
+ @collection_resource.display_name = "#{@collection_resource.log_type} #{@collection_resource.short_display_name} (#{text_year_range_format(@collection_resource.year)})"
+
+ @collection_resource.validate_attached_file
+ @collection_resource.validate_short_display_name
+ return render "collection_resources/new" if @collection_resource.errors.any?
+
+ if @collection_resource.save
+ begin
+ CollectionResourcesService.new.upload_collection_resource(@collection_resource.download_filename, @collection_resource.file)
+ flash[:notice] = if displayed_collection_resource_years.include?(@collection_resource.year)
+ "The #{@collection_resource.log_type} #{text_year_range_format(@collection_resource.year)} #{@collection_resource.short_display_name} is now available to users."
+ else
+ "The #{@collection_resource.log_type} #{text_year_range_format(@collection_resource.year)} #{@collection_resource.short_display_name} has been uploaded."
+ end
+ redirect_to collection_resources_path
+ rescue StandardError
+ @collection_resource.errors.add(:file, :error_uploading)
+ render "collection_resources/new"
+ end
+ else
+ render "collection_resources/new"
+ end
+ end
+
+ def delete_confirmation
+ return render_not_authorized unless current_user.support?
+
+ @collection_resource = CollectionResource.find_by(id: params[:collection_resource_id])
+
+ return render_not_found unless @collection_resource
+
+ render "collection_resources/delete_confirmation"
+ end
+
+ def delete
+ return render_not_authorized unless current_user.support?
+
+ @collection_resource = CollectionResource.find_by(id: params[:collection_resource_id])
+
+ return render_not_found unless @collection_resource
+
+ @collection_resource.discard!
+
+ flash[:notice] = "The #{@collection_resource.log_type} #{text_year_range_format(@collection_resource.year)} #{@collection_resource.short_display_name.downcase} has been deleted."
+ redirect_to collection_resources_path
+ end
+
private
def resource_params
- params.require(:collection_resource).permit(:year, :log_type, :resource_type, :file)
+ params.require(:collection_resource).permit(:year, :log_type, :resource_type, :file, :mandatory, :short_display_name)
end
def download_resource(filename)
@@ -113,23 +229,4 @@ private
def resource_for_year_can_be_updated?(year)
editable_collection_resource_years.include?(year)
end
-
- def validate_file(file)
- return @collection_resource.errors.add(:file, :blank) unless file
- return @collection_resource.errors.add(:file, :above_100_mb) if file.size > 100.megabytes
-
- argv = %W[file --brief --mime-type -- #{file.path}]
- output = `#{argv.shelljoin}`
-
- case @collection_resource.resource_type
- when "paper_form"
- unless output.match?(/application\/pdf/)
- @collection_resource.errors.add(:file, :must_be_pdf)
- end
- when "bulk_upload_template", "bulk_upload_specification"
- unless output.match?(/application\/vnd\.ms-excel|application\/vnd\.openxmlformats-officedocument\.spreadsheetml\.sheet/)
- @collection_resource.errors.add(:file, :must_be_xlsx, resource: @collection_resource.short_display_name.downcase)
- end
- end
- end
end
diff --git a/app/controllers/start_controller.rb b/app/controllers/start_controller.rb
index 5bd49df3f..d6df81c39 100644
--- a/app/controllers/start_controller.rb
+++ b/app/controllers/start_controller.rb
@@ -4,6 +4,8 @@ class StartController < ApplicationController
def index
@mandatory_lettings_collection_resources_per_year = MandatoryCollectionResourcesService.generate_resources("lettings", displayed_collection_resource_years)
@mandatory_sales_collection_resources_per_year = MandatoryCollectionResourcesService.generate_resources("sales", displayed_collection_resource_years)
+ @additional_lettings_collection_resources_per_year = CollectionResource.visible.where(log_type: "lettings", mandatory: false, year: displayed_collection_resource_years).group_by(&:year)
+ @additional_sales_collection_resources_per_year = CollectionResource.visible.where(log_type: "sales", mandatory: false, year: displayed_collection_resource_years).group_by(&:year)
if current_user
@homepage_presenter = HomepagePresenter.new(current_user)
render "home/index"
diff --git a/app/frontend/styles/_bulk-uploads.scss b/app/frontend/styles/_bulk-uploads.scss
index eceae6565..65213ee82 100644
--- a/app/frontend/styles/_bulk-uploads.scss
+++ b/app/frontend/styles/_bulk-uploads.scss
@@ -12,6 +12,11 @@
border-bottom: 1px solid #b1b4b6;
}
+.no-bottom-border,
+.no-bottom-border > tbody > tr:last-of-type td {
+ border-bottom: none;
+}
+
.text-normal-break {
white-space: normal;
word-break: break-all;
diff --git a/app/frontend/styles/_document-list.scss b/app/frontend/styles/_document-list.scss
index e8cef0625..a1f812551 100644
--- a/app/frontend/styles/_document-list.scss
+++ b/app/frontend/styles/_document-list.scss
@@ -1,5 +1,4 @@
.app-document-list {
- margin-top: govuk-spacing(3);
margin-bottom: govuk-spacing(6);
}
diff --git a/app/helpers/collection_resources_helper.rb b/app/helpers/collection_resources_helper.rb
index 9deb13887..1a5887744 100644
--- a/app/helpers/collection_resources_helper.rb
+++ b/app/helpers/collection_resources_helper.rb
@@ -49,7 +49,7 @@ module CollectionResourcesHelper
def document_list_component_items(resources)
resources.map do |resource|
{
- name: "Download the #{resource.display_name}",
+ name: "Download the #{resource.display_name.downcase}",
href: resource.download_path,
metadata: file_type_size_and_pages(resource.download_filename),
}
diff --git a/app/models/collection_resource.rb b/app/models/collection_resource.rb
index 81665ccf6..fc5808cec 100644
--- a/app/models/collection_resource.rb
+++ b/app/models/collection_resource.rb
@@ -1,9 +1,45 @@
class CollectionResource < ApplicationRecord
include Rails.application.routes.url_helpers
+ has_paper_trail
attr_accessor :file
+ scope :visible, -> { where(discarded_at: nil) }
+ validates :short_display_name, presence: true
+
def download_path
- download_mandatory_collection_resource_path(log_type:, year:, resource_type:)
+ if mandatory
+ download_mandatory_collection_resource_path(log_type:, year:, resource_type:)
+ else
+ collection_resource_download_path(self)
+ end
+ end
+
+ def validate_attached_file
+ return errors.add(:file, :blank) unless file
+ return errors.add(:file, :above_100_mb) if file.size > 100.megabytes
+
+ argv = %W[file --brief --mime-type -- #{file.path}]
+ output = `#{argv.shelljoin}`
+
+ case resource_type
+ when "paper_form"
+ unless output.match?(/application\/pdf/)
+ errors.add(:file, :must_be_pdf)
+ end
+ when "bulk_upload_template", "bulk_upload_specification"
+ unless output.match?(/application\/vnd\.ms-excel|application\/vnd\.openxmlformats-officedocument\.spreadsheetml\.sheet/)
+ errors.add(:file, :must_be_xlsx, resource: short_display_name.downcase)
+ end
+ end
+ end
+
+ def validate_short_display_name
+ errors.add(:short_display_name, :blank) if short_display_name.blank?
+ end
+
+ def discard!
+ CollectionResourcesService.new.delete_collection_resource(download_filename)
+ update!(discarded_at: Time.zone.now)
end
end
diff --git a/app/models/form/sales/questions/uprn.rb b/app/models/form/sales/questions/uprn.rb
index 75a41a0ec..94aaccb43 100644
--- a/app/models/form/sales/questions/uprn.rb
+++ b/app/models/form/sales/questions/uprn.rb
@@ -16,7 +16,7 @@ class Form::Sales::Questions::Uprn < ::Form::Question
end
def unanswered_error_message
- I18n.t("validations.property.uprn.invalid")
+ I18n.t("validations.sales.property_information.uprn.invalid")
end
def get_extra_check_answer_value(log)
diff --git a/app/models/form/sales/questions/uprn_known.rb b/app/models/form/sales/questions/uprn_known.rb
index 44ce03cad..fdd0c9e9a 100644
--- a/app/models/form/sales/questions/uprn_known.rb
+++ b/app/models/form/sales/questions/uprn_known.rb
@@ -31,7 +31,7 @@ class Form::Sales::Questions::UprnKnown < ::Form::Question
}.freeze
def unanswered_error_message
- I18n.t("validations.property.uprn_known.invalid")
+ I18n.t("validations.sales.property_information.uprn_known.invalid")
end
QUESTION_NUMBER_FROM_YEAR = { 2023 => 14, 2024 => 15 }.freeze
diff --git a/app/models/validations/sales/household_validations.rb b/app/models/validations/sales/household_validations.rb
index e006374aa..fb1ea65c2 100644
--- a/app/models/validations/sales/household_validations.rb
+++ b/app/models/validations/sales/household_validations.rb
@@ -9,9 +9,9 @@ module Validations::Sales::HouseholdValidations
return unless record.form.start_date.year >= 2023
if record.buyers_will_live_in? && record.buyer_one_will_not_live_in_property? && record.buyer_two_will_not_live_in_property?
- record.errors.add :buylivein, I18n.t("validations.household.buylivein.buyers_will_live_in_property_values_inconsistent_setup")
- record.errors.add :buy1livein, I18n.t("validations.household.buylivein.buyers_will_live_in_property_values_inconsistent")
- record.errors.add :buy2livein, I18n.t("validations.household.buylivein.buyers_will_live_in_property_values_inconsistent")
+ record.errors.add :buylivein, I18n.t("validations.sales.household.buylivein.buyers_will_live_in_property_values_inconsistent")
+ record.errors.add :buy1livein, I18n.t("validations.sales.household.buy1livein.buyers_will_live_in_property_values_inconsistent")
+ record.errors.add :buy2livein, I18n.t("validations.sales.household.buy2livein.buyers_will_live_in_property_values_inconsistent")
end
end
@@ -20,8 +20,8 @@ module Validations::Sales::HouseholdValidations
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")
+ record.errors.add :prevten, I18n.t("validations.sales.household.prevten.prevten_invalid_for_discounted_sale")
+ record.errors.add :ownershipsch, I18n.t("validations.sales.household.ownershipsch.prevten_invalid_for_discounted_sale")
end
end
@@ -34,11 +34,11 @@ module Validations::Sales::HouseholdValidations
next unless age && relationship
if age < 16 && !relationship_is_child_other_or_refused?(relationship)
- record.errors.add "age#{person_num}", I18n.t("validations.household.age.child_under_16_relat_sales", person_num:)
- record.errors.add "relat#{person_num}", I18n.t("validations.household.relat.child_under_16_sales", person_num:)
+ record.errors.add "age#{person_num}", I18n.t("validations.sales.household.age.child_under_16", person_num:)
+ record.errors.add "relat#{person_num}", I18n.t("validations.sales.household.relat.child_under_16", person_num:)
elsif age >= 20 && person_is_child?(relationship)
- record.errors.add "age#{person_num}", I18n.t("validations.household.age.child_over_20")
- record.errors.add "relat#{person_num}", I18n.t("validations.household.relat.child_over_20")
+ record.errors.add "age#{person_num}", I18n.t("validations.sales.household.age.child_over_20")
+ record.errors.add "relat#{person_num}", I18n.t("validations.sales.household.relat.child_over_20")
end
end
end
@@ -58,16 +58,16 @@ module Validations::Sales::HouseholdValidations
child = person_is_child?(relationship)
if age_between_16_19 && !(student || economic_status_refused) && child
- record.errors.add "ecstat#{person_num}", I18n.t("validations.household.ecstat.student_16_19.must_be_student")
- record.errors.add "age#{person_num}", I18n.t("validations.household.age.student_16_19.cannot_be_16_19.child_not_student")
- record.errors.add "relat#{person_num}", I18n.t("validations.household.relat.student_16_19.cannot_be_child.16_19_not_student")
+ record.errors.add "ecstat#{person_num}", I18n.t("validations.sales.household.ecstat.student_16_19.must_be_student")
+ record.errors.add "age#{person_num}", I18n.t("validations.sales.household.age.student_16_19.cannot_be_16_19.child_not_student")
+ record.errors.add "relat#{person_num}", I18n.t("validations.sales.household.relat.student_16_19.cannot_be_child.16_19_not_student")
end
next unless !age_between_16_19 && student && child
- record.errors.add "age#{person_num}", I18n.t("validations.household.age.student_16_19.must_be_16_19")
- record.errors.add "ecstat#{person_num}", I18n.t("validations.household.ecstat.student_16_19.cannot_be_student.child_not_16_19")
- record.errors.add "relat#{person_num}", I18n.t("validations.household.relat.student_16_19.cannot_be_child.student_not_16_19")
+ record.errors.add "age#{person_num}", I18n.t("validations.sales.household.age.student_16_19.must_be_16_19")
+ record.errors.add "ecstat#{person_num}", I18n.t("validations.sales.household.ecstat.student_16_19.cannot_be_student.child_not_16_19")
+ record.errors.add "relat#{person_num}", I18n.t("validations.sales.household.relat.student_16_19.cannot_be_child.student_not_16_19")
end
end
@@ -78,12 +78,12 @@ module Validations::Sales::HouseholdValidations
next unless age && economic_status
if age < 16 && !economic_status_is_child_other_or_refused?(economic_status) && !record.form.start_year_after_2024?
- record.errors.add "ecstat#{person_num}", I18n.t("validations.household.ecstat.child_under_16", person_num:)
- record.errors.add "age#{person_num}", I18n.t("validations.household.age.child_under_16_ecstat", person_num:)
+ record.errors.add "ecstat#{person_num}", I18n.t("validations.sales.household.ecstat.child_under_16", person_num:)
+ record.errors.add "age#{person_num}", I18n.t("validations.sales.household.age.child_under_16_ecstat", person_num:)
end
if person_is_economic_child?(economic_status) && age > 16
- record.errors.add "ecstat#{person_num}", I18n.t("validations.household.ecstat.child_over_16", person_num:)
- record.errors.add "age#{person_num}", I18n.t("validations.household.age.child_over_16", person_num:)
+ record.errors.add "ecstat#{person_num}", I18n.t("validations.sales.household.ecstat.child_over_16", person_num:)
+ record.errors.add "age#{person_num}", I18n.t("validations.sales.household.age.child_over_16", person_num:)
end
end
end
@@ -99,17 +99,17 @@ module Validations::Sales::HouseholdValidations
next unless person_age > buyer_1_age - 12 && person_is_child?(relationship)
- record.errors.add "age1", I18n.t("validations.household.age.child_12_years_younger")
- record.errors.add "age#{person_num}", I18n.t("validations.household.age.child_12_years_younger")
- record.errors.add "relat#{person_num}", I18n.t("validations.household.age.child_12_years_younger")
+ record.errors.add "age1", I18n.t("validations.sales.household.age1.child_12_years_younger")
+ record.errors.add "age#{person_num}", I18n.t("validations.sales.household.age.child_12_years_younger")
+ record.errors.add "relat#{person_num}", I18n.t("validations.sales.household.relat.child_12_years_younger")
end
end
def validate_buyer_not_child(record)
return unless record.saledate && record.form.start_year_after_2024?
- record.errors.add "ecstat1", I18n.t("validations.household.ecstat.buyer_cannot_be_child", buyer_index: "1") if person_is_economic_child?(record.ecstat1)
- record.errors.add "ecstat2", I18n.t("validations.household.ecstat.buyer_cannot_be_child", buyer_index: "2") if person_is_economic_child?(record.ecstat2) && record.joint_purchase?
+ record.errors.add "ecstat1", I18n.t("validations.sales.household.ecstat1.buyer_cannot_be_child") if person_is_economic_child?(record.ecstat1)
+ record.errors.add "ecstat2", I18n.t("validations.sales.household.ecstat2.buyer_cannot_be_child") if person_is_economic_child?(record.ecstat2) && record.joint_purchase?
end
private
diff --git a/app/models/validations/sales/property_validations.rb b/app/models/validations/sales/property_validations.rb
index 50818c422..59d616c9a 100644
--- a/app/models/validations/sales/property_validations.rb
+++ b/app/models/validations/sales/property_validations.rb
@@ -4,10 +4,11 @@ module Validations::Sales::PropertyValidations
return unless record.ppostcode_full.present? && record.postcode_full.present?
if record.discounted_ownership_sale? && record.ppostcode_full != record.postcode_full
- record.errors.add :postcode_full, I18n.t("validations.property.postcode.must_match_previous", buyer_possessive: record.joint_purchase? ? "Buyers’" : "Buyer’s")
- record.errors.add :ppostcode_full, I18n.t("validations.property.postcode.must_match_previous", buyer_possessive: record.joint_purchase? ? "Buyers’" : "Buyer’s")
- record.errors.add :ownershipsch, I18n.t("validations.property.postcode.must_match_previous", buyer_possessive: record.joint_purchase? ? "Buyers’" : "Buyer’s")
- record.errors.add :uprn, I18n.t("validations.property.postcode.must_match_previous", buyer_possessive: record.joint_purchase? ? "Buyers’" : "Buyer’s")
+ joint_purchase_id = record.joint_purchase? ? "joint_purchase" : "not_joint_purchase"
+ record.errors.add :postcode_full, I18n.t("validations.sales.property_information.postcode_full.postcode_must_match_previous.#{joint_purchase_id}")
+ record.errors.add :ppostcode_full, I18n.t("validations.sales.property_information.ppostcode_full.postcode_must_match_previous.#{joint_purchase_id}")
+ record.errors.add :ownershipsch, I18n.t("validations.sales.property_information.ownershipsch.postcode_must_match_previous.#{joint_purchase_id}")
+ record.errors.add :uprn, I18n.t("validations.sales.property_information.uprn.postcode_must_match_previous.#{joint_purchase_id}")
end
end
@@ -15,8 +16,8 @@ module Validations::Sales::PropertyValidations
return unless record.proptype.present? && record.beds.present?
if record.is_bedsit? && record.beds > 1
- record.errors.add :proptype, I18n.t("validations.property.proptype.bedsits_have_max_one_bedroom")
- record.errors.add :beds, I18n.t("validations.property.beds.bedsits_have_max_one_bedroom")
+ record.errors.add :proptype, I18n.t("validations.sales.property_information.proptype.bedsits_have_max_one_bedroom")
+ record.errors.add :beds, I18n.t("validations.sales.property_information.beds.bedsits_have_max_one_bedroom")
end
end
@@ -25,6 +26,6 @@ module Validations::Sales::PropertyValidations
return if record.uprn.match?(/^[0-9]{1,12}$/)
- record.errors.add :uprn, I18n.t("validations.property.uprn.invalid")
+ record.errors.add :uprn, I18n.t("validations.sales.property_information.uprn.invalid")
end
end
diff --git a/app/models/validations/shared_validations.rb b/app/models/validations/shared_validations.rb
index 217b2c170..a2ae916c1 100644
--- a/app/models/validations/shared_validations.rb
+++ b/app/models/validations/shared_validations.rb
@@ -131,7 +131,11 @@ module Validations::SharedValidations
partner_numbers = (2..max_people).select { |n| person_is_partner?(record["relat#{n}"]) }
if partner_numbers.count > 1
partner_numbers.each do |n|
- record.errors.add "relat#{n}", I18n.t("validations.household.relat.one_partner")
+ if record.sales?
+ record.errors.add "relat#{n}", I18n.t("validations.sales.household.relat.one_partner")
+ else
+ record.errors.add "relat#{n}", I18n.t("validations.household.relat.one_partner")
+ end
end
end
end
diff --git a/app/services/bulk_upload/lettings/year2023/csv_parser.rb b/app/services/bulk_upload/lettings/year2023/csv_parser.rb
index 64e27108f..96be2256d 100644
--- a/app/services/bulk_upload/lettings/year2023/csv_parser.rb
+++ b/app/services/bulk_upload/lettings/year2023/csv_parser.rb
@@ -109,9 +109,11 @@ private
def first_record_start_date
if with_headers?
- Date.new(row_parsers.first.field_9.to_i + 2000, row_parsers.first.field_8.to_i, row_parsers.first.field_7.to_i)
+ year = row_parsers.first.field_9.to_s.strip.length.between?(1, 2) ? row_parsers.first.field_9.to_i + 2000 : row_parsers.first.field_9.to_i
+ Date.new(year, row_parsers.first.field_8.to_i, row_parsers.first.field_7.to_i)
else
- Date.new(rows.first[97].to_i + 2000, rows.first[96].to_i, rows.first[95].to_i)
+ year = rows.first[97].to_s.strip.length.between?(1, 2) ? rows.first[97].to_i + 2000 : rows.first[97].to_i
+ Date.new(year, rows.first[96].to_i, rows.first[95].to_i)
end
end
end
diff --git a/app/services/bulk_upload/lettings/year2023/row_parser.rb b/app/services/bulk_upload/lettings/year2023/row_parser.rb
index 8c435ad24..59cf7ea34 100644
--- a/app/services/bulk_upload/lettings/year2023/row_parser.rb
+++ b/app/services/bulk_upload/lettings/year2023/row_parser.rb
@@ -323,8 +323,8 @@ class BulkUpload::Lettings::Year2023::RowParser
category: :setup,
},
format: {
- with: /\A\d{2}\z/,
- message: I18n.t("validations.setup.startdate.year_not_two_digits"),
+ with: /\A(\d{2}|\d{4})\z/,
+ message: I18n.t("validations.setup.startdate.year_not_two_or_four_digits"),
category: :setup,
unless: -> { field_9.blank? },
},
@@ -618,14 +618,6 @@ private
end
end
- def start_date
- return if field_7.blank? || field_8.blank? || field_9.blank?
-
- Date.parse("20#{field_9.to_s.rjust(2, '0')}-#{field_8}-#{field_7}")
- rescue StandardError
- nil
- end
-
def validate_no_and_dont_know_disabled_needs_conjunction
if field_87 == 1 && field_88 == 1
errors.add(:field_87, I18n.t("validations.household.housingneeds.no_and_dont_know_disabled_needs_conjunction"))
@@ -736,9 +728,9 @@ private
end
def validate_relevant_collection_window
- return if start_date.blank? || bulk_upload.form.blank?
+ return if startdate.blank? || bulk_upload.form.blank?
- unless bulk_upload.form.valid_start_date_for_form?(start_date)
+ unless bulk_upload.form.valid_start_date_for_form?(startdate)
errors.add(:field_7, I18n.t("validations.date.outside_collection_window", year_combo: bulk_upload.year_combo, start_year: bulk_upload.year, end_year: bulk_upload.end_year), category: :setup)
errors.add(:field_8, I18n.t("validations.date.outside_collection_window", year_combo: bulk_upload.year_combo, start_year: bulk_upload.year, end_year: bulk_upload.end_year), category: :setup)
errors.add(:field_9, I18n.t("validations.date.outside_collection_window", year_combo: bulk_upload.year_combo, start_year: bulk_upload.year, end_year: bulk_upload.end_year), category: :setup)
@@ -1388,7 +1380,8 @@ private
end
def startdate
- Date.new(field_9 + 2000, field_8, field_7) if field_9.present? && field_8.present? && field_7.present?
+ year = field_9.to_s.strip.length.between?(1, 2) ? field_9 + 2000 : field_9
+ Date.new(year, field_8, field_7) if field_9.present? && field_8.present? && field_7.present?
rescue Date::Error
Date.new
end
@@ -1584,13 +1577,15 @@ private
end
def mrcdate
- Date.new(field_38 + 2000, field_37, field_36) if field_38.present? && field_37.present? && field_36.present?
+ year = field_38.to_s.strip.length.between?(1, 2) ? field_38 + 2000 : field_38
+ Date.new(year, field_37, field_36) if field_38.present? && field_37.present? && field_36.present?
rescue Date::Error
Date.new
end
def voiddate
- Date.new(field_35 + 2000, field_34, field_33) if field_35.present? && field_34.present? && field_33.present?
+ year = field_35.to_s.strip.length.between?(1, 2) ? field_35 + 2000 : field_35
+ Date.new(year, field_34, field_33) if field_35.present? && field_34.present? && field_33.present?
rescue Date::Error
Date.new
end
diff --git a/app/services/bulk_upload/lettings/year2024/csv_parser.rb b/app/services/bulk_upload/lettings/year2024/csv_parser.rb
index d8d430755..22caeab02 100644
--- a/app/services/bulk_upload/lettings/year2024/csv_parser.rb
+++ b/app/services/bulk_upload/lettings/year2024/csv_parser.rb
@@ -112,9 +112,11 @@ private
def first_record_start_date
if with_headers?
- Date.new(row_parsers.first.field_10.to_i + 2000, row_parsers.first.field_9.to_i, row_parsers.first.field_8.to_i)
+ year = row_parsers.first.field_10.to_s.strip.length.between?(1, 2) ? row_parsers.first.field_10.to_i + 2000 : row_parsers.first.field_10.to_i
+ Date.new(year, row_parsers.first.field_9.to_i, row_parsers.first.field_8.to_i)
else
- Date.new(rows.first[9].to_i + 2000, rows.first[8].to_i, rows.first[7].to_i)
+ year = rows.first[9].to_s.strip.length.between?(1, 2) ? rows.first[9].to_i + 2000 : rows.first[9].to_i
+ Date.new(year, rows.first[8].to_i, rows.first[7].to_i)
end
end
end
diff --git a/app/services/bulk_upload/lettings/year2024/row_parser.rb b/app/services/bulk_upload/lettings/year2024/row_parser.rb
index a7eb96c61..312a5472a 100644
--- a/app/services/bulk_upload/lettings/year2024/row_parser.rb
+++ b/app/services/bulk_upload/lettings/year2024/row_parser.rb
@@ -324,8 +324,8 @@ class BulkUpload::Lettings::Year2024::RowParser
category: :setup,
},
format: {
- with: /\A\d{2}\z/,
- message: I18n.t("validations.setup.startdate.year_not_two_digits"),
+ with: /\A(\d{2}|\d{4})\z/,
+ message: I18n.t("validations.setup.startdate.year_not_two_or_four_digits"),
category: :setup,
unless: -> { field_10.blank? },
},
@@ -686,14 +686,6 @@ private
end
end
- def start_date
- return if field_8.blank? || field_9.blank? || field_10.blank?
-
- Date.parse("20#{field_10.to_s.rjust(2, '0')}-#{field_9}-#{field_8}")
- rescue StandardError
- nil
- end
-
def validate_no_and_dont_know_disabled_needs_conjunction
if field_83 == 1 && field_84 == 1
errors.add(:field_83, I18n.t("validations.household.housingneeds.no_and_dont_know_disabled_needs_conjunction"))
@@ -790,9 +782,9 @@ private
end
def validate_relevant_collection_window
- return if start_date.blank? || bulk_upload.form.blank?
+ return if startdate.blank? || bulk_upload.form.blank?
- unless bulk_upload.form.valid_start_date_for_form?(start_date)
+ unless bulk_upload.form.valid_start_date_for_form?(startdate)
errors.add(:field_8, I18n.t("validations.date.outside_collection_window", year_combo: bulk_upload.year_combo, start_year: bulk_upload.year, end_year: bulk_upload.end_year), category: :setup)
errors.add(:field_9, I18n.t("validations.date.outside_collection_window", year_combo: bulk_upload.year_combo, start_year: bulk_upload.year, end_year: bulk_upload.end_year), category: :setup)
errors.add(:field_10, I18n.t("validations.date.outside_collection_window", year_combo: bulk_upload.year_combo, start_year: bulk_upload.year, end_year: bulk_upload.end_year), category: :setup)
@@ -1394,7 +1386,8 @@ private
end
def startdate
- Date.new(field_10 + 2000, field_9, field_8) if field_10.present? && field_9.present? && field_8.present?
+ year = field_10.to_s.strip.length.between?(1, 2) ? field_10 + 2000 : field_10
+ Date.new(year, field_9, field_8) if field_10.present? && field_9.present? && field_8.present?
rescue Date::Error
Date.new
end
@@ -1599,13 +1592,15 @@ private
end
def mrcdate
- Date.new(field_35 + 2000, field_34, field_33) if field_35.present? && field_34.present? && field_33.present?
+ year = field_35.to_s.strip.length.between?(1, 2) ? field_35 + 2000 : field_35
+ Date.new(year, field_34, field_33) if field_35.present? && field_34.present? && field_33.present?
rescue Date::Error
Date.new
end
def voiddate
- Date.new(field_32 + 2000, field_31, field_30) if field_32.present? && field_31.present? && field_30.present?
+ year = field_32.to_s.strip.length.between?(1, 2) ? field_32 + 2000 : field_32
+ Date.new(year, field_31, field_30) if field_32.present? && field_31.present? && field_30.present?
rescue Date::Error
Date.new
end
diff --git a/app/services/bulk_upload/sales/year2023/csv_parser.rb b/app/services/bulk_upload/sales/year2023/csv_parser.rb
index ec7346f92..85d455d01 100644
--- a/app/services/bulk_upload/sales/year2023/csv_parser.rb
+++ b/app/services/bulk_upload/sales/year2023/csv_parser.rb
@@ -111,9 +111,11 @@ private
def first_record_start_date
if with_headers?
- Date.new(row_parsers.first.field_5.to_i + 2000, row_parsers.first.field_4.to_i, row_parsers.first.field_3.to_i)
+ year = row_parsers.first.field_5.to_s.strip.length.between?(1, 2) ? row_parsers.first.field_5.to_i + 2000 : row_parsers.first.field_5.to_i
+ Date.new(year, row_parsers.first.field_4.to_i, row_parsers.first.field_3.to_i)
else
- Date.new(rows.first[3].to_i + 2000, rows.first[2].to_i, rows.first[1].to_i)
+ year = rows.first[3].to_s.strip.length.between?(1, 2) ? rows.first[3].to_i + 2000 : rows.first[3].to_i
+ Date.new(year, rows.first[2].to_i, rows.first[1].to_i)
end
end
end
diff --git a/app/services/bulk_upload/sales/year2023/row_parser.rb b/app/services/bulk_upload/sales/year2023/row_parser.rb
index 6a18512aa..d87c94dce 100644
--- a/app/services/bulk_upload/sales/year2023/row_parser.rb
+++ b/app/services/bulk_upload/sales/year2023/row_parser.rb
@@ -328,8 +328,8 @@ class BulkUpload::Sales::Year2023::RowParser
category: :setup,
},
format: {
- with: /\A\d{2}\z/,
- message: I18n.t("validations.setup.saledate.year_not_two_digits"),
+ with: /\A(\d{2}|\d{4})\z/,
+ message: I18n.t("validations.setup.saledate.year_not_two_or_four_digits"),
category: :setup,
if: proc { field_5.present? },
}, on: :after_log
@@ -954,19 +954,22 @@ private
end
def saledate
- Date.new(field_5 + 2000, field_4, field_3) if field_5.present? && field_4.present? && field_3.present?
+ year = field_5.to_s.strip.length.between?(1, 2) ? field_5 + 2000 : field_5
+ Date.new(year, field_4, field_3) if field_5.present? && field_4.present? && field_3.present?
rescue Date::Error
Date.new
end
def hodate
- Date.new(field_97 + 2000, field_96, field_95) if field_97.present? && field_96.present? && field_95.present?
+ year = field_97.to_s.strip.length.between?(1, 2) ? field_97 + 2000 : field_97
+ Date.new(year, field_96, field_95) if field_97.present? && field_96.present? && field_95.present?
rescue Date::Error
Date.new
end
def exdate
- Date.new(field_94 + 2000, field_93, field_92) if field_94.present? && field_93.present? && field_92.present?
+ year = field_94.to_s.strip.length.between?(1, 2) ? field_94 + 2000 : field_94
+ Date.new(year, field_93, field_92) if field_94.present? && field_93.present? && field_92.present?
rescue Date::Error
Date.new
end
diff --git a/app/services/bulk_upload/sales/year2024/csv_parser.rb b/app/services/bulk_upload/sales/year2024/csv_parser.rb
index 9ba99c19b..4a3cb7ac9 100644
--- a/app/services/bulk_upload/sales/year2024/csv_parser.rb
+++ b/app/services/bulk_upload/sales/year2024/csv_parser.rb
@@ -114,9 +114,11 @@ private
def first_record_start_date
if with_headers?
- Date.new(row_parsers.first.field_6.to_i + 2000, row_parsers.first.field_5.to_i, row_parsers.first.field_4.to_i)
+ year = row_parsers.first.field_6.to_s.strip.length.between?(1, 2) ? row_parsers.first.field_6.to_i + 2000 : row_parsers.first.field_6.to_i
+ Date.new(year, row_parsers.first.field_5.to_i, row_parsers.first.field_4.to_i)
else
- Date.new(rows.first[5].to_i + 2000, rows.first[4].to_i, rows.first[3].to_i)
+ year = rows.first[5].to_s.strip.length.between?(1, 2) ? rows.first[5].to_i + 2000 : rows.first[5].to_i
+ Date.new(year, rows.first[4].to_i, rows.first[3].to_i)
end
end
end
diff --git a/app/services/bulk_upload/sales/year2024/row_parser.rb b/app/services/bulk_upload/sales/year2024/row_parser.rb
index fbc99ba02..7b72eef5c 100644
--- a/app/services/bulk_upload/sales/year2024/row_parser.rb
+++ b/app/services/bulk_upload/sales/year2024/row_parser.rb
@@ -320,8 +320,8 @@ class BulkUpload::Sales::Year2024::RowParser
category: :setup,
},
format: {
- with: /\A\d{2}\z/,
- message: I18n.t("validations.setup.saledate.year_not_two_digits"),
+ with: /\A(\d{2}|\d{4})\z/,
+ message: I18n.t("validations.setup.saledate.year_not_two_or_four_digits"),
category: :setup,
if: proc { field_6.present? },
}, on: :after_log
@@ -994,19 +994,22 @@ private
end
def saledate
- Date.new(field_6 + 2000, field_5, field_4) if field_6.present? && field_5.present? && field_4.present?
+ year = field_6.to_s.strip.length.between?(1, 2) ? field_6 + 2000 : field_6
+ Date.new(year, field_5, field_4) if field_6.present? && field_5.present? && field_4.present?
rescue Date::Error
Date.new
end
def hodate
- Date.new(field_96 + 2000, field_95, field_94) if field_96.present? && field_95.present? && field_94.present?
+ year = field_96.to_s.strip.length.between?(1, 2) ? field_96 + 2000 : field_96
+ Date.new(year, field_95, field_94) if field_96.present? && field_95.present? && field_94.present?
rescue Date::Error
Date.new
end
def exdate
- Date.new(field_93 + 2000, field_92, field_91) if field_93.present? && field_92.present? && field_91.present?
+ year = field_93.to_s.strip.length.between?(1, 2) ? field_93 + 2000 : field_93
+ Date.new(year, field_92, field_91) if field_93.present? && field_92.present? && field_91.present?
rescue Date::Error
Date.new
end
@@ -1469,10 +1472,10 @@ private
def validate_buyer1_economic_status
if field_35 == 9
if field_31.present? && field_31.to_i >= 16
- errors.add(:field_35, I18n.t("validations.household.ecstat.buyer_cannot_be_over_16_and_child", buyer_index: "1"))
- errors.add(:field_31, I18n.t("validations.household.ecstat.buyer_cannot_be_over_16_and_child", buyer_index: "1"))
+ errors.add(:field_35, I18n.t("validations.sales.household.ecstat.buyer_cannot_be_over_16_and_child", buyer_index: "1"))
+ errors.add(:field_31, I18n.t("validations.sales.household.ecstat.buyer_cannot_be_over_16_and_child", buyer_index: "1"))
else
- errors.add(:field_35, I18n.t("validations.household.ecstat.buyer_cannot_be_child", buyer_index: "1"))
+ errors.add(:field_35, I18n.t("validations.sales.household.ecstat1.buyer_cannot_be_child"))
end
end
end
@@ -1482,10 +1485,10 @@ private
if field_42 == 9
if field_38.present? && field_38.to_i >= 16
- errors.add(:field_42, I18n.t("validations.household.ecstat.buyer_cannot_be_over_16_and_child", buyer_index: "2"))
- errors.add(:field_38, I18n.t("validations.household.ecstat.buyer_cannot_be_over_16_and_child", buyer_index: "2"))
+ errors.add(:field_42, I18n.t("validations.sales.household.ecstat.buyer_cannot_be_over_16_and_child", buyer_index: "2"))
+ errors.add(:field_38, I18n.t("validations.sales.household.ecstat.buyer_cannot_be_over_16_and_child", buyer_index: "2"))
else
- errors.add(:field_42, I18n.t("validations.household.ecstat.buyer_cannot_be_child", buyer_index: "2"))
+ errors.add(:field_42, I18n.t("validations.sales.household.ecstat2.buyer_cannot_be_child"))
end
end
end
diff --git a/app/services/collection_resources_service.rb b/app/services/collection_resources_service.rb
index 81ab08254..f6762116a 100644
--- a/app/services/collection_resources_service.rb
+++ b/app/services/collection_resources_service.rb
@@ -24,6 +24,11 @@ class CollectionResourcesService
end
def upload_collection_resource(filename, file)
- @storage_service.write_file(filename, file)
+ content_type = MiniMime.lookup_by_filename(filename)&.content_type
+ @storage_service.write_file(filename, file, content_type:)
+ end
+
+ def delete_collection_resource(filename)
+ @storage_service.delete_file(filename)
end
end
diff --git a/app/services/mandatory_collection_resources_service.rb b/app/services/mandatory_collection_resources_service.rb
index 8b34153e9..397e4b5d0 100644
--- a/app/services/mandatory_collection_resources_service.rb
+++ b/app/services/mandatory_collection_resources_service.rb
@@ -27,6 +27,7 @@ class MandatoryCollectionResourcesService
year:,
log_type:,
download_filename: download_filename(resource_type, year, log_type),
+ mandatory: true,
)
end
diff --git a/app/services/storage/local_disk_service.rb b/app/services/storage/local_disk_service.rb
index bb5825340..228f0339e 100644
--- a/app/services/storage/local_disk_service.rb
+++ b/app/services/storage/local_disk_service.rb
@@ -19,7 +19,7 @@ module Storage
File.open(path, "r")
end
- def write_file(filename, data)
+ def write_file(filename, data, _content_type: nil)
path = Rails.root.join("tmp/storage", filename)
FileUtils.mkdir_p(path.dirname)
@@ -43,5 +43,11 @@ module Storage
File.exist?(path)
end
+
+ def delete_file(filename)
+ path = Rails.root.join("tmp/storage", filename)
+
+ File.delete(path)
+ end
end
end
diff --git a/app/services/storage/s3_service.rb b/app/services/storage/s3_service.rb
index 88199c0a0..a6eef7d49 100644
--- a/app/services/storage/s3_service.rb
+++ b/app/services/storage/s3_service.rb
@@ -36,12 +36,21 @@ module Storage
.body.read
end
- def write_file(file_name, data)
- @client.put_object(
- body: data,
- bucket: @configuration.bucket_name,
- key: file_name,
- )
+ def write_file(file_name, data, content_type: nil)
+ if content_type.nil?
+ @client.put_object(
+ body: data,
+ bucket: @configuration.bucket_name,
+ key: file_name,
+ )
+ else
+ @client.put_object(
+ body: data,
+ bucket: @configuration.bucket_name,
+ key: file_name,
+ content_type:,
+ )
+ end
end
def get_file_metadata(file_name)
@@ -55,6 +64,10 @@ module Storage
false
end
+ def delete_file(file_name)
+ @client.delete_object(bucket: @configuration.bucket_name, key: file_name)
+ end
+
private
def create_configuration
diff --git a/app/services/storage/storage_service.rb b/app/services/storage/storage_service.rb
index afb3d4a58..37d4cc0fd 100644
--- a/app/services/storage/storage_service.rb
+++ b/app/services/storage/storage_service.rb
@@ -15,5 +15,13 @@ module Storage
def write_file(_file_name, _data)
raise NotImplementedError
end
+
+ def get_file(_file_name, _data)
+ raise NotImplementedError
+ end
+
+ def delete_file(_file_name, _data)
+ raise NotImplementedError
+ end
end
end
diff --git a/app/views/collection_resources/_collection_resource_summary_list.erb b/app/views/collection_resources/_collection_resource_summary_list.erb
index 8ef588fd5..581bb6303 100644
--- a/app/views/collection_resources/_collection_resource_summary_list.erb
+++ b/app/views/collection_resources/_collection_resource_summary_list.erb
@@ -23,10 +23,27 @@
<% end %>
<% end %>
<% end %>
+ <% additional_resources&.each do |resource| %>
+ <% summary_list.with_row do |row| %>
+ <% row.with_key { resource.short_display_name } %>
+ <% row.with_value do %>
+ <%= render DocumentListComponent.new(items: document_list_edit_component_items([resource]), label: "") %>
+ <% end %>
+ <% row.with_action(
+ text: "Change",
+ href: collection_resource_edit_path(resource),
+ ) %>
+ <% row.with_action(
+ text: "Delete",
+ href: collection_resource_delete_confirmation_path(resource),
+ classes: "app-!-colour-red"
+ ) %>
+ <% end %>
+ <% end %>
<% end %>
-
- <%= govuk_link_to "Add new #{mandatory_resources.first.log_type} #{text_year_range_format(mandatory_resources.first.year)} resource", href: "/" %>
+
+ <%= govuk_link_to "Add new #{mandatory_resources.first.log_type} #{text_year_range_format(mandatory_resources.first.year)} resource", href: new_collection_resource_path(year: mandatory_resources.first.year, log_type: mandatory_resources.first.log_type) %>
-
+
diff --git a/app/views/collection_resources/delete_confirmation.html.erb b/app/views/collection_resources/delete_confirmation.html.erb
new file mode 100644
index 000000000..9f861bc19
--- /dev/null
+++ b/app/views/collection_resources/delete_confirmation.html.erb
@@ -0,0 +1,31 @@
+<% content_for :before_content do %>
+ <% content_for :title, "Are you sure you want to delete the #{@collection_resource.short_display_name.downcase}?" %>
+ <%= govuk_back_link href: collection_resources_path %>
+<% end %>
+
+