Skip to content

Commit

Permalink
Basic models and validations for recruitment cycle timetables
Browse files Browse the repository at this point in the history
  • Loading branch information
elceebee committed Jan 30, 2025
1 parent 2e3cb0b commit 6328bb0
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 19 deletions.
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: 9 additions & 9 deletions db/migrate/20250127161433_create_recruitment_cycle_timetable.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
class CreateRecruitmentCycleTimetable < ActiveRecord::Migration[8.0]
def change
create_table :recruitment_cycle_timetables do |t|
t.string :type
t.datetime :find_opens
t.datetime :apply_opens
t.datetime :apply_deadline
t.datetime :reject_by_default
t.datetime :decline_by_default
t.datetime :find_closes
t.daterange :christmas_holiday
t.daterange :easter_holiday
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
Expand Down
18 changes: 9 additions & 9 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -751,18 +751,18 @@
end

create_table "recruitment_cycle_timetables", force: :cascade do |t|
t.string "type"
t.datetime "find_opens"
t.datetime "apply_opens"
t.datetime "apply_deadline"
t.datetime "reject_by_default"
t.datetime "decline_by_default"
t.datetime "find_closes"
t.daterange "christmas_holiday"
t.daterange "easter_holiday"
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|
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
2 changes: 1 addition & 1 deletion spec/workers/detect_invariants_hourly_check_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
One or more application choices are still in `awaiting_references` or
`application_complete` state, but all these states have been removed:
#{HostingEnvironment.application_url}/support/application-choices/#{application_choice_bad.id}
#{HostingEnvironment.application_url}/support/applicarection-choices/#{application_choice_bad.id}
#{HostingEnvironment.application_url}/support/application-choices/#{application_choice_bad_too.id}
MSG
),
Expand Down

0 comments on commit 6328bb0

Please sign in to comment.