CiviCRM Community Forums (archive)

*

News:

Have a question about CiviCRM?
Get it answered quickly at the new
CiviCRM Stack Exchange Q+A site

This forum was archived on 25 November 2017. Learn more.
How to get involved.
What to do if you think you've found a bug.



  • CiviCRM Community Forums (archive) »
  • Old sections (read-only, deprecated) »
  • Developer Discussion (Moderator: Donald Lobo) »
  • rollover dates in CiviMember
Pages: [1]

Author Topic: rollover dates in CiviMember  (Read 894 times)

ccowens

  • Guest
rollover dates in CiviMember
April 09, 2010, 08:54:19 am
There may be a bug in the way CiviMember processes rollover dates for fixed period memberships.

Reproduce it by setting up a fixed membership type with a one year period, a start date of June 1, and a rollover date of August 31.

Now enter a new membership with a join date of (say) February 5, 2010.  Because February 5, 2010 is after the rollover date of August 31, he should get until May 31, 2011, but instead, his membership ends on May 31, 2010.

I took a look at CRM/Member/BAO/MembershipType.php. There appear to be a couple of conceptual errors in how the dates are calculated.

In sympathy with the developer, the use case is really ugly.  What does it mean, for example, if someone enters a membership type with a fixed 47 day duration and a start date of May 17, and then tries to join someone in September 2010. 
  • Is the membership supposed to cover the 47 days that started May 17 2010, that is already expired by the time the member joined?  Doubtful
  • Or maybe the the 47 days beginning May 17, 2011...
  • Or maybe we're supposed to take the "47 day" period and start rolling it, so that there are membership periods starting May 17, July 3, Aug 19, Oct 5, etc.

The original developer was right to punt on the "rollover date" concept for membership periods other than 1 year until the use case is fleshed out a little better.

Focusing only on the "1 year" case, I basically rewrote the function getDatesForMembershipType to get my application working, but by no means have I put it through any kind of QA.  I'll include the code in a follow-up post if anyone wants to have a look at it.


ccowens

  • Guest
Re: rollover dates in CiviMember
April 09, 2010, 08:57:09 am
Here is the code I promised
Code: [Select]
/**                                                                                                                                                                 
   * Function to calculate start date and end date for new membership                                                                                                 
   *                                                                                                                                                                   
   * @param int  $membershipTypeId membership type id                                                                                                                 
   * @param date $joinDate join date ( in mysql date format )                                                                                                         
   * @param date $startDate start date ( in mysql date format )                                                                                                       
   * @param date $endDate start date ( in mysql date format )                                                                                                         
   *                                                                                                                                                                   
   * @return array associated array with  start date, end date and join date for the membership                                                                       
   * @static                                                                                                                                                           
   */
  function getDatesForMembershipType( $membershipTypeId, $joinDate = null, $startDate = null, $endDate = null )
  {
    $membershipTypeDetails = self::getMembershipTypeDetails( $membershipTypeId );

    // keep dates in PHP internal format to make calculation easy.                                                                                                     
    if ( $joinDate )
      $join_i = strtotime($joinDate);
    else
      $join_i = strtotime("now"); // Default to *now*                                                                                                                 

    // The interval string, e.g. '4 year' is going to be useful                                                                                                       
    if ( $membershipTypeDetails['duration_unit'] == 'lifetime' )
      $interval = null;
    else
      $interval = $membershipTypeDetails['duration_interval'] . ' ' . $membershipTypeDetails['duration_unit'];


    // Handle rolling and fixed membership periods differently                                                                                                         
    switch ( $membershipTypeDetails['period_type'] ) {
    case 'rolling' :
      {
        if ( $startDate )
          $start_i = strtotime($startDate);
        else
          $start_i = $join_i; // Start date defaults to join date                                                                                                     
        if ( $endDate )
          $end_i = strtotime($endDate);
        else if ( $membershipTypeDetails['duration_unit'] == 'lifetime' )
          $end_i = null;
        else
          $end_i = strtotime( '+' . $interval, $start_i );
      }
      break;
    case 'fixed' :
      {
        // This is ugly - basically we're doing modular arithmetic with                                                                                               
        // arbitrary membership terms. Consider pathological cases like                                                                                               
        // 47 day memberships with a start date of May 4th....                                                                                                         
        // We're just going to punt on the pathological cases for now.                                                                                                 
        // unit = year or lifetime: use starting day or month.                                                                                                         
        // unit = month, using starting day of current month.                                                                                                         
        // unit = day, use join date.                                                                                                                                 
        // fixed period start day looks like '731', '1201', '101' etc.                                                                                                 
        $fixed_start_day =  substr( $membershipTypeDetails['fixed_period_start_day'], -2 );
        $fixed_start_month = substr( $membershipTypeDetails['fixed_period_start_day'], 0,
                                     strlen( $membershipTypeDetails['fixed_period_start_day'] ) -2);

        $join_x = getdate($join_i);
        $join_year = $join_x['year'];
        $join_month = $join_x['mon'];
        $join_day = $join_x['mday'];

        switch ($membershipTypeDetails['duration_unit'])  {
        case 'day' :
          $start_i = $join_i;
          $end_i = strtotime ( '+' . $interval, $start_i );
          break;
        case 'month':
          $start_i = mktime( 0, 0, 0, $join_month, 1, $join_year );
          $end_i = strtotime ( '+' . $interval, $start_i );
          break;
        case 'year':
        case 'lifetime':
          if ( ($join_month > $fixed_start_month) ||
               (($join_month == $fixed_start_month) &&
                ($join_day >= $fixed_start_day)) )
            $fixed_start_year = $join_year;
          else
            $fixed_start_year = $join_year - 1;
          $start_i = mktime (0, 0, 0, $fixed_start_month, $fixed_start_day, $fixed_start_year);

          if  ( $membershipTypeDetails['duration_unit'] == 'lifetime' )
            $end_i = null;
          else
            $end_i = strtotime ( '+' . $interval, $start_i );
          break;
        default :
          //error                                                                                                                                                     
          throw new Exception('Ugh. I cannot deal with membership duration unit: "' . $membershipTypeDetails['duration_unit'] . '".');
        }
       // Now we're going to look at the rollover window, and if you                                                                                                 
        // are within it, give you an extra period of membership.                                                                                                     
        // Only for yearly, though.                                                                                                                                   
        if ( $membershipTypeDetails['duration_unit'] == 'year' ) {
          $roll = $membershipTypeDetails['fixed_period_rollover_day'];
          if ( $roll ) {
            $roll_day = substr( $roll, -2 );
            $roll_month = substr ($roll, 0, strlen( $roll ) -2);
            if ( ($roll_month > $fixed_start_month) ||
                 (($roll_month == $fixed_start_month) &&
                  ($roll_day >= $fixed_start_day)) )
              $roll_year = $fixed_start_year;
            else
              $roll_year = $fixed_start_year + 1;
            $roll_i = mktime (0, 0, 0, $roll_month, $roll_day, $roll_year);
            if ($join_i >= $roll_i) { // roll it again                                                                                                                 
            $end_i = strtotime('+1 year', $end_i );


            }
          }
        }
      }
    }
    // Now let's deal with reminder dates                                                                                                                             

    if ( isset( $membershipTypeDetails['renewal_reminder_day'] ) &&
         $membershipTypeDetails['renewal_reminder_day']          &&
         $end_i )
      $reminder_i = strtotime('-' . $membershipTypeDetails['renewal_reminder_day'] . ' day', $end_i);
    else
      $reminder_i = null;

    $dates = array(  'start_date'    => $start_i,
                     'end_date'      => $end_i,
                     'join_date'     => $join_i,
                     'reminder_date' => $reminder_i );
    $membershipDates = array( );
    foreach ( $dates as $varName => $value )  {
      if ($value){
        $membershipDates[$varName] = strftime ('%Y%m%d', $value);
      }
    }
    return $membershipDates;
  }




Dave Greenberg

  • Administrator
  • I’m (like) Lobo ;)
  • *****
  • Posts: 5760
  • Karma: 226
    • My CiviCRM Blog
Re: rollover dates in CiviMember
April 09, 2010, 05:35:08 pm
Chris,
You're right that the code is 'punting' on rollover date for 'Fixed' period memberships that have a duration UNIT other than year(s). In fact the admin form for setting up membership types explicitly exposes the Rollover Date field only when duration unit is years.

I'm not clear on whether you're reporting a bug for the supported use case - duration = 1 year. If so, and you can recreate on our demo, then please submit a bug report on the issue tracker with recommended patch (against current stable code version) to fix the supported case.

thx!
Protect your investment in CiviCRM by  becoming a Member!

ccowens

  • Guest
Re: rollover dates in CiviMember
April 09, 2010, 06:14:41 pm
I can recreate the problem on the demo site with fixed membership of 1 year, start date of June 1, rollover date of Aug 31.  When I create a new membership with a join date of January 5, 2010 it gives me a start of 6/1/09 and an end of 5/31/10.   http://issues.civicrm.org/jira/browse/CRM-6079

Pages: [1]
  • CiviCRM Community Forums (archive) »
  • Old sections (read-only, deprecated) »
  • Developer Discussion (Moderator: Donald Lobo) »
  • rollover dates in CiviMember

This forum was archived on 2017-11-26.