1
0
mirror of https://github.com/meineerde/redmine.git synced 2026-03-15 05:28:13 +00:00

Merge branch 'master' of git://github.com/edavis10/redmine

This commit is contained in:
Holger Just 2010-09-18 19:20:42 +02:00
commit 553e193705
126 changed files with 3227 additions and 1123 deletions

View File

@ -169,6 +169,13 @@ class ApplicationController < ActionController::Base
render_404
end
# Find project of id params[:project_id]
def find_project_by_project_id
@project = Project.find(params[:project_id])
rescue ActiveRecord::RecordNotFound
render_404
end
# Find a project based on params[:project_id]
# TODO: some subclasses override this, see about merging their logic
def find_optional_project

View File

@ -6,9 +6,15 @@ class ContextMenusController < ApplicationController
if (@issues.size == 1)
@issue = @issues.first
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
else
@allowed_statuses = @issues.map do |i|
i.new_statuses_allowed_to(User.current)
end.inject do |memo,s|
memo & s
end
end
projects = @issues.collect(&:project).compact.uniq
@project = projects.first if projects.size == 1
@projects = @issues.collect(&:project).compact.uniq
@project = @projects.first if @projects.size == 1
@can = {:edit => (@project && User.current.allowed_to?(:edit_issues, @project)),
:log_time => (@project && User.current.allowed_to?(:log_time, @project)),

View File

@ -1,7 +1,7 @@
class FilesController < ApplicationController
menu_item :files
before_filter :find_project
before_filter :find_project_by_project_id
before_filter :authorize
helper :sort
@ -19,4 +19,18 @@ class FilesController < ApplicationController
render :layout => !request.xhr?
end
def new
@versions = @project.versions.sort
end
def create
container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
attachments = Attachment.attach_files(container, params[:attachments])
render_attachment_warning_if_needed(container)
if !attachments.empty? && Setting.notified_events.include?('file_added')
Mailer.deliver_attachments_added(attachments[:files])
end
redirect_to project_files_path(@project)
end
end

View File

@ -4,6 +4,7 @@ class GanttsController < ApplicationController
rescue_from Query::StatementInvalid, :with => :query_statement_invalid
helper :gantt
helper :issues
helper :projects
helper :queries
@ -14,32 +15,17 @@ class GanttsController < ApplicationController
def show
@gantt = Redmine::Helpers::Gantt.new(params)
@gantt.project = @project
retrieve_query
@query.group_by = nil
if @query.valid?
events = []
# Issues that have start and due dates
events += @query.issues(:include => [:tracker, :assigned_to, :priority],
:order => "start_date, due_date",
:conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
)
# Issues that don't have a due date but that are assigned to a version with a date
events += @query.issues(:include => [:tracker, :assigned_to, :priority, :fixed_version],
:order => "start_date, effective_date",
:conditions => ["(((start_date>=? and start_date<=?) or (effective_date>=? and effective_date<=?) or (start_date<? and effective_date>?)) and start_date is not null and due_date is null and effective_date is not null)", @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to, @gantt.date_from, @gantt.date_to]
)
# Versions
events += @query.versions(:conditions => ["effective_date BETWEEN ? AND ?", @gantt.date_from, @gantt.date_to])
@gantt.events = events
end
@gantt.query = @query if @query.valid?
basename = (@project ? "#{@project.identifier}-" : '') + 'gantt'
respond_to do |format|
format.html { render :action => "show", :layout => !request.xhr? }
format.png { send_data(@gantt.to_image(@project), :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image')
format.pdf { send_data(gantt_to_pdf(@gantt, @project), :type => 'application/pdf', :filename => "#{basename}.pdf") }
format.png { send_data(@gantt.to_image, :disposition => 'inline', :type => 'image/png', :filename => "#{basename}.png") } if @gantt.respond_to?('to_image')
format.pdf { send_data(@gantt.to_pdf, :type => 'application/pdf', :filename => "#{basename}.pdf") }
end
end

View File

@ -47,6 +47,7 @@ class IssuesController < ApplicationController
include SortHelper
include IssuesHelper
helper :timelog
helper :gantt
include Redmine::Export::PDF
verify :method => [:post, :delete],
@ -133,7 +134,7 @@ class IssuesController < ApplicationController
call_hook(:controller_issues_new_after_save, { :params => params, :issue => @issue})
respond_to do |format|
format.html {
redirect_to(params[:continue] ? { :action => 'new', :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
redirect_to(params[:continue] ? { :action => 'new', :project_id => @project, :issue => {:tracker_id => @issue.tracker, :parent_issue_id => @issue.parent_issue_id}.reject {|k,v| v.nil?} } :
{ :action => 'show', :id => @issue })
}
format.xml { render :action => 'show', :status => :created, :location => url_for(:controller => 'issues', :action => 'show', :id => @issue) }

View File

@ -0,0 +1,26 @@
class ProjectEnumerationsController < ApplicationController
before_filter :find_project_by_project_id
before_filter :authorize
def update
if request.put? && params[:enumerations]
Project.transaction do
params[:enumerations].each do |id, activity|
@project.update_or_create_time_entry_activity(id, activity)
end
end
flash[:notice] = l(:notice_successful_update)
end
redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
end
def destroy
@project.time_entry_activities.each do |time_entry_activity|
time_entry_activity.destroy(time_entry_activity.parent)
end
flash[:notice] = l(:notice_successful_update)
redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
end
end

View File

@ -18,21 +18,23 @@
class ProjectsController < ApplicationController
menu_item :overview
menu_item :roadmap, :only => :roadmap
menu_item :files, :only => [:add_file]
menu_item :settings, :only => :settings
before_filter :find_project, :except => [ :index, :list, :add, :copy ]
before_filter :authorize, :except => [ :index, :list, :add, :copy, :archive, :unarchive, :destroy]
before_filter :authorize_global, :only => :add
before_filter :find_project, :except => [ :index, :list, :new, :create, :copy ]
before_filter :authorize, :except => [ :index, :list, :new, :create, :copy, :archive, :unarchive, :destroy]
before_filter :authorize_global, :only => [:new, :create]
before_filter :require_admin, :only => [ :copy, :archive, :unarchive, :destroy ]
accept_key_auth :index
after_filter :only => [:add, :edit, :archive, :unarchive, :destroy] do |controller|
after_filter :only => [:create, :edit, :update, :archive, :unarchive, :destroy] do |controller|
if controller.request.post?
controller.send :expire_action, :controller => 'welcome', :action => 'robots.txt'
end
end
# TODO: convert to PUT only
verify :method => [:post, :put], :only => :update, :render => {:nothing => true, :status => :method_not_allowed }
helper :sort
include SortHelper
helper :custom_fields
@ -61,40 +63,45 @@ class ProjectsController < ApplicationController
end
end
# Add a new project
def add
def new
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.all
@project = Project.new(params[:project])
if request.get?
@project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
@project.trackers = Tracker.all
@project.is_public = Setting.default_projects_public?
@project.enabled_module_names = Setting.default_projects_modules
else
@project.enabled_module_names = params[:enabled_modules]
if validate_parent_id && @project.save
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
# Add current user as a project member if he is not admin
unless User.current.admin?
r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
m = Member.new(:user => User.current, :roles => [r])
@project.members << m
end
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'projects', :action => 'settings', :id => @project
}
format.xml { head :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
end
else
respond_to do |format|
format.html
format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
end
@project.identifier = Project.next_identifier if Setting.sequential_project_identifiers?
@project.trackers = Tracker.all
@project.is_public = Setting.default_projects_public?
@project.enabled_module_names = Setting.default_projects_modules
end
def create
@issue_custom_fields = IssueCustomField.find(:all, :order => "#{CustomField.table_name}.position")
@trackers = Tracker.all
@project = Project.new(params[:project])
@project.enabled_module_names = params[:enabled_modules]
if validate_parent_id && @project.save
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
# Add current user as a project member if he is not admin
unless User.current.admin?
r = Role.givable.find_by_id(Setting.new_project_user_role_id.to_i) || Role.givable.first
m = Member.new(:user => User.current, :roles => [r])
@project.members << m
end
end
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_create)
redirect_to :controller => 'projects', :action => 'settings', :id => @project
}
format.xml { head :created, :location => url_for(:controller => 'projects', :action => 'show', :id => @project.id) }
end
else
respond_to do |format|
format.html { render :action => 'new' }
format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
end
end
end
def copy
@ -175,28 +182,27 @@ class ProjectsController < ApplicationController
@wiki ||= @project.wiki
end
# Edit @project
def edit
if request.get?
end
def update
@project.attributes = params[:project]
if validate_parent_id && @project.save
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'settings', :id => @project
}
format.xml { head :ok }
end
else
@project.attributes = params[:project]
if validate_parent_id && @project.save
@project.set_allowed_parent!(params[:project]['parent_id']) if params[:project].has_key?('parent_id')
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'settings', :id => @project
}
format.xml { head :ok }
end
else
respond_to do |format|
format.html {
settings
render :action => 'settings'
}
format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
end
respond_to do |format|
format.html {
settings
render :action => 'settings'
}
format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
end
end
end
@ -239,42 +245,6 @@ class ProjectsController < ApplicationController
@project = nil
end
def add_file
if request.post?
container = (params[:version_id].blank? ? @project : @project.versions.find_by_id(params[:version_id]))
attachments = Attachment.attach_files(container, params[:attachments])
render_attachment_warning_if_needed(container)
if !attachments.empty? && Setting.notified_events.include?('file_added')
Mailer.deliver_attachments_added(attachments[:files])
end
redirect_to :controller => 'files', :action => 'index', :id => @project
return
end
@versions = @project.versions.sort
end
def save_activities
if request.post? && params[:enumerations]
Project.transaction do
params[:enumerations].each do |id, activity|
@project.update_or_create_time_entry_activity(id, activity)
end
end
flash[:notice] = l(:notice_successful_update)
end
redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
end
def reset_activities
@project.time_entry_activities.each do |time_entry_activity|
time_entry_activity.destroy(time_entry_activity.parent)
end
flash[:notice] = l(:notice_successful_update)
redirect_to :controller => 'projects', :action => 'settings', :tab => 'activities', :id => @project
end
private
def find_optional_project
return true unless params[:id]

View File

@ -260,8 +260,8 @@ private
end
@from, @to = @to, @from if @from && @to && @from > @to
@from ||= (TimeEntry.minimum(:spent_on, :include => :project, :conditions => Project.allowed_to_condition(User.current, :view_time_entries)) || Date.today) - 1
@to ||= (TimeEntry.maximum(:spent_on, :include => :project, :conditions => Project.allowed_to_condition(User.current, :view_time_entries)) || Date.today)
@from ||= (TimeEntry.earilest_date_for_project(@project) || Date.today)
@to ||= (TimeEntry.latest_date_for_project(@project) || Date.today)
end
def load_available_criterias

View File

@ -95,7 +95,9 @@ class UsersController < ApplicationController
if request.post?
@user.admin = params[:user][:admin] if params[:user][:admin]
@user.login = params[:user][:login] if params[:user][:login]
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation] unless params[:password].nil? or params[:password].empty? or @user.auth_source_id
if params[:password].present? && (@user.auth_source_id.nil? || params[:user][:auth_source_id].blank?)
@user.password, @user.password_confirmation = params[:password], params[:password_confirmation]
end
@user.group_ids = params[:user][:group_ids] if params[:user][:group_ids]
@user.attributes = params[:user]
# Was the account actived ? (do it before User#save clears the change)

View File

@ -18,9 +18,9 @@
class VersionsController < ApplicationController
menu_item :roadmap
model_object Version
before_filter :find_model_object, :except => [:index, :new, :close_completed]
before_filter :find_project_from_association, :except => [:index, :new, :close_completed]
before_filter :find_project, :only => [:index, :new, :close_completed]
before_filter :find_model_object, :except => [:index, :new, :create, :close_completed]
before_filter :find_project_from_association, :except => [:index, :new, :create, :close_completed]
before_filter :find_project, :only => [:index, :new, :create, :close_completed]
before_filter :authorize
helper :custom_fields
@ -63,6 +63,17 @@ class VersionsController < ApplicationController
attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
@version.attributes = attributes
end
end
def create
# TODO: refactor with code above in #new
@version = @project.versions.build
if params[:version]
attributes = params[:version].dup
attributes.delete('sharing') unless attributes.nil? || @version.allowed_sharings.include?(attributes['sharing'])
@version.attributes = attributes
end
if request.post?
if @version.save
respond_to do |format|
@ -79,7 +90,7 @@ class VersionsController < ApplicationController
end
else
respond_to do |format|
format.html
format.html { render :action => 'new' }
format.js do
render(:update) {|page| page.alert(@version.errors.full_messages.join('\n')) }
end
@ -87,9 +98,12 @@ class VersionsController < ApplicationController
end
end
end
def edit
if request.post? && params[:version]
end
def update
if request.put? && params[:version]
attributes = params[:version].dup
attributes.delete('sharing') unless @version.allowed_sharings.include?(attributes['sharing'])
if @version.update_attributes(attributes)
@ -100,7 +114,7 @@ class VersionsController < ApplicationController
end
def close_completed
if request.post?
if request.put?
@project.close_completed_versions
end
redirect_to :controller => 'projects', :action => 'settings', :tab => 'versions', :id => @project

View File

@ -20,12 +20,4 @@ module AdminHelper
options_for_select([[l(:label_all), ''],
[l(:status_active), 1]], selected)
end
def css_project_classes(project)
s = 'project'
s << ' root' if project.root?
s << ' child' if project.child?
s << (project.leaf? ? ' leaf' : ' parent')
s
end
end

View File

@ -32,8 +32,27 @@ module ApplicationHelper
end
# Display a link if user is authorized
#
# @param [String] name Anchor text (passed to link_to)
# @param [Hash, String] options Hash params or url for the link target (passed to link_to).
# This will checked by authorize_for to see if the user is authorized
# @param [optional, Hash] html_options Options passed to link_to
# @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
if options.is_a?(String)
begin
route = ActionController::Routing::Routes.recognize_path(options.gsub(/\?.*/,''), :method => options[:method] || :get)
link_controller = route[:controller]
link_action = route[:action]
rescue ActionController::RoutingError # Parse failed, not a route
link_controller, link_action = nil, nil
end
else
link_controller = options[:controller] || params[:controller]
link_action = options[:action]
end
link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(link_controller, link_action)
end
# Display a link to remote if user is authorized
@ -102,6 +121,11 @@ module ApplicationHelper
link_to(text, {:controller => 'repositories', :action => 'revision', :id => project, :rev => revision}, :title => l(:label_revision_id, revision))
end
def link_to_project(project, options={})
options[:class] ||= 'project'
link_to(h(project), {:controller => 'projects', :action => 'show', :id => project}, :class => options[:class])
end
# Generates a link to a project if active
# Examples:
@ -302,7 +326,7 @@ module ApplicationHelper
def time_tag(time)
text = distance_of_time_in_words(Time.now, time)
if @project
link_to(text, {:controller => 'projects', :action => 'activity', :id => @project, :from => time.to_date}, :title => format_time(time))
link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
else
content_tag('acronym', text, :title => format_time(time))
end
@ -813,6 +837,8 @@ module ApplicationHelper
email = $1
end
return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
else
''
end
end

View File

@ -0,0 +1,24 @@
# redMine - project management software
# Copyright (C) 2006 Jean-Philippe Lang
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module GanttHelper
def number_of_issues_on_versions(gantt)
versions = gantt.events.collect {|event| (event.is_a? Version) ? event : nil}.compact
versions.sum {|v| v.fixed_issues.for_gantt.with_query(@query).count}
end
end

View File

@ -35,8 +35,10 @@ module IssuesHelper
@cached_label_due_date ||= l(:field_due_date)
@cached_label_assigned_to ||= l(:field_assigned_to)
@cached_label_priority ||= l(:field_priority)
@cached_label_project ||= l(:field_project)
link_to_issue(issue) + "<br /><br />" +
"<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />" +
"<strong>#{@cached_label_status}</strong>: #{issue.status.name}<br />" +
"<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
"<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
@ -243,7 +245,7 @@ module IssuesHelper
when :in
if gantt.zoom < 4
link_to_remote(l(:text_zoom_in) + image_tag('zoom_in.png', img_attributes.merge(:alt => l(:text_zoom_in))),
{:url => gantt.params.merge(:zoom => (gantt.zoom+1)), :update => 'content'},
{:url => gantt.params.merge(:zoom => (gantt.zoom+1)), :method => :get, :update => 'content'},
{:href => url_for(gantt.params.merge(:zoom => (gantt.zoom+1)))})
else
l(:text_zoom_in) +
@ -253,7 +255,7 @@ module IssuesHelper
when :out
if gantt.zoom > 1
link_to_remote(l(:text_zoom_out) + image_tag('zoom_out.png', img_attributes.merge(:alt => l(:text_zoom_out))),
{:url => gantt.params.merge(:zoom => (gantt.zoom-1)), :update => 'content'},
{:url => gantt.params.merge(:zoom => (gantt.zoom-1)), :method => :get, :update => 'content'},
{:href => url_for(gantt.params.merge(:zoom => (gantt.zoom-1)))})
else
l(:text_zoom_out) +

View File

@ -62,10 +62,28 @@ class Issue < ActiveRecord::Base
named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
named_scope :recently_updated, :order => "#{self.table_name}.updated_on DESC"
named_scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC"
named_scope :with_limit, lambda { |limit| { :limit => limit} }
named_scope :on_active_project, :include => [:status, :project, :tracker],
:conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
named_scope :for_gantt, lambda {
{
:include => [:tracker, :status, :assigned_to, :priority, :project, :fixed_version],
:order => "#{Issue.table_name}.due_date ASC, #{Issue.table_name}.start_date ASC, #{Issue.table_name}.id ASC"
}
}
named_scope :without_version, lambda {
{
:conditions => { :fixed_version_id => nil}
}
}
named_scope :with_query, lambda {|query|
{
:conditions => Query.merge_conditions(query.statement)
}
}
before_create :default_assign
before_save :reschedule_following_issues, :close_duplicates, :update_done_ratio_from_issue_status
@ -357,6 +375,13 @@ class Issue < ActiveRecord::Base
def overdue?
!due_date.nil? && (due_date < Date.today) && !status.is_closed?
end
# Is the amount of work done less than it should for the due date
def behind_schedule?
return false if start_date.nil? || due_date.nil?
done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor
return done_date <= Date.today
end
# Users the issue can be assigned to
def assignable_users
@ -821,7 +846,7 @@ class Issue < ActiveRecord::Base
j.id as #{select_field},
count(i.id) as total
from
#{Issue.table_name} i, #{IssueStatus.table_name} s, #{joins} as j
#{Issue.table_name} i, #{IssueStatus.table_name} s, #{joins} j
where
i.status_id=s.id
and #{where}

View File

@ -65,4 +65,12 @@ class Journal < ActiveRecord::Base
def attachments
journalized.respond_to?(:attachments) ? journalized.attachments : nil
end
# Returns a string of css classes
def css_classes
s = 'journal'
s << ' has-notes' unless notes.blank?
s << ' has-details' unless details.blank?
s
end
end

View File

@ -33,7 +33,11 @@ class Principal < ActiveRecord::Base
}
before_create :set_default_empty_values
def name(formatter = nil)
to_s
end
def <=>(principal)
if self.class.name == principal.class.name
self.to_s.downcase <=> principal.to_s.downcase

View File

@ -412,6 +412,58 @@ class Project < ActiveRecord::Base
def short_description(length = 255)
description.gsub(/^(.{#{length}}[^\n\r]*).*$/m, '\1...').strip if description
end
def css_classes
s = 'project'
s << ' root' if root?
s << ' child' if child?
s << (leaf? ? ' leaf' : ' parent')
s
end
# The earliest start date of a project, based on it's issues and versions
def start_date
if module_enabled?(:issue_tracking)
[
issues.minimum('start_date'),
shared_versions.collect(&:effective_date),
shared_versions.collect {|v| v.fixed_issues.minimum('start_date')}
].flatten.compact.min
end
end
# The latest due date of an issue or version
def due_date
if module_enabled?(:issue_tracking)
[
issues.maximum('due_date'),
shared_versions.collect(&:effective_date),
shared_versions.collect {|v| v.fixed_issues.maximum('due_date')}
].flatten.compact.max
end
end
def overdue?
active? && !due_date.nil? && (due_date < Date.today)
end
# Returns the percent completed for this project, based on the
# progress on it's versions.
def completed_percent(options={:include_subprojects => false})
if options.delete(:include_subprojects)
total = self_and_descendants.collect(&:completed_percent).sum
total / self_and_descendants.count
else
if versions.count > 0
total = versions.collect(&:completed_pourcent).sum
total / versions.count
else
100
end
end
end
# Return true if this project is allowed to do the specified action.
# action can be:
@ -441,6 +493,15 @@ class Project < ActiveRecord::Base
enabled_modules.clear
end
end
# Returns an array of projects that are in this project's hierarchy
#
# Example: parents, children, siblings
def hierarchy
parents = project.self_and_ancestors || []
descendants = project.descendants || []
project_hierarchy = parents | descendants # Set union
end
# Returns an auto-generated project identifier based on the last identifier used
def self.next_identifier

View File

@ -195,6 +195,12 @@ class Query < ActiveRecord::Base
end
@available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
@available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
group_values = Group.all.collect {|g| [g.name, g.id] }
@available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
role_values = Role.givable.collect {|r| [r.name, r.id] }
@available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
if User.current.logged?
@available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
@ -432,6 +438,47 @@ class Query < ActiveRecord::Base
db_field = 'user_id'
sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND "
sql << sql_for_field(field, '=', v, db_table, db_field) + ')'
elsif field == "member_of_group" # named field
if operator == '*' # Any group
groups = Group.all
operator = '=' # Override the operator since we want to find by assigned_to
elsif operator == "!*"
groups = Group.all
operator = '!' # Override the operator since we want to find by assigned_to
else
groups = Group.find_all_by_id(v)
end
groups ||= []
members_of_groups = groups.inject([]) {|user_ids, group|
if group && group.user_ids.present?
user_ids << group.user_ids
end
user_ids.flatten.uniq.compact
}.sort.collect(&:to_s)
sql << '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
elsif field == "assigned_to_role" # named field
if operator == "*" # Any Role
roles = Role.givable
operator = '=' # Override the operator since we want to find by assigned_to
elsif operator == "!*" # No role
roles = Role.givable
operator = '!' # Override the operator since we want to find by assigned_to
else
roles = Role.givable.find_all_by_id(v)
end
roles ||= []
members_of_roles = roles.inject([]) {|user_ids, role|
if role && role.members
user_ids << role.members.collect(&:user_id)
end
user_ids.flatten.uniq.compact
}.sort.collect(&:to_s)
sql << '(' + sql_for_field("assigned_to_id", operator, members_of_roles, Issue.table_name, "assigned_to_id", false) + ')'
else
# regular field
db_table = Issue.table_name

View File

@ -81,4 +81,20 @@ class TimeEntry < ActiveRecord::Base
yield
end
end
def self.earilest_date_for_project(project=nil)
finder_conditions = ARCondition.new(Project.allowed_to_condition(User.current, :view_time_entries))
if project
finder_conditions << ["project_id IN (?)", project.hierarchy.collect(&:id)]
end
TimeEntry.minimum(:spent_on, :include => :project, :conditions => finder_conditions.conditions)
end
def self.latest_date_for_project(project=nil)
finder_conditions = ARCondition.new(Project.allowed_to_condition(User.current, :view_time_entries))
if project
finder_conditions << ["project_id IN (?)", project.hierarchy.collect(&:id)]
end
TimeEntry.maximum(:spent_on, :include => :project, :conditions => finder_conditions.conditions)
end
end

View File

@ -73,6 +73,18 @@ class Version < ActiveRecord::Base
def completed?
effective_date && (effective_date <= Date.today) && (open_issues_count == 0)
end
def behind_schedule?
if completed_pourcent == 100
return false
elsif due_date && fixed_issues.present? && fixed_issues.minimum('start_date') # TODO: should use #start_date but that method is wrong...
start_date = fixed_issues.minimum('start_date')
done_date = start_date + ((due_date - start_date+1)* completed_pourcent/100).floor
return done_date <= Date.today
else
false # No issues so it's not late
end
end
# Returns the completion percentage of this version based on the amount of open/closed issues
# and the time spent on the open issues.
@ -123,6 +135,10 @@ class Version < ActiveRecord::Base
end
def to_s; name end
def to_s_with_project
"#{project} - #{name}"
end
# Versions are sorted by effective_date and "Project Name - Version name"
# Those with no effective_date are at the end, sorted by "Project Name - Version name"

View File

@ -1,5 +1,5 @@
<div class="contextual">
<%= link_to l(:label_project_new), {:controller => 'projects', :action => 'add'}, :class => 'icon icon-add' %>
<%= link_to l(:label_project_new), {:controller => 'projects', :action => 'new'}, :class => 'icon icon-add' %>
</div>
<h2><%=l(:label_project_plural)%></h2>
@ -26,7 +26,7 @@
</tr></thead>
<tbody>
<% project_tree(@projects) do |project, level| %>
<tr class="<%= cycle("odd", "even") %> <%= css_project_classes(project) %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>">
<tr class="<%= cycle("odd", "even") %> <%= project.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>">
<td class="name"><%= link_to_project(project, :action => 'settings') %></td>
<td><%= textilizable project.short_description, :project => project %></td>
<td align="center"><%= checked_image project.is_public? %></td>
@ -35,7 +35,7 @@
<%= link_to(l(:button_archive), { :controller => 'projects', :action => 'archive', :id => project, :status => params[:status] }, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-lock') if project.active? %>
<%= link_to(l(:button_unarchive), { :controller => 'projects', :action => 'unarchive', :id => project, :status => params[:status] }, :method => :post, :class => 'icon icon-unlock') if !project.active? && (project.parent.nil? || project.parent.active?) %>
<%= link_to(l(:button_copy), { :controller => 'projects', :action => 'copy', :id => project }, :class => 'icon icon-copy') %>
<%= link_to(l(:button_delete), { :controller => 'projects', :action => 'destroy', :id => project }, :class => 'icon icon-del') %>
<%= link_to(l(:button_delete), project_destroy_confirm_path(project), :class => 'icon icon-del') %>
</td>
</tr>
<% end %>

View File

@ -4,20 +4,23 @@
<% if !@issue.nil? -%>
<li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue},
:class => 'icon-edit', :disabled => !@can[:edit] %></li>
<li class="folder">
<a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a>
<ul>
<% @statuses.each do |s| -%>
<li><%= context_menu_link s.name, {:controller => 'issues', :action => 'update', :id => @issue, :issue => {:status_id => s}, :back_url => @back}, :method => :put,
:selected => (s == @issue.status), :disabled => !(@can[:update] && @allowed_statuses.include?(s)) %></li>
<% end -%>
</ul>
</li>
<% else %>
<li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id)},
:class => 'icon-edit', :disabled => !@can[:edit] %></li>
<% end %>
<% unless @allowed_statuses.empty? %>
<li class="folder">
<a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a>
<ul>
<% @statuses.each do |s| -%>
<li><%= context_menu_link s.name, {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id), :issue => {:status_id => s}, :back_url => @back}, :method => :post,
:selected => (@issue && s == @issue.status), :disabled => !(@can[:update] && @allowed_statuses.include?(s)) %></li>
<% end -%>
</ul>
</li>
<% end %>
<% unless @trackers.nil? %>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_tracker) %></a>
@ -29,6 +32,8 @@
</ul>
</li>
<% end %>
<% if @projects.size == 1 %>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_priority) %></a>
<ul>
@ -38,6 +43,8 @@
<% end -%>
</ul>
</li>
<% end %>
<% unless @project.nil? || @project.shared_versions.open.empty? -%>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_fixed_version) %></a>
@ -77,7 +84,8 @@
</ul>
</li>
<% end -%>
<% if Issue.use_field_for_done_ratio? %>
<% if Issue.use_field_for_done_ratio? && @projects.size == 1 %>
<li class="folder">
<a href="#" class="submenu"><%= l(:field_done_ratio) %></a>
<ul>
@ -88,6 +96,7 @@
</ul>
</li>
<% end %>
<% if !@issue.nil? %>
<% if @can[:log_time] -%>
<li><%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue},

View File

@ -1,5 +1,5 @@
<div class="contextual">
<%= link_to_if_authorized l(:label_attachment_new), {:controller => 'projects', :action => 'add_file', :id => @project}, :class => 'icon icon-add' %>
<%= link_to_if_authorized l(:label_attachment_new), new_project_file_path(@project), :class => 'icon icon-add' %>
</div>
<h2><%=l(:label_attachment_plural)%></h2>

View File

@ -2,7 +2,7 @@
<%= error_messages_for 'attachment' %>
<div class="box">
<% form_tag({ :action => 'add_file', :id => @project }, :multipart => true, :class => "tabular") do %>
<% form_tag(project_files_path(@project), :multipart => true, :class => "tabular") do %>
<% if @versions.any? %>
<p><label for="version_id"><%=l(:field_version)%></label>

View File

@ -1,3 +1,4 @@
<% @gantt.view = self %>
<h2><%= l(:label_gantt) %></h2>
<% form_tag(gantt_path(:month => params[:month], :year => params[:year], :months => params[:months]), :method => :put, :id => 'query_form') do %>
@ -55,11 +56,12 @@ if @gantt.zoom >1
end
end
# Width of the entire chart
g_width = (@gantt.date_to - @gantt.date_from + 1)*zoom
g_height = [(20 * @gantt.events.length + 6)+150, 206].max
# Collect the number of issues on Versions
g_height = [(20 * (@gantt.number_of_rows + 6))+150, 206].max
t_height = g_height + headers_height
%>
<table width="100%" style="border:0; border-collapse: collapse;">
<tr>
<td style="width:<%= subject_width %>px; padding:0px;">
@ -67,26 +69,10 @@ t_height = g_height + headers_height
<div style="position:relative;height:<%= t_height + 24 %>px;width:<%= subject_width + 1 %>px;">
<div style="right:-2px;width:<%= subject_width %>px;height:<%= headers_height %>px;background: #eee;" class="gantt_hdr"></div>
<div style="right:-2px;width:<%= subject_width %>px;height:<%= t_height %>px;border-left: 1px solid #c0c0c0;overflow:hidden;" class="gantt_hdr"></div>
<%
#
# Tasks subjects
#
top = headers_height + 8
@gantt.events.each do |i|
left = 4 + (i.is_a?(Issue) ? i.level * 16 : 0)
%>
<div style="position: absolute;line-height:1.2em;height:16px;top:<%= top %>px;left:<%= left %>px;overflow:hidden;"><small>
<% if i.is_a? Issue %>
<%= h("#{i.project} -") unless @project && @project == i.project %>
<%= link_to_issue i %>
<% else %>
<span class="icon icon-package">
<%= link_to_version i %>
</span>
<% end %>
</small></div>
<% top = top + 20
end %>
<% top = headers_height + 8 %>
<%= @gantt.subjects(:headers_height => headers_height, :top => top, :g_width => g_width) %>
</div>
</td>
<td>
@ -164,53 +150,9 @@ if show_days
end
end %>
<%
#
# Tasks
#
top = headers_height + 10
@gantt.events.each do |i|
if i.is_a? Issue
i_start_date = (i.start_date >= @gantt.date_from ? i.start_date : @gantt.date_from )
i_end_date = (i.due_before <= @gantt.date_to ? i.due_before : @gantt.date_to )
i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor
i_done_date = (i_done_date <= @gantt.date_from ? @gantt.date_from : i_done_date )
i_done_date = (i_done_date >= @gantt.date_to ? @gantt.date_to : i_done_date )
i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
i_left = ((i_start_date - @gantt.date_from)*zoom).floor
i_width = ((i_end_date - i_start_date + 1)*zoom).floor - 2 # total width of the issue (- 2 for left and right borders)
d_width = ((i_done_date - i_start_date)*zoom).floor - 2 # done width
l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor - 2 : 0 # delay width
css = "task " + (i.leaf? ? 'leaf' : 'parent')
%>
<div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= i_width %>px;" class="<%= css %> task_todo"><div class="left"></div>&nbsp;<div class="right"></div></div>
<% if l_width > 0 %>
<div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= l_width %>px;" class="<%= css %> task_late">&nbsp;</div>
<% end %>
<% if d_width > 0 %>
<div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= d_width %>px;" class="<%= css %> task_done">&nbsp;</div>
<% end %>
<div style="top:<%= top %>px;left:<%= i_left + i_width + 8 %>px;background:#fff;" class="<%= css %>">
<%= i.status.name %>
<%= (i.done_ratio).to_i %>%
</div>
<div class="tooltip" style="position: absolute;top:<%= top %>px;left:<%= i_left %>px;width:<%= i_width %>px;height:12px;">
<span class="tip">
<%= render_issue_tooltip i %>
</span></div>
<% else
i_left = ((i.start_date - @gantt.date_from)*zoom).floor
%>
<div style="top:<%= top %>px;left:<%= i_left %>px;width:15px;" class="task milestone">&nbsp;</div>
<div style="top:<%= top %>px;left:<%= i_left + 12 %>px;background:#fff;" class="task">
<strong><%= format_version_name i %></strong>
</div>
<% end %>
<% top = top + 20
end %>
<% top = headers_height + 10 %>
<%= @gantt.lines(:top => top, :zoom => zoom, :g_width => g_width ) %>
<%
#
@ -227,8 +169,8 @@ if Date.today >= @gantt.date_from and Date.today <= @gantt.date_to %>
<table width="100%">
<tr>
<td align="left"><%= link_to_remote ('&#171; ' + l(:label_previous)), {:url => @gantt.params_previous, :update => 'content', :complete => 'window.scrollTo(0,0)'}, {:href => url_for(@gantt.params_previous)} %></td>
<td align="right"><%= link_to_remote (l(:label_next) + ' &#187;'), {:url => @gantt.params_next, :update => 'content', :complete => 'window.scrollTo(0,0)'}, {:href => url_for(@gantt.params_next)} %></td>
<td align="left"><%= link_to_remote ('&#171; ' + l(:label_previous)), {:url => @gantt.params_previous, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'}, {:href => url_for(@gantt.params_previous)} %></td>
<td align="right"><%= link_to_remote (l(:label_next) + ' &#187;'), {:url => @gantt.params_next, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'}, {:href => url_for(@gantt.params_next)} %></td>
</tr>
</table>

View File

@ -23,7 +23,7 @@
<%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
l(:label_version_new),
'version[name]',
{:controller => 'versions', :action => 'new', :project_id => @project},
{:controller => 'versions', :action => 'create', :project_id => @project},
:title => l(:label_version_new),
:tabindex => 200) if authorize_for('versions', 'new') %>
</p>
@ -40,6 +40,6 @@
</div>
<div style="clear:both;"> </div>
<%= render :partial => 'form_custom_fields' %>
<%= render :partial => 'issues/form_custom_fields' %>
<% end %>

View File

@ -20,7 +20,7 @@
</div>
<div id="attributes" class="attributes">
<%= render :partial => 'attributes' %>
<%= render :partial => 'issues/attributes' %>
</div>
<% if @issue.new_record? %>

View File

@ -1,6 +1,6 @@
<% reply_links = authorize_for('issues', 'edit') -%>
<% for journal in journals %>
<div id="change-<%= journal.id %>" class="journal">
<div id="change-<%= journal.id %>" class="<%= journal.css_classes %>">
<h4><div class="journal-link"><%= link_to "##{journal.indice}", :anchor => "note-#{journal.indice}" %></div>
<%= avatar(journal.user, :size => "24") %>
<%= content_tag('a', '', :name => "note-#{journal.indice}")%>

View File

@ -3,7 +3,7 @@
<div class="autoscroll">
<table class="list issues">
<thead><tr>
<th><%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(Element.up(this, "form")); return false;',
<th class="checkbox hide-when-print"><%= link_to image_tag('toggle_check.png'), {}, :onclick => 'toggleIssuesSelection(Element.up(this, "form")); return false;',
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
</th>
<%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %>
@ -25,7 +25,7 @@
<% previous_group = group %>
<% end %>
<tr id="issue-<%= issue.id %>" class="hascontextmenu <%= cycle('odd', 'even') %> <%= issue.css_classes %> <%= level > 0 ? "idnt idnt-#{level}" : nil %>">
<td class="checkbox"><%= check_box_tag("ids[]", issue.id, false, :id => nil) %></td>
<td class="checkbox hide-when-print"><%= check_box_tag("ids[]", issue.id, false, :id => nil) %></td>
<td class="id"><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td>
<% query.columns.each do |column| %><%= content_tag 'td', column_content(column, issue), :class => column.name %><% end %>
</tr>

View File

@ -28,6 +28,7 @@
<% remote_form_for(:relation, @relation,
:url => {:controller => 'issue_relations', :action => 'new', :issue_id => @issue},
:method => :post,
:complete => "Form.Element.focus('relation_issue_to_id');",
:html => {:id => 'new-relation-form', :style => (@relation ? '' : 'display: none;')}) do |f| %>
<%= render :partial => 'issue_relations/form', :locals => {:f => f}%>
<% end %>

View File

@ -39,6 +39,7 @@
{ :url => { :set_filter => 1 },
:before => 'selectAllOptions("selected_columns");',
:update => "content",
:complete => "apply_filters_observer()",
:with => "Form.serialize('query_form')"
}, :class => 'icon icon-checked' %>

View File

@ -1,4 +1,4 @@
<% labelled_tabular_form_for :project, @project, :url => { :action => "edit", :id => @project } do |f| %>
<% labelled_tabular_form_for :project, @project, :url => project_path(@project), :html => {:method => (@project.new_record? ? :post : :put) } do |f| %>
<%= render :partial => 'form', :locals => { :f => f } %>
<%= submit_tag l(:button_save) %>
<% end %>

View File

@ -8,7 +8,7 @@
<% end %>
</p>
<p>
<% form_tag({:controller => 'projects', :action => 'destroy', :id => @project_to_destroy}) do %>
<% form_tag(project_path(@project_to_destroy), :method => :delete) do %>
<label><%= check_box_tag 'confirm', 1 %> <%= l(:general_text_Yes) %></label>
<%= submit_tag l(:button_delete) %>
<% end %>

View File

@ -3,7 +3,7 @@
<% end %>
<div class="contextual">
<%= link_to(l(:label_project_new), {:controller => 'projects', :action => 'add'}, :class => 'icon icon-add') + ' |' if User.current.allowed_to?(:add_project, nil, :global => true) %>
<%= link_to(l(:label_project_new), {:controller => 'projects', :action => 'new'}, :class => 'icon icon-add') + ' |' if User.current.allowed_to?(:add_project, nil, :global => true) %>
<%= link_to(l(:label_issue_view_all), { :controller => 'issues' }) + ' |' if User.current.allowed_to?(:view_issues, nil, :global => true) %>
<%= link_to(l(:label_overall_spent_time), { :controller => 'time_entries' }) + ' |' if User.current.allowed_to?(:view_time_entries, nil, :global => true) %>
<%= link_to l(:label_overall_activity), { :controller => 'activities', :action => 'index' }%>

View File

@ -1,6 +1,6 @@
<h2><%=l(:label_project_new)%></h2>
<% labelled_tabular_form_for :project, @project, :url => { :action => "add" } do |f| %>
<% labelled_tabular_form_for :project, @project, :url => { :action => "create" } do |f| %>
<%= render :partial => 'form', :locals => { :f => f } %>
<fieldset class="box"><legend><%= l(:label_module_plural) %></legend>

View File

@ -1,4 +1,4 @@
<% form_tag({:controller => 'projects', :action => 'save_activities', :id => @project}, :class => "tabular") do %>
<% form_tag(project_project_enumerations_path(@project), :method => :put, :class => "tabular") do %>
<table class="list">
<thead><tr>
@ -32,7 +32,7 @@
</table>
<div class="contextual">
<%= link_to(l(:button_reset), {:controller => 'projects', :action => 'reset_activities', :id => @project},
<%= link_to(l(:button_reset), project_project_enumerations_path(@project),
:method => :delete,
:confirm => l(:text_are_you_sure),
:class => 'icon icon-del') %>

View File

@ -21,7 +21,7 @@
<td class="buttons">
<% if version.project == @project %>
<%= link_to_if_authorized l(:button_edit), {:controller => 'versions', :action => 'edit', :id => version }, :class => 'icon icon-edit' %>
<%= link_to_if_authorized l(:button_delete), {:controller => 'versions', :action => 'destroy', :id => version}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
<%= link_to_if_authorized l(:button_delete), {:controller => 'versions', :action => 'destroy', :id => version}, :confirm => l(:text_are_you_sure), :method => :delete, :class => 'icon icon-del' %>
<% end %>
</td>
</tr>
@ -34,7 +34,7 @@
<div class="contextual">
<% if @project.versions.any? %>
<%= link_to l(:label_close_versions), {:controller => 'versions', :action => 'close_completed', :project_id => @project}, :method => :post %>
<%= link_to l(:label_close_versions), close_completed_project_versions_path(@project), :method => :put %>
<% end %>
</div>

View File

@ -1,6 +1,6 @@
<div class="contextual">
<% if User.current.allowed_to?(:add_subprojects, @project) %>
<%= link_to l(:label_subproject_new), {:controller => 'projects', :action => 'add', :parent_id => @project}, :class => 'icon icon-add' %>
<%= link_to l(:label_subproject_new), {:controller => 'projects', :action => 'new', :parent_id => @project}, :class => 'icon icon-add' %>
<% end %>
</div>

View File

@ -53,6 +53,18 @@ function toggle_multi_select(field) {
select.multiple = true;
}
}
function apply_filters_observer() {
$$("#query_form input[type=text]").invoke("observe", "keypress", function(e){
if(e.keyCode == Event.KEY_RETURN) {
<%= remote_function(:url => { :set_filter => 1},
:update => "content",
:with => "Form.serialize('query_form')",
:complete => "e.stop(); apply_filters_observer()") %>
}
});
}
Event.observe(document,"dom:loaded", apply_filters_observer);
//]]>
</script>

View File

@ -1,7 +1,7 @@
<h2><%= l(:label_revision) %> <%= format_revision(@rev_to) + ':' if @rev_to %><%= format_revision(@rev) %> <%=h @path %></h2>
<!-- Choose view type -->
<% form_tag({:path => @path}, :method => 'get') do %>
<% form_tag({:path => to_path_param(@path)}, :method => 'get') do %>
<%= hidden_field_tag('rev', params[:rev]) if params[:rev] %>
<%= hidden_field_tag('rev_to', params[:rev_to]) if params[:rev_to] %>
<p><label><%= l(:label_view_diff) %></label>

View File

@ -5,7 +5,7 @@
select_tag('status_by',
status_by_options_for_select(criteria),
:id => 'status_by_select',
:onchange => remote_function(:url => { :action => :status_by, :id => version },
:onchange => remote_function(:url => status_by_project_version_path(version.project, version),
:with => "Form.serialize('status_by_form')"))) %>
</legend>
<% if counts.empty? %>

View File

@ -1,6 +1,6 @@
<h2><%=l(:label_version)%></h2>
<% labelled_tabular_form_for :version, @version, :url => { :action => 'edit' } do |f| %>
<% labelled_tabular_form_for :version, @version, :url => project_version_path(@project, @version), :html => {:method => :put} do |f| %>
<%= render :partial => 'form', :locals => { :f => f } %>
<%= submit_tag l(:button_save) %>
<% end %>

View File

@ -51,4 +51,4 @@
<% html_title(l(:label_roadmap)) %>
<%= context_menu :controller => 'issues', :action => 'context_menu' %>
<%= context_menu issues_context_menu_path %>

View File

@ -1,6 +1,6 @@
<h2><%=l(:label_version_new)%></h2>
<% labelled_tabular_form_for :version, @version, :url => { :action => 'new' } do |f| %>
<% labelled_tabular_form_for :version, @version, :url => project_versions_path(@project) do |f| %>
<%= render :partial => 'versions/form', :locals => { :f => f } %>
<%= submit_tag l(:button_create) %>
<% end %>
<% end %>

View File

@ -16,11 +16,11 @@
<% unless @pages.empty? %>
<% other_formats_links do |f| %>
<%= f.link_to 'Atom', :url => {:controller => 'projects', :action => 'activity', :id => @project, :show_wiki_edits => 1, :key => User.current.rss_key} %>
<%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'index', :id => @project, :show_wiki_edits => 1, :key => User.current.rss_key} %>
<%= f.link_to('HTML', :url => {:action => 'special', :page => 'export'}) if User.current.allowed_to?(:export_wiki_pages, @project) %>
<% end %>
<% end %>
<% content_for :header_tags do %>
<%= auto_discovery_link_tag(:atom, :controller => 'projects', :action => 'activity', :id => @project, :show_wiki_edits => 1, :format => 'atom', :key => User.current.rss_key) %>
<%= auto_discovery_link_tag(:atom, :controller => 'activities', :action => 'index', :id => @project, :show_wiki_edits => 1, :format => 'atom', :key => User.current.rss_key) %>
<% end %>

View File

@ -4,9 +4,14 @@
# Code is not reloaded between requests
config.cache_classes = true
#####
# Customize the default logger (http://ruby-doc.org/core/classes/Logger.html)
#
# Use a different logger for distributed setups
# config.logger = SyslogLogger.new
#
# Rotate logs bigger than 1MB, keeps no more than 7 rotated logs around.
# config.logger = Logger.new(config.log_path, 7, 1048576)
# Full error reports are disabled and caching is turned on
config.action_controller.consider_all_requests_local = false

View File

@ -116,6 +116,7 @@ bg:
greater_than_start_date: "трябва да е след началната дата"
not_same_project: "не е от същия проект"
circular_dependency: "Тази релация ще доведе до безкрайна зависимост"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: Изберете
@ -906,3 +907,5 @@ bg:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -130,6 +130,7 @@ bs:
greater_than_start_date: "mora biti veći nego početni datum"
not_same_project: "ne pripada istom projektu"
circular_dependency: "Ova relacija stvar cirkularnu zavisnost"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: Molimo odaberite
@ -926,3 +927,5 @@ bs:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -1,4 +1,8 @@
# Redmine catalan translation:
# by Joan Duran
ca:
# Text direction: Left-to-Right (ltr) or Right-to-Left (rtl)
direction: ltr
date:
formats:
@ -65,6 +69,7 @@ ca:
other: "almost {{count}} years"
number:
# Default format for numbers
format:
separator: "."
delimiter: ""
@ -83,6 +88,7 @@ ca:
mb: "MB"
gb: "GB"
tb: "TB"
# Used in array.to_sentence.
support:
@ -116,6 +122,7 @@ ca:
greater_than_start_date: "ha de ser superior que la data inicial"
not_same_project: "no pertany al mateix projecte"
circular_dependency: "Aquesta relació crearia una dependència circular"
cant_link_an_issue_with_a_descendant: "Un assumpte no es pot enllaçar a una de les seves subtasques"
actionview_instancetag_blank_option: Seleccioneu
@ -149,18 +156,33 @@ ca:
notice_email_sent: "S'ha enviat un correu electrònic a {{value}}"
notice_email_error: "S'ha produït un error en enviar el correu ({{value}})"
notice_feeds_access_key_reseted: "S'ha reiniciat la clau d'accés del RSS."
notice_api_access_key_reseted: "S'ha reiniciat la clau d'accés a l'API."
notice_failed_to_save_issues: "No s'han pogut desar %s assumptes de {{count}} seleccionats: {{ids}}."
notice_failed_to_save_members: "No s'han pogut desar els membres: {{errors}}."
notice_no_issue_selected: "No s'ha seleccionat cap assumpte. Activeu els assumptes que voleu editar."
notice_account_pending: "S'ha creat el compte i ara està pendent de l'aprovació de l'administrador."
notice_default_data_loaded: "S'ha carregat correctament la configuració predeterminada."
notice_unable_delete_version: "No s'ha pogut suprimir la versió."
notice_unable_delete_time_entry: "No s'ha pogut suprimir l'entrada del registre de temps."
notice_issue_done_ratios_updated: "S'ha actualitzat el tant per cent dels assumptes."
error_can_t_load_default_data: "No s'ha pogut carregar la configuració predeterminada: {{value}} "
error_scm_not_found: "No s'ha trobat l'entrada o la revisió en el dipòsit."
error_scm_command_failed: "S'ha produït un error en intentar accedir al dipòsit: {{value}}"
error_scm_annotate: "L'entrada no existeix o no s'ha pogut anotar."
error_issue_not_found_in_project: "No s'ha trobat l'assumpte o no pertany a aquest projecte"
error_no_tracker_in_project: "Aquest projecte no té seguidor associat. Comproveu els paràmetres del projecte."
error_no_default_issue_status: "No s'ha definit cap estat d'assumpte predeterminat. Comproveu la configuració (aneu a «Administració -> Estats de l'assumpte»)."
error_can_not_delete_custom_field: "No s'ha pogut suprimir el camp personalitat"
error_can_not_delete_tracker: "Aquest seguidor conté assumptes i no es pot suprimir."
error_can_not_remove_role: "Aquest rol s'està utilitzant i no es pot suprimir."
error_can_not_reopen_issue_on_closed_version: "Un assumpte assignat a una versió tancada no es pot tornar a obrir"
error_can_not_archive_project: "Aquest projecte no es pot arxivar"
error_issue_done_ratios_not_updated: "No s'ha actualitza el tant per cent dels assumptes."
error_workflow_copy_source: "Seleccioneu un seguidor o rol font"
error_workflow_copy_target: "Seleccioneu seguidors i rols objectiu"
error_unable_delete_issue_status: "No s'ha pogut suprimir l'estat de l'assumpte"
error_unable_to_connect: "No s'ha pogut connectar ({{value}})"
warning_attachments_not_saved: "No s'han pogut desar {{count}} fitxers."
mail_subject_lost_password: "Contrasenya de {{value}}"
@ -173,6 +195,10 @@ ca:
mail_body_account_activation_request: "S'ha registrat un usuari nou ({{value}}). El seu compte està pendent d'aprovació:"
mail_subject_reminder: "{{count}} assumptes venceran els següents {{days}} dies"
mail_body_reminder: "{{count}} assumptes que teniu assignades venceran els següents {{days}} dies:"
mail_subject_wiki_content_added: "S'ha afegit la pàgina wiki «{{page}}»"
mail_body_wiki_content_added: "En {{author}} ha afegit la pàgina wiki «{{page}}»."
mail_subject_wiki_content_updated: "S'ha actualitzat la pàgina wiki «{{page}}»"
mail_body_wiki_content_updated: "En {{author}} ha actualitzat la pàgina wiki «{{page}}»."
gui_validation_error: 1 error
gui_validation_error_plural: "{{count}} errors"
@ -212,6 +238,7 @@ ca:
field_priority: Prioritat
field_fixed_version: Versió objectiu
field_user: Usuari
field_principal: Principal
field_role: Rol
field_homepage: Pàgina web
field_is_public: Públic
@ -256,6 +283,7 @@ ca:
field_redirect_existing_links: Redirigeix els enllaços existents
field_estimated_hours: Temps previst
field_column_names: Columnes
field_time_entries: "Registre de temps"
field_time_zone: Zona horària
field_searchable: Es pot cercar
field_default_value: Valor predeterminat
@ -265,6 +293,9 @@ ca:
field_watcher: Vigilància
field_identity_url: URL OpenID
field_content: Contingut
field_group_by: "Agrupa els resultats per"
field_sharing: Compartició
field_parent_issue: "Tasca pare"
setting_app_title: "Títol de l'aplicació"
setting_app_subtitle: "Subtítol de l'aplicació"
@ -300,20 +331,35 @@ ca:
setting_activity_days_default: "Dies a mostrar l'activitat del projecte"
setting_display_subprojects_issues: "Mostra els assumptes d'un subprojecte en el projecte pare per defecte"
setting_enabled_scm: "Habilita l'SCM"
setting_mail_handler_body_delimiters: "Trunca els correus electrònics després d'una d'aquestes línies"
setting_mail_handler_api_enabled: "Habilita el WS per correus electrònics d'entrada"
setting_mail_handler_api_key: Clau API
setting_sequential_project_identifiers: Genera identificadors de projecte seqüencials
setting_gravatar_enabled: "Utilitza les icones d'usuari Gravatar"
setting_gravatar_default: "Imatge Gravatar predeterminada"
setting_diff_max_lines_displayed: Número màxim de línies amb diferències mostrades
setting_file_max_size_displayed: Mida màxima dels fitxers de text mostrats en línia
setting_repository_log_display_limit: Número màxim de revisions que es mostren al registre de fitxers
setting_openid: "Permet entrar i registrar-se amb l'OpenID"
setting_password_min_length: "Longitud mínima de la contrasenya"
setting_new_project_user_role_id: "Aquest rol es dóna a un usuari no administrador per a crear projectes"
setting_default_projects_modules: "Mòduls activats per defecte en els projectes nous"
setting_issue_done_ratio: "Calcula tant per cent realitzat de l'assumpte amb"
setting_issue_done_ratio_issue_status: "Utilitza l'estat de l'assumpte"
setting_issue_done_ratio_issue_field: "Utilitza el camp de l'assumpte"
setting_start_of_week: "Inicia les setmanes en"
setting_rest_api_enabled: "Habilita el servei web REST"
setting_cache_formatted_text: Cache formatted text
permission_add_project: "Crea projectes"
permission_add_subprojects: "Crea subprojectes"
permission_edit_project: Edita el projecte
permission_select_project_modules: Selecciona els mòduls del projecte
permission_manage_members: Gestiona els membres
permission_manage_project_activities: "Gestiona les activitats del projecte"
permission_manage_versions: Gestiona les versions
permission_manage_categories: Gestiona les categories dels assumptes
permission_view_issues: "Visualitza els assumptes"
permission_add_issues: Afegeix assumptes
permission_edit_issues: Edita els assumptes
permission_manage_issue_relations: Gestiona les relacions dels assumptes
@ -328,6 +374,7 @@ ca:
permission_view_calendar: Visualitza el calendari
permission_view_issue_watchers: Visualitza la llista de vigilàncies
permission_add_issue_watchers: Afegeix vigilàncies
permission_delete_issue_watchers: Suprimeix els vigilants
permission_log_time: Registra el temps invertit
permission_view_time_entries: Visualitza el temps invertit
permission_edit_time_entries: Edita els registres de temps
@ -357,6 +404,8 @@ ca:
permission_edit_own_messages: Edita els missatges propis
permission_delete_messages: Suprimeix els missatges
permission_delete_own_messages: Suprimeix els missatges propis
permission_export_wiki_pages: "Exporta les pàgines wiki"
permission_manage_subtasks: "Gestiona subtasques"
project_module_issue_tracking: "Seguidor d'assumptes"
project_module_time_tracking: Seguidor de temps
@ -366,10 +415,13 @@ ca:
project_module_wiki: Wiki
project_module_repository: Dipòsit
project_module_boards: Taulers
project_module_calendar: Calendari
project_module_gantt: Gantt
label_user: Usuari
label_user_plural: Usuaris
label_user_new: Usuari nou
label_user_anonymous: Anònim
label_project: Projecte
label_project_new: Projecte nou
label_project_plural: Projectes
@ -416,12 +468,13 @@ ca:
label_information_plural: Informació
label_please_login: Entreu
label_register: Registre
label_login_with_open_id_option: o entra amb l'OpenID
label_login_with_open_id_option: "o entra amb l'OpenID"
label_password_lost: Contrasenya perduda
label_home: Inici
label_my_page: La meva pàgina
label_my_account: El meu compte
label_my_projects: Els meus projectes
label_my_page_block: "Els meus blocs de pàgina"
label_administration: Administració
label_login: Entra
label_logout: Surt
@ -441,6 +494,7 @@ ca:
label_auth_source_new: "Mode d'autenticació nou"
label_auth_source_plural: "Modes d'autenticació"
label_subproject_plural: Subprojectes
label_subproject_new: "Subprojecte nou"
label_and_its_subprojects: "{{value}} i els seus subprojectes"
label_min_max_length: Longitud mín - max
label_list: Llist
@ -475,8 +529,9 @@ ca:
label_version: Versió
label_version_new: Versió nova
label_version_plural: Versions
label_close_versions: "Tanca les versions completades"
label_confirmation: Confirmació
label_export_to: 'També disponible a:'
label_export_to: "També disponible a:"
label_read: Llegeix...
label_public_projects: Projectes públics
label_open_issues: obert
@ -533,6 +588,8 @@ ca:
label_not_equals: no és
label_in_less_than: en menys de
label_in_more_than: en més de
label_greater_or_equal: ">="
label_less_or_equal: <=
label_in: en
label_today: avui
label_all_time: tot el temps
@ -555,17 +612,21 @@ ca:
label_browse: Navega
label_modification: "{{count}} canvi"
label_modification_plural: "{{count}} canvis"
label_branch: Branca
label_tag: Etiqueta
label_revision: Revisió
label_revision_plural: Revisions
label_revision_id: "Revisió {{value}}"
label_associated_revisions: Revisions associades
label_added: afegit
label_modified: modificat
label_renamed: reanomenat
label_copied: copiat
label_renamed: reanomenat
label_deleted: suprimit
label_latest_revision: Última revisió
label_latest_revision_plural: Últimes revisions
label_view_revisions: Visualitza les revisions
label_view_all_revisions: "Visualitza totes les revisions"
label_max_size: Mida màxima
label_sort_highest: Mou a la part superior
label_sort_higher: Mou cap amunt
@ -591,6 +652,7 @@ ca:
label_changes_details: Detalls de tots els canvis
label_issue_tracking: "Seguiment d'assumptes"
label_spent_time: Temps invertit
label_overall_spent_time: "Temps total invertit"
label_f_hour: "{{value}} hora"
label_f_hour_plural: "{{value}} hores"
label_time_tracking: Temps de seguiment
@ -628,6 +690,8 @@ ca:
label_board: Fòrum
label_board_new: Fòrum nou
label_board_plural: Fòrums
label_board_locked: Bloquejat
label_board_sticky: Sticky
label_topic_plural: Temes
label_message_plural: Missatges
label_message_last: Últim missatge
@ -643,6 +707,8 @@ ca:
label_language_based: "Basat en l'idioma de l'usuari"
label_sort_by: "Ordena per {{value}}"
label_send_test_email: Envia un correu electrònic de prova
label_feeds_access_key: "Clau d'accés del RSS"
label_missing_feeds_access_key: "Falta una clau d'accés del RSS"
label_feeds_access_key_created_on: "Clau d'accés del RSS creada fa {{value}}"
label_module_plural: Mòduls
label_added_time_by: "Afegit per {{author}} fa {{age}}"
@ -688,6 +754,28 @@ ca:
label_ascending: Ascendent
label_descending: Descendent
label_date_from_to: Des de {{start}} a {{end}}
label_wiki_content_added: "S'ha afegit la pàgina wiki"
label_wiki_content_updated: "S'ha actualitzat la pàgina wiki"
label_group: Grup
label_group_plural: Grups
label_group_new: Grup nou
label_time_entry_plural: Temps invertit
label_version_sharing_hierarchy: "Amb la jerarquia del projecte"
label_version_sharing_system: "Amb tots els projectes"
label_version_sharing_descendants: "Amb tots els subprojectes"
label_version_sharing_tree: "Amb l'arbre del projecte"
label_version_sharing_none: "Sense compartir"
label_update_issue_done_ratios: "Actualitza el tant per cent dels assumptes realitzats"
label_copy_source: Font
label_copy_target: Objectiu
label_copy_same_as_target: "El mateix que l'objectiu"
label_display_used_statuses_only: "Mostra només els estats que utilitza aquest seguidor"
label_api_access_key: "Clau d'accés a l'API"
label_missing_api_access_key: "Falta una clau d'accés de l'API"
label_api_access_key_created_on: "Clau d'accés de l'API creada fa {{value}}"
label_profile: Perfil
label_subtask_plural: Subtasques
label_project_copy_notifications: "Envia notificacions de correu electrònic durant la còpia del projecte"
button_login: Entra
button_submit: Tramet
@ -709,11 +797,12 @@ ca:
button_list: Llista
button_view: Visualitza
button_move: Mou
button_move_and_follow: "Mou i segueix"
button_back: Enrere
button_cancel: Cancel·la
button_activate: Activa
button_sort: Ordena
button_log_time: "Hora d'entrada"
button_log_time: "Registre de temps"
button_rollback: Torna a aquesta versió
button_watch: Vigila
button_unwatch: No vigilis
@ -724,15 +813,24 @@ ca:
button_rename: Reanomena
button_change_password: Canvia la contrasenya
button_copy: Copia
button_copy_and_follow: "Copia i segueix"
button_annotate: Anota
button_update: Actualitza
button_configure: Configura
button_quote: Cita
button_duplicate: Duplica
button_show: Mostra
status_active: actiu
status_registered: informat
status_locked: bloquejat
version_status_open: oberta
version_status_locked: bloquejada
version_status_closed: tancada
field_active: Actiu
text_select_mail_notifications: "Seleccioneu les accions per les quals s'hauria d'enviar una notificació per correu electrònic."
text_regexp_info: ex. ^[A-Z0-9]+$
text_min_max_length_info: 0 significa sense restricció
@ -740,6 +838,10 @@ ca:
text_subprojects_destroy_warning: "També seran suprimits els seus subprojectes: {{value}}."
text_workflow_edit: Seleccioneu un rol i un seguidor per a editar el flux de treball
text_are_you_sure: Segur?
text_journal_changed: "{{label}} ha canviat de {{old}} a {{new}}"
text_journal_set_to: "{{label}} s'ha establert a {{value}}"
text_journal_deleted: "{{label}} s'ha suprimit ({{old}})"
text_journal_added: "S'ha afegit {{label}} {{value}}"
text_tip_task_begin_day: "tasca que s'inicia aquest dia"
text_tip_task_end_day: tasca que finalitza aquest dia
text_tip_task_begin_end_day: "tasca que s'inicia i finalitza aquest dia"
@ -750,6 +852,7 @@ ca:
text_tracker_no_workflow: "No s'ha definit cap flux de treball per a aquest seguidor"
text_unallowed_characters: Caràcters no permesos
text_comma_separated: Es permeten valors múltiples (separats per una coma).
text_line_separated: "Es permeten diversos valors (una línia per cada valor)."
text_issues_ref_in_commit_messages: Referència i soluciona els assumptes en els missatges publicats
text_issue_added: "L'assumpte {{id}} ha sigut informat per {{author}}."
text_issue_updated: "L'assumpte {{id}} ha sigut actualitzat per {{author}}."
@ -770,14 +873,21 @@ ca:
text_destroy_time_entries_question: "S'han informat {{hours}} hores en els assumptes que aneu a suprimir. Què voleu fer?"
text_destroy_time_entries: Suprimeix les hores informades
text_assign_time_entries_to_project: Assigna les hores informades al projecte
text_reassign_time_entries: 'Torna a assignar les hores informades a aquest assumpte:'
text_reassign_time_entries: "Torna a assignar les hores informades a aquest assumpte:"
text_user_wrote: "{{value}} va escriure:"
text_enumeration_destroy_question: "{{count}} objectes estan assignats a aquest valor."
text_enumeration_category_reassign_to: 'Torna a assignar-los a aquest valor:'
text_enumeration_category_reassign_to: "Torna a assignar-los a aquest valor:"
text_email_delivery_not_configured: "El lliurament per correu electrònic no està configurat i les notificacions estan inhabilitades.\nConfigureu el servidor SMTP a config/email.yml i reinicieu l'aplicació per habilitar-lo."
text_repository_usernames_mapping: "Seleccioneu l'assignació entre els usuaris del Redmine i cada nom d'usuari trobat al dipòsit.\nEls usuaris amb el mateix nom d'usuari o correu del Redmine i del dipòsit s'assignaran automàticament."
text_diff_truncated: "... Aquestes diferències s'han trucat perquè excedeixen la mida màxima que es pot mostrar."
text_custom_field_possible_values_info: 'Una línia per a cada valor'
text_custom_field_possible_values_info: "Una línia per a cada valor"
text_wiki_page_destroy_question: "Aquesta pàgina té {{descendants}} pàgines fill i descendents. Què voleu fer?"
text_wiki_page_nullify_children: "Deixa les pàgines fill com a pàgines arrel"
text_wiki_page_destroy_children: "Suprimeix les pàgines fill i tots els seus descendents"
text_wiki_page_reassign_children: "Reasigna les pàgines fill a aquesta pàgina pare"
text_own_membership_delete_confirmation: "Esteu a punt de suprimir algun o tots els vostres permisos i potser no podreu editar més aquest projecte.\nSegur que voleu continuar?"
text_zoom_in: Redueix
text_zoom_out: Amplia
default_role_manager: Gestor
default_role_developer: Desenvolupador
@ -804,108 +914,7 @@ ca:
enumeration_issue_priorities: Prioritat dels assumptes
enumeration_doc_categories: Categories del document
enumeration_activities: Activitats (seguidor de temps)
label_greater_or_equal: ">="
label_less_or_equal: <=
text_wiki_page_destroy_question: This page has {{descendants}} child page(s) and descendant(s). What do you want to do?
text_wiki_page_reassign_children: Reassign child pages to this parent page
text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants
setting_password_min_length: Minimum password length
field_group_by: Group results by
mail_subject_wiki_content_updated: "'{{page}}' wiki page has been updated"
label_wiki_content_added: Wiki page added
mail_subject_wiki_content_added: "'{{page}}' wiki page has been added"
mail_body_wiki_content_added: The '{{page}}' wiki page has been added by {{author}}.
label_wiki_content_updated: Wiki page updated
mail_body_wiki_content_updated: The '{{page}}' wiki page has been updated by {{author}}.
permission_add_project: Create project
setting_new_project_user_role_id: Role given to a non-admin user who creates a project
label_view_all_revisions: View all revisions
label_tag: Tag
label_branch: Branch
error_no_tracker_in_project: No tracker is associated to this project. Please check the Project settings.
error_no_default_issue_status: No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").
text_journal_changed: "{{label}} changed from {{old}} to {{new}}"
text_journal_set_to: "{{label}} set to {{value}}"
text_journal_deleted: "{{label}} deleted ({{old}})"
label_group_plural: Groups
label_group: Group
label_group_new: New group
label_time_entry_plural: Spent time
text_journal_added: "{{label}} {{value}} added"
field_active: Active
enumeration_system_activity: System Activity
permission_delete_issue_watchers: Delete watchers
version_status_closed: closed
version_status_locked: locked
version_status_open: open
error_can_not_reopen_issue_on_closed_version: An issue assigned to a closed version can not be reopened
label_user_anonymous: Anonymous
button_move_and_follow: Move and follow
setting_default_projects_modules: Default enabled modules for new projects
setting_gravatar_default: Default Gravatar image
field_sharing: Sharing
label_version_sharing_hierarchy: With project hierarchy
label_version_sharing_system: With all projects
label_version_sharing_descendants: With subprojects
label_version_sharing_tree: With project tree
label_version_sharing_none: Not shared
error_can_not_archive_project: This project can not be archived
button_duplicate: Duplicate
button_copy_and_follow: Copy and follow
label_copy_source: Source
setting_issue_done_ratio: Calculate the issue done ratio with
setting_issue_done_ratio_issue_status: Use the issue status
error_issue_done_ratios_not_updated: Issue done ratios not updated.
error_workflow_copy_target: Please select target tracker(s) and role(s)
setting_issue_done_ratio_issue_field: Use the issue field
label_copy_same_as_target: Same as target
label_copy_target: Target
notice_issue_done_ratios_updated: Issue done ratios updated.
error_workflow_copy_source: Please select a source tracker or role
label_update_issue_done_ratios: Update issue done ratios
setting_start_of_week: Start calendars on
permission_view_issues: View Issues
label_display_used_statuses_only: Only display statuses that are used by this tracker
label_revision_id: Revision {{value}}
label_api_access_key: API access key
label_api_access_key_created_on: API access key created {{value}} ago
label_feeds_access_key: RSS access key
notice_api_access_key_reseted: Your API access key was reset.
setting_rest_api_enabled: Enable REST web service
label_missing_api_access_key: Missing an API access key
label_missing_feeds_access_key: Missing a RSS access key
button_show: Show
text_line_separated: Multiple values allowed (one line for each value).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
permission_add_subprojects: Create subprojects
label_subproject_new: New subproject
text_own_membership_delete_confirmation: |-
You are about to remove some or all of your permissions and may no longer be able to edit this project after that.
Are you sure you want to continue?
label_close_versions: Close completed versions
label_board_sticky: Sticky
label_board_locked: Locked
permission_export_wiki_pages: Export wiki pages
setting_cache_formatted_text: Cache formatted text
permission_manage_project_activities: Manage project activities
error_unable_delete_issue_status: Unable to delete issue status
label_profile: Profile
permission_manage_subtasks: Manage subtasks
field_parent_issue: Parent task
label_subtask_plural: Subtasks
label_project_copy_notifications: Send email notifications during the project copy
error_can_not_delete_custom_field: Unable to delete custom field
error_unable_to_connect: Unable to connect ({{value}})
error_can_not_remove_role: This role is in use and can not be deleted.
error_can_not_delete_tracker: This tracker contains issues and can't be deleted.
field_principal: Principal
label_my_page_block: My page block
notice_failed_to_save_members: "Failed to save member(s): {{errors}}."
text_zoom_out: Zoom out
text_zoom_in: Zoom in
notice_unable_delete_time_entry: Unable to delete time log entry.
label_overall_spent_time: Overall spent time
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
enumeration_system_activity: Activitat del sistema
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -116,6 +116,7 @@ cs:
greater_than_start_date: "musí být větší než počáteční datum"
not_same_project: "nepatří stejnému projektu"
circular_dependency: "Tento vztah by vytvořil cyklickou závislost"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
# Updated by Josef Liška <jl@chl.cz>
# CZ translation by Maxim Krušina | Massimo Filippi, s.r.o. | maxim@mxm.cz
@ -912,3 +913,5 @@ cs:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -130,6 +130,7 @@ da:
greater_than_start_date: "skal være senere end startdatoen"
not_same_project: "hører ikke til samme projekt"
circular_dependency: "Denne relation vil skabe et afhængighedsforhold"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
template:
header:
@ -928,3 +929,5 @@ da:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -932,3 +932,5 @@ de:
enumeration_activities: Aktivitäten (Zeiterfassung)
enumeration_system_activity: System-Aktivität
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -119,6 +119,7 @@ el:
greater_than_start_date: "πρέπει να είναι αργότερα από την ημερομηνία έναρξης"
not_same_project: "δεν ανήκει στο ίδιο έργο"
circular_dependency: "Αυτή η σχέση θα δημιουργήσει κυκλικές εξαρτήσεις"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: Παρακαλώ επιλέξτε
@ -912,3 +913,5 @@ el:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -122,6 +122,7 @@ en-GB:
greater_than_start_date: "must be greater than start date"
not_same_project: "doesn't belong to the same project"
circular_dependency: "This relation would create a circular dependency"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: Please select
@ -916,3 +917,5 @@ en-GB:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -293,6 +293,8 @@ en:
field_group_by: Group results by
field_sharing: Sharing
field_parent_issue: Parent task
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role
setting_app_title: Application title
setting_app_subtitle: Application subtitle

View File

@ -132,6 +132,7 @@ es:
greater_than_start_date: "debe ser posterior a la fecha de comienzo"
not_same_project: "no pertenece al mismo proyecto"
circular_dependency: "Esta relación podría crear una dependencia circular"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
# Append your own errors here or at the model/attributes scope.
@ -952,3 +953,5 @@ es:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -121,6 +121,7 @@ eu:
greater_than_start_date: "hasiera data baino handiagoa izan behar du"
not_same_project: "ez dago proiektu berdinean"
circular_dependency: "Erlazio honek mendekotasun zirkular bat sortuko luke"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: Hautatu mesedez
@ -916,3 +917,5 @@ eu:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -147,7 +147,7 @@ fi:
greater_than_start_date: "tulee olla aloituspäivän jälkeinen"
not_same_project: "ei kuulu samaan projektiin"
circular_dependency: "Tämä suhde loisi kehän."
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: Valitse, ole hyvä
@ -938,3 +938,5 @@ fi:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -931,3 +931,5 @@ fr:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -150,6 +150,7 @@ gl:
greater_than_start_date: "debe ser posterior á data de comezo"
not_same_project: "non pertence ao mesmo proxecto"
circular_dependency: "Esta relación podería crear unha dependencia circular"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: Por favor seleccione
@ -928,3 +929,5 @@ gl:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -123,6 +123,7 @@ he:
greater_than_start_date: "חייב להיות מאוחר יותר מתאריך ההתחלה"
not_same_project: "לא שייך לאותו הפרויקט"
circular_dependency: "הקשר הזה יצור תלות מעגלית"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: בחר בבקשה
@ -917,3 +918,5 @@ he:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -117,6 +117,7 @@ hr:
greater_than_start_date: "mora biti veci nego pocetni datum"
not_same_project: "ne pripada istom projektu"
circular_dependency: "Ovaj relacija stvara kružnu ovisnost"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: Molimo odaberite
@ -919,3 +920,5 @@ hr:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -143,6 +143,7 @@
greater_than_start_date: "nagyobbnak kell lennie, mint az indítás dátuma"
not_same_project: "nem azonos projekthez tartozik"
circular_dependency: "Ez a kapcsolat egy körkörös függőséget eredményez"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: Kérem válasszon
@ -935,3 +936,5 @@
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -119,6 +119,7 @@ id:
greater_than_start_date: "harus lebih besar dari tanggal mulai"
not_same_project: "tidak tergabung dalam proyek yang sama"
circular_dependency: "kaitan ini akan menghasilkan circular dependency"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: Silakan pilih
@ -920,3 +921,5 @@ id:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -126,6 +126,7 @@ it:
greater_than_start_date: "deve essere maggiore della data di partenza"
not_same_project: "non appartiene allo stesso progetto"
circular_dependency: "Questa relazione creerebbe una dipendenza circolare"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: Scegli
@ -916,3 +917,5 @@ it:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -144,6 +144,7 @@ ja:
greater_than_start_date: "を開始日より後にしてください"
not_same_project: "同じプロジェクトに属していません"
circular_dependency: "この関係では、循環依存になります"
cant_link_an_issue_with_a_descendant: "指定したチケットとは親子関係になっているため関連づけられません"
actionview_instancetag_blank_option: 選んでください
@ -318,6 +319,8 @@ ja:
field_group_by: グループ条件
field_sharing: 共有
field_parent_issue: 親チケット
field_member_of_group: 担当者のグループ
field_assigned_to_role: 担当者のロール
setting_app_title: アプリケーションのタイトル
setting_app_subtitle: アプリケーションのサブタイトル

View File

@ -2,7 +2,7 @@
# by Kihyun Yoon(ddumbugie@gmail.com),http://plenum.textcube.com/
# by John Hwang (jhwang@tavon.org),http://github.com/tavon
# by Yonghwan SO(please insert your email), last update at 2009-09-11
# last update at 2010-01-23 by Kihyun Yoon
# last update at 2010-09-06 by Kihyun Yoon
ko:
direction: ltr
date:
@ -173,6 +173,7 @@ ko:
greater_than_start_date: "는 시작날짜보다 커야 합니다"
not_same_project: "는 같은 프로젝트에 속해 있지 않습니다"
circular_dependency: "이 관계는 순환 의존관계를 만들 수 있습니다"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: 선택하세요
@ -958,13 +959,15 @@ ko:
error_unable_to_connect: 연결할 수 없습니다(({{value}})
error_can_not_remove_role: 이 역할은 현재 사용 중이이서 삭제할 수 없습니다.
error_can_not_delete_tracker: 이 유형의 일감들이 있에서 삭제할 수 없습니다.
field_principal: Principal
label_my_page_block: My page block
field_principal: 신원
label_my_page_block: 내 페이지 출력화면
notice_failed_to_save_members: "Failed to save member(s): {{errors}}."
text_zoom_out: Zoom out
text_zoom_in: Zoom in
notice_unable_delete_time_entry: Unable to delete time log entry.
label_overall_spent_time: Overall spent time
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
text_zoom_out: 더 작게
text_zoom_in: 더 크게
notice_unable_delete_time_entry: 시간 기록 항목을 삭제할 수 없습니다.
label_overall_spent_time: 총 소요시간
field_time_entries: 기록된 시간
project_module_gantt: Gantt 챠트
project_module_calendar: 달력
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -143,7 +143,7 @@ lt:
other: "Išsaugant objektą {{model}} rastos {{count}} klaidos"
body: "Šiuose laukuose yra klaidų:"
pranešimus:
messages:
inclusion: "nenumatyta reikšmė"
exclusion: "užimtas"
invalid: "neteisingas"
@ -179,6 +179,7 @@ lt:
greater_than_start_date: "turi būti didesnė negu pradžios data"
not_same_project: "nepriklauso tam pačiam projektui"
circular_dependency: "Šis ryšys sukurtų ciklinę priklausomybę"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: prašom parinkti
@ -976,3 +977,5 @@ lt:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -113,6 +113,7 @@ lv:
greater_than_start_date: "jābūt vēlākam par sākuma datumu"
not_same_project: "nepieder pie tā paša projekta"
circular_dependency: "Šī relācija radītu ciklisku atkarību"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: Izvēlieties
@ -907,3 +908,5 @@ lv:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -913,3 +913,5 @@ mk:
enumeration_activities: Активности (следење на време)
enumeration_system_activity: Системска активност
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -117,6 +117,7 @@ mn:
greater_than_start_date: "must be greater than start date"
not_same_project: "нэг ижил төсөлд хамаарахгүй байна"
circular_dependency: "Энэ харьцаа нь гинжин(рекурсив) харьцаа үүсгэх юм байна"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: Сонгоно уу
@ -913,3 +914,5 @@ mn:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -116,6 +116,7 @@ nl:
greater_than_start_date: "moet na de startdatum liggen"
not_same_project: "hoort niet bij hetzelfde project"
circular_dependency: "Deze relatie zou een circulaire afhankelijkheid tot gevolg hebben"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: Selecteer
@ -701,7 +702,7 @@ nl:
setting_date_format: Datumformaat
setting_default_language: Standaard taal
setting_default_projects_public: Nieuwe projecten zijn standaard publiek
setting_diff_max_lines_displayed: Max number of diff lines displayed
setting_diff_max_lines_displayed: Max aantal diff regels weer te geven
setting_display_subprojects_issues: Standaard issues van subproject tonen
setting_emails_footer: E-mails footer
setting_enabled_scm: SCM ingeschakeld
@ -709,7 +710,7 @@ nl:
setting_gravatar_enabled: Gebruik Gravatar gebruikersiconen
setting_host_name: Hostnaam
setting_issue_list_default_columns: Standaardkolommen getoond op de lijst met issues
setting_issues_export_limit: Limiet export issues
setting_issues_export_limit: Max aantal te exporteren issues
setting_login_required: Authenticatie vereist
setting_mail_from: Afzender e-mail adres
setting_mail_handler_api_enabled: Schakel WS in voor inkomende mail.
@ -727,7 +728,7 @@ nl:
setting_welcome_text: Welkomsttekst
setting_wiki_compression: Wikigeschiedenis comprimeren
status_active: actief
status_locked: gelockt
status_locked: vergrendeld
status_registered: geregistreerd
text_are_you_sure: Weet u het zeker?
text_assign_time_entries_to_project: Gerapporteerde uren toevoegen aan dit project
@ -753,7 +754,7 @@ nl:
text_load_default_configuration: Laad de standaardconfiguratie
text_min_max_length_info: 0 betekent geen restrictie
text_no_configuration_data: "Rollen, trackers, issue statussen en workflows zijn nog niet geconfigureerd.\nHet is ten zeerste aangeraden om de standaard configuratie in te laden. U kunt deze aanpassen nadat deze is ingeladen."
text_plugin_assets_writable: Plugin assets directory writable
text_plugin_assets_writable: Plugin assets directory beschrijfbaar
text_project_destroy_confirmation: Weet u zeker dat u dit project en alle gerelateerde gegevens wilt verwijderen?
text_project_identifier_info: 'kleine letters (a-z), cijfers en liggende streepjes toegestaan.<br />Eenmaal bewaard kan de identificatiecode niet meer worden gewijzigd.'
text_reassign_time_entries: 'Gerapporteerde uren opnieuw toewijzen:'
@ -778,7 +779,7 @@ nl:
text_custom_field_possible_values_info: 'Per lijn een waarde'
label_display: Toon
field_editable: Bewerkbaar
setting_repository_log_display_limit: Maximum hoeveelheid van revisies zichbaar
setting_repository_log_display_limit: Max aantal revisies zichbaar
setting_file_max_size_displayed: Max grootte van tekst bestanden inline zichtbaar
field_watcher: Watcher
setting_openid: Sta OpenID login en registratie toe
@ -824,7 +825,7 @@ nl:
version_status_closed: gesloten
version_status_locked: vergrendeld
version_status_open: open
error_can_not_reopen_issue_on_closed_version: An issue assigned to a closed version can not be reopened
error_can_not_reopen_issue_on_closed_version: Een issue toegewezen aan een gesloten versie kan niet heropend worden
label_user_anonymous: Anoniem
button_move_and_follow: Verplaats en volg
setting_default_projects_modules: Standaard geactiveerde modules voor nieuwe projecten
@ -833,36 +834,36 @@ nl:
label_version_sharing_hierarchy: Met project hiërarchie
label_version_sharing_system: Met alle projecten
label_version_sharing_descendants: Met subprojecten
label_version_sharing_tree: With project tree
label_version_sharing_tree: Met project boom
label_version_sharing_none: Niet gedeeld
error_can_not_archive_project: Dit project kan niet worden gearchiveerd
button_duplicate: Dupliceer
button_copy_and_follow: Kopiëer en volg
label_copy_source: Bron
setting_issue_done_ratio: Bereken issue done ratio met
setting_issue_done_ratio: Bereken issue percentage voldaan met
setting_issue_done_ratio_issue_status: Gebruik de issue status
error_issue_done_ratios_not_updated: Issue done ratios niet geupdate.
error_issue_done_ratios_not_updated: Issue percentage voldaan niet geupdate.
error_workflow_copy_target: Selecteer tracker(s) en rol(len)
setting_issue_done_ratio_issue_field: Gebruik het issue veld
label_copy_same_as_target: Zelfde als doel
label_copy_target: Doel
notice_issue_done_ratios_updated: Issue done ratios updated.
notice_issue_done_ratios_updated: Issue percentage voldaan geupdate.
error_workflow_copy_source: Selecteer een bron tracker of rol
label_update_issue_done_ratios: Update issue done ratios
label_update_issue_done_ratios: Update issue percentage voldaan
setting_start_of_week: Week begint op
permission_view_issues: Bekijk Issues
label_display_used_statuses_only: Laat alleen statussen zien die gebruikt worden door deze tracker
label_revision_id: Revision {{value}}
label_revision_id: Revisie {{value}}
label_api_access_key: API access key
label_api_access_key_created_on: API access key gemaakt {{value}} geleden
label_feeds_access_key: RSS access key
notice_api_access_key_reseted: Uw API access key was gereset.
setting_rest_api_enabled: Enable REST web service
setting_rest_api_enabled: Activeer REST web service
label_missing_api_access_key: Geen API access key
label_missing_feeds_access_key: Geen RSS access key
button_show: Laat zien
text_line_separated: Meerdere waarden toegestaan (elke regel is een waarde).
setting_mail_handler_body_delimiters: Truncate emails after one of these lines
setting_mail_handler_body_delimiters: Breek email verwerking af na een van deze regels
permission_add_subprojects: Maak subprojecten
label_subproject_new: Nieuw subproject
text_own_membership_delete_confirmation: |-
@ -873,10 +874,10 @@ nl:
label_board_locked: Vergrendeld
permission_export_wiki_pages: Exporteer wiki pagina's
setting_cache_formatted_text: Cache opgemaakte tekst
permission_manage_project_activities: Manage project activities
permission_manage_project_activities: Beheer project activiteiten
error_unable_delete_issue_status: Verwijderen van issue status niet gelukt
label_profile: Profiel
permission_manage_subtasks: Manage subtasks
permission_manage_subtasks: Beheer subtasks
field_parent_issue: Parent task
label_subtask_plural: Subtasks
label_project_copy_notifications: Stuur email notificaties voor de project kopie
@ -891,6 +892,8 @@ nl:
text_zoom_in: Zoom in
notice_unable_delete_time_entry: Verwijderen niet mogelijk van tijd log invoer.
label_overall_spent_time: Totaal bestede tijd
field_time_entries: Log time
field_time_entries: Log tijd
project_module_gantt: Gantt
project_module_calendar: Calendar
project_module_calendar: Kalender
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -111,6 +111,7 @@
greater_than_start_date: "må være større enn startdato"
not_same_project: "hører ikke til samme prosjekt"
circular_dependency: "Denne relasjonen ville lagd en sirkulær avhengighet"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: Vennligst velg
@ -903,3 +904,5 @@
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -96,8 +96,8 @@ pl:
few: "ponad {{count}} lata"
other: "ponad {{count}} lat"
almost_x_years:
one: "almost 1 year"
other: "almost {{count}} years"
one: "prawie rok"
other: "prawie {{count}} lata"
activerecord:
errors:
@ -122,13 +122,14 @@ pl:
greater_than: "musi być większe niż {{count}}"
greater_than_or_equal_to: "musi być większe lub równe {{count}}"
equal_to: "musi być równe {{count}}"
less_than: "musie być mniejsze niż {{count}}"
less_than: "musi być mniejsze niż {{count}}"
less_than_or_equal_to: "musi być mniejsze lub równe {{count}}"
odd: "musi być nieparzyste"
even: "musi być parzyste"
greater_than_start_date: "musi być większe niż początkowa data"
not_same_project: "nie należy do tego samego projektu"
circular_dependency: "Ta relacja może wytworzyć kołową zależność"
cant_link_an_issue_with_a_descendant: "Zagadnienie nie może zostać powiązane z jednym z własnych podzagadnień"
support:
array:
@ -159,19 +160,19 @@ pl:
button_edit: Edytuj
button_list: Lista
button_lock: Zablokuj
button_log_time: Log czasu
button_log_time: Dziennik
button_login: Login
button_move: Przenieś
button_quote: Cytuj
button_rename: Zmień nazwę
button_reply: Odpowiedz
button_reset: Resetuj
button_rollback: Przywróc do tej wersji
button_rollback: Przywróć do tej wersji
button_save: Zapisz
button_sort: Sortuj
button_submit: Wyślij
button_test: Testuj
button_unarchive: Przywróc z archiwum
button_unarchive: Przywróć z archiwum
button_uncheck_all: Odznacz wszystko
button_unlock: Odblokuj
button_unwatch: Nie obserwuj
@ -538,12 +539,12 @@ pl:
label_project_latest: Ostatnie projekty
label_project_new: Nowy projekt
label_project_plural234: Projekty
label_project_plural5: Projekty
label_project_plural5: Projektów
label_project_plural: Projekty
label_x_projects:
zero: no projects
one: 1 project
other: "{{count}} projects"
zero: brak projektów
one: jeden projekt
other: "{{count}} projektów"
label_public_projects: Projekty publiczne
label_query: Kwerenda
label_query_new: Nowa kwerenda
@ -589,7 +590,7 @@ pl:
label_sort_highest: Przesuń na górę
label_sort_lower: Do dołu
label_sort_lowest: Przesuń na dół
label_spent_time: Spędzony czas
label_spent_time: Przepracowany czas
label_start_to_end: początek do końca
label_start_to_start: początek do początku
label_statistics: Statystyki
@ -601,7 +602,7 @@ pl:
label_this_month: ten miesiąc
label_this_week: ten tydzień
label_this_year: ten rok
label_time_tracking: Śledzenie czasu
label_time_tracking: Śledzenie czasu pracy
label_today: dzisiaj
label_topic_plural: Tematy
label_total: Ogółem
@ -683,11 +684,11 @@ pl:
permission_edit_messages: Edycja wiadomości
permission_edit_own_issue_notes: Edycja własnych notatek
permission_edit_own_messages: Edycja własnych wiadomości
permission_edit_own_time_entries: Edycja własnego logu czasu
permission_edit_own_time_entries: Edycja własnego dziennika
permission_edit_project: Edycja projektów
permission_edit_time_entries: Edycja logów czasu
permission_edit_time_entries: Edycja wpisów dziennika
permission_edit_wiki_pages: Edycja stron wiki
permission_log_time: Zapisywanie spędzonego czasu
permission_log_time: Zapisywanie przepracowanego czasu
permission_manage_boards: Zarządzanie forami
permission_manage_categories: Zarządzanie kategoriami zaganień
permission_manage_documents: Zarządzanie dokumentami
@ -711,7 +712,7 @@ pl:
permission_view_gantt: Podgląd diagramu Gantta
permission_view_issue_watchers: Podgląd listy obserwatorów
permission_view_messages: Podgląd wiadomości
permission_view_time_entries: Podgląd spędzonego czasu
permission_view_time_entries: Podgląd przepracowanego czasu
permission_view_wiki_edits: Podgląd historii wiki
permission_view_wiki_pages: Podgląd wiki
project_module_boards: Fora
@ -720,7 +721,7 @@ pl:
project_module_issue_tracking: Śledzenie zagadnień
project_module_news: Komunikaty
project_module_repository: Repozytorium
project_module_time_tracking: Śledzenie czasu
project_module_time_tracking: Śledzenie czasu pracy
project_module_wiki: Wiki
setting_activity_days_default: Dni wyświetlane w aktywności projektu
setting_app_subtitle: Podtytuł aplikacji
@ -742,7 +743,7 @@ pl:
setting_feeds_limit: Limit danych RSS
setting_gravatar_enabled: Używaj ikon użytkowników Gravatar
setting_host_name: Nazwa hosta i ścieżka
setting_issue_list_default_columns: Domyślne kolumny wiświetlane na liście zagadnień
setting_issue_list_default_columns: Domyślne kolumny wyświetlane na liście zagadnień
setting_issues_export_limit: Limit eksportu zagadnień
setting_login_required: Identyfikacja wymagana
setting_mail_from: Adres email wysyłki
@ -764,20 +765,20 @@ pl:
status_locked: zablokowany
status_registered: zarejestrowany
text_are_you_sure: Jesteś pewien ?
text_assign_time_entries_to_project: Przypisz logowany czas do projektu
text_assign_time_entries_to_project: Przypisz wpisy dziennika do projektu
text_caracters_maximum: "{{count}} znaków maksymalnie."
text_caracters_minimum: "Musi być nie krótsze niż {{count}} znaków."
text_comma_separated: Wielokrotne wartości dozwolone (rozdzielone przecinkami).
text_default_administrator_account_changed: Zmieniono domyślne hasło administratora
text_destroy_time_entries: Usuń zalogowany czas
text_destroy_time_entries_question: Zalogowano {{hours}} godzin przy zagadnieniu, które chcesz usunąć. Co chcesz zrobić?
text_destroy_time_entries: Usuń wpisy dziennika
text_destroy_time_entries_question: Przepracowano {{hours}} godzin przy zagadnieniu, które chcesz usunąć. Co chcesz zrobić?
text_email_delivery_not_configured: "Dostarczanie poczty elektronicznej nie zostało skonfigurowane, więc powiadamianie jest nieaktywne.\nSkonfiguruj serwer SMTP w config/email.yml a następnie zrestartuj aplikację i uaktywnij to."
text_enumeration_category_reassign_to: 'Zmień przypisanie na tą wartość:'
text_enumeration_destroy_question: "{{count}} obiektów jest przypisana do tej wartości."
text_file_repository_writable: Zapisywalne repozytorium plików
text_issue_added: "Zagadnienie {{id}} zostało wprowadzone (by {{author}})."
text_issue_category_destroy_assignments: Usuń przydziały kategorii
text_issue_category_destroy_question: "Zagadnienia ({{count}}) są przypisane do tej kategorii. Co chcesz uczynić?"
text_issue_category_destroy_question: "Zagadnienia ({{count}}) są przypisane do tej kategorii. Co chcesz zrobić?"
text_issue_category_reassign_to: Przydziel zagadnienie do tej kategorii
text_issue_updated: "Zagadnienie {{id}} zostało zaktualizowane (by {{author}})."
text_issues_destroy_confirmation: 'Czy jestes pewien, że chcesz usunąć wskazane zagadnienia?'
@ -786,9 +787,9 @@ pl:
text_load_default_configuration: Załaduj domyślną konfigurację
text_min_max_length_info: 0 oznacza brak restrykcji
text_no_configuration_data: "Role użytkowników, typy zagadnień, statusy zagadnień oraz przepływ pracy nie zostały jeszcze skonfigurowane.\nJest wysoce rekomendowane by załadować domyślną konfigurację. Po załadowaniu będzie możliwość edycji tych danych."
text_project_destroy_confirmation: Jesteś pewien, że chcesz usunąć ten projekt i wszyskie powiązane dane?
text_project_destroy_confirmation: Jesteś pewien, że chcesz usunąć ten projekt i wszystkie powiązane dane?
text_project_identifier_info: 'Małe litery (a-z), liczby i myślniki dozwolone.<br />Raz zapisany, identyfikator nie może być zmieniony.'
text_reassign_time_entries: 'Przepnij zalogowany czas do tego zagadnienia:'
text_reassign_time_entries: 'Przepnij przepracowany czas do tego zagadnienia:'
text_regexp_info: np. ^[A-Z0-9]+$
text_repository_usernames_mapping: "Wybierz lub uaktualnij przyporządkowanie użytkowników Redmine do użytkowników repozytorium.\nUżytkownicy z taką samą nazwą lub adresem email są przyporządkowani automatycznie."
text_rmagick_available: RMagick dostępne (opcjonalnie)
@ -799,9 +800,9 @@ pl:
text_tip_task_begin_day: zadanie zaczynające się dzisiaj
text_tip_task_begin_end_day: zadanie zaczynające i kończące się dzisiaj
text_tip_task_end_day: zadanie kończące się dzisiaj
text_tracker_no_workflow: Brak przepływu zefiniowanego dla tego typu zagadnienia
text_tracker_no_workflow: Brak przepływu zdefiniowanego dla tego typu zagadnienia
text_unallowed_characters: Niedozwolone znaki
text_user_mail_option: "W przypadku niezaznaczonych projektów, będziesz otrzymywał powiadomienia tylko na temat zagadnien, które obserwujesz, lub w których bierzesz udział (np. jesteś autorem lub adresatem)."
text_user_mail_option: "W przypadku niezaznaczonych projektów, będziesz otrzymywał powiadomienia tylko na temat zagadnień, które obserwujesz, lub w których bierzesz udział (np. jesteś autorem lub adresatem)."
text_user_wrote: "{{value}} napisał:"
text_wiki_destroy_confirmation: Jesteś pewien, że chcesz usunąć to wiki i całą jego zawartość ?
text_workflow_edit: Zaznacz rolę i typ zagadnienia do edycji przepływu
@ -817,7 +818,7 @@ pl:
button_create_and_continue: Stwórz i dodaj kolejne
text_custom_field_possible_values_info: 'Każda wartość w osobnej linii'
setting_repository_log_display_limit: Maksymalna liczba rewizji pokazywanych w logu pliku
setting_file_max_size_displayed: Maksymalny rozmiar plików tekstowych zagnieżdżanych w stronie
setting_file_max_size_displayed: Maksymalny rozmiar plików tekstowych osadzanych w stronie
field_watcher: Obserwator
setting_openid: Logowanie i rejestracja przy użyciu OpenID
field_identity_url: Identyfikator OpenID (URL)
@ -829,10 +830,10 @@ pl:
label_date_from_to: Od {{start}} do {{end}}
label_greater_or_equal: ">="
label_less_or_equal: <=
text_wiki_page_destroy_question: This page has {{descendants}} child page(s) and descendant(s). What do you want to do?
text_wiki_page_reassign_children: Reassign child pages to this parent page
text_wiki_page_nullify_children: Keep child pages as root pages
text_wiki_page_destroy_children: Delete child pages and all their descendants
text_wiki_page_destroy_question: Ta strona posiada podstrony ({{descendants}}). Co chcesz zrobić?
text_wiki_page_reassign_children: Podepnij je do strony nadrzędnej względem usuwanej
text_wiki_page_nullify_children: Przesuń je na szczyt hierarchii
text_wiki_page_destroy_children: Usuń wszystkie podstrony
setting_password_min_length: Minimalna długość hasła
field_group_by: Grupuj wyniki wg
mail_subject_wiki_content_updated: "Strona wiki '{{page}}' została uaktualniona"
@ -844,7 +845,7 @@ pl:
permission_add_project: Tworzenie projektu
setting_new_project_user_role_id: Rola nadawana twórcom projektów, którzy nie posiadają uprawnień administatora
label_view_all_revisions: Pokaż wszystkie rewizje
label_tag: Tag
label_tag: Słowo kluczowe
label_branch: Gałąź
error_no_tracker_in_project: Projekt nie posiada powiązanych typów zagadnień. Sprawdź ustawienia projektu.
error_no_default_issue_status: Nie zdefiniowano domyślnego statusu zagadnień. Sprawdź konfigurację (Przejdź do "Administracja -> Statusy zagadnień).
@ -854,7 +855,7 @@ pl:
label_group_plural: Grupy
label_group: Grupa
label_group_new: Nowa grupa
label_time_entry_plural: Spędzony czas
label_time_entry_plural: Przepracowany czas
text_journal_added: "Dodano {{label}} {{value}}"
field_active: Aktywne
enumeration_system_activity: Aktywność Systemowa
@ -912,24 +913,26 @@ pl:
label_board_locked: Zamknięta
permission_export_wiki_pages: Eksport stron wiki
permission_manage_project_activities: Zarządzanie aktywnościami projektu
setting_cache_formatted_text: Cache formatted text
error_unable_delete_issue_status: Unable to delete issue status
label_profile: Profile
permission_manage_subtasks: Manage subtasks
field_parent_issue: Parent task
label_subtask_plural: Subtasks
label_project_copy_notifications: Send email notifications during the project copy
error_can_not_delete_custom_field: Unable to delete custom field
error_unable_to_connect: Unable to connect ({{value}})
error_can_not_remove_role: This role is in use and can not be deleted.
error_can_not_delete_tracker: This tracker contains issues and can't be deleted.
field_principal: Principal
label_my_page_block: My page block
notice_failed_to_save_members: "Failed to save member(s): {{errors}}."
text_zoom_out: Zoom out
text_zoom_in: Zoom in
notice_unable_delete_time_entry: Unable to delete time log entry.
label_overall_spent_time: Overall spent time
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
setting_cache_formatted_text: Buforuj sformatowany tekst
error_unable_delete_issue_status: Nie można usunąć statusu zagadnienia
label_profile: Profil
permission_manage_subtasks: Zarządzanie podzagadnieniami
field_parent_issue: Zagadnienie nadrzędne
label_subtask_plural: Podzagadnienia
label_project_copy_notifications: Wyślij powiadomienia mailowe przy kopiowaniu projektu
error_can_not_delete_custom_field: Nie można usunąć tego pola
error_unable_to_connect: Nie można połączyć ({{value}})
error_can_not_remove_role: Ta rola przypisana jest niektórym użytkownikom i nie może zostać usunięta.
error_can_not_delete_tracker: Ten typ przypisany jest do części zagadnień i nie może zostać usunięty.
field_principal: Przełożony
label_my_page_block: Elementy
notice_failed_to_save_members: "Nie można zapisać uczestników: {{errors}}."
text_zoom_out: Zmniejsz czcionkę
text_zoom_in: Powiększ czcionkę
notice_unable_delete_time_entry: Nie można usunąć wpisu z dziennika.
label_overall_spent_time: Przepracowany czas
field_time_entries: Dziennik
project_module_gantt: Diagram Gantta
project_module_calendar: Kalendarz
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -143,6 +143,7 @@ pt-BR:
greater_than_start_date: "deve ser maior que a data inicial"
not_same_project: "não pertence ao mesmo projeto"
circular_dependency: "Esta relação geraria uma dependência circular"
cant_link_an_issue_with_a_descendant: "Uma tarefa não pode ser relaciona a uma de suas subtarefas"
actionview_instancetag_blank_option: Selecione
@ -936,3 +937,5 @@ pt-BR:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -128,6 +128,7 @@ pt:
greater_than_start_date: "deve ser maior que a data inicial"
not_same_project: "não pertence ao mesmo projecto"
circular_dependency: "Esta relação iria criar uma dependência circular"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
## Translated by: Pedro Araújo <phcrva19@hotmail.com>
actionview_instancetag_blank_option: Seleccione
@ -920,3 +921,5 @@ pt:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -110,6 +110,7 @@ ro:
greater_than_start_date: "trebuie să fie după data de început"
not_same_project: "trebuie să aparțină aceluiași proiect"
circular_dependency: "Această relație ar crea o dependență circulară"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: Selectați
@ -905,3 +906,5 @@ ro:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -192,11 +192,12 @@ ru:
equal_to: "может иметь лишь значение, равное {{count}}"
less_than: "может иметь значение меньшее чем {{count}}"
less_than_or_equal_to: "может иметь значение меньшее или равное {{count}}"
odd: "может иметь лишь четное значение"
even: "может иметь лишь нечетное значение"
odd: "может иметь лишь нечетное значение"
even: "может иметь лишь четное значение"
greater_than_start_date: "должна быть позднее даты начала"
not_same_project: "не относятся к одному проекту"
not_same_project: "не относится к одному проекту"
circular_dependency: "Такая связь приведет к циклической зависимости"
cant_link_an_issue_with_a_descendant: "Задача не может быть связана со своей подзадачей"
support:
array:
@ -293,6 +294,7 @@ ru:
field_admin: Администратор
field_assignable: Задача может быть назначена этой роли
field_assigned_to: Назначена
field_assigned_to_role: Роль участника
field_attr_firstname: Имя
field_attr_lastname: Фамилия
field_attr_login: Атрибут Регистрация
@ -342,6 +344,7 @@ ru:
field_mail: Email
field_mail_notification: Уведомления по email
field_max_length: Максимальная длина
field_member_of_group: Группа участника
field_min_length: Минимальная длина
field_name: Имя
field_new_password: Новый пароль

View File

@ -112,6 +112,7 @@ sk:
greater_than_start_date: "musí byť neskôr ako počiatočný dátum"
not_same_project: "nepatrí rovnakému projektu"
circular_dependency: "Tento vzťah by vytvoril cyklickú závislosť"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
# SK translation by Stanislav Pach | stano.pach@seznam.cz
@ -907,3 +908,5 @@ sk:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -116,6 +116,7 @@ sl:
greater_than_start_date: "mora biti kasnejši kot začeten datum"
not_same_project: "ne pripada istemu projektu"
circular_dependency: "Ta odnos bi povzročil krožno odvisnost"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: Prosimo izberite
@ -908,3 +909,5 @@ sl:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -68,7 +68,7 @@ sr-YU:
number:
format:
separator: "."
separator: ","
delimiter: ""
precision: 3
human:
@ -910,6 +910,8 @@ sr-YU:
enumeration_activities: Aktivnosti (praćenje vremena)
enumeration_system_activity: Sistemska aktivnost
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_time_entries: Vreme evidencije
project_module_gantt: Gantov dijagram
project_module_calendar: Kalendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -68,7 +68,7 @@ sr:
number:
format:
separator: "."
separator: ","
delimiter: ""
precision: 3
human:
@ -910,6 +910,9 @@ sr:
enumeration_activities: Активности (праћење времена)
enumeration_system_activity: Системска активност
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_time_entries: Време евиденције
project_module_gantt: Гантов дијаграм
project_module_calendar: Календар
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -130,6 +130,7 @@ sv:
greater_than_start_date: "måste vara senare än startdatumet"
not_same_project: "tillhör inte samma projekt"
circular_dependency: "Denna relation skulle skapa ett cirkulärt beroende"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
direction: ltr
date:
@ -957,3 +958,5 @@ sv:
enumeration_doc_categories: Dokumentkategorier
enumeration_activities: Aktiviteter (tidsuppföljning)
enumeration_system_activity: Systemaktivitet
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -116,6 +116,7 @@ th:
greater_than_start_date: "ต้องมากกว่าวันเริ่ม"
not_same_project: "ไม่ได้อยู่ในโครงการเดียวกัน"
circular_dependency: "ความสัมพันธ์อ้างอิงเป็นวงกลม"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: กรุณาเลือก
@ -909,3 +910,5 @@ th:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -143,6 +143,7 @@ tr:
greater_than_start_date: "başlangıç tarihinden büyük olmalı"
not_same_project: "aynı projeye ait değil"
circular_dependency: "Bu ilişki döngüsel bağımlılık meydana getirecektir"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
models:
actionview_instancetag_blank_option: Lütfen Seçin
@ -935,3 +936,5 @@ tr:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -116,6 +116,7 @@ uk:
greater_than_start_date: "повинна бути пізніша за дату початку"
not_same_project: "не відносяться до одного проекту"
circular_dependency: "Такий зв'язок приведе до циклічної залежності"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: Оберіть
@ -908,3 +909,5 @@ uk:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -139,6 +139,7 @@ vi:
greater_than_start_date: "phải đi sau ngày bắt đầu"
not_same_project: "không thuộc cùng dự án"
circular_dependency: "quan hệ có thể gây ra lặp vô tận"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
direction: ltr
date:
@ -967,3 +968,5 @@ vi:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -177,6 +177,7 @@
greater_than_start_date: "必須在起始日期之後"
not_same_project: "不屬於同一個專案"
circular_dependency: "這個關聯會導致環狀相依"
cant_link_an_issue_with_a_descendant: "項目無法被連結至自己的子項目"
# You can define own errors for models or model attributes.
# The values :model, :attribute and :value are always available for interpolation.
@ -997,3 +998,5 @@
enumeration_doc_categories: 文件分類
enumeration_activities: 活動 (時間追蹤)
enumeration_system_activity: 系統活動
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -137,6 +137,7 @@ zh:
greater_than_start_date: "必须在起始日期之后"
not_same_project: "不属于同一个项目"
circular_dependency: "此关联将导致循环依赖"
cant_link_an_issue_with_a_descendant: "An issue can not be linked to one of its subtasks"
actionview_instancetag_blank_option: 请选择
@ -930,3 +931,5 @@ zh:
field_time_entries: Log time
project_module_gantt: Gantt
project_module_calendar: Calendar
field_member_of_group: Member of Group
field_assigned_to_role: Member of Role

View File

@ -172,55 +172,41 @@ ActionController::Routing::Routes.draw do |map|
user_actions.connect 'users/:id/memberships/:membership_id/destroy', :action => 'destroy_membership'
end
end
map.with_options :controller => 'projects' do |projects|
projects.with_options :conditions => {:method => :get} do |project_views|
project_views.connect 'projects', :action => 'index'
project_views.connect 'projects.:format', :action => 'index'
project_views.connect 'projects/new', :action => 'add'
project_views.connect 'projects/:id', :action => 'show'
project_views.connect 'projects/:id.:format', :action => 'show'
project_views.connect 'projects/:id/:action', :action => /destroy|settings/
project_views.connect 'projects/:id/files', :controller => 'files', :action => 'index'
project_views.connect 'projects/:id/files/new', :action => 'add_file'
project_views.connect 'projects/:id/settings/:tab', :action => 'settings'
# For nice "roadmap" in the url for the index action
map.connect 'projects/:project_id/roadmap', :controller => 'versions', :action => 'index'
map.resources :projects, :member => {
:copy => [:get, :post],
:settings => :get,
:modules => :post,
:archive => :post,
:unarchive => :post
} do |project|
project.resource :project_enumerations, :as => 'enumerations', :only => [:update, :destroy]
project.resources :files, :only => [:index, :new, :create]
project.resources :versions, :collection => {:close_completed => :put}, :member => {:status_by => :post}
end
# Destroy uses a get request to prompt the user before the actual DELETE request
map.project_destroy_confirm 'projects/:id/destroy', :controller => 'projects', :action => 'destroy', :conditions => {:method => :get}
# TODO: port to be part of the resources route(s)
map.with_options :controller => 'projects' do |project_mapper|
project_mapper.with_options :conditions => {:method => :get} do |project_views|
project_views.connect 'projects/:id/settings/:tab', :controller => 'projects', :action => 'settings'
project_views.connect 'projects/:project_id/issues/:copy_from/copy', :controller => 'issues', :action => 'new'
end
end
map.with_options :controller => 'activities', :action => 'index', :conditions => {:method => :get} do |activity|
activity.connect 'projects/:id/activity'
activity.connect 'projects/:id/activity.:format'
activity.connect 'activity', :id => nil
activity.connect 'activity.:format', :id => nil
end
projects.with_options :controller => 'activities', :action => 'index', :conditions => {:method => :get} do |activity|
activity.connect 'projects/:id/activity'
activity.connect 'projects/:id/activity.:format'
activity.connect 'activity', :id => nil
activity.connect 'activity.:format', :id => nil
end
projects.with_options :conditions => {:method => :post} do |project_actions|
project_actions.connect 'projects/new', :action => 'add'
project_actions.connect 'projects', :action => 'add'
project_actions.connect 'projects.:format', :action => 'add', :format => /xml/
project_actions.connect 'projects/:id/:action', :action => /edit|destroy|archive|unarchive/
project_actions.connect 'projects/:id/files/new', :action => 'add_file'
project_actions.connect 'projects/:id/activities/save', :action => 'save_activities'
end
projects.with_options :conditions => {:method => :put} do |project_actions|
project_actions.conditions 'projects/:id.:format', :action => 'edit', :format => /xml/
end
projects.with_options :conditions => {:method => :delete} do |project_actions|
project_actions.conditions 'projects/:id.:format', :action => 'destroy', :format => /xml/
project_actions.conditions 'projects/:id/reset_activities', :action => 'reset_activities'
end
end
map.with_options :controller => 'versions' do |versions|
versions.connect 'projects/:project_id/versions/new', :action => 'new'
versions.connect 'projects/:project_id/roadmap', :action => 'index'
versions.with_options :conditions => {:method => :post} do |version_actions|
version_actions.connect 'projects/:project_id/versions/close_completed', :action => 'close_completed'
end
end
map.with_options :controller => 'issue_categories' do |categories|
categories.connect 'projects/:project_id/issue_categories/new', :action => 'new'
end

View File

@ -46,12 +46,12 @@ end
Redmine::AccessControl.map do |map|
map.permission :view_project, {:projects => [:show], :activities => [:index]}, :public => true
map.permission :search_project, {:search => :index}, :public => true
map.permission :add_project, {:projects => :add}, :require => :loggedin
map.permission :edit_project, {:projects => [:settings, :edit]}, :require => :member
map.permission :add_project, {:projects => [:new, :create]}, :require => :loggedin
map.permission :edit_project, {:projects => [:settings, :edit, :update]}, :require => :member
map.permission :select_project_modules, {:projects => :modules}, :require => :member
map.permission :manage_members, {:projects => :settings, :members => [:new, :edit, :destroy, :autocomplete_for_member]}, :require => :member
map.permission :manage_versions, {:projects => :settings, :versions => [:new, :edit, :close_completed, :destroy]}, :require => :member
map.permission :add_subprojects, {:projects => :add}, :require => :member
map.permission :manage_versions, {:projects => :settings, :versions => [:new, :create, :edit, :update, :close_completed, :destroy]}, :require => :member
map.permission :add_subprojects, {:projects => [:new, :create]}, :require => :member
map.project_module :issue_tracking do |map|
# Issue categories
@ -87,7 +87,7 @@ Redmine::AccessControl.map do |map|
map.permission :view_time_entries, :timelog => [:details, :report]
map.permission :edit_time_entries, {:timelog => [:edit, :destroy]}, :require => :member
map.permission :edit_own_time_entries, {:timelog => [:edit, :destroy]}, :require => :loggedin
map.permission :manage_project_activities, {:projects => [:save_activities, :reset_activities]}, :require => :member
map.permission :manage_project_activities, {:project_enumerations => [:update, :destroy]}, :require => :member
end
map.project_module :news do |map|
@ -102,7 +102,7 @@ Redmine::AccessControl.map do |map|
end
map.project_module :files do |map|
map.permission :manage_files, {:projects => :add_file}, :require => :loggedin
map.permission :manage_files, {:files => [:new, :create]}, :require => :loggedin
map.permission :view_files, :files => :index, :versions => :download
end
@ -198,7 +198,7 @@ Redmine::MenuManager.map :project_menu do |menu|
:if => Proc.new { |p| p.wiki && !p.wiki.new_record? }
menu.push :boards, { :controller => 'boards', :action => 'index', :id => nil }, :param => :project_id,
:if => Proc.new { |p| p.boards.any? }, :caption => :label_board_plural
menu.push :files, { :controller => 'files', :action => 'index' }, :caption => :label_file_plural
menu.push :files, { :controller => 'files', :action => 'index' }, :caption => :label_file_plural, :param => :project_id
menu.push :repository, { :controller => 'repositories', :action => 'show' },
:if => Proc.new { |p| p.repository && !p.repository.new_record? }
menu.push :settings, { :controller => 'projects', :action => 'settings' }, :last => true

View File

@ -184,7 +184,7 @@ module Redmine
end
pdf.Output
end
# Returns a PDF string of a single issue
def issue_to_pdf(issue)
pdf = IFPDF.new(current_language)
@ -208,7 +208,7 @@ module Redmine
pdf.SetFontStyle('',9)
pdf.Cell(60,5, issue.priority.to_s,"RT")
pdf.Ln
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_author) + ":","L")
pdf.SetFontStyle('',9)
@ -238,14 +238,14 @@ module Redmine
pdf.SetFontStyle('',9)
pdf.Cell(60,5, format_date(issue.due_date),"RB")
pdf.Ln
for custom_value in issue.custom_field_values
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, custom_value.custom_field.name + ":","L")
pdf.SetFontStyle('',9)
pdf.MultiCell(155,5, (show_value custom_value),"R")
end
pdf.SetFontStyle('B',9)
pdf.Cell(35,5, l(:field_subject) + ":","LTB")
pdf.SetFontStyle('',9)
@ -311,187 +311,7 @@ module Redmine
end
pdf.Output
end
# Returns a PDF string of a gantt chart
def gantt_to_pdf(gantt, project)
pdf = IFPDF.new(current_language)
pdf.SetTitle("#{l(:label_gantt)} #{project}")
pdf.AliasNbPages
pdf.footer_date = format_date(Date.today)
pdf.AddPage("L")
pdf.SetFontStyle('B',12)
pdf.SetX(15)
pdf.Cell(70, 20, project.to_s)
pdf.Ln
pdf.SetFontStyle('B',9)
subject_width = 100
header_heigth = 5
headers_heigth = header_heigth
show_weeks = false
show_days = false
if gantt.months < 7
show_weeks = true
headers_heigth = 2*header_heigth
if gantt.months < 3
show_days = true
headers_heigth = 3*header_heigth
end
end
g_width = 280 - subject_width
zoom = (g_width) / (gantt.date_to - gantt.date_from + 1)
g_height = 120
t_height = g_height + headers_heigth
y_start = pdf.GetY
# Months headers
month_f = gantt.date_from
left = subject_width
height = header_heigth
gantt.months.times do
width = ((month_f >> 1) - month_f) * zoom
pdf.SetY(y_start)
pdf.SetX(left)
pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
left = left + width
month_f = month_f >> 1
end
# Weeks headers
if show_weeks
left = subject_width
height = header_heigth
if gantt.date_from.cwday == 1
# gantt.date_from is monday
week_f = gantt.date_from
else
# find next monday after gantt.date_from
week_f = gantt.date_from + (7 - gantt.date_from.cwday + 1)
width = (7 - gantt.date_from.cwday + 1) * zoom-1
pdf.SetY(y_start + header_heigth)
pdf.SetX(left)
pdf.Cell(width + 1, height, "", "LTR")
left = left + width+1
end
while week_f <= gantt.date_to
width = (week_f + 6 <= gantt.date_to) ? 7 * zoom : (gantt.date_to - week_f + 1) * zoom
pdf.SetY(y_start + header_heigth)
pdf.SetX(left)
pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
left = left + width
week_f = week_f+7
end
end
# Days headers
if show_days
left = subject_width
height = header_heigth
wday = gantt.date_from.cwday
pdf.SetFontStyle('B',7)
(gantt.date_to - gantt.date_from + 1).to_i.times do
width = zoom
pdf.SetY(y_start + 2 * header_heigth)
pdf.SetX(left)
pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C")
left = left + width
wday = wday + 1
wday = 1 if wday > 7
end
end
pdf.SetY(y_start)
pdf.SetX(15)
pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
# Tasks
top = headers_heigth + y_start
pdf.SetFontStyle('B',7)
gantt.events.each do |i|
pdf.SetY(top)
pdf.SetX(15)
text = ""
if i.is_a? Issue
text = "#{i.tracker} #{i.id}: #{i.subject}"
else
text = i.name
end
text = "#{i.project} - #{text}" unless project && project == i.project
pdf.Cell(subject_width-15, 5, text, "LR")
pdf.SetY(top + 0.2)
pdf.SetX(subject_width)
pdf.SetFillColor(255, 255, 255)
pdf.Cell(g_width, 4.6, "", "LR", 0, "", 1)
pdf.SetY(top+1.5)
if i.is_a? Issue
i_start_date = (i.start_date >= gantt.date_from ? i.start_date : gantt.date_from )
i_end_date = (i.due_before <= gantt.date_to ? i.due_before : gantt.date_to )
i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor
i_done_date = (i_done_date <= gantt.date_from ? gantt.date_from : i_done_date )
i_done_date = (i_done_date >= gantt.date_to ? gantt.date_to : i_done_date )
i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
i_left = ((i_start_date - gantt.date_from)*zoom)
i_width = ((i_end_date - i_start_date + 1)*zoom)
d_width = ((i_done_date - i_start_date)*zoom)
l_width = ((i_late_date - i_start_date+1)*zoom) if i_late_date
l_width ||= 0
pdf.SetX(subject_width + i_left)
pdf.SetFillColor(200,200,200)
pdf.Cell(i_width, 2, "", 0, 0, "", 1)
if l_width > 0
pdf.SetY(top+1.5)
pdf.SetX(subject_width + i_left)
pdf.SetFillColor(255,100,100)
pdf.Cell(l_width, 2, "", 0, 0, "", 1)
end
if d_width > 0
pdf.SetY(top+1.5)
pdf.SetX(subject_width + i_left)
pdf.SetFillColor(100,100,255)
pdf.Cell(d_width, 2, "", 0, 0, "", 1)
end
pdf.SetY(top+1.5)
pdf.SetX(subject_width + i_left + i_width)
pdf.Cell(30, 2, "#{i.status} #{i.done_ratio}%")
else
i_left = ((i.start_date - gantt.date_from)*zoom)
pdf.SetX(subject_width + i_left)
pdf.SetFillColor(50,200,50)
pdf.Cell(2, 2, "", 0, 0, "", 1)
pdf.SetY(top+1.5)
pdf.SetX(subject_width + i_left + 3)
pdf.Cell(30, 2, "#{i.name}")
end
top = top + 5
pdf.SetDrawColor(200, 200, 200)
pdf.Line(15, top, subject_width+g_width, top)
if pdf.GetY() > 180
pdf.AddPage("L")
top = 20
pdf.Line(15, top, subject_width+g_width, top)
end
pdf.SetDrawColor(0, 0, 0)
end
pdf.Line(15, top, subject_width+g_width, top)
pdf.Output
end
end
end
end

View File

@ -19,11 +19,28 @@ module Redmine
module Helpers
# Simple class to handle gantt chart data
class Gantt
attr_reader :year_from, :month_from, :date_from, :date_to, :zoom, :months, :events
include ERB::Util
include Redmine::I18n
# :nodoc:
# Some utility methods for the PDF export
class PDF
MaxCharactorsForSubject = 45
TotalWidth = 280
LeftPaneWidth = 100
def self.right_pane_width
TotalWidth - LeftPaneWidth
end
end
attr_reader :year_from, :month_from, :date_from, :date_to, :zoom, :months
attr_accessor :query
attr_accessor :project
attr_accessor :view
def initialize(options={})
options = options.dup
@events = []
if options[:year] && options[:year].to_i >0
@year_from = options[:year].to_i
@ -51,47 +68,668 @@ module Redmine
@date_from = Date.civil(@year_from, @month_from, 1)
@date_to = (@date_from >> @months) - 1
end
def events=(e)
@events = e
# Adds all ancestors
root_ids = e.select {|i| i.is_a?(Issue) && i.parent_id? }.collect(&:root_id).uniq
if root_ids.any?
# Retrieves all nodes
parents = Issue.find_all_by_root_id(root_ids, :conditions => ["rgt - lft > 1"])
# Only add ancestors
@events += parents.select {|p| @events.detect {|i| i.is_a?(Issue) && p.is_ancestor_of?(i)}}
end
@events.uniq!
# Sort issues by hierarchy and start dates
@events.sort! {|x,y|
if x.is_a?(Issue) && y.is_a?(Issue)
gantt_issue_compare(x, y, @events)
else
gantt_start_compare(x, y)
end
}
# Removes issues that have no start or end date
@events.reject! {|i| i.is_a?(Issue) && (i.start_date.nil? || i.due_before.nil?) }
@events
def common_params
{ :controller => 'gantts', :action => 'show', :project_id => @project }
end
def params
{ :zoom => zoom, :year => year_from, :month => month_from, :months => months }
common_params.merge({ :zoom => zoom, :year => year_from, :month => month_from, :months => months })
end
def params_previous
{ :year => (date_from << months).year, :month => (date_from << months).month, :zoom => zoom, :months => months }
common_params.merge({:year => (date_from << months).year, :month => (date_from << months).month, :zoom => zoom, :months => months })
end
def params_next
{ :year => (date_from >> months).year, :month => (date_from >> months).month, :zoom => zoom, :months => months }
common_params.merge({:year => (date_from >> months).year, :month => (date_from >> months).month, :zoom => zoom, :months => months })
end
### Extracted from the HTML view/helpers
# Returns the number of rows that will be rendered on the Gantt chart
def number_of_rows
if @project
return number_of_rows_on_project(@project)
else
Project.roots.inject(0) do |total, project|
total += number_of_rows_on_project(project)
end
end
end
# Returns the number of rows that will be used to list a project on
# the Gantt chart. This will recurse for each subproject.
def number_of_rows_on_project(project)
# Remove the project requirement for Versions because it will
# restrict issues to only be on the current project. This
# ends up missing issues which are assigned to shared versions.
@query.project = nil if @query.project
# One Root project
count = 1
# Issues without a Version
count += project.issues.for_gantt.without_version.with_query(@query).count
# Versions
count += project.versions.count
# Issues on the Versions
project.versions.each do |version|
count += version.fixed_issues.for_gantt.with_query(@query).count
end
# Subprojects
project.children.each do |subproject|
count += number_of_rows_on_project(subproject)
end
count
end
# Renders the subjects of the Gantt chart, the left side.
def subjects(options={})
options = {:indent => 4, :render => :subject, :format => :html}.merge(options)
output = ''
if @project
output << render_project(@project, options)
else
Project.roots.each do |project|
output << render_project(project, options)
end
end
output
end
# Renders the lines of the Gantt chart, the right side
def lines(options={})
options = {:indent => 4, :render => :line, :format => :html}.merge(options)
output = ''
if @project
output << render_project(@project, options)
else
Project.roots.each do |project|
output << render_project(project, options)
end
end
output
end
def render_project(project, options={})
options[:top] = 0 unless options.key? :top
options[:indent_increment] = 20 unless options.key? :indent_increment
options[:top_increment] = 20 unless options.key? :top_increment
output = ''
# Project Header
project_header = if options[:render] == :subject
subject_for_project(project, options)
else
# :line
line_for_project(project, options)
end
output << project_header if options[:format] == :html
options[:top] += options[:top_increment]
options[:indent] += options[:indent_increment]
# Second, Issues without a version
issues = project.issues.for_gantt.without_version.with_query(@query)
if issues
issue_rendering = render_issues(issues, options)
output << issue_rendering if options[:format] == :html
end
# Third, Versions
project.versions.sort.each do |version|
version_rendering = render_version(version, options)
output << version_rendering if options[:format] == :html
end
# Fourth, subprojects
project.children.each do |project|
subproject_rendering = render_project(project, options)
output << subproject_rendering if options[:format] == :html
end
# Remove indent to hit the next sibling
options[:indent] -= options[:indent_increment]
output
end
def render_issues(issues, options={})
output = ''
issues.each do |i|
issue_rendering = if options[:render] == :subject
subject_for_issue(i, options)
else
# :line
line_for_issue(i, options)
end
output << issue_rendering if options[:format] == :html
options[:top] += options[:top_increment]
end
output
end
def render_version(version, options={})
output = ''
# Version header
version_rendering = if options[:render] == :subject
subject_for_version(version, options)
else
# :line
line_for_version(version, options)
end
output << version_rendering if options[:format] == :html
options[:top] += options[:top_increment]
# Remove the project requirement for Versions because it will
# restrict issues to only be on the current project. This
# ends up missing issues which are assigned to shared versions.
@query.project = nil if @query.project
issues = version.fixed_issues.for_gantt.with_query(@query)
if issues
# Indent issues
options[:indent] += options[:indent_increment]
output << render_issues(issues, options)
options[:indent] -= options[:indent_increment]
end
output
end
def subject_for_project(project, options)
case options[:format]
when :html
output = ''
output << "<div class='project-name' style='position: absolute;line-height:1.2em;height:16px;top:#{options[:top]}px;left:#{options[:indent]}px;overflow:hidden;'><small> "
if project.is_a? Project
output << "<span class='icon icon-projects #{project.overdue? ? 'project-overdue' : ''}'>"
output << view.link_to_project(project)
output << '</span>'
else
ActiveRecord::Base.logger.debug "Gantt#subject_for_project was not given a project"
''
end
output << "</small></div>"
output
when :image
options[:image].fill('black')
options[:image].stroke('transparent')
options[:image].stroke_width(1)
options[:image].text(options[:indent], options[:top] + 2, project.name)
when :pdf
options[:pdf].SetY(options[:top])
options[:pdf].SetX(15)
char_limit = PDF::MaxCharactorsForSubject - options[:indent]
options[:pdf].Cell(options[:subject_width]-15, 5, (" " * options[:indent]) +"#{project.name}".sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
options[:pdf].SetY(options[:top])
options[:pdf].SetX(options[:subject_width])
options[:pdf].Cell(options[:g_width], 5, "", "LR")
end
end
def line_for_project(project, options)
# Skip versions that don't have a start_date
if project.is_a?(Project) && project.start_date
options[:zoom] ||= 1
options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
case options[:format]
when :html
output = ''
i_left = ((project.start_date - self.date_from)*options[:zoom]).floor
start_date = project.start_date
start_date ||= self.date_from
start_left = ((start_date - self.date_from)*options[:zoom]).floor
i_end_date = ((project.due_date <= self.date_to) ? project.due_date : self.date_to )
i_done_date = start_date + ((project.due_date - start_date+1)* project.completed_percent(:include_subprojects => true)/100).floor
i_done_date = (i_done_date <= self.date_from ? self.date_from : i_done_date )
i_done_date = (i_done_date >= self.date_to ? self.date_to : i_done_date )
i_late_date = [i_end_date, Date.today].min if start_date < Date.today
i_end = ((i_end_date - self.date_from) * options[:zoom]).floor
i_width = (i_end - i_left + 1).floor - 2 # total width of the issue (- 2 for left and right borders)
d_width = ((i_done_date - start_date)*options[:zoom]).floor - 2 # done width
l_width = i_late_date ? ((i_late_date - start_date+1)*options[:zoom]).floor - 2 : 0 # delay width
# Bar graphic
# Make sure that negative i_left and i_width don't
# overflow the subject
if i_end > 0 && i_left <= options[:g_width]
output << "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:#{ i_width }px;' class='task project_todo'>&nbsp;</div>"
end
if l_width > 0 && i_left <= options[:g_width]
output << "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:#{ l_width }px;' class='task project_late'>&nbsp;</div>"
end
if d_width > 0 && i_left <= options[:g_width]
output<< "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:#{ d_width }px;' class='task project_done'>&nbsp;</div>"
end
# Starting diamond
if start_left <= options[:g_width] && start_left > 0
output << "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:15px;' class='task project-line starting'>&nbsp;</div>"
output << "<div style='top:#{ options[:top] }px;left:#{ start_left + 12 }px;' class='task label'>"
output << "</div>"
end
# Ending diamond
# Don't show items too far ahead
if i_end <= options[:g_width] && i_end > 0
output << "<div style='top:#{ options[:top] }px;left:#{ i_end }px;width:15px;' class='task project-line ending'>&nbsp;</div>"
end
# DIsplay the Project name and %
if i_end <= options[:g_width]
# Display the status even if it's floated off to the left
status_px = i_end + 12 # 12px for the diamond
status_px = 0 if status_px <= 0
output << "<div style='top:#{ options[:top] }px;left:#{ status_px }px;' class='task label project-name'>"
output << "<strong>#{h project } #{h project.completed_percent(:include_subprojects => true).to_i.to_s}%</strong>"
output << "</div>"
end
output
when :image
options[:image].stroke('transparent')
i_left = options[:subject_width] + ((project.due_date - self.date_from)*options[:zoom]).floor
# Make sure negative i_left doesn't overflow the subject
if i_left > options[:subject_width]
options[:image].fill('blue')
options[:image].rectangle(i_left, options[:top], i_left + 6, options[:top] - 6)
options[:image].fill('black')
options[:image].text(i_left + 11, options[:top] + 1, project.name)
end
when :pdf
options[:pdf].SetY(options[:top]+1.5)
i_left = ((project.due_date - @date_from)*options[:zoom])
# Make sure negative i_left doesn't overflow the subject
if i_left > 0
options[:pdf].SetX(options[:subject_width] + i_left)
options[:pdf].SetFillColor(50,50,200)
options[:pdf].Cell(2, 2, "", 0, 0, "", 1)
options[:pdf].SetY(options[:top]+1.5)
options[:pdf].SetX(options[:subject_width] + i_left + 3)
options[:pdf].Cell(30, 2, "#{project.name}")
end
end
else
ActiveRecord::Base.logger.debug "Gantt#line_for_project was not given a project with a start_date"
''
end
end
def subject_for_version(version, options)
case options[:format]
when :html
output = ''
output << "<div class='version-name' style='position: absolute;line-height:1.2em;height:16px;top:#{options[:top]}px;left:#{options[:indent]}px;overflow:hidden;'><small> "
if version.is_a? Version
output << "<span class='icon icon-package #{version.behind_schedule? ? 'version-behind-schedule' : ''} #{version.overdue? ? 'version-overdue' : ''}'>"
output << view.link_to_version(version)
output << '</span>'
else
ActiveRecord::Base.logger.debug "Gantt#subject_for_version was not given a version"
''
end
output << "</small></div>"
output
when :image
options[:image].fill('black')
options[:image].stroke('transparent')
options[:image].stroke_width(1)
options[:image].text(options[:indent], options[:top] + 2, version.to_s_with_project)
when :pdf
options[:pdf].SetY(options[:top])
options[:pdf].SetX(15)
char_limit = PDF::MaxCharactorsForSubject - options[:indent]
options[:pdf].Cell(options[:subject_width]-15, 5, (" " * options[:indent]) +"#{version.to_s_with_project}".sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
options[:pdf].SetY(options[:top])
options[:pdf].SetX(options[:subject_width])
options[:pdf].Cell(options[:g_width], 5, "", "LR")
end
end
def line_for_version(version, options)
# Skip versions that don't have a start_date
if version.is_a?(Version) && version.start_date
options[:zoom] ||= 1
options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
case options[:format]
when :html
output = ''
i_left = ((version.start_date - self.date_from)*options[:zoom]).floor
# TODO: or version.fixed_issues.collect(&:start_date).min
start_date = version.fixed_issues.minimum('start_date') if version.fixed_issues.present?
start_date ||= self.date_from
start_left = ((start_date - self.date_from)*options[:zoom]).floor
i_end_date = ((version.due_date <= self.date_to) ? version.due_date : self.date_to )
i_done_date = start_date + ((version.due_date - start_date+1)* version.completed_pourcent/100).floor
i_done_date = (i_done_date <= self.date_from ? self.date_from : i_done_date )
i_done_date = (i_done_date >= self.date_to ? self.date_to : i_done_date )
i_late_date = [i_end_date, Date.today].min if start_date < Date.today
i_width = (i_left - start_left + 1).floor - 2 # total width of the issue (- 2 for left and right borders)
d_width = ((i_done_date - start_date)*options[:zoom]).floor - 2 # done width
l_width = i_late_date ? ((i_late_date - start_date+1)*options[:zoom]).floor - 2 : 0 # delay width
i_end = ((i_end_date - self.date_from) * options[:zoom]).floor # Ending pixel
# Bar graphic
# Make sure that negative i_left and i_width don't
# overflow the subject
if i_width > 0 && i_left <= options[:g_width]
output << "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:#{ i_width }px;' class='task milestone_todo'>&nbsp;</div>"
end
if l_width > 0 && i_left <= options[:g_width]
output << "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:#{ l_width }px;' class='task milestone_late'>&nbsp;</div>"
end
if d_width > 0 && i_left <= options[:g_width]
output<< "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:#{ d_width }px;' class='task milestone_done'>&nbsp;</div>"
end
# Starting diamond
if start_left <= options[:g_width] && start_left > 0
output << "<div style='top:#{ options[:top] }px;left:#{ start_left }px;width:15px;' class='task milestone starting'>&nbsp;</div>"
output << "<div style='top:#{ options[:top] }px;left:#{ start_left + 12 }px;background:#fff;' class='task'>"
output << "</div>"
end
# Ending diamond
# Don't show items too far ahead
if i_left <= options[:g_width] && i_end > 0
output << "<div style='top:#{ options[:top] }px;left:#{ i_end }px;width:15px;' class='task milestone ending'>&nbsp;</div>"
end
# Display the Version name and %
if i_end <= options[:g_width]
# Display the status even if it's floated off to the left
status_px = i_end + 12 # 12px for the diamond
status_px = 0 if status_px <= 0
output << "<div style='top:#{ options[:top] }px;left:#{ status_px }px;' class='task label version-name'>"
output << h("#{version.project} -") unless @project && @project == version.project
output << "<strong>#{h version } #{h version.completed_pourcent.to_i.to_s}%</strong>"
output << "</div>"
end
output
when :image
options[:image].stroke('transparent')
i_left = options[:subject_width] + ((version.start_date - @date_from)*options[:zoom]).floor
# Make sure negative i_left doesn't overflow the subject
if i_left > options[:subject_width]
options[:image].fill('green')
options[:image].rectangle(i_left, options[:top], i_left + 6, options[:top] - 6)
options[:image].fill('black')
options[:image].text(i_left + 11, options[:top] + 1, version.name)
end
when :pdf
options[:pdf].SetY(options[:top]+1.5)
i_left = ((version.start_date - @date_from)*options[:zoom])
# Make sure negative i_left doesn't overflow the subject
if i_left > 0
options[:pdf].SetX(options[:subject_width] + i_left)
options[:pdf].SetFillColor(50,200,50)
options[:pdf].Cell(2, 2, "", 0, 0, "", 1)
options[:pdf].SetY(options[:top]+1.5)
options[:pdf].SetX(options[:subject_width] + i_left + 3)
options[:pdf].Cell(30, 2, "#{version.name}")
end
end
else
ActiveRecord::Base.logger.debug "Gantt#line_for_version was not given a version with a start_date"
''
end
end
def subject_for_issue(issue, options)
case options[:format]
when :html
output = ''
output << "<div class='tooltip'>"
output << "<div class='issue-subject' style='position: absolute;line-height:1.2em;height:16px;top:#{options[:top]}px;left:#{options[:indent]}px;overflow:hidden;'><small> "
if issue.is_a? Issue
css_classes = []
css_classes << 'issue-overdue' if issue.overdue?
css_classes << 'issue-behind-schedule' if issue.behind_schedule?
css_classes << 'icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
if issue.assigned_to.present?
assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
output << view.avatar(issue.assigned_to, :class => 'gravatar icon-gravatar', :size => 10, :title => assigned_string)
end
output << "<span class='#{css_classes.join(' ')}'>"
output << view.link_to_issue(issue)
output << ":"
output << h(issue.subject)
output << '</span>'
else
ActiveRecord::Base.logger.debug "Gantt#subject_for_issue was not given an issue"
''
end
output << "</small></div>"
# Tooltip
if issue.is_a? Issue
output << "<span class='tip' style='position: absolute;top:#{ options[:top].to_i + 16 }px;left:#{ options[:indent].to_i + 20 }px;'>"
output << view.render_issue_tooltip(issue)
output << "</span>"
end
output << "</div>"
output
when :image
options[:image].fill('black')
options[:image].stroke('transparent')
options[:image].stroke_width(1)
options[:image].text(options[:indent], options[:top] + 2, issue.subject)
when :pdf
options[:pdf].SetY(options[:top])
options[:pdf].SetX(15)
char_limit = PDF::MaxCharactorsForSubject - options[:indent]
options[:pdf].Cell(options[:subject_width]-15, 5, (" " * options[:indent]) +"#{issue.tracker} #{issue.id}: #{issue.subject}".sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
options[:pdf].SetY(options[:top])
options[:pdf].SetX(options[:subject_width])
options[:pdf].Cell(options[:g_width], 5, "", "LR")
end
end
def line_for_issue(issue, options)
# Skip issues that don't have a due_before (due_date or version's due_date)
if issue.is_a?(Issue) && issue.due_before
case options[:format]
when :html
output = ''
# Handle nil start_dates, rare but can happen.
i_start_date = if issue.start_date && issue.start_date >= self.date_from
issue.start_date
else
self.date_from
end
i_end_date = ((issue.due_before && issue.due_before <= self.date_to) ? issue.due_before : self.date_to )
i_done_date = i_start_date + ((issue.due_before - i_start_date+1)*issue.done_ratio/100).floor
i_done_date = (i_done_date <= self.date_from ? self.date_from : i_done_date )
i_done_date = (i_done_date >= self.date_to ? self.date_to : i_done_date )
i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
i_left = ((i_start_date - self.date_from)*options[:zoom]).floor
i_width = ((i_end_date - i_start_date + 1)*options[:zoom]).floor - 2 # total width of the issue (- 2 for left and right borders)
d_width = ((i_done_date - i_start_date)*options[:zoom]).floor - 2 # done width
l_width = i_late_date ? ((i_late_date - i_start_date+1)*options[:zoom]).floor - 2 : 0 # delay width
css = "task " + (issue.leaf? ? 'leaf' : 'parent')
# Make sure that negative i_left and i_width don't
# overflow the subject
if i_width > 0
output << "<div style='top:#{ options[:top] }px;left:#{ i_left }px;width:#{ i_width }px;' class='#{css} task_todo'>&nbsp;</div>"
end
if l_width > 0
output << "<div style='top:#{ options[:top] }px;left:#{ i_left }px;width:#{ l_width }px;' class='#{css} task_late'>&nbsp;</div>"
end
if d_width > 0
output<< "<div style='top:#{ options[:top] }px;left:#{ i_left }px;width:#{ d_width }px;' class='#{css} task_done'>&nbsp;</div>"
end
# Display the status even if it's floated off to the left
status_px = i_left + i_width + 5
status_px = 5 if status_px <= 0
output << "<div style='top:#{ options[:top] }px;left:#{ status_px }px;' class='#{css} label issue-name'>"
output << issue.status.name
output << ' '
output << (issue.done_ratio).to_i.to_s
output << "%"
output << "</div>"
output << "<div class='tooltip' style='position: absolute;top:#{ options[:top] }px;left:#{ i_left }px;width:#{ i_width }px;height:12px;'>"
output << '<span class="tip">'
output << view.render_issue_tooltip(issue)
output << "</span></div>"
output
when :image
# Handle nil start_dates, rare but can happen.
i_start_date = if issue.start_date && issue.start_date >= @date_from
issue.start_date
else
@date_from
end
i_end_date = (issue.due_before <= date_to ? issue.due_before : date_to )
i_done_date = i_start_date + ((issue.due_before - i_start_date+1)*issue.done_ratio/100).floor
i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date )
i_done_date = (i_done_date >= date_to ? date_to : i_done_date )
i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
i_left = options[:subject_width] + ((i_start_date - @date_from)*options[:zoom]).floor
i_width = ((i_end_date - i_start_date + 1)*options[:zoom]).floor # total width of the issue
d_width = ((i_done_date - i_start_date)*options[:zoom]).floor # done width
l_width = i_late_date ? ((i_late_date - i_start_date+1)*options[:zoom]).floor : 0 # delay width
# Make sure that negative i_left and i_width don't
# overflow the subject
if i_width > 0
options[:image].fill('grey')
options[:image].rectangle(i_left, options[:top], i_left + i_width, options[:top] - 6)
options[:image].fill('red')
options[:image].rectangle(i_left, options[:top], i_left + l_width, options[:top] - 6) if l_width > 0
options[:image].fill('blue')
options[:image].rectangle(i_left, options[:top], i_left + d_width, options[:top] - 6) if d_width > 0
end
# Show the status and % done next to the subject if it overflows
options[:image].fill('black')
if i_width > 0
options[:image].text(i_left + i_width + 5,options[:top] + 1, "#{issue.status.name} #{issue.done_ratio}%")
else
options[:image].text(options[:subject_width] + 5,options[:top] + 1, "#{issue.status.name} #{issue.done_ratio}%")
end
when :pdf
options[:pdf].SetY(options[:top]+1.5)
# Handle nil start_dates, rare but can happen.
i_start_date = if issue.start_date && issue.start_date >= @date_from
issue.start_date
else
@date_from
end
i_end_date = (issue.due_before <= @date_to ? issue.due_before : @date_to )
i_done_date = i_start_date + ((issue.due_before - i_start_date+1)*issue.done_ratio/100).floor
i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date )
i_done_date = (i_done_date >= @date_to ? @date_to : i_done_date )
i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
i_left = ((i_start_date - @date_from)*options[:zoom])
i_width = ((i_end_date - i_start_date + 1)*options[:zoom])
d_width = ((i_done_date - i_start_date)*options[:zoom])
l_width = ((i_late_date - i_start_date+1)*options[:zoom]) if i_late_date
l_width ||= 0
# Make sure that negative i_left and i_width don't
# overflow the subject
if i_width > 0
options[:pdf].SetX(options[:subject_width] + i_left)
options[:pdf].SetFillColor(200,200,200)
options[:pdf].Cell(i_width, 2, "", 0, 0, "", 1)
end
if l_width > 0
options[:pdf].SetY(options[:top]+1.5)
options[:pdf].SetX(options[:subject_width] + i_left)
options[:pdf].SetFillColor(255,100,100)
options[:pdf].Cell(l_width, 2, "", 0, 0, "", 1)
end
if d_width > 0
options[:pdf].SetY(options[:top]+1.5)
options[:pdf].SetX(options[:subject_width] + i_left)
options[:pdf].SetFillColor(100,100,255)
options[:pdf].Cell(d_width, 2, "", 0, 0, "", 1)
end
options[:pdf].SetY(options[:top]+1.5)
# Make sure that negative i_left and i_width don't
# overflow the subject
if (i_left + i_width) >= 0
options[:pdf].SetX(options[:subject_width] + i_left + i_width)
else
options[:pdf].SetX(options[:subject_width])
end
options[:pdf].Cell(30, 2, "#{issue.status} #{issue.done_ratio}%")
end
else
ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
''
end
end
# Generates a gantt image
# Only defined if RMagick is avalaible
def to_image(project, format='PNG')
def to_image(format='PNG')
date_to = (@date_from >> @months)-1
show_weeks = @zoom > 1
show_days = @zoom > 2
@ -101,7 +739,7 @@ module Redmine
# width of one day in pixels
zoom = @zoom*2
g_width = (@date_to - @date_from + 1)*zoom
g_height = 20 * events.length + 20
g_height = 20 * number_of_rows + 30
headers_heigth = (show_weeks ? 2*header_heigth : header_heigth)
height = g_height + headers_heigth
@ -110,21 +748,7 @@ module Redmine
gc = Magick::Draw.new
# Subjects
top = headers_heigth + 20
gc.fill('black')
gc.stroke('transparent')
gc.stroke_width(1)
events.each do |i|
text = ""
if i.is_a? Issue
text = "#{i.tracker} #{i.id}: #{i.subject}"
else
text = i.name
end
text = "#{i.project} - #{text}" unless project && project == i.project
gc.text(4, top + 2, text)
top = top + 20
end
subjects(:image => gc, :top => (headers_heigth + 20), :indent => 4, :format => :image)
# Months headers
month_f = @date_from
@ -202,38 +826,8 @@ module Redmine
# content
top = headers_heigth + 20
gc.stroke('transparent')
events.each do |i|
if i.is_a?(Issue)
i_start_date = (i.start_date >= @date_from ? i.start_date : @date_from )
i_end_date = (i.due_before <= date_to ? i.due_before : date_to )
i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor
i_done_date = (i_done_date <= @date_from ? @date_from : i_done_date )
i_done_date = (i_done_date >= date_to ? date_to : i_done_date )
i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
i_left = subject_width + ((i_start_date - @date_from)*zoom).floor
i_width = ((i_end_date - i_start_date + 1)*zoom).floor # total width of the issue
d_width = ((i_done_date - i_start_date)*zoom).floor # done width
l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor : 0 # delay width
gc.fill('grey')
gc.rectangle(i_left, top, i_left + i_width, top - 6)
gc.fill('red')
gc.rectangle(i_left, top, i_left + l_width, top - 6) if l_width > 0
gc.fill('blue')
gc.rectangle(i_left, top, i_left + d_width, top - 6) if d_width > 0
gc.fill('black')
gc.text(i_left + i_width + 5,top + 1, "#{i.status.name} #{i.done_ratio}%")
else
i_left = subject_width + ((i.start_date - @date_from)*zoom).floor
gc.fill('green')
gc.rectangle(i_left, top, i_left + 6, top - 6)
gc.fill('black')
gc.text(i_left + 11, top + 1, i.name)
end
top = top + 20
end
lines(:image => gc, :top => top, :zoom => zoom, :subject_width => subject_width, :format => :image)
# today red line
if Date.today >= @date_from and Date.today <= date_to
@ -246,36 +840,137 @@ module Redmine
imgl.format = format
imgl.to_blob
end if Object.const_defined?(:Magick)
def to_pdf
pdf = ::Redmine::Export::PDF::IFPDF.new(current_language)
pdf.SetTitle("#{l(:label_gantt)} #{project}")
pdf.AliasNbPages
pdf.footer_date = format_date(Date.today)
pdf.AddPage("L")
pdf.SetFontStyle('B',12)
pdf.SetX(15)
pdf.Cell(PDF::LeftPaneWidth, 20, project.to_s)
pdf.Ln
pdf.SetFontStyle('B',9)
subject_width = PDF::LeftPaneWidth
header_heigth = 5
headers_heigth = header_heigth
show_weeks = false
show_days = false
if self.months < 7
show_weeks = true
headers_heigth = 2*header_heigth
if self.months < 3
show_days = true
headers_heigth = 3*header_heigth
end
end
g_width = PDF.right_pane_width
zoom = (g_width) / (self.date_to - self.date_from + 1)
g_height = 120
t_height = g_height + headers_heigth
y_start = pdf.GetY
# Months headers
month_f = self.date_from
left = subject_width
height = header_heigth
self.months.times do
width = ((month_f >> 1) - month_f) * zoom
pdf.SetY(y_start)
pdf.SetX(left)
pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
left = left + width
month_f = month_f >> 1
end
# Weeks headers
if show_weeks
left = subject_width
height = header_heigth
if self.date_from.cwday == 1
# self.date_from is monday
week_f = self.date_from
else
# find next monday after self.date_from
week_f = self.date_from + (7 - self.date_from.cwday + 1)
width = (7 - self.date_from.cwday + 1) * zoom-1
pdf.SetY(y_start + header_heigth)
pdf.SetX(left)
pdf.Cell(width + 1, height, "", "LTR")
left = left + width+1
end
while week_f <= self.date_to
width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom
pdf.SetY(y_start + header_heigth)
pdf.SetX(left)
pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
left = left + width
week_f = week_f+7
end
end
# Days headers
if show_days
left = subject_width
height = header_heigth
wday = self.date_from.cwday
pdf.SetFontStyle('B',7)
(self.date_to - self.date_from + 1).to_i.times do
width = zoom
pdf.SetY(y_start + 2 * header_heigth)
pdf.SetX(left)
pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C")
left = left + width
wday = wday + 1
wday = 1 if wday > 7
end
end
pdf.SetY(y_start)
pdf.SetX(15)
pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
# Tasks
top = headers_heigth + y_start
pdf_subjects_and_lines(pdf, {
:top => top,
:zoom => zoom,
:subject_width => subject_width,
:g_width => g_width
})
pdf.Line(15, top, subject_width+g_width, top)
pdf.Output
end
private
def gantt_issue_compare(x, y, issues)
if x.parent_id == y.parent_id
gantt_start_compare(x, y)
elsif x.is_ancestor_of?(y)
-1
elsif y.is_ancestor_of?(x)
1
# Renders both the subjects and lines of the Gantt chart for the
# PDF format
def pdf_subjects_and_lines(pdf, options = {})
subject_options = {:indent => 0, :indent_increment => 5, :top_increment => 3, :render => :subject, :format => :pdf, :pdf => pdf}.merge(options)
line_options = {:indent => 0, :indent_increment => 5, :top_increment => 3, :render => :line, :format => :pdf, :pdf => pdf}.merge(options)
if @project
render_project(@project, subject_options)
render_project(@project, line_options)
else
ax = issues.select {|i| i.is_a?(Issue) && i.is_ancestor_of?(x) && !i.is_ancestor_of?(y) }.sort_by(&:lft).first
ay = issues.select {|i| i.is_a?(Issue) && i.is_ancestor_of?(y) && !i.is_ancestor_of?(x) }.sort_by(&:lft).first
if ax.nil? && ay.nil?
gantt_start_compare(x, y)
else
gantt_issue_compare(ax || x, ay || y, issues)
Project.roots.each do |project|
render_project(project, subject_options)
render_project(project, line_options)
end
end
end
def gantt_start_compare(x, y)
if x.start_date.nil?
-1
elsif y.start_date.nil?
1
else
x.start_date <=> y.start_date
end
end
end
end
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 855 B

After

Width:  |  Height:  |  Size: 137 B

Some files were not shown because too many files have changed in this diff Show More