Overview
Comment:Création de l'entité Exercice
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA1: 24709246dfeceb72a9fb42ef4378f3732ca758dc
User & Date: bohwaz on 2020-01-21 13:28:02
Other Links: branch diff | manifest | tags
Context
2020-01-27
00:43
* Gérer les fichiers CSV provenant des vieilles versions d'Excel pour Mac OS. * Ajouter la mention de la taille limite du fichier check-in: 738039429a user: bohwaz tags: dev
2020-01-21
13:28
Création de l'entité Exercice check-in: 24709246df user: bohwaz tags: dev
13:27
Déplacement des entités, ajout de vérifications supplémentaires check-in: 5a3f31fcb9 user: bohwaz tags: dev
Changes

Modified src/VERSION from [72f19f670a] to [c538b66c71].

1
0.9.5
|
1
1.0.0

Modified src/include/data/1.0.0_migration.sql from [ab0d748387] to [0f53908e4c].

1
2
3

4
5
6
7
8
9
10
ALTER TABLE compta_journal RENAME TO compta_journal_old;
ALTER TABLE compta_comptes RENAME TO compta_comptes_old;
ALTER TABLE compta_categories RENAME TO compta_categories_old;

ALTER TABLE membres_operations RENAME TO membres_operations_old;

DROP TABLE fichiers_compta_journal; -- Inutilisé à ce jour

.read 1.0.0_schema.sql

-- Migration comptes de code comme identifiant à ID unique



>







1
2
3
4
5
6
7
8
9
10
11
ALTER TABLE compta_journal RENAME TO compta_journal_old;
ALTER TABLE compta_comptes RENAME TO compta_comptes_old;
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;

DROP TABLE fichiers_compta_journal; -- Inutilisé à ce jour

.read 1.0.0_schema.sql

-- Migration comptes de code comme identifiant à ID unique
31
32
33
34
35
36
37




38
39
40
41

42
43
44
45
46
47
48
INSERT INTO compta_categories
	SELECT id, type, intitule, description, (SELECT id FROM compta_comptes WHERE code = compte) FROM compta_categories_old;

-- Recopie des opérations, mais le nom a changé pour "mouvements"
INSERT INTO membres_mouvements
	SELECT * FROM membres_operations_old;





DROP TABLE compta_journal_old;
DROP TABLE membres_operations_old;
DROP TABLE compta_categories_old;
DROP TABLE compta_comptes_old;


-- CREATE TABLE IF NOT EXISTS compta_comptes_soldes
-- -- Soldes des comptes
-- (
--     compte TEXT NOT NULL REFERENCES compta_comptes(id) ON DELETE CASCADE,
--     exercice INTEGER NULL REFERENCES compta_exercices(id) ON DELETE CASCADE,
--     solde INTEGER NOT NULL,







>
>
>
>




>







32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
INSERT INTO compta_categories
	SELECT id, type, intitule, description, (SELECT id FROM compta_comptes WHERE code = compte) FROM compta_categories_old;

-- Recopie des opérations, mais le nom a changé pour "mouvements"
INSERT INTO membres_mouvements
	SELECT * FROM membres_operations_old;

-- Recopie des exercices, mais la date de fin ne peut être nulle
INSERT INTO compta_exercices
	SELECT id, libelle, debut, CASE WHEN fin IS NULL THEN date(debut, '+1 year') ELSE fin END, cloture FROM compta_exercices_old;

DROP TABLE compta_journal_old;
DROP TABLE membres_operations_old;
DROP TABLE compta_categories_old;
DROP TABLE compta_comptes_old;
DROP TABLE compta_exercices_old;

-- CREATE TABLE IF NOT EXISTS compta_comptes_soldes
-- -- Soldes des comptes
-- (
--     compte TEXT NOT NULL REFERENCES compta_comptes(id) ON DELETE CASCADE,
--     exercice INTEGER NULL REFERENCES compta_exercices(id) ON DELETE CASCADE,
--     solde INTEGER NOT NULL,

Modified src/include/data/1.0.0_schema.sql from [4c7e56746d] to [dff2a1e0df].

181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
CREATE TABLE IF NOT EXISTS compta_exercices
-- Exercices
(
    id INTEGER NOT NULL PRIMARY KEY,

    libelle TEXT NOT NULL,

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

    cloture INTEGER NOT NULL DEFAULT 0
);


CREATE TABLE IF NOT EXISTS compta_comptes
-- Plan comptable







|
|







181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
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
258
259
260
261
262
263
264
265
266
267
























268
269
270
271
272
273
274
    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_operations_exercice ON compta_mouvements (id_exercice);
CREATE INDEX IF NOT EXISTS compta_operations_date ON compta_mouvements (date);
CREATE INDEX IF NOT EXISTS compta_operations_auteur ON compta_mouvements (id_auteur);

























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,







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







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
    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,

Modified src/include/lib/Garradin/Compta/Exercices.php from [3bc6667d2a] to [ee673cc62e].

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

namespace Garradin\Compta;


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

class Exercices
{
    public function add($data)
    {
        $this->_checkFields($data);

        $db = DB::getInstance();

        if ($db->firstColumn('SELECT 1 FROM compta_exercices WHERE
            (debut <= :debut AND fin >= :debut) OR (debut <= :fin AND fin >= :fin);',
            ['debut' => $data['debut'], 'fin' => $data['fin']]))
        {
            throw new UserException('La date de début ou de fin se recoupe avec un autre exercice.');
        }

        if ($db->firstColumn('SELECT 1 FROM compta_exercices WHERE cloture = 0;'))
        {
            throw new UserException('Il n\'est pas possible de créer un nouvel exercice tant qu\'il existe un exercice non-clôturé.');
        }

        $db->insert('compta_exercices', [
            'libelle'   =>  trim($data['libelle']),
            'debut'     =>  $data['debut'],
            'fin'       =>  $data['fin'],
        ]);

        return $db->lastInsertRowId();
    }

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

        $this->_checkFields($data);

        // Evitons que les exercices se croisent
        if ($db->firstColumn('SELECT 1 FROM compta_exercices WHERE id != :id AND
            ((debut <= :debut AND fin >= :debut) OR (debut <= :fin AND fin >= :fin));',
            ['debut' => $data['debut'], 'fin' => $data['fin'], 'id' => (int) $id]))
        {
            throw new UserException('La date de début ou de fin se recoupe avec un autre exercice.');
        }

        // On vérifie qu'on ne va pas mettre des opérations en dehors de tout exercice
        if ($db->firstColumn('SELECT 1 FROM compta_journal WHERE id_exercice = ?
            AND date < ? LIMIT 1;', (int)$id, $data['debut']))
        {
            throw new UserException('Des opérations de cet exercice ont une date antérieure à la date de début de l\'exercice.');
        }

        if ($db->firstColumn('SELECT 1 FROM compta_journal WHERE id_exercice = ?
            AND date > ? LIMIT 1;', (int)$id, $data['fin']))
        {
            throw new UserException('Des opérations de cet exercice ont une date postérieure à la date de fin de l\'exercice.');
        }

        $db->update('compta_exercices', [
            'libelle'   =>  trim($data['libelle']),
            'debut'     =>  $data['debut'],
            'fin'       =>  $data['fin'],
        ], 'id = :id', ['id' => (int)$id]);

        return true;
    }

    /**
     * Clôturer un exercice et en ouvrir un nouveau
     * Le report à nouveau n'est pas effectué automatiquement par cette fonction, voir doReports pour ça.
     * @param  integer  $id     ID de l'exercice à clôturer
     * @param  string   $end    Date de clôture de l'exercice au format Y-m-d
     * @return integer          L'ID du nouvel exercice créé
     */
    public function close($id, $end)
    {
        $db = DB::getInstance();

        if (!Utils::checkDate($end))
        {
            throw new UserException('Date de fin vide ou invalide.');
        }

        $db->begin();

        // Clôture de l'exercice
        $db->update('compta_exercices', [
            'cloture'   =>  1,
            'fin'       =>  $end,
        ], 'id = :id', ['id' => (int)$id]);

        // Date de début du nouvel exercice : lendemain de la clôture du précédent exercice
        $new_begin = Utils::modifyDate($end, '+1 day');

        // Date de fin du nouvel exercice : un an moins un jour après l'ouverture
        $new_end = Utils::modifyDate($new_begin, '+1 year -1 day');

        // Enfin sauf s'il existe déjà des opérations après cette date, auquel cas la date de fin
        // est fixée à la date de la dernière opération, ceci pour ne pas avoir d'opération
        // orpheline d'exercice
        $last = $db->firstColumn('SELECT date FROM compta_journal WHERE id_exercice = ? AND date >= ? ORDER BY date DESC LIMIT 1;', $id, $new_end);
        $new_end = $last ?: $new_end;

        $year_begin = substr($new_begin, 0, 4);
        $year_end = substr($new_end, 0, 4);

        // Nom du nouvel exercice
        if ($year_begin == $year_end) {
            $label = sprintf('Exercice %d', $year_begin);
        }
        else {
            $label = sprintf('Exercice %d-%d', $year_begin, $year_end);
        }

        // Création du nouvel exercice
        $new_id = $this->add([
            'debut'     =>  $new_begin,
            'fin'       =>  $new_end,
            'libelle'   =>  $label,
        ]);

        // Ré-attribution des opérations de l'exercice à clôturer qui ne sont pas dans son
        // intervale au nouvel exercice
        $db->update('compta_journal', ['id_exercice' => $new_id], 'id_exercice = :id AND date >= :date', [
            'id'   => $id,
            'date' => $new_begin,
        ]);

        $db->commit();

        return $new_id;
    }

    /**
     * Créer les reports à nouveau issus de l'exercice $old_id dans le nouvel exercice courant
     * @param  integer $old_id  ID de l'ancien exercice
     * @param  integer $new_id  ID du nouvel exercice
     * @param  string  $date    Date Y-m-d donnée aux opérations créées
     * @return boolean          true si succès
     */




>
|
|
|



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







1
2
3
4
5
6
7
8
9
10
11

































































































































12
13
14
15
16
17
18
<?php

namespace Garradin\Compta;

use Garradin\Entities\Exercice;
use Garradin\DB;
use Garradin\Utils;
use Garradin\UserException;

class Exercices
{

































































































































    /**
     * Créer les reports à nouveau issus de l'exercice $old_id dans le nouvel exercice courant
     * @param  integer $old_id  ID de l'ancien exercice
     * @param  integer $new_id  ID du nouvel exercice
     * @param  string  $date    Date Y-m-d donnée aux opérations créées
     * @return boolean          true si succès
     */
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
                'date'      =>  $date,
                'montant'   =>  abs($resultat),
                'compte_debit'  =>  $resultat < 0 ? $resultat_debiteur : 890,
                'compte_credit' =>  $resultat > 0 ? $resultat_excedent : 890,
            ]);
        }

        return true;
    }

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

        // Ne pas supprimer un compte qui est utilisé !
        if ($db->test('compta_journal', $db->where('id_exercice', $id)))
        {
            throw new UserException('Cet exercice ne peut être supprimé car des opérations comptables y sont liées.');
        }

        $db->delete('compta_exercices', 'id = ?', (int)$id);

        return true;
    }

    public function get($id, $with_count = false)
    {
        $with_count = $with_count
            ? ', (SELECT COUNT(*) FROM compta_journal WHERE id_exercice = compta_exercices.id) AS nb_operations'
            : '';

        $db = DB::getInstance();
        return $db->first('SELECT *, strftime(\'%s\', debut) AS debut,
            strftime(\'%s\', fin) AS fin ' . $with_count . '
            FROM compta_exercices WHERE id = ?;', (int)$id);
    }

    public function getCurrent()
    {
        $db = DB::getInstance();
        return $db->first('SELECT *, strftime(\'%s\', debut) AS debut, strftime(\'%s\', fin) AS fin FROM compta_exercices
            WHERE cloture = 0 LIMIT 1;');
    }

    public function getCurrentId()
    {
        $db = DB::getInstance();
        return $db->firstColumn('SELECT id FROM compta_exercices WHERE cloture = 0 LIMIT 1;');
    }

    public function getList()
    {
        $db = DB::getInstance();
        return $db->getGrouped('SELECT id, *, strftime(\'%s\', debut) AS debut,
            strftime(\'%s\', fin) AS fin,
            (SELECT COUNT(*) FROM compta_journal WHERE id_exercice = compta_exercices.id) AS nb_operations
            FROM compta_exercices ORDER BY fin DESC;');
    }

    protected function _checkFields(&$data)
    {
        if (empty($data['libelle']) || !trim($data['libelle']))
        {
            throw new UserException('Le libellé ne peut rester vide.');
        }

        $data['libelle'] = trim($data['libelle']);

        if (empty($data['debut']) || !checkdate(substr($data['debut'], 5, 2), substr($data['debut'], 8, 2), substr($data['debut'], 0, 4)))
        {
            throw new UserException('Date de début vide ou invalide.');
        }

        if (empty($data['fin']) || !checkdate(substr($data['fin'], 5, 2), substr($data['fin'], 8, 2), substr($data['fin'], 0, 4)))
        {
            throw new UserException('Date de fin vide ou invalide.');
        }

        return true;
    }
}









|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
127
128
129
130
131
132
133
134
135
136







































































                'date'      =>  $date,
                'montant'   =>  abs($resultat),
                'compte_debit'  =>  $resultat < 0 ? $resultat_debiteur : 890,
                'compte_credit' =>  $resultat > 0 ? $resultat_excedent : 890,
            ]);
        }

        return true;
    }
}







































































Added src/include/lib/Garradin/Entities/Compta/Exercice.php version [f7e29fc30f].























































































































































































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

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'      => 'int',
        'libelle' => 'string',
        'debut'   => 'date',
        'fin'     => 'date',
        'cloture' => 'int',
    ];

    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()
    {
        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()
    {
        $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.');
        }

        parent::delete();
    }
}