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