init
[garradin.git] / include / class.compta_journal.php
1 <?php
2
3 namespace Garradin;
4
5 class Compta_Journal
6 {
7 protected function _getCurrentExercice()
8 {
9 $db = DB::getInstance();
10 $id = $db->querySingle('SELECT id FROM compta_exercices WHERE cloture = 0 LIMIT 1;');
11
12 if (!$id)
13 {
14 throw new UserException('Aucun exercice en cours.');
15 }
16
17 return $id;
18 }
19
20 public function checkExercice()
21 {
22 return $this->_getCurrentExercice();
23 }
24
25 protected function _checkOpenExercice($id)
26 {
27 if (is_null($id))
28 return true;
29
30 $db = DB::getInstance();
31 $id = $db->simpleQuerySingle('SELECT id FROM compta_exercices
32 WHERE cloture = 0 AND id = ? LIMIT 1;', false, (int)$id);
33
34 if ($id)
35 return true;
36
37 return false;
38 }
39
40 public function getSolde($id_compte, $inclure_sous_comptes = false)
41 {
42 $db = DB::getInstance();
43 $exercice = $this->_getCurrentExercice();
44 $compte = $inclure_sous_comptes
45 ? 'LIKE \'' . $db->escapeString(trim($id_compte)) . '%\''
46 : '= \'' . $db->escapeString(trim($id_compte)) . '\'';
47
48 $debit = 'COALESCE((SELECT SUM(montant) FROM compta_journal WHERE compte_debit '.$compte.' AND id_exercice = '.(int)$exercice.'), 0)';
49 $credit = 'COALESCE((SELECT SUM(montant) FROM compta_journal WHERE compte_credit '.$compte.' AND id_exercice = '.(int)$exercice.'), 0)';
50
51 // L'actif augmente au débit, le passif au crédit
52 $position = $db->simpleQuerySingle('SELECT position FROM compta_comptes WHERE id = ?;', false, $id_compte);
53
54 if (($position & Compta_Comptes::ACTIF) || ($position & Compta_Comptes::CHARGE))
55 {
56 $query = $debit . ' - ' . $credit;
57 }
58 else
59 {
60 $query = $credit . ' - ' . $debit;
61 }
62
63 return $db->querySingle('SELECT ' . $query . ';');
64 }
65
66 public function getJournalCompte($compte, $inclure_sous_comptes = false)
67 {
68 $db = DB::getInstance();
69
70 $position = $db->simpleQuerySingle('SELECT position FROM compta_comptes WHERE id = ?;', false, $compte);
71
72 $exercice = $this->_getCurrentExercice();
73 $compte = $inclure_sous_comptes
74 ? 'LIKE \'' . $db->escapeString(trim($compte)) . '%\''
75 : '= \'' . $db->escapeString(trim($compte)) . '\'';
76
77 // L'actif et les charges augmentent au débit, le passif et les produits au crédit
78 if (($position & Compta_Comptes::ACTIF) || ($position & Compta_Comptes::CHARGE))
79 {
80 $d = '';
81 $c = '-';
82 }
83 else
84 {
85 $d = '-';
86 $c = '';
87 }
88
89 $query = 'SELECT *, strftime(\'%s\', date) AS date,
90 running_sum(CASE WHEN compte_debit '.$compte.' THEN '.$d.'montant ELSE '.$c.'montant END) AS solde
91 FROM compta_journal WHERE (compte_debit '.$compte.' OR compte_credit '.$compte.') AND id_exercice = '.(int)$exercice.'
92 ORDER BY date ASC;';
93
94 // Obligatoire pour bien taper dans l'index de la date
95 // sinon running_sum est appelé 2 fois et ça marche pas du coup
96 // FIXME mettre ça ailleurs pour que ça soit appelé moins souvent
97 $db->exec('ANALYZE compta_journal;');
98
99 $db->resetRunningSum();
100 return $db->simpleStatementFetch($query);
101 }
102
103 public function add($data)
104 {
105 $this->_checkFields($data);
106
107 $db = DB::getInstance();
108
109 $data['id_exercice'] = $this->_getCurrentExercice();
110
111 $db->simpleInsert('compta_journal', $data);
112 $id = $db->lastInsertRowId();
113
114 return $id;
115 }
116
117 public function edit($id, $data)
118 {
119 $db = DB::getInstance();
120
121 // Vérification que l'on peut éditer cette opération
122 if (!$this->_checkOpenExercice($db->simpleQuerySingle('SELECT id_exercice FROM compta_journal WHERE id = ?;', false, $id)))
123 {
124 throw new UserException('Cette opération fait partie d\'un exercice qui a été clôturé.');
125 }
126
127 $this->_checkFields($data);
128
129 $db->simpleUpdate('compta_journal', $data,
130 'id = \''.trim($id).'\'');
131
132 return true;
133 }
134
135 public function delete($id)
136 {
137 $db = DB::getInstance();
138
139 // Vérification que l'on peut éditer cette opération
140 if (!$this->_checkOpenExercice($db->simpleQuerySingle('SELECT id_exercice FROM compta_journal WHERE id = ?;', false, $id)))
141 {
142 throw new UserException('Cette opération fait partie d\'un exercice qui a été clôturé.');
143 }
144
145 $db->simpleExec('DELETE FROM membres_operations WHERE id_operation = ?;', (int)$id);
146 $db->simpleExec('DELETE FROM compta_journal WHERE id = ?;', (int)$id);
147
148 return true;
149 }
150
151 public function get($id)
152 {
153 $db = DB::getInstance();
154 return $db->simpleQuerySingle('SELECT *, strftime(\'%s\', date) AS date FROM compta_journal WHERE id = ?;', true, $id);
155 }
156
157 public function countForMember($id)
158 {
159 $db = DB::getInstance();
160 return $db->simpleQuerySingle('SELECT COUNT(*)
161 FROM compta_journal WHERE id_auteur = ?;', false, (int)$id);
162 }
163
164 public function listForMember($id, $exercice)
165 {
166 $db = DB::getInstance();
167 return $db->simpleStatementFetch('SELECT * FROM compta_journal
168 WHERE id_auteur = ? AND id_exercice = ?;', \SQLITE3_ASSOC, (int)$id, (int)$exercice);
169 }
170
171 protected function _checkFields(&$data)
172 {
173 $db = DB::getInstance();
174
175 if (empty($data['libelle']) || !trim($data['libelle']))
176 {
177 throw new UserException('Le libellé ne peut rester vide.');
178 }
179
180 $data['libelle'] = trim($data['libelle']);
181
182 if (!empty($data['moyen_paiement'])
183 && !$db->simpleQuerySingle('SELECT 1 FROM compta_moyens_paiement WHERE code = ?;', false, $data['moyen_paiement']))
184 {
185 throw new UserException('Moyen de paiement invalide.');
186 }
187
188 if (empty($data['date']) || !utils::checkDate($data['date']))
189 {
190 throw new UserException('Date vide ou invalide.');
191 }
192
193 if (!$db->simpleQuerySingle('SELECT 1 FROM compta_exercices WHERE cloture = 0
194 AND debut <= :date AND fin >= :date;', false, ['date' => $data['date']]))
195 {
196 throw new UserException('La date ne correspond pas à l\'exercice en cours.');
197 }
198
199 if (empty($data['moyen_paiement']))
200 {
201 $data['moyen_paiement'] = null;
202 $data['numero_cheque'] = null;
203 }
204 else
205 {
206 $data['moyen_paiement'] = strtoupper($data['moyen_paiement']);
207
208 if ($data['moyen_paiement'] != 'CH')
209 {
210 $data['numero_cheque'] = null;
211 }
212
213 if (!$db->simpleQuerySingle('SELECT 1 FROM compta_moyens_paiement WHERE code = ? LIMIT 1;',
214 false, $data['moyen_paiement']))
215 {
216 throw new UserException('Moyen de paiement invalide.');
217 }
218 }
219
220 $data['montant'] = str_replace(',', '.', $data['montant']);
221 $data['montant'] = (float)$data['montant'];
222
223 if ($data['montant'] <= 0)
224 {
225 throw new UserException('Le montant ne peut être égal ou inférieur à zéro.');
226 }
227
228 foreach (['remarques', 'numero_piece', 'numero_cheque'] as $champ)
229 {
230 if (empty($data[$champ]) || !trim($data[$champ]))
231 {
232 $data[$champ] = '';
233 }
234 else
235 {
236 $data[$champ] = trim($data[$champ]);
237 }
238 }
239
240 if (!array_key_exists('compte_debit', $data) ||
241 (!is_null($data['compte_debit']) &&
242 !$db->simpleQuerySingle('SELECT 1 FROM compta_comptes WHERE id = ?;', false, $data['compte_debit'])))
243 {
244 throw new UserException('Compte débité inconnu.');
245 }
246
247 if (!array_key_exists('compte_credit', $data) ||
248 (!is_null($data['compte_credit']) &&
249 !$db->simpleQuerySingle('SELECT 1 FROM compta_comptes WHERE id = ?;', false, $data['compte_credit'])))
250 {
251 throw new UserException('Compte crédité inconnu.');
252 }
253
254 $data['compte_credit'] = is_null($data['compte_credit']) ? null : strtoupper(trim($data['compte_credit']));
255 $data['compte_debit'] = is_null($data['compte_debit']) ? null : strtoupper(trim($data['compte_debit']));
256
257 if ($data['compte_credit'] == $data['compte_debit'])
258 {
259 throw new UserException('Compte crédité identique au compte débité.');
260 }
261
262 if (isset($data['id_categorie']))
263 {
264 if (!$db->simpleQuerySingle('SELECT 1 FROM compta_categories WHERE id = ?;', false, (int)$data['id_categorie']))
265 {
266 throw new UserException('Catégorie inconnue.');
267 }
268
269 $data['id_categorie'] = (int)$data['id_categorie'];
270 }
271 else
272 {
273 $data['id_categorie'] = NULL;
274 }
275
276 if (isset($data['id_auteur']))
277 {
278 $data['id_auteur'] = (int)$data['id_auteur'];
279 }
280
281 return true;
282 }
283
284 public function getListForCategory($type = null, $cat = null)
285 {
286 $db = DB::getInstance();
287 $exercice = $this->_getCurrentExercice();
288
289 $query = 'SELECT compta_journal.*, strftime(\'%s\', compta_journal.date) AS date ';
290
291 if (is_null($cat) && !is_null($type))
292 {
293 $query.= ', compta_categories.intitule AS categorie
294 FROM compta_journal LEFT JOIN compta_categories
295 ON compta_journal.id_categorie = compta_categories.id ';
296 }
297 else
298 {
299 $query.= ' FROM compta_journal ';
300 }
301
302 $query .= ' WHERE ';
303
304 if (!is_null($cat))
305 {
306 $query .= 'id_categorie = ' . (int)$cat;
307 }
308 elseif (is_null($type) && is_null($cat))
309 {
310 $query .= 'id_categorie IS NULL';
311 }
312 else
313 {
314 $query.= 'id_categorie IN (SELECT id FROM compta_categories WHERE type = '.(int)$type.')';
315 }
316
317 $query .= ' AND id_exercice = ' . (int)$exercice;
318 $query .= ' ORDER BY date;';
319
320 return $db->simpleStatementFetch($query);
321 }
322
323 public function searchSQL($query)
324 {
325 $db = DB::getInstance();
326
327 if (!preg_match('/LIMIT\s+/', $query))
328 {
329 $query = preg_replace('/;?\s*$/', '', $query);
330 $query .= ' LIMIT 100';
331 }
332
333 $st = $db->prepare($query);
334
335 if (!$st->readOnly())
336 {
337 throw new UserException('Seules les requêtes en lecture sont autorisées.');
338 }
339
340 $res = $st->execute();
341 $out = [];
342
343 while ($row = $res->fetchArray(SQLITE3_ASSOC))
344 {
345 $out[] = $row;
346 }
347
348 return $out;
349 }
350
351 public function schemaSQL()
352 {
353 $db = DB::getInstance();
354
355 $tables = [
356 'journal' => $db->querySingle('SELECT sql FROM sqlite_master WHERE type = \'table\' AND name = \'compta_journal\';'),
357 ];
358
359 return $tables;
360 }
361 }
362
363 ?>