/** * 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; }
/** * 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); }
/** * 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; }