renalware-core 2.0.113 → 2.0.115

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 (194) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +0 -2
  3. data/app/assets/javascripts/renalware/application.js.erb +0 -1
  4. data/app/assets/javascripts/renalware/feed_only_inputs.js.erb +26 -0
  5. data/app/assets/javascripts/renalware/feeds.js +50 -0
  6. data/app/assets/javascripts/renalware/person_ajax_search.js +43 -0
  7. data/app/assets/stylesheets/renalware/application.scss +0 -1
  8. data/app/assets/stylesheets/renalware/modules/_admin.scss +7 -0
  9. data/app/assets/stylesheets/renalware/modules/_pathology.scss +1 -1
  10. data/app/assets/stylesheets/renalware/modules/_patients.scss +6 -0
  11. data/app/assets/stylesheets/renalware/modules/_snippets.scss +2 -2
  12. data/app/assets/stylesheets/renalware/partials/_forms.scss +5 -1
  13. data/app/assets/stylesheets/renalware/partials/_layout.scss +4 -0
  14. data/app/{views/renalware/patients/_side_menu.html.slim → components/renalware/patients/side_menu_component.html.slim} +0 -0
  15. data/app/components/renalware/patients/side_menu_component.rb +18 -0
  16. data/app/controllers/renalware/clinical/body_compositions_controller.rb +6 -0
  17. data/app/controllers/renalware/feeds/hl7_test_messages_controller.rb +60 -0
  18. data/app/controllers/renalware/hd/mdm_patients_controller.rb +16 -6
  19. data/app/controllers/renalware/hd/scheduling/diaries_controller.rb +91 -0
  20. data/app/controllers/renalware/hd/scheduling/diary_slots_controller.rb +169 -0
  21. data/app/controllers/renalware/hd/session_forms/batches_controller.rb +1 -1
  22. data/app/controllers/renalware/pathology/observation_requests_controller.rb +23 -4
  23. data/app/controllers/renalware/pathology/requests/requests_controller.rb +0 -4
  24. data/app/controllers/renalware/patients/abridgements_controller.rb +39 -0
  25. data/app/controllers/renalware/pd/regimes_controller.rb +26 -0
  26. data/app/controllers/renalware/problems/problems_controller.rb +2 -2
  27. data/app/helpers/renalware/application_helper.rb +9 -0
  28. data/app/helpers/renalware/article_helper.rb +18 -0
  29. data/app/helpers/renalware/form_helper.rb +3 -2
  30. data/app/helpers/renalware/users_helper.rb +5 -1
  31. data/app/javascript/controllers/clipboard_controller.js +16 -0
  32. data/app/javascript/packs/renalware_core.js +20 -0
  33. data/app/jobs/feed_job.rb +1 -3
  34. data/app/jobs/hl7_message_example.yml +2 -2
  35. data/app/models/renalware/feeds/files/practice_memberships/import_job.rb +3 -0
  36. data/app/models/renalware/feeds/files/primary_care_physicians/import_job.rb +2 -0
  37. data/app/models/renalware/feeds/hl7_message.rb +57 -6
  38. data/app/models/renalware/feeds/hl7_test_form.rb +10 -0
  39. data/app/models/renalware/feeds/hl7_test_message.rb +11 -0
  40. data/app/models/renalware/feeds/message_parser.rb +6 -2
  41. data/app/models/renalware/feeds/message_processor.rb +44 -32
  42. data/app/models/renalware/feeds/persist_message.rb +9 -3
  43. data/app/models/renalware/feeds.rb +2 -0
  44. data/app/models/renalware/hd/scheduling/archive_arguments.rb +19 -0
  45. data/app/models/renalware/hd/scheduling/diary.rb +54 -0
  46. data/app/models/renalware/hd/scheduling/diary_housekeeping_job.rb +84 -0
  47. data/app/models/renalware/hd/scheduling/diary_range.rb +69 -0
  48. data/app/models/renalware/hd/scheduling/diary_slot.rb +88 -0
  49. data/app/models/renalware/hd/scheduling/find_or_create_diary_by_week_query.rb +52 -0
  50. data/app/models/renalware/hd/scheduling/find_or_create_master_diary.rb +27 -0
  51. data/app/models/renalware/hd/scheduling/master_diary.rb +35 -0
  52. data/app/models/renalware/hd/scheduling/weekly_diary.rb +47 -0
  53. data/app/models/renalware/pathology/message_listener.rb +2 -2
  54. data/app/models/renalware/pathology/requests/global_rule/latest_crf_older_than_weeks.rb +38 -0
  55. data/app/models/renalware/pathology/requests/global_rule/patient_is_diabetic.rb +1 -1
  56. data/app/models/renalware/patient.rb +1 -1
  57. data/app/models/renalware/patients/abridgement.rb +18 -0
  58. data/app/models/renalware/patients/abridgement_search_form.rb +12 -0
  59. data/app/models/renalware/patients/ingestion/command.rb +25 -0
  60. data/app/models/renalware/patients/ingestion/command_factory.rb +135 -0
  61. data/app/models/renalware/patients/ingestion/commands/add_or_update_patient.rb +65 -0
  62. data/app/models/renalware/patients/ingestion/message_listener.rb +21 -0
  63. data/app/models/renalware/patients/ingestion/message_mapper.rb +26 -0
  64. data/app/models/renalware/patients/ingestion/message_mappers/patient.rb +73 -0
  65. data/app/models/renalware/patients/ingestion/update_master_patient_index.rb +60 -0
  66. data/app/models/renalware/patients/ingestion/update_master_patient_index_job.rb.dead +20 -0
  67. data/app/models/renalware/patients.rb +4 -3
  68. data/app/models/renalware/problems/problem.rb +6 -2
  69. data/app/models/renalware/ukrdc/incoming/paths.rb +6 -7
  70. data/app/policies/renalware/admin/devops_policy.rb +18 -0
  71. data/app/policies/renalware/hd/{diary_policy.rb → scheduling/diary_policy.rb} +3 -1
  72. data/app/presenters/renalware/clinical/profile_presenter.rb +2 -1
  73. data/app/presenters/renalware/directory/person_auto_complete_presenter.rb +1 -1
  74. data/app/presenters/renalware/hd/scheduling/diary_presenter.rb +84 -0
  75. data/app/presenters/renalware/hd/scheduling/diary_slot_presenter.rb +74 -0
  76. data/app/presenters/renalware/hd/scheduling/null_slot.rb +42 -0
  77. data/app/presenters/renalware/pd/dashboard_presenter.rb +3 -2
  78. data/app/views/renalware/accesses/procedures/_form.html.slim +5 -1
  79. data/app/views/renalware/accesses/procedures/_list.html.slim +11 -4
  80. data/app/views/renalware/accesses/procedures/show.html.slim +17 -19
  81. data/app/views/renalware/addresses/_form.html.slim +17 -12
  82. data/app/views/renalware/admin/cache/show.html.slim +24 -0
  83. data/app/views/renalware/clinical/body_compositions/_list.html.slim +7 -43
  84. data/app/views/renalware/clinical/body_compositions/_table.html.slim +42 -0
  85. data/app/views/renalware/clinical/body_compositions/index.html.slim +9 -0
  86. data/app/views/renalware/clinical/dry_weights/_list.html.slim +2 -8
  87. data/app/views/renalware/clinical/dry_weights/_table.html.slim +7 -0
  88. data/app/views/renalware/clinical/profiles/show.html.slim +4 -2
  89. data/app/views/renalware/feeds/hl7_test_messages/create.js.erb +10 -0
  90. data/app/views/renalware/feeds/hl7_test_messages/new.slim +18 -0
  91. data/app/views/renalware/hd/mdm_patients/_filters.html.slim +0 -11
  92. data/app/views/renalware/hd/mdm_patients/_page_actions.html.slim +29 -0
  93. data/app/views/renalware/hd/mdm_patients/index.html.slim +7 -4
  94. data/app/views/renalware/hd/{diaries → scheduling/diaries}/_page_actions.html.slim +4 -4
  95. data/app/views/renalware/hd/{diaries → scheduling/diaries}/_table.html.slim +0 -0
  96. data/app/views/renalware/hd/{diaries → scheduling/diaries}/_weekly_diary.html.slim +1 -1
  97. data/app/views/renalware/hd/{diaries → scheduling/diaries}/edit.html.slim +6 -6
  98. data/app/views/renalware/hd/{diaries → scheduling/diaries}/index.html.slim +0 -0
  99. data/app/views/renalware/hd/{diaries → scheduling/diaries}/show.pdf.slim +0 -0
  100. data/app/views/renalware/hd/{diary_slots → scheduling/diary_slots}/_form.html.slim +15 -6
  101. data/app/views/renalware/hd/{diary_slots → scheduling/diary_slots}/_slot.html.slim +2 -2
  102. data/app/views/renalware/hd/{diary_slots → scheduling/diary_slots}/_tab.html.slim +1 -1
  103. data/app/views/renalware/hd/{diary_slots → scheduling/diary_slots}/create.js.erb +1 -1
  104. data/app/views/renalware/hd/{diary_slots → scheduling/diary_slots}/destroy.js.erb +1 -1
  105. data/app/views/renalware/hd/{diary_slots → scheduling/diary_slots}/edit.html.slim +2 -2
  106. data/app/views/renalware/hd/{diary_slots → scheduling/diary_slots}/new.html.slim +0 -0
  107. data/app/views/renalware/hd/{diary_slots → scheduling/diary_slots}/new.js.erb +0 -0
  108. data/app/views/renalware/hd/{diary_slots → scheduling/diary_slots}/show.js.erb +0 -0
  109. data/app/views/renalware/hd/{diary_slots → scheduling/diary_slots}/update.js.erb +1 -1
  110. data/app/views/renalware/hospitals/units/index.html.slim +2 -2
  111. data/app/views/renalware/layouts/_patient.html.slim +2 -1
  112. data/app/views/renalware/letters/contacts/_form.html.slim +10 -13
  113. data/app/views/renalware/letters/letters/_form.html.slim +2 -9
  114. data/app/views/renalware/navigation/_developer.html.slim +4 -0
  115. data/app/views/renalware/navigation/_menu.html.slim +2 -0
  116. data/app/views/renalware/pathology/observation_requests/_filters.html.slim +17 -0
  117. data/app/views/renalware/pathology/observation_requests/index.html.slim +2 -1
  118. data/app/views/renalware/patients/_layout.html.slim +1 -1
  119. data/app/views/renalware/patients/_side_menu.html.slim.dead +7 -0
  120. data/app/views/renalware/patients/abridgements/_abridgement.html.slim +8 -0
  121. data/app/views/renalware/patients/abridgements/_table.html.slim +12 -0
  122. data/app/views/renalware/patients/abridgements/index.html.slim +22 -0
  123. data/app/views/renalware/patients/alerts/create.json +3 -0
  124. data/app/views/renalware/patients/patients/_form.html.slim +36 -17
  125. data/app/views/renalware/patients/patients/edit.html.slim +1 -1
  126. data/app/views/renalware/patients/patients/new.html.slim +1 -1
  127. data/app/views/renalware/patients/side_menu/_actions.html.slim +2 -0
  128. data/app/views/renalware/pd/_apd_regimes.html.slim +1 -1
  129. data/app/views/renalware/pd/_capd_regimes.html.slim +1 -1
  130. data/app/views/renalware/pd/dashboards/show/_apd_regimes.html.slim +11 -2
  131. data/app/views/renalware/pd/dashboards/show/_capd_regimes.html.slim +11 -2
  132. data/app/views/renalware/pd/regimes/index.html.slim +7 -0
  133. data/app/views/renalware/renal/profiles/_form.html.slim +7 -24
  134. data/app/views/renalware/shared/documents/_binary_marker_input.html.slim +2 -2
  135. data/app/views/renalware/shared/documents/_blood_group_input.html.slim +4 -2
  136. data/app/views/renalware/transplants/registrations/_form.html.slim +22 -17
  137. data/config/locales/renalware/clinical/body_composition.yml +6 -3
  138. data/config/locales/renalware/clinical/dry_weight.en.yml +4 -3
  139. data/config/locales/renalware/hd/diary_slots.en.yml +3 -2
  140. data/config/locales/renalware/letters/contact.en.yml +0 -2
  141. data/config/routes/feeds.rb +5 -0
  142. data/config/routes/hd.rb +11 -7
  143. data/config/routes/letters.rb +0 -5
  144. data/config/routes/patients.rb +1 -0
  145. data/config/routes/pd.rb +2 -2
  146. data/config/routes/renal.rb +0 -5
  147. data/config/routes.rb +1 -0
  148. data/config/webpack/development.js +5 -0
  149. data/config/webpack/environment.js +3 -0
  150. data/config/webpack/production.js +5 -0
  151. data/config/webpack/test.js +5 -0
  152. data/config/webpacker.yml +103 -0
  153. data/db/functions/hd_diary_archive_elapsed_master_slots_v01.sql +40 -0
  154. data/db/migrate/20190322120025_create_feed_hl7_test_messages.rb +12 -0
  155. data/db/migrate/20190325134823_create_patients_master_index.rb +24 -0
  156. data/db/migrate/20190327100851_add_handled_to_feed_messages.rb +5 -0
  157. data/db/migrate/20191012121433_add_consultant_to_users.rb +7 -0
  158. data/db/migrate/20191018143635_create_hd_diary_matrix_view.rb +7 -0
  159. data/db/migrate/20191018144917_create_fn_to_archive_master_slots.rb +9 -0
  160. data/db/views/hd_diary_matrix_v01.sql +38 -0
  161. data/lib/core_extensions/active_record/sort.rb +27 -9
  162. data/lib/renalware/configuration.rb +3 -0
  163. data/lib/renalware/engine.rb +31 -0
  164. data/lib/renalware/version.rb +1 -1
  165. data/lib/renalware/week_period.rb +8 -7
  166. data/lib/renalware.rb +13 -1
  167. data/lib/tasks/hd.rake +7 -0
  168. data/lib/tasks/pathology.rake +1 -1
  169. data/lib/tasks/renalware.rake +89 -4
  170. data/spec/factories/hd/scheduling/diaries.rb +18 -0
  171. data/spec/factories/hd/{slots.rb → scheduling/slots.rb} +1 -1
  172. data/spec/factories/patients/abridgements.rb +9 -0
  173. data/spec/support/hl7_helpers.rb +13 -0
  174. data/spec/support/pages/letters/form.rb +4 -1
  175. metadata +126 -50
  176. data/app/assets/javascripts/renalware/auto_complete.js +0 -63
  177. data/app/controllers/renalware/hd/diaries_controller.rb +0 -89
  178. data/app/controllers/renalware/hd/diary_slots_controller.rb +0 -175
  179. data/app/controllers/renalware/letters/descriptions_controller.rb +0 -22
  180. data/app/controllers/renalware/renal/prd_descriptions_controller.rb +0 -15
  181. data/app/models/renalware/hd/archive_yesterdays_slots_job.rb +0 -69
  182. data/app/models/renalware/hd/diary.rb +0 -41
  183. data/app/models/renalware/hd/diary_slot.rb +0 -83
  184. data/app/models/renalware/hd/find_or_create_diary_by_week_query.rb +0 -50
  185. data/app/models/renalware/hd/find_or_create_master_diary.rb +0 -26
  186. data/app/models/renalware/hd/master_diary.rb +0 -32
  187. data/app/models/renalware/hd/weekly_diary.rb +0 -45
  188. data/app/models/renalware/renal/prd_descriptions/search_query.rb +0 -35
  189. data/app/presenters/renalware/hd/diary_presenter.rb +0 -79
  190. data/app/presenters/renalware/hd/diary_slot_presenter.rb +0 -72
  191. data/app/presenters/renalware/hd/null_slot.rb +0 -40
  192. data/app/views/renalware/renal/prd_descriptions/search.json.jbuilder +0 -6
  193. data/lib/test_support/autocomplete_helpers.rb +0 -14
  194. data/spec/factories/hd/diaries.rb +0 -14
@@ -10,8 +10,12 @@ module Renalware
10
10
 
11
11
  def index
12
12
  observation_requests = find_observation_requests
13
-
14
- render locals: { observation_requests: observation_requests, patient: @patient }
13
+ render locals: {
14
+ observation_requests: observation_requests.result .page(page).per(per_page),
15
+ search: observation_requests,
16
+ obr_filter_options: obr_filter_options,
17
+ patient: @patient
18
+ }
15
19
  end
16
20
 
17
21
  def show
@@ -22,16 +26,31 @@ module Renalware
22
26
 
23
27
  private
24
28
 
29
+ # Select just the OBR description ids and codes that have been associated with this patient.
30
+ # We'll uses them to build a filter dropdown list.
31
+ def obr_filter_options
32
+ @patient.observation_requests
33
+ .joins(:description)
34
+ .order("pathology_request_descriptions.code asc")
35
+ .pluck(
36
+ Arel.sql(
37
+ "distinct on(pathology_request_descriptions.code) pathology_request_descriptions.code"
38
+ ),
39
+ "pathology_request_descriptions.id",
40
+ "pathology_request_descriptions.name"
41
+ )
42
+ end
43
+
25
44
  def find_observation_requests
26
45
  @patient.observation_requests
27
- .page(page)
28
46
  .includes(:description)
29
47
  .ordered
48
+ .ransack(params[:q])
30
49
  end
31
50
 
32
51
  def find_observation_request
33
52
  @patient.observation_requests
34
- .includes(:description, observations: :description)
53
+ .includes(observations: :description)
35
54
  .find(params[:id])
36
55
  end
37
56
  end
@@ -104,10 +104,6 @@ module Renalware
104
104
  Pathology::OrderedPatientQuery
105
105
  .new(patient_ids)
106
106
  .call
107
- .eager_load(:requests)
108
- .eager_load(:rules)
109
- .eager_load(:prescriptions)
110
- .eager_load(:drugs)
111
107
  authorize Renalware::Patient
112
108
  end
113
109
 
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/patients"
4
+
5
+ module Renalware
6
+ module Patients
7
+ class AbridgementsController < BaseController
8
+ def index
9
+ authorize Abridgement, :index?
10
+ render locals: {
11
+ form: AbridgementSearchForm.new,
12
+ results: abridgements_matching_search_criteria,
13
+ results_matching_dob: abridgements_matching_dobs
14
+ }
15
+ end
16
+
17
+ private
18
+
19
+ def abridgements_matching_search_criteria
20
+ return [] if search_params.blank?
21
+
22
+ Abridgement.where(hospital_number: search_params)
23
+ end
24
+
25
+ def abridgements_matching_dobs
26
+ return [] if abridgements_matching_search_criteria.empty?
27
+
28
+ dobs = abridgements_matching_search_criteria.map(&:born_on).uniq.compact
29
+ return [] if dobs.empty?
30
+
31
+ Abridgement.where(born_on: dobs) - abridgements_matching_search_criteria
32
+ end
33
+
34
+ def search_params
35
+ params.dig(:search, :criteria)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -5,8 +5,26 @@ require_dependency "renalware/hd/base_controller"
5
5
  module Renalware
6
6
  module PD
7
7
  class RegimesController < BaseController
8
+ include Renalware::Concerns::Pageable
8
9
  before_action :load_patient
9
10
 
11
+ def index
12
+ regimes = regime_type_class.for_patient(patient).with_bags.ordered.page(page).per(per_page)
13
+ render locals: {
14
+ patient: patient,
15
+ regimes: regimes,
16
+ pd_type_string: pd_type_string
17
+ }
18
+ end
19
+
20
+ def capd_regimes
21
+ @capd_regimes ||= CAPDRegime.for_patient(patient).with_bags.ordered.page(1).per(5)
22
+ end
23
+
24
+ def apd_regimes
25
+ @apd_regimes ||= APDRegime.for_patient(patient).with_bags.ordered.page(1).per(5)
26
+ end
27
+
10
28
  def new
11
29
  regime = cloned_last_known_regime_of_type || patient.pd_regimes.new(type: regime_type)
12
30
 
@@ -70,6 +88,14 @@ module Renalware
70
88
  params[:type] ? "Renalware::#{params[:type]}" : nil
71
89
  end
72
90
 
91
+ def regime_type_class
92
+ @regime_type_class ||= regime_type.constantize
93
+ end
94
+
95
+ def pd_type_string
96
+ regime_type_class.new.pd_type.upcase # CAPD or APD
97
+ end
98
+
73
99
  def cloned_last_known_regime_of_type
74
100
  regime = patient.pd_regimes
75
101
  .order(start_date: :desc, created_at: :desc)
@@ -4,7 +4,7 @@ module Renalware
4
4
  module Problems
5
5
  class ProblemsController < BaseController
6
6
  def index
7
- problems = patient.problems
7
+ problems = patient.problems.with_notes
8
8
  authorize problems
9
9
  render locals: {
10
10
  patient: patient,
@@ -14,7 +14,7 @@ module Renalware
14
14
  end
15
15
 
16
16
  def show
17
- problem = patient.problems.with_archived.includes(versions: :item).find(params[:id])
17
+ problem = patient.problems.with_archived.with_versions.find(params[:id])
18
18
  notes = problem.notes.with_updated_by.ordered
19
19
  authorize problem
20
20
  render locals: {
@@ -3,10 +3,17 @@
3
3
  require "inline_image"
4
4
  require "git_commit_sha"
5
5
  require "breadcrumb"
6
+ require "webpacker/helper"
6
7
 
7
8
  module Renalware
8
9
  module ApplicationHelper
9
10
  include Renalware::Engine.routes.url_helpers
11
+ include ::Webpacker::Helper
12
+
13
+ # See https://github.com/rails/webpacker/blob/master/docs/engines.md
14
+ def current_webpacker_instance
15
+ Renalware.webpacker
16
+ end
10
17
 
11
18
  def default_patient_link(patient)
12
19
  link_to(patient.to_s(:default), patient_clinical_summary_path(patient))
@@ -37,9 +44,11 @@ module Renalware
37
44
  end
38
45
 
39
46
  # For use in pages
47
+ # rubocop:disable Rails/OutputSafety
40
48
  def page_heading(title)
41
49
  content_for(:page_title) { title.html_safe }
42
50
  end
51
+ # rubocop:enable Rails/OutputSafety
43
52
 
44
53
  def t?(key)
45
54
  t(key, cascade: false, raise: false, default: "").present?
@@ -21,5 +21,23 @@ module Renalware
21
21
  output.concat(capture(&block)) if block_given?
22
22
  output.safe_concat("</article>")
23
23
  end
24
+
25
+ # Renders
26
+ # <span>5 of 16<div>
27
+ # if the collection has been paginated, otherwise
28
+ # <span>5<div>
29
+ def collection_count(collection)
30
+ return unless collection&.respond_to?(:length)
31
+
32
+ parts = ["("]
33
+ parts.append(collection.length)
34
+ if collection.respond_to?(:total_count)
35
+ if collection.total_count > collection.length
36
+ parts.append(" of #{collection.total_count}")
37
+ end
38
+ end
39
+ parts.append(")")
40
+ content_tag("span", parts.join(""))
41
+ end
24
42
  end
25
43
  end
@@ -6,13 +6,14 @@ module Renalware
6
6
  " field_with_errors" if model.errors.key?(attr)
7
7
  end
8
8
 
9
- def render_input(builder, attribute)
9
+ def render_input(builder, attribute, html_options: {})
10
10
  renderable = builder.object.public_send(attribute)
11
11
  return unless renderable
12
12
 
13
13
  render input_partial_path_for(renderable),
14
14
  attribute: attribute,
15
- f: builder
15
+ f: builder,
16
+ html_options: html_options
16
17
  end
17
18
 
18
19
  def input_partial_path_for(renderable)
@@ -2,8 +2,12 @@
2
2
 
3
3
  module Renalware
4
4
  module UsersHelper
5
+ def current_user_is_developer?
6
+ current_user.has_role?(:devops)
7
+ end
8
+
5
9
  def current_user_is_super_admin?
6
- current_user.has_role?(:super_admin) || current_user.has_role?(:devops)
10
+ current_user.has_role?(:super_admin) || current_user_is_developer?
7
11
  end
8
12
 
9
13
  def current_user_is_admin?
@@ -0,0 +1,16 @@
1
+ import { Controller } from "stimulus"
2
+
3
+ // Experimental Stimulus clipboard controller. No IE11 support.
4
+ // Can be enhanced but initially this exists just so we can test stimulus/webpack integration
5
+ // when the engine is pulled into a host app
6
+ export default class extends Controller {
7
+ static targets = ["source", "result"]
8
+
9
+ copy(event) {
10
+ event.preventDefault()
11
+ this.sourceTarget.select()
12
+ document.execCommand("copy")
13
+ console.log("Copied")
14
+ this.resultTarget.innerHTML = "Copied"
15
+ }
16
+ }
@@ -0,0 +1,20 @@
1
+ // This file is automatically compiled by Webpack, along with any other files
2
+ // present in this directory. You're encouraged to place your actual application logic in
3
+ // a relevant structure within app/javascript and only use these pack files to reference
4
+ // that code so it'll be compiled.
5
+ // Uncomment to copy all static images under ../images to the output folder and reference
6
+ // them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
7
+ // or the `imagePath` JavaScript helper below.
8
+ //
9
+ // const images = require.context('../images', true)
10
+ // const imagePath = (name) => images(name, true)
11
+
12
+ // require("@rails/ujs").start()
13
+ // require("@rails/activestorage").start()
14
+
15
+ import { Application } from "stimulus"
16
+ import { definitionsFromContext } from "stimulus/webpack-helpers"
17
+
18
+ const application = Application.start()
19
+ const context = require.context("../controllers", true, /\.js$/)
20
+ application.load(definitionsFromContext(context))
data/app/jobs/feed_job.rb CHANGED
@@ -13,9 +13,7 @@
13
13
  #
14
14
  FeedJob = Struct.new(:raw_message) do
15
15
  def perform
16
- Renalware::Feeds
17
- .message_processor
18
- .call(raw_message)
16
+ Renalware::Feeds.message_processor.call(raw_message)
19
17
  end
20
18
 
21
19
  def max_attempts
@@ -1,10 +1,10 @@
1
1
  --- !ruby/struct:FeedJob
2
2
  raw_message: |
3
3
  MSH|^~\&|HM|LBE|SCM||20091112164645||ORU^R01|1258271|P|2.3.1|||AL||||
4
- PID|||Z999990^^^PAS Number||RABBIT^JESSICA^^^MS||19880924|F|||18 RABBITHOLE ROAD^LONDON^^^SE8 8JR|||||||||||||||||||
4
+ PID|||Z100002^^^PAS Number||RABBIT^JESSICA^^^MS||19880924|F|||18 RABBITHOLE ROAD^LONDON^^^SE8 8JR|||||||||||||||||||
5
5
  PV1||Inpatient|NIBC^^^^^^^^|||||MID^KINGS MIDWIVES||||||||||NHS|HXF888888^^^Visit Number|||||||||
6
6
  ORC|RE|^PCS|09B0099478^LA||CM||||200911111841|||MID^KINGS MIDWIVES|||||||
7
- OBR|1|^PCS|09B0099478^LA|FBC^FULL BLOOD COUNT^MB||200911111841|200911111841|||||||200911111841|B^Blood|MID^KINGS MIDWIVES||09B0099478||||200911121646||HM|F||||||||||||||||||
7
+ OBR|1|PlacerOrderNumber1^PCS|09B0099478^LA|FBC^FULL BLOOD COUNT^MB||200911111841|200911111841|||||||200911111841|B^Blood|MID^KINGS MIDWIVES||09B0099478||||200911121646||HM|F||||||||||||||||||
8
8
  OBX|1|TX|WBC^WBC^MB||6.09||||||F|||200911112026||BBKA^Donald DUCK|
9
9
  OBX|2|TX|RBC^RBC^MB||4.00||||||F|||200911112026||BBKA^Donald DUCK|
10
10
  OBX|3|TX|HGB^Hb^MB||11.8||||||F|||200911112026||BBKA^Donald DUCK|
@@ -11,6 +11,8 @@ module Renalware
11
11
  include Feeds::Job
12
12
  FILE_TO_EXTRACT_FROM_ARCHIVE = /epracmem.csv/.freeze
13
13
 
14
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
15
+ # TODO: refactor
14
16
  def perform(file)
15
17
  logging_to_stringio(strio = StringIO.new)
16
18
  log "Before upload there are #{practice_membership_count} practice memberships"
@@ -25,6 +27,7 @@ module Renalware
25
27
  ensure
26
28
  file.update!(status: status, result: strio.string, time_taken: elapsed_ms)
27
29
  end
30
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
28
31
 
29
32
  private
30
33
 
@@ -12,6 +12,7 @@ module Renalware
12
12
 
13
13
  FILE_TO_EXTRACT_FROM_ARCHIVE = /^egpcur.csv$/.freeze
14
14
 
15
+ # rubocop:disable Metrics/AbcSize
15
16
  def perform(file)
16
17
  logging_to_stringio(strio = StringIO.new)
17
18
  log "PrimaryCarePhysician count before update: #{primary_care_physician_count}"
@@ -26,6 +27,7 @@ module Renalware
26
27
  ensure
27
28
  file.update!(status: status, result: strio.string, time_taken: elapsed_ms)
28
29
  end
30
+ # rubocop:enable Metrics/AbcSize
29
31
 
30
32
  private
31
33
 
@@ -11,10 +11,19 @@ module Renalware
11
11
  # HL7Message.new(raw_message).patient_identification.internal_id
12
12
  #
13
13
  class HL7Message < SimpleDelegator
14
- def initialize(message_string)
15
- @message_string = message_string
16
- super(::HL7::Message.new(message_string.lines))
17
- end
14
+ ACTIONS = {
15
+ "ADT^A28" => :add_person_information,
16
+ "ADT^A31" => :update_person_information,
17
+ "ADT^A08" => :update_admission,
18
+ "ADT^A01" => :admit_patient,
19
+ "ADT^A02" => :transfer_patient,
20
+ "ADT^A03" => :discharge_patient,
21
+ "ADT^A11" => :cancel_admission,
22
+ "MFN^M02" => :add_consultant,
23
+ "ADT^A34" => :merge_patient,
24
+ "ADT^A13" => :cancel_discharge,
25
+ "ORU^R01" => :add_pathology_observations
26
+ }.freeze
18
27
 
19
28
  class ObservationRequest < SimpleDelegator
20
29
  alias_attribute :date_time, :observation_date
@@ -117,6 +126,8 @@ module Renalware
117
126
  alias_attribute :external_id, :patient_id
118
127
  alias_attribute :sex, :admin_sex
119
128
  alias_attribute :dob, :patient_dob
129
+ alias_attribute :born_on, :patient_dob
130
+ alias_attribute :died_at, :death_date
120
131
 
121
132
  def internal_id
122
133
  patient_id_list.split("^").first
@@ -134,6 +145,18 @@ module Renalware
134
145
  patient_name[1]
135
146
  end
136
147
 
148
+ def suffix
149
+ patient_name[3]
150
+ end
151
+
152
+ def title
153
+ patient_name[4]
154
+ end
155
+
156
+ def address
157
+ super.split("^")
158
+ end
159
+
137
160
  private
138
161
 
139
162
  def patient_name
@@ -153,8 +176,36 @@ module Renalware
153
176
  self[:MSH].message_control_id
154
177
  end
155
178
 
156
- def to_s
157
- @message_string.tr("\r", "\n")
179
+ # Adding this so it is part of the interface and we can mock an HL7Message in tests
180
+ def to_hl7
181
+ super
182
+ end
183
+
184
+ def message_type
185
+ type.split("^").first
186
+ end
187
+
188
+ def event_type
189
+ parts = type.split("^")
190
+ parts.length == 2 && parts.last
191
+ end
192
+
193
+ %i(ORU ADT).each do |msg_type|
194
+ define_method(:"#{msg_type.to_s.downcase}?") do
195
+ msg_type.to_s == message_type
196
+ end
197
+ end
198
+
199
+ def action
200
+ ACTIONS.fetch(type)
201
+ end
202
+
203
+ def practice_code
204
+ self[:PD1].e3.split("^")[2] if self[:PD1]
205
+ end
206
+
207
+ def gp_code
208
+ self[:PD1].e4.split("^")[0] if self[:PD1]
158
209
  end
159
210
  end
160
211
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renalware
4
+ module Feeds
5
+ class HL7TestForm
6
+ include ActiveModel::Model
7
+ attr_accessor :body
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/feeds"
4
+
5
+ module Renalware
6
+ module Feeds
7
+ # Test messages stored in the databse that we can run to check they work
8
+ class HL7TestMessage < ApplicationRecord
9
+ end
10
+ end
11
+ end
@@ -8,9 +8,13 @@ module Renalware
8
8
  # message object.
9
9
  #
10
10
  class MessageParser
11
+ def self.parse(*args)
12
+ new.parse(*args)
13
+ end
14
+
11
15
  def parse(message_string)
12
- lines = message_string.split("\n").join("\r")
13
- HL7Message.new(lines)
16
+ lines = message_string.split("\n").join("\r").lines
17
+ HL7Message.new(::HL7::Message.new(lines))
14
18
  end
15
19
  end
16
20
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_dependency "renalware/feeds"
4
+ require "attr_extras"
4
5
 
5
6
  module Renalware
6
7
  module Feeds
@@ -9,32 +10,48 @@ module Renalware
9
10
  #
10
11
  class MessageProcessor
11
12
  include Broadcasting
13
+ attr_reader :raw_message, :hl7_message, :feed_message
12
14
 
13
- # For SubscriptionRegistry only, may not be needed as also inc in Broadcasting module
14
- include Wisper::Publisher
15
-
15
+ # We want to wrap message processing in a transaction because if message processing
16
+ # fails we don't want to leave an unprocessed message in the feed_messages table.
17
+ # If we did, and the same FeedJob retires a few minutes later, if will try to save to
18
+ # feed_messages with the same MD5 body_hash (the message is identical to one already saved)
19
+ # resulting in unique key violation.
20
+ # Using a transaction here prevents any orphaned records if there is an error.
21
+ # However we should be aware that any listeners raising an error will prevent successful
22
+ # in all other listeners. So a listener should be careful to catch errors and not re-raise
23
+ # them, or use the :message_processed message (lower down) which is safer.
16
24
  def call(raw_message)
17
- feed_message = nil
25
+ @raw_message = raw_message
18
26
 
19
- # We want to wrap message processing in a transaction because if message processing
20
- # fails we don't want to leave an unprocessed message in the feed_messages table.
21
- # If we did, and the same FeedJob retires a few minutes later, if will try to save to
22
- # feed_messages with the same MD5 body_hash (the message is identical to one already saved)
23
- # resulting in unique key violation.
24
- # Using a transaction here prevents any orphaned records if there is an error.
25
- # However we should be aware that any listeners raising an error will prevent successful
26
- # in all other listeners. So a listener should be careful to catch errors and not re-raise
27
- # them, or use the :message_processed message (lower down) which is safer.
27
+ parse_raw_message_into_hl7_object
28
28
  ActiveRecord::Base.transaction do
29
- hl7_message = build_hl7_object_from(raw_message)
30
- feed_message = persist_message(hl7_message)
31
- broadcast(
32
- :message_arrived,
33
- hl7_message: hl7_message,
34
- feed_message: feed_message
35
- )
29
+ create_feed_message_using_raw_message_and_basic_extracted_patient_data
30
+ allow_listeners_to_process_the_message
36
31
  end
37
32
 
33
+ allow_listeners_to_post_process_the_message
34
+ rescue StandardError => exception
35
+ notify_exception(exception)
36
+ raise exception
37
+ end
38
+
39
+ private
40
+
41
+ def notify_exception(exception)
42
+ Engine.exception_notifier.notify(exception)
43
+ end
44
+
45
+ def allow_listeners_to_process_the_message
46
+ message_to_broadcast = "#{hl7_message.message_type.downcase}_message_arrived"
47
+ broadcast(
48
+ message_to_broadcast.to_sym,
49
+ hl7_message: hl7_message,
50
+ feed_message: feed_message
51
+ )
52
+ end
53
+
54
+ def allow_listeners_to_post_process_the_message
38
55
  # Another event, this one letting anyone interested know that a message been successfully
39
56
  # processed. They might want to forward the message on somewhere else for instance.
40
57
  # Think Diaverum.
@@ -44,8 +61,9 @@ module Renalware
44
61
  # e.g. forwarding, logging etc.
45
62
  # Its is recommended here to use an async listener - see example in renalware-diaverum
46
63
  # - so that any error in the listener has its own try mechansim and does not cause the
47
- # current job to retry,
48
- broadcast(:message_processed, feed_message: feed_message)
64
+ # current job to retry.
65
+ message_to_broadcast = "#{hl7_message.message_type.downcase}_message_processed"
66
+ broadcast(message_to_broadcast.to_sym, feed_message: feed_message)
49
67
  rescue Feeds::DuplicateMessageReceivedError => e
50
68
  Rails.logger.warn("Rejected duplicate HL7 message: #{e.message}")
51
69
  rescue StandardError => e
@@ -53,10 +71,8 @@ module Renalware
53
71
  raise e
54
72
  end
55
73
 
56
- private
57
-
58
- def build_hl7_object_from(raw_message)
59
- MessageParser.new.parse(raw_message)
74
+ def parse_raw_message_into_hl7_object
75
+ @hl7_message = MessageParser.parse(raw_message)
60
76
  end
61
77
 
62
78
  # If the incoming message has already been processed we should not be processing it again.
@@ -64,12 +80,8 @@ module Renalware
64
80
  # same message payload is saved twice - in this case we exit #call early, the broadcast
65
81
  # is not issued and therefore the message is not processed. The message will go back into
66
82
  # the delayed_job queue and retry, failing until it finally gives up!
67
- def persist_message(hl7_message)
68
- PersistMessage.new.call(hl7_message)
69
- end
70
-
71
- def notify_exception(exception)
72
- Engine.exception_notifier.notify(exception)
83
+ def create_feed_message_using_raw_message_and_basic_extracted_patient_data
84
+ @feed_message = PersistMessage.new.call(hl7_message)
73
85
  end
74
86
  end
75
87
  end
@@ -8,21 +8,27 @@ module Renalware
8
8
  # hl7_message is an HL7Message (a decorator around ::HL7::Message)
9
9
  # If the same message is persisted twice we'll get an ActiveRecord::RecordNotUnique error
10
10
  # but that's fine as we don't want to process the same HL7 message twice.
11
+ # rubocop:disable Metrics/MethodLength
11
12
  def call(hl7_message)
13
+ body_hash = Digest::MD5.hexdigest(hl7_message.to_hl7)
12
14
  Message.create!(
13
15
  event_code: hl7_message.type,
14
16
  header_id: hl7_message.header_id,
15
17
  body: hl7_message.to_s,
16
- body_hash: Digest::MD5.hexdigest(hl7_message.to_s),
18
+ body_hash: body_hash,
17
19
  patient_identifier: hl7_message.patient_identification&.internal_id
18
20
  )
19
- rescue ActiveRecord::RecordNotUnique => e
21
+ rescue ActiveRecord::RecordNotUnique
20
22
  # If a duplicate messages comes in (we have calculated the body_hash for the message and it
21
23
  # turns out that body_hash is not unique in the database, meaning the message is already
22
24
  # stored) then raise a custom error so it can be handled upstream - ie we can choose to
23
25
  # ignore it.
24
- raise Feeds::DuplicateMessageReceivedError, e.message
26
+ raise(
27
+ DuplicateMessageError,
28
+ "header_id=#{hl7_message.header_id}, body_hash=#{body_hash}"
29
+ )
25
30
  end
31
+ # rubocop:enable Metrics/MethodLength
26
32
  end
27
33
  end
28
34
  end
@@ -13,6 +13,8 @@ module Renalware
13
13
  "feed_"
14
14
  end
15
15
 
16
+ class DuplicateMessageError < StandardError; end
17
+
16
18
  def message_processor
17
19
  @message_processor ||= build_message_processor
18
20
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "renalware/hd"
4
+
5
+ module Renalware
6
+ module HD
7
+ module Scheduling
8
+ class ArchiveArguments
9
+ attr_reader :from_week_period, :to_week_period, :up_until_date
10
+
11
+ def initialize(from: nil, to: nil)
12
+ @from_week_period = WeekPeriod.from_date(from || 1.year.ago)
13
+ @up_until_date = (to || Time.zone.now - 1.day).to_date
14
+ @to_week_period = WeekPeriod.from_date(@up_until_date)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end