From 0c8105277070c20590d22a0d4b8fc1f09ce981d9 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Sun, 25 May 2008 17:31:50 +0000 Subject: [PATCH] Adds a rake task to send reminders. An email is sent to each user with a list of the issues due in the next days, if any. See @rake -D redmine:send_reminders@ for options. git-svn-id: http://redmine.rubyforge.org/svn/trunk@1459 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/models/mailer.rb | 31 +++++++++++++++++ app/views/mailer/reminder.text.html.rhtml | 9 +++++ app/views/mailer/reminder.text.plain.rhtml | 7 ++++ lang/bg.yml | 2 ++ lang/cs.yml | 2 ++ lang/da.yml | 2 ++ lang/de.yml | 2 ++ lang/en.yml | 2 ++ lang/es.yml | 2 ++ lang/fi.yml | 2 ++ lang/fr.yml | 2 ++ lang/he.yml | 2 ++ lang/hu.yml | 2 ++ lang/it.yml | 2 ++ lang/ja.yml | 2 ++ lang/ko.yml | 2 ++ lang/lt.yml | 2 ++ lang/nl.yml | 2 ++ lang/no.yml | 2 ++ lang/pl.yml | 2 ++ lang/pt-br.yml | 2 ++ lang/pt.yml | 2 ++ lang/ro.yml | 2 ++ lang/ru.yml | 2 ++ lang/sr.yml | 2 ++ lang/sv.yml | 2 ++ lang/th.yml | 2 ++ lang/uk.yml | 2 ++ lang/zh-tw.yml | 2 ++ lang/zh.yml | 4 ++- lib/tasks/reminder.rake | 39 ++++++++++++++++++++++ test/fixtures/issues.yml | 2 +- test/unit/mailer_test.rb | 9 +++++ 33 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 app/views/mailer/reminder.text.html.rhtml create mode 100644 app/views/mailer/reminder.text.plain.rhtml create mode 100644 lib/tasks/reminder.rake diff --git a/app/models/mailer.rb b/app/models/mailer.rb index 6fc879a15..aae374268 100644 --- a/app/models/mailer.rb +++ b/app/models/mailer.rb @@ -51,6 +51,15 @@ class Mailer < ActionMailer::Base :issue_url => url_for(:controller => 'issues', :action => 'show', :id => issue) end + def reminder(user, issues, days) + set_language_if_valid user.language + recipients user.mail + subject l(:mail_subject_reminder, issues.size) + body :issues => issues, + :days => days, + :issues_url => url_for(:controller => 'issues', :action => 'index', :set_filter => 1, :assigned_to_id => user.id, :sort_key => 'issues.due_date', :sort_order => 'asc') + end + def document_added(document) redmine_headers 'Project' => document.project.identifier recipients document.project.recipients @@ -144,6 +153,28 @@ class Mailer < ActionMailer::Base (bcc.nil? || bcc.empty?) super end + + # Sends reminders to issue assignees + # Available options: + # * :days => how many days in the future to remind about (defaults to 7) + # * :tracker => id of tracker for filtering issues (defaults to all trackers) + # * :project => id or identifier of project to process (defaults to all projects) + def self.reminders(options={}) + days = options[:days] || 7 + project = options[:project] ? Project.find(options[:project]) : nil + tracker = options[:tracker] ? Tracker.find(options[:tracker]) : nil + + s = ARCondition.new ["#{IssueStatus.table_name}.is_closed = ? AND #{Issue.table_name}.due_date <= ? AND #{Issue.table_name}.assigned_to_id IS NOT NULL", false, days.day.from_now.to_date] + s << "#{Issue.table_name}.project_id = #{project.id}" if project + s << "#{Issue.table_name}.tracker_id = #{tracker.id}" if tracker + + issues_by_assignee = Issue.find(:all, :include => [:status, :assigned_to, :project, :tracker], + :conditions => s.conditions + ).group_by(&:assigned_to) + issues_by_assignee.each do |assignee, issues| + deliver_reminder(assignee, issues, days) unless assignee.nil? + end + end private def initialize_defaults(method_name) diff --git a/app/views/mailer/reminder.text.html.rhtml b/app/views/mailer/reminder.text.html.rhtml new file mode 100644 index 000000000..1e33fbe43 --- /dev/null +++ b/app/views/mailer/reminder.text.html.rhtml @@ -0,0 +1,9 @@ +

<%= l(:mail_body_reminder, @issues.size, @days) %>

+ + + +

<%= link_to l(:label_issue_view_all), @issues_url %>

diff --git a/app/views/mailer/reminder.text.plain.rhtml b/app/views/mailer/reminder.text.plain.rhtml new file mode 100644 index 000000000..7e6a2e585 --- /dev/null +++ b/app/views/mailer/reminder.text.plain.rhtml @@ -0,0 +1,7 @@ +<%= l(:mail_body_reminder, @issues.size, @days) %>: + +<% @issues.each do |issue| -%> +* <%= "#{issue.project} - #{issue.tracker} ##{issue.id}: #{issue.subject}" %> +<% end -%> + +<%= @issues_url %> diff --git a/lang/bg.yml b/lang/bg.yml index 224177d94..10198bce9 100644 --- a/lang/bg.yml +++ b/lang/bg.yml @@ -619,3 +619,5 @@ error_scm_annotate: "Обектът не съществува или не мож label_planning: Планиране text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/cs.yml b/lang/cs.yml index 2126553fc..f90068b3b 100644 --- a/lang/cs.yml +++ b/lang/cs.yml @@ -624,3 +624,5 @@ error_scm_annotate: "Položka neexistuje nebo nemůže být komentována." label_planning: Plánování text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/da.yml b/lang/da.yml index 3b5ca07d3..ca4ddeb46 100644 --- a/lang/da.yml +++ b/lang/da.yml @@ -621,3 +621,5 @@ error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planlægning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/de.yml b/lang/de.yml index 273bad277..fa33e1c74 100644 --- a/lang/de.yml +++ b/lang/de.yml @@ -620,3 +620,5 @@ enumeration_doc_categories: Dokumentenkategorien enumeration_activities: Aktivitäten (Zeiterfassung) text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/en.yml b/lang/en.yml index 892a63a1b..f9e07d211 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -91,6 +91,8 @@ mail_body_account_information_external: You can use your "%s" account to log in. mail_body_account_information: Your account information mail_subject_account_activation_request: %s account activation request mail_body_account_activation_request: 'A new user (%s) has registered. His account is pending your approval:' +mail_subject_reminder: "%d issue(s) due in the next days" +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" gui_validation_error: 1 error gui_validation_error_plural: %d errors diff --git a/lang/es.yml b/lang/es.yml index 26009d716..3d8c0a931 100644 --- a/lang/es.yml +++ b/lang/es.yml @@ -622,3 +622,5 @@ error_scm_annotate: "No existe la entrada o no ha podido ser anotada" label_planning: Planificación text_subprojects_destroy_warning: 'Sus subprojectos: %s también se eliminarán' label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/fi.yml b/lang/fi.yml index 908ef1283..60428bb49 100644 --- a/lang/fi.yml +++ b/lang/fi.yml @@ -619,3 +619,5 @@ error_scm_annotate: "Merkintää ei ole tai siihen ei voi lisätä selityksiä." label_planning: Suunnittelu text_subprojects_destroy_warning: 'Tämän alaprojekti(t): %s tullaan myös poistamaan.' label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/fr.yml b/lang/fr.yml index 8827edd5e..8b002bbf6 100644 --- a/lang/fr.yml +++ b/lang/fr.yml @@ -91,6 +91,8 @@ mail_body_account_information_external: Vous pouvez utiliser votre compte "%s" p mail_body_account_information: Paramètres de connexion de votre compte mail_subject_account_activation_request: "Demande d'activation d'un compte %s" mail_body_account_activation_request: "Un nouvel utilisateur (%s) s'est inscrit. Son compte nécessite votre approbation:" +mail_subject_reminder: "%d demande(s) arrivent à échéance" +mail_body_reminder: "%d demande(s) qui vous sont assignées arrivent à échéance dans les %d prochains jours:" gui_validation_error: 1 erreur gui_validation_error_plural: %d erreurs diff --git a/lang/he.yml b/lang/he.yml index 71bc5e5b3..f972ef62a 100644 --- a/lang/he.yml +++ b/lang/he.yml @@ -619,3 +619,5 @@ error_scm_annotate: "הכניסה לא קיימת או שלא ניתן לתאר label_planning: תכנון text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/hu.yml b/lang/hu.yml index c3feaf16d..f6d312ce0 100644 --- a/lang/hu.yml +++ b/lang/hu.yml @@ -620,3 +620,5 @@ default_activity_development: Fejlesztés enumeration_issue_priorities: Feladat prioritások enumeration_doc_categories: Dokumentum kategóriák enumeration_activities: Tevékenységek (idő rögzítés) +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/it.yml b/lang/it.yml index 1771e1bda..a8646644a 100644 --- a/lang/it.yml +++ b/lang/it.yml @@ -619,3 +619,5 @@ error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/ja.yml b/lang/ja.yml index 84f27a2eb..d119cf55e 100644 --- a/lang/ja.yml +++ b/lang/ja.yml @@ -620,3 +620,5 @@ error_scm_annotate: "エントリが存在しない、もしくはアノテー label_planning: 計画 text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/ko.yml b/lang/ko.yml index ee94c4025..98dde9e06 100644 --- a/lang/ko.yml +++ b/lang/ko.yml @@ -619,3 +619,5 @@ error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/lt.yml b/lang/lt.yml index bc2faaa00..6b791d87b 100644 --- a/lang/lt.yml +++ b/lang/lt.yml @@ -621,3 +621,5 @@ label_planning: Planavimas text_subprojects_destroy_warning: 'Šis(ie) subprojektas(ai): %s taip pat bus ištrintas(i).' label_and_its_subprojects: %s projektas ir jo subprojektai +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/nl.yml b/lang/nl.yml index 01755137a..c36eaecd5 100644 --- a/lang/nl.yml +++ b/lang/nl.yml @@ -620,3 +620,5 @@ error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/no.yml b/lang/no.yml index 13fcd24f5..5e788dc50 100644 --- a/lang/no.yml +++ b/lang/no.yml @@ -620,3 +620,5 @@ default_activity_development: Utvikling enumeration_issue_priorities: Sakssprioriteringer enumeration_doc_categories: Dokument-kategorier enumeration_activities: Aktiviteter (tidssporing) +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/pl.yml b/lang/pl.yml index c83173ca2..4476c2160 100644 --- a/lang/pl.yml +++ b/lang/pl.yml @@ -619,3 +619,5 @@ error_scm_annotate: "Wpis nie istnieje lub nie można do niego dodawać adnotacj label_planning: Planning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/pt-br.yml b/lang/pt-br.yml index 9714df2ca..9b3d23151 100644 --- a/lang/pt-br.yml +++ b/lang/pt-br.yml @@ -619,3 +619,5 @@ label_planning: Planejamento text_subprojects_destroy_warning: 'Seu(s) subprojeto(s): %s também serão excluídos.' label_age: Age label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/pt.yml b/lang/pt.yml index 9d0586775..c56b76a0b 100644 --- a/lang/pt.yml +++ b/lang/pt.yml @@ -619,3 +619,5 @@ error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/ro.yml b/lang/ro.yml index 01127f5c5..30464569b 100644 --- a/lang/ro.yml +++ b/lang/ro.yml @@ -619,3 +619,5 @@ error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/ru.yml b/lang/ru.yml index 4f0a4bb7b..f4fd92037 100644 --- a/lang/ru.yml +++ b/lang/ru.yml @@ -623,3 +623,5 @@ error_scm_annotate: "Данные отсутствуют или не могут label_planning: Планирование text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/sr.yml b/lang/sr.yml index 263bd1187..53e4f9f2c 100644 --- a/lang/sr.yml +++ b/lang/sr.yml @@ -620,3 +620,5 @@ error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/sv.yml b/lang/sv.yml index 5b0c57acf..248f3dcba 100644 --- a/lang/sv.yml +++ b/lang/sv.yml @@ -620,3 +620,5 @@ error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/th.yml b/lang/th.yml index 643ee01c6..444316aa1 100644 --- a/lang/th.yml +++ b/lang/th.yml @@ -622,3 +622,5 @@ enumeration_issue_priorities: ความสำคัญของปัญห enumeration_doc_categories: ประเภทเอกสาร enumeration_activities: กิจกรรม (ใช้ในการติดตามเวลา) label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/uk.yml b/lang/uk.yml index e61fc5b38..3cc60ef48 100644 --- a/lang/uk.yml +++ b/lang/uk.yml @@ -621,3 +621,5 @@ error_scm_annotate: "The entry does not exist or can not be annotated." label_planning: Planning text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.' label_and_its_subprojects: %s and its subprojects +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/zh-tw.yml b/lang/zh-tw.yml index 1bdc7d715..3926113b2 100644 --- a/lang/zh-tw.yml +++ b/lang/zh-tw.yml @@ -620,3 +620,5 @@ default_activity_development: 開發 enumeration_issue_priorities: 項目優先權 enumeration_doc_categories: 文件分類 enumeration_activities: 活動 (時間追蹤) +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" diff --git a/lang/zh.yml b/lang/zh.yml index ac5eb456d..ffd7cd811 100644 --- a/lang/zh.yml +++ b/lang/zh.yml @@ -619,4 +619,6 @@ default_activity_development: 开发 enumeration_issue_priorities: 问题优先级 enumeration_doc_categories: 文档类别 -enumeration_activities: 活动(时间跟踪) \ No newline at end of file +enumeration_activities: 活动(时间跟踪)mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" +mail_subject_reminder: "%d issue(s) due in the next days" +mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:" diff --git a/lib/tasks/reminder.rake b/lib/tasks/reminder.rake new file mode 100644 index 000000000..73844fb79 --- /dev/null +++ b/lib/tasks/reminder.rake @@ -0,0 +1,39 @@ +# redMine - project management software +# Copyright (C) 2008 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +desc <<-END_DESC +Send reminders about issues due in the next days. + +Available options: + * days => number of days to remind about (defaults to 7) + * tracker => id of tracker (defaults to all trackers) + * project => id or identifier of project (defaults to all projects) + +Example: + rake redmine:send_reminders days=7 RAILS_ENV="production" +END_DESC + +namespace :redmine do + task :send_reminders => :environment do + options = {} + options[:days] = ENV['days'].to_i if ENV['days'] + options[:project] = ENV['project'] if ENV['project'] + options[:tracker] = ENV['tracker'].to_i if ENV['tracker'] + + Mailer.reminders(options) + end +end diff --git a/test/fixtures/issues.yml b/test/fixtures/issues.yml index c037624ae..9d3287c6f 100644 --- a/test/fixtures/issues.yml +++ b/test/fixtures/issues.yml @@ -42,7 +42,7 @@ issues_003: category_id: description: Error 281 is encountered when saving a recipe tracker_id: 1 - assigned_to_id: + assigned_to_id: 3 author_id: 2 status_id: 1 start_date: <%= 1.day.from_now.to_date.to_s(:db) %> diff --git a/test/unit/mailer_test.rb b/test/unit/mailer_test.rb index 64648b94c..8cf6c1423 100644 --- a/test/unit/mailer_test.rb +++ b/test/unit/mailer_test.rb @@ -116,4 +116,13 @@ class MailerTest < Test::Unit::TestCase assert Mailer.deliver_register(token) end end + + def test_reminders + ActionMailer::Base.deliveries.clear + Mailer.reminders(:days => 42) + assert_equal 1, ActionMailer::Base.deliveries.size + mail = ActionMailer::Base.deliveries.last + assert mail.bcc.include?('dlopper@somenet.foo') + assert mail.body.include?('Bug #3: Error 281 when updating a recipe') + end end