diff --git a/.gitignore b/.gitignore index 705d2bf..1dbd321 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,6 @@ # Ignore user profile files /.rvmrc -.DS_Store \ No newline at end of file +.DS_Store + +/config/smtp_config.yml diff --git a/Gemfile b/Gemfile index f608d48..74482b9 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,7 @@ gem 'devise', '~> 3.0.0.rc' gem 'jbuilder' gem 'gravatar_image_tag', '~> 1.1.3' gem 'simple_form', '~> 3.0.0.beta1' +gem 'whenever' # Heroku gems diff --git a/Gemfile.lock b/Gemfile.lock index 37dfd8c..75fa960 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -34,6 +34,7 @@ GEM bootswatch-rails (0.4.0) railties (>= 3.1) builder (3.1.4) + chronic (0.10.2) coffee-rails (4.0.1) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.0) @@ -138,6 +139,9 @@ GEM json (>= 1.8.0) warden (1.2.3) rack (>= 1.0) + whenever (0.9.0) + activesupport (>= 2.3.4) + chronic (>= 0.6.3) wirble (0.1.3) PLATFORMS @@ -163,4 +167,5 @@ DEPENDENCIES simple_form (~> 3.0.0.beta1) thin (~> 1.4.1) uglifier (>= 1.0.3) + whenever wirble (~> 0.1.3) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 85478ef..eaf820c 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -22,4 +22,5 @@ //= require partials/mask //= require partials/timer //= require partials/timesheet -//= require partials/reports \ No newline at end of file +//= require partials/reports +//= require partials/users diff --git a/app/assets/javascripts/partials/users.js.coffee b/app/assets/javascripts/partials/users.js.coffee new file mode 100644 index 0000000..0d07fde --- /dev/null +++ b/app/assets/javascripts/partials/users.js.coffee @@ -0,0 +1,7 @@ +jQuery -> + if $("#user_is_admin").is ":checked" + $("#subscription_to_admin").show() + else + $("#subscription_to_admin").hide() + + $("#user_is_admin").change -> $("#subscription_to_admin").toggle() diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 38c6dae..4ee1724 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -64,8 +64,14 @@ def set_user # Never trust parameters from the scary internet, only allow the white list through. def user_params - user_attributes = [:name, :email, :password, :password_confirmation, :time_zone] - user_attributes += [:is_admin, :is_active] if current_user.is_admin + user_attributes = [:name, :email, :password, :password_confirmation, :time_zone, + :subscriber_to_user_summary_email] + if current_user.is_admin + user_attributes += [:is_admin, :is_active] + if params[:user][:is_admin].to_i == 1 + user_attributes.push(:subscriber_to_admin_summary_email) + end + end params.require(:user).permit(*user_attributes) end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb new file mode 100644 index 0000000..b31b72d --- /dev/null +++ b/app/mailers/user_mailer.rb @@ -0,0 +1,83 @@ +class UserMailer < ActionMailer::Base + include TimesheetHelper + helper_method :hours_and_minutes + + before_action :set_date + + default from: "Cuckoo <#{SMTP_CONFIG[:from_email]}>" + + def send_day_summary_to_all_subscribers + + users = User.where(subscriber_to_user_summary_email: true) + + users.each do |user| + user_day_summary_email(user).deliver + end + + admins = User.where(subscriber_to_admin_summary_email: true) + + admin_day_summary_email(admins).deliver + + end + + def send_week_summary_to_all_subscribers + + users = User.where(subscriber_to_user_summary_email: true) + + users.each do |user| + user_week_summary_email(user).deliver + end + + admins = User.where(subscriber_to_admin_summary_email: true) + + admin_week_summary_email(admins).deliver + + end + + def user_day_summary_email(user) + @user = user + subject = (@date.strftime('%b/%d')) + ' summary' + mail to: user.email, subject: subject, + content: 'html', template_name: 'user_day_summary_email' + end + + def admin_day_summary_email(recipients) + @users = User.all + subject = 'All users ' + @date.strftime('%b/%d') + ' summary' + emails = get_emails_from(recipients) + mail to: emails, subject: subject, + content: 'html', template_name: 'admin_day_summary_email' + end + + def user_week_summary_email(user) + @user = user + subject = @date.at_beginning_of_week.strftime('%b/%d') \ + + ' to ' + @date.at_end_of_week.strftime('%b/%d') + ' summary' + mail to: user.email, subject: subject, + content: 'html', template_name: 'user_week_summary_email' + end + + def admin_week_summary_email(recipients) + @users = User.all + subject = 'All users ' + @date.at_beginning_of_week.strftime('%b/%d') \ + + ' to ' + @date.at_end_of_week.strftime('%b/%d') + ' summary' + emails = get_emails_from(recipients) + mail to: emails, subject: subject, + content: 'html', template_name: 'admin_week_summary_email' + end + + private + + def set_date + @date = Time.current.to_date + end + + def get_emails_from(recipients) + if (recipients.class == User) + recipients.email + else + recipients.collect(&:email) + end + end + +end diff --git a/app/models/report.rb b/app/models/report.rb new file mode 100644 index 0000000..0e81aa7 --- /dev/null +++ b/app/models/report.rb @@ -0,0 +1,68 @@ +class Report + + def initialize(user) + @user = user + end + + def week_summary(date = Time.current.to_date) + + week_begin = date.at_beginning_of_week.at_midnight.gmtime + week_end = date.at_end_of_week.at_midnight.gmtime + entries = TimeEntry.where(user_id: @user.id) + .where('started_at >= ? AND started_at < ?', week_begin, week_end) + .includes(:project, :task) + + time_summary = {} + entries.each do |time_entry| + project = time_entry.project.name + task = time_entry.task.name + + if (time_summary[project].nil?) + time_summary[project] = {} + time_summary[project][:total] = 0 + time_summary[project][:tasks] = {} + end + + if (time_summary[project][:tasks][task].nil?) + time_summary[project][:tasks][task] = 0 + end + + if (!time_entry.total_time.nil?) + time_summary[project][:total] += time_entry.total_time + time_summary[project][:tasks][task] += time_entry.total_time + end + + end + + { + date: date, + weekdays: week_days(date), + week_hours: week_hours(date), + time_summary: time_summary + } + + end + + def day_summary(date = Time.current.to_date) + { + date: date, + weekdays: week_days(date), + week_hours: week_hours(date), + day_entries: day_entries(date) + } + end + + def week_hours(date = Time.current.to_date) + Timesheet.new(@user).week_hours(date) + end + + def week_days(date = Time.current.to_date) + (date.at_beginning_of_week..date.at_end_of_week) + end + + def day_entries(date = Time.current.to_date) + Timesheet.new(@user).day_entries(date) + end + + +end \ No newline at end of file diff --git a/app/views/user_mailer/_day_summary.html.erb b/app/views/user_mailer/_day_summary.html.erb new file mode 100644 index 0000000..101005f --- /dev/null +++ b/app/views/user_mailer/_day_summary.html.erb @@ -0,0 +1,92 @@ +<% if !summary[:day_entries].find_by(ended_at: nil).nil? %> +

You have clock(s) still Running!!!

+<% end %> + + + + <% summary[:weekdays].each do |weekday| %> + + <% end %> + + +
" > + <%= weekday.strftime('%A') %>
+ (<%= hours_and_minutes(summary[:week_hours][weekday.day]) %>) +
+ Total
+ (<%= hours_and_minutes(summary[:week_hours].values.sum) %>) +
+ +
+ +<% if summary[:day_entries].empty? %> +
<%= t :no_time_entry %>
+<% else %> + + + + + + + + + + + + <% summary[:day_entries].each do |time_entry| %> + + + + + + + + + + <% end %> + + + +
ActivityTimeDuration
+
    + + +
  • + +
    + <% if time_entry.is_billable? %> + Billable + <% end %> + <%= time_entry.project.name %> | <%= time_entry.task.name %> + <% if time_entry.description.present? %>><% end %> +
    + + <% if time_entry.description.present? %> +
    + <%= simple_format time_entry.description %> +
    + <% end %> + +
  • + +
+
+ + +
+ <%= time_entry.started_at.strftime("%H:%M") %>
+ <%= time_entry.ended_at.strftime("%H:%M") unless time_entry.ended_at.nil? %> +
+ +
+ + +
+ <% if !time_entry.ended_at.nil? %> + <%= hours_and_minutes(time_entry.ended_at - time_entry.started_at) %> + <% else %> + Running + <% end %> +
+
+<% end %> diff --git a/app/views/user_mailer/_week_summary.html.erb b/app/views/user_mailer/_week_summary.html.erb new file mode 100644 index 0000000..fa854f0 --- /dev/null +++ b/app/views/user_mailer/_week_summary.html.erb @@ -0,0 +1,65 @@ + + + + <% summary[:weekdays].each do |weekday| %> + + <% end %> + + +
+ <%= weekday.strftime('%A') %>
+ (<%= hours_and_minutes(summary[:week_hours][weekday.day]) %>) +
+ Total
+ (<%= hours_and_minutes(summary[:week_hours].values.sum) %>) +
+ +<% if summary[:time_summary].empty? %> +
+
<%= t :no_time_entry %>
+<% else %> + + + + <% summary[:time_summary].each do |project, info| %> + + + + + + + + + + + + <% end %> + + + +
+
    + +
  • + +
    + <%= project %> +
    + +
  • + +
+
+ +
+ <%= hours_and_minutes(info[:total]) %> +
+ +
+ <% info[:tasks].each do |task, duration| %> + + <%= task %> (<%= hours_and_minutes(duration) %>) +
+ <% end %> +
+<% end %> diff --git a/app/views/user_mailer/admin_day_summary_email.html.erb b/app/views/user_mailer/admin_day_summary_email.html.erb new file mode 100644 index 0000000..4cf62f1 --- /dev/null +++ b/app/views/user_mailer/admin_day_summary_email.html.erb @@ -0,0 +1,12 @@ + + + + + + + <% @users.each do |user| %> +

<%= user.name %>

+ <%= render 'day_summary', summary: Report.new(user).day_summary %> + <% end %> + + diff --git a/app/views/user_mailer/admin_week_summary_email.html.erb b/app/views/user_mailer/admin_week_summary_email.html.erb new file mode 100644 index 0000000..e5457cc --- /dev/null +++ b/app/views/user_mailer/admin_week_summary_email.html.erb @@ -0,0 +1,14 @@ + + + + + + + <% @users.each do |user| %> +
<%= user.name %>

+
+ <%= render 'week_summary', summary: Report.new(user).week_summary %> +

+ <% end %> + + diff --git a/app/views/user_mailer/user_day_summary_email.html.erb b/app/views/user_mailer/user_day_summary_email.html.erb new file mode 100644 index 0000000..2605540 --- /dev/null +++ b/app/views/user_mailer/user_day_summary_email.html.erb @@ -0,0 +1,9 @@ + + + + + + + <%= render 'day_summary', summary: Report.new(@user).day_summary %> + + diff --git a/app/views/user_mailer/user_week_summary_email.html.erb b/app/views/user_mailer/user_week_summary_email.html.erb new file mode 100644 index 0000000..4c92c17 --- /dev/null +++ b/app/views/user_mailer/user_week_summary_email.html.erb @@ -0,0 +1,9 @@ + + + + + + + <%= render 'week_summary', summary: Report.new(@user).week_summary %> + + diff --git a/app/views/users/_form.html.erb b/app/views/users/_form.html.erb index d76caf4..a4bf81c 100644 --- a/app/views/users/_form.html.erb +++ b/app/views/users/_form.html.erb @@ -14,6 +14,13 @@ <%= f.input :password %> <%= f.input :password_confirmation %> + <%= f.input :subscriber_to_user_summary_email, as: :boolean %> + + <% if current_user.is_admin %> + <%= f.input :subscriber_to_admin_summary_email, as: :boolean, + wrapper_html: { id: 'subscription_to_admin' } %> + <% end %> +
<%= f.button :submit %>
diff --git a/config/initializers/smtp_settings.rb b/config/initializers/smtp_settings.rb new file mode 100644 index 0000000..65418e3 --- /dev/null +++ b/config/initializers/smtp_settings.rb @@ -0,0 +1,14 @@ +# Be sure to restart your server when you modify this file. + +SMTP_CONFIG = YAML.load_file("#{Rails.root}/config/smtp_config.yml") + +Cuckoo::Application.config.action_mailer.delivery_method = :smtp + +Cuckoo::Application.config.action_mailer.smtp_settings = { + :tls => SMTP_CONFIG[:tls], + :address => SMTP_CONFIG[:address], + :port => SMTP_CONFIG[:port], + :authentication => SMTP_CONFIG[:authentication], + :user_name => SMTP_CONFIG[:user_name], + :password => SMTP_CONFIG[:password] + } diff --git a/config/schedule.rb b/config/schedule.rb new file mode 100644 index 0000000..7f3c2dd --- /dev/null +++ b/config/schedule.rb @@ -0,0 +1,28 @@ +# Use this file to easily define all of your cron jobs. +# +# It's helpful, but not entirely necessary to understand cron before proceeding. +# http://en.wikipedia.org/wiki/Cron + +# Example: +# +# set :output, "/path/to/my/cron_log.log" +# +# every 2.hours do +# command "/usr/bin/some_great_command" +# runner "MyModel.some_method" +# rake "some:great:rake:task" +# end +# +# every 4.days do +# runner "AnotherModel.prune_old_records" +# end + +# Learn more: http://github.com/javan/whenever + +every :day do + runner "UserMailer.send_day_summary_to_all_subscribers" +end + +every :week do + runner "UserMailer.send_week_summary_to_all_subscribers" +end diff --git a/config/smtp_config.yml.sample b/config/smtp_config.yml.sample new file mode 100644 index 0000000..5591bd1 --- /dev/null +++ b/config/smtp_config.yml.sample @@ -0,0 +1,27 @@ + +development: + tls: true + address: addresss + port: port + authentication: :plain + user_name: username + password: password + from_email: cuckoo@email.com + +test: + tls: true + address: addresss + port: port + authentication: :plain + user_name: username + password: password + from_email: cuckoo@email.com + +production: + tls: true + address: addresss + port: port + authentication: :plain + user_name: username + password: password + from_email: cuckoo@email.com diff --git a/db/migrate/20140307172800_add_subscriber_to_summary_emails_to_users.rb b/db/migrate/20140307172800_add_subscriber_to_summary_emails_to_users.rb new file mode 100644 index 0000000..5420dea --- /dev/null +++ b/db/migrate/20140307172800_add_subscriber_to_summary_emails_to_users.rb @@ -0,0 +1,9 @@ +class AddSubscriberToSummaryEmailsToUsers < ActiveRecord::Migration + def change + add_column :users, :subscriber_to_admin_summary_email, :boolean, default: false + add_column :users, :subscriber_to_user_summary_email, :boolean, default: true + + add_index :users, :subscriber_to_admin_summary_email + add_index :users, :subscriber_to_user_summary_email + end +end diff --git a/db/schema.rb b/db/schema.rb index 14fe202..7220e19 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20140204141711) do +ActiveRecord::Schema.define(version: 20140307172800) do create_table "projects", force: true do |t| t.string "name" @@ -39,18 +39,22 @@ t.boolean "is_admin" t.datetime "created_at" t.datetime "updated_at" - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" t.string "last_sign_in_ip" t.string "time_zone" - t.boolean "is_active", default: true + t.boolean "is_active", default: true + t.boolean "subscriber_to_admin_summary_email", default: false + t.boolean "subscriber_to_user_summary_email", default: true end add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree + add_index "users", ["subscriber_to_admin_summary_email"], name: "index_users_on_subscriber_to_admin_summary_email", using: :btree + add_index "users", ["subscriber_to_user_summary_email"], name: "index_users_on_subscriber_to_user_summary_email", using: :btree end diff --git a/test/mailers/user_mailer_test.rb b/test/mailers/user_mailer_test.rb new file mode 100644 index 0000000..67a1629 --- /dev/null +++ b/test/mailers/user_mailer_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class UserMailerTest < ActionMailer::TestCase + # test "the truth" do + # assert true + # end +end