]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Controllers/Crud/ContributionsController.php
Small rework and few typo fixes
[galette.git] / galette / lib / Galette / Controllers / Crud / ContributionsController.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Galette contributions controller
7 *
8 * PHP version 5
9 *
10 * Copyright © 2020-2021 The Galette Team
11 *
12 * This file is part of Galette (http://galette.tuxfamily.org).
13 *
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.
18 *
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.
23 *
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/>.
26 *
27 * @category Controllers
28 * @package Galette
29 *
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
35 */
36
37 namespace Galette\Controllers\Crud;
38
39 use Throwable;
40 use Galette\Controllers\CrudController;
41 use Slim\Http\Request;
42 use Slim\Http\Response;
43 use Galette\Entity\Adherent;
44 use Galette\Entity\Contribution;
45 use Galette\Entity\Transaction;
46 use Galette\Repository\Members;
47 use Galette\Entity\ContributionsTypes;
48 use Galette\Repository\PaymentTypes;
49
50 /**
51 * Galette contributions controller
52 *
53 * @category Controllers
54 * @name ContributionsController
55 * @package Galette
56 * @author Johan Cwiklinski <johan@x-tnd.be>
57 * @copyright 2020-2021 The Galette Team
58 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
59 * @link http://galette.tuxfamily.org
60 * @since Available since 0.9.4dev - 2020-05-02
61 */
62
63 class ContributionsController extends CrudController
64 {
65 // CRUD - Create
66
67 /**
68 * Add/Edit page
69 *
70 * Only a few things changes in add and edit pages,
71 * boths methods will use this common one.
72 *
73 * @param Request $request PSR Request
74 * @param Response $response PSR Response
75 * @param string $type Contribution type
76 * @param Contribution $contrib Contribution instance
77 *
78 * @return Response
79 */
80 public function addEditPage(
81 Request $request,
82 Response $response,
83 string $type,
84 Contribution $contrib
85 ): Response {
86 // contribution types
87 $ct = new ContributionsTypes($this->zdb);
88 $contributions_types = $ct->getList($type === Contribution::TYPE_FEE);
89
90 // template variable declaration
91 $title = null;
92 if ($type === Contribution::TYPE_FEE) {
93 $title = _T("Membership fee");
94 } else {
95 $title = _T("Donation");
96 }
97
98 if ($contrib->id != '') {
99 $title .= ' (' . _T("modification") . ')';
100 } else {
101 $title .= ' (' . _T("creation") . ')';
102 }
103
104 $params = [
105 'page_title' => $title,
106 'required' => $contrib->getRequired(),
107 'contribution' => $contrib,
108 'adh_selected' => $contrib->member,
109 'type' => $type
110 ];
111
112 // contribution types
113 $params['type_cotis_options'] = $contributions_types;
114
115 // members
116 $m = new Members();
117 $members = $m->getSelectizedMembers(
118 $this->zdb,
119 $contrib->member > 0 ? $contrib->member : null
120 );
121
122 $params['members'] = [
123 'filters' => $m->getFilters(),
124 'count' => $m->getCount()
125 ];
126
127 if (count($members)) {
128 $params['members']['list'] = $members;
129 }
130
131 $ext_membership = '';
132 if ($contrib->isFee() || !isset($contrib) && $type === Contribution::TYPE_FEE) {
133 $ext_membership = $this->preferences->pref_membership_ext;
134 }
135 $params['pref_membership_ext'] = $ext_membership;
136 $params['autocomplete'] = true;
137
138 // display page
139 $this->view->render(
140 $response,
141 'ajouter_contribution.tpl',
142 $params
143 );
144 return $response;
145 }
146
147 /**
148 * Add page
149 *
150 * @param Request $request PSR Request
151 * @param Response $response PSR Response
152 * @param string|null $type Contribution type
153 *
154 * @return Response
155 */
156 public function add(Request $request, Response $response, string $type = null): Response
157 {
158 if ($this->session->contribution !== null) {
159 $contrib = $this->session->contribution;
160 $this->session->contribution = null;
161 } else {
162 $get = $request->getQueryParams();
163
164 $ct = new ContributionsTypes($this->zdb);
165 $contributions_types = $ct->getList($type === Contribution::TYPE_FEE);
166
167 $cparams = ['type' => array_keys($contributions_types)[0]];
168
169 //member id
170 if (isset($get[Adherent::PK]) && $get[Adherent::PK] > 0) {
171 $cparams['adh'] = (int)$get[Adherent::PK];
172 }
173
174 //transaction id
175 if (isset($get[Transaction::PK]) && $get[Transaction::PK] > 0) {
176 $cparams['trans'] = $get[Transaction::PK];
177 }
178
179 $contrib = new Contribution(
180 $this->zdb,
181 $this->login,
182 (count($cparams) > 0 ? $cparams : null)
183 );
184
185 if (isset($cparams['adh'])) {
186 $contrib->member = $cparams['adh'];
187 }
188
189 if (isset($get['montant_cotis']) && $get['montant_cotis'] > 0) {
190 $contrib->amount = $get['montant_cotis'];
191 }
192 }
193
194 return $this->addEditPage($request, $response, $type, $contrib);
195 }
196
197 /**
198 * Add action
199 *
200 * @param Request $request PSR Request
201 * @param Response $response PSR Response
202 * @param string|null $type Contribution type
203 *
204 * @return Response
205 */
206 public function doAdd(Request $request, Response $response, string $type = null): Response
207 {
208 return $this->store($request, $response, 'add', $type);
209 }
210
211 /**
212 * Choose contribution type to mass add contribution
213 *
214 * @param Request $request PSR Request
215 * @param Response $response PSR Response
216 *
217 * @return Response
218 */
219 public function massAddChooseType(Request $request, Response $response): Response
220 {
221 $filters = $this->session->filter_members;
222 $data = [
223 'id' => $filters->selected,
224 'redirect_uri' => $this->router->pathFor('members')
225 ];
226
227 // display page
228 $this->view->render(
229 $response,
230 'mass_choose_type.tpl',
231 array(
232 'mode' => $request->isXhr() ? 'ajax' : '',
233 'page_title' => str_replace(
234 '%count',
235 count($data['id']),
236 _T('Mass add contribution on %count members')
237 ),
238 'data' => $data,
239 'form_url' => $this->router->pathFor('massAddContributions'),
240 'cancel_uri' => $this->router->pathFor('members')
241 )
242 );
243 return $response;
244 }
245
246 /**
247 * Massive change page
248 *
249 * @param Request $request PSR Request
250 * @param Response $response PSR Response
251 *
252 * @return Response
253 */
254 public function massAddContributions(Request $request, Response $response): Response
255 {
256 $post = $request->getParsedBody();
257 $filters = $this->session->filter_members;
258 $contribution = new Contribution($this->zdb, $this->login);
259
260 $type = $post['type'];
261 $data = [
262 'id' => $filters->selected,
263 'redirect_uri' => $this->router->pathFor('members'),
264 'type' => $type
265 ];
266
267 // contribution types
268 $ct = new ContributionsTypes($this->zdb);
269 $contributions_types = $ct->getList($type === Contribution::TYPE_FEE);
270
271 // display page
272 $this->view->render(
273 $response,
274 'mass_add_contribution.tpl',
275 array(
276 'mode' => $request->isXhr() ? 'ajax' : '',
277 'page_title' => str_replace(
278 '%count',
279 count($data['id']),
280 _T('Mass add contribution on %count members')
281 ),
282 'form_url' => $this->router->pathFor('doMassAddContributions'),
283 'cancel_uri' => $this->router->pathFor('members'),
284 'data' => $data,
285 'contribution' => $contribution,
286 'type' => $type,
287 'require_mass' => true,
288 'required' => $contribution->getRequired(),
289 'type_cotis_options' => $contributions_types
290 )
291 );
292 return $response;
293 }
294
295 /**
296 * Do massive contribution add
297 *
298 * @param Request $request PSR Request
299 * @param Response $response PSR Response
300 *
301 * @return Response
302 */
303 public function doMassAddContributions(Request $request, Response $response): Response
304 {
305 $post = $request->getParsedBody();
306 $members_ids = $post['id'];
307 unset($post['id']);
308
309 $error_detected = [];
310
311 // flagging required fields for first step only
312 $disabled = [];
313 $success = 0;
314 $errors = 0;
315
316 foreach ($members_ids as $member_id) {
317 $post[Adherent::PK] = (int)$member_id;
318 $contrib = new Contribution($this->zdb, $this->login);
319
320 // regular fields
321 $valid = $contrib->check($post, $contrib->getRequired(), $disabled);
322 if ($valid !== true) {
323 $error_detected = array_merge($error_detected, $valid);
324 }
325
326 //all goes well, we can proceed
327 if (count($error_detected) == 0) {
328 $store = $contrib->store();
329 if ($store === true) {
330 ++$success;
331 $files_res = $contrib->handleFiles($_FILES);
332 if (is_array($files_res)) {
333 $error_detected = array_merge($error_detected, $files_res);
334 }
335 } else {
336 ++$errors;
337 }
338 }
339 }
340
341 if (count($error_detected) == 0) {
342 $redirect_url = $this->router->pathFor('members');
343 } else {
344 //something went wrong.
345 //store entity in session
346 $redirect_url = $this->router->pathFor('massAddContributions');
347 //report errors
348 foreach ($error_detected as $error) {
349 $this->flash->addMessage(
350 'error_detected',
351 $error
352 );
353 }
354 }
355
356 //redirect to calling action
357 return $response
358 ->withStatus(301)
359 ->withHeader('Location', $redirect_url);
360 }
361
362 // /CRUD - Create
363 // CRUD - Read
364
365 /**
366 * List page
367 *
368 * @param Request $request PSR Request
369 * @param Response $response PSR Response
370 * @param string $option One of 'page' or 'order'
371 * @param string|integer $value Value of the option
372 * @param string $type One of 'transactions' or 'contributions'
373 *
374 * @return Response
375 */
376 public function list(Request $request, Response $response, $option = null, $value = null, $type = null): Response
377 {
378 $ajax = false;
379 $get = $request->getQueryParams();
380
381 if (
382 $request->isXhr()
383 || isset($get['ajax'])
384 && $get['ajax'] == 'true'
385 ) {
386 $ajax = true;
387 }
388
389 $raw_type = null;
390
391 switch ($type) {
392 case 'transactions':
393 $raw_type = 'transactions';
394 break;
395 case 'contributions':
396 $raw_type = 'contributions';
397 break;
398 }
399
400 $filter_name = 'filter_' . $raw_type;
401
402 if (isset($this->session->$filter_name) && $ajax === false) {
403 $filters = $this->session->$filter_name;
404 } else {
405 $filter_class = '\\Galette\\Filters\\' . ucwords($raw_type . 'List');
406 $filters = new $filter_class();
407 }
408
409 //member id
410 if (isset($get[Adherent::PK]) && $get[Adherent::PK] > 0) {
411 $filters->filtre_cotis_adh = (int)$get[Adherent::PK];
412 }
413
414 $filters->filtre_transactions = false;
415 if (isset($request->getQueryParams()['max_amount'])) {
416 $filters->filtre_transactions = true;
417 $filters->max_amount = (int)$request->getQueryParams()['max_amount'];
418 }
419
420 if ($option !== null) {
421 switch ($option) {
422 case 'page':
423 $filters->current_page = (int)$value;
424 break;
425 case 'order':
426 $filters->orderby = $value;
427 break;
428 case 'member':
429 if (
430 ($this->login->isAdmin()
431 || $this->login->isStaff())
432 ) {
433 if ($value == 'all') {
434 $filters->filtre_cotis_adh = null;
435 } else {
436 $filters->filtre_cotis_adh = $value;
437 }
438 }
439 break;
440 }
441 }
442
443 if (!$this->login->isAdmin() && !$this->login->isStaff()) {
444 $filters->filtre_cotis_adh = $this->login->id;
445 }
446
447 $class = '\\Galette\\Repository\\' . ucwords($raw_type);
448 $contrib = new $class($this->zdb, $this->login, $filters);
449 $contribs_list = $contrib->getList(true);
450
451 //store filters into session
452 if ($ajax === false) {
453 $this->session->$filter_name = $filters;
454 }
455
456 //assign pagination variables to the template and add pagination links
457 $filters->setSmartyPagination($this->router, $this->view->getSmarty());
458
459 $tpl_vars = [
460 'page_title' => $raw_type === 'contributions' ?
461 _T("Contributions management") : _T("Transactions management"),
462 'contribs' => $contrib,
463 'list' => $contribs_list,
464 'nb' => $contrib->getCount(),
465 'filters' => $filters,
466 'mode' => ($ajax === true ? 'ajax' : 'std')
467 ];
468
469 if ($filters->filtre_cotis_adh != null) {
470 $member = new Adherent($this->zdb);
471 $member->load($filters->filtre_cotis_adh);
472 $tpl_vars['member'] = $member;
473 }
474
475 // display page
476 $this->view->render(
477 $response,
478 'gestion_' . $raw_type . '.tpl',
479 $tpl_vars
480 );
481 return $response;
482 }
483
484 /**
485 * Filtering
486 *
487 * @param Request $request PSR Request
488 * @param Response $response PSR Response
489 * @param string|null $type Contribution type
490 *
491 * @return Response
492 */
493 public function filter(Request $request, Response $response, string $type = null): Response
494 {
495 $raw_type = null;
496 switch ($type) {
497 case 'transactions':
498 $raw_type = 'transactions';
499 break;
500 case 'contributions':
501 $raw_type = 'contributions';
502 break;
503 }
504
505 $type = 'filter_' . $raw_type;
506 $post = $request->getParsedBody();
507 $error_detected = [];
508
509 if ($this->session->$type !== null) {
510 $filters = $this->session->$type;
511 } else {
512 $filter_class = '\\Galette\\Filters\\' . ucwords($raw_type) . 'List';
513 $filters = new $filter_class();
514 }
515
516 if (isset($post['clear_filter'])) {
517 $filters->reinit();
518 } else {
519 if (isset($post['max_amount'])) {
520 $filters->max_amount = null;
521 }
522
523 if (
524 (isset($post['nbshow']) && is_numeric($post['nbshow']))
525 ) {
526 $filters->show = $post['nbshow'];
527 }
528
529 if (isset($post['end_date_filter']) || isset($post['start_date_filter'])) {
530 try {
531 if (isset($post['start_date_filter'])) {
532 $filters->start_date_filter = $post['start_date_filter'];
533 }
534 if (isset($post['end_date_filter'])) {
535 $filters->end_date_filter = $post['end_date_filter'];
536 }
537 } catch (Throwable $e) {
538 $error_detected[] = $e->getMessage();
539 }
540 }
541
542 if (isset($post['payment_type_filter'])) {
543 $ptf = (int)$post['payment_type_filter'];
544 $ptypes = new PaymentTypes(
545 $this->zdb,
546 $this->preferences,
547 $this->login
548 );
549 $ptlist = $ptypes->getList();
550 if (isset($ptlist[$ptf])) {
551 $filters->payment_type_filter = $ptf;
552 } elseif ($ptf == -1) {
553 $filters->payment_type_filter = null;
554 } else {
555 $error_detected[] = _T("- Unknown payment type!");
556 }
557 }
558 }
559
560 $this->session->$type = $filters;
561
562 if (count($error_detected) > 0) {
563 //report errors
564 foreach ($error_detected as $error) {
565 $this->flash->addMessage(
566 'error_detected',
567 $error
568 );
569 }
570 }
571
572 return $response
573 ->withStatus(301)
574 ->withHeader('Location', $this->router->pathFor('contributions', ['type' => $raw_type]));
575 }
576
577 // /CRUD - Read
578 // CRUD - Update
579
580 /**
581 * Edit page
582 *
583 * @param Request $request PSR Request
584 * @param Response $response PSR Response
585 * @param int $id Contribution id
586 * @param string|null $type Contribution type
587 *
588 * @return Response
589 */
590 public function edit(Request $request, Response $response, int $id, string $type = null): Response
591 {
592 if ($this->session->contribution !== null) {
593 $contrib = $this->session->contribution;
594 $this->session->contribution = null;
595 } else {
596 $contrib = new Contribution($this->zdb, $this->login, $id);
597 if ($contrib->id == '') {
598 //not possible to load contribution, exit
599 $this->flash->addMessage(
600 'error_detected',
601 str_replace(
602 '%id',
603 $id,
604 _T("Unable to load contribution #%id!")
605 )
606 );
607 return $response
608 ->withStatus(301)
609 ->withHeader('Location', $this->router->pathFor(
610 'contributions',
611 ['type' => 'contributions']
612 ));
613 }
614 }
615
616 return $this->addEditPage($request, $response, $type, $contrib);
617 }
618
619 /**
620 * Edit action
621 *
622 * @param Request $request PSR Request
623 * @param Response $response PSR Response
624 * @param integer $id Contribution id
625 * @param string|null $type Contribution type
626 *
627 * @return Response
628 */
629 public function doEdit(Request $request, Response $response, int $id, string $type = null): Response
630 {
631 return $this->store($request, $response, 'edit', $type, $id);
632 }
633
634 /**
635 * Store contribution (new or existing)
636 *
637 * @param Request $request PSR Request
638 * @param Response $response PSR Response
639 * @param string $action Action ('edit' or 'add')
640 * @param string $type Contribution type
641 * @param integer $id Contribution id
642 *
643 * @return Response
644 */
645 public function store(Request $request, Response $response, $action, string $type, $id = null): Response
646 {
647 $post = $request->getParsedBody();
648 $args = [
649 'action' => $action,
650 'type' => $type
651 ];
652 if ($id !== null) {
653 $args['id'] = $id;
654 }
655
656 if ($action == 'edit' && isset($post['btnreload'])) {
657 $redirect_url = $this->router->pathFor($action . 'Contribution', $args);
658 $redirect_url .= '?' . Adherent::PK . '=' . $post[Adherent::PK] . '&' .
659 ContributionsTypes::PK . '=' . $post[ContributionsTypes::PK] . '&' .
660 'montant_cotis=' . $post['montant_cotis'];
661 return $response
662 ->withStatus(301)
663 ->withHeader('Location', $redirect_url);
664 }
665
666 $error_detected = [];
667
668 if ($this->session->contribution !== null) {
669 $contrib = $this->session->contribution;
670 $this->session->contribution = null;
671 } else {
672 if ($id === null) {
673 $contrib = new Contribution($this->zdb, $this->login);
674 } else {
675 $contrib = new Contribution($this->zdb, $this->login, $id);
676 }
677 }
678
679 $disabled = [];
680
681 // regular fields
682 $valid = $contrib->check($post, $contrib->getRequired(), $disabled);
683 if ($valid !== true) {
684 $error_detected = array_merge($error_detected, $valid);
685 }
686
687 //all goes well, we can proceed
688 if (count($error_detected) == 0) {
689 // send email to member
690 if (isset($post['mail_confirm']) && $post['mail_confirm'] == '1') {
691 $contrib->setSendmail(); //flag to send creation email
692 }
693
694 $store = $contrib->store();
695 if ($store === true) {
696 $this->flash->addMessage(
697 'success_detected',
698 _T('Contribution has been successfully stored')
699 );
700 } else {
701 //something went wrong :'(
702 $error_detected[] = _T("An error occurred while storing the contribution.");
703 }
704 }
705
706 if (count($error_detected) === 0) {
707 $files_res = $contrib->handleFiles($_FILES);
708 if (is_array($files_res)) {
709 $error_detected = array_merge($error_detected, $files_res);
710 }
711 }
712
713 if (count($error_detected) == 0) {
714 $this->session->contribution = null;
715 if ($contrib->isTransactionPart() && $contrib->transaction->getMissingAmount() > 0) {
716 //new contribution
717 $redirect_url = $this->router->pathFor(
718 'addContribution',
719 [
720 'type' => $post['contrib_type']
721 ]
722 ) . '?' . Transaction::PK . '=' . $contrib->transaction->id .
723 '&' . Adherent::PK . '=' . $contrib->member;
724 } else {
725 //contributions list for member
726 $redirect_url = $this->router->pathFor(
727 'contributions',
728 [
729 'type' => 'contributions'
730 ]
731 ) . '?' . Adherent::PK . '=' . $contrib->member;
732 }
733 } else {
734 //something went wrong.
735 //store entity in session
736 $this->session->contribution = $contrib;
737 $redirect_url = $this->router->pathFor($action . 'Contribution', $args);
738
739 //report errors
740 foreach ($error_detected as $error) {
741 $this->flash->addMessage(
742 'error_detected',
743 $error
744 );
745 }
746 }
747
748 //redirect to calling action
749 return $response
750 ->withStatus(301)
751 ->withHeader('Location', $redirect_url);
752 }
753
754 // /CRUD - Update
755 // CRUD - Delete
756
757 /**
758 * Get redirection URI
759 *
760 * @param array $args Route arguments
761 *
762 * @return string
763 */
764 public function redirectUri(array $args)
765 {
766 return $this->router->pathFor('contributions', ['type' => $args['type']]);
767 }
768
769 /**
770 * Get form URI
771 *
772 * @param array $args Route arguments
773 *
774 * @return string
775 */
776 public function formUri(array $args)
777 {
778 return $this->router->pathFor(
779 'doRemoveContribution',
780 $args
781 );
782 }
783
784 /**
785 * Get confirmation removal page title
786 *
787 * @param array $args Route arguments
788 *
789 * @return string
790 */
791 public function confirmRemoveTitle(array $args)
792 {
793 $raw_type = null;
794
795 switch ($args['type']) {
796 case 'transactions':
797 $raw_type = 'transactions';
798 break;
799 case 'contributions':
800 $raw_type = 'contributions';
801 break;
802 }
803
804 if (isset($args['ids'])) {
805 return sprintf(
806 _T('Remove %1$s %2$s'),
807 count($args['ids']),
808 ($raw_type === 'contributions') ? _T('contributions') : _T('transactions')
809 );
810 } else {
811 return sprintf(
812 _T('Remove %1$s #%2$s'),
813 ($raw_type === 'contributions') ? _T('contribution') : _T('transaction'),
814 $args['id']
815 );
816 }
817 }
818
819 /**
820 * Remove object
821 *
822 * @param array $args Route arguments
823 * @param array $post POST values
824 *
825 * @return boolean
826 */
827 protected function doDelete(array $args, array $post)
828 {
829 $raw_type = null;
830 switch ($args['type']) {
831 case 'transactions':
832 $raw_type = 'transactions';
833 break;
834 case 'contributions':
835 $raw_type = 'contributions';
836 break;
837 }
838
839 $class = '\\Galette\Repository\\' . ucwords($raw_type);
840 $contribs = new $class($this->zdb, $this->login);
841 $rm = $contribs->remove($args['ids'] ?? $args['id'], $this->history);
842 return $rm;
843 }
844
845 // /CRUD - Delete
846 // /CRUD
847 }