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,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/hd/base_controller"
4
+
5
+ module Renalware
6
+ module HD
7
+ module SessionForms
8
+ class BatchesController < BaseController
9
+ def create
10
+ batch = create_unprocessed_batch_and_batch_items
11
+ authorize batch
12
+ Delayed::Job.enqueue BatchPrintJob.new(batch.id, current_user.id)
13
+
14
+ respond_to do |format|
15
+ format.js {
16
+ render locals: { batch: batch }
17
+ }
18
+ end
19
+ end
20
+
21
+ # rubocop:disable Metrics/MethodLength
22
+ def status
23
+ batch = find_and_authorize_batch(params[:batch_id])
24
+ respond_to do |format|
25
+ format.json {
26
+ render(
27
+ status: :ok,
28
+ json: {
29
+ id: batch.id,
30
+ status: batch.status,
31
+ percent_complete: batch.percent_complete
32
+ }
33
+ )
34
+ }
35
+ end
36
+ end
37
+ # rubocop:enable Metrics/MethodLength
38
+ #
39
+
40
+ # rubocop:disable Metrics/MethodLength
41
+ def show
42
+ batch = find_and_authorize_batch
43
+ respond_to do |format|
44
+ format.pdf do
45
+ # Returns the PDF itself using path saved in the Batch - provided status is correct
46
+ send_file(
47
+ batch.filepath,
48
+ type: "application/pdf",
49
+ disposition: "inline",
50
+ filename: "session_forms_batch_#{batch.id}.pdf"
51
+ )
52
+ end
53
+ format.html do
54
+ # Returns a partial with a link to the compiled batch pdf
55
+ render layout: false, locals: { batch: find_and_authorize_batch }
56
+ end
57
+ end
58
+ end
59
+ # rubocop:enable Metrics/MethodLength
60
+
61
+ private
62
+
63
+ def create_unprocessed_batch_and_batch_items
64
+ batch = Batch.new
65
+ Batch.transaction do
66
+ patient_ids.each { |patient_id| batch.items.build(printable_id: patient_id) }
67
+ batch.save_by!(current_user)
68
+ end
69
+ batch
70
+ end
71
+
72
+ def patient_ids
73
+ params.require(:batch).require(:patient_ids).split(",")
74
+ end
75
+
76
+ def find_and_authorize_batch(id = nil)
77
+ Batch.find(id || params[:id]).tap { |batch| authorize batch }
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -18,6 +18,7 @@ module Renalware
18
18
  render :index, locals: { requests: requests, query: requests_query.search }
19
19
  end
20
20
 
21
+ # Displays an historical PDF
21
22
  def show
22
23
  request = RequestPresenter.new(
23
24
  Request.find(params[:id])
@@ -25,17 +26,19 @@ module Renalware
25
26
  authorize request
26
27
  render(
27
28
  pdf: "show",
28
- layout: false,
29
+ page_size: "A4",
30
+ layout: "renalware/layouts/pdf",
29
31
  locals: { request: request },
32
+ show_as_html: params.key?(:debug),
30
33
  extra: "--no-print-media-type" # NOTE: Foundation CSS does not work well in print mode
31
34
  )
32
35
  end
33
36
 
37
+ # HTML POST
34
38
  # NOTE: This needs to be POST since params[:patient_ids] may exceed url char limit in GET
35
39
  def new
36
40
  render(
37
41
  :new,
38
- layout: false,
39
42
  locals: local_vars.merge(
40
43
  all_clinics: Renalware::Pathology::Clinic.for_algorithm,
41
44
  all_consultants: Renalware::Renal::Consultant.ordered,
@@ -44,12 +47,15 @@ module Renalware
44
47
  )
45
48
  end
46
49
 
50
+ # PDF POST
51
+ # Displays multiple PDFs
47
52
  def create
48
53
  requests.each(&:print_form)
49
54
 
50
55
  render(
51
56
  pdf: "create",
52
- layout: false,
57
+ page_size: "A4",
58
+ layout: "renalware/layouts/pdf",
53
59
  locals: local_vars,
54
60
  extra: "--no-print-media-type" # NOTE: Foundation CSS does not work well in print mode
55
61
  )
@@ -67,8 +73,8 @@ module Renalware
67
73
  def request_html_form_params
68
74
  OpenStruct.new(
69
75
  patient_ids: raw_request_params[:patient_ids],
70
- clinic_id: request_params[:clinic].id,
71
- consultant_id: request_params[:consultant].id,
76
+ clinic_id: request_params[:clinic]&.id,
77
+ consultant_id: request_params[:consultant]&.id,
72
78
  telephone: request_params[:telephone],
73
79
  template: request_params[:template]
74
80
  )
@@ -93,7 +99,15 @@ module Renalware
93
99
 
94
100
  def load_patients
95
101
  patient_ids = raw_request_params[:patient_ids]
96
- @patients = Pathology::OrderedPatientQuery.new(patient_ids).call
102
+
103
+ @patients =
104
+ Pathology::OrderedPatientQuery
105
+ .new(patient_ids)
106
+ .call
107
+ .eager_load(:requests)
108
+ .eager_load(:rules)
109
+ .eager_load(:prescriptions)
110
+ .eager_load(:drugs)
97
111
  authorize Renalware::Patient
98
112
  end
99
113
 
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/patients"
4
+
5
+ module Renalware
6
+ module Surveys
7
+ class DashboardsController < BaseController
8
+ def show
9
+ authorize Survey, :index?
10
+ render locals: {
11
+ patient: patient,
12
+ eq5d_responses: EQ5DPivotedResponse.where(patient: patient).ordered,
13
+ pos_s_responses: POSSPivotedResponse.where(patient: patient).ordered
14
+ }
15
+ end
16
+
17
+ private
18
+
19
+ def query_params
20
+ params.fetch(:q, {})
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/letters"
4
+
5
+ module Renalware
6
+ module HD
7
+ module SessionForms
8
+ BatchPrintJob = Struct.new(:batch_id, :user_id) do
9
+ include UsingTempFolder
10
+
11
+ # Returns the name of a temp file containing the pdf data
12
+ def perform
13
+ in_a_temporary_folder do |dir|
14
+ Dir.chdir(dir) do
15
+ batch = Batch.find(batch_id)
16
+ user = User.find(user_id)
17
+ BatchCompilePdfs.call(batch, user)
18
+ end
19
+ end
20
+ end
21
+
22
+ def max_attempts
23
+ 1
24
+ end
25
+
26
+ def queue_name
27
+ "hd_session_forms"
28
+ end
29
+
30
+ def priority
31
+ 0 # high
32
+ end
33
+
34
+ def destroy_failed_jobs?
35
+ true
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module Renalware
6
+ module PdfCompilation
7
+ extend ActiveSupport::Concern
8
+
9
+ def combine_multiple_pdfs_into_file(filepath:, glob:)
10
+ filepath = Pathname(filepath)
11
+ Rails.logger.info " Compiling PDFs #{glob.join(',')} into #{filepath}"
12
+ shell_to_ghostscript_to_combine_files(glob, dir, filepath)
13
+ filepath
14
+ end
15
+
16
+ def combine_multiple_pdfs_using_filenames(filenames, dir, output_filepath)
17
+ filenames = Array(filenames)
18
+ Rails.logger.info " Compiling PDFs #{filenames.join(',')} into #{output_filepath}"
19
+ using_a_temporary_output_file do |tmp_outfile|
20
+ shell_to_ghostscript_to_combine_files(filenames, dir, tmp_outfile)
21
+ move_tempfile_to_output_file(tmp_outfile, output_filepath)
22
+ end
23
+ end
24
+
25
+ # rubocop:disable Metrics/LineLength
26
+ def shell_to_ghostscript_to_combine_files(filenames, dir, outputfile)
27
+ outputfile = Pathname(outputfile)
28
+ cmd = "gs -dNOPAUSE -sDEVICE=pdfwrite -sOUTPUTFILE=#{outputfile} -dBATCH #{filenames.join(' ')}"
29
+ err = msg = nil
30
+ Open3.popen3(cmd, chdir: dir.to_s) do |_stdin, stdout, stderr|
31
+ err = stderr.read
32
+ msg = stdout.read
33
+ end
34
+ if err.present?
35
+ raise "Error combining PDFs: #{[err, msg].join(' ')} command: #{cmd}"
36
+ end
37
+ end
38
+ # rubocop:enable Metrics/LineLength
39
+
40
+ def move_tempfile_to_output_file(tmp_outfile, output_file)
41
+ FileUtils.mv tmp_outfile.path, output_file
42
+ output_file
43
+ end
44
+
45
+ def rails_tmp_folder
46
+ Rails.root.join("tmp").to_s
47
+ end
48
+
49
+ # Create a tempfile outside the temp dir as dir will be destroyed when outside block closes.
50
+ def using_a_temporary_output_file
51
+ file = Tempfile.new("pdf_combined", rails_tmp_folder)
52
+ begin
53
+ yield file
54
+ ensure
55
+ file.close
56
+ file.unlink # deletes the temp file
57
+ end
58
+ end
59
+ end
60
+ end
@@ -9,7 +9,7 @@ module Renalware
9
9
  attribute :no_known_allergies, Integer
10
10
 
11
11
  def save(patient, user)
12
- return unless patient.allergies.count == 0
12
+ return unless patient.allergies.count.zero?
13
13
 
14
14
  patient.allergy_status = no_known_allergies? ? :no_known_allergies : :unrecorded
15
15
  patient.by = user
@@ -12,7 +12,7 @@ module Renalware
12
12
  Allergy.transaction do
13
13
  patient = allergy.patient
14
14
  allergy.destroy
15
- if patient.allergies.count == 0
15
+ if patient.allergies.count.zero?
16
16
  patient.update(allergy_status: :unrecorded, by: user)
17
17
  end
18
18
  end
@@ -25,13 +25,9 @@ module Renalware
25
25
  end
26
26
 
27
27
  class QueryableAppointment < ActiveType::Record[Appointment]
28
- ransacker :starts_at, type: :date do
28
+ ransacker :starts_on, type: :date do
29
29
  Arel.sql("DATE(starts_at)")
30
30
  end
31
-
32
- ransacker :start_time, type: :datetime do
33
- Arel.sql("starts_at")
34
- end
35
31
  end
36
32
  end
37
33
  end
@@ -9,5 +9,11 @@ module Renalware
9
9
  def self.cast_patient(patient)
10
10
  ActiveType.cast(patient, ::Renalware::HD::Patient)
11
11
  end
12
+
13
+ module SessionForms
14
+ def self.table_name_prefix
15
+ "hd_session_form_"
16
+ end
17
+ end
12
18
  end
13
19
  end
@@ -3,7 +3,7 @@
3
3
  module Renalware
4
4
  module HD
5
5
  class PatientListener
6
- def patient_modality_changed_to_death(patient:, modality:, actor:)
6
+ def patient_modality_changed_to_death(patient:, actor:, **)
7
7
  nullify_significant_hd_profile_attributes(patient, by: actor)
8
8
  end
9
9
 
@@ -0,0 +1,26 @@
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 Batch < ApplicationRecord
10
+ include Accountable
11
+
12
+ enum status: { queued: 0, processing: 10, awaiting_printing: 15, failure: 20, success: 30 }
13
+
14
+ has_many(
15
+ :items,
16
+ dependent: :restrict_with_exception,
17
+ class_name: "BatchItem"
18
+ )
19
+
20
+ def percent_complete
21
+ ((items.where(status: :compiled).count.to_f / batch_items_count) * 100).ceil
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/letters"
4
+ require "attr_extras"
5
+
6
+ module Renalware
7
+ module HD
8
+ module SessionForms
9
+ # Given a Batch object representing a request to 'print' (ie compile) a PDF of multiple
10
+ # HD Session Forms (aka protocols), where each batch.item points to the patient we want to
11
+ # print, we render each PDF in the current folder (we assume the caller has chdir'ed
12
+ # us into a tmp location), and then append them all together as <batchid>.pdf in a known
13
+ # location. The filepath is assigned to the batch and saved, so it can be served to
14
+ # a user via the user later.
15
+ class BatchCompilePdfs
16
+ include PdfCompilation
17
+
18
+ def self.call(batch, user)
19
+ new(batch, user).call
20
+ end
21
+
22
+ def initialize(batch, user)
23
+ @batch = batch
24
+ @user = user
25
+ @dir = Pathname(Dir.pwd)
26
+ end
27
+
28
+ def call
29
+ batch.update_by(user, status: :processing)
30
+ process_batch_items
31
+ batch.filepath = append_files
32
+ batch.status = :awaiting_printing
33
+ batch.save_by!(user)
34
+ rescue StandardError => e
35
+ batch.update(last_error: e.message, status: :failure)
36
+ raise e
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :batch, :dir, :user
42
+
43
+ def process_batch_items
44
+ filename = "batch_#{batch.id}.pdf"
45
+ File.open(filename, "wb") do |file|
46
+ file.write(PdfRenderer.new(patients: patients).call)
47
+ end
48
+ batch.items.each { |item| item.update(status: :compiled) }
49
+ end
50
+
51
+ def patients
52
+ @patients ||= HD::Patient.where(id: batch.items.pluck(:printable_id))
53
+ end
54
+
55
+ def render_session_form_pdf_to_file_for(patient)
56
+ filename = "session_form_#{patient.id}.pdf"
57
+ File.open(filename, "wb") do |file|
58
+ file.write(PdfRenderer.new(patient: patient).call)
59
+ end
60
+ filename
61
+ end
62
+
63
+ def append_files
64
+ glob = Dir.glob(dir.join("*.pdf"))
65
+ if glob.any?
66
+ combine_multiple_pdfs_into_file(
67
+ filepath: compiled_output_pdf_filename,
68
+ glob: glob
69
+ )
70
+ end
71
+ Pathname(compiled_output_pdf_filename).to_s # TODO: what happens if no content?
72
+ end
73
+
74
+ def working_folder
75
+ Rails.root.join("tmp", "batched_hd_session_forms").tap do |folder|
76
+ FileUtils.mkdir_p folder
77
+ end
78
+ end
79
+
80
+ def compiled_output_pdf_filename
81
+ working_folder.join("#{batch.id}.pdf")
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end