pvdgm_services 0.1.0

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.
Files changed (256) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +29 -0
  3. data/Rakefile +40 -0
  4. data/app/contexts/services/accept_hl7_message_context.rb +32 -0
  5. data/app/contexts/services/application_mds_context.rb +21 -0
  6. data/app/contexts/services/complete_sltc_registration_process_context.rb +23 -0
  7. data/app/contexts/services/create_account_mapping_context.rb +23 -0
  8. data/app/contexts/services/create_configured_account_context.rb +26 -0
  9. data/app/contexts/services/create_configured_facility_context.rb +26 -0
  10. data/app/contexts/services/create_credential_context.rb +23 -0
  11. data/app/contexts/services/create_facility_mapping_context.rb +23 -0
  12. data/app/contexts/services/create_public_key_context.rb +22 -0
  13. data/app/contexts/services/create_service_context.rb +22 -0
  14. data/app/contexts/services/create_service_definition_context.rb +26 -0
  15. data/app/contexts/services/create_sltc_registration_context.rb +24 -0
  16. data/app/contexts/services/create_third_party_context.rb +22 -0
  17. data/app/contexts/services/hl7_message_trimming_context.rb +19 -0
  18. data/app/contexts/services/invoke_service_context.rb +21 -0
  19. data/app/contexts/services/list_sltc_providers_context.rb +22 -0
  20. data/app/contexts/services/mds_file_processing_context.rb +27 -0
  21. data/app/contexts/services/mds_file_upload_context.rb +25 -0
  22. data/app/contexts/services/mds_pull_context.rb +21 -0
  23. data/app/contexts/services/monthly_service_table_cleanup_context.rb +19 -0
  24. data/app/contexts/services/notify_new_registration_context.rb +22 -0
  25. data/app/contexts/services/notify_sltc_provider_change_context.rb +19 -0
  26. data/app/contexts/services/request_sltc_baseline_context.rb +23 -0
  27. data/app/contexts/services/update_account_mapping_context.rb +23 -0
  28. data/app/contexts/services/update_configured_account_context.rb +26 -0
  29. data/app/contexts/services/update_configured_facility_context.rb +26 -0
  30. data/app/contexts/services/update_credential_context.rb +23 -0
  31. data/app/contexts/services/update_facility_mapping_context.rb +23 -0
  32. data/app/contexts/services/update_public_key_context.rb +23 -0
  33. data/app/contexts/services/update_service_context.rb +23 -0
  34. data/app/contexts/services/update_service_definition_context.rb +26 -0
  35. data/app/contexts/services/update_third_party_context.rb +23 -0
  36. data/app/controllers/services/account_mappings_controller.rb +54 -0
  37. data/app/controllers/services/adts_controller.rb +35 -0
  38. data/app/controllers/services/application_controller.rb +17 -0
  39. data/app/controllers/services/assessment_requests_controller.rb +48 -0
  40. data/app/controllers/services/available_files_controller.rb +19 -0
  41. data/app/controllers/services/configured_account_base_controller.rb +17 -0
  42. data/app/controllers/services/configured_accounts_controller.rb +59 -0
  43. data/app/controllers/services/configured_facilities_controller.rb +65 -0
  44. data/app/controllers/services/facility_mappings_controller.rb +58 -0
  45. data/app/controllers/services/mds_files_controller.rb +75 -0
  46. data/app/controllers/services/mds_pull_accounts_controller.rb +57 -0
  47. data/app/controllers/services/public_keys_controller.rb +50 -0
  48. data/app/controllers/services/service_base_controller.rb +18 -0
  49. data/app/controllers/services/service_definition_base_controller.rb +31 -0
  50. data/app/controllers/services/service_definitions_controller.rb +59 -0
  51. data/app/controllers/services/services_controller.rb +56 -0
  52. data/app/controllers/services/sltc_providers_controller.rb +15 -0
  53. data/app/controllers/services/sltc_registrations_controller.rb +74 -0
  54. data/app/controllers/services/status_masking.rb +17 -0
  55. data/app/controllers/services/third_parties_controller.rb +47 -0
  56. data/app/controllers/services/third_party_base_controller.rb +17 -0
  57. data/app/controllers/services/validation_controller.rb +17 -0
  58. data/app/helpers/services/application_helper.rb +8 -0
  59. data/app/helpers/services/assessment_request_helper.rb +19 -0
  60. data/app/helpers/services/available_files_helper.rb +27 -0
  61. data/app/helpers/services/mds_pull_accounts_helper.rb +27 -0
  62. data/app/helpers/services/sltc_registrations_helper.rb +15 -0
  63. data/app/mailers/services_mailer.rb +57 -0
  64. data/app/models/services/abaqis_mds_push.rb +51 -0
  65. data/app/models/services/account_mapping.rb +10 -0
  66. data/app/models/services/application_api.rb +19 -0
  67. data/app/models/services/assessment_request.rb +18 -0
  68. data/app/models/services/available_file.rb +73 -0
  69. data/app/models/services/configured_account.rb +26 -0
  70. data/app/models/services/configured_facility.rb +16 -0
  71. data/app/models/services/credential.rb +9 -0
  72. data/app/models/services/facility_mapping.rb +12 -0
  73. data/app/models/services/ftp_server.rb +69 -0
  74. data/app/models/services/hl7_inbound_service.rb +47 -0
  75. data/app/models/services/hl7_message.rb +27 -0
  76. data/app/models/services/isc_code.rb +15 -0
  77. data/app/models/services/isc_code_lookup.rb +75 -0
  78. data/app/models/services/mds_assessment.rb +371 -0
  79. data/app/models/services/mds_content.rb +55 -0
  80. data/app/models/services/mds_pull.rb +41 -0
  81. data/app/models/services/mds_pull_account.rb +192 -0
  82. data/app/models/services/mds_push.rb +24 -0
  83. data/app/models/services/mds_upload.rb +64 -0
  84. data/app/models/services/mds_upload_content.rb +148 -0
  85. data/app/models/services/mds_ws_response.rb +21 -0
  86. data/app/models/services/mds_ws_response_handler.rb +31 -0
  87. data/app/models/services/pcc_mds_pull.rb +77 -0
  88. data/app/models/services/provider_change.rb +26 -0
  89. data/app/models/services/public_key.rb +11 -0
  90. data/app/models/services/service.rb +20 -0
  91. data/app/models/services/service_definition.rb +37 -0
  92. data/app/models/services/service_implementation.rb +29 -0
  93. data/app/models/services/sltc_api.rb +179 -0
  94. data/app/models/services/sltc_api_exception.rb +54 -0
  95. data/app/models/services/sltc_mds_pull.rb +230 -0
  96. data/app/models/services/sltc_registration.rb +23 -0
  97. data/app/models/services/third_party.rb +18 -0
  98. data/app/roles/services/account_mapping_creator.rb +13 -0
  99. data/app/roles/services/account_mapping_updator.rb +12 -0
  100. data/app/roles/services/configured_account_creator.rb +13 -0
  101. data/app/roles/services/configured_account_updater.rb +12 -0
  102. data/app/roles/services/configured_facility_creator.rb +13 -0
  103. data/app/roles/services/configured_facility_updater.rb +12 -0
  104. data/app/roles/services/credential_creator.rb +11 -0
  105. data/app/roles/services/credential_updater.rb +20 -0
  106. data/app/roles/services/facility_mapping_creator.rb +13 -0
  107. data/app/roles/services/facility_mapping_updater.rb +11 -0
  108. data/app/roles/services/hl7_adt_message_saver.rb +15 -0
  109. data/app/roles/services/hl7_message_trimmer.rb +14 -0
  110. data/app/roles/services/mds_assessment_categorizer.rb +71 -0
  111. data/app/roles/services/mds_file_processor.rb +86 -0
  112. data/app/roles/services/mds_notifications.rb +44 -0
  113. data/app/roles/services/monthly_service_table_cleaner.rb +19 -0
  114. data/app/roles/services/new_registration_notifier.rb +11 -0
  115. data/app/roles/services/public_key_creator.rb +14 -0
  116. data/app/roles/services/public_key_updater.rb +12 -0
  117. data/app/roles/services/service_creator.rb +13 -0
  118. data/app/roles/services/service_definition_creator.rb +13 -0
  119. data/app/roles/services/service_definition_updater.rb +12 -0
  120. data/app/roles/services/service_invoker.rb +25 -0
  121. data/app/roles/services/service_updater.rb +11 -0
  122. data/app/roles/services/sltc_baseline_requestor.rb +19 -0
  123. data/app/roles/services/sltc_provider_change_notifier.rb +25 -0
  124. data/app/roles/services/sltc_provider_lister.rb +13 -0
  125. data/app/roles/services/sltc_registration_completer.rb +81 -0
  126. data/app/roles/services/sltc_registration_saver.rb +13 -0
  127. data/app/roles/services/submit_mds_file_for_processing.rb +28 -0
  128. data/app/roles/services/third_party_creator.rb +13 -0
  129. data/app/roles/services/third_party_updater.rb +12 -0
  130. data/app/utils/services/file_upload_handler.rb +33 -0
  131. data/app/utils/services/mds_upload_filters.rb +26 -0
  132. data/app/utils/services/mds_xml_file_parser.rb +104 -0
  133. data/app/utils/services/upload_file.rb +13 -0
  134. data/app/validators/isc_code_validator.rb +42 -0
  135. data/app/validators/mds_birthdate_validator.rb +37 -0
  136. data/app/validators/mds_date_validator.rb +8 -0
  137. data/app/validators/mds_integer_validator.rb +15 -0
  138. data/app/validators/mds_version_validator.rb +16 -0
  139. data/app/validators/state_code_validator.rb +9 -0
  140. data/app/views/layouts/services/application.html.erb +14 -0
  141. data/app/views/services/account_mappings/index.json.jbuilder +9 -0
  142. data/app/views/services/account_mappings/show.json.jbuilder +7 -0
  143. data/app/views/services/assessment_requests/index.json.jbuilder +11 -0
  144. data/app/views/services/available_files/index.json.jbuilder +13 -0
  145. data/app/views/services/configured_accounts/index.json.jbuilder +13 -0
  146. data/app/views/services/configured_accounts/show.json.jbuilder +11 -0
  147. data/app/views/services/configured_facilities/index.json.jbuilder +13 -0
  148. data/app/views/services/configured_facilities/show.json.jbuilder +11 -0
  149. data/app/views/services/facility_mappings/index.json.jbuilder +10 -0
  150. data/app/views/services/facility_mappings/show.json.jbuilder +8 -0
  151. data/app/views/services/mds_files/create.builder +10 -0
  152. data/app/views/services/mds_pull_accounts/index.json.jbuilder +11 -0
  153. data/app/views/services/public_keys/index.json.jbuilder +8 -0
  154. data/app/views/services/public_keys/show.json.jbuilder +6 -0
  155. data/app/views/services/service_definitions/index.json.jbuilder +17 -0
  156. data/app/views/services/service_definitions/show.json.jbuilder +16 -0
  157. data/app/views/services/services/index.json.jbuilder +6 -0
  158. data/app/views/services/services/show.json.jbuilder +4 -0
  159. data/app/views/services/sltc_providers/index.json.jbuilder +10 -0
  160. data/app/views/services/sltc_registrations/index.json.jbuilder +10 -0
  161. data/app/views/services/third_parties/index.json.jbuilder +7 -0
  162. data/app/views/services/third_parties/show.json.jbuilder +5 -0
  163. data/app/views/services_mailer/burying_job.html.erb +20 -0
  164. data/app/views/services_mailer/delaying_job.html.erb +20 -0
  165. data/app/views/services_mailer/notify_sltc_client_registration.html.erb +39 -0
  166. data/app/views/services_mailer/notify_sltc_client_registration_invalid_request_type.html.erb +8 -0
  167. data/app/views/services_mailer/notify_sltc_client_registration_request_invalid.html.erb +10 -0
  168. data/app/views/services_mailer/notify_sltc_client_registration_validation_errors.html.erb +13 -0
  169. data/app/views/services_mailer/notify_sltc_provider_changes.html.erb +26 -0
  170. data/app/views/services_mailer/notify_support_about_no_mds_pull.html.erb +6 -0
  171. data/config/routes.rb +48 -0
  172. data/db/migrate/20140102000000_create_services_engine_tables.rb +131 -0
  173. data/db/migrate/20140517184450_new_services_columns.rb +24 -0
  174. data/db/migrate/20140525142842_new_configured_provider_table.rb +18 -0
  175. data/db/migrate/20140714172442_add_error_column_to_hl7_messages.rb +5 -0
  176. data/db/migrate/20140730164152_mds_upload_tables.rb +78 -0
  177. data/db/sql_data/service_data_setup.sql +24 -0
  178. data/lib/services/engine.rb +40 -0
  179. data/lib/services/version.rb +3 -0
  180. data/lib/services.rb +4 -0
  181. data/lib/tasks/services_tasks.rake +40 -0
  182. data/spec/dummy/README.rdoc +261 -0
  183. data/spec/dummy/Rakefile +7 -0
  184. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  185. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  186. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  187. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  188. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  189. data/spec/dummy/config/application.rb +60 -0
  190. data/spec/dummy/config/boot.rb +10 -0
  191. data/spec/dummy/config/database.yml +29 -0
  192. data/spec/dummy/config/environment.rb +5 -0
  193. data/spec/dummy/config/environments/development.rb +35 -0
  194. data/spec/dummy/config/environments/production.rb +68 -0
  195. data/spec/dummy/config/environments/test.rb +32 -0
  196. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  197. data/spec/dummy/config/initializers/email.rb +1 -0
  198. data/spec/dummy/config/initializers/inflections.rb +15 -0
  199. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  200. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  201. data/spec/dummy/config/initializers/session_store.rb +8 -0
  202. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  203. data/spec/dummy/config/locales/en.yml +5 -0
  204. data/spec/dummy/config/routes.rb +4 -0
  205. data/spec/dummy/config.ru +4 -0
  206. data/spec/dummy/db/schema.rb +237 -0
  207. data/spec/dummy/db/sql_data/services_isc_codes.sql +878 -0
  208. data/spec/dummy/log/test.log +3498 -0
  209. data/spec/dummy/public/404.html +26 -0
  210. data/spec/dummy/public/422.html +26 -0
  211. data/spec/dummy/public/500.html +25 -0
  212. data/spec/dummy/public/favicon.ico +0 -0
  213. data/spec/dummy/script/rails +6 -0
  214. data/spec/models/abaqis_mds_push_spec.rb +120 -0
  215. data/spec/models/available_file_spec.rb +234 -0
  216. data/spec/models/configured_account_spec.rb +39 -0
  217. data/spec/models/ftp_server_spec.rb +221 -0
  218. data/spec/models/isc_code_lookup_spec.rb +125 -0
  219. data/spec/models/isc_code_spec.rb +5 -0
  220. data/spec/models/mds_assessment_spec.rb +1070 -0
  221. data/spec/models/mds_pull_account_spec.rb +468 -0
  222. data/spec/models/mds_pull_spec.rb +48 -0
  223. data/spec/models/mds_push_spec.rb +43 -0
  224. data/spec/models/mds_ws_response_spec.rb +54 -0
  225. data/spec/models/pcc_mds_pull_spec.rb +273 -0
  226. data/spec/models/service_implementation_spec.rb +88 -0
  227. data/spec/models/sltc_api_exception_spec.rb +136 -0
  228. data/spec/models/sltc_api_spec.rb +192 -0
  229. data/spec/models/sltc_mds_pull_spec.rb +776 -0
  230. data/spec/roles/account_mapping_creator_spec.rb +40 -0
  231. data/spec/roles/account_mapping_updator_spec.rb +16 -0
  232. data/spec/roles/configured_account_creator_spec.rb +40 -0
  233. data/spec/roles/configured_account_updater_spec.rb +16 -0
  234. data/spec/roles/configured_facility_creator_spec.rb +40 -0
  235. data/spec/roles/configured_facility_updater_spec.rb +16 -0
  236. data/spec/roles/credential_creator_spec.rb +23 -0
  237. data/spec/roles/credential_updater_spec.rb +38 -0
  238. data/spec/roles/facility_mapping_creator_spec.rb +40 -0
  239. data/spec/roles/facility_mapping_updater_spec.rb +16 -0
  240. data/spec/roles/hl7_adt_message_saver_spec.rb +35 -0
  241. data/spec/roles/hl7_message_trimmer_spec.rb +31 -0
  242. data/spec/roles/monthly_service_table_cleaner_spec.rb +27 -0
  243. data/spec/roles/new_registration_notifier_spec.rb +18 -0
  244. data/spec/roles/service_ceator_spec.rb +34 -0
  245. data/spec/roles/service_definition_creator_spec.rb +40 -0
  246. data/spec/roles/service_definition_updater_spec.rb +16 -0
  247. data/spec/roles/service_invoker_spec.rb +22 -0
  248. data/spec/roles/service_updater_spec.rb +17 -0
  249. data/spec/roles/sltc_baseline_requestor_spec.rb +30 -0
  250. data/spec/roles/sltc_provider_lister_spec.rb +27 -0
  251. data/spec/roles/sltc_registration_completer_spec.rb +187 -0
  252. data/spec/roles/sltc_registration_saver_spec.rb +34 -0
  253. data/spec/roles/third_party_creator_spec.rb +34 -0
  254. data/spec/roles/third_party_updater_spec.rb +17 -0
  255. data/spec/spec_helper.rb +72 -0
  256. metadata +581 -0
@@ -0,0 +1,64 @@
1
+ module Services
2
+
3
+ class MdsUpload < ActiveRecord::Base
4
+ include MdsUploadContent
5
+ include MdsNotifications
6
+
7
+ NEW = 0
8
+ ASSESSMENTS_LOADED = 1
9
+ COMPLETE = 2
10
+ ERROR = 3
11
+
12
+ TIME_TO_RUN = 36000
13
+ PRIORITY = 500
14
+ ASYNC_SEND_OPTIONS = { :priority => PRIORITY, :time_to_run => TIME_TO_RUN }
15
+
16
+
17
+ belongs_to :account
18
+ belongs_to :upload_user, class_name: "User"
19
+ has_many :mds_assessments
20
+ has_many :facilities, through: :mds_assessments
21
+ has_many :residents, through: :mds_assessments
22
+ has_one :mds_content, as: :uploadable
23
+
24
+ serialize :file_errors, JSON
25
+
26
+ scope :by_upload_user, ->(user_id) { where("upload_user_id = ?", user_id ) }
27
+ scope :completed, -> { where(status: COMPLETE) }
28
+
29
+ mds_upload_content :mds_file, max_size: { size: 75.megabytes,
30
+ message: "MDS Files need to be less than 75 MB in size." },
31
+ presence: { message: "Please select an MDS file for upload." },
32
+ name_match: { regex: /\.zip$/i,
33
+ message: "Uploaded file must have a .zip extension." },
34
+ file_type: { regex: /zip/i,
35
+ message: "Uploaded file must be a ZIP file" }
36
+
37
+ before_create { self.file_errors ||= {} }
38
+ after_save :handle_mds_notifications, :if => :ready_for_notification?
39
+
40
+ def add_file_errors(file_name, error)
41
+ self.file_errors ||= {}
42
+ error_array = error.is_a?(Array) ? error : [ error ]
43
+ if self.file_errors[file_name].present?
44
+ self.file_errors[file_name].concat(error_array)
45
+ else
46
+ self.file_errors[file_name] = error_array
47
+ end
48
+ end
49
+
50
+ def has_errors?
51
+ status == COMPLETE && (fatal_error.present? || file_errors.size > num_duplicated)
52
+ end
53
+
54
+ def successful?
55
+ status == COMPLETE
56
+ end
57
+
58
+ def ready_for_notification?
59
+ status == COMPLETE
60
+ end
61
+
62
+ end
63
+
64
+ end
@@ -0,0 +1,148 @@
1
+ module Services
2
+
3
+ module MdsUploadContent
4
+
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+ #
12
+ # Specify that there is a file attachment associated with this model.
13
+ #
14
+ # attr_name - the name of the parameter in the request that contains the file upload.
15
+ # args - a hash of options to control operations. Valid options include:
16
+ #
17
+ # :max_size => { :size => size-in-bytes,
18
+ # :message => 'Error message if size exceeded' }
19
+ # If :size is not specified, a default of 5.megabytes is used
20
+ # If :message is not specified, a default message of "uploaded file exceedes maximum of #{args[:max_size][:size] bytes" is used
21
+ #
22
+ # :presence => { :message => 'Error message if upload not provided.' }
23
+ # If :message is not specified, a default message of "no file upload provided"
24
+ #
25
+ # :name_match => { :regex => regular expression for file name to match,
26
+ # :message => 'Error message if upload not provided.' }
27
+ # If :regex is not specified, all names will match.
28
+ # If :message is not specified, a default message of "uploaded file name does not match regular expression".
29
+ #
30
+ # :file_type => { :message => 'Error message if no match on file type',
31
+ # :regex => regular expression for the results of 'file -b --mime' to match }
32
+ # If :message is not specified, a default message of "unexpected upload file type"
33
+ # If :regex is not specified, then there will be no check of the file type.
34
+ #
35
+ def mds_upload_content(attr_name, args={})
36
+
37
+ # Define the setter method for the upload file.
38
+ define_method("#{attr_name}=".to_sym) do | upload_file |
39
+ return if upload_file.nil?
40
+ self.original_file_name = File.basename(upload_file.original_filename)
41
+ self.content_type = upload_file.content_type
42
+ self.size = upload_file.size
43
+ @upload_file = upload_file
44
+ end
45
+
46
+ define_method("clear_#{attr_name}".to_sym) do
47
+ @upload_file = nil
48
+ end
49
+
50
+ define_max_size_validation(attr_name, args[:max_size]) if args.has_key?(:max_size)
51
+
52
+ define_presence_validation(attr_name, args[:presence]) if args.has_key?(:presence)
53
+
54
+ define_name_match_validation(attr_name, args[:name_match]) if args.has_key?(:name_match)
55
+
56
+ define_file_type_validation(attr_name, args[:file_type]) if args.has_key?(:file_type)
57
+
58
+ before_create :construct_file_contents
59
+ end
60
+
61
+ private
62
+
63
+ def define_max_size_validation(attr_name, max_size_args)
64
+ max_size_args ||= {}
65
+ max_size_args[:size] ||= 5.megabytes
66
+ max_size_args[:message] ||= "uploaded file exceedes maximum of #{max_size_args[:size]} bytes"
67
+
68
+ # set up the validator
69
+ validate :attachment_size, :on => :create
70
+
71
+ define_method(:attachment_size) do
72
+ return if self.size.nil? # If the size isn't set, what's the point?
73
+ errors.add(attr_name, max_size_args[:message]) if self.size > max_size_args[:size]
74
+ end
75
+ end
76
+
77
+ def define_presence_validation(attr_name, presence_args)
78
+ presence_args ||= {}
79
+ presence_args[:message] ||= "no file upload provided"
80
+
81
+ # Set up the validator
82
+ validate :attachment_presence, :on => :create
83
+
84
+ define_method(:attachment_presence) do
85
+ errors.add(attr_name, presence_args[:message]) if @upload_file.nil?
86
+ end
87
+ end
88
+
89
+ def define_name_match_validation(attr_name, name_match_args)
90
+ name_match_args ||= {}
91
+ name_match_args[:regex] ||= nil
92
+ name_match_args[:message] ||= "uploaded file name does not match regular expression"
93
+
94
+ # Set up the validator
95
+ validate :attachment_name_match, :on => :create
96
+
97
+ define_method(:attachment_name_match) do
98
+ return if self.original_file_name.blank?
99
+ errors.add(attr_name, name_match_args[:message]) if name_match_args[:regex].present? && !(self.original_file_name =~ name_match_args[:regex])
100
+ end
101
+ end
102
+
103
+ def define_file_type_validation(attr_name, file_type_args)
104
+ file_type_args ||= {}
105
+ file_type_args[:regex] ||= nil
106
+ file_type_args[:message] ||= "unexpected upload file type"
107
+
108
+ # Set up the validator
109
+ validate :file_type_match, :on => :create
110
+
111
+ define_method(:file_type_match) do
112
+ return if @upload_file.nil? || file_type_args[:regex].nil?
113
+ mime_type = mime_file_type(@upload_file.path)
114
+ errors.add(attr_name, file_type_args[:message]) if mime_type.present? && !(mime_type =~ file_type_args[:regex])
115
+ end
116
+ end
117
+
118
+ end # End of ClassMethods
119
+
120
+ private
121
+
122
+ #
123
+ # This is the implementation of a before_create method. It's responsibility is to
124
+ # set the content attribute of the model to the contents of the uploaded file.
125
+ # This method has effect only in the case of saving a new record.
126
+ #
127
+ def construct_file_contents
128
+ return if @upload_file.nil?
129
+
130
+ # Construct and populate the content record
131
+ content_record = content_class.new
132
+ content_record.content = @upload_file.read
133
+
134
+ # Make the association
135
+ self.send(content_assn_assignment, content_record)
136
+ end
137
+
138
+ def content_class() MdsContent end
139
+ def content_assn_assignment() "mds_content=".to_sym end
140
+
141
+ def mime_file_type(path)
142
+ raise "Unable to find upload file at: #{path}" if !File.exists?(path)
143
+ `file -b --mime #{path}`
144
+ end
145
+
146
+ end
147
+
148
+ end
@@ -0,0 +1,21 @@
1
+ module Services
2
+
3
+ class MdsWsResponse
4
+
5
+ STATUS_ERROR = "ERROR"
6
+ STATUS_SUCCESS = "SUCCESS"
7
+
8
+ attr_accessor :status, :error_message, :filename, :acknowledgement_id, :submission_datetime
9
+
10
+ def self.parse_xml_response(response_xml)
11
+ handler = MdsWsResponseHandler.new
12
+ parser = LibXML::XML::SaxParser.string(response_xml)
13
+ parser.callbacks = handler
14
+ parser.parse
15
+
16
+ handler.response
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,31 @@
1
+ require 'libxml'
2
+
3
+ module Services
4
+
5
+ class MdsWsResponseHandler
6
+ include LibXML::XML::SaxParser::Callbacks
7
+
8
+ COMPOSITE_FILE_RESPONSE = 'composite_file_response'
9
+ RESPONSE_NAME = 'Response'
10
+
11
+ attr_reader :response
12
+
13
+ def on_start_element(element_name, attributes)
14
+ @current_reader = element_name.to_sym
15
+ @current_writer = "#{element_name}=".to_sym
16
+
17
+ if element_name == COMPOSITE_FILE_RESPONSE
18
+ @response = MdsWsResponse.new
19
+ end
20
+ end
21
+
22
+ def on_characters(text)
23
+ return unless @response.respond_to?(@current_reader)
24
+
25
+ current_value = @response.send(@current_reader)
26
+ @response.send(@current_writer, current_value ? current_value + text.strip : text.strip)
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,77 @@
1
+ module Services
2
+
3
+ class PccMdsPull < MdsPull
4
+
5
+ #
6
+ # Factory method to return one and only one instance of the ftp server
7
+ # class for an instance of the PccMdsPull class.
8
+ #
9
+ def ftp_server
10
+ # Create a new FtpServer instance only if necessary
11
+ @ftp_server ||= FtpServer.new(service_definition.hostname,
12
+ service_definition.username,
13
+ server_credentials.password,
14
+ service_definition.base_uri)
15
+ end
16
+
17
+ #
18
+ # Overridden from MdsPull.
19
+ #
20
+ def list_available_files(configured_account)
21
+ mapped_account = account_map(configured_account.account_id)
22
+
23
+ files = []
24
+ ftp_server.connect do | server |
25
+ files = server.file_list mapped_account.account_code, '*.zip'
26
+ end
27
+ files.map { | fn | [ fn, fn ] }
28
+ end
29
+
30
+ #
31
+ # Overridden from MdsPull.
32
+ #
33
+ def perform_file_downloads(configured_account, files_to_download)
34
+ mapped_account = account_map(configured_account.account_id)
35
+
36
+ downloaded_files = []
37
+ ftp_server.connect do | server |
38
+ files_to_download.each do | available_file |
39
+ downloaded_files << download_file(server, available_file, mapped_account.account_code)
40
+ end
41
+ end
42
+ downloaded_files.compact # The compact is here because there could be nil's in the list
43
+ end
44
+
45
+ #
46
+ # Overridden from MdsPull
47
+ #
48
+ def perform_cleanup_on_remote_server(configured_account)
49
+ mapped_account = account_map(configured_account.account_id)
50
+
51
+ cleared_files = []
52
+ ftp_server.connect do | server |
53
+ configured_account.available_files.deleted.each do | available_file |
54
+ cleared_files << remove_file(server, available_file, mapped_account.account_code)
55
+ end
56
+ end
57
+ cleared_files.compact
58
+ end
59
+
60
+ def download_file(server, available_file, account_directory)
61
+ server.download_file account_directory, available_file.request_id, available_file.download_path
62
+ available_file.id
63
+ rescue Exception => ex
64
+ Rails.logger.error "Error downloading file: #{available_file.to_rrepr}: #{ex.message}\n#{ex.backtrace.join("\n")}"
65
+ nil # Return nil; we failed to download the specified file and 'nil' is the correct response
66
+ end
67
+
68
+ def remove_file(server, available_file, account_directory)
69
+ server.remove_file account_directory, available_file.request_id
70
+ available_file.id
71
+ rescue Exception => ex
72
+ Rails.logger.error "Error removing server file: #{available_file.to_rrepr}: #{ex.message}\n#{ex.backtrace.join("\n")}"
73
+ nil # Return nil; we failed to remove the specified file and 'nil' is the correct response
74
+ end
75
+ end
76
+
77
+ end
@@ -0,0 +1,26 @@
1
+ module Services
2
+
3
+ class ProviderChange < ActiveRecord::Base
4
+ belongs_to :configured_account
5
+
6
+ VERB_ADD = 1
7
+ VERB_DELETE = 2
8
+
9
+ STATUS_NEW = 0
10
+ STATUS_REPORTED = 1
11
+
12
+ validates :configured_account_id, presence: true
13
+ validates :facility_code, presence: true, length: { maximum: 255 }
14
+ validates :name, presence: true, length: { maximum: 255 }
15
+ validates :ccn, presence: true, length: { maximum: 255 }
16
+ validates :npi, length: { maximum: 255 }
17
+ validates :fac_id, length: { maximum: 16 }
18
+ validates :state, length: { maximum: 15 }
19
+ validates :verb, presence: true
20
+
21
+ scope :added_providers, ->(ca_id, facility_codes) { where(configured_account_id: ca_id, facility_code: facility_codes, verb: VERB_ADD).order("facility_code ASC")}
22
+ scope :deleted_providers, ->(ca_id, facility_codes) { where(configured_account_id: ca_id, facility_code: facility_codes, verb: VERB_DELETE).order("facility_code ASC")}
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,11 @@
1
+ module Services
2
+
3
+ class PublicKey < ActiveRecord::Base
4
+
5
+ scope :active, -> { where("valid_until IS NULL OR valid_until >= NOW()") }
6
+
7
+ validates :name, presence: true, uniqueness: true, length: { maximum: 255 }
8
+ validates :key, presence: true
9
+ end
10
+
11
+ end
@@ -0,0 +1,20 @@
1
+ module Services
2
+
3
+ class Service < ActiveRecord::Base
4
+ has_many :service_definitions, dependent: :destroy
5
+
6
+ #
7
+ # The MDS Pull service is specified by the DB ID. This is the only one
8
+ # we can predict this way. In production, the IDs of the other records
9
+ # will be unpredictable
10
+ #
11
+ MDS_PULL = 'mds_pull'
12
+ MDS_PUSH = "mds_push"
13
+ HL7_INBOUND = "hl7_inbound"
14
+
15
+ validates :name, presence: true, uniqueness: true, length: { maximum: 255 }
16
+ validates :key, presence: true, uniqueness: true, length: { maximum: 255 }
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,37 @@
1
+ module Services
2
+
3
+ class ServiceDefinition < ActiveRecord::Base
4
+ belongs_to :third_party
5
+ belongs_to :service
6
+
7
+ has_many :configured_accounts, dependent: :destroy
8
+ has_many :configured_facilities, dependent: :destroy
9
+ has_many :credentials, as: :credentialled, dependent: :destroy
10
+
11
+ validates :hostname, length: { minimum: 0, maximum: 255 }, allow_nil: true, allow_blank: true
12
+ validates :base_uri, length: { minimum: 0, maximum: 255 }, allow_nil: true, allow_blank: true
13
+ validates :username, length: { minimum: 0, maximum: 255 }, allow_nil: true, allow_blank: true
14
+ validates :service_class, presence: true, length: { maximum: 255 }
15
+
16
+ def invoke
17
+ service_implementation.invoke
18
+ end
19
+
20
+ def service_implementation
21
+ clazz = eval(service_class)
22
+ clazz.new(self)
23
+ end
24
+
25
+ def hostname_with_port
26
+ hwp = hostname
27
+ hwp << ":#{port}" if self.port.present?
28
+ hwp
29
+ end
30
+
31
+ def server_base_url
32
+ ERB.new(base_uri).result(binding)
33
+ end
34
+
35
+ end
36
+
37
+ end
@@ -0,0 +1,29 @@
1
+ module Services
2
+
3
+ class ServiceImplementation
4
+
5
+ attr_reader :service_definition
6
+
7
+ def initialize(srv_def)
8
+ @service_definition = srv_def
9
+ end
10
+
11
+ #
12
+ # Returns the credentials assocated with this service definition's server
13
+ #
14
+ def server_credentials
15
+ (@credentials ||= service_definition.credentials.first).tap do | cred |
16
+ raise "No credentials configured for server: (#{service_definition.to_rrepr})" if cred.blank?
17
+ end
18
+ end
19
+
20
+ def account_map(account_id)
21
+ AccountMapping.where(account_id: account_id,
22
+ third_party_id: service_definition.third_party_id).first.tap do |am|
23
+ raise "No account mapping associated with #{service_definition.to_rrepr} and account_id = #{account_id}" if am.blank?
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,179 @@
1
+ module Services
2
+
3
+ class SltcApi
4
+
5
+ BATCH_SIZE = 100
6
+
7
+ attr_reader :base_uri, :api_key, :account, :username, :password
8
+
9
+ def self.for_configured_account(configured_account)
10
+ service_definition = configured_account.service_definition
11
+ mapped_account = configured_account.account_mapping
12
+ credentials = configured_account.credentials.last
13
+
14
+ SltcApi.new(service_definition.server_base_url,
15
+ service_definition.credentials.last.token,
16
+ mapped_account.account_code,
17
+ configured_account.username,
18
+ credentials.password)
19
+ end
20
+
21
+ def initialize(base_uri, api_key, account, username, password)
22
+ @base_uri = base_uri
23
+ @api_key = api_key
24
+ @account = account
25
+ @username = username
26
+ @password = password
27
+ end
28
+
29
+ def list_providers
30
+ get_response do
31
+ RestClient::Request.execute(method: :get,
32
+ url: build_url(:Providers),
33
+ user: "#{username}@#{account}",
34
+ password: password,
35
+ headers: authentication_headers.merge({ 'Accept' => 'application/json' }))
36
+ end
37
+ end
38
+
39
+ def list_batches(provider, pull_from, pull_to)
40
+ batches = []
41
+ page = 1
42
+ while true
43
+ response = get_response do
44
+ RestClient::Request.execute(method: :get,
45
+ url: build_url(:MdsBatches, { provider_id: provider,
46
+ page: page,
47
+ uploaded_after: pull_from,
48
+ uploaded_before: pull_to }),
49
+ user: "#{username}@#{account}",
50
+ password: password,
51
+ headers: authentication_headers.merge({ 'Accept' => 'application/json' }))
52
+ end
53
+ batches += response
54
+ if response.size < BATCH_SIZE
55
+ break
56
+ else
57
+ page += 1
58
+ end
59
+ end
60
+ batches
61
+ end
62
+
63
+ def search_assessments(provider, pull_from, pull_to, status='Accepted')
64
+ get_response do
65
+ RestClient::Request.execute(method: :get,
66
+ url: build_url(:MdsAssessments, { provider_id: provider,
67
+ status_updated_after: pull_from,
68
+ status_updated_before: pull_to,
69
+ status: status }),
70
+ user: "#{username}@#{account}",
71
+ password: password,
72
+ headers: authentication_headers.merge({ 'Accept' => 'application/json' }))
73
+ end
74
+ end
75
+
76
+ def upload_batch(provider_id, path)
77
+ file = nil
78
+ File.open(path, "rb") { |f| file = f.read }
79
+ get_response do
80
+ RestClient::Request.execute(method: :post,
81
+ url: build_url(:MdsBatches, { filename: path }),
82
+ payload: file,
83
+ user: "#{username}@#{account}",
84
+ password: password,
85
+ headers: authentication_headers.merge( { :content_type => 'application/octet-stream',
86
+ 'Accept' => 'application/json' } ))
87
+ end
88
+ end
89
+
90
+ def download_batch(batch_id)
91
+ get_response(true) do
92
+ RestClient::Request.execute(method: :get,
93
+ url: build_url(:MdsBatches, batch_id),
94
+ user: "#{username}@#{account}",
95
+ password: password,
96
+ headers: authentication_headers.merge( { 'Accept' => 'application/octet-stream' } ))
97
+ end
98
+ end
99
+
100
+ def download_assessment_request(request_id)
101
+ get_response(true) do
102
+ RestClient::Request.execute(method: :get,
103
+ url: build_url(:MdsAssessmentsAsync, request_id),
104
+ user: "#{username}@#{account}",
105
+ password: password,
106
+ headers: authentication_headers.merge({ 'Accept' => 'application/octet-stream' }))
107
+ end
108
+ end
109
+
110
+ def assessment_request_status(request_id)
111
+ get_response do
112
+ RestClient::Request.execute(method: :get,
113
+ url: build_url(:MdsAssessmentsAsync, request_id),
114
+ user: "#{username}@#{account}",
115
+ password: password,
116
+ headers: authentication_headers.merge( { 'Accept' => 'application/json' }))
117
+ end
118
+ end
119
+
120
+ def assessment_request(provider_id, uploaded_after, uploaded_before, status)
121
+ parameters = {
122
+ 'provider_id' => provider_id,
123
+ 'status_updated_after' => uploaded_after.strftime("%Y-%m-%dT%H:%MZ"),
124
+ 'status_updated_before' => uploaded_before.strftime("%Y-%m-%dT%H:%MZ"),
125
+ 'status' => status
126
+ }
127
+
128
+ get_response do
129
+ RestClient::Request.execute(method: :post,
130
+ url: build_url(:MdsAssessmentsAsync),
131
+ payload: parameters.to_json,
132
+ user: "#{username}@#{account}",
133
+ password: password,
134
+ headers: authentication_headers.merge({ :content_type => :json,
135
+ 'Accept' => 'application/json' }))
136
+ end
137
+ end
138
+
139
+ private
140
+
141
+ def get_response(binary_response=false)
142
+ response = nil
143
+ begin
144
+ response = yield
145
+ rescue RestClient::Exception => ex
146
+ raise SltcApiException.new(ex)
147
+ end
148
+ binary_response ? response : JSON.parse(response)
149
+ end
150
+
151
+ def authentication_headers
152
+ {
153
+ 'SLTC-Api-Key' => api_key,
154
+ 'Host' => URI(base_uri).host
155
+ }
156
+ end
157
+
158
+ def build_url(api_name, parameters=nil)
159
+ url = "#{base_uri}/#{api_name.to_s}"
160
+ if parameters.present?
161
+ if parameters.is_a?(Fixnum)
162
+ url << '/' << parameters.to_s
163
+ elsif parameters.is_a?(String)
164
+ url << '/' << parameters
165
+ elsif parameters.is_a?(Hash)
166
+ url << "?"
167
+ url << parameters.keys.each.reduce([]) do | a, key |
168
+ raw_value = parameters[key]
169
+ value = raw_value.respond_to?(:strftime) ? raw_value.strftime("%Y-%m-%dT%H:%MZ") : raw_value
170
+ a << "#{key}=#{value}"
171
+ end.join("&")
172
+ end
173
+ end
174
+ url
175
+ end
176
+
177
+ end
178
+
179
+ end