Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions app/controllers/admin/schools_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ def reopen
redirect_to admin_school_path(requested_resource)
end

def archive
if requested_resource.archive
flash[:notice] = t('administrate.controller.archive_school.success')
else
flash[:error] = requested_resource.errors.full_messages.to_sentence.presence || t('administrate.controller.archive_school.error')
end

redirect_to admin_school_path(requested_resource)
end

def default_sorting_attribute
:created_at
end
Expand Down
3 changes: 2 additions & 1 deletion app/controllers/api/teacher_invitations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ def show
end

def accept
role = Role.teacher.find_or_initialize_by(user_id: current_user.id, school: @invitation.school)
role = Role.unscoped.teacher.find_or_initialize_by(user_id: current_user.id, school: @invitation.school)
role.archived_at = nil
Comment thread
zetter-rpf marked this conversation as resolved.
if role.save
@invitation.update!(accepted_at: Time.current) if @invitation.accepted_at.blank?
head :ok
Expand Down
4 changes: 4 additions & 0 deletions app/dashboards/school_dashboard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ class SchoolDashboard < Administrate::BaseDashboard
lessons: Field::HasMany,
projects: Field::HasMany,
roles: SchoolRolesField,
student_count: Field::Number,
reference: Field::String,
district_name: Field::String,
district_nces_id: Field::String,
school_roll_number: Field::String,
verified_at: Field::DateTime,
rejected_at: Field::DateTime,
archived_at: Field::DateTime,
created_at: Field::DateTime,
updated_at: Field::DateTime,
user_origin: EnumField
Expand Down Expand Up @@ -64,6 +66,7 @@ class SchoolDashboard < Administrate::BaseDashboard
user_origin
creator
roles
student_count
creator_role
creator_department
reference
Expand All @@ -84,6 +87,7 @@ class SchoolDashboard < Administrate::BaseDashboard
updated_at
verified_at
rejected_at
archived_at
].freeze

# FORM_ATTRIBUTES
Expand Down
3 changes: 2 additions & 1 deletion app/jobs/salesforce/role_sync_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ class RoleSyncJob < SalesforceSyncJob
contact__r__pi_accounts_unique_id__c: :user_id,
editor__r__editoruuid__c: :school_id,
roletype__c: :role,
offboardedat__c: :archived_at,
Comment thread
cursor[bot] marked this conversation as resolved.
createdat__c: :created_at,
updatedat__c: :updated_at
}.freeze

def perform(role_id:)
role = ::Role.find(role_id)
role = ::Role.unscoped.find(role_id)

return if role.student?

Expand Down
3 changes: 2 additions & 1 deletion app/jobs/salesforce/school_sync_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ class SchoolSyncJob < SalesforceSyncJob
website__c: :website,
districtnamesupplied__c: :district_name,
ncesid__c: :district_nces_id,
schoolrollnumber__c: :school_roll_number
schoolrollnumber__c: :school_roll_number,
discardedat__c: :archived_at
}.freeze

def perform(school_id:, is_create: false)
Expand Down
2 changes: 1 addition & 1 deletion app/models/ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def initialize(user)
return unless user

define_authenticated_non_student_abilities(user)
user.schools.each do |school|
user.schools.active.each do |school|
define_school_student_abilities(user:, school:) if user.school_student?(school)
define_school_teacher_abilities(user:, school:) if user.school_teacher?(school)
define_school_owner_abilities(school:) if user.school_owner?(school)
Expand Down
6 changes: 6 additions & 0 deletions app/models/role.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ class Role < ApplicationRecord
validate :students_cannot_have_additional_roles
validate :users_can_only_have_roles_in_one_school

default_scope { where(archived_at: nil) }
Comment thread
zetter-rpf marked this conversation as resolved.
Comment thread
cursor[bot] marked this conversation as resolved.

has_paper_trail(
meta: {
meta_school_id: ->(cm) { cm.school&.id }
Expand All @@ -18,6 +20,10 @@ class Role < ApplicationRecord

after_commit :do_salesforce_sync, on: %i[create update], if: -> { FeatureFlags.salesforce_sync? && !student? }

def archive!
update!(archived_at: Time.zone.now)
end

private

def students_cannot_have_additional_roles
Expand Down
47 changes: 40 additions & 7 deletions app/models/school.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class School < ApplicationRecord

enum :user_origin, { for_education: 0, experience_cs: 1 }, default: :for_education, validate: true

scope :active, -> { where(rejected_at: nil, archived_at: nil) }

validates :name, presence: true
validates :website, presence: true, format: { with: VALID_URL_REGEX, message: I18n.t('validations.school.website') }
validates :address_line_1, presence: true
Expand All @@ -20,22 +22,22 @@ class School < ApplicationRecord
validates :postal_code, presence: true
validates :country_code, presence: true, inclusion: { in: ISO3166::Country.codes }
validates :reference,
uniqueness: { conditions: -> { where(rejected_at: nil) }, case_sensitive: false, allow_blank: true, message: I18n.t('validations.school.reference_urn_exists') },
uniqueness: { conditions: -> { active }, case_sensitive: false, allow_blank: true, message: I18n.t('validations.school.reference_urn_exists') },
format: { with: /\A\d{5,6}\z/, allow_nil: true, message: I18n.t('validations.school.reference') },
if: :united_kingdom?, on: :create, unless: :rejected?
if: :united_kingdom?, on: :create, unless: :hidden?
validates :district_nces_id,
format: { with: /\A\d{7}\z/, allow_nil: true, message: I18n.t('validations.school.district_nces_id') },
if: :united_states?, on: :create
validates :district_name, presence: true, if: :united_states?
validates :school_roll_number,
uniqueness: { conditions: -> { where(rejected_at: nil) }, case_sensitive: false, allow_blank: true, message: I18n.t('validations.school.school_roll_number_exists') },
uniqueness: { conditions: -> { active }, case_sensitive: false, allow_blank: true, message: I18n.t('validations.school.school_roll_number_exists') },
format: { with: /\A[0-9]+[A-Z]+\z/, allow_nil: true, message: I18n.t('validations.school.school_roll_number') },
presence: true, on: :create, if: :ireland?, unless: :rejected?
presence: true, on: :create, if: :ireland?, unless: :hidden?
validates :creator_id,
presence: true,
uniqueness: {
conditions: -> { where(rejected_at: nil) }
}, unless: :rejected?
conditions: -> { active }
}, unless: :hidden?
validates :creator_agree_authority, presence: true, acceptance: true
validates :creator_agree_terms_and_conditions, presence: true, acceptance: true
validates :creator_agree_responsible_safeguarding, presence: true, acceptance: true
Expand All @@ -44,6 +46,7 @@ class School < ApplicationRecord
validates :code,
uniqueness: { allow_nil: true },
format: { with: /\d\d-\d\d-\d\d/, allow_nil: true }
validate :cannot_archive_with_students, on: :archive
validate :verified_at_cannot_be_changed
validate :code_cannot_be_changed

Expand All @@ -59,7 +62,7 @@ class School < ApplicationRecord
after_commit -> { do_salesforce_sync(is_create: false) }, on: :update, if: -> { FeatureFlags.salesforce_sync? }

def self.find_for_user!(user)
school = Role.find_by(user_id: user.id)&.school || find_by(creator_id: user.id, rejected_at: nil)
school = Role.find_by(user_id: user.id)&.school || active.find_by(creator_id: user.id)
Comment thread
zetter-rpf marked this conversation as resolved.
raise ActiveRecord::RecordNotFound unless school

school
Expand All @@ -82,6 +85,10 @@ def verify!
update!(verified_at: Time.zone.now)
end

def hidden?
rejected? || archived?
end

def generate_code!
return code if code.present?

Expand All @@ -103,6 +110,28 @@ def reopen
update(rejected_at: nil)
end

def archive
return true if archived?

transaction do
self.archived_at = Time.zone.now
save!(context: :archive)
roles.where(role: %i[owner teacher]).find_each(&:archive!)
end

true
rescue ActiveRecord::RecordInvalid
false
end

def archived?
archived_at.present?
end

def student_count
roles.student.count
end

def postal_code=(str)
super(str.to_s.upcase)
end
Expand Down Expand Up @@ -155,6 +184,10 @@ def normalize_school_roll_number
self.school_roll_number = (school_roll_number.presence&.upcase)
end

def cannot_archive_with_students
errors.add(:base, 'Cannot archive a school with students') if roles.student.exists?
end

def verified_at_cannot_be_changed
errors.add(:verified_at, 'cannot be changed after verification') if verified_at_was.present? && verified_at_changed?
end
Expand Down
17 changes: 13 additions & 4 deletions app/views/admin/schools/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,32 @@ as well as a link to its edit page.
class: "button",
) if accessible_action?(page.resource, :edit) %>

<%= link_to(
<%= button_to(
t("administrate.actions.verify_school"),
verify_admin_school_path(page.resource),
class: "button button--verify",
method: :post,
style: "background-color: green; margin: 5px;",
data: { confirm: t("administrate.actions.confirm_verify_school") }
data: { turbo_confirm: t("administrate.actions.confirm_verify_school") }
) unless page.resource.verified? || page.resource.rejected? %>

<%= link_to(
<%= button_to(
t("administrate.actions.reopen_school"),
reopen_admin_school_path(page.resource),
class: "button button--verify",
method: :patch,
style: "background-color: darkorange; margin: 5px;",
data: { confirm: t("administrate.actions.confirm_reopen_school") }
data: { turbo_confirm: t("administrate.actions.confirm_reopen_school") }
) unless page.resource.verified? || !page.resource.rejected? %>

<%= button_to(
t("administrate.actions.archive_school"),
archive_admin_school_path(page.resource),
class: "button button--archive",
method: :patch,
style: "background-color: firebrick; margin: 5px;",
data: { turbo_confirm: t("administrate.actions.confirm_archive_school") }
) unless page.resource.archived? %>
</div>
</header>

Expand Down
5 changes: 5 additions & 0 deletions config/locales/admin/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ en:
confirm_verify_school: Are you sure you want to verify this school?
reopen_school: Reopen School
confirm_reopen_school: Are you sure you want to reopen this school?
archive_school: Archive School
confirm_archive_school: Are you sure you want to archive this school?
controller:
verify_school:
success: "Successfully verified."
error: "There was an error verifying the school"
reopen_school:
success: "Successfully reopened."
error: "There was an error reopening the school"
archive_school:
success: "Successfully archived."
error: "There was an error archiving the school"
activerecord:
attributes:
school:
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
member do
post :verify
patch :reopen
patch :archive
end
end

Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20260703145125_add_archived_at_to_schools.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddArchivedAtToSchools < ActiveRecord::Migration[8.1]
def change
add_column :schools, :archived_at, :datetime
end
end
Comment thread
zetter-rpf marked this conversation as resolved.
12 changes: 12 additions & 0 deletions db/migrate/20260703150521_update_school_index_conditions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class UpdateSchoolIndexConditions < ActiveRecord::Migration[8.1]
def change
remove_index :schools, :creator_id
add_index :schools, :creator_id, unique: true, where: "rejected_at IS NULL AND archived_at IS NULL"

remove_index :schools, :reference
add_index :schools, :reference, unique: true, where: "rejected_at IS NULL AND archived_at IS NULL"

remove_index :schools, :school_roll_number
add_index :schools, :school_roll_number, unique: true, where: "rejected_at IS NULL AND archived_at IS NULL"
end
end
Comment thread
zetter-rpf marked this conversation as resolved.
5 changes: 5 additions & 0 deletions db/migrate/20260703151018_add_archived_at_to_roles.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddArchivedAtToRoles < ActiveRecord::Migration[8.1]
def change
add_column :roles, :archived_at, :datetime
end
end
Comment thread
zetter-rpf marked this conversation as resolved.
10 changes: 6 additions & 4 deletions db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions spec/features/admin/schools_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
expect(response.body).to include(I18n.t('administrate.actions.verify_school'))
end

it 'includes link to archive school' do
expect(response.body).to include(I18n.t('administrate.actions.archive_school'))
end

it 'does not include a link to search for this school by its ZIP code in the NCES public schools database' do
expect(response.body).not_to include('Search for this school in the NCES database')
end
Expand Down Expand Up @@ -201,6 +205,34 @@
end
end

describe 'PUT #archive' do
let(:creator) { create(:user) }
let(:school) { create(:school, creator_id: creator.id) }

before do
stub_user_info_api_for(creator)
end

it 'marks the school as archived' do
patch archive_admin_school_path(school)
expect(school.reload).to be_archived
end

it 'displays the validation error when the school has students' do
create(:student_role, school:)

patch archive_admin_school_path(school)
follow_redirect!

expect(response.body).to include('Cannot archive a school with students')
end

it 'redirects to school path' do
patch archive_admin_school_path(school)
expect(response).to redirect_to(admin_school_path(school))
end
end

private

def sign_in_as(user)
Expand Down
Loading
Loading