his_emr_api_lab 2.4.4 → 2.4.6
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/services/lab/acknowledgement_service.rb +4 -2
- data/app/services/lab/lims/acknowledgement_worker.rb +6 -2
- data/app/services/lab/lims/api/rest_api.rb +16 -16
- data/app/services/lab/lims/pull_worker.rb +7 -5
- data/app/services/lab/lims/push_worker.rb +11 -5
- data/app/services/lab/lims/worker.rb +28 -18
- data/app/services/lab/notification_service.rb +33 -6
- data/app/services/lab/orders_search_service.rb +8 -1
- data/app/services/lab/orders_service.rb +2 -2
- data/app/services/lab/tests_service.rb +1 -1
- data/lib/lab/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2ac3fb7e82f896ac9c5f0545aa1c7e97290e7aaf11cc149fd6e8f385938397dc
|
|
4
|
+
data.tar.gz: 707411853618c5b391c99ecdc043bdbc71ef5df5a1b801567869d279b068c6d7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e0ae79db2d3cb66121a3daf92c1fb0e141a09309dc148f604da49cb167203fe22c77e50c843a9a10e6531fe505cb8241c5212668a643bff38ef5f851d8232a72
|
|
7
|
+
data.tar.gz: 32ec983d0e45aa6af8b280ae1850544ad42bb250465cdfa45a597818e589c3ff9238d7ebea7ba7cdc7c613ae4f2368fc05c2c854809fa2f1d2d8b76ed82c1bcf
|
|
@@ -14,11 +14,13 @@ module Lab
|
|
|
14
14
|
date_received: params[:date_received])
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def acknowledgements_pending_sync(batch_size, start_date: nil, last_order_id: 0)
|
|
17
|
+
def acknowledgements_pending_sync(batch_size, start_date: nil, last_order_id: 0, accession_numbers: [], patient_id: nil)
|
|
18
18
|
query = Lab::LabAcknowledgement.joins(:order).where(pushed: false)
|
|
19
19
|
.where('lims_acknowledgement_statuses.order_id > ?', last_order_id)
|
|
20
20
|
query = query.where('orders.date_created >= ?', start_date) if start_date
|
|
21
|
-
query.
|
|
21
|
+
query = query.where(orders: {accession_number: accession_numbers}) if accession_numbers.any?
|
|
22
|
+
query = query.where(orders: {patient_id: patient_id}) if patient_id
|
|
23
|
+
query.order('lims_acknowledgement_statuses.order_id DESC').limit(batch_size)
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
def push_acknowledgement(acknowledgement, lims_api)
|
|
@@ -4,15 +4,17 @@ module Lab
|
|
|
4
4
|
module Lims
|
|
5
5
|
# This class is responsible for handling the acknowledgement of lab orders
|
|
6
6
|
class AcknowledgementWorker
|
|
7
|
-
attr_reader :lims_api, :start_date
|
|
7
|
+
attr_reader :lims_api, :start_date, :accession_numbers, :patient_id
|
|
8
8
|
|
|
9
9
|
include Utils # for logger
|
|
10
10
|
|
|
11
11
|
SECONDS_TO_WAIT_FOR_ORDERS = 30
|
|
12
12
|
|
|
13
|
-
def initialize(lims_api, start_date: nil)
|
|
13
|
+
def initialize(lims_api, start_date: nil, accession_numbers: [], patient_id: nil)
|
|
14
14
|
@lims_api = lims_api
|
|
15
15
|
@start_date = start_date
|
|
16
|
+
@accession_numbers = accession_numbers
|
|
17
|
+
@patient_id = patient_id
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def push_acknowledgement(batch_size: 1000, wait: false)
|
|
@@ -21,6 +23,8 @@ module Lab
|
|
|
21
23
|
logger.info('Looking for new acknowledgements to push to LIMS...')
|
|
22
24
|
acknowledgements = Lab::AcknowledgementService.acknowledgements_pending_sync(batch_size,
|
|
23
25
|
start_date: start_date,
|
|
26
|
+
accession_numbers: accession_numbers,
|
|
27
|
+
patient_id: patient_id,
|
|
24
28
|
last_order_id: last_order_id).all
|
|
25
29
|
|
|
26
30
|
logger.debug("Found #{acknowledgements.size} acknowledgements...")
|
|
@@ -75,8 +75,8 @@ module Lab
|
|
|
75
75
|
{ tracking_number: order_dto[:tracking_number] }
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
def consume_orders(*_args, patient_id: nil, start_date: nil, **_kwargs)
|
|
79
|
-
orders_pending_updates(patient_id, start_date: start_date).each do |order|
|
|
78
|
+
def consume_orders(*_args, patient_id: nil, start_date: nil, accession_numbers: [], **_kwargs)
|
|
79
|
+
orders_pending_updates(patient_id, start_date: start_date, accession_numbers: accession_numbers).each do |order|
|
|
80
80
|
order_dto = Lab::Lims::OrderSerializer.serialize_order(order)
|
|
81
81
|
|
|
82
82
|
# Always fetch the full order from NLIMS to get status trails
|
|
@@ -84,11 +84,6 @@ module Lab
|
|
|
84
84
|
lims_order = find_lims_order(order.accession_number)
|
|
85
85
|
patch_order_dto_with_lims_order!(order_dto, lims_order)
|
|
86
86
|
|
|
87
|
-
Rails.logger.debug("NLIMS order structure for #{order.accession_number}:")
|
|
88
|
-
Rails.logger.debug(" Has 'order' key: #{lims_order.key?('order')}")
|
|
89
|
-
Rails.logger.debug(" Has 'data' key: #{lims_order.key?('data')}")
|
|
90
|
-
Rails.logger.debug(" Top level keys: #{lims_order.keys.inspect}")
|
|
91
|
-
|
|
92
87
|
# Also extract status trails from the NLIMS order
|
|
93
88
|
# Note: NLIMS might return order data under 'order' or 'data.order'
|
|
94
89
|
order_data = lims_order['order'] || lims_order.dig('data', 'order') || lims_order
|
|
@@ -606,18 +601,20 @@ module Lab
|
|
|
606
601
|
}
|
|
607
602
|
end
|
|
608
603
|
|
|
609
|
-
def orders_pending_updates(patient_id = nil, start_date: nil)
|
|
610
|
-
Rails.logger.info(
|
|
604
|
+
def orders_pending_updates(patient_id = nil, start_date: nil, accession_numbers: [])
|
|
605
|
+
Rails.logger.info("Looking for orders that need to be updated... [patient_id=#{patient_id}] [start_date=#{start_date}]")
|
|
611
606
|
orders = {}
|
|
612
|
-
|
|
613
|
-
orders_without_specimen(patient_id, start_date: start_date).each { |order| orders[order.order_id] = order }
|
|
614
|
-
|
|
615
|
-
|
|
607
|
+
Rails.logger.info("Fetching orders without specimen...")
|
|
608
|
+
orders_without_specimen(patient_id, start_date: start_date, accession_numbers: accession_numbers).each { |order| orders[order.order_id] = order }
|
|
609
|
+
Rails.logger.info("Fetching orders without results...")
|
|
610
|
+
orders_without_results(patient_id, start_date: start_date, accession_numbers: accession_numbers).each { |order| orders[order.order_id] = order }
|
|
611
|
+
Rails.logger.info("Fetching orders without reason...")
|
|
612
|
+
orders_without_reason(patient_id, start_date: start_date, accession_numbers: accession_numbers).each { |order| orders[order.order_id] = order }
|
|
616
613
|
|
|
617
614
|
orders.values
|
|
618
615
|
end
|
|
619
616
|
|
|
620
|
-
def orders_without_specimen(patient_id = nil, start_date: nil)
|
|
617
|
+
def orders_without_specimen(patient_id = nil, start_date: nil, accession_numbers: [])
|
|
621
618
|
Rails.logger.debug('Looking for orders without a specimen')
|
|
622
619
|
unknown_specimen = ConceptName.where(name: Lab::Metadata::UNKNOWN_SPECIMEN)
|
|
623
620
|
.select(:concept_id)
|
|
@@ -625,21 +622,23 @@ module Lab
|
|
|
625
622
|
.where.not(accession_number: Lab::LimsOrderMapping.select(:lims_id))
|
|
626
623
|
orders = orders.where(patient_id:) if patient_id
|
|
627
624
|
orders = orders.where('orders.date_created >= ?', start_date) if start_date
|
|
625
|
+
orders = orders.where(accession_number: accession_numbers) if accession_numbers.any?
|
|
628
626
|
|
|
629
627
|
orders
|
|
630
628
|
end
|
|
631
629
|
|
|
632
|
-
def orders_without_results(patient_id = nil, start_date: nil)
|
|
630
|
+
def orders_without_results(patient_id = nil, start_date: nil, accession_numbers: [])
|
|
633
631
|
Rails.logger.debug('Looking for orders without a result')
|
|
634
632
|
# Lab::OrdersSearchService.find_orders_without_results(patient_id: patient_id)
|
|
635
633
|
# .where.not(accession_number: Lab::LimsOrderMapping.select(:lims_id).where("pulled_at IS NULL"))
|
|
636
634
|
orders = Lab::OrdersSearchService.find_orders_without_results(patient_id:)
|
|
637
635
|
.where(order_id: Lab::LimsOrderMapping.select(:order_id))
|
|
638
636
|
orders = orders.where('orders.date_created >= ?', start_date) if start_date
|
|
637
|
+
orders = orders.where(accession_number: accession_numbers) if accession_numbers.any?
|
|
639
638
|
orders
|
|
640
639
|
end
|
|
641
640
|
|
|
642
|
-
def orders_without_reason(patient_id = nil, start_date: nil)
|
|
641
|
+
def orders_without_reason(patient_id = nil, start_date: nil, accession_numbers: [])
|
|
643
642
|
Rails.logger.debug('Looking for orders without a reason for test')
|
|
644
643
|
orders = Lab::LabOrder.joins(:reason_for_test)
|
|
645
644
|
.merge(Observation.where(value_coded: nil, value_text: nil))
|
|
@@ -647,6 +646,7 @@ module Lab
|
|
|
647
646
|
.where.not(accession_number: Lab::LimsOrderMapping.select(:lims_id))
|
|
648
647
|
orders = orders.where(patient_id:) if patient_id
|
|
649
648
|
orders = orders.where('orders.date_created >= ?', start_date) if start_date
|
|
649
|
+
orders = orders.where(accession_number: accession_numbers) if accession_numbers.any?
|
|
650
650
|
|
|
651
651
|
orders
|
|
652
652
|
end
|
|
@@ -7,15 +7,17 @@ module Lab
|
|
|
7
7
|
##
|
|
8
8
|
# Pulls orders from a Lims API object and saves them to the local database.
|
|
9
9
|
class PullWorker
|
|
10
|
-
attr_reader :lims_api, :start_date
|
|
10
|
+
attr_reader :lims_api, :start_date, :accession_numbers
|
|
11
11
|
|
|
12
12
|
include Utils # for logger
|
|
13
13
|
|
|
14
14
|
LIMS_LOG_PATH = Rails.root.join('log', 'lims')
|
|
15
15
|
|
|
16
|
-
def initialize(lims_api, start_date: nil)
|
|
16
|
+
def initialize(lims_api, start_date: nil, accession_numbers: [], patient_id: nil)
|
|
17
17
|
@lims_api = lims_api
|
|
18
18
|
@start_date = start_date
|
|
19
|
+
@accession_numbers = accession_numbers
|
|
20
|
+
@patient_id = patient_id
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
##
|
|
@@ -23,7 +25,7 @@ module Lab
|
|
|
23
25
|
def pull_orders(batch_size: 10_000, **)
|
|
24
26
|
logger.info("Retrieving LIMS orders starting from #{last_seq}")
|
|
25
27
|
|
|
26
|
-
lims_api.consume_orders(from: last_seq, limit: batch_size, start_date: start_date, **) do |order_dto, context|
|
|
28
|
+
lims_api.consume_orders(from: last_seq, limit: batch_size, start_date: start_date, accession_numbers: accession_numbers, **) do |order_dto, context|
|
|
27
29
|
logger.debug("Retrieved order ##{order_dto[:tracking_number]}: #{order_dto}")
|
|
28
30
|
|
|
29
31
|
patient = find_patient_by_nhid(order_dto[:patient][:id], order_dto[:tracking_number])
|
|
@@ -395,7 +397,7 @@ module Lab
|
|
|
395
397
|
value_text: status_data['status'], # Store status as text
|
|
396
398
|
obs_datetime: timestamp,
|
|
397
399
|
comments: updated_by.to_json,
|
|
398
|
-
creator: User.current&.user_id
|
|
400
|
+
creator: User.current&.user_id,
|
|
399
401
|
location_id:,
|
|
400
402
|
date_created: Time.now,
|
|
401
403
|
uuid: SecureRandom.uuid
|
|
@@ -463,7 +465,7 @@ module Lab
|
|
|
463
465
|
value_text: status_data['status'], # Store status as text
|
|
464
466
|
obs_datetime: timestamp,
|
|
465
467
|
comments: updated_by.to_json,
|
|
466
|
-
creator: User.current&.user_id
|
|
468
|
+
creator: User.current&.user_id,
|
|
467
469
|
location_id:,
|
|
468
470
|
date_created: Time.now,
|
|
469
471
|
uuid: SecureRandom.uuid
|
|
@@ -5,16 +5,18 @@ module Lab
|
|
|
5
5
|
##
|
|
6
6
|
# Pushes all local orders to a LIMS Api object.
|
|
7
7
|
class PushWorker
|
|
8
|
-
attr_reader :lims_api, :start_date
|
|
8
|
+
attr_reader :lims_api, :start_date, :accession_numbers, :patient_id
|
|
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, start_date: nil)
|
|
15
|
+
def initialize(lims_api, start_date: nil, accession_numbers: [], patient_id: nil)
|
|
16
16
|
@lims_api = lims_api
|
|
17
17
|
@start_date = start_date
|
|
18
|
+
@accession_numbers = accession_numbers
|
|
19
|
+
@patient_id = patient_id
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
def push_orders(batch_size: 1000, wait: false)
|
|
@@ -109,7 +111,8 @@ module Lab
|
|
|
109
111
|
else
|
|
110
112
|
query.where('orders.date_created >= ? AND orders.date_created <= ?', START_DATE, Date.today + 1.day)
|
|
111
113
|
end
|
|
112
|
-
|
|
114
|
+
query = query.where(accession_number: accession_numbers) if accession_numbers.any?
|
|
115
|
+
query = query.where(patient_id: patient_id) if patient_id
|
|
113
116
|
query.order(date_created: :desc)
|
|
114
117
|
end
|
|
115
118
|
|
|
@@ -130,11 +133,14 @@ module Lab
|
|
|
130
133
|
|
|
131
134
|
def voided_orders
|
|
132
135
|
Rails.logger.debug('Looking for voided orders that are being tracked by LIMS...')
|
|
133
|
-
Lab::LabOrder.unscoped
|
|
136
|
+
orders = Lab::LabOrder.unscoped
|
|
134
137
|
.where(order_type: OrderType.where(name: Lab::Metadata::ORDER_TYPE_NAME),
|
|
135
138
|
order_id: Lab::LimsOrderMapping.all.select(:order_id),
|
|
136
139
|
voided: 1)
|
|
137
|
-
|
|
140
|
+
orders = orders.where(accession_number: accession_numbers) if accession_numbers.any?
|
|
141
|
+
orders = orders.where(patient_id: patient_id) if patient_id
|
|
142
|
+
orders = orders.order(date_voided: :desc)
|
|
143
|
+
orders
|
|
138
144
|
end
|
|
139
145
|
|
|
140
146
|
##
|
|
@@ -9,46 +9,46 @@ 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(start_date: nil)
|
|
12
|
+
def self.start(start_date: nil, accession_numbers: [], patient_id: nil)
|
|
13
13
|
User.current = Utils.lab_user
|
|
14
14
|
|
|
15
15
|
# Eager load classes before forking to avoid autoloading issues in child processes
|
|
16
16
|
Rails.application.eager_load! if defined?(Rails) && Rails.env.development?
|
|
17
17
|
|
|
18
|
-
fork { start_push_worker(start_date: start_date) }
|
|
19
|
-
fork { start_pull_worker(start_date: start_date) }
|
|
20
|
-
fork { start_acknowledgement_worker(start_date: start_date) }
|
|
21
|
-
fork { start_realtime_pull_worker(start_date: start_date) } if realtime_updates_enabled?
|
|
18
|
+
fork { start_push_worker(start_date: start_date, accession_numbers: accession_numbers, patient_id: patient_id) }
|
|
19
|
+
fork { start_pull_worker(start_date: start_date, accession_numbers: accession_numbers, patient_id: patient_id) }
|
|
20
|
+
fork { start_acknowledgement_worker(start_date: start_date, accession_numbers: accession_numbers, patient_id: patient_id) }
|
|
21
|
+
fork { start_realtime_pull_worker(start_date: start_date, accession_numbers: accession_numbers, patient_id: patient_id) } if realtime_updates_enabled?
|
|
22
22
|
|
|
23
23
|
Process.waitall
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
def self.start_push_worker(start_date: nil)
|
|
27
|
-
start_worker('push_worker') do
|
|
28
|
-
worker = PushWorker.new(lims_api, start_date: start_date)
|
|
26
|
+
def self.start_push_worker(start_date: nil, accession_numbers: [], patient_id: nil)
|
|
27
|
+
start_worker('push_worker', accession_numbers: accession_numbers, patient_id: patient_id) do
|
|
28
|
+
worker = PushWorker.new(lims_api, start_date: start_date, accession_numbers: accession_numbers, patient_id: patient_id)
|
|
29
29
|
|
|
30
30
|
worker.push_orders # (wait: true)
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
def self.start_acknowledgement_worker(start_date: nil)
|
|
35
|
-
start_worker('acknowledgement_worker') do
|
|
36
|
-
worker = AcknowledgementWorker.new(lims_api, start_date: start_date)
|
|
34
|
+
def self.start_acknowledgement_worker(start_date: nil, accession_numbers: [], patient_id: nil)
|
|
35
|
+
start_worker('acknowledgement_worker', accession_numbers: accession_numbers, patient_id: patient_id) do
|
|
36
|
+
worker = AcknowledgementWorker.new(lims_api, start_date: start_date, accession_numbers: accession_numbers, patient_id: patient_id)
|
|
37
37
|
worker.push_acknowledgement
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def self.start_pull_worker(start_date: nil)
|
|
42
|
-
start_worker('pull_worker') do
|
|
43
|
-
worker = PullWorker.new(lims_api, start_date: start_date)
|
|
41
|
+
def self.start_pull_worker(start_date: nil, accession_numbers: [], patient_id: nil)
|
|
42
|
+
start_worker('pull_worker', accession_numbers: accession_numbers, patient_id: patient_id) do
|
|
43
|
+
worker = PullWorker.new(lims_api, start_date: start_date, accession_numbers: accession_numbers, patient_id: patient_id)
|
|
44
44
|
|
|
45
45
|
worker.pull_orders
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
def self.start_realtime_pull_worker(start_date: nil)
|
|
50
|
-
start_worker('realtime_pull_worker') do
|
|
51
|
-
worker = PullWorker.new(Lims::Api::WsApi.new(Lab::Lims::Config.updates_socket), start_date: start_date)
|
|
49
|
+
def self.start_realtime_pull_worker(start_date: nil, accession_numbers: [], patient_id: nil)
|
|
50
|
+
start_worker('realtime_pull_worker', accession_numbers: accession_numbers, patient_id: patient_id) do
|
|
51
|
+
worker = PullWorker.new(Lims::Api::WsApi.new(Lab::Lims::Config.updates_socket), start_date: start_date, accession_numbers: accession_numbers, patient_id: patient_id)
|
|
52
52
|
|
|
53
53
|
worker.pull_orders
|
|
54
54
|
end
|
|
@@ -57,7 +57,8 @@ module Lab
|
|
|
57
57
|
LOG_FILES_TO_KEEP = 5
|
|
58
58
|
LOG_FILE_SIZE = 500.megabytes
|
|
59
59
|
|
|
60
|
-
def self.start_worker(worker_name)
|
|
60
|
+
def self.start_worker(worker_name, accession_numbers: [], patient_id: nil)
|
|
61
|
+
worker_name = create_file_name(worker_name, accession_numbers: accession_numbers, patient_id: patient_id)
|
|
61
62
|
Rails.logger = LoggerMultiplexor.new(file_logger(worker_name), $stdout)
|
|
62
63
|
ActiveRecord::Base.logger = Rails.logger
|
|
63
64
|
Rails.logger.level = :debug
|
|
@@ -78,6 +79,15 @@ module Lab
|
|
|
78
79
|
Logger.new(log_path("#{worker_name}.log"), LOG_FILES_TO_KEEP, LOG_FILE_SIZE)
|
|
79
80
|
end
|
|
80
81
|
|
|
82
|
+
def self.create_file_name(worker_name, accession_numbers: [], patient_id: nil)
|
|
83
|
+
accession_numbers = accession_numbers.nil? ? [] : accession_numbers
|
|
84
|
+
accession_numbers = accession_numbers.map { |accession| accession.gsub(/[^a-zA-Z0-9]/, '') }.join('_')
|
|
85
|
+
filename = "#{worker_name}"
|
|
86
|
+
filename += "_#{accession_numbers}" unless accession_numbers.empty?
|
|
87
|
+
filename += "_#{patient_id}" if patient_id
|
|
88
|
+
filename
|
|
89
|
+
end
|
|
90
|
+
|
|
81
91
|
def self.log_path(filename)
|
|
82
92
|
Lab::Lims::Utils::LIMS_LOG_PATH.join(filename)
|
|
83
93
|
end
|
|
@@ -88,13 +88,40 @@ class Lab::NotificationService
|
|
|
88
88
|
Observation.find_by(order_id: order_id)&.location_id
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
# Was one exists? + one create per recipient (2 round trips x N users,
|
|
92
|
+
# sequentially) - the slow part users() and order_location() were feeding
|
|
93
|
+
# into. notification_alert_recipient's primary key is (alert_id, user_id),
|
|
94
|
+
# so a single batched insert_all does the "notify each user at most once
|
|
95
|
+
# per alert" dedup at the DB level instead of a round trip per user.
|
|
96
|
+
#
|
|
97
|
+
# Plain insert_all, no unique_by/on_duplicate kwargs - verified live
|
|
98
|
+
# against this app's Rails 8.1/mysql2 combination:
|
|
99
|
+
# - relation.rb's insert_all hardcodes InsertAll.execute(..., on_duplicate:
|
|
100
|
+
# :skip, ...) internally; on_duplicate isn't a caller-facing keyword at
|
|
101
|
+
# all (passing it raises "unknown keyword: :on_duplicate"), and skip-on-
|
|
102
|
+
# duplicate is already what plain insert_all does by default.
|
|
103
|
+
# - unique_by IS a caller-facing keyword, but only for adapters that need
|
|
104
|
+
# to be told which constraint backs the ON CONFLICT target (e.g.
|
|
105
|
+
# Postgres). Passing it here raises "Mysql2Adapter does not support
|
|
106
|
+
# :unique_by" - MySQL's own INSERT IGNORE applies to any unique/primary
|
|
107
|
+
# -key violation on the table, so it needs nothing extra to know that
|
|
108
|
+
# (alert_id, user_id) is the key to dedup against.
|
|
109
|
+
#
|
|
110
|
+
# insert_all skips AR callbacks, so uuid - the one thing the model's
|
|
111
|
+
# before_create/before_save would have set - is generated here;
|
|
112
|
+
# alert_read/cleared/date_changed are left to their column defaults.
|
|
113
|
+
# Chunked so one notification fan-out to a very large recipient list
|
|
114
|
+
# doesn't become a single giant statement.
|
|
115
|
+
BATCH_SIZE = 1000
|
|
116
|
+
|
|
117
|
+
def notify(notification_alert, recipients)
|
|
118
|
+
rows = recipients.pluck(:user_id).map do |user_id|
|
|
119
|
+
{ alert_id: notification_alert.id, user_id: user_id, uuid: SecureRandom.uuid }
|
|
120
|
+
end
|
|
121
|
+
return if rows.empty?
|
|
94
122
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
)
|
|
123
|
+
rows.each_slice(BATCH_SIZE) do |batch|
|
|
124
|
+
NotificationAlertRecipient.insert_all(batch)
|
|
98
125
|
end
|
|
99
126
|
end
|
|
100
127
|
|
|
@@ -25,7 +25,14 @@ module Lab
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def find_orders_without_results(patient_id: nil)
|
|
28
|
-
|
|
28
|
+
# order_id IS NOT NULL matters here, not just there: SQL's NOT IN is
|
|
29
|
+
# poisoned by a single NULL in the subquery — `x NOT IN (1, NULL)` is
|
|
30
|
+
# never true for any x, because `x <> NULL` is unknown rather than
|
|
31
|
+
# true/false, and one unknown collapses the whole AND chain. Some
|
|
32
|
+
# legacy "Lab test result" observations have a NULL order_id, so
|
|
33
|
+
# without this filter, one such row for a patient silently zeroes out
|
|
34
|
+
# every order this returns for them, not just the row with the NULL.
|
|
35
|
+
results_query = Lab::LabResult.all.where.not(order_id: nil)
|
|
29
36
|
results_query = results_query.where(person_id: patient_id) if patient_id
|
|
30
37
|
|
|
31
38
|
query = Lab::LabOrder.where.not(order_id: results_query.select(:order_id))
|
|
@@ -600,7 +600,7 @@ module Lab
|
|
|
600
600
|
value_text: status, # Store status as text
|
|
601
601
|
obs_datetime: timestamp,
|
|
602
602
|
comments: updated_by.to_json,
|
|
603
|
-
creator: User.current&.user_id
|
|
603
|
+
creator: User.current&.user_id,
|
|
604
604
|
location_id:,
|
|
605
605
|
date_created: Time.now,
|
|
606
606
|
uuid: SecureRandom.uuid
|
|
@@ -641,7 +641,7 @@ module Lab
|
|
|
641
641
|
value_text: status, # Store status as text
|
|
642
642
|
obs_datetime: timestamp,
|
|
643
643
|
comments: updated_by.to_json,
|
|
644
|
-
creator: User.current&.user_id
|
|
644
|
+
creator: User.current&.user_id,
|
|
645
645
|
location_id:,
|
|
646
646
|
date_created: Time.now,
|
|
647
647
|
uuid: SecureRandom.uuid
|
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.6
|
|
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-09-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: couchrest
|