renalware-core 2.0.24 → 2.0.25

Sign up to get free protection for your applications and to get access to all the features.
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
+