his_emr_api_lab 2.1.9.pre.beta → 2.2.1

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.
@@ -5,15 +5,16 @@ module Lab
5
5
  ##
6
6
  # Pushes all local orders to a LIMS Api object.
7
7
  class PushWorker
8
- attr_reader :lims_api
8
+ attr_reader :lims_api, :start_date
9
9
 
10
10
  include Utils # for logger
11
11
 
12
12
  SECONDS_TO_WAIT_FOR_ORDERS = 30
13
13
  START_DATE = Time.parse('2024-09-03').freeze
14
14
 
15
- def initialize(lims_api)
15
+ def initialize(lims_api, start_date: nil)
16
16
  @lims_api = lims_api
17
+ @start_date = start_date
17
18
  end
18
19
 
19
20
  def push_orders(batch_size: 1000, wait: false)
@@ -81,8 +82,8 @@ module Lab
81
82
 
82
83
  def void_order_in_lims(order_id)
83
84
  order = Lab::LabOrder.joins(order_type: { name: 'Lab' })
84
- .unscoped
85
- .find(order_id)
85
+ .unscoped
86
+ .find(order_id)
86
87
  order_dto = Lab::Lims::OrderSerializer.serialize_order(order)
87
88
  Rails.logger.info("Deleting order ##{order_dto[:accession_number]} from LIMS")
88
89
  lims_api.delete_order('', order_dto)
@@ -100,10 +101,16 @@ module Lab
100
101
 
101
102
  def new_orders
102
103
  Rails.logger.debug('Looking for new orders that need to be created in LIMS...')
103
- Lab::LabOrder.where.not(order_id: Lab::LimsOrderMapping.all.select(:order_id))
104
- .where("accession_number IS NOT NULL AND accession_number !=''")
105
- .where(date_created: START_DATE..(Date.today + 1.day))
106
- .order(date_created: :desc)
104
+ query = Lab::LabOrder.where.not(order_id: Lab::LimsOrderMapping.all.select(:order_id))
105
+ .where("accession_number IS NOT NULL AND accession_number !=''")
106
+
107
+ query = if start_date
108
+ query.where('orders.date_created >= ?', start_date)
109
+ else
110
+ query.where('orders.date_created >= ? AND orders.date_created <= ?', START_DATE, Date.today + 1.day)
111
+ end
112
+
113
+ query.order(date_created: :desc)
107
114
  end
108
115
 
109
116
  def updated_orders
@@ -43,8 +43,7 @@ module Lab
43
43
  end
44
44
 
45
45
  def self.lab_user
46
- # Use unscoped to find user regardless of location context
47
- user = User.unscoped.find_by_username('lab_daemon')
46
+ user = User.find_by_username('lab_daemon')
48
47
  return user if user
49
48
 
50
49
  god_user = User.first
@@ -9,43 +9,43 @@ module Lab
9
9
  ##
10
10
  # Pull/Push orders from/to the LIMS queue (Oops meant CouchDB).
11
11
  module Worker
12
- def self.start
12
+ def self.start(start_date: nil)
13
13
  User.current = Utils.lab_user
14
14
 
15
- fork(&method(:start_push_worker))
16
- fork(&method(:start_pull_worker))
17
- fork(&method(:start_acknowledgement_worker))
18
- fork(&method(:start_realtime_pull_worker)) if realtime_updates_enabled?
15
+ fork { start_push_worker(start_date: start_date) }
16
+ fork { start_pull_worker(start_date: start_date) }
17
+ fork { start_acknowledgement_worker(start_date: start_date) }
18
+ fork { start_realtime_pull_worker(start_date: start_date) } if realtime_updates_enabled?
19
19
 
20
20
  Process.waitall
21
21
  end
22
22
 
23
- def self.start_push_worker
23
+ def self.start_push_worker(start_date: nil)
24
24
  start_worker('push_worker') do
25
- worker = PushWorker.new(lims_api)
25
+ worker = PushWorker.new(lims_api, start_date: start_date)
26
26
 
27
27
  worker.push_orders # (wait: true)
28
28
  end
29
29
  end
30
30
 
31
- def self.start_acknowledgement_worker
31
+ def self.start_acknowledgement_worker(start_date: nil)
32
32
  start_worker('acknowledgement_worker') do
33
- worker = AcknowledgementWorker.new(lims_api)
33
+ worker = AcknowledgementWorker.new(lims_api, start_date: start_date)
34
34
  worker.push_acknowledgement
35
35
  end
36
36
  end
37
37
 
38
- def self.start_pull_worker
38
+ def self.start_pull_worker(start_date: nil)
39
39
  start_worker('pull_worker') do
40
- worker = PullWorker.new(lims_api)
40
+ worker = PullWorker.new(lims_api, start_date: start_date)
41
41
 
42
42
  worker.pull_orders
43
43
  end
44
44
  end
45
45
 
46
- def self.start_realtime_pull_worker
46
+ def self.start_realtime_pull_worker(start_date: nil)
47
47
  start_worker('realtime_pull_worker') do
48
- worker = PullWorker.new(Lims::Api::WsApi.new(Lab::Lims::Config.updates_socket))
48
+ worker = PullWorker.new(Lims::Api::WsApi.new(Lab::Lims::Config.updates_socket), start_date: start_date)
49
49
 
50
50
  worker.pull_orders
51
51
  end
@@ -12,6 +12,7 @@ module Lab
12
12
  TEST_RESULT_INDICATOR_CONCEPT_NAME = 'Lab test result indicator'
13
13
  TEST_TYPE_CONCEPT_NAME = 'Test type'
14
14
  LAB_ORDER_STATUS_CONCEPT_NAME = 'lab order status'
15
+ LAB_TEST_STATUS_CONCEPT_NAME = 'lab test status'
15
16
  UNKNOWN_SPECIMEN = 'Unknown'
16
17
  VISIT_TYPE_UUID = '8a4a4488-c2cc-11de-8d13-0010c6dffd0f'
17
18
  TEST_METHOD_CONCEPT_NAME = 'Recommended test method'
@@ -30,8 +30,7 @@ class Lab::NotificationService
30
30
  def create_notification(alert_type, alert_message)
31
31
  return if alert_type != 'LIMS'
32
32
 
33
- # Use unscoped to find user regardless of location context
34
- lab = User.unscoped.find_by(username: 'lab_daemon')
33
+ lab = User.find_by(username: 'lab_daemon')
35
34
  ActiveRecord::Base.transaction do
36
35
  alert = NotificationAlert.create!(text: alert_message.to_json, date_to_expire: Time.now + not_period.days,
37
36
  creator: lab, changed_by: lab, date_created: Time.now)
@@ -60,8 +60,14 @@ module Lab
60
60
 
61
61
  attach_test_method(order, order_params) if order_params[:test_method]
62
62
 
63
+ # Create initial order status trail
64
+ create_initial_order_status_trail(order)
65
+
63
66
  Lab::TestsService.create_tests(order, order_params[:date], order_params[:tests])
64
67
 
68
+ # Reload order to include status trails and tests
69
+ order = Lab::LabOrder.prefetch_relationships.find(order.order_id)
70
+
65
71
  Lab::LabOrderSerializer.serialize_order(
66
72
  order, requesting_clinician: add_requesting_clinician(order, order_params),
67
73
  reason_for_test: add_reason_for_test(order, order_params),
@@ -102,14 +108,16 @@ module Lab
102
108
 
103
109
  if reason_for_test
104
110
  Rails.logger.debug("Updating reason for test on order ##{order.order_id}")
105
- update_reason_for_test(order, Concept.find(reason_for_test)&.id, force_update: params.fetch('force_update', false))
111
+ update_reason_for_test(order, Concept.find(reason_for_test)&.id,
112
+ force_update: params.fetch('force_update', false))
106
113
  end
107
114
 
108
115
  Lab::LabOrderSerializer.serialize_order(order)
109
116
  end
110
117
 
111
118
  def void_order(order_id, reason)
112
- order = Lab::LabOrder.includes(%i[requesting_clinician reason_for_test target_lab comment_to_fulfiller], tests: [:result])
119
+ order = Lab::LabOrder.includes(%i[requesting_clinician reason_for_test target_lab comment_to_fulfiller],
120
+ tests: [:result])
113
121
  .find(order_id)
114
122
 
115
123
  order.requesting_clinician&.void(reason)
@@ -140,14 +148,55 @@ module Lab
140
148
  value_text: order_params['status'],
141
149
  creator: User.current.id
142
150
  )
151
+
152
+ # Save order status trail if available
153
+ save_order_status_trail(order, order_params) if order_params['status']
143
154
  end
144
155
  create_rejection_notification(order_params) if order_params['status'] == 'test-rejected'
145
156
  end
146
157
 
147
158
  def update_order_result(order_params)
148
- order = find_order(order_params['tracking_number'])
159
+ # Extract tracking number from nested structure if present
160
+ tracking_number = order_params['tracking_number'] || order_params.dig('order', 'tracking_number')
161
+ order = find_order(tracking_number)
162
+
149
163
  order_dto = Lab::Lims::OrderSerializer.serialize_order(order)
150
- patch_order_dto_with_lims_results!(order_dto, order_params['results'])
164
+
165
+ # Handle results if present in the old format
166
+ patch_order_dto_with_lims_results!(order_dto, order_params['results']) if order_params['results']
167
+
168
+ # Handle test results in NLIMS format
169
+ if order_params['tests']
170
+ # Extract test results from NLIMS tests payload
171
+ test_results = {}
172
+ order_params['tests'].each do |test_data|
173
+ test_name = test_data.dig('test_type', 'name')
174
+ next unless test_name && test_data['test_results']
175
+
176
+ test_results[test_name] = {
177
+ 'results' => test_data['test_results'].each_with_object({}) do |result, formatted|
178
+ measure_name = result.dig('measure', 'name')
179
+ result_value = result.dig('result', 'value')
180
+ next unless measure_name && result_value
181
+
182
+ formatted[measure_name] = { 'result_value' => result_value }
183
+ end,
184
+ 'result_date' => test_data['test_results'].first&.dig('result', 'result_date'),
185
+ 'result_entered_by' => {}
186
+ }
187
+ end
188
+
189
+ patch_order_dto_with_lims_results!(order_dto, test_results) unless test_results.empty?
190
+ end
191
+
192
+ # Save order status trail if available from NLIMS
193
+ if order_params['order'] && order_params['order']['status_trail']
194
+ save_order_status_trails_from_nlims(order, order_params['order']['status_trail'])
195
+ end
196
+
197
+ # Save test status trails if available from NLIMS
198
+ save_test_status_trails_from_nlims(order, order_params['tests']) if order_params['tests']
199
+
151
200
  Lab::Lims::PullWorker.new(nil).process_order(order_dto)
152
201
  end
153
202
 
@@ -160,13 +209,15 @@ module Lab
160
209
  last_order_date: Lab::LabOrder.last&.start_date&.to_date,
161
210
  lab_orders: []
162
211
  }
163
- data[:lab_orders] = orders.map do |order|
164
- Lab::LabOrderSerializer.serialize_order(
165
- order, requesting_clinician: order.requesting_clinician,
166
- reason_for_test: order.reason_for_test,
167
- target_lab: order.target_lab
168
- )
169
- end if include_data
212
+ if include_data
213
+ data[:lab_orders] = orders.map do |order|
214
+ Lab::LabOrderSerializer.serialize_order(
215
+ order, requesting_clinician: order.requesting_clinician,
216
+ reason_for_test: order.reason_for_test,
217
+ target_lab: order.target_lab
218
+ )
219
+ end
220
+ end
170
221
  data
171
222
  end
172
223
 
@@ -174,14 +225,14 @@ module Lab
174
225
 
175
226
  def create_rejection_notification(order_params)
176
227
  order = find_order order_params['tracking_number']
177
- data = { 'type': 'LIMS',
178
- 'specimen': ConceptName.find_by(concept_id: order.concept_id)&.name,
179
- 'accession_number': order&.accession_number,
180
- 'order_date': order&.start_date,
181
- 'arv_number': find_arv_number(order.patient_id),
182
- 'patient_id': result.person_id,
183
- 'ordered_by': order&.provider&.person&.name,
184
- 'rejection_reason': order_params['comments'] }.as_json
228
+ data = { type: 'LIMS',
229
+ specimen: ConceptName.find_by(concept_id: order.concept_id)&.name,
230
+ accession_number: order&.accession_number,
231
+ order_date: order&.start_date,
232
+ arv_number: find_arv_number(order.patient_id),
233
+ patient_id: result.person_id,
234
+ ordered_by: order&.provider&.person&.name,
235
+ rejection_reason: order_params['comments'] }.as_json
185
236
  NotificationService.new.create_notification('LIMS', data)
186
237
  end
187
238
 
@@ -235,7 +286,9 @@ module Lab
235
286
  encounter.encounter_datetime = order_params[:date] || Date.today
236
287
  encounter.visit = Visit.find_by_uuid(visit) if Encounter.column_names.include?('visit_id')
237
288
  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?
289
+ if Encounter.column_names.include?('program_id') && order_params[:program_id].present?
290
+ encounter.program_id = order_params[:program_id]
291
+ end
239
292
  encounter.save!
240
293
  encounter.reload
241
294
  end
@@ -247,6 +300,7 @@ module Lab
247
300
 
248
301
  concept = params.dig(:specimen, :concept)
249
302
  concept ||= params.dig(:specimen, :concept_id)
303
+ concept ||= unknown_concept_id
250
304
 
251
305
  order_type = nil
252
306
  order_type = OrderType.find_by_order_type_id!(params[:order_type_id])&.id if params[:order_type_id].present?
@@ -259,7 +313,7 @@ module Lab
259
313
  order.date_created = params[:date]&.to_date || Date.today if order.respond_to?(:date_created)
260
314
  order.start_date = params[:date]&.to_date || Date.today if order.respond_to?(:start_date)
261
315
  order.auto_expire_date = params[:end_date]
262
- # Note: comment_to_fulfiller is a has_one association, not a field
316
+ # NOTE: comment_to_fulfiller is a has_one association, not a field
263
317
  # It will be created via add_comment_to_fulfiller method
264
318
  order.accession_number = access_number
265
319
  order.orderer = User.current&.user_id
@@ -331,8 +385,7 @@ module Lab
331
385
  end
332
386
 
333
387
  def create_order_observation(order, concept_name, date, **values)
334
- # Use unscoped to find user regardless of location context
335
- creator = User.unscoped.find_by(username: 'lab_daemon')
388
+ creator = User.find_by(username: 'lab_daemon')
336
389
  User.current ||= creator
337
390
  Observation.create!(
338
391
  order:,
@@ -349,7 +402,7 @@ module Lab
349
402
  end
350
403
 
351
404
  def unknown_concept_id
352
- ConceptName.find_by_name!('Unknown').concept
405
+ ConceptName.find_by_name!('Unknown').concept_id
353
406
  end
354
407
 
355
408
  def update_reason_for_test(order, concept_id, force_update: false)
@@ -357,7 +410,10 @@ module Lab
357
410
 
358
411
  return if order.reason_for_test&.value_coded == concept_id
359
412
 
360
- raise InvalidParameterError, "Can't change reason for test once set" if order.reason_for_test&.value_coded && !force_update
413
+ if order.reason_for_test&.value_coded && !force_update
414
+ raise InvalidParameterError,
415
+ "Can't change reason for test once set"
416
+ end
361
417
 
362
418
  order.reason_for_test&.delete
363
419
  date = order.start_date if order.respond_to?(:start_date)
@@ -370,6 +426,150 @@ module Lab
370
426
  obs.void('New Status Received from LIMS')
371
427
  end
372
428
  end
429
+
430
+ def create_initial_order_status_trail(order)
431
+ create_order_status_observation(
432
+ order: order,
433
+ status: 'Drawn',
434
+ timestamp: order.start_date || order.date_created || Time.now,
435
+ updated_by: {
436
+ 'first_name' => User.current&.person&.names&.first&.given_name,
437
+ 'last_name' => User.current&.person&.names&.first&.family_name,
438
+ 'id' => User.current&.user_id&.to_s,
439
+ 'phone_number' => nil
440
+ }
441
+ )
442
+ rescue StandardError => e
443
+ Rails.logger.warn("Failed to create initial order status trail: #{e.message}")
444
+ end
445
+
446
+ def save_order_status_trail(order, status_params)
447
+ create_order_status_observation(
448
+ order: order,
449
+ status: status_params['status'],
450
+ timestamp: status_params['status_time'] || Time.now,
451
+ updated_by: status_params['updated_by'] || {}
452
+ )
453
+ end
454
+
455
+ def save_order_status_trails_from_nlims(order, status_trail)
456
+ return unless status_trail.is_a?(Array)
457
+
458
+ status_trail.each do |trail_entry|
459
+ create_order_status_observation(
460
+ order: order,
461
+ status: trail_entry['status'],
462
+ timestamp: trail_entry['timestamp'],
463
+ updated_by: trail_entry['updated_by'] || {}
464
+ )
465
+ end
466
+ end
467
+
468
+ def save_test_status_trails_from_nlims(order, tests)
469
+ return unless tests.is_a?(Array)
470
+
471
+ tests.each do |test_data|
472
+ next unless test_data['status_trail'].is_a?(Array)
473
+
474
+ # Find the test by test_type
475
+ test_name = test_data.dig('test_type', 'name')
476
+ next unless test_name
477
+
478
+ # Find concept for this test
479
+ concept = Lab::Lims::Utils.find_concept_by_name(test_name)
480
+ next unless concept
481
+
482
+ # Find the test observation
483
+ test = order.tests.find_by(value_coded: concept.concept_id)
484
+ next unless test
485
+
486
+ # Save each status trail entry
487
+ test_data['status_trail'].each do |trail_entry|
488
+ create_test_status_observation(
489
+ test: test,
490
+ status: trail_entry['status'],
491
+ timestamp: trail_entry['timestamp'],
492
+ updated_by: trail_entry['updated_by'] || {}
493
+ )
494
+ end
495
+ end
496
+ end
497
+
498
+ # Creates an observation for order status trail using obs table
499
+ def create_order_status_observation(order:, status:, timestamp:, updated_by: {})
500
+ # Find concept
501
+ order_status_concept = ConceptName.find_by(name: 'Lab Order Status')&.concept
502
+
503
+ unless order_status_concept
504
+ Rails.logger.warn('Missing Lab Order Status concept')
505
+ return
506
+ end
507
+
508
+ # Check if this exact status already exists
509
+ return if Observation.unscoped.exists?(
510
+ person_id: order.patient_id,
511
+ order_id: order.order_id,
512
+ concept_id: order_status_concept.concept_id,
513
+ value_text: status,
514
+ obs_datetime: timestamp,
515
+ voided: 0
516
+ )
517
+
518
+ # Create status observation
519
+ Observation.create!(
520
+ person_id: order.patient_id,
521
+ encounter_id: order.encounter_id,
522
+ order_id: order.order_id,
523
+ concept_id: order_status_concept.concept_id,
524
+ value_text: status, # Store status as text
525
+ obs_datetime: timestamp,
526
+ comments: updated_by.to_json,
527
+ creator: User.current&.user_id || 1,
528
+ date_created: Time.now,
529
+ uuid: SecureRandom.uuid
530
+ )
531
+ rescue StandardError => e
532
+ Rails.logger.error("Failed to create order status observation: #{e.message}")
533
+ Rails.logger.error(e.backtrace.first(5).join("\n"))
534
+ end
535
+
536
+ # Creates an observation for test status trail using obs table
537
+ def create_test_status_observation(test:, status:, timestamp:, updated_by: {})
538
+ # Find concept
539
+ test_status_concept = ConceptName.find_by(name: 'Lab Test Status')&.concept
540
+
541
+ unless test_status_concept
542
+ Rails.logger.warn('Missing Lab Test Status concept')
543
+ return
544
+ end
545
+
546
+ # Check if this exact status already exists
547
+ return if Observation.unscoped.exists?(
548
+ person_id: test.person_id,
549
+ obs_group_id: test.obs_id,
550
+ concept_id: test_status_concept.concept_id,
551
+ value_text: status,
552
+ obs_datetime: timestamp,
553
+ voided: 0
554
+ )
555
+
556
+ # Create status observation
557
+ Observation.create!(
558
+ person_id: test.person_id,
559
+ encounter_id: test.encounter_id,
560
+ obs_group_id: test.obs_id, # Link to parent test observation
561
+ concept_id: test_status_concept.concept_id,
562
+ value_text: status, # Store status as text
563
+ obs_datetime: timestamp,
564
+ comments: updated_by.to_json,
565
+ creator: User.current&.user_id || 1,
566
+ date_created: Time.now,
567
+ uuid: SecureRandom.uuid
568
+ )
569
+ rescue StandardError => e
570
+ Rails.logger.error("Failed to create test status observation: #{e.message}")
571
+ Rails.logger.error(e.backtrace.first(5).join("\n"))
572
+ end
373
573
  end
374
574
  end
375
575
  end
@@ -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(User.unscoped.find(order.creator).person_id)&.name,
68
+ 'Ordered By': Order.columns.include?('provider_id') ? order&.provider&.person&.name : Person.find(order.creator)&.name,
69
69
  Result: values }.as_json
70
70
  NotificationService.new.create_notification(result_enter_by, data)
71
71
  end
@@ -14,8 +14,7 @@ module Lab
14
14
 
15
15
  tests = filter_tests(tests, test_type_id: filters.delete(:test_type_id),
16
16
  patient_id: filters.delete(:patient_id),
17
- patient: filters.delete(:patient)
18
- )
17
+ patient: filters.delete(:patient))
19
18
 
20
19
  tests = filter_tests_by_results(tests) if %w[1 true].include?(filters[:pending_results]&.downcase)
21
20
 
@@ -29,12 +28,11 @@ module Lab
29
28
  def create_tests(order, date, tests_params)
30
29
  raise InvalidParameterError, 'tests are required' if tests_params.nil? || tests_params.empty?
31
30
 
32
-
33
31
  Lab::LabTest.transaction do
34
32
  tests_params.map do |params|
35
33
  concept_id = params[:concept_id]
36
34
  concept_id = Concept.find_concept_by_uuid(params[:concept]).id if concept_id.nil?
37
-
35
+
38
36
  test = Lab::LabTest.create!(
39
37
  concept_id: ConceptName.find_by_name!(Lab::Metadata::TEST_TYPE_CONCEPT_NAME)
40
38
  .concept_id,
@@ -45,6 +43,9 @@ module Lab
45
43
  value_coded: concept_id
46
44
  )
47
45
 
46
+ # Create initial test status trail
47
+ create_initial_test_status_trail(test, date)
48
+
48
49
  Lab::TestSerializer.serialize(test, order:)
49
50
  end
50
51
  end
@@ -100,6 +101,49 @@ module Lab
100
101
  value_coded: test_type_id
101
102
  )
102
103
  end
104
+
105
+ def create_initial_test_status_trail(test, date)
106
+ # Find concept
107
+ test_status_concept = ConceptName.find_by(name: 'Lab Test Status')&.concept
108
+
109
+ unless test_status_concept
110
+ Rails.logger.warn('Missing Lab Test Status concept')
111
+ return
112
+ end
113
+
114
+ timestamp = date || test.obs_datetime || Time.now
115
+
116
+ # Check if 'Drawn' status already exists for this test
117
+ return if Observation.unscoped.exists?(
118
+ person_id: test.person_id,
119
+ obs_group_id: test.obs_id,
120
+ concept_id: test_status_concept.concept_id,
121
+ value_text: 'Drawn',
122
+ voided: 0
123
+ )
124
+
125
+ # Create status observation with 'Drawn' as initial status
126
+ Observation.create!(
127
+ person_id: test.person_id,
128
+ encounter_id: test.encounter_id,
129
+ obs_group_id: test.obs_id, # Link to parent test observation
130
+ concept_id: test_status_concept.concept_id,
131
+ value_text: 'Drawn', # Store status as text
132
+ obs_datetime: timestamp,
133
+ status: 'FINAL',
134
+ comments: {
135
+ 'first_name' => User.current&.person&.names&.first&.given_name,
136
+ 'last_name' => User.current&.person&.names&.first&.family_name,
137
+ 'id' => User.current&.user_id&.to_s,
138
+ 'phone_number' => nil
139
+ }.to_json,
140
+ creator: User.current&.user_id || 1,
141
+ date_created: Time.now,
142
+ uuid: SecureRandom.uuid
143
+ )
144
+ rescue StandardError => e
145
+ Rails.logger.warn("Failed to create initial test status trail: #{e.message}")
146
+ end
103
147
  end
104
148
  end
105
149
  end
@@ -15,8 +15,7 @@ module Lab
15
15
  end
16
16
 
17
17
  def authenticate_user(username:, password:, user_agent:, request_ip:)
18
- # Use unscoped for authentication - should not be location-dependent
19
- user = User.unscoped.find_by_username username
18
+ user = User.find_by_username username
20
19
  encrypted_pass = Password.new(user.password)
21
20
  if encrypted_pass == password
22
21
  generate_token(user, user_agent, request_ip)
@@ -31,7 +30,7 @@ module Lab
31
30
  ##
32
31
  # Validate that the username doesn't already exists
33
32
  def validate(username:)
34
- raise UnprocessableEntityError, 'Username already exists' if User.unscoped.find_by_username username
33
+ raise UnprocessableEntityError, 'Username already exists' if User.find_by_username username
35
34
  end
36
35
 
37
36
  def create_lims_person
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This migration creates concepts for lab order and test status tracking using obs table
4
+ # Status values are stored as text (value_text), not coded values
5
+ class CreateLabStatusConcepts < ActiveRecord::Migration[5.2]
6
+ def up
7
+ ActiveRecord::Base.transaction do
8
+ # Find concept class and datatype
9
+ concept_class = ConceptClass.find_by(name: 'Finding') || ConceptClass.find_by(name: 'Misc')
10
+ text_datatype = ConceptDatatype.find_by(name: 'Text')
11
+
12
+ # Create Lab Order Status concept (stores status as value_text)
13
+ unless ConceptName.exists?(name: 'Lab Order Status')
14
+ order_status_concept = create_concept(
15
+ name: 'Lab Order Status',
16
+ class_id: concept_class.id,
17
+ datatype_id: text_datatype.id,
18
+ is_set: 0
19
+ )
20
+ puts "Created 'Lab Order Status' concept with ID: #{order_status_concept.concept_id}"
21
+ end
22
+
23
+ # Create Lab Test Status concept (stores status as value_text)
24
+ unless ConceptName.exists?(name: 'Lab Test Status')
25
+ test_status_concept = create_concept(
26
+ name: 'Lab Test Status',
27
+ class_id: concept_class.id,
28
+ datatype_id: text_datatype.id,
29
+ is_set: 0
30
+ )
31
+ puts "Created 'Lab Test Status' concept with ID: #{test_status_concept.concept_id}"
32
+ end
33
+
34
+ puts 'Lab status concepts created successfully'
35
+ end
36
+ end
37
+
38
+ def down
39
+ ActiveRecord::Base.transaction do
40
+ # Remove concepts
41
+ concepts_to_remove = ['Lab Order Status', 'Lab Test Status']
42
+
43
+ concepts_to_remove.each do |name|
44
+ concept = ConceptName.find_by(name: name)&.concept
45
+ next unless concept
46
+
47
+ ConceptName.where(concept: concept).destroy_all
48
+ concept.destroy
49
+ end
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def create_concept(name:, class_id:, datatype_id:, is_set:)
56
+ concept = Concept.create!(
57
+ class_id: class_id,
58
+ datatype_id: datatype_id,
59
+ short_name: name,
60
+ retired: 0,
61
+ is_set: is_set,
62
+ creator: 1,
63
+ date_created: Time.current,
64
+ uuid: SecureRandom.uuid
65
+ )
66
+
67
+ ConceptName.create!(
68
+ concept: concept,
69
+ name: name,
70
+ locale: 'en',
71
+ locale_preferred: 1,
72
+ concept_name_type: 'FULLY_SPECIFIED',
73
+ creator: 1,
74
+ date_created: Time.current,
75
+ uuid: SecureRandom.uuid
76
+ )
77
+
78
+ concept
79
+ end
80
+ end