/** 
  * Generate a set of suggestions for the given bank transaction
  * 
  * @return array(match structures)
  */
 public function match(CRM_Banking_BAO_BankTransaction $btx, CRM_Banking_Matcher_Context $context)
 {
     $config = $this->_plugin_config;
     $threshold = $this->getThreshold();
     $data_parsed = $btx->getDataParsed();
     // find potential contacts
     $contacts_found = $context->findContacts($threshold, $data_parsed['name'], $config->lookup_contact_by_name);
     // with the identified contacts, look up matching memberships
     $memberships = $this->findMemberships($contacts_found, $btx, $context);
     // transform all memberships into suggestions
     foreach ($memberships as $membership) {
         $suggestion = new CRM_Banking_Matcher_Suggestion($this, $btx);
         if (isset($contact->general_options->suggestion_title)) {
             $suggestion->setTitle($contact->general_options->suggestion_title);
         } else {
             $suggestion->setTitle(ts("Record as Membership Fee"));
         }
         $suggestion->setId("existing-{$contribution_id}");
         $suggestion->setParameter('membership_id', $membership['id']);
         $suggestion->setParameter('last_fee_id', $membership['last_fee_id']);
         $suggestion->setProbability($membership['probability']);
         $btx->addSuggestion($suggestion);
     }
     // that's it...
     return empty($this->_suggestions) ? null : $this->_suggestions;
 }
Exemplo n.º 2
0
 /** 
  * this matcher does not really create suggestions, but rather enriches the parsed data
  */
 public function analyse(CRM_Banking_BAO_BankTransaction $btx, CRM_Banking_Matcher_Context $context)
 {
     $config = $this->_plugin_config;
     $data_parsed = $btx->getDataParsed();
     // itreate trough all rules
     foreach ($this->_plugin_config->rules as $rule) {
         if (empty($rule->fields)) {
             $fields = array('purpose');
         } else {
             $fields = $rule->fields;
         }
         // replace [[...]] style variables in the pattern
         $pattern = $rule->pattern;
         $variables = $this->getVariableList();
         foreach ($variables as $variable) {
             if (preg_match("#\\[\\[{$variable}\\]\\]#", $pattern)) {
                 $value = $this->getVariable($variable);
                 $pattern = preg_replace("#\\[\\[{$variable}\\]\\]#", print_r($value, 1), $pattern);
             }
         }
         // appy rule to all the fields listed...
         foreach ($fields as $field) {
             if (isset($data_parsed[$field])) {
                 $field_data = $data_parsed[$field];
                 $matches = array();
                 // match the pattern on the given field data
                 $match_count = preg_match_all($pattern, $field_data, $matches);
                 // and execute the actions for each match...
                 for ($i = 0; $i < $match_count; $i++) {
                     $this->processMatch($matches, $i, $data_parsed, $rule);
                 }
             }
         }
     }
     // save changes and that's it
     $btx->setDataParsed($data_parsed);
 }
Exemplo n.º 3
0
 /**
  * check if this ignore pattern applies to this btx
  */
 private function matches_pattern($ignore_record, CRM_Banking_BAO_BankTransaction $btx, CRM_Banking_Matcher_Context $context)
 {
     // collect all the fields
     $fields = array();
     if (isset($ignore_record->field)) {
         array_push($fields, $ignore_record->field);
     }
     if (isset($ignore_record->fields)) {
         $fields = array_merge($fields, $ignore_record->fields);
     }
     // extract the values
     $values = array();
     foreach ($fields as $field) {
         if (isset($btx->{$field})) {
             array_push($values, $btx->{$field});
         } else {
             $data = $btx->getDataParsed();
             if (isset($data[$field])) {
                 array_push($values, $data[$field]);
             }
         }
     }
     if (isset($ignore_record->regex)) {
         foreach ($values as $value) {
             if (preg_match($ignore_record->regex, $value)) {
                 return true;
             }
         }
     }
     return false;
 }
 /** 
  * Generate a set of suggestions for the given bank transaction
  * 
  * @return array(match structures)
  */
 public function match(CRM_Banking_BAO_BankTransaction $btx, CRM_Banking_Matcher_Context $context)
 {
     $config = $this->_plugin_config;
     $threshold = $this->getThreshold();
     $data_parsed = $btx->getDataParsed();
     $penalty = $this->getPenalty();
     // find potential contacts
     $contactID2probability = array();
     if (empty($config->contact_id_list)) {
         $nameSearch = $context->findContacts($threshold, $data_parsed['name'], $config->lookup_contact_by_name);
         foreach ($nameSearch as $contact_id => $probability) {
             if ($probability - $penalty < $threshold) {
                 continue;
             }
             $contactID2probability[$contact_id] = $probability;
         }
     } else {
         if (!empty($data_parsed[$config->contact_id_list])) {
             $id_list = explode(',', $data_parsed[$config->contact_id_list]);
             foreach ($id_list as $contact_id) {
                 $contact_id = (int) $contact_id;
                 if ($contact_id > 0) {
                     $contactID2probability[$contact_id] = 1.0;
                 }
             }
         }
     }
     // create suggestions
     if (!empty($config->search_terms)) {
         $query = $this->getPropagationSet($btx, $suggestion, '', $config->search_terms);
     } else {
         $query = array();
     }
     if ($config->search_wo_contacts) {
         $suggestions = $this->createRecurringContributionSuggestions($query, 1.0, $btx, $context);
     } else {
         $suggestions = array();
         foreach ($contactID2probability as $contact_id => $probability) {
             $query['contact_id'] = $contact_id;
             $new_suggestions = $this->createRecurringContributionSuggestions($query, $probability, $btx, $context);
             $suggestions = array_merge($suggestions, $new_suggestions);
         }
     }
     // apply penalties and threshold
     foreach ($suggestions as $suggestion) {
         $probability = $suggestion->getProbability();
         $probability -= $penalty;
         if ($probability >= $threshold) {
             if ($penalty) {
                 $suggestion->addEvidence($penalty, ts("A general penalty was applied."));
             }
             $suggestion->setProbability($probability);
             $btx->addSuggestion($suggestion);
         }
     }
     // that's it...
     return empty($this->_suggestions) ? null : $this->_suggestions;
 }
 /** 
  * Generate a set of suggestions for the given bank transaction
  * 
  * @return array(match structures)
  */
 public function match(CRM_Banking_BAO_BankTransaction $btx, CRM_Banking_Matcher_Context $context)
 {
     $config = $this->_plugin_config;
     $threshold = $this->getThreshold();
     $data_parsed = $btx->getDataParsed();
     $probability = 1.0 - $this->getPenalty($btx);
     $cancellation_mode = (bool) $config->cancellation_enabled && $btx->amount < 0;
     // look for the 'sepa_mandate' key
     if (empty($data_parsed['sepa_mandate'])) {
         return null;
     }
     // now load the mandate
     $mandate_reference = $data_parsed['sepa_mandate'];
     $mandate = civicrm_api('SepaMandate', 'getsingle', array('version' => 3, 'reference' => $mandate_reference));
     if (!empty($mandate['is_error'])) {
         CRM_Core_Session::setStatus(sprintf(ts("Couldn't load SEPA mandate for reference %s"), $mandate_reference), ts('Error'), 'error');
         return null;
     }
     // find the contribution
     if ($mandate['type'] == 'OOFF' && $mandate['entity_table'] == 'civicrm_contribution') {
         $contribution_id = $mandate['entity_id'];
     } elseif ($mandate['entity_table'] == 'civicrm_contribution_recur') {
         $contribution_recur_id = $mandate['entity_id'];
         $value_date = strtotime($btx->value_date);
         if ($cancellation_mode) {
             $earliest_date = date('Ymdhis', strtotime($config->cancellation_date_minimum, $value_date));
             $latest_date = date('Ymdhis', strtotime($config->cancellation_date_maximum, $value_date));
         } else {
             $earliest_date = date('Ymdhis', strtotime($config->received_date_minimum, $value_date));
             $latest_date = date('Ymdhis', strtotime($config->received_date_maximum, $value_date));
         }
         $contribution_id = 0;
         $find_contribution_query = "\n      SELECT  id\n      FROM    civicrm_contribution\n      WHERE   contribution_recur_id={$contribution_recur_id}\n      AND     receive_date <= DATE('{$latest_date}')\n      AND     receive_date >= DATE('{$earliest_date}');";
         $found_contribution = CRM_Core_DAO::executeQuery($find_contribution_query);
         while ($found_contribution->fetch()) {
             if (!$contribution_id) {
                 $contribution_id = $found_contribution->id;
             } else {
                 // this is the second contribution found!
                 CRM_Core_Session::setStatus(ts("There was more than one matching contribution found! Try to configure the plugin with a smaller search time span."), ts('Error'), 'error');
                 return null;
             }
         }
         if (!$contribution_id) {
             // no contribution found
             CRM_Core_Session::setStatus(ts("There was no matching contribution! Try to configure the plugin with a larger search time span."), ts('Error'), 'error');
             return null;
         }
     } else {
         error_log("org.project60.sepa: matcher_sepa: Bad mandate type.");
         return null;
     }
     // now, let's have a look at this contribution and its contact...
     $contribution = civicrm_api('Contribution', 'getsingle', array('id' => $contribution_id, 'version' => 3));
     if (!empty($contribution['is_error'])) {
         CRM_Core_Session::setStatus(ts("The contribution connected to this mandate could not be read."), ts('Error'), 'error');
         return null;
     }
     $contact = civicrm_api('Contact', 'getsingle', array('id' => $contribution['contact_id'], 'version' => 3));
     if (!empty($contact['is_error'])) {
         CRM_Core_Session::setStatus(ts("The contact connected to this mandate could not be read."), ts('Error'), 'error');
         return null;
     }
     // now: create a suggestion
     $suggestion = new CRM_Banking_Matcher_Suggestion($this, $btx);
     $suggestion->setParameter('contribution_id', $contribution_id);
     $suggestion->setParameter('contact_id', $contribution['contact_id']);
     $suggestion->setParameter('mandate_id', $mandate['id']);
     $suggestion->setParameter('mandate_reference', $mandate_reference);
     if (!$cancellation_mode) {
         // STANDARD SUGGESTION:
         $suggestion->setTitle(ts("SEPA SDD Transaction"));
         // add penalties for deviations in amount,status,deleted contact
         if ($btx->amount != $contribution['total_amount']) {
             $suggestion->addEvidence($config->deviation_penalty, ts("The contribution does not feature the expected amount."));
             $probability -= $config->deviation_penalty;
         }
         $status_inprogress = banking_helper_optionvalue_by_groupname_and_name('contribution_status', 'In Progress');
         if ($contribution['contribution_status_id'] != $status_inprogress) {
             $suggestion->addEvidence($config->deviation_penalty, ts("The contribution does not have the expected status 'in Progress'."));
             $probability -= $config->deviation_penalty;
         }
         if (!empty($contact['contact_is_deleted'])) {
             $suggestion->addEvidence($config->deviation_penalty, ts("The contact this mandate belongs to has been deleted."));
             $probability -= $config->deviation_penalty;
         }
     } else {
         // CANCELLATION SUGGESTION:
         $suggestion->setTitle(ts("Cancel SEPA SDD Transaction"));
         $suggestion->setParameter('cancellation_mode', $cancellation_mode);
         // calculate penalties (based on CRM_Banking_PluginImpl_Matcher_ExistingContribution::rateContribution)
         $contribution_amount = $contribution['total_amount'];
         $target_amount = -$context->btx->amount;
         $amount_range_rel = $contribution_amount * ($config->cancellation_amount_relative_maximum - $config->cancellation_amount_relative_minimum);
         $amount_range_abs = $config->cancellation_amount_absolute_maximum - $config->cancellation_amount_absolute_minimum;
         $amount_range = max($amount_range_rel, $amount_range_abs);
         $amount_delta = $contribution_amount - $target_amount;
         // check for amount limits
         if ($amount_range) {
             $penalty = $config->cancellation_amount_penalty * (abs($amount_delta) / $amount_range);
             if ($penalty > $config->cancellation_penalty_threshold) {
                 $suggestion->addEvidence($config->cancellation_amount_penalty, ts("The cancellation fee, i.e. the deviation from the original amount, is not in the specified range."));
                 $probability -= $penalty;
             }
         }
         // add general cancellation penalty, if set
         $probability -= (double) $config->cancellation_general_penalty;
         // generate cancellation extra parameters
         if ($config->cancellation_cancel_reason) {
             // determine the cancel reason
             if (empty($data_parsed[$config->cancellation_cancel_reason_source])) {
                 $suggestion->setParameter('cancel_reason', $config->cancellation_cancel_reason_default);
             } else {
                 $suggestion->setParameter('cancel_reason', $data_parsed[$config->cancellation_cancel_reason_source]);
             }
         }
         if ($config->cancellation_cancel_fee) {
             // calculate / determine the cancellation fee
             try {
                 $meval = new EvalMath();
                 // first initialise variables 'difference' and 'source'
                 $meval->evaluate("difference = -{$btx->amount} - {$contribution_amount}");
                 if (empty($config->cancellation_cancel_fee_source) || empty($data_parsed[$config->cancellation_cancel_fee_source])) {
                     $meval->evaluate("source = 0.0");
                 } else {
                     $meval->evaluate("source = {$data_parsed[$config->cancellation_cancel_fee_source]}");
                 }
                 $suggestion->setParameter('cancel_fee', number_format($meval->evaluate($config->cancellation_cancel_fee_default), 2));
             } catch (Exception $e) {
                 error_log("org.project60.banking.matcher.existing: Couldn't calculate cancellation_fee. Error was: {$e}");
             }
         }
     }
     // store it
     $suggestion->setProbability($probability);
     $btx->addSuggestion($suggestion);
     return $this->_suggestions;
 }