From a4a80f863f58c917261b45c03bbc0f57d293a1b0 Mon Sep 17 00:00:00 2001 From: Paul Robert Lloyd Date: Mon, 11 Jul 2022 15:09:43 +0100 Subject: [PATCH 01/17] Fix Swagger docs not loading external resources (#725) --- docs/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.html b/docs/index.html index 0c25ee1da..2378c6457 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,9 +3,9 @@ - + OpenAPI DLUHC CORE Data Collection -
+
+ + diff --git a/docs/api/DLUHC-CORE-Data.v1.json b/docs/api/v1.json similarity index 99% rename from docs/api/DLUHC-CORE-Data.v1.json rename to docs/api/v1.json index 07a911425..893abc0c3 100644 --- a/docs/api/DLUHC-CORE-Data.v1.json +++ b/docs/api/v1.json @@ -1,13 +1,13 @@ { "openapi": "3.0.0", "info": { - "title": "DLUHC CORE Data", + "title": "DLUHC CORE Data Collection API", "version": "1.0", - "description": "Submit or Update CORE Case Log Data on Lettings and Sales of Social Housing in England" + "description": "Submit social housing lettings and sales data (CORE)" }, "servers": [ { - "url": "https://dluhc-core.london.cloudapps.digital", + "url": "https://dluhc-core-staging.london.cloudapps.digital/logs", "description": "Staging" } ], diff --git a/docs/exports.md b/docs/exports.md index 2b7e1ba25..cadad0962 100644 --- a/docs/exports.md +++ b/docs/exports.md @@ -1,3 +1,7 @@ +--- +nav_order: 7 +--- + # Exporting to CDS All data collected by the application needs to be exported to the Consolidated Data Store (CDS) which is a data warehouse based on MS SQL running in the DAP (Data Analytics Platform). @@ -6,7 +10,7 @@ This is done via XML exports saved in an S3 bucket located in the DAP VPC using Initially the application database field names and field types were chosen to match the existing CDS data as closely as possible to minimise the amount of transformation needed. This has led to a less than optimal data model though and increasingly we should look to transform at the mapping layer where beneficial for our application. -The export service is triggered nightly using [Gov PaaS tasks](https://docs.cloudfoundry.org/devguide/using-tasks.html). These tasks are triggered from a Github action, as Gov PaaS does not currently support the Cloud Foundry Task Scheduler. +The export service is triggered nightly using [Gov PaaS tasks](https://docs.cloudfoundry.org/devguide/using-tasks.html). These tasks are triggered from a GitHub action, as Gov PaaS does not currently support the Cloud Foundry Task Scheduler. The S3 bucket is located in the DAP VPC rather than the application VPC as DAP runs in an AWS account directly so access to the S3 bucket can be restricted to only the IPs used by the application. This is not possible the other way around as [Gov PaaS does not support restricting S3 access by IP](https://github.com/alphagov/paas-roadmap/issues/107). diff --git a/docs/form_builder.md b/docs/form/builder.md similarity index 70% rename from docs/form_builder.md rename to docs/form/builder.md index 35bac7ac7..5d7ba5295 100644 --- a/docs/form_builder.md +++ b/docs/form/builder.md @@ -1,36 +1,15 @@ -# Form Builder +--- +parent: Generating forms +nav_order: 1 +--- -## Background - -Social housing lettings and sales data is collected in annual collection windows that run from 1st April to 1st April. - -During this window the form and questions generally stay constant. The form will generally change by small amounts between each collection window. Typical changes are adding new questions, adding or removing answer options from questions or tweaking question wording for clarity. - -A paper form is produced for guidance and to help data providers collect the data offline, and a bulk upload template is circulated which need to match the online form. - -Data is accepted for a collection window for up to 3 months after it’s finished to allow for late data submission. This means that between April and July two version of the form run simultaneously. - -Other considerations that went into our design are being able to re-use as much of this solution for other data collections, and possibly having the ability to generate the form and/or form changes from a user interface. - -We haven’t used micro-services, preferring to deploy a single application but we have modelled the form itself as configuration in the form of a JSON structure that acts as a sort of DSL/form builder for the form. - -The idea is to decouple the code that creates the required routes, controller methods, views etc to display the form from the actual wording of questions or order of pages such that it becomes possible to make changes to the form with little or no code changes. - -This should also mean that in the future it could be possible to create an interface that can construct the JSON config, which would open up the ability to make form changes to a wider audience. Doing this fully would require generating and running the necessary migrations for data storage, generating the required ActiveRecord methods to validate the data server side, and generating/updating API endpoints and documentation. All of this is likely to be beyond the scope of initial MVP but could be looked at in the future. - -Since initially the JSON config will not create database migrations or ActiveRecord model validations, it will instead assume that these have been correctly created for the config provided. The reasoning for this is the following assumptions: - -- The form will be tweaked regularly (amending questions wording, changing the order of questions or the page a question is displayed on) - -- The actual data collected will change very infrequently. Time series continuity is very important to ADD (Analysis and Data Directorate) so the actual data collected should stay largely consistent i.e. in general we can change the question wording in ways that makes the intent clearer or easier to understand, but not in ways that would make the data provider give a different answer. - -A form parser class will parse this config into ruby objects/methods that can be used as an API by the rest of the application, such that we could change the underlying config if needed (for example swap JSON for YAML or for DataBase objects) without needing to change the rest of the application. We’ll call this the Form Runner part of the application. +# Form builder ## Setup this log The setup this log section is treated slightly differently from the rest of the form. It is more accurately viewed as providing metadata about the form than as being part of the form itself. It also needs to know far more about the application specific context than other parts of the form such as who the current user is, what organisation they’re part of and what role they have etc. -As a result it’s not modelled as part of the config but rather as code. It still uses the same Form Runner components though. +As a result it’s not modelled as part of the config but rather as code. It still uses the same [Form Runner](runner) components though. ## Features the Form Config supports @@ -183,7 +162,7 @@ This will validate all forms in directories `["config/forms", "spec/fixtures/for ## Form models and definition -For information about the form model and related models (section, subsection, page, question) and how these relate to each other follow [this link](/docs/form/form.md) +For information about the form model and related models (section, subsection, page, question) and how these relate to each other see [form definition](/form/definition). ## Improvements that could be made diff --git a/docs/form/form.md b/docs/form/definition.md similarity index 60% rename from docs/form/form.md rename to docs/form/definition.md index 44200ec3e..499abd079 100644 --- a/docs/form/form.md +++ b/docs/form/definition.md @@ -1,4 +1,10 @@ -## Form Definition +--- +parent: Generating forms +has_children: true +nav_order: 3 +--- + +# Form definition The current system is built around a form definition written in JSON. At the top level every form will expect to have the following attributes: @@ -8,7 +14,8 @@ The current system is built around a form definition written in JSON. At the top - Sections: the sections in the form, this block is where the bulk of the form definition will be. An example of this might look like the following: -```JSON + +```json { "form_type": "lettings", "start_date": "2021-04-01T00:00:00.000+01:00", @@ -21,50 +28,39 @@ An example of this might look like the following: Note that the end date of one form will overlap the start date of another to allow for late submissions. This means that every year there will be a period of time in which two forms are running simultaneously. -### How is the form split up? - -A summary of how the form is split up is as follows: - -- A form is divided up into one or more sections. -- Each section can have one or more subsections. -- Each subsection can have one or more pages. -- Each page can have one or more questions. - -More information about these form elements can be found in the following links: +A form is split up is as follows: -- [Section](/docs/form/section.md) -- [Subsection](/docs/form/subsection.md) -- [Page](/docs/form/page.md) -- [Question](/docs/form/question.md) +- A form is divided up into one or more [sections](section) +- Each section can have one or more [subsections](subsection) +- Each subsection can have one or more [pages](page) +- Each page can have one or more [questions](question) -### The Form Model, Views and Controller +Rails uses the model, view, controller (MVC) pattern which we follow. -Rails uses the Model, View, Controller (MVC) pattern which we follow. - -#### The Form Model +## Form model There is no need to manually initialise a form object as this is handled by the FormHandler class at boot time. If a new form needs to be added then a JSON file containing the form definition should be added to `config/forms` where the FormHandler will be able to locate it and instantiate it. A form has the following attributes: -- name: The name of the form -- setup_sections: The setup section (this is not defined in the JSON, for more information see this) -- form_definition: The parsed form JSON -- form_sections: The sections found within the form definition JSON -- type: The type of form (this is used to indicate if the form is for a sale or a letting) -- sections: The combination of the setup section with those found in the JSON definition -- subsections: The subsections of the form (these live under the sections) -- pages: The pages of the form (these live under the subsections) -- questions: The questions of the form (these live under the pages) -- start_date: The start date of the form, in iso8601 format -- end_date: The end date of the form, in iso8601 format - +- `name`: The name of the form +- `setup_sections`: The setup section (this is not defined in the JSON, for more information see this) +- `form_definition`: The parsed form JSON +- `form_sections`: The sections found within the form definition JSON +- `type`: The type of form (this is used to indicate if the form is for a sale or a letting) +- `sections`: The combination of the setup section with those found in the JSON definition +- `subsections`: The subsections of the form (these live under the sections) +- `pages`: The pages of the form (these live under the subsections) +- `questions`: The questions of the form (these live under the pages) +- `start_date`: The start date of the form, in ISO 8601 format +- `end_date`: The end date of the form, in ISO 8601 format -#### The Form Views +## Form views The main view used for rendering the form is the `app/views/form/page.html.erb` view as the Form contains multiple pages (which live in subsections within sections). This page view then renders the appropriate partials for the question types of the questions on the current page. We currently have views for the following question types: + - Numerical - Date - Checkbox @@ -76,11 +72,10 @@ We currently have views for the following question types: Interruption screen questions are radio questions used for soft validation of fields. They usually have yes and no options for a user to confirm a value is correct. -#### The Form Controller +## Form controller The form controller handles the form submission as well as the rendering of the check answers page and the review page. -### The FormHandler helper class +## FormHandler helper class The FormHandler helper is a helper that loads all of the defined forms and initialises them as Form objects. It can also be used to get specific forms if needed. - diff --git a/docs/form/index.md b/docs/form/index.md new file mode 100644 index 000000000..148ffb1c4 --- /dev/null +++ b/docs/form/index.md @@ -0,0 +1,30 @@ +--- +has_children: true +nav_order: 8 +--- + +# Generating forms + +Social housing lettings and sales data is collected in annual collection windows that run from 1 April to 1 April the following year. + +During this window the form and questions generally stay constant. The form will generally change by small amounts between each collection window. Typical changes are adding new questions, adding or removing answer options from questions or tweaking question wording for clarity. + +A paper form is produced for guidance and to help data providers collect the data offline, and a bulk upload template is circulated which need to match the online form. + +Data is accepted for a collection window for up to 3 months after it’s finished to allow for late data submission. This means that between April and July 2 versions of the form run simultaneously. + +Other considerations that went into our design are being able to re-use as much of this solution for other data collections, and possibly having the ability to generate the form and/or form changes from a user interface. + +We haven’t used micro-services, preferring to deploy a single application but we have modelled the form itself as configuration in the form of a JSON structure that acts as a sort of DSL/form builder for the form. + +The idea is to decouple the code that creates the required routes, controller methods, views etc to display the form from the actual wording of questions or order of pages such that it becomes possible to make changes to the form with little or no code changes. + +This should also mean that in the future it could be possible to create an interface that can construct the JSON config, which would open up the ability to make form changes to a wider audience. Doing this fully would require generating and running the necessary migrations for data storage, generating the required ActiveRecord methods to validate the data server side, and generating/updating API endpoints and documentation. All of this is likely to be beyond the scope of initial MVP but could be looked at in the future. + +Since initially the JSON config will not create database migrations or ActiveRecord model validations, it will instead assume that these have been correctly created for the config provided. The reasoning for this is the following assumptions: + +- The form will be tweaked regularly (amending questions wording, changing the order of questions or the page a question is displayed on) + +- The actual data collected will change very infrequently. Time series continuity is very important to ADD (Analysis and Data Directorate) so the actual data collected should stay largely consistent i.e. in general we can change the question wording in ways that makes the intent clearer or easier to understand, but not in ways that would make the data provider give a different answer. + +A form parser class will parse this config into ruby objects/methods that can be used as an API by the rest of the application, such that we could change the underlying config if needed (for example swap JSON for YAML or for DataBase objects) without needing to change the rest of the application. We’ll call this the Form Runner part of the application. diff --git a/docs/form/page.md b/docs/form/page.md index ab47b4f0d..a7a8a86c3 100644 --- a/docs/form/page.md +++ b/docs/form/page.md @@ -1,8 +1,16 @@ -## Page +--- +parent: Form definition +grand_parent: Generating forms +nav_order: 3 +--- -Pages are under the subsection level of the form definition. A example page might look something like this: +# Page -```JSON +Pages sit below the [`Subsection`](subsection) level of a form definition. + +An example page might look something like this: + +```json "property_postcode": { "header": "", "description": "", @@ -23,6 +31,6 @@ The header is optional but if provided is used for the heading displayed on the The description is optional but if provided is used for a paragraph displayed under the page header. -It's worth noting that like subsections a page can also have a `depends_on` which contains the set of conditions that must be met for the section to be accessibile to a data provider. If the conditions are not met then the page is not routed to as part of the form flow. The `depends_on` for a page will usually depend on answers given to questions, most likely to be questions in the setup section. In the above example the page is dependent on the answer to the `needstype` question being `1`, which corresponds to picking `General needs` on that question as displayed to the data provider. +It’s worth noting that like subsections a page can also have a `depends_on` which contains the set of conditions that must be met for the section to be accessible to a data provider. If the conditions are not met then the page is not routed to as part of the form flow. The `depends_on` for a page will usually depend on answers given to questions, most likely to be questions in the setup section. In the above example the page is dependent on the answer to the `needstype` question being `1`, which corresponds to picking `General needs` on that question as displayed to the data provider. -Pages can contain one or more questions. \ No newline at end of file +Pages can contain one or more [questions](question). diff --git a/docs/form/question.md b/docs/form/question.md index 4050531f9..01d598a4f 100644 --- a/docs/form/question.md +++ b/docs/form/question.md @@ -1,8 +1,16 @@ -## Question +--- +parent: Form definition +grand_parent: Generating forms +nav_order: 4 +--- -Questions are under the page level of the form definition. A example question might look something like this: +# Question -```JSON +Questions are under the page level of the form definition. + +An example question might look something like this: + +```json "postcode_known": { "check_answer_label": "Do you know the property postcode?", "header": "Do you know the property’s postcode?", @@ -27,7 +35,7 @@ In the above example the the question has the id `postcode_known`. The `check_answer_label` contains the text that will be displayed in the label of the table on the check answers page. -The header is text that is displayed for the question. +The header is text that is displayed for the question. Hint text is optional, but if provided it sits under the header and is normally given to provide the data inputters with guidance when answering the question, for example it might inform them about terms used in the question. @@ -35,9 +43,9 @@ The type is question type, which is used to determine the view rendered for the The `conditional_for` contains the value needed to be selected by the data inputter in order to display another question that appears on the same page. In the example above the `postcode_full` question depends on the answer to `postcode_known` being selected as `1` or `Yes`, this would then display the `postcode_full` underneath the `Yes` option on the page, allowing the provide the provide the postcode if they have indicated they know it. If the user has JavaScript enabled then this realtime conditional display is handled by the `app/frontend/controllers/conditional_question_controller.js` file. -the `hidden_in_check_answers` is used to hide a value from displaying on the check answers page. You only need to provide this if you want to set it to true in order to hide the value for some reason e.g. it's one of two questions appearing on a page and the other question is displayed on the check answers page. It's also worth noting that you can declare this as a with a `depends_on` which can be useful for conditionally displaying values on the check answers page. For example: +the `hidden_in_check_answers` is used to hide a value from displaying on the check answers page. You only need to provide this if you want to set it to true in order to hide the value for some reason e.g. it's one of two questions appearing on a page and the other question is displayed on the check answers page. It's also worth noting that you can declare this as a with a `depends_on` which can be useful for conditionally displaying values on the check answers page. For example: -```JSON +```json "hidden_in_check_answers": { "depends_on": [ { @@ -54,7 +62,7 @@ Would mean the question the above is attached to would be hidden in the check an The answer the data inputter provides to some questions allows us to infer the values of other questions we might have asked in the form, allowing us to save the data inputters some time. An example of how this might look is as follows: -```JSON +```json "postcode_full": { "check_answer_label": "Postcode", "header": "What is the property’s postcode?", @@ -79,4 +87,4 @@ In the above example the width is an optional attribute and can be provided for The above example links to the first example as both of these questions would be on the same page. The `inferred_check_answers_value` is what should be displayed on the check answers page for this question if we infer it. If the value of `postcode_known` was given as `0` (which is a no), as seen in the condition part of `inferred_check_answers_value` then we can infer that the data inputter does not know the postcode and so we would display the value of `Not known` on the check answers page for the postcode. -In the above example the `inferred_answers` refers to a question where we can infer the answer based on the answer of this question. In this case the `la` question can be inferred from the postcode value given by the data inputter as we are able to lookup the local authority based on the postcode given. We then set a property on the case log `is_la_inferred` to true to indicate that this is an answer we've inferred. \ No newline at end of file +In the above example the `inferred_answers` refers to a question where we can infer the answer based on the answer of this question. In this case the `la` question can be inferred from the postcode value given by the data inputter as we are able to lookup the local authority based on the postcode given. We then set a property on the case log `is_la_inferred` to true to indicate that this is an answer we've inferred. diff --git a/docs/form_runner.md b/docs/form/runner.md similarity index 85% rename from docs/form_runner.md rename to docs/form/runner.md index 1719baac1..59da9d845 100644 --- a/docs/form_runner.md +++ b/docs/form/runner.md @@ -1,12 +1,17 @@ -# Form Runner +--- +parent: Generating forms +nav_order: 2 +--- -The Form Runner is composed of: +# Form runner -Ruby Classes: +The Form runner is composed of: + +Ruby classes: - A singleton form handler that instantiates an instances of each form definition (config file we have) combined with the setup section that is common to all forms. This is created at rails boot time. (`app/models/form_handler.rb`) - A `Form` class that is the entry point for parsing a form definition and handles most of the associated logic (`app/models/form.rb`) -- `Section`, `Subsection`, `Page` and `Question` classes (`app/models/form/`) +- [`Section`](section), [`Subsection`](subsection), [`Page`](page) and [`Question`](question) classes (`app/models/form/`) - Setup subsection specific instances (subclasses) of `Section`, `Subsection`, `Pages` and `Questions` (`app/form/setup/`) ERB templates: @@ -22,4 +27,4 @@ All form pages submit to the same controller method (`app/controllers/form_contr ## Form models and definition -For information about the form model and related models (section, subsection, page, question) and how these relate to each other follow [this link](/docs/form/form.md) \ No newline at end of file +For information about the form model and related models (section, subsection, page, question) and how these relate to each other see [form definition](/form/definition). diff --git a/docs/form/section.md b/docs/form/section.md index 9454a6569..82ee7cffb 100644 --- a/docs/form/section.md +++ b/docs/form/section.md @@ -1,26 +1,34 @@ -## Section +--- +parent: Form definition +grand_parent: Generating forms +nav_order: 1 +--- -Sections are under the top level of the form definition. A example section might look something like this: +# Section -```JSON +Sections sit at the top level of a form definition. + +An example section might look something like this: + +```json "sections": { - "tenancy_and_property": { - "label": "Property and tenancy information", - "subsections": { - "property_information": { - ... - }, - "tenancy_information": { - ... - } + "tenancy_and_property": { + "label": "Property and tenancy information", + "subsections": { + "property_information": { + ... + }, + "tenancy_information": { + ... } - }, - ... + } + }, + ... } ``` In the above example the section id would be `tenancy_and_property` and its subsections would be `property_information` and `tenancy_information`. -The label contains the text that users will see for that section in the tasklist page of a case log. +The label contains the text that users will see for that section in the task list page of a case log. -Sections can contain one or more subsections. \ No newline at end of file +Sections can contain one or more [subsections](subsection). diff --git a/docs/form/subsection.md b/docs/form/subsection.md index b65f064c6..737a2fd08 100644 --- a/docs/form/subsection.md +++ b/docs/form/subsection.md @@ -1,8 +1,16 @@ -## Subsection +--- +parent: Form definition +grand_parent: Generating forms +nav_order: 2 +--- -Subsections are under the section level of the form definition. A example subsection might look something like this: +# Subsection -```JSON +Subsections sit below the [`Section`](section) level of a form definition. + +An example subsection might look something like this: + +```json "property_information": { "label": "Property information", "depends_on": [ @@ -21,8 +29,10 @@ Subsections are under the section level of the form definition. A example subsec } ``` -In the above example the the subsection has the id `property_information`. The `depends_on` contains the set of conditions that must be met for the section to be accessibile to a data provider, in this example subsection depends on the completion of the setup section/subsection (note that this is a common condition as the answers provided to questions in the setup subsection often have an impact on what questions are asked of the data provider in later subsections of the form). +In the above example the the subsection has the id `property_information`. The `depends_on` contains the set of conditions that must be met for the section to be accessible to a data provider, in this example subsection depends on the completion of the setup section/subsection (note that this is a common condition as the answers provided to questions in the setup subsection often have an impact on what questions are asked of the data provider in later subsections of the form). + +The label contains the text that users will see for that subsection in the task list page of a case log. -The label contains the text that users will see for that subsection in the tasklist page of a case log. +The pages of the subsection in the example would be `property_postcode` and `property_local_authority`. -The pages of the subsection in the example would be `property_postcode` and `property_local_authority`. Subsections can contain one or more pages. \ No newline at end of file +Subsections can contain one or more [pages](page). diff --git a/docs/frontend.md b/docs/frontend.md index d8957429c..4157a892c 100644 --- a/docs/frontend.md +++ b/docs/frontend.md @@ -1,3 +1,7 @@ +--- +nav_order: 2 +--- + # Frontend ## GOV.UK Design System components diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 2378c6457..000000000 --- a/docs/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - -OpenAPI DLUHC CORE Data Collection -
- - diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..ecb2272d6 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,67 @@ +--- +nav_order: 1 +--- + +# Overview + +All lettings and and sales of social housing in England need to be logged with the Department for levelling up, housing and communities (DLUHC). This is done by data providing organisations: Local Authorities and Private Registered Providers (PRPs, i.e. housing associations). + +Data is collected via a form that runs on an annual data collection window basis. Form changes are made annually to add new questions, remove any that are no longer needed, or adjust wording or answer options etc. + +Each data collection window runs from 1 April to 1 April the following year (plus an extra 3 months to allow for any late submissions). This means that between April and June, 2 collection windows are open simultaneously and logs can be submitted for either. + +ADD (Analytics & Data Directorate) statisticians are the other primary users of the service. The data collected is transferred to DLUHCs consolidated data store (CDS) via nightly XML exports to an S3 bucket. CDS ingests and transforms this data, ultimately storing it in a MS SQL database and exposing it to analysts and statisticians via Amazon Workspaces. + +![Diagram of the CORE system architecture](../images/architecture.drawio.png) + +## Users + +External data providing organisations have 2 main user types: + +- **Data coordinators** are administrators for their organisation, but may also complete logs +- **Data providers** complete the logs + +Additionally there are data protection officers (DPO). For some organisations this is a separate role, but in our codebase this is modelled as an attribute of a user (i.e. a data coordinator or provider can additionally be a DPO). They are responsible for ensuring the organisation has signed the data sharing agreement. + +There are also 2 internal user types: + +- **Customer support:** can administrate all organisations +- **Statisticians:** primary consumers of the collected data + +## Organisations + +There are 2 types of organisation: + +- An **owning organisations** own housing stock. It may manage the allocation of people in and out of their accommodation, or contract this function out to managing agents. + +- A **managing organisation** (or managing agent) is responsible for the allocation of people in and out of accommodation, and/or responsible for the services provided to support those people in the accommodation (in the case of supported housing). + +### Relationships between organisations + +Organisations that own stock can contract out the management of that stock to another organisation. This relationship is often referred to as a parent/child relationship. + +This is a useful analogy as a parent can have multiple children, and a child can have many parents. A child organisation can also be a parent, and a parent organisation can also be a child organisation: + +![Organisational relationships](../images/organisational_relationships.png) + +### User permissions within organisations + +The case logs that a user can see depends on their role: + +- Customer support users can access any case log + +- Data coordinators can access any case log for which the organisation they work for is ultimately responsible for, meaning they can see logs managed by a child organisation + +- Data providers can only access case logs for which their organisation manages (or directly owns) + +Taking the relationships from the above diagram, and looking at which logs each user can access: + +![User log access permissions](../images/user_log_permissions.png) + +## Supported housing schemes + +A supported housing scheme (or service) provides shared or self-contained housing for a particular client group, for example younger or vulnerable people. A scheme can be run at multiple locations, and a single location may contain multiple units (for example bedrooms in shared houses or a bungalow with 3 bedrooms). + +Logs for supported housing will share a number of similar characteristics at this location. Additional data also needs to be collected specifically regarding the supported housing scheme, such as the type of client groups served and type of support provided. + +Asking these questions would require data inputters to re-enter the same information repeatedly and answer more questions than those asked for general needs lettings. Schemes exist in CORE to reduce this burden, and effectively act as predefined answer sets. diff --git a/docs/infrastructure.md b/docs/infrastructure.md index 6dec9f295..9c3d1c8d7 100644 --- a/docs/infrastructure.md +++ b/docs/infrastructure.md @@ -1,3 +1,7 @@ +--- +nav_order: 5 +--- + # Infrastructure ## Deployment @@ -26,7 +30,7 @@ This application is running on [GOV.UK PaaS](https://www.cloud.service.gov.uk/). cf push dluhc-core --strategy rolling ``` - This will use the [manifest file](staging_manifest.yml) + This will use the [manifest file](https://github.com/communitiesuk/submit-social-housing-lettings-and-sales-data/blob/main/manifest.yml) Once the app is deployed: diff --git a/docs/monitoring.md b/docs/monitoring.md index e26af48c1..ac1a12f83 100644 --- a/docs/monitoring.md +++ b/docs/monitoring.md @@ -1,6 +1,10 @@ +--- +nav_order: 6 +--- + # Monitoring -We use self-hosted Prometheus and Grafana for monitoring infrastructure metrics. These are run in a dedicated Gov PaaS space called "monitoring" and are deployed as Docker images using Github action pipelines. The repository for these and more information is here: [dluhc-data-collection-monitoring](https://github.com/communitiesuk/dluhc-data-collection-monitoring). +We use self-hosted Prometheus and Grafana for monitoring infrastructure metrics. These are run in a dedicated Gov PaaS space called "monitoring" and are deployed as Docker images using GitHub action pipelines. The repository for these and more information is here: [dluhc-data-collection-monitoring](https://github.com/communitiesuk/dluhc-data-collection-monitoring). ## Performance monitoring and alerting diff --git a/docs/organisations.md b/docs/organisations.md deleted file mode 100644 index e5564519c..000000000 --- a/docs/organisations.md +++ /dev/null @@ -1,25 +0,0 @@ -# Organisational relationships - -## Definitions - -- **Stock owning organisation**: An organisation that owns housing stock. It may manage the allocation of people in and out of their accommodation, or it may contract this out to managing agents. - -- **Managing agent**: In scenarios where one organisation owns stock and another organisation is contracted to manage the stock and tenants, the latter organisation is often called a ‘managing agent’. Managing agents are responsible for the allocation of people in and out of the accommodation, and/or responsible for the services provided to support those people in the accommodation (in the case of supported housing). - -## Permissions - -Organisations that own stock can contract out the management of that stock to another organisation. This relationship is often referred to as a parent/child relationship. This is a useful analogy as a parent can have multiple children, and a child can have many parents. A child organisation can also be a parent, and a parent organisation can also be a child organisation: - -![Organisational relationships](images/organisational_relationships.png) - -The case logs that a user can see depends on their role: - -- Customer support users can access any case log - -- Data coordinators can access any case log for which the organisation they work for is ultimately responsible for, meaning they can see logs managed by a child organisation - -- Data providers can only access case logs for which their organisation manages (or directly owns) - -Taking the relationships from the above diagram, and looking at which logs each user can access: - -![User log access permissions](images/user_log_permissions.png) diff --git a/docs/schemes.md b/docs/schemes.md deleted file mode 100644 index 90d0f58bd..000000000 --- a/docs/schemes.md +++ /dev/null @@ -1,7 +0,0 @@ -# Supported housing schemes - -A supported housing scheme (or service) provides shared or self-contained housing for a particular client group, for example younger or vulnerable people. A scheme can be run at multiple locations, and a single location may contain multiple units (for example bedrooms in shared houses or a bungalow with 3 bedrooms). - -Logs for supported housing will share a number of similar characteristics at this location. Additional data also needs to be collected specifically regarding the supported housing scheme, such as the type of client groups served and type of support provided. - -Asking these questions would require data inputters to re-enter the same information repeatedly and answer more questions than those asked for general needs lettings. Schemes exist in CORE to reduce this burden, and effectively act as predefined answer sets. diff --git a/docs/service_overview.md b/docs/service_overview.md deleted file mode 100644 index c921cd82a..000000000 --- a/docs/service_overview.md +++ /dev/null @@ -1,5 +0,0 @@ -# Service overview - -All lettings and and sales of social housing in England need to be logged with the Department for levelling up, housing and communities (DLUHC). This is done by Local Authorities and Housing Associations, who are the primary users of this service. Data is collected via a form that runs on an annual data collection window basis. Form changes are made annually to add new questions, remove any that are no longer needed, or adjust wording or answer options etc. Each data collection window runs from 1st April to 1st April + an extra 3 months to allow for any late submissions, meaning that between April and June, two collection windows are open simultaneously and logs can be submitted for either. - -ADD (Analytics & Data Directorate) statisticians are the other primary users of the service. The data collected is transferred to DLUHCs data warehouse (CDS - consolidated data store), via nightly exports to XML which are transferred to S3 and ingested from there. CDS ingests and transforms the data, ultimately storing it in a MS SQL database and exposing it to analysts and statisticians via Amazon Workspaces. diff --git a/docs/developer_setup.md b/docs/setup.md similarity index 97% rename from docs/developer_setup.md rename to docs/setup.md index d01ddb982..12bb4ea4c 100644 --- a/docs/developer_setup.md +++ b/docs/setup.md @@ -1,4 +1,8 @@ -# Developing locally on host machine +--- +nav_order: 2 +--- + +# Local development The most common way to run a development version of the application is run with local dependencies. @@ -8,7 +12,7 @@ Dependencies: - [Rails](https://rubyonrails.org/) - [PostgreSQL](https://www.postgresql.org/) - [NodeJS](https://nodejs.org/en/) -- [Gecko driver](https://github.com/mozilla/geckodriver/releases) [for running Selenium tests] +- [Gecko driver](https://github.com/mozilla/geckodriver/releases) (for running Selenium tests) We recommend using [RBenv](https://github.com/rbenv/rbenv) to manage Ruby versions. @@ -34,7 +38,7 @@ We recommend using [RBenv](https://github.com/rbenv/rbenv) to manage Ruby versio sudo su - postgres -c "createuser -s -P" ``` -3. Install RBenv & Ruby-build +3. Install RBenv and Ruby-build macOS: diff --git a/docs/testing.md b/docs/testing.md index 8797a7673..a453c69ca 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -1,4 +1,8 @@ -# Testing strategy +--- +nav_order: 4 +--- + +# Testing - We use [RSpec](https://rspec.info/) and [Capybara](https://teamcapybara.github.io/capybara/) diff --git a/docs/users.md b/docs/users.md deleted file mode 100644 index a7816eb75..000000000 --- a/docs/users.md +++ /dev/null @@ -1,15 +0,0 @@ -# User roles - -## External users - -The primary users of the system are external data providing organisations: Local Authorities and Private Registered Providers (Housing Associations). These have 2 main user types: - -- Data coordinators – administrators for their own organisation, can also complete logs -- Data providers – complete the logs - -Additionally there are Data Protection Officers (DPO), which for some organisations is a separate role, but in our codebase is modelled as an attribute of the user (i.e. a data coordinator or provider can additionally be a DPO). They are responsible for ensuring the organisation has signed the data sharing agreement. - -## Internal users - -- Customer support (help desk) – can administrate all organisations -- ADD statisticians – primary consumers of the data collected via CDS/DAP From da2c8dd50aa83c97f0e428e9e82c1bfc6e3bfd51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Meny?= Date: Tue, 12 Jul 2022 11:31:44 +0100 Subject: [PATCH 06/17] CLDC-1228 Scheme and Location imports (#727) --- app/controllers/locations_controller.rb | 2 +- app/models/location.rb | 4 +- app/models/scheme.rb | 8 +- app/services/imports/scheme_import_service.rb | 60 ++++++ .../imports/scheme_location_import_service.rb | 173 +++++++++++++++++ app/views/locations/edit.html.erb | 2 +- app/views/locations/index.html.erb | 2 +- app/views/locations/new.html.erb | 2 +- app/views/schemes/check_answers.html.erb | 2 +- ...0708112603_add_missing_fields_to_scheme.rb | 9 + ...08133052_add_missing_fields_to_location.rb | 8 + ...20711081400_rename_units_from_locations.rb | 6 + ...0711152629_add_unique_index_for_old_ids.rb | 5 + db/schema.rb | 15 +- lib/tasks/data_import.rake | 6 +- spec/factories/location.rb | 2 +- spec/factories/scheme.rb | 2 +- .../00d2343e-d5fa-4c89-8400-ec3854b0f2b4.xml | 0 .../0ead17cb-1668-442d-898c-0d52879ff592.xml | 0 .../166fc004-392e-47a8-acb8-1c018734882b.xml | 0 .../5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd.xml | 0 .../893ufj2s-lq77-42m4-rty6-ej09gh585uy1.xml | 0 ...5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml | 0 ...d22326d33e389e9f1bfd546979d2c05f9e68d6.xml | 0 ...5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml | 0 ...e7ad6dc0f1cf7ef33c18cc8c108bebc1b4923e.xml | 25 +++ ...b3836b70b4dd9903263d5a764a5c45b964a89d.xml | 23 +++ ...6d7618b58affe2a150a5ef2e9f4765fa6cd05d.xml | 9 + ...c887710550844e2551b3e0fb88dc9b4a8a642b.xml | 0 ...d81a262215a1634f0809effa683e38924d8bcb.xml | 0 ...829b1a5dfb68bb1e01c08445830c0add40907c.xml | 0 ...729b1a5dfb68bb1e01c08445830c0add40907c.xml | 0 ...717836154cd9a58f9e2f1d3077e3ab81e07613.xml | 0 ...7625a02b24ae16162aa63ae7cb33feeec0c373.xml | 0 spec/lib/tasks/data_import_spec.rb | 46 ++++- spec/lib/tasks/date_import_field_spec.rb | 2 +- spec/requests/locations_controller_spec.rb | 84 ++++---- .../case_logs_field_import_service_spec.rb | 2 +- .../imports/case_logs_import_service_spec.rb | 2 +- ...ection_confirmation_import_service_spec.rb | 2 +- .../organisation_import_service_spec.rb | 2 +- ...isation_rent_period_import_service_spec.rb | 2 +- .../imports/scheme_import_service_spec.rb | 64 +++++++ .../scheme_location_import_service_spec.rb | 180 ++++++++++++++++++ .../imports/user_import_service_spec.rb | 2 +- 45 files changed, 680 insertions(+), 73 deletions(-) create mode 100644 app/services/imports/scheme_import_service.rb create mode 100644 app/services/imports/scheme_location_import_service.rb create mode 100644 db/migrate/20220708112603_add_missing_fields_to_scheme.rb create mode 100644 db/migrate/20220708133052_add_missing_fields_to_location.rb create mode 100644 db/migrate/20220711081400_rename_units_from_locations.rb create mode 100644 db/migrate/20220711152629_add_unique_index_for_old_ids.rb rename spec/fixtures/{softwire_imports => imports}/case_logs/00d2343e-d5fa-4c89-8400-ec3854b0f2b4.xml (100%) rename spec/fixtures/{softwire_imports => imports}/case_logs/0ead17cb-1668-442d-898c-0d52879ff592.xml (100%) rename spec/fixtures/{softwire_imports => imports}/case_logs/166fc004-392e-47a8-acb8-1c018734882b.xml (100%) rename spec/fixtures/{softwire_imports => imports}/case_logs/5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd.xml (100%) rename spec/fixtures/{softwire_imports => imports}/case_logs/893ufj2s-lq77-42m4-rty6-ej09gh585uy1.xml (100%) rename spec/fixtures/{softwire_imports => imports}/data_protection_confirmations/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml (100%) rename spec/fixtures/{softwire_imports => imports}/organisation_rent_periods/ebd22326d33e389e9f1bfd546979d2c05f9e68d6.xml (100%) rename spec/fixtures/{softwire_imports => imports}/organisations/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml (100%) create mode 100644 spec/fixtures/imports/scheme_locations/0ae7ad6dc0f1cf7ef33c18cc8c108bebc1b4923e.xml create mode 100644 spec/fixtures/imports/scheme_locations/0bb3836b70b4dd9903263d5a764a5c45b964a89d.xml create mode 100644 spec/fixtures/imports/schemes/6d6d7618b58affe2a150a5ef2e9f4765fa6cd05d.xml rename spec/fixtures/{softwire_imports => imports}/users/10c887710550844e2551b3e0fb88dc9b4a8a642b.xml (100%) rename spec/fixtures/{softwire_imports => imports}/users/9ed81a262215a1634f0809effa683e38924d8bcb.xml (100%) rename spec/fixtures/{softwire_imports => imports}/users/b7829b1a5dfb68bb1e01c08445830c0add40907c.xml (100%) rename spec/fixtures/{softwire_imports => imports}/users/d4729b1a5dfb68bb1e01c08445830c0add40907c.xml (100%) rename spec/fixtures/{softwire_imports => imports}/users/d6717836154cd9a58f9e2f1d3077e3ab81e07613.xml (100%) rename spec/fixtures/{softwire_imports => imports}/users/fc7625a02b24ae16162aa63ae7cb33feeec0c373.xml (100%) create mode 100644 spec/services/imports/scheme_import_service_spec.rb create mode 100644 spec/services/imports/scheme_location_import_service_spec.rb diff --git a/app/controllers/locations_controller.rb b/app/controllers/locations_controller.rb index e900a12ff..b482b81f0 100644 --- a/app/controllers/locations_controller.rb +++ b/app/controllers/locations_controller.rb @@ -69,7 +69,7 @@ private end def location_params - required_params = params.require(:location).permit(:postcode, :name, :total_units, :type_of_unit, :wheelchair_adaptation, :add_another_location).merge(scheme_id: @scheme.id) + required_params = params.require(:location).permit(:postcode, :name, :units, :type_of_unit, :wheelchair_adaptation, :add_another_location).merge(scheme_id: @scheme.id) required_params[:postcode] = PostcodeService.clean(required_params[:postcode]) if required_params[:postcode] required_params end diff --git a/app/models/location.rb b/app/models/location.rb index 6718c79a7..bb2453625 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -16,8 +16,8 @@ class Location < ApplicationRecord "Self-contained flat or bedsit with common facilities": 2, "Shared flat": 3, "Shared house or hostel": 4, - "Bungalow": 5, - "Self-contained house": 6, + "Bungalow": 6, + "Self-contained house": 7, }.freeze enum type_of_unit: TYPE_OF_UNIT diff --git a/app/models/scheme.rb b/app/models/scheme.rb index 13f090797..bafa7d395 100644 --- a/app/models/scheme.rb +++ b/app/models/scheme.rb @@ -17,10 +17,10 @@ class Scheme < ApplicationRecord enum sensitive: SENSITIVE, _suffix: true REGISTERED_UNDER_CARE_ACT = { - "No": 0, - "Yes – registered care home providing nursing care": 1, - "Yes – registered care home providing personal care": 2, - "Yes – part registered as a care home": 3, + "No": 1, + "Yes – registered care home providing nursing care": 4, + "Yes – registered care home providing personal care": 3, + "Yes – part registered as a care home": 2, }.freeze enum registered_under_care_act: REGISTERED_UNDER_CARE_ACT diff --git a/app/services/imports/scheme_import_service.rb b/app/services/imports/scheme_import_service.rb new file mode 100644 index 000000000..b68e7e7da --- /dev/null +++ b/app/services/imports/scheme_import_service.rb @@ -0,0 +1,60 @@ +module Imports + class SchemeImportService < ImportService + def create_schemes(folder) + import_from(folder, :create_scheme) + end + + def create_scheme(xml_document) + old_id = string_or_nil(xml_document, "id") + status = string_or_nil(xml_document, "status") + + if status == "Approved" + Scheme.create!( + owning_organisation_id: find_owning_organisation_id(xml_document), + managing_organisation_id: find_managing_organisation_id(xml_document), + service_name: string_or_nil(xml_document, "name"), + arrangement_type: string_or_nil(xml_document, "arrangement_type"), + old_id:, + old_visible_id: safe_string_as_integer(xml_document, "visible-id"), + ) + else + @logger.warn("Scheme with legacy ID #{old_id} is not approved (#{status}), skipping") + end + end + + private + + def scheme_field_value(xml_document, field) + field_value(xml_document, "mgmtgroup", field) + end + + def string_or_nil(xml_doc, attribute) + str = scheme_field_value(xml_doc, attribute) + str.presence + end + + # Safe: A string that represents only an integer (or empty/nil) + def safe_string_as_integer(xml_doc, attribute) + str = scheme_field_value(xml_doc, attribute) + Integer(str, exception: false) + end + + def find_owning_organisation_id(xml_doc) + old_org_id = string_or_nil(xml_doc, "institution") + organisation = Organisation.find_by(old_org_id:) + raise "Organisation not found with legacy ID #{old_org_id}" if organisation.nil? + + organisation.id + end + + def find_managing_organisation_id(xml_doc) + old_visible_id = safe_string_as_integer(xml_doc, "agent") + return unless old_visible_id + + organisation = Organisation.find_by(old_visible_id:) + raise "Organisation not found with legacy visible ID #{old_visible_id}" if organisation.nil? + + organisation.id + end + end +end diff --git a/app/services/imports/scheme_location_import_service.rb b/app/services/imports/scheme_location_import_service.rb new file mode 100644 index 000000000..203df64c9 --- /dev/null +++ b/app/services/imports/scheme_location_import_service.rb @@ -0,0 +1,173 @@ +module Imports + class SchemeLocationImportService < ImportService + def create_scheme_locations(folder) + import_from(folder, :create_scheme_location) + end + + def create_scheme_location(xml_document) + management_group = location_field_value(xml_document, "mgmtgroup") + schemes = Scheme.where(old_id: management_group) + raise "Scheme not found with legacy ID #{management_group}" if schemes.empty? + + if schemes.size == 1 && schemes.first.locations&.empty? + scheme = update_scheme(schemes.first, xml_document) + else + scheme = find_scheme_to_merge(xml_document) + scheme ||= duplicate_scheme(schemes, xml_document) + end + add_location(scheme, xml_document) + end + + private + + REGISTERED_UNDER_CARE_ACT = { + 2 => "(Part-registered care home)", + 3 => "(Registered personal care home)", + 4 => "(Registered nursing care home)", + }.freeze + + def create_scheme(source_scheme, xml_doc) + attributes = scheme_attributes(xml_doc) + attributes["owning_organisation_id"] = source_scheme.owning_organisation_id + attributes["managing_organisation_id"] = source_scheme.managing_organisation_id + attributes["service_name"] = source_scheme.service_name + attributes["arrangement_type"] = source_scheme.arrangement_type + attributes["old_id"] = source_scheme.old_id + attributes["old_visible_id"] = source_scheme.old_visible_id + Scheme.create!(attributes) + end + + def update_scheme(scheme, xml_doc) + attributes = scheme_attributes(xml_doc) + scheme.update!(attributes) + scheme + end + + def scheme_attributes(xml_doc) + attributes = {} + attributes["scheme_type"] = safe_string_as_integer(xml_doc, "scheme-type") + registered_under_care_act = safe_string_as_integer(xml_doc, "reg-home-type") + attributes["registered_under_care_act"] = registered_under_care_act.zero? ? nil : registered_under_care_act + attributes["support_type"] = safe_string_as_integer(xml_doc, "support-type") + attributes["intended_stay"] = string_or_nil(xml_doc, "intended-stay") + attributes["primary_client_group"] = string_or_nil(xml_doc, "client-group-1") + attributes["secondary_client_group"] = string_or_nil(xml_doc, "client-group-2") + attributes["secondary_client_group"] = nil if attributes["primary_client_group"] == attributes["secondary_client_group"] + attributes["sensitive"] = sensitive(xml_doc) + attributes["end_date"] = parse_end_date(xml_doc) + attributes + end + + def add_location(scheme, xml_doc) + end_date = parse_end_date(xml_doc) + old_id = string_or_nil(xml_doc, "id") + + if end_date.nil? || end_date >= Time.zone.now + # wheelchair_adaptation: string_or_nil(xml_doc, "mobility-type"), + begin + Location.create!( + name: string_or_nil(xml_doc, "name"), + postcode: string_or_nil(xml_doc, "postcode"), + units: safe_string_as_integer(xml_doc, "total-units"), + type_of_unit: safe_string_as_integer(xml_doc, "unit-type"), + old_visible_id: safe_string_as_integer(xml_doc, "visible-id"), + old_id:, + scheme:, + ) + rescue ActiveRecord::RecordNotUnique + @logger.warn("Location is already present with legacy ID #{old_id}, skipping") + end + else + @logger.warn("Location with legacy ID #{old_id} is expired (#{end_date}), skipping") + end + end + + def find_scheme_to_merge(xml_doc) + attributes = scheme_attributes(xml_doc) + + Scheme.find_by( + scheme_type: attributes["scheme_type"], + registered_under_care_act: attributes["registered_under_care_act"], + support_type: attributes["support_type"], + intended_stay: attributes["intended_stay"], + primary_client_group: attributes["primary_client_group"], + secondary_client_group: attributes["secondary_client_group"], + ) + end + + def duplicate_scheme(schemes, xml_doc) + # Since all schemes in the array are different, pick the first one + # In the future, consider a better selection method if needed + old_scheme = schemes.first + new_scheme = create_scheme(old_scheme, xml_doc) + + if old_scheme.scheme_type != new_scheme.scheme_type + rename_schemes(old_scheme, new_scheme, :scheme_type) + elsif old_scheme.registered_under_care_act != new_scheme.registered_under_care_act + rename_registered_care(old_scheme, new_scheme) + elsif old_scheme.support_type != new_scheme.support_type + rename_schemes(old_scheme, new_scheme, :support_type) + elsif old_scheme.intended_stay != new_scheme.intended_stay + rename_schemes(old_scheme, new_scheme, :intended_stay) + elsif old_scheme.primary_client_group != new_scheme.primary_client_group + rename_schemes(old_scheme, new_scheme, :primary_client_group) + elsif old_scheme.secondary_client_group != new_scheme.secondary_client_group + rename_schemes(old_scheme, new_scheme, :secondary_client_group) + end + + new_scheme + end + + def rename_registered_care(*schemes) + schemes.each do |scheme| + if REGISTERED_UNDER_CARE_ACT.key?(scheme.registered_under_care_act_before_type_cast) + suffix = REGISTERED_UNDER_CARE_ACT[scheme.registered_under_care_act_before_type_cast] + scheme.update!(service_name: "#{scheme.service_name} - #{suffix}") + end + end + end + + def rename_schemes(old_scheme, new_scheme, attribute) + old_scheme_attribute = old_scheme.send(attribute) + new_scheme_attribute = new_scheme.send(attribute) + + if old_scheme_attribute + old_scheme_name = "#{old_scheme.service_name} - #{old_scheme_attribute}" + old_scheme.update!(service_name: old_scheme_name) + end + if new_scheme_attribute + new_scheme_name = "#{new_scheme.service_name} - #{new_scheme_attribute}" + new_scheme.update!(service_name: new_scheme_name) + end + end + + def location_field_value(xml_doc, field) + field_value(xml_doc, "scheme", field) + end + + def string_or_nil(xml_doc, attribute) + str = location_field_value(xml_doc, attribute) + str.presence + end + + # Safe: A string that represents only an integer (or empty/nil) + def safe_string_as_integer(xml_doc, attribute) + str = location_field_value(xml_doc, attribute) + Integer(str, exception: false) + end + + def sensitive(xml_doc) + value = string_or_nil(xml_doc, "sensitive") + if value == "true" + 1 + else + 0 + end + end + + def parse_end_date(xml_doc) + end_date = string_or_nil(xml_doc, "end-date") + Time.zone.parse(end_date) if end_date + end + end +end diff --git a/app/views/locations/edit.html.erb b/app/views/locations/edit.html.erb index 4d516b020..8e1da602c 100644 --- a/app/views/locations/edit.html.erb +++ b/app/views/locations/edit.html.erb @@ -23,7 +23,7 @@ label: { text: "Name (optional)", size: "m" }, hint: { text: "This is how you refer to this location within your organisation" } %> - <%= f.govuk_number_field :total_units, + <%= f.govuk_number_field :units, label: { text: "Total number of units at this location", size: "m" }, width: 2, hint: { text: "A unit can be a bedroom in a shared house or flat, or a house with 4 bedrooms. Do not include bedrooms used for wardens, managers, volunteers or sleep-in staff.s" }, diff --git a/app/views/locations/index.html.erb b/app/views/locations/index.html.erb index 3770c612a..2fa495d2a 100644 --- a/app/views/locations/index.html.erb +++ b/app/views/locations/index.html.erb @@ -37,7 +37,7 @@ <%= body.row do |row| %> <% row.cell(text: location.id) %> <% row.cell(text: simple_format(location_cell(location, "/schemes/#{@scheme.id}/locations/#{location.id}/edit-name"), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %> - <% row.cell(text: location.total_units) %> + <% row.cell(text: location.units) %> <% row.cell(text: simple_format("#{location.type_of_unit}#{location.wheelchair_adaptation == 'Yes' ? "\nWith wheelchair adaptations" : ''}")) %> <% end %> <% end %> diff --git a/app/views/locations/new.html.erb b/app/views/locations/new.html.erb index 19c2ae125..a599eacbe 100644 --- a/app/views/locations/new.html.erb +++ b/app/views/locations/new.html.erb @@ -23,7 +23,7 @@ label: { text: "Name (optional)", size: "m" }, hint: { text: "This is how you refer to this location within your organisation" } %> - <%= f.govuk_number_field :total_units, + <%= f.govuk_number_field :units, label: { text: "Total number of units at this location", size: "m" }, width: 2, hint: { text: "A unit can be a bedroom in a shared house or flat, or a house with 4 bedrooms. Do not include bedrooms used for wardens, managers, volunteers or sleep-in staff.s" }, diff --git a/app/views/schemes/check_answers.html.erb b/app/views/schemes/check_answers.html.erb index f211ba784..eb9fe5ef3 100644 --- a/app/views/schemes/check_answers.html.erb +++ b/app/views/schemes/check_answers.html.erb @@ -88,7 +88,7 @@ <%= body.row do |row| %> <% row.cell(text: location.id) %> <% row.cell(text: simple_format(location_cell(location, "/schemes/#{@scheme.id}/locations/#{location.id}/edit"), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %> - <% row.cell(text: location.total_units) %> + <% row.cell(text: location.units) %> <% row.cell(text: simple_format("#{location.type_of_unit}#{location.wheelchair_adaptation == 'Yes' ? "\nWith wheelchair adaptations" : ''}")) %> <% end %> <% end %> diff --git a/db/migrate/20220708112603_add_missing_fields_to_scheme.rb b/db/migrate/20220708112603_add_missing_fields_to_scheme.rb new file mode 100644 index 000000000..82669a5b2 --- /dev/null +++ b/db/migrate/20220708112603_add_missing_fields_to_scheme.rb @@ -0,0 +1,9 @@ +class AddMissingFieldsToScheme < ActiveRecord::Migration[7.0] + def change + change_table :schemes, bulk: true do |t| + t.column :arrangement_type, :string + t.column :old_id, :string + t.column :old_visible_id, :integer + end + end +end diff --git a/db/migrate/20220708133052_add_missing_fields_to_location.rb b/db/migrate/20220708133052_add_missing_fields_to_location.rb new file mode 100644 index 000000000..2d140c49e --- /dev/null +++ b/db/migrate/20220708133052_add_missing_fields_to_location.rb @@ -0,0 +1,8 @@ +class AddMissingFieldsToLocation < ActiveRecord::Migration[7.0] + def change + change_table :locations, bulk: true do |t| + t.column :old_id, :string + t.column :old_visible_id, :integer + end + end +end diff --git a/db/migrate/20220711081400_rename_units_from_locations.rb b/db/migrate/20220711081400_rename_units_from_locations.rb new file mode 100644 index 000000000..fef1c4132 --- /dev/null +++ b/db/migrate/20220711081400_rename_units_from_locations.rb @@ -0,0 +1,6 @@ +class RenameUnitsFromLocations < ActiveRecord::Migration[7.0] + def change + rename_column :locations, :total_units, :units + add_column :schemes, :total_units, :integer + end +end diff --git a/db/migrate/20220711152629_add_unique_index_for_old_ids.rb b/db/migrate/20220711152629_add_unique_index_for_old_ids.rb new file mode 100644 index 000000000..401ece0bd --- /dev/null +++ b/db/migrate/20220711152629_add_unique_index_for_old_ids.rb @@ -0,0 +1,5 @@ +class AddUniqueIndexForOldIds < ActiveRecord::Migration[7.0] + def change + add_index :locations, :old_id, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 10a390d90..3aa1c43ca 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: 2022_07_06_104313) do +ActiveRecord::Schema[7.0].define(version: 2022_07_11_152629) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -191,9 +191,9 @@ ActiveRecord::Schema[7.0].define(version: 2022_07_06_104313) do t.integer "joint" t.bigint "created_by_id" t.integer "illness_type_0" - t.integer "retirement_value_check" t.integer "tshortfall_known" t.integer "sheltered" + t.integer "retirement_value_check" t.integer "pregnancy_value_check" t.integer "hhtype" t.integer "new_old" @@ -247,14 +247,17 @@ ActiveRecord::Schema[7.0].define(version: 2022_07_06_104313) do t.string "county" t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.integer "total_units" + t.integer "units" t.integer "type_of_unit" + t.string "old_id" + t.integer "old_visible_id" + t.index ["old_id"], name: "index_locations_on_old_id", unique: true t.index ["scheme_id"], name: "index_locations_on_scheme_id" end create_table "logs_exports", force: :cascade do |t| t.datetime "created_at", default: -> { "CURRENT_TIMESTAMP" } - t.datetime "started_at", null: false + t.datetime "started_at", precision: nil, 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 @@ -314,6 +317,10 @@ ActiveRecord::Schema[7.0].define(version: 2022_07_06_104313) do t.datetime "end_date" t.integer "has_other_client_group" t.bigint "managing_organisation_id" + t.string "arrangement_type" + t.string "old_id" + t.integer "old_visible_id" + t.integer "total_units" t.index ["managing_organisation_id"], name: "index_schemes_on_managing_organisation_id" t.index ["owning_organisation_id"], name: "index_schemes_on_owning_organisation_id" end diff --git a/lib/tasks/data_import.rake b/lib/tasks/data_import.rake index 6a64c1fd0..54ebe60d4 100644 --- a/lib/tasks/data_import.rake +++ b/lib/tasks/data_import.rake @@ -10,12 +10,14 @@ namespace :core do case type when "organisation" Imports::OrganisationImportService.new(storage_service).create_organisations(path) + when "scheme" + Imports::SchemeImportService.new(storage_service).create_schemes(path) + when "scheme-location" + Imports::SchemeLocationImportService.new(storage_service).create_scheme_locations(path) when "user" Imports::UserImportService.new(storage_service).create_users(path) when "data-protection-confirmation" Imports::DataProtectionConfirmationImportService.new(storage_service).create_data_protection_confirmations(path) - when "organisation-las" - Imports::OrganisationLaImportService.new(storage_service).create_organisation_las(path) when "organisation-rent-periods" Imports::OrganisationRentPeriodImportService.new(storage_service).create_organisation_rent_periods(path) when "case-logs" diff --git a/spec/factories/location.rb b/spec/factories/location.rb index efec1aa76..7aebbbcde 100644 --- a/spec/factories/location.rb +++ b/spec/factories/location.rb @@ -3,7 +3,7 @@ FactoryBot.define do location_code { Faker::Name.initials(number: 10) } postcode { Faker::Address.postcode.delete(" ") } name { Faker::Address.street_name } - type_of_unit { Faker::Number.within(range: 1..6) } + type_of_unit { [1, 2, 3, 4, 6, 7].sample } type_of_building { "Purpose built" } wheelchair_adaptation { 0 } county { Faker::Address.state } diff --git a/spec/factories/scheme.rb b/spec/factories/scheme.rb index 5317d9e1d..ecd83a930 100644 --- a/spec/factories/scheme.rb +++ b/spec/factories/scheme.rb @@ -2,7 +2,7 @@ FactoryBot.define do factory :scheme do service_name { Faker::Name.name } sensitive { Faker::Number.within(range: 0..1) } - registered_under_care_act { Faker::Number.within(range: 0..1) } + registered_under_care_act { 1 } support_type { Faker::Number.within(range: 0..6) } scheme_type { 0 } intended_stay { %w[M P S V X].sample } diff --git a/spec/fixtures/softwire_imports/case_logs/00d2343e-d5fa-4c89-8400-ec3854b0f2b4.xml b/spec/fixtures/imports/case_logs/00d2343e-d5fa-4c89-8400-ec3854b0f2b4.xml similarity index 100% rename from spec/fixtures/softwire_imports/case_logs/00d2343e-d5fa-4c89-8400-ec3854b0f2b4.xml rename to spec/fixtures/imports/case_logs/00d2343e-d5fa-4c89-8400-ec3854b0f2b4.xml diff --git a/spec/fixtures/softwire_imports/case_logs/0ead17cb-1668-442d-898c-0d52879ff592.xml b/spec/fixtures/imports/case_logs/0ead17cb-1668-442d-898c-0d52879ff592.xml similarity index 100% rename from spec/fixtures/softwire_imports/case_logs/0ead17cb-1668-442d-898c-0d52879ff592.xml rename to spec/fixtures/imports/case_logs/0ead17cb-1668-442d-898c-0d52879ff592.xml diff --git a/spec/fixtures/softwire_imports/case_logs/166fc004-392e-47a8-acb8-1c018734882b.xml b/spec/fixtures/imports/case_logs/166fc004-392e-47a8-acb8-1c018734882b.xml similarity index 100% rename from spec/fixtures/softwire_imports/case_logs/166fc004-392e-47a8-acb8-1c018734882b.xml rename to spec/fixtures/imports/case_logs/166fc004-392e-47a8-acb8-1c018734882b.xml diff --git a/spec/fixtures/softwire_imports/case_logs/5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd.xml b/spec/fixtures/imports/case_logs/5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd.xml similarity index 100% rename from spec/fixtures/softwire_imports/case_logs/5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd.xml rename to spec/fixtures/imports/case_logs/5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd.xml diff --git a/spec/fixtures/softwire_imports/case_logs/893ufj2s-lq77-42m4-rty6-ej09gh585uy1.xml b/spec/fixtures/imports/case_logs/893ufj2s-lq77-42m4-rty6-ej09gh585uy1.xml similarity index 100% rename from spec/fixtures/softwire_imports/case_logs/893ufj2s-lq77-42m4-rty6-ej09gh585uy1.xml rename to spec/fixtures/imports/case_logs/893ufj2s-lq77-42m4-rty6-ej09gh585uy1.xml diff --git a/spec/fixtures/softwire_imports/data_protection_confirmations/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml b/spec/fixtures/imports/data_protection_confirmations/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml similarity index 100% rename from spec/fixtures/softwire_imports/data_protection_confirmations/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml rename to spec/fixtures/imports/data_protection_confirmations/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml diff --git a/spec/fixtures/softwire_imports/organisation_rent_periods/ebd22326d33e389e9f1bfd546979d2c05f9e68d6.xml b/spec/fixtures/imports/organisation_rent_periods/ebd22326d33e389e9f1bfd546979d2c05f9e68d6.xml similarity index 100% rename from spec/fixtures/softwire_imports/organisation_rent_periods/ebd22326d33e389e9f1bfd546979d2c05f9e68d6.xml rename to spec/fixtures/imports/organisation_rent_periods/ebd22326d33e389e9f1bfd546979d2c05f9e68d6.xml diff --git a/spec/fixtures/softwire_imports/organisations/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml b/spec/fixtures/imports/organisations/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml similarity index 100% rename from spec/fixtures/softwire_imports/organisations/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml rename to spec/fixtures/imports/organisations/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml diff --git a/spec/fixtures/imports/scheme_locations/0ae7ad6dc0f1cf7ef33c18cc8c108bebc1b4923e.xml b/spec/fixtures/imports/scheme_locations/0ae7ad6dc0f1cf7ef33c18cc8c108bebc1b4923e.xml new file mode 100644 index 000000000..68eda865e --- /dev/null +++ b/spec/fixtures/imports/scheme_locations/0ae7ad6dc0f1cf7ef33c18cc8c108bebc1b4923e.xml @@ -0,0 +1,25 @@ + + 0ae7ad6dc0f1cf7ef33c18cc8c108bebc1b4923e + Location 1 + 134 + S44 6EJ + true + false + 5 + True + 7 + 6 + 1 + 2 + A + P + M + + 1900-01-01 + 2050-12-31 + 6d6d7618b58affe2a150a5ef2e9f4765fa6cd05d + Approved + 10 + 1 + 08:12.0 + diff --git a/spec/fixtures/imports/scheme_locations/0bb3836b70b4dd9903263d5a764a5c45b964a89d.xml b/spec/fixtures/imports/scheme_locations/0bb3836b70b4dd9903263d5a764a5c45b964a89d.xml new file mode 100644 index 000000000..d62b40b78 --- /dev/null +++ b/spec/fixtures/imports/scheme_locations/0bb3836b70b4dd9903263d5a764a5c45b964a89d.xml @@ -0,0 +1,23 @@ + + 0bb3836b70b4dd9903263d5a764a5c45b964a89d + Location 2 + 134 + NG19 8SW + false + false + 11 + 7 + 6 + 1 + 2 + W + False + P + M + + 2014-04-07 + + 6d6d7618b58affe2a150a5ef2e9f4765fa6cd05d + Approved + 001 + diff --git a/spec/fixtures/imports/schemes/6d6d7618b58affe2a150a5ef2e9f4765fa6cd05d.xml b/spec/fixtures/imports/schemes/6d6d7618b58affe2a150a5ef2e9f4765fa6cd05d.xml new file mode 100644 index 000000000..5a7b23b5b --- /dev/null +++ b/spec/fixtures/imports/schemes/6d6d7618b58affe2a150a5ef2e9f4765fa6cd05d.xml @@ -0,0 +1,9 @@ + + 6d6d7618b58affe2a150a5ef2e9f4765fa6cd05d + Management Group + O + 456 + 7c5bd5fb549c09z2c55d9cb90d7ba84927e64618 + Approved + 123 + diff --git a/spec/fixtures/softwire_imports/users/10c887710550844e2551b3e0fb88dc9b4a8a642b.xml b/spec/fixtures/imports/users/10c887710550844e2551b3e0fb88dc9b4a8a642b.xml similarity index 100% rename from spec/fixtures/softwire_imports/users/10c887710550844e2551b3e0fb88dc9b4a8a642b.xml rename to spec/fixtures/imports/users/10c887710550844e2551b3e0fb88dc9b4a8a642b.xml diff --git a/spec/fixtures/softwire_imports/users/9ed81a262215a1634f0809effa683e38924d8bcb.xml b/spec/fixtures/imports/users/9ed81a262215a1634f0809effa683e38924d8bcb.xml similarity index 100% rename from spec/fixtures/softwire_imports/users/9ed81a262215a1634f0809effa683e38924d8bcb.xml rename to spec/fixtures/imports/users/9ed81a262215a1634f0809effa683e38924d8bcb.xml diff --git a/spec/fixtures/softwire_imports/users/b7829b1a5dfb68bb1e01c08445830c0add40907c.xml b/spec/fixtures/imports/users/b7829b1a5dfb68bb1e01c08445830c0add40907c.xml similarity index 100% rename from spec/fixtures/softwire_imports/users/b7829b1a5dfb68bb1e01c08445830c0add40907c.xml rename to spec/fixtures/imports/users/b7829b1a5dfb68bb1e01c08445830c0add40907c.xml diff --git a/spec/fixtures/softwire_imports/users/d4729b1a5dfb68bb1e01c08445830c0add40907c.xml b/spec/fixtures/imports/users/d4729b1a5dfb68bb1e01c08445830c0add40907c.xml similarity index 100% rename from spec/fixtures/softwire_imports/users/d4729b1a5dfb68bb1e01c08445830c0add40907c.xml rename to spec/fixtures/imports/users/d4729b1a5dfb68bb1e01c08445830c0add40907c.xml diff --git a/spec/fixtures/softwire_imports/users/d6717836154cd9a58f9e2f1d3077e3ab81e07613.xml b/spec/fixtures/imports/users/d6717836154cd9a58f9e2f1d3077e3ab81e07613.xml similarity index 100% rename from spec/fixtures/softwire_imports/users/d6717836154cd9a58f9e2f1d3077e3ab81e07613.xml rename to spec/fixtures/imports/users/d6717836154cd9a58f9e2f1d3077e3ab81e07613.xml diff --git a/spec/fixtures/softwire_imports/users/fc7625a02b24ae16162aa63ae7cb33feeec0c373.xml b/spec/fixtures/imports/users/fc7625a02b24ae16162aa63ae7cb33feeec0c373.xml similarity index 100% rename from spec/fixtures/softwire_imports/users/fc7625a02b24ae16162aa63ae7cb33feeec0c373.xml rename to spec/fixtures/imports/users/fc7625a02b24ae16162aa63ae7cb33feeec0c373.xml diff --git a/spec/lib/tasks/data_import_spec.rb b/spec/lib/tasks/data_import_spec.rb index fb3794414..1d49e6c70 100644 --- a/spec/lib/tasks/data_import_spec.rb +++ b/spec/lib/tasks/data_import_spec.rb @@ -22,7 +22,7 @@ describe "rake core:data_import", type: :task do context "when importing organisation data" do let(:type) { "organisation" } let(:import_service) { instance_double(Imports::OrganisationImportService) } - let(:fixture_path) { "spec/fixtures/softwire_imports/organisations" } + let(:fixture_path) { "spec/fixtures/imports/organisations" } before do allow(Imports::OrganisationImportService).to receive(:new).and_return(import_service) @@ -40,7 +40,7 @@ describe "rake core:data_import", type: :task do context "when importing user data" do let(:type) { "user" } let(:import_service) { instance_double(Imports::UserImportService) } - let(:fixture_path) { "spec/fixtures/softwire_imports/users" } + let(:fixture_path) { "spec/fixtures/imports/users" } before do allow(Imports::UserImportService).to receive(:new).and_return(import_service) @@ -58,7 +58,7 @@ describe "rake core:data_import", type: :task do context "when importing data protection confirmation data" do let(:type) { "data-protection-confirmation" } let(:import_service) { instance_double(Imports::DataProtectionConfirmationImportService) } - let(:fixture_path) { "spec/fixtures/softwire_imports/data_protection_confirmations" } + let(:fixture_path) { "spec/fixtures/imports/data_protection_confirmations" } before do allow(Imports::DataProtectionConfirmationImportService).to receive(:new).and_return(import_service) @@ -76,7 +76,7 @@ describe "rake core:data_import", type: :task do context "when importing organisation rent period data" do let(:type) { "organisation-rent-periods" } let(:import_service) { instance_double(Imports::OrganisationRentPeriodImportService) } - let(:fixture_path) { "spec/fixtures/softwire_imports/organisation_rent_periods" } + let(:fixture_path) { "spec/fixtures/imports/organisation_rent_periods" } before do allow(Imports::OrganisationRentPeriodImportService).to receive(:new).and_return(import_service) @@ -94,7 +94,7 @@ describe "rake core:data_import", type: :task do context "when importing case logs" do let(:type) { "case-logs" } let(:import_service) { instance_double(Imports::CaseLogsImportService) } - let(:fixture_path) { "spec/fixtures/softwire_imports/case_logs" } + let(:fixture_path) { "spec/fixtures/imports/case_logs" } before do allow(Imports::CaseLogsImportService).to receive(:new).and_return(import_service) @@ -109,6 +109,42 @@ describe "rake core:data_import", type: :task do end end + context "when importing scheme data" do + let(:type) { "scheme" } + let(:import_service) { instance_double(Imports::SchemeImportService) } + let(:fixture_path) { "spec/fixtures/imports/schemes" } + + before do + allow(Imports::SchemeImportService).to receive(:new).and_return(import_service) + end + + it "creates a scheme from the given XML file" do + expect(StorageService).to receive(:new).with(paas_config_service, instance_name) + expect(Imports::SchemeImportService).to receive(:new).with(storage_service) + expect(import_service).to receive(:create_schemes).with(fixture_path) + + task.invoke(type, fixture_path) + end + end + + context "when importing scheme location data" do + let(:type) { "scheme-location" } + let(:import_service) { instance_double(Imports::SchemeLocationImportService) } + let(:fixture_path) { "spec/fixtures/imports/organisations" } + + before do + allow(Imports::SchemeLocationImportService).to receive(:new).and_return(import_service) + end + + it "creates a scheme location from the given XML file" do + expect(StorageService).to receive(:new).with(paas_config_service, instance_name) + expect(Imports::SchemeLocationImportService).to receive(:new).with(storage_service) + expect(import_service).to receive(:create_scheme_locations).with(fixture_path) + + task.invoke(type, fixture_path) + end + end + it "raises an exception if no parameters are provided" do expect { task.invoke }.to raise_error(/Usage/) end diff --git a/spec/lib/tasks/date_import_field_spec.rb b/spec/lib/tasks/date_import_field_spec.rb index 8a11d6631..a5a74b2e8 100644 --- a/spec/lib/tasks/date_import_field_spec.rb +++ b/spec/lib/tasks/date_import_field_spec.rb @@ -21,7 +21,7 @@ describe "rake core:data_import_field", type: :task do context "when importing a case log field" do let(:import_service) { instance_double(Imports::CaseLogsFieldImportService) } - let(:fixture_path) { "spec/fixtures/softwire_imports/case_logs" } + let(:fixture_path) { "spec/fixtures/imports/case_logs" } before do allow(Imports::CaseLogsFieldImportService).to receive(:new).and_return(import_service) diff --git a/spec/requests/locations_controller_spec.rb b/spec/requests/locations_controller_spec.rb index 8d1fce823..c67b4358a 100644 --- a/spec/requests/locations_controller_spec.rb +++ b/spec/requests/locations_controller_spec.rb @@ -90,7 +90,7 @@ RSpec.describe LocationsController, type: :request do context "when signed in as a data coordinator" do let(:user) { FactoryBot.create(:user, :data_coordinator) } let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) } - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ" } } } before do sign_in user @@ -108,13 +108,13 @@ RSpec.describe LocationsController, type: :request do expect(Location.last.scheme.owning_organisation_id).to eq(user.organisation_id) expect(Location.last.name).to eq("Test") expect(Location.last.postcode).to eq("ZZ11ZZ") - expect(Location.last.total_units).to eq(5) + expect(Location.last.units).to eq(5) expect(Location.last.type_of_unit).to eq("Bungalow") expect(Location.last.wheelchair_adaptation).to eq("No") end context "when postcode is submitted with lower case" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "zz1 1zz" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "zz1 1zz" } } } it "creates a new location for scheme with postcode " do expect(Location.last.postcode).to eq("ZZ11ZZ") @@ -123,7 +123,7 @@ RSpec.describe LocationsController, type: :request do context "when trying to add location to a scheme that belongs to another organisation" do let(:another_scheme) { FactoryBot.create(:scheme) } - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ" } } } it "displays the new page with an error message" do post "/schemes/#{another_scheme.id}/locations", params: params @@ -132,7 +132,7 @@ RSpec.describe LocationsController, type: :request do end context "when required postcode param is missing" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No" } } } it "displays the new page with an error message" do expect(response).to have_http_status(:unprocessable_entity) @@ -141,7 +141,7 @@ RSpec.describe LocationsController, type: :request do end context "when do you want to add another location is selected as yes" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "Yes", postcode: "ZZ1 1ZZ" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "Yes", postcode: "ZZ1 1ZZ" } } } it "creates a new location for scheme with valid params and redirects to correct page" do expect { post "/schemes/#{scheme.id}/locations", params: }.to change(Location, :count).by(1) @@ -153,14 +153,14 @@ RSpec.describe LocationsController, type: :request do it "creates a new location for scheme with valid params" do expect(Location.last.scheme.owning_organisation_id).to eq(user.organisation_id) expect(Location.last.name).to eq("Test") - expect(Location.last.total_units).to eq(5) + expect(Location.last.units).to eq(5) expect(Location.last.type_of_unit).to eq("Bungalow") expect(Location.last.wheelchair_adaptation).to eq("No") end end context "when do you want to add another location is selected as no" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ" } } } it "creates a new location for scheme with valid params and redirects to correct page" do expect { post "/schemes/#{scheme.id}/locations", params: }.to change(Location, :count).by(1) @@ -172,14 +172,14 @@ RSpec.describe LocationsController, type: :request do it "creates a new location for scheme with valid params" do expect(Location.last.scheme.owning_organisation_id).to eq(user.organisation_id) expect(Location.last.name).to eq("Test") - expect(Location.last.total_units).to eq(5) + expect(Location.last.units).to eq(5) expect(Location.last.type_of_unit).to eq("Bungalow") expect(Location.last.wheelchair_adaptation).to eq("No") end end context "when do you want to add another location is not selected" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", postcode: "ZZ1 1ZZ" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", postcode: "ZZ1 1ZZ" } } } it "creates a new location for scheme with valid params and redirects to correct page" do expect { post "/schemes/#{scheme.id}/locations", params: }.to change(Location, :count).by(1) @@ -191,7 +191,7 @@ RSpec.describe LocationsController, type: :request do it "creates a new location for scheme with valid params" do expect(Location.last.scheme.owning_organisation_id).to eq(user.organisation_id) expect(Location.last.name).to eq("Test") - expect(Location.last.total_units).to eq(5) + expect(Location.last.units).to eq(5) expect(Location.last.type_of_unit).to eq("Bungalow") expect(Location.last.wheelchair_adaptation).to eq("No") end @@ -201,7 +201,7 @@ RSpec.describe LocationsController, type: :request do context "when signed in as a support user" do let(:user) { FactoryBot.create(:user, :support) } let!(:scheme) { FactoryBot.create(:scheme) } - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ" } } } before do allow(user).to receive(:need_two_factor_authentication?).and_return(false) @@ -219,13 +219,13 @@ RSpec.describe LocationsController, type: :request do it "creates a new location for scheme with valid params" do expect(Location.last.name).to eq("Test") expect(Location.last.postcode).to eq("ZZ11ZZ") - expect(Location.last.total_units).to eq(5) + expect(Location.last.units).to eq(5) expect(Location.last.type_of_unit).to eq("Bungalow") expect(Location.last.wheelchair_adaptation).to eq("No") end context "when postcode is submitted with lower case" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "zz1 1zz" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "zz1 1zz" } } } it "creates a new location for scheme with postcode " do expect(Location.last.postcode).to eq("ZZ11ZZ") @@ -233,7 +233,7 @@ RSpec.describe LocationsController, type: :request do end context "when required postcode param is missing" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No" } } } it "displays the new page with an error message" do post "/schemes/#{scheme.id}/locations", params: params @@ -243,7 +243,7 @@ RSpec.describe LocationsController, type: :request do end context "when do you want to add another location is selected as yes" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "Yes", postcode: "ZZ1 1ZZ" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "Yes", postcode: "ZZ1 1ZZ" } } } it "creates a new location for scheme with valid params and redirects to correct page" do expect { post "/schemes/#{scheme.id}/locations", params: }.to change(Location, :count).by(1) @@ -254,14 +254,14 @@ RSpec.describe LocationsController, type: :request do it "creates a new location for scheme with valid params" do expect(Location.last.name).to eq("Test") - expect(Location.last.total_units).to eq(5) + expect(Location.last.units).to eq(5) expect(Location.last.type_of_unit).to eq("Bungalow") expect(Location.last.wheelchair_adaptation).to eq("No") end end context "when do you want to add another location is selected as no" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ" } } } it "creates a new location for scheme with valid params and redirects to correct page" do expect { post "/schemes/#{scheme.id}/locations", params: }.to change(Location, :count).by(1) @@ -272,14 +272,14 @@ RSpec.describe LocationsController, type: :request do it "creates a new location for scheme with valid params" do expect(Location.last.name).to eq("Test") - expect(Location.last.total_units).to eq(5) + expect(Location.last.units).to eq(5) expect(Location.last.type_of_unit).to eq("Bungalow") expect(Location.last.wheelchair_adaptation).to eq("No") end end context "when do you want to add another location is not selected" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", postcode: "ZZ1 1ZZ" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", postcode: "ZZ1 1ZZ" } } } it "creates a new location for scheme with valid params and redirects to correct page" do expect { post "/schemes/#{scheme.id}/locations", params: }.to change(Location, :count).by(1) @@ -290,7 +290,7 @@ RSpec.describe LocationsController, type: :request do it "creates a new location for scheme with valid params" do expect(Location.last.name).to eq("Test") - expect(Location.last.total_units).to eq(5) + expect(Location.last.units).to eq(5) expect(Location.last.type_of_unit).to eq("Bungalow") expect(Location.last.wheelchair_adaptation).to eq("No") end @@ -390,7 +390,7 @@ RSpec.describe LocationsController, type: :request do let(:user) { FactoryBot.create(:user, :data_coordinator) } let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) } let!(:location) { FactoryBot.create(:location, scheme:) } - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", page: "edit" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", page: "edit" } } } before do sign_in user @@ -407,7 +407,7 @@ RSpec.describe LocationsController, type: :request do expect(Location.last.scheme.owning_organisation_id).to eq(user.organisation_id) expect(Location.last.name).to eq("Test") expect(Location.last.postcode).to eq("ZZ11ZZ") - expect(Location.last.total_units).to eq(5) + expect(Location.last.units).to eq(5) expect(Location.last.type_of_unit).to eq("Bungalow") expect(Location.last.wheelchair_adaptation).to eq("No") end @@ -427,7 +427,7 @@ RSpec.describe LocationsController, type: :request do end context "when postcode is submitted with lower case" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "zz1 1zz", page: "edit" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "zz1 1zz", page: "edit" } } } it "updates existing location for scheme with postcode " do expect(Location.last.postcode).to eq("ZZ11ZZ") @@ -437,7 +437,7 @@ RSpec.describe LocationsController, type: :request do context "when trying to update location for a scheme that belongs to another organisation" do let(:another_scheme) { FactoryBot.create(:scheme) } let(:another_location) { FactoryBot.create(:location) } - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", page: "edit" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", page: "edit" } } } it "displays the new page with an error message" do patch "/schemes/#{another_scheme.id}/locations/#{another_location.id}", params: params @@ -446,7 +446,7 @@ RSpec.describe LocationsController, type: :request do end context "when required postcode param is invalid" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "invalid", page: "edit" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "invalid", page: "edit" } } } it "displays the new page with an error message" do expect(response).to have_http_status(:unprocessable_entity) @@ -455,7 +455,7 @@ RSpec.describe LocationsController, type: :request do end context "when do you want to add another location is selected as yes" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "Yes", postcode: "ZZ1 1ZZ", page: "edit" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "Yes", postcode: "ZZ1 1ZZ", page: "edit" } } } it "updates existing location for scheme with valid params and redirects to correct page" do follow_redirect! @@ -466,14 +466,14 @@ RSpec.describe LocationsController, type: :request do it "updates existing location for scheme with valid params" do expect(Location.last.scheme.owning_organisation_id).to eq(user.organisation_id) expect(Location.last.name).to eq("Test") - expect(Location.last.total_units).to eq(5) + expect(Location.last.units).to eq(5) expect(Location.last.type_of_unit).to eq("Bungalow") expect(Location.last.wheelchair_adaptation).to eq("No") end end context "when do you want to add another location is selected as no" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", page: "edit" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", page: "edit" } } } it "updates existing location for scheme with valid params and redirects to correct page" do follow_redirect! @@ -484,14 +484,14 @@ RSpec.describe LocationsController, type: :request do it "updates existing location for scheme with valid params" do expect(Location.last.scheme.owning_organisation_id).to eq(user.organisation_id) expect(Location.last.name).to eq("Test") - expect(Location.last.total_units).to eq(5) + expect(Location.last.units).to eq(5) expect(Location.last.type_of_unit).to eq("Bungalow") expect(Location.last.wheelchair_adaptation).to eq("No") end end context "when do you want to add another location is not selected" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", postcode: "ZZ1 1ZZ", page: "edit" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", postcode: "ZZ1 1ZZ", page: "edit" } } } it "updates existing location for scheme with valid params and redirects to correct page" do follow_redirect! @@ -502,7 +502,7 @@ RSpec.describe LocationsController, type: :request do it "updates existing location for scheme with valid params" do expect(Location.last.scheme.owning_organisation_id).to eq(user.organisation_id) expect(Location.last.name).to eq("Test") - expect(Location.last.total_units).to eq(5) + expect(Location.last.units).to eq(5) expect(Location.last.type_of_unit).to eq("Bungalow") expect(Location.last.wheelchair_adaptation).to eq("No") end @@ -513,7 +513,7 @@ RSpec.describe LocationsController, type: :request do let(:user) { FactoryBot.create(:user, :data_coordinator) } let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) } let!(:location) { FactoryBot.create(:location, scheme:) } - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", page: "edit" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", page: "edit" } } } before do allow(user).to receive(:need_two_factor_authentication?).and_return(false) @@ -530,7 +530,7 @@ RSpec.describe LocationsController, type: :request do it "updates existing location for scheme with valid params" do expect(Location.last.name).to eq("Test") expect(Location.last.postcode).to eq("ZZ11ZZ") - expect(Location.last.total_units).to eq(5) + expect(Location.last.units).to eq(5) expect(Location.last.type_of_unit).to eq("Bungalow") expect(Location.last.wheelchair_adaptation).to eq("No") end @@ -550,7 +550,7 @@ RSpec.describe LocationsController, type: :request do end context "when postcode is submitted with lower case" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "zz1 1zz", page: "edit" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "zz1 1zz", page: "edit" } } } it "updates a location for scheme with postcode " do expect(Location.last.postcode).to eq("ZZ11ZZ") @@ -558,7 +558,7 @@ RSpec.describe LocationsController, type: :request do end context "when required postcode param is missing" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "invalid", page: "edit" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "invalid", page: "edit" } } } it "displays the new page with an error message" do expect(response).to have_http_status(:unprocessable_entity) @@ -567,7 +567,7 @@ RSpec.describe LocationsController, type: :request do end context "when do you want to add another location is selected as yes" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "Yes", postcode: "ZZ1 1ZZ", page: "edit" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "Yes", postcode: "ZZ1 1ZZ", page: "edit" } } } it "updates location for scheme with valid params and redirects to correct page" do follow_redirect! @@ -577,14 +577,14 @@ RSpec.describe LocationsController, type: :request do it "updates existing location for scheme with valid params" do expect(Location.last.name).to eq("Test") - expect(Location.last.total_units).to eq(5) + expect(Location.last.units).to eq(5) expect(Location.last.type_of_unit).to eq("Bungalow") expect(Location.last.wheelchair_adaptation).to eq("No") end end context "when do you want to add another location is selected as no" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", page: "edit" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", page: "edit" } } } it "updates a location for scheme with valid params and redirects to correct page" do follow_redirect! @@ -594,14 +594,14 @@ RSpec.describe LocationsController, type: :request do it "updates existing location for scheme with valid params" do expect(Location.last.name).to eq("Test") - expect(Location.last.total_units).to eq(5) + expect(Location.last.units).to eq(5) expect(Location.last.type_of_unit).to eq("Bungalow") expect(Location.last.wheelchair_adaptation).to eq("No") end end context "when do you want to add another location is not selected" do - let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", postcode: "ZZ1 1ZZ", page: "edit" } } } + let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", postcode: "ZZ1 1ZZ", page: "edit" } } } it "updates a location for scheme with valid params and redirects to correct page" do follow_redirect! @@ -611,7 +611,7 @@ RSpec.describe LocationsController, type: :request do it "updates a location for scheme with valid params" do expect(Location.last.name).to eq("Test") - expect(Location.last.total_units).to eq(5) + expect(Location.last.units).to eq(5) expect(Location.last.type_of_unit).to eq("Bungalow") expect(Location.last.wheelchair_adaptation).to eq("No") end diff --git a/spec/services/imports/case_logs_field_import_service_spec.rb b/spec/services/imports/case_logs_field_import_service_spec.rb index 4b1084a25..22c1371b0 100644 --- a/spec/services/imports/case_logs_field_import_service_spec.rb +++ b/spec/services/imports/case_logs_field_import_service_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Imports::CaseLogsFieldImportService do let(:logger) { instance_double(ActiveSupport::Logger) } let(:real_2021_2022_form) { Form.new("config/forms/2021_2022.json", "2021_2022") } - let(:fixture_directory) { "spec/fixtures/softwire_imports/case_logs" } + let(:fixture_directory) { "spec/fixtures/imports/case_logs" } let(:case_log_id) { "0ead17cb-1668-442d-898c-0d52879ff592" } let(:case_log_file) { open_file(fixture_directory, case_log_id) } diff --git a/spec/services/imports/case_logs_import_service_spec.rb b/spec/services/imports/case_logs_import_service_spec.rb index 9d86772f6..e44d15f31 100644 --- a/spec/services/imports/case_logs_import_service_spec.rb +++ b/spec/services/imports/case_logs_import_service_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Imports::CaseLogsImportService do let(:real_2021_2022_form) { Form.new("config/forms/2021_2022.json", "2021_2022") } let(:real_2022_2023_form) { Form.new("config/forms/2022_2023.json", "2022_2023") } - let(:fixture_directory) { "spec/fixtures/softwire_imports/case_logs" } + let(:fixture_directory) { "spec/fixtures/imports/case_logs" } let(:organisation) { FactoryBot.create(:organisation, old_visible_id: "1", provider_type: "PRP") } def open_file(directory, filename) diff --git a/spec/services/imports/data_protection_confirmation_import_service_spec.rb b/spec/services/imports/data_protection_confirmation_import_service_spec.rb index 12f260618..e14ce6b1a 100644 --- a/spec/services/imports/data_protection_confirmation_import_service_spec.rb +++ b/spec/services/imports/data_protection_confirmation_import_service_spec.rb @@ -1,7 +1,7 @@ require "rails_helper" RSpec.describe Imports::DataProtectionConfirmationImportService do - let(:fixture_directory) { "spec/fixtures/softwire_imports/data_protection_confirmations" } + let(:fixture_directory) { "spec/fixtures/imports/data_protection_confirmations" } let(:old_org_id) { "7c5bd5fb549c09a2c55d7cb90d7ba84927e64618" } let(:old_id) { old_org_id } let(:import_file) { File.open("#{fixture_directory}/#{old_id}.xml") } diff --git a/spec/services/imports/organisation_import_service_spec.rb b/spec/services/imports/organisation_import_service_spec.rb index c2f23e199..b91b566ef 100644 --- a/spec/services/imports/organisation_import_service_spec.rb +++ b/spec/services/imports/organisation_import_service_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Imports::OrganisationImportService do let(:logger) { instance_double(Rails::Rack::Logger) } let(:folder_name) { "organisations" } let(:filenames) { %w[my_folder/my_file1.xml my_folder/my_file2.xml] } - let(:fixture_directory) { "spec/fixtures/softwire_imports/organisations" } + let(:fixture_directory) { "spec/fixtures/imports/organisations" } def create_organisation_file(fixture_directory, visible_id, name = nil) file = File.open("#{fixture_directory}/7c5bd5fb549c09a2c55d7cb90d7ba84927e64618.xml") diff --git a/spec/services/imports/organisation_rent_period_import_service_spec.rb b/spec/services/imports/organisation_rent_period_import_service_spec.rb index 2921e5a4e..e15d10a22 100644 --- a/spec/services/imports/organisation_rent_period_import_service_spec.rb +++ b/spec/services/imports/organisation_rent_period_import_service_spec.rb @@ -1,7 +1,7 @@ require "rails_helper" RSpec.describe Imports::OrganisationRentPeriodImportService do - let(:fixture_directory) { "spec/fixtures/softwire_imports/organisation_rent_periods" } + let(:fixture_directory) { "spec/fixtures/imports/organisation_rent_periods" } let(:old_org_id) { "44026acc7ed5c29516b26f2a5deb639e5e37966d" } let(:old_id) { "ebd22326d33e389e9f1bfd546979d2c05f9e68d6" } let(:import_file) { File.open("#{fixture_directory}/#{old_id}.xml") } diff --git a/spec/services/imports/scheme_import_service_spec.rb b/spec/services/imports/scheme_import_service_spec.rb new file mode 100644 index 000000000..3cfb76c96 --- /dev/null +++ b/spec/services/imports/scheme_import_service_spec.rb @@ -0,0 +1,64 @@ +require "rails_helper" + +RSpec.describe Imports::SchemeImportService do + subject(:scheme_service) { described_class.new(storage_service, logger) } + + let(:storage_service) { instance_double(StorageService) } + let(:logger) { instance_double(ActiveSupport::Logger) } + + let(:fixture_directory) { "spec/fixtures/imports/schemes" } + let(:scheme_id) { "6d6d7618b58affe2a150a5ef2e9f4765fa6cd05d" } + + let!(:owning_org) { FactoryBot.create(:organisation, old_org_id: "7c5bd5fb549c09z2c55d9cb90d7ba84927e64618") } + let!(:managing_org) { FactoryBot.create(:organisation, old_visible_id: 456) } + + def open_file(directory, filename) + File.open("#{directory}/#{filename}.xml") + end + + context "when importing schemes" do + let(:remote_folder) { "mgmtgroups" } + + before do + # Stub the S3 file listing and download + allow(storage_service).to receive(:list_files) + .and_return(%W[#{remote_folder}/#{scheme_id}.xml]) + allow(storage_service).to receive(:get_file_io) + .with("#{remote_folder}/#{scheme_id}.xml") + .and_return(open_file(fixture_directory, scheme_id)) + end + + it "successfully create all schemes" do + expect(logger).not_to receive(:error) + expect(logger).not_to receive(:warn) + expect(logger).not_to receive(:info) + expect { scheme_service.create_schemes(remote_folder) } + .to change(Scheme, :count).by(1) + end + end + + context "when importing a specific scheme" do + let(:scheme_file) { open_file(fixture_directory, scheme_id) } + let(:scheme_xml) { Nokogiri::XML(scheme_file) } + + it "matches expected values" do + scheme = scheme_service.create_scheme(scheme_xml) + expect(scheme.owning_organisation).to eq(owning_org) + expect(scheme.managing_organisation).to eq(managing_org) + expect(scheme.old_id).to eq("6d6d7618b58affe2a150a5ef2e9f4765fa6cd05d") + expect(scheme.old_visible_id).to eq(123) + expect(scheme.service_name).to eq("Management Group") + expect(scheme.arrangement_type).to eq("O") + end + + context "and the scheme status is not approved" do + before { scheme_xml.at_xpath("//mgmtgroup:status").content = "Temporary" } + + it "does not create the scheme" do + expect(logger).to receive(:warn).with("Scheme with legacy ID 6d6d7618b58affe2a150a5ef2e9f4765fa6cd05d is not approved (Temporary), skipping") + expect { scheme_service.create_scheme(scheme_xml) } + .not_to change(Scheme, :count) + end + end + end +end diff --git a/spec/services/imports/scheme_location_import_service_spec.rb b/spec/services/imports/scheme_location_import_service_spec.rb new file mode 100644 index 000000000..f87fc3724 --- /dev/null +++ b/spec/services/imports/scheme_location_import_service_spec.rb @@ -0,0 +1,180 @@ +require "rails_helper" + +RSpec.describe Imports::SchemeLocationImportService do + subject(:location_service) { described_class.new(storage_service, logger) } + + let(:storage_service) { instance_double(StorageService) } + let(:logger) { instance_double(ActiveSupport::Logger) } + + let(:fixture_directory) { "spec/fixtures/imports/scheme_locations" } + let(:first_location_id) { "0ae7ad6dc0f1cf7ef33c18cc8c108bebc1b4923e" } + let(:second_location_id) { "0bb3836b70b4dd9903263d5a764a5c45b964a89d" } + + let!(:scheme) { FactoryBot.create(:scheme, service_name: "Management Group", old_id: "6d6d7618b58affe2a150a5ef2e9f4765fa6cd05d") } + + def open_file(directory, filename) + File.open("#{directory}/#{filename}.xml") + end + + context "when importing scheme locations" do + let(:remote_folder) { "schemes" } + + before do + # Stub the S3 file listing and download + allow(storage_service).to receive(:list_files) + .and_return(%W[#{remote_folder}/#{first_location_id}.xml #{remote_folder}/#{second_location_id}.xml]) + allow(storage_service).to receive(:get_file_io) + .with("#{remote_folder}/#{first_location_id}.xml") + .and_return(open_file(fixture_directory, first_location_id)) + allow(storage_service).to receive(:get_file_io) + .with("#{remote_folder}/#{second_location_id}.xml") + .and_return(open_file(fixture_directory, second_location_id)) + end + + it "successfully create all scheme locations" do + expect(logger).not_to receive(:error) + expect(logger).not_to receive(:warn) + expect(logger).not_to receive(:info) + expect { location_service.create_scheme_locations(remote_folder) } + .to change(Location, :count).by(2) + .and(change(Scheme, :count).by(0)) + end + end + + context "when importing different scheme locations" do + let(:location_xml_1) { Nokogiri::XML(open_file(fixture_directory, first_location_id)) } + let(:location_xml_2) { Nokogiri::XML(open_file(fixture_directory, second_location_id)) } + + before { location_service.create_scheme_location(location_xml_1) } + + context "and the scheme type is different" do + before { location_xml_2.at_xpath("//scheme:scheme-type").content = "5" } + + it "renames the location scheme name" do + location = location_service.create_scheme_location(location_xml_2) + old_scheme = Scheme.find(scheme.id) + new_scheme = location.scheme + + expect(old_scheme.service_name).to eq("Management Group - Housing for older people") + expect(new_scheme.service_name).to eq("Management Group - Direct Access Hostel") + end + end + + context "and the registered under care act is different" do + before { location_xml_2.at_xpath("//scheme:reg-home-type").content = "2" } + + it "renames both scheme names" do + location = location_service.create_scheme_location(location_xml_2) + old_scheme = Scheme.find(scheme.id) + new_scheme = location.scheme + + expect(old_scheme.service_name).to eq("Management Group") + expect(new_scheme.service_name).to eq("Management Group - (Part-registered care home)") + end + end + + context "and the support type is different" do + before { location_xml_2.at_xpath("//scheme:support-type").content = "3" } + + it "renames both scheme names" do + location = location_service.create_scheme_location(location_xml_2) + old_scheme = Scheme.find(scheme.id) + new_scheme = location.scheme + + expect(old_scheme.service_name).to eq("Management Group - Low levels of support") + expect(new_scheme.service_name).to eq("Management Group - Medium levels of support") + end + end + + context "and the intended stay is different" do + before { location_xml_2.at_xpath("//scheme:intended-stay").content = "S" } + + it "renames both scheme names" do + location = location_service.create_scheme_location(location_xml_2) + old_scheme = Scheme.find(scheme.id) + new_scheme = location.scheme + + expect(old_scheme.service_name).to eq("Management Group - Permanent") + expect(new_scheme.service_name).to eq("Management Group - Short stay") + end + end + + context "and the primary client group is different" do + before { location_xml_2.at_xpath("//scheme:client-group-1").content = "F" } + + it "renames both scheme names" do + location = location_service.create_scheme_location(location_xml_2) + old_scheme = Scheme.find(scheme.id) + new_scheme = location.scheme + + expect(old_scheme.service_name).to eq("Management Group - Older people with support needs") + expect(new_scheme.service_name).to eq("Management Group - People with drug problems") + end + end + + context "and the secondary client group is different" do + before { location_xml_2.at_xpath("//scheme:client-group-2").content = "S" } + + it "renames both scheme names" do + location = location_service.create_scheme_location(location_xml_2) + old_scheme = Scheme.find(scheme.id) + new_scheme = location.scheme + + expect(old_scheme.service_name).to eq("Management Group") + expect(new_scheme.service_name).to eq("Management Group - Rough sleepers") + end + end + end + + context "when importing a specific scheme location" do + let(:location_xml) { Nokogiri::XML(open_file(fixture_directory, first_location_id)) } + + it "matches expected location values" do + location = location_service.create_scheme_location(location_xml) + expect(location.name).to eq("Location 1") + expect(location.postcode).to eq("S44 6EJ") + expect(location.units).to eq(5) + expect(location.type_of_unit).to eq("Bungalow") + expect(location.old_id).to eq(first_location_id) + expect(location.old_visible_id).to eq(10) + expect(location.scheme).to eq(scheme) + end + + it "matches expected schemes values" do + location = location_service.create_scheme_location(location_xml) + expect(location.scheme.scheme_type).to eq("Housing for older people") + expect(location.scheme.registered_under_care_act).to eq("No") + expect(location.scheme.support_type).to eq("Low levels of support") + expect(location.scheme.intended_stay).to eq("Permanent") + expect(location.scheme.primary_client_group).to eq("Older people with support needs") + expect(location.scheme.secondary_client_group).to be_nil + expect(location.scheme.sensitive).to eq("No") + expect(location.scheme.end_date).to eq("2050-12-31") + end + + context "and the end date is before the current date" do + before do + Timecop.freeze(2022, 6, 1) + location_xml.at_xpath("//scheme:end-date").content = "2022-05-01" + end + + after { Timecop.unfreeze } + + it "does not create the location" do + expect(logger).to receive(:warn).with("Location with legacy ID 0ae7ad6dc0f1cf7ef33c18cc8c108bebc1b4923e is expired (2022-05-01 00:00:00 +0100), skipping") + expect { location_service.create_scheme_location(location_xml) } + .not_to change(Location, :count) + end + end + + context "and we import the same location twice" do + before { location_service.create_scheme_location(location_xml) } + + it "does not create the location" do + expect(logger).to receive(:warn).with("Location is already present with legacy ID 0ae7ad6dc0f1cf7ef33c18cc8c108bebc1b4923e, skipping") + expect { location_service.create_scheme_location(location_xml) } + .not_to change(Location, :count) + end + end + end +end diff --git a/spec/services/imports/user_import_service_spec.rb b/spec/services/imports/user_import_service_spec.rb index 305613545..02a776ecc 100644 --- a/spec/services/imports/user_import_service_spec.rb +++ b/spec/services/imports/user_import_service_spec.rb @@ -1,7 +1,7 @@ require "rails_helper" RSpec.describe Imports::UserImportService do - let(:fixture_directory) { "spec/fixtures/softwire_imports/users" } + let(:fixture_directory) { "spec/fixtures/imports/users" } let(:old_user_id) { "fc7625a02b24ae16162aa63ae7cb33feeec0c373" } let(:old_org_id) { "7c5bd5fb549c09a2c55d7cb90d7ba84927e64618" } let(:user_file) { File.open("#{fixture_directory}/#{old_user_id}.xml") } From 4114e89769c273ae4be4d8926cf5b3fd72fe73aa Mon Sep 17 00:00:00 2001 From: Paul Robert Lloyd Date: Tue, 12 Jul 2022 11:34:24 +0100 Subject: [PATCH 07/17] Use absolute paths for images in tech docs (#731) --- docs/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/index.md b/docs/index.md index ecb2272d6..b30bd3737 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,7 +12,7 @@ Each data collection window runs from 1 April to 1 April the following year (plu ADD (Analytics & Data Directorate) statisticians are the other primary users of the service. The data collected is transferred to DLUHCs consolidated data store (CDS) via nightly XML exports to an S3 bucket. CDS ingests and transforms this data, ultimately storing it in a MS SQL database and exposing it to analysts and statisticians via Amazon Workspaces. -![Diagram of the CORE system architecture](../images/architecture.drawio.png) +![Diagram of the CORE system architecture](https://raw.githubusercontent.com/communitiesuk/submit-social-housing-lettings-and-sales-data/main/docs/images/architecture.drawio.png) ## Users @@ -42,7 +42,7 @@ Organisations that own stock can contract out the management of that stock to an This is a useful analogy as a parent can have multiple children, and a child can have many parents. A child organisation can also be a parent, and a parent organisation can also be a child organisation: -![Organisational relationships](../images/organisational_relationships.png) +![Organisational relationships](https://raw.githubusercontent.com/communitiesuk/submit-social-housing-lettings-and-sales-data/main/docs/images/organisational_relationships.png) ### User permissions within organisations @@ -56,7 +56,7 @@ The case logs that a user can see depends on their role: Taking the relationships from the above diagram, and looking at which logs each user can access: -![User log access permissions](../images/user_log_permissions.png) +![User log access permissions](https://raw.githubusercontent.com/communitiesuk/submit-social-housing-lettings-and-sales-data/main/docs/images/user_log_permissions.png) ## Supported housing schemes From 6b90107799bed987303dd196eebce8dcf8200fc6 Mon Sep 17 00:00:00 2001 From: Paul Robert Lloyd Date: Tue, 12 Jul 2022 11:40:19 +0100 Subject: [PATCH 08/17] Fix link to API browser on tech docs website (#732) --- docs/_config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_config.yml b/docs/_config.yml index 8feb16d75..786d0ab41 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -5,7 +5,7 @@ permalink: pretty color_scheme: govuk aux_links: "API browser": - - /api + - https://communitiesuk.github.io/submit-social-housing-lettings-and-sales-data/api "Design history": - https://core-design-history.herokuapp.com "GitHub": From 35ab87462636bd2d10c60b9c6745827c93a6c754 Mon Sep 17 00:00:00 2001 From: baarkerlounger <5101747+baarkerlounger@users.noreply.github.com> Date: Tue, 12 Jul 2022 13:51:16 +0100 Subject: [PATCH 09/17] Location name label (#734) * Location name label * Just page title --- Gemfile.lock | 6 ++-- app/views/locations/edit.html.erb | 2 +- app/views/locations/new.html.erb | 2 +- spec/features/schemes_spec.rb | 4 +-- yarn.lock | 58 +++++++++++++++---------------- 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index bc6d661d7..5e2ae17ee 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -82,7 +82,7 @@ GEM public_suffix (>= 2.0.2, < 5.0) ast (2.4.2) aws-eventstream (1.2.0) - aws-partitions (1.603.0) + aws-partitions (1.604.0) aws-sdk-core (3.131.2) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) @@ -168,7 +168,7 @@ GEM ffi (1.15.5) globalid (1.0.0) activesupport (>= 5.0) - govuk-components (3.1.3) + govuk-components (3.1.4) actionpack (>= 6.1) activemodel (>= 6.1) html-attributes-utils (~> 0.9, >= 0.9.2) @@ -187,7 +187,7 @@ GEM html-attributes-utils (0.9.2) activesupport (>= 6.1.4.4) html_tokenizer (0.0.7) - i18n (1.10.0) + i18n (1.11.0) concurrent-ruby (~> 1.0) iniparse (1.5.0) jmespath (1.6.1) diff --git a/app/views/locations/edit.html.erb b/app/views/locations/edit.html.erb index 8e1da602c..9227b3efb 100644 --- a/app/views/locations/edit.html.erb +++ b/app/views/locations/edit.html.erb @@ -20,7 +20,7 @@ width: 5 %> <%= f.govuk_text_field :name, - label: { text: "Name (optional)", size: "m" }, + label: { text: "Location name (optional)", size: "m" }, hint: { text: "This is how you refer to this location within your organisation" } %> <%= f.govuk_number_field :units, diff --git a/app/views/locations/new.html.erb b/app/views/locations/new.html.erb index a599eacbe..8d964d07c 100644 --- a/app/views/locations/new.html.erb +++ b/app/views/locations/new.html.erb @@ -20,7 +20,7 @@ width: 5 %> <%= f.govuk_text_field :name, - label: { text: "Name (optional)", size: "m" }, + label: { text: "Location name (optional)", size: "m" }, hint: { text: "This is how you refer to this location within your organisation" } %> <%= f.govuk_number_field :units, diff --git a/spec/features/schemes_spec.rb b/spec/features/schemes_spec.rb index 838b28801..cb5dd6770 100644 --- a/spec/features/schemes_spec.rb +++ b/spec/features/schemes_spec.rb @@ -400,7 +400,7 @@ RSpec.describe "Schemes scheme Features" do context "when I add location to the scheme" do before do fill_in "Postcode", with: "SW1P 4DF" - fill_in "Name (optional)", with: "Some name" + fill_in "Location name (optional)", with: "Some name" fill_in "Total number of units at this location", with: 1 choose "Self-contained house" choose "location-wheelchair-adaptation-no-field" @@ -433,7 +433,7 @@ RSpec.describe "Schemes scheme Features" do before do click_link "Add a location" fill_in "Postcode", with: "XX1 1XX" - fill_in "Name (optional)", with: "Other name" + fill_in "Location name (optional)", with: "Other name" fill_in "Total number of units at this location", with: 2 choose "Self-contained house" choose "location-wheelchair-adaptation-no-field" diff --git a/yarn.lock b/yarn.lock index a5df5932c..abefb081f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -979,9 +979,9 @@ to-fast-properties "^2.0.0" "@csstools/selector-specificity@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.1.tgz#b6b8d81780b9a9f6459f4bfe9226ac6aefaefe87" - integrity sha512-aG20vknL4/YjQF9BSV7ts4EWm/yrjagAN7OWBNmlbEOUiu0llj4OGrFoOKK3g2vey4/p2omKCoHrWtPxSwV3HA== + version "2.0.2" + resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36" + integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg== "@discoveryjs/json-ext@^0.5.0": version "0.5.7" @@ -1845,7 +1845,7 @@ browser-sync@^2.27.9: ua-parser-js "1.0.2" yargs "^17.3.1" -browserslist@^4.14.5, browserslist@^4.20.2, browserslist@^4.21.0: +browserslist@^4.14.5, browserslist@^4.20.2, browserslist@^4.21.1: version "4.21.1" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.1.tgz#c9b9b0a54c7607e8dc3e01a0d311727188011a00" integrity sha512-Nq8MFCSrnJXSc88yliwlzQe3qNe3VntIjhsArW9IJOEPSHNx23FalwApUVbzAWABLhYJJ7y8AynWI/XM8OdfjQ== @@ -1915,9 +1915,9 @@ camelcase@^5.3.1: integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== caniuse-lite@^1.0.30001359: - version "1.0.30001363" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001363.tgz#26bec2d606924ba318235944e1193304ea7c4f15" - integrity sha512-HpQhpzTGGPVMnCjIomjt+jvyUu8vNFo3TaDiZ/RcoTrlOq/5+tC8zHdsbgFB6MxmaY+jCpsH09aD80Bb4Ow3Sg== + version "1.0.30001365" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001365.tgz#72c2c3863b1a545cfd3d9953535bd2ee17568158" + integrity sha512-VDQZ8OtpuIPMBA4YYvZXECtXbddMCUFJk1qu8Mqxfm/SZJNSr1cy4IuLCOL7RJ/YASrvJcYg1Zh+UEUQ5m6z8Q== chalk@^1.1.3: version "1.1.3" @@ -2133,17 +2133,17 @@ copy-webpack-plugin@^10.2.4: serialize-javascript "^6.0.0" core-js-compat@^3.21.0, core-js-compat@^3.22.1: - version "3.23.3" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.23.3.tgz#7d8503185be76bb6d8d592c291a4457a8e440aa9" - integrity sha512-WSzUs2h2vvmKsacLHNTdpyOC9k43AEhcGoFlVgCY4L7aw98oSBKtPL6vD0/TqZjRWRQYdDSLkzZIni4Crbbiqw== + version "3.23.4" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.23.4.tgz#56ad4a352884317a15f6b04548ff7139d23b917f" + integrity sha512-RkSRPe+JYEoflcsuxJWaiMPhnZoFS51FcIxm53k4KzhISCBTmaGlto9dTIrYuk0hnJc3G6pKufAKepHnBq6B6Q== dependencies: - browserslist "^4.21.0" + browserslist "^4.21.1" semver "7.0.0" core-js@^3.21.1, core-js@^3.4.0: - version "3.23.3" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.23.3.tgz#3b977612b15da6da0c9cc4aec487e8d24f371112" - integrity sha512-oAKwkj9xcWNBAvGbT//WiCdOMpb9XQG92/Fe3ABFM/R16BsHgePG00mFOgKf7IsCtfj8tA1kHtf/VwErhriz5Q== + version "3.23.4" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.23.4.tgz#92d640faa7f48b90bbd5da239986602cfc402aa6" + integrity sha512-vjsKqRc1RyAJC3Ye2kYqgfdThb3zYnx9CrqoCcjMOENMtQPC7ZViBvlDxwYU/2z2NI/IPuiXw5mT4hWhddqjzQ== cors@~2.8.5: version "2.8.5" @@ -2384,9 +2384,9 @@ ejs@^3.1.6: jake "^10.8.5" electron-to-chromium@^1.4.172: - version "1.4.184" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.184.tgz#381d4d111fc82d3376ed690dfb621e675f9078a9" - integrity sha512-IADi390FRdvxWfVX3hjzfTDNVHiTqVo9ar53/7em/SfhUG9YcjVhyQecY/XwmBHRKden/wFud7RWOUH7+7LFng== + version "1.4.186" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.186.tgz#a811bba15f0868d3f4164b0f4ede8adc8773831b" + integrity sha512-YoVeFrGd/7ROjz4R9uPoND1K/hSRC/xADy9639ZmIZeJSaBnKdYx3I6LMPsY7CXLpK7JFgKQVzeZ/dk2br6Eaw== element-closest@^2.0.2: version "2.0.2" @@ -3822,9 +3822,9 @@ linkify-it@^3.0.1: uc.micro "^1.0.1" liquidjs@^9.36.1: - version "9.38.0" - resolved "https://registry.yarnpkg.com/liquidjs/-/liquidjs-9.38.0.tgz#0fb4d380007ecde00d8c43c5fd5535d6189b7778" - integrity sha512-LDGPbOfc8L9LG7ZSG0NueTcCe0AZzJAIa0BjYgMnCR+4VO0yS+AnOZjx0WOih8mtLRNSIYo9fik5exY6Cf4TOQ== + version "9.39.0" + resolved "https://registry.yarnpkg.com/liquidjs/-/liquidjs-9.39.0.tgz#0ee685118d397354556c35f558c3e5d3ecddc12c" + integrity sha512-WmsBwFlvhHrMtrlSjVR58kAYjZT/0QVO5sqw4zs9mpGQdf3xTon388WbWwOwyCoTtKzXwedbvWtq2M2wnozr0g== list-to-array@^1.1.0: version "1.1.0" @@ -3942,9 +3942,9 @@ lru-cache@^6.0.0: yallist "^4.0.0" luxon@^2.1.1, luxon@^2.3.2: - version "2.4.0" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-2.4.0.tgz#9435806545bb32d4234dab766ab8a3d54847a765" - integrity sha512-w+NAwWOUL5hO0SgwOHsMBAmZ15SoknmQXhSO0hIbJCAmPKSsGeK8MlmhYh2w6Iib38IxN2M+/ooXWLbeis7GuA== + version "2.5.0" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-2.5.0.tgz#098090f67d690b247e83c090267a60b1aa8ea96c" + integrity sha512-IDkEPB80Rb6gCAU+FEib0t4FeJ4uVOuX1CQ9GsvU3O+JAGIgu0J7sf1OarXKaKDygTZIoJyU6YdZzTFRu+YR0A== magic-string@^0.25.7: version "0.25.9" @@ -4225,9 +4225,9 @@ neo-async@^2.6.0, neo-async@^2.6.2: integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== node-releases@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" - integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== + version "2.0.6" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" + integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== nopt@^5.0.0: version "5.0.0" @@ -5571,9 +5571,9 @@ stylelint-config-standard@^25.0.0: stylelint-config-recommended "^7.0.0" stylelint-scss@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-4.2.0.tgz#e25fd390ee38a7e89fcfaec2a8f9dce2ec6ddee8" - integrity sha512-HHHMVKJJ5RM9pPIbgJ/XA67h9H0407G68Rm69H4fzFbFkyDMcTV1Byep3qdze5+fJ3c0U7mJrbj6S0Fg072uZA== + version "4.3.0" + resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-4.3.0.tgz#638800faf823db11fff60d537c81051fe74c90fa" + integrity sha512-GvSaKCA3tipzZHoz+nNO7S02ZqOsdBzMiCx9poSmLlb3tdJlGddEX/8QzCOD8O7GQan9bjsvLMsO5xiw6IhhIQ== dependencies: lodash "^4.17.21" postcss-media-query-parser "^0.2.3" From b3195bacda093fd78eb288566eb2924759106ade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Meny?= Date: Tue, 12 Jul 2022 14:44:57 +0100 Subject: [PATCH 10/17] CLDC-1228: Match schemes on management group ID (old_id) (#736) --- .../imports/scheme_location_import_service.rb | 94 +++++++++++-------- 1 file changed, 57 insertions(+), 37 deletions(-) diff --git a/app/services/imports/scheme_location_import_service.rb b/app/services/imports/scheme_location_import_service.rb index 203df64c9..f05381c3b 100644 --- a/app/services/imports/scheme_location_import_service.rb +++ b/app/services/imports/scheme_location_import_service.rb @@ -5,17 +5,17 @@ module Imports end def create_scheme_location(xml_document) - management_group = location_field_value(xml_document, "mgmtgroup") - schemes = Scheme.where(old_id: management_group) - raise "Scheme not found with legacy ID #{management_group}" if schemes.empty? + attributes = scheme_attributes(xml_document) + schemes = Scheme.where(old_id: attributes["scheme_old_id"]) + raise "Scheme not found with legacy ID #{attributes['scheme_old_id']}" if schemes.empty? if schemes.size == 1 && schemes.first.locations&.empty? - scheme = update_scheme(schemes.first, xml_document) + scheme = update_scheme(schemes.first, attributes) else - scheme = find_scheme_to_merge(xml_document) - scheme ||= duplicate_scheme(schemes, xml_document) + scheme = find_scheme_to_merge(attributes) + scheme ||= duplicate_scheme(schemes, attributes) end - add_location(scheme, xml_document) + add_location(scheme, attributes) end private @@ -26,20 +26,37 @@ module Imports 4 => "(Registered nursing care home)", }.freeze - def create_scheme(source_scheme, xml_doc) - attributes = scheme_attributes(xml_doc) - attributes["owning_organisation_id"] = source_scheme.owning_organisation_id - attributes["managing_organisation_id"] = source_scheme.managing_organisation_id - attributes["service_name"] = source_scheme.service_name - attributes["arrangement_type"] = source_scheme.arrangement_type - attributes["old_id"] = source_scheme.old_id - attributes["old_visible_id"] = source_scheme.old_visible_id - Scheme.create!(attributes) + def create_scheme(source_scheme, attributes) + Scheme.create!( + scheme_type: attributes["scheme_type"], + registered_under_care_act: attributes["registered_under_care_act"], + support_type: attributes["support_type"], + intended_stay: attributes["intended_stay"], + primary_client_group: attributes["primary_client_group"], + secondary_client_group: attributes["secondary_client_group"], + sensitive: attributes["sensitive"], + end_date: attributes["end_date"], + # These values were set by the scheme import (management groups) + owning_organisation_id: source_scheme.owning_organisation_id, + managing_organisation_id: source_scheme.managing_organisation_id, + service_name: source_scheme.service_name, + arrangement_type: source_scheme.arrangement_type, + old_id: source_scheme.old_id, + old_visible_id: source_scheme.old_visible_id, + ) end - def update_scheme(scheme, xml_doc) - attributes = scheme_attributes(xml_doc) - scheme.update!(attributes) + def update_scheme(scheme, attributes) + scheme.update!( + scheme_type: attributes["scheme_type"], + registered_under_care_act: attributes["registered_under_care_act"], + support_type: attributes["support_type"], + intended_stay: attributes["intended_stay"], + primary_client_group: attributes["primary_client_group"], + secondary_client_group: attributes["secondary_client_group"], + sensitive: attributes["sensitive"], + end_date: attributes["end_date"], + ) scheme end @@ -55,37 +72,40 @@ module Imports attributes["secondary_client_group"] = nil if attributes["primary_client_group"] == attributes["secondary_client_group"] attributes["sensitive"] = sensitive(xml_doc) attributes["end_date"] = parse_end_date(xml_doc) + attributes["location_name"] = string_or_nil(xml_doc, "name") + attributes["postcode"] = string_or_nil(xml_doc, "postcode") + attributes["units"] = safe_string_as_integer(xml_doc, "total-units") + attributes["type_of_unit"] = safe_string_as_integer(xml_doc, "unit-type") + attributes["location_old_id"] = string_or_nil(xml_doc, "id") + attributes["location_old_visible_id"] = safe_string_as_integer(xml_doc, "visible-id") + attributes["scheme_old_id"] = string_or_nil(xml_doc, "mgmtgroup") attributes end - def add_location(scheme, xml_doc) - end_date = parse_end_date(xml_doc) - old_id = string_or_nil(xml_doc, "id") - - if end_date.nil? || end_date >= Time.zone.now + def add_location(scheme, attributes) + if attributes["end_date"].nil? || attributes["end_date"] >= Time.zone.now # wheelchair_adaptation: string_or_nil(xml_doc, "mobility-type"), begin Location.create!( - name: string_or_nil(xml_doc, "name"), - postcode: string_or_nil(xml_doc, "postcode"), - units: safe_string_as_integer(xml_doc, "total-units"), - type_of_unit: safe_string_as_integer(xml_doc, "unit-type"), - old_visible_id: safe_string_as_integer(xml_doc, "visible-id"), - old_id:, + name: attributes["location_name"], + postcode: attributes["postcode"], + units: attributes["units"], + type_of_unit: attributes["type_of_unit"], + old_visible_id: attributes["location_old_visible_id"], + old_id: attributes["location_old_id"], scheme:, ) rescue ActiveRecord::RecordNotUnique - @logger.warn("Location is already present with legacy ID #{old_id}, skipping") + @logger.warn("Location is already present with legacy ID #{attributes['location_old_id']}, skipping") end else - @logger.warn("Location with legacy ID #{old_id} is expired (#{end_date}), skipping") + @logger.warn("Location with legacy ID #{attributes['location_old_id']} is expired (#{attributes['end_date']}), skipping") end end - def find_scheme_to_merge(xml_doc) - attributes = scheme_attributes(xml_doc) - + def find_scheme_to_merge(attributes) Scheme.find_by( + old_id: attributes["scheme_old_id"], scheme_type: attributes["scheme_type"], registered_under_care_act: attributes["registered_under_care_act"], support_type: attributes["support_type"], @@ -95,11 +115,11 @@ module Imports ) end - def duplicate_scheme(schemes, xml_doc) + def duplicate_scheme(schemes, attributes) # Since all schemes in the array are different, pick the first one # In the future, consider a better selection method if needed old_scheme = schemes.first - new_scheme = create_scheme(old_scheme, xml_doc) + new_scheme = create_scheme(old_scheme, attributes) if old_scheme.scheme_type != new_scheme.scheme_type rename_schemes(old_scheme, new_scheme, :scheme_type) From ecf222335aa9bff2ca4598c17072524130cf6c07 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Tue, 12 Jul 2022 16:32:15 +0100 Subject: [PATCH 11/17] update unittype mapping when infering the case log value (#735) * remove unittype_sh column, use a method to access the value instead * remove inferring builtype * remove return --- app/models/case_log.rb | 4 ++++ app/models/derived_variables/case_log_variables.rb | 10 ---------- app/services/exports/case_log_export_service.rb | 2 ++ db/migrate/20220712143943_remove_unittype_sh.rb | 5 +++++ db/schema.rb | 3 +-- spec/fixtures/exports/case_logs.csv | 4 ++-- spec/fixtures/exports/case_logs.xml | 2 +- spec/models/case_log_spec.rb | 10 ++-------- 8 files changed, 17 insertions(+), 23 deletions(-) create mode 100644 db/migrate/20220712143943_remove_unittype_sh.rb diff --git a/app/models/case_log.rb b/app/models/case_log.rb index 305582db2..c2465105c 100644 --- a/app/models/case_log.rb +++ b/app/models/case_log.rb @@ -460,6 +460,10 @@ class CaseLog < ApplicationRecord public_send("age#{person_num}_known") == 1 end + def unittype_sh + location.type_of_unit_before_type_cast if location + end + private PIO = Postcodes::IO.new diff --git a/app/models/derived_variables/case_log_variables.rb b/app/models/derived_variables/case_log_variables.rb index 33fcef3d1..8a8d8429a 100644 --- a/app/models/derived_variables/case_log_variables.rb +++ b/app/models/derived_variables/case_log_variables.rb @@ -1,13 +1,5 @@ module DerivedVariables::CaseLogVariables RENT_TYPE_MAPPING = { 0 => 1, 1 => 2, 2 => 2, 3 => 3, 4 => 3, 5 => 3 }.freeze - TYPE_OF_UNIT_MAP = { - "Self-contained flat or bedsit" => 1, - "Self-contained flat or bedsit with common facilities" => 2, - "Shared flat" => 3, - "Shared house or hostel" => 4, - "Bungalow" => 5, - "Self-contained house" => 6, - }.freeze def supported_housing_schemes_enabled? FeatureToggle.supported_housing_schemes_enabled? @@ -83,8 +75,6 @@ module DerivedVariables::CaseLogVariables if location self.la = location.county self.postcode_full = location.postcode - self.unittype_sh = TYPE_OF_UNIT_MAP[location.type_of_unit] - self.builtype = form.questions.find { |x| x.id == "builtype" }.answer_options.find { |_key, value| value["value"] == location.type_of_building }.first wheelchair_adaptation_map = { 1 => 1, 0 => 2 } self.wchair = wheelchair_adaptation_map[location.wheelchair_adaptation.to_i] end diff --git a/app/services/exports/case_log_export_service.rb b/app/services/exports/case_log_export_service.rb index 17f271762..d2e031e90 100644 --- a/app/services/exports/case_log_export_service.rb +++ b/app/services/exports/case_log_export_service.rb @@ -189,6 +189,8 @@ module Exports attribute_hash["age#{index}"] = -9 if attribute_hash["age#{index}_known"] == 1 end + attribute_hash["unittype_sh"] = case_log.unittype_sh + attribute_hash end diff --git a/db/migrate/20220712143943_remove_unittype_sh.rb b/db/migrate/20220712143943_remove_unittype_sh.rb new file mode 100644 index 000000000..fb2d68c87 --- /dev/null +++ b/db/migrate/20220712143943_remove_unittype_sh.rb @@ -0,0 +1,5 @@ +class RemoveUnittypeSh < ActiveRecord::Migration[7.0] + def change + remove_column :case_logs, :unittype_sh, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 3aa1c43ca..0461bee2d 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: 2022_07_11_152629) do +ActiveRecord::Schema[7.0].define(version: 2022_07_12_143943) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -200,7 +200,6 @@ ActiveRecord::Schema[7.0].define(version: 2022_07_11_152629) do t.integer "vacdays" t.bigint "scheme_id" t.bigint "location_id" - t.integer "unittype_sh" t.index ["created_by_id"], name: "index_case_logs_on_created_by_id" t.index ["location_id"], name: "index_case_logs_on_location_id" t.index ["managing_organisation_id"], name: "index_case_logs_on_managing_organisation_id" diff --git a/spec/fixtures/exports/case_logs.csv b/spec/fixtures/exports/case_logs.csv index 3122967a2..c13929611 100644 --- a/spec/fixtures/exports/case_logs.csv +++ b/spec/fixtures/exports/case_logs.csv @@ -1,2 +1,2 @@ -status,tenancycode,age1,sex1,ethnic,national,prevten,ecstat1,hhmemb,age2,sex2,ecstat2,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,leftreg,reservist,illness,preg_occ,startertenancy,tenancylength,tenancy,ppostcode_full,rsnvac,unittype_gn,beds,offered,wchair,earnings,incfreq,benefits,period,layear,waityear,postcode_full,reasonpref,cbl,chr,cap,reasonother,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_f,housingneeds_g,housingneeds_h,illness_type_1,illness_type_2,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,irproduct_other,reason,propcode,la,prevloc,hb,hbrentshortfall,mrcdate,incref,startdate,armedforces,unitletas,builtype,voiddate,renttype,needstype,lettype,totchild,totelder,totadult,nocharge,referral,brent,scharge,pscharge,supcharg,tcharge,tshortfall,chcharge,ppcodenk,has_benefits,renewal,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat2,relat3,relat4,relat5,relat6,relat7,relat8,lar,irproduct,joint,sheltered,hhtype,new_old,vacdays,unittype_sh,form,owningorgid,owningorgname,hcnum,maningorgid,maningorgname,manhcnum,createddate,uploaddate -2,BZ737,35,F,2,4,6,0,2,32,M,6,,,,,,,,,,,,,,,,,,,1,0,1,0,1,2,0,5,1,SE26RT,6,7,3,2,1,68,1,1,2,2,1,NW15TY,1,1,1,2,,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,,,4,123,E09000003,E07000105,6,1,2020-05-05 10:36:49 UTC,0,2022-02-02 10:36:49 UTC,1,2,1,2019-11-03 00:00:00 UTC,2,1,7,0,0,2,0,,200.0,50.0,40.0,35.0,325.0,12.0,,1,1,0,100.0,25.0,20.0,17.5,162.5,6.0,0,1,,2,P,,,,,,,,,,0,4,2,638,,{id},{owning_org_id},DLUHC,1234,{managing_org_id},DLUHC,1234,2022-02-08 16:52:15 UTC,2022-02-08 16:52:15 UTC +status,tenancycode,age1,sex1,ethnic,national,prevten,ecstat1,hhmemb,age2,sex2,ecstat2,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,leftreg,reservist,illness,preg_occ,startertenancy,tenancylength,tenancy,ppostcode_full,rsnvac,unittype_gn,beds,offered,wchair,earnings,incfreq,benefits,period,layear,waityear,postcode_full,reasonpref,cbl,chr,cap,reasonother,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_f,housingneeds_g,housingneeds_h,illness_type_1,illness_type_2,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,irproduct_other,reason,propcode,la,prevloc,hb,hbrentshortfall,mrcdate,incref,startdate,armedforces,unitletas,builtype,voiddate,renttype,needstype,lettype,totchild,totelder,totadult,nocharge,referral,brent,scharge,pscharge,supcharg,tcharge,tshortfall,chcharge,ppcodenk,has_benefits,renewal,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat2,relat3,relat4,relat5,relat6,relat7,relat8,lar,irproduct,joint,sheltered,hhtype,new_old,vacdays,form,owningorgid,owningorgname,hcnum,maningorgid,maningorgname,manhcnum,createddate,uploaddate,unittype_sh +2,BZ737,35,F,2,4,6,0,2,32,M,6,,,,,,,,,,,,,,,,,,,1,0,1,0,1,2,0,5,1,SE26RT,6,7,3,2,1,68,1,1,2,2,1,NW15TY,1,1,1,2,,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,,,4,123,E09000003,E07000105,6,1,2020-05-05 10:36:49 UTC,0,2022-02-02 10:36:49 UTC,1,2,1,2019-11-03 00:00:00 UTC,2,1,7,0,0,2,0,,200.0,50.0,40.0,35.0,325.0,12.0,,1,1,0,100.0,25.0,20.0,17.5,162.5,6.0,0,1,,2,P,,,,,,,,,,0,4,2,638,{id},{owning_org_id},DLUHC,1234,{managing_org_id},DLUHC,1234,2022-02-08 16:52:15 UTC,2022-02-08 16:52:15 UTC, diff --git a/spec/fixtures/exports/case_logs.xml b/spec/fixtures/exports/case_logs.xml index 2608bf92e..a4089db67 100644 --- a/spec/fixtures/exports/case_logs.xml +++ b/spec/fixtures/exports/case_logs.xml @@ -135,7 +135,6 @@ 4 2 638 -
{id}
{owning_org_id} DLUHC @@ -145,6 +144,7 @@ 1234 2022-02-08 16:52:15 UTC 2022-02-08 16:52:15 UTC + 1 diff --git a/spec/models/case_log_spec.rb b/spec/models/case_log_spec.rb index ae5c550d6..d5cbed1cc 100644 --- a/spec/models/case_log_spec.rb +++ b/spec/models/case_log_spec.rb @@ -1726,14 +1726,8 @@ RSpec.describe CaseLog do expect(record_from_db["postcode_full"]).to eq(location.postcode) end - it "correctly infers and saves type of unit" do - record_from_db = ActiveRecord::Base.connection.execute("SELECT unittype_sh from case_logs WHERE id=#{supported_housing_case_log.id}").to_a[0] - expect(record_from_db["unittype_sh"]).to eq(1) - end - - it "correctly infers and saves type of building" do - record_from_db = ActiveRecord::Base.connection.execute("SELECT builtype from case_logs WHERE id=#{supported_housing_case_log.id}").to_a[0] - expect(record_from_db["builtype"]).to eq(1) + it "unittype_sh method returns the type_of_unit of the location" do + expect(supported_housing_case_log.unittype_sh).to eq(1) end it "correctly infers and saves wchair" do From 66d8bb3e2d01729016e1d363c2c8637aae24faa5 Mon Sep 17 00:00:00 2001 From: baarkerlounger <5101747+baarkerlounger@users.noreply.github.com> Date: Tue, 12 Jul 2022 16:39:06 +0100 Subject: [PATCH 12/17] Add source code link to footer (#737) --- app/views/layouts/_footer.html.erb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/views/layouts/_footer.html.erb b/app/views/layouts/_footer.html.erb index 49a6e689c..91cff0282 100644 --- a/app/views/layouts/_footer.html.erb +++ b/app/views/layouts/_footer.html.erb @@ -34,6 +34,9 @@ +