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
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/hd"
4
+
5
+ # A Diary is the schedule of HD Station/Patients assignments for one week for a particular unit.
6
+ # A Diary is split into Periods (eg. am pm eve), and each Period has a matrix of slots
7
+ # Imagine Days eg 1 to 6 on the X axis and Stations on the Y axis, and into each 'slot' we
8
+ # put a patient.
9
+ # A patient can only be in one slot in any period.
10
+ #
11
+ # Do not create instances of this class explicity, always use the STI sub classes eg MasterDiary.
12
+ module Renalware
13
+ module HD
14
+ module Scheduling
15
+ class Diary < ApplicationRecord
16
+ include Accountable
17
+ self.table_name = :hd_diaries
18
+ has_many :slots, class_name: "DiarySlot", dependent: :restrict_with_exception
19
+ belongs_to :hospital_unit, class_name: "Hospitals::Unit"
20
+ has_many :patients,
21
+ through: :slots, class_name: "Renalware::HD::Patient",
22
+ dependent: :restrict_with_exception
23
+ validates :hospital_unit_id, presence: true
24
+ validates :master, inclusion: { in: [true, false], allow_nil: true }
25
+ composed_of :week,
26
+ mapping: [%w(week_number week_number), %w(year year)],
27
+ constructor: lambda { |week_number, year|
28
+ WeekPeriod.new(week_number: week_number, year: year)
29
+ }
30
+
31
+ def self.policy_class
32
+ DiaryPolicy
33
+ end
34
+
35
+ # Searchs the object graph rather than a SQL search
36
+ def slot_for(diurnal_period_code_id, station_id, day_of_week, valid_from: nil)
37
+ slots.find do |slot|
38
+ found = slot.diurnal_period_code_id == diurnal_period_code_id &&
39
+ slot.station_id == station_id &&
40
+ slot.day_of_week == day_of_week
41
+
42
+ # This applies to master diaries only really, where we only
43
+ # show recurring slots if they were created before the timeframe
44
+ # of the current week being viewed.
45
+ if found && valid_from
46
+ found = (slot.created_at <= valid_from.end_of_week)
47
+ end
48
+ found
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "renalware/hd"
4
+
5
+ # A cron job creates this delayed job, which does the following:
6
+ # For all Weekly Diaries in the past, plus this week
7
+ # for each day that is in the past
8
+ # mark any slots in the weekly diary as archived
9
+ # and where the weekly diary is inheriting slots from the master diary
10
+ # create corresponding slots in the weekly diary (ie migrate those slots from master to weekly)
11
+ # and archive them.
12
+ # No slots before the current day should be touched
13
+ module Renalware
14
+ module HD
15
+ module Scheduling
16
+ DiaryHousekeepingJob = Struct.new(:up_until) do
17
+ def max_attempts
18
+ 1
19
+ end
20
+
21
+ def queue_name
22
+ "hd_diary_houskeeping"
23
+ end
24
+
25
+ def priority
26
+ 5 # medium
27
+ end
28
+
29
+ def destroy_failed_jobs?
30
+ true
31
+ end
32
+
33
+ # :reek:UtilityFunction
34
+ def perform
35
+ up_until ||= Time.zone.today - 1.day
36
+ up_until = up_until.to_date
37
+ create_missing_weekly_diaries
38
+ move_elapsed_master_slots_into_weekly_slot_equivalent
39
+ archive_old_weekly_slots(up_until)
40
+ end
41
+
42
+ private
43
+
44
+ # Just inc case there are some missing weekly diaries
45
+ def create_missing_weekly_diaries
46
+ args = ArchiveArguments.new
47
+ Hospitals::Unit.hd_sites.each do |unit|
48
+ DiaryRange.new(
49
+ from_week_period: args.from_week_period,
50
+ to_week_period: args.to_week_period,
51
+ unit: unit
52
+ ).create_missing_weekly_diaries(by: User.first)
53
+ end
54
+ end
55
+
56
+ # Copy any master slots that are now in the past (as of midnight Sunday just gone) and
57
+ # which do not already have a weekly slot overriding the master, into weekly slots.
58
+ # This is to stop changes to the master diary affecting diary history.
59
+ # This executes a SQL view to do the dirty work. It references a SQL view called
60
+ # hd_diary_matrix to help it to decide which master slots to copy to the weekly diary.
61
+ # That view could be useful for other things. We could in fact build the diary UI from it.
62
+ def move_elapsed_master_slots_into_weekly_slot_equivalent
63
+ Rails.logger.debug("DEBUG: SELECT renalware.hd_diary_archive_elapsed_master_slots()")
64
+ conn = ActiveRecord::Base.connection
65
+ conn.execute("SELECT renalware.hd_diary_archive_elapsed_master_slots()")
66
+ end
67
+
68
+ # Flag elapsed slots as archived. Principally this will help us to stop them from being
69
+ # changed.
70
+ def archive_old_weekly_slots(up_until)
71
+ slots = DiarySlot
72
+ .unarchived
73
+ .joins(:diary)
74
+ .where(hd_diaries: { year: up_until.year, week_number: up_until.cweek })
75
+ .where(day_of_week: up_until.cwday)
76
+
77
+ ids = slots.pluck(:id)
78
+ Rails.logger.info("Archiving #{ids.length} slots up_until #{up_until} with ids #{ids}")
79
+ slots.update_all(archived: true, archived_at: Time.zone.now)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "renalware/hd"
4
+ require "renalware/week_period"
5
+ require "attr_extras"
6
+
7
+ module Renalware
8
+ module HD
9
+ module Scheduling
10
+ # Represents a range of WeekPeriods and their diaries for a hospital unit
11
+ class DiaryRange
12
+ pattr_initialize [:from_week_period!, :to_week_period!, :unit!]
13
+
14
+ # If there are any missing weekly diaries in the date range, create them
15
+ def create_missing_weekly_diaries(by:)
16
+ week_periods_in_range.each do |period|
17
+ WeeklyDiary.find_or_create_by!(
18
+ hospital_unit_id: unit.id,
19
+ week_number: period.week_number,
20
+ year: period.year
21
+ ) do |diary|
22
+ log_weekly_diary_creation(period)
23
+ diary.master_diary = master_diary_for(unit, by)
24
+ diary.by = by
25
+ end
26
+ end
27
+ end
28
+
29
+ def log_weekly_diary_creation(period)
30
+ Rails.logger.debug(
31
+ "DEBUG: Creating weekly diary for unit #{unit.id} period "\
32
+ "#{period.week_number}/#{period.year}"
33
+ )
34
+ end
35
+
36
+ def from_date
37
+ from_week_period.date_on_first_day_of_week
38
+ end
39
+
40
+ def to_date
41
+ to_week_period.date_on_first_day_of_week
42
+ end
43
+
44
+ private
45
+
46
+ # Returns an array of WeekPeriods for each cwyear and cweek in the date range.
47
+ def week_periods_in_range
48
+ @week_periods_in_range ||= begin
49
+ # The array in the uniq block here serves to filter the date range. It filters out
50
+ # just the dates representing the first day of each unique week/year.
51
+ mondays_in_date_range = (from_date..to_date).uniq { |dt| [dt.cwyear, dt.cweek] }
52
+ # Now map the dates (one per week) into WeekPeriods
53
+ mondays_in_date_range.map do |date|
54
+ WeekPeriod.new(week_number: date.cweek, year: date.cwyear)
55
+ end
56
+ end
57
+ end
58
+
59
+ def master_diary_for(unit, by)
60
+ MasterDiary.find_or_create_by!(hospital_unit_id: unit.id) do |diary|
61
+ Rails.logger.debug "DEBUG: Creating master diary for unit #{unit.id}"
62
+ diary.master = true
63
+ diary.by = by
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/hd"
4
+
5
+ module Renalware
6
+ module HD
7
+ module Scheduling
8
+ class DiarySlot < ApplicationRecord
9
+ self.table_name = :hd_diary_slots
10
+ # Changing to_ary from private to public here is a hack required to remove a SimpleDelgator
11
+ # warning in eg Renalware::HD::Scheduling::WeeklyDiary::WeeklySlotDecorator:
12
+ #
13
+ # `respond_to?': delegator does not forward private method #to_ary
14
+ #
15
+ # Yielding a slot from DiaryPresenter to the view seems to want to call #to_ary on it, (have
16
+ # not looked into why) and as the slot is wrapped in a decorator using SimpleDelegator,
17
+ # SimpleDelegatr complains it can't delegate to a private method.
18
+ # An alternative is to define the following in each Slot decorator
19
+ #
20
+ # def respond_to?(method, include_private = false)
21
+ # return false if method == :to_ary
22
+ # super
23
+ # end
24
+ #
25
+ # or to use method_missnig instead of SimpleDelegator in Slot Decorators.
26
+ #
27
+ public :to_ary
28
+
29
+ # Virtual attribute used on a form to determine what action as preformed on the slot
30
+ attr_accessor :change_type
31
+ # Virtual to help the prepopulation of select2 with the offending patient if an error
32
+ attr_accessor :patient_ids
33
+
34
+ include Accountable
35
+ belongs_to :diary, class_name: "Renalware::HD::Scheduling::Diary", touch: true
36
+ belongs_to :patient, touch: true
37
+ belongs_to :station, class_name: "Renalware::HD::Station"
38
+ belongs_to :diurnal_period_code
39
+
40
+ validates :diary, presence: true
41
+ validates :patient, presence: true
42
+ validates :station, presence: true
43
+ validates :diurnal_period_code, presence: true
44
+ validates :day_of_week, presence: true, inclusion: { in: 1..7 }
45
+
46
+ scope :weekly, -> { joins(:diary).where(hd_diaries: { master: false }) }
47
+ scope :archived, -> { weekly.where(archived: true) }
48
+ scope :unarchived, -> { weekly.where(archived: false) }
49
+
50
+ # A patient can only be assigned to one station in any period (e.g. am)/day combination
51
+ # for a diary. I.e. they can't be on two stations on Monday morning.
52
+ validates :patient_id,
53
+ uniqueness: { scope: [:diary, :diurnal_period_code, :day_of_week] }
54
+
55
+ # Scoped to a diary, the combination of day + station + diurnal_period is unique
56
+ validates :diurnal_period_code_id,
57
+ uniqueness: { scope: [:diary, :station_id, :day_of_week] }
58
+
59
+ delegate :id, to: :diary, prefix: true
60
+
61
+ def self.policy_class
62
+ DiaryPolicy
63
+ end
64
+
65
+ def on_master_diary?
66
+ diary&.master?
67
+ end
68
+
69
+ def description
70
+ period = diurnal_period_code.code.upcase # e.g. AM
71
+ if diary.master
72
+ "Recurring every #{day_of_week_name} #{period}"
73
+ else
74
+ "This week only on #{day_of_week_name} #{period}"
75
+ end
76
+ end
77
+
78
+ def day_of_week_name
79
+ Time::DAYS_INTO_WEEK.keys[day_of_week - 1].capitalize
80
+ end
81
+
82
+ def cell_id
83
+ "#{diurnal_period_code&.id}-#{station&.id}-#{day_of_week}"
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/hd"
4
+ require "renalware/week_period"
5
+ ##
6
+ # Returns a hospital unit's diary for the requested week (passed to #new as a WeekPeriod
7
+ # value object).
8
+ #
9
+ module Renalware
10
+ module HD
11
+ module Scheduling
12
+ class FindOrCreateDiaryByWeekQuery
13
+ attr_reader :unit_id, :period, :by, :relation
14
+
15
+ def initialize(unit_id:, week_period:, by:, relation: WeeklyDiary.all)
16
+ @relation = relation
17
+ @period = week_period
18
+ @unit_id = unit_id
19
+ @by = by
20
+ end
21
+
22
+ def call
23
+ find_or_create_diary
24
+ end
25
+
26
+ private
27
+
28
+ # Find the diary for the current unit/week/year, or, if for example if a user wants to fill
29
+ # next week's diary and it does not yet exist, create it
30
+ # NB we _always_ return a diary from this method - whether it is found or built just-in-time
31
+ def find_or_create_diary
32
+ attrs = {
33
+ hospital_unit_id: unit_id,
34
+ week_number: period.week_number,
35
+ year: period.year,
36
+ master: false
37
+ }
38
+ relation.find_by(**attrs) || build_diary(**attrs)
39
+ end
40
+
41
+ # Create a new Weekly diary for the current week/year/unit
42
+ # Add in DiaryPeriods for each currently defined diurnal period eg am pm eve
43
+ def build_diary(attrs)
44
+ master_diary = FindOrCreateMasterDiary.for_unit(unit_id, by)
45
+ diary = WeeklyDiary.create!(**attrs, by: by, master_diary: master_diary)
46
+ # Reload the diary using the supplied relation (might be egager_loads etc)
47
+ relation.find(diary.id)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renalware
4
+ module HD
5
+ module Scheduling
6
+ class FindOrCreateMasterDiary
7
+ attr_reader :unit_id, :user
8
+
9
+ def self.for_unit(unit_id, user)
10
+ new(unit_id, user).call
11
+ end
12
+
13
+ def initialize(unit_id, user)
14
+ @unit_id = unit_id
15
+ @user = user
16
+ end
17
+
18
+ def call
19
+ MasterDiary.find_or_initialize_by(hospital_unit_id: unit_id).tap do |master|
20
+ master.by = user
21
+ master.save!
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/hd"
4
+
5
+ module Renalware
6
+ module HD
7
+ module Scheduling
8
+ class MasterDiary < Diary
9
+ # Overwrite the existing master attribute to ensure it defaults to true
10
+ attribute :master, :boolean, default: true
11
+ validates :hospital_unit_id, uniqueness: true
12
+ has_many :weekly_diaries, class_name: "WeeklyDiary"
13
+ # While our DB constraints could check for the string
14
+ # "Renalware::HD::Scheduling::MasterDiary" in the type column, this feels a bit fragile, so
15
+ # instead a MasterDiary must have a corresponding
16
+ # master = TRUE column
17
+ validates :master, inclusion: { in: [true], allow_nil: false }
18
+
19
+ class MasterSlotDecorator < SimpleDelegator
20
+ def master?
21
+ true
22
+ end
23
+ end
24
+
25
+ def decorate_slot(slot)
26
+ MasterSlotDecorator.new(slot)
27
+ end
28
+
29
+ def slot_for(*args)
30
+ (slot = super) && decorate_slot(slot)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/hd"
4
+
5
+ module Renalware
6
+ module HD
7
+ module Scheduling
8
+ class WeeklyDiary < Diary
9
+ # Overwrite the existing master attribute to ensure it defaults to false
10
+ attribute :master, :boolean, default: false
11
+ belongs_to :master_diary, class_name: "Renalware::HD::Scheduling::MasterDiary"
12
+ validates :week_number,
13
+ presence: true,
14
+ uniqueness: { scope: [:year, :hospital_unit_id] },
15
+ inclusion: { in: 1..53 }
16
+ validates :year, presence: true
17
+ validates :master, inclusion: { in: [false], allow_nil: false }
18
+ validates :master_diary, presence: true
19
+ delegate :to_s, to: :week
20
+ scope :ordered, -> { order(year: :desc, week_number: :desc) }
21
+
22
+ def applies_to_current_week?
23
+ today = Time.zone.today
24
+ [today.cweek, today.year] == [week.week_number, week.year]
25
+ end
26
+
27
+ def archived?
28
+ false
29
+ end
30
+
31
+ class WeeklySlotDecorator < SimpleDelegator
32
+ def master?
33
+ false
34
+ end
35
+ end
36
+
37
+ def decorate_slot(slot)
38
+ WeeklySlotDecorator.new(slot)
39
+ end
40
+
41
+ def slot_for(*args)
42
+ (slot = super) && decorate_slot(slot)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -3,14 +3,14 @@
3
3
  require_dependency "renalware/pathology"
4
4
 
5
5
  #
6
- # When subscribed to HL7 `message_arrived` messages, gets notified of incoming HL7 messages
6
+ # When subscribed to HL7 `oru_message_arrived` messages, gets notified of incoming HL7 messages
7
7
  # and creates the observations contained therein provided the patient exists.
8
8
  #
9
9
  module Renalware
10
10
  module Pathology
11
11
  class MessageListener
12
12
  # Note: We are already inside a transaction here
13
- def message_arrived(hl7_message:, **)
13
+ def oru_message_arrived(hl7_message:, **)
14
14
  pathology_params = parse_pathology_params(hl7_message)
15
15
  create_observation_requests_and_their_child_observations_from(pathology_params)
16
16
  #
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/pathology/requests"
4
+
5
+ module Renalware
6
+ module Pathology
7
+ module Requests
8
+ class GlobalRule
9
+ class LatestCRFOlderThanWeeks < GlobalRule
10
+ validates :param_comparison_value, presence: true
11
+
12
+ # Returns true if the patient has a transplant registration with a latest CRF date
13
+ # older than <param_comparison_value> weeks ago.
14
+ def observation_required_for_patient?(patient, date)
15
+ registration = registration_for(patient)
16
+ return false unless registration
17
+
18
+ latest_crf_date = registration.document.crf.latest.recorded_on
19
+ return false if latest_crf_date.blank?
20
+
21
+ max_crf_date = date - param_comparison_value.to_i.weeks
22
+ latest_crf_date < max_crf_date
23
+ end
24
+
25
+ def to_s
26
+ "latest CRF older than #{param_comparison_value} weeks"
27
+ end
28
+
29
+ private
30
+
31
+ def registration_for(patient)
32
+ Transplants::Registration.for_patient(patient).first
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -22,7 +22,7 @@ module Renalware
22
22
  end
23
23
 
24
24
  def param_comparison_boolean
25
- param_comparison_value == "true"
25
+ param_comparison_value&.casecmp("true") == 0
26
26
  end
27
27
  end
28
28
  end
@@ -58,7 +58,7 @@ module Renalware
58
58
  has_many :exit_site_infections, class_name: "PD::ExitSiteInfection"
59
59
  has_many :peritonitis_episodes, class_name: "PD::PeritonitisEpisode"
60
60
  has_many :pd_regimes, class_name: "PD::Regime"
61
- has_many :problems, class_name: "Problems::Problem"
61
+ has_many :problems, -> { ordered }, class_name: "Problems::Problem"
62
62
  has_many :prescriptions, class_name: "Medications::Prescription"
63
63
  has_many :drugs, through: :prescriptions
64
64
  has_many :medication_routes, through: :prescriptions, class_name: "Medications::MedicationRoute"
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/patients"
4
+
5
+ module Renalware
6
+ module Patients
7
+ class Abridgement < ApplicationRecord
8
+ self.table_name = "patient_master_index"
9
+ validates :hospital_number, presence: true
10
+ validates :given_name, presence: true
11
+ validates :family_name, presence: true
12
+
13
+ def self.policy_class
14
+ ::Renalware::BasePolicy
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/patients"
4
+
5
+ module Renalware
6
+ module Patients
7
+ class AbridgementSearchForm
8
+ include ActiveModel::Model
9
+ attr_accessor :criteria
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/patients"
4
+
5
+ module Renalware
6
+ module Patients
7
+ module Ingestion
8
+ class Command
9
+ attr_reader :message
10
+
11
+ def initialize(message)
12
+ @message = message
13
+ end
14
+
15
+ def self.for(message)
16
+ CommandFactory.new.for(message)
17
+ end
18
+
19
+ def call
20
+ raise NotImplementedError
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end