renalware-core 2.0.111 → 2.0.112

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +9 -0
  3. data/app/assets/javascripts/renalware/application.js.erb +7 -8
  4. data/app/assets/javascripts/renalware/charting/charting.js.erb +4 -0
  5. data/app/assets/javascripts/renalware/dialogs.js.erb +0 -11
  6. data/app/assets/javascripts/renalware/hd.js +59 -9
  7. data/app/assets/stylesheets/renalware/application.scss +2 -3
  8. data/app/assets/stylesheets/renalware/modules/_hd.scss +19 -0
  9. data/app/assets/stylesheets/renalware/modules/_patients.scss +7 -0
  10. data/app/assets/stylesheets/renalware/print/pathology_request_forms.scss +26 -0
  11. data/app/assets/stylesheets/renalware/protocol_pdf.scss +9 -4
  12. data/app/controllers/renalware/hd/mdm_patients_controller.rb +17 -6
  13. data/app/controllers/renalware/hd/protocols_controller.rb +22 -31
  14. data/app/controllers/renalware/hd/session_forms/batches_controller.rb +82 -0
  15. data/app/controllers/renalware/pathology/requests/requests_controller.rb +20 -6
  16. data/app/controllers/renalware/surveys/dashboards_controller.rb +24 -0
  17. data/app/jobs/renalware/hd/session_forms/batch_print_job.rb +40 -0
  18. data/app/models/concerns/renalware/pdf_compilation.rb +60 -0
  19. data/app/models/renalware/clinical/allergy_status_form.rb +1 -1
  20. data/app/models/renalware/clinical/delete_allergy.rb +1 -1
  21. data/app/models/renalware/clinics/appointment_query.rb +1 -5
  22. data/app/models/renalware/hd.rb +6 -0
  23. data/app/models/renalware/hd/patient_listener.rb +1 -1
  24. data/app/models/renalware/hd/session_forms/batch.rb +26 -0
  25. data/app/models/renalware/hd/session_forms/batch_compile_pdfs.rb +86 -0
  26. data/app/models/renalware/hd/session_forms/batch_item.rb +15 -0
  27. data/app/models/renalware/hd/session_forms/form.rb +16 -0
  28. data/app/models/renalware/hd/session_forms/pdf_renderer.rb +74 -0
  29. data/app/models/renalware/letters/batch.rb +2 -0
  30. data/app/models/renalware/letters/printing/batch_compile_pdfs.rb +0 -3
  31. data/app/models/renalware/medications/patient_listener.rb +1 -1
  32. data/app/models/renalware/pathology/ordered_patient_query.rb +1 -1
  33. data/app/models/renalware/pathology/request_for_patient_request_description_query.rb +1 -1
  34. data/app/models/renalware/pd/apd/non_tidal_regime_calculations.rb +1 -1
  35. data/app/models/renalware/pd/apd/tidal_regime_calculations.rb +1 -1
  36. data/app/models/renalware/snippets/snippet_query.rb +1 -1
  37. data/app/models/renalware/surveys.rb +9 -0
  38. data/app/models/renalware/surveys/eq5d_pivoted_response.rb +11 -0
  39. data/app/models/renalware/surveys/eq5d_summary_part.rb +51 -0
  40. data/app/models/renalware/surveys/pos_s_pivoted_response.rb +11 -0
  41. data/app/models/renalware/surveys/pos_s_summary_part.rb +51 -0
  42. data/app/models/renalware/surveys/question.rb +22 -0
  43. data/app/models/renalware/surveys/response.rb +28 -0
  44. data/app/models/renalware/surveys/survey.rb +17 -0
  45. data/app/models/renalware/ukrdc.rb +3 -0
  46. data/app/models/renalware/ukrdc/incoming/file_list.rb +21 -0
  47. data/app/models/renalware/ukrdc/incoming/import_survey.rb +57 -0
  48. data/app/models/renalware/ukrdc/incoming/import_surveys.rb +77 -0
  49. data/app/models/renalware/ukrdc/incoming/paths.rb +30 -0
  50. data/app/models/renalware/ukrdc/incoming/xml_document.rb +68 -0
  51. data/app/models/renalware/ukrdc/transmission_log.rb +14 -3
  52. data/app/policies/renalware/hd/session_forms/batch_policy.rb +13 -0
  53. data/app/policies/renalware/surveys/survey_policy.rb +10 -0
  54. data/app/presenters/renalware/hd/session_presenter.rb +1 -1
  55. data/app/views/renalware/clinics/appointments/index.html.slim +22 -11
  56. data/app/views/renalware/hd/dashboards/_page_actions.html.slim +1 -1
  57. data/app/views/renalware/hd/diary_slots/_form.html.slim +0 -3
  58. data/app/views/renalware/hd/mdm_patients/_filters.html.slim +11 -0
  59. data/app/views/renalware/hd/mdm_patients/_page_actions.html.slim +0 -0
  60. data/app/views/renalware/hd/mdm_patients/index.html.slim +9 -1
  61. data/app/views/renalware/hd/protocols/{show.pdf.slim → _protocol.pdf.slim} +1 -7
  62. data/app/views/renalware/hd/protocols/index.pdf.slim +4 -0
  63. data/app/views/renalware/hd/session_forms/batches/_create.html.slim +25 -0
  64. data/app/views/renalware/hd/session_forms/batches/create.js.erb +3 -0
  65. data/app/views/renalware/hd/sessions/_row.html.slim +1 -1
  66. data/app/views/renalware/hd/sessions/_thead.html.slim +1 -1
  67. data/app/views/renalware/pathology/historical_observation_results/_table.html.slim +1 -1
  68. data/app/views/renalware/pathology/recent_observation_results/_table.html.slim +1 -1
  69. data/app/views/renalware/pathology/requests/requests/create.pdf.slim +2 -1
  70. data/app/views/renalware/pathology/requests/requests/new.html.slim +18 -13
  71. data/app/views/renalware/pathology/requests/requests/request_form/_crs.pdf.slim +1 -0
  72. data/app/views/renalware/pathology/requests/requests/request_form/_crs_form.html.slim +46 -45
  73. data/app/views/renalware/pathology/requests/requests/request_form/_manual_form_global.html.slim +9 -8
  74. data/app/views/renalware/pathology/requests/requests/request_form/_manual_form_patient.html.slim +9 -8
  75. data/app/views/renalware/pathology/requests/requests/show.pdf.slim +2 -1
  76. data/app/views/renalware/patients/side_menu/_general.html.slim +4 -0
  77. data/app/views/renalware/surveys/_eq5d_summary_part.html.slim +26 -0
  78. data/app/views/renalware/surveys/_pos_s_summary_part.html.slim +26 -0
  79. data/app/views/renalware/surveys/dashboards/show.html.slim +32 -0
  80. data/config/initializers/chartkick.rb +6 -0
  81. data/config/initializers/core_extensions.rb +4 -4
  82. data/config/initializers/inflections.rb +3 -0
  83. data/config/locales/renalware/patients/side_menu.en.yml +1 -0
  84. data/config/routes/hd.rb +5 -1
  85. data/config/routes/patients.rb +4 -0
  86. data/db/migrate/20190927124840_create_hd_session_form_batches.rb +16 -0
  87. data/db/migrate/20190927130911_create_hd_session_form_batch_items.rb +17 -0
  88. data/db/migrate/20191008010839_create_surveys.rb +70 -0
  89. data/db/migrate/20191008024636_create_eq5d_views.rb +8 -0
  90. data/db/migrate/20191008030154_add_validation_regex_to_patient_survey_questions.rb +7 -0
  91. data/db/migrate/20191008045159_allow_null_patient_in_ukrdc_transmission_logs.rb +8 -0
  92. data/db/seeds/default/patients/seeds.rb +2 -0
  93. data/db/seeds/default/patients/surveys/eq5d_5l.rb +22 -0
  94. data/db/seeds/default/patients/surveys/eq5d_5l_questions.csv +7 -0
  95. data/db/seeds/default/patients/surveys/pos_s.rb +22 -0
  96. data/db/seeds/default/patients/surveys/pos_s_questions.csv +23 -0
  97. data/db/views/survey_eq5d_pivoted_responses_v01.sql +41 -0
  98. data/db/views/survey_pos_s_pivoted_responses_v01.sql +36 -0
  99. data/lib/core_extensions/active_record/migration_helpers.rb +1 -1
  100. data/lib/core_extensions/active_support/duration.rb +1 -1
  101. data/lib/core_extensions/scenic.rb +1 -1
  102. data/lib/renalware/engine.rb +4 -4
  103. data/lib/renalware/version.rb +1 -1
  104. data/lib/tasks/ukrdc.rake +9 -0
  105. data/spec/factories/patients/surveys.rb +24 -0
  106. data/spec/support/ukrdc_helpers.rb +16 -0
  107. metadata +98 -59
  108. data/app/views/renalware/letters/batches/show.html.slim +0 -7
  109. data/app/views/renalware/pathology/requests/requests/_pdf_header.html.slim +0 -13
  110. data/vendor/assets/javascripts/renalware/masonry.min.js +0 -9
  111. data/vendor/assets/javascripts/renalware/print.min.js +0 -1
  112. data/vendor/assets/stylesheets/renalware/balloon.css +0 -383
  113. data/vendor/assets/stylesheets/renalware/print.min.css +0 -1
  114. data/vendor/assets/stylesheets/renalware/select2/css/select2.css +0 -431
  115. data/vendor/assets/stylesheets/renalware/select2/css/select2.min.css +0 -1
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "attr_extras"
4
+ require_dependency "renalware/hd"
5
+
6
+ module Renalware
7
+ module HD
8
+ module SessionForms
9
+ class BatchItem < ApplicationRecord
10
+ belongs_to :batch, counter_cache: true
11
+ enum status: { queued: 0, compiled: 10 }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/hd"
4
+
5
+ module Renalware
6
+ module HD
7
+ module SessionForms
8
+ class Form
9
+ include ActiveModel::Model
10
+ include Virtus::Model
11
+
12
+ attribute :patient_ids, String
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renalware
4
+ module HD
5
+ module SessionForms
6
+ # Renders 1 or more HD Protocols (Session Forms) to PDF. If > 1 patient
7
+ class PdfRenderer
8
+ def initialize(patients:, output_html_for_debugging: false)
9
+ @patients = Array(patients)
10
+ @output_html_for_debugging = output_html_for_debugging
11
+ end
12
+
13
+ def call
14
+ return pdf_html if output_html_for_debugging?
15
+
16
+ WickedPdf.new.pdf_from_string(pdf_html, pdf_options)
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :patients, :output_html_for_debugging
22
+
23
+ def pdf_html
24
+ @pdf_html ||= begin
25
+ # Note we don't actually hit the ProtocolController#index action here.
26
+ controller.render_to_string(
27
+ template: "renalware/hd/protocols/index",
28
+ layout: "renalware/layouts/pdf",
29
+ locals: {
30
+ protocols: protocols_to_render,
31
+ host: "localhost"
32
+ }
33
+ )
34
+ end
35
+ end
36
+
37
+ def controller
38
+ @controller ||= MyController.new
39
+ end
40
+
41
+ def protocols_to_render
42
+ patients.map { |pat| ProtocolPresenter.new(pat, controller.view_context) }
43
+ end
44
+
45
+ def pdf_options
46
+ {
47
+ page_size: "A4",
48
+ orientation: "Landscape",
49
+ layout: "renalware/layouts/pdf",
50
+ print_media_type: true,
51
+ margin: { top: 10, bottom: 10, left: 10, right: 10 },
52
+ footer: {
53
+ font_size: 8
54
+ }
55
+ }
56
+ end
57
+
58
+ def output_html_for_debugging?
59
+ output_html_for_debugging == true
60
+ end
61
+
62
+ # Local ProtocolsController controller used for 'rendering anywhere'.
63
+ # Using ProtocolsController here m,ainly to benefit from the view paths associated with it.
64
+ class MyController < ProtocolsController
65
+ def url_options
66
+ {
67
+ host: ENV.fetch("HOST", "localhost")
68
+ }
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -6,6 +6,8 @@ module Renalware
6
6
  module Letters
7
7
  class Batch < ApplicationRecord
8
8
  include Accountable
9
+
10
+ # TODO: I think these attributes are dead
9
11
  attr_accessor(
10
12
  :url,
11
13
  :enclosures_present,
@@ -10,9 +10,6 @@ module Renalware
10
10
  # - compiles each letter along with its recipient cover sheets
11
11
  # - adds blank pages to make sure the letter always starts on an odd page
12
12
  # - appends all PDF letters
13
- # 2-page documents (from the contents of the /2 folder)
14
- # - moves these compiled PDFs to a network share.
15
- # - removes the temp folder structure - auto
16
13
  class BatchCompilePdfs
17
14
  include PdfCombining
18
15
  PAGE_COUNTS = %w(2 3 4 5 6 7 8 9 10).freeze
@@ -5,7 +5,7 @@ require_dependency "renalware/medications"
5
5
  module Renalware
6
6
  module Medications
7
7
  class PatientListener
8
- def patient_modality_changed_to_death(patient:, modality:, actor:)
8
+ def patient_modality_changed_to_death(patient:, actor:, **)
9
9
  TerminateAllPatientPrescriptions.call(patient: patient, by: actor)
10
10
  end
11
11
  end
@@ -19,7 +19,7 @@ module Renalware
19
19
  private
20
20
 
21
21
  def ids_index
22
- ::SQL::IndexedCaseStmt.new(:id, @patient_ids).generate
22
+ ::SQL::IndexedCaseStmt.new("patients.id", @patient_ids).generate
23
23
  end
24
24
  end
25
25
  end
@@ -13,7 +13,7 @@ module Renalware
13
13
  def call
14
14
  @patient
15
15
  .requests
16
- .joins(:request_descriptions)
16
+ .eager_load(:request_descriptions)
17
17
  .where(
18
18
  pathology_request_descriptions_requests_requests: {
19
19
  request_description_id: @request_description.id
@@ -15,7 +15,7 @@ module Renalware
15
15
  private
16
16
 
17
17
  def volume_calculable?
18
- fill_volume.to_i > 0 && cycles.to_i > 0
18
+ fill_volume.to_i.positive? && cycles.to_i.positive?
19
19
  end
20
20
  end
21
21
  end
@@ -26,7 +26,7 @@ module Renalware
26
26
  def volume_calculable?
27
27
  raise ArgumentError, "Tidal indicator not set" unless tidal?
28
28
 
29
- fill_volume.to_i > 0 && cycles.to_i > 0 && tidal_percentage.to_i > 0
29
+ fill_volume.to_i.positive? && cycles.to_i.positive? && tidal_percentage.to_i.positive?
30
30
  end
31
31
 
32
32
  def volume_when_1st_and_then_every_3rd_exchange_is_full_and_the_remainder_are_tidal
@@ -6,7 +6,7 @@ module Renalware
6
6
  attr_reader :author, :relation
7
7
 
8
8
  def initialize(relation: nil, author:)
9
- @relation ||= Snippet.includes(:author).all
9
+ @relation = relation || Snippet.includes(:author).all
10
10
  @author = author
11
11
  end
12
12
 
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renalware
4
+ module Surveys
5
+ def self.table_name_prefix
6
+ "survey_"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/surveys"
4
+
5
+ module Renalware
6
+ module Surveys
7
+ class EQ5DPivotedResponse < ApplicationRecord
8
+ scope :ordered, -> { order(answered_on: :desc) }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/surveys"
4
+
5
+ module Renalware
6
+ module Surveys
7
+ class EQ5DSummaryPart < Renalware::SummaryPart
8
+ def rows
9
+ @rows ||= EQ5DPivotedResponse.where(patient_id: patient.id)
10
+ end
11
+
12
+ def question_labels
13
+ survey
14
+ .questions
15
+ .order(:position)
16
+ .select(:code, :label)
17
+ .each_with_object({}) { |q, hash| hash[q.code] = q.label }
18
+ end
19
+
20
+ def data_for_question_code(_code)
21
+ Renalware::Surveys::Response
22
+ .where(patient_id: patient.id, question_id: 1)
23
+ .pluck(:answered_on, :value)
24
+ .to_h
25
+ end
26
+
27
+ def column_headings
28
+ @column_headings ||= begin
29
+ headings = ["Date"]
30
+
31
+ headings.concat(
32
+ Renalware::Survey
33
+ .find_by!(code: "eq5d")
34
+ .questions
35
+ .order(:position)
36
+ .pluck(:label)
37
+ )
38
+ headings
39
+ end
40
+ end
41
+
42
+ def to_partial_path
43
+ "renalware/surveys/eq5d_summary_part"
44
+ end
45
+
46
+ def survey
47
+ @survey ||= Renalware::Surveys::Survey.find_by!(code: "eq5d")
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/surveys"
4
+
5
+ module Renalware
6
+ module Surveys
7
+ class POSSPivotedResponse < ApplicationRecord
8
+ scope :ordered, -> { order(answered_on: :desc) }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/surveys"
4
+
5
+ module Renalware
6
+ module Surveys
7
+ class POSSSummaryPart < Renalware::SummaryPart
8
+ def rows
9
+ @rows ||= POSSPivotedResponse.where(patient_id: patient.id)
10
+ end
11
+
12
+ def question_labels
13
+ survey
14
+ .questions
15
+ .order(:position)
16
+ .select(:code, :label)
17
+ .each_with_object({}) { |q, hash| hash[q.code] = q.label }
18
+ end
19
+
20
+ def data_for_question_code(_code)
21
+ Renalware::Surveys::Response
22
+ .where(patient_id: patient.id, question_id: 1)
23
+ .pluck(:answered_on, :value)
24
+ .to_h
25
+ end
26
+
27
+ def column_headings
28
+ @column_headings ||= begin
29
+ headings = ["Date"]
30
+
31
+ headings.concat(
32
+ Renalware::Survey
33
+ .find_by!(code: "prom")
34
+ .questions
35
+ .order(:position)
36
+ .pluck(:label)
37
+ )
38
+ headings
39
+ end
40
+ end
41
+
42
+ def to_partial_path
43
+ "renalware/surveys/pos_s_summary_part"
44
+ end
45
+
46
+ def survey
47
+ @survey ||= Renalware::Surveys::Survey.find_by!(code: "prom")
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/surveys"
4
+
5
+ module Renalware
6
+ module Surveys
7
+ class Question < ApplicationRecord
8
+ include PatientScope
9
+ acts_as_paranoid
10
+ belongs_to :survey
11
+ has_many(
12
+ :responses,
13
+ class_name: "Response",
14
+ dependent: :nullify,
15
+ foreign_key: :question_id
16
+ )
17
+ validates :code, presence: true, uniqueness: { scope: :survey_id }
18
+ validates :label, presence: true
19
+ validates :position, presence: true
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/surveys"
4
+
5
+ module Renalware
6
+ module Surveys
7
+ class Response < ApplicationRecord
8
+ belongs_to :question, class_name: "Question"
9
+ validates :patient_id, presence: true
10
+ validates :answered_on, presence: true
11
+ validates :question, presence: true
12
+ validate :value_is_in_range
13
+
14
+ private
15
+
16
+ def value_is_in_range
17
+ return if question&.validation_regex.blank?
18
+
19
+ unless value =~ /#{question.validation_regex}/
20
+ errors.add(
21
+ :value,
22
+ "(#{value}) must comply to regular expression /#{question.validation_regex}/"
23
+ )
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/surveys"
4
+
5
+ module Renalware
6
+ module Surveys
7
+ class Survey < ApplicationRecord
8
+ acts_as_paranoid
9
+ has_many :questions, dependent: :destroy
10
+ validates :name, presence: true, uniqueness: true
11
+
12
+ def to_s
13
+ name
14
+ end
15
+ end
16
+ end
17
+ end
@@ -4,6 +4,9 @@ require_dependency "renalware"
4
4
 
5
5
  module Renalware
6
6
  module UKRDC
7
+ class SurveyNotFoundError < StandardError; end
8
+ class QuestionNotFoundError < StandardError; end
9
+
7
10
  def self.table_name_prefix
8
11
  "ukrdc_"
9
12
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "attr_extras"
4
+
5
+ module Renalware
6
+ module UKRDC
7
+ module Incoming
8
+ class FileList
9
+ pattr_initialize [pattern: "*.xml", paths: Paths.new]
10
+
11
+ # Helper which yields each file in the incoming folder.
12
+ def each_file
13
+ Dir.glob(paths.incoming.join(pattern)).sort.each do |filepath|
14
+ filename = File.basename(filepath)
15
+ yield(Pathname(filepath), filename) if block_given?
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/ukrdc"
4
+ require "attr_extras"
5
+
6
+ module Renalware
7
+ module UKRDC
8
+ module Incoming
9
+ class ImportSurvey
10
+ pattr_initialize [:patient, :survey_hash]
11
+
12
+ def call
13
+ responses.each do |question_code, answer|
14
+ question = question_having_code(question_code)
15
+
16
+ Surveys::Response.create!(
17
+ patient_id: patient.id,
18
+ question: question,
19
+ value: answer,
20
+ answered_on: answered_on
21
+ )
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def responses
28
+ survey_hash[:responses]
29
+ end
30
+
31
+ def survey
32
+ @survey ||= begin
33
+ survey_code = survey_hash[:code].downcase
34
+ Surveys::Survey.includes(:questions).find_by!(code: survey_code)
35
+ end
36
+ rescue ActiveRecord::RecordNotFound
37
+ raise UKRDC::SurveyNotFoundError, "Survey with name #{survey_hash[:code]} not found"
38
+ end
39
+
40
+ def question_having_code(code)
41
+ survey.questions.detect { |q| q.code == code }.tap do |question|
42
+ if question.nil?
43
+ raise(
44
+ UKRDC::QuestionNotFoundError,
45
+ "Question with code #{code} not found in survey #{survey.name}"
46
+ )
47
+ end
48
+ end
49
+ end
50
+
51
+ def answered_on
52
+ survey_hash[:time]
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end