renalware-core 2.0.109 → 2.0.110

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/renalware/loading-gears-animation.gif +0 -0
  3. data/app/assets/javascripts/renalware/drugs.js +3 -4
  4. data/app/assets/javascripts/renalware/letters.js +47 -2
  5. data/app/assets/pdf/blank_page.pdf +56 -0
  6. data/app/assets/stylesheets/renalware/modules/_letters.scss +46 -0
  7. data/app/assets/stylesheets/renalware/partials/_tables.scss +2 -2
  8. data/app/controllers/renalware/base_controller.rb +9 -2
  9. data/app/controllers/renalware/clinics/appointments_controller.rb +8 -3
  10. data/app/controllers/renalware/letters/batches_controller.rb +106 -0
  11. data/app/controllers/renalware/letters/completed_batches_controller.rb +32 -0
  12. data/app/controllers/renalware/letters/lists_controller.rb +34 -5
  13. data/app/controllers/renalware/pathology/requests/requests_controller.rb +1 -1
  14. data/app/jobs/renalware/hd/update_rolling_patient_statistics_dj_job.rb +38 -0
  15. data/app/jobs/renalware/hd/update_rolling_patient_statistics_job.rb +4 -2
  16. data/app/jobs/renalware/letters/printing/batch_print_job.rb +27 -0
  17. data/app/models/concerns/renalware/patients_ransack_helper.rb +1 -1
  18. data/app/models/concerns/renalware/using_temp_folder.rb +14 -0
  19. data/app/models/renalware/clinics/appointment.rb +2 -3
  20. data/app/models/renalware/clinics/appointment_query.rb +5 -1
  21. data/app/models/renalware/hd/sessions/save_session.rb +5 -1
  22. data/app/models/renalware/hd/update_rolling_patient_statistics.rb +1 -1
  23. data/app/models/renalware/letters/batch.rb +37 -0
  24. data/app/models/renalware/letters/batch_item.rb +17 -0
  25. data/app/models/renalware/letters/lists/form.rb +101 -0
  26. data/app/models/renalware/letters/pdf_letter_cache.rb +1 -1
  27. data/app/models/renalware/letters/printing/batch_compile_pdfs.rb +121 -0
  28. data/app/models/renalware/letters/printing/complete_batch.rb +24 -0
  29. data/app/models/renalware/letters/printing/create_pdf_by_interleaving_address_sheet_and_letter_for_each_recipient.rb.dead +90 -0
  30. data/app/models/renalware/letters/printing/duplex_interleaved_pdf_renderer.rb +1 -1
  31. data/app/models/renalware/letters/printing/pdf_combining.rb +24 -21
  32. data/app/models/renalware/pathology/{consultant.rb → consultant.rb.dead} +0 -0
  33. data/app/models/renalware/pathology/observations_grouped_by_date_query.rb +1 -1
  34. data/app/models/renalware/pathology/relevant_observation_description.rb +1 -1
  35. data/app/models/renalware/pathology/requests/global_rule/transplant_date_within_weeks.rb +1 -1
  36. data/app/models/renalware/pathology/requests/global_rule/transplant_registration_status.rb +3 -1
  37. data/app/models/renalware/pathology/requests/request.rb +1 -1
  38. data/app/models/renalware/pathology/requests/request_params_factory.rb +11 -12
  39. data/app/models/renalware/patient.rb +3 -2
  40. data/app/models/renalware/pd/apd_regime.rb +6 -6
  41. data/app/models/renalware/renal/consultant.rb +16 -0
  42. data/app/models/renalware/transplants/registrations/wait_list_query.rb +4 -10
  43. data/app/models/renalware/ukrdc/treatment_timeline/generator_factory.rb +1 -1
  44. data/app/pdfs/renalware/letters/printing/recipient_address_page_pdf.rb +50 -0
  45. data/app/policies/renalware/letters/batch_policy.rb +13 -0
  46. data/app/presenters/renalware/letters/recipient_presenter.rb +1 -1
  47. data/app/presenters/renalware/pathology/requests/request_presenter.rb +1 -1
  48. data/app/presenters/renalware/transplants/mdm_presenter.rb +1 -1
  49. data/app/presenters/renalware/virology/dashboard_presenter.rb +2 -1
  50. data/app/views/renalware/api/ukrdc/patients/_hd_session_observations.xml.builder +2 -2
  51. data/app/views/renalware/clinics/appointments/index.html.slim +5 -4
  52. data/app/views/renalware/clinics/appointments/new.html.slim +3 -0
  53. data/app/views/renalware/letters/batches/_create.html.slim +27 -0
  54. data/app/views/renalware/letters/batches/create.js.erb +3 -0
  55. data/app/views/renalware/letters/batches/index.html.slim +20 -0
  56. data/app/views/renalware/letters/batches/show.html.slim +7 -0
  57. data/app/views/renalware/letters/completed_batches/create.js.erb +9 -0
  58. data/app/views/renalware/letters/completed_batches/new.html.slim +26 -0
  59. data/app/views/renalware/letters/lists/_table.html.slim +1 -1
  60. data/app/views/renalware/letters/lists/_tabs.html.slim +9 -0
  61. data/app/views/renalware/letters/lists/index.html.slim.dead +101 -0
  62. data/app/views/renalware/letters/lists/index.html.slim.dead1 +90 -0
  63. data/app/views/renalware/letters/lists/show.html.slim +57 -11
  64. data/app/views/renalware/medications/prescriptions/_form.html.slim +1 -0
  65. data/app/views/renalware/navigation/_super_admin.html.slim +1 -0
  66. data/app/views/renalware/pathology/requests/requests/_pdf_header.html.slim +2 -3
  67. data/app/views/renalware/pathology/requests/requests/index.html.slim +1 -1
  68. data/app/views/renalware/pathology/requests/requests/new.html.slim +0 -1
  69. data/config/locales/renalware/letters/letter.en.yml +5 -0
  70. data/config/routes/letters.rb +18 -1
  71. data/db/migrate/20190830153416_create_letter_batches.rb +14 -0
  72. data/db/migrate/20190902085216_create_letter_batch_items.rb +13 -0
  73. data/db/migrate/20190919073410_add_filepath_to_letter_batches.rb +7 -0
  74. data/db/migrate/20190920063447_add_status_to_letter_batch_items.rb +15 -0
  75. data/db/migrate/20190925104902_remove_user_from_appointments.rb +19 -0
  76. data/db/migrate/20190925130052_add_telephone_to_renal_consultants.rb +7 -0
  77. data/db/migrate/20190925161724_change_path_request_consultant_id.rb +19 -0
  78. data/db/migrate/20190925173849_renal_consultants_changes.rb +9 -0
  79. data/db/migrate/20190928131032_add_some_more_missing_indexes.rb +18 -0
  80. data/db/views/pathology_observation_digests_v01.sql +1 -1
  81. data/lib/renalware/configuration.rb +2 -0
  82. data/lib/renalware/engine.rb +1 -0
  83. data/lib/renalware/version.rb +1 -1
  84. data/lib/tasks/demo_data.rake +66 -0
  85. data/lib/tasks/pd.rake +32 -0
  86. data/spec/factories/clinics/appointments.rb +1 -1
  87. data/spec/factories/medications/medication_route.rb +5 -0
  88. data/spec/factories/renal/consultants.rb +9 -0
  89. data/spec/support/pages/medications/prescription_fom.rb +54 -0
  90. metadata +43 -7
  91. data/app/models/renalware/letters/printing/create_pdf_by_interleaving_address_sheet_and_letter_for_each_recipient.rb +0 -140
  92. data/spec/factories/pathology/consultant.rb +0 -5
@@ -38,7 +38,7 @@ module Renalware
38
38
  layout: false,
39
39
  locals: local_vars.merge(
40
40
  all_clinics: Renalware::Pathology::Clinic.for_algorithm,
41
- all_consultants: Renalware::Pathology::Consultant.ordered,
41
+ all_consultants: Renalware::Renal::Consultant.ordered,
42
42
  all_templates: Renalware::Pathology::Requests::Request::TEMPLATES
43
43
  )
44
44
  )
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # When executed this job updates rolling statistics for a patient's
4
+ # last past 12 HD sessions.
5
+ # Because this job will be triggered again the next time an HD Sessions is
6
+ # created, it is not crucial to keep each event around - ie they have a short
7
+ # shelf-life. For this reason we only retry 3 times then delete failed jobs.
8
+ module Renalware
9
+ module HD
10
+ UpdateRollingPatientStatisticsDjJob = Struct.new(:patient_id) do
11
+ def perform
12
+ patient = Renalware::HD::Patient.find(patient_id)
13
+ UpdateRollingPatientStatistics.new(patient: patient).call
14
+ end
15
+
16
+ def max_attempts
17
+ 3
18
+ end
19
+
20
+ def queue_name
21
+ "hd_patient_statistics"
22
+ end
23
+
24
+ def priority
25
+ 4
26
+ end
27
+
28
+ def destroy_failed_jobs?
29
+ true
30
+ end
31
+
32
+ # Retry at intervals of 1, 2, 3 hours
33
+ def reschedule_at(current_time, attempts)
34
+ current_time + attempts.hours
35
+ end
36
+ end
37
+ end
38
+ end
@@ -2,12 +2,14 @@
2
2
 
3
3
  # When executed this job updates rolling statistics for a patient's
4
4
  # last past 12 HD sessions.
5
-
5
+ # Because this job will be triggered again the next time an HD Sessions is
6
+ # created, it is not crucial to keep each event around - ie they have a short
7
+ # shelf-life. For this reason we only retry 3 times then delete failed jobs.
6
8
  module Renalware
7
9
  module HD
8
10
  class UpdateRollingPatientStatisticsJob < ApplicationJob
9
11
  queue_as :hd_patient_statistics
10
- queue_with_priority 1
12
+ queue_with_priority 4
11
13
 
12
14
  # :reek:UtilityFunction
13
15
  def perform(patient)
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/letters"
4
+ require "attr_extras"
5
+
6
+ module Renalware
7
+ module Letters
8
+ module Printing
9
+ class BatchPrintJob < ApplicationJob
10
+ include UsingTempFolder
11
+
12
+ # Returns the name of a temp file containing the pdf data
13
+ def perform(batch, user)
14
+ in_a_temporary_folder do |dir|
15
+ Dir.chdir(dir) do
16
+ BatchCompilePdfs.call(batch, user)
17
+ end
18
+ end
19
+ end
20
+
21
+ def max_attempts
22
+ 2
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -6,7 +6,7 @@ module Renalware
6
6
  module PatientsRansackHelper
7
7
  extend ActiveSupport::Concern
8
8
 
9
- UUID_REGEXP = /[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/
9
+ UUID_REGEXP = /[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/.freeze
10
10
 
11
11
  included do
12
12
  class_eval do
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renalware
4
+ module UsingTempFolder
5
+ extend ActiveSupport::Concern
6
+
7
+ def in_a_temporary_folder
8
+ Dir.mktmpdir(nil, Rails.root.join("tmp").to_s) do |dir|
9
+ yield Pathname(dir)
10
+ # temp dir removed here
11
+ end
12
+ end
13
+ end
14
+ end
@@ -5,15 +5,14 @@ require_dependency "renalware/clinics"
5
5
  module Renalware
6
6
  module Clinics
7
7
  class Appointment < ApplicationRecord
8
+ include Accountable
8
9
  belongs_to :patient, touch: true
9
10
  belongs_to :clinic
10
- belongs_to :user
11
+ belongs_to :consultant, class_name: "Renal::Consultant"
11
12
 
12
13
  validates :starts_at, presence: true
13
14
  validates :patient_id, presence: true
14
15
  validates :clinic_id, presence: true
15
- validates :user, presence: true
16
-
17
16
  validates :starts_at, timeliness: { type: :datetime }
18
17
 
19
18
  def starts_on
@@ -13,7 +13,11 @@ module Renalware
13
13
  end
14
14
 
15
15
  def call
16
- search.result.includes(:user, :clinic, patient: [current_modality: [:description]])
16
+ search.result.includes(
17
+ :clinic,
18
+ :consultant,
19
+ patient: [current_modality: [:description]]
20
+ )
17
21
  end
18
22
 
19
23
  def search
@@ -35,7 +35,11 @@ module Renalware
35
35
 
36
36
  if session.save
37
37
  # Might be cleaner if something listened for this event and created this job there?
38
- UpdateRollingPatientStatisticsJob.perform_later(patient) unless session.open?
38
+ # UpdateRollingPatientStatisticsJob.perform_later(patient) unless session.open?
39
+ unless session.open?
40
+ Delayed::Job.enqueue UpdateRollingPatientStatisticsDjJob.new(patient.id)
41
+ end
42
+
39
43
  broadcast(:save_success, session)
40
44
  else
41
45
  session.type = session_type # See method comment
@@ -30,7 +30,7 @@ module Renalware
30
30
  end
31
31
 
32
32
  def rolling_stats_for_this_patient
33
- @patient_rolling_stats ||= begin
33
+ @rolling_stats_for_this_patient ||= begin
34
34
  PatientStatistics.where(patient: patient,
35
35
  rolling: true,
36
36
  year: nil,
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/letters"
4
+
5
+ module Renalware
6
+ module Letters
7
+ class Batch < ApplicationRecord
8
+ include Accountable
9
+ attr_accessor(
10
+ :url,
11
+ :enclosures_present,
12
+ :state_eq,
13
+ :author_id_eq,
14
+ :created_by_id_eq,
15
+ :letterhead_id_eq,
16
+ :page_count_in_array,
17
+ :s
18
+ )
19
+
20
+ enum status: { queued: 0, processing: 10, awaiting_printing: 15, failure: 20, success: 30 }
21
+ has_many(
22
+ :items,
23
+ dependent: :restrict_with_exception,
24
+ class_name: "Renalware::Letters::BatchItem"
25
+ )
26
+ has_many(
27
+ :letters,
28
+ through: :items,
29
+ class_name: "Renalware::Letters::Letter"
30
+ )
31
+
32
+ def percent_complete
33
+ ((items.where(status: :compiled).count.to_f / batch_items_count) * 100).ceil
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/letters"
4
+
5
+ module Renalware
6
+ module Letters
7
+ class BatchItem < ApplicationRecord
8
+ belongs_to :letter
9
+ belongs_to :batch, counter_cache: true
10
+ enum status: { queued: 0, compiled: 10 }
11
+
12
+ def self.policy_class
13
+ BasePolicy
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renalware
4
+ module Letters
5
+ module Lists
6
+ # Form object to help us build and parse the appropriate filters for the
7
+ # letters_lists controller #show action, where we display all/batch_printable letters etc.
8
+ # For example, when displaying batchprintable letters we need to disable the enclosures
9
+ # filter (setting it to No), and restrict the optionsin the letter states dropdown so only
10
+ # Approved or Completed letters can be batch printed.
11
+ # We delegate logic to an instance of a private class which is (or is a subclass of)
12
+ # AllLetters.
13
+ # If we need to add another filter type, create another sub class of AllLetters.
14
+ class Form
15
+ delegate_missing_to :@handler
16
+
17
+ def initialize(named_filter:, params: {})
18
+ @handler = create_filter_specific_object_to_handle_all_requests(named_filter, params)
19
+ end
20
+
21
+ def create_filter_specific_object_to_handle_all_requests(named_filter, params)
22
+ handler_klass = "#{self.class.name}::#{named_filter.to_s.classify}Letters"
23
+ handler_klass.constantize.new(params)
24
+ end
25
+
26
+ class AllLetters
27
+ include ActiveModel::Model
28
+ include Virtus::Model
29
+
30
+ attribute :s, String # sort order, not really part of the form
31
+ attribute :enclosures_present, Boolean
32
+ attribute :state_eq, Integer
33
+ attribute :author_id_eq, Integer
34
+ attribute :created_by_id_eq, Integer
35
+ attribute :letterhead_id_eq, Integer
36
+ attribute :page_count_in_array, Integer
37
+
38
+ def letter_state_options(states = Letters::Letter.states)
39
+ states.map do |state|
40
+ label = I18n.t(state.to_sym, scope: "enums.letter.for_receptionists.state")
41
+ [label, state]
42
+ end
43
+ end
44
+
45
+ def author_options
46
+ @author_options ||= User.author.ordered
47
+ end
48
+
49
+ def typist_options
50
+ @typist_options ||= User.ordered
51
+ end
52
+
53
+ def letterhead_options
54
+ @letterhead_options ||= Letters::Letterhead.ordered
55
+ end
56
+
57
+ def page_count_options
58
+ [["1 or 2", "[1,2]"], ["3 or 4", "[3,4]"], ["5 or 6", "[5,6]"]]
59
+ end
60
+
61
+ def disabled_inputs
62
+ []
63
+ end
64
+
65
+ def allow_blank_inputs
66
+ [:state_eq, :page_count_in_array]
67
+ end
68
+ end
69
+
70
+ class BatchPrintableLetters < AllLetters
71
+ def initialize(params)
72
+ super
73
+ # These are the default values that must match the filters when the page is first
74
+ # loaded, so the right results are displayed
75
+ self.page_count_in_array ||= "[1,2]"
76
+ end
77
+
78
+ def letter_state_options
79
+ super([:approved])
80
+ end
81
+
82
+ def disabled_inputs
83
+ [:enclosures_present, :state_eq]
84
+ end
85
+
86
+ def allow_blank_inputs
87
+ []
88
+ end
89
+
90
+ def enclosures_present
91
+ false
92
+ end
93
+
94
+ def state_eq
95
+ @state_eq || :approved
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -44,7 +44,7 @@ module Renalware
44
44
  end
45
45
 
46
46
  # Note the letter must be a LetterPresenter which has a #to_html method
47
- # The to_html method should (and does on the LetterPrsenter class) render the complete
47
+ # The to_html method should (and does on the LetterPresenter class) render the complete
48
48
  # html including surrounding layout with inline css and images. This way if the
49
49
  # layout changes or the image is changed for example, the cache for the pdf is no longer
50
50
  # valid and a new key and cache entry will be created.
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/letters"
4
+ require "attr_extras"
5
+
6
+ module Renalware
7
+ module Letters
8
+ module Printing
9
+ # - takes a batch of letters
10
+ # - compiles each letter along with its recipient cover sheets
11
+ # - adds blank pages to make sure the letter always starts on an odd page
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
+ class BatchCompilePdfs
17
+ include PdfCombining
18
+ PAGE_COUNTS = %w(2 3 4 5 6 7 8 9 10).freeze
19
+
20
+ def self.call(batch, user)
21
+ new(batch, user).call
22
+ end
23
+
24
+ def initialize(batch, user)
25
+ @batch = batch
26
+ @user = user
27
+ @dir = Pathname(Dir.pwd)
28
+ Rails.logger.info "Compiling letter PDFs for batch #{batch.id} in folder #{dir}"
29
+ end
30
+
31
+ # rubocop:disable Metrics/AbcSize
32
+ def call
33
+ batch.status = :processing
34
+ batch.save_by!(user)
35
+ batch.items.each do |item|
36
+ validate_letter(item.letter)
37
+ assemble_letter_pdfs(item.letter, dir)
38
+ item.update(status: :compiled)
39
+ end
40
+ batch.filepath = append_files
41
+ batch.status = :awaiting_printing
42
+ batch.save_by!(user)
43
+ end
44
+ # rubocop:enable Metrics/AbcSize
45
+
46
+ private
47
+
48
+ attr_reader :batch, :dir, :user
49
+
50
+ def append_files
51
+ folder = Rails.root.join("tmp", "batched_letters")
52
+ FileUtils.mkdir_p folder
53
+ pdf_file = folder.join("#{batch.id}.pdf")
54
+ glob = Dir.glob(dir.join("*.pdf"))
55
+ combine_multiple_pdfs_into_file(filepath: pdf_file, glob: glob) if glob.any?
56
+ Pathname(pdf_file).to_s
57
+ end
58
+
59
+ def page_count_allowing_for_cover_sheet(letter)
60
+ (letter.page_count + 1).to_s
61
+ end
62
+
63
+ def validate_letter(letter)
64
+ raise "Letter has no page_count" if letter.page_count.to_i.zero?
65
+ if letter.page_count.to_i > PAGE_COUNTS.last.to_i
66
+ raise "Letter page count (#{letter.page_count}) unexpected"
67
+ end
68
+ end
69
+
70
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
71
+ # TODO: refactor
72
+ def assemble_letter_pdfs(letter, dir)
73
+ Rails.logger.info "Interleaving address sheets and letters for letter #{letter.id}"
74
+ letter_has_an_odd_number_of_pages = letter.page_count.odd?
75
+ filenames = []
76
+ working_folder = dir.join("letter_#{letter.id}")
77
+ FileUtils.mkdir_p working_folder
78
+
79
+ output_filepath = dir.join("compiled_letter_#{letter.id}.pdf")
80
+
81
+ Dir.chdir(working_folder) do
82
+ letter_filename = write_letter_to_pdf_file(letter)
83
+ letter.recipients.each do |recipient|
84
+ Rails.logger.info " Recipient #{recipient.id}"
85
+ filenames << write_recipient_cover_sheet_pdf(letter, recipient)
86
+ filenames << letter_filename
87
+ filenames << blank_page_filename if letter_has_an_odd_number_of_pages
88
+ end
89
+ combine_multiple_pdfs_using_filenames(filenames, working_folder, output_filepath)
90
+ end
91
+ end
92
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
93
+
94
+ # Remove letter and recipient working files
95
+ def remove_working_files
96
+ FileUtils.rm Dir.glob(dir.join("*.pdf"))
97
+ end
98
+
99
+ def write_letter_to_pdf_file(letter)
100
+ filename = "original_letter_#{letter.id}.pdf"
101
+ File.open(filename, "wb") do |file|
102
+ file.write(PdfRenderer.call(letter))
103
+ end
104
+ filename
105
+ end
106
+
107
+ def write_recipient_cover_sheet_pdf(letter, recipient)
108
+ filename = "letter_#{letter.id}_address_sheet_for_recipient_#{recipient.id}.pdf"
109
+ RecipientAddressPagePdf.new(recipient).render_file filename
110
+ filename
111
+ end
112
+
113
+ def blank_page_filename
114
+ @blank_page_filename ||= begin
115
+ Renalware::Engine.root.join("app", "assets", "pdf", "blank_page.pdf").to_s
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end