1. flipping dupe pairs for a row or selections. 2. store meaningful - conflict labels...
authordeepak-srivastava <deepak.srivastava.0303@gmail.com>
Tue, 21 Jul 2015 14:58:46 +0000 (15:58 +0100)
committerdeepak-srivastava <deepak.srivastava.0303@gmail.com>
Sat, 8 Aug 2015 20:53:01 +0000 (21:53 +0100)
CRM/Contact/Page/AJAX.php
CRM/Contact/Page/DedupeMerge.php
CRM/Core/BAO/CustomValueTable.php
CRM/Core/BAO/PrevNextCache.php
CRM/Core/Permission.php
CRM/Core/xml/Menu/Contact.xml
CRM/Dedupe/Merger.php
templates/CRM/Contact/Page/DedupeFind.tpl

index e597802c87a47cdb7f240a3a5c9d7247c925e736..dc7ea9185156867cffe2125891337b07d98eb070 100644 (file)
@@ -816,37 +816,38 @@ LIMIT {$offset}, {$rowCount}
       $srcTypeImage = CRM_Contact_BAO_Contact_Utils::getImage($srcContactSubType ?
         $srcContactSubType : $pairInfo['src_contact_type'],
         FALSE,
-        $pair['srcID']
+        $pairInfo['entity_id1']
       );
       $dstTypeImage = CRM_Contact_BAO_Contact_Utils::getImage($dstContactSubType ?
         $dstContactSubType : $pairInfo['dst_contact_type'],
         FALSE,
-        $pair['dstID']
+        $pairInfo['entity_id2']
       );
 
       $searchRows[$count]['is_selected'] = $pairInfo['is_selected'];
       $searchRows[$count]['is_selected_input'] = "<input type='checkbox' class='crm-dedupe-select' name='pnid_{$pairInfo['prevnext_id']}' value='{$pairInfo['is_selected']}' onclick='toggleDedupeSelect(this)'>";
       $searchRows[$count]['src_image'] = $srcTypeImage;
-      $searchRows[$count]['src'] = CRM_Utils_System::href($pair['srcName'], 'civicrm/contact/view', "reset=1&cid={$pair['srcID']}");
+      $searchRows[$count]['src'] = CRM_Utils_System::href($pair['srcName'], 'civicrm/contact/view', "reset=1&cid={$pairInfo['entity_id1']}");
       $searchRows[$count]['src_email'] = CRM_Utils_Array::value('src_email', $pairInfo);
       $searchRows[$count]['src_street'] = CRM_Utils_Array::value('src_street', $pairInfo);
       $searchRows[$count]['src_postcode'] = CRM_Utils_Array::value('src_postcode', $pairInfo);
       $searchRows[$count]['dst_image'] = $dstTypeImage;
-      $searchRows[$count]['dst'] = CRM_Utils_System::href($pair['dstName'], 'civicrm/contact/view', "reset=1&cid={$pair['dstID']}");
+      $searchRows[$count]['dst'] = CRM_Utils_System::href($pair['dstName'], 'civicrm/contact/view', "reset=1&cid={$pairInfo['entity_id2']}");
       $searchRows[$count]['dst_email'] = CRM_Utils_Array::value('dst_email', $pairInfo);
       $searchRows[$count]['dst_street'] = CRM_Utils_Array::value('dst_street', $pairInfo);
       $searchRows[$count]['dst_postcode'] = CRM_Utils_Array::value('dst_postcode', $pairInfo);
       $searchRows[$count]['conflicts'] = CRM_Utils_Array::value('conflicts', $pair);
       $searchRows[$count]['weight'] = CRM_Utils_Array::value('weight', $pair);
 
-      if (!empty($pair['canMerge'])) {
-        $mergeParams = "reset=1&cid={$pair['srcID']}&oid={$pair['dstID']}&action=update&rgid={$rgid}";
+      if (!empty($pairInfo['data']['canMerge'])) {
+        $mergeParams = "reset=1&cid={$pairInfo['entity_id1']}&oid={$pairInfo['entity_id2']}&action=update&rgid={$rgid}";
         if ($gid) {
           $mergeParams .= "&gid={$gid}";
         }
 
+        $searchRows[$count]['actions']  = "<a class='crm-dedupe-flip' href='#' data-pnid={$pairInfo['prevnext_id']}>" . ts('flip') . "</a>&nbsp;|&nbsp;";
         $searchRows[$count]['actions'] = CRM_Utils_System::href(ts('merge'), 'civicrm/contact/merge', $mergeParams);
-        $searchRows[$count]['actions'] .= "&nbsp;|&nbsp;<a id='notDuplicate' href='#' onClick=\"processDupes( {$pair['srcID']}, {$pair['dstID']}, 'dupe-nondupe', 'dupe-listing'); return false;\">" . ts('not a duplicate') . "</a>";
+        $searchRows[$count]['actions'] .= "&nbsp;|&nbsp;<a id='notDuplicate' href='#' onClick=\"processDupes( {$pairInfo['entity_id1']}, {$pairInfo['entity_id2']}, 'dupe-nondupe', 'dupe-listing'); return false;\">" . ts('not a duplicate') . "</a>";
       }
       else {
         $searchRows[$count]['actions'] = '<em>' . ts('Insufficient access rights - cannot merge') . '</em>';
@@ -882,6 +883,26 @@ LIMIT {$offset}, {$rowCount}
     CRM_Utils_JSON::output($paperSize);
   }
 
+  static function flipDupePairs($prevNextId = NULL) {
+    if (!$prevNextId) {
+      $prevNextId = $_REQUEST['pnid'];
+    }
+    $query = "
+      UPDATE civicrm_prevnext_cache cpc
+      INNER JOIN civicrm_prevnext_cache old on cpc.id = old.id 
+      SET cpc.entity_id1 = cpc.entity_id2, cpc.entity_id2 = old.entity_id1 ";
+    if (is_array($prevNextId) && !CRM_Utils_Array::crmIsEmptyArray($prevNextId)) {
+      $prevNextId = implode(', ', $prevNextId);
+      $prevNextId = CRM_Utils_Type::escape($prevNextId, 'String');
+      $query     .= "WHERE cpc.id IN ({$prevNextId}) AND cpc.is_selected = 1";
+    } else {
+      $prevNextId = CRM_Utils_Type::escape($prevNextId, 'Positive');
+      $query     .= "WHERE cpc.id = $prevNextId";
+    }
+    CRM_Core_DAO::executeQuery($query);
+    CRM_Utils_JSON::output();
+  }
+
   /**
    * Used to store selected contacts across multiple pages in advanced search.
    */
index a3e393f66ac2c9f9e67b577fc46d2d5b1d627ac6..97970ea44a8ff12aabd5eef41d0a44751e1e951e 100644 (file)
@@ -66,6 +66,13 @@ class CRM_Contact_Page_DedupeMerge extends CRM_Core_Page{
     $cacheKeyString .= $rgid ? "_{$rgid}" : '_0';
     $cacheKeyString .= $gid ? "_{$gid}" : '_0';
 
+    $urlQry = "reset=1&action=update&rgid={$rgid}";
+    $urlQry = $gid ? ($urlQry . "&gid={$gid}") : $urlQry;
+
+    if ($mode == 'aggressive' && !CRM_Core_Permission::check('force merge duplicate contacts')) {
+      CRM_Core_Session::setStatus(ts('You do not have permission to force merge duplicate contact records'), ts('Permission Denied'), 'error');
+      CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/dedupefind', $urlQry));
+    }
     // Setup the Queue
     $queue = CRM_Queue_Service::singleton()->create(array(
       'name'  => $cacheKeyString,
@@ -82,9 +89,6 @@ class CRM_Contact_Page_DedupeMerge extends CRM_Core_Page{
       $isSelected = 2; 
     }
 
-    $urlQry = "reset=1&action=update&rgid={$rgid}";
-    $urlQry = $gid ? ($urlQry . "&gid={$gid}") : $urlQry;
-
     $total  = CRM_Core_BAO_PrevNextCache::getCount($cacheKeyString, NULL, $where);
     if ($total <= 0) {
       // Nothing to do.
index 10c377872758bbaa2e60c5d7e6c1c3ef2c1212e4..d2da71444e39469479150aae750299c852a00266 100644 (file)
@@ -210,9 +210,15 @@ class CRM_Core_BAO_CustomValueTable {
             default:
               break;
           }
-          $set[$field['column_name']] = "%{$count}";
-          $params[$count] = array($value, $type);
-          $count++;
+          if (strtolower($value) === "null") {
+            // when unsetting a value to null, we don't need to validate the type
+            // https://projectllr.atlassian.net/browse/VGQBMP-20
+            $set[$field['column_name']] = $value;
+          } else {
+            $set[$field['column_name']] = "%{$count}";
+            $params[$count] = array($value, $type);
+            $count++;
+          }
         }
 
         if (!empty($set)) {
index 90bc832f07b1d04ed297fffe778a9ea4081156b2..74d9995a33f2a2adce0f8a903dd11de7db9c4366 100644 (file)
@@ -181,7 +181,7 @@ WHERE  cacheKey     = %3 AND
       $data = $pncFind->data;
       if (!empty($data)) {
         $data = unserialize($data);
-        $data['conflicts'] = implode(",", array_keys($conflicts));
+        $data['conflicts'] = implode(",", array_values($conflicts));
 
         $pncUp = new CRM_Core_DAO_PrevNextCache();
         $pncUp->id = $pncFind->id;
index 9d983b921f05d7932c50fd67eac619e4bc6e8ba2..0e99a8d1b6ee2ad9c2ee3bbf51f3c649fe5b2e27 100644 (file)
@@ -771,6 +771,10 @@ class CRM_Core_Permission {
         $prefix . ts('merge duplicate contacts'),
         ts('Delete Contacts must also be granted in order for this to work.'),
       ),
+      'force merge duplicate contacts' => array(
+        $prefix . ts('force merge duplicate contacts'),
+        ts('Delete Contacts must also be granted in order for this to work.'),
+      ),
       'view debug output' => array(
         $prefix . ts('view debug output'),
         ts('View results of debug and backtrace'),
index b1ddb7d330ecd2ce15316bb5929ac72c8b51ffe9..3da74d9070096ff5b50c1a78b745041780df01a0 100644 (file)
      <page_callback>CRM_Contact_Page_AJAX::toggleDedupeSelect</page_callback>
      <access_arguments>merge duplicate contacts</access_arguments>
 </item>
+<item>
+     <path>civicrm/ajax/flipDupePairs</path>
+     <page_callback>CRM_Contact_Page_AJAX::flipDupePairs</page_callback>
+     <access_arguments>merge duplicate contacts</access_arguments>
+</item>
 <item>
   <path>civicrm/activity/sms/add</path>
   <path_arguments>action=add</path_arguments>
index 1d25f0841038995d823860256e7738b2b5de9a9f..2f5e5d7de7d0b6ae14fe5b664ac0624f0dea9f82 100644 (file)
@@ -731,6 +731,9 @@ INNER JOIN  civicrm_membership membership2 ON membership1.membership_type_id = m
 
         // store  any conflicts
         if (!empty($conflicts)) {
+          foreach ($conflicts as $key => $dnc) {
+            $conflicts[$key] = "{$migrationInfo['rows'][$key]['title']}: '{$migrationInfo['rows'][$key]['main']}' vs. '{$migrationInfo['rows'][$key]['other']}'";
+          }
           CRM_Core_BAO_PrevNextCache::markConflict($mainId, $otherId, $cacheKeyString, $conflicts);
         } else {
           // delete entry from PrevNextCache table so we don't consider the pair next time
@@ -798,15 +801,10 @@ INNER JOIN  civicrm_membership membership2 ON membership1.membership_type_id = m
         // particular field or not
         if (!empty($migrationInfo['rows'][$key]['main'])) {
           // if main also has a value its a conflict
-          if ($mode == 'safe') {
-            // note it down & lets wait for response from the hook.
-            // For no response skip this merge
-            $conflicts[$key] = NULL;
-          }
-          elseif ($mode == 'aggressive') {
-            // let the main-field be overwritten
-            continue;
-          }
+
+          // note it down & lets wait for response from the hook.
+          // For no response $mode will decide if to skip this merge
+          $conflicts[$key] = NULL;
         }
       }
       elseif (substr($key, 0, 14) == 'move_location_' and $val != NULL) {
@@ -831,16 +829,11 @@ INNER JOIN  civicrm_membership membership2 ON membership1.membership_type_id = m
               // try insert address at new available loc-type
               $migrationInfo['location'][$fieldName][$fieldCount]['locTypeId'] = $newTypeId;
             }
-            elseif ($mode == 'safe') {
+            else {
               // note it down & lets wait for response from the hook.
-              // For no response skip this merge
+              // For no response $mode will decide if to skip this merge
               $conflicts[$key] = NULL;
             }
-            elseif ($mode == 'aggressive') {
-              // let the loc-type-id be same as that of other-contact & go ahead
-              // with merge assuming aggressive mode
-              continue;
-            }
           }
         }
         elseif ($migrationInfo['rows'][$key]['main'] == $migrationInfo['rows'][$key]['other']) {
@@ -856,6 +849,7 @@ INNER JOIN  civicrm_membership membership2 ON membership1.membership_type_id = m
     // merge happens with new values filled in here. For a particular field / row not to be merged
     // field should be unset from fields_in_conflict.
     $migrationData['fields_in_conflict'] = $conflicts;
+    $migrationData['merge_mode']         = $mode;
     CRM_Utils_Hook::merge('batch', $migrationData, $mainId, $otherId);
     $conflicts = $migrationData['fields_in_conflict'];
     // allow hook to override / manipulate migrationInfo as well
@@ -872,6 +866,10 @@ INNER JOIN  civicrm_membership membership2 ON membership1.membership_type_id = m
           $migrationInfo[$key] = $val;
         }
       }
+      // if there are conflicts and mode is aggressive, allow hooks to decide if to skip merges
+      if (array_key_exists('skip_merge', $migrationData)) {
+        return (bool) $migrationData['skip_merge'];
+      }
     }
     return FALSE;
   }
index 76f380bdbb52c03b2d3e0facbafd3e64791f32f8..c3e11010c5ad250134492f7387d268ace89ee7ce 100644 (file)
 {if $context eq 'search'}
    {crmButton href=$backURL icon="close"}{ts}Done{/ts}{/crmButton}
 {elseif $context eq 'conflicts'}
-   {if $gid}
-      {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&gid=`$gid`&action=map&mode=aggressive" a=1}{/capture}
-   {else}
-      {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&action=map&mode=aggressive" a=1}{/capture}
-   {/if}
-   <a href="{$backURL}" title="{ts}Batch Merge Duplicate Contacts{/ts}" onclick="return confirm('{ts escape="js"}This will run the batch merge process on the selected duplicates. The operation will run in force merge mode - all selected duplicates will be merged into main contacts even in case of any conflicts. Click OK to proceed if you are sure you wish to run this operation.{/ts}');" class="button"><span>{ts}Force Merge Selected Duplicates{/ts}</span></a>
+   {if call_user_func(array('CRM_Core_Permission','check'), 'force merge duplicate contacts')}
+     {if $gid}
+       {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&gid=`$gid`&action=map&mode=aggressive" a=1}{/capture}
+     {else}
+       {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&action=map&mode=aggressive" a=1}{/capture}
+     {/if}
+     <a href="{$backURL}" title="{ts}Batch Merge Duplicate Contacts{/ts}" onclick="return confirm('{ts escape="js"}This will run the batch merge process on the selected duplicates. The operation will run in force merge mode - all selected duplicates will be merged into main contacts even in case of any conflicts. Click OK to proceed if you are sure you wish to run this operation.{/ts}');" class="button"><span>{ts}Force Merge Selected Duplicates{/ts}</span></a>
 
-   {if $gid}
-     {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&gid=`$gid`&mode=aggressive" a=1}{/capture}
-   {else}
-     {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&mode=aggressive" a=1}{/capture}
+     {if $gid}
+       {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&gid=`$gid`&action=map" a=1}{/capture}
+     {else}
+       {capture assign=backURL}{crmURL p="civicrm/contact/dedupemerge" q="reset=1&rgid=`$rgid`&action=map" a=1}{/capture}
+     {/if}
+     <a href="{$backURL}" title="{ts}Batch Merge Duplicate Contacts{/ts}" onclick="return confirm('{ts escape="js"}This will run the batch merge process on the selected duplicates. The operation will run in safe mode - only records with no direct data conflicts will be merged. Click OK to proceed if you are sure you wish to run this operation.{/ts}');" class="button"><span>{ts}Safe Merge Selected Duplicates{/ts}</span></a>
    {/if}
-   <a href="{$backURL}" title="{ts}Batch Merge Duplicate Contacts{/ts}" onclick="return confirm('{ts escape="js"}This will run the batch merge process on the listed duplicates. The operation will run in safe mode - only records with no direct data conflicts will be merged. Click OK to proceed if you are sure you wish to run this operation.{/ts}');" class="button"><span>{ts}Force Merge All Duplicates{/ts}</span></a>
 
    {if $gid}
       {capture assign=backURL}{crmURL p="civicrm/contact/dedupefind" q="reset=1&action=update&rgid=`$rgid`&gid=`$gid`" a=1}{/capture}
    {/if}
    <a href="{$backURL}" title="{ts}Batch Merge Duplicate Contacts{/ts}" onclick="return confirm('{ts escape="js"}This will run the batch merge process on the listed duplicates. The operation will run in safe mode - only records with no direct data conflicts will be merged. Click OK to proceed if you are sure you wish to run this operation.{/ts}');" class="button"><span><div class="icon ui-icon-script"></div>{ts}Batch Merge All Duplicates{/ts}</span></a>
 
+   <a href='#' title="{ts}Flip Selected Duplicates{/ts}" class="crm-dedupe-flip-selections button"><span>{ts}Flip Selected Duplicates{/ts}</span></a>
+
    {capture assign=backURL}{crmURL p="civicrm/contact/deduperules" q="reset=1" a=1}{/capture}
    <a href="{$backURL}" class="button crm-button-type-cancel">
      <span><div class="icon ui-icon-close"></div> {ts}Done{/ts}</span>
@@ -208,7 +212,10 @@ CRM.$(function($) {
       {data: "dst_street"},
       {data: "src_postcode"},
       {data: "dst_postcode"},
-      {data: "conflicts"},
+      {
+        data: "conflicts",
+        className: "crm-pair-conflict"
+      },
       {data: "weight"},
       {data: "actions"},
     ],
@@ -231,6 +238,8 @@ CRM.$(function($) {
       }
       // for action column at the last, set nowrap
       $('td:last', row).attr('nowrap','nowrap');
+      // for conflcts column
+      $('td.crm-pair-conflict', row).attr('nowrap','nowrap');
     }
   });
 
@@ -246,13 +255,14 @@ CRM.$(function($) {
   $('#dupePairs_length_selection').appendTo('#dupePairs_length');
   
   // apply selected class on click of a row
-  $('#dupePairs tbody').on('click', 'tr', function() {
+  $('#dupePairs tbody').on('click', 'tr', function(e) {
     $(this).toggleClass('crm-row-selected');
     $('input.crm-dedupe-select', this).prop('checked', $(this).hasClass('crm-row-selected'));
     var ele = $('input.crm-dedupe-select', this);
     toggleDedupeSelect(ele, 0);
   });
-  
+
+  // when select-all checkbox is checked
   $('#dupePairs thead tr .crm-dedupe-selection').on('click', function() {
     var checked = $('.crm-dedupe-select-all').prop('checked');
     if (checked) {
@@ -291,17 +301,68 @@ CRM.$(function($) {
     var column = table.column( $(this).attr('data-column-main') );
     column.visible( ! column.visible() );
 
+    // nowrap to conflicts column is applied only during initial rendering
+    // for show / hide clicks we need to set it explicitly
+    $('#dupePairs tbody td.crm-pair-conflict').attr('nowrap', 'nowrap');
+
     if ($(this).attr('data-column-dupe')) {
       column = table.column( $(this).attr('data-column-dupe') );
       column.visible( ! column.visible() );
     }
   });
   
+  // keep the conflicts checkbox checked when context is "conflicts"
   if(context == 'conflicts') {
     $('#conflicts').attr('checked', true);  
     var column = table.column( $('#conflicts').attr('data-column-main') );
     column.visible( ! column.visible() );
   }
+
+  // on click of flip link of a row
+  $('#dupePairs tbody').on('click', 'tr .crm-dedupe-flip', function(e) {
+    e.stopPropagation();
+    var $el   = $(this);
+    var $elTr = $(this).closest('tr');
+    var postUrl = {/literal}"{crmURL p='civicrm/ajax/flipDupePairs' h=0 q='snippet=4'}"{literal};
+    var request = $.post(postUrl, {pnid : $el.data('pnid')});
+    request.done(function(dt) {
+      var mapper = {2:4, 5:6, 7:8, 9:10}
+      var idx = table.row($elTr).index();
+      $.each(mapper, function(key, val) {
+        var v1  = table.cell(idx, key).data();
+        var v2  = table.cell(idx, val).data();
+        table.cell(idx, key).data(v2);
+        table.cell(idx, val).data(v1);
+      });
+      // keep the checkbox checked if needed
+      $('input.crm-dedupe-select', $elTr).prop('checked', $elTr.hasClass('crm-row-selected'));
+    });
+  });
+
+  $(".crm-dedupe-flip-selections").on('click', function(e) {
+    var ids = [];
+    $('.crm-row-selected').each(function() {
+      var ele = CRM.$('input.crm-dedupe-select', this);
+      ids.push(CRM.$(ele).attr('name').substr(5));
+    });
+    if (ids.length > 0) {
+      var dataUrl = {/literal}"{crmURL p='civicrm/ajax/flipDupePairs' h=0 q='snippet=4'}"{literal};
+      CRM.$.post(dataUrl, {pnid: ids}, function (response) {
+        var mapper = {2:4, 5:6, 7:8, 9:10}
+        $('.crm-row-selected').each(function() {
+          var idx = table.row(this).index();
+          $.each(mapper, function(key, val) {
+            var v1  = table.cell(idx, key).data();
+            var v2  = table.cell(idx, val).data();
+            table.cell(idx, key).data(v2);
+            table.cell(idx, val).data(v1);
+          });
+          // keep the checkbox checked if needed
+          $('input.crm-dedupe-select', this).prop('checked', $(this).hasClass('crm-row-selected'));
+        });
+      }, 'json');
+    }
+  });
 });
 
 function toggleDedupeSelect(element, isMultiple) {