/**
  * changes the amount of a SEPA mandate
  * 
  * @return success as boolean
  * @author endres -at- systopia.de 
  */
 static function adjustAmount($mandate_id, $adjusted_amount)
 {
     $adjusted_amount = (double) $adjusted_amount;
     $contribution_id_pending = CRM_Core_OptionGroup::getValue('contribution_status', 'Pending', 'name');
     // use a lock, in case somebody is batching just now
     $lock = CRM_Sepa_Logic_Settings::getLock();
     if (empty($lock)) {
         CRM_Core_Session::setStatus(sprintf(ts("Cannot adjust mandate [%s], batching in progress!"), $mandate_id), ts('Error'), 'error');
         return FALSE;
     }
     // first, load the mandate
     $mandate = civicrm_api("SepaMandate", "getsingle", array('id' => $mandate_id, 'version' => 3));
     if (isset($mandate['is_error'])) {
         CRM_Core_Session::setStatus(sprintf(ts("Cannot read mandate [%s]. Error was: '%s'"), $mandate_id, $mandate['error_message']), ts('Error'), 'error');
         $lock->release();
         return FALSE;
     }
     // check the mandate type
     if ($mandate['type'] != "RCUR") {
         CRM_Core_Session::setStatus(ts("You can only adjust the amount of recurring contribution mandates."), ts('Error'), 'error');
         $lock->release();
         return FALSE;
     }
     // load the contribution
     $contribution_id = $mandate['entity_id'];
     $contribution = civicrm_api('ContributionRecur', "getsingle", array('id' => $contribution_id, 'version' => 3));
     if (isset($contribution['is_error']) && $contribution['is_error']) {
         CRM_Core_Session::setStatus(sprintf(ts("Cannot read contribution [%s]. Error was: '%s'"), $contribution_id, $contribution['error_message']), ts('Error'), 'error');
         $lock->release();
         return FALSE;
     }
     // check the amount
     if ($adjusted_amount <= 0) {
         CRM_Core_Session::setStatus(ts("The amount cannot be changed to zero or less."), ts('Error'), 'error');
         $lock->release();
         return FALSE;
     }
     // check the amount
     $old_amount = (double) $contribution['amount'];
     if ($old_amount == $adjusted_amount) {
         CRM_Core_Session::setStatus(ts("The requested amount is the same as the current one."), ts('Error'), 'error');
         $lock->release();
         return FALSE;
     }
     // modify the amount in the recurring contribution
     $query = array('version' => 3, 'id' => $contribution_id, 'amount' => $adjusted_amount, 'currency' => 'EUR');
     $result = civicrm_api("ContributionRecur", "create", $query);
     if (!empty($result['is_error'])) {
         CRM_Core_Session::setStatus(sprintf(ts("Cannot modify recurring contribution [%s]. Error was: '%s'"), $contribution_id, $result['error_message']), ts('Error'), 'error');
         $lock->release();
         return FALSE;
     }
     // find already created contributions. Those also need to be modified
     $contributions2adjust = array();
     $adjusted_ids = array();
     $find_need2adjust = "\n    SELECT id\n    FROM civicrm_contribution\n    WHERE receive_date >= DATE(NOW())\n      AND contribution_recur_id = {$contribution_id}\n      AND contribution_status_id = {$contribution_id_pending};";
     $contributions2adjust_query = CRM_Core_DAO::executeQuery($find_need2adjust);
     while ($contributions2adjust_query->fetch()) {
         $contributions2adjust[] = $contributions2adjust_query->id;
     }
     // ...and adjust them:
     foreach ($contributions2adjust as $contribution2adjust_id) {
         $update_result = civicrm_api("Contribution", "create", array('version' => 3, 'id' => $contribution2adjust_id, 'total_amount' => $adjusted_amount, 'contribution_status_id' => $contribution_id_pending));
         if (!empty($update_result['is_error'])) {
             CRM_Core_Session::setStatus(sprintf(ts("Cannot update scheduled contribution [%s]. Error was: '%s'"), $contribution2adjust_id, $update_result['error_message']), ts('Error'), 'warn');
         } else {
             array_push($adjusted_ids, $contribution2adjust_id);
         }
     }
     if (count($adjusted_ids)) {
         CRM_Core_Session::setStatus(sprintf(ts("Successfully updated %d generated contributions."), count($adjusted_ids)), ts('Mandate updated.'), 'info');
     }
     $lock->release();
     return TRUE;
 }
 /**
  * This method will mark the given transaction group as 'received':
  *   - set txgroup status to 'received'
  *   - change status from 'In Progress' to 'Completed' for all contributions
  *   - (store/update the bank account information)
  *
  * @return error message, unless successful
  */
 static function received($txgroup_id)
 {
     // step 0: check lock
     $lock = CRM_Sepa_Logic_Settings::getLock();
     if (empty($lock)) {
         return "Batching in progress. Please try again later.";
     }
     // step 1: gather data
     $group_status_id_open = (int) CRM_Core_OptionGroup::getValue('batch_status', 'Open', 'name');
     $group_status_id_closed = (int) CRM_Core_OptionGroup::getValue('batch_status', 'Closed', 'name');
     $group_status_id_received = (int) CRM_Core_OptionGroup::getValue('batch_status', 'Received', 'name');
     $status_pending = (int) CRM_Core_OptionGroup::getValue('contribution_status', 'Pending', 'name');
     $status_closed = (int) CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name');
     $status_inprogress = (int) CRM_Core_OptionGroup::getValue('contribution_status', 'In Progress', 'name');
     if (empty($group_status_id_received)) {
         return civicrm_api3_create_error("Status 'Received' does not exist!");
     }
     // step 0: load the group object
     $txgroup = civicrm_api('SepaTransactionGroup', 'getsingle', array('id' => $txgroup_id, 'version' => 3));
     if (!empty($txgroup['is_error'])) {
         $lock->release();
         return "Cannot find transaction group " . $txgroup_id;
     }
     // check status
     if ($txgroup['status_id'] != $group_status_id_closed) {
         $lock->release();
         return "Transaction group " . $txgroup_id . " is not 'closed'.";
     }
     // step 1.1: fix contributions, that have no financial transactions. (happens due to a status-bug in civicrm)
     $find_rotten_contributions_sql = "\n    SELECT\n     contribution.id AS contribution_id\n    FROM\n      civicrm_sdd_contribution_txgroup AS txn_to_contribution\n    LEFT JOIN\n      civicrm_contribution AS contribution ON contribution.id = txn_to_contribution.contribution_id\n    WHERE\n      txn_to_contribution.txgroup_id IN ({$txgroup_id})\n    AND\n      contribution.id NOT IN (SELECT entity_id FROM civicrm_entity_financial_trxn WHERE entity_table='civicrm_contribution');\n    ";
     $rotten_contribution = CRM_Core_DAO::executeQuery($find_rotten_contributions_sql);
     while ($rotten_contribution->fetch()) {
         $contribution_id = $rotten_contribution->contribution_id;
         // set these rotten contributions to 'Pending', no 'pay_later'
         CRM_Core_DAO::executeQuery("UPDATE civicrm_contribution SET contribution_status_id={$status_pending}, is_pay_later=0 WHERE id={$contribution_id};");
         // now they will get their transactions back when they get set to 'completed' in the next step...
         error_log("org.project60.sepa: reset bad contribution [{$contribution_id}] to 'Pending'.");
     }
     // step 1.2: in CiviCRM before 4.4.4, the status 'In Progress' => 'Completed' was not allowed:
     if (version_compare(CRM_Utils_System::version(), '4.4.4', '<')) {
         // therefore, we change all these contributions' statuses back to 'Pending'
         $fix_status_query = "\n      UPDATE\n          civicrm_contribution\n      SET\n          contribution_status_id = {$status_pending},\n          is_pay_later = 0\n      WHERE \n          contribution_status_id = {$status_inprogress}\n      AND id IN (SELECT contribution_id FROM civicrm_sdd_contribution_txgroup WHERE txgroup_id={$txgroup_id});\n      ";
         CRM_Core_DAO::executeQuery($fix_status_query);
     }
     // step 2: update all the contributions
     $find_txgroup_contributions_sql = "\n    SELECT\n     contribution.id AS contribution_id\n    FROM\n      civicrm_sdd_contribution_txgroup AS txn_to_contribution\n    LEFT JOIN\n      civicrm_contribution AS contribution ON contribution.id = txn_to_contribution.contribution_id\n    WHERE\n      contribution_status_id != {$status_closed}\n    AND\n      txn_to_contribution.txgroup_id IN ({$txgroup_id});\n    ";
     $contribution = CRM_Core_DAO::executeQuery($find_txgroup_contributions_sql);
     $error_count = 0;
     while ($contribution->fetch()) {
         // update status for $contribution->contribution_id
         //   and set receive_date to collection_date (see https://github.com/Project60/sepa_dd/issues/190)
         $result = civicrm_api('Contribution', 'create', array('version' => 3, 'id' => $contribution->contribution_id, 'contribution_status_id' => $status_closed, 'receive_date' => date('YmdHis', strtotime($txgroup['collection_date']))));
         if (!empty($result['is_error'])) {
             $error_count += 1;
             error_log("org.project60.sepa: " . $result['error_message']);
         }
     }
     // step 3: update group status
     $result = civicrm_api('SepaTransactionGroup', 'create', array('id' => $txgroup_id, 'status_id' => $group_status_id_received, 'version' => 3));
     if (!empty($result['is_error'])) {
         $lock->release();
         return "Cannot update transaction group status for ID " . $txgroup_id;
     }
     // check if there was problems
     if ($error_count) {
         $lock->release();
         return "{$error_count} contributions could not be updated to status 'completed'.";
     }
     $lock->release();
 }
 /**
  * Maintenance: Close all mandates that have expired
  */
 static function closeEnded()
 {
     // check lock
     $lock = CRM_Sepa_Logic_Settings::getLock();
     if (empty($lock)) {
         return "Batching in progress. Please try again later.";
     }
     $contribution_status_closed = (int) CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name');
     // first, load all of the mandates, that have run out
     $sql_query = "\n      SELECT\n        mandate.id AS mandate_id,\n        mandate.date AS mandate_date,\n        mandate.entity_id AS mandate_entity_id,\n        mandate.creation_date AS mandate_creation_date,\n        mandate.validation_date AS mandate_validation_date,\n        rcontribution.end_date AS end_date\n      FROM civicrm_sdd_mandate AS mandate\n      INNER JOIN civicrm_contribution_recur AS rcontribution       ON mandate.entity_id = rcontribution.id\n      WHERE mandate.type = 'RCUR'\n        AND mandate.status IN ('RCUR','FRST')\n        AND end_date <= DATE(NOW());";
     $results = CRM_Core_DAO::executeQuery($sql_query);
     $mandates_to_end = array();
     while ($results->fetch()) {
         array_push($mandates_to_end, array('mandate_id' => $results->mandate_id, 'recur_id' => $results->mandate_entity_id, 'creation_date' => $results->mandate_creation_date, 'validation_date' => $results->mandate_validation_date, 'date' => $results->mandate_date));
     }
     // then, end them one by one
     foreach ($mandates_to_end as $mandate_to_end) {
         $change_mandate = civicrm_api('SepaMandate', 'create', array('id' => $mandate_to_end['mandate_id'], 'date' => $mandate_to_end['date'], 'creation_date' => $mandate_to_end['creation_date'], 'validation_date' => $mandate_to_end['validation_date'], 'status' => 'COMPLETE', 'version' => 3));
         if (isset($change_mandate['is_error']) && $change_mandate['is_error']) {
             $lock->release();
             return sprintf("Couldn't set mandate '%s' to 'complete. Error was: '%s'", $mandates_to_end['mandate_id'], $change_mandate['error_message']);
         }
         $change_rcur = civicrm_api('ContributionRecur', 'create', array('id' => $mandate_to_end['recur_id'], 'contribution_status_id' => $contribution_status_closed, 'modified_date' => date('YmdHis'), 'currency' => 'EUR', 'version' => 3));
         if (isset($change_rcur['is_error']) && $change_rcur['is_error']) {
             $lock->release();
             return sprintf("Couldn't set recurring contribution '%s' to 'complete. Error was: '%s'", $mandates_to_end['recur_id'], $change_rcur['error_message']);
         }
     }
     $lock->release();
 }