diff --git a/CHANGELOG b/CHANGELOG index e4e5dae..635627c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,15 +1,3 @@ -0.12 -- Ajout Mollie à la table moyens de paiement -0.11 -- Changement mention finale pour devis -0.9 -- Ajout possibilité choisir champs identité et adresse membre -0.8.8 -- correction typo -0.8.7 -- correction typo -0.8.6 -- Correction erreur si pas de prix saisi 0.8.5 - Ajout numéro SIREN/SIRET pour les clients - Ajout pagination liste factures diff --git a/README.md b/README.md index 8685449..9f161d4 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,56 @@ # Plugin Facturation pour Paheko (ex Garradin) -Plugin de facturation pour le logiciel de gestion d'association Paheko -( https://paheko.eu/ - https://fossil.kd2.org/paheko ). - +Plugin de facturation pour le logiciel de gestion d'association Paheko ( https://paheko.eu/ - https://fossil.kd2.org/paheko ). Source : -- version compatible paheko 1.3.x : https://git.roflcopter.fr/lesanges/facturation -- version historique non compatible paheko 1.3.x : https://gitlab.com/noizette/garradin-plugin-facturation +- version historique : https://gitlab.com/noizette/garradin-plugin-facturation +- version compatible paheko 1.3.x : https://git.roflcopter.fr/lesanges/paheko-plugin-facturation -## Installation : -Vous pouvez télécharger l'archive .tar.gz depuis la page des -[releases](https://git.roflcopter.fr/lesanges/paheko-plugin-facturation/releases), -supprimer le numéro de version du nom de l'archive puis la placer dans -le dossier plugins de Paheko. +## Installation: +Vous pouvez télécharger l'archive .tar.gz depuis la page des [releases](https://git.roflcopter.fr/lesanges/paheko-plugin-facturation/releases), supprimer le numéro de version du nom de l'archive puis la placer dans le dossier plugins de Paheko. + +### Anciennes versions (<0.6) + +Normalement, les plugins de Paheko doivent seulement être laissé sous forme d'archive .tar.gz dans le dossier plugins, or pour la génération des PDF, la librairie mPDF a besoin d'écrire des fichiers temporaires. + +Il faut pour cela faire : + + tar xvf paheko-plugin-facturation-v0.5.0.tar.gz + mv paheko-plugin-facturation-v0.5.0 facturation + rm paheko-plugin-facturation-v0.5.0.tar.gz + chown -R www-data:www-data facturation/ + chmod -R g+w facturation/ + + +Supprimer l'archive permet à Paheko de ne pas la lire à la place du dossier. + +*Note : www-data correspond dans la plupart des cas à l'utlisateur d'Apache, si vous utilisez un autre serveur web, il faudra probablement adapter.* + +## Migration vers Garradin 1.0 (obsolète) + +Lorsque vous tentez de mettre à jour une installation de Garradin avec le plugin facturation vers Garradin 1.0, l'upgrade est bloquée par le plugin. + +Pour remédier à cela et parvenir à faire la mise à jour, il faut dans un premier temps installer la version 0.4 du plugin sur votre installation Garradin 0.9.8, se rendre sur la page principale du plugin (menu > Facturation), vous pouvez ensuite mettre à jour Garradin vers la 1.0. ## Fonctionnalités : - Créer et gérer une base de client·es - Créer et modifier des factures et devis adressés aux membres de l'association ou des client·es ajouté·es -- (obsolète) Créer des reçus fiscaux pour des dons et génération du CERFA correspondant -- (obsolète) Créer des reçus sur des cotisations -- Génération des documents (facture et devis) en PDF +- Créer des reçus fiscaux pour des dons et génération du cerfa correspondant +- Créer des reçus sur des cotisations +- Génération des documents (facture et devis) en PDF grâce à la librairie mPDF - Liste les documents associés sur la fiche d'un·e client·e -- Permet de définir le statut du document à « réglé » +- Permet de définir le statut du document sur reglée - **Configuration** : - Possibilité d'ajouter un numéro RNA et SIRET de l'association si elle en possède (apparait alors sur les documents) - - Possibilité de choisir certains champs à faire figurer sur la facture (adresse, code postal, ville) - - Modification du pied de page des documents (notamment pour y inscrire des mentions légales) + - Modification du pied de page des documents (notament pour y inscrire des mentions légales) - Vérifier le code postal : si coché, lors d'ajout ou de modification de client, le plugin vérifiera que le code postal entré est bien formaté (par rapport aux codes postaux français seulement) - - Noms de client·es uniques : si coché, lors d'ajout ou de modification de client·e, le nom du/de la client·e ne pourra pas être le même que celui d'un·e client·e déjà existant - - (obsolète) Informations relatives au CERFA pour les reçus fiscaux - - (obsolète) Image qui sert de signature sur le CERFA + - Noms de client·es uniques : si coché, lors d'ajout ou de modicifation de client·e, le nom du/de la client·e ne pourra pas être le même que celui d'un·e client·e déjà existant + - Informations relatives au cerfa pour les reçus fiscaux + - Image qui set de signature sur le cerfa -Note : pour le moment, les actions sur la liste des clients à cocher -ne fonctionnent pas. Pour supprimer un client, le faire depuis sa -fiche. +Note : pour le moment, les actions sur la liste des clients à cocher ne fonctionnent pas. Pour supprimer un client, le faire depuis sa fiche. -## Futur de ce plugin : -Un nouveau plugin est en cours de développement par BohwaZ, donc il -n'est pas pertinent d'ajouter de nouvelles fonctionnalités à celui-ci. - -Par contre, si des bugs sont signalés sur la liste -hebergement@paheko.cloud ou aide@paheko.cloud, je -(lesanges@zaclys.net) peux tenter de les corriger, à condition que ça -n'impacte pas trop la structure du plugin. - -## Futur improbable (obsolète) : -- Ajout des champs Référence, Prix unitaire, Quantité sur les documents +## Futur : +- Ajout des champs Référence, Prix unitaire, Quantité sur les documents - Actions sur liste de client·es (exporter, supprimer) - Afficher/filtrer les documents par statuts réglé/archivé - Changer statut depuis la liste des documents @@ -53,12 +59,15 @@ n'impacte pas trop la structure du plugin. - Gestion TVA ? - Un devis ne devrait pas pouvoir être réglé - Quid si un·e membre de l'asso est supprimé·e alors que des documents lui sont adressés ? + +## Futur improbable : - Opérations de paiements dans la compta liés à une facture - Gestion de produits -Le plugin nécessite l'extension PHP mbstring. -## (???) Inclus les bibliothèques suivantes : +Le plugin nécessite l'extension PHP mbstring. + +## Inclus les bibliothèques suivantes : - Composer : https://getcomposer.org/ diff --git a/admin/_facture_common.php b/admin/_facture_common.php index b729f34..cedacfa 100644 --- a/admin/_facture_common.php +++ b/admin/_facture_common.php @@ -4,28 +4,6 @@ namespace Paheko; require_once __DIR__ . '/_inc.php'; -function toArray($array, $cle, $sep=",") -{ - $result = array(); - foreach ($array as $elem) - { - $ro = new \ReflectionObject($elem); - $proprietes = $ro->getProperties(); - $ligne = ""; - foreach ($proprietes as $p) - { - if ($p->getName() == $cle) { - $key = $p->getValue($elem); - } - else { - $ligne .= $sep . $p->getValue($elem); - } - } - $result[$key] = substr($ligne, strlen($sep)); - } - return $result; -} - if (!isset($target) || !in_array( $target, ['new', 'edit'])) { throw new Exception('blabla illegal call'); // Fix: exception type? } else { @@ -56,14 +34,14 @@ $tpl->assign('formes_don', array('1' => 'Acte authentique', '3' => 'Don manuel', '4' => 'Autres')); $tpl->assign('natures_don', array('1' => 'Numéraire', - '2' => 'Chèque', - '3' => 'Virement, CB; ...')); + '2' => 'Chèque', + '3' => 'Virement, CB; ...')); $tpl->assign('textes_don', $facture->listTextesCerfa()); if ( !$target ) { f(['id' => 'required|numeric']); $id = (int) qg('id'); - + if (!$f = $facture->get($id)) { throw new UserException("Ce document n'existe pas."); @@ -74,126 +52,109 @@ if ( !$target ) { $data=[]; $form->runIf(f('save') && !$form->hasErrors(), function () use ($client, &$data, $form) - { - try - { - if ( count(f('designation')) !== count(f('prix')) ) - { - throw new UserException('Nombre de désignations et de prix reçus différent.'); - } + { + try + { + if ( count(f('designation')) !== count(f('prix')) ) + { + throw new UserException('Nombre de désignations et de prix reçus différent.'); + } - $data = [ - 'numero' => f('numero_facture'), - 'date_emission' => f('date_emission'), - 'date_echeance' => f('date_echeance'), - 'reglee' => f('reglee') == 1?1:0, - 'archivee' => f('archivee') == 1?1:0, - 'moyen_paiement' => f('moyen_paiement'), - 'nom_contact' => f('nom_contact'), - 'toto' => 0 - ]; - $data['type_facture'] = f('type'); - if (in_array(f('type'), [DEVIS, FACT])) - { - foreach(f('designation') as $k=>$value) - { - if (empty($value) && f('prix')[$k] != null) { - throw new UserException("Il manque la désignation de la ligne " . $k+1 . " !!"); - } - elseif ($value != '' && f('prix')[$k] == null) { - throw new UserException('Il manque le prix sur la ligne '. $k+1 . ' !!'); - } elseif (empty($value) && f('prix')[$k] == null) { - continue; - } + $data = [ + 'numero' => f('numero_facture'), + 'date_emission' => f('date_emission'), + 'date_echeance' => f('date_echeance'), + 'reglee' => f('reglee') == 1?1:0, + 'archivee' => f('archivee') == 1?1:0, + 'moyen_paiement' => f('moyen_paiement'), + 'toto' => 0 + ]; + $data['type_facture'] = f('type'); + if (in_array(f('type'), [DEVIS, FACT])) + { + foreach(f('designation') as $k=>$value) + { + $data['contenu'][$k]['designation'] = $value; + $data['contenu'][$k]['prix'] = Utils::moneyToInteger(f('prix')[$k]); + $data['toto'] += Utils::moneyToInteger(f('prix')[$k]); + } + $data['total'] = $data['toto']; + unset($data['toto']); + } + elseif ( f('type') == CERFA ) + { + $data['moyen_paiement'] = f('moyen_paiement_cerfa'); + $data['contenu'] = [ + 'forme' => f('forme_don'), + 'nature' => f('nature_don'), + 'texte' => f('texte_don')]; + $data['total'] = Utils::moneyToInteger(f('total')); + unset($data['toto']); + } + if (f('base_receveur') == 'client') + { + $data['receveur_membre'] = 0; + $data['receveur_id'] = f('client'); + } + elseif (f('base_receveur') == 'membre') + { + $data['receveur_membre'] = 1; + $data['receveur_id'] = f('membre'); + } + else + { + throw new UserException('Vous devez indiquer si le receveur est un client ou un membre'); + } - $data['contenu'][$k]['designation'] = $value; - $data['contenu'][$k]['prix'] = Utils::moneyToInteger(f('prix')[$k]); - $data['toto'] += Utils::moneyToInteger(f('prix')[$k]); - } - $data['total'] = $data['toto']; - unset($data['toto']); - if (! isset($data['contenu'])) { - throw new UserException("Aucune désignation ni aucun prix saisi !!"); - } - if (f('type') == FACT) { - $data['numero_commande'] = f('numero_commande'); - $data['reference_acheteur'] = f('reference_acheteur'); - } - } - elseif ( f('type') == CERFA ) - { - $data['moyen_paiement'] = f('moyen_paiement_cerfa'); - $data['contenu'] = [ - 'forme' => f('forme_don'), - 'nature' => f('nature_don'), - 'texte' => f('texte_don')]; - $data['total'] = Utils::moneyToInteger(f('total')); - unset($data['toto']); - } - if (f('base_receveur') == 'client') - { - $data['receveur_membre'] = 0; - $data['receveur_id'] = f('client'); - } - elseif (f('base_receveur') == 'membre') - { - $data['receveur_membre'] = 1; - $data['receveur_id'] = f('membre'); - } - else - { - throw new UserException('Vous devez indiquer si le receveur est un client ou un membre'); - } - - } - catch(UserException $e) - { - $form->addError($e->getMessage()); - } - - }, $csrf_key); + } + catch(UserException $e) + { + $form->addError($e->getMessage()); + } + + }, $csrf_key); $form->runIf(f('select_cotis') && !$form->hasErrors(), function () use ($step) - { - $step = true; - }, 'add_cotis_1'); + { + $step = true; + }, 'add_cotis_1'); $form->runIf(f('add_cotis') && !$form->hasErrors(), function () use ($radio, $fields, $facture, $form) - { - $radio['type'] = f('cotisation'); - try - { - $num = (int) str_replace('cotis_', '', $radio['type']); - foreach($fields as $field) - { - $cotis[$field] = f($field.'_'.$num); - } + { + $radio['type'] = f('cotisation'); + try + { + $num = (int) str_replace('cotis_', '', $radio['type']); + foreach($fields as $field) + { + $cotis[$field] = f($field.'_'.$num); + } - $r = $facture->getCotis(f('membre_cotis'), $cotis['id']); - $r = $r[0]; + $r = $facture->getCotis(f('membre_cotis'), $cotis['id']); + $r = $r[0]; - $data = [ - 'type_facture' => COTIS, - 'numero' => f('numero_facture'), - 'receveur_membre' => 1, - 'receveur_id' => f('membre_cotis'), - 'date_emission' => f('date_emission'), - 'moyen_paiement' => 'AU', - 'total' => $r->paid_amount ?? $r->amount, - 'contenu' => ['id' => $cotis['id'], - 'intitule' => $cotis['label'], - 'souscription' => $cotis['date'], - 'expiration' => $cotis['expiry'] ] - ]; + $data = [ + 'type_facture' => COTIS, + 'numero' => f('numero_facture'), + 'receveur_membre' => 1, + 'receveur_id' => f('membre_cotis'), + 'date_emission' => f('date_emission'), + 'moyen_paiement' => 'AU', + 'total' => $r->paid_amount ?? $r->amount, + 'contenu' => ['id' => $cotis['id'], + 'intitule' => $cotis['label'], + 'souscription' => $cotis['date'], + 'expiration' => $cotis['expiry'] ] + ]; - } - catch (UserException $e) - { - $form->addError($e->getMessage()); - } - }, 'add_cotis_2'); + } + catch (UserException $e) + { + $form->addError($e->getMessage()); + } + }, 'add_cotis_2'); if (! $form->hasErrors()) { @@ -263,12 +224,12 @@ if ($target) elseif (isset($doc['type'])) { $radio['type'] = $doc['type']; } // ... ou par défaut - else + else { $radio['type'] = FACT; } } -else +else { $doc['moyen_paiement'] = $f->moyen_paiement; $doc['type'] = $f->type_facture; @@ -278,12 +239,9 @@ else $doc['client'] = $f->receveur_id; $doc['membre'] = $f->receveur_id; $doc['contenu'] = $f->contenu; - + $doc['date_emission'] = f('date_emission') ?: $f->date_emission; $doc['date_echeance'] = f('date_echeance')?: $f->date_echeance; // Smarty m'a saoulé pour utiliser form_field|date_fr:--- - $doc['nom_contact'] = $f->nom_contact; - $doc['numero_commande'] = $f->numero_commande; - $doc['reference_acheteur'] = $f->reference_acheteur; /* modif DD -- CERFA -------------------------------------- */ if ( $f->type_facture == CERFA ) { $doc['total'] = $f->total; @@ -291,7 +249,7 @@ else $doc['nature_don'] = $f->contenu['nature']; $doc['texte_don'] = $f->contenu['texte']; } - + $radio['type'] = f('type')??$doc['type']; } $tpl->assign('types_details', $facture->types); @@ -330,10 +288,8 @@ if (in_array($radio['type'], [DEVIS, FACT])) } } else { - /* - $designations = ['Exemple']; - $prix = [250]; - */ + $designations = ['Exemple']; + $prix = [250]; } } @@ -341,10 +297,10 @@ $date = new \DateTime; $date->setTimestamp(time()); $tpl->assign('date', $date->format('d/m/Y')); + $tpl->assign(compact('liste', 'radio', 'step', 'designations', 'prix', 'from_user', 'identite', 'csrf_key', 'doc')); -$tpl->assign('users', toArray($db->get('SELECT id, '.$identite.' FROM users WHERE id_category != -2 NOT IN (SELECT id FROM users_categories WHERE hidden = 1) ORDER BY ' .$identite. ';'), 'id', " ")); +$tpl->assign('users', $db->getAssoc('SELECT id, '.$identite.' FROM users WHERE id_category != -2 NOT IN (SELECT id FROM users_categories WHERE hidden = 1) ORDER BY ' .$identite. ';')); $tpl->assign('clients', $db->getAssoc('SELECT id, nom FROM plugin_facturation_clients;')); -$tpl->assign('contacts', $db->getAssoc('SELECT id, nom_contact FROM plugin_facturation_clients;')); $tpl->assign('require_number', $require_number); $tpl->assign('number_pattern', PATTERNS_LIST[$plugin->getConfig('pattern')]); diff --git a/admin/_inc.php b/admin/_inc.php index f11fc22..582e462 100644 --- a/admin/_inc.php +++ b/admin/_inc.php @@ -11,7 +11,7 @@ define('CERFA', 2); define('COTIS', 3); const PATTERNS_LIST = [ - '' => 'Aucun, le numéro sera à spécifier manuellement pour chaque document', + null => 'Aucun, le numéro sera à spécifier manuellement pour chaque document', '%{type}-%{year}-%{ynumber}' => 'Type-Année-Numéro du document par année ("FACT-2021-42")', '%{year}-%{type}-%04{ynumber}' => 'Année-Type-Numéro du document par année ("2021-DEVIS-0042")', '%{t}-%{year}-%{ynumber}' => 'Type court-Année-Numéro du document par année ("F-2021-42")', @@ -48,11 +48,11 @@ $tpl->register_function('money_fac', function (array $params) if (!isset($user)) { $user = false; - } + } if (!isset($name)) { - $name = 'prix[]'; + $name = 'prix[]'; } if (null !== $current_value && !$user) { @@ -62,7 +62,7 @@ $tpl->register_function('money_fac', function (array $params) if (null !== $current_value) { $current_value = htmlspecialchars($current_value, ENT_QUOTES, 'UTF-8'); } - + $currency = Config::getInstance()->get('monnaie'); return sprintf('
$footer
- EOF; - if ($f->type_facture == DEVIS) { - echo <<| $logo | $asso | $receveur |