02edb1477f8852015b894e0e27a484983fb01e4c
7 public function add($data)
9 $this->_checkFields($data);
11 $db = DB
::getInstance();
13 if ($db->simpleQuerySingle('SELECT 1 FROM compta_exercices WHERE
14 (debut <= :debut AND fin >= :debut) OR (debut <= :fin AND fin >= :fin);', false,
15 ['debut' => $data['debut'], 'fin' => $data['fin']]))
17 throw new UserException('La date de début ou de fin se recoupe avec un autre exercice.');
20 if ($db->querySingle('SELECT 1 FROM compta_exercices WHERE cloture = 0;'))
22 throw new UserException('Il n\'est pas possible de créer un nouvel exercice tant qu\'il existe un exercice non-clôturé.');
25 $db->simpleInsert('compta_exercices', [
26 'libelle' => trim($data['libelle']),
27 'debut' => $data['debut'],
28 'fin' => $data['fin'],
31 return $db->lastInsertRowId();
34 public function edit($id, $data)
36 $db = DB
::getInstance();
38 $this->_checkFields($data);
40 // Evitons que les exercices se croisent
41 if ($db->simpleQuerySingle('SELECT 1 FROM compta_exercices WHERE id != :id AND
42 ((debut <= :debut AND fin >= :debut) OR (debut <= :fin AND fin >= :fin));', false,
43 ['debut' => $data['debut'], 'fin' => $data['fin'], 'id' => (int) $id]))
45 throw new UserException('La date de début ou de fin se recoupe avec un autre exercice.');
48 // On vérifie qu'on ne va pas mettre des opérations en dehors de tout exercice
49 if ($db->simpleQuerySingle('SELECT 1 FROM compta_journal WHERE id_exercice = ?
50 AND date < ? LIMIT 1;', false, (int)$id, $data['debut']))
52 throw new UserException('Des opérations de cet exercice ont une date antérieure à la date de début de l\'exercice.');
55 if ($db->simpleQuerySingle('SELECT 1 FROM compta_journal WHERE id_exercice = ?
56 AND date > ? LIMIT 1;', false, (int)$id, $data['fin']))
58 throw new UserException('Des opérations de cet exercice ont une date postérieure à la date de fin de l\'exercice.');
61 $db->simpleUpdate('compta_exercices', [
62 'libelle' => trim($data['libelle']),
63 'debut' => $data['debut'],
64 'fin' => $data['fin'],
65 ], 'id = \''.(int)$id.'\'');
71 * Clôturer un exercice et en ouvrir un nouveau
72 * Le report à nouveau n'est pas effectué automatiquement par cette fonction, voir doReports pour ça.
73 * @param integer $id ID de l'exercice à clôturer
74 * @param string $end Date de clôture de l'exercice au format Y-m-d
75 * @return integer L'ID du nouvel exercice créé
77 public function close($id, $end)
79 $db = DB
::getInstance();
81 if (!utils
::checkDate($end))
83 throw new UserException('Date de fin vide ou invalide.');
88 // Clôture de l'exercice
89 $db->simpleUpdate('compta_exercices', [
92 ], 'id = \''.(int)$id.'\'');
94 // Date de début du nouvel exercice : lendemain de la clôture du précédent exercice
95 $new_begin = utils
::modifyDate($end, '+1 day');
97 // Date de fin du nouvel exercice : un an moins un jour après l'ouverture
98 $new_end = utils
::modifyDate($new_begin, '+1 year -1 day');
100 // Enfin sauf s'il existe déjà des opérations après cette date, auquel cas la date de fin
101 // est fixée à la date de la dernière opération, ceci pour ne pas avoir d'opération
102 // orpheline d'exercice
103 $last = $db->simpleQuerySingle('SELECT date FROM compta_journal WHERE id_exercice = ? AND date >= ? ORDER BY date DESC LIMIT 1;', false, $id, $new_end);
104 $new_end = $last ?
: $new_end;
106 // Création du nouvel exercice
107 $new_id = $this->add([
108 'debut' => $new_begin,
110 'libelle' => 'Nouvel exercice'
113 // Ré-attribution des opérations de l'exercice à clôturer qui ne sont pas dans son
114 // intervale au nouvel exercice
115 $db->simpleExec('UPDATE compta_journal SET id_exercice = ? WHERE id_exercice = ? AND date >= ?;',
116 $new_id, $id, $new_begin);
124 * Créer les reports à nouveau issus de l'exercice $old_id dans le nouvel exercice courant
125 * @param integer $old_id ID de l'ancien exercice
126 * @param integer $new_id ID du nouvel exercice
127 * @param string $date Date Y-m-d donnée aux opérations créées
128 * @return boolean true si succès
130 public function doReports($old_id, $date)
132 $db = DB
::getInstance();
136 $this->solderResultat($old_id, $date);
138 $report_crediteur = 110;
139 $report_debiteur = 119;
141 // Récupérer chacun des comptes de bilan et leurs soldes (uniquement les classes 1 à 5)
142 $statement = $db->simpleStatement('SELECT compta_comptes.id AS compte, compta_comptes.position AS position,
143 COALESCE((SELECT SUM(montant) FROM compta_journal WHERE compte_debit = compta_comptes.id AND id_exercice = :id), 0)
144 - COALESCE((SELECT SUM(montant) FROM compta_journal WHERE compte_credit = compta_comptes.id AND id_exercice = :id), 0) AS solde
146 INNER JOIN compta_journal ON compta_comptes.id = compta_journal.compte_debit
147 OR compta_comptes.id = compta_journal.compte_credit
148 WHERE id_exercice = :id AND solde != 0 AND CAST(substr(compta_comptes.id, 1, 1) AS INTEGER) <= 5
149 GROUP BY compta_comptes.id;', ['id' => $old_id]);
152 $journal = new Compta_Journal
;
154 while ($row = $statement->fetchArray(SQLITE3_ASSOC
))
156 $solde = ($row['position'] & Compta_Comptes
::ACTIF
) ?
abs($row['solde']) : -abs($row['solde']);
157 $solde = round($solde, 2);
166 // Chaque solde de compte est reporté dans le nouvel exercice
168 'libelle' => 'Report à nouveau',
170 'montant' => abs($solde),
171 'compte_debit' => ($solde < 0 ?
NULL : $row['compte']),
172 'compte_credit' => ($solde > 0 ?
NULL : $row['compte']),
173 'remarques' => 'Report de solde créé automatiquement à la clôture de l\'exercice précédent',
177 // FIXME utiliser $diff pour équilibrer
185 * Solder les comptes de charge et de produits de l'exercice N
186 * et les inscrire au résultat de l'exercice N+1
187 * @param integer $exercice ID de l'exercice à solder
188 * @param string $date Date de début de l'exercice Y-m-d
189 * @return boolean true en cas de succès
191 public function solderResultat($exercice, $date)
193 $db = DB
::getInstance();
195 $resultat_excedent = 120;
196 $resultat_debiteur = 129;
198 $resultat = $this->getCompteResultat($exercice);
199 $resultat = $resultat['resultat'];
203 $journal = new Compta_Journal
;
205 'libelle' => 'Résultat de l\'exercice précédent',
207 'montant' => abs($resultat),
208 'compte_debit' => $resultat < 0 ?
129 : NULL,
209 'compte_credit' => $resultat > 0 ?
120 : NULL,
216 public function delete($id)
218 $db = DB
::getInstance();
220 // Ne pas supprimer un compte qui est utilisé !
221 if ($db->simpleQuerySingle('SELECT 1 FROM compta_journal WHERE id_exercice = ? LIMIT 1;', false, $id))
223 throw new UserException('Cet exercice ne peut être supprimé car des opérations comptables y sont liées.');
226 $db->simpleExec('DELETE FROM compta_exercices WHERE id = ?;', (int)$id);
231 public function get($id)
233 $db = DB
::getInstance();
234 return $db->simpleQuerySingle('SELECT *, strftime(\'%s\', debut) AS debut,
235 strftime(\'%s\', fin) AS fin FROM compta_exercices WHERE id = ?;', true, (int)$id);
238 public function getCurrent()
240 $db = DB
::getInstance();
241 return $db->querySingle('SELECT *, strftime(\'%s\', debut) AS debut, strftime(\'%s\', fin) FROM compta_exercices
242 WHERE cloture = 0 LIMIT 1;', true);
245 public function getCurrentId()
247 $db = DB
::getInstance();
248 return $db->querySingle('SELECT id FROM compta_exercices WHERE cloture = 0 LIMIT 1;');
251 public function getList()
253 $db = DB
::getInstance();
254 return $db->simpleStatementFetchAssocKey('SELECT id, *, strftime(\'%s\', debut) AS debut,
255 strftime(\'%s\', fin) AS fin,
256 (SELECT COUNT(*) FROM compta_journal WHERE id_exercice = compta_exercices.id) AS nb_operations
257 FROM compta_exercices ORDER BY fin DESC;', SQLITE3_ASSOC
);
260 protected function _checkFields(&$data)
262 if (empty($data['libelle']) ||
!trim($data['libelle']))
264 throw new UserException('Le libellé ne peut rester vide.');
267 $data['libelle'] = trim($data['libelle']);
269 if (empty($data['debut']) ||
!checkdate(substr($data['debut'], 5, 2), substr($data['debut'], 8, 2), substr($data['debut'], 0, 4)))
271 throw new UserException('Date de début vide ou invalide.');
274 if (empty($data['fin']) ||
!checkdate(substr($data['fin'], 5, 2), substr($data['fin'], 8, 2), substr($data['fin'], 0, 4)))
276 throw new UserException('Date de fin vide ou invalide.');
283 public function getJournal($exercice)
285 $db = DB
::getInstance();
286 $query = 'SELECT *, strftime(\'%s\', date) AS date FROM compta_journal
287 WHERE id_exercice = '.(int)$exercice.' ORDER BY date, id;';
288 return $db->simpleStatementFetch($query);
291 public function getGrandLivre($exercice)
293 $db = DB
::getInstance();
294 $livre = ['classes' => [], 'debit' => 0.0, 'credit' => 0.0];
296 $res = $db->prepare('SELECT compte FROM
297 (SELECT compte_debit AS compte FROM compta_journal
298 WHERE id_exercice = '.(int)$exercice.' GROUP BY compte_debit
300 SELECT compte_credit AS compte FROM compta_journal
301 WHERE id_exercice = '.(int)$exercice.' GROUP BY compte_credit)
302 ORDER BY base64(compte) COLLATE BINARY ASC;'
305 while ($row = $res->fetchArray(SQLITE3_NUM
))
309 if (is_null($compte))
312 $classe = substr($compte, 0, 1);
313 $parent = substr($compte, 0, 2);
315 if (!array_key_exists($classe, $livre['classes']))
317 $livre['classes'][$classe] = [];
320 if (!array_key_exists($parent, $livre['classes'][$classe]))
322 $livre['classes'][$classe][$parent] = [
328 $livre['classes'][$classe][$parent]['comptes'][$compte] = ['debit' => 0.0, 'credit' => 0.0, 'journal' => []];
330 $livre['classes'][$classe][$parent]['comptes'][$compte]['journal'] = $db->simpleStatementFetch(
331 'SELECT *, strftime(\'%s\', date) AS date FROM (
332 SELECT * FROM compta_journal WHERE compte_debit = :compte AND id_exercice = '.(int)$exercice.'
334 SELECT * FROM compta_journal WHERE compte_credit = :compte AND id_exercice = '.(int)$exercice.'
336 ORDER BY date, numero_piece, id;', SQLITE3_ASSOC
, ['compte' => $compte]);
338 $debit = (float) $db->simpleQuerySingle(
339 'SELECT SUM(montant) FROM compta_journal WHERE compte_debit = ? AND id_exercice = '.(int)$exercice.';',
342 $credit = (float) $db->simpleQuerySingle(
343 'SELECT SUM(montant) FROM compta_journal WHERE compte_credit = ? AND id_exercice = '.(int)$exercice.';',
346 $livre['classes'][$classe][$parent]['comptes'][$compte]['debit'] = $debit;
347 $livre['classes'][$classe][$parent]['comptes'][$compte]['credit'] = $credit;
349 $livre['classes'][$classe][$parent]['total'] +
= $debit;
350 $livre['classes'][$classe][$parent]['total'] -= $credit;
352 $livre['debit'] +
= $debit;
353 $livre['credit'] +
= $credit;
361 public function getCompteResultat($exercice)
363 $db = DB
::getInstance();
365 $charges = ['comptes' => [], 'total' => 0.0];
366 $produits = ['comptes' => [], 'total' => 0.0];
369 $res = $db->prepare('SELECT compte, SUM(debit), SUM(credit)
371 (SELECT compte_debit AS compte, SUM(montant) AS debit, 0 AS credit
372 FROM compta_journal WHERE id_exercice = '.(int)$exercice.' GROUP BY compte_debit
374 SELECT compte_credit AS compte, 0 AS debit, SUM(montant) AS credit
375 FROM compta_journal WHERE id_exercice = '.(int)$exercice.' GROUP BY compte_credit)
376 WHERE compte LIKE \'6%\' OR compte LIKE \'7%\'
378 ORDER BY base64(compte) COLLATE BINARY ASC;'
381 while ($row = $res->fetchArray(SQLITE3_NUM
))
383 list($compte, $debit, $credit) = $row;
384 $classe = substr($compte, 0, 1);
385 $parent = substr($compte, 0, 2);
389 if (!isset($charges['comptes'][$parent]))
391 $charges['comptes'][$parent] = ['comptes' => [], 'solde' => 0.0];
394 $solde = round($debit - $credit, 2);
399 $charges['comptes'][$parent]['comptes'][$compte] = $solde;
400 $charges['total'] +
= $solde;
401 $charges['comptes'][$parent]['solde'] +
= $solde;
403 elseif ($classe == 7)
405 if (!isset($produits['comptes'][$parent]))
407 $produits['comptes'][$parent] = ['comptes' => [], 'solde' => 0.0];
410 $solde = round($credit - $debit, 2);
415 $produits['comptes'][$parent]['comptes'][$compte] = $solde;
416 $produits['total'] +
= $solde;
417 $produits['comptes'][$parent]['solde'] +
= $solde;
423 $resultat = $produits['total'] - $charges['total'];
425 return ['charges' => $charges, 'produits' => $produits, 'resultat' => $resultat];
429 * Calculer le bilan comptable pour l'exercice $exercice
430 * @param integer $exercice ID de l'exercice dont il faut produire le bilan
431 * @param boolean $resultat true s'il faut calculer le résultat de l'exercice (utile pour un exercice en cours)
432 * @return array Un tableau multi-dimensionnel avec deux clés : actif et passif
434 public function getBilan($exercice)
436 $db = DB
::getInstance();
438 $include = [Compta_Comptes
::ACTIF
, Compta_Comptes
::PASSIF
,
439 Compta_Comptes
::PASSIF | Compta_Comptes
::ACTIF
];
441 $actif = ['comptes' => [], 'total' => 0.0];
442 $passif = ['comptes' => [], 'total' => 0.0];
444 $resultat = $this->getCompteResultat($exercice);
446 if ($resultat['resultat'] >= 0)
448 $passif['comptes']['12'] = [
449 'comptes' => ['120' => $resultat['resultat']],
450 'solde' => $resultat['resultat']
453 $passif['total'] = $resultat['resultat'];
457 $passif['comptes']['12'] = [
458 'comptes' => ['129' => $resultat['resultat']],
459 'solde' => $resultat['resultat']
462 $passif['total'] = $resultat['resultat'];
465 // Y'a sûrement moyen d'améliorer tout ça pour que le maximum de travail
466 // soit fait au niveau du SQL, mais pour le moment ça marche
467 $res = $db->prepare('SELECT compte, debit, credit, (SELECT position FROM compta_comptes WHERE id = compte) AS position
469 (SELECT compte_debit AS compte, SUM(montant) AS debit, NULL AS credit
470 FROM compta_journal WHERE id_exercice = '.(int)$exercice.' GROUP BY compte_debit
472 SELECT compte_credit AS compte, NULL AS debit, SUM(montant) AS credit
473 FROM compta_journal WHERE id_exercice = '.(int)$exercice.' GROUP BY compte_credit)
474 WHERE compte IN (SELECT id FROM compta_comptes WHERE position IN ('.implode(', ', $include).'))
475 ORDER BY base64(compte) COLLATE BINARY ASC;'
478 while ($row = $res->fetchArray(SQLITE3_NUM
))
480 list($compte, $debit, $credit, $position) = $row;
481 $parent = substr($compte, 0, 2);
482 $classe = $compte[0];
484 if (($position & Compta_Comptes
::ACTIF
) && ($position & Compta_Comptes
::PASSIF
))
486 $solde = $debit - $credit;
491 $position = 'passif';
495 $solde = abs($solde);
497 else if ($position & Compta_Comptes
::ACTIF
)
500 $solde = $debit - $credit;
502 else if ($position & Compta_Comptes
::PASSIF
)
504 $position = 'passif';
505 $solde = $credit - $debit;
512 if (!isset($
{$position}['comptes'][$parent]))
514 $
{$position}['comptes'][$parent] = ['comptes' => [], 'solde' => 0];
517 if (!isset($
{$position}['comptes'][$parent]['comptes'][$compte]))
519 $
{$position}['comptes'][$parent]['comptes'][$compte] = 0;
522 $solde = round($solde, 2);
523 $
{$position}['comptes'][$parent]['comptes'][$compte] +
= $solde;
524 $
{$position}['total'] +
= $solde;
525 $
{$position}['comptes'][$parent]['solde'] +
= $solde;
530 // Suppression des soldes nuls
531 foreach ($passif['comptes'] as $parent=>$p)
533 if ($p['solde'] == 0)
535 unset($passif['comptes'][$parent]);
539 foreach ($p['comptes'] as $id=>$solde)
543 unset($passif['comptes'][$parent]['comptes'][$id]);
548 foreach ($actif['comptes'] as $parent=>$p)
550 if (empty($p['solde']))
552 unset($actif['comptes'][$parent]);
556 foreach ($p['comptes'] as $id=>$solde)
560 unset($actif['comptes'][$parent]['comptes'][$id]);
565 return ['actif' => $actif, 'passif' => $passif];