Browse Source

Add abstract log class and sales log class

Created a parent log class for sales log and lettings log. Any bits common
to both sales and lettings can live in the parent class. As the sales log
functionality is built up any commonalities with lettings log can be extracted
into the parent log class. The sales log model is set up without a json form
and instead the form is defined in code - like the setup section of the lettings
log.
pull/863/head
Kat 3 years ago committed by baarkerlounger
parent
commit
5ece7f98e6
  1. 95
      app/controllers/form_controller.rb
  2. 60
      app/controllers/lettings_logs_controller.rb
  3. 64
      app/controllers/logs_controller.rb
  4. 28
      app/controllers/sales_logs_controller.rb
  5. 10
      app/helpers/tasklist_helper.rb
  6. 66
      app/models/form.rb
  7. 15
      app/models/form/sales/setup/pages/sale_date.rb
  8. 10
      app/models/form/sales/setup/questions/sale_date.rb
  9. 10
      app/models/form/sales/setup/sections/setup.rb
  10. 14
      app/models/form/sales/setup/subsections/setup.rb
  11. 15
      app/models/form_handler.rb
  12. 5
      app/models/lettings_log.rb
  13. 7
      app/models/log.rb
  14. 72
      app/models/sales_log.rb
  15. 10
      app/models/user.rb
  16. 12
      app/views/form/_check_answers_summary_list.html.erb
  17. 4
      app/views/form/_checkbox_question.html.erb
  18. 2
      app/views/form/_numeric_question.html.erb
  19. 2
      app/views/form/_radio_question.html.erb
  20. 6
      app/views/form/_select_question.html.erb
  21. 22
      app/views/form/check_answers.html.erb
  22. 12
      app/views/form/page.html.erb
  23. 8
      app/views/form/review.html.erb
  24. 2
      app/views/layouts/application.html.erb
  25. 1
      app/views/lettings_logs/index.html.erb
  26. 25
      app/views/sales_logs/_tasklist.html.erb
  27. 37
      app/views/sales_logs/edit.html.erb
  28. 16
      config/routes.rb
  29. 12
      db/migrate/20220826093411_add_sales_log.rb
  30. 16
      db/schema.rb
  31. 12
      spec/factories/sales_log.rb
  32. 2
      spec/features/form/validations_spec.rb
  33. 34
      spec/features/log_spec.rb
  34. 29
      spec/models/form/sales/setup/pages/sale_date_spec.rb
  35. 33
      spec/models/form/sales/setup/questions/sale_date_spec.rb
  36. 29
      spec/models/form/sales/setup/sections/setup_spec.rb
  37. 27
      spec/models/form/sales/setup/subsections/setup_spec.rb
  38. 5
      spec/models/form_handler_spec.rb
  39. 21
      spec/models/form_spec.rb
  40. 5
      spec/models/lettings_log_spec.rb
  41. 8
      spec/models/log_spec.rb
  42. 85
      spec/models/sales_log_spec.rb
  43. 87
      spec/requests/sales_logs_controller_spec.rb
  44. 2
      spec/views/form/page_view_spec.rb

95
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

60
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

64
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

28
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

10
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)

66
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

15
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

10
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

10
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

14
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

15
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

5
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

7
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

72
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

10
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

12
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 %>
<span class="govuk-!-margin-right-4"><%= get_answer_label(question, @lettings_log) %></span>
<% extra_value = question.get_extra_check_answer_value(@lettings_log) %>
<span class="govuk-!-margin-right-4"><%= get_answer_label(question, @log) %></span>
<% extra_value = question.get_extra_check_answer_value(@log) %>
<% if extra_value %>
<span class="govuk-!-font-weight-regular app-!-colour-muted"><%= extra_value %></span>
<% end %>
<br>
<% question.get_inferred_answers(@lettings_log).each do |inferred_answer| %>
<% question.get_inferred_answers(@log).each do |inferred_answer| %>
<span class="govuk-!-font-weight-regular app-!-colour-muted"><%= inferred_answer %></span>
<% 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 %>

4
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 %>

2
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? %>

2
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 %>

6
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 %></option>
<% end %>
<% end %>

22
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
</h1>
<% 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 %>

12
app/views/form/page.html.erb

@ -5,10 +5,10 @@
<% end %>
<div data-controller="govukfrontend"></div>
<%= 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| %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-<%= @page.questions[0].type == "interruption_screen" ? "full-from-desktop" : "two-thirds-from-desktop" %>">
<% 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 %>
</div>
<% end %>
@ -42,10 +42,10 @@
<div class="govuk-button-group">
<% 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 %>
</div>

8
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) %>
</h1>
<p class="govuk-body">
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 : "" %>.
</p>
<% @lettings_log.form.sections.map do |section| %>
<% @log.form.sections.map do |section| %>
<h2 class="govuk-heading-m"><%= section.label %></h2>
<% section.subsections.map do |subsection| %>
<div class="x-govuk-summary-card govuk-!-margin-bottom-6">
@ -23,7 +23,7 @@
<div class="x-govuk-summary-card__body">
<%= render partial: "form/check_answers_summary_list", locals: {
subsection:,
lettings_log: @lettings_log,
lettings_log: @log,
} %>
</div>
</div>

2
app/views/layouts/application.html.erb

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en" class="govuk-template">
<head>
<title><%= browser_title(yield(:title), @pagy, @admin_user, @user, @organisation, @lettings_log, @resource) %></title>
<title><%= browser_title(yield(:title), @pagy, @admin_user, @user, @organisation, @log, @resource) %></title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= tag.meta name: "viewport", content: "width=device-width, initial-scale=1" %>

1
app/views/lettings_logs/index.html.erb

@ -8,6 +8,7 @@
<div class="app-filter-layout" data-controller="filter-layout">
<div class="govuk-button-group app-filter-toggle">
<%= 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 %>
</div>

25
app/views/sales_logs/_tasklist.html.erb

@ -0,0 +1,25 @@
<ol class="app-task-list govuk-!-margin-top-8">
<% @sales_log.form.sections.map do |section| %>
<li>
<h2 class="app-task-list__section-heading">
<%= section.label %>
</h2>
<% if section.description %>
<p class="govuk-body"><%= section.description.html_safe %></p>
<% end %>
<ul class="app-task-list__items">
<% section.subsections.map do |subsection| %>
<% if subsection.applicable_questions(@sales_log).count > 0 || !subsection.enabled?(@sales_log) %>
<% subsection_status = subsection.status(@sales_log) %>
<li class="app-task-list__item">
<span class="app-task-list__task-name" id="<%= subsection.id.dasherize %>">
<%= subsection_link(subsection, @sales_log, current_user) %>
</span>
<%= status_tag(subsection_status, "app-task-list__tag") %>
</li>
<% end %>
<% end %>
</ul>
</li>
<% end %>
</ol>

37
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) => "",
}) %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<h1 class="govuk-heading-xl">
<%= content_for(:title) %>
</h1>
<% if @sales_log.status == "in_progress" %>
<p class="govuk-body govuk-!-margin-bottom-7"><%= get_subsections_count(@sales_log, :completed) %> of <%= get_subsections_count(@sales_log, :all) %> sections completed.</p>
<p class="govuk-body govuk-!-margin-bottom-2">
<% next_incomplete_section = get_next_incomplete_section(@sales_log) %>
</p>
<p>
<% if next_incomplete_section.present? %>
<a class="app-section-skip-link" href="#<%= next_incomplete_section.id.dasherize %>">
Skip to next incomplete section: <%= next_incomplete_section.label %>
</a>
<% end %>
</p>
<% elsif @sales_log.status == "not_started" %>
<p class="govuk-body">This log has not been started.</p>
<% elsif @sales_log.status == "completed" %>
<p class="govuk-body">
<%= status_tag(@sales_log.status) %>
</p>
<p class="govuk-body">
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 : "" %>.
</p>
<% end %>
<%= render "tasklist" %>
</div>
</div>

16
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|

12
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

16
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

12
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

2
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")

34
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

29
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

33
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

29
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

27
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

5
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

21
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

5
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) }

8
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

85
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

87
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

2
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)

Loading…
Cancel
Save