diff --git a/Gemfile b/Gemfile index f28f6dee0..3039202f0 100644 --- a/Gemfile +++ b/Gemfile @@ -19,8 +19,12 @@ gem "jbuilder", "~> 2.7" gem "bootsnap", ">= 1.4.4", require: false # Gov.UK frontend components gem "govuk-components" +# Gov.UK component form builder DSL gem "govuk_design_system_formbuilder" +# Turbo & Stimulus gem "hotwire-rails" +# Soft delete ActiveRecords objects +gem "discard" group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console diff --git a/Gemfile.lock b/Gemfile.lock index 328d2a9ef..176bb4202 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -123,12 +123,14 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - childprocess (3.0.0) + childprocess (4.1.0) coderay (1.1.3) concurrent-ruby (1.1.9) crass (1.0.6) deep_merge (1.2.1) diff-lcs (1.4.4) + discard (1.2.0) + activerecord (>= 4.2, < 7) docile (1.4.0) dotenv (2.7.6) dotenv-rails (2.7.6) @@ -143,7 +145,7 @@ GEM ffi (1.15.4) globalid (0.5.2) activesupport (>= 5.0) - govuk-components (2.1.2) + govuk-components (2.1.3) activemodel (>= 6.0) railties (>= 6.0) view_component (~> 2.39.0) @@ -171,7 +173,7 @@ GEM mini_mime (>= 0.1.1) marcel (1.0.2) method_source (1.0.0) - mini_mime (1.1.1) + mini_mime (1.1.2) minitest (5.14.4) msgpack (1.4.2) nio4r (2.5.8) @@ -194,7 +196,7 @@ GEM byebug (~> 11.0) pry (~> 0.13.0) public_suffix (4.0.6) - puma (5.5.0) + puma (5.5.2) nio4r (~> 2.0) racc (1.5.2) rack (2.2.3) @@ -277,8 +279,9 @@ GEM sass (~> 3.5, >= 3.5.5) scss_lint-govuk (0.2.0) scss_lint - selenium-webdriver (3.142.7) - childprocess (>= 0.5, < 4.0) + selenium-webdriver (4.0.0) + childprocess (>= 0.5, < 5.0) + rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2) semantic_range (3.0.0) simplecov (0.21.2) @@ -294,10 +297,10 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - stimulus-rails (0.7.0) + stimulus-rails (0.7.1) rails (>= 6.0.0) thor (1.1.0) - turbo-rails (0.8.1) + turbo-rails (7.1.0) rails (>= 6.0.0) tzinfo (2.0.4) concurrent-ruby (~> 1.0) @@ -331,6 +334,7 @@ DEPENDENCIES bootsnap (>= 1.4.4) byebug capybara + discard dotenv-rails factory_bot_rails govuk-components diff --git a/app/controllers/case_logs_controller.rb b/app/controllers/case_logs_controller.rb index 4dc0ddc6e..20cc5c64f 100644 --- a/app/controllers/case_logs_controller.rb +++ b/app/controllers/case_logs_controller.rb @@ -6,8 +6,8 @@ class CaseLogsController < ApplicationController # rubocop:enable Style/ClassVars def index - @submitted_case_logs = CaseLog.where(status: 1) - @in_progress_case_logs = CaseLog.where(status: 0) + @completed_case_logs = CaseLog.where(status: 2) + @in_progress_case_logs = CaseLog.where(status: 1) end def create @@ -63,6 +63,18 @@ class CaseLogsController < ApplicationController end end + def destroy + if case_log = CaseLog.find_by(id: params[:id]) + if case_log.discard + head :no_content + else + render json: { errors: case_log.errors.full_messages }, status: :unprocessable_entity + end + else + render json: { error: "Case Log #{params[:id]} not found" }, status: :not_found + end + end + def check_answers form = @@form_handler.get_form("2021_2022") @case_log = CaseLog.find(params[:case_log_id]) @@ -81,7 +93,7 @@ class CaseLogsController < ApplicationController private - API_ACTIONS = %w[create update].freeze + API_ACTIONS = %w[create update destroy].freeze def question_responses(questions_for_page) questions_for_page.each_with_object({}) do |(question_key, question_info), result| diff --git a/app/helpers/tasklist_helper.rb b/app/helpers/tasklist_helper.rb index 549ef2d22..3da3ddc52 100644 --- a/app/helpers/tasklist_helper.rb +++ b/app/helpers/tasklist_helper.rb @@ -15,7 +15,7 @@ module TasklistHelper def get_subsection_status(subsection_name, case_log, questions) if subsection_name == "declaration" - return all_questions_completed(case_log) ? :not_started : :cannot_start_yet + return case_log.completed? ? :not_started : :cannot_start_yet end return :not_started if questions.all? { |question| case_log[question].blank? } @@ -47,10 +47,6 @@ module TasklistHelper private - def all_questions_completed(case_log) - case_log.attributes.all? { |_question, answer| answer.present? } - end - def is_incomplete?(subsection, case_log, questions) status = get_subsection_status(subsection, case_log, questions) %i[not_started in_progress].include?(status) diff --git a/app/models/case_log.rb b/app/models/case_log.rb index bcbbc432a..efcf09f92 100644 --- a/app/models/case_log.rb +++ b/app/models/case_log.rb @@ -26,29 +26,59 @@ class CaseLogValidator < ActiveModel::Validator end class CaseLog < ApplicationRecord + include Discard::Model + default_scope -> { kept } + validate :instance_validations before_save :update_status! attr_writer :previous_page - enum status: { "in progress" => 0, "submitted" => 1 } + enum status: { "not_started" => 0, "in_progress" => 1, "completed" => 2 } - AUTOGENERATED_FIELDS = %w[status created_at updated_at id].freeze + AUTOGENERATED_FIELDS = %w[id status created_at updated_at discarded_at].freeze def instance_validations validates_with CaseLogValidator, ({ previous_page: @previous_page } || {}) end + def self.editable_fields + attribute_names - AUTOGENERATED_FIELDS + end + + def completed? + status == "completed" + end + + def not_started? + status == "not started" + end + + def in_progress? + status == "in progress" + end + + private + def update_status! - self.status = (all_fields_completed? && errors.empty? ? "submitted" : "in progress") + self.status = if all_fields_completed? && errors.empty? + "completed" + elsif all_fields_nil? + "not_started" + else + "in_progress" + end end def all_fields_completed? - mandatory_fields = attributes.except(*AUTOGENERATED_FIELDS) mandatory_fields.none? { |_key, val| val.nil? } end - def self.editable_fields - attribute_names - AUTOGENERATED_FIELDS + def all_fields_nil? + mandatory_fields.all? { |_key, val| val.nil? } + end + + def mandatory_fields + attributes.except(*AUTOGENERATED_FIELDS) end end diff --git a/app/views/case_logs/index.html.erb b/app/views/case_logs/index.html.erb index 35ae34399..7352c304c 100644 --- a/app/views/case_logs/index.html.erb +++ b/app/views/case_logs/index.html.erb @@ -10,10 +10,10 @@ <%= render partial: "log_list", locals: { case_logs: @in_progress_case_logs, title: "Logs you need to complete", date_title: "Last Changed" } %> <% end %> - <% if @submitted_case_logs.present? %> - <%= render partial: "log_list", locals: { case_logs: @submitted_case_logs, title: "Logs you've submitted", date_title: "Date Submitted" } %> + <% if @completed_case_logs.present? %> + <%= render partial: "log_list", locals: { case_logs: @completed_case_logs, title: "Logs you've submitted", date_title: "Date Submitted" } %> <% end %> -
See all completed logs (<%= @submitted_case_logs.count %>)
+See all completed logs (<%= @completed_case_logs.count %>)
diff --git a/db/migrate/20211014154616_add_discarded_at_to_case_logs.rb b/db/migrate/20211014154616_add_discarded_at_to_case_logs.rb new file mode 100644 index 000000000..429640759 --- /dev/null +++ b/db/migrate/20211014154616_add_discarded_at_to_case_logs.rb @@ -0,0 +1,6 @@ +class AddDiscardedAtToCaseLogs < ActiveRecord::Migration[6.1] + def change + add_column :case_logs, :discarded_at, :datetime + add_index :case_logs, :discarded_at + end +end diff --git a/db/schema.rb b/db/schema.rb index 3e73a03e0..fbfb2467e 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.define(version: 2021_10_13_113607) do +ActiveRecord::Schema.define(version: 2021_10_14_154616) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -131,6 +131,8 @@ ActiveRecord::Schema.define(version: 2021_10_13_113607) do t.boolean "reasonable_preference_reason_medical_grounds" t.boolean "reasonable_preference_reason_avoid_hardship" t.boolean "reasonable_preference_reason_do_not_know" + t.datetime "discarded_at" + t.index ["discarded_at"], name: "index_case_logs_on_discarded_at" end end diff --git a/spec/factories/case_log.rb b/spec/factories/case_log.rb index a9aa4ab56..044a4cbac 100644 --- a/spec/factories/case_log.rb +++ b/spec/factories/case_log.rb @@ -2,14 +2,14 @@ FactoryBot.define do factory :case_log do sequence(:id) { |i| i } trait :in_progress do - status { 0 } + status { 1 } tenant_code { "TH356" } property_postcode { "SW2 6HI" } previous_postcode { "P0 5ST" } tenant_age { "12" } end - trait :submitted do - status { 1 } + trait :completed do + status { 2 } tenant_code { "BZ737" } property_postcode { "NW1 7TY" } end diff --git a/spec/helpers/tasklist_helper_spec.rb b/spec/helpers/tasklist_helper_spec.rb index 28860962f..e2ced980f 100644 --- a/spec/helpers/tasklist_helper_spec.rb +++ b/spec/helpers/tasklist_helper_spec.rb @@ -1,8 +1,9 @@ require "rails_helper" RSpec.describe TasklistHelper do - let!(:empty_case_log) { FactoryBot.create(:case_log) } - let!(:case_log) { FactoryBot.create(:case_log, :in_progress) } + let(:empty_case_log) { FactoryBot.build(:case_log) } + let(:case_log) { FactoryBot.build(:case_log, :in_progress) } + let(:completed_case_log) { FactoryBot.build(:case_log, :completed) } form_handler = FormHandler.instance let(:form) { form_handler.get_form("test_form") } @@ -35,7 +36,6 @@ RSpec.describe TasklistHelper do end it "returns not started if the subsection is declaration and all the questions are completed" do - completed_case_log = CaseLog.new(case_log.attributes.map { |key, value| Hash[key, value || "value"] }.reduce(:merge)) status = get_subsection_status("declaration", completed_case_log, declaration_questions) expect(status).to eq(:not_started) end diff --git a/spec/requests/case_log_controller_spec.rb b/spec/requests/case_log_controller_spec.rb index 3b08a493c..0cdf5d791 100644 --- a/spec/requests/case_log_controller_spec.rb +++ b/spec/requests/case_log_controller_spec.rb @@ -26,8 +26,8 @@ RSpec.describe CaseLogsController, type: :request do let(:tenant_code) { "T365" } let(:tenant_age) { 35 } let(:property_postcode) { "SE11 6TY" } - let(:in_progress) { "in progress" } - let(:submitted) { "submitted" } + let(:in_progress) { "in_progress" } + let(:completed) { "completed" } let(:params) do { @@ -79,9 +79,9 @@ RSpec.describe CaseLogsController, type: :request do JSON.parse(File.open("spec/fixtures/complete_case_log.json").read) end - it "marks the record as submitted" do + it "marks the record as completed" do json_response = JSON.parse(response.body) - expect(json_response["status"]).to eq(submitted) + expect(json_response["status"]).to eq(completed) end end @@ -182,4 +182,42 @@ RSpec.describe CaseLogsController, type: :request do end end end + + describe "DELETE" do + let!(:case_log) do + FactoryBot.create(:case_log, :in_progress) + end + let(:id) { case_log.id } + + before do + delete "/case_logs/#{id}", headers: headers + end + + it "returns http success" do + expect(response).to have_http_status(:success) + end + + it "soft deletes the case log" do + expect { CaseLog.find(id) }.to raise_error(ActiveRecord::RecordNotFound) + expect(CaseLog.with_discarded.find(id)).to be_a(CaseLog) + end + + context "invalid case log id" do + let(:id) { (CaseLog.order(:id).last&.id || 0) + 1 } + + it "returns 404" do + expect(response).to have_http_status(:not_found) + end + end + + context "request with invalid credentials" do + let(:basic_credentials) do + ActionController::HttpAuthentication::Basic.encode_credentials(api_username, "Oops") + end + + it "returns 401" do + expect(response).to have_http_status(:unauthorized) + end + end + end end diff --git a/spec/views/case_log_index_view_spec.rb b/spec/views/case_log_index_view_spec.rb index b97347cb6..cc826e3c8 100644 --- a/spec/views/case_log_index_view_spec.rb +++ b/spec/views/case_log_index_view_spec.rb @@ -1,12 +1,12 @@ require "rails_helper" RSpec.describe "case_logs/index" do let(:in_progress_log) { FactoryBot.build(:case_log, :in_progress) } - let(:submitted_log) { FactoryBot.build(:case_log, :submitted) } + let(:completed_log) { FactoryBot.build(:case_log, :completed) } context "given an in progress log list" do it "renders a table for in progress logs only" do assign(:in_progress_case_logs, [in_progress_log]) - assign(:submitted_case_logs, []) + assign(:completed_case_logs, []) render expect(rendered).to match(/