Fixing how "next scheduled contribution date" is calculated for fixed memberships payment plans to be based on the membership end date + cycle day, instead of last contribution date.
Before
As part of of the work we did in MAE-372 Epic, we changed how installments (contributions) "receive date" is calculated upon autorenewal to be based on the payment plan (recurring contribution) "next scheduled contribution date" .
But with fixed memberships this result in some problems, here is an example:
1- Create new "Fixed" membership type with duration of "1 Year" and period start day = "1st of December" and period rollover day = "30th of November":
2- Create new membership using Membership creation form through CiviCRM, use "Payment Plan" as a method of payment, set Schedule to "Annual, and set the membership start and join dates to : "3rd of July 2020"
3- The created contribution date will be "3rd of July 2020" since we by default set the first installment date (in case on non direct debit payments) to equal the membership start date (which is also "3rd of July 2020" as we set it above during the membership creation).
4- But based on the current logic, the "Next scheduled contribution date" will be "last contribution receive date + 1 year" for annual payment plans, so in this case it will be "3rd of July 2021":
This means when the autorenewal happen, the newly created installment (contribution) will have its receive date set to "3rd of July 2021", but the membership new start date will be "1st of December 2020", so the payment will be collected about 7 months ahead of the membership new term after renewal, which is too far and does not make sense to be the case.
After
The above is solved by adding new rule for "Fixed" memberships, so if we are creating a fixed membership with payment plan we do the following steps:
1- Use the membership end date + 1 day as a base to calculate the "next scheduled contribution date", so in the example above the it will be "1st of December 2020" (30th of November 2020 [the membership end date] + 1 day), so upon renewal the new contribution receive date will also be "1st of December 2020".
2- Adjust the date to match the cycle day in case of monthly payment plans. In our example it is an annual payment plan where cycle day does not apply, so the next scheduled contribution date will be "1st of December 2020".
The above steps are only applied when new payment plan is created for the first time, and does not apply to renewals, because on renewals the memberships will be renewed for full period and not for partial period as it might be when it gets created for the first time, also if this logic was force on renewals, it will cause issues when the user change the "next schedule contribution date manually" and will result in it getting ignored which is against of what we are doing with this field in other cases, so it was important to exclude the renewal logic .
The above also raises the following questions:
1- Does the issue happen when buying fixed memberships using monthly or quarterly payment plans?
It should not, given in such cases only the first installment "receive date" might be off, but the rest of the installments will have dates that are properly spread across the remaining period of the membership, so our original logic will still work correctly for them. But in this PR I did not add an exception for them and applied the logic here to them too to keep the code easier to understand, and because both the current and the new logic should end up in the same result for both of them.
2- What if someone set up a Priceset with one fixed membership and one rolling membership ?
We don't allow that anymore in Membershipextras, like you can still create such priceset, but in the membership form it will show the following error:
3- What if someone set up a Priceset with two fixed memberships but each one has different period start date and period rollover date ?
We also don't allow that anymore in Membershipextras, like you can still create such priceset, but in the membership form it will show the following error:
4- What about webforms?
Given the logic we have in this PR is also called by webforms to calculate the "next scheduled contribution date", then we have them covered.
Though the rules we specified in point 2 and 3 above are not enforced on webforms and only work on Admin forms, so it is the person who configure the webform responsibility to make sure that such kind of configurations does not happen. (though it is something we should improve at some point and enforce in code instead of relying on users to not do such kind of things)
Technical Details
I've updated the PaymentPlanNextContributionDate service which is the service that is responsible for calculating the "next contribution date" across the extension, so it gets the payment plan memberships and checks if there is a fixed membership, if so then it will add one day to it, then correct it to match the cycle day, and then use it to be the "next contribution date". I also added new parameter to it called "$operation" so it knows if the who request this service is form payment plan creation context or renewal context, so it know if the logic we added for fixed membership should apply or not.
Overview
Fixing how "next scheduled contribution date" is calculated for fixed memberships payment plans to be based on the membership end date + cycle day, instead of last contribution date.
Before
As part of of the work we did in MAE-372 Epic, we changed how installments (contributions) "receive date" is calculated upon autorenewal to be based on the payment plan (recurring contribution) "next scheduled contribution date" .
But with fixed memberships this result in some problems, here is an example:
1- Create new "Fixed" membership type with duration of "1 Year" and period start day = "1st of December" and period rollover day = "30th of November":
2- Create new membership using Membership creation form through CiviCRM, use "Payment Plan" as a method of payment, set Schedule to "Annual, and set the membership start and join dates to : "3rd of July 2020"
3- The created contribution date will be "3rd of July 2020" since we by default set the first installment date (in case on non direct debit payments) to equal the membership start date (which is also "3rd of July 2020" as we set it above during the membership creation).
4- But based on the current logic, the "Next scheduled contribution date" will be "last contribution receive date + 1 year" for annual payment plans, so in this case it will be "3rd of July 2021":
This means when the autorenewal happen, the newly created installment (contribution) will have its receive date set to "3rd of July 2021", but the membership new start date will be "1st of December 2020", so the payment will be collected about 7 months ahead of the membership new term after renewal, which is too far and does not make sense to be the case.
After
The above is solved by adding new rule for "Fixed" memberships, so if we are creating a fixed membership with payment plan we do the following steps:
1- Use the membership end date + 1 day as a base to calculate the "next scheduled contribution date", so in the example above the it will be "1st of December 2020" (30th of November 2020 [the membership end date] + 1 day), so upon renewal the new contribution receive date will also be "1st of December 2020".
2- Adjust the date to match the cycle day in case of monthly payment plans. In our example it is an annual payment plan where cycle day does not apply, so the next scheduled contribution date will be "1st of December 2020".
The above steps are only applied when new payment plan is created for the first time, and does not apply to renewals, because on renewals the memberships will be renewed for full period and not for partial period as it might be when it gets created for the first time, also if this logic was force on renewals, it will cause issues when the user change the "next schedule contribution date manually" and will result in it getting ignored which is against of what we are doing with this field in other cases, so it was important to exclude the renewal logic .
The above also raises the following questions:
1- Does the issue happen when buying fixed memberships using monthly or quarterly payment plans?
It should not, given in such cases only the first installment "receive date" might be off, but the rest of the installments will have dates that are properly spread across the remaining period of the membership, so our original logic will still work correctly for them. But in this PR I did not add an exception for them and applied the logic here to them too to keep the code easier to understand, and because both the current and the new logic should end up in the same result for both of them.
2- What if someone set up a Priceset with one fixed membership and one rolling membership ?
We don't allow that anymore in Membershipextras, like you can still create such priceset, but in the membership form it will show the following error:
3- What if someone set up a Priceset with two fixed memberships but each one has different period start date and period rollover date ?
We also don't allow that anymore in Membershipextras, like you can still create such priceset, but in the membership form it will show the following error:
4- What about webforms?
Given the logic we have in this PR is also called by webforms to calculate the "next scheduled contribution date", then we have them covered.
Though the rules we specified in point 2 and 3 above are not enforced on webforms and only work on Admin forms, so it is the person who configure the webform responsibility to make sure that such kind of configurations does not happen. (though it is something we should improve at some point and enforce in code instead of relying on users to not do such kind of things)
Technical Details
I've updated the PaymentPlanNextContributionDate service which is the service that is responsible for calculating the "next contribution date" across the extension, so it gets the payment plan memberships and checks if there is a fixed membership, if so then it will add one day to it, then correct it to match the cycle day, and then use it to be the "next contribution date". I also added new parameter to it called "$operation" so it knows if the who request this service is form payment plan creation context or renewal context, so it know if the logic we added for fixed membership should apply or not.