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

Conversation

otetard
Copy link
Contributor

@otetard otetard commented Nov 18, 2017

Overview

Adds the ability to merge memberships without losing data.

With the current code, if you merge the two contact (without checking the “add new” checkbox), you will loose some information on the final membership: join_date, start_date and end_date will be overridden and the contributions from the final contact won’t be associated to the final membership.

Related issue: https://issues.civicrm.org/jira/browse/CRM-19151

Before

Currently CiviCRM can’t merge memberships and can result in data loss (contributions).

After

Really merge memberships by moving related contributions to the final membership and move memberships data to the new one (if relevant).

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, etc.

Technical Details

Add a new custom processing function mergeMemberships() in CRM_Member_BAO_Membership that allows to merge memberships.

Links

This patch solve CRM-19151. This issue was also discussed on StackExchange:

@civicrm-builder
Copy link

Can one of the admins verify this patch?

@otetard
Copy link
Contributor Author

otetard commented Nov 20, 2017

I’ve just updated the pull request:

  • don’t move contributions to the main membership if user didn’t request to move them;
  • bypass the whole custom processing function if user requested to “add” membership rather than merged it.

Olivier;

@agilewarealok
Copy link
Contributor

Hey @otetard ,

Can you give me any use case to test this ? Because when I tested this against 4.7.27 and on merging two contacts their contributions from old contact were transferred to new contact without apply this patch. Which case are we handling here ?

  • Alok

@otetard
Copy link
Contributor Author

otetard commented Dec 5, 2017

Hi @agilewarealok,

This patch tries to solve CRM-19151. This issue was also discussed on StackExchange:

With the current code, if you merge the two contact (without checking the “add new” checkbox), you will loose some information on the final membership: join_date, start_date and end_date will be overridden and the contributions from the final contact won’t be associated to the final membership.

Let’s consider two contacts:

  • “Contact A” has one membership which type is “General” (start date is 2005-01-01; end date is 2006-12-31; status is expired);
  • “Contact B” has one membership which type is “General” (start date is 2017-01-01 ; end date is 2018-12-31; status is current);

Each membership have an associated contribution (through MembershipPayment).

If you merge “Contact A” in “Contact B”, the final membership will be “Contact A”’s one (start date will be 2005-01-01; end date will be 2006-12-31; status will be expired). All membership information from Contact B will be lost. You will also loss the relation (MembershipPayment) between Contact B’s contribution and its deleted membership.

@agilewarealok
Copy link
Contributor

@otetard Thanks for the information and use case, We will try to review this today.

@totten
Copy link
Member

totten commented Dec 6, 2017

jenkins, ok to test

@otetard otetard force-pushed the feature/merge_memberships branch 4 times, most recently from bace79a to 755c1fd Compare December 6, 2017 08:29
@otetard
Copy link
Contributor Author

otetard commented Dec 6, 2017

I’ve just fixed the PHP coding style.

I’ll have a look at the failing test (testBatchMergeWithAssets).

Copy link
Contributor

@agilewarealok agilewarealok left a comment

Choose a reason for hiding this comment

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

@otetard We've tested the mentioned use case and it's working as expected.
Expired membership of Contact A is not merged into Contact B and the link between contribution and membership in Contact B is intact.

@otetard otetard force-pushed the feature/merge_memberships branch 5 times, most recently from 092f2ab to 17a6ec9 Compare January 31, 2018 17:12
@otetard otetard changed the title [WIP] CRM-19151: Add the ability to merge memberships without data loss CRM-19151: Add the ability to merge memberships without data loss Jan 31, 2018
@otetard
Copy link
Contributor Author

otetard commented Jan 31, 2018

I’ve just updated my patches. This PR was successfully checked by Jenkins and I’ve fixed some issues in edge cases.

This PR is now in a correct state so it can be merged.

Olivier;

@otetard
Copy link
Contributor Author

otetard commented Mar 15, 2018

Hi @agilewarealok,

I’ve just rebased this PR on current master. From my perspective, this PR is ready to be merged.

I would be glad if someone could take a look at it and merge it (or tell me what should I change).

Thanks!

Copy link
Contributor

@agilewarealok agilewarealok left a comment

Choose a reason for hiding this comment

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

Hi @otetard,

I've tested the PR and it seems it's not working for a case. I've tested it in following way,

  1. Create Contact A
  2. Add membership with Start Date 01/01/2005 and end date 31/12/2006
  3. Create Contact B
  4. Add membership with start date 01/01/2018 and end date 31/12/2019
  5. Merge Contact A into Contact B

Results:

  1. Membership and Contributions are deleted from Contact A.
  2. Membership in Contact B overrides with Member since 01/01/2005 Start date 01/01/2018 and end date as 31/12/2006 and status is expired.
  3. Contribution from previous membership is linked in above Membership.

I think end date should be 31/12/2019

Let me know if I'm missing something.

Thanks!

When calling for some custom processing function in the merge process,
add tables that needs to be merged and related options.
Add a new custom processing function `mergeMemberships()` in
`CRM_Member_BAO_Membership` that allows to merge memberships.

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`, etc.

Related issue: https://issues.civicrm.org/jira/browse/CRM-19151
@otetard
Copy link
Contributor Author

otetard commented Mar 20, 2018

Hi @agilewarealok,

Thanks for having tested this code. I’m sorry for not having tested enough my code…

I’ve updated this PR, everything should work fine:

  • date comparison was wrong for end_date (L2262);
  • new status was not correctly calculated (arguments to CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate were not correctly ordered).

Olivier;

Copy link
Contributor

@agilewarealok agilewarealok left a comment

Choose a reason for hiding this comment

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

Hi @otetard

Thanks for the update. I've tested the PR again and it seems to be working as expected. Performed following steps to test

  1. Create Contact A
  2. Add membership with Start Date 01/01/2005 and end date 31/12/2006
  3. Create Contact B
  4. Add membership with start date 01/01/2018 and end date 31/12/2019
  5. Merge Contact A into Contact B

Results:

  1. Membership and Contributions are deleted from Contact A.
  2. Membership in Contact B overrides with Member since 01/01/2005 Start date 01/01/2018 and end date as 31/12/2019 and status is Current.
  3. Contribution from previous membership is linked in above Membership.

Thanks!

@jusfreeman
Copy link
Contributor

@eileenmcnaughton would you mind checking this one? It would be a good fix to get into CiviCRM. We've done our own due diligence on it.

@jusfreeman
Copy link
Contributor

Thanks for your work on this one @otetard and @agilewarealok too :)

@eileenmcnaughton
Copy link
Contributor

eileenmcnaughton commented Mar 22, 2018

So the main concerns I have are

  1. we really need a unit test for this
  2. I haven't used memberships much for a while & don't want to make a call on whether merging the memberships like this is the right approach - I feel like we should get some more membership-oriented eyes on - perhaps @seamuslee001 (who logged it) @petednz @Stoob @lcdservices @agh1 @davejenx @JKingsnorth @deepak-srivastava

@jusfreeman
Copy link
Contributor

Sure, just a heads up as described in the overview. The current implementation in CiviCRM for membership merging is broken. This is a fix, not a new feature.

@eileenmcnaughton
Copy link
Contributor

@jusfreeman to clarify - does this mean the memberships are moved across unchanged (which would be a fix I would feel I could merge without broader input) or that it merges them by joining 2 memberships together?

@lcdservices
Copy link
Contributor

I tested and this works as intended. I agree that the existing behavior was quite broken -- I generally set the "add new" to default enabled for clients and advise they never trust the membership merge for the reasons previously specified.

@eileenmcnaughton I think this should be merged. There could be future discussion about enhancements/options to the membership merge behavior, but for now, this does fix/improve broken functionality.

@eileenmcnaughton
Copy link
Contributor

Ok - I'm happy to merge based on @lcdservices @agilewarealok & @jusfreeman - I don't really have my head in the space on this PR - but I feel that is enough endorsement. I skimmed the code & it looks generally good.

WARNING - this could regress at any time unless someone adds a test! (api_v3_JobTest is where all the merge tests are)

@eileenmcnaughton eileenmcnaughton merged commit 22b09a4 into civicrm:master Mar 22, 2018
@eileenmcnaughton
Copy link
Contributor

Also - are any of you able to confirm the status of this one after the merge ...

https://issues.civicrm.org/jira/browse/CRM-21151?filter=24614

(and how good is it that it perfectly demonstrates my point about the dangers of fixing something without adding a test - how did I lower my guard!?!)

@agh1
Copy link
Contributor

agh1 commented Mar 22, 2018

Sorry I didn't have a chance to test this until just now @eileenmcnaughton but there are big problems with merging memberships that have custom fields and/or inherited memberships. It also seems to have trouble with identical memberships.

I'm not sure if this PR is the source of the problem. I'm just concerned that this addresses the most glaring problems with merging memberships without solving more fundamental ones. With this change, you now get silent data loss rather than more obvious problems.

Personally, I don't think membership merging is safe to use in production. I don't know whether it's good to merge this PR or not, but I don't think it's responsible to allow merging of memberships. At the very least, I'd like to see "Add New" as the default and a big warning about losing the "other" contact's membership data if you don't "Add New".

Here are the details of what I tried out.

For these tests, I created an integer custom field called "membership sticker number" and for the inherited memberships, I created a one-year rolling membership type called "corporate" that inherits along "employer of" relationships. Otherwise, this is from a totally clean dmaster built with master as of an hour ago. (And I confirmed that the code matched the changes in this PR.)

Test 1: identical contacts with identical memberships

  • Created contact ID 204, added general membership ID 31, which had membership sticker number 1.
  • Created contact ID 205, added general membership ID 32, which had membership sticker number 2.
  • Merged the contacts (cid=204&oid=205)
  • The combined contact ID 204 had general membership ID 31, which had membership sticker number 1.
  • The membership dates were identical to original membership (consistent with the theory that these are duplicate entries of a single membership, contrary to the alternative scenario where someone accidentally paid dues twice).
  • Membership 31 had both contributions
  • Contact ID 205 was in the trash, and it had general membership ID 32 with membership sticker number 2

This outcome was a bit of a surprise, but it doesn't result in true data loss.

Test 2: different membership dates

  • Created contact ID 204, added general membership ID 33, which had membership sticker number 4 and join/end dates 3/22/17 - 3/21/19
  • Created contact ID 207, added general membership ID 34, which had membership sticker number 5 and join/end dates 3/22/18 - 3/21/20
  • Merged the contacts (cid=206&oid=207)
  • The combined contact ID 206 had general membership ID 33, which had membership sticker number 4 and join/end dates 3/22/17 - 3/21/20.
  • Membership 33 had both contributions on it.
  • Contact ID 207 was in the trash, with no memberships.
  • No record of membership ID 34 is in the database.
  • No record with membership sticker number 5 is in the database

This is a serious problem. With no warning and potentially little opportunity to notice after the fact, I've deleted membership data while thinking I'm merging it into the combined output.

The problem here of course is that there's no UI for selecting which of two conflicting values should be kept, but I feel that this outcome is unacceptable.

Test 3: inherited members

  • Created contact ID 208 (org) that employs contact ID 209 (ind)
  • Added corporate membership ID 37 for contact ID 208, having membership sticker number 7 and join/end dates 3/25/17 - 3/24/18
  • Corporate membership ID 38 is inherited by contact ID 209, having membership sticker number 7 and join/end dates 3/25/17 - 3/24/18
  • Created contact ID 210 (org) that employs contact ID 211 (ind)
  • Added corporate membership ID 39 for contact ID 210, having membership sticker number 8 and join/end dates 3/22/18 - 3/21/19
  • Merged the two organization contacts (cid=208&oid=210)
  • The combined contact ID 208 had corporate membership ID 37, which had sticker number 7 and join/end dates 3/25/17 - 3/21/19.
  • Contact ID 209 (the original employee of 208) had no membership at all
  • Contact ID 211 had two corporate memberships:
    • ID 38 (inherited from ID 37) with sticker number 7 and join/end dates of 3/25/17 - 3/21/19
    • ID 40 (not inherited) with sticker number 8 and join/end dates 3/22/18 - 3/21/19
  • No record of membership ID 39 in the database
  • Only instance of membership sticker number 8 is on the standalone, duplicative membership ID 40.

This is also a problem: inherited memberships from the merged-onto contact have evaporated, contacts inheriting memberships from the to-be-deleted contact now have extra, uninherited memberships, and some custom data is only available on those uninherited memberships.

I had originally planned on doing another test with inherited memberships where the membership type has a limit on the number of inherited memberships, but things are so messed up that I didn't think that would be worth trying until even the basics of inherited memberships are covered.

@eileenmcnaughton
Copy link
Contributor

Wow thanks for that great testing @agh1 - keen to hear other's thoughts

@jusfreeman
Copy link
Contributor

@agh1 the PR scope and code does not include any handling of merging memberships that have custom fields and/or inherited memberships. It looks like all your test cases 1 - 3 are directly related to those points.

This PR fixes a known problem as documented . Incremental fixes are good.

Fixing merging of custom fields and inherited memberships should be separate PR(s) and build upon this code further.

@agh1
Copy link
Contributor

agh1 commented Mar 23, 2018

I don't think we can treat custom fields as being some kind of edge case. Incidentally, as best I can tell, fields on the membership itself like Source are also overwritten without warning when the membership is "merged".

To be honest, I had never done a systematic test of merging memberships before yesterday; the main use case for me and our clients has always been merging one contact with a membership with another contact that has none. I've heard sporadic reports of trouble, but people have always resolved it manually case-by-case.

I just know that the premise of this PR is to "merge memberships without data loss", and there definitely is data loss. This PR isn't causing the data loss, but I'm concerned that it does two things:

  1. It obscures the data loss by actually merging some things.
  2. It doubles down on merging memberships from the contact merge screen, which I think is an ill-advised feature.

For an uninitiated user, merging contacts and having both memberships on the new contact side-by-side (the "add new" outcome) may be an irritating quirk, but merging contacts and arbitrarily keeping only one membership (albeit with the combined dates and payments) is what I'd consider a nasty surprise.

I imagine there could be a variety of solutions to this, but the status quo, with and without this PR, is a problem waiting to happen.

@JKingsnorth
Copy link
Contributor

For an uninitiated user, merging contacts and having both memberships on the new contact side-by-side (the "add new" outcome) may be an irritating quirk, but merging contacts and arbitrarily keeping only one membership (albeit with the combined dates and payments) is what I'd consider a nasty surprise.

I agree with this.

The merging tool is complex, but should primarily be merging contacts, not trying to merge their associated information. Otherwise, what about all the other related entities (event registrations for the same event, etc?)

I agree it's not ideal, but I have reservations about attempting to automagically merge memberships during the merge process. Admittedly we do not use the membership element, so I do not have much background on the issues here.

@mattwire
Copy link
Contributor

mattwire commented Mar 23, 2018 via email

@agh1
Copy link
Contributor

agh1 commented Mar 23, 2018

I got thinking about typical use cases, and I did realize that there is one somewhat common use case among our clients for merging memberships: a contact with a lapsed membership from years ago merging with a contact who has a newer membership.

I decided to test that out as well as the scenario where a contact has a status override set.

Unfortunately, it appears that the date and status calculations that are introduced by this PR have some significant problems. Fundamentally, I think it stems from blindly accepting accepting values from the surviving membership and discarding values from the merged-in membership.

Test 4: Lapsed member

  • Created contact ID 212, added general membership ID 41, which had join/end dates 3/23/15 - 3/22/17
  • Created contact ID 213, added general membership ID 42, which had join/end dates 3/23/18 - 3/22/20
  • Merged the contacts (cid=212&oid=213)
  • The combined contact ID 212 had general membership ID 41, which had a join date of 3/23/15, a start date of 3/23/15, and an end date of 3/22/20

The start date should be the start of the current continuous membership period, so it should be 3/23/18 rather than the join date.

I tried this same situation again but merging the opposite direction, and the start date for the "merged" membership is 3/23/18. It appears that this PR just takes the value from the surviving membership record.

Test 5: Status override

  • Created contact ID 214, added general membership ID 43, which had join/end dates 3/23/15 - 3/22/17, permanent status override, and status of Current
  • Created contact ID 216, added general membership ID 45, which had join/end dates 3/23/12 - 3/23/14
  • Merged the contacts (cid=216&oid=214)
  • The combined contact 216 had general membership ID 45, which had a join date of 3/23/12, a start date of 3/23/12, an end date of 3/22/17, no status override, and a status of Expired.

There is no record of there ever being a status override.

I tried this the other way around, merging onto the contact with the status override, and the status override field is set to Override Permanently but the status is set to Expired.

Unlike with start date, which appears to be taken as-is from the surviving membership, the status seems to always get wiped out. This makes for a dangerous combination with the status itself being changed, since in this case, the member would never be able to renew themselves into good standing without an admin noticing that they have status override set.

Test 6: Member in good standing, back from the dead

  • Created contact ID 219, added general membership ID 48, which had join/end dates 3/23/15 - 3/22/17
  • Created contact ID 220 with deceased date 3/1/18, added general membership ID 49, which had join/end dates 3/23/17 - 3/22/19, permanent status override, and a status of Deceased
  • Merged the contacts (cid=219&oid=220)
  • The combined contact 219 had general membership ID 48, which had join/end dates 3/23/15 - 3/22/19, no status override, and a status of Current

It appears that even though the status override from membership ID 49 was ignored, the status was calculated based upon the end date brought in from that membership.

@Stoob
Copy link
Contributor

Stoob commented Mar 23, 2018 via email

@jusfreeman
Copy link
Contributor

@agh1 this is a great discussion, which highlights the need for further work in this area. Should we move it to Gitlab? Since this PR has been merged and it would be a waste for these thoughts to be lost in the void.

@eileenmcnaughton
Copy link
Contributor

@agh1 @Stoob @JKingsnorth is anyone proposing this should be reverted? Is anyone of the opinion that this PR makes things WORSE

@agh1
Copy link
Contributor

agh1 commented Mar 23, 2018

@eileenmcnaughton I'm on the fence. The PR introduces the status override problems in tests 5 and 6 above, but those pale in comparison to the amount of mayhem involved in merging memberships that is neither introduced nor resolved by this PR.

I think what I really feel is that this PR should become moot because it affects a feature that shouldn't exist in anything like its current form.

I think the one thing I'm sure of is that CRM-19151 should be reopened, which I just did, because it's clear that what it calls for is not complete.

@agh1
Copy link
Contributor

agh1 commented Mar 23, 2018

I just noticed that one of the items @seamuslee001 mentions in CRM-19151 is

We would also need the ability to scan and update the relevant custom data for membership

As @Stoob said, membership custom data is often really important. Among the use cases for us are membership card numbers, the day when a CSA subscriber picks up food, and whether a member receives the journal in print or electronically.

@eileenmcnaughton
Copy link
Contributor

Note discussion here https://lab.civicrm.org/dev/core/issues/631 about removing merge membership functionality from core

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.