renalware-core 2.0.160 → 2.0.165

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/renalware/rollup_compiled.js +768 -201
  3. data/app/assets/stylesheets/renalware/modules/_clinical.scss +28 -0
  4. data/app/assets/stylesheets/renalware/partials/_tables.scss +6 -0
  5. data/app/components/renalware/medications/tabbed_prescriptions_list_component.html.slim +2 -2
  6. data/app/components/renalware/pathology/sparkline_component.html.slim +1 -1
  7. data/app/components/renalware/pd/pet_results_component.html.slim +4 -4
  8. data/app/components/renalware/system/admin_menu_component.html.slim +1 -0
  9. data/app/controllers/renalware/virology/vaccination_types_controller.rb +69 -0
  10. data/app/javascript/renalware/controllers/modal_controller.js +1 -1
  11. data/app/models/renalware/events/alertable_events_query.rb +27 -0
  12. data/app/models/renalware/events/event.rb +4 -0
  13. data/app/models/renalware/events/event_type_alert_trigger.rb +15 -0
  14. data/app/models/renalware/events/type.rb +7 -0
  15. data/app/models/renalware/letters/pdf_letter_cache.rb +1 -1
  16. data/app/models/renalware/virology/vaccination.rb +10 -2
  17. data/app/models/renalware/virology/vaccination_type.rb +38 -0
  18. data/app/views/renalware/admin/cache/show.html.slim +0 -36
  19. data/app/views/renalware/admin/playgrounds/show.html.slim +2 -2
  20. data/app/views/renalware/events/events/_alerts.html.slim +3 -0
  21. data/app/views/renalware/events/events/alert/_simple.html.slim +5 -0
  22. data/app/views/renalware/events/events/toggled_cell/_swab.html.slim +1 -1
  23. data/app/views/renalware/hd/dashboards/_page_actions.html.slim +4 -1
  24. data/app/views/renalware/hd/mdm_patients/_patient.html.slim +1 -1
  25. data/app/views/renalware/hd/prescription_administrations/_form.html.slim +2 -2
  26. data/app/views/renalware/hd/transmission_logs/index.html.slim +2 -1
  27. data/app/views/renalware/layouts/_patient.html.slim +1 -0
  28. data/app/views/renalware/letters/batches/_create.html.slim +2 -2
  29. data/app/views/renalware/medications/home_delivery/events/_edit.html.slim +4 -4
  30. data/app/views/renalware/medications/prescriptions/_form.html.slim +3 -3
  31. data/app/views/renalware/system/downloads/index.html.slim +1 -1
  32. data/app/views/renalware/system/messages/_form.html.slim +1 -1
  33. data/app/views/renalware/system/view_metadata/edit.html.slim +6 -6
  34. data/app/views/renalware/virology/vaccination_types/_form.html.slim +10 -0
  35. data/app/views/renalware/virology/vaccination_types/edit.html.slim +3 -0
  36. data/app/views/renalware/virology/vaccination_types/index.html.slim +42 -0
  37. data/app/views/renalware/virology/vaccination_types/new.html.slim +3 -0
  38. data/app/views/renalware/virology/vaccinations/_alert.html.slim +5 -0
  39. data/app/views/renalware/virology/vaccinations/_inputs.html.slim +2 -1
  40. data/app/views/renalware/virology/vaccinations/_toggled_cell.html.slim +1 -1
  41. data/config/permissions.yml +1 -0
  42. data/config/routes/virology.rb +6 -0
  43. data/db/migrate/20210105163944_create_virology_vaccination_types.rb +13 -0
  44. data/db/migrate/20210115181817_create_event_type_alert_triggers.rb +15 -0
  45. data/db/seeds/default/seeds.rb +1 -0
  46. data/db/seeds/default/virology/seeds.rb +3 -0
  47. data/db/seeds/default/virology/vaccination_types.rb +25 -0
  48. data/lib/renalware/version_number.rb +1 -1
  49. data/spec/factories/virology/vaccination_types.rb +8 -0
  50. metadata +23 -7
@@ -141,6 +141,7 @@ article.clinical-allergies {
141
141
  .patient-alerts {
142
142
  color: $white;
143
143
  background-color: transparent;
144
+ padding-top: .1rem;
144
145
 
145
146
  @media print {
146
147
  display: none;
@@ -199,6 +200,33 @@ article.clinical-allergies {
199
200
  }
200
201
  }
201
202
 
203
+ &.vaccination {
204
+ background-color: $nhs-aqua-green;
205
+
206
+ &:hover {
207
+ background-color: darken($nhs-aqua-green, 4);
208
+ }
209
+
210
+ .title a,
211
+ i:before {
212
+ color: $white;
213
+ }
214
+ }
215
+
216
+ &.event {
217
+ background-color: $nhs-yellow;
218
+
219
+ &:hover {
220
+ background-color: darken($nhs-yellow, 7);
221
+ }
222
+
223
+ .title a,
224
+ .date,
225
+ i:before {
226
+ color: $dark-grey;
227
+ }
228
+ }
229
+
202
230
  &.research-study-participant {
203
231
  background-color: darken($nhs-green, 3);
204
232
  color: $white;
@@ -409,6 +409,12 @@ table {
409
409
  }
410
410
  }
411
411
 
412
+ tr.deleted {
413
+ td {
414
+ background-color: lighten($nhs-red, 53);
415
+ }
416
+ }
417
+
412
418
  table.report {
413
419
  table-layout: auto;
414
420
 
@@ -2,11 +2,11 @@ div(data-controller="tabs" data-tabs-active-tab="active")
2
2
  .tabs-container
3
3
  ul.list-reset.sub-nav
4
4
  - groups.each do |group|
5
- li(data-target="tabs.tab" data-action="click->tabs#change")
5
+ li(data-tabs-target="tab" data-action="click->tabs#change")
6
6
  a(class="" href="#" data-trigger-masonry-refresh="true")= group.title
7
7
 
8
8
  - groups.each do |group|
9
- .hidden(data-target="tabs.panel")
9
+ .hidden(data-tabs-target="panel")
10
10
  table.prescriptions-list
11
11
  thead
12
12
  tr
@@ -6,7 +6,7 @@
6
6
  / This is the stimulusjs version. See sparklines_controller.js
7
7
  div(data-controller="pathology-sparklines"
8
8
  data-pathology-sparklines-chart-data='[["2010-06-03T16:37:00.000+01:00","106"], ["2011-06-03T16:37:00.000+01:00","110"]]')
9
- div(data-target="pathology-sparklines.chart")
9
+ div(data-pathology-sparklines-target="chart")
10
10
  - else
11
11
  / This implementation uses ChartKick to insert the chart javascript into the
12
12
  / page. It works fine, but when there are 50 path results on screen, we are
@@ -7,12 +7,12 @@
7
7
  .flex.justify-end
8
8
 
9
9
  .inline-flex
10
- a.reset.opacity-50.bg-white.m-05.pt-px.pb-0.px-1(href="#" data-target="tabs.tab"
10
+ a.reset.opacity-50.bg-white.m-05.pt-px.pb-0.px-1(href="#" data-tabs-target="tab"
11
11
  data-action="click->tabs#change")
12
12
  svg.h-6.w-6.fill-current.text-black xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
13
13
  path d="M1 4h2v2H1V4zm4 0h14v2H5V4zM1 9h2v2H1V9zm4 0h14v2H5V9zm-4 5h2v2H1v-2zm4 0h14v2H5v-2z"
14
14
 
15
- a.reset.opacity-50.bg-white.m-0.pt-px.px-2(data-target="tabs.tab" href="#"
15
+ a.reset.opacity-50.bg-white.m-0.pt-px.px-2(data-tabs-target="tab" href="#"
16
16
  data-action="click->tabs#change")
17
17
  svg.h-6.w-6.stroke-current.text-black viewBox="0 0 24 26" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"
18
18
  path d="M12 20V10M18 20V4M6 20v-4"
@@ -20,7 +20,7 @@
20
20
  = link_to "Show all", renalware.patient_pd_pet_results_path(patient), remote: true, class: "float-none button flex-initial secondary"
21
21
  = link_to "Add", renalware.new_patient_pd_pet_result_path(patient), class: "float-none button flex-initial"
22
22
 
23
- div#pet-results-table data-target="tabs.panel"
23
+ div#pet-results-table data-tabs-target="panel"
24
24
 
25
25
  = render "renalware/pd/pet_results/table",
26
26
  patient: patient,
@@ -28,7 +28,7 @@
28
28
  pagination: pagination,
29
29
  current_user: current_user
30
30
 
31
- .chart.hidden.mt-5 data-target="tabs.panel"
31
+ .chart.hidden.mt-5 data-tabs-target="panel"
32
32
  div(style="display:block; position:relative; height: 400px; width: 100%; text-align: center; margin-bottom: 20px")
33
33
  #containerx(data-controller="pd-pet-chart"
34
34
  data-pd-pet-chart-url=renalware.patient_pd_pet_results_path(patient, format: :json)
@@ -24,6 +24,7 @@ ul.side-nav.side-nav--admin
24
24
  = super_admin_menu_item "Print batches", renalware.letters_batches_path, %r{letters/batches}
25
25
  = super_admin_menu_item "OBX", renalware.pathology_observation_descriptions_path, %r{admin/pathology_observation_descriptions}
26
26
  = super_admin_menu_item "Pathology Code Groups", renalware.pathology_code_groups_path, %r{pathology/code_groups}
27
+ = super_admin_menu_item "Virology Vaccination Types", renalware.virology_vaccination_types_path, %r{virology/vaccination_types}
27
28
  = admin_menu_item "Mailshots", renalware.letters_mailshots_path, %r{letters/mailshots}
28
29
  = super_admin_menu_item "Playground", renalware.admin_playground_path, %r{admin/playground}
29
30
  = developer_menu_item "HL7 Test", renalware.new_feeds_hl7_test_message_path, %r{admin/123}
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/virology"
4
+
5
+ module Renalware
6
+ module Virology
7
+ class VaccinationTypesController < BaseController
8
+ def index
9
+ types = VaccinationType.with_deleted.ordered
10
+ authorize types
11
+ render locals: { types: types }
12
+ end
13
+
14
+ def edit
15
+ type = find_and_authorise_type
16
+ render locals: { type: type }
17
+ end
18
+
19
+ def update
20
+ type = find_and_authorise_type
21
+ if type.update(type_params)
22
+ redirect_to virology_vaccination_types_path
23
+ else
24
+ render :edit, locals: { type: type }
25
+ end
26
+ end
27
+
28
+ def new
29
+ type = VaccinationType.new
30
+ authorize type
31
+ render locals: { type: type }
32
+ end
33
+
34
+ def create
35
+ type = VaccinationType.new(type_params)
36
+ authorize type
37
+ if type.save
38
+ redirect_to virology_vaccination_types_path
39
+ else
40
+ render :new, locals: { type: type }
41
+ end
42
+ end
43
+
44
+ def destroy
45
+ find_and_authorise_type.destroy!
46
+ redirect_to virology_vaccination_types_path
47
+ end
48
+
49
+ def sort
50
+ authorize VaccinationType, :sort?
51
+ ids = params[:virology_vaccination_type]
52
+ VaccinationType.sort(ids)
53
+ render json: ids
54
+ end
55
+
56
+ private
57
+
58
+ def find_and_authorise_type
59
+ VaccinationType.find(params[:id]).tap { |type| authorize type }
60
+ end
61
+
62
+ def type_params
63
+ params
64
+ .require("virology_vaccination_type")
65
+ .permit(:name, :code, :position)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -11,7 +11,7 @@
11
11
  // <span>Open Modal</span>
12
12
  // </a>
13
13
  // <!-- Modal Container -->
14
- // <div data-target="modal.container" data-action="click->modal#closeBackground keyup@window->modal#closeWithKeyboard" class="hidden animated fadeIn fixed inset-0 overflow-y-auto flex items-center justify-center" style="z-index: 9999;">
14
+ // <div data-modal-target="container" data-action="click->modal#closeBackground keyup@window->modal#closeWithKeyboard" class="hidden animated fadeIn fixed inset-0 overflow-y-auto flex items-center justify-center" style="z-index: 9999;">
15
15
  // <!-- Modal Inner Container -->
16
16
  // <div class="max-h-screen w-full max-w-lg relative">
17
17
  // <!-- Modal Card -->
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/events"
4
+
5
+ module Renalware
6
+ module Events
7
+ # Query object that returns, for a patient, the most recent matching event (if any) for each
8
+ # event_type_alert_trigger row in the databse. The results are used to display
9
+ # alerts in the UI.
10
+ # For example given a vaccination Event::Type and an EventTypeAlertTrigger
11
+ # which is configured to find any vaccination event with the word "covid" in
12
+ # anywhere in the document, this query will return the most matching event.
13
+ # It is possible to have mltiple triggers rows for the same event type, so for eample one
14
+ # could display triggers for the most recent covid vaccination, and the most recent HBV
15
+ # vaccination.
16
+ class AlertableEventsQuery
17
+ def self.call(patient:)
18
+ Event
19
+ .for_patient(patient)
20
+ .joins(event_type: :alert_triggers)
21
+ .select("DISTINCT ON (events.patient_id, event_type_alert_triggers.id) events.*")
22
+ .where("(events.document::text ilike '%' || when_event_document_contains || '%') or (events.description ilike '%' || when_event_description_contains || '%')")
23
+ .order("events.patient_id, event_type_alert_triggers.id, events.created_at desc")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -45,6 +45,10 @@ module Renalware
45
45
  end
46
46
  alias :to_input_partial_path :to_partial_path
47
47
 
48
+ def to_alert_partial_path
49
+ partial_for "alert"
50
+ end
51
+
48
52
  def to_cell_partial_path
49
53
  partial_for "cell"
50
54
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/events"
4
+
5
+ module Renalware
6
+ module Events
7
+ # Used to defines conditions where, for a particular event type, if the there is e.g. a case-
8
+ # insensitive text match against the contents of the event document (eg 'COVID')
9
+ # then an alert will be displayed in the UI (for the most recent match is there are > 1).
10
+ class EventTypeAlertTrigger < ApplicationRecord
11
+ belongs_to :event_type, class_name: "Events::Type"
12
+ validates :event_type, presence: true
13
+ end
14
+ end
15
+ end
@@ -6,6 +6,13 @@ module Renalware
6
6
  module Events
7
7
  class Type < ApplicationRecord
8
8
  self.table_name = "event_types"
9
+ has_many(
10
+ :alert_triggers,
11
+ class_name: "EventTypeAlertTrigger",
12
+ foreign_key: :event_type_id,
13
+ dependent: :destroy
14
+ )
15
+
9
16
  DEFAULT_EVENT_CLASS_NAME = "Renalware::Events::Simple"
10
17
 
11
18
  acts_as_paranoid
@@ -40,7 +40,7 @@ module Renalware
40
40
  delegate :clear, to: :store
41
41
 
42
42
  def fetch(letter, **options)
43
- store.fetch(cache_key_for(letter, **options)) { yield }
43
+ store.fetch(cache_key_for(letter, **options), expires_in: 4.weeks) { yield }
44
44
  end
45
45
 
46
46
  # Note the letter must be a LetterPresenter which has a #to_html method
@@ -17,12 +17,20 @@ module Renalware
17
17
  end
18
18
 
19
19
  class Document < Document::Embedded
20
- attribute :type, ::Document::Enum # See i18n for options
20
+ attribute :type # This used to be a ::Document::Enum but now we load options from the db
21
21
  attribute :drug, String
22
22
  validates :type, presence: true
23
23
 
24
24
  def to_s
25
- [type&.text, drug].reject(&:blank?).join(" - ")
25
+ [type_name, drug].reject(&:blank?).join(" - ")
26
+ end
27
+
28
+ # Try and find the proper name for the vaccination type - we only store the vaccination
29
+ # type code in the jsonb document. If no match found just display the code eg 'hbv_booster'.
30
+ # The Type mighjt have been deleted (has a deleted_at date) but we still want to display the
31
+ # actual name depite this, we include deleted rows when searching.
32
+ def type_name
33
+ VaccinationType.with_deleted.find_by(code: type)&.name || type
26
34
  end
27
35
  end
28
36
  has_document
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/virology"
4
+
5
+ module Renalware
6
+ module Virology
7
+ class VaccinationType < ApplicationRecord
8
+ include Sortable
9
+
10
+ class UniquenessIncludingDeletedValidator < ActiveModel::EachValidator
11
+ def validate_each(record, attribute, value)
12
+ return unless record.send(:"#{attribute}_changed?")
13
+
14
+ if record.class.with_deleted.exists?(attribute => value)
15
+ record.errors.add attribute, (options[:message] || "already used")
16
+ end
17
+ end
18
+ end
19
+ validates :name, presence: true, uniqueness_including_deleted: true
20
+ validates :code, presence: true, uniqueness_including_deleted: true
21
+ before_create :underscore_code
22
+
23
+ acts_as_paranoid
24
+
25
+ scope :ordered, -> { order(deleted_at: :desc, position: :asc, name: :asc) }
26
+
27
+ def self.policy_class
28
+ BasePolicy
29
+ end
30
+
31
+ def underscore_code
32
+ return if code.blank?
33
+
34
+ self.code = code.downcase.tr(" ", " ").tr(" ", "_")
35
+ end
36
+ end
37
+ end
38
+ end
@@ -42,39 +42,3 @@
42
42
  method: :delete,
43
43
  data: { confirm: "Are you sure you want to clear the PDF letter cache?\n" },
44
44
  class: "button alert"
45
-
46
- <label class="block">
47
- <span class="text-gray-700">Name</span>
48
- <input class="form-input mt-1 block w-full" placeholder="Jane Doe">
49
- </label>
50
-
51
- <div class="mt-4">
52
- <span class="text-gray-700">Account Type</span>
53
- <div class="mt-2">
54
- <label class="inline-flex items-center">
55
- <input type="radio" class="form-radio" name="accountType" value="personal">
56
- <span class="ml-2">Personal</span>
57
- </label>
58
- <label class="inline-flex items-center ml-6">
59
- <input type="radio" class="form-radio" name="accountType" value="busines">
60
- <span class="ml-2">Business</span>
61
- </label>
62
- </div>
63
- </div>
64
-
65
- <label class="block mt-4">
66
- <span class="text-gray-700">Requested Limit</span>
67
- <select class="form-select mt-1 block w-full">
68
- <option>$1,000</option>
69
- <option>$5,000</option>
70
- <option>$10,000</option>
71
- <option>$25,000</option>
72
- </select>
73
- </label>
74
-
75
- <div class="flex mt-6">
76
- <label class="flex items-center">
77
- <input type="checkbox" class="form-checkbox">
78
- <span class="ml-2">I agree to the <span class="underline">privacy policy</span></span>
79
- </label>
80
- </div>
@@ -34,7 +34,7 @@ css:
34
34
  url: pathology_chart_data_admin_playground_path,
35
35
  wrapper: nil,
36
36
  method: :get,
37
- data: { target: "charts.form", action: "ajax:success->charts#redisplay" }) do |f|
37
+ data: { "charts-target" => "form", action: "ajax:success->charts#redisplay" }) do |f|
38
38
 
39
39
  = f.input :patient_id,
40
40
  collection: [[form.patient_id, form.patient_id]],
@@ -52,7 +52,7 @@ css:
52
52
  include_blank: false
53
53
  = f.submit "Refresh", class: :button
54
54
 
55
- #chart1 style="height: 300px; width: 500px" data-target="charts.chart"
55
+ #chart1 style="height: 300px; width: 500px" data-charts-target="chart"
56
56
 
57
57
  / To test tailwindcss
58
58
  .max-w-sm.rounded.overflow-hidden.shadow-lg &nbsp;
@@ -0,0 +1,3 @@
1
+ - events = Renalware::Events::AlertableEventsQuery.call(patient: patient)
2
+ - events.each do |event|
3
+ = render event.to_alert_partial_path, event: event
@@ -0,0 +1,5 @@
1
+ / The default markup for an alertable event
2
+ li.patient-alert.event
3
+ i.fas.fa-exclamation-triangle
4
+ span.title= link_to event.description&.truncate(12), patient_events_path
5
+ span.date= l(event.date_time.to_date)
@@ -1,6 +1,6 @@
1
1
  - document = event.document
2
2
  dl.dl-horizontal
3
3
  dt= attr_name(document, :location, suffix: ":")
4
- dd= document.location.present? ? document.location : t("unspecified")
4
+ dd= document.location.presence || t("unspecified")
5
5
  dt= attr_name(event, :notes, suffix: ":")
6
6
  dd== event.notes
@@ -1,7 +1,10 @@
1
1
  = link_to(patient_hd_mdm_path(patient), class: "button with-icon secondary") do
2
2
  i.fas.fa-users
3
3
  = t(".mdm")
4
- = link_to(patient_hd_protocol_path(patient), class: "button with-icon secondary", target: "_blank") do
4
+ = link_to(patient_hd_protocol_path(patient),
5
+ class: "button with-icon secondary",
6
+ target: "_blank",
7
+ rel: "noopener") do
5
8
  i.fas.fa-print
6
9
  = t(".protocol")
7
10
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  tr
6
6
  td.actions
7
- = link_to view_proc.call(patient), target: "_blank" do
7
+ = link_to view_proc.call(patient), target: "_blank", rel: "noopener" do
8
8
  i.fa.fa-external-link-square-alt
9
9
  | &nbsp;&nbsp;
10
10
  = t(".view")