Ajout du support des écritures ventilées.
[garradin.git] / include / class.rappels.php
1 <?php
2
3 namespace Garradin;
4
5 class Rappels
6 {
7 /**
8 * Vérification des champs fournis pour la modification de donnée
9 * @param array $data Tableau contenant les champs à ajouter/modifier
10 * @return void
11 */
12 protected function _checkFields(&$data)
13 {
14 $db = DB::getInstance();
15
16 if (empty($data['id_cotisation'])
17 || !$db->simpleQuerySingle('SELECT 1 FROM cotisations WHERE id = ?;', false, (int) $data['id_cotisation']))
18 {
19 throw new UserException('Cotisation inconnue.');
20 }
21
22 $data['id_cotisation'] = (int) $data['id_cotisation'];
23
24 if ((trim($data['delai']) === '') || !is_numeric($data['delai']))
25 {
26 throw new UserException('Délai avant rappel invalide : doit être indiqué en nombre de jours.');
27 }
28
29 $data['delai'] = (int) $data['delai'];
30
31 if (!isset($data['sujet']) || trim($data['sujet']) === '')
32 {
33 throw new UserException('Le sujet du rappel ne peut être vide.');
34 }
35
36 $data['sujet'] = trim($data['sujet']);
37
38 if (!isset($data['texte']) || trim($data['texte']) === '')
39 {
40 throw new UserException('Le contenu du rappel ne peut être vide.');
41 }
42
43 $data['texte'] = trim($data['texte']);
44 }
45
46 /**
47 * Ajouter un rappel
48 * @param array $data Données du rappel
49 * @return integer Numéro ID du rappel créé
50 */
51 public function add($data)
52 {
53 $db = DB::getInstance();
54
55 $this->_checkFields($data);
56
57 $db->simpleInsert('rappels', $data);
58
59 return $db->lastInsertRowId();
60 }
61
62 /**
63 * Modifier un rappel automatique
64 * @param integer $id Numéro du rappel
65 * @param array $data Données du rappel
66 * @return boolean TRUE si tout s'est bien passé
67 * @throws UserException En cas d'erreur dans une donnée à modifier
68 */
69 public function edit($id, $data)
70 {
71 $db = DB::getInstance();
72
73 $this->_checkFields($data);
74
75 return $db->simpleUpdate('rappels', $data, 'id = ' . (int)$id);
76 }
77
78 /**
79 * Supprimer un rappel automatique
80 * @param integer $id Numéro du rappel
81 * @param boolean $delete_history Effacer aussi l'historique des rappels envoyés
82 * @return boolean TRUE en cas de succès
83 */
84 public function delete($id, $delete_history = false)
85 {
86 $db = DB::getInstance();
87
88 $db->exec('BEGIN;');
89
90 if ($delete_history)
91 {
92 $db->simpleExec('DELETE FROM rappels_envoyes WHERE id_rappel = ?;', (int) $id);
93 }
94 else
95 {
96 $db->simpleExec('UPDATE rappels_envoyes SET id_rappel = NULL WHERE id_rappel = ?;', (int) $id);
97 }
98
99 $db->simpleExec('DELETE FROM rappels WHERE id = ?;', (int) $id);
100 $db->exec('END;');
101
102 return true;
103 }
104
105 /**
106 * Renvoie les données sur un rappel
107 * @param integer $id Numéro du rappel
108 * @return array Données du rappel
109 */
110 public function get($id)
111 {
112 return DB::getInstance()->simpleQuerySingle('SELECT * FROM rappels WHERE id = ?;', true, (int)$id);
113 }
114
115 /**
116 * Renvoie le nombre de rappels automatiques enregistrés
117 * @return integer Nombre de rappels
118 */
119 public function countAll()
120 {
121 return DB::getInstance()->simpleQuerySingle('SELECT COUNT(*) FROM rappels;');
122 }
123
124 /**
125 * Liste des rappels triés par cotisation
126 * @return array Liste des rappels
127 */
128 public function listByCotisation()
129 {
130 return DB::getInstance()->simpleStatementFetch('SELECT r.*,
131 c.intitule, c.montant, c.duree, c.debut, c.fin
132 FROM rappels AS r
133 INNER JOIN cotisations AS c ON c.id = r.id_cotisation
134 ORDER BY r.id_cotisation, r.delai, r.sujet;');
135 }
136
137 /**
138 * Liste des rappels pour une cotisation donnée
139 * @param integer $id Numéro du rappel
140 * @return array Liste des rappels
141 */
142 public function listForCotisation($id)
143 {
144 return DB::getInstance()->simpleStatementFetch('SELECT * FROM rappels
145 WHERE id_cotisation = ? ORDER BY delai, sujet;', \SQLITE3_ASSOC, (int)$id);
146 }
147
148 /**
149 * Envoi des rappels automatiques par e-mail
150 * @return boolean TRUE en cas de succès
151 */
152 public function sendPending()
153 {
154 $db = DB::getInstance();
155 $config = Config::getInstance();
156
157 // Requête compliquée qui fait tout le boulot
158 // la logique est un JOIN des tables rappels, cotisations, cotisations_membres et membres
159 // pour récupérer la liste des membres qui doivent recevoir une cotisation
160 $query = '
161 SELECT
162 *,
163 /* Nombre de jours avant ou après expiration */
164 (julianday(date()) - julianday(expiration)) AS nb_jours,
165 /* Date de mise en œuvre du rappel */
166 date(expiration, delai || \' days\') AS date_rappel
167 FROM (
168 SELECT m.*, r.delai, r.sujet, r.texte, r.id_cotisation,
169 m.'.$config->get('champ_identite').' AS identite,
170 CASE WHEN c.duree IS NOT NULL THEN date(cm.date, \'+\'||c.duree||\' days\')
171 WHEN c.fin IS NOT NULL THEN c.fin ELSE 0 END AS expiration
172 FROM rappels AS r
173 INNER JOIN cotisations AS c ON c.id = r.id_cotisation
174 INNER JOIN cotisations_membres AS cm ON cm.id_cotisation = c.id
175 INNER JOIN membres AS m ON m.id = cm.id_membre
176 WHERE
177 /* Inutile de sélectionner les membres sans email */
178 m.email IS NOT NULL AND m.email != \'\'
179 /* Les cotisations ponctuelles ne comptent pas */
180 AND (c.fin IS NOT NULL OR c.duree IS NOT NULL)
181 /* Rien nest envoyé aux membres des catégories cachées, logique */
182 AND m.id_categorie NOT IN (SELECT id FROM membres_categories WHERE cacher = 1)
183 ORDER BY r.delai ASC
184 )
185 WHERE nb_jours >= delai
186 /* Pour ne pas spammer on n\'envoie pas de rappel antérieur au dernier rappel déjà effectué */
187 AND id NOT IN (SELECT id_membre FROM rappels_envoyes AS re
188 WHERE id_cotisation = re.id_cotisation AND id = re.id_membre
189 AND re.date >= date(expiration, delai || \' days\')
190 )
191 /* Grouper par membre, pour n\'envoyer qu\'un seul rappel par membre/cotise */
192 GROUP BY id, id_cotisation
193 ORDER BY nb_jours DESC;';
194
195 $db->exec('BEGIN');
196 $st = $db->prepare($query);
197 $res = $st->execute();
198 $re = new Rappels_Envoyes;
199
200 while ($row = $res->fetchArray(DB::ASSOC))
201 {
202 $re->sendAuto($row);
203 }
204
205 $db->exec('END;');
206 return true;
207 }
208 }