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

Support financial ACLs #731

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
2f87cc7
Refactor mandate contact tab queries to use APIv4 and check permissio…
jensschuppe Aug 20, 2024
c06afc0
Validate financial types when creating mandates
jensschuppe Aug 20, 2024
165af16
Add API4 Get action for SEPA mandates checking Financial ACLs
jensschuppe Aug 20, 2024
0d1de98
Do not evaluate $_REQUEST superglobal directly in CiviSEPA dashboard
jensschuppe Aug 22, 2024
d67ce17
Add and use API4 Get action for SEPA transaction groups checking Fina…
jensschuppe Aug 22, 2024
d694df2
Dashboard: filter for SEPA transaction groups the user has permission…
jensschuppe Aug 23, 2024
4a875cf
Use API4 for retrieving contributions of SEPA transaction groups, fil…
jensschuppe Aug 26, 2024
d4d95ed
Add `financial_type_id` field to SepaTransactionGroup entity
jensschuppe Sep 13, 2024
7407b42
Add a setting for grouping by financial types
jensschuppe Sep 13, 2024
dc6a302
Update OOFF transaction groups with financial type grouping
jensschuppe Sep 13, 2024
4ca9b65
Update RCUR transaction groups with financial type grouping
jensschuppe Sep 17, 2024
e80ed08
Use API4 SepaMandate.get for edit mandate page
jensschuppe Sep 18, 2024
c5b95c4
Use API4 SepaMandate.get for create mandate form
jensschuppe Sep 18, 2024
bef328d
Use API4 SepaMandate.get for Action Provider "FindMandate" action
jensschuppe Sep 18, 2024
5bfdfbd
Fix error in updating RCUR transaction groups
jensschuppe Sep 25, 2024
1ac233f
Replace various ocurrences of APIv3 calls to CiviSEPA entities with A…
jensschuppe Oct 1, 2024
053073c
Catch CRM_Core_Exception only when expected to be thrown by API4 sing…
jensschuppe Oct 9, 2024
ac24272
Code style issues
jensschuppe Oct 10, 2024
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
3 changes: 3 additions & 0 deletions CRM/Admin/Form/Setting/SepaSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ public function buildQuickForm( ) {

// look up some values
$async_batch = CRM_Sepa_Logic_Settings::getGenericSetting('sdd_async_batching');
$financial_type_grouping = CRM_Sepa_Logic_Settings::getGenericSetting('sdd_financial_type_grouping');
$skip_closed = CRM_Sepa_Logic_Settings::getGenericSetting('sdd_skip_closed');
$no_draftxml = CRM_Sepa_Logic_Settings::getGenericSetting('sdd_no_draft_xml');
$excld_we = CRM_Sepa_Logic_Settings::getGenericSetting('exclude_weekends');
Expand All @@ -157,6 +158,7 @@ public function buildQuickForm( ) {
$this->addElement('checkbox', 'is_test_creditor', E::ts("Is a Test Creditor"), "", array('value' =>'0'));
$this->addElement('checkbox', 'exclude_weekends', E::ts("Exclude Weekends"), "", ($excld_we?array('checked'=>'checked'):array()));
$this->addElement('checkbox', 'sdd_async_batching', E::ts("Large Groups"), "", ($async_batch?array('checked'=>'checked'):array()));
$this->addElement('checkbox', 'sdd_financial_type_grouping', E::ts('Groups by Financial Types'), "", ($financial_type_grouping?array('checked'=>'checked'):array()));
$this->addElement('checkbox', 'sdd_skip_closed', E::ts("Only Completed Contributions"), "", ($skip_closed?array('checked'=>'checked'):array()));
$this->addElement('checkbox', 'sdd_no_draft_xml', E::ts("No XML drafts"), "", ($no_draftxml?array('checked'=>'checked'):array()));
$this->addElement('text', 'pp_buffer_days', E::ts("Buffer Days"), array('size' => 2, 'value' => $bffrdays));
Expand Down Expand Up @@ -252,6 +254,7 @@ function postProcess() {

CRM_Sepa_Logic_Settings::setSetting((isset($values['exclude_weekends']) ? "1" : "0"), 'exclude_weekends');
CRM_Sepa_Logic_Settings::setSetting((isset($values['sdd_async_batching']) ? "1" : "0"), 'sdd_async_batching');
CRM_Sepa_Logic_Settings::setSetting((isset($values['sdd_financial_type_grouping']) ? "1" : "0"), 'sdd_financial_type_grouping');
CRM_Sepa_Logic_Settings::setSetting((isset($values['sdd_skip_closed']) ? "1" : "0"), 'sdd_skip_closed');
CRM_Sepa_Logic_Settings::setSetting((isset($values['sdd_no_draft_xml']) ? "1" : "0"), 'sdd_no_draft_xml');
CRM_Sepa_Logic_Settings::setSetting((isset($values['pp_buffer_days']) ? (int) $values['pp_buffer_days'] : "0"), 'pp_buffer_days');
Expand Down
36 changes: 27 additions & 9 deletions CRM/Sepa/BAO/SEPAMandate.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
*
*/

use CRM_Sepa_ExtensionUtil as E;

jensschuppe marked this conversation as resolved.
Show resolved Hide resolved
/**
* Class contains functions for Sepa mandates
Expand Down Expand Up @@ -48,9 +49,14 @@ static function add(&$params) {
// new mandate, use the default creditor
$default_creditor = CRM_Sepa_Logic_Settings::defaultCreditor();
$params['creditor_id'] = $default_creditor->id;
} else {
}
else {
// existing mandate, get creditor
$params['creditor_id'] = civicrm_api3('SepaMandate', 'getvalue', array ('id' => $params['id'], 'return' => 'creditor_id'));
$params['creditor_id'] = \Civi\Api4\SepaMandate::get(TRUE)
jensschuppe marked this conversation as resolved.
Show resolved Hide resolved
->addSelect('creditor_id')
->addWhere('id', '=', $params['id'])
->execute()
->single()['creditor_id'];
}
}
$creditor = civicrm_api3 ('SepaCreditor', 'getsingle', array ('id' => $params['creditor_id'], 'return' => 'mandate_prefix,creditor_type'));
Expand Down Expand Up @@ -371,16 +377,22 @@ static function terminateMandate($mandate_id, $new_end_date_str, $cancel_reason=
}

// first, load the mandate
$mandate = civicrm_api("SepaMandate", "getsingle", array('id'=>$mandate_id, 'version'=>3));
if (isset($mandate['is_error'])) {
try {
$mandate = \Civi\Api4\SepaMandate::get(TRUE)
->addWhere('id', '=', $mandate_id)
->execute()
->single();
}
catch (\CRM_Core_Exception $exception) {
$lock->release();

$error_message = sprintf(ts("Cannot read mandate [%s]. Error was: '%s'", array('domain' => 'org.project60.sepa')), $mandate_id, $mandate['error_message']);
$error_message = E::ts("Cannot read mandate [%1]. Error was: '%2'", [1 => $mandate_id, 2 => $exception->getMessage()]);
if ($error_to_ui) {
CRM_Core_Session::setStatus($error_message, ts('Error'), 'error');
return FALSE;
} else {
throw new Exception($error_message);
}
else {
throw new CRM_Core_Exception($error_message);
}
}

Expand Down Expand Up @@ -546,7 +558,10 @@ static function modifyMandate($mandate_id, $changes) {
}

// load the mandate
$mandate = civicrm_api3('SepaMandate', 'getsingle', array('id' => $mandate_id));
$mandate = \Civi\Api4\SepaMandate::get(TRUE)
->addWhere('id', '=', $mandate_id)
->execute()
->single();
if ($mandate['type'] != 'RCUR') {
$lock->release();
throw new Exception(ts("You can only modify RCUR mandates.", array('domain' => 'org.project60.sepa')));
Expand Down Expand Up @@ -906,7 +921,10 @@ public static function getLastMandateOfContact($cid) {
]);
if ($result->fetch()) {
// return the mandate
return civicrm_api3('SepaMandate', 'getsingle', array('id' => $result->mandate_id));
return \Civi\Api4\SepaMandate::get(TRUE)
->addWhere('id', '=', $result->mandate_id)
->execute()
->single();
}
else {
return FALSE;
Expand Down
39 changes: 28 additions & 11 deletions CRM/Sepa/BAO/SEPATransactionGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,13 @@ function generateXML ($id = null) {
* @return int id of the sepa file entity created, or an error message string
*/
static function createFile($txgroup_id, $override = false) {
$txgroup = civicrm_api('SepaTransactionGroup', 'getsingle', array('id'=>$txgroup_id, 'version'=>3));
if (isset($txgroup['is_error']) && $txgroup['is_error']) {
try {
$txgroup = \Civi\Api4\SepaTransactionGroup::get(TRUE)
->addWhere('id', '=', $txgroup_id)
->execute()
->single();
}
catch (CRM_Core_Exception $exception) {
return "Cannot find transaction group ".$txgroup_id;
}

Expand Down Expand Up @@ -252,7 +257,10 @@ static function createFile($txgroup_id, $override = false) {
* @return an update array with the txgroup or a string with an error message
*/
static function adjustCollectionDate($txgroup_id, $latest_submission_date) {
$txgroup = civicrm_api3('SepaTransactionGroup', 'getsingle', array('id' => $txgroup_id));
$txgroup = \Civi\Api4\SepaTransactionGroup::get(TRUE)
->addWhere('id', '=', $txgroup_id)
->execute()
->single();
if ($txgroup['type'] == 'RTRY') {
$txgroup['type'] = 'RCUR';
}
Expand All @@ -277,12 +285,16 @@ static function adjustCollectionDate($txgroup_id, $latest_submission_date) {
}

// reload the item
$txgroup = civicrm_api('SepaTransactionGroup', 'getsingle', array('version'=>3, 'id'=>$txgroup_id));
if (!empty($txgroup['is_error'])) {
return $txgroup['error_message'];
} else {
return $txgroup;
try {
$txgroup = \Civi\Api4\SepaTransactionGroup::get(TRUE)
->addWhere('id', '=', $txgroup_id)
->execute()
->single();
}
catch (CRM_Core_Exception $exception) {
return $exception->getMessage();
}
return $txgroup;
}


Expand All @@ -302,9 +314,14 @@ static function adjustCollectionDate($txgroup_id, $latest_submission_date) {
*/
static function deleteGroup($txgroup_id, $delete_contributions_mode = 'no') {
// load the group
$txgroup = civicrm_api('SepaTransactionGroup', 'getsingle', array('id' => $txgroup_id, 'version' => 3));
if (!empty($txgroup['is_error'])) {
return "Transaction group [$txgroup_id] could not be loaded. Error was: ".$txgroup['error_message'];
try {
$txgroup = \Civi\Api4\SepaTransactionGroup::get(TRUE)
->addWhere('id', '=', $txgroup_id)
->execute()
->single();
}
catch (\CRM_Core_Exception $exception) {
return "Transaction group [$txgroup_id] could not be loaded. Error was: " . $exception->getMessage();
}

// first, delete the contents of this group
Expand Down
29 changes: 29 additions & 0 deletions CRM/Sepa/DAO/SEPATransactionGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ class CRM_Sepa_DAO_SEPATransactionGroup extends CRM_Core_DAO {
*/
public $collection_date;

/**
* Financial type of contained contributions if CiviSEPA is generating groups
* matching financial types.
*
* @var int|string|null
* (SQL type: int unsigned)
* Note that values will be retrieved from the database as a string.
*/
public $financial_type_id;

/**
* Latest submission date
*
Expand Down Expand Up @@ -233,6 +243,25 @@ public static function &fields() {
'localizable' => 0,
'add' => NULL,
],
'financial_type_id' => [
'name' => 'financial_type_id',
'type' => CRM_Utils_Type::T_INT,
'title' => E::ts('Financial Type ID'),
'description' => E::ts('Financial type of contained contributions if CiviSEPA is generating groups matching financial types.'),
'required' => FALSE,
'usage' => [
'import' => FALSE,
'export' => FALSE,
'duplicate_matching' => FALSE,
'token' => FALSE,
],
'where' => 'civicrm_sdd_txgroup.financial_type_id',
'table_name' => 'civicrm_sdd_txgroup',
'entity' => 'SEPATransactionGroup',
'bao' => 'CRM_Sepa_DAO_SEPATransactionGroup',
'localizable' => 0,
'add' => NULL,
],
'latest_submission_date' => [
'name' => 'latest_submission_date',
'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME,
Expand Down
84 changes: 36 additions & 48 deletions CRM/Sepa/Form/CreateMandate.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
| copyright header is strictly prohibited without |
| written permission from the original author(s). |
+--------------------------------------------------------*/

use Civi\Sepa\Util\ContributionUtil;
use CRM_Sepa_ExtensionUtil as E;

/**
Expand Down Expand Up @@ -52,17 +54,29 @@ public function buildQuickForm() {
}

// load mandate
$this->old_mandate = civicrm_api3('SepaMandate', 'getsingle', array('id' => $mandate_id));
try {
// API4 SepaMandate.get checks Financial ACLs for corresponding (recurring) contribution.
$this->old_mandate = \Civi\Api4\SepaMandate::get(TRUE)
->addWhere('id', '=', $mandate_id)
->execute()
->single();
}
catch (\Exception $e) {
Civi::log()->error($e->getMessage());
CRM_Core_Error::statusBounce(
E::ts('The mandate to clone/replace from does not exist or you do not have permission for it.'),
);
}
if ($this->old_mandate['type'] != 'RCUR') {
CRM_Core_Error::fatal(E::ts("You can only replace RCUR mandates"));
CRM_Core_Error::statusBounce(E::ts('You can only replace RCUR mandates'));
}
$this->contact_id = (int) $this->old_mandate['contact_id'];

$this->old_contrib = civicrm_api3('ContributionRecur', 'getsingle', array('id' => $this->old_mandate['entity_id']));
}

if (empty($this->contact_id)) {
CRM_Core_Error::fatal(E::ts("No contact ID (cid) given."));
CRM_Core_Error::statusBounce(E::ts("No contact ID (cid) given."));
}

// load the contact and set the title
Expand Down Expand Up @@ -111,7 +125,7 @@ public function buildQuickForm() {
'select',
'payment_instrument_id',
E::ts('Payment Method'),
$this->getPaymentInstrumentList(),
ContributionUtil::getPaymentInstrumentList(),
TRUE,
array('class' => 'crm-select2')
);
Expand All @@ -121,7 +135,7 @@ public function buildQuickForm() {
'select',
'financial_type_id',
E::ts('Financial Type'),
$this->getFinancialTypeList(),
ContributionUtil::getFinancialTypeList(),
TRUE,
array('class' => 'crm-select2')
);
Expand Down Expand Up @@ -455,9 +469,11 @@ public function postProcess() {

try {
$mandate = civicrm_api3('SepaMandate', 'createfull', $mandate_data);
$mandate = civicrm_api3('SepaMandate', 'getsingle', array(
'id' => $mandate['id'],
'return' => 'reference,id,type'));
$mandate = \Civi\Api4\SepaMandate::get(TRUE)
->addSelect('reference', 'id', 'type')
->addWhere('id', '=', $mandate['id'])
->execute()
->single();

// if we get here, everything went o.k.
CRM_Core_Session::setStatus(E::ts("'%3' SEPA Mandate <a href=\"%2\">%1</a> created.", array(
Expand All @@ -469,7 +485,10 @@ public function postProcess() {

// terminate old mandate, of requested
if (!empty($values['replace'])) {
$rpl_mandate = civicrm_api3('SepaMandate', 'getsingle', array('id' => $values['replace']));
$rpl_mandate = \Civi\Api4\SepaMandate::get(TRUE)
->addWhere('id', '=', $values['replace'])
->execute()
->single();

CRM_Sepa_BAO_SEPAMandate::terminateMandate(
$values['replace'],
Expand Down Expand Up @@ -572,37 +591,6 @@ protected function getCreditorList($creditors) {
return $creditor_list;
}

/**
* Get the list of (active) financial types
*/
protected function getFinancialTypeList() {
$list = array();
$query = civicrm_api3('FinancialType', 'get',array(
'is_active' => 1,
'option.limit' => 0,
'return' => 'id,name'
));

foreach ($query['values'] as $value) {
$list[$value['id']] = $value['name'];
}

return $list;
}

/**
* Get the list of (CiviSEPA) payment instruments
*/
protected function getPaymentInstrumentList() {
$list = array();
$payment_instruments = CRM_Sepa_Logic_PaymentInstruments::getAllSddPaymentInstruments();
foreach ($payment_instruments as $payment_instrument) {
$list[$payment_instrument['id']] = $payment_instrument['label'];
}

return $list;
}

/**
* Get the list of (active) financial types
*/
Expand Down Expand Up @@ -631,14 +619,14 @@ protected function getKnownBankAccounts() {
$known_accounts = array('' => E::ts("new account"));

// get data from SepaMandates
$mandates = civicrm_api3('SepaMandate', 'get', array(
'contact_id' => $this->contact_id,
'status' => array('IN' => array('RCUR', 'COMPLETE', 'SENT')),
'option.limit' => 0,
'return' => 'iban,bic,reference',
'option.sort' => 'id desc'
));
foreach ($mandates['values'] as $mandate) {
// API4 SepaMandate.get checks Financial ACLs for corresponding (recurring) contribution.
$mandates = \Civi\Api4\SepaMandate::get(TRUE)
->addSelect('iban', 'bic', 'reference')
->addWhere('contact_id', '=', $this->contact_id)
->addWhere('status', 'IN', ['RCUR', 'COMPLETE', 'SENT'])
->addOrderBy('id', 'DESC')
->execute();
foreach ($mandates as $mandate) {
$key = "{$mandate['iban']}/{$mandate['bic']}";
if (!isset($known_accounts[$key])) {
$known_accounts[$key] = "{$mandate['iban']} ({$mandate['reference']})";
Expand Down
15 changes: 6 additions & 9 deletions CRM/Sepa/Form/RetryCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -221,15 +221,12 @@ protected function getCreditorList() {
* Get the list of creditors
*/
protected function getGroupList() {
$txgroup_list = array();
$txgroup_query = civicrm_api3('SepaTransactionGroup', 'get', array(
'option.limit' => 0,
'type' => array('IN' => array('RCUR', 'FRST')),
'return' => 'reference,id'));
foreach ($txgroup_query['values'] as $txgroup) {
$txgroup_list[$txgroup['id']] = $txgroup['reference'];
}
return $txgroup_list;
return \Civi\Api4\SepaTransactionGroup::get(TRUE)
->addSelect('reference', 'id')
->addWhere('type', 'IN', ['RCUR', 'FRST'])
->execute()
->indexBy('id')
->column('reference');
}

/*
Expand Down
Loading