diff --git a/app/controllers/form_controller.rb b/app/controllers/form_controller.rb index a7ee469b8..4da903999 100644 --- a/app/controllers/form_controller.rb +++ b/app/controllers/form_controller.rb @@ -4,22 +4,22 @@ class FormController < ApplicationController before_action :find_resource_by_named_id, except: %i[submit_form review] def submit_form - if @lettings_log - @page = @lettings_log.form.get_page(params[:lettings_log][:page]) + if @log + @page = @log.form.get_page(params[@log.model_name.param_key][:page]) responses_for_page = responses_for_page(@page) mandatory_questions_with_no_response = mandatory_questions_with_no_response(responses_for_page) - if mandatory_questions_with_no_response.empty? && @lettings_log.update(responses_for_page) + if mandatory_questions_with_no_response.empty? && @log.update(responses_for_page) session[:errors] = session[:fields] = nil redirect_to(successful_redirect_path) else - redirect_path = "lettings_log_#{@page.id}_path" + redirect_path = "#{@log.class.name.underscore}_#{@page.id}_path" mandatory_questions_with_no_response.map do |question| - @lettings_log.errors.add question.id.to_sym, question.unanswered_error_message + @log.errors.add question.id.to_sym, question.unanswered_error_message end - session[:errors] = @lettings_log.errors.to_json - Rails.logger.info "User triggered validation(s) on: #{@lettings_log.errors.map(&:attribute).join(', ')}" - redirect_to(send(redirect_path, @lettings_log)) + session[:errors] = @log.errors.to_json + Rails.logger.info "User triggered validation(s) on: #{@log.errors.map(&:attribute).join(', ')}" + redirect_to(send(redirect_path, @log)) end else render_not_found @@ -27,9 +27,9 @@ class FormController < ApplicationController end def check_answers - if @lettings_log + if @log current_url = request.env["PATH_INFO"] - subsection = @lettings_log.form.get_subsection(current_url.split("/")[-2]) + subsection = @log.form.get_subsection(current_url.split("/")[-2]) render "form/check_answers", locals: { subsection:, current_user: } else render_not_found @@ -37,29 +37,26 @@ class FormController < ApplicationController end def review - if @lettings_log + if @log render "form/review" else render_not_found end end - FormHandler.instance.forms.each do |_key, form| - form.pages.map do |page| - define_method(page.id) do |_errors = {}| - if @lettings_log - restore_error_field_values - @subsection = @lettings_log.form.subsection_for_page(page) - @page = @lettings_log.form.get_page(page.id) - if @page.routed_to?(@lettings_log, current_user) - render "form/page" - else - redirect_to lettings_log_path(@lettings_log) - end - else - render_not_found - end + def show_page + if @log + restore_error_field_values + page_id = request.path.split("/")[-1].underscore + @page = @log.form.get_page(page_id) + @subsection = @log.form.subsection_for_page(@page) + if @page.routed_to?(@log, current_user) + render "form/page" + else + redirect_to lettings_log_path(@log) end + else + render_not_found end end @@ -68,13 +65,13 @@ private def restore_error_field_values if session["errors"] JSON(session["errors"]).each do |field, messages| - messages.each { |message| @lettings_log.errors.add field.to_sym, message } + messages.each { |message| @log.errors.add field.to_sym, message } end end if session["fields"] session["fields"].each do |field, value| - unless @lettings_log.form.get_question(field, @lettings_log)&.type == "date" - @lettings_log[field] = value + unless @log.form.get_question(field, @log)&.type == "date" + @log[field] = value end end end @@ -82,11 +79,11 @@ private def responses_for_page(page) page.questions.each_with_object({}) do |question, result| - question_params = params["lettings_log"][question.id] + question_params = params[@log.class.name.underscore][question.id] if question.type == "date" - day = params["lettings_log"]["#{question.id}(3i)"] - month = params["lettings_log"]["#{question.id}(2i)"] - year = params["lettings_log"]["#{question.id}(1i)"] + day = params[@log.class.name.underscore]["#{question.id}(3i)"] + month = params[@log.class.name.underscore]["#{question.id}(2i)"] + year = params[@log.class.name.underscore]["#{question.id}(1i)"] next unless [day, month, year].any?(&:present?) result[question.id] = if Date.valid_date?(year.to_i, month.to_i, day.to_i) && year.to_i.between?(2000, 2200) @@ -109,11 +106,19 @@ private end def find_resource - @lettings_log = current_user.lettings_logs.find_by(id: params[:id]) + @log = if params.key?("sales_log") + current_user.sales_logs.find_by(id: params[:id]) + else + current_user.lettings_logs.find_by(id: params[:id]) + end end def find_resource_by_named_id - @lettings_log = current_user.lettings_logs.find_by(id: params[:lettings_log_id]) + @log = if params["sales_log_id"].present? + current_user.sales_logs.find_by(id: params[:sales_log_id]) + else + current_user.lettings_logs.find_by(id: params[:lettings_log_id]) + end end def is_referrer_check_answers? @@ -123,18 +128,18 @@ private def successful_redirect_path if is_referrer_check_answers? - page_ids = @lettings_log.form.subsection_for_page(@page).pages.map(&:id) + page_ids = @log.form.subsection_for_page(@page).pages.map(&:id) page_index = page_ids.index(@page.id) - next_page = @lettings_log.form.next_page(@page, @lettings_log, current_user) - previous_page = @lettings_log.form.previous_page(page_ids, page_index, @lettings_log, current_user) + next_page = @log.form.next_page(@page, @log, current_user) + previous_page = @log.form.previous_page(page_ids, page_index, @log, current_user) if next_page.to_s.include?("value_check") || next_page == previous_page - return "/logs/#{@lettings_log.id}/#{next_page.dasherize}?referrer=check_answers" + return "/logs/#{@log.id}/#{next_page.dasherize}?referrer=check_answers" else - return send("lettings_log_#{@lettings_log.form.subsection_for_page(@page).id}_check_answers_path", @lettings_log) + return send("#{@log.class.name.underscore}_#{@log.form.subsection_for_page(@page).id}_check_answers_path", @log) end end - redirect_path = @lettings_log.form.next_page_redirect_path(@page, @lettings_log, current_user) - send(redirect_path, @lettings_log) + redirect_path = @log.form.next_page_redirect_path(@page, @log, current_user) + send(redirect_path, @log) end def mandatory_questions_with_no_response(responses_for_page) @@ -153,7 +158,7 @@ private def required_questions @required_questions ||= begin - log = @lettings_log + log = @log log.assign_attributes(responses_for_page(@page)) @page.subsection.applicable_questions(log).select { |q| q.enabled?(log) }.map(&:id) end @@ -162,12 +167,12 @@ private def question_missing_response?(responses_for_page, question) if %w[checkbox validation_override].include?(question.type) answered = question.answer_options.keys.reject { |x| x.match(/divider/) }.map do |option| - session["fields"][option] = @lettings_log[option] = params["lettings_log"][question.id].include?(option) ? 1 : 0 + session["fields"][option] = @log[option] = params["lettings_log"][question.id].include?(option) ? 1 : 0 params["lettings_log"][question.id].exclude?(option) end answered.all? else - session["fields"][question.id] = @lettings_log[question.id] = responses_for_page[question.id] + session["fields"][question.id] = @log[question.id] = responses_for_page[question.id] responses_for_page[question.id].nil? || responses_for_page[question.id].blank? end end diff --git a/app/controllers/lettings_logs_controller.rb b/app/controllers/lettings_logs_controller.rb index a19fdbe6d..641a71214 100644 --- a/app/controllers/lettings_logs_controller.rb +++ b/app/controllers/lettings_logs_controller.rb @@ -1,10 +1,8 @@ -class LettingsLogsController < ApplicationController +class LettingsLogsController < LogsController include Pagy::Backend include Modules::LettingsLogsFilter include Modules::SearchFilter - skip_before_action :verify_authenticity_token, if: :json_api_request? - before_action :authenticate, if: :json_api_request? before_action :authenticate_user!, unless: :json_api_request? before_action :find_resource, except: %i[create index edit] @@ -28,25 +26,12 @@ class LettingsLogsController < ApplicationController end def create - lettings_log = LettingsLog.new(lettings_log_params) - respond_to do |format| - format.html do - lettings_log.save! - redirect_to lettings_log_url(lettings_log) - end - format.json do - if lettings_log.save - render json: lettings_log, status: :created - else - render json: { errors: lettings_log.errors.messages }, status: :unprocessable_entity - end - end - end + super { LettingsLog.new(log_params) } end def update if @lettings_log - if @lettings_log.update(api_lettings_log_params) + if @lettings_log.update(api_log_params) render json: @lettings_log, status: :ok else render json: { errors: @lettings_log.errors.messages }, status: :unprocessable_entity @@ -99,40 +84,15 @@ private params["search"] end - def json_api_request? - API_ACTIONS.include?(request["action"]) && request.format.json? - end - - def authenticate - http_basic_authenticate_or_request_with name: ENV["API_USER"], password: ENV["API_KEY"] - end - - def lettings_log_params - if current_user && !current_user.support? - org_params.merge(api_lettings_log_params) - else - api_lettings_log_params - end - end - - def org_params - { - "owning_organisation_id" => current_user.organisation.id, - "managing_organisation_id" => current_user.organisation.id, - "created_by_id" => current_user.id, - } - end - - def api_lettings_log_params - return {} unless params[:lettings_log] - - permitted = params.require(:lettings_log).permit(LettingsLog.editable_fields) - owning_id = permitted["owning_organisation_id"] - permitted["owning_organisation"] = Organisation.find(owning_id) if owning_id - permitted - end + def permitted_log_params + params.require(:lettings_log).permit(LettingsLog.editable_fields) + end def find_resource @lettings_log = LettingsLog.find_by(id: params[:id]) end + + def post_create_redirect_url(log) + lettings_log_url(log) + end end diff --git a/app/controllers/logs_controller.rb b/app/controllers/logs_controller.rb new file mode 100644 index 000000000..fb274993b --- /dev/null +++ b/app/controllers/logs_controller.rb @@ -0,0 +1,64 @@ +class LogsController < ApplicationController + skip_before_action :verify_authenticity_token, if: :json_api_request? + before_action :authenticate, if: :json_api_request? + +private + + def create + log = yield + raise "Caller must pass a block that implements model creation" if log.blank? + + respond_to do |format| + format.html do + log.save! + redirect_to post_create_redirect_url(log) + end + format.json do + if log.save + render json: log, status: :created + else + render json: { errors: log.errors.messages }, status: :unprocessable_entity + end + end + end + end + + def post_create_redirect_url + raise "implement in sub class" + end + + API_ACTIONS = %w[create show update destroy].freeze + + def json_api_request? + API_ACTIONS.include?(request["action"]) && request.format.json? + end + + def authenticate + http_basic_authenticate_or_request_with name: ENV["API_USER"], password: ENV["API_KEY"] + end + + def log_params + if current_user && !current_user.support? + org_params.merge(api_log_params) + else + api_log_params + end + end + + def api_log_params + return {} unless params[:lettings_log] || params[:sales_log] + + permitted = permitted_log_params + owning_id = permitted["owning_organisation_id"] + permitted["owning_organisation"] = Organisation.find(owning_id) if owning_id + permitted + end + + def org_params + { + "owning_organisation_id" => current_user.organisation.id, + "managing_organisation_id" => current_user.organisation.id, + "created_by_id" => current_user.id, + } + end +end diff --git a/app/controllers/sales_logs_controller.rb b/app/controllers/sales_logs_controller.rb new file mode 100644 index 000000000..84cf0d275 --- /dev/null +++ b/app/controllers/sales_logs_controller.rb @@ -0,0 +1,28 @@ +class SalesLogsController < LogsController + def create + super { SalesLog.new(log_params) } + end + + def show + respond_to do |format| + format.html { edit } + end + end + + def edit + @sales_log = current_user.sales_logs.find_by(id: params[:id]) + if @sales_log + render :edit, locals: { current_user: } + else + render_not_found + end + end + + def post_create_redirect_url(log) + sales_log_url(log) + end + + def permitted_log_params + params.require(:sales_log).permit(SalesLog.editable_fields) + end +end diff --git a/app/helpers/tasklist_helper.rb b/app/helpers/tasklist_helper.rb index e4308d97e..4256979e0 100644 --- a/app/helpers/tasklist_helper.rb +++ b/app/helpers/tasklist_helper.rb @@ -11,13 +11,13 @@ module TasklistHelper lettings_log.form.subsections.count { |subsection| subsection.status(lettings_log) == status && subsection.applicable_questions(lettings_log).count.positive? } end - def next_page_or_check_answers(subsection, lettings_log, current_user) - path = if subsection.is_started?(lettings_log) - "lettings_log_#{subsection.id}_check_answers_path" + def next_page_or_check_answers(subsection, log, current_user) + path = if subsection.is_started?(log) + "#{log.class.name.underscore}_#{subsection.id}_check_answers_path" else - "lettings_log_#{next_question_page(subsection, lettings_log, current_user)}_path" + "#{log.class.name.underscore}_#{next_question_page(subsection, log, current_user)}_path" end - send(path, lettings_log) + send(path, log) end def next_question_page(subsection, lettings_log, current_user) diff --git a/app/models/form.rb b/app/models/form.rb index 6fa92bba0..6e683ed5a 100644 --- a/app/models/form.rb +++ b/app/models/form.rb @@ -5,20 +5,38 @@ class Form include Form::Setup - def initialize(form_path, name) - raise "No form definition file exists for given year".freeze unless File.exist?(form_path) - - @name = name - @setup_sections = [Form::Setup::Sections::Setup.new(nil, nil, self)] - @form_definition = JSON.parse(File.open(form_path).read) - @form_sections = form_definition["sections"].map { |id, s| Form::Section.new(id, s, self) } - @type = form_definition["form_type"] - @sections = setup_sections + form_sections - @subsections = sections.flat_map(&:subsections) - @pages = subsections.flat_map(&:pages) - @questions = pages.flat_map(&:questions) - @start_date = Time.iso8601(form_definition["start_date"]) - @end_date = Time.iso8601(form_definition["end_date"]) + def initialize(form_path, name, sections_in_form = [], type = "lettings") + if type == "sales" + # @form_definition = sections.map do |s| + # s.new(nil, nil, self) + # end + @name = name + # @setup_sections = [Form::Setup::Sections::Setup.new(nil, nil, self)] + # @form_definition = JSON.parse(File.open(form_path).read) + @form_sections = sections_in_form.map { |sec| sec.new(nil, nil, self) } + @type = "sales" + @sections = form_sections + @subsections = sections.flat_map(&:subsections) + @pages = subsections.flat_map(&:pages) + @questions = pages.flat_map(&:questions) + @start_date = Time.zone.local(name[0..3], 4, 1) + @end_date = Time.zone.local(start_date.year + 1, 4, 1) + + else + raise "No form definition file exists for given year".freeze unless File.exist?(form_path) + + @name = name + @setup_sections = [Form::Setup::Sections::Setup.new(nil, nil, self)] + @form_definition = JSON.parse(File.open(form_path).read) + @form_sections = form_definition["sections"].map { |id, s| Form::Section.new(id, s, self) } + @type = form_definition["form_type"] + @sections = setup_sections + form_sections + @subsections = sections.flat_map(&:subsections) + @pages = subsections.flat_map(&:pages) + @questions = pages.flat_map(&:questions) + @start_date = Time.iso8601(form_definition["start_date"]) + @end_date = Time.iso8601(form_definition["end_date"]) + end end def get_subsection(id) @@ -58,28 +76,28 @@ class Form def next_page_redirect_path(page, lettings_log, current_user) nxt_page = next_page(page, lettings_log, current_user) if nxt_page == :check_answers - "lettings_log_#{subsection_for_page(page).id}_check_answers_path" + "#{type}_log_#{subsection_for_page(page).id}_check_answers_path" else - "lettings_log_#{nxt_page}_path" + "#{type}_log_#{nxt_page}_path" end end - def next_incomplete_section_redirect_path(subsection, lettings_log) + def next_incomplete_section_redirect_path(subsection, log) subsection_ids = subsections.map(&:id) - if lettings_log.status == "completed" + if log.status == "completed" return first_question_in_last_subsection(subsection_ids) end - next_subsection = next_subsection(subsection, lettings_log, subsection_ids) + next_subsection = next_subsection(subsection, log, subsection_ids) - case next_subsection.status(lettings_log) + case next_subsection.status(log) when :completed - next_incomplete_section_redirect_path(next_subsection, lettings_log) + next_incomplete_section_redirect_path(next_subsection, log) when :in_progress "#{next_subsection.id}/check_answers".dasherize when :not_started - first_question_in_subsection = next_subsection.pages.find { |page| page.routed_to?(lettings_log, nil) }.id + first_question_in_subsection = next_subsection.pages.find { |page| page.routed_to?(log, nil) }.id first_question_in_subsection.to_s.dasherize else "error" @@ -92,11 +110,11 @@ class Form first_question_in_subsection.to_s.dasherize end - def next_subsection(subsection, lettings_log, subsection_ids) + def next_subsection(subsection, log, subsection_ids) next_subsection_id_index = subsection_ids.index(subsection.id) + 1 next_subsection = get_subsection(subsection_ids[next_subsection_id_index]) - if subsection_ids[subsection_ids.length - 1] == subsection.id && lettings_log.status != "completed" + if subsection_ids[subsection_ids.length - 1] == subsection.id && log.status != "completed" next_subsection = get_subsection(subsection_ids[0]) end diff --git a/app/models/form/sales/setup/pages/sale_date.rb b/app/models/form/sales/setup/pages/sale_date.rb new file mode 100644 index 000000000..4cdfe5a71 --- /dev/null +++ b/app/models/form/sales/setup/pages/sale_date.rb @@ -0,0 +1,15 @@ +class Form::Sales::Setup::Pages::SaleDate < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "sale_date" + @header = "" + @description = "" + @subsection = subsection + end + + def questions + @questions ||= [ + Form::Sales::Setup::Questions::SaleDate.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/setup/questions/sale_date.rb b/app/models/form/sales/setup/questions/sale_date.rb new file mode 100644 index 000000000..4f9088aaf --- /dev/null +++ b/app/models/form/sales/setup/questions/sale_date.rb @@ -0,0 +1,10 @@ +class Form::Sales::Setup::Questions::SaleDate < ::Form::Question + def initialize(id, hsh, page) + super + @id = "saledate" + @check_answer_label = "Sale completion date" + @header = "What is the sale completion date?" + @type = "date" + @page = page + end +end diff --git a/app/models/form/sales/setup/sections/setup.rb b/app/models/form/sales/setup/sections/setup.rb new file mode 100644 index 000000000..42835ec39 --- /dev/null +++ b/app/models/form/sales/setup/sections/setup.rb @@ -0,0 +1,10 @@ +class Form::Sales::Setup::Sections::Setup < ::Form::Section + def initialize(id, hsh, form) + super + @id = "setup" + @label = "Before you start" + @description = "" + @form = form + @subsections = [Form::Sales::Setup::Subsections::Setup.new(nil, nil, self)] || [] + end +end diff --git a/app/models/form/sales/setup/subsections/setup.rb b/app/models/form/sales/setup/subsections/setup.rb new file mode 100644 index 000000000..ee0af6350 --- /dev/null +++ b/app/models/form/sales/setup/subsections/setup.rb @@ -0,0 +1,14 @@ +class Form::Sales::Setup::Subsections::Setup < ::Form::Subsection + def initialize(id, hsh, section) + super + @id = "setup" + @label = "Set up this sales log" + @section = section + end + + def pages + @pages ||= [ + Form::Sales::Setup::Pages::SaleDate.new(nil, nil, self), + ] + end +end diff --git a/app/models/form_handler.rb b/app/models/form_handler.rb index 13efcb9ca..41414eb7d 100644 --- a/app/models/form_handler.rb +++ b/app/models/form_handler.rb @@ -14,9 +14,12 @@ class FormHandler forms[forms.keys.max_by(&:to_i)] end -private + def sales_forms + sales_sections = [Form::Sales::Setup::Sections::Setup] + { "2022_2023_sales" => Form.new(nil, "2022_2023_sales", sales_sections, "sales") } + end - def get_all_forms + def lettings_forms forms = {} directories.each do |directory| Dir.glob("#{directory}/*.json").each do |form_path| @@ -27,7 +30,15 @@ private forms end +private + + def get_all_forms + lettings_forms.merge(sales_forms) + end + def directories Rails.env.test? ? ["spec/fixtures/forms"] : ["config/forms"] end + + # SALES_FORM = [Form::Sales::Sections::Setup, Form::Sales::Sections::Property] end diff --git a/app/models/lettings_log.rb b/app/models/lettings_log.rb index 142c48e52..81c69f1f5 100644 --- a/app/models/lettings_log.rb +++ b/app/models/lettings_log.rb @@ -14,7 +14,7 @@ class LettingsLogValidator < ActiveModel::Validator end end -class LettingsLog < ApplicationRecord +class LettingsLog < Log include Validations::SoftValidations include DerivedVariables::LettingsLogVariables @@ -31,9 +31,6 @@ class LettingsLog < ApplicationRecord before_validation :set_derived_fields! before_save :update_status! - belongs_to :owning_organisation, class_name: "Organisation", optional: true - belongs_to :managing_organisation, class_name: "Organisation", optional: true - belongs_to :created_by, class_name: "User", optional: true belongs_to :scheme, optional: true belongs_to :location, optional: true diff --git a/app/models/log.rb b/app/models/log.rb new file mode 100644 index 000000000..316af6d06 --- /dev/null +++ b/app/models/log.rb @@ -0,0 +1,7 @@ +class Log < ApplicationRecord + self.abstract_class = true + + belongs_to :owning_organisation, class_name: "Organisation", optional: true + belongs_to :managing_organisation, class_name: "Organisation", optional: true + belongs_to :created_by, class_name: "User", optional: true +end diff --git a/app/models/sales_log.rb b/app/models/sales_log.rb new file mode 100644 index 000000000..cdeaf0695 --- /dev/null +++ b/app/models/sales_log.rb @@ -0,0 +1,72 @@ +class SalesLogValidator < ActiveModel::Validator + def validate(record); end +end + +class SalesLog < Log + has_paper_trail + + validates_with SalesLogValidator + before_save :update_status! + + scope :filter_by_organisation, ->(org, _user = nil) { where(owning_organisation: org).or(where(managing_organisation: org)) } + + STATUS = { "not_started" => 0, "in_progress" => 1, "completed" => 2 }.freeze + enum status: STATUS + + def self.editable_fields + attribute_names + end + + def form_name + return unless saledate + + "#{collection_start_year}_#{collection_start_year + 1}_sales" + end + + def collection_start_year + return @sale_year if @sale_year + return unless saledate + + window_end_date = Time.zone.local(saledate.year, 4, 1) + @sale_year = saledate < window_end_date ? saledate.year - 1 : saledate.year + end + + def form + FormHandler.instance.get_form(form_name) || FormHandler.instance.get_form("2022_2023_sales") + end + + def optional_fields + [] + end + + def not_started? + status == "not_started" + end + + def completed? + status == "completed" + end + + private + + def update_status! + self.status = if all_fields_completed? && errors.empty? + "completed" + elsif all_fields_nil? + "not_started" + else + "in_progress" + end + end + + def all_fields_completed? + subsection_statuses = form.subsections.map { |subsection| subsection.status(self) }.uniq + subsection_statuses == [:completed] + end + + def all_fields_nil? + not_started_statuses = %i[not_started cannot_start_yet] + subsection_statuses = form.subsections.map { |subsection| subsection.status(self) }.uniq + subsection_statuses.all? { |status| not_started_statuses.include?(status) } + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 22cc14b77..4384bc495 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,6 +9,8 @@ class User < ApplicationRecord belongs_to :organisation, optional: true has_many :owned_lettings_logs, through: :organisation, dependent: :delete_all has_many :managed_lettings_logs, through: :organisation + has_many :owned_sales_logs, through: :organisation, dependent: :delete_all + has_many :managed_sales_logs, through: :organisation validates :name, presence: true validates :email, presence: true @@ -58,6 +60,14 @@ class User < ApplicationRecord end end + def sales_logs + if support? + SalesLog.all + else + SalesLog.filter_by_organisation(organisation) + end + end + def completed_lettings_logs lettings_logs.completed end diff --git a/app/views/form/_check_answers_summary_list.html.erb b/app/views/form/_check_answers_summary_list.html.erb index 85c29beca..68a9cf00a 100644 --- a/app/views/form/_check_answers_summary_list.html.erb +++ b/app/views/form/_check_answers_summary_list.html.erb @@ -1,21 +1,21 @@ <%= govuk_summary_list do |summary_list| %> - <% total_applicable_questions(subsection, @lettings_log, current_user).each do |question| %> + <% total_applicable_questions(subsection, @log, current_user).each do |question| %> <% summary_list.row do |row| %> <% row.key { question.check_answer_label.to_s.presence || question.header.to_s } %> <% row.value do %> - <%= get_answer_label(question, @lettings_log) %> - <% extra_value = question.get_extra_check_answer_value(@lettings_log) %> + <%= get_answer_label(question, @log) %> + <% extra_value = question.get_extra_check_answer_value(@log) %> <% if extra_value %> <%= extra_value %> <% end %>
- <% question.get_inferred_answers(@lettings_log).each do |inferred_answer| %> + <% question.get_inferred_answers(@log).each do |inferred_answer| %> <%= inferred_answer %> <% end %> <% end %> <% row.action( - text: question.action_text(@lettings_log), - href: question.action_href(@lettings_log, question.page.id), + text: question.action_text(@log), + href: question.action_href(@log, question.page.id), visually_hidden_text: question.check_answer_label.to_s.downcase, ) %> <% end %> diff --git a/app/views/form/_checkbox_question.html.erb b/app/views/form/_checkbox_question.html.erb index 245056c39..c855e2d88 100644 --- a/app/views/form/_checkbox_question.html.erb +++ b/app/views/form/_checkbox_question.html.erb @@ -6,7 +6,7 @@ hint: { text: question.hint_text&.html_safe } do %> <% after_divider = false %> - <% question.displayed_answer_options(@lettings_log).map do |key, options| %> + <% question.displayed_answer_options(@log).map do |key, options| %> <% if key.starts_with?("divider") %> <% after_divider = true %> <%= f.govuk_check_box_divider %> @@ -14,7 +14,7 @@ <%= f.govuk_check_box question.id, key, label: { text: options["value"] }, hint: { text: options["hint"] }, - checked: @lettings_log[key] == 1, + checked: @log[key] == 1, exclusive: after_divider, **stimulus_html_attributes(question) %> <% end %> diff --git a/app/views/form/_numeric_question.html.erb b/app/views/form/_numeric_question.html.erb index fc6aa274a..0aeb63801 100644 --- a/app/views/form/_numeric_question.html.erb +++ b/app/views/form/_numeric_question.html.erb @@ -8,7 +8,7 @@ width: question.width, readonly: question.read_only?, prefix_text: question.prefix.to_s, - suffix_text: question.suffix_label(@lettings_log), + suffix_text: question.suffix_label(@log), **stimulus_html_attributes(question) %> <%= render partial: "form/guidance/#{question.guidance_partial}" if question.bottom_guidance? %> diff --git a/app/views/form/_radio_question.html.erb b/app/views/form/_radio_question.html.erb index f153a838b..ed5e71a0b 100644 --- a/app/views/form/_radio_question.html.erb +++ b/app/views/form/_radio_question.html.erb @@ -5,7 +5,7 @@ legend: legend(question, page_header, conditional), hint: { text: question.hint_text&.html_safe } do %> - <% question.displayed_answer_options(@lettings_log).map do |key, options| %> + <% question.displayed_answer_options(@log).map do |key, options| %> <% if key.starts_with?("divider") %> <%= f.govuk_radio_divider %> <% else %> diff --git a/app/views/form/_select_question.html.erb b/app/views/form/_select_question.html.erb index fbdb20e9a..63f50fb94 100644 --- a/app/views/form/_select_question.html.erb +++ b/app/views/form/_select_question.html.erb @@ -1,7 +1,7 @@ <%= render partial: "form/guidance/#{question.guidance_partial}" if question.top_guidance? %> -<% selected = @lettings_log.public_send(question.id) || "" %> -<% answers = question.displayed_answer_options(@lettings_log).map { |key, value| OpenStruct.new(id: key, name: value.respond_to?(:service_name) ? value.service_name : nil, resource: value) } %> +<% selected = @log.public_send(question.id) || "" %> +<% answers = question.displayed_answer_options(@log).map { |key, value| OpenStruct.new(id: key, name: value.respond_to?(:service_name) ? value.service_name : nil, resource: value) } %> <%= f.govuk_select(question.id.to_sym, label: legend(question, page_header, conditional), "data-controller": "accessible-autocomplete", @@ -12,7 +12,7 @@ data-synonyms="<%= question.answer_option_synonyms(answer.resource) %>" data-append="<%= question.answer_option_append(answer.resource) %>" data-hint="<%= question.answer_option_hint(answer.resource) %>" - <%= question.answer_selected?(@lettings_log, answer) ? "selected" : "" %> + <%= question.answer_selected?(@log, answer) ? "selected" : "" %> <%= answer.id == "" ? "disabled" : "" %>><%= answer.name || answer.resource %> <% end %> <% end %> diff --git a/app/views/form/check_answers.html.erb b/app/views/form/check_answers.html.erb index a2bcb09bc..ec85104c5 100644 --- a/app/views/form/check_answers.html.erb +++ b/app/views/form/check_answers.html.erb @@ -1,7 +1,7 @@ <% content_for :title, "#{subsection.id.humanize} - Check your answers" %> <% content_for :breadcrumbs, govuk_breadcrumbs(breadcrumbs: { "Logs" => "/logs", - "Log #{@lettings_log.id}" => "/logs/#{@lettings_log.id}", + "Log #{@log.id}" => "/logs/#{@log.id}", subsection.label => "", }) %> @@ -12,27 +12,27 @@ Check your answers - <% if subsection.id == "setup" && subsection.status(@lettings_log) == :completed %> + <% if subsection.id == "setup" && subsection.status(@log) == :completed %> <%= govuk_inset_text(text: "Changing these answers might remove answers you’ve already given in other sections.") %> <% end %> - <%= display_answered_questions_summary(subsection, @lettings_log, current_user) %> + <%= display_answered_questions_summary(subsection, @log, current_user) %> - <% if any_questions_have_summary_card_number?(subsection, @lettings_log) %> - <% subsection.applicable_questions(@lettings_log).group_by(&:check_answers_card_number).values.each do |question_group| %> - <%= render CheckAnswersSummaryListCardComponent.new(questions: question_group, lettings_log: @lettings_log, user: current_user) %> + <% if any_questions_have_summary_card_number?(subsection, @log) %> + <% subsection.applicable_questions(@log).group_by(&:check_answers_card_number).values.each do |question_group| %> + <%= render CheckAnswersSummaryListCardComponent.new(questions: question_group, lettings_log: @log, user: current_user) %> <% end %> <% else %> <%= render partial: "form/check_answers_summary_list", locals: { subsection:, - lettings_log: @lettings_log, + lettings_log: @log, } %> <% end %> - <%= form_with model: @lettings_log, method: "get" do |f| %> + <%= form_with model: @log, method: "get" do |f| %> <%= f.govuk_submit "Save and return to log" do %> - <% next_incomplete_section_redirect_path = @lettings_log.form.next_incomplete_section_redirect_path(subsection, @lettings_log) %> - <% if @lettings_log.status == "in_progress" && next_incomplete_section_redirect_path != "error" %> - <%= govuk_button_link_to "Save and go to next incomplete section", "/logs/#{@lettings_log.id}/#{next_incomplete_section_redirect_path}", secondary: true %> + <% next_incomplete_section_redirect_path = @log.form.next_incomplete_section_redirect_path(subsection, @log) %> + <% if @log.status == "in_progress" && next_incomplete_section_redirect_path != "error" %> + <%= govuk_button_link_to "Save and go to next incomplete section", "/logs/#{@log.id}/#{next_incomplete_section_redirect_path}", secondary: true %> <% end %> <% end %> <% end %> diff --git a/app/views/form/page.html.erb b/app/views/form/page.html.erb index 0fe72f647..542f5fd51 100644 --- a/app/views/form/page.html.erb +++ b/app/views/form/page.html.erb @@ -5,10 +5,10 @@ <% end %>
-<%= form_with model: @lettings_log, url: form_lettings_log_path(@lettings_log), method: "post", local: true do |f| %> +<%= form_with model: @log, url: form_lettings_log_path(@log), method: "post", local: true do |f| %>
"> - <% remove_other_page_errors(@lettings_log, @page) %> + <% remove_other_page_errors(@log, @page) %> <%= f.govuk_error_summary %> <% if @page.header.present? %> @@ -30,9 +30,9 @@ <%= govuk_section_break(visible: true, size: "m") %> <% end %> <% if question.type == "interruption_screen" %> - <%= render partial: "form/#{question.type}_question", locals: { question:, caption_text: @subsection.label, page_header: @page.header, lettings_log: @lettings_log, title_text: @page.title_text, informative_text: @page.informative_text, form: @form, f:, conditional: false } %> + <%= render partial: "form/#{question.type}_question", locals: { question:, caption_text: @subsection.label, page_header: @page.header, lettings_log: @log, title_text: @page.title_text, informative_text: @page.informative_text, form: @form, f:, conditional: false } %> <% else %> - <%= render partial: "form/#{question.type}_question", locals: { question:, caption_text: @subsection.label, page_header: @page.header, lettings_log: @lettings_log, f:, conditional: false } %> + <%= render partial: "form/#{question.type}_question", locals: { question:, caption_text: @subsection.label, page_header: @page.header, lettings_log: @log, f:, conditional: false } %> <% end %>
<% end %> @@ -42,10 +42,10 @@
<% if !@page.id.include?("value_check") && if request.query_parameters["referrer"] != "check_answers" %> <%= f.govuk_submit "Save and continue" %> - <%= govuk_link_to "Skip for now", send(@lettings_log.form.next_page_redirect_path(@page, @lettings_log, current_user), @lettings_log) %> + <%= govuk_link_to "Skip for now", send(@log.form.next_page_redirect_path(@page, @log, current_user), @log) %> <% else %> <%= f.govuk_submit "Save changes" %> - <%= govuk_link_to "Cancel", "/logs/#{@lettings_log.id}/setup/check-answers" %> + <%= govuk_link_to "Cancel", "/logs/#{@log.id}/setup/check-answers" %> <% end %> <% end %>
diff --git a/app/views/form/review.html.erb b/app/views/form/review.html.erb index 55a693913..59e0bde40 100644 --- a/app/views/form/review.html.erb +++ b/app/views/form/review.html.erb @@ -1,7 +1,7 @@ <% content_for :title, "Review lettings log" %> <% content_for :breadcrumbs, govuk_breadcrumbs(breadcrumbs: { "Logs" => "/logs", - "Log #{@lettings_log.id}" => "/logs/#{@lettings_log.id}", + "Log #{@log.id}" => "/logs/#{@log.id}", "Review lettings log" => "", }) %> @@ -11,9 +11,9 @@ <%= content_for(:title) %>

- You can review and make changes to this log until 2nd June <%= @lettings_log.collection_start_year.present? ? @lettings_log.collection_start_year + 1 : "" %>. + You can review and make changes to this log until 2nd June <%= @log.collection_start_year.present? ? @log.collection_start_year + 1 : "" %>.

- <% @lettings_log.form.sections.map do |section| %> + <% @log.form.sections.map do |section| %>

<%= section.label %>

<% section.subsections.map do |subsection| %>
@@ -23,7 +23,7 @@
<%= render partial: "form/check_answers_summary_list", locals: { subsection:, - lettings_log: @lettings_log, + lettings_log: @log, } %>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 5dc643e4e..c868337ff 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,7 +1,7 @@ - <%= browser_title(yield(:title), @pagy, @admin_user, @user, @organisation, @lettings_log, @resource) %> + <%= browser_title(yield(:title), @pagy, @admin_user, @user, @organisation, @log, @resource) %> <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= tag.meta name: "viewport", content: "width=device-width, initial-scale=1" %> diff --git a/app/views/lettings_logs/index.html.erb b/app/views/lettings_logs/index.html.erb index 9378aee09..d1c7511f3 100644 --- a/app/views/lettings_logs/index.html.erb +++ b/app/views/lettings_logs/index.html.erb @@ -8,6 +8,7 @@
<%= govuk_button_to "Create a new lettings log", lettings_logs_path %> + <%= govuk_button_to "Create a new sales log", sales_logs_path %> <%#= govuk_link_to "Upload logs", bulk_upload_lettings_logs_path %>
diff --git a/app/views/sales_logs/_tasklist.html.erb b/app/views/sales_logs/_tasklist.html.erb new file mode 100644 index 000000000..e635d8241 --- /dev/null +++ b/app/views/sales_logs/_tasklist.html.erb @@ -0,0 +1,25 @@ +
    + <% @sales_log.form.sections.map do |section| %> +
  1. +

    + <%= section.label %> +

    + <% if section.description %> +

    <%= section.description.html_safe %>

    + <% end %> +
      + <% section.subsections.map do |subsection| %> + <% if subsection.applicable_questions(@sales_log).count > 0 || !subsection.enabled?(@sales_log) %> + <% subsection_status = subsection.status(@sales_log) %> +
    • + + <%= subsection_link(subsection, @sales_log, current_user) %> + + <%= status_tag(subsection_status, "app-task-list__tag") %> +
    • + <% end %> + <% end %> +
    +
  2. + <% end %> +
diff --git a/app/views/sales_logs/edit.html.erb b/app/views/sales_logs/edit.html.erb new file mode 100644 index 000000000..e444dd0e8 --- /dev/null +++ b/app/views/sales_logs/edit.html.erb @@ -0,0 +1,37 @@ +<% content_for :title, "Log #{@sales_log.id}" %> +<% content_for :breadcrumbs, govuk_breadcrumbs(breadcrumbs: { + "Logs" => "/logs", + content_for(:title) => "", +}) %> + +
+
+

+ <%= content_for(:title) %> +

+ + <% if @sales_log.status == "in_progress" %> +

<%= get_subsections_count(@sales_log, :completed) %> of <%= get_subsections_count(@sales_log, :all) %> sections completed.

+

+ <% next_incomplete_section = get_next_incomplete_section(@sales_log) %> +

+

+ <% if next_incomplete_section.present? %> + + Skip to next incomplete section: <%= next_incomplete_section.label %> + + <% end %> +

+ <% elsif @sales_log.status == "not_started" %> +

This log has not been started.

+ <% elsif @sales_log.status == "completed" %> +

+ <%= status_tag(@sales_log.status) %> +

+

+ You can <%= govuk_link_to "review and make changes to this log", "/logs/#{@sales_log.id}/review" %> until 2nd June <%= @sales_log.collection_start_year.present? ? @sales_log.collection_start_year + 1 : "" %>. +

+ <% end %> + <%= render "tasklist" %> +
+
diff --git a/config/routes.rb b/config/routes.rb index 5a2fda475..5a3cdb43f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -84,9 +84,21 @@ Rails.application.routes.draw do get "review", to: "form#review" end - FormHandler.instance.forms.each do |_key, form| + FormHandler.instance.lettings_forms.each do |_key, form| form.pages.map do |page| - get page.id.to_s.dasherize, to: "form##{page.id}" + get page.id.to_s.dasherize, to: "form#show_page" + end + + form.subsections.map do |subsection| + get "#{subsection.id.to_s.dasherize}/check-answers", to: "form#check_answers" + end + end + end + + resources :sales_logs, path: "/sales-logs" do + FormHandler.instance.sales_forms.each do |_key, form| + form.pages.map do |page| + get page.id.to_s.dasherize, to: "form#show_page" end form.subsections.map do |subsection| diff --git a/db/migrate/20220826093411_add_sales_log.rb b/db/migrate/20220826093411_add_sales_log.rb new file mode 100644 index 000000000..549f1b6cd --- /dev/null +++ b/db/migrate/20220826093411_add_sales_log.rb @@ -0,0 +1,12 @@ +class AddSalesLog < ActiveRecord::Migration[7.0] + def change + create_table :sales_logs do |t| + t.integer :status, default: 0 + t.datetime :saledate + t.timestamps + t.references :owning_organisation, class_name: "Organisation", foreign_key: { to_table: :organisations, on_delete: :cascade } + t.references :managing_organisation, class_name: "Organisation", foreign_key: { to_table: :organisations } + t.references :created_by, class_name: "User", foreign_key: { to_table: :users } + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 23db463fc..dbdad5c31 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -313,6 +313,19 @@ ActiveRecord::Schema[7.0].define(version: 2022_09_02_082245) do t.index ["old_visible_id"], name: "index_organisations_on_old_visible_id", unique: true end + create_table "sales_logs", force: :cascade do |t| + t.integer "status", default: 0 + t.datetime "saledate" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.bigint "owning_organisation_id" + t.bigint "managing_organisation_id" + t.bigint "created_by_id" + t.index ["created_by_id"], name: "index_sales_logs_on_created_by_id" + t.index ["managing_organisation_id"], name: "index_sales_logs_on_managing_organisation_id" + t.index ["owning_organisation_id"], name: "index_sales_logs_on_owning_organisation_id" + end + create_table "schemes", force: :cascade do |t| t.string "service_name" t.bigint "owning_organisation_id", null: false @@ -395,6 +408,9 @@ ActiveRecord::Schema[7.0].define(version: 2022_09_02_082245) do add_foreign_key "lettings_logs", "organisations", column: "owning_organisation_id", on_delete: :cascade add_foreign_key "lettings_logs", "schemes" add_foreign_key "locations", "schemes" + add_foreign_key "sales_logs", "organisations", column: "managing_organisation_id" + add_foreign_key "sales_logs", "organisations", column: "owning_organisation_id", on_delete: :cascade + add_foreign_key "sales_logs", "users", column: "created_by_id" add_foreign_key "schemes", "organisations", column: "managing_organisation_id" add_foreign_key "schemes", "organisations", column: "owning_organisation_id", on_delete: :cascade add_foreign_key "users", "organisations", on_delete: :cascade diff --git a/spec/factories/sales_log.rb b/spec/factories/sales_log.rb new file mode 100644 index 000000000..bacb8e3e6 --- /dev/null +++ b/spec/factories/sales_log.rb @@ -0,0 +1,12 @@ +FactoryBot.define do + factory :sales_log do + created_by { FactoryBot.create(:user) } + owning_organisation { created_by.organisation } + managing_organisation { created_by.organisation } + created_at { Time.utc(2022, 2, 8, 16, 52, 15) } + updated_at { Time.utc(2022, 2, 8, 16, 52, 15) } + trait :completed do + saledate { Time.utc(2022, 2, 2, 10, 36, 49) } + end + end +end diff --git a/spec/features/form/validations_spec.rb b/spec/features/form/validations_spec.rb index d944f5161..30a69b989 100644 --- a/spec/features/form/validations_spec.rb +++ b/spec/features/form/validations_spec.rb @@ -37,7 +37,7 @@ RSpec.describe "validations" do describe "Question validation" do context "when the tenant age is invalid" do - it "shows validation for under 0" do + it "shows validation for under 0", js: true do visit("/logs/#{id}/person-1-age") fill_in_number_question(empty_lettings_log.id, "age1", -5, "person-1-age") expect(page).to have_selector("#error-summary-title") diff --git a/spec/features/log_spec.rb b/spec/features/log_spec.rb index cef344145..2ae3d74d7 100644 --- a/spec/features/log_spec.rb +++ b/spec/features/log_spec.rb @@ -77,7 +77,7 @@ RSpec.describe "Log Features" do click_button("Submit") end - context "when completing the setup log section" do + context "when completing the setup lettings log section" do it "includes the organisation and created by questions" do visit("/logs") click_button("Create a new lettings log") @@ -93,6 +93,22 @@ RSpec.describe "Log Features" do expect(page).to have_content("You have answered 2 of 8 questions") end end + + context "when completing the setup sales log section" do + it "includes the sale completion date question", js: true do + visit("/logs") + click_button("Create a new sales log") + click_link("Set up this sales log") + fill_in("sales_log[saledate(1i)]", with: "2022") + fill_in("sales_log[saledate(2i)]", with: "08") + fill_in("sales_log[saledate(3i)]", with: "10") + click_button("Save and continue") + log_id = page.current_path.scan(/\d/).join + visit("sales-logs/#{log_id}/setup/check-answers") + expect(page).to have_content("Sale completion date") + expect(page).to have_content("2022") + end + end end context "when the signed is user is not a Support user" do @@ -123,5 +139,21 @@ RSpec.describe "Log Features" do expect(page).to have_content("You have answered 0 of 6 questions") end end + + context "when completing the setup sales log section" do + it "includes the sale completion date question", js: true do + visit("/logs") + click_button("Create a new sales log") + click_link("Set up this sales log") + fill_in("sales_log[saledate(1i)]", with: "2022") + fill_in("sales_log[saledate(2i)]", with: "08") + fill_in("sales_log[saledate(3i)]", with: "10") + click_button("Save and continue") + log_id = page.current_path.scan(/\d/).join + visit("sales-logs/#{log_id}/setup/check-answers") + expect(page).to have_content("Sale completion date") + expect(page).to have_content("2022") + end + end end end diff --git a/spec/models/form/sales/setup/pages/sale_date_spec.rb b/spec/models/form/sales/setup/pages/sale_date_spec.rb new file mode 100644 index 000000000..56f6a6953 --- /dev/null +++ b/spec/models/form/sales/setup/pages/sale_date_spec.rb @@ -0,0 +1,29 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Setup::Pages::SaleDate, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[saledate]) + end + + it "has the correct id" do + expect(page.id).to eq("sale_date") + end + + it "has the correct header" do + expect(page.header).to eq("") + end + + it "has the correct description" do + expect(page.description).to eq("") + end +end diff --git a/spec/models/form/sales/setup/questions/sale_date_spec.rb b/spec/models/form/sales/setup/questions/sale_date_spec.rb new file mode 100644 index 000000000..c340ea0c7 --- /dev/null +++ b/spec/models/form/sales/setup/questions/sale_date_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Setup::Questions::SaleDate, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page) } + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("saledate") + end + + it "has the correct header" do + expect(question.header).to eq("What is the sale completion date?") + end + + it "has the correct check_answer_label" do + expect(question.check_answer_label).to eq("Sale completion date") + end + + it "has the correct type" do + expect(question.type).to eq("date") + end + + it "is not marked as derived" do + expect(question.derived?).to be false + end +end diff --git a/spec/models/form/sales/setup/sections/setup_spec.rb b/spec/models/form/sales/setup/sections/setup_spec.rb new file mode 100644 index 000000000..7bfab8cd1 --- /dev/null +++ b/spec/models/form/sales/setup/sections/setup_spec.rb @@ -0,0 +1,29 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Setup::Sections::Setup, type: :model do + subject(:setup) { described_class.new(section_id, section_definition, form) } + + let(:section_id) { nil } + let(:section_definition) { nil } + let(:form) { instance_double(Form) } + + it "has correct form" do + expect(setup.form).to eq(form) + end + + it "has correct subsections" do + expect(setup.subsections.map(&:id)).to eq(%w[setup]) + end + + it "has the correct id" do + expect(setup.id).to eq("setup") + end + + it "has the correct label" do + expect(setup.label).to eq("Before you start") + end + + it "has the correct description" do + expect(setup.description).to eq("") + end +end diff --git a/spec/models/form/sales/setup/subsections/setup_spec.rb b/spec/models/form/sales/setup/subsections/setup_spec.rb new file mode 100644 index 000000000..d2e1bedce --- /dev/null +++ b/spec/models/form/sales/setup/subsections/setup_spec.rb @@ -0,0 +1,27 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Setup::Subsections::Setup, type: :model do + subject(:setup) { described_class.new(subsection_id, subsection_definition, section) } + + let(:subsection_id) { nil } + let(:subsection_definition) { nil } + let(:section) { instance_double(Form::Sales::Setup::Sections::Setup) } + + it "has correct section" do + expect(setup.section).to eq(section) + end + + it "has correct pages" do + expect(setup.pages.map(&:id)).to eq( + %w[sale_date], + ) + end + + it "has the correct id" do + expect(setup.id).to eq("setup") + end + + it "has the correct label" do + expect(setup.label).to eq("Set up this sales log") + end +end diff --git a/spec/models/form_handler_spec.rb b/spec/models/form_handler_spec.rb index 94a3af2fe..4e6a61913 100644 --- a/spec/models/form_handler_spec.rb +++ b/spec/models/form_handler_spec.rb @@ -35,4 +35,9 @@ RSpec.describe FormHandler do expect(Form).not_to receive(:new).with(:any, test_form_name) expect(form_handler.get_form(test_form_name)).to be_a(Form) end + + it "can get a saleslog form" do + form_handler = described_class.instance + expect(form_handler.get_form("2022_2023_sales")).to be_a(Form) + end end diff --git a/spec/models/form_spec.rb b/spec/models/form_spec.rb index 7f1d4cb81..14297fcd4 100644 --- a/spec/models/form_spec.rb +++ b/spec/models/form_spec.rb @@ -205,4 +205,25 @@ RSpec.describe Form, type: :model do end end end + + describe "when creating a sales log" do + it "creates a valid sales form" do + sections = [Form::Sales::Setup::Sections::Setup] + form = described_class.new(nil, "2022_23_sales", sections, "sales") + expect(form.type).to eq("sales") + expect(form.name).to eq("2022_23_sales") + expect(form.form_sections.count).to eq(1) + expect(form.form_sections[0].class).to eq(sections[0]) + expect(form.sections.count).to eq(1) + expect(form.sections[0].class).to eq(sections[0]) + expect(form.subsections.count).to eq(1) + expect(form.subsections.first.id).to eq("setup") + expect(form.pages.count).to eq(1) + expect(form.pages.first.id).to eq("sale_date") + expect(form.questions.count).to eq(1) + expect(form.questions.first.id).to eq("saledate") + expect(form.start_date).to eq(Time.zone.parse("2022-04-01")) + expect(form.end_date).to eq(Time.zone.parse("2023-04-01")) + end + end end diff --git a/spec/models/lettings_log_spec.rb b/spec/models/lettings_log_spec.rb index 772255b19..e2d78e60c 100644 --- a/spec/models/lettings_log_spec.rb +++ b/spec/models/lettings_log_spec.rb @@ -5,6 +5,11 @@ RSpec.describe LettingsLog do let(:different_managing_organisation) { FactoryBot.create(:organisation) } let(:created_by_user) { FactoryBot.create(:user) } + it "inherits from log" do + expect(described_class).to be < Log + expect(described_class).to be < ApplicationRecord + end + describe "#form" do let(:lettings_log) { FactoryBot.build(:lettings_log, created_by: created_by_user) } let(:lettings_log_2) { FactoryBot.build(:lettings_log, startdate: Time.zone.local(2022, 1, 1), created_by: created_by_user) } diff --git a/spec/models/log_spec.rb b/spec/models/log_spec.rb new file mode 100644 index 000000000..0cba24086 --- /dev/null +++ b/spec/models/log_spec.rb @@ -0,0 +1,8 @@ +require "rails_helper" + +RSpec.describe Log, type: :model do + it "has two child log classes" do + expect(SalesLog).to be < described_class + expect(LettingsLog).to be < described_class + end +end diff --git a/spec/models/sales_log_spec.rb b/spec/models/sales_log_spec.rb new file mode 100644 index 000000000..39f694cd8 --- /dev/null +++ b/spec/models/sales_log_spec.rb @@ -0,0 +1,85 @@ +require "rails_helper" + +RSpec.describe SalesLog, type: :model do + let(:owning_organisation) { FactoryBot.create(:organisation) } + let(:created_by_user) { FactoryBot.create(:user) } + + it "inherits from log" do + expect(described_class).to be < Log + expect(described_class).to be < ApplicationRecord + end + + describe "#new" do + context "when creating a record" do + let(:sales_log) do + described_class.create + end + + it "attaches the correct custom validator" do + expect(sales_log._validators.values.flatten.map(&:class)) + .to include(SalesLogValidator) + end + end + end + + describe "#form" do + let(:sales_log) { FactoryBot.build(:sales_log, created_by: created_by_user) } + let(:sales_log_2) { FactoryBot.build(:sales_log, saledate: Time.zone.local(2022, 5, 1), created_by: created_by_user) } + + it "has returns the correct form based on the start date" do + expect(sales_log.form_name).to be_nil + expect(sales_log.form).to be_a(Form) + expect(sales_log_2.form_name).to eq("2022_2023_sales") + expect(sales_log_2.form).to be_a(Form) + end + end + + describe "status" do + let!(:empty_sales_log) { FactoryBot.create(:sales_log) } + # let!(:in_progress_sales_log) { FactoryBot.create(:sales_log, :in_progress) } + let!(:completed_sales_log) { FactoryBot.create(:sales_log, :completed) } + + it "is set to not started for an empty sales log" do + expect(empty_sales_log.not_started?).to be(true) + # expect(empty_sales_log.in_progress?).to be(false) + expect(empty_sales_log.completed?).to be(false) + end + + # it "is set to in progress for a started sales log" do + # expect(in_progress_sales_log.in_progress?).to be(true) + # expect(in_progress_sales_log.not_started?).to be(false) + # expect(in_progress_sales_log.completed?).to be(false) + # end + + it "is set to completed for a completed sales log" do + # expect(completed_sales_log.in_progress?).to be(false) + expect(completed_sales_log.not_started?).to be(false) + expect(completed_sales_log.completed?).to be(true) + end + end + + context "when filtering by organisation" do + let(:organisation_1) { FactoryBot.create(:organisation) } + let(:organisation_2) { FactoryBot.create(:organisation) } + let(:organisation_3) { FactoryBot.create(:organisation) } + + before do + FactoryBot.create(:sales_log, :in_progress, owning_organisation: organisation_1, managing_organisation: organisation_1) + FactoryBot.create(:sales_log, :completed, owning_organisation: organisation_1, managing_organisation: organisation_2) + FactoryBot.create(:sales_log, :completed, owning_organisation: organisation_2, managing_organisation: organisation_1) + FactoryBot.create(:sales_log, :completed, owning_organisation: organisation_2, managing_organisation: organisation_2) + end + + it "filters by given organisation id" do + expect(described_class.filter_by_organisation([organisation_1.id]).count).to eq(3) + expect(described_class.filter_by_organisation([organisation_1.id, organisation_2.id]).count).to eq(4) + expect(described_class.filter_by_organisation([organisation_3.id]).count).to eq(0) + end + + it "filters by given organisation" do + expect(described_class.filter_by_organisation([organisation_1]).count).to eq(3) + expect(described_class.filter_by_organisation([organisation_1, organisation_2]).count).to eq(4) + expect(described_class.filter_by_organisation([organisation_3]).count).to eq(0) + end + end +end diff --git a/spec/requests/sales_logs_controller_spec.rb b/spec/requests/sales_logs_controller_spec.rb new file mode 100644 index 000000000..6fc6337ab --- /dev/null +++ b/spec/requests/sales_logs_controller_spec.rb @@ -0,0 +1,87 @@ +require "rails_helper" + +RSpec.describe SalesLogsController, type: :request do + let(:user) { FactoryBot.create(:user) } + let(:owning_organisation) { user.organisation } + let(:managing_organisation) { owning_organisation } + let(:api_username) { "test_user" } + let(:api_password) { "test_password" } + let(:basic_credentials) do + ActionController::HttpAuthentication::Basic + .encode_credentials(api_username, api_password) + end + + let(:params) do + { + "owning_organisation_id": owning_organisation.id, + "managing_organisation_id": managing_organisation.id, + "created_by_id": user.id, + } + end + + let(:headers) do + { + "Content-Type" => "application/json", + "Accept" => "application/json", + "Authorization" => basic_credentials, + } + end + + before do + allow(ENV).to receive(:[]) + allow(ENV).to receive(:[]).with("API_USER").and_return(api_username) + allow(ENV).to receive(:[]).with("API_KEY").and_return(api_password) + end + + describe "POST #create" do + context "when API" do + before do + post "/sales-logs", headers:, params: params.to_json + end + + it "returns http success" do + expect(response).to have_http_status(:success) + end + + it "returns a serialized sales log" do + json_response = JSON.parse(response.body) + expect(json_response.keys).to match_array(SalesLog.new.attributes.keys) + end + + it "creates a sales log with the values passed" do + json_response = JSON.parse(response.body) + expect(json_response["owning_organisation_id"]).to eq(owning_organisation.id) + expect(json_response["managing_organisation_id"]).to eq(managing_organisation.id) + expect(json_response["created_by_id"]).to eq(user.id) + end + + context "with a request containing 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 + + context "when UI" do + let(:user) { FactoryBot.create(:user) } + let(:headers) { { "Accept" => "text/html" } } + + before do + RequestHelper.stub_http_requests + sign_in user + post "/sales-logs", headers: + end + + it "tracks who created the record" do + created_id = response.location.match(/[0-9]+/)[0] + whodunnit_actor = SalesLog.find_by(id: created_id).versions.last.actor + expect(whodunnit_actor).to be_a(User) + expect(whodunnit_actor.id).to eq(user.id) + end + end + end +end diff --git a/spec/views/form/page_view_spec.rb b/spec/views/form/page_view_spec.rb index 2cedf6eba..a7039a9a9 100644 --- a/spec/views/form/page_view_spec.rb +++ b/spec/views/form/page_view_spec.rb @@ -18,7 +18,7 @@ RSpec.describe "form/page" do end before do - assign(:lettings_log, lettings_log) + assign(:log, lettings_log) assign(:page, page) assign(:subsection, subsection) assign_attributes(page, page_attributes)