]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Repository/Contributions.php
Fix pagination; closes #1732
[galette.git] / galette / lib / Galette / Repository / Contributions.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Contributions class
7 *
8 * PHP version 5
9 *
10 * Copyright © 2010-2023 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 Repository
28 * @package Galette
29 *
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2010-2023 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.7dev - 2010-03-11
35 */
36
37 namespace Galette\Repository;
38
39 use ArrayObject;
40 use Throwable;
41 use Analog\Analog;
42 use Laminas\Db\Sql\Expression;
43 use Galette\Core\Db;
44 use Galette\Core\Login;
45 use Galette\Core\History;
46 use Galette\Entity\Contribution;
47 use Galette\Entity\Adherent;
48 use Galette\Entity\Transaction;
49 use Galette\Entity\ContributionsTypes;
50 use Galette\Filters\ContributionsList;
51 use Laminas\Db\Sql\Select;
52
53 /**
54 * Contributions class for galette
55 *
56 * @name Contributions
57 * @category Repository
58 * @package Galette
59 *
60 * @author Johan Cwiklinski <johan@x-tnd.be>
61 * @copyright 2009-2023 The Galette Team
62 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
63 * @link http://galette.tuxfamily.org
64 */
65 class Contributions
66 {
67 public const TABLE = Contribution::TABLE;
68 public const PK = Contribution::PK;
69
70 private $filters = false;
71 private $count = null;
72
73 private $zdb;
74 private $login;
75 private $sum;
76 private $current_selection;
77
78 /**
79 * Default constructor
80 *
81 * @param Db $zdb Database
82 * @param Login $login Login
83 * @param ContributionsList $filters Filtering
84 */
85 public function __construct(Db $zdb, Login $login, $filters = null)
86 {
87 $this->zdb = $zdb;
88 $this->login = $login;
89
90 if ($filters === null) {
91 $this->filters = new ContributionsList();
92 } else {
93 $this->filters = $filters;
94 }
95 }
96
97 /**
98 * Get contributions list for a specific transaction
99 *
100 * @param int $trans_id Transaction identifier
101 *
102 * @return Contribution[]|ArrayObject
103 */
104 public function getListFromTransaction($trans_id)
105 {
106 $this->filters->from_transaction = $trans_id;
107 return $this->getList(true);
108 }
109
110 /**
111 * Get contributions list for a specific transaction
112 *
113 * @param array $ids an array of members id that has been selected
114 * @param bool $as_contrib return the results as an array of
115 * @param array $fields field(s) name(s) to get. Should be a string or
116 * an array. If null, all fields will be
117 * returned
118 * @param boolean $count true if we want to count members
119 *
120 * @return Contribution[]|ArrayObject[]|false
121 */
122 public function getArrayList(array $ids, bool $as_contrib = false, array $fields = null, bool $count = true)
123 {
124 if (count($ids) < 1) {
125 Analog::log('No contribution selected.', Analog::INFO);
126 return false;
127 }
128
129 $this->current_selection = $ids;
130 $list = $this->getList($as_contrib, $fields, $count);
131 $array_list = [];
132 foreach ($list as $entry) {
133 $array_list[] = $entry;
134 }
135 return $array_list;
136 }
137
138 /**
139 * Get contributions list
140 *
141 * @param bool $as_contrib return the results as an array of
142 * Contribution object.
143 * @param array $fields field(s) name(s) to get. Should be a string or
144 * an array. If null, all fields will be
145 * returned
146 * @param boolean $count true if we want to count members
147 *
148 * @return Contribution[]|ArrayObject
149 */
150 public function getList($as_contrib = false, $fields = null, $count = true)
151 {
152 try {
153 $select = $this->buildSelect($fields, $count);
154
155 $this->filters->setLimits($select);
156
157 $contributions = array();
158 $results = $this->zdb->execute($select);
159 if ($as_contrib) {
160 foreach ($results as $row) {
161 $contributions[] = new Contribution($this->zdb, $this->login, $row);
162 }
163 } else {
164 $contributions = $results;
165 }
166 return $contributions;
167 } catch (Throwable $e) {
168 Analog::log(
169 'Cannot list contributions | ' . $e->getMessage(),
170 Analog::WARNING
171 );
172 throw $e;
173 }
174 }
175
176 /**
177 * Builds the SELECT statement
178 *
179 * @param array $fields fields list to retrieve
180 * @param bool $count true if we want to count members
181 * (not applicable from static calls), defaults to false
182 *
183 * @return string SELECT statement
184 */
185 private function buildSelect($fields, $count = false)
186 {
187 try {
188 $fieldsList = ($fields != null)
189 ? ((!is_array($fields) || count($fields) < 1) ? (array)'*'
190 : implode(', ', $fields)) : (array)'*';
191
192 $select = $this->zdb->select(self::TABLE, 'a');
193 $select->columns($fieldsList);
194
195 $select->join(
196 array('p' => PREFIX_DB . Adherent::TABLE),
197 'a.' . Adherent::PK . '= p.' . Adherent::PK,
198 array()
199 );
200
201 $this->buildWhereClause($select);
202 $select->order(self::buildOrderClause());
203
204 $this->calculateSum($select);
205
206 if ($count) {
207 $this->proceedCount($select);
208 }
209
210 return $select;
211 } catch (Throwable $e) {
212 Analog::log(
213 'Cannot build SELECT clause for contributions | ' . $e->getMessage(),
214 Analog::WARNING
215 );
216 throw $e;
217 }
218 }
219
220 /**
221 * Count contributions from the query
222 *
223 * @param Select $select Original select
224 *
225 * @return void
226 */
227 private function proceedCount(Select $select)
228 {
229 try {
230 $countSelect = clone $select;
231 $countSelect->reset($countSelect::COLUMNS);
232 $countSelect->reset($countSelect::JOINS);
233 $countSelect->reset($countSelect::ORDER);
234 $countSelect->columns(
235 array(
236 self::PK => new Expression('COUNT(' . self::PK . ')')
237 )
238 );
239
240 $results = $this->zdb->execute($countSelect);
241 $result = $results->current();
242
243 $k = self::PK;
244 $this->count = $result->$k;
245 $this->filters->setCounter($this->count);
246 } catch (Throwable $e) {
247 Analog::log(
248 'Cannot count contributions | ' . $e->getMessage(),
249 Analog::WARNING
250 );
251 throw $e;
252 }
253 }
254
255 /**
256 * Calculate sum of all selected contributions
257 *
258 * @param Select $select Original select
259 *
260 * @return void
261 */
262 private function calculateSum(Select $select)
263 {
264 try {
265 $sumSelect = clone $select;
266 $sumSelect->reset($sumSelect::COLUMNS);
267 $sumSelect->reset($sumSelect::JOINS);
268 $sumSelect->reset($sumSelect::ORDER);
269 $sumSelect->columns(
270 array(
271 'contribsum' => new Expression('SUM(montant_cotis)')
272 )
273 );
274
275 $results = $this->zdb->execute($sumSelect);
276 $result = $results->current();
277 if ($result->contribsum) {
278 $this->sum = round($result->contribsum, 2);
279 }
280 } catch (Throwable $e) {
281 Analog::log(
282 'Cannot calculate contributions sum | ' . $e->getMessage(),
283 Analog::WARNING
284 );
285 throw $e;
286 }
287 }
288
289 /**
290 * Builds the order clause
291 *
292 * @return array SQL ORDER clauses
293 */
294 private function buildOrderClause()
295 {
296 $order = array();
297
298 switch ($this->filters->orderby) {
299 case ContributionsList::ORDERBY_ID:
300 $order[] = Contribution::PK . ' ' . $this->filters->ordered;
301 break;
302 case ContributionsList::ORDERBY_DATE:
303 $order[] = 'date_enreg ' . $this->filters->ordered;
304 break;
305 case ContributionsList::ORDERBY_BEGIN_DATE:
306 $order[] = 'date_debut_cotis ' . $this->filters->ordered;
307 break;
308 case ContributionsList::ORDERBY_END_DATE:
309 $order[] = 'date_fin_cotis ' . $this->filters->ordered;
310 break;
311 case ContributionsList::ORDERBY_MEMBER:
312 $order[] = 'nom_adh ' . $this->filters->ordered;
313 $order[] = 'prenom_adh ' . $this->filters->ordered;
314 break;
315 case ContributionsList::ORDERBY_TYPE:
316 $order[] = ContributionsTypes::PK;
317 break;
318 case ContributionsList::ORDERBY_AMOUNT:
319 $order[] = 'montant_cotis ' . $this->filters->ordered;
320 break;
321 /*
322 Hum... I really do not know how to sort a query with a value that
323 is calculated code side :/
324 case ContributionsList::ORDERBY_DURATION:
325 break;*/
326 case ContributionsList::ORDERBY_PAYMENT_TYPE:
327 $order[] = 'type_paiement_cotis ' . $this->filters->ordered;
328 break;
329 default:
330 $order[] = $this->filters->orderby . ' ' . $this->filters->ordered;
331 break;
332 }
333
334 return $order;
335 }
336
337 /**
338 * Builds where clause, for filtering on simple list mode
339 *
340 * @param Select $select Original select
341 *
342 * @return void
343 */
344 private function buildWhereClause(Select $select)
345 {
346 $field = 'date_debut_cotis';
347
348 switch ($this->filters->date_field) {
349 case ContributionsList::DATE_RECORD:
350 $field = 'date_enreg';
351 break;
352 case ContributionsList::DATE_END:
353 $field = 'date_fin_cotis';
354 break;
355 case ContributionsList::DATE_BEGIN:
356 default:
357 $field = 'date_debut_cotis';
358 break;
359 }
360
361 if (isset($this->current_selection)) {
362 $select->where->in('a.' . self::PK, $this->current_selection);
363 }
364
365 try {
366 if ($this->filters->start_date_filter != null) {
367 $d = new \DateTime($this->filters->rstart_date_filter);
368 $select->where->greaterThanOrEqualTo(
369 $field,
370 $d->format('Y-m-d')
371 );
372 }
373
374 if ($this->filters->end_date_filter != null) {
375 $d = new \DateTime($this->filters->rend_date_filter);
376 $select->where->lessThanOrEqualTo(
377 $field,
378 $d->format('Y-m-d')
379 );
380 }
381
382 if ($this->filters->payment_type_filter !== null) {
383 $select->where->equalTo(
384 'type_paiement_cotis',
385 $this->filters->payment_type_filter
386 );
387 }
388
389 if ($this->filters->from_transaction !== false) {
390 $select->where->equalTo(
391 Transaction::PK,
392 $this->filters->from_transaction
393 );
394 }
395
396 if ($this->filters->max_amount !== null && is_int($this->filters->max_amount)) {
397 $select->where(
398 '(montant_cotis <= ' . $this->filters->max_amount .
399 ' OR montant_cotis IS NULL)'
400 );
401 }
402
403 $member_clause = null;
404 if ($this->filters->filtre_cotis_adh != null) {
405 $member_clause = [$this->filters->filtre_cotis_adh];
406 if (!$this->login->isAdmin() && !$this->login->isStaff() && $this->filters->filtre_cotis_adh != $this->login->id) {
407 $member = new Adherent(
408 $this->zdb,
409 (int)$this->filters->filtre_cotis_adh,
410 [
411 'picture' => false,
412 'groups' => false,
413 'dues' => false,
414 'parent' => true
415 ]
416 );
417 if (
418 !$member->hasParent() ||
419 $member->hasParent() && $member->parent->id != $this->login->id
420 ) {
421 Analog::log(
422 'Trying to display contributions for member #' . $member->id .
423 ' without appropriate ACLs',
424 Analog::WARNING
425 );
426 $this->filters->filtre_cotis_adh = $this->login->id;
427 $member_clause = [$this->login->id];
428 }
429 }
430 } elseif ($this->filters->filtre_cotis_children !== false) {
431 $member_clause = [$this->login->id];
432 $member = new Adherent(
433 $this->zdb,
434 (int)$this->filters->filtre_cotis_children,
435 [
436 'picture' => false,
437 'groups' => false,
438 'dues' => false,
439 'children' => true
440 ]
441 );
442 foreach ($member->children as $child) {
443 $member_clause[] = $child->id;
444 }
445 } elseif (!$this->login->isAdmin() && !$this->login->isStaff()) {
446 //non staff members can only view their own contributions
447 $member_clause = $this->login->id;
448 }
449
450 if ($member_clause !== null) {
451 $select->where(
452 array(
453 'a.' . Adherent::PK => $member_clause
454 )
455 );
456 }
457
458 if ($this->filters->filtre_transactions === true) {
459 $select->where('a.trans_id IS NULL');
460 }
461 } catch (Throwable $e) {
462 Analog::log(
463 __METHOD__ . ' | ' . $e->getMessage(),
464 Analog::WARNING
465 );
466 throw $e;
467 }
468 }
469
470 /**
471 * Get count for current query
472 *
473 * @return int
474 */
475 public function getCount()
476 {
477 return $this->count;
478 }
479
480 /**
481 * Get sum
482 *
483 * @return int
484 */
485 public function getSum()
486 {
487 return $this->sum;
488 }
489
490 /**
491 * Remove specified contributions
492 *
493 * @param integer|array $ids Contributions identifiers to delete
494 * @param History $hist History
495 * @param boolean $transaction True to begin a database transaction
496 *
497 * @return boolean
498 */
499 public function remove($ids, History $hist, $transaction = true)
500 {
501 $list = array();
502 if (is_numeric($ids)) {
503 //we've got only one identifier
504 $list[] = $ids;
505 } else {
506 $list = $ids;
507 }
508
509 if (is_array($list)) {
510 try {
511 if ($transaction) {
512 $this->zdb->connection->beginTransaction();
513 }
514 $select = $this->zdb->select(self::TABLE);
515 $select->where->in(self::PK, $list);
516 $contributions = $this->zdb->execute($select);
517 foreach ($contributions as $contribution) {
518 $c = new Contribution($this->zdb, $this->login, $contribution);
519 $res = $c->remove(false);
520 if ($res === false) {
521 throw new \Exception();
522 }
523 }
524 if ($transaction) {
525 $this->zdb->connection->commit();
526 }
527 $hist->add(
528 str_replace(
529 '%list',
530 print_r($list, true),
531 _T("Contributions deleted (%list)")
532 )
533 );
534 return true;
535 } catch (Throwable $e) {
536 if ($transaction) {
537 $this->zdb->connection->rollBack();
538 }
539 Analog::log(
540 'An error occurred trying to remove contributions | ' .
541 $e->getMessage(),
542 Analog::ERROR
543 );
544 throw $e;
545 }
546 } else {
547 //not numeric and not an array: incorrect.
548 Analog::log(
549 'Asking to remove contribution, but without providing an array or a single numeric value.',
550 Analog::WARNING
551 );
552 return false;
553 }
554 }
555 }