/**
* Contribution class for galette
+ * Manage membership fees and donations.
*
* PHP version 5
*
- * Copyright © 2010-2014 The Galette Team
+ * Copyright © 2010-2023 The Galette Team
*
* This file is part of Galette (http://galette.tuxfamily.org).
*
* @package Galette
*
* @author Johan Cwiklinski <johan@x-tnd.be>
- * @copyright 2010-2014 The Galette Team
+ * @copyright 2010-2023 The Galette Team
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
- * @version SVN: $Id$
* @link http://galette.tuxfamily.org
* @since Available since 0.7dev - 2010-03-11
*/
namespace Galette\Entity;
+use ArrayObject;
+use DateTime;
+use Galette\Events\GaletteEvent;
+use Galette\Features\HasEvent;
+use Throwable;
use Analog\Analog;
-use Zend\Db\Sql\Expression;
+use Laminas\Db\Sql\Expression;
use Galette\Core\Db;
use Galette\Core\Login;
use Galette\IO\ExternalScript;
use Galette\IO\PdfContribution;
+use Galette\Repository\PaymentTypes;
+use Galette\Features\Dynamics;
/**
* Contribution class for galette
+ * Manage membership fees and donations.
*
* @category Entity
* @name Contribution
* @package Galette
* @author Johan Cwiklinski <johan@x-tnd.be>
- * @copyright 2010-2014 The Galette Team
+ * @copyright 2010-2023 The Galette Team
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
* @link http://galette.tuxfamily.org
* @since Available since 0.7dev - 2010-03-11
+ *
+ * @property integer $id
+ * @property string $date
+ * @property DateTime $raw_date
+ * @property integer $member
+ * @property ContributionsTypes|int $type
+ * @property double $amount
+ * @property integer $payment_type
+ * @property double $orig_amount
+ * @property string $info
+ * @property string $begin_date
+ * @property DateTime $raw_begin_date
+ * @property string $end_date
+ * @property DateTime $raw_end_date
+ * @property Transaction|null $transaction
+ * @property integer $extension
+ * @property integer $duration
+ * @property string $spayment_type
+ * @property integer $model
*/
class Contribution
{
- use DynamicsTrait;
+ use Dynamics;
+ use HasEvent;
- const TABLE = 'cotisations';
- const PK = 'id_cotis';
+ public const TABLE = 'cotisations';
+ public const PK = 'id_cotis';
- const PAYMENT_OTHER = 0;
- const PAYMENT_CASH = 1;
- const PAYMENT_CREDITCARD = 2;
- const PAYMENT_CHECK = 3;
- const PAYMENT_TRANSFER = 4;
- const PAYMENT_PAYPAL = 5;
+ public const TYPE_FEE = 'fee';
+ public const TYPE_DONATION = 'donation';
private $_id;
private $_date;
//fields list and their translation
private $_fields;
+ /** @var Db */
private $zdb;
+ /** @var Login */
private $login;
-
+ /** @var array */
private $errors;
+ private $sendmail = false;
+
/**
* Default constructor
*
- * @param Db $zdb Database
- * @param Login $login Login instance
- * @param null|int|ResultSet $args Either a ResultSet row to load
- * a specific contribution, or an type id
- * to just instanciate object
+ * @param Db $zdb Database
+ * @param Login $login Login instance
+ * @param null|int|array|ArrayObject $args Either a ResultSet row to load
+ * a specific contribution, or a type id
+ * to just instantiate object
*/
public function __construct(Db $zdb, Login $login, $args = null)
{
$this->zdb = $zdb;
$this->login = $login;
+ global $preferences;
+ $this->_payment_type = (int)$preferences->pref_default_paymenttype;
+
+ $this
+ ->withAddEvent()
+ ->withEditEvent()
+ ->withoutDeleteEvent()
+ ->activateEvents();
+
/*
* Fields configuration. Each field is an array and must reflect:
* array(
* (string)label,
- * (string) propname
+ * (string) property name
* )
*
* I'd prefer a static private variable for this...
*/
$this->_fields = array(
'id_cotis' => array(
- 'label' => null, //not a field in the form
+ 'label' => _T('Contribution id'), //not a field in the form
'propname' => 'id'
),
Adherent::PK => array(
'propname' => 'info'
),
'date_enreg' => array(
- 'label' => null, //not a field in the form
+ 'label' => _T('Date'), //not a field in the form
'propname' => 'date'
),
'date_debut_cotis' => array(
'label' => _T("Date of contribution:"),
- 'cotlabel' => _T("Start date of membership:"), //if contribution is a cotisation, label differs
+ 'cotlabel' => _T("Start date of membership:"), //if contribution is a membership fee, label differs
'propname' => 'begin_date'
),
'date_fin_cotis' => array(
'propname' => 'end_date'
),
Transaction::PK => array(
- 'label' => null, //not a field in the form
+ 'label' => _T('Transaction ID'), //not a field in the form
'propname' => 'transaction'
),
//this one is not really a field, but is required in some cases...
- //adding it here make simplier to check required fields
+ //adding it here make more simple to check required fields
'duree_mois_cotis' => array(
'label' => _T("Membership extension:"),
'propname' => 'extension'
$this->_amount = $this->_transaction->getMissingAmount();
}
$this->type = (int)$args['type'];
- //calculate begin date for cotisation
+ //calculate begin date for membership fee
$this->_begin_date = $this->_date;
if ($this->_is_cotis) {
- $curend = self::getDueDate($this->zdb, $this->_member);
- if ($curend != '') {
- $dend = new \DateTime($curend);
- $now = date('Y-m-d');
- $dnow = new \DateTime($now);
- if ($dend < $dnow) {
+ $due_date = self::getDueDate($this->zdb, $this->_member);
+ if ($due_date != '') {
+ $now = new \DateTime();
+ $due_date = new \DateTime($due_date);
+ if ($due_date < $now) {
// Member didn't renew on time
- $this->_begin_date = $now;
+ $this->_begin_date = $now->format('Y-m-d');
} else {
- $this->_begin_date = $curend;
+ // Caution : the next_begin_date is the day after the due_date.
+ $next_begin_date = clone $due_date;
+ $next_begin_date->add(new \DateInterval('P1D'));
+ $this->_begin_date = $next_begin_date->format('Y-m-d');
}
}
$this->retrieveEndDate();
$this->loadFromRS($args);
}
- if ($this->id !== null) {
- $this->loadDynamicFields();
- }
+ $this->loadDynamicFields();
}
/**
{
global $preferences;
- $bdate = new \DateTime($this->_begin_date);
+ $now = new \DateTime();
+ $begin_date = new \DateTime($this->_begin_date);
if ($preferences->pref_beg_membership != '') {
//case beginning of membership
list($j, $m) = explode('/', $preferences->pref_beg_membership);
- $edate = new \DateTime($bdate->format('Y') . '-' . $m . '-' . $j);
- while ($edate <= $bdate) {
- $edate->modify('+1 year');
+ $next_begin_date = new \DateTime($begin_date->format('Y') . '-' . $m . '-' . $j);
+ while ($next_begin_date <= $begin_date) {
+ $next_begin_date->add(new \DateInterval('P1Y'));
+ }
+
+ if ($preferences->pref_membership_offermonths > 0) {
+ //count days until next membership begin date
+ $diff1 = (int)$now->diff($next_begin_date)->format('%a');
+
+ //count days between next membership begin date and offered months
+ $tdate = clone $next_begin_date;
+ $tdate->sub(new \DateInterval('P' . $preferences->pref_membership_offermonths . 'M'));
+ $diff2 = (int)$next_begin_date->diff($tdate)->format('%a');
+
+ //when number of days until next membership begin date is less than or equal to the offered months, it's free :)
+ if ($diff1 <= $diff2) {
+ $next_begin_date->add(new \DateInterval('P1Y'));
+ }
}
- $this->_end_date = $edate->format('Y-m-d');
+
+ // Caution : the end_date to retrieve is the day before the next_begin_date.
+ $end_date = clone $next_begin_date;
+ $end_date->sub(new \DateInterval('P1D'));
+ $this->_end_date = $end_date->format('Y-m-d');
} elseif ($preferences->pref_membership_ext != '') {
//case membership extension
- $this->_extension = $preferences->pref_membership_ext;
+ if ($this->_extension == null) {
+ $this->_extension = $preferences->pref_membership_ext;
+ }
$dext = new \DateInterval('P' . $this->_extension . 'M');
- $edate = $bdate->add($dext);
- $this->_end_date = $edate->format('Y-m-d');
+ // Caution : the end_date to retrieve is the day before the next_begin_date.
+ $next_begin_date = $begin_date->add($dext);
+ $end_date = clone $next_begin_date;
+ $end_date->sub(new \DateInterval('P1D'));
+ $this->_end_date = $end_date->format('Y-m-d');
} else {
throw new \RuntimeException(
'Unable to define end date; none of pref_beg_membership nor pref_membership_ext are defined!'
/**
* Loads a contribution from its id
*
- * @param int $id the identifiant for the contribution to load
+ * @param int $id the identifier for the contribution to load
*
* @return bool true if query succeed, false otherwise
*/
public function load($id)
{
+ if (!$this->login->isLogged() && $this->login->id == '') {
+ return false;
+ }
+
try {
- $select = $this->zdb->select(self::TABLE);
- $select->where(self::PK . ' = ' . $id);
+ $select = $this->zdb->select(self::TABLE, 'c');
+ $select->join(
+ array('a' => PREFIX_DB . Adherent::TABLE),
+ 'c.' . Adherent::PK . '=a.' . Adherent::PK,
+ array()
+ );
//restrict query on current member id if he's not admin nor staff member
if (!$this->login->isAdmin() && !$this->login->isStaff()) {
- $select->where(Adherent::PK . ' = ' . $this->login->id);
+ $select->where
+ ->nest()
+ ->equalTo('a.' . Adherent::PK, $this->login->id)
+ ->or
+ ->equalTo('a.parent_id', $this->login->id)
+ ->unnest()
+ ->and
+ ->equalTo('c.' . self::PK, $id)
+ ;
+ } else {
+ $select->where->equalTo(self::PK, $id);
}
$results = $this->zdb->execute($select);
- $row = $results->current();
- if ($row !== false) {
+ if ($results->count() > 0) {
+ $row = $results->current();
$this->loadFromRS($row);
return true;
} else {
- throw new \Exception(
- 'No contribution #' . $id . ' (user ' .$this->login->id . ')'
+ Analog::log(
+ 'No contribution #' . $id . ' (user ' . $this->login->id . ')',
+ Analog::ERROR
);
+ return false;
}
- } catch (\Exception $e) {
+ } catch (Throwable $e) {
Analog::log(
- 'An error occured attempting to load contribution #' . $id .
+ 'An error occurred attempting to load contribution #' . $id .
$e->getMessage(),
Analog::ERROR
);
- return false;
+ throw $e;
}
}
/**
* Populate object from a resultset row
*
- * @param ResultSet $r the resultset row
+ * @param ArrayObject $r the resultset row
*
* @return void
*/
- private function loadFromRS($r)
+ private function loadFromRS(ArrayObject $r)
{
$pk = self::PK;
$this->_id = (int)$r->$pk;
$this->_date = $r->date_enreg;
- $this->_amount = $r->montant_cotis;
- //save original amount, we need it for transactions parts calulations
- $this->_orig_amount = $r->montant_cotis;
+ $this->_amount = (double)$r->montant_cotis;
+ //save original amount, we need it for transactions parts calculations
+ $this->_orig_amount = (double)$r->montant_cotis;
$this->_payment_type = $r->type_paiement_cotis;
$this->_info = $r->info_cotis;
$this->_begin_date = $r->date_debut_cotis;
- $enddate = $r->date_fin_cotis;
+ $end_date = $r->date_fin_cotis;
//do not work with knows bad dates...
- //the one with BC comes from 0.63/pgsl demo... Why the hell a so
- //strange date? dont know :(
- if ($enddate !== '0000-00-00'
- && $enddate !== '1901-01-01'
- && $enddate !== '0001-01-01 BC'
+ //the one with BC comes from 0.63/pgsql demo... Why the hell a so
+ //strange date? don't know :(
+ if (
+ $end_date !== '0000-00-00'
+ && $end_date !== '1901-01-01'
+ && $end_date !== '0001-01-01 BC'
) {
$this->_end_date = $r->date_fin_cotis;
}
}
$this->type = (int)$r->id_type_cotis;
+ $this->loadDynamicFields();
}
/**
*/
public function check($values, $required, $disabled)
{
+ global $preferences;
$this->errors = array();
$fields = array_keys($this->_fields);
foreach ($fields as $key) {
- //first of all, let's sanitize values
+ //first, let's sanitize values
$key = strtolower($key);
$prop = '_' . $this->_fields[$key]['propname'];
try {
$d = \DateTime::createFromFormat(__("Y-m-d"), $value);
if ($d === false) {
- throw new \Exception('Incorrect format');
+ //try with non localized date
+ $d = \DateTime::createFromFormat("Y-m-d", $value);
+ if ($d === false) {
+ throw new \Exception('Incorrect format');
+ }
}
$this->$prop = $d->format('Y-m-d');
- } catch (\Exception $e) {
+ } catch (Throwable $e) {
Analog::log(
'Wrong date format. field: ' . $key .
', value: ' . $value . ', expected fmt: ' .
break;
case Adherent::PK:
if ($value != '') {
- $this->_member = $value;
+ $this->_member = (int)$value;
}
break;
case ContributionsTypes::PK:
}
break;
case 'montant_cotis':
- $this->_amount = $value;
$value = strtr($value, ',', '.');
+ if (!empty($value) || $value === '0') {
+ $this->_amount = (double)$value;
+ }
if (!is_numeric($value) && $value !== '') {
$this->errors[] = _T("- The amount must be an integer!");
}
break;
case 'type_paiement_cotis':
- if ($value == self::PAYMENT_OTHER
- || $value == self::PAYMENT_CASH
- || $value == self::PAYMENT_CREDITCARD
- || $value == self::PAYMENT_CHECK
- || $value == self::PAYMENT_TRANSFER
- || $value == self::PAYMENT_PAYPAL
- ) {
+ $ptypes = new PaymentTypes(
+ $this->zdb,
+ $preferences,
+ $this->login
+ );
+ $ptlist = $ptypes->getList();
+ if (isset($ptlist[$value])) {
$this->_payment_type = $value;
} else {
$this->errors[] = _T("- Unknown payment type");
foreach ($required as $key => $val) {
if ($val === 1) {
$prop = '_' . $this->_fields[$key]['propname'];
- if (!isset($disabled[$key])
+ if (
+ !isset($disabled[$key])
&& (!isset($this->$prop)
|| (!is_object($this->$prop) && trim($this->$prop) == '')
|| (is_object($this->$prop) && trim($this->$prop->id) == ''))
) {
- $this->errors[] = _T("- Mandatory field empty: ") .
- ' <a href="#' . $key . '">' . $this->getFieldLabel($key) .'</a>';
+ $this->errors[] = str_replace(
+ '%field',
+ '<a href="#' . $key . '">' . $this->getFieldLabel($key) . '</a>',
+ _T("- Mandatory field %field empty.")
+ );
}
}
}
}
}
- if ($this->isCotis() && count($this->errors) == 0) {
+ if ($this->isFee() && count($this->errors) == 0) {
$overlap = $this->checkOverlap();
if ($overlap !== true) {
- if ($overlap === false) {
- $this->errors[] = _T("An error occured checking overlaping fees :(");
- } else {
- //method directly return error message
- $this->errors[] = $overlap;
- }
+ //method directly return error message
+ $this->errors[] = $overlap;
}
}
+ $this->dynamicsCheck($values, $required, $disabled);
+
if (count($this->errors) > 0) {
Analog::log(
- 'Some errors has been throwed attempting to edit/store a contribution' .
+ 'Some errors has been threw attempting to edit/store a contribution' .
print_r($this->errors, true),
Analog::ERROR
);
{
try {
$select = $this->zdb->select(self::TABLE, 'c');
+ //@phpstan-ignore-next-line
$select->columns(
array('date_debut_cotis', 'date_fin_cotis')
)->join(
array('ct' => PREFIX_DB . ContributionsTypes::TABLE),
'c.' . ContributionsTypes::PK . '=ct.' . ContributionsTypes::PK,
array()
- )->where(Adherent::PK . ' = ' . $this->_member)
+ )->where([Adherent::PK => $this->_member])
->where(array('cotis_extension' => new Expression('true')))
->where->nest->nest
->greaterThanOrEqualTo('date_debut_cotis', $this->_begin_date)
- ->lessThan('date_debut_cotis', $this->_end_date)
+ ->lessThanOrEqualTo('date_debut_cotis', $this->_end_date)
->unnest
->or->nest
- ->greaterThan('date_fin_cotis', $this->_begin_date)
+ ->greaterThanOrEqualTo('date_fin_cotis', $this->_begin_date)
->lessThanOrEqualTo('date_fin_cotis', $this->_end_date);
if ($this->id != '') {
- $select->where(self::PK . ' != ' . $this->id);
+ $select->where->notEqualTo(self::PK, $this->id);
}
$results = $this->zdb->execute($select);
if ($results->count() > 0) {
$result = $results->current();
- $d = new \DateTime($result->date_debut_cotis);
+
+ $d_begin = new \DateTime($result->date_debut_cotis);
+ $d_end = new \DateTime($result->date_fin_cotis);
+
+ if ($d_begin->format('m-d') == $d_end->format('m-d') && $result->date_fin_cotis == $this->_begin_date) {
+ //see https://bugs.galette.eu/issues/1762
+ return true;
+ }
return _T("- Membership period overlaps period starting at ") .
- $d->format(__("Y-m-d"));
+ $d_begin->format(__("Y-m-d"));
}
return true;
- } catch (\Exception $e) {
+ } catch (Throwable $e) {
Analog::log(
- 'An error occured checking overlaping fee. ' . $e->getMessage(),
+ 'An error occurred checking overlapping fee. ' . $e->getMessage(),
Analog::ERROR
);
- return false;
+ throw $e;
}
}
*/
public function store()
{
- global $hist;
+ global $hist, $emitter;
+
+ $event = null;
if (count($this->errors) > 0) {
throw new \RuntimeException(
'Existing errors prevents storing contribution: ' .
print_r($this->errors, true)
);
- return false;
}
try {
}
//no end date, let's take database defaults
- if (!$this->isCotis() && !$this->_end_date) {
+ if (!$this->isFee() && !$this->_end_date) {
unset($values['date_fin_cotis']);
}
$add = $this->zdb->execute($insert);
if ($add->count() > 0) {
- if ($this->zdb->isPostgres()) {
- $this->_id = $this->zdb->driver->getLastGeneratedValue(
- PREFIX_DB . 'cotisations_id_seq'
- );
- } else {
- $this->_id = $this->zdb->driver->getLastGeneratedValue();
- }
+ $this->_id = $this->zdb->getLastGeneratedValue($this);
// logging
$hist->add(
_T("Contribution added"),
Adherent::getSName($this->zdb, $this->_member)
);
+ $event = $this->getAddEventName();
} else {
$hist->add(_T("Fail to add new contribution."));
throw new \Exception(
- 'An error occured inserting new contribution!'
+ 'An error occurred inserting new contribution!'
);
}
} else {
//we're editing an existing contribution
$update = $this->zdb->update(self::TABLE);
- $update->set($values)->where(
- self::PK . '=' . $this->_id
- );
+ $update->set($values)->where([self::PK => $this->_id]);
$edit = $this->zdb->execute($update);
//edit == 0 does not mean there were an error, but that there
_T("Contribution updated"),
Adherent::getSName($this->zdb, $this->_member)
);
- } elseif ($edit === false) {
- throw new \Exception(
- 'An error occured updating contribution # ' . $this->_id . '!'
- );
}
+
+ $event = $this->getEditEventName();
}
//update deadline
- if ($this->isCotis()) {
- $deadline = $this->updateDeadline();
- if ($deadline !== true) {
- //if something went wrong, we rollback transaction
- throw new \Exception('An error occured updating member\'s deadline');
- }
+ if ($this->isFee()) {
+ $this->updateDeadline();
}
+
+ //dynamic fields
+ $this->dynamicsStore(true);
+
$this->zdb->connection->commit();
$this->_orig_amount = $this->_amount;
+
+ //send event at the end of process, once all has been stored
+ if ($event !== null && $this->areEventsEnabled()) {
+ $emitter->dispatch(new GaletteEvent($event, $this));
+ }
+
return true;
- } catch (\Exception $e) {
- $this->zdb->connection->rollBack();
- Analog::log(
- 'Something went wrong :\'( | ' . $e->getMessage() . "\n" .
- $e->getTraceAsString(),
- Analog::ERROR
- );
- return false;
+ } catch (Throwable $e) {
+ if ($this->zdb->connection->inTransaction()) {
+ $this->zdb->connection->rollBack();
+ }
+ throw $e;
}
}
$due_date = self::getDueDate($this->zdb, $this->_member);
if ($due_date != '') {
- $date_fin_update = $due_date;
+ $due_date_update = $due_date;
} else {
- $date_fin_update = new Expression('NULL');
+ $due_date_update = new Expression('NULL');
}
$update = $this->zdb->update(Adherent::TABLE);
$update->set(
- array('date_echeance' => $date_fin_update)
+ array('date_echeance' => $due_date_update)
)->where(
- Adherent::PK . '=' . $this->_member
+ [Adherent::PK => $this->_member]
);
$this->zdb->execute($update);
return true;
- } catch (\Exception $e) {
+ } catch (Throwable $e) {
Analog::log(
- 'An error occured updating member ' . $this->_member .
+ 'An error occurred updating member ' . $this->_member .
'\'s deadline |' .
$e->getMessage(),
Analog::ERROR
);
- return false;
+ throw $e;
}
}
*/
public function remove($transaction = true)
{
+ global $emitter;
+
try {
if ($transaction) {
$this->zdb->connection->beginTransaction();
}
$delete = $this->zdb->delete(self::TABLE);
- $delete->where(self::PK . ' = ' . $this->_id);
+ $delete->where([self::PK => $this->_id]);
$del = $this->zdb->execute($delete);
if ($del->count() > 0) {
$this->updateDeadline();
+ $this->dynamicsRemove(true);
+ } else {
+ Analog::log(
+ 'Contribution has not been removed!',
+ Analog::WARNING
+ );
+ return false;
}
if ($transaction) {
$this->zdb->connection->commit();
}
+ $emitter->dispatch(new GaletteEvent('contribution.remove', $this));
return true;
- } catch (\Exception $e) {
+ } catch (Throwable $e) {
if ($transaction) {
$this->zdb->connection->rollBack();
}
Analog::log(
- 'An error occured trying to remove contribution #' .
+ 'An error occurred trying to remove contribution #' .
$this->_id . ' | ' . $e->getMessage(),
Analog::ERROR
);
- return false;
+ throw $e;
}
}
public function getFieldLabel($field)
{
$label = $this->_fields[$field]['label'];
- if ($this->isCotis() && $field == 'date_debut_cotis') {
+ if ($this->isFee() && $field == 'date_debut_cotis') {
$label = $this->_fields[$field]['cotlabel'];
}
//replace " "
*/
public function getRowClass()
{
- return ( $this->_end_date != $this->_begin_date && $this->_is_cotis) ?
- 'cotis-normal' :
- 'cotis-give';
+ return ($this->_end_date != $this->_begin_date && $this->_is_cotis) ?
+ 'cotis-normal' : 'cotis-give';
}
/**
* @param Db $zdb Database instance
* @param integer $member_id Member identifier
*
- * @return date
+ * @return string
*/
public static function getDueDate(Db $zdb, $member_id)
{
+ if (!$member_id) {
+ return '';
+ }
try {
$select = $zdb->select(self::TABLE, 'c');
$select->columns(
'c.' . ContributionsTypes::PK . '=ct.' . ContributionsTypes::PK,
array()
)->where(
- Adherent::PK . ' = ' . $member_id
+ [Adherent::PK => $member_id]
)->where(
array('cotis_extension' => new Expression('true'))
);
$due_date = '';
}
return $due_date;
- } catch (\Exception $e) {
+ } catch (Throwable $e) {
Analog::log(
- 'An error occured trying to retrieve member\'s due date',
+ 'An error occurred trying to retrieve member\'s due date',
Analog::ERROR
);
- return false;
+ throw $e;
}
}
$update->set(
array(Transaction::PK => null)
)->where(
- self::PK . ' = ' . $contrib_id
+ [self::PK => $contrib_id]
);
$zdb->execute($update);
return true;
);
return false;
}
- } catch (\Exception $e) {
+ } catch (Throwable $e) {
Analog::log(
'Unable to detach contribution #' . $contrib_id .
' to transaction #' . $trans_id . ' | ' . $e->getMessage(),
Analog::ERROR
);
- return false;
+ throw $e;
}
}
$update = $zdb->update(self::TABLE);
$update->set(
array(Transaction::PK => $trans_id)
- )->where(self::PK . ' = ' . $contrib_id);
+ )->where([self::PK => $contrib_id]);
$zdb->execute($update);
return true;
- } catch (\Exception $e) {
+ } catch (Throwable $e) {
Analog::log(
'Unable to attach contribution #' . $contrib_id .
' to transaction #' . $trans_id . ' | ' . $e->getMessage(),
Analog::ERROR
);
- return false;
+ throw $e;
}
}
/**
- * Is current contribution a cotisation
+ * Is current contribution a membership fee
*
* @return boolean
*/
- public function isCotis()
+ public function isFee()
{
return $this->_is_cotis;
}
* Execute post contribution script
*
* @param ExternalScript $es External script to execute
- * @param array $extra Extra informations on contribution
+ * @param array $extra Extra information on contribution
* Defaults to null
* @param array $pextra Extra information on payment
* Defaults to null
}
$contrib = array(
+ 'id' => (int)$this->_id,
'date' => $this->_date,
'type' => $this->getRawType(),
'amount' => $this->amount,
if ($this->_member !== null) {
$m = new Adherent($this->zdb, (int)$this->_member);
$member = array(
+ 'id' => (int)$this->_member,
'name' => $m->sfullname,
'email' => $m->email,
'organization' => ($m->isCompany() ? 1 : 0),
if ($res !== true) {
Analog::log(
- 'An error occured calling post contribution ' .
+ 'An error occurred calling post contribution ' .
"script:\n" . $es->getOutput(),
Analog::ERROR
);
- $res = _T("Contribution informations") . "\n";
+ $res = _T("Contribution information") . "\n";
$res .= print_r($contrib, true);
$res .= "\n\n" . _T("Script output") . "\n";
$res .= $es->getOutput();
*/
public function getRawType()
{
- if ($this->isCotis()) {
+ if ($this->isFee()) {
return 'membership';
} else {
return 'donation';
*/
public function getTypeLabel()
{
- if ($this->isCotis()) {
+ if ($this->isFee()) {
return _T("Membership");
} else {
return _T("Donation");
}
/**
- * Get payent type label
+ * Get payment type label
+ *
+ * @param boolean $translated Whether to translate
*
* @return string
*/
- public function getPaymentType()
+ public function getPaymentType(bool $translated = false): string
{
if ($this->_payment_type === null) {
return '-';
}
- switch ($this->payment_type) {
- case Contribution::PAYMENT_CASH:
- return 'cash';
- break;
- case Contribution::PAYMENT_CREDITCARD:
- return 'credit_card';
- break;
- case Contribution::PAYMENT_CHECK:
- return 'check';
- break;
- case Contribution::PAYMENT_TRANSFER:
- return 'transfer';
- break;
- case Contribution::PAYMENT_PAYPAL:
- return 'paypal';
- break;
- case Contribution::PAYMENT_OTHER:
- return 'other';
- break;
- default:
- Analog::log(
- __METHOD__ . ' Unknonw payment type ' . $this->payment_type,
- Analog::ERROR
- );
- throw new \RuntimeException(
- 'Unknonw payment type ' . $this->payment_type
- );
- }
+ $ptype = new PaymentType($this->zdb, (int)$this->payment_type);
+ return $ptype->getName($translated);
}
/**
* Global getter method
*
- * @param string $name name of the property we want to retrive
+ * @param string $name name of the property we want to retrieve
*
- * @return false|object the called property
+ * @return mixed the called property
*/
public function __get($name)
{
switch ($name) {
case 'is_cotis':
- return $this->isCotis();
- break;
+ return $this->isFee();
default:
throw new \RuntimeException("Call to __get for '$name' is forbidden!");
}
- } elseif (property_exists($this, $rname)
+ } elseif (
+ property_exists($this, $rname)
|| in_array($name, $virtuals)
) {
switch ($name) {
case 'raw_begin_date':
case 'raw_end_date':
$rname = '_' . substr($name, 4);
- if ($this->$rname != '') {
+ if ($this->$rname !== null && $this->$rname != '') {
try {
$d = new \DateTime($this->$rname);
return $d;
- } catch (\Exception $e) {
+ } catch (Throwable $e) {
//oops, we've got a bad date :/
Analog::log(
'Bad date (' . $this->$rname . ') | ' .
case 'date':
case 'begin_date':
case 'end_date':
- if ($this->$rname != '') {
+ if ($this->$rname !== null && $this->$rname != '') {
try {
$d = new \DateTime($this->$rname);
return $d->format(__("Y-m-d"));
- } catch (\Exception $e) {
+ } catch (Throwable $e) {
//oops, we've got a bad date :/
Analog::log(
'Bad date (' . $this->$rname . ') | ' .
break;
case 'duration':
if ($this->_is_cotis) {
- $date_end = new \DateTime($this->_end_date);
- $date_start = new \DateTime($this->_begin_date);
- $diff = $date_end->diff($date_start);
- return $diff->format('%y') * 12 + $diff->format('%m');
+ // Caution : the end_date stored is actually the due date.
+ // Adding a day to compute the next_begin_date is required
+ // to return the right number of months.
+ $next_begin_date = new \DateTime($this->_end_date ?? $this->_begin_date);
+ $next_begin_date->add(new \DateInterval('P1D'));
+ $begin_date = new \DateTime($this->_begin_date);
+ $diff = $next_begin_date->diff($begin_date);
+ return (int)$diff->format('%y') * 12 + (int)$diff->format('%m');
} else {
return '';
}
- break;
case 'spayment_type':
- if ($this->_payment_type === null) {
- return '-';
- }
- switch ($this->_payment_type) {
- case self::PAYMENT_OTHER:
- return _T("Other");
- break;
- case self::PAYMENT_CASH:
- return _T("Cash");
- break;
- case self::PAYMENT_CREDITCARD:
- return _T("Credit card");
- break;
- case self::PAYMENT_CHECK:
- return _T("Check");
- break;
- case self::PAYMENT_TRANSFER:
- return _T("Transfer");
- break;
- case self::PAYMENT_PAYPAL:
- return _T("Paypal");
- break;
- default:
- Analog::log(
- 'Unknown payment type ' . $this->_payment_type,
- Analog::WARNING
- );
- return '-';
- break;
- }
- break;
+ return $this->getPaymentType(true);
case 'model':
if ($this->_is_cotis === null) {
return null;
}
- return ($this->isCotis()) ?
- PdfModel::INVOICE_MODEL :
- PdfModel::RECEIPT_MODEL;
- break;
+ return ($this->isFee()) ?
+ PdfModel::INVOICE_MODEL : PdfModel::RECEIPT_MODEL;
default:
return $this->$rname;
- break;
}
} else {
Analog::log(
}
}
+ /**
+ * Global isset method
+ * Required for twig to access properties via __get
+ *
+ * @param string $name name of the property we want to retrieve
+ *
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ $forbidden = array('is_cotis');
+ $virtuals = array('duration', 'spayment_type', 'model', 'raw_date',
+ 'raw_begin_date', 'raw_end_date'
+ );
+
+ $rname = '_' . $name;
+
+ if (in_array($name, $forbidden)) {
+ switch ($name) {
+ case 'is_cotis':
+ return true;
+ }
+ } elseif (
+ property_exists($this, $rname)
+ || in_array($name, $virtuals)
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+
/**
* Global setter method
*
* @param string $name name of the property we want to assign a value to
- * @param object $value a relevant value for the property
+ * @param mixed $value a relevant value for the property
*
* @return void
*/
public function __set($name, $value)
{
+ global $preferences;
+
$forbidden = array('fields', 'is_cotis', 'end_date');
if (!in_array($name, $forbidden)) {
throw new \Exception('Incorrect format');
}
$this->_begin_date = $d->format('Y-m-d');
- } catch (\Exception $e) {
+ } catch (Throwable $e) {
Analog::log(
'Wrong date format. field: ' . $name .
', value: ' . $value . ', expected fmt: ' .
}
break;
case 'payment_type':
- if ($value == self::PAYMENT_OTHER
- || $value == self::PAYMENT_CASH
- || $value == self::PAYMENT_CREDITCARD
- || $value == self::PAYMENT_CHECK
- || $value == self::PAYMENT_TRANSFER
- || $value == self::PAYMENT_PAYPAL
- ) {
+ $ptypes = new PaymentTypes(
+ $this->zdb,
+ $preferences,
+ $this->login
+ );
+ $list = $ptypes->getList();
+ if (isset($list[$value])) {
$this->_payment_type = $value;
} else {
Analog::log(
}
}
}
+
+ /**
+ * Flag creation mail sending
+ *
+ * @param boolean $send True (default) to send creation email
+ *
+ * @return Contribution
+ */
+ public function setSendmail(bool $send = true)
+ {
+ $this->sendmail = $send;
+ return $this;
+ }
+
+ /**
+ * Should we send administrative emails to member?
+ *
+ * @return boolean
+ */
+ public function sendEMail()
+ {
+ return $this->sendmail;
+ }
+
+ /**
+ * Handle files (dynamics files)
+ *
+ * @param array $files Files sent
+ *
+ * @return array|true
+ */
+ public function handleFiles($files)
+ {
+ $this->errors = [];
+
+ $this->dynamicsFiles($files);
+
+ if (count($this->errors) > 0) {
+ Analog::log(
+ 'Some errors has been threw attempting to edit/store a contribution files' . "\n" .
+ print_r($this->errors, true),
+ Analog::ERROR
+ );
+ return $this->errors;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Get required fields list
+ *
+ * @return array
+ */
+ public function getRequired(): array
+ {
+ return [
+ 'id_type_cotis' => 1,
+ 'id_adh' => 1,
+ 'date_enreg' => 1,
+ 'date_debut_cotis' => 1,
+ 'date_fin_cotis' => $this->isFee() ? 1 : 0,
+ 'montant_cotis' => $this->isFee() ? 1 : 0
+ ];
+ }
+
+ /**
+ * Can current logged-in user display contribution
+ *
+ * @param Login $login Login instance
+ *
+ * @return boolean
+ */
+ public function canShow(Login $login): bool
+ {
+ //non-logged-in members cannot show contributions
+ if (!$login->isLogged()) {
+ return false;
+ }
+
+ //admin and staff users can edit, as well as member itself
+ if (!$this->id || $login->id == $this->_member || $login->isAdmin() || $login->isStaff()) {
+ return true;
+ }
+
+ //parent can see their children contributions
+ $parent = new Adherent($this->zdb);
+ $parent
+ ->disableAllDeps()
+ ->enableDep('children')
+ ->load($this->login->id);
+ if ($parent->hasChildren()) {
+ foreach ($parent->children as $child) {
+ if ($child->id === $this->_member) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get prefix for events
+ *
+ * @return string
+ */
+ protected function getEventsPrefix(): string
+ {
+ return 'contribution';
+ }
}