diff --git a/app/components/status_tag/component.rb b/app/components/status_tag/component.rb index ceeb8d70aa..6c56e0129c 100644 --- a/app/components/status_tag/component.rb +++ b/app/components/status_tag/component.rb @@ -21,6 +21,7 @@ def classes COLOURS = { accepted: "green", + assessment: "blue", assessment_in_progress: "blue", awarded: "green", awarded_pending_checks: "turquoise", @@ -37,6 +38,7 @@ def classes overdue_qualification: "pink", overdue_reference: "pink", potential_duplicate_in_dqt: "pink", + pre_assessment: "pink", preliminary_check: "pink", received: "purple", received_further_information: "purple", @@ -45,8 +47,10 @@ def classes received_reference: "purple", rejected: "red", requested: "yellow", + review: "purple", submitted: "grey", valid: "green", + verification: "yellow", waiting_on: "yellow", waiting_on_further_information: "yellow", waiting_on_professional_standing: "yellow", diff --git a/app/components/timeline_entry/component.rb b/app/components/timeline_entry/component.rb index d1870d1cd1..58121b0198 100644 --- a/app/components/timeline_entry/component.rb +++ b/app/components/timeline_entry/component.rb @@ -170,5 +170,24 @@ def action_required_by_changed_vars end, } end + + def stage_changed_vars + { + old_stage: + render( + StatusTag::Component.new( + status: timeline_event.old_value, + class_context: "timeline-event", + ), + ).strip, + new_stage: + render( + StatusTag::Component.new( + status: timeline_event.new_value, + class_context: "timeline-event", + ), + ).strip, + } + end end end diff --git a/app/lib/application_form_status_updater.rb b/app/lib/application_form_status_updater.rb index 30b061175f..34466c9921 100644 --- a/app/lib/application_form_status_updater.rb +++ b/app/lib/application_form_status_updater.rb @@ -43,6 +43,15 @@ def call new_value: action_required_by, ) end + + if (old_stage = application_form.stage) != stage + application_form.update!(stage:) + create_timeline_event( + event_type: "stage_changed", + old_value: old_stage, + new_value: stage, + ) + end end end @@ -182,6 +191,40 @@ def action_required_by end end + def stage + @stage ||= + if application_form.withdrawn_at.present? || + application_form.declined_at.present? || + application_form.awarded_at.present? + "completed" + elsif dqt_trn_request.present? + "review" + elsif preliminary_check? || + ( + teaching_authority_provides_written_statement && + waiting_on_professional_standing + ) + "pre_assessment" + elsif overdue_professional_standing || overdue_qualification || + overdue_reference || + ( + !teaching_authority_provides_written_statement && + received_professional_standing + ) || received_qualification || received_reference || + waiting_on_professional_standing || waiting_on_qualification || + waiting_on_reference + "verification" + elsif overdue_further_information || received_further_information || + waiting_on_further_information || + assessment&.any_not_preliminary_section_finished? + "assessment" + elsif application_form.submitted_at.present? + "not_started" + else + "draft" + end + end + delegate :assessment, :dqt_trn_request, :region, diff --git a/app/models/application_form.rb b/app/models/application_form.rb index f454daf7d1..77ac8a4683 100644 --- a/app/models/application_form.rb +++ b/app/models/application_form.rb @@ -43,6 +43,7 @@ # registration_number :text # registration_number_status :string default("not_started"), not null # requires_preliminary_check :boolean default(FALSE), not null +# stage :string default("draft"), not null # status :string default("draft"), not null # subjects :text default([]), not null, is an Array # subjects_status :string default("not_started"), not null @@ -76,6 +77,7 @@ # index_application_forms_on_reference (reference) UNIQUE # index_application_forms_on_region_id (region_id) # index_application_forms_on_reviewer_id (reviewer_id) +# index_application_forms_on_stage (stage) # index_application_forms_on_status (status) # index_application_forms_on_teacher_id (teacher_id) # @@ -132,6 +134,17 @@ class ApplicationForm < ApplicationRecord }, _prefix: true + enum stage: { + draft: "draft", + pre_assessment: "pre_assessment", + not_started: "not_started", + assessment: "assessment", + verification: "verification", + review: "review", + completed: "completed", + }, + _suffix: true + enum status: { draft: "draft", submitted: "submitted", diff --git a/app/models/timeline_event.rb b/app/models/timeline_event.rb index c41481fde2..8ffb896733 100644 --- a/app/models/timeline_event.rb +++ b/app/models/timeline_event.rb @@ -75,6 +75,7 @@ class TimelineEvent < ApplicationRecord requestable_received: "requestable_received", requestable_requested: "requestable_requested", reviewer_assigned: "reviewer_assigned", + stage_changed: "stage_changed", state_changed: "state_changed", } @@ -152,11 +153,17 @@ class TimelineEvent < ApplicationRecord validates :old_value, :new_value, presence: true, - if: -> { action_required_by_changed? || information_changed? } + if: -> do + action_required_by_changed? || information_changed? || + stage_changed? + end validates :old_value, :new_value, absence: true, - unless: -> { action_required_by_changed? || information_changed? } + unless: -> do + action_required_by_changed? || information_changed? || + stage_changed? + end validates :column_name, presence: true, if: :information_changed? validates :work_history_id, :column_name, diff --git a/config/analytics.yml b/config/analytics.yml index f0c015e04d..2eba07d5dd 100644 --- a/config/analytics.yml +++ b/config/analytics.yml @@ -68,6 +68,7 @@ - registration_number_status - requires_preliminary_check - reviewer_id + - stage - status - subjects - subjects_status diff --git a/config/locales/components.en.yml b/config/locales/components.en.yml index 399f9249f9..adafbdb5e5 100644 --- a/config/locales/components.en.yml +++ b/config/locales/components.en.yml @@ -2,6 +2,7 @@ en: components: status_tag: accepted: Accepted + assessment: Assessment assessment_in_progress: Assessment in progress awarded: Awarded awarded_pending_checks: Award pending @@ -19,6 +20,7 @@ en: overdue_qualification: Overdue qualification overdue_reference: Overdue reference potential_duplicate_in_dqt: Potential duplication in DQT + pre_assessment: Pre-assessment preliminary_check: Preliminary check received: Received received_further_information: Received further information @@ -27,8 +29,10 @@ en: received_reference: Received reference rejected: Rejected requested: Waiting on + review: Review submitted: Not started valid: Valid + verification: Verification waiting_on: Waiting on waiting_on_further_information: Waiting on further information waiting_on_professional_standing: Waiting on professional standing @@ -39,14 +43,14 @@ en: timeline_entry: title: action_required_by_changed: Action required by changed - assessor_assigned: Assessor assigned - reviewer_assigned: Reviewer assigned - state_changed: Status changed - assessment_section_completed: Section completed - note_created: Note created - email_sent: Email sent age_range_subjects_verified: Age range and subjects verified + assessment_section_completed: Section completed assessment_section_recorded: Assessment section recorded + assessor_assigned: Assessor assigned + email_sent: Email sent + information_changed: Information changed after submission + note_created: Note created + reviewer_assigned: Reviewer assigned requestable_requested: FurtherInformationRequest: Further information requested ProfessionalStandingRequest: Professional standing requested @@ -67,14 +71,14 @@ en: ProfessionalStandingRequest: Professional standing assessed QualificationRequest: Qualification assessed ReferenceRequest: Reference assessed - information_changed: Information changed after submission + stage_changed: Stage changed + state_changed: Status changed description: action_required_by_changed: Application requires %{action} action. assessor_assigned: "%{assignee_name} is assigned as the assessor." - reviewer_assigned: "%{assignee_name} is assigned as the reviewer." - state_changed: Status changed from %{old_state} to %{new_state}. - note_created: "%{text}" email_sent: "%{subject}" + note_created: "%{text}" + reviewer_assigned: "%{assignee_name} is assigned as the reviewer." requestable_requested: FurtherInformationRequest: Further information has been requested. ProfessionalStandingRequest: The professional standing has been requested. @@ -95,6 +99,8 @@ en: ProfessionalStandingRequest: The professional standing request has been assessed. QualificationRequest: A qualification has been assessed. ReferenceRequest: A reference has been assessed. + stage_changed: Stage changed from %{old_stage} to %{new_stage}. + state_changed: Status changed from %{old_state} to %{new_state}. columns: contact_email: Reference email address contact_job: Reference job diff --git a/db/migrate/20230914084340_add_stage_to_application_forms.rb b/db/migrate/20230914084340_add_stage_to_application_forms.rb new file mode 100644 index 0000000000..1a18a02f91 --- /dev/null +++ b/db/migrate/20230914084340_add_stage_to_application_forms.rb @@ -0,0 +1,10 @@ +class AddStageToApplicationForms < ActiveRecord::Migration[7.0] + def change + add_column :application_forms, + :stage, + :string, + default: "draft", + null: false + add_index :application_forms, :stage + end +end diff --git a/db/schema.rb b/db/schema.rb index 356d3d0673..8620b61695 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_09_12_144508) do +ActiveRecord::Schema[7.0].define(version: 2023_09_14_084340) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -106,6 +106,7 @@ t.jsonb "dqt_match", default: {} t.datetime "withdrawn_at" t.string "action_required_by", default: "none", null: false + t.string "stage", default: "draft", null: false t.index ["action_required_by"], name: "index_application_forms_on_action_required_by" t.index ["assessor_id"], name: "index_application_forms_on_assessor_id" t.index ["english_language_provider_id"], name: "index_application_forms_on_english_language_provider_id" @@ -114,6 +115,7 @@ t.index ["reference"], name: "index_application_forms_on_reference", unique: true t.index ["region_id"], name: "index_application_forms_on_region_id" t.index ["reviewer_id"], name: "index_application_forms_on_reviewer_id" + t.index ["stage"], name: "index_application_forms_on_stage" t.index ["status"], name: "index_application_forms_on_status" t.index ["teacher_id"], name: "index_application_forms_on_teacher_id" end diff --git a/spec/components/timeline_entry_spec.rb b/spec/components/timeline_entry_spec.rb index 5b8d9692da..051a83ef7d 100644 --- a/spec/components/timeline_entry_spec.rb +++ b/spec/components/timeline_entry_spec.rb @@ -518,4 +518,24 @@ expect(component.text).to include(creator.name) end end + + context "stage changed" do + let(:timeline_event) { create(:timeline_event, :stage_changed) } + let(:old_stage) do + I18n.t("components.status_tag.#{timeline_event.old_value}") + end + let(:new_stage) do + I18n.t("components.status_tag.#{timeline_event.new_value}") + end + + it "describes the event" do + expect(component.text.squish).to include( + "Stage changed from #{old_stage} to #{new_stage}", + ) + end + + it "attributes to the creator" do + expect(component.text).to include(creator.name) + end + end end diff --git a/spec/factories/application_forms.rb b/spec/factories/application_forms.rb index 31f30f0b19..b2023353ec 100644 --- a/spec/factories/application_forms.rb +++ b/spec/factories/application_forms.rb @@ -43,6 +43,7 @@ # registration_number :text # registration_number_status :string default("not_started"), not null # requires_preliminary_check :boolean default(FALSE), not null +# stage :string default("draft"), not null # status :string default("draft"), not null # subjects :text default([]), not null, is an Array # subjects_status :string default("not_started"), not null @@ -76,6 +77,7 @@ # index_application_forms_on_reference (reference) UNIQUE # index_application_forms_on_region_id (region_id) # index_application_forms_on_reviewer_id (reviewer_id) +# index_application_forms_on_stage (stage) # index_application_forms_on_status (status) # index_application_forms_on_teacher_id (teacher_id) # @@ -140,8 +142,42 @@ action_required_by { "none" } end - trait :submitted do + trait :draft_stage do + stage { "draft" } + action_required_by_none + end + + trait :pre_assessment_stage do + stage { "pre_assessment" } + action_required_by_admin + end + + trait :not_started_stage do + stage { "not_started" } action_required_by_assessor + end + + trait :assessment_stage do + stage { "assessment" } + action_required_by_assessor + end + + trait :verification_stage do + stage { "verification" } + end + + trait :review_stage do + stage { "review" } + action_required_by_assessor + end + + trait :completed_stage do + stage { "completed" } + action_required_by_none + end + + trait :submitted do + not_started_stage status { "submitted" } submitted_at { Time.zone.now } working_days_since_submission { 0 } @@ -160,59 +196,65 @@ trait :preliminary_check do submitted - action_required_by_admin + pre_assessment_stage requires_preliminary_check { true } status { "preliminary_check" } end trait :assessment_in_progress do submitted + assessment_stage status { "assessment_in_progress" } end trait :waiting_on do submitted action_required_by_external + verification_stage status { "waiting_on" } end trait :received do submitted + verification_stage status { "received" } end trait :overdue do submitted + verification_stage status { "overdue" } end trait :awarded_pending_checks do submitted + review_stage status { "awarded_pending_checks" } end trait :potential_duplicate_in_dqt do submitted + review_stage status { "potential_duplicate_in_dqt" } end trait :awarded do submitted - action_required_by_none + completed_stage status { "awarded" } awarded_at { Time.zone.now } end trait :declined do submitted - action_required_by_none + completed_stage status { "declined" } declined_at { Time.zone.now } end trait :withdrawn do submitted - action_required_by_none + completed_stage status { "withdrawn" } withdrawn_at { Time.zone.now } end diff --git a/spec/factories/timeline_events.rb b/spec/factories/timeline_events.rb index d9a2af715f..89c356e78e 100644 --- a/spec/factories/timeline_events.rb +++ b/spec/factories/timeline_events.rb @@ -111,5 +111,11 @@ old_value { %w[admin assessor external none].sample } new_value { %w[admin assessor external none].sample } end + + trait :stage_changed do + event_type { "stage_changed" } + old_value { ApplicationForm.stages.values.sample } + new_value { ApplicationForm.stages.values.sample } + end end end diff --git a/spec/lib/application_form_status_updater_spec.rb b/spec/lib/application_form_status_updater_spec.rb index 45e05b8a41..2d785a1a9b 100644 --- a/spec/lib/application_form_status_updater_spec.rb +++ b/spec/lib/application_form_status_updater_spec.rb @@ -40,6 +40,22 @@ end end + shared_examples "changes stage" do |new_stage| + it "changes status to #{new_stage}" do + expect { call }.to change(application_form, :stage).to(new_stage) + end + + it "records a timeline event" do + expect { call }.to have_recorded_timeline_event( + :stage_changed, + creator: user, + application_form:, + old_value: "draft", + new_value: new_stage, + ) + end + end + shared_examples "changes status" do |new_status| it "changes status to #{new_status}" do expect { call }.to change(application_form, :status).to(new_status) @@ -66,6 +82,7 @@ end include_examples "changes action required by", "assessor" + include_examples "changes stage", "review" include_examples "changes status", "potential_duplicate_in_dqt" end @@ -78,6 +95,7 @@ end include_examples "doesn't change action required by" + include_examples "changes stage", "completed" include_examples "changes status", "withdrawn" end @@ -90,6 +108,7 @@ end include_examples "doesn't change action required by" + include_examples "changes stage", "completed" include_examples "changes status", "declined" end @@ -102,6 +121,7 @@ end include_examples "doesn't change action required by" + include_examples "changes stage", "completed" include_examples "changes status", "awarded" end @@ -112,6 +132,7 @@ end include_examples "changes action required by", "assessor" + include_examples "changes stage", "review" include_examples "changes status", "awarded_pending_checks" end @@ -124,6 +145,7 @@ end include_examples "changes action required by", "assessor" + include_examples "changes stage", "assessment" include_examples "changes status", "received" it "changes received_further_information" do @@ -143,6 +165,7 @@ end include_examples "changes action required by", "external" + include_examples "changes stage", "assessment" include_examples "changes status", "waiting_on" it "changes waiting_on_further_information" do @@ -162,6 +185,7 @@ end include_examples "changes action required by", "external" + include_examples "changes stage", "verification" include_examples "changes status", "waiting_on" it "changes waiting_on_professional_standing" do @@ -185,6 +209,7 @@ end include_examples "changes action required by", "assessor" + include_examples "changes stage", "not_started" include_examples "changes status", "submitted" it "doesn't change received_professional_standing" do @@ -202,6 +227,7 @@ end include_examples "changes action required by", "assessor" + include_examples "changes stage", "verification" include_examples "changes status", "received" it "changes received_professional_standing" do @@ -222,6 +248,7 @@ end include_examples "changes action required by", "assessor" + include_examples "changes stage", "verification" include_examples "changes status", "received" it "changes received_further_information" do @@ -241,6 +268,7 @@ end include_examples "changes action required by", "external" + include_examples "changes stage", "verification" include_examples "changes status", "waiting_on" it "changes waiting_on_qualification" do @@ -275,6 +303,7 @@ end include_examples "changes action required by", "external" + include_examples "changes stage", "verification" include_examples "changes status", "waiting_on" it "doesn't change received_reference" do @@ -304,6 +333,7 @@ context "and it's the only reference request" do include_examples "changes action required by", "assessor" + include_examples "changes stage", "verification" include_examples "changes status", "received" it "changes received_reference" do @@ -318,6 +348,7 @@ before { create(:reference_request, :requested, assessment:) } include_examples "changes action required by", "external" + include_examples "changes stage", "verification" include_examples "changes status", "waiting_on" it "doesn't change received_reference" do @@ -347,6 +378,7 @@ end include_examples "changes action required by", "assessor" + include_examples "changes stage", "verification" include_examples "changes status", "received" it "changes received_reference" do @@ -366,6 +398,7 @@ end include_examples "changes action required by", "external" + include_examples "changes stage", "verification" include_examples "changes status", "waiting_on" it "changes waiting_on_reference" do @@ -382,6 +415,7 @@ end include_examples "changes action required by", "assessor" + include_examples "changes stage", "assessment" include_examples "changes status", "assessment_in_progress" end @@ -389,6 +423,7 @@ before { application_form.update!(submitted_at: Time.zone.now) } include_examples "changes action required by", "assessor" + include_examples "changes stage", "not_started" include_examples "changes status", "submitted" end @@ -400,6 +435,13 @@ it "doesn't record a timeline event" do expect { call }.to_not have_recorded_timeline_event(:state_changed) end + it "doesn't change the stage from draft" do + expect { call }.to_not change(application_form, :stage).from("draft") + end + + it "doesn't record a timeline event" do + expect { call }.to_not have_recorded_timeline_event(:stage_changed) + end include_examples "doesn't change action required by" end @@ -419,6 +461,7 @@ end include_examples "changes action required by", "admin" + include_examples "changes stage", "pre_assessment" include_examples "changes status", "preliminary_check" context "when teaching authority provides written statement" do @@ -430,12 +473,14 @@ end include_examples "changes action required by", "admin" + include_examples "changes stage", "pre_assessment" include_examples "changes status", "preliminary_check" context "when the preliminary check has passed" do before { preliminary_assessment_section.update!(passed: true) } include_examples "changes action required by", "external" + include_examples "changes stage", "pre_assessment" include_examples "changes status", "waiting_on" end @@ -449,12 +494,14 @@ end include_examples "changes action required by", "admin" + include_examples "changes stage", "pre_assessment" include_examples "changes status", "preliminary_check" context "and the application form is declined" do before { application_form.update!(declined_at: Time.zone.now) } include_examples "doesn't change action required by" + include_examples "changes stage", "completed" include_examples "changes status", "declined" end end diff --git a/spec/models/application_form_spec.rb b/spec/models/application_form_spec.rb index d1b6c51b36..ce0249496b 100644 --- a/spec/models/application_form_spec.rb +++ b/spec/models/application_form_spec.rb @@ -43,6 +43,7 @@ # registration_number :text # registration_number_status :string default("not_started"), not null # requires_preliminary_check :boolean default(FALSE), not null +# stage :string default("draft"), not null # status :string default("draft"), not null # subjects :text default([]), not null, is an Array # subjects_status :string default("not_started"), not null @@ -76,6 +77,7 @@ # index_application_forms_on_reference (reference) UNIQUE # index_application_forms_on_region_id (region_id) # index_application_forms_on_reviewer_id (reviewer_id) +# index_application_forms_on_stage (stage) # index_application_forms_on_status (status) # index_application_forms_on_teacher_id (teacher_id) # @@ -113,6 +115,19 @@ .with_prefix .backed_by_column_of_type(:string) + is_expected.to define_enum_for(:stage) + .with_values( + draft: "draft", + pre_assessment: "pre_assessment", + not_started: "not_started", + assessment: "assessment", + verification: "verification", + review: "review", + completed: "completed", + ) + .with_suffix + .backed_by_column_of_type(:string) + is_expected.to define_enum_for(:status).with_values( draft: "draft", submitted: "submitted", diff --git a/spec/models/timeline_event_spec.rb b/spec/models/timeline_event_spec.rb index 2f4bea97c9..7325614cb2 100644 --- a/spec/models/timeline_event_spec.rb +++ b/spec/models/timeline_event_spec.rb @@ -91,6 +91,7 @@ requestable_received: "requestable_received", requestable_requested: "requestable_requested", reviewer_assigned: "reviewer_assigned", + stage_changed: "stage_changed", state_changed: "state_changed", ).backed_by_column_of_type(:string) end @@ -410,5 +411,28 @@ it { is_expected.to validate_presence_of(:old_value) } it { is_expected.to validate_presence_of(:new_value) } end + + context "with a stage changed event type" do + before { timeline_event.event_type = :stage_changed } + + it { is_expected.to validate_absence_of(:assignee) } + it { is_expected.to validate_absence_of(:old_state) } + it { is_expected.to validate_absence_of(:new_state) } + it { is_expected.to validate_absence_of(:assessment_section) } + it { is_expected.to validate_absence_of(:note) } + it { is_expected.to validate_absence_of(:mailer_class_name) } + it { is_expected.to validate_absence_of(:mailer_action_name) } + it { is_expected.to validate_absence_of(:message_subject) } + it { is_expected.to validate_absence_of(:assessment) } + it { is_expected.to validate_absence_of(:age_range_min) } + it { is_expected.to validate_absence_of(:age_range_max) } + it { is_expected.to validate_absence_of(:subjects) } + it { is_expected.to validate_absence_of(:requestable_id) } + it { is_expected.to validate_absence_of(:requestable_type) } + it { is_expected.to validate_absence_of(:work_history_id) } + it { is_expected.to validate_absence_of(:column_name) } + it { is_expected.to validate_presence_of(:old_value) } + it { is_expected.to validate_presence_of(:new_value) } + end end end