diff --git a/app/models/suitability_record.rb b/app/models/suitability_record.rb new file mode 100644 index 000000000..f77f10cf8 --- /dev/null +++ b/app/models/suitability_record.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: suitability_records +# +# id :bigint not null, primary key +# archive_note :text default(""), not null +# archived_at :datetime +# country_code :text default(""), not null +# date_of_birth :date +# note :text not null +# created_at :datetime not null +# updated_at :datetime not null +# +class SuitabilityRecord < ApplicationRecord + has_and_belongs_to_many :application_forms + has_many :emails, class_name: "SuitabilityRecord::Email" + has_many :names, class_name: "SuitabilityRecord::Name" + + scope :active, -> { where(archived_at: nil) } + + validates :note, presence: true + validates :archive_note, presence: true, if: :archived? + + def archived? + archived_at.present? + end + + class Email < ApplicationRecord + self.table_name = "suitability_record_emails" + + validates :value, presence: true + validates :canonical, presence: true + + def value=(value) + self.value = value + self.canonical = EmailAddress.canonical(value) + end + end + + class Name < ApplicationRecord + self.table_name = "suitability_record_names" + + validates :value, presence: true + end +end diff --git a/config/analytics.yml b/config/analytics.yml index 70c8cd52d..de44e22ef 100644 --- a/config/analytics.yml +++ b/config/analytics.yml @@ -55,6 +55,9 @@ - written_statement_confirmation - written_statement_optional - written_statement_status + :application_forms_suitability_records: + - application_form_id + - suitability_record_id :assessment_sections: - assessed_at - assessment_id @@ -314,6 +317,26 @@ - updated_at - verify_permission - withdraw_permission + :suitability_record_emails: + - canonical + - created_at + - id + - suitability_record_id + - updated_at + - value + :suitability_record_names: + - created_at + - id + - suitability_record_id + - updated_at + - value + :suitability_records: + - archived_at + - country_code + - created_at + - date_of_birth + - id + - updated_at :teachers: - canonical_email - created_at diff --git a/config/analytics_blocklist.yml b/config/analytics_blocklist.yml index 10756130f..8e68637c0 100644 --- a/config/analytics_blocklist.yml +++ b/config/analytics_blocklist.yml @@ -86,6 +86,9 @@ - invitation_token - reset_password_token - unlock_token + :suitability_records: + - archive_note + - note :teachers: - access_your_teaching_qualifications_url :timeline_events: diff --git a/config/analytics_hidden_pii.yml b/config/analytics_hidden_pii.yml index ce6529335..de98a3e60 100644 --- a/config/analytics_hidden_pii.yml +++ b/config/analytics_hidden_pii.yml @@ -17,6 +17,13 @@ - last_sign_in_ip - name - unconfirmed_email + :suitability_record_emails: + - canonical + - value + :suitability_record_names: + - value + :suitability_records: + - date_of_birth :teachers: - canonical_email - current_sign_in_ip diff --git a/db/migrate/20240711100342_add_suitability_records.rb b/db/migrate/20240711100342_add_suitability_records.rb new file mode 100644 index 000000000..0ee040065 --- /dev/null +++ b/db/migrate/20240711100342_add_suitability_records.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class AddSuitabilityRecords < ActiveRecord::Migration[7.1] + def change + create_table :suitability_records do |t| + t.text :country_code, limit: 7, null: false, default: "" + t.date :date_of_birth + + t.text :note, null: false + + t.datetime :archived_at + t.text :archive_note, null: false, default: "" + + t.timestamps + end + + create_table :suitability_record_emails do |t| + t.text :value, null: false + t.text :canonical, null: false + t.references :suitability_record, null: false, foreign_key: true + t.timestamps + end + + create_table :suitability_record_names do |t| + t.text :value, null: false + t.references :suitability_record, null: false, foreign_key: true + t.timestamps + end + + create_join_table :suitability_records, :application_forms + end +end diff --git a/db/schema.rb b/db/schema.rb index 9a2f843aa..c79435680 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.1].define(version: 2024_07_05_082709) do +ActiveRecord::Schema[7.1].define(version: 2024_07_11_100342) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -110,6 +110,11 @@ t.index ["teacher_id"], name: "index_application_forms_on_teacher_id" end + create_table "application_forms_suitability_records", id: false, force: :cascade do |t| + t.bigint "suitability_record_id", null: false + t.bigint "application_form_id", null: false + end + create_table "assessment_sections", force: :cascade do |t| t.bigint "assessment_id", null: false t.string "key", null: false @@ -483,6 +488,33 @@ t.index ["unlock_token"], name: "index_staff_on_unlock_token", unique: true end + create_table "suitability_record_emails", force: :cascade do |t| + t.text "value", null: false + t.text "canonical", null: false + t.bigint "suitability_record_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["suitability_record_id"], name: "index_suitability_record_emails_on_suitability_record_id" + end + + create_table "suitability_record_names", force: :cascade do |t| + t.text "value", null: false + t.bigint "suitability_record_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["suitability_record_id"], name: "index_suitability_record_names_on_suitability_record_id" + end + + create_table "suitability_records", force: :cascade do |t| + t.text "country_code", default: "", null: false + t.date "date_of_birth" + t.text "note", null: false + t.datetime "archived_at" + t.text "archive_note", default: "", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "teachers", force: :cascade do |t| t.string "email", null: false t.datetime "created_at", null: false @@ -597,6 +629,8 @@ add_foreign_key "reference_requests", "work_histories" add_foreign_key "regions", "countries" add_foreign_key "selected_failure_reasons", "assessment_sections" + add_foreign_key "suitability_record_emails", "suitability_records" + add_foreign_key "suitability_record_names", "suitability_records" add_foreign_key "timeline_events", "application_forms" add_foreign_key "timeline_events", "assessment_sections" add_foreign_key "timeline_events", "assessments" diff --git a/docs/diagram.pdf b/docs/diagram.pdf index 5df6287e3..fed4da43f 100644 Binary files a/docs/diagram.pdf and b/docs/diagram.pdf differ diff --git a/spec/factories/suitability_records.rb b/spec/factories/suitability_records.rb new file mode 100644 index 000000000..ebc17d781 --- /dev/null +++ b/spec/factories/suitability_records.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :suitability_record do + note { Faker::Lorem.sentence } + + trait :archived do + archived_at { Time.zone.now } + archive_note { Faker::Lorem.sentence } + end + end + + factory :suitability_record_email do + association :suitability_record + + value { Faker::Name.name } + end + + factory :suitability_record_name do + association :suitability_record + + value { Faker::Internet.email } + canonical { EmailAddress.canonical(value) } + end +end diff --git a/spec/models/suitability_record_spec.rb b/spec/models/suitability_record_spec.rb new file mode 100644 index 000000000..e2a6918d5 --- /dev/null +++ b/spec/models/suitability_record_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe SuitabilityRecord, type: :model do + subject(:suitability_record) { build(:suitability_record) } + + describe "validations" do + it { is_expected.to be_valid } + + it { is_expected.to validate_presence_of(:note) } + it { is_expected.not_to validate_presence_of(:archive_note) } + + context "when archived" do + before { suitability_record.archived_at = Time.zone.now } + + it { is_expected.to validate_presence_of(:archive_note) } + end + end +end