Created on 23 October 2024, 7 months ago

Problem/Motivation

It is important to be able to track which contacts belong to which Organization.

Steps to reproduce

Proposed resolution

Copy the features of CiviCRM Member

Remaining tasks

User interface changes

API changes

Data model changes

✨ Feature request
Status

Active

Version

1.0

Component

Code

Created by

πŸ‡ΊπŸ‡ΈUnited States bluegeek9

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @bluegeek9
  • πŸ‡ΊπŸ‡ΈUnited States bluegeek9
  • πŸ‡ΊπŸ‡ΈUnited States jdleonard Austin, TX, USA

    I need to analyze this more fully, but some very quick questions/thoughts...

    1. CRM Membership -- Status -- Things to consider capturing: "Future", "Active", "Expired", "Suspended", "Cancelled/Voided/Revoked", "Grace period" (though the first three can be computed from the start/end date so perhaps the last three are the interesting ones)
    2. CRM Membership -- Visibility -- Consider a flag determining whether a membership is public (e.g. "show this membership on our list of members"). This could instead be a Contact-level preference, but I could foresee use cases where a member wants one membership public (e.g. "general member") and another non-public (e.g. "ultra rich benefactor"). I guess there's a higher-level question of how/where to track privacy/visibility of membership.
    3. CRM Membership Type -- Duration -- consider supporting all RFC 3339 durations ( https://www.drupal.org/project/duration_field β†’ uses its precursor). I've seen 90 minute memberships e.g. at a trampoline park. Maybe best to not unnecessarily restrict flexibility at the data model level (I could certainly see not exposing every option by default at the UI level).
    4. CRM Membership Type -- Fixed Period rollover -- how does this work?
    5. CRM Membership Log -- what is this for?
    6. How to handle a multi-person membership (that is not an entire household)? E.g. my local YMCA offers a membership including "two adults of the same household" (they also offer a true household membership). Or https://www.thedoseum.org/member offers "Family of 2", "Family of 3" etc. There seems to be a more general need for tracking the Contacts that a Household or Organization's Membership covers. Perhaps one Membership could be dependent on another e.g. YMCA example could be represented as a Household "Two-Person Membership" + two Memberships for the specific people in the Household? This approach would suggest (at least) a reference between (some) Memberships. Perhaps also specifying a Sub-Contact Type for a Membership Type.
    7. Building on the previous bullet, how to handle a membership model wherein an overall membership (e.g. "3-Year Family of 5") covers a different set of people over the course of the membership and not necessarily the maximum number of people. This is similar to seat licensing.
  • πŸ‡ΊπŸ‡ΈUnited States bluegeek9
    1. Workflow: CiviCRM has a similair status. I think this will be needed eventually. I imagine it would depend on the workflow module.
    2. Do we want to have promote and sticky? Have them would like Nodes?
    3. Duration field: I looked at the Duration field. I do not know if it will work. It does not support Drupal 11. Memberships of less than a day will be harder to implement.
    4. Fixed or Rollover: Let us look at a monthly membership. Someone joins on the 15th of the month. With Fixed, the membership expires at the end of the month. With rollover, the membership expires on the 15th of next month.
    5. CRM Membership Log: The CRM Membership entity corresponds to a Contact and a membership type. Each time a membership is created or renewed a Membership Log is created. Handling payments is out of scope now. It is like a payment record. E.g. Contact A renewed monthly membership for 6 months.
    6. Multi-Person Membership: Not sure. Each Membership type defines which contact type can 'join'. This would allow households and organizations to join. How does that filter down, to the individual; I have no idea yet. My first thought was an unlimited entity reference field, with an item for each individual. That would work for households, but organizations will have issues; entity references do not scale. If memberships can have 'parent' memberships, that could also work.
    7. Note sure. If we used entity references, each membership type could have a different limit.
  • πŸ‡ΊπŸ‡ΈUnited States jdleonard Austin, TX, USA

    Have you evaluated https://www.drupal.org/project/membership β†’ at all? Its project page description made it seem overly focused/limited, but perhaps there's an opportunity to contribute to / leverage its Membership entity.

  • πŸ‡ΊπŸ‡ΈUnited States freelock Seattle

    I've built 3 sites using Membership module -- unfortunately none are active anymore. I did a fair amount of refactoring to make that more clear -- the current maintainer had different goals, but the overall structure can be part of the solution.

    The most recent site was for a company that was selling subscription access to its services, and its customers were businesses that typically bought 5 or more seats -- they had a couple different pricing tiers based on number of logins. It was essentially a domain-specific SaaS. I ended up using both Membership and Group to make this work. And it did work, with some glue code (this was before I was a big user of ECA).

    Membership provides two entity types:

    - "Membership" which was a permanent entity for a particular account -- might be pending, active, expired, or in some cooldown period -- you can set different state machines to allow grace periods for renewal. So this contains the current status, along with any number of members. You can have different bundles here for different levels of access.

    - "Membership Term" - this uses a special plugin so it can connect directly to external membership providers, at first. I created a general "membership_term" plugin so you could manage memberships entirely within Drupal. This entity represents the current active period, with the plugin defining details around how to handle fixed vs floating membership periods, handling pro-rating amounts for partial periods, triggering automatic renewals if connected to Commerce, and renewal notices at various points (I made a "Scheduled Message" module just for this reason, but none of our active sites still use it, we've replaced that with ECA). You make different bundles of this for different membership lengths.

    I had also created a Commerce Membership β†’ module, and we did have it working, but the client pulled the plug on the whole project before we had that wrapped up -- I can find and post that code.

    On this project, we used Group to enforce access. The custom code was simply keeping the various modules in sync -- sending people to buy more seats if they tried to add more users than they had in their agreement, pro-rating additional memberships so we could use the same membership term with a new quantity, making it easy to invite new users to your membership, etc. All the app's functionality was inside the group, so without group membership, authenticated users couldn't do anything other than purchase a membership and browse some help pages.

    I did find myself refactoring a lot of the membership term code, and sent a bunch of patches to the maintainer, most but not all are in the Membership module.

  • πŸ‡¬πŸ‡§United Kingdom robcarr Perthshire, Scotland

    The approach with Commerce License β†’ and Commerce Recurring β†’ is to create a [Drupal] User Role specific for a membership type (eg, Full member, Associate, Supporter). These roles (think of them as membership plan) can be purchased for a fixed or indefinite period using Commerce License. If such membership is to be renewed on a recurring (eg, annual) basis then Commerce Recurring can be used for repeated billing.

    So the architecture is:

    User - Subscription - Plan

    which seems to be comparable (but more abstracted) to the CiviCRM approach of 3 core entities

    Subscription:

    • Member (User reference)
    • Plan (reference)
    • Expiry date (date)

    Plan:

    • User role (reference)
    • Description
    • Fee
    • Duration

    I've not tried this approach for Membership plans that have no cost - Commerce may well take users through a checkout process even though there's nothing to pay. There's probably a way to tweak the checkout UX so this use case wouldn't look too weird though.

  • πŸ‡ΊπŸ‡ΈUnited States bluegeek9
  • πŸ‡ΊπŸ‡ΈUnited States freelock Seattle

    I have used Commerce Recurring for automatic renewals. I forget exactly why we didn't use Commerce License -- we had a reason at the time, but maybe whatever it was is no longer an issue. Could Commerce License be used to purchase a group membership, instead of a site role?

  • πŸ‡ΊπŸ‡ΈUnited States freelock Seattle

    As for no-cost memberships, easiest was to set the membership term to "lifetime". I don't think that is correctly built in the membership module -- I think we set a date 20 years in the future as a short-term placeholder behavior -- I haven't had a customer yet that wanted a membership that didn't expire. (We did hit the Unix Y2K/2038 bug doing this!)

  • πŸ‡ΊπŸ‡ΈUnited States jdleonard Austin, TX, USA

    I think there's significant value to keeping the representation of a "membership" very lightweight so that an ecosystem can form around it to serve many use cases, with various modules / recipes leveraging the key concept of membership (whether for an individual, household, or organization, or multiples of those types).

    It might indeed make sense to use Group for some Member Platform functionality, but I don't think we should necessarily require it simply to track memberships.

    Commerce License seems incompatible with the approach @bluegeek9 is taking here in CRM, which doesn't require a Contact/member to be a User.

    Contacts β†’ does require the use of Users (but removes the need for a User to be able to log in / have credentials) and thus would be more compatible with Commerce License. However, overloading the concept of a User to cover people who don't log in seems unwise and a Drupal anti-pattern.

    Looking more at the Membership module's model, I see that its Membership entity has a User as its subject/owner.

    My bias for Member Platform (and for a CRM in general) is toward the approach proposed here for CRM of tracking Contacts (and their Memberships) separate from Users and separate from Commerce. I would expect some module to offer the ability to link a User to a Contact and grant roles or Group membership or whatever. I would expect another module to offer integration with the Commerce ecosystem or whatever other payment system.

    I think there is lots to learn from the Membership module, but I'm not confident it's worth attempting to actually use the module. Anyone else have thoughts on this?

    @freelock Would be great to see the Commerce Membership code!

  • πŸ‡ΊπŸ‡ΈUnited States freelock Seattle

    Membership entities do require an owner, but we have used it on sites where the owner is the only user, and additional members I think used Profile β†’ entities -- at some point on a D7 site we made some custom code to allow an existing profile to be associated with a new user... we've managed membership sites for families with children that do not have email addresses or logins but still needed to be distinct (a sports academy that had open field time as well as lessons).

    If Commerce License requires a user, that would be why we didn't use it...

    Architecture-wise, I fully agree -- only one of our sites used Group with memberships, and having different entities for Users, Profiles (or Contact), Memberships, Membership Terms, Payment subscriptions makes a lot of sense, especially with ECA now available to build actions to sync data to whatever entities your site needs.

  • πŸ‡ΊπŸ‡ΈUnited States freelock Seattle

    Oh wow, looks like the code I wrote for commerce_membership (name on Drupal.org) is implemented in the site as "membership_commerce". It's not very big -- ~260 line .module file with several hooks, a service with some convenience functions, and two PriceResolver services.

    It also looks like these aren't written in a portable way -- field names are hard-coded instead of looked up from config. Went ahead and pushed what I have up to Commerce Membership β†’ .

Production build 0.71.5 2024