Ajout du support des écritures ventilées.
[garradin.git] / include / class.compta_comptes.php
1 <?php
2
3 namespace Garradin;
4
5 class Compta_Comptes
6 {
7 const CAISSE = 530;
8
9 const PASSIF = 0x01;
10 const ACTIF = 0x02;
11 const PRODUIT = 0x04;
12 const CHARGE = 0x08;
13
14 public function importPlan($file = NULL)
15 {
16 $plan = json_decode(file_get_contents($file ? $file : ROOT.'/include/data/plan_comptable.json'), true);
17
18 $db = DB::getInstance();
19 $db->exec('BEGIN;');
20 $ids = [];
21
22 foreach ($plan as $id=>$compte)
23 {
24 $ids[] = $id;
25
26 if ($db->simpleQuerySingle('SELECT 1 FROM compta_comptes WHERE id = ?;', false, $id))
27 {
28 $db->simpleUpdate('compta_comptes', [
29 'parent' => $compte['parent'],
30 'libelle' => $compte['nom'],
31 'position' => $compte['position'],
32 'plan_comptable' => 1,
33 ], 'id = \''.$db->escapeString($id).'\'');
34 }
35 else
36 {
37 $db->simpleInsert('compta_comptes', [
38 'id' => $id,
39 'parent' => $compte['parent'],
40 'libelle' => $compte['nom'],
41 'position' => $compte['position'],
42 'plan_comptable' => 1,
43 ]);
44 }
45 }
46
47 //$db->exec('DELETE FROM compta_comptes WHERE id NOT IN(\''.implode('\', \'', $ids).'\') AND plan_comptable = 1;');
48
49 $db->exec('END;');
50
51 return true;
52 }
53
54 public function add($data)
55 {
56 $this->_checkFields($data, true);
57
58 $db = DB::getInstance();
59
60 if (empty($data['id']))
61 {
62 $new_id = $data['parent'];
63 $nb_sous_comptes = $db->simpleQuerySingle('SELECT COUNT(*) FROM compta_comptes WHERE parent = ?;', false, $new_id);
64
65 // Pas plus de 26 sous-comptes par compte, parce que l'alphabet s'arrête à 26 lettres
66 if ($nb_sous_comptes >= 26)
67 {
68 throw new UserException('Nombre de sous-comptes maximal atteint pour ce compte parent-ci.');
69 }
70
71 $new_id .= chr(65+(int)$nb_sous_comptes);
72 }
73 else
74 {
75 $new_id = $data['id'];
76 }
77
78 if (isset($data['position']))
79 {
80 $position = (int) $data['position'];
81 }
82 else
83 {
84 $position = $db->simpleQuerySingle('SELECT position FROM compta_comptes WHERE id = ?;', false, $data['parent']);
85 }
86
87 $db->simpleInsert('compta_comptes', [
88 'id' => $new_id,
89 'libelle' => trim($data['libelle']),
90 'parent' => $data['parent'],
91 'plan_comptable' => 0,
92 'position' => (int)$position,
93 ]);
94
95 return $new_id;
96 }
97
98 public function edit($id, $data)
99 {
100 $db = DB::getInstance();
101
102 // Vérification que l'on peut éditer ce compte
103 if ($db->simpleQuerySingle('SELECT plan_comptable FROM compta_comptes WHERE id = ?;', false, $id))
104 {
105 throw new UserException('Ce compte fait partie du plan comptable et n\'est pas modifiable.');
106 }
107
108 if (isset($data['position']) && empty($data['position']))
109 {
110 throw new UserException('Aucune position du compte n\'a été indiquée.');
111 }
112
113 $this->_checkFields($data);
114
115 $update = [
116 'libelle' => trim($data['libelle']),
117 ];
118
119 if (isset($data['position']))
120 {
121 $update['position'] = (int) trim($data['position']);
122 }
123
124 $db->simpleUpdate('compta_comptes', $update, 'id = \''.$db->escapeString(trim($id)).'\'');
125
126 return true;
127 }
128
129 public function delete($id)
130 {
131 $db = DB::getInstance();
132
133 // Ne pas supprimer un compte qui est utilisé !
134 if ($db->simpleQuerySingle('SELECT 1 FROM compta_flux WHERE compte = ? LIMIT 1;', false, $id))
135 {
136 throw new UserException('Ce compte ne peut être supprimé car des opérations comptables y sont liées.');
137 }
138
139 if ($db->simpleQuerySingle('SELECT 1 FROM compta_comptes_bancaires WHERE id = ? LIMIT 1;', false, $id))
140 {
141 throw new UserException('Ce compte ne peut être supprimé car il est lié à un compte bancaire.');
142 }
143
144 if ($db->simpleQuerySingle('SELECT 1 FROM compta_categories WHERE compte = ? LIMIT 1;', false, $id))
145 {
146 throw new UserException('Ce compte ne peut être supprimé car des catégories y sont liées.');
147 }
148
149 $db->simpleExec('DELETE FROM compta_comptes WHERE id = ?;', trim($id));
150
151 return true;
152 }
153
154 /**
155 * Peut-on supprimer ce compte ? (OUI s'il n'a pas d'écriture liée)
156 * @param string $id Numéro du compte
157 * @return boolean TRUE si le compte n'a pas d'écriture liée
158 */
159 public function canDelete($id)
160 {
161 $db = DB::getInstance();
162
163 if ($db->simpleQuerySingle('SELECT 1 FROM compta_flux
164 WHERE compte = ? LIMIT 1;', false, $id))
165 {
166 return false;
167 }
168
169 if ($db->simpleQuerySingle('SELECT 1 FROM compta_categories WHERE compte = ? LIMIT 1;', false, $id))
170 {
171 return false;
172 }
173
174 return true;
175 }
176
177 /**
178 * Peut-on désactiver ce compte ? (OUI s'il n'a pas d'écriture liée dans l'exercice courant)
179 * @param string $id Numéro du compte
180 * @return boolean TRUE si le compte n'a pas d'écriture liée dans l'exercice courant
181 */
182 public function canDisable($id)
183 {
184 $db = DB::getInstance();
185
186 if ($db->simpleQuerySingle('SELECT 1 FROM compta_journal
187 LEFT JOIN compta_flux ON compta_journal.id = compta_flux.id_journal
188 WHERE id_exercice = (SELECT id FROM compta_exercices WHERE cloture = 0 LIMIT 1)
189 AND compte = ? LIMIT 1;', false, $id))
190 {
191 return false;
192 }
193
194 if ($db->simpleQuerySingle('SELECT 1 FROM compta_categories WHERE compte = ? LIMIT 1;', false, $id))
195 {
196 return false;
197 }
198
199 return true;
200 }
201
202 /**
203 * Désactiver un compte
204 * Le compte ne sera plus utilisable pour les écritures ou les catégories mais restera en base de données
205 * @param string $id Numéro du compte
206 * @return boolean TRUE si la désactivation a fonctionné, une exception utilisateur si
207 * la désactivation n'est pas possible.
208 */
209 public function disable($id)
210 {
211 $db = DB::getInstance();
212
213 // Ne pas désactiver un compte utilisé dans l'exercice courant
214 if ($db->simpleQuerySingle('SELECT 1 FROM compta_journal
215 LEFT JOIN compta_flux ON compta_journal.id = compta_flux.id_journal
216 WHERE id_exercice = (SELECT id FROM compta_exercices WHERE cloture = 0 LIMIT 1)
217 AND compte = ? LIMIT 1;', false, $id))
218 {
219 throw new UserException('Ce compte ne peut être désactivé car des écritures y sont liées sur l\'exercice courant. '
220 . 'Il faut supprimer ou ré-attribuer ces écritures avant de pouvoir supprimer le compte.');
221 }
222
223 // Ne pas désactiver un compte utilisé pour une catégorie
224 if ($db->simpleQuerySingle('SELECT 1 FROM compta_categories WHERE compte = ? LIMIT 1;', false, $id))
225 {
226 throw new UserException('Ce compte ne peut être désactivé car des catégories y sont liées.');
227 }
228
229 return $db->simpleUpdate('compta_comptes', ['desactive' => 1], 'id = \''.$db->escapeString(trim($id)).'\'');
230 }
231
232 public function get($id)
233 {
234 $db = DB::getInstance();
235 return $db->simpleQuerySingle('SELECT * FROM compta_comptes WHERE id = ?;', true, trim($id));
236 }
237
238 public function getList($parent = 0)
239 {
240 $db = DB::getInstance();
241 return $db->simpleStatementFetchAssocKey('SELECT id, * FROM compta_comptes WHERE parent = ? ORDER BY id;', SQLITE3_ASSOC, $parent);
242 }
243
244 public function getListAll($parent = 0)
245 {
246 $db = DB::getInstance();
247 return $db->queryFetchAssoc('SELECT id, libelle FROM compta_comptes ORDER BY id;');
248 }
249
250 public function listTree($parent = 0, $include_children = true)
251 {
252 $db = DB::getInstance();
253
254 if ($include_children)
255 {
256 $parent = $parent ? 'WHERE parent LIKE \''.$db->escapeString($parent).'%\' ' : '';
257 }
258 else
259 {
260 $parent = $parent ? 'WHERE parent = \''.$db->escapeString($parent).'\' ' : 'WHERE parent = 0';
261 }
262
263 return $db->simpleStatementFetch('SELECT * FROM compta_comptes '.$parent.' ORDER BY id;');
264 }
265
266 protected function _checkFields(&$data, $force_parent_check = false)
267 {
268 $db = DB::getInstance();
269
270 if (empty($data['libelle']) || !trim($data['libelle']))
271 {
272 throw new UserException('Le libellé ne peut rester vide.');
273 }
274
275 $data['libelle'] = trim($data['libelle']);
276
277 if (isset($data['id']))
278 {
279 $force_parent_check = true;
280 $data['id'] = trim($data['id']);
281
282 if ($db->simpleQuerySingle('SELECT 1 FROM compta_comptes WHERE id = ?;', false, $data['id']))
283 {
284 throw new UserException('Le compte numéro '.$data['id'].' existe déjà.');
285 }
286 }
287
288 if (isset($data['parent']) || $force_parent_check)
289 {
290 if (empty($data['parent']) && !trim($data['parent']))
291 {
292 throw new UserException('Le compte ne peut pas ne pas avoir de compte parent.');
293 }
294
295 if (!($id = $db->simpleQuerySingle('SELECT id FROM compta_comptes WHERE id = ?;', false, $data['parent'])))
296 {
297 throw new UserException('Le compte parent indiqué n\'existe pas.');
298 }
299
300 $data['parent'] = trim($id);
301 }
302
303 if (isset($data['id']))
304 {
305 if (strncmp($data['id'], $data['parent'], strlen($data['parent'])) !== 0)
306 {
307 throw new UserException('Le compte '.$data['id'].' n\'est pas un sous-compte de '.$data['parent'].'.');
308 }
309 }
310
311 return true;
312 }
313
314 public function getPositions()
315 {
316 return [
317 self::ACTIF => 'Actif',
318 self::PASSIF => 'Passif',
319 self::ACTIF | self::PASSIF => 'Actif ou passif (déterminé automatiquement au bilan selon le solde du compte)',
320 self::CHARGE => 'Charge',
321 self::PRODUIT => 'Produit',
322 self::CHARGE | self::PRODUIT => 'Charge et produit',
323 ];
324 }
325 }
326
327 ?>