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