diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 997e95390..0f1918fe4 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -72,6 +72,7 @@ Rails/BulkChangeTable: - 'db/migrate/20221014140003_add_more_trust_fields_to_support_organisations.rb' - 'db/migrate/20230210133230_refactor_notifications_into_one_table.rb' - 'db/migrate/20230919092926_update_lot_frameworks_framework.rb' + - 'db/migrate/20230919144709_add_reference_sequence_to_frameworks_framework.rb' # Offense count: 2 # Configuration parameters: Include. diff --git a/Gemfile b/Gemfile index 4bdb768f9..f3b49363f 100644 --- a/Gemfile +++ b/Gemfile @@ -6,6 +6,7 @@ ruby "3.2.1" gem "aasm" gem "application_insights" +gem "ar-sequence" gem "aws-sdk-s3", require: false # # awaiting PR merge here: https://github.com/Azure/azure-storage-ruby/pull/228 due to need for faraday 2 diff --git a/Gemfile.lock b/Gemfile.lock index ccf1e8bc2..db4a0db6b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -87,6 +87,8 @@ GEM public_suffix (>= 2.0.2, < 6.0) aes_key_wrap (1.1.0) application_insights (0.5.6) + ar-sequence (0.2.1) + activerecord ast (2.4.2) attr_required (1.0.1) aws-eventstream (1.2.0) @@ -668,6 +670,7 @@ PLATFORMS DEPENDENCIES aasm application_insights + ar-sequence aws-sdk-s3 azure-storage-blob! better_errors diff --git a/app/assets/stylesheets/base/_misc.scss b/app/assets/stylesheets/base/_misc.scss index 709dd8c77..6a6ef05f7 100644 --- a/app/assets/stylesheets/base/_misc.scss +++ b/app/assets/stylesheets/base/_misc.scss @@ -1,6 +1,11 @@ .flex-align-center { display: flex; align-items: center; + gap: 10px; + + .govuk-body { + margin-bottom: 0; + } } .pull-up-10 { diff --git a/app/controllers/frameworks/evaluation_contacts_controller.rb b/app/controllers/frameworks/evaluation_contacts_controller.rb new file mode 100644 index 000000000..1ea0bd09f --- /dev/null +++ b/app/controllers/frameworks/evaluation_contacts_controller.rb @@ -0,0 +1,2 @@ +class Frameworks::EvaluationContactsController < Frameworks::ApplicationController +end diff --git a/app/controllers/frameworks/evaluations_controller.rb b/app/controllers/frameworks/evaluations_controller.rb new file mode 100644 index 000000000..db0620426 --- /dev/null +++ b/app/controllers/frameworks/evaluations_controller.rb @@ -0,0 +1,50 @@ +class Frameworks::EvaluationsController < Frameworks::ApplicationController + before_action :redirect_to_register_tab, unless: :turbo_frame_request?, only: :index + before_action :set_form_options, only: %i[new create] + + def index + @filtering = Frameworks::Evaluation.filtering(filter_form_params) + @evaluations = @filtering.results.paginate(page: params[:evaluations_page]) + end + + def show + @evaluation = Frameworks::Evaluation.find(params[:id]) + @activity_log_items = @evaluation.activity_log_items.paginate(page: params[:history_page]) + end + + def new + @evaluation = Frameworks::Evaluation.new(framework_id: params[:framework_id]) + end + + def create + @evaluation = Frameworks::Evaluation.new(evaluation_params) + + if @evaluation.save + redirect_to @evaluation + else + render :new + end + end + +private + + def set_form_options + @frameworks = Frameworks::Framework.for_evaluation + @agents = Support::Agent.framework_evaluators + end + + def filter_form_params + params.fetch(:evaluations_filter, {}).permit( + :sort_by, :sort_order, + status: [] + ) + end + + def evaluation_params + params.require(:frameworks_evaluation).permit(:framework_id, :assignee_id) + end + + def redirect_to_register_tab + redirect_to frameworks_root_path(anchor: "evaluations", **request.params.except(:controller, :action)) + end +end diff --git a/app/controllers/frameworks/categorisations_controller.rb b/app/controllers/frameworks/framework_categorisations_controller.rb similarity index 66% rename from app/controllers/frameworks/categorisations_controller.rb rename to app/controllers/frameworks/framework_categorisations_controller.rb index 69b8ac11e..da494859e 100644 --- a/app/controllers/frameworks/categorisations_controller.rb +++ b/app/controllers/frameworks/framework_categorisations_controller.rb @@ -1,4 +1,4 @@ -class Frameworks::CategorisationsController < Frameworks::ApplicationController +class Frameworks::FrameworkCategorisationsController < Frameworks::ApplicationController def edit @framework = Frameworks::Framework.find(params[:framework_id]) end @@ -8,7 +8,7 @@ def update @framework.update!(category_params) - redirect_to @framework + redirect_to frameworks_framework_path(@framework, back_to: params[:back_to]) end private diff --git a/app/models/concerns/filter_form.rb b/app/models/concerns/filter_form.rb index 03ed2db05..bf2ff159f 100644 --- a/app/models/concerns/filter_form.rb +++ b/app/models/concerns/filter_form.rb @@ -33,6 +33,10 @@ def initial_scope(proc) define_method :scoped_records do proc.call end + + define_method :available_sort_options do + proc.call.available_sort_options + end end def sort_options(proc) diff --git a/app/models/frameworks/activity_log_item/presentable.rb b/app/models/frameworks/activity_log_item/presentable.rb index 07f2d670a..bc73d42dd 100644 --- a/app/models/frameworks/activity_log_item/presentable.rb +++ b/app/models/frameworks/activity_log_item/presentable.rb @@ -1,5 +1,14 @@ module Frameworks::ActivityLogItem::Presentable extend ActiveSupport::Concern + + def activity_type_identifier + activity_type.demodulize.underscore + end + + def subject_type_identifier + subject_type.demodulize.underscore + end + def display_subject "#{subject.try(:activity_log_display_type)} #{subject.try(:activity_log_display_short_name)}" end diff --git a/app/models/frameworks/activity_loggable/activity_log_presentable.rb b/app/models/frameworks/activity_loggable/activity_log_presentable.rb index d8488928f..a4a73edde 100644 --- a/app/models/frameworks/activity_loggable/activity_log_presentable.rb +++ b/app/models/frameworks/activity_loggable/activity_log_presentable.rb @@ -5,6 +5,10 @@ module Frameworks::ActivityLoggable::ActivityLogPresentable delegate :version_at, to: :paper_trail end + def specific_change_template_for(_activity_loggable_version) + nil + end + def activity_log_display_name try(:full_name) || try(:short_name) || try(:name) || try(:reference) end diff --git a/app/models/frameworks/activity_loggable_version.rb b/app/models/frameworks/activity_loggable_version.rb index c2c315315..cdbea95ff 100644 --- a/app/models/frameworks/activity_loggable_version.rb +++ b/app/models/frameworks/activity_loggable_version.rb @@ -1,8 +1,34 @@ class Frameworks::ActivityLoggableVersion < PaperTrail::Version include Presentable + after_create_commit :save_default_attributes_on_item_created + + def field_changes + object_changes.except("id", "created_at", "updated_at") + end + + def changed_fields_only?(*fields) + field_changes.keys == Array(fields) + end + + def changed_fields_including?(*fields) + field_changes.keys.intersection(Array(fields)).any? + end + def item_association_at_version_or_current(association:, id:, version_at: created_at) current_version_of_association = item.class.reflections[association].klass.find_by(id:) current_version_of_association.try(:version_at, version_at).presence || current_version_of_association end + +private + + def save_default_attributes_on_item_created + return unless item.id_previously_changed? + + object_changes_including_database_default_values = item.reload.attributes.each_with_object(object_changes) do |(attr, value), all_changes| + all_changes[attr] ||= [nil, value] + end + + update!(object_changes: object_changes_including_database_default_values) + end end diff --git a/app/models/frameworks/activity_loggable_version/presentable.rb b/app/models/frameworks/activity_loggable_version/presentable.rb index 3273294c7..1ea734912 100644 --- a/app/models/frameworks/activity_loggable_version/presentable.rb +++ b/app/models/frameworks/activity_loggable_version/presentable.rb @@ -5,6 +5,10 @@ def presentable_changes Frameworks::ActivityLoggableVersion::PresentableChanges.new(self) end + def specific_change_template + item.specific_change_template_for(self) + end + def display_field_version(field:, value:) display_value = if field.ends_with?("_id") diff --git a/app/models/frameworks/activity_loggable_version/presentable_changes.rb b/app/models/frameworks/activity_loggable_version/presentable_changes.rb index 1eec7cb73..f369c871d 100644 --- a/app/models/frameworks/activity_loggable_version/presentable_changes.rb +++ b/app/models/frameworks/activity_loggable_version/presentable_changes.rb @@ -10,10 +10,13 @@ def initialize(version) def each return to_enum(:each) unless block_given? - @version.object_changes.except("id", "created_at", "updated_at").each do |field, changes| + @version.field_changes.each do |field, changes| from = @version.display_field_version(field:, value: changes.first) to = @version.display_field_version(field:, value: changes.last) + # Ignore empty -> empty changes + next if [String(to), String(from)].all?(&:empty?) + yield PresentableChange.new(field:, from:, to:) end end diff --git a/app/models/frameworks/evaluation.rb b/app/models/frameworks/evaluation.rb new file mode 100644 index 000000000..e99ff932b --- /dev/null +++ b/app/models/frameworks/evaluation.rb @@ -0,0 +1,13 @@ +class Frameworks::Evaluation < ApplicationRecord + include Frameworks::ActivityLoggable + include Filterable + include Sortable + include StatusChangeable + include Presentable + include ActivityLogPresentable + + belongs_to :framework + has_one :provider, through: :framework + belongs_to :assignee, class_name: "Support::Agent" + belongs_to :contact, class_name: "Frameworks::ProviderContact", optional: true +end diff --git a/app/models/frameworks/evaluation/activity_log_presentable.rb b/app/models/frameworks/evaluation/activity_log_presentable.rb new file mode 100644 index 000000000..ef5bc1692 --- /dev/null +++ b/app/models/frameworks/evaluation/activity_log_presentable.rb @@ -0,0 +1,8 @@ +module Frameworks::Evaluation::ActivityLogPresentable + extend ActiveSupport::Concern + + def specific_change_template_for(activity_loggable_version) + return "evaluations/status" if activity_loggable_version.changed_fields_only?("status") + return "evaluations/contact" if activity_loggable_version.changed_fields_only?("contact_id") + end +end diff --git a/app/models/frameworks/evaluation/filterable.rb b/app/models/frameworks/evaluation/filterable.rb new file mode 100644 index 000000000..93efc0837 --- /dev/null +++ b/app/models/frameworks/evaluation/filterable.rb @@ -0,0 +1,13 @@ +module Frameworks::Evaluation::Filterable + extend ActiveSupport::Concern + + included do + scope :by_status, ->(statuses) { where(status: Array(statuses)) } + end + + class_methods do + def filtering(params = {}) + Frameworks::Evaluation::Filtering.new(params) + end + end +end diff --git a/app/models/frameworks/evaluation/filtering.rb b/app/models/frameworks/evaluation/filtering.rb new file mode 100644 index 000000000..0c76c03bc --- /dev/null +++ b/app/models/frameworks/evaluation/filtering.rb @@ -0,0 +1,7 @@ +class Frameworks::Evaluation::Filtering + include FilterForm + + initial_scope -> { Frameworks::Evaluation } + + filter_by :status, options: -> { Frameworks::Evaluation.statuses.map { |label, _id| [label.humanize, label] } } +end diff --git a/app/models/frameworks/evaluation/presentable.rb b/app/models/frameworks/evaluation/presentable.rb new file mode 100644 index 000000000..4f177a3e9 --- /dev/null +++ b/app/models/frameworks/evaluation/presentable.rb @@ -0,0 +1,43 @@ +module Frameworks::Evaluation::Presentable + extend ActiveSupport::Concern + + def framework_name + framework.try(:name) + end + + def framework_reference_and_name + framework.try(:reference_and_name) + end + + def framework_provider_name + framework.try(:provider_name) + end + + def framework_category_names + framework.try(:category_names) + end + + def display_status + ApplicationController.render(partial: "frameworks/evaluations/status", locals: { status: }) + end + + def display_assignee + assignee.try(:full_name) + end + + def display_last_updated + "- TODO -" + end + + def contact_name + contact.try(:name) + end + + def contact_email + contact.try(:email) + end + + def contact_phone + contact.try(:phone) + end +end diff --git a/app/models/frameworks/evaluation/sortable.rb b/app/models/frameworks/evaluation/sortable.rb new file mode 100644 index 000000000..b44326ad3 --- /dev/null +++ b/app/models/frameworks/evaluation/sortable.rb @@ -0,0 +1,27 @@ +module Frameworks::Evaluation::Sortable + extend ActiveSupport::Concern + + included do + scope :sort_by_updated, ->(direction = "descending") { order("frameworks_evaluations.updated_at #{safe_direction(direction)}") } + scope :sort_by_reference, ->(direction = "descending") { order("frameworks_evaluations.reference #{safe_direction(direction)}") } + end + + class_methods do + def sorted_by(sort_by:, sort_order:) + return sort_by_reference("descending") unless sort_by.present? && sort_order.present? + + public_send("sort_by_#{sort_by}", sort_order) + end + + def available_sort_options + [ + %w[Reference reference], + %w[Updated updated], + ] + end + + def safe_direction(direction) + direction == "descending" ? "DESC" : "ASC" + end + end +end diff --git a/app/models/frameworks/evaluation/status_changeable.rb b/app/models/frameworks/evaluation/status_changeable.rb new file mode 100644 index 000000000..19099ebbd --- /dev/null +++ b/app/models/frameworks/evaluation/status_changeable.rb @@ -0,0 +1,53 @@ +module Frameworks::Evaluation::StatusChangeable + extend ActiveSupport::Concern + + included do + include AASM + + enum status: { + draft: 0, + in_progress: 1, + approved: 2, + not_approved: 3, + cancelled: 4, + }, _scopes: false + + aasm column: :status do + Frameworks::Evaluation.statuses.each { |status, _| state status.to_sym } + + event :start do + transitions from: :draft, to: :in_progress, after: :after_starting_evaluation + end + + event :approve do + transitions from: :in_progress, to: :approved, after: :after_approving + end + + event :disapprove do + transitions from: :in_progress, to: :not_approved, after: :after_disapproving + end + + event :cancel do + transitions from: :in_progress, to: :cancelled, after: :after_cancelling + end + end + end + +private + + def after_starting_evaluation + framework.start_evaluation!(self) + end + + def after_approving + framework.approve_evaluation!(self) + end + + def after_disapproving + framework.disapprove_evaluation!(self) + end + + def after_cancelling + framework.cancel_evaluation!(self) + end +end diff --git a/app/models/frameworks/framework.rb b/app/models/frameworks/framework.rb index 3cfa9c606..7459f3271 100644 --- a/app/models/frameworks/framework.rb +++ b/app/models/frameworks/framework.rb @@ -8,19 +8,20 @@ class Frameworks::Framework < ApplicationRecord include ActivityEventLoggable include Filterable include Sortable + include Evaluatable belongs_to :provider belongs_to :provider_contact, optional: true belongs_to :proc_ops_lead, class_name: "Support::Agent", optional: true belongs_to :e_and_o_lead, class_name: "Support::Agent", optional: true + has_many :evaluations has_many :framework_categories has_many :support_categories, through: :framework_categories, after_add: :log_framework_category_added, after_remove: :log_framework_category_removed validates :provider_id, presence: { message: "Please select a provider" }, on: :creation_form - validates :provider_contact_id, presence: { message: "Please select a contact" }, on: :creation_form enum lot: { single: 0, diff --git a/app/models/frameworks/framework/activity_event_loggable.rb b/app/models/frameworks/framework/activity_event_loggable.rb index 20c534b39..32f2efa30 100644 --- a/app/models/frameworks/framework/activity_event_loggable.rb +++ b/app/models/frameworks/framework/activity_event_loggable.rb @@ -3,18 +3,20 @@ module Frameworks::Framework::ActivityEventLoggable def activity_event_data_for(activity_event) case activity_event.event - when "framework_category_added", "framework_category_removed" + when "category_added", "category_removed" { support_category: Support::Category.find(activity_event.data["support_category_id"]) } + when "evaluation_started", "evaluation_approved", "evaluation_not_approved", "evaluation_cancelled" + { evaluation: Frameworks::Evaluation.find(activity_event.data["evaluation_id"]) } end end protected def log_framework_category_added(category) - log_activity_event("framework_category_added", support_category_id: category.id) + log_activity_event("category_added", support_category_id: category.id) end def log_framework_category_removed(category) - log_activity_event("framework_category_removed", support_category_id: category.id) + log_activity_event("category_removed", support_category_id: category.id) end end diff --git a/app/models/frameworks/framework/activity_log_presentable.rb b/app/models/frameworks/framework/activity_log_presentable.rb index 031c6d229..677121358 100644 --- a/app/models/frameworks/framework/activity_log_presentable.rb +++ b/app/models/frameworks/framework/activity_log_presentable.rb @@ -1,6 +1,14 @@ module Frameworks::Framework::ActivityLogPresentable extend ActiveSupport::Concern + def specific_change_template_for(activity_loggable_version) + "frameworks/status" if activity_loggable_version.changed_fields_only?("status") + end + + def activity_log_display_name + reference_and_name + end + def display_field_version_dfe_start_date(date) Date.parse(date).strftime("%d/%m/%y") if date.present? end diff --git a/app/models/frameworks/framework/evaluatable.rb b/app/models/frameworks/framework/evaluatable.rb new file mode 100644 index 000000000..db5cae88f --- /dev/null +++ b/app/models/frameworks/framework/evaluatable.rb @@ -0,0 +1,7 @@ +module Frameworks::Framework::Evaluatable + extend ActiveSupport::Concern + + included do + scope :for_evaluation, -> { by_status(%w[pending_evaluation not_approved]) } + end +end diff --git a/app/models/frameworks/framework/filtering.rb b/app/models/frameworks/framework/filtering.rb index 67a3e2680..9bf0d8d4d 100644 --- a/app/models/frameworks/framework/filtering.rb +++ b/app/models/frameworks/framework/filtering.rb @@ -4,7 +4,6 @@ class Frameworks::Framework::Filtering get_agents = ->(type_id) { Support::Agent.by_first_name.where(id: Frameworks::Framework.pluck(type_id)) } initial_scope -> { Frameworks::Framework } - sort_options -> { Frameworks::Framework.available_sort_options } filter_by :status, options: -> { Frameworks::Framework.statuses.map { |label, _id| [label.humanize, label] } } filter_by :provider, options: -> { Frameworks::Provider.order("short_name ASC").pluck(:short_name, :id) } diff --git a/app/models/frameworks/framework/presentable.rb b/app/models/frameworks/framework/presentable.rb index 6971d8b5f..f36465e13 100644 --- a/app/models/frameworks/framework/presentable.rb +++ b/app/models/frameworks/framework/presentable.rb @@ -5,6 +5,10 @@ def provider_name provider.name || provider.short_name end + def reference_and_name + [reference, name.presence || short_name].compact.join(" - ") + end + def reference_and_short_name [reference, short_name].compact.join(" - ") end diff --git a/app/models/frameworks/framework/sortable.rb b/app/models/frameworks/framework/sortable.rb index 42518e761..0cf360461 100644 --- a/app/models/frameworks/framework/sortable.rb +++ b/app/models/frameworks/framework/sortable.rb @@ -7,7 +7,8 @@ module Frameworks::Framework::Sortable scope :sort_by_dfe_end_date, ->(direction = "descending") { order("dfe_end_date #{safe_direction(direction)}") } scope :sort_by_provider_start_date, ->(direction = "descending") { order("provider_start_date #{safe_direction(direction)}") } scope :sort_by_provider_end_date, ->(direction = "descending") { order("provider_end_date #{safe_direction(direction)}") } - scope :sort_by_reference, ->(direction = "descending") { order("reference #{safe_direction(direction)}") } + scope :sort_by_reference, ->(direction = "descending") { order("frameworks_frameworks.reference #{safe_direction(direction)}") } + scope :sort_by_provider_reference, ->(direction = "descending") { order("provider_reference #{safe_direction(direction)}") } end class_methods do diff --git a/app/models/frameworks/framework/spreadsheet_importable.rb b/app/models/frameworks/framework/spreadsheet_importable.rb index 7ce2e063a..780b9230e 100644 --- a/app/models/frameworks/framework/spreadsheet_importable.rb +++ b/app/models/frameworks/framework/spreadsheet_importable.rb @@ -16,7 +16,7 @@ def import_from_spreadsheet(file) end def import_from_spreadsheet_row(row_mapping) - framework = find_or_initialize_by(provider: row_mapping.provider, reference: row_mapping.reference) + framework = find_or_initialize_by(provider: row_mapping.provider, provider_reference: row_mapping.provider_reference) framework.source = :spreadsheet_import framework.attributes = row_mapping.attributes framework.save! diff --git a/app/models/frameworks/framework/spreadsheet_importable/row_mapping.rb b/app/models/frameworks/framework/spreadsheet_importable/row_mapping.rb index b85027e62..c4591efbd 100644 --- a/app/models/frameworks/framework/spreadsheet_importable/row_mapping.rb +++ b/app/models/frameworks/framework/spreadsheet_importable/row_mapping.rb @@ -7,7 +7,7 @@ def initialize(row) def provider = Frameworks::Provider.find_or_create_by!(short_name: row["FWK Provider"]) - def reference = row["Framework Reference "] + def provider_reference = row["Framework Reference "] def attributes { @@ -16,7 +16,7 @@ def attributes status:, name:, url:, - reference:, + provider_reference:, dfe_start_date:, dfe_end_date:, sct_framework_provider_lead:, diff --git a/app/models/frameworks/framework/status_changeable.rb b/app/models/frameworks/framework/status_changeable.rb index b268e5537..e425f6e68 100644 --- a/app/models/frameworks/framework/status_changeable.rb +++ b/app/models/frameworks/framework/status_changeable.rb @@ -2,12 +2,52 @@ module Frameworks::Framework::StatusChangeable extend ActiveSupport::Concern included do + include AASM + enum status: { pending_evaluation: 0, evaluating: 1, not_approved: 2, dfe_approved: 3, cab_approved: 4, - } + }, _scopes: false + + aasm column: :status do + Frameworks::Framework.statuses.each { |status, _| state status.to_sym } + + event :start_evaluation do + transitions from: %i[pending_evaluation not_approved], to: :evaluating, after: :after_starting_evaluation + end + + event :approve_evaluation do + transitions from: :evaluating, to: :dfe_approved, after: :after_approving_evaluation + end + + event :disapprove_evaluation do + transitions from: :evaluating, to: :not_approved, after: :after_disapproving_evaluation + end + + event :cancel_evaluation do + transitions from: :evaluating, to: :not_approved, after: :after_cancelling_evaluation + end + end + end + +private + + def after_starting_evaluation(evaluation) + log_activity_event("evaluation_started", evaluation_id: evaluation.id) + end + + def after_approving_evaluation(evaluation) + log_activity_event("evaluation_approved", evaluation_id: evaluation.id) + end + + def after_disapproving_evaluation(evaluation) + log_activity_event("evaluation_not_approved", evaluation_id: evaluation.id) + end + + def after_cancelling_evaluation(evaluation) + log_activity_event("evaluation_cancelled", evaluation_id: evaluation.id) end end diff --git a/app/models/support/agent.rb b/app/models/support/agent.rb index 379501bae..06653e565 100644 --- a/app/models/support/agent.rb +++ b/app/models/support/agent.rb @@ -22,8 +22,18 @@ class Agent < ApplicationRecord belongs_to :support_tower, class_name: "Support::Tower", optional: true belongs_to :user, foreign_key: "dsi_uid", primary_key: "dfe_sign_in_uid", optional: true - scope :caseworkers, -> { where("ARRAY['procops', 'procops_admin'] && roles::text[]") } + scope :caseworkers, -> { by_role(%w[procops procops_admin]) } scope :by_first_name, -> { order("first_name ASC, last_name ASC") } + scope :by_role, ->(roles) { where("ARRAY[?] && roles::text[]", Array(roles)) } + + scope :framework_evaluators, lambda { + by_role(%w[ + procops_admin + procops + framework_evaluator + framework_evaluator_admin + ]) + } scope :omnisearch, lambda { |query| sql = <<-SQL diff --git a/app/views/frameworks/activity_log_items/_activity_log_item.html.erb b/app/views/frameworks/activity_log_items/_activity_log_item.html.erb index ecee3c40f..4b069aaa5 100644 --- a/app/views/frameworks/activity_log_items/_activity_log_item.html.erb +++ b/app/views/frameworks/activity_log_items/_activity_log_item.html.erb @@ -1,4 +1,4 @@ -<%= render "frameworks/activity_log_items/activity/#{activity_log_item.activity_type.demodulize.underscore}", +<%= render "frameworks/activity_log_items/activity/#{activity_log_item.activity_type_identifier}", activity_log_item:, activity: activity_log_item.activity, subject: activity_log_item.subject %> diff --git a/app/views/frameworks/activity_log_items/activity/_activity_event.html.erb b/app/views/frameworks/activity_log_items/activity/_activity_event.html.erb index 0082550d6..4e9ee0e11 100644 --- a/app/views/frameworks/activity_log_items/activity/_activity_event.html.erb +++ b/app/views/frameworks/activity_log_items/activity/_activity_event.html.erb @@ -1,4 +1,4 @@ -<%= render "frameworks/activity_log_items/activity/activity_event/#{activity_log_item.activity.event}", +<%= render "frameworks/activity_log_items/activity/activity_event/#{activity_log_item.subject_type_identifier}s/#{activity_log_item.activity.event}", activity_log_item:, - activity: activity_log_item.activity, - subject: activity_log_item.subject %> + activity:, + subject: %> diff --git a/app/views/frameworks/activity_log_items/activity/_activity_loggable_version.html.erb b/app/views/frameworks/activity_log_items/activity/_activity_loggable_version.html.erb index c8451f02c..93b3b6e1f 100644 --- a/app/views/frameworks/activity_log_items/activity/_activity_loggable_version.html.erb +++ b/app/views/frameworks/activity_log_items/activity/_activity_loggable_version.html.erb @@ -1,20 +1,5 @@ -<%= activity.event.humanize %>d <%= activity.item.activity_log_display_type %> -<%= link_to "[+]", "#", title: "See changes", class: "govuk-link", "data-component" => "toggle-panel-visibility", "data-panel" => activity_log_item.id %> - -
- - - <% activity.presentable_changes.each do |changes| %> - - - - - <% end %> - -
<%= changes.field.humanize %> - <%= changes.from.presence || "empty" %> - - <%= changes.to.presence || "empty" %> -
-
+<% resolved_template = (activity.specific_change_template.present? \ + ? "frameworks/activity_log_items/activity/activity_loggable_version/#{activity.specific_change_template}" + : "frameworks/activity_log_items/activity/activity_loggable_version/presentable_changes") %> +<%= render resolved_template, activity_log_item:, activity:, subject: %> diff --git a/app/views/frameworks/activity_log_items/activity/activity_event/evaluations/_evaluation_started.html.erb b/app/views/frameworks/activity_log_items/activity/activity_event/evaluations/_evaluation_started.html.erb new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/frameworks/activity_log_items/activity/activity_event/_framework_category_added.html.erb b/app/views/frameworks/activity_log_items/activity/activity_event/frameworks/_category_added.html.erb similarity index 100% rename from app/views/frameworks/activity_log_items/activity/activity_event/_framework_category_added.html.erb rename to app/views/frameworks/activity_log_items/activity/activity_event/frameworks/_category_added.html.erb diff --git a/app/views/frameworks/activity_log_items/activity/activity_event/_framework_category_removed.html.erb b/app/views/frameworks/activity_log_items/activity/activity_event/frameworks/_category_removed.html.erb similarity index 100% rename from app/views/frameworks/activity_log_items/activity/activity_event/_framework_category_removed.html.erb rename to app/views/frameworks/activity_log_items/activity/activity_event/frameworks/_category_removed.html.erb diff --git a/app/views/frameworks/activity_log_items/activity/activity_event/frameworks/_evaluation_started.html.erb b/app/views/frameworks/activity_log_items/activity/activity_event/frameworks/_evaluation_started.html.erb new file mode 100644 index 000000000..4debb4fce --- /dev/null +++ b/app/views/frameworks/activity_log_items/activity/activity_event/frameworks/_evaluation_started.html.erb @@ -0,0 +1 @@ +Framework Evaluation started - <%= link_to "View #{activity.loaded_data.evaluation.reference}", activity.loaded_data.evaluation, class: "govuk-link", "data-turbo" => false %> diff --git a/app/views/frameworks/activity_log_items/activity/activity_loggable_version/_presentable_changes.html.erb b/app/views/frameworks/activity_log_items/activity/activity_loggable_version/_presentable_changes.html.erb new file mode 100644 index 000000000..20bc61698 --- /dev/null +++ b/app/views/frameworks/activity_log_items/activity/activity_loggable_version/_presentable_changes.html.erb @@ -0,0 +1,22 @@ +<%= activity.event.humanize %>d <%= activity.item.activity_log_display_type %> +<%= link_to "[+]", "#", title: "See changes", class: "govuk-link", "data-component" => "toggle-panel-visibility", "data-panel" => activity_log_item.id %> + +
+ + + <% activity.presentable_changes.each do |changes| %> + + + + + <% end %> + +
<%= changes.field.humanize %> + <% unless activity.event == "create" %> + <%= changes.from.presence || "empty" %> + + <% end %> + + <%= changes.to.presence || "empty" %> +
+
diff --git a/app/views/frameworks/activity_log_items/activity/activity_loggable_version/evaluations/_contact.html.erb b/app/views/frameworks/activity_log_items/activity/activity_loggable_version/evaluations/_contact.html.erb new file mode 100644 index 000000000..a9d937476 --- /dev/null +++ b/app/views/frameworks/activity_log_items/activity/activity_loggable_version/evaluations/_contact.html.erb @@ -0,0 +1 @@ +Assigned contact <%= link_to activity.item.contact_name, frameworks_provider_contact_path(activity.item.contact, back_to: current_url_b64(:history)), class: "govuk-link", "data-turbo" => false %> to the evaluation diff --git a/app/views/frameworks/activity_log_items/activity/activity_loggable_version/evaluations/_status.html.erb b/app/views/frameworks/activity_log_items/activity/activity_loggable_version/evaluations/_status.html.erb new file mode 100644 index 000000000..292c5393a --- /dev/null +++ b/app/views/frameworks/activity_log_items/activity/activity_loggable_version/evaluations/_status.html.erb @@ -0,0 +1,4 @@ +Status changed from +<%= render "frameworks/evaluations/status", status: activity.field_changes["status"].first %> +to +<%= render "frameworks/evaluations/status", status: activity.field_changes["status"].last %> diff --git a/app/views/frameworks/activity_log_items/activity/activity_loggable_version/frameworks/_status.html.erb b/app/views/frameworks/activity_log_items/activity/activity_loggable_version/frameworks/_status.html.erb new file mode 100644 index 000000000..208d61a97 --- /dev/null +++ b/app/views/frameworks/activity_log_items/activity/activity_loggable_version/frameworks/_status.html.erb @@ -0,0 +1,4 @@ +Status changed from +<%= render "frameworks/frameworks/framework_status", status: activity.field_changes["status"].first %> +to +<%= render "frameworks/frameworks/framework_status", status: activity.field_changes["status"].last %> diff --git a/app/views/frameworks/dashboards/index.html.erb b/app/views/frameworks/dashboards/index.html.erb index 817f3a19e..47d4601a4 100644 --- a/app/views/frameworks/dashboards/index.html.erb +++ b/app/views/frameworks/dashboards/index.html.erb @@ -6,6 +6,10 @@ <%= link_to "Frameworks Register", "#frameworks-register", class: "govuk-tabs__tab" %> +
  • + <%= link_to "Framework Evaluations", "#evaluations", class: "govuk-tabs__tab" %> +
  • +
  • <%= link_to "Provider Contacts", "#provider-contacts", class: "govuk-tabs__tab" %>
  • @@ -21,6 +25,12 @@ <% end %> + <%= turbo_frame_tag "evaluations-frame", src: frameworks_evaluations_path(**request.params.except(:controller, :action)) do %> +
    +

    Loading...

    +
    + <% end %> + <%= turbo_frame_tag "provider-contacts-frame", src: frameworks_provider_contacts_path(**request.params.except(:controller, :action)) do %>

    Loading...

    diff --git a/app/views/frameworks/evaluations/_evaluation.html.erb b/app/views/frameworks/evaluations/_evaluation.html.erb new file mode 100644 index 000000000..2d85d247d --- /dev/null +++ b/app/views/frameworks/evaluations/_evaluation.html.erb @@ -0,0 +1,44 @@ +
    +
    +
    +
    +
    Case ID
    +
    <%= link_to evaluation.reference, frameworks_evaluation_path(evaluation, back_to: current_url_b64(:evaluations)), class: "govuk-link", "data-turbo" => false %>
    +
    + +
    +
    Framework Name
    +
    <%= evaluation.framework_name %>
    +
    + +
    +
    Framework Provider
    +
    <%= evaluation.framework_provider_name %>
    +
    + +
    +
    Categories
    +
    <%= evaluation.framework_category_names %>
    +
    +
    +
    + +
    +
    +
    +
    Status
    +
    <%= evaluation.display_status %>
    +
    + +
    +
    Assignee
    +
    <%= evaluation.display_assignee %>
    +
    + +
    +
    Updated
    +
    <%= evaluation.display_last_updated %>
    +
    +
    +
    +
    diff --git a/app/views/frameworks/evaluations/_index_filters.html.erb b/app/views/frameworks/evaluations/_index_filters.html.erb new file mode 100644 index 000000000..f69a07655 --- /dev/null +++ b/app/views/frameworks/evaluations/_index_filters.html.erb @@ -0,0 +1,28 @@ +
    +

    <%= I18n.t("support.case.label.filters") %>

    + + <%= form_with model: @filtering, scope: :evaluations_filter, method: :get, url: frameworks_evaluations_path, html: { "data-controller" => "case-filters" } do |form| %> + + + <%= link_to I18n.t("support.case.filter.buttons.clear"), frameworks_evaluations_path(viewing_from_entity: params[:viewing_from_entity]), class: "govuk-button govuk-button--secondary", role: "button" %> + + <%= form.govuk_select :sort_by, + options_for_select(@filtering.available_sort_options, @filtering.sort_by), + label: { text: I18n.t("support.case.label.sort_by"), size: "s" } %> + + <%= form.govuk_collection_radio_buttons :sort_order, + available_sort_orders, + :id, + :title, + legend: nil, + small: true %> + + <%= expander title: "Status", subtitle: "#{@filtering.number_of_selected(:statuses)} selected", expanded: true do %> + <%= form.govuk_check_boxes_fieldset :status, legend: nil, small: true, form_group: { class: "govuk-!-margin-bottom-0" } do %> + <% @filtering.available_status_options.each do |status| %> + <%= form.govuk_check_box :status, status.last, exclusive: false, label: { text: status.first } %> + <% end %> + <% end %> + <% end %> + <% end %> +
    diff --git a/app/views/frameworks/evaluations/_status.html.erb b/app/views/frameworks/evaluations/_status.html.erb new file mode 100644 index 000000000..7e5224b24 --- /dev/null +++ b/app/views/frameworks/evaluations/_status.html.erb @@ -0,0 +1,12 @@ +<%# Required local: either literal status or a evaluation record %> +<% the_status = (defined?(status) ? status : evaluation.status).inquiry %> + + + <%= "govuk-tag--purple" if the_status.evaluating? %> + <%= "govuk-tag--grey" if the_status.not_approved? %> + <%= "govuk-tag--green" if the_status.approved? %> + <%= "govuk-tag--red" if the_status.cancelled? %> +"> + <%= the_status.humanize %> + diff --git a/app/views/frameworks/evaluations/index.html.erb b/app/views/frameworks/evaluations/index.html.erb new file mode 100644 index 000000000..23d87e756 --- /dev/null +++ b/app/views/frameworks/evaluations/index.html.erb @@ -0,0 +1,24 @@ +<%= turbo_frame_tag "evaluations-frame" do %> +
    +
    +
    + <% unless params[:viewing_from_entity] == "true" %> + <%= link_to "Add Evalutaion", new_frameworks_evaluation_path(back_to: current_url_b64(:evaluations)), class: "govuk-button", "data-turbo" => false %> + <% end %> + + <%= render "index_filters" %> +
    + +
    + <%= render @evaluations %> + + <% unless @evaluations.any? %> +

    No framework evaluations found

    +

    Try clearing or changing your filters.

    + <% end %> + + <%= render "components/pagination", records: @evaluations, page_param_name: :evaluations_page, pagination_params: { anchor: "evaluations" } %> +
    +
    +
    +<% end %> diff --git a/app/views/frameworks/evaluations/new.html.erb b/app/views/frameworks/evaluations/new.html.erb new file mode 100644 index 000000000..818343c60 --- /dev/null +++ b/app/views/frameworks/evaluations/new.html.erb @@ -0,0 +1,20 @@ +<%= content_for :title, "GHBS | Frameworks | New Evaluation" %> + +

    New Framework Evaluation

    + +
    + <%= form_for @evaluation do |f| %> + <%= f.govuk_select :framework_id, @frameworks.map {|framework| [framework.reference_and_name, framework.id] }, + options: { include_blank: "Select a framework" }, + label: { text: "Framework" } %> + + <%= f.govuk_select :assignee_id, @agents.map {|agent| [agent.full_name, agent.id] }, + options: { include_blank: "Select an agent" }, + label: { text: "Assignee" } %> + +
    + <%= f.govuk_submit "Create evaluation" %> + <%= link_to "Cancel", @back_url, class: "govuk-link govuk-link--no-visited-state" %> +
    + <% end %> +
    diff --git a/app/views/frameworks/evaluations/show.html.erb b/app/views/frameworks/evaluations/show.html.erb new file mode 100644 index 000000000..8ef9d7782 --- /dev/null +++ b/app/views/frameworks/evaluations/show.html.erb @@ -0,0 +1,58 @@ +<%= content_for :title, "GHBS | Frameworks | Evaluation #{@evaluation.reference}" %> + +<%= turbo_frame_tag dom_id(@evaluation) do %> + + [<%= @evaluation.reference %>] Framework Evaluation +

    <%= @evaluation.framework_name %>

    + +
    + <%= @evaluation.display_status %> + Case Owner: <%= @evaluation.display_assignee %> +
    + +
    + + + <%= turbo_frame_tag "framework-frame" do %> +
    + <%= render "frameworks/evaluations/show/framework" %> +
    + <% end %> + + <%= turbo_frame_tag "messages-frame" do %> +
    + <%= render "frameworks/evaluations/show/messages" %> +
    + <% end %> + + <%= turbo_frame_tag "history-frame" do %> +
    + <%= render "frameworks/evaluations/show/history" %> +
    + <% end %> + + <%= turbo_frame_tag "attachments-frame" do %> +
    + <%= render "frameworks/evaluations/show/attachments" %> +
    + <% end %> +
    + +

    Actions

    + +<% end %> diff --git a/app/views/frameworks/evaluations/show/_attachments.html.erb b/app/views/frameworks/evaluations/show/_attachments.html.erb new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/frameworks/evaluations/show/_framework.html.erb b/app/views/frameworks/evaluations/show/_framework.html.erb new file mode 100644 index 000000000..10fe57fc6 --- /dev/null +++ b/app/views/frameworks/evaluations/show/_framework.html.erb @@ -0,0 +1,27 @@ +
    +
    +
    Framework Provider
    +
    <%= link_to @evaluation.framework_provider_name, frameworks_provider_path(@evaluation.provider, back_to: current_url_b64(:framework)), class: "govuk-link", "data-turbo" => false %>
    +
    + +
    +
    Framework Name
    +
    <%= link_to @evaluation.framework_reference_and_name, frameworks_framework_path(@evaluation.framework, back_to: current_url_b64(:framework)), class: "govuk-link", "data-turbo" => false %>
    +
    + +
    +
    Contact
    +
    <%= link_to @evaluation.contact_name, frameworks_provider_contact_path(@evaluation.contact, back_to: current_url_b64(:framework)), class: "govuk-link", "data-turbo" => false %>
    +
    <%= link_to "Change", "#", class: "govuk-link", "data-turbo" => false %>
    +
    + +
    +
    Contact Email
    +
    <%= @evaluation.contact_email %>
    +
    + +
    +
    Contact Phone
    +
    <%= @evaluation.contact_phone %>
    +
    +
    diff --git a/app/views/frameworks/evaluations/show/_history.html.erb b/app/views/frameworks/evaluations/show/_history.html.erb new file mode 100644 index 000000000..63d780895 --- /dev/null +++ b/app/views/frameworks/evaluations/show/_history.html.erb @@ -0,0 +1,2 @@ +<%= render "frameworks/activity_log_items/list", activity_log_items: @activity_log_items %> +<%= render "components/pagination", records: @activity_log_items, page_param_name: 'history_page', pagination_params: { anchor: "history" } %> diff --git a/app/views/frameworks/evaluations/show/_messages.html.erb b/app/views/frameworks/evaluations/show/_messages.html.erb new file mode 100644 index 000000000..e69de29bb diff --git a/app/views/frameworks/categorisations/edit.html.erb b/app/views/frameworks/framework_categorisations/edit.html.erb similarity index 92% rename from app/views/frameworks/categorisations/edit.html.erb rename to app/views/frameworks/framework_categorisations/edit.html.erb index c8d074cac..5b521e99b 100644 --- a/app/views/frameworks/categorisations/edit.html.erb +++ b/app/views/frameworks/framework_categorisations/edit.html.erb @@ -3,7 +3,7 @@ Edit Framework Categories

    <%= @framework.name %>

    -<%= form_for @framework, url: frameworks_framework_categorisations_path(@framework), method: :put do |f| %> +<%= form_for @framework, url: frameworks_framework_categorisations_path(@framework, back_to: params[:back_to]), method: :put do |f| %> <% procurement_category_grouped_options.each do |group, options| %> <% next if group == "Or" %> diff --git a/app/views/frameworks/frameworks/show.html.erb b/app/views/frameworks/frameworks/show.html.erb index 1a2588fd7..1806c09d3 100644 --- a/app/views/frameworks/frameworks/show.html.erb +++ b/app/views/frameworks/frameworks/show.html.erb @@ -2,7 +2,7 @@ <%= turbo_frame_tag dom_id(@framework) do %> -<%= @framework.reference_and_short_name %> +[<%= @framework.reference %>] Framework

    <%= @framework.name %>

    <%= @framework.display_status %>

    @@ -14,9 +14,12 @@
  • <%= link_to "Provider", "#framework-provider", class: "govuk-tabs__tab" %> +
  • +
  • + <%= link_to "Evaluations", "#framework-evaluations", class: "govuk-tabs__tab" %>
  • - <%= link_to "History", "#framework-activity", class: "govuk-tabs__tab" %> + <%= link_to "History", "#history", class: "govuk-tabs__tab" %>
  • @@ -32,9 +35,15 @@
    <% end %> - <%= turbo_frame_tag "framework-activity-frame" do %> -
    - <%= render "frameworks/frameworks/show/framework_activity" %> + <%= turbo_frame_tag "framework-evaluations-frame" do %> +
    + <%= render "frameworks/frameworks/show/framework_evaluations" %> +
    + <% end %> + + <%= turbo_frame_tag "history-frame" do %> +
    + <%= render "frameworks/frameworks/show/framework_history" %>
    <% end %>
    diff --git a/app/views/frameworks/frameworks/show/_framework_evaluations.html.erb b/app/views/frameworks/frameworks/show/_framework_evaluations.html.erb new file mode 100644 index 000000000..04b5734c3 --- /dev/null +++ b/app/views/frameworks/frameworks/show/_framework_evaluations.html.erb @@ -0,0 +1,22 @@ +<%= link_to "Add Evaluation", new_frameworks_evaluation_path(framework_id: @framework.id, back_to: current_url_b64(:framework_evaluations)), class: "govuk-button", "data-turbo" => false %> + + + + + + + + + + + + <% @framework.evaluations.each do |evaluation| %> + + + + + + + <% end %> + +
    Case IDStatusAssigneeLast Updated
    <%= link_to evaluation.reference, frameworks_evaluation_path(evaluation, back_to: current_url_b64(:framework_evaluations)), class: "govuk-link", "data-turbo" => false %><%= evaluation.display_status %><%= evaluation.display_assignee %><%= evaluation.display_last_updated %>
    diff --git a/app/views/frameworks/frameworks/show/_framework_activity.html.erb b/app/views/frameworks/frameworks/show/_framework_history.html.erb similarity index 100% rename from app/views/frameworks/frameworks/show/_framework_activity.html.erb rename to app/views/frameworks/frameworks/show/_framework_history.html.erb diff --git a/app/views/frameworks/frameworks/show/_framework_provider.html.erb b/app/views/frameworks/frameworks/show/_framework_provider.html.erb index 24a6a3d0c..d65fab083 100644 --- a/app/views/frameworks/frameworks/show/_framework_provider.html.erb +++ b/app/views/frameworks/frameworks/show/_framework_provider.html.erb @@ -4,6 +4,11 @@
    <%= link_to @framework.provider_name, frameworks_provider_path(@framework.provider, back_to: current_url_b64(:framework_provider)), class: "govuk-link", "data-turbo" => false if @framework.provider.present? %>
    +
    +
    Provider Reference
    +
    <%= @framework.provider_reference %>
    +
    +
    SCT Provider Lead
    <%= @framework.sct_framework_provider_lead %>
    diff --git a/config/brakeman.ignore b/config/brakeman.ignore index 1065b1d9b..e1175f4b1 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -25,7 +25,7 @@ { "type": "template", "name": "frameworks/frameworks/show", - "line": 25, + "line": 28, "file": "app/views/frameworks/frameworks/show.html.erb", "rendered": { "name": "frameworks/frameworks/show/_framework_details", @@ -67,6 +67,40 @@ ], "note": "" }, + { + "warning_type": "Dynamic Render Path", + "warning_code": 15, + "fingerprint": "0bb9d316930182410224e235ece8dd8591c92966fb97a1f3520c82420bf7a066", + "check_name": "Render", + "message": "Render path contains parameter value", + "file": "app/views/frameworks/evaluations/index.html.erb", + "line": 13, + "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "code": "render(action => Frameworks::Evaluation.filtering(filter_form_params).results.paginate(:page => params[:evaluations_page]), {})", + "render_path": [ + { + "type": "controller", + "class": "Frameworks::EvaluationsController", + "method": "index", + "line": 8, + "file": "app/controllers/frameworks/evaluations_controller.rb", + "rendered": { + "name": "frameworks/evaluations/index", + "file": "app/views/frameworks/evaluations/index.html.erb" + } + } + ], + "location": { + "type": "template", + "template": "frameworks/evaluations/index" + }, + "user_input": "params[:evaluations_page]", + "confidence": "Weak", + "cwe_id": [ + 22 + ], + "note": "" + }, { "warning_type": "Dynamic Render Path", "warning_code": 15, @@ -265,7 +299,7 @@ "check_name": "MassAssignment", "message": "Specify exact keys allowed for mass assignment instead of using `permit!` which allows any keys", "file": "app/controllers/support/cases_controller.rb", - "line": 140, + "line": 142, "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", "code": "params.fetch(:tower, {}).fetch(tab, {}).permit!", "render_path": null, @@ -350,6 +384,6 @@ "note": "" } ], - "updated": "2023-09-14 15:05:19 +0100", + "updated": "2023-09-28 11:18:30 +0100", "brakeman_version": "6.0.1" } diff --git a/config/routes.rb b/config/routes.rb index c5ccfa9d7..2f866aef6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -301,11 +301,15 @@ # Frameworks portal namespace :frameworks do root to: "dashboards#index" + + resources :evaluations do + resource :contacts, only: %i[edit update], controller: :evaluation_contacts + end resources :frameworks do - resource :categorisations, only: %w[edit update] + resource :categorisations, only: %i[edit update], controller: :framework_categorisations end - resources :provider_contacts resources :providers + resources :provider_contacts namespace :management do get "/", to: "management#index" diff --git a/db/migrate/20230919142106_create_frameworks_evaluations.rb b/db/migrate/20230919142106_create_frameworks_evaluations.rb new file mode 100644 index 000000000..d76f8cfae --- /dev/null +++ b/db/migrate/20230919142106_create_frameworks_evaluations.rb @@ -0,0 +1,14 @@ +class CreateFrameworksEvaluations < ActiveRecord::Migration[7.0] + def change + create_sequence :evaluation_refs + create_table :frameworks_evaluations, id: :uuid do |t| + t.string :reference, default: -> { "'FE' || nextval('evaluation_refs')" } + t.uuid :framework_id + t.uuid :assignee_id + t.uuid :contact_id + t.integer :status, default: 0 + + t.timestamps + end + end +end diff --git a/db/migrate/20230919144709_add_reference_sequence_to_frameworks_framework.rb b/db/migrate/20230919144709_add_reference_sequence_to_frameworks_framework.rb new file mode 100644 index 000000000..4afaf3e6a --- /dev/null +++ b/db/migrate/20230919144709_add_reference_sequence_to_frameworks_framework.rb @@ -0,0 +1,7 @@ +class AddReferenceSequenceToFrameworksFramework < ActiveRecord::Migration[7.0] + def change + create_sequence :framework_refs + change_column_default :frameworks_frameworks, :reference, to: -> { "'F' || nextval('framework_refs')" }, from: nil + add_column :frameworks_frameworks, :provider_reference, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index e2fdf998a..866247395 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,10 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_09_19_092926) do +ActiveRecord::Schema[7.0].define(version: 2023_09_19_144709) do + create_sequence "evaluation_refs" + create_sequence "framework_refs" + # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" enable_extension "pgcrypto" @@ -225,6 +228,16 @@ t.datetime "updated_at", null: false end + create_table "frameworks_evaluations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.string "reference", default: -> { "('FE'::text || nextval('evaluation_refs'::regclass))" } + t.uuid "framework_id" + t.uuid "assignee_id" + t.uuid "contact_id" + t.integer "status", default: 0 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "frameworks_framework_categories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.uuid "support_category_id", null: false t.uuid "framework_id", null: false @@ -238,7 +251,7 @@ t.string "name" t.string "short_name" t.string "url" - t.string "reference" + t.string "reference", default: -> { "('F'::text || nextval('framework_refs'::regclass))" } t.string "description" t.uuid "provider_id", null: false t.uuid "provider_contact_id" @@ -254,6 +267,7 @@ t.datetime "updated_at", null: false t.boolean "dps", default: true t.integer "lot", default: 0 + t.string "provider_reference" end create_table "frameworks_provider_contacts", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| diff --git a/spec/factories/frameworks/evaluations.rb b/spec/factories/frameworks/evaluations.rb new file mode 100644 index 000000000..167485d56 --- /dev/null +++ b/spec/factories/frameworks/evaluations.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :frameworks_evaluation, class: "Frameworks::Evaluation" do + reference { "MyString" } + framework_id { "" } + status { 1 } + end +end diff --git a/spec/features/frameworks/evaluations/agent_can_create_framework_evaluation_spec.rb b/spec/features/frameworks/evaluations/agent_can_create_framework_evaluation_spec.rb new file mode 100644 index 000000000..eeeefd8e0 --- /dev/null +++ b/spec/features/frameworks/evaluations/agent_can_create_framework_evaluation_spec.rb @@ -0,0 +1,15 @@ +require "rails_helper" + +xdescribe "Agent can create new framework evaluations", js: true do + include_context "with a framework evaluation agent" + + let(:framework) { create(:frameworks_framework, status: "pending_evaluation") } + + it "can create a new evaluation from the framework" do + visit frameworks_framework_path(framework) + click_on "Evaluations" + click_on "Add Evaluation" + select agent.full_name, from: "Assignee" + click_on "Create Evaluation" + end +end diff --git a/spec/models/frameworks/activity_loggable_version_spec.rb b/spec/models/frameworks/activity_loggable_version_spec.rb new file mode 100644 index 000000000..63445413c --- /dev/null +++ b/spec/models/frameworks/activity_loggable_version_spec.rb @@ -0,0 +1,11 @@ +require "rails_helper" + +describe Frameworks::ActivityLoggableVersion do + context "when the item is being created" do + it "also records the default fields added by enums, db etc" do + framework = create(:frameworks_framework) + expect(framework.versions.last.object_changes.keys).to include("reference") # added by db + expect(framework.versions.last.object_changes.keys).to include("status") # default enum value + end + end +end diff --git a/spec/models/frameworks/evaluation/activity_log_presentable_spec.rb b/spec/models/frameworks/evaluation/activity_log_presentable_spec.rb new file mode 100644 index 000000000..c9705504f --- /dev/null +++ b/spec/models/frameworks/evaluation/activity_log_presentable_spec.rb @@ -0,0 +1,19 @@ +require "rails_helper" + +describe Frameworks::Evaluation::ActivityLogPresentable do + subject(:presentable) { Frameworks::Evaluation.new } + + describe "#specific_change_template_for" do + it "returns nil by default" do + template = presentable.specific_change_template_for(Frameworks::ActivityLoggableVersion.new(object_changes: {})) + expect(template).to be_nil + end + + context "when the only changes are to the status field" do + it "returns evaluations/status" do + template = presentable.specific_change_template_for(Frameworks::ActivityLoggableVersion.new(object_changes: { status: [] })) + expect(template).to eq("evaluations/status") + end + end + end +end diff --git a/spec/models/frameworks/evaluation/status_changeable_spec.rb b/spec/models/frameworks/evaluation/status_changeable_spec.rb new file mode 100644 index 000000000..c794d4af8 --- /dev/null +++ b/spec/models/frameworks/evaluation/status_changeable_spec.rb @@ -0,0 +1,45 @@ +require "rails_helper" + +describe Frameworks::Evaluation::StatusChangeable do + subject(:evaluation) { Frameworks::Evaluation.new(framework:, status: evaluation_status) } + + describe "starting the evaluation" do + let(:framework) { create(:frameworks_framework, status: "pending_evaluation") } + let(:evaluation_status) { "draft" } + + it "updates the frameworks status to evaluating" do + evaluation.start! + expect(framework.reload.status).to eq("evaluating") + end + end + + describe "approving the evaluation" do + let(:framework) { create(:frameworks_framework, status: "evaluating") } + let(:evaluation_status) { "in_progress" } + + it "updates the frameworks status to dfe-approved" do + evaluation.approve! + expect(framework.reload.status).to eq("dfe_approved") + end + end + + describe "disaproving the evaluation" do + let(:framework) { create(:frameworks_framework, status: "evaluating") } + let(:evaluation_status) { "in_progress" } + + it "updates the frameworks status to not approved" do + evaluation.disapprove! + expect(framework.reload.status).to eq("not_approved") + end + end + + describe "cancelling the evaluation" do + let(:framework) { create(:frameworks_framework, status: "evaluating") } + let(:evaluation_status) { "in_progress" } + + it "updates the frameworks status to not approved" do + evaluation.cancel! + expect(framework.reload.status).to eq("not_approved") + end + end +end diff --git a/spec/models/frameworks/evaluation_spec.rb b/spec/models/frameworks/evaluation_spec.rb new file mode 100644 index 000000000..50aa5a9de --- /dev/null +++ b/spec/models/frameworks/evaluation_spec.rb @@ -0,0 +1,5 @@ +require "rails_helper" + +RSpec.describe Frameworks::Evaluation, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/frameworks/framework/activity_log_presentable_spec.rb b/spec/models/frameworks/framework/activity_log_presentable_spec.rb new file mode 100644 index 000000000..d328d9b39 --- /dev/null +++ b/spec/models/frameworks/framework/activity_log_presentable_spec.rb @@ -0,0 +1,19 @@ +require "rails_helper" + +describe Frameworks::Framework::ActivityLogPresentable do + subject(:presentable) { Frameworks::Framework.new } + + describe "#specific_change_template_for" do + it "returns nil by default" do + template = presentable.specific_change_template_for(Frameworks::ActivityLoggableVersion.new(object_changes: {})) + expect(template).to be_nil + end + + context "when the only changes are to the status field" do + it "returns frameworks/status" do + template = presentable.specific_change_template_for(Frameworks::ActivityLoggableVersion.new(object_changes: { status: [] })) + expect(template).to eq("frameworks/status") + end + end + end +end