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

BASW-131: Membership end date rules #6

Open
wants to merge 3 commits into
base: 7.x-4.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 30 additions & 16 deletions includes/utils.inc
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function wf_crm_field_options($field, $context, $data) {
elseif ($table == 'membership' && $name == 'num_terms') {
$ret = drupal_map_assoc(range(1, 9));
}
elseif ($table == 'membership' && $name == 'start_date_rules') {
elseif ($table == 'membership' && in_array($name, array('start_date_rules', 'end_date_rules'))) {
$ret = array(0 => '- ' . t('Automatic') . ' -', 1 => '- ' . t('User Select') . ' -', 2 => t('Relative to active membership end date'));
}
// Aside from the above special cases, most lists can be fetched from api.getoptions
Expand All @@ -99,7 +99,7 @@ function wf_crm_field_options($field, $context, $data) {
// Pass data into api.getoptions for contextual filtering
$params += wf_crm_aval($data, "$ent:$c:$table:$n", array());
// We need the same options as membership_type_id for start_date_memberships
if($params['field'] == 'start_date_memberships') {
if (in_array($params['field'], array('start_date_memberships', 'end_date_memberships'))) {
$params['field'] = 'membership_type_id';
}
}
Expand Down Expand Up @@ -1186,20 +1186,7 @@ function wf_crm_get_fields($var = 'fields') {
'value' => 1,
'empty_option' => t('Enter Dates Manually'),
);
// Membership Date Rule Fields
$fields['membership_start_date_rules'] = array(
'name' => t('Start Date'),
'type' => 'select',
'expose_list' => TRUE,
'value' => 0,
'exposed_empty_option' => '- ' . t('Automatic') . ' -',
);
$fields['membership_start_date_memberships'] = array (
'name' => t('Select Membership Types'),
'type' => 'select',
'expose_list' => TRUE,
'extra' => array('multiple' => 1),
);

if (isset($sets['contribution'])) {
$fields['membership_fee_amount'] = array(
'name' => t('Membership Fee'),
Expand Down Expand Up @@ -1242,6 +1229,33 @@ function wf_crm_get_fields($var = 'fields') {
'empty_option' => t('None'),
);
}
// Membership Date Rule Fields
$fields['membership_start_date_rules'] = array(
'name' => t('Start Date'),
'type' => 'select',
'expose_list' => TRUE,
'value' => 0,
'exposed_empty_option' => '- ' . t('Automatic') . ' -',
);
$fields['membership_start_date_memberships'] = array (
Copy link
Member

Choose a reason for hiding this comment

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

maybe membership_start_date_rules_memberships is more clear, same for end_date_rules

'name' => t('Select Membership Types'),
'type' => 'select',
'expose_list' => TRUE,
'extra' => array('multiple' => 1),
);
$fields['membership_end_date_rules'] = array(
'name' => t('End Date'),
'type' => 'select',
'expose_list' => TRUE,
'value' => 0,
'exposed_empty_option' => '- ' . t('Automatic') . ' -',
);
$fields['membership_end_date_memberships'] = array (
'name' => t('Select Membership Types'),
'type' => 'select',
'expose_list' => TRUE,
'extra' => array('multiple' => 1),
);
}
// CiviGrant fields
if (isset($sets['grant'])) {
Expand Down
45 changes: 42 additions & 3 deletions includes/wf_crm_admin_form.inc
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,37 @@ class wf_crm_admin_form {
}
}
}

// Pro rate price checkbox
Copy link
Member

Choose a reason for hiding this comment

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

would it be easy to move this logic to a new private method with a clear name ?

$fieldName = 'pro_rate_memberships';
$fidProRate = "civicrm_{$c}_membership_{$n}_membership_{$fieldName}";
$this->form['membership'][$c]['membership'][$fs][$fidProRate] = array(
'#type' => 'checkbox',
'#title' => t('Pro Rate price of Membership').'&nbsp;<span class="crm-container"><a class="helpicon" href="#membership_pro_rate_membership">&nbsp;</a></span>',
'#default_value' => !empty($this->settings['data']['membership'][$c]['membership'][$n][$fieldName]),
);
// Handle dependent fields for membership date rules.
$dependentFields = array (
array (
'field' => "civicrm_{$c}_membership_{$n}_membership_start_date_memberships",
'depends_on' => "civicrm_{$c}_membership_{$n}_membership_start_date_rules",
),
array (
'field' => "civicrm_{$c}_membership_{$n}_membership_end_date_memberships",
'depends_on' => "civicrm_{$c}_membership_{$n}_membership_end_date_rules",
),
array (
'field' => "civicrm_{$c}_membership_{$n}_membership_pro_rate_memberships",
'depends_on' => "civicrm_{$c}_membership_{$n}_membership_end_date_rules",
),
);
foreach ($dependentFields as $dependentField) {
$this->form['membership'][$c]['membership'][$fs][$dependentField['field']]['#states'] = array (
'visible' => array (
":input[name='{$dependentField['depends_on']}']" => array ('value' => 2),
),
);
}
}
}
}
Expand Down Expand Up @@ -1311,9 +1342,14 @@ class wf_crm_admin_form {
}
// Remove 'create_civicrm_webform_element' option for membership date rules
if (strpos($field['form_key'], '_start_date_rules') !== FALSE
||
strpos($field['form_key'], '_start_date_memberships') !== FALSE) {
unset($options['create_civicrm_webform_element']);
||
strpos($field['form_key'], '_start_date_memberships') !== FALSE
||
strpos($field['form_key'], '_end_date_rules') !== FALSE
||
strpos($field['form_key'], '_end_date_memberships') !== FALSE
) {
unset($options['create_civicrm_webform_element']);
}
$options += wf_crm_field_options($field, 'config_form', $this->data);
$item += array(
Expand Down Expand Up @@ -1543,6 +1579,9 @@ class wf_crm_admin_form {
$this->data[$ent][$c][$table][$n][$name] = $val;
}
}
if ($name == 'pro_rate_memberships') {
$this->data[$ent][$c][$table][$n][$name] = $val;
}
}
}
}
Expand Down
27 changes: 26 additions & 1 deletion includes/wf_crm_admin_help.inc
Original file line number Diff line number Diff line change
Expand Up @@ -308,18 +308,43 @@ class wf_crm_admin_help {
'</p>';
}


public static function membership_start_date_rules() {
print '<p>'.
t('\'Automatic\' indicates that the start date will follow normal CiviCRM rules. i.e. New memberships will start immediately from date of sign-up and renewals will extend memberships.').
'</p><br /><p>'.
t('User select will create a new webform date component that the administrator can configure in many ways including adding a fixed or relative start or end date.').'</p>'.
t('\'User select\' will create a new webform date component that the administrator can configure in many ways including adding a fixed or relative start or end date.').'</p>'.
'</p><br /><p>'.
t('\'Relative to active membership end date\' will modify the start date of the new membership as follows:
<p>It will first check whether the user has an existing membership of this type. If so it will follow the automatic membership rules.</p>
<p>If the user does not have a membership of this type, it will create a new membership and then check if the user has any active memberships of the relevant types specified. If not then the system will follow automatic membership rules.</p>
<p>If so it will set the start date of the new membership to be the day following the end date of the existing active membership.</p>
<p>If the user currently has multiple relevant membership types the start date will be one day following the end date of the membership with the latest end date.</p>');
}

public static function membership_end_date_rules() {
print '<p>'.
t('"Automatic" indicates that new membership end date will be calculated according to the start date and the membership duration').
'</p>';
}

public static function membership_pro_rate_membership() {
print '<p>'.
t('If this box is ticked, the membership price will be calculated based on the number of days left until the end date compared to the number of days in a regular membership period').
'</p>';
}
public static function membership_start_date_memberships() {
print '<p>'.
t('If you do not select a membership type, new membership will start immediately once the current active membership with the latest end date has expired.').
'</p>';
}

public static function membership_end_date_memberships() {
print '<p>'.
t('If you do not select a membership type, new membership will end when the current active membership with the latest end date expires.').
'</p>';
}

public static function membership_fee_amount() {
print '<p>' .
t('Price for this membership per term. If this field is enabled, the default minimum membership fee from CiviCRM membership type settings will not be loaded.') .
Expand Down
149 changes: 134 additions & 15 deletions includes/wf_crm_webform_postprocess.inc
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ class wf_crm_webform_postprocess extends wf_crm_webform_base {
$this->loadMultiPageData();
$this->validateParticipants();
}

// Validate end dates as per membership date rules.
if(!empty($this->data['membership'])) {
$this->validateMembershipDateRules();
}

// Process live contribution. If the transaction is unsuccessful it will trigger a form validation error.
if ($this->contribution_page) {
Expand All @@ -103,6 +108,35 @@ class wf_crm_webform_postprocess extends wf_crm_webform_base {
$form_state['civicrm']['ent'] = $this->ent;
$form_state['civicrm']['line_items'] = $this->line_items;
}

/**
* Validate function for membership date rules.
*/
public function validateMembershipDateRules() {
Copy link
Member

Choose a reason for hiding this comment

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

no need for this to be public method

if (!empty($this->data['membership'])) {
Copy link
Member

Choose a reason for hiding this comment

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

or :

if (empty($this->data['membership'])) {
return;
}
// rest of the code

foreach ($this->ent['contact'] as $c => $contact) {
if ($contact['id'] && isset($this->all_sets['membership']) && !empty($this->data['membership'][$c]['number_of_membership'])) {
$isError = FALSE;
$membershipFieldName = '';
foreach (wf_crm_aval($this->data, "membership:$c:membership", array()) as $n => $params) {
$params = $this->processMembershipDateRules($contact['id'], $params, $c, $n);
$membershipFieldName = "civicrm_{$c}_membership_{$n}_membership_membership_type_id";
if (!empty($params['end_date_rules']) && $params['end_date_rules'] == 2 && empty($params['end_date'])) {
$isError = TRUE;
break;
}
elseif (!empty($params['end_date']) && (strtotime($params['end_date']) < time())) {
$isError = TRUE;
break;
}
}
if ($isError) {
form_set_error($membershipFieldName, 'Unfortuntately you cannot purchase this item as your membership has expired.');
}
}
}
}
}

/**
* Process webform submission when it is about to be saved. Called by the following hook:
Expand Down Expand Up @@ -1186,10 +1220,10 @@ class wf_crm_webform_postprocess extends wf_crm_webform_base {
$params['contact_id'] = $cid;
// The api won't let us manually set status without this weird param
$params['skipStatusCal'] = !empty($params['status_id']);

// Process date rules only for new memberships
// Process date rules only for new memberships.
if (!isset($params['id'])) {
$params = $this->processMembershipDateRules($cid, $params);
$params = $this->processMembershipDateRules($cid, $params, $c, $n);
}

$result = wf_civicrm_api('membership', 'create', $params);
Expand Down Expand Up @@ -1219,32 +1253,74 @@ class wf_crm_webform_postprocess extends wf_crm_webform_base {
}
}

/**
/**
* Process membership date rules
*/
private function processMembershipDateRules($cid, $params) {
if (!empty($params['start_date_rules']) && $params['start_date_rules'] == 2) {
// fetch existing membership having latest end_date
$membershipParams = array (
'sequential' => 1,
'active_only' => 1,
'contact_id' => $cid,
'options' => array ('sort' => "end_date DESC", 'limit' => 1),
);
// Handle case when memberships are selected for start date rules
private function processMembershipDateRules($cid, $params, $c, $n) {
$membershipParams = array (
'sequential' => 1,
'active_only' => 1,
'contact_id' => $cid,
'options' => array ('sort' => "end_date DESC", 'limit' => 1),
);
$rawValues = _webform_client_form_submit_flatten($this->node, wf_crm_aval($this->form_state, 'values:submitted'));

// Process start date rules.
if (!empty($params['start_date_rules']) && $params['start_date_rules'] == 1) {
$fieldName = 'rule_start_date';
$fieldKey = "civicrm_{$c}_membership_{$n}_membership_{$fieldName}";
$componentId = $this->getComponent($fieldKey)['cid'];
if (!empty($params['rule_start_date'])) {
$params['start_date'] = $params['rule_start_date'];
}
elseif (!empty($rawValues[$componentId])) {
$params['start_date'] = $rawValues[$componentId]['year'] . '-' . $rawValues[$componentId]['month'] . '-' . $rawValues[$componentId]['day'];
}
}
elseif (!empty($params['start_date_rules']) && $params['start_date_rules'] == 2){
// Handle case when memberships are selected for start date rules.
if (!empty($params['start_date_memberships'])) {
$membershipParams['membership_type_id'] = array (
'IN' => $params['start_date_memberships']
);
}

$activeMemberships = civicrm_api3('Membership', 'get', $membershipParams);
if ($activeMemberships['count']) {
$activeMembershipsEndDate = new DateTime($activeMemberships['values'][0]['end_date']);
$start_date = $activeMembershipsEndDate->modify('+1 day');
$params['start_date'] = $start_date->format('Ymd');
}
}

// Process end date rules.
if (!empty($params['end_date_rules']) && $params['end_date_rules'] == 1) {
$fieldName = 'rule_end_date';
$fieldKey = "civicrm_{$c}_membership_{$n}_membership_{$fieldName}";
$componentId = $this->getComponent($fieldKey)['cid'];
if (!empty($params['rule_end_date'])) {
$params['end_date'] = $params['rule_end_date'];
}
elseif (!empty($rawValues[$componentId])) {
$params['start_date'] = $rawValues[$componentId]['year'] . '-' . $rawValues[$componentId]['month'] . '-' . $rawValues[$componentId]['day'];
}
}
elseif (!empty($params['end_date_rules']) && $params['end_date_rules'] == 2) {
// Handle case when memberships are selected for end date rules.
if (!empty($params['end_date_memberships'])) {
$membershipParams['membership_type_id'] = array (
'IN' => $params['end_date_memberships']
);
}
$activeMemberships = civicrm_api3('Membership', 'get', $membershipParams);
if ($activeMemberships['count']) {
$activeMembershipsEndDate = $activeMemberships['values'][0]['end_date'];
if (empty($params['start_date'])) {
$params['start_date'] = date('Y-m-d');
}
$params['end_date'] = $activeMembershipsEndDate;
}
}
return $params;
}

Expand Down Expand Up @@ -1587,6 +1663,16 @@ class wf_crm_webform_postprocess extends wf_crm_webform_base {
$membership_financialtype = $item['financial_type_id'];
};

// Pro rate logic.
if (!empty($item['end_date_rules'])
&&
!empty($item['pro_rate_memberships'])
&&
$item['end_date_rules'] == 2
) {
$price = $this->pro_rated_membership_fee($type, $price, $c, $item);
}

if ($price) {
$this->line_items[] = array(
'qty' => $item['num_terms'],
Expand Down Expand Up @@ -1626,6 +1712,39 @@ class wf_crm_webform_postprocess extends wf_crm_webform_base {
return round($this->totalContribution, 2);
}

/**
* Calculate pro rated membership fee.
* @param Integer $type
* @param Float $price
* @param Integer $c
* @param Array $item
* @return Float $price
Copy link
Member

Choose a reason for hiding this comment

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

the docblock params does not match the method params

*/
private function pro_rated_membership_fee($type, $price, $c, $n, $item) {
Copy link
Member

Choose a reason for hiding this comment

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

or change the method name to : caculateMembershipProRateFee

Copy link
Member

Choose a reason for hiding this comment

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

can you replace $c and $n and $item with names that represent what they actually about.

$durationInterval = $this->getMembershipTypeField($type, 'duration_interval');
$durationUnit = $this->getMembershipTypeField($type, 'duration_unit');

// Calculate price/day.
$dateToday = date('Y-m-d');
$dateAfterGiveDuration = date('Y-m-d', strtotime("{$dateToday} +{$durationInterval} {$durationUnit}"));
$dateToday = date_create($dateToday);
$dateAfterGiveDuration = date_create($dateAfterGiveDuration);
$durationInDays = date_diff($dateToday, $dateAfterGiveDuration)->format('%a');

$pricePerDay = $price / $durationInDays;

// Find number of pro rate days.
$params = $this->processMembershipDateRules($this->existing_contacts[$c], $item, $c, $n);
$start_date = date_create($params['start_date']);
$end_date = date_create($params['end_date']);
$proRateDays = date_diff($start_date, $end_date)->format('%a');

// Calculate Pro Rated membership fee.
$price = $pricePerDay * $proRateDays;

return $price;
}

/**
* Are billing fields exposed to this webform page?
* @return bool
Expand Down
Loading