Overview
Comment:Envoi automatisé des rappels
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 12e7f0652b25f0895ab4622585b0393775e94f64
User & Date: bohwaz on 2014-03-31 21:13:51
Other Links: manifest | tags
Context
2014-04-01
04:13
Gestion manuelle des rappels + Suivi des rappels check-in: 92b8bfe0a6 user: bohwaz tags: trunk
2014-03-31
21:13
Envoi automatisé des rappels check-in: 12e7f0652b user: bohwaz tags: trunk
16:34
Associer les opérations comptables aux cotisations membres check-in: 45c128dbf0 user: bohwaz tags: trunk
Changes

Modified src/cron.php from [7ce5319fc9] to [112e7a2074].

7
8
9
10
11
12
13









// Exécution des tâches automatiques

if ($config->get('frequence_sauvegardes') && $config->get('nombre_sauvegardes'))
{
	$s = new Sauvegarde;
	$s->auto();
}
















>
>
>
>
>
>
>
>
>
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Exécution des tâches automatiques

if ($config->get('frequence_sauvegardes') && $config->get('nombre_sauvegardes'))
{
	$s = new Sauvegarde;
	$s->auto();
}


// Exécution des rappels automatiques
$rappels = new Rappels;

if ($rappels->countAll())
{
	$rappels->sendPending();
}

Modified src/include/class.rappels.php from [257856f691] to [6331104166].

107
108
109
110
111
112
113









114
115
116
117
118
119
120
...
132
133
134
135
136
137
138
139
140
141
142
143
144
145

146

147
148
149
150
151
152
153


154
155



















156
157









158
159



160
161
162
163
164

165
166

167
168

	 * @param  integer $id Numéro du rappel
	 * @return array     Données du rappel
	 */
	public function get($id)
	{
		return DB::getInstance()->simpleQuerySingle('SELECT * FROM rappels WHERE id = ?;', true, (int)$id);
	}










	/**
	 * Liste des rappels triés par cotisation
	 * @return array Liste des rappels
	 */
	public function listByCotisation()
	{
................................................................................
	public function listForCotisation($id)
	{
		return DB::getInstance()->simpleStatementFetch('SELECT * FROM rappels 
			WHERE id_cotisation = ? ORDER BY delai, sujet;', \SQLITE3_ASSOC, (int)$id);
	}

	/**
	 * Remplacer les tags dans le contenu/sujet du mail
	 * @param  string $content Chaîne à traiter
	 * @param  array  $data    Données supplémentaires à utiliser comme tags (tableau associatif)
	 * @return string          $content dont les tags ont été remplacés par le contenu correct
	 */
	public function replaceTagsInContent($content, $data = null)
	{

		$config = Config::getInstance();

		$tags = [
			'#NOM_ASSO'		=>	$config->get('nom_asso'),
			'#ADRESSE_ASSO'	=>	$config->get('adresse_asso'),
			'#EMAIL_ASSO'	=>	$config->get('email_asso'),
			'#SITE_ASSO'	=>	$config->get('site_asso'),
			'#URL_RACINE'	=>	WWW_URL,
			'#URL_SITE'		=>	WWW_URL,


			'#URL_ADMIN'	=>	WWW_URL . 'admin/',
		];




















		if (!empty($data) && is_array($data))









		{
			foreach ($data as $key=>$value)



			{
				$key = '#' . strtoupper($key);
				$tags[$key] = $value;
			}
		}


		return strtr($content, $tags);

	}
}








>
>
>
>
>
>
>
>
>







 







|
|
<
<

|

>

>
|
|
|
|
|
<
<
>
>
|
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
<
>
>
>
>
>
>
>
>
>
|
<
>
>
>
|
<
<
<
<
>
|
<
>
|
|
>
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
...
141
142
143
144
145
146
147
148
149


150
151
152
153
154
155
156
157
158
159
160


161
162
163

164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183

184
185
186
187
188
189
190
191
192
193

194
195
196
197




198
199

200
201
202
203
	 * @param  integer $id Numéro du rappel
	 * @return array     Données du rappel
	 */
	public function get($id)
	{
		return DB::getInstance()->simpleQuerySingle('SELECT * FROM rappels WHERE id = ?;', true, (int)$id);
	}

	/**
	 * Renvoie le nombre de rappels automatiques enregistrés
	 * @return integer Nombre de rappels
	 */
	public function countAll()
	{
		return DB::getInstance()->simpleQuerySingle('SELECT COUNT(*) FROM rappels;');
	}

	/**
	 * Liste des rappels triés par cotisation
	 * @return array Liste des rappels
	 */
	public function listByCotisation()
	{
................................................................................
	public function listForCotisation($id)
	{
		return DB::getInstance()->simpleStatementFetch('SELECT * FROM rappels 
			WHERE id_cotisation = ? ORDER BY delai, sujet;', \SQLITE3_ASSOC, (int)$id);
	}

	/**
	 * Envoi des rappels automatiques par e-mail
	 * @return boolean TRUE en cas de succès


	 */
	public function sendPending()
	{
		$db = DB::getInstance();
		$config = Config::getInstance();

		// Requête compliquée qui fait tout le boulot
		// la logique est un JOIN des tables rappels, cotisations, cotisations_membres et membres
		// pour récupérer la liste des membres qui doivent recevoir une cotisation
		$query = '
		SELECT 


			*,
			/* Nombre de jours avant ou après expiration */
			(julianday(date()) - julianday(expiration)) AS nb_jours,

			/* Date de mise en œuvre du rappel */
			date(expiration, delai || \' days\') AS date_rappel
		FROM (
			SELECT m.*, r.delai, r.sujet, r.texte, r.id_cotisation,
				m.'.$config->get('champ_identite').' AS identite,
				CASE WHEN c.duree IS NOT NULL THEN date(cm.date, \'+\'||c.duree||\' days\')
				WHEN c.fin IS NOT NULL THEN c.fin ELSE 0 END AS expiration
			FROM rappels AS r
				INNER JOIN cotisations AS c ON c.id = r.id_cotisation
				INNER JOIN cotisations_membres AS cm ON cm.id_cotisation = c.id
				INNER JOIN membres AS m ON m.id = cm.id_membre
			WHERE
				/* Inutile de sélectionner les membres sans email */
				m.email IS NOT NULL AND m.email != ""
				/* Les cotisations ponctuelles ne comptent pas */
				AND (c.fin IS NOT NULL OR c.duree IS NOT NULL)
				/* Rien nest envoyé aux membres des catégories cachées, logique */
				AND m.id_categorie NOT IN (SELECT id FROM membres_categories WHERE cacher = 1)
			ORDER BY r.delai ASC
		)

		WHERE nb_jours >= delai 
			/* Pour ne pas spammer on n\'envoie pas de rappel antérieur au dernier rappel déjà effectué */
			AND id NOT IN (SELECT id_membre FROM rappels_envoyes AS re 
				WHERE id_cotisation = re.id_cotisation AND id = re.id_membre 
				AND re.date >= date(expiration, delai || \' days\')
			)
		/* Grouper par membre, pour n\'envoyer qu\'un seul rappel par membre/cotise */
		GROUP BY id, id_cotisation
		ORDER BY nb_jours DESC;';


		$st = $db->prepare($query);
		$res = $st->execute();
		$re = new Rappels_Envoyes;





		while ($row = $res->fetchArray(DB::ASSOC))
		{

			$re->sendAuto($row);
		}
	}
}

Modified src/include/class.rappels_envoyes.php from [648dfeb78f] to [8aeb0ab1be].

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
..
91
92
93
94
95
96
97

































































98
99
100
101
102
103
104
        	|| !$db->simpleQuerySingle('SELECT 1 FROM membres WHERE id = ?;', false, (int) $data['id_membre']))
        {
            throw new UserException('Membre inconnu.');
        }

		$data['id_membre'] = (int) $data['id_membre'];

		if (empty($data['media']) || !is_numeric($data['delai']) 
			|| !in_array((int)$data['media'], [self::MEDIA_EMAIL, self::MEDIA_COURRIER, self::MEDIA_TELEPHONE, self::MEDIA_AUTRE]))
		{
			throw new UserException('Média invalide.');
		}

		$data['media'] = (int) $data['media'];

................................................................................
	 * @param  integer $id Numéro du rappel
	 * @return array     Données du rappel
	 */
	public function get($id)
	{
		return DB::getInstance()->simpleQuerySingle('SELECT * FROM rappels_envoyes WHERE id = ?;', true, (int)$id);
	}


































































	/**
	 * Liste des rappels envoyés à un membre
	 * @param integer $id Numéro du membre
	 * @return array Liste des rappels
	 */
	public function listForMember($id)







|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
..
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
        	|| !$db->simpleQuerySingle('SELECT 1 FROM membres WHERE id = ?;', false, (int) $data['id_membre']))
        {
            throw new UserException('Membre inconnu.');
        }

		$data['id_membre'] = (int) $data['id_membre'];

		if (empty($data['media']) || !is_numeric($data['media']) 
			|| !in_array((int)$data['media'], [self::MEDIA_EMAIL, self::MEDIA_COURRIER, self::MEDIA_TELEPHONE, self::MEDIA_AUTRE]))
		{
			throw new UserException('Média invalide.');
		}

		$data['media'] = (int) $data['media'];

................................................................................
	 * @param  integer $id Numéro du rappel
	 * @return array     Données du rappel
	 */
	public function get($id)
	{
		return DB::getInstance()->simpleQuerySingle('SELECT * FROM rappels_envoyes WHERE id = ?;', true, (int)$id);
	}

	/**
	 * Remplacer les tags dans le contenu/sujet du mail
	 * @param  string $content Chaîne à traiter
	 * @param  array  $data    Données supplémentaires à utiliser comme tags (tableau associatif)
	 * @return string          $content dont les tags ont été remplacés par le contenu correct
	 */
	public function replaceTagsInContent($content, $data = null)
	{
		$config = Config::getInstance();
		$tags = [
			'#NOM_ASSO'		=>	$config->get('nom_asso'),
			'#ADRESSE_ASSO'	=>	$config->get('adresse_asso'),
			'#EMAIL_ASSO'	=>	$config->get('email_asso'),
			'#SITE_ASSO'	=>	$config->get('site_asso'),
			'#URL_RACINE'	=>	WWW_URL,
			'#URL_SITE'		=>	WWW_URL,
			'#URL_ADMIN'	=>	WWW_URL . 'admin/',
		];

		if (!empty($data) && is_array($data))
		{
			foreach ($data as $key=>$value)
			{
				$key = '#' . strtoupper($key);
				$tags[$key] = $value;
			}
		}

		return strtr($content, $tags);
	}

	/**
	 * Envoi de mail pour rappel automatisé
	 * @param  array $data Données du rappel automatisé
	 * @return boolean     TRUE
	 */
	public function sendAuto($data)
	{
		$replace = $data;
		$replace['date_rappel'] = utils::sqliteDateToFrench($replace['date_rappel']);
		$replace['date_expiration'] = utils::sqliteDateToFrench($replace['expiration']);
		$replace['nb_jours'] = abs($replace['nb_jours']);
		$replace['delai'] = abs($replace['delai']);

		$subject = $this->replaceTagsInContent($data['sujet'], $replace);
		$text = $this->replaceTagsInContent($data['texte'], $replace);

		// Envoi du mail
		utils::mail($data['email'], $subject, $text);

		// Enregistrement en DB
		$this->add([
			'id_cotisation'	=>	$data['id_cotisation'],
			'id_membre'		=>	$data['id'],
			'media'			=>	Rappels_Envoyes::MEDIA_EMAIL,
			// On enregistre la date de mise en œuvre du rappel
			// et non pas la date d'envoi effective du rappel
			// car l'envoi du rappel peut ne pas être effectué
			// le jour où il aurait dû être envoyé (la magie des cron)
			'date'			=>	$data['date_rappel'],
		]);

		return true;
	}

	/**
	 * Liste des rappels envoyés à un membre
	 * @param integer $id Numéro du membre
	 * @return array Liste des rappels
	 */
	public function listForMember($id)

Modified src/include/data/0.6.0.sql from [188660d97f] to [99e36ee7c6].

48
49
50
51
52
53
54
55

56
57
58
59

60


61
62
63
64
65
66
67
    sujet TEXT NOT NULL,
    texte TEXT NOT NULL
);

CREATE TABLE rappels_envoyes
-- Enregistrement des rappels envoyés à qui et quand
(
    id INTEGER NOT NULL PRIMARY KEY,


    id_membre INTEGER NOT NULL REFERENCES membres (id),
    id_rappel INTEGER NULL REFERENCES rappels (id),
    date TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,

    media INTEGER NOT NULL -- Média utilisé pour le rappel : 1 = email, 2 = courrier, 3 = autre


);

CREATE TABLE plugins
-- Plugins / extensions
(
    id TEXT PRIMARY KEY,
    officiel INTEGER NOT NULL DEFAULT 0,







|
>

<
<

>
|
>
>







48
49
50
51
52
53
54
55
56
57


58
59
60
61
62
63
64
65
66
67
68
69
    sujet TEXT NOT NULL,
    texte TEXT NOT NULL
);

CREATE TABLE rappels_envoyes
-- Enregistrement des rappels envoyés à qui et quand
(
    id_membre INTEGER NOT NULL REFERENCES membres (id),
    id_cotisation INTEGER NOT NULL REFERENCES cotisations (id),



    date TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,

    media INTEGER NOT NULL, -- Média utilisé pour le rappel : 1 = email, 2 = courrier, 3 = autre
    
    PRIMARY KEY(id_membre, id_cotisation, date)
);

CREATE TABLE plugins
-- Plugins / extensions
(
    id TEXT PRIMARY KEY,
    officiel INTEGER NOT NULL DEFAULT 0,

Modified src/include/data/schema.sql from [f901a8e68f] to [f478267547].

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
..
81
82
83
84
85
86
87
88

89

90
91
92
93
94
95
96
97
98
99

CREATE TABLE membres_operations
-- Liaision des enregistrement des paiements en compta
(
    id_membre INTEGER NOT NULL REFERENCES membres (id),
    id_operation INTEGER NOT NULL REFERENCES compta_journal (id),
    id_cotisation INTEGER NULL REFERENCES cotisations_membres (id),
    
    PRIMARY KEY (id_membre, id_operation)
);

CREATE TABLE rappels
-- Rappels de devoir renouveller une cotisation
(
    id INTEGER PRIMARY KEY,
................................................................................
    texte TEXT NOT NULL
);

CREATE TABLE rappels_envoyes
-- Enregistrement des rappels envoyés à qui et quand
(
    id_membre INTEGER NOT NULL REFERENCES membres (id),
    id_rappel INTEGER NOT NULL REFERENCES rappels (id),

    date TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,

    media INTEGER NOT NULL, -- Média utilisé pour le rappel : 1 = email, 2 = courrier, 3 = autre
    
    PRIMARY KEY(id_membre, id_rappel, date)
);

--
-- WIKI
--

CREATE TABLE wiki_pages







|







 







|
>

>


|







61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
..
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

CREATE TABLE membres_operations
-- Liaision des enregistrement des paiements en compta
(
    id_membre INTEGER NOT NULL REFERENCES membres (id),
    id_operation INTEGER NOT NULL REFERENCES compta_journal (id),
    id_cotisation INTEGER NULL REFERENCES cotisations_membres (id),

    PRIMARY KEY (id_membre, id_operation)
);

CREATE TABLE rappels
-- Rappels de devoir renouveller une cotisation
(
    id INTEGER PRIMARY KEY,
................................................................................
    texte TEXT NOT NULL
);

CREATE TABLE rappels_envoyes
-- Enregistrement des rappels envoyés à qui et quand
(
    id_membre INTEGER NOT NULL REFERENCES membres (id),
    id_cotisation INTEGER NOT NULL REFERENCES cotisations (id),

    date TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,

    media INTEGER NOT NULL, -- Média utilisé pour le rappel : 1 = email, 2 = courrier, 3 = autre
    
    PRIMARY KEY(id_membre, id_cotisation, date)
);

--
-- WIKI
--

CREATE TABLE wiki_pages

Modified src/include/init.php from [9f7a1dec4b] to [489f54a69f].

193
194
195
196
197
198
199



200
201
202
203
204
205
206
    $error .= "\n-------------\n";
    $error .= 'Garradin version: ' . garradin_version() . "\n";
    $error .= 'Garradin manifest: ' . garradin_manifest() . "\n";
    $error .= 'PHP version: ' . phpversion() . "\n";

    foreach ($_SERVER as $key=>$value)
    {



        $error .= $key . ': ' . $value . "\n";
    }
    
    $error = str_replace("\r", '', $error);
    
    if (PHP_SAPI == 'cli')
    {







>
>
>







193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
    $error .= "\n-------------\n";
    $error .= 'Garradin version: ' . garradin_version() . "\n";
    $error .= 'Garradin manifest: ' . garradin_manifest() . "\n";
    $error .= 'PHP version: ' . phpversion() . "\n";

    foreach ($_SERVER as $key=>$value)
    {
        if (is_array($value))
            $value = json_encode($value);

        $error .= $key . ': ' . $value . "\n";
    }
    
    $error = str_replace("\r", '', $error);
    
    if (PHP_SAPI == 'cli')
    {

Modified src/include/lib.template.php from [173c0a167e] to [29cd731071].

548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
        return implode(', ', $out);
    } else {
        return $v;
    }

});

$tpl->register_modifier('format_sqlite_date_to_french', function ($d, $short = false) {
    if (strlen($d) == 10 || $short)
        return \DateTime::createFromFormat('Y-m-d', substr($d, 0, 10))->format('d/m/Y');
    elseif (strlen($d) == 16)
        return \DateTime::createFromFormat('Y-m-d H:i', $d)->format('d/m/Y H:i');
    else
        return \DateTime::createFromFormat('Y-m-d H:i:s', $d)->format('d/m/Y H:i');
});

$tpl->register_modifier('format_bytes', function ($size) {
    if ($size > (1024 * 1024))
        return round($size / 1024 / 1024, 2) . ' Mo';
    elseif ($size > 1024)
        return round($size / 1024, 2) . ' Ko';
    else







|
<
<
<
<
<
<
<







548
549
550
551
552
553
554
555







556
557
558
559
560
561
562
        return implode(', ', $out);
    } else {
        return $v;
    }

});

$tpl->register_modifier('format_sqlite_date_to_french', ['Garradin\utils', 'sqliteDateToFrench']);








$tpl->register_modifier('format_bytes', function ($size) {
    if ($size > (1024 * 1024))
        return round($size / 1024 / 1024, 2) . ' Mo';
    elseif ($size > 1024)
        return round($size / 1024, 2) . ' Ko';
    else

Modified src/include/lib.utils.php from [ebb0aada90] to [bf7e62d419].

37
38
39
40
41
42
43










44
45
46
47
48
49
50
        }

        $date = date($format, $ts);
        $date = strtr($date, self::$french_date_names);
        $date = strtolower($date);
        return $date;
    }











    static public function makeTimestampFromForm($d)
    {
        return mktime($d['h'], $d['min'], 0, $d['m'], $d['d'], $d['y']);
    }

    static public function modifyDate($str, $change)







>
>
>
>
>
>
>
>
>
>







37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
        }

        $date = date($format, $ts);
        $date = strtr($date, self::$french_date_names);
        $date = strtolower($date);
        return $date;
    }

    static public function sqliteDateToFrench($d, $short = false)
    {
        if (strlen($d) == 10 || $short)
            return \DateTime::createFromFormat('Y-m-d', substr($d, 0, 10))->format('d/m/Y');
        elseif (strlen($d) == 16)
            return \DateTime::createFromFormat('Y-m-d H:i', $d)->format('d/m/Y H:i');
        else
            return \DateTime::createFromFormat('Y-m-d H:i:s', $d)->format('d/m/Y H:i');
    }

    static public function makeTimestampFromForm($d)
    {
        return mktime($d['h'], $d['min'], 0, $d['m'], $d['d'], $d['y']);
    }

    static public function modifyDate($str, $change)

Modified src/www/admin/membres/cotisations/gestion/rappels.php from [1c721a0634] to [c06898a5af].

49
50
51
52
53
54
55
56
57
58
59
60

$tpl->assign('liste', $rappels->listByCotisation());
$tpl->assign('cotisations', $cotisations->listCurrent());

$tpl->assign('default_subject', '[#NOM_ASSO] Échéance de cotisation');
$tpl->assign('default_text', "Bonjour #IDENTITE,\n\nVotre cotisation arrive à échéance dans #NB_JOURS jours.\n\n"
	.	"Merci de nous contacter pour renouveler votre cotisation.\n\nCordialement.\n\n"
    .   "--\n#NOM_ASSO\nAdresse : #ADRESSE_ASSO\nE-Mail : #EMAIL_ASSO\nSite web : #SITE_ASSO");

$tpl->display('admin/membres/cotisations/gestion/rappels.tpl');

?>







|




49
50
51
52
53
54
55
56
57
58
59
60

$tpl->assign('liste', $rappels->listByCotisation());
$tpl->assign('cotisations', $cotisations->listCurrent());

$tpl->assign('default_subject', '[#NOM_ASSO] Échéance de cotisation');
$tpl->assign('default_text', "Bonjour #IDENTITE,\n\nVotre cotisation arrive à échéance dans #NB_JOURS jours.\n\n"
	.	"Merci de nous contacter pour renouveler votre cotisation.\n\nCordialement.\n\n"
    .   "--\n#NOM_ASSO\n#ADRESSE_ASSO\nE-Mail : #EMAIL_ASSO\nSite web : #SITE_ASSO");

$tpl->display('admin/membres/cotisations/gestion/rappels.tpl');

?>