3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
6 * Galette contributions controller
10 * Copyright © 2020-2021 The Galette Team
12 * This file is part of Galette (http://galette.tuxfamily.org).
14 * Galette is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation, either version 3 of the License, or
17 * (at your option) any later version.
19 * Galette is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
27 * @category Controllers
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2020-2021 The Galette Team
32 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
33 * @link http://galette.tuxfamily.org
34 * @since Available since 0.9.4dev - 2020-05-08
37 namespace Galette\Controllers\Crud
;
39 use Galette\Filters\ContributionsList
;
42 use Galette\Controllers\CrudController
;
43 use Slim\Http\Request
;
44 use Slim\Http\Response
;
45 use Galette\Entity\Adherent
;
46 use Galette\Entity\Contribution
;
47 use Galette\Entity\Transaction
;
48 use Galette\Repository\Members
;
49 use Galette\Entity\ContributionsTypes
;
50 use Galette\Repository\PaymentTypes
;
53 * Galette contributions controller
55 * @category Controllers
56 * @name ContributionsController
58 * @author Johan Cwiklinski <johan@x-tnd.be>
59 * @copyright 2020-2021 The Galette Team
60 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
61 * @link http://galette.tuxfamily.org
62 * @since Available since 0.9.4dev - 2020-05-02
65 class ContributionsController
extends CrudController
72 * Only a few things changes in add and edit pages,
73 * boths methods will use this common one.
75 * @param Request $request PSR Request
76 * @param Response $response PSR Response
77 * @param string $type Contribution type
78 * @param Contribution $contrib Contribution instance
82 public function addEditPage(
89 $ct = new ContributionsTypes($this->zdb
);
90 $contributions_types = $ct->getList($type === Contribution
::TYPE_FEE
);
92 // template variable declaration
94 if ($type === Contribution
::TYPE_FEE
) {
95 $title = _T("Membership fee");
97 $title = _T("Donation");
100 if ($contrib->id
!= '') {
101 $title .= ' (' . _T("modification") . ')';
103 $title .= ' (' . _T("creation") . ')';
107 'page_title' => $title,
108 'required' => $contrib->getRequired(),
109 'contribution' => $contrib,
110 'adh_selected' => $contrib->member
,
114 // contribution types
115 $params['type_cotis_options'] = $contributions_types;
119 $members = $m->getSelectizedMembers(
121 $contrib->member
> 0 ?
$contrib->member
: null
124 $params['members'] = [
125 'filters' => $m->getFilters(),
126 'count' => $m->getCount()
129 if (count($members)) {
130 $params['members']['list'] = $members;
133 $ext_membership = '';
134 if ($contrib->isFee() ||
!isset($contrib) && $type === Contribution
::TYPE_FEE
) {
135 $ext_membership = $this->preferences
->pref_membership_ext
;
137 $params['pref_membership_ext'] = $ext_membership;
138 $params['autocomplete'] = true;
143 'ajouter_contribution.tpl',
152 * @param Request $request PSR Request
153 * @param Response $response PSR Response
154 * @param string|null $type Contribution type
158 public function add(Request
$request, Response
$response, string $type = null): Response
160 if ($this->session
->contribution
!== null) {
161 $contrib = $this->session
->contribution
;
162 $this->session
->contribution
= null;
164 $get = $request->getQueryParams();
166 $ct = new ContributionsTypes($this->zdb
);
167 $contributions_types = $ct->getList($type === Contribution
::TYPE_FEE
);
169 $cparams = ['type' => array_keys($contributions_types)[0]];
172 if (isset($get[Adherent
::PK
]) && $get[Adherent
::PK
] > 0) {
173 $cparams['adh'] = (int)$get[Adherent
::PK
];
177 if (isset($get[Transaction
::PK
]) && $get[Transaction
::PK
] > 0) {
178 $cparams['trans'] = $get[Transaction
::PK
];
181 $contrib = new Contribution(
184 (count($cparams) > 0 ?
$cparams : null)
187 if (isset($cparams['adh'])) {
188 $contrib->member
= $cparams['adh'];
191 if (isset($get['montant_cotis']) && $get['montant_cotis'] > 0) {
192 $contrib->amount
= $get['montant_cotis'];
196 return $this->addEditPage($request, $response, $type, $contrib);
202 * @param Request $request PSR Request
203 * @param Response $response PSR Response
204 * @param string|null $type Contribution type
208 public function doAdd(Request
$request, Response
$response, string $type = null): Response
210 return $this->store($request, $response, 'add', $type);
214 * Choose contribution type to mass add contribution
216 * @param Request $request PSR Request
217 * @param Response $response PSR Response
221 public function massAddChooseType(Request
$request, Response
$response): Response
223 $filters = $this->session
->filter_members
;
225 'id' => $filters->selected
,
226 'redirect_uri' => $this->router
->pathFor('members')
232 'mass_choose_type.tpl',
234 'mode' => $request->isXhr() ?
'ajax' : '',
235 'page_title' => str_replace(
238 _T('Mass add contribution on %count members')
241 'form_url' => $this->router
->pathFor('massAddContributions'),
242 'cancel_uri' => $this->router
->pathFor('members')
249 * Massive change page
251 * @param Request $request PSR Request
252 * @param Response $response PSR Response
256 public function massAddContributions(Request
$request, Response
$response): Response
258 $post = $request->getParsedBody();
259 $filters = $this->session
->filter_members
;
260 $contribution = new Contribution($this->zdb
, $this->login
);
262 $type = $post['type'];
264 'id' => $filters->selected
,
265 'redirect_uri' => $this->router
->pathFor('members'),
269 // contribution types
270 $ct = new ContributionsTypes($this->zdb
);
271 $contributions_types = $ct->getList($type === Contribution
::TYPE_FEE
);
276 'mass_add_contribution.tpl',
278 'mode' => $request->isXhr() ?
'ajax' : '',
279 'page_title' => str_replace(
282 _T('Mass add contribution on %count members')
284 'form_url' => $this->router
->pathFor('doMassAddContributions'),
285 'cancel_uri' => $this->router
->pathFor('members'),
287 'contribution' => $contribution,
289 'require_mass' => true,
290 'required' => $contribution->getRequired(),
291 'type_cotis_options' => $contributions_types
298 * Do massive contribution add
300 * @param Request $request PSR Request
301 * @param Response $response PSR Response
305 public function doMassAddContributions(Request
$request, Response
$response): Response
307 $post = $request->getParsedBody();
308 $members_ids = $post['id'];
311 $error_detected = [];
313 // flagging required fields for first step only
318 foreach ($members_ids as $member_id) {
319 $post[Adherent
::PK
] = (int)$member_id;
320 $contrib = new Contribution($this->zdb
, $this->login
);
323 $valid = $contrib->check($post, $contrib->getRequired(), $disabled);
324 if ($valid !== true) {
325 $error_detected = array_merge($error_detected, $valid);
328 //all goes well, we can proceed
329 if (count($error_detected) == 0) {
330 $store = $contrib->store();
331 if ($store === true) {
333 $files_res = $contrib->handleFiles($_FILES);
334 if (is_array($files_res)) {
335 $error_detected = array_merge($error_detected, $files_res);
343 if (count($error_detected) == 0) {
344 $redirect_url = $this->router
->pathFor('members');
346 //something went wrong.
347 //store entity in session
348 $redirect_url = $this->router
->pathFor('massAddContributions');
350 foreach ($error_detected as $error) {
351 $this->flash
->addMessage(
358 //redirect to calling action
361 ->withHeader('Location', $redirect_url);
370 * @param Request $request PSR Request
371 * @param Response $response PSR Response
372 * @param string $option One of 'page' or 'order'
373 * @param string|integer $value Value of the option
374 * @param string $type One of 'transactions' or 'contributions'
378 public function list(Request
$request, Response
$response, $option = null, $value = null, $type = null): Response
381 $get = $request->getQueryParams();
385 ||
isset($get['ajax'])
386 && $get['ajax'] == 'true'
393 $raw_type = 'transactions';
395 case 'contributions':
396 $raw_type = 'contributions';
400 'Trying to load unknown contribution type ' . $type,
407 $this->router
->pathFor('me')
411 $filter_name = 'filter_' . $raw_type;
413 if (isset($this->session
->$filter_name) && $ajax === false) {
414 $filters = $this->session
->$filter_name;
416 $filter_class = '\\Galette\\Filters\\' . ucwords($raw_type . 'List');
417 $filters = new $filter_class();
421 if (isset($get[Adherent
::PK
]) && $get[Adherent
::PK
] > 0) {
422 $filters->filtre_cotis_adh
= (int)$get[Adherent
::PK
];
425 $filters->filtre_transactions
= false;
426 if (isset($request->getQueryParams()['max_amount'])) {
427 $filters->filtre_transactions
= true;
428 $filters->max_amount
= (int)$request->getQueryParams()['max_amount'];
431 if ($option !== null) {
434 $filters->current_page
= (int)$value;
437 $filters->orderby
= $value;
440 $filters->filtre_cotis_adh
= ($value === 'all' ?
null : $value);
445 if (!$this->login
->isAdmin() && !$this->login
->isStaff() && $value != $this->login
->id
) {
446 if ($value === 'all' ||
empty($value)) {
447 $value = $this->login
->id
;
449 $member = new Adherent(
460 !$member->hasParent() ||
461 $member->hasParent() && $member->parent
->id
!= $this->login
->id
463 $value = $this->login
->id
;
465 'Trying to display contributions for member #' . $value .
466 ' without appropriate ACLs',
471 $filters->filtre_cotis_children
= $value;
474 $class = '\\Galette\\Entity\\' . ucwords(trim($raw_type, 's'));
475 $contrib = new $class($this->zdb
, $this->login
);
477 if (!$contrib->canShow($this->login
)) {
479 'Trying to display contributions without appropriate ACLs',
486 $this->router
->pathFor('me')
490 $class = '\\Galette\\Repository\\' . ucwords($raw_type);
491 $contrib = new $class($this->zdb
, $this->login
, $filters);
492 $contribs_list = $contrib->getList(true);
494 //store filters into session
495 if ($ajax === false) {
496 $this->session
->$filter_name = $filters;
499 //assign pagination variables to the template and add pagination links
500 $filters->setSmartyPagination($this->router
, $this->view
->getSmarty());
503 'page_title' => $raw_type === 'contributions' ?
504 _T("Contributions management") : _T("Transactions management"),
505 'contribs' => $contrib,
506 'list' => $contribs_list,
507 'nb' => $contrib->getCount(),
508 'filters' => $filters,
509 'mode' => ($ajax === true ?
'ajax' : 'std')
512 if ($filters->filtre_cotis_adh
!= null) {
513 $member = new Adherent($this->zdb
);
514 $member->load($filters->filtre_cotis_adh
);
515 $tpl_vars['member'] = $member;
518 if ($filters->filtre_cotis_children
!= false) {
519 $member = new Adherent(
521 $filters->filtre_cotis_children
,
529 $tpl_vars['pmember'] = $member;
535 'gestion_' . $raw_type . '.tpl',
544 * @param Request $request PSR Request
545 * @param Response $response PSR Response
546 * @param string|null $type One of 'transactions' or 'contributions'
550 public function filter(Request
$request, Response
$response, string $type = null): Response
552 $filter_name = 'filter_' . $type;
553 $post = $request->getParsedBody();
554 $error_detected = [];
556 if ($this->session
->$filter_name !== null) {
557 $filters = $this->session
->$filter_name;
559 $filter_class = '\\Galette\\Filters\\' . ucwords($type) . 'List';
560 $filters = new $filter_class();
563 if (isset($post['clear_filter'])) {
566 if (isset($post['max_amount'])) {
567 $filters->max_amount
= null;
571 (isset($post['nbshow']) && is_numeric($post['nbshow']))
573 $filters->show
= $post['nbshow'];
576 if (isset($post['end_date_filter']) ||
isset($post['start_date_filter'])) {
578 if (isset($post['start_date_filter'])) {
579 $filters->start_date_filter
= $post['start_date_filter'];
581 if (isset($post['end_date_filter'])) {
582 $filters->end_date_filter
= $post['end_date_filter'];
584 } catch (Throwable
$e) {
585 $error_detected[] = $e->getMessage();
589 if (isset($post['payment_type_filter'])) {
590 $ptf = (int)$post['payment_type_filter'];
591 $ptypes = new PaymentTypes(
596 $ptlist = $ptypes->getList();
597 if (isset($ptlist[$ptf])) {
598 $filters->payment_type_filter
= $ptf;
599 } elseif ($ptf == -1) {
600 $filters->payment_type_filter
= null;
602 $error_detected[] = _T("- Unknown payment type!");
607 $this->session
->$filter_name = $filters;
609 if (count($error_detected) > 0) {
611 foreach ($error_detected as $error) {
612 $this->flash
->addMessage(
621 ->withHeader('Location', $this->router
->pathFor('contributions', ['type' => $type]));
625 * Batch actions handler
627 * @param Request $request PSR Request
628 * @param Response $response PSR Response
629 * @param string $type One of 'transactions' or 'contributions'
633 public function handleBatch(Request
$request, Response
$response, string $type): Response
635 $filter_name = 'filter_' . $type;
636 $post = $request->getParsedBody();
638 if (isset($post['contrib_sel'])) {
639 if (isset($this->session
->$filter_name)) {
640 $filters = $this->session
->$filter_name;
642 $filters = new ContributionsList();
645 $filters->selected
= $post['contrib_sel'];
646 $this->session
->$filter_name = $filters;
648 if (isset($post['csv'])) {
651 ->withHeader('Location', $this->router
->pathFor('csv-contributionslist', ['type' => $type]));
654 if (isset($post['delete'])) {
657 ->withHeader('Location', $this->router
->pathFor('removeContributions'));
660 throw new \
RuntimeException('Does not know what to batch :(');
662 $this->flash
->addMessage(
664 _T("No contribution was selected, please check at least one.")
669 ->withHeader('Location', $this->router
->pathFor('contributions', ['type' => $type]));
679 * @param Request $request PSR Request
680 * @param Response $response PSR Response
681 * @param int $id Contribution id
682 * @param string|null $type Contribution type
686 public function edit(Request
$request, Response
$response, int $id, string $type = null): Response
688 if ($this->session
->contribution
!== null) {
689 $contrib = $this->session
->contribution
;
690 $this->session
->contribution
= null;
692 $contrib = new Contribution($this->zdb
, $this->login
, $id);
693 if ($contrib->id
== '') {
694 //not possible to load contribution, exit
695 $this->flash
->addMessage(
700 _T("Unable to load contribution #%id!")
705 ->withHeader('Location', $this->router
->pathFor(
707 ['type' => 'contributions']
712 return $this->addEditPage($request, $response, $type, $contrib);
718 * @param Request $request PSR Request
719 * @param Response $response PSR Response
720 * @param integer $id Contribution id
721 * @param string|null $type Contribution type
725 public function doEdit(Request
$request, Response
$response, int $id, string $type = null): Response
727 return $this->store($request, $response, 'edit', $type, $id);
731 * Store contribution (new or existing)
733 * @param Request $request PSR Request
734 * @param Response $response PSR Response
735 * @param string $action Action ('edit' or 'add')
736 * @param string $type Contribution type
737 * @param integer $id Contribution id
741 public function store(Request
$request, Response
$response, $action, string $type, $id = null): Response
743 $post = $request->getParsedBody();
752 if ($action == 'edit' && isset($post['btnreload'])) {
753 $redirect_url = $this->router
->pathFor($action . 'Contribution', $args);
754 $redirect_url .= '?' . Adherent
::PK
. '=' . $post[Adherent
::PK
] . '&' .
755 ContributionsTypes
::PK
. '=' . $post[ContributionsTypes
::PK
] . '&' .
756 'montant_cotis=' . $post['montant_cotis'];
759 ->withHeader('Location', $redirect_url);
762 $error_detected = [];
764 if ($this->session
->contribution
!== null) {
765 $contrib = $this->session
->contribution
;
766 $this->session
->contribution
= null;
769 $contrib = new Contribution($this->zdb
, $this->login
);
771 $contrib = new Contribution($this->zdb
, $this->login
, $id);
778 $valid = $contrib->check($post, $contrib->getRequired(), $disabled);
779 if ($valid !== true) {
780 $error_detected = array_merge($error_detected, $valid);
783 //all goes well, we can proceed
784 if (count($error_detected) == 0) {
785 // send email to member
786 if (isset($post['mail_confirm']) && $post['mail_confirm'] == '1') {
787 $contrib->setSendmail(); //flag to send creation email
790 $store = $contrib->store();
791 if ($store === true) {
792 $this->flash
->addMessage(
794 _T('Contribution has been successfully stored')
797 //something went wrong :'(
798 $error_detected[] = _T("An error occurred while storing the contribution.");
802 if (count($error_detected) === 0) {
803 $files_res = $contrib->handleFiles($_FILES);
804 if (is_array($files_res)) {
805 $error_detected = array_merge($error_detected, $files_res);
809 if (count($error_detected) == 0) {
810 $this->session
->contribution
= null;
811 if ($contrib->isTransactionPart() && $contrib->transaction
->getMissingAmount() > 0) {
813 $redirect_url = $this->router
->pathFor(
816 'type' => $post['contrib_type']
818 ) . '?' . Transaction
::PK
. '=' . $contrib->transaction
->id
.
819 '&' . Adherent
::PK
. '=' . $contrib->member
;
821 //contributions list for member
822 $redirect_url = $this->router
->pathFor(
825 'type' => 'contributions'
827 ) . '?' . Adherent
::PK
. '=' . $contrib->member
;
830 //something went wrong.
831 //store entity in session
832 $this->session
->contribution
= $contrib;
833 $redirect_url = $this->router
->pathFor($action . 'Contribution', $args);
836 foreach ($error_detected as $error) {
837 $this->flash
->addMessage(
844 //redirect to calling action
847 ->withHeader('Location', $redirect_url);
854 * Get redirection URI
856 * @param array $args Route arguments
860 public function redirectUri(array $args)
862 return $this->router
->pathFor('contributions', ['type' => $args['type']]);
868 * @param array $args Route arguments
872 public function formUri(array $args)
874 return $this->router
->pathFor(
875 'doRemoveContribution',
881 * Get confirmation removal page title
883 * @param array $args Route arguments
887 public function confirmRemoveTitle(array $args)
891 switch ($args['type']) {
893 $raw_type = 'transactions';
895 case 'contributions':
896 $raw_type = 'contributions';
900 if (isset($args['ids'])) {
902 _T('Remove %1$s %2$s'),
904 ($raw_type === 'contributions') ?
_T('contributions') : _T('transactions')
908 _T('Remove %1$s #%2$s'),
909 ($raw_type === 'contributions') ?
_T('contribution') : _T('transaction'),
918 * @param array $args Route arguments
919 * @param array $post POST values
923 protected function doDelete(array $args, array $post)
926 switch ($args['type']) {
928 $raw_type = 'transactions';
930 case 'contributions':
931 $raw_type = 'contributions';
935 $class = '\\Galette\Repository\\' . ucwords($raw_type);
936 $contribs = new $class($this->zdb
, $this->login
);
937 $rm = $contribs->remove($args['ids'] ??
$args['id'], $this->history
);