There's no aggregation reports for now, it's just possible to see all time entries for a project or an issue. A new "activities" enumeration is added. Permission for a role to log time must be set (new "Time tracking" section in role permissions screen). git-svn-id: http://redmine.rubyforge.org/svn/trunk@368 e93f8b46-1217-0410-a6f0-8f06a7374b81pull/351/head
parent
7cf2d889d8
commit
8d54d97007
@ -0,0 +1,80 @@ |
||||
class TimelogController < ApplicationController |
||||
layout 'base' |
||||
|
||||
before_filter :find_project |
||||
before_filter :authorize, :only => :edit |
||||
before_filter :check_project_privacy, :only => :details |
||||
|
||||
helper :sort |
||||
include SortHelper |
||||
|
||||
def details |
||||
sort_init 'spent_on', 'desc' |
||||
sort_update |
||||
|
||||
@entries = (@issue ? @issue : @project).time_entries.find(:all, :include => [:activity, :user, {:issue => [:tracker, :assigned_to, :priority]}], :order => sort_clause) |
||||
|
||||
@total_hours = @entries.inject(0) { |sum,entry| sum + entry.hours } |
||||
@owner_id = logged_in_user ? logged_in_user.id : 0 |
||||
|
||||
send_csv and return if 'csv' == params[:export] |
||||
render :action => 'details', :layout => false if request.xhr? |
||||
end |
||||
|
||||
def edit |
||||
render_404 and return if @time_entry && @time_entry.user != logged_in_user |
||||
@time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => logged_in_user, :spent_on => Date.today) |
||||
@time_entry.attributes = params[:time_entry] |
||||
if request.post? and @time_entry.save |
||||
flash[:notice] = l(:notice_successful_update) |
||||
redirect_to :action => 'details', :project_id => @time_entry.project, :issue_id => @time_entry.issue |
||||
return |
||||
end |
||||
@activities = Enumeration::get_values('ACTI') |
||||
end |
||||
|
||||
private |
||||
def find_project |
||||
if params[:id] |
||||
@time_entry = TimeEntry.find(params[:id]) |
||||
@project = @time_entry.project |
||||
elsif params[:issue_id] |
||||
@issue = Issue.find(params[:issue_id]) |
||||
@project = @issue.project |
||||
elsif params[:project_id] |
||||
@project = Project.find(params[:project_id]) |
||||
else |
||||
render_404 |
||||
return false |
||||
end |
||||
end |
||||
|
||||
def send_csv |
||||
ic = Iconv.new(l(:general_csv_encoding), 'UTF-8') |
||||
export = StringIO.new |
||||
CSV::Writer.generate(export, l(:general_csv_separator)) do |csv| |
||||
# csv header fields |
||||
headers = [l(:field_spent_on), |
||||
l(:field_user), |
||||
l(:field_activity), |
||||
l(:field_issue), |
||||
l(:field_hours), |
||||
l(:field_comment) |
||||
] |
||||
csv << headers.collect {|c| ic.iconv(c) } |
||||
# csv lines |
||||
@entries.each do |entry| |
||||
fields = [l_date(entry.spent_on), |
||||
entry.user.name, |
||||
entry.activity.name, |
||||
(entry.issue ? entry.issue.id : nil), |
||||
entry.hours, |
||||
entry.comment |
||||
] |
||||
csv << fields.collect {|c| ic.iconv(c.to_s) } |
||||
end |
||||
end |
||||
export.rewind |
||||
send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv') |
||||
end |
||||
end |
@ -0,0 +1,2 @@ |
||||
module TimelogHelper |
||||
end |
@ -0,0 +1,33 @@ |
||||
class TimeEntry < ActiveRecord::Base |
||||
# could have used polymorphic association |
||||
# project association here allows easy loading of time entries at project level with one database trip |
||||
belongs_to :project |
||||
belongs_to :issue |
||||
belongs_to :user |
||||
belongs_to :activity, :class_name => 'Enumeration', :foreign_key => :activity_id |
||||
|
||||
attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek |
||||
|
||||
validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on |
||||
validates_numericality_of :hours, :allow_nil => true |
||||
validates_length_of :comment, :maximum => 255 |
||||
|
||||
def before_validation |
||||
self.project = issue.project if issue && project.nil? |
||||
end |
||||
|
||||
def validate |
||||
errors.add :hours, :activerecord_error_invalid if hours && hours < 0 |
||||
errors.add :project_id, :activerecord_error_invalid if project.nil? |
||||
errors.add :issue_id, :activerecord_error_invalid if (issue_id && !issue) || (issue && project!=issue.project) |
||||
end |
||||
|
||||
# tyear, tmonth, tweek assigned where setting spent_on attributes |
||||
# these attributes make time aggregations easier |
||||
def spent_on=(date) |
||||
super |
||||
self.tyear = spent_on ? spent_on.year : nil |
||||
self.tmonth = spent_on ? spent_on.month : nil |
||||
self.tweek = spent_on ? spent_on.cweek : nil |
||||
end |
||||
end |
@ -0,0 +1,51 @@ |
||||
<div class="contextual"> |
||||
<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time' %> |
||||
</div> |
||||
|
||||
<h2><%= l(:label_spent_time) %></h2> |
||||
|
||||
<h3><%= link_to(@project.name, {:action => 'details', :project_id => @project}) if @project %> |
||||
<%= "/ " + link_to("#{@issue.tracker.name} ##{@issue.id}", {:action => 'details', :issue_id => @issue }) + ": #{h(@issue.subject)}" if @issue %></h3> |
||||
|
||||
<h3 class="textright"><%= l(:label_total) %>: <%= lwr(:label_f_hour, @total_hours) %></h3> |
||||
|
||||
<% unless @entries.empty? %> |
||||
<table class="list"> |
||||
<thead> |
||||
<%= sort_header_tag('spent_on', :caption => l(:label_date)) %> |
||||
<%= sort_header_tag('user_id', :caption => l(:label_member)) %> |
||||
<%= sort_header_tag('activity_id', :caption => l(:label_activity)) %> |
||||
<%= sort_header_tag('issue_id', :caption => l(:label_issue)) %> |
||||
<th><%= l(:label_comment) %></th> |
||||
<%= sort_header_tag('hours', :caption => l(:field_hours)) %> |
||||
<th></th> |
||||
</thead> |
||||
<tbody> |
||||
<% @entries.each do |entry| %> |
||||
<tr class="<%= cycle("odd", "even") %>"> |
||||
<td align="center"><%= format_date(entry.spent_on) %></td> |
||||
<td align="center"><%= entry.user.name %></td> |
||||
<td align="center"><%= entry.activity.name %></td> |
||||
<td align="center"> |
||||
<% if entry.issue %> |
||||
<div class="tooltip"> |
||||
<%= link_to "#{entry.issue.tracker.name} ##{entry.issue.id}", {:action => 'details', :issue_id => entry.issue } %> |
||||
<span class="tip"> |
||||
<%= render :partial => "issues/tooltip", :locals => { :issue => entry.issue }%> |
||||
</span> |
||||
</div> |
||||
<% end %> |
||||
</td> |
||||
<td><%=h entry.comment %></td> |
||||
<td align="center"><strong><%= entry.hours %></strong></td> |
||||
<td align="center"><%= link_to_if_authorized(l(:button_edit), {:controller => 'timelog', :action => 'edit', :id => entry}, :class => "icon icon-edit") if entry.user_id == @owner_id %></td> |
||||
</tr> |
||||
<% end %> |
||||
</tbdoy> |
||||
</table> |
||||
|
||||
<div class="contextual"> |
||||
<%= l(:label_export_to) %> |
||||
<%= link_to 'CSV', params.update(:export => 'csv'), :class => 'icon icon-csv' %> |
||||
</div> |
||||
<% end %> |
@ -0,0 +1,23 @@ |
||||
<h2><%= l(:label_spent_time) %></h2> |
||||
|
||||
<% labelled_tabular_form_for :time_entry, @time_entry, :url => {:action => 'edit', :project_id => @time_entry.project} do |f| %> |
||||
<%= error_messages_for 'time_entry' %> |
||||
|
||||
<div class="box"> |
||||
<p><%= f.text_field :issue_id, :size => 6 %> <em><%= h("#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}") if @time_entry.issue %></em></p> |
||||
<p><%= f.text_field :spent_on, :size => 10, :required => true %><%= calendar_for('time_entry_spent_on') %></p> |
||||
<p><%= f.text_field :hours, :size => 6, :required => true %></p> |
||||
<p><%= f.text_field :comment, :size => 100 %></p> |
||||
<p><%= f.select :activity_id, (@activities.collect {|p| [p.name, p.id]}), :required => true %></p> |
||||
</div> |
||||
|
||||
<%= submit_tag l(:button_save) %> |
||||
|
||||
<% end %> |
||||
|
||||
<% content_for :header_tags do %> |
||||
<%= javascript_include_tag 'calendar/calendar' %> |
||||
<%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %> |
||||
<%= javascript_include_tag 'calendar/calendar-setup' %> |
||||
<%= stylesheet_link_tag 'calendar' %> |
||||
<% end %> |
@ -0,0 +1,24 @@ |
||||
class CreateTimeEntries < ActiveRecord::Migration |
||||
def self.up |
||||
create_table :time_entries do |t| |
||||
t.column :project_id, :integer, :null => false |
||||
t.column :user_id, :integer, :null => false |
||||
t.column :issue_id, :integer |
||||
t.column :hours, :float, :null => false |
||||
t.column :comment, :string, :limit => 255 |
||||
t.column :activity_id, :integer, :null => false |
||||
t.column :spent_on, :date, :null => false |
||||
t.column :tyear, :integer, :null => false |
||||
t.column :tmonth, :integer, :null => false |
||||
t.column :tweek, :integer, :null => false |
||||
t.column :created_on, :datetime, :null => false |
||||
t.column :updated_on, :datetime, :null => false |
||||
end |
||||
add_index :time_entries, [:project_id], :name => :time_entries_project_id |
||||
add_index :time_entries, [:issue_id], :name => :time_entries_issue_id |
||||
end |
||||
|
||||
def self.down |
||||
drop_table :time_entries |
||||
end |
||||
end |
@ -0,0 +1,9 @@ |
||||
class AddTimelogPermissions < ActiveRecord::Migration |
||||
def self.up |
||||
Permission.create :controller => "timelog", :action => "edit", :description => "button_log_time", :sort => 1520, :is_public => false, :mail_option => 0, :mail_enabled => 0 |
||||
end |
||||
|
||||
def self.down |
||||
Permission.find_by_controller_and_action('timelog', 'edit').destroy |
||||
end |
||||
end |
After Width: | Height: | Size: 993 B |
Loading…
Reference in new issue