dde_mahis 0.1.4
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +20 -0
- data/Rakefile +8 -0
- data/app/assets/config/dde_manifest.js +1 -0
- data/app/assets/stylesheets/dde/application.css +15 -0
- data/app/controllers/dde_mahis/api/v1/dde_controller.rb +94 -0
- data/app/controllers/dde_mahis/api/v1/rollback_controller.rb +32 -0
- data/app/controllers/dde_mahis/application_controller.rb +4 -0
- data/app/helpers/dde_mahis/application_helper.rb +4 -0
- data/app/jobs/dde_mahis/application_job.rb +4 -0
- data/app/mailers/dde_mahis/application_mailer.rb +6 -0
- data/app/models/dde_mahis/application_record.rb +5 -0
- data/app/services/dde_client.rb +162 -0
- data/app/services/dde_service.rb +643 -0
- data/app/services/matcher.rb +92 -0
- data/app/services/merge_audit_service.rb +56 -0
- data/app/services/merging_service.rb +769 -0
- data/app/services/rollback_service.rb +320 -0
- data/app/utils/model_utils.rb +62 -0
- data/app/views/layouts/dde/application.html.erb +15 -0
- data/config/routes.rb +14 -0
- data/lib/dde_mahis/client_error.rb +3 -0
- data/lib/dde_mahis/engine.rb +7 -0
- data/lib/dde_mahis/version.rb +3 -0
- data/lib/dde_mahis.rb +6 -0
- data/lib/tasks/dde_tasks.rake +4 -0
- metadata +115 -0
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# module DdeMahis
|
4
|
+
##
|
5
|
+
# Matches local and remote (DdeMahis) people.
|
6
|
+
#
|
7
|
+
# TODO: Move module to own file
|
8
|
+
class Matcher
|
9
|
+
class << self
|
10
|
+
def find_differences(local_person, remote_person)
|
11
|
+
FIELDS_TO_MATCH
|
12
|
+
.map { |field| [field, diff_field(field, local_person, remote_person)] }
|
13
|
+
.reject { |_field, diff| diff.nil? }
|
14
|
+
.each_with_object({}) { |sub_diff, diff| diff[sub_diff[0]] = sub_diff[1] }
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
FIELDS_TO_MATCH = %w[given_name family_name birthdate birthdate_estimated gender
|
20
|
+
current_village current_traditional_authority current_district
|
21
|
+
home_village home_traditional_authority home_district
|
22
|
+
npid].freeze
|
23
|
+
|
24
|
+
LOCATION_FIELDS = %w[current_village current_traditional_authority current_district
|
25
|
+
home_village home_traditional_authority home_district].freeze
|
26
|
+
|
27
|
+
def diff_field(field, local_person, remote_person)
|
28
|
+
return diff_location(field, local_person, remote_person) if LOCATION_FIELDS.include?(field)
|
29
|
+
|
30
|
+
send("diff_#{field}", local_person, remote_person)
|
31
|
+
end
|
32
|
+
|
33
|
+
def diff_given_name(local_person, remote_person)
|
34
|
+
local_name = PersonName.find_by_person_id(local_person.person_id)
|
35
|
+
|
36
|
+
return nil if local_name&.given_name&.casecmp?(remote_person['given_name'])
|
37
|
+
|
38
|
+
{ local: local_name.given_name, remote: remote_person['given_name'] }
|
39
|
+
end
|
40
|
+
|
41
|
+
def diff_family_name(local_person, remote_person)
|
42
|
+
local_name = PersonName.find_by_person_id(local_person.person_id)
|
43
|
+
|
44
|
+
return nil if local_name&.family_name&.casecmp?(remote_person['family_name'])
|
45
|
+
|
46
|
+
{ local: local_name.family_name, remote: remote_person['family_name'] }
|
47
|
+
end
|
48
|
+
|
49
|
+
def diff_birthdate(local_person, remote_person)
|
50
|
+
return nil if local_person.birthdate&.to_date == remote_person['birthdate'].to_date
|
51
|
+
|
52
|
+
{ local: local_person.birthdate, remote: remote_person['birthdate'] }
|
53
|
+
end
|
54
|
+
|
55
|
+
def diff_birthdate_estimated(local_person, remote_person)
|
56
|
+
birthdate_estimated = local_person.birthdate_estimated.positive?
|
57
|
+
|
58
|
+
return nil if birthdate_estimated == remote_person['birthdate_estimated']
|
59
|
+
|
60
|
+
{ local: birthdate_estimated, remote: remote_person['birthdate_estimated'] }
|
61
|
+
end
|
62
|
+
|
63
|
+
def diff_gender(local_person, remote_person)
|
64
|
+
return nil if local_person.gender.first.casecmp?(remote_person['gender'].first)
|
65
|
+
|
66
|
+
{ local: local_person.gender, remote: remote_person['gender'] }
|
67
|
+
end
|
68
|
+
|
69
|
+
def diff_npid(local_person, remote_person)
|
70
|
+
npid_type = PatientIdentifierType.where(name: 'National id')
|
71
|
+
local_npid = PatientIdentifier.find_by(patient_id: local_person.person_id, identifier_type: npid_type)&.identifier
|
72
|
+
|
73
|
+
return nil if local_npid&.casecmp?(remote_person['npid'])
|
74
|
+
|
75
|
+
{ local: local_npid, remote: remote_person['npid'] }
|
76
|
+
end
|
77
|
+
|
78
|
+
def diff_location(field, local_person, remote_person)
|
79
|
+
local_address = PersonAddress.find_by_person_id(local_person.person_id)
|
80
|
+
remote_address = remote_person['attributes']
|
81
|
+
|
82
|
+
return nil if local_address.send(field).casecmp?(remote_address[field])
|
83
|
+
{ local: local_address.send(field), remote: remote_address[field] }
|
84
|
+
end
|
85
|
+
|
86
|
+
def current_location_id
|
87
|
+
Location.current_health_center.location_id
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This service will handle merge audits with their tree structure
|
4
|
+
class MergeAuditService
|
5
|
+
# This method a merge audit for us
|
6
|
+
def create_merge_audit(primary_patient, secondary_patient, merge_type)
|
7
|
+
recent_merge_id = MergeAudit.where(primary_id: secondary_patient).last&.id
|
8
|
+
merge_audit = MergeAudit.create({ primary_id: primary_patient, secondary_id: secondary_patient,
|
9
|
+
creator: User.current.id, merge_type: merge_type,
|
10
|
+
secondary_previous_merge_id: recent_merge_id })
|
11
|
+
raise "Could not create audit trail due to #{merge_audit.errors.as_json}" unless merge_audit.errors.empty?
|
12
|
+
end
|
13
|
+
|
14
|
+
# this uses the patient identifier to get the audit tree
|
15
|
+
def get_patient_audit(identifier)
|
16
|
+
fetch_merge_audit(find_voided_identifier(identifier))
|
17
|
+
end
|
18
|
+
|
19
|
+
# this uses the patient id to get the audit tree and it is used by get patient_audit
|
20
|
+
def fetch_merge_audit(secondary)
|
21
|
+
first_merge = common_merge_fetch('ma.secondary_id', secondary)
|
22
|
+
raise NotFoundError, "There is no merge for #{secondary}" if first_merge.blank?
|
23
|
+
|
24
|
+
count = 0
|
25
|
+
tree = [first_merge.merge({ 'merge_number' => count += 1 })]
|
26
|
+
merge_id = MergeAudit.where(primary_id: first_merge['primary_id']).last&.id
|
27
|
+
until merge_id.blank?
|
28
|
+
parent = common_merge_fetch('ma.secondary_previous_merge_id', merge_id)
|
29
|
+
tree << parent.merge({ 'merge_number' => count += 1 }) unless parent.blank?
|
30
|
+
merge_id = parent.blank? ? nil : MergeAudit.where(primary_id: parent['primary_id']).last&.id
|
31
|
+
end
|
32
|
+
tree.reverse
|
33
|
+
end
|
34
|
+
|
35
|
+
def common_merge_fetch(field, fetch_value)
|
36
|
+
ActiveRecord::Base.connection.select_one <<~SQL
|
37
|
+
SELECT ma.id, ma.primary_id, ma.secondary_id, ma.created_at merge_date, ma.merge_type, pn.given_name primary_first_name, pn.family_name primary_surname, p.gender primary_gender, p.birthdate primary_birthdate,
|
38
|
+
spn.given_name secondary_first_name, spn.family_name secondary_surname, sp.gender secondary_gender, sp.birthdate secondary_birthdate
|
39
|
+
FROM merge_audits ma
|
40
|
+
INNER JOIN person_name pn ON pn.person_id = ma.primary_id
|
41
|
+
INNER JOIN person p ON p.person_id = ma.primary_id
|
42
|
+
INNER JOIN person_name spn ON spn.person_id = ma.secondary_id AND spn.voided = 1
|
43
|
+
INNER JOIN person sp ON sp.person_id = ma.secondary_id AND sp.voided = 1
|
44
|
+
WHERE #{field} = #{fetch_value} AND ma.voided = 0
|
45
|
+
SQL
|
46
|
+
end
|
47
|
+
|
48
|
+
def find_voided_identifier(identifier)
|
49
|
+
result = ActiveRecord::Base.connection.select_one <<~SQL
|
50
|
+
SELECT patient_id FROM patient_identifier WHERE identifier = '#{identifier}' AND identifier_type = 3 AND voided = 1 ORDER BY date_voided ASC
|
51
|
+
SQL
|
52
|
+
raise NotFoundError, "Failed to find voided identifier: #{identifier}" if result.blank?
|
53
|
+
|
54
|
+
result['patient_id']
|
55
|
+
end
|
56
|
+
end
|