renalware-core 2.0.105 → 2.0.106

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