renalware-core 2.0.105 → 2.0.106

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -0
  3. data/app/assets/javascripts/renalware/hd.js +108 -0
  4. data/app/assets/stylesheets/renalware/modules/_hd.scss +204 -0
  5. data/app/controllers/renalware/hd/prescription_administration_authorisations_controller.rb +27 -0
  6. data/app/controllers/renalware/hd/sessions_controller.rb +14 -3
  7. data/app/models/renalware/hd/mdm_patients_query.rb +1 -2
  8. data/app/models/renalware/hd/prescription_administration.rb +48 -0
  9. data/app/models/renalware/hd/prescription_administration_reason.rb +10 -0
  10. data/app/models/renalware/hd/session_factory.rb +4 -1
  11. data/app/models/renalware/hd/sessions/save_session.rb +7 -0
  12. data/app/models/renalware/pd/mdm_patients_query.rb +1 -2
  13. data/app/models/renalware/renal/registry/preflight_checks/deaths_query.rb +1 -2
  14. data/app/models/renalware/transplants/mdm_patients_query.rb +1 -2
  15. data/app/models/renalware/user.rb +13 -0
  16. data/app/presenters/renalware/hd/session_presenter.rb +5 -0
  17. data/app/views/renalware/api/ukrdc/patients/_clinic_visit_observation.xml.builder +1 -2
  18. data/app/views/renalware/hd/sessions/_form.html.slim +3 -12
  19. data/app/views/renalware/hd/sessions/_form_actions.html.slim +19 -18
  20. data/app/views/renalware/hd/sessions/form/_drugs_to_be_administered.html.slim +84 -0
  21. data/app/views/renalware/hospitals/units/index.html.slim +1 -1
  22. data/app/views/renalware/transplants/registration_statuses/_list.html.slim +0 -8
  23. data/config/initializers/simple_form_foundation.rb +18 -0
  24. data/config/locales/renalware/hd/prescription_administration.en.yml +5 -0
  25. data/config/locales/renalware/navigation/renal.en.yml +1 -1
  26. data/config/routes/hd.rb +1 -0
  27. data/db/migrate/20190624130020_add_authentication_to_hd_prescription_administrations.rb +16 -0
  28. data/db/migrate/20190627141751_add_tokens_to_hd_prescription_administrations.rb +22 -0
  29. data/db/migrate/20190716125837_create_hd_prescription_administration_reasons.rb +17 -0
  30. data/db/seeds/default/hd/prescription_administration_reasons.rb +16 -0
  31. data/db/seeds/default/hd/seeds.rb +1 -0
  32. data/lib/renalware/version.rb +1 -1
  33. data/spec/factories/transplants/modality_descriptions.rb +7 -2
  34. metadata +10 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b9b42c69084c04d467667d66060481ae92e81db155c9324f76b23b952314c9d
4
- data.tar.gz: 45fc5ad4564fd35587d7b87a2fd0f578ddd8849b07a84e8818da7d2234ece38f
3
+ metadata.gz: efb794ae27f5828ca612778342c373a1c21523c321b8d128fc375b599c73e6f5
4
+ data.tar.gz: 193b964ca2a0d8dddbe61fdf32ca7dec8a30b13d775d4f711e84f1d348adb0c5
5
5
  SHA512:
6
- metadata.gz: dda5b2284b0a3fc51cd33c5df151d1949f4ac40671ef086b55fc25672259ea52f280dd59fcca51aa213b98df726306ed0fbde98560797014f2f3ca5215a82b61
7
- data.tar.gz: e093eb880bb2c8b5e0c36ead99dac1736a33bdc6fa9e0e26098317714fdd55d18abf3bc25e8eb5d91c1a8879327a6b1ccb05cd32e4bfece83c8d0c1fb904c9aa
6
+ metadata.gz: 7897419fac1a376460c4f7e696ca853686845ba2a4c3918928dfc85964c1a3e2bede291f6db07fa7a3fef179e335fe9b0df62d8cf353278fb18a23d84b89d3fc
7
+ data.tar.gz: 61043d951f2a2c2194679f0ac9682ee7cb22181ebe3bbc8f51e01f2832facaaef79b8632ef34df056aa7db6c37d2ed4bc3fb2e39af17de9352ae4dbe8353ff9f
data/README.md CHANGED
@@ -223,3 +223,9 @@ docker build -t renalware .
223
223
  docker-compose run web rake db:create
224
224
  docker-compose run web rake app:db:create
225
225
  ```
226
+
227
+ #### Browser testing
228
+
229
+ <a href="https://www.browserstack.com">
230
+ <img alt="Browserstack logo" src="doc/Browserstack-logo.svg" width="188" height="43">
231
+ </a>
@@ -0,0 +1,108 @@
1
+ /*
2
+ These are functions relating to the authorisation by nurse + witness of prescriptions
3
+ within the HD Session form.
4
+ TODO: This needs refactoring.
5
+ */
6
+ $(document).ready(function() {
7
+ /*
8
+ If enter is pressed in a password field (out of habit as enter is oftern used when logging in,
9
+ after entering their password) throw it away. We could map it to a tab but frankly that gets
10
+ a bit complicated - see here:
11
+ https://stackoverflow.com/questions/2335553/jquery-how-to-catch-enter-key-and-change-event-to-tab
12
+ */
13
+ $("input.user-password").on("keypress", function(e) {
14
+ if (e.keyCode == 13) { return false; }
15
+ });
16
+
17
+ $(".hd-drug-administered input[type='radio']").on("change", function(e) {
18
+ var checked = ($(this).val() == "true");
19
+ var container = $(this).closest(".hd-drug-administration");
20
+ $(container).toggleClass("administered", checked)
21
+ $(container).toggleClass("not-administered", !checked)
22
+ $(container).removeClass("undecided");
23
+ $(".authentication", container).toggle(checked)
24
+ $(".authentication", container).toggleClass("disabled-with-faded-overlay", !checked)
25
+ $(".reason-why-not-administered", container).toggle(!checked)
26
+ });
27
+
28
+ $(".hd-drug-administration .authentication-user-id").on("select2:select", function(e) {
29
+ var container = $(this).closest(".user-and-password");
30
+ var topContainer = $(container).closest(".hd-drug-administration")
31
+ $(container).find(".authentication-token").val("")
32
+ $("input.user-password", container).val("");
33
+ $(container).removeClass("authorised");
34
+ var tokenCount = $(topContainer).find(".authorised").length;
35
+ $(topContainer).attr("data-token-count", tokenCount);
36
+ });
37
+
38
+
39
+ // When a user clicks the link to clear the authorisation (they might have used the wrong user
40
+ // for instance) the clear the relevant token and password fields and classes.
41
+ $(".hd-drug-administration .user-and-password .user-and-password--clear").on("click", function(e) {
42
+ e.preventDefault();
43
+ var container = $(this).closest(".user-and-password");
44
+ var topContainer = $(container).closest(".hd-drug-administration")
45
+ $(container).find(".authentication-token").val("")
46
+ $("input.user-password", container).val("");
47
+ $(container).removeClass("authorised");
48
+ var tokenCount = $(topContainer).find(".authorised").length;
49
+ $(topContainer).attr("data-token-count", tokenCount);
50
+ });
51
+
52
+ // When the user has entered a password and leaves the password field, make an ajax POST to
53
+ // authenticate the user and on success return an authorisation token which is added to the html
54
+ // form - it will be validated when the form is submitted.
55
+ // TODO: also do this if enter pressed while in the password field.
56
+ $(".user-and-password input.user-password").on("blur", function(e) {
57
+ var container = $(this).closest(".user-and-password");
58
+ var topContainer = $(container).closest(".hd-drug-administration")
59
+ var authUrl = $(container).closest(".authentication").data("authentication-url");
60
+ var userSelect = $(container).find(".authentication-user-id");
61
+ var userId = $(userSelect).find("option:selected").val();
62
+ var authorisationTokenHiddenField = $(container).find(".authentication-token");
63
+ var password = $(this).val();
64
+
65
+ // Prevent the a 401 xhr code from redirecting us to the login page - see ajax_errors.js
66
+ $(document).off('ajaxError');
67
+
68
+ if (userId && password) {
69
+ $.ajax({
70
+ async: false, /* needed as if pwd field has focus and user clicks submit, it would not wait for ajax */
71
+ url: authUrl,
72
+ type: "POST",
73
+ data: "[user][id]=" + userId + "&[user][password]=" + password,
74
+ beforeSend: function() {
75
+ // Add a 'working' class during ajax operations so we can show a spinner for example
76
+ $(container).addClass("working");
77
+ $(container).removeClass("error");
78
+ // $(".hd-session-form button[type='submit]").prop("disabled", "disabled")
79
+ },
80
+ complete: function() {
81
+ $(container).removeClass("working");
82
+ // $(".hd-session-form input[type='submit']").removeProp("disabled")
83
+ },
84
+ statusCode: {
85
+ 200: function (token) {
86
+ // The user id/password combination is valid and a token has been returned.
87
+ // We save the token to a hidden field so it wikll be submitted in the session form.
88
+ $(authorisationTokenHiddenField).val(token);
89
+ $(container).removeClass("unauthorised").addClass("authorised").removeClass("error");
90
+ // $(userSelect).prop("disabled", true);
91
+ var tokenCount = $(topContainer).find(".authorised").length;
92
+ $(topContainer).attr("data-token-count", tokenCount);
93
+
94
+ },
95
+ 401: function (data) {
96
+ // The user id/password combination was not valid
97
+ console.log('401: Unauthenticated');
98
+ $(authorisationTokenHiddenField).prop("value", "");
99
+ $(container).removeClass("authorised").addClass("error");
100
+
101
+ var tokenCount = $(topContainer).find(".authorised").length;
102
+ $(topContainer).attr("data-token-count", tokenCount);
103
+ }
104
+ }
105
+ });
106
+ }
107
+ });
108
+ });
@@ -375,3 +375,207 @@ table.hd-sessions {
375
375
  margin-bottom: 0.2rem;
376
376
  }
377
377
  }
378
+
379
+ /*
380
+ For the prescription administration section in the HD Session form
381
+ use flexbox to layout items inside each prescription.
382
+ */
383
+ .hd-drug-administration {
384
+ display: flex;
385
+ border: solid 1px $light-grey;
386
+ margin: .7rem 0;
387
+ padding: .3rem .6rem;
388
+ border-right-width: 15px;
389
+ flex-wrap: wrap;
390
+
391
+ .summary {
392
+ flex-basis: 100%;
393
+ display: flex; /* also a flex for items within it */
394
+
395
+ .hd-drug {
396
+ flex: 1;
397
+ padding-bottom: .5rem;
398
+ color: $mid-grey;
399
+
400
+ .hd-drug--name {
401
+ font-weight: bold;
402
+ }
403
+ .hd-drug--name,
404
+ .hd-drug--dose,
405
+ .hd-drug--route,
406
+ .hd-drug--prescribed-on,
407
+ .hd-drug--frequency {
408
+ display: inline-block;
409
+ padding-right: .5rem;
410
+ color: $dark-grey;
411
+ }
412
+
413
+ .hd-drug--prescriber-name {
414
+ display: inline-block;
415
+ color: $mid-grey;
416
+ }
417
+
418
+ .hd-drug--termination {
419
+ .hd-drug--termination-date {
420
+ color: $dark-grey;
421
+ display: inline-block;
422
+ color: $dark-grey;
423
+ padding: 0 .3rem;
424
+ }
425
+ }
426
+ }
427
+
428
+ /*
429
+ This is the checkox to indicate the drug was administered.
430
+ We fix its width as we never want it to shrink.
431
+ */
432
+ .hd-drug-administered {
433
+ flex-basis: 220px;
434
+ text-align: right;
435
+
436
+ .radio {
437
+ display: inline-block !important;
438
+ }
439
+ }
440
+ }
441
+
442
+ .reason-why-not-administered {
443
+ display: none;
444
+ }
445
+
446
+ &.undecided {
447
+ .authentication {
448
+ display: block;
449
+ }
450
+ }
451
+
452
+ &.administered {
453
+ border-right-color: $nhs-red;
454
+
455
+ /* The 'status' values for the prescription-administration */
456
+ &[data-token-count='2'] {
457
+ border-right-color: $nhs-green;
458
+ }
459
+ }
460
+
461
+ &.not-administered {
462
+ .authentication {
463
+ display: none;
464
+ }
465
+
466
+ .reason-why-not-administered {
467
+ display: block;
468
+ }
469
+ }
470
+
471
+ .small-input {
472
+ max-width: inherit !important;
473
+ }
474
+
475
+ .notes {
476
+ flex: 1;
477
+ margin-right: 1rem;
478
+
479
+ textarea {
480
+ height: 5.2rem;
481
+ }
482
+
483
+ @media screen and (max-width: 768px) {
484
+ // As the screen shrinks, put the user/password boxes under the notes
485
+ flex-basis: 100%;
486
+ }
487
+ }
488
+
489
+ /* this section contains select2 inputs and pwd fields for the 2 users */
490
+ .authentication {
491
+ flex-basis: 330px;
492
+ flex-grow: 0;
493
+
494
+ // Addinng the disabled-with-faded-overlay class to authentication adds a semi transparent overlay
495
+ // that indicates the section is no receiving input ie because the drug has been marked
496
+ // as not administered.
497
+ &.disabled-with-faded-overlay {
498
+ position: relative;
499
+ }
500
+
501
+ &.disabled-with-faded-overlay:after {
502
+ content: " ";
503
+ z-index: 10;
504
+ display: block;
505
+ position: absolute;
506
+ height: 100%;
507
+ top: 0;
508
+ left: 0;
509
+ right: 0;
510
+ background: rgba(255, 255, 255, 0.5);
511
+ }
512
+
513
+ .user-and-password {
514
+ display: inline-block;
515
+ width: 50%;
516
+ padding-right: 1rem;
517
+ vertical-align: top;
518
+
519
+ .select2-container--default.select2-container--disabled .select2-selection--single {
520
+ background-color: $white;
521
+ }
522
+
523
+ .spinner {
524
+ display: none;
525
+ }
526
+
527
+ &.working {
528
+ .spinner {
529
+ display: inline-block;
530
+ }
531
+
532
+ .user-password {
533
+ background: $mid-grey;
534
+ }
535
+
536
+ }
537
+
538
+ .error.invalid-password {
539
+ display: none;
540
+ }
541
+
542
+ &.error {
543
+ small.error {
544
+ display: block;
545
+ }
546
+ }
547
+
548
+ .select2 {
549
+ // margin-bottom: 0.75rem;
550
+ }
551
+
552
+ .user-password {
553
+ margin-bottom: 0;
554
+ }
555
+
556
+ .confirmed {
557
+ display: none;
558
+ text-align: center;
559
+
560
+ i {
561
+ font-size: 1.6rem;
562
+ color: $nhs-green;
563
+ }
564
+
565
+ a {
566
+ margin-left: .4rem;
567
+ }
568
+ }
569
+
570
+ &.authorised {
571
+ .user-password {
572
+ display: none
573
+ }
574
+
575
+ .confirmed {
576
+ display: block;
577
+ }
578
+ }
579
+ }
580
+ }
581
+ }
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/hd/base_controller"
4
+
5
+ module Renalware
6
+ module HD
7
+ class PrescriptionAdministrationAuthorisationsController < BaseController
8
+ skip_after_action :verify_authorized
9
+
10
+ def create
11
+ user = User.find(auth_params[:id])
12
+ if user.valid_password?(auth_params[:password])
13
+ render status: :ok, plain: user.auth_token
14
+ else
15
+ # head :bad_request
16
+ head :unauthorized
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def auth_params
23
+ params.require(:user).permit(:id, :password)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -5,6 +5,7 @@ require "collection_presenter"
5
5
 
6
6
  module Renalware
7
7
  module HD
8
+ # rubocop:disable Metrics/ClassLength
8
9
  class SessionsController < BaseController
9
10
  include PresenterHelper
10
11
  include Renalware::Concerns::Pageable
@@ -45,6 +46,14 @@ module Renalware
45
46
 
46
47
  def edit
47
48
  session = Session.for_patient(patient).find(params[:id])
49
+ session.prescription_administrations.each do |a|
50
+ if a.administrator_authorised?
51
+ a.administrator_authorisation_token = a.administered_by&.auth_token
52
+ end
53
+ if a.witness_authorised?
54
+ a.witness_authorisation_token = a.witnessed_by&.auth_token
55
+ end
56
+ end
48
57
  authorize session
49
58
  render :edit, locals: locals(session)
50
59
  rescue Pundit::NotAuthorizedError
@@ -65,8 +74,7 @@ module Renalware
65
74
  end
66
75
 
67
76
  def save_session
68
- command = Sessions::SaveSession.new(patient: patient,
69
- current_user: current_user)
77
+ command = Sessions::SaveSession.new(patient: patient, current_user: current_user)
70
78
  command.subscribe(self)
71
79
  command.call(params: session_params,
72
80
  id: params[:id],
@@ -124,7 +132,9 @@ module Renalware
124
132
  :hospital_unit_id, :notes, :dialysate_id,
125
133
  :signed_on_by_id, :signed_off_by_id, :type,
126
134
  prescription_administrations_attributes: [
127
- :id, :hd_session_id, :prescription_id, :administered, :notes
135
+ :id, :hd_session_id, :prescription_id, :administered, :notes,
136
+ :administered_by_id, :administrator_authorisation_token,
137
+ :witnessed_by_id, :witness_authorisation_token, :reason_id
128
138
  ],
129
139
  document: []]
130
140
  end
@@ -136,5 +146,6 @@ module Renalware
136
146
  .try(:permit!)
137
147
  end
138
148
  end
149
+ # rubocop:enable Metrics/ClassLength
139
150
  end
140
151
  end
@@ -7,7 +7,6 @@ module Renalware
7
7
  class MDMPatientsQuery
8
8
  include ModalityScopes
9
9
  include PatientPathologyScopes
10
- MODALITY_NAMES = "HD"
11
10
  DEFAULT_SEARCH_PREDICATE = "hgb_date desc"
12
11
  attr_reader :params, :named_filter
13
12
 
@@ -35,7 +34,7 @@ module Renalware
35
34
  .extending(NamedFilterScopes)
36
35
  .with_current_pathology
37
36
  .with_registration_statuses
38
- .with_current_modality_matching(MODALITY_NAMES)
37
+ .with_current_modality_of_class(Renalware::HD::ModalityDescription)
39
38
  .public_send(named_filter.to_s)
40
39
  .ransack(params)
41
40
  end
@@ -6,14 +6,62 @@ module Renalware
6
6
  module HD
7
7
  class PrescriptionAdministration < ApplicationRecord
8
8
  include Accountable
9
+ attr_accessor :administrator_authorisation_token
10
+ attr_accessor :witness_authorisation_token
9
11
 
10
12
  # Set to true by the parent hd_session if we are not signing off at this stage
11
13
  attr_accessor :skip_validation
12
14
 
13
15
  belongs_to :hd_session, class_name: "HD::Session", touch: true
14
16
  belongs_to :prescription, class_name: "Medications::Prescription"
17
+ belongs_to :administered_by, class_name: "User"
18
+ belongs_to :witnessed_by, class_name: "User"
19
+ belongs_to :reason, class_name: "PrescriptionAdministrationReason"
15
20
  validates :administered, inclusion: { in: [true, false] }, unless: :skip_validation
16
21
  validates :prescription, presence: true
22
+ validates :administered_by, presence: true, if: :validate_administrator_and_witness?
23
+ validates :witnessed_by, presence: true, if: :validate_administrator_and_witness?
24
+ validate :check_administrator_authorisation_token
25
+ validate :check_witness_authorisation_token
26
+
27
+ private
28
+
29
+ def validate_administrator_and_witness?
30
+ return false if skip_validation || not_administered?
31
+
32
+ true
33
+ end
34
+
35
+ def check_administrator_authorisation_token
36
+ verify_submitted_user_token(
37
+ administered_by,
38
+ administrator_authorisation_token,
39
+ :administrator_authorisation_token
40
+ )
41
+ end
42
+
43
+ def check_witness_authorisation_token
44
+ verify_submitted_user_token(
45
+ witnessed_by,
46
+ witness_authorisation_token,
47
+ :witness_authorisation_token
48
+ )
49
+ end
50
+
51
+ def verify_submitted_user_token(user, token, error_key)
52
+ return if skip_validation
53
+ return if user.blank? || not_administered?
54
+
55
+ if token.blank?
56
+ errors[error_key] << "can't be blank"
57
+ elsif user.auth_token != token
58
+ errors[error_key] << "invalid token"
59
+ end
60
+ end
61
+
62
+ def not_administered?
63
+ administered.nil? || administered == false
64
+ end
17
65
  end
18
66
  end
19
67
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "renalware/hd"
4
+
5
+ module Renalware
6
+ module HD
7
+ class PrescriptionAdministrationReason < ApplicationRecord
8
+ end
9
+ end
10
+ end
@@ -66,7 +66,10 @@ module Renalware
66
66
  return unless session.new_record?
67
67
 
68
68
  patient.prescriptions.to_be_administered_on_hd.map do |prescription|
69
- session.prescription_administrations.build(prescription: prescription)
69
+ session.prescription_administrations.build(
70
+ prescription: prescription,
71
+ administered_by: user
72
+ )
70
73
  end
71
74
  end
72
75
 
@@ -26,6 +26,13 @@ module Renalware
26
26
  session = find_or_build_session(id)
27
27
  session = update_session_attributes(session, signing_off)
28
28
 
29
+ session.prescription_administrations.each do |pa|
30
+ pa.administrator_authorised =
31
+ (pa.administrator_authorisation_token == pa.administered_by&.auth_token)
32
+ pa.witness_authorised =
33
+ (pa.witness_authorisation_token == pa.witnessed_by&.auth_token)
34
+ end
35
+
29
36
  if session.save
30
37
  # Might be cleaner if something listened for this event and created this job there?
31
38
  UpdateRollingPatientStatisticsJob.perform_later(patient) unless session.open?
@@ -5,7 +5,6 @@ module Renalware
5
5
  class MDMPatientsQuery
6
6
  include ModalityScopes
7
7
  include PatientPathologyScopes
8
- MODALITY_NAMES = "PD"
9
8
  DEFAULT_SEARCH_PREDICATE = "hgb_date desc"
10
9
  attr_reader :q, :relation, :named_filter
11
10
 
@@ -28,7 +27,7 @@ module Renalware
28
27
  .extending(ModalityScopes)
29
28
  .extending(PatientPathologyScopes)
30
29
  .extending(NamedFilterScopes)
31
- .with_current_modality_matching(MODALITY_NAMES)
30
+ .with_current_modality_of_class(Renalware::PD::ModalityDescription)
32
31
  .with_current_pathology
33
32
  .with_registration_statuses
34
33
  .left_outer_joins(:current_observation_set)
@@ -8,7 +8,6 @@ module Renalware
8
8
  module PreflightChecks
9
9
  class DeathsQuery
10
10
  include ModalityScopes
11
- MODALITY_NAMES = %w(Death).freeze
12
11
 
13
12
  attr_reader :relation, :query_params
14
13
 
@@ -27,7 +26,7 @@ module Renalware
27
26
  .result
28
27
  .extending(ModalityScopes)
29
28
  .preload(current_modality: [:description])
30
- .with_current_modality_matching(MODALITY_NAMES)
29
+ .with_current_modality_of_class(Renalware::Deaths::ModalityDescription)
31
30
  .where("patients.first_cause_id is NULL AND renal_profiles.esrf_on IS NOT NULL")
32
31
  end
33
32
 
@@ -9,7 +9,6 @@ module Renalware
9
9
  class MDMPatientsQuery
10
10
  include ModalityScopes
11
11
  include PatientPathologyScopes
12
- MODALITY_NAMES = "Transplant"
13
12
  DEFAULT_SEARCH_PREDICATE = "hgb_date DESC"
14
13
  attr_reader :q, :relation, :named_filter
15
14
 
@@ -31,7 +30,7 @@ module Renalware
31
30
  .extending(ModalityScopes)
32
31
  .extending(PatientPathologyScopes)
33
32
  .extending(NamedFilterScopes)
34
- .with_current_modality_matching(MODALITY_NAMES)
33
+ .with_current_modality_of_class(Transplants::RecipientModalityDescription)
35
34
  .with_current_pathology
36
35
  .left_outer_joins(:current_observation_set)
37
36
  .public_send(named_filter.to_s)
@@ -75,6 +75,19 @@ module Renalware
75
75
  end
76
76
  end
77
77
 
78
+ # Create a sha that can be saved in another model to indicate a user has authenticated
79
+ # (or perhaps more correctly, authorised) an action - e.g. in HD Session form where a nurse
80
+ # and witness both enter their credentials against a prescription administered on HD.
81
+ # The idea is that we can check the token belongs to the user buy regenerating the token at any
82
+ # time and checking it still matches. Unlike Devise.friendly_token, we can always regenerate
83
+ # the same token here for any user as it is salted with the same secret. This secret is not
84
+ # stored git for staging and production environments.
85
+ def auth_token
86
+ digest = OpenSSL::Digest.new("sha256")
87
+ key = Rails.application.secrets.secret_key_base
88
+ OpenSSL::HMAC.hexdigest(digest, key, id.to_s)
89
+ end
90
+
78
91
  private
79
92
 
80
93
  def build_authentication_token
@@ -152,6 +152,11 @@ module Renalware
152
152
  return parts.last if parts.length > 1
153
153
  end
154
154
 
155
+ # Ensure drug administrations are always in the same order.
156
+ def prescription_administrations
157
+ __getobj__.prescription_administrations.order(created_at: :asc)
158
+ end
159
+
155
160
  protected
156
161
 
157
162
  attr_reader :session, :view_context
@@ -9,8 +9,7 @@ xml.Observation do
9
9
  xml.ObservationTime visit.datetime
10
10
 
11
11
  xml.ObservationCode do
12
- # xml.CodingStandard "2.16.840.1.113883.4.642.2.115"
13
- xml.CodingStandard "PV"
12
+ xml.CodingStandard "UKRR"
14
13
  xml.Code I18n.t("loinc.#{i18n_key}.code")
15
14
  xml.Description I18n.t("loinc.#{i18n_key}.description")
16
15
  end
@@ -124,18 +124,9 @@ ruby:
124
124
  = fcm.input :subs_goal, wrapper: :horizontal_tiny
125
125
  = fcm.input :subs_volume, wrapper: :horizontal_tiny
126
126
 
127
- = render layout: "renalware/shared/fieldset",
128
- locals: { legend: "HD Drugs", name: "hd_prescriptions" } do
129
-
130
- = f.simple_fields_for :prescription_administrations,
131
- f.object.prescription_administrations do |fpa|
132
-
133
- - presenter = Renalware::Medications::PrescriptionPresenter.new(fpa.object.prescription)
134
- b= "#{presenter.drug_name}"
135
- span= "#{presenter.dose} #{presenter.frequency}"
136
- = fpa.input :prescription_id, as: :hidden
137
- = fpa.input :administered, as: :inline_radio_buttons
138
- = fpa.input :notes, input_html: { class: "small-input" }
127
+ = render "renalware/hd/sessions/form/drugs_to_be_administered",
128
+ f: f,
129
+ prescription_administrations: f.object.prescription_administrations
139
130
 
140
131
  = render layout: "renalware/shared/fieldset",
141
132
  locals: { legend: "Notes/Complications", name: "complications" } do
@@ -1,19 +1,20 @@
1
- = f.button :submit,
2
- value: t(".save"),
3
- data: { disable_with: t(".save_disable_with") },
4
- class: "button secondary"
5
- | &nbsp;
6
- = f.button :submit,
7
- as: :ssdsdsd,
8
- name: "signoff",
9
- value: t(".signoff"),
10
- data: { disable_with: t(".signoff_disable_with") }
11
- - if policy(session).destroy?
1
+ .hd-session-form-actions
2
+ = f.button :submit,
3
+ value: t(".save"),
4
+ data: { disable_with: t(".save_disable_with") },
5
+ class: "button secondary"
12
6
  | &nbsp;
13
- = link_to("Delete", patient_hd_session_path(patient, session),
14
- data: { confirm: t(".delete_confirmation") },
15
- method: :delete,
16
- class: "button alert")
17
- | &nbsp;
18
- span= " or "
19
- = link_to "cancel", back_path
7
+ = f.button :submit,
8
+ as: :ssdsdsd,
9
+ name: "signoff",
10
+ value: t(".signoff"),
11
+ data: { disable_with: t(".signoff_disable_with") }
12
+ - if policy(session).destroy?
13
+ | &nbsp;
14
+ = link_to("Delete", patient_hd_session_path(patient, session),
15
+ data: { confirm: t(".delete_confirmation") },
16
+ method: :delete,
17
+ class: "button alert")
18
+ | &nbsp;
19
+ span= " or "
20
+ = link_to "cancel", back_path
@@ -0,0 +1,84 @@
1
+ = render layout: "renalware/shared/fieldset",
2
+ locals: { legend: "HD Drugs", name: "hd_prescriptions" } do
3
+
4
+ .hd-drug-administrations
5
+ = f.simple_fields_for :prescription_administrations,
6
+ prescription_administrations do |fpa|
7
+
8
+ - prescription = Renalware::Medications::PrescriptionPresenter.new(fpa.object.prescription)
9
+ - administration = fpa.object
10
+
11
+ / There are three possible states for a drug administration:
12
+ / 1. administration.administered == nil -> no decision has been made
13
+ / 2. administration.administered == false -> drug was not administered
14
+ / 3. administration.administered == true -> drug was administered
15
+ ruby:
16
+ administration_klass = case administration.administered
17
+ when true then "administered"
18
+ when false then "not-administered"
19
+ else "undecided"
20
+ end
21
+ # administration_klass = "not-administered" unless administration.administered?
22
+ #- administration_klass = "undecided" if administration.administered.nil?
23
+ .hd-drug-administration(
24
+ data-token-count="#{'2' if administration.administrator_authorised? && administration.witness_authorised?}"
25
+ class="#{administration_klass}"
26
+ )
27
+ .summary
28
+ .hd-drug
29
+ .hd-drug--name= prescription.drug_name
30
+ .hd-drug--dose= prescription.dose
31
+ .hd-drug--route= prescription.route_code
32
+ .hd-drug--frequency= prescription.frequency
33
+ .hd-drug--prescribed-on= l(prescription.prescribed_on)
34
+ .hd-drug--prescriber-name= prescription.updated_by
35
+
36
+ - if prescription.terminated_on.present?
37
+ .hd-drug--termination
38
+ | Terminates
39
+ .hd-drug--termination-date= l(prescription.terminated_on)
40
+
41
+ .hd-drug-administered
42
+ = fpa.input :administered, as: :inline_radio_buttons
43
+
44
+ = fpa.input :prescription_id, as: :hidden
45
+
46
+ .notes
47
+ = fpa.label :notes
48
+ = fpa.input :notes, label: false, wrapper: :zilch, input_html: { rows: 2 }
49
+
50
+ .reason-why-not-administered
51
+ = fpa.label :reason_id
52
+ = fpa.association :reason, wrapper: :zilch
53
+
54
+ .authentication(
55
+ class="#{'disabled-with-faded-overlay' unless administration.administered?}"
56
+ data-authentication-url=renalware.hd_prescription_administration_authorisations_path
57
+ )
58
+ / Output username and password fields for administrator and witness
59
+ - { administrator: :administered, witness: :witnessed }.each do |user_role, prefix|
60
+
61
+ - authorised = administration.public_send(:"#{user_role}_authorised?")
62
+ - token_symbol = :"#{user_role}_authorisation_token"
63
+ .user-and-password(
64
+ class="user-and-password--#{user_role} #{'authorised' if authorised} #{'error' if fpa.object.errors[token_symbol].any?}"
65
+ )
66
+ = fpa.hidden_field token_symbol, class: "authentication-token"
67
+ = fpa.label :"#{prefix}_by"
68
+ = fpa.association :"#{prefix}_by",
69
+ as: :user_picker,
70
+ collection: Renalware::User.ordered,
71
+ wrapper: :zilch,
72
+ label: false,
73
+ input_html: { class: "authentication-user-id" }
74
+
75
+ / Password is only visible as long as the user has not been authenticated
76
+ = password_field_tag :"#{prefix}_by_password",
77
+ "",
78
+ placeholder: "Password",
79
+ class: "user-password",
80
+ autocomplete: :off
81
+ small.error.invalid-password Invalid password
82
+ .confirmed
83
+ i.fas.fa-check-circle
84
+ = link_to "Clear", "#", class: "user-and-password--clear"
@@ -31,7 +31,7 @@
31
31
  td= unit.unit_code
32
32
  td= unit.name
33
33
  td= unit.hospital_centre
34
- td= unit.unit_type.text
34
+ td= unit.unit_type&.text
35
35
  td= unit.renal_registry_code
36
36
  td= yes_no unit.is_hd_site
37
37
  td= Renalware::HD::Station.where(hospital_unit_id: unit.id).count
@@ -19,14 +19,6 @@ article.status-history.secondary
19
19
  - registration.statuses.reversed.each do |status|
20
20
  tr
21
21
  td
22
- = link_to "Edit",
23
- edit_patient_transplants_registration_status_path(patient, status)
24
- = pipe_separator
25
- = link_to "Delete",
26
- patient_transplants_registration_status_path(patient, status),
27
- method: :delete,
28
- data: { confirm: I18n.t("prompts.confirm_delete") }
29
- = pipe_separator
30
22
  = link_to("Toggle",
31
23
  "#status-quick-preview-#{status.id}",
32
24
  data: { behaviour: "toggler" })
@@ -91,6 +91,24 @@ SimpleForm.setup do |config|
91
91
  end
92
92
  end
93
93
 
94
+ config.wrappers :zilch,
95
+ tag: "div",
96
+ hint_class: :field_with_hint,
97
+ error_class: :error do |b|
98
+ b.use :html5
99
+ b.use :placeholder
100
+ b.optional :maxlength
101
+ b.optional :pattern
102
+ b.optional :min_max
103
+ b.optional :readonly
104
+
105
+ b.wrapper :right_input_wrapper, tag: :div, class: "" do |ba|
106
+ ba.use :input
107
+ ba.use :error, wrap_with: { tag: :small, class: ["error", "small-input"] }
108
+ ba.use :hint, wrap_with: { tag: :span, class: ["hint", "small-input"] }
109
+ end
110
+ end
111
+
94
112
  config.wrappers :horizontal_medium,
95
113
  tag: "div",
96
114
  class: "row",
@@ -3,6 +3,11 @@ en:
3
3
  attributes:
4
4
  renalware/hd/prescription_administration:
5
5
  administered: Administered
6
+ administered_by: Nurse
7
+ witnessed_by: Witness
8
+ witnessed_by_password: Password
9
+ administered_by_password: Password
10
+ reason_id: Reason not administered
6
11
  errors:
7
12
  models:
8
13
  renalware/hd/prescription_administration:
@@ -15,7 +15,7 @@ en:
15
15
  aki_alerts: AKI Alerts
16
16
  studies: Clinical Studies
17
17
  renal_registry_checks: Renal Registry Checks
18
- hospital_units: Hospitals
18
+ hospital_units: Dialysis Units
19
19
  mdms:
20
20
  menu_title: MDMs
21
21
  hd: HD
data/config/routes/hd.rb CHANGED
@@ -26,6 +26,7 @@ namespace :hd do
26
26
  get "patients_dialysing_at_hospital" => "patients#dialysing_at_hospital"
27
27
  end
28
28
 
29
+ resources :prescription_administration_authorisations, only: :create
29
30
  resources :transmission_logs, only: [:show, :index]
30
31
  resources :cannulation_types, except: :show
31
32
  resources :dialysers, except: :show
@@ -0,0 +1,16 @@
1
+ class AddAuthenticationToHDPrescriptionAdministrations < ActiveRecord::Migration[5.2]
2
+ def change
3
+ within_renalware_schema do
4
+ add_reference :hd_prescription_administrations,
5
+ :administered_by,
6
+ foreign_key: { to_table: :users },
7
+ index: true,
8
+ null: true
9
+ add_reference :hd_prescription_administrations,
10
+ :witnessed_by,
11
+ foreign_key: { to_table: :users },
12
+ index: true,
13
+ null: true
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ class AddTokensToHDPrescriptionAdministrations < ActiveRecord::Migration[5.2]
2
+ def change
3
+ within_renalware_schema do
4
+ # add_column :hd_prescription_administrations, :administrator_authorisation_token, :string
5
+ # add_column :hd_prescription_administrations, :witness_authorisation_token, :string
6
+ add_column(
7
+ :hd_prescription_administrations,
8
+ :administrator_authorised,
9
+ :boolean,
10
+ default: false,
11
+ null: false
12
+ )
13
+ add_column(
14
+ :hd_prescription_administrations,
15
+ :witness_authorised,
16
+ :boolean,
17
+ default: false,
18
+ null: false
19
+ )
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ class CreateHDPrescriptionAdministrationReasons < ActiveRecord::Migration[5.2]
2
+ def change
3
+ within_renalware_schema do
4
+ create_table :hd_prescription_administration_reasons do |t|
5
+ t.string :name, null: false, index: { unique: true }
6
+ t.timestamps null: false
7
+ end
8
+
9
+ add_reference(
10
+ :hd_prescription_administrations,
11
+ :reason,
12
+ foreign_key: { to_table: :hd_prescription_administration_reasons },
13
+ index: true
14
+ )
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Renalware
4
+ log "Adding HD Prescription Administration Reasons" do
5
+ [
6
+ "No supply available",
7
+ "Target HB exceeded",
8
+ "Patient refused",
9
+ "Patient unwell",
10
+ "Patient received blood transfusion",
11
+ "Wrong dose / route"
12
+ ].each do |reason|
13
+ HD::PrescriptionAdministrationReason.find_or_create_by!(name: reason)
14
+ end
15
+ end
16
+ end
@@ -2,3 +2,4 @@
2
2
 
3
3
  require_relative "./cannulation_types"
4
4
  require_relative "./dialysers"
5
+ require_relative "./prescription_administration_reasons.rb"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Renalware
4
- VERSION = "2.0.105"
4
+ VERSION = "2.0.106"
5
5
  end
@@ -6,7 +6,12 @@ FactoryBot.define do
6
6
  name { "Live Donor" }
7
7
  end
8
8
 
9
- factory :transplant_modality_description, class: "Renalware::HD::ModalityDescription" do
10
- initialize_with { Renalware::HD::ModalityDescription.find_or_create_by(name: "Transplant") }
9
+ factory(
10
+ :transplant_modality_description,
11
+ class: "Renalware::Transplants::RecipientModalityDescription"
12
+ ) do
13
+ initialize_with do
14
+ Renalware::Transplants::RecipientModalityDescription.find_or_create_by(name: "Transplant")
15
+ end
11
16
  end
12
17
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: renalware-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.105
4
+ version: 2.0.106
5
5
  platform: ruby
6
6
  authors:
7
7
  - Airslie
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-09 00:00:00.000000000 Z
11
+ date: 2019-09-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: active_type
@@ -999,6 +999,7 @@ files:
999
999
  - app/assets/javascripts/renalware/events.js
1000
1000
  - app/assets/javascripts/renalware/forms.js
1001
1001
  - app/assets/javascripts/renalware/full_screen.js
1002
+ - app/assets/javascripts/renalware/hd.js
1002
1003
  - app/assets/javascripts/renalware/iframe.js
1003
1004
  - app/assets/javascripts/renalware/keyboard_shortcuts.js
1004
1005
  - app/assets/javascripts/renalware/layout.js
@@ -1152,6 +1153,7 @@ files:
1152
1153
  - app/controllers/renalware/hd/ongoing_sessions_controller.rb
1153
1154
  - app/controllers/renalware/hd/patients_controller.rb
1154
1155
  - app/controllers/renalware/hd/preference_sets_controller.rb
1156
+ - app/controllers/renalware/hd/prescription_administration_authorisations_controller.rb
1155
1157
  - app/controllers/renalware/hd/protocols_controller.rb
1156
1158
  - app/controllers/renalware/hd/sessions_controller.rb
1157
1159
  - app/controllers/renalware/hd/stations_controller.rb
@@ -1497,6 +1499,7 @@ files:
1497
1499
  - app/models/renalware/hd/patients_with_unmet_preferences_query.rb
1498
1500
  - app/models/renalware/hd/preference_set.rb
1499
1501
  - app/models/renalware/hd/prescription_administration.rb
1502
+ - app/models/renalware/hd/prescription_administration_reason.rb
1500
1503
  - app/models/renalware/hd/profile.rb
1501
1504
  - app/models/renalware/hd/profile_for_modality.rb
1502
1505
  - app/models/renalware/hd/profiles_in_date_range_query.rb
@@ -2357,6 +2360,7 @@ files:
2357
2360
  - app/views/renalware/hd/sessions/dna/_row.html.slim
2358
2361
  - app/views/renalware/hd/sessions/dna/_show.html.slim
2359
2362
  - app/views/renalware/hd/sessions/edit.html.slim
2363
+ - app/views/renalware/hd/sessions/form/_drugs_to_be_administered.html.slim
2360
2364
  - app/views/renalware/hd/sessions/index.html.slim
2361
2365
  - app/views/renalware/hd/sessions/new.html.slim
2362
2366
  - app/views/renalware/hd/sessions/open/_form.html.slim
@@ -3534,9 +3538,12 @@ files:
3534
3538
  - db/migrate/20190611152859_add_fields_to_transplant_recipient_workup.rb
3535
3539
  - db/migrate/20190612124015_create_transplant_rejection_episodes.rb
3536
3540
  - db/migrate/20190617121528_create_transplant_rejection_treatments.rb
3541
+ - db/migrate/20190624130020_add_authentication_to_hd_prescription_administrations.rb
3542
+ - db/migrate/20190627141751_add_tokens_to_hd_prescription_administrations.rb
3537
3543
  - db/migrate/20190705083727_alter_ukrdc_treatments.rb
3538
3544
  - db/migrate/20190705105921_create_hd_profile_for_modalites.rb
3539
3545
  - db/migrate/20190709101610_create_pd_regime_for_modalities.rb
3546
+ - db/migrate/20190716125837_create_hd_prescription_administration_reasons.rb
3540
3547
  - db/migrate/20190718091430_add_code_to_modality_descriptions.rb
3541
3548
  - db/migrate/20190718095851_add_discharge_reason_code_to_ukrdc_treatments.rb
3542
3549
  - db/migrate/20190722145936_change_type_of_patients_ukrdc_external_id.rb
@@ -3572,6 +3579,7 @@ files:
3572
3579
  - db/seeds/default/hd/cannulation_types.rb
3573
3580
  - db/seeds/default/hd/dialysers.csv
3574
3581
  - db/seeds/default/hd/dialysers.rb
3582
+ - db/seeds/default/hd/prescription_administration_reasons.rb
3575
3583
  - db/seeds/default/hd/seeds.rb
3576
3584
  - db/seeds/default/housekeeping.rb
3577
3585
  - db/seeds/default/letters/seeds.rb