-
-
Notifications
You must be signed in to change notification settings - Fork 824
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
WIP CRM-21682 automatic membership renewal cleanup #11556
WIP CRM-21682 automatic membership renewal cleanup #11556
Conversation
1bf6c57
to
669f24e
Compare
The completetransaction process is heavily tested from api_v3_ContributionTest & we have been very stringent about ensuring that any updates are accompanied by tests in there |
de40302
to
2184f52
Compare
ab0ee09
to
dd7bbae
Compare
dd7bbae
to
9bf9237
Compare
9bf9237
to
60f19a8
Compare
@mattwire Matt can you rebase this please |
6e44266
to
9ee3ec5
Compare
Thanks @mattwire, the business logic looks good. @omarabuhussein can you help review this PR please? |
'start_date', 'join_date', 'end_date', 'status_id'); | ||
$memberships = array(); | ||
if (!empty($contribution->contribution_recur_id)) { | ||
// Load memberships associated with recurring contribution |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this part a change in behaviour?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eileenmcnaughton I believe the expected behaviour was that you could have multiple memberships linked to a single recurring contribution and all of the code except this one point supports that. I believe it actually fixes a bug. It relates directly to the notes on the PR:
Before: When repeattransaction is called ONLY the membership linked to the original contribution will be renewed (if the contribution is in the correct state (ie. Completed - see Before/After)). loadRelatedObjects only loads one membership but the code is expected all memberships to be loaded so does handle multiple memberships if passed in.
After: A recurring contribution can be linked to multiple memberships and all memberships will be updated/renewed if all other conditions are met.
Part of the issue with this area of code is that there is no documentation about what it is actually meant to do! I am still getting reports of auto-renewals failing for some clients in obscure circumstances but I really need this patch reviewing/merging before I can get to the bottom of those issues (as the current core code is littered with inconsistencies including comparisons using getLabel instead of getKey/getName).
@omarabuhussein Did you get a chance to look at this? |
sorry @mattwire for being late, it is in my todo list but I was busy with many things during the last months and forget about it. Will make my best to review it this week. |
sorry I get sick in the previous week and had to take 3 days off and now I am a bit late on many things, I know this get delayed too much from my side and I am really sorry for that but I am putting this now on my first things todo on next Monday morning. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reviewed the PR and left some notes, hence that I reviewed the commits one by one and not by looking at the complete diff for all the commits, so it might be possible to see I commented on something but it was fixed in one of your next commits.
api/v3/Membership.php
Outdated
if (empty($params['id'])) { | ||
// This is a new membership, calculate the membership dates. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of writing an inline comment, you can do :
$isNewMembership = empty($params['id']);
then :
if ($isNewMembership ) {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I would prefer to leave the comments here
api/v3/Membership.php
Outdated
@@ -113,6 +116,7 @@ function civicrm_api3_membership_create($params) { | |||
); | |||
} | |||
else { | |||
// This is an existing membership, calculate the membership dates after renewal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you did what I said in my comment above, then I don't think there will be a need for this inline comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I would prefer to leave the comments here
api/v3/Membership.php
Outdated
@@ -101,9 +101,12 @@ function civicrm_api3_membership_create($params) { | |||
_civicrm_api3_custom_format_params($params, $values, 'Membership'); | |||
$params = array_merge($params, $values); | |||
|
|||
// Calculate membership dates |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of this inline comment, you can maybe move the dates calculation logic to a new function and pass the parameters by reference
api/v3/Membership.php
Outdated
$action = CRM_Core_Action::UPDATE; | ||
else { | ||
// edit mode | ||
$params['action'] = CRM_Core_Action::UPDATE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but now the $ids variable is no longer aviable in edit mode, though still you are passing it to :
$membershipBAO = CRM_Member_BAO_Membership::create($params, $ids, TRUE);
also for edit mode you are no longer passing :
$ids['membership']
which I am not sure if it is the right thing to do since there is still some code that use it in the create() method.
// this picks up membership type changes during renewals | ||
// @todo this is almost certainly an obsolete sql call, the pre-change | ||
// membership is accessible via $this->_relatedObjects | ||
$sql = " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmmm, but what if the membership type changed ? , your new code removed this query that handle that but did not offer a replacement
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@omarabuhussein Can you give an example of when this could happen? I thought from the comments that we can just get the pre-change membership directly.
if (!empty($contribution->contribution_recur_id)) { | ||
// Load memberships associated with recurring contribution | ||
$membershipResult = civicrm_api3('Membership', 'get', array( | ||
'contribution_recur_id' => $contribution->contribution_recur_id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is unlikely to have more than 25 memberships associated with the same contribution, but still would be better to add 'limit' => 0 option here. same for all the API calls below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
foreach ($membershipPaymentResult['values'] as $payment) { | ||
$membershipIDs[] = $payment['membership_id']; | ||
} | ||
$membershipResult = civicrm_api3('Membership', 'get', array( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmmm, why not just get the memberships using $primaryContributionID directly instead of fetching the payments IDs from the payments first ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@omarabuhussein We are working with a single contribution at this point. Membership.get requires a recurring contribution ID which we don't have. Am I missing something?
'contact_id' => $membership->contact_id, | ||
'is_test' => $membership->is_test, | ||
'membership_type_id' => $membership->membership_type_id, | ||
'id' => $membership['id'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add space before
$membershipParams = array(..etc
line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@eileenmcnaughton I believe it is important to have a 2nd or maybe even a 3rd eye on this PR in case I missed anything. @guanhuan Is it possible to have one of our QA team to go through this too? |
@mattwire @omarabuhussein I feel like a bunch of tests here would help me feel better.... |
do you mean manual tests or unit tests here @eileenmcnaughton ? |
@omarabuhussein unit tests. TBH I'm generally uncomfortable with such a big refactor - I'd prefer to see a single function extraction as one PR & review with a new test & then each change on top would get more tests. |
yeah such amount of changes is a bit scary for such legacy tightly coupled code. |
Right - some of it is just extractions / moving stuff around - which is not too hard to review in isolation - but REALLY hard to review when other changes are combined |
@mattwire can you rebase this & then remove any changes that don't relate to the one function & focus on how to break down the changes in there to a series of smaller refactors with tests. I'm keen to work through this but I want to get the risk down! |
9ee3ec5
to
94c0fb1
Compare
…ionalPayment Cleanup recordAdditionalPayment towards #11556
…leanup Cleanup of api_v3_membership_create towards #11556
@mattwire just a thought on your next refactor round after current ones are merged - it might be worth extracting
By not passing the $membership you can separate the loading of the membership from the processing of it & focus on the 2 parts of that equation separately |
other than @eileenmcnaughton notes above, is this ready to be reviewed again @mattwire ? |
@omarabuhussein my sense is that @mattwire is going to continue rebasing this & picking things off into smaller commits until it's merged |
I agree with the proposed changes to the business logic. The existing behaviour is clearly broken in my view, particularly the (almost) ignoring of contribution status, which we have observed to result in memberships being renewed from failed Direct Debit transactions, using the Smart Debit extension. |
7df185c
to
fb6a9e1
Compare
@mattwire I think we just merged something so this is due a rebase |
00c6d4b
to
0b7ddf7
Compare
0b7ddf7
to
cde3f38
Compare
Rebased and refactored commits. Extracted the next partial into #12542 |
@mattwire this is now the oldest PR & it's WIP - although there is a review-ready sub-PR. I'm going to close this & you can pull the next chunk out when that is reviewed |
Overview
WORK IN PROGRESS Parts of this are gradually being extracted into smaller, easier to review PRs. Once this is fully reviewed and merged documentation will also need updating.
Ref: #12315
This is mostly code cleanup and an attempt to understand exactly what happens when a membership is automatically renewed!
Fixes (and this needs adding to Civi docs somewhere as it's currently undocumented and unclear what it does):
Before
After
Documentation (After)