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
@@ -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