Index: src/include/init.php ================================================================== --- src/include/init.php +++ src/include/init.php @@ -218,10 +218,14 @@ */ class UserException extends \LogicException { } + +class ValidationException extends UserException +{ +} // activer le gestionnaire d'erreurs/exceptions ErrorManager::enable(SHOW_ERRORS ? ErrorManager::DEVELOPMENT : ErrorManager::PRODUCTION); ErrorManager::setLogFile(DATA_ROOT . '/error.log'); Index: src/include/lib/Garradin/Entities/Accounting/Account.php ================================================================== --- src/include/lib/Garradin/Entities/Accounting/Account.php +++ src/include/lib/Garradin/Entities/Accounting/Account.php @@ -82,16 +82,14 @@ 'type' => 'int', 'type_parent' => 'int', 'user' => 'int', ]; - protected $_validation_rules = [ - 'id_chart' => 'required|integer|in_table:acc_charts,id', + protected $_form_rules = [ 'code' => 'required|string|alpha_num|max:10', 'label' => 'required|string|max:200', 'description' => 'string|max:2000', 'position' => 'required|integer', - 'type' => 'required|integer|min:0', - 'type_parent' => 'integer|min:0', - 'user' => 'integer|min:0|max:1', + 'type' => 'numeric|min:0', + 'type_parent' => 'numeric|min:0', ]; } Index: src/include/lib/Garradin/Entities/Accounting/Chart.php ================================================================== --- src/include/lib/Garradin/Entities/Accounting/Chart.php +++ src/include/lib/Garradin/Entities/Accounting/Chart.php @@ -21,23 +21,23 @@ 'country' => 'string', 'code' => '?string', 'archived' => 'integer', ]; - protected $_validation_rules = [ + protected $_form_rules = [ 'label' => 'required|string|max:200', 'country' => 'required|string|size:2', 'code' => 'string', - 'archived' => 'integer|required|max:1|min:0', ]; public function selfCheck(): void { parent::selfCheck(); $this->assert(Utils::getCountryName($this->country), 'Le code pays doit être un code ISO valide'); + $this->assert($this->archived === 0 || $this->archived === 1); } public function accounts() { return new Accounts($this->id()); } } Index: src/include/lib/Garradin/Entities/Accounting/Line.php ================================================================== --- src/include/lib/Garradin/Entities/Accounting/Line.php +++ src/include/lib/Garradin/Entities/Accounting/Line.php @@ -13,11 +13,13 @@ protected $id_transaction; protected $id_account; protected $credit = 0; protected $debit = 0; protected $reference; + protected $label; protected $reconciled = 0; + protected $id_analytical; protected $_types = [ 'id' => 'int', 'id_transaction' => 'int', 'id_account' => 'int', @@ -24,36 +26,35 @@ 'credit' => 'int', 'debit' => 'int', 'reference' => '?string', 'label' => '?string', 'reconciled' => 'int', + 'id_analytical' => '?int', ]; - protected $_validation_rules = [ - 'id_transaction' => 'required|integer|in_table:acc_transactions,id', - 'id_account' => 'required|integer|in_table:acc_accounts,id', - 'credit' => 'required|integer|min:0', - 'debit' => 'required|integer|min:0', + protected $_form_rules = [ + 'id_account' => 'required|numeric|in_table:acc_accounts,id', + 'id_analytical' => 'numeric|in_table:acc_accounts,id', + 'credit' => 'required|money|min:0', + 'debit' => 'required|money|min:0', 'reference' => 'string|max:200', 'label' => 'string|max:200', - 'reconciled' => 'int|min:0|max:1', ]; - public function filterUserValue(string $key, $value, array $source) + public function filterUserValue(string $key, $value) { if ($key == 'credit' || $key == 'debit') { - if (!preg_match('/^(\d+)(?:[,.](\d{1,2}))?$/', $value, $match)) - { + if (!preg_match('/^(\d+)(?:[,.](\d{1,2}))?$/', $value, $match)) { throw new ValidationException('Le format du montant est invalide. Format accepté, exemple : 142,02'); } $value = $match[1] . str_pad((int)@$match[2], 2, '0', STR_PAD_RIGHT); $value = (int) $value; } - $value = parent::filterUserValue($key, $value, $source); + $value = parent::filterUserValue($key, $value); return $value; } public function selfCheck(): void @@ -60,7 +61,8 @@ { parent::selfCheck(); $this->assert($this->credit || $this->debit, 'Aucun montant au débit ou au crédit.'); $this->assert(($this->credit * $this->debit) === 0 && ($this->credit + $this->debit) > 0, 'Ligne non équilibrée : crédit ou débit doit valoir zéro.'); $this->assert($this->id_transaction, 'Aucun mouvement n\'a été indiqué pour cette ligne.'); + $this->assert($this->reconciled === 0 || $this->reconciled === 1); } } Index: src/include/lib/Garradin/Entities/Accounting/Transaction.php ================================================================== --- src/include/lib/Garradin/Entities/Accounting/Transaction.php +++ src/include/lib/Garradin/Entities/Accounting/Transaction.php @@ -2,11 +2,11 @@ namespace Garradin\Entities\Accounting; use Garradin\Entity; use Garradin\Accounting\Accounts; -use LogicException; +use Garradin\ValidationException; use Garradin\DB; use Garradin\Config; class Transaction extends Entity { @@ -36,17 +36,15 @@ 'hash' => '?string', 'prev_hash' => '?string', 'id_year' => 'int', ]; - protected $_validated_rules = [ + protected $_form_rules = [ 'label' => 'required|string|max:200', 'notes' => 'string|max:20000', 'reference' => 'string|max:200', - 'date' => 'required|date', - 'validated' => 'bool', - 'id_year' => 'integer|in_table:acc_years,id', + 'date' => 'required|date_format:Y-m-d', ]; protected $_lines; public function getLines() @@ -104,11 +102,11 @@ } public function save(): bool { if ($this->validated && !isset($this->_modified['validated'])) { - throw new LogicException('Il n\'est pas possible de modifier un mouvement qui a été validé'); + throw new ValidationException('Il n\'est pas possible de modifier un mouvement qui a été validé'); } if (!parent::save()) { return false; } @@ -123,11 +121,11 @@ } public function delete(): bool { if ($this->validated) { - throw new LogicException('Il n\'est pas possible de supprimer un mouvement qui a été validé'); + throw new ValidationException('Il n\'est pas possible de supprimer un mouvement qui a été validé'); } return parent::delete(); } @@ -138,16 +136,16 @@ $db = DB::getInstance(); $config = Config::getInstance(); // ID d'exercice obligatoire if (null === $this->id_year) { - throw new LogicException('Aucun exercice spécifié.'); + throw new \LogicException('Aucun exercice spécifié.'); } if (!$db->test(Year::TABLE, 'id = ? AND start_date <= ? AND end_date >= ?;', $this->id_year, $this->date, $this->date)) { - throw new LogicException('La date ne correspond pas à l\'exercice sélectionné.'); + throw new ValidationException('La date ne correspond pas à l\'exercice sélectionné.'); } $total = 0; $lines = $this->getLines(); @@ -156,11 +154,11 @@ $total += $line->credit; $total -= $line->debit; } if (0 !== $total) { - throw new LogicException('Mouvement non équilibré : déséquilibre entre débits et crédits'); + throw new ValidationException('Écriture non équilibrée : déséquilibre entre débits et crédits'); } } public function importFromSimpleForm(int $chart_id, ?array $source = null): void { @@ -167,47 +165,53 @@ if (null === $source) { $source = $_POST; } if (empty($source['type'])) { - throw new LogicException('Type d\'écriture inconnu'); + throw new ValidationException('Type d\'écriture inconnu'); } $type = $source['type']; - $this->import(); + $this->importForm(); $accounts = new Accounts($chart_id); if ($type !== 'advanced') { $from = $accounts->getIdFromCode($source[$type . '_from']); $to = $accounts->getIdFromCode($source[$type . '_to']); $amount = $source['amount']; $line = new Line; - $line->import([ + $line->importForm([ 'reference' => $source['payment_reference'], + 'credit' => '0', 'debit' => $amount, 'id_account' => $from, 'id_analytical' => $source['id_analytical'] ?? null, ]); $this->add($line); $line = new Line; - $line->import([ + $line->importForm([ 'reference' => $source['payment_reference'], 'credit' => $amount, + 'debit' => '0', 'id_account' => $to, 'id_analytical' => $source['id_analytical'] ?? null, ]); $this->add($line); } else { - foreach ($sources['lines'] as $line) { + foreach ($source['lines'] as $i => $line) { $line['id_account'] = $accounts->getIdFromCode($line['account']); + + if (!$line['id_account']) { + throw new ValidationException('Numéro de compte invalide sur la ligne ' . ($i+1)); + } $line = (new Line)->import($line); $this->add($line); } } } } Index: src/include/lib/Garradin/Entities/Accounting/Year.php ================================================================== --- src/include/lib/Garradin/Entities/Accounting/Year.php +++ src/include/lib/Garradin/Entities/Accounting/Year.php @@ -25,25 +25,26 @@ 'end_date' => 'date', 'closed' => 'integer', 'id_chart' => 'integer', ]; - protected $_validation_rules = [ + protected $_form_rules = [ 'label' => 'required|string|max:200', 'start_date' => 'required|date|before:end_date', 'end_date' => 'required|date|after:start_date', - 'closed' => 'int|min:0|max:1', - 'id_chart' => 'required|integer', ]; public function selfCheck(): void { parent::selfCheck(); $this->assert($this->start_date < $this->end_date, 'La date de fin doit être postérieure à la date de début'); + $this->assert($this->closed === 0 || $this->closed === 1); $this->assert($this->closed == 1 || !isset($this->_modified['closed']), 'Il est interdit de réouvrir un exercice clôturé'); $db = DB::getInstance(); + + $this->assert($this->id_chart !== null); // Vérifier qu'on ne crée pas 2 exercices qui se recoupent if ($this->exists()) { $this->assert( !$db->test(self::TABLE, 'id != :id AND ((start_date <= :start_date AND end_date >= :start_date) OR (start_date <= :end_date AND end_date >= :start_date))', Index: src/include/lib/Garradin/Entity.php ================================================================== --- src/include/lib/Garradin/Entity.php +++ src/include/lib/Garradin/Entity.php @@ -1,30 +1,36 @@ _fields, $errors, $this->toArray())) - { - $messages = []; - - foreach ($errors as $error) - { - $messages[] = $this->getValidationMessage($error); - } + public function importForm(array $source = null) + { + $form = new Form; + + if (!$form->validate($this->_form_rules, $source)) + { + $messages = $form->getErrorMessages(); throw new ValidationException(implode("\n", $messages)); } + + return $this->import($source); + } + + protected function assert(bool $test, string $message = null): void + { + if (null !== $message && !$test) { + throw new ValidationException($message); + } } } Index: src/include/lib/Garradin/Form.php ================================================================== --- src/include/lib/Garradin/Form.php +++ src/include/lib/Garradin/Form.php @@ -35,13 +35,13 @@ } return true; } - public function validate(Array $rules) + public function validate(Array $rules, array $source = null) { - return \KD2\Form::validate($rules, $this->errors, $_POST); + return \KD2\Form::validate($rules, $this->errors, $source); } public function hasErrors() { return (count($this->errors) > 0); Index: src/include/lib/Garradin/Template.php ================================================================== --- src/include/lib/Garradin/Template.php +++ src/include/lib/Garradin/Template.php @@ -283,10 +283,14 @@ 'label' => $multiple ? 'Ajouter' : 'Sélectionner' ]); $input = sprintf('%s%3$s', $this->escape($attributes['id']), $button, $this->escape($current_value), $attributes_string); } + elseif ($type == 'money') { + $currency = Config::getInstance()->get('monnaie'); + $input = sprintf('%s', $attributes_string, $this->escape($current_value), $currency); + } else { $input = sprintf('', $type, $attributes_string, $this->escape($current_value)); } // No label? then we only want the input without the widget Index: src/templates/acc/transactions/new.tpl ================================================================== --- src/templates/acc/transactions/new.tpl +++ src/templates/acc/transactions/new.tpl @@ -3,12 +3,12 @@