Skip to content

Commit e2e5e34

Browse files
author
Dominic Tubach
committed
Allow to accept/reject multiple drawdowns at once
1 parent f74cd42 commit e2e5e34

13 files changed

+739
-32
lines changed

Civi/Api4/FundingDrawdown.php

+10
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
namespace Civi\Api4;
55

66
use Civi\Funding\Api4\Action\FundingDrawdown\AcceptAction;
7+
use Civi\Funding\Api4\Action\FundingDrawdown\AcceptMultipleAction;
78
use Civi\Funding\Api4\Action\FundingDrawdown\CreateAction;
89
use Civi\Funding\Api4\Action\FundingDrawdown\GetAction;
910
use Civi\Funding\Api4\Action\FundingDrawdown\GetFieldsAction;
1011
use Civi\Funding\Api4\Action\FundingDrawdown\RejectAction;
12+
use Civi\Funding\Api4\Action\FundingDrawdown\RejectMultipleAction;
1113
use Civi\Funding\Api4\Action\FundingDrawdown\SaveAction;
1214
use Civi\Funding\Api4\Action\FundingDrawdown\UpdateAction;
1315
use Civi\Funding\Api4\Permissions;
@@ -30,6 +32,10 @@ public static function accept(bool $checkPermissions = TRUE): AcceptAction {
3032
return \Civi::service(AcceptAction::class)->setCheckPermissions($checkPermissions);
3133
}
3234

35+
public static function acceptMultiple(bool $checkPermissions = TRUE): AcceptMultipleAction {
36+
return (new AcceptMultipleAction())->setCheckPermissions($checkPermissions);
37+
}
38+
3339
public static function create($checkPermissions = TRUE) {
3440
return \Civi::service(CreateAction::class)->setCheckPermissions($checkPermissions);
3541
}
@@ -46,6 +52,10 @@ public static function reject(bool $checkPermissions = TRUE): RejectAction {
4652
return \Civi::service(RejectAction::class)->setCheckPermissions($checkPermissions);
4753
}
4854

55+
public static function rejectMultiple(bool $checkPermissions = TRUE): RejectMultipleAction {
56+
return (new RejectMultipleAction())->setCheckPermissions($checkPermissions);
57+
}
58+
4959
public static function save($checkPermissions = TRUE) {
5060
return \Civi::service(SaveAction::class)->setCheckPermissions($checkPermissions);
5161
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
/*
3+
* Copyright (C) 2025 SYSTOPIA GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published by
7+
* the Free Software Foundation in version 3.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Affero General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Affero General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
declare(strict_types = 1);
19+
20+
namespace Civi\Funding\Api4\Action\FundingDrawdown;
21+
22+
use Civi\Api4\FundingDrawdown;
23+
use Civi\Api4\Generic\AbstractAction;
24+
use Civi\Api4\Generic\Result;
25+
use Civi\Funding\Api4\Action\Traits\IdsParameterTrait;
26+
27+
class AcceptMultipleAction extends AbstractAction {
28+
29+
use IdsParameterTrait;
30+
31+
public function __construct() {
32+
parent::__construct(FundingDrawdown::getEntityName(), 'acceptMultiple');
33+
}
34+
35+
/**
36+
* @inheritDoc
37+
* @throws \CRM_Core_Exception
38+
*/
39+
public function _run(Result $result): void {
40+
foreach ($this->getIds() as $id) {
41+
$result[$id] = FundingDrawdown::accept()
42+
->setId($id)
43+
->execute()
44+
->single();
45+
}
46+
}
47+
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
/*
3+
* Copyright (C) 2025 SYSTOPIA GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published by
7+
* the Free Software Foundation in version 3.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Affero General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Affero General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
declare(strict_types = 1);
19+
20+
namespace Civi\Funding\Api4\Action\FundingDrawdown;
21+
22+
use Civi\Api4\FundingDrawdown;
23+
use Civi\Api4\Generic\AbstractAction;
24+
use Civi\Api4\Generic\Result;
25+
use Civi\Funding\Api4\Action\Traits\IdsParameterTrait;
26+
27+
class RejectMultipleAction extends AbstractAction {
28+
29+
use IdsParameterTrait;
30+
31+
public function __construct() {
32+
parent::__construct(FundingDrawdown::getEntityName(), 'rejectMultiple');
33+
}
34+
35+
/**
36+
* @inheritDoc
37+
* @throws \CRM_Core_Exception
38+
*/
39+
public function _run(Result $result): void {
40+
foreach ($this->getIds() as $id) {
41+
$result[$id] = FundingDrawdown::reject()
42+
->setId($id)
43+
->execute()
44+
->single();
45+
}
46+
}
47+
48+
}

Civi/Funding/Controller/DrawdownDocumentDownloadController.php

+60
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,18 @@
2323
use Civi\Funding\FundingAttachmentManagerInterface;
2424
use Civi\Funding\PayoutProcess\DrawdownManager;
2525
use CRM_Funding_ExtensionUtil as E;
26+
use setasign\Fpdi\Fpdi;
2627
use Symfony\Component\HttpFoundation\BinaryFileResponse;
28+
use Symfony\Component\HttpFoundation\HeaderUtils;
2729
use Symfony\Component\HttpFoundation\Request;
2830
use Symfony\Component\HttpFoundation\Response;
2931
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
3032
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
3133
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
3234
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
35+
use Tomsgu\PdfMerger\PdfCollection;
36+
use Tomsgu\PdfMerger\PdfFile;
37+
use Tomsgu\PdfMerger\PdfMerger;
3338

3439
final class DrawdownDocumentDownloadController implements PageControllerInterface {
3540

@@ -47,6 +52,19 @@ public function __construct(FundingAttachmentManagerInterface $attachmentManager
4752
* @throws \CRM_Core_Exception
4853
*/
4954
public function handle(Request $request): Response {
55+
if ($request->query->has('drawdownIds')) {
56+
$drawdownIds = (string) $request->query->get('drawdownIds');
57+
58+
if (preg_match('/^[1-9][0-9]*(,[1-9][0-9]*)*$/', $drawdownIds) !== 1) {
59+
throw new BadRequestHttpException('Invalid drawdown IDs');
60+
}
61+
62+
$drawdownIds = array_map(fn (string $id) => (int) $id, explode(',', $drawdownIds));
63+
/** @phpstan-var list<int> $drawdownIds */
64+
65+
return $this->downloadMultiple($drawdownIds);
66+
}
67+
5068
$drawdownId = $request->query->get('drawdownId');
5169

5270
if (!is_numeric($drawdownId)) {
@@ -92,4 +110,46 @@ private function download(int $drawdownId): Response {
92110
->setMaxAge(300);
93111
}
94112

113+
/**
114+
* @phpstan-param list<int> $drawdownIds
115+
*
116+
* @throws \CRM_Core_Exception
117+
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
118+
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
119+
*/
120+
private function downloadMultiple(array $drawdownIds): Response {
121+
$pdfCollection = new PdfCollection();
122+
foreach ($drawdownIds as $drawdownId) {
123+
$drawdown = $this->drawdownManager->get($drawdownId);
124+
if (NULL === $drawdown) {
125+
throw new AccessDeniedHttpException();
126+
}
127+
128+
$attachment = $this->attachmentManager->getLastByFileType(
129+
'civicrm_funding_drawdown',
130+
$drawdownId,
131+
$drawdown->getAmount() < 0 ? FileTypeNames::PAYBACK_CLAIM : FileTypeNames::PAYMENT_INSTRUCTION,
132+
);
133+
134+
if (NULL === $attachment) {
135+
throw new NotFoundHttpException("Drawdown document (ID: $drawdownId) does not exist");
136+
}
137+
138+
$pdfCollection->addPdf($attachment->getPath());
139+
}
140+
141+
$filename = E::ts('payment-instructions') . '.pdf';
142+
$headers = [
143+
'Content-Type' => 'application/pdf',
144+
'Content-Disposition' => HeaderUtils::makeDisposition(HeaderUtils::DISPOSITION_INLINE, $filename),
145+
];
146+
147+
$merger = new PdfMerger(new Fpdi());
148+
return (new Response(
149+
$merger->merge($pdfCollection, $filename, PdfMerger::MODE_STRING, PdfFile::ORIENTATION_AUTO_DETECT),
150+
200,
151+
$headers
152+
))->setMaxAge(300);
153+
}
154+
95155
}

Civi/Funding/Page/RemoteTransferContractDownloadPage.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
/**
1111
* @codeCoverageIgnore
1212
*/
13-
class RemoteTransferContractDownloadPage extends AbstractRemoteControllerPage {
13+
final class RemoteTransferContractDownloadPage extends AbstractRemoteControllerPage {
1414

1515
protected function getController(): PageControllerInterface {
1616
return \Civi::service(TransferContractDownloadController::class);

ang/afsearchDrawdowns.aff.html

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<div af-fieldset="">
2+
<div class="af-container af-layout-inline">
3+
<af-field name="creation_date" defn="{search_range: true, input_attrs: {}, required: false, input_type: 'Select'}" />
4+
<af-field name="status" defn="{required: false, input_attrs: {}}" />
5+
<af-field name="FundingDrawdown_FundingPayoutProcess_payout_process_id_01_FundingPayoutProcess_FundingCase_funding_case_id_01.recipient_contact_id" defn="{required: false, input_attrs: {}}" />
6+
<af-field name="FundingDrawdown_FundingPayoutProcess_payout_process_id_01_FundingPayoutProcess_FundingCase_funding_case_id_01.funding_program_id" defn="{input_type: 'Select', input_attrs: {multiple: true}, required: false}" />
7+
</div>
8+
<crm-search-display-table search-name="FundingDrawdownsAll" display-name="Table"></crm-search-display-table>
9+
</div>

ang/afsearchDrawdowns.aff.php

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
use CRM_Funding_ExtensionUtil as E;
3+
4+
return [
5+
'type' => 'search',
6+
'requires' => [
7+
'crmFunding',
8+
],
9+
'title' => E::ts('Drawdowns'),
10+
'icon' => 'fa-list-alt',
11+
'server_route' => 'civicrm/funding/drawdown/list',
12+
'permission' => [
13+
'access Funding',
14+
'administer Funding',
15+
],
16+
'permission_operator' => 'OR',
17+
'search_displays' => [
18+
'FundingDrawdownsAll.Table',
19+
],
20+
];

0 commit comments

Comments
 (0)