Skip to content

Commit

Permalink
Merge pull request #10315 from DFE-Digital/266-part-1-recruitment-cyc…
Browse files Browse the repository at this point in the history
…le-timetables-models-and-migrations

[266] part 1 recruitment cycle timetables models and migrations
  • Loading branch information
elceebee authored Jan 30, 2025
2 parents c154462 + cecdeb7 commit 4897aec
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 1 deletion.
80 changes: 80 additions & 0 deletions app/models/recruitment_cycle_timetable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
class RecruitmentCycleTimetable < ApplicationRecord
validates :recruitment_cycle_year,
:find_opens_at,
:apply_opens_at,
:apply_deadline_at,
:reject_by_default_at,
:decline_by_default_at,
:find_closes_at,
presence: true
validates :recruitment_cycle_year, uniqueness: { allow_nil: false }
validate :sequential_dates
validate :christmas_holiday_validation
validate :easter_holiday_validation

private

def christmas_holiday_validation
return if [christmas_holiday_range, find_opens_at, find_closes_at].any?(&:blank?)

holidays = Holidays.between(
christmas_holiday_range.first,
christmas_holiday_range.last, :gb
).map do |holiday|
holiday[:name]
end

if !christmas_holiday_range.in? cycle_range
errors.add(:christmas_holiday_range, :christmas_holiday_range_should_be_in_cycle)
elsif holidays.exclude? 'Christmas Day'
errors.add(:christmas_holiday_range, :christmas_holiday_range_should_include_christmas)
end
end

def easter_holiday_validation
return if [easter_holiday_range, find_opens_at, find_closes_at].any?(&:blank?)

holidays = Holidays.between(
easter_holiday_range.first,
easter_holiday_range.last, :gb
).map do |holiday|
holiday[:name]
end

if !easter_holiday_range.in? cycle_range
errors.add(:easter_holiday_range, :easter_holiday_range_should_be_within_cycle)

elsif holidays.exclude?('Easter Sunday')
errors.add(:easter_holiday_range, :easter_holiday_range_should_include_easter)
end
end

def cycle_range
find_opens_at..find_closes_at
end

def sequential_dates
required_dates = [
find_opens_at,
apply_opens_at,
apply_deadline_at,
reject_by_default_at,
decline_by_default_at,
find_closes_at,
]

return if required_dates.any?(&:blank?)

if find_opens_at.after? apply_opens_at
errors.add(:apply_opens_at, :apply_opens_after_find_opens)
elsif apply_opens_at.after? apply_deadline_at
errors.add(:apply_deadline_at, :apply_deadline_after_apply_opens)
elsif apply_deadline_at.after? reject_by_default_at
errors.add(:reject_by_default_at, :reject_by_default_after_apply_deadline)
elsif reject_by_default_at.after? decline_by_default_at
errors.add(:decline_by_default_at, :decline_by_default_after_reject_by_default)
elsif decline_by_default_at.after? find_closes_at
errors.add(:find_closes_at, :find_closes_after_decline_by_default)
end
end
end
14 changes: 14 additions & 0 deletions config/analytics.yml
Original file line number Diff line number Diff line change
Expand Up @@ -519,3 +519,17 @@ shared:
- created_at
- updated_at
- status
:recruitment_cycle_timetables:
- id
- find_opens_at
- apply_opens_at
- apply_deadline_at
- reject_by_default_at
- decline_by_default_at
- find_closes_at
- christmas_holiday_range
- easter_holiday_range
- recruitment_cycle_year
- created_at
- updated_at

18 changes: 18 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,24 @@ en:
pg_teaching_apprenticeship: Postgraduate teaching apprenticeship
errors:
models:
recruitment_cycle_timetable:
attributes:
apply_opens_at:
apply_opens_after_find_opens: Apply opens after find opens
apply_deadline_at:
apply_deadline_after_apply_opens: Apply deadline must be after apply opens
reject_by_default_at:
reject_by_default_after_apply_deadline: Reject by default must be after the apply deadline
decline_by_default_at:
decline_by_default_after_reject_by_default: Decline by default must be after reject by default
find_closes_at:
find_closes_after_decline_by_default: Find closes after decline by default
easter_holiday_range:
easter_holiday_range_should_be_within_cycle: Easter holiday range should be within cycle
easter_holiday_range_should_include_easter: Easter holiday range should include easter
christmas_holiday_range:
christmas_holiday_range_should_be_in_cycle: Christmas holiday range should be within cycle
christmas_holiday_range_should_include_christmas: Christmas holiday range should include Christmas day
candidate:
attributes:
email_address:
Expand Down
18 changes: 18 additions & 0 deletions db/migrate/20250127161433_create_recruitment_cycle_timetable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class CreateRecruitmentCycleTimetable < ActiveRecord::Migration[8.0]
def change
create_table :recruitment_cycle_timetables do |t|
t.datetime :find_opens_at
t.datetime :apply_opens_at
t.datetime :apply_deadline_at
t.datetime :reject_by_default_at
t.datetime :decline_by_default_at
t.datetime :find_closes_at
t.daterange :christmas_holiday_range
t.daterange :easter_holiday_range
t.integer :recruitment_cycle_year
t.index [:recruitment_cycle_year], unique: true

t.timestamps
end
end
end
17 changes: 16 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[8.0].define(version: 2025_01_23_164914) do
ActiveRecord::Schema[8.0].define(version: 2025_01_27_161433) do
create_sequence "qualifications_public_id_seq", start: 120000

# These are extensions that must be enabled in order to support this database
Expand Down Expand Up @@ -750,6 +750,21 @@
t.index ["vendor_id"], name: "index_providers_on_vendor_id"
end

create_table "recruitment_cycle_timetables", force: :cascade do |t|
t.datetime "find_opens_at"
t.datetime "apply_opens_at"
t.datetime "apply_deadline_at"
t.datetime "reject_by_default_at"
t.datetime "decline_by_default_at"
t.datetime "find_closes_at"
t.daterange "christmas_holiday_range"
t.daterange "easter_holiday_range"
t.integer "recruitment_cycle_year"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["recruitment_cycle_year"], name: "index_recruitment_cycle_timetables_on_recruitment_cycle_year", unique: true
end

create_table "reference_tokens", force: :cascade do |t|
t.bigint "application_reference_id", null: false
t.string "hashed_token", null: false
Expand Down
Binary file modified docs/domain-model.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions spec/factories/recruitment_cycle_timetable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FactoryBot.define do
factory :recruitment_cycle_timetable do
recruitment_cycle_year { CycleTimetable.current_year }

find_opens_at { CycleTimetable.find_opens }
apply_opens_at { CycleTimetable.apply_opens }
apply_deadline_at { CycleTimetable.apply_deadline }
reject_by_default_at { CycleTimetable.reject_by_default }
decline_by_default_at { CycleTimetable.decline_by_default_date }
find_closes_at { CycleTimetable.find_closes }
christmas_holiday_range { CycleTimetable.holidays[:christmas] }
easter_holiday_range { CycleTimetable.holidays[:easter] }
end
end
79 changes: 79 additions & 0 deletions spec/models/recruitment_cycle_timetable_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
require 'rails_helper'

RSpec.describe RecruitmentCycleTimetable do
describe 'validations' do
it { is_expected.to validate_presence_of(:recruitment_cycle_year) }
it { is_expected.to validate_presence_of(:find_opens_at) }
it { is_expected.to validate_presence_of(:apply_opens_at) }
it { is_expected.to validate_presence_of(:apply_deadline_at) }
it { is_expected.to validate_presence_of(:reject_by_default_at) }
it { is_expected.to validate_presence_of(:decline_by_default_at) }
it { is_expected.to validate_presence_of(:find_closes_at) }
it { is_expected.to validate_uniqueness_of(:recruitment_cycle_year) }

describe 'validates christmas range' do
it 'validates christmas range is within cycle' do
timetable = build(:recruitment_cycle_timetable)
timetable.christmas_holiday_range = timetable.find_opens_at - 3.days..timetable.christmas_holiday_range.last

expect(timetable.valid?).to be false
expect(timetable.errors[:christmas_holiday_range]).to eq ['Christmas holiday range should be within cycle']
end

it 'validates christmas range includes christmas day' do
timetable = build(:recruitment_cycle_timetable)
timetable.christmas_holiday_range =
timetable.christmas_holiday_range.last..timetable.christmas_holiday_range.last + 10.days

expect(timetable.valid?).to be false
expect(timetable.errors[:christmas_holiday_range])
.to eq ['Christmas holiday range should include Christmas day']
end
end

describe 'validates easter range' do
it 'validates easter range is within cycle' do
timetable = build(:recruitment_cycle_timetable)
timetable.easter_holiday_range = timetable.easter_holiday_range.last..timetable.find_closes_at + 2.days

expect(timetable.valid?).to be false
expect(timetable.errors[:easter_holiday_range]).to eq ['Easter holiday range should be within cycle']
end

it 'validates easter range includes Easter Day' do
timetable = build(:recruitment_cycle_timetable)
timetable.easter_holiday_range =
timetable.easter_holiday_range.last..timetable.easter_holiday_range.last + 10.days

expect(timetable.valid?).to be false
expect(timetable.errors[:easter_holiday_range]).to eq ['Easter holiday range should include easter']
end
end

describe 'validates sequential order of dates' do
it 'validates apply opens after find opens' do
timetable = build(:recruitment_cycle_timetable)
timetable.find_opens_at = timetable.apply_opens_at + 1.day

expect(timetable.valid?).to be false
expect(timetable.errors[:apply_opens_at]).to eq ['Apply opens after find opens']
end

it 'validates apply deadline is after apply opens' do
timetable = build(:recruitment_cycle_timetable)
timetable.apply_opens_at = timetable.apply_deadline_at + 1.day

expect(timetable.valid?).to be false
expect(timetable.errors[:apply_deadline_at]).to eq ['Apply deadline must be after apply opens']
end

it 'validates reject by default is after apply deadline' do
timetable = build(:recruitment_cycle_timetable)
timetable.apply_deadline_at = timetable.reject_by_default_at + 1.day

expect(timetable.valid?).to be false
expect(timetable.errors[:reject_by_default_at]).to eq ['Reject by default must be after the apply deadline']
end
end
end
end

0 comments on commit 4897aec

Please sign in to comment.