Correction erreurs, sécurisation code, personnalisation présentation (Jordan)

This commit is contained in:
Jean-Christophe Engel 2026-07-02 13:38:15 +02:00
parent 717fc5320f
commit 4c71100366
19 changed files with 822 additions and 471 deletions

View file

@ -6,9 +6,10 @@ use DateTime;
use Paheko\Config;
use Paheko\DB;
use Paheko\DynamicList;
use Paheko\Plugins;
use Paheko\UserException;
use Paheko\Users\DynamicFields;
use Paheko\Utils;
use Paheko\Services\Services_User;
class Facture
{
@ -47,17 +48,52 @@ class Facture
'accounts' => [],
'label' => 'Facture',
'help' => ''],
CERFA => [
'id' => CERFA,
'accounts' => [],
'label' => 'Reçu fiscal',
'help' => 'Fonction historique conservée pour les anciens reçus. Le module Paheko "Reçus fiscaux" est recommandé pour les nouveaux usages.'],
COTIS => [
'id' => COTIS,
'accounts' => [],
'label' => 'Reçu de cotisation',
'help' => 'Fonction historique conservée pour les anciens reçus. Le module Paheko "Reçu de paiement" est recommandé pour les nouveaux usages.'],
];
public function __construct()
{
}
private function normalizeDate($value, string $field): string
{
if ($value instanceof \DateTimeInterface) {
return $value->format('Y-m-d');
}
$value = trim((string) $value);
foreach (['!Y-m-d', '!d/m/Y'] as $format) {
$date = DateTime::createFromFormat($format, $value);
$errors = DateTime::getLastErrors();
if ($date && (!$errors || (!$errors['warning_count'] && !$errors['error_count']))) {
return $date->format('Y-m-d');
}
}
throw new UserException(sprintf('La date "%s" est invalide pour le champ %s.', $value, $field));
}
// Fix : est dépendant de l'ordre des données dans l'array
// et implique que toutes les données soient présentes (pas possible de faire un update partiel)
public function _checkFields(&$datas)
{
$type_facture = isset($datas['type_facture']) ? (int) $datas['type_facture'] : null;
$fac = in_array($type_facture, [DEVIS, FACT], true);
$cerfa = $type_facture === CERFA;
$recu = $type_facture === COTIS;
$total = 0;
foreach($datas as $k=>$data)
{
if (!in_array($k, $this->keys))
@ -76,24 +112,11 @@ class Facture
switch($k)
{
case 'type_facture':
if (!array_key_exists($datas[$k], $this->types)) {
$datas[$k] = (int) $datas[$k];
if (!array_key_exists($datas[$k], self::TYPES_NAMES)) {
throw new UserException("$k est de type non-attendue ($data).");
}
if ($datas[$k] < 2) {
$fac = true;
$cerfa = false;
$recu = false;
}
elseif ($datas[$k] == 2) {
$fac = false;
$cerfa = true;
$recu = false;
}
elseif ($datas[$k] == 3) {
$fac = false;
$cerfa = false;
$recu = true;
}
break;
case 'receveur_membre':
case 'reglee':
@ -108,11 +131,11 @@ class Facture
}
break;
case 'date_emission':
$datas[$k] = \DateTime::createFromFormat('!d/m/Y', $data)->format('Y-m-d');
$datas[$k] = $this->normalizeDate($data, $k);
break;
case 'date_echeance':
$datas[$k] = \DateTime::createFromFormat('!d/m/Y', $data)->format('Y-m-d');
if (DateTime::createFromFormat('!Y-m-d', $datas[$k])->format('U') < DateTime::createFromFormat('!Y-m-d', $datas['date_emission'])->format('U'))
$datas[$k] = $this->normalizeDate($data, $k);
if (isset($datas['date_emission']) && DateTime::createFromFormat('!Y-m-d', $datas[$k])->format('U') < DateTime::createFromFormat('!Y-m-d', $datas['date_emission'])->format('U'))
{
throw new UserException("La date d'échéance est antérieure à la date d'émission ($data).");
}
@ -134,12 +157,12 @@ class Facture
if (empty($r['designation']) && empty($r['prix']))
{
unset($datas[$k][$g]);
unset($datas[$k]['prix']);
continue;
}
elseif (! is_numeric($r['prix']) && empty($r['prix']))
elseif (!isset($r['prix']) || $r['prix'] === '')
{
$datas[$k]['prix'] = 0;
$datas[$k][$g]['prix'] = 0;
$r['prix'] = 0;
}
elseif (empty($r['designation'])) {
throw new UserException("Une au moins des désignations est absente.");
@ -251,7 +274,7 @@ class Facture
$type = ctype_digit($v) ? 'd' : 's';
return sprintf('%' . $match[1] . $type, $v);
}, $selpattern);
$modele = '/^' . $prefixe . '\d+$/';
$modele = '/^' . preg_quote($prefixe, '/') . '\d+$/';
$numeros_filtres = array_filter($numeros, function($elem) use ($modele) {
return preg_match($modele, $elem);
}, 0);
@ -292,6 +315,12 @@ class Facture
$r->contenu = json_decode($r->contenu, true);
}
foreach (['nom_contact', 'numero_commande', 'reference_acheteur'] as $key) {
if (!property_exists($r, $key)) {
$r->$key = null;
}
}
$r->date_emission = \DateTime::createFromFormat('!Y-m-d', $r->date_emission);
if ($r->date_echeance)
{
@ -320,15 +349,19 @@ class Facture
public function list($all = true): DynamicList
{
$id_field = \Paheko\Users\DynamicFields::getNameFieldsSQL('u');
$plugin_name = preg_replace('/^.*\/(\w+)\/$/', '${1}', \Paheko\PLUGIN_ADMIN_URL);
$plugin = \Paheko\Plugins::get($plugin_name);
$db = DB::getInstance();
$id_field = DynamicFields::getNameFieldsSQL('u');
$plugin = Plugins::get('facturation');
$user_fields = DynamicFields::getInstance()->listAssocNames();
// adresse et ville peuvent être redéfinies dans la configuration du plugin
$adresse_client = $plugin->getConfig('adresse_client');
if ($adresse_client == null) { $adresse = 'u.adresse'; } else { $adresse = 'u.' . $adresse_client; }
$ville_client = $plugin->getConfig('ville_client');
if ($ville_client == null) { $ville = 'u.ville'; } else { $ville = 'u.' . $ville_client; }
$adresse_client = $plugin ? $plugin->getConfig('adresse_client') : null;
$adresse_client = $adresse_client ?: 'adresse';
$adresse = array_key_exists($adresse_client, $user_fields) ? 'u.' . $db->quoteIdentifier($adresse_client) : 'NULL';
$ville_client = $plugin ? $plugin->getConfig('ville_client') : null;
$ville_client = $ville_client ?: 'ville';
$ville = array_key_exists($ville_client, $user_fields) ? 'u.' . $db->quoteIdentifier($ville_client) : 'NULL';
$columns = [
// Sélectionner cette colonne, mais ne pas la mettre dans la liste des colonnes
@ -357,7 +390,7 @@ class Facture
'receveur' => [
'label' => 'Receveur',
// l'identité du membre peut être redéfinie dans la configuration des membres
'select' => sprintf('CASE WHEN receveur_membre THEN CASE %s WHEN "" THEN "** ABSENT **" ELSE %s END ELSE c.nom END', $id_field, $id_field),
'select' => sprintf('CASE WHEN receveur_membre THEN COALESCE(NULLIF(%s, \'\'), \'** ABSENT **\') ELSE c.nom END', $id_field),
],
'receveur_adresse' => [
// l'adresse peut être redéfinie dans la configuration du plugin
@ -393,7 +426,7 @@ class Facture
$tables = 'plugin_facturation_factures AS f
INNER JOIN plugin_facturation_paiement AS mp ON mp.code = f.moyen_paiement
LEFT JOIN users AS u ON f.receveur_membre = 1 AND u.id = f.receveur_id
LEFT JOIN users_view AS u ON f.receveur_membre = 1 AND u.id = f.receveur_id
LEFT JOIN plugin_facturation_clients AS c ON f.receveur_membre = 0 AND c.id = f.receveur_id';
if ($all) {
@ -404,7 +437,7 @@ class Facture
$list = new DynamicList($columns, $tables, $where);
$list->orderBy('date_emission', true);
$currency = Config::getInstance()->monnaie;
$currency = Config::getInstance()->currency;
$list->setModifier(function ($row) use ($currency) {
// Remplir la colonne virtuelle
@ -460,7 +493,10 @@ class Facture
}
else // Si c'est un membre de l'asso
{
throw new UserException("Woopsie, g pô encore implémenté l'usage des users de l'asso comme clients");
if (!\Paheko\Users\Users::exists((int) $id))
{
throw new UserException("Ce membre n'existe pas.");
}
}
$r = (array)DB::getInstance()->get('SELECT *, strftime(\'%s\', date_emission) AS date_emission,
@ -492,7 +528,10 @@ class Facture
}
else // Si c'est un membre de l'asso
{
throw new UserException("Woopsie, g pô encore implémenté l'usage des users de l'asso comme clients");
if (!\Paheko\Users\Users::exists((int) $id))
{
throw new UserException("Ce membre n'existe pas.");
}
}
return DB::getInstance()->test('plugin_facturation_factures', 'receveur_membre = ? AND receveur_id = ?', $base, $id);