mahis_his_emr_api_lab 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +71 -0
  4. data/Rakefile +32 -0
  5. data/app/controllers/lab/application_controller.rb +6 -0
  6. data/app/controllers/lab/labels_controller.rb +17 -0
  7. data/app/controllers/lab/orders_controller.rb +78 -0
  8. data/app/controllers/lab/reasons_for_test_controller.rb +9 -0
  9. data/app/controllers/lab/results_controller.rb +20 -0
  10. data/app/controllers/lab/specimen_types_controller.rb +15 -0
  11. data/app/controllers/lab/test_result_indicators_controller.rb +9 -0
  12. data/app/controllers/lab/test_types_controller.rb +15 -0
  13. data/app/controllers/lab/tests_controller.rb +25 -0
  14. data/app/controllers/lab/users_controller.rb +32 -0
  15. data/app/jobs/lab/application_job.rb +4 -0
  16. data/app/jobs/lab/push_order_job.rb +12 -0
  17. data/app/jobs/lab/update_patient_orders_job.rb +32 -0
  18. data/app/jobs/lab/void_order_job.rb +17 -0
  19. data/app/mailers/lab/application_mailer.rb +6 -0
  20. data/app/models/lab/application_record.rb +5 -0
  21. data/app/models/lab/lab_accession_number_counter.rb +13 -0
  22. data/app/models/lab/lab_acknowledgement.rb +6 -0
  23. data/app/models/lab/lab_encounter.rb +7 -0
  24. data/app/models/lab/lab_order.rb +58 -0
  25. data/app/models/lab/lab_result.rb +31 -0
  26. data/app/models/lab/lab_test.rb +19 -0
  27. data/app/models/lab/lims_failed_import.rb +4 -0
  28. data/app/models/lab/lims_order_mapping.rb +10 -0
  29. data/app/models/lab/order_extension.rb +14 -0
  30. data/app/serializers/lab/lab_order_serializer.rb +56 -0
  31. data/app/serializers/lab/result_serializer.rb +36 -0
  32. data/app/serializers/lab/test_serializer.rb +52 -0
  33. data/app/services/lab/accession_number_service.rb +77 -0
  34. data/app/services/lab/acknowledgement_service.rb +47 -0
  35. data/app/services/lab/concepts_service.rb +82 -0
  36. data/app/services/lab/json_web_token_service.rb +20 -0
  37. data/app/services/lab/labelling_service/order_label.rb +106 -0
  38. data/app/services/lab/lims/acknowledgement_serializer.rb +29 -0
  39. data/app/services/lab/lims/acknowledgement_worker.rb +37 -0
  40. data/app/services/lab/lims/api/blackhole_api.rb +21 -0
  41. data/app/services/lab/lims/api/couchdb_api.rb +53 -0
  42. data/app/services/lab/lims/api/mysql_api.rb +316 -0
  43. data/app/services/lab/lims/api/rest_api.rb +434 -0
  44. data/app/services/lab/lims/api/ws_api.rb +121 -0
  45. data/app/services/lab/lims/api_factory.rb +19 -0
  46. data/app/services/lab/lims/config.rb +105 -0
  47. data/app/services/lab/lims/exceptions.rb +11 -0
  48. data/app/services/lab/lims/migrator.rb +216 -0
  49. data/app/services/lab/lims/order_dto.rb +105 -0
  50. data/app/services/lab/lims/order_serializer.rb +251 -0
  51. data/app/services/lab/lims/pull_worker.rb +314 -0
  52. data/app/services/lab/lims/push_worker.rb +152 -0
  53. data/app/services/lab/lims/utils.rb +91 -0
  54. data/app/services/lab/lims/worker.rb +94 -0
  55. data/app/services/lab/metadata.rb +26 -0
  56. data/app/services/lab/notification_service.rb +72 -0
  57. data/app/services/lab/orders_search_service.rb +72 -0
  58. data/app/services/lab/orders_service.rb +330 -0
  59. data/app/services/lab/results_service.rb +166 -0
  60. data/app/services/lab/tests_service.rb +105 -0
  61. data/app/services/lab/user_service.rb +62 -0
  62. data/config/routes.rb +28 -0
  63. data/db/migrate/20210126092910_create_lab_lab_accession_number_counters.rb +12 -0
  64. data/db/migrate/20210310115457_create_lab_lims_order_mappings.rb +15 -0
  65. data/db/migrate/20210323080140_change_lims_id_to_string_in_lims_order_mapping.rb +15 -0
  66. data/db/migrate/20210326195504_add_order_revision_to_lims_order_mapping.rb +5 -0
  67. data/db/migrate/20210407071728_create_lab_lims_failed_imports.rb +19 -0
  68. data/db/migrate/20210610095024_fix_numeric_results_value_type.rb +20 -0
  69. data/db/migrate/20210807111531_add_default_to_lims_order_mapping.rb +7 -0
  70. data/lib/auto12epl.rb +201 -0
  71. data/lib/couch_bum/couch_bum.rb +92 -0
  72. data/lib/generators/lab/install/USAGE +9 -0
  73. data/lib/generators/lab/install/install_generator.rb +19 -0
  74. data/lib/generators/lab/install/templates/rswag-ui-lab.rb +5 -0
  75. data/lib/generators/lab/install/templates/start_worker.rb +32 -0
  76. data/lib/generators/lab/install/templates/swagger.yaml +714 -0
  77. data/lib/his_emr_api_lab.rb +5 -0
  78. data/lib/lab/engine.rb +15 -0
  79. data/lib/lab/version.rb +5 -0
  80. data/lib/logger_multiplexor.rb +38 -0
  81. data/lib/tasks/lab_tasks.rake +25 -0
  82. data/lib/tasks/loaders/data/reasons-for-test.csv +7 -0
  83. data/lib/tasks/loaders/data/test-measures.csv +225 -0
  84. data/lib/tasks/loaders/data/tests.csv +161 -0
  85. data/lib/tasks/loaders/loader_mixin.rb +53 -0
  86. data/lib/tasks/loaders/metadata_loader.rb +26 -0
  87. data/lib/tasks/loaders/reasons_for_test_loader.rb +23 -0
  88. data/lib/tasks/loaders/specimens_loader.rb +65 -0
  89. data/lib/tasks/loaders/test_result_indicators_loader.rb +54 -0
  90. metadata +331 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2c610aa9f69216fd8b30291678d80c82423821fdf6985db8525902f3623330ab
4
+ data.tar.gz: 84f699727009dc4d3f9f40a26daf731c309faed5d7e69010471b480ccf8468ec
5
+ SHA512:
6
+ metadata.gz: 226ace82e6b0b2b19711c2b2c82c3ff8c75bd632236f2724e0529c2e461b2ffd3a70c4351ffbd5c357286290af773cfee9a5f0ba11a191fcbb78e921f6599ff9
7
+ data.tar.gz: 9f02b5a11f81a3fd49df49b9080a5ac0458907db91af3c456e420b5f220d77544feac064b7141dd56e3f698df88d7109697b7b278bbd38859dde243928d256ee
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2020 Walter Kaunda
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ ![Ruby](https://github.com/EGPAFMalawiHIS/HIS-EMR-API-Lab/workflows/Ruby/badge.svg?branch=development)
2
+ # Lab
3
+
4
+ A Rails engine for [HIS-EMR-API](https://github.com/EGPAFMalawiHIS/HIS-EMR-API)
5
+ compatible applications that provides an API for managing various lab
6
+ activities.
7
+
8
+ ## Usage
9
+
10
+ The engine provides an API that provides the following functionalities:
11
+
12
+ - Search/Retrieve test types
13
+ - Search/Retrieve sample types for a given test
14
+ - Order lab tests
15
+ - Attach a result to a test
16
+
17
+ For details on how to perform these operations please see the
18
+ [API docs](https://raw.githack.com/EGPAFMalawiHIS/HIS-EMR-API-Lab/development/docs/api.html).
19
+
20
+ ## Installation
21
+
22
+ Add this line to your application's Gemfile:
23
+
24
+ ```ruby
25
+ gem 'lab', git: 'https://github.com/EGPAFMalawiHIS/HIS-EMR-API-Lab', branch: 'development'
26
+ ```
27
+
28
+ And then execute:
29
+
30
+ ```bash
31
+ $ bundle install lab
32
+ ```
33
+
34
+ Or install it yourself as:
35
+
36
+ ```bash
37
+ $ gem install lab
38
+ ```
39
+
40
+ Finally run:
41
+
42
+ ```bash
43
+ $ bundle exec rails lab:install
44
+ ```
45
+
46
+ ## Configuration
47
+
48
+ This module in most cases should work without any configuration, however to enable
49
+ certain features some configuration may be required. Visit the
50
+ [configuration](./docs/configuration.md) page to learn how to configure the
51
+ application.
52
+
53
+ ## Contributing
54
+
55
+ Fork this application create a branch for the contribution you want to make,
56
+ push your changes to the branch and then issue a pull request. You may want
57
+ to create a new first on our repository, so that your pull request references
58
+ this issue.
59
+
60
+ If you are fixing a bug, it will be nice to add a unit test that exposes
61
+ the bug. Although this is not a requirement in most cases.
62
+
63
+ Be sure to follow [this](https://github.com/rubocop/ruby-style-guide) Ruby
64
+ style guide. We don't necessarily look for strict adherence to the guidelines
65
+ but too much a departure from it is frowned upon. For example, you will be forgiven
66
+ for writing a method with 15 to 20 lines if you clearly justify why you couldn't
67
+ break that method into multiple smaller methods.
68
+
69
+ ## License
70
+
71
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Lab'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'spec'
28
+ t.pattern = 'spec/**/*_spec.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ class ApplicationController < ::ApplicationController
5
+ end
6
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ class LabelsController < ApplicationController
5
+ skip_before_action :authenticate
6
+
7
+ def print_order_label
8
+ order_id = params.require(:order_id)
9
+
10
+ label = LabellingService::OrderLabel.new(order_id)
11
+ send_data(label.print, type: 'application/label; charset=utf-8',
12
+ stream: false,
13
+ filename: "#{SecureRandom.hex(24)}.lbl",
14
+ disposition: 'inline')
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ class OrdersController < ApplicationController
5
+ skip_before_action :authorize_request, only: %i[order_status order_result]
6
+
7
+ def create
8
+ order_params_list = params.require(:orders)
9
+ orders = order_params_list.map do |order_params|
10
+ OrdersService.order_test(order_params)
11
+ end
12
+
13
+ orders.each { |order| Lab::PushOrderJob.perform_later(order.fetch(:order_id)) }
14
+
15
+ render json: orders, status: :created
16
+ end
17
+
18
+ def update
19
+ specimen = params.require(:specimen).permit(:concept_id)
20
+ order = OrdersService.update_order(params[:id], specimen: specimen, force_update: params[:force_update])
21
+ Lab::PushOrderJob.perform_later(order.fetch(:order_id))
22
+
23
+ render json: order
24
+ end
25
+
26
+ def index
27
+ filters = params.permit(%i[patient_id patient accession_number date status])
28
+
29
+ id = filters[:patient_id] || filters[:patient]
30
+
31
+ patient = Patient.find(id) if filters[:patient_id] || filters[:patient]
32
+
33
+ Lab::UpdatePatientOrdersJob.perform_later(patient.id) if filters[:patient_id] || filters[:patient]
34
+ orders = OrdersSearchService.find_orders(filters)
35
+ begin
36
+ render json: orders.reload, status: :ok
37
+ rescue StandardError
38
+ render json: orders
39
+ end
40
+ end
41
+
42
+ def verify_tracking_number
43
+ tracking_number = params.require(:accession_number)
44
+ render json: { exists: OrdersService.check_tracking_number(tracking_number) }, status: :ok
45
+ end
46
+
47
+ def destroy
48
+ OrdersService.void_order(params[:id], params[:reason])
49
+ Lab::VoidOrderJob.perform_later(params[:id])
50
+
51
+ render status: :no_content
52
+ end
53
+
54
+ def order_status
55
+ order_params = params.permit(:tracking_number, :status, :status_time, :comments)
56
+ OrdersService.update_order_status(order_params)
57
+ render json: { message: "Status for order #{order_params['tracking_number']} successfully updated" }, status: :ok
58
+ end
59
+
60
+ def order_result
61
+ params.permit!
62
+ order_params = params[:data].to_h
63
+ OrdersService.update_order_result(order_params)
64
+ render json: { message: 'Results processed successfully' }, status: :ok
65
+ end
66
+
67
+ private
68
+
69
+ def authenticate_request
70
+ decoded_user = authorize_request
71
+ user(decoded_user)
72
+ end
73
+
74
+ def user(decoded)
75
+ User.current = User.find decoded[:user_id]
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ class ReasonsForTestController < ApplicationController
5
+ def index
6
+ render json: ConceptsService.reasons_for_test
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ class ResultsController < ApplicationController
5
+ def create
6
+ result_params = params.require(:result)
7
+ .permit([:encounter_id,
8
+ :encounter,
9
+ :date,
10
+ { measures: [:value,
11
+ :value_type,
12
+ :value_modifier,
13
+ { indicator: %i[concept_id concept] }] }])
14
+
15
+ result = Lab::ResultsService.create_results(params[:test_id], result_params, 'user entered')
16
+
17
+ render json: result, status: :created
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ class SpecimenTypesController < ApplicationController
5
+ def index
6
+ filters = params.permit(%w[name test_type])
7
+
8
+ specimen_types = ConceptsService.specimen_types(name: filters['name'],
9
+ test_type: filters['test_type'])
10
+ .as_json(only: %w[concept_id name])
11
+
12
+ render json: specimen_types
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Lab::TestResultIndicatorsController < ApplicationController
4
+ def index
5
+ test_type_id = params.require(:test_type_id)
6
+
7
+ render json: Lab::ConceptsService.test_result_indicators(test_type_id)
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ class TestTypesController < ApplicationController
5
+ def index
6
+ filters = params.permit(%w[name specimen_type])
7
+
8
+ test_types = ConceptsService.test_types(name: filters['name'],
9
+ specimen_type: filters['specimen_type'])
10
+ .as_json(only: %w[concept_id name])
11
+
12
+ render json: test_types
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Lab::TestsController < ::ApplicationController
4
+ def index
5
+ filters = params.permit(%i[order_date accession_number patient patient_id test_type_id specimen_type_id pending_results])
6
+ tests = service.find_tests(filters)
7
+ render json: tests
8
+ end
9
+
10
+ # Add a specimen to an existing order
11
+ def create
12
+ test_params = params.permit(:order_id, :date, tests: [:concept_id])
13
+ order_id, test_concepts = test_params.require(%i[order_id tests])
14
+ date = test_params[:date] || Date.today
15
+
16
+ tests = service.create_tests(Lab::LabOrder.find(order_id), date, test_concepts)
17
+ Lab::PushOrderJob.perform_later(order_id)
18
+
19
+ render json: tests, status: :created
20
+ end
21
+
22
+ def service
23
+ Lab::TestsService
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This controller handles creation and authentication of LIMS User
4
+ module Lab
5
+ class UsersController < ::ApplicationController
6
+ skip_before_action :authenticate
7
+ # create a LIMS User that will be responsible for sending lab results
8
+ def create
9
+ user_params = params.permit(:username, :password)
10
+ service.create_lims_user(username: user_params['username'], password: user_params['password'])
11
+ render json: { message: 'User successfully created' }, status: 200
12
+ end
13
+
14
+ # authenticate the lims user
15
+ def login
16
+ user_params = params.permit(:username, :password)
17
+ result = service.authenticate_user(username: user_params['username'], password: user_params['password'],
18
+ user_agent: request.user_agent, request_ip: request.remote_ip)
19
+ if result.present?
20
+ render json: result, status: 200
21
+ else
22
+ render json: { message: 'Invalid Credentials Provided' }, status: 401
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def service
29
+ Lab::UserService
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,4 @@
1
+ module Lab
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ ##
5
+ # Push an order to LIMS.
6
+ class PushOrderJob < ApplicationJob
7
+ def perform(order_id)
8
+ push_worker = Lab::Lims::PushWorker.new(Lab::Lims::ApiFactory.create_api)
9
+ push_worker.push_order_by_id(order_id)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ ##
5
+ # Fetches updates on a patient's orders from external sources.
6
+ class UpdatePatientOrdersJob < ApplicationJob
7
+ queue_as :default
8
+
9
+ def perform(patient_id)
10
+ Rails.logger.info('Initialising LIMS REST API...')
11
+
12
+ User.current = Lab::Lims::Utils.lab_user
13
+ Location.current = Location.find_by_name('ART clinic')
14
+
15
+ lockfile = Rails.root.join('tmp', "update-patient-orders-#{patient_id}.lock")
16
+
17
+ done = File.open(lockfile, File::RDWR | File::CREAT) do |lock|
18
+ unless lock.flock(File::LOCK_NB | File::LOCK_EX)
19
+ Rails.logger.info('Another update patient job is already running...')
20
+ break false
21
+ end
22
+
23
+ worker = Lab::Lims::PullWorker.new(Lab::Lims::ApiFactory.create_api)
24
+ worker.pull_orders(patient_id: patient_id)
25
+
26
+ true
27
+ end
28
+
29
+ File.unlink(lockfile) if done
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ class VoidOrderJob < ApplicationJob
5
+ queue_as :default
6
+
7
+ def perform(order_id)
8
+ Rails.logger.info("Voiding order ##{order_id} in LIMS")
9
+
10
+ User.current = Lab::Lims::Utils.lab_user
11
+ Location.current = Location.find_by_name('ART clinic')
12
+
13
+ worker = Lab::Lims::PushWorker.new(Lab::Lims::ApiFactory.create_api)
14
+ worker.push_order(Lab::LabOrder.unscoped.find(order_id))
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ module Lab
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Lab
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ # Used to keep track of counters for accession numbers.
5
+ #
6
+ # An accession number is just a prefix plus a running encounter
7
+ # that's reset at the end of the day
8
+ class LabAccessionNumberCounter < ApplicationRecord
9
+ self.table_name = :lab_accession_number_counters
10
+
11
+ validates_presence_of :date, :value
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ class LabAcknowledgement < ::LimsAcknowledgementStatus
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ class LabEncounter < ::Encounter
5
+ default_scope { joins(:type).merge(EncounterType.where('name LIKE ?', ENCOUNTER_TYPE_NAME)) }
6
+ end
7
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ class LabOrder < ::Order
5
+ class << self
6
+ def make_obs_concept_filter(concept_name)
7
+ concept = ConceptName.where(name: concept_name).select(:concept_id)
8
+
9
+ -> { where(concept: concept) }
10
+ end
11
+ end
12
+
13
+ has_many :tests,
14
+ make_obs_concept_filter(Lab::Metadata::TEST_TYPE_CONCEPT_NAME),
15
+ class_name: '::Lab::LabTest',
16
+ foreign_key: :order_id
17
+
18
+ has_many :results,
19
+ make_obs_concept_filter(Lab::Metadata::TEST_RESULT_CONCEPT_NAME),
20
+ class_name: 'Observation',
21
+ foreign_key: :order_id
22
+
23
+ has_one :reason_for_test,
24
+ make_obs_concept_filter(Lab::Metadata::REASON_FOR_TEST_CONCEPT_NAME),
25
+ class_name: 'Observation',
26
+ foreign_key: :order_id
27
+
28
+ has_one :requesting_clinician,
29
+ make_obs_concept_filter(Lab::Metadata::REQUESTING_CLINICIAN_CONCEPT_NAME),
30
+ class_name: 'Observation',
31
+ foreign_key: :order_id
32
+
33
+ has_one :target_lab,
34
+ make_obs_concept_filter(Lab::Metadata::TARGET_LAB_CONCEPT_NAME),
35
+ class_name: 'Observation',
36
+ foreign_key: :order_id
37
+
38
+ has_one :mapping,
39
+ class_name: '::Lab::LimsOrderMapping',
40
+ foreign_key: :order_id
41
+
42
+ default_scope do
43
+ joins(:order_type)
44
+ .merge(OrderType.where(name: Lab::Metadata::ORDER_TYPE_NAME))
45
+ .where.not(concept_id: ConceptName.where(name: 'Tests ordered').select(:concept_id))
46
+ end
47
+
48
+ scope :drawn, -> { where.not(concept_id: ConceptName.where(name: 'Unknown').select(:concept_id)) }
49
+ scope :not_drawn, -> { where(concept_id: ConceptName.where(name: 'Unknown').select(:concept_id)) }
50
+
51
+ def self.prefetch_relationships
52
+ includes(:reason_for_test,
53
+ :requesting_clinician,
54
+ :target_lab,
55
+ tests: [:result])
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ class LabResult < Observation
5
+ alias measures children
6
+
7
+ default_scope do
8
+ lab_test_concept = ConceptName.where(name: Lab::Metadata::TEST_RESULT_CONCEPT_NAME)
9
+ .select(:concept_id)
10
+ where(voided: 0, concept: lab_test_concept)
11
+ end
12
+
13
+ belongs_to :test,
14
+ (lambda do
15
+ where(concept: ConceptName.where(name: Lab::Metadata::TEST_TYPE_CONCEPT_NAME)
16
+ .select(:concept_id))
17
+ end),
18
+ class_name: 'Observation',
19
+ foreign_key: :obs_group_id
20
+
21
+ def void(reason)
22
+ children.each do |measure|
23
+ # Need to have a LabResultMeasure model that privately handles it's children
24
+ measure.children.each { |provider| provider.void(reason) }
25
+ measure.void(reason)
26
+ end
27
+
28
+ super(reason)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ class LabTest < ::Observation
5
+ default_scope do
6
+ where(concept: ConceptName.where(name: Lab::Metadata::TEST_TYPE_CONCEPT_NAME))
7
+ end
8
+
9
+ has_one :result,
10
+ -> { where(concept: ConceptName.where(name: Lab::Metadata::TEST_RESULT_CONCEPT_NAME)) },
11
+ class_name: 'Lab::LabResult',
12
+ foreign_key: :obs_group_id
13
+
14
+ def void(reason)
15
+ result&.void(reason)
16
+ super(reason)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,4 @@
1
+ module Lab
2
+ class LimsFailedImport < ApplicationRecord
3
+ end
4
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lab
4
+ class LimsOrderMapping < ApplicationRecord
5
+ belongs_to :order, class_name: 'LabOrder', foreign_key: 'order_id'
6
+
7
+ validates_presence_of :lims_id, :order_id
8
+ validates_uniqueness_of :lims_id, :order_id
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # model managing extra details for orders
4
+ class Lab::OrderExtension < ApplicationRecord
5
+ include Voidable
6
+ self.table_name = :order_extension
7
+ self.primary_key = :order_extension_id
8
+
9
+ default_scope { where(voided: 0) }
10
+ scope :voided, -> { unscoped.where.not(voided: 0) }
11
+
12
+ belongs_to :order, foreign_key: :order_id
13
+ belongs_to :creator, class_name: 'User', foreign_key: :creator
14
+ end