Overview
Comment:Merge with trunk
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA1: ab8bff586d57139d9a6fff63673cd00a9bcea5c2
User & Date: bohwaz on 2020-12-10 18:21:10
Other Links: branch diff | manifest | tags
Context
2020-12-10
20:01
Migrate more from Fichiers to File and Files check-in: 332a8b6494 user: bohwaz tags: dev
18:21
Merge with trunk check-in: ab8bff586d user: bohwaz tags: dev
18:17
New release check-in: 031185e587 user: bohwaz tags: trunk, stable, 1.0.0-rc12
2020-12-07
01:17
First steps of file management implementation check-in: 1d45c38659 user: bohwaz tags: dev
Changes

Added doc/index.md version [0009160b56].



























































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# Garradin, le gestionnaire d'association libre et simple

<nav id="gnav">

* [Guides d'installation](/wiki/?name=Installation)
* [Documentation](/wiki/?name=Documentation)
* <a href="https://garradin.eu/" target="_blank">Essayer gratuitement</a>
* [Entraide](/wiki/?name=Entraide)

<ul id="download">
</ul>

</nav>

<p id="give"><a href="http://kd2.org/asso/soutien/" target="_blank">Soutenir Garradin en effectuant un don :-)</a></p>


<script type="text/javascript">
document.head.innerHTML += `<style type="text/css">
#give {
	text-align: center;
	padding: 1em;
}

#give a {
	display: inline-block;
	padding: .5em;
	padding-left: 70px;
	border-radius: .5em;
	font-size: 1.5em;
	background: #ffc url("https://kd2.org/asso/soutien/coins.png") no-repeat .5em .5em;
	border: 2px solid #990;
}

#gnav ul {
	display: flex;
	padding: 0;
	margin: 1em;
	margin-bottom: 1em;
	font-size: 1.2em;
	list-style: none;
	justify-content: center;
	align-items: center;
}

#gnav li {
	margin: 0;
	padding: 0;
	font-size: 1.2em;
	margin: .5em;
	text-align: center;
}

#gnav li a {
	height: 100%;
	padding: .5rem;
	background: #ddf;
	color: black;
	display: flex;
	align-items: center;
	justify-content: center;
	border-radius: .5em;
	border: 2px solid #99f;
	text-decoration: none;
}

#gnav li strong, #gnav li em {
	height: 100%;
	padding: .5rem;
	display: block;
}

#gnav li a:hover {
	text-decoration: underline;
	opacity: 0.7;
}

#download li {
	font-size: 1em;
}

#download li a {
	border-color: #060;
	background: #dfd;
}
`;

function isNewerVersion (oldVer, newVer) {
	const oldParts = oldVer.split('.')
	const newParts = newVer.split('.')
	for (var i = 0; i < newParts.length; i++) {
		const a = ~~newParts[i] // parse int
		const b = ~~oldParts[i] // parse int
		if (a > b) return true
		if (a < b) return false
	}
	return false
}

fetch('/garradin/juvlist').then((r) => {
	r.json().then((list) => {
		let last;
		let selected;

		list.forEach((file) => {
			var v = file.name.match(/^garradin-(.*)\.tar\.bz2/);

			if (!v) {
				return;
			}

			if (!last || isNewerVersion(last, v[1])) {
				last = v[1];
				selected = file;
			}
		});

		let days = ((+new Date)/1000 - selected.mtime) / 3600 / 24;

		if (days < 31) {
			time = Math.ceil(days) + ' jours';
		}
		else if (days >= 31) {
			time = Math.round(days / 30.5) + ' mois';
		}

		document.querySelector('#download').innerHTML += `<li><strong>Dernière version : ${last}</strong></li>
			<li><em>il y a ${time}</em></li>
			<li><a href="$ROOT/wiki/?name=Changelog">Nouveautés</a></li>
			<li><a href="$ROOT/uv/${selected.name}">Télécharger</a></li>`;
	});
});
</script>

## C'est quoi ?

<a href="$ROOT/raw/7bb068963b9f6301b27b81fe925caae9e86a229b?m=image/png" target="_blank" style="float: right; margin: 1em;"><img src="/garradin/raw/7bb068963b9f6301b27b81fe925caae9e86a229b?m=image/png" alt="Liste des membres" width="400" /></a>

Garradin est un logiciel de gestion d'association (loi 1901 / ASBL / etc.). Son but est de permettre :

*  la gestion des __adhérent⋅e⋅s__ : ajout, modification, suppression, possibilité de choisir les informations présentes sur les fiches adhérent, envoi de mails collectifs aux adhérent⋅e⋅s
*  la tenue de la __comptabilité__ : avoir une gestion comptable complète à même de satisfaire un expert-comptable tout en restant à la portée de celles et ceux qui ne savent pas ce qu'est la comptabilité à double entrée, permettre la production des rapports et bilans annuels et de suivre au jour le jour le budget de l'association
*  la gestion des __cotisations__ et __activités__ : suivi des cotisations à jour, inscriptions et paiement des activités, rappels automatiques par e-mail, etc.
*  le travail __collaboratif__ et __collectif__ : gestion fine des droits d'accès aux fonctions, échange de mails entre membres…
*  la __simplification administrative__ : prise de notes en réunion, archivage et partage de fichiers (afin d'éliminer le besoin d'archiver les documents papier), etc.
*  la publication d'un __site web__ pour l'association, simple mais suffisamment flexible pour pouvoir adapter le fonctionnement à la plupart des besoins
*  l'__autonomisation des adhérents__ : possibilité de mettre à jour leurs informations par eux-même, ou de s'inscrire seul depuis un ordinateur ou un smartphone
*  la possibilité d'adapter aux besoins spécifiques de chaque association via des __extensions__.

Tous ces objectifs ne sont pas encore réalisés, voir :

* [la liste des fonctionnalités disponibles](/wiki/?name=Fonctionnalités) pour ce qui est actuellement disponible ;
* [la feuille de route](/wiki/?name=Roadmap) pour la liste des fonctionnalités qu'il reste à implémenter.

Garradin est un logiciel libre disponible sous licence [AGPL v3](https://www.gnu.org/licenses/why-affero-gpl.fr.html).

Garradin signifie *argent* en *Wagiman*, un dialecte aborigène du nord de l'Australie.

## Documentation et entraide

*  D'abord lire la [documentation](/wiki/?name=Documentation) et notamment la [foire aux questions](/wiki/?name=FAQ)
*  La [liste de discussion d'entraide entre utilisateurs](https://admin.kd2.org/lists/aide@garradin.eu) est le meilleur moyen de vous faire aider :)
*  [Chat d'entraide en direct](https://kiwiirc.com/nextclient/#irc://irc.freenode.net/#garradin?nick=garradin%7C?), ou via IRC : salon `#garradin` sur `irc.freenode.net`

## Participer

Tout coup de main est le bienvenu, pas besoin d'avoir des connaissances techniques ! Nous avons un [guide de contribution](/wiki/?name=Contribuer) pour vous aider à voir comment vous pouvez participer à Garradin :)

### Développement

Garradin est un logiciel libre, développé en PHP, utilisant la base de données SQLite, et avec une interface utilisant HTML, CSS et un peu de Javascript.

Nous acceptons les contributions (plugins, patch, code, tickets, etc.) avec plaisir, consultez la [documentation développeur⋅euse](/wiki/?name=Documentation développeur) pour découvrir comment vous pouvez contribuer.

Modified src/VERSION from [4225b16d8a] to [4c3ec47899].

1
1.0.0-rc8
|
1
1.0.0-rc12

Added src/include/data/1.0.0-rc10_migration.sql version [ab6262425c].









>
>
>
>
1
2
3
4
UPDATE acc_accounts SET type = 8, position = 4 WHERE id_chart = (SELECT id FROM acc_charts WHERE code IS NOT NULL) AND (code LIKE '86_%');
UPDATE acc_accounts SET type = 8, position = 5 WHERE id_chart = (SELECT id FROM acc_charts WHERE code IS NOT NULL) AND (code LIKE '87_%');

UPDATE acc_accounts SET position = 3 WHERE code IN ('5112', '5115', '530') AND code IS NOT NULL;

Modified src/include/data/1.0.0_schema.sql from [2cb870482b] to [4710567bc0].

228
229
230
231
232
233
234




235
236
237
238
239
240
241
    start_date TEXT NOT NULL CHECK (date(start_date) IS NOT NULL AND date(start_date) = start_date),
    end_date TEXT NOT NULL CHECK (date(end_date) IS NOT NULL AND date(end_date) = end_date),

    closed INTEGER NOT NULL DEFAULT 0,

    id_chart INTEGER NOT NULL REFERENCES acc_charts (id)
);





CREATE INDEX IF NOT EXISTS acc_years_closed ON acc_years (closed);

CREATE TABLE IF NOT EXISTS acc_transactions
-- Opérations comptables
(
    id INTEGER PRIMARY KEY NOT NULL,







>
>
>
>







228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
    start_date TEXT NOT NULL CHECK (date(start_date) IS NOT NULL AND date(start_date) = start_date),
    end_date TEXT NOT NULL CHECK (date(end_date) IS NOT NULL AND date(end_date) = end_date),

    closed INTEGER NOT NULL DEFAULT 0,

    id_chart INTEGER NOT NULL REFERENCES acc_charts (id)
);

CREATE TRIGGER IF NOT EXISTS acc_years_delete BEFORE DELETE ON acc_years BEGIN
    UPDATE services_fees SET id_account = NULL, id_year = NULL WHERE id_year = OLD.id;
END;

CREATE INDEX IF NOT EXISTS acc_years_closed ON acc_years (closed);

CREATE TABLE IF NOT EXISTS acc_transactions
-- Opérations comptables
(
    id INTEGER PRIMARY KEY NOT NULL,

Modified src/include/data/charts/fr_1999.csv from [38d9f67e8d] to [4db158f82a].

173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
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,
5,Classe 5 — Comptes financiers,,Actif,
50,VALEURS MOBILIÈRES DE PLACEMENT,,Actif,
51,"BANQUES, ÉTABLISSEMENTS FINANCIERS ET ASSIMILÉS",,Actif,
511,Valeurs à l'encaissement,,Actif,
5112,Chèques à encaisser,,Actif,Attente d'encaissement
5115,Paiements par carte à encaisser,,Actif,
512,Banques,,Actif ou passif,
53,CAISSE,,Actif,
530,Caisse,,Actif,Caisse
54,RÉGIES D'AVANCES ET ACCRÉDITIFS,,Actif,
58,VIREMENTS INTERNES,,Actif,
59,PROVISIONS POUR DÉPRÉCIATION DES COMPTES FINANCIERS,,Actif,
6,Classe 6 — Comptes de charges,,Charge,
60,ACHATS,,Charge,
601,Achats stockés - Matières premières et fournitures,,Charge,
602,Achats stockés - Autres approvisionnements,,Charge,







|
|


|







173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
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,
5,Classe 5 — Comptes financiers,,Actif,
50,VALEURS MOBILIÈRES DE PLACEMENT,,Actif,
51,"BANQUES, ÉTABLISSEMENTS FINANCIERS ET ASSIMILÉS",,Actif,
511,Valeurs à l'encaissement,,Actif,
5112,Chèques à encaisser,,Actif ou passif,Attente d'encaissement
5115,Paiements par carte à encaisser,,Actif ou passif,
512,Banques,,Actif ou passif,
53,CAISSE,,Actif,
530,Caisse,,Actif ou passif,Caisse
54,RÉGIES D'AVANCES ET ACCRÉDITIFS,,Actif,
58,VIREMENTS INTERNES,,Actif,
59,PROVISIONS POUR DÉPRÉCIATION DES COMPTES FINANCIERS,,Actif,
6,Classe 6 — Comptes de charges,,Charge,
60,ACHATS,,Charge,
601,Achats stockés - Matières premières et fournitures,,Charge,
602,Achats stockés - Autres approvisionnements,,Charge,
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
78,REPRISES SUR AMORTISSEMENTS ET PROVISIONS,,Produit,
79,TRANSFERT DE CHARGES,,Produit,
791,Transferts de charges d'exploitation,,Produit,
796,Transferts de charges financières,,Produit,
797,Transferts de charges exceptionnels,,Produit,
8,Classe 8 ­— Comptes spéciaux,,,
86,RÉPARTITION PAR NATURE DE CHARGES,,Charge,
861,Mise à dispositions gratuites de biens,,Charge,
862,Prestations,,Charge,
864,Personnel bénévole,,Charge,
87,RÉPARTITION PAR NATURE DE RESSOURCES,,Produit,
870,Bénévolat,,Produit,
871,Prestations en nature,,Produit,
875,Dons en nature,,Produit,
89,BILAN,,,
890,Bilan d'ouverture,,,Ouverture
891,Bilan de clôture,,,Clôture
9,Classe 9 — Comptes analytiques,,,
99,Projets,,,Analytique







|
|
|

|
|
|





279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
78,REPRISES SUR AMORTISSEMENTS ET PROVISIONS,,Produit,
79,TRANSFERT DE CHARGES,,Produit,
791,Transferts de charges d'exploitation,,Produit,
796,Transferts de charges financières,,Produit,
797,Transferts de charges exceptionnels,,Produit,
8,Classe 8 ­— Comptes spéciaux,,,
86,RÉPARTITION PAR NATURE DE CHARGES,,Charge,
861,Mise à dispositions gratuites de biens,,Charge,Bénévolat
862,Prestations,,Charge,Bénévolat
864,Personnel bénévole,,Charge,Bénévolat
87,RÉPARTITION PAR NATURE DE RESSOURCES,,Produit,
870,Bénévolat,,Produit,Bénévolat
871,Prestations en nature,,Produit,Bénévolat
875,Dons en nature,,Produit,Bénévolat
89,BILAN,,,
890,Bilan d'ouverture,,,Ouverture
891,Bilan de clôture,,,Clôture
9,Classe 9 — Comptes analytiques,,,
99,Projets,,,Analytique

Modified src/include/data/charts/fr_2018.csv from [7724166e91] to [387ccd52c1].

225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
496,Prov. pour dépreci. cptes débiteurs divers,,Passif,
5,Classe 5 — Comptes financiers,,Actif,
50,VALEURS MOBILIERES DE PLACEMENT,,Actif,
503,Actions,,Actif,
506,Obligations,,Actif,
508,Autres VMP et créances assimilées,,Actif,
51,"BANQUES, ETABLISSEMENTS FINANCIERS",,Actif,
5112,Chèques à encaisser,,Actif,Attente d'encaissement
5115,Paiements par carte à encaisser,,Actif,
512,Banques,,Actif ou passif,
5186,Intérêts courus à payer,,Passif,
5187,Intérêts courus à recevoir,,Actif,
53,Caisses,,Actif ou passif,
530,Caisse,,Actif,Caisse
58,VIREMENTS INTERNES,,Actif ou passif,
580,Virements internes,,Actif ou passif,
59,DEPRECIATIONS DES COMPTES FINANCIERS,,Actif ou passif,
5903,Actions,,Actif ou passif,
5906,Obligations,,Actif ou passif,
5908,Autres VMP et créances assimilées,,Actif ou passif,
6,Classe 6 — Comptes de charges,,Charge,







|
|




|







225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
496,Prov. pour dépreci. cptes débiteurs divers,,Passif,
5,Classe 5 — Comptes financiers,,Actif,
50,VALEURS MOBILIERES DE PLACEMENT,,Actif,
503,Actions,,Actif,
506,Obligations,,Actif,
508,Autres VMP et créances assimilées,,Actif,
51,"BANQUES, ETABLISSEMENTS FINANCIERS",,Actif,
5112,Chèques à encaisser,,Actif ou passif,Attente d'encaissement
5115,Paiements par carte à encaisser,,Actif ou passif,
512,Banques,,Actif ou passif,
5186,Intérêts courus à payer,,Passif,
5187,Intérêts courus à recevoir,,Actif,
53,Caisses,,Actif ou passif,
530,Caisse,,Actif ou passif,Caisse
58,VIREMENTS INTERNES,,Actif ou passif,
580,Virements internes,,Actif ou passif,
59,DEPRECIATIONS DES COMPTES FINANCIERS,,Actif ou passif,
5903,Actions,,Actif ou passif,
5906,Obligations,,Actif ou passif,
5908,Autres VMP et créances assimilées,,Actif ou passif,
6,Classe 6 — Comptes de charges,,Charge,
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
7896,Utilisations des fds dédiés / générosité,,Produit,
79,TRANSFERT DE CHARGES,,Produit,
791,Transferts de charges d'exploitation,,Produit,
796,Transferts de charges financières,,Produit,
797,Transferts de charges exceptionnelles,,Produit,
8,Classe 8 ­— Comptes spéciaux,,,
86,EMPLOIS CONTRIBUTIONS VOLONTAIRES EN NATURE,,,
860,Secours en nature,,,
8601,Alimentaires,,,
8602,Vestimentaires,,,
861,Mise à dispositions gratuites de biens,,,
8611,Locaux,,,
8612,Matériels,,,
862,Prestations,,,
864,Personnel bénévole,,,
87,CONTRIBUTIONS VOLONTAIRES EN NATURE,,,
870,Dons en nature,,,
871,Prestations en nature,,,
875,Bénévolat,,,Bénévolat
89,COMPTES DE BILAN,,,
890,Bilan d'ouverture,,,Ouverture
891,Bilan de clôture,,,Clôture
9,Classe 9 — Comptes analytiques,,,
90,COMPTES RÉFLÉCHIS,,,
906,Charges réfléchies,,,
907,Produits réfléchis,,,
99,Projets,,,Analytique







|
|
|
|
|
|
|
|

|
|
|








526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
7896,Utilisations des fds dédiés / générosité,,Produit,
79,TRANSFERT DE CHARGES,,Produit,
791,Transferts de charges d'exploitation,,Produit,
796,Transferts de charges financières,,Produit,
797,Transferts de charges exceptionnelles,,Produit,
8,Classe 8 ­— Comptes spéciaux,,,
86,EMPLOIS CONTRIBUTIONS VOLONTAIRES EN NATURE,,,
860,Secours en nature,,Charge,Bénévolat
8601,Alimentaires,,Charge,Bénévolat
8602,Vestimentaires,,Charge,Bénévolat
861,Mise à dispositions gratuites de biens,,Charge,Bénévolat
8611,Locaux,,Charge,Bénévolat
8612,Matériels,,Charge,Bénévolat
862,Prestations,,Charge,Bénévolat
864,Personnel bénévole,,Charge,Bénévolat
87,CONTRIBUTIONS VOLONTAIRES EN NATURE,,,
870,Dons en nature,,Produit,Bénévolat
871,Prestations en nature,,Produit,Bénévolat
875,Bénévolat,,Produit,Bénévolat
89,COMPTES DE BILAN,,,
890,Bilan d'ouverture,,,Ouverture
891,Bilan de clôture,,,Clôture
9,Classe 9 — Comptes analytiques,,,
90,COMPTES RÉFLÉCHIS,,,
906,Charges réfléchies,,,
907,Produits réfléchis,,,
99,Projets,,,Analytique

Modified src/include/lib/Garradin/Accounting/Accounts.php from [104aa964ca] to [36e8a3ddb7].

41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

	/**
	 * Return common accounting accounts from current chart
	 * (will not return analytical and volunteering accounts)
	 */
	public function listCommonTypes(): array
	{
		return $this->em->all('SELECT * FROM @TABLE WHERE id_chart = ? AND type != 0 AND type NOT IN (?, ?) ORDER BY code COLLATE NOCASE;',
			$this->chart_id, Account::TYPE_ANALYTICAL, Account::TYPE_VOLUNTEERING);
	}

	/**
	 * Return all accounts from current chart
	 */
	public function listAll(): array
	{







|
|







41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

	/**
	 * Return common accounting accounts from current chart
	 * (will not return analytical and volunteering accounts)
	 */
	public function listCommonTypes(): array
	{
		return $this->em->all('SELECT * FROM @TABLE WHERE id_chart = ? AND type != 0 AND type NOT IN (?) ORDER BY code COLLATE NOCASE;',
			$this->chart_id, Account::TYPE_ANALYTICAL);
	}

	/**
	 * Return all accounts from current chart
	 */
	public function listAll(): array
	{

Modified src/include/lib/Garradin/Accounting/Graph.php from [a1b33675d1] to [01efa75756].

1
2
3
4
5
6
7
8
9
10

11
12
13
14
15
16
17
<?php

namespace Garradin\Accounting;

use Garradin\Entities\Accounting\Account;
use Garradin\Entities\Accounting\Line;
use Garradin\Entities\Accounting\Transaction;
use Garradin\Utils;
use Garradin\Config;
use Garradin\DB;

use const Garradin\ADMIN_COLOR1;
use const Garradin\ADMIN_COLOR2;
use const Garradin\ADMIN_URL;
use KD2\DB\EntityManager;

use KD2\Graphics\SVG\Plot;
use KD2\Graphics\SVG\Plot_Data;










>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

namespace Garradin\Accounting;

use Garradin\Entities\Accounting\Account;
use Garradin\Entities\Accounting\Line;
use Garradin\Entities\Accounting\Transaction;
use Garradin\Utils;
use Garradin\Config;
use Garradin\DB;
use Garradin\Static_Cache;
use const Garradin\ADMIN_COLOR1;
use const Garradin\ADMIN_COLOR2;
use const Garradin\ADMIN_URL;
use KD2\DB\EntityManager;

use KD2\Graphics\SVG\Plot;
use KD2\Graphics\SVG\Plot_Data;
56
57
58
59
60
61
62






63
64
65
66
67
68
69
	const MONTHLY_INTERVAL = 2635200; // 1 month

	static public function plot(string $type, array $criterias, int $interval = self::WEEKLY_INTERVAL, int $width = 700)
	{
		if (!array_key_exists($type, self::PLOT_TYPES)) {
			throw new \InvalidArgumentException('Unknown type');
		}







		$plot = new Plot($width, 300);

		$lines = self::PLOT_TYPES[$type];
		$data = [];

		foreach ($lines as $label => $line_criterias) {







>
>
>
>
>
>







57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
	const MONTHLY_INTERVAL = 2635200; // 1 month

	static public function plot(string $type, array $criterias, int $interval = self::WEEKLY_INTERVAL, int $width = 700)
	{
		if (!array_key_exists($type, self::PLOT_TYPES)) {
			throw new \InvalidArgumentException('Unknown type');
		}

		$cache_id = sha1(json_encode(func_get_args()));

		if (!Static_Cache::expired($cache_id)) {
			return Static_Cache::get($cache_id);
		}

		$plot = new Plot($width, 300);

		$lines = self::PLOT_TYPES[$type];
		$data = [];

		foreach ($lines as $label => $line_criterias) {
109
110
111
112
113
114
115
116




117
118
119
120
121
122
123






124
125
126
127
128
129
130
				$plot->add($line);

				if ($i >= count($colors))
					$i = 0;
			}
		}

		return $plot->output();




	}

	static public function pie(string $type, array $criterias)
	{
		if (!array_key_exists($type, self::PIE_TYPES)) {
			throw new \InvalidArgumentException('Unknown type');
		}







		$pie = new Pie(700, 300);

		$pie_criterias = self::PIE_TYPES[$type];
		$data = Reports::getClosingSumsWithAccounts(array_merge($criterias, $pie_criterias), 'ABS(sum) DESC');

		$others = 0;







|
>
>
>
>







>
>
>
>
>
>







116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
				$plot->add($line);

				if ($i >= count($colors))
					$i = 0;
			}
		}

		$out = $plot->output();

		Static_Cache::store($cache_id, $out);

		return $out;
	}

	static public function pie(string $type, array $criterias)
	{
		if (!array_key_exists($type, self::PIE_TYPES)) {
			throw new \InvalidArgumentException('Unknown type');
		}

		$cache_id = sha1(json_encode(func_get_args()));

		if (!Static_Cache::expired($cache_id)) {
			return Static_Cache::get($cache_id);
		}

		$pie = new Pie(700, 300);

		$pie_criterias = self::PIE_TYPES[$type];
		$data = Reports::getClosingSumsWithAccounts(array_merge($criterias, $pie_criterias), 'ABS(sum) DESC');

		$others = 0;
157
158
159
160
161
162
163
164




165
166
167
168
169
170
171
		if ($others != 0)
		{
			$pie->add(new Pie_Data(abs($others) / 100, 'Autres', '#ccc'));
		}

		$pie->togglePercentage(true);

		return $pie->output();




	}

	static protected function getColors()
	{
		$config = Config::getInstance();
		$c1 = $config->get('couleur1') ?: ADMIN_COLOR1;
		$c2 = $config->get('couleur2') ?: ADMIN_COLOR2;







|
>
>
>
>







174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
		if ($others != 0)
		{
			$pie->add(new Pie_Data(abs($others) / 100, 'Autres', '#ccc'));
		}

		$pie->togglePercentage(true);

		$out = $pie->output();

		Static_Cache::store($cache_id, $out);

		return $out;
	}

	static protected function getColors()
	{
		$config = Config::getInstance();
		$c1 = $config->get('couleur1') ?: ADMIN_COLOR1;
		$c2 = $config->get('couleur2') ?: ADMIN_COLOR2;

Modified src/include/lib/Garradin/Accounting/Reports.php from [bd1f8f1a69] to [657f1c3461].

26
27
28
29
30
31
32
33
34
35
36





37
38
39
40
41
42
43

		if (!empty($criterias['exclude_position'])) {
			$db = DB::getInstance();
			$where[] = $db->where('position', 'NOT IN', $criterias['exclude_position']);
		}

		if (!empty($criterias['type'])) {
			$db = DB::getInstance();
			$criterias['type'] = array_map('intval', (array)$criterias['type']);
			$where[] = sprintf('a.type IN (%s)', implode(',', $criterias['type']));
		}






		if (!empty($criterias['user'])) {
			$where[] = sprintf('t.id IN (SELECT id_transaction FROM acc_transactions_users WHERE id_user = %d)', $criterias['user']);
		}

		if (!empty($criterias['creator'])) {
			$where[] = sprintf('t.id_creator = %d', $criterias['creator']);







<



>
>
>
>
>







26
27
28
29
30
31
32

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

		if (!empty($criterias['exclude_position'])) {
			$db = DB::getInstance();
			$where[] = $db->where('position', 'NOT IN', $criterias['exclude_position']);
		}

		if (!empty($criterias['type'])) {

			$criterias['type'] = array_map('intval', (array)$criterias['type']);
			$where[] = sprintf('a.type IN (%s)', implode(',', $criterias['type']));
		}

		if (!empty($criterias['exclude_type'])) {
			$criterias['exclude_type'] = array_map('intval', (array)$criterias['exclude_type']);
			$where[] = sprintf('a.type NOT IN (%s)', implode(',', $criterias['exclude_type']));
		}

		if (!empty($criterias['user'])) {
			$where[] = sprintf('t.id IN (SELECT id_transaction FROM acc_transactions_users WHERE id_user = %d)', $criterias['user']);
		}

		if (!empty($criterias['creator'])) {
			$where[] = sprintf('t.id_creator = %d', $criterias['creator']);
60
61
62
63
64
65
66

67
68
69
70
71
72
73
74

	/**
	 * Return account sums per year or per account
	 * @param  bool $order_year If true will return accounts grouped by year, if false it will return years grouped by account
	 */
	static public function getAnalyticalSums(bool $by_year = false): \Generator
	{

		$sql = 'SELECT a.label AS account_label, a.id AS id_account, y.id AS id_year, y.label AS year_label, y.start_date, y.end_date,
			SUM(l.credit - l.debit) AS sum, SUM(l.credit) AS credit, SUM(l.debit) AS debit
			FROM acc_transactions_lines l
			INNER JOIN acc_transactions t ON t.id = l.id_transaction
			INNER JOIN acc_accounts a ON a.id = l.id_analytical
			INNER JOIN acc_years y ON y.id = t.id_year
			GROUP BY %s
			ORDER BY %s;';







>
|







64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

	/**
	 * Return account sums per year or per account
	 * @param  bool $order_year If true will return accounts grouped by year, if false it will return years grouped by account
	 */
	static public function getAnalyticalSums(bool $by_year = false): \Generator
	{
		$sql = 'SELECT a.label AS account_label, a.description AS account_description, a.id AS id_account,
			y.id AS id_year, y.label AS year_label, y.start_date, y.end_date,
			SUM(l.credit - l.debit) AS sum, SUM(l.credit) AS credit, SUM(l.debit) AS debit
			FROM acc_transactions_lines l
			INNER JOIN acc_transactions t ON t.id = l.id_transaction
			INNER JOIN acc_accounts a ON a.id = l.id_analytical
			INNER JOIN acc_years y ON y.id = t.id_year
			GROUP BY %s
			ORDER BY %s;';
103
104
105
106
107
108
109

110
111
112
113
114
115
116
				$current = null;
			}

			if (null === $current) {
				$current = (object) [
					'id' => $by_year ? $row->id_year : $row->id_account,
					'label' => $by_year ? $row->year_label : $row->account_label,

					'credit' => 0,
					'debit' => 0,
					'sum' => 0,
					'items' => []
				];
			}








>







108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
				$current = null;
			}

			if (null === $current) {
				$current = (object) [
					'id' => $by_year ? $row->id_year : $row->id_account,
					'label' => $by_year ? $row->year_label : $row->account_label,
					'description' => !$by_year ? $row->account_description : null,
					'credit' => 0,
					'debit' => 0,
					'sum' => 0,
					'items' => []
				];
			}

440
441
442
443
444
445
446
447























		if (null === $transaction) {
			return;
		}

		yield $transaction;
	}
}





























|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475

		if (null === $transaction) {
			return;
		}

		yield $transaction;
	}

	static public function getStatement(array $criterias): array
	{
		$revenue = Reports::getClosingSumsWithAccounts($criterias + ['position' => Account::REVENUE]);
		$expense = Reports::getClosingSumsWithAccounts($criterias + ['position' => Account::EXPENSE], null, true);

		$get_sum = function (array $in): int {
			$sum = 0;

			foreach ($in as $row) {
				$sum += $row->sum;
			}

			return abs($sum);
		};

		$revenue_sum = $get_sum($revenue);
		$expense_sum = $get_sum($expense);
		$result = $revenue_sum - $expense_sum;

		return compact('revenue', 'expense', 'revenue_sum', 'expense_sum', 'result');
	}
}

Modified src/include/lib/Garradin/Accounting/Transactions.php from [6561462819] to [4a62ce7a06].

142
143
144
145
146
147
148

149
150
151
152
153
154
155
156
157
158
159
160
161
						$status[] = $v;
					}
				}

				$row->status = implode(', ', $status);
				$row->date = \DateTime::createFromFormat('Y-m-d', $row->date);
				$row->date = $row->date->format('d/m/Y');

			}

			$row->credit = Utils::money_format($row->credit, ',', '');
			$row->debit = Utils::money_format($row->debit, ',', '');

			$previous_id = $row->id;
			yield $row;
		}
	}

	static public function importCSV(Year $year, array $file, int $user_id)
	{
		if ($year->closed) {







>





<







142
143
144
145
146
147
148
149
150
151
152
153
154

155
156
157
158
159
160
161
						$status[] = $v;
					}
				}

				$row->status = implode(', ', $status);
				$row->date = \DateTime::createFromFormat('Y-m-d', $row->date);
				$row->date = $row->date->format('d/m/Y');
				$previous_id = $row->id;
			}

			$row->credit = Utils::money_format($row->credit, ',', '');
			$row->debit = Utils::money_format($row->debit, ',', '');


			yield $row;
		}
	}

	static public function importCSV(Year $year, array $file, int $user_id)
	{
		if ($year->closed) {
187
188
189
190
191
192
193
194




195
196
197
198
199
200
201
202
203
204
205
						throw new UserException('cette ligne n\'est reliée à aucune écriture');
					}

					if ($row->id) {
						$transaction = self::get((int)$row->id);

						if (!$transaction) {
							throw new UserException(sprintf('l\'écriture %d est introuvable', $row->id));




						}

						if ($transaction->validated) {
							throw new UserException(sprintf('l\'écriture %d est validée et ne peut être modifiée', $row->id));
						}
					}
					else {
						$transaction = new Transaction;
						$transaction->id_creator = $user_id;
						$transaction->id_year = $year->id();
					}







|
>
>
>
>



|







187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
						throw new UserException('cette ligne n\'est reliée à aucune écriture');
					}

					if ($row->id) {
						$transaction = self::get((int)$row->id);

						if (!$transaction) {
							throw new UserException(sprintf('l\'écriture #%d est introuvable', $row->id));
						}

						if (!$transaction->id_year != $year->id()) {
							throw new UserException(sprintf('l\'écriture #%d appartient à un autre exercice', $row->id));
						}

						if ($transaction->validated) {
							throw new UserException(sprintf('l\'écriture #%d est validée et ne peut être modifiée', $row->id));
						}
					}
					else {
						$transaction = new Transaction;
						$transaction->id_creator = $user_id;
						$transaction->id_year = $year->id();
					}
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
			LEFT JOIN acc_accounts b ON b.id = l.id_analytical';
		$conditions = sprintf('t.type = %s AND t.id_year = %d', $type, $year_id);

		$sum = 0;

		$list = new DynamicList($columns, $tables, $conditions);
		$list->orderBy('date', true);
		$list->setCount('COUNT(*)');
		$list->groupBy('t.id');
		$list->setModifier(function (&$row) use (&$sum, $reverse) {
			$row->date = \DateTime::createFromFormat('!Y-m-d', $row->date);
		});
		$list->setExportCallback(function (&$row) {
			$row->change = Utils::money_format($row->change, '.', '', false);
		});

		return $list;
	}
}







|











389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
			LEFT JOIN acc_accounts b ON b.id = l.id_analytical';
		$conditions = sprintf('t.type = %s AND t.id_year = %d', $type, $year_id);

		$sum = 0;

		$list = new DynamicList($columns, $tables, $conditions);
		$list->orderBy('date', true);
		$list->setCount('COUNT(DISTINCT t.id)');
		$list->groupBy('t.id');
		$list->setModifier(function (&$row) use (&$sum, $reverse) {
			$row->date = \DateTime::createFromFormat('!Y-m-d', $row->date);
		});
		$list->setExportCallback(function (&$row) {
			$row->change = Utils::money_format($row->change, '.', '', false);
		});

		return $list;
	}
}

Modified src/include/lib/Garradin/Accounting/Years.php from [31fc8b31a2] to [adce096083].

14
15
16
17
18
19
20





21
22
23
24
25
26
27
		return EntityManager::findOneById(Year::class, $year_id);
	}

	static public function getCurrentOpenYear()
	{
		return EntityManager::findOne(Year::class, 'SELECT * FROM @TABLE WHERE closed = 0 ORDER BY start_date LIMIT 1;');
	}






	static public function listOpen()
	{
		$db = EntityManager::getInstance(Year::class)->DB();
		return $db->get('SELECT *, (SELECT COUNT(*) FROM acc_transactions WHERE id_year = acc_years.id) AS nb_transactions
			FROM acc_years WHERE closed = 0 ORDER BY end_date;');
	}







>
>
>
>
>







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
		return EntityManager::findOneById(Year::class, $year_id);
	}

	static public function getCurrentOpenYear()
	{
		return EntityManager::findOne(Year::class, 'SELECT * FROM @TABLE WHERE closed = 0 ORDER BY start_date LIMIT 1;');
	}

	static public function getCurrentOpenYearId()
	{
		return EntityManager::getInstance(Year::class)->col('SELECT id FROM @TABLE WHERE closed = 0 ORDER BY start_date LIMIT 1;');
	}

	static public function listOpen()
	{
		$db = EntityManager::getInstance(Year::class)->DB();
		return $db->get('SELECT *, (SELECT COUNT(*) FROM acc_transactions WHERE id_year = acc_years.id) AS nb_transactions
			FROM acc_years WHERE closed = 0 ORDER BY end_date;');
	}
41
42
43
44
45
46
47
48

49
50
51
52
53
54
55
56
	{
		return DB::getInstance()->count(Year::TABLE, 'closed = 1');
	}

	static public function list(bool $reverse = false)
	{
		$desc = $reverse ? 'DESC' : '';
		$em = EntityManager::getInstance(Year::class);

		return $em->all(sprintf('SELECT * FROM @TABLE ORDER BY end_date %s;', $desc));
	}

	static public function getNewYearDates(): array
	{
		$last_year = EntityManager::findOne(Year::class, 'SELECT * FROM @TABLE ORDER BY end_date DESC LIMIT 1;');

		if ($last_year) {







|
>
|







46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
	{
		return DB::getInstance()->count(Year::TABLE, 'closed = 1');
	}

	static public function list(bool $reverse = false)
	{
		$desc = $reverse ? 'DESC' : '';
		return DB::getInstance()->get(sprintf('SELECT *,
			(SELECT COUNT(*) FROM acc_transactions WHERE id_year = acc_years.id) AS nb_transactions
			FROM acc_years ORDER BY end_date %s;', $desc));
	}

	static public function getNewYearDates(): array
	{
		$last_year = EntityManager::findOne(Year::class, 'SELECT * FROM @TABLE ORDER BY end_date DESC LIMIT 1;');

		if ($last_year) {

Modified src/include/lib/Garradin/Entities/Services/Fee.php from [6f825919cc] to [242617c34e].

156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
		];

		$tables = 'services_users su
			INNER JOIN membres m ON m.id = su.id_user
			INNER 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 l ON l.id_transaction = tu.id_transaction';
		$conditions = sprintf('su.id_fee = %d AND su.paid = 1 AND su.expiry_date >= date()', $this->id());

		$list = new DynamicList($columns, $tables, $conditions);
		$list->groupBy('su.id_user');
		$list->orderBy('date', true);
		$list->setCount('COUNT(DISTINCT su.id_user)');
		return $list;
	}







|







156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
		];

		$tables = 'services_users su
			INNER JOIN membres m ON m.id = su.id_user
			INNER 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 l ON l.id_transaction = tu.id_transaction';
		$conditions = sprintf('su.id_fee = %d AND su.paid = 1 AND (su.expiry_date >= date() OR su.expiry_date IS NULL)', $this->id());

		$list = new DynamicList($columns, $tables, $conditions);
		$list->groupBy('su.id_user');
		$list->orderBy('date', true);
		$list->setCount('COUNT(DISTINCT su.id_user)');
		return $list;
	}

Modified src/include/lib/Garradin/Entities/Services/Service.php from [5fa29d7db5] to [b46b1da9ff].

103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
			],
		];

		$tables = 'services_users su
			INNER JOIN membres m ON m.id = su.id_user
			INNER JOIN services s ON s.id = su.id_service
			INNER JOIN services_fees sf ON sf.id = su.id_fee';
		$conditions = sprintf('su.id_service = %d AND su.paid = 1 AND su.expiry_date >= date()', $this->id());

		$list = new DynamicList($columns, $tables, $conditions);
		$list->groupBy('su.id_user');
		$list->orderBy('date', true);
		$list->setCount('COUNT(DISTINCT su.id_user)');
		return $list;
	}







|







103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
			],
		];

		$tables = 'services_users su
			INNER JOIN membres m ON m.id = su.id_user
			INNER JOIN services s ON s.id = su.id_service
			INNER JOIN services_fees sf ON sf.id = su.id_fee';
		$conditions = sprintf('su.id_service = %d AND su.paid = 1 AND (su.expiry_date >= date() OR su.expiry_date IS NULL)', $this->id());

		$list = new DynamicList($columns, $tables, $conditions);
		$list->groupBy('su.id_user');
		$list->orderBy('date', true);
		$list->setCount('COUNT(DISTINCT su.id_user)');
		return $list;
	}

Modified src/include/lib/Garradin/Entities/Services/Service_User.php from [d694479106] to [c3706f63b1].

1
2
3
4
5
6

7
8
9
10
11
12
13
<?php

namespace Garradin\Entities\Services;

use Garradin\DB;
use Garradin\Entity;

use Garradin\ValidationException;
use Garradin\Services\Fees;
use Garradin\Services\Services;
use Garradin\Entities\Accounting\Transaction;

class Service_User extends Entity
{






>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

namespace Garradin\Entities\Services;

use Garradin\DB;
use Garradin\Entity;
use Garradin\Membres;
use Garradin\ValidationException;
use Garradin\Services\Fees;
use Garradin\Services\Services;
use Garradin\Entities\Accounting\Transaction;

class Service_User extends Entity
{
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105








106
107
108
109
110
111
112
113
		if (null === $this->_fee) {
			$this->_fee = Fees::get($this->id_fee);
		}

		return $this->_fee;
	}

	public function addPayment(int $user_id, ?array $source = null)
	{
		if (null === $source) {
			$source = $_POST;
		}

		$transaction = new Transaction;
		$transaction->id_creator = $user_id;
		$transaction->id_year = $this->fee()->id_year;

		$source['type'] = Transaction::TYPE_REVENUE;
		$key = sprintf('account_%d_', $source['type']);
		$source[$key . '0'] = [$this->fee()->id_account => ''];
		$source[$key . '1'] = isset($source['account']) ? $source['account'] : null;









		$source['label'] = 'Règlement activité - ' . $this->service()->label . ' - ' . $this->fee()->label;
		$source['date'] = $this->date->format('d/m/Y');

		$transaction->importFromNewForm($source);
		$transaction->save();
		$transaction->linkToUser($this->id_user, $this->id());

		return $transaction;







|














>
>
>
>
>
>
>
>
|







85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
		if (null === $this->_fee) {
			$this->_fee = Fees::get($this->id_fee);
		}

		return $this->_fee;
	}

	public function addPayment(int $user_id, ?array $source = null): Transaction
	{
		if (null === $source) {
			$source = $_POST;
		}

		$transaction = new Transaction;
		$transaction->id_creator = $user_id;
		$transaction->id_year = $this->fee()->id_year;

		$source['type'] = Transaction::TYPE_REVENUE;
		$key = sprintf('account_%d_', $source['type']);
		$source[$key . '0'] = [$this->fee()->id_account => ''];
		$source[$key . '1'] = isset($source['account']) ? $source['account'] : null;

		$label = $this->service()->label;

		if ($this->fee()->label != $label) {
			$label .= ' - ' . $this->fee()->label;
		}

		$label .= sprintf(' (%s)', (new Membres)->getNom($this->id_user));

		$source['label'] = $label;
		$source['date'] = $this->date->format('d/m/Y');

		$transaction->importFromNewForm($source);
		$transaction->save();
		$transaction->linkToUser($this->id_user, $this->id());

		return $transaction;
128
129
130
131
132
133
134
135


136
137
138
139
140
141
142
143

		if ($su->id_fee && $su->fee()->id_account && $su->id_user) {
			$su->expected_amount = $su->fee()->getAmountForUser($su->id_user);
		}

		$su->save();

		if ($su->id_fee && $su->fee()->id_account && !empty($source['amount'])) {


			$su->addPayment($user_id, $source);
		}

		$db->commit();

		return $su;
	}
}







|
>
>








137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154

		if ($su->id_fee && $su->fee()->id_account && $su->id_user) {
			$su->expected_amount = $su->fee()->getAmountForUser($su->id_user);
		}

		$su->save();

		if ($su->id_fee && $su->fee()->id_account
			&& !empty($source['amount'])
			&& !empty($source['create_payment'])) {
			$su->addPayment($user_id, $source);
		}

		$db->commit();

		return $su;
	}
}

Modified src/include/lib/Garradin/Files/Files.php from [85cdda535c] to [9ec11674dc].

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

32













	static public function callStorage(string $function, ...$args)
	{
		$storage = FILE_STORAGE_BACKEND ?? 'SQLite';
		$class_name = get_class(__NAMESPACE__ . '\\Backend\\' . $storage);
		return call_user_func_array([$class_name, $function], $args);
	}

	static public function migrateStorage(string $from, string $to)
	{
		$res = EM::getInstance(File::class)->iterate('SELECT * FROM @TABLE;');

		$from = get_class(__NAMESPACE__ . '\\Backend\\' . $from);
		$to = get_class(__NAMESPACE__ . '\\Backend\\' . $to);

		foreach ($res as $file) {
			$from_path = call_user_func([$from, 'path'], $file);
			call_user_func([$to, 'store'], $file, $from_path);
		}
	}
}






















|











|
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
	static public function callStorage(string $function, ...$args)
	{
		$storage = FILE_STORAGE_BACKEND ?? 'SQLite';
		$class_name = get_class(__NAMESPACE__ . '\\Backend\\' . $storage);
		return call_user_func_array([$class_name, $function], $args);
	}

	static public function migrateStorage(string $from, string $to): void
	{
		$res = EM::getInstance(File::class)->iterate('SELECT * FROM @TABLE;');

		$from = get_class(__NAMESPACE__ . '\\Backend\\' . $from);
		$to = get_class(__NAMESPACE__ . '\\Backend\\' . $to);

		foreach ($res as $file) {
			$from_path = call_user_func([$from, 'path'], $file);
			call_user_func([$to, 'store'], $file, $from_path);
		}
	}

	static public function generatePathsIndex(): void
	{
		$all = DB::getInstance()->getAssoc('SELECT path, path FROM files GROUP BY path;');
		$paths = [];

		foreach ($all as $path) {
			$path = explode('/', $path);

			foreach ($path as $part) {
				
			}
		}
	}
}

Modified src/include/lib/Garradin/Recherche.php from [5f2af851fc] to [1d10925eeb].

1
2
3
4
5
6
7
8
9
10

11
12
13
14
15
16
17
<?php

namespace Garradin;

use Garradin\Entities\Accounting\Transaction;

class Recherche
{
	const TYPE_JSON = 'json';
	const TYPE_SQL = 'sql';


	const TARGETS = [
		'membres',
		'compta',
	];

	protected function _checkFields($data)










>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

namespace Garradin;

use Garradin\Entities\Accounting\Transaction;

class Recherche
{
	const TYPE_JSON = 'json';
	const TYPE_SQL = 'sql';
	const TYPE_SQL_UNPROTECTED = 'sql_unprotected';

	const TARGETS = [
		'membres',
		'compta',
	];

	protected function _checkFields($data)
29
30
31
32
33
34
35


36
37
38
39
40
41
42
43
		}

		if (array_key_exists('id_membre', $data) && null !== $data['id_membre'] && !$db->test('membres', 'id = ?', $data['id_membre']))
		{
			throw new \InvalidArgumentException('Numéro d\'utilisateur inconnu.');
		}



		if (array_key_exists('type', $data) && $data['type'] !== self::TYPE_SQL && $data['type'] !== self::TYPE_JSON)
		{
			throw new \InvalidArgumentException('Type de recherche inconnu.');
		}

		if (array_key_exists('cible', $data) && !in_array($data['cible'], self::TARGETS, true))
		{
			throw new \InvalidArgumentException('Cible de recherche invalide.');







>
>
|







30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
		}

		if (array_key_exists('id_membre', $data) && null !== $data['id_membre'] && !$db->test('membres', 'id = ?', $data['id_membre']))
		{
			throw new \InvalidArgumentException('Numéro d\'utilisateur inconnu.');
		}

		static $types = [self::TYPE_SQL, self::TYPE_JSON, self::TYPE_SQL_UNPROTECTED];

		if (array_key_exists('type', $data) && !in_array($data['type'], $types))
		{
			throw new \InvalidArgumentException('Type de recherche inconnu.');
		}

		if (array_key_exists('cible', $data) && !in_array($data['cible'], self::TARGETS, true))
		{
			throw new \InvalidArgumentException('Cible de recherche invalide.');
163
164
165
166
167
168
169






170
171
172

173
174

175

176
177






178
179
180
181
182
183
184
			return [];
		}

		$out = [];
		$columns = $this->getColumns($target);

		foreach (reset($result) as $key => $v) {






			foreach ($columns as $ckey => $config) {
				if ($ckey == $key) {
					$out[$key] = $config->label;

				}
				elseif (isset($config->alias) && $config->alias == $key) {

					$out[$config->alias] = $config->label;

				}
			}






		}

		return $out;
	}

	/**
	 * Renvoie la liste des colonnes d'une cible







>
>
>
>
>
>


|
>


>
|
>


>
>
>
>
>
>







166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
			return [];
		}

		$out = [];
		$columns = $this->getColumns($target);

		foreach (reset($result) as $key => $v) {
			if (substr($key, 0, 1) == '_') {
				continue;
			}

			$label = null;

			foreach ($columns as $ckey => $config) {
				if ($ckey == $key) {
					$label = $config->label;
					break;
				}
				elseif (isset($config->alias) && $config->alias == $key) {
					$key = $config->alias;
					$label = $config->label;
					break;
				}
			}

			if (!$label) {
				$label = $key;
			}

			$out[$key] = $label;
		}

		return $out;
	}

	/**
	 * Renvoie la liste des colonnes d'une cible
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
		}
		elseif ($target === 'compta') {
			$columns['t.id'] = (object) [
				'textMatch'=> false,
				'label'    => 'Numéro écriture',
				'type'     => 'integer',
				'null'     => false,
				'alias'    => 'id',
			];

			$columns['t.date'] = (object) [
				'textMatch'=> false,
				'label'    => 'Date',
				'type'     => 'date',
				'null'     => false,







|







259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
		}
		elseif ($target === 'compta') {
			$columns['t.id'] = (object) [
				'textMatch'=> false,
				'label'    => 'Numéro écriture',
				'type'     => 'integer',
				'null'     => false,
				'alias'    => 'transaction_id',
			];

			$columns['t.date'] = (object) [
				'textMatch'=> false,
				'label'    => 'Date',
				'type'     => 'date',
				'null'     => false,
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
		{
			throw new UserException('Aucune clause trouvée dans la recherche.');
		}

		// Ajout du champ identité si pas présent
		if ($target == 'membres')
		{
			$query_columns = array_merge(['id', $config->get('champ_identite')], $query_columns);
		}
		// Ajout de champs compta si pas présents
		elseif ($target == 'compta')
		{
			$query_columns = array_merge(['t.id', 't.date', 't.label', 'l.debit', 'l.credit', 'a.code'], $query_columns);
		}








|







516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
		{
			throw new UserException('Aucune clause trouvée dans la recherche.');
		}

		// Ajout du champ identité si pas présent
		if ($target == 'membres')
		{
			$query_columns = array_merge([$config->get('champ_identite')], $query_columns);
		}
		// Ajout de champs compta si pas présents
		elseif ($target == 'compta')
		{
			$query_columns = array_merge(['t.id', 't.date', 't.label', 'l.debit', 'l.credit', 'a.code'], $query_columns);
		}

541
542
543
544
545
546
547




548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583







584
585
586
587
588
589
590
591
				INNER JOIN acc_transactions_lines AS l ON l.id_transaction = t.id
				INNER JOIN acc_accounts AS a ON l.id_account = a.id
				LEFT JOIN acc_accounts AS a2 ON l.id_analytical = a2.id
				WHERE %s GROUP BY t.id ORDER BY %s %s LIMIT %d;',
				$query_columns, $query_groups, $order, $desc, (int) $limit);
			$sql_query = preg_replace('/"(a|a2|l|t)\./', '"$1"."', $sql_query);
		}




		else {
			$sql_query = sprintf('SELECT id, %s FROM %s WHERE %s ORDER BY %s %s LIMIT %d;',
				$query_columns, $target, $query_groups, $order, $desc, (int) $limit);
		}

		return $sql_query;
	}

	/**
	 * Lancer une recherche SQL
	 */
	public function searchSQL($target, $query, array $force_select = null, $no_limit = false)
	{
		if (!in_array($target, self::TARGETS, true))
		{
			throw new \InvalidArgumentException('Cible inconnue : ' . $target);
		}

		if (null !== $force_select)
		{
			$query = preg_replace('/^\s*SELECT.*FROM\s+/Ui', 'SELECT ' . implode(', ', $force_select) . ' FROM ', $query);
		}

		if (!$no_limit && !preg_match('/LIMIT\s+\d+/i', $query))
		{
			$query = preg_replace('/;?\s*$/', '', $query);
			$query .= ' LIMIT 100';
		}

		try {
			$db = DB::getInstance();
			static $allowed = [
				'compta' => ['acc_transactions' => null, 'acc_transactions_lines' => null, 'acc_accounts' => null],
				'membres' => ['membres' => null],
			];








			$db->protectSelect($allowed[$target], $query);
			return $db->get($query);
		}
		catch (\Exception $e) {
			$message = 'Erreur dans la requête : ' . $e->getMessage();

			if (null !== $force_select)
			{







>
>
>
>











|




















|
|


>
>
>
>
>
>
>
|







559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
				INNER JOIN acc_transactions_lines AS l ON l.id_transaction = t.id
				INNER JOIN acc_accounts AS a ON l.id_account = a.id
				LEFT JOIN acc_accounts AS a2 ON l.id_analytical = a2.id
				WHERE %s GROUP BY t.id ORDER BY %s %s LIMIT %d;',
				$query_columns, $query_groups, $order, $desc, (int) $limit);
			$sql_query = preg_replace('/"(a|a2|l|t)\./', '"$1"."', $sql_query);
		}
		else if ('membres' === $target) {
			$sql_query = sprintf('SELECT id AS _user_id, %s FROM %s WHERE %s ORDER BY %s %s LIMIT %d;',
				$query_columns, $target, $query_groups, $order, $desc, (int) $limit);
		}
		else {
			$sql_query = sprintf('SELECT id, %s FROM %s WHERE %s ORDER BY %s %s LIMIT %d;',
				$query_columns, $target, $query_groups, $order, $desc, (int) $limit);
		}

		return $sql_query;
	}

	/**
	 * Lancer une recherche SQL
	 */
	public function searchSQL(string $target, $query, array $force_select = null, bool $no_limit = false, bool $unprotected = false)
	{
		if (!in_array($target, self::TARGETS, true))
		{
			throw new \InvalidArgumentException('Cible inconnue : ' . $target);
		}

		if (null !== $force_select)
		{
			$query = preg_replace('/^\s*SELECT.*FROM\s+/Ui', 'SELECT ' . implode(', ', $force_select) . ' FROM ', $query);
		}

		if (!$no_limit && !preg_match('/LIMIT\s+\d+/i', $query))
		{
			$query = preg_replace('/;?\s*$/', '', $query);
			$query .= ' LIMIT 100';
		}

		try {
			$db = DB::getInstance();
			static $allowed = [
				'compta' => ['acc_transactions' => null, 'acc_transactions_lines' => null, 'acc_accounts' => null, 'acc_charts' => null, 'acc_years' => null, 'acc_transactions_users' => null],
				'membres' => ['membres' => null, 'membres_categories' => null],
			];

			if ($unprotected) {
				$allowed_tables = null;
			}
			else {
				$allowed_tables = $allowed[$target];
			}

			$db->protectSelect($allowed_tables, $query);
			return $db->get($query);
		}
		catch (\Exception $e) {
			$message = 'Erreur dans la requête : ' . $e->getMessage();

			if (null !== $force_select)
			{

Modified src/include/lib/Garradin/Services/Fees.php from [40efe50c3b] to [7018d3147a].

54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
		return $result;
	}

	public function listWithStats()
	{
		$db = DB::getInstance();
		return $db->get('SELECT f.*,
			(SELECT COUNT(DISTINCT id_user) FROM services_users WHERE id_fee = f.id AND expiry_date >= date() AND paid = 1) AS nb_users_ok,
			(SELECT COUNT(DISTINCT id_user) FROM services_users WHERE id_fee = f.id AND expiry_date < date()) AS nb_users_expired,
			(SELECT COUNT(DISTINCT id_user) FROM services_users WHERE id_fee = f.id AND paid = 0) AS nb_users_unpaid
			FROM services_fees f
			WHERE id_service = ?
			ORDER BY amount, transliterate_to_ascii(label) COLLATE NOCASE;', $this->service_id);
	}
}







|







54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
		return $result;
	}

	public function listWithStats()
	{
		$db = DB::getInstance();
		return $db->get('SELECT f.*,
			(SELECT COUNT(DISTINCT id_user) FROM services_users WHERE id_fee = f.id AND (expiry_date >= date() OR expiry_date IS NULL) AND paid = 1) AS nb_users_ok,
			(SELECT COUNT(DISTINCT id_user) FROM services_users WHERE id_fee = f.id AND expiry_date < date()) AS nb_users_expired,
			(SELECT COUNT(DISTINCT id_user) FROM services_users WHERE id_fee = f.id AND paid = 0) AS nb_users_unpaid
			FROM services_fees f
			WHERE id_service = ?
			ORDER BY amount, transliterate_to_ascii(label) COLLATE NOCASE;', $this->service_id);
	}
}

Modified src/include/lib/Garradin/Services/Services.php from [8034954296] to [c0c763cc9c].

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
	}

	static public function listGroupedWithFees(?int $user_id = null)
	{
		$services = DB::getInstance()->getGrouped('SELECT
			id, label, duration, start_date, end_date, description,
			CASE WHEN end_date IS NOT NULL THEN end_date WHEN duration IS NOT NULL THEN date(\'now\', \'+\'||duration||\' days\') ELSE NULL END AS expiry_date
			FROM services;');
		$fees = Fees::listAllByService($user_id);
		$out = [];

		foreach ($services as $service) {
			$out[$service->id] = $service;
			$out[$service->id]->fees = [];
		}

		foreach ($fees as $fee) {
			$out[$fee->id_service]->fees[] = $fee;
		}

		return $out;
	}

	static public function listWithStats()
	{
		$db = DB::getInstance();
		return $db->get('SELECT s.*,
			(SELECT COUNT(DISTINCT id_user) FROM services_users WHERE id_service = s.id AND expiry_date >= date() AND paid = 1) AS nb_users_ok,
			(SELECT COUNT(DISTINCT id_user) FROM services_users WHERE id_service = s.id AND expiry_date < date()) AS nb_users_expired,
			(SELECT COUNT(DISTINCT id_user) FROM services_users WHERE id_service = s.id AND paid = 0) AS nb_users_unpaid
			FROM services s
			ORDER BY transliterate_to_ascii(s.label) COLLATE NOCASE;');
	}
}







|



















|






20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
	}

	static public function listGroupedWithFees(?int $user_id = null)
	{
		$services = DB::getInstance()->getGrouped('SELECT
			id, label, duration, start_date, end_date, description,
			CASE WHEN end_date IS NOT NULL THEN end_date WHEN duration IS NOT NULL THEN date(\'now\', \'+\'||duration||\' days\') ELSE NULL END AS expiry_date
			FROM services ORDER BY label COLLATE NOCASE;');
		$fees = Fees::listAllByService($user_id);
		$out = [];

		foreach ($services as $service) {
			$out[$service->id] = $service;
			$out[$service->id]->fees = [];
		}

		foreach ($fees as $fee) {
			$out[$fee->id_service]->fees[] = $fee;
		}

		return $out;
	}

	static public function listWithStats()
	{
		$db = DB::getInstance();
		return $db->get('SELECT s.*,
			(SELECT COUNT(DISTINCT id_user) FROM services_users WHERE id_service = s.id AND (expiry_date IS NULL OR expiry_date >= date()) AND paid = 1) AS nb_users_ok,
			(SELECT COUNT(DISTINCT id_user) FROM services_users WHERE id_service = s.id AND expiry_date < date()) AS nb_users_expired,
			(SELECT COUNT(DISTINCT id_user) FROM services_users WHERE id_service = s.id AND paid = 0) AS nb_users_unpaid
			FROM services s
			ORDER BY transliterate_to_ascii(s.label) COLLATE NOCASE;');
	}
}

Modified src/include/lib/Garradin/Upgrade.php from [846a817db9] to [b4ea8189bd].

85
86
87
88
89
90
91















92
93
94
95
96
97
98

			if (version_compare($v, '1.0.0-beta1', '>=') && version_compare($v, '1.0.0-rc3', '<'))
			{
				$db->beginSchemaUpdate();
				$db->import(ROOT . '/include/data/1.0.0-rc3_migration.sql');
				$db->commitSchemaUpdate();
			}
















			// Vérification de la cohérence des clés étrangères
			$db->foreignKeyCheck();

			Utils::clearCaches();

			$config->setVersion(garradin_version());







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113

			if (version_compare($v, '1.0.0-beta1', '>=') && version_compare($v, '1.0.0-rc3', '<'))
			{
				$db->beginSchemaUpdate();
				$db->import(ROOT . '/include/data/1.0.0-rc3_migration.sql');
				$db->commitSchemaUpdate();
			}

			if (version_compare($v, '1.0.0-beta1', '>=') && version_compare($v, '1.0.0-rc10', '<'))
			{
				$db->beginSchemaUpdate();
				$db->import(ROOT . '/include/data/1.0.0-rc10_migration.sql');
				$db->commitSchemaUpdate();
			}

			if (version_compare($v, '1.0.0-beta1', '>=') && version_compare($v, '1.0.0-rc11', '<'))
			{
				// Missing trigger
				$db->beginSchemaUpdate();
				$db->import(ROOT . '/include/data/1.0.0_schema.sql');
				$db->commitSchemaUpdate();
			}

			// Vérification de la cohérence des clés étrangères
			$db->foreignKeyCheck();

			Utils::clearCaches();

			$config->setVersion(garradin_version());

Modified src/templates/acc/accounts/index.tpl from [d00593bab9] to [6db865d25e].

1
2
3
4
5



6
7
8
9
10
11
12
13
14
15
16
{include file="admin/_head.tpl" title="Comptes favoris" current="acc/accounts"}

{include file="acc/_year_select.tpl"}

<nav class="tabs">



	<ul>
		<li class="current"><a href="{$admin_url}acc/accounts/">Comptes favoris</a></li>
		<li><a href="{$admin_url}acc/reports/trial_balance.php?year={$current_year.id}">Balance générale (tous les comptes)</a></li>
		<li><a href="{$admin_url}acc/search.php?year={$current_year.id}">Recherche</a></li>
		{if $session->canAccess('compta', Membres::DROIT_ADMIN)}
			<li><a href="{$admin_url}acc/charts/accounts/all.php?id={$chart_id}">Plan comptable</a></li>
		{/if}
	</ul>
</nav>

{include file="acc/_simple_help.tpl" link="../reports/trial_balance.php?year=%d"|args:$current_year.id type=null}





>
>
>



<







1
2
3
4
5
6
7
8
9
10
11

12
13
14
15
16
17
18
{include file="admin/_head.tpl" title="Comptes favoris" current="acc/accounts"}

{include file="acc/_year_select.tpl"}

<nav class="tabs">
	<aside>
		{linkbutton shape="search" href="!acc/search.php?year=%d"|args:$current_year.id label="Recherche"}
	</aside>
	<ul>
		<li class="current"><a href="{$admin_url}acc/accounts/">Comptes favoris</a></li>
		<li><a href="{$admin_url}acc/reports/trial_balance.php?year={$current_year.id}">Balance générale (tous les comptes)</a></li>

		{if $session->canAccess('compta', Membres::DROIT_ADMIN)}
			<li><a href="{$admin_url}acc/charts/accounts/all.php?id={$chart_id}">Plan comptable</a></li>
		{/if}
	</ul>
</nav>

{include file="acc/_simple_help.tpl" link="../reports/trial_balance.php?year=%d"|args:$current_year.id type=null}

Modified src/templates/acc/accounts/journal.tpl from [eea4b60d81] to [98708499a9].

48
49
50
51
52
53
54

55
56
57
58
59
60
61

	<nav class="tabs">
		<aside>
		{if $session->canAccess('compta', Membres::DROIT_ADMIN)}
			{linkbutton href="%s&export=csv"|args:$self_url label="Export CSV" shape="export"}
			{linkbutton href="%s&export=ods"|args:$self_url label="Export tableur" shape="export"}
		{/if}

		{if $year.id == CURRENT_YEAR_ID}
			{linkbutton href="!acc/transactions/new.php?account=%d"|args:$account.id label="Saisir une écriture dans ce compte" shape="plus"}
		{/if}
		</aside>
		<ul>
			<li{if $simple} class="current"{/if}><a href="?id={$account.id}&amp;simple=1&amp;year={$year.id}">Vue simplifiée</a></li>
			<li{if !$simple} class="current"{/if}><a href="?id={$account.id}&amp;simple=0&amp;year={$year.id}">Vue comptable</a></li>







>







48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

	<nav class="tabs">
		<aside>
		{if $session->canAccess('compta', Membres::DROIT_ADMIN)}
			{linkbutton href="%s&export=csv"|args:$self_url label="Export CSV" shape="export"}
			{linkbutton href="%s&export=ods"|args:$self_url label="Export tableur" shape="export"}
		{/if}
			{linkbutton shape="search" href="!acc/search.php?year=%d&account=%s"|args:$year.id,$account.code label="Recherche"}
		{if $year.id == CURRENT_YEAR_ID}
			{linkbutton href="!acc/transactions/new.php?account=%d"|args:$account.id label="Saisir une écriture dans ce compte" shape="plus"}
		{/if}
		</aside>
		<ul>
			<li{if $simple} class="current"{/if}><a href="?id={$account.id}&amp;simple=1&amp;year={$year.id}">Vue simplifiée</a></li>
			<li{if !$simple} class="current"{/if}><a href="?id={$account.id}&amp;simple=0&amp;year={$year.id}">Vue comptable</a></li>

Modified src/templates/acc/accounts/simple.tpl from [d13f106002] to [55f085e703].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

18
19
20
21
22
23
24
{include file="admin/_head.tpl" title="Suivi : %s"|args:$types[$type] current="acc/simple"}

{if empty($year)}
	{include file="acc/_year_select.tpl"}
{else}
	<nav class="acc-year">
		<h4>Exercice sélectionné&nbsp;:</h4>
		<h3>{$year.label} — {$year.start_date|date_short} au {$year.end_date|date_short}</h3>
	</nav>
{/if}

<nav class="tabs">
	<aside>
	{if $session->canAccess('compta', Membres::DROIT_ADMIN)}
		{linkbutton href="?type=%d&export=csv"|args:$type label="Export CSV" shape="export"}
		{linkbutton href="?type=%d&export=ods"|args:$type label="Export tableur" shape="export"}
	{/if}

	</aside>
	<ul>
		{foreach from=$types key="key" item="label"}
		<li{if $type == $key} class="current"{/if}><a href="?type={$key}">{$label}</a></li>
		{/foreach}
	</ul>
</nav>


<
|
<
<
<
<
<
<







>







1
2

3






4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{include file="admin/_head.tpl" title="Suivi : %s"|args:$types[$type] current="acc/simple"}


{include file="acc/_year_select.tpl"}







<nav class="tabs">
	<aside>
	{if $session->canAccess('compta', Membres::DROIT_ADMIN)}
		{linkbutton href="?type=%d&export=csv"|args:$type label="Export CSV" shape="export"}
		{linkbutton href="?type=%d&export=ods"|args:$type label="Export tableur" shape="export"}
	{/if}
		{linkbutton shape="search" href="!acc/search.php?year=%d&type=%d"|args:$year.id,$type label="Recherche"}
	</aside>
	<ul>
		{foreach from=$types key="key" item="label"}
		<li{if $type == $key} class="current"{/if}><a href="?type={$key}">{$label}</a></li>
		{/foreach}
	</ul>
</nav>

Modified src/templates/acc/index.tpl from [275f4e0a55] to [b029a4865a].

1
2
3
4
5
6
7
8
9
10
11
12
13

14
15
16
17
18
19
20
{include file="admin/_head.tpl" title="Comptabilité" current="acc"}

{foreach from=$years item="year"}
<section class="year-infos">
	<h2 class="ruler">{$year.label} —
		Du {$year.start_date|date_short} au {$year.end_date|date_short}</h2>

	<nav class="tabs">
		<aside>
			{linkbutton shape="search" href="!acc/search.php?year=%d"|args:$year.id label="Recherche"}
			{if $session->canAccess('compta', Membres::DROIT_ADMIN)}
				{linkbutton shape="upload" href="!acc/years/import.php?year=%d"|args:$year.id label="Import & export"}
			{/if}

		</aside>
		<ul>
			<li><a href="{$admin_url}acc/reports/graphs.php?year={$year.id}">Graphiques</a></li>
			<li><a href="{$admin_url}acc/reports/trial_balance.php?year={$year.id}">Balance générale</a></li>
			<li><a href="{$admin_url}acc/reports/journal.php?year={$year.id}">Journal général</a></li>
			<li><a href="{$admin_url}acc/reports/ledger.php?year={$year.id}">Grand livre</a></li>
			<li><a href="{$admin_url}acc/reports/statement.php?year={$year.id}">Compte de résultat</a></li>









<



>







1
2
3
4
5
6
7
8
9

10
11
12
13
14
15
16
17
18
19
20
{include file="admin/_head.tpl" title="Comptabilité" current="acc"}

{foreach from=$years item="year"}
<section class="year-infos">
	<h2 class="ruler">{$year.label} —
		Du {$year.start_date|date_short} au {$year.end_date|date_short}</h2>

	<nav class="tabs">
		<aside>

			{if $session->canAccess('compta', Membres::DROIT_ADMIN)}
				{linkbutton shape="upload" href="!acc/years/import.php?year=%d"|args:$year.id label="Import & export"}
			{/if}
			{linkbutton shape="search" href="!acc/search.php?year=%d"|args:$year.id label="Recherche"}
		</aside>
		<ul>
			<li><a href="{$admin_url}acc/reports/graphs.php?year={$year.id}">Graphiques</a></li>
			<li><a href="{$admin_url}acc/reports/trial_balance.php?year={$year.id}">Balance générale</a></li>
			<li><a href="{$admin_url}acc/reports/journal.php?year={$year.id}">Journal général</a></li>
			<li><a href="{$admin_url}acc/reports/ledger.php?year={$year.id}">Grand livre</a></li>
			<li><a href="{$admin_url}acc/reports/statement.php?year={$year.id}">Compte de résultat</a></li>

Modified src/templates/acc/reports/_header.tpl from [c9b9d26764] to [8e4c7a5fd3].

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
			<li{if $current == "journal"} class="current"{/if}><a href="{$admin_url}acc/reports/journal.php?{$criterias_query}">Journal général</a></li>
			<li{if $current == "ledger"} class="current"{/if}><a href="{$admin_url}acc/reports/ledger.php?{$criterias_query}">Grand livre</a></li>
			<li{if $current == "statement"} class="current"{/if}><a href="{$admin_url}acc/reports/statement.php?{$criterias_query}">Compte de résultat</a></li>
			<li{if $current == "balance_sheet"} class="current"{/if}><a href="{$admin_url}acc/reports/balance_sheet.php?{$criterias_query}">Bilan</a></li>
		</ul>
	</nav>

	<h2>{$config.nom_asso}</h2>
	{if isset($analytical)}
		<h3>Projet&nbsp;: {$analytical.label}</h3>
	{/if}
	{if isset($year)}
		<p>Exercice comptable {if $year.closed}clôturé{else}en cours{/if} du
			{$year.start_date|date_short} au {$year.end_date|date_short}, généré le {$close_date|date_short}</p>
	{/if}

	<p class="noprint print-btn">
		<button onclick="window.print(); return false;" class="icn-btn" data-icon="⎙">Imprimer</button>
	</p>
</div>







|












10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
			<li{if $current == "journal"} class="current"{/if}><a href="{$admin_url}acc/reports/journal.php?{$criterias_query}">Journal général</a></li>
			<li{if $current == "ledger"} class="current"{/if}><a href="{$admin_url}acc/reports/ledger.php?{$criterias_query}">Grand livre</a></li>
			<li{if $current == "statement"} class="current"{/if}><a href="{$admin_url}acc/reports/statement.php?{$criterias_query}">Compte de résultat</a></li>
			<li{if $current == "balance_sheet"} class="current"{/if}><a href="{$admin_url}acc/reports/balance_sheet.php?{$criterias_query}">Bilan</a></li>
		</ul>
	</nav>

	<h2>{$config.nom_asso} — {$title}</h2>
	{if isset($analytical)}
		<h3>Projet&nbsp;: {$analytical.label}</h3>
	{/if}
	{if isset($year)}
		<p>Exercice comptable {if $year.closed}clôturé{else}en cours{/if} du
			{$year.start_date|date_short} au {$year.end_date|date_short}, généré le {$close_date|date_short}</p>
	{/if}

	<p class="noprint print-btn">
		<button onclick="window.print(); return false;" class="icn-btn" data-icon="⎙">Imprimer</button>
	</p>
</div>

Modified src/templates/acc/reports/_journal.tpl from [37e07b0e8b] to [4ecba1da68].

1
2
3

4
5
6
7
8
9
10
11
12
13
14
15
16
17

18
19
20
21
22
23
24
<table class="list multi">
	<thead>
		<tr>

			<td>Réf.</td>
			<td>Date</td>
			<th>Libellé</th>
			<td>Comptes</td>
			<td class="money">Débit</td>
			<td class="money">Crédit</td>
			<td>Libellé ligne</td>
			<td>Réf. ligne</td>
		</tr>
	</thead>
	{foreach from=$journal item="transaction"}
	<tbody>
		<tr>
			<td rowspan="{$transaction.lines|count}" class="num"><a href="{$admin_url}acc/transactions/details.php?id={$transaction.id}">{if $transaction.reference}{$transaction.reference}{else}#{$transaction.id}{/if}</a></td>

			<td rowspan="{$transaction.lines|count}">{$transaction.date|date_short}</td>
			<th rowspan="{$transaction.lines|count}">{$transaction.label}</th>
		{foreach from=$transaction.lines item="line"}
			<td>{$line.account_code} - {$line.account_label}</td>
			<td class="money">{$line.debit|raw|html_money}</td>
			<td class="money">{$line.credit|raw|html_money}</td>
			<td>{$line.label}</td>



>
|












|
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<table class="list multi">
	<thead>
		<tr>
			<td class="num">N°</td>
			<td>Pièce comptable</td>
			<td>Date</td>
			<th>Libellé</th>
			<td>Comptes</td>
			<td class="money">Débit</td>
			<td class="money">Crédit</td>
			<td>Libellé ligne</td>
			<td>Réf. ligne</td>
		</tr>
	</thead>
	{foreach from=$journal item="transaction"}
	<tbody>
		<tr>
			<td rowspan="{$transaction.lines|count}" class="num"><a href="{$admin_url}acc/transactions/details.php?id={$transaction.id}">#{$transaction.id}</a></td>
			<td rowspan="{$transaction.lines|count}">{$transaction.reference}</td>
			<td rowspan="{$transaction.lines|count}">{$transaction.date|date_short}</td>
			<th rowspan="{$transaction.lines|count}">{$transaction.label}</th>
		{foreach from=$transaction.lines item="line"}
			<td>{$line.account_code} - {$line.account_label}</td>
			<td class="money">{$line.debit|raw|html_money}</td>
			<td class="money">{$line.credit|raw|html_money}</td>
			<td>{$line.label}</td>

Added src/templates/acc/reports/_statement.tpl version [2408ac0c3b].









































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<table class="statement">
	<colgroup>
		<col width="50%" />
		<col width="50%" />
	</colgroup>
	<tbody>
		<tr>
			<td>
				{include file="acc/reports/_statement_table.tpl" accounts=$statement.expense caption=$caption1}
			</td>
			<td>
				{include file="acc/reports/_statement_table.tpl" accounts=$statement.revenue caption=$caption2}
			</td>
		</tr>
	</tbody>
	<tfoot>
		<tr>
			<td>
				<table>
					<tfoot>
						<tr>
							<th>Total</th>
							<td class="money">{$statement.expense_sum|raw|html_money:false}</td>
						</tr>
					</tfoot>
				</table>
			</td>
			<td>
				<table>
					<tfoot>
						<tr>
							<th>Total</th>
							<td class="money">{$statement.revenue_sum|raw|html_money:false}</td>
						</tr>
					</tfoot>
				</table>
			</td>
		</tr>
		{if $statement.result}
		<tr>
			<td>
			{if ($statement.result < 0)}
				<table>
					<tfoot>
						<tr>
							<th>Résultat (perte)</th>
							<td class="money">{$statement.result|raw|html_money:false}</td>
						</tr>
					</tfoot>
				</table>
			{/if}
			</td>
			<td>
			{if ($statement.result >= 0)}
				<table>
					<tfoot>
						<tr>
							<th>Résultat (excédent)</th>
							<td class="money">{$statement.result|raw|html_money:false}</td>
						</tr>
					</tfoot>
				</table>
			{/if}
			</td>
		</tr>
		{/if}
	</tfoot>
</table>

Modified src/templates/acc/reports/balance_sheet.tpl from [0249d23d75] to [a7a5928f43].

1
2
3







4
5
6
7
8
9
10
{include file="admin/_head.tpl" title="Bilan" current="acc/years"}

{include file="acc/reports/_header.tpl" current="balance_sheet"}








<table class="statement">
	<colgroup>
		<col width="50%" />
		<col width="50%" />
	</colgroup>
	<tbody>


|
>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{include file="admin/_head.tpl" title="Bilan" current="acc/years"}

{include file="acc/reports/_header.tpl" current="balance_sheet" title="Bilan"}

{if $asset_sum != $liability_sum}
	<p class="alert block">
		<strong>Le bilan n'est pas équilibré&nbsp;!</strong><br />
		Vérifiez que vous n'avez pas oublié de reporter des soldes depuis le précédent exercice.
	</p>
{/if}

<table class="statement">
	<colgroup>
		<col width="50%" />
		<col width="50%" />
	</colgroup>
	<tbody>

Modified src/templates/acc/reports/graphs.tpl from [526c29e562] to [f56b53d0dd].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{include file="admin/_head.tpl" title="Graphiques" current="acc"}

{include file="acc/reports/_header.tpl" current="graphs"}

<section class="year-infos">
	<section class="graphs">
		{foreach from=$graphs key="url" item="label"}
		<figure>
			<img src="{$url|args:$criterias_query}" alt="" />
			<figcaption>{$label}</figcaption>
		</figure>
		{/foreach}
	</section>
</section>

{include file="admin/_foot.tpl"}


|













1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{include file="admin/_head.tpl" title="Graphiques" current="acc"}

{include file="acc/reports/_header.tpl" current="graphs" title="Graphiques"}

<section class="year-infos">
	<section class="graphs">
		{foreach from=$graphs key="url" item="label"}
		<figure>
			<img src="{$url|args:$criterias_query}" alt="" />
			<figcaption>{$label}</figcaption>
		</figure>
		{/foreach}
	</section>
</section>

{include file="admin/_foot.tpl"}

Modified src/templates/acc/reports/journal.tpl from [5b585d401b] to [fd83a86c0b].

1
2
3
4
5
6
7
8
9
{include file="admin/_head.tpl" title="Journal général" current="acc/years"}

{include file="acc/reports/_header.tpl" current="journal"}

{include file="acc/reports/_journal.tpl"}

<p class="help">Toutes les écritures sont libellées en {$config.monnaie}.</p>

{include file="admin/_foot.tpl"}


|






1
2
3
4
5
6
7
8
9
{include file="admin/_head.tpl" title="Journal général" current="acc/years"}

{include file="acc/reports/_header.tpl" current="journal" title="Journal général"}

{include file="acc/reports/_journal.tpl"}

<p class="help">Toutes les écritures sont libellées en {$config.monnaie}.</p>

{include file="admin/_foot.tpl"}

Modified src/templates/acc/reports/ledger.tpl from [58b1dc5251] to [a06ebc88ec].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

28
29
30
31
32
33
34
35
36
37
38
39

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
{include file="admin/_head.tpl" title="Grand livre" current="acc/years"}

{include file="acc/reports/_header.tpl" current="ledger"}

<div class="year-header noprint">
	<button type="button" data-icon="↓" class="icn-btn" id="open_details">Déplier tous les comptes</button>
	<button type="button" data-icon="↑" class="icn-btn" id="close_details">Replier tous les comptes</button>
</div>

{foreach from=$ledger item="account"}

<details open="open">
	<summary><h2 class="ruler"><a href="{$admin_url}acc/accounts/journal.php?id={$account.id}&amp;year={$account.id_year}">{$account.code} — {$account.label}</a></h2></summary>

	<table class="list">
		<colgroup>
			<col width="5%" />
			<col width="5%" />
			<col width="10%" />
			<col width="50%" />
			<col width="10%" />
			<col width="10%" />
			<col width="10%" />
		</colgroup>
		<thead>
			<tr>
				<td>Réf.</td>

				<td>Réf. ligne</td>
				<td>Date</td>
				<th>Intitulé</th>
				<td class="money">Débit</td>
				<td class="money">Crédit</td>
				<td class="money">Solde</td>
			</tr>
		</thead>
		<tbody>
		{foreach from=$account.lines item="line"}
			<tr>
				<td class="num"><a href="{$admin_url}acc/transactions/details.php?id={$line.id}">{if $line.reference}{$line.reference}{else}#{$line.id}{/if}</a></td>

				<td class="num">{$line.line_reference}</td>
				<td>{$line.date|date_short}</td>
				<th>{$line.label}{if $line.line_label} <em>({$line.line_label})</em>{/if}</th>
				<td class="money">{$line.debit|raw|html_money}</td>
				<td class="money">{$line.credit|raw|html_money}</td>
				<td class="money">{$line.running_sum|raw|html_money:false}</td>
			</tr>
		{/foreach}
		</tbody>
		<tfoot>
			<tr>
				<td colspan="3"></td>
				<th>Solde final</th>
				<td class="money">{$account.debit|raw|html_money}</td>
				<td class="money">{$account.credit|raw|html_money}</td>
				<td class="money">{$account.sum|raw|html_money:false}</td>
			</tr>
		</tfoot>
	</table>


|












<
<
<
<
<
<
<
<
<


|
>











|
>
|










|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15









16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
{include file="admin/_head.tpl" title="Grand livre" current="acc/years"}

{include file="acc/reports/_header.tpl" current="ledger" title="Grand livre"}

<div class="year-header noprint">
	<button type="button" data-icon="↓" class="icn-btn" id="open_details">Déplier tous les comptes</button>
	<button type="button" data-icon="↑" class="icn-btn" id="close_details">Replier tous les comptes</button>
</div>

{foreach from=$ledger item="account"}

<details open="open">
	<summary><h2 class="ruler"><a href="{$admin_url}acc/accounts/journal.php?id={$account.id}&amp;year={$account.id_year}">{$account.code} — {$account.label}</a></h2></summary>

	<table class="list">









		<thead>
			<tr>
				<td></td>
				<td>N° pièce</td>
				<td>Réf. ligne</td>
				<td>Date</td>
				<th>Intitulé</th>
				<td class="money">Débit</td>
				<td class="money">Crédit</td>
				<td class="money">Solde</td>
			</tr>
		</thead>
		<tbody>
		{foreach from=$account.lines item="line"}
			<tr>
				<td class="num"><a href="{$admin_url}acc/transactions/details.php?id={$line.id}">#{$line.id}</a></td>
				<td>{$line.reference}</td>
				<td>{$line.line_reference}</td>
				<td>{$line.date|date_short}</td>
				<th>{$line.label}{if $line.line_label} <em>({$line.line_label})</em>{/if}</th>
				<td class="money">{$line.debit|raw|html_money}</td>
				<td class="money">{$line.credit|raw|html_money}</td>
				<td class="money">{$line.running_sum|raw|html_money:false}</td>
			</tr>
		{/foreach}
		</tbody>
		<tfoot>
			<tr>
				<td colspan="4"></td>
				<th>Solde final</th>
				<td class="money">{$account.debit|raw|html_money}</td>
				<td class="money">{$account.credit|raw|html_money}</td>
				<td class="money">{$account.sum|raw|html_money:false}</td>
			</tr>
		</tfoot>
	</table>

Modified src/templates/acc/reports/projects.tpl from [a54424c23b] to [097c5f9f39].

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25








26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

45
46
47
48
49
50
51
		<li><a href="{$admin_url}acc/years/">Exercices</a></li>
		{if $session->canAccess('compta', Membres::DROIT_ADMIN)}
		<li><a href="{$admin_url}acc/years/new.php">Nouvel exercice</a></li>
		{/if}
		<li class="current"><a href="{$admin_url}acc/reports/projects.php">Projets <em>(compta analytique)</em></a></li>
	</ul>

	<aside>
		<button onclick="window.print(); return false;" class="icn-btn" data-icon="⎙">Imprimer</button>
	</aside>
	<ul class="sub">
		<li{if !$by_year} class="current"{/if}><a href="{$self_url_no_qs}">Par projet</a></li>
		<li{if $by_year} class="current"{/if}><a href="{$self_url_no_qs}?by_year=1">Par exercice</a></li>
	</ul>
</nav>









{if !empty($list)}


	<table class="list">
		<thead>
			<tr>
				<td>Année</td>
				<td></td>
				<td class="money">Débits</td>
				<td class="money">Crédits</td>
				<td class="money">Solde</td>
			</tr>
		</thead>
		{foreach from=$list item="parent"}
			<tbody>
				<tr>
					<th colspan="5">
						<h2 class="ruler">{$parent.label}</h2>

					</th>
				</tr>
			{foreach from=$parent.items item="item"}
				<tr>
					<th>{$item.label}</th>
					<td>
					<span class="noprint">







<
<
<





>
>
>
>
>
>
>
>




|











|


>







11
12
13
14
15
16
17



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
		<li><a href="{$admin_url}acc/years/">Exercices</a></li>
		{if $session->canAccess('compta', Membres::DROIT_ADMIN)}
		<li><a href="{$admin_url}acc/years/new.php">Nouvel exercice</a></li>
		{/if}
		<li class="current"><a href="{$admin_url}acc/reports/projects.php">Projets <em>(compta analytique)</em></a></li>
	</ul>




	<ul class="sub">
		<li{if !$by_year} class="current"{/if}><a href="{$self_url_no_qs}">Par projet</a></li>
		<li{if $by_year} class="current"{/if}><a href="{$self_url_no_qs}?by_year=1">Par exercice</a></li>
	</ul>
</nav>

<div class="year-header">
	<h2>{$config.nom_asso} — Projets</h2>

	<p class="noprint print-btn">
		<button onclick="window.print(); return false;" class="icn-btn" data-icon="⎙">Imprimer</button>
	</p>
</div>

{if !empty($list)}


	<table class="list projects">
		<thead>
			<tr>
				<td>Année</td>
				<td></td>
				<td class="money">Débits</td>
				<td class="money">Crédits</td>
				<td class="money">Solde</td>
			</tr>
		</thead>
		{foreach from=$list item="parent"}
			<tbody>
				<tr class="title">
					<th colspan="5">
						<h2 class="ruler">{$parent.label}</h2>
						{if $parent.description}<p class="help">{$parent.description|escape|nl2br}</p>{/if}
					</th>
				</tr>
			{foreach from=$parent.items item="item"}
				<tr>
					<th>{$item.label}</th>
					<td>
					<span class="noprint">

Modified src/templates/acc/reports/statement.tpl from [8f97001d6f] to [176cb70b6d].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
{include file="admin/_head.tpl" title="Compte de résultat" current="acc/years"}

{include file="acc/reports/_header.tpl" current="statement"}

<table class="statement">
	<colgroup>
		<col width="50%" />
		<col width="50%" />
	</colgroup>
	<tbody>
		<tr>
			<td>
				{include file="acc/reports/_statement_table.tpl" accounts=$expense caption="Charges"}
			</td>
			<td>
				{include file="acc/reports/_statement_table.tpl" accounts=$revenue caption="Produits"}
			</td>
		</tr>
	</tbody>
	<tfoot>
		<tr>
			<td>
				<table>
					<tfoot>
						<tr>
							<th>Total charges</th>
							<td class="money">{$expense_sum|raw|html_money:false}</td>
						</tr>
					</tfoot>
				</table>
			</td>
			<td>
				<table>
					<tfoot>
						<tr>
							<th>Total produits</th>
							<td class="money">{$revenue_sum|raw|html_money:false}</td>
						</tr>
					</tfoot>
				</table>
			</td>
		</tr>
		{if $result}
		<tr>
			<td>
			{if ($result < 0)}
				<table>
					<tfoot>
						<tr>
							<th>Résultat (perte)</th>
							<td class="money">{$result|raw|html_money:false}</td>
						</tr>
					</tfoot>
				</table>

			{/if}
			</td>
			<td>
			{if ($result >= 0)}
				<table>
					<tfoot>
						<tr>
							<th>Résultat (excédent)</th>
							<td class="money">{$result|raw|html_money:false}</td>
						</tr>
					</tfoot>
				</table>
			{/if}
			</td>
		</tr>
		{/if}
	</tfoot>
</table>

<p class="help">Toutes les écritures sont libellées en {$config.monnaie}.</p>

{include file="admin/_foot.tpl"}


|

<
<
<
<
<
<
<
<
|
|
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
>
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




1
2
3
4








5
6












7























8



9
10

















11
12
13
14
{include file="admin/_head.tpl" title="Compte de résultat" current="acc/years"}

{include file="acc/reports/_header.tpl" current="statement" title="Compte de résultat"}









{include file="acc/reports/_statement.tpl" statement=$general caption1="Charges" caption2="Produits"}













{if !empty($volunteering.expense_sum) || !empty($volunteering.revenue_sum)}























	<h2 class="ruler">Contributions en nature</h2>



	{include file="acc/reports/_statement.tpl" statement=$volunteering header=false caption1="Emplois des contributions volontaires en nature" caption2="Contributions volontaires en nature"}
{/if}


















<p class="help">Toutes les écritures sont libellées en {$config.monnaie}.</p>

{include file="admin/_foot.tpl"}

Modified src/templates/acc/reports/trial_balance.tpl from [f08d97e86e] to [98a27d9282].

1
2
3
4
5
6
7
8
9
10
{include file="admin/_head.tpl" title="Balance générale" current="acc/years"}

{include file="acc/reports/_header.tpl" current="trial_balance"}

<table class="list">
	<thead>
		<tr>
			<td>Numéro</td>
			<th>Compte</th>
			<td class="money">Total des débits</td>


|







1
2
3
4
5
6
7
8
9
10
{include file="admin/_head.tpl" title="Balance générale" current="acc/years"}

{include file="acc/reports/_header.tpl" current="trial_balance" title="Balance générale"}

<table class="list">
	<thead>
		<tr>
			<td>Numéro</td>
			<th>Compte</th>
			<td class="money">Total des débits</td>

Modified src/templates/acc/search.tpl from [b1c54ffaff] to [1184e1ecfe].

26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

50

51
52
53
54
55
56
57
			</tr>
		</thead>
		<tbody>
			{foreach from=$result item="row"}
				<tr>
					{*if $session->canAccess('membres', Membres::DROIT_ADMIN)}<td class="check"><input type="checkbox" name="selected[]" value="{$row.id}" /></td>{/if*}
					{foreach from=$row key="key" item="value"}
						{if $key == 'id'}
						<td class="num">
							<a href="{$admin_url}acc/transactions/details.php?id={$value}">{$value}</a>
						</td>
						{else}
						<td>
							{if $key == 'credit' || $key == 'debit'}
								{$value|raw|html_money:false}
							{elseif null == $value}
								<em>(nul)</em>
							{else}
								{$value}
							{/if}
						</td>
						{/if}
					{/foreach}
					<td class="actions">

						{linkbutton shape="search" label="Détails" href="!acc/transactions/details.php?id=%d"|args:$row.id}

					</td>
				</tr>
			{/foreach}
		</tbody>
	{*if $session->canAccess('membres', Membres::DROIT_ADMIN)}
		{include file="admin/membres/_list_actions.tpl" colspan=count($result_header)+1}
	{/if*}







|
















>
|
>







26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
			</tr>
		</thead>
		<tbody>
			{foreach from=$result item="row"}
				<tr>
					{*if $session->canAccess('membres', Membres::DROIT_ADMIN)}<td class="check"><input type="checkbox" name="selected[]" value="{$row.id}" /></td>{/if*}
					{foreach from=$row key="key" item="value"}
						{if $key == 'transaction_id'}
						<td class="num">
							<a href="{$admin_url}acc/transactions/details.php?id={$value}">{$value}</a>
						</td>
						{else}
						<td>
							{if $key == 'credit' || $key == 'debit'}
								{$value|raw|html_money:false}
							{elseif null == $value}
								<em>(nul)</em>
							{else}
								{$value}
							{/if}
						</td>
						{/if}
					{/foreach}
					<td class="actions">
						{if $row.transaction_id}
						{linkbutton shape="search" label="Détails" href="!acc/transactions/details.php?id=%d"|args:$row.transaction_id}
						{/if}
					</td>
				</tr>
			{/foreach}
		</tbody>
	{*if $session->canAccess('membres', Membres::DROIT_ADMIN)}
		{include file="admin/membres/_list_actions.tpl" colspan=count($result_header)+1}
	{/if*}

Modified src/templates/acc/transactions/edit.tpl from [e42938496f] to [4887135527].

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
			{/if}
		</fieldset>
	{/foreach}

	<fieldset>
		<legend>Détails facultatifs</legend>
		<dl data-types="t{$transaction::TYPE_REVENUE} t{$transaction::TYPE_EXPENSE} t{$transaction::TYPE_TRANSFER}">
			{input type="text" name="payment_reference" label="Référence de paiement" help="Numéro de chèque, numéro de transaction CB, etc." source=$transaction}
		</dl>
		<dl>
			{input type="list" multiple=true name="users" label="Membres associés" target="membres/selector.php" default=$linked_users}
			{input type="textarea" name="notes" label="Remarques" rows=4 cols=30 source=$transaction}

			{input type="file" name="file" label="Ajouter un fichier joint"}
		</dl>
		<dl data-types="all-but-advanced">
			{if count($analytical_accounts) > 1}
				{input type="select" name="id_analytical" label="Projet (compte analytique)" options=$analytical_accounts}
			{/if}
		</dl>
	</fieldset>

	<p class="submit">
		{csrf_field key="acc_edit_%d"|args:$transaction.id}
		{button type="submit" name="save" label="Enregistrer" shape="right" class="main"}







|









|







50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
			{/if}
		</fieldset>
	{/foreach}

	<fieldset>
		<legend>Détails facultatifs</legend>
		<dl data-types="t{$transaction::TYPE_REVENUE} t{$transaction::TYPE_EXPENSE} t{$transaction::TYPE_TRANSFER}">
			{input type="text" name="payment_reference" label="Référence de paiement" help="Numéro de chèque, numéro de transaction CB, etc." default=$first_line.reference}
		</dl>
		<dl>
			{input type="list" multiple=true name="users" label="Membres associés" target="membres/selector.php" default=$linked_users}
			{input type="textarea" name="notes" label="Remarques" rows=4 cols=30 source=$transaction}

			{input type="file" name="file" label="Ajouter un fichier joint"}
		</dl>
		<dl data-types="all-but-advanced">
			{if count($analytical_accounts) > 1}
				{input type="select" name="id_analytical" label="Projet (compte analytique)" options=$analytical_accounts default=$first_line.id_analytical}
			{/if}
		</dl>
	</fieldset>

	<p class="submit">
		{csrf_field key="acc_edit_%d"|args:$transaction.id}
		{button type="submit" name="save" label="Enregistrer" shape="right" class="main"}

Modified src/templates/acc/years/index.tpl from [1a1526a67f] to [f4fb5ef736].

1
2
3



4
5
6
7
8
9
10
{include file="admin/_head.tpl" title="Exercices" current="acc/years"}

<nav class="tabs">



	<ul>
		<li class="current"><a href="{$self_url}">Exercices</a></li>
		{if $session->canAccess('compta', Membres::DROIT_ADMIN)}
		<li><a href="{$admin_url}acc/years/new.php">Nouvel exercice</a></li>
		{/if}
		<li><a href="{$admin_url}acc/reports/projects.php">Projets <em>(compta analytique)</em></a></li>
	</ul>



>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
{include file="admin/_head.tpl" title="Exercices" current="acc/years"}

<nav class="tabs">
	<aside>
		{linkbutton shape="search" href="!acc/search.php" label="Recherche"}
	</aside>
	<ul>
		<li class="current"><a href="{$self_url}">Exercices</a></li>
		{if $session->canAccess('compta', Membres::DROIT_ADMIN)}
		<li><a href="{$admin_url}acc/years/new.php">Nouvel exercice</a></li>
		{/if}
		<li><a href="{$admin_url}acc/reports/projects.php">Projets <em>(compta analytique)</em></a></li>
	</ul>
29
30
31
32
33
34
35
36
37


38
39
40
41
42
43
44


45
46
47
48
49
50
51




52
53
54
55
56
57
58
59
60
61
62
63
64



65
66
67
68
69
70
71
72
73
			<figure>
				<img src="{$admin_url}acc/reports/graph_plot_all.php?type=result" alt="" />
			</figure>
		</section>
	</section>
	{/if}

	<dl class="list">
	{foreach from=$list item="year"}


		<dt>{$year.label}</dt>
		<dd class="desc">
			{if $year.closed}Clôturé{else}En cours{/if}
			| Du {$year.start_date|date_short} au {$year.end_date|date_short}
			| <a href="../charts/accounts/?id={$year.id_chart}">Plan comptable</a>
		</dd>
		<dd class="desc">


			<a href="{$admin_url}acc/reports/graphs.php?year={$year.id}">Graphiques</a>
			| <a href="{$admin_url}acc/reports/trial_balance.php?year={$year.id}">Balance générale</a>
			| <a href="{$admin_url}acc/reports/journal.php?year={$year.id}">Journal général</a>
			| <a href="{$admin_url}acc/reports/ledger.php?year={$year.id}">Grand livre</a>
			| <a href="{$admin_url}acc/reports/statement.php?year={$year.id}">Compte de résultat</a>
			| <a href="{$admin_url}acc/reports/balance_sheet.php?year={$year.id}">Bilan</a>
		</dd>




		{if $session->canAccess('compta', Membres::DROIT_ADMIN)}
		<dd class="actions">
			{linkbutton label="Export CSV" shape="export" href="import.php?id=%d&export=csv"|args:$year.id}
			{linkbutton label="Export tableur" shape="export" href="import.php?id=%d&export=ods"|args:$year.id}
			{if !$year.closed}
				{linkbutton label="Import" shape="upload" href="import.php?id=%d"|args:$year.id}
				{linkbutton label="Balance d'ouverture" shape="reset" href="balance.php?id=%d"|args:$year.id}
				{linkbutton label="Modifier" shape="edit" href="edit.php?id=%d"|args:$year.id}
				{linkbutton label="Clôturer" shape="lock" href="close.php?id=%d"|args:$year.id}
				{linkbutton label="Supprimer" shape="delete" href="delete.php?id=%d"|args:$year.id}
			{/if}
		</dd>
		{/if}



	{/foreach}
	</dl>
{else}
	<p class="block alert">
		Il n'y a pas d'exercice en cours.
	</p>
{/if}

{include file="admin/_foot.tpl"}







|

>
>
|
<
<
<
|
|
|
>
>
|
|
|
|
|
|
|
>
>
>
>
|
<
|
|
|
|
|
|
|
|
|
<
|
>
>
>

|







32
33
34
35
36
37
38
39
40
41
42
43



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

61
62
63
64
65
66
67
68
69

70
71
72
73
74
75
76
77
78
79
80
81
82
			<figure>
				<img src="{$admin_url}acc/reports/graph_plot_all.php?type=result" alt="" />
			</figure>
		</section>
	</section>
	{/if}

	<table class="list">
	{foreach from=$list item="year"}
		<tbody>
			<tr>
				<th><h3>{$year.label}</h3></th>



				<td>{$year.nb_transactions} écritures | <a href="../charts/accounts/?id={$year.id_chart}">Plan comptable</a></td>
			</tr>
			<tr>
				<td>{$year.start_date|date_short} au {$year.end_date|date_short}</td>
				<td>
					<a href="{$admin_url}acc/reports/graphs.php?year={$year.id}">Graphiques</a>
					| <a href="{$admin_url}acc/reports/trial_balance.php?year={$year.id}">Balance générale</a>
					| <a href="{$admin_url}acc/reports/journal.php?year={$year.id}">Journal général</a>
					| <a href="{$admin_url}acc/reports/ledger.php?year={$year.id}">Grand livre</a>
					| <a href="{$admin_url}acc/reports/statement.php?year={$year.id}">Compte de résultat</a>
					| <a href="{$admin_url}acc/reports/balance_sheet.php?year={$year.id}">Bilan</a>
				</td>
			</tr>
			<tr>
				<td><em>{if $year.closed}Clôturé{else}En cours{/if}</em></td>
				<td>
				{if $session->canAccess('compta', Membres::DROIT_ADMIN)}

					{linkbutton label="Export CSV" shape="export" href="import.php?id=%d&export=csv"|args:$year.id}
					{linkbutton label="Export tableur" shape="export" href="import.php?id=%d&export=ods"|args:$year.id}
					{if !$year.closed}
						{linkbutton label="Import" shape="upload" href="import.php?id=%d"|args:$year.id}
						{linkbutton label="Balance d'ouverture" shape="reset" href="balance.php?id=%d"|args:$year.id}
						{linkbutton label="Modifier" shape="edit" href="edit.php?id=%d"|args:$year.id}
						{linkbutton label="Clôturer" shape="lock" href="close.php?id=%d"|args:$year.id}
						{linkbutton label="Supprimer" shape="delete" href="delete.php?id=%d"|args:$year.id}
					{/if}

				{/if}
				</td>
			</tr>
		</tbody>
	{/foreach}
	</table>
{else}
	<p class="block alert">
		Il n'y a pas d'exercice en cours.
	</p>
{/if}

{include file="admin/_foot.tpl"}

Modified src/templates/admin/login.tpl from [2b0fb0f94e] to [5f99a8a020].

57
58
59
60
61
62
63
64
65
66
67
68


69
70
71
72
        <a href="{$admin_url}password.php">Pas de mot de passe ou mot de passe perdu ?</a>
    </p>

</form>

{literal}
<script type="text/javascript">
g.enhancePasswordField($('#f_passe'));

if (!!window.document.documentMode) {
    document.getElementById('old_browser').style.display = 'block';
}


</script>
{/literal}

{include file="admin/_foot.tpl"}







<
|
<


>
>




57
58
59
60
61
62
63

64

65
66
67
68
69
70
71
72
        <a href="{$admin_url}password.php">Pas de mot de passe ou mot de passe perdu ?</a>
    </p>

</form>

{literal}
<script type="text/javascript">

if (window.navigator.userAgent.match(/MSIE|Trident\/|Edge\//)) {

    document.getElementById('old_browser').style.display = 'block';
}

g.enhancePasswordField($('#f_passe'));
</script>
{/literal}

{include file="admin/_foot.tpl"}

Modified src/templates/admin/membres/recherche.tpl from [9258528c92] to [5a5c80b31f].

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41


42
43
44

45
46
47

48
49
50
51
52
53
54
55
56
57
58
59
60
	{/if}

	<p class="help">{$result|count} membres trouvés pour cette recherche.</p>
	<table class="list search">
		<thead>
			<tr>
				{if $session->canAccess('membres', Membres::DROIT_ADMIN)}<td class="check"><input type="checkbox" value="Tout cocher / décocher" id="f_all" /><label for="f_all"></label></td>{/if}
				{foreach from=$result_header key="c" item="cfg"}
					<td>{$cfg.title}</td>
				{/foreach}
				<td></td>
			</tr>
		</thead>
		<tbody>
			{foreach from=$result item="row"}
				<tr>
					{if $session->canAccess('membres', Membres::DROIT_ADMIN)}<td class="check">{input type="checkbox" name="selected[]" value=$row.id}</td>{/if}
					{foreach from=$row key="key" item="value"}
						<?php $link = false; ?>
						{if isset($result_header[$key])}
							<td>
								{if !$link}
									<a href="{$admin_url}membres/fiche.php?id={$row.id}">
								{/if}

								{$value|raw|display_champ_membre:$key}

								{if !$link}
									<?php $link = true; ?>
									</a>
								{/if}
							</td>


						{/if}
					{/foreach}
					<td class="actions">

						{linkbutton shape="user" label="Fiche membre" href="!membres/fiche.php?id=%d"|args:$row.id}
						{if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
						{linkbutton shape="edit" label="Modifier" href="!membres/modifier.php?id=%d"|args:$row.id}

						{/if}
					</td>
				</tr>
			{/foreach}
		</tbody>
	{if $session->canAccess('membres', Membres::DROIT_ADMIN)}
		{include file="admin/membres/_list_actions.tpl" colspan=count($result_header)+1}
	{/if}
	</table>

	{if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
		</form>
	{/if}







|
|







|




|
|
|

|

|
|
|
|

>
>



>
|
|
|
>





|







10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
	{/if}

	<p class="help">{$result|count} membres trouvés pour cette recherche.</p>
	<table class="list search">
		<thead>
			<tr>
				{if $session->canAccess('membres', Membres::DROIT_ADMIN)}<td class="check"><input type="checkbox" value="Tout cocher / décocher" id="f_all" /><label for="f_all"></label></td>{/if}
				{foreach from=$result_header item="label"}
					<td>{$label}</td>
				{/foreach}
				<td></td>
			</tr>
		</thead>
		<tbody>
			{foreach from=$result item="row"}
				<tr>
					{if $session->canAccess('membres', Membres::DROIT_ADMIN)}<td class="check">{if $row._user_id}{input type="checkbox" name="selected[]" value=$row._user_id}{/if}</td>{/if}
					{foreach from=$row key="key" item="value"}
						<?php $link = false; ?>
						{if isset($result_header[$key])}
							<td>
							{if !$link && $row._user_id}
								<a href="{$admin_url}membres/fiche.php?id={$row._user_id}">
							{/if}

							{$value|raw|display_champ_membre:$key}

							{if !$link}
								<?php $link = true; ?>
								</a>
							{/if}
							</td>
						{elseif substr($key, 0, 1) != '_'}
							<td>{$value}</td>
						{/if}
					{/foreach}
					<td class="actions">
						{if $row._user_id}
							{linkbutton shape="user" label="Fiche membre" href="!membres/fiche.php?id=%d"|args:$row._user_id}
							{if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
								{linkbutton shape="edit" label="Modifier" href="!membres/modifier.php?id=%d"|args:$row._user_id}
							{/if}
						{/if}
					</td>
				</tr>
			{/foreach}
		</tbody>
	{if $session->canAccess('membres', Membres::DROIT_ADMIN) && $row._user_id}
		{include file="admin/membres/_list_actions.tpl" colspan=count($result_header)+1}
	{/if}
	</table>

	{if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
		</form>
	{/if}

Modified src/templates/common/search/advanced.tpl from [793be9f367] to [e6d27e3303].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17




18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
assert(isset($columns));
assert(isset($action_url));
assert(isset($query));
assert(isset($is_admin));
$sql_disabled = !$is_admin;
?>

{form_errors}

<form method="post" action="{$action_url}" id="queryBuilderForm">
	<fieldset>
	{if $sql_query}
		<legend>Schéma des tables SQL</legend>
		<pre class="sql_schema">{foreach from=$schema item="table"}{$table}<br />{/foreach}</pre>
		<dl>
			{input type="textarea" name="sql_query" cols="100" rows="7" required=1 label="Requête SQL" help="Si aucune limite n'est précisée, une limite de 100 résultats sera appliquée." default=$sql_query disabled=$sql_disabled}




		</dl>
		<p class="submit">
			{button type="submit" name="run" label="Exécuter" shape="search" class="main"}
			<input type="hidden" name="id" value="{$search.id}" />
			{if $search.id}
				{button name="save" value=1 type="submit" label="Enregistrer : %s"|args:$search.intitule|truncate:40:"…":true shape="upload"}
			{else}
				{button name="save" value=1 type="submit" label="Enregistrer cette recherche" shape="upload"}
			{/if}
		</p>
	{else}
		<legend>Rechercher</legend>
		<div class="queryBuilder" id="queryBuilder"></div>
		<p class="actions">
			<label>Trier par
				<select name="order">
					{foreach from=$columns key="column" item="properties"}
					<option value="{$column}"{if $query.order == $column} selected="selected"{/if}>{$properties.label}</option>





|






|



|
>
>
>
>










|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
assert(isset($columns));
assert(isset($action_url));
assert(isset($query));
assert(isset($is_admin));
$sql_disabled = !$is_admin || (!$session->canAccess('config', Membres::DROIT_ADMIN) && $is_unprotected);
?>

{form_errors}

<form method="post" action="{$action_url}" id="queryBuilderForm">
	<fieldset>
	{if $sql_query && !$sql_disabled}
		<legend>Schéma des tables SQL</legend>
		<pre class="sql_schema">{foreach from=$schema item="table"}{$table}<br />{/foreach}</pre>
		<dl>
			{input type="textarea" name="sql_query" cols="100" rows="7" required=1 label="Requête SQL" help="Si aucune limite n'est précisée, une limite de 100 résultats sera appliquée." default=$sql_query}
			{if $session->canAccess('config', Membres::DROIT_ADMIN)}
				{input type="checkbox" name="unprotected" value=1 label="Autoriser l'accès à toutes les tables de la base de données" default=$is_unprotected}
				<dd class="help">Attention : en cochant cette case vous autorisez la requête à lire toutes les données de toutes les tables de la base de données&nbsp;!</dd>
			{/if}
		</dl>
		<p class="submit">
			{button type="submit" name="run" label="Exécuter" shape="search" class="main"}
			<input type="hidden" name="id" value="{$search.id}" />
			{if $search.id}
				{button name="save" value=1 type="submit" label="Enregistrer : %s"|args:$search.intitule|truncate:40:"…":true shape="upload"}
			{else}
				{button name="save" value=1 type="submit" label="Enregistrer cette recherche" shape="upload"}
			{/if}
		</p>
	{elseif !$sql_disabled}
		<legend>Rechercher</legend>
		<div class="queryBuilder" id="queryBuilder"></div>
		<p class="actions">
			<label>Trier par
				<select name="order">
					{foreach from=$columns key="column" item="properties"}
					<option value="{$column}"{if $query.order == $column} selected="selected"{/if}>{$properties.label}</option>
48
49
50
51
52
53
54



55
56
57
58
59
60
61
			{else}
				{button name="save" value=1 type="submit" label="Enregistrer cette recherche" shape="upload"}
			{/if}
			{if $is_admin}
				{button name="to_sql" value=1 type="submit" label="Recherche SQL" shape="edit"}
			{/if}
		</p>



	{/if}
	</fieldset>
</form>

<script type="text/javascript">
var columns = {$columns|escape:'json'};








>
>
>







52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
			{else}
				{button name="save" value=1 type="submit" label="Enregistrer cette recherche" shape="upload"}
			{/if}
			{if $is_admin}
				{button name="to_sql" value=1 type="submit" label="Recherche SQL" shape="edit"}
			{/if}
		</p>
	{else}
		<legend>Recherche enregistrée</legend>
		<h3>{$search.intitule}</h3>
	{/if}
	</fieldset>
</form>

<script type="text/javascript">
var columns = {$columns|escape:'json'};

Modified src/templates/common/search/saved_searches.tpl from [22a975d3e5] to [4b50bfbcd9].

14
15
16
17
18
19
20
21
22
23

24
25
26

27






28
29
30
31
32
33
34
{if $mode == 'edit'}
	{form_errors}

	<form method="post" action="{$self_url}">
		<fieldset>
			<legend>Modifier une recherche enregistrée</legend>
			<dl>
				<dt><label for="f_intitule">Intitulé</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
				<dd><input type="text" name="intitule" id="f_intitule" value="{form_field name="intitule" data=$recherche}" size="80" required="required" /></dd>
				<dt>Statut</dt>

				<dd><label><input type="radio" name="prive" value="1" {if $recherche.id_membre}checked="checked"{/if} /> Recherche privée</label> — Visible seulement par moi-même</dd>
				<dd><label><input type="radio" name="prive" value="0" {if !$recherche.id_membre}checked="checked"{/if} /> Recherche publique</label> — Visible et exécutable par tous les membres ayant accès à la gestion {$target}</dd>
				<dt>Type</dt>

				<dd>{if $recherche.type == Recherche::TYPE_JSON}Avancée{else}SQL{/if}</dd>






				<dt>Cible</dt>
				<dd>{$recherche.cible}</dd>
			</dl>
		</fieldset>

		<p class="submit">
			{csrf_field key="edit_recherche_%s"|args:$recherche.id}







<
|

>
|
|

>
|
>
>
>
>
>
>







14
15
16
17
18
19
20

21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{if $mode == 'edit'}
	{form_errors}

	<form method="post" action="{$self_url}">
		<fieldset>
			<legend>Modifier une recherche enregistrée</legend>
			<dl>

				{input type="text" name="intitule" label="Intitulé" required=1 source=$recherche}
				<dt>Statut</dt>
				<?php $checked = (int)(bool)$recherche->id_membre; ?>
				{input type="radio" name="prive" value="1" default=$checked label="Recherche privée" help="Visible seulement par moi-même"}
				{input type="radio" name="prive" value="0" default=$checked label="Recherche publique" help="Visible et exécutable par tous les membres ayant accès à la gestion %s"|args:$target}
				<dt>Type</dt>
				<dd>
					{if $recherche.type == Recherche::TYPE_JSON}
						Avancée
					{elseif $recherche.type == Recherche::TYPE_SQL_UNPROTECTED}
						SQL non protégée
					{else}
						SQL
					{/if}</dd>
				<dt>Cible</dt>
				<dd>{$recherche.cible}</dd>
			</dl>
		</fieldset>

		<p class="submit">
			{csrf_field key="edit_recherche_%s"|args:$recherche.id}

Modified src/templates/services/save.tpl from [69201d6af6] to [76ea220052].

84
85
86
87
88
89
90
91
92
93
94
95

96
97
98
99
100
101
102
			{input type="date" name="expiry_date" default=$today label="Date d'expiration de l'inscription"}
			{input type="checkbox" name="paid" value="1" default="1" label="Marquer cette inscription comme payée"}
			<dd class="help">Décocher cette case pour pouvoir suivre les règlements de personnes qui payent en plusieurs fois. Il sera possible de cocher cette case lorsque le solde aura été réglé.</dd>
		</dl>
	</fieldset>

	<fieldset class="accounting">
		<legend>Enregistrement en comptabilité</legend>

		<dl>
			{input type="money" name="amount" label="Montant réglé par le membre" fake_required=1 help="En cas de règlement en plusieurs fois il sera possible d'ajouter des règlements via la page de suivi des activités de ce membre."}
			{input type="list" target="acc/charts/accounts/selector.php?targets=%s"|args:$account_targets name="account" label="Compte de règlement" required=1}

			{input type="text" name="payment_reference" label="Référence de paiement" help="Numéro de chèque, numéro de transaction CB, etc."}
		</dl>
{/if}
	</fieldset>

	<p class="submit">
		{csrf_field key=$csrf_key}







|




>







84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
			{input type="date" name="expiry_date" default=$today label="Date d'expiration de l'inscription"}
			{input type="checkbox" name="paid" value="1" default="1" label="Marquer cette inscription comme payée"}
			<dd class="help">Décocher cette case pour pouvoir suivre les règlements de personnes qui payent en plusieurs fois. Il sera possible de cocher cette case lorsque le solde aura été réglé.</dd>
		</dl>
	</fieldset>

	<fieldset class="accounting">
		<legend>{input type="checkbox" name="create_payment" value=1 default=1 label="Enregistrer en comptabilité"}</legend>

		<dl>
			{input type="money" name="amount" label="Montant réglé par le membre" fake_required=1 help="En cas de règlement en plusieurs fois il sera possible d'ajouter des règlements via la page de suivi des activités de ce membre."}
			{input type="list" target="acc/charts/accounts/selector.php?targets=%s"|args:$account_targets name="account" label="Compte de règlement" required=1}
			{input type="text" name="reference" label="Numéro de pièce comptable" help="Numéro de facture, de note de frais, etc."}
			{input type="text" name="payment_reference" label="Référence de paiement" help="Numéro de chèque, numéro de transaction CB, etc."}
		</dl>
{/if}
	</fieldset>

	<p class="submit">
		{csrf_field key=$csrf_key}
149
150
151
152
153
154
155




156
157
158
159
});

var selected = document.querySelector('input[name="id_service"]:checked, input[name="id_service"]');
selected.checked = true;

g.toggle('.accounting', false);
selectService(selected);




</script>
{/literal}

{include file="admin/_foot.tpl"}







>
>
>
>




150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
});

var selected = document.querySelector('input[name="id_service"]:checked, input[name="id_service"]');
selected.checked = true;

g.toggle('.accounting', false);
selectService(selected);

$('#f_create_payment_1').onchange = (e) => {
	g.toggle('.accounting dl', $('#f_create_payment_1').checked);
};
</script>
{/literal}

{include file="admin/_foot.tpl"}

Modified src/www/admin/acc/reports/graph_pie.php from [ca1e3741d3] to [f97272ab9f].

1
2
3
4
5
6
7
8
9




10


<?php
namespace Garradin;

use Garradin\Accounting\Graph;

require_once __DIR__ . '/_inc.php';

header('Content-Type: image/svg+xml');





echo Graph::pie(qg('type'), $criterias);











>
>
>
>
|
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
namespace Garradin;

use Garradin\Accounting\Graph;

require_once __DIR__ . '/_inc.php';

header('Content-Type: image/svg+xml');

$expiry = time() + 1800;
$hash = sha1('pie_' . json_encode($criterias));

if (!Utils::HTTPCache($hash, $expiry)) {
	echo Graph::pie(qg('type'), $criterias);
}

Modified src/www/admin/acc/reports/graph_plot.php from [041f1bcddf] to [77ffd7aec0].

1
2
3
4
5
6
7
8
9




10


<?php
namespace Garradin;

use Garradin\Accounting\Graph;

require_once __DIR__ . '/_inc.php';

header('Content-Type: image/svg+xml');





echo Graph::plot(qg('type'), $criterias);











>
>
>
>
|
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
namespace Garradin;

use Garradin\Accounting\Graph;

require_once __DIR__ . '/_inc.php';

header('Content-Type: image/svg+xml');

$expiry = time() + 1800;
$hash = sha1('plot_' . json_encode($criterias));

if (!Utils::HTTPCache($hash, $expiry)) {
	echo Graph::plot(qg('type'), $criterias);
}

Modified src/www/admin/acc/reports/graph_plot_all.php from [d2282ca501] to [80e43ce701].

1
2
3
4
5
6
7
8
9
10
11




12

<?php
namespace Garradin;

use Garradin\Accounting\Graph;

require_once __DIR__ . '/../_inc.php';

qv(['type' => 'string|required']);

header('Content-Type: image/svg+xml');





echo Graph::plot(qg('type'), [], Graph::MONTHLY_INTERVAL, 600);












>
>
>
>
|
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
namespace Garradin;

use Garradin\Accounting\Graph;

require_once __DIR__ . '/../_inc.php';

qv(['type' => 'string|required']);

header('Content-Type: image/svg+xml');

$expiry = time() + 1800;
$hash = sha1('plot_all');

if (!Utils::HTTPCache($hash, $expiry)) {
	echo Graph::plot(qg('type'), [], Graph::MONTHLY_INTERVAL, 600);
}

Modified src/www/admin/acc/reports/statement.php from [a66219b6ce] to [3be4e9af46].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php

namespace Garradin;

use Garradin\Accounting\Reports;
use Garradin\Entities\Accounting\Account;

require_once __DIR__ . '/_inc.php';

$revenue = Reports::getClosingSumsWithAccounts($criterias + ['position' => Account::REVENUE]);
$expense = Reports::getClosingSumsWithAccounts($criterias + ['position' => Account::EXPENSE], null, true);

$get_sum = function (array $in): int {
	$sum = 0;

	foreach ($in as $row) {
		$sum += $row->sum;
	}

	return abs($sum);
};

$revenue_sum = $get_sum($revenue);
$expense_sum = $get_sum($expense);
$result = $revenue_sum - $expense_sum;

$tpl->assign(compact('revenue', 'expense', 'revenue_sum', 'expense_sum', 'result'));

$tpl->display('acc/reports/statement.tpl');









<
<
|
<
<
|
<
<
|
<
<
<
<
<
<
<
<
<
<

1
2
3
4
5
6
7
8
9


10


11


12










13
<?php

namespace Garradin;

use Garradin\Accounting\Reports;
use Garradin\Entities\Accounting\Account;

require_once __DIR__ . '/_inc.php';



$tpl->assign('general', Reports::getStatement($criterias + ['exclude_type' => Account::TYPE_VOLUNTEERING]));


$tpl->assign('volunteering', Reports::getStatement($criterias + ['type' => Account::TYPE_VOLUNTEERING]));













$tpl->display('acc/reports/statement.tpl');

Modified src/www/admin/acc/transactions/edit.php from [4ea18fe075] to [88e340bd1d].

77
78
79
80
81
82
83


84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
else {
	$lines = $transaction->getLinesWithAccounts();

	foreach ($lines as $k => &$line) {
		$line->account = [$line->id_account => sprintf('%s — %s', $line->account_code, $line->account_name)];
	}
}



if ($transaction->type != Transaction::TYPE_ADVANCED) {
	$types_accounts = $transaction->getTypesAccounts();
}

$amount = $transaction->getLinesCreditSum();

$tpl->assign(compact('transaction', 'lines', 'types_accounts', 'amount'));

$tpl->assign('types_details', Transaction::getTypesDetails());
$tpl->assign('chart_id', $chart->id());
$tpl->assign('analytical_accounts', ['' => '-- Aucun'] + $accounts->listAnalytical());
$tpl->assign('linked_users', $transaction->listLinkedUsersAssoc());

$tpl->display('acc/transactions/edit.tpl');







>
>







|







77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
else {
	$lines = $transaction->getLinesWithAccounts();

	foreach ($lines as $k => &$line) {
		$line->account = [$line->id_account => sprintf('%s — %s', $line->account_code, $line->account_name)];
	}
}

$first_line = $transaction->getFirstLine();

if ($transaction->type != Transaction::TYPE_ADVANCED) {
	$types_accounts = $transaction->getTypesAccounts();
}

$amount = $transaction->getLinesCreditSum();

$tpl->assign(compact('transaction', 'lines', 'types_accounts', 'amount', 'first_line'));

$tpl->assign('types_details', Transaction::getTypesDetails());
$tpl->assign('chart_id', $chart->id());
$tpl->assign('analytical_accounts', ['' => '-- Aucun'] + $accounts->listAnalytical());
$tpl->assign('linked_users', $transaction->listLinkedUsersAssoc());

$tpl->display('acc/transactions/edit.tpl');

Modified src/www/admin/acc/transactions/new.php from [7173ab8670] to [bb4740dd64].

80
81
82
83
84
85
86


87








88
89
90
91
92
93
94
95
96
		Utils::redirect(Utils::getSelfURL(false) . '?ok=' . $transaction->id());
	}
	catch (UserException $e) {
		$form->addError($e->getMessage());
	}
}



$tpl->assign('date', $session->get('acc_last_date') ?: $current_year->start_date->format('d/m/Y'));








$tpl->assign(compact('transaction', 'payoff_for', 'amount', 'lines'));
$tpl->assign('payoff_targets', implode(':', [Account::TYPE_BANK, Account::TYPE_CASH, Account::TYPE_OUTSTANDING]));
$tpl->assign('ok', (int) qg('ok'));

$tpl->assign('types_details', Transaction::getTypesDetails());
$tpl->assign('chart_id', $chart->id());

$tpl->assign('analytical_accounts', ['' => '-- Aucun'] + $accounts->listAnalytical());
$tpl->display('acc/transactions/new.tpl');







>
>
|
>
>
>
>
>
>
>
>









80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
		Utils::redirect(Utils::getSelfURL(false) . '?ok=' . $transaction->id());
	}
	catch (UserException $e) {
		$form->addError($e->getMessage());
	}
}

$date = new \DateTime;

if ($session->get('acc_last_date')) {
	$date = \DateTime::createFromFormat('!d/m/Y', $session->get('acc_last_date'));
}

if (!$date || ($date < $current_year->start_date || $date > $current_year->end_date)) {
	$date = $current_year->start_date;
}

$tpl->assign('date', $date->format('d/m/Y'));
$tpl->assign(compact('transaction', 'payoff_for', 'amount', 'lines'));
$tpl->assign('payoff_targets', implode(':', [Account::TYPE_BANK, Account::TYPE_CASH, Account::TYPE_OUTSTANDING]));
$tpl->assign('ok', (int) qg('ok'));

$tpl->assign('types_details', Transaction::getTypesDetails());
$tpl->assign('chart_id', $chart->id());

$tpl->assign('analytical_accounts', ['' => '-- Aucun'] + $accounts->listAnalytical());
$tpl->display('acc/transactions/new.tpl');

Modified src/www/admin/acc/years/balance.php from [1c5fd61cf1] to [19f637ea79].

16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
	throw new UserException('Exercice inconnu.');
}

if ($year->closed) {
	throw new UserException('Impossible de modifier un exercice clôturé.');
}

if (f('next') && !f('from_year')) {
	Utils::redirect('/admin/acc/years/');
}

if (f('save') && $form->check('acc_years_balance_' . $year->id()))
{
	try {
		$transaction = new Transaction;
		$transaction->importFromBalanceForm($year);
		$transaction->save();








<
<
<
<







16
17
18
19
20
21
22




23
24
25
26
27
28
29
	throw new UserException('Exercice inconnu.');
}

if ($year->closed) {
	throw new UserException('Impossible de modifier un exercice clôturé.');
}





if (f('save') && $form->check('acc_years_balance_' . $year->id()))
{
	try {
		$transaction = new Transaction;
		$transaction->importFromBalanceForm($year);
		$transaction->save();

41
42
43
44
45
46
47

48
49
50
51
52
53
54
55

$previous_year = null;
$chart_change = false;
$lines = [[]];
$lines_accounts = [[]];
$years = Years::listClosed();


if (!count($years)) {
	$previous_year = 0;
}
elseif (null !== f('from_year')) {
	$previous_year = (int)f('from_year');
	$previous_year = Years::get($previous_year);

	if (!$previous_year) {







>
|







37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

$previous_year = null;
$chart_change = false;
$lines = [[]];
$lines_accounts = [[]];
$years = Years::listClosed();

// Empty balance
if (!count($years) || f('from_year') === '') {
	$previous_year = 0;
}
elseif (null !== f('from_year')) {
	$previous_year = (int)f('from_year');
	$previous_year = Years::get($previous_year);

	if (!$previous_year) {

Modified src/www/admin/acc/years/index.php from [7a17ddf96c] to [9f9b364525].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
namespace Garradin;

use Garradin\Accounting\Years;

require_once __DIR__ . '/../../_inc.php';

$session->requireAccess('compta', Membres::DROIT_ACCES);

$years = new Years;

$tpl->assign('list', $years->list(true));

$tpl->display('acc/years/index.tpl');









<
<
|


1
2
3
4
5
6
7
8
9


10
11
12
<?php
namespace Garradin;

use Garradin\Accounting\Years;

require_once __DIR__ . '/../../_inc.php';

$session->requireAccess('compta', Membres::DROIT_ACCES);



$tpl->assign('list', Years::list(true));

$tpl->display('acc/years/index.tpl');

Modified src/www/admin/common/delete_file.php from [f9d6581182] to [dbcb7d7cc0].

1
2
3
4
5
6
7
8
9
10
11
12
13

14




15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
namespace Garradin;

use Garradin\Services\Services;

if (!defined('Garradin\ROOT')) {
	die('Access denied.');
}

if (!isset($csrf_key, $redirect)) {
	throw new \InvalidArgumentException('Missing params');
}


$file = new Fichiers(qg('id'));





if (!$file->checkAccess($session))
{
    throw new UserException('Vous n\'avez pas accès à ce fichier.');
}

$form->runIf('delete', function () use ($file) {
	$file->remove();
}, $csrf_key, $redirect);

$tpl->assign(compact('file', 'csrf_key'));

$tpl->display('common/delete_file.tpl');













>
|
>
>
>
>













1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
namespace Garradin;

use Garradin\Services\Services;

if (!defined('Garradin\ROOT')) {
	die('Access denied.');
}

if (!isset($csrf_key, $redirect)) {
	throw new \InvalidArgumentException('Missing params');
}

try {
	$file = new Fichiers(qg('id'));
}
catch (\InvalidArgumentException $e) {
	throw new UserException($e->getMessage());
}

if (!$file->checkAccess($session))
{
    throw new UserException('Vous n\'avez pas accès à ce fichier.');
}

$form->runIf('delete', function () use ($file) {
	$file->remove();
}, $csrf_key, $redirect);

$tpl->assign(compact('file', 'csrf_key'));

$tpl->display('common/delete_file.tpl');

Modified src/www/admin/common/search.php from [19bc74c76e] to [f997b1b1c1].

20
21
22
23
24
25
26


27
28
29
30
31
32
33
34
35
36
37
38
39
40

41



42
43
44
45
46
47
48
49
50
51
52
53
54







55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

86







87
88
89
90
91
92
93

$text_query = trim(qg('qt'));
$result = null;
$sql_query = null;
$search = null;
$id = f('id') ?: qg('id');



// Recherche simple
if ($text_query !== '' && $target === 'membres' && empty($query->query))
{
	$query = $recherche->buildSimpleMemberQuery($text_query);
}
// Recherche existante
elseif ($id && empty($query->query))
{
	$search = $recherche->get($id);

	if (!$search) {
		throw new UserException('Recherche inconnue ou invalide');
	}


	if ($search->type === Recherche::TYPE_SQL) {



		$sql_query = $search->contenu;
	}
	else {
		$query = $search->query;
		$query->limit = (int) f('limit') ?: $query->limit;
	}
}

// Recherche SQL
if (f('sql_query')) {
	// Only admins can run custom queries, others can only run saved queries
	$session->requireAccess($target, Membres::DROIT_ADMIN);
	$sql_query = f('sql_query');







}

// Execute search
if ($query->query || $sql_query) {
	try {
		if ($sql_query) {
			$sql = $sql_query;
		}
		else {
			$sql = $recherche->buildQuery($target, $query->query, $query->order, $query->desc, $query->limit);
		}

	   $result = $recherche->searchSQL($target, $sql);
	}
	catch (UserException $e) {
		$form->addError($e->getMessage());
	}

	if (f('to_sql')) {
		$sql_query = $sql;
	}
}

if (null !== $result)
{
	if (count($result) == 1 && $text_query !== '' && $target === 'membres') {
		Utils::redirect(ADMIN_URL . 'membres/fiche.php?id=' . (int)$result[0]->id);
	}

	if (f('save') && !$form->hasErrors())
	{

		$type = $sql_query ? Recherche::TYPE_SQL : Recherche::TYPE_JSON;








		if ($id) {
			$recherche->edit($id, [
				'type'    => $type,
				'contenu' => $sql_query ?: $query,
			]);
		}







>
>














>
|
>
>
>













>
>
>
>
>
>
>












|













|




>
|
>
>
>
>
>
>
>







20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114

$text_query = trim(qg('qt'));
$result = null;
$sql_query = null;
$search = null;
$id = f('id') ?: qg('id');

$is_unprotected = false;

// Recherche simple
if ($text_query !== '' && $target === 'membres' && empty($query->query))
{
	$query = $recherche->buildSimpleMemberQuery($text_query);
}
// Recherche existante
elseif ($id && empty($query->query))
{
	$search = $recherche->get($id);

	if (!$search) {
		throw new UserException('Recherche inconnue ou invalide');
	}

	if ($search->type != Recherche::TYPE_JSON) {
		if ($search->type == Recherche::TYPE_SQL_UNPROTECTED) {
			$is_unprotected = true;
		}

		$sql_query = $search->contenu;
	}
	else {
		$query = $search->query;
		$query->limit = (int) f('limit') ?: $query->limit;
	}
}

// Recherche SQL
if (f('sql_query')) {
	// Only admins can run custom queries, others can only run saved queries
	$session->requireAccess($target, Membres::DROIT_ADMIN);
	$sql_query = f('sql_query');

	if ($session->canAccess('config', Membres::DROIT_ADMIN)) {
		$is_unprotected = (bool) f('unprotected');
	}
	else {
		$is_unprotected = false;
	}
}

// Execute search
if ($query->query || $sql_query) {
	try {
		if ($sql_query) {
			$sql = $sql_query;
		}
		else {
			$sql = $recherche->buildQuery($target, $query->query, $query->order, $query->desc, $query->limit);
		}

	   $result = $recherche->searchSQL($target, $sql, null, false, $is_unprotected);
	}
	catch (UserException $e) {
		$form->addError($e->getMessage());
	}

	if (f('to_sql')) {
		$sql_query = $sql;
	}
}

if (null !== $result)
{
	if (count($result) == 1 && $text_query !== '' && $target === 'membres') {
		Utils::redirect(ADMIN_URL . 'membres/fiche.php?id=' . (int)$result[0]->_user_id);
	}

	if (f('save') && !$form->hasErrors())
	{
		if (!$sql_query) {
			$type = Recherche::TYPE_JSON;
		}
		elseif ($is_unprotected) {
			$type = Recherche::TYPE_SQL_UNPROTECTED;
		}
		else {
			$type = Recherche::TYPE_SQL;
		}

		if ($id) {
			$recherche->edit($id, [
				'type'    => $type,
				'contenu' => $sql_query ?: $query,
			]);
		}
116
117
118
119
120
121
122
123
124
125
126
127
128
129





130
131
132
133
134
135
136
137
138

















139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
			],
		],
	]];
	$result = null;
}
elseif ($target === 'compta')
{
	$years = Years::list();
	$query->query = [[
		'operator' => 'AND',
		'conditions' => [
			[
				'column'   => 't.id_year',
				'operator' => '= ?',





				'values'   => [qg('year')],
			],
			[
				'column'   => 't.reference',
				'operator' => 'LIKE %?%',
				'values'   => '',
			],
		],
	]];

















	$query->desc = true;
	$result = null;
}

$columns = $recherche->getColumns($target);
$is_admin = $session->canAccess($target, Membres::DROIT_ADMIN);
$schema = $recherche->schema($target);

$tpl->assign(compact('query', 'sql_query', 'result', 'columns', 'is_admin', 'schema', 'search'));

if ($target == 'compta') {
	$tpl->display('acc/search.tpl');
}
else {
	$tpl->display('admin/membres/recherche.tpl');
}







|






>
>
>
>
>
|








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>








|







137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
			],
		],
	]];
	$result = null;
}
elseif ($target === 'compta')
{
	// Default
	$query->query = [[
		'operator' => 'AND',
		'conditions' => [
			[
				'column'   => 't.id_year',
				'operator' => '= ?',
				'values'   => [(int)qg('year') ?: Years::getCurrentOpenYearId()],
			],
			[
				'column'   => 't.label',
				'operator' => 'LIKE %?%',
				'values'   => '',
			],
			[
				'column'   => 't.reference',
				'operator' => 'LIKE %?%',
				'values'   => '',
			],
		],
	]];

	if (null !== qg('type')) {
		$query->query[0]['conditions'][] = [
			'column' => 't.type',
			'operator' => '= ?',
			'values' => [(int)qg('type')],
		];
	}

	if (null !== qg('account')) {
		$query->query[0]['conditions'][] = [
			'column' => 'a.code',
			'operator' => '= ?',
			'values' => [qg('account')],
		];
	}

	$query->desc = true;
	$result = null;
}

$columns = $recherche->getColumns($target);
$is_admin = $session->canAccess($target, Membres::DROIT_ADMIN);
$schema = $recherche->schema($target);

$tpl->assign(compact('query', 'sql_query', 'result', 'columns', 'is_admin', 'schema', 'search', 'target', 'is_unprotected'));

if ($target == 'compta') {
	$tpl->display('acc/search.tpl');
}
else {
	$tpl->display('admin/membres/recherche.tpl');
}

Modified src/www/admin/membres/modifier.php from [6fe3b3ffab] to [a765c3047e].

76
77
78
79
80
81
82

83
84
85
86
87
88
89
90
91
92
93
94
        catch (UserException $e)
        {
            $form->addError($e->getMessage());
        }
    }
}


$tpl->assign('id_field_name', $config->get('champ_identifiant'));
$tpl->assign('passphrase', Utils::suggestPassword());
$tpl->assign('champs', $champs->getAll());

$tpl->assign('membres_cats', $cats->listSimple());
$tpl->assign('current_cat', f('id_categorie') ?: $membre->id_categorie);

$tpl->assign('can_change_id', $session->canAccess('membres', Membres::DROIT_ADMIN));

$tpl->assign('membre', $membre);

$tpl->display('admin/membres/modifier.tpl');







>












76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
        catch (UserException $e)
        {
            $form->addError($e->getMessage());
        }
    }
}

$config = Config::getInstance();
$tpl->assign('id_field_name', $config->get('champ_identifiant'));
$tpl->assign('passphrase', Utils::suggestPassword());
$tpl->assign('champs', $champs->getAll());

$tpl->assign('membres_cats', $cats->listSimple());
$tpl->assign('current_cat', f('id_categorie') ?: $membre->id_categorie);

$tpl->assign('can_change_id', $session->canAccess('membres', Membres::DROIT_ADMIN));

$tpl->assign('membre', $membre);

$tpl->display('admin/membres/modifier.tpl');

Modified src/www/admin/static/handheld.css from [02f3d81c1c] to [82c8dcdda9].

153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
	width: auto;
}

dl.describe {
	margin: 0 .5em;
}

dl.describe dt, dl.describe dd {
	float: none;
	width: auto;
}

dl.describe dt {
	font-size: .9em;
	color: #666;
}

dl.describe dd {
	margin-left: 1em;
}

fieldset {
	border-left: none;
	border-right: none;
}

.shortFormRight, .shortFormLeft {
	float: none;







<
<
<
<
<
<
<
<
<
<
<
<
<
<







153
154
155
156
157
158
159














160
161
162
163
164
165
166
	width: auto;
}

dl.describe {
	margin: 0 .5em;
}















fieldset {
	border-left: none;
	border-right: none;
}

.shortFormRight, .shortFormLeft {
	float: none;
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
		display: inline-block;
		border-left: 2px dashed #999;
		width: auto !important;
	}

	colgroup {
		/* Hack pour désactiver les largeurs de colonnes */
		display: none;
	}

	.print-btn {
		display: none;
	}

	.radio-btn input {
		position: absolute;
		right: 1em;
	}







<
<
<
<







179
180
181
182
183
184
185




186
187
188
189
190
191
192
		display: inline-block;
		border-left: 2px dashed #999;
		width: auto !important;
	}

	colgroup {
		/* Hack pour désactiver les largeurs de colonnes */




		display: none;
	}

	.radio-btn input {
		position: absolute;
		right: 1em;
	}
262
263
264
265
266
267
268
269





270




271







272
273
274
275
276
277
278
279
280
281
	dl.list {
		text-align: center;
	}

	table.statement td, table.statement tr {
		display: block;
	}






	dl.describe {




		grid-template: auto / 30% 1fr;







	}

	.datepicker-parent {
		position: static;
	}

	dialog.datepicker {
		margin: auto;
	}
}








>
>
>
>
>

>
>
>
>
|
>
>
>
>
>
>
>










244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
	dl.list {
		text-align: center;
	}

	table.statement td, table.statement tr {
		display: block;
	}

	aside.describe {
		float: none;
		margin: 1em auto;
	}

	dl.describe {
		display: block;
		margin: 1em;
		text-align: left;
	}

	dl.describe > dt {
		text-align: unset;
	}

	dl.describe > dd {
		font-size: 1.2em;
		margin-bottom: .8em;
	}

	.datepicker-parent {
		position: static;
	}

	dialog.datepicker {
		margin: auto;
	}
}

Modified src/www/admin/static/scripts/datepicker2.js from [180ac8cc87] to [6f612d2aa2].

14
15
16
17
18
19
20
21

22
23
24
25
26
27
28
29
30
31
32
33

34
35
36
37
38
39
40
			this.button = button;
			this.input = input;
			this.date = null;

			Object.assign(this, {
				format: 0, // 0 = Y-m-d, 1 = d/m/Y
				lang: 'fr',
				class: 'datepicker'

			}, config);

			var c = document.createElement('dialog');
			c.className = this.class;
			this.container = button.parentNode.insertBefore(c, button.nextSibling);

			button.onclick = () => { this.container.hasAttribute('open') ? this.close() : this.open() };
		}

		open()
		{
			var d = this.input.value;

			if (d == '') {
				d = new CalendarDate;
			}
			else if (this.format == 1) {
				d = d.split('/');
				d = new CalendarDate(d[2], d[1] - 1, d[0]);
			}







|
>











|
>







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
			this.button = button;
			this.input = input;
			this.date = null;

			Object.assign(this, {
				format: 0, // 0 = Y-m-d, 1 = d/m/Y
				lang: 'fr',
				class: 'datepicker',
				onchange: null
			}, config);

			var c = document.createElement('dialog');
			c.className = this.class;
			this.container = button.parentNode.insertBefore(c, button.nextSibling);

			button.onclick = () => { this.container.hasAttribute('open') ? this.close() : this.open() };
		}

		open()
		{
			var d = this.input ? this.input.value : '';

			if (d == '') {
				d = new CalendarDate;
			}
			else if (this.format == 1) {
				d = d.split('/');
				d = new CalendarDate(d[2], d[1] - 1, d[0]);
			}
173
174
175
176
177
178
179
180





181




182
183
184
185
186
187
188
				this.date.setDate(parseInt(e.target.textContent, 10));
			}

			var y = this.date.getFullYear(),
				m = ('0' + (this.date.getMonth() + 1)).substr(-2),
				d = ('0' + this.date.getDate()).substr(-2);

			this.input.value = this.format == 1 ? d + '/' + m + '/' + y : y + '-' + m + '-' + d;





			this.close();




		}

		focus()
		{
			this.container.querySelectorAll('tbody td').forEach((cell) => {
				var v = parseInt(cell.innerHTML, 10);








|
>
>
>
>
>

>
>
>
>







175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
				this.date.setDate(parseInt(e.target.textContent, 10));
			}

			var y = this.date.getFullYear(),
				m = ('0' + (this.date.getMonth() + 1)).substr(-2),
				d = ('0' + this.date.getDate()).substr(-2);

			let v = this.format == 1 ? d + '/' + m + '/' + y : y + '-' + m + '-' + d;

			if (this.input) {
				this.input.value = v;
			}

			this.close();

			if (this.onchange) {
				this.onchange(v, this);
			}
		}

		focus()
		{
			this.container.querySelectorAll('tbody td').forEach((cell) => {
				var v = parseInt(cell.innerHTML, 10);

Modified src/www/admin/static/scripts/global.js from [054e0148e7] to [a4e04fc8bc].

102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120




121
122
123


124
125
126
127
128
129
130
131


132
133
134
135
136
137
138
		}

		g.dialog = document.createElement('dialog');
		g.dialog.id = 'dialog';
		g.dialog.open = true;

		var btn = document.createElement('button');
		btn.className = 'icn-btn';
		btn.setAttribute('data-icon', '✘');
		btn.type = 'button';
		btn.innerHTML = 'Fermer';
		btn.onclick = g.closeDialog;
		g.dialog.appendChild(btn);

		if (typeof content == 'string') {
			var container = document.createElement('div');
			container.innerHTML = content;
			content = container;
		}





		content.style.opacity = g.dialog.style.opacity = 0;
		g.dialog.appendChild(content);


		g.dialog.onclick = (e) => { if (e.target == g.dialog) g.closeDialog(); };
		window.onkeyup = (e) => { if (e.key == 'Escape') g.closeDialog(); };

		document.body.appendChild(g.dialog);

		// Restore CSS defaults
		window.setTimeout(() => { g.dialog.style.opacity = ''; }, 50);
		window.setTimeout(() => { content.style.opacity = ''; }, 100);


	}

	g.openFrameDialog = function (url) {
		var iframe = document.createElement('iframe');
		iframe.src = url;
		iframe.name = 'dialog';
		iframe.frameborder = '0';







|











>
>
>
>
|
|

>
>








>
>







102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
		}

		g.dialog = document.createElement('dialog');
		g.dialog.id = 'dialog';
		g.dialog.open = true;

		var btn = document.createElement('button');
		btn.className = 'icn-btn closeBtn';
		btn.setAttribute('data-icon', '✘');
		btn.type = 'button';
		btn.innerHTML = 'Fermer';
		btn.onclick = g.closeDialog;
		g.dialog.appendChild(btn);

		if (typeof content == 'string') {
			var container = document.createElement('div');
			container.innerHTML = content;
			content = container;
		}
		else if (content instanceof DocumentFragment) {
			var container = document.createElement('div');
			container.appendChild(content.cloneNode(true));
			content = container;
		}

		g.dialog.appendChild(content);

		content.style.opacity = g.dialog.style.opacity = 0;
		g.dialog.onclick = (e) => { if (e.target == g.dialog) g.closeDialog(); };
		window.onkeyup = (e) => { if (e.key == 'Escape') g.closeDialog(); };

		document.body.appendChild(g.dialog);

		// Restore CSS defaults
		window.setTimeout(() => { g.dialog.style.opacity = ''; }, 50);
		window.setTimeout(() => { content.style.opacity = ''; }, 100);

		return content;
	}

	g.openFrameDialog = function (url) {
		var iframe = document.createElement('iframe');
		iframe.src = url;
		iframe.name = 'dialog';
		iframe.frameborder = '0';

Modified src/www/admin/static/scripts/wiki-encryption.js from [2ac75e40c2] to [2f0eae2745].

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115

116






117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137

		// nl2br
		content = content.replace(/\r/g, '').replace(/\n/g, '<br />');

		return content;
	}

	window.wikiDecrypt = function (edit)
	{
		loadAESlib();

		encryptPassword = window.prompt('Mot de passe ?');

		if (!encryptPassword)
		{
			encryptPassword = null;

			if (edit)
			{
				if (window.confirm("Aucun mot de passe entré.\nDésactiver le chiffrement et effacer le contenu ?"))
				{
					document.getElementById('f_contenu').value = '';
					document.getElementById('f_chiffrement').checked = false;
					checkEncryption(document.getElementById('f_chiffrement'));
				}
				else
				{
					wikiDecrypt(true);
				}
			}

			return;
		}

		iteration = 0;
		decrypt(edit);
	};

	var decrypt = function (edit)
	{
		if (typeof GibberishAES == 'undefined')
		{
			if (iteration >= 10)
			{
				iteration = 0;
				encryptPassword = null;
				window.alert("Impossible de charger la bibliothèque AES, empêchant le déchiffrement de la page.\nAttendez quelques instants avant de recommencer ou rechargez la page.");
				return;
			}

			iteration++;
			window.setTimeout(decrypt, 500);
			return;
		}

		var content = document.getElementById(edit ? 'f_contenu' : 'wikiEncryptedContent');

		var wikiContent = !edit ? (content.textContent ? content.textContent : content.innerText) : content.value;






		wikiContent = wikiContent.replace(/\s+/g, '');

		try {
			wikiContent = GibberishAES.dec(wikiContent, encryptPassword);
		}
		catch (e)
		{
			encryptPassword = null;
			window.alert('Impossible de déchiffrer. Mauvais mot de passe ?');

			if (edit)
			{
				// Redemander le mot de passe
				wikiDecrypt(true);
			}
			return false;
		}

		if (!edit)
		{
			content.style.display = 'block';







|









|









|







|


|
















|
>
|
>
>
>
>
>
>













|







60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144

		// nl2br
		content = content.replace(/\r/g, '').replace(/\n/g, '<br />');

		return content;
	}

	window.wikiDecrypt = function ()
	{
		loadAESlib();

		encryptPassword = window.prompt('Mot de passe ?');

		if (!encryptPassword)
		{
			encryptPassword = null;

			if (document.getElementById('f_contenu'))
			{
				if (window.confirm("Aucun mot de passe entré.\nDésactiver le chiffrement et effacer le contenu ?"))
				{
					document.getElementById('f_contenu').value = '';
					document.getElementById('f_chiffrement').checked = false;
					checkEncryption(document.getElementById('f_chiffrement'));
				}
				else
				{
					wikiDecrypt();
				}
			}

			return;
		}

		iteration = 0;
		decrypt();
	};

	var decrypt = function ()
	{
		if (typeof GibberishAES == 'undefined')
		{
			if (iteration >= 10)
			{
				iteration = 0;
				encryptPassword = null;
				window.alert("Impossible de charger la bibliothèque AES, empêchant le déchiffrement de la page.\nAttendez quelques instants avant de recommencer ou rechargez la page.");
				return;
			}

			iteration++;
			window.setTimeout(decrypt, 500);
			return;
		}

		var content = document.getElementById('f_contenu');
		var edit = true;

		if (!content) {
		 	content = document.getElementById('wikiEncryptedContent');
		 	edit = false;
		}

		var wikiContent = content.value || content.innerText;
		wikiContent = wikiContent.replace(/\s+/g, '');

		try {
			wikiContent = GibberishAES.dec(wikiContent, encryptPassword);
		}
		catch (e)
		{
			encryptPassword = null;
			window.alert('Impossible de déchiffrer. Mauvais mot de passe ?');

			if (edit)
			{
				// Redemander le mot de passe
				wikiDecrypt();
			}
			return false;
		}

		if (!edit)
		{
			content.style.display = 'block';

Deleted src/www/admin/static/scripts/wiki_editor.css version [30c3c98262].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
.textEditor {
    border-radius: .5em;
    background: #ccc;
    padding: 1%;
    overflow: hidden;
    position: relative;
}

.textEditor textarea {
    border: none;
    margin: 0;
    background: #eee;
    border-radius: .5em;
    height: 96%;
    width: 98%;
}

nav.te {
    margin-bottom: .5em;
    height: 30px;
}

nav.te button {
    text-decoration: none;
    cursor: pointer;
    background: #eee no-repeat center center;
    display: inline-block;
    vertical-align: bottom;
    transition: all .2s;
    border: 1px solid #999;
    box-shadow: 2px 2px 5px #999;
}

nav.te .bold, nav.te .italic, nav.te .title, nav.te .link {
    font-family: Georgia, "Times New Roman", serif;
}

nav.te .bold { font-weight: bold; }
nav.te .italic { font-style: italic; }
nav.te .link { text-decoration: underline; color: blue; }

nav.te .fullscreen {
    text-indent: -70em;
    width: 32px;
    overflow: hidden;
}

nav.te .icnl {
    font-size: 18px;
}

nav.te .ext.icnl {
    width: 24px;
    line-height: 5px;
    overflow: hidden;
}

nav.te .file {
    margin-left: 2em;
}

nav.te .fullscreen {
    background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEUAAABMTFFOTlBOTlCQ1uMHAAAAA3RSTlMAOcKBmOr4AAAAQUlEQVQI12P4/4D7P8N/B0Yo8XsC23+GZw+4pzM4TmBzYsAGgBKODNcecM9m+D+B7T3DPwfG/Qz/G5iABlzg+g8ANzMax/3kkQoAAAAASUVORK5CYII=");
    float: right;
}

nav.te .preview {
    margin-left: 2em;
}

.textEditor.fullscreen nav.te .fullscreen {
    background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAADFBMVEUAAABNTVFOTlBOTlBLB/faAAAAA3RSTlMAOsPdsomtAAAAQElEQVQI12NIEWCRZNi5gTePIe8D/08G6Q/8Txj0P/B/YVj/gf8fAzawHyQhD1ICJvI/8O9k2FnAl8eQosAiCQCgixb13aKGIwAAAABJRU5ErkJggg==");
}

.textEditor nav button.close {
    display: none;
    float: right;
}

.textEditor nav button.reload {
    display: none;
    float: left;
}

.textEditor.fullscreen {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    width: 98%;
    height: 98%;
    padding: 1%;
    border-radius: 0;
    z-index: 100000;
}

.textEditor.fullscreen textarea {
    height: 90%;
}

.textEditor.iframe textarea {
    display: none;
}

.textEditor.iframe nav button {
    display: none;
}

.textEditor.iframe nav button.close, .textEditor.iframe nav button.reload {
    display: inline-block;
}

.textEditor iframe {
    border: none;
    background: #eee;
    border-radius: .5em;
    padding: 1%;
    width: 98%;
}

.textEditor iframe.hidden {
    visibility: hidden;
    width: 0px;
    height: 0px;
    position: absolute;
    top: -1000px;
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
































































































































































































































































Deleted src/www/admin/static/scripts/wiki_editor.js version [2d8c9cc5c3].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
(function () {
	var wiki_id = window.location.search.match(/id=(\d+)/)[1];

	g.onload(function () {
		g.style('scripts/wiki_editor.css');

		g.script('scripts/text_editor.min.js', function () {
			var t = new textEditor('f_contenu');
			t.parent = t.textarea.parentNode;

			var toolbar = document.createElement('nav');
			toolbar.className = 'te';

			var toggleFullscreen = function (e)
			{
				var classes = t.parent.className.split(' ');

				for (var i = 0; i < classes.length; i++)
				{
					if (classes[i] == 'fullscreen')
					{
						classes.splice(i, 1);
						t.parent.className = classes.join(' ');
						t.fullscreen = false;
						return true;
					}
				}
				
				classes.push('fullscreen');
				t.parent.className = classes.join(' ');
				t.fullscreen = true;
				return true;
			};

			var openPreview = function ()
			{
				openIFrame('');
				var form = document.createElement('form');
				form.appendChild(t.textarea.cloneNode(true));
				form.firstChild.value = t.textarea.value;
				form.target = 'editorFrame';
				form.action = g.admin_url + 'wiki/_preview.php?id=' + wiki_id;
				form.style.display = 'none';
				form.method = 'post';
				document.body.appendChild(form);
				form.submit();
				//document.body.removeChild(form);
			};

			var openSyntaxHelp = function ()
			{
				openIFrame(g.admin_url + 'wiki/_syntaxe.html');
			};

			var openFileInsert = function ()
			{
				openIFrame(g.admin_url + 'wiki/_fichiers.php?page=' + wiki_id);
			};

			window.te_insertFile = function (file)
			{
				var tag = '<<fichier|'+file+'>>';
				
				t.insertAtPosition(t.getSelection().start, tag);
				
				closeIFrame();
			};

			window.te_insertImage = function (file, position, caption)
			{
				var tag = '<<image|' + file;

				if (position)
					tag += '|' + position;

				if (caption)
					tag += '|' + caption;
				
				tag += '>>';
				
				t.insertAtPosition(t.getSelection().start, tag);
				
				closeIFrame();
			};

			var openIFrame = function(url)
			{
				if (t.iframe && t.iframe.src == t.base_url + url)
				{
					t.iframe.className = '';
					t.parent.className += ' iframe';
					return true;
				}
				else if (t.iframe)
				{
					t.parent.removeChild(t.iframe);
					t.iframe = null;
				}

				var w = t.textarea.offsetWidth,
					h = t.textarea.offsetHeight;

				var iframe = document.createElement('iframe');
				iframe.width = w;
				iframe.height = h;
				iframe.src = url;
				iframe.name = 'editorFrame';
				iframe.frameborder = '0';
				iframe.scrolling = 'yes';

				t.parent.appendChild(iframe);
				t.parent.className += ' iframe';
				t.iframe = iframe;
			};

			var closeIFrame = function ()
			{
				t.parent.className = t.parent.className.replace(/ iframe$/, '');
				t.iframe.className = 'hidden';
			};


			var appendButton = function (name, title, action, altTitle)
			{
				var btn = document.createElement('button');
				btn.type = 'button';
				btn.title = altTitle ? altTitle : title;
				if (title.length == 1) {
					btn.dataset.icon = title;
				}
				else {
					btn.innerText = title;
				}
				btn.className = 'icn-btn ' +name;
				btn.onclick = function () { action.call(); return false; };

				toolbar.appendChild(btn);
				return btn;
			};

			var wrapTags = function (left, right)
			{
				t.wrapSelection(t.getSelection(), left, right);
				return true;
			};

			appendButton('title', "== Titre", function () { wrapTags("== ", ""); } );
			appendButton('bold', '**gras**', function () { wrapTags('**', '**'); } );
			appendButton('italic', "''italique''", function () { wrapTags("''", "''"); } );
			appendButton('link', "[[lien|http://]]", function () { 
				if (url = window.prompt('Adresse URL ?')) 
					wrapTags("[[", "|" + url + ']]'); 
			} );
			appendButton('file', "📎", openFileInsert, 'Insérer fichier / image');

			appendButton('ext preview', '⎙', openPreview, 'Prévisualiser');

			appendButton('ext help', '❓', openSyntaxHelp, 'Aide sur la syntaxe');
			appendButton('ext fullscreen', 'Plein écran', toggleFullscreen, 'Plein écran');
			appendButton('ext close', 'Fermer', closeIFrame);
			
			t.parent.insertBefore(toolbar, t.parent.firstChild);

			t.shortcuts.push({key: 'F11', callback: toggleFullscreen});
			t.shortcuts.push({ctrl: true, key: 'b', callback: function () { return wrapTags('**', '**'); } });
			t.shortcuts.push({ctrl: true, key: 'g', callback: function () { return wrapTags('**', '**'); } });
			t.shortcuts.push({ctrl: true, key: 'i', callback: function () { return wrapTags("''", "''"); } });

			if (window.location.hash.match(/fullscreen/))
			{
				t.toggleFullscreen();
				window.location.hash = '';
			}
		});
	});
}());
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
































































































































































































































































































































































Modified src/www/admin/static/styles/03-forms.css from [c5b73006cf] to [cb1bad2db4].

137
138
139
140
141
142
143





144
145
146
147
148
149
150
input:hover + label::before {
    color: rgb(var(--gSecondColor));
}

input:checked + label::before {
    text-shadow: 1px 1px 5px #ff9;
}






input:focus, button:focus, select:focus, textarea:focus, input[type=radio]:focus + label::before, input[type=checkbox]:focus + label::before {
    box-shadow: 0 0 5px .2rem rgb(var(--gSecondColor));
    outline: 0;
}

/* buttons */







>
>
>
>
>







137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
input:hover + label::before {
    color: rgb(var(--gSecondColor));
}

input:checked + label::before {
    text-shadow: 1px 1px 5px #ff9;
}

#queryBuilder input[type=checkbox] {
    position: unset;
    opacity: unset;
}

input:focus, button:focus, select:focus, textarea:focus, input[type=radio]:focus + label::before, input[type=checkbox]:focus + label::before {
    box-shadow: 0 0 5px .2rem rgb(var(--gSecondColor));
    outline: 0;
}

/* buttons */
231
232
233
234
235
236
237









238
239
240
241
242
243
244
}

input[disabled]:hover, input[readonly]:hover {
    background-color: unset;
    color: unset;
    border-color: unset;
}










select, input[size], input[type=color], button, input[type=button], input[type=submit], input[type=number] {
    min-width: 0;
}

/* Radio button lists (eg. new transaction) */
form .radio-btn {







>
>
>
>
>
>
>
>
>







236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
}

input[disabled]:hover, input[readonly]:hover {
    background-color: unset;
    color: unset;
    border-color: unset;
}

input[disabled] + label {
    color: #666;
}

input[disabled] + label::before {
    color: #999;
    cursor: not-allowed;
}

select, input[size], input[type=color], button, input[type=button], input[type=submit], input[type=number] {
    min-width: 0;
}

/* Radio button lists (eg. new transaction) */
form .radio-btn {

Modified src/www/admin/static/styles/04-dialogs.css from [9ca4a3ffe3] to [9ade8c8089].

40
41
42
43
44
45
46
47
48


49
50
51
52
53




54
55
56
57
58
59
60
#dialog > iframe {
    width: 90%;
    height: 90%;
    border: none;
    box-shadow: 0px 0px 5px #000;
}

#dialog button {
    background: rgb(var(--gMainColor));


    color: #fff;
    font-size: 1.3em;
    display: block;
    width: 90%;
}





.loader {
    width: 100%;
    min-height: 32px;
    display: block;
    position: relative;
}







|
|
>
>





>
>
>
>







40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#dialog > iframe {
    width: 90%;
    height: 90%;
    border: none;
    box-shadow: 0px 0px 5px #000;
}

#dialog > button.closeBtn {
    background: unset;
    border: unset;
    box-shadow: unset;
    color: #fff;
    font-size: 1.3em;
    display: block;
    width: 90%;
}

#dialog > button.closeBtn:hover {
    color: #999 !important;
}

.loader {
    width: 100%;
    min-height: 32px;
    display: block;
    position: relative;
}

Modified src/www/admin/static/styles/10-accounting.css from [2779caf5e1] to [c404c68c91].

100
101
102
103
104
105
106


table.accounts th { font-weight: normal; }
table.accounts .account-level-1 th { font-size: 1.6em; }
table.accounts .account-level-2 th { padding-left: 1em; font-size: 1.3em; }
table.accounts .account-level-3 th { padding-left: 2em; }
table.accounts .account-level-4 th { padding-left: 3em; }
table.accounts .account-level-5 th { padding-left: 4em; }
table.accounts .account-level-6 th { padding-left: 5em; }









>
>
100
101
102
103
104
105
106
107
108
table.accounts th { font-weight: normal; }
table.accounts .account-level-1 th { font-size: 1.6em; }
table.accounts .account-level-2 th { padding-left: 1em; font-size: 1.3em; }
table.accounts .account-level-3 th { padding-left: 2em; }
table.accounts .account-level-4 th { padding-left: 3em; }
table.accounts .account-level-5 th { padding-left: 4em; }
table.accounts .account-level-6 th { padding-left: 5em; }

table.projects tr.title p.help { font-weight: normal; text-align: center; }

Added tools/categories_as_projects.sh version [02cc9257ed].





























































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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