Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Grouping by project version, sorting by some columns, XLSX export #5

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions app/controllers/timesheet_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,22 @@ class TimesheetController < ApplicationController
helper :timelog

SessionKey = 'timesheet_filter'
SessionKey2 = 'timesheet_session'

verify :method => :delete, :only => :reset, :render => {:nothing => true, :status => :method_not_allowed }

# save filter values into session because of sorting
def save_session(values)
session[SessionKey2] = values
end

def load_session
values = session[SessionKey2]
return values
end

def index
session[SessionKey2] = nil
load_filters_from_session
unless @timesheet
@timesheet ||= Timesheet.new
Expand All @@ -30,6 +42,33 @@ def index
end

def report
@p = params
if params[:sort].nil?
save_session(params[:timesheet])
params[:sort] = "date"
params[:type] = "asc"
else
sort = params[:sort]
type = params[:type]
params[:timesheet] = load_session

# new ordering
case sort
when "date"
params[:timesheet][:order] = "spent_on #{type}"
when "member"
params[:timesheet][:order] = "users.firstname #{type}"
when "activity"
params[:timesheet][:order] = "enumerations.name #{type}"
when "hours"
params[:timesheet][:order] = "hours #{type}"
else
params[:timesheet][:order] = "spent_on ASC"
end
end

@ts = params[:timesheet]

if params && params[:timesheet]
@timesheet = Timesheet.new( params[:timesheet] )
else
Expand Down Expand Up @@ -84,6 +123,7 @@ def report

respond_to do |format|
format.html { render :action => 'details', :layout => false if request.xhr? }
format.xlsx { send_file @timesheet.to_xlsx }
format.csv { send_data @timesheet.to_csv, :filename => 'timesheet.csv', :type => "text/csv" }
end
end
Expand Down
36 changes: 36 additions & 0 deletions app/helpers/timesheet_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@ def showing_users(users)
l(:timesheet_showing_users) + users.collect(&:name).join(', ')
end

# generate link with parameters to order by specific field
def link_to_sort_by(label, field)
unless params[:sort].nil?
type = params[:type] == "desc" ? "asc" : "desc" if params[:sort] == field
else
type = "asc"
end

css = ""
# css class for arrow must be opossite to new sort type
css = type == "desc" ? "asc" : "desc" if field == params[:sort]

return link_to label,
{:controller => 'timesheet',
:action => 'report',
:sort => field,
:type => type},
:class => css
end

def permalink_to_timesheet(timesheet)
link_to(l(:timesheet_permalink),
:controller => 'timesheet',
Expand All @@ -21,6 +41,18 @@ def link_to_csv_export(timesheet)
:method => 'post',
:class => 'icon icon-timesheet')
end

def link_to_xlsx_export(timesheet)
link_to('XLSX',
{
:controller => 'timesheet',
:action => 'report',
:format => 'xlsx',
:timesheet => timesheet.to_param
},
:method => 'post',
:class => 'icon, icon-timesheet')
end

def toggle_issue_arrows(issue_id)
js = "toggleTimeEntries('#{issue_id}'); return false;"
Expand Down Expand Up @@ -70,4 +102,8 @@ def user_options(timesheet)
selected_users)

end

# def number_with_custom_delimiter(number)
# number.to_s.gsub('.', Setting.plugin_timesheet_plugin['custom_delimiter'])
# end
end
138 changes: 129 additions & 9 deletions app/models/timesheet.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class Timesheet
attr_accessor :date_from, :date_to, :projects, :activities, :users, :allowed_projects, :period, :period_type
class Timesheet
attr_accessor :date_from, :date_to, :projects, :activities, :users, :allowed_projects, :period, :period_type, :version, :order

# Time entries on the Timesheet in the form of:
# project.name => {:logs => [time entries], :users => [users shown in logs] }
Expand All @@ -15,7 +15,8 @@ class Timesheet
ValidSortOptions = {
:project => 'Project',
:user => 'User',
:issue => 'Issue'
:issue => 'Issue',
:version => 'Version'
}

ValidPeriodType = {
Expand Down Expand Up @@ -63,6 +64,7 @@ def initialize(options = { })
self.period_type = ValidPeriodType[:free_period]
end
self.period = options[:period] || nil
self.order = options[:order] || nil
end

# Gets all the time_entries for all the projects
Expand All @@ -75,6 +77,8 @@ def fetch_time_entries
fetch_time_entries_by_user
when :issue
fetch_time_entries_by_issue
when :version
fetch_time_entries_by_version
else
fetch_time_entries_by_project
end
Expand Down Expand Up @@ -133,7 +137,7 @@ def to_csv

# Write the CSV based on the group/sort
case sort
when :user, :project
when :user, :project, :version
time_entries.sort.each do |entryname, entry|
entry[:logs].each do |e|
csv << time_entry_to_csv(e)
Expand All @@ -152,6 +156,28 @@ def to_csv
end
end

def to_xlsx
path ="#{RAILS_ROOT}/files/timesheet.xlsx"
FileUtils.rm(path) if FileTest.exists?(path)

case sort
when :user, :project, :version
SimpleXlsx::Serializer.new(path) do |doc|
doc.add_sheet("Sesit") do |sheet|
sheet.add_row csv_header
time_entries.sort.each do |entryname, entry|
entry[:logs].each do |e|
sheet.add_row time_entry_to_csv(e)
end
end
end
end
end

return path

end

def self.viewable_users
if Setting['plugin_timesheet_plugin'].present? && Setting['plugin_timesheet_plugin']['user_status'] == 'all'
user_scope = User.all
Expand Down Expand Up @@ -248,31 +274,60 @@ def time_entries_for_all_users(project)
return project.time_entries.find(:all,
:conditions => self.conditions(self.users),
:include => self.includes,
:order => "spent_on ASC")
:order => self.order)
end

def time_entries_for_current_user(project)
return project.time_entries.find(:all,
:conditions => self.conditions(User.current.id),
:include => self.includes,
:include => [:activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
:order => "spent_on ASC")
:order => self.order)
end

def empty_version_time_entries_for_all_users(project)
return project.time_entries.find(:all,
:conditions => self.conditions(self.users, "version_id is NULL"),
:include => self.includes,
:order => self.order)
end

def empty_version_time_entries_for_current_user(project)
return project.time_entries.find(:all,
:conditions => self.conditions(User.current.id, "version_id is NULL"),
:include => self.includes,
:order => self.order)
end

def version_time_entries_for_all_users(version)
return version.time_entries.find(:all,
:conditions => self.conditions(self.users),
:include => self.includes,
:order => self.order)
end

def version_time_entries_for_current_user(version)
return version.time_entries.find(:all,
:conditions => self.conditions(User.current.id),
:include => self.includes,
:include => [:activity, :user, {:issue => [:tracker, :assigned_to, :priority]}],
:order => self.order)
end

def issue_time_entries_for_all_users(issue)
return issue.time_entries.find(:all,
:conditions => self.conditions(self.users),
:include => self.includes,
:include => [:activity, :user],
:order => "spent_on ASC")
:order => self.order)
end

def issue_time_entries_for_current_user(issue)
return issue.time_entries.find(:all,
:conditions => self.conditions(User.current.id),
:include => self.includes,
:include => [:activity, :user],
:order => "spent_on ASC")
:order => self.order)
end

def time_entries_for_user(user, options={})
Expand All @@ -281,9 +336,70 @@ def time_entries_for_user(user, options={})
return TimeEntry.find(:all,
:conditions => self.conditions([user], extra_conditions),
:include => self.includes,
:order => "spent_on ASC"
:order => self.order
)
end

def fetch_time_entries_by_version
self.projects.each do |project|
project.versions.each do |version|
unless version.nil?
logs = []
users = []

if User.current.admin?
# Administrators can see all time entries
logs = version_time_entries_for_all_users(version)
users = logs.collect(&:user).uniq.sort
elsif User.current.allowed_to_on_single_potentially_archived_project?(:see_project_timesheets, project)
# Users with the Role and correct permission can see all time entries
logs = time_entries_for_all_users(project)
users = logs.collect(&:user).uniq.sort
elsif User.current.allowed_to_on_single_potentially_archived_project?(:view_time_entries, project)
# Users with permission to see their time entries
logs = version_time_entries_for_current_user(version)
users = logs.collect(&:user).uniq.sort
else
# Rest can see nothing
end

# Append project and version name
unless logs.empty?
self.time_entries[project.name + ' / v: ' + version.name] = { :logs => logs, :users => users }
end
end
end

logs = []
users = []
if User.current.admin?
# Administrators can see all time entries
logs = empty_version_time_entries_for_all_users(project)
users = logs.collect(&:user).uniq.sort
elsif User.current.allowed_to_on_single_potentially_archived_project?(:see_project_timesheets, project)
# Users with the Role and correct permission can see all time entries
logs = time_entries_for_all_users(project)
users = logs.collect(&:user).uniq.sort
elsif User.current.allowed_to_on_single_potentially_archived_project?(:view_time_entries, project)
# Users with permission to see their time entries
logs = empty_version_time_entries_for_current_user(project)
users = logs.collect(&:user).uniq.sort
else
# Rest can see nothing
end

# Append the parent project name
if project.parent.nil?
unless logs.empty?
self.time_entries[project.name] = { :logs => logs, :users => users }
end
else
unless logs.empty?
self.time_entries[project.parent.name + ' / ' + project.name] = { :logs => logs, :users => users }
end
end
end
end

def fetch_time_entries_by_project
self.projects.each do |project|
Expand Down Expand Up @@ -391,4 +507,8 @@ def fetch_time_entries_by_issue
def l(*args)
I18n.t(*args)
end

# def number_with_custom_delimiter(number)
# number.to_s.gsub('.', Setting.plugin_timesheet_plugin['custom_delimiter'])
# end
end
5 changes: 4 additions & 1 deletion app/views/settings/_timesheet_settings.rhtml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

<p><label for="settings_precision">Number precision</label><%= text_field_tag 'settings[precision]', @settings['precision'] %></p>



<p>
<label for="settings_project_status"><%= l(:label_show_project_status) %></label>
<%= select_tag('settings[project_status]',
Expand All @@ -16,4 +18,5 @@
options_for_select({
l(:text_active_users) => 'active',
l(:text_all_users) => 'all'}, @settings['user_status'])) %>
</p>
</p>

6 changes: 4 additions & 2 deletions app/views/timesheet/_time_entry.rhtml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
<td align="center"><%= check_box_tag 'ids[]', time_entry.id, false, { :class => 'checkbox' } %></td>
<td align="center"><%= format_date(time_entry.spent_on) %></td>
<td align="center"><%= time_entry.user.name %></td>
<td align="center"><% time_entry.user.groups.each do |group| %><%= group.name %> <% end %></td>
<td align="center"><%= time_entry.activity.name %></td>
<td align="center"><%= time_entry.project.name %></td>
<td align="center"><%= link_to_project time_entry.project %></td>
<td align="center"><%= time_entry.version %></td>
<td align="center">
<% if time_entry.issue %>
<div class="tooltip">
Expand All @@ -16,7 +18,7 @@
<% end %>
</td>
<td><%=h time_entry.comments %></td>
<td align="center"><strong><%= number_with_precision(time_entry.hours, @precision) %></strong></td>
<td align="center"><strong><%= number_with_precision(time_entry.hours, :precision => @precision) %></strong></td>
<%= Redmine::Hook.call_hook(:plugin_timesheet_views_timesheet_time_entry, {:time_entry => time_entry, :precision => @precision }) %>
<td align="center">
<% if time_entry.editable_by?(User.current) -%>
Expand Down
12 changes: 7 additions & 5 deletions app/views/timesheet/_timesheet_group.rhtml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
<%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(Element.up(this, "table")); return false;',
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}", :class => 'toggle-all' %>
</th>
<th width="8%"><%= l(:label_date) %></th>
<th width="10%"><%= l(:label_member) %></th>
<th width="15%"><%= l(:label_activity) %></th>
<th width="15%"><%= l(:label_project) %></th>
<th width="8%"><%= link_to_sort_by l(:label_date), "date" %></th>
<th width="10%"><%= link_to_sort_by l(:label_member), "member" %></th>
<th width="10%"><%= l(:label_group) %></td>
<th width="10%"><%= link_to_sort_by l(:label_activity), "activity" %></th>
<th width="10%"><%= l(:label_project) %></th>
<th width="10%"><%= l(:label_version) %></th>
<th width="10%"><%= l(:label_issue) %></th>
<th width="25%"><%= l(:field_comments) %></th>
<th width="10%"><%= l(:field_hours) %></th>
<th width="10%"><%= link_to_sort_by l(:field_hours), "hours" %></th>
<%= Redmine::Hook.call_hook(:plugin_timesheet_views_timesheet_group_header, { }) %>
<th></th>
</thead>
Expand Down
Loading