diff --git a/CHANGELOG b/CHANGELOG index 635627c..e4e5dae 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,15 @@ +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 9f161d4..8685449 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,50 @@ # 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 historique : https://gitlab.com/noizette/garradin-plugin-facturation -- version compatible paheko 1.3.x : https://git.roflcopter.fr/lesanges/paheko-plugin-facturation +- 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 -## 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. +## 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. ## 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 -- 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 +- (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 - Liste les documents associés sur la fiche d'un·e client·e -- Permet de définir le statut du document sur reglée +- Permet de définir le statut du document à « réglé » - **Configuration** : - Possibilité d'ajouter un numéro RNA et SIRET de l'association si elle en possède (apparait alors sur les documents) - - Modification du pied de page des documents (notament pour y inscrire des mentions légales) + - 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) - 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 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 + - 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 -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 : -- Ajout des champs Référence, Prix unitaire, Quantité sur les documents +## 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 - Actions sur liste de client·es (exporter, supprimer) - Afficher/filtrer les documents par statuts réglé/archivé - Changer statut depuis la liste des documents @@ -59,15 +53,12 @@ Note : pour le moment, les actions sur la liste des clients à cocher ne fonctio - 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. -Le plugin nécessite l'extension PHP mbstring. - -## Inclus les bibliothèques suivantes : +## (???) Inclus les bibliothèques suivantes : - Composer : https://getcomposer.org/ diff --git a/admin/_facture_common.php b/admin/_facture_common.php index cedacfa..b729f34 100644 --- a/admin/_facture_common.php +++ b/admin/_facture_common.php @@ -4,6 +4,28 @@ 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 { @@ -34,14 +56,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."); @@ -52,109 +74,126 @@ 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'), - '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 = [ + '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; + } - } - catch(UserException $e) - { - $form->addError($e->getMessage()); - } - - }, $csrf_key); + $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); $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()) { @@ -224,12 +263,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; @@ -239,9 +278,12 @@ 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; @@ -249,7 +291,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); @@ -288,8 +330,10 @@ if (in_array($radio['type'], [DEVIS, FACT])) } } else { - $designations = ['Exemple']; - $prix = [250]; + /* + $designations = ['Exemple']; + $prix = [250]; + */ } } @@ -297,10 +341,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', $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('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('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 582e462..f11fc22 100644 --- a/admin/_inc.php +++ b/admin/_inc.php @@ -11,7 +11,7 @@ define('CERFA', 2); define('COTIS', 3); const PATTERNS_LIST = [ - null => 'Aucun, le numéro sera à spécifier manuellement pour chaque document', + '' => '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 |