Skip to content

Commit 30dfa2f

Browse files
murugaplthomasleese
authored andcommitted
Create ImportantNotice model and policy
This supports the dismiss notice feature. The policy restricts notices to superusers in the select team only.
1 parent 61bfbb3 commit 30dfa2f

5 files changed

Lines changed: 329 additions & 0 deletions

File tree

app/models/important_notice.rb

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# frozen_string_literal: true
2+
3+
# == Schema Information
4+
#
5+
# Table name: important_notices
6+
#
7+
# id :bigint not null, primary key
8+
# dismissed_at :datetime
9+
# recorded_at :datetime not null
10+
# type :integer not null
11+
# created_at :datetime not null
12+
# updated_at :datetime not null
13+
# dismissed_by_user_id :bigint
14+
# patient_id :bigint not null
15+
# team_id :bigint not null
16+
# vaccination_record_id :bigint
17+
#
18+
# Indexes
19+
#
20+
# index_important_notices_on_dismissed_by_user_id (dismissed_by_user_id)
21+
# index_important_notices_on_patient_id (patient_id)
22+
# index_important_notices_on_team_id (team_id)
23+
# index_important_notices_on_vaccination_record_id (vaccination_record_id)
24+
# index_notices_on_patient_and_type_and_recorded_at_and_team (patient_id,type,recorded_at,team_id) UNIQUE
25+
#
26+
# Foreign Keys
27+
#
28+
# fk_rails_... (dismissed_by_user_id => users.id)
29+
# fk_rails_... (patient_id => patients.id)
30+
# fk_rails_... (team_id => teams.id)
31+
# fk_rails_... (vaccination_record_id => vaccination_records.id)
32+
#
33+
class ImportantNotice < ApplicationRecord
34+
self.inheritance_column = nil
35+
36+
belongs_to :patient
37+
38+
belongs_to :vaccination_record, optional: true
39+
40+
enum :type,
41+
{ deceased: 0, invalidated: 1, restricted: 2, gillick_no_notify: 3 }
42+
43+
scope :active, ->(team:) { where(dismissed_at: nil, team_id: team.id) }
44+
scope :dismissed,
45+
->(team:) { where.not(dismissed_at: nil).where(team_id: team.id) }
46+
47+
validates :type, presence: true
48+
validates :recorded_at, presence: true
49+
validates :message, presence: true
50+
51+
def dismiss!(user: nil)
52+
update!(dismissed_at: Time.current, dismissed_by_user_id: user&.id)
53+
end
54+
55+
def message
56+
case type
57+
when "deceased"
58+
"Record updated with child’s date of death"
59+
when "invalidated"
60+
"Record flagged as invalid"
61+
when "restricted"
62+
"Record flagged as sensitive"
63+
when "gillick_no_notify"
64+
"Child gave consent for #{vaccination_record.programme.name} under Gillick competence and " \
65+
"does not want their parents to be notified. " \
66+
"These records will not be automatically synced with GP records. " \
67+
"Your team must let the child's GP know they were vaccinated."
68+
else
69+
"Important notice"
70+
end
71+
end
72+
73+
def can_dismiss?
74+
type.in?(%w[deceased restricted gillick_no_notify])
75+
end
76+
end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# frozen_string_literal: true
2+
3+
class ImportantNoticePolicy < ApplicationPolicy
4+
class Scope < ApplicationPolicy::Scope
5+
def resolve
6+
scope
7+
.joins(:patient)
8+
.active(team: user.selected_team)
9+
.where(team_id: user.selected_team.id)
10+
end
11+
end
12+
13+
def index?
14+
user.can_access_sensitive_flagged_records?
15+
end
16+
end
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# frozen_string_literal: true
2+
3+
# == Schema Information
4+
#
5+
# Table name: important_notices
6+
#
7+
# id :bigint not null, primary key
8+
# dismissed_at :datetime
9+
# recorded_at :datetime not null
10+
# type :integer not null
11+
# created_at :datetime not null
12+
# updated_at :datetime not null
13+
# dismissed_by_user_id :bigint
14+
# patient_id :bigint not null
15+
# team_id :bigint not null
16+
# vaccination_record_id :bigint
17+
#
18+
# Indexes
19+
#
20+
# index_important_notices_on_dismissed_by_user_id (dismissed_by_user_id)
21+
# index_important_notices_on_patient_id (patient_id)
22+
# index_important_notices_on_team_id (team_id)
23+
# index_important_notices_on_vaccination_record_id (vaccination_record_id)
24+
# index_notices_on_patient_and_type_and_recorded_at_and_team (patient_id,type,recorded_at,team_id) UNIQUE
25+
#
26+
# Foreign Keys
27+
#
28+
# fk_rails_... (dismissed_by_user_id => users.id)
29+
# fk_rails_... (patient_id => patients.id)
30+
# fk_rails_... (team_id => teams.id)
31+
# fk_rails_... (vaccination_record_id => vaccination_records.id)
32+
#
33+
FactoryBot.define do
34+
factory :important_notice do
35+
patient
36+
team_id { create(:team).id }
37+
38+
type { ImportantNotice.types.keys.sample }
39+
recorded_at { Time.current }
40+
41+
after(:build) do |notice|
42+
if notice.type == "gillick_no_notify" && notice.vaccination_record.nil?
43+
notice.vaccination_record = build(:vaccination_record)
44+
end
45+
end
46+
47+
trait :deceased do
48+
type { :deceased }
49+
end
50+
51+
trait :restricted do
52+
type { :restricted }
53+
end
54+
55+
trait :invalidated do
56+
type { :invalidated }
57+
end
58+
59+
trait :gillick_no_notify do
60+
type { :gillick_no_notify }
61+
vaccination_record
62+
end
63+
64+
trait :dismissed do
65+
dismissed_at { 1.day.ago }
66+
dismissed_by_user { association :user }
67+
end
68+
end
69+
end
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# frozen_string_literal: true
2+
3+
# == Schema Information
4+
#
5+
# Table name: important_notices
6+
#
7+
# id :bigint not null, primary key
8+
# dismissed_at :datetime
9+
# recorded_at :datetime not null
10+
# type :integer not null
11+
# created_at :datetime not null
12+
# updated_at :datetime not null
13+
# dismissed_by_user_id :bigint
14+
# patient_id :bigint not null
15+
# team_id :bigint not null
16+
# vaccination_record_id :bigint
17+
#
18+
# Indexes
19+
#
20+
# index_important_notices_on_dismissed_by_user_id (dismissed_by_user_id)
21+
# index_important_notices_on_patient_id (patient_id)
22+
# index_important_notices_on_team_id (team_id)
23+
# index_important_notices_on_vaccination_record_id (vaccination_record_id)
24+
# index_notices_on_patient_and_type_and_recorded_at_and_team (patient_id,type,recorded_at,team_id) UNIQUE
25+
#
26+
# Foreign Keys
27+
#
28+
# fk_rails_... (dismissed_by_user_id => users.id)
29+
# fk_rails_... (patient_id => patients.id)
30+
# fk_rails_... (team_id => teams.id)
31+
# fk_rails_... (vaccination_record_id => vaccination_records.id)
32+
#
33+
describe ImportantNotice do
34+
subject(:notice) { build(:important_notice) }
35+
36+
let(:team) { create(:team) }
37+
let(:patient) { create(:patient) }
38+
let(:user) { create(:user) }
39+
40+
describe "validations" do
41+
it { should validate_presence_of(:type) }
42+
it { should validate_presence_of(:recorded_at) }
43+
end
44+
45+
describe "associations" do
46+
it { should belong_to(:patient) }
47+
end
48+
49+
describe "enums" do
50+
it do
51+
expect(notice).to define_enum_for(:type).with_values(
52+
deceased: 0,
53+
invalidated: 1,
54+
restricted: 2,
55+
gillick_no_notify: 3
56+
)
57+
end
58+
end
59+
60+
describe "#dismiss!" do
61+
let(:notice) { create(:important_notice, team_id: team.id) }
62+
63+
context "when dismissing without a user" do
64+
it "sets dismissed_at" do
65+
freeze_time do
66+
notice.dismiss!
67+
expect(notice.dismissed_at).to eq(Time.current)
68+
end
69+
end
70+
71+
it "does not set dismissed_by_user_id" do
72+
notice.dismiss!
73+
expect(notice.dismissed_by_user_id).to be_nil
74+
end
75+
end
76+
77+
context "when dismissing with a user" do
78+
it "sets dismissed_at and dismissed_by_user_id" do
79+
freeze_time do
80+
notice.dismiss!(user: user)
81+
expect(notice.dismissed_at).to eq(Time.current)
82+
expect(notice.dismissed_by_user_id).to eq(user.id)
83+
end
84+
end
85+
end
86+
end
87+
end
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# frozen_string_literal: true
2+
3+
describe ImportantNoticePolicy do
4+
let(:team) { create(:team) }
5+
let(:notice) { create(:important_notice, team_id: team.id) }
6+
let(:policy) { described_class.new(user, notice) }
7+
8+
describe "Scope#resolve" do
9+
subject { ImportantNoticePolicy::Scope.new(user, ImportantNotice).resolve }
10+
11+
let(:team) { create(:team) }
12+
let(:another_team) { create(:team) }
13+
let(:user) { create(:user, team:) }
14+
15+
context "when notices are in different teams" do
16+
let(:notice_in_team) { create(:important_notice, team_id: team.id) }
17+
let(:notice_in_another_team) do
18+
create(:important_notice, team_id: another_team.id)
19+
end
20+
let(:notice_in_another_team_2) do
21+
create(:important_notice, team_id: another_team.id)
22+
end
23+
24+
before do
25+
notice_in_team
26+
notice_in_another_team
27+
notice_in_another_team_2
28+
end
29+
30+
it { should contain_exactly(notice_in_team) }
31+
end
32+
33+
context "when multiple notices exist in the team" do
34+
let(:notice_one) { create(:important_notice, team_id: team.id) }
35+
let(:notice_two) { create(:important_notice, team_id: team.id) }
36+
let(:notice_in_another_team) do
37+
create(:important_notice, team_id: another_team.id)
38+
end
39+
40+
before do
41+
notice_one
42+
notice_two
43+
notice_in_another_team
44+
end
45+
46+
it { should contain_exactly(notice_one, notice_two) }
47+
end
48+
end
49+
50+
shared_examples "local system admin access" do
51+
context "with a superuser" do
52+
let(:user) { create(:superuser, team:) }
53+
54+
it { should be(true) }
55+
end
56+
57+
context "with a nurse" do
58+
let(:user) { create(:nurse, team:) }
59+
60+
it { should be(false) }
61+
end
62+
63+
context "with a prescriber" do
64+
let(:user) { create(:prescriber, team:) }
65+
66+
it { should be(false) }
67+
end
68+
69+
context "with a healthcare assistant" do
70+
let(:user) { create(:healthcare_assistant, team:) }
71+
72+
it { should be(false) }
73+
end
74+
75+
context "with a medical secretary" do
76+
let(:user) { create(:medical_secretary, team:) }
77+
78+
it { should be(false) }
79+
end
80+
end
81+
end

0 commit comments

Comments
 (0)