renalware-core 2.0.113 → 2.0.115

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