/** 
  * 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();
     $penalty = $this->getPenalty($btx);
     $data_parsed = $btx->getDataParsed();
     // first see if all the required values are there
     if (!$this->requiredValuesPresent($btx)) {
         return null;
     }
     // then look up potential contacts
     $contacts_found = $context->findContacts($threshold, $data_parsed['name'], $config->lookup_contact_by_name);
     // finally generate suggestions
     foreach ($contacts_found as $contact_id => $contact_probability) {
         $suggestion = new CRM_Banking_Matcher_Suggestion($this, $btx);
         $suggestion->setTitle(ts("Create a new contribution"));
         $suggestion->setId("create-{$contact_id}");
         $suggestion->setParameter('contact_id', $contact_id);
         // set probability manually, I think the automatic calculation provided by ->addEvidence might not be what we need here
         $contact_probability -= $penalty;
         if ($contact_probability >= $threshold) {
             $suggestion->setProbability($contact_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();
     // 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;
 }
 public function match(CRM_Banking_BAO_BankTransaction $btx, CRM_Banking_Matcher_Context $context)
 {
     $config = $this->_plugin_config;
     // create 'manually processed' suggestion, if applicable
     if ($config->manual_enabled) {
         if ($config->manual_show_always || $this->has_other_suggestions($btx)) {
             $manually_processed = new CRM_Banking_Matcher_Suggestion($this, $btx);
             $manually_processed->setProbability($this->get_probability($config->manual_probability, $btx));
             $manually_processed->setTitle($config->manual_title);
             $manually_processed->setId('manual');
             // add related contacts
             $data_parsed = $btx->getDataParsed();
             $contacts = $context->findContacts(0, $data_parsed['name'], $config->lookup_contact_by_name);
             $manually_processed->setParameter('contact_ids', implode(',', array_keys($contacts)));
             $manually_processed->setParameter('contact_ids2probablility', json_encode($contacts));
             $btx->addSuggestion($manually_processed);
         }
     }
     // create 'not relevant' suggestion, if applicable
     if ($config->ignore_enabled) {
         if ($config->ignore_show_always || $this->has_other_suggestions($btx)) {
             $not_relevant = new CRM_Banking_Matcher_Suggestion($this, $btx);
             $not_relevant->addEvidence($this->get_probability($config->ignore_probability, $btx));
             $not_relevant->setTitle($config->ignore_title);
             $not_relevant->setId('ignore');
             $btx->addSuggestion($not_relevant);
         }
     }
     // that's it...
     return empty($this->_suggestions) ? null : $this->_suggestions;
 }
 /**
  * create suggestions for matching recurring contributions
  */
 function createRecurringContributionSuggestions($query, $probability, $btx, $context)
 {
     $config = $this->_plugin_config;
     $threshold = $this->getThreshold();
     $data_parsed = $btx->getDataParsed();
     $suggestions = array();
     // don't waste your time contacts below the threshold...
     if ($probability < $threshold) {
         return $suggestions;
     }
     $rcur_result = civicrm_api3('ContributionRecur', 'get', $query);
     foreach ($rcur_result['values'] as $rcur_id => $rcur) {
         // find the next expected date for the recurring contribution
         $expected_date = self::getExpectedDate($rcur, $btx, $config->recurring_mode);
         if ($expected_date == NULL) {
             continue;
         }
         // create a suggestion
         $suggestion = new CRM_Banking_Matcher_Suggestion($this, $btx);
         $suggestion->setId("recurring-{$rcur_id}");
         $suggestion->setParameter('recurring_contribution_id', $rcur_id);
         $suggestion->setParameter('contact_id', $rcur['contact_id']);
         $suggestion->setParameter('expected_date', date('Y-m-d', $expected_date));
         $suggestion->setParameter('expected_amount', $rcur['amount']);
         if (!empty($config->suggestion_title)) {
             $suggestion->setTitle($config->suggestion_title);
         }
         if ($probability < 1.0) {
             $suggestion->addEvidence(1.0 - $probability, ts("The contact could not be uniquely identified."));
         }
         // CHECK AMOUNT
         if ($config->amount_check) {
             // calculate the amount penalties (equivalent to CRM_Banking_PluginImpl_Matcher_ExistingContribution)
             $transaction_amount = $btx->amount;
             $expected_amount = $rcur['amount'];
             $amount_delta = $transaction_amount - $expected_amount;
             if ($transaction_amount < $expected_amount * $config->amount_relative_minimum && $amount_delta < $config->amount_absolute_minimum) {
                 continue;
             }
             if ($transaction_amount > $expected_amount * $config->amount_relative_maximum && $amount_delta > $config->amount_absolute_maximum) {
                 continue;
             }
             $amount_range_rel = $transaction_amount * ($config->amount_relative_maximum - $config->amount_relative_minimum);
             $amount_range_abs = $config->amount_absolute_maximum - $config->amount_absolute_minimum;
             $amount_range = max($amount_range_rel, $amount_range_abs);
             if ($amount_range) {
                 $penalty = $config->amount_penalty * (abs($amount_delta) / $amount_range);
                 if ($penalty) {
                     $suggestion->addEvidence($penalty, ts("The amount of the transaction differs from the expected amount."));
                     $probability -= $penalty;
                 }
             }
         }
         // CHECK CURRENCY
         if ($context->btx->currency != $rcur['currency']) {
             $suggestion->addEvidence($config->currency_penalty, ts("The currency of the transaction is not as expected."));
             $probability -= $config->currency_penalty;
         }
         // CHECK EXPECTED DATE
         if ($config->received_date_check) {
             // use date only
             $transaction_date = strtotime(date('Y-m-d', strtotime($context->btx->value_date)));
             // only apply penalties, if the offset is outside the accepted range
             $date_offset = $transaction_date - $expected_date;
             if ($date_offset < strtotime($config->acceptable_date_offset_from, 0) || $date_offset > strtotime($config->acceptable_date_offset_to, 0)) {
                 // check if the payment is completely out of bounds
                 if ($date_offset < strtotime($config->date_offset_minimum, 0)) {
                     continue;
                 }
                 if ($date_offset > strtotime($config->date_offset_maximum, 0)) {
                     continue;
                 }
                 // calculate the date penalties
                 $date_range = strtotime($config->date_offset_maximum) - strtotime($config->date_offset_minimum) - (strtotime($config->acceptable_date_offset_to) - strtotime($config->acceptable_date_offset_from));
                 if ($date_offset < 0) {
                     $date_delta = abs($date_offset - strtotime($config->acceptable_date_offset_from, 0));
                 } else {
                     $date_delta = abs($date_offset - strtotime($config->acceptable_date_offset_to, 0));
                 }
                 if ($date_range) {
                     $penalty = $config->date_penalty * ($date_delta / $date_range);
                     if ($penalty) {
                         $suggestion->addEvidence($penalty, ts("The date of the transaction deviates too much from the expected date."));
                         $probability -= $penalty;
                     }
                 }
             }
         }
         // CHECK FOR OTHER PAYMENTS
         if ($config->existing_check) {
             $other_contributions_id_list = array();
             $expected_date_string = date('Y-m-d', $expected_date);
             if (empty($config->existing_status_list)) {
                 $config->existing_status_list = array(1, 2);
             }
             $existing_status_list = implode(',', $config->existing_status_list);
             // determine date range
             // TODO: use date_offset_minimum/maximum
             if (preg_match("/[0-9]+%/", $config->existing_precision)) {
                 $cycle_length_seconds = strtotime("+{$rcur['frequency_interval']} {$rcur['frequency_unit']}", 0);
                 $date_range = (int) ((100.0 - (double) $config->existing_precision) / 100.0 * ((double) $cycle_length_seconds / (double) (60 * 60 * 24)));
             } else {
                 $date_range = (int) $config->existing_precision;
             }
             $sql = "\n        SELECT id AS contribution_id\n        FROM civicrm_contribution \n        WHERE contribution_recur_id = {$rcur_id}\n          AND contribution_status_id IN ({$existing_status_list})\n          AND (receive_date BETWEEN ('{$expected_date_string}' - INTERVAL {$date_range} DAY)\n                                AND ('{$expected_date_string}' + INTERVAL {$date_range} DAY) );";
             $sql_query = CRM_Core_DAO::executeQuery($sql);
             while ($sql_query->fetch()) {
                 $other_contributions_id_list[] = $sql_query->contribution_id;
             }
             if (!empty($other_contributions_id_list)) {
                 $links = array();
                 if (count($other_contributions_id_list) == 1) {
                     $message = ts("There is already another contribution recorded for this interval: ");
                 } else {
                     $message = ts("There are already multiple contributions recorded for this interval: ");
                 }
                 foreach ($other_contributions_id_list as $other_contributions_id) {
                     $links[] = "<a href='" . CRM_Utils_System::url('civicrm/contact/view/contribution', "reset=1&id={$other_contributions_id}&cid={$rcur['contact_id']}&action=view") . "'>[{$other_contributions_id}]</a>";
                 }
                 $message .= implode(', ', $links);
                 $suggestion->addEvidence($config->existing_penalty, $message);
                 $probability -= $config->existing_penalty;
             }
         }
         $suggestion->setProbability($probability);
         $suggestions[] = $suggestion;
     }
     return $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();
     $penalty = $this->getPenalty($btx);
     $data_parsed = $btx->getDataParsed();
     // first see if all the required values are there
     if (!$this->requiredValuesPresent($btx)) {
         return null;
     }
     // resolve accepted states
     $accepted_status_ids = $this->getAcceptedContributionStatusIDs();
     $contributions = array();
     $contribution2contact = array();
     $contribution2totalamount = array();
     $contributions_identified = array();
     // check if this is actually enabled
     if ($config->contribution_search) {
         // find contacts
         $contacts_found = $context->findContacts($threshold, $data_parsed['name'], $config->lookup_contact_by_name);
         // with the identified contacts, look up contributions
         foreach ($contacts_found as $contact_id => $contact_probabiliy) {
             if ($contact_probabiliy < $threshold) {
                 continue;
             }
             $potential_contributions = $this->getPotentialContributionsForContact($contact_id, $context);
             foreach ($potential_contributions as $contribution) {
                 // check for expected status
                 if (!in_array($contribution['contribution_status_id'], $accepted_status_ids)) {
                     continue;
                 }
                 $contribution_probability = $this->rateContribution($contribution, $context);
                 // apply penalty
                 $contribution_probability -= $penalty;
                 if ($contribution_probability > $threshold) {
                     $contributions[$contribution['id']] = $contribution_probability;
                     $contribution2contact[$contribution['id']] = $contact_id;
                     $contribution2totalamount[$contribution['id']] = $contribution['total_amount'];
                 }
             }
         }
     }
     // add the contributions coming in from a list (if any)
     if (!empty($config->contribution_list)) {
         if (!empty($data_parsed[$config->contribution_list])) {
             $id_list = explode(',', $data_parsed[$config->contribution_list]);
             foreach ($id_list as $contribution_id_string) {
                 $contribution_id = (int) $contribution_id_string;
                 if ($contribution_id) {
                     $contribution_bao = new CRM_Contribute_DAO_Contribution();
                     if ($contribution_bao->get('id', $contribution_id)) {
                         $contribution = $contribution_bao->toArray();
                         // check for expected status
                         if (!in_array($contribution['contribution_status_id'], $accepted_status_ids)) {
                             continue;
                         }
                         $contribution_probability = $this->rateContribution($contribution, $context);
                         // apply penalty
                         $contribution_probability -= $penalty;
                         if ($contribution_probability > $threshold) {
                             $contributions[$contribution['id']] = $contribution_probability;
                             $contribution2contact[$contribution['id']] = $contribution['contact_id'];
                             $contribution2totalamount[$contribution['id']] = $contribution['total_amount'];
                             $contacts_found[$contribution['contact_id']] = 1.0;
                             $contributions_identified[] = $contribution['id'];
                         }
                     }
                 }
             }
         }
     }
     // transform all of the contributions found into suggestions
     foreach ($contributions as $contribution_id => $contribution_probability) {
         $contact_id = $contribution2contact[$contribution_id];
         $suggestion = new CRM_Banking_Matcher_Suggestion($this, $btx);
         if (!in_array($contribution_id, $contributions_identified)) {
             if ($contacts_found[$contact_id] >= 1.0) {
                 $suggestion->addEvidence(1.0, ts("Contact was positively identified."));
             } else {
                 $suggestion->addEvidence($contacts_found[$contact_id], ts("Contact was likely identified."));
             }
         }
         if ($contribution_probability >= 1.0) {
             $suggestion->setTitle(ts("Matching contribution found"));
             if ($config->mode != "cancellation") {
                 $suggestion->addEvidence(1.0, ts("A pending contribution matching the transaction was found."));
             } else {
                 $suggestion->addEvidence(1.0, ts("This transaction is the <b>cancellation</b> of the below contribution."));
             }
         } else {
             $suggestion->setTitle(ts("Possible matching contribution found"));
             if ($config->mode != "cancellation") {
                 $suggestion->addEvidence($contacts_found[$contact_id], ts("A pending contribution partially matching the transaction was found."));
             } else {
                 $suggestion->addEvidence($contacts_found[$contact_id], ts("This transaction could be the <b>cancellation</b> of the below contribution."));
             }
         }
         $suggestion->setId("existing-{$contribution_id}");
         $suggestion->setParameter('contribution_id', $contribution_id);
         $suggestion->setParameter('contact_id', $contact_id);
         $suggestion->setParameter('mode', $config->mode);
         // generate cancellation extra parameters
         if ($config->mode == 'cancellation') {
             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} - {$contribution2totalamount[$contribution_id]}");
                     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', $meval->evaluate($config->cancellation_cancel_fee_default));
                 } catch (Exception $e) {
                     error_log("org.project60.banking.matcher.existing: Couldn't calculate cancellation_fee. Error was: {$e}");
                 }
             }
         }
         // set probability manually, I think the automatic calculation provided by ->addEvidence might not be what we need here
         $suggestion->setProbability($contribution_probability * $contacts_found[$contact_id]);
         // update title if requested
         if (!empty($config->title)) {
             $suggestion->setTitle($config->title);
         }
         $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;
 }
 public function match(CRM_Banking_BAO_BankTransaction $btx, CRM_Banking_Matcher_Context $context)
 {
     // get list of existing batches (cache in context)
     $existing_batches = $context->getCachedEntry('banking.pluginimpl.matcher.batch');
     if ($existing_batches == NULL) {
         $existing_batches = $this->generateBatchList();
         $context->setCachedEntry('banking.pluginimpl.matcher.batch', $existing_batches);
     }
     // look for a matching batch
     $config = $this->_plugin_config;
     $booking_date = strtotime($btx->booking_date);
     $matching_batches = array();
     foreach ($existing_batches as $batch) {
         $total_amount = $batch['total'];
         if (!empty($batch['export_date'])) {
             $submission_date = strtotime($batch['export_date']);
         } elseif ($batch['modified_date']) {
             $submission_date = strtotime($batch['modified_date']);
         } else {
             $submission_date = strtotime($batch['created_date']);
         }
         // check amount
         if (abs(1 - $total_amount / $btx->amount) > $config->total_amount_tolerance) {
             continue;
         }
         // check export_date_to_payment_min / max
         if ($booking_date < strtotime($config->export_date_to_payment_min, $submission_date)) {
             continue;
         }
         if ($booking_date > strtotime($config->export_date_to_payment_max, $submission_date)) {
             continue;
         }
         // batch is accepted -> calculate probability:
         // first factor: expected income time
         $time_penalty_total = strtotime('-' . $config->export_date_to_payment_tolerance, abs($booking_date - $submission_date));
         $time_penalty = min(1.0, 1 - $time_penalty_total / (strtotime($config->export_date_to_payment_max) - strtotime($config->export_date_to_payment_min)));
         // second factor: equal amount
         $amount_penalty = 1.0 - abs(1 - $total_amount / $btx->amount) / $config->total_amount_tolerance;
         // third factor: statmentes pending
         $status_penalty = 1.0 - count($this->getNonPendingContributionIDs($batch['id'])) / $batch['item_count'];
         $matching_batches[$batch['id']] = $time_penalty * $amount_penalty * $status_penalty;
     }
     // for each matched batch, create a suggestion
     foreach ($matching_batches as $batch_id => $batch_probability) {
         $suggestion = new CRM_Banking_Matcher_Suggestion($this, $btx);
         $suggestion->setTitle(ts("Settles a contribution batch"));
         $suggestion->setParameter('batch_id', $batch_id);
         $suggestion->setId("batch-" . $batch_id);
         $suggestion->setProbability($batch_probability);
         $btx->addSuggestion($suggestion);
     }
     // that's it...
     return empty($this->_suggestions) ? null : $this->_suggestions;
 }