Compare commits

...

108 Commits

Author SHA1 Message Date
4aafa3f44f Ajout champ contact à la fiche client 2025-03-25 20:49:24 +01:00
aff20099aa Ajout divers champs pour facture/devis 2025-03-23 18:27:47 +01:00
950a6b8b2c Ajout champ note à la fiche client 2025-03-22 18:21:00 +01:00
4ee3f65d0a Suppression possibilité générer reçu dons et cotisations 2025-03-21 20:32:41 +01:00
060802a43d Correction config : oubli enregistrement coordonnées membres 2025-03-21 17:41:48 +01:00
f63f3b6ecd Amélioration contrôles saisie facture 2025-02-26 11:02:45 +01:00
6e2ee31670 Ajout Mollie à la table moyens de paiement 2025-01-04 15:02:15 +01:00
38494a1a85 màj CHANGELOG, README et plugin.ini 2024-10-21 09:09:25 +02:00
6248904bd8 correction typos 2024-10-19 14:32:41 +02:00
f6eef8897e Annulation "fusion branche simplification"
This reverts commit 16daac2e24, reversing
changes made to 46d0c838ae.
2024-10-19 14:26:06 +02:00
201682f4d7 Changement mention finale pour devis 2024-10-18 22:25:31 +02:00
77180a38ff Suppression message inutile pour devis 2024-10-18 21:30:45 +02:00
16daac2e24 fusion branche simplification 2024-09-19 09:54:34 +02:00
46d0c838ae Prise en compte du cas où le champ nom n'existe pas 2024-09-18 18:44:06 +02:00
da713cc06d Correction identité membre dans formulaire 2024-09-10 15:37:22 +02:00
72999b9fc6 suppression image cerfa 2024-09-06 22:03:07 +02:00
f321e83e20 Simplification : suppression génération reçu dons et cotisations 2024-09-06 21:40:27 +02:00
57e6ad09f9 Ajout possibilité choisir champs identité et adresse membre 2024-08-30 21:32:13 +02:00
2ecbac159a templates/config.tpl : correction typo 2024-08-13 20:43:03 +02:00
3d5af4ebb0 Ajout impression logo avec option de configuration 2024-06-20 15:55:33 +02:00
f93bb57906 Adaptation Readme à la situation actuelle 2024-06-08 20:52:29 +02:00
128e538431 Correction typo 2024-06-03 09:55:27 +02:00
bd1f541bc0 Correction typo 2024-06-02 22:37:04 +02:00
dc7f944ead Mise-à-jour README et CHANGELOG 2024-04-03 13:11:14 +02:00
1d4f17ead6 Correction erreur si pas de prix saisi 2024-03-29 11:57:36 +01:00
b0a44b85c6 Ajout format numéro téléphone 2024-03-12 18:36:07 +01:00
26f4514fd0 Correction bug si SIRET non renseigné 2024-01-12 18:05:38 +01:00
e2065bdb37 Amélioration cosmétique 2024-01-12 17:09:49 +01:00
fa519a9567 Mise-à-jour CHANGELOG 2024-01-12 16:48:27 +01:00
39bde25d24 Améliorations cosmétiques 2024-01-12 16:37:04 +01:00
b2e63c0383 Ne pas rendre la saisie du SIRET obligatoire pour les cas où il ne
serait pas disponible
2024-01-12 12:05:38 +01:00
b5dc9d0efa Correction oubli 2024-01-12 11:50:04 +01:00
c9300fe943 Ajout pagination liste factures 2024-01-12 11:30:37 +01:00
b6bb4fd80d Ajout numéro SIREN/SIRET pour un client 2024-01-11 20:41:19 +01:00
438e9c6116 Mise-à-jour CHANGELOG 2024-01-11 11:12:31 +01:00
e22ea9ca8b Augmentation limite taille case saisie numéro 2024-01-11 10:50:58 +01:00
80a5245bc8 Suppression code inutile 2024-01-11 10:28:21 +01:00
a187babbac Correction bug oubli choix receveur 2024-01-10 18:43:23 +01:00
93c1daeffd Mise-à-jour README 2024-01-10 18:01:58 +01:00
64ca9a6aba Évolution plugin.ini 2024-01-10 17:36:13 +01:00
42e893f8d3 Élimination bug numérotation si suppression de document 2024-01-10 17:35:38 +01:00
6c3af00f16 Résolution problèmes upgrade.php 2024-01-10 17:35:07 +01:00
bf45ffd223 Correction erreur adresse et numéro téléphone 2024-01-05 11:18:02 +01:00
d258b21267 Order members alphabetically by the fields defining the name 2023-11-23 14:17:05 +01:00
cff7b57a41 Ajout appel fonction upgrade 2023-11-03 14:53:50 +01:00
9de9e10987 Correction erreur nom 2023-10-23 19:56:44 +02:00
b9e8115af4 Correction erreur placement directive charset 2023-10-20 12:21:57 +02:00
2454883ffc Élimination de form->check() pour version 1.3 2023-10-19 10:24:49 +02:00
0ed03eb362 Changement nom variables pour version 1.3 2023-10-19 10:23:09 +02:00
bohwaz
ac8c79bf9e No need for menuItem signal now 2023-09-13 15:24:46 +02:00
Noizette
c4d9b8d38b Update help text 2023-08-04 18:02:23 +02:00
Noizette
d32c589a4a Fix plugin config 2023-08-04 18:00:08 +02:00
Noizette
899bf02775 Fix install, use Paheko sign, fix $plugin vars 2023-08-04 17:41:29 +02:00
Noizette
235f8ceeed Migrate to Paheko 1.3 2023-08-01 22:56:38 +02:00
Noizette
161659acfb Add meta charset in generated PDF 2023-07-22 21:31:15 +02:00
Noizette
3537314762 First steps in update to 1.3.0 2023-07-21 23:28:54 +02:00
Noizette
4aaed6f3c2 Fix #45 champs numéro vide en créant des cotis 2023-04-17 01:10:08 +02:00
Noizette
b08572e6d2 Update plugin version 2023-04-16 00:52:15 +02:00
Noizette
97404460ee Add new menu item call 2023-04-16 00:51:55 +02:00
Noizette
b74226871e Factorize facture pages + moyen_paiement things 2023-04-16 00:08:10 +02:00
Noizette
868174e6ab Remove utr8_decode call, PHP8 depreciation 2023-04-16 00:05:29 +02:00
Noizette
00ae3cd89e Fix another PHP8 depreciation 2023-04-16 00:04:47 +02:00
Noizette
56f90fad60 Remove duplicate js inclusion 2023-04-15 22:47:14 +02:00
Noizette
b2c1f2adea Remove bad fix for unreproducible bug 2023-04-15 02:55:40 +02:00
Noizette
afedc83193 Fix some PHP8 depreciation 2023-04-14 19:48:07 +02:00
unset-name
8a10dcfab3 Merge branch 'master' of gitlab.com:noizette/garradin-plugin-facturation 2023-04-14 18:56:06 +02:00
unset-name
4619ac0ffd Fix deprecated PHP8.2 string interpolation 2023-04-14 18:52:48 +02:00
Noizette
5f87e96df4 Update gitlab link 2022-04-14 04:40:55 +00:00
Noizette
3635f5a5da Update version number 2022-03-03 04:41:47 +01:00
Noizette
fd7adc2a58 Fix formulaire bloqué 2022-03-03 04:41:09 +01:00
Noizette
c3da50df04 Fix indent 2022-01-31 20:00:45 +01:00
Noizette
3f92e849fa Update version number 2022-01-31 19:51:40 +01:00
Noizette
0694d1fb51 Fix moyen paiement 2022-01-31 19:49:36 +01:00
Noizette
3ae4b3f177 Update version number 2022-01-10 18:17:54 +01:00
Noizette
727d8b11f2 Fix alignement PDF 2022-01-10 18:16:32 +01:00
Noizette
af7b3a9eaf Config TTC/HT 2022-01-10 06:40:40 +01:00
Noizette
5de9d9fa97 Fix champs required 2022-01-10 06:19:26 +01:00
Noizette
57b54bc14c Fix formulaire CERFA 2022-01-07 01:32:14 +01:00
Noizette
7adb4ae2f3 Update changelog + version 2022-01-07 01:25:54 +01:00
Noizette
61f40e100d Integration contrib @DDgallo sur CERFA 2022-01-07 01:24:32 +01:00
Noizette
637c493671 Config par défaut dans config.json et non install.php 2022-01-06 23:01:36 +01:00
Noizette
0a00c207ae Merge branch 'improve-fix' into 'master'
Fix config.json syntax

See merge request ramoloss/garradin-plugin-facturation!6
2022-01-06 21:55:44 +00:00
bohwaz
8de2c8950c Fix config.json syntax 2022-01-06 12:26:17 +01:00
Noizette
ec1b1f3c65 Merge branch 'duplicate' from bohwaz
Duplication de document et numérotation automatique

See merge request ramoloss/garradin-plugin-facturation!5
2021-12-21 02:46:47 +00:00
Noizette
c042452e56 Merge branch 'improve' from bohwaz
Améliorations / corrections diverses

See merge request ramoloss/garradin-plugin-facturation!4
2021-12-21 02:33:26 +00:00
bohwaz
e4402e4553 PNG de meilleure résolution et plus compressés 2021-12-17 21:33:09 +01:00
bohwaz
2e7710f29c Ajout fonction : numérotation automatique des documents 2021-12-17 21:10:54 +01:00
bohwaz
c1f34a11fa Fix: faille de sécu potentielle = passer les arguments à la requête correctement 2021-12-17 14:58:11 +01:00
bohwaz
8e2ddf19c6 Duplication de document pour transformer rapidement un devis en facture 2021-12-17 14:49:03 +01:00
bohwaz
92408f663c Mise en commun du code entre modifier et ajouter, moins de duplication à gérer 2021-12-17 14:35:12 +01:00
bohwaz
a28b644795 L'embed est du HTML, pas un PDF 2021-12-17 14:20:18 +01:00
bohwaz
651d035ecc Bouton supprimer ne devrait être visible que par les admins 2021-12-17 14:19:52 +01:00
bohwaz
b644eb34bf Fix calcul de la somme des éléments en direct 2021-12-17 14:17:59 +01:00
bohwaz
364c7065fc Correctifs du formulaire pour sélection membre/client 2021-12-17 14:11:25 +01:00
bohwaz
4fb4c9025c Fix JS pour la sélection client/membre dans le formulaire d'ajout 2021-12-17 14:09:27 +01:00
bohwaz
fcbcd813df Ajout boutons accès rapide dans la liste des clients 2021-12-17 14:09:10 +01:00
bohwaz
f0d644e947 Le type est cotisation, pas facture, d'oh 2021-12-17 14:08:29 +01:00
bohwaz
e275520481 Fix: la modification de cotisation était cassée 2021-12-17 14:08:00 +01:00
bohwaz
fa35d7479d Liste dynamique des documents 2021-12-17 13:47:23 +01:00
bohwaz
440b2e3e66 Liste dynamique triable des clients + export CSV/ODS 2021-12-17 13:01:05 +01:00
bohwaz
6db516f5b2 Modernisation liste clients + mise en commentaire fonctionnalité non implémentée 2021-12-17 12:44:02 +01:00
bohwaz
530f0886b3 Suppression de client : utilisation du formulaire générique 2021-12-17 12:38:58 +01:00
bohwaz
2d4052e984 Correctif: isDeletable renvoyait TRUE si le client n'était *PAS* supprimable, inversion du sens 2021-12-17 12:38:25 +01:00
bohwaz
0ef04439f1 Utilisation de {input} pour le formulaire de modification 2021-12-17 12:37:46 +01:00
bohwaz
73248375bd Corrige l'apparence de la fiche client 2021-12-17 12:10:43 +01:00
bohwaz
f39755121d Corrige le fonctionemment de l'ajout de document quand aucun client n'existe 2021-12-17 12:07:53 +01:00
bohwaz
97abfb4774 Modernisation des éléments du formulaire 2021-12-17 11:56:52 +01:00
Noizette
323bc87a38 Mise à jour de l'aide 2021-12-16 21:41:30 +00:00
57 changed files with 2123 additions and 1916 deletions

View File

@ -1,4 +1,62 @@
0.6.0 :
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
- Correction erreur si pas de prix saisi
0.8.5
- Ajout numéro SIREN/SIRET pour les clients
- Ajout pagination liste factures
0.8.4
- Correction bug si identité définie par plusieurs champs
- Correction bug oubli choix receveur
- Mise-à-jour Changelog
0.8.3 :
- Mise-à-jour README
0.8.2 :
- Tri des membres en ordre alphabétique
- Élimination de plusieurs bugs
0.8.1 :
- reprise du code par jce (lesanges@zaclys.net ; https://git.roflcopter.fr/lesanges/paheko-plugin-facturation)
- Achèvement migration vers Paheko 1.3
0.7.4 :
- Corrections de dépréciations PHP, le plugin être compatible PHP8.2
- Quelques problèmes réglés de formulaires invalidables
- Nettoyage du code et factorization des facture_{ajouter,modifier}.php
0.7.3 :
- Règle le problème de formulaire bloqué que je croyais avoir résolu la dernière fois :)
0.7.2 :
- Petite correction du moyen de paiement qui n'était pas retenu à cause d'un doublon dans le formulaire.
0.7.1 :
- Ajout d'une case dans la config pour choisir si les factures sont TTC ou HT
- Le formulaire de document était cassé à cause d'un champs obligatoire invisible
0.7.0 :
- Modernisation des formulaires, de la fiche client
- Liste dynamiques des documents et des clients + export CSV/ODS
- Numérotation automatique des documents
- Ajout du bouton Dupliquer sur les documents
- Divers fixes
- Changements de la gestion des CERFA :
- organisation du champ `contenu` pour gérer les paramètres de CERFA sous la forme {"forme":"3","nature":"1","texte":"1"}
- ajout d'une table `plugin_facturation_txt_cerfa` pour stocker les textes explicatifs additionnels
- formulaires adaptés aux CERFA pour permettre le choix de la forme, de la nature, du mode de versement et du texte explicatif éventuel
0.6.0 :
- Génération des PDF par Garradin, on se débarasse de mPDF et l'installation du plugin devient standard
- Fix #32 (prix total affiché incorrect), en stockant les prix sous formes d'entiers à la manière de Garradin
- Possibilité de supprimer un document

View File

@ -1,4 +1,4 @@
Plugin Facturation pour Garradin
Plugin Facturation pour Paheko
Copyright (C) 2019 zou
This program is free software: you can redistribute it and/or modify

View File

@ -1,54 +1,50 @@
# Plugin Facturation pour Garradin
# Plugin Facturation pour Paheko (ex Garradin)
Plugin de facturation pour le logiciel de gestion d'association Garradin ( https://garradin.eu/ - https://fossil.kd2.org/garradin ).
Source : https://gitlab.com/ramoloss/garradin-plugin-facturation
Plugin de facturation pour le logiciel de gestion d'association Paheko
( https://paheko.eu/ - https://fossil.kd2.org/paheko ).
## Installation:
Vous pouvez télécharger l'archive .tar.gz depuis la page des [releases](https://gitlab.com/ramoloss/garradin-plugin-facturation/-/releases), et la placer directement dans le dossier plugins de Garradin.
Source :
- 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
### Anciennes versions (<0.6)
Normalement, les plugins de Garradin 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 garradin-plugin-facturation-v0.5.0.tar.gz
mv garradin-plugin-facturation-v0.5.0 facturation
rm garradin-plugin-facturation-v0.5.0.tar.gz
chown -R www-data:www-data facturation/
chmod -R g+w facturation/
Supprimer l'archive permet à Garradin 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
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
@ -57,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/

355
admin/_facture_common.php Normal file
View File

@ -0,0 +1,355 @@
<?php
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 {
$target = ($target === 'new' ? true:false);
}
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE);
use Paheko\DB;
use stdClass;
use const \Paheko\Plugin\Facturation\PATTERNS_LIST;
$db = DB::getInstance();
$step = false;
$radio = $liste = $designations = $prix = [];
$fields = $facture->recu_fields;
$moyens_paiement = $facture->listMoyensPaiement(true);
$tpl->assign('moyens_paiement', $moyens_paiement);
$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',
'2' => 'Acte sous seing privé',
'3' => 'Don manuel',
'4' => 'Autres'));
$tpl->assign('natures_don', array('1' => 'Numéraire',
'2' => 'Chèque',
'3' => 'Virement, CB; ...'));
$tpl->assign('textes_don', $facture->listTextesCerfa());
if ( !$target ) {
f(['id' => 'required|numeric']);
$id = (int) qg('id');
if (!$f = $facture->get($id))
{
throw new UserException("Ce document n'existe pas.");
}
}
// Traitement
$data=[];
$form->runIf(f('save') && !$form->hasErrors(),
function () use ($client, &$data, $form)
{
try
{
if ( count(f('designation')) !== count(f('prix')) )
{
throw new UserException('Nombre de désignations et de prix reçus différent.');
}
$data = [
'numero' => f('numero_facture'),
'date_emission' => f('date_emission'),
'date_echeance' => f('date_echeance'),
'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)
{
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;
$data['contenu'][$k]['prix'] = Utils::moneyToInteger(f('prix')[$k]);
$data['toto'] += Utils::moneyToInteger(f('prix')[$k]);
}
$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');
$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')
{
$data['receveur_membre'] = 0;
$data['receveur_id'] = f('client');
}
elseif (f('base_receveur') == 'membre')
{
$data['receveur_membre'] = 1;
$data['receveur_id'] = f('membre');
}
else
{
throw new UserException('Vous devez indiquer si le receveur est un client ou un membre');
}
}
catch(UserException $e)
{
$form->addError($e->getMessage());
}
}, $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 ($step)
{
try
{
$liste = $facture->getCotis((int)f('membre_cotis'));
}
catch (UserException $e)
{
$form->addError($e->getMessage());
}
}
elseif (count($data) > 0)
{
if ($target)
{
$id = $facture->add($data, $plugin->getConfig('pattern'));
Utils::redirect(PLUGIN_ADMIN_URL . 'facture.php?id='.(int)$id);
}
else
{
if ($facture->edit($id, $data))
{
Utils::redirect(PLUGIN_ADMIN_URL . 'facture.php?id='.(int)$id);
}
throw new UserException('Erreur d\'édition du reçu');
}
}
}
// Affichage
if ($target)
{
$doc = null;
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;
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 = qg('t') ? (int) qg('t') : null;
// Si le type est défini dans l'URL
if (in_array($type, [DEVIS, FACT, CERFA, COTIS], true))
{
$radio['type'] = $type;
} // ... s'il a été rempli dans le formulaire envoyé
elseif (null !== f('type'))
{
$radio['type'] = f('type');
} // ... s'il est défini dans le document copié
elseif (isset($doc['type'])) {
$radio['type'] = $doc['type'];
} // ... ou par défaut
else
{
$radio['type'] = FACT;
}
}
else
{
$doc['moyen_paiement'] = $f->moyen_paiement;
$doc['type'] = $f->type_facture;
$doc['numero_facture'] = $f->numero;
$doc['reglee'] = $f->reglee;
$doc['base_receveur'] = $f->receveur_membre?'membre':'client';
$doc['client'] = $f->receveur_id;
$doc['membre'] = $f->receveur_id;
$doc['contenu'] = $f->contenu;
$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;
$doc['forme_don'] = $f->contenu['forme'];
$doc['nature_don'] = $f->contenu['nature'];
$doc['texte_don'] = $f->contenu['texte'];
}
$radio['type'] = f('type')??$doc['type'];
}
$tpl->assign('types_details', $facture->types);
$tpl->assign('client_id', f('client') ?: -1);
$tpl->assign('membre_id', f('membre') ?: -1);
// C'est un peu l'équivalent de form_field, mais j'avais écrit ça avant
// et oulala, c'est un peu complexe, faudrait réfléchir keskivomieux
$from_user = false;
if (in_array($radio['type'], [DEVIS, FACT]))
{
if (($d = f('designation')) && ($p = f('prix')) && implode($d))
{
foreach($d as $k=>$v)
{
if (empty($v) && empty($p[$k]))
{
continue;
}
$designations[] = $v;
$prix[] = $p[$k];
}
$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];
*/
}
}
$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', 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')]);
if ($target) {
$tpl->display(PLUGIN_ROOT . '/templates/facture_ajouter.tpl');
} else {
$tpl->display(PLUGIN_ROOT . '/templates/facture_modifier.tpl');
}

69
admin/_inc.php Normal file
View File

@ -0,0 +1,69 @@
<?php
namespace Paheko\Plugin\Facturation;
use Paheko\Config;
use Paheko\Utils;
define('DEVIS', 0);
define('FACT', 1);
define('CERFA', 2);
define('COTIS', 3);
const PATTERNS_LIST = [
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;
$facture = new Facture;
$tpl->assign('www_url', \Paheko\WWW_URL);
$tpl->assign('f_obj', $facture);
$identite = implode(',', \Paheko\Users\DynamicFields::getNameFields());
$tpl->register_function('money_fac', function (array $params)
{
static $params_list = ['value', 'name', 'user'];
// Extract params and keep attributes separated
$attributes = array_diff_key($params, array_flip($params_list));
$params = array_intersect_key($params, array_flip($params_list));
extract($params, \EXTR_SKIP);
$current_value = null;
if (isset($value)) {
$current_value = $value;
}
if (!isset($user)) {
$user = false;
}
if (!isset($name))
{
$name = 'prix[]';
}
if (null !== $current_value && !$user) {
$current_value = Utils::money_format($current_value, ',', '');
}
if (null !== $current_value) {
$current_value = htmlspecialchars($current_value, ENT_QUOTES, 'UTF-8');
}
$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);
}
);

View File

@ -1,6 +1,6 @@
<?php
namespace Garradin;
namespace Paheko;
require_once __DIR__ . '/_inc.php';

View File

@ -1,12 +1,12 @@
<?php
namespace Garradin;
namespace Paheko;
require_once __DIR__ . '/_inc.php';
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_READ);
qv(['id' => 'required|numeric']);
f(['id' => 'required|numeric']);
$id = (int) qg('id');

46
admin/client_modifier.php Normal file
View File

@ -0,0 +1,46 @@
<?php
namespace Paheko;
require_once __DIR__ . '/_inc.php';
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE);
f(['id' => 'required|numeric']);
$id = (int) qg('id');
$c = $client->get($id);
if (!$c)
{
throw new UserException("Ce client n'existe pas.");
}
$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'),
'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');
$tpl->assign('client', $c);
$tpl->display(PLUGIN_ROOT . '/templates/client_modifier.tpl');

View File

@ -1,12 +1,12 @@
<?php
namespace Garradin;
namespace Paheko;
require_once __DIR__ . '/_inc.php';
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE);
qv(['id' => 'required|numeric']);
f(['id' => 'required|numeric']);
$id = (int) qg('id');
@ -17,24 +17,13 @@ if (!$client)
throw new UserException("Ce client n'existe pas.");
}
if (f('delete'))
{
$form->check('delete_client_'.$c->id);
if (!$form->hasErrors())
{
try {
$client->delete($c->id);
Utils::redirect(PLUGIN_URL . 'clients.php');
}
catch (UserException $e)
{
$form->addError($e->getMessage());
}
}
}
$csrf_key = 'delete_client_'.$c->id;
$form->runIf('delete', function () use ($client, $c) {
$client->delete($c->id);
}, $csrf_key, PLUGIN_ADMIN_URL . 'clients.php');
$tpl->assign('deletable', $client->isDeletable($id));
$tpl->assign('client', $c);
$tpl->assign(compact('csrf_key'));
$tpl->display(PLUGIN_ROOT . '/templates/client_supprimer.tpl');

39
admin/clients.php Normal file
View File

@ -0,0 +1,39 @@
<?php
namespace Paheko;
require_once __DIR__ . '/_inc.php';
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_READ);
$form->runIf(f('add') && !$form->hasErrors(),
function () use ($client, $form)
{
try
{
$id = $client->add([
'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')
]);
$id ? Utils::redirect(PLUGIN_ADMIN_URL . 'client.php?id='.(int)$id):'';
}
catch (UserException $e)
{
$form->addError($e->getMessage());
}
}, 'add_client');
$list = $client->list();
$list->loadFromQueryString();
$tpl->assign(compact('list'));
$tpl->display(PLUGIN_ROOT . '/templates/clients.tpl');

43
admin/config.php Normal file
View File

@ -0,0 +1,43 @@
<?php
namespace Paheko;
use Paheko\Users\DynamicFields;
require_once __DIR__ . '/_inc.php';
$session->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')));
$plugin->setConfigProperty('ttc', (bool) f('ttc'));
$plugin->setConfigProperty('numero_rue_asso', trim(f('numero_rue_asso')));
$plugin->setConfigProperty('rue_asso', trim(f('rue_asso')));
$plugin->setConfigProperty('cp_asso', trim(f('cp_asso')));
$plugin->setConfigProperty('ville_asso', trim(f('ville_asso')));
$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();
}, 'facturation_config', PLUGIN_ADMIN_URL . 'config.php?ok');
$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');

View File

@ -1,16 +1,16 @@
<?php
namespace Garradin;
namespace Paheko;
require_once __DIR__ . '/_inc.php';
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_READ);
use Garradin\Membres;
use Paheko\Users\Users;
$membres = new Membres;
$users = new Users;
qv(['id' => 'required|numeric']);
f(['id' => 'required|numeric']);
$id = (int) qg('id');
$f = $facture->get($id);
@ -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')?:'');

View File

@ -0,0 +1,9 @@
<?php
namespace Paheko;
$target = 'new';
$csrf_key = 'ajout_facture';
$require_number = $plugin->getConfig('pattern') ? false : true;
require __DIR__ .'/_facture_common.php';

View File

@ -0,0 +1,9 @@
<?php
namespace Paheko;
$target = 'edit';
$csrf_key = 'modifier_facture';
$require_number = true;
require __DIR__ .'/_facture_common.php';

View File

@ -0,0 +1,34 @@
<?php
namespace Paheko;
require_once __DIR__ . '/_inc.php';
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE);
f(['id' => 'required|numeric']);
$id = (int) qg('id');
$f = $facture->get($id);
if (!$client)
{
throw new UserException("Ce document n'existe pas.");
}
$form->runIf(f('delete') && !$form->hasErrors(),
function () use ($facture, $f, $form)
{
try {
$facture->delete($f->id);
Utils::redirect(PLUGIN_ADMIN_URL . 'index.php');
}
catch (UserException $e)
{
$form->addError($e->getMessage());
}
});
$tpl->assign('doc', $f);
$tpl->display(PLUGIN_ROOT . '/templates/facture_supprimer.tpl');

19
admin/index.php Normal file
View File

@ -0,0 +1,19 @@
<?php
namespace Paheko;
if ($plugin->needUpgrade()) {
$plugin->upgrade();
}
require_once __DIR__ . '/_inc.php';
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_READ);
$list = $facture->list();
$list->loadFromQueryString();
$list->setPageSize(50);
$tpl->assign(compact('list'));
$tpl->display(PLUGIN_ROOT . '/templates/index.tpl');

View File

@ -1,13 +1,14 @@
<?php
namespace Garradin;
namespace Paheko;
require_once __DIR__ . '/_inc.php';
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_READ);
$membres = new Membres;
$users = new Users\Users;
qv(['id' => 'required|numeric']);
f(['id' => 'required|numeric']);
$id = (int) qg('id');
$sign_tag = UserTemplate\Functions::signature();
// Vérification que le document existe
if (!$f = $facture->get($id))
@ -22,20 +23,37 @@ try
{
if ($f->receveur_membre)
{
$c = $membres->get($f->receveur_id);
$c->identite = $c->$identite;
$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);
$c->identite = $c->nom;
$nom_client = $c->nom;
}
}
catch(UserException $e)
@ -59,41 +77,81 @@ if ($f->type_facture != CERFA)
switch ($f->type_facture)
{
case FACT:
$doc = 'Facture n°'. $f->numero;
break;
$doc = 'Facture n° '. $f->numero;
$txtemis = $doc . " - Émise le " . $emission;
$txtdest = "Adressée à :";
break;
case DEVIS:
$doc = 'Devis n°'. $f->numero;
break;
$doc = 'Devis n° '. $f->numero;
$txtemis = $doc . " - Émis le " . $emission;
$txtdest = "Adressé à :";
break;
case COTIS:
$doc = 'Reçu de cotisation n°'. $f->numero;
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 !
if ($plugin->getConfig('rue_asso') != null &&
$plugin->getConfig('cp_asso') != null &&
$plugin->getConfig('ville_asso') != null)
{
$adresse =
(($plugin->getConfig('numero_rue_asso') != null) ? $plugin->getConfig('numero_rue_asso') . " " : "") .
$plugin->getConfig('rue_asso') . "<br>" .
$plugin->getConfig('cp_asso') . " " .
$plugin->getConfig('ville_asso');
}
else if ($config->get('org_address') != null)
{
$adresse = str_replace("\n", '<br>', $config->get('org_address'));
}
else {
$adresse = "";
}
$logo='';
if ($plugin->getConfig('logo')) {
$logo = '<img id="logo" src="' . $config->fileURL('logo') . '" />';
}
$asso =
// 'Émis par :<br><br>'.
'<b>'.$config->get('nom_asso')."</b><br>".
str_replace("\n", '<br>', $config->get('adresse_asso'))."<br>".
'<b>'.$config->get('org_name')."</b><br>".
$adresse ."<br>".
(($t = $plugin->getConfig('rna_asso'))?"RNA : $t<br>":'').
(($t = $plugin->getConfig('siret_asso'))?"SIRET : $t<br>":'').
(($t = $plugin->getConfig('siret_asso'))?"SIRET : " . implode(' ', str_split($t, 3)) . "<br>":'').
(($t = $config->get('email_asso'))?"Email : $t<br>":'').
(($t = $config->get('site_asso'))?"Site web : $t<br>":'');
$receveur =
'Adressé à :<br><br>'.
'<b>'.$c->identite.'</b><br>'.
$txtdest.'<br>'.
'<b>'.$nom_client.'</b><br>'.
(($t = $f->nom_contact)?"Contact : $t<br>":'').
$c->adresse."<br>".
$c->code_postal.' '.$c->ville."<br>".
(($t = $c->siret)?"SIREN/SIRET : " . implode(' ', str_split($t, 3)) . "<br>":'').
(($t = $c->email)?"Email : $t<br>":'').
(($t = $c->telephone)?"Tel : $t<br>":'');
if ($f->type_facture == FACT) {
$receveur .=
(($t = $f->numero_commande)?"Commande N° : $t<br>":'').
(($t = $f->reference_acheteur)?"Référence : $t<br>":'');
}
$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é reglée.';
$footer = str_replace("\n", '<br>', $plugin->getConfig('footer'));
if ($f->type_facture == FACT) {
$reglee = !$f->reglee?'Cette facture est en attente de règlement.':'Cette facture a été réglée.';
} else {
$reglee = "";
}
$footer = str_replace("\n", '<br>', $plugin->getConfig('footer') ?? '[Pied de page à configurer]');
$ttc = $plugin->getConfig('ttc') ? 'TTC':'HT';
// Génération du contenu de la facture
ob_start();
@ -119,7 +177,7 @@ EOF;
$i = 1;
foreach($f->contenu as $k=>$v)
{
{
echo '<tr><td>';
echo str_replace("\n", '<br>', $v['designation']);
echo '</td><td>';
@ -132,8 +190,8 @@ EOF;
</tbody>
<tfoot>
<tr>
<td><h3>Total</h3>Net à payer</td>
<td><b>$total </b><br>(HT)</td>
<td><b>Total</b><br>Net à payer</td>
<td><b>$total </b><br>({$ttc})</td>
</tr>
</tfoot>
</table>
@ -144,12 +202,17 @@ EOF;
$echeance <br>
$reglee
Moyen de paiement : $moyen_paiement
<p>
$footer
</p>
</footer>
EOF;
if ($f->type_facture == DEVIS) {
echo <<<EOF
<p><b>Bon pour accord, date et signature<b></p>
EOF;
}
$content = ob_get_clean();
@ -158,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')
@ -194,19 +257,20 @@ EOF;
}
//-- Layout du document
ob_start();
echo <<<EOF
<!DOCTYPE html>
<html>
<head>
<title>${doc}_${emission}</title>
<meta charset="UTF-8">
<title>{$doc}_{$emission}</title>
<style>
@page {
size: A4 portrait;
margin: 0;
}
body {
padding: 4mm;
font-family: Helvetica, Arial, sans;
@ -240,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;
@ -286,11 +357,12 @@ EOF;
</head>
<body>
<p class="titre">
$doc - Émis le $emission
$txtemis
</p>
<table class="adressage">
<tr>
<td id="logo">$logo</td>
<td>$asso</td>
<td>$receveur</td>
</tr>
@ -309,9 +381,10 @@ 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['nom_asso'] = $config->get('nom_asso');
$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');
@ -320,7 +393,7 @@ elseif ($f->type_facture == CERFA)
$t['objet1'] = $plugin->getConfig('objet_1');
$t['objet2'] = $plugin->getConfig('objet_2');
$t['nom'] = $c->identite;
$t['nom'] = $nom_client;
$t['adresse'] = $c->adresse;
$t['cp'] = $c->code_postal;
$t['ville'] = $c->ville;
@ -328,9 +401,13 @@ elseif ($f->type_facture == CERFA)
$t['total_lettre'] = numfmt_create('fr_FR', \NumberFormatter::SPELLOUT)->format($f->total/100). ' euros';
$t['d'] = utf8_decode($f->date_emission->format('d'));
$t['m'] = utf8_decode($f->date_emission->format('m'));
$t['Y'] = utf8_decode($f->date_emission->format('Y'));
$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')){
@ -343,34 +420,61 @@ elseif ($f->type_facture == CERFA)
$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'] = ' top: 158.2mm; left: 15mm;';
$t['pos'] = 'left: 15mm;';
break;
case 'CH':
$t['pos'] = ' top: 158.2mm; left: 57.3mm;';
$t['pos'] = 'left: 57.3mm;';
break;
default:
$t['pos'] = ' top: 158.2mm; left: 115.2mm;';
$t['pos'] = 'left: 115.2mm;';
}
$t['d2'] = utf8_decode($f->date_echeance->format('d'));
$t['m2'] = utf8_decode($f->date_echeance->format('m'));
$t['Y2'] = utf8_decode($f->date_echeance->format('Y'));
$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>
<title>${doc}_${emission}</title>
<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;
@ -392,13 +496,13 @@ elseif ($f->type_facture == CERFA)
background-size: cover;
background-position: -5mm -4.8mm;
}
#p1 {
background-image: url('${url}p/facturation/cerfa-1.png');
background-image: url('{$url}p/facturation/cerfa-1.png');
}
#p2 {
background-image: url('${url}p/facturation/cerfa-2.png');
background-image: url('{$url}p/facturation/cerfa-2.png');
position: relative;
}
@ -406,47 +510,46 @@ elseif ($f->type_facture == CERFA)
</head>
<body>
<div class="page" id="p1">
<div style="top: 10mm; left: 170mm;">${t['numero']}</div>
<div style="top: 10mm; left: 170mm;">{$t['numero']}</div>
<div style="top: 35mm; left: 20mm;">${t['nom_asso']}</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: 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: 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: 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: 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: 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: 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: 113mm; left: 115mm;">X</div>
<div style="top: 136mm; left: 15mm;">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="${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;"><img src="${url}plugin/facturation/sign.png" style="width: 50mm;"/></div>
<div style="top: 243mm; left: 137mm;">{$sign_tag}</div>
</div>
</body>
</html>
@ -461,7 +564,7 @@ if(qg('d') !== null)
{
$filename = 'Print';
if (preg_match('!<title>(.*)</title>!U', $html, $match)) {
$filename = trim($match[1]);
$filename = str_replace(" ", "_", trim($match[1]));
}
header('Content-type: application/pdf');

View File

@ -1,3 +1,6 @@
{
"footer": "[EXEMPLE]\nAssociation exonérée des impôts commerciaux\nEn cas de retard de paiement, indemnité forfaitaire légale pour frais de recouvrement : 40,00 €\n[Coordonnées bancaires]\nAssociation enregistrée en préfecture de XXX au numéro YYY",
"pattern": "%{type}-%{year}-%{ynumber}",
"ttc": false,
"validate_cp": true
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -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)
);
@ -21,10 +24,12 @@ CREATE TABLE IF NOT EXISTS plugin_facturation_clients (
adresse TEXT NOT NULL,
code_postal TEXT NOT NULL,
ville TEXT NOT NULL,
-- date_creation INTEGER NOT NULL,
siret TEXT,
date_creation TEXT NOT NULL DEFAULT CURRENT_DATE CHECK (date(date_creation) IS NOT NULL AND date(date_creation) = date_creation), -- Date d\'inscription
telephone TEXT,
email TEXT
email TEXT,
nom_contact TEXT,
note TEXT
);
@ -42,6 +47,26 @@ INSERT OR IGNORE INTO plugin_facturation_paiement (code, nom) VALUES ('ES', 'Esp
INSERT OR IGNORE INTO plugin_facturation_paiement (code, nom) VALUES ('PR', 'Prélèvement');
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
CREATE TABLE IF NOT EXISTS plugin_facturation_txt_cerfa
-- Textes explicatifs associés aux CERFA
(
id PRIMARY KEY,
menu TEXT NOT NULL UNIQUE,
texte TEXT NOT NULL
);
----
-- Data dump for plugin_facturation_txt_cerfa, a total of 4 rows
----
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") VALUES ('1','HelloAsso','Don via HelloAsso');
INSERT OR IGNORE INTO "plugin_facturation_txt_cerfa" ("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") VALUES ('3','Don en nature','Don en nature');
-- CREATE TABLE IF NOT EXISTS plugin_facturation_produits (
-- id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,

View File

@ -1,4 +1,5 @@
DROP TABLE `plugin_facturation_factures`;
DROP TABLE `plugin_facturation_clients`;
DROP TABLE `plugin_facturation_paiement`;
DROP TABLE `plugin_facturation_txt_cerfa`;
-- DROP TABLE `plugin_facturation_produits`;

View File

@ -1,8 +0,0 @@
nom="Facturation"
description="Permet d'éditer des factures, devis et reçus à ses membres ainsi qu'à une base de clients supplémentaire."
auteur="zou"
url="https://gitlab.com/ramoloss/garradin-plugin-facturation/"
version="0.6.1"
menu=1
config=1
min_version="1.1.0"

View File

@ -1,18 +1,8 @@
<?php
namespace Garradin;
use Garradin\Entities\Files\File;
namespace Paheko;
use Paheko\Entities\Files\File;
use Paheko\Plugin\Facturation\Facture;
$db = DB::getInstance();
$db->import(dirname(__FILE__) . "/data/schema.sql");
$plugin->setConfig('footer', "[EXEMPLE]\n".
"Association exonérée des impôts commerciaux\n".
"En cas de retard de paiement, indemnité forfaitaire légale pour frais de recouvrement : 40,00 €\n".
"[Coordonnées bancaires]\n".
"Association enregistrée en préfecture de XXX au numéro YYY"
);
$plugin->setConfig('validate_cp', true);
$path = __DIR__.'/data/default_sign.png';
$png = (new File)->createAndStore('skel/plugin/facturation','sign.png', $path, null);

View File

@ -1,11 +1,12 @@
<?php
namespace Garradin\Plugin\Facturation;
namespace Paheko\Plugin\Facturation;
use Garradin\DB;
use Garradin\Plugin;
use Garradin\UserException;
use Garradin\Utils;
use Paheko\DB;
use Paheko\DynamicList;
use Paheko\Entities\Plugin;
use Paheko\UserException;
use Paheko\Utils;
class Client
{
@ -14,8 +15,11 @@ class Client
'adresse',
'code_postal',
'ville',
'siret',
'telephone',
'email'
'email',
'nom_contact',
'note'
];
private $config = [
@ -38,7 +42,7 @@ class Client
{
$data[$key] = trim($data[$key]);
if($data[$key] == '' && ($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é.');
}
@ -54,6 +58,10 @@ class Client
throw new UserException('Le code postal est erroné.');
}
}
elseif ($key == "siret")
{
$data[$key] = str_replace(' ', '', $data[$key]);
}
elseif ($key == "telephone")
{
$data[$key] = Utils::normalizePhoneNumber($data[$key]);
@ -83,7 +91,7 @@ class Client
if($this->config['unique_client_name'] && isset($data['nom']) && $db->test('plugin_facturation_clients', 'nom = ? COLLATE NOCASE', $data['nom']))
{
throw new UserException('La valeur du champ nom est déjà utilisée, hors ce champ doit être unique à chaque client.');
throw new UserException('La valeur du champ nom est déjà utilisée, or ce champ doit être unique à chaque client.');
}
$db->insert('plugin_facturation_clients', $data);
@ -103,6 +111,53 @@ class Client
return DB::getInstance()->get('SELECT *, strftime(\'%s\', date_creation) AS date_creation FROM plugin_facturation_clients');
}
public function list(): DynamicList
{
$columns = [
'id' => [
'label' => 'Numéro',
],
'nom' => [
'label' => 'Nom',
],
'adresse' => [
'label' => 'Adresse',
],
'code_postal' => [
'label' => 'Code postal',
],
'ville' => [
'label' => 'Ville',
],
'siret' => [
'label' => 'SIRET',
],
'telephone' => [
'label' => 'Téléphone',
],
'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)',
],
];
$tables = 'plugin_facturation_clients AS c';
$list = new DynamicList($columns, $tables);
$list->orderBy('id', false);
$list->setPageSize(1000);
return $list;
}
public function edit($id, $data = [])
{
$db = DB::getInstance();
@ -111,21 +166,21 @@ class Client
if($this->config['unique_client_name'] && isset($data['nom']) && $db->test('plugin_facturation_clients', 'nom = ? COLLATE NOCASE AND id != ?', $data['nom'], (int)$id))
{
throw new UserException('La valeur du champ nom est déjà utilisée, hors ce champ doit être unique à chaque client.');
throw new UserException('La valeur du champ nom est déjà utilisée, or ce champ doit être unique à chaque client.');
}
return $db->update('plugin_facturation_clients', $data, $db->where('id', (int)$id));
}
public function isDeletable($id)
public function isDeletable($id): bool
{
$f = new Facture;
return $f->hasDocs(0, $id);
return !$f->hasDocs(0, $id);
}
public function delete($id)
{
if($this->isDeletable($id))
if(!$this->isDeletable($id))
{
return false;
}

View File

@ -1,14 +1,24 @@
<?php
namespace Garradin\Plugin\Facturation;
namespace Paheko\Plugin\Facturation;
use DateTime;
use Garradin\DB;
use Garradin\UserException;
use Garradin\Services\Services_User;
use Paheko\Config;
use Paheko\DB;
use Paheko\DynamicList;
use Paheko\UserException;
use Paheko\Utils;
use Paheko\Services\Services_User;
class Facture
{
const TYPES_NAMES = [
DEVIS => 'Devis',
FACT => 'Facture',
CERFA => 'Reçu fiscal',
COTIS => 'Reçu de cotisation',
];
private $keys = [
'type_facture', // 0 : devis, 1 : facture, 2 : reçu cerfa, 3 : reçu cotis
'numero',
@ -20,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()
{
@ -62,10 +65,10 @@ class Facture
throw new UserException("Clé inattendue : $k.");
}
if(!is_array($data)){
if(!is_array($data) && null !== $data){
$datas[$k] = trim($data);
}
if ($datas[$k] === '')
if ($datas[$k] === '' && ! in_array($k, ['numero', 'nom_contact', 'numero_commande', 'reference_acheteur']))
{
throw new UserException("La valeur de $k est vide");
}
@ -73,124 +76,204 @@ class Facture
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;
}
}
}
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']))
{
throw new UserException('Un document avec ce numéro existe déjà, hors le numéro doit être unique.');
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('Le numéro de document doit être unique, or il existe déjà un document avec le numéro ' . $data['numero']);
}
$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');
// Garantir l'unicité du numéro
$db = DB::getInstance();
$sql = sprintf('SELECT numero FROM plugin_facturation_factures');
$numeros = array_column($db->get($sql), 'numero');
//sélectionner les numéros qui correspondent au pattern
$selpattern = preg_replace('/%(\d+)?\{(ynumber|id)\}/', '', $pattern);
$data = compact('type', 't', 'year', 'y');
$prefixe = preg_replace_callback('/%(\d+)?\{([a-z]+)\}/', function ($match) use ($data) {
$v = (string) $data[$match[2]];
$type = ctype_digit($v) ? 'd' : 's';
return sprintf('%' . $match[1] . $type, $v);
}, $selpattern);
$modele = '/^' . $prefixe . '\d+$/';
$numeros_filtres = array_filter($numeros, function($elem) use ($modele) {
return preg_match($modele, $elem);
}, 0);
// extraire le numéro d'ordre
$rangs = array_map(function($elem) use($prefixe) {
return (int) substr($elem, strlen($prefixe));
}, array_values($numeros_filtres));
sort($rangs);
if (empty($rangs)) {
$ynumber = 1;
} else {
$ynumber = end($rangs) + 1;
}
// fabriquer le numéro selon le pattern
$data = compact('type', 't', 'year', 'y', 'ynumber', 'id');
return preg_replace_callback('/%(\d+)?\{([a-z]+)\}/', function ($match) use ($data) {
$v = (string) $data[$match[2]];
$type = ctype_digit($v) ? 'd' : 's';
return sprintf('%' . $match[1] . $type, $v);
}, $pattern);
}
public function get($id)
@ -200,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.");
}
@ -235,6 +318,119 @@ class Facture
return $r;
}
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
// (absence de label)
'id' => [
'select' => 'f.id',
],
'type_facture' => [
],
'receveur_membre' => [
],
'receveur_id' => [
],
// Créer une colonne virtuelle
'type' => [
'label' => 'Type',
'select' => null,
],
'numero' => [
'label' => 'Numéro',
'select' => 'f.numero',
],
'receveur' => [
'label' => 'Receveur',
// 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' => 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' => sprintf('CASE WHEN receveur_membre THEN %s ELSE c.ville END', $ville),
],
'date_emission' => [
'label' => 'Émission',
'order' => 'date_emission %s, id %1$s',
],
'date_echeance' => [
'label' => 'Échéance',
],
'reglee' => [
'label' => 'Réglée',
],
'archivee' => [
'label' => 'Archivée',
],
'moyen_paiement' => [
'label' => 'Moyen de paiement',
'select' => 'mp.nom',
],
'contenu' => [
'label' => 'Contenu',
],
'total' => [
'label' => 'Total',
],
];
$tables = 'plugin_facturation_factures AS f
INNER JOIN plugin_facturation_paiement AS mp ON mp.code = f.moyen_paiement
LEFT JOIN users AS u ON f.receveur_membre = 1 AND u.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->orderBy('date_emission', true);
$list->setCount('COUNT(f.id)');
$currency = Config::getInstance()->monnaie;
$list->setModifier(function ($row) use ($currency) {
// Remplir la colonne virtuelle
$row->type = self::TYPES_NAMES[$row->type_facture] ?? null;
$row->reglee = $row->reglee ? 'Réglée' : 'Non';
$row->archivee = $row->archivee ? 'Archivée' : 'Non';
// Remplir le contenu
$content = json_decode((string)$row->contenu);
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')
);
}
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;
}
public function edit($id, $data = [])
{
$db = DB::getInstance();
@ -243,7 +439,7 @@ class Facture
if(isset($data['numero']) && $db->test('plugin_facturation_factures', 'numero = ? COLLATE NOCASE AND id != ?', $data['numero'], (int)$id))
{
throw new UserException('Un document avec ce numéro existe déjà, hors le numéro doit être unique.');
throw new UserException('Un document avec ce numéro existe déjà, or le numéro doit être unique.');
}
return $db->update('plugin_facturation_factures', $data, $db->where('id', (int)$id));
}
@ -261,7 +457,7 @@ class Facture
}
else // Si c'est un membre de l'asso
{
throw new UserException("Woopsie, g pô encore implémenté l'usage des membres de l'asso comme clients");
throw new UserException("Woopsie, g pô encore implémenté l'usage des users de l'asso comme clients");
}
$r = (array)DB::getInstance()->get('SELECT *, strftime(\'%s\', date_emission) AS date_emission,
@ -293,10 +489,10 @@ class Facture
}
else // Si c'est un membre de l'asso
{
throw new UserException("Woopsie, g pô encore implémenté l'usage des membres de l'asso comme clients");
throw new UserException("Woopsie, g pô encore implémenté l'usage des users de l'asso comme clients");
}
return DB::getInstance()->test('plugin_facturation_factures', 'receveur_membre = '. $base .' AND receveur_id = '. $id);
return DB::getInstance()->test('plugin_facturation_factures', 'receveur_membre = ? AND receveur_id = ?', $base, $id);
}
// ** Pour type reçu **
@ -317,36 +513,46 @@ 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;';
return DB::getInstance()->get($sql, $user_id);
}
public function listMoyensPaiement($assoc = false)
{
$db = DB::getInstance();
public function listMoyensPaiement($assoc = false)
{
$db = DB::getInstance();
$query = 'SELECT code, nom FROM plugin_facturation_paiement ORDER BY nom COLLATE NOCASE;';
$query = 'SELECT code, nom FROM plugin_facturation_paiement ORDER BY nom COLLATE NOCASE;';
if ($assoc) {
return $db->getAssoc($query);
}
else {
return $db->getGrouped($query);
}
if ($assoc) {
return $db->getAssoc($query);
}
else {
return $db->getGrouped($query);
}
}
public function getMoyenPaiement($code)
{
$db = DB::getInstance();
return $db->firstColumn('SELECT nom FROM plugin_facturation_paiement WHERE code = ?;', $code);
}
/* 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);
}
}

9
plugin.ini Normal file
View File

@ -0,0 +1,9 @@
name="Facturation"
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.15"
menu=true
restrict_section="accounting"
restrict_level="read"
min_version="1.3.0"

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

250
templates/_form.tpl Normal file
View File

@ -0,0 +1,250 @@
{form_errors}
<form method="post" action="{$self_url}">
<fieldset>
<legend>Type de document</legend>
<dl>
{foreach from=$types_details item="type"}
<dd class="radio-btn">
{input type="radio" name="type" value=$type.id source=$radio label=null}
<label for="f_type_{$type.id}">
<div>
<h3>{$type.label}</h3>
{if !empty($type.help)}
<p>{$type.help}</p>
{/if}
</div>
</label>
</dd>
{/foreach}
</dl>
</fieldset>
<fieldset>
<legend data-types="t0">Créer un devis</legend>
<legend data-types="t1">Créer une facture</legend>
<dl>
{input type="text" name="numero_facture" maxlength=18 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 puissent 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}
<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}
<dd class="help" data-types="t2">
<p>Date d'établissement du document</p>
</dd>
</div>
<dt><label>Statut</label></dt>
{input type="checkbox" name="reglee" value="1" label="Réglée" source=$doc data-types="t1"}
<div data-types="t0 t1 t2">
{input type="checkbox" name="archivee" value="1" label="Archivée" source=$doc disabled="disabled"}
</div>
</dl>
</fieldset>
<fieldset data-types="t0 t1 t2">
<legend>Destinataire</legend>
<dl>
<dt><label>Document adressé à :</label></dt>
{if !empty($clients)}
<dd>
{input type="radio" name="base_receveur" value="membre" label="Un·e membre" default=1 source=$doc}
{input type="radio" name="base_receveur" value="client" label="Un·e client·e" source=$doc}
</dd>
{/if}
</dl>
<dl class="type_membre">
{input type="select" name="membre" label="Membre" options=$users required=1 source=$doc default_empty="— Choisir un membre —"}
</dl>
{if !empty($clients)}
<dl class="type_client">
{input type="select" name="client" label="Client" options=$clients required=1 class="type_client" source=$doc default_empty="— Choisir un client —"}
</dl>
{else}
<input type="hidden" name="base_receveur" value="membre" />
{/if}
</fieldset>
<fieldset data-types="t0 t1">
<legend>Autres informations</legend>
{input type="text" name="nom_contact" label="Nom du contact" source=$doc}
<div class="hidden">
{input type="select" name="contact_list" options=$contacts}
</div>
<div data-types="t1">
{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}
</div>
</fieldset>
<fieldset data-types="t0 t1">
<legend>Contenu</legend>
<dl>
{input type="select" name="moyen_paiement" required=1 label="Moyen de paiement" source=$doc options=$moyens_paiement default=$doc.moyen_paiement}
<dt><label for="f_contenu">Contenu du document</label><dt>
<dd>
<table class="list" style="max-width: 800px;">
<colgroup>
<col width="65%">
<col width="33%">
<col width="2%">
</colgroup>
<thead>
<tr>
<td>Désignation</td>
<td>Prix</td>
<td></td>
</tr>
</thead>
<tbody id="Lines">
{if count($designations) > 0}
<tr id="Line1" class="hidden">
<td><textarea name="designation_tpl[]" style="width:98%;"></textarea></td>
{money_fac name="prix_tpl[]"}
<td class="fact_rm_line">{button label="Enlever" title="Enlever la ligne" shape="minus" min="2" name="remove_line"}</td>
</tr>
{foreach from=$designations item=designation key=key}
<tr>
<td><textarea name="designation[]" style="width:98%;">{$designation}</textarea></td>
{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>
</tr>
{/foreach}
{else}
<tr id="Line1" class="hidden">
<td><textarea name="designation_tpl[]" style="width:98%;"></textarea></td>
{money_fac name="prix_tpl[]"}
<td class="fact_rm_line">{button label="Enlever" title="Enlever la ligne" shape="minus" min="2" name="remove_line"}</td>
</tr>
{/if}
</tbody>
<tfoot>
<tr>
<td style="text-align: right;">Total :</td>
<td><span id="total">0.00</span> €</td>
<td>{button label="Ajouter" title="Ajouter une ligne" id="ajouter_ligne" shape="plus"}</td>
</tr>
</tfoot>
</table>
</dd>
</dl>
</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>
</fieldset>
<p class="submit" data-types="t0 t1 t2">
{csrf_field key=$csrf_key}
{button type="submit" name="save" label="Enregistrer" shape="right" class="main"}
</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>
{include file="%s/templates/_js.tpl"|args:$plugin_root}

View File

@ -4,10 +4,14 @@
function updateSum(){
var total = 0;
e = document.querySelectorAll('input[name="prix[]"]');
e.forEach( function sum(item, index){
total = total + Number(item.value);
e.forEach((item) => {
if (!item.value) {
return;
}
total += g.getMoneyAsInt(item.value);
});
document.getElementById('total').innerHTML = total.toFixed(2);
document.getElementById('total').innerHTML = g.formatMoney(total);
}
(function () {
@ -15,12 +19,13 @@
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();
};
newdiv.getElementsByTagName('input')[0].onkeyup = updateSum;
document.getElementById('Lines').appendChild(newdiv);
}
plus();
@ -28,22 +33,38 @@
$('#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();
};
}
function changeTypeSaisie(type)
{
g.toggle(['.type_client', '.type_membre'], false);
g.toggle('.type_' + type, true);
if (type) {
g.toggle('.type_' + type, true);
}
}
changeTypeSaisie(document.forms[0].base_receveur.value);
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);
var inputs = $('input[name="base_receveur"]');
@ -54,6 +75,11 @@
};
}
const selclient = document.querySelector('#f_client');
selclient.addEventListener("change", () => {
modifierContact(selclient, '#f_contact_list', '#f_nom_contact');
});
} ());
@ -83,7 +109,6 @@
g.toggle(e, true);
g.toggle('p.submit', true);
});
console.log(e.value);
selectType(e.value);
};
});
@ -91,4 +116,4 @@
hideAllTypes();
{/literal}
selectType({$radio.type});
</script>
</script>

View File

@ -4,7 +4,7 @@
<td></td>
<td class="tabs" colspan={$colspan}>
<em>PAS FONCTIONNEL - Pour les client·es coché·es :</em>
{csrf_field key="membres_action"}
{csrf_field key="users_action"}
<select name="action">
<option value="">— Choisir une action à effectuer —</option>
<option value="csv">Exporter en tableau CSV</option>

View File

@ -3,14 +3,14 @@
</style>
<nav class="tabs">
<ul>
<li{if $current == 'index'} class="current"{/if}><a href="{plugin_url file=""}">Liste documents</a></li>
<li{if $current == 'index'} class="current"{/if}><a href="{$plugin_admin_url}">Liste documents</a></li>
{if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE)}
<li{if $current == 'facture'} class="current"{/if}><a href="{plugin_url file="facture_ajouter.php"}">Nouveau document</a></li>
<li{if $current == 'facture'} class="current"{/if}><a href="{$plugin_admin_url}facture_ajouter.php">Nouveau document</a></li>
{/if}
<li{if $current == 'clients'} class="current"{/if}><a href="{plugin_url file="clients.php"}">Liste clients</a></li>
<li{if $current == 'clients'} class="current"{/if}><a href="{$plugin_admin_url}clients.php">Liste clients</a></li>
{if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN)}
<li{if $current == 'config'} class="current"{/if}><a href="{plugin_url file="config.php"}">Configuration</a></li>
<li{if $current == 'config'} class="current"{/if}><a href="{$plugin_admin_url}config.php">Configuration</a></li>
{/if}
<li{if $current == 'aide'} class="current"{/if}><a href="{plugin_url file="aide.php"}">Aide</a></li>
<li{if $current == 'aide'} class="current"{/if}><a href="{$plugin_admin_url}aide.php">Aide</a></li>
</ul>
</nav>

View File

@ -1,12 +1,12 @@
<nav class="tabs">
<ul>
<li{if $current == 'clients'} class="current"{/if}><a href="{plugin_url file="clients.php"}">Liste clients</a></li>
<li{if $current == 'client'} class="current"{/if}><a href="{plugin_url file="client.php"}?id={$client.id}">{$client.nom}</a></li>
<li{if $current == 'clients'} class="current"{/if}><a href="{$plugin_admin_url}clients.php">Liste clients</a></li>
<li{if $current == 'client'} class="current"{/if}><a href="{$plugin_admin_url}client.php?id={$client.id}">{$client.nom}</a></li>
{if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE)}
<li{if $current == 'client_modifier'} class="current"{/if}>
<a href="{plugin_url file="client_modifier.php"}?id={$client.id}">Modifier</a></li>{/if}
<a href="{$plugin_admin_url}client_modifier.php?id={$client.id}">Modifier</a></li>{/if}
{if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN)}
<li{if $current == 'client_supprimer'} class="current"{/if}>
<a href="{plugin_url file="client_supprimer.php"}?id={$client.id}">Supprimer</a></li>{/if}
<a href="{$plugin_admin_url}client_supprimer.php?id={$client.id}">Supprimer</a></li>{/if}
</ul>
</nav>

View File

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

View File

@ -1,23 +1,30 @@
{include file="admin/_head.tpl" title="Aide — %s"|args:$plugin.nom 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"}
<fieldset>
<legend><h2>Quelques remarques et conseils sur l'utilisation du plugin Facturation</h2></legend>
<h2>Quelques remarques et conseils sur l'utilisation du plugin Facturation</h2>
<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>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 Garradin qui fait foi plutôt que celle dans le plugin (qui sert en revanche pour les reçus).</p>
<p>Pensez à mettre une image en signature aussi, il me semble que les reçus fiscales peuvent méfonctionner sans ça :o (j'ai copié cette partie d'un ancien plugin, alors je connais pas bien).</p>
<br>
<p>- Pour créer un reçu sur une cotisation, il faut pour le moment que cette cotisation soit attachée à la compta.</p>
<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>
<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>
<br>
<p>Hésitez pas à faire des retours, proposer meilleures explications, ou quoi, vous pouvez venir en causer soit <a href="https://gitlab.com/ramoloss/garradin-plugin-facturation">sur mon gitlab</a>, soit sur l'adresse d'entraide de garradin. 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>
<div class="aide">
<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>
<fieldset>
<legend>Informations à configurer</legend>
<dl>
<dt><label>Adresse</label></dt>
<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>
<dt><label>Nom et prénom des membres</label></dt>
<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>
<dt><label>Champs à faire figurer sur la facture</label></dt>
<dd>Permet de choisir les champs pour l'adresse, le code postal et la ville parmi les {link href="!config/fields" label="champs de la fiche membre"}</dd>
</dl>
</fieldset>
<br>
<p><i>Remarque : la gestion de la signature pour les reçus fiscaux a été désactivée temporairement, dû au changements de Garradin 1.1 sur les fichiers</i></p>
<p>Pensez à mettre une image en signature (cela sert pour les reçus fiscaux), cela se passe dans la {link href="!config/custom.php" label="configuration de Paheko, onglet personnalisation"}. Il est préférable d'avoir un fond transparent.
</p>
<ul>
<li>Pour créer un reçu sur une cotisation, il vaut mieux utiliser le module {link href="!config/ext" label="Reçu de paiement"} intégré à Paheko.</li>
<li>Pour créer un reçu fiscal, il vaut mieux utiliser le module {link href="!config/ext" label="Reçus fiscaux"} intégré à Paheko.</li>
</ul>
<p>N'hésitez pas à faire des retours, proposer de meilleures explications dans le forum d'entraide de Paheko. Si vous êtes un peu dev, le code est un peu cracra mais j'espère que ça ne vous repoussera pas trop à le bidouiller :)</p>
</fieldset>
</div>
{include file="admin/_foot.tpl"}
{include file="_foot.tpl"}

View File

@ -1,22 +1,34 @@
{include file="admin/_head.tpl" title="Client — %s"|args:$plugin.nom current="plugin_%s"|args:$plugin.id js=0}
{include file="_head.tpl" title="Client — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.id js=0}
{include file="%s/templates/_menu_client.tpl"|args:$plugin_root current="client"}
<dl class="describe" style="display: inline-block;">
<dl class="describe">
<dt>Numéro de client</dt>
<dd><p>{$client.id}</p></dd>
<dd>{$client.id}</dd>
<dt>Nom</dt>
<dd><p>{$client.nom|escape|rtrim|nl2br}</p>
<dd>{$client.nom|escape|rtrim|nl2br}
<dt>Adresse</dt>
<dd><p>{$client.adresse|escape|rtrim|nl2br}</p></dd>
<dd>{$client.adresse|escape|rtrim|nl2br}</dd>
<dt>Ville</dt>
<dd><p>{$client.ville|escape|rtrim|nl2br}</p></dd>
<dd>{$client.ville|escape|rtrim|nl2br}</dd>
<dt>Code postal</dt>
<dd><p>{$client.code_postal|escape|rtrim|nl2br}</p></dd>
<dd>{$client.code_postal|escape|rtrim|nl2br}</dd>
<dt>SIREN/SIRET</dt>
<dd>
{if empty($client->siret)}
<em>(Non renseigné)</em>
{else}
<?php
$siret = implode(' ', str_split($client->siret, 3));
?>
{$siret|escape|trim}
{/if}
</dd>
<dt>Adresse électronique</dt>
<dd>
@ -32,7 +44,25 @@
{if empty($client.telephone)}
<em>(Non renseigné)</em>
{else}
<a href="tel:{$client.telephone}">{$client.telephone|format_tel}</a>
<a href="tel:{$client.telephone}">{$client.telephone|format_phone_number}</a>
{/if}
</dd>
<dt>Nom du contact</dt>
<dd>
{if empty($client.nom_contact)}
<em>(Non renseigné)</em>
{else}
{$client.nom_contact}
{/if}
</dd>
<dt>Note</dt>
<dd>
{if empty($client.note)}
<em>(Non renseigné)</em>
{else}
{$client.note}
{/if}
</dd>
@ -60,7 +90,7 @@
{foreach from=$docs item=facture}
<tr>
<td>{$f_obj->types[$facture.type_facture]['label'] }</td>
<td><a href="{plugin_url file="facture.php"}?id={$facture.id}">{$facture.numero}</a></td>
<td><a href="{$plugin_admin_url}facture.php?id={$facture.id}">{$facture.numero}</a></td>
<td>{$facture.date_emission|date:'d/m/Y'}</td>
<td>{$facture.date_echeance|date:'d/m/Y'}</td>
<td>{$facture.reglee}</td>
@ -68,10 +98,10 @@
<td>{$facture.moyen_paiement}</td>
<td>
{foreach from=$facture.contenu item=contenu}
<p>{$contenu.designation} : {$contenu.prix|escape|money}&nbsp;{$config.monnaie}</p>
{$contenu.designation} : {$contenu.prix|escape|money_currency}
{/foreach}
</td>
<td>{$facture.total|escape|money}&nbsp;{$config.monnaie}</td>
<td>{$facture.total|escape|money_currency}</td>
</tr>
{/foreach}
</tbody>
@ -79,7 +109,7 @@
</table>
</div>
{else}
<h4>Cet utilisateur n'a pas de document associé.</h4>
<p class="alert block">Ce client n'a pas de document associé.</p>
{/if}
{include file="admin/_foot.tpl"}
{include file="_foot.tpl"}

View File

@ -1,4 +1,4 @@
{include file="admin/_head.tpl" title="Modifier un client — %s"|args:$plugin.nom 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.id js=0}
{include file="%s/templates/_menu_client.tpl"|args:$plugin_root current="client_modifier"}
{form_errors}
@ -7,26 +7,23 @@
<fieldset>
<legend>Modifier un client</legend>
<dl>
<dt><label for="f_nom">Nom</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
<dd><input type="text" name="nom" id="f_nom" value="{$client.nom}"/></dd>
<dt><label for="f_adresse">Adresse</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
<dd><input type="text" name="adresse" id="f_adresse" value="{$client.adresse}"/></dd>
<dt><label for="f_cp">Code postal</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
<dd><input type="text" name="code_postal" id="f_cp" value="{$client.code_postal}"/></dd>
<dt><label for="f_ville">Ville</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
<dd><input type="text" name="ville" id="f_ville" value="{$client.ville}"/></dd>
<dt><label for="f_tel">Téléphone</label></dt>
<dd><input type="text" name="telephone" id="f_tel" value="{$client.telephone}"/></dd>
<dt><label for="f_email">Adresse mail</label></dt>
<dd><input type="text" name="email" id="f_email" value="{$client.email}"/></dd>
{input type="text" name="nom" label="Nom" required=true source=$client}
{input type="text" name="adresse" label="Adresse" required=true source=$client}
{input type="text" name="code_postal" label="Code postal" required=true source=$client}
{input type="text" name="ville" label="Ville" required=true source=$client}
{input type="text" name="siret" label="SIREN/SIRET" source=$client}
{input type="tel" name="telephone" label="Téléphone" source=$client}
{input type="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}
</dl>
</fieldset>
<p class="submit">
{csrf_field key="edit_client"}
<input type="submit" name="save" value="Enregistrer &rarr;" />
{button type="submit" name="save" label="Enregistrer" shape="right" class="main"}
</p>
</form>
{include file="admin/_foot.tpl"}
{include file="_foot.tpl"}

View File

@ -1,30 +1,15 @@
{include file="admin/_head.tpl" title="Supprimer un client — %s"|args:$plugin.nom 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.id js=0}
{include file="%s/templates/_menu_client.tpl"|args:$plugin_root current="client_supprimer"}
{form_errors}
{if !$deletable}
<form method="post" action="{$self_url}">
<fieldset>
<legend>Supprimer ce client ?</legend>
<h3 class="warning">
Êtes-vous sûr de vouloir supprimer le membre «&nbsp;{$client.nom}&nbsp;» ?
</h3>
<p class="alert">
<strong>Attention</strong> : cette action est irréversible.
</p>
</fieldset>
<p class="submit">
{csrf_field key="delete_client_"|cat:$client.id}
<input type="submit" name="delete" value="Supprimer &rarr;" />
</p>
</form>
<p class="error block">Ce/cette client·e ne peut pas être supprimé·e car des documents lui y sont liés.</p>
{else}
<p>Ce/cette client·e ne peut pas être supprimé·e car des documents lui y sont liés.</p>
{include file="common/delete_form.tpl"
legend="Supprimer ce client ?"
warning="Êtes-vous sûr de vouloir supprimer le client « %s » ?"|args:$client.nom
alert="Attention, cette action est irréversible."}
{/if}
{include file="admin/_foot.tpl"}
{include file="_foot.tpl"}

View File

@ -1,79 +1,76 @@
{include file="admin/_head.tpl" title="Clients — %s"|args:$plugin.nom current="plugin_%s"|args:$plugin.id js=1}
{include file="_head.tpl" title="Clients — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.id js=1}
{include file="%s/templates/_menu.tpl"|args:$plugin_root current="clients"}
{form_errors}
{if $list->count()}
{include file="common/dynamic_list_head.tpl"}
<form method="post" action="{$self_url}" class="memberList">
{if !empty($clients)}
<table class="list">
<thead class="userOrder">
{foreach from=$list->iterate() item="row"}
<tr>
{if $session->canAccess($session::SECTION_USERS, $session::ACCESS_ADMIN)}<td class="check"><input type="checkbox" title="Tout cocher / décocher" /></td>{/if}
{foreach from=$champs key="c" item="champ"}
<td>{if $c == "numero"}#{else}{$champ.title}{/if} </td>
<td>{$row.id}</td>
<th><a href="client.php?id={$row.id}">{$row.nom}</a></th>
{foreach from=$row item="value" key="key"}
{if $key == 'id' || $key == 'nom'}
<?php continue; ?>
{/if}
{if $key == 'siret'}
<?php
if (null === $value) { $value = ""; }
$value = implode(' ', str_split($value, 3));
?>
{/if}
<td>{$value}</td>
{/foreach}
<td></td>
<td class="actions">
{if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN)}
{linkbutton shape="delete" href="client_supprimer.php?id=%d"|args:$row.id label="Supprimer"}
{/if}
{if $session->canAccess($session::SECTION_USERS, $session::ACCESS_ADMIN)}
{linkbutton shape="edit" href="client_modifier.php?id=%d"|args:$row.id label="Modifier"}
{/if}
{linkbutton shape="user" href="client.php?id=%d"|args:$row.id label="Fiche client"}
</td>
</tr>
</thead>
<tbody>
{foreach from=$clients item="membre"}
<tr>
{if $session->canAccess($session::SECTION_USERS, $session::ACCESS_ADMIN)}<td class="check">
{input type="checkbox" name="selected" value=$membre.id default=0}
</td>
{/if}
{foreach from=$champs key="c" item="cfg"}
<td>
{if $c == 'nom'}<a href="{plugin_url file="client.php"}?id={$membre.id}">{/if}
{$membre->$c}
{if $c == 'nom'}</a>{/if}
</td>
{/foreach}
<td class="tabs">
<a class="icn" href="{plugin_url file="client.php"}?id={$membre.id}" title="Fiche membre">👤</a>
{if $session->canAccess($session::SECTION_USERS, $session::ACCESS_WRITE)}<a class="icn" href="{plugin_url file="client_modifier.php"}?id={$membre.id}" title="Modifier la fiche membre">✎</a>{/if}
</td>
</tr>
{/foreach}
{/foreach}
</tbody>
{if $session->canAccess($session::SECTION_USERS, $session::ACCESS_ADMIN)}
{include file="%s/templates/_list_actions.tpl"|args:$plugin_root colspan=count((array)$champs)}
{/if}
</table>
<p class="help">
Export de la liste&nbsp;:
{linkbutton href="?export=csv" label="Export CSV" shape="download"}
{linkbutton href="?export=ods" label="Export tableur" shape="download"}
</p>
{else}
<p class="alert">
<p class="alert block">
Aucun client trouvé.
</p>
{/if}
</form>
{form_errors}
<form method="post" action="{$self_url}">
<fieldset>
<legend>Ajouter un client</legend>
<dl>
<dt><label for="f_nom">Nom</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
<dd><input type="nom" name="nom" id="f_nom" value="{form_field name="nom"}"/></dd>
<dt><label for="f_adresse">Adresse</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
<dd><input type="text" name="adresse" id="f_adresse" value="{form_field name="adresse"}"/></dd>
<dt><label for="f_cp">Code postal</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
<dd><input type="text" name="code_postal" id="f_cp" value="{form_field name="code_postal"}"/></dd>
<dt><label for="f_ville">Ville</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
<dd><input type="text" name="ville" id="f_ville" value="{form_field name="ville"}"/></dd>
<dt><label for="f_tel">Téléphone</label></dt>
<dd><input type="text" name="telephone" id="f_tel" value="{form_field name="telephone"}"/></dd>
<dt><label for="f_email">Adresse mail</label></dt>
<dd><input type="text" name="email" id="f_email" value="{form_field name="email"}"/></dd>
{input type="text" name="nom" label="Nom" required=true}
{input type="text" name="adresse" label="Adresse" required=true}
{input type="text" name="code_postal" label="Code postal" required=true}
{input type="text" name="ville" label="Ville" required=true}
{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"}
</dl>
</fieldset>
<p class="submit">
{csrf_field key="add_client"}
<input type="submit" name="add" value="Enregistrer &rarr;" />
{button type="submit" name="add" label="Enregistrer" shape="right" class="main"}
</p>
</form>
{include file="admin/_foot.tpl"}
{include file="_foot.tpl"}

View File

@ -1,4 +1,4 @@
{include file="admin/_head.tpl" title="Configuration — %s"|args:$plugin.nom current="plugin_%s"|args:$plugin.id}
{include file="_head.tpl" title="Configuration — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.id}
{include file="%s/templates/_menu.tpl"|args:$plugin_root current="config"}
{if $ok && !$form->hasErrors()}
@ -13,55 +13,52 @@
<fieldset>
<legend>Informations de l'association</legend>
<dl>
{input type="text" name="rna_asso" label="RNA de l'association" source=$plugin.config}
{input type="text" name="siret_asso" label="SIRET de l'association" source=$plugin.config}
{input type="text" name="rna_asso" label="RNA de l'association" source=$conf}
{input type="text" name="siret_asso" label="SIRET de l'association" source=$conf}
{input type="checkbox" name="ttc" value="1" label="L'association émet des factures TTC (TVA)" source=$conf}
</dl>
<br>
<fieldset>
<legend>Adresse</legend>
<legend>Adresse de l'association</legend>
<dd class="help">
à 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
</dd>
<dl>
{input type="text" name="numero_rue_asso" source=$plugin.config label="Numéro de rue" required=1 maxlength=5}
{input type="text" name="rue_asso" source=$plugin.config label="Nom de rue" required=1}
{input type="text" name="cp_asso" source=$plugin.config label="Code postal" required=1}
{input type="text" name="ville_asso" source=$plugin.config label="Ville" required=1}
{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"}
{input type="text" name="cp_asso" source=$conf label="Code postal"}
{input type="text" name="ville_asso" source=$conf label="Ville"}
</dl>
</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=$plugin.config label="Ligne 1" maxlength=95}
{input type="text" name="objet_1" source=$plugin.config label="Ligne 2" maxlength=95}
{input type="text" name="objet_2" source=$plugin.config 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=$plugin.config label="Article 200"}
{input type="checkbox" name="droit_art238bis" value="1" source=$plugin.config label="Article 238 bis"}
{input type="checkbox" name="droit_art885-0VbisA" value="1" source=$plugin.config label="Article 885-0V bis A"}
</dl>
</fieldset>
</fieldset>
<fieldset>
<legend>Factures</legend>
<legend>Factures et devis</legend>
<dl>
<dt><label for="f_footer">Pied de documents/informations légales</label></dt>
<dd><textarea name="footer" id="f_footer" cols="50" rows="5">{form_field data=$plugin.config name=footer}</textarea></dd>
{input type="checkbox" name="logo" value="1" source=$conf label="Imprimer le logo de l'association"}
{input type="textarea" class="full-width" rows="5" name="footer" source=$conf label="Pied de document — informations légales" required=true}
</dl>
<fieldset>
<legend>
Choisir les champs à faire figurer sur la facture ou le devis pour l'adresse d'un membre
</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>
<legend>Configuration du plugin</legend>
<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="unique_client_name" value="1" source=$plugin.config label="Noms des clients uniques"}
{input type="checkbox" name="validate_cp" value="1" source=$conf label="Vérifier le code postal lors de saisie/modification de client (seulement FR)"}
{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}
<dd class="help">
F = Facture, D = Devis
</dd>
</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>
</fieldset>
@ -72,25 +69,4 @@
</p>
</form>
<form method="post" enctype="multipart/form-data" action="{$self_url|escape}" id="f_upload">
<fieldset>
<legend>Signature du responsable</legend>
<p>
Uploadez votre signature dans les documents, onglet Squelettes du site web, sous plugin/facturation/sign.png
<br>
Il est préférable d'avoir un fond transparent.
</p>
<br>
<p>
La signature actuelle :
<br>
<img src="{$www_url}plugin/facturation/sign.png">
</p>
<p>A cause de l'implémentation de la signature, votre signature est acessible publiquement à l'adresse suivante : {$www_url}plugin/facturation/sign.png !</p>
</fieldset>
</form>
{include file="admin/_foot.tpl"}
{include file="_foot.tpl"}

View File

@ -1,17 +1,20 @@
{include file="admin/_head.tpl" title="Document — %s"|args:$plugin.nom current="plugin_%s"|args:$plugin.id}
{include file="_head.tpl" title="Document — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.id}
{include file="%s/templates/_menu.tpl"|args:$plugin_root current="index"}
{form_errors}
{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="edit" href="%sfacture_modifier.php?id=%d"|args:$plugin_admin_url,$facture.id label="Modifier ce document"}
{linkbutton shape="plus" href="%sfacture_ajouter.php?copy=%d"|args:$plugin_admin_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"}
{linkbutton shape="download" href="%spdf.php?d&id=%d"|args:$plugin_admin_url,$facture.id label="Télécharger ce document"}
{linkbutton shape="delete" href="%sfacture_supprimer.php?id=%d"|args:$plugin_url,$facture.id label="Supprimer ce document"}
{if $session->canAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN)}
{linkbutton shape="delete" href="%sfacture_supprimer.php?id=%d"|args:$plugin_admin_url,$facture.id label="Supprimer ce document"}
{/if}
<div style="margin-top: 1em; width: min-content;">
<embed src="pdf.php?id={$id}" type="application/pdf" width="840px" height="1188px" style="max-width: 900px; border: 1px solid black;">
<embed src="pdf.php?id={$id}" width="840px" height="1188px" style="max-width: 900px; border: 1px solid black;">
</div>
{include file="admin/_foot.tpl"}
{include file="_foot.tpl"}

View File

@ -1,231 +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="_head.tpl" title="Créer un document — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.id js=1}
{include file="%s/templates/_menu.tpl"|args:$plugin_root current="facture"}
{form_errors}
<form method="post" action="{$self_url}">
<fieldset>
<legend>Type d'écriture</legend>
<dl>
{foreach from=$types_details item="type"}
<dd class="radio-btn">
{input type="radio" name="type" value=$type.id source=$radio label=null}
<label for="f_type_{$type.id}">
<div>
<h3>{$type.label}</h3>
{if !empty($type.help)}
<p>{$type.help}</p>
{/if}
</div>
</label>
</dd>
{/foreach}
</dl>
</fieldset>
<fieldset>
<legend data-types="t0">Créer un devis</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>
{input type="text" name="numero_facture" maxlength=12 label="Numéro du document" required=1 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="date" name="date_emission" default=$date label="Date d'émission" required=1 }
<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 }
<dd class="help" data-types="t2">
<p>Date d'établissement du document</p>
</dd>
</div>
<dt><label>Statut</label></dt>
{input type="checkbox" name="reglee" value="" label="Réglée" data-types="t1"}
<div data-types="t0 t1 t2">
{input type="checkbox" name="archivee" value="" label="Archivée" disabled="disabled"}
</div>
</dl>
</fieldset>
<fieldset data-types="t0 t1 t2">
<legend>Client</legend>
<dl>
<dt><label>Document adressée à :</label></dt>
{if !empty($clients)}
<dd>
{input type="radio" name="base_receveur" value="membre" label="Un·e membre" default=1}
{input type="radio" name="base_receveur" value="client" label="Un·e client·e"}
</dd>
{/if}
<div class="type_membre">
{input type="select" name="membre" label="Membre" options=$membres required=1}
</div>
{if !empty($clients)}
<div class="type_client">
{input type="select" name="client" label="Client" options=$clients required=1 class="type_client"}
</div>
{/if}
</dl>
</fieldset>
<fieldset data-types="t0 t1 t2">
<legend>Contenu</legend>
<dl>
<dt><label for="f_moyen_paiement">Moyen de paiement</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
<dd>
<select name="moyen_paiement" id="f_moyen_paiement" required="required">
{foreach from=$moyens_paiement item="moyen"}
<option value="{$moyen.code}"{if $moyen.code == $moyen_paiement} selected="selected"{/if}>{$moyen.nom}</option>
{/foreach}
</select>
</dd>
<dt><label for="f_contenu">Contenu du document</label><dt>
<dd>
<table class="list" style="max-width: 800px;">
<colgroup>
<col width="65%">
<col width="33%">
<col width="2%">
</colgroup>
<thead>
<tr>
<td>Désignation</td>
<td>Prix</td>
<td></td>
</tr>
</thead>
<tbody id="Lines">
{if count($designations) > 0}
<tr id="Line1" class="hidden">
<td><textarea name="designation_tpl[]" style="width:98%;"></textarea></td>
{money_fac name="prix_tpl[]"}
<td class="fact_rm_line">{button label="Enlever" title="Enlever la ligne" shape="minus" min="2" name="remove_line"}</td>
</tr>
{foreach from=$designations item=designation key=key}
<tr>
<td><textarea name="designation[]" style="width:98%;">{$designation}</textarea></td>
{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>
</tr>
{/foreach}
{else}
<tr id="Line1" class="hidden">
<td><textarea name="designation_tpl[]" style="width:98%;"></textarea></td>
{money_fac name="prix_tpl[]"}
<td class="fact_rm_line">{button label="Enlever" title="Enlever la ligne" shape="minus" min="2" name="remove_line"}</td>
</tr>
{/if}
</tbody>
<tfoot>
<tr>
<td style="text-align: right;">Total :</td>
<td><span id="total">0.00</span> €</td>
<td>{button label="Ajouter" title="Ajouter une ligne" id="ajouter_ligne" shape="plus"}</td>
</tr>
</tfoot>
</table>
</dd>
</dl>
</fieldset>
<p class="submit" data-types="t0 t1 t2">
{csrf_field key="ajout_facture"}
{button type="submit" name="add" label="Enregistrer" shape="right" class="main"}
</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=$membres required=1}
</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>
{include file="%s/templates/_form.tpl"|args:$plugin_root}
{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>
{include file="%s/templates/_js.tpl"|args:$plugin_root}
{include file="admin/_foot.tpl"}
{include file="_foot.tpl"}

View File

@ -1,224 +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="_head.tpl" title="Modifier un document — %s"|args:$plugin.name 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/_form.tpl"|args:$plugin_root}
<form method="post" action="{$self_url}">
<fieldset>
<legend>Type d'écriture</legend>
<dl>
{foreach from=$types_details item="type"}
<dd class="radio-btn">
{input type="radio" name="type" value=$type.id source=$radio label=null}
<label for="f_type_{$type.id}">
<div>
<h3>{$type.label}</h3>
{if !empty($type.help)}
<p>{$type.help}</p>
{/if}
</div>
</label>
</dd>
{/foreach}
</dl>
</fieldset>
<fieldset>
<legend data-types="t0">Créer un devis</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>
{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="date" name="date_emission" default=$date label="Date d'émission" required=1 source=$doc}
<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}
<dd class="help" data-types="t2">
<p>Date d'établissement du document</p>
</dd>
</div>
<dt><label>Statut</label></dt>
{input type="checkbox" name="reglee" value="1" label="Réglée" source=$doc data-types="t1"}
<div data-types="t0 t1 t2">
{input type="checkbox" name="archivee" value="1" label="Archivée" source=$doc disabled="disabled"}
</div>
</dl>
</fieldset>
<fieldset data-types="t0 t1 t2">
<legend>Client</legend>
<dl>
<dt><label>Document adressée à :</label></dt>
{if !empty($clients)}
<dd>
{input type="radio" name="base_receveur" value="membre" source=$doc label="Un·e membre"}
{input type="radio" name="base_receveur" value="client" source=$doc label="Un·e client·e"}
</dd>
{/if}
<div class="type_membre">
{input type="select" name="membre" label="Membre" options=$membres required=1 source=$doc}
</div>
{if !empty($clients)}
<div class="type_client">
{input type="select" name="client" label="Client" options=$clients required=1 source=$doc class="type_client"}
</div>
{/if}
</dl>
</fieldset>
<fieldset data-types="t0 t1 t2">
<legend>Contenu</legend>
<dl>
{input type="select" name="moyen_paiement" required=1 label="Moyen de paiement" source=$doc options=$doc.moyens_paiement}
<dt><label for="f_contenu">Contenu du document</label><dt>
<dd>
<table class="list" style="max-width: 800px;">
<colgroup>
<col width="65%">
<col width="33%">
<col width="2%">
</colgroup>
<thead>
<tr>
<td>Désignation</td>
<td>Prix</td>
<td></td>
</tr>
</thead>
<tbody id="Lines">
{if count($designations) > 0}
<tr id="Line1" class="hidden">
<td><textarea name="designation_tpl[]" style="width:98%;"></textarea></td>
{money_fac name="prix_tpl[]"}
<td class="fact_rm_line">{button label="Enlever" title="Enlever la ligne" shape="minus" min="2" name="remove_line"}</td>
</tr>
{foreach from=$designations item=designation key=key}
<tr>
<td><textarea name="designation[]" style="width:98%;">{$designation}</textarea></td>
{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>
</tr>
{/foreach}
{else}
<tr id="Line1" class="hidden">
<td><textarea name="designation_tpl[]" style="width:98%;"></textarea></td>
{money_fac name="prix_tpl[]"}
<td class="fact_rm_line">{button label="Enlever" title="Enlever la ligne" shape="minus" min="2" name="remove_line"}</td>
</tr>
{/if}
</tbody>
<tfoot>
<tr>
<td style="text-align: right;">Total :</td>
<td><span id="total">0.00</span> €</td>
<td>{button label="Ajouter" title="Ajouter une ligne" id="ajouter_ligne" shape="plus"}</td>
</tr>
</tfoot>
</table>
</dd>
</dl>
</fieldset>
<p class="submit" data-types="t0 t1 t2">
{csrf_field key="modifier_facture"}
{button type="submit" name="save" label="Enregistrer" shape="right" class="main"}
</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=$membres required=1 source=$doc}
</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>
{include file="%s/templates/_js.tpl"|args:$plugin_root}
{include file="admin/_foot.tpl"}
{include file="_foot.tpl"}

View File

@ -1,4 +1,4 @@
{include file="admin/_head.tpl" title="Supprimer un document — %s"|args:$plugin.nom 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.id js=0}
{include file="%s/templates/_menu.tpl"|args:$plugin_root current="index"}
{form_errors}
@ -22,4 +22,4 @@
</form>
{include file="admin/_foot.tpl"}
{include file="_foot.tpl"}

View File

@ -1,71 +1,47 @@
{include file="admin/_head.tpl" title="Documents — %s"|args:$plugin.nom current="plugin_%s"|args:$plugin.id}
{include file="_head.tpl" title="Documents — %s"|args:$plugin.name current="plugin_%s"|args:$plugin.id}
{include file="%s/templates/_menu.tpl"|args:$plugin_root current="index"}
{form_errors}
<table class="list">
<thead>
<td>Type</td>
<td>Numéro</td>
<td>Receveur</td>
<td>Son adresse</td>
<td>Sa ville</td>
<td>Emission</td>
<td>Echéance</td>
<td>Réglée</td>
<td>Archivée</td>
<td>Moyen paiement</td>
<td>Contenu</td>
<td>Total</td>
<td></td>
</thead>
<tbody>
{foreach from=$factures item=facture}
<tr>
<td><?php switch($facture->type_facture) {
case 0:
echo 'Devis';
break;
case 1:
echo 'Facture';
break;
case 2:
echo 'Reçu fiscal';
break;
case 3:
echo 'Reçu cotisation';
break;
}
?></td>
<td><a href="{plugin_url file="facture.php"}?id={$facture.id}">{$facture.numero}</a></td>
{if $facture.receveur_membre}
<td><a href="{$admin_url}membres/fiche.php?id={$facture.receveur.id}">{$facture.receveur->$identite}</a></td>
{else}
<td><a href="{plugin_url file="client.php"}?id={$facture.receveur.id}">{$facture.receveur.nom}</a></td>
{/if}
<td>{$facture.receveur.adresse}</td>
<td>{$facture.receveur.ville}</td>
<td>{$facture.date_emission|date:'d/m/Y'}</td>
<td>{$facture.date_echeance|date:'d/m/Y'}</td>
<td><?= $facture->reglee?'Réglée':'Non' ?></td>
<td><?= $facture->archivee?'Archivée':'Non' ?></td>
<td>{$facture.moyen_paiement}</td>
<td>
{if $facture.type_facture == 3}
<p>Cotisation {$facture.contenu.intitule}</p>
<p>Souscrite le {$facture.contenu.souscription|date_short}</p>
{else}
{foreach from=$facture.contenu item=contenu}
<p>{$contenu.designation} : {$contenu.prix|escape|money:false}&nbsp;{$config.monnaie}</p>
{if $list->count()}
{include file="common/dynamic_list_head.tpl"}
{foreach from=$list->iterate() item="facture"}
<tr>
<td>{$facture.type}</td>
<th><a href="facture.php?id={$facture.id}">{$facture.numero}</a></th>
{if $facture.receveur_membre}
<td>{link href="!users/details.php?id=%d"|args:$facture.receveur_id label=$facture.receveur}</td>
{else}
<td>{link href="client.php?id=%d"|args:$facture.receveur_id label=$facture.receveur}</td>
{/if}
<td>{$facture.receveur_adresse}</td>
<td>{$facture.receveur_ville}</td>
<td>{$facture.date_emission|date:'d/m/Y'}</td>
<td>{$facture.date_echeance|date:'d/m/Y'}</td>
<td>{$facture.reglee}</td>
<td>{$facture.archivee}</td>
<td>{$facture.moyen_paiement}</td>
<td>{$facture.contenu|escape|nl2br}</td>
<td>{$facture.total|escape|money_currency}</td>
<td class="actions">
{linkbutton shape="download" href="pdf.php?id=%d&d"|args:$facture.id label="Télécharger"}
{linkbutton shape="menu" href="facture.php?id=%d"|args:$facture.id label="Voir"}
</td>
</tr>
{/foreach}
{/if}
</td>
<td>{$facture.total|escape|money}&nbsp;{$config.monnaie}</td>
{* <td>{linkbutton shape="delete" href="%sfacture_supprimer.php?id=%d"|args:$plugin_url,$facture.id label="Supprimer"}</td> *}
</tr>
{/foreach}
</tbody>
</tbody>
</table>
{$list->getHTMLPagination()|raw}
{include file="admin/_foot.tpl"}
<p class="help">
Export de la liste&nbsp;:
{linkbutton href="?export=csv" label="Export CSV" shape="download"}
{linkbutton href="?export=ods" label="Export tableur" shape="download"}
</p>
{else}
<p class="help">Aucun document, vous pouvez commencer par {link href="facture_ajouter.php" label="créer un nouveau document"}.</p>
{/if}
{include file="_foot.tpl"}

View File

@ -1,6 +1,6 @@
<?php
namespace Garradin;
namespace Paheko;
$db = DB::getInstance();
$db->import(dirname(__FILE__) . "/data/schema_remove.sql");
$db->import(dirname(__FILE__) . "/data/schema_remove.sql");

View File

@ -1,24 +1,18 @@
<?php
namespace Garradin;
use Garradin\Plugin\Facturation\Facture;
use Garradin\Entities\Files\File;
define('DEVIS', 0);
define('FACT', 1);
define('CERFA', 2);
define('COTIS', 3);
namespace Paheko;
use Paheko\Plugin\Facturation\Facture;
use Paheko\Entities\Files\File;
$db = DB::getInstance();
$facture = new Facture;
$infos = $plugin->getInfos();
$old_version = $plugin->oldVersion();
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($infos->version, '0.2.0', '<'))
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));
@ -26,7 +20,7 @@ if (version_compare($infos->version, '0.2.0', '<'))
}
// 0.3.0 - Migration Facturation\Config vers la table plugins
if (version_compare($infos->version, '0.3.0', '<'))
if (version_compare($old_version, '0.3.0', '<'))
{
$conf = $db->getAssoc('SELECT cle, valeur FROM plugin_facturation_config ORDER BY cle;');
foreach($conf as $k=>$v)
@ -39,8 +33,8 @@ if (version_compare($infos->version, '0.3.0', '<'))
$db->exec('DROP TABLE `plugin_facturation_config`;');
}
// 0.4.0 - Migration Facturation\Config vers la table plugins
if (version_compare($infos->version, '0.4.0', '<'))
// 0.4.0 -
if (version_compare($old_version, '0.4.0', '<'))
{
$db->exec(<<<EOT
CREATE TABLE IF NOT EXISTS plugin_facturation_paiement
@ -58,7 +52,7 @@ if (version_compare($infos->version, '0.4.0', '<'))
INSERT OR IGNORE INTO plugin_facturation_paiement (code, nom) VALUES ('VI', 'Virement');
INSERT OR IGNORE INTO plugin_facturation_paiement (code, nom) VALUES ('AU', 'Autre');
CREATE TABLE IF NOT EXISTS plugin_facturation_factures_tmp
(
id INTEGER PRIMARY KEY,
@ -80,13 +74,18 @@ if (version_compare($infos->version, '0.4.0', '<'))
ALTER TABLE plugin_facturation_factures_tmp RENAME TO plugin_facturation_factures;
EOT
);
);
}
// 0.6.0 - Migration Facturation\Config vers la table plugins
if (version_compare($infos->version, '0.6.0', '<'))
// 0.6.0 -
if (version_compare($old_version, '0.6.0', '<'))
{
define('DEVIS', 0);
define('FACT', 1);
define('CERFA', 2);
define('COTIS', 3);
$facture = new Facture;
$r = $db->first('SELECT id, total FROM plugin_facturation_factures;');
if (strpos($r->total,'.'))
{
@ -107,25 +106,26 @@ if (version_compare($infos->version, '0.6.0', '<'))
contenu TEXT NOT NULL,
total INTEGER DEFAULT 0
);
INSERT INTO plugin_facturation_factures_tmp SELECT * FROM plugin_facturation_factures;
DROP TABLE plugin_facturation_factures;
ALTER TABLE plugin_facturation_factures_tmp RENAME TO plugin_facturation_factures;
EOT
);
foreach($factures = $facture->listAll() as $k=>$f)
);
$factures = $facture->listAll();
foreach($factures as $k=>$f)
{
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;
@ -141,4 +141,184 @@ EOT
$path = __DIR__.'/data/default_sign.png';
$png = (new File)->createAndStore('skel/plugin/facturation','sign.png', $path, null);
}
}
}
// 0.6.2 -
if (version_compare($old_version, '0.6.2', '<'))
{
define('DEVIS', 0);
define('FACT', 1);
define('CERFA', 2);
define('COTIS', 3);
$facture = new Facture;
$db->exec(<<<EOT
INSERT OR IGNORE INTO plugin_facturation_paiement
(code, nom) VALUES ('HA', 'HelloAsso');
INSERT OR IGNORE INTO plugin_facturation_paiement
(code, nom) VALUES ('AU', 'Autre');
CREATE TABLE IF NOT EXISTS
plugin_facturation_txt_cerfa
(
id PRIMARY KEY,
menu TEXT NOT NULL UNIQUE,
texte TEXT NOT NULL
);
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")
VALUES ('1','HelloAsso','Don via HelloAsso');
INSERT OR IGNORE INTO plugin_facturation_txt_cerfa
("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")
VALUES ('3','Don en nature','Don en nature');
EOT
);
// Migration CERFA
$factures = $facture->listAll();
foreach($factures as $k=>$f)
{
if ($f->type_facture != CERFA)
{
continue;
}
$f->contenu = ['forme' => 1, 'nature' => 1, 'texte' => 0];
$data = (array) $f;
unset($data['id']);
unset($data['date_emission']);
unset($data['date_echeance']);
$facture->edit($f->id, $data);
}
}
// 0.7.1 - Ajout clé config TTC/HT
if (version_compare($old_version, '0.7.1', '<'))
{
$plugin->setConfig('ttc', false);
}
// 0.8.1 - Signal menu item
if (version_compare($old_version, '0.8.1', '<'))
{
$plugin->unregisterSignal('menu.item');
}
// 0.8.5 Ajout champs SIREN/SIRET à la table clients
if (version_compare($old_version, '0.8.5', '<'))
{
$db->exec(<<<EOT
CREATE TABLE IF NOT EXISTS plugin_facturation_clients_tmp
(
id INTEGER PRIMARY KEY,
nom TEXT NOT NULL,
adresse TEXT NOT NULL,
code_postal TEXT NOT NULL,
ville TEXT NOT NULL,
siret TEXT,
date_creation TEXT NOT NULL DEFAULT CURRENT_DATE CHECK (date(date_creation) IS NOT NULL AND date(date_creation) = date_creation),
telephone TEXT,
email TEXT
);
EOT
);
// copier les clients dans la table temporaire en ajoutant un siret fictif
$sql = 'SELECT * FROM plugin_facturation_clients';
foreach ($db->iterate($sql) as $client)
{
$db->insert('plugin_facturation_clients_tmp', $client);
}
// remplacer l'ancienne table par la nouvelle
$db->exec(<<<EOT
DROP TABLE plugin_facturation_clients;
ALTER TABLE plugin_facturation_clients_tmp RENAME TO plugin_facturation_clients;
EOT
);
}
// 0.12 Ajout Mollie à la table moyens de paiement
if (version_compare($old_version, '0.12', '<'))
{
$db->exec(<<<EOT
INSERT OR IGNORE INTO plugin_facturation_paiement
(code, nom) VALUES ('MO', 'Mollie');
EOT
);
}
// version 0.15
// Ajout champs note et contact à la table clients
// Ajout divers champs à la table factures
if (version_compare($old_version, '0.15', '<'))
{
$db->exec(<<<EOT
CREATE TABLE IF NOT EXISTS plugin_facturation_clients_tmp
(
id INTEGER PRIMARY KEY,
nom TEXT NOT NULL,
adresse TEXT NOT NULL,
code_postal TEXT NOT NULL,
ville TEXT NOT NULL,
siret TEXT,
date_creation TEXT NOT NULL DEFAULT CURRENT_DATE CHECK (date(date_creation) IS NOT NULL AND date(date_creation) = date_creation),
telephone TEXT,
email TEXT,
nom_contact TEXT,
note TEXT
);
EOT
);
// copier les clients dans la table temporaire
$sql = 'SELECT * FROM plugin_facturation_clients';
foreach ($db->iterate($sql) as $client)
{
$db->insert('plugin_facturation_clients_tmp', $client);
}
// remplacer l'ancienne table par la nouvelle
$db->exec(<<<EOT
DROP TABLE plugin_facturation_clients;
ALTER TABLE plugin_facturation_clients_tmp RENAME TO plugin_facturation_clients;
EOT
);
$db->exec(<<<EOT
CREATE TABLE IF NOT EXISTS plugin_facturation_factures_tmp(
id INTEGER PRIMARY KEY,
type_facture INTEGER NOT NULL DEFAULT 0,
numero TEXT NOT NULL UNIQUE,
receveur_membre INTEGER NOT NULL, -- bool
receveur_id INTEGER NOT NULL,
date_emission TEXT NOT NULL, -- CHECK (date(date_emission) IS NOT NULL AND date(date_emission) = date_emission),
date_echeance TEXT NOT NULL, -- CHECK (date(date_echeance) IS NOT NULL AND date(date_echeance) = date_echeance),
reglee INTEGER DEFAULT 0, -- bool
archivee INTEGER DEFAULT 0, -- bool
moyen_paiement TEXT NOT NULL,
contenu TEXT NOT NULL,
total INTEGER DEFAULT 0,
nom_contact TEXT,
numero_commande TEXT,
reference_acheteur TEXT
);
EOT
);
// copier les factures dans la table temporaire
$sql = 'SELECT * FROM plugin_facturation_factures';
foreach ($db->iterate($sql) as $facture)
{
$db->insert('plugin_facturation_factures_tmp', $facture);
}
// remplacer l'ancienne table par la nouvelle
$db->exec(<<<EOT
DROP TABLE plugin_facturation_factures;
ALTER TABLE plugin_facturation_factures_tmp RENAME TO plugin_facturation_factures;
EOT
);
}

View File

@ -1,55 +0,0 @@
<?php
namespace Garradin;
define('DEVIS', 0);
define('FACT', 1);
define('CERFA', 2);
define('COTIS', 3);
use Garradin\Plugin\Facturation\Facture;
use Garradin\Plugin\Facturation\Client;
$client = new Client;
$facture = new Facture;
$tpl->assign('www_url', WWW_URL);
$tpl->assign('f_obj', $facture);
$tpl->assign('plugin_url', Utils::plugin_url());
$identite = (string) Config::getInstance()->get('champ_identite');
$tpl->register_function('money_fac', function (array $params)
{
static $params_list = ['value', 'name', 'user'];
// Extract params and keep attributes separated
$attributes = array_diff_key($params, array_flip($params_list));
$params = array_intersect_key($params, array_flip($params_list));
extract($params, \EXTR_SKIP);
$current_value = null;
if (isset($value)) {
$current_value = $value;
}
if (!isset($user)) {
$user = false;
}
if (!isset($name))
{
$name = 'prix[]';
}
if (null !== $current_value && !$user) {
$current_value = Utils::money_format($current_value, ',', '');
}
$current_value = htmlspecialchars($current_value, ENT_QUOTES, 'UTF-8');
$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);
}
);

View File

@ -1,54 +0,0 @@
<?php
namespace Garradin;
require_once __DIR__ . '/_inc.php';
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE);
qv(['id' => 'required|numeric']);
$id = (int) qg('id');
$c = $client->get($id);
if (!$c)
{
throw new UserException("Ce client n'existe pas.");
}
if(f('save'))
{
$form->check('edit_client', [
'nom' => 'required|string',
'adresse' => 'required|string',
'code_postal' => 'required|string',
'ville' => 'required|string',
'telephone' => 'string',
'email' => 'email'
]);
if (!$form->hasErrors())
{
try
{
$r = $client->edit($id,[
'nom' => f('nom'),
'adresse' => f('adresse'),
'code_postal' => f('code_postal'),
'ville' => f('ville'),
'telephone' => f('telephone'),
'email' => f('email')
]);
$r ? Utils::redirect(PLUGIN_URL . 'client.php?id='.(int)$id):'';
}
catch (UserException $e)
{
$form->addError($e->getMessage());
}
}
}
$tpl->assign('client', $c);
$tpl->display(PLUGIN_ROOT . '/templates/client_modifier.tpl');

View File

@ -1,56 +0,0 @@
<?php
namespace Garradin;
require_once __DIR__ . '/_inc.php';
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_READ);
if(f('add'))
{
$form->check('add_client', [
'nom' => 'required|string',
'adresse' => 'required|string',
'code_postal' => 'required|string',
'ville' => 'required|string',
'telephone' => 'string',
'email' => 'email'
]);
if (!$form->hasErrors())
{
try
{
$id = $client->add([
'nom' => f('nom'),
'adresse' => f('adresse'),
'code_postal' => f('code_postal'),
'ville' => f('ville'),
'telephone' => f('telephone'),
'email' => f('email')
]);
$id ? Utils::redirect(PLUGIN_URL . 'client.php?id='.(int)$id):'';
}
catch (UserException $e)
{
$form->addError($e->getMessage());
}
}
}
$tpl->assign('clients', $client->listAll());
$tpl->assign('champs',
[
'id' => 'id',
'nom' => 'Nom',
'adresse' => 'Adresse',
'code_postal' => 'Code postal',
'ville' => 'Ville',
'telephone' => 'Numéro de téléphone',
'email' => 'Adresse mail'
]
);
$tpl->display(PLUGIN_ROOT . '/templates/clients.tpl');

View File

@ -1,127 +0,0 @@
<?php
namespace Garradin;
require_once __DIR__ . '/_inc.php';
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_ADMIN);
if (f('save') && $form->check('facturation_config'))
{
try {
$plugin->setConfig('rna_asso', trim(f('rna_asso')));
$plugin->setConfig('siret_asso', trim(f('siret_asso')));
$plugin->setConfig('numero_rue_asso', trim(f('numero_rue_asso')));
$plugin->setConfig('rue_asso', trim(f('rue_asso')));
$plugin->setConfig('cp_asso', trim(f('cp_asso')));
$plugin->setConfig('ville_asso', trim(f('ville_asso')));
$plugin->setConfig('droit_art200', (bool)f('droit_art200'));
$plugin->setConfig('droit_art238bis', (bool)f('droit_art238bis'));
$plugin->setConfig('droit_art885-0VbisA', (bool)f('droit_art885-0VbisA'));
$plugin->setConfig('objet_0', trim(f('objet_0')));
$plugin->setConfig('objet_1', trim(f('objet_1')));
$plugin->setConfig('objet_2', trim(f('objet_2')));;
$plugin->setConfig('footer', f('footer'));
$plugin->setConfig('validate_cp', (bool)f('validate_cp'));
$plugin->setConfig('unique_client_name', (bool)f('unique_client_name'));
Utils::redirect(PLUGIN_URL . 'config.php?ok');
}
catch (UserException $e)
{
$form->addError($e->getMessage());
}
}
// Traitement de l'image de signature
// Copié du plugin de nfrery
/*
if (f('upload') || isset($_POST['uploadHelper_status']))
{
$form->check('signature_config');
if (f('uploadHelper_status') > 0)
{
throw new UserException('Un seul fichier peut être envoyé en même temps.');
}
elseif (!empty($_POST['fichier']) || isset($_FILES['fichier']))
{
try {
if (isset($_POST['uploadHelper_status']) && !empty($_POST['fichier']))
{
$fichier = Fichiers::uploadExistingHash(f('fichier'), f('uploadHelper_fileHash'));
}
else
{
$fichier = Fichiers::upload($_FILES['fichier']);
}
// ?? je comprends pas tout dans ce bloc
if (isset($_POST['uploadHelper_status']))
{
echo json_encode([
'redirect' => WWW_URL,
'callback' => 'insertHelper',
'file' => [
'image' => (int)$fichier->image,
'id' => (int)$fichier->id,
'nom' => $fichier->nom,
'thumb' => $fichier->image ? $fichier->getURL(200) : false
],
]);
exit;
}
if (!$plugin->getConfig('signaturetxt') == "")
{
$fichier_old = new Fichiers($plugin->getConfig('signaturetxt'));
$fichier_old->remove();
}
Static_Cache::storeFromUpload('fichiers.'.$fichier->id, $fichier->nom);
$plugin->setConfig('signaturetxt', $fichier->id);
Utils::redirect(PLUGIN_URL . 'config.php?ok');
}
catch (UserException $e)
{
throw new UserException($e->getMessage());
}
}
else
{
$error = 'Aucun fichier envoyé.';
}
}
if(!$plugin->getConfig('signaturetxt') == "")
{
$img1 = new Fichiers($plugin->getConfig('signaturetxt'));
$cache_id = 'fichiers.' . $img1->id_contenu;
if (!Static_Cache::exists($cache_id))
{
$blob = DB::getInstance()->openBlob('fichiers_contenu', 'contenu', (int)$img1->id_contenu);
Static_Cache::storeFromPointer($cache_id, $blob);
fclose($blob);
}
$uri = $img1->getURL();
$tpl->assign('image', $uri);
}
else
{
$tpl->assign('image', false);
}
*/
$tpl->assign('ok', qg('ok') !== null);
// $tpl->assign('max_size', Utils::getMaxUploadSize());
$tpl->display(PLUGIN_ROOT . '/templates/config.tpl');

View File

@ -1,221 +0,0 @@
<?php
namespace Garradin;
require_once __DIR__ . '/_inc.php';
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE);
use Garradin\DB;
use stdClass;
$db = DB::getInstance();
$step = $radio = false;
$liste = [];
$fields = $facture->recu_fields;
$tpl->assign('moyens_paiement', $facture->listMoyensPaiement());
$tpl->assign('moyen_paiement', f('moyen_paiement') ?: 'ES');
if (f('add'))
{
$form->check('ajout_facture', [
'type' => 'required|in:'.implode(',', [DEVIS, FACT, CERFA]),
'numero_facture' => 'required|string',
'date_emission' => 'required|date_format:d/m/Y',
'date_echeance' => 'required|date_format:d/m/Y',
// 'reglee' => '',
// 'archivee' => '',
'base_receveur' => 'required|in:membre,client',
// 'client' => '',
// 'membre' => '',
'moyen_paiement' => 'required|in:' . implode(',', array_keys($facture->listMoyensPaiement())),
'designation' => 'array|required',
'prix' => 'array|required'
]);
if (!$form->hasErrors())
{
try
{
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'),
'date_emission' => f('date_emission'),
'date_echeance' => f('date_echeance'),
'reglee' => f('reglee') == 1?1:0,
'archivee' => f('archivee') == 1?1:0,
'moyen_paiement' => f('moyen_paiement'),
'toto' => 0
];
if (in_array(f('type'), [DEVIS, FACT, CERFA]))
{
$truc['type_facture'] = f('type');
}
foreach(f('designation') as $k=>$value)
{
$truc['contenu'][$k]['designation'] = $value;
$truc['contenu'][$k]['prix'] = Utils::moneyToInteger(f('prix')[$k]);
$truc['toto'] += Utils::moneyToInteger(f('prix')[$k]);
}
$truc['total'] = $truc['toto'];
unset($truc['toto']);
if (f('base_receveur') == 'client')
{
$truc['receveur_membre'] = 0;
$truc['receveur_id'] = f('client');
}
elseif (f('base_receveur') == 'membre')
{
$truc['receveur_membre'] = 1;
$truc['receveur_id'] = f('membre');
}
$id = $facture->add($truc);
Utils::redirect(PLUGIN_URL . 'facture.php?id='.(int)$id);
}
catch(UserException $e)
{
$form->addError($e->getMessage());
}
}
}
elseif (f('select_cotis'))
{
$form->check('add_cotis_1',[
'numero_facture' => 'required|string',
'date_emission' => 'required|date_format:d/m/Y',
'membre_cotis' => 'required|numeric',
]);
$step = true;
}
elseif (f('add_cotis'))
{
$form->check('add_cotis_2',[
'numero_facture' => 'required|string',
'date_emission' => 'required|date_format:d/m/Y',
'membre_cotis' => 'required|numeric',
'cotisation' => 'required',
]);
$radio['type'] = f('cotisation');
if (!$form->hasErrors())
{
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' => f('moyen_paiement'),
'total' => $r->paid_amount ?? $r->amount,
'contenu' => ['id' => $cotis['id'],
'intitule' => $cotis['label'],
'souscription' => $cotis['date'],
'expiration' => $cotis['expiry'] ]
];
$id = $facture->add($data);
Utils::redirect(PLUGIN_URL . 'facture.php?id='.(int)$id);
}
catch (UserException $e)
{
$form->addError($e->getMessage());
}
}
$step = true;
}
if ($step)
{
try
{
$liste = $facture->getCotis((int)f('membre_cotis'));
}
catch (UserException $e)
{
$form->addError($e->getMessage());
}
}
$type = qg('t') ? (int) qg('t') : null;
if (in_array($type, [DEVIS, FACT, CERFA, COTIS], true))
{
$radio['type'] = $type;
}
elseif (null !== f('type'))
{
$radio['type'] = f('type');
}
else
{
$radio['type'] = FACT;
}
$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))
{
foreach($d as $k=>$v)
{
if (empty($v) && empty($p[$k]))
{
continue;
}
$designations[] = $v;
$prix[] = $p[$k];
}
$from_user = true;
}
else {
$designations = ['Exemple'];
$prix = [250];
}
$tpl->assign(compact('liste', 'radio', 'step'));
$date = new \DateTime;
$date->setTimestamp(time());
$tpl->assign('date', $date->format('d/m/Y'));
$tpl->assign(compact('designations', 'prix', 'from_user', 'identite'));
$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;'));
$tpl->display(PLUGIN_ROOT . '/templates/facture_ajouter.tpl');

View File

@ -1,261 +0,0 @@
<?php
namespace Garradin;
require_once __DIR__ . '/_inc.php';
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE);
use Garradin\DB;
$db = DB::getInstance();
$step = false;
$liste = [];
$fields = $facture->recu_fields;
$tpl->assign('moyens_paiement', $facture->listMoyensPaiement());
$tpl->assign('moyen_paiement', f('moyen_paiement') ?: 'ES');
qv(['id' => 'required|numeric']);
$id = (int) qg('id');
if (!$f = $facture->get($id))
{
throw new UserException("Ce document n'existe pas.");
}
// Traitement
if(f('save'))
{
$form->check('modifier_facture', [
'type' => 'required|in:'.implode(',', [DEVIS, FACT, CERFA]),
'numero_facture' => 'required|string',
'date_emission' => 'required|date_format:d/m/Y',
'date_echeance' => 'required|date_format:d/m/Y',
// 'reglee' => '',
// 'archivee' => '',
'base_receveur' => 'required|in:membre,client',
// 'client' => '',
// 'membre' => '',
'moyen_paiement' => 'required|in:' . implode(',', array_keys($facture->listMoyensPaiement())),
'designation' => 'array|required',
'prix' => 'array|required'
]);
if (!$form->hasErrors())
{
try
{
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'),
'date_emission' => f('date_emission'),
'date_echeance' => f('date_echeance'),
'reglee' => f('reglee') == 1?1:0,
'archivee' => f('archivee') == 1?1:0,
'moyen_paiement' => f('moyen_paiement'),
'toto' => 0
];
if (in_array(f('type'), [DEVIS, FACT, CERFA]))
{
$truc['type_facture'] = f('type');
}
foreach(f('designation') as $k=>$value)
{
$truc['contenu'][$k]['designation'] = $value;
$truc['contenu'][$k]['prix'] = Utils::moneyToInteger(f('prix')[$k]);
$truc['toto'] += Utils::moneyToInteger(f('prix')[$k]);
}
$truc['total'] = $truc['toto'];
unset($truc['toto']);
if (f('base_receveur') == 'client')
{
$truc['receveur_membre'] = 0;
$truc['receveur_id'] = f('client');
}
elseif (f('base_receveur') == 'membre')
{
$truc['receveur_membre'] = 1;
$truc['receveur_id'] = f('membre');
}
$r = $facture->edit($id, $truc);
Utils::redirect(PLUGIN_URL . 'facture.php?id='.(int)$id);
}
catch(UserException $e)
{
$form->addError($e->getMessage());
}
}
}
// Cotis
$fields = $facture->recu_fields;
$membre_id = f('membre') ?: $f->receveur_id;
$values['numero_facture'] = $f->numero;
$values['date_emission'] = $f->date_emission;
$radio = $liste = [];
if (f('select_cotis'))
{
$form->check('add_cotis_1',[
'numero_facture' => 'required|string',
'date_emission' => 'required|date',
'membre_cotis' => 'required|numeric',
]);
$step = true;
}
elseif (f('add_cotis'))
{
$form->check('add_cotis_2',[
'numero_facture' => 'required|string',
'date_emission' => 'required|date',
'membre_cotis' => 'required|numeric',
'cotisation' => 'required',
]);
$radio['type'] = f('cotisation');
if (!$form->hasErrors())
{
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' => 3,
'numero' => f('numero_facture'),
'receveur_membre' => 1,
'receveur_id' => f('membre_cotis'),
'date_emission' => f('date_emission'),
'moyen_paiement' => f('moyen_paiement'),
'total' => $r->paid_amount ?? $r->amount,
'contenu' => ['id' => $cotis['id'],
'intitule' => $cotis['label'],
'souscription' => $cotis['date'],
'expiration' => $cotis['expiry'] ]
];
if($facture->edit($id, $data))
{
Utils::redirect(PLUGIN_URL . 'facture.php?id='.(int)$id);
}
throw new UserException('Erreur d\'édition du reçu');
}
catch (UserException $e)
{
$form->addError($e->getMessage());
}
}
$step = true;
}
if ($step)
{
try
{
$liste = $facture->getCotis((int)f('membre_cotis'));
}
catch (UserException $e)
{
$form->addError($e->getMessage());
}
}
// 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;
$doc['numero_facture'] = $f->numero;
$doc['reglee'] = $f->reglee;
$doc['base_receveur'] = $f->receveur_membre?'membre':'client';
$doc['client'] = $f->receveur_id;
$doc['membre'] = $f->receveur_id;
$doc['date_emission'] = strtotime(f('date_emission')) ?: $f->date_emission;
$doc['date_echeance'] = strtotime(f('date_echeance')) ?: $f->date_echeance; // Smarty m'a saoulé pour utiliser form_field|date_fr:---
$tpl->assign('doc', $doc);
$radio['type'] = f('type')??$doc['type'];
$tpl->assign('types_details', $facture->types);
$tpl->assign('client_id', f('client') ?: -1);
$tpl->assign('membre_id', f('membre') ?: -1);
$tpl->assign(compact('liste', 'radio', 'step'));
// C'est un peu l'équivalent de form_field, mais j'avais écrit ça avant
// et oulala, c'est un peu complexe, faudrait réfléchir keskivomieux
$from_user = false;
if ($f->type_facture != COTIS)
{
if (($d = f('designation')) && ($p = f('prix')))
{
foreach($d as $k=>$v)
{
if (empty($v) && empty($p[$k]))
{
continue;
}
$designations[] = $v;
$prix[] = $p[$k];
}
$from_user = true;
}
else
{
foreach($f->contenu as $k=>$v)
{
if (empty($v['designation']) && empty($v['prix']))
{
continue;
}
$designations[] = $v['designation'];
$prix[] = $v['prix'];
}
$from_user = false;
}
}
$tpl->assign(compact('designations', 'prix', 'from_user', 'identite'));
$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;'));
$date = new \DateTime;
$date->setTimestamp(time());
$tpl->assign('date', $date->format('d/m/Y'));
$tpl->display(PLUGIN_ROOT . '/templates/facture_modifier.tpl');

View File

@ -1,38 +0,0 @@
<?php
namespace Garradin;
require_once __DIR__ . '/_inc.php';
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_WRITE);
qv(['id' => 'required|numeric']);
$id = (int) qg('id');
$f = $facture->get($id);
if (!$client)
{
throw new UserException("Ce document n'existe pas.");
}
if (f('delete'))
{
$form->check('delete_doc_'.$f->id);
if (!$form->hasErrors())
{
try {
$facture->delete($f->id);
Utils::redirect(PLUGIN_URL . 'index.php');
}
catch (UserException $e)
{
$form->addError($e->getMessage());
}
}
}
$tpl->assign('doc', $f);
$tpl->display(PLUGIN_ROOT . '/templates/facture_supprimer.tpl');

View File

@ -1,23 +0,0 @@
<?php
namespace Garradin;
require_once __DIR__ . '/_inc.php';
$session->requireAccess($session::SECTION_ACCOUNTING, $session::ACCESS_READ);
$membres = new Membres;
$tpl->assign('moyens_paiement', $facture->listMoyensPaiement());
foreach($factures = $facture->listAll() as $k=>$f)
{
$factures[$k]->receveur = $f->receveur_membre? $membres->get($f->receveur_id) : $client->get($f->receveur_id);
$factures[$k]->moyen_paiement = $facture->getMoyenPaiement($f->moyen_paiement);
}
$tpl->assign('identite', $identite);
$tpl->assign('factures', $factures);
$tpl->assign('clients', $client->listAll());
$tpl->display(PLUGIN_ROOT . '/templates/index.tpl');

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 KiB