Overview
Comment:Renommer les entités de compta, passer à l'anglais, simplifier (suppression des moyens de paiement, possibilité d'avoir plusieurs plans comptables, etc.)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA1: 9c856dd92cfea39a2e1d817dc37a269eae04bd84
User & Date: bohwaz on 2020-04-20 01:05:06
Other Links: branch diff | manifest | tags
Context
2020-04-21
00:58
Renommer le namespace Compta -> Accounting check-in: 1eec72f23b user: bohwaz tags: dev
2020-04-20
01:05
Renommer les entités de compta, passer à l'anglais, simplifier (suppression des moyens de paiement, possibilité d'avoir plusieurs plans comptables, etc.) check-in: 9c856dd92c user: bohwaz tags: dev
01:03
Suppression clés de config non utilisées check-in: 09e570ec23 user: bohwaz tags: dev
Changes

Modified src/include/data/1.0.0_migration.sql from [f261086c64] to [214a92ada1].

3
4
5
6
7
8
9



10



11
12
13
14
15
16
17
..
64
65
66
67
68
69
70
71






ALTER TABLE compta_categories RENAME TO compta_categories_old;
ALTER TABLE compta_exercices RENAME TO compta_exercices_old;
ALTER TABLE membres_operations RENAME TO membres_operations_old;
ALTER TABLE membres_categories RENAME TO membres_categories_old;

DROP TABLE fichiers_compta_journal; -- Inutilisé à ce jour




.read 1.0.0_schema.sql




-- Migration comptes de code comme identifiant à ID unique
INSERT INTO compta_comptes (id, code, parent, libelle, position, plan_comptable, id_exercice)
	SELECT NULL, id, NULL, libelle, position, plan_comptable, NULL FROM compta_comptes_old;

-- Migration de la hiérarchie
UPDATE compta_comptes AS a SET parent = (SELECT id FROM compta_comptes AS b WHERE code = (SELECT parent FROM compta_comptes_old AS c WHERE id = b.code));
................................................................................
-- CREATE TRIGGER IF NOT EXISTS ON compta_journal_ecritures

-- Transfert des rapprochements
UPDATE compta_mouvements_lignes SET rapprochement = 1 WHERE id_mouvement IN (SELECT id_operation FROM compta_rapprochement);

-- Suppression de la table rapprochements
DROP TABLE compta_rapprochement;














>
>
>

>
>
>







 








>
>
>
>
>
>
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
..
70
71
72
73
74
75
76
77
78
79
80
81
82
83
ALTER TABLE compta_categories RENAME TO compta_categories_old;
ALTER TABLE compta_exercices RENAME TO compta_exercices_old;
ALTER TABLE membres_operations RENAME TO membres_operations_old;
ALTER TABLE membres_categories RENAME TO membres_categories_old;

DROP TABLE fichiers_compta_journal; -- Inutilisé à ce jour

-- N'est pas utilisé
DELETE FROM config WHERE cle = 'categorie_dons' OR cle = 'categorie_cotisations';

.read 1.0.0_schema.sql

-- FIXME: insertion en comptes analytiques des projets et associations dans transactions
-- FIXME: création plan comptable

-- Migration comptes de code comme identifiant à ID unique
INSERT INTO compta_comptes (id, code, parent, libelle, position, plan_comptable, id_exercice)
	SELECT NULL, id, NULL, libelle, position, plan_comptable, NULL FROM compta_comptes_old;

-- Migration de la hiérarchie
UPDATE compta_comptes AS a SET parent = (SELECT id FROM compta_comptes AS b WHERE code = (SELECT parent FROM compta_comptes_old AS c WHERE id = b.code));
................................................................................
-- CREATE TRIGGER IF NOT EXISTS ON compta_journal_ecritures

-- Transfert des rapprochements
UPDATE compta_mouvements_lignes SET rapprochement = 1 WHERE id_mouvement IN (SELECT id_operation FROM compta_rapprochement);

-- Suppression de la table rapprochements
DROP TABLE compta_rapprochement;

-- Suppression inutilisées
DROP TABLE compta_projets;
DROP TABLE compta_comptes_bancaires;
DROP TABLE compta_moyens_paiements;


Modified src/include/data/1.0.0_schema.sql from [9e284814b8] to [fcd675e17c].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
..
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54










55
56
57
58
59
60
61
...
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

203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
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
280
281
282
283
284
285
286
287
288
289
290
291
292

293
294
295
296


297
298
299
300
301
302

303
304
305
306
307
308

309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
...
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
CREATE TABLE IF NOT EXISTS config (
-- Configuration de Garradin
    cle TEXT PRIMARY KEY NOT NULL,
    valeur TEXT
);

-- On stocke ici les ID de catégorie de compta correspondant aux types spéciaux
-- compta_categorie_cotisations => id_categorie
-- compta_categorie_dons => id_categorie

CREATE TABLE IF NOT EXISTS membres_categories
-- Catégories de membres
(
    id INTEGER PRIMARY KEY NOT NULL,
    nom TEXT NOT NULL,

    droit_wiki INTEGER NOT NULL DEFAULT 1,
................................................................................
    PRIMARY KEY (selecteur, id_membre)
);

CREATE TABLE IF NOT EXISTS cotisations
-- Types de cotisations et activités
(
    id INTEGER PRIMARY KEY NOT NULL,
    id_categorie_compta INTEGER NULL REFERENCES compta_categories (id) ON DELETE SET NULL, -- NULL si le type n'est pas associé automatiquement à la compta

    intitule TEXT NOT NULL,
    description TEXT NULL,
    montant REAL NOT NULL,

    duree INTEGER NULL, -- En jours
    debut TEXT NULL, -- timestamp
    fin TEXT NULL
);











CREATE TABLE IF NOT EXISTS cotisations_membres
-- Enregistrement des cotisations et activités
(
    id INTEGER NOT NULL PRIMARY KEY,
    id_membre INTEGER NOT NULL REFERENCES membres (id) ON DELETE CASCADE,
    id_cotisation INTEGER NOT NULL REFERENCES cotisations (id) ON DELETE CASCADE,
................................................................................
        UPDATE wiki_recherche SET contenu = '' WHERE id = new.id_page;
    END;

--
-- COMPTA
--

CREATE TABLE IF NOT EXISTS compta_exercices
-- Exercices
(
    id INTEGER NOT NULL PRIMARY KEY,

    libelle TEXT NOT NULL,

    debut TEXT NOT NULL CHECK (date(debut) IS NOT NULL AND date(debut) = debut),
    fin TEXT NOT NULL CHECK (date(fin) IS NOT NULL AND date(fin) = fin),

    cloture INTEGER NOT NULL DEFAULT 0


);










CREATE TABLE IF NOT EXISTS compta_comptes
-- Plan comptable
(
    id INTEGER NOT NULL PRIMARY KEY,


    code TEXT NOT NULL, -- peut contenir des lettres, eg. 53A, 53B, etc.
    parent INTEGER NULL REFERENCES compta_comptes(id),

    libelle TEXT NOT NULL,

    position INTEGER NOT NULL, -- position actif/passif/charge/produit

    plan_comptable INTEGER NOT NULL DEFAULT 1, -- 1 = fait partie du plan comptable original, 0 = a été ajouté par l'utilisateur
    id_exercice INTEGER NULL REFERENCES compta_exercices (id)
    -- Quand un exercice est clôturé, on copie les comptes utilisés dans cet exercice, avec id_exercice renseigné
    -- pour garder une archive en cas de modification du plan comptable dans les exercices suivants
);

CREATE UNIQUE INDEX IF NOT EXISTS compta_comptes_code ON compta_comptes (code, id_exercice);
CREATE INDEX IF NOT EXISTS compta_comptes_parent ON compta_comptes (parent);

CREATE TABLE IF NOT EXISTS compta_comptes_bancaires
-- Comptes bancaires
(
    id INTEGER NOT NULL PRIMARY KEY,

    banque TEXT NOT NULL,

    iban TEXT NULL,
    bic TEXT NULL,

    FOREIGN KEY(id) REFERENCES compta_comptes(id) ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS compta_projets
-- Projets (compta analytique)
(
    id INTEGER PRIMARY KEY NOT NULL,

    libelle TEXT NOT NULL
);

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

    libelle TEXT NOT NULL,
    remarques TEXT NULL,
    numero_piece TEXT NULL, -- N° de pièce comptable

    date TEXT NOT NULL DEFAULT CURRENT_DATE CHECK (date(date) IS NOT NULL AND date(date) = date),
    moyen_paiement TEXT NULL,
    reference_paiement TEXT NULL, -- Référence de paiement, eg. numéro de chèque

    validation INTEGER NOT NULL DEFAULT 0, -- 1 = écriture validée, non modifiable

    hash TEXT NULL,
    prev_hash TEXT NULL,

    id_exercice INTEGER NULL DEFAULT NULL, -- En cas de compta simple, l'exercice est permanent (NULL)
    id_auteur INTEGER NULL,

    id_categorie INTEGER NULL, -- Numéro de catégorie (en mode simple)
    id_projet INTEGER NULL,

    FOREIGN KEY(moyen_paiement) REFERENCES compta_moyens_paiement(code),
    FOREIGN KEY(id_exercice) REFERENCES compta_exercices(id),
    FOREIGN KEY(id_auteur) REFERENCES membres(id) ON DELETE SET NULL,
    FOREIGN KEY(id_categorie) REFERENCES compta_categories(id) ON DELETE SET NULL,
    FOREIGN KEY(id_projet) REFERENCES compta_projets(id) ON DELETE SET NULL

);

CREATE INDEX IF NOT EXISTS compta_mouvements_exercice ON compta_mouvements (id_exercice);
CREATE INDEX IF NOT EXISTS compta_mouvements_date ON compta_mouvements (date);
CREATE INDEX IF NOT EXISTS compta_mouvements_auteur ON compta_mouvements (id_auteur);



CREATE TRIGGER IF NOT EXISTS compta_mouvements_exercice_i BEFORE INSERT ON compta_mouvements
BEGIN
    SELECT
        CASE WHEN (old.id_exercice IS NOT NULL AND (SELECT cloture FROM compta_exercices WHERE id = old.id_exercice) = 1)
        THEN RAISE(FAIL, 'Modification interdite de mouvement lié à un exercice clôturé')
    END;
END;

CREATE TRIGGER IF NOT EXISTS compta_mouvements_exercice_d BEFORE DELETE ON compta_mouvements
BEGIN
    SELECT
        CASE WHEN (old.id_exercice IS NOT NULL AND (SELECT cloture FROM compta_exercices WHERE id = old.id_exercice) = 1)
        THEN RAISE(FAIL, 'Modification interdite de mouvement lié à un exercice clôturé')
    END;
END;

CREATE TRIGGER IF NOT EXISTS compta_mouvements_exercice_u BEFORE UPDATE ON compta_mouvements
BEGIN
    SELECT
        CASE WHEN (old.id_exercice IS NOT NULL AND (SELECT cloture FROM compta_exercices WHERE id = old.id_exercice) = 1)
        THEN RAISE(FAIL, 'Modification interdite de mouvement lié à un exercice clôturé')
    END;
END;

CREATE TABLE IF NOT EXISTS compta_mouvements_lignes
-- Écritures

(
    id INTEGER PRIMARY KEY NOT NULL,

    id_mouvement INTEGER NOT NULL REFERENCES compta_mouvements (id) ON DELETE CASCADE,



    compte INTEGER NOT NULL REFERENCES compta_comptes(id), -- N° du compte dans le plan comptable
    credit INTEGER NOT NULL,
    debit INTEGER NOT NULL,

    rapprochement INTEGER NOT NULL DEFAULT 0,


    CONSTRAINT ligne_check1 CHECK ((credit * debit) = 0),
    CONSTRAINT ligne_check2 CHECK ((credit + debit) > 0)
);

CREATE INDEX IF NOT EXISTS compta_mouvements_lignes_compte ON compta_mouvements_lignes (compte);


CREATE TABLE IF NOT EXISTS compta_moyens_paiement
-- Moyens de paiement
(
    code TEXT NOT NULL PRIMARY KEY,
    nom TEXT NOT NULL
);

INSERT OR IGNORE INTO compta_moyens_paiement (code, nom) VALUES ('AU', 'Autre');
INSERT OR IGNORE INTO compta_moyens_paiement (code, nom) VALUES ('CB', 'Carte bleue');
INSERT OR IGNORE INTO compta_moyens_paiement (code, nom) VALUES ('CH', 'Chèque');
INSERT OR IGNORE INTO compta_moyens_paiement (code, nom) VALUES ('AC', 'Autres chèques (vacances, cadeau, etc.)');
INSERT OR IGNORE INTO compta_moyens_paiement (code, nom) VALUES ('ES', 'Espèces');
INSERT OR IGNORE INTO compta_moyens_paiement (code, nom) VALUES ('PR', 'Prélèvement');
INSERT OR IGNORE INTO compta_moyens_paiement (code, nom) VALUES ('TI', 'TIP');
INSERT OR IGNORE INTO compta_moyens_paiement (code, nom) VALUES ('VI', 'Virement');

CREATE TABLE IF NOT EXISTS compta_categories
-- Catégories pour simplifier le plan comptable
(
    id INTEGER NOT NULL PRIMARY KEY,
    type INTEGER NOT NULL DEFAULT 1, -- 1 = recette, -1 = dépense, 0 = autre (utilisé uniquement pour l'interface)

    intitule TEXT NOT NULL,
    description TEXT NULL,

    compte INTEGER NULL REFERENCES compta_comptes(id) ON DELETE CASCADE -- Compte affecté par cette catégorie
);

CREATE TABLE IF NOT EXISTS plugins
(
    id TEXT NOT NULL PRIMARY KEY,
    officiel INTEGER NOT NULL DEFAULT 0,
    nom TEXT NOT NULL,
................................................................................
-- Associations entre fichiers et pages du wiki
(
    fichier INTEGER NOT NULL REFERENCES fichiers (id),
    id INTEGER NOT NULL REFERENCES wiki_pages (id),
    PRIMARY KEY(fichier, id)
);

CREATE TABLE IF NOT EXISTS fichiers_compta_mouvements
-- Associations entre fichiers et journal de compta (pièce comptable par exemple)
(
    fichier INTEGER NOT NULL REFERENCES fichiers (id),
    id INTEGER NOT NULL REFERENCES compta_mouvements (id),
    PRIMARY KEY(fichier, id)
);

CREATE TABLE IF NOT EXISTS recherches
-- Recherches enregistrées
(
    id INTEGER NOT NULL PRIMARY KEY,






<
<
<
<







 







<



<





>
>
>
>
>
>
>
>
>
>







 







|




|

|
|

|
>
>


>
>
|
>
>
>
>
>
>
|
|


>
>



|


>
|
<
<
<


|
|

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




|
|
|


<
<

|




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


<
<
<
>
>

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



<
>
>

<



|
>

|
|


<
>

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





|


|







 







|



|







1
2
3
4
5
6




7
8
9
10
11
12
13
..
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
...
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220



221
222
223
224
225













226








227
228
229
230
231
232
233
234
235


236
237
238
239
240
241


242
243







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
280
281
282
283
284
285
286
...
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
CREATE TABLE IF NOT EXISTS config (
-- Configuration de Garradin
    cle TEXT PRIMARY KEY NOT NULL,
    valeur TEXT
);





CREATE TABLE IF NOT EXISTS membres_categories
-- Catégories de membres
(
    id INTEGER PRIMARY KEY NOT NULL,
    nom TEXT NOT NULL,

    droit_wiki INTEGER NOT NULL DEFAULT 1,
................................................................................
    PRIMARY KEY (selecteur, id_membre)
);

CREATE TABLE IF NOT EXISTS cotisations
-- Types de cotisations et activités
(
    id INTEGER PRIMARY KEY NOT NULL,


    intitule TEXT NOT NULL,
    description TEXT NULL,


    duree INTEGER NULL, -- En jours
    debut TEXT NULL, -- timestamp
    fin TEXT NULL
);

CREATE TABLE IF NOT EXISTS cotisations_tarifs
(
    id INTEGER PRIMARY KEY NOT NULL,
    label TEXT NOT NULL,
    description TEXT NULL,
    amount INTEGER NULL,
    formula TEXT NULL,
    id_category INTEGER NULL REFERENCES acc_categories (id) ON DELETE SET NULL, -- NULL si le type n'est pas associé automatiquement à la compta
);

CREATE TABLE IF NOT EXISTS cotisations_membres
-- Enregistrement des cotisations et activités
(
    id INTEGER NOT NULL PRIMARY KEY,
    id_membre INTEGER NOT NULL REFERENCES membres (id) ON DELETE CASCADE,
    id_cotisation INTEGER NOT NULL REFERENCES cotisations (id) ON DELETE CASCADE,
................................................................................
        UPDATE wiki_recherche SET contenu = '' WHERE id = new.id_page;
    END;

--
-- COMPTA
--

CREATE TABLE IF NOT EXISTS acc_years
-- Exercices
(
    id INTEGER NOT NULL PRIMARY KEY,

    label TEXT NOT NULL,

    start_date TEXT NOT NULL CHECK (date(debut) IS NOT NULL AND date(debut) = debut),
    end_date TEXT NOT NULL CHECK (date(fin) IS NOT NULL AND date(fin) = fin),

    closed INTEGER NOT NULL DEFAULT 0,

    id_plan INTEGER NOT NULL REFERENCES acc_plans (id)
);

CREATE TABLE IF NOT EXISTS acc_plans
-- Plans comptables : il peut y en avoir plusieurs
(
    id INTEGER NOT NULL PRIMARY KEY,
    country TEXT NOT NULL,
    code TEXT NULL, -- NULL = plan comptable créé par l'utilisateur
    label TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS acc_accounts
-- Comptes des plans comptables
(
    id INTEGER NOT NULL PRIMARY KEY,
    id_plan INTEGER NOT NULL REFERENCES compta_plans,

    code TEXT NOT NULL, -- peut contenir des lettres, eg. 53A, 53B, etc.
    parent INTEGER NULL REFERENCES compta_comptes(id),

    label TEXT NOT NULL,

    position INTEGER NOT NULL, -- position actif/passif/charge/produit
    type INTEGER NOT NULL DEFAULT 0, -- compte spécial : banque, caisse, en attente d'encaissement
    user INTEGER NOT NULL DEFAULT 1 -- 1 = fait partie du plan comptable original, 0 = a été ajouté par l'utilisateur



);

CREATE UNIQUE INDEX IF NOT EXISTS acc_accounts_codes ON acc_accounts (code, id_plan);
CREATE INDEX IF NOT EXISTS acc_accounts_parent ON acc_accounts (parent);














CREATE TABLE IF NOT EXISTS acc_transactions








-- Opérations comptables
(
    id INTEGER PRIMARY KEY NOT NULL,

    label TEXT NOT NULL,
    notes TEXT NULL,
    reference TEXT NULL, -- N° de pièce comptable

    date TEXT NOT NULL DEFAULT CURRENT_DATE CHECK (date(date) IS NOT NULL AND date(date) = date),



    validated INTEGER NOT NULL DEFAULT 0, -- 1 = écriture validée, non modifiable

    hash TEXT NULL,
    prev_hash TEXT NULL,



    id_year INTEGER NOT NULL REFERENCES acc_years(id),
    id_category INTEGER NULL REFERENCES acc_categories(id) ON DELETE SET NULL, -- Numéro de catégorie (en mode simple)







    id_analytical INTEGER NULL REFERENCES acc_accounts(id) ON DELETE SET NULL
);




CREATE INDEX IF NOT EXISTS acc_transactions_year ON acc_transactions (id_year);
CREATE INDEX IF NOT EXISTS acc_transactions_date ON acc_transactions (date);

























CREATE TABLE IF NOT EXISTS acc_transactions_lines

-- Lignes d'écritures d'une opération
(
    id INTEGER PRIMARY KEY NOT NULL,


    id_transaction INTEGER NOT NULL REFERENCES acc_transactions (id) ON DELETE CASCADE,
    id_account INTEGER NOT NULL REFERENCES acc_accounts (id), -- N° du compte dans le plan comptable


    credit INTEGER NOT NULL,
    debit INTEGER NOT NULL,

    reconcilied INTEGER NOT NULL DEFAULT 0,
    payment_reference TEXT NULL, -- Référence de paiement, eg. numéro de chèque

    CONSTRAINT line_check1 CHECK ((credit * debit) = 0),
    CONSTRAINT line_check2 CHECK ((credit + debit) > 0)
);


CREATE INDEX IF NOT EXISTS acc_transactions_lines_account ON acc_transactions_lines (id_account);

















CREATE TABLE IF NOT EXISTS acc_categories
-- Catégories pour simplifier le plan comptable
(
    id INTEGER NOT NULL PRIMARY KEY,
    type INTEGER NOT NULL DEFAULT 1, -- 1 = recette, -1 = dépense, 0 = autre (utilisé uniquement pour l'interface)

    label TEXT NOT NULL,
    description TEXT NULL,

    id_account INTEGER NULL REFERENCES acc_accounts(id) ON DELETE CASCADE -- Compte affecté par cette catégorie
);

CREATE TABLE IF NOT EXISTS plugins
(
    id TEXT NOT NULL PRIMARY KEY,
    officiel INTEGER NOT NULL DEFAULT 0,
    nom TEXT NOT NULL,
................................................................................
-- Associations entre fichiers et pages du wiki
(
    fichier INTEGER NOT NULL REFERENCES fichiers (id),
    id INTEGER NOT NULL REFERENCES wiki_pages (id),
    PRIMARY KEY(fichier, id)
);

CREATE TABLE IF NOT EXISTS fichiers_acc_transactions
-- Associations entre fichiers et journal de compta (pièce comptable par exemple)
(
    fichier INTEGER NOT NULL REFERENCES fichiers (id),
    id INTEGER NOT NULL REFERENCES acc_transactions (id),
    PRIMARY KEY(fichier, id)
);

CREATE TABLE IF NOT EXISTS recherches
-- Recherches enregistrées
(
    id INTEGER NOT NULL PRIMARY KEY,

Deleted src/include/lib/Garradin/Compta/Compte_Bancaire.php version [03377fbdd1].

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
<?php

namespace Garradin\Compta;

use Garradin\DB;
use Garradin\Utils;
use Garradin\ValidationException;

class Compte_Bancaire extends Compte
{
	protected $banque;
	protected $iban;
	protected $bic;

	protected $_extra_fields = [
		'banque' => 'required|string',
		'iban'   => 'alpha_num|max:34',
		'bic'    => 'alpha_num|max:11|min:8'
	];

	public function __construct($id = null)
	{
		$this->_fields = array_merge($this->_fields, $this->_extra_fields);
		parent::__construct($id);
	}

	public function filterUserEntry($key, $value)
	{
		$value = parent::filterUserEntry($key, $value);

		if ($key == 'iban' || $key == 'bic')
		{
			// Ne garder que les lettres et chiffres
			$value = preg_replace('![^\dA-Z]!', '', strtoupper($value));
		}

		return $value;
	}

	public function selfValidate()
	{
		parent::selfValidate();

		if (null !== $this->iban && !Utils::checkIBAN($this->iban))
		{
			throw new ValidationException('Code IBAN invalide');
		}

		if (null !== $this->bic && !Utils::checkBIC($this->bic))
		{
			throw new ValidationException('Code BIC/SWIFT invalide');
		}
	}
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<












































































































Deleted src/include/lib/Garradin/Compta/Comptes_Bancaires.php version [c2ddfadd03].

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
<?php

namespace Garradin\Compta;

use \Garradin\DB;
use \Garradin\Utils;
use \Garradin\UserException;


class Comptes_Bancaires extends Comptes
{
    const NUMERO_PARENT_COMPTES = 512;

    public function add($data)
    {
        $db = DB::getInstance();

        $data['parent'] = self::NUMERO_PARENT_COMPTES;
        $data['id'] = null;

        $this->_checkBankFields($data);

        $new_id = parent::add($data);

        $db->insert('compta_comptes_bancaires', [
            'id'     =>  $new_id,
            'banque' =>  $data['banque'],
            'iban'   =>  $data['iban'],
            'bic'    =>  $data['bic'],
        ]);

        return $new_id;
    }

    public function edit($id, $data)
    {
        $db = DB::getInstance();
        $id = trim($id);

        if (!$db->test('compta_comptes_bancaires', $db->where('id', $id)))
        {
            throw new UserException('Ce compte n\'est pas un compte bancaire.');
        }

        $this->_checkBankFields($data);
        $result = parent::edit($id, $data);

        if (!$result)
        {
            return $result;
        }

        $db->update('compta_comptes_bancaires', [
            'banque' =>  $data['banque'],
            'iban'   =>  $data['iban'],
            'bic'    =>  $data['bic'],
        ], $db->where('id', $id));

        return true;
    }

    /**
     * Supprime un compte bancaire
     * La suppression sera refusée si le compte est utilisé dans l'exercice en cours
     * ou dans une catégorie.
     * Le compte bancaire sera supprimé et le compte au plan comptable seulement désactivé
     * si le compte est utilisé dans un exercice précédent.
     *
     * La désactivation d'un compte fait qu'il n'est plus utilisable dans l'exercice courant
     * ou les exercices suivants, mais il est possible de le réactiver.
     * @param  string $id  Numéro du compte
     * @return boolean     TRUE si la suppression ou désactivation a été effectuée, une exception ou FALSE sinon
     */
    public function delete($id)
    {
        $db = DB::getInstance();
        $id = trim($id);

        if (!$db->test('compta_comptes_bancaires', $db->where('id', $id)))
        {
            throw new UserException('Ce compte n\'est pas un compte bancaire.');
        }

        // Ne pas supprimer/désactiver un compte qui est utilisé dans l'exercice courant
        if ($db->firstColumn('SELECT 1 FROM compta_journal
                WHERE id_exercice = (SELECT id FROM compta_exercices WHERE cloture = 0 LIMIT 1) 
                AND (compte_debit = ? OR compte_credit = ?) LIMIT 1;', $id, $id))
        {
            throw new UserException('Ce compte ne peut être supprimé car des écritures y sont liées sur l\'exercice courant. '
                . 'Il faut supprimer ou ré-attribuer ces écritures avant de pouvoir supprimer le compte.');
        }

        // Il n'est pas possible de supprimer ou désactiver un compte qui est lié à des catégories
        if ($db->test('compta_categories', $db->where('compte', $id)))
        {
            throw new UserException('Ce compte ne peut être supprimé car des catégories y sont liées. '
                . 'Merci de supprimer ou modifier les catégories liées avant de le supprimer.');
        }

        $db->delete('compta_comptes_bancaires', $db->where('id', $id));

        try {
            $return = parent::delete($id);
        }
        catch (UserException $e) {
            // Impossible de supprimer car des opérations y sont encore liées
            // sur les exercices précédents, alors on le désactive
            $return = parent::disable($id);
        }

        return $return;
    }

    public function get($id)
    {
        $db = DB::getInstance();
        return $db->first('SELECT * FROM compta_comptes AS c
            INNER JOIN compta_comptes_bancaires AS cc
            ON c.id = cc.id
            WHERE c.id = ?;', $id);
    }

    public function getList($parent = false)
    {
        $db = DB::getInstance();
        return $db->getGrouped('SELECT c.id AS id, * FROM compta_comptes AS c
            INNER JOIN compta_comptes_bancaires AS cc ON c.id = cc.id
            WHERE c.parent = ? ORDER BY c.id;', self::NUMERO_PARENT_COMPTES);
    }

    protected function _checkBankFields(&$data)
    {
        if (empty($data['banque']) || !trim($data['banque']))
        {
            throw new UserException('Le nom de la banque ne peut rester vide.');
        }

        if (empty($data['bic']))
        {
            $data['bic'] = '';
        }
        else
        {
            $data['bic'] = trim(strtoupper($data['bic']));
            $data['bic'] = preg_replace('![^\dA-Z]!', '', $data['bic']);

            if (!Utils::checkBIC($data['bic']))
            {
                throw new UserException('Code BIC/SWIFT invalide.');
            }
        }

        if (empty($data['iban']))
        {
            $data['iban'] = '';
        }
        else
        {
            $data['iban'] = trim(strtoupper($data['iban']));
            $data['iban'] = preg_replace('![^\dA-Z]!', '', $data['iban']);

            if (!Utils::checkIBAN($data['iban']))
            {
                throw new UserException('Code IBAN invalide.');
            }
        }

        return true;
    }
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




















































































































































































































































































































































Modified src/include/lib/Garradin/Entities/Compta/Account.php from [ec757b0335] to [ca23ed40e3].

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
namespace Garradin\Entities\Compta;

use Garradin\Entity;
use Garradin\DB;
use Garradin\Utils;
use Garradin\UserException;

class Compte extends Entity
{
	const TABLE = 'compta_comptes';

	const PASSIF = 1;
	const ACTIF = 2;
	const ACTIF_PASSIF = 3;

	const PRODUIT = 4;
	const CHARGE = 5;
	const PRODUIT_CHARGE = 6;

	// Types spéciaux de comptes
	const BANQUE = 7;
	const CAISSE = 8;

	const A_ENCAISSER = 9;





	protected $id;

	protected $code;
	protected $parent;
	protected $libelle;
	protected $position;
	protected $plan_comptable;
	protected $id_exercice;

	protected $_types = [

		'code'           => 'string',
		'parent'         => '?int',
		'libelle'        => 'string',
		'position'       => 'int',
		'plan_comptable' => 'int',
		'id_exercice'    => '?int',

	];

	protected $_validation_rules = [
		'id'             => 'required|integer',
		'libelle'        => 'required|string',

		'parent'         => 'required|integer|in_table:compta_comptes,id',
		'position'       => 'required|integer',

		'plan_comptable' => 'integer|min:0|max:1',
		'id_exercice'    => 'integer|in_table:compta_exercices,id'
	];
}







|

|









|
|
|

<
>
>
>
>


>


|

|
|


>
|
|
|
|
|
<
>



|
|
>
|
|
>
|
<


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
namespace Garradin\Entities\Compta;

use Garradin\Entity;
use Garradin\DB;
use Garradin\Utils;
use Garradin\UserException;

class Account extends Entity
{
	const TABLE = 'acc_accounts';

	const PASSIF = 1;
	const ACTIF = 2;
	const ACTIF_PASSIF = 3;

	const PRODUIT = 4;
	const CHARGE = 5;
	const PRODUIT_CHARGE = 6;

	const TYPE_NONE = 0;
	const TYPE_BANK = 1;
	const TYPE_CASH = 2;


	/**
	 * Outstanding transaction accounts (like cheque or card payments)
	 */
	const TYPE_OUTSTANDING = 3;

	protected $id;
	protected $id_plan;
	protected $code;
	protected $parent;
	protected $label;
	protected $position;
	protected $type;
	protected $user;

	protected $_types = [
		'id_plan'  => 'int',
		'code'     => 'string',
		'parent'   => '?int',
		'label'    => 'string',
		'type' => 'int',
		'special'  => 'int',

		'user'     => 'int',
	];

	protected $_validation_rules = [
		'id'       => 'required|integer',
		'libelle'  => 'required|string',
		'parent'   => 'required|nullable|integer|in_table:acc_accounts,id',
		'id_plan'  => 'required|integer|in_table:acc_plans,id',
		'type' => 'required|integer',
		'special'  => 'required|integer',
		'user'     => 'integer|min:0|max:1',

	];
}

Modified src/include/lib/Garradin/Entities/Compta/Line.php from [820ffb3e7d] to [a3e700e1fa].

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
..
47
48
49
50
51
52
53
54
55
56
<?php

namespace Garradin\Entities\Compta;

use Garradin\Entity;
use Garradin\ValidationException;

class Ligne extends Entity
{
	const TABLE = 'compta_mouvements_lignes';

	protected $id;

	protected $id_mouvement;
	protected $credit = 0;
	protected $debit = 0;

	protected $compte;

	protected $_types = [
		'id_mouvement' => 'int',
		'credit'       => 'int',
		'debit'        => 'int',
		'compte'       => 'int',



	];

	protected $_validation_rules = [
		'id_mouvement' => 'required|integer|in_table:compta_mouvements,id',
		'compte'       => 'required|integer|in_table:compta_comptes,id',
		'credit'       => 'required|integer|min:0',
		'debit'        => 'required|integer|min:0'


	];

	public function filterUserValue(string $key, $value, array $source)
	{
		$value = parent::filterUserValue($key, $value);

		if ($key == 'credit' || $key == 'debit')
................................................................................
	}

	public function selfCheck()
	{
		parent::selfCheck();
		$this->assert($this->credit || $this->debit, 'Aucun montant au débit ou au crédit.');
		$this->assert(($this->credit * $this->debit) === 0 && ($this->credit + $this->debit) > 0, 'Ligne non équilibrée : crédit ou débit doit valoir zéro.');
		$this->assert($this->id_mouvement, 'Aucun mouvement n\'a été indiqué pour cette ligne.');
	}
}







|

|


>
|


>
|


<
|
|
|
>
>
>



|
|
|
|
>
>







 







|


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
..
53
54
55
56
57
58
59
60
61
62
<?php

namespace Garradin\Entities\Compta;

use Garradin\Entity;
use Garradin\ValidationException;

class Line extends Entity
{
	const TABLE = 'acc_transactions_lines';

	protected $id;
	protected $id_transaction;
	protected $id_account;
	protected $credit = 0;
	protected $debit = 0;
	protected $payment_reference;
	protected $reconciled;

	protected $_types = [

		'id_transaction'    => 'int',
		'id_account'        => 'int',
		'credit'            => 'int',
		'debit'             => 'int',
		'payment_reference' => '?string',
		'reconcilied'       => 'int',
	];

	protected $_validation_rules = [
		'id_transaction'    => 'required|integer|in_table:acc_transactions,id',
		'id_account'        => 'required|integer|in_table:acc_accounts,id',
		'credit'            => 'required|integer|min:0',
		'debit'             => 'required|integer|min:0',
		'payment_reference' => 'string|max:200',
		'reconcilied'       => 'int|min:0|max:1',
	];

	public function filterUserValue(string $key, $value, array $source)
	{
		$value = parent::filterUserValue($key, $value);

		if ($key == 'credit' || $key == 'debit')
................................................................................
	}

	public function selfCheck()
	{
		parent::selfCheck();
		$this->assert($this->credit || $this->debit, 'Aucun montant au débit ou au crédit.');
		$this->assert(($this->credit * $this->debit) === 0 && ($this->credit + $this->debit) > 0, 'Ligne non équilibrée : crédit ou débit doit valoir zéro.');
		$this->assert($this->id_transaction, 'Aucun mouvement n\'a été indiqué pour cette ligne.');
	}
}

Modified src/include/lib/Garradin/Entities/Compta/Transaction.php from [d018180264] to [71f26cd748].

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
...
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
198
199
200
201
202
203
204
205
206
207

208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
<?php

namespace Garradin\Entities\Compta;

use Garradin\Entity;
use Garradin\ValidationException;
use Garradin\DB;
use Garradin\Config;

class Mouvement extends Entity
{
	const TABLE = 'compta_mouvements';

	protected $id;
	protected $libelle;
	protected $remarques;
	protected $numero_piece;

	protected $date;
	protected $moyen_paiement;
	protected $reference_paiement;

	protected $validation;

	protected $hash;
	protected $prev_hash;

	protected $id_exercice;
	protected $id_auteur;
	protected $id_categorie;
	protected $id_projet;

	protected $_types = [
		'libelle'            => 'string',
		'remarques'          => '?string',
		'numero_piece'       => '?string',
		'date'               => 'date',
		'moyen_paiement'     => '?string',
		'reference_paiement' => '?string',
		'validation'         => 'bool',
		'hash'               => '?string',
		'prev_hash'          => '?string',
		'id_exercice'        => '?int',
		'id_auteur'          => '?int',
		'id_categorie'       => '?int',
		'id_projet'          => '?int',
	];

	protected $_validation_rules = [
		'libelle'            => 'required|string|max:200',
		'remarques'          => 'string|max:20000',
		'numero_piece'       => 'string|max:200',
		'date'               => 'required|date',
		'moyen_paiement'     => 'string|min:2|max:2|in_table:compta_moyens_paiement,code|required_with:id_categorie',
		'reference_paiement' => 'string|max:200',
		'validation'         => 'bool',
		'id_exercice'        => 'integer|in_table:compta_exercices,id',
		'id_auteur'          => 'integer|in_table:membres,id',
		'id_categorie'       => 'integer|in_table:compta_categories,id',
		'id_projet'          => 'integer|in_table:compta_projets,id'
	];

	protected $lignes;

	public function getLignes()
	{
		if (null === $this->lignes && $this->exists()) {
			$db = DB::getInstance();
			$this->lignes = $db->toObject($db->get('SELECT * FROM compta_mouvements_lignes WHERE id_mouvement = ? ORDER BY id;', $this->id), Ligne::class);

		}
		else {
			$this->lignes = [];
		}

		return $this->lignes;
	}

/*
	public function getHash()
	{
		if (!$this->id_exercice) {
			throw new \LogicException('Il n\'est pas possible de hasher un mouvement qui n\'est pas associé à un exercice');
		}

		static $keep_keys = [
			'libelle',
			'remarques',
			'numero_piece',
			'date',
			'moyen_paiement',
			'reference_paiement',
			'validation',
			'prev_hash',
		];

		$hash = hash_init('sha256');
		$values = $this->asArray();
		$values = array_intersect_key($values, $keep_keys);

		hash_update($hash, implode(',', array_keys($values)));
		hash_update($hash, implode(',', $values));

		foreach ($this->getLignes() as $ligne) {
			hash_update($hash, implode(',', [$ligne->compte, $ligne->debit, $ligne->credit]));
		}

		return hash_final($hash, false);
	}

	public function checkHash()
................................................................................
	{
		return hash_equals($this->getHash(), $this->hash);
	}
*/

	public function add(Ligne $ligne)
	{
		$this->lignes[] = $ligne;
	}

	public function simple($montant, $moyen, $compte)
	{
		$this->moyen_paiement = $moyen;
		$categorie = new Categorie($this->id_categorie);

		if ($categorie->type == Categorie::DEPENSE)
		{
			$from = $categorie->compte;
			$to = $compte;
		}
		else
................................................................................
		$ligne1->credit = $amount;

		return $this->add($ligne1) && $this->add($ligne2);
	}

	public function save()
	{
		if ($this->validation && !isset($this->_modified['validation'])) {
			throw new ValidationException('Il n\'est pas possible de modifier un mouvement qui a été validé');
		}

		if (!parent::save()) {
			return false;
		}

		foreach ($this->lignes as $ligne)
		{
			$ligne->id_mouvement = $this->id;
			$ligne->save();
		}
	}

	public function delete()
	{
		if ($this->validation) {
			throw new ValidationException('Il n\'est pas possible de supprimer un mouvement qui a été validé');
		}

		parent::delete();
	}

	public function filterUserValue($key, $value)
	{
		$value = parent::filterUserValue($key, $value);

		if ($key == 'moyen_paiement')
		{
			$value = strtoupper($value);
		}

		return $value;
	}

	public function selfCheck()
	{
		parent::selfCheck();

		$db = DB::getInstance();
		$config = Config::getInstance();

		// ID d'exercice obligatoire s'il existe déjà des exercices
		if (null === $this->id_exercice && $db->firstColumn('SELECT 1 FROM compta_exercices LIMIT 1;'))
		{
			throw new ValidationException('Aucun exercice spécifié.');
		}

		if (null !== $this->id_exercice
			&& !$db->test('compta_exercices', 'id = ? AND debut <= ? AND fin >= ?;', $this->id_exercice, $this->date, $this->date))

		{
			throw new ValidationException('La date ne correspond pas à l\'exercice sélectionné.');
		}

		$total = 0;

		$lignes = $this->getLignes();

		foreach ($lignes as $ligne) {
			$total += $ligne->credit;
			$total -= $ligne->debit;
		}

		if (0 !== $total) {
			throw new ValidationException('Mouvement non équilibré : déséquilibre entre débits et crédits');
		}
	}
}





|



|

|


|
|
|


<
<

|




<
|
|
|


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


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


|

|

|

<
>


|


|





|




|
|
|

<
<
|










|







 







|


|

<
|







 







|
|






|

|






|
|





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








|
<
|


|
<
>

|




|

|
|
|



|



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
...
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
198
199
200
<?php

namespace Garradin\Entities\Compta;

use Garradin\Entity;
use Garradin\validatedException;
use Garradin\DB;
use Garradin\Config;

class Transaction extends Entity
{
	const TABLE = 'acc_transactions';

	protected $id;
	protected $label;
	protected $notes;
	protected $reference;

	protected $date;



	protected $validated;

	protected $hash;
	protected $prev_hash;


	protected $id_year;
	protected $id_category;
	protected $id_analytical;

	protected $_types = [
		'label'             => 'string',
		'notes'             => '?string',
		'reference'         => '?string',
		'date'              => 'date',


		'validated'         => 'bool',
		'hash'              => '?string',
		'prev_hash'         => '?string',
		'id_year'           => 'int',
		'id_category'       => '?int',
		'id_analytical'     => '?int',

	];

	protected $_validated_rules = [
		'label'             => 'required|string|max:200',
		'notes'             => 'string|max:20000',
		'reference'         => 'string|max:200',
		'date'              => 'required|date',


		'validated'         => 'bool',
		'id_year'           => 'integer|in_table:acc_years,id',
		'id_category'       => 'integer|in_table:acc_categories,id',

		'id_analytical'     => 'integer|in_table:acc_accounts,id'
	];

	protected $lines;

	public function getLines()
	{
		if (null === $this->lines && $this->exists()) {
			$db = DB::getInstance();

			$this->lines = $db->toObject($db->get('SELECT * FROM acc_transactions_lines WHERE id_transaction = ? ORDER BY id;', $this->id), Ligne::class);
		}
		else {
			$this->lines = [];
		}

		return $this->lines;
	}

/*
	public function getHash()
	{
		if (!$this->id_year) {
			throw new \LogicException('Il n\'est pas possible de hasher un mouvement qui n\'est pas associé à un exercice');
		}

		static $keep_keys = [
			'label',
			'notes',
			'reference',
			'date',


			'validated',
			'prev_hash',
		];

		$hash = hash_init('sha256');
		$values = $this->asArray();
		$values = array_intersect_key($values, $keep_keys);

		hash_update($hash, implode(',', array_keys($values)));
		hash_update($hash, implode(',', $values));

		foreach ($this->getLines() as $ligne) {
			hash_update($hash, implode(',', [$ligne->compte, $ligne->debit, $ligne->credit]));
		}

		return hash_final($hash, false);
	}

	public function checkHash()
................................................................................
	{
		return hash_equals($this->getHash(), $this->hash);
	}
*/

	public function add(Ligne $ligne)
	{
		$this->lines[] = $ligne;
	}

	public function simple($montant, $compte)
	{

		$categorie = new Categorie($this->id_category);

		if ($categorie->type == Categorie::DEPENSE)
		{
			$from = $categorie->compte;
			$to = $compte;
		}
		else
................................................................................
		$ligne1->credit = $amount;

		return $this->add($ligne1) && $this->add($ligne2);
	}

	public function save()
	{
		if ($this->validated && !isset($this->_modified['validated'])) {
			throw new validatedException('Il n\'est pas possible de modifier un mouvement qui a été validé');
		}

		if (!parent::save()) {
			return false;
		}

		foreach ($this->lines as $ligne)
		{
			$ligne->id_transaction = $this->id;
			$ligne->save();
		}
	}

	public function delete()
	{
		if ($this->validated) {
			throw new validatedException('Il n\'est pas possible de supprimer un mouvement qui a été validé');
		}

		parent::delete();
	}













	public function selfCheck()
	{
		parent::selfCheck();

		$db = DB::getInstance();
		$config = Config::getInstance();

		// ID d'exercice obligatoire s'il existe déjà des exercices
		if (null === $this->id_year && $db->firstColumn('SELECT 1 FROM acc_years LIMIT 1;')) {

			throw new validatedException('Aucun exercice spécifié.');
		}

		if (null !== $this->id_year

			&& !$db->test('acc_years', 'id = ? AND start_date <= ? AND end_date >= ?;', $this->id_year, $this->date, $this->date))
		{
			throw new validatedException('La date ne correspond pas à l\'exercice sélectionné.');
		}

		$total = 0;

		$lines = $this->getLines();

		foreach ($lines as $line) {
			$total += $line->credit;
			$total -= $line->debit;
		}

		if (0 !== $total) {
			throw new validatedException('Mouvement non équilibré : déséquilibre entre débits et crédits');
		}
	}
}

Modified src/include/lib/Garradin/Entities/Compta/Year.php from [d63b31b5b9] to [7f892d19e6].

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
namespace Garradin\Entities\Compta;

use Garradin\Entity;
use Garradin\DB;
use Garradin\Utils;
use Garradin\UserException;

class Exercice extends Entity
{
    const TABLE = 'compta_exercices';

    protected $id;
    protected $libelle;
    protected $debut;
    protected $fin;
    protected $cloture = 0;

    protected $_types = [
        'id'      => 'integer',
        'libelle' => 'string',
        'debut'   => 'date',
        'fin'     => 'date',
        'cloture' => 'integer',
    ];

    protected $_validation_rules = [
        'libelle' => 'required|string|max:200',
        'debut'   => 'required|date|before:fin',
        'fin'     => 'required|date|after:debut',
        'cloture' => 'int|min:0|max:1',
    ];

    public function selfCheck(): void
    {
        parent::selfCheck();
        $this->assert($this->debut < $this->fin, 'La date de fin doit être postérieure à la date de début');
        $this->assert($this->cloture == 1 || !isset($this->_modified['cloture']), 'Il est interdit de réouvrir un exercice clôturé');

        $db = DB::getInstance();

        // Vérifier qu'on ne crée pas 2 exercices qui se recoupent
        if ($this->exists()) {
            $this->assert(
                !$db->test(self::TABLE, 'id != :id AND ((debut <= :debut AND fin >= :debut) OR (debut <= :fin AND fin >= :fin))',
                    ['id' => $this->id(), 'debut' => $this->debut, 'fin' => $this->fin]),
                'La date de début ou de fin se recoupe avec un exercice existant.'
            );

            $this->assert(
                !$db->test(Mouvements::TABLE, 'id_exercice = ? AND date < ?', $this->id(), $this->debut),
                'Des mouvements de cet exercice ont une date antérieure à la date de début de l\'exercice.'
            );

            $this->assert(
                !$db->test(Mouvements::TABLE, 'id_exercice = ? AND date > ?', $this->id(), $this->fin),
                'Des mouvements de cet exercice ont une date postérieure à la date de fin de l\'exercice.'
            );
        }
        else {
            $this->assert(
                !$db->test(self::TABLE, '(debut <= :debut AND fin >= :debut) OR (debut <= :fin AND fin >= :fin)',
                    ['debut' => $this->debut, 'fin' => $this->fin]),
                'La date de début ou de fin se recoupe avec un exercice existant.'
            );
        }
    }

    public function close()
    {
        if ($this->cloture) {
            throw new \LogicException('Cet exercice est déjà clôturé');
        }

        $this->set('cloture', 1);
    }

    public function delete(): bool
    {
        $db = DB::getInstance();

        // Ne pas supprimer un compte qui est utilisé !
        if ($db->test(Mouvements::TABLE, $db->where('id_exercice', $this->id())))
        {
            throw new UserException('Cet exercice ne peut être supprimé car des mouvements y sont liés.');
        }

        return parent::delete();
    }
}







|

|


|
|
|
|


|
|
|
|
|



|
|
|
|





|
|






|
|




|




|





|
|







|



|







|







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
namespace Garradin\Entities\Compta;

use Garradin\Entity;
use Garradin\DB;
use Garradin\Utils;
use Garradin\UserException;

class Year extends Entity
{
    const TABLE = 'acc_years';

    protected $id;
    protected $label;
    protected $start_date;
    protected $end_date;
    protected $closed = 0;

    protected $_types = [
        'id'         => 'integer',
        'label'      => 'string',
        'start_date' => 'date',
        'end_date'   => 'date',
        'closed'     => 'integer',
    ];

    protected $_validation_rules = [
        'label'      => 'required|string|max:200',
        'start_date' => 'required|date|before:end_date',
        'end_date'   => 'required|date|after:start_date',
        'closed'     => 'int|min:0|max:1',
    ];

    public function selfCheck(): void
    {
        parent::selfCheck();
        $this->assert($this->start_date < $this->end_date, 'La date de fin doit être postérieure à la date de début');
        $this->assert($this->closed == 1 || !isset($this->_modified['closed']), 'Il est interdit de réouvrir un exercice clôturé');

        $db = DB::getInstance();

        // Vérifier qu'on ne crée pas 2 exercices qui se recoupent
        if ($this->exists()) {
            $this->assert(
                !$db->test(self::TABLE, 'id != :id AND ((start_date <= :start_date AND end_date >= :start_date) OR (start_date <= :end_date AND end_date >= :start_date))',
                    ['id' => $this->id(), 'start_date' => $this->start_date, 'end_date' => $this->end_date]),
                'La date de début ou de fin se recoupe avec un exercice existant.'
            );

            $this->assert(
                !$db->test(Transaction::TABLE, 'id_year = ? AND date < ?', $this->id(), $this->start_date),
                'Des mouvements de cet exercice ont une date antérieure à la date de début de l\'exercice.'
            );

            $this->assert(
                !$db->test(Transaction::TABLE, 'id_year = ? AND date > ?', $this->id(), $this->end_date),
                'Des mouvements de cet exercice ont une date postérieure à la date de fin de l\'exercice.'
            );
        }
        else {
            $this->assert(
                !$db->test(self::TABLE, '(start_date <= :start_date AND end_date >= :start_date) OR (start_date <= :end_date AND end_date >= :start_date)',
                    ['start_date' => $this->start_date, 'end_date' => $this->end_date]),
                'La date de début ou de fin se recoupe avec un exercice existant.'
            );
        }
    }

    public function close()
    {
        if ($this->closed) {
            throw new \LogicException('Cet exercice est déjà clôturé');
        }

        $this->set('closed', 1);
    }

    public function delete(): bool
    {
        $db = DB::getInstance();

        // Ne pas supprimer un compte qui est utilisé !
        if ($db->test(Transaction::TABLE, $db->where('id_year', $this->id())))
        {
            throw new UserException('Cet exercice ne peut être supprimé car des mouvements y sont liés.');
        }

        return parent::delete();
    }
}