├── app ├── models │ ├── user_leave_report.rb │ ├── log_time_reminder_mailer.rb │ ├── user_leave.rb │ ├── leave_mailer.rb │ └── user_time_check.rb ├── views │ ├── user_time_checks │ │ ├── checkout_timelog_success.html.erb │ │ ├── check_in.html.erb │ │ ├── _check_out_info.html.erb │ │ ├── _show_logged_time_entries.html.erb │ │ ├── user_time_reporting_weekly.erb │ │ ├── user_time_reporting_monthly.erb │ │ ├── user_time_reporting.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── _time_check_grid_filter.erb │ │ ├── _time_report_grid_filter.erb │ │ ├── _time_check_grid.html.erb │ │ ├── _time_report_grid_filter_weekly.erb │ │ ├── _time_report_grid_filter_monthly.erb │ │ ├── check_out.html.erb │ │ ├── _time_report_grid.html.erb │ │ ├── _time_report_grid_monthly.html.erb │ │ ├── _availible_open_issues.html.erb │ │ ├── _time_report_grid_weekly.erb │ │ ├── user_time_activity_report.erb │ │ └── user_time_activity_report_monthly.erb │ ├── user_leave_reports │ │ ├── index.html.erb │ │ ├── report.html.erb │ │ ├── _options.html.erb │ │ └── _grid.html.erb │ ├── layouts │ │ ├── redmine_leaves.html.erb │ │ ├── user_time_analytics.erb │ │ └── user_time_reports.html.erb │ ├── log_time_reminder_mailer │ │ ├── reminder_email.text.erb │ │ └── reminder_email.html.erb │ ├── leave_mailer │ │ ├── notify_absentee.txt.erb │ │ ├── notify_absentee.html.erb │ │ ├── missing_time_log.html.erb │ │ ├── group_timesheet.html.erb │ │ └── project_timesheet.html.erb │ ├── user_leaves │ │ ├── destroy.js.erb │ │ ├── edit.html.erb │ │ ├── _form.html.erb │ │ └── new.html.erb │ ├── common │ │ └── _calendar.html.erb │ ├── user_leave_analytics │ │ └── report.html.erb │ └── settings │ │ └── _settings.html.erb ├── helpers │ ├── user_time_checks_helper.rb │ ├── user_leave_analytics_helper.rb │ ├── user_leaves_helper.rb │ └── user_leave_reports_helper.rb └── controllers │ ├── user_leave_reports_controller.rb │ ├── user_leaves_controller.rb │ ├── user_leave_analytics_controller.rb │ └── user_time_checks_controller.rb ├── spec ├── user_leave_reports_controller.rb └── features │ └── user_time_checks_spec.rb ├── assets ├── images │ └── icons │ │ └── grid │ │ ├── table.png │ │ ├── delete.png │ │ ├── expand.gif │ │ ├── arrow_down.gif │ │ ├── arrow_up.gif │ │ ├── collapse.gif │ │ ├── table-old.png │ │ ├── tick_all.png │ │ ├── untick_all.png │ │ ├── page_white_find.png │ │ ├── table_refresh.png │ │ ├── page_white_excel.png │ │ ├── table_refresh-old.png │ │ └── calendar_view_month.png ├── stylesheets │ ├── redmine_leaves.css │ └── wice_grid.css └── javascripts │ └── jquery_ujs.js ├── test ├── test_helper.rb ├── unit │ ├── user_leaves_test.rb │ └── user_time_check_test.rb └── functional │ ├── user_leaves_controller_test.rb │ ├── user_time_checks_controller.rb │ ├── user_leave_reports_controller_test.rb │ └── user_leave_analytics_controller_test.rb ├── db └── migrate │ ├── 003_add_comments_to_user_leave.rb │ ├── 004_add_fraction_column_to_leaves.rb │ ├── 005_add_comments_to_user_time_checks.rb │ ├── 20140930054907_add_time_spent_to_user_time_check.rb │ ├── 002_create_user_leaves.rb │ ├── 001_create_user_time_checks.rb │ └── 20180307054907_add_timestamps.rb ├── .gitignore ├── config ├── locales │ ├── de.yml │ └── en.yml ├── schedule.rb └── routes.rb ├── Gemfile ├── lib ├── tasks │ ├── reminder_email.rake │ ├── populate_time_spent.rake │ └── auto_leave.rake ├── redmine_leaves │ └── patches │ │ ├── user_patch.rb │ │ ├── time_restriction_patch.rb │ │ └── leaves_in_calendar_patch.rb └── wice_grid_config.rb ├── LICENSE ├── init.rb ├── README.md └── Gemfile.lock /app/models/user_leave_report.rb: -------------------------------------------------------------------------------- 1 | class UserLeaveReport 2 | end 3 | -------------------------------------------------------------------------------- /spec/user_leave_reports_controller.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "testing" do 4 | 5 | end -------------------------------------------------------------------------------- /assets/images/icons/grid/table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkhitech/redmine_leaves/HEAD/assets/images/icons/grid/table.png -------------------------------------------------------------------------------- /assets/images/icons/grid/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkhitech/redmine_leaves/HEAD/assets/images/icons/grid/delete.png -------------------------------------------------------------------------------- /assets/images/icons/grid/expand.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkhitech/redmine_leaves/HEAD/assets/images/icons/grid/expand.gif -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # Load the Redmine helper 2 | require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper') 3 | -------------------------------------------------------------------------------- /assets/images/icons/grid/arrow_down.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkhitech/redmine_leaves/HEAD/assets/images/icons/grid/arrow_down.gif -------------------------------------------------------------------------------- /assets/images/icons/grid/arrow_up.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkhitech/redmine_leaves/HEAD/assets/images/icons/grid/arrow_up.gif -------------------------------------------------------------------------------- /assets/images/icons/grid/collapse.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkhitech/redmine_leaves/HEAD/assets/images/icons/grid/collapse.gif -------------------------------------------------------------------------------- /assets/images/icons/grid/table-old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkhitech/redmine_leaves/HEAD/assets/images/icons/grid/table-old.png -------------------------------------------------------------------------------- /assets/images/icons/grid/tick_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkhitech/redmine_leaves/HEAD/assets/images/icons/grid/tick_all.png -------------------------------------------------------------------------------- /assets/images/icons/grid/untick_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkhitech/redmine_leaves/HEAD/assets/images/icons/grid/untick_all.png -------------------------------------------------------------------------------- /assets/images/icons/grid/page_white_find.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkhitech/redmine_leaves/HEAD/assets/images/icons/grid/page_white_find.png -------------------------------------------------------------------------------- /assets/images/icons/grid/table_refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkhitech/redmine_leaves/HEAD/assets/images/icons/grid/table_refresh.png -------------------------------------------------------------------------------- /assets/images/icons/grid/page_white_excel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkhitech/redmine_leaves/HEAD/assets/images/icons/grid/page_white_excel.png -------------------------------------------------------------------------------- /assets/images/icons/grid/table_refresh-old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkhitech/redmine_leaves/HEAD/assets/images/icons/grid/table_refresh-old.png -------------------------------------------------------------------------------- /assets/images/icons/grid/calendar_view_month.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arkhitech/redmine_leaves/HEAD/assets/images/icons/grid/calendar_view_month.png -------------------------------------------------------------------------------- /app/views/user_time_checks/checkout_timelog_success.html.erb: -------------------------------------------------------------------------------- 1 |

<%= t(:label_time_logged_success)%>

2 |

<%= t(:label_check_out)%>

3 | <%= render 'check_out_info' %> 4 | -------------------------------------------------------------------------------- /app/views/user_leave_reports/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= render template: "layouts/redmine_leaves" %> 2 |
3 |

<%= t(:title_leave_summary) %>

4 | <%= render 'options' %> 5 |
-------------------------------------------------------------------------------- /db/migrate/003_add_comments_to_user_leave.rb: -------------------------------------------------------------------------------- 1 | class AddCommentsToUserLeave < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :user_leaves, :comments, :string 4 | end 5 | end -------------------------------------------------------------------------------- /db/migrate/004_add_fraction_column_to_leaves.rb: -------------------------------------------------------------------------------- 1 | class AddFractionColumnToLeaves < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :user_leaves, :fractional_leave, :float 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/005_add_comments_to_user_time_checks.rb: -------------------------------------------------------------------------------- 1 | class AddCommentsToUserTimeChecks < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :user_time_checks, :comments, :string 4 | end 5 | end -------------------------------------------------------------------------------- /db/migrate/20140930054907_add_time_spent_to_user_time_check.rb: -------------------------------------------------------------------------------- 1 | class AddTimeSpentToUserTimeCheck < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :user_time_checks, :time_spent, :int 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/layouts/redmine_leaves.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :header_tags do %> 2 | <%= stylesheet_link_tag 'redmine_leaves', plugin: 'redmine_leaves' %> 3 | <% end %> 4 |
5 | <%= render_menu(:leave_report_menu) %> 6 |
-------------------------------------------------------------------------------- /test/unit/user_leaves_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class UserleavesTest < ActiveSupport::TestCase 4 | 5 | # Replace this with your real tests. 6 | def test_truth 7 | assert true 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/log_time_reminder_mailer/reminder_email.text.erb: -------------------------------------------------------------------------------- 1 | <%= t(:text_email_remainder_time) %> <%= @issue.id %> 2 | =============================================== 3 | 4 | <%= t(:text_email_issue_inform) %> <%= @issue.id %> 5 | <%= t(:text_email_log_request) %> 6 | -------------------------------------------------------------------------------- /app/views/layouts/user_time_analytics.erb: -------------------------------------------------------------------------------- 1 | <% content_for :header_tags do %> 2 | <%= stylesheet_link_tag 'redmine_leaves', :plugin => 'redmine_leaves' %> 3 | <% end %> 4 |
5 | <%= render_menu(:user_time_analytics_menu) %> 6 |
-------------------------------------------------------------------------------- /app/views/layouts/user_time_reports.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :header_tags do %> 2 | <%= stylesheet_link_tag 'redmine_leaves', :plugin => 'redmine_leaves' %> 3 | <% end %> 4 |
5 | <%= render_menu(:user_time_report_menu) %> 6 |
-------------------------------------------------------------------------------- /test/unit/user_time_check_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class UserTimeCheckTest < ActiveSupport::TestCase 4 | 5 | # Replace this with your real tests. 6 | def test_truth 7 | assert true 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.rbc 2 | *.sassc 3 | .sass-cache 4 | capybara-*.html 5 | .rspec 6 | /.bundle 7 | /vendor/bundle 8 | /log/* 9 | /tmp/* 10 | /db/*.sqlite3 11 | /public/system/* 12 | /coverage/ 13 | /spec/tmp/* 14 | **.orig 15 | rerun.txt 16 | pickle-email-*.html 17 | *~ 18 | -------------------------------------------------------------------------------- /test/functional/user_leaves_controller_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class UserLeavesControllerTest < ActionController::TestCase 4 | # Replace this with your real tests. 5 | def test_truth 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/functional/user_time_checks_controller.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class UserTimeChecksControllerTest < ActionController::TestCase 4 | # Replace this with your real tests. 5 | def test_truth 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/functional/user_leave_reports_controller_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class UserLeaveReportsControllerTest < ActionController::TestCase 4 | # Replace this with your real tests. 5 | def test_truth 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/002_create_user_leaves.rb: -------------------------------------------------------------------------------- 1 | class CreateUserLeaves < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :user_leaves do |t| 4 | t.integer :user_id, indexed: true 5 | t.string :leave_type 6 | t.date :leave_date 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/functional/user_leave_analytics_controller_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../test_helper', __FILE__) 2 | 3 | class RedmineLeaveAnalyticsControllerTest < ActionController::TestCase 4 | # Replace this with your real tests. 5 | def test_truth 6 | assert true 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /config/locales/de.yml: -------------------------------------------------------------------------------- 1 | de: 2 | title_leave_summary: "Lassen Zusammenfassung" 3 | # link_add_leave: "Add Leave" 4 | # label_leave_types: "Leave Types" 5 | # label_button_apply: "Apply" 6 | # options_group_by_title: "Leave Type" 7 | # option_group_by_user: "User" 8 | # option_group_by_date: "Date" -------------------------------------------------------------------------------- /db/migrate/001_create_user_time_checks.rb: -------------------------------------------------------------------------------- 1 | class CreateUserTimeChecks < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :user_time_checks do |t| 4 | t.references :user, indexed: true 5 | t.datetime :check_in_time 6 | t.datetime :check_out_time 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | group :test, :development do 4 | gem 'factory_girl_rails' 5 | gem 'guard-rspec' 6 | # gem 'spork-rails' 7 | gem 'guard-spork' 8 | gem 'childprocess' 9 | 10 | end 11 | 12 | gem "whenever", ">=0.8.4" 13 | gem 'business_time' 14 | gem 'lazy_high_charts' 15 | -------------------------------------------------------------------------------- /app/views/leave_mailer/notify_absentee.txt.erb: -------------------------------------------------------------------------------- 1 | <%= @leave.user %> 2 | 3 | <%= I18n.t("text_notify_your_leave_for", leave_date: @leave.leave_date, 4 | leave_type: @leave.leave_type, comments: @leave.comments, 5 | fraction: @leave.fractional_leave, total_yearly_leaves: @total_yearly_leaves, user_name: @leave.user.name).html_safe %> 6 | -------------------------------------------------------------------------------- /app/views/leave_mailer/notify_absentee.html.erb: -------------------------------------------------------------------------------- 1 | <%= @leave.user %>

2 | 3 | <%= I18n.t("text_notify_your_leave_for_html", leave_date: @leave.leave_date, 4 | leave_type: @leave.leave_type, comments: @leave.comments, 5 | fraction: @leave.fractional_leave, total_yearly_leaves: @total_yearly_leaves, user_name: @leave.user.name).html_safe %> 6 | 7 | -------------------------------------------------------------------------------- /lib/tasks/reminder_email.rake: -------------------------------------------------------------------------------- 1 | namespace :redmine_leaves do 2 | desc "Sends email to user who have not logged their time and are not marked as off" 3 | task :email_reminder_for_time_log, [:arg1] => :environment do |t, args| 4 | UserTimeCheck.email_reminder_for_issue_time_log 5 | end 6 | 7 | task report_time_logging_activity: :environment do 8 | UserTimeCheck.report_activity 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20180307054907_add_timestamps.rb: -------------------------------------------------------------------------------- 1 | class AddTimestamps < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :user_time_checks, :created_at, :datetime, null: false 4 | add_column :user_time_checks, :updated_at, :datetime, null: false 5 | 6 | add_column :user_leaves, :created_at, :datetime, null: false 7 | add_column :user_leaves, :updated_at, :datetime, null: false 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/leave_mailer/missing_time_log.html.erb: -------------------------------------------------------------------------------- 1 | <%= @user %>

2 | 3 | <%if @logged_hours > 0%> 4 | <%= I18n.t('body_time_log_hours_missing_html', logged_hours: @logged_hours, report_date: @report_date)%> 5 | <%else%> 6 | <%= I18n.t('body_time_log_hours_not_logged_html', report_date: @report_date)%> 7 | <%end%> 8 | <%= I18n.t('body_affirm_missing_time_log_html')%> 9 |

10 | <%= I18n.t('footer_missing_time_log')%> 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /config/schedule.rb: -------------------------------------------------------------------------------- 1 | every :weekday, :at => '12:01 pm' do 2 | rake "redmine_leaves:auto_leave_mark" 3 | end 4 | 5 | every :weekday, :at => '7:00 am' do 6 | rake "redmine_leaves:email_reminder_for_time_log[daily]" 7 | end 8 | 9 | every :monday, :at => '7:00 am' do 10 | rake "redmine_leaves:email_reminder_for_time_log[weekly]" 11 | end 12 | 13 | every 1.month, :at=> 'start of the month at 7am' do 14 | rake "redmine_leaves:email_reminder_for_time_log[monthly]" 15 | end 16 | -------------------------------------------------------------------------------- /lib/redmine_leaves/patches/user_patch.rb: -------------------------------------------------------------------------------- 1 | module RedmineLeaves 2 | module Patches 3 | module UserPatch 4 | def self.included(base) 5 | 6 | base.class_eval do 7 | unloadable 8 | has_many :user_leaves, :foreign_key => 'user_id', :class_name => "UserLeave" 9 | has_many :user_time_checks, :foreign_key => 'user_id', :class_name => "UserTimeCheck" 10 | has_many :time_entries 11 | end 12 | end 13 | 14 | end 15 | end 16 | end -------------------------------------------------------------------------------- /app/views/user_leaves/destroy.js.erb: -------------------------------------------------------------------------------- 1 | var row_id = "table#user-leave-report tr#table-row-"+<%= @user_leave.id %>; 2 | $(row_id).fadeOut(); 3 | //*$("#flash_delete_option").html("<%= escape_javascript flash.now[:notice]=t(:label_deleted)%>"); 4 | //$("#flash_delete_option").toggle(); 5 | 6 | function alertWithoutNotice(message){ 7 | setTimeout(function(){ 8 | alert(message); 9 | }, 1000); 10 | } 11 | alertWithoutNotice("<%=t(:label_deleted)%>"); 12 | 13 | //alert("<%=t(:label_deleted)%>"); -------------------------------------------------------------------------------- /app/views/log_time_reminder_mailer/reminder_email.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

<%= t(:text_email_remainder_time) %><%= @issue.id %>

8 |

<%= t(:text_email_assignee) %> <%= @user.name %>

9 |

10 | <%= t(:text_email_issue_inform) %> <%= @issue.id %> 11 |
12 | <%= t(:text_email_log_request) %> 13 |

14 | 15 | -------------------------------------------------------------------------------- /lib/tasks/populate_time_spent.rake: -------------------------------------------------------------------------------- 1 | namespace :redmine_leaves do 2 | task populate_time_spent: :environment do 3 | 4 | missing_timespent = UserTimeCheck.where("time_spent is NULL and check_out_time is NOT NULL") 5 | unless missing_timespent.blank? 6 | missing_timespent.each do |missing| 7 | checked_time = missing.check_out_time.to_time - missing.check_in_time.to_time 8 | missing.update_attributes(time_spent: checked_time/60) 9 | 10 | end 11 | end 12 | end 13 | end 14 | 15 | -------------------------------------------------------------------------------- /lib/tasks/auto_leave.rake: -------------------------------------------------------------------------------- 1 | namespace :redmine_leaves do 2 | task auto_leave_mark: :environment do 3 | 4 | missing_timecheck_users = User.active.joins("LEFT OUTER JOIN #{UserTimeCheck. 5 | table_name} ON #{User.table_name}.id = #{UserTimeCheck. 6 | table_name}.user_id AND check_in_time > #{Date.today.to_s}").where("user_id IS NULL") 7 | 8 | missing_timecheck_users.each do |auto_leave| 9 | UserLeave.create!(user_id: auto_leave.id, leave_type: Setting.plugin_redmine_leaves['default_type'], 10 | leave_date: Date.today, comments: 'Auto-Marked Leave') 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/views/user_time_checks/check_in.html.erb: -------------------------------------------------------------------------------- 1 | 2 |

<%= t(:label_check_in)%>

3 | 4 | " > 5 | 6 | 7 | 8 | "> 9 | 10 | 11 | 12 | "> 13 | 14 | 15 | 16 |
<%= t(:label_check_in_time)%>:<%= @user_time_check.check_in_time %>
<%= t(:label_current_user_id) %><%= @user_time_check.user_id %>
<%= t(:label_current_user_name)%> <%= User.current.name %>
17 | -------------------------------------------------------------------------------- /app/models/log_time_reminder_mailer.rb: -------------------------------------------------------------------------------- 1 | class LogTimeReminderMailer < ActionMailer::Base 2 | 3 | default from: 'timelog_reminder@redmine.com' 4 | 5 | def self.default_url_options 6 | ::Mailer.default_url_options 7 | end 8 | 9 | def reminder_email(user, issue, cc_group, reminder, cc_emails = []) 10 | @user = user 11 | @issue = issue 12 | cc_emails = [] 13 | unless cc_group.empty? 14 | cc_group_users = User.active.joins(:groups). 15 | where("#{User.table_name_prefix}groups_users#{User.table_name_suffix}.id" => group) 16 | cc_group_users.each do |user| 17 | cc_emails << user.mail 18 | end 19 | end 20 | 21 | mail(to: @user.mail, subject: "#{reminder} #{t(:label_reminder_for_time_log)} #{@issue.id}", 22 | cc: cc_emails) 23 | end 24 | 25 | end -------------------------------------------------------------------------------- /app/views/user_time_checks/_check_out_info.html.erb: -------------------------------------------------------------------------------- 1 | 2 | " > 3 | 4 | 5 | 6 | "> 7 | 8 | 9 | 10 | "> 11 | 12 | 13 | 14 | "> 15 | 16 | 20 | 21 |
<%= t(:label_check_out_time)%>: <%= @user_time_check.check_out_time %>
<%= t(:label_current_user_id) %> <%= @user_time_check.user_id %>
<%= t(:label_current_user_name)%> <%= User.current.name %>
<%= t(:label_time_spent)%> 17 | <% checked_time = @user_time_check.check_out_time - @user_time_check.check_in_time %> 18 | <%= Time.at(checked_time).utc.strftime("%H:%M:%S")%> 19 |
-------------------------------------------------------------------------------- /app/views/user_time_checks/_show_logged_time_entries.html.erb: -------------------------------------------------------------------------------- 1 | 2 | " > 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <% @time_entries.each do |time_entry| %> 11 | " > 12 | 13 | 14 | 15 | 16 | 17 | <% end %> 18 | 19 |
<%= t(:label_issue_no)%><%= t(:label_subject)%><%= t(:label_time_logged)%><%= t(:label_activity_to_log)%>
<%= link_to time_entry.issue_id, issue_path(time_entry.issue_id) %> <%= link_to time_entry.issue.subject,issue_path(time_entry.issue_id) %> <%= time_entry.hours %> <%= time_entry.activity.name %>
20 | 21 | 22 | -------------------------------------------------------------------------------- /app/views/leave_mailer/group_timesheet.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <%@timesheet_table.each do |timesheet_row|%> 11 | <% (user = timesheet_row[:user]) && (timesheet_row[:user] = link_to_user(user, only_path: false, class: user.css_classes)) %> 12 | <% (project = timesheet_row[:project]) && (timesheet_row[:project] = link_to_project(project, only_path: false)) %> 13 | <% (issues = timesheet_row[:issues]) && (timesheet_row[:issues] = issues.collect {|issue| 14 | issue.nil? ? 'Project': link_to_issue(issue, subject: false, only_path: false)}.join(', ')) %> 15 | <%= "".html_safe %> 16 | <%end%> 17 |
<%=I18n.t(:field_user)%><%=I18n.t(:field_project)%><%=I18n.t(:field_activity)%><%=I18n.t(:field_issue)%><%=I18n.t(:field_hours)%>
#{timesheet_row.values.join('')}
18 | 19 | -------------------------------------------------------------------------------- /app/views/user_time_checks/user_time_reporting_weekly.erb: -------------------------------------------------------------------------------- 1 | <%= render template: "layouts/user_time_reports" %> 2 |
3 |

Weekly User Time Report

4 |
5 | 6 | <% content_for :header_tags do %> 7 | <%= javascript_include_tag "jquery-ui", plugin: 'redmine_leaves' %> 8 | <%= javascript_include_tag "wice_grid", plugin: 'redmine_leaves' %> 9 | 10 | <%= stylesheet_link_tag 'wice_grid', plugin: 'redmine_leaves' %> 11 | <% end %> 12 | 13 | 14 | 15 | 16 | <% if User.current.allowed_to_globally?(:view_time_reports,{}) -%> 17 | 18 | 19 | 20 |
21 | <%= render partial: 'time_report_grid_weekly', locals: {time_report_grid_weekly: @time_report_grid_weekly}%> 22 | <%=render partial: 'time_report_grid_filter_weekly'%> 23 | 24 |
25 |
26 | 27 | <%= render_grid(@time_report_grid_weekly)%> 28 |
29 | 30 | 31 |
32 | 33 | 34 | 35 | <%else%> 36 | 37 |

You do not have permission to view reports

38 | Back 39 | <%end%> 40 | -------------------------------------------------------------------------------- /app/views/user_time_checks/user_time_reporting_monthly.erb: -------------------------------------------------------------------------------- 1 | <%= render template: "layouts/user_time_reports" %> 2 |
3 |

Monthly User Time Report

4 |
5 | 6 | <% content_for :header_tags do %> 7 | <%= javascript_include_tag "jquery-ui", plugin: 'redmine_leaves' %> 8 | <%= javascript_include_tag "wice_grid", plugin: 'redmine_leaves' %> 9 | 10 | <%= stylesheet_link_tag 'wice_grid', plugin: 'redmine_leaves' %> 11 | <% end %> 12 | 13 | 14 | 15 | 16 | <% if User.current.allowed_to_globally?(:view_time_reports,{}) -%> 17 | 18 | 19 | 20 |
21 | <%= render partial: 'time_report_grid_monthly', locals: {time_report_grid_monthly: @time_report_grid_monthly}%> 22 | <%=render partial: 'time_report_grid_filter_monthly'%> 23 | 24 |
25 |
26 | 27 | <%= render_grid(@time_report_grid_monthly)%> 28 |
29 | 30 | 31 |
32 | 33 | 34 | 35 | <%else%> 36 | 37 |

You do not have permission to view reports

38 | Back 39 | <%end%> 40 | -------------------------------------------------------------------------------- /app/views/leave_mailer/project_timesheet.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | <%@timesheet_table.each do |timesheet_row|%> 11 | <%user = timesheet_row[:user]%> 12 | <% timesheet_row[:user] = user && link_to(user.name, user_path(user), only_path: false, class: user.css_classes) %> 13 | <% project = timesheet_row[:project]%> 14 | <% timesheet_row[:project] = project && link_to_project(project, only_path: false) %> 15 | <%issues = timesheet_row[:issues]%> 16 | <% timesheet_row[:issues] = issues && issues.collect {|issue| 17 | issue.nil? ? 'Project': link_to_issue(issue, subject: false, only_path: false)}.join(', ') %> 18 | <%= "".html_safe %> 19 | <%end%> 20 |
<%=I18n.t(:field_user)%><%=I18n.t(:field_project)%><%=I18n.t(:field_activity)%><%=I18n.t(:field_issue)%><%=I18n.t(:field_hours)%>
#{timesheet_row.values.join('')}
21 | 22 | -------------------------------------------------------------------------------- /app/views/user_leave_reports/report.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :header_tags do %> 2 | <%= stylesheet_link_tag 'wice_grid', plugin: 'redmine_wice_grid' %> 3 | <%= stylesheet_link_tag 'grid', plugin: 'redmine_payments' %> 4 | <%= javascript_include_tag 'application', plugin: 'redmine_wice_grid' %> 5 | <%= javascript_include_tag 'ntogglework', plugin: 'redmine_payments' %> 6 | <% end %> 7 | 8 | 9 | <%= render template: "layouts/redmine_leaves" %> 10 | 11 |

<%= t(:title_leave_summary) %>

12 | <%= render 'options' %>

13 | 14 | 15 | <% if @user_leave || @user_leave.empty? %> 16 | <%= render partial: 'grid' %> 17 | 21 | 22 | <% end %> 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Arkhitech 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /app/views/user_time_checks/user_time_reporting.erb: -------------------------------------------------------------------------------- 1 | <%= render template: "layouts/user_time_reports" %> 2 |
3 |

Custom User Time Report

4 | 5 | 6 | <% content_for :header_tags do %> 7 | <%= javascript_include_tag "jquery-ui", plugin: 'redmine_leaves' %> 8 | <%= javascript_include_tag "wice_grid", plugin: 'redmine_leaves' %> 9 | <%= stylesheet_link_tag 'wice_grid', plugin: 'redmine_leaves' %> 10 | <%= javascript_include_tag "application", plugin: 'redmine_leaves' %> 11 | <% end %> 12 | 13 | 14 | 15 | 16 | 17 | <% if User.current.allowed_to_globally?(:view_time_reports,{}) -%> 18 | 19 | 20 | 21 |
22 | <%= render partial: 'time_report_grid', locals: {time_report_grid: @time_report_grid}%> 23 | <%=render partial: 'time_report_grid_filter'%> 24 | 25 |
26 |
27 | 28 | <%= render_grid(@time_report_grid)%> 29 |
30 | 31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | <%else%> 48 | 49 |

You do not have permission to view reports

50 | Back 51 | <%end%> 52 | -------------------------------------------------------------------------------- /app/views/user_time_checks/edit.html.erb: -------------------------------------------------------------------------------- 1 |

<%= t(:label_edit_attendance)%>

2 | 3 | <%= form_for @time_checks do |attendance_form| %> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | <% unless attendance_form.object.check_in_time.blank? %> 12 | 14 | <% else %> 15 | 16 | <% end %> 17 | 18 | 19 | 20 | <% unless attendance_form.object.check_out_time.blank? %> 21 | 23 | <% else %> 24 | 25 | <% end %> 26 | 27 |
<%= t(:label_user)%><%= User.find(@time_checks.user_id).name %>
<%= t(:label_check_in_time)%><%= attendance_form.text_field :check_in_time, 13 | :value => attendance_form.object.check_in_time.to_s(:date_format) %><%= attendance_form.text_field :check_in_time %>
<%= t(:label_check_out_time)%><%= attendance_form.text_field :check_out_time, 22 | :value => attendance_form.object.check_out_time.to_s(:date_format) %><%= attendance_form.text_field :check_out_time %>
28 | <%= attendance_form.submit t(:label_button_update) %> 29 | <% end %> 30 | -------------------------------------------------------------------------------- /app/views/user_leaves/edit.html.erb: -------------------------------------------------------------------------------- 1 |

<%= t(:label_update_leave ) %>

2 | 3 | <%= form_for @user_leave do |leave_form| %> 4 | <%= error_messages_for @user_leave %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 32 |
<%= t(:label_user) %><%= @user_leave.user.name %>
<%= t(:label_leave_type) %><%= leave_form.select(:leave_type, 13 | add_leave_options(@user_leave.leave_type)) %>
<%= t(:label_leave_date) %><%= leave_form.date_select :leave_date %>
<%= t(:label_comments) %> 22 | <%= leave_form.text_field :comments %> 23 | <%= t(:label_optional ) %> 24 |
<%= t(:label_fractional_leave ) %> 29 | <%= leave_form.number_field :fractional_leave,:step =>'.25', min: 0,max: 2.5 %> 30 | <%= t(:label_fractional_leave_optional ) %> 31 |
33 | <%= leave_form.submit t(:label_button_update) %> 34 | <% end %> 35 | 36 | -------------------------------------------------------------------------------- /app/views/user_time_checks/index.html.erb: -------------------------------------------------------------------------------- 1 |

<%= t(:label_attendance_summary ) %>

2 |
3 |

4 | <%= t(:label_import_csv) %> 5 | <%= form_tag import_user_time_checks_path, multipart: true do %> 6 | <%= file_field_tag :file %> 7 | <%= submit_tag t(:label_button_import) %> 8 | 9 | <%= label_tag t(:label_file_note)%> 10 | 11 | <% end %> 12 |


13 |
14 | 15 | <% content_for :header_tags do %> 16 | <%= javascript_include_tag "jquery-ui", plugin: 'redmine_leaves' %> 17 | <%= javascript_include_tag "wice_grid", plugin: 'redmine_leaves' %> 18 | <%= stylesheet_link_tag 'wice_grid', plugin: 'redmine_leaves' %> 19 | <%= javascript_include_tag "application", plugin: 'redmine_leaves' %> 20 | <% end %> 21 | 22 | 23 | 24 |
25 | <%#*
%> 26 |
27 | <%= render partial: 'time_check_grid', locals: {time_check_grid: @time_check_grid}%> 28 | <%=render partial: 'time_check_grid_filter'%> 29 | 30 |
31 |
32 | 33 | <%= render_grid(@time_check_grid)%> 34 |
35 | 36 | 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /lib/redmine_leaves/patches/time_restriction_patch.rb: -------------------------------------------------------------------------------- 1 | module RedmineLeaves 2 | module Patches 3 | module TimeRestrictionPatch 4 | def self.included(base) # :nodoc: 5 | base.extend(ClassMethods) 6 | 7 | base.send(:include, InstanceMethods) 8 | 9 | # Same as typing in the class 10 | base.class_eval do 11 | unloadable # Send unloadable so it will not be unloaded in development 12 | validate :spent_on_log_time_restriction 13 | end 14 | 15 | end 16 | 17 | module ClassMethods 18 | end 19 | 20 | module InstanceMethods 21 | def spent_on_log_time_restriction 22 | unless User.current.admin 23 | errors.add(:spent_on, "can't be #{max_past_time_log_insert_days} days older in the past") if !spent_on.blank? && spent_on < (Date.today - max_past_time_log_insert_days.days) 24 | errors.add(:spent_on, "can't be in future") if !spent_on.blank? && spent_on > Date.today 25 | end 26 | end 27 | 28 | def max_past_time_log_insert_days 29 | (Setting.plugin_redmine_leaves['max_past_timelog_insert_days'] || 7).to_i 30 | end 31 | private :max_past_time_log_insert_days 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | resources :user_leaves 2 | resources :user_leave_reports 3 | #resources :user_leave_analytics 4 | resources :user_time_checks, :only => [:index, :update, :edit] do 5 | collection { post :import } 6 | end 7 | 8 | match '/create_time_entries' => 'user_time_checks#create_time_entries', via: :get 9 | 10 | #get "user_time_checks/home" 11 | get "user_time_checks/check_in" 12 | get "user_time_checks/check_out" 13 | get "user_time_checks/checkout_timelog_success" 14 | get "user_time_checks/user_time_reporting" 15 | get "user_time_checks/user_time_reporting_weekly" 16 | get "user_time_checks/user_time_reporting_monthly" 17 | get "user_time_checks/user_time_activity_report" 18 | post "user_time_checks/user_time_activity_report" 19 | get "user_time_checks/user_time_activity_report_monthly" 20 | post "user_time_checks/user_time_activity_report_monthly" 21 | 22 | #match '/home', to: 'user_time_checks#home', via: 'get' 23 | match '/check_in' => 'user_time_checks#check_in', via: :get 24 | match '/check_out' => 'user_time_checks#check_out', via: :get 25 | match '/checkout_timelog_success' => 'user_time_checks#checkout_timelog_success', via: :get 26 | match '/user_leave_report' => 'user_leave_reports#report', via: [:get, :post] 27 | match '/user_leave_analytics' => 'user_leave_analytics#report', via: [:get, :post] 28 | 29 | 30 | -------------------------------------------------------------------------------- /lib/redmine_leaves/patches/leaves_in_calendar_patch.rb: -------------------------------------------------------------------------------- 1 | module RedmineLeaves 2 | module Patches 3 | module LeavesInCalendarPatch 4 | def self.included(base) 5 | base.send(:include, InstanceMethods) 6 | base.class_eval do 7 | unloadable 8 | end 9 | end 10 | 11 | module InstanceMethods 12 | 13 | def events_with_leaves(project) 14 | @project=project 15 | @events_with_leaves ||= (self.events_with_leaves = @events) 16 | end 17 | 18 | def events_with_leaves_on(day,project) 19 | events_with_leaves(project) 20 | events_on(day) 21 | end 22 | 23 | def events_with_leaves=(events) 24 | leaves=[] 25 | if @project && @project.users 26 | @project.users.each do |user| 27 | leaves< self.startdt..self.enddt) 28 | end 29 | end 30 | events += leaves.flatten 31 | @events = events 32 | @ending_events_by_days = @events.group_by {|event| (event.is_a? UserLeave) ? event.leave_date : event.due_date} 33 | @starting_events_by_days = @events.group_by {|event| (event.is_a? UserLeave) ? event.leave_date : event.due_date} 34 | @events 35 | end 36 | 37 | end 38 | end 39 | end 40 | end -------------------------------------------------------------------------------- /app/views/user_leaves/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for @user_leave do |leave_form| %> 2 | <%= error_messages_for @user_leave %> 3 | 4 | 5 | 6 | 8 | 10 | 11 |
User/Group<%= leave_form.select(:user_id, 7 | add_user_options(@user_leave.user_id),{},{multiple: true}) %><%= leave_form.select(:user_id, 9 | options_from_collection_for_select(Group.all, :id, :name, @selected_group),{},{multiple: true}) %>
12 | 13 | 14 | 15 | 17 | 18 |
Leave Type<%= leave_form.select(:leave_type, 16 | add_leave_options(@user_leave.leave_type)) %>
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
Leave DateFrom
<%= leave_form.date_select :leave_date %>
To
<%= leave_form.date_select :leave_date %>
37 | 38 | 39 | 40 | 44 | 45 |
Comments 41 | <%= leave_form.text_field :comments %> 42 | (Optional) 43 |
46 | <%= leave_form.submit 'Add' %> 47 | <% end %> 48 | -------------------------------------------------------------------------------- /app/models/user_leave.rb: -------------------------------------------------------------------------------- 1 | class UserLeave < ActiveRecord::Base 2 | unloadable 3 | before_save :default_fractional_leave_value 4 | belongs_to :user 5 | after_save :notify_the_absentee 6 | 7 | validates :leave_date, :user_id, :leave_type, presence: true 8 | validates :leave_date, uniqueness: {scope: :user_id, message: 'has already been marked for the user'} 9 | validate :validate_fractional_leave 10 | validate :uniqueness_and_returns_leave_type_of_existing_leave, :on => :create 11 | def notify_the_absentee 12 | LeaveMailer.notify_absentee(self).deliver_now 13 | end 14 | private :notify_the_absentee 15 | 16 | def default_fractional_leave_value 17 | self.fractional_leave ||= 1 18 | end 19 | private :default_fractional_leave_value 20 | 21 | def validate_fractional_leave 22 | if fractional_leave 23 | unless (fractional_leave <= 3) 24 | errors.add(:fractional_value, I18n.t(:error_fractional_value_greater_than_one)) 25 | end 26 | unless (fractional_leave >= -3) 27 | errors.add(:fractional_value, I18n.t(:error_fractional_value_less_than_zero)) 28 | end 29 | end 30 | end 31 | private :validate_fractional_leave 32 | 33 | def uniqueness_and_returns_leave_type_of_existing_leave 34 | existing_leaves=UserLeave.where(leave_date: leave_date, user_id: user_id) 35 | unless existing_leaves.blank? 36 | errors.add(:leave_type,existing_leaves.first.leave_type) 37 | end 38 | end 39 | private :uniqueness_and_returns_leave_type_of_existing_leave 40 | end 41 | -------------------------------------------------------------------------------- /app/helpers/user_time_checks_helper.rb: -------------------------------------------------------------------------------- 1 | module UserTimeChecksHelper 2 | 3 | include TimelogHelper 4 | include ApplicationHelper 5 | include ActionView 6 | include Helpers 7 | include FormTagHelper 8 | 9 | def user_time_reporting 10 | if User.current.allowed_to_globally?(:view_time_reports,{}) 11 | # return deny_access 12 | return true 13 | 14 | else 15 | # return deny_access 16 | return false 17 | end 18 | end 19 | 20 | 21 | def edit_leave_options(user_leave_user_id) 22 | edit_leave_groups = Setting.plugin_redmine_leaves['edit_attendance'] 23 | edit_leave_users = User.active.joins(:groups). 24 | where("#{User.table_name_prefix}groups_users#{User.table_name_suffix}.id" => edit_leave_groups) 25 | 26 | edit_own_leave_groups ||= Setting.plugin_redmine_leaves['edit_own_attendance'] 27 | edit_own_leave_users ||= User.active.joins(:groups). 28 | where("#{User.table_name_prefix}groups_users#{User.table_name_suffix}.id" => edit_own_leave_groups) 29 | 30 | is_current_user_in_edit_leave_users = edit_leave_users.include?(User.current) 31 | is_current_user_in_edit_own_leave_users = edit_own_leave_users.include?(User.current) 32 | 33 | if is_current_user_in_edit_leave_users 34 | return false if User.current.id == user_leave_user_id && !is_current_user_in_edit_own_leave_users 35 | return true 36 | else 37 | return true if User.current.id == user_leave_user_id && is_current_user_in_edit_own_leave_users 38 | return false 39 | end 40 | end 41 | 42 | 43 | end 44 | -------------------------------------------------------------------------------- /app/views/user_time_checks/_time_check_grid_filter.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/user_time_checks/_time_report_grid_filter.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/helpers/user_leave_analytics_helper.rb: -------------------------------------------------------------------------------- 1 | module UserLeaveAnalyticsHelper 2 | include UserLeaveReportsHelper 3 | 4 | def all_leave_types 5 | (Setting.plugin_redmine_leaves['leave_types'] || '').split(',').delete_if { |index| index.blank? } 6 | end 7 | 8 | def leaves_count_for(user, leave, start_date, end_date) 9 | UserLeave.where(user_id: user, leave_type: leave, leave_date: start_date..end_date).sum(:fractional_leave) 10 | end 11 | 12 | def populate_pie_chart_for_user(user, start_date, end_date) 13 | data=[] 14 | all_leave_types.each do |leave| 15 | data << [leave.strip.to_s, 16 | ((leaves_count_for(user, leave,start_date,end_date)*100)/UserLeave.where(user_id: user, leave_date: start_date..end_date).sum(:fractional_leave)).round(2)] 17 | end 18 | data 19 | end 20 | 21 | def group_leaves(leave, start_date, end_date) 22 | group_leave_count = [] 23 | Group.all.each do |group| 24 | group_leave_count << UserLeave. 25 | where(leave_type: leave,user_id: group.users,leave_date: start_date..end_date).sum(:fractional_leave).round(2) 26 | end 27 | group_leave_count 28 | end 29 | 30 | def populate_pie_chart_for_group(start_date, end_date) 31 | data=[] 32 | all_group_leaves = 0 33 | Group.all.each do |group| 34 | all_group_leaves += UserLeave.where(user_id: group.users, leave_date: start_date..end_date).sum(:fractional_leave) 35 | end 36 | Group.all.each do |group| 37 | data << [group.name, ((UserLeave.where(user_id: group.users, leave_date: start_date..end_date) 38 | .sum(:fractional_leave)*100)/all_group_leaves).round(2)] 39 | end 40 | data 41 | end 42 | 43 | end 44 | -------------------------------------------------------------------------------- /app/views/user_time_checks/_time_check_grid.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <%=define_grid @time_check_grid, hide_submit_button: true, hide_reset_button: true, hide_csv_button: false do |e| 3 | 4 | 5 | e.column name: t(:field_user_id), html: {style: 'text-align: center'}, 6 | :attribute => 'user_id', 7 | detach_with_id: 'user_filter' , 8 | custom_filter: User.active.collect{|u|[u.name, u.id]} do |t| 9 | t.user.name unless t.user.nil? 10 | end 11 | 12 | 13 | 14 | e.column name: t(:field_check_in_time), html: {style: 'text-align: center' }, 15 | :attribute => 'check_in_time' , 16 | detach_with_id: 'check_in_time_filter' 17 | 18 | 19 | e.column name: t(:field_check_out_time), html: {style: 'text-align: center'}, 20 | :attribute => 'check_out_time', 21 | detach_with_id: 'check_out_time_filter' 22 | 23 | 24 | e.column name: t(:field_comments), html: {style: 'text-align: center'}, 25 | :attribute => 'comments', detach_with_id: 'comments_filter' 26 | 27 | 28 | e.column name: t(:field_time_spent), html: {style: 'text-align: center'}, 29 | :attribute => 'time_spent',filter_type: :range, 30 | detach_with_id: 'time_spent_filter' do |t| 31 | unless t.check_out_time.nil? 32 | Time.at(t.check_out_time - t.check_in_time).utc.strftime("%H:%M") 33 | 34 | 35 | end 36 | #unless t.time_spent.nil? 37 | #output=(t.time_spent.to_i/60).to_s 38 | 39 | #output+':'+(t.time_spent.to_i%60).to_s 40 | 41 | 42 | #end 43 | end 44 | 45 | 46 | e.column name: 'Logged Time', html: {style: 'text-align: center'}do |t| 47 | unless t.logged_hours.nil? 48 | 49 | t.logged_hours.round(2) 50 | end 51 | end 52 | 53 | end %> 54 | -------------------------------------------------------------------------------- /app/views/user_time_checks/_time_report_grid_filter_weekly.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/user_time_checks/_time_report_grid_filter_monthly.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/user_time_checks/check_out.html.erb: -------------------------------------------------------------------------------- 1 | <% unless @user_time_check.check_out_time.nil? %> 2 |

Check-Out

3 | <%= render 'check_out_info' %> 4 | 5 |


6 | 7 | <%if (@assigned_issues!= nil) then%> 8 | 9 | <% logger.debug "#{'*'*100}\nIssue populated at controller #{@assigned_issues}\n#{'*'*100}"%> 10 | <% time_difference = @user_time_check.check_out_time - @user_time_check.check_in_time %> 11 |

<%= t(:label_total_checked_time_is)%> | <%= Time.at(time_difference).utc.strftime("%H:%M:%S")%> | <%= t(:label_hours_for_today)%>

12 | 13 | <% logged_hours=@time_entries.sum(:hours)%> 14 |

<%= t(:label_total_logged_time_is)%> | <%= Time.at(logged_hours*3600).utc.strftime("%H:%M:%S")%> | <%= t(:label_hours_for_today)%>

15 | 16 | <% logger.debug "#{'*'*100}\nLogged hours are #{Time.at(logged_hours).utc.strftime("%H:%M:%S")}\n#{'*'*100}"%> 17 | 18 | 19 | <% unless logged_hours == 0%> 20 | <%= render 'show_logged_time_entries' %> 21 | <% end %> 22 | 23 |

24 | 25 | <% remaining_time=time_difference-(logged_hours*3600)%> 26 |

<%= t(:label_please_log_remaining)%> | <%= Time.at(remaining_time).utc.strftime("%H:%M:%S")%> | <%= t(:label_hours_for_today)%>

27 |
28 | 29 | <% unless @assigned_issues.blank?%> 30 | <%= render 'availible_open_issues' %> 31 | <% end %> 32 | 33 | <% end %> 34 | <%end%> 35 | -------------------------------------------------------------------------------- /app/views/user_leave_reports/_options.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_tag controller: 'user_leave_reports', action: 'report' do %> 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 |
<%= t(:label_date_from) %>
<%= date_field_tag "user_leave_report[date_from]", 8 | (params[:user_leave_report] && params[:user_leave_report][:date_from]) || Date.today - 1.month %> 9 |
<%= t(:label_date_to) %>
<%= date_field_tag "user_leave_report[date_to]", 16 | (params[:user_leave_report] && params[:user_leave_report][:date_to]) || Date.today %> 17 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 35 | 40 | 41 | 46 | 47 |
<%= t(:label_user) %><%= t(:label_leave_types) %><%= t(:label_group) %>
31 | <%= select_tag("user_leave_report[selected_users][]", 32 | user_options(params[:user_leave_report] && params[:user_leave_report][:selected_users]), 33 | multiple: true) %> 34 | 36 | <%= select_tag("user_leave_report[selected_leave_types][]", 37 | leave_options(params[:user_leave_report] && params[:user_leave_report][:selected_leave_types]), 38 | multiple: true) %> 39 | 42 | <%= select_tag("user_leave_report[selected_groups][]", 43 | group_options(params[:user_leave_report] && params[:user_leave_report][:selected_groups]), 44 | multiple: true) %> 45 |

48 | <%= submit_tag 'Apply' %> 49 | <% end %> 50 | -------------------------------------------------------------------------------- /app/views/user_time_checks/_time_report_grid.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <%=define_grid @time_report_grid, hide_submit_button: true, hide_reset_button: true, hide_csv_button: false do |e| 3 | 4 | #e.column name: t(:field_user_id), html: {style: 'text-align: center'}, 5 | #:attribute => 'user_id',#model:'User', 6 | #detach_with_id: 'user_filter' do |t| 7 | #t.user.name unless t.user.nil? 8 | ##t.user.grou 9 | #end 10 | e.column name: t(:field_user_id), html: {style: 'text-align: center'}, 11 | :attribute => 'user_id', 12 | detach_with_id: 'user_filter' , 13 | custom_filter: User.active.collect{|u|[u.name, u.id]} do |t| 14 | t.user.name unless t.user.nil? 15 | end 16 | 17 | e.column name: 'Average Clock-in', html: {style: 'text-align: center'} do |t| 18 | unless t.avg_check_in_time.nil? 19 | time=Time.at(t.avg_check_in_time) 20 | t= time.in_time_zone 21 | t.to_formatted_s(:time) 22 | end 23 | end 24 | 25 | e.column name: 'Average Clock-out', html: {style: 'text-align: center'} do |t| 26 | unless t.avg_check_out_time.nil? 27 | time=Time.at(t.avg_check_out_time) 28 | t= time.in_time_zone 29 | t.to_formatted_s(:time) 30 | end 31 | end 32 | 33 | e.column name: t(:field_check_in_time), html: {style: 'text-align: center' }, 34 | :attribute => 'check_in_time' , 35 | detach_with_id: 'check_in_time_filter' 36 | 37 | e.column name: t(:field_check_out_time), html: {style: 'text-align: center'}, 38 | :attribute => 'check_out_time', 39 | detach_with_id: 'check_out_time_filter' 40 | 41 | 42 | 43 | 44 | 45 | e.column name: 'Average Time Spent', html: {style: 'text-align: center'} do |t| 46 | 47 | 48 | unless t.average_time.nil? 49 | 50 | output=(t.average_time.to_i/60).to_s 51 | 52 | output+':'+(t.average_time.to_i%60).to_s 53 | 54 | 55 | end 56 | 57 | 58 | end 59 | 60 | 61 | end %> 62 | -------------------------------------------------------------------------------- /app/views/user_time_checks/_time_report_grid_monthly.html.erb: -------------------------------------------------------------------------------- 1 | 2 | <%=define_grid @time_report_grid_monthly, hide_submit_button: true, hide_csv_button: false, hide_reset_button: true do |e| 3 | 4 | e.column name: 'Month', html: {style: 'text-align: center'} do |t| 5 | #t.week 6 | Time.at(t.check_in_time).strftime("%B") 7 | end 8 | 9 | e.column name: 'Year', html: {style: 'text-align: center'} do |t| 10 | #t.week 11 | t.check_in_time.year 12 | end 13 | 14 | 15 | e.column name: t(:field_user_id), html: {style: 'text-align: center'}, 16 | :attribute => 'user_id', 17 | detach_with_id: 'user_filter' , 18 | custom_filter: User.active.collect{|u|[u.name, u.id]} do |t| 19 | t.user.name unless t.user.nil? 20 | end 21 | 22 | 23 | 24 | e.column name: 'Average Clock-in', html: {style: 'text-align: center'} do |t| 25 | unless t.avg_check_in_time.nil? 26 | time=Time.at(t.avg_check_in_time) 27 | t= time.in_time_zone 28 | t.to_formatted_s(:time) 29 | end 30 | end 31 | 32 | e.column name: 'Average Clock-out', html: {style: 'text-align: center'} do |t| 33 | unless t.avg_check_out_time.nil? 34 | time=Time.at(t.avg_check_out_time) 35 | t= time.in_time_zone 36 | t.to_formatted_s(:time) 37 | end 38 | end 39 | 40 | #e.column name: 'Average Clock-out Secs', html: {style: 'text-align: center'} do |t| 41 | #unless t.avg_check_out_time.nil? 42 | 43 | #t.avg_check_out_time 44 | 45 | #end 46 | #end 47 | 48 | e.column name: t(:field_check_in_time), html: {style: 'text-align: center' }, 49 | :attribute => 'check_in_time' , 50 | detach_with_id: 'check_in_time_filter' 51 | 52 | e.column name: t(:field_check_out_time), html: {style: 'text-align: center'}, 53 | :attribute => 'check_out_time', 54 | detach_with_id: 'check_out_time_filter' 55 | 56 | 57 | 58 | 59 | e.column name: 'average_time', html: {style: 'text-align: center'} do |t| 60 | unless t.average_time.nil? 61 | 62 | output=(t.average_time.to_i/60).to_s 63 | 64 | output+':'+(t.average_time.to_i%60).to_s 65 | 66 | 67 | end 68 | end 69 | 70 | 71 | end %> 72 | -------------------------------------------------------------------------------- /app/views/user_time_checks/_availible_open_issues.html.erb: -------------------------------------------------------------------------------- 1 | 2 | " > 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | <%= form_tag({:controller => :user_time_checks, :action => :create_time_entries}) do %> 13 | 14 | <% @new_time_entries.each do |new_time_entry| %> 15 | <% if new_time_entry.errors.any? %> 16 | "> 17 | 18 |
19 |

<%= pluralize(new_time_entry.errors.count, "Error") %> <%= t(:error_post_save)%>

20 |
    21 | <% new_time_entry.errors.full_messages.each do |msg| %> 22 |
  • <%= msg %>
  • 23 | <% end %> 24 |
25 |
27 | <%end%> 28 | 29 | " > 30 | <% assigned_issue = new_time_entry.issue %> 31 | <%#if assigned_issue.status.is_closed==false%> 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | <%= hidden_field_tag "time_entries[][issue_id]", new_time_entry.issue_id %> 40 | <%= hidden_field_tag "time_entries[][user_id]", User.current.id %> 41 | <%= hidden_field_tag "time_entries[][spent_on]", Date.today %> 42 | 43 | 44 | 45 | 46 | <%# end %> 47 | <% end %> 48 | 49 | 50 | 51 | <% end %> 52 |
<%= t(:label_issue_no)%> <%= t(:label_tracker)%><%= t(:label_status)%><%= t(:label_subject)%><%= t(:label_hours_to_log)%><%= t(:label_activity)%><%= t(:label_comments)%>
<%= link_to assigned_issue.id, issue_path(assigned_issue.id) %><%= Tracker.find(assigned_issue.tracker_id).name %><%= assigned_issue.status.name %><%= link_to assigned_issue.subject, issue_path(assigned_issue.id) %> <%= text_field_tag "time_entries[][hours]", new_time_entry.hours, :size => 10%><%= select_tag "time_entries[][activity_id]", options_for_select(activity_collection_for_select_options(new_time_entry))%> <%= text_field_tag "time_entries[][comments]", new_time_entry.comments, :size => 100, :maxlength => 255 %>
<%= submit_tag( t(:label_button_add) ) %>
-------------------------------------------------------------------------------- /assets/stylesheets/redmine_leaves.css: -------------------------------------------------------------------------------- 1 | #leave-menu { 2 | padding: 0px 0px 30px 0px; 3 | 4 | /*position: absolute; bottom: 0px; left:6px; margin-right: -500px;*/} 5 | #leave-menu ul {margin: 0; padding: 0;} 6 | #leave-menu li { 7 | float:left; 8 | list-style-type:none; 9 | margin: 0px 0px 0px 0px; 10 | padding: 0px 0px 0px 0px; 11 | white-space:nowrap; 12 | } 13 | #leave-menu li a { 14 | display: block; 15 | color: #555; 16 | 17 | background: #FFFFFF; 18 | text-decoration: none; 19 | font-weight: bold; 20 | margin: 0; 21 | padding: 4px 10px 4px 10px; 22 | } 23 | #leave-menu li a:hover {background:#759FCF; color:#fff;} 24 | #leave-menu li a.selected, #leave-menu li a.selected:hover {background:#fff; color:#179;} 25 | 26 | 27 | 28 | #user-time-report-menu { 29 | padding: 0px 0px 30px 0px; 30 | 31 | /*position: absolute; bottom: 0px; left:6px; margin-right: -500px;*/} 32 | #user-time-report-menu ul {margin: 0; padding: 0;} 33 | #user-time-report-menu li { 34 | float:left; 35 | list-style-type:none; 36 | margin: 0px 0px 0px 0px; 37 | padding: 0px 0px 0px 0px; 38 | white-space:nowrap; 39 | } 40 | #user-time-report-menu li a { 41 | display: block; 42 | color: #555; 43 | 44 | background: #FFFFFF; 45 | text-decoration: none; 46 | font-weight: bold; 47 | margin: 0; 48 | padding: 4px 10px 4px 10px; 49 | } 50 | #user-time-report-menu li a:hover {background:#759FCF; color:#fff;} 51 | #user-time-report-menu li a.selected, #user-time-report-menu li a.selected:hover {background:#fff; color:#179;} 52 | 53 | 54 | 55 | 56 | 57 | 58 | #user-time-analytics-menu { 59 | padding: 0px 0px 30px 0px; 60 | 61 | /*position: absolute; bottom: 0px; left:6px; margin-right: -500px;*/} 62 | #user-time-analytics-menu ul {margin: 0; padding: 0;} 63 | #user-time-analytics-menu li { 64 | float:left; 65 | list-style-type:none; 66 | margin: 0px 0px 0px 0px; 67 | padding: 0px 0px 0px 0px; 68 | white-space:nowrap; 69 | } 70 | #user-time-analytics-menu li a { 71 | display: block; 72 | color: #555; 73 | 74 | background: #FFFFFF; 75 | text-decoration: none; 76 | font-weight: bold; 77 | margin: 0; 78 | padding: 4px 10px 4px 10px; 79 | } 80 | #user-time-analytics-menu li a:hover {background:#759FCF; color:#fff;} 81 | #user-time-analytics-menu li a.selected, #user-time-analytics-menu li a.selected:hover {background:#fff; color:#179;} 82 | 83 | -------------------------------------------------------------------------------- /app/views/user_time_checks/_time_report_grid_weekly.erb: -------------------------------------------------------------------------------- 1 | 2 | <%=define_grid @time_report_grid_weekly, hide_submit_button: true, hide_csv_button: false, hide_reset_button: true do |e| 3 | 4 | 5 | e.column name: 'Week # ', html: {style: 'text-align: center'} do |t| 6 | t.week 7 | # time=Time.at(t.week) 8 | # time.strftime("%W") 9 | end 10 | e.column name: 'Week Start Date', html: {style: 'text-align: center'} do |t| 11 | 12 | time=Time.at(t.weekdays.beginning_of_week) 13 | time.strftime("%F") 14 | end 15 | e.column name: 'Week End Date', html: {style: 'text-align: center'} do |t| 16 | time=Time.at(t.weekdays.end_of_week) 17 | time.strftime("%F") 18 | end 19 | 20 | 21 | e.column name: 'Year', html: {style: 'text-align: center'} do |t| 22 | #t.week 23 | # Time.at(t.year).year 24 | t.year 25 | end 26 | 27 | 28 | 29 | e.column name: t(:field_user_id), html: {style: 'text-align: center'}, 30 | :attribute => 'user_id', 31 | detach_with_id: 'user_filter' , 32 | custom_filter: User.active.collect{|u|[u.name, u.id]} do |t| 33 | t.user.name unless t.user.nil? 34 | end 35 | 36 | 37 | e.column name: 'Average Clock-in', html: {style: 'text-align: center'} do |t| 38 | unless t.avg_check_in_time.nil? 39 | time=Time.at(t.avg_check_in_time) 40 | t= time.in_time_zone 41 | t.to_formatted_s(:time) 42 | end 43 | end 44 | 45 | e.column name: 'Average Clock-out', html: {style: 'text-align: center'} do |t| 46 | unless t.avg_check_out_time.nil? 47 | time=Time.at(t.avg_check_out_time) 48 | t= time.in_time_zone 49 | t.to_formatted_s(:time) 50 | end 51 | end 52 | 53 | e.column name: t(:field_check_in_time), html: {style: 'text-align: center' }, 54 | :attribute => 'check_in_time' , detach_with_id: 'check_in_time_filter' 55 | 56 | e.column name: t(:field_check_out_time), html: {style: 'text-align: center'}, 57 | :attribute => 'check_out_time', detach_with_id: 'check_out_time_filter' 58 | 59 | 60 | 61 | e.column name: 'Average Time Spent', html: {style: 'text-align: center'}, 62 | :attribute => 'comments',#filter_type: :range, 63 | filter: false do |t| 64 | unless t.average_time.nil? 65 | 66 | output=(t.average_time.to_i/60).to_s 67 | 68 | output+':'+(t.average_time.to_i%60).to_s 69 | 70 | 71 | end 72 | 73 | end 74 | 75 | 76 | end %> 77 | -------------------------------------------------------------------------------- /app/views/user_leaves/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= render template: "layouts/redmine_leaves" %> 2 |

<%= t(:label_add_leave ) %>

3 | <%= form_for UserLeave.new do %> 4 | 5 |
6 | <%= t(:label_user_group) %> 7 | <%= select_tag("create_user_leave[selected_users][]", 8 | add_user_options(params[:create_user_leave] && params[:create_user_leave][:selected_users]), 9 | multiple: true) %> 10 | <%= select_tag('create_user_leave[selected_groups][]', 11 | add_group_options(params[:create_user_leave] && params[:create_user_leave][:selected_groups]), 12 | multiple: true, disabled: @disable_group_leave) %> 13 |

14 | 15 |
16 | <%= t(:label_leave_type ) %> 17 | <%= select_tag('create_user_leave[selected_leave]', 18 | add_leave_options(params[:create_user_leave] && params[:create_user_leave][:selected_leave])) %> 19 |

20 | 21 |
22 | 23 |
24 | <%= t(:label_leave_date ) %> 25 | <%= t(:label_date_from) %>
26 | <%= text_field_tag("create_user_leave[selected_date_from]", 27 | (params[:create_user_leave] && params[:create_user_leave][:selected_date_from]) || Date.today) %> 28 | <%= calendar_for 'create_user_leave_selected_date_from' %>
29 | <%= t(:label_date_to) %>
30 | <%= text_field_tag("create_user_leave[selected_date_to]", 31 | (params[:create_user_leave] && params[:create_user_leave][:selected_date_to]) || Date.today) %> 32 | <%= calendar_for 'create_user_leave_selected_date_to' %>
33 |

34 |
35 |
36 | 37 |
38 | <%= t(:label_comments) %> 39 | 40 | <%= text_field_tag "create_user_leave[comments]", 41 | params[:create_user_leave] && params[:create_user_leave][:comments], size: 28 %> 42 | 43 | <%= t(:label_optional ) %> 44 |

45 | 46 |
47 | <%= t(:label_fractional_leave) %> 48 | 49 | <%= number_field_tag "create_user_leave[fractional_leave]", 50 | params[:create_user_leave] && params[:create_user_leave][:fractional_leave], 51 | :step =>'.25', min: -2,max: 2.5 %> 52 | <%= t(:label_fractional_leave_optional ) %> 53 |

54 | 55 | <%= submit_tag t(:label_button_add) %> 56 | 57 | <% end %> 58 | 59 | -------------------------------------------------------------------------------- /app/views/common/_calendar.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <% 7.times do |i| %><% end %> 4 | 5 | 6 | 7 | <% day = calendar.startdt 8 | while day <= calendar.enddt %> 9 | <%= ("".html_safe) if day.cwday == calendar.first_wday %> 10 | 41 | <%= ''.html_safe if day.cwday==calendar.last_wday and day!=calendar.enddt %> 42 | <% day = day + 1 43 | end %> 44 | 45 | 46 |
<%= day_name( (calendar.first_wday+i)%7 ) %>
#{(day+(11-day.cwday)%7).cweek} 11 |

<%= day.day %>

12 | <% calendar.events_with_leaves_on(day,@project).each do |i| %> 13 | <% if i.is_a? Issue %> 14 |
15 | <%= h("#{i.project} -") unless @project && @project == i.project %> 16 | <%= link_to_issue i, :truncate => 30 %> 17 | <%= render_issue_tooltip i %> 18 |
19 | <% elsif i.is_a? Version %> 20 | 21 | <%= h("#{i.project} -") unless @project && @project == i.project %> 22 | <%= link_to_version i%> 23 | 24 | <% elsif i.is_a? UserLeave %> 25 |
26 | 27 | <%= link_to "#{User.find(i.user_id).name}", user_url(User.find(i.user_id).id) %> 28 | <%= "- #{i.fractional_leave}" %>
29 | 30 | <%=t(:label_name)%>: <%=link_to "#{User.find(i.user_id).name}", user_url(User.find(i.user_id).id) %>
31 | <%=t(:label_leave_type)%>: <%= i.leave_type %>
32 | <%=t(:label_leave_date)%>: <%= i.leave_date %>
33 | <%=t(:label_comments)%>: <%= i.comments %>
34 | <%=t(:label_weight)%>: <%= i.fractional_leave %>
35 |
36 |
37 | <% end %> 38 |
39 | <% end %> 40 |
-------------------------------------------------------------------------------- /app/controllers/user_leave_reports_controller.rb: -------------------------------------------------------------------------------- 1 | class UserLeaveReportsController < ApplicationController 2 | unloadable 3 | 4 | before_action :require_login 5 | 6 | include UserLeaveReportsHelper 7 | 8 | def index 9 | @all_users = User.all 10 | @all_leaves = plugin_setting('leave_types') 11 | end 12 | 13 | def report 14 | if params[:user_leave_report].nil? 15 | flash.now[:error] = t(:error_no_params ) 16 | return 17 | end 18 | 19 | user_leaves = nil 20 | where_statements = [] 21 | where_clause = [''] 22 | selected_groups = params[:user_leave_report][:selected_groups] 23 | 24 | unless (params[:user_leave_report][:selected_users].nil? && selected_groups.nil?) 25 | where_statements << 'user_id IN (?)' 26 | all_users=[] 27 | all_users << params[:user_leave_report][:selected_users] 28 | 29 | unless selected_groups.nil? 30 | selected_groups.each do |selected_group| 31 | group=Group.where(lastname: selected_group) 32 | all_users << group.first.users unless group.first.users.nil? 33 | end 34 | end 35 | where_clause << all_users.flatten.uniq 36 | end 37 | 38 | selected_leave_types = params[:user_leave_report][:selected_leave_types] 39 | unless selected_leave_types.nil? 40 | where_statements << 'leave_type IN (?)' 41 | where_clause << selected_leave_types 42 | end 43 | 44 | unless params[:user_leave_report][:date_from].blank? 45 | begin 46 | where_statements << 'leave_date >= ?' 47 | where_clause << params[:user_leave_report][:date_from].to_date 48 | rescue StandardError 49 | flash.now[:error] = t(:error_invalid_date) 50 | return 51 | end 52 | end 53 | 54 | unless params[:user_leave_report][:date_to].blank? 55 | begin 56 | where_statements << 'leave_date <= ?' 57 | where_clause << params[:user_leave_report][:date_to].to_date 58 | rescue StandardError 59 | flash.now[:error] = t(:error_invalid_date) 60 | return 61 | end 62 | end 63 | 64 | where_clause[0] = where_statements.join(' AND ') 65 | @user_leave = UserLeave.where(where_clause).order('leave_date desc') 66 | 67 | if @user_leave .nil? || @user_leave .empty? 68 | flash.now[:error] = t(:error_no_results) 69 | end 70 | 71 | @leaves_report_grid = initialize_grid(@user_leave.order(id: :desc), 72 | name: 'grid', 73 | enable_export_to_csv: true, 74 | csv_field_separator: ';', 75 | csv_file_name: 'LeavesReport') 76 | 77 | export_grid_if_requested('grid' => 'grid') 78 | 79 | 80 | 81 | 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /app/models/leave_mailer.rb: -------------------------------------------------------------------------------- 1 | class LeaveMailer < ActionMailer::Base 2 | layout 'mailer' 3 | default from: Setting.mail_from 4 | helper ApplicationHelper 5 | def self.default_url_options 6 | ::Mailer.default_url_options 7 | end 8 | 9 | def cc_role_email_addresses(user) 10 | cc_role_ids = Setting.plugin_redmine_leaves['cc_roles'] 11 | if cc_role_ids.present? 12 | User.active.preload(:email_address).joins(members: :member_roles).where("#{Member.table_name}.project_id" => user.projects.pluck(:id)). 13 | where("#{MemberRole.table_name}.role_id IN (?)", cc_role_ids).map(&:email_address).map(&:address) 14 | else 15 | [] 16 | end 17 | end 18 | private :cc_role_email_addresses 19 | 20 | def cc_email_addresses(user) 21 | emails = User.in_group(UserTimeCheck.time_log_receivers_group).map(&:mail) 22 | emails += cc_role_email_addresses(user) 23 | emails.uniq 24 | end 25 | private :cc_email_addresses 26 | 27 | def notify_absentee(user_leave) 28 | @total_yearly_leaves = UserLeave.where(user_id: user_leave.user_id, leave_type: user_leave.leave_type).where("leave_date >= ?", Date.today.beginning_of_year).sum(:fractional_leave) 29 | @leave = user_leave 30 | mail(to: @leave.user.mail, 31 | cc: cc_email_addresses(@leave.user), 32 | subject: I18n.t('subject_leave_marked_for', 33 | user_name: @leave.user.name, 34 | fraction: @leave.fractional_leave, total_yearly_leaves: @total_yearly_leaves, 35 | leave_type: @leave.leave_type, leave_date: I18n.l(@leave.leave_date))) 36 | end 37 | 38 | #send project timesheet 39 | def project_timesheet(users, timesheet_table, project, start_date, end_date) 40 | @timesheet_table = timesheet_table 41 | @report_date = start_date == end_date ? I18n.l(end_date) : "#{I18n.l(start_date)} - #{I18n.l(end_date)}" 42 | mail(to: users.map(&:mail), subject: I18n.t('subject_project_timesheet', 43 | project: project, report_date: @report_date)) 44 | end 45 | 46 | def group_timesheet(users, timesheet_table, group, start_date, end_date) 47 | @timesheet_table = timesheet_table 48 | @report_date = start_date == end_date ? I18n.l(end_date) : "#{I18n.l(start_date)} - #{I18n.l(end_date)}" 49 | mail(to: users.map(&:mail), subject: I18n.t('subject_group_timesheet', 50 | group: group.lastname, report_date: @report_date)) 51 | end 52 | 53 | def missing_time_log(user, start_date, end_date, logged_hours) 54 | @user = user 55 | @report_date = start_date == end_date ? I18n.l(end_date) : "#{I18n.l(start_date)} - #{I18n.l(end_date)}" 56 | @logged_hours = logged_hours 57 | mail(to: user.mail, 58 | cc: cc_email_addresses(user), 59 | subject: I18n.t('subject_missing_time_log', 60 | user_name: user.name, report_date: @report_date)) 61 | end 62 | 63 | end -------------------------------------------------------------------------------- /app/views/user_leave_analytics/report.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :header_tags do %> 2 | <%= javascript_include_tag "highcharts", plugin: 'redmine_leaves' %> 3 | <% end %> 4 | 5 | <%= form_tag controller: 'user_leave_analytics', action: 'report' do %> 6 | 7 | <%= render template: "layouts/redmine_leaves" %> 8 |

<%= t(:title_leave_analytics) %>

9 | 10 |
11 |
12 | <%= t(:label_date_from) %> 13 | <%= text_field_tag "user_leave_analytic[date_from]", 14 | (params[:user_leave_analytic] && params[:user_leave_analytic][:date_from]) || (Date.today.beginning_of_year) %> 15 | <%= calendar_for 'user_leave_analytic_date_from' %>
16 |
17 | <%= t(:label_date_to) %> 18 | <%= text_field_tag "user_leave_analytic[date_to]", 19 | (params[:user_leave_analytic] && params[:user_leave_analytic][:date_to]) || Date.today %> 20 | <%= calendar_for 'user_leave_analytic_date_to' %>
21 |
22 | <%= submit_tag 'Apply' %>
23 |
24 | <%= t(:label_user) %> 25 | <%= select_tag "user_leave_analytic[selected_user]", 26 | user_options(@user.id), 27 | {onchange: "this.form.submit();"} %>
28 |
29 | 30 | 31 |
32 | <%= high_chart("my_b1", @bar1) %> 33 |
34 | 35 |
36 | <%= high_chart("my_p1", @pie1) do |c| %> 37 | <%= raw "options.tooltip.formatter = function() {return this.point.name +': '+ this.y +' %';}" %> 38 | <%= raw "options.plotOptions.pie.dataLabels.formatter = function() { if (this.y > 5) return this.point.name; }" %> 39 | <% end %> 40 |
41 | 42 |
43 | <%= high_chart("my_b2", @bar2) %> 44 |
45 | 46 |
47 | <%= high_chart("my_p2", @pie2) do |c| %> 48 | <%= raw "options.tooltip.formatter = function() {return this.point.name +': '+ this.y +' %';}" %> 49 | <%= raw "options.plotOptions.pie.dataLabels.formatter = function() { if (this.y > 5) return this.point.name; }" %> 50 | <% end %> 51 |
52 | 53 |
54 | <%= high_chart("my_b3", @bar3) %> 55 |
56 | <% end %> 57 | 58 |
59 | <%= high_chart("my_p3", @pie3) do |c| %> 60 | <%= raw "options.tooltip.formatter = function() {return this.point.name +': '+ this.y +' %';}" %> 61 | <%= raw "options.plotOptions.pie.dataLabels.formatter = function() { if (this.y > 5) return this.point.name; }" %> 62 | <% end %> 63 |
64 | -------------------------------------------------------------------------------- /app/helpers/user_leaves_helper.rb: -------------------------------------------------------------------------------- 1 | module UserLeavesHelper 2 | 3 | def plugin_setting(setting_name) 4 | (Setting.plugin_redmine_leaves[setting_name] || '').split(',').delete_if { |index| index.blank? } 5 | end 6 | 7 | def mark_leave_options(mark_leave) 8 | User.active.joins(:groups). 9 | where("#{User.table_name_prefix}groups_users#{User.table_name_suffix}.id" => mark_leave) 10 | end 11 | 12 | def add_user_options(selected_user) 13 | mark_leave_groups = Setting.plugin_redmine_leaves['mark_leaves'] 14 | mark_leave_users = mark_leave_options(mark_leave_groups) 15 | 16 | mark_own_leave_groups = Setting.plugin_redmine_leaves['mark_own_leave'] 17 | mark_own_leave_users = mark_leave_options(mark_own_leave_groups) 18 | 19 | time_loggers_group = Group.find(Setting.plugin_redmine_leaves['time_loggers_group']) 20 | if(mark_leave_users.include?(User.current) && mark_own_leave_users.include?(User.current)) 21 | # if current user have both permissions 22 | all_users = User.in_group(time_loggers_group) 23 | elsif(mark_leave_users.include?(User.current) && !mark_own_leave_users.include?(User.current)) 24 | # if current user have permission to mark leaves only 25 | all_users = User.in_group(time_loggers_group).where.not(id: User.current.id) 26 | elsif(!mark_leave_users.include?(User.current) && mark_own_leave_users.include?(User.current)) 27 | # if current user have permission to mark own leaves 28 | all_users = User.where(id: User.current.id).active 29 | else 30 | all_users = User.active 31 | end 32 | 33 | options_from_collection_for_select(all_users.sort_by{|e| e[:firstname]}, :id, :name, selected_user) 34 | 35 | end 36 | 37 | def add_group_options(selected_group) 38 | mark_leave_groups = Setting.plugin_redmine_leaves['mark_leaves'] 39 | mark_leave_users = mark_leave_options(mark_leave_groups) 40 | 41 | mark_own_leave_groups = Setting.plugin_redmine_leaves['mark_own_leave'] 42 | mark_own_leave_users = mark_leave_options(mark_own_leave_groups) 43 | 44 | unless(!mark_leave_users.include?(User.current) && mark_own_leave_users.include?(User.current)) 45 | # if current user have permission to mark own leaves 46 | @disable_group_leave = false 47 | options_from_collection_for_select(Group.all, :id, :name, selected_group) 48 | else 49 | @disable_group_leave = true 50 | options_from_collection_for_select(Group.all, :id, :name, selected_group) 51 | end 52 | end 53 | 54 | def add_leave_options(selected_leave_type) 55 | all_leave_types = plugin_setting('leave_types') 56 | options_for_select(all_leave_types, selected_leave_type || Setting.plugin_redmine_leaves['default_type']) 57 | end 58 | 59 | def check_selected_users(selected_users) 60 | mark_leave_groups = Setting.plugin_redmine_leaves['mark_leaves'] 61 | mark_leave_users = mark_leave_options(mark_leave_groups) 62 | 63 | mark_own_leave_groups = Setting.plugin_redmine_leaves['mark_own_leave'] 64 | mark_own_leave_users = mark_leave_options(mark_own_leave_groups) 65 | if(mark_leave_users.include?(User.current) && !mark_own_leave_users.include?(User.current)) 66 | selected_users = selected_users - [User.current.id.to_s] 67 | end 68 | selected_users 69 | end 70 | 71 | end 72 | -------------------------------------------------------------------------------- /init.rb: -------------------------------------------------------------------------------- 1 | require 'redmine' 2 | 3 | Rails.configuration.to_prepare do 4 | require_dependency 'user' 5 | User.send(:include, RedmineLeaves::Patches::UserPatch) 6 | 7 | require_dependency 'redmine/helpers/calendar' 8 | Redmine::Helpers::Calendar.send(:include, RedmineLeaves::Patches::LeavesInCalendarPatch) 9 | 10 | require_dependency 'time_entry' 11 | TimeEntry.send(:include, RedmineLeaves::Patches::TimeRestrictionPatch) 12 | end 13 | 14 | require 'wice_grid_config' 15 | 16 | Redmine::Plugin.register :redmine_leaves do 17 | name 'Redmine Leaves Plugin' 18 | author 'Arkhitech' 19 | description 'This is a plugin for user check-in/check-out' 20 | url 'http://github.com/arkhitech/redmine_leaves' 21 | author_url 'https://github.com/arkhitech' 22 | version '0.1.0' 23 | 24 | permission :receive_timesheet_email, { }, require: :member 25 | 26 | permission :view_time_reports, user_time_check: :user_time_reporting 27 | 28 | 29 | menu :top_menu, :time_check_in, { controller: 'user_time_checks', action: 'check_in' }, 30 | caption: :caption_top_menu_check_in, if: Proc.new {!UserTimeCheck.checked_in?(User.current.id)}, last: true 31 | menu :top_menu, :time_check_out, { controller: 'user_time_checks', action: 'check_out' }, 32 | caption: :caption_top_menu_check_out, if: Proc.new {UserTimeCheck.checked_in?(User.current.id)}, last: true 33 | 34 | menu :top_menu, :user_leave_reports, { controller: 'user_leave_reports', action: 'index' }, caption: :caption_leave_report 35 | 36 | menu :top_menu, :user_time_checks, { controller: 'user_time_checks', action: 'index' }, caption: :caption_user_time_check 37 | 38 | menu :top_menu, :user_time_reporting, { controller: 'user_time_checks', action: 'user_time_reporting' }, :caption => 'User Time Reports' 39 | menu :user_time_report_menu, :user_time_report_custom, { :controller => 'user_time_checks', :action => 'user_time_reporting'}, :caption => 'Custom' 40 | menu :user_time_report_menu, :user_time_report_weekly, { :controller => 'user_time_checks', :action => 'user_time_reporting_weekly'}, :caption => 'Weekly' 41 | menu :user_time_report_menu, :user_time_report_monthly, { :controller => 'user_time_checks', :action => 'user_time_reporting_monthly'}, :caption => 'Monthly' 42 | 43 | menu :top_menu, :user_time_activity_report, { controller: 'user_time_checks', action: 'user_time_activity_report' }, :caption => 'User Time-Activity Report' 44 | menu :user_time_analytics_menu, :user_time_activity_report_custom, { :controller => 'user_time_checks', :action => 'user_time_activity_report'}, :caption => 'All Time' 45 | menu :user_time_analytics_menu, :user_time_activity_report_monthly, { :controller => 'user_time_checks', :action => 'user_time_activity_report_monthly'}, :caption => 'Monthly' 46 | # 47 | 48 | 49 | 50 | 51 | 52 | 53 | menu :leave_report_menu, :user_leave_reports, { :controller => 'user_leave_reports', :action => 'index' }, :caption => 'Overview' 54 | menu :leave_report_menu, :user_leaves, { :controller => 'user_leaves', :action => 'new' }, :caption => 'Add Leave' 55 | menu :leave_report_menu, :user_leave_analytics, { :controller => 'user_leave_analytics', :action => 'report'}, :caption => 'Analytics' 56 | 57 | settings default: { 58 | 'leave_types' => 'Annual, Sick, Unannounced', 59 | 'default_type' => 'Annual', 60 | 'time_loggers_group' => 'Staff', 61 | 'time_log_receivers_group' => 'HR', 62 | 'num_min_working_hours' => '8', 63 | 'max_past_timelog_insert_days' => '7' 64 | }, partial: 'settings' 65 | end 66 | -------------------------------------------------------------------------------- /app/helpers/user_leave_reports_helper.rb: -------------------------------------------------------------------------------- 1 | module UserLeaveReportsHelper 2 | 3 | def mark_leave_users 4 | @mark_leave_users ||= begin 5 | mark_leave_groups = Setting.plugin_redmine_leaves['mark_leaves'] 6 | if mark_leave_groups.present? 7 | User.active.joins(:groups). 8 | where("#{User.table_name_prefix}groups_users#{User.table_name_suffix}.id" => mark_leave_groups) 9 | else 10 | User.active.all 11 | end 12 | end 13 | end 14 | 15 | def mark_own_leave_users 16 | @mark_own_leave_users ||= begin 17 | mark_own_leave_groups = Setting.plugin_redmine_leaves['mark_own_leave'] 18 | User.active.joins(:groups). 19 | where("#{User.table_name_prefix}groups_users#{User.table_name_suffix}.id" => mark_own_leave_groups) 20 | end 21 | end 22 | 23 | def user_allowed_to_edit_leaves? 24 | User.current.admin? || edit_attendance_users.include?(User.current) || edit_own_attendance_users.include?(User.current) 25 | end 26 | 27 | def user_allowed_to_edit_leave?(user_leave) 28 | return true if User.current.admin? 29 | return true if user_leave.user != User.current && edit_attendance_users.include?(User.current) 30 | return true if user_leave.user == User.current && edit_own_attendance_users.include?(User.current) 31 | #else 32 | false 33 | end 34 | def edit_attendance_users 35 | @edit_attendance_users ||= begin 36 | edit_attendance_groups = Setting.plugin_redmine_leaves['edit_attendance'] 37 | User.active.joins(:groups). 38 | where("#{User.table_name_prefix}groups_users#{User.table_name_suffix}.id" => edit_attendance_groups) 39 | end 40 | end 41 | def edit_own_attendance_users 42 | @edit_own_attendance_users ||= begin 43 | edit_own_attendance_groups = Setting.plugin_redmine_leaves['edit_own_attendance'] 44 | User.active.joins(:groups). 45 | where("#{User.table_name_prefix}groups_users#{User.table_name_suffix}.id" => edit_own_attendance_groups) 46 | end 47 | end 48 | def plugin_setting(setting_name) 49 | (Setting.plugin_redmine_leaves[setting_name] || '').split(',').delete_if { |index| index.blank? } 50 | end 51 | 52 | def eligible_for_leave_users 53 | group_ids = Setting.plugin_redmine_leaves['eligible_for_leave_groups'] 54 | if group_ids.blank? 55 | time_loggers_group = Group.find(Setting.plugin_redmine_leaves['time_loggers_group']) 56 | @eligible_users = User.in_group(time_loggers_group) 57 | # @eligible_users.order('users.name ASC').all.map{ |c| [c.name, c.id] } 58 | @eligible_users.sort_by{|e| e[:firstname]} 59 | else 60 | @eligible_users= User.active.joins(:groups). 61 | where("#{User.table_name_prefix}groups_users#{User.table_name_suffix}.id" => 62 | group_ids).group("#{User.table_name}.id") 63 | @eligible_users.sort_by{|e| e[:firstname]} 64 | 65 | end 66 | end 67 | 68 | def user_options(selected_users) 69 | options_from_collection_for_select(eligible_for_leave_users, :id, :name, selected_users) 70 | end 71 | 72 | def leave_options(selected_leave_types) 73 | all_leave_types = plugin_setting('leave_types') 74 | options_for_select(all_leave_types, selected_leave_types) 75 | end 76 | 77 | def group_options(selected_groups) 78 | all_group_types = Group.all 79 | options_for_select(all_group_types, selected_groups) 80 | end 81 | def group_by_options(selected_group_by) 82 | group_by = [[t(:options_group_by_title ),'Leave type'], 83 | [t(:option_group_by_user),'User'], 84 | [t(:option_group_by_date),'Date']] 85 | options_for_select(group_by, selected_group_by) 86 | end 87 | 88 | end 89 | -------------------------------------------------------------------------------- /app/views/user_leave_reports/_grid.html.erb: -------------------------------------------------------------------------------- 1 | <%leave_types_totals = {}%> 2 | 3 | <%=grid(@leaves_report_grid,hide_submit_button: true, hide_csv_button: false, hide_reset_button: true ,:show_filters => :always,html: {class: 'my-grid'}, header_tr_html: {class: 'my-header'}) do |g| 4 | 5 | g.column name: t(:label_name), in_csv: false do |user_leave| 6 | link_to user_leave.user.name, user_path(user_leave.user) 7 | end 8 | g.column name: t(:label_name), in_html: false do |user_leave| 9 | user_leave.user.name 10 | end 11 | 12 | 13 | g.column name: t(:label_leave_type), in_csv: false, attribute: 'leave_type', detach_with_id: :leave_type_filter do |user_leave| 14 | link_to user_leave.leave_type, controller: 'user_leave_reports', action: 'report', user_leave_report:{selected_leave_types: user_leave.leave_type, selected_group_by: 'User'} 15 | end 16 | g.column name: t(:label_leave_type), in_html: false, attribute: 'leave_type', detach_with_id: :leave_type_filter do |user_leave| 17 | user_leave.leave_type 18 | end 19 | 20 | g.column name: t(:label_leave_date), in_csv: false, attribute: 'leave_date', detach_with_id: :leave_date_filter do |user_leave| 21 | link_to format_date(user_leave.leave_date), controller: 'user_leave_reports', action: 'report', user_leave_report:{date_from: user_leave.leave_date,date_to: user_leave.leave_date, selected_group_by: 'User'} 22 | end 23 | g.column name: t(:label_leave_date), in_html: false, attribute: 'leave_date', detach_with_id: :leave_date_filter do |user_leave| 24 | format_date user_leave.leave_date 25 | end 26 | 27 | g.column name: t(:label_weight), attribute: 'fractional_leave', detach_with_id: :fractional_leave_filter do |user_leave| 28 | 29 | leave_types_totals [user_leave.leave_type] ||= 0 30 | leave_types_totals [user_leave.leave_type] += user_leave.fractional_leave 31 | 32 | user_leave.fractional_leave 33 | 34 | end 35 | 36 | 37 | g.column name: t(:label_comments), attribute: 'comments', detach_with_id: :comments_filter do |user_leave| 38 | user_leave.comments 39 | end 40 | 41 | g.column name: t(:field_updated_on), in_csv: false, attribute: 'updated_at', detach_with_id: 'updated_filter' do |user_leave| 42 | format_date(user_leave.updated_at) 43 | end 44 | g.column name: t(:field_updated_on), in_html: false, attribute: 'updated_at', detach_with_id: 'updated_filter' do |user_leave| 45 | format_date(user_leave.updated_at) 46 | end 47 | 48 | g.column name: t(:field_created_on), in_csv: false, attribute: 'created_at', detach_with_id: 'created_filter' do |user_leave| 49 | format_date(user_leave.created_at) 50 | end 51 | g.column name: t(:field_created_on), in_html: false, attribute: 'created_at', detach_with_id: 'created_filter' do |user_leave| 52 | format_date(user_leave.created_at) 53 | end 54 | 55 | if user_allowed_to_edit_leaves? 56 | g.column name: 'Edit', in_csv: false do |user_leave| 57 | link_to t(:link_edit), edit_user_leafe_path(user_leave) 58 | end 59 | g.column name: 'Delete', in_csv: false do |user_leave| 60 | link_to t(:link_delete), user_leafe_path(user_leave), :remote => true, method: :delete, data: { confirm: t(:label_confirm) } 61 | end 62 | end 63 | 64 | 65 | g.last_row do |number_of_columns| 66 | 67 | leave_types_string = [] 68 | leave_weights_string=[] 69 | 70 | leave_types_totals.each_pair do |key, value| 71 | leave_types_string << key 72 | leave_weights_string << '%.2f' % leave_types_totals[key] 73 | end 74 | 75 | 76 | " 77 | Total 78 | #{leave_types_string.join('
')} 79 | 80 | #{leave_weights_string.join('
')} 81 | 82 | 83 | " 84 | 85 | end 86 | 87 | 88 | end -%> 89 | 90 | 91 | -------------------------------------------------------------------------------- /app/controllers/user_leaves_controller.rb: -------------------------------------------------------------------------------- 1 | class UserLeavesController < ApplicationController 2 | unloadable 3 | 4 | include UserLeaveReportsHelper 5 | include UserLeavesHelper 6 | 7 | def new 8 | if !(!mark_leave_users.include?(User.current) && !mark_own_leave_users.include?(User.current)) 9 | @user_leave = UserLeave.new 10 | else 11 | return deny_access 12 | end 13 | end 14 | 15 | def create 16 | errors = [] 17 | selected_users = [] 18 | if params['create_user_leave']['selected_users'] 19 | selected_users = params['create_user_leave']['selected_users'] 20 | else 21 | selected_users = User.active.joins(:groups). 22 | where("#{User.table_name_prefix}groups_users#{User.table_name_suffix}.id" => params['create_user_leave']['selected_groups']).map(&:id) 23 | end 24 | if params['create_user_leave']['selected_date_from'].blank? || 25 | params['create_user_leave']['selected_date_to'].blank? 26 | errors << "Date Field(s) cannot be empty!" 27 | else 28 | begin 29 | selected_date_from = params['create_user_leave']['selected_date_from'].to_date#.map{|k,v| v}.join("-").to_date 30 | selected_date_to = params['create_user_leave']['selected_date_to'].to_date#.map{|k,v| v}.join("-").to_date 31 | rescue 32 | errors << "Invalid Date Format!" 33 | end 34 | end 35 | selected_users = check_selected_users(selected_users) 36 | notices = [] 37 | if !selected_users.empty? && errors.empty? 38 | selected_users = selected_users.uniq 39 | selected_users.each do |user| 40 | leave_date = selected_date_from 41 | while leave_date <= selected_date_to 42 | user_leave = UserLeave.new(user_id: user.to_i, leave_type: params['create_user_leave']['selected_leave'], 43 | leave_date: leave_date, comments: params['create_user_leave']['comments'], 44 | fractional_leave: params['create_user_leave']['fractional_leave']) 45 | leave_date += 1 46 | unless user_leave.save 47 | errors << t(:error_leave_add, user_name: user_leave.user.name, leave_type: user_leave.leave_type, 48 | leave_date: user_leave.leave_date, reason: user_leave.errors.full_messages.join('
')) 49 | else 50 | total_yearly_leaves = UserLeave.where(user_id: user, leave_type: user_leave.leave_type).where("leave_date >= ?", Date.today.beginning_of_year).sum(:fractional_leave) 51 | notices << t(:notice_leave_add, user_name: user_leave.user.name, 52 | leave_type: user_leave.leave_type, 53 | leave_date: user_leave.leave_date, total_yearly_leaves: total_yearly_leaves) 54 | end 55 | end 56 | end 57 | errors = errors.flatten.uniq 58 | notices = notices.flatten.uniq 59 | unless errors.blank? 60 | flash.now[:notice] = "#{notices.join('
')}" 61 | flash.now[:error] = "#{errors.join('
')}" 62 | render 'new' 63 | else 64 | redirect_to user_leave_reports_path, notice: "#{notices.join('
')}" 65 | end 66 | else 67 | flash.now[:error] = t(:error_no_user_group_selected) 68 | render 'new' 69 | end 70 | end 71 | 72 | def edit 73 | @user_leave = UserLeave.find(params[:id]) 74 | end 75 | 76 | def update 77 | @user_leave = UserLeave.find(params[:id]) 78 | if @user_leave.update_attributes(params.require(:user_leave).permit!) 79 | redirect_to edit_user_leafe_path(@user_leave), notice: t(:notice_leaves_updated) 80 | else 81 | redirect_to edit_user_leafe_path(@user_leave), error: t(:error_leaves_not_updated) 82 | end 83 | end 84 | 85 | def destroy 86 | @user_leave = UserLeave.find(params[:id]) 87 | @user_leave.destroy 88 | respond_to do |format| 89 | format.js {} 90 | end 91 | end 92 | end 93 | 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Redmine Leaves 2 | ============== 3 | 4 | A simple plugin for user leaves management. It allows user to check-in and check-out for attendance marking. 5 | Leave is marked for any user that does not check-in by the specified time. 6 | Administrator can define leave types via the administrative interface for the plugin. 7 | 8 | Users with permission 'eligible_for_leave' are shown in the list. 9 | A report summary page similar to the Timesheet Plugin shows the leaves summary. 10 | Leaves are also integrated into the Calendar Module. 11 | User who does not check-in, is automatically marked as off after some cut-off period Leave Types are defined through Redmine settings. 12 | User can also view the check-ins/check-outs and allowed users can edit attendance too. 13 | Also, attendance can be imported in the form of csv files. 14 | 15 | Redmine 3+ compatible. 16 | 17 | Installation: 18 | ------------- 19 | 20 | - To install plugin, go to plugins folder of your Redmine repository and run: 21 | 22 | git clone http://github.com/arkhitech/redmine_leaves 23 | 24 | - Run db migrations for the plugin 25 | 26 | rake redmine:plugins:migrate RAILS_ENV=production 27 | 28 | - Run rake task to populate time_spent field in user_time_checks 29 | 30 | RAILS_ENV=production rake redmine_leaves:populate_time_spent 31 | 32 | - Bundle install all the gems using the following command 33 | 34 | bundle install 35 | 36 | - After bundle install, go to Redmine leave plugin folder and run the following command 37 | 38 | wheneverize . 39 | 40 | - Above command creates a file “schedule.rb” in config folder. Go to config/schedule.rb and type the following code 41 | 42 | every :weekday, :at => '12:01 pm' do 43 | rake "redmine_leaves:auto_leave_mark" 44 | end 45 | 46 | - Run the following command to automate the leave marking process 47 | 48 | whenever --update-crontab store 49 | 50 | - After installation, log in to Redmine as administrator and go to plugin settings for Redmine leaves plugin configuration. 51 | 52 | Instructions: 53 | ------------- 54 | 55 | - After installing Redmine leave plugin, it is advised to make Redmine groups and add users into these groups. This will allow the admin to assign permissions to users accordingly. 56 | 57 | - Admin will be required to always update the "Auto-Mark" process whenever the server restarts. To do so, go to congif/schedule.rb and run the following command 58 | 59 | whenever --update-crontab store 60 | 61 | Snapshots: 62 | ------------- 63 | 64 | ![alt tag](http://arkhitech.com/sites/default/files/1-redmine_login.png) 65 | ![alt tag](http://arkhitech.com/sites/default/files/2-redmine_check-in.png) 66 | ![alt tag](http://arkhitech.com/sites/default/files/3-user_time_check_topmenu.png) 67 | ![alt tag](http://arkhitech.com/sites/default/files/4-browse_import_csv_file.png) 68 | ![alt tag](http://arkhitech.com/sites/default/files/5-edit-attendance.png) 69 | ![alt tag](http://arkhitech.com/sites/default/files/6-invalid_values_for_attendance_edit.png) 70 | ![alt tag](http://arkhitech.com/sites/default/files/7-attendance_time_value_%20updated.png) 71 | ![alt tag](http://arkhitech.com/sites/default/files/8-redmine_leaves_overview_sub_menu.png) 72 | ![alt tag](http://arkhitech.com/sites/default/files/9-redmine_leaves_summary_report.png) 73 | ![alt tag](http://arkhitech.com/sites/default/files/10.1-redmine_leaves_add_leave_submenu.png) 74 | ![alt tag](http://arkhitech.com/sites/default/files/10.2-redmine_leaves_leave_added_success.png) 75 | ![alt tag](http://arkhitech.com/sites/default/files/11-redmine_leaves_analytics_submenu.png) 76 | ![alt tag](http://arkhitech.com/sites/default/files/13-redmine_leaves_log_remaining_time.png) 77 | ![alt tag](http://arkhitech.com/sites/default/files/14-redmine_leaves_check_out_success.png) 78 | ![alt tag](http://arkhitech.com/sites/default/files/15-redmine_leaves_administration_plugins_redmine_leaves_configure.png) 79 | ![alt tag](http://arkhitech.com/sites/default/files/16-redmine_leaves_settings.png) 80 | -------------------------------------------------------------------------------- /app/views/user_time_checks/user_time_activity_report.erb: -------------------------------------------------------------------------------- 1 | <%= render template: "layouts/user_time_analytics" %> 2 |
3 |

User Time Activity Report

4 | <% if User.current.allowed_to_globally?(:view_time_reports,{}) -%> 5 | 6 | <%if @all_trackers%> 7 | <%= form_tag controller: 'user_time_checks', action: 'user_time_activity_report' do %> 8 |
<%= t(:label_filter_plural) %> 9 | 10 | 14 | 18 |
<%= "Date From:"%> <%= text_field_tag "date_from", 11 | (params[:date_from]) || Date.today - 1.month %> 12 | <%= calendar_for 'date_from' %> 13 |
<%=" Date To:" %> <%= text_field_tag "date_to", 15 | (params[:date_to]) ||Date.today %> 16 | <%= calendar_for 'date_to' %> 17 |
<%= submit_tag 'Apply' %>
19 |
20 |

21 |
22 | 23 | 24 | 25 | 29 | 30 | 31 | <%# unless @trackers.nil? || @trackers.empty? %> 32 | <% @all_trackers.each do |tracker|%> 33 | <%# @trackers.each do |tracker| %> 34 | 37 | 40 | 43 | <%end%> 44 | <%#end%> 45 | 46 | 47 | 48 | <% @user_stats.each_pair do |user_id, user_stats| %> 49 | " > 50 | <%user = user_stats[:user]%> 51 | 54 | 62 | <% @all_trackers.each do |tracker|%> 63 | <%#@trackers.each do |tracker|%> 64 | 72 | 73 | 80 | 81 | 88 | <%end%> 89 | 90 | <%end%> 91 | 92 |
26 | <%= "USER NAME" %> 27 | 28 | <%= "MISSED DUE DATES" %> 35 | #<%= " #{tracker} HANDLED"%> 36 | 38 | <%= "ACTUAL TIME FOR #{tracker}" %> 39 | 41 | <%= "TIME SPENT ON #{tracker}" %> 42 |
52 | <%= "#{user.firstname} #{user.lastname}"%> 53 | 55 | <% t = user_stats[:missed_due_dates] %> 56 | <% if t %> 57 | <%= t.missed_dates %> 58 | <% else %> 59 | 0 60 | <% end %> 61 | 65 | <% t = user_stats[:trackers][tracker] %> 66 | <% if t %> 67 | <%=t.num_of_trackers %> 68 | <% else %> 69 | 0 70 | <% end %> 71 | 74 | <% if t %> 75 | <%= t.estimated_hours_on_tracker.round(2) %> 76 | <% else %> 77 | 0 78 | <% end %> 79 | 82 | <% if t %> 83 | <%= t.time_spent.round(2) %> 84 | <% else %> 85 | 0 86 | <% end %> 87 |
93 |
94 | 95 | <% end %><%#form end%> 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | <%else%> 105 | 106 |

No data to view

107 | Back 108 | <%end%> 109 | 110 | 111 | 112 | <%else%> 113 | 114 |

You do not have permission to view reports

115 | Back 116 | <%end%> 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /spec/features/user_time_checks_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "UserTimeChecks" do 4 | #works for all users to get them login 5 | before (:all) do 6 | @user=User.find_by_login('testuser') 7 | @user ||= begin 8 | user = User.new(firstname: "test", lastname: "user", mail: "test@user.com") 9 | user.login = "testuser" 10 | user.password = "testuser" 11 | user.save! 12 | user 13 | end 14 | 15 | end 16 | 17 | 18 | 19 | describe "Check In Page" do 20 | #Used to check the Check-in and Refresh Functionality 21 | before :each do 22 | User.stubs(:current).returns(@user) 23 | end 24 | 25 | it "should have the content 'Check-in time:'" do 26 | visit '/user_time_checks/check_in' 27 | expect(page).to have_content('Check-in time:') 28 | 29 | end 30 | it "Should Display error flash message if Check-in is Refreshed [Pressed Twice]" do 31 | visit("/") 32 | click_link("CHECK-IN") 33 | visit '/user_time_checks/check_in' 34 | expect(page).to have_content('You did not check-out last time, please check-out first') 35 | end 36 | end 37 | 38 | describe "Check Out Page" do 39 | 40 | #Used to check the Check-out and Refresh Functionality 41 | before :each do 42 | User.stubs(:current).returns(@user) 43 | 44 | @user_time_check ||= begin 45 | user_time_check = UserTimeCheck.new(user_id: User.current.id, check_in_time: Time.now - 8.hours) 46 | user_time_check.save! 47 | user_time_check 48 | end 49 | 50 | 51 | end 52 | 53 | it "should have the content 'Check-Out'" do 54 | visit '/user_time_checks/check_out' 55 | expect(page).to have_content('Check-Out') 56 | end 57 | 58 | it "should have the content 'Check-out time:'" do 59 | visit '/user_time_checks/check_out' 60 | expect(page).to have_content('Check-out time:') 61 | end 62 | 63 | it "Should display error flash message if Check-out is Refreshed [Pressed Twice]" do 64 | visit("/") 65 | click_link("CHECK-OUT") 66 | visit '/user_time_checks/check_out' 67 | expect(page).to have_content('You did not check-in last time, please check-in first') 68 | end 69 | 70 | before do 71 | time_entries = TimeEntry.where(user_id: User.current.id , 72 | spent_on: [@user_time_check.check_in_time, @user_time_check.check_in_time + 8.hours]) 73 | time_entries.destroy_all 74 | end 75 | 76 | it "Should display error Flash meesage when Logged Time is less than Checked Time" do 77 | visit("/") 78 | click_link("CHECK-OUT") 79 | #visit '/user_time_checks/check_out' 80 | expect(page).to have_content('Your logged in time is less than required Percentage. Log your remaining time') 81 | click_button('Add') 82 | visit '/user_time_checks/checkout_timelog_success' 83 | end 84 | 85 | 86 | 87 | end 88 | 89 | # describe "Check Out Time Log Success" do 90 | # 91 | # before :each do 92 | # User.stubs(:current).returns(@user) 93 | # 94 | # @user_time_check ||= begin 95 | # user_time_check = UserTimeCheck.new(user_id: User.current.id, check_in_time: Time.now - 8.hours,check_out_time: Time.now+1.hours) 96 | # user_time_check.save! 97 | # user_time_check 98 | # end 99 | # 100 | # time_entries = TimeEntry.where(user_id: User.current.id , 101 | # spent_on: [@user_time_check.check_in_time, @user_time_check.check_in_time + 8.hours]) 102 | # 103 | # end 104 | # 105 | # it "Should display 'Time Logged Successfully' " do 106 | ## visit("/") 107 | ## click_link("CHECK-OUT") 108 | ## visit '/user_time_checks/check_out' 109 | ## render :action => "create_time_entries" 110 | ## click_button('Add') 111 | ## visit '/user_time_checks/checkout_timelog_success' 112 | # expect(view).to render_template("/user_time_checks/checkout_timelog_success") 113 | # expect(page).to have_content('Time Logged Successfully') 114 | # 115 | # end 116 | # 117 | # end 118 | 119 | 120 | 121 | end 122 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actionmailer (4.0.1) 5 | actionpack (= 4.0.1) 6 | mail (~> 2.5.4) 7 | actionpack (4.0.1) 8 | activesupport (= 4.0.1) 9 | builder (~> 3.1.0) 10 | erubis (~> 2.7.0) 11 | rack (~> 1.5.2) 12 | rack-test (~> 0.6.2) 13 | activemodel (4.0.1) 14 | activesupport (= 4.0.1) 15 | builder (~> 3.1.0) 16 | activerecord (4.0.1) 17 | activemodel (= 4.0.1) 18 | activerecord-deprecated_finders (~> 1.0.2) 19 | activesupport (= 4.0.1) 20 | arel (~> 4.0.0) 21 | activerecord-deprecated_finders (1.0.3) 22 | activesupport (4.0.1) 23 | i18n (~> 0.6, >= 0.6.4) 24 | minitest (~> 4.2) 25 | multi_json (~> 1.3) 26 | thread_safe (~> 0.1) 27 | tzinfo (~> 0.3.37) 28 | arel (4.0.1) 29 | atomic (1.1.14) 30 | builder (3.1.4) 31 | business_time (0.6.2) 32 | activesupport (>= 3.1.0) 33 | tzinfo (~> 0.3.31) 34 | celluloid (0.15.2) 35 | timers (~> 1.1.0) 36 | childprocess (0.3.6) 37 | ffi (~> 1.0, >= 1.0.6) 38 | chronic (0.10.2) 39 | coderay (1.1.0) 40 | diff-lcs (1.2.5) 41 | erubis (2.7.0) 42 | factory_girl (4.3.0) 43 | activesupport (>= 3.0.0) 44 | factory_girl_rails (4.3.0) 45 | factory_girl (~> 4.3.0) 46 | railties (>= 3.0.0) 47 | ffi (1.9.3) 48 | formatador (0.2.4) 49 | guard (2.2.4) 50 | formatador (>= 0.2.4) 51 | listen (~> 2.1) 52 | lumberjack (~> 1.0) 53 | pry (>= 0.9.12) 54 | thor (>= 0.18.1) 55 | guard-rspec (2.5.0) 56 | guard (>= 1.1) 57 | rspec (~> 2.11) 58 | guard-spork (1.5.0) 59 | childprocess (>= 0.2.3) 60 | guard (>= 1.1) 61 | spork (>= 0.8.4) 62 | hike (1.2.3) 63 | i18n (0.6.5) 64 | listen (2.2.0) 65 | celluloid (>= 0.15.2) 66 | rb-fsevent (>= 0.9.3) 67 | rb-inotify (>= 0.9) 68 | lumberjack (1.0.4) 69 | mail (2.5.4) 70 | mime-types (~> 1.16) 71 | treetop (~> 1.4.8) 72 | method_source (0.8.2) 73 | mime-types (1.25.1) 74 | minitest (4.7.5) 75 | multi_json (1.8.2) 76 | polyglot (0.3.3) 77 | pry (0.9.12.4) 78 | coderay (~> 1.0) 79 | method_source (~> 0.8) 80 | slop (~> 3.4) 81 | rack (1.5.2) 82 | rack-test (0.6.2) 83 | rack (>= 1.0) 84 | rails (4.0.1) 85 | actionmailer (= 4.0.1) 86 | actionpack (= 4.0.1) 87 | activerecord (= 4.0.1) 88 | activesupport (= 4.0.1) 89 | bundler (>= 1.3.0, < 2.0) 90 | railties (= 4.0.1) 91 | sprockets-rails (~> 2.0.0) 92 | railties (4.0.1) 93 | actionpack (= 4.0.1) 94 | activesupport (= 4.0.1) 95 | rake (>= 0.8.7) 96 | thor (>= 0.18.1, < 2.0) 97 | rake (10.1.0) 98 | rb-fsevent (0.9.3) 99 | rb-inotify (0.9.2) 100 | ffi (>= 0.5.0) 101 | rspec (2.13.0) 102 | rspec-core (~> 2.13.0) 103 | rspec-expectations (~> 2.13.0) 104 | rspec-mocks (~> 2.13.0) 105 | rspec-core (2.13.1) 106 | rspec-expectations (2.13.0) 107 | diff-lcs (>= 1.1.3, < 2.0) 108 | rspec-mocks (2.13.1) 109 | rspec-rails (2.13.1) 110 | actionpack (>= 3.0) 111 | activesupport (>= 3.0) 112 | railties (>= 3.0) 113 | rspec-core (~> 2.13.0) 114 | rspec-expectations (~> 2.13.0) 115 | rspec-mocks (~> 2.13.0) 116 | slop (3.4.7) 117 | spork (1.0.0rc4) 118 | spork-rails (4.0.0) 119 | rails (>= 3.0.0, < 5) 120 | spork (>= 1.0rc0) 121 | sprockets (2.10.1) 122 | hike (~> 1.2) 123 | multi_json (~> 1.0) 124 | rack (~> 1.0) 125 | tilt (~> 1.1, != 1.3.0) 126 | sprockets-rails (2.0.1) 127 | actionpack (>= 3.0) 128 | activesupport (>= 3.0) 129 | sprockets (~> 2.8) 130 | thor (0.18.1) 131 | thread_safe (0.1.3) 132 | atomic 133 | tilt (1.4.1) 134 | timers (1.1.0) 135 | treetop (1.4.15) 136 | polyglot 137 | polyglot (>= 0.3.1) 138 | tzinfo (0.3.38) 139 | whenever (>= 0.8.4) 140 | activesupport (>= 2.3.4) 141 | chronic (>= 0.6.3) 142 | 143 | PLATFORMS 144 | ruby 145 | 146 | DEPENDENCIES 147 | business_time 148 | childprocess 149 | factory_girl_rails 150 | guard-rspec 151 | guard-spork 152 | rspec-rails 153 | spork-rails 154 | whenever (>= 0.8.4) 155 | -------------------------------------------------------------------------------- /app/views/user_time_checks/user_time_activity_report_monthly.erb: -------------------------------------------------------------------------------- 1 | <%= render template: "layouts/user_time_analytics" %> 2 |
3 |

User Time Activity Monthly Report

4 | <% if User.current.allowed_to_globally?(:view_time_reports,{}) -%> 5 | <%if @all_trackers%> 6 | <%= form_tag controller: 'user_time_checks', action: 'user_time_activity_report_monthly' do %> 7 |
<%= t(:label_filter_plural) %> 8 | 9 | 13 | 17 | 18 | 19 |
<%= "Date From:"%> <%= text_field_tag "date_from", 10 | (params[:date_from]) || Date.today - 1.month %> 11 | <%= calendar_for 'date_from' %> 12 |
<%=" Date To:" %> <%= text_field_tag "date_to", 14 | (params[:date_to]) ||Date.today %> 15 | <%= calendar_for 'date_to' %> 16 |
<%= submit_tag 'Apply' %>
20 |
21 |

22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | <%# unless @trackers.nil? || @trackers.empty? %> 31 | <%# @trackers.each do |tracker| %> 32 | <% @all_trackers.each do |tracker|%> 33 | 34 | 35 | 38 | 39 | <%end%> 40 | <%#end%> 41 | 42 | 43 | 44 | <% unless @months_and_years.nil? || @months_and_years.empty? %> 45 | <% @months_and_years.each do |user| %> 46 | 47 | 50 | 58 | 61 | 64 | <% @all_trackers.each do |tracker|%> 65 | <%# @trackers.each do |tracker| %> 66 | 75 | 76 | 83 | 84 | 91 | <%end%><%#end of all trackers loop%> 92 | 93 | <%end%> 94 | <% end %><%#end of unless%> 95 | 96 |
<%= "User Name " %><%= "Missed due dates " %><%= "Month " %><%= "Year " %><%= " Num of "+tracker+" Handled"%> 36 | <%= "ACTUAL TIME FOR #{tracker}" %> 37 | <%= "Time Spent on "+tracker %>
48 | <%= user.user_id%> 49 | 51 | <% a = @missed_dates[user.user_id.to_s+user.month.to_s+user.year.to_s].first %> 52 | <% if a %> 53 | <%=+a.missed_dates %> 54 | <% else %> 55 | 0 56 | <% end %> 57 | 59 | <%= user.month%> 60 | 62 | <%= user.year%> 63 | 67 | 68 | <% t = @time_spent_on_tracker[tracker+user.user_id.to_s+user.month.to_s+user.year.to_s].first %> 69 | <% if t %> 70 | <%=t.num_of_trackers %> 71 | <% else %> 72 | 0 73 | <% end %> 74 | 77 | <% if t %> 78 | <%= t.estimated_hours_on_tracker.round(2) %> 79 | <% else %> 80 | 0 81 | <% end %> 82 | 85 | <% if t %> 86 | <%= t.time_spent %> 87 | <% else %> 88 | 0 89 | <% end %> 90 |
97 | 98 | <% end %> 99 | 100 | 101 | 102 | 103 | <%else%> 104 | 105 |

No data to view

106 | Back 107 | <%end%> 108 | 109 | 110 | 111 | <%else%> 112 | 113 |

You do not have permission to view reports

114 | Back 115 | <%end%> 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 | -------------------------------------------------------------------------------- /app/controllers/user_leave_analytics_controller.rb: -------------------------------------------------------------------------------- 1 | class UserLeaveAnalyticsController < ApplicationController 2 | unloadable 3 | 4 | include UserLeaveAnalyticsHelper 5 | def report 6 | 7 | start_date = params[:user_leave_analytic] && params[:user_leave_analytic][:date_from].presence || Date.today.beginning_of_year 8 | end_date = params[:user_leave_analytic] && params[:user_leave_analytic][:date_to].presence || Date.today 9 | user_id = params[:user_leave_analytic] && params[:user_leave_analytic][:selected_user] || User.current.id 10 | @user = User.find(user_id) 11 | flash_message = "" 12 | 13 | @bar3 = LazyHighCharts::HighChart.new('graph') do |f| 14 | f.title({ text: "Overall Leaves Taken/Given - Bar Chart"}) 15 | f.options[:xAxis][:categories] = ['Leave Type'] 16 | all_leave_types.each do |leave| 17 | f.series(type: 'column',name: leave,data: [leaves_count_for(User.active, leave, start_date, end_date)]) 18 | end 19 | end 20 | result = 0 21 | @bar3.series_data.each do |data| 22 | result += data[:data].first 23 | end 24 | flash_message ="No Results Found for Overall Leaves Taken/Given
" if result==0 25 | 26 | @pie3 = LazyHighCharts::HighChart.new('pie') do |f| 27 | f.chart({defaultSeriesType: "pie" , margin: [50, 50, 50, 50]} ) 28 | series = { 29 | type: 'pie', 30 | name: 'Group Leave Types', 31 | data: populate_pie_chart_for_user(User.active, start_date,end_date) 32 | } 33 | f.series(series) 34 | f.options[:title][:text] = "Overall Leaves Taken/Given - Pie Chart" 35 | f.legend(layout: 'vertical',style: {left: 'auto', bottom: 'auto',right: 'auto',top: '100px'}) 36 | f.plot_options(pie:{ 37 | allowPointSelect: true, 38 | cursor: "pointer" , 39 | dataLabels:{ 40 | enabled: true, 41 | color: "black", 42 | style:{ 43 | font: "13px Trebuchet MS, Verdana, sans-serif" 44 | } 45 | } 46 | }) 47 | end 48 | 49 | @bar2 = LazyHighCharts::HighChart.new('graph') do |f| 50 | f.title({ text: "Leaves taken by Groups - Bar Chart"}) 51 | f.options[:xAxis][:categories] = Group.all.collect{|g| g.name} 52 | all_leave_types.each do |leave| 53 | f.series(type: 'column',name: leave,data: group_leaves(leave, start_date, end_date)) 54 | end 55 | end 56 | result = 0 57 | @bar2.series_data.each do |data| 58 | result += data[:data].first 59 | end 60 | flash_message +="No Results Found for Leaves taken by Groups
" if result==0 61 | 62 | @pie2 = LazyHighCharts::HighChart.new('pie') do |f| 63 | f.chart({defaultSeriesType: "pie" , margin: [50, 50, 50, 50]} ) 64 | series = { 65 | type: 'pie', 66 | name: 'Group Leaves', 67 | data: populate_pie_chart_for_group(start_date,end_date) 68 | } 69 | f.series(series) 70 | f.options[:title][:text] = "Leaves taken by Groups - Pie Chart" 71 | f.legend(layout: 'vertical',style: {left: 'auto', bottom: 'auto',right: 'auto',top: '100px'}) 72 | f.plot_options(pie:{ 73 | allowPointSelect: true, 74 | cursor: "pointer", 75 | dataLabels:{ 76 | enabled: true, 77 | color: "black", 78 | style:{ 79 | font: "13px Trebuchet MS, Verdana, sans-serif" 80 | } 81 | } 82 | }) 83 | end 84 | 85 | @bar1 = LazyHighCharts::HighChart.new('graph') do |f| 86 | f.title({ text: "Type of Leaves taken by #{@user.name} - Bar Chart"}) 87 | f.options[:xAxis][:categories] = ['Leave Type'] 88 | 89 | all_leave_types.each do |leave| 90 | f.series(type: 'column',name: leave,data: [leaves_count_for(@user.id, leave, start_date, end_date)]) 91 | end 92 | end 93 | result = 0 94 | @bar1.series_data.each do |data| 95 | result += data[:data].first 96 | end 97 | flash_message +="No Results Found for Type of Leaves taken by #{@user.name}" if result==0 98 | 99 | @pie1 = LazyHighCharts::HighChart.new('pie') do |f| 100 | f.chart({defaultSeriesType: "pie" , margin: [50, 50, 50, 50]} ) 101 | series = { 102 | type: 'pie', 103 | name: 'User Leave Types', 104 | data: populate_pie_chart_for_user(@user.id, start_date,end_date) 105 | } 106 | f.series(series) 107 | f.options[:title][:text] = "Type of Leaves taken by #{@user.name} - Pie Chart" 108 | f.legend(layout: 'vertical',style: {left: 'auto', bottom: 'auto',right: 'auto',top: '100px'}) 109 | f.plot_options(pie:{ 110 | allowPointSelect: true, 111 | cursor: "pointer" , 112 | dataLabels:{ 113 | enabled: true, 114 | color: "black", 115 | style:{ 116 | font: "13px Trebuchet MS, Verdana, sans-serif" 117 | } 118 | } 119 | }) 120 | end 121 | flash.now[:warning] = flash_message unless flash_message.blank? 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /app/views/settings/_settings.html.erb: -------------------------------------------------------------------------------- 1 |
2 | Leave Type Settings 3 | 4 | 5 | 12 | 13 | 20 | 21 | 35 | 36 |
6 | 7 | <%= t(:label_leave_types)%> 8 | 9 | 10 |
<%= text_field_tag('settings[leave_types]', settings['leave_types']) %>
<%= t(:label_multiple_leaves_instructions )%>
11 |
14 | 15 | 16 | <%= t(:label_default_type ) %> 17 | 18 |
<%= text_field_tag('settings[default_type]', settings['default_type']) %>
19 |
22 | 23 | 24 | <%= t(:label_eligible_for_leave_groups ) %> 25 | 26 | 27 | 32 | 33 |
<%= select_tag('settings[eligible_for_leave_groups]', 28 | options_from_collection_for_select(Group.all, :id, :name, 29 | settings['eligible_for_leave_groups']), 30 | multiple: true) %> 31 |
34 |
37 | 38 |
39 | 40 |
41 | Permission Settings 42 | 43 | 44 | 72 | 100 | 101 |
45 | 46 | Mark 47 | 48 | 57 | 58 | 68 | 69 | 70 |
49 | <%= t(:label_mark_leaves ) %> 50 | 51 | <%= select_tag('settings[mark_leaves]', 52 | options_from_collection_for_select(Group.all, :id, :name, 53 | settings['mark_leaves']), 54 | multiple: true) %> 55 |
56 |
59 | <%= t(:label_mark_own_leave ) %> 60 | 61 | 62 | <%= select_tag('settings[mark_own_leave]', 63 | options_from_collection_for_select(Group.all, :id, :name, 64 | settings['mark_own_leave']), 65 | multiple: true) %> 66 |
67 |
71 |
73 | 74 | Edit 75 | 76 | 85 | 86 | 96 | 97 | 98 |
77 | <%= t(:label_edit_attendance) %> 78 | 79 | <%= select_tag('settings[edit_attendance]', 80 | options_from_collection_for_select(Group.all, :id, :name, 81 | settings['edit_attendance']), 82 | multiple: true) %> 83 |
84 |
87 | <%= t(:label_edit_own_attendance) %> 88 | 89 | 90 | <%= select_tag('settings[edit_own_attendance]', 91 | options_from_collection_for_select(Group.all, :id, :name, 92 | settings['edit_own_attendance']), 93 | multiple: true) %> 94 |
95 |
99 |
102 |
103 | 104 |
105 | Reminder Settings 106 | 107 | 108 | 117 | 126 | 135 | 136 |
109 | <%= t(:label_daily_mail_reminder_cc) %> 110 | 111 |
<%= select_tag('settings[daily_reminder]', 112 | options_from_collection_for_select(Group.all, :id, :name, 113 | settings['daily_reminder']), 114 | multiple: true) %> 115 |
116 |
118 | <%= t(:label_weekly_mail_reminder_cc) %> 119 | 120 |
<%= select_tag('settings[weekly_reminder]', 121 | options_from_collection_for_select(Group.all, :id, :name, 122 | settings['weekly_reminder']), 123 | multiple: true) %> 124 |
125 |
127 | <%= t(:label_monthly_mail_reminder_cc) %> 128 | 129 |
<%= select_tag('settings[monthly_reminder]', 130 | options_from_collection_for_select(Group.all, :id, :name, 131 | settings['monthly_reminder']), 132 | multiple: true) %> 133 |
134 |
137 |
138 | 139 |
140 | Reminder Settings 141 |

142 | 143 | <%= select_tag('settings[cc_roles]', 144 | options_for_select(Role.all.collect{|r| [r.name, r.id]}, 145 | @settings['cc_roles'])) %> 146 |

147 |
148 | 149 |
150 | Time Activity Report Settings 151 | 152 | 153 | 160 | 161 | 162 | 163 | 164 | 165 |
166 | Time Activity Report Settings 167 |

168 | 169 | <%= select_tag('settings[time_loggers_group]', 170 | options_from_collection_for_select(Group.all, :id, :name, 171 | settings['time_loggers_group']), 172 | multiple: false) %> 173 |

174 | 175 |

176 | 177 | <%= select_tag('settings[time_log_receivers_group]', 178 | options_from_collection_for_select(Group.all, :id, :name, 179 | settings['time_log_receivers_group']), 180 | multiple: false) %> 181 |

182 | 183 |

184 | 185 | <%= select_tag('settings[cc_roles]', 186 | options_for_select(Role.all.collect{|r| [r.name, r.id]}, 187 | @settings['cc_roles'])) %> 188 |

189 | 190 |

191 | 192 | <%= select_tag('settings[num_min_working_hours]', 193 | options_for_select(0..10, settings['num_min_working_hours'].to_i)) %> 194 |

195 | 196 |

197 | 198 | <%= select_tag('settings[max_past_timelog_insert_days]', 199 | options_for_select(0..30, @settings['max_past_timelog_insert_days'].to_i)) %> 200 |

201 |
202 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # English strings go here for Rails i18n 2 | en: 3 | #pagination in redmine time checks 4 | wice_grid: 5 | previous_label: « 6 | next_label: » 7 | 8 | #Main Menu 9 | caption_top_menu_check_in: "CHECK-IN" 10 | caption_top_menu_check_out: "CHECK-OUT" 11 | caption_leave_report: "Leaves" 12 | caption_user_time_check: "User Time Check" 13 | 14 | #user_leave_report/index 15 | title_leave_summary: " Leave Summary" 16 | title_leave_analytics: "Leave Analytics" 17 | 18 | #user_leave_report/_options 19 | link_add_leave: "Add Leave" 20 | label_date_from: "From" 21 | label_date_to: "To" 22 | label_sortby: "Sort By" 23 | label_user: "User" 24 | label_leave_types: "Leave Types" 25 | label_group: "Group" 26 | label_button_apply: "Apply" 27 | options_group_by_title: "Leave Type" 28 | option_group_by_user: "User" 29 | option_group_by_date: "Date" 30 | 31 | #user_leave_report/report 32 | label_name: "User Name" 33 | label_leave_type: "Leave Type" 34 | label_leave_date: "Leave Date" 35 | label_weight: "Weight" 36 | label_comments: "Comments" 37 | label_action: "Action" 38 | label_confirm: "Are you sure?" 39 | link_edit: "Edit" 40 | link_delete: "Delete" 41 | 42 | #user_leaves/new 43 | label_add_leave: "Add Leave" 44 | label_user_group: "User/Group" 45 | label_optional: "(Optional)" 46 | label_fractional_leave: "Fractional Leave" 47 | label_fractional_leave_optional: "(Optional:only required for half leaves or earned leaves [use negative value for earned])" 48 | label_button_add: "Add" 49 | 50 | #user_leaves/destroy 51 | label_deleted: "Leave has been deleted successfully!" 52 | #user_leaves/edit 53 | label_update_leave: "Update Leave" 54 | label_button_update: "Update" 55 | 56 | #leave_mailer/notify_absentee.html/.txt 57 | text_notify_your_leave_for: "%{fraction} %{leave_type}(%{total_yearly_leaves}) leave on %{leave_date} has been marked for %{user_name} with comments: %{comments}" 58 | text_notify_your_leave_for_html: "%{fraction} %{leave_type}(%{total_yearly_leaves}) leave on %{leave_date} has been marked for %{user_name} with comments:
%{comments}" 59 | 60 | 61 | #log_time_reminder_mailer/reminder_email.html/.txt 62 | text_email_remainder_time: "Time Log Reminder for Issue# " 63 | text_email_assignee: "Assignee:" 64 | text_email_issue_inform: "This is to inform you that you have not logged time for Issue# " 65 | text_email_log_request: "Kindly log time for this issue." 66 | 67 | #settings/ 68 | label_leave_types: "Leave Type(s)" 69 | label_multiple_leaves_instructions: " For multiple Leave Types, enter values separated by comma (,)" 70 | label_default_type: "Default Type" 71 | label_eligible_for_leave_groups: "Eligible For Leave Group(s)" 72 | label_mark_leaves: "Mark Leaves" 73 | label_mark_own_leave: "Mark Own Leave" 74 | label_daily_mail_reminder_cc: "Daily Mail Reminder CC" 75 | label_weekly_mail_reminder_cc: "Weekly Mail Reminder CC" 76 | label_monthly_mail_reminder_cc: "Monthly Mail Reminder CC" 77 | label_edit_attendance: "Edit Attendance" 78 | label_edit_own_attendance: "Edit Own Attendance" 79 | 80 | #user_timecheks/index.html 81 | label_attendance_summary: "Attendance Summary" 82 | label_import_csv: "Import CSV" 83 | label_button_import: "Import" 84 | label_file_note: "NOTE: File should be headerless, tab separated and column sequence should be [S.No. DATE Check-In Check-Out NAME ...]" 85 | error_no_records_found: "No Records Found!" 86 | label_check_in_time: "Check-in Time" 87 | label_check_out_time: "Check-out Time" 88 | label_spent_time: "Spent Time" 89 | label_not_allowed: "Not Allowed" 90 | 91 | #user_timecheks/_availible_open_issues 92 | label_issue_no: "Issue # " 93 | label_tracker: "Tracker" 94 | label_status: "Status" 95 | label_subject: "Subject" 96 | label_hours_to_log: "Hours To Log" 97 | label_activity: "Activity" 98 | label_comments: "Comments" 99 | error_post_save: "Prohibited this post from being saved:" 100 | 101 | #user_timechecks/_check_out_info 102 | label_time_spent: "Time Spent: " 103 | 104 | #user_timechecks/_show_logged_time_entries 105 | label_time_logged: "Time Logged" 106 | label_activity_to_log: "Activity to log" 107 | 108 | 109 | #user_timechecks/check_in 110 | label_check_in: "Check-In" 111 | label_current_user_id: "Current User ID:" 112 | label_current_user_name: "Current User Name:" 113 | 114 | #user_timechecks/check_out 115 | label_total_checked_time_is: "Your Total Checked Time is" 116 | label_total_logged_time_is: "Your Total Logged Time is" 117 | label_hours_for_today: "Hours For Today" 118 | label_please_log_remaining: "Please Log Remaining" 119 | 120 | 121 | #user_timechecks/checkout_timelog_success 122 | label_time_logged_success: "Time Logged Successfully" 123 | label_check_out: "Check-Out" 124 | 125 | #user_timecheks/ edit 126 | 127 | 128 | #CONTROLLERS 129 | #user_leave_reports_controller 130 | error_no_params: "No parameters selected" 131 | error_invalid_date: "Invalid Date Format" 132 | error_no_results: "No Results Found!" 133 | error_from_greater_than_to: "From date can not be greater than to Date" 134 | 135 | #user_leaves_controller 136 | notice_leave_add: "%{leave_type}(%{total_yearly_leaves}) leave marked for %{user_name} for %{leave_date}" 137 | error_leave_add: "%{leave_type} leave for %{user_name} for %{leave_date} could not be added. Reason: %{reason}" 138 | 139 | error_no_user_group_selected: "No User/Group Selected!" 140 | error_invalid_user_group_selected: "Invalid User/Group Selected!" 141 | notice_leaves_updated: "User Leave Updated!" 142 | error_leaves_not_updated: "User Leave not Updated!" 143 | 144 | #user_time_checks_controller 145 | notice_time_check_updated: "User Time Check Updated" 146 | notice_time_check_file_imported: "User Time Checks Imported" 147 | 148 | error_invalid_file_format: "Invalid File Format!" 149 | error_checkout_first: "You did not check-out last time, please check-out first" 150 | error_checkin_first: "You did not check-in last time, please check-in first" 151 | error_less_time_logged: "Your logged in time is less than required Percentage. Log your remaining time" 152 | 153 | #MODELS 154 | #leave_mailer 155 | subject_leave_marked_for: "Leave - %{user_name} - %{fraction} %{leave_type}(%{total_yearly_leaves}) %{leave_date}" 156 | subject_project_timesheet: "Daily Timesheet Report - %{project} - %{report_date}" 157 | subject_group_timesheet: "Daily Timesheet Report - %{group} - %{report_date}" 158 | 159 | subject_missing_time_log: "Missing Time Log - %{user_name} - %{report_date}" 160 | 161 | body_time_log_hours_not_logged_html: "You have not logged your time for %{report_date}" 162 | body_time_log_hours_missing_html: "You have only logged %{logged_hours} for %{report_date}" 163 | body_affirm_missing_time_log_html: "Please update your time log to ensure time is logged completely. You will not be able to log time after three days and your leave will be marked." 164 | footer_missing_time_log: "Thank you for your co-operation." 165 | #log_time_remainder_mailer 166 | label_reminder_for_time_log: "Time Log Reminder for Issue#" 167 | 168 | #user_leave 169 | error_fractional_value_greater_than_one: ": Fractional Leave value can't be grater than 1" 170 | error_fractional_value_less_than_zero: ": Fractional Leave value can't be less than 0" 171 | 172 | #user_time_check 173 | auto_generated_comment: "Auto-Generated Check-Out" 174 | field_user_id: "User name" 175 | field_user_name: "User Name" 176 | field_check_out_time: "Check Out Time" 177 | field_check_in_time: "Check In Time" 178 | field_time_spent: "Time Spent" 179 | 180 | label_Timeentry_plural : "User Time Entries" 181 | label_Timereport: "User Time Report" 182 | label_filters: "Filters" 183 | label_trackers: "Trackers" 184 | label_tracker_instructions: "Enter trackers to display,separated by comma(,)" -------------------------------------------------------------------------------- /assets/stylesheets/wice_grid.css: -------------------------------------------------------------------------------- 1 | /* line 9, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 2 | .wg-detached-filter .text-filter-container input, .wice-grid .text-filter-container input { 3 | width: auto; 4 | margin-right: 10px; 5 | display: inline; 6 | } 7 | /* line 16, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves//stylesheets/wice_grid.css.scss */ 8 | .wg-detached-filter a.date-label, .wice-grid a.date-label { 9 | text-decoration: none; 10 | } 11 | /* line 17, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 12 | .wg-detached-filter a.date-label:hover, .wice-grid a.date-label:hover { 13 | text-decoration: line-through; 14 | } 15 | /* line 19, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 16 | .wg-detached-filter .clickable, .wice-grid .clickable { 17 | cursor: pointer; 18 | margin-bottom: 2px; 19 | margin-right: 2px; 20 | } 21 | /* line 25, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 22 | .wg-detached-filter .ui-datepicker-trigger, .wg-detached-filter .wg-detached-filter .ui-datepicker-trigger, .wice-grid .ui-datepicker-trigger, .wice-grid .wg-detached-filter .ui-datepicker-trigger { 23 | cursor: pointer; 24 | } 25 | /* line 31, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 26 | .wg-detached-filter .custom-dropdown-container .expand-multi-select-icon, .wg-detached-filter .custom-dropdown-container .collapse-multi-select-icon, .wice-grid .custom-dropdown-container .expand-multi-select-icon, .wice-grid .custom-dropdown-container .collapse-multi-select-icon { 27 | width: 10px; 28 | height: 10px; 29 | display: inline-block; 30 | margin-left: 5px; 31 | vertical-align: top; 32 | } 33 | /* line 39, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 34 | .wg-detached-filter .custom-dropdown-container .collapse-multi-select-icon, .wice-grid .custom-dropdown-container .collapse-multi-select-icon { 35 | background: transparent url(/plugin_assets/redmine_leaves/images/icons/grid/collapse.gif) no-repeat; 36 | } 37 | /* line 43, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 38 | .wg-detached-filter .custom-dropdown-container .expand-multi-select-icon, .wice-grid .custom-dropdown-container .expand-multi-select-icon { 39 | background: transparent url(/plugin_assets/redmine_leaves/images/icons/grid/expand.gif) no-repeat; 40 | } 41 | 42 | /* line 50, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 43 | .wice-grid { 44 | background-color: #D1D0CE; 45 | /* in case of twitter bootstrap :) */ 46 | } 47 | /* line 53, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 48 | .wice-grid .desc, .wice-grid .asc { 49 | padding-right: 18px; 50 | text-decoration: none; 51 | } 52 | /* line 58, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 53 | .wice-grid .desc { 54 | background: transparent url(/plugin_assets/redmine_leaves/images/icons/grid/arrow_down.gif) right no-repeat; 55 | } 56 | /* line 61, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 57 | .wice-grid .asc { 58 | background: transparent url(/plugin_assets/redmine_leaves/images/icons/grid/arrow_up.gif) right no-repeat; 59 | } 60 | /* line 65, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 61 | .wice-grid .submit.clickable { 62 | background: transparent url(/plugin_assets/redmine_leaves/images/icons/grid/table_refresh.png) no-repeat; 63 | width: 50px; 64 | height: 20px; 65 | float: left; 66 | margin-left: -100%; 67 | } 68 | 69 | .wice-grid .ui-datepicker-trigger{ 70 | background: transparent url(/plugin_assets/redmine_leaves/images/icons/grid/calendar_view_month.png) no-repeat; 71 | width: 50px; 72 | height: 16px; 73 | padding: 0px 17px 2px 20px; 74 | } 75 | /* line 70, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 76 | .wice-grid .reset.clickable { 77 | background: transparent url(/plugin_assets/redmine_leaves/images/icons/grid/table.png) no-repeat; 78 | width: 50px; 79 | height: 20px; 80 | } 81 | /* line 77, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 82 | .wice-grid .wg-show-filter.clickable, 83 | .wice-grid .wg-hide-filter.clickable { 84 | background: transparent url(/plugin_assets/redmine_leaves/images/icons/grid/page_white_find.png) no-repeat; 85 | width: 16px; 86 | height: 16px; 87 | } 88 | /* line 83, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 89 | .wice-grid .export-to-csv-button.clickable { 90 | background: transparent url(/plugin_assets/redmine_leaves/images/icons/grid/page_white_excel.png) no-repeat; 91 | width: 16px; 92 | height: 16px; 93 | } 94 | /* line 88, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 95 | .wice-grid .clickable.select-all { 96 | background: transparent url(/plugin_assets/redmine_leaves/images/icons/grid/tick_all.png) no-repeat; 97 | width: 16px; 98 | height: 16px; 99 | float: left; 100 | } 101 | /* line 94, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 102 | .wice-grid .clickable.deselect-all { 103 | background: transparent url(/plugin_assets/redmine_leaves/images/icons/grid/untick_all.png) no-repeat; 104 | width: 16px; 105 | height: 16px; 106 | float: left; 107 | } 108 | /* line 102, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 109 | .wice-grid thead th select { 110 | width: auto; 111 | } 112 | /* line 106, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 113 | .wice-grid .my_pagination { 114 | margin: 0px; 115 | float: left; 116 | } 117 | /* line 112, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 118 | .wice-grid tr.wg-filter-row input[type=text] { 119 | width: 100px; 120 | } 121 | /* line 116, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 122 | .wice-grid .pagination_status { 123 | font-weight: bold; 124 | float: right; 125 | } 126 | 127 | /* line 124, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 128 | .wice-grid-query-panel li { 129 | list-style-type: none; 130 | } 131 | /* line 126, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 132 | .wice-grid-query-panel ul { 133 | margin-left: 0; 134 | } 135 | /* line 128, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 136 | .wice-grid-query-panel a.wice-grid-delete-query .delete-icon { 137 | background: transparent url(/plugin_assets/redmine_leaves/images/icons/grid/delete.png) no-repeat; 138 | float: left; 139 | width: 16px; 140 | height: 16px; 141 | margin-left: 0px; 142 | } 143 | 144 | /* line 135, /home/arkhitech/Desktop/projects/reality_tours/app/plugin_assets/redmine_leaves/stylesheets/wice_grid.css.scss */ 145 | input.wice-grid-save-query-field { 146 | width: auto; 147 | display: inline-block; 148 | margin-right: -128px; 149 | display: block; 150 | text-align: justify; 151 | 152 | } 153 | .wice-grid-save-query-button{ 154 | display: block; 155 | padding: 2px; 156 | text-align: justify; 157 | margin-top: 6px; 158 | } 159 | 160 | li { 161 | line-height: 18px; 162 | } 163 | 164 | #export-button { 165 | float: right; 166 | margin-left: 5px; 167 | } 168 | -------------------------------------------------------------------------------- /lib/wice_grid_config.rb: -------------------------------------------------------------------------------- 1 | if defined?(Wice::Defaults) 2 | 3 | # Default number of rows to show per page. 4 | Wice::Defaults::PER_PAGE = 20 5 | 6 | # Default order direction 7 | Wice::Defaults::ORDER_DIRECTION = 'asc' 8 | 9 | # Default name for a grid. A grid name is the basis for a lot of 10 | # names including parameter names, DOM IDs, etc 11 | # The shorter the name is the shorter the request URI will be. 12 | Wice::Defaults::GRID_NAME = 'grid' 13 | 14 | # If REUSE_LAST_COLUMN_FOR_FILTER_ICONS is true and the last column doesn't have any filter and column name, it will be used 15 | # for filter related icons (filter icon, reset icon, show/hide icon), otherwise an additional table column is added. 16 | Wice::Defaults::REUSE_LAST_COLUMN_FOR_FILTER_ICONS = true 17 | 18 | # The label of the first option of a custom dropdown list meaning 'All items' 19 | Wice::Defaults::CUSTOM_FILTER_ALL_LABEL = '--' 20 | 21 | # A list of classes for the table tag of the grid 22 | Wice::Defaults::DEFAULT_TABLE_CLASSES = ['table', 'table-bordered', 'table-striped'] 23 | 24 | # Allow switching between a single and multiple selection modes in custom filters (dropdown boxes) 25 | Wice::Defaults::ALLOW_MULTIPLE_SELECTION = true 26 | 27 | # Show the upper pagination panel by default or not 28 | Wice::Defaults::SHOW_UPPER_PAGINATION_PANEL = false 29 | 30 | # Disabling CSV export by default 31 | Wice::Defaults::ENABLE_EXPORT_TO_CSV = false 32 | 33 | # Default CSV field separator 34 | Wice::Defaults::CSV_FIELD_SEPARATOR = ',' 35 | 36 | # Default CSV encoding (p.e. 'CP1252:UTF-8' to make Microsoft Excel(tm) happy) 37 | Wice::Defaults::CSV_ENCODING = nil 38 | 39 | # The strategy when to show the filter. 40 | # * :when_filtered - when the table is the result of filtering 41 | # * :always - show the filter always 42 | # * :no - never show the filter 43 | Wice::Defaults::SHOW_FILTER = :always 44 | 45 | # A boolean value specifying if a change in a filter triggers reloading of the grid. 46 | Wice::Defaults::AUTO_RELOAD = false 47 | 48 | # SQL operator used for matching strings in string filters. 49 | Wice::Defaults::STRING_MATCHING_OPERATOR = 'LIKE' 50 | # STRING_MATCHING_OPERATOR = 'ILIKE' # Use this for Postgresql case-insensitive matching. 51 | 52 | # Defining one string matching operator globally for the whole application turns is not enough 53 | # when you connect to two databases one of which is MySQL and the other is Postgresql. 54 | # If the key for an adapter is missing it will fall back to Wice::Defaults::STRING_MATCHING_OPERATOR. 55 | # 56 | # 'CI_LIKE' is a special value. Setting a value in STRING_MATCHING_OPERATORS to CI_LIKE will result in the following SQL: 57 | # 58 | # UPPER(table.field) LIKE UPPER(?)" 59 | Wice::Defaults::STRING_MATCHING_OPERATORS = { 60 | 'ActiveRecord::ConnectionAdapters::MysqlAdapter' => 'LIKE', 61 | 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter' => 'ILIKE' 62 | } 63 | 64 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 65 | # Advanced Filters # 66 | 67 | # Switch of the negation checkbox in all text filters 68 | Wice::Defaults::NEGATION_IN_STRING_FILTERS = false 69 | 70 | # Each WiceGrid filter column is defined in two classes, one used for rendering the filter, the other 71 | # for generating query conditions. All these columns are in lib/wice/columns/*.rb . 72 | # File lib/wice/columns/column_processor_index.rb lists all predefined processors. 73 | # In most cases a processor is chosen automatically based on the DB column type, 74 | # for example, integer columns 75 | # can have two of processors, the default one with one input field, and a processor called "range", 76 | # with 2 input fields. In this case it is possible to specify a processor in the column definition: 77 | # 78 | # g.column filter_type: :range 79 | # 80 | # It is also possible to define you own processors: 81 | # 82 | # Wice::Defaults::ADDITIONAL_COLUMN_PROCESSORS = { 83 | # some_key_identifying_new_column_type: ['AViewColumnProcessorClass', 'ConditionsGeneratorClass'], 84 | # another_key_identifying_new_column_type: ['AnotherViewColumnProcessorClass', 'AnotherConditionsGeneratorClass'] 85 | # } 86 | # 87 | # Column processor keys/names should not coincide with the existing keys/names (see lib/wice/columns/column_processor_index.rb) 88 | # the value is a 2-element array with 2 strings, the first should be a name of view processor class inherited from 89 | # Wice::Columns::ViewColumn, the second should be a name of conditions generator class inherited from 90 | # Wice::Columns::ConditionsGeneratorColumn . 91 | 92 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 93 | # Showing All Records # 94 | 95 | # Enable or disable showing all records (non-paginated table) 96 | Wice::Defaults::ALLOW_SHOWING_ALL_RECORDS = true 97 | 98 | # If number of all queries is more than this value, the user will be given a warning message 99 | Wice::Defaults::START_SHOWING_WARNING_FROM = 100 100 | 101 | # Hide the "show all" link if the number of all records is more than... 102 | # Force-resets back to pagination starting from this value. 103 | # Set to nil to always show it 104 | Wice::Defaults::SHOW_ALL_ALLOWED_UP_TO = nil 105 | 106 | # 107 | # set to nil to skip the check 108 | Wice::Defaults::SWITCH_BACK_TO_PAGINATION_FROM = nil 109 | 110 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 111 | # Saving Queries # 112 | 113 | # ActiveRecord model to store queries. Read the documentation for details 114 | # QUERY_STORE_MODEL = 'WiceGridSerializedQuery' 115 | Wice::Defaults::QUERY_STORE_MODEL = 'WiceGridSerializedQuery' 116 | 117 | # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 118 | # Here go settings related to the date/datetime filters # 119 | 120 | # Default column filters 121 | # Possible values: 122 | # * :jquery_datepicker - Jquery datepicker (works for datetime, too) 123 | # * :bootstrap_datepicker - Bootstrap datepicker (works for datetime, too) 124 | # * :rails_date_helper - standard Rails date helper 125 | # * :rails_datetime_helper - standard Rails datetime helper 126 | 127 | Wice::Defaults::DEFAULT_FILTER_FOR_DATE = :jquery_datepicker 128 | Wice::Defaults::DEFAULT_FILTER_FOR_DATETIME = :jquery_datepicker 129 | 130 | # Format of the datetime displayed. 131 | # If you change the format, make sure to check if +DATETIME_PARSER+ can still parse this string. 132 | Wice::Defaults::DATETIME_FORMAT = '%Y-%m-%d %H:%M' 133 | 134 | # Format of the date displayed. 135 | # If you change the format, make sure to check if +DATE_PARSER+ can still parse this string. 136 | Wice::Defaults::DATE_FORMAT = '%Y-%m-%d' 137 | 138 | # Format of the date displayed in jQuery's Datepicker 139 | # If you change the format, make sure to check if +DATE_PARSER+ can still parse this string. 140 | Wice::Defaults::DATE_FORMAT_JQUERY = 'yy-mm-dd' 141 | 142 | # Format of the date displayed in Bootstrap's Datepicker 143 | # If you change the format, make sure to check if +DATE_PARSER+ can still parse this string. 144 | Wice::Defaults::DATE_FORMAT_BOOTSTRAP = 'yyyy-mm-dd' 145 | 146 | # With Calendar helpers enabled the parameter sent is the string displayed. This lambda will be given a date string in the 147 | # format defined by +DATETIME_FORMAT+ and must generate a DateTime object. 148 | # In many cases Time.zone.parse is enough, for instance, %Y-%m-%d. If you change the format, make sure to check this code 149 | # and modify it if needed. 150 | Wice::Defaults::DATETIME_PARSER = lambda do|datetime_string| 151 | if datetime_string.blank? 152 | nil 153 | elsif Time.zone 154 | Time.zone.parse(datetime_string) 155 | else 156 | Time.parse(datetime_string) 157 | end 158 | end 159 | 160 | # The range of years to display in jQuery Datepicker. 161 | # It can always be changed dynamically with the following javascript: 162 | # $( ".hasDatepicker" ).datepicker( "option", "yearRange", "2000:2042" ); 163 | Wice::Defaults::DATEPICKER_YEAR_RANGE = (from = Date.current.year - 10).to_s + ':' + (from + 15).to_s 164 | 165 | # With Calendar helpers enabled the parameter sent is the string displayed. This lambda will be given a date string in the 166 | # format defined by +DATETIME+ and must generate a Date object. 167 | # In many cases Date.parse is enough, for instance, %Y-%m-%d. If you change the format, make sure to check this code 168 | # and modify it if needed. 169 | Wice::Defaults::DATE_PARSER = lambda do|date_string| 170 | if date_string.blank? 171 | nil 172 | else 173 | begin 174 | Date.parse(date_string) 175 | rescue ArgumentError 176 | nil 177 | end 178 | end 179 | end 180 | 181 | # The name of the page method (should correspond to Kaminari.config.page_method_name) 182 | Wice::Defaults::PAGE_METHOD_NAME = :page 183 | 184 | # The name of the theme to use for the pagination with Kaminari 185 | Wice::Defaults::PAGINATION_THEME = :wice_grid 186 | 187 | # By default ActiveRecord calls are always executed inside Model.unscoped{}. 188 | # Setting USE_DEFAULT_SCOPE to true will use the default scope for all queries. 189 | Wice::Defaults::USE_DEFAULT_SCOPE = false 190 | 191 | end 192 | -------------------------------------------------------------------------------- /assets/javascripts/jquery_ujs.js: -------------------------------------------------------------------------------- 1 | (function($, undefined) { 2 | 3 | /** 4 | * Unobtrusive scripting adapter for jQuery 5 | * https://github.com/rails/jquery-ujs 6 | * 7 | * Requires jQuery 1.7.0 or later. 8 | * 9 | * Released under the MIT license 10 | * 11 | */ 12 | 13 | // Cut down on the number of issues from people inadvertently including jquery_ujs twice 14 | // by detecting and raising an error when it happens. 15 | if ( $.rails !== undefined ) { 16 | $.error('jquery-ujs has already been loaded!'); 17 | } 18 | 19 | // Shorthand to make it a little easier to call public rails functions from within rails.js 20 | var rails; 21 | var $document = $(document); 22 | 23 | $.rails = rails = { 24 | // Link elements bound by jquery-ujs 25 | linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]', 26 | 27 | // Button elements bound by jquery-ujs 28 | buttonClickSelector: 'button[data-remote]', 29 | 30 | // Select elements bound by jquery-ujs 31 | inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]', 32 | 33 | // Form elements bound by jquery-ujs 34 | formSubmitSelector: 'form', 35 | 36 | // Form input elements bound by jquery-ujs 37 | formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])', 38 | 39 | // Form input elements disabled during form submission 40 | disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]', 41 | 42 | // Form input elements re-enabled after form submission 43 | enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled', 44 | 45 | // Form required input elements 46 | requiredInputSelector: 'input[name][required]:not([disabled]),textarea[name][required]:not([disabled])', 47 | 48 | // Form file input elements 49 | fileInputSelector: 'input[type=file]', 50 | 51 | // Link onClick disable selector with possible reenable after remote submission 52 | linkDisableSelector: 'a[data-disable-with]', 53 | 54 | // Make sure that every Ajax request sends the CSRF token 55 | CSRFProtection: function(xhr) { 56 | var token = $('meta[name="csrf-token"]').attr('content'); 57 | if (token) xhr.setRequestHeader('X-CSRF-Token', token); 58 | }, 59 | 60 | // making sure that all forms have actual up-to-date token(cached forms contain old one) 61 | refreshCSRFTokens: function(){ 62 | var csrfToken = $('meta[name=csrf-token]').attr('content'); 63 | var csrfParam = $('meta[name=csrf-param]').attr('content'); 64 | $('form input[name="' + csrfParam + '"]').val(csrfToken); 65 | }, 66 | 67 | // Triggers an event on an element and returns false if the event result is false 68 | fire: function(obj, name, data) { 69 | var event = $.Event(name); 70 | obj.trigger(event, data); 71 | return event.result !== false; 72 | }, 73 | 74 | // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm 75 | confirm: function(message) { 76 | return confirm(message); 77 | }, 78 | 79 | // Default ajax function, may be overridden with custom function in $.rails.ajax 80 | ajax: function(options) { 81 | return $.ajax(options); 82 | }, 83 | 84 | // Default way to get an element's href. May be overridden at $.rails.href. 85 | href: function(element) { 86 | return element.attr('href'); 87 | }, 88 | 89 | // Submits "remote" forms and links with ajax 90 | handleRemote: function(element) { 91 | var method, url, data, elCrossDomain, crossDomain, withCredentials, dataType, options; 92 | 93 | if (rails.fire(element, 'ajax:before')) { 94 | elCrossDomain = element.data('cross-domain'); 95 | crossDomain = elCrossDomain === undefined ? null : elCrossDomain; 96 | withCredentials = element.data('with-credentials') || null; 97 | dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType); 98 | 99 | if (element.is('form')) { 100 | method = element.attr('method'); 101 | url = element.attr('action'); 102 | data = element.serializeArray(); 103 | // memoized value from clicked submit button 104 | var button = element.data('ujs:submit-button'); 105 | if (button) { 106 | data.push(button); 107 | element.data('ujs:submit-button', null); 108 | } 109 | } else if (element.is(rails.inputChangeSelector)) { 110 | method = element.data('method'); 111 | url = element.data('url'); 112 | data = element.serialize(); 113 | if (element.data('params')) data = data + "&" + element.data('params'); 114 | } else if (element.is(rails.buttonClickSelector)) { 115 | method = element.data('method') || 'get'; 116 | url = element.data('url'); 117 | data = element.serialize(); 118 | if (element.data('params')) data = data + "&" + element.data('params'); 119 | } else { 120 | method = element.data('method'); 121 | url = rails.href(element); 122 | data = element.data('params') || null; 123 | } 124 | 125 | options = { 126 | type: method || 'GET', data: data, dataType: dataType, 127 | // stopping the "ajax:beforeSend" event will cancel the ajax request 128 | beforeSend: function(xhr, settings) { 129 | if (settings.dataType === undefined) { 130 | xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script); 131 | } 132 | return rails.fire(element, 'ajax:beforeSend', [xhr, settings]); 133 | }, 134 | success: function(data, status, xhr) { 135 | element.trigger('ajax:success', [data, status, xhr]); 136 | }, 137 | complete: function(xhr, status) { 138 | element.trigger('ajax:complete', [xhr, status]); 139 | }, 140 | error: function(xhr, status, error) { 141 | element.trigger('ajax:error', [xhr, status, error]); 142 | }, 143 | crossDomain: crossDomain 144 | }; 145 | 146 | // There is no withCredentials for IE6-8 when 147 | // "Enable native XMLHTTP support" is disabled 148 | if (withCredentials) { 149 | options.xhrFields = { 150 | withCredentials: withCredentials 151 | }; 152 | } 153 | 154 | // Only pass url to `ajax` options if not blank 155 | if (url) { options.url = url; } 156 | 157 | var jqxhr = rails.ajax(options); 158 | element.trigger('ajax:send', jqxhr); 159 | return jqxhr; 160 | } else { 161 | return false; 162 | } 163 | }, 164 | 165 | // Handles "data-method" on links such as: 166 | // Delete 167 | handleMethod: function(link) { 168 | var href = rails.href(link), 169 | method = link.data('method'), 170 | target = link.attr('target'), 171 | csrfToken = $('meta[name=csrf-token]').attr('content'), 172 | csrfParam = $('meta[name=csrf-param]').attr('content'), 173 | form = $('
'), 174 | metadataInput = ''; 175 | 176 | if (csrfParam !== undefined && csrfToken !== undefined) { 177 | metadataInput += ''; 178 | } 179 | 180 | if (target) { form.attr('target', target); } 181 | 182 | form.hide().append(metadataInput).appendTo('body'); 183 | form.submit(); 184 | }, 185 | 186 | /* Disables form elements: 187 | - Caches element value in 'ujs:enable-with' data store 188 | - Replaces element text with value of 'data-disable-with' attribute 189 | - Sets disabled property to true 190 | */ 191 | disableFormElements: function(form) { 192 | form.find(rails.disableSelector).each(function() { 193 | var element = $(this), method = element.is('button') ? 'html' : 'val'; 194 | element.data('ujs:enable-with', element[method]()); 195 | element[method](element.data('disable-with')); 196 | element.prop('disabled', true); 197 | }); 198 | }, 199 | 200 | /* Re-enables disabled form elements: 201 | - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`) 202 | - Sets disabled property to false 203 | */ 204 | enableFormElements: function(form) { 205 | form.find(rails.enableSelector).each(function() { 206 | var element = $(this), method = element.is('button') ? 'html' : 'val'; 207 | if (element.data('ujs:enable-with')) element[method](element.data('ujs:enable-with')); 208 | element.prop('disabled', false); 209 | }); 210 | }, 211 | 212 | /* For 'data-confirm' attribute: 213 | - Fires `confirm` event 214 | - Shows the confirmation dialog 215 | - Fires the `confirm:complete` event 216 | 217 | Returns `true` if no function stops the chain and user chose yes; `false` otherwise. 218 | Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog. 219 | Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function 220 | return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog. 221 | */ 222 | allowAction: function(element) { 223 | var message = element.data('confirm'), 224 | answer = false, callback; 225 | if (!message) { return true; } 226 | 227 | if (rails.fire(element, 'confirm')) { 228 | answer = rails.confirm(message); 229 | callback = rails.fire(element, 'confirm:complete', [answer]); 230 | } 231 | return answer && callback; 232 | }, 233 | 234 | // Helper function which checks for blank inputs in a form that match the specified CSS selector 235 | blankInputs: function(form, specifiedSelector, nonBlank) { 236 | var inputs = $(), input, valueToCheck, 237 | selector = specifiedSelector || 'input,textarea', 238 | allInputs = form.find(selector); 239 | 240 | allInputs.each(function() { 241 | input = $(this); 242 | valueToCheck = input.is('input[type=checkbox],input[type=radio]') ? input.is(':checked') : input.val(); 243 | // If nonBlank and valueToCheck are both truthy, or nonBlank and valueToCheck are both falsey 244 | if (!valueToCheck === !nonBlank) { 245 | 246 | // Don't count unchecked required radio if other radio with same name is checked 247 | if (input.is('input[type=radio]') && allInputs.filter('input[type=radio]:checked[name="' + input.attr('name') + '"]').length) { 248 | return true; // Skip to next input 249 | } 250 | 251 | inputs = inputs.add(input); 252 | } 253 | }); 254 | return inputs.length ? inputs : false; 255 | }, 256 | 257 | // Helper function which checks for non-blank inputs in a form that match the specified CSS selector 258 | nonBlankInputs: function(form, specifiedSelector) { 259 | return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank 260 | }, 261 | 262 | // Helper function, needed to provide consistent behavior in IE 263 | stopEverything: function(e) { 264 | $(e.target).trigger('ujs:everythingStopped'); 265 | e.stopImmediatePropagation(); 266 | return false; 267 | }, 268 | 269 | // replace element's html with the 'data-disable-with' after storing original html 270 | // and prevent clicking on it 271 | disableElement: function(element) { 272 | element.data('ujs:enable-with', element.html()); // store enabled state 273 | element.html(element.data('disable-with')); // set to disabled state 274 | element.bind('click.railsDisable', function(e) { // prevent further clicking 275 | return rails.stopEverything(e); 276 | }); 277 | }, 278 | 279 | // restore element to its original state which was disabled by 'disableElement' above 280 | enableElement: function(element) { 281 | if (element.data('ujs:enable-with') !== undefined) { 282 | element.html(element.data('ujs:enable-with')); // set to old enabled state 283 | element.removeData('ujs:enable-with'); // clean up cache 284 | } 285 | element.unbind('click.railsDisable'); // enable element 286 | } 287 | 288 | }; 289 | 290 | if (rails.fire($document, 'rails:attachBindings')) { 291 | 292 | $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }}); 293 | 294 | $document.delegate(rails.linkDisableSelector, 'ajax:complete', function() { 295 | rails.enableElement($(this)); 296 | }); 297 | 298 | $document.delegate(rails.linkClickSelector, 'click.rails', function(e) { 299 | var link = $(this), method = link.data('method'), data = link.data('params'), metaClick = e.metaKey || e.ctrlKey; 300 | if (!rails.allowAction(link)) return rails.stopEverything(e); 301 | 302 | if (!metaClick && link.is(rails.linkDisableSelector)) rails.disableElement(link); 303 | 304 | if (link.data('remote') !== undefined) { 305 | if (metaClick && (!method || method === 'GET') && !data) { return true; } 306 | 307 | var handleRemote = rails.handleRemote(link); 308 | // response from rails.handleRemote() will either be false or a deferred object promise. 309 | if (handleRemote === false) { 310 | rails.enableElement(link); 311 | } else { 312 | handleRemote.error( function() { rails.enableElement(link); } ); 313 | } 314 | return false; 315 | 316 | } else if (link.data('method')) { 317 | rails.handleMethod(link); 318 | return false; 319 | } 320 | }); 321 | 322 | $document.delegate(rails.buttonClickSelector, 'click.rails', function(e) { 323 | var button = $(this); 324 | if (!rails.allowAction(button)) return rails.stopEverything(e); 325 | 326 | rails.handleRemote(button); 327 | return false; 328 | }); 329 | 330 | $document.delegate(rails.inputChangeSelector, 'change.rails', function(e) { 331 | var link = $(this); 332 | if (!rails.allowAction(link)) return rails.stopEverything(e); 333 | 334 | rails.handleRemote(link); 335 | return false; 336 | }); 337 | 338 | $document.delegate(rails.formSubmitSelector, 'submit.rails', function(e) { 339 | var form = $(this), 340 | remote = form.data('remote') !== undefined, 341 | blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector), 342 | nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector); 343 | 344 | if (!rails.allowAction(form)) return rails.stopEverything(e); 345 | 346 | // skip other logic when required values are missing or file upload is present 347 | if (blankRequiredInputs && form.attr("novalidate") == undefined && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) { 348 | return rails.stopEverything(e); 349 | } 350 | 351 | if (remote) { 352 | if (nonBlankFileInputs) { 353 | // slight timeout so that the submit button gets properly serialized 354 | // (make it easy for event handler to serialize form without disabled values) 355 | setTimeout(function(){ rails.disableFormElements(form); }, 13); 356 | var aborted = rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]); 357 | 358 | // re-enable form elements if event bindings return false (canceling normal form submission) 359 | if (!aborted) { setTimeout(function(){ rails.enableFormElements(form); }, 13); } 360 | 361 | return aborted; 362 | } 363 | 364 | rails.handleRemote(form); 365 | return false; 366 | 367 | } else { 368 | // slight timeout so that the submit button gets properly serialized 369 | setTimeout(function(){ rails.disableFormElements(form); }, 13); 370 | } 371 | }); 372 | 373 | $document.delegate(rails.formInputClickSelector, 'click.rails', function(event) { 374 | var button = $(this); 375 | 376 | if (!rails.allowAction(button)) return rails.stopEverything(event); 377 | 378 | // register the pressed submit button 379 | var name = button.attr('name'), 380 | data = name ? {name:name, value:button.val()} : null; 381 | 382 | button.closest('form').data('ujs:submit-button', data); 383 | }); 384 | 385 | $document.delegate(rails.formSubmitSelector, 'ajax:beforeSend.rails', function(event) { 386 | if (this == event.target) rails.disableFormElements($(this)); 387 | }); 388 | 389 | $document.delegate(rails.formSubmitSelector, 'ajax:complete.rails', function(event) { 390 | if (this == event.target) rails.enableFormElements($(this)); 391 | }); 392 | 393 | $(function(){ 394 | rails.refreshCSRFTokens(); 395 | }); 396 | } 397 | 398 | })( jQuery ); 399 | -------------------------------------------------------------------------------- /app/controllers/user_time_checks_controller.rb: -------------------------------------------------------------------------------- 1 | class UserTimeChecksController < ApplicationController 2 | unloadable 3 | 4 | # before_action :require_login, :authorize, :only => :index 5 | include SortHelper 6 | # before_action :authorize, :only => :index 7 | helper :sort 8 | 9 | def index 10 | 11 | time_checks= UserTimeCheck. 12 | select("#{UserTimeCheck.table_name}.*,sum(#{TimeEntry.table_name}.hours ) as logged_hours"). 13 | joins("LEFT JOIN #{TimeEntry.table_name} on DATE(check_in_time) <= spent_on AND DATE(check_out_time) >= spent_on"). 14 | group("#{UserTimeCheck.table_name}.id") 15 | 16 | @time_check_grid = initialize_grid(time_checks.where('check_in_time > ?', Time.now - 6.months), 17 | name: 'time_checks_grid', 18 | enable_export_to_csv: true, 19 | csv_field_separator: ';', 20 | csv_file_name: 'UserTimeChecks')#, 21 | 22 | export_grid_if_requested('time_checks_grid' => 'time_check_grid') 23 | 24 | end 25 | def all_trackers 26 | (Setting.plugin_redmine_leaves['tracker_names'] || '').split(',').delete_if { |index| index.blank? } 27 | end 28 | 29 | def user_time_activity_report 30 | 31 | 32 | @trackers=Tracker.all 33 | 34 | @time_checks_for_trackers = {} 35 | @users_with_logged_activities=User. 36 | select("Distinct #{User.table_name}.id as user_id,#{User.table_name}.firstname,#{User.table_name}.lastname") 37 | .joins("INNER JOIN #{TimeEntry.table_name} on #{User.table_name}.id= #{TimeEntry.table_name}.user_id") 38 | .where(" #{TimeEntry.table_name}.spent_on>=? and #{TimeEntry.table_name}.spent_on<=?",params[:date_from]||Date.today - 1.month,params[:date_to]||Date.today ) 39 | 40 | @user_stats = {} 41 | 42 | @time_spent_on_tracker = {} 43 | @trackers.each do |tracker| 44 | 45 | @time_spent_on_tracker[tracker.name] = User. 46 | select("#{User.table_name}.lastname, #{User.table_name}.firstname, #{User. 47 | table_name}.id as user_id, #{Tracker.table_name}.id as tracker_id,#{Tracker. 48 | table_name}.name as tracker_name,count(#{Tracker. 49 | table_name}.name)as num_of_trackers,sum(#{TimeEntry. 50 | table_name}.hours ) as time_spent") 51 | .joins("INNER JOIN #{TimeEntry.table_name} on #{User.table_name}.id= #{TimeEntry.table_name}.user_id") 52 | .joins("INNER JOIN #{Issue.table_name} on #{Issue.table_name}.id= #{TimeEntry.table_name}.issue_id") 53 | .joins("INNER JOIN #{Tracker.table_name} on #{Issue.table_name}.tracker_id= #{Tracker.table_name}.id") 54 | .group("#{Tracker.table_name}.id, #{User.table_name}.id") 55 | .order("#{Tracker.table_name}.id") 56 | .where("#{Tracker.table_name}.name=? AND #{TimeEntry. 57 | table_name}.spent_on>=? AND #{TimeEntry.table_name}.spent_on<=?", 58 | tracker.name, params[:date_from]||Date.today - 1.month,params[:date_to]||Date.today ) 59 | end 60 | 61 | 62 | # @trackers.each do |tracker| 63 | @all_trackers=all_trackers 64 | 65 | # Sum(CASE 66 | # WHEN CMTS_RQ.US_Pwr >=37 AND CMTS_RQ.US_Pwr <= 49 67 | # THEN 1 68 | # ELSE 0 69 | all_trackers.each do |tracker| 70 | 71 | user_tracker_stats = User. 72 | select("#{User.table_name}.lastname, #{User.table_name}.firstname, #{User. 73 | table_name}.id as user_id, #{Tracker.table_name}.id as tracker_id,#{Tracker. 74 | table_name}.name as tracker_name, 75 | sum(CASE 76 | WHEN #{Issue.table_name}.estimated_hours IS NOT NULL 77 | THEN estimated_hours 78 | ELSE 0 79 | end) as estimated_hours_on_tracker, 80 | count(#{Tracker. 81 | table_name}.name)as num_of_trackers,sum(#{TimeEntry.table_name}.hours ) as time_spent") 82 | .joins("INNER JOIN #{TimeEntry.table_name} on #{User.table_name}.id= #{TimeEntry.table_name}.user_id") 83 | .joins("INNER JOIN #{Issue.table_name} on #{Issue.table_name}.id= #{TimeEntry.table_name}.issue_id") 84 | .joins("INNER JOIN #{Tracker.table_name} on #{Issue.table_name}.tracker_id= #{Tracker.table_name}.id") 85 | .group("#{Tracker.table_name}.id, #{User.table_name}.id") 86 | .order("#{Tracker.table_name}.id") 87 | .where("#{Tracker.table_name}.name=? AND #{TimeEntry. 88 | table_name}.spent_on>=? AND #{TimeEntry.table_name}.spent_on<=?", 89 | tracker, params[:date_from]||Date.today - 1.month,params[:date_to]||Date.today ) 90 | 91 | user_tracker_stats.each do |user_tracker_stat| 92 | @user_stats[user_tracker_stat.user_id] ||= {user: user_tracker_stat, trackers: {}} 93 | @user_stats[user_tracker_stat.user_id][:trackers][user_tracker_stat.tracker_name] = user_tracker_stat 94 | end 95 | end 96 | 97 | missed_due_dates=User. 98 | select("#{User.table_name}.firstname,#{User.table_name}.lastname,#{User.table_name}.id as user_id, count(#{Tracker. 99 | table_name}.id)as missed_dates") 100 | .joins("INNER JOIN #{TimeEntry.table_name} on #{User.table_name}.id= #{TimeEntry.table_name}.user_id") 101 | .joins("INNER JOIN #{Issue.table_name} on #{Issue.table_name}.id= #{TimeEntry.table_name}.issue_id") 102 | .joins("INNER JOIN #{Tracker.table_name} on #{Issue.table_name}.tracker_id= #{Tracker.table_name}.id") 103 | .group("#{User.table_name}.id,#{Issue.table_name}.id") 104 | .where( 105 | "#{Issue.table_name}.due_date< #{Issue.table_name}.closed_on 106 | and #{Issue.table_name}.due_date is not NULL 107 | and #{Issue.table_name }.due_date >=? 108 | and #{Issue.table_name }.due_date <=?",params[:date_from]||Date.today - 1.month,params[:date_to]||Date.today ) 109 | 110 | missed_due_dates.each do |missed_due_dates| 111 | @user_stats[missed_due_dates.user_id] ||= {user: missed_due_dates, trackers: {}} 112 | 113 | @user_stats[missed_due_dates.user_id][:missed_due_dates] = missed_due_dates 114 | end 115 | end 116 | 117 | 118 | 119 | 120 | def user_time_activity_report_monthly 121 | 122 | @trackers=Tracker.all 123 | @months_and_years=User. 124 | select("Distinct #{User.table_name}.id as user_id,month(#{TimeEntry.table_name}.spent_on) as month,year(#{TimeEntry.table_name}.spent_on) as year") 125 | .joins("INNER JOIN #{TimeEntry.table_name} on #{User.table_name}.id= #{TimeEntry.table_name}.user_id") 126 | .where(" #{TimeEntry.table_name}.spent_on>=? and #{TimeEntry.table_name}.spent_on<=?",params[:date_from]||Date.today - 1.month,params[:date_to]||Date.today ) 127 | .order("user_id") 128 | count_users=0 129 | @months_and_years.each do |user| 130 | unless user.nil? 131 | count_users=count_users+1 132 | end 133 | end 134 | 135 | @time_spent_on_tracker = {} 136 | @missed_dates = {} 137 | @all_trackers=all_trackers 138 | 139 | 140 | @months_and_years.each do |user| 141 | # @trackers.each do |tracker| 142 | all_trackers.each do |tracker| 143 | @time_spent_on_tracker[tracker+user.user_id.to_s+user.month.to_s+user.year.to_s] = User. 144 | select(" #{User.table_name}.firstname,year(#{TimeEntry.table_name}.spent_on)as year, 145 | month(#{TimeEntry.table_name}.spent_on) as month") 146 | .joins("INNER JOIN #{TimeEntry.table_name} on #{User.table_name}.id= #{TimeEntry.table_name}.user_id") 147 | .joins("INNER JOIN #{Issue.table_name} on #{Issue.table_name}.id= #{TimeEntry.table_name}.issue_id") 148 | .joins("INNER JOIN #{Tracker.table_name} on #{Issue.table_name}.tracker_id= #{Tracker.table_name}.id") 149 | .group("#{Tracker.table_name}.id,#{User.table_name}.id,year(#{TimeEntry.table_name}.spent_on),month(#{TimeEntry.table_name}.spent_on)") 150 | .select("#{Tracker.table_name}.id as tracker_id,#{Tracker.table_name}.name as tracker_name, 151 | count(#{Tracker.table_name}.name)as num_of_trackers, 152 | sum(CASE 153 | WHEN #{Issue.table_name}.estimated_hours IS NOT NULL 154 | THEN estimated_hours 155 | ELSE 0 156 | end) as estimated_hours_on_tracker, 157 | sum(#{TimeEntry.table_name}.hours ) as time_spent") 158 | .order("year(#{TimeEntry.table_name}.spent_on),month(#{TimeEntry.table_name}.spent_on),#{Tracker.table_name}.id") 159 | .where("#{Tracker.table_name}.name=? 160 | and #{TimeEntry.table_name}.spent_on>=? 161 | and #{TimeEntry.table_name}.spent_on<=? 162 | and month(#{TimeEntry.table_name}.spent_on)=? 163 | and year(#{TimeEntry.table_name}.spent_on)=? 164 | and #{User.table_name}.id=?",tracker,params[:date_from]||Date.today - 1.month,params[:date_to]||Date.today ,user.month,user.year,user.user_id) 165 | 166 | 167 | end 168 | 169 | @missed_dates[user.user_id.to_s+user.month.to_s+user.year.to_s]=User. 170 | select(" #{User.table_name}.firstname,#{User.table_name}.lastname, 171 | year(#{TimeEntry.table_name}.spent_on)as year, 172 | month(#{TimeEntry.table_name}.spent_on) as month, 173 | count(#{Tracker.table_name}.id)as missed_dates") 174 | .joins("INNER JOIN #{TimeEntry.table_name} on #{User.table_name}.id= #{TimeEntry.table_name}.user_id") 175 | .joins("INNER JOIN #{Issue.table_name} on #{Issue.table_name}.id= #{TimeEntry.table_name}.issue_id") 176 | .joins("INNER JOIN #{Tracker.table_name} on #{Issue.table_name}.tracker_id= #{Tracker.table_name}.id") 177 | .group("#{User.table_name}.id, 178 | #{Issue.table_name}.id, 179 | year(#{TimeEntry.table_name}.spent_on), 180 | month(#{TimeEntry.table_name}.spent_on)") 181 | .where("#{Issue.table_name}.due_date< #{Issue.table_name}.closed_on 182 | and #{Issue.table_name}.due_date is not NULL 183 | and #{TimeEntry.table_name}.spent_on>=? 184 | and #{TimeEntry.table_name}.spent_on<=? 185 | and month(#{TimeEntry.table_name}.spent_on)=? 186 | and year(#{TimeEntry.table_name}.spent_on)=? 187 | and #{User.table_name}.id=?",params[:date_from]||Date.today - 1.month,params[:date_to]||Date.today ,user.month,user.year,user.user_id) 188 | .order("year(#{Issue.table_name}.start_date),month(#{Issue.table_name}.start_date)") 189 | 190 | end 191 | 192 | @trackers=Tracker.all 193 | 194 | 195 | 196 | 197 | end 198 | 199 | 200 | 201 | 202 | def user_time_reporting 203 | 204 | time_checks = UserTimeCheck.select("user_id, check_in_time,check_out_time, 205 | AVG(check_in_time) as avg_check_in_time, 206 | AVG(check_out_time) as avg_check_out_time, 207 | avg(time_spent) as average_time") 208 | .includes(:user) 209 | .group('user_id') 210 | .where("check_out_time IS NOT NULL")# 211 | 212 | @time_report_grid = initialize_grid(time_checks.where('check_in_time > ?', Time.now - 6.months), 213 | name: 'time_checks_grid', 214 | enable_export_to_csv: true, 215 | csv_field_separator: ';', 216 | csv_file_name: 'UserTimeCustom')#, 217 | 218 | export_grid_if_requested('time_checks_grid' => 'time_report_grid') 219 | 220 | 221 | end 222 | 223 | 224 | 225 | 226 | def user_time_reporting_weekly 227 | 228 | time_checks = UserTimeCheck.select("check_in_time as weekdays,week(check_in_time) as week,year(check_in_time) as year,check_in_time, 229 | check_out_time ,user_id, 230 | AVG(check_in_time) as avg_check_in_time, 231 | AVG(check_out_time) as avg_check_out_time, 232 | sum(time_spent) as time_spent,avg(time_spent) as average_time"). 233 | includes(:user). 234 | group('user_id,year(check_in_time),week(check_in_time)'). 235 | order('year(check_in_time),week(check_in_time)')#.includes(:user) 236 | 237 | @time_report_grid_weekly = initialize_grid(time_checks.where('check_in_time > ?', Time.now - 6.months), 238 | name: 'time_checks_grid', 239 | enable_export_to_csv: true, 240 | csv_field_separator: ';', 241 | csv_file_name: 'UserTimeWeekly')#, 242 | 243 | export_grid_if_requested('time_checks_grid' => 'time_report_grid_weekly') 244 | 245 | 246 | 247 | 248 | end 249 | 250 | 251 | 252 | def user_time_reporting_monthly 253 | 254 | 255 | time_checks = UserTimeCheck.includes(:user) 256 | .select("check_in_time, check_out_time, user_id, 257 | AVG(check_in_time) as avg_check_in_time, 258 | AVG(check_out_time) as avg_check_out_time, 259 | sum(time_spent) as time_spent,avg(time_spent) as average_time") 260 | .group('user_id,year(check_in_time),month(check_in_time)') 261 | .order('year(check_in_time),month(check_in_time),user_id') 262 | .where("check_out_time IS NOT NULL") 263 | @time_report_grid_monthly = initialize_grid(time_checks, 264 | name: 'time_checks_grid', 265 | enable_export_to_csv: true, 266 | # conditions: ["check_in_time > ?", Time.now - 12.months], 267 | csv_field_separator: ';', 268 | csv_file_name: 'UserTimeMonthly')#, 269 | 270 | export_grid_if_requested('time_checks_grid' => 'time_report_grid_monthly') 271 | 272 | end 273 | 274 | 275 | 276 | 277 | 278 | def edit 279 | @time_checks = UserTimeCheck.find(params[:id]) 280 | end 281 | 282 | def update 283 | @time_checks = UserTimeCheck.find(params[:id]) 284 | if @time_checks.update_attributes(params[:user_time_check]) 285 | redirect_to user_time_checks_path, 286 | notice: "User Time Check for #{@time_checks.user.name} 287 | on #{@time_checks.check_in_time.to_date} Updated. 288 | #{view_context.link_to t(:link_edit), edit_user_time_check_path(@time_checks)}".html_safe 289 | else 290 | redirect_to edit_user_time_check_path(@time_checks), :flash => { :error => "Invalid Input!" } 291 | end 292 | end 293 | 294 | def import 295 | begin 296 | UserTimeCheck.import(params[:file]) 297 | redirect_to user_time_checks_path, notice: t(:notice_time_check_file_imported) if params[:file] 298 | rescue StandardError => e 299 | puts "#{e.message}\n#{e.backtrace.join("\n")}" 300 | redirect_to user_time_checks_path, flash: { error: t(:error_invalid_file_format) } 301 | end 302 | end 303 | 304 | def check_in 305 | checkin_timechecks = UserTimeCheck.where(['user_id = ? AND check_out_time IS NULL', User.current.id]) 306 | 307 | if checkin_timechecks.empty? 308 | @user_time_check = UserTimeCheck.create(user_id: User.current.id, check_in_time: DateTime.now) 309 | else 310 | flash.now[:error] = t(:error_checkout_first) 311 | @user_time_check = checkin_timechecks.first 312 | end 313 | end 314 | 315 | def check_out 316 | checkout_timechecks = UserTimeCheck.where(['user_id = ? AND check_out_time IS NULL', User.current.id]) 317 | 318 | if checkout_timechecks.empty? 319 | flash.now[:error] = t(:error_checkin_first) 320 | @user_time_check = UserTimeCheck.new(:user_id => User.current.id) 321 | 322 | else 323 | @user_time_check = checkout_timechecks.first 324 | @user_time_check.update_attributes(check_out_time: DateTime.now) 325 | 326 | 327 | @time_entries= TimeEntry.where(user_id: User.current.id , created_on: (@user_time_check.check_in_time)..@user_time_check.check_out_time, spent_on: [@user_time_check.check_in_time.to_date,@user_time_check.check_out_time.to_date]) 328 | 329 | 330 | logged_in_time= @time_entries.sum(:hours) 331 | checked_time = @user_time_check.check_out_time - @user_time_check.check_in_time 332 | 333 | if logged_in_time<0.9*(checked_time/3600) 334 | flash.now[:error] = t(:error_less_time_logged) 335 | #@assigned_issues= Issue.where(assigned_to_id: User.current.id) 336 | @assigned_issues= Issue.where(assigned_to_id: User.current.id).joins(:status). 337 | where("#{IssueStatus.table_name}.is_closed" => false) 338 | 339 | #@new_time_entries = Array.new(3) { assigned_issue.time_entries.build } 340 | @new_time_entries = [] 341 | @assigned_issues.each do |assigned_issue| 342 | #@new_time_entries << TimeEntry.new(:issue_id => assigned_issue.id) 343 | @new_time_entries << assigned_issue.time_entries.build 344 | end 345 | end 346 | end 347 | end 348 | 349 | def create_time_entries 350 | logger.debug "#{'*'*80}\nReceived parameters: #{params.inspect}\n#{'*'*80}" 351 | #"time_entries"=>{"issue_id"=>["1", "2"], "hours"=>["1", "2"], "activity_id"=>["8", "8"], "comments"=>["asim", "hello"]} 352 | @new_time_entries = [] 353 | # issue_ids = params[:issue_id] 354 | # issue_ids.each_index do |idx| 355 | # time_entries << TimeEntry.create(:issue_id => issue_ids[idx], :hours => params[:hours][idx]) 356 | # end 357 | 358 | time_entry_paramss = params[:time_entries] || [] 359 | time_entry_paramss.each do |time_entry_params| 360 | time_entry_this = TimeEntry.new(time_entry_params) # This solves the .permit problem : See Model 361 | time_entry_this.user_id = User.current.id 362 | time_entry_this.save 363 | @new_time_entries << time_entry_this 364 | end 365 | #@assigned_issues= Issue.where(assigned_to_id: User.current.id) 366 | @assigned_issues= Issue.where(assigned_to_id: User.current.id).joins(:status). 367 | where("#{IssueStatus.table_name}.is_closed" => false) 368 | @user_time_check = UserTimeCheck.where(["user_id = ? and check_out_time IS NOT NULL", User.current.id]).limit(1).order('id DESC').first 369 | # @time_entries= TimeEntry.where(user_id: User.current.id , spent_on: (@user_time_check.check_in_time)..@user_time_check.check_out_time) 370 | @time_entries= TimeEntry.where(user_id: User.current.id , created_on: (@user_time_check.check_in_time)..@user_time_check.check_out_time+1.hour, spent_on: [@user_time_check.check_in_time.to_date,@user_time_check.check_out_time.to_date]) 371 | 372 | logged_time= @time_entries.sum(:hours) 373 | checked_time = @user_time_check.check_out_time - @user_time_check.check_in_time 374 | 375 | if logged_time<0.90*(checked_time/3600) #may changed this 376 | render 'check_out' 377 | else 378 | render 'checkout_timelog_success' 379 | end 380 | 381 | end 382 | 383 | 384 | end 385 | -------------------------------------------------------------------------------- /app/models/user_time_check.rb: -------------------------------------------------------------------------------- 1 | class UserTimeCheck < ActiveRecord::Base 2 | unloadable 3 | 4 | extend Redmine::Utils::DateCalculation 5 | 6 | belongs_to :user 7 | 8 | validates :time_spent, :check_in_time, :check_out_time, presence: true, on: :update 9 | validate :correctness_of_user_time_checks 10 | 11 | class << self 12 | def checked_in?(user_id) 13 | exists?(['user_id = ? and check_out_time IS NULL', user_id]) 14 | end 15 | end 16 | 17 | INDEX_ZK_NAME = 4 18 | INDEX_ZK_DATETIME = 1 19 | 20 | 21 | def correctness_of_user_time_checks 22 | unless check_in_time.nil? || check_out_time.nil? 23 | if check_in_time > check_out_time 24 | errors.add(:check_in_time, ": cannot be greater than Check-Out Time") 25 | end 26 | end 27 | end 28 | 29 | class << self 30 | def time_loggers_group 31 | Group.find(Setting.plugin_redmine_leaves['time_loggers_group']) 32 | end 33 | def time_log_receivers_group 34 | Group.find(Setting.plugin_redmine_leaves['time_log_receivers_group']) 35 | end 36 | 37 | def num_min_working_hours 38 | Setting.plugin_redmine_leaves['num_min_working_hours'].to_i || 8 39 | end 40 | 41 | def default_leave_type 42 | Setting.plugin_redmine_leaves['default_type'] || 'Annual' 43 | end 44 | 45 | def email_reminder_for_issue_time_log 46 | cc_group = [] 47 | reminder_time = '' 48 | 49 | if args.arg1 == 'daily' 50 | cc_group = Setting.plugin_redmine_leaves['daily_reminder'] 51 | reminder_time = 'Daily' 52 | elsif args.arg1 == 'weekly' 53 | cc_group = Setting.plugin_redmine_leaves['weekly_reminder'] 54 | reminder_time = 'Weekly' 55 | elsif args.arg1 == 'monthly' 56 | cc_group = Setting.plugin_redmine_leaves['monthly_reminder'] 57 | reminder_time = 'Monthly' 58 | end 59 | 60 | all_users = User.active 61 | all_users.find_each do |user| 62 | assigned_issues= Issue.where(assigned_to_id: user.id).joins(:status). 63 | where("#{IssueStatus.table_name}.is_closed" => false) 64 | user_leave = UserLeave.where(user_id: user.id, leave_date: Date.today) 65 | assigned_issues.each do |issue| 66 | time_entry = TimeEntry.find_by_issue_id(issue) 67 | if !time_entry 68 | #if user never logged time for this issue 69 | LogTimeReminderMailer.reminder_email(user, issue, cc_group, reminder_time).deliver_now 70 | elsif !(time_entry.updated_on.to_date == Date.today) && !user_leave.exists? 71 | #if user is present and did not log time today 72 | LogTimeReminderMailer.reminder_email(user, issue, cc_group, reminder_time).deliver_now 73 | end 74 | end 75 | end 76 | end 77 | 78 | def report_activity(report_days = 1, report_projects = true, 79 | notify_missing_time = true, mark_leave_after_days = -1, error_tolerance = 0.25) 80 | projects = Project.active.includes(members: :user) #get project timesheet 81 | 82 | start_date = Date.today - report_days.day 83 | end_date = Date.today - 1.day 84 | end_date = start_date if start_date > end_date 85 | 86 | return if working_days(start_date, end_date + 1.day) < 1 87 | 88 | users = User.in_group(time_loggers_group).where(status: User::STATUS_ACTIVE) 89 | 90 | 91 | email_group_timesheet(time_log_receivers_group, time_loggers_group, start_date, end_date) if report_projects 92 | 93 | email_project_timesheets(projects, start_date, end_date) if report_projects 94 | 95 | email_users_missing_hours(users, start_date, end_date) if notify_missing_time 96 | 97 | mark_leave_for_missing_hours(users, start_date, end_date, mark_leave_after_days, error_tolerance) if mark_leave_after_days > 0 98 | end 99 | 100 | def mark_leave_for_missing_hours(users, start_date, end_date, mark_leave_after_days, error_tolerance = 0.25) 101 | total_days = working_days(start_date, end_date + 1.day) 102 | num_working_hours = total_days * num_min_working_hours 103 | 104 | start_date = start_date - mark_leave_after_days.days 105 | end_date = end_date - mark_leave_after_days.days 106 | 107 | user_ids = users.map(&:id) 108 | (start_date..end_date).each do |curr_date| 109 | num_working_hours = num_min_working_hours * (1 - error_tolerance) 110 | 111 | logged_time_user_ids = User.joins(:time_entries).where("#{TimeEntry.table_name}.spent_on" => curr_date, 112 | "#{User.table_name}.id" => user_ids).ids 113 | 114 | missing_hours_users = TimeEntry.where(spent_on: curr_date, 115 | user_id: logged_time_user_ids).group(:user_id). 116 | having("sum_hours < #{num_working_hours}").sum('hours') 117 | 118 | users_missing_time = User.where(id: missing_hours_users.keys) 119 | 120 | users_missing_time.each do |user| 121 | leave_day = calculate_leave_days(user, curr_date, curr_date) 122 | 123 | unless leave_day > 0 124 | leave_weight = (num_min_working_hours - missing_hours_user[user.id].to_f) / num_min_working_hours 125 | UserLeave.create!(user: user, leave_type: default_leave_type, comments: 'Missing time log', fractional_leave: leave_weight) 126 | end 127 | end 128 | 129 | no_hours_user_ids = user_ids - logged_time_user_ids 130 | users_no_time = User.where(id: no_hours_user_ids) 131 | users_no_time.each do |user| 132 | leave_days = calculate_leave_days(user, curr_date, curr_date) 133 | unless leave_days > 0 134 | leave_weight = 1 135 | UserLeave.create!(user: user, leave_type: default_leave_type, comments: 'Missing time log', fractional_leave: leave_weight) 136 | end 137 | end 138 | end 139 | end 140 | 141 | def calculate_leave_days(user, start_date, end_date) 142 | leaves = user.user_leaves.where(leave_date: start_date..end_date) 143 | leaves.map do |leave| 144 | leave.fractional_leave > 1 ? 1 : leave.fractional_leave 145 | end.reduce(:+).to_f 146 | end 147 | private :calculate_leave_days 148 | 149 | 150 | def email_users_missing_hours(users, start_date, end_date) 151 | total_days = working_days(start_date, end_date + 1.day) 152 | num_working_hours = total_days * num_min_working_hours 153 | user_ids = users.map(&:id) 154 | 155 | logged_time_user_ids = User.joins(:time_entries).where("#{TimeEntry.table_name}.spent_on" => start_date..end_date, 156 | "#{User.table_name}.id" => user_ids).ids 157 | 158 | missing_hours_users = TimeEntry.where(spent_on: start_date..end_date, 159 | user_id: logged_time_user_ids).group(:user_id). 160 | having("sum(hours) < ?", num_working_hours - 0.001).sum("hours") 161 | users_missing_time = User.where(id: missing_hours_users.keys) 162 | 163 | users_missing_time.each do |user| 164 | leave_days = calculate_leave_days(user, start_date, end_date) 165 | 166 | if leave_days > 0 167 | #recalculate missing hours for user 168 | num_working_hours = (total_days - leave_days) * num_min_working_hours 169 | 170 | #take into account leave time for user 171 | missing_hours_user = TimeEntry.where(spent_on: start_date..end_date, 172 | user_id: user.id).group(:user_id). 173 | having("sum(hours) < ?", num_working_hours - 0.001).sum('hours') 174 | 175 | LeaveMailer.missing_time_log(user, start_date, end_date, 176 | missing_hours_user[user.id]).deliver if missing_hours_user[user.id] 177 | else 178 | LeaveMailer.missing_time_log(user, start_date, end_date, 179 | missing_hours_users[user.id]).deliver 180 | end 181 | end 182 | 183 | no_hours_user_ids = user_ids - logged_time_user_ids 184 | users_no_time = User.where(id: no_hours_user_ids) 185 | users_no_time.each do |user| 186 | leave_days = calculate_leave_days(user, start_date, end_date) 187 | if leave_days > 0 188 | #recalculate missing hours for user 189 | num_working_hours = (total_days - leave_days) * num_min_working_hours 190 | 191 | if num_working_hours > 0 192 | LeaveMailer.missing_time_log(user, start_date, end_date, 0).deliver 193 | end 194 | else 195 | LeaveMailer.missing_time_log(user, start_date, end_date, 0).deliver 196 | end 197 | end 198 | end 199 | 200 | def email_project_timesheets(projects, start_date, end_date) 201 | User.current.admin = true #set admin = true so that all time entries can be fetched 202 | for project in projects #get project Members for every project and send it to send_time_sheet_emial 203 | project_members = project.members 204 | for project_member in project_members 205 | email_project_timesheet(project_member.user, project_member.project, 206 | start_date, end_date) if project_member.roles. 207 | detect{|role|role.allowed_to?(:receive_timesheet_email)} 208 | end 209 | end 210 | end 211 | 212 | def email_project_timesheet(user, project, start_date, end_date) 213 | total_days = working_days(start_date, end_date + 1.day) 214 | return if total_days < 1 215 | #Make new object for time sheet get user ids form project and assign it to users 216 | 217 | billable_users_for_project = billable_users(project) 218 | time_entries_billable = TimeEntry.where(user_id: billable_users_for_project, 219 | spent_on: start_date..end_date). 220 | includes(:activity, :project, :user) 221 | time_entries_users = {} 222 | time_entries_billable.each do |time_entry| 223 | time_entries_users[time_entry.user] ||= {time_entries: [], user_leaves: []} 224 | time_entries_users[time_entry.user][:time_entries] << time_entry 225 | end 226 | 227 | if time_entries_users.size < billable_users_for_project.size 228 | @@default_activity ||= TimeEntryActivity.first 229 | #put empty time entries for users who have not logged their time 230 | billable_users_for_project.each do |billable_user| 231 | time_entries_users[billable_user] ||= { 232 | time_entries: [build_empty_time_entry(billable_user, project, end_date)], 233 | user_leaves: [] 234 | } 235 | end 236 | end 237 | 238 | time_entries_other = TimeEntry.where("user_id NOT IN (?) and project_id = ? and spent_on >= ? and spent_on <= ?", 239 | billable_users_for_project, project, start_date, end_date). 240 | includes(:activity, :project, :user) 241 | time_entries_other.each do |time_entry| 242 | time_entries_users[time_entry.user] ||= {time_entries: [], user_leaves: []} 243 | time_entries_users[time_entry.user][:time_entries] << time_entry 244 | end 245 | 246 | user_leaves = UserLeave.where(leave_date: start_date..end_date).includes(:user) 247 | user_leaves.each do |user_leave| 248 | time_entries_users[user_leave.user] ||= {time_entries: [], user_leaves: []} 249 | time_entries_users[user_leave.user][:user_leaves] << user_leave 250 | 251 | end 252 | 253 | timesheet_table = fetch_timesheet_table(time_entries_users) 254 | LeaveMailer.project_timesheet([user], timesheet_table, project.name, start_date, end_date).deliver 255 | end 256 | 257 | def email_group_timesheet(receiver_group, logger_group, start_date, end_date) 258 | #ignore project if no time is logged whatsoever 259 | total_days = working_days(start_date, end_date + 1.day) 260 | return if total_days < 1 261 | 262 | receiver_users = User.in_group(receiver_group) 263 | users = User.in_group(logger_group) 264 | #Make new object for time sheet get user ids form project and assign it to users 265 | 266 | time_entries_billable = TimeEntry.where(user_id: users.map(&:id), 267 | spent_on: start_date..end_date). 268 | includes(:activity, :project, :user) 269 | time_entries_users = {} 270 | time_entries_billable.each do |time_entry| 271 | time_entries_users[time_entry.user] ||= {time_entries: [], user_leaves: []} 272 | time_entries_users[time_entry.user][:time_entries] << time_entry 273 | end 274 | 275 | if time_entries_users.size < users.size 276 | @@default_activity ||= TimeEntryActivity.first 277 | #put empty time entries for users who have not logged their time 278 | users.each do |billable_user| 279 | time_entries_users[billable_user] ||= { 280 | time_entries: [build_empty_time_entry(billable_user, nil, end_date)], 281 | user_leaves: [] 282 | } 283 | end 284 | end 285 | 286 | time_entries_other = TimeEntry.where("user_id NOT IN (?) and spent_on >= ? and spent_on <= ?", 287 | users.map(&:id), start_date, end_date). 288 | includes(:activity, :project, :user) 289 | time_entries_other.each do |time_entry| 290 | time_entries_users[time_entry.user] ||= {time_entries: [], user_leaves: []} 291 | time_entries_users[time_entry.user][:time_entries] << time_entry 292 | end 293 | 294 | user_leaves = UserLeave.where(leave_date: start_date..end_date).includes(:user) 295 | user_leaves.each do |user_leave| 296 | time_entries_users[user_leave.user] ||= {time_entries: [], user_leaves: []} 297 | time_entries_users[user_leave.user][:user_leaves] << user_leave 298 | 299 | end 300 | 301 | timesheet_table = fetch_timesheet_table(time_entries_users) 302 | LeaveMailer.group_timesheet(receiver_users, timesheet_table, logger_group, start_date, end_date).deliver 303 | end 304 | 305 | def build_empty_time_entry(user_id, project, spent_on) 306 | @@default_activity ||= TimeEntryActivity.first 307 | time_entry = TimeEntry.new 308 | time_entry.user_id = user_id; time_entry.project = project 309 | time_entry.activity = @@default_activity; time_entry.hours = 0 310 | time_entry.spent_on = spent_on 311 | time_entry 312 | end 313 | private :build_empty_time_entry 314 | 315 | def billable_users(project) 316 | users = project.members.map(&:user) 317 | User.in_group(time_loggers_group).where(id: users.map(&:id)) 318 | end 319 | private :billable_users 320 | 321 | def fetch_timesheet_table(time_entries_users) 322 | timesheet_table = [] 323 | time_entries_users.each_pair do |user, time_leave_entries| 324 | time_entries = time_leave_entries[:time_entries]#: [], user_leaves: []} 325 | leaves = time_leave_entries[:user_leaves]#: [], user_leaves: []} 326 | project_total={} #hash to get the project total against particular project 327 | user_project_activity_hours = {} #hash to build a particular model Users=>Projects=>activities=>issues 328 | for time_entry in time_entries 329 | project_total[time_entry.project_id] ||= 0 330 | project_total[time_entry.project_id] += time_entry.hours 331 | user_project_activity_hours[user] ||= {} 332 | activity_hours = user_project_activity_hours[user][time_entry.project] ||= {} 333 | 334 | activity_hours[time_entry.activity.name] ||= [[],0] 335 | activity_hours[time_entry.activity.name][0] << time_entry.issue 336 | activity_hours[time_entry.activity.name][1] += time_entry.hours 337 | end 338 | 339 | user_project_activity_hours.each_pair do |user, project_activity_hours| 340 | user_total = 0 #total time spent by user in all projects 341 | project_activity_hours.each_pair do |project, activity_hours| 342 | project_total = 0 #total time spent on project 343 | activity_hours.each_pair do |activity_name, issue_hours| 344 | timesheet_table << {user: user, project: project, activity: activity_name, 345 | issues: issue_hours[0], hours: issue_hours[1], total_heading: nil, total: nil} 346 | project_total += issue_hours[1] 347 | end 348 | timesheet_table << {user: nil, project: nil, activity: nil, 349 | issues: nil, hours: nil, total_heading: 'Project Total', total: project_total} 350 | user_total += project_total 351 | end 352 | timesheet_table << {user: nil, project: nil, activity: nil, 353 | issues: nil, hours: nil, total_heading: 'Total Leaves', 354 | total: leaves.map(&:fractional_leave).reduce(&:+).to_f} 355 | timesheet_table << {user: nil, project: nil, activity: nil, 356 | issues: nil, hours: nil, total_heading: 'User Total', total: user_total} 357 | end 358 | end 359 | 360 | timesheet_table 361 | end 362 | private :fetch_timesheet_table 363 | 364 | def as_csv 365 | 366 | CSV.generate do |csv| 367 | csv << ["USER ID", "User Name", "Check In Time", "Check Out Time ", "Time Spent", "Comments"] ## Header values of CSV 368 | all.each do |emp| 369 | csv << [emp.user_id, emp.user.name, emp.check_in_time, emp.check_in_time, emp.time_spent, emp.comments] ##Row values of CSV 370 | end 371 | end 372 | end 373 | 374 | 375 | def autogenerate_checkout(missed_checkout) 376 | if missed_checkout.check_in_time.hour < 16 377 | missed_checkout.update_attributes(check_out_time: missed_checkout.check_in_time + 6.hours, 378 | comments: t(:auto_generated_comment),time_spent: 6*60) 379 | else 380 | #consider previously marked check_in_time as actually check_out time 381 | missed_checkout.update_attributes(check_in_time: missed_checkout.check_in_time - 6.hours, 382 | check_out_time: missed_checkout.check_in_time, 383 | comments: t(:auto_generated_comment),time_spent: 6*60) 384 | 385 | end 386 | end 387 | 388 | def import(file, options = {:headers => false, :col_sep => "\t"} ) 389 | unless file.nil? 390 | CSV.foreach(file.path, options) do |row| 391 | name = row[INDEX_ZK_NAME] 392 | date = row[INDEX_ZK_DATETIME] 393 | first_last_name = name.split(" ") 394 | user = User.where(firstname: first_last_name.first, lastname: first_last_name.last).first 395 | logger.debug "#{name}#{date.to_datetime}#{user}" 396 | 397 | unless name && date && user 398 | # missing name or date or no records found in database 399 | next 400 | end 401 | 402 | # date = "#{date} #{Time.zone}".to_datetime 403 | date=date.to_datetime 404 | missed_checkouts = UserTimeCheck. 405 | where(['user_id = ? AND check_in_time < ? AND check_out_time IS NULL', 406 | user.id, date.beginning_of_day]) 407 | missed_checkouts.each do |missed_checkout| 408 | autogenerate_checkout(missed_checkout) 409 | end 410 | 411 | checkin_timechecks = UserTimeCheck. 412 | where(['user_id = ? AND check_in_time < ? AND check_in_time > ? AND check_out_time IS NULL', 413 | user.id, date.end_of_day, date.beginning_of_day]) 414 | logger.debug checkin_timechecks 415 | if checkin_timechecks.empty? 416 | UserTimeCheck.create(user_id: user.id, check_in_time: date,check_out_time: nil) 417 | logger.debug "#{'*'*50}#{user}#{'*'*50}" 418 | else 419 | user_time_check = checkin_timechecks.first 420 | checked_time = date.to_time - user_time_check.check_in_time.to_time 421 | difference = Time.at(checked_time).utc.strftime("%H").to_i 422 | if user_time_check.check_in_time.to_date == date.to_date 423 | if difference > 1 && difference < 16 424 | #difference between first checkin and current pulled time is less than 16 and greater than 1 (meaning) not by mistake), so asssume checkout 425 | user_time_check.update_attributes(check_out_time: date,time_spent: checked_time/60) 426 | logger.debug '&'*50 427 | elsif difference > 16 428 | #difference between previous checkout and current is greater than 16, meaning that user missed out 429 | user_time_check.update_attributes(check_out_time: user_time_check.check_in_time + 6.hours, 430 | comments: t(:auto_generated_comment),time_spent: 6*60) 431 | # UserTimeCheck.create(user_id: user.id, check_in_time: date) 432 | logger.debug '$'*50 433 | 434 | else 435 | #ignore the check-in 436 | end 437 | elsif date.hour < 8 && difference > 1 && difference < 16 438 | #allow checkin/checkout for different days 439 | user_time_check.update_attributes(check_out_time: date,time_spent: checked_time/60) 440 | else 441 | #difference between previous checkout and current is greater than 16, meaning that user missed out 442 | autogenerate_checkout(user_time_check) 443 | UserTimeCheck.create(user_id: user.id, check_in_time: date) 444 | p '$'*50 445 | end 446 | end 447 | end 448 | end 449 | end 450 | end 451 | end 452 | 453 | --------------------------------------------------------------------------------
154 | 155 | <%= t(:label_trackers)%> 156 | 157 | 158 |
<%= text_field_tag('settings[tracker_names]', settings['tracker_names']) %>
<%= t(:label_tracker_instructions )%>
159 |