From 8d54d9700746636849bd104f4d18db479492505e Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Fri, 23 Mar 2007 12:22:31 +0000 Subject: [PATCH] Simple time tracking functionality added. Time can be logged at issue or project level. 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 --- app/controllers/reports_controller.rb | 1 + app/controllers/timelog_controller.rb | 80 ++++++++++++++++++++++ app/helpers/sort_helper.rb | 6 +- app/helpers/timelog_helper.rb | 2 + app/models/enumeration.rb | 5 +- app/models/issue.rb | 6 +- app/models/permission.rb | 3 +- app/models/project.rb | 1 + app/models/time_entry.rb | 33 +++++++++ app/views/issues/show.rhtml | 4 +- app/views/reports/issue_report.rhtml | 11 +++ app/views/timelog/details.rhtml | 51 ++++++++++++++ app/views/timelog/edit.rhtml | 23 +++++++ db/migrate/032_create_time_entries.rb | 24 +++++++ db/migrate/033_add_timelog_permissions.rb | 9 +++ lang/de.yml | 11 +++ lang/en.yml | 11 +++ lang/es.yml | 11 +++ lang/fr.yml | 11 +++ lang/it.yml | 11 +++ lang/ja.yml | 11 +++ lib/tasks/load_default_data.rake | 8 ++- public/images/time.png | Bin 0 -> 993 bytes public/stylesheets/application.css | 3 +- 24 files changed, 326 insertions(+), 10 deletions(-) create mode 100644 app/controllers/timelog_controller.rb create mode 100644 app/helpers/timelog_helper.rb create mode 100644 app/models/time_entry.rb create mode 100644 app/views/timelog/details.rhtml create mode 100644 app/views/timelog/edit.rhtml create mode 100644 db/migrate/032_create_time_entries.rb create mode 100644 db/migrate/033_add_timelog_permissions.rb create mode 100644 public/images/time.png diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index fa00f7c30..ab648460b 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -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 index 000000000..5902390d4 --- /dev/null +++ b/app/controllers/timelog_controller.rb @@ -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 diff --git a/app/helpers/sort_helper.rb b/app/helpers/sort_helper.rb index 300fbfe54..eac0d4d3f 100644 --- a/app/helpers/sort_helper.rb +++ b/app/helpers/sort_helper.rb @@ -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 index 000000000..9054ccd18 --- /dev/null +++ b/app/helpers/timelog_helper.rb @@ -0,0 +1,2 @@ +module TimelogHelper +end diff --git a/app/models/enumeration.rb b/app/models/enumeration.rb index 251f00fbe..de8526067 100644 --- a/app/models/enumeration.rb +++ b/app/models/enumeration.rb @@ -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 diff --git a/app/models/issue.rb b/app/models/issue.rb index 140071872..dd512017f 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -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 diff --git a/app/models/permission.rb b/app/models/permission.rb index 3ce40d116..23f8a5e91 100644 --- a/app/models/permission.rb +++ b/app/models/permission.rb @@ -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 diff --git a/app/models/project.rb b/app/models/project.rb index 3579921b7..10730ed1e 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -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 index 000000000..4f2c561f5 --- /dev/null +++ b/app/models/time_entry.rb @@ -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 diff --git a/app/views/issues/show.rhtml b/app/views/issues/show.rhtml index 43e959c1b..98e88671c 100644 --- a/app/views/issues/show.rhtml +++ b/app/views/issues/show.rhtml @@ -28,7 +28,8 @@ <%=l(:field_fixed_version)%> :<%= @issue.fixed_version ? @issue.fixed_version.name : "-" %> - + <%=l(:label_spent_time)%> : + <%= @issue.spent_hours > 0 ? (link_to lwr(:label_f_hour, @issue.spent_hours), {:controller => 'timelog', :action => 'details', :issue_id => @issue}, :class => 'icon icon-time') : "-" %> <% n = 0 @@ -51,6 +52,7 @@ end %>
<%= 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' %>
diff --git a/app/views/reports/issue_report.rhtml b/app/views/reports/issue_report.rhtml index 8ad45afcf..8f832f87e 100644 --- a/app/views/reports/issue_report.rhtml +++ b/app/views/reports/issue_report.rhtml @@ -1,5 +1,6 @@

<%=l(:label_report_plural)%>

+
<%= link_to_if_authorized l(:label_query_new), {:controller => 'projects', :action => 'add_query', :id => @project}, :class => 'icon icon-add' %>
@@ -11,6 +12,16 @@
  • <%= link_to query.name, :controller => 'projects', :action => 'list_issues', :id => @project, :query_id => query %>
  • <% end %> +
    +
    +<% if @total_hours %> +

    <%= l(:label_spent_time) %>: +<%= link_to(lwr(:label_f_hour, @total_hours), {:controller => 'timelog', :action => 'details', :project_id => @project}, :class => 'icon icon-time') %> +

    +<% end %> +
    + +

    <%=l(:field_tracker)%>  <%= link_to image_tag('zoom_in.png'), :detail => 'tracker' %>

    diff --git a/app/views/timelog/details.rhtml b/app/views/timelog/details.rhtml new file mode 100644 index 000000000..f85eb0f5a --- /dev/null +++ b/app/views/timelog/details.rhtml @@ -0,0 +1,51 @@ +
    +<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time' %> +
    + +

    <%= l(:label_spent_time) %>

    + +

    <%= 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 %>

    + +

    <%= l(:label_total) %>: <%= lwr(:label_f_hour, @total_hours) %>

    + +<% unless @entries.empty? %> + + +<%= 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)) %> + +<%= sort_header_tag('hours', :caption => l(:field_hours)) %> + + + +<% @entries.each do |entry| %> +"> + + + + + + + + +<% end %> + +
    <%= l(:label_comment) %>
    <%= format_date(entry.spent_on) %><%= entry.user.name %><%= entry.activity.name %> + <% if entry.issue %> +
    + <%= link_to "#{entry.issue.tracker.name} ##{entry.issue.id}", {:action => 'details', :issue_id => entry.issue } %> + + <%= render :partial => "issues/tooltip", :locals => { :issue => entry.issue }%> + +
    + <% end %> +
    <%=h entry.comment %><%= entry.hours %><%= link_to_if_authorized(l(:button_edit), {:controller => 'timelog', :action => 'edit', :id => entry}, :class => "icon icon-edit") if entry.user_id == @owner_id %>
    + +
    +<%= l(:label_export_to) %> +<%= link_to 'CSV', params.update(:export => 'csv'), :class => 'icon icon-csv' %> +
    +<% 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 index 000000000..b826f7be7 --- /dev/null +++ b/app/views/timelog/edit.rhtml @@ -0,0 +1,23 @@ +

    <%= l(:label_spent_time) %>

    + +<% labelled_tabular_form_for :time_entry, @time_entry, :url => {:action => 'edit', :project_id => @time_entry.project} do |f| %> +<%= error_messages_for 'time_entry' %> + +
    +

    <%= f.text_field :issue_id, :size => 6 %> <%= h("#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}") if @time_entry.issue %>

    +

    <%= f.text_field :spent_on, :size => 10, :required => true %><%= calendar_for('time_entry_spent_on') %>

    +

    <%= f.text_field :hours, :size => 6, :required => true %>

    +

    <%= f.text_field :comment, :size => 100 %>

    +

    <%= f.select :activity_id, (@activities.collect {|p| [p.name, p.id]}), :required => true %>

    +
    + +<%= 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 %> \ 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 index 000000000..e055c13e6 --- /dev/null +++ b/db/migrate/032_create_time_entries.rb @@ -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 index 000000000..3b5b81ed6 --- /dev/null +++ b/db/migrate/033_add_timelog_permissions.rb @@ -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 diff --git a/lang/de.yml b/lang/de.yml index d7db911f4..4cd6eab51 100644 --- a/lang/de.yml +++ b/lang/de.yml @@ -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) diff --git a/lang/en.yml b/lang/en.yml index f2733b9b5..00519ec53 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -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) diff --git a/lang/es.yml b/lang/es.yml index 32f8bbbef..13c32bf1d 100644 --- a/lang/es.yml +++ b/lang/es.yml @@ -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) diff --git a/lang/fr.yml b/lang/fr.yml index c4cbb7c53..c113301d3 100644 --- a/lang/fr.yml +++ b/lang/fr.yml @@ -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) diff --git a/lang/it.yml b/lang/it.yml index bb347c5a9..9bab4c9b7 100644 --- a/lang/it.yml +++ b/lang/it.yml @@ -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) diff --git a/lang/ja.yml b/lang/ja.yml index 66de4e57f..eb0be8825 100644 --- a/lang/ja.yml +++ b/lang/ja.yml @@ -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) diff --git a/lib/tasks/load_default_data.rake b/lib/tasks/load_default_data.rake index b554df1ba..488cd2a64 100644 --- a/lib/tasks/load_default_data.rake +++ b/lib/tasks/load_default_data.rake @@ -39,7 +39,7 @@ begin manager.permissions = Permission.find(:all, :conditions => ["is_public=?", false]) developper = Role.create :name => l(:default_role_developper), :position => 2 - perms = [150, 320, 321, 322, 420, 421, 422, 1050, 1060, 1070, 1075, 1130, 1220, 1221, 1222, 1223, 1224, 1320, 1322, 1061, 1057] + perms = [150, 320, 321, 322, 420, 421, 422, 1050, 1060, 1070, 1075, 1130, 1220, 1221, 1222, 1223, 1224, 1320, 1322, 1061, 1057, 1520] developper.permissions = Permission.find(:all, :conditions => ["sort IN (#{perms.join(',')})"]) reporter = Role.create :name => l(:default_role_reporter), :position => 3 @@ -88,12 +88,16 @@ begin # enumerations Enumeration.create(:opt => "DCAT", :name => l(:default_doc_category_user)) Enumeration.create(:opt => "DCAT", :name => l(:default_doc_category_tech)) + Enumeration.create(:opt => "IPRI", :name => l(:default_priority_low)) Enumeration.create(:opt => "IPRI", :name => l(:default_priority_normal)) Enumeration.create(:opt => "IPRI", :name => l(:default_priority_high)) Enumeration.create(:opt => "IPRI", :name => l(:default_priority_urgent)) Enumeration.create(:opt => "IPRI", :name => l(:default_priority_immediate)) - + + Enumeration.create(:opt => "ACTI", :name => l(:default_activity_design)) + Enumeration.create(:opt => "ACTI", :name => l(:default_activity_development)) + rescue => error puts "Error: " + error puts "Default configuration data can't be loaded." diff --git a/public/images/time.png b/public/images/time.png new file mode 100644 index 0000000000000000000000000000000000000000..c6061cf505c4a4b271863d57e9028ebdb4b30c3d GIT binary patch literal 993 zcmV<710MW|P)8;SqUV7+1u=U`fMcACggY?jgvPdZvq=9D9 zEUe8XqTouB7-dLDvDWV2aZUS4V=BO_nxx<1Vq%Me1)>2%KT z@9+OOIy(A$aB$FBTwMInp#ZS7w1hAWp(x7d)6>&0QmNDyA>^6kICHjb&jEOrOeVLc zrlwv17!Sh`%gf6E0J5;KfQ^j}Y;A3QJuoovi|e|d`o0gtFrX+3D5Yq%TBy}(;G9EM z)su^hi|^*==l`6Wn*-V0+=Ol0p9~KV|JLbrMq{xUOw)v-C@2<-Fbo5_uETX*oSmJ) zbzLZm^7`Q5;M-U%c18e@-QC^i@p$}uMN!b--w(zZG)+UJ(E#ThoO9gW-Qnu$3XbD| zQi@8Y^7HKM><`lF>S`vPPX9zHHBzY*VzC%BO#`JAoO8&sj7p_~VzG!!CIem9Q7)Ik z82c!b$!yC#Jw2ncESsEjba!`ya}GiXLLuN-7OtvQc%Fx$p&>*f5eOl`IY+zQHhX(} zM`cNpJ|={)NF)NwvcMRFVHhBU;GdT-A)--CJ$(v^Qj|(1$g&L1Iegz|nx^&1uIskK zFbpI~LL!j>r4#_r2?7XR$4GZK&}aaO1mf{H!Z1V_h5!%M`D$Wf;v3U6$D+|_PZ)-1Hk;P< z_4Vs~KL7j9&dy)1>sA1?0sK$!ACju7(ed%|C#Go{0LabFO}$VklswO~0r-!@{{qIM zeGS-KpIQI_03v!+SaefwW^{L9a%BJjc-kv3FW1Y=%Pvk%EJ)SMFG>dhHrNJO5L3!r P00000NkvXXu0mjfL9f>w literal 0 HcmV?d00001 diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 20395a7a2..b9f550261 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -155,6 +155,7 @@ vertical-align: middle; .icon-index { background-image: url(../images/index.png); } .icon-history { background-image: url(../images/history.png); } .icon-feed { background-image: url(../images/feed.png); } +.icon-time { background-image: url(../images/time.png); } .icon22-projects { background-image: url(../images/22x22/projects.png); } .icon22-users { background-image: url(../images/22x22/users.png); } @@ -542,7 +543,7 @@ font-size: 1em; /***** Tooltips ******/ .tooltip{position:relative;z-index:24;} .tooltip:hover{z-index:25;color:#000;} -.tooltip span.tip{display: none} +.tooltip span.tip{display: none; text-align:left;} div.tooltip:hover span.tip{ display:block; -- 2.39.5