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

Extract out remaining usage of MembershipPayment #32222

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
2 changes: 1 addition & 1 deletion CRM/Batch/Form/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class CRM_Batch_Form_Entry extends CRM_Core_Form {
*/
public function getCurrentRowContributionID(): int {
if (!isset($this->currentRowContributionID)) {
$this->currentRowContributionID = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipPayment', $this->getCurrentRowMembershipID(), 'contribution_id', 'membership_id');
$this->currentRowContributionID = CRM_Member_BAO_MembershipPayment::getLatestContributionIDFromLineitemAndFallbackToMembershipPayment($this->getCurrentRowMembershipID());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wow - I think we could ditch the fallback & just use the line item - this is a slightly obscure screen & there are enough other places that rely on people having good data re line item entity_id that I don't think this is the place we need to bend over backwards for bad data

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we probably could. Except that this getFieldValue logic pops up in various places and causes obscure bugs. I've come across sites missing lineitems and sites missing MembershipPayment records and a mixture of both... so I feel like the safer option is to extract out this logic and leave the fallback in initially. We might add some kind of upgrader/check before we can completely remove the MembershipPayment fallback. See #32244 for the extraction of just this function for easier review!

}
return $this->currentRowContributionID;
}
Expand Down
22 changes: 3 additions & 19 deletions CRM/Contribute/BAO/Contribution.php
Original file line number Diff line number Diff line change
Expand Up @@ -953,27 +953,11 @@ protected static function getRelatedMemberships(int $contributionID): array {
->addSelect('entity_id')
->execute()->indexBy('entity_id'));

$doubleCheckParams = [
'return' => 'membership_id',
'contribution_id' => $contributionID,
];
if (!empty($membershipIDs)) {
$doubleCheckParams['membership_id'] = ['NOT IN' => $membershipIDs];
}
$membershipPayments = civicrm_api3('MembershipPayment', 'get', $doubleCheckParams)['values'];
if (!empty($membershipPayments)) {
$membershipIDs = [];
CRM_Core_Error::deprecatedWarning('Not having valid line items for membership payments is invalid.');
foreach ($membershipPayments as $membershipPayment) {
$membershipIDs[] = $membershipPayment['membership_id'];
}
}
$membershipIDs = CRM_Member_BAO_MembershipPayment::getMembershipPaymentsWithMissingLineitems($contributionID, $membershipIDs);

if (empty($membershipIDs)) {
return [];
}
// We could combine this with the MembershipPayment.get - we'd
// need to re-wrangle the params (here or in the calling function)
// as they would then me membership.contact_id, membership.is_test etc
return civicrm_api3('Membership', 'get', [
'id' => ['IN' => $membershipIDs],
'return' => ['id', 'contact_id', 'membership_type_id', 'is_test', 'status_id', 'end_date'],
Expand Down Expand Up @@ -3420,7 +3404,7 @@ public static function getPaymentInfo($id, $component = 'contribution', $getTrxn
}
}
elseif ($component == 'membership') {
$contributionId = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipPayment', $id, 'contribution_id', 'membership_id');
$contributionId = CRM_Member_BAO_MembershipPayment::getLatestContributionIDFromLineitemAndFallbackToMembershipPayment($id);
}
else {
$contributionId = $id;
Expand Down
7 changes: 1 addition & 6 deletions CRM/Contribute/Form/Contribution/Confirm.php
Original file line number Diff line number Diff line change
Expand Up @@ -1507,12 +1507,7 @@ protected function postProcessMembership(

if (!empty($membershipContribution)) {
// Next line is probably redundant. Checks prevent it happening twice.
$membershipPaymentParams = [
'membership_id' => $membership->id,
'membership_type_id' => $membership->membership_type_id,
'contribution_id' => $membershipContribution->id,
];
civicrm_api3('MembershipPayment', 'create', $membershipPaymentParams);
CRM_Member_BAO_MembershipPayment::legacyMembershipPaymentCreate($membership->id, $membershipContribution->id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could check if this does anything & maybe remove it. I'm pretty sure creating the line item does this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the extracted function is only called in one place. But my thinking is that it was easier to extract everything related to MembershipPayment first and then it should be easier to justify just removing stuff. Eg. if getLatestContributionIDFromLineitemAndFallbackToMembershipPayment() function is used everywhere we can probably justify just removing this call to MembershipPayment.create on the basis that we don't need the MembershipPayment record to be created at all as we can verify that a Membership LineItem is created and we know that the LineItem will be checked first.

}
if ($membership) {
CRM_Core_BAO_CustomValueTable::postProcess($this->_params, 'civicrm_membership', $membership->id, 'Membership');
Expand Down
2 changes: 1 addition & 1 deletion CRM/Core/BAO/FinancialTrxn.php
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ public static function getPartialPaymentWithType($entityId, $entityName = 'parti
$contributionId = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_ParticipantPayment', $entityId, 'contribution_id', 'participant_id');
}
elseif ($entityName == 'membership') {
$contributionId = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipPayment', $entityId, 'contribution_id', 'membership_id');
$contributionId = CRM_Member_BAO_MembershipPayment::getLatestContributionIDFromLineitemAndFallbackToMembershipPayment($entityId);
}
else {
$contributionId = $entityId;
Expand Down
52 changes: 6 additions & 46 deletions CRM/Member/BAO/Membership.php
Original file line number Diff line number Diff line change
Expand Up @@ -312,16 +312,10 @@ public static function create(&$params, $ids = []) {
// once we are rid of direct calls to the BAO::create from core
// we will deprecate this stuff into the v3 api.
if (($params['version'] ?? 0) !== 4) {
// @todo further cleanup required to remove use of $ids['contribution'] from here
if (isset($ids['membership'])) {
$contributionID = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipPayment',
$membership->id,
'contribution_id',
'membership_id'
);
// @todo this is a temporary step to removing $ids['contribution'] completely
if (empty($params['contribution_id']) && !empty($contributionID)) {
$params['contribution_id'] = $contributionID;
$latestContributionID = CRM_Member_BAO_MembershipPayment::getLatestContributionIDFromLineitemAndFallbackToMembershipPayment($membership->id);
if (empty($params['contribution_id']) && !empty($latestContributionID)) {
$params['contribution_id'] = $latestContributionID;
}
}

Expand All @@ -341,7 +335,7 @@ public static function create(&$params, $ids = []) {
// If the membership has no associated contribution then we ensure
// the line items are 'correct' here. This is a lazy legacy
// hack whereby they are deleted and recreated
if (empty($contributionID)) {
if (empty($latestContributionID)) {
if (!empty($params['lineItems'])) {
$params['line_item'] = $params['lineItems'];
}
Expand Down Expand Up @@ -637,7 +631,7 @@ public static function deleteMembership($membershipId, $preserveContrib = FALSE)
$params['source_record_id'] = $membershipId;
CRM_Activity_BAO_Activity::deleteActivity($params);
}
self::deleteMembershipPayment($membershipId, $preserveContrib);
CRM_Member_BAO_MembershipPayment::deleteMembershipPayment($membershipId, $preserveContrib);
CRM_Price_BAO_LineItem::deleteLineItems($membershipId, 'civicrm_membership');

$results = $membership->delete();
Expand Down Expand Up @@ -1412,32 +1406,6 @@ public static function createRelatedMemberships($params, $dao) {
}
}

/**
* Delete the record that are associated with this Membership Payment.
*
* @param int $membershipId
* @param bool $preserveContrib
*
* @return object
* $membershipPayment deleted membership payment object
*/
public static function deleteMembershipPayment($membershipId, $preserveContrib = FALSE) {

$membershipPayment = new CRM_Member_DAO_MembershipPayment();
$membershipPayment->membership_id = $membershipId;
$membershipPayment->find();

while ($membershipPayment->fetch()) {
if (!$preserveContrib) {
CRM_Contribute_BAO_Contribution::deleteContribution($membershipPayment->contribution_id);
}
CRM_Utils_Hook::pre('delete', 'MembershipPayment', $membershipPayment->id, $membershipPayment);
$membershipPayment->delete();
CRM_Utils_Hook::post('delete', 'MembershipPayment', $membershipPayment->id, $membershipPayment);
}
return $membershipPayment;
}

/**
* Build an array of available membership types in the current context.
*
Expand Down Expand Up @@ -2172,15 +2140,7 @@ public static function recordMembershipContribution(&$params) {
$params['contribution_id'] = $contribution->id;

// Create membership payment if it does not already exist
$membershipPayment = civicrm_api3('MembershipPayment', 'get', [
'contribution_id' => $contribution->id,
]);
if (empty($membershipPayment['count'])) {
civicrm_api3('MembershipPayment', 'create', [
'membership_id' => $params['membership_id'],
'contribution_id' => $contribution->id,
]);
}
CRM_Member_BAO_MembershipPayment::legacyMembershipPaymentCreateIfNotExist($contribution->id, $params['membership_id']);

return $contribution;
}
Expand Down
148 changes: 148 additions & 0 deletions CRM/Member/BAO/MembershipPayment.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
+--------------------------------------------------------------------+
*/

use Civi\Api4\LineItem;

/**
*
* @package CRM
Expand Down Expand Up @@ -85,4 +87,150 @@ public static function del($id) {
return (bool) self::deleteRecord(['id' => $id]);
}

/**
* Log a deprecated warning that there is a contribution with MembershipPayment records and missing LineItems
*
* @param int $contributionID
*
* @return void
*/
private static function deprecatedWarning(int $contributionID) {
CRM_Core_Error::deprecatedWarning('ContributionID: ' . $contributionID . ' has memberships with MembershipPayment records but missing LineItems. MembershipPayment records are deprecated.');
}

/**
* Given a Membership ID we should be able to get the latest Contribution ID from the LineItems
* But we might not have LineItems, in which case we try to get it from the MembershipPayment record
* if that exists and log a deprecation warning
*
* @param int $membershipID
*
* @return ?int
* @throws \CRM_Core_Exception
* @throws \Civi\API\Exception\UnauthorizedException
* @internal
*/
public static function getLatestContributionIDFromLineitemAndFallbackToMembershipPayment(int $membershipID) {
$latestMembershipLineItem = LineItem::get(FALSE)
->addSelect('contribution_id')
->addWhere('entity_table', '=', 'civicrm_membership')
->addWhere('entity_id', '=', $membershipID)
->addOrderBy('contribution_id.receive_date', 'DESC')
->execute()
->first();
if (!empty($latestMembershipLineItem['contribution_id'])) {
$latestContributionID = $latestMembershipLineItem['contribution_id'];
}
else {
$membershipPayments = civicrm_api3('MembershipPayment', 'get', [
'sequential' => 1,
'return' => ["contribution_id.receive_date", "contribution_id"],
'membership_id' => $membershipID,
'options' => ['sort' => "contribution_id.receive_date DESC"],
])['values'];
if (!empty($membershipPayments[0]['contribution_id'])) {
$latestContributionID = $membershipPayments[0]['contribution_id'];
self::deprecatedWarning($latestContributionID);
}
}
return $latestContributionID ?? NULL;
}

/**
* This is a helper function towards deprecating/removing MembershipPayment
* It checks for memberships linked via MembershipPayment with missing LineItems
*
* @param int $contributionID
* @param array $membershipIDsWithLineItems
*
* @return array
* @throws \CRM_Core_Exception
* @internal
*/
public static function getMembershipPaymentsWithMissingLineitems(int $contributionID, array $membershipIDsWithLineItems): array {
$doubleCheckParams = [
'return' => 'membership_id',
'contribution_id' => $contributionID,
];
if (!empty($membershipIDsWithLineItems)) {
$doubleCheckParams['membership_id'] = ['NOT IN' => $membershipIDsWithLineItems];
}
$membershipPayments = civicrm_api3('MembershipPayment', 'get', $doubleCheckParams)['values'];
if (!empty($membershipPayments)) {
$membershipIDsWithLineItems = [];
self::deprecatedWarning($contributionID);
foreach ($membershipPayments as $membershipPayment) {
$membershipIDsWithLineItems[] = $membershipPayment['membership_id'];
}
}
return $membershipIDsWithLineItems;
}

/**
* Delete the records that are associated with this Membership Payment.
*
* @param int $membershipId
* @param bool $preserveContrib
*
* @return object
* $membershipPayment deleted membership payment object
* @internal
*/
public static function deleteMembershipPayment(int $membershipId, bool $preserveContrib = FALSE) {
$membershipPayment = new CRM_Member_DAO_MembershipPayment();
$membershipPayment->membership_id = $membershipId;
$membershipPayment->find();

while ($membershipPayment->fetch()) {
if (!$preserveContrib) {
CRM_Contribute_BAO_Contribution::deleteContribution($membershipPayment->contribution_id);
}
CRM_Utils_Hook::pre('delete', 'MembershipPayment', $membershipPayment->id, $membershipPayment);
$membershipPayment->delete();
CRM_Utils_Hook::post('delete', 'MembershipPayment', $membershipPayment->id, $membershipPayment);
}
return $membershipPayment;
}

/**
* @param int $membershipID
* @param int $contributionID
* @param bool $isSkipLineItem Creating a legacy MembershipPayment record creates a LineItem. If we already have a lineItem
* set this to TRUE to skip creating lineItems
*
* @return void
* @throws \CRM_Core_Exception
* @internal
*/
public static function legacyMembershipPaymentCreate(int $membershipID, int $contributionID, bool $isSkipLineItem = FALSE) {
$membershipPaymentParams = [
'membership_id' => $membershipID,
'contribution_id' => $contributionID,
'isSkipLineItem' => $isSkipLineItem,
];
civicrm_api3('MembershipPayment', 'create', $membershipPaymentParams);
}

/**
* Checks if a MembershipPayment exists and if not creates one
*
* @param int $membershipID
* @param int $contributionID
* @param bool $isSkipLineItem Creating a legacy MembershipPayment record creates a LineItem. If we already have a lineItem
* set this to TRUE to skip creating lineItems
*
* @return void
* @throws \CRM_Core_Exception
* @internal
*/
public static function legacyMembershipPaymentCreateIfNotExist(int $membershipID, int $contributionID, bool $isSkipLineItem = FALSE) {
$membershipPayment = civicrm_api3('MembershipPayment', 'get', [
'membership_id' => $membershipID,
'contribution_id' => $contributionID,
]);
if (empty($membershipPayment['count'])) {
self::legacyMembershipPaymentCreate($membershipID, $contributionID, $isSkipLineItem);
}
}

}
8 changes: 2 additions & 6 deletions CRM/Member/Form/Membership.php
Original file line number Diff line number Diff line change
Expand Up @@ -1394,12 +1394,8 @@ protected function updateContributionOnMembershipTypeChange($inputParams) {
}

// retrieve the related contribution ID
$contributionID = CRM_Core_DAO::getFieldValue(
'CRM_Member_DAO_MembershipPayment',
$this->getMembershipID(),
'contribution_id',
'membership_id'
);
$contributionID = CRM_Member_BAO_MembershipPayment::getLatestContributionIDFromLineitemAndFallbackToMembershipPayment($this->getMembershipID());

// get price fields of chosen price-set
$priceSetDetails = CRM_Utils_Array::value(
$this->_priceSetId,
Expand Down
6 changes: 1 addition & 5 deletions CRM/Member/Form/MembershipRenewal.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,7 @@ public function setDefaultValues() {
$defaults['receive_date'] = $now . ' ' . CRM_Utils_Time::date('H:i:s');

if ($defaults['id']) {
$defaults['record_contribution'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipPayment',
$defaults['id'],
'contribution_id',
'membership_id'
);
$defaults['record_contribution'] = CRM_Member_BAO_MembershipPayment::getLatestContributionIDFromLineitemAndFallbackToMembershipPayment($defaults['id']);
}

$defaults['financial_type_id'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType', $this->_memType, 'financial_type_id');
Expand Down
11 changes: 1 addition & 10 deletions CRM/Price/BAO/LineItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,7 @@ public static function create(&$params) {

$return = $lineItemBAO->save();
if ($lineItemBAO->entity_table === 'civicrm_membership' && $lineItemBAO->contribution_id && $lineItemBAO->entity_id) {
$membershipPaymentParams = [
'membership_id' => $lineItemBAO->entity_id,
'contribution_id' => $lineItemBAO->contribution_id,
];
if (!civicrm_api3('MembershipPayment', 'getcount', $membershipPaymentParams)) {
// If we are creating the membership payment row from the line item then we
// should have correct line item & membership payment should not need to fix.
$membershipPaymentParams['isSkipLineItem'] = TRUE;
civicrm_api3('MembershipPayment', 'create', $membershipPaymentParams);
}
CRM_Member_BAO_MembershipPayment::legacyMembershipPaymentCreateIfNotExist($lineItemBAO->entity_id, $lineItemBAO->contribution_id, TRUE);
}
if ($lineItemBAO->entity_table === 'civicrm_participant' && $lineItemBAO->contribution_id && $lineItemBAO->entity_id) {
$participantPaymentParams = [
Expand Down
1 change: 0 additions & 1 deletion api/v3/MembershipPayment.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
* This api exposes CiviCRM membership contribution link.
*
* @package CiviCRM_APIv3
* @todo delete function doesn't exist
*/

/**
Expand Down