diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc
index c9d564782..9cd4bba71 100644
--- a/config/credentials.yml.enc
+++ b/config/credentials.yml.enc
@@ -1 +1 @@
-EZNV2LiNWzf52erbQ41Dz3Bh+2f3Uih8liEyhXp5XzHCLzAbmN6/IJqr7b9cTZiCiroFo4n/dFoG3yYrospp3frKsDXxF1K2/MTCJWjpgnn7wc+HiPQWG0W3HRtQCNkyyrHes0YKcYyDWIP6kztYv1I/Me3p0pGEx6t3CpSTg1v46eRnOlDWiUz3rVxPauwq9IYZ75gmnThqvg/Z8wcYsWLx0arago0SXtRPASCNj4uO/lbqTcAfyIXOTSiOlcAIjoPFRSQY7UqY0o2p8jRR/1L16SmGDsk8ijm+UygNmMexa3Khy5WcKctpQICakHs4NRjHNflqgXpXKL9dVBmNc9d7h+gbhbGJQ53Y0d+a35UbhPRMiv4SRH98FwB+WEsLCDdGSHvdmM6ArfOLljTrqrsmSRf0JfUrvzyVYmMCxjv4xgJwUS/TD5lQD1yPwkp2ss00kQJqzNmB7qwFhA8a3e2iNzV8qtAV/Nj+tMlr99Hb7vZZs98/38G2p5RAsE/5Xl9taKhc/ACnVc/bwJND4JWaBB7duCa08xVB8nkjlt5cCwMurzAcy1ZT+e8JepR+g6s8fpScMEWVJXE0hd8=--rZ41rY9TMXmiBUJw--QiLRVNVXZzTW446s7cec1g==
\ No newline at end of file
+QGn9IiI91BaO4IGAtfy92FrNP46X9T2jJErRv+o/PRG9LrimEGeuOE+FwhArKZQ5cTipaDqo8u9Ajv45Kitv3c0GynOOvz0r3OjPRHO/p4hW8BFWQDv581cWWPsyZT2JO51zZ5LnwNFvWrjEB2q49YESgtfADPkJWmtx/By5Cg2/PVIRxvhGKOnheme5cih050wqg/43BdiF0PD9FDTZXJDLJg/QQ8nQYkvQe2jN4nM4mTVpkQkmzDKgGknmUWFfW3qWFzlsdMkdkPdeP9wLnJVbFTeyaaJT3wv6l19d2rKqo8iVvacdaQjRev+LVXqOsNAjVHwcPNQVq9s8pxG24HLk3aQ14Eyjf6tHAuZAV4jLnNqQtBQ0AIldWeOl6SKmlTom1P1tcLp9KpajEADplmWSwUktIGmaakFjk/ApYaUBiYTku2iLHMrT/xSc3jPj5W/ZggeJ0Ij6nuGYE1cmBxWGxda9PzOrDP8coEK9vPHiNeDDM1RoukVmf8gwDmshILi5EwIAsO2gJXM1wtPYMu41+H4/y3c0GIwgfv9QP11q+nqhG1MMcOrAUKGhypAS+M+uLwfGQudfQDKP9Zv3VCnOk3mkKlpIzMMD4UdJxQeE/8sfwIsEhWggEo3oa93ptbRdvJ7YYcVvmMmkVBxk0KWFprl4i/BkFHLWrKNl5LBOGA==--ziMOTnYBB5TDyXYU--3FJMs8e6R8lheqcqB8p8uQ==
\ No newline at end of file
diff --git a/config/locales/en.yml b/config/locales/en.yml
index c666746da..50a821ce0 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -33,13 +33,13 @@ en:
service_name: "Submit social housing lettings and sales data (CORE)"
feedback_form: "https://forms.office.com/Pages/ResponsePage.aspx?id=EGg0v32c3kOociSi7zmVqC4YDsCJ3llAvEZelBFBLUBURFVUTzFDTUJPQlM4M0laTE5DTlNFSjJBQi4u"
organisation:
- created: "%{organisation} was created"
- updated: "Organisation details updated"
+ created: "%{organisation} was created."
+ updated: "Organisation details updated."
reactivated: "%{organisation} has been reactivated."
deactivated: "%{organisation} has been deactivated."
user:
- create_password: "Create a password to finish setting up your account"
- reset_password: "Reset your password"
+ create_password: "Create a password to finish setting up your account."
+ reset_password: "Reset your password."
active_notifications:
zero: "There are no active notifications"
@@ -50,8 +50,8 @@ en:
errors:
models:
bulk_upload/row_parser: &bulk_upload__row_parser__base
- inclusion: Enter a valid value for %{question}
- spreadsheet_dupe: This is a duplicate of a log in your file
+ inclusion: "Enter a valid value for %{question}"
+ spreadsheet_dupe: "This is a duplicate of a log in your file."
bulk_upload/lettings/year2024/row_parser:
<<: *bulk_upload__row_parser__base
bulk_upload/lettings/year2023/row_parser:
@@ -63,118 +63,118 @@ en:
bulk_upload/lettings/validator:
attributes:
base:
- blank_file: Template is blank - The template must be filled in for us to create the logs and check if data is correct.
- wrong_field_numbers_count: "Incorrect number of fields, please ensure you have used the correct template"
- over_max_column_count: "Too many columns, please ensure you have used the correct template"
- wrong_template: "Incorrect start dates, please ensure you have used the correct template"
+ blank_file: "Template is blank - The template must be filled in for us to create the logs and check if data is correct."
+ wrong_field_numbers_count: "Incorrect number of fields, please ensure you have used the correct template."
+ over_max_column_count: "Too many columns, please ensure you have used the correct template."
+ wrong_template: "Incorrect start dates, please ensure you have used the correct template."
no_headers: "Your file does not contain the required header rows. Add or check the header rows and upload your file again. [Read more about using the template headers](%{guidance_link})."
bulk_upload/sales/validator:
attributes:
base:
- blank_file: Template is blank - The template must be filled in for us to create the logs and check if data is correct.
- wrong_field_numbers_count: "Incorrect number of fields, please ensure you have used the correct template"
- over_max_column_count: "Too many columns, please ensure you have used the correct template"
- wrong_template: "Incorrect sale dates, please ensure you have used the correct template"
+ blank_file: "Template is blank - The template must be filled in for us to create the logs and check if data is correct."
+ wrong_field_numbers_count: "Incorrect number of fields, please ensure you have used the correct template."
+ over_max_column_count: "Too many columns, please ensure you have used the correct template."
+ wrong_template: "Incorrect sale dates, please ensure you have used the correct template."
no_headers: "Your file does not contain the required header rows. Add or check the header rows and upload your file again. [Read more about using the template headers](%{guidance_link})."
forms/bulk_upload_lettings/year:
attributes:
year:
- blank: You must select a collection period to upload for
+ blank: "You must select a collection period to upload for."
forms/bulk_upload_sales/year:
attributes:
year:
- blank: You must select a collection period to upload for
+ blank: "You must select a collection period to upload for."
forms/bulk_upload_lettings/upload_your_file:
attributes:
file:
- blank: Select which file to upload
- not_csv: Your file must be in CSV format
+ blank: "Select which file to upload."
+ not_csv: "Your file must be in CSV format."
forms/bulk_upload_sales/upload_your_file:
attributes:
file:
- blank: Select which file to upload
- not_csv: Your file must be in CSV format
+ blank: "Select which file to upload."
+ not_csv: "Your file must be in CSV format."
forms/bulk_upload_lettings/needstype:
attributes:
needstype:
- blank: You must answer needs type
+ blank: "You must answer needs type."
forms/bulk_upload_lettings_resume/fix_choice:
attributes:
choice:
- blank: Select how you would like to fix these errors
- inclusion: You must select one of the following options for how you would like to fix these errors
+ blank: "Select how you would like to fix these errors."
+ inclusion: "You must select one of the following options for how you would like to fix these errors."
forms/bulk_upload_sales_resume/fix_choice:
attributes:
choice:
- blank: Select how you would like to fix these errors
- inclusion: You must select one of the following options for how you would like to fix these errors
+ blank: "Select how you would like to fix these errors."
+ inclusion: "You must select one of the following options for how you would like to fix these errors."
forms/bulk_upload_lettings_soft_validations_check/confirm_soft_errors:
attributes:
confirm_soft_errors:
- blank: You must select if there are errors in these fields
+ blank: "You must select if there are errors in these fields."
forms/bulk_upload_sales_soft_validations_check/confirm_soft_errors:
attributes:
confirm_soft_errors:
- blank: You must select if there are errors in these fields
+ blank: "You must select if there are errors in these fields."
activerecord:
attributes:
user:
- email: email
+ email: "email"
errors:
models:
scheme:
attributes:
owning_organisation_id:
- invalid: "Enter the name of the organisation that owns the housing stock"
+ invalid: "Enter the name of the organisation that owns the housing stock."
service_name:
- invalid: "Enter the name of the scheme"
+ invalid: "Enter the name of the scheme."
scheme_type:
- invalid: "Select the type of scheme"
+ invalid: "Select the type of scheme."
registered_under_care_act:
- invalid: "Select if this scheme is registered under the Care Standards Act 2000"
+ invalid: "Select if this scheme is registered under the Care Standards Act 2000."
primary_client_group:
- invalid: "Select what client group this scheme is intended for"
+ invalid: "Select what client group this scheme is intended for."
secondary_client_group:
- invalid: "Select the other client group for this scheme"
+ invalid: "Select the other client group for this scheme."
support_type:
- invalid: "Select the level of support provided by this scheme"
+ invalid: "Select the level of support provided by this scheme."
intended_stay:
- invalid: "Select the intended length of stay"
+ invalid: "Select the intended length of stay."
has_other_client_group:
- invalid: "Select if this scheme provides for another client group"
+ invalid: "Select if this scheme provides for another client group."
arrangement_type:
- invalid: "Select who provides the support services used by this scheme"
+ invalid: "Select who provides the support services used by this scheme."
location:
attributes:
startdate:
- invalid: "Enter a date in the correct format, for example 31 1 2022"
+ invalid: "Enter a date in the correct format, for example 31 1 2022."
units:
- blank: "Enter the total number of units at this location"
+ blank: "Enter the total number of units at this location."
type_of_unit:
- blank: "Select the most common type of unit at this location"
+ blank: "Select the most common type of unit at this location."
mobility_type:
- blank: "Select the mobility standards for the majority of units in this location"
+ blank: "Select the mobility standards for the majority of units in this location."
user:
attributes:
organisation_id:
- blank: "Select the user’s organisation"
- invalid: "Select the user’s organisation"
+ blank: "Select the user’s organisation."
+ invalid: "Select the user’s organisation."
name:
- blank: "Enter a name"
+ blank: "Enter a name."
email:
- invalid: "Enter an email address in the correct format, like name@example.com"
- blank: "Enter an email address"
- taken: "Enter an email address that hasn’t already been used to sign up"
+ invalid: "Enter an email address in the correct format, like name@example.com."
+ blank: "Enter an email address."
+ taken: "Enter an email address that hasn’t already been used to sign up."
phone:
- invalid: "Enter a telephone number in the correct format"
- blank: "Enter a telephone number"
+ invalid: "Enter a telephone number in the correct format."
+ blank: "Enter a telephone number."
role:
- invalid: "Role must be data accessor, data provider or data coordinator"
- blank: "Select role"
+ invalid: "Role must be data accessor, data provider or data coordinator."
+ blank: "Select role."
password:
- blank: Enter a password
- too_short: The password you entered is too short. Enter a password that is %{count} characters or longer.
+ blank: "Enter a password."
+ too_short: "The password you entered is too short. Enter a password that is %{count} characters or longer."
reset_password_token:
invalid: "That link is invalid. Check you are using the correct link."
log_reassignment:
@@ -184,20 +184,20 @@ en:
merge_request:
attributes:
absorbing_organisation_id:
- blank: "Select the absorbing organisation"
+ blank: "Select the absorbing organisation."
merge_date:
- blank: "Enter a merge date"
- invalid: "Enter a valid merge date"
+ blank: "Enter a merge date."
+ invalid: "Enter a valid merge date."
existing_absorbing_organisation:
blank: "You must answer absorbing organisation already active?"
notification:
attributes:
title:
- blank: "Enter a title"
+ blank: "Enter a title."
link_text:
- blank_when_additional_page_set: "Enter the link text"
+ blank_when_additional_page_set: "Enter the link text."
page_content:
- blank_when_additional_page_set: "Enter the page content"
+ blank_when_additional_page_set: "Enter the page content."
notification:
logs_deleted:
@@ -209,8 +209,8 @@ en:
duplicate_logs:
deduplication_success_banner: "%{log_link} is no longer a duplicate and has been removed from the list.
You changed the %{changed_question_label}.
"
duplicate_sets:
- one: "There is %{count} set of duplicate logs"
- other: "There are %{count} sets of duplicate logs"
+ one: "There is %{count} set of duplicate logs."
+ other: "There are %{count} sets of duplicate logs."
location_deleted: "%{postcode} has been deleted."
scheme_deleted: "%{service_name} has been deleted."
user_deleted: "%{name} has been deleted."
@@ -218,72 +218,72 @@ en:
validations:
organisation:
- data_sharing_agreement_not_signed: Your organisation must accept the Data Sharing Agreement before you can create any logs.
- name_missing: "Enter the name of the organisation"
- provider_type_missing: "Select the organisation type"
+ data_sharing_agreement_not_signed: "Your organisation must accept the Data Sharing Agreement before you can create any logs."
+ name_missing: "Enter the name of the organisation."
+ provider_type_missing: "Select the organisation type."
stock_owner:
- blank: "You must choose a stock owner"
- already_added: "You have already added this stock owner"
+ blank: "You must choose a stock owner."
+ already_added: "You have already added this stock owner."
does_not_own_stock: "You can only add stock owners who own stock, which this organisation does not."
managing_agent:
- blank: "You must choose a managing agent"
- already_added: "You have already added this managing agent"
+ blank: "You must choose a managing agent."
+ already_added: "You have already added this managing agent."
merged: "That organisation has already been merged. Select a different organisation."
not_answered: "You must answer %{question}"
invalid_option: "Enter a valid value for %{question}"
invalid_number: "Enter a number for %{question}"
no_address_found: "We could not find this address. Check the address data in your CSV file is correct and complete, or select the correct address using the CORE site."
- other_field_missing: "If %{main_field_label} is other then %{other_field_label} must be provided"
- other_field_not_required: "%{other_field_label} must not be provided if %{main_field_label} was not other"
+ other_field_missing: "If %{main_field_label} is other then %{other_field_label} must be provided."
+ other_field_not_required: "%{other_field_label} must not be provided if %{main_field_label} was not other."
numeric:
- within_range: "%{field} must be between %{min} and %{max}"
- above_min: "%{field} must be at least %{min}"
- whole_number: "%{field} must be a whole number"
- nearest_ten: "%{field} must be given to the nearest ten"
- nearest_hundredth: "%{field} must be given to the nearest hundredth"
- normal_format: "Enter a number"
- format: "%{field} must be a number"
+ within_range: "%{field} must be between %{min} and %{max}."
+ above_min: "%{field} must be at least %{min}."
+ whole_number: "%{field} must be a whole number."
+ nearest_ten: "%{field} must be given to the nearest ten."
+ nearest_hundredth: "%{field} must be given to the nearest hundredth."
+ normal_format: "Enter a number."
+ format: "%{field} must be a number."
date:
- invalid_date: "Enter a date in the correct format, for example 31 1 2022"
- outside_collection_window: Enter a date within the %{year_combo} collection year, which is between 1st April %{start_year} and 31st March %{end_year}
- postcode: "Enter a postcode in the correct format, for example AA1 1AA"
- location_admin_district: "Select a local authority"
+ invalid_date: "Enter a date in the correct format, for example 31 1 2024."
+ outside_collection_window: "Enter a date within the %{year_combo} collection year, which is between 1st April %{start_year} and 31st March %{end_year}."
+ postcode: "Enter a postcode in the correct format, for example AA1 1AA."
+ location_admin_district: "Select a local authority."
email:
- taken: "Enter an email address that hasn’t already been used to sign up"
- invalid: "Enter an email address in the correct format, like name@example.com"
- blank: "Enter an email address"
+ taken: "Enter an email address that hasn’t already been used to sign up."
+ invalid: "Enter an email address in the correct format, like name@example.com."
+ blank: "Enter an email address."
role:
- invalid: "Role must be data accessor, data provider or data coordinator"
+ invalid: "Role must be data accessor, data provider or data coordinator."
setup:
intermediate_rent_product_name:
- blank: "Enter name of other intermediate rent product"
+ blank: "Enter name of other intermediate rent product."
saledate:
- later_than_14_days_after: "Sale completion date must not be later than 14 days from today’s date"
+ later_than_14_days_after: "Sale completion date must not be later than 14 days from today’s date."
current_collection_year:
- Enter a date within the %{current_start_year_short}/%{current_end_year_short} collection year, which is between %{current_start_year_long} and %{current_end_year_long}
+ "Enter a date within the %{current_start_year_short}/%{current_end_year_short} collection year, which is between %{current_start_year_long} and %{current_end_year_long}."
previous_and_current_collection_year:
- "Enter a date within the %{previous_start_year_short}/%{previous_end_year_short} or %{previous_end_year_short}/%{current_end_year_short} collection years, which is between %{previous_start_year_long} and %{current_end_year_long}"
- year_not_two_digits: "Sale completion year must be 2 digits"
+ "Enter a date within the %{previous_start_year_short}/%{previous_end_year_short} or %{previous_end_year_short}/%{current_end_year_short} collection years, which is between %{previous_start_year_long} and %{current_end_year_long}."
+ year_not_two_digits: "Sale completion year must be 2 digits."
invalid_merged_organisations_saledate: "Enter a date when the owning organisation was active. %{owning_organisation} became inactive on %{owning_organisation_merge_date} and was replaced by %{owning_absorbing_organisation}."
invalid_absorbing_organisations_saledate: "Enter a date when the owning organisation was active. %{owning_organisation} became active on %{owning_organisation_available_from}."
type:
- percentage_bought_must_be_at_least_threshold: "The minimum increase in equity while staircasing is %{threshold}% for this shared ownership type"
+ percentage_bought_must_be_at_least_threshold: "The minimum increase in equity while staircasing is %{threshold}% for this shared ownership type."
startdate:
current_collection_year:
- Enter a date within the %{current_start_year_short}/%{current_end_year_short} collection year, which is between %{current_start_year_long} and %{current_end_year_long}
+ "Enter a date within the %{current_start_year_short}/%{current_end_year_short} collection year, which is between %{current_start_year_long} and %{current_end_year_long}."
previous_and_current_collection_year:
- "Enter a date within the %{previous_start_year_short}/%{previous_end_year_short} or %{previous_end_year_short}/%{current_end_year_short} collection years, which is between %{previous_start_year_long} and %{current_end_year_long}"
- later_than_14_days_after: "The tenancy start date must not be later than 14 days from today’s date"
- before_scheme_end_date: "The tenancy start date must be before the end date for this supported housing scheme"
- after_void_date: "Enter a tenancy start date that is after the void date"
- after_major_repair_date: "Enter a tenancy start date that is after the major repair date"
- year_not_two_digits: Tenancy start year must be 2 digits
- ten_years_after_void_date: "Enter a tenancy start date that is no more than 10 years after the void date"
- ten_years_after_mrc_date: "Enter a tenancy start date that is no more than 10 years after the major repairs completion date"
+ "Enter a date within the %{previous_start_year_short}/%{previous_end_year_short} or %{previous_end_year_short}/%{current_end_year_short} collection years, which is between %{previous_start_year_long} and %{current_end_year_long}."
+ later_than_14_days_after: "The tenancy start date must not be later than 14 days from today’s date."
+ before_scheme_end_date: "The tenancy start date must be before the end date for this supported housing scheme."
+ after_void_date: "Enter a tenancy start date that is after the void date."
+ after_major_repair_date: "Enter a tenancy start date that is after the major repair date."
+ year_not_two_digits: "Tenancy start year must be 2 digits."
+ ten_years_after_void_date: "Enter a tenancy start date that is no more than 10 years after the void date."
+ ten_years_after_mrc_date: "Enter a tenancy start date that is no more than 10 years after the major repairs completion date."
invalid_merged_organisations_start_date:
same_organisation: "Enter a date when the owning and managing organisation was active. %{owning_organisation} became inactive on %{owning_organisation_merge_date} and was replaced by %{owning_absorbing_organisation}."
same_merge: "Enter a date when the owning and managing organisations were active. %{owning_organisation} and %{managing_organisation} became inactive on %{owning_organisation_merge_date} and were replaced by %{owning_absorbing_organisation}."
@@ -301,110 +301,110 @@ en:
startdate: "The location %{postcode} is inactive on this date. Enter another date or choose another location."
location_id: "This location is not active on the tenancy start date. Choose another location or edit the tenancy start date."
activating_soon:
- startdate: "The location %{postcode} is not available until %{date}. Enter a tenancy start date after %{date}"
- location_id: "The location %{postcode} is not available until %{date}. Select another location or edit the tenancy start date"
+ startdate: "The location %{postcode} is not available until %{date}. Enter a tenancy start date after %{date}."
+ location_id: "The location %{postcode} is not available until %{date}. Select another location or edit the tenancy start date."
reactivating_soon:
- startdate: "The location %{postcode} is not available until %{date}. Enter a tenancy start date after %{date}"
- location_id: "The location %{postcode} is not available until %{date}. Select another location or edit the tenancy start date"
+ startdate: "The location %{postcode} is not available until %{date}. Enter a tenancy start date after %{date}."
+ location_id: "The location %{postcode} is not available until %{date}. Select another location or edit the tenancy start date."
scheme:
deactivated:
- startdate: "The scheme %{name} was deactivated on %{date} and was not available on the day you entered. Select another scheme or edit the tenancy start date"
- scheme_id: "The scheme %{name} was deactivated on %{date} and was not available on the day you entered. Select another scheme or edit the tenancy start date"
+ startdate: "The scheme %{name} was deactivated on %{date} and was not available on the day you entered. Select another scheme or edit the tenancy start date."
+ scheme_id: "The scheme %{name} was deactivated on %{date} and was not available on the day you entered. Select another scheme or edit the tenancy start date."
reactivating_soon:
- startdate: "The scheme %{name} is not available until %{date}. Enter a tenancy start date after %{date}"
- scheme_id: "The scheme %{name} is not available until %{date}. Select another scheme or edit the tenancy start date"
+ startdate: "The scheme %{name} is not available until %{date}. Enter a tenancy start date after %{date}."
+ scheme_id: "The scheme %{name} is not available until %{date}. Select another scheme or edit the tenancy start date."
locations_inactive:
startdate: "The scheme %{name} has no locations that are active on this date. Enter another date or choose another scheme."
scheme_id: "The scheme %{name} has no locations that are active on this date. Enter another date or choose another scheme."
owning_organisation:
- invalid: "Please select the owning organisation or managing organisation that you belong to"
+ invalid: "Please select the owning organisation or managing organisation that you belong to."
data_sharing_agreement_not_signed: "The organisation must accept the Data Sharing Agreement before it can be selected as the owning organisation."
inactive_merged_organisation: "The owning organisation must be active on the tenancy start date. %{owning_organisation} became inactive on %{owning_organisation_merge_date} and was replaced by %{owning_absorbing_organisation}."
inactive_absorbing_organisation: "The owning organisation must be active on the tenancy start date. %{owning_organisation} became active on %{owning_organisation_available_from}."
inactive_merged_organisation_sales: "The owning organisation must be active on the sale completion date. %{owning_organisation} became inactive on %{owning_organisation_merge_date} and was replaced by %{owning_absorbing_organisation}."
inactive_absorbing_organisation_sales: "The owning organisation must be active on the sale completion date. %{owning_organisation} became active on %{owning_organisation_available_from}."
managing_organisation:
- invalid: "Please select the owning organisation or managing organisation that you belong to"
+ invalid: "Please select the owning organisation or managing organisation that you belong to."
data_sharing_agreement_not_signed: "The organisation must accept the Data Sharing Agreement before it can be selected as the managing organisation."
inactive_merged_organisation: "The managing organisation must be active on the tenancy start date. %{managing_organisation} became inactive on %{managing_organisation_merge_date} and was replaced by %{managing_absorbing_organisation}."
inactive_absorbing_organisation: "The managing organisation must be active on the tenancy start date. %{managing_organisation} became active on %{managing_organisation_available_from}."
assigned_to:
- invalid: "Please select the owning organisation or managing organisation that you belong to"
+ invalid: "Please select the owning organisation or managing organisation that you belong to."
lettype:
- general_needs_mismatch: Lettings type must be a general needs type because you selected general needs when uploading the file
- supported_housing_mismatch: Lettings type must be a supported housing type because you selected supported housing when uploading the file
- needstype_general_needs: This needs type is general needs, but the letting type is supported housing. Change either the needs type or the letting type.
- needstype_supported_housing: This needs type is supported housing, but the letting type is general needs. Change either the needs type or the letting type.
+ general_needs_mismatch: "Lettings type must be a general needs type because you selected general needs when uploading the file."
+ supported_housing_mismatch: "Lettings type must be a supported housing type because you selected supported housing when uploading the file."
+ needstype_general_needs: "This needs type is general needs, but the letting type is supported housing. Change either the needs type or the letting type."
+ needstype_supported_housing: "This needs type is supported housing, but the letting type is general needs. Change either the needs type or the letting type."
needstype:
- lettype_not_general_needs: This letting type is supported housing, but the needs type is general needs. Change either the needs type or the letting type.
- lettype_not_supported_housing: This letting type is general needs, but the needs type is supported housing. Change either the needs type or the letting type.
+ lettype_not_general_needs: "This letting type is supported housing, but the needs type is general needs. Change either the needs type or the letting type."
+ lettype_not_supported_housing: "This letting type is general needs, but the needs type is supported housing. Change either the needs type or the letting type."
location:
- incomplete: "This location is incomplete. Select another location or update this one"
+ incomplete: "This location is incomplete. Select another location or update this one."
scheme:
- incomplete: "This scheme is incomplete. Select another scheme or update this one"
+ incomplete: "This scheme is incomplete. Select another scheme or update this one."
property:
uprn:
- invalid: "UPRN must be 12 digits or less"
+ invalid: "UPRN must be 12 digits or less."
uprn_known:
invalid: "You must answer UPRN known?"
mrcdate:
- before_tenancy_start: "Enter a major repairs date that is before the tenancy start date"
- not_first_let: "Major repairs date must not be completed if the tenancy is a first let"
- ten_years_before_tenancy_start: "Enter a major repairs completion date that is no more than 10 years before the tenancy start date"
- before_void_date: "Major repairs date must be after the void date if provided"
+ before_tenancy_start: "Enter a major repairs date that is before the tenancy start date."
+ not_first_let: "Major repairs date must not be completed if the tenancy is a first let."
+ ten_years_before_tenancy_start: "Enter a major repairs completion date that is no more than 10 years before the tenancy start date."
+ before_void_date: "Major repairs date must be after the void date if provided."
void_date:
- ten_years_before_tenancy_start: "Enter a void date no more than 10 years before the tenancy start date"
- before_tenancy_start: "Enter a void date that is before the tenancy start date"
- after_mrcdate: "Void date must be before the major repairs date if provided"
+ ten_years_before_tenancy_start: "Enter a void date no more than 10 years before the tenancy start date."
+ before_tenancy_start: "Enter a void date that is before the tenancy start date."
+ after_mrcdate: "Void date must be before the major repairs date if provided."
la:
- la_invalid_for_org: "%{org_name} does not operate in %{la_name}"
- postcode_invalid_for_org: "Enter a postcode in an area covered by %{org_name}"
+ la_invalid_for_org: "%{org_name} does not operate in %{la_name}."
+ postcode_invalid_for_org: "Enter a postcode in an area covered by %{org_name}."
rsnvac:
- first_let_not_social: "Enter a reason for vacancy that is not 'first let' if unit has been previously let as social housing"
- first_let_social: "Reason for vacancy must be first let if unit has been previously let as social housing"
- previous_let_social: "Property cannot have a previous let type if being let as social housing for the first time"
- non_temp_accommodation: "Answer cannot be re-let to tenant who occupied the same property as temporary accommodation as this accommodation is not temporary"
- referral_invalid: "Answer cannot be re-let to tenant who occupied the same property as temporary accommodation as a different source of referral for this letting"
- not_a_renewal: "Reason for vacancy cannot be 'Renewal of fixed-term tenancy' if letting is not a renewal"
+ first_let_not_social: "Enter a reason for vacancy that is not 'first let' if unit has been previously let as social housing."
+ first_let_social: "Reason for vacancy must be first let if unit has been previously let as social housing."
+ previous_let_social: "Property cannot have a previous let type if being let as social housing for the first time."
+ non_temp_accommodation: "Answer cannot be re-let to tenant who occupied the same property as temporary accommodation as this accommodation is not temporary."
+ referral_invalid: "Answer cannot be re-let to tenant who occupied the same property as temporary accommodation as a different source of referral for this letting."
+ not_a_renewal: "Reason for vacancy cannot be 'Renewal of fixed-term tenancy' if letting is not a renewal."
unittype_gn:
- one_bedroom_bedsit: "A bedsit can only have one bedroom"
- one_seven_bedroom_shared: "A shared house must have 1 to 7 bedrooms"
- one_three_bedroom_single_tenant_shared: "A shared house with fewer than two tenants must have 1 to 3 bedrooms"
+ one_bedroom_bedsit: "A bedsit can only have one bedroom."
+ one_seven_bedroom_shared: "A shared house must have 1 to 7 bedrooms."
+ one_three_bedroom_single_tenant_shared: "A shared house with fewer than two tenants must have 1 to 3 bedrooms."
beds:
- bedsits_have_max_one_bedroom: "Number of bedrooms must be 1 if the property is a bedsit"
+ bedsits_have_max_one_bedroom: "Number of bedrooms must be 1 if the property is a bedsit."
proptype:
- bedsits_have_max_one_bedroom: "Answer cannot be 'Bedsit' if the property has 2 or more bedrooms"
+ bedsits_have_max_one_bedroom: "Answer cannot be 'Bedsit' if the property has 2 or more bedrooms."
postcode:
- must_match_previous: "%{buyer_possessive} last accommodation and discounted ownership postcodes must match"
+ must_match_previous: "%{buyer_possessive} last accommodation and discounted ownership postcodes must match."
financial:
tshortfall:
- outstanding_amount_not_expected: "You cannot answer the outstanding amount question if you don’t have outstanding rent or charges"
- more_than_total_charge: "Enter a value less than the total charge"
- must_be_positive: "Enter a value over £0.01 as you told us there is an outstanding amount"
+ outstanding_amount_not_expected: "You cannot answer the outstanding amount question if you don’t have outstanding rent or charges."
+ more_than_total_charge: "Enter a value less than the total charge."
+ must_be_positive: "Enter a value over £0.01 as you told us there is an outstanding amount."
hbrentshortfall:
- outstanding_amount_not_expected: "Answer must be ‘yes’ as you have answered the outstanding amount question"
- outstanding_no_benefits: "Answer cannot be ‘yes’ to outstanding amount for basic rent or charges if tenant does not receive housing benefit or Universal Credit or you‘re not sure"
+ outstanding_amount_not_expected: "Answer must be ‘yes’ as you have answered the outstanding amount question."
+ outstanding_no_benefits: "Answer cannot be ‘yes’ to outstanding amount for basic rent or charges if tenant does not receive housing benefit or Universal Credit or you‘re not sure."
benefits:
- part_or_full_time: "Answer cannot be ‘all’ for income from Universal Credit, state pensions or benefits if the tenant or their partner works part-time or full-time"
+ part_or_full_time: "Answer cannot be ‘all’ for income from Universal Credit, state pensions or benefits if the tenant or their partner works part-time or full-time."
earnings:
- over_hard_max: "The household’s income cannot be greater than %{hard_max} per week given the household’s working situation"
- under_hard_min: "The household’s income cannot be less than %{hard_min} per week given the household’s working situation"
- freq_missing: "Select how often the household receives income"
- earnings_missing: "Enter how much income the household has in total"
+ over_hard_max: "The household’s income cannot be greater than %{hard_max} per week given the household’s working situation."
+ under_hard_min: "The household’s income cannot be less than %{hard_min} per week given the household’s working situation."
+ freq_missing: "Select how often the household receives income."
+ earnings_missing: "Enter how much income the household has in total."
income:
- outside_london_income_range: "Income must be between £0 and £90,000 for properties within a London local authority"
- outside_non_london_income_range: "Income must be between £0 and £80,000 for properties in a non-London local authority"
- combined_over_hard_max_for_london: "Combined income must be £90,000 or lower for properties within a London local authority"
- combined_over_hard_max_for_outside_london: "Combined income must be £80,000 or lower for properties outside London local authorities"
- child_has_income: "Child's income must be £0"
- negative_currency: "Enter an amount above 0"
+ outside_london_income_range: "Income must be between £0 and £90,000 for properties within a London local authority."
+ outside_non_london_income_range: "Income must be between £0 and £80,000 for properties in a non-London local authority."
+ combined_over_hard_max_for_london: "Combined income must be £90,000 or lower for properties within a London local authority."
+ combined_over_hard_max_for_outside_london: "Combined income must be £80,000 or lower for properties outside London local authorities."
+ child_has_income: "Child's income must be £0."
+ negative_currency: "Enter an amount above 0."
rent:
out_of_range: "Enter a value for the %{charge_name} between £0 and %{maximum_per_period} paid %{frequency}. %{maximum_per_period} is the max limit for rent and charges paid %{frequency} for %{letting_type} lettings owned by a %{provider_type}."
ecstat:
- over_hard_max: "The household’s income of %{earnings} %{frequency} is too high given the household’s working situation"
- under_hard_min: "The household’s income of %{earnings} %{frequency} is too low given the household’s working situation"
+ over_hard_max: "The household’s income of %{earnings} %{frequency} is too high given the household’s working situation."
+ under_hard_min: "The household’s income of %{earnings} %{frequency} is too low given the household’s working situation."
age:
earnings_over_hard_max: "The household’s income of %{earnings} %{frequency} is too high for the number of adults. Change either the household income or the age of the tenants."
hhmemb:
@@ -412,159 +412,158 @@ en:
over_hard_max: "The household’s income of %{earnings} %{frequency} is too high for this number of tenants. Change either the household income or number of tenants."
under_hard_min: "The household’s income of %{earnings} %{frequency} is too low for this number of tenants. Change either the household income or number of tenants."
brent:
- below_hard_min: "Rent is below the absolute minimum expected for a property of this type. Please check the rent, rent period, local authority and (if general needs) number of bedrooms"
- above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type. Please check the rent, rent period, local authority and (if general needs) number of bedrooms"
+ below_hard_min: "Rent is below the absolute minimum expected for a property of this type. Please check the rent, rent period, local authority and (if general needs) number of bedrooms."
+ above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type. Please check the rent, rent period, local authority and (if general needs) number of bedrooms."
scheme_id:
- below_hard_min: "Rent is below the absolute minimum expected for a property of this type. Please check the rent, rent period and local authority"
- above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type. Please check the rent, rent period and local authority"
+ below_hard_min: "Rent is below the absolute minimum expected for a property of this type. Please check the rent, rent period and local authority."
+ above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type. Please check the rent, rent period and local authority."
location_id:
- below_hard_min: "Rent is below the absolute minimum expected for a property of this type. Please check the rent, rent period and local authority"
- above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type. Please check the rent, rent period and local authority"
+ below_hard_min: "Rent is below the absolute minimum expected for a property of this type. Please check the rent, rent period and local authority."
+ above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type. Please check the rent, rent period and local authority."
postcode_known:
- below_hard_min: "Rent is below the absolute minimum expected for a property of this type. Please check the rent, rent period, local authority and number of bedrooms"
- above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type. Please check the rent, rent period, local authority and number of bedrooms"
+ below_hard_min: "Rent is below the absolute minimum expected for a property of this type. Please check the rent, rent period, local authority and number of bedrooms."
+ above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type. Please check the rent, rent period, local authority and number of bedrooms."
uprn:
- below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this UPRN"
- above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this UPRN"
+ below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this UPRN."
+ above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this UPRN."
la:
- below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this local authority"
- above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this local authority"
+ below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this local authority."
+ above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this local authority."
beds:
- below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this number of bedrooms"
- above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this number of bedrooms"
+ below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this number of bedrooms."
+ above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this number of bedrooms."
needstype:
- below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this lettings type"
- above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this lettings type"
+ below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this lettings type."
+ above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this lettings type."
rent_type:
- below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this lettings type"
- above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this lettings type"
+ below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this lettings type."
+ above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this lettings type."
period:
- below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this period"
- above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this period"
+ below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this period."
+ above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this period."
charges:
complete_1_of_3: "Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?’"
missing_charges: "Please enter the %{question}. If there is no %{question}, please enter '0'."
tcharge:
- under_10: "Enter a total charge that is at least £10.00 per week"
- less_than_shortfall: "The total charge must be more than the outstanding amount"
+ under_10: "Enter a total charge that is at least £10.00 per week."
+ less_than_shortfall: "The total charge must be more than the outstanding amount."
rent_period:
invalid_for_org:
- period: "%{org_name} does not use %{rent_period} as a rent period. Choose another rent period, or a data coordinator can add rent periods to your organisation"
- managing_org: "%{org_name} does not use %{rent_period} as a rent period. Set another rent period on this log, or a data coordinator can add rent periods to this organisation"
+ period: "%{org_name} does not use %{rent_period} as a rent period. Choose another rent period, or a data coordinator can add rent periods to your organisation."
+ managing_org: "%{org_name} does not use %{rent_period} as a rent period. Set another rent period on this log, or a data coordinator can add rent periods to this organisation."
carehome:
- out_of_range: "Household rent and other charges must be between %{min_chcharge} and %{max_chcharge} if paying %{period}"
- not_provided: "Enter how much rent and other charges the household pays %{period}"
- cash_discount_invalid: "Cash discount must be £0 - £999,999"
+ out_of_range: "Household rent and other charges must be between %{min_chcharge} and %{max_chcharge} if paying %{period}."
+ not_provided: "Enter how much rent and other charges the household pays %{period}."
+ cash_discount_invalid: "Cash discount must be £0 - £999,999."
staircasing:
- percentage_bought_must_be_greater_than_percentage_owned: "Total percentage %{buyer_now_owns} must be more than percentage bought in this transaction"
- percentage_bought_must_be_at_least_threshold: "The minimum increase in equity while staircasing is %{threshold}%"
+ percentage_bought_must_be_greater_than_percentage_owned: "Total percentage %{buyer_now_owns} must be more than percentage bought in this transaction."
+ percentage_bought_must_be_at_least_threshold: "The minimum increase in equity while staircasing is %{threshold}%."
percentage_bought_equal_percentage_owned: "The percentage bought is %{stairbought}% and the percentage owned in total is %{stairowned}%. These figures cannot be the same."
monthly_leasehold_charges:
- not_zero: "Monthly leasehold charges cannot be £0 if the property has monthly charges"
+ not_zero: "Monthly leasehold charges cannot be £0 if the property has monthly charges."
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}%"
+ 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"
+ mortgage: "Mortgage value cannot be £0 if a mortgage was used for the purchase of this property."
mortgage_used:
year: "You must answer either ‘yes’ or ‘no’ to the question ‘was a mortgage used’ for the selected year."
staircasing: "You must answer either ‘yes’ or ‘no’ to the question ‘was a mortgage used’ for staircasing transactions."
- 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"
+ 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."
household:
reasonable_preference_reason:
- reason_required: "Enter a reason if you've answered 'yes' to reasonable preference"
- reason_not_required: "Do not enter a reason if you've answered 'no' to reasonable preference"
+ reason_required: "Enter a reason if you've answered 'yes' to reasonable preference."
+ reason_not_required: "Do not enter a reason if you've answered 'no' to reasonable preference."
underoccupation_benefitcap:
- dont_know_required: "Answer must be ‘don’t know’ as you told us you don’t know the tenant’s main reason for leaving"
+ dont_know_required: "Answer must be ‘don’t know’ as you told us you don’t know the tenant’s main reason for leaving."
reservist:
- injury_required: "Tell us whether the person was seriously injured or ill as a result of serving in the UK armed forces"
- injury_not_required: "You cannot answer this question as you told us the person has not served in the UK armed forces or prefers not to say"
+ injury_required: "Tell us whether the person was seriously injured or ill as a result of serving in the UK armed forces."
+ injury_not_required: "You cannot answer this question as you told us the person has not served in the UK armed forces or prefers not to say."
leftreg:
- question_required: "Tell us whether the person is still serving in the UK armed forces as you told us they’re a current or former regular"
- question_not_required: "You cannot answer whether the person is still serving in the UK armed forces as you told us they’re not a current or former regular"
+ question_required: "Tell us whether the person is still serving in the UK armed forces as you told us they’re a current or former regular."
+ question_not_required: "You cannot answer whether the person is still serving in the UK armed forces as you told us they’re not a current or former regular."
age:
- retired_male: "A male tenant who is retired must be 65 or over"
- retired_female: "A female tenant who is retired must be 60 or over"
- retired_over_70: "Answer cannot be over 70 as person %{person_num} has economic status that is not ‘retired’"
- child_under_16_relat_lettings: "Answer cannot be under 16 as person %{person_num}'s relationship to the lead tenant is ‘partner’"
- child_under_16_relat_sales: "Answer cannot be under 16 as person %{person_num}'s relationship to buyer 1 is ‘partner’"
- child_under_16_ecstat: "Answer cannot be under 16 as person %{person_num}’s working situation is not ‘child under 16’, ‘other’ or ‘prefers not to say’"
- child_over_16: "Answer cannot be over 16 as person’s %{person_num} working situation is ‘child under 16‘"
- child_over_20: "Answer cannot be 20 or over as the relationship is ‘child’"
- child_12_years_younger: "A child must be at least 12 years younger than their parent"
- not_student_16_19: "Answer cannot be between 16 and 19 as person %{person_num} is a child of the lead tenant but is not a full-time student"
+ retired_male: "A male tenant who is retired must be 65 or over."
+ retired_female: "A female tenant who is retired must be 60 or over."
+ retired_over_70: "Answer cannot be over 70 as person %{person_num} has economic status that is not ‘retired’."
+ child_under_16_relat_lettings: "Answer cannot be under 16 as person %{person_num}'s relationship to the lead tenant is ‘partner’."
+ child_under_16_relat_sales: "Answer cannot be under 16 as person %{person_num}'s relationship to buyer 1 is ‘partner’."
+ child_under_16_ecstat: "Answer cannot be under 16 as person %{person_num}’s working situation is not ‘child under 16’, ‘other’ or ‘prefers not to say’."
+ child_over_16: "Answer cannot be over 16 as person’s %{person_num} working situation is ‘child under 16‘."
+ child_over_20: "Answer cannot be 20 or over as the relationship is ‘child’."
+ child_12_years_younger: "A child must be at least 12 years younger than their parent."
+ not_student_16_19: "Answer cannot be between 16 and 19 as person %{person_num} is a child of the lead tenant but is not a full-time student."
student_16_19:
cannot_be_16_19:
- child_not_student: "Person cannot be aged 16-19 if they have relationship ‘child’ but are not a student"
- must_be_16_19: "Person must be aged 16-19 if they are a student and have relationship ‘child’"
+ child_not_student: "Person cannot be aged 16-19 if they have relationship ‘child’ but are not a student."
+ must_be_16_19: "Person must be aged 16-19 if they are a student and have relationship ‘child’."
lead:
- over_25: "The lead tenant must be under 26 as you told us their housing situation immediately before this letting was a children’s home or foster care"
+ over_25: "The lead tenant must be under 26 as you told us their housing situation immediately before this letting was a children’s home or foster care."
student_not_child:
- cannot_be_16_19: "Person cannot be aged 16-19 if they are a student but not a child"
+ cannot_be_16_19: "Person cannot be aged 16-19 if they are a student but not a child."
ecstat:
- retired_over_70: "Person %{person_num} must be retired if over 70"
- child_under_16: "Person %{person_num}’s working situation must be ‘child under 16’, ‘other’ or ‘prefers not to say’ as you told us they’re under 16"
- child_over_16: "Answer cannot be ‘child under 16’ as you told us the person %{person_num} is older than 16"
+ retired_over_70: "Person %{person_num} must be retired if over 70."
+ child_under_16: "Person %{person_num}’s working situation must be ‘child under 16’, ‘other’ or ‘prefers not to say’ as you told us they’re under 16."
+ child_over_16: "Answer cannot be ‘child under 16’ as you told us the person %{person_num} is older than 16."
not_student_16_19: "Person’s %{person_num} working situation must be full-time student or prefers not to say as you told us they’re between 16 and 19."
student_16_19:
cannot_be_student:
- child_not_16_19: "Person cannot be a student if they are not aged 16-19 but have relationship ‘child’"
- must_be_student: "Person must be a student if they are aged 16-19 and have relationship ‘child’"
- retired_male: "Answer cannot be ‘retired’ as the male tenant is under 65"
- retired_female: "Answer cannot be ‘retired’ as the female tenant is under 60"
+ child_not_16_19: "Person cannot be a student if they are not aged 16-19 but have relationship ‘child’."
+ must_be_student: "Person must be a student if they are aged 16-19 and have relationship ‘child’."
+ retired_male: "Answer cannot be ‘retired’ as the male tenant is under 65."
+ retired_female: "Answer cannot be ‘retired’ as the female tenant is under 60."
not_child_16_19:
- cannot_be_student: "Person cannot be a student if they are aged 16-19 but are not a child"
- buyer_cannot_be_child: "Buyer %{buyer_index} cannot have a working situation of child under 16"
- buyer_cannot_be_over_16_and_child: "Buyer %{buyer_index}'s age cannot be 16 or over if their working situation is child under 16"
+ cannot_be_student: "Person cannot be a student if they are aged 16-19 but are not a child."
+ buyer_cannot_be_child: "Buyer %{buyer_index} cannot have a working situation of child under 16."
+ buyer_cannot_be_over_16_and_child: "Buyer %{buyer_index}'s age cannot be 16 or over if their working situation is child under 16."
relat:
- child_under_16_sales: "Answer cannot be ‘partner’ as you told us person %{person_num}'s age is under 16"
- child_under_16_lettings: "Answer cannot be ‘partner’ as you told us person %{person_num}'s age is under 16"
- child_over_20: "Answer cannot be ‘child’ if the person's age is 20 or over"
- one_partner: "Number of partners cannot be greater than 1"
- not_student_16_19: "Answer cannot be ‘child’ as you told us the person %{person_num} is between 16 and 19 and is not a full-time student"
+ child_under_16_sales: "Answer cannot be ‘partner’ as you told us person %{person_num}'s age is under 16."
+ child_under_16_lettings: "Answer cannot be ‘partner’ as you told us person %{person_num}'s age is under 16."
+ child_over_20: "Answer cannot be ‘child’ if the person's age is 20 or over."
+ one_partner: "Number of partners cannot be greater than 1."
+ not_student_16_19: "Answer cannot be ‘child’ as you told us the person %{person_num} is between 16 and 19 and is not a full-time student."
student_16_19:
cannot_be_child:
- student_not_16_19: "Answer cannot be ‘child’ if the person is a student but not aged 16-19"
- 16_19_not_student: "Answer cannot be ‘child’ if the person is aged 16-19 but not a student"
- child_over_19: "Answer cannot be child as you told us person %{person_num} is over 19"
+ student_not_16_19: "Answer cannot be ‘child’ if the person is a student but not aged 16-19."
+ 16_19_not_student: "Answer cannot be ‘child’ if the person is aged 16-19 but not a student."
+ child_over_19: "Answer cannot be child as you told us person %{person_num} is over 19."
housingneeds_a:
- one_or_two_choices: "You can only select one option or ‘other disabled access needs’ plus ‘wheelchair-accessible housing’, ‘wheelchair access to essential rooms’ or ‘level access housing’"
+ one_or_two_choices: "You can only select one option or ‘other disabled access needs’ plus ‘wheelchair-accessible housing’, ‘wheelchair access to essential rooms’ or ‘level access housing’."
housingneeds_type:
- only_one_option_permitted: "Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected"
+ only_one_option_permitted: "Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected."
housingneeds:
- invalid:
- If somebody in the household has disabled access needs, they must have the access needs listed, or other access needs
- no_disabled_needs_conjunction: "No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs"
- dont_know_disabled_needs_conjunction: "Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs"
- no_and_dont_know_disabled_needs_conjunction: "No disabled access needs and don’t know disabled access needs cannot be selected together"
+ invalid: "If somebody in the household has disabled access needs, they must have the access needs listed, or other access needs."
+ no_disabled_needs_conjunction: "No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs."
+ dont_know_disabled_needs_conjunction: "Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs."
+ no_and_dont_know_disabled_needs_conjunction: "No disabled access needs and don’t know disabled access needs cannot be selected together."
prevten:
- non_temp_accommodation: "Answer cannot be non-temporary accommodation as this is a re-let to a tenant who occupied the same property as temporary accommodation"
- over_25_foster_care: "Answer cannot be a children’s home or foster care as the lead tenant is 26 or older"
- internal_transfer: "Answer cannot be %{prevten} as this tenancy is an internal transfer"
+ non_temp_accommodation: "Answer cannot be non-temporary accommodation as this is a re-let to a tenant who occupied the same property as temporary accommodation."
+ over_25_foster_care: "Answer cannot be a children’s home or foster care as the lead tenant is 26 or older."
+ 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"
+ 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"
- cannot_be_secure_tenancy: "Answer cannot be secure tenancy as this is not an internal transfer"
- assessed_homeless: "Answer cannot be internal transfer as the tenant was assessed as homeless"
- other_homeless: "Answer cannot be internal transfer as the tenant was considered homeless by their landlord"
- prevten_invalid: "Answer cannot be internal transfer as the household situation immediately before this letting was %{prevten}"
- reason_permanently_decanted: "Answer must be internal transfer as the tenant was permanently decanted from another property owned by this landlord"
- nominated_by_local_ha_but_la: The source of the referral cannot be Nominated by local housing authority as your organisation is a local authority
+ 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."
+ cannot_be_secure_tenancy: "Answer cannot be secure tenancy as this is not an internal transfer."
+ assessed_homeless: "Answer cannot be internal transfer as the tenant was assessed as homeless."
+ other_homeless: "Answer cannot be internal transfer as the tenant was considered homeless by their landlord."
+ prevten_invalid: "Answer cannot be internal transfer as the household situation immediately before this letting was %{prevten}."
+ reason_permanently_decanted: "Answer must be internal transfer as the tenant was permanently decanted from another property owned by this landlord."
+ nominated_by_local_ha_but_la: "The source of the referral cannot be Nominated by local housing authority as your organisation is a local authority."
la_general_needs:
- internal_transfer: "Answer cannot be internal transfer as it’s the same landlord on the tenancy agreement and the household had either a fixed-term or lifetime local authority general needs tenancy immediately before this letting"
- prp_referred_by_la: "The source of the referral cannot be referred by local authority housing department for a general needs log"
+ internal_transfer: "Answer cannot be internal transfer as it’s the same landlord on the tenancy agreement and the household had either a fixed-term or lifetime local authority general needs tenancy immediately before this letting."
+ prp_referred_by_la: "The source of the referral cannot be referred by local authority housing department for a general needs log."
homeless:
assessed:
- internal_transfer: "Answer cannot be 'assessed as homeless' as this tenancy is an internal transfer"
+ internal_transfer: "Answer cannot be 'assessed as homeless' as this tenancy is an internal transfer."
other:
- internal_transfer: "Answer cannot be 'other homelessness' as this tenancy was an internal transfer"
+ internal_transfer: "Answer cannot be 'other homelessness' as this tenancy was an internal transfer."
reasonpref:
- not_homeless: "Answer cannot be ‘no’ as the tenant was homeless or about to lose their home"
- previous_la_known: "Enter name of local authority"
+ not_homeless: "Answer cannot be ‘no’ as the tenant was homeless or about to lose their home."
+ previous_la_known: "Enter name of local authority."
renewal_just_moved_to_area:
layear: 'The household cannot have just moved to the local authority area if this letting is a renewal'
renewal: 'This letting cannot be a renewal if the household has just moved to the local authority area'
@@ -573,95 +572,95 @@ en:
current_la: 'You told us the tenant had just moved into the local authority, but this location is in the same local authority. Check your answers are correct'
previous_la: 'The local authority of the previous property should not be the same as the current local authority, as you told us they had just moved to the local authority area. Check your answers are correct.'
gender:
- retired_male: "Answer cannot be ‘male’ as tenant is under 65 and retired"
- retired_female: "Answer cannot be ‘female’ as tenant is under 60 and retired"
+ retired_male: "Answer cannot be ‘male’ as tenant is under 65 and retired."
+ retired_female: "Answer cannot be ‘female’ as tenant is under 60 and retired."
reason:
- not_internal_transfer: "Answer cannot be ‘permanently decanted from another property owned by this landlord’ as you told us the source of referral for this tenancy was not an internal transfer"
- renewal_reason_needed: 'The reason for leaving must be "End of assured shorthold tenancy - no fault" or "End of fixed term tenancy - no fault" if the letting is a renewal'
- renewal_reason_needed_2024: 'The reason for leaving must be "End of social or private sector tenancy - no fault", "End of social or private sector tenancy - evicted due to anti-social behaviour (ASB)", "End of social or private sector tenancy - evicted due to rent arrears" or "End of social or private sector tenancy - evicted for any other reason"'
- other_not_settled: "Please give the reason for the tenant leaving their last settled home. This is where they were living before they became homeless, were living in temporary accommodation or sleeping rough"
+ not_internal_transfer: "Answer cannot be ‘permanently decanted from another property owned by this landlord’ as you told us the source of referral for this tenancy was not an internal transfer."
+ renewal_reason_needed: 'The reason for leaving must be "End of assured shorthold tenancy - no fault" or "End of fixed term tenancy - no fault" if the letting is a renewal.'
+ renewal_reason_needed_2024: 'The reason for leaving must be "End of social or private sector tenancy - no fault", "End of social or private sector tenancy - evicted due to anti-social behaviour (ASB)", "End of social or private sector tenancy - evicted due to rent arrears" or "End of social or private sector tenancy - evicted for any other reason".'
+ other_not_settled: "Please give the reason for the tenant leaving their last settled home. This is where they were living before they became homeless, were living in temporary accommodation or sleeping rough."
condition_effects:
- no_choices: "You cannot answer this question as you told us nobody in the household has a physical or mental health condition (or other illness) expected to last 12 months or more"
+ no_choices: "You cannot answer this question as you told us nobody in the household has a physical or mental health condition (or other illness) expected to last 12 months or more."
postcode:
- discounted_ownership: "Last settled accommodation and discounted ownership property postcodes must match"
+ discounted_ownership: "Last settled accommodation and discounted ownership property postcodes must match."
buylivein:
- buyers_will_live_in_property_values_inconsistent_setup: "You have already told us that both buyer 1 and buyer 2 will not live in the property"
- buyers_will_live_in_property_values_inconsistent: "You have already told us that the buyers will live in the property. Either buyer 1 or buyer 2 must live in the property"
- nationality: "Select a valid nationality"
+ buyers_will_live_in_property_values_inconsistent_setup: "You have already told us that both buyer 1 and buyer 2 will not live in the property."
+ buyers_will_live_in_property_values_inconsistent: "You have already told us that the buyers will live in the property. Either buyer 1 or buyer 2 must live in the property."
+ nationality: "Select a valid nationality."
tenancy:
length:
- fixed_term_not_required: "You must only answer the length of the tenancy if it's fixed-term"
- invalid_fixed: "Enter a tenancy length between %{min_tenancy_length} and 99 years for a tenancy of this type"
- invalid_periodic: "Enter a tenancy length between %{min_tenancy_length} and 99 years (or don't specify the length) for a tenancy of this type"
- internal_transfer: "Answer must be secure tenancy as this tenancy is an internal transfer"
- cannot_be_internal_transfer: "Answer cannot be internal transfer as this is not a secure tenancy"
- not_joint: "This cannot be a joint tenancy as you've told us there's only one person in the household"
- 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"
+ fixed_term_not_required: "You must only answer the length of the tenancy if it's fixed-term."
+ invalid_fixed: "Enter a tenancy length between %{min_tenancy_length} and 99 years for a tenancy of this type."
+ invalid_periodic: "Enter a tenancy length between %{min_tenancy_length} and 99 years (or don't specify the length) for a tenancy of this type."
+ internal_transfer: "Answer must be secure tenancy as this tenancy is an internal transfer."
+ cannot_be_internal_transfer: "Answer cannot be internal transfer as this is not a secure tenancy."
+ not_joint: "This cannot be a joint tenancy as you've told us there's only one person in the household."
+ 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:
- pre_2024: "You must show the MHCLG privacy notice to the tenant before you can submit this log"
- post_2024: "You must show or give the tenant access to the MHCLG privacy notice before you can submit this log"
+ pre_2024: "You must show the MHCLG privacy notice to the tenant before you can submit this log."
+ post_2024: "You must show or give the tenant access to the MHCLG privacy notice before you can submit this log."
privacynotice:
missing:
- pre_2024: "You must show the MHCLG privacy notice to the %{buyer_or_buyers} before you can submit this log"
- post_2024: "You must show or give the %{buyer_or_buyers} access to the MHCLG privacy notice before you can submit this log"
+ pre_2024: "You must show the MHCLG privacy notice to the %{buyer_or_buyers} before you can submit this log."
+ post_2024: "You must show or give the %{buyer_or_buyers} access to the MHCLG privacy notice before you can submit this log."
scheme:
toggle_date:
- not_selected: "Select one of the options"
- invalid: "Enter a valid day, month and year"
- before_creation: "The scheme cannot be deactivated before %{date}, the start of the collection year when it was created"
- out_of_range: "The date must be on or after the %{date}"
+ not_selected: "Select one of the options."
+ invalid: "Enter a valid day, month and year."
+ before_creation: "The scheme cannot be deactivated before %{date}, the start of the collection year when it was created."
+ out_of_range: "The date must be on or after the %{date}."
reactivation:
- before_deactivation: "This scheme was deactivated on %{date}. The reactivation date must be on or after deactivation date"
+ before_deactivation: "This scheme was deactivated on %{date}. The reactivation date must be on or after deactivation date."
deactivation:
- during_deactivated_period: "The scheme is already deactivated during this date, please enter a different date"
+ during_deactivated_period: "The scheme is already deactivated during this date, please enter a different date."
owning_organisation:
- does_not_own_stock: "Enter an organisation that owns housing stock"
- no_completed_locations: "This scheme cannot be chosen as it has no completed locations"
+ does_not_own_stock: "Enter an organisation that owns housing stock."
+ no_completed_locations: "This scheme cannot be chosen as it has no completed locations."
location:
- postcode_blank: "Enter a postcode"
- units: "The units at this location must be a number"
- type_of_unit: "Select the most common type of unit at this location"
- mobility_standards: "Select the mobility standard for the majority of the units at this location"
- startdate_invalid: "Enter a valid day, month and year when the first property became available at this location"
- startdate_out_of_range: "Availability date must be on or after the %{date}"
+ postcode_blank: "Enter a postcode."
+ units: "The units at this location must be a number."
+ type_of_unit: "Select the most common type of unit at this location."
+ mobility_standards: "Select the mobility standard for the majority of the units at this location."
+ startdate_invalid: "Enter a valid day, month and year when the first property became available at this location."
+ startdate_out_of_range: "Availability date must be on or after the %{date}."
toggle_date:
- not_selected: "Select one of the options"
- invalid: "Enter a valid day, month and year"
- before_creation: "The location cannot be deactivated before %{date}, the date when it was first available"
- out_of_range: "The date must be on or after the %{date}"
+ not_selected: "Select one of the options."
+ invalid: "Enter a valid day, month and year."
+ before_creation: "The location cannot be deactivated before %{date}, the date when it was first available."
+ out_of_range: "The date must be on or after the %{date}."
reactivation:
- before_deactivation: "This location was deactivated on %{date}. The reactivation date must be on or after deactivation date"
+ before_deactivation: "This location was deactivated on %{date}. The reactivation date must be on or after deactivation date."
deactivation:
- during_deactivated_period: "The location is already deactivated during this date, please enter a different date"
+ during_deactivated_period: "The location is already deactivated during this date, please enter a different date."
sale_information:
proplen:
- social_homebuy: "Social HomeBuy buyers should not have lived here before"
- rent_to_buy: "Rent to Buy buyers should not have lived here before"
+ social_homebuy: "Social HomeBuy buyers should not have lived here before."
+ rent_to_buy: "Rent to Buy buyers should not have lived here before."
hodate:
- must_be_before_saledate: "Practical completion or handover date must be before sale completion date"
- must_be_less_than_3_years_from_saledate: "Practical completion or handover date must be less than 3 years before sale completion date"
+ must_be_before_saledate: "Practical completion or handover date must be before sale completion date."
+ must_be_less_than_3_years_from_saledate: "Practical completion or handover date must be less than 3 years before sale completion date."
exdate:
- must_be_before_saledate: "Contract exchange date must be before sale completion date"
- must_be_less_than_1_year_from_saledate: "Contract exchange date must be less than 1 year before sale completion date"
+ must_be_before_saledate: "Contract exchange date must be before sale completion date."
+ must_be_less_than_1_year_from_saledate: "Contract exchange date must be less than 1 year before sale completion date."
saledate:
- must_be_after_exdate: "Sale completion date must be after contract exchange date"
- must_be_less_than_1_year_from_exdate: "Sale completion date must be less than 1 year after contract exchange date"
- must_be_less_than_3_years_from_hodate: "Sale completion date must be less than 3 years after practical completion or handover date"
- must_be_after_hodate: "Sale completion date must be after practical completion or handover date"
+ must_be_after_exdate: "Sale completion date must be after contract exchange date."
+ must_be_less_than_1_year_from_exdate: "Sale completion date must be less than 1 year after contract exchange date."
+ must_be_less_than_3_years_from_hodate: "Sale completion date must be less than 3 years after practical completion or handover date."
+ must_be_after_hodate: "Sale completion date must be after practical completion or handover date."
previous_property_type:
- property_type_bedsit: "A bedsit cannot have more than 1 bedroom"
+ property_type_bedsit: "A bedsit cannot have more than 1 bedroom."
discounted_ownership_value: "The mortgage%{mortgage}%{deposit_and_grant_sentence} added together is %{mortgage_deposit_and_grant_total}.The full purchase price%{discount_sentence} is %{value_with_discount}.These two amounts should be the same."
outright_sale_value: "The mortgage%{mortgage} and cash deposit (%{deposit}) when added together is %{mortgage_and_deposit_total}.The full purchase price is %{value}.These two amounts should be the same."
monthly_rent:
- higher_than_expected: "Basic monthly rent must be between £0.00 and £9,999.00"
+ higher_than_expected: "Basic monthly rent must be between £0.00 and £9,999.00."
grant:
- out_of_range: "Loan, grants or subsidies must be between £9,000 and £16,000"
+ out_of_range: "Loan, grants or subsidies must be between £9,000 and £16,000."
stairbought:
over_max: "The percentage bought in this staircasing transaction cannot be higher than %{max_stairbought}% for %{type} sales."
value:
@@ -680,12 +679,12 @@ en:
stairowned:
mortgageused_dont_know: "The percentage owned has to be 100% if the mortgage used is 'Don’t know'"
merge_request:
- organisation_part_of_another_merge: "This organisation is part of another merge - select a different one"
- organisation_not_selected: "Select an organisation from the search list"
+ organisation_part_of_another_merge: "This organisation is part of another merge - select a different one."
+ organisation_not_selected: "Select an organisation from the search list."
soft_validations:
net_income:
- title_text: "You told us that the household’s income is %{earnings} %{incfreq}"
+ title_text: "You told us that the household’s income is %{earnings} %{incfreq}."
hint_text: "This is %{net_income_higher_or_lower_text} than we would expect for the household’s working situation."
in_soft_min_range:
message: "Net income is lower than expected based on the household’s working situation. Are you sure this is correct?"
@@ -699,16 +698,16 @@ en:
over_soft_max_for_la_buyer_2: "You told us the income of buyer 2 is %{income}. This seems high. Are you sure this is correct?"
over_soft_max_for_la_combined: "You told us the combined income of this household is %{combined_income}. This seems high. Are you sure this is correct?"
rent:
- outside_range_title: "You told us the rent is %{brent}"
+ outside_range_title: "You told us the rent is %{brent}."
informative_text: "This is %{higher_or_lower} than we would expect."
hint_text: "Check the following:
- the decimal point
- the frequency, for example every week or every calendar month
- the rent type is correct, for example affordable or social rent
"
purchase_price:
- title_text: "You told us the purchase price is %{value}"
- hint_text: "This is %{higher_or_lower} than we would expect"
+ title_text: "You told us the purchase price is %{value}."
+ hint_text: "This is %{higher_or_lower} than we would expect."
staircase_owned:
title_text:
- one: "You told us that the buyer now owns %{stairowned} of the property"
- two: "You told us that the buyers now own %{stairowned} of the property"
+ one: "You told us that the buyer now owns %{stairowned} of the property."
+ two: "You told us that the buyers now own %{stairowned} of the property."
hint_text: "The maximum percentage that can be owned under the Older Persons Shared Ownership scheme is 75%, unless the property was funded outside the Affordable Homes Programme.
Make sure these answers are correct."
retirement:
@@ -716,13 +715,13 @@ Make sure these answers are correct."
title: "You told us this person is aged %{age} years and retired."
hint_text: "The minimum expected retirement age in England is 66."
max:
- title: "You told us this person is over 66 and not retired"
+ title: "You told us this person is over 66 and not retired."
hint_text: "The minimum expected retirement age in England is 66."
extra_borrowing:
- title_text: "You told us that the mortgage and deposit total is %{mortgage_and_deposit_total}"
+ title_text: "You told us that the mortgage and deposit total is %{mortgage_and_deposit_total}."
hint_text: "This is higher than the purchase price minus the discount."
pregnancy:
- title: "You told us somebody in the household is pregnant"
+ title: "You told us somebody in the household is pregnant."
all_male_tenants: "You also told us that all the tenants living at the property are male."
females_not_in_soft_age_range: "You also told us that any female tenants living at the property are in the following age ranges:
- under 16 years old
- over 50 years old
"
major_repairs_date:
@@ -732,7 +731,7 @@ Make sure these answers are correct."
title_text: "You told us that the property has been vacant for more than 2 years."
hint_text: "This is higher than we would expect."
shared_ownership_deposit:
- title_text: "You told us that the %{mortgage_deposit_and_discount_error_fields} add up to %{mortgage_deposit_and_discount_total}"
+ title_text: "You told us that the %{mortgage_deposit_and_discount_error_fields} add up to %{mortgage_deposit_and_discount_total}."
old_persons_shared_ownership:
title_text:
one: "You told us the buyer is using the Older Persons Shared Ownership scheme."
@@ -745,12 +744,12 @@ Make sure these answers are correct."
title_text: "You told us that the monthly charges were %{mscharge}."
hint_text: "This is higher than we would expect."
student_not_child:
- title_text: "You told us this person is a student aged beween 16 and 19"
+ title_text: "You told us this person is a student aged between 16 and 19."
discounted_sale_value:
- title_text: "Mortgage, deposit, and grant total must equal %{value_with_discount}"
- informative_text: "Your given mortgage, deposit and grant total is %{mortgage_deposit_and_grant_total}"
+ title_text: "Mortgage, deposit, and grant total must equal %{value_with_discount}."
+ informative_text: "Your given mortgage, deposit and grant total is %{mortgage_deposit_and_grant_total}."
care_home_charges:
- title_text: "Care home charges should be provided if this is a care home accommodation"
+ title_text: "Care home charges should be provided if this is a care home accommodation."
buyer1_livein_wrong_for_ownership_type:
title_text: "You told us that buyer 1 will not live in the property."
hint_text: " For %{ownership_scheme} types, the buyer usually lives in the property."
@@ -771,22 +770,22 @@ Make sure these answers are correct."
two: "You told us the buyers’ deposit was %{deposit} and their savings were %{savings}."
hint_text: "The deposit amount is higher than we would expect for the amount of savings they have."
grant:
- title_text: "You told us that the grant amount is %{grant}"
+ title_text: "You told us that the grant amount is %{grant}."
hint_text: "Loans, grants and subsidies are usually between £9,000 and £16,000."
wheelchair:
title_text: "You told us that someone in the household uses a wheelchair."
mortgage:
- title_text: "You told us that the mortgage amount is %{mortgage}"
+ title_text: "You told us that the mortgage amount is %{mortgage}."
hint_text: "This is more than 5 times the income, which is higher than we would expect."
referral:
title_text: "Are you sure?"
hint_text: "This is a general needs log, and this referral type is for supported housing."
scharge:
- over_soft_max_title: "You told us the service charge is %{scharge}"
+ over_soft_max_title: "You told us the service charge is %{scharge}."
pscharge:
- over_soft_max_title: "You told us the personal service charge is %{pscharge}"
+ over_soft_max_title: "You told us the personal service charge is %{pscharge}."
supcharg:
- over_soft_max_title: "You told us the support charge is %{supcharg}"
+ over_soft_max_title: "You told us the support charge is %{supcharg}."
charges:
informative_text: "This is higher than we would expect."
hint_text: "Check the following:
- the decimal point
- the frequency, for example every week or every calendar month
- the needs type
"
@@ -794,16 +793,16 @@ Make sure these answers are correct."
title_text: "You told us the mortgage amount was %{mortgage}, the cash deposit was %{deposit} and the discount was %{discount}."
hint_text: "We would expect the mortgage amount and the deposit added together to be the same as the purchase price minus the discount."
reasonother:
- title_text: "You told us that the tenant’s main reason for leaving their last settled home was %{reasonother}"
+ title_text: "You told us that the tenant’s main reason for leaving their last settled home was %{reasonother}."
informative_text: "The reason you have entered looks very similar to one of the existing response categories.
Please check the categories and select the appropriate one.
If the existing categories are not suitable, please confirm here to move onto the next question."
hodate:
- must_be_less_than_3_years_from_saledate: "You told us practical completion or handover date is more than 3 years before sale completion date"
+ must_be_less_than_3_years_from_saledate: "You told us practical completion or handover date is more than 3 years before sale completion date."
saledate:
- must_be_less_than_3_years_from_hodate: "You told us sale completion date is more than 3 years after practical completion or handover date"
+ must_be_less_than_3_years_from_hodate: "You told us sale completion date is more than 3 years after practical completion or handover date."
no_address_found:
- title_text: "No address found"
+ title_text: "No address found."
informative_text: "We could not find an address that matches your search. You can search again or continue to enter the address manually."
partner_under_16_lettings:
title: "You told us this person is aged %{age} years and has 'Partner' relationship to the lead tenant."
@@ -816,16 +815,16 @@ Make sure these answers are correct."
devise:
email:
- updated: An email has been sent to %{email} to confirm this change.
+ updated: "An email has been sent to %{email} to confirm this change."
two_factor_authentication:
- success: "Two-factor authentication successful"
- attempt_failed: "Attempt failed"
- max_login_attempts_reached: "Too many incorrect log in attempts"
- account_locked: "Your account has been locked for security reasons"
- contact_administrator: "Contact another helpdesk administrator for access"
- code_has_been_sent: "Your security code has been sent"
- code_required: "Security code is required"
- code_incorrect: "Security code is incorrect"
+ success: "Two-factor authentication successful."
+ attempt_failed: "Attempt failed."
+ max_login_attempts_reached: "Too many incorrect log in attempts."
+ account_locked: "Your account has been locked for security reasons."
+ contact_administrator: "Contact another helpdesk administrator for access."
+ code_has_been_sent: "Your security code has been sent."
+ code_required: "Security code is required."
+ code_incorrect: "Security code is incorrect."
questions:
location:
@@ -860,10 +859,10 @@ Make sure these answers are correct."
hints:
location:
postcode: "For example, SW1P 4DF."
- name: "This is how you refer to this location within your organisation"
+ name: "This is how you refer to this location within your organisation."
units: "A unit is the space being let. For example, the property might be a block of flats and the unit would be the specific flat being let. A unit can also be a bedroom in a shared house or flat. Do not include spaces used for staff."
toggle_active: "If the date is before %{date}, select ‘From the start of the open collection period’ because the previous period has now closed."
- startdate: "For example, 27 3 2021"
+ startdate: "For example, 27 3 2021."
scheme:
toggle_active: "If the date is before %{date}, select ‘From the start of the open collection period’ because the previous period has now closed."
bulk_upload:
@@ -875,12 +874,12 @@ Make sure these answers are correct."
one: "Buyer was a registered provider, housing association or local authority tenant immediately before this sale?"
other: "Any buyers were registered providers, housing association or local authority tenants immediately before this sale?"
prevown:
- one: "Buyer previously owned a property"
- other: "Buyers previously owned a property"
+ one: "Buyer previously owned a property."
+ other: "Buyers previously owned a property."
stairowned:
- one: "Percentage the buyer now owns in total"
- other: "Percentage the buyers now own in total"
- offered: "Times previously offered since becoming available"
+ one: "Percentage the buyer now owns in total."
+ other: "Percentage the buyers now own in total."
+ offered: "Times previously offered since becoming available."
warnings:
organisation:
@@ -900,7 +899,7 @@ Make sure these answers are correct."
existing_logs: "You’ll be able to add logs with this scheme if their tenancy start date is on or after the date you enter."
test:
- one_argument: "This is based on the tenant’s work situation: %{ecstat1}"
+ one_argument: "This is based on the tenant’s work situation: %{ecstat1}."
title_text:
- no_argument: "Some test text"
- one_argument: "You said this: %{argument}"
+ no_argument: "Some test text."
+ one_argument: "You said this: %{argument}."
diff --git a/db/migrate/20240802093255_rename_export_table.rb b/db/migrate/20240802093255_rename_export_table.rb
new file mode 100644
index 000000000..68c14d0a9
--- /dev/null
+++ b/db/migrate/20240802093255_rename_export_table.rb
@@ -0,0 +1,5 @@
+class RenameExportTable < ActiveRecord::Migration[7.0]
+ def change
+ rename_table :logs_exports, :exports
+ end
+end
diff --git a/db/migrate/20240905092332_add_organisation_id_to_bulk_uploads.rb b/db/migrate/20240905092332_add_organisation_id_to_bulk_uploads.rb
new file mode 100644
index 000000000..d9f4f16da
--- /dev/null
+++ b/db/migrate/20240905092332_add_organisation_id_to_bulk_uploads.rb
@@ -0,0 +1,5 @@
+class AddOrganisationIdToBulkUploads < ActiveRecord::Migration[7.0]
+ def change
+ add_column :bulk_uploads, :organisation_id, :integer
+ end
+end
diff --git a/db/migrate/20240923145326_add_validation_checked_field.rb b/db/migrate/20240923145326_add_validation_checked_field.rb
new file mode 100644
index 000000000..899992974
--- /dev/null
+++ b/db/migrate/20240923145326_add_validation_checked_field.rb
@@ -0,0 +1,5 @@
+class AddValidationCheckedField < ActiveRecord::Migration[7.0]
+ def change
+ add_column :log_validations, :checked, :boolean
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 80463eaad..ff1f913df 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.0].define(version: 2024_09_18_112702) do
+ActiveRecord::Schema[7.0].define(version: 2024_09_23_145326) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -42,6 +42,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_18_112702) do
t.text "choice"
t.integer "total_logs_count"
t.string "rent_type_fix_status", default: "not_applied"
+ t.integer "organisation_id"
t.integer "moved_user_id"
t.index ["identifier"], name: "index_bulk_uploads_on_identifier", unique: true
t.index ["user_id"], name: "index_bulk_uploads_on_user_id"
@@ -77,6 +78,15 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_18_112702) do
t.index ["organisation_id"], name: "index_data_protection_confirmations_on_organisation_id"
end
+ create_table "exports", force: :cascade do |t|
+ t.datetime "created_at", default: -> { "CURRENT_TIMESTAMP" }
+ t.datetime "started_at", null: false
+ t.integer "base_number", default: 1, null: false
+ t.integer "increment_number", default: 1, null: false
+ t.boolean "empty_export", default: false, null: false
+ t.string "collection"
+ end
+
create_table "la_rent_ranges", force: :cascade do |t|
t.integer "ranges_rent_id"
t.integer "lettype"
@@ -410,15 +420,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_18_112702) do
t.string "other_validated_models"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- end
-
- create_table "logs_exports", force: :cascade do |t|
- t.datetime "created_at", default: -> { "CURRENT_TIMESTAMP" }
- t.datetime "started_at", null: false
- t.integer "base_number", default: 1, null: false
- t.integer "increment_number", default: 1, null: false
- t.boolean "empty_export", default: false, null: false
- t.string "collection"
+ t.boolean "checked"
end
create_table "merge_request_organisations", force: :cascade do |t|
diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock
index 2a406a953..8270007c8 100644
--- a/docs/Gemfile.lock
+++ b/docs/Gemfile.lock
@@ -253,7 +253,7 @@ GEM
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (1.8.0)
- webrick (1.8.1)
+ webrick (1.8.2)
PLATFORMS
arm64-darwin-21
diff --git a/lib/tasks/data_export.rake b/lib/tasks/data_export.rake
index 719462cb4..7a9a90bc8 100644
--- a/lib/tasks/data_export.rake
+++ b/lib/tasks/data_export.rake
@@ -7,11 +7,13 @@ namespace :core do
end
desc "Export all data XMLs for import into Central Data System (CDS)"
- task :full_data_export_xml, %i[year] => :environment do |_task, args|
- collection_year = args[:year].present? ? args[:year].to_i : nil
+ task :full_data_export_xml, %i[collection] => :environment do |_task, args|
+ collection = args[:collection].presence
+ collection = collection.to_i if collection.present? && collection.scan(/\D/).empty?
+
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["EXPORT_BUCKET"])
- export_service = Exports::LettingsLogExportService.new(storage_service)
+ export_service = Exports::ExportService.new(storage_service)
- export_service.export_xml_lettings_logs(full_update: true, collection_year:)
+ export_service.export_xml(full_update: true, collection:)
end
end
diff --git a/lib/tasks/recalculate_status_after_sales_over_retirement_age_validation.rake b/lib/tasks/recalculate_status_after_sales_over_retirement_age_validation.rake
new file mode 100644
index 000000000..e827e1c10
--- /dev/null
+++ b/lib/tasks/recalculate_status_after_sales_over_retirement_age_validation.rake
@@ -0,0 +1,20 @@
+desc "Recalculates status for 2024 logs that will trigger new sales over retirement age soft validation"
+task recalculate_status_over_retirement: :environment do
+ validation_trigger_condition = "(ecstat1 != 5 AND age1 > 66) OR (ecstat2 != 5 AND age2 > 66) OR (ecstat3 != 5 AND age3 > 66) OR (ecstat4 != 5 AND age4 > 66) OR (ecstat5 != 5 AND age5 > 66) OR (ecstat6 != 5 AND age6 > 66)"
+ SalesLog.filter_by_year(2024).where(status: "pending", status_cache: "completed").where(validation_trigger_condition).find_each do |log|
+ log.status_cache = log.calculate_status
+ log.skip_update_status = true
+
+ unless log.save
+ Rails.logger.info "Could not save changes to pending sales log #{log.id}"
+ end
+ end
+
+ SalesLog.filter_by_year(2024).where(status: "completed").where(validation_trigger_condition).find_each do |log|
+ log.status = log.calculate_status
+
+ unless log.save
+ Rails.logger.info "Could not save changes to sales log #{log.id}"
+ end
+ end
+end
diff --git a/spec/factories/bulk_upload.rb b/spec/factories/bulk_upload.rb
index c848fb071..cefe95c2b 100644
--- a/spec/factories/bulk_upload.rb
+++ b/spec/factories/bulk_upload.rb
@@ -9,6 +9,7 @@ FactoryBot.define do
sequence(:filename) { |n| "bulk-upload-#{n}.csv" }
needstype { 1 }
rent_type_fix_status { BulkUpload.rent_type_fix_statuses.values.sample }
+ organisation_id { user.organisation_id }
trait(:sales) do
log_type { BulkUpload.log_types[:sales] }
diff --git a/spec/features/organisation_spec.rb b/spec/features/organisation_spec.rb
index 1867285eb..3d65cda87 100644
--- a/spec/features/organisation_spec.rb
+++ b/spec/features/organisation_spec.rb
@@ -139,6 +139,10 @@ RSpec.describe "User Features" do
expect(page).to have_button("Create a new lettings log for this organisation")
end
+ it "shows a upload lettings logs in bulk link" do
+ expect(page).to have_link("Upload lettings logs in bulk")
+ end
+
context "when creating a log for that organisation" do
it "pre-fills the value for owning organisation for that log" do
click_button("Create a new lettings log for this organisation")
@@ -230,6 +234,10 @@ RSpec.describe "User Features" do
expect(page).to have_button("Create a new sales log for this organisation")
end
+ it "shows a upload sales logs in bulk link" do
+ expect(page).to have_link("Upload sales logs in bulk")
+ end
+
context "when creating a log for that organisation" do
it "pre-fills the value for owning organisation for that log" do
click_button("Create a new sales log for this organisation")
diff --git a/spec/fixtures/exports/organisation.xml b/spec/fixtures/exports/organisation.xml
new file mode 100644
index 000000000..8d87da16c
--- /dev/null
+++ b/spec/fixtures/exports/organisation.xml
@@ -0,0 +1,26 @@
+
+
+
+
diff --git a/spec/fixtures/exports/user.xml b/spec/fixtures/exports/user.xml
new file mode 100644
index 000000000..5652ac9c6
--- /dev/null
+++ b/spec/fixtures/exports/user.xml
@@ -0,0 +1,17 @@
+
+
+
+
diff --git a/spec/helpers/filters_helper_spec.rb b/spec/helpers/filters_helper_spec.rb
index a2b658cdf..58c82f0c9 100644
--- a/spec/helpers/filters_helper_spec.rb
+++ b/spec/helpers/filters_helper_spec.rb
@@ -527,6 +527,56 @@ RSpec.describe FiltersHelper do
end
end
+ describe "#collection_year_radio_options" do
+ context "with 23/24 as the current collection year" do
+ before do
+ allow(Time).to receive(:now).and_return(Time.zone.local(2023, 5, 1))
+ end
+
+ context "and in crossover period" do
+ before do
+ allow(FormHandler.instance).to receive(:in_crossover_period?).and_return(true)
+ end
+
+ it "has the correct options" do
+ expect(collection_year_radio_options).to eq(
+ {
+ "2023" => { label: "2023/24" }, "2022" => { label: "2022/23" }, "2021" => { label: "2021/22" }
+ },
+ )
+ end
+ end
+
+ context "and not in crossover period" do
+ before do
+ allow(FormHandler.instance).to receive(:in_crossover_period?).and_return(false)
+ end
+
+ it "has the correct options" do
+ expect(collection_year_radio_options).to eq(
+ {
+ "2023" => { label: "2023/24" }, "2022" => { label: "2022/23" }
+ },
+ )
+ end
+ end
+ end
+
+ context "with 24/25 as the current collection year" do
+ before do
+ allow(Time).to receive(:now).and_return(Time.zone.local(2024, 5, 1))
+ end
+
+ it "has the correct options" do
+ expect(collection_year_radio_options).to eq(
+ {
+ "2024" => { label: "2024/25" }, "2023" => { label: "2023/24" }, "2022" => { label: "2022/23" }
+ },
+ )
+ end
+ end
+ end
+
describe "#filters_applied_text" do
let(:filter_type) { "lettings_logs" }
let(:result) { filters_applied_text(filter_type) }
diff --git a/spec/helpers/interruption_screen_helper_spec.rb b/spec/helpers/interruption_screen_helper_spec.rb
index cd044e61a..60cd71131 100644
--- a/spec/helpers/interruption_screen_helper_spec.rb
+++ b/spec/helpers/interruption_screen_helper_spec.rb
@@ -151,7 +151,7 @@ RSpec.describe InterruptionScreenHelper do
},
],
}
- expect(display_informative_text(informative_text_hash, lettings_log)).to eq("You said this: £12,345.00")
+ expect(display_informative_text(informative_text_hash, lettings_log)).to eq("You said this: £12,345.00.")
end
end
@@ -216,7 +216,7 @@ RSpec.describe InterruptionScreenHelper do
},
],
}
- expect(display_title_text(title_text, lettings_log)).to eq("You said this: £12,345.00")
+ expect(display_title_text(title_text, lettings_log)).to eq("You said this: £12,345.00.")
end
end
end
diff --git a/spec/helpers/user_helper_spec.rb b/spec/helpers/user_helper_spec.rb
index c88790aa0..eb0a672db 100644
--- a/spec/helpers/user_helper_spec.rb
+++ b/spec/helpers/user_helper_spec.rb
@@ -105,6 +105,32 @@ RSpec.describe UserHelper do
end
end
+ describe "display_pending_email_change_banner?" do
+ context "when the user doesn't have an unconfirmed email" do
+ let(:user) { FactoryBot.create(:user, :data_provider, unconfirmed_email: nil) }
+
+ it "does not display pending email change banner" do
+ expect(display_pending_email_change_banner?(user)).to be false
+ end
+ end
+
+ context "when the user has the same unconfirmed email as current email" do
+ let(:user) { FactoryBot.create(:user, :data_provider, unconfirmed_email: "updated_email@example.com", email: "updated_email@example.com") }
+
+ it "does not display pending email change banner" do
+ expect(display_pending_email_change_banner?(user)).to be false
+ end
+ end
+
+ context "when the user has a different unconfirmed email" do
+ let(:user) { FactoryBot.create(:user, :data_provider, unconfirmed_email: "updated_email@example.com", email: "old_email@example.com") }
+
+ it "displays pending email change banner" do
+ expect(display_pending_email_change_banner?(user)).to be true
+ end
+ end
+ end
+
describe "organisation_change_confirmation_warning" do
context "when user owns logs" do
before do
@@ -147,4 +173,39 @@ RSpec.describe UserHelper do
end
end
end
+
+ describe "pending_email_change_title_text" do
+ let(:user) { FactoryBot.create(:user, :data_provider, unconfirmed_email: "updated_email@example.com", email: "old_email@example.com") }
+ let(:current_user) { FactoryBot.create(:user, :support) }
+
+ context "when viewing own profile" do
+ it "returns the correct text" do
+ expect(pending_email_change_title_text(user, user)).to eq("You have requested to change your email address to updated_email@example.com.")
+ end
+ end
+
+ context "when viewing another user's profile" do
+ it "returns the correct text" do
+ expect(pending_email_change_title_text(current_user, user)).to eq("There has been a request to change this user’s email address to updated_email@example.com.")
+ end
+ end
+ end
+
+ describe "pending_email_change_banner_text" do
+ context "with provider user" do
+ let(:user) { FactoryBot.create(:user, :data_provider) }
+
+ it "returns the correct text" do
+ expect(pending_email_change_banner_text(user)).to eq("A confirmation link has been sent to the new email address. The current email will continue to work until the change is confirmed.")
+ end
+ end
+
+ context "with support user" do
+ let(:user) { FactoryBot.create(:user, :support) }
+
+ it "returns the correct text" do
+ expect(pending_email_change_banner_text(user)).to eq("A confirmation link has been sent to the new email address. The current email will continue to work until the change is confirmed. Deactivating this user will cancel the email change request.")
+ end
+ end
+ end
end
diff --git a/spec/jobs/data_export_xml_job_spec.rb b/spec/jobs/data_export_xml_job_spec.rb
index c029dad71..3712e115c 100644
--- a/spec/jobs/data_export_xml_job_spec.rb
+++ b/spec/jobs/data_export_xml_job_spec.rb
@@ -1,25 +1,33 @@
require "rails_helper"
describe DataExportXmlJob do
- let(:storage_service) { instance_double(Storage::S3Service) }
+ let(:storage_service) { instance_double(Storage::S3Service, write_file: nil) }
let(:env_config_service) { instance_double(Configuration::EnvConfigurationService) }
- let(:export_service) { instance_double(Exports::LettingsLogExportService) }
+ let(:lettings_export_service) { instance_double(Exports::LettingsLogExportService, export_xml_lettings_logs: {}) }
+ let(:user_export_service) { instance_double(Exports::UserExportService, export_xml_users: {}) }
+ let(:organisation_export_service) { instance_double(Exports::OrganisationExportService, export_xml_organisations: {}) }
before do
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(Configuration::EnvConfigurationService).to receive(:new).and_return(env_config_service)
- allow(Exports::LettingsLogExportService).to receive(:new).and_return(export_service)
+ allow(Exports::LettingsLogExportService).to receive(:new).and_return(lettings_export_service)
+ allow(Exports::UserExportService).to receive(:new).and_return(user_export_service)
+ allow(Exports::OrganisationExportService).to receive(:new).and_return(organisation_export_service)
end
- it "calls the export service" do
- expect(export_service).to receive(:export_xml_lettings_logs)
+ it "calls the export services" do
+ expect(lettings_export_service).to receive(:export_xml_lettings_logs)
+ expect(user_export_service).to receive(:export_xml_users)
+ expect(organisation_export_service).to receive(:export_xml_organisations)
described_class.perform_now
end
context "with full update enabled" do
it "calls the export service" do
- expect(export_service).to receive(:export_xml_lettings_logs).with(full_update: true)
+ expect(lettings_export_service).to receive(:export_xml_lettings_logs).with(full_update: true, collection_year: nil)
+ expect(user_export_service).to receive(:export_xml_users).with(full_update: true)
+ expect(organisation_export_service).to receive(:export_xml_organisations).with(full_update: true)
described_class.perform_now(full_update: true)
end
diff --git a/spec/lib/tasks/data_export_spec.rb b/spec/lib/tasks/data_export_spec.rb
index afb99f872..8b5dd5fbe 100644
--- a/spec/lib/tasks/data_export_spec.rb
+++ b/spec/lib/tasks/data_export_spec.rb
@@ -4,7 +4,7 @@ require "rake"
describe "rake core:data_export", type: task do
let(:export_bucket) { "export_bucket" }
let(:storage_service) { instance_double(Storage::S3Service) }
- let(:export_service) { instance_double(Exports::LettingsLogExportService) }
+ let(:export_service) { instance_double(Exports::ExportService) }
before do
Rake.application.rake_require("tasks/data_export")
@@ -12,7 +12,7 @@ describe "rake core:data_export", type: task do
task.reenable
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
- allow(Exports::LettingsLogExportService).to receive(:new).and_return(export_service)
+ allow(Exports::ExportService).to receive(:new).and_return(export_service)
allow(ENV).to receive(:[])
allow(ENV).to receive(:[]).with("EXPORT_BUCKET").and_return(export_bucket)
end
@@ -30,15 +30,15 @@ describe "rake core:data_export", type: task do
context "with all available years" do
it "calls the export service" do
- expect(export_service).to receive(:export_xml_lettings_logs).with(full_update: true, collection_year: nil)
+ expect(export_service).to receive(:export_xml).with(full_update: true, collection: nil)
task.invoke
end
end
- context "with a specific year" do
+ context "with a specific collection" do
it "calls the export service" do
- expect(export_service).to receive(:export_xml_lettings_logs).with(full_update: true, collection_year: 2022)
+ expect(export_service).to receive(:export_xml).with(full_update: true, collection: 2022)
task.invoke("2022")
end
diff --git a/spec/lib/tasks/recalculate_status_after_sales_over_retirement_age_validation_spec.rb b/spec/lib/tasks/recalculate_status_after_sales_over_retirement_age_validation_spec.rb
new file mode 100644
index 000000000..b76380f90
--- /dev/null
+++ b/spec/lib/tasks/recalculate_status_after_sales_over_retirement_age_validation_spec.rb
@@ -0,0 +1,55 @@
+require "rails_helper"
+require "rake"
+
+RSpec.describe "recalculate_status_after_sales_over_retirement_age_validation" do
+ describe ":recalculate_status_over_retirement", type: :task do
+ subject(:task) { Rake::Task["recalculate_status_over_retirement"] }
+
+ before do
+ Rake.application.rake_require("tasks/recalculate_status_after_sales_over_retirement_age_validation")
+ Rake::Task.define_task(:environment)
+ task.reenable
+ end
+
+ context "when the rake task is run" do
+ context "and there is a completed sales log that trips the validation" do
+ let(:log) { create(:sales_log, :completed, ecstat1: 1, age1: 67) }
+
+ before do
+ log.status = "completed"
+ log.skip_update_status = true
+ log.save!
+ end
+
+ it "sets the log to in progress" do
+ task.invoke
+ log.reload
+ expect(log.status).to eq("in_progress")
+ end
+ end
+
+ context "and there is a pending sales log that trips the validation" do
+ let(:log) { create(:sales_log, :completed, ecstat2: 1, age2: 70) }
+
+ before do
+ log.status = "pending"
+ log.status_cache = "completed"
+ log.skip_update_status = true
+ log.save!
+ end
+
+ it "updates the status cache" do
+ task.invoke
+ log.reload
+ expect(log.status_cache).to eq("in_progress")
+ end
+
+ it "does not change the log status" do
+ task.invoke
+ log.reload
+ expect(log.status).to eq("pending")
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/tasks/update_schemes_and_locations_from_csv_spec.rb b/spec/lib/tasks/update_schemes_and_locations_from_csv_spec.rb
index 818a2b589..c397b456a 100644
--- a/spec/lib/tasks/update_schemes_and_locations_from_csv_spec.rb
+++ b/spec/lib/tasks/update_schemes_and_locations_from_csv_spec.rb
@@ -445,7 +445,7 @@ RSpec.describe "bulk_update" do
expect(Rails.logger).to receive(:info).with("Will not export log #{lettings_log_5.id} as it is before the exportable date")
expect(Rails.logger).to receive(:info).with("No changes to location #{locations[1].id}.")
- expect(Rails.logger).to receive(:info).with("Cannot update location #{locations[2].id} with postcode: SWAAA. Enter a postcode in the correct format, for example AA1 1AA")
+ expect(Rails.logger).to receive(:info).with("Cannot update location #{locations[2].id} with postcode: SWAAA. Enter a postcode in the correct format, for example AA1 1AA.")
expect(Rails.logger).to receive(:info).with("Cannot update location #{locations[2].id} with scheme_code: S. Scheme with id S is not in the database")
expect(Rails.logger).to receive(:info).with("Cannot update location #{locations[2].id} with location_admin_district: Westminst. Location admin distrint Westminst is not a valid option")
expect(Rails.logger).to receive(:info).with("Cannot update location #{locations[2].id} with type_of_unit: elf-contained house. 'elf-contained house' is not a valid type_of_unit")
diff --git a/spec/models/form/lettings/questions/declaration_spec.rb b/spec/models/form/lettings/questions/declaration_spec.rb
index c64e7f3c8..ea1e10402 100644
--- a/spec/models/form/lettings/questions/declaration_spec.rb
+++ b/spec/models/form/lettings/questions/declaration_spec.rb
@@ -63,7 +63,7 @@ RSpec.describe Form::Lettings::Questions::Declaration, type: :model do
end
it "returns correct unanswered_error_message" do
- expect(question.unanswered_error_message).to eq("You must show the MHCLG privacy notice to the tenant before you can submit this log")
+ expect(question.unanswered_error_message).to eq("You must show the MHCLG privacy notice to the tenant before you can submit this log.")
end
end
@@ -87,7 +87,7 @@ RSpec.describe Form::Lettings::Questions::Declaration, type: :model do
end
it "returns correct unanswered_error_message" do
- expect(question.unanswered_error_message).to eq("You must show or give the tenant access to the MHCLG privacy notice before you can submit this log")
+ expect(question.unanswered_error_message).to eq("You must show or give the tenant access to the MHCLG privacy notice before you can submit this log.")
end
end
end
diff --git a/spec/models/form/lettings/questions/offered_spec.rb b/spec/models/form/lettings/questions/offered_spec.rb
index bda69490e..753ab459e 100644
--- a/spec/models/form/lettings/questions/offered_spec.rb
+++ b/spec/models/form/lettings/questions/offered_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe Form::Lettings::Questions::Offered, type: :model do
end
it "has the correct check_answer_label" do
- expect(question.check_answer_label).to eq "Times previously offered since becoming available"
+ expect(question.check_answer_label).to eq "Times previously offered since becoming available."
end
it "has the correct type" do
diff --git a/spec/models/form/lettings/questions/uprn_spec.rb b/spec/models/form/lettings/questions/uprn_spec.rb
index 966b9b970..7d4822c16 100644
--- a/spec/models/form/lettings/questions/uprn_spec.rb
+++ b/spec/models/form/lettings/questions/uprn_spec.rb
@@ -40,7 +40,7 @@ RSpec.describe Form::Lettings::Questions::Uprn, type: :model do
end
it "has the correct unanswered_error_message" do
- expect(question.unanswered_error_message).to eq("UPRN must be 12 digits or less")
+ expect(question.unanswered_error_message).to eq("UPRN must be 12 digits or less.")
end
describe "get_extra_check_answer_value" do
diff --git a/spec/models/form/sales/questions/prevown_spec.rb b/spec/models/form/sales/questions/prevown_spec.rb
index b086534d7..4e5d006d9 100644
--- a/spec/models/form/sales/questions/prevown_spec.rb
+++ b/spec/models/form/sales/questions/prevown_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe Form::Sales::Questions::Prevown, type: :model do
end
it "has the correct check_answer_label" do
- expect(question.check_answer_label).to eq("Buyer previously owned a property")
+ expect(question.check_answer_label).to eq("Buyer previously owned a property.")
end
end
@@ -34,7 +34,7 @@ RSpec.describe Form::Sales::Questions::Prevown, type: :model do
end
it "has the correct check_answer_label" do
- expect(question.check_answer_label).to eq("Buyers previously owned a property")
+ expect(question.check_answer_label).to eq("Buyers previously owned a property.")
end
end
diff --git a/spec/models/form/sales/questions/privacy_notice_spec.rb b/spec/models/form/sales/questions/privacy_notice_spec.rb
index 9fb81057a..9242804ae 100644
--- a/spec/models/form/sales/questions/privacy_notice_spec.rb
+++ b/spec/models/form/sales/questions/privacy_notice_spec.rb
@@ -60,7 +60,7 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do
end
it "returns correct unanswered_error_message" do
- expect(question.unanswered_error_message).to eq("You must show the MHCLG privacy notice to the buyer before you can submit this log")
+ expect(question.unanswered_error_message).to eq("You must show the MHCLG privacy notice to the buyer before you can submit this log.")
end
end
@@ -78,7 +78,7 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do
end
it "returns correct unanswered_error_message" do
- expect(question.unanswered_error_message).to eq("You must show the MHCLG privacy notice to the buyers before you can submit this log")
+ expect(question.unanswered_error_message).to eq("You must show the MHCLG privacy notice to the buyers before you can submit this log.")
end
end
end
@@ -100,7 +100,7 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do
end
it "returns correct unanswered_error_message" do
- expect(question.unanswered_error_message).to eq("You must show or give the buyer access to the MHCLG privacy notice before you can submit this log")
+ expect(question.unanswered_error_message).to eq("You must show or give the buyer access to the MHCLG privacy notice before you can submit this log.")
end
end
@@ -118,7 +118,7 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do
end
it "returns correct unanswered_error_message" do
- expect(question.unanswered_error_message).to eq("You must show or give the buyers access to the MHCLG privacy notice before you can submit this log")
+ expect(question.unanswered_error_message).to eq("You must show or give the buyers access to the MHCLG privacy notice before you can submit this log.")
end
end
end
diff --git a/spec/models/form/sales/questions/staircase_owned_spec.rb b/spec/models/form/sales/questions/staircase_owned_spec.rb
index a9b252f05..80cd30543 100644
--- a/spec/models/form/sales/questions/staircase_owned_spec.rb
+++ b/spec/models/form/sales/questions/staircase_owned_spec.rb
@@ -24,7 +24,7 @@ RSpec.describe Form::Sales::Questions::StaircaseOwned, type: :model do
end
it "has the correct check_answer_label" do
- expect(question.check_answer_label).to eq("Percentage the buyers now own in total")
+ expect(question.check_answer_label).to eq("Percentage the buyers now own in total.")
end
end
@@ -34,7 +34,7 @@ RSpec.describe Form::Sales::Questions::StaircaseOwned, type: :model do
end
it "has the correct check_answer_label" do
- expect(question.check_answer_label).to eq("Percentage the buyer now owns in total")
+ expect(question.check_answer_label).to eq("Percentage the buyer now owns in total.")
end
end
diff --git a/spec/models/form/sales/questions/uprn_spec.rb b/spec/models/form/sales/questions/uprn_spec.rb
index afc4e4a24..845358bce 100644
--- a/spec/models/form/sales/questions/uprn_spec.rb
+++ b/spec/models/form/sales/questions/uprn_spec.rb
@@ -40,7 +40,7 @@ RSpec.describe Form::Sales::Questions::Uprn, type: :model do
end
it "has the correct unanswered_error_message" do
- expect(question.unanswered_error_message).to eq("UPRN must be 12 digits or less")
+ expect(question.unanswered_error_message).to eq("UPRN must be 12 digits or less.")
end
describe "get_extra_check_answer_value" do
diff --git a/spec/models/form/sales/subsections/household_characteristics_spec.rb b/spec/models/form/sales/subsections/household_characteristics_spec.rb
index be78f91d3..7c6546d79 100644
--- a/spec/models/form/sales/subsections/household_characteristics_spec.rb
+++ b/spec/models/form/sales/subsections/household_characteristics_spec.rb
@@ -34,7 +34,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_1_old_persons_shared_ownership_joint_purchase_value_check
age_1_old_persons_shared_ownership_value_check
buyer_1_gender_identity
- gender_1_retirement_value_check
buyer_1_ethnic_group
buyer_1_ethnic_background_black
buyer_1_ethnic_background_asian
@@ -55,7 +54,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_2_buyer_retirement_value_check
buyer_2_age_student_not_child_value_check
buyer_2_gender_identity
- gender_2_buyer_retirement_value_check
buyer_2_working_situation
working_situation_2_retirement_value_check_joint_purchase
working_situation_buyer_2_income_min_value_check
@@ -71,7 +69,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_2_retirement_value_check
age_2_student_not_child_value_check
person_2_gender_identity
- gender_2_retirement_value_check
person_2_working_situation
working_situation_2_retirement_value_check
working_situation_2_student_not_child_value_check
@@ -82,7 +79,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_3_retirement_value_check
age_3_student_not_child_value_check
person_3_gender_identity
- gender_3_retirement_value_check
person_3_working_situation
working_situation_3_retirement_value_check
working_situation_3_student_not_child_value_check
@@ -93,7 +89,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_4_retirement_value_check
age_4_student_not_child_value_check
person_4_gender_identity
- gender_4_retirement_value_check
person_4_working_situation
working_situation_4_retirement_value_check
working_situation_4_student_not_child_value_check
@@ -104,7 +99,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_5_retirement_value_check
age_5_student_not_child_value_check
person_5_gender_identity
- gender_5_retirement_value_check
person_5_working_situation
working_situation_5_retirement_value_check
working_situation_5_student_not_child_value_check
@@ -115,7 +109,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_6_retirement_value_check
age_6_student_not_child_value_check
person_6_gender_identity
- gender_6_retirement_value_check
person_6_working_situation
working_situation_6_retirement_value_check
working_situation_6_student_not_child_value_check
@@ -142,7 +135,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_1_old_persons_shared_ownership_joint_purchase_value_check
age_1_old_persons_shared_ownership_value_check
buyer_1_gender_identity
- gender_1_retirement_value_check
buyer_1_ethnic_group
buyer_1_ethnic_background_black
buyer_1_ethnic_background_asian
@@ -163,7 +155,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_2_buyer_retirement_value_check
buyer_2_age_student_not_child_value_check
buyer_2_gender_identity
- gender_2_buyer_retirement_value_check
buyer_2_ethnic_group
buyer_2_ethnic_background_black
buyer_2_ethnic_background_asian
@@ -186,7 +177,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_2_retirement_value_check
age_2_student_not_child_value_check
person_2_gender_identity
- gender_2_retirement_value_check
person_2_working_situation
working_situation_2_retirement_value_check
working_situation_2_student_not_child_value_check
@@ -197,7 +187,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_3_retirement_value_check
age_3_student_not_child_value_check
person_3_gender_identity
- gender_3_retirement_value_check
person_3_working_situation
working_situation_3_retirement_value_check
working_situation_3_student_not_child_value_check
@@ -208,7 +197,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_4_retirement_value_check
age_4_student_not_child_value_check
person_4_gender_identity
- gender_4_retirement_value_check
person_4_working_situation
working_situation_4_retirement_value_check
working_situation_4_student_not_child_value_check
@@ -219,7 +207,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_5_retirement_value_check
age_5_student_not_child_value_check
person_5_gender_identity
- gender_5_retirement_value_check
person_5_working_situation
working_situation_5_retirement_value_check
working_situation_5_student_not_child_value_check
@@ -230,7 +217,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_6_retirement_value_check
age_6_student_not_child_value_check
person_6_gender_identity
- gender_6_retirement_value_check
person_6_working_situation
working_situation_6_retirement_value_check
working_situation_6_student_not_child_value_check
@@ -250,10 +236,10 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
%w[
buyer_1_age
age_1_retirement_value_check
+ age_1_not_retired_value_check
age_1_old_persons_shared_ownership_joint_purchase_value_check
age_1_old_persons_shared_ownership_value_check
buyer_1_gender_identity
- gender_1_retirement_value_check
buyer_1_ethnic_group
buyer_1_ethnic_background_black
buyer_1_ethnic_background_asian
@@ -263,6 +249,7 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
buyer_1_nationality
buyer_1_working_situation
working_situation_1_retirement_value_check
+ working_situation_1_not_retired_value_check
working_situation_buyer_1_income_min_value_check
buyer_1_live_in_property
buyer_1_live_in_property_value_check
@@ -272,9 +259,9 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_2_old_persons_shared_ownership_joint_purchase_value_check
age_2_old_persons_shared_ownership_value_check
age_2_buyer_retirement_value_check
+ age_2_buyer_not_retired_value_check
buyer_2_age_student_not_child_value_check
buyer_2_gender_identity
- gender_2_buyer_retirement_value_check
buyer_2_ethnic_group
buyer_2_ethnic_background_black
buyer_2_ethnic_background_asian
@@ -284,6 +271,7 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
buyer_2_nationality
buyer_2_working_situation
working_situation_2_retirement_value_check_joint_purchase
+ working_situation_2_not_retired_value_check_joint_purchase
working_situation_buyer_2_income_min_value_check
buyer_2_working_situation_student_not_child_value_check
buyer_2_live_in_property
@@ -297,12 +285,13 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
relationship_2_student_not_child_value_check
person_2_age
age_2_retirement_value_check
+ age_2_not_retired_value_check
age_2_student_not_child_value_check
age_2_partner_under_16_value_check
person_2_gender_identity
- gender_2_retirement_value_check
person_2_working_situation
working_situation_2_retirement_value_check
+ working_situation_2_not_retired_value_check
working_situation_2_student_not_child_value_check
person_3_known
person_3_relationship_to_buyer_1
@@ -311,12 +300,13 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
relationship_3_student_not_child_value_check
person_3_age
age_3_retirement_value_check
+ age_3_not_retired_value_check
age_3_student_not_child_value_check
age_3_partner_under_16_value_check
person_3_gender_identity
- gender_3_retirement_value_check
person_3_working_situation
working_situation_3_retirement_value_check
+ working_situation_3_not_retired_value_check
working_situation_3_student_not_child_value_check
person_4_known
person_4_relationship_to_buyer_1
@@ -325,12 +315,13 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
relationship_4_student_not_child_value_check
person_4_age
age_4_retirement_value_check
+ age_4_not_retired_value_check
age_4_student_not_child_value_check
age_4_partner_under_16_value_check
person_4_gender_identity
- gender_4_retirement_value_check
person_4_working_situation
working_situation_4_retirement_value_check
+ working_situation_4_not_retired_value_check
working_situation_4_student_not_child_value_check
person_5_known
person_5_relationship_to_buyer_1
@@ -339,12 +330,13 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
relationship_5_student_not_child_value_check
person_5_age
age_5_retirement_value_check
+ age_5_not_retired_value_check
age_5_student_not_child_value_check
age_5_partner_under_16_value_check
person_5_gender_identity
- gender_5_retirement_value_check
person_5_working_situation
working_situation_5_retirement_value_check
+ working_situation_5_not_retired_value_check
working_situation_5_student_not_child_value_check
person_6_known
person_6_relationship_to_buyer_1
@@ -353,12 +345,13 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
relationship_6_student_not_child_value_check
person_6_age
age_6_retirement_value_check
+ age_6_not_retired_value_check
age_6_student_not_child_value_check
age_6_partner_under_16_value_check
person_6_gender_identity
- gender_6_retirement_value_check
person_6_working_situation
working_situation_6_retirement_value_check
+ working_situation_6_not_retired_value_check
working_situation_6_student_not_child_value_check
],
)
diff --git a/spec/models/lettings_log_spec.rb b/spec/models/lettings_log_spec.rb
index 92452f197..e28f0f2c5 100644
--- a/spec/models/lettings_log_spec.rb
+++ b/spec/models/lettings_log_spec.rb
@@ -1345,6 +1345,13 @@ RSpec.describe LettingsLog do
expect(result.third.id).to eq lettings_log_with_postcode.id
end
end
+
+ it "sanitises input for order" do
+ lettings_log_to_search.update!(tenancycode: "' 1234")
+ result = described_class.search_by(lettings_log_to_search.tenancycode)
+ expect(result.count).to eq(1)
+ expect(result.first.id).to eq lettings_log_to_search.id
+ end
end
end
diff --git a/spec/models/location_deactivation_period_spec.rb b/spec/models/location_deactivation_period_spec.rb
index e2cfd5758..6157b893b 100644
--- a/spec/models/location_deactivation_period_spec.rb
+++ b/spec/models/location_deactivation_period_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe LocationDeactivationPeriod do
record.deactivation_date = current_collection_start_date - 1.year
location.location_deactivation_periods.clear
validator.validate(record)
- expect(record.errors[:deactivation_date]).to include "The date must be on or after the 1 April 2023"
+ expect(record.errors[:deactivation_date]).to include "The date must be on or after the 1 April 2023."
end
end
@@ -47,7 +47,7 @@ RSpec.describe LocationDeactivationPeriod do
record.deactivation_date = previous_collection_start_date - 2.years
location.location_deactivation_periods.clear
validator.validate(record)
- expect(record.errors[:deactivation_date]).to include "The date must be on or after the 1 April 2022"
+ expect(record.errors[:deactivation_date]).to include "The date must be on or after the 1 April 2022."
end
end
@@ -80,7 +80,7 @@ RSpec.describe LocationDeactivationPeriod do
location.location_deactivation_periods.clear
validator.validate(record)
start_date = startdate.to_formatted_s(:govuk_date)
- expect(record.errors[:deactivation_date]).to include "The location cannot be deactivated before #{start_date}, the date when it was first available"
+ expect(record.errors[:deactivation_date]).to include "The location cannot be deactivated before #{start_date}, the date when it was first available."
end
end
end
diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb
index 86c02476f..b873affbd 100644
--- a/spec/models/notification_spec.rb
+++ b/spec/models/notification_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe Notification, type: :model do
it "adds an error to page_content" do
notification.valid?
- expect(notification.errors[:page_content]).to include("Enter the page content")
+ expect(notification.errors[:page_content]).to include("Enter the page content.")
end
end
@@ -19,7 +19,7 @@ RSpec.describe Notification, type: :model do
it "adds an error to link_text" do
notification.valid?
- expect(notification.errors[:link_text]).to include("Enter the link text")
+ expect(notification.errors[:link_text]).to include("Enter the link text.")
end
end
end
diff --git a/spec/models/sales_log_spec.rb b/spec/models/sales_log_spec.rb
index a568ca330..ae9b00d4c 100644
--- a/spec/models/sales_log_spec.rb
+++ b/spec/models/sales_log_spec.rb
@@ -214,6 +214,13 @@ RSpec.describe SalesLog, type: :model do
expect(result).to include(have_attributes(id: sales_log_to_search.id))
end
end
+
+ it "sanitises input for order" do
+ sales_log_to_search.update!(purchid: "' 123456")
+ result = described_class.search_by(sales_log_to_search.purchid)
+ expect(result.count).to be >= 1
+ expect(result).to include(have_attributes(id: sales_log_to_search.id))
+ end
end
context "when filtering by year or nil" do
diff --git a/spec/models/scheme_deactivation_period_spec.rb b/spec/models/scheme_deactivation_period_spec.rb
index 9e59399e9..5d4e6f6e2 100644
--- a/spec/models/scheme_deactivation_period_spec.rb
+++ b/spec/models/scheme_deactivation_period_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe SchemeDeactivationPeriod do
record.deactivation_date = current_collection_start_date - 1.year
scheme.scheme_deactivation_periods.clear
validator.validate(record)
- expect(record.errors[:deactivation_date]).to include("The date must be on or after the 1 April 2023")
+ expect(record.errors[:deactivation_date]).to include("The date must be on or after the 1 April 2023.")
end
end
@@ -47,7 +47,7 @@ RSpec.describe SchemeDeactivationPeriod do
record.deactivation_date = previous_collection_start_date - 2.years
scheme.scheme_deactivation_periods.clear
validator.validate(record)
- expect(record.errors[:deactivation_date]).to include("The date must be on or after the 1 April 2022")
+ expect(record.errors[:deactivation_date]).to include("The date must be on or after the 1 April 2022.")
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 5cb6cb580..beb3d589e 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -243,6 +243,128 @@ RSpec.describe User, type: :model do
expect(user.need_two_factor_authentication?(nil)).to be false
end
end
+
+ context "when the user is in non staging environment" do
+ before do
+ allow(Rails.env).to receive(:staging?).and_return(false)
+ end
+
+ context "and the user is in the staging role update email allowlist" do
+ before do
+ allow(Rails.application.credentials).to receive(:[]).with(:staging_role_update_email_allowlist).and_return(["example.com"])
+ end
+
+ context "when the user is a data provider" do
+ it "cannot assign roles" do
+ expect(user.assignable_roles).to eq({})
+ end
+ end
+
+ context "when the user is a data coordinator" do
+ let(:user) { create(:user, :data_coordinator) }
+
+ it "can assign all roles except support" do
+ expect(user.assignable_roles).to eq({
+ data_provider: 1,
+ data_coordinator: 2,
+ })
+ end
+ end
+
+ context "when the user is a Support user" do
+ let(:user) { create(:user, :support) }
+
+ it "can assign all roles" do
+ expect(user.assignable_roles).to eq({
+ data_provider: 1,
+ data_coordinator: 2,
+ support: 99,
+ })
+ end
+ end
+ end
+ end
+
+ context "when the user is in staging environment" do
+ before do
+ allow(Rails.env).to receive(:staging?).and_return(true)
+ end
+
+ context "and the user is not in the staging role update email allowlist" do
+ context "when the user is a data provider" do
+ let(:user) { create(:user, :data_provider) }
+
+ it "cannot assign roles" do
+ expect(user.assignable_roles).to eq({})
+ end
+ end
+
+ context "when the user is a data coordinator" do
+ let(:user) { create(:user, :data_coordinator) }
+
+ it "can assign all roles except support" do
+ expect(user.assignable_roles).to eq({
+ data_provider: 1,
+ data_coordinator: 2,
+ })
+ end
+ end
+
+ context "when the user is a Support user" do
+ let(:user) { create(:user, :support) }
+
+ it "can assign all roles" do
+ expect(user.assignable_roles).to eq({
+ data_provider: 1,
+ data_coordinator: 2,
+ support: 99,
+ })
+ end
+ end
+ end
+
+ context "and the user is in the staging role update email allowlist" do
+ before do
+ allow(Rails.application.credentials).to receive(:[]).with(:staging_role_update_email_allowlist).and_return(["example.com"])
+ end
+
+ context "when the user is a data provider" do
+ let(:user) { create(:user, :data_provider) }
+
+ it "can assign all roles" do
+ expect(user.assignable_roles).to eq({
+ data_provider: 1,
+ data_coordinator: 2,
+ support: 99,
+ })
+ end
+ end
+
+ context "when the user is a data coordinator" do
+ let(:user) { create(:user, :data_coordinator) }
+
+ it "can assign all roles" do
+ expect(user.assignable_roles).to eq({
+ data_provider: 1,
+ data_coordinator: 2,
+ support: 99,
+ })
+ end
+ end
+
+ context "when the user is a Support user" do
+ let(:user) { create(:user, :support) }
+
+ it "can assign all roles" do
+ expect(user.assignable_roles).to eq({
+ data_provider: 1,
+ data_coordinator: 2,
+ support: 99,
+ })
+ end
+ end
+ end
+ end
end
describe "paper trail" do
diff --git a/spec/models/validations/financial_validations_spec.rb b/spec/models/validations/financial_validations_spec.rb
index a6eefa708..4e1aa400f 100644
--- a/spec/models/validations/financial_validations_spec.rb
+++ b/spec/models/validations/financial_validations_spec.rb
@@ -237,9 +237,9 @@ RSpec.describe Validations::FinancialValidations do
record.ecstat1 = 1
financial_validator.validate_net_income(record)
expect(record.errors["earnings"])
- .to eq(["The household’s income cannot be greater than £1,230.00 per week given the household’s working situation"])
+ .to eq(["The household’s income cannot be greater than £1,230.00 per week given the household’s working situation."])
expect(record.errors["ecstat1"])
- .to eq(["The household’s income of £5,000.00 weekly is too high given the household’s working situation"])
+ .to eq(["The household’s income of £5,000.00 weekly is too high given the household’s working situation."])
expect(record.errors["hhmemb"])
.to eq(["The household’s income of £5,000.00 weekly is too high for this number of tenants. Change either the household income or number of tenants."])
end
@@ -254,9 +254,9 @@ RSpec.describe Validations::FinancialValidations do
record.ecstat1 = 1
financial_validator.validate_net_income(record)
expect(record.errors["earnings"])
- .to eq(["The household’s income cannot be less than £90.00 per week given the household’s working situation"])
+ .to eq(["The household’s income cannot be less than £90.00 per week given the household’s working situation."])
expect(record.errors["ecstat1"])
- .to eq(["The household’s income of £50.00 weekly is too low given the household’s working situation"])
+ .to eq(["The household’s income of £50.00 weekly is too low given the household’s working situation."])
expect(record.errors["hhmemb"])
.to eq(["The household’s income of £50.00 weekly is too low for this number of tenants. Change either the household income or number of tenants."])
end
@@ -286,7 +286,7 @@ RSpec.describe Validations::FinancialValidations do
record.ecstat3 = 9
financial_validator.validate_net_income(record)
expect(record.errors["earnings"])
- .to eq(["The household’s income cannot be less than £150.00 per week given the household’s working situation"])
+ .to eq(["The household’s income cannot be less than £150.00 per week given the household’s working situation."])
end
it "adds errors to relevant fields for each tenant when income is too high" do
@@ -301,7 +301,7 @@ RSpec.describe Validations::FinancialValidations do
financial_validator.validate_net_income(record)
(1..record.hhmemb).each do |n|
expect(record.errors["ecstat#{n}"])
- .to eq(["The household’s income of £5,000.00 weekly is too high given the household’s working situation"])
+ .to eq(["The household’s income of £5,000.00 weekly is too high given the household’s working situation."])
end
expect(record.errors["age1"]).to be_empty
expect(record.errors["age2"]).to be_empty
@@ -325,7 +325,7 @@ RSpec.describe Validations::FinancialValidations do
financial_validator.validate_net_income(record)
(1..record.hhmemb).each do |n|
expect(record.errors["ecstat#{n}"])
- .to eq(["The household’s income of £50.00 weekly is too low given the household’s working situation"])
+ .to eq(["The household’s income of £50.00 weekly is too low given the household’s working situation."])
end
(record.hhmemb + 1..8).each do |n|
expect(record.errors["ecstat#{n}"]).to be_empty
@@ -1121,9 +1121,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 5001
financial_validator.validate_care_home_charges(record)
expect(record.errors["chcharge"])
- .to include("Household rent and other charges must be between £10.00 and £5,000.00 if paying weekly for 52 weeks")
+ .to include("Household rent and other charges must be between £10.00 and £5,000.00 if paying weekly for 52 weeks.")
expect(record.errors["period"])
- .to include("Household rent and other charges must be between £10.00 and £5,000.00 if paying weekly for 52 weeks")
+ .to include("Household rent and other charges must be between £10.00 and £5,000.00 if paying weekly for 52 weeks.")
end
it "validates charge when period is monthly" do
@@ -1131,9 +1131,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 21_667
financial_validator.validate_care_home_charges(record)
expect(record.errors["chcharge"])
- .to include("Household rent and other charges must be between £43.00 and £21,666.00 if paying every calendar month")
+ .to include("Household rent and other charges must be between £43.00 and £21,666.00 if paying every calendar month.")
expect(record.errors["period"])
- .to include("Household rent and other charges must be between £43.00 and £21,666.00 if paying every calendar month")
+ .to include("Household rent and other charges must be between £43.00 and £21,666.00 if paying every calendar month.")
end
it "validates charge when period is every 2 weeks" do
@@ -1141,9 +1141,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 12_001
financial_validator.validate_care_home_charges(record)
expect(record.errors["chcharge"])
- .to include("Household rent and other charges must be between £20.00 and £10,000.00 if paying every 2 weeks")
+ .to include("Household rent and other charges must be between £20.00 and £10,000.00 if paying every 2 weeks.")
expect(record.errors["period"])
- .to include("Household rent and other charges must be between £20.00 and £10,000.00 if paying every 2 weeks")
+ .to include("Household rent and other charges must be between £20.00 and £10,000.00 if paying every 2 weeks.")
end
it "validates charge when period is every 4 weeks" do
@@ -1151,9 +1151,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 24_001
financial_validator.validate_care_home_charges(record)
expect(record.errors["chcharge"])
- .to include("Household rent and other charges must be between £40.00 and £20,000.00 if paying every 4 weeks")
+ .to include("Household rent and other charges must be between £40.00 and £20,000.00 if paying every 4 weeks.")
expect(record.errors["period"])
- .to include("Household rent and other charges must be between £40.00 and £20,000.00 if paying every 4 weeks")
+ .to include("Household rent and other charges must be between £40.00 and £20,000.00 if paying every 4 weeks.")
end
end
@@ -1209,9 +1209,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 9
financial_validator.validate_care_home_charges(record)
expect(record.errors["chcharge"])
- .to include("Household rent and other charges must be between £10.00 and £5,000.00 if paying weekly for 52 weeks")
+ .to include("Household rent and other charges must be between £10.00 and £5,000.00 if paying weekly for 52 weeks.")
expect(record.errors["period"])
- .to include("Household rent and other charges must be between £10.00 and £5,000.00 if paying weekly for 52 weeks")
+ .to include("Household rent and other charges must be between £10.00 and £5,000.00 if paying weekly for 52 weeks.")
end
it "validates charge when period is monthly" do
@@ -1219,9 +1219,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 42
financial_validator.validate_care_home_charges(record)
expect(record.errors["chcharge"])
- .to include("Household rent and other charges must be between £43.00 and £21,666.00 if paying every calendar month")
+ .to include("Household rent and other charges must be between £43.00 and £21,666.00 if paying every calendar month.")
expect(record.errors["period"])
- .to include("Household rent and other charges must be between £43.00 and £21,666.00 if paying every calendar month")
+ .to include("Household rent and other charges must be between £43.00 and £21,666.00 if paying every calendar month.")
end
it "validates charge when period is every 2 weeks" do
@@ -1229,9 +1229,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 19
financial_validator.validate_care_home_charges(record)
expect(record.errors["chcharge"])
- .to include("Household rent and other charges must be between £20.00 and £10,000.00 if paying every 2 weeks")
+ .to include("Household rent and other charges must be between £20.00 and £10,000.00 if paying every 2 weeks.")
expect(record.errors["period"])
- .to include("Household rent and other charges must be between £20.00 and £10,000.00 if paying every 2 weeks")
+ .to include("Household rent and other charges must be between £20.00 and £10,000.00 if paying every 2 weeks.")
end
it "validates charge when period is every 4 weeks" do
@@ -1239,9 +1239,9 @@ RSpec.describe Validations::FinancialValidations do
record.chcharge = 39
financial_validator.validate_care_home_charges(record)
expect(record.errors["chcharge"])
- .to include("Household rent and other charges must be between £40.00 and £20,000.00 if paying every 4 weeks")
+ .to include("Household rent and other charges must be between £40.00 and £20,000.00 if paying every 4 weeks.")
expect(record.errors["period"])
- .to include("Household rent and other charges must be between £40.00 and £20,000.00 if paying every 4 weeks")
+ .to include("Household rent and other charges must be between £40.00 and £20,000.00 if paying every 4 weeks.")
end
end
end
diff --git a/spec/models/validations/household_validations_spec.rb b/spec/models/validations/household_validations_spec.rb
index 7c8e023d0..b240b7b2a 100644
--- a/spec/models/validations/household_validations_spec.rb
+++ b/spec/models/validations/household_validations_spec.rb
@@ -763,7 +763,7 @@ RSpec.describe Validations::HouseholdValidations do
it "is invalid" do
household_validator.validate_combination_of_housing_needs_responses(record)
- error_message = ["If somebody in the household has disabled access needs, they must have the access needs listed, or other access needs"]
+ error_message = ["If somebody in the household has disabled access needs, they must have the access needs listed, or other access needs."]
expect(record.errors["housingneeds"]).to eq(error_message)
expect(record.errors["housingneeds_type"]).to eq(error_message)
@@ -779,7 +779,7 @@ RSpec.describe Validations::HouseholdValidations do
it "is invalid" do
household_validator.validate_combination_of_housing_needs_responses(record)
- error_message = ["If somebody in the household has disabled access needs, they must have the access needs listed, or other access needs"]
+ error_message = ["If somebody in the household has disabled access needs, they must have the access needs listed, or other access needs."]
expect(record.errors["housingneeds"]).to eq(error_message)
expect(record.errors["housingneeds_type"]).to eq(error_message)
diff --git a/spec/models/validations/property_validations_spec.rb b/spec/models/validations/property_validations_spec.rb
index dcbcae337..3f6f9877c 100644
--- a/spec/models/validations/property_validations_spec.rb
+++ b/spec/models/validations/property_validations_spec.rb
@@ -276,7 +276,7 @@ RSpec.describe Validations::PropertyValidations do
it "adds an error" do
property_validator.validate_uprn(log)
- expect(log.errors.added?(:uprn, "UPRN must be 12 digits or less")).to be true
+ expect(log.errors.added?(:uprn, "UPRN must be 12 digits or less.")).to be true
end
end
@@ -285,7 +285,7 @@ RSpec.describe Validations::PropertyValidations do
it "adds an error" do
property_validator.validate_uprn(log)
- expect(log.errors.added?(:uprn, "UPRN must be 12 digits or less")).to be true
+ expect(log.errors.added?(:uprn, "UPRN must be 12 digits or less.")).to be true
end
end
diff --git a/spec/models/validations/sales/financial_validations_spec.rb b/spec/models/validations/sales/financial_validations_spec.rb
index d9f47d39a..b0e80303a 100644
--- a/spec/models/validations/sales/financial_validations_spec.rb
+++ b/spec/models/validations/sales/financial_validations_spec.rb
@@ -187,7 +187,7 @@ RSpec.describe Validations::Sales::FinancialValidations do
record.stairowned = 40
record.jointpur = 1
financial_validator.validate_percentage_bought_not_greater_than_percentage_owned(record)
- expect(record.errors["stairowned"]).to include("Total percentage buyers now own must be more than percentage bought in this transaction")
+ expect(record.errors["stairowned"]).to include("Total percentage buyers now own must be more than percentage bought in this transaction.")
end
it "adds an error to stairowned and not stairbought if the percentage bought is more than the percentage owned for non joint purchase" do
@@ -195,7 +195,7 @@ RSpec.describe Validations::Sales::FinancialValidations do
record.stairowned = 40
record.jointpur = 2
financial_validator.validate_percentage_bought_not_greater_than_percentage_owned(record)
- expect(record.errors["stairowned"]).to include("Total percentage buyer now owns must be more than percentage bought in this transaction")
+ expect(record.errors["stairowned"]).to include("Total percentage buyer now owns must be more than percentage bought in this transaction.")
end
end
@@ -270,8 +270,8 @@ RSpec.describe Validations::Sales::FinancialValidations do
[2, 16, 18, 24].each do |type|
record.type = type
financial_validator.validate_percentage_bought_at_least_threshold(record)
- expect(record.errors["stairbought"]).to eq(["The minimum increase in equity while staircasing is 10%"])
- expect(record.errors["type"]).to eq(["The minimum increase in equity while staircasing is 10% for this shared ownership type"])
+ expect(record.errors["stairbought"]).to eq(["The minimum increase in equity while staircasing is 10%."])
+ expect(record.errors["type"]).to eq(["The minimum increase in equity while staircasing is 10% for this shared ownership type."])
record.errors.clear
end
@@ -279,8 +279,8 @@ RSpec.describe Validations::Sales::FinancialValidations do
[28, 30, 31, 32].each do |type|
record.type = type
financial_validator.validate_percentage_bought_at_least_threshold(record)
- expect(record.errors["stairbought"]).to eq(["The minimum increase in equity while staircasing is 1%"])
- expect(record.errors["type"]).to eq(["The minimum increase in equity while staircasing is 1% for this shared ownership type"])
+ expect(record.errors["stairbought"]).to eq(["The minimum increase in equity while staircasing is 1%."])
+ expect(record.errors["type"]).to eq(["The minimum increase in equity while staircasing is 1% for this shared ownership type."])
record.errors.clear
end
end
diff --git a/spec/models/validations/sales/household_validations_spec.rb b/spec/models/validations/sales/household_validations_spec.rb
index 12135c585..510681420 100644
--- a/spec/models/validations/sales/household_validations_spec.rb
+++ b/spec/models/validations/sales/household_validations_spec.rb
@@ -345,8 +345,8 @@ RSpec.describe Validations::Sales::HouseholdValidations 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")
+ 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
@@ -420,7 +420,7 @@ RSpec.describe Validations::Sales::HouseholdValidations do
record.ecstat1 = 9
household_validator.validate_buyer_not_child(record)
expect(record.errors["ecstat1"])
- .to include("Buyer 1 cannot have a working situation of child under 16")
+ .to include("Buyer 1 cannot have a working situation of child under 16.")
end
it "validates buyer 2 isn't a child" do
@@ -428,7 +428,7 @@ RSpec.describe Validations::Sales::HouseholdValidations do
record.ecstat2 = 9
household_validator.validate_buyer_not_child(record)
expect(record.errors["ecstat2"])
- .to include("Buyer 2 cannot have a working situation of child under 16")
+ .to include("Buyer 2 cannot have a working situation of child under 16.")
end
it "allows person 2 to be a child" do
diff --git a/spec/models/validations/sales/property_validations_spec.rb b/spec/models/validations/sales/property_validations_spec.rb
index 6d4c0c9ae..df0396845 100644
--- a/spec/models/validations/sales/property_validations_spec.rb
+++ b/spec/models/validations/sales/property_validations_spec.rb
@@ -51,9 +51,9 @@ RSpec.describe Validations::Sales::PropertyValidations do
record.ppostcode_full = "SW1A 0AA"
record.jointpur = 1
property_validator.validate_postcodes_match_if_discounted_ownership(record)
- expect(record.errors["postcode_full"]).to include("Buyers’ last accommodation and discounted ownership postcodes must match")
- expect(record.errors["ppostcode_full"]).to include("Buyers’ last accommodation and discounted ownership postcodes must match")
- expect(record.errors["ownershipsch"]).to include("Buyers’ last accommodation and discounted ownership postcodes must match")
+ expect(record.errors["postcode_full"]).to include("Buyers’ last accommodation and discounted ownership postcodes must match.")
+ expect(record.errors["ppostcode_full"]).to include("Buyers’ last accommodation and discounted ownership postcodes must match.")
+ expect(record.errors["ownershipsch"]).to include("Buyers’ last accommodation and discounted ownership postcodes must match.")
end
it "when postcodes do not match an error is added for non joint purchase" do
@@ -61,9 +61,9 @@ RSpec.describe Validations::Sales::PropertyValidations do
record.ppostcode_full = "SW1A 0AA"
record.jointpur = 2
property_validator.validate_postcodes_match_if_discounted_ownership(record)
- expect(record.errors["postcode_full"]).to include("Buyer’s last accommodation and discounted ownership postcodes must match")
- expect(record.errors["ppostcode_full"]).to include("Buyer’s last accommodation and discounted ownership postcodes must match")
- expect(record.errors["ownershipsch"]).to include("Buyer’s last accommodation and discounted ownership postcodes must match")
+ expect(record.errors["postcode_full"]).to include("Buyer’s last accommodation and discounted ownership postcodes must match.")
+ expect(record.errors["ppostcode_full"]).to include("Buyer’s last accommodation and discounted ownership postcodes must match.")
+ expect(record.errors["ownershipsch"]).to include("Buyer’s last accommodation and discounted ownership postcodes must match.")
end
it "does not add error for 2024 log" do
@@ -93,8 +93,8 @@ RSpec.describe Validations::Sales::PropertyValidations do
it "does add an error if it's a bedsit" do
property_validator.validate_bedsit_number_of_beds(record)
- expect(record.errors.added?(:proptype, "Answer cannot be 'Bedsit' if the property has 2 or more bedrooms")).to be true
- expect(record.errors.added?(:beds, "Number of bedrooms must be 1 if the property is a bedsit")).to be true
+ expect(record.errors.added?(:proptype, "Answer cannot be 'Bedsit' if the property has 2 or more bedrooms.")).to be true
+ expect(record.errors.added?(:beds, "Number of bedrooms must be 1 if the property is a bedsit.")).to be true
end
it "does not add an error if proptype is undefined" do
@@ -120,7 +120,7 @@ RSpec.describe Validations::Sales::PropertyValidations do
it "adds an error" do
property_validator.validate_uprn(record)
- expect(record.errors.added?(:uprn, "UPRN must be 12 digits or less")).to be true
+ expect(record.errors.added?(:uprn, "UPRN must be 12 digits or less.")).to be true
end
end
@@ -129,7 +129,7 @@ RSpec.describe Validations::Sales::PropertyValidations do
it "adds an error" do
property_validator.validate_uprn(record)
- expect(record.errors.added?(:uprn, "UPRN must be 12 digits or less")).to be true
+ expect(record.errors.added?(:uprn, "UPRN must be 12 digits or less.")).to be true
end
end
diff --git a/spec/models/validations/sales/sale_information_validations_spec.rb b/spec/models/validations/sales/sale_information_validations_spec.rb
index 6cfa71d21..945d1b25b 100644
--- a/spec/models/validations/sales/sale_information_validations_spec.rb
+++ b/spec/models/validations/sales/sale_information_validations_spec.rb
@@ -147,10 +147,10 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
sale_information_validator.validate_exchange_date(record)
expect(record.errors[:exdate]).to eq(
- ["Contract exchange date must be less than 1 year before sale completion date"],
+ ["Contract exchange date must be less than 1 year before sale completion date."],
)
expect(record.errors[:saledate]).to eq(
- ["Sale completion date must be less than 1 year after contract exchange date"],
+ ["Sale completion date must be less than 1 year after contract exchange date."],
)
end
end
@@ -162,10 +162,10 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
sale_information_validator.validate_exchange_date(record)
expect(record.errors[:exdate]).to eq(
- ["Contract exchange date must be before sale completion date"],
+ ["Contract exchange date must be before sale completion date."],
)
expect(record.errors[:saledate]).to eq(
- ["Sale completion date must be after contract exchange date"],
+ ["Sale completion date must be after contract exchange date."],
)
end
end
@@ -671,7 +671,7 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
it "adds an error" do
sale_information_validator.validate_grant_amount(record)
- expect(record.errors[:grant]).to include("Loan, grants or subsidies must be between £9,000 and £16,000")
+ expect(record.errors[:grant]).to include("Loan, grants or subsidies must be between £9,000 and £16,000.")
end
end
@@ -681,7 +681,7 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
it "adds an error" do
sale_information_validator.validate_grant_amount(record)
- expect(record.errors[:grant]).to include("Loan, grants or subsidies must be between £9,000 and £16,000")
+ expect(record.errors[:grant]).to include("Loan, grants or subsidies must be between £9,000 and £16,000.")
end
end
diff --git a/spec/models/validations/sales/setup_validations_spec.rb b/spec/models/validations/sales/setup_validations_spec.rb
index a9b3563e6..797b0c22c 100644
--- a/spec/models/validations/sales/setup_validations_spec.rb
+++ b/spec/models/validations/sales/setup_validations_spec.rb
@@ -91,7 +91,7 @@ RSpec.describe Validations::Sales::SetupValidations do
it "adds error" do
setup_validator.validate_saledate_collection_year(record)
- expect(record.errors[:saledate]).to include("Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025")
+ expect(record.errors[:saledate]).to include("Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025.")
end
end
@@ -105,7 +105,7 @@ RSpec.describe Validations::Sales::SetupValidations do
it "adds error" do
setup_validator.validate_saledate_collection_year(record)
- expect(record.errors[:saledate]).to include("Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025")
+ expect(record.errors[:saledate]).to include("Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025.")
end
end
@@ -119,7 +119,7 @@ RSpec.describe Validations::Sales::SetupValidations do
it "cannot create new logs for the archived collection year" do
record.saledate = Time.zone.local(2023, 1, 1)
setup_validator.validate_saledate_collection_year(record)
- expect(record.errors["saledate"]).to include(match "Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025")
+ expect(record.errors["saledate"]).to include(match "Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025.")
end
it "can edit already created logs for the previous collection year" do
@@ -127,7 +127,7 @@ RSpec.describe Validations::Sales::SetupValidations do
record.save!(validate: false)
record.saledate = Time.zone.local(2024, 1, 1)
setup_validator.validate_saledate_collection_year(record)
- expect(record.errors["saledate"]).not_to include(match "Enter a date within the 24/25 collection year, which is between 1st April 2024 and 31st March 2025")
+ expect(record.errors["saledate"]).not_to include(match "Enter a date within the 24/25 collection year, which is between 1st April 2024 and 31st March 2025.")
end
end
@@ -142,7 +142,7 @@ RSpec.describe Validations::Sales::SetupValidations do
record.update!(saledate: nil)
record.saledate = Time.zone.local(2023, 1, 1)
setup_validator.validate_saledate_collection_year(record)
- expect(record.errors["saledate"]).to include(match "Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025")
+ expect(record.errors["saledate"]).to include(match "Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025.")
end
it "cannot edit already created logs for the archived collection year" do
@@ -150,7 +150,7 @@ RSpec.describe Validations::Sales::SetupValidations do
record.save!(validate: false)
record.saledate = Time.zone.local(2023, 1, 1)
setup_validator.validate_saledate_collection_year(record)
- expect(record.errors["saledate"]).to include(match "Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025")
+ expect(record.errors["saledate"]).to include(match "Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025.")
end
end
end
@@ -183,7 +183,7 @@ RSpec.describe Validations::Sales::SetupValidations do
it "adds an error" do
setup_validator.validate_saledate_two_weeks(record)
- expect(record.errors[:saledate]).to include("Sale completion date must not be later than 14 days from today’s date")
+ expect(record.errors[:saledate]).to include("Sale completion date must not be later than 14 days from today’s date.")
end
end
end
diff --git a/spec/models/validations/setup_validations_spec.rb b/spec/models/validations/setup_validations_spec.rb
index 884c4e22e..f39b71328 100644
--- a/spec/models/validations/setup_validations_spec.rb
+++ b/spec/models/validations/setup_validations_spec.rb
@@ -157,7 +157,7 @@ RSpec.describe Validations::SetupValidations do
record.startdate = Time.zone.local(2024, 4, 1)
setup_validator.validate_startdate_setup(record)
expect(record.errors["startdate"].length).to be >= 2
- expect(record.errors["startdate"][0]).to eq("Enter a date within the 23/24 collection year, which is between 1st April 2023 and 31st March 2024")
+ expect(record.errors["startdate"][0]).to eq("Enter a date within the 23/24 collection year, which is between 1st April 2023 and 31st March 2024.")
expect(record.errors["startdate"][1]).to eq(I18n.t("validations.setup.startdate.later_than_14_days_after"))
end
end
@@ -700,7 +700,7 @@ RSpec.describe Validations::SetupValidations do
record.location = location
setup_validator.validate_location(record)
expect(record.errors["location_id"])
- .to include("This location is incomplete. Select another location or update this one")
+ .to include("This location is incomplete. Select another location or update this one.")
end
it "produces no error when location is completes" do
diff --git a/spec/models/validations/shared_validations_spec.rb b/spec/models/validations/shared_validations_spec.rb
index fb389d175..b4d7fb0b8 100644
--- a/spec/models/validations/shared_validations_spec.rb
+++ b/spec/models/validations/shared_validations_spec.rb
@@ -75,14 +75,14 @@ RSpec.describe Validations::SharedValidations do
sales_log.details_known_2 = 1
sales_log.age2 = 130
shared_validator.validate_numeric_min_max(sales_log)
- expect(sales_log.errors["age2"].first).to eq("Person 2’s age must be between 0 and 110")
+ expect(sales_log.errors["age2"].first).to eq("Person 2’s age must be between 0 and 110.")
end
it "validates that buyer 2's age is between 0 and 110 for joint purchase" do
sales_log.jointpur = 1
sales_log.age2 = 130
shared_validator.validate_numeric_min_max(sales_log)
- expect(sales_log.errors["age2"].first).to eq("Buyer 2’s age must be between 16 and 110")
+ expect(sales_log.errors["age2"].first).to eq("Buyer 2’s age must be between 16 and 110.")
end
end
end
diff --git a/spec/requests/lettings_logs_controller_spec.rb b/spec/requests/lettings_logs_controller_spec.rb
index 4cd0a0609..f4fa6f1fb 100644
--- a/spec/requests/lettings_logs_controller_spec.rb
+++ b/spec/requests/lettings_logs_controller_spec.rb
@@ -1570,7 +1570,7 @@ RSpec.describe LettingsLogsController, type: :request do
it "returns an error message" do
json_response = JSON.parse(response.body)
- expect(json_response["errors"]).to eq({ "age1" => ["Lead tenant’s age must be between 16 and 120"] })
+ expect(json_response["errors"]).to eq({ "age1" => ["Lead tenant’s age must be between 16 and 120."] })
end
end
diff --git a/spec/requests/sales_logs_controller_spec.rb b/spec/requests/sales_logs_controller_spec.rb
index 1083c7f58..607349a68 100644
--- a/spec/requests/sales_logs_controller_spec.rb
+++ b/spec/requests/sales_logs_controller_spec.rb
@@ -93,7 +93,7 @@ RSpec.describe SalesLogsController, type: :request do
it "validates sales log parameters" do
json_response = JSON.parse(response.body)
expect(response).to have_http_status(:unprocessable_entity)
- expect(json_response["errors"]).to match_array([["beds", ["Number of bedrooms must be 1 if the property is a bedsit"]], ["proptype", ["Answer cannot be 'Bedsit' if the property has 2 or more bedrooms"]]])
+ expect(json_response["errors"]).to match_array([["beds", ["Number of bedrooms must be 1 if the property is a bedsit."]], ["proptype", ["Answer cannot be 'Bedsit' if the property has 2 or more bedrooms."]]])
end
end
end
diff --git a/spec/services/bulk_upload/lettings/validator_spec.rb b/spec/services/bulk_upload/lettings/validator_spec.rb
index 88f4801f3..a8773b63d 100644
--- a/spec/services/bulk_upload/lettings/validator_spec.rb
+++ b/spec/services/bulk_upload/lettings/validator_spec.rb
@@ -51,7 +51,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
it "is not valid" do
expect(validator).not_to be_valid
- expect(validator.errors["base"]).to eql(["Incorrect number of fields, please ensure you have used the correct template"])
+ expect(validator.errors["base"]).to eql(["Incorrect number of fields, please ensure you have used the correct template."])
end
end
@@ -68,7 +68,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
it "is not valid" do
expect(validator).not_to be_valid
- expect(validator.errors["base"]).to eql(["Incorrect number of fields, please ensure you have used the correct template"])
+ expect(validator.errors["base"]).to eql(["Incorrect number of fields, please ensure you have used the correct template."])
end
end
end
@@ -90,7 +90,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
it "is not valid" do
expect(validator).not_to be_valid
- expect(validator.errors["base"]).to eql(["Incorrect start dates, please ensure you have used the correct template"])
+ expect(validator.errors["base"]).to eql(["Incorrect start dates, please ensure you have used the correct template."])
end
end
end
@@ -179,7 +179,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
end
it "creates errors" do
- expect { validator.call }.to change(BulkUploadError.where(category: :setup, error: "This is a duplicate of a log in your file"), :count)
+ expect { validator.call }.to change(BulkUploadError.where(category: :setup, error: "This is a duplicate of a log in your file."), :count)
end
end
diff --git a/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb
index e38396328..18c28189a 100644
--- a/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb
+++ b/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb
@@ -263,7 +263,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
it "adds an error to all (and only) the fields used to determine duplicates" do
parser.valid?
- error_message = "This is a duplicate log"
+ error_message = "This is a duplicate log."
[
:field_1, # owning_organisation
@@ -300,7 +300,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
it "adds an error to all the fields used to determine duplicates" do
parser.valid?
- error_message = "This is a duplicate log"
+ error_message = "This is a duplicate log."
[
:field_1, # owning_organisation
@@ -338,7 +338,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
it "adds an error to all the fields used to determine duplicates" do
parser.valid?
- error_message = "This is a duplicate log"
+ error_message = "This is a duplicate log."
[
:field_1, # owning_organisation
@@ -385,7 +385,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
it "adds an error to all the fields used to determine duplicates" do
parser.valid?
- error_message = "This is a duplicate log"
+ error_message = "This is a duplicate log."
[
:field_1, # owning_organisation
@@ -444,7 +444,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
it "does not add an error to all the fields used to determine duplicates" do
parser_too.valid?
- error_message = "This is a duplicate log"
+ error_message = "This is a duplicate log."
[
:field_1, # owning_organisation
@@ -479,7 +479,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
it "adds an error to all the fields used to determine duplicates" do
parser.valid?
- error_message = "This is a duplicate log"
+ error_message = "This is a duplicate log."
[
:field_1, # owning_organisation
@@ -526,7 +526,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
it "adds an error to all the fields used to determine duplicates" do
parser.valid?
- error_message = "This is a duplicate log"
+ error_message = "This is a duplicate log."
[
:field_1, # owning_organisation
@@ -585,7 +585,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
it "does not add an error to all the fields used to determine duplicates" do
parser_too.valid?
- error_message = "This is a duplicate log"
+ error_message = "This is a duplicate log."
[
:field_1, # owning_organisation
@@ -686,7 +686,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
it "cannot be nulled" do
parser.valid?
- expect(parser.errors[:field_45]).to eq(["You must show the MHCLG privacy notice to the tenant before you can submit this log"])
+ expect(parser.errors[:field_45]).to eq(["You must show the MHCLG privacy notice to the tenant before you can submit this log."])
end
end
end
@@ -979,7 +979,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
parser.valid?
expect(parser.errors[:field_15]).to be_blank
- expect(parser.errors.where(:field_16, category: :setup).map(&:message)).to eq(["This scheme code does not belong to the owning organisation or managing organisation"])
+ expect(parser.errors.where(:field_16, category: :setup).map(&:message)).to eq(["This scheme code does not belong to the owning organisation or managing organisation."])
expect(parser.errors[:field_17]).to be_blank
end
end
@@ -1018,7 +1018,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
expect(parser.errors[:field_15]).to be_blank
expect(parser.errors[:field_16]).to be_blank
- expect(parser.errors.where(:field_17, category: :setup).map(&:message)).to eq(["Location code must relate to a location that is owned by the owning organisation or managing organisation"])
+ expect(parser.errors.where(:field_17, category: :setup).map(&:message)).to eq(["Location code must relate to a location that is owned by the owning organisation or managing organisation."])
end
end
@@ -1054,7 +1054,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
expect(parser.errors[:field_15]).to be_blank
expect(parser.errors[:field_16]).to be_blank
- expect(parser.errors.where(:field_17, category: :setup).map(&:message)).to eq(["Location code must relate to a location that is owned by the owning organisation or managing organisation"])
+ expect(parser.errors.where(:field_17, category: :setup).map(&:message)).to eq(["Location code must relate to a location that is owned by the owning organisation or managing organisation."])
end
end
@@ -1067,7 +1067,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
parser.valid?
expect(parser.errors[:field_15]).to be_blank
- expect(parser.errors.where(:field_16, category: :setup).map(&:message)).to eq(["This scheme code does not belong to the owning organisation or managing organisation"])
+ expect(parser.errors.where(:field_16, category: :setup).map(&:message)).to eq(["This scheme code does not belong to the owning organisation or managing organisation."])
expect(parser.errors[:field_17]).to be_blank
end
end
@@ -1106,8 +1106,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
parser.valid?
expect(parser.errors[:field_15]).to be_blank
- expect(parser.errors.where(:field_16).map(&:message)).to eq(["This location is incomplete. Select another location or update this one"])
- expect(parser.errors.where(:field_17).map(&:message)).to eq(["This location is incomplete. Select another location or update this one"])
+ expect(parser.errors.where(:field_16).map(&:message)).to eq(["This location is incomplete. Select another location or update this one."])
+ expect(parser.errors.where(:field_17).map(&:message)).to eq(["This location is incomplete. Select another location or update this one."])
end
end
end
@@ -1122,7 +1122,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
it "returns a setup error" do
parser.valid?
- expect(parser.errors.where(:field_15, category: :setup).map(&:message)).to eq(["This management group code does not belong to the owning organisation or managing organisation"])
+ expect(parser.errors.where(:field_15, category: :setup).map(&:message)).to eq(["This management group code does not belong to the owning organisation or managing organisation."])
expect(parser.errors[:field_16]).to be_blank
expect(parser.errors[:field_17]).to be_blank
end
@@ -1147,7 +1147,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
parser.valid?
expect(parser.errors[:field_15]).to be_blank
- expect(parser.errors.where(:field_16, category: :setup).map(&:message)).to eq(["Scheme code must relate to a scheme that is owned by the owning organisation or managing organisation"])
+ expect(parser.errors.where(:field_16, category: :setup).map(&:message)).to eq(["Scheme code must relate to a scheme that is owned by the owning organisation or managing organisation."])
expect(parser.errors[:field_17]).to be_blank
end
end
@@ -1173,7 +1173,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
parser.valid?
expect(parser.errors[:field_15]).to be_blank
- expect(parser.errors.where(:field_16, category: :setup).map(&:message)).to eq(["Scheme code must relate to a scheme that is owned by the owning organisation or managing organisation"])
+ expect(parser.errors.where(:field_16, category: :setup).map(&:message)).to eq(["Scheme code must relate to a scheme that is owned by the owning organisation or managing organisation."])
expect(parser.errors[:field_17]).to be_blank
end
end
@@ -1186,7 +1186,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
it "returns a setup error" do
parser.valid?
- expect(parser.errors.where(:field_15, category: :setup).map(&:message)).to eq(["This management group code does not belong to the owning organisation or managing organisation"])
+ expect(parser.errors.where(:field_15, category: :setup).map(&:message)).to eq(["This management group code does not belong to the owning organisation or managing organisation."])
expect(parser.errors[:field_16]).to be_blank
expect(parser.errors[:field_17]).to be_blank
end
@@ -1480,7 +1480,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
it "returns an error" do
parser.valid?
- expect(parser.errors[:field_9]).to include("Tenancy start year must be 2 digits")
+ expect(parser.errors[:field_9]).to include("Tenancy start year must be 2 digits.")
end
end
@@ -1543,7 +1543,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
parser.valid?
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
- expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code is incorrect")
+ expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code is incorrect.")
end
it "blocks log creation" do
@@ -1561,7 +1561,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
parser.valid?
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
- expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock")
+ expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock.")
end
it "blocks log creation" do
@@ -1579,7 +1579,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
parser.valid?
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
- expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("You do not have permission to add logs for this owning organisation")
+ expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("You do not have permission to add logs for this owning organisation.")
end
it "blocks log creation" do
@@ -1642,7 +1642,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
parser.valid?
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
- expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("The managing organisation code is incorrect")
+ expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("The managing organisation code is incorrect.")
end
it "blocks log creation" do
@@ -1658,7 +1658,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
parser.valid?
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
- expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("The managing organisation code is incorrect")
+ expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("The managing organisation code is incorrect.")
end
it "blocks log creation" do
@@ -1676,7 +1676,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
parser.valid?
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
- expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("This managing organisation does not have a relationship with the owning organisation")
+ expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("This managing organisation does not have a relationship with the owning organisation.")
end
it "blocks log creation" do
@@ -2710,8 +2710,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
it "sets error on housingneeds a and b" do
parser.valid?
- expect(parser.errors[:field_83]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected")
- expect(parser.errors[:field_84]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected")
+ expect(parser.errors[:field_83]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.")
+ expect(parser.errors[:field_84]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.")
expect(parser.errors[:field_85]).to be_blank
end
end
@@ -2721,8 +2721,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
it "sets error on housingneeds a and c" do
parser.valid?
- expect(parser.errors[:field_83]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected")
- expect(parser.errors[:field_85]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected")
+ expect(parser.errors[:field_83]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.")
+ expect(parser.errors[:field_85]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.")
expect(parser.errors[:field_84]).to be_blank
end
end
@@ -2732,8 +2732,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
it "sets error on housingneeds b and c" do
parser.valid?
- expect(parser.errors[:field_84]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected")
- expect(parser.errors[:field_85]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected")
+ expect(parser.errors[:field_84]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.")
+ expect(parser.errors[:field_85]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.")
expect(parser.errors[:field_83]).to be_blank
end
end
@@ -2743,8 +2743,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
it "sets error on housingneeds a and g" do
parser.valid?
- expect(parser.errors[:field_87]).to include("No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs")
- expect(parser.errors[:field_83]).to include("No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs")
+ expect(parser.errors[:field_87]).to include("No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs.")
+ expect(parser.errors[:field_83]).to include("No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs.")
expect(parser.errors[:field_84]).to be_blank
expect(parser.errors[:field_85]).to be_blank
end
@@ -2767,8 +2767,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
it "sets error on housingneeds a and h" do
parser.valid?
- expect(parser.errors[:field_88]).to include("Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs")
- expect(parser.errors[:field_83]).to include("Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs")
+ expect(parser.errors[:field_88]).to include("Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs.")
+ expect(parser.errors[:field_83]).to include("Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs.")
expect(parser.errors[:field_84]).to be_blank
expect(parser.errors[:field_85]).to be_blank
end
diff --git a/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb
index 3faa0a699..3910e1281 100644
--- a/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb
+++ b/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb
@@ -283,7 +283,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "adds an error to all (and only) the fields used to determine duplicates" do
parser.valid?
- error_message = "This is a duplicate log"
+ error_message = "This is a duplicate log."
[
:field_1, # owning_organisation
@@ -339,7 +339,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "adds an error to all the fields used to determine duplicates" do
parser.valid?
- error_message = "This is a duplicate log"
+ error_message = "This is a duplicate log."
[
:field_1, # owning_organisation
@@ -381,7 +381,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "adds an error to all the fields used to determine duplicates" do
parser.valid?
- error_message = "This is a duplicate log"
+ error_message = "This is a duplicate log."
[
:field_1, # owning_organisation
@@ -432,7 +432,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "adds an error to all the fields used to determine duplicates" do
parser.valid?
- error_message = "This is a duplicate log"
+ error_message = "This is a duplicate log."
[
:field_1, # owning_organisation
@@ -492,7 +492,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "does not add an error to all the fields used to determine duplicates" do
parser_too.valid?
- error_message = "This is a duplicate log"
+ error_message = "This is a duplicate log."
[
:field_1, # owning_organisation
@@ -527,7 +527,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "adds an error to all the fields used to determine duplicates" do
parser.valid?
- error_message = "This is a duplicate log"
+ error_message = "This is a duplicate log."
[
:field_1, # owning_organisation
@@ -574,7 +574,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "adds an error to all the fields used to determine duplicates" do
parser.valid?
- error_message = "This is a duplicate log"
+ error_message = "This is a duplicate log."
[
:field_1, # owning_organisation
@@ -634,7 +634,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "does not add an error to all the fields used to determine duplicates" do
parser_too.valid?
- error_message = "This is a duplicate log"
+ error_message = "This is a duplicate log."
[
:field_1, # owning_organisation
@@ -747,7 +747,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "cannot be nulled" do
parser.valid?
- expect(parser.errors[:field_15]).to eq(["You must show or give the tenant access to the MHCLG privacy notice before you can submit this log"])
+ expect(parser.errors[:field_15]).to eq(["You must show or give the tenant access to the MHCLG privacy notice before you can submit this log."])
end
end
@@ -815,6 +815,23 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
end
end
+ context "when blank and bulk upload user is support" do
+ let(:bulk_upload) { create(:bulk_upload, :sales, user: create(:user, :support), year: 2024) }
+
+ let(:attributes) { setup_section_params.merge(bulk_upload:, field_3: nil) }
+
+ it "is not permitted" do
+ parser.valid?
+ expect(parser.errors[:field_3]).to be_present
+ expect(parser.errors[:field_3]).to include("You must answer what is the CORE username of the account this letting log should be assigned to?")
+ end
+
+ it "blocks log creation" do
+ parser.valid?
+ expect(parser).to be_block_log_creation
+ end
+ end
+
context "when user could not be found" do
let(:attributes) { { bulk_upload:, field_3: "idonotexist@example.com" } }
@@ -915,7 +932,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
let(:attributes) { { bulk_upload:, field_1: owning_org.old_visible_id, field_2: owning_org.old_visible_id, field_4: "2", field_11: "2", field_5: "S123", field_6: location.id } }
it "returns a setup error" do
- expect(parser.errors.where(:field_5, category: :setup).map(&:message)).to eq(["This scheme code does not belong to the owning organisation or managing organisation"])
+ expect(parser.errors.where(:field_5, category: :setup).map(&:message)).to eq(["This scheme code does not belong to the owning organisation or managing organisation."])
expect(parser.errors[:field_6]).to be_blank
end
end
@@ -935,7 +952,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "returns a setup error" do
expect(parser.errors[:field_5]).to be_blank
- expect(parser.errors.where(:field_6, category: :setup).map(&:message)).to eq(["Location code must relate to a location that is owned by the owning organisation or managing organisation"])
+ expect(parser.errors.where(:field_6, category: :setup).map(&:message)).to eq(["Location code must relate to a location that is owned by the owning organisation or managing organisation."])
end
end
@@ -964,7 +981,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "returns a setup error" do
expect(parser.errors[:field_5]).to be_blank
- expect(parser.errors.where(:field_6, category: :setup).map(&:message)).to eq(["Location code must relate to a location that is owned by the owning organisation or managing organisation"])
+ expect(parser.errors.where(:field_6, category: :setup).map(&:message)).to eq(["Location code must relate to a location that is owned by the owning organisation or managing organisation."])
end
end
@@ -974,7 +991,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
let(:attributes) { { bulk_upload:, field_4: "2", field_11: "2", field_5: "S#{other_scheme.id}", field_6: other_location.id, field_1: owning_org.old_visible_id, field_2: owning_org.old_visible_id } }
it "returns a setup error" do
- expect(parser.errors.where(:field_5, category: :setup).map(&:message)).to eq(["This scheme code does not belong to the owning organisation or managing organisation"])
+ expect(parser.errors.where(:field_5, category: :setup).map(&:message)).to eq(["This scheme code does not belong to the owning organisation or managing organisation."])
expect(parser.errors[:field_6]).to be_blank
end
end
@@ -1004,8 +1021,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
let(:attributes) { { bulk_upload:, field_1: owning_org.old_visible_id, field_2: owning_org.old_visible_id, field_4: "2", field_11: "2", field_5: "S#{scheme.id}", field_6: incomplete_location.id } }
it "returns a setup error for scheme" do
- expect(parser.errors.where(:field_5).map(&:message)).to eq(["This location is incomplete. Select another location or update this one"])
- expect(parser.errors.where(:field_6).map(&:message)).to eq(["This location is incomplete. Select another location or update this one"])
+ expect(parser.errors.where(:field_5).map(&:message)).to eq(["This location is incomplete. Select another location or update this one."])
+ expect(parser.errors.where(:field_6).map(&:message)).to eq(["This location is incomplete. Select another location or update this one."])
end
end
end
@@ -1054,7 +1071,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "is not permitted" do
parser.valid?
- expect(parser.errors[:field_98]).to include('The reason for leaving must be "End of social or private sector tenancy - no fault", "End of social or private sector tenancy - evicted due to anti-social behaviour (ASB)", "End of social or private sector tenancy - evicted due to rent arrears" or "End of social or private sector tenancy - evicted for any other reason"')
+ expect(parser.errors[:field_98]).to include('The reason for leaving must be "End of social or private sector tenancy - no fault", "End of social or private sector tenancy - evicted due to anti-social behaviour (ASB)", "End of social or private sector tenancy - evicted due to rent arrears" or "End of social or private sector tenancy - evicted for any other reason".')
end
end
end
@@ -1280,7 +1297,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "returns an error" do
parser.valid?
- expect(parser.errors[:field_10]).to include("Tenancy start year must be 2 digits")
+ expect(parser.errors[:field_10]).to include("Tenancy start year must be 2 digits.")
end
end
@@ -1348,7 +1365,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
- expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code is incorrect")
+ expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code is incorrect.")
end
it "blocks log creation" do
@@ -1367,7 +1384,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
- expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock")
+ expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock.")
end
it "blocks log creation" do
@@ -1386,7 +1403,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
- expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("You do not have permission to add logs for this owning organisation")
+ expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("You do not have permission to add logs for this owning organisation.")
end
it "blocks log creation" do
@@ -1439,6 +1456,43 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
expect(parser.errors[:field_10]).to include(/Enter a date when the owning and managing organisation was active/)
end
end
+
+ context "when user is an unaffiliated non-support user and bulk upload organisation is affiliated with the owning organisation" do
+ let(:affiliated_org) { create(:organisation, :with_old_visible_id) }
+ let(:unaffiliated_user) { create(:user, organisation: create(:organisation)) }
+ let(:attributes) { { bulk_upload:, field_1: affiliated_org.old_visible_id } }
+ let(:organisation_id) { unaffiliated_user.organisation_id }
+
+ before do
+ create(:organisation_relationship, parent_organisation: owning_org, child_organisation: affiliated_org)
+ bulk_upload.update!(organisation_id:, user: unaffiliated_user)
+ end
+
+ it "blocks log creation and adds an error to field_1" do
+ parser = described_class.new(attributes)
+ parser.valid?
+ expect(parser).to be_block_log_creation
+ expect(parser.errors[:field_1]).to include("You do not have permission to add logs for this owning organisation.")
+ end
+ end
+
+ context "when user is an unaffiliated support user and bulk upload organisation is affiliated with the owning organisation" do
+ let(:affiliated_org) { create(:organisation, :with_old_visible_id) }
+ let(:unaffiliated_support_user) { create(:user, :support, organisation: create(:organisation)) }
+ let(:attributes) { { bulk_upload:, field_1: affiliated_org.old_visible_id } }
+ let(:organisation_id) { affiliated_org.id }
+
+ before do
+ create(:organisation_relationship, parent_organisation: owning_org, child_organisation: affiliated_org)
+ bulk_upload.update!(organisation_id:, user: unaffiliated_support_user)
+ end
+
+ it "does not block log creation and does not add an error to field_1" do
+ parser = described_class.new(attributes)
+ parser.valid?
+ expect(parser.errors[:field_1]).not_to include("You do not have permission to add logs for this owning organisation.")
+ end
+ end
end
describe "#field_2" do # managing org
@@ -1467,7 +1521,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
- expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("The managing organisation code is incorrect")
+ expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("The managing organisation code is incorrect.")
end
it "blocks log creation" do
@@ -1486,7 +1540,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
- expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("This managing organisation does not have a relationship with the owning organisation")
+ expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("This managing organisation does not have a relationship with the owning organisation.")
end
it "blocks log creation" do
@@ -1548,7 +1602,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "adds an appropriate error to the UPRN field" do
parser.valid?
- expect(parser.errors[:field_16]).to eql(["UPRN must be 12 digits or less"])
+ expect(parser.errors[:field_16]).to eql(["UPRN must be 12 digits or less."])
end
it "adds errors to missing key address fields" do
@@ -1565,7 +1619,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "adds an error to the UPRN field only" do
parser.valid?
- expect(parser.errors[:field_16]).to eql(["UPRN must be 12 digits or less"])
+ expect(parser.errors[:field_16]).to eql(["UPRN must be 12 digits or less."])
%i[field_17 field_19 field_21 field_22].each do |field|
expect(parser.errors[field]).to be_empty
end
@@ -1790,7 +1844,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "adds an error to field_45" do
parser.valid?
- expect(parser.errors["field_45"]).to include("Select a valid nationality")
+ expect(parser.errors["field_45"]).to include("Select a valid nationality.")
end
end
end
@@ -2759,8 +2813,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "sets error on housingneeds a and b" do
parser.valid?
- expect(parser.errors[:field_79]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected")
- expect(parser.errors[:field_80]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected")
+ expect(parser.errors[:field_79]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.")
+ expect(parser.errors[:field_80]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.")
expect(parser.errors[:field_81]).to be_blank
end
end
@@ -2770,8 +2824,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "sets error on housingneeds a and c" do
parser.valid?
- expect(parser.errors[:field_79]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected")
- expect(parser.errors[:field_81]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected")
+ expect(parser.errors[:field_79]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.")
+ expect(parser.errors[:field_81]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.")
expect(parser.errors[:field_80]).to be_blank
end
end
@@ -2781,8 +2835,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "sets error on housingneeds b and c" do
parser.valid?
- expect(parser.errors[:field_80]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected")
- expect(parser.errors[:field_81]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected")
+ expect(parser.errors[:field_80]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.")
+ expect(parser.errors[:field_81]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.")
expect(parser.errors[:field_79]).to be_blank
end
end
@@ -2792,8 +2846,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "sets error on housingneeds a and g" do
parser.valid?
- expect(parser.errors[:field_83]).to include("No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs")
- expect(parser.errors[:field_79]).to include("No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs")
+ expect(parser.errors[:field_83]).to include("No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs.")
+ expect(parser.errors[:field_79]).to include("No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs.")
expect(parser.errors[:field_80]).to be_blank
expect(parser.errors[:field_81]).to be_blank
end
@@ -2816,8 +2870,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
it "sets error on housingneeds a and h" do
parser.valid?
- expect(parser.errors[:field_84]).to include("Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs")
- expect(parser.errors[:field_79]).to include("Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs")
+ expect(parser.errors[:field_84]).to include("Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs.")
+ expect(parser.errors[:field_79]).to include("Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs.")
expect(parser.errors[:field_80]).to be_blank
expect(parser.errors[:field_81]).to be_blank
end
diff --git a/spec/services/bulk_upload/sales/validator_spec.rb b/spec/services/bulk_upload/sales/validator_spec.rb
index c5a35bd56..f968f5661 100644
--- a/spec/services/bulk_upload/sales/validator_spec.rb
+++ b/spec/services/bulk_upload/sales/validator_spec.rb
@@ -54,7 +54,7 @@ RSpec.describe BulkUpload::Sales::Validator do
it "is not valid" do
expect(validator).not_to be_valid
- expect(validator.errors["base"]).to eql(["Incorrect sale dates, please ensure you have used the correct template"])
+ expect(validator.errors["base"]).to eql(["Incorrect sale dates, please ensure you have used the correct template."])
end
end
@@ -199,7 +199,7 @@ RSpec.describe BulkUpload::Sales::Validator do
end
it "creates errors" do
- expect { validator.call }.to change(BulkUploadError.where(category: :setup, error: "This is a duplicate of a log in your file"), :count).by(20)
+ expect { validator.call }.to change(BulkUploadError.where(category: :setup, error: "This is a duplicate of a log in your file."), :count).by(20)
end
end
end
diff --git a/spec/services/bulk_upload/sales/year2023/row_parser_spec.rb b/spec/services/bulk_upload/sales/year2023/row_parser_spec.rb
index 6e193978e..7d7c62885 100644
--- a/spec/services/bulk_upload/sales/year2023/row_parser_spec.rb
+++ b/spec/services/bulk_upload/sales/year2023/row_parser_spec.rb
@@ -432,7 +432,7 @@ RSpec.describe BulkUpload::Sales::Year2023::RowParser do
let(:attributes) { { bulk_upload:, field_1: unaffiliated_org.old_visible_id } }
it "is not permitted as setup error" do
- expect(parser.errors.where(:field_1, category: :setup).map(&:message)).to eql(["You do not have permission to add logs for this owning organisation"])
+ expect(parser.errors.where(:field_1, category: :setup).map(&:message)).to eql(["You do not have permission to add logs for this owning organisation."])
end
it "blocks log creation" do
@@ -597,7 +597,7 @@ RSpec.describe BulkUpload::Sales::Year2023::RowParser do
let(:attributes) { setup_section_params.merge({ bulk_upload:, field_5: "2022" }) }
it "returns a setup error" do
- expect(parser.errors.where(:field_5, category: :setup).map(&:message)).to include("Sale completion year must be 2 digits")
+ expect(parser.errors.where(:field_5, category: :setup).map(&:message)).to include("Sale completion year must be 2 digits.")
end
end
@@ -661,7 +661,7 @@ RSpec.describe BulkUpload::Sales::Year2023::RowParser do
it "adds an error to all (and only) the fields used to determine duplicates" do
parser.valid?
- error_message = "This is a duplicate log"
+ error_message = "This is a duplicate log."
[
:field_1, # Owning org
@@ -969,7 +969,7 @@ RSpec.describe BulkUpload::Sales::Year2023::RowParser do
let(:attributes) { valid_attributes.merge({ field_35: "9" }) }
it "a custom validation is applied" do
- validation_message = "Buyer 1 cannot be a child under 16"
+ validation_message = "Buyer 1 cannot be a child under 16."
expect(parser.errors[:field_35]).to include validation_message
end
end
@@ -1121,7 +1121,7 @@ RSpec.describe BulkUpload::Sales::Year2023::RowParser do
it "only adds errors to the discounted ownership field" do
expect(parser.errors[:field_105]).to be_empty
- expect(parser.errors[:field_119]).to include("Mortgage, deposit, and grant total must equal £90.00. Your given mortgage, deposit and grant total is £100.00")
+ expect(parser.errors[:field_119]).to include("Mortgage, deposit, and grant total must equal £90.00. Your given mortgage, deposit and grant total is £100.00.")
expect(parser.errors[:field_128]).to be_empty
end
end
@@ -1409,7 +1409,7 @@ RSpec.describe BulkUpload::Sales::Year2023::RowParser do
parser.valid?
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
- expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("This user belongs to an organisation that does not have a relationship with the owning organisation")
+ expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("This user belongs to an organisation that does not have a relationship with the owning organisation.")
end
it "blocks log creation" do
@@ -1431,7 +1431,7 @@ RSpec.describe BulkUpload::Sales::Year2023::RowParser do
parser.valid?
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
- expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock")
+ expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock.")
end
it "blocks log creation" do
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 e428f7792..b74545a9a 100644
--- a/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb
+++ b/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb
@@ -467,7 +467,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
it "is not permitted as a setup error" do
parser.valid?
- expect(parser.errors.where(:field_1, category: :setup).map(&:message)).to eql(["The owning organisation code is incorrect"])
+ expect(parser.errors.where(:field_1, category: :setup).map(&:message)).to eql(["The owning organisation code is incorrect."])
end
it "blocks log creation" do
@@ -483,7 +483,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
it "is not permitted as setup error" do
parser.valid?
- expect(parser.errors.where(:field_1, category: :setup).map(&:message)).to eql(["You do not have permission to add logs for this owning organisation"])
+ expect(parser.errors.where(:field_1, category: :setup).map(&:message)).to eql(["You do not have permission to add logs for this owning organisation."])
end
it "blocks log creation" do
@@ -554,6 +554,43 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
expect(parser.errors[:field_6]).to include(/Enter a date when the owning organisation was active/)
end
end
+
+ context "when user is an unaffiliated non-support user and bulk upload organisation is affiliated with the owning organisation" do
+ let(:affiliated_org) { create(:organisation, :with_old_visible_id) }
+ let(:unaffiliated_user) { create(:user, organisation: create(:organisation)) }
+ let(:attributes) { { bulk_upload:, field_1: affiliated_org.old_visible_id } }
+ let(:organisation_id) { unaffiliated_user.organisation_id }
+
+ before do
+ create(:organisation_relationship, parent_organisation: owning_org, child_organisation: affiliated_org)
+ bulk_upload.update!(organisation_id:, user: unaffiliated_user)
+ end
+
+ it "blocks log creation and adds an error to field_1" do
+ parser = described_class.new(attributes)
+ parser.valid?
+ expect(parser).to be_block_log_creation
+ expect(parser.errors[:field_1]).to include("You do not have permission to add logs for this owning organisation.")
+ end
+ end
+
+ context "when user is an unaffiliated support user and bulk upload organisation is affiliated with the owning organisation" do
+ let(:affiliated_org) { create(:organisation, :with_old_visible_id) }
+ let(:unaffiliated_support_user) { create(:user, :support, organisation: create(:organisation)) }
+ let(:attributes) { { bulk_upload:, field_1: affiliated_org.old_visible_id } }
+ let(:organisation_id) { affiliated_org.id }
+
+ before do
+ create(:organisation_relationship, parent_organisation: owning_org, child_organisation: affiliated_org)
+ bulk_upload.update!(organisation_id:, user: unaffiliated_support_user)
+ end
+
+ it "does not block log creation and does not add an error to field_1" do
+ parser = described_class.new(attributes)
+ parser.valid?
+ expect(parser.errors[:field_1]).not_to include("You do not have permission to add logs for this owning organisation.")
+ end
+ end
end
describe "#field_3" do # username for assigned_to
@@ -576,6 +613,23 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
end
end
+ context "when blank and bulk upload user is support" do
+ let(:bulk_upload) { create(:bulk_upload, :sales, user: create(:user, :support), year: 2024) }
+
+ let(:attributes) { setup_section_params.merge(bulk_upload:, field_3: nil) }
+
+ it "is not permitted" do
+ parser.valid?
+ expect(parser.errors[:field_3]).to be_present
+ expect(parser.errors[:field_3]).to include("You must answer what is the CORE username of the account this sales log should be assigned to?")
+ end
+
+ it "blocks log creation" do
+ parser.valid?
+ expect(parser).to be_block_log_creation
+ end
+ end
+
context "when user could not be found" do
let(:attributes) { { bulk_upload:, field_3: "idonotexist@example.com" } }
@@ -662,7 +716,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
it "returns a setup error" do
parser.valid?
- expect(parser.errors.where(:field_6, category: :setup).map(&:message)).to include("Sale completion year must be 2 digits")
+ expect(parser.errors.where(:field_6, category: :setup).map(&:message)).to include("Sale completion year must be 2 digits.")
end
end
@@ -728,7 +782,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
it "adds an error to all (and only) the fields used to determine duplicates" do
parser.valid?
- error_message = "This is a duplicate log"
+ error_message = "This is a duplicate log."
[
:field_1, # Owning org
@@ -817,7 +871,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
it "returns correct error" do
parser.valid?
- expect(parser.errors.where(:field_116).map(&:message)).to include("Percentage discount must be between 0% and 70%")
+ expect(parser.errors.where(:field_116).map(&:message)).to include("Percentage discount must be between 0% and 70%.")
end
end
@@ -835,7 +889,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
it "returns correct error" do
parser.valid?
- expect(parser.errors.where(:field_116).map(&:message)).to include("Percentage discount must be between 0% and 70%")
+ expect(parser.errors.where(:field_116).map(&:message)).to include("Percentage discount must be between 0% and 70%.")
end
end
end
@@ -932,7 +986,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
it "adds an appropriate error to the UPRN field" do
parser.valid?
- expect(parser.errors[:field_22]).to eql(["UPRN must be 12 digits or less"])
+ expect(parser.errors[:field_22]).to eql(["UPRN must be 12 digits or less."])
end
it "adds errors to missing key address fields" do
@@ -949,7 +1003,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
it "adds an error to the UPRN field only" do
parser.valid?
- expect(parser.errors[:field_22]).to eql(["UPRN must be 12 digits or less"])
+ expect(parser.errors[:field_22]).to eql(["UPRN must be 12 digits or less."])
%i[field_23 field_25 field_27 field_28].each do |field|
expect(parser.errors[field]).to be_empty
end
@@ -1054,7 +1108,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
context "when the privacy notice is not accepted" do
it "cannot be nulled" do
- expect(parser.errors[:field_18]).to eq(["You must show or give the buyer access to the MHCLG privacy notice before you can submit this log"])
+ expect(parser.errors[:field_18]).to eq(["You must show or give the buyer access to the MHCLG privacy notice before you can submit this log."])
end
end
end
@@ -1144,7 +1198,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
it "a custom validation is applied" do
parser.valid?
- validation_message = "Buyer 2 cannot have a working situation of child under 16"
+ validation_message = "Buyer 2 cannot have a working situation of child under 16."
expect(parser.errors[:field_42]).to include validation_message
end
end
@@ -1155,7 +1209,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
it "a custom validation is applied" do
parser.valid?
- validation_message = "Buyer 2’s age must be between 16 and 110"
+ validation_message = "Buyer 2’s age must be between 16 and 110."
expect(parser.errors[:field_38]).to include validation_message
end
end
@@ -1166,7 +1220,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
it "a custom validation is applied" do
parser.valid?
- validation_message = "Buyer 2's age cannot be 16 or over if their working situation is child under 16"
+ validation_message = "Buyer 2's age cannot be 16 or over if their working situation is child under 16."
expect(parser.errors[:field_42]).to include validation_message
expect(parser.errors[:field_38]).to include validation_message
end
@@ -1190,7 +1244,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
it "a custom validation is applied" do
parser.valid?
- validation_message = "Buyer 1 cannot have a working situation of child under 16"
+ validation_message = "Buyer 1 cannot have a working situation of child under 16."
expect(parser.errors[:field_35]).to include validation_message
end
end
@@ -1201,7 +1255,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
it "a custom validation is applied" do
parser.valid?
- validation_message = "Buyer 1’s age must be between 16 and 110"
+ validation_message = "Buyer 1’s age must be between 16 and 110."
expect(parser.errors[:field_31]).to include validation_message
end
end
@@ -1212,7 +1266,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
it "a custom validation is applied" do
parser.valid?
- validation_message = "Buyer 1's age cannot be 16 or over if their working situation is child under 16"
+ validation_message = "Buyer 1's age cannot be 16 or over if their working situation is child under 16."
expect(parser.errors[:field_35]).to include validation_message
expect(parser.errors[:field_31]).to include validation_message
end
@@ -1622,7 +1676,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
parser.valid?
expect(parser.log.nationality_all).to be(nil)
expect(parser.log.nationality_all_group).to be(nil)
- expect(parser.errors["field_34"]).to include("Select a valid nationality")
+ expect(parser.errors["field_34"]).to include("Select a valid nationality.")
end
end
end
@@ -1707,7 +1761,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
parser.valid?
expect(parser.log.nationality_all_buyer2).to be(nil)
expect(parser.log.nationality_all_buyer2_group).to be(nil)
- expect(parser.errors["field_41"]).to include("Select a valid nationality")
+ expect(parser.errors["field_41"]).to include("Select a valid nationality.")
end
end
end
@@ -1943,7 +1997,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
parser.valid?
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
- expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("This organisation does not have a relationship with the owning organisation")
+ expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("This organisation does not have a relationship with the owning organisation.")
end
it "blocks log creation" do
@@ -1965,7 +2019,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
parser.valid?
setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
- expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock")
+ expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock.")
end
it "blocks log creation" do
diff --git a/spec/services/exports/export_service_spec.rb b/spec/services/exports/export_service_spec.rb
new file mode 100644
index 000000000..fb52c5274
--- /dev/null
+++ b/spec/services/exports/export_service_spec.rb
@@ -0,0 +1,333 @@
+require "rails_helper"
+
+RSpec.describe Exports::ExportService do
+ subject(:export_service) { described_class.new(storage_service) }
+
+ let(:storage_service) { instance_double(Storage::S3Service) }
+ let(:expected_master_manifest_filename) { "Manifest_2022_05_01_0001.csv" }
+ let(:start_time) { Time.zone.local(2022, 5, 1) }
+ let(:user) { FactoryBot.create(:user, email: "test1@example.com") }
+ let(:organisations_export_service) { instance_double("Exports::OrganisationExportService", export_xml_organisations: {}) }
+ let(:users_export_service) { instance_double("Exports::UserExportService", export_xml_users: {}) }
+
+ before do
+ Timecop.freeze(start_time)
+ Singleton.__init__(FormHandler)
+ allow(storage_service).to receive(:write_file)
+ allow(Exports::LettingsLogExportService).to receive(:new).and_return(lettings_logs_export_service)
+ allow(Exports::UserExportService).to receive(:new).and_return(users_export_service)
+ allow(Exports::OrganisationExportService).to receive(:new).and_return(organisations_export_service)
+ end
+
+ after do
+ Timecop.return
+ end
+
+ context "when exporting daily XMLs" do
+ context "and no lettings archives get created in lettings logs export" do
+ let(:lettings_logs_export_service) { instance_double("Exports::LettingsLogExportService", export_xml_lettings_logs: {}) }
+
+ context "and no user or organisation archives get created in user export" do
+ it "generates a master manifest with the correct name" do
+ expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args)
+ export_service.export_xml
+ end
+
+ it "generates a master manifest with CSV headers but no data" do
+ actual_content = nil
+ expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\n"
+ allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
+
+ export_service.export_xml
+ expect(actual_content).to eq(expected_content)
+ end
+ end
+
+ context "and one user archive gets created in user export" do
+ let(:users_export_service) { instance_double("Exports::UserExportService", export_xml_users: { "some_user_file_base_name" => start_time }) }
+
+ it "generates a master manifest with the correct name" do
+ expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args)
+ export_service.export_xml
+ end
+
+ it "generates a master manifest with CSV headers and correct data" do
+ actual_content = nil
+ expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_user_file_base_name,2022-05-01 00:00:00 +0100,some_user_file_base_name.zip\n"
+ allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
+
+ export_service.export_xml
+ expect(actual_content).to eq(expected_content)
+ end
+ end
+
+ context "and one organisation archive gets created in organisation export" do
+ let(:organisations_export_service) { instance_double("Exports::OrganisationExportService", export_xml_organisations: { "some_organisation_file_base_name" => start_time }) }
+
+ it "generates a master manifest with the correct name" do
+ expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args)
+ export_service.export_xml
+ end
+
+ it "generates a master manifest with CSV headers and correct data" do
+ actual_content = nil
+ expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_organisation_file_base_name,2022-05-01 00:00:00 +0100,some_organisation_file_base_name.zip\n"
+ allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
+
+ export_service.export_xml
+ expect(actual_content).to eq(expected_content)
+ end
+ end
+
+ context "and user and organisation archive gets created in organisation export" do
+ let(:organisations_export_service) { instance_double("Exports::OrganisationExportService", export_xml_organisations: { "some_organisation_file_base_name" => start_time }) }
+ let(:users_export_service) { instance_double("Exports::UserExportService", export_xml_users: { "some_user_file_base_name" => start_time }) }
+
+ it "generates a master manifest with the correct name" do
+ expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args)
+ export_service.export_xml
+ end
+
+ it "generates a master manifest with CSV headers and correct data" do
+ actual_content = nil
+ expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_user_file_base_name,2022-05-01 00:00:00 +0100,some_user_file_base_name.zip\nsome_organisation_file_base_name,2022-05-01 00:00:00 +0100,some_organisation_file_base_name.zip\n"
+ allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
+
+ export_service.export_xml
+ expect(actual_content).to eq(expected_content)
+ end
+ end
+ end
+
+ context "and one lettings archive gets created in lettings logs export" do
+ let(:lettings_logs_export_service) { instance_double("Exports::LettingsLogExportService", export_xml_lettings_logs: { "some_file_base_name" => start_time }) }
+
+ context "and no user archives get created in user export" do
+ it "generates a master manifest with the correct name" do
+ expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args)
+ export_service.export_xml
+ end
+
+ it "generates a master manifest with CSV headers and correct data" do
+ actual_content = nil
+ expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_file_base_name,2022-05-01 00:00:00 +0100,some_file_base_name.zip\n"
+ allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
+
+ export_service.export_xml
+ expect(actual_content).to eq(expected_content)
+ end
+ end
+
+ context "and one user archive gets created in user export" do
+ let(:users_export_service) { instance_double("Exports::UserExportService", export_xml_users: { "some_user_file_base_name" => start_time }) }
+
+ it "generates a master manifest with the correct name" do
+ expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args)
+ export_service.export_xml
+ end
+
+ it "generates a master manifest with CSV headers and correct data" do
+ actual_content = nil
+ expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_file_base_name,2022-05-01 00:00:00 +0100,some_file_base_name.zip\nsome_user_file_base_name,2022-05-01 00:00:00 +0100,some_user_file_base_name.zip\n"
+ allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
+
+ export_service.export_xml
+ expect(actual_content).to eq(expected_content)
+ end
+ end
+ end
+
+ context "and multiple lettings archives get created in lettings logs export" do
+ let(:lettings_logs_export_service) { instance_double("Exports::LettingsLogExportService", export_xml_lettings_logs: { "some_file_base_name" => start_time, "second_file_base_name" => start_time }) }
+
+ context "and no user archives get created in user export" do
+ it "generates a master manifest with the correct name" do
+ expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args)
+ export_service.export_xml
+ end
+
+ it "generates a master manifest with CSV headers and correct data" do
+ actual_content = nil
+ expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_file_base_name,2022-05-01 00:00:00 +0100,some_file_base_name.zip\nsecond_file_base_name,2022-05-01 00:00:00 +0100,second_file_base_name.zip\n"
+ allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
+
+ export_service.export_xml
+ expect(actual_content).to eq(expected_content)
+ end
+ end
+
+ context "and multiple user archive gets created in user export" do
+ let(:users_export_service) { instance_double("Exports::UserExportService", export_xml_users: { "some_user_file_base_name" => start_time, "second_user_file_base_name" => start_time }) }
+
+ it "generates a master manifest with the correct name" do
+ expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args)
+ export_service.export_xml
+ end
+
+ it "generates a master manifest with CSV headers and correct data" do
+ actual_content = nil
+ expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_file_base_name,2022-05-01 00:00:00 +0100,some_file_base_name.zip\nsecond_file_base_name,2022-05-01 00:00:00 +0100,second_file_base_name.zip\nsome_user_file_base_name,2022-05-01 00:00:00 +0100,some_user_file_base_name.zip\nsecond_user_file_base_name,2022-05-01 00:00:00 +0100,second_user_file_base_name.zip\n"
+ allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
+
+ export_service.export_xml
+ expect(actual_content).to eq(expected_content)
+ end
+ end
+
+ context "and multiple user and organisation archives gets created in user export" do
+ let(:users_export_service) { instance_double("Exports::UserExportService", export_xml_users: { "some_user_file_base_name" => start_time, "second_user_file_base_name" => start_time }) }
+ let(:organisations_export_service) { instance_double("Exports::OrganisationExportService", export_xml_organisations: { "some_organisation_file_base_name" => start_time, "second_organisation_file_base_name" => start_time }) }
+
+ it "generates a master manifest with the correct name" do
+ expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args)
+ export_service.export_xml
+ end
+
+ it "generates a master manifest with CSV headers and correct data" do
+ actual_content = nil
+ expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_file_base_name,2022-05-01 00:00:00 +0100,some_file_base_name.zip\nsecond_file_base_name,2022-05-01 00:00:00 +0100,second_file_base_name.zip\nsome_user_file_base_name,2022-05-01 00:00:00 +0100,some_user_file_base_name.zip\nsecond_user_file_base_name,2022-05-01 00:00:00 +0100,second_user_file_base_name.zip\nsome_organisation_file_base_name,2022-05-01 00:00:00 +0100,some_organisation_file_base_name.zip\nsecond_organisation_file_base_name,2022-05-01 00:00:00 +0100,second_organisation_file_base_name.zip\n"
+ allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
+
+ export_service.export_xml
+ expect(actual_content).to eq(expected_content)
+ end
+ end
+ end
+ end
+
+ context "when exporting specific lettings log collection" do
+ context "and no lettings archives get created in lettings logs export" do
+ let(:lettings_logs_export_service) { instance_double("Exports::LettingsLogExportService", export_xml_lettings_logs: {}) }
+
+ context "and user archive gets created in user export" do
+ let(:users_export_service) { instance_double("Exports::UserExportService", export_xml_users: { "some_user_file_base_name" => start_time }) }
+
+ it "generates a master manifest with the correct name" do
+ expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args)
+ export_service.export_xml(full_update: true, collection: "2022")
+ end
+
+ it "does not write user data" do
+ actual_content = nil
+ expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\n"
+ allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
+
+ export_service.export_xml(full_update: true, collection: "2022")
+ expect(actual_content).to eq(expected_content)
+ end
+ end
+ end
+
+ context "and lettings archive gets created in lettings logs export" do
+ let(:lettings_logs_export_service) { instance_double("Exports::LettingsLogExportService", export_xml_lettings_logs: { "some_file_base_name" => start_time }) }
+
+ context "and user archive gets created in user export" do
+ let(:users_export_service) { instance_double("Exports::UserExportService", export_xml_users: { "some_user_file_base_name" => start_time }) }
+
+ it "generates a master manifest with the correct name" do
+ expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args)
+ export_service.export_xml(full_update: true, collection: "2023")
+ end
+
+ it "does not write user data" do
+ actual_content = nil
+ expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_file_base_name,2022-05-01 00:00:00 +0100,some_file_base_name.zip\n"
+ allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
+
+ export_service.export_xml(full_update: true, collection: "2023")
+ expect(actual_content).to eq(expected_content)
+ end
+ end
+ end
+ end
+
+ context "when exporting user collection" do
+ context "and no user archives get created in users export" do
+ context "and lettings log archive gets created in lettings logs export" do
+ let(:lettings_logs_export_service) { instance_double("Exports::LettingsLogExportService", export_xml_lettings_logs: { "some_file_base_name" => start_time }) }
+
+ it "generates a master manifest with the correct name" do
+ expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args)
+ export_service.export_xml(full_update: true, collection: "users")
+ end
+
+ it "does not write lettings log data" do
+ actual_content = nil
+ expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\n"
+ allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
+
+ export_service.export_xml(full_update: true, collection: "users")
+ expect(actual_content).to eq(expected_content)
+ end
+ end
+ end
+
+ context "and users archive gets created in users export" do
+ let(:lettings_logs_export_service) { instance_double("Exports::LettingsLogExportService", export_xml_lettings_logs: { "some_file_base_name" => start_time }) }
+
+ context "and lettings log archive gets created in lettings log export" do
+ let(:users_export_service) { instance_double("Exports::UserExportService", export_xml_users: { "some_user_file_base_name" => start_time }) }
+
+ it "generates a master manifest with the correct name" do
+ expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args)
+ export_service.export_xml(full_update: true, collection: "users")
+ end
+
+ it "does not write lettings log data" do
+ actual_content = nil
+ expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_user_file_base_name,2022-05-01 00:00:00 +0100,some_user_file_base_name.zip\n"
+ allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
+
+ export_service.export_xml(full_update: true, collection: "users")
+ expect(actual_content).to eq(expected_content)
+ end
+ end
+ end
+ end
+
+ context "when exporting organisation collection" do
+ context "and no organisation archives get created in organisations export" do
+ let(:organisations_export_service) { instance_double("Exports::OrganisationExportService", export_xml_organisations: {}) }
+
+ context "and lettings log archive gets created in lettings logs export" do
+ let(:lettings_logs_export_service) { instance_double("Exports::LettingsLogExportService", export_xml_lettings_logs: { "some_file_base_name" => start_time }) }
+
+ it "generates a master manifest with the correct name" do
+ expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args)
+ export_service.export_xml(full_update: true, collection: "organisations")
+ end
+
+ it "does not write lettings log data" do
+ actual_content = nil
+ expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\n"
+ allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
+
+ export_service.export_xml(full_update: true, collection: "organisations")
+ expect(actual_content).to eq(expected_content)
+ end
+ end
+ end
+
+ context "and organisations archive gets created in organisations export" do
+ let(:lettings_logs_export_service) { instance_double("Exports::LettingsLogExportService", export_xml_lettings_logs: { "some_file_base_name" => start_time }) }
+
+ context "and lettings log archive gets created in lettings log export" do
+ let(:organisations_export_service) { instance_double("Exports::OrganisationExportService", export_xml_organisations: { "some_organisation_file_base_name" => start_time }) }
+
+ it "generates a master manifest with the correct name" do
+ expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args)
+ export_service.export_xml(full_update: true, collection: "organisations")
+ end
+
+ it "does not write lettings log data" do
+ actual_content = nil
+ expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_organisation_file_base_name,2022-05-01 00:00:00 +0100,some_organisation_file_base_name.zip\n"
+ allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
+
+ export_service.export_xml(full_update: true, collection: "organisations")
+ expect(actual_content).to eq(expected_content)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/exports/lettings_log_export_service_spec.rb b/spec/services/exports/lettings_log_export_service_spec.rb
index b3f33f24f..6f7d88c91 100644
--- a/spec/services/exports/lettings_log_export_service_spec.rb
+++ b/spec/services/exports/lettings_log_export_service_spec.rb
@@ -1,7 +1,7 @@
require "rails_helper"
RSpec.describe Exports::LettingsLogExportService do
- subject(:export_service) { described_class.new(storage_service) }
+ subject(:export_service) { described_class.new(storage_service, start_time) }
let(:storage_service) { instance_double(Storage::S3Service) }
@@ -11,8 +11,6 @@ RSpec.describe Exports::LettingsLogExportService do
let(:real_2021_2022_form) { Form.new("config/forms/2021_2022.json") }
let(:real_2022_2023_form) { Form.new("config/forms/2022_2023.json") }
- let(:expected_master_manifest_filename) { "Manifest_2022_05_01_0001.csv" }
- let(:expected_master_manifest_rerun) { "Manifest_2022_05_01_0002.csv" }
let(:expected_zip_filename) { "core_2021_2022_apr_mar_f0001_inc0001.zip" }
let(:expected_data_filename) { "core_2021_2022_apr_mar_f0001_inc0001_pt001.xml" }
let(:expected_manifest_filename) { "manifest.xml" }
@@ -49,18 +47,9 @@ RSpec.describe Exports::LettingsLogExportService do
context "when exporting daily lettings logs in XML" do
context "and no lettings logs is available for export" do
- it "generates a master manifest with the correct name" do
- expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args)
- export_service.export_xml_lettings_logs
- end
-
- it "generates a master manifest with CSV headers but no data" do
- actual_content = nil
- expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\n"
- allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
-
- export_service.export_xml_lettings_logs
- expect(actual_content).to eq(expected_content)
+ it "returns an empty archives list" do
+ expect(storage_service).not_to receive(:write_file)
+ expect(export_service.export_xml_lettings_logs).to eq({})
end
end
@@ -83,13 +72,9 @@ RSpec.describe Exports::LettingsLogExportService do
)
end
- it "generates a master manifest with CSV headers but no data" do
- actual_content = nil
- expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\n"
- allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
-
- export_service.export_xml_lettings_logs
- expect(actual_content).to eq(expected_content)
+ it "returns empty archives list for archives manifest" do
+ expect(storage_service).not_to receive(:write_file)
+ expect(export_service.export_xml_lettings_logs).to eq({})
end
end
@@ -101,15 +86,6 @@ RSpec.describe Exports::LettingsLogExportService do
export_service.export_xml_lettings_logs
end
- it "generates an XML manifest file with the expected filename within the ZIP file" do
- expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content|
- entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename)
- expect(entry).not_to be_nil
- expect(entry.name).to eq(expected_manifest_filename)
- end
- export_service.export_xml_lettings_logs
- end
-
it "generates an XML export file with the expected filename within the ZIP file" do
expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content|
entry = Zip::File.open_buffer(content).find_entry(expected_data_filename)
@@ -141,13 +117,8 @@ RSpec.describe Exports::LettingsLogExportService do
export_service.export_xml_lettings_logs
end
- it "generates a master manifest with CSV headers" do
- actual_content = nil
- expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\ncore_2021_2022_apr_mar_f0001_inc0001,2022-05-01 00:00:00 +0100,#{expected_zip_filename}\n"
- allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string }
-
- export_service.export_xml_lettings_logs
- expect(actual_content).to eq(expected_content)
+ it "returns the list with correct archive" do
+ expect(export_service.export_xml_lettings_logs).to eq({ expected_zip_filename.gsub(".zip", "") => start_time })
end
end
@@ -178,8 +149,10 @@ RSpec.describe Exports::LettingsLogExportService do
end
context "with 23/24 collection period" do
+ let(:start_time) { Time.zone.local(2023, 4, 3) }
+
before do
- Timecop.freeze(Time.zone.local(2023, 4, 3))
+ Timecop.freeze(start_time)
Singleton.__init__(FormHandler)
stub_request(:get, "https://api.os.uk/search/places/v1/uprn?dataset=DPA,LPI&key=OS_DATA_KEY&uprn=100023336956")
.to_return(status: 200, body: '{"status":200,"results":[{"DPA":{
@@ -234,15 +207,15 @@ RSpec.describe Exports::LettingsLogExportService do
expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args)
expect(storage_service).to receive(:write_file).with(expected_zip_filename2, any_args)
expect(Rails.logger).to receive(:info).with("Building export run for 2021")
- expect(Rails.logger).to receive(:info).with("Creating core_2021_2022_apr_mar_f0001_inc0001 - 1 logs")
+ expect(Rails.logger).to receive(:info).with("Creating core_2021_2022_apr_mar_f0001_inc0001 - 1 resources")
expect(Rails.logger).to receive(:info).with("Added core_2021_2022_apr_mar_f0001_inc0001_pt001.xml")
expect(Rails.logger).to receive(:info).with("Writing core_2021_2022_apr_mar_f0001_inc0001.zip")
expect(Rails.logger).to receive(:info).with("Building export run for 2022")
- expect(Rails.logger).to receive(:info).with("Creating core_2022_2023_apr_mar_f0001_inc0001 - 1 logs")
+ expect(Rails.logger).to receive(:info).with("Creating core_2022_2023_apr_mar_f0001_inc0001 - 1 resources")
expect(Rails.logger).to receive(:info).with("Added core_2022_2023_apr_mar_f0001_inc0001_pt001.xml")
expect(Rails.logger).to receive(:info).with("Writing core_2022_2023_apr_mar_f0001_inc0001.zip")
expect(Rails.logger).to receive(:info).with("Building export run for 2023")
- expect(Rails.logger).to receive(:info).with("Creating core_2023_2024_apr_mar_f0001_inc0001 - 0 logs")
+ expect(Rails.logger).to receive(:info).with("Creating core_2023_2024_apr_mar_f0001_inc0001 - 0 resources")
export_service.export_xml_lettings_logs
end
@@ -250,7 +223,7 @@ RSpec.describe Exports::LettingsLogExportService do
it "generates zip export files only for specified year" do
expect(storage_service).to receive(:write_file).with(expected_zip_filename2, any_args)
expect(Rails.logger).to receive(:info).with("Building export run for 2022")
- expect(Rails.logger).to receive(:info).with("Creating core_2022_2023_apr_mar_f0001_inc0001 - 1 logs")
+ expect(Rails.logger).to receive(:info).with("Creating core_2022_2023_apr_mar_f0001_inc0001 - 1 resources")
expect(Rails.logger).to receive(:info).with("Added core_2022_2023_apr_mar_f0001_inc0001_pt001.xml")
expect(Rails.logger).to receive(:info).with("Writing core_2022_2023_apr_mar_f0001_inc0001.zip")
@@ -262,22 +235,22 @@ RSpec.describe Exports::LettingsLogExportService do
let(:expected_zip_filename2) { "core_2022_2023_apr_mar_f0001_inc0001.zip" }
before do
- LogsExport.new(started_at: Time.zone.yesterday, base_number: 7, increment_number: 3, collection: 2021).save!
+ Export.new(started_at: Time.zone.yesterday, base_number: 7, increment_number: 3, collection: 2021).save!
end
it "generates multiple ZIP export files with different base numbers in the filenames" do
expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args)
expect(storage_service).to receive(:write_file).with(expected_zip_filename2, any_args)
expect(Rails.logger).to receive(:info).with("Building export run for 2021")
- expect(Rails.logger).to receive(:info).with("Creating core_2021_2022_apr_mar_f0007_inc0004 - 1 logs")
+ expect(Rails.logger).to receive(:info).with("Creating core_2021_2022_apr_mar_f0007_inc0004 - 1 resources")
expect(Rails.logger).to receive(:info).with("Added core_2021_2022_apr_mar_f0007_inc0004_pt001.xml")
expect(Rails.logger).to receive(:info).with("Writing core_2021_2022_apr_mar_f0007_inc0004.zip")
expect(Rails.logger).to receive(:info).with("Building export run for 2022")
- expect(Rails.logger).to receive(:info).with("Creating core_2022_2023_apr_mar_f0001_inc0001 - 1 logs")
+ expect(Rails.logger).to receive(:info).with("Creating core_2022_2023_apr_mar_f0001_inc0001 - 1 resources")
expect(Rails.logger).to receive(:info).with("Added core_2022_2023_apr_mar_f0001_inc0001_pt001.xml")
expect(Rails.logger).to receive(:info).with("Writing core_2022_2023_apr_mar_f0001_inc0001.zip")
expect(Rails.logger).to receive(:info).with("Building export run for 2023")
- expect(Rails.logger).to receive(:info).with("Creating core_2023_2024_apr_mar_f0001_inc0001 - 0 logs")
+ expect(Rails.logger).to receive(:info).with("Creating core_2023_2024_apr_mar_f0001_inc0001 - 0 resources")
export_service.export_xml_lettings_logs
end
@@ -304,18 +277,13 @@ RSpec.describe Exports::LettingsLogExportService do
it "creates a logs export record in a database with correct time" do
expect { export_service.export_xml_lettings_logs }
- .to change(LogsExport, :count).by(3)
- expect(LogsExport.last.started_at).to be_within(2.seconds).of(start_time)
+ .to change(Export, :count).by(3)
+ expect(Export.last.started_at).to be_within(2.seconds).of(start_time)
end
context "when this is the first export (full)" do
- it "records a ZIP archive in the master manifest (existing lettings logs)" do
- expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) do |_, csv_content|
- csv = CSV.parse(csv_content, headers: true)
- expect(csv&.count).to be > 0
- end
-
- export_service.export_xml_lettings_logs
+ it "returns a ZIP archive for the master manifest (existing lettings logs)" do
+ expect(export_service.export_xml_lettings_logs).to eq({ expected_zip_filename.gsub(".zip", "").gsub(".zip", "") => start_time })
end
end
@@ -360,15 +328,12 @@ RSpec.describe Exports::LettingsLogExportService do
context "when this is a second export (partial)" do
before do
start_time = Time.zone.local(2022, 6, 1)
- LogsExport.new(started_at: start_time).save!
+ Export.new(started_at: start_time, collection: 2021).save!
end
- it "does not add any entry in the master manifest (no lettings logs)" do
- expect(storage_service).to receive(:write_file).with(expected_master_manifest_rerun, any_args) do |_, csv_content|
- csv = CSV.parse(csv_content, headers: true)
- expect(csv&.count).to eq(0)
- end
- export_service.export_xml_lettings_logs
+ it "does not add any entry for the master manifest (no lettings logs)" do
+ expect(storage_service).not_to receive(:write_file)
+ expect(export_service.export_xml_lettings_logs).to eq({})
end
end
end
@@ -379,28 +344,19 @@ RSpec.describe Exports::LettingsLogExportService do
export_service.export_xml_lettings_logs
end
- it "increments the master manifest number by 1" do
- expect(storage_service).to receive(:write_file).with(expected_master_manifest_rerun, any_args)
- export_service.export_xml_lettings_logs
- end
-
context "and we trigger another full update" do
it "increments the base number" do
export_service.export_xml_lettings_logs(full_update: true)
- expect(LogsExport.last.base_number).to eq(2)
+ expect(Export.last.base_number).to eq(2)
end
it "resets the increment number" do
export_service.export_xml_lettings_logs(full_update: true)
- expect(LogsExport.last.increment_number).to eq(1)
+ expect(Export.last.increment_number).to eq(1)
end
- it "records a ZIP archive in the master manifest (existing lettings logs)" do
- expect(storage_service).to receive(:write_file).with(expected_master_manifest_rerun, any_args) do |_, csv_content|
- csv = CSV.parse(csv_content, headers: true)
- expect(csv&.count).to be > 0
- end
- export_service.export_xml_lettings_logs(full_update: true)
+ it "returns a correct archives list for manifest file" do
+ expect(export_service.export_xml_lettings_logs(full_update: true)).to eq({ "core_2021_2022_apr_mar_f0002_inc0001" => start_time })
end
it "generates a ZIP export file with the expected filename" do
@@ -416,7 +372,7 @@ RSpec.describe Exports::LettingsLogExportService do
it "doesn't increment the manifest number by 1" do
export_service.export_xml_lettings_logs
- expect(LogsExport.last.increment_number).to eq(1)
+ expect(Export.last.increment_number).to eq(1)
end
end
@@ -424,19 +380,18 @@ RSpec.describe Exports::LettingsLogExportService do
before do
FactoryBot.create(:lettings_log, startdate: Time.zone.local(2022, 2, 1), updated_at: Time.zone.local(2022, 4, 27), values_updated_at: Time.zone.local(2022, 4, 29))
FactoryBot.create(:lettings_log, startdate: Time.zone.local(2022, 2, 1), updated_at: Time.zone.local(2022, 4, 27), values_updated_at: Time.zone.local(2022, 4, 29))
- LogsExport.create!(started_at: Time.zone.local(2022, 4, 28), base_number: 1, increment_number: 1)
+ Export.create!(started_at: Time.zone.local(2022, 4, 28), base_number: 1, increment_number: 1)
end
it "generates an XML manifest file with the expected content within the ZIP file" do
expected_content = replace_record_number(local_manifest_file.read, 2)
- expect(storage_service).to receive(:write_file).with(expected_master_manifest_rerun, any_args)
expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content|
entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename)
expect(entry).not_to be_nil
expect(entry.get_input_stream.read).to eq(expected_content)
end
- export_service.export_xml_lettings_logs
+ expect(export_service.export_xml_lettings_logs).to eq({ expected_zip_filename.gsub(".zip", "") => start_time })
end
end
@@ -461,8 +416,10 @@ RSpec.describe Exports::LettingsLogExportService do
end
context "with 24/25 collection period" do
+ let(:start_time) { Time.zone.local(2024, 4, 3) }
+
before do
- Timecop.freeze(Time.zone.local(2024, 4, 3))
+ Timecop.freeze(start_time)
Singleton.__init__(FormHandler)
end
diff --git a/spec/services/exports/organisation_export_service_spec.rb b/spec/services/exports/organisation_export_service_spec.rb
new file mode 100644
index 000000000..4de0e84a8
--- /dev/null
+++ b/spec/services/exports/organisation_export_service_spec.rb
@@ -0,0 +1,219 @@
+require "rails_helper"
+
+RSpec.describe Exports::OrganisationExportService do
+ subject(:export_service) { described_class.new(storage_service, start_time) }
+
+ let(:storage_service) { instance_double(Storage::S3Service) }
+
+ let(:xml_export_file) { File.open("spec/fixtures/exports/organisation.xml", "r:UTF-8") }
+ let(:local_manifest_file) { File.open("spec/fixtures/exports/manifest.xml", "r:UTF-8") }
+
+ let(:expected_zip_filename) { "organisations_2024_2025_apr_mar_f0001_inc0001.zip" }
+ let(:expected_data_filename) { "organisations_2024_2025_apr_mar_f0001_inc0001_pt001.xml" }
+ let(:expected_manifest_filename) { "manifest.xml" }
+ let(:start_time) { Time.zone.local(2022, 5, 1) }
+ let(:organisation) { create(:organisation, with_dsa: false) }
+
+ def replace_entity_ids(organisation, export_template)
+ export_template.sub!(/\{id\}/, organisation["id"].to_s)
+ export_template.sub!(/\{dsa_signed_at\}/, organisation.data_protection_confirmation&.signed_at.to_s)
+ export_template.sub!(/\{dpo_email\}/, organisation.data_protection_confirmation&.data_protection_officer_email)
+ end
+
+ def replace_record_number(export_template, record_number)
+ export_template.sub!(/\{recno\}/, record_number.to_s)
+ end
+
+ before do
+ Timecop.freeze(start_time)
+ Singleton.__init__(FormHandler)
+ allow(storage_service).to receive(:write_file)
+ end
+
+ after do
+ Timecop.return
+ end
+
+ context "when exporting daily organisations in XML" do
+ context "and no organisations are available for export" do
+ it "returns an empty archives list" do
+ expect(export_service.export_xml_organisations).to eq({})
+ end
+ end
+
+ context "and one organisation is available for export" do
+ let!(:organisation) { create(:organisation) }
+
+ it "generates a ZIP export file with the expected filename" do
+ expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args)
+ export_service.export_xml_organisations
+ end
+
+ it "generates an XML export file with the expected filename within the ZIP file" do
+ expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content|
+ entry = Zip::File.open_buffer(content).find_entry(expected_data_filename)
+ expect(entry).not_to be_nil
+ expect(entry.name).to eq(expected_data_filename)
+ end
+ export_service.export_xml_organisations
+ end
+
+ it "generates an XML manifest file with the expected content within the ZIP file" do
+ expected_content = replace_record_number(local_manifest_file.read, 1)
+ expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content|
+ entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename)
+ expect(entry).not_to be_nil
+ expect(entry.get_input_stream.read).to eq(expected_content)
+ end
+
+ export_service.export_xml_organisations
+ end
+
+ it "generates an XML export file with the expected content within the ZIP file" do
+ expected_content = replace_entity_ids(organisation, xml_export_file.read)
+ expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content|
+ entry = Zip::File.open_buffer(content).find_entry(expected_data_filename)
+ expect(entry).not_to be_nil
+ expect(entry.get_input_stream.read).to eq(expected_content)
+ end
+
+ export_service.export_xml_organisations
+ end
+
+ it "returns the list with correct archive" do
+ expect(export_service.export_xml_organisations).to eq({ expected_zip_filename.gsub(".zip", "") => start_time })
+ end
+ end
+
+ context "and multiple organisations are available for export" do
+ before do
+ create(:organisation)
+ create(:organisation)
+ end
+
+ it "generates an XML manifest file with the expected content within the ZIP file" do
+ expected_content = replace_record_number(local_manifest_file.read, 2)
+ expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content|
+ entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename)
+ expect(entry).not_to be_nil
+ expect(entry.get_input_stream.read).to eq(expected_content)
+ end
+
+ export_service.export_xml_organisations
+ end
+
+ it "creates an export record in a database with correct time" do
+ expect { export_service.export_xml_organisations }
+ .to change(Export, :count).by(1)
+ expect(Export.last.started_at).to be_within(2.seconds).of(start_time)
+ end
+
+ context "when this is the first export (full)" do
+ it "returns a ZIP archive for the master manifest" do
+ expect(export_service.export_xml_organisations).to eq({ expected_zip_filename.gsub(".zip", "").gsub(".zip", "") => start_time })
+ end
+ end
+
+ context "and underlying data changes between getting the organisations and writting the manifest" do
+ def remove_organisations(organisations)
+ organisations.each(&:destroy)
+ file = Tempfile.new
+ doc = Nokogiri::XML("
")
+ doc.write_xml_to(file, encoding: "UTF-8")
+ file.rewind
+ file
+ end
+
+ def create_fake_maifest
+ file = Tempfile.new
+ doc = Nokogiri::XML("
")
+ doc.write_xml_to(file, encoding: "UTF-8")
+ file.rewind
+ file
+ end
+
+ it "maintains the same record number" do
+ # rubocop:disable RSpec/SubjectStub
+ allow(export_service).to receive(:build_export_xml) do |organisations|
+ remove_organisations(organisations)
+ end
+ allow(export_service).to receive(:build_manifest_xml) do
+ create_fake_maifest
+ end
+
+ expect(export_service).to receive(:build_manifest_xml).with(2)
+ # rubocop:enable RSpec/SubjectStub
+ export_service.export_xml_organisations
+ end
+ end
+
+ context "when this is a second export (partial)" do
+ before do
+ start_time = Time.zone.local(2022, 6, 1)
+ Export.new(started_at: start_time, collection: "organisations").save! # this should be organisation export
+ end
+
+ it "does not add any entry for the master manifest (no organisations)" do
+ expect(export_service.export_xml_organisations).to eq({})
+ end
+ end
+ end
+
+ context "and a previous export has run the same day having organisations" do
+ before do
+ create(:organisation)
+ export_service.export_xml_organisations
+ end
+
+ context "and we trigger another full update" do
+ it "increments the base number" do
+ export_service.export_xml_organisations(full_update: true)
+ expect(Export.last.base_number).to eq(2)
+ end
+
+ it "resets the increment number" do
+ export_service.export_xml_organisations(full_update: true)
+ expect(Export.last.increment_number).to eq(1)
+ end
+
+ it "returns a correct archives list for manifest file" do
+ expect(export_service.export_xml_organisations(full_update: true)).to eq({ "organisations_2024_2025_apr_mar_f0002_inc0001" => start_time })
+ end
+
+ it "generates a ZIP export file with the expected filename" do
+ expect(storage_service).to receive(:write_file).with("organisations_2024_2025_apr_mar_f0002_inc0001.zip", any_args)
+ export_service.export_xml_organisations(full_update: true)
+ end
+ end
+ end
+
+ context "and a previous export has run having no organisations" do
+ before { export_service.export_xml_organisations }
+
+ it "doesn't increment the manifest number by 1" do
+ export_service.export_xml_organisations
+
+ expect(Export.last.increment_number).to eq(1)
+ end
+ end
+
+ context "and an organisation has been migrated since the previous partial export" do
+ before do
+ create(:organisation, updated_at: Time.zone.local(2022, 4, 27))
+ create(:organisation, updated_at: Time.zone.local(2022, 4, 27))
+ Export.create!(started_at: Time.zone.local(2022, 4, 26), base_number: 1, increment_number: 1)
+ end
+
+ it "generates an XML manifest file with the expected content within the ZIP file" do
+ expected_content = replace_record_number(local_manifest_file.read, 2)
+ expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content|
+ entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename)
+ expect(entry).not_to be_nil
+ expect(entry.get_input_stream.read).to eq(expected_content)
+ end
+
+ expect(export_service.export_xml_organisations).to eq({ expected_zip_filename.gsub(".zip", "") => start_time })
+ end
+ end
+ end
+end
diff --git a/spec/services/exports/user_export_service_spec.rb b/spec/services/exports/user_export_service_spec.rb
new file mode 100644
index 000000000..713d6f907
--- /dev/null
+++ b/spec/services/exports/user_export_service_spec.rb
@@ -0,0 +1,219 @@
+require "rails_helper"
+
+RSpec.describe Exports::UserExportService do
+ subject(:export_service) { described_class.new(storage_service, start_time) }
+
+ let(:storage_service) { instance_double(Storage::S3Service) }
+
+ let(:xml_export_file) { File.open("spec/fixtures/exports/user.xml", "r:UTF-8") }
+ let(:local_manifest_file) { File.open("spec/fixtures/exports/manifest.xml", "r:UTF-8") }
+
+ let(:expected_zip_filename) { "users_2024_2025_apr_mar_f0001_inc0001.zip" }
+ let(:expected_data_filename) { "users_2024_2025_apr_mar_f0001_inc0001_pt001.xml" }
+ let(:expected_manifest_filename) { "manifest.xml" }
+ let(:start_time) { Time.zone.local(2022, 5, 1) }
+ let(:organisation) { create(:organisation, with_dsa: false) }
+
+ def replace_entity_ids(user, export_template)
+ export_template.sub!(/\{id\}/, user["id"].to_s)
+ export_template.sub!(/\{organisation_id\}/, user["organisation_id"].to_s)
+ export_template.sub!(/\{email\}/, user["email"].to_s)
+ end
+
+ def replace_record_number(export_template, record_number)
+ export_template.sub!(/\{recno\}/, record_number.to_s)
+ end
+
+ before do
+ Timecop.freeze(start_time)
+ Singleton.__init__(FormHandler)
+ allow(storage_service).to receive(:write_file)
+ end
+
+ after do
+ Timecop.return
+ end
+
+ context "when exporting daily users in XML" do
+ context "and no users are available for export" do
+ it "returns an empty archives list" do
+ expect(export_service.export_xml_users).to eq({})
+ end
+ end
+
+ context "and one user is available for export" do
+ let!(:user) { create(:user, organisation:, phone_extension: "123") }
+
+ it "generates a ZIP export file with the expected filename" do
+ expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args)
+ export_service.export_xml_users
+ end
+
+ it "generates an XML export file with the expected filename within the ZIP file" do
+ expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content|
+ entry = Zip::File.open_buffer(content).find_entry(expected_data_filename)
+ expect(entry).not_to be_nil
+ expect(entry.name).to eq(expected_data_filename)
+ end
+ export_service.export_xml_users
+ end
+
+ it "generates an XML manifest file with the expected content within the ZIP file" do
+ expected_content = replace_record_number(local_manifest_file.read, 1)
+ expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content|
+ entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename)
+ expect(entry).not_to be_nil
+ expect(entry.get_input_stream.read).to eq(expected_content)
+ end
+
+ export_service.export_xml_users
+ end
+
+ it "generates an XML export file with the expected content within the ZIP file" do
+ expected_content = replace_entity_ids(user, xml_export_file.read)
+ expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content|
+ entry = Zip::File.open_buffer(content).find_entry(expected_data_filename)
+ expect(entry).not_to be_nil
+ expect(entry.get_input_stream.read).to eq(expected_content)
+ end
+
+ export_service.export_xml_users
+ end
+
+ it "returns the list with correct archive" do
+ expect(export_service.export_xml_users).to eq({ expected_zip_filename.gsub(".zip", "") => start_time })
+ end
+ end
+
+ context "and multiple users are available for export" do
+ before do
+ create(:user, organisation:)
+ create(:user, organisation:)
+ end
+
+ it "generates an XML manifest file with the expected content within the ZIP file" do
+ expected_content = replace_record_number(local_manifest_file.read, 2)
+ expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content|
+ entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename)
+ expect(entry).not_to be_nil
+ expect(entry.get_input_stream.read).to eq(expected_content)
+ end
+
+ export_service.export_xml_users
+ end
+
+ it "creates an export record in a database with correct time" do
+ expect { export_service.export_xml_users }
+ .to change(Export, :count).by(1)
+ expect(Export.last.started_at).to be_within(2.seconds).of(start_time)
+ end
+
+ context "when this is the first export (full)" do
+ it "returns a ZIP archive for the master manifest (existing lettings logs)" do
+ expect(export_service.export_xml_users).to eq({ expected_zip_filename.gsub(".zip", "").gsub(".zip", "") => start_time })
+ end
+ end
+
+ context "and underlying data changes between getting the users and writting the manifest" do
+ def remove_users(users)
+ users.each(&:destroy)
+ file = Tempfile.new
+ doc = Nokogiri::XML("
")
+ doc.write_xml_to(file, encoding: "UTF-8")
+ file.rewind
+ file
+ end
+
+ def create_fake_maifest
+ file = Tempfile.new
+ doc = Nokogiri::XML("
")
+ doc.write_xml_to(file, encoding: "UTF-8")
+ file.rewind
+ file
+ end
+
+ it "maintains the same record number" do
+ # rubocop:disable RSpec/SubjectStub
+ allow(export_service).to receive(:build_export_xml) do |users|
+ remove_users(users)
+ end
+ allow(export_service).to receive(:build_manifest_xml) do
+ create_fake_maifest
+ end
+
+ expect(export_service).to receive(:build_manifest_xml).with(2)
+ # rubocop:enable RSpec/SubjectStub
+ export_service.export_xml_users
+ end
+ end
+
+ context "when this is a second export (partial)" do
+ before do
+ start_time = Time.zone.local(2022, 6, 1)
+ Export.new(started_at: start_time, collection: "users").save! # this should be user export
+ end
+
+ it "does not add any entry for the master manifest (no users)" do
+ expect(export_service.export_xml_users).to eq({})
+ end
+ end
+ end
+
+ context "and a previous export has run the same day having users" do
+ before do
+ create(:user, organisation:)
+ export_service.export_xml_users
+ end
+
+ context "and we trigger another full update" do
+ it "increments the base number" do
+ export_service.export_xml_users(full_update: true)
+ expect(Export.last.base_number).to eq(2)
+ end
+
+ it "resets the increment number" do
+ export_service.export_xml_users(full_update: true)
+ expect(Export.last.increment_number).to eq(1)
+ end
+
+ it "returns a correct archives list for manifest file" do
+ expect(export_service.export_xml_users(full_update: true)).to eq({ "users_2024_2025_apr_mar_f0002_inc0001" => start_time })
+ end
+
+ it "generates a ZIP export file with the expected filename" do
+ expect(storage_service).to receive(:write_file).with("users_2024_2025_apr_mar_f0002_inc0001.zip", any_args)
+ export_service.export_xml_users(full_update: true)
+ end
+ end
+ end
+
+ context "and a previous export has run having no users" do
+ before { export_service.export_xml_users }
+
+ it "doesn't increment the manifest number by 1" do
+ export_service.export_xml_users
+
+ expect(Export.last.increment_number).to eq(1)
+ end
+ end
+
+ context "and a user has been migrated since the previous partial export" do
+ before do
+ create(:user, updated_at: Time.zone.local(2022, 4, 27), organisation:)
+ create(:user, updated_at: Time.zone.local(2022, 4, 27), organisation:)
+ Export.create!(started_at: Time.zone.local(2022, 4, 26), base_number: 1, increment_number: 1)
+ end
+
+ it "generates an XML manifest file with the expected content within the ZIP file" do
+ expected_content = replace_record_number(local_manifest_file.read, 2)
+ expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content|
+ entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename)
+ expect(entry).not_to be_nil
+ expect(entry.get_input_stream.read).to eq(expected_content)
+ end
+
+ expect(export_service.export_xml_users).to eq({ expected_zip_filename.gsub(".zip", "") => start_time })
+ end
+ end
+ end
+end