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
;
41 use Galette\Controllers\CrudController
;
42 use Slim\Http\Request
;
43 use Slim\Http\Response
;
44 use Galette\Entity\Adherent
;
45 use Galette\Entity\Contribution
;
46 use Galette\Entity\Transaction
;
47 use Galette\Repository\Members
;
48 use Galette\Entity\ContributionsTypes
;
49 use Galette\Repository\PaymentTypes
;
52 * Galette contributions controller
54 * @category Controllers
55 * @name ContributionsController
57 * @author Johan Cwiklinski <johan@x-tnd.be>
58 * @copyright 2020-2021 The Galette Team
59 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
60 * @link http://galette.tuxfamily.org
61 * @since Available since 0.9.4dev - 2020-05-02
64 class ContributionsController
extends CrudController
71 * Only a few things changes in add and edit pages,
72 * boths methods will use this common one.
74 * @param Request $request PSR Request
75 * @param Response $response PSR Response
76 * @param string $type Contribution type
77 * @param Contribution $contrib Contribution instance
81 public function addEditPage(
88 $ct = new ContributionsTypes($this->zdb
);
89 $contributions_types = $ct->getList($type === Contribution
::TYPE_FEE
);
91 // template variable declaration
93 if ($type === Contribution
::TYPE_FEE
) {
94 $title = _T("Membership fee");
96 $title = _T("Donation");
99 if ($contrib->id
!= '') {
100 $title .= ' (' . _T("modification") . ')';
102 $title .= ' (' . _T("creation") . ')';
106 'page_title' => $title,
107 'required' => $contrib->getRequired(),
108 'contribution' => $contrib,
109 'adh_selected' => $contrib->member
,
113 // contribution types
114 $params['type_cotis_options'] = $contributions_types;
118 $members = $m->getSelectizedMembers(
120 $contrib->member
> 0 ?
$contrib->member
: null
123 $params['members'] = [
124 'filters' => $m->getFilters(),
125 'count' => $m->getCount()
128 if (count($members)) {
129 $params['members']['list'] = $members;
132 $ext_membership = '';
133 if ($contrib->isFee() ||
!isset($contrib) && $type === Contribution
::TYPE_FEE
) {
134 $ext_membership = $this->preferences
->pref_membership_ext
;
136 $params['pref_membership_ext'] = $ext_membership;
137 $params['autocomplete'] = true;
142 'ajouter_contribution.tpl',
151 * @param Request $request PSR Request
152 * @param Response $response PSR Response
153 * @param string|null $type Contribution type
157 public function add(Request
$request, Response
$response, string $type = null): Response
159 if ($this->session
->contribution
!== null) {
160 $contrib = $this->session
->contribution
;
161 $this->session
->contribution
= null;
163 $get = $request->getQueryParams();
165 $ct = new ContributionsTypes($this->zdb
);
166 $contributions_types = $ct->getList($type === Contribution
::TYPE_FEE
);
168 $cparams = ['type' => array_keys($contributions_types)[0]];
171 if (isset($get[Adherent
::PK
]) && $get[Adherent
::PK
] > 0) {
172 $cparams['adh'] = (int)$get[Adherent
::PK
];
176 if (isset($get[Transaction
::PK
]) && $get[Transaction
::PK
] > 0) {
177 $cparams['trans'] = $get[Transaction
::PK
];
180 $contrib = new Contribution(
183 (count($cparams) > 0 ?
$cparams : null)
186 if (isset($cparams['adh'])) {
187 $contrib->member
= $cparams['adh'];
190 if (isset($get['montant_cotis']) && $get['montant_cotis'] > 0) {
191 $contrib->amount
= $get['montant_cotis'];
195 return $this->addEditPage($request, $response, $type, $contrib);
201 * @param Request $request PSR Request
202 * @param Response $response PSR Response
203 * @param string|null $type Contribution type
207 public function doAdd(Request
$request, Response
$response, string $type = null): Response
209 return $this->store($request, $response, 'add', $type);
213 * Choose contribution type to mass add contribution
215 * @param Request $request PSR Request
216 * @param Response $response PSR Response
220 public function massAddChooseType(Request
$request, Response
$response): Response
222 $filters = $this->session
->filter_members
;
224 'id' => $filters->selected
,
225 'redirect_uri' => $this->router
->pathFor('members')
231 'mass_choose_type.tpl',
233 'mode' => $request->isXhr() ?
'ajax' : '',
234 'page_title' => str_replace(
237 _T('Mass add contribution on %count members')
240 'form_url' => $this->router
->pathFor('massAddContributions'),
241 'cancel_uri' => $this->router
->pathFor('members')
248 * Massive change page
250 * @param Request $request PSR Request
251 * @param Response $response PSR Response
255 public function massAddContributions(Request
$request, Response
$response): Response
257 $post = $request->getParsedBody();
258 $filters = $this->session
->filter_members
;
259 $contribution = new Contribution($this->zdb
, $this->login
);
261 $type = $post['type'];
263 'id' => $filters->selected
,
264 'redirect_uri' => $this->router
->pathFor('members'),
268 // contribution types
269 $ct = new ContributionsTypes($this->zdb
);
270 $contributions_types = $ct->getList($type === Contribution
::TYPE_FEE
);
275 'mass_add_contribution.tpl',
277 'mode' => $request->isXhr() ?
'ajax' : '',
278 'page_title' => str_replace(
281 _T('Mass add contribution on %count members')
283 'form_url' => $this->router
->pathFor('doMassAddContributions'),
284 'cancel_uri' => $this->router
->pathFor('members'),
286 'contribution' => $contribution,
288 'require_mass' => true,
289 'required' => $contribution->getRequired(),
290 'type_cotis_options' => $contributions_types
297 * Do massive contribution add
299 * @param Request $request PSR Request
300 * @param Response $response PSR Response
304 public function doMassAddContributions(Request
$request, Response
$response): Response
306 $post = $request->getParsedBody();
307 $members_ids = $post['id'];
310 $error_detected = [];
312 // flagging required fields for first step only
317 foreach ($members_ids as $member_id) {
318 $post[Adherent
::PK
] = (int)$member_id;
319 $contrib = new Contribution($this->zdb
, $this->login
);
322 $valid = $contrib->check($post, $contrib->getRequired(), $disabled);
323 if ($valid !== true) {
324 $error_detected = array_merge($error_detected, $valid);
327 //all goes well, we can proceed
328 if (count($error_detected) == 0) {
329 $store = $contrib->store();
330 if ($store === true) {
332 $files_res = $contrib->handleFiles($_FILES);
333 if (is_array($files_res)) {
334 $error_detected = array_merge($error_detected, $files_res);
342 if (count($error_detected) == 0) {
343 $redirect_url = $this->router
->pathFor('members');
345 //something went wrong.
346 //store entity in session
347 $redirect_url = $this->router
->pathFor('massAddContributions');
349 foreach ($error_detected as $error) {
350 $this->flash
->addMessage(
357 //redirect to calling action
360 ->withHeader('Location', $redirect_url);
369 * @param Request $request PSR Request
370 * @param Response $response PSR Response
371 * @param string $option One of 'page' or 'order'
372 * @param string|integer $value Value of the option
373 * @param string $type One of 'transactions' or 'contributions'
377 public function list(Request
$request, Response
$response, $option = null, $value = null, $type = null): Response
380 $get = $request->getQueryParams();
384 ||
isset($get['ajax'])
385 && $get['ajax'] == 'true'
392 $raw_type = 'transactions';
394 case 'contributions':
395 $raw_type = 'contributions';
399 'Trying to load unknown contribution type ' . $type,
406 $this->router
->pathFor('me')
410 $filter_name = 'filter_' . $raw_type;
412 if (isset($this->session
->$filter_name) && $ajax === false) {
413 $filters = $this->session
->$filter_name;
415 $filter_class = '\\Galette\\Filters\\' . ucwords($raw_type . 'List');
416 $filters = new $filter_class();
420 if (isset($get[Adherent
::PK
]) && $get[Adherent
::PK
] > 0) {
421 $filters->filtre_cotis_adh
= (int)$get[Adherent
::PK
];
424 $filters->filtre_transactions
= false;
425 if (isset($request->getQueryParams()['max_amount'])) {
426 $filters->filtre_transactions
= true;
427 $filters->max_amount
= (int)$request->getQueryParams()['max_amount'];
430 if ($option !== null) {
433 $filters->current_page
= (int)$value;
436 $filters->orderby
= $value;
439 $filters->filtre_cotis_adh
= ($value === 'all' ?
null : $value);
444 if (!$this->login
->isAdmin() && !$this->login
->isStaff() && $value != $this->login
->id
) {
445 if ($value === 'all') {
446 $value = $this->login
->id
;
448 $member = new Adherent(
459 !$member->hasParent() ||
460 $member->hasParent() && $member->parent
->id
!= $this->login
->id
462 $value = $this->login
->id
;
464 'Trying to display contributions for member #' . $value .
465 ' without appropriate ACLs',
470 $filters->filtre_cotis_children
= $value;
473 $class = '\\Galette\\Entity\\' . ucwords(trim($raw_type, 's'));
474 $contrib = new $class($this->zdb
, $this->login
);
476 if (!$contrib->canShow($this->login
)) {
478 'Trying to display contributions without appropriate ACLs',
485 $this->router
->pathFor('me')
489 $class = '\\Galette\\Repository\\' . ucwords($raw_type);
490 $contrib = new $class($this->zdb
, $this->login
, $filters);
491 $contribs_list = $contrib->getList(true);
493 //store filters into session
494 if ($ajax === false) {
495 $this->session
->$filter_name = $filters;
498 //assign pagination variables to the template and add pagination links
499 $filters->setSmartyPagination($this->router
, $this->view
->getSmarty());
502 'page_title' => $raw_type === 'contributions' ?
503 _T("Contributions management") : _T("Transactions management"),
504 'contribs' => $contrib,
505 'list' => $contribs_list,
506 'nb' => $contrib->getCount(),
507 'filters' => $filters,
508 'mode' => ($ajax === true ?
'ajax' : 'std')
511 if ($filters->filtre_cotis_adh
!= null) {
512 $member = new Adherent($this->zdb
);
513 $member->load($filters->filtre_cotis_adh
);
514 $tpl_vars['member'] = $member;
517 if ($filters->filtre_cotis_children
!= false) {
518 $member = new Adherent(
520 $filters->filtre_cotis_children
,
528 $tpl_vars['pmember'] = $member;
534 'gestion_' . $raw_type . '.tpl',
543 * @param Request $request PSR Request
544 * @param Response $response PSR Response
545 * @param string|null $type Contribution type
549 public function filter(Request
$request, Response
$response, string $type = null): Response
554 $raw_type = 'transactions';
556 case 'contributions':
557 $raw_type = 'contributions';
561 $type = 'filter_' . $raw_type;
562 $post = $request->getParsedBody();
563 $error_detected = [];
565 if ($this->session
->$type !== null) {
566 $filters = $this->session
->$type;
568 $filter_class = '\\Galette\\Filters\\' . ucwords($raw_type) . 'List';
569 $filters = new $filter_class();
572 if (isset($post['clear_filter'])) {
575 if (isset($post['max_amount'])) {
576 $filters->max_amount
= null;
580 (isset($post['nbshow']) && is_numeric($post['nbshow']))
582 $filters->show
= $post['nbshow'];
585 if (isset($post['end_date_filter']) ||
isset($post['start_date_filter'])) {
587 if (isset($post['start_date_filter'])) {
588 $filters->start_date_filter
= $post['start_date_filter'];
590 if (isset($post['end_date_filter'])) {
591 $filters->end_date_filter
= $post['end_date_filter'];
593 } catch (Throwable
$e) {
594 $error_detected[] = $e->getMessage();
598 if (isset($post['payment_type_filter'])) {
599 $ptf = (int)$post['payment_type_filter'];
600 $ptypes = new PaymentTypes(
605 $ptlist = $ptypes->getList();
606 if (isset($ptlist[$ptf])) {
607 $filters->payment_type_filter
= $ptf;
608 } elseif ($ptf == -1) {
609 $filters->payment_type_filter
= null;
611 $error_detected[] = _T("- Unknown payment type!");
616 $this->session
->$type = $filters;
618 if (count($error_detected) > 0) {
620 foreach ($error_detected as $error) {
621 $this->flash
->addMessage(
630 ->withHeader('Location', $this->router
->pathFor('contributions', ['type' => $raw_type]));
639 * @param Request $request PSR Request
640 * @param Response $response PSR Response
641 * @param int $id Contribution id
642 * @param string|null $type Contribution type
646 public function edit(Request
$request, Response
$response, int $id, string $type = null): Response
648 if ($this->session
->contribution
!== null) {
649 $contrib = $this->session
->contribution
;
650 $this->session
->contribution
= null;
652 $contrib = new Contribution($this->zdb
, $this->login
, $id);
653 if ($contrib->id
== '') {
654 //not possible to load contribution, exit
655 $this->flash
->addMessage(
660 _T("Unable to load contribution #%id!")
665 ->withHeader('Location', $this->router
->pathFor(
667 ['type' => 'contributions']
672 return $this->addEditPage($request, $response, $type, $contrib);
678 * @param Request $request PSR Request
679 * @param Response $response PSR Response
680 * @param integer $id Contribution id
681 * @param string|null $type Contribution type
685 public function doEdit(Request
$request, Response
$response, int $id, string $type = null): Response
687 return $this->store($request, $response, 'edit', $type, $id);
691 * Store contribution (new or existing)
693 * @param Request $request PSR Request
694 * @param Response $response PSR Response
695 * @param string $action Action ('edit' or 'add')
696 * @param string $type Contribution type
697 * @param integer $id Contribution id
701 public function store(Request
$request, Response
$response, $action, string $type, $id = null): Response
703 $post = $request->getParsedBody();
712 if ($action == 'edit' && isset($post['btnreload'])) {
713 $redirect_url = $this->router
->pathFor($action . 'Contribution', $args);
714 $redirect_url .= '?' . Adherent
::PK
. '=' . $post[Adherent
::PK
] . '&' .
715 ContributionsTypes
::PK
. '=' . $post[ContributionsTypes
::PK
] . '&' .
716 'montant_cotis=' . $post['montant_cotis'];
719 ->withHeader('Location', $redirect_url);
722 $error_detected = [];
724 if ($this->session
->contribution
!== null) {
725 $contrib = $this->session
->contribution
;
726 $this->session
->contribution
= null;
729 $contrib = new Contribution($this->zdb
, $this->login
);
731 $contrib = new Contribution($this->zdb
, $this->login
, $id);
738 $valid = $contrib->check($post, $contrib->getRequired(), $disabled);
739 if ($valid !== true) {
740 $error_detected = array_merge($error_detected, $valid);
743 //all goes well, we can proceed
744 if (count($error_detected) == 0) {
745 // send email to member
746 if (isset($post['mail_confirm']) && $post['mail_confirm'] == '1') {
747 $contrib->setSendmail(); //flag to send creation email
750 $store = $contrib->store();
751 if ($store === true) {
752 $this->flash
->addMessage(
754 _T('Contribution has been successfully stored')
757 //something went wrong :'(
758 $error_detected[] = _T("An error occurred while storing the contribution.");
762 if (count($error_detected) === 0) {
763 $files_res = $contrib->handleFiles($_FILES);
764 if (is_array($files_res)) {
765 $error_detected = array_merge($error_detected, $files_res);
769 if (count($error_detected) == 0) {
770 $this->session
->contribution
= null;
771 if ($contrib->isTransactionPart() && $contrib->transaction
->getMissingAmount() > 0) {
773 $redirect_url = $this->router
->pathFor(
776 'type' => $post['contrib_type']
778 ) . '?' . Transaction
::PK
. '=' . $contrib->transaction
->id
.
779 '&' . Adherent
::PK
. '=' . $contrib->member
;
781 //contributions list for member
782 $redirect_url = $this->router
->pathFor(
785 'type' => 'contributions'
787 ) . '?' . Adherent
::PK
. '=' . $contrib->member
;
790 //something went wrong.
791 //store entity in session
792 $this->session
->contribution
= $contrib;
793 $redirect_url = $this->router
->pathFor($action . 'Contribution', $args);
796 foreach ($error_detected as $error) {
797 $this->flash
->addMessage(
804 //redirect to calling action
807 ->withHeader('Location', $redirect_url);
814 * Get redirection URI
816 * @param array $args Route arguments
820 public function redirectUri(array $args)
822 return $this->router
->pathFor('contributions', ['type' => $args['type']]);
828 * @param array $args Route arguments
832 public function formUri(array $args)
834 return $this->router
->pathFor(
835 'doRemoveContribution',
841 * Get confirmation removal page title
843 * @param array $args Route arguments
847 public function confirmRemoveTitle(array $args)
851 switch ($args['type']) {
853 $raw_type = 'transactions';
855 case 'contributions':
856 $raw_type = 'contributions';
860 if (isset($args['ids'])) {
862 _T('Remove %1$s %2$s'),
864 ($raw_type === 'contributions') ?
_T('contributions') : _T('transactions')
868 _T('Remove %1$s #%2$s'),
869 ($raw_type === 'contributions') ?
_T('contribution') : _T('transaction'),
878 * @param array $args Route arguments
879 * @param array $post POST values
883 protected function doDelete(array $args, array $post)
886 switch ($args['type']) {
888 $raw_type = 'transactions';
890 case 'contributions':
891 $raw_type = 'contributions';
895 $class = '\\Galette\Repository\\' . ucwords($raw_type);
896 $contribs = new $class($this->zdb
, $this->login
);
897 $rm = $contribs->remove($args['ids'] ??
$args['id'], $this->history
);