diff --git a/config.json b/config.json index 0e0dcd2..5b28698 100644 --- a/config.json +++ b/config.json @@ -1,3 +1,3 @@ { - + 'pattern': '%{type}-%{year}-%{ynumber}' } \ No newline at end of file diff --git a/lib/Facture.php b/lib/Facture.php index 017db78..ece72c0 100644 --- a/lib/Facture.php +++ b/lib/Facture.php @@ -75,7 +75,7 @@ class Facture if(!is_array($data)){ $datas[$k] = trim($data); } - if ($datas[$k] === '') + if ($datas[$k] === '' && $k != 'numero') { 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(); $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.'); } + $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) @@ -284,6 +336,7 @@ class Facture ], 'date_emission' => [ 'label' => 'Émission', + 'order' => 'date_emission %s, id %1$s', ], 'date_echeance' => [ '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'; $list = new DynamicList($columns, $tables); - $list->orderBy('numero', true); + $list->orderBy('date_emission', true); $currency = Config::getInstance()->monnaie; diff --git a/templates/_form.tpl b/templates/_form.tpl new file mode 100644 index 0000000..8a55b0c --- /dev/null +++ b/templates/_form.tpl @@ -0,0 +1,228 @@ +{form_errors} + +
+ +{include file="%s/templates/_js.tpl"|args:$plugin_root} diff --git a/templates/config.tpl b/templates/config.tpl index 286582c..6402890 100644 --- a/templates/config.tpl +++ b/templates/config.tpl @@ -20,10 +20,10 @@ diff --git a/templates/facture.tpl b/templates/facture.tpl index 5c26840..8edecd5 100644 --- a/templates/facture.tpl +++ b/templates/facture.tpl @@ -5,6 +5,7 @@ {if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE)} {linkbutton shape="edit" href="%sfacture_modifier.php?id=%d"|args:$plugin_url,$facture.id label="Modifier ce document"} +{linkbutton shape="plus" href="%sfacture_ajouter.php?copy=%d"|args:$plugin_url,$facture.id label="Dupliquer ce document"} {/if} {linkbutton shape="download" href="%spdf.php?d&id=%d"|args:$plugin_url,$facture.id label="Télécharger ce document"} diff --git a/templates/facture_ajouter.tpl b/templates/facture_ajouter.tpl index 9aa9471..6e6ae58 100644 --- a/templates/facture_ajouter.tpl +++ b/templates/facture_ajouter.tpl @@ -1,232 +1,7 @@ {include file="admin/_head.tpl" title="Créer un document — %s"|args:$plugin.nom current="plugin_%s"|args:$plugin.id js=1} {include file="%s/templates/_menu.tpl"|args:$plugin_root current="facture"} -{form_errors} - - +{include file="%s/templates/_form.tpl"|args:$plugin_root} {include file="%s/templates/_js.tpl"|args:$plugin_root} diff --git a/templates/facture_modifier.tpl b/templates/facture_modifier.tpl index 195753d..a738782 100644 --- a/templates/facture_modifier.tpl +++ b/templates/facture_modifier.tpl @@ -1,226 +1,6 @@ {include file="admin/_head.tpl" title="Modifier un document — %s"|args:$plugin.nom current="plugin_%s"|args:$plugin.id js=1} {include file="%s/templates/_menu.tpl"|args:$plugin_root current="index"} -{form_errors} - - - -{include file="%s/templates/_js.tpl"|args:$plugin_root} +{include file="%s/templates/_form.tpl"|args:$plugin_root} {include file="admin/_foot.tpl"} diff --git a/www/admin/_inc.php b/www/admin/_inc.php index 463f4bb..b052a36 100644 --- a/www/admin/_inc.php +++ b/www/admin/_inc.php @@ -1,19 +1,31 @@ '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; $facture = new Facture; -$tpl->assign('www_url', WWW_URL); +$tpl->assign('www_url', \Garradin\WWW_URL); $tpl->assign('f_obj', $facture); $tpl->assign('plugin_url', Utils::plugin_url()); diff --git a/www/admin/config.php b/www/admin/config.php index 9b9c8ce..dd449fe 100644 --- a/www/admin/config.php +++ b/www/admin/config.php @@ -29,6 +29,8 @@ if (f('save') && $form->check('facturation_config')) $plugin->setConfig('validate_cp', (bool)f('validate_cp')); $plugin->setConfig('unique_client_name', (bool)f('unique_client_name')); + $plugin->setConfig('pattern', f('pattern')); + Utils::redirect(PLUGIN_URL . 'config.php?ok'); } catch (UserException $e) @@ -122,6 +124,8 @@ else $tpl->assign('ok', qg('ok') !== null); +$tpl->assign('patterns', \Garradin\Plugin\Facturation\PATTERNS_LIST); + // $tpl->assign('max_size', Utils::getMaxUploadSize()); $tpl->display(PLUGIN_ROOT . '/templates/config.tpl'); diff --git a/www/admin/facture_ajouter.php b/www/admin/facture_ajouter.php index 903d6f3..7d56042 100644 --- a/www/admin/facture_ajouter.php +++ b/www/admin/facture_ajouter.php @@ -2,6 +2,8 @@ namespace Garradin; +use const \Garradin\Plugin\Facturation\PATTERNS_LIST; + require_once __DIR__ . '/_inc.php'; $session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE); @@ -13,17 +15,39 @@ $db = DB::getInstance(); $step = $radio = false; $liste = []; +$designations = []; +$prix = []; +$csrf_key = 'ajout_facture'; $fields = $facture->recu_fields; -$tpl->assign('moyens_paiement', $facture->listMoyensPaiement()); +$moyens_paiement = $facture->listMoyensPaiement(true); + +$doc = null; +$require_number = $plugin->getConfig('pattern') ? false : true; + +if (qg('copy') !== null && $f = $facture->get((int)qg('copy'))) { + $doc = (array) $f; + + // Copié depuis facture_modifier.php + $doc['type'] = $f->type_facture; + $doc['numero_facture'] = ''; + $doc['base_receveur'] = $f->receveur_membre ? 'membre' : 'client'; + $doc['client'] = $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('moyen_paiement', f('moyen_paiement') ?: 'ES'); -if (f('add')) +if (f('save')) { - $form->check('ajout_facture', [ + $form->check($csrf_key, [ '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_echeance' => 'required|date_format:d/m/Y', // 'reglee' => '', @@ -31,7 +55,7 @@ if (f('add')) 'base_receveur' => 'required|in:membre,client', // 'client' => '', // 'membre' => '', - 'moyen_paiement' => 'required|in:' . implode(',', array_keys($facture->listMoyensPaiement())), + 'moyen_paiement' => 'required|in:' . implode(',', array_keys($moyens_paiement)), 'designation' => 'array|required', 'prix' => 'array|required' ]); @@ -43,10 +67,10 @@ if (f('add')) if ( count(f('designation')) !== count(f('prix')) ) { throw new UserException('Nombre de désignations et de prix reçus différent.'); - } - + } + $truc = [ - 'numero' =>f('numero_facture'), + 'numero' => f('numero_facture'), 'date_emission' => f('date_emission'), 'date_echeance' => f('date_echeance'), 'reglee' => f('reglee') == 1?1:0, @@ -80,7 +104,7 @@ if (f('add')) $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); @@ -140,7 +164,7 @@ elseif (f('add_cotis')) 'expiration' => $cotis['expiry'] ] ]; - $id = $facture->add($data); + $id = $facture->add($data, $plugin->getConfig('pattern')); Utils::redirect(PLUGIN_URL . 'facture.php?id='.(int)$id); } @@ -176,6 +200,9 @@ elseif (null !== f('type')) { $radio['type'] = f('type'); } +elseif (isset($doc['type'])) { + $radio['type'] = $doc['type']; +} else { $radio['type'] = FACT; @@ -187,8 +214,6 @@ $tpl->assign('types_details', $facture->types); $tpl->assign('client_id', f('client') ?: -1); $tpl->assign('membre_id', f('membre') ?: -1); -$designations = []; -$prix = []; $from_user = false; if (($d = f('designation')) && ($p = f('prix')) && implode($d)) { @@ -203,6 +228,17 @@ if (($d = f('designation')) && ($p = f('prix')) && implode($d)) } $from_user = true; } +else if (!empty($doc['contenu'])) { + foreach($doc['contenu'] as $k=>$v) + { + if (empty($v['designation']) && empty($v['prix'])) + { + continue; + } + $designations[] = $v['designation']; + $prix[] = $v['prix']; + } +} else { $designations = ['Exemple']; $prix = [250]; @@ -214,7 +250,7 @@ $date = new \DateTime; $date->setTimestamp(time()); $tpl->assign('date', $date->format('d/m/Y')); -$tpl->assign(compact('designations', 'prix', 'from_user', 'identite')); +$tpl->assign(compact('designations', 'prix', 'from_user', 'identite', 'csrf_key', 'doc')); $tpl->assign('membres', $db->getAssoc('SELECT id, '.$identite.' FROM membres WHERE id_category != -2 NOT IN (SELECT id FROM users_categories WHERE hidden = 1);')); $tpl->assign('clients', $db->getAssoc('SELECT id, nom FROM plugin_facturation_clients;')); diff --git a/www/admin/facture_modifier.php b/www/admin/facture_modifier.php index 013be9e..6b950fc 100644 --- a/www/admin/facture_modifier.php +++ b/www/admin/facture_modifier.php @@ -13,9 +13,9 @@ $db = DB::getInstance(); $step = false; $liste = []; -$fields = $facture->recu_fields; +$moyens_paiement = $facture->listMoyensPaiement(true); -$tpl->assign('moyens_paiement', $facture->listMoyensPaiement()); +$tpl->assign('moyens_paiement', $moyens_paiement); $tpl->assign('moyen_paiement', f('moyen_paiement') ?: 'ES'); qv(['id' => 'required|numeric']); @@ -26,11 +26,13 @@ if (!$f = $facture->get($id)) throw new UserException("Ce document n'existe pas."); } +$csrf_key = 'modifier_facture'; + // Traitement if(f('save')) { - $form->check('modifier_facture', [ + $form->check($csrf_key, [ 'type' => 'required|in:'.implode(',', [DEVIS, FACT, CERFA]), 'numero_facture' => 'required|string', 'date_emission' => 'required|date_format:d/m/Y', @@ -40,7 +42,7 @@ if(f('save')) 'base_receveur' => 'required|in:membre,client', // 'client' => '', // 'membre' => '', - 'moyen_paiement' => 'required|in:' . implode(',', array_keys($facture->listMoyensPaiement())), + 'moyen_paiement' => 'required|in:' . implode(',', array_keys($moyens_paiement)), 'designation' => 'array|required', 'prix' => 'array|required' ]); @@ -193,7 +195,6 @@ if ($step) // Affichage -$doc['moyens_paiement'] = $facture->listMoyensPaiement(true); // $doc['moyen_paiement'] = $doc['moyens_paiement'][$f->moyen_paiement]; $doc['moyen_paiement'] = $f->moyen_paiement; $doc['type'] = $f->type_facture; @@ -253,7 +254,7 @@ if ($f->type_facture != COTIS) } -$tpl->assign(compact('designations', 'prix', 'from_user', 'identite')); +$tpl->assign(compact('designations', 'prix', 'from_user', 'identite', 'csrf_key')); $tpl->assign('membres', $db->getAssoc('SELECT id, '.$identite.' FROM membres WHERE id_category != -2 NOT IN (SELECT id FROM users_categories WHERE hidden = 1);')); $tpl->assign('clients', $db->getAssoc('SELECT id, nom FROM plugin_facturation_clients;')); @@ -261,4 +262,6 @@ $date = new \DateTime; $date->setTimestamp(time()); $tpl->assign('date', $date->format('d/m/Y')); +$tpl->assign('require_number', true); + $tpl->display(PLUGIN_ROOT . '/templates/facture_modifier.tpl'); \ No newline at end of file