]> source.dussan.org Git - redmine.git/commitdiff
Simple time tracking functionality added. Time can be logged at issue or project...
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Fri, 23 Mar 2007 12:22:31 +0000 (12:22 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Fri, 23 Mar 2007 12:22:31 +0000 (12:22 +0000)
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-8f06a7374b81

24 files changed:
app/controllers/reports_controller.rb
app/controllers/timelog_controller.rb [new file with mode: 0644]
app/helpers/sort_helper.rb
app/helpers/timelog_helper.rb [new file with mode: 0644]
app/models/enumeration.rb
app/models/issue.rb
app/models/permission.rb
app/models/project.rb
app/models/time_entry.rb [new file with mode: 0644]
app/views/issues/show.rhtml
app/views/reports/issue_report.rhtml
app/views/timelog/details.rhtml [new file with mode: 0644]
app/views/timelog/edit.rhtml [new file with mode: 0644]
db/migrate/032_create_time_entries.rb [new file with mode: 0644]
db/migrate/033_add_timelog_permissions.rb [new file with mode: 0644]
lang/de.yml
lang/en.yml
lang/es.yml
lang/fr.yml
lang/it.yml
lang/ja.yml
lib/tasks/load_default_data.rake
public/images/time.png [new file with mode: 0644]
public/stylesheets/application.css

index fa00f7c30e22cf32770ead789cddc2851fc0602c..ab648460b8d075ba0b7abde0a095f5e95157e7a6 100644 (file)
@@ -57,6 +57,7 @@ class ReportsController < ApplicationController
       issues_by_priority
       issues_by_category
       issues_by_author
+      @total_hours = @project.time_entries.sum(:hours)
       render :template => "reports/issue_report"
     end
   end  
diff --git a/app/controllers/timelog_controller.rb b/app/controllers/timelog_controller.rb
new file mode 100644 (file)
index 0000000..5902390
--- /dev/null
@@ -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
index 300fbfe54680dc1925a5476f92b9e38e2024c4a9..eac0d4d3f77a96da9e1d9a7ebe8617703b7b26f9 100644 (file)
@@ -107,10 +107,10 @@ module SortHelper
       order = 'desc' # changed for desc order by default
     end
     caption = titleize(Inflector::humanize(column)) unless caption
-    params = {:params => {:sort_key => column, :sort_order => order}}
+    #params = {:params => {:sort_key => column, :sort_order => order}}
     link_to_remote(caption,
-                  {:update => "content", :url => { :sort_key => column, :sort_order => order}},
-                  {:href => url_for(:params => { :sort_key => column, :sort_order => order})}) +
+                  {:update => "content", :url => params.update( :sort_key => column, :sort_order => order)},
+                  {:href => url_for(:params => params.update(:sort_key => column, :sort_order => order))}) +
     (icon ? nbsp(2) + image_tag(icon) : '')
   end
 
diff --git a/app/helpers/timelog_helper.rb b/app/helpers/timelog_helper.rb
new file mode 100644 (file)
index 0000000..9054ccd
--- /dev/null
@@ -0,0 +1,2 @@
+module TimelogHelper
+end
index 251f00fbe4e254ff90a916bf35bc922f48dfcef0..de85260676d47dbe3460f757c2c08c9e9834239a 100644 (file)
@@ -24,7 +24,8 @@ class Enumeration < ActiveRecord::Base
        
        OPTIONS = {
          "IPRI" => :enumeration_issue_priorities,
-      "DCAT" => :enumeration_doc_categories
+      "DCAT" => :enumeration_doc_categories,
+      "ACTI" => :enumeration_activities
        }.freeze
        
        def self.get_values(option)
@@ -42,6 +43,8 @@ private
       raise "Can't delete enumeration" if Issue.find(:first, :conditions => ["priority_id=?", self.id])
     when "DCAT"
       raise "Can't delete enumeration" if Document.find(:first, :conditions => ["category_id=?", self.id])
+    when "ACTI"
+      raise "Can't delete enumeration" if TimeEntry.find(:first, :conditions => ["activity_id=?", self.id])
     end
   end
 end
index 1400718722efdf6ba2696f5b2c8da29d14d7c3ef..dd512017f61584508e550eb85040b555aa7c0197 100644 (file)
@@ -28,7 +28,7 @@ class Issue < ActiveRecord::Base
 
   has_many :journals, :as => :journalized, :dependent => :destroy
   has_many :attachments, :as => :container, :dependent => :destroy
-
+  has_many :time_entries
   has_many :custom_values, :dependent => :delete_all, :as => :customized
   has_many :custom_fields, :through => :custom_values
 
@@ -91,6 +91,10 @@ class Issue < ActiveRecord::Base
     self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
     @current_journal
   end
+  
+  def spent_hours
+    @spent_hours ||= time_entries.sum(:hours) || 0
+  end
 
 private
   # Creates an history for the issue
index 3ce40d116ded8f0da9511ce153ab7a033571980a..23f8a5e91488bc47ef02d8522a1efa629b7e19d0 100644 (file)
@@ -30,7 +30,8 @@ class Permission < ActiveRecord::Base
     1100 => :label_news_plural,
     1200 => :label_document_plural,
     1300 => :label_attachment_plural,
-    1400 => :label_repository
+    1400 => :label_repository,
+    1500 => :label_time_tracking
   }.freeze
   
   @@cached_perms_for_public = nil
index 3579921b769c7a2b4071ae240092957793c27311..10730ed1e33f1ae4a9851e2ce3a2ee5cca62c9a8 100644 (file)
@@ -21,6 +21,7 @@ class Project < ActiveRecord::Base
   has_many :users, :through => :members
   has_many :custom_values, :dependent => :delete_all, :as => :customized
   has_many :issues, :dependent => :destroy, :order => "#{Issue.table_name}.created_on DESC", :include => [:status, :tracker]
+  has_many :time_entries, :dependent => :delete_all
   has_many :queries, :dependent => :delete_all
   has_many :documents, :dependent => :destroy
   has_many :news, :dependent => :delete_all, :include => :author
diff --git a/app/models/time_entry.rb b/app/models/time_entry.rb
new file mode 100644 (file)
index 0000000..4f2c561
--- /dev/null
@@ -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
index 43e959c1b95eed127d6e7c5a6e738e53641c9f3f..98e88671cbd87953ff2c330246d2fa7b1b286a5c 100644 (file)
@@ -28,7 +28,8 @@
 </tr>
 <tr>
     <td><b><%=l(:field_fixed_version)%> :</b></td><td><%= @issue.fixed_version ? @issue.fixed_version.name : "-" %></td>
-    <td></td><td></td>
+    <td><b><%=l(:label_spent_time)%> :</b></td>
+    <td><%= @issue.spent_hours > 0 ? (link_to lwr(:label_f_hour, @issue.spent_hours), {:controller => 'timelog', :action => 'details', :issue_id => @issue}, :class => 'icon icon-time') : "-" %></td>
 </tr>
 <tr>
 <% n = 0
@@ -51,6 +52,7 @@ end %>
 
 <div class="contextual">
 <%= link_to_if_authorized l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue}, :class => 'icon icon-edit' %>
+<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %>
 <%= link_to_if_authorized l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id }, :class => 'icon icon-move' %>
 <%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
 </div>
index 8ad45afcf5c55872fceb3afab8d67ab4991ec97b..8f832f87e10a78e81e6523ecb5591b6611d9897e 100644 (file)
@@ -1,5 +1,6 @@
 <h2><%=l(:label_report_plural)%></h2>
 
+<div class="splitcontentleft">
 <div class="contextual">
 <%= link_to_if_authorized l(:label_query_new), {:controller => 'projects', :action => 'add_query', :id => @project}, :class => 'icon icon-add' %>
 </div>
     <li><%= link_to query.name, :controller => 'projects', :action => 'list_issues', :id => @project, :query_id => query %></li>
 <% end %>
 </ul>
+</div>
+<div class="splitcontentright">
+<% if @total_hours %>
+<h3 class="textright"><%= l(:label_spent_time) %>:
+<%= link_to(lwr(:label_f_hour, @total_hours), {:controller => 'timelog', :action => 'details', :project_id => @project}, :class => 'icon icon-time') %>
+</h3>
+<% end %>
+</div>
+
+<div class="clear"></div>
 
 <div class="splitcontentleft">
 <h3><%=l(:field_tracker)%>&nbsp;&nbsp;<%= link_to image_tag('zoom_in.png'), :detail => 'tracker' %></h3>
diff --git a/app/views/timelog/details.rhtml b/app/views/timelog/details.rhtml
new file mode 100644 (file)
index 0000000..f85eb0f
--- /dev/null
@@ -0,0 +1,51 @@
+<div class="contextual">\r
+<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time' %>\r
+</div>\r
+\r
+<h2><%= l(:label_spent_time) %></h2>\r
+\r
+<h3><%= link_to(@project.name, {:action => 'details', :project_id => @project}) if @project %>\r
+<%= "/ " + link_to("#{@issue.tracker.name} ##{@issue.id}", {:action => 'details', :issue_id => @issue }) + ": #{h(@issue.subject)}" if @issue %></h3>\r
+\r
+<h3 class="textright"><%= l(:label_total) %>: <%= lwr(:label_f_hour, @total_hours) %></h3>\r
+\r
+<% unless @entries.empty? %>\r
+<table class="list">\r
+<thead>\r
+<%= sort_header_tag('spent_on', :caption => l(:label_date)) %>\r
+<%= sort_header_tag('user_id', :caption => l(:label_member)) %>\r
+<%= sort_header_tag('activity_id', :caption => l(:label_activity)) %>\r
+<%= sort_header_tag('issue_id', :caption => l(:label_issue)) %>\r
+<th><%= l(:label_comment) %></th>\r
+<%= sort_header_tag('hours', :caption => l(:field_hours)) %>\r
+<th></th>\r
+</thead>\r
+<tbody>\r
+<% @entries.each do |entry| %>\r
+<tr class="<%= cycle("odd", "even") %>">\r
+<td align="center"><%= format_date(entry.spent_on) %></td>\r
+<td align="center"><%= entry.user.name %></td>\r
+<td align="center"><%= entry.activity.name %></td>\r
+<td align="center">\r
+    <% if entry.issue %>\r
+    <div class="tooltip">\r
+    <%= link_to "#{entry.issue.tracker.name} ##{entry.issue.id}", {:action => 'details', :issue_id => entry.issue } %>\r
+    <span class="tip">\r
+    <%= render :partial => "issues/tooltip", :locals => { :issue => entry.issue }%>\r
+    </span>            \r
+       </div>\r
+       <% end %>\r
+</td>\r
+<td><%=h entry.comment %></td>\r
+<td align="center"><strong><%= entry.hours %></strong></td>\r
+<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>\r
+</tr>\r
+<% end %>\r
+</tbdoy>\r
+</table>\r
+\r
+<div class="contextual">\r
+<%= l(:label_export_to) %>\r
+<%= link_to 'CSV', params.update(:export => 'csv'), :class => 'icon icon-csv' %>\r
+</div>\r
+<% end %>
\ No newline at end of file
diff --git a/app/views/timelog/edit.rhtml b/app/views/timelog/edit.rhtml
new file mode 100644 (file)
index 0000000..b826f7b
--- /dev/null
@@ -0,0 +1,23 @@
+<h2><%= l(:label_spent_time) %></h2>\r
+\r
+<% labelled_tabular_form_for :time_entry, @time_entry, :url => {:action => 'edit', :project_id => @time_entry.project} do |f| %>\r
+<%= error_messages_for 'time_entry' %>\r
+\r
+<div class="box">\r
+<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>\r
+<p><%= f.text_field :spent_on, :size => 10, :required => true %><%= calendar_for('time_entry_spent_on') %></p>\r
+<p><%= f.text_field :hours, :size => 6, :required => true %></p>\r
+<p><%= f.text_field :comment, :size => 100 %></p>\r
+<p><%= f.select :activity_id, (@activities.collect {|p| [p.name, p.id]}), :required => true %></p>\r
+</div>\r
+\r
+<%= submit_tag l(:button_save) %>\r
+\r
+<% end %>\r
+\r
+<% content_for :header_tags do %>\r
+<%= javascript_include_tag 'calendar/calendar' %>\r
+<%= javascript_include_tag "calendar/lang/calendar-#{current_language}.js" %>\r
+<%= javascript_include_tag 'calendar/calendar-setup' %>\r
+<%= stylesheet_link_tag 'calendar' %>\r
+<% end %>
\ No newline at end of file
diff --git a/db/migrate/032_create_time_entries.rb b/db/migrate/032_create_time_entries.rb
new file mode 100644 (file)
index 0000000..e055c13
--- /dev/null
@@ -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
diff --git a/db/migrate/033_add_timelog_permissions.rb b/db/migrate/033_add_timelog_permissions.rb
new file mode 100644 (file)
index 0000000..3b5b81e
--- /dev/null
@@ -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
index d7db911f4f3e0eea6199e4e4280bad4868fb966d..4cd6eab5162bba007bd0a9802d3181255d0172d0 100644 (file)
@@ -143,6 +143,9 @@ field_comment: Kommentar
 field_url: URL
 field_start_page: Hauptseite
 field_subproject: Subprojekt von
+field_hours: Hours
+field_activity: Activity
+field_spent_on: Datum
 
 setting_app_title: Applikation Titel
 setting_app_subtitle: Applikation Untertitel
@@ -331,6 +334,10 @@ label_preview: Preview
 label_feed_plural: Feeds
 label_changes_details: Details aller Änderungen
 label_issue_tracking: Tickets
+label_spent_time: Spent time
+label_f_hour: %.2f hour
+label_f_hour_plural: %.2f hours
+label_time_tracking: Time tracking
 
 button_login: Einloggen
 button_submit: OK
@@ -355,6 +362,7 @@ button_back: Zurück
 button_cancel: Abbrechen
 button_activate: Aktivieren
 button_sort: Sortieren
+button_log_time: Log time
 
 status_active: aktiv
 status_registered: angemeldet
@@ -392,6 +400,9 @@ default_priority_normal: Normal
 default_priority_high: Hoch
 default_priority_urgent: Dringend
 default_priority_immediate: Sofort
+default_activity_design: Design
+default_activity_development: Development
 
 enumeration_issue_priorities: Ticket-Prioritäten
 enumeration_doc_categories: Dokumentenkategorien
+enumeration_activities: Activities (time tracking)
index f2733b9b55825f54df0000778484cc0f3f81b055..00519ec533d6508403a5a403d7462b546bf325bd 100644 (file)
@@ -143,6 +143,9 @@ field_comment: Comment
 field_url: URL
 field_start_page: Start page
 field_subproject: Subproject
+field_hours: Hours
+field_activity: Activity
+field_spent_on: Date
 
 setting_app_title: Application title
 setting_app_subtitle: Application subtitle
@@ -331,6 +334,10 @@ label_preview: Preview
 label_feed_plural: Feeds
 label_changes_details: Details of all changes
 label_issue_tracking: Issue tracking
+label_spent_time: Spent time
+label_f_hour: %.2f hour
+label_f_hour_plural: %.2f hours
+label_time_tracking: Time tracking
 
 button_login: Login
 button_submit: Submit
@@ -355,6 +362,7 @@ button_back: Back
 button_cancel: Cancel
 button_activate: Activate
 button_sort: Sort
+button_log_time: Log time
 
 status_active: active
 status_registered: registered
@@ -392,6 +400,9 @@ default_priority_normal: Normal
 default_priority_high: High
 default_priority_urgent: Urgent
 default_priority_immediate: Immediate
+default_activity_design: Design
+default_activity_development: Development
 
 enumeration_issue_priorities: Issue priorities
 enumeration_doc_categories: Document categories
+enumeration_activities: Activities (time tracking)
index 32f8bbbeff9cdacbbb9503e173f2ff27826a912f..13c32bf1df19e4a2dae1be55fe794631264eeed4 100644 (file)
@@ -143,6 +143,9 @@ field_comment: Comentario
 field_url: URL
 field_start_page: Página principal
 field_subproject: Proyecto secundario
+field_hours: Hours
+field_activity: Activity
+field_spent_on: Fecha
 
 setting_app_title: Título del aplicación
 setting_app_subtitle: Subtítulo del aplicación
@@ -331,6 +334,10 @@ label_preview: Previo
 label_feed_plural: Feeds
 label_changes_details: Detalles de todos los cambios
 label_issue_tracking: Issue tracking
+label_spent_time: Spent time
+label_f_hour: %.2f hour
+label_f_hour_plural: %.2f hours
+label_time_tracking: Time tracking
 
 button_login: Conexión
 button_submit: Someter
@@ -355,6 +362,7 @@ button_back: Atrás
 button_cancel: Cancelar
 button_activate: Activar
 button_sort: Clasificar
+button_log_time: Log time
 
 status_active: active
 status_registered: registered
@@ -392,6 +400,9 @@ default_priority_normal: Normal
 default_priority_high: Alto
 default_priority_urgent: Urgente
 default_priority_immediate: Ahora
+default_activity_design: Design
+default_activity_development: Development
 
 enumeration_issue_priorities: Prioridad de las peticiones
 enumeration_doc_categories: Categorías del documento
+enumeration_activities: Activities (time tracking)
index c4cbb7c5396dfcd1947902f9407a70292c38dbee..c113301d32bbdb61c3f93da3c04da5e7173da4eb 100644 (file)
@@ -143,6 +143,9 @@ field_comment: Commentaire
 field_url: URL
 field_start_page: Page de démarrage
 field_subproject: Sous-projet
+field_hours: Heures
+field_activity: Activité
+field_spent_on: Date
 
 setting_app_title: Titre de l'application
 setting_app_subtitle: Sous-titre de l'application
@@ -331,6 +334,10 @@ label_preview: Prévisualisation
 label_feed_plural: Flux RSS
 label_changes_details: Détails de tous les changements
 label_issue_tracking: Suivi des demandes
+label_spent_time: Temps passé
+label_f_hour: %.2f heure
+label_f_hour_plural: %.2f heures
+label_time_tracking: Suivi du temps
 
 button_login: Connexion
 button_submit: Soumettre
@@ -355,6 +362,7 @@ button_back: Retour
 button_cancel: Annuler
 button_activate: Activer
 button_sort: Trier
+button_log_time: Saisir temps
 
 status_active: actif
 status_registered: enregistré
@@ -392,6 +400,9 @@ default_priority_normal: Normal
 default_priority_high: Haut
 default_priority_urgent: Urgent
 default_priority_immediate: Immédiat
+default_activity_design: Conception
+default_activity_development: Développement
 
 enumeration_issue_priorities: Priorités des demandes
 enumeration_doc_categories: Catégories des documents
+enumeration_activities: Activités (suivi du temps)
index bb347c5a9b13400e6f3369a6efe2b758ec407046..9bab4c9b7c60d298d826003583aab183593986a9 100644 (file)
@@ -143,6 +143,9 @@ field_comment: Commento
 field_url: URL
 field_start_page: Pagina principale
 field_subproject: Sottoprogetto
+field_hours: Hours
+field_activity: Activity
+field_spent_on: Data
 
 setting_app_title: Titolo applicazione
 setting_app_subtitle: Sottotitolo applicazione
@@ -331,6 +334,10 @@ label_preview: Previsione
 label_feed_plural: Feeds
 label_changes_details: Particolari di tutti i cambiamenti
 label_issue_tracking: Issue tracking
+label_spent_time: Spent time
+label_f_hour: %.2f hour
+label_f_hour_plural: %.2f hours
+label_time_tracking: Time tracking
 
 button_login: Login
 button_submit: Invia
@@ -355,6 +362,7 @@ button_back: Indietro
 button_cancel: Annulla
 button_activate: Attiva
 button_sort: Ordina
+button_log_time: Log time
 
 status_active: active
 status_registered: registered
@@ -392,6 +400,9 @@ default_priority_normal: Normale
 default_priority_high: Alta
 default_priority_urgent: Urgente
 default_priority_immediate: Immediata
+default_activity_design: Design
+default_activity_development: Development
 
 enumeration_issue_priorities: Priorità contesti
 enumeration_doc_categories: Categorie di documenti
+enumeration_activities: Activities (time tracking)
index 66de4e57f42b252c5c668beb2ed0c6215af2f60d..eb0be882566694d864b834b965bdaf4bc446a5fa 100644 (file)
@@ -144,6 +144,9 @@ field_comment: コメント
 field_url: URL
 field_start_page: メインページ
 field_subproject: サブプロジェクト
+field_hours: Hours
+field_activity: Activity
+field_spent_on: 日付
 
 setting_app_title: アプリケーションのタイトル
 setting_app_subtitle: アプリケーションのサブタイトル
@@ -332,6 +335,10 @@ label_preview: 下検分
 label_feed_plural: Feeds
 label_changes_details: Details of all changes
 label_issue_tracking: Issue tracking
+label_spent_time: Spent time
+label_f_hour: %.2f hour
+label_f_hour_plural: %.2f hours
+label_time_tracking: Time tracking
 
 button_login: ログイン
 button_submit: 変更
@@ -356,6 +363,7 @@ button_back: 戻る
 button_cancel: キャンセル
 button_activate: 有効にする
 button_sort: ソート
+button_log_time: Log time
 
 status_active: active
 status_registered: registered
@@ -393,6 +401,9 @@ default_priority_normal: 通常
 default_priority_high: 高め
 default_priority_urgent: 急いで
 default_priority_immediate: 今すぐ
+default_activity_design: Design
+default_activity_development: Development
 
 enumeration_issue_priorities: 問題の優先度
 enumeration_doc_categories: 文書カテゴリ
+enumeration_activities: Activities (time tracking)
index b554df1ba47eb2b62bf9058b5b4fc4bc54b1d308..488cd2a64af6ab40b791fafca9a5bb74b1b9d38d 100644 (file)
@@ -39,7 +39,7 @@ begin
   manager.permissions = Permission.find(:all, :conditions => ["is_public=?", false])\r
   \r
   developper = Role.create :name => l(:default_role_developper), :position => 2\r
-  perms = [150, 320, 321, 322, 420, 421, 422, 1050, 1060, 1070, 1075, 1130, 1220, 1221, 1222, 1223, 1224, 1320, 1322, 1061, 1057]\r
+  perms = [150, 320, 321, 322, 420, 421, 422, 1050, 1060, 1070, 1075, 1130, 1220, 1221, 1222, 1223, 1224, 1320, 1322, 1061, 1057, 1520]\r
   developper.permissions = Permission.find(:all, :conditions => ["sort IN (#{perms.join(',')})"])\r
   \r
   reporter = Role.create :name => l(:default_role_reporter), :position => 3\r
@@ -88,12 +88,16 @@ begin
   # enumerations\r
   Enumeration.create(:opt => "DCAT", :name => l(:default_doc_category_user))\r
   Enumeration.create(:opt => "DCAT", :name => l(:default_doc_category_tech))\r
+\r
   Enumeration.create(:opt => "IPRI", :name => l(:default_priority_low))\r
   Enumeration.create(:opt => "IPRI", :name => l(:default_priority_normal))\r
   Enumeration.create(:opt => "IPRI", :name => l(:default_priority_high))\r
   Enumeration.create(:opt => "IPRI", :name => l(:default_priority_urgent))\r
   Enumeration.create(:opt => "IPRI", :name => l(:default_priority_immediate))\r
-  \r
+\r
+  Enumeration.create(:opt => "ACTI", :name => l(:default_activity_design))\r
+  Enumeration.create(:opt => "ACTI", :name => l(:default_activity_development))\r
\r
 rescue => error\r
   puts "Error: " + error\r
   puts "Default configuration data can't be loaded."\r
diff --git a/public/images/time.png b/public/images/time.png
new file mode 100644 (file)
index 0000000..c6061cf
Binary files /dev/null and b/public/images/time.png differ
index 20395a7a2d3e80fdb051a51f0b28f08fcd22d1b7..b9f550261a729215664c191047fb501c36d5f92b 100644 (file)
@@ -155,6 +155,7 @@ vertical-align: middle;
 .icon-index  { background-image: url(../images/index.png); }\r
 .icon-history  { background-image: url(../images/history.png); }\r
 .icon-feed  { background-image: url(../images/feed.png); }\r
+.icon-time  { background-image: url(../images/time.png); }\r
 \r
 .icon22-projects { background-image: url(../images/22x22/projects.png); }\r
 .icon22-users { background-image: url(../images/22x22/users.png); }\r
@@ -542,7 +543,7 @@ font-size: 1em;
 /***** Tooltips ******/\r
 .tooltip{position:relative;z-index:24;}\r
 .tooltip:hover{z-index:25;color:#000;}\r
-.tooltip span.tip{display: none}\r
+.tooltip span.tip{display: none; text-align:left;}\r
 \r
 div.tooltip:hover span.tip{\r
 display:block;\r