diff --git a/CHANGELOG b/CHANGELOG index e42abea..e4e5dae 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,11 @@ +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 diff --git a/README.md b/README.md index 1829668..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/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 ecc0c30..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 { @@ -67,15 +89,21 @@ $form->runIf(f('save') && !$form->hasErrors(), '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) + foreach(f('designation') as $k=>$value) { - if ($value != '' && f('prix')[$k] == null) { + 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['contenu'][$k]['designation'] = $value; @@ -84,7 +112,14 @@ $form->runIf(f('save') && !$form->hasErrors(), } $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'); @@ -120,45 +155,45 @@ $form->runIf(f('save') && !$form->hasErrors(), $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()) { @@ -246,6 +281,9 @@ else $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; @@ -292,8 +330,10 @@ if (in_array($radio['type'], [DEVIS, FACT])) } } else { - $designations = ['Exemple']; - $prix = [250]; + /* + $designations = ['Exemple']; + $prix = [250]; + */ } } @@ -301,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('%s', $name, $current_value, $currency); } diff --git a/admin/client_modifier.php b/admin/client_modifier.php index c3b8e3a..587f9f3 100644 --- a/admin/client_modifier.php +++ b/admin/client_modifier.php @@ -17,27 +17,29 @@ if (!$c) } $form->runIf(f('save') && !$form->hasErrors(), - function () use ($client, $id, $form) - { - try - { - $r = $client->edit($id,[ - 'nom' => f('nom'), - 'adresse' => f('adresse'), - 'code_postal' => f('code_postal'), - 'ville' => f('ville'), - 'siret' => f('siret'), - 'telephone' => f('telephone'), - 'email' => f('email') - ]); + function () use ($client, $id, $form) + { + try + { + $r = $client->edit($id,[ + 'nom' => f('nom'), + 'adresse' => f('adresse'), + 'code_postal' => f('code_postal'), + 'ville' => f('ville'), + 'siret' => f('siret'), + 'telephone' => f('telephone'), + 'email' => f('email'), + 'nom_contact' => f('nom_contact'), + 'note' => f('note') + ]); - $r ? Utils::redirect(PLUGIN_ADMIN_URL . 'client.php?id='.(int)$id):''; - } - catch (UserException $e) - { - $form->addError($e->getMessage()); - } - }, 'edit_client'); + $r ? Utils::redirect(PLUGIN_ADMIN_URL . 'client.php?id='.(int)$id):''; + } + catch (UserException $e) + { + $form->addError($e->getMessage()); + } + }, 'edit_client'); $tpl->assign('client', $c); diff --git a/admin/clients.php b/admin/clients.php index 24fe497..ae8808f 100644 --- a/admin/clients.php +++ b/admin/clients.php @@ -18,7 +18,9 @@ $form->runIf(f('add') && !$form->hasErrors(), 'ville' => f('ville'), 'siret' => f('siret'), 'telephone' => f('telephone'), - 'email' => f('email') + 'email' => f('email'), + 'nom_contact' => f('nom_contact'), + 'note' => f('note') ]); $id ? Utils::redirect(PLUGIN_ADMIN_URL . 'client.php?id='.(int)$id):''; diff --git a/admin/config.php b/admin/config.php index 0987bde..638cd56 100644 --- a/admin/config.php +++ b/admin/config.php @@ -1,11 +1,15 @@ requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN); +$champsPaheko = DynamicFields::getInstance()->listAssocNames(); +$champsPaheko = array('' => '- Choisir un champ -') + $champsPaheko; + $form->runIf('save', function () use ($plugin) { $plugin->setConfigProperty('rna_asso', trim(f('rna_asso'))); $plugin->setConfigProperty('siret_asso', trim(f('siret_asso'))); @@ -16,18 +20,15 @@ $form->runIf('save', function () use ($plugin) { $plugin->setConfigProperty('cp_asso', trim(f('cp_asso'))); $plugin->setConfigProperty('ville_asso', trim(f('ville_asso'))); - $plugin->setConfigProperty('droit_art200', (bool)f('droit_art200')); - $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('logo', (bool)f('logo')); $plugin->setConfigProperty('footer', f('footer')); - + + $plugin->setConfigProperty('adresse_client', f('adresse_client')); + $plugin->setConfigProperty('code_postal_client', f('code_postal_client')); + $plugin->setConfigProperty('ville_client', f('ville_client')); + $plugin->setConfigProperty('validate_cp', (bool)f('validate_cp')); $plugin->setConfigProperty('unique_client_name', (bool)f('unique_client_name')); - $plugin->setConfigProperty('pattern', f('pattern')); $plugin->save(); @@ -37,5 +38,6 @@ $form->runIf('save', function () use ($plugin) { $tpl->assign('ok', qg('ok') !== null); $tpl->assign('conf', $plugin->getConfig()); $tpl->assign('patterns', \Paheko\Plugin\Facturation\PATTERNS_LIST); +$tpl->assign('champsPaheko', $champsPaheko); -$tpl->display(PLUGIN_ROOT . '/templates/config.tpl'); \ No newline at end of file +$tpl->display(PLUGIN_ROOT . '/templates/config.tpl'); diff --git a/admin/facture.php b/admin/facture.php index e0044b5..e375bf3 100644 --- a/admin/facture.php +++ b/admin/facture.php @@ -21,7 +21,7 @@ if (!$f) } $tpl->assign('type', $f->type_facture); -$tpl->assign('facture', $f); +$tpl->assign('facture', $f); $tpl->assign('id', $id); $tpl->assign('footer', $plugin->getConfig('footer')?:''); $tpl->assign('siret_asso', $plugin->getConfig('siret_asso')?:''); diff --git a/admin/pdf.php b/admin/pdf.php index b2e6d50..d953414 100644 --- a/admin/pdf.php +++ b/admin/pdf.php @@ -24,17 +24,36 @@ try if ($f->receveur_membre) { $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) { if($c->$v == '') { - $c->$v = '[A RENSEIGNER DANS LA FICHE MEMBRE]'; + $c->$v = '[À RENSEIGNER DANS LA FICHE MEMBRE]'; } } } else { $c = $client->get($f->receveur_id); + $nom_client = $c->nom; } } catch(UserException $e) @@ -58,20 +77,20 @@ if ($f->type_facture != CERFA) switch ($f->type_facture) { case FACT: - $doc = 'Facture n° '. $f->numero; - $txtemis = $doc . " - Émise le " . $emission; - $txtdest = "Adressée à :"; - break; + $doc = 'Facture n° '. $f->numero; + $txtemis = $doc . " - Émise le " . $emission; + $txtdest = "Adressée à :"; + break; case DEVIS: - $doc = 'Devis n° '. $f->numero; - $txtemis = $doc . " - Émis le " . $emission; - $txtdest = "Adressé à :"; - break; + $doc = 'Devis n° '. $f->numero; + $txtemis = $doc . " - Émis le " . $emission; + $txtdest = "Adressé à :"; + break; case COTIS: - $doc = 'Reçu de cotisation n° '. $f->numero; - $txtemis = $doc . " - Émis le " . $emission; - $txtdest = "Adressé à :"; - break; + $doc = 'Reçu de cotisation n° '. $f->numero; + $txtemis = $doc . " - Émis le " . $emission; + $txtdest = "Adressé à :"; + break; } // utiliser l'adresse configurée dans le plugin sinon celle de l'asso sinon rien ! @@ -93,6 +112,10 @@ if ($f->type_facture != CERFA) $adresse = ""; } + $logo=''; + if ($plugin->getConfig('logo')) { + $logo = ''; + } $asso = // 'Émis par :

'. ''.$config->get('org_name')."
". @@ -104,20 +127,29 @@ if ($f->type_facture != CERFA) $receveur = $txtdest.'
'. - ''.$c->nom.'
'. + ''.$nom_client.'
'. + (($t = $f->nom_contact)?"Contact : $t
":''). $c->adresse."
". $c->code_postal.' '.$c->ville."
". (($t = $c->siret)?"SIREN/SIRET : " . implode(' ', str_split($t, 3)) . "
":''). (($t = $c->email)?"Email : $t
":''). (($t = $c->telephone)?"Tel : $t
":''); - + if ($f->type_facture == FACT) { + $receveur .= + (($t = $f->numero_commande)?"Commande N° : $t
":''). + (($t = $f->reference_acheteur)?"Référence : $t
":''); + } $total = Utils::money_format($f->total, ',', ' '); // Devis et facture if ($f->type_facture != COTIS) { $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é réglé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", '
', $plugin->getConfig('footer') ?? '[Pied de page à configurer]'); $ttc = $plugin->getConfig('ttc') ? 'TTC':'HT'; @@ -145,7 +177,7 @@ EOF; $i = 1; foreach($f->contenu as $k=>$v) - { + { echo ''; echo str_replace("\n", '
', $v['designation']); echo ''; @@ -170,12 +202,17 @@ EOF; $echeance
$reglee Moyen de paiement : $moyen_paiement -

$footer

+ EOF; + if ($f->type_facture == DEVIS) { + echo <<Bon pour accord, date et signature

+EOF; + } $content = ob_get_clean(); @@ -184,7 +221,7 @@ EOF; { $lieu = $plugin->getConfig('ville_asso'); $intitule = $f->contenu['intitule']; - + $souscription = date('d/m/Y', strtotime($f->contenu['souscription'])); if($f->contenu['expiration'] == '1970-01-01') @@ -220,7 +257,7 @@ EOF; } //-- Layout du document - + ob_start(); echo << @@ -233,7 +270,7 @@ EOF; size: A4 portrait; margin: 0; } - + body { padding: 4mm; font-family: Helvetica, Arial, sans; @@ -267,6 +304,13 @@ EOF; width: 40%; vertical-align: top; } + .adressage td#logo { + width: 20%; + vertical-align: top; + } + .adressage img#logo { + height : 3cm; + } .contenuTexte { padding: 0 6mm; @@ -318,6 +362,7 @@ EOF; + @@ -348,7 +393,7 @@ elseif ($f->type_facture == CERFA) $t['objet1'] = $plugin->getConfig('objet_1'); $t['objet2'] = $plugin->getConfig('objet_2'); - $t['nom'] = $c->nom; + $t['nom'] = $nom_client; $t['adresse'] = $c->adresse; $t['cp'] = $c->code_postal; $t['ville'] = $c->ville; @@ -363,7 +408,7 @@ elseif ($f->type_facture == CERFA) $t['forme'] = $f->contenu['forme']; $t['nature'] = $f->contenu['nature']; $t['texte'] = $libelles[$f->contenu['texte']]; - + $t['art200'] = $t['art238'] = $t['art885'] = ''; if($plugin->getConfig('droit_art200')){ $t['art200'] = 'X'; @@ -429,7 +474,7 @@ elseif ($f->type_facture == CERFA) margin: 0; padding: 0; } - + body { font-family: Helvetica, Arial, sans; font-size: 10pt; @@ -451,11 +496,11 @@ elseif ($f->type_facture == CERFA) background-size: cover; background-position: -5mm -4.8mm; } - + #p1 { background-image: url('{$url}p/facturation/cerfa-1.png'); } - + #p2 { background-image: url('{$url}p/facturation/cerfa-2.png'); position: relative; @@ -519,7 +564,7 @@ if(qg('d') !== null) { $filename = 'Print'; if (preg_match('!(.*)!U', $html, $match)) { - $filename = trim($match[1]); + $filename = str_replace(" ", "_", trim($match[1])); } header('Content-type: application/pdf'); diff --git a/data/schema.sql b/data/schema.sql index 0cc7a67..923b4eb 100644 --- a/data/schema.sql +++ b/data/schema.sql @@ -10,7 +10,10 @@ CREATE TABLE IF NOT EXISTS plugin_facturation_factures ( archivee INTEGER DEFAULT 0, -- bool moyen_paiement TEXT NOT NULL, contenu TEXT NOT NULL, - total INTEGER DEFAULT 0 + total INTEGER DEFAULT 0, + nom_contact TEXT, + numero_commande TEXT, + reference_acheteur TEXT -- FOREIGN KEY(moyen_paiement) REFERENCES compta_moyens_paiement(code) ); @@ -24,7 +27,9 @@ CREATE TABLE IF NOT EXISTS plugin_facturation_clients ( 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 telephone TEXT, - email TEXT + email TEXT, + nom_contact TEXT, + note TEXT ); @@ -43,6 +48,7 @@ INSERT OR IGNORE INTO plugin_facturation_paiement (code, nom) VALUES ('PR', 'Pr INSERT OR IGNORE INTO plugin_facturation_paiement (code, nom) VALUES ('TI', 'TIP'); INSERT OR IGNORE INTO plugin_facturation_paiement (code, nom) VALUES ('VI', 'Virement'); INSERT OR IGNORE INTO plugin_facturation_paiement (code, nom) VALUES ('HA', 'HelloAsso'); +INSERT OR IGNORE INTO plugin_facturation_paiement (code, nom) VALUES ('MO', 'Mollie'); INSERT OR IGNORE INTO plugin_facturation_paiement (code, nom) VALUES ('AU', 'Autre'); -- Modif DD -- ajout de la table des textes associés aux CERFA diff --git a/lib/Client.php b/lib/Client.php index 66aabf3..66e7d75 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -17,7 +17,9 @@ class Client 'ville', 'siret', 'telephone', - 'email' + 'email', + 'nom_contact', + 'note' ]; private $config = [ @@ -40,7 +42,7 @@ class Client { $data[$key] = trim($data[$key]); - if($data[$key] == '' && ($key != 'siret' && $key != 'telephone' && $key != 'email')) + if($data[$key] == '' && ! in_array($key, ['siret', 'telephone', 'email', 'nom_contact', 'note'])) { throw new UserException('Le champs '.$key.' doit être renseigné.'); } @@ -136,6 +138,12 @@ class Client 'email' => [ 'label' => 'E-Mail', ], + 'nom_contact' => [ + 'label' => 'Contact', + ], + 'note' => [ + 'label' => 'Note', + ], 'nb_documents' => [ 'label' => 'Nombre de documents', 'select' => '(SELECT COUNT(*) FROM plugin_facturation_factures WHERE receveur_id = c.id)', diff --git a/lib/Facture.php b/lib/Facture.php index 0776728..270fca7 100644 --- a/lib/Facture.php +++ b/lib/Facture.php @@ -30,31 +30,24 @@ class Facture 'archivee', 'moyen_paiement', 'contenu', - 'total' + 'total', + 'nom_contact', + 'numero_commande', + 'reference_acheteur' ]; public $types = [ - DEVIS => [ - 'id' => DEVIS, - 'accounts' => [], - 'label' => 'Devis', - 'help' => ''], - FACT => [ - 'id' => FACT, - 'accounts' => [], - 'label' => 'Facture', - 'help' => ''], - CERFA => [ - 'id' => CERFA, - 'accounts' => [], - 'label' => 'Reçu fiscal', - 'help' => 'Reçu fiscal pour un don (membre ou client)'], - COTIS => [ - 'id' => COTIS, - 'accounts' => [], - 'label' => 'Reçu de cotisation', - 'help' => 'Reçu pour une cotisation payée par un·e membre'], - ]; + DEVIS => [ + 'id' => DEVIS, + 'accounts' => [], + 'label' => 'Devis', + 'help' => ''], + FACT => [ + 'id' => FACT, + 'accounts' => [], + 'label' => 'Facture', + 'help' => ''], + ]; public function __construct() { @@ -75,116 +68,119 @@ class Facture if(!is_array($data) && null !== $data){ $datas[$k] = trim($data); } - if ($datas[$k] === '' && $k != 'numero') + if ($datas[$k] === '' && ! in_array($k, ['numero', 'nom_contact', 'numero_commande', 'reference_acheteur'])) { throw new UserException("La valeur de $k est vide"); } - + switch($k) { case 'type_facture': - if (!array_key_exists($datas[$k], $this->types)) { - 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; + if (!array_key_exists($datas[$k], $this->types)) { + 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': case 'archivee': - if ($datas[$k] != 1 && $datas[$k] != 0) { - throw new UserException("$k est de valeur non-attendue ($data)."); - } - break; + if ($datas[$k] != 1 && $datas[$k] != 0) { + throw new UserException("$k est de valeur non-attendue ($data)."); + } + break; case 'receveur_id': - if (!is_numeric($datas[$k]) || $datas[$k] < 0) { - throw new UserException("L'id du receveur est non-attendu ($data)."); - } - break; + if (!is_numeric($datas[$k]) || $datas[$k] < 0) { + throw new UserException("L'id du receveur est non-attendu ($data)."); + } + break; case 'date_emission': - $datas[$k] = \DateTime::createFromFormat('!d/m/Y', $data)->format('Y-m-d'); - break; + $datas[$k] = \DateTime::createFromFormat('!d/m/Y', $data)->format('Y-m-d'); + 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')) - { - throw new UserException("La date d'échéance est antérieure à la date d'émission ($data)."); - } - break; + $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')) + { + throw new UserException("La date d'échéance est antérieure à la date d'émission ($data)."); + } + break; case 'moyen_paiement': - if (!array_key_exists($datas[$k], $this->listMoyensPaiement())) { - throw new UserException("Le moyen de paiement ne correspond pas à la liste interne ($data)."); - } - break; + if (!array_key_exists($datas[$k], $this->listMoyensPaiement())) { + throw new UserException("Le moyen de paiement ne correspond pas à la liste interne ($data)."); + } + break; case 'contenu': - if ($fac) + if ($fac) + { + if (!is_array($datas[$k]) || empty($datas[$k])) { + throw new UserException("Le contenu du document est vide ($data)."); + } + $total = 0; + foreach($datas[$k] as $g => $r) { - if (!is_array($datas[$k]) || empty($datas[$k])) { - throw new UserException("Le contenu du document est vide ($data)."); - } - $total = 0; - foreach($datas[$k] as $g => $r) + if (empty($r['designation']) && empty($r['prix'])) { - if (empty($r['designation']) && empty($r['prix'])) - { - unset($datas[$k][$g]); - unset($datas[$k]['prix']); - continue; - } - elseif (empty($r['prix'])) - { - $datas[$k]['prix'] = 0; - } - - if (!is_int($r['prix'])) - { - throw new UserException('Un (ou plus) des prix n\'est pas un entier.'); - } - - $total += $r['prix']; + unset($datas[$k][$g]); + unset($datas[$k]['prix']); + continue; } - - if($fac && !$total) + elseif (! is_numeric($r['prix']) && empty($r['prix'])) { - throw new UserException("Toutes les désignations/prix sont vides."); + $datas[$k]['prix'] = 0; + } + elseif (empty($r['designation'])) { + throw new UserException("Une au moins des désignations est absente."); } - } - elseif ($cerfa) - { + if (!is_int($r['prix'])) + { + throw new UserException('Un (ou plus) des prix n\'est pas un entier.'); + } + + $total += $r['prix']; } - elseif ($recu) + + if ($fac && count($datas['contenu']) == 0) { - // $fields = ['id', 'intitule', 'date', 'expiration']; - // foreach ($datas[$k]as $) + throw new UserException("Toutes les désignations/prix sont vides."); } - $datas[$k] = json_encode($datas[$k]); - break; + } + elseif ($cerfa) + { + + } + elseif ($recu) + { + // $fields = ['id', 'intitule', 'date', 'expiration']; + // foreach ($datas[$k]as $) + } + $datas[$k] = json_encode($datas[$k]); + break; case 'total': - if ($cerfa && $datas[$k] < 1) { - throw new UserException('Le total ne peut être inférieur à 1€ pour les reçus (bug encore non résolu).'); - } - if ($fac && !isset($datas['contenu'])) { - throw new UserException("Pas de contenu fourni pour vérifier le total."); - } - if ($fac && $total != $datas[$k]) - { - throw new UserException("Les totaux sont différents ($total != $datas[$k]."); - } - break; + if ($cerfa && $datas[$k] < 1) { + throw new UserException('Le total ne peut être inférieur à 1€ pour les reçus (bug encore non résolu).'); + } + if ($fac && !isset($datas['contenu'])) { + throw new UserException("Pas de contenu fourni pour vérifier le total."); + } + if ($fac && $total != $datas[$k]) + { + throw new UserException("Les totaux sont différents ($total != $datas[$k]."); + } + break; } } } @@ -287,7 +283,7 @@ class Facture $r = $db->first('SELECT * FROM plugin_facturation_factures WHERE id = ? LIMIT 1;', (int)$id); if(!$r) - { + { throw new UserException("Pas de document retournée avec cet id."); } @@ -325,6 +321,14 @@ class Facture public function list(): DynamicList { $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 = [ // Sélectionner cette colonne, mais ne pas la mettre dans la liste des colonnes @@ -349,15 +353,18 @@ class Facture ], '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' => [ + // l'adresse peut être redéfinie dans la configuration du plugin 'label' => 'Adresse', - 'select' => 'CASE WHEN receveur_membre THEN u.adresse ELSE c.adresse END', + 'select' => sprintf('CASE WHEN receveur_membre THEN %s ELSE c.adresse END', $adresse), ], 'receveur_ville' => [ + // la ville peut être redéfinie dans la configuration du plugin 'label' => 'Ville', - 'select' => 'CASE WHEN receveur_membre THEN u.ville ELSE c.ville END', + 'select' => sprintf('CASE WHEN receveur_membre THEN %s ELSE c.ville END', $ville), ], 'date_emission' => [ 'label' => 'Émission', @@ -391,7 +398,6 @@ class Facture $list = new DynamicList($columns, $tables); $list->orderBy('date_emission', true); - $list->setCount('COUNT(f.id)'); $currency = Config::getInstance()->monnaie; @@ -406,8 +412,8 @@ class Facture if ($row->type_facture == COTIS && isset($content->intitule, $content->souscription)) { $row->contenu = sprintf("Cotisation %s\nSouscrite le %s", - $content->intitule, - Utils::date_fr($content->souscription, 'd/m/Y') + $content->intitule, + Utils::date_fr($content->souscription, 'd/m/Y') ); } elseif ($row->type_facture != CERFA) { @@ -492,7 +498,7 @@ class Facture public $recu_fields = ['id', 'label', 'amount', 'date', 'expiry', 'paid', 'paid_amount']; - public function getCotis(int $user_id, int $su_id = null) + public function getCotis(int $user_id, ?int $su_id = null) { $where = 'WHERE su.id_user = ?'; if (null !== $su_id) @@ -506,7 +512,7 @@ class Facture LEFT JOIN services_fees sf ON sf.id = su.id_fee LEFT JOIN acc_transactions_users tu ON tu.id_service_user = su.id LEFT JOIN acc_transactions_lines tl ON tl.id_transaction = tu.id_transaction - '.$where.' + '.$where.' GROUP BY su.id ORDER BY su.date;'; @@ -526,24 +532,24 @@ class Facture return $db->getGrouped($query); } } - + /* modif DD -- lecture et retour des textes de CERFA -- */ public function listTextesCerfa($menu = true) { $db = DB::getInstance(); - + $sel = ($menu) ? 'id, menu' : 'id, texte'; $query = 'SELECT '.$sel.' FROM "plugin_facturation_txt_cerfa" WHERE 1 ORDER BY id ;'; return $db->getAssoc($query); } - + public function getMoyenPaiement($code) { $db = DB::getInstance(); return $db->firstColumn('SELECT nom FROM plugin_facturation_paiement WHERE code = ?;', $code); } - + public function delete($id) { return DB::getInstance()->delete('plugin_facturation_factures', 'id = '. (int)$id); diff --git a/plugin.ini b/plugin.ini index e4cd258..c75a449 100644 --- a/plugin.ini +++ b/plugin.ini @@ -1,8 +1,8 @@ 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 et devis à ses membres ainsi qu'à une base de clients supplémentaire." author="zou ; adapté par jce" url="https://git.roflcopter.fr/lesanges/paheko-plugin-facturation" -version="0.8.7" +version="0.16" menu=true restrict_section="accounting" restrict_level="read" diff --git a/templates/_form.tpl b/templates/_form.tpl index 3e6029d..f533acf 100644 --- a/templates/_form.tpl +++ b/templates/_form.tpl @@ -3,7 +3,7 @@
- Type d'écriture + Type de document
{foreach from=$types_details item="type"}
@@ -24,15 +24,13 @@
Créer un devis Créer une facture - Créer un reçu fiscal - Créer un reçu de cotisation
{input type="text" name="numero_facture" maxlength=18 label="Numéro du document" required=$require_number source=$doc}
{if $require_number} - Chaque document doit comporter un numéro unique délivré chronologiquement et de façon continue. Il faut que le système adopté par l'association garantisse que deux factures émises la même année ne peuvent pas porter le même numéro. + Chaque document doit comporter un numéro unique délivré chronologiquement et de façon continue. Il faut que le système adopté par l'association garantisse que deux factures émises la même année ne puissent pas porter le même numéro. {else} Laisser vide pour qu'un numéro soit automatiquement affecté au format {$number_pattern}. {/if} @@ -49,7 +47,7 @@
- + {input type="checkbox" name="reglee" value="1" label="Réglée" source=$doc data-types="t1"}
{input type="checkbox" name="archivee" value="1" label="Archivée" source=$doc disabled="disabled"} @@ -59,7 +57,7 @@
- Client + Destinataire
@@ -72,18 +70,30 @@
- {input type="select" name="membre" label="Membre" options=$users required=1 source=$doc} + {input type="select" name="membre" label="Membre" options=$users required=1 source=$doc default_empty="— Choisir un membre —"}
{if !empty($clients)}
- {input type="select" name="client" label="Client" options=$clients required=1 class="type_client" source=$doc} + {input type="select" name="client" label="Client" options=$clients required=1 class="type_client" source=$doc default_empty="— Choisir un client —"}
{else} {/if}
+
+ Autres informations + {input type="text" name="nom_contact" label="Nom du contact" source=$doc} + +
+ {input type="text" name="numero_commande" label="Numéro de commande" source=$doc} + {input type="text" name="reference_acheteur" label="Référence acheteur" source=$doc} +
+
+
Contenu @@ -113,14 +123,14 @@
- {foreach from=$designations item=designation key=key} + {foreach from=$designations item=designation key=key} {money_fac value=$prix[$key] user=$from_user} {/foreach} - {else} + {else} {money_fac name="prix_tpl[]"} @@ -139,7 +149,7 @@ - +
Contenu
diff --git a/templates/_js.tpl b/templates/_js.tpl index 301ddae..ed7feeb 100644 --- a/templates/_js.tpl +++ b/templates/_js.tpl @@ -19,8 +19,8 @@ function plus(){ var newdiv = document.createElement('tr'); newdiv.innerHTML = document.getElementById('Line1').innerHTML; - newdiv.getElementsByTagName('textarea')[0].setAttribute('name', 'designation[]'); - newdiv.getElementsByTagName('input')[0].setAttribute('name', 'prix[]'); + newdiv.getElementsByTagName('textarea')[0].setAttribute('name', 'designation[]'); + newdiv.getElementsByTagName('input')[0].setAttribute('name', 'prix[]'); newdiv.querySelector('.fact_rm_line button').onclick = function(){ this.parentNode.parentNode.parentNode.removeChild(this.parentNode.parentNode); updateSum(); @@ -33,12 +33,12 @@ $('#ajouter_ligne').onclick = plus; - a = document.querySelectorAll('[name="remove_line"]'); + a = document.querySelectorAll('[name="remove_line"]'); l = a.length; for(i = 0; i < l; i++) { a[i].onclick = function(){ this.parentNode.parentNode.parentNode.removeChild(this.parentNode.parentNode); - updateSum(); + updateSum(); }; } @@ -51,6 +51,18 @@ } } + function modifierContact(client, idlist, idcontact) + { + let contactlist = document.querySelector(idlist); + let options = contactlist.querySelectorAll('option'); + for (i=0; i < options.length; ++i) { + if (options[i].value == client.value) { + break; + } + } + document.querySelector(idcontact).value = options[i].textContent; + } + const form = document.querySelector('#f_numero_facture').form; changeTypeSaisie(form.base_receveur.value); @@ -63,6 +75,11 @@ }; } + const selclient = document.querySelector('#f_client'); + selclient.addEventListener("change", () => { + modifierContact(selclient, '#f_contact_list', '#f_nom_contact'); + }); + } ()); @@ -99,4 +116,4 @@ hideAllTypes(); {/literal} selectType({$radio.type}); - \ No newline at end of file + diff --git a/templates/_style.css b/templates/_style.css index 89a586e..f860f56 100644 --- a/templates/_style.css +++ b/templates/_style.css @@ -7,4 +7,15 @@ display: none; } -{/literal} \ No newline at end of file +div.aide { + margin: 1em; + padding: 1em; +} + +div.aide ul { + list-style: square; + margin: 1em; + padding: 1em; +} + +{/literal} diff --git a/templates/aide.tpl b/templates/aide.tpl index e947f09..f84b14d 100644 --- a/templates/aide.tpl +++ b/templates/aide.tpl @@ -1,21 +1,32 @@ -{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.name} {include file="%s/templates/_menu.tpl"|args:$plugin_root current="aide"} -
-

Quelques remarques et conseils sur l'utilisation du plugin Facturation

+

Quelques remarques et conseils sur l'utilisation du plugin Facturation

-

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.

-

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).

-

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. -

-
-

- Pour créer un reçu sur une cotisation, il faut pour le moment que cette cotisation soit attachée à la compta.

-

- 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 :)

-

- 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 :(

-
-

Hésitez pas à faire des retours, proposer meilleures explications, ou quoi, vous pouvez venir en causer soit sur mon gitlab, 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 :)

+
+

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.

+
+ Informations à configurer +
+
+
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
+
+
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"}
+
+
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"}
+
+
-
+{* +

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. +

+*} +
    +
  • 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.
  • +
  • Pour créer un reçu fiscal, il vaut mieux utiliser le module {link href="!config/ext" label="Reçus fiscaux"} intégré à Paheko.
  • +
+

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 :)

+ -{include file="_foot.tpl"} \ No newline at end of file +{include file="_foot.tpl"} diff --git a/templates/client.tpl b/templates/client.tpl index 6b8db5b..f9d5609 100644 --- a/templates/client.tpl +++ b/templates/client.tpl @@ -1,4 +1,4 @@ -{include file="_head.tpl" title="Client — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.id js=0} +{include file="_head.tpl" title="Client — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.name js=0} {include file="%s/templates/_menu_client.tpl"|args:$plugin_root current="client"}
@@ -48,6 +48,24 @@ {/if} +
Nom du contact
+
+ {if empty($client.nom_contact)} + (Non renseigné) + {else} + {$client.nom_contact} + {/if} +
+ +
Note
+
+ {if empty($client.note)} + (Non renseigné) + {else} + {$client.note} + {/if} +
+
Date d'ajout
{$client.date_creation|date:'d/m/Y'}
@@ -94,4 +112,4 @@

Ce client n'a pas de document associé.

{/if} -{include file="_foot.tpl"} \ No newline at end of file +{include file="_foot.tpl"} diff --git a/templates/client_modifier.tpl b/templates/client_modifier.tpl index 42e6f1c..62f713b 100644 --- a/templates/client_modifier.tpl +++ b/templates/client_modifier.tpl @@ -1,4 +1,4 @@ -{include file="_head.tpl" title="Modifier un client — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.id js=0} +{include file="_head.tpl" title="Modifier un client — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.name js=0} {include file="%s/templates/_menu_client.tpl"|args:$plugin_root current="client_modifier"} {form_errors} @@ -14,6 +14,8 @@ {input type="text" name="siret" label="SIREN/SIRET" 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="text" name="nom_contact" label="Nom du contact" source=$client} + {input type="textarea" cols="60" rows="3" name="note" label="Note" source=$client}
@@ -24,4 +26,4 @@ -{include file="_foot.tpl"} \ No newline at end of file +{include file="_foot.tpl"} diff --git a/templates/client_supprimer.tpl b/templates/client_supprimer.tpl index de3becb..5759d2f 100644 --- a/templates/client_supprimer.tpl +++ b/templates/client_supprimer.tpl @@ -1,4 +1,4 @@ -{include file="_head.tpl" title="Supprimer un client — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.id js=0} +{include file="_head.tpl" title="Supprimer un client — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.name js=0} {include file="%s/templates/_menu_client.tpl"|args:$plugin_root current="client_supprimer"} {if !$deletable} @@ -12,4 +12,4 @@ {/if} -{include file="_foot.tpl"} \ No newline at end of file +{include file="_foot.tpl"} diff --git a/templates/clients.tpl b/templates/clients.tpl index f0b037b..2c30760 100644 --- a/templates/clients.tpl +++ b/templates/clients.tpl @@ -1,4 +1,4 @@ -{include file="_head.tpl" title="Clients — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.id js=1} +{include file="_head.tpl" title="Clients — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.name js=1} {include file="%s/templates/_menu.tpl"|args:$plugin_root current="clients"} {if $list->count()} @@ -14,7 +14,7 @@ {/if} {if $key == 'siret'} - @@ -61,6 +61,8 @@ {input type="text" name="siret" label="SIREN/SIRET"} {input type="tel" name="telephone" label="Téléphone"} {input type="email" name="email" label="Adresse e-mail"} + {input type="text" name="nom_contact" label="Nom contact"} + {input type="textarea" cols="60" rows="3" name="note" label="Note"} diff --git a/templates/config.tpl b/templates/config.tpl index de55d71..5bde404 100644 --- a/templates/config.tpl +++ b/templates/config.tpl @@ -1,4 +1,4 @@ -{include file="_head.tpl" title="Configuration — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.id} +{include file="_head.tpl" title="Configuration — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.name} {include file="%s/templates/_menu.tpl"|args:$plugin_root current="config"} {if $ok && !$form->hasErrors()} @@ -19,7 +19,10 @@
- Adresse + Adresse de l'association +
+ à saisir uniquement si elle n'est pas dans la configuration de la compta ou si elle doit remplacer celle qui est définie dans la configuration de la compta +
{input type="text" name="numero_rue_asso" source=$conf label="Numéro de rue" maxlength=5} {input type="text" name="rue_asso" source=$conf label="Nom de rue"} @@ -27,33 +30,24 @@ {input type="text" name="ville_asso" source=$conf label="Ville"}
-
- Objet -
-
obligatoire pour reçus fiscaux
- {input type="text" name="objet_0" source=$conf label="Ligne 1" maxlength=95} - {input type="text" name="objet_1" source=$conf label="Ligne 2" maxlength=95} - {input type="text" name="objet_2" source=$conf label="Ligne 3" maxlength=95} -
-
- -
- Droit à la réduction d'impôt -
-
obligatoire pour reçus fiscaux
- {input type="checkbox" name="droit_art200" value="1" source=$conf label="Article 200"} - {input type="checkbox" name="droit_art238bis" value="1" source=$conf label="Article 238 bis"} - {input type="checkbox" name="droit_art885_0VbisA" value="1" source=$conf label="Article 885-0V bis A"} -
-
-
- Factures + Factures et devis
- {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}
+
+ + Choisir les champs à faire figurer sur la facture ou le devis pour l'adresse d'un membre + +
+ {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} +
+
@@ -63,7 +57,7 @@ {input type="checkbox" name="unique_client_name" value="1" source=$conf label="Noms des clients uniques"} {input type="select" name="pattern" label="Format de numéro de document" required=false options=$patterns source=$conf}
- F = Facture, D = Devis, RF = Reçu fiscal, RC = Reçu cotisation + F = Facture, D = Devis
Pour personnaliser l'apparence de la facture, il faut pour l'instant se retrousser les manches et éditer soi-même le fichier www/admin/pdf.php du plugin ! @@ -75,4 +69,4 @@

-{include file="_foot.tpl"} \ No newline at end of file +{include file="_foot.tpl"} diff --git a/templates/facture.tpl b/templates/facture.tpl index 9248779..1464bc4 100644 --- a/templates/facture.tpl +++ b/templates/facture.tpl @@ -1,4 +1,4 @@ -{include file="_head.tpl" title="Document — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.id} +{include file="_head.tpl" title="Document — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.name} {include file="%s/templates/_menu.tpl"|args:$plugin_root current="index"} {form_errors} diff --git a/templates/facture_ajouter.tpl b/templates/facture_ajouter.tpl index e4add63..3730550 100644 --- a/templates/facture_ajouter.tpl +++ b/templates/facture_ajouter.tpl @@ -1,4 +1,4 @@ -{include file="_head.tpl" title="Créer un document — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.id js=1} +{include file="_head.tpl" title="Créer un document — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.name js=1} {include file="%s/templates/_menu.tpl"|args:$plugin_root current="facture"} {include file="%s/templates/_form.tpl"|args:$plugin_root} diff --git a/templates/facture_modifier.tpl b/templates/facture_modifier.tpl index 77dd30b..73fb70d 100644 --- a/templates/facture_modifier.tpl +++ b/templates/facture_modifier.tpl @@ -1,4 +1,4 @@ -{include file="_head.tpl" title="Modifier un document — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.id js=1} +{include file="_head.tpl" title="Modifier un document — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.name js=1} {include file="%s/templates/_menu.tpl"|args:$plugin_root current="index"} {include file="%s/templates/_form.tpl"|args:$plugin_root} diff --git a/templates/facture_supprimer.tpl b/templates/facture_supprimer.tpl index 4ba5791..3f9c761 100644 --- a/templates/facture_supprimer.tpl +++ b/templates/facture_supprimer.tpl @@ -1,4 +1,4 @@ -{include file="_head.tpl" title="Supprimer un document — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.id js=0} +{include file="_head.tpl" title="Supprimer un document — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.name js=0} {include file="%s/templates/_menu.tpl"|args:$plugin_root current="index"} {form_errors} @@ -22,4 +22,4 @@ -{include file="_foot.tpl"} \ No newline at end of file +{include file="_foot.tpl"} diff --git a/templates/index.tpl b/templates/index.tpl index fd673cb..b6da4de 100644 --- a/templates/index.tpl +++ b/templates/index.tpl @@ -1,4 +1,4 @@ -{include file="_head.tpl" title="Documents — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.id} +{include file="_head.tpl" title="Documents — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.name} {include file="%s/templates/_menu.tpl"|args:$plugin_root current="index"} {form_errors} diff --git a/upgrade.php b/upgrade.php index 5df22ea..f47ac27 100644 --- a/upgrade.php +++ b/upgrade.php @@ -6,13 +6,13 @@ use Paheko\Entities\Files\File; $db = DB::getInstance(); $old_version = $plugin->oldVersion(); -error_log("upgrade::version = " . $old_version); +error_log("upgrade:: à partir de la version = " . $old_version); // 0.2.0 - Stock le contenu en json plutôt qu'en serialized if (version_compare($old_version, '0.2.0', '<')) { $r = (array) DB::getInstance()->get('SELECT * FROM plugin_facturation_factures'); - + foreach ($r as $e) { $e->contenu =json_encode(unserialize((string) $e->contenu)); $db->update('plugin_facturation_factures', $e, $db->where('id', (int)$e->id)); @@ -33,7 +33,7 @@ if (version_compare($old_version, '0.3.0', '<')) $db->exec('DROP TABLE `plugin_facturation_config`;'); } -// 0.4.0 - +// 0.4.0 - if (version_compare($old_version, '0.4.0', '<')) { $db->exec(<<listAll(); foreach($factures as $k=>$f) @@ -119,13 +119,13 @@ EOT foreach($f->contenu as $line => $content) { // Petit bug qui peut arriver avec des contenus mal enregistrés en db - if (is_int($content)) - { - continue; - } + if (is_int($content)) + { + continue; + } $contenu[] = ['designation' => $content['designation'], - 'prix' => (int) ($content['prix'] * 100) ]; + 'prix' => (int) ($content['prix'] * 100) ]; } $f->contenu = $contenu; @@ -143,7 +143,7 @@ EOT } } -// 0.6.2 - +// 0.6.2 - if (version_compare($old_version, '0.6.2', '<')) { define('DEVIS', 0); @@ -168,14 +168,14 @@ if (version_compare($old_version, '0.6.2', '<')) INSERT OR IGNORE INTO plugin_facturation_txt_cerfa ("id","menu","texte") VALUES ('0','Aucun',''); INSERT OR IGNORE INTO plugin_facturation_txt_cerfa - ("id","menu","texte") + ("id","menu","texte") VALUES ('1','HelloAsso','Don via HelloAsso'); INSERT OR IGNORE INTO plugin_facturation_txt_cerfa - ("id","menu","texte") + ("id","menu","texte") VALUES ('2','Frais de déplacement', 'Renonciation aux remboursements de frais de déplacement'); INSERT OR IGNORE INTO plugin_facturation_txt_cerfa - ("id","menu","texte") + ("id","menu","texte") VALUES ('3','Don en nature','Don en nature'); EOT ); @@ -211,7 +211,6 @@ if (version_compare($old_version, '0.8.1', '<')) } // 0.8.5 Ajout champs SIREN/SIRET à la table clients - if (version_compare($old_version, '0.8.5', '<')) { $db->exec(<<exec(<<exec(<<iterate($sql) as $client) + { + $db->insert('plugin_facturation_clients_tmp', $client); + } + // remplacer l'ancienne table par la nouvelle + $db->exec(<<exec(<<iterate($sql) as $facture) + { + $db->insert('plugin_facturation_factures_tmp', $facture); + } + // remplacer l'ancienne table par la nouvelle + $db->exec(<<
$asso $receveur
{button label="Enlever" title="Enlever la ligne" shape="minus" min="2" name="remove_line"}
{button label="Enlever" title="Enlever la ligne" shape="minus" min="2" name="remove_line"}