initial commit

This commit is contained in:
Matthieu Bessat 2024-01-19 16:39:49 +01:00
commit 5ebc506921
975 changed files with 154341 additions and 0 deletions

62
tools/categories_as_projects.sh Executable file
View file

@ -0,0 +1,62 @@
#!/bin/bash
# Ce script permet de convertir les anciennes catégories en projets
# et d'affecter ces projets aux écritures.
#
# - Création de nouveaux comptes de projets
# - Affectation des lignes des écritures à ces nouvelles écritures
#
# Le premier argument doit être l'ancienne base de données (version 0.9.8)
# Le second argument doit être la nouvelle base de données (1.0)
#
# Évidemment ça ne marche que si la BDD 1.0 est une mise à jour de la BDD de la 0.9.8 !
# Sinon ça sera tout mélangé !
if [ ! -f "$1" ] || [ ! -f "$2" ]; then
echo "Usage: $0 OLD_DATABASE NEW_DATABASE"
exit 1
fi
sqlite3 "$1" <<EOF
CREATE TEMP TABLE projects_categories (id, code, label, description);
INSERT INTO projects_categories SELECT id, NULL, intitule, description FROM compta_categories;
UPDATE projects_categories SET code = printf('99%03d', rowid);
--SELECT code, label FROM projects_categories;
--SELECT id, (SELECT code FROM projects_categories WHERE id = id_categorie) FROM compta_journal WHERE id_categorie IS NOT NULL;
CREATE TEMP TABLE projects_transactions (id, code, account_id);
INSERT INTO projects_transactions
SELECT
id,
(SELECT code FROM projects_categories WHERE id = id_categorie),
NULL
FROM compta_journal
WHERE id_categorie IS NOT NULL;
ATTACH '${2}' AS new;
BEGIN;
INSERT INTO new.acc_accounts (id_chart, code, label, description, position, type, user)
SELECT
(SELECT id FROM acc_charts WHERE code = 'PCGA1999'),
code,
label,
description,
0,
7, -- type
1
FROM projects_categories;
UPDATE projects_transactions AS t SET account_id = (SELECT id FROM new.acc_accounts a WHERE a.code = t.code);
UPDATE new.acc_transactions_lines AS l
SET
id_analytical = (SELECT account_id FROM projects_transactions t WHERE t.id = l.id_transaction);
COMMIT;
EOF

View file

@ -0,0 +1,593 @@
<?php
namespace Garradin;
require __DIR__ . '/../src/include/class.compta_comptes.php';
require __DIR__ . '/../src/include/lib.utils.php';
$plan = <<<EOF_PLAN
Classe 1 Comptes de capitaux (Fonds propres, emprunts et dettes assimilés)
10 FONDS ASSOCIATIFS ET RÉSERVES
102 Fonds associatif sans droit de reprise
1021 Valeur du patrimoine intégré
1022 Fonds statutaire
1024 Apports sans droit de reprise
103 Fonds associatif avec droit de reprise
1034 Apports avec droit de reprise
105 Écarts de réévaluation
106 Réserves
1063 Réserves statutaires ou contractuelles
1064 Réserves réglementées
1068 Autres réserves (dont réserves pour projet associatif)
11 REPORT À NOUVEAU
110 Report à nouveau (Solde créditeur)
119 Report à nouveau (Solde débiteur)
12 RÉSULTAT NET DE L'EXERCICE
120 Résultat de l'exercice (excédent)
129 Résultat de l'exercice (déficit)
13 SUBVENTIONS D'INVESTISSEMENT AFFECTÉES A DES BIENS NON RENOUVELABLES
131 Subventions d'investissement (renouvelables)
139 Subventions d'investissement inscrites au compte de résultat
14 PROVISIONS REGLEMENTÉES
15 PROVISIONS
151 Provisions pour risques
157 Provisions pour charges à répartir sur plusieurs exercices
158 Autres provisions pour charges
16 EMPRUNTS ET DETTES ASSIMILÉES
164 Emprunts auprès des établissements de crédits
165 Dépôts et cautionnements reçus
167 Emprunts et dettes assorties de conditions particulières
168 Autres emprunts et dettes assimilés
17 DETTES RATTACHÉES À DES PARTICIPATIONS
18 COMPTES DE LIAISON DES ÉTABLISSEMENTS
181 Apports permanents entre siège social et établissements
185 Biens et prestations de services échangés entre établissements et siège social
186 Biens et prestations de services échangés entre établissements (charges)
187 Biens et prestations de services échangés entre établissements (produits)
19 FONDS DÉDIÉS
194 Fonds dédiés sur subventions de fonctionnement
195 Fonds dédiés sur dons manuels affectés
197 Fonds dédiés sur legs et donations affectés
198 Excédent disponible après affectation au projet associatif
199 Reprise des fonds affectés au projet associatif
Classe 2 Comptes d'immobilisations
20 IMMOBILISATIONS INCORPORELLES
200 Immobilisations incorporelles
21 IMMOBILISATIONS CORPORELLES
210 Investissements
22 IMMOBILISATIONS GREVÉES DE DROITS
228 Immobilisations grevées de droits
229 Droits des propriétaires
23 IMMOBILISATIONS EN COURS
231 Immobilisations corporelles en cours
238 Avances et acomptes versés sur commande d'immobilisations corporelles
26 PARTICIPATIONS ET CRÉANCES RATTACHÉES A DES PARTICIPATIONS
261 Titres de participation
27 AUTRES IMMOBILISATIONS FINANCIÈRES
270 Participations financières
275 Dépôts et cautionnements versés
28 AMORTISSEMENTS DES IMMOBILISATIONS
280 Amortissements des immobilisations incorporelles
281 Amortissements des immobilisations corporelles
29 DÉPRÉCIATION DES IMMOBILISATIONS
290 Dépréciation des immobilisations incorporelles
291 Dépréciation des immobilisations corporelles
Classe 3 Comptes de stocks
31 MATIERES PREMIERES ET FOURNITURES
311 Matières
317 Fournitures
32 AUTRES APPROVISIONNEMENTS
321 Matières consommables
322 Fournitures consommables
33 EN-COURS DE PRODUCTION DE BIENS
331 Produits en cours
335 Travaux en cours
34 EN-COURS DE PRODUCTION DE SERVICES
35 STOCKS DE PRODUITS
351 Produits intermédiaires
355 Produits finis
358 Produits résiduels
3581 Déchets
3585 Rebuts
3586 Matière de récupération
37 STOCKS DE MARCHANDISES
370 Autres stocks de marchandises
39 PROVISIONS POUR DEPRECIATION DES STOCKS ET EN-COURS
391 Provisions pour dépréciation des matières premières et fournitures
Classe 4 Comptes de tiers
40 FOURNISSEURS ET COMPTES RATTACHÉS [PASSIF]
401 Fournisseurs [PASSIF]
4010 Autres fournisseurs [PASSIF]
408 Fournisseurs - Factures non parvenues [PASSIF]
409 Avances aux fournisseurs [ACTIF]
41 USAGERS ET COMPTES RATTACHÉS [ACTIF]
411 Usagers [ACTIF]
4110 Autres usagers [ACTIF]
419 Avances aux usagers [PASSIF]
42 PERSONNEL ET COMPTES RATTACHÉS [PASSIF]
421 Personnel - Rémunérations dues [PASSIF]
4210 Autres membres du personnel [PASSIF]
425 Personnel - Avances et acomptes [ACTIF]
428 Personnel - Charges à payer et produits à recevoir [PASSIF]
43 SÉCURITÉ SOCIALE ET AUTRES ORGANISMES SOCIAUX [PASSIF]
430 Dettes et crédits envers les organismes sociaux [PASSIF]
431 Sécurité sociale [PASSIF]
437 Autres organismes sociaux [PASSIF]
4372 Mutuelles [PASSIF]
4373 Caisse de retraite et de prévoyance [PASSIF]
4374 Caisse d'allocations de chômage - Pôle emploi [PASSIF]
4375 AGESSA [PASSIF]
4378 Autres organismes sociaux - Divers [PASSIF]
438 Organismes sociaux - Charges à payer et produits à recevoir [PASSIF]
4382 Charges sociales sur congés à payer [PASSIF]
4386 Autres charges à payer [PASSIF]
4387 Produits à recevoir [ACTIF]
439 Avances auprès des organismes sociaux [PASSIF]
44 ÉTAT ET AUTRES COLLECTIVITÉS PUBLIQUES [ACTIF]
441 État - Subventions à recevoir [ACTIF]
4411 Subventions d'investissement [ACTIF]
4417 Subventions d'exploitation [ACTIF]
4418 Subventions d'équilibre [ACTIF]
4419 Avances sur subventions [ACTIF]
442 État - Impôts et taxes recouvrables sur des tiers [PASSIF]
444 État - Impôts sur les bénéfices [ACTIF]
445 État - Taxes sur le chiffre d'affaires [ACTIF]
4455 Taxes sur le chiffre d'affaires à décaisser [ACTIF]
44551 TVA à décaisser [ACTIF]
44558 Taxes assimilées à la TVA [ACTIF]
4456 Taxes sur le chiffre d'affaires déductibles [ACTIF]
44562 TVA sur immobilisations [ACTIF]
44566 TVA sur autres biens et services [ACTIF]
4457 Taxes sur le chiffre d'affaires collectées par l'association [ACTIF]
4458 Taxes sur le chiffre d'affaires à régulariser ou en attente [ACTIF]
44581 Acomptes - Régime simplifié d'imposition [ACTIF]
44582 Acomptes - Régime du forfait [ACTIF]
44583 Remboursement de taxes sur le chiffre d'affaires demandé [ACTIF]
44584 TVA récupérée d'avance [ACTIF]
44586 Taxes sur le chiffre d'affaires sur factures non parvenues [ACTIF]
44587 Taxes sur le chiffre d'affaires sur factures à établir [ACTIF]
447 Autres impôts, taxes et versements assimilés [PASSIF]
4471 Autres impôts, taxes et versements assimilés sur rémunérations (Administration des impôts) [PASSIF]
44711 Taxe sur les salaires [PASSIF]
44713 Participation des employeurs à la formation professionnelle continue [PASSIF]
44714 Cotisation par défaut d'investissement obligatoire dans la construction [PASSIF]
44718 Autres impôts, taxes et versements assimilés [PASSIF]
4473 Autres impôts, taxes et versements assimilés sur rémunérations (Autres organismes) [PASSIF]
44733 Participation des employeurs à la formation professionnelle continue [PASSIF]
44734 Participation des employeurs à l'effort de construction (versements à fonds perdus) [PASSIF]
4475 Autres impôts, taxes et versements assimilés (Administration des impôts) [PASSIF]
4477 Autres impôts, taxes et versements assimilés (Autres organismes) [PASSIF]
448 État - Charges à payer et produits à recevoir [PASSIF]
4482 Charges fiscales sur congés à payer [PASSIF]
4486 Autres charges à payer [PASSIF]
4487 Produits à recevoir [ACTIF]
449 Avances auprès de l'état et des collectivités publiques [PASSIF]
45 CONFÉDÉRATION, FÉDÉRATION, UNIONS ET ASSOCIATIONS AFFILIÉES
451 Confédération, fédération et associations affiliées - Compte courant
455 Sociétaires - Comptes courants
46 DÉBITEURS DIVERS ET CRÉDITEURS DIVERS
467 Autres comptes débiteurs et créditeurs
468 Divers - Charges à payer et produits à recevoir
4686 Charges à payer [PASSIF]
4687 Produits à recevoir [ACTIF]
47 COMPTES TRANSITOIRES OU D'ATTENTE
471 Recettes à classer [PASSIF]
472 Dépenses à classer et à régulariser [ACTIF]
48 COMPTES DE RÉGULARISATION
481 Charges à répartir sur plusieurs exercices [ACTIF]
486 Charges constatées d'avance [ACTIF]
487 Produits constatés d'avance [PASSIF]
49 DEPRECIATION DES COMPTES DE TIERS [ACTIF]
491 Dépréciation des comptes clients [ACTIF]
496 Dépréciation des comptes débiteurs divers [ACTIF]
Classe 5 Comptes financiers
50 VALEURS MOBILIÈRES DE PLACEMENT
51 BANQUES, ÉTABLISSEMENTS FINANCIERS ET ASSIMILÉS
512 Banques
53 CAISSE
530 Caisse
54 RÉGIES D'AVANCES ET ACCRÉDITIFS
58 VIREMENTS INTERNES
59 PROVISIONS POUR DÉPRÉCIATION DES COMPTES FINANCIERS
Classe 6 Comptes de charges
60 ACHATS
601 Achats stockés - Matières premières et fournitures
602 Achats stockés - Autres approvisionnements
604 Achat d'études et prestations de services
606 Achats non stockés de matières et fournitures
6061 Fournitures non stockables (eau, énergie...)
6063 Fournitures d'entretien et de petit équipement
6064 Fournitures administratives
6068 Autres matières et fournitures
607 Achats de marchandises
61 SERVICES EXTÉRIEURS
611 Sous-traitance générale
612 Redevances de crédit-bail
613 Locations
614 Charges locatives et de co-propriété
615 Entretiens et réparations
616 Primes d'assurance
618 Divers
62 AUTRES SERVICES EXTÉRIEURS
621 Personnel extérieur à l'association
622 Rémunérations d'intermédiaires et honoraires
6226 Honoraires
6227 Frais d'actes et de contentieux
6228 Divers
623 Publicité, publications, relations publiques
624 Transports de biens et transports collectifs du personnel
625 Déplacements, missions et réceptions
626 Frais postaux et de télécommunications
627 Services bancaires et assimilés
628 Divers
63 IMPÔTS, TAXES ET VERSEMENTS ASSIMILÉS
631 Impôts, taxes et versements assimilés sur rémunérations (Administration des impôts)
6311 Taxes sur les salaires
6313 Participations des employeurs à la formation professionnelle continue
635 Autres impôts, taxes et versements assimilés (Administration des impôts)
6351 Impôts directs (sauf impôts sur les bénéfices)
6353 Impôts indirects
637 Autres impôts, taxes et versements assimilés (Autres organismes)
64 CHARGES DE PERSONNEL
641 Rémunérations du personnel
643 Rémunérations du personnel artistique et assimilés
645 Charges de sécurité sociale et de prévoyance
647 Autres charges sociales
648 Autres charges de personnel
65 AUTRES CHARGES DE GESTION COURANTE
658 Charges diverses de gestion courante
66 CHARGES FINANCIÈRES
661 Charges d'intérêts
67 CHARGES EXCEPTIONNELLES
671 Charges exceptionnelles sur opérations de gestion
6713 Dons, libéralités
678 Autres charges exceptionnelles
6788 Charges exceptionnelles diverses
68 DOTATIONS AUX AMORTISSEMENTS, DÉPRÉCIATIONS, PROVISIONS ET ENGAGEMENTS
681 Dotations aux amortissements, dépréciations et provisions - Charges d'exploitation
6811 Dotations aux amortissements des immobilisations incorporelles et corporelles
68111 Immobilisations incorporelles
68112 Immobilisations corporelles
686 Dotations aux amortissements, dépréciations et provisions - Charges financières
69 PARTICIPATION DES SALARIÉS - IMPÔTS SUR LES BÉNÉFICES ET ASSIMILÉS
695 Impôts sur les sociétés (y compris impôts sur les sociétés des personnes morales non lucratives)
Classe 7 Comptes de produits
70 VENTES DE PRODUITS FINIS, PRESTATIONS DE SERVICES, MARCHANDISES
701 Ventes de produits finis
706 Prestations de services
707 Ventes de marchandises
708 Produits des activités annexes
71 PRODUCTION STOCKÉE (OU DÉSTOCKAGE)
72 PRODUCTION IMMOBILISÉE
74 SUBVENTIONS D'EXPLOITATION
740 Subventions reçues
75 AUTRES PRODUITS DE GESTION COURANTE
754 Collectes
756 Cotisations
758 Produits divers de gestion courante
7587 Ventes de dons en nature
7588 Autres produits de la générosité du public
76 PRODUITS FINANCIERS
760 Produits financiers
77 PRODUITS EXCEPTIONNELS
771 Produits exceptionnels sur opérations de gestion
7713 Libéralités reçues
7715 Subventions d'équilibre
775 Produits des cessions d'éléments d'actifs
778 Autres produits exceptionnels
7780 Manifestations diverses
7788 Produits exceptionnels divers
78 REPRISES SUR AMORTISSEMENTS ET PROVISIONS
79 TRANSFERT DE CHARGES
791 Transferts de charges d'exploitation
796 Transferts de charges financières
797 Transferts de charges exceptionnels
Classe 8 ­— Contributions bénévoles en nature
86 RÉPARTITION PAR NATURE DE CHARGES
861 Mise à dispositions gratuites de biens
862 Prestations
864 Personnel bénévole
87 RÉPARTITION PAR NATURE DE RESSOURCES
870 Bénévolat
871 Prestations en nature
875 Dons en nature
Classe 9 Comptes analytiques
EOF_PLAN;
$plan = preg_replace("/\r/", '', $plan);
$plan = preg_replace("/\n{2,}/", "\n", $plan);
$src = explode("\n", $plan);
$plan = array();
foreach ($src as $line)
{
$line = trim($line);
if (preg_match('!^(\d+)\s+(.+)$!', $line, $match))
{
$code = (int)$match[1];
$nom = trim($match[2]);
$parent = (int)substr($match[1], 0, -1);
}
elseif (preg_match('!^Classe (\d+)\s+.*$!', $line, $match))
{
$code = (int)$match[1];
$nom = trim($match[0]);
$parent = 0;
}
else
{
echo "$line\n";
continue;
}
if (preg_match('/^(.+?)\s+\[(ACTIF|PASSIF)\]\s*$/', $nom, $match))
{
$position = ($match[2] == 'PASSIF') ? Compta_Comptes::PASSIF : Compta_Comptes::ACTIF;
$nom = $match[1];
}
else
{
$position = false;
}
$classe = substr((string)$code, 0, 1);
if ($classe == 1)
{
$position = Compta_Comptes::PASSIF;
}
elseif ($classe == 2 || $classe == 3 || $classe == 5)
{
$position = Compta_Comptes::ACTIF;
}
// Comptes de classe 4, c'est compliqué là
elseif ($classe == 4)
{
if ($position === false)
{
$position = Compta_Comptes::PASSIF | Compta_Comptes::ACTIF;
}
}
elseif ($classe == 6)
{
$position = Compta_Comptes::CHARGE;
}
elseif ($classe == 7)
{
$position = Compta_Comptes::PRODUIT;
}
elseif ($classe == 8)
{
if (substr($code, 0, 2) == 86)
{
$position = Compta_Comptes::CHARGE;
}
elseif (substr($code, 0, 2) == 87)
{
$position = Compta_Comptes::PRODUIT;
}
else
{
$position = Compta_Comptes::CHARGE | Compta_Comptes::PRODUIT;
}
}
elseif ($classe == 9)
{
$position = Compta_Comptes::CHARGE | Compta_Comptes::PRODUIT;
}
$plan[$code] = array(
'code' => $code,
'nom' => $nom,
'parent' => $parent,
'position' => $position,
);
}
$json = json_encode($plan, JSON_PRETTY_PRINT);
file_put_contents(__DIR__ . '/../src/include/data/plan_comptable.json', $json);
die("OK\n");
?>

109
tools/doc_md_to_html.php Normal file
View file

@ -0,0 +1,109 @@
<?php
use KD2\HTML\Markdown;
use KD2\HTML\Markdown_Extensions;
require_once __DIR__ . '/../src/include/lib/KD2/HTML/Markdown.php';
require_once __DIR__ . '/../src/include/lib/KD2/HTML/Markdown_Extensions.php';
$md = new Markdown;
// Allow extra tags for Markdown quickref
$extra_tags = [
'blockquote' => null,
'pre' => null,
'br' => null,
'h1' => null,
'h2' => null,
'h3' => null,
'h4' => null,
'h5' => null,
'h6' => null,
'ul' => null,
'ol' => null,
'li' => null,
'table' => null,
'thead' => null,
'tbody' => null,
'tr' => null,
'th' => null,
'td' => null,
'hr' => null,
'div' => ['style'],
];
Markdown_Extensions::register($md);
foreach (glob(__DIR__ . '/../doc/admin/*.md') as $file) {
if (basename($file) == 'markdown_quickref.md') {
$md->allowed_inline_tags = array_merge($md->allowed_inline_tags, $extra_tags);
}
else {
$md->allowed_inline_tags = $md::DEFAULT_INLINE_TAGS;
}
$t = file_get_contents($file);
if (preg_match('/^Title: (.*)/', $t, $match)) {
$t = substr($t, strlen($match[0]));
}
$t = $md->text($t);
$t = preg_replace('!(<a\s+[^>]+external[^>]+)>!', '$1 target="_blank">', $t);
$title = $match[1] ?? $file;
$out = '<!DOCTYPE html>
<html>
<head>
<title>' . htmlspecialchars($title) . '</title>
<meta charset="utf-8" />
<style type="text/css">
body, form, p, div, hr, fieldset, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6 {
margin: 0;
padding: 0;
}
body {
font-family: "Trebuchet MS", Arial, Helvetica, Sans-serif;
padding: .8em;
background: #eee;
}
.web-content .nav ul {
list-style-type: none;
margin: -.8em;
margin-bottom: 1em;
padding: 1em;
background: #ddd;
border-bottom: 1px solid #999;
text-align: center;
}
.web-content .boutons ul {
list-style-type: none;
background: #ccc;
padding: .5em;
margin: 0;
}
.web-content .nav li, .web-content .boutons li {
display: inline-block;
margin: 0 1em;
}
.web-content .nav a, .web-content .boutons a {
display: inline-block;
background: #fff;
color: darkblue;
border-radius: .2em;
padding: .3em .5em;
font-size: 1.2em;
}
.web-content .nav strong a {
color: darkred;
box-shadow: 0px 0px 5px orange;
}
</style>
<link rel="stylesheet" type="text/css" href="../../../content.css" />
</head>
<body><div class="web-content">' . $t . '</div></body></html>';
$dest = __DIR__ . '/../src/www/admin/static/doc/' . str_replace('.md', '.html', basename($file));
file_put_contents($dest, $out);
}

View file

@ -0,0 +1,164 @@
<?php
/**
* Extracteur de données des relevés de compte du Crédit Mutuel
* à destination de Garradin (ou autre logiciel de compta)
*
* https://garradin.eu/
*
* Ce script prend en argument un répertoire contenant des extraits
* de compte en PDF (ou un seul extrait de compte) et crée un fichier
* CSV importable directement dans Garradin.
*
* Ce script requiert d'avoir installé Tabula (Java) :
* https://github.com/tabulapdf/tabula-java
*
* Copyright (C) 2020 BohwaZ - licence AGPLv3
*/
// Ajuster cette constante en fonction du chemin où vous avez placé
// le JAR de Tabula
const TABULA_PATH = 'tabula.jar';
if (empty($argv[1]) || empty($argv[2])) {
printf("Usage: %s REPERTOIRE_OU_FICHIER FICHIER_SORTIE_CSV\n", $argv[0]);
exit(1);
}
$path = $argv[1];
$dest = $argv[2];
if (!is_readable($path)) {
die("Ne peut lire le répertoire\n");
}
$path = rtrim($path, '/');
$header = null;
$sum_header = null;
$out = [];
$i = 0;
if (is_file($path)) {
$files = [$path];
}
else {
$files = glob($path . '/*.pdf');
}
// Lecture du CSV
foreach ($files as $file) {
printf("Lecture de %s…" . PHP_EOL, $file);
$csv = shell_exec(sprintf('java -jar %s -g -l -p all %s', TABULA_PATH, escapeshellarg($file)));
$csv = explode("\n", $csv);
/*
Date
Date valeur
Opération
Débit EUROS
Crédit EUROS
*/
foreach ($csv as $line) {
$row = str_getcsv($line);
if (count($row) < 2) {
echo "Saut ligne vide\n";
continue;
}
if (preg_match('!^Solde.*(?:AU\s+(\d+/\d+/\d+))!i', trim($row[0]), $match)) {
if (null === $sum_header) {
$row = [$match[1], null, $row[0], null, $row[2]];
$out[$i++] = $row;
$sum_header = $row;
continue;
}
echo "Saut solde : ";
echo implode(', ', $row);
echo PHP_EOL;
continue;
}
elseif (preg_match('!^(?:Solde|Total|Réf\s+:.*SOLDE)!i', trim($row[0]))) {
echo "Saut solde : ";
echo implode(', ', $row);
echo PHP_EOL;
continue;
}
if (null === $header) {
echo "Saut entête\n";
$header = $row;
continue;
}
if ($header === $row) {
echo "Saut répétition entête\n";
continue;
}
if (count($row) !== count($header)) {
echo "Ligne incohérente\n";
var_dump($row); exit;
}
foreach ($row as &$cell) {
$cell = preg_replace('/\s\s+/', ' ', $cell);
}
unset($cell);
if (empty($row[0])) {
$out[$i - 1][2] .= PHP_EOL . $row[2];
continue;
}
$out[$i++] = $row;
}
}
// Création du CSV de sortie
$fp = fopen($dest, 'w');
fputcsv($fp, ['Numéro d\'écriture', 'Date', 'Libellé', 'Compte de débit', 'Compte de crédit', 'Montant', 'Numéro pièce comptable', 'Référence paiement', 'Notes']);
foreach ($out as $line) {
$label = $line[2];
$notes = null;
$ref = null;
if (false !== ($pos = strpos($label, "\n"))) {
$notes = trim(substr($label, $pos));
$label = trim(substr($label, 0, $pos));
}
if (preg_match('/^VRST (REF.*)/', $label, $match)) {
$label = 'Versement espèces';
$ref = $match[1];
}
elseif (preg_match('/^REM CHQ (REF.*)/', $label, $match)) {
$label = 'Remise de chèques';
$ref = $match[1];
}
elseif (preg_match('/^FACTURE (SGT.*)/', $label, $match)) {
$label = 'Frais bancaires Crédit Mutuel';
$ref = $match[1];
}
elseif (preg_match('/^CHEQUE (\d+)/', $label, $match)) {
$label = 'Chèque ' . $match[1];
$ref = $match[1];
}
elseif (preg_match('/^VIR ROZO/', $label) && preg_match('/(REP\d+-\d+)/s', $notes, $match)) {
$label = 'Virement Rozo';
$ref = $match[1];
}
$amount = !empty($line[4]) ? $line[4] : '-' . $line[3];
$amount = str_replace('.', '', $amount);
fputcsv($fp, ['', $line[0], $label, '', '', $amount, '', $ref, $notes]);
}
fclose($fp);

View file

@ -0,0 +1,163 @@
<?php
/**
* Extracteur de données des relevés de compte Paypal
* à destination de Garradin (ou autre logiciel de compta)
*
* https://garradin.eu/
*
* Ce script prend en argument un fichier CSV exporté de Paypal
* https://business.paypal.com/merchantdata/reportHome?reportType=DLOG
* et produit un import exploitable dans Garradin
*
* Copyright (C) 2020 BohwaZ - licence AGPLv3
*/
if (empty($argv[1]) || empty($argv[2])) {
printf("Usage: %s FICHIER_PAYPAL_CSV FICHIER_SORTIE_CSV\n", $argv[0]);
exit(1);
}
$path = $argv[1];
$dest = $argv[2];
if (!is_readable($path)) {
die("Ne peut lire le répertoire\n");
}
$path = rtrim($path, '/');
$header = null;
$sum_header = null;
$out = [];
$i = 0;
if (!is_file($path)) {
die("Le fichier Paypal est invalide\n");
}
// Lecture du CSV
printf("Lecture de %s…" . PHP_EOL, $path);
/*
Date
Heure
Fuseau horaire
Nom
Type
État
Devise
Avant commission
Commission
Net
De l'adresse email
À l'adresse email
Numéro de transaction
Adresse de livraison
État de l'adresse
Titre de l'objet
Numéro de l'objet
Montant des frais d'expédition et de traitement
Montant de l'assurance
TVA
Nom de l'option 1
Valeur de l'option 1
Nom de l'option 2
Valeur de l'option 2
Numéro de la transaction de référence
Numéro de facture
Numéro de client
Quantité
Numéro de reçu
Solde
Adresse
Adresse (suite)/District/Quartier
Ville
État/Province/Région/Comté/Territoire/Préfecture/République
Code postal
Pays
Numéro de téléphone du contact
Objet
Remarque
Indicatif pays
Impact sur le solde
*/
$header = null;
$bom = "\xef\xbb\xbf";
// Read file from beginning.
$fp = fopen($path, 'r');
// Progress file pointer and get first 3 characters to compare to the BOM string.
if (fgets($fp, 4) !== $bom) {
// BOM not found - rewind pointer to start of file.
rewind($fp);
}
$l = 0;
static $required = ['Date', 'Nom', 'Type', 'Commission', 'Net', 'Objet', 'Remarque', 'Numéro de transaction'];
while (!feof($fp)) {
$l++;
$row = fgetcsv($fp);
if (!$row) {
break;
}
if (null === $header) {
$header = $row;
continue;
}
$c = (object) array_combine($header, $row);
foreach ($required as $key) {
if (!isset($c->$key)) {
printf('Colonne "%s" manquante sur ligne %d' . PHP_EOL, $key, $l);
continue(2);
}
}
$out[] = $c;
}
fclose($fp);
// Création du CSV de sortie
$fp = fopen($dest, 'w');
fputcsv($fp, ['Numéro d\'écriture', 'Date', 'Libellé', 'Compte de débit', 'Compte de crédit', 'Montant', 'Numéro pièce comptable', 'Référence paiement', 'Notes']);
static $notes_keys = ['Nom', 'Objet', 'Remarque'];
foreach ($out as $c) {
$label = $c->Type;
if ($c->Nom) {
$label = $c->Nom . ' - ' . $label;
}
$notes = '';
$ref = $c->{'Numéro de transaction'};
foreach ($notes_keys as $k) {
if ($c->{$k}) {
$notes .= $c->{$k} . "\n";
}
}
$notes = trim($notes);
if ($c->Commission != '0,00') {
$amount = preg_replace('/\s+/U', '', $c->Commission);
fputcsv($fp, ['', $c->Date, 'Commission PayPal sur transaction', '', '', $amount, '', $ref, $notes]);
}
$amount = preg_replace('/[\s ]+/U', '', $c->{'Avant commission'});
fputcsv($fp, ['', $c->Date, $label, '', '', $amount, '', $ref, $notes]);
}
fclose($fp);

View file

@ -0,0 +1,122 @@
<?php
namespace Paheko;
/**
* Ce fichier permet de configurer Paheko pour une utilisation
* avec plusieurs associations, mais une seule copie du code source.
* (aussi appelé installation multi-sites, ferme ou usine)
*
* Voir la doc : https://fossil.kd2.org/paheko/wiki?name=Multi-sites
*
* N'oubliez pas d'installer également le script cron.sh fournit
* pour lancer les rappels automatiques et sauvegardes.
*
* Si cela ne suffit pas à vos besoins, contactez-nous :
* https://paheko.cloud/contact
* pour une aide spécifique à votre installation.
*/
// Décommenter cette ligne si vous n'utilisez pas NFS,
// pour rendre les bases de données plus rapides.
//
// Si vous utilisez NFS, décommenter cette ligne risque
// de provoquer des corruptions de base de données !
#const SQLITE_JOURNAL_MODE = 'WAL';
// Nom de domaine parent des associations hébergées
// Exemple : si vos associations sont hébergées en clubdetennis.paheko.cloud,
// indiquer ici 'paheko.cloud'
const FACTORY_DOMAIN = 'monsite.tld';
// Répertoire où seront stockées les données des utilisateurs
// Dans ce répertoire, un sous-répertoire sera créé pour chaque compte
// Ainsi 'clubdetennis.paheko.cloud' sera dans le répertoire courant (__DIR__),
// sous-répertoire 'users' et dans celui-ci, sous-répertoire 'clubdetennis'
//
// Pour chaque utilisateur il faudra créer le sous-répertoire en premier lieu
// (eg. mkdir .../users/clubdetennis)
const FACTORY_USER_DIRECTORY = __DIR__ . '/users';
// Envoyer les erreurs PHP par mail à l'adresse de l'administrateur système
// (mettre à null pour ne pas recevoir d'erreurs)
const MAIL_ERRORS = 'administrateur@monsite.tld';
// IMPORTANT !
// Modifier pour indiquer une valeur aléatoire de plus de 30 caractères
const SECRET_KEY = 'Indiquer ici une valeur aléatoire !';
// Quota de stockage de documents (en octets)
// Définit la taille de stockage disponible pour chaque association pour ses documents
const FILE_STORAGE_QUOTA = 1 * 1024 * 1024 * 1024; // 1 Go
////////////////////////////////////////////////////////////////
// Réglages conseillés, normalement il n'y a rien à modifier ici
// Indiquer que l'on va utiliser cron pour lancer les tâches à exécuter (envoi de rappels de cotisation)
const USE_CRON = true;
// Cache partagé
const SHARED_CACHE_ROOT = __DIR__ . '/cache/shared';
// Cache web partagé
const WEB_CACHE_ROOT = __DIR__ . '/cache/web/%host%';
// Désactiver le log des erreurs PHP visible dans l'interface (sécurité)
const ENABLE_TECH_DETAILS = false;
// Désactiver les mises à jour depuis l'interface web
// Pour être sûr que seul l'admin sys puisse faire des mises à jour
const ENABLE_UPGRADES = false;
// Ne pas afficher les erreurs de code PHP
const SHOW_ERRORS = false;
////////////////////////////////////////////////////////////////
// Code 'magique' qui va configurer Paheko selon les réglages
$login = null;
// Un sous-domaine ne peut pas faire plus de 63 caractères
$login_regexp = '([a-z0-9_-]{1,63})';
$domain_regexp = sprintf('/^%s\.%s$/', $login_regexp, preg_quote(FACTORY_DOMAIN, '/'));
if (isset($_SERVER['SERVER_NAME']) && preg_match($domain_regexp, $_SERVER['SERVER_NAME'], $match)) {
$login = $match[1];
}
elseif (PHP_SAPI == 'cli' && !empty($_SERVER['PAHEKO_FACTORY_USER']) && preg_match('/^' . $login_regexp . '$/', $_SERVER['PAHEKO_FACTORY_USER'])) {
$login = $_SERVER['PAHEKO_FACTORY_USER'];
}
else {
// Login invalide ou non fourni
http_response_code(404);
die('<h1>Page non trouvée</h1>');
}
$user_data_dir = rtrim(FACTORY_USER_DIRECTORY, '/') . '/' . $login;
if (!is_dir($user_data_dir)) {
http_response_code(404);
die("<h1>Cette association n'existe pas.</h1>");
}
// Définir le dossier où sont stockés les données
define('Paheko\DATA_ROOT', $user_data_dir);
// Définir l'URL
define('Paheko\WWW_URL', 'https://' . $login . '.' . FACTORY_DOMAIN . '/');
define('Paheko\WWW_URI', '/');
// Créer le lien symbolique vers le cache partagé des pages du site web
$web_cache_public = __DIR__ . '/www/.cache';
if (!file_exists($web_cache_public)) {
$web_cache_root = dirname(WEB_CACHE_ROOT);
if (!@symlink($web_cache_root, $web_cache_public)) {
echo "<h2>Impossible de créer le lien symbolique pour le cache web partagé</h2>";
echo "<p>Vous pouvez exécuter la commande suivante :</p>";
printf("<pre>ln -s %s %s</pre>", $web_cache_root, $web_cache_public);
exit;
}
}

14
tools/factory/factory_cron.sh Executable file
View file

@ -0,0 +1,14 @@
#!/bin/sh
# Répertoire où sont stockées les données des utilisateurs
# veiller à ce que ce soit le même que dans config.local.php
FACTORY_USER_DIRECTORY="users"
# Chemin vers le script cron.php de Paheko
PAHEKO_CRON_SCRIPT="scripts/cron.php"
for user in $(cd ${FACTORY_USER_DIRECTORY} && ls -1d */)
do
PAHEKO_FACTORY_USER=$(basename "$user") php $PAHEKO_CRON_SCRIPT
echo $PAHEKO_FACTORY_USER
done

View file

@ -0,0 +1,14 @@
#!/bin/sh
# Répertoire où sont stockées les données des utilisateurs
# veiller à ce que ce soit le même que dans config.local.php
FACTORY_USER_DIRECTORY="users"
# Chemin vers le script emails.php de Paheko
PAHEKO_CRON_SCRIPT="scripts/emails.php"
for user in $(cd ${FACTORY_USER_DIRECTORY} && ls -1d */)
do
PAHEKO_FACTORY_USER=$(basename "$user") php $PAHEKO_CRON_SCRIPT
echo $PAHEKO_FACTORY_USER
done

View file

@ -0,0 +1,14 @@
#!/bin/sh
# Répertoire où sont stockées les données des utilisateurs
# veiller à ce que ce soit le même que dans config.local.php
FACTORY_USER_DIRECTORY="users"
# Chemin vers le script upgrade.php de Paheko
PAHEKO_UPGRADE_SCRIPT="scripts/cron.php"
for user in $(cd ${FACTORY_USER_DIRECTORY} && ls -1d */)
do
PAHEKO_FACTORY_USER=$(basename "$user")
php $PAHEKO_UPGRADE_SCRIPT
done

74
tools/fossil-verify.sh Executable file
View file

@ -0,0 +1,74 @@
#!/bin/bash
REPO="$1"
if [ ! -f "$1/manifest" ]
then
echo "Missing manifest, maybe you didn't specify a repository path,"
echo "or you didn't enable the manifest? (fossil settings manifest on)"
echo "Usage: $0 FOSSIL_REPOSITORY_PATH"
exit 1
fi
gpg --verify "$1/manifest" 2> /dev/null
if [ $? != 0 ]
then
echo "Manifest signature failed to verify"
exit 2
fi
TMPFILE=$(mktemp)
while IFS= read -r LINE
do
if [ "${LINE:0:2}" != "F " ]
then
echo "$LINE" >> $TMPFILE
continue
fi
# Split string by spaces
PARTS=($LINE)
FILE_ENCODED="${PARTS[1]}"
FILE="${PARTS[1]//\\s/ }"
HASH="${PARTS[2]}"
if [ "${#HASH}" = 40 ]
then
NEW_HASH=$(sha1sum "$1/$FILE" | awk '{print $1}')
else
NEW_HASH=$(openssl dgst -sha3-256 -binary "$1/$FILE" | xxd -p -c 100)
fi
if [ "$HASH" != "$NEW_HASH" ]
then
echo "Local file has changed"
echo "$FILE"
echo "Manifest hash: $HASH"
echo "Local file hash: $NEW_HASH"
exit 2
fi
PARTS[2]="$HASH"
# join parts in a new string
NEW_LINE="$(printf " %s" "${PARTS[@]}")"
NEW_LINE="${NEW_LINE:1}"
echo "$NEW_LINE" >> $TMPFILE
done < "$1/manifest"
gpg --verify $TMPFILE 2>/dev/null
if [ $? != 0 ]
then
echo "Something has changed between manifest and check?!"
diff "$1/manifest" $TMPFILE
rm -f $TMPFILE
exit 2
fi
rm -f $TMPFILE
exit 0

191
tools/make_installer.php Normal file
View file

@ -0,0 +1,191 @@
<?php
$includes = [
'##KD2' => [
__DIR__ . '/../src/include/lib/KD2/Security.php',
__DIR__ . '/../src/include/lib/KD2/HTTP.php',
__DIR__ . '/../src/include/lib/KD2/FossilInstaller.php',
],
];
$template = <<<'EOF'
<?php
// Copier ce fichier dans un nouveau répertoire vide
// Et s'y rendre avec un navigateur web :-)
namespace KD2 {
##KD2
}
namespace {
const WEBSITE = 'https://fossil.kd2.org/paheko/';
const INSTALL_DIR = __DIR__ . '/.install';
echo '
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<style type="text/css">
body {
font-family: sans-serif;
}
h2, p {
margin: 0;
margin-bottom: 1rem;
}
div {
position: relative;
border: 1px solid #999;
max-width: 500px;
padding: 1em;
border-radius: .5em;
}
.spinner h2::after {
display: block;
content: " ";
margin: 1rem auto;
width: 50px;
height: 50px;
border: 5px solid #000;
border-radius: 50%;
border-top-color: #999;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
</style>';
function exception_error_handler($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) {
return;
}
throw new ErrorException($message, 0, $severity, $file, $line);
}
function mini_exception_handler($e) {
printf('
<div style="padding: 1rem;
background: #fee;
border: 2px solid darkred;"><h2>%s</h2>
<h3>in %s:%d</h3>
<pre>%s</pre>
</div>',
$e->getMessage(), $e->getFile(), $e->getLine(), (string) $e);
}
set_error_handler("exception_error_handler");
set_exception_handler('mini_exception_handler');
if (!version_compare(phpversion(), '7.4', '>=')) {
throw new \Exception('PHP 7.4 ou supérieur requis. PHP version ' . phpversion() . ' installée.');
}
if (!class_exists('SQLite3')) {
throw new \Exception('Le module de base de données SQLite3 n\'est pas disponible.');
}
$v = \SQLite3::version();
if (!version_compare($v['versionString'], '3.16', '>=')) {
throw new \Exception('SQLite3 version 3.16 ou supérieur requise. Version installée : ' . $v['versionString']);
}
$step = $_GET['step'] ?? null;
$error = null;
@mkdir(INSTALL_DIR);
$i = new KD2\FossilInstaller(WEBSITE, __DIR__, INSTALL_DIR, '!^paheko-(.*)\.tar\.gz$!');
if ($step == 'download') {
$latest = $i->latest();
if (!$latest) {
die('</head><h1>Aucune version à télécharger n\'a été trouvée.</h1>');
}
$i->download($latest);
$next = 'install';
}
elseif ($step == 'install') {
$latest = $i->latest();
if (!$latest) {
die('</head><h1>Aucune version à télécharger n\'a été trouvée.</h1>');
}
$i->install($latest);
$i->clean($latest);
if (class_exists('\OCP\AppFramework\Controller')) {
$next = 'nc' . time();
}
else {
$next = null;
}
}
else {
$next = 'download';
}
echo $next ? '<meta http-equiv="refresh" content="0;url=?step='.$next.'" />' : '';
echo '
</head>';
if ($step == 'download') {
echo '
<div class="spinner">
<h2>Décompression en cours…</h2>
</div>';
}
elseif ($step == 'install') {
echo '<div>
<h2>Installation réussie</h2>
<p>Configurez désormais votre sous-domaine pour pointer sur le sous-répertoire <strong>www</strong> de cette installation.</p>
<p><a href="' . WEBSITE . '">Consultez la documentation pour plus d\'infos</a></p>
</div>';
}
else {
echo '
<div class="spinner">
<h2>Téléchargement en cours…</h2>
</div>';
}
echo '
</body>
</html>
';
if ($step == 'install') {
$i->prune(0);
@rmdir(INSTALL_DIR);
@unlink(__FILE__);
}
}
?>
EOF;
foreach ($includes as $tag => $files) {
$source = [];
foreach ($files as $file) {
$content = file_get_contents($file);
$content = preg_replace('!^(?:<\?php|namespace |use ).*$!m', '', $content);
$content = preg_replace("!^!m", "\t$0", $content);
$content = preg_replace("!^\t$!m", '', $content);
$content = trim($content);
$source[] = $content;
}
$source = implode("\n\n", $source);
$template = str_replace($tag, $source, $template);
}
echo $template;

View file

@ -0,0 +1,98 @@
<?php
if (empty($argv[1]))
{
echo sprintf('Usage: %s PLUGIN_DIRECTORY' . PHP_EOL, $argv[0]);
exit(1);
}
assert_options(ASSERT_ACTIVE, true);
assert_options(ASSERT_BAIL, true);
$dir = rtrim($argv[1], '/\\') . DIRECTORY_SEPARATOR;
assert(is_readable($dir), 'Le répertoire du plugin n\'est pas lisible');
assert(is_dir($dir), sprintf('%s n\'est pas un répertoire', $dir));
assert(file_exists($dir . 'garradin_plugin.ini'), sprintf('%s n\'est pas un répertoire de plugin Garradin', $dir));
$dir_iterator = new RecursiveDirectoryIterator(substr($dir, 0, -1), FilesystemIterator::SKIP_DOTS);
$iterator = new RecursiveIteratorIterator($dir_iterator, RecursiveIteratorIterator::SELF_FIRST);
https://fossil.kd2.org/garradin-plugins/timeline
$base = realpath($dir);
foreach ($iterator as $file)
{
if ($file->isDir())
{
// Skip directories
continue;
}
$source = str_replace($base, '', $file->getRealPath());
if ($file->getExtension() == 'php')
{
check_php($file->getRealPath(), $source);
}
elseif ($file->getExtension() == 'tpl')
{
check_smarty($file->getRealPath(), $source);
}
}
function check_php($file, $source)
{
static $deprecated_php_functions = [
'->simpleQuerySingle',
'->queryFetchAssocKey',
'->queryFetchAssoc',
'->queryFetch',
'->simpleStatementFetchAssocKey',
'->simpleStatementFetchAssoc',
'->simpleStatementFetch',
'->simpleStatement',
'->escapeString',
'->simpleExec',
'->simpleUpdate',
'->simpleInsert',
'utils::get(',
'utils::post(',
'utils::CRSF',
];
$content = file_get_contents($file);
foreach ($deprecated_php_functions as $func)
{
if (stripos($content, $func) !== false)
{
fputs(STDERR, sprintf('ERROR: %s: la fonction %s a été supprimée de Garradin.', $source, $func) . PHP_EOL);
}
}
}
function check_smarty($file, $source)
{
$content = file_get_contents($file);
if (preg_match('/\{.*`.*\}/sU', $content))
{
fputs(STDERR, sprintf('ERROR: %s: la syntaxe `$variable` est invalide dans Smartyer, utiliser le modifieur |args:$variable', $source) . PHP_EOL);
}
if (preg_match('/\{\s*(section|php|switch|insert|capture)/', $content, $match))
{
fputs(STDERR, sprintf('ERROR: %s: le bloc "%s" est absent dans Smartyer', $source, $match[1]) . PHP_EOL);
}
if (preg_match('/(\$smarty\.|\$tpl\.|\$templatelite\.)/', $content, $match))
{
fputs(STDERR, sprintf('ERROR: %s: les variables "%s" sont absentes dans Smartyer', $source, $match[1]) . PHP_EOL);
}
if (preg_match('/\{.*\|escape/sU', $content))
{
fputs(STDERR, sprintf('SUGGESTION: %s: le modifieur |escape n\'est plus nécessaire (escaping automatique)', $source) . PHP_EOL);
}
}