Improving Civi's Membership History

Gepubliceerd
2014-02-03 14:46
Written by
roberttdev - member of the CiviCRM community - view blog guidelines

Our organization has been using (and loving) CiviCRM for almost a year now, but we initially had issues getting the membership history to easily capture the full range of detail needed.  Our members often lapse from year to year, and because Civi’s natural process is to renew a single membership record instead of creating multiple, this caused us some problems.  When discussing our experience and solution at various events, we found there was a lot of interest, so the purpose of this blog post is to discuss them in further detail with those who might find it useful to use our solution for their needs, and facilitate conversation towards sharing the solution with them.

 

Example:  An Often Lapsed Member




Our organization offers yearly memberships, and a common sight in our records would be a member like “Jane Member”.  Jane bought a 2007 Student membership, as well as one in 2009 and 2010.  She also bought a Professional membership in 2012 and 2014.  Civi’s base solution would capture this in a single membership record like so:

Member Since: 1/1/2007
Membership Type: Professional
Start Date: 1/1/2014
End Date: 12/31/2014
(5 associated member dues Contributions)

This angle had a number of limitations:

- Membership lapses were difficult to track and report on.  One could theoretically use Activity entries to recreate these lapses, but the process was programatically complicated, especially in edge cases such as membership cancellations.  This made reports involving yearly fluctuations in membership more difficult for staff than it needed to be.
- Membership type changes over the years were also difficult to track.
- Contributions did not have hard references to the memberships they were paying for.  Again, a human could eventually manually figure it out, but it required an effort to reconcile multiple screens in the application.  Programatically it was made difficult by the fact that one can pay for memberships in advance, so there was not necessarily a correlation between payment date and membership date.
- One could not track custom data per membership period.  One overall membership record meant one instance of the custom fields, instead of one set per membership purchased.  Ultimately, this meant that membership custom data was basically contact custom data, as they were both one-to-one, and the only outlet for custom data that was one-per-membership was by including it in the contribution.  And we did not always have a contribution, as sometimes memberships were complimentary.
 


Our Solution: One Membership Record Per Membership

To get back to “Jane Member”, here’s how our solution would look – 5 memberships:


1. Start Date: 1/1/2007, End Date: 12/31/2007 (1 associated member dues Contribution)
2. Start Date: 1/1/2009, End Date: 12/31/2009 (1 associated member dues Contribution)
3. Start Date: 1/1/2010, End Date: 12/31/2010 (1 associated member dues Contribution)
4. Start Date: 1/1/2012, End Date: 12/31/2012 (1 associated member dues Contribution)
5. Start Date: 1/1/2014, End Date: 12/31/2014 (1 associated member dues Contribution)

You may notice that there’s no “Member Since” tracked here.. since that data technically only exists once for a Contact, we simply moved it into a custom field there.  The existing “Member Since” field is disregarded.

This addresses our four problems:
- Membership lapses are tracked in one place, making reporting by staff easier.  Now reports such as “Show me who has been a member at least 3 of the last 5 years”, or past timeframe searches such as “Show me who was a member in 2011 but not 2012” much easier.
- Membership type changes are easily tracked by year, helping searches such as “Show me who was Member Type A in 2012, but changed to Member Type B in 2013”.
- Each contribution is tied directly to its membership, removing the guessing game if contributions are bought in timeframes other than those they are valid.
- Each record gets its own instance of custom data per year, instead of having to share a single membership record.

 

What Changes Were Needed?

Luckily, the existing database structure was accommodating, but certain rules needed adjusting.

- Online registration needed to be adjusted to create a new record instead of renewing an existing one
- The concept of an “Upcoming” membership that was separate from the this year’s membership had to be added to the Member Status logic, and addressed in the membership status update job
- Certain queries needed optimization to return far fewer results based on the assumptions of the new structure


Potential Limitations

Our business model only requires one membership at a time.  We do not depend on previous or existing memberships to process any logic (such as special pricing on renewals).  Logic such as this may require additional functionality.


How Can I Use This For My Site?

We’re certainly interested in sharing our results with the community, and this blog is a means to figure out the best way to do so.  We’d like to both gauge interest, and receive contact from people who are interested in using this solution.  Currently, the code is composed of individual file changes, and is not slated to go into a core release.  Our initial thought is to package it into an extension, if people are interested in facilitating it.  Please feel free to comment, or contact me at rwilliams@aarweb.org.

 

Filed under

Comments

robert. thanks for posting and offering to find a way to share this - i agree that i think this different model of memberships would fit some organisations better than the core approach.

hopefully we can help get this out to the community either by testing the files as they stand - or by testing the extension when it comes along.

Thanks!  We would love to make this into an extension, if people are willing to chip in and help us figure out the best way to deliver it to meet their needs.

When trying to change rather than merely extend core behavior the challenges mount in trying to keep all the moving parts playing nicely with each other. Your extension will need to take account of the fact that core upgrades assume a certain meaning and logic to the data in different records as they occasionally transform data during upgrades to handle new features or resolve outstanding issues. The responsibility of keeping the sites working with up to date core upgrades that deal with security issues would fall to the extension creator and maintainer. So you'll need to accept some significant responsibility for supporting users of extension going forward.

To provide one example, Eileen and I are exploring with the core team the possibility of replacing the membership payment and participant payment tables with a direct reference from line items to the contribution (this avoids an awkward and potentially slow reference through half a dozen tables). This particular change might alleviate a bit of the trouble you document.

I previously contributed code to create activity records when status and membership type changes occurred. The intent was to track changes so that a report could be created that would show a membership history properly in terms of when it lapsed and then was re-initiated. True, we didn't put the work in to write the report, but it shouldn't be too hard. Perhaps there are some state transitions that don't produce needed activities for your work flow, but I'm not sure what they would be.

You might want to check if there is a benefit to having a membership Id that endures through the years - many organizations find it useful.

One way to create a nice administration U I for you extension would be to provide an option on a Administration screen for Civil ember to switch to your alternative membership model. Give it a name, maybe Strictly Annual, or Disconnected, or Fully Expiring or something. Then on the enable and disable hook you could Co vert the membership to and from the two paradigms so that people could get in and get out of using the proposed alternative approach. Might save people including you some possible grief down the road, though it would be a bit of work up front.

Thanks for the heads-up.  Any extension we create will in turn be used by us in place of our current workaround, so I'm hoping that naturally keeps things fairly up-to-date.

Converting back-and-forth would be new ground for us, as we had to create this structure and import into it from the beginning in order to avoid losing data (like membership status changes over the years, or year-to-year membership custom data, or which contributions applied to which memberships).  We didn't have the in-between step of getting the data in Core format (such as creating Activity records for legacy data, to represent membership lapses).  This may not be a problem for people who have already made do with Core data, however.. we'll have to hear some use cases from people interested in using the code.
 

I have clients that have similar needs, though I think a modified version of your method might be more attractive for a core contribution.

I think the idea of membership continuity is really valuable and important. If a member joins in 2007 and renews annually through 2014, it is useful to see that as a single continuous membership. But as you point out, it's equally important to be able to meaningfully capture a "break" in that membership, and intentionally discontinue it from the past membership.

Rather than create completely distinct period-based records as you have done, I'd rather see a configuration option that defines the length of time for which a break becomes valid. For example, let's say the break window is 6 mos. for a rolling membership type with a 1 year period. If the person renews 4 mos after their last end date, we extend the existing membership (i.e. renew it) by a year. Even though they are late paying, we connect it to the existing membership. That also means their membership will come due again in 8 months -- since we are in essence back-dating their renewal.

However, if that same person did not "renew" until 7 months after their last end date, then we *don't* treat it as a renewal, but rather start a new membership record for them. That provides the necessary historical break so we see the period during which they lapsed.

As for membership type changes -- there was work done about 4 or 5 versions ago to change the behavior so that a membership with a certain type could be changed into a different type -- basically supporting the idea of "membership upselling". But I agree -- that behavior is not always desireable. It would be great if there was a configuration option to support two different workflows -- change the type for the existing membership, or treat a change in membership type as ending the existing membership and starting a new one (perhaps retaining the same member since date in order to achieve some level of continuity).

What you describe could work with our logic, although it would require some tweaks to the membership creation hook to implement your rolling membership and grace period rules instead of our calendar year rules.  It's not a heavy change, however.

Regarding renewals.. what is the business rule that you have that creates the need for a renewal to share a membership record, instead of having two consecutive records (i.e. 6/4/2012-6/3/2013 and 6/4/2013-6/3-2014)?  I also saw this mentioned in the comment above, and am curious to learn the needs for this particular structure.

As for the "Member Since" date, we don't use the core field, and have just moved it to a custom Contact field.  It seemed to make more sense there, as conceptually there is only one Join Date per contact.. and having it repeated in multiple membership records just invited data issues when someone accidentally typed it wrong.

One reason for storing it at the Membership level rather than with the Contact is that many CiviCRM sites support a hierarchy of memberships. For example, a person may have both a local/regional membership and a national membership - and the Member Since date can be different for those two memberships.

Some organizations also offer paid subscriptions to some products, which need to be managed separately from the membership(s) in the organization and its 'chapters' or related entities being managed in the same CiviCRM instance. Also, in many cases there will be contacts in a database that are not members, and it might be slightly confusing to see a blank member since field on their contact records. So treating contacts and memberships as a one-to-one relationship doesn't work in all cases.

With regard to the period of interruption needed before another membership is created, I think that it should be based on the grace period. After the grace period, renewals are not allowed, and a new membership must be purchased.

Just FYI, there was some back and forth on the specifications and implementation of whether one should create a separate membership or not when a contact without a current membership was signing up for one. I think that it would make sense to implement this functionality as an enhancement to core rather than an extension in order to reduce the issues with upgrading of data.

In your scenario, I think we would have had to set up Member Since to be a custom field for a subtype of Contact that a Member would be, as opposed to a subtype of Contact that a non-member would be.  But then an organization that has non-members that can later become members would have their own struggles.. it just speaks to the complexity of so many organizations with their own special business rules :)

If a change to core is deemed reasonable, we'd be happy to share our experiences and code.. having it in core would certainly help with our upgrades, although currently we try as hard as possible to make the code changes in hook areas that would be outside the scope of upgrades anyway.

Seems to me that by simply limiting the ability for a member to even use the renewal link after the "grace period" has ended would fix this issue altogether and require them to create a new membership.  Maybe I missed something about the way core works but I thought it did this already and simply disabling and forcing a new membership.  Maybe I'm wrong and by forcing a new membership core still treats it as a renewal.  I know it would make the report writing a bit complex but using membership status and date range I think it could be done.  Please excuse my ignorance if I missed a big picture item here.

Unfortunately, in our experience, trying to create a new membership when one is expired tends to push you towards renewing the existing membership (we don't have grace periods).  It also limits us from allowing members to pay for future memberships while still current, as well as the 4 or so issues mentioned in the article.

Anonymous (niet gecontroleerd)
2014-02-18 - 23:38

2 pretty big other issues are:

custom data per membership period (think volunteer clothing each year - sizes and if allocated etc)

accurate and easy to access membership details over time - eg How many members did we have 7 years ago.. how many people have been members for the past 3 years etc

seems to me, funny enough, that this should be the default.......

Anonymous (niet gecontroleerd)
2014-09-05 - 12:48

Did any of this make it into 4.5? I'm very sympathetic to what you're doing here. We have similar headaches matching contributions to memberships. In addtion, there appears to be a lack of any documentation for things like Under-payments: what status should the under-payer have until he or she has paid up fully?; Over-payments: can they be marked as a subscription + gift or a multi-year subscription; Renewals: difficulties with the Import functions.

Is there an overview doc of the changes to CiviMember in the upcoming 4.5? I can see cryptic lists of individual issue and documentation changes in JIRA but would like to know what to expect after 4.4.

David