Ajout : ./garradin
[garradin.git] / include / class.cotisations_membres.php
1 <?php
2
3 namespace Garradin;
4
5 class Cotisations_Membres
6 {
7 const ITEMS_PER_PAGE = 100;
8
9 /**
10 * Vérification des champs fournis pour la modification de donnée
11 * @param array $data Tableau contenant les champs à ajouter/modifier
12 * @return void
13 */
14 protected function _checkFields(&$data, $compta = false)
15 {
16 $db = DB::getInstance();
17
18 if (empty($data['date']) || !utils::checkDate($data['date']))
19 {
20 throw new UserException('Date vide ou invalide.');
21 }
22
23 if (empty($data['id_cotisation'])
24 || !$db->simpleQuerySingle('SELECT 1 FROM cotisations WHERE id = ?;', false, (int) $data['id_cotisation']))
25 {
26 throw new UserException('Cotisation inconnue.');
27 }
28
29 $data['id_cotisation'] = (int) $data['id_cotisation'];
30
31 if (empty($data['id_membre'])
32 || !$db->simpleQuerySingle('SELECT 1 FROM membres WHERE id = ?;', false, (int) $data['id_membre']))
33 {
34 throw new UserException('Membre inconnu ou invalide.');
35 }
36
37 $data['id_membre'] = (int) $data['id_membre'];
38
39 if ($compta)
40 {
41 if (!isset($data['moyen_paiement']) || trim($data['moyen_paiement']) === '')
42 {
43 throw new UserException('Moyen de paiement inconnu ou invalide.');
44 }
45
46 if ($data['moyen_paiement'] != 'ES')
47 {
48 if (trim($data['banque']) == '')
49 {
50 throw new UserException('Le compte bancaire choisi est invalide.');
51 }
52
53 if (!$db->simpleQuerySingle('SELECT 1 FROM compta_comptes_bancaires WHERE id = ?;',
54 false, $data['banque']))
55 {
56 throw new UserException('Le compte bancaire choisi n\'existe pas.');
57 }
58 }
59
60 if (empty($data['montant']) || !is_numeric($data['montant']))
61 {
62 throw new UserException('Le montant indiqué n\'est pas un nombre valide.');
63 }
64 }
65 }
66
67 /**
68 * Enregistrer un événement de cotisation
69 * @param array $data Tableau des champs à insérer
70 * @return integer ID de l'événement créé
71 */
72 public function add($data)
73 {
74 $db = DB::getInstance();
75
76 $co = $db->simpleQuerySingle('SELECT * FROM cotisations WHERE id = ?;',
77 true, (int)$data['id_cotisation']);
78
79 $this->_checkFields($data, !empty($co['id_categorie_compta']));
80
81 $check = $db->simpleQuerySingle('SELECT 1 FROM cotisations_membres
82 WHERE id_cotisation = ? AND id_membre = ? AND date = ?;',
83 false, (int)$data['id_cotisation'], (int)$data['id_membre'], $data['date']);
84
85 if ($check)
86 {
87 throw new UserException('Cette cotisation a déjà été enregistrée pour ce jour-ci et ce membre-ci.');
88 }
89
90 $db->begin();
91
92 $db->simpleInsert('cotisations_membres', [
93 'date' => $data['date'],
94 'id_cotisation' => $data['id_cotisation'],
95 'id_membre' => $data['id_membre'],
96 ]);
97
98 $id = $db->lastInsertRowId();
99
100 if ($co['id_categorie_compta'] && $co['montant'] > 0)
101 {
102 try {
103 $id_operation = $this->addOperationCompta($id, [
104 'id_categorie' => $co['id_categorie_compta'],
105 'libelle' => 'Cotisation (automatique)',
106 'montant' => $data['montant'],
107 'date' => $data['date'],
108 'moyen_paiement'=> $data['moyen_paiement'],
109 'numero_cheque' => isset($data['numero_cheque']) ? $data['numero_cheque'] : null,
110 'id_auteur' => $data['id_auteur'],
111 'banque' => isset($data['banque']) ? $data['banque'] : null,
112 'id_membre' => $data['id_membre'],
113 ]);
114 }
115 catch (\Exception $e)
116 {
117 $db->rollback();
118 throw $e;
119 }
120 }
121
122 $db->commit();
123
124 return $id;
125 }
126
127 /**
128 * Supprimer un événement de cotisation
129 * @param integer $id ID de l'événement à supprimer
130 * @return integer true en cas de succès
131 */
132 public function delete($id)
133 {
134 $db = DB::getInstance();
135 $db->simpleExec('DELETE FROM membres_operations WHERE id_cotisation = ?;', (int)$id);
136 return $db->simpleExec('DELETE FROM cotisations_membres WHERE id = ?;', (int) $id);
137 }
138
139 public function get($id)
140 {
141 $db = DB::getInstance();
142 return $db->simpleQuerySingle('SELECT * FROM cotisations_membres WHERE id = ?;', true, (int)$id);
143 }
144
145 /**
146 * Renvoie une liste des écritures comptables liées à une cotisation
147 * @param int $id Numéro de la cotisation membre
148 * @return array Liste des écritures
149 */
150 public function listOperationsCompta($id)
151 {
152 $db = DB::getInstance();
153 return $db->simpleStatementFetch('SELECT * FROM compta_journal
154 WHERE id IN (SELECT id_operation FROM membres_operations
155 WHERE id_cotisation = ?);', \SQLITE3_ASSOC, (int)$id);
156 }
157
158 /**
159 * Ajouter une écriture comptable pour un paiemement membre
160 * @param int $id Numéro de la cotisation membre
161 * @param array $data Données
162 */
163 public function addOperationCompta($id, $data)
164 {
165 $journal = new Compta_Journal;
166 $db = DB::getInstance();
167
168 if (!isset($data['libelle']) || trim($data['libelle']) == '')
169 {
170 throw new UserException('Le libellé ne peut rester vide.');
171 }
172
173 $data['libelle'] = trim($data['libelle']);
174
175 if (!isset($data['montant']) || !is_numeric($data['montant']) || (float)$data['montant'] < 0)
176 {
177 throw new UserException('Le montant doit être un nombre positif et valide.');
178 }
179
180 $data['montant'] = (float) $data['montant'];
181
182 if ($data['moyen_paiement'] != 'ES')
183 {
184 $debit = $data['banque'];
185 }
186 else
187 {
188 $debit = Compta_Comptes::CAISSE;
189 }
190
191 $credit = $db->simpleQuerySingle('SELECT compte FROM compta_categories WHERE id = ?;',
192 false, $data['id_categorie']);
193
194 $id_operation = $journal->add([
195 'libelle' => $data['libelle'],
196 'date' => $data['date'],
197 'fluxs' =>
198 [ ['compte'=>$credit, 'montant' => - $data['montant'] ]
199 , ['compte'=>$debit, 'montant' => $data['montant'] ] ],
200 'moyen_paiement'=> $data['moyen_paiement'],
201 'numero_cheque' => isset($data['numero_cheque']) ? $data['numero_cheque'] : null,
202 'id_categorie' => (int)$data['id_categorie'],
203 'id_auteur' => (int)$data['id_auteur'],
204 ]);
205
206 $db->simpleInsert('membres_operations', [
207 'id_operation' => $id_operation,
208 'id_membre' => $data['id_membre'],
209 'id_cotisation' => (int)$id,
210 ]);
211
212 return $id_operation;
213 }
214
215 /**
216 * Nombre de membres pour une cotisation
217 * @param integer $id Numéro de la cotisation
218 * @return integer Nombre d'événements pour cette cotisation
219 */
220 public function countMembersForCotisation($id)
221 {
222 $db = DB::getInstance();
223 return $db->simpleQuerySingle('SELECT COUNT(DISTINCT id_membre) FROM cotisations_membres
224 WHERE id_cotisation = ?;',
225 false, (int)$id);
226 }
227
228 /**
229 * Liste des membres qui sont inscrits à une cotisation
230 * @param integer $id Numéro de la cotisation
231 * @return array Liste des membres
232 */
233 public function listMembersForCotisation($id, $page = 1, $order = null, $desc = true)
234 {
235 $begin = ($page - 1) * self::ITEMS_PER_PAGE;
236
237 $db = DB::getInstance();
238 $champ_id = Config::getInstance()->get('champ_identite');
239
240 if (empty($order))
241 $order = 'date';
242
243 switch ($order)
244 {
245 case 'date':
246 case 'a_jour':
247 break;
248 case 'identite':
249 $order = 'transliterate_to_ascii('.$champ_id.') COLLATE NOCASE';
250 break;
251 default:
252 $order = 'cm.id_membre';
253 break;
254 }
255
256 $desc = $desc ? 'DESC' : 'ASC';
257
258 return $db->simpleStatementFetch('SELECT cm.id_membre, cm.date, cm.id,
259 (SELECT '.$champ_id.' FROM membres WHERE id = cm.id_membre) AS nom, c.montant,
260 CASE WHEN c.duree IS NOT NULL THEN date(cm.date, \'+\'||c.duree||\' days\') >= date()
261 WHEN c.fin IS NOT NULL THEN c.fin >= date() ELSE 1 END AS a_jour
262 FROM cotisations_membres AS cm
263 INNER JOIN cotisations AS c ON c.id = cm.id_cotisation
264 WHERE
265 cm.id_cotisation = ?
266 GROUP BY cm.id_membre ORDER BY '.$order.' '.$desc.' LIMIT ?,?;',
267 \SQLITE3_ASSOC, (int)$id, $begin, self::ITEMS_PER_PAGE);
268 }
269
270 /**
271 * Liste des événements d'un membre
272 * @param integer $id Numéro de membre
273 * @return array Liste des événements de cotisation fait par ce membre
274 */
275 public function listForMember($id)
276 {
277 $db = DB::getInstance();
278 return $db->simpleStatementFetch('SELECT cm.*, c.intitule, c.duree, c.debut, c.fin, c.montant,
279 (SELECT COUNT(*) FROM membres_operations WHERE id_cotisation = cm.id) AS nb_operations
280 FROM cotisations_membres AS cm
281 LEFT JOIN cotisations AS c ON c.id = cm.id_cotisation
282 WHERE cm.id_membre = ? ORDER BY cm.date DESC;', \SQLITE3_ASSOC, (int)$id);
283 }
284
285 /**
286 * Liste des cotisations / activités en cours pour ce membre
287 * @param integer $id Numéro de membre
288 * @return array Liste des cotisations en cours de validité
289 */
290 public function listSubscriptionsForMember($id)
291 {
292 $db = DB::getInstance();
293 return $db->simpleStatementFetch('SELECT c.*,
294 CASE WHEN c.duree IS NOT NULL THEN date(cm.date, \'+\'||c.duree||\' days\') >= date()
295 WHEN c.fin IS NOT NULL THEN c.fin >= date()
296 WHEN cm.id IS NOT NULL THEN 1 ELSE 0 END AS a_jour,
297 CASE WHEN c.duree IS NOT NULL THEN date(cm.date, \'+\'||c.duree||\' days\')
298 WHEN c.fin IS NOT NULL THEN c.fin ELSE 1 END AS expiration,
299 (julianday(date()) - julianday(CASE WHEN c.duree IS NOT NULL THEN date(cm.date, \'+\'||c.duree||\' days\')
300 WHEN c.fin IS NOT NULL THEN c.fin END)) AS nb_jours
301 FROM cotisations_membres AS cm
302 INNER JOIN cotisations AS c ON c.id = cm.id_cotisation
303 WHERE cm.id_membre = ?
304 GROUP BY cm.id_cotisation
305 ORDER BY cm.date DESC;', \SQLITE3_ASSOC, (int)$id);
306 }
307
308 /**
309 * Ce membre est-il à jour sur cette cotisation ?
310 * @param integer $id Numéro de membre
311 * @param integer $id_cotisation Numéro de cotisation
312 * @return array Infos sur la cotisation, et champ expiration
313 * (si NULL = cotisation jamais enregistrée, si 1 = cotisation ponctuelle enregistrée, sinon date d'expiration)
314 */
315 public function isMemberUpToDate($id, $id_cotisation)
316 {
317 $db = DB::getInstance();
318 return $db->simpleQuerySingle('SELECT c.*,
319 CASE WHEN c.duree IS NOT NULL THEN date(cm.date, \'+\'||c.duree||\' days\') >= date()
320 WHEN c.fin IS NOT NULL THEN c.fin >= date()
321 WHEN cm.id IS NOT NULL THEN 1 ELSE 0 END AS a_jour,
322 CASE WHEN c.duree IS NOT NULL THEN date(cm.date, \'+\'||c.duree||\' days\')
323 WHEN c.fin IS NOT NULL THEN c.fin ELSE 1 END AS expiration
324 FROM cotisations AS c
325 LEFT JOIN cotisations_membres AS cm ON cm.id_cotisation = c.id AND cm.id_membre = ?
326 WHERE c.id = ? ORDER BY cm.date DESC;',
327 true, (int)$id, (int)$id_cotisation);
328 }
329
330 public function countForMember($id)
331 {
332 $db = DB::getInstance();
333 return $db->simpleQuerySingle('SELECT COUNT(DISTINCT id_cotisation) FROM cotisations_membres
334 WHERE id_membre = ?;', false, (int)$id);
335 }
336 }