his_emr_api_lab 2.4.1 → 2.4.2.pre.beta
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/models/lab/lab_result.rb +3 -2
- data/app/serializers/lab/lab_order_serializer.rb +33 -10
- data/app/serializers/lab/result_serializer.rb +2 -2
- data/app/services/lab/lims/api/rest_api.rb +31 -9
- data/app/services/lab/lims/order_dto.rb +4 -0
- data/app/services/lab/lims/order_serializer.rb +3 -16
- data/app/services/lab/lims/pull_worker.rb +21 -5
- data/app/services/lab/order_location_resolver.rb +143 -0
- data/app/services/lab/orders_service.rb +43 -9
- data/app/services/lab/results_service.rb +23 -15
- data/app/services/lab/tests_service.rb +6 -0
- data/lib/lab/version.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4c709c42cf59eed483429321c58ddf08678dc809850c2d59cb58ca8d8bf4f3c3
|
|
4
|
+
data.tar.gz: 3f54225c34b8547665dfa5d553971e7d0b299d6744496d9e457184f3c7c105b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c53d18764e2dd4d99e4881855778acec6c8a2852744eb8bba144aea85249d8a0d5555f6c6e85773333367439606fa8652104d14169cb0290964bd308c23f5e7a
|
|
7
|
+
data.tar.gz: 45a1847d5591bbd4e352d15d320651cbaa2b2d774b5752cc17caceb8416a3fa56c505efd230b3c4635bb334c4d2bd3d2e885de5996324e94da1f5a82d01260c6
|
|
@@ -16,8 +16,9 @@ module Lab
|
|
|
16
16
|
|
|
17
17
|
belongs_to :test,
|
|
18
18
|
(lambda do
|
|
19
|
-
where
|
|
20
|
-
|
|
19
|
+
unscope(where: :location_id)
|
|
20
|
+
.where(concept: ConceptName.where(name: Lab::Metadata::TEST_TYPE_CONCEPT_NAME)
|
|
21
|
+
.select(:concept_id))
|
|
21
22
|
end),
|
|
22
23
|
class_name: 'Observation',
|
|
23
24
|
foreign_key: :obs_group_id
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../../services/lab/order_location_resolver'
|
|
4
|
+
|
|
3
5
|
module Lab
|
|
4
6
|
module LabOrderSerializer
|
|
5
7
|
def self.serialize_order(order, tests: nil, requesting_clinician: nil, reason_for_test: nil, target_lab: nil, comment_to_fulfiller: nil)
|
|
6
|
-
tests ||= order.voided
|
|
7
|
-
requesting_clinician ||= order
|
|
8
|
-
comment_to_fulfiller ||= order
|
|
9
|
-
reason_for_test ||= order
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
tests ||= [1, true].include?(order.voided) ? voided_tests(order) : order_tests(order)
|
|
9
|
+
requesting_clinician ||= order_observation(order, Lab::Metadata::REQUESTING_CLINICIAN_CONCEPT_NAME)
|
|
10
|
+
comment_to_fulfiller ||= order_observation(order, Lab::Metadata::COMMENT_TO_FULFILLER_CONCEPT_NAME)
|
|
11
|
+
reason_for_test ||= order_observation(order, Lab::Metadata::REASON_FOR_TEST_CONCEPT_NAME)
|
|
12
|
+
|
|
13
|
+
encounter = Encounter.unscoped.find_by_encounter_id(order.encounter_id)
|
|
14
|
+
location = Lab::OrderLocationResolver.location_for_order(order, fallback_location_id: encounter&.location_id)
|
|
15
|
+
target_lab = target_lab&.value_text ||
|
|
16
|
+
order_observation(order, Lab::Metadata::TARGET_LAB_CONCEPT_NAME)&.value_text ||
|
|
17
|
+
location&.name ||
|
|
18
|
+
Location.current_health_center&.name
|
|
13
19
|
program = Program.find_by_program_id(encounter&.program_id)
|
|
14
20
|
|
|
15
21
|
ActiveSupport::HashWithIndifferentAccess.new(
|
|
@@ -20,7 +26,7 @@ module Lab
|
|
|
20
26
|
encounter_id: order.encounter_id,
|
|
21
27
|
**(Encounter.column_names.include?('visit_id') ? { visit_id: encounter&.visit_id } : {}),
|
|
22
28
|
order_date: order.start_date,
|
|
23
|
-
location_id:
|
|
29
|
+
location_id: location&.location_id,
|
|
24
30
|
program_id: encounter&.program_id,
|
|
25
31
|
program_name: program&.name,
|
|
26
32
|
patient_id: order.patient_id,
|
|
@@ -59,8 +65,10 @@ module Lab
|
|
|
59
65
|
|
|
60
66
|
def self.test_method(order, _concept_id)
|
|
61
67
|
obs = ::Observation
|
|
68
|
+
.unscoped
|
|
62
69
|
.select(:value_coded)
|
|
63
|
-
.where(concept_id: ConceptName.find_by_name(Metadata::TEST_METHOD_CONCEPT_NAME).concept_id, order_id: order.
|
|
70
|
+
.where(concept_id: ConceptName.find_by_name(Metadata::TEST_METHOD_CONCEPT_NAME).concept_id, order_id: order.order_id)
|
|
71
|
+
.where(voided: 0)
|
|
64
72
|
.first
|
|
65
73
|
{
|
|
66
74
|
concept_id: obs&.value_coded,
|
|
@@ -75,10 +83,25 @@ module Lab
|
|
|
75
83
|
::ConceptName.find_by_concept_id(concept_id)&.name
|
|
76
84
|
end
|
|
77
85
|
|
|
86
|
+
def self.order_tests(order)
|
|
87
|
+
concept = ConceptName.where(name: Lab::Metadata::TEST_TYPE_CONCEPT_NAME)
|
|
88
|
+
.select(:concept_id)
|
|
89
|
+
LabTest.unscoped.where(concept_id: concept, order_id: order.order_id, voided: 0)
|
|
90
|
+
.order(:date_created, :obs_id)
|
|
91
|
+
end
|
|
92
|
+
|
|
78
93
|
def self.voided_tests(order)
|
|
79
94
|
concept = ConceptName.where(name: Lab::Metadata::TEST_TYPE_CONCEPT_NAME)
|
|
80
95
|
.select(:concept_id)
|
|
81
|
-
LabTest.unscoped.where(concept
|
|
96
|
+
LabTest.unscoped.where(concept_id: concept, order_id: order.order_id, voided: 1)
|
|
97
|
+
.order(:date_voided, :obs_id)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def self.order_observation(order, concept_name)
|
|
101
|
+
concept = ConceptName.where(name: concept_name).select(:concept_id)
|
|
102
|
+
Observation.unscoped.where(order_id: order.order_id, concept_id: concept, voided: 0)
|
|
103
|
+
.order(:date_created, :obs_id)
|
|
104
|
+
.first
|
|
82
105
|
end
|
|
83
106
|
|
|
84
107
|
def self.latest_order_status(order)
|
|
@@ -11,8 +11,8 @@ module Lab
|
|
|
11
11
|
concept_name = get_test_catalog_concept_name(measure.concept_id)
|
|
12
12
|
program_id = ''
|
|
13
13
|
if measure.obs_id.present?
|
|
14
|
-
obs = Observation.
|
|
15
|
-
encounter = Encounter.find_by(encounter_id: obs&.encounter_id)
|
|
14
|
+
obs = Observation.unscoped.find_by(obs_id: measure.obs_id)
|
|
15
|
+
encounter = Encounter.unscoped.find_by(encounter_id: obs&.encounter_id)
|
|
16
16
|
program_id = encounter&.program_id
|
|
17
17
|
end
|
|
18
18
|
|
|
@@ -19,11 +19,12 @@ module Lab
|
|
|
19
19
|
def create_order(order_dto)
|
|
20
20
|
response = in_authenticated_session do |headers|
|
|
21
21
|
Rails.logger.info("Pushing order ##{order_dto[:tracking_number]} to LIMS")
|
|
22
|
+
payload = make_create_params(order_dto)
|
|
22
23
|
if order_dto['sample_type'].casecmp?('not_specified')
|
|
23
|
-
RestClient.post(expand_uri('orders/requests', api_version: 'v2'),
|
|
24
|
-
headers)
|
|
24
|
+
RestClient.post(expand_uri('orders/requests', api_version: 'v2'), payload.to_json,
|
|
25
|
+
json_headers(headers))
|
|
25
26
|
else
|
|
26
|
-
RestClient.post(expand_uri('orders', api_version: 'v2'),
|
|
27
|
+
RestClient.post(expand_uri('orders', api_version: 'v2'), payload.to_json, json_headers(headers))
|
|
27
28
|
end
|
|
28
29
|
end
|
|
29
30
|
|
|
@@ -54,7 +55,7 @@ module Lab
|
|
|
54
55
|
date_acknowledged: acknowledgement_dto[:date_acknowledged],
|
|
55
56
|
recipient_type: acknowledgement_dto[:recipient_type],
|
|
56
57
|
acknowledged_by: 'emr_at_facility'
|
|
57
|
-
}, headers)
|
|
58
|
+
}.to_json, json_headers(headers))
|
|
58
59
|
end
|
|
59
60
|
Rails.logger.info("Acknowledged order ##{acknowledgement_dto} in LIMS. Response: #{response}")
|
|
60
61
|
JSON.parse(response)
|
|
@@ -66,7 +67,7 @@ module Lab
|
|
|
66
67
|
def update_order(_id, order_dto)
|
|
67
68
|
in_authenticated_session do |headers|
|
|
68
69
|
RestClient.put(expand_uri("orders/#{order_dto[:tracking_number]}", api_version: 'v2'),
|
|
69
|
-
make_update_params(order_dto), headers)
|
|
70
|
+
make_update_params(order_dto).to_json, json_headers(headers))
|
|
70
71
|
end
|
|
71
72
|
|
|
72
73
|
update_order_results(order_dto)
|
|
@@ -176,7 +177,8 @@ module Lab
|
|
|
176
177
|
in_authenticated_session do |headers|
|
|
177
178
|
date_voided, voided_status = find_test_status(order_dto, test.name, 'Voided')
|
|
178
179
|
params = make_void_test_params(tracking_number, test, voided_status['updated_by'], date_voided)
|
|
179
|
-
RestClient.put(expand_uri("tests/#{tracking_number}", api_version: 'v2'), params.to_json,
|
|
180
|
+
RestClient.put(expand_uri("tests/#{tracking_number}", api_version: 'v2'), params.to_json,
|
|
181
|
+
json_headers(headers))
|
|
180
182
|
end
|
|
181
183
|
end
|
|
182
184
|
end
|
|
@@ -277,6 +279,10 @@ module Lab
|
|
|
277
279
|
raise InvalidParameters, body['message']
|
|
278
280
|
end
|
|
279
281
|
|
|
282
|
+
def json_headers(headers)
|
|
283
|
+
headers.merge('Content-Type' => 'application/json', 'Accept' => 'application/json')
|
|
284
|
+
end
|
|
285
|
+
|
|
280
286
|
##
|
|
281
287
|
# Takes a LIMS API relative URI and converts it to a full URL.
|
|
282
288
|
def expand_uri(uri, api_version: 'v1')
|
|
@@ -328,18 +334,33 @@ module Lab
|
|
|
328
334
|
date_of_birth: order_dto.fetch(:patient).fetch(:dob)
|
|
329
335
|
},
|
|
330
336
|
tests: order_dto.fetch(:tests_map).map do |test|
|
|
331
|
-
concept =
|
|
337
|
+
concept = test_type_concept(test)
|
|
332
338
|
{
|
|
333
339
|
test_type: {
|
|
334
340
|
name: concept.test_catalogue_name,
|
|
335
341
|
nlims_code: concept.nlims_code,
|
|
336
342
|
method_of_testing: test&.test_method&.name
|
|
337
|
-
}
|
|
343
|
+
}.compact
|
|
338
344
|
}
|
|
339
345
|
end
|
|
340
346
|
}
|
|
341
347
|
end
|
|
342
348
|
|
|
349
|
+
def test_type_concept(test)
|
|
350
|
+
concept = ::Concept.find(test.concept_id)
|
|
351
|
+
return concept if concept.nlims_code.to_s.match?(/\ANLIMS_TT_/)
|
|
352
|
+
|
|
353
|
+
parent_test_types = ConceptSet.where(concept_id: concept.concept_id).filter_map do |concept_set|
|
|
354
|
+
parent = ::Concept.find_by(concept_id: concept_set.concept_set)
|
|
355
|
+
parent if parent&.nlims_code.to_s.match?(/\ANLIMS_TT_/)
|
|
356
|
+
end.uniq(&:concept_id)
|
|
357
|
+
|
|
358
|
+
return parent_test_types.first if parent_test_types.one?
|
|
359
|
+
|
|
360
|
+
Rails.logger.warn("Could not resolve test type for concept ##{concept.concept_id} (#{concept.test_catalogue_name})")
|
|
361
|
+
concept
|
|
362
|
+
end
|
|
363
|
+
|
|
343
364
|
##
|
|
344
365
|
# Converts an OrderDto to parameters for POST /update_order
|
|
345
366
|
def make_update_params(order_dto)
|
|
@@ -506,7 +527,8 @@ module Lab
|
|
|
506
527
|
in_authenticated_session do |headers|
|
|
507
528
|
params = make_update_test_params(order_dto, test_name, results)
|
|
508
529
|
|
|
509
|
-
RestClient.put(expand_uri("tests/#{order_dto['tracking_number']}", api_version: 'v2'), params,
|
|
530
|
+
RestClient.put(expand_uri("tests/#{order_dto['tracking_number']}", api_version: 'v2'), params.to_json,
|
|
531
|
+
json_headers(headers))
|
|
510
532
|
end
|
|
511
533
|
end
|
|
512
534
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'exceptions'
|
|
4
|
+
require_relative '../order_location_resolver'
|
|
4
5
|
|
|
5
6
|
module Lab
|
|
6
7
|
module Lims
|
|
@@ -12,10 +13,13 @@ module Lab
|
|
|
12
13
|
##
|
|
13
14
|
# Unpacks a LIMS order into an object that OrdersService can handle
|
|
14
15
|
def to_order_service_params(patient_id:)
|
|
16
|
+
location = Lab::OrderLocationResolver.location_from_name(facility_name(self['sending_facility'])) ||
|
|
17
|
+
Lab::OrderLocationResolver.location_from_name(self['order_location'])
|
|
15
18
|
ActiveSupport::HashWithIndifferentAccess.new(
|
|
16
19
|
program_id: lab_program.program_id,
|
|
17
20
|
accession_number: self['tracking_number'],
|
|
18
21
|
patient_id:,
|
|
22
|
+
location_id: location&.location_id,
|
|
19
23
|
specimen: { concept_id: specimen_type_id },
|
|
20
24
|
tests: self['tests_map']&.map { |test| { concept_id: test.concept_id } },
|
|
21
25
|
requesting_clinician:,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require_relative 'config'
|
|
4
4
|
require_relative 'order_dto'
|
|
5
5
|
require_relative 'utils'
|
|
6
|
+
require_relative '../order_location_resolver'
|
|
6
7
|
|
|
7
8
|
module Lab
|
|
8
9
|
module Lims
|
|
@@ -46,21 +47,7 @@ module Lab
|
|
|
46
47
|
private
|
|
47
48
|
|
|
48
49
|
def get_location(location_id, order)
|
|
49
|
-
|
|
50
|
-
location = Location.find_by(location_id: location_id) if location_id.present?
|
|
51
|
-
|
|
52
|
-
# Fallback to current health center
|
|
53
|
-
location ||= Location.current_health_center
|
|
54
|
-
|
|
55
|
-
# Last fallback: try to get from order's observation encounter
|
|
56
|
-
# Use unscoped to find observations/encounters across all locations
|
|
57
|
-
if location.nil? && order.present?
|
|
58
|
-
obs = Observation.unscoped.find_by(order_id: order.order_id)
|
|
59
|
-
if obs.respond_to?(:encounter) && obs.encounter.respond_to?(:location)
|
|
60
|
-
encounter = Encounter.unscoped.find_by(encounter_id: obs.encounter_id)
|
|
61
|
-
location = encounter&.location
|
|
62
|
-
end
|
|
63
|
-
end
|
|
50
|
+
location = Lab::OrderLocationResolver.location_for_order(order, fallback_location_id: location_id)
|
|
64
51
|
|
|
65
52
|
raise 'Current health center not set' unless location
|
|
66
53
|
|
|
@@ -161,7 +148,7 @@ module Lab
|
|
|
161
148
|
end
|
|
162
149
|
|
|
163
150
|
def format_test_status_trail(order)
|
|
164
|
-
tests = [0, false].include?(order.voided) ? order
|
|
151
|
+
tests = [0, false].include?(order.voided) ? Lab::LabOrderSerializer.order_tests(order) : Lab::LabOrderSerializer.voided_tests(order)
|
|
165
152
|
tests.each_with_object({}) do |test, trail|
|
|
166
153
|
test_name = format_test_name(::Concept.find(test.value_coded).test_catalogue_name)
|
|
167
154
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../order_location_resolver'
|
|
4
|
+
|
|
3
5
|
module Lab
|
|
4
6
|
module Lims
|
|
5
7
|
##
|
|
@@ -191,7 +193,9 @@ module Lab
|
|
|
191
193
|
|
|
192
194
|
def create_order(patient, order_dto)
|
|
193
195
|
logger.debug("Creating order ##{order_dto['_id']}")
|
|
194
|
-
|
|
196
|
+
params = order_dto.to_order_service_params(patient_id: patient.patient_id)
|
|
197
|
+
params[:location_id] ||= location_id_for_order_dto(order_dto)
|
|
198
|
+
order = OrdersService.order_test(params)
|
|
195
199
|
|
|
196
200
|
# Extract and save status trails from NLIMS
|
|
197
201
|
save_status_trails_from_nlims(order, order_dto)
|
|
@@ -204,8 +208,9 @@ module Lab
|
|
|
204
208
|
|
|
205
209
|
def update_order(patient, order_id, order_dto)
|
|
206
210
|
logger.debug("Updating order ##{order_dto['_id']}")
|
|
207
|
-
|
|
208
|
-
|
|
211
|
+
params = order_dto.to_order_service_params(patient_id: patient.patient_id)
|
|
212
|
+
params[:location_id] ||= Lab::OrderLocationResolver.location_id_for_order_id(order_id, facility_name: order_dto[:sending_facility])
|
|
213
|
+
order = OrdersService.update_order(order_id, params.merge(force_update: 'true'))
|
|
209
214
|
|
|
210
215
|
# Extract and save status trails from NLIMS
|
|
211
216
|
save_status_trails_from_nlims(order, order_dto)
|
|
@@ -245,6 +250,7 @@ module Lab
|
|
|
245
250
|
ResultsService.create_results(test.id, { provider_id: User.current.person_id,
|
|
246
251
|
date: Utils.parse_date(test_results['result_date'] || result_date,
|
|
247
252
|
order[:order_date].to_s),
|
|
253
|
+
location_id: Lab::OrderLocationResolver.location_id_for_test(test),
|
|
248
254
|
comments: "LIMS import: Entered by: #{creator}",
|
|
249
255
|
measures: })
|
|
250
256
|
end
|
|
@@ -255,7 +261,13 @@ module Lab
|
|
|
255
261
|
test_concept = Utils.find_concept_by_name(test_name)
|
|
256
262
|
raise "Unknown test name, #{test_name}!" unless test_concept
|
|
257
263
|
|
|
258
|
-
LabTest.find_by(order_id:, value_coded: test_concept.concept_id)
|
|
264
|
+
LabTest.unscoped.find_by(order_id:, value_coded: test_concept.concept_id, voided: 0)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def location_id_for_order_dto(order_dto)
|
|
268
|
+
local_order = Lab::LabOrder.unscoped.find_by(accession_number: order_dto[:tracking_number])
|
|
269
|
+
|
|
270
|
+
Lab::OrderLocationResolver.location_id_for_order(local_order, facility_name: order_dto[:sending_facility])
|
|
259
271
|
end
|
|
260
272
|
|
|
261
273
|
def find_measure(_order, indicator_name, value)
|
|
@@ -342,6 +354,7 @@ module Lab
|
|
|
342
354
|
logger.error("Order not found: #{order_id}")
|
|
343
355
|
return
|
|
344
356
|
end
|
|
357
|
+
location_id = Lab::OrderLocationResolver.location_id_for_order(lab_order)
|
|
345
358
|
|
|
346
359
|
# sample_statuses is an array of single-key hashes like:
|
|
347
360
|
# [{ "20260225120000" => { "status" => "Drawn", ... } }, { "20260225130000" => { ... } }]
|
|
@@ -383,6 +396,7 @@ module Lab
|
|
|
383
396
|
obs_datetime: timestamp,
|
|
384
397
|
comments: updated_by.to_json,
|
|
385
398
|
creator: User.current&.user_id || 1,
|
|
399
|
+
location_id:,
|
|
386
400
|
date_created: Time.now,
|
|
387
401
|
uuid: SecureRandom.uuid
|
|
388
402
|
)
|
|
@@ -412,8 +426,9 @@ module Lab
|
|
|
412
426
|
test_concept = Utils.find_concept_by_name(Utils.translate_test_name(test_name))
|
|
413
427
|
next unless test_concept
|
|
414
428
|
|
|
415
|
-
test = Lab::LabTest.find_by(order_id: order['order_id'], value_coded: test_concept.concept_id)
|
|
429
|
+
test = Lab::LabTest.unscoped.find_by(order_id: order['order_id'], value_coded: test_concept.concept_id, voided: 0)
|
|
416
430
|
next unless test
|
|
431
|
+
location_id = Lab::OrderLocationResolver.location_id_for_test(test)
|
|
417
432
|
|
|
418
433
|
# Process each status in the trail
|
|
419
434
|
statuses.each do |timestamp_key, status_data|
|
|
@@ -449,6 +464,7 @@ module Lab
|
|
|
449
464
|
obs_datetime: timestamp,
|
|
450
465
|
comments: updated_by.to_json,
|
|
451
466
|
creator: User.current&.user_id || 1,
|
|
467
|
+
location_id:,
|
|
452
468
|
date_created: Time.now,
|
|
453
469
|
uuid: SecureRandom.uuid
|
|
454
470
|
)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'metadata'
|
|
4
|
+
|
|
5
|
+
module Lab
|
|
6
|
+
# Resolves the location that should be used for lab sync records.
|
|
7
|
+
#
|
|
8
|
+
# LIMS workers often run as lab_daemon, whose current location can differ from
|
|
9
|
+
# the clinician/order location. Order-linked observations are the most reliable
|
|
10
|
+
# source because they carry the location where the order was created.
|
|
11
|
+
module OrderLocationResolver
|
|
12
|
+
ORDER_OBSERVATION_CONCEPT_NAMES = [
|
|
13
|
+
Lab::Metadata::TEST_TYPE_CONCEPT_NAME,
|
|
14
|
+
Lab::Metadata::TEST_METHOD_CONCEPT_NAME,
|
|
15
|
+
Lab::Metadata::REQUESTING_CLINICIAN_CONCEPT_NAME,
|
|
16
|
+
Lab::Metadata::REASON_FOR_TEST_CONCEPT_NAME,
|
|
17
|
+
Lab::Metadata::TARGET_LAB_CONCEPT_NAME,
|
|
18
|
+
Lab::Metadata::COMMENT_TO_FULFILLER_CONCEPT_NAME
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
def location_for_order(order, fallback_location_id: nil, facility_name: nil)
|
|
24
|
+
observation_location_for_order(order) ||
|
|
25
|
+
location_by_id(fallback_location_id || record_location_id(order)) ||
|
|
26
|
+
encounter_location_for_order(order) ||
|
|
27
|
+
creator_location_for_order(order) ||
|
|
28
|
+
location_from_name(facility_name) ||
|
|
29
|
+
Location.current_health_center
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def location_id_for_order(order, fallback_location_id: nil, facility_name: nil)
|
|
33
|
+
location_for_order(order, fallback_location_id:, facility_name:)&.location_id
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def location_for_order_id(order_id, fallback_location_id: nil, facility_name: nil)
|
|
37
|
+
order = Lab::LabOrder.unscoped.find_by(order_id:)
|
|
38
|
+
|
|
39
|
+
location_for_order(order, fallback_location_id:, facility_name:)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def location_id_for_order_id(order_id, fallback_location_id: nil, facility_name: nil)
|
|
43
|
+
location_for_order_id(order_id, fallback_location_id:, facility_name:)&.location_id
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def location_for_test(test)
|
|
47
|
+
location_for_order_id(record_order_id(test), fallback_location_id: record_location_id(test)) ||
|
|
48
|
+
location_by_id(record_location_id(test)) ||
|
|
49
|
+
location_by_id(encounter_location_id_for_record(test))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def location_id_for_test(test)
|
|
53
|
+
location_for_test(test)&.location_id
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def location_from_name(name)
|
|
57
|
+
name = name.to_s.strip
|
|
58
|
+
return nil if name.blank? || name.casecmp?('Unknown') || name.casecmp?('not_assigned')
|
|
59
|
+
|
|
60
|
+
Location.unscoped.where('LOWER(name) = ?', name.downcase).first
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def location_by_id(location_id)
|
|
64
|
+
return nil if location_id.blank? || location_id.to_i.zero?
|
|
65
|
+
|
|
66
|
+
Location.unscoped.find_by(location_id:)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def observation_location_for_order(order)
|
|
70
|
+
order_id = record_order_id(order)
|
|
71
|
+
return nil if order_id.blank?
|
|
72
|
+
|
|
73
|
+
location_id = prioritized_order_observations(order_id).first&.location_id
|
|
74
|
+
location_by_id(location_id)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def encounter_location_for_order(order)
|
|
78
|
+
location_by_id(encounter_location_id_for_record(order))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def creator_location_for_order(order)
|
|
82
|
+
creator_id = record_creator_id(order)
|
|
83
|
+
return nil if creator_id.blank?
|
|
84
|
+
|
|
85
|
+
user = User.unscoped.find_by(user_id: creator_id)
|
|
86
|
+
location_by_id(user&.location_id)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def prioritized_order_observations(order_id)
|
|
90
|
+
scope = Observation.unscoped.where(order_id:, voided: 0).where.not(location_id: [nil, 0])
|
|
91
|
+
concept_ids = order_location_concept_ids
|
|
92
|
+
return scope.order(:date_created, :obs_id) if concept_ids.blank?
|
|
93
|
+
|
|
94
|
+
quoted_ids = concept_ids.map(&:to_i).join(',')
|
|
95
|
+
scope.order(Arel.sql("CASE WHEN concept_id IN (#{quoted_ids}) THEN 0 ELSE 1 END, date_created ASC, obs_id ASC"))
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def order_location_concept_ids
|
|
99
|
+
ConceptName.where(name: ORDER_OBSERVATION_CONCEPT_NAMES).pluck(:concept_id)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def encounter_location_id_for_record(record)
|
|
103
|
+
encounter_id = record_encounter_id(record)
|
|
104
|
+
return nil if encounter_id.blank?
|
|
105
|
+
|
|
106
|
+
Encounter.unscoped.find_by(encounter_id:)&.location_id
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def record_order_id(record)
|
|
110
|
+
return nil unless record
|
|
111
|
+
return record.order_id if record.respond_to?(:order_id)
|
|
112
|
+
|
|
113
|
+
value_from_hash(record, :order_id) || value_from_hash(record, :id)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def record_encounter_id(record)
|
|
117
|
+
return nil unless record
|
|
118
|
+
return record.encounter_id if record.respond_to?(:encounter_id)
|
|
119
|
+
|
|
120
|
+
value_from_hash(record, :encounter_id)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def record_location_id(record)
|
|
124
|
+
return nil unless record
|
|
125
|
+
return record.location_id if record.respond_to?(:location_id)
|
|
126
|
+
|
|
127
|
+
value_from_hash(record, :location_id)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def record_creator_id(record)
|
|
131
|
+
return nil unless record
|
|
132
|
+
return record.creator if record.respond_to?(:creator)
|
|
133
|
+
|
|
134
|
+
value_from_hash(record, :creator)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def value_from_hash(record, key)
|
|
138
|
+
return nil unless record.respond_to?(:[])
|
|
139
|
+
|
|
140
|
+
record[key] || record[key.to_s]
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'order_location_resolver'
|
|
4
|
+
|
|
3
5
|
module Lab
|
|
4
6
|
##
|
|
5
7
|
# Manage lab orders.
|
|
@@ -163,6 +165,7 @@ module Lab
|
|
|
163
165
|
# find the order
|
|
164
166
|
order = find_order(order_params['tracking_number'])
|
|
165
167
|
concept = ConceptName.find_by_name Lab::Metadata::LAB_ORDER_STATUS_CONCEPT_NAME
|
|
168
|
+
location_id = Lab::OrderLocationResolver.location_id_for_order(order)
|
|
166
169
|
ActiveRecord::Base.transaction do
|
|
167
170
|
void_order_status(order, concept)
|
|
168
171
|
Observation.create!(
|
|
@@ -172,7 +175,8 @@ module Lab
|
|
|
172
175
|
order_id: order.id,
|
|
173
176
|
obs_datetime: order_params['status_time'] || Time.now,
|
|
174
177
|
value_text: order_params['status'],
|
|
175
|
-
creator: User.current.id
|
|
178
|
+
creator: User.current.id,
|
|
179
|
+
location_id:
|
|
176
180
|
)
|
|
177
181
|
|
|
178
182
|
# Save order status trail if available
|
|
@@ -312,9 +316,14 @@ module Lab
|
|
|
312
316
|
encounter_id = order_params[:encounter_id] || order_params[:encounter]
|
|
313
317
|
patient_id = order_params[:patient_id] || order_params[:patient]
|
|
314
318
|
visit = order_params[:visit]
|
|
319
|
+
location_id = location_id_from_order_params(order_params)
|
|
315
320
|
|
|
316
|
-
|
|
317
|
-
|
|
321
|
+
if order_params[:encounter] || order_params[:encounter_id]
|
|
322
|
+
encounter = Encounter.unscoped.find(encounter_id)
|
|
323
|
+
encounter.update!(location_id:) if location_id.present? && encounter.respond_to?(:location_id) && encounter.location_id.blank?
|
|
324
|
+
return encounter
|
|
325
|
+
end
|
|
326
|
+
raise StandardError, 'encounter_id|uuid or patient_id|uuid required' unless patient_id
|
|
318
327
|
|
|
319
328
|
encounter = Encounter.new
|
|
320
329
|
encounter.patient = Patient.find(patient_id)
|
|
@@ -325,6 +334,7 @@ module Lab
|
|
|
325
334
|
if Encounter.column_names.include?('program_id') && order_params[:program_id].present?
|
|
326
335
|
encounter.program_id = order_params[:program_id]
|
|
327
336
|
end
|
|
337
|
+
encounter.location_id = location_id if Encounter.column_names.include?('location_id') && location_id.present?
|
|
328
338
|
encounter.save!
|
|
329
339
|
encounter.reload
|
|
330
340
|
end
|
|
@@ -433,6 +443,7 @@ module Lab
|
|
|
433
443
|
# Use unscoped to find user regardless of location context
|
|
434
444
|
creator = User.unscoped.find_by(username: 'lab_daemon')
|
|
435
445
|
User.current ||= creator
|
|
446
|
+
values[:location_id] ||= Lab::OrderLocationResolver.location_id_for_order(order)
|
|
436
447
|
Observation.create!(
|
|
437
448
|
order:,
|
|
438
449
|
encounter_id: order.encounter_id,
|
|
@@ -454,25 +465,42 @@ module Lab
|
|
|
454
465
|
def update_reason_for_test(order, concept_id, force_update: false)
|
|
455
466
|
raise InvalidParameterError, "Reason for test can't be blank" if concept_id.blank?
|
|
456
467
|
|
|
457
|
-
|
|
468
|
+
current_reason_for_test = order_observation(order, Lab::Metadata::REASON_FOR_TEST_CONCEPT_NAME)
|
|
469
|
+
return if current_reason_for_test&.value_coded == concept_id
|
|
458
470
|
|
|
459
|
-
if
|
|
471
|
+
if current_reason_for_test&.value_coded && !force_update
|
|
460
472
|
raise InvalidParameterError,
|
|
461
473
|
"Can't change reason for test once set"
|
|
462
474
|
end
|
|
463
475
|
|
|
464
|
-
|
|
476
|
+
current_reason_for_test&.delete
|
|
465
477
|
date = order.start_date if order.respond_to?(:start_date)
|
|
466
478
|
date ||= order.date_created
|
|
467
479
|
add_reason_for_test(order, date: date, reason_for_test_id: concept_id)
|
|
468
480
|
end
|
|
469
481
|
|
|
470
482
|
def void_order_status(order, concept)
|
|
471
|
-
Observation.where(order_id: order.id, concept_id: concept.concept_id).each do |obs|
|
|
483
|
+
Observation.unscoped.where(order_id: order.id, concept_id: concept.concept_id).each do |obs|
|
|
472
484
|
obs.void('New Status Received from LIMS')
|
|
473
485
|
end
|
|
474
486
|
end
|
|
475
487
|
|
|
488
|
+
def location_id_from_order_params(order_params)
|
|
489
|
+
order_params[:location_id] ||
|
|
490
|
+
order_params[:order_location_id] ||
|
|
491
|
+
Lab::OrderLocationResolver.location_from_name(order_params[:order_location])&.location_id ||
|
|
492
|
+
Lab::OrderLocationResolver.location_from_name(order_params['order_location'])&.location_id ||
|
|
493
|
+
Lab::OrderLocationResolver.location_from_name(order_params[:target_lab])&.location_id ||
|
|
494
|
+
Lab::OrderLocationResolver.location_from_name(order_params['target_lab'])&.location_id
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def order_observation(order, concept_name)
|
|
498
|
+
concept = ConceptName.where(name: concept_name).select(:concept_id)
|
|
499
|
+
Observation.unscoped.where(order_id: order.order_id, concept_id: concept, voided: 0)
|
|
500
|
+
.order(:date_created, :obs_id)
|
|
501
|
+
.first
|
|
502
|
+
end
|
|
503
|
+
|
|
476
504
|
def create_initial_order_status_trail(order)
|
|
477
505
|
create_order_status_observation(
|
|
478
506
|
order: order,
|
|
@@ -525,8 +553,8 @@ module Lab
|
|
|
525
553
|
concept = Lab::Lims::Utils.find_concept_by_name(test_name)
|
|
526
554
|
next unless concept
|
|
527
555
|
|
|
528
|
-
# Find the test observation
|
|
529
|
-
test =
|
|
556
|
+
# Find the test observation without the daemon's current-location scope.
|
|
557
|
+
test = Lab::LabTest.unscoped.find_by(order_id: order.order_id, value_coded: concept.concept_id, voided: 0)
|
|
530
558
|
next unless test
|
|
531
559
|
|
|
532
560
|
# Save each status trail entry
|
|
@@ -561,6 +589,8 @@ module Lab
|
|
|
561
589
|
voided: 0
|
|
562
590
|
)
|
|
563
591
|
|
|
592
|
+
location_id = Lab::OrderLocationResolver.location_id_for_order(order)
|
|
593
|
+
|
|
564
594
|
# Create status observation
|
|
565
595
|
Observation.create!(
|
|
566
596
|
person_id: order.patient_id,
|
|
@@ -571,6 +601,7 @@ module Lab
|
|
|
571
601
|
obs_datetime: timestamp,
|
|
572
602
|
comments: updated_by.to_json,
|
|
573
603
|
creator: User.current&.user_id || 1,
|
|
604
|
+
location_id:,
|
|
574
605
|
date_created: Time.now,
|
|
575
606
|
uuid: SecureRandom.uuid
|
|
576
607
|
)
|
|
@@ -599,6 +630,8 @@ module Lab
|
|
|
599
630
|
voided: 0
|
|
600
631
|
)
|
|
601
632
|
|
|
633
|
+
location_id = Lab::OrderLocationResolver.location_id_for_test(test)
|
|
634
|
+
|
|
602
635
|
# Create status observation
|
|
603
636
|
Observation.create!(
|
|
604
637
|
person_id: test.person_id,
|
|
@@ -609,6 +642,7 @@ module Lab
|
|
|
609
642
|
obs_datetime: timestamp,
|
|
610
643
|
comments: updated_by.to_json,
|
|
611
644
|
creator: User.current&.user_id || 1,
|
|
645
|
+
location_id:,
|
|
612
646
|
date_created: Time.now,
|
|
613
647
|
uuid: SecureRandom.uuid
|
|
614
648
|
)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'order_location_resolver'
|
|
4
|
+
|
|
3
5
|
module Lab
|
|
4
6
|
module ResultsService
|
|
5
7
|
class << self
|
|
@@ -20,16 +22,15 @@ module Lab
|
|
|
20
22
|
serializer = {}
|
|
21
23
|
results_obs = {}
|
|
22
24
|
ActiveRecord::Base.transaction do
|
|
23
|
-
test =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
end
|
|
28
|
-
test = Lab::LabTest.find_by_uuid(test_id) if test.blank?
|
|
25
|
+
test = Lab::LabTest.unscoped.find_by(obs_id: test_id)
|
|
26
|
+
test ||= Lab::LabTest.unscoped.find_by(uuid: test_id)
|
|
27
|
+
raise ActiveRecord::RecordNotFound, "Couldn't find Lab::LabTest with id=#{test_id}" unless test
|
|
28
|
+
|
|
29
29
|
encounter = find_encounter(test, encounter_id: params[:encounter_id],
|
|
30
30
|
encounter_uuid: params[:encounter],
|
|
31
31
|
date: params[:date]&.to_date,
|
|
32
|
-
provider_id: params[:provider_id]
|
|
32
|
+
provider_id: params[:provider_id],
|
|
33
|
+
location_id: params[:location_id])
|
|
33
34
|
|
|
34
35
|
results_obs = create_results_obs(encounter, test, params[:date], params[:comments])
|
|
35
36
|
params[:measures].map { |measure| add_measure_to_results(results_obs, measure, params[:date]) }
|
|
@@ -105,22 +106,25 @@ module Lab
|
|
|
105
106
|
.first&.identifier
|
|
106
107
|
end
|
|
107
108
|
|
|
108
|
-
def find_encounter(test, encounter_id: nil, encounter_uuid: nil, date: nil, provider_id: nil)
|
|
109
|
-
return Encounter.find(encounter_id) if encounter_id
|
|
110
|
-
return Encounter.find_by_uuid(encounter_uuid) if encounter_uuid
|
|
109
|
+
def find_encounter(test, encounter_id: nil, encounter_uuid: nil, date: nil, provider_id: nil, location_id: nil)
|
|
110
|
+
return Encounter.unscoped.find(encounter_id) if encounter_id
|
|
111
|
+
return Encounter.unscoped.find_by_uuid(encounter_uuid) if encounter_uuid
|
|
111
112
|
|
|
112
113
|
lab_encounter_type = EncounterType.find_by_name!(Lab::Metadata::ENCOUNTER_TYPE_NAME)
|
|
114
|
+
source_encounter = Encounter.unscoped.find_by(encounter_id: test.encounter_id)
|
|
115
|
+
location_id ||= Lab::OrderLocationResolver.location_id_for_test(test)
|
|
113
116
|
|
|
114
117
|
encounter = Encounter.new
|
|
115
118
|
encounter.patient_id = test.person_id
|
|
116
|
-
encounter.program_id =
|
|
117
|
-
encounter.visit_id =
|
|
119
|
+
encounter.program_id = source_encounter&.program_id if Encounter.column_names.include?('program_id')
|
|
120
|
+
encounter.visit_id = source_encounter&.visit_id if Encounter.column_names.include?('visit_id')
|
|
118
121
|
# Use bracket notation to set the encounter_type column directly (bypasses association)
|
|
119
122
|
# This handles both Integer and EncounterType object
|
|
120
123
|
encounter_type_value = lab_encounter_type.is_a?(Integer) ? lab_encounter_type : lab_encounter_type.encounter_type_id
|
|
121
124
|
encounter[:encounter_type] = encounter_type_value
|
|
122
125
|
encounter.encounter_datetime = date || Date.today
|
|
123
126
|
encounter.provider_id = provider_id || User.current.user_id if Encounter.column_names.include?('provider_id')
|
|
127
|
+
encounter.location_id = location_id if Encounter.column_names.include?('location_id') && location_id.present?
|
|
124
128
|
encounter.save!
|
|
125
129
|
encounter.reload
|
|
126
130
|
encounter
|
|
@@ -130,20 +134,23 @@ module Lab
|
|
|
130
134
|
def create_results_obs(encounter, test, date, comments = nil)
|
|
131
135
|
void_existing_results_obs(encounter, test)
|
|
132
136
|
Lab::LabResult.create!(
|
|
137
|
+
test:,
|
|
133
138
|
person_id: encounter.patient_id,
|
|
134
139
|
encounter_id: encounter.encounter_id,
|
|
135
140
|
concept_id: test_result_concept.concept_id,
|
|
136
141
|
order_id: test.order_id,
|
|
137
142
|
obs_group_id: test.obs_id,
|
|
143
|
+
location_id: encounter.location_id || Lab::OrderLocationResolver.location_id_for_test(test),
|
|
138
144
|
obs_datetime: date&.to_datetime || DateTime.now,
|
|
139
145
|
comments:
|
|
140
146
|
)
|
|
141
147
|
end
|
|
142
148
|
|
|
143
149
|
def void_existing_results_obs(encounter, test)
|
|
144
|
-
result = Lab::LabResult.find_by(person_id: encounter.patient_id,
|
|
145
|
-
|
|
146
|
-
|
|
150
|
+
result = Lab::LabResult.unscoped.find_by(person_id: encounter.patient_id,
|
|
151
|
+
concept_id: test_result_concept.concept_id,
|
|
152
|
+
obs_group_id: test.obs_id,
|
|
153
|
+
voided: 0)
|
|
147
154
|
return unless result
|
|
148
155
|
|
|
149
156
|
OrderExtension.find_by(order_id: result.order_id)&.void("Updated/overwritten by #{User.current.username}")
|
|
@@ -167,6 +174,7 @@ module Lab
|
|
|
167
174
|
order_id: results_obs.order_id,
|
|
168
175
|
concept_id: concept_id,
|
|
169
176
|
obs_group_id: results_obs.obs_id,
|
|
177
|
+
location_id: results_obs.location_id,
|
|
170
178
|
obs_datetime: date&.to_datetime || DateTime.now,
|
|
171
179
|
**make_measure_value(params)
|
|
172
180
|
)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'order_location_resolver'
|
|
4
|
+
|
|
3
5
|
module Lab
|
|
4
6
|
##
|
|
5
7
|
# Manage tests that have been ordered through the ordering service.
|
|
@@ -29,6 +31,7 @@ module Lab
|
|
|
29
31
|
raise InvalidParameterError, 'tests are required' if tests_params.nil? || tests_params.empty?
|
|
30
32
|
|
|
31
33
|
Lab::LabTest.transaction do
|
|
34
|
+
location_id = Lab::OrderLocationResolver.location_id_for_order(order)
|
|
32
35
|
tests_params.map do |params|
|
|
33
36
|
concept_id = params[:concept_id]
|
|
34
37
|
concept_id = Concept.find_concept_by_uuid(params[:concept]).id if concept_id.nil?
|
|
@@ -40,6 +43,7 @@ module Lab
|
|
|
40
43
|
order_id: order.order_id,
|
|
41
44
|
person_id: order.patient_id,
|
|
42
45
|
obs_datetime: date&.to_time || Time.now,
|
|
46
|
+
location_id:,
|
|
43
47
|
value_coded: concept_id
|
|
44
48
|
)
|
|
45
49
|
|
|
@@ -123,6 +127,7 @@ module Lab
|
|
|
123
127
|
)
|
|
124
128
|
|
|
125
129
|
# Create status observation with 'Drawn' as initial status
|
|
130
|
+
location_id = Lab::OrderLocationResolver.location_id_for_test(test)
|
|
126
131
|
Observation.create!(
|
|
127
132
|
person_id: test.person_id,
|
|
128
133
|
encounter_id: test.encounter_id,
|
|
@@ -137,6 +142,7 @@ module Lab
|
|
|
137
142
|
'phone_number' => nil
|
|
138
143
|
}.to_json,
|
|
139
144
|
creator: User.current&.user_id || 1,
|
|
145
|
+
location_id:,
|
|
140
146
|
date_created: Time.now,
|
|
141
147
|
uuid: SecureRandom.uuid
|
|
142
148
|
)
|
data/lib/lab/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: his_emr_api_lab
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.4.
|
|
4
|
+
version: 2.4.2.pre.beta
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Elizabeth Glaser Pediatric Foundation Malawi
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-07-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: couchrest
|
|
@@ -286,6 +286,7 @@ files:
|
|
|
286
286
|
- app/services/lab/lims/worker.rb
|
|
287
287
|
- app/services/lab/metadata.rb
|
|
288
288
|
- app/services/lab/notification_service.rb
|
|
289
|
+
- app/services/lab/order_location_resolver.rb
|
|
289
290
|
- app/services/lab/orders_search_service.rb
|
|
290
291
|
- app/services/lab/orders_service.rb
|
|
291
292
|
- app/services/lab/results_service.rb
|
|
@@ -341,9 +342,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
341
342
|
version: '0'
|
|
342
343
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
343
344
|
requirements:
|
|
344
|
-
- - "
|
|
345
|
+
- - ">"
|
|
345
346
|
- !ruby/object:Gem::Version
|
|
346
|
-
version:
|
|
347
|
+
version: 1.3.1
|
|
347
348
|
requirements: []
|
|
348
349
|
rubygems_version: 3.4.1
|
|
349
350
|
signing_key:
|