his_emr_api_lab 2.1.8 → 2.1.9.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/README.md +4 -17
- data/app/jobs/lab/process_lab_result_job.rb +5 -2
- data/app/jobs/lab/update_patient_orders_job.rb +50 -1
- data/app/jobs/lab/void_order_job.rb +6 -2
- data/app/serializers/lab/lab_order_serializer.rb +2 -1
- data/app/services/lab/concepts_service.rb +12 -7
- data/app/services/lab/lims/api/rest_api.rb +31 -38
- data/app/services/lab/lims/order_serializer.rb +39 -29
- data/app/services/lab/lims/utils.rb +2 -1
- data/app/services/lab/notification_service.rb +2 -1
- data/app/services/lab/orders_service.rb +5 -4
- data/app/services/lab/results_service.rb +1 -1
- data/app/services/lab/user_service.rb +3 -2
- data/lib/lab/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c6c20f1ba07edd60ba5795eaaef1ca3f34395060b1f9ed0160a5513777d657d0
|
|
4
|
+
data.tar.gz: d16b695b3592233474fa9bbc2f90c85db7d04161a02f8107c56f92fd4e82a9fb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 035cf4578ee6f60ba3406a66f88ea33cc54ea7d85ab38524b7e6ae54c9b4b80e03fe2938fc34d047e72df9423b71b8eba1f40004a8fd688555b56c3aebb81605
|
|
7
|
+
data.tar.gz: feda70dff8797923f46078a42429b274d7562eb5105a5d43c1d746f744436c6c516847aa2ef68d7b235f1d1a216981e10d4c707bc6f4fb45372d6b867c412a2a
|
data/README.md
CHANGED
|
@@ -22,25 +22,25 @@ For details on how to perform these operations please see the
|
|
|
22
22
|
Add this line to your application's Gemfile:
|
|
23
23
|
|
|
24
24
|
```ruby
|
|
25
|
-
gem '
|
|
25
|
+
gem 'lab', git: 'https://github.com/EGPAFMalawiHIS/HIS-EMR-API-Lab', branch: 'development'
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
And then execute:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
$ bundle install
|
|
31
|
+
$ bundle install lab
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
Or install it yourself as:
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
$ gem install
|
|
37
|
+
$ gem install lab
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
Finally run:
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
|
-
$ bundle exec rails
|
|
43
|
+
$ bundle exec rails lab:install
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
## Configuration
|
|
@@ -66,19 +66,6 @@ but too much a departure from it is frowned upon. For example, you will be forgi
|
|
|
66
66
|
for writing a method with 15 to 20 lines if you clearly justify why you couldn't
|
|
67
67
|
break that method into multiple smaller methods.
|
|
68
68
|
|
|
69
|
-
## Publishing
|
|
70
|
-
|
|
71
|
-
To publish a new version of the gem, first update the version number in
|
|
72
|
-
`lib/lab/version.rb` and then run the following command:
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
$ gem build his_emr_api_lab.gemspec
|
|
76
|
-
$ gem push his_emr_api_lab-<version>.gem
|
|
77
|
-
```
|
|
78
|
-
Make sure to replace `<version>` with the version number you set in `lib/lab/version.rb`.
|
|
79
|
-
|
|
80
|
-
NB: You need to have an account on [rubygems.org](https://rubygems.org/) and permission to publish gems.
|
|
81
|
-
|
|
82
69
|
## License
|
|
83
70
|
|
|
84
71
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -7,8 +7,11 @@ module Lab
|
|
|
7
7
|
queue_as :default
|
|
8
8
|
def perform(results_obs_id, serializer, result_enter_by)
|
|
9
9
|
Rails.logger.info("Lab::ProcessLabResultJob: Processing result completion for #{serializer}")
|
|
10
|
-
|
|
10
|
+
# set location context for the job based on the order's encounter to ensure proper context for any operations performed in the job
|
|
11
|
+
results_obs = Lab::LabResult.unscoped.find(results_obs_id)
|
|
12
|
+
encounter = Encounter.unscoped.find_by(encounter_id: results_obs.encounter_id)
|
|
13
|
+
Location.current = Location.find(encounter.location_id) if encounter&.location_id
|
|
11
14
|
Lab::ResultsService.process_result_completion(results_obs, serializer, result_enter_by)
|
|
12
15
|
end
|
|
13
16
|
end
|
|
14
|
-
end
|
|
17
|
+
end
|
|
@@ -10,7 +10,8 @@ module Lab
|
|
|
10
10
|
Rails.logger.info('Initialising LIMS REST API...')
|
|
11
11
|
|
|
12
12
|
User.current = Lab::Lims::Utils.lab_user
|
|
13
|
-
|
|
13
|
+
# Set location from patient's most recent encounter to ensure proper context
|
|
14
|
+
set_location_from_patient_encounter(patient_id)
|
|
14
15
|
|
|
15
16
|
lockfile = Rails.root.join('tmp', "update-patient-orders-#{patient_id}.lock")
|
|
16
17
|
|
|
@@ -28,5 +29,53 @@ module Lab
|
|
|
28
29
|
|
|
29
30
|
File.unlink(lockfile) if done
|
|
30
31
|
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def set_location_from_patient_encounter(patient_id)
|
|
36
|
+
Rails.logger.info("Setting location context for patient #{patient_id}")
|
|
37
|
+
|
|
38
|
+
# Strategy 1: Find location from patient's most recent order (ANY order type)
|
|
39
|
+
recent_order = Order.unscoped
|
|
40
|
+
.where(patient_id: patient_id)
|
|
41
|
+
.order(start_date: :desc)
|
|
42
|
+
.first
|
|
43
|
+
|
|
44
|
+
if recent_order
|
|
45
|
+
encounter = Encounter.unscoped.find_by(encounter_id: recent_order.encounter_id)
|
|
46
|
+
if encounter&.location_id
|
|
47
|
+
Location.current = Location.find(encounter.location_id)
|
|
48
|
+
Rails.logger.info("Location set from patient's recent order: #{Location.current.name} (ID: #{Location.current.location_id})")
|
|
49
|
+
return
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Strategy 2: Find location from patient's most recent encounter
|
|
54
|
+
recent_encounter = Encounter.unscoped
|
|
55
|
+
.where(patient_id: patient_id)
|
|
56
|
+
.order(encounter_datetime: :desc)
|
|
57
|
+
.first
|
|
58
|
+
|
|
59
|
+
if recent_encounter&.location_id
|
|
60
|
+
Location.current = Location.find(recent_encounter.location_id)
|
|
61
|
+
Rails.logger.info("Location set from patient's recent encounter: #{Location.current.name} (ID: #{Location.current.location_id})")
|
|
62
|
+
return
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Fallback chain: Try multiple options to ensure location is ALWAYS set
|
|
66
|
+
Location.current ||= begin
|
|
67
|
+
Location.current_health_center
|
|
68
|
+
rescue StandardError
|
|
69
|
+
nil
|
|
70
|
+
end
|
|
71
|
+
Location.current ||= Location.first
|
|
72
|
+
|
|
73
|
+
if Location.current
|
|
74
|
+
Rails.logger.info("Location set to fallback: #{Location.current.name} (ID: #{Location.current.location_id})")
|
|
75
|
+
else
|
|
76
|
+
Rails.logger.error('CRITICAL: Could not set Location.current - no locations found in database!')
|
|
77
|
+
raise 'No locations available in database'
|
|
78
|
+
end
|
|
79
|
+
end
|
|
31
80
|
end
|
|
32
81
|
end
|
|
@@ -8,10 +8,14 @@ module Lab
|
|
|
8
8
|
Rails.logger.info("Voiding order ##{order_id} in LIMS")
|
|
9
9
|
|
|
10
10
|
User.current = Lab::Lims::Utils.lab_user
|
|
11
|
-
|
|
11
|
+
# Set location from order's encounter to ensure proper context
|
|
12
|
+
order = Lab::LabOrder.unscoped.find(order_id)
|
|
13
|
+
encounter = Encounter.unscoped.find_by(encounter_id: order.encounter_id)
|
|
14
|
+
Location.current = Location.find(encounter.location_id) if encounter&.location_id
|
|
15
|
+
Location.current ||= Location.find_by_name('ART clinic')
|
|
12
16
|
|
|
13
17
|
worker = Lab::Lims::PushWorker.new(Lab::Lims::ApiFactory.create_api)
|
|
14
|
-
worker.push_order(
|
|
18
|
+
worker.push_order(order)
|
|
15
19
|
end
|
|
16
20
|
end
|
|
17
21
|
end
|
|
@@ -9,7 +9,8 @@ module Lab
|
|
|
9
9
|
reason_for_test ||= order.reason_for_test
|
|
10
10
|
target_lab = target_lab&.value_text || order.target_lab&.value_text || Location.current_health_center&.name
|
|
11
11
|
|
|
12
|
-
encounter
|
|
12
|
+
# Use unscoped to find encounter across all locations
|
|
13
|
+
encounter = Encounter.unscoped.find_by_encounter_id(order.encounter_id)
|
|
13
14
|
program = Program.find_by_program_id(encounter.program_id)
|
|
14
15
|
|
|
15
16
|
ActiveSupport::HashWithIndifferentAccess.new(
|
|
@@ -34,10 +34,11 @@ module Lab
|
|
|
34
34
|
|
|
35
35
|
unless specimen_type
|
|
36
36
|
return ActiveRecord::Base.connection.select_all <<~SQL
|
|
37
|
-
SELECT ca.concept_id, ca.value_reference as name, ca2.value_reference as nlims_code
|
|
37
|
+
SELECT ca.concept_id, ca.value_reference as name, ca2.value_reference as nlims_code, c.uuid
|
|
38
38
|
FROM concept_attribute ca
|
|
39
39
|
INNER JOIN concept_attribute ca2 ON ca.concept_id = ca2.concept_id
|
|
40
40
|
AND ca2.attribute_type_id = #{ConceptAttributeType.nlims_code.concept_attribute_type_id}
|
|
41
|
+
INNER JOIN concept c ON c.concept_id = ca.concept_id
|
|
41
42
|
WHERE ca.attribute_type_id = #{ConceptAttributeType.test_catalogue_name.concept_attribute_type_id}
|
|
42
43
|
AND ca.concept_id IN (#{test_types.select(:concept_id).to_sql})
|
|
43
44
|
GROUP BY ca.concept_id
|
|
@@ -56,10 +57,11 @@ module Lab
|
|
|
56
57
|
)
|
|
57
58
|
|
|
58
59
|
return ActiveRecord::Base.connection.select_all <<~SQL
|
|
59
|
-
SELECT ca.concept_id, ca.value_reference as name, ca2.value_reference as nlims_code
|
|
60
|
+
SELECT ca.concept_id, ca.value_reference as name, ca2.value_reference as nlims_code, c.uuid
|
|
60
61
|
FROM concept_attribute ca
|
|
61
62
|
INNER JOIN concept_attribute ca2 ON ca.concept_id = ca2.concept_id
|
|
62
63
|
AND ca2.attribute_type_id = #{ConceptAttributeType.nlims_code.concept_attribute_type_id}
|
|
64
|
+
INNER JOIN concept c ON c.concept_id = ca.concept_id
|
|
63
65
|
WHERE ca.attribute_type_id = #{ConceptAttributeType.test_catalogue_name.concept_attribute_type_id}
|
|
64
66
|
AND ca.concept_id IN (#{concept_set.select(:concept_set).to_sql})
|
|
65
67
|
GROUP BY ca.concept_id
|
|
@@ -72,10 +74,11 @@ module Lab
|
|
|
72
74
|
|
|
73
75
|
unless test_type
|
|
74
76
|
return ActiveRecord::Base.connection.select_all <<~SQL
|
|
75
|
-
SELECT ca.concept_id, ca.value_reference as name, ca2.value_reference as nlims_code
|
|
77
|
+
SELECT ca.concept_id, ca.value_reference as name, ca2.value_reference as nlims_code, c.uuid
|
|
76
78
|
FROM concept_attribute ca
|
|
77
79
|
INNER JOIN concept_attribute ca2 ON ca.concept_id = ca2.concept_id
|
|
78
80
|
AND ca2.attribute_type_id = #{ConceptAttributeType.nlims_code.concept_attribute_type_id}
|
|
81
|
+
INNER JOIN concept c ON c.concept_id = ca.concept_id
|
|
79
82
|
WHERE ca.attribute_type_id = #{ConceptAttributeType.test_catalogue_name.concept_attribute_type_id}
|
|
80
83
|
AND ca.concept_id IN (#{specimen_types.select(:concept_id).to_sql})
|
|
81
84
|
GROUP BY ca.concept_id
|
|
@@ -94,10 +97,11 @@ module Lab
|
|
|
94
97
|
)
|
|
95
98
|
|
|
96
99
|
return ActiveRecord::Base.connection.select_all <<~SQL
|
|
97
|
-
SELECT ca.concept_id, ca.value_reference as name, ca2.value_reference as nlims_code
|
|
100
|
+
SELECT ca.concept_id, ca.value_reference as name, ca2.value_reference as nlims_code, c.uuid
|
|
98
101
|
FROM concept_attribute ca
|
|
99
102
|
INNER JOIN concept_attribute ca2 ON ca.concept_id = ca2.concept_id
|
|
100
103
|
AND ca2.attribute_type_id = #{ConceptAttributeType.nlims_code.concept_attribute_type_id}
|
|
104
|
+
INNER JOIN concept c ON c.concept_id = ca.concept_id
|
|
101
105
|
WHERE ca.attribute_type_id = #{ConceptAttributeType.test_catalogue_name.concept_attribute_type_id}
|
|
102
106
|
AND ca.concept_id IN (#{concept_set.pluck(:concept_id).push(0).join(',')})
|
|
103
107
|
GROUP BY ca.concept_id
|
|
@@ -114,15 +118,16 @@ module Lab
|
|
|
114
118
|
measures = ConceptSet.find_members_by_name(Lab::Metadata::TEST_RESULT_INDICATOR_CONCEPT_NAME)
|
|
115
119
|
.select(:concept_id)
|
|
116
120
|
|
|
117
|
-
sets = ConceptSet.where(concept_set:
|
|
121
|
+
sets = ConceptSet.where(concept_set: test, concept_id: measures)
|
|
118
122
|
|
|
119
123
|
return ActiveRecord::Base.connection.select_all <<~SQL
|
|
120
|
-
SELECT ca.concept_id, ca.value_reference as name, ca2.value_reference as nlims_code
|
|
124
|
+
SELECT ca.concept_id, ca.value_reference as name, ca2.value_reference as nlims_code, c.uuid
|
|
121
125
|
FROM concept_attribute ca
|
|
122
126
|
INNER JOIN concept_attribute ca2 ON ca.concept_id = ca2.concept_id
|
|
123
127
|
AND ca2.attribute_type_id = #{ConceptAttributeType.nlims_code.concept_attribute_type_id}
|
|
128
|
+
INNER JOIN concept c ON c.concept_id = ca.concept_id
|
|
124
129
|
WHERE ca.attribute_type_id = #{ConceptAttributeType.test_catalogue_name.concept_attribute_type_id}
|
|
125
|
-
AND ca.concept_id IN (#{sets.pluck(:
|
|
130
|
+
AND ca.concept_id IN (#{sets.pluck(:concept_id).push(0).join(',')})
|
|
126
131
|
GROUP BY ca.concept_id
|
|
127
132
|
SQL
|
|
128
133
|
end
|
|
@@ -227,7 +227,7 @@ module Lab
|
|
|
227
227
|
{
|
|
228
228
|
order: {
|
|
229
229
|
tracking_number: order_dto.fetch(:tracking_number),
|
|
230
|
-
district:
|
|
230
|
+
district: order_dto.fetch(:districy),
|
|
231
231
|
health_facility_name: order_dto.fetch(:sending_facility),
|
|
232
232
|
sending_facility: order_dto.fetch(:sending_facility),
|
|
233
233
|
arv_number: order_dto.fetch(:patient).fetch(:arv_number),
|
|
@@ -281,24 +281,17 @@ module Lab
|
|
|
281
281
|
status: 'specimen_collected',
|
|
282
282
|
time_updated: date_updated,
|
|
283
283
|
sample_type: order_dto.fetch(:sample_type_map),
|
|
284
|
-
updated_by: status.fetch(:updated_by)
|
|
284
|
+
updated_by: status.fetch(:updated_by),
|
|
285
|
+
status_trail: [
|
|
286
|
+
updated_by: {
|
|
287
|
+
first_name: status.fetch(:updated_by).fetch(:first_name),
|
|
288
|
+
last_name: status.fetch(:updated_by).fetch(:last_name),
|
|
289
|
+
id_number: status.fetch(:updated_by).fetch(:id)
|
|
290
|
+
}
|
|
291
|
+
]
|
|
285
292
|
}
|
|
286
293
|
end
|
|
287
294
|
|
|
288
|
-
def current_district
|
|
289
|
-
health_centre = Location.current_health_center
|
|
290
|
-
raise 'Current health centre not set' unless health_centre
|
|
291
|
-
|
|
292
|
-
district = health_centre.district || Lab::Lims::Config.application['district']
|
|
293
|
-
|
|
294
|
-
unless district
|
|
295
|
-
health_centre_name = "##{health_centre.id} - #{health_centre.name}"
|
|
296
|
-
raise "Current health centre district not set: #{health_centre_name}"
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
district
|
|
300
|
-
end
|
|
301
|
-
|
|
302
295
|
##
|
|
303
296
|
# Extracts sample drawn status from an OrderDto
|
|
304
297
|
def sample_drawn_status(order_dto)
|
|
@@ -444,23 +437,25 @@ module Lab
|
|
|
444
437
|
order_dto['test_results'].each do |test_name, results|
|
|
445
438
|
Rails.logger.info("Pushing result for order ##{order_dto['tracking_number']}")
|
|
446
439
|
in_authenticated_session do |headers|
|
|
447
|
-
params = make_update_test_params(order_dto
|
|
440
|
+
params = make_update_test_params(order_dto, test_name, results)
|
|
448
441
|
|
|
449
|
-
RestClient.
|
|
442
|
+
RestClient.put(expand_uri("tests/#{order_dto['tracking_number']}", api_version: 'v2'), params, headers)
|
|
450
443
|
end
|
|
451
444
|
end
|
|
452
445
|
end
|
|
453
446
|
|
|
454
|
-
def make_update_test_params(
|
|
447
|
+
def make_update_test_params(order_dto, test, results, test_status = 'Drawn')
|
|
448
|
+
# Find the concept from the test name (which is a string)
|
|
449
|
+
concept = ::ConceptName.find_by(name: test)&.concept
|
|
455
450
|
{
|
|
456
451
|
test_status:,
|
|
457
452
|
time_updated: results['result_date'],
|
|
458
453
|
test_type: {
|
|
459
|
-
name:
|
|
460
|
-
nlims_code:
|
|
454
|
+
name: concept&.test_catalogue_name,
|
|
455
|
+
nlims_code: concept&.nlims_code
|
|
461
456
|
},
|
|
462
|
-
test_results: results['results'].map do |
|
|
463
|
-
|
|
457
|
+
test_results: results['results'].map do |measure_name, value|
|
|
458
|
+
measure_value = value['result_value']
|
|
464
459
|
{
|
|
465
460
|
measure: {
|
|
466
461
|
name: measure_name,
|
|
@@ -472,6 +467,18 @@ module Lab
|
|
|
472
467
|
result_date: results['result_date']
|
|
473
468
|
}
|
|
474
469
|
}
|
|
470
|
+
end,
|
|
471
|
+
status_trail: order_dto['sample_statuses'].map do |trail_entry|
|
|
472
|
+
date, status = trail_entry.each_pair.first
|
|
473
|
+
{
|
|
474
|
+
status: status['status'],
|
|
475
|
+
timestamp: date,
|
|
476
|
+
updated_by: {
|
|
477
|
+
first_name: status.fetch('updated_by').fetch('first_name'),
|
|
478
|
+
last_name: status.fetch('updated_by').fetch('last_name'),
|
|
479
|
+
id_number: status.fetch('updated_by').fetch('id')
|
|
480
|
+
}
|
|
481
|
+
}
|
|
475
482
|
end
|
|
476
483
|
}
|
|
477
484
|
end
|
|
@@ -678,7 +685,7 @@ module Lab
|
|
|
678
685
|
def make_create_params(order_dto)
|
|
679
686
|
{
|
|
680
687
|
tracking_number: order_dto.fetch(:tracking_number),
|
|
681
|
-
district:
|
|
688
|
+
district: order_dto.fetch(:districy),
|
|
682
689
|
health_facility_name: order_dto.fetch(:sending_facility),
|
|
683
690
|
first_name: order_dto.fetch(:patient).fetch(:first_name),
|
|
684
691
|
last_name: order_dto.fetch(:patient).fetch(:last_name),
|
|
@@ -716,20 +723,6 @@ module Lab
|
|
|
716
723
|
}
|
|
717
724
|
end
|
|
718
725
|
|
|
719
|
-
def current_district
|
|
720
|
-
health_centre = Location.current_health_center
|
|
721
|
-
raise 'Current health centre not set' unless health_centre
|
|
722
|
-
|
|
723
|
-
district = health_centre.district || Lab::Lims::Config.application['district']
|
|
724
|
-
|
|
725
|
-
unless district
|
|
726
|
-
health_centre_name = "##{health_centre.id} - #{health_centre.name}"
|
|
727
|
-
raise "Current health centre district not set: #{health_centre_name}"
|
|
728
|
-
end
|
|
729
|
-
|
|
730
|
-
district
|
|
731
|
-
end
|
|
732
|
-
|
|
733
726
|
##
|
|
734
727
|
# Extracts sample drawn status from an OrderDto
|
|
735
728
|
def sample_drawn_status(order_dto)
|
|
@@ -14,15 +14,17 @@ module Lab
|
|
|
14
14
|
|
|
15
15
|
def serialize_order(order)
|
|
16
16
|
serialized_order = Lims::Utils.structify(Lab::LabOrderSerializer.serialize_order(order))
|
|
17
|
+
location = get_location(serialized_order.location_id, order)
|
|
18
|
+
|
|
17
19
|
Lims::OrderDto.new(
|
|
18
20
|
_id: Lab::LimsOrderMapping.find_by(order: order)&.lims_id || serialized_order.accession_number,
|
|
19
21
|
tracking_number: serialized_order.accession_number,
|
|
20
|
-
sending_facility:
|
|
22
|
+
sending_facility: location.name,
|
|
21
23
|
receiving_facility: serialized_order.target_lab,
|
|
22
24
|
tests: serialized_order.tests.map { |test| format_test_name(test.name) },
|
|
23
25
|
tests_map: serialized_order.tests,
|
|
24
26
|
patient: format_patient(serialized_order.patient_id),
|
|
25
|
-
order_location:
|
|
27
|
+
order_location: location.name,
|
|
26
28
|
sample_type: format_sample_type(serialized_order.specimen.name),
|
|
27
29
|
sample_type_map: {
|
|
28
30
|
name: format_sample_type(serialized_order.specimen.name),
|
|
@@ -32,7 +34,7 @@ module Lab
|
|
|
32
34
|
sample_statuses: format_sample_status_trail(order),
|
|
33
35
|
test_statuses: format_test_status_trail(order),
|
|
34
36
|
who_order_test: format_orderer(order),
|
|
35
|
-
districy:
|
|
37
|
+
districy: get_district(location), # yes districy [sic]...
|
|
36
38
|
priority: format_sample_priority(serialized_order.reason_for_test.name),
|
|
37
39
|
date_created: serialized_order.order_date,
|
|
38
40
|
test_results: format_test_results(serialized_order),
|
|
@@ -43,13 +45,26 @@ module Lab
|
|
|
43
45
|
|
|
44
46
|
private
|
|
45
47
|
|
|
46
|
-
def
|
|
47
|
-
|
|
48
|
-
location = Location.
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
def get_location(location_id, order)
|
|
49
|
+
# Try to get location from the location_id first
|
|
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
|
|
64
|
+
|
|
65
|
+
raise 'Current health center not set' unless location
|
|
51
66
|
|
|
52
|
-
location
|
|
67
|
+
location
|
|
53
68
|
end
|
|
54
69
|
|
|
55
70
|
# Format patient into a structure that LIMS expects
|
|
@@ -123,11 +138,13 @@ module Lab
|
|
|
123
138
|
def format_sample_status_trail(order)
|
|
124
139
|
return [] if order.concept_id == ConceptName.find_by_name!('Unknown').concept_id
|
|
125
140
|
|
|
126
|
-
user = User.find(order.creator)
|
|
127
|
-
user = User.find(order.discontinued_by) if Order.columns_hash.key?('discontinued_by') && user.blank?
|
|
141
|
+
user = User.unscoped.find(order.creator)
|
|
142
|
+
user = User.unscoped.find(order.discontinued_by) if Order.columns_hash.key?('discontinued_by') && user.blank?
|
|
128
143
|
|
|
129
144
|
drawn_by = PersonName.find_by_person_id(user.user_id)
|
|
130
|
-
drawn_date = order.discontinued_date || order.start_date if [
|
|
145
|
+
drawn_date = order.discontinued_date || order.start_date if %w[discontinued_date start_date].all? do |column|
|
|
146
|
+
order.respond_to?(column)
|
|
147
|
+
end
|
|
131
148
|
drawn_date ||= order.date_created
|
|
132
149
|
|
|
133
150
|
[
|
|
@@ -179,13 +196,15 @@ module Lab
|
|
|
179
196
|
order.tests&.each_with_object({}) do |test, results|
|
|
180
197
|
next if test.result.nil? || test.result.empty?
|
|
181
198
|
|
|
182
|
-
|
|
199
|
+
# Use unscoped to find observations across locations
|
|
200
|
+
result_obs = Observation.unscoped.find_by(obs_id: test.result.first.id)
|
|
183
201
|
unless result_obs
|
|
184
202
|
Rails.logger.warn("Observation with obs_id=#{test.result.first.id} not found for test #{test.name} in order #{order.accession_number}")
|
|
185
203
|
next
|
|
186
204
|
end
|
|
187
205
|
|
|
188
|
-
|
|
206
|
+
# Use unscoped to find user regardless of location
|
|
207
|
+
test_creator = User.unscoped.find(result_obs.creator)
|
|
189
208
|
test_creator_name = PersonName.find_by_person_id(test_creator.person_id)
|
|
190
209
|
|
|
191
210
|
results[format_test_name(test.name)] = {
|
|
@@ -208,7 +227,7 @@ module Lab
|
|
|
208
227
|
end
|
|
209
228
|
|
|
210
229
|
def format_test_name(test_name)
|
|
211
|
-
|
|
230
|
+
test_name
|
|
212
231
|
end
|
|
213
232
|
|
|
214
233
|
def format_sample_priority(priority)
|
|
@@ -217,16 +236,11 @@ module Lab
|
|
|
217
236
|
priority&.titleize
|
|
218
237
|
end
|
|
219
238
|
|
|
220
|
-
def
|
|
221
|
-
|
|
222
|
-
raise 'Current health center not set' unless health_center
|
|
223
|
-
|
|
224
|
-
health_center
|
|
225
|
-
end
|
|
239
|
+
def get_district(location)
|
|
240
|
+
return nil unless location
|
|
226
241
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|| current_health_center.parent&.name \
|
|
242
|
+
district = location.city_village \
|
|
243
|
+
|| location.parent&.name \
|
|
230
244
|
|| GlobalProperty.find_by_property('current_health_center_district')&.property_value
|
|
231
245
|
|
|
232
246
|
return district if district
|
|
@@ -238,12 +252,8 @@ module Lab
|
|
|
238
252
|
Config.application['district']
|
|
239
253
|
end
|
|
240
254
|
|
|
241
|
-
def current_facility_name
|
|
242
|
-
current_health_center.name
|
|
243
|
-
end
|
|
244
|
-
|
|
245
255
|
def find_user(user_id)
|
|
246
|
-
user = User.find(user_id)
|
|
256
|
+
user = User.unscoped.find(user_id)
|
|
247
257
|
person_name = PersonName.find_by(person_id: user.person_id)
|
|
248
258
|
phone_number = PersonAttribute.find_by(type: PersonAttributeType.where(name: 'Cell phone number'),
|
|
249
259
|
person_id: user.person_id)
|
|
@@ -30,7 +30,8 @@ class Lab::NotificationService
|
|
|
30
30
|
def create_notification(alert_type, alert_message)
|
|
31
31
|
return if alert_type != 'LIMS'
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
# Use unscoped to find user regardless of location context
|
|
34
|
+
lab = User.unscoped.find_by(username: 'lab_daemon')
|
|
34
35
|
ActiveRecord::Base.transaction do
|
|
35
36
|
alert = NotificationAlert.create!(text: alert_message.to_json, date_to_expire: Time.now + not_period.days,
|
|
36
37
|
creator: lab, changed_by: lab, date_created: Time.now)
|
|
@@ -247,7 +247,6 @@ module Lab
|
|
|
247
247
|
|
|
248
248
|
concept = params.dig(:specimen, :concept)
|
|
249
249
|
concept ||= params.dig(:specimen, :concept_id)
|
|
250
|
-
concept ||= unknown_concept_id
|
|
251
250
|
|
|
252
251
|
order_type = nil
|
|
253
252
|
order_type = OrderType.find_by_order_type_id!(params[:order_type_id])&.id if params[:order_type_id].present?
|
|
@@ -260,7 +259,8 @@ module Lab
|
|
|
260
259
|
order.date_created = params[:date]&.to_date || Date.today if order.respond_to?(:date_created)
|
|
261
260
|
order.start_date = params[:date]&.to_date || Date.today if order.respond_to?(:start_date)
|
|
262
261
|
order.auto_expire_date = params[:end_date]
|
|
263
|
-
|
|
262
|
+
# Note: comment_to_fulfiller is a has_one association, not a field
|
|
263
|
+
# It will be created via add_comment_to_fulfiller method
|
|
264
264
|
order.accession_number = access_number
|
|
265
265
|
order.orderer = User.current&.user_id
|
|
266
266
|
|
|
@@ -331,7 +331,8 @@ module Lab
|
|
|
331
331
|
end
|
|
332
332
|
|
|
333
333
|
def create_order_observation(order, concept_name, date, **values)
|
|
334
|
-
|
|
334
|
+
# Use unscoped to find user regardless of location context
|
|
335
|
+
creator = User.unscoped.find_by(username: 'lab_daemon')
|
|
335
336
|
User.current ||= creator
|
|
336
337
|
Observation.create!(
|
|
337
338
|
order:,
|
|
@@ -348,7 +349,7 @@ module Lab
|
|
|
348
349
|
end
|
|
349
350
|
|
|
350
351
|
def unknown_concept_id
|
|
351
|
-
ConceptName.find_by_name!('Unknown').
|
|
352
|
+
ConceptName.find_by_name!('Unknown').concept
|
|
352
353
|
end
|
|
353
354
|
|
|
354
355
|
def update_reason_for_test(order, concept_id, force_update: false)
|
|
@@ -65,7 +65,7 @@ module Lab
|
|
|
65
65
|
order_date: Order.columns.include?('start_date') ? order.start_date : order.date_created,
|
|
66
66
|
'ARV-Number': find_arv_number(result.person_id),
|
|
67
67
|
PatientID: result.person_id,
|
|
68
|
-
'Ordered By': Order.columns.include?('provider_id') ? order&.provider&.person&.name : Person.find(order.creator)&.name,
|
|
68
|
+
'Ordered By': Order.columns.include?('provider_id') ? order&.provider&.person&.name : Person.find(User.unscoped.find(order.creator).person_id)&.name,
|
|
69
69
|
Result: values }.as_json
|
|
70
70
|
NotificationService.new.create_notification(result_enter_by, data)
|
|
71
71
|
end
|
|
@@ -15,7 +15,8 @@ module Lab
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def authenticate_user(username:, password:, user_agent:, request_ip:)
|
|
18
|
-
|
|
18
|
+
# Use unscoped for authentication - should not be location-dependent
|
|
19
|
+
user = User.unscoped.find_by_username username
|
|
19
20
|
encrypted_pass = Password.new(user.password)
|
|
20
21
|
if encrypted_pass == password
|
|
21
22
|
generate_token(user, user_agent, request_ip)
|
|
@@ -30,7 +31,7 @@ module Lab
|
|
|
30
31
|
##
|
|
31
32
|
# Validate that the username doesn't already exists
|
|
32
33
|
def validate(username:)
|
|
33
|
-
raise UnprocessableEntityError, 'Username already exists' if User.find_by_username username
|
|
34
|
+
raise UnprocessableEntityError, 'Username already exists' if User.unscoped.find_by_username username
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
def create_lims_person
|
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.1.
|
|
4
|
+
version: 2.1.9.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-03-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: couchrest
|
|
@@ -340,9 +340,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
340
340
|
version: '0'
|
|
341
341
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
342
342
|
requirements:
|
|
343
|
-
- - "
|
|
343
|
+
- - ">"
|
|
344
344
|
- !ruby/object:Gem::Version
|
|
345
|
-
version:
|
|
345
|
+
version: 1.3.1
|
|
346
346
|
requirements: []
|
|
347
347
|
rubygems_version: 3.4.1
|
|
348
348
|
signing_key:
|