From d48ad2c0f9c6463877aeb81651b157430e4c8ced Mon Sep 17 00:00:00 2001 From: eileen Date: Wed, 25 May 2016 12:21:28 +1200 Subject: [PATCH] CRM-18673 fix merge handling of identical addresses --- CRM/Dedupe/Merger.php | 40 ++++++++++++++++++++++-- tests/phpunit/api/v3/JobTest.php | 52 ++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/CRM/Dedupe/Merger.php b/CRM/Dedupe/Merger.php index 46ac476192..0bc09a6f4d 100644 --- a/CRM/Dedupe/Merger.php +++ b/CRM/Dedupe/Merger.php @@ -868,20 +868,33 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m // Rule: Catch address conflicts (same address type on both contacts) if ($fieldName == 'address') { - $mainNewLocTypeId = $migrationInfo['location_blocks'][$fieldName][$fieldCount]['locTypeId']; if ( isset($migrationInfo['main_details']['location_blocks']['address']) && !empty($migrationInfo['main_details']['location_blocks']['address']) ) { + $otherAddresses = $migrationInfo['other_details']['location_blocks']['address']; + $otherAddressLookup = array(); + foreach ($otherAddresses as $otherAddressIndex => $otherAddress) { + $otherAddressLookup[$otherAddress['location_type_id']] = $otherAddressIndex; + } // Look for this LocTypeId in the results // @todo This can be streamlined using array_column() in PHP 5.5+ foreach ($migrationInfo['main_details']['location_blocks']['address'] as $addressKey => $addressRecord) { - if ($addressRecord['location_type_id'] == $mainNewLocTypeId) { + $otherAddressIndex = CRM_Utils_Array::value($addressRecord['location_type_id'], $otherAddressLookup); + $hasMatchingAddress = FALSE; + foreach ($otherAddresses as $otherAddress) { + if (self::addressIsSame($addressRecord, $otherAddress)) { + $hasMatchingAddress = TRUE; + } + } + if ($hasMatchingAddress) { + unset($migrationInfo[$key]); + } + elseif (!empty($otherAddresses[$otherAddressIndex]) && !self::addressIsSame($addressRecord, $otherAddresses[$otherAddressIndex])) { $conflicts[$key] = NULL; break; } } - } } // For other locations, don't merge/add if the values are the same @@ -926,6 +939,27 @@ INNER JOIN civicrm_membership membership2 ON membership1.membership_type_id = m return FALSE; } + /** + * Compare 2 addresses to see if they are the same. + * + * @param array $mainAddress + * @param array $comparisonAddress + * + * @return bool + */ + static protected function addressIsSame($mainAddress, $comparisonAddress) { + $keysToIgnore = array('id', 'is_primary', 'is_billing', 'manual_geo_code', 'contact_id'); + foreach ($comparisonAddress as $field => $value) { + if (in_array($field, $keysToIgnore)) { + continue; + } + if (!empty($value) && $mainAddress[$field] != $value) { + return FALSE; + } + } + return TRUE; + } + /** * A function to build an array of information about location blocks that is * required when merging location fields diff --git a/tests/phpunit/api/v3/JobTest.php b/tests/phpunit/api/v3/JobTest.php index 63de713203..dfe3648dcf 100644 --- a/tests/phpunit/api/v3/JobTest.php +++ b/tests/phpunit/api/v3/JobTest.php @@ -353,6 +353,58 @@ class api_v3_JobTest extends CiviUnitTestCase { ), 4); } + /** + * Test the batch merge does not create duplicate emails. + * + * Test CRM-18546, a 4.7 regression whereby a merged contact gets duplicate emails. + */ + public function testBatchMergeMatchingAddress() { + for ($x = 0; $x <= 2; $x++) { + $this->individualCreate(array( + 'api.address.create' => array( + 'location_type_id' => 'Home', + 'street_address' => 'Appt 115, The Batcave', + 'city' => 'Gotham', + 'postal_code' => 'Nananananana', + ), + )); + } + // Different location type, still merge, identical. + $this->individualCreate(array( + 'api.address.create' => array( + 'location_type_id' => 'Main', + 'street_address' => 'Appt 115, The Batcave', + 'city' => 'Gotham', + 'postal_code' => 'Nananananana', + ), + )); + + $this->individualCreate(array( + 'api.address.create' => array( + 'location_type_id' => 'Home', + 'street_address' => 'Appt 115, The Batcave', + 'city' => 'Gotham', + 'postal_code' => 'Batman', + ), + )); + + $result = $this->callAPISuccess('Job', 'process_batch_merge', array()); + $this->assertEquals(3, count($result['values']['merged'])); + $this->assertEquals(1, count($result['values']['skipped'])); + $this->callAPISuccessGetCount('Contact', array('street_address' => 'Appt 115, The Batcave'), 2); + $contacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 0)); + $deletedContacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 1)); + $this->callAPISuccessGetCount('Address', array( + 'street_address' => 'Appt 115, The Batcave', + 'contact_id' => array('IN' => array_keys($contacts['values'])), + ), 3); + + $this->callAPISuccessGetCount('Address', array( + 'street_address' => 'Appt 115, The Batcave', + 'contact_id' => array('IN' => array_keys($deletedContacts['values'])), + ), 2); + } + /** * Test the batch merge by id range. * -- 2.25.1