his_emr_api_lab 2.2.1 → 2.2.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c59744a50921b0f2244c94e052d4d66cbf39096485f99755e84f9cb66c311a0b
4
- data.tar.gz: 989cc056aa59acbcd9d24977d3fc34b330cd1793db5d35908ccce44ca08dab34
3
+ metadata.gz: 5430975d278d3f06b169b906ca5c471f0ad632dc158540f358545f4da2aa05d1
4
+ data.tar.gz: e75574b9ac3023e974d70ef021ae158ded0614a8d379eec120124f9e45891479
5
5
  SHA512:
6
- metadata.gz: 22429cb56bfb9c5af91e5b44e2da11b66551ac89d084eeea45f5b16bb06daf5cc4f99d41cb5f2e4f3d5a38443fb62c15e5a30a14c5e5fff85006a3233d8f71de
7
- data.tar.gz: 622a040596dc790f1d5a3c05469f7afe131badeca6313eb2412f8da378e632b7b28643c6a12eba6f02c618b9bed680722a635ce6e2e57ea6863872eb5e9b0966
6
+ metadata.gz: b3f869dd69e746a19f35be7741f717c1b657ae6d8df6a55cf72a427f70fbdee62c76d61af605ac81f248a376428db8fab67bd6ef9fa623ceb2ca0bec45678cd6
7
+ data.tar.gz: a7c3ef0003f803d83f47337f3d4ba4f9388a4b40962f6b95b36d14c904ab361280971e6689f8fcbb3cd48f41ba4863722d422c6d884e44e346f0e48626cacedc
@@ -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
+ # set location context for the job based on the order's encounter to ensure proper context for any operations performed in the job
10
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
- Location.current = Location.find_by_name('ART clinic')
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
- Location.current = Location.find_by_name('ART clinic')
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(Lab::LabOrder.unscoped.find(order_id))
18
+ worker.push_order(order)
15
19
  end
16
20
  end
17
21
  end
@@ -11,9 +11,9 @@ 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.unscope(where: :obs_group_id).find(measure.obs_id)
15
- encounter = Encounter.find(obs.encounter_id)
16
- program_id = encounter.program_id
14
+ obs = Observation.unscope(where: :obs_group_id).find_by(obs_id: measure.obs_id)
15
+ encounter = Encounter.find_by(encounter_id: obs&.encounter_id)
16
+ program_id = encounter&.program_id
17
17
  end
18
18
 
19
19
  {
@@ -294,7 +294,7 @@ module Lab
294
294
  {
295
295
  order: {
296
296
  tracking_number: order_dto.fetch(:tracking_number),
297
- district: current_district,
297
+ district: order_dto.fetch(:districy),
298
298
  health_facility_name: order_dto.fetch(:sending_facility),
299
299
  sending_facility: order_dto.fetch(:sending_facility),
300
300
  arv_number: order_dto.fetch(:patient).fetch(:arv_number),
@@ -348,24 +348,17 @@ module Lab
348
348
  status: 'specimen_collected',
349
349
  time_updated: date_updated,
350
350
  sample_type: order_dto.fetch(:sample_type_map),
351
- updated_by: status.fetch(:updated_by)
351
+ updated_by: status.fetch(:updated_by),
352
+ status_trail: [
353
+ updated_by: {
354
+ first_name: status.fetch(:updated_by).fetch(:first_name),
355
+ last_name: status.fetch(:updated_by).fetch(:last_name),
356
+ id_number: status.fetch(:updated_by).fetch(:id)
357
+ }
358
+ ]
352
359
  }
353
360
  end
354
361
 
355
- def current_district
356
- health_centre = Location.current_health_center
357
- raise 'Current health centre not set' unless health_centre
358
-
359
- district = health_centre.district || Lab::Lims::Config.application['district']
360
-
361
- unless district
362
- health_centre_name = "##{health_centre.id} - #{health_centre.name}"
363
- raise "Current health centre district not set: #{health_centre_name}"
364
- end
365
-
366
- district
367
- end
368
-
369
362
  ##
370
363
  # Extracts sample drawn status from an OrderDto
371
364
  def sample_drawn_status(order_dto)
@@ -511,23 +504,25 @@ module Lab
511
504
  order_dto['test_results'].each do |test_name, results|
512
505
  Rails.logger.info("Pushing result for order ##{order_dto['tracking_number']}")
513
506
  in_authenticated_session do |headers|
514
- params = make_update_test_params(order_dto['tracking_number'], test_name, results)
507
+ params = make_update_test_params(order_dto, test_name, results)
515
508
 
516
- RestClient.post(expand_uri("tests/#{order_dto['tracking_number']}", api_version: 'v2'), params, headers)
509
+ RestClient.put(expand_uri("tests/#{order_dto['tracking_number']}", api_version: 'v2'), params, headers)
517
510
  end
518
511
  end
519
512
  end
520
513
 
521
- def make_update_test_params(_tracking_number, test, results, test_status = 'Drawn')
514
+ def make_update_test_params(order_dto, test, results, test_status = 'Drawn')
515
+ # Find the concept from the test name (which is a string)
516
+ concept = ::ConceptName.find_by(name: test)&.concept
522
517
  {
523
518
  test_status:,
524
519
  time_updated: results['result_date'],
525
520
  test_type: {
526
- name: ::Concept.find(test.concept_id).test_catalogue_name,
527
- nlims_code: ::Concept.find(test.concept_id).nlims_code
521
+ name: concept&.test_catalogue_name,
522
+ nlims_code: concept&.nlims_code
528
523
  },
529
- test_results: results['results'].map do |measure, _value|
530
- measure_name, measure_value = measure
524
+ test_results: results['results'].map do |measure_name, value|
525
+ measure_value = value['result_value']
531
526
  {
532
527
  measure: {
533
528
  name: measure_name,
@@ -539,6 +534,18 @@ module Lab
539
534
  result_date: results['result_date']
540
535
  }
541
536
  }
537
+ end,
538
+ status_trail: order_dto['sample_statuses'].map do |trail_entry|
539
+ date, status = trail_entry.each_pair.first
540
+ {
541
+ status: status['status'],
542
+ timestamp: date,
543
+ updated_by: {
544
+ first_name: status.fetch('updated_by').fetch('first_name'),
545
+ last_name: status.fetch('updated_by').fetch('last_name'),
546
+ id_number: status.fetch('updated_by').fetch('id')
547
+ }
548
+ }
542
549
  end
543
550
  }
544
551
  end
@@ -763,7 +770,7 @@ module Lab
763
770
  def make_create_params(order_dto)
764
771
  {
765
772
  tracking_number: order_dto.fetch(:tracking_number),
766
- district: current_district,
773
+ district: order_dto.fetch(:districy),
767
774
  health_facility_name: order_dto.fetch(:sending_facility),
768
775
  first_name: order_dto.fetch(:patient).fetch(:first_name),
769
776
  last_name: order_dto.fetch(:patient).fetch(:last_name),
@@ -801,20 +808,6 @@ module Lab
801
808
  }
802
809
  end
803
810
 
804
- def current_district
805
- health_centre = Location.current_health_center
806
- raise 'Current health centre not set' unless health_centre
807
-
808
- district = health_centre.district || Lab::Lims::Config.application['district']
809
-
810
- unless district
811
- health_centre_name = "##{health_centre.id} - #{health_centre.name}"
812
- raise "Current health centre district not set: #{health_centre_name}"
813
- end
814
-
815
- district
816
- end
817
-
818
811
  ##
819
812
  # Extracts sample drawn status from an OrderDto
820
813
  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: current_facility_name,
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: format_order_location(serialized_order.encounter_id),
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: current_district, # yes districy [sic]...
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 format_order_location(encounter_id)
47
- location_id = Encounter.select(:location_id).where(encounter_id:)
48
- location = Location.select(:name)
49
- .where(location_id:)
50
- .first
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&.name
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 ['discontinued_date', 'start_date'].all? { |column| order.respond_to?(column) }
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
- result_obs = Observation.find_by(obs_id: test.result.first.id)
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
- test_creator = User.find(result_obs.creator)
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
- return test_name
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 current_health_center
221
- health_center = Location.current_health_center
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
- def current_district
228
- district = current_health_center.city_village \
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)
@@ -43,7 +43,8 @@ module Lab
43
43
  end
44
44
 
45
45
  def self.lab_user
46
- user = User.find_by_username('lab_daemon')
46
+ # Use unscoped to find user regardless of location context
47
+ user = User.unscoped.find_by_username('lab_daemon')
47
48
  return user if user
48
49
 
49
50
  god_user = User.first
@@ -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
- lab = User.find_by(username: 'lab_daemon')
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)
@@ -385,7 +385,8 @@ module Lab
385
385
  end
386
386
 
387
387
  def create_order_observation(order, concept_name, date, **values)
388
- creator = User.find_by(username: 'lab_daemon')
388
+ # Use unscoped to find user regardless of location context
389
+ creator = User.unscoped.find_by(username: 'lab_daemon')
389
390
  User.current ||= creator
390
391
  Observation.create!(
391
392
  order:,
@@ -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
@@ -130,7 +130,6 @@ module Lab
130
130
  concept_id: test_status_concept.concept_id,
131
131
  value_text: 'Drawn', # Store status as text
132
132
  obs_datetime: timestamp,
133
- status: 'FINAL',
134
133
  comments: {
135
134
  'first_name' => User.current&.person&.names&.first&.given_name,
136
135
  'last_name' => User.current&.person&.names&.first&.family_name,
@@ -15,7 +15,8 @@ module Lab
15
15
  end
16
16
 
17
17
  def authenticate_user(username:, password:, user_agent:, request_ip:)
18
- user = User.find_by_username username
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lab
4
- VERSION = '2.2.1'
4
+ VERSION = '2.2.2'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: his_emr_api_lab
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elizabeth Glaser Pediatric Foundation Malawi