his_emr_api_lab 2.1.1 → 2.1.3
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.
- checksums.yaml +4 -4
- data/app/controllers/lab/labels_controller.rb +2 -1
- data/app/controllers/lab/orders_controller.rb +14 -25
- data/app/controllers/lab/results_controller.rb +2 -1
- data/app/controllers/lab/specimen_types_controller.rb +0 -1
- data/app/controllers/lab/test_methods_controller.rb +9 -0
- data/app/controllers/lab/test_types_controller.rb +1 -1
- data/app/controllers/lab/tests_controller.rb +1 -3
- data/app/controllers/lab/users_controller.rb +2 -1
- data/app/models/lab/lab_result.rb +4 -0
- data/app/models/lab/lab_test.rb +1 -1
- data/app/models/lab/order_extension.rb +14 -0
- data/app/serializers/lab/lab_order_serializer.rb +20 -4
- data/app/serializers/lab/result_serializer.rb +1 -1
- data/app/serializers/lab/test_serializer.rb +24 -1
- data/app/services/lab/acknowledgement_service.rb +4 -1
- data/app/services/lab/concepts_service.rb +77 -22
- data/app/services/lab/lims/acknowledgement_worker.rb +1 -1
- data/app/services/lab/lims/api/rest_api.rb +543 -78
- data/app/services/lab/lims/config.rb +7 -2
- data/app/services/lab/lims/exceptions.rb +18 -6
- data/app/services/lab/lims/migrator.rb +3 -3
- data/app/services/lab/lims/order_dto.rb +1 -1
- data/app/services/lab/lims/order_serializer.rb +28 -7
- data/app/services/lab/lims/push_worker.rb +3 -3
- data/app/services/lab/lims/utils.rb +5 -7
- data/app/services/lab/lims/worker.rb +1 -1
- data/app/services/lab/metadata.rb +3 -0
- data/app/services/lab/notification_service.rb +72 -0
- data/app/services/lab/orders_search_service.rb +11 -5
- data/app/services/lab/orders_service.rb +79 -38
- data/app/services/lab/results_service.rb +30 -16
- data/app/services/lab/tests_service.rb +15 -3
- data/config/routes.rb +1 -0
- data/db/migrate/20260119104240_add_fulfiller_fields_to_orders.rb +11 -0
- data/db/migrate/20260119104241_create_comment_to_fulfiller_concept.rb +50 -0
- data/lib/lab/version.rb +1 -1
- data/lib/mahis_emr_api_lab.rb +6 -0
- metadata +11 -5
- /data/app/services/lab/lims/api/{couchdb_api.rb → couch_db_api.rb} +0 -0
|
@@ -50,10 +50,15 @@ module Lab
|
|
|
50
50
|
30 # Seconds
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
def config_file_name
|
|
54
|
+
# if nlims.yml exists return it, else return application.yml
|
|
55
|
+
File.exist?(Rails.root.join('config', 'nlims.yml')) ? 'nlims.yml' : 'application.yml'
|
|
56
|
+
end
|
|
57
|
+
|
|
53
58
|
##
|
|
54
59
|
# Returns LIMS' application.yml configuration file
|
|
55
60
|
def application
|
|
56
|
-
@application ||= YAML.load_file(find_config_path(
|
|
61
|
+
@application ||= YAML.load_file(find_config_path(config_file_name))
|
|
57
62
|
end
|
|
58
63
|
|
|
59
64
|
##
|
|
@@ -65,7 +70,7 @@ module Lab
|
|
|
65
70
|
private
|
|
66
71
|
|
|
67
72
|
def emr_api_application(param, fallback = nil)
|
|
68
|
-
@emr_api_application ||= YAML.load_file(Rails.root.join('config',
|
|
73
|
+
@emr_api_application ||= YAML.load_file(Rails.root.join('config', config_file_name))
|
|
69
74
|
|
|
70
75
|
@emr_api_application.fetch(param) do
|
|
71
76
|
raise ConfigNotFound, "Missing config param: #{param}" unless fallback
|
|
@@ -2,11 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
module Lab
|
|
4
4
|
module Lims
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
module Exceptions
|
|
6
|
+
class LimsException < StandardError; end
|
|
7
|
+
class DuplicateNHID < LimsException; end
|
|
8
|
+
class MissingAccessionNumber < LimsException; end
|
|
9
|
+
class UnknownSpecimenType < LimsException; end
|
|
10
|
+
class UnknownTestType < LimsException; end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
module Lab
|
|
17
|
+
module Lims
|
|
18
|
+
class LimsException < StandardError; end
|
|
19
|
+
class DuplicateNHID < LimsException; end
|
|
20
|
+
class MissingAccessionNumber < LimsException; end
|
|
21
|
+
class UnknownSpecimenType < LimsException; end
|
|
22
|
+
class UnknownTestType < LimsException; end
|
|
11
23
|
end
|
|
12
24
|
end
|
|
@@ -29,7 +29,7 @@ require 'lab/lab_test'
|
|
|
29
29
|
require 'lab/lims_order_mapping'
|
|
30
30
|
require 'lab/lims_failed_import'
|
|
31
31
|
|
|
32
|
-
require_relative 'api/
|
|
32
|
+
require_relative 'api/couch_db_api'
|
|
33
33
|
require_relative 'config'
|
|
34
34
|
require_relative 'pull_worker'
|
|
35
35
|
require_relative 'utils'
|
|
@@ -60,8 +60,8 @@ module Lab
|
|
|
60
60
|
##
|
|
61
61
|
# A Lab::Lims::Api object that supports crawling of a LIMS CouchDB instance.
|
|
62
62
|
class CouchDbMigratorApi < Lab::Lims::Api::CouchdbApi
|
|
63
|
-
def initialize(
|
|
64
|
-
super(
|
|
63
|
+
def initialize(*, processes: 1, on_merge_processes: nil, **)
|
|
64
|
+
super(*, **)
|
|
65
65
|
|
|
66
66
|
@processes = processes
|
|
67
67
|
@on_merge_processes = on_merge_processes
|
|
@@ -17,7 +17,7 @@ module Lab
|
|
|
17
17
|
accession_number: self['tracking_number'],
|
|
18
18
|
patient_id:,
|
|
19
19
|
specimen: { concept_id: specimen_type_id },
|
|
20
|
-
tests: self['
|
|
20
|
+
tests: self['tests_map']&.map { |test| { concept_id: test.concept_id } },
|
|
21
21
|
requesting_clinician:,
|
|
22
22
|
date: start_date,
|
|
23
23
|
target_lab: facility_name(self['receiving_facility']),
|
|
@@ -14,16 +14,20 @@ module Lab
|
|
|
14
14
|
|
|
15
15
|
def serialize_order(order)
|
|
16
16
|
serialized_order = Lims::Utils.structify(Lab::LabOrderSerializer.serialize_order(order))
|
|
17
|
-
|
|
18
17
|
Lims::OrderDto.new(
|
|
19
|
-
_id: Lab::LimsOrderMapping.find_by(order:)&.lims_id || serialized_order.accession_number,
|
|
18
|
+
_id: Lab::LimsOrderMapping.find_by(order: order)&.lims_id || serialized_order.accession_number,
|
|
20
19
|
tracking_number: serialized_order.accession_number,
|
|
21
20
|
sending_facility: current_facility_name,
|
|
22
21
|
receiving_facility: serialized_order.target_lab,
|
|
23
22
|
tests: serialized_order.tests.map { |test| format_test_name(test.name) },
|
|
23
|
+
tests_map: serialized_order.tests,
|
|
24
24
|
patient: format_patient(serialized_order.patient_id),
|
|
25
25
|
order_location: format_order_location(serialized_order.encounter_id),
|
|
26
26
|
sample_type: format_sample_type(serialized_order.specimen.name),
|
|
27
|
+
sample_type_map: {
|
|
28
|
+
name: format_sample_type(serialized_order.specimen.name),
|
|
29
|
+
nlims_code: Concept.find(serialized_order.specimen.concept_id).nlims_code
|
|
30
|
+
},
|
|
27
31
|
sample_status: format_sample_status(serialized_order.specimen.name),
|
|
28
32
|
sample_statuses: format_sample_status_trail(order),
|
|
29
33
|
test_statuses: format_test_status_trail(order),
|
|
@@ -32,7 +36,8 @@ module Lab
|
|
|
32
36
|
priority: format_sample_priority(serialized_order.reason_for_test.name),
|
|
33
37
|
date_created: serialized_order.order_date,
|
|
34
38
|
test_results: format_test_results(serialized_order),
|
|
35
|
-
type: 'Order'
|
|
39
|
+
type: 'Order',
|
|
40
|
+
clinical_history: serialized_order.comment_to_fulfiller
|
|
36
41
|
)
|
|
37
42
|
end
|
|
38
43
|
|
|
@@ -81,6 +86,8 @@ module Lab
|
|
|
81
86
|
return nil if regimen_data.blank?
|
|
82
87
|
|
|
83
88
|
regimen_data['regimen']
|
|
89
|
+
rescue StandardError
|
|
90
|
+
nil
|
|
84
91
|
end
|
|
85
92
|
|
|
86
93
|
def find_arv_number(patient_id)
|
|
@@ -97,6 +104,8 @@ module Lab
|
|
|
97
104
|
return nil if start_date.blank?
|
|
98
105
|
|
|
99
106
|
start_date['earliest_date']
|
|
107
|
+
rescue StandardError
|
|
108
|
+
nil
|
|
100
109
|
end
|
|
101
110
|
|
|
102
111
|
def format_sample_type(name)
|
|
@@ -114,9 +123,12 @@ module Lab
|
|
|
114
123
|
def format_sample_status_trail(order)
|
|
115
124
|
return [] if order.concept_id == ConceptName.find_by_name!('Unknown').concept_id
|
|
116
125
|
|
|
117
|
-
user = User.find(order.
|
|
126
|
+
user = User.find(order.creator)
|
|
127
|
+
user = User.find(order.discontinued_by) if Order.columns_hash.key?('discontinued_by') && user.blank?
|
|
128
|
+
|
|
118
129
|
drawn_by = PersonName.find_by_person_id(user.user_id)
|
|
119
|
-
drawn_date = order.discontinued_date || order.start_date
|
|
130
|
+
drawn_date = order.discontinued_date || order.start_date if ['discontinued_date', 'start_date'].all? { |column| order.respond_to?(column) }
|
|
131
|
+
drawn_date ||= order.date_created
|
|
120
132
|
|
|
121
133
|
[
|
|
122
134
|
drawn_date.strftime('%Y%m%d%H%M%S') => {
|
|
@@ -132,7 +144,7 @@ module Lab
|
|
|
132
144
|
end
|
|
133
145
|
|
|
134
146
|
def format_test_status_trail(order)
|
|
135
|
-
tests = order.voided
|
|
147
|
+
tests = order.voided ? order.tests : Lab::LabOrderSerializer.voided_tests(order)
|
|
136
148
|
|
|
137
149
|
tests.each_with_object({}) do |test, trail|
|
|
138
150
|
test_name = format_test_name(ConceptName.find_by_concept_id!(test.value_coded).name)
|
|
@@ -168,7 +180,13 @@ module Lab
|
|
|
168
180
|
order.tests&.each_with_object({}) do |test, results|
|
|
169
181
|
next if test.result.nil? || test.result.empty?
|
|
170
182
|
|
|
171
|
-
|
|
183
|
+
result_obs = Observation.find_by(obs_id: test.result.first.id)
|
|
184
|
+
unless result_obs
|
|
185
|
+
Rails.logger.warn("Observation with obs_id=#{test.result.first.id} not found for test #{test.name} in order #{order.accession_number}")
|
|
186
|
+
next
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
test_creator = User.find(result_obs.creator)
|
|
172
190
|
test_creator_name = PersonName.find_by_person_id(test_creator.person_id)
|
|
173
191
|
|
|
174
192
|
results[format_test_name(test.name)] = {
|
|
@@ -184,6 +202,9 @@ module Lab
|
|
|
184
202
|
id: test_creator.username
|
|
185
203
|
}
|
|
186
204
|
}
|
|
205
|
+
rescue ActiveRecord::RecordNotFound => e
|
|
206
|
+
Rails.logger.error("Failed to format test results for test #{test.name} in order #{order.accession_number}: #{e.message}")
|
|
207
|
+
next
|
|
187
208
|
end
|
|
188
209
|
end
|
|
189
210
|
|
|
@@ -10,6 +10,7 @@ module Lab
|
|
|
10
10
|
include Utils # for logger
|
|
11
11
|
|
|
12
12
|
SECONDS_TO_WAIT_FOR_ORDERS = 30
|
|
13
|
+
START_DATE = Time.parse('2024-09-03').freeze
|
|
13
14
|
|
|
14
15
|
def initialize(lims_api)
|
|
15
16
|
@lims_api = lims_api
|
|
@@ -23,8 +24,6 @@ module Lab
|
|
|
23
24
|
logger.debug("Found #{orders.size} orders...")
|
|
24
25
|
orders.each do |order|
|
|
25
26
|
push_order(order)
|
|
26
|
-
rescue GatewayError => e
|
|
27
|
-
logger.error("Failed to push order ##{order.accession_number}: #{e.class} - #{e.message}")
|
|
28
27
|
rescue StandardError => e
|
|
29
28
|
logger.error("Failed to push order ##{order.id}: #{order&.accession_number} : #{e.class} - #{e.message}")
|
|
30
29
|
end
|
|
@@ -53,7 +52,7 @@ module Lab
|
|
|
53
52
|
|
|
54
53
|
ActiveRecord::Base.transaction do
|
|
55
54
|
if mapping && !order.voided.zero?
|
|
56
|
-
Rails.logger.info("Deleting order ##{order_dto[
|
|
55
|
+
Rails.logger.info("Deleting order ##{order_dto['accession_number']} from LIMS")
|
|
57
56
|
lims_api.delete_order(mapping.lims_id, order_dto)
|
|
58
57
|
mapping.destroy
|
|
59
58
|
elsif mapping
|
|
@@ -103,6 +102,7 @@ module Lab
|
|
|
103
102
|
Rails.logger.debug('Looking for new orders that need to be created in LIMS...')
|
|
104
103
|
Lab::LabOrder.where.not(order_id: Lab::LimsOrderMapping.all.select(:order_id))
|
|
105
104
|
.where("accession_number IS NOT NULL AND accession_number !=''")
|
|
105
|
+
.where(date_created: START_DATE..(Date.today + 1.day))
|
|
106
106
|
.order(date_created: :desc)
|
|
107
107
|
end
|
|
108
108
|
|
|
@@ -47,10 +47,9 @@ module Lab
|
|
|
47
47
|
return user if user
|
|
48
48
|
|
|
49
49
|
god_user = User.first
|
|
50
|
-
|
|
51
|
-
person = Person.create!(creator: god_user.user_id)
|
|
52
|
-
PersonName.create!(person
|
|
53
|
-
|
|
50
|
+
User.current = god_user
|
|
51
|
+
person = Person.create!(creator: god_user.user_id, birthdate: '1980-01-01')
|
|
52
|
+
PersonName.create!(person: person, given_name: 'Lab', family_name: 'Daemon', creator: god_user.user_id)
|
|
54
53
|
User.create!(username: 'lab_daemon', person:, creator: god_user.user_id)
|
|
55
54
|
end
|
|
56
55
|
|
|
@@ -79,9 +78,8 @@ module Lab
|
|
|
79
78
|
end
|
|
80
79
|
|
|
81
80
|
def self.find_concept_by_name(name)
|
|
82
|
-
ConceptName.joins(
|
|
83
|
-
.
|
|
84
|
-
.where(name: CGI.unescapeHTML(name))
|
|
81
|
+
ConceptName.joins("INNER JOIN concept_attribute ON concept_attribute.concept_id = concept_name.concept_id")
|
|
82
|
+
.where("concept_attribute.value_reference = ?", CGI.unescapeHTML(name))
|
|
85
83
|
.first
|
|
86
84
|
end
|
|
87
85
|
end
|
|
@@ -5,6 +5,7 @@ module Lab
|
|
|
5
5
|
# Concepts
|
|
6
6
|
REASON_FOR_TEST_CONCEPT_NAME = 'Reason for test'
|
|
7
7
|
REQUESTING_CLINICIAN_CONCEPT_NAME = 'Person making request'
|
|
8
|
+
COMMENT_TO_FULFILLER_CONCEPT_NAME = 'Comment to fulfiller'
|
|
8
9
|
SPECIMEN_TYPE_CONCEPT_NAME = 'Specimen type'
|
|
9
10
|
TARGET_LAB_CONCEPT_NAME = 'Lab'
|
|
10
11
|
TEST_RESULT_CONCEPT_NAME = 'Lab test result'
|
|
@@ -12,6 +13,8 @@ module Lab
|
|
|
12
13
|
TEST_TYPE_CONCEPT_NAME = 'Test type'
|
|
13
14
|
LAB_ORDER_STATUS_CONCEPT_NAME = 'lab order status'
|
|
14
15
|
UNKNOWN_SPECIMEN = 'Unknown'
|
|
16
|
+
VISIT_TYPE_UUID = '8a4a4488-c2cc-11de-8d13-0010c6dffd0f'
|
|
17
|
+
TEST_METHOD_CONCEPT_NAME = 'Recommended test method'
|
|
15
18
|
|
|
16
19
|
# Encounter
|
|
17
20
|
ENCOUNTER_TYPE_NAME = 'Lab'
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# notification service
|
|
4
|
+
class Lab::NotificationService
|
|
5
|
+
# this gets all uncleared notifications of the user
|
|
6
|
+
def uncleared
|
|
7
|
+
NotificationAlert.joins(:notification_alert_recipients).where(
|
|
8
|
+
'notification_alert_recipient.user_id = ?', User.current.user_id
|
|
9
|
+
)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def clear(alert_id)
|
|
13
|
+
alert = NotificationAlert.find(alert_id)
|
|
14
|
+
# update the notification alert recipient to cleared and read only for the current user
|
|
15
|
+
alert.notification_alert_recipients.where(user_id: User.current.user_id).update_all(cleared: true, alert_read: true)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# this updates the notification to read
|
|
19
|
+
def read(alerts)
|
|
20
|
+
alerts.each do |alert|
|
|
21
|
+
notification = NotificationAlertRecipient.where(user_id: User.current.user_id, alert_id: alert,
|
|
22
|
+
alert_read: false).first
|
|
23
|
+
next if notification.blank?
|
|
24
|
+
|
|
25
|
+
notification.alert_read = true
|
|
26
|
+
notification.save
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def create_notification(alert_type, alert_message)
|
|
31
|
+
return if alert_type != 'LIMS'
|
|
32
|
+
|
|
33
|
+
lab = User.find_by(username: 'lab_daemon')
|
|
34
|
+
ActiveRecord::Base.transaction do
|
|
35
|
+
alert = NotificationAlert.create!(text: alert_message.to_json, date_to_expire: Time.now + not_period.days,
|
|
36
|
+
creator: lab, changed_by: lab, date_created: Time.now)
|
|
37
|
+
notify(alert, User.joins(:roles).uniq)
|
|
38
|
+
# ActionCable.server.broadcast('nlims_channel', alert)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def not_period
|
|
43
|
+
result = GlobalProperty.where(property: 'notification.period')&.first
|
|
44
|
+
return result.property_value.to_i if result.present?
|
|
45
|
+
|
|
46
|
+
7 # default to 7 days
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def notify(notification_alert, recipients)
|
|
50
|
+
recipients.each do |recipient|
|
|
51
|
+
recipient.notification_alert_recipients.create(
|
|
52
|
+
alert_id: notification_alert.id
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def notify_all(notification_alert, users)
|
|
58
|
+
users.each do |user|
|
|
59
|
+
user.notification_alert_recipients.create(
|
|
60
|
+
alert_id: notification_alert.id
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def notify_all_users(notification_alert)
|
|
66
|
+
User.all.each do |user|
|
|
67
|
+
user.notification_alert_recipients.create!(
|
|
68
|
+
alert_id: notification_alert.id
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -7,12 +7,18 @@ module Lab
|
|
|
7
7
|
def find_orders(filters)
|
|
8
8
|
extra_filters = pop_filters(filters, :date, :end_date, :status)
|
|
9
9
|
|
|
10
|
+
uuid = filters.delete(:patient)
|
|
11
|
+
patient = Patient.find(uuid) if uuid
|
|
12
|
+
|
|
13
|
+
filters.merge!(patient_id: patient.id) if patient
|
|
14
|
+
|
|
10
15
|
orders = Lab::LabOrder.prefetch_relationships
|
|
11
16
|
.where(filters)
|
|
12
|
-
|
|
17
|
+
orders = orders.order(start_date: :desc) if Order.column_names.include?('start_date')
|
|
18
|
+
orders = orders.order(date_created: :desc) unless Order.column_names.include?('start_date')
|
|
13
19
|
|
|
14
|
-
orders = filter_orders_by_status(orders
|
|
15
|
-
orders = filter_orders_by_date(orders,
|
|
20
|
+
orders = filter_orders_by_status(orders: orders, status: extra_filters[:status])
|
|
21
|
+
orders = filter_orders_by_date(orders: orders, date: extra_filters[:date], end_date: extra_filters[:end_date])
|
|
16
22
|
|
|
17
23
|
orders.map { |order| Lab::LabOrderSerializer.serialize_order(order) }
|
|
18
24
|
end
|
|
@@ -27,7 +33,7 @@ module Lab
|
|
|
27
33
|
query
|
|
28
34
|
end
|
|
29
35
|
|
|
30
|
-
def filter_orders_by_date(orders
|
|
36
|
+
def filter_orders_by_date(orders:, date: nil, end_date: nil)
|
|
31
37
|
date = date&.to_date
|
|
32
38
|
end_date = end_date&.to_date
|
|
33
39
|
|
|
@@ -40,7 +46,7 @@ module Lab
|
|
|
40
46
|
orders
|
|
41
47
|
end
|
|
42
48
|
|
|
43
|
-
def filter_orders_by_status(orders
|
|
49
|
+
def filter_orders_by_status(orders:, status: nil)
|
|
44
50
|
case status&.downcase
|
|
45
51
|
when 'ordered' then orders.where(concept_id: unknown_concept_id)
|
|
46
52
|
when 'drawn' then orders.where.not(concept_id: unknown_concept_id)
|
|
@@ -58,16 +58,28 @@ module Lab
|
|
|
58
58
|
|
|
59
59
|
order = create_order(encounter, order_params)
|
|
60
60
|
|
|
61
|
+
attach_test_method(order, order_params) if order_params[:test_method]
|
|
62
|
+
|
|
61
63
|
Lab::TestsService.create_tests(order, order_params[:date], order_params[:tests])
|
|
62
64
|
|
|
63
65
|
Lab::LabOrderSerializer.serialize_order(
|
|
64
66
|
order, requesting_clinician: add_requesting_clinician(order, order_params),
|
|
65
67
|
reason_for_test: add_reason_for_test(order, order_params),
|
|
66
|
-
target_lab: add_target_lab(order, order_params)
|
|
68
|
+
target_lab: add_target_lab(order, order_params),
|
|
69
|
+
comment_to_fulfiller: add_comment_to_fulfiller(order, order_params)
|
|
67
70
|
)
|
|
68
71
|
end
|
|
69
72
|
end
|
|
70
73
|
|
|
74
|
+
def attach_test_method(order, order_params)
|
|
75
|
+
create_order_observation(
|
|
76
|
+
order,
|
|
77
|
+
Lab::Metadata::TEST_METHOD_CONCEPT_NAME,
|
|
78
|
+
order_params[:date],
|
|
79
|
+
value_coded: order_params[:test_method]
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
71
83
|
def update_order(order_id, params)
|
|
72
84
|
specimen_id = params.dig(:specimen, :concept_id)
|
|
73
85
|
raise ::InvalidParameterError, 'Specimen concept_id is required' unless specimen_id
|
|
@@ -86,20 +98,23 @@ module Lab
|
|
|
86
98
|
discontinued_reason_non_coded: 'Sample drawn/updated')
|
|
87
99
|
end
|
|
88
100
|
|
|
89
|
-
|
|
101
|
+
reason_for_test = params[:reason_for_test] || params[:reason_for_test_id]
|
|
102
|
+
|
|
103
|
+
if reason_for_test
|
|
90
104
|
Rails.logger.debug("Updating reason for test on order ##{order.order_id}")
|
|
91
|
-
update_reason_for_test(order, params
|
|
105
|
+
update_reason_for_test(order, Concept.find(reason_for_test)&.id, force_update: params.fetch('force_update', false))
|
|
92
106
|
end
|
|
93
107
|
|
|
94
108
|
Lab::LabOrderSerializer.serialize_order(order)
|
|
95
109
|
end
|
|
96
110
|
|
|
97
111
|
def void_order(order_id, reason)
|
|
98
|
-
order = Lab::LabOrder.includes(%i[requesting_clinician reason_for_test target_lab], tests: [:result])
|
|
112
|
+
order = Lab::LabOrder.includes(%i[requesting_clinician reason_for_test target_lab comment_to_fulfiller], tests: [:result])
|
|
99
113
|
.find(order_id)
|
|
100
114
|
|
|
101
115
|
order.requesting_clinician&.void(reason)
|
|
102
116
|
order.reason_for_test&.void(reason)
|
|
117
|
+
order.comment_to_fulfiller&.void(reason)
|
|
103
118
|
order.target_lab&.void(reason)
|
|
104
119
|
|
|
105
120
|
order.tests.each { |test| test.void(reason) }
|
|
@@ -166,8 +181,7 @@ module Lab
|
|
|
166
181
|
'arv_number': find_arv_number(order.patient_id),
|
|
167
182
|
'patient_id': result.person_id,
|
|
168
183
|
'ordered_by': order&.provider&.person&.name,
|
|
169
|
-
'rejection_reason': order_params['comments']
|
|
170
|
-
}.as_json
|
|
184
|
+
'rejection_reason': order_params['comments'] }.as_json
|
|
171
185
|
NotificationService.new.create_notification('LIMS', data)
|
|
172
186
|
end
|
|
173
187
|
|
|
@@ -208,19 +222,22 @@ module Lab
|
|
|
208
222
|
# a 'Lab' encounter is created using the provided program_id and
|
|
209
223
|
# patient_id.
|
|
210
224
|
def find_encounter(order_params)
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
)
|
|
225
|
+
encounter_id = order_params[:encounter_id] || order_params[:encounter]
|
|
226
|
+
patient_id = order_params[:patient_id] || order_params[:patient]
|
|
227
|
+
visit = order_params[:visit]
|
|
228
|
+
|
|
229
|
+
return Encounter.find(encounter_id) if order_params[:encounter] || order_params[:encounter_id]
|
|
230
|
+
raise StandardError, 'encounter_id|uuid or patient_id|uuid required' unless order_params[:patient]
|
|
231
|
+
|
|
232
|
+
encounter = Encounter.new
|
|
233
|
+
encounter.patient = Patient.find(patient_id)
|
|
234
|
+
encounter.encounter_type = EncounterType.find_by_name!(Lab::Metadata::ENCOUNTER_TYPE_NAME)
|
|
235
|
+
encounter.encounter_datetime = order_params[:date] || Date.today
|
|
236
|
+
encounter.visit = Visit.find_by_uuid(visit) if Encounter.column_names.include?('visit_id')
|
|
237
|
+
encounter.provider_id = User.current&.person.id if Encounter.column_names.include?('provider_id')
|
|
238
|
+
encounter.program_id = order_params[:program_id] if Encounter.column_names.include?('program_id') && order_params[:program_id].present?
|
|
239
|
+
encounter.save!
|
|
240
|
+
encounter.reload
|
|
224
241
|
end
|
|
225
242
|
|
|
226
243
|
def create_order(encounter, params)
|
|
@@ -228,19 +245,27 @@ module Lab
|
|
|
228
245
|
raise 'Accession Number cannot be blank' unless access_number.present?
|
|
229
246
|
raise 'Accession cannot be this short' unless access_number.length > 6
|
|
230
247
|
|
|
248
|
+
concept = params.dig(:specimen, :concept)
|
|
249
|
+
concept ||= params.dig(:specimen, :concept_id)
|
|
250
|
+
|
|
231
251
|
order_type = nil
|
|
232
|
-
order_type = OrderType.find_by_order_type_id!(params[:order_type_id]) if params[:order_type_id].present?
|
|
233
|
-
|
|
234
|
-
Lab::LabOrder.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
252
|
+
order_type = OrderType.find_by_order_type_id!(params[:order_type_id])&.id if params[:order_type_id].present?
|
|
253
|
+
|
|
254
|
+
order = Lab::LabOrder.new
|
|
255
|
+
order.order_type_id = order_type || OrderType.find_by_name!(Lab::Metadata::ORDER_TYPE_NAME).id
|
|
256
|
+
order.concept_id = Concept.find(concept)&.id
|
|
257
|
+
order.encounter_id = encounter.id
|
|
258
|
+
order.patient_id = encounter.patient.id
|
|
259
|
+
order.date_created = params[:date]&.to_date || Date.today if order.respond_to?(:date_created)
|
|
260
|
+
order.start_date = params[:date]&.to_date || Date.today if order.respond_to?(:start_date)
|
|
261
|
+
order.auto_expire_date = params[:end_date]
|
|
262
|
+
order.comment_to_fulfiller = params[:comment_to_fulfiller] if params[:comment_to_fulfiller]
|
|
263
|
+
order.accession_number = access_number
|
|
264
|
+
order.orderer = User.current&.user_id
|
|
265
|
+
|
|
266
|
+
order.save!
|
|
267
|
+
|
|
268
|
+
order.reload
|
|
244
269
|
end
|
|
245
270
|
|
|
246
271
|
def accession_number_exists?(accession_number)
|
|
@@ -267,16 +292,28 @@ module Lab
|
|
|
267
292
|
)
|
|
268
293
|
end
|
|
269
294
|
|
|
295
|
+
def add_comment_to_fulfiller(order, params)
|
|
296
|
+
create_order_observation(
|
|
297
|
+
order,
|
|
298
|
+
Lab::Metadata::COMMENT_TO_FULFILLER_CONCEPT_NAME,
|
|
299
|
+
params[:date],
|
|
300
|
+
value_text: params['comment_to_fulfiller']
|
|
301
|
+
)
|
|
302
|
+
end
|
|
303
|
+
|
|
270
304
|
##
|
|
271
305
|
# Attach a reason for the order/test
|
|
272
306
|
#
|
|
273
307
|
# Examples of reasons include: Routine, Targeted, Confirmatory, Repeat, or Stat.
|
|
274
308
|
def add_reason_for_test(order, params)
|
|
309
|
+
reason = params[:reason_for_test_id] || params[:reason_for_test]
|
|
310
|
+
|
|
311
|
+
reason = Concept.find(reason)
|
|
275
312
|
create_order_observation(
|
|
276
313
|
order,
|
|
277
314
|
Lab::Metadata::REASON_FOR_TEST_CONCEPT_NAME,
|
|
278
315
|
params[:date],
|
|
279
|
-
value_coded:
|
|
316
|
+
value_coded: reason
|
|
280
317
|
)
|
|
281
318
|
end
|
|
282
319
|
|
|
@@ -293,14 +330,16 @@ module Lab
|
|
|
293
330
|
)
|
|
294
331
|
end
|
|
295
332
|
|
|
296
|
-
def create_order_observation(order, concept_name, date, **)
|
|
333
|
+
def create_order_observation(order, concept_name, date, **values)
|
|
334
|
+
creator = User.find_by(username: 'lab_daemon')
|
|
335
|
+
User.current ||= creator
|
|
297
336
|
Observation.create!(
|
|
298
337
|
order:,
|
|
299
338
|
encounter_id: order.encounter_id,
|
|
300
339
|
person_id: order.patient_id,
|
|
301
340
|
concept_id: ConceptName.find_by_name!(concept_name).concept_id,
|
|
302
341
|
obs_datetime: date&.to_time || Time.now,
|
|
303
|
-
**
|
|
342
|
+
**values
|
|
304
343
|
)
|
|
305
344
|
end
|
|
306
345
|
|
|
@@ -309,18 +348,20 @@ module Lab
|
|
|
309
348
|
end
|
|
310
349
|
|
|
311
350
|
def unknown_concept_id
|
|
312
|
-
ConceptName.find_by_name!('Unknown').
|
|
351
|
+
ConceptName.find_by_name!('Unknown').concept
|
|
313
352
|
end
|
|
314
353
|
|
|
315
|
-
def update_reason_for_test(order, concept_id)
|
|
354
|
+
def update_reason_for_test(order, concept_id, force_update: false)
|
|
316
355
|
raise InvalidParameterError, "Reason for test can't be blank" if concept_id.blank?
|
|
317
356
|
|
|
318
357
|
return if order.reason_for_test&.value_coded == concept_id
|
|
319
358
|
|
|
320
|
-
raise InvalidParameterError, "Can't change reason for test once set" if order.reason_for_test&.value_coded
|
|
359
|
+
raise InvalidParameterError, "Can't change reason for test once set" if order.reason_for_test&.value_coded && !force_update
|
|
321
360
|
|
|
322
361
|
order.reason_for_test&.delete
|
|
323
|
-
|
|
362
|
+
date = order.start_date if order.respond_to?(:start_date)
|
|
363
|
+
date ||= order.date_created
|
|
364
|
+
add_reason_for_test(order, date: date, reason_for_test_id: concept_id)
|
|
324
365
|
end
|
|
325
366
|
|
|
326
367
|
def void_order_status(order, concept)
|