Compare commits

...

27 Commits

Author SHA1 Message Date
Jean-Christophe Engel 38494a1a85 màj CHANGELOG, README et plugin.ini 2024-10-21 09:09:25 +02:00
Jean-Christophe Engel 6248904bd8 correction typos 2024-10-19 14:32:41 +02:00
Jean-Christophe Engel f6eef8897e Annulation "fusion branche simplification"
This reverts commit 16daac2e24, reversing
changes made to 46d0c838ae.
2024-10-19 14:26:06 +02:00
Jean-Christophe Engel 201682f4d7 Changement mention finale pour devis 2024-10-18 22:25:31 +02:00
Jean-Christophe Engel 77180a38ff Suppression message inutile pour devis 2024-10-18 21:30:45 +02:00
Jean-Christophe Engel 16daac2e24 fusion branche simplification 2024-09-19 09:54:34 +02:00
Jean-Christophe Engel 46d0c838ae Prise en compte du cas où le champ nom n'existe pas 2024-09-18 18:44:06 +02:00
Jean-Christophe Engel da713cc06d Correction identité membre dans formulaire 2024-09-10 15:37:22 +02:00
Jean-Christophe Engel 72999b9fc6 suppression image cerfa 2024-09-06 22:03:07 +02:00
Jean-Christophe Engel f321e83e20 Simplification : suppression génération reçu dons et cotisations 2024-09-06 21:40:27 +02:00
Jean-Christophe Engel 57e6ad09f9 Ajout possibilité choisir champs identité et adresse membre 2024-08-30 21:32:13 +02:00
Jean-Christophe Engel 2ecbac159a templates/config.tpl : correction typo 2024-08-13 20:43:03 +02:00
Jean-Christophe Engel 3d5af4ebb0 Ajout impression logo avec option de configuration 2024-06-20 15:55:33 +02:00
Jean-Christophe Engel f93bb57906 Adaptation Readme à la situation actuelle 2024-06-08 20:52:29 +02:00
Jean-Christophe Engel 128e538431 Correction typo 2024-06-03 09:55:27 +02:00
Jean-Christophe Engel bd1f541bc0 Correction typo 2024-06-02 22:37:04 +02:00
Jean-Christophe Engel dc7f944ead Mise-à-jour README et CHANGELOG 2024-04-03 13:11:14 +02:00
Jean-Christophe Engel 1d4f17ead6 Correction erreur si pas de prix saisi 2024-03-29 11:57:36 +01:00
Jean-Christophe Engel b0a44b85c6 Ajout format numéro téléphone 2024-03-12 18:36:07 +01:00
Jean-Christophe Engel 26f4514fd0 Correction bug si SIRET non renseigné 2024-01-12 18:05:38 +01:00
Jean-Christophe Engel e2065bdb37 Amélioration cosmétique 2024-01-12 17:09:49 +01:00
Jean-Christophe Engel fa519a9567 Mise-à-jour CHANGELOG 2024-01-12 16:48:27 +01:00
Jean-Christophe Engel 39bde25d24 Améliorations cosmétiques 2024-01-12 16:37:04 +01:00
Jean-Christophe Engel b2e63c0383 Ne pas rendre la saisie du SIRET obligatoire pour les cas où il ne
serait pas disponible
2024-01-12 12:05:38 +01:00
Jean-Christophe Engel b5dc9d0efa Correction oubli 2024-01-12 11:50:04 +01:00
Jean-Christophe Engel c9300fe943 Ajout pagination liste factures 2024-01-12 11:30:37 +01:00
Jean-Christophe Engel b6bb4fd80d Ajout numéro SIREN/SIRET pour un client 2024-01-11 20:41:19 +01:00
25 changed files with 538 additions and 355 deletions

View File

@ -1,3 +1,17 @@
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
0.8.4 0.8.4
- Correction bug si identité définie par plusieurs champs - Correction bug si identité définie par plusieurs champs
- Correction bug oubli choix receveur - Correction bug oubli choix receveur

View File

@ -1,55 +1,49 @@
# Plugin Facturation pour Paheko (ex Garradin) # 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 : Source :
- version historique : https://gitlab.com/noizette/garradin-plugin-facturation - version compatible paheko 1.3.x : https://git.roflcopter.fr/lesanges/facturation
- version compatible paheko 1.3.x : https://git.roflcopter.fr/lesanges/paheko-plugin-facturation - version historique non compatible paheko 1.3.x : https://gitlab.com/noizette/garradin-plugin-facturation
## Installation: ## 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. Vous pouvez télécharger l'archive .tar.gz depuis la page des
[releases](https://git.roflcopter.fr/lesanges/paheko-plugin-facturation/releases),
### Anciennes versions (<0.6) supprimer le numéro de version du nom de l'archive puis la placer dans
le dossier plugins de Paheko.
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 : ## Fonctionnalités :
- Créer et gérer une base de client·es - 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 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 - (obsolète) Créer des reçus fiscaux pour des dons et génération du CERFA correspondant
- Créer des reçus sur des cotisations - (obsolète) Créer des reçus sur des cotisations
- Génération des documents (facture et devis) en PDF grâce à la librairie mPDF - Génération des documents (facture et devis) en PDF
- Liste les documents associés sur la fiche d'un·e client·e - 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** : - **Configuration** :
- Possibilité d'ajouter un numéro RNA et SIRET de l'association si elle en possède (apparait alors sur les documents) - 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) - 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 - 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
- Informations relatives au cerfa pour les reçus fiscaux - (obsolète) Informations relatives au CERFA pour les reçus fiscaux
- Image qui set de signature sur le cerfa - (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 : ## 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 - Ajout des champs Référence, Prix unitaire, Quantité sur les documents
- Actions sur liste de client·es (exporter, supprimer) - Actions sur liste de client·es (exporter, supprimer)
- Afficher/filtrer les documents par statuts réglé/archivé - Afficher/filtrer les documents par statuts réglé/archivé
@ -59,15 +53,12 @@ Note : pour le moment, les actions sur la liste des clients à cocher ne fonctio
- Gestion TVA ? - Gestion TVA ?
- Un devis ne devrait pas pouvoir être réglé - 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 ? - 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 - Opérations de paiements dans la compta liés à une facture
- Gestion de produits - 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 : - Composer :
https://getcomposer.org/ https://getcomposer.org/

View File

@ -4,6 +4,28 @@ namespace Paheko;
require_once __DIR__ . '/_inc.php'; 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'])) { if (!isset($target) || !in_array( $target, ['new', 'edit'])) {
throw new Exception('blabla illegal call'); // Fix: exception type? throw new Exception('blabla illegal call'); // Fix: exception type?
} else { } else {
@ -74,6 +96,10 @@ $form->runIf(f('save') && !$form->hasErrors(),
{ {
foreach(f('designation') as $k=>$value) foreach(f('designation') as $k=>$value)
{ {
if ($value != '' && f('prix')[$k] == null) {
throw new UserException('Il manque le prix sur la ligne '. $k+1 . ' !!');
}
$data['contenu'][$k]['designation'] = $value; $data['contenu'][$k]['designation'] = $value;
$data['contenu'][$k]['prix'] = Utils::moneyToInteger(f('prix')[$k]); $data['contenu'][$k]['prix'] = Utils::moneyToInteger(f('prix')[$k]);
$data['toto'] += Utils::moneyToInteger(f('prix')[$k]); $data['toto'] += Utils::moneyToInteger(f('prix')[$k]);
@ -121,7 +147,7 @@ $form->runIf(f('select_cotis') && !$form->hasErrors(),
}, 'add_cotis_1'); }, 'add_cotis_1');
$form->runIf(f('add_cotis') && !$form->hasErrors(), $form->runIf(f('add_cotis') && !$form->hasErrors(),
function () use ($radio, $fields, $facture) function () use ($radio, $fields, $facture, $form)
{ {
$radio['type'] = f('cotisation'); $radio['type'] = f('cotisation');
try try
@ -297,9 +323,8 @@ $date = new \DateTime;
$date->setTimestamp(time()); $date->setTimestamp(time());
$tpl->assign('date', $date->format('d/m/Y')); $tpl->assign('date', $date->format('d/m/Y'));
$tpl->assign(compact('liste', 'radio', 'step', 'designations', 'prix', 'from_user', 'identite', 'csrf_key', 'doc')); $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('clients', $db->getAssoc('SELECT id, nom FROM plugin_facturation_clients;'));
$tpl->assign('require_number', $require_number); $tpl->assign('require_number', $require_number);
$tpl->assign('number_pattern', PATTERNS_LIST[$plugin->getConfig('pattern')]); $tpl->assign('number_pattern', PATTERNS_LIST[$plugin->getConfig('pattern')]);

View File

@ -17,7 +17,7 @@ if (!$c)
} }
$form->runIf(f('save') && !$form->hasErrors(), $form->runIf(f('save') && !$form->hasErrors(),
function () use ($client, $id) function () use ($client, $id, $form)
{ {
try try
{ {
@ -26,6 +26,7 @@ $form->runIf(f('save') && !$form->hasErrors(),
'adresse' => f('adresse'), 'adresse' => f('adresse'),
'code_postal' => f('code_postal'), 'code_postal' => f('code_postal'),
'ville' => f('ville'), 'ville' => f('ville'),
'siret' => f('siret'),
'telephone' => f('telephone'), 'telephone' => f('telephone'),
'email' => f('email') 'email' => f('email')
]); ]);

View File

@ -7,7 +7,7 @@ require_once __DIR__ . '/_inc.php';
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_READ); $session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_READ);
$form->runIf(f('add') && !$form->hasErrors(), $form->runIf(f('add') && !$form->hasErrors(),
function () use ($client) function () use ($client, $form)
{ {
try try
{ {
@ -16,6 +16,7 @@ $form->runIf(f('add') && !$form->hasErrors(),
'adresse' => f('adresse'), 'adresse' => f('adresse'),
'code_postal' => f('code_postal'), 'code_postal' => f('code_postal'),
'ville' => f('ville'), 'ville' => f('ville'),
'siret' => f('siret'),
'telephone' => f('telephone'), 'telephone' => f('telephone'),
'email' => f('email') 'email' => f('email')
]); ]);

View File

@ -1,11 +1,15 @@
<?php <?php
namespace Paheko; namespace Paheko;
use Paheko\Users\DynamicFields;
require_once __DIR__ . '/_inc.php'; require_once __DIR__ . '/_inc.php';
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN); $session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN);
$champsPaheko = DynamicFields::getInstance()->listAssocNames();
$champsPaheko = array('' => '- Choisir un champ -') + $champsPaheko;
$form->runIf('save', function () use ($plugin) { $form->runIf('save', function () use ($plugin) {
$plugin->setConfigProperty('rna_asso', trim(f('rna_asso'))); $plugin->setConfigProperty('rna_asso', trim(f('rna_asso')));
$plugin->setConfigProperty('siret_asso', trim(f('siret_asso'))); $plugin->setConfigProperty('siret_asso', trim(f('siret_asso')));
@ -16,18 +20,11 @@ $form->runIf('save', function () use ($plugin) {
$plugin->setConfigProperty('cp_asso', trim(f('cp_asso'))); $plugin->setConfigProperty('cp_asso', trim(f('cp_asso')));
$plugin->setConfigProperty('ville_asso', trim(f('ville_asso'))); $plugin->setConfigProperty('ville_asso', trim(f('ville_asso')));
$plugin->setConfigProperty('droit_art200', (bool)f('droit_art200')); $plugin->setConfigProperty('logo', (bool)f('logo'));
$plugin->setConfigProperty('droit_art238bis', (bool)f('droit_art238bis'));
$plugin->setConfigProperty('droit_art885_0VbisA', (bool)f('droit_art885_0VbisA'));
$plugin->setConfigProperty('objet_0', trim(f('objet_0')));
$plugin->setConfigProperty('objet_1', trim(f('objet_1')));
$plugin->setConfigProperty('objet_2', trim(f('objet_2')));;
$plugin->setConfigProperty('footer', f('footer')); $plugin->setConfigProperty('footer', f('footer'));
$plugin->setConfigProperty('validate_cp', (bool)f('validate_cp')); $plugin->setConfigProperty('validate_cp', (bool)f('validate_cp'));
$plugin->setConfigProperty('unique_client_name', (bool)f('unique_client_name')); $plugin->setConfigProperty('unique_client_name', (bool)f('unique_client_name'));
$plugin->setConfigProperty('pattern', f('pattern')); $plugin->setConfigProperty('pattern', f('pattern'));
$plugin->save(); $plugin->save();
@ -37,5 +34,6 @@ $form->runIf('save', function () use ($plugin) {
$tpl->assign('ok', qg('ok') !== null); $tpl->assign('ok', qg('ok') !== null);
$tpl->assign('conf', $plugin->getConfig()); $tpl->assign('conf', $plugin->getConfig());
$tpl->assign('patterns', \Paheko\Plugin\Facturation\PATTERNS_LIST); $tpl->assign('patterns', \Paheko\Plugin\Facturation\PATTERNS_LIST);
$tpl->assign('champsPaheko', $champsPaheko);
$tpl->display(PLUGIN_ROOT . '/templates/config.tpl'); $tpl->display(PLUGIN_ROOT . '/templates/config.tpl');

View File

@ -18,7 +18,7 @@ if (!$client)
} }
$form->runIf(f('delete') && !$form->hasErrors(), $form->runIf(f('delete') && !$form->hasErrors(),
function () use ($facture, $f) function () use ($facture, $f, $form)
{ {
try { try {
$facture->delete($f->id); $facture->delete($f->id);

View File

@ -12,6 +12,7 @@ $session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_READ);
$list = $facture->list(); $list = $facture->list();
$list->loadFromQueryString(); $list->loadFromQueryString();
$list->setPageSize(50);
$tpl->assign(compact('list')); $tpl->assign(compact('list'));

View File

@ -24,17 +24,36 @@ try
if ($f->receveur_membre) if ($f->receveur_membre)
{ {
$c = $users->get($f->receveur_id); $c = $users->get($f->receveur_id);
// l'identité du membre peut être redéfinie dans la configuration des membres
$name_fields = \Paheko\Users\DynamicFields::getNameFields();
array_walk($name_fields, function(&$elem) use ($c) {
$elem = $c->$elem;
});
$nom_client = implode(" ", $name_fields);
if (preg_match('/^ +$/', $nom_client)) {
$nom_client = "** ABSENT **";
}
// adresse, code postal et ville peuvent être redéfini(e)s dans la configuration du plugin
$adresse_client = $plugin->getConfig('adresse_client');
if ($adresse_client != null && $c->$adresse_client != null) { $c->adresse = $c->$adresse_client; }
$code_postal_client = $plugin->getConfig('code_postal_client');
if ($code_postal_client != null && $c->$code_postal_client != null) { $c->code_postal = $c->$code_postal_client; }
$ville_client = $plugin->getConfig('ville_client');
if ($ville_client != null && $c->$ville_client != null) { $c->ville = $c->$ville_client; }
foreach(['ville','code_postal','adresse'] as $v) foreach(['ville','code_postal','adresse'] as $v)
{ {
if($c->$v == '') if($c->$v == '')
{ {
$c->$v = '[A RENSEIGNER DANS LA FICHE MEMBRE]'; $c->$v = '[À RENSEIGNER DANS LA FICHE MEMBRE]';
} }
} }
} }
else else
{ {
$c = $client->get($f->receveur_id); $c = $client->get($f->receveur_id);
$nom_client = $c->nom;
} }
} }
catch(UserException $e) catch(UserException $e)
@ -58,13 +77,19 @@ if ($f->type_facture != CERFA)
switch ($f->type_facture) switch ($f->type_facture)
{ {
case FACT: case FACT:
$doc = 'Facture n°'. $f->numero; $doc = 'Facture n° '. $f->numero;
$txtemis = $doc . " - Émise le " . $emission;
$txtdest = "Adressée à :";
break; break;
case DEVIS: case DEVIS:
$doc = 'Devis n°'. $f->numero; $doc = 'Devis n° '. $f->numero;
$txtemis = $doc . " - Émis le " . $emission;
$txtdest = "Adressé à :";
break; break;
case COTIS: case COTIS:
$doc = 'Reçu de cotisation n°'. $f->numero; $doc = 'Reçu de cotisation n° '. $f->numero;
$txtemis = $doc . " - Émis le " . $emission;
$txtdest = "Adressé à :";
break; break;
} }
@ -87,20 +112,25 @@ if ($f->type_facture != CERFA)
$adresse = ""; $adresse = "";
} }
$logo='';
if ($plugin->getConfig('logo')) {
$logo = '<img id="logo" src="' . $config->fileURL('logo') . '" />';
}
$asso = $asso =
// 'Émis par :<br><br>'. // 'Émis par :<br><br>'.
'<b>'.$config->get('org_name')."</b><br>". '<b>'.$config->get('org_name')."</b><br>".
$adresse ."<br>". $adresse ."<br>".
(($t = $plugin->getConfig('rna_asso'))?"RNA : $t<br>":''). (($t = $plugin->getConfig('rna_asso'))?"RNA : $t<br>":'').
(($t = $plugin->getConfig('siret_asso'))?"SIRET : $t<br>":''). (($t = $plugin->getConfig('siret_asso'))?"SIRET : " . implode(' ', str_split($t, 3)) . "<br>":'').
(($t = $config->get('email_asso'))?"Email : $t<br>":''). (($t = $config->get('email_asso'))?"Email : $t<br>":'').
(($t = $config->get('site_asso'))?"Site web : $t<br>":''); (($t = $config->get('site_asso'))?"Site web : $t<br>":'');
$receveur = $receveur =
'Adressé à :<br><br>'. $txtdest.'<br>'.
'<b>'.$c->nom.'</b><br>'. '<b>'.$nom_client.'</b><br>'.
$c->adresse."<br>". $c->adresse."<br>".
$c->code_postal.' '.$c->ville."<br>". $c->code_postal.' '.$c->ville."<br>".
(($t = $c->siret)?"SIREN/SIRET : " . implode(' ', str_split($t, 3)) . "<br>":'').
(($t = $c->email)?"Email : $t<br>":''). (($t = $c->email)?"Email : $t<br>":'').
(($t = $c->telephone)?"Tel : $t<br>":''); (($t = $c->telephone)?"Tel : $t<br>":'');
@ -110,7 +140,11 @@ if ($f->type_facture != CERFA)
if ($f->type_facture != COTIS) if ($f->type_facture != COTIS)
{ {
$echeance = ($f->type_facture?'Échéance de paiement':'Échéance du devis')." : ".$echeance; $echeance = ($f->type_facture?'Échéance de paiement':'Échéance du devis')." : ".$echeance;
$reglee = !$f->reglee?'Cette facture est en attente de règlement.':'Cette facture a été reglée.'; if ($f->type_facture == FACT) {
$reglee = !$f->reglee?'Cette facture est en attente de règlement.':'Cette facture a été réglée.';
} else {
$reglee = "";
}
$footer = str_replace("\n", '<br>', $plugin->getConfig('footer') ?? '[Pied de page à configurer]'); $footer = str_replace("\n", '<br>', $plugin->getConfig('footer') ?? '[Pied de page à configurer]');
$ttc = $plugin->getConfig('ttc') ? 'TTC':'HT'; $ttc = $plugin->getConfig('ttc') ? 'TTC':'HT';
@ -163,12 +197,17 @@ EOF;
$echeance <br> $echeance <br>
$reglee $reglee
Moyen de paiement : $moyen_paiement Moyen de paiement : $moyen_paiement
<p> <p>
$footer $footer
</p> </p>
</footer> </footer>
EOF; EOF;
if ($f->type_facture == DEVIS) {
echo <<<EOF
<p><b>Bon pour accord, date et signature<b></p>
EOF;
}
$content = ob_get_clean(); $content = ob_get_clean();
@ -260,6 +299,13 @@ EOF;
width: 40%; width: 40%;
vertical-align: top; vertical-align: top;
} }
.adressage td#logo {
width: 20%;
vertical-align: top;
}
.adressage img#logo {
height : 3cm;
}
.contenuTexte { .contenuTexte {
padding: 0 6mm; padding: 0 6mm;
@ -306,11 +352,12 @@ EOF;
</head> </head>
<body> <body>
<p class="titre"> <p class="titre">
$doc - Émis le $emission $txtemis
</p> </p>
<table class="adressage"> <table class="adressage">
<tr> <tr>
<td id="logo">$logo</td>
<td>$asso</td> <td>$asso</td>
<td>$receveur</td> <td>$receveur</td>
</tr> </tr>
@ -341,7 +388,7 @@ elseif ($f->type_facture == CERFA)
$t['objet1'] = $plugin->getConfig('objet_1'); $t['objet1'] = $plugin->getConfig('objet_1');
$t['objet2'] = $plugin->getConfig('objet_2'); $t['objet2'] = $plugin->getConfig('objet_2');
$t['nom'] = $c->nom; $t['nom'] = $nom_client;
$t['adresse'] = $c->adresse; $t['adresse'] = $c->adresse;
$t['cp'] = $c->code_postal; $t['cp'] = $c->code_postal;
$t['ville'] = $c->ville; $t['ville'] = $c->ville;
@ -512,7 +559,7 @@ if(qg('d') !== null)
{ {
$filename = 'Print'; $filename = 'Print';
if (preg_match('!<title>(.*)</title>!U', $html, $match)) { if (preg_match('!<title>(.*)</title>!U', $html, $match)) {
$filename = trim($match[1]); $filename = str_replace(" ", "_", trim($match[1]));
} }
header('Content-type: application/pdf'); header('Content-type: application/pdf');

View File

@ -21,7 +21,7 @@ CREATE TABLE IF NOT EXISTS plugin_facturation_clients (
adresse TEXT NOT NULL, adresse TEXT NOT NULL,
code_postal TEXT NOT NULL, code_postal TEXT NOT NULL,
ville TEXT NOT NULL, ville TEXT NOT NULL,
-- date_creation INTEGER NOT NULL, siret TEXT,
date_creation TEXT NOT NULL DEFAULT CURRENT_DATE CHECK (date(date_creation) IS NOT NULL AND date(date_creation) = date_creation), -- Date d\'inscription date_creation TEXT NOT NULL DEFAULT CURRENT_DATE CHECK (date(date_creation) IS NOT NULL AND date(date_creation) = date_creation), -- Date d\'inscription
telephone TEXT, telephone TEXT,
email TEXT email TEXT

View File

@ -15,6 +15,7 @@ class Client
'adresse', 'adresse',
'code_postal', 'code_postal',
'ville', 'ville',
'siret',
'telephone', 'telephone',
'email' 'email'
]; ];
@ -39,7 +40,7 @@ class Client
{ {
$data[$key] = trim($data[$key]); $data[$key] = trim($data[$key]);
if($data[$key] == '' && ($key != 'telephone' && $key != 'email')) if($data[$key] == '' && ($key != 'siret' && $key != 'telephone' && $key != 'email'))
{ {
throw new UserException('Le champs '.$key.' doit être renseigné.'); throw new UserException('Le champs '.$key.' doit être renseigné.');
} }
@ -55,6 +56,10 @@ class Client
throw new UserException('Le code postal est erroné.'); throw new UserException('Le code postal est erroné.');
} }
} }
elseif ($key == "siret")
{
$data[$key] = str_replace(' ', '', $data[$key]);
}
elseif ($key == "telephone") elseif ($key == "telephone")
{ {
$data[$key] = Utils::normalizePhoneNumber($data[$key]); $data[$key] = Utils::normalizePhoneNumber($data[$key]);
@ -84,7 +89,7 @@ class Client
if($this->config['unique_client_name'] && isset($data['nom']) && $db->test('plugin_facturation_clients', 'nom = ? COLLATE NOCASE', $data['nom'])) if($this->config['unique_client_name'] && isset($data['nom']) && $db->test('plugin_facturation_clients', 'nom = ? COLLATE NOCASE', $data['nom']))
{ {
throw new UserException('La valeur du champ nom est déjà utilisée, hors ce champ doit être unique à chaque client.'); throw new UserException('La valeur du champ nom est déjà utilisée, or ce champ doit être unique à chaque client.');
} }
$db->insert('plugin_facturation_clients', $data); $db->insert('plugin_facturation_clients', $data);
@ -122,6 +127,9 @@ class Client
'ville' => [ 'ville' => [
'label' => 'Ville', 'label' => 'Ville',
], ],
'siret' => [
'label' => 'SIRET',
],
'telephone' => [ 'telephone' => [
'label' => 'Téléphone', 'label' => 'Téléphone',
], ],
@ -150,7 +158,7 @@ class Client
if($this->config['unique_client_name'] && isset($data['nom']) && $db->test('plugin_facturation_clients', 'nom = ? COLLATE NOCASE AND id != ?', $data['nom'], (int)$id)) if($this->config['unique_client_name'] && isset($data['nom']) && $db->test('plugin_facturation_clients', 'nom = ? COLLATE NOCASE AND id != ?', $data['nom'], (int)$id))
{ {
throw new UserException('La valeur du champ nom est déjà utilisée, hors ce champ doit être unique à chaque client.'); throw new UserException('La valeur du champ nom est déjà utilisée, or ce champ doit être unique à chaque client.');
} }
return $db->update('plugin_facturation_clients', $data, $db->where('id', (int)$id)); return $db->update('plugin_facturation_clients', $data, $db->where('id', (int)$id));

View File

@ -325,6 +325,14 @@ class Facture
public function list(): DynamicList public function list(): DynamicList
{ {
$id_field = \Paheko\Users\DynamicFields::getNameFieldsSQL('u'); $id_field = \Paheko\Users\DynamicFields::getNameFieldsSQL('u');
$plugin_name = preg_replace('/^.*\/(\w+)\/$/', '${1}', \Paheko\PLUGIN_ADMIN_URL);
$plugin = \Paheko\Plugins::get($plugin_name);
// 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; }
$columns = [ $columns = [
// Sélectionner cette colonne, mais ne pas la mettre dans la liste des colonnes // Sélectionner cette colonne, mais ne pas la mettre dans la liste des colonnes
@ -349,15 +357,18 @@ class Facture
], ],
'receveur' => [ 'receveur' => [
'label' => 'Receveur', 'label' => 'Receveur',
'select' => sprintf('CASE WHEN receveur_membre THEN %s ELSE c.nom END', $id_field), // 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),
], ],
'receveur_adresse' => [ 'receveur_adresse' => [
'label' => 'Son adresse', // l'adresse peut être redéfinie dans la configuration du plugin
'select' => 'CASE WHEN receveur_membre THEN u.adresse ELSE c.adresse END', 'label' => 'Adresse',
'select' => sprintf('CASE WHEN receveur_membre THEN %s ELSE c.adresse END', $adresse),
], ],
'receveur_ville' => [ 'receveur_ville' => [
'label' => 'Sa ville', // la ville peut être redéfinie dans la configuration du plugin
'select' => 'CASE WHEN receveur_membre THEN u.ville ELSE c.ville END', 'label' => 'Ville',
'select' => sprintf('CASE WHEN receveur_membre THEN %s ELSE c.ville END', $ville),
], ],
'date_emission' => [ 'date_emission' => [
'label' => 'Émission', 'label' => 'Émission',
@ -391,6 +402,7 @@ class Facture
$list = new DynamicList($columns, $tables); $list = new DynamicList($columns, $tables);
$list->orderBy('date_emission', true); $list->orderBy('date_emission', true);
$list->setCount('COUNT(f.id)');
$currency = Config::getInstance()->monnaie; $currency = Config::getInstance()->monnaie;
@ -420,7 +432,6 @@ class Facture
} }
}); });
$list->setPageSize(1000);
return $list; return $list;
} }
@ -432,7 +443,7 @@ class Facture
if(isset($data['numero']) && $db->test('plugin_facturation_factures', 'numero = ? COLLATE NOCASE AND id != ?', $data['numero'], (int)$id)) if(isset($data['numero']) && $db->test('plugin_facturation_factures', 'numero = ? COLLATE NOCASE AND id != ?', $data['numero'], (int)$id))
{ {
throw new UserException('Un document avec ce numéro existe déjà, hors le numéro doit être unique.'); throw new UserException('Un document avec ce numéro existe déjà, or le numéro doit être unique.');
} }
return $db->update('plugin_facturation_factures', $data, $db->where('id', (int)$id)); return $db->update('plugin_facturation_factures', $data, $db->where('id', (int)$id));
} }

View File

@ -2,7 +2,7 @@ name="Facturation"
description="Permet d'éditer des factures, devis et reçus à ses membres ainsi qu'à une base de clients supplémentaire." description="Permet d'éditer des factures, devis et reçus à ses membres ainsi qu'à une base de clients supplémentaire."
author="zou ; adapté par jce" author="zou ; adapté par jce"
url="https://git.roflcopter.fr/lesanges/paheko-plugin-facturation" url="https://git.roflcopter.fr/lesanges/paheko-plugin-facturation"
version="0.8.3" version="0.11"
menu=true menu=true
restrict_section="accounting" restrict_section="accounting"
restrict_level="read" restrict_level="read"

View File

@ -7,4 +7,15 @@
display: none; display: none;
} }
div.aide {
margin: 1em;
padding: 1em;
}
div.aide ul {
list-style: square;
margin: 1em;
padding: 1em;
}
{/literal} {/literal}

View File

@ -1,21 +1,30 @@
{include file="_head.tpl" title="Aide — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.id} {include file="_head.tpl" title="Aide — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.id}
{include file="%s/templates/_menu.tpl"|args:$plugin_root current="aide"} {include file="%s/templates/_menu.tpl"|args:$plugin_root current="aide"}
<fieldset> <h2>Quelques remarques et conseils sur l'utilisation du plugin Facturation</h2>
<legend><h2>Quelques remarques et conseils sur l'utilisation du plugin Facturation</h2></legend>
<p>Une des premières choses à faire est d'aller dans l'onglet Configuration pour renseigner les valeurs nécessaires à la génération des documents.</p> <div class="aide">
<p>Pour l'instant, il y a des choses encore brouillonnes. Notamment, dans les factures et devis, c'est l'adresse postale renseignée dans la configuration de Paheko qui fait foi plutôt que celle dans le plugin (qui sert en revanche pour les reçus).</p> <p>Une des premières choses à faire est d'aller dans l'onglet {link href="config.php" label="Configuration du plugin"} pour renseigner les valeurs nécessaires à la génération des documents.</p>
<p>Pensez à mettre une image en signature (cela sert pour les reçus fiscaux), cela se passe dans la configuration de Paheko, onglet personnalisation. Il est préférable d'avoir un fond transparent. <fieldset>
</p> <legend>Informations à configurer</legend>
<br> <dl>
<p>- Pour créer un reçu sur une cotisation, il faut pour le moment que cette cotisation soit attachée à la compta.</p> <dt><label>Adresse</label></dt>
<p>- Pour créer un reçu fiscal, l'interface est pour l'instant la même que pour créer une facture/devis. Les champs correspondent mais les noms/labels autour ne sont pas adaptés. Vous pouvez de toutes façons tester, et si le résultat est pas celui attendu, remodifiez derrière :)</p> <dd>Si elle n'est pas remplie, c'est l'adresse qui figure dans la {link href="!config" label="configuration de l'association"} qui sera utilisée</dd>
<p>- La partie « Droit à la réduction d'impôt » peut faire peur, elle correspond simplement à des cases du cerfa pour les reçus fiscaux. Je n'y connais pas grand chose pour le moment, je ne peux vous éclairer davantage, il va falloir se retourner vers légifrance :(</p> <dt><label>Nom et prénom des membres</label></dt>
<br> <dd>Les champs qui définissent l'identité d'un membre peuvent être choisis dans la {link href="!config/users" label="configuration de la fiche membre"} </dd>
<p>Hésitez pas à faire des retours, proposer meilleures explications, ou quoi, vous pouvez venir en causer soit <a href="https://gitlab.com/ramoloss/Paheko-plugin-facturation">sur mon gitlab</a>, soit sur l'adresse d'entraide de Paheko. Si vous êtes un peu dev, le code est un peu cracra mais j'espère que ça vous repoussera pas trop à le bidouiller :)</p> <dt><label>Champs à faire figurer sur la facture</label></dt>
<dd>Permet de choisir les champs pour l'adresse, le code postal et la ville parmi les {link href="!config/fields" label="champs de la fiche membre"}</dd>
</dl>
</fieldset>
</fieldset> <p>Pensez à mettre une image en signature (cela sert pour les reçus fiscaux), cela se passe dans la {link href="!config/custom.php" label="configuration de Paheko, onglet personnalisation"}. Il est préférable d'avoir un fond transparent.
</p>
<ul>
<li>Pour créer un reçu sur une cotisation, il vaut mieux utiliser le module {link href="!config/ext" label="Reçu de paiement"} intégré à Paheko.</li>
<li>Pour créer un reçu fiscal, il vaut mieux utiliser le module {link href="!config/ext" label="Reçus fiscaux"} intégré à Paheko.</li>
</ul>
<p>N'hésitez pas à faire des retours, proposer de meilleures explications dans le forum d'entraide de Paheko. Si vous êtes un peu dev, le code est un peu cracra mais j'espère que ça ne vous repoussera pas trop à le bidouiller :)</p>
</div>
{include file="_foot.tpl"} {include file="_foot.tpl"}

View File

@ -18,6 +18,18 @@
<dt>Code postal</dt> <dt>Code postal</dt>
<dd>{$client.code_postal|escape|rtrim|nl2br}</dd> <dd>{$client.code_postal|escape|rtrim|nl2br}</dd>
<dt>SIREN/SIRET</dt>
<dd>
{if empty($client->siret)}
<em>(Non renseigné)</em>
{else}
<?php
$siret = implode(' ', str_split($client->siret, 3));
?>
{$siret|escape|trim}
{/if}
</dd>
<dt>Adresse électronique</dt> <dt>Adresse électronique</dt>
<dd> <dd>
{if empty($client.email)} {if empty($client.email)}
@ -32,7 +44,7 @@
{if empty($client.telephone)} {if empty($client.telephone)}
<em>(Non renseigné)</em> <em>(Non renseigné)</em>
{else} {else}
<a href="tel:{$client.telephone}">{$client.telephone}</a> <a href="tel:{$client.telephone}">{$client.telephone|format_phone_number}</a>
{/if} {/if}
</dd> </dd>

View File

@ -11,6 +11,7 @@
{input type="text" name="adresse" label="Adresse" required=true source=$client} {input type="text" name="adresse" label="Adresse" required=true source=$client}
{input type="text" name="code_postal" label="Code postal" required=true source=$client} {input type="text" name="code_postal" label="Code postal" required=true source=$client}
{input type="text" name="ville" label="Ville" required=true source=$client} {input type="text" name="ville" label="Ville" required=true source=$client}
{input type="text" name="siret" label="SIREN/SIRET" source=$client}
{input type="tel" name="telephone" label="Téléphone" source=$client} {input type="tel" name="telephone" label="Téléphone" source=$client}
{input type="email" name="email" label="Adresse e-mail" source=$client} {input type="email" name="email" label="Adresse e-mail" source=$client}
</dl> </dl>

View File

@ -13,6 +13,12 @@
{if $key == 'id' || $key == 'nom'} {if $key == 'id' || $key == 'nom'}
<?php continue; ?> <?php continue; ?>
{/if} {/if}
{if $key == 'siret'}
<?php
if (null === $value) { $value = ""; }
$value = implode(' ', str_split($value, 3));
?>
{/if}
<td>{$value}</td> <td>{$value}</td>
{/foreach} {/foreach}
<td class="actions"> <td class="actions">
@ -52,6 +58,7 @@
{input type="text" name="adresse" label="Adresse" required=true} {input type="text" name="adresse" label="Adresse" required=true}
{input type="text" name="code_postal" label="Code postal" required=true} {input type="text" name="code_postal" label="Code postal" required=true}
{input type="text" name="ville" label="Ville" required=true} {input type="text" name="ville" label="Ville" required=true}
{input type="text" name="siret" label="SIREN/SIRET"}
{input type="tel" name="telephone" label="Téléphone"} {input type="tel" name="telephone" label="Téléphone"}
{input type="email" name="email" label="Adresse e-mail"} {input type="email" name="email" label="Adresse e-mail"}
</dl> </dl>

View File

@ -52,8 +52,19 @@
<fieldset> <fieldset>
<legend>Factures</legend> <legend>Factures</legend>
<dl> <dl>
{input type="textarea" class="full-width" rows="10" name="footer" source=$conf label="Pied de document — informations légales" required=true} {input type="checkbox" name="logo" value="1" source=$conf label="Imprimer le logo de l'association"}
{input type="textarea" class="full-width" rows="5" name="footer" source=$conf label="Pied de document — informations légales" required=true}
</dl> </dl>
<fieldset>
<legend>
Choisir les champs à faire figurer sur la facture
</legend>
<dl>
{input type="select" name="adresse_client" label="Adresse" required=true options=$champsPaheko source=$conf}
{input type="select" name="code_postal_client" label="Code postal" required=true options=$champsPaheko source=$conf}
{input type="select" name="ville_client" label="Ville" required=true options=$champsPaheko source=$conf}
</dl>
</fieldset>
</fieldset> </fieldset>
<fieldset> <fieldset>

View File

@ -33,6 +33,7 @@
</tbody> </tbody>
</table> </table>
{$list->getHTMLPagination()|raw}
<p class="help"> <p class="help">
Export de la liste&nbsp;: Export de la liste&nbsp;:

View File

@ -209,3 +209,37 @@ if (version_compare($old_version, '0.8.1', '<'))
{ {
$plugin->unregisterSignal('menu.item'); $plugin->unregisterSignal('menu.item');
} }
// 0.8.5 Ajout champs SIREN/SIRET à la table clients
if (version_compare($old_version, '0.8.5', '<'))
{
$db->exec(<<<EOT
CREATE TABLE IF NOT EXISTS plugin_facturation_clients_tmp
(
id INTEGER PRIMARY KEY,
nom TEXT NOT NULL,
adresse TEXT NOT NULL,
code_postal TEXT NOT NULL,
ville TEXT NOT NULL,
siret TEXT,
date_creation TEXT NOT NULL DEFAULT CURRENT_DATE CHECK (date(date_creation) IS NOT NULL AND date(date_creation) = date_creation),
telephone TEXT,
email TEXT
);
EOT
);
// copier les clients dans la table temporaire en ajoutant un siret fictif
$sql = 'SELECT * FROM plugin_facturation_clients';
foreach ($db->iterate($sql) as $client)
{
$db->insert('plugin_facturation_clients_tmp', $client);
}
// remplacer l'ancienne table par la nouvelle
$db->exec(<<<EOT
DROP TABLE plugin_facturation_clients;
ALTER TABLE plugin_facturation_clients_tmp RENAME TO plugin_facturation_clients;
EOT
);
}