renalware-core 2.0.24 → 2.0.25

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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -5
  3. data/app/assets/images/renalware/print-button-example.png +0 -0
  4. data/app/assets/stylesheets/renalware/partials/_layout.scss +4 -0
  5. data/app/assets/stylesheets/renalware/pdf.scss +12 -0
  6. data/app/assets/stylesheets/renalware/protocol_pdf.scss +4 -0
  7. data/app/assets/stylesheets/renalware/table_pdf.scss +210 -0
  8. data/app/controllers/renalware/admissions/consults_controller.rb +24 -6
  9. data/app/controllers/renalware/concerns/pdf_renderable.rb +11 -2
  10. data/app/controllers/renalware/renal/aki_alerts_controller.rb +29 -7
  11. data/app/controllers/renalware/transplants/registration_statuses_controller.rb +1 -1
  12. data/app/models/renalware/admissions/consult_query.rb +3 -0
  13. data/app/models/renalware/feeds/message_processor.rb +5 -0
  14. data/app/models/renalware/feeds/persist_message.rb +4 -1
  15. data/app/models/renalware/renal.rb +1 -1
  16. data/app/models/renalware/renal/aki_alert.rb +1 -0
  17. data/app/presenters/renalware/admissions/admission_presenter.rb +1 -1
  18. data/app/views/renalware/admissions/consults/_filters.html.slim +10 -1
  19. data/app/views/renalware/admissions/consults/_filters.pdf.slim +32 -0
  20. data/app/views/renalware/admissions/consults/_table.html.slim +9 -8
  21. data/app/views/renalware/admissions/consults/_table.pdf.slim +35 -0
  22. data/app/views/renalware/admissions/consults/index.html.slim +7 -0
  23. data/app/views/renalware/admissions/consults/index.pdf.slim +10 -0
  24. data/app/views/renalware/hd/sessions/_empty_row.html.slim +2 -0
  25. data/app/views/renalware/hd/sessions/_row.html.slim +3 -0
  26. data/app/views/renalware/hd/sessions/_show.html.slim +5 -6
  27. data/app/views/renalware/hd/sessions/_thead.html.slim +4 -0
  28. data/app/views/renalware/layouts/pdf.pdf.slim +1 -1
  29. data/app/views/renalware/letters/contacts/_edit.html.slim +1 -1
  30. data/app/views/renalware/renal/aki_alerts/_filters.pdf.slim +25 -0
  31. data/app/views/renalware/renal/aki_alerts/_table.pdf.slim +29 -0
  32. data/app/views/renalware/renal/aki_alerts/edit.html.slim +1 -1
  33. data/app/views/renalware/renal/aki_alerts/index.html.slim +10 -0
  34. data/app/views/renalware/renal/aki_alerts/index.pdf.slim +14 -0
  35. data/app/views/renalware/shared/_please_print_using_print_button_warning.html.slim +18 -0
  36. data/app/views/renalware/transplants/donor_followups/_form.html.slim +2 -2
  37. data/app/views/renalware/transplants/recipient_followups/_form.html.slim +6 -1
  38. data/app/views/renalware/transplants/registration_statuses/_inputs.html.slim +4 -0
  39. data/app/views/renalware/transplants/registration_statuses/_list.html.slim +18 -5
  40. data/config/locales/custom.yml +2 -3
  41. data/config/locales/renalware/hd/session.en.yml +3 -0
  42. data/db/migrate/20180510151959_update_hd_overall_audit_to_version_7.rb +6 -0
  43. data/db/migrate/20180511100345_add_notes_to_transplant_registration_statuses.rb +5 -0
  44. data/db/migrate/20180511140415_add_message_hash_messaging_messages.rb +6 -0
  45. data/db/seeds/default/deaths/death_causes.rb +4 -4
  46. data/db/seeds/default/patients/patients_languages.rb +4 -3
  47. data/db/seeds/default/renal/prd_descriptions.rb +7 -5
  48. data/db/seeds/default/system/countries.rb +10 -6
  49. data/db/views/reporting_hd_overall_audit_v07.sql +44 -0
  50. data/lib/renalware/configuration.rb +4 -0
  51. data/lib/renalware/engine.rb +2 -0
  52. data/lib/renalware/version.rb +1 -1
  53. data/spec/factories/admissions/consults.rb +12 -0
  54. data/spec/support/capybara.rb +16 -21
  55. metadata +15 -3
  56. data/spec/support/poltergeist.rb +0 -4
@@ -10,6 +10,7 @@ module Renalware
10
10
  def initialize(query = nil)
11
11
  @query = query || {}
12
12
  @query[:ended_on_null] ||= true
13
+ @query[:s] ||= "hospital_ward_name"
13
14
  end
14
15
 
15
16
  def call
@@ -20,6 +21,7 @@ module Renalware
20
21
  # It might be better to refactor PatientsRansackHelper so we can include where required
21
22
  # eg below using .extending(PatientsRansackHelper) rather than relying on it being in
22
23
  # included in the model file.
24
+ # rubocop:disable Metrics/MethodLength
23
25
  def search
24
26
  @search ||= begin
25
27
  Consult
@@ -35,6 +37,7 @@ module Renalware
35
37
  .ransack(query)
36
38
  end
37
39
  end
40
+ # rubocop:enable Metrics/MethodLength
38
41
  end
39
42
  end
40
43
  end
@@ -27,6 +27,11 @@ module Renalware
27
27
  MessageParser.new.parse(raw_message)
28
28
  end
29
29
 
30
+ # If the incoming message has already been processed we should not be processing it again.
31
+ # To help enforce this there is a unique MD5 hash on feed_messages which will baulk if the
32
+ # same message payload is saved twice - in this case we exit #call early, the broadcast
33
+ # is not issued and therefore the message is not processed. The message will go back into
34
+ # the delayed_job queue and retry, failing until it finally gives up!
30
35
  def persist_message(message_payload)
31
36
  PersistMessage.new.call(message_payload)
32
37
  end
@@ -6,11 +6,14 @@ module Renalware
6
6
  module Feeds
7
7
  class PersistMessage
8
8
  # message_payload is an HL7Message (a decorator around ::HL7::Message)
9
+ # If the same message is persisted twice we'll get an ActiveRecord::RecordNotUnique error
10
+ # but that's fine as we don't want to process the same HL7 message twice.
9
11
  def call(message_payload)
10
12
  Message.create!(
11
13
  event_code: message_payload.type,
12
14
  header_id: message_payload.header_id,
13
- body: message_payload.to_s
15
+ body: message_payload.to_s,
16
+ body_hash: Digest::MD5.hexdigest(message_payload.to_s)
14
17
  )
15
18
  end
16
19
  end
@@ -4,7 +4,7 @@ require_dependency "renalware"
4
4
 
5
5
  module Renalware
6
6
  module Renal
7
- AKI_ALERT_FILTERS = %w(today all).freeze
7
+ AKI_ALERT_FILTERS = %w(today all hotlist).freeze
8
8
 
9
9
  def self.table_name_prefix
10
10
  "renal_"
@@ -16,6 +16,7 @@ module Renalware
16
16
  alias_attribute :decided_by, :updated_by
17
17
 
18
18
  scope :today, ->{ where(created_at: Time.zone.today.all_day) }
19
+ scope :hotlist, ->{ where(hotlist: true) }
19
20
  end
20
21
  end
21
22
  end
@@ -22,7 +22,7 @@ module Renalware
22
22
  ward = hospital_ward || NullObject.instance
23
23
  [
24
24
  ward.hospital_unit.unit_code,
25
- ward.name
25
+ [ward.name, ward.code].reject.first
26
26
  ].compact.join(" / ")
27
27
  end
28
28
 
@@ -11,11 +11,16 @@
11
11
  = f.input :consult_site_id_eq,
12
12
  collection: Renalware::Admissions::ConsultSite.pluck(:name, :id),
13
13
  label: "Site"
14
- .columns.medium-2.large-1
14
+ .columns.medium-1.large-1
15
15
  = f.input :seen_by_id_eq,
16
16
  include_blank: false,
17
17
  collection: [["Anyone", nil], ["Me", current_user.id]],
18
18
  label: "Seen by"
19
+ .columns.medium-1.large-1
20
+ = f.input :rrt_eq,
21
+ include_blank: true,
22
+ collection: [["Yes", true], ["No", false]],
23
+ label: "RRT"
19
24
  .columns.medium-1.large-1
20
25
  = f.input :aki_risk_eq,
21
26
  include_blank: true,
@@ -26,6 +31,10 @@
26
31
  include_blank: true,
27
32
  collection: [["Yes", true], ["No", false]],
28
33
  label: "Active"
34
+ .columns.medium-2.large-2
35
+ = f.input :patient_current_modality_description_id_eq,
36
+ collection: Renalware::Modalities::Description.pluck("name", "id"),
37
+ label: "Modality"
29
38
  .columns.medium-2.large-2.actions.end
30
39
  = f.submit "Filter", class: "button"
31
40
  span= " or "
@@ -0,0 +1,32 @@
1
+ // Compile a summary of filters applied. Leave out anything not set.
2
+ - filters = params.fetch(:q, {}).reject{ |key, value| value.blank? }
3
+
4
+ .filter-summary
5
+ table
6
+ tr
7
+ th Filters
8
+ - if filters.empty?
9
+ td None
10
+ - else
11
+ - if filters[:identity_match].present?
12
+ th Query:
13
+ td= filters[:identity_match]
14
+ - if filters[:consult_site_id_eq].present?
15
+ th Site:
16
+ td= Renalware::Admissions::ConsultSite.find_by(id: filters[:consult_site_id_eq]).name
17
+ - if filters[:rrt_eq].present?
18
+ th RRT:
19
+ td= yes_no(filters[:rrt_eq] == "true")
20
+ - if filters[:seen_by_id_eq].present?
21
+ th Seen By:
22
+ td= Renalware::User.find(filters[:seen_by_id_eq])&.to_s
23
+ - if filters[:aki_risk_eq].present?
24
+ th AKI Risk:
25
+ td= yes_no(filters[:ended_on_null]&.humanize)
26
+ - if filters[:ended_on_null].present?
27
+ th Active:
28
+ td= yes_no(filters[:ended_on_null] == "true")
29
+ - modality_id = filters[:patient_current_modality_description_id_eq]
30
+ - if modality_id.present?
31
+ th Modality:
32
+ td= Renalware::Modalities::Description.where(id: modality_id).first&.name
@@ -7,10 +7,8 @@
7
7
  = sort_link(query, :patient, ["patient_family_name", "patient_given_name asc"], "Patient")
8
8
  th.col-width-nhs-no.noprint NHS No.
9
9
  th.col-width-reference-no Hosp Nos.
10
- th.col-width-medium= sort_link(query,
11
- :location,
12
- [:consult_site_name, :hospital_ward_name],
13
- "Location")
10
+ th.col-width-medium= sort_link(query, :hospital_ward_name, "Ward")
11
+ th.col-width-tiny RRT
14
12
  th.col-width-date.noprint= sort_link(query, :started_on, "Started")
15
13
  th.col-width-date.noprint= sort_link(query, :ended_on, "Ended")
16
14
  th.col-width-small.show-for-large-up
@@ -19,13 +17,11 @@
19
17
  th.col-width-date.show-for-large-up= sort_link(query, :patient_born_on, "DOB")
20
18
  th.col-width-tiny.show-for-large-up Age
21
19
  th.col-width-tiny.noprint= sort_link(query, :aki_risk, "AKI Risk")
22
- /th.show-for-large-up Author
23
- /th.col-width-medium.show-for-xlarge-up Description
24
20
 
25
21
  tbody
26
22
  - consults.each do |consult|
27
23
  - uid = "consult-#{consult.id}"
28
- tr
24
+ tr.keep-with-next
29
25
  td.actions-dropdown.going-right.noprint
30
26
  / As we have lots of possible actions, group them in a button group.
31
27
  / A wrapping div is required for now in order for the button group display correctly
@@ -61,7 +57,8 @@
61
57
  td= default_patient_link(consult.patient)
62
58
  td.noprint= consult.patient_nhs_number
63
59
  td= consult.patient_hospital_identifiers&.to_s_multiline
64
- td= consult.location
60
+ td= consult.hospital_ward
61
+ td= yes_no(consult.rrt?)
65
62
  td.noprint= l(consult.started_on)
66
63
  td.noprint= l(consult.ended_on)
67
64
  td.show-for-large-up.col-width-medium-with-ellipsis= consult.patient_current_modality
@@ -82,3 +79,7 @@
82
79
  dt Description
83
80
  dd= simple_format consult.description
84
81
  td
82
+ / When printing we just show the notes on the second row
83
+ tr.print-only.child-row
84
+ td(colspan=12)= simple_format consult.description
85
+
@@ -0,0 +1,35 @@
1
+ table
2
+ thead
3
+ tr
4
+ th.col-width-large PATIENT
5
+ th.col-width-reference-no HOSP NOs.
6
+ th.col-width-large LOCATION
7
+ th MODALITY
8
+ th.col-width-tiny SEX
9
+ th.col-width-date DOB
10
+
11
+ - consults.each do |consult|
12
+ tr
13
+ / Nested tables. Yum. But necessary to get wkhtml2pdf to avoid orphaning the
14
+ / second 'notes' row (using css - see class definition) from the first 'patient details' row.
15
+ / When having just one table with 2 rows per consult, wkhtml2pdf sometimes breaks a page
16
+ / between the 2 rows when we really want to keep them together. Using a table like this and
17
+ / applying page-break-inside: avoid to it we can avoid this problem. The downside is that the
18
+ / columns may not always line up exactly if the content is wide.
19
+ table.nested-table-in-order-to-prevent-orphaned-rows.spacy
20
+ tbody
21
+ tr.consult-details
22
+ td.col-width-large
23
+ b= consult.patient.to_s(:default)
24
+ td.col-width-reference-no= consult.patient_hospital_identifiers&.to_s_multiline
25
+ td.col-width-large
26
+ - unit = consult.hospital_ward&.hospital_unit
27
+ = [unit&.unit_code,
28
+ consult&.hospital_ward,
29
+ consult.other_site_or_ward].compact.join(" / ")
30
+ td= consult.patient_current_modality
31
+ td.col-width-tiny= consult.patient_sex
32
+ td.col-width-date= l(consult.patient.born_on)
33
+
34
+ tr.consult-notes
35
+ td(colspan=6)= simple_format consult.description
@@ -2,8 +2,15 @@
2
2
 
3
3
  = content_for(:actions) do
4
4
  = link_to("Add", new_admissions_consult_path, class: :button)
5
+ = link_to(admissions_consults_path(q: params[:q]&.permit!, format: :pdf), class: "button secondary") do
6
+ i.fa.fa-print
7
+ | Print (PDF)
8
+ /data: { target: admissions_consults_path(format: :pdf)
5
9
 
6
10
  = within_admin_layout(title: "Admission Consults") do
11
+ = render "renalware/shared/please_print_using_print_button_warning"
12
+ = render "renalware/admissions/requests/modal_dialog_placeholder"
13
+
7
14
  .panel.radius.compact
8
15
  | If the patient is not on Renalware yet, please 
9
16
  = link_to("add them", new_patient_path)
@@ -0,0 +1,10 @@
1
+ - content_for(:head) do
2
+ = wicked_pdf_stylesheet_link_tag "renalware/table_pdf"
3
+
4
+ .pdf
5
+ .header
6
+ h1 Admission Consults
7
+ h2.muted= I18n.l(Time.zone.today)
8
+ = render "filters"
9
+ = render "table", consults: consults
10
+
@@ -19,6 +19,7 @@ tr.empty-session-row.first
19
19
  td(rowspan=2)
20
20
  td(rowspan=2)
21
21
  td(rowspan=2)
22
+ td
22
23
  tr.empty-session-row
23
24
  td
24
25
  td
@@ -27,3 +28,4 @@ tr.empty-session-row
27
28
  td
28
29
  td
29
30
  td
31
+ td
@@ -18,7 +18,9 @@ tr.hd-session-row(class=[session.state, stripe_class])
18
18
  td(rowspan=2)= session.machine_no
19
19
  td(rowspan=2)= session.machine_ktv
20
20
  td(rowspan=2)= session.machine_urr
21
+ td.print-only(rowspan=2)
21
22
  td.print-only(rowspan=2)= session.notes
23
+ td.print-only
22
24
 
23
25
  tr(class=[session.state, stripe_class])
24
26
  td= tooltip(label: session.summarised_access_used, content: session.access_used)
@@ -31,3 +33,4 @@ tr(class=[session.state, stripe_class])
31
33
  td= session.after_measurement_for(:temperature)
32
34
  td= session.after_measurement_for(:bm_stix)
33
35
  td= session.after_measurement_for(:blood_pressure)
36
+ td.print-only
@@ -1,6 +1,6 @@
1
1
  .document-view
2
- .row
3
- .columns.large-6
2
+ .grid
3
+ .row
4
4
  = render "renalware/shared/attributes_group",
5
5
  legend: "Sign-In", destination: "signin",
6
6
  models: { session => [:performed_on,
@@ -28,7 +28,6 @@
28
28
  group: session.document.observations_after,
29
29
  legend: "Post-Dialysis Observations", destination: "after"
30
30
 
31
- .columns.large-6
32
31
  - %W(Dialysis HDF).each do |group|
33
32
  = render "renalware/shared/documents/attributes_group",
34
33
  group: session.document.public_send(group.parameterize(separator: "_")),
@@ -39,6 +38,6 @@
39
38
  legend: "Notes/Complications", destination: "complications",
40
39
  models: { session.document.complications => attributes_list,
41
40
  session => [:notes] }
42
- .row
43
- .columns.large-12
44
- = render "prescription_administrations", session: session
41
+ .row
42
+ .columns
43
+ = render "prescription_administrations", session: session
@@ -18,6 +18,8 @@ thead
18
18
  th.col-width-tiny.text-center(colspan=3)= t(".machine")
19
19
  th.col-width-drugs.text-center.print-only(rowspan=3)= t(".hd_drugs_administered")
20
20
  th.col-width-notes.text-center.print-only(rowspan=3)= t(".notes")
21
+ th.col-width-user.text-center.print-only= t(".user")
22
+
21
23
 
22
24
  tr.row2
23
25
  th(rowspan=2)= t(".access")
@@ -30,6 +32,7 @@ thead
30
32
  th.col-width-minute.col-width-tiny(rowspan=2)=t(".machine_no")
31
33
  th.col-width-minute.col-width-tiny(rowspan=2)=t(".machine_ktv")
32
34
  th.col-width-minute.col-width-tiny(rowspan=2)=t(".machine_urr")
35
+ th.print-only= t(".put_on_by")
33
36
  tr.row2
34
37
  th.col-width-time= t(".end_time")
35
38
  th= t(".post_with_changed")
@@ -37,3 +40,4 @@ thead
37
40
  th= t(".post")
38
41
  th= t(".post")
39
42
  th= t(".post")
43
+ th.print-only= t(".taken_off_by")
@@ -4,8 +4,8 @@ html
4
4
  meta content=("text/html; charset=UTF-8") http-equiv="Content-Type" /
5
5
  meta charset="utf-8" /
6
6
 
7
- = wicked_pdf_stylesheet_link_tag "renalware/pdf"
8
7
  = yield(:head)
8
+ = wicked_pdf_stylesheet_link_tag "renalware/pdf"
9
9
 
10
10
  body
11
11
  = yield
@@ -15,7 +15,7 @@
15
15
  remote: true,
16
16
  wrapper: :horizontal_form) do |f|
17
17
 
18
- = f.input :default_cc, as: :inline_radio_buttons
18
+ = f.input :default_cc, as: :inline_radio_buttons, readonly: nil
19
19
  = f.input :description_id, collection: contact_descriptions
20
20
  = f.input :other_description
21
21
  = f.input :notes, as: :text
@@ -0,0 +1,25 @@
1
+ // Compile a summary of filters applied. Leave out anything not set.
2
+ - filters = params.fetch(:q, {}).reject{ |key, value| value.blank? }
3
+
4
+ .filter-summary
5
+ table
6
+ tr
7
+ th Filters
8
+ - if filters.empty?
9
+ td None
10
+ - else
11
+ - if filters[:term].present?
12
+ th Query:
13
+ td= filters[:term]
14
+ - if filters[:on_hotlist].present?
15
+ th On Hotlist:
16
+ td= yes_no(filters[:on_hotlist] == "true")
17
+ - if filters[:action].present?
18
+ th Action:
19
+ td= Renalware::Renal::AKIAlertAction.find(filters[:action])&.to_s
20
+ - if filters[:hospital_unit_id].present?
21
+ th Site:
22
+ td= Renalware::Hospitals::Unit.find_by(id: filters[:hospital_unit_id])
23
+ - if filters[:hospital_ward_id].present?
24
+ th Ward:
25
+ td= Renalware::Hospitals::Ward.find_by(id: filters[:hospital_ward_id])
@@ -0,0 +1,29 @@
1
+ table
2
+ thead
3
+ tr
4
+ th.col-width-large PATIENT
5
+ th.col-width-reference-no HOSP NOs.
6
+ th.col-width-medium WARD
7
+ th MODALITY
8
+ th.col-width-tiny CRE
9
+ th.col-width-date CRE DATE
10
+ th.col-width-tiny SEX
11
+ th.col-width-date DOB
12
+
13
+ - alerts.each do |alert|
14
+ tr
15
+ table.nested-table-in-order-to-prevent-orphaned-rows.spacy
16
+ tbody
17
+ tr.alert-details
18
+ td.col-width-large
19
+ b= alert.patient.to_s(:default)
20
+ td.col-width-reference-no= alert.patient&.hospital_identifiers&.to_s_multiline
21
+ td.col-width-medium= alert.hospital_ward
22
+ td= alert.patient&.current_modality
23
+ td.col-width-tiny= alert.max_cre
24
+ td.col-width-date= I18n.l(alert.cre_date)
25
+ td.col-width-tiny= alert.patient&.sex
26
+ td.col-width-date= l(alert.patient&.born_on)
27
+
28
+ tr.alert-notes
29
+ td(colspan=8)= simple_format alert.notes
@@ -7,7 +7,7 @@
7
7
  as: :grouped_select,
8
8
  group_method: :wards,
9
9
  collection: Renalware::Hospitals::Unit.includes(:wards).ordered,
10
- label_method: :to_s,
10
+ label_method: ->(ward){ [ward.name, ward.code].compact.join(" - ") },
11
11
  wrapper: :horizontal_medium
12
12
  = f.input :hotlist, wrapper: :horizontal_small, as: :inline_radio_buttons
13
13
  = f.input :max_cre, wrapper: :horizontal_small
@@ -1,4 +1,14 @@
1
+ = content_for(:actions) do
2
+ / Make sure when generating the PDF we pass in exactly the same params eg q and named_filter
3
+ / An alternative to what we have used here is to use
4
+ / url_for(request.filtered_parameters&.merge({format: :pdf})
5
+ / however this seems to loose the ransack sort option in params[:q][:s]
6
+ = link_to(renal_filtered_aki_alerts_path(q: params[:q]&.permit!, format: :pdf), class: "button secondary") do
7
+ i.fa.fa-print
8
+ | Print (PDF)
9
+
1
10
  = within_admin_layout(title: "AKI Alerts") do
11
+ = render "renalware/shared/please_print_using_print_button_warning"
2
12
  = render "filters", form: form, path_params: path_params
3
13
  = render "table", alerts: alerts, search: search
4
14
 
@@ -0,0 +1,14 @@
1
+ - content_for(:head) do
2
+ = wicked_pdf_stylesheet_link_tag "renalware/table_pdf"
3
+
4
+ .pdf
5
+ .header
6
+ h1 AKI Alerts
7
+ - if params[:named_filter].present?
8
+ h1.muted
9
+ |  / 
10
+ = params[:named_filter]&.humanize
11
+ h2.muted= I18n.l(Time.zone.today)
12
+ = render "filters"
13
+ = render "table", alerts: alerts
14
+