Compare commits

..

No commits in common. "16daac2e244d7b8278a3d750dfb710704d1c6ccb" and "2ecbac159a8b51448ea9c3290135ea2ed5b94aed" have entirely different histories.

14 changed files with 727 additions and 299 deletions

View File

@ -16,19 +16,18 @@ le dossier plugins de Paheko.
## 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
- (obsolète) Créer des reçus fiscaux pour des dons et génération du CERFA correspondant - 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 - Créer des reçus sur des cotisations
- Génération des documents (facture et devis) en PDF - Génération des documents (facture et devis) en PDF grâce à la librairie mPDF
- Liste les documents associés sur la fiche d'un·e client·e - Liste les documents associés sur la fiche d'un·e client·e
- Permet de définir le statut du document à « réglé » - Permet de définir le statut du document sur reglée
- **Configuration** : - **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)
- Possibilité de choisir certains champs à faire figurer sur la facture (adresse, code postal, ville)
- Modification du pied de page des documents (notament pour y inscrire des mentions légales) - Modification du pied de page des documents (notament pour y inscrire des mentions légales)
- Vérifier le code postal : si coché, lors d'ajout ou de modification de client, le plugin vérifiera que le code postal entré est bien formaté (par rapport aux codes postaux français seulement) - Vérifier le code postal : si coché, lors d'ajout ou de modification de client, le plugin vérifiera que le code postal entré est bien formaté (par rapport aux codes postaux français seulement)
- Noms de client·es uniques : si coché, lors d'ajout ou de modification de client·e, le nom du/de la client·e ne pourra pas être le même que celui d'un·e client·e déjà existant - 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
- (obsolète) Informations relatives au CERFA pour les reçus fiscaux - Informations relatives au cerfa pour les reçus fiscaux
- (obsolète) Image qui sert de signature sur le CERFA - Image qui sert de signature sur le cerfa
Note : pour le moment, les actions sur la liste des clients à cocher Note : pour le moment, les actions sur la liste des clients à cocher
ne fonctionnent pas. Pour supprimer un client, le faire depuis sa ne fonctionnent pas. Pour supprimer un client, le faire depuis sa
@ -39,9 +38,8 @@ 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. n'est pas pertinent d'ajouter de nouvelles fonctionnalités à celui-ci.
Par contre, si des bugs sont signalés sur la liste Par contre, si des bugs sont signalés sur la liste
hebergement@paheko.cloud ou aide@paheko.cloud, je hebergement@paheko.cloud, je peux tenter de les corriger, à condition
(lesanges@zaclys.net) peux tenter de les corriger, à condition que ça que ça n'impacte pas trop la structure du plugin.
n'impacte pas trop la structure du plugin.
## Futur improbable (obsolète) : ## 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
@ -58,7 +56,7 @@ n'impacte pas trop la structure du plugin.
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,28 +4,6 @@ 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 {
@ -43,10 +21,14 @@ $db = DB::getInstance();
$step = false; $step = false;
$radio = $liste = $designations = $prix = []; $radio = $liste = $designations = $prix = [];
$fields = $facture->recu_fields;
$moyens_paiement = $facture->listMoyensPaiement(true); $moyens_paiement = $facture->listMoyensPaiement(true);
$tpl->assign('moyens_paiement', $moyens_paiement); $tpl->assign('moyens_paiement', $moyens_paiement);
$tpl->assign('moyen_paiement', f('moyen_paiement') ?: 'ES'); $tpl->assign('moyen_paiement', f('moyen_paiement') ?: 'ES');
$tpl->assign('moyen_paiement_cerfa', f('moyen_paiement_cerfa') ?: 'ES');
$tpl->assign('formes_don', array('1' => 'Acte authentique', $tpl->assign('formes_don', array('1' => 'Acte authentique',
'2' => 'Acte sous seing privé', '2' => 'Acte sous seing privé',
'3' => 'Don manuel', '3' => 'Don manuel',
@ -54,6 +36,7 @@ $tpl->assign('formes_don', array('1' => 'Acte authentique',
$tpl->assign('natures_don', array('1' => 'Numéraire', $tpl->assign('natures_don', array('1' => 'Numéraire',
'2' => 'Chèque', '2' => 'Chèque',
'3' => 'Virement, CB; ...')); '3' => 'Virement, CB; ...'));
$tpl->assign('textes_don', $facture->listTextesCerfa());
if ( !$target ) { if ( !$target ) {
f(['id' => 'required|numeric']); f(['id' => 'required|numeric']);
@ -102,6 +85,16 @@ $form->runIf(f('save') && !$form->hasErrors(),
$data['total'] = $data['toto']; $data['total'] = $data['toto'];
unset($data['toto']); unset($data['toto']);
} }
elseif ( f('type') == CERFA )
{
$data['moyen_paiement'] = f('moyen_paiement_cerfa');
$data['contenu'] = [
'forme' => f('forme_don'),
'nature' => f('nature_don'),
'texte' => f('texte_don')];
$data['total'] = Utils::moneyToInteger(f('total'));
unset($data['toto']);
}
if (f('base_receveur') == 'client') if (f('base_receveur') == 'client')
{ {
$data['receveur_membre'] = 0; $data['receveur_membre'] = 0;
@ -125,9 +118,62 @@ $form->runIf(f('save') && !$form->hasErrors(),
}, $csrf_key); }, $csrf_key);
$form->runIf(f('select_cotis') && !$form->hasErrors(),
function () use ($step)
{
$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);
}
$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'] ]
];
}
catch (UserException $e)
{
$form->addError($e->getMessage());
}
}, 'add_cotis_2');
if (! $form->hasErrors()) if (! $form->hasErrors())
{ {
if (count($data) > 0) if ($step)
{
try
{
$liste = $facture->getCotis((int)f('membre_cotis'));
}
catch (UserException $e)
{
$form->addError($e->getMessage());
}
}
elseif (count($data) > 0)
{ {
if ($target) if ($target)
{ {
@ -159,13 +205,19 @@ if ($target)
$doc['base_receveur'] = $f->receveur_membre ? 'membre' : 'client'; $doc['base_receveur'] = $f->receveur_membre ? 'membre' : 'client';
$doc['client'] = $f->receveur_id; $doc['client'] = $f->receveur_id;
$doc['membre'] = $f->receveur_id; $doc['membre'] = $f->receveur_id;
if ( $f->type_facture == CERFA ) {
$doc['forme_don'] = $f->contenu['forme'];
$doc['nature_don'] = $f->contenu['nature'];
$doc['texte_don'] = $f->contenu['texte'];
}
} }
// Type du document: // Type du document:
$type = qg('t') ? (int) qg('t') : null; $type = qg('t') ? (int) qg('t') : null;
// Si le type est défini dans l'URL // Si le type est défini dans l'URL
if (in_array($type, [DEVIS, FACT], true)) if (in_array($type, [DEVIS, FACT, CERFA, COTIS], true))
{ {
$radio['type'] = $type; $radio['type'] = $type;
} // ... s'il a été rempli dans le formulaire envoyé } // ... s'il a été rempli dans le formulaire envoyé
@ -194,6 +246,14 @@ else
$doc['date_emission'] = f('date_emission') ?: $f->date_emission; $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['date_echeance'] = f('date_echeance')?: $f->date_echeance; // Smarty m'a saoulé pour utiliser form_field|date_fr:---
/* modif DD -- CERFA -------------------------------------- */
if ( $f->type_facture == CERFA ) {
$doc['total'] = $f->total;
$doc['forme_don'] = $f->contenu['forme'];
$doc['nature_don'] = $f->contenu['nature'];
$doc['texte_don'] = $f->contenu['texte'];
}
$radio['type'] = f('type')??$doc['type']; $radio['type'] = f('type')??$doc['type'];
} }
$tpl->assign('types_details', $facture->types); $tpl->assign('types_details', $facture->types);
@ -241,8 +301,9 @@ $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', toArray($db->get('SELECT id, '.$identite.' FROM users WHERE id_category != -2 NOT IN (SELECT id FROM users_categories WHERE hidden = 1) ORDER BY ' .$identite. ';'), 'id', " ")); $tpl->assign('users', $db->getAssoc('SELECT id, '.$identite.' FROM users WHERE id_category != -2 NOT IN (SELECT id FROM users_categories WHERE hidden = 1) ORDER BY ' .$identite. ';'));
$tpl->assign('clients', $db->getAssoc('SELECT id, nom FROM plugin_facturation_clients;')); $tpl->assign('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

@ -7,6 +7,8 @@ use Paheko\Utils;
define('DEVIS', 0); define('DEVIS', 0);
define('FACT', 1); define('FACT', 1);
define('CERFA', 2);
define('COTIS', 3);
const PATTERNS_LIST = [ const PATTERNS_LIST = [
null => 'Aucun, le numéro sera à spécifier manuellement pour chaque document', null => 'Aucun, le numéro sera à spécifier manuellement pour chaque document',
@ -46,11 +48,11 @@ $tpl->register_function('money_fac', function (array $params)
if (!isset($user)) { if (!isset($user)) {
$user = false; $user = false;
} }
if (!isset($name)) if (!isset($name))
{ {
$name = 'prix[]'; $name = 'prix[]';
} }
if (null !== $current_value && !$user) { if (null !== $current_value && !$user) {
@ -60,7 +62,7 @@ $tpl->register_function('money_fac', function (array $params)
if (null !== $current_value) { if (null !== $current_value) {
$current_value = htmlspecialchars($current_value, ENT_QUOTES, 'UTF-8'); $current_value = htmlspecialchars($current_value, ENT_QUOTES, 'UTF-8');
} }
$currency = Config::getInstance()->get('monnaie'); $currency = Config::getInstance()->get('monnaie');
return sprintf('<td><nobr><input type="text" pattern="[0-9]*([.,][0-9]{1,2})?" inputmode="decimal" size="8" class="money" style="width: 60%%" onchange="updateSum();" name="%s" value="%s" /><b>%s</b></nobr></td>', $name, $current_value, $currency); return sprintf('<td><nobr><input type="text" pattern="[0-9]*([.,][0-9]{1,2})?" inputmode="decimal" size="8" class="money" style="width: 60%%" onchange="updateSum();" name="%s" value="%s" /><b>%s</b></nobr></td>', $name, $current_value, $currency);
} }

View File

@ -1,15 +1,11 @@
<?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')));
@ -30,14 +26,9 @@ $form->runIf('save', function () use ($plugin) {
$plugin->setConfigProperty('logo', (bool)f('logo')); $plugin->setConfigProperty('logo', (bool)f('logo'));
$plugin->setConfigProperty('footer', f('footer')); $plugin->setConfigProperty('footer', f('footer'));
$plugin->setConfigProperty('nom_client', f('nom_client'));
$plugin->setConfigProperty('prenom_client', f('prenom_client'));
$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('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();
@ -47,6 +38,5 @@ $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

@ -24,36 +24,17 @@ 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 = '[À RENSEIGNER DANS LA FICHE MEMBRE]'; $c->$v = '[A 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)
@ -71,74 +52,82 @@ if (isset($f->date_echeance))
// -- Création du PDF // -- Création du PDF
// Génération factures, devis // Génération factures, devis et cotisation
switch ($f->type_facture) if ($f->type_facture != CERFA)
{ {
case FACT: switch ($f->type_facture)
$doc = 'Facture n° '. $f->numero; {
$txtemis = $doc . " - Émise le " . $emission; case FACT:
$txtdest = "Adressée à :"; $doc = 'Facture n° '. $f->numero;
break; $txtemis = $doc . " - Émise le " . $emission;
case DEVIS: $txtdest = "Adressée à :";
$doc = 'Devis n° '. $f->numero; break;
$txtemis = $doc . " - Émis le " . $emission; case DEVIS:
$txtdest = "Adressé à :"; $doc = 'Devis n° '. $f->numero;
break; $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;
}
// utiliser l'adresse configurée dans le plugin sinon celle de l'asso sinon rien ! // utiliser l'adresse configurée dans le plugin sinon celle de l'asso sinon rien !
if ($plugin->getConfig('rue_asso') != null && if ($plugin->getConfig('rue_asso') != null &&
$plugin->getConfig('cp_asso') != null && $plugin->getConfig('cp_asso') != null &&
$plugin->getConfig('ville_asso') != null) $plugin->getConfig('ville_asso') != null)
{ {
$adresse = $adresse =
(($plugin->getConfig('numero_rue_asso') != null) ? $plugin->getConfig('numero_rue_asso') . " " : "") . (($plugin->getConfig('numero_rue_asso') != null) ? $plugin->getConfig('numero_rue_asso') . " " : "") .
$plugin->getConfig('rue_asso') . "<br>" . $plugin->getConfig('rue_asso') . "<br>" .
$plugin->getConfig('cp_asso') . " " . $plugin->getConfig('cp_asso') . " " .
$plugin->getConfig('ville_asso'); $plugin->getConfig('ville_asso');
} }
else if ($config->get('org_address') != null) else if ($config->get('org_address') != null)
{ {
$adresse = str_replace("\n", '<br>', $config->get('org_address')); $adresse = str_replace("\n", '<br>', $config->get('org_address'));
} }
else { else {
$adresse = ""; $adresse = "";
} }
$logo=''; $logo='';
if ($plugin->getConfig('logo')) { if ($plugin->getConfig('logo')) {
$logo = '<img id="logo" src="' . $config->fileURL('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 : " . implode(' ', str_split($t, 3)) . "<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 =
$txtdest.'<br>'. $txtdest.'<br>'.
'<b>'.$nom_client.'</b><br>'. '<b>'.$c->nom.'</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->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>":'');
$total = Utils::money_format($f->total, ',', ' '); $total = Utils::money_format($f->total, ',', ' ');
// Devis et facture // 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.'; $echeance = ($f->type_facture?'Échéance de paiement':'Échéance du devis')." : ".$echeance;
$footer = str_replace("\n", '<br>', $plugin->getConfig('footer') ?? '[Pied de page à configurer]'); $reglee = !$f->reglee?'Cette facture est en attente de règlement.':'Cette facture a été réglée.';
$ttc = $plugin->getConfig('ttc') ? 'TTC':'HT'; $footer = str_replace("\n", '<br>', $plugin->getConfig('footer') ?? '[Pied de page à configurer]');
$ttc = $plugin->getConfig('ttc') ? 'TTC':'HT';
// Génération du contenu de la facture // Génération du contenu de la facture
ob_start(); ob_start();
echo <<<EOF echo <<<EOF
<div class="h2"> <div class="h2">
<span>Contenu</span> - $doc <span>Contenu</span> - $doc
</div> </div>
@ -158,18 +147,18 @@ $total = Utils::money_format($f->total, ',', ' ');
<tbody> <tbody>
EOF; EOF;
$i = 1; $i = 1;
foreach($f->contenu as $k=>$v) foreach($f->contenu as $k=>$v)
{ {
echo '<tr><td>'; echo '<tr><td>';
echo str_replace("\n", '<br>', $v['designation']); echo str_replace("\n", '<br>', $v['designation']);
echo '</td><td>'; echo '</td><td>';
echo Utils::money_format($v['prix'], ',', ' ') .' €'; echo Utils::money_format($v['prix'], ',', ' ') .' €';
echo '</td></tr>'; echo '</td></tr>';
$i++; $i++;
} }
echo <<<EOF echo <<<EOF
</tbody> </tbody>
<tfoot> <tfoot>
<tr> <tr>
@ -192,14 +181,52 @@ EOF;
</footer> </footer>
EOF; EOF;
$content = ob_get_clean(); $content = ob_get_clean();
} }
else // Reçu de cotisation
{
$lieu = $plugin->getConfig('ville_asso');
$intitule = $f->contenu['intitule'];
$souscription = date('d/m/Y', strtotime($f->contenu['souscription']));
//-- Layout du document if($f->contenu['expiration'] == '1970-01-01')
{
$expiration = "jour même, s'agissant d'une cotisation ponctuelle.";
}
else {
$expiration = date('d/m/Y', strtotime($f->contenu['expiration']));
}
ob_start(); // Génération du contenu du reçu de cotisation
echo <<<EOF $content = <<<EOF
<div class="h2">
<span>Reçu de votre cotisation</span> - $doc
</div>
<hr>
<div class="contenuTexte">
<p>À $lieu, le $emission,</p>
<p>Bonjour,</p>
<p>Nous accusons réception de votre cotisation « $intitule » reçue le $emission et nous vous en remercions.</p>
<p>Nous reconnaissons que vous avez acquitté la somme de {$total} .<br>111
Votre adhésion sera donc effective à compter du $souscription jusquau $expiration.</p>
<br>
<p>Nous vous prions de recevoir, chère adhérente, cher adhérent, nos meilleures salutations,</p>
<br>
<p>-représentant·e de l'asso-</p>
<br>
<p><i>Nous vous rappelons que la cotisation nest pas soumise à la TVA et quelle ne donne pas lieu à la délivrance dune facture. Elle nouvre pas droit au bénéfice des dispositions des articles 200, 238 bis et 885-0 V bis A du code général des impôts.</i></p>
</div>
EOF;
}
//-- Layout du document
ob_start();
echo <<<EOF
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
@ -210,7 +237,7 @@ echo <<<EOF
size: A4 portrait; size: A4 portrait;
margin: 0; margin: 0;
} }
body { body {
padding: 4mm; padding: 4mm;
font-family: Helvetica, Arial, sans; font-family: Helvetica, Arial, sans;
@ -313,13 +340,198 @@ echo <<<EOF
</html> </html>
EOF; EOF;
$html = ob_get_clean(); $html = ob_get_clean();
} // Génération du CERFA
elseif ($f->type_facture == CERFA)
{
$doc = 'Reçu de don n°'. $f->numero;
$url = WWW_URL;
$libelles = $facture->listTextesCerfa(false);
$t['numero'] = $f->numero;
$t['org_name'] = $config->get('org_name');
$t['n_rue_asso'] = $plugin->getConfig('numero_rue_asso');
$t['rue_asso'] = $plugin->getConfig('rue_asso');
$t['cp_asso'] = $plugin->getConfig('cp_asso');
$t['ville_asso'] = $plugin->getConfig('ville_asso');
$t['objet0'] = $plugin->getConfig('objet_0');
$t['objet1'] = $plugin->getConfig('objet_1');
$t['objet2'] = $plugin->getConfig('objet_2');
$t['nom'] = $c->nom;
$t['adresse'] = $c->adresse;
$t['cp'] = $c->code_postal;
$t['ville'] = $c->ville;
$t['total'] = '***'.Utils::money_format($f->total).'***';
$t['total_lettre'] = numfmt_create('fr_FR', \NumberFormatter::SPELLOUT)->format($f->total/100). ' euros';
$t['d'] = ($f->date_emission->format('d'));
$t['m'] = ($f->date_emission->format('m'));
$t['Y'] = ($f->date_emission->format('Y'));
$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';
}
if($plugin->getConfig('droit_art238bis')){
$t['art238'] = 'X';
}
if($plugin->getConfig('droit_art885-0VbisA')){
$t['art885'] = 'X';
}
// forme du don
switch ($t['forme']){
case '1':
$t['frm'] = 'left: 15mm;';
break;
case '2':
$t['frm'] = 'left: 57.3mm;';
break;
case '3':
$t['frm'] = 'left: 115.2mm;';
break;
case '4':
$t['frm'] = 'left: 175.2mm;';
}
// nature du don
switch ($t['nature']){
case '1':
$t['nat'] = 'left: 15mm;';
break;
case '2':
$t['nat'] = 'left: 57.3mm;';
break;
case '3':
$t['nat'] = 'left: 115.2mm;';
}
// moyen de paiement
switch ($f->moyen_paiement){
case 'ES':
$t['pos'] = 'left: 15mm;';
break;
case 'CH':
$t['pos'] = 'left: 57.3mm;';
break;
default:
$t['pos'] = 'left: 115.2mm;';
}
$t['d2'] = ($f->date_echeance->format('d'));
$t['m2'] = ($f->date_echeance->format('m'));
$t['Y2'] = ($f->date_echeance->format('Y'));
ob_start();
echo <<<EOF
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{$doc}_{$emission}</title>
<style>
@page {
size: A4 portrait;
margin: 0;
padding: 0;
}
body {
font-family: Helvetica, Arial, sans;
font-size: 10pt;
background: white;
padding: 0;
margin: 0;
}
.page div {
position: absolute;
}
.page {
padding: 0;
margin: 0;
break-after: always;
width: 210mm;
height: 297mm;
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;
}
</style>
</head>
<body>
<div class="page" id="p1">
<div style="top: 10mm; left: 170mm;">{$t['numero']}</div>
<div style="top: 35mm; left: 20mm;">{$t['org_name']}</div>
<div style="top: 46mm; left: 20mm;">{$t['n_rue_asso']}</div>
<div style="top: 46mm; left: 40mm;">{$t['rue_asso']}</div>
<div style="top: 51.5mm; left: 37mm;">{$t['cp_asso']}</div>
<div style="top: 51.5mm; left: 75mm;">{$t['ville_asso']}</div>
<div style="top: 62mm; left: 18mm;">{$t['objet0']}</div>
<div style="top: 66.5mm; left: 18mm;">{$t['objet1']}</div>
<div style="top: 70.8mm; left: 18mm;">{$t['objet2']}</div>
<div style="top: 128.5mm; left: 15mm;">X</div>
</div>
<div class="page" id="p2">
<div style="top: 18mm; left: 18mm;">{$t['nom']}</div>
<div style="top: 32mm; left: 18mm;">{$t['adresse']}</div>
<div style="top: 37mm; left: 40mm;">{$t['cp']}</div>
<div style="top: 37mm; left: 80mm;">{$t['ville']}</div>
<div style="top: 63mm; left: 87mm;">{$t['total']}</div>
<div style="top: 73mm; left: 58mm;">{$t['total_lettre']}</div>
<div style="top: 82mm; left: 69mm;">{$t['d']}</div>
<div style="top: 82mm; left: 82mm;">{$t['m']}</div>
<div style="top: 82mm; left: 99mm;">{$t['Y']}</div>
<div style="top: 96mm; left: 53mm;">{$t['art200']}</div>
<div style="top: 96mm; left: 103mm;">{$t['art238']}</div>
<div style="top: 96mm; left: 153.0mm;">{$t['art885']}</div>
<div style="top: 113mm; {$t['frm']}">X</div>
<div style="top: 136mm; {$t['nat']}">X</div>
<div style="top: 142mm; left: 25mm;">{$t['texte']}</div>
<div style="top: 158.2mm; {$t['pos']}">X</div>
<div style="top: 239mm; left: 139mm;">{$t['d2']}</div>
<div style="top: 239mm; left: 148mm;">{$t['m2']}</div>
<div style="top: 239mm; left: 156mm;">{$t['Y2']}</div>
<div style="top: 243mm; left: 137mm;">{$sign_tag}</div>
</div>
</body>
</html>
EOF;
$html = ob_get_clean();
} // End if cerfa
if(qg('d') !== null) 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 = str_replace(" ", "_", trim($match[1])); $filename = trim($match[1]);
} }
header('Content-type: application/pdf'); header('Content-type: application/pdf');

View File

@ -15,15 +15,17 @@ class Facture
const TYPES_NAMES = [ const TYPES_NAMES = [
DEVIS => 'Devis', DEVIS => 'Devis',
FACT => 'Facture', FACT => 'Facture',
CERFA => 'Reçu fiscal',
COTIS => 'Reçu de cotisation',
]; ];
private $keys = [ private $keys = [
'type_facture', // 0 : devis, 1 : facture 'type_facture', // 0 : devis, 1 : facture, 2 : reçu cerfa, 3 : reçu cotis
'numero', 'numero',
'receveur_membre', 'receveur_membre',
'receveur_id', 'receveur_id',
'date_emission', 'date_emission', // Reçus : date du don
'date_echeance', 'date_echeance', // Reçus : date d'édition du reçu
'reglee', 'reglee',
'archivee', 'archivee',
'moyen_paiement', 'moyen_paiement',
@ -32,17 +34,27 @@ class Facture
]; ];
public $types = [ public $types = [
DEVIS => [ DEVIS => [
'id' => DEVIS, 'id' => DEVIS,
'accounts' => [], 'accounts' => [],
'label' => 'Devis', 'label' => 'Devis',
'help' => ''], 'help' => ''],
FACT => [ FACT => [
'id' => FACT, 'id' => FACT,
'accounts' => [], 'accounts' => [],
'label' => 'Facture', 'label' => 'Facture',
'help' => ''], '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'],
];
public function __construct() public function __construct()
{ {
@ -67,94 +79,112 @@ class Facture
{ {
throw new UserException("La valeur de $k est vide"); throw new UserException("La valeur de $k est vide");
} }
switch($k) switch($k)
{ {
case 'type_facture': case 'type_facture':
if (!array_key_exists($datas[$k], $this->types)) { if (!array_key_exists($datas[$k], $this->types)) {
throw new UserException("$k est de type non-attendue ($data)."); throw new UserException("$k est de type non-attendue ($data).");
} }
if ($datas[$k] < 2) { if ($datas[$k] < 2) {
$fac = true; $fac = true;
} $cerfa = false;
elseif ($datas[$k] == 2) { $recu = false;
$fac = false; }
} elseif ($datas[$k] == 2) {
elseif ($datas[$k] == 3) { $fac = false;
$fac = false; $cerfa = true;
} $recu = false;
break; }
elseif ($datas[$k] == 3) {
$fac = false;
$cerfa = false;
$recu = true;
}
break;
case 'receveur_membre': case 'receveur_membre':
case 'reglee': case 'reglee':
case 'archivee': case 'archivee':
if ($datas[$k] != 1 && $datas[$k] != 0) { if ($datas[$k] != 1 && $datas[$k] != 0) {
throw new UserException("$k est de valeur non-attendue ($data)."); throw new UserException("$k est de valeur non-attendue ($data).");
} }
break; break;
case 'receveur_id': case 'receveur_id':
if (!is_numeric($datas[$k]) || $datas[$k] < 0) { if (!is_numeric($datas[$k]) || $datas[$k] < 0) {
throw new UserException("L'id du receveur est non-attendu ($data)."); throw new UserException("L'id du receveur est non-attendu ($data).");
} }
break; break;
case 'date_emission': case 'date_emission':
$datas[$k] = \DateTime::createFromFormat('!d/m/Y', $data)->format('Y-m-d'); $datas[$k] = \DateTime::createFromFormat('!d/m/Y', $data)->format('Y-m-d');
break; break;
case 'date_echeance': case 'date_echeance':
$datas[$k] = \DateTime::createFromFormat('!d/m/Y', $data)->format('Y-m-d'); $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')) 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)."); throw new UserException("La date d'échéance est antérieure à la date d'émission ($data).");
} }
break; break;
case 'moyen_paiement': case 'moyen_paiement':
if (!array_key_exists($datas[$k], $this->listMoyensPaiement())) { if (!array_key_exists($datas[$k], $this->listMoyensPaiement())) {
throw new UserException("Le moyen de paiement ne correspond pas à la liste interne ($data)."); throw new UserException("Le moyen de paiement ne correspond pas à la liste interne ($data).");
} }
break; break;
case 'contenu': 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 (empty($r['designation']) && empty($r['prix'])) if (!is_array($datas[$k]) || empty($datas[$k])) {
{ throw new UserException("Le contenu du document est vide ($data).");
unset($datas[$k][$g]);
unset($datas[$k]['prix']);
continue;
} }
elseif (empty($r['prix'])) $total = 0;
foreach($datas[$k] as $g => $r)
{ {
$datas[$k]['prix'] = 0; 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'];
} }
if (!is_int($r['prix'])) if($fac && !$total)
{ {
throw new UserException('Un (ou plus) des prix n\'est pas un entier.'); throw new UserException("Toutes les désignations/prix sont vides.");
} }
$total += $r['prix'];
} }
elseif ($cerfa)
if($fac && !$total)
{ {
throw new UserException("Toutes les désignations/prix sont vides.");
} }
} elseif ($recu)
$datas[$k] = json_encode($datas[$k]); {
break; // $fields = ['id', 'intitule', 'date', 'expiration'];
// foreach ($datas[$k]as $)
}
$datas[$k] = json_encode($datas[$k]);
break;
case 'total': case 'total':
if ($fac && !isset($datas['contenu'])) { if ($cerfa && $datas[$k] < 1) {
throw new UserException("Pas de contenu fourni pour vérifier le total."); throw new UserException('Le total ne peut être inférieur à 1€ pour les reçus (bug encore non résolu).');
} }
if ($fac && $total != $datas[$k]) if ($fac && !isset($datas['contenu'])) {
{ throw new UserException("Pas de contenu fourni pour vérifier le total.");
throw new UserException("Les totaux sont différents ($total != $datas[$k]."); }
} if ($fac && $total != $datas[$k])
break; {
throw new UserException("Les totaux sont différents ($total != $datas[$k].");
}
break;
} }
} }
} }
@ -200,6 +230,15 @@ class Facture
$type = 'FACT'; $type = 'FACT';
$t = 'F'; $t = 'F';
} }
elseif ($type == CERFA) {
$type = 'CERFA';
$t = 'RF';
}
else {
$type = 'COTIS';
$t = 'RC';
}
$year = $date->format('Y'); $year = $date->format('Y');
$y = $date->format('y'); $y = $date->format('y');
@ -248,7 +287,7 @@ class Facture
$r = $db->first('SELECT * FROM plugin_facturation_factures WHERE id = ? LIMIT 1;', (int)$id); $r = $db->first('SELECT * FROM plugin_facturation_factures WHERE id = ? LIMIT 1;', (int)$id);
if(!$r) if(!$r)
{ {
throw new UserException("Pas de document retournée avec cet id."); throw new UserException("Pas de document retournée avec cet id.");
} }
@ -286,14 +325,6 @@ 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
@ -318,18 +349,15 @@ class Facture
], ],
'receveur' => [ 'receveur' => [
'label' => 'Receveur', 'label' => 'Receveur',
// l'identité du membre peut être redéfinie dans la configuration des membres 'select' => sprintf('CASE WHEN receveur_membre THEN %s ELSE c.nom END', $id_field),
'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' => [
// l'adresse peut être redéfinie dans la configuration du plugin
'label' => 'Adresse', 'label' => 'Adresse',
'select' => sprintf('CASE WHEN receveur_membre THEN %s ELSE c.adresse END', $adresse), 'select' => 'CASE WHEN receveur_membre THEN u.adresse ELSE c.adresse END',
], ],
'receveur_ville' => [ 'receveur_ville' => [
// la ville peut être redéfinie dans la configuration du plugin
'label' => 'Ville', 'label' => 'Ville',
'select' => sprintf('CASE WHEN receveur_membre THEN %s ELSE c.ville END', $ville), 'select' => 'CASE WHEN receveur_membre THEN u.ville ELSE c.ville END',
], ],
'date_emission' => [ 'date_emission' => [
'label' => 'Émission', 'label' => 'Émission',
@ -376,9 +404,21 @@ class Facture
// Remplir le contenu // Remplir le contenu
$content = json_decode((string)$row->contenu); $content = json_decode((string)$row->contenu);
$row->contenu = implode("\n", array_map(function ($row) use ($currency) { if ($row->type_facture == COTIS && isset($content->intitule, $content->souscription)) {
return sprintf('%s : %s %s', $row->designation, Utils::money_format($row->prix), $currency); $row->contenu = sprintf("Cotisation %s\nSouscrite le %s",
}, (array)$content)); $content->intitule,
Utils::date_fr($content->souscription, 'd/m/Y')
);
}
elseif ($row->type_facture != CERFA) {
$row->contenu = implode("\n", array_map(function ($row) use ($currency) {
return sprintf('%s : %s %s', $row->designation, Utils::money_format($row->prix), $currency);
}, (array)$content));
}
else
{
$row->contenu = '';
}
}); });
return $list; return $list;
@ -448,6 +488,31 @@ class Facture
return DB::getInstance()->test('plugin_facturation_factures', 'receveur_membre = ? AND receveur_id = ?', $base, $id); return DB::getInstance()->test('plugin_facturation_factures', 'receveur_membre = ? AND receveur_id = ?', $base, $id);
} }
// ** Pour type reçu **
public $recu_fields = ['id', 'label', 'amount', 'date', 'expiry', 'paid', 'paid_amount'];
public function getCotis(int $user_id, int $su_id = null)
{
$where = 'WHERE su.id_user = ?';
if (null !== $su_id)
{
$where .= ' AND su.id = '.$su_id;
}
$sql = 'SELECT su.id, s.label, su.date, MAX(su.expiry_date) as expiry, sf.label as fee, sf.amount as amount, su.paid, SUM(tl.debit) as paid_amount
FROM services_users su
INNER JOIN services s ON s.id = su.id_service
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.'
GROUP BY su.id
ORDER BY su.date;';
return DB::getInstance()->get($sql, $user_id);
}
public function listMoyensPaiement($assoc = false) public function listMoyensPaiement($assoc = false)
{ {
$db = DB::getInstance(); $db = DB::getInstance();
@ -461,13 +526,24 @@ class Facture
return $db->getGrouped($query); 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) public function getMoyenPaiement($code)
{ {
$db = DB::getInstance(); $db = DB::getInstance();
return $db->firstColumn('SELECT nom FROM plugin_facturation_paiement WHERE code = ?;', $code); return $db->firstColumn('SELECT nom FROM plugin_facturation_paiement WHERE code = ?;', $code);
} }
public function delete($id) public function delete($id)
{ {
return DB::getInstance()->delete('plugin_facturation_factures', 'id = '. (int)$id); return DB::getInstance()->delete('plugin_facturation_factures', 'id = '. (int)$id);

BIN
public/cerfa-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

BIN
public/cerfa-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@ -24,6 +24,8 @@
<fieldset> <fieldset>
<legend data-types="t0">Créer un devis</legend> <legend data-types="t0">Créer un devis</legend>
<legend data-types="t1">Créer une facture</legend> <legend data-types="t1">Créer une facture</legend>
<legend data-types="t2">Créer un reçu fiscal</legend>
<legend data-types="t3">Créer un reçu de cotisation</legend>
<dl> <dl>
{input type="text" name="numero_facture" maxlength=18 label="Numéro du document" required=$require_number source=$doc} {input type="text" name="numero_facture" maxlength=18 label="Numéro du document" required=$require_number source=$doc}
@ -36,21 +38,27 @@
{/if} {/if}
{input type="date" name="date_emission" default=$date label="Date d'émission" required=1 source=$doc} {input type="date" name="date_emission" default=$date label="Date d'émission" required=1 source=$doc}
<div data-types="t0 t1"> <dd class="help" data-types="t2">
<p>Date du versemen du don</p>
</dd>
<div data-types="t0 t1 t2">
{input type="date" name="date_echeance" default=$date label="Date d'échéance" required=1 source=$doc} {input type="date" name="date_echeance" default=$date label="Date d'échéance" required=1 source=$doc}
<dd class="help" data-types="t2">
<p>Date d'établissement du document</p>
</dd>
</div> </div>
<dt><label>Statut</label></dt> <dt><label>Statut</label></dt>
{input type="checkbox" name="reglee" value="1" label="Réglée" source=$doc data-types="t1"} {input type="checkbox" name="reglee" value="1" label="Réglée" source=$doc data-types="t1"}
<div data-types="t0 t1"> <div data-types="t0 t1 t2">
{input type="checkbox" name="archivee" value="1" label="Archivée" source=$doc disabled="disabled"} {input type="checkbox" name="archivee" value="1" label="Archivée" source=$doc disabled="disabled"}
</div> </div>
</dl> </dl>
</fieldset> </fieldset>
<fieldset data-types="t0 t1"> <fieldset data-types="t0 t1 t2">
<legend>Client</legend> <legend>Client</legend>
<dl> <dl>
@ -105,14 +113,14 @@
<td class="fact_rm_line">{button label="Enlever" title="Enlever la ligne" shape="minus" min="2" name="remove_line"}</td> <td class="fact_rm_line">{button label="Enlever" title="Enlever la ligne" shape="minus" min="2" name="remove_line"}</td>
</tr> </tr>
{foreach from=$designations item=designation key=key} {foreach from=$designations item=designation key=key}
<tr> <tr>
<td><textarea name="designation[]" style="width:98%;">{$designation}</textarea></td> <td><textarea name="designation[]" style="width:98%;">{$designation}</textarea></td>
{money_fac value=$prix[$key] user=$from_user} {money_fac value=$prix[$key] user=$from_user}
<td class="fact_rm_line">{button label="Enlever" title="Enlever la ligne" shape="minus" min="2" name="remove_line"}</td> <td class="fact_rm_line">{button label="Enlever" title="Enlever la ligne" shape="minus" min="2" name="remove_line"}</td>
</tr> </tr>
{/foreach} {/foreach}
{else} {else}
<tr id="Line1" class="hidden"> <tr id="Line1" class="hidden">
<td><textarea name="designation_tpl[]" style="width:98%;"></textarea></td> <td><textarea name="designation_tpl[]" style="width:98%;"></textarea></td>
{money_fac name="prix_tpl[]"} {money_fac name="prix_tpl[]"}
@ -131,12 +139,102 @@
</dd> </dd>
</dl> </dl>
</fieldset> </fieldset>
<fieldset data-types="t2">
<legend>Contenu</legend>
<dl>
{input type="money" name="total" label="Montant du don" required=1 source=$doc default="0,0"}
{input type="select" name="forme_don" required=1 label="Forme du don" source=$doc options=$formes_don default=$doc.forme_don}
{input type="select" name="nature_don" required=1 label="Nature du don" source=$doc options=$natures_don default=$doc.nature_don}
{input type="select" name="texte_don" required=1 label="Texte explicatif" source=$doc options=$textes_don default=$doc.texte_don}
{input type="select" name="moyen_paiement_cerfa" required=1 label="Moyen de paiement" source=$doc options=$moyens_paiement default=$doc.moyen_paiement_cerfa}
</dl>
<p class="submit" data-types="t0 t1"> </fieldset>
<p class="submit" data-types="t0 t1 t2">
{csrf_field key=$csrf_key} {csrf_field key=$csrf_key}
{button type="submit" name="save" label="Enregistrer" shape="right" class="main"} {button type="submit" name="save" label="Enregistrer" shape="right" class="main"}
</p> </p>
<fieldset data-types="t3">
<legend>Membre</legend>
<dl>
<dt><label>Reçu adressée à :</label></dt>
<dd>
{input type="select" name="membre_cotis" label="Membre" options=$users required=1 default=$doc.membre}
</dd>
</dl>
</fieldset>
<p class="submit" data-types="t3">
{csrf_field key="add_cotis_1"}
{button type="submit" name="select_cotis" label="Sélectionner" shape="right" class="main"}
</p>
{if $step}
<fieldset data-types="t3">
<legend>Cotisation</legend>
{if count($liste)}
<dl>
<dt>Sélectionnez la cotisation concernée :</dt>
<table class='list'>
<thead>
<td></td>
<td>Id</td>
<td>Intitulé</td>
<td>Date d'inscription</td>
<td>Expiration d'expiration</td>
<td>Tarif</td>
<td>Montant</td>
<td>Somme payée</td>
</thead>
{foreach from=$liste item=cotis key=i}
{if !$cotis.paid}
{continue}
{/if}
<tr>
<td>
{input type="radio" name="cotisation" value="%s"|args:$i}
</td>
{foreach from=$cotis item=element key=key}
{if $key == 'paid'}
{continue}
{/if}
<td>
<label for="f_cotisation_{$i}">
{if ($key == 'date' || $key == 'expiry') && $element > 0}
{$element|date_short}
{elseif $key == 'amount' OR $key == 'paid_amount'}
{$element|raw|money_currency}
{else}
{$element}
{/if}
<input type="hidden" name="{$key}_{$i}" value="{$element}">
</label>
</td>
{/foreach}
</tr>
{/foreach}
</table>
</dl>
</fieldset>
<p class="submit" data-types="t3">
{csrf_field key="add_cotis_2"}
{button type="submit" name="add_cotis" label="Enregistrer" shape="right" class="main"}
</p>
{else}
<p>Ce membre n'a aucune cotisation payée.</p>
</fieldset>
{/if}
{/if}
</form> </form>
{include file="%s/templates/_js.tpl"|args:$plugin_root} {include file="%s/templates/_js.tpl"|args:$plugin_root}

View File

@ -19,8 +19,8 @@
function plus(){ function plus(){
var newdiv = document.createElement('tr'); var newdiv = document.createElement('tr');
newdiv.innerHTML = document.getElementById('Line1').innerHTML; newdiv.innerHTML = document.getElementById('Line1').innerHTML;
newdiv.getElementsByTagName('textarea')[0].setAttribute('name', 'designation[]'); newdiv.getElementsByTagName('textarea')[0].setAttribute('name', 'designation[]');
newdiv.getElementsByTagName('input')[0].setAttribute('name', 'prix[]'); newdiv.getElementsByTagName('input')[0].setAttribute('name', 'prix[]');
newdiv.querySelector('.fact_rm_line button').onclick = function(){ newdiv.querySelector('.fact_rm_line button').onclick = function(){
this.parentNode.parentNode.parentNode.removeChild(this.parentNode.parentNode); this.parentNode.parentNode.parentNode.removeChild(this.parentNode.parentNode);
updateSum(); updateSum();
@ -33,12 +33,12 @@
$('#ajouter_ligne').onclick = plus; $('#ajouter_ligne').onclick = plus;
a = document.querySelectorAll('[name="remove_line"]'); a = document.querySelectorAll('[name="remove_line"]');
l = a.length; l = a.length;
for(i = 0; i < l; i++) { for(i = 0; i < l; i++) {
a[i].onclick = function(){ a[i].onclick = function(){
this.parentNode.parentNode.parentNode.removeChild(this.parentNode.parentNode); this.parentNode.parentNode.parentNode.removeChild(this.parentNode.parentNode);
updateSum(); updateSum();
}; };
} }

View File

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

View File

@ -1,28 +1,21 @@
{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"}
<h2>Quelques remarques et conseils sur l'utilisation du plugin Facturation</h2> <fieldset>
<legend><h2>Quelques remarques et conseils sur l'utilisation du plugin Facturation</h2></legend>
<div class="aide"> <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>
<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>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>
<fieldset> <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.
<legend>Informations à configurer</legend> </p>
<dl> <br>
<dt><label>Adresse</label></dt> <p>- Pour créer un reçu sur une cotisation, il faut pour le moment que cette cotisation soit attachée à la compta.</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>- 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>
<dt><label>Nom et prénom des membres</label></dt> <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>
<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> <br>
<dt><label>Champs à faire figurer sur la facture</label></dt> <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>
<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>
<ul> </fieldset>
<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

@ -14,7 +14,7 @@
<?php continue; ?> <?php continue; ?>
{/if} {/if}
{if $key == 'siret'} {if $key == 'siret'}
<?php <?php
if (null === $value) { $value = ""; } if (null === $value) { $value = ""; }
$value = implode(' ', str_split($value, 3)); $value = implode(' ', str_split($value, 3));
?> ?>

View File

@ -27,6 +27,25 @@
{input type="text" name="ville_asso" source=$conf label="Ville"} {input type="text" name="ville_asso" source=$conf label="Ville"}
</dl> </dl>
</fieldset> </fieldset>
<fieldset>
<legend>Objet</legend>
<dl>
<dt><label>L'objet (but) de l'association doit tenir sur 3 lignes, chaque ligne pouvant accueillir un maximum de 100 caractères.</label><b title="(Champ obligatoire)">obligatoire pour reçus fiscaux</b></dt>
{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}
</dl>
</fieldset>
<fieldset>
<legend>Droit à la réduction d'impôt</legend>
<dl>
<dt><label>Articles concernés par l'association :</label> <b title="(Champ obligatoire)">obligatoire pour reçus fiscaux</b></dt>
{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"}
</dl>
</fieldset>
</fieldset> </fieldset>
@ -34,18 +53,8 @@
<legend>Factures</legend> <legend>Factures</legend>
<dl> <dl>
{input type="checkbox" name="logo" value="1" source=$conf label="Imprimer le logo de l'association"} {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} {input type="textarea" class="full-width" rows="10" 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>
@ -55,7 +64,7 @@
{input type="checkbox" name="unique_client_name" value="1" source=$conf label="Noms des clients uniques"} {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} {input type="select" name="pattern" label="Format de numéro de document" required=false options=$patterns source=$conf}
<dd class="help"> <dd class="help">
F = Facture, D = Devis F = Facture, D = Devis, RF = Reçu fiscal, RC = Reçu cotisation
</dd> </dd>
</dl> </dl>
<i>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 ! </i> <i>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 ! </i>