Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CRM-19151: Add the ability to merge memberships without data loss #11298

Merged
merged 2 commits into from
Mar 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CRM/Dedupe/Merger.php
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ public static function cpTables() {
// Empty array == do nothing - this table is handled by mergeGroupContact
'civicrm_subscription_history' => array(),
'civicrm_relationship' => array('CRM_Contact_BAO_Relationship' => 'mergeRelationships'),
'civicrm_membership' => array('CRM_Member_BAO_Membership' => 'mergeMemberships'),
);
}
return $tables;
Expand Down Expand Up @@ -518,7 +519,7 @@ public static function moveContactBelongings($mainId, $otherId, $tables = FALSE,
// Call custom processing function for objects that require it
if (isset($cpTables[$table])) {
foreach ($cpTables[$table] as $className => $fnName) {
$className::$fnName($mainId, $otherId, $sqls);
$className::$fnName($mainId, $otherId, $sqls, $tables, $tableOperations);
}
// Skip normal processing
continue;
Expand Down
127 changes: 127 additions & 0 deletions CRM/Member/BAO/Membership.php
Original file line number Diff line number Diff line change
Expand Up @@ -2570,4 +2570,131 @@ public static function getContactsCancelledMembership($contactID, $isTest = FALS
return $cancelledMembershipIds;
}

/**
* Merges the memberships from otherContactID to mainContactID.
*
* General idea is to merge memberships in regards to their type. We
* move the other contact’s contributions to the main contact’s
* membership which has the same type (if any) and then we update
* membership to avoid loosing `join_date`, `end_date`, and
* `status_id`. In this function, we don’t touch the contributions
* directly (CRM_Dedupe_Merger::moveContactBelongings() takes care
* of it).
*
* This function adds new SQL queries to the $sqlQueries parameter.
*
* @param int $mainContactID
* Contact id of main contact record.
* @param int $otherContactID
* Contact id of record which is going to merge.
* @param array $sqlQueries
* (reference) array of SQL queries to be executed.
* @param array $tables
* List of tables that have to be merged.
* @param array $tableOperations
* Special options/params for some tables to be merged.
*
* @see CRM_Dedupe_Merger::cpTables()
*/
public static function mergeMemberships($mainContactID, $otherContactID, &$sqlQueries, $tables, $tableOperations) {
/*
* If the user requests not to merge memberships but to add them,
* just attribute the `civicrm_membership` to the
* `$mainContactID`. We have to do this here since the general
* merge process is bypassed by this function.
*/
if (array_key_exists("civicrm_membership", $tableOperations) && $tableOperations['civicrm_membership']['add']) {
$sqlQueries[] = "UPDATE IGNORE civicrm_membership SET contact_id = $mainContactID WHERE contact_id = $otherContactID";
return;
}

/*
* Retrieve all memberships that belongs to each contacts and
* keep track of each membership type.
*/
$mainContactMemberships = array();
$otherContactMemberships = array();

$sql = "SELECT id, membership_type_id FROM civicrm_membership membership WHERE contact_id = %1";
$dao = CRM_Core_DAO::executeQuery($sql, array(1 => array($mainContactID, "Integer")));
while ($dao->fetch()) {
$mainContactMemberships[$dao->id] = $dao->membership_type_id;
}

$dao = CRM_Core_DAO::executeQuery($sql, array(1 => array($otherContactID, "Integer")));
while ($dao->fetch()) {
$otherContactMemberships[$dao->id] = $dao->membership_type_id;
}

/*
* For each membership, move related contributions to the main
* contact’s membership (by updating `membership_payments`). Then,
* update membership’s `join_date` (if the other membership’s
* join_date is older) and `end_date` (if the other membership’s
* `end_date` is newer) and `status_id` (if the newly calculated
* status is different).
*
* FIXME: what should we do if we have multiple memberships with
* the same type (currently we only take the first one)?
*/
$newSql = array();
foreach ($otherContactMemberships as $otherMembershipId => $otherMembershipTypeId) {
if ($newMembershipId = array_search($otherMembershipTypeId, $mainContactMemberships)) {

/*
* Move other membership’s contributions to the main one only
* if user requested to merge contributions.
*/
if (!empty($tables) && in_array('civicrm_contribution', $tables)) {
$newSql[] = "UPDATE civicrm_membership_payment SET membership_id=$newMembershipId WHERE membership_id=$otherMembershipId";
}

$sql = "SELECT * FROM civicrm_membership membership WHERE id = %1";

$newMembership = CRM_Member_DAO_Membership::findById($newMembershipId);
$otherMembership = CRM_Member_DAO_Membership::findById($otherMembershipId);

$updates = array();
if (new DateTime($otherMembership->join_date) < new DateTime($newMembership->join_date)) {
$updates["join_date"] = $otherMembership->join_date;
}

if (new DateTime($otherMembership->end_date) > new DateTime($newMembership->end_date)) {
$updates["end_date"] = $otherMembership->end_date;
}

if (count($updates)) {

/*
* Update status
*/
$status = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate(
isset($updates["start_date"]) ? $updates["start_date"] : $newMembership->start_date,
isset($updates["end_date"]) ? $updates["end_date"] : $newMembership->end_date,
isset($updates["join_date"]) ? $updates["join_date"] : $newMembership->join_date,
'today',
FALSE,
$newMembershipId,
$newMembership
);

if (!empty($status['id']) and $status['id'] != $newMembership->status_id) {
$updates['status_id'] = $status['id'];
}

$updates_sql = [];
foreach ($updates as $k => $v) {
$updates_sql[] = "$k = '{$v}'";
}

$newSql[] = sprintf("UPDATE civicrm_membership SET %s WHERE id=%s", implode(", ", $updates_sql), $newMembershipId);
$newSql[] = sprintf("DELETE FROM civicrm_membership WHERE id=%s", $otherMembershipId);
}

}
}

$sqlQueries = array_merge($sqlQueries, $newSql);
}

}