Ajout fonction : numérotation automatique des documents

This commit is contained in:
bohwaz 2021-12-17 21:10:54 +01:00
parent 8e2ddf19c6
commit 2e7710f29c
8 changed files with 110 additions and 22 deletions

View File

@ -1,3 +1,3 @@
{ {
'pattern': '%{type}-%{year}-%{ynumber}'
} }

View File

@ -75,7 +75,7 @@ class Facture
if(!is_array($data)){ if(!is_array($data)){
$datas[$k] = trim($data); $datas[$k] = trim($data);
} }
if ($datas[$k] === '') if ($datas[$k] === '' && $k != 'numero')
{ {
throw new UserException("La valeur de $k est vide"); throw new UserException("La valeur de $k est vide");
} }
@ -189,18 +189,70 @@ class Facture
} }
} }
public function add($data) public function add($data, ?string $number_pattern = null)
{ {
$db = DB::getInstance(); $db = DB::getInstance();
$this->_checkFields($data); $this->_checkFields($data);
$generate_number = false;
if(isset($data['numero']) && $db->test('plugin_facturation_factures', 'numero = ? COLLATE NOCASE', $data['numero'])) if (empty($data['numero']) && $number_pattern) {
$generate_number = true;
$data['numero'] = sha1(random_bytes(10));
}
if ($db->test('plugin_facturation_factures', 'numero = ? COLLATE NOCASE', $data['numero']))
{ {
throw new UserException('Un document avec ce numéro existe déjà, hors le numéro doit être unique.'); throw new UserException('Un document avec ce numéro existe déjà, hors le numéro doit être unique.');
} }
$db->insert('plugin_facturation_factures', $data); $db->insert('plugin_facturation_factures', $data);
return $db->lastInsertRowId(); $id = $db->lastInsertRowId();
if ($generate_number) {
$numero = $this->getNewNumber($number_pattern, $data['type_facture'], \DateTime::createFromFormat('!Y-m-d', $data['date_emission']), $id);
$db->update('plugin_facturation_factures', compact('numero'), 'id = ' . (int) $id);
}
return $id;
}
/**
* Renvoie un nouveau numéro de facture selon un motif défini et le type de document
*/
public function getNewNumber(string $pattern, int $type, \DateTimeInterface $date, int $id)
{
if ($type == DEVIS) {
$type = 'DEVIS';
$t = 'D';
}
elseif ($type == FACT) {
$type = 'FACT';
$t = 'F';
}
elseif ($type == CERFA) {
$type = 'CERFA';
$t = 'RF';
}
else {
$type = 'COTIS';
$t = 'RC';
}
$year = $date->format('Y');
$y = $date->format('y');
// On récupère le nombre de documents pour cette année
// vu qu'on vient d'ajouter un document, celui-ci est bien le dernier numéro
$ynumber = DB::getInstance()->count('plugin_facturation_factures', 'strftime(\'%Y\', date_emission) = ?', (string) $year);
$data = compact('type', 't', 'year', 'y', 'ynumber', 'id');
return preg_replace_callback('/%(\d+)?\{([a-z]+)\}/', function ($match) use ($data) {
$v = $data[$match[2]];
$type = ctype_digit($data[$match[2]]) ? 'd' : 's';
return sprintf('%' . $match[1] . $type, $v);
}, $pattern);
} }
public function get($id) public function get($id)
@ -284,6 +336,7 @@ class Facture
], ],
'date_emission' => [ 'date_emission' => [
'label' => 'Émission', 'label' => 'Émission',
'order' => 'date_emission %s, id %1$s',
], ],
'date_echeance' => [ 'date_echeance' => [
'label' => 'Échéance', 'label' => 'Échéance',
@ -312,7 +365,7 @@ class Facture
LEFT JOIN plugin_facturation_clients AS c ON f.receveur_membre = 0 AND c.id = f.receveur_id'; LEFT JOIN plugin_facturation_clients AS c ON f.receveur_membre = 0 AND c.id = f.receveur_id';
$list = new DynamicList($columns, $tables); $list = new DynamicList($columns, $tables);
$list->orderBy('numero', true); $list->orderBy('date_emission', true);
$currency = Config::getInstance()->monnaie; $currency = Config::getInstance()->monnaie;

View File

@ -28,7 +28,14 @@
<legend data-types="t3">Créer un reçu de cotisation</legend> <legend data-types="t3">Créer un reçu de cotisation</legend>
<dl> <dl>
{input type="text" name="numero_facture" maxlength=12 label="Numéro du document" required=1 source=$doc help="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."} {input type="text" name="numero_facture" maxlength=12 label="Numéro du document" required=$require_number source=$doc}
<dd class="help">
{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.
{else}
Laisser vide pour qu'un numéro soit automatiquement affecté au format <code>{$number_pattern}</code>.
{/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}
<dd class="help" data-types="t2"> <dd class="help" data-types="t2">

View File

@ -20,10 +20,10 @@
<fieldset> <fieldset>
<legend>Adresse</legend> <legend>Adresse</legend>
<dl> <dl>
{input type="text" name="numero_rue_asso" source=$plugin.config label="Numéro de rue" required=1 maxlength=5} {input type="text" name="numero_rue_asso" source=$plugin.config label="Numéro de rue" maxlength=5}
{input type="text" name="rue_asso" source=$plugin.config label="Nom de rue" required=1} {input type="text" name="rue_asso" source=$plugin.config label="Nom de rue"}
{input type="text" name="cp_asso" source=$plugin.config label="Code postal" required=1} {input type="text" name="cp_asso" source=$plugin.config label="Code postal"}
{input type="text" name="ville_asso" source=$plugin.config label="Ville" required=1} {input type="text" name="ville_asso" source=$plugin.config label="Ville"}
</dl> </dl>
</fieldset> </fieldset>
<fieldset> <fieldset>
@ -60,6 +60,10 @@
<dl> <dl>
{input type="checkbox" name="validate_cp" value="1" source=$plugin.config label="Vérifier le code postal lors de saisie/modification de client (seulement FR)"} {input type="checkbox" name="validate_cp" value="1" source=$plugin.config label="Vérifier le code postal lors de saisie/modification de client (seulement FR)"}
{input type="checkbox" name="unique_client_name" value="1" source=$plugin.config label="Noms des clients uniques"} {input type="checkbox" name="unique_client_name" value="1" source=$plugin.config label="Noms des clients uniques"}
{input type="select" name="pattern" label="Format de numéro de document" required=false options=$patterns source=$plugin.config}
<dd class="help">
F = Facture, D = Devis, RF = Reçu fiscal, RC = Reçu cotisation
</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>
</fieldset> </fieldset>

View File

@ -1,19 +1,31 @@
<?php <?php
namespace Garradin; namespace Garradin\Plugin\Facturation;
use Garradin\Config;
use Garradin\Utils;
define('DEVIS', 0); define('DEVIS', 0);
define('FACT', 1); define('FACT', 1);
define('CERFA', 2); define('CERFA', 2);
define('COTIS', 3); define('COTIS', 3);
use Garradin\Plugin\Facturation\Facture; const PATTERNS_LIST = [
use Garradin\Plugin\Facturation\Client; null => 'Aucun, le numéro sera à spécifier manuellement pour chaque document',
'%{type}-%{year}-%{ynumber}' => 'Type-Année-Numéro du document par année ("FACT-2021-42")',
'%{year}-%{type}-%04{ynumber}' => 'Année-Type-Numéro du document par année ("2021-DEVIS-0042")',
'%{t}-%{year}-%{ynumber}' => 'Type court-Année-Numéro du document par année ("F-2021-42")',
'%{y}%{t}%{ynumber}' => 'Année courte-Type court-Numéro du document par année ("21D42")',
'%{type}-%{id}' => 'Type - Numéro unique du document ("FACT-42")',
'%{t}%{id}' => 'Type court et numéro unique du document ("F42")',
'%{id}' => 'Numéro unique du document ("42"))',
'%06{id}' => 'Numéro unique du document sur 6 chiffres ("000042")',
];
$client = new Client; $client = new Client;
$facture = new Facture; $facture = new Facture;
$tpl->assign('www_url', WWW_URL); $tpl->assign('www_url', \Garradin\WWW_URL);
$tpl->assign('f_obj', $facture); $tpl->assign('f_obj', $facture);
$tpl->assign('plugin_url', Utils::plugin_url()); $tpl->assign('plugin_url', Utils::plugin_url());

View File

@ -29,6 +29,8 @@ if (f('save') && $form->check('facturation_config'))
$plugin->setConfig('validate_cp', (bool)f('validate_cp')); $plugin->setConfig('validate_cp', (bool)f('validate_cp'));
$plugin->setConfig('unique_client_name', (bool)f('unique_client_name')); $plugin->setConfig('unique_client_name', (bool)f('unique_client_name'));
$plugin->setConfig('pattern', f('pattern'));
Utils::redirect(PLUGIN_URL . 'config.php?ok'); Utils::redirect(PLUGIN_URL . 'config.php?ok');
} }
catch (UserException $e) catch (UserException $e)
@ -122,6 +124,8 @@ else
$tpl->assign('ok', qg('ok') !== null); $tpl->assign('ok', qg('ok') !== null);
$tpl->assign('patterns', \Garradin\Plugin\Facturation\PATTERNS_LIST);
// $tpl->assign('max_size', Utils::getMaxUploadSize()); // $tpl->assign('max_size', Utils::getMaxUploadSize());
$tpl->display(PLUGIN_ROOT . '/templates/config.tpl'); $tpl->display(PLUGIN_ROOT . '/templates/config.tpl');

View File

@ -2,6 +2,8 @@
namespace Garradin; namespace Garradin;
use const \Garradin\Plugin\Facturation\PATTERNS_LIST;
require_once __DIR__ . '/_inc.php'; require_once __DIR__ . '/_inc.php';
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE); $session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE);
@ -22,18 +24,22 @@ $fields = $facture->recu_fields;
$moyens_paiement = $facture->listMoyensPaiement(true); $moyens_paiement = $facture->listMoyensPaiement(true);
$doc = null; $doc = null;
$require_number = $plugin->getConfig('pattern') ? false : true;
if (qg('copy') !== null && $f = $facture->get((int)qg('copy'))) { if (qg('copy') !== null && $f = $facture->get((int)qg('copy'))) {
$doc = (array) $f; $doc = (array) $f;
// Copié depuis facture_modifier.php // Copié depuis facture_modifier.php
$doc['type'] = $f->type_facture; $doc['type'] = $f->type_facture;
$doc['numero_facture'] = $f->numero; $doc['numero_facture'] = '';
$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;
} }
$tpl->assign('require_number', $require_number);
$tpl->assign('number_pattern', PATTERNS_LIST[$plugin->getConfig('pattern')]);
$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');
@ -41,7 +47,7 @@ if (f('save'))
{ {
$form->check($csrf_key, [ $form->check($csrf_key, [
'type' => 'required|in:'.implode(',', [DEVIS, FACT, CERFA]), 'type' => 'required|in:'.implode(',', [DEVIS, FACT, CERFA]),
'numero_facture' => 'required|string', 'numero_facture' => $require_number ? 'required|string' : 'string',
'date_emission' => 'required|date_format:d/m/Y', 'date_emission' => 'required|date_format:d/m/Y',
'date_echeance' => 'required|date_format:d/m/Y', 'date_echeance' => 'required|date_format:d/m/Y',
// 'reglee' => '', // 'reglee' => '',
@ -61,10 +67,10 @@ if (f('save'))
if ( count(f('designation')) !== count(f('prix')) ) if ( count(f('designation')) !== count(f('prix')) )
{ {
throw new UserException('Nombre de désignations et de prix reçus différent.'); throw new UserException('Nombre de désignations et de prix reçus différent.');
} }
$truc = [ $truc = [
'numero' =>f('numero_facture'), 'numero' => f('numero_facture'),
'date_emission' => f('date_emission'), 'date_emission' => f('date_emission'),
'date_echeance' => f('date_echeance'), 'date_echeance' => f('date_echeance'),
'reglee' => f('reglee') == 1?1:0, 'reglee' => f('reglee') == 1?1:0,
@ -98,7 +104,7 @@ if (f('save'))
$truc['receveur_id'] = f('membre'); $truc['receveur_id'] = f('membre');
} }
$id = $facture->add($truc); $id = $facture->add($truc, $plugin->getConfig('pattern'));
Utils::redirect(PLUGIN_URL . 'facture.php?id='.(int)$id); Utils::redirect(PLUGIN_URL . 'facture.php?id='.(int)$id);
@ -158,7 +164,7 @@ elseif (f('add_cotis'))
'expiration' => $cotis['expiry'] ] 'expiration' => $cotis['expiry'] ]
]; ];
$id = $facture->add($data); $id = $facture->add($data, $plugin->getConfig('pattern'));
Utils::redirect(PLUGIN_URL . 'facture.php?id='.(int)$id); Utils::redirect(PLUGIN_URL . 'facture.php?id='.(int)$id);
} }

View File

@ -262,4 +262,6 @@ $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('require_number', true);
$tpl->display(PLUGIN_ROOT . '/templates/facture_modifier.tpl'); $tpl->display(PLUGIN_ROOT . '/templates/facture_modifier.tpl');