dde_client 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.
@@ -0,0 +1,643 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DdeClient::DdeService
4
+ require_relative './matcher'
5
+
6
+ class DdeError < StandardError; end
7
+
8
+ Dde_CONFIG_PATH = Rails.root.join('config', 'dde.yml')
9
+ LOGGER = Rails.logger
10
+
11
+ # Limit all find queries for local patients to this
12
+ PATIENT_SEARCH_RESULTS_LIMIT = 10
13
+
14
+ attr_accessor :visit_type
15
+
16
+ include ModelUtils
17
+
18
+ def initialize(visit_type:)
19
+ raise InvalidParameterError, 'VisitType (visit_type_id) is required' unless visit_type
20
+
21
+ @visit_type = visit_type
22
+ end
23
+
24
+ def self.dde_enabled?
25
+ property = GlobalProperty.find_by_property('dde_enabled')&.property_value
26
+ return false unless property
27
+
28
+ case property
29
+ when /true/i then true
30
+ when /false/i then false
31
+ else raise "Invalid value for property dde_enabled: #{property.property_value}"
32
+ end
33
+ end
34
+
35
+ def test_connection
36
+ response = { connection_available: false, message: 'No connection to Dde', status: 500 }
37
+ begin
38
+ result, status = dde_client
39
+ response[:connection_available] = status == 200
40
+ response[:message] = result
41
+ rescue StandardError => exception
42
+ LOGGER.error "Failed to connect to Dde: #{exception.message}"
43
+ response[:message] = exception.message
44
+ end
45
+ response
46
+ end
47
+
48
+ # Registers local OpenMRS patient in Dde
49
+ #
50
+ # On success patient get two identifiers under the types
51
+ # 'Dde person document ID' and 'National id'. The
52
+ # 'Dde person document ID' is the patient's record ID in the local
53
+ # Dde instance and the 'National ID' is the national unique identifier
54
+ # for the patient.
55
+ def create_patient(patient)
56
+ push_local_patient_to_dde(patient)
57
+ end
58
+
59
+ def remaining_npids
60
+ response, status = dde_client.get("/location_npid_status?location_id=#{Location.current_location.id}")
61
+ raise DdeError, "Failed to fetch remaining npids: #{status} - #{response}" unless status == 200
62
+
63
+ response
64
+ end
65
+
66
+ def void_patient(patient, reason)
67
+ raise ArgumentError, "Can't request a Dde void for a non-voided patient" unless patient.voided?
68
+ raise ArgumentError, 'void_reason is required' if reason.blank?
69
+
70
+ doc_id = PatientIdentifier.unscoped
71
+ .where(identifier_type: dde_doc_id_type, patient: patient)
72
+ .order(:date_voided)
73
+ .last
74
+ &.identifier
75
+ return patient unless doc_id
76
+
77
+ response, status = dde_client.delete("void_person/#{doc_id}?void_reason=#{reason}")
78
+ raise DdeError, "Failed to void person in Dde: #{status} - #{response}" unless status == 200
79
+
80
+ patient
81
+ end
82
+
83
+ # Updates patient demographics in Dde.
84
+ #
85
+ # Local patient is not affected in anyway by the update
86
+ def update_patient(patient)
87
+ dde_patient = openmrs_to_dde_patient(patient)
88
+ response, status = dde_client.post('update_person', dde_patient)
89
+
90
+ raise DdeError, "Failed to update person in Dde: #{response}" unless status == 200
91
+
92
+ patient
93
+ end
94
+
95
+ ##
96
+ # Pushes a footprint for patient in current visit_type to Dde
97
+ def create_patient_footprint(patient, date = nil, creator_id = nil)
98
+ LOGGER.debug("Pushing footprint to Dde for patient ##{patient.patient_id}")
99
+ doc_id = find_patient_doc_id(patient)
100
+ unless doc_id
101
+ LOGGER.debug("Patient ##{patient.patient_id} is not a Dde patient")
102
+ return
103
+ end
104
+
105
+ response, status = dde_client.post('update_footprint', person_uuid: doc_id,
106
+ location_id: Location.current_location_health_center.location_id,
107
+ visit_type_id: visit_type.id,
108
+ encounter_datetime: date || Date.tody,
109
+ user_id: creator_id || User.current.user_id)
110
+
111
+ LOGGER.warn("Failed to push patient footprint to Dde: #{status} - #{response}") unless status == 200
112
+ end
113
+
114
+ ##
115
+ # Updates local patient with demographics currently in Dde.
116
+ def update_local_patient(patient, update_npid: false)
117
+ doc_id = patient_doc_id(patient)
118
+ unless doc_id
119
+ Rails.logger.warn("No Dde doc_id found for patient ##{patient.patient_id}")
120
+ push_local_patient_to_dde(patient)
121
+ return patient
122
+ end
123
+
124
+ dde_patient = find_remote_patients_by_doc_id(doc_id).first
125
+ unless dde_patient
126
+ Rails.logger.warn("Couldn't find patient ##{patient.patient_id} in Dde by doc_id ##{doc_id}")
127
+ push_local_patient_to_dde(patient)
128
+ return patient
129
+ end
130
+
131
+ if update_npid
132
+ merging_service.link_local_to_remote_patient(patient, dde_patient)
133
+ return patient
134
+ end
135
+
136
+ person_service.update_person(patient.person, dde_patient_to_local_person(dde_patient))
137
+ patient
138
+ end
139
+
140
+ # Import patients from Dde using doc id
141
+ def import_patients_by_doc_id(doc_id)
142
+ doc_id_type = patient_identifier_type('Dde person document id')
143
+ locals = patient_service.find_patients_by_identifier(doc_id, doc_id_type).limit(PATIENT_SEARCH_RESULTS_LIMIT)
144
+ remotes = find_remote_patients_by_doc_id(doc_id)
145
+
146
+ import_remote_patient(locals, remotes)
147
+ end
148
+
149
+ # Imports patients from Dde to the local database
150
+ def import_patients_by_npid(npid)
151
+ doc_id_type = patient_identifier_type('National id')
152
+ locals = patient_service.find_patients_by_identifier(npid, doc_id_type).limit(PATIENT_SEARCH_RESULTS_LIMIT)
153
+ remotes = find_remote_patients_by_npid(npid)
154
+
155
+ import_remote_patient(locals, remotes)
156
+ end
157
+
158
+ # Similar to import_patients_by_npid but uses name and gender instead of npid
159
+ def import_patients_by_name_and_gender(given_name, family_name, gender)
160
+ locals = patient_service.find_patients_by_name_and_gender(given_name, family_name, gender).limit(PATIENT_SEARCH_RESULTS_LIMIT)
161
+
162
+ remotes = begin
163
+ find_remote_patients_by_name_and_gender(given_name, family_name, gender)
164
+ rescue StandardError
165
+ []
166
+ end
167
+
168
+ import_remote_patient(locals, remotes)
169
+ end
170
+
171
+ def find_patients_by_npid(npid)
172
+ locals = patient_service.find_patients_by_npid(npid).limit(PATIENT_SEARCH_RESULTS_LIMIT)
173
+ remotes = find_remote_patients_by_npid(npid)
174
+
175
+ package_patients(locals, remotes, auto_push_singular_local: true)
176
+ end
177
+
178
+ def find_patients_by_name_and_gender(given_name, family_name, gender)
179
+ locals = []
180
+ begin
181
+ locals = patient_service.find_patients_by_name_and_gender(given_name, family_name, gender).limit(PATIENT_SEARCH_RESULTS_LIMIT)
182
+ remotes = find_remote_patients_by_name_and_gender(given_name, family_name, gender)
183
+
184
+ package_patients(locals, remotes)
185
+ rescue StandardError => e
186
+ Rails.logger.warn("Error packaging patients: #{e.message}")
187
+ package_patients(locals, [])
188
+ end
189
+ end
190
+
191
+ def find_patient_updates(local_patient_id)
192
+ dde_doc_id_type = PatientIdentifierType.where(name: 'Dde Person Document ID')
193
+ doc_id = PatientIdentifier.find_by(patient_id: local_patient_id, identifier_type: dde_doc_id_type)
194
+ &.identifier
195
+ return nil unless doc_id
196
+
197
+ remote_patient = find_remote_patients_by_doc_id(doc_id).first
198
+ return nil unless remote_patient
199
+
200
+ Matcher.find_differences(Person.find(local_patient_id), remote_patient)
201
+ rescue DdeError => e
202
+ Rails.logger.warn("Check for Dde patient updates failed: #{e.message}")
203
+ nil
204
+ end
205
+
206
+ # Matches patients using a bunch of demographics
207
+ def match_patients_by_demographics(family_name:, given_name:, birthdate:,
208
+ gender:, home_district:, home_traditional_authority:,
209
+ home_village:, birthdate_estimated: 0)
210
+ response, status = dde_client.post(
211
+ 'search/people', family_name: family_name,
212
+ given_name: given_name,
213
+ gender: gender,
214
+ birthdate: birthdate,
215
+ birthdate_estimated: !birthdate_estimated.zero?,
216
+ attributes: {
217
+ home_district: home_district,
218
+ home_traditional_authority: home_traditional_authority,
219
+ home_village: home_village
220
+ }
221
+ )
222
+
223
+ raise DdeError, "Dde patient search failed: #{status} - #{response}" unless status == 200
224
+
225
+ response.collect do |match|
226
+ doc_id = match['person']['id']
227
+ patient = patient_service.find_patients_by_identifier(
228
+ doc_id, patient_identifier_type('Dde person document id')
229
+ ).first
230
+ match['person']['patient_id'] = patient&.id
231
+ match
232
+ end
233
+ end
234
+
235
+ # Trigger a merge of patients in Dde
236
+ def merge_patients(primary_patients_ids, secondary_patient_ids)
237
+ merging_service.merge_patients(primary_patients_ids, secondary_patient_ids)
238
+ end
239
+
240
+ def reassign_patient_npid(patient_ids)
241
+ patient_id = patient_ids['patient_id']
242
+ doc_id = patient_ids['doc_id']
243
+
244
+ raise InvalidParameterError, 'patient_id and/or doc_id required' if patient_id.blank? && doc_id.blank?
245
+
246
+ if doc_id.blank?
247
+ # Only have patient id thus we have patient locally only
248
+ return push_local_patient_to_dde(Patient.find(patient_ids['patient_id']))
249
+ end
250
+
251
+ # NOTE: Fail patient retrieval as early as possible before making any
252
+ # changes to Dde (ie if patient_id does not exist)
253
+ patient = patient_id.blank? ? nil : Patient.find(patient_id)
254
+
255
+ # We have a doc_id thus we can re-assign npid in Dde
256
+ # Check if person if available in Dde if not add person using doc_id
257
+ response, status = dde_client.post('search_by_doc_id', doc_id: doc_id)
258
+ if !response.blank? && status.to_i == 200
259
+ response, status = dde_client.post('reassign_npid', doc_id: doc_id)
260
+ elsif response.blank? && status.to_i == 200
261
+ return push_local_patient_to_dde(Patient.find(patient_ids['patient_id']))
262
+ end
263
+
264
+ unless status == 200 && !response.empty?
265
+ # The Dde's reassign_npid end point responds with a 200 - OK but returns
266
+ # an empty object when patient with given doc_id is not found.
267
+ raise DdeError, "Failed to reassign npid: Dde Response => #{status} - #{response}"
268
+ end
269
+
270
+ return save_remote_patient(response) unless patient
271
+
272
+ merging_service.link_local_to_remote_patient(patient, response)
273
+ end
274
+
275
+ # Convert a Dde person to an openmrs person.
276
+ #
277
+ # NOTE: This creates a person on the database.
278
+ def save_remote_patient(remote_patient)
279
+ LOGGER.debug "Converting Dde person to openmrs: #{remote_patient}"
280
+ params = dde_patient_to_local_person(remote_patient)
281
+
282
+ Person.transaction do
283
+ person = person_service.create_person(params)
284
+
285
+ patient = Patient.create(patient_id: person.id)
286
+ merging_service.link_local_to_remote_patient(patient, remote_patient)
287
+ end
288
+ end
289
+
290
+ ##
291
+ # Converts a dde_patient object into an object that can be passed to the person_service
292
+ # to create or update a person.
293
+ def dde_patient_to_local_person(dde_patient)
294
+ attributes = dde_patient.fetch('attributes')
295
+
296
+ # ActiveSupport::HashWithIndifferentAccess.new(
297
+ # birthdate: dde_patient.fetch('birthdate'),
298
+ # birthdate_estimated: dde_patient.fetch('birthdate_estimated'),
299
+ # gender: dde_patient.fetch('gender'),
300
+ # given_name: dde_patient.fetch('given_name'),
301
+ # family_name: dde_patient.fetch('family_name'),
302
+ # middle_name: dde_patient.fetch('middle_name'),
303
+ # home_village: attributes.fetch('home_village'),
304
+ # home_traditional_authority: attributes.fetch('home_traditional_authority'),
305
+ # home_district: attributes.fetch('home_district'),
306
+ # current_village: attributes.fetch('current_village'),
307
+ # current_traditional_authority: attributes.fetch('current_traditional_authority'),
308
+ # current_district: attributes.fetch('current_district')
309
+ # # cell_phone_number: attributes.fetch('cellphone_number'),
310
+ # # occupation: attributes.fetch('occupation')
311
+ # )
312
+ {
313
+ birthdate: dde_patient.fetch('birthdate'),
314
+ birthdate_estimated: dde_patient.fetch('birthdate_estimated'),
315
+ gender: dde_patient.fetch('gender'),
316
+ names: [
317
+ {
318
+ given_name: dde_patient.fetch('given_name'),
319
+ family_name: dde_patient.fetch('family_name'),
320
+ middle_name: dde_patient.fetch('middle_name')
321
+ }
322
+ ],
323
+ addresses: [
324
+ {
325
+ address1: attributes.fetch('home_district'),
326
+ address3: attributes.fetch('current_district'),
327
+ county_district: attributes.fetch('home_traditional_authority'),
328
+ state_province: attributes.fetch('current_traditional_authority'),
329
+ address2: attributes.fetch('home_village'),
330
+ city_village: attributes.fetch('current_village')
331
+ }
332
+ ]
333
+ }
334
+ end
335
+
336
+ private
337
+
338
+ def find_remote_patients_by_npid(npid)
339
+ response, _status = dde_client.post('search_by_npid', npid: npid)
340
+ raise DdeError, "Patient search by npid failed: Dde Response => #{response}" unless response.instance_of?(Array)
341
+
342
+ response
343
+ end
344
+
345
+ def find_remote_patients_by_name_and_gender(given_name, family_name, gender)
346
+ response = nil
347
+ begin
348
+ response, _status = dde_client.post('search_by_name_and_gender', given_name: given_name,
349
+ family_name: family_name,
350
+ gender: gender)
351
+ unless response.instance_of?(Array)
352
+ print "Patient search by name and gender failed: Dde Response => #{response}"
353
+ return []
354
+ end
355
+ rescue StandardError => e
356
+ print "Patient search by name and gender failed: Dde Response => #{e}"
357
+ return []
358
+ end
359
+
360
+ response
361
+ end
362
+
363
+ def find_remote_patients_by_doc_id(doc_id)
364
+ Rails.logger.info("Searching for Dde patient by doc_id ##{doc_id}")
365
+ response, _status = dde_client.post('search_by_doc_id', doc_id: doc_id)
366
+ raise DdeError, "Patient search by doc_id failed: Dde Response => #{response}" unless response.instance_of?(Array)
367
+
368
+ response
369
+ end
370
+
371
+ def find_patient_doc_id(patient)
372
+ patient.patient_identifiers.where(identifier_type: dde_doc_id_type).first
373
+ end
374
+
375
+ # Resolves local and remote patients and post processes the remote
376
+ # patients to take on a structure similar to that of local
377
+ # patients.
378
+ def package_patients(local_patients, remote_patients, auto_push_singular_local: false)
379
+ patients = resolve_patients(local_patients: local_patients,
380
+ remote_patients: remote_patients,
381
+ auto_push_singular_local: auto_push_singular_local)
382
+
383
+ # In some cases we may have remote patients that were previously imported but
384
+ # whose NPID has changed, we need to find and resolve these local patients.
385
+ unresolved_patients = find_patients_by_doc_id(patients[:remotes].collect { |remote_patient| remote_patient['doc_id'] })
386
+ if unresolved_patients.empty?
387
+ return { locals: patients[:locals], remotes: patients[:remotes].collect { |patient| localise_remote_patient(patient) } }
388
+ end
389
+
390
+ additional_patients = resolve_patients(local_patients: unresolved_patients, remote_patients: patients[:remotes])
391
+
392
+ {
393
+ locals: patients[:locals] + additional_patients[:locals],
394
+ remotes: additional_patients[:remotes].collect { |patient| localise_remote_patient(patient) }
395
+ }
396
+ end
397
+
398
+ # Locally saves the first unresolved remote patient.
399
+ #
400
+ # Method internally calls resolve_patients on the passed arguments then
401
+ # attempts to save the first unresolved patient in the local database.
402
+ #
403
+ # Returns: The imported patient (or nil if no local and remote patients are
404
+ # present).
405
+ def import_remote_patient(local_patients, remote_patients)
406
+ patients = resolve_patients(local_patients: local_patients, remote_patients: remote_patients)
407
+
408
+ return patients[:locals].first if patients[:remotes].empty?
409
+
410
+ save_remote_patient(patients[:remotes].first)
411
+ end
412
+
413
+ # Filters out @{param remote_patients} that exist in @{param local_patients}.
414
+ #
415
+ # Returns a hash with all resolved and unresolved remote patients:
416
+ #
417
+ # { resolved: [..,], locals: [...], remotes: [...] }
418
+ #
419
+ # NOTE: All resolved patients are available in the local database
420
+ def resolve_patients(local_patients:, remote_patients:, auto_push_singular_local: false)
421
+ remote_patients = remote_patients.dup # Will be modifying this copy
422
+
423
+ # Match all locals to remotes, popping out the matching patients from
424
+ # the list of remotes. The remaining remotes are considered unresolved
425
+ # remotes.
426
+ resolved_patients = local_patients.each_with_object([]) do |local_patient, resolved_patients|
427
+ # Local patient present on remote?
428
+ remote_patient = remote_patients.detect do |patient|
429
+ same_patient?(local_patient: local_patient, remote_patient: patient)
430
+ end
431
+
432
+ remote_patients.delete(remote_patient) if remote_patient
433
+
434
+ resolved_patients << local_patient
435
+ end
436
+
437
+ if resolved_patients.empty? && (local_patients.size.zero? && remote_patients.size == 1)
438
+ # HACK: Frontenders requested that if only a single patient exists
439
+ # remotely and locally none exists, the remote patient should be
440
+ # imported.
441
+ local_patient = find_patients_by_doc_id(remote_patients[0]['doc_id']).first
442
+ resolved_patients = [local_patient || save_remote_patient(remote_patients[0])]
443
+ remote_patients = []
444
+ elsif auto_push_singular_local && resolved_patients.size == 1\
445
+ && remote_patients.empty? && local_only_patient?(resolved_patients.first)
446
+ # ANOTHER HACK: Push local only patient to Dde
447
+ resolved_patients = [push_local_patient_to_dde(resolved_patients[0])]
448
+ else
449
+ resolved_patients = local_patients
450
+ end
451
+
452
+ { locals: resolved_patients, remotes: remote_patients }
453
+ end
454
+
455
+ # Checks if patient only exists on local database
456
+ def local_only_patient?(patient)
457
+ !(patient.patient_identifiers.where(identifier_type: patient_identifier_type('National id')).exists?\
458
+ && patient.patient_identifiers.where(identifier_type: patient_identifier_type('Dde person document id')).exists?)
459
+ end
460
+
461
+ # Matches local and remote patient
462
+ def same_patient?(local_patient:, remote_patient:)
463
+ PatientIdentifier.where(patient: local_patient, identifier_type: dde_doc_id_type).any? do |doc_id|
464
+ doc_id.identifier == remote_patient['doc_id']
465
+ end
466
+ end
467
+
468
+ # Saves local patient to Dde and links the two using the IDs
469
+ # generated by Dde.
470
+ def push_local_patient_to_dde(patient)
471
+ Rails.logger.info("Pushing local patient ##{patient.patient_id} to Dde")
472
+ response, status = dde_client.post('add_person', openmrs_to_dde_patient(patient))
473
+
474
+ if status == 422
475
+ error = UnprocessableEntityError.new("Failed to create patient in Dde: #{response.to_json}")
476
+ error.add_entity(patient)
477
+ raise error
478
+ end
479
+
480
+ raise response.to_json if status != 200
481
+
482
+ merging_service.link_local_to_remote_patient(patient, response)
483
+ end
484
+
485
+ # Converts a remote patient coming from Dde into a structure similar
486
+ # to that of a local patient
487
+ def localise_remote_patient(patient)
488
+ Patient.new(
489
+ patient_identifiers: localise_remote_patient_identifiers(patient),
490
+ person: Person.new(
491
+ names: localise_remote_patient_names(patient),
492
+ addresses: localise_remote_patient_addresses(patient),
493
+ birthdate: patient['birthdate'],
494
+ birthdate_estimated: patient['birthdate_estimated'],
495
+ gender: patient['gender']
496
+ )
497
+ )
498
+ end
499
+
500
+ def localise_remote_patient_identifiers(remote_patient)
501
+ [PatientIdentifier.new(identifier: remote_patient['npid'],
502
+ identifier_type: patient_identifier_type('National ID')),
503
+ PatientIdentifier.new(identifier: remote_patient['doc_id'],
504
+ identifier_type: patient_identifier_type('Dde Person Document ID'))]
505
+ end
506
+
507
+ def localise_remote_patient_names(remote_patient)
508
+ [PersonName.new(given_name: remote_patient['given_name'],
509
+ family_name: remote_patient['family_name'],
510
+ middle_name: remote_patient['middle_name'])]
511
+ end
512
+
513
+ def localise_remote_patient_addresses(remote_patient)
514
+ address = PersonAddress.new
515
+ address.city_village = remote_patient['attributes']['home_village']
516
+ address.home_traditional_authority = remote_patient['attributes']['home_traditional_authority']
517
+ address.home_district = remote_patient['attributes']['home_district']
518
+ address.current_village = remote_patient['attributes']['current_village']
519
+ address.current_traditional_authority = remote_patient['attributes']['current_traditional_authority']
520
+ address.current_district = remote_patient['attributes']['current_district']
521
+ [address]
522
+ end
523
+
524
+ def dde_client
525
+ client = DdeClient::DdeClient.new
526
+
527
+ connection = dde_connections[visit_type.id]
528
+
529
+ @dde_connections[visit_type.id] = if connection && %i[url username password].all? { |key| connection&.key?(key) }
530
+ client.restore_connection(connection)
531
+ else
532
+ client.connect(url: dde_config[:url],
533
+ username: dde_config[:username],
534
+ password: dde_config[:password])
535
+ end
536
+
537
+ client
538
+ end
539
+
540
+ # Loads a dde client into the dde_clients_cache for the
541
+ def dde_config
542
+ main_config = YAML.load_file(Dde_CONFIG_PATH)['config']
543
+ raise 'No configuration for Dde found' unless main_config
544
+
545
+ visit_type_config = main_config[visit_type.name.downcase]
546
+ raise "No Dde config for visit_type #{visit_type.name.downcase} found" unless visit_type_config
547
+
548
+ {
549
+ url: main_config['url'],
550
+ username: visit_type_config['username'],
551
+ password: visit_type_config['password']
552
+ }
553
+ end
554
+
555
+ # Converts an openmrs patient structure to a Dde person structure
556
+ def openmrs_to_dde_patient(patient)
557
+ LOGGER.debug "Converting OpenMRS person to dde_patient: #{patient}"
558
+ person = patient.person
559
+
560
+ person_name = person.names[0]
561
+ person_address = person.addresses[0]
562
+ person_attributes = filter_person_attributes(person.person_attributes)
563
+
564
+ dde_patient = HashWithIndifferentAccess.new(
565
+ given_name: person_name.given_name,
566
+ family_name: person_name.family_name,
567
+ gender: person.gender&.first,
568
+ birthdate: person.birthdate,
569
+ birthdate_estimated: person.birthdate_estimated ? 1 : 0,
570
+ attributes: {
571
+ current_district: person_address ? person_address.address3 : nil,
572
+ current_traditional_authority: person_address ? person_address.state_province : nil,
573
+ current_village: person_address ? person_address.city_village : nil,
574
+ home_district: person_address ? person_address.address1 : nil,
575
+ home_village: person_address ? person_address.city_village : nil,
576
+ home_traditional_authority: person_address ? person_address.county_district : nil,
577
+ occupation: person_attributes ? person_attributes[:occupation] : nil
578
+ }
579
+ )
580
+
581
+ doc_id = patient.patient_identifiers.where(identifier_type: patient_identifier_type('Dde person document id')).first
582
+ dde_patient[:doc_id] = doc_id.identifier if doc_id
583
+
584
+ LOGGER.debug "Converted openmrs person to dde_patient: #{dde_patient}"
585
+ dde_patient
586
+ end
587
+
588
+ def filter_person_attributes(person_attributes)
589
+ return nil unless person_attributes
590
+
591
+ person_attributes.each_with_object({}) do |attr, filtered|
592
+ case attr.person_attribute_type.name.downcase.gsub(/\s+/, '_')
593
+ when 'cell_phone_number'
594
+ filtered[:cell_phone_number] = attr.value
595
+ when 'occupation'
596
+ filtered[:occupation] = attr.value
597
+ when 'birthplace'
598
+ filtered[:home_district] = attr.value
599
+ when 'home_village'
600
+ filtered[:home_village] = attr.value
601
+ when 'ancestral_traditional_authority'
602
+ filtered[:home_traditional_authority] = attr.value
603
+ end
604
+ end
605
+ end
606
+
607
+ def patient_doc_id(patient)
608
+ PatientIdentifier
609
+ .joins(:identifier_type)
610
+ .merge(PatientIdentifierType.where(name: 'Dde person document id'))
611
+ .where(patient: patient)
612
+ .first
613
+ &.identifier
614
+ end
615
+
616
+ def dde_doc_id_type
617
+ PatientIdentifierType.find_by_name('Dde Person document ID')
618
+ end
619
+
620
+ def find_patients_by_doc_id(doc_ids)
621
+ identifiers = PatientIdentifier.joins(:identifier_type)
622
+ .merge(PatientIdentifierType.where(name: 'Dde Person Document ID'))
623
+ .where(identifier: doc_ids)
624
+ Patient.joins(:identifiers).merge(identifiers).distinct
625
+ end
626
+
627
+ def person_service
628
+ PersonService.new
629
+ end
630
+
631
+ def patient_service
632
+ PatientService.new
633
+ end
634
+
635
+ def merging_service
636
+ DdeClient::MergingService.new(self, -> { dde_client })
637
+ end
638
+
639
+ # A cache for all connections to dde (indexed by visit_type id)
640
+ def dde_connections
641
+ @dde_connections ||= {}
642
+ end
643
+ end