subissues.diff

Patch for trunk version with some features and fixes. - Aleksei Gusev, 2008-11-25 14:44

Download (93.9 KB)

View differences:

app/controllers/issues_controller.rb
18 18
class IssuesController < ApplicationController
19 19
  menu_item :new_issue, :only => :new
20 20
  
21
  before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment]
21
  before_filter :find_issue_and_project_id, :only => [ :show_children, :hide_children ]
22
  before_filter :find_issue, :only => [:show, :edit, :reply, :destroy_attachment, :update_subject ]
22 23
  before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
23
  before_filter :find_project, :only => [:new, :update_form, :preview, :gantt, :calendar]
24
  before_filter :authorize, :except => [:index, :changes, :preview, :update_form, :context_menu]
24
  before_filter :find_project, :only => [:new, :auto_complete_for_issue_parent, :add_subissue, :update_form, :preview, :gantt, :calendar]
25
  before_filter :authorize, :except => [:index, :changes, :preview, :update_form, :context_menu, :show_children, :hide_children, :auto_complete_for_issue_parent ]
25 26
  before_filter :find_optional_project, :only => [:index, :changes]
26 27
  accept_key_auth :index, :changes
27 28

  
......
43 44
  include SortHelper
44 45
  include IssuesHelper
45 46
  helper :timelog
47
  include ActionView::Helpers::PrototypeHelper
48

  
49
  def update_subject
50
    @notes = params[:notes]
51
    journal = @issue.init_journal(User.current, @notes)
52
    @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
53
    # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
54
    if (@edit_allowed || !@allowed_statuses.empty?) && params[:subject]
55
      @issue.subject = params[:subject]
56
    end    
57
    if @issue.save
58
      if !journal.new_record?
59
        # Only send notification if something was actually changed
60
        flash[:notice] = l(:notice_successful_update)
61
        Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
62
      end
63
      render(:text => params[:subject])
64
    end    
65
  rescue ActiveRecord::StaleObjectError
66
    # Optimistic locking exception
67
    flash.now[:error] = l(:notice_locking_conflict)       
68
  end
69
  
70
  def show_children
71
    retrieve_query
72
    if @query.valid?
73
      render :update do |page|
74
        page.replace "issue-#{@issue.id}", :partial => 'show_children', :locals => {:issue => @issue, :query => @query}
75
      end
76
    end    
77
  end
78
  
79
  def hide_children
80
    retrieve_query #verificar necessidade
81
    if @query.valid?
82
      render :update do |page|
83
        page.select(".issue-#{@issue.id}-child").each do |child|
84
          child.remove
85
        end
86
        page.select("#issue-#{@issue.id} a.contract-link").each do |contractIcon|
87
          contractIcon.replace link_to_remote(image_tag("expand.png", :class=>'expand-icon'), {:url => {:controller => 'issues', :action => "show_children", :id => @issue, :project_id => @project}}, :class=>'expand-link')
88
        end
89
      end
90
    end    
91
  end
92

  
93
  def auto_complete_for_issue_parent
94
    @phrase = params[:issue_parent]
95
    @candidates = []
96

  
97
    # If cross project issue relations is allowed we should get
98
    # candidates from every project
99
    if Setting.cross_project_issue_relations?
100
      projects_to_search = nil
101
    else
102
      projects_to_search = [ @project ] + @project.active_children
103
    end
104

  
105
    # Try to find issue by id.
106
    if @phrase.match(/^#?(\d+)$/)
107
      if Setting.cross_project_issue_relations?
108
        issue = Issue.find_by_id( $1)
109
      else
110
        issue = Issue.find_by_id_and_project_id( $1, projects_to_search.collect { |i| i.id})
111
      end
112
      @candidates = [ issue ] if issue
113
    end
114

  
115
    # If finding by id is fail, try to find by searching in subject
116
    # and description.
117
    if @candidates.empty?
118
      # extract tokens from the question
119
      # eg. hello "bye bye" => ["hello", "bye bye"]
120
      tokens = @phrase.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')}
121
      # tokens must be at least 3 character long
122
      tokens = tokens.uniq.select {|w| w.length > 2 }
123
      like_tokens = tokens.collect {|w| "%#{w.downcase}%"}      
124

  
125
      @candidates, count = Issue.search( like_tokens, projects_to_search)
126
    end
127

  
128
    render :inline => "<%= auto_complete_result_parent_issue( @candidates, @phrase) %>"
129
  end
46 130

  
47 131
  def index
48 132
    sort_init "#{Issue.table_name}.id", "desc"
......
58 142
      end
59 143
      @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
60 144
      @issue_pages = Paginator.new self, @issue_count, limit, params['page']
61
      @issues = Issue.find :all, :order => sort_clause,
62
                           :include => [ :assigned_to, :status, :tracker, :project, :priority, :category, :fixed_version ],
63
                           :conditions => @query.statement,
64
                           :limit  =>  limit,
65
                           :offset =>  @issue_pages.current.offset
145
      @issues = Issue.find_with_parents( :all, :order => sort_clause,
146
                                         :include => [ :assigned_to,
147
                                                       :status,
148
                                                       :tracker,
149
                                                       :project,
150
                                                       :priority,
151
                                                       :category,
152
                                                       :fixed_version ],
153
                                         :conditions => @query.statement,
154
                                         :limit  => limit,
155
                                         :offset => @issue_pages.current.offset)
156
      
66 157
      respond_to do |format|
67 158
        format.html { render :template => 'issues/index.rhtml', :layout => !request.xhr? }
68 159
        format.atom { render_feed(@issues, :title => "#{@project || Setting.app_title}: #{l(:label_issue_plural)}") }
......
132 223
    end    
133 224
    @issue.status = default_status
134 225
    @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)).uniq
135
    
226

  
227
    @parent_issue = Issue.find( params[:issue_parent_issue_id]) unless params[:issue_parent_issue_id].blank?
228

  
136 229
    if request.get? || request.xhr?
137 230
      @issue.start_date ||= Date.today
138 231
    else
......
140 233
      # Check that the user is allowed to apply the requested status
141 234
      @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
142 235
      if @issue.save
236
        set_parent( @issue, params[:issue_parent_issue_id]) if params[:issue_parent_issue_id]
143 237
        attach_files(@issue, params[:attachments])
144 238
        flash[:notice] = l(:notice_successful_create)
145 239
        Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
......
150 244
    @priorities = Enumeration::get_values('IPRI')
151 245
    render :layout => !request.xhr?
152 246
  end
247

  
248
  def add_subissue
249
    if params[:issue_parent_issue_id].nil?
250
      flash.now[:error] = 'No parent issue specified.'
251
      render :nothing => true, :layout => true
252
      return
253
    else
254
      redirect_to( :controller => 'issues', :action => 'new',
255
                   :issue_parent_issue_id => params[:issue_parent_issue_id])
256
      return
257
    end
258
  end
153 259
  
154 260
  # Attributes that can be updated on workflow transition (without :edit permission)
155 261
  # TODO: make it configurable (at least per role)
......
177 283
      attachments = attach_files(@issue, params[:attachments])
178 284
      attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
179 285
      if (@time_entry.hours.nil? || @time_entry.valid?) && @issue.save
286
        set_parent(@issue, params[:issue_parent_issue_id]) if params[:issue_parent_issue_id]
180 287
        # Log spend time
181 288
        if current_role.allowed_to?(:log_time)
182 289
          @time_entry.save
......
236 343
        call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue })
237 344
        # Don't save any change to the issue if the user is not authorized to apply the requested status
238 345
        if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
346
          set_parent(issue, params[:issue_parent_issue_id]) unless params[:issue_parent_issue_id].blank?
239 347
          # Send notification for each issue (if changed)
240 348
          Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
241 349
        else
......
410 518

  
411 519
  def update_form
412 520
    @issue = Issue.new(params[:issue])
521
    @parent_issue = @issue.parent
413 522
    render :action => :new, :layout => false
414 523
  end
415 524
  
......
423 532
private
424 533
  def find_issue
425 534
    @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
535
    @parent_issue = @issue.parent
426 536
    @project = @issue.project
427 537
  rescue ActiveRecord::RecordNotFound
428 538
    render_404
429 539
  end
430 540
  
541
  def find_issue_and_project_id
542
    @issue = Issue.find( params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
543
    @parent_issue = @issue.parent
544
    @project = Project.find( params[:project_id])
545
  rescue ActiveRecord::RecordNotFound
546
    render_404
547
  end
548

  
431 549
  # Filter for bulk operations
432 550
  def find_issues
433 551
    @issues = Issue.find_all_by_id(params[:id] || params[:ids])
app/controllers/projects_controller.rb
42 42
  helper :repositories
43 43
  include RepositoriesHelper
44 44
  include ProjectsHelper
45
  helper :versions
46
  include VersionsHelper
45 47
  
46 48
  # Lists visible projects
47 49
  def index
......
271 273
      @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
272 274
    end
273 275
  end
276
  
277
  
278
  def sort_as_tree(issues)
279
    issues.sort!{|a,b| a.hierarchical_level <=> b.hierarchical_level}
280
    @sorted_issues = []
281
    issues.each do |issue|
282
      if @sorted_issues.empty?
283
        @sorted_issues << issue
284
        next
285
      end
286
      @time_to_stop = false #indicates when this task reaches its parent task (important because it has to stop between its parent task and the next aunt task
287
      @sorted_issues.each do |sorted_issue|
288
        #if same parent and smaller date, stop; if same parent, same date and smaller id, stop; after parent and before next parent, stop; 
289
        if ((sorted_issue.parent == issue.parent) && (sorted_issue.start_date > issue.start_date)) ||
290
         ((sorted_issue.parent == issue.parent) && (sorted_issue.start_date == issue.start_date) && (sorted_issue.id > issue.id)) ||
291
         (@time_to_stop && (sorted_issue.hierarchical_level < issue.hierarchical_level))
292
          @sorted_issues.insert(@sorted_issues.index(sorted_issue), issue)
293
          break
294
        end
295
        @time_to_stop = true if sorted_issue == issue.parent      
296
      end
297
      #if this issue's parent is the last element
298
      @sorted_issues << issue if @time_to_stop
299
    end
300
    @sorted_issues
301
  end
302
  
303
  #assumes that first level issues are ordered by date (sort_as_tree)
304
  def integrate_versions_with_issues_tree(issues, versions)
305
    versions.sort! {|x,y| x.start_date <=> y.start_date }    
306
    versions.each do |version|
307
      issues << version if issues.empty?
308
      issues.each do |issue|
309
        if ((issue.is_a? Issue && issue.root?) || (issue.is_a? Version)) && version.start_date < issue.start_date
310
          #insert version before a root task or another version whose date is immediately after this task's one 
311
          issues.insert(issues.index(issue), version)
312
        elsif issue == issues.last
313
          issues << version        
314
        end
315
      end
316
    end
317
    issues
318
  end  
319
  
274 320
end
app/controllers/versions_controller.rb
20 20
  before_filter :find_project, :authorize
21 21

  
22 22
  def show
23
    @issues = @version.fixed_issues.find(:all,
24
                                         :include => [:status, :tracker],
25
                                         :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id")
26
    @issues = Issue.find_with_parents( @issues.collect { |i| i.id})
23 27
  end
24 28
  
25 29
  def edit
app/helpers/issues_helper.rb
19 19

  
20 20
module IssuesHelper
21 21
  include ApplicationHelper
22
  
23
  def issue_ancestors(issue=@issue)
24
    ancestors = ""
25
    return "" if issue.parent == nil
26
    ancestors += "issue-#{issue.parent.id}-child " + issue_ancestors(issue.parent)        
27
  end
22 28

  
23 29
  def render_issue_tooltip(issue)
24 30
    @cached_label_start_date ||= l(:field_start_date)
......
185 191
    export.rewind
186 192
    export
187 193
  end
194
  
195
  def set_parent(issue, parent_id)
196
    if (issue && parent_id.to_s.size > 0)
197
      issue.relations_from.each do |relation|
198
        if relation.relation_type == IssueRelation::TYPE_PARENTS
199
          relation.destroy
200
        end
201
      end
202
      issue.reload
203
      IssueRelation.new do |relation|
204
        relation.issue_from = issue
205
        relation.issue_to = Issue.find(parent_id)
206
        relation.relation_type = IssueRelation::TYPE_PARENTS
207
        relation.save
208
      end unless parent_id == '0'
209
    end    
210
  end
211
  
212
  def auto_complete_result_parent_issue(candidates, phrase)
213
    return "" if candidates.empty?
214
    candidates.map! do |c|
215
      content_tag("li", highlight( c.to_s, phrase), :id => String( c[:id]))
216
    end
217
    content_tag("ul", candidates.uniq)
218
  end
188 219
end
app/helpers/queries_helper.rb
1
# -*- coding: mule-utf-8 -*-
1 2
# redMine - project management software
2 3
# Copyright (C) 2006-2007  Jean-Philippe Lang
3 4
#
......
40 41
      else
41 42
        case column.name
42 43
        when :subject
43
        h((@project.nil? || @project != issue.project) ? "#{issue.project.name} - " : '') +
44
          link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
44
          subject_in_tree(issue, value)
45 45
        when :done_ratio
46 46
          progress_bar(value, :width => '80px')
47 47
        when :fixed_version
......
52 52
      end
53 53
    end
54 54
  end
55
  
56
  def subject_in_tree(issue, value)
57
    image = ""
58
    unless issue.edge? #don't show + or - icons for leaf issues
59
      #o controle das tarefas a ser mostradas não está sendo feito por _list? verificar
60
      image = @show_all_issues || 
61
      @show_children ? 
62
      link_to_remote(image_tag("contract.png", :class=>'contract-icon'), {:url => {:controller => 'issues', :action => "hide_children", :id => issue, :project_id => @project}}, :class=>'contract-link') : 
63
      link_to_remote(image_tag("expand.png", :class=>'expand-icon'), {:url => {:controller => 'issues', :action => "show_children", :id => issue, :project_id => @project}}, :class=>'expand-link')
64
    end
65
    content_tag('span', image + content_tag('div', subject_text(issue, value), :class=>'issue-subject'), :class=>"issue-subject-level-#{issue.hierarchical_level}")
66
  end
67
  
68
  def subject_text(issue, value)
69
    subject_text = link_to(h(value), :controller => 'issues', :action => 'show', :id => issue)
70
    h((@project.nil? || @project != issue.project) ? "#{issue.project.name} - " : '') + subject_text
71
  end
72
  
55 73
end
app/helpers/versions_helper.rb
44 44
  def status_by_options_for_select(value)
45 45
    options_for_select(STATUS_BY_CRITERIAS.collect {|criteria| [l("field_#{criteria}".to_sym), criteria]}, value)
46 46
  end
47

  
48
  def render_list_of_related_issues( issues, version, current_level = 1)
49
    issues_on_current_level = issues.select { |i| i.hierarchical_level == current_level }
50
    issues -= issues_on_current_level
51
    content_tag( 'ul') do
52
      html = ''
53
      issues_on_current_level.each do |issue|
54
        opts_for_issue_li = { }
55
        if !issue.fixed_version or issue.fixed_version != version
56
          opts_for_issue_li[:style] = 'opacity: 0.5;filter: alpha(opacity=50);'
57
        end
58
        html << content_tag( 'li', opts_for_issue_li) do
59
          opts = { }
60
          if issue.done_ratio == 100
61
            opts[:style] = 'font-weight: bold'
62
          end
63
          link_to_issue(issue, opts)  + ": " + h(issue.subject)
64
        end
65
        children_to_print = issues & issue.children
66
        children_to_print += issues.select { |i| i.hierarchical_level >= current_level + 2}
67
        unless children_to_print.empty?
68
          html << render_list_of_related_issues( children_to_print, version, current_level + 1)
69
        end
70
      end
71
      html
72
    end
73
  end
47 74
end
app/models/issue.rb
18 18
class Issue < ActiveRecord::Base
19 19
  belongs_to :project
20 20
  belongs_to :tracker
21
  belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
22
  belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
23
  belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
24
  belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
25
  belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
26
  belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
21
  belongs_to :status,        :class_name => 'IssueStatus',   :foreign_key => 'status_id'
22
  belongs_to :author,        :class_name => 'User',          :foreign_key => 'author_id'
23
  belongs_to :assigned_to,   :class_name => 'User',          :foreign_key => 'assigned_to_id'
24
  belongs_to :fixed_version, :class_name => 'Version',       :foreign_key => 'fixed_version_id'
25
  belongs_to :priority,      :class_name => 'Enumeration',   :foreign_key => 'priority_id'
26
  belongs_to :category,      :class_name => 'IssueCategory', :foreign_key => 'category_id'
27 27

  
28
  has_many :journals, :as => :journalized, :dependent => :destroy
29
  has_many :attachments, :as => :container, :dependent => :destroy
28
  has_many :journals,    :as => :journalized, :dependent => :destroy
29
  has_many :attachments, :as => :container,   :dependent => :destroy
30 30
  has_many :time_entries, :dependent => :delete_all
31 31
  has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
32 32
  
33 33
  has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
34
  has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
34
  has_many :relations_to,   :class_name => 'IssueRelation', :foreign_key => 'issue_to_id',   :dependent => :delete_all
35 35
  
36 36
  acts_as_customizable
37 37
  acts_as_watchable
......
44 44
  
45 45
  acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]}
46 46
  
47
  validates_presence_of :subject, :description, :priority, :project, :tracker, :author, :status
48
  validates_length_of :subject, :maximum => 255
49
  validates_inclusion_of :done_ratio, :in => 0..100
47
  validates_presence_of     :subject,    :description,   :priority, :project, :tracker, :author, :status
48
  validates_length_of       :subject,    :maximum => 255
49
  validates_inclusion_of    :done_ratio, :in => 0..100
50 50
  validates_numericality_of :estimated_hours, :allow_nil => true
51 51

  
52 52
  def after_initialize
......
99 99
    return true
100 100
  end
101 101
  
102
  def priority_id=(pid)
103
    self.priority = nil
104
    write_attribute(:priority_id, pid)
105
  end
106
  
107 102
  def estimated_hours=(h)
108 103
    write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
109 104
  end
......
120 115
    if start_date && soonest_start && start_date < soonest_start
121 116
      errors.add :start_date, :activerecord_error_invalid
122 117
    end
118

  
119
    if IssueStatus.find_by_id( @attributes['status_id']).is_closed? && children.find { |i| !i.closed? }
120
      errors.add :status, "Can't close parent issue while on of the children still is open."
121
    end
123 122
  end
124 123
  
125 124
  def validate_on_create
......
140 139
        @current_journal.details << JournalDetail.new(:property => 'attr',
141 140
                                                      :prop_key => c,
142 141
                                                      :old_value => @issue_before_change.send(c),
143
                                                      :value => send(c)) unless send(c)==@issue_before_change.send(c)
142
                                                      :value => send(c)) unless send(c)==@issue_before_change.send(c) || (!self.edge? && %w(status_id priority_id fixed_version_id start_date due_date done_ratio estimated_hours).include?(c))
144 143
      }
145 144
      # custom fields changes
146 145
      custom_values.each {|c|
......
153 152
      }      
154 153
      @current_journal.save
155 154
    end
155

  
156 156
    # Save the issue even if the journal is not saved (because empty)
157 157
    true
158 158
  end
......
176 176
        duplicate.update_attribute :status, self.status
177 177
      end
178 178
    end
179

  
180
    # Set new status to parent if new status openes the issue.
181
    if parent
182
      if parent.closed? && !self.closed?
183
        parent.update_attribute :status, IssueStatus.default
184
      end
185
    end
179 186
  end
180
  
187

  
188
  def fixed_version
189
    if children?
190
      children.select { |c| c.fixed_version } .collect { |c| c.fixed_version } .max
191
    else
192
      Version.find(self[:fixed_version_id]) if self[:fixed_version_id]  && self[:fixed_version_id] != 0
193
    end 
194
  end
195

  
181 196
  def init_journal(user, notes = "")
182 197
    @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
183 198
    @issue_before_change = self.clone
......
243 258
    due_date || (fixed_version ? fixed_version.effective_date : nil)
244 259
  end
245 260
  
261
  def duration1
262
    (start_date && due_date) ? (due_date - start_date + 1) : 0
263
  end
264
  
246 265
  def duration
247 266
    (start_date && due_date) ? due_date - start_date : 0
248 267
  end
......
257 276
    end
258 277
  end
259 278
  
279
  def done_ratio
280
   if children?
281
     @total_planned_days ||= 0
282
     @total_actual_days ||= 0
283
     children.each do |child| # from every subtask get the total number of days and the number of days already "worked"
284
       planned_days = child.duration1
285
       actual_days = child.done_ratio ?  (planned_days * child.done_ratio / 100).floor : 0
286
       @total_planned_days += planned_days
287
       @total_actual_days += actual_days
288
     end
289
     @total_done_ratio = @total_planned_days != 0 ? (@total_actual_days * 100 / @total_planned_days).floor : 0
290
   else
291
     read_attribute(:done_ratio)
292
   end
293
  end
294
  
295
  def estimated_hours
296
    if children?
297
      is_set = false
298
      children.each do |child|
299
        if child.estimated_hours
300
          if is_set
301
            @est_hours += child.estimated_hours
302
          else
303
            @est_hours = child.estimated_hours
304
            is_set = true
305
          end
306
        end     
307
      end
308
      @est_hours
309
    else
310
      read_attribute(:estimated_hours)
311
    end
312
  end
313
  
314
  def start_date
315
    calculate 'start_date'
316
  end  
317
  
318
  def due_date
319
    calculate 'due_date'
320
  end  
321
  
322
  def calculate(field)
323
    if children?
324
      @value = eval "children.first.#{field}" 
325
      children.each do |child|        
326
        case field
327
          when 'start_date'
328
          if child.start_date && (!@value || @value > child.start_date)
329
            @value = child.start_date
330
          end
331
          when 'due_date'
332
          if child.due_date && (!@value || @value < child.due_date)
333
            @value = child.due_date
334
          end
335
        end        
336
      end
337
      @value
338
    else
339
      read_attribute(eval(":#{field}"))
340
    end
341
  end  
342

  
343
  def children?
344
    children != []
345
  end
346
  
347
  def children
348
    children = []
349
    relations_to.each do |relation|
350
      if relation.relation_type == IssueRelation::TYPE_PARENTS
351
        children << relation.other_issue(self)
352
      end
353
    end
354
    children
355
  end
356
  
357
  def parent
358
    relations_from.each do |relation|
359
      if relation.relation_type == IssueRelation::TYPE_PARENTS
360
        return parent = relation.other_issue(self)
361
      end
362
    end
363
    return nil
364
  end
365

  
366
  def parent?
367
    parent != nil
368
  end 
369
  
370
  def root?
371
    !parent?
372
  end
373
  
374
  def ancestors(issue=self)
375
    a = []
376
    return a if ! issue.parent?
377
    (a << issue.parent) | ancestors(issue.parent)
378
  end
379
  
380
  #First level tasks have hierarchical level = 1 and so on
381
  def hierarchical_level(issue=self)
382
    issue.parent? ? (1 + hierarchical_level(issue.parent)) : 1
383
  end
384
  
385
  def edge?
386
    relations_to.each do |relation|
387
      if relation.relation_type == IssueRelation::TYPE_PARENTS
388
        return false
389
      end
390
    end
391
    return true
392
  end
393

  
394
  def orphan?
395
    root? && edge?
396
  end
397
  
398
  def self.find_with_parents( *args)
399
    issues = find( *args)
400
    return [] if issues.empty?
401
    issues.each do |i|
402
      while not i.root?
403
        issues += [ i.parent ]
404
        i = i.parent
405
      end
406
    end
407
    issues.uniq
408
  end
409
  
260 410
  def to_s
261 411
    "#{tracker} ##{id}: #{subject}"
262 412
  end
app/models/issue_relation.rb
17 17

  
18 18
class IssueRelation < ActiveRecord::Base
19 19
  belongs_to :issue_from, :class_name => 'Issue', :foreign_key => 'issue_from_id'
20
  belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id'
20
  belongs_to :issue_to,   :class_name => 'Issue', :foreign_key => 'issue_to_id'
21 21
  
22 22
  TYPE_RELATES      = "relates"
23 23
  TYPE_DUPLICATES   = "duplicates"
24 24
  TYPE_BLOCKS       = "blocks"
25 25
  TYPE_PRECEDES     = "precedes"
26
  TYPE_PARENTS      = "parents"
26 27
  
27
  TYPES = { TYPE_RELATES =>     { :name => :label_relates_to, :sym_name => :label_relates_to, :order => 1 },
28
            TYPE_DUPLICATES =>  { :name => :label_duplicates, :sym_name => :label_duplicated_by, :order => 2 },
29
            TYPE_BLOCKS =>      { :name => :label_blocks, :sym_name => :label_blocked_by, :order => 3 },
30
            TYPE_PRECEDES =>    { :name => :label_precedes, :sym_name => :label_follows, :order => 4 },
28
  TYPES = { TYPE_RELATES    => { :name => :label_relates_to, :sym_name => :label_relates_to,    :order => 1 },
29
            TYPE_DUPLICATES => { :name => :label_duplicates, :sym_name => :label_duplicated_by, :order => 2 },
30
            TYPE_BLOCKS     => { :name => :label_blocks,     :sym_name => :label_blocked_by,    :order => 3 },
31
            TYPE_PRECEDES   => { :name => :label_precedes,   :sym_name => :label_follows,       :order => 4 },
32
            TYPE_PARENTS    => { :name => :label_parents,    :sym_name => :label_children,      :order => 5 },
31 33
          }.freeze
32 34
  
33
  validates_presence_of :issue_from, :issue_to, :relation_type
34
  validates_inclusion_of :relation_type, :in => TYPES.keys
35
  validates_numericality_of :delay, :allow_nil => true
36
  validates_uniqueness_of :issue_to_id, :scope => :issue_from_id
35
  validates_presence_of     :issue_from,    :issue_to, :relation_type 
36
  validates_inclusion_of    :relation_type, :in        => TYPES.keys
37
  validates_uniqueness_of   :relation_type, :scope     => [:issue_from_id, :relation_type], :message=>l(:error_issue_can_have_only_one_parent)
38
  validates_numericality_of :delay,         :allow_nil => true
39
  validates_uniqueness_of   :issue_to_id,   :scope     => :issue_from_id
37 40
  
38 41
  def validate
39 42
    if issue_from && issue_to
......
57 60
    else
58 61
      self.delay = nil
59 62
    end
63
    # Set new status to parent if new status openes the issue.
64
    if TYPE_PARENTS == relation_type
65
      set_issue_to_status
66
    end
60 67
    set_issue_to_dates
61 68
  end
69

  
70
  def set_issue_to_status
71
    if issue_to.closed? && !issue_from.closed?
72
      issue_to.update_attribute :status, IssueStatus.default
73
    end
74
  end
62 75
  
63 76
  def set_issue_to_dates
64 77
    soonest_start = self.successor_soonest_start
app/models/version.rb
26 26
  validates_length_of :name, :maximum => 60
27 27
  validates_format_of :effective_date, :with => /^\d{4}-\d{2}-\d{2}$/, :message => 'activerecord_error_not_a_date', :allow_nil => true
28 28
  
29
  include Comparable
30
  
29 31
  def start_date
30 32
    effective_date
31 33
  end
app/views/issues/_form.rhtml
1
<!-- This function is needed for get ID from the text field with parent issue selection. -->
2
<script type="text/javascript">
3
  //<![CDATA[
4
  function setParentIssueValue(element, value) {
5
          document.getElementById('issue_parent_issue_id').value = value.id;
6
      }
7
//]]>
8
</script>
1 9
<% if @issue.new_record? %>
2 10
<p><%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %></p>
3 11
<%= observe_field :issue_tracker_id, :url => { :action => :new },
......
16 24
</div>
17 25

  
18 26
<div class="splitcontentleft">
19
<% if @issue.new_record? || @allowed_statuses.any? %>
27
<% if (@issue.new_record? || @allowed_statuses.any?) %>
20 28
<p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
21 29
<% else %>
22 30
<p><label><%= l(:field_status) %></label> <%= @issue.status.name %></p>
23 31
<% end %>
24 32

  
25 33
<p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), :required => true %></p>
34

  
26 35
<p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
27 36
<p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
28 37
<%= prompt_to_remote(l(:label_issue_category_new),
29 38
                     l(:label_issue_category_new), 'category[name]', 
30 39
                     {:controller => 'projects', :action => 'add_issue_category', :id => @project},
31 40
                     :class => 'small', :tabindex => 199) if authorize_for('projects', 'add_issue_category') %></p>
41

  
42
<% if @issue.edge? %>
32 43
<%= content_tag('p', f.select(:fixed_version_id, 
33 44
                              (@project.versions.sort.collect {|v| [v.name, v.id]}),
34 45
                              { :include_blank => true })) unless @project.versions.empty? %>
46
<% else %>
47
<p><label><%= l(:field_fixed_version) %></label> <%= @issue.fixed_version ? @issue.fixed_version.name : '-' %></p>
48
<% end %>
35 49
</div>
36 50

  
37 51
<div class="splitcontentright">
52
<% if @issue.edge? %>	
38 53
<p><%= f.text_field :start_date, :size => 10 %><%= calendar_for('issue_start_date') %></p>
39 54
<p><%= f.text_field :due_date, :size => 10 %><%= calendar_for('issue_due_date') %></p>
40 55
<p><%= f.text_field :estimated_hours, :size => 3 %> <%= l(:field_hours) %></p>
41 56
<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
57
<% else %>
58
<p><label><%= l(:field_start_date) %></label> <%= format_date(@issue.start_date) %></p>
59
<p><label><%= l(:field_due_date) %></label> <%= format_date(@issue.due_date) %></p>
60
<p><label><%= l(:field_done_ratio) %></label> <%= "#{@issue.done_ratio}%" %></p>
61
<% end %>
62
<%= content_tag( :input, {}, 
63
		 :id => :issue_parent_issue_id,
64
		 :type => :hidden,
65
		 :name => 'issue_parent_issue_id', :value => @parent_issue ? @parent_issue.id : "") %>
66
<p><label><%= l(:field_parent_issue) %></label>
67
<% if authorize_for( 'issues', 'add_subissue') %>
68
  <%= text_field_with_auto_complete( :issue, :parent,
69
				     { :name => 'issue_parent', :value => @parent_issue || "" },
70
				     :url => { :action => 'auto_complete_for_issue_parent', :project_id => @project},
71
				     :after_update_element => 'setParentIssueValue') %>
72
<% else %>
73
  <%= @parent_issue || "-" %>
74
<% end %>
75
</p>
42 76
</div>
43 77

  
44 78
<div style="clear:both;"> </div>
app/views/issues/_form_update.rhtml
3 3
<p><%= f.select :assigned_to_id, (@issue.assignable_users.collect {|m| [m.name, m.id]}), :include_blank => true %></p>
4 4
</div>
5 5
<div class="splitcontentright">
6
<% if @issue.edge? %>
6 7
<p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
7 8
<%= content_tag('p', f.select(:fixed_version_id, 
8 9
                          (@project.versions.sort.collect {|v| [v.name, v.id]}),
9 10
                          { :include_blank => true })) unless @project.versions.empty? %>
11
<% else %>
12
<p><label><%= l(:field_done_ratio) %></label> <%= "#{@issue.done_ratio}%" %></p>
13
<p><label><%= l(:field_fixed_version) %></label> <%= @issue.fixed_version ? @issue.fixed_version.name : '-' %></p>
14
<% end %>
10 15
</div>
app/views/issues/_list.rhtml
10 10
        <% end %>
11 11
	</tr></thead>
12 12
	<tbody>
13
	<% issues.each do |issue| -%>
13
	<% issues.each do |issue|
14
	   next if !@show_all_issues && (issue.hierarchical_level != 1) -%>	    
14 15
	<tr id="issue-<%= issue.id %>" class="issue hascontextmenu <%= cycle('odd', 'even') %> <%= "status-#{issue.status.position} priority-#{issue.priority.position}" %>">
15 16
	    <td class="checkbox"><%= check_box_tag("ids[]", issue.id, false, :id => nil) %></td>
16 17
		<td><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td>
app/views/issues/_show_children.rhtml
1
<%	
2
issues = [@issue]
3
issues += @issue.children
4
issues.each do |issue|
5
	is_parent = (issue == @issue)
6
    #used in queries_helper.rb when deciding which icon will be shown (if + or -)  
7
	@show_children = is_parent -%>	    
8
	<tr id="issue-<%=issue.id%>" class="issue <%= issue_ancestors(issue) %> hascontextmenu <%= cycle('odd', 'even') %> <%= "status-#{issue.status.position} priority-#{issue.priority.position}" %>">
9
	    <td class="checkbox"><%= check_box_tag("ids[]", issue.id, false, :id => nil) %></td>
10
		<td><%= link_to issue.id, :controller => 'issues', :action => 'show', :id => issue %></td>
11
	    <% query.columns.each do |column| %><%= content_tag 'td', column_content(column, issue), :class => column.name %><% end %>
12
	</tr>
13
<% end -%>
app/views/issues/context_menu.rhtml
2 2
<% if !@issue.nil? -%>
3 3
	<li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue},
4 4
	        :class => 'icon-edit', :disabled => !@can[:edit] %></li>
5
	<% if @issue.edge? %>
5 6
	<li class="folder">			
6 7
		<a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a>
7 8
		<ul>
......
11 12
		<% end -%>
12 13
		</ul>
13 14
	</li>
15
	<% end %>
14 16
<% else %>
15 17
	<li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id)},
16 18
	        :class => 'icon-edit', :disabled => !@can[:edit] %></li>
17 19
<% end %>
18

  
19 20
	<li class="folder">			
20 21
		<a href="#" class="submenu"><%= l(:field_priority) %></a>
21 22
		<ul>
......
64 65
		</ul>
65 66
	</li>
66 67
	<% end -%>
68
	<% if @issue && @issue.edge? %>
67 69
	<li class="folder">
68 70
		<a href="#" class="submenu"><%= l(:field_done_ratio) %></a>
69 71
		<ul>
......
73 75
		<% end -%>
74 76
		</ul>
75 77
	</li>
76
	
78
	<% end %>
77 79
<% if !@issue.nil? %>
78 80
	<li><%= context_menu_link l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue},
79 81
	        :class => 'icon-copy', :disabled => !@can[:copy] %></li>
app/views/issues/index.rfpdf
24 24
   	pdf.Cell(30, row_height, l(:field_priority), 0, 0, 'L', 1)
25 25
   	pdf.Cell(40, row_height, l(:field_assigned_to), 0, 0, 'L', 1)
26 26
   	pdf.Cell(25, row_height, l(:field_updated_on), 0, 0, 'L', 1)
27
   	pdf.Cell(15, row_height, l(:field_parent_issue), 0, 0, 'L', 1)
27 28
   	pdf.Cell(0, row_height, l(:field_subject), 0, 0, 'L', 1)
28 29
   	pdf.Line(10, pdf.GetY, 287, pdf.GetY)
29 30
   	pdf.Ln
......
42 43
	   	pdf.Cell(30, row_height, issue.priority.name, 0, 0, 'L', 1)
43 44
	   	pdf.Cell(40, row_height, issue.assigned_to ? issue.assigned_to.name : '', 0, 0, 'L', 1)
44 45
	   	pdf.Cell(25, row_height, format_date(issue.updated_on), 0, 0, 'L', 1)
46
   		pdf.Cell(15, row_height, issue.parent ? "# #{issue.parent.id}" : '', 0, 0, 'L', 1)
45 47
	   	pdf.MultiCell(0, row_height, (@project == issue.project ? issue.subject : "#{issue.project.name} - #{issue.subject}"))
46 48
   		pdf.Line(10, pdf.GetY, 287, pdf.GetY)
47 49
   		pdf.SetY(pdf.GetY() + 1)
app/views/issues/show.rhtml
1 1
<div class="contextual">
2
<%= link_to_if_authorized(l(:button_update), {:controller => 'issues', :action => 'edit', :id => @issue }, :onclick => 'showAndScrollTo("update", "notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %>
2
<%= link_to_if_authorized(l(:button_add_subissue),
3
			  { :controller => 'issues', :action => 'add_subissue',
4
			    :project_id => @project.id, :issue_parent_issue_id => @issue.id },
5
			  :class => 'icon icon-add') %>
6
<%= link_to_if_authorized(l(:button_update), {:controller => 'issues', :action => 'edit', :id => @issue, :project_id => @project }, :onclick => 'showAndScrollTo("update", "notes"); return false;', :class => 'icon icon-edit', :accesskey => accesskey(:edit)) %>
3 7
<%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %>
4 8
<%= watcher_tag(@issue, User.current) %>
5 9
<%= link_to_if_authorized l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue }, :class => 'icon icon-copy' %>
......
51 55
   if (n > 1) 
52 56
        n = 0 %>
53 57
        </tr><tr>
54
 <%end
55
end %>
58
   <% end %>
59
<% end %>
56 60
</tr>
57 61
<%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
58 62
</table>
app/views/projects/roadmap.rhtml
10 10
    <%= render :partial => 'versions/overview', :locals => {:version => version} %>
11 11
    <%= render(:partial => "wiki/content", :locals => {:content => version.wiki_page.content}) if version.wiki_page %>
12 12

  
13
    <% issues = version.fixed_issues.find(:all,
14
                                          :include => [:status, :tracker],
15
                                          :conditions => ["tracker_id in (#{@selected_tracker_ids.join(',')})"],
16
                                          :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id") unless @selected_tracker_ids.empty?
13
    <%
14
       unless @selected_tracker_ids.empty?
15
	 issues = version.fixed_issues.find(:all,
16
                                           :include => [:status, :tracker],
17
                                           :conditions => ["tracker_id in (#{@selected_tracker_ids.join(',')})"],
18
                                           :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id") 
19
         issues = Issue.find_with_parents( issues.collect { |i| i.id })
20
       end
17 21
       issues ||= []
18 22
    %>
19 23
    <% if issues.size > 0 %>
20 24
    <fieldset class="related-issues"><legend><%= l(:label_related_issues) %></legend>
21
    <ul>
22
    <%- issues.each do |issue| -%>
23
        <li><%= link_to_issue(issue) %>: <%=h issue.subject %></li>
24
    <%- end -%>
25
    </ul>
25
      <%= render_list_of_related_issues( issues, version) %>
26 26
    </fieldset>
27 27
    <% end %>
28 28
<% end %>
lang/bg.yml
119 119
field_assigned_to: Възложена на
120 120
field_priority: Приоритет
121 121
field_fixed_version: Планувана версия
122
field_parent_issue: Child of
122 123
field_user: Потребител
123 124
field_role: Роля
124 125
field_homepage: Начална страница
......
396 397
label_blocked_by: блокирана от
397 398
label_precedes: предшества
398 399
label_follows: изпълнява се след
400
label_parents: child of
401
label_children: parent of
399 402
label_end_to_start: end to start
400 403
label_end_to_end: end to end
401 404
label_start_to_start: start to start
......
618 621
setting_default_projects_public: Новите проекти са публични по подразбиране
619 622
error_scm_annotate: "Обектът не съществува или не може да бъде анотиран."
620 623
label_planning: Планиране
624
error_issue_can_have_only_one_parent: allows only one relationship (a task can have only one parent).
621 625
text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.'
622 626
label_and_its_subprojects: %s and its subprojects
623 627
mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:"
......
692 696
text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
693 697
permission_edit_own_messages: Edit own messages
694 698
permission_delete_won_messages: Delete own messages
699
button_add_subissue: Add sub-issue
lang/ca.yml
693 693
text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
694 694
permission_edit_own_messages: Edit own messages
695 695
permission_delete_won_messages: Delete own messages
696
button_add_subissue: Add sub-issue
697
label_parents: child of
698
label_children: parent of
699
field_parent_issue: Child of
700
error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent).
lang/cs.yml
133 133
field_assigned_to: Přiřazeno
134 134
field_priority: Priorita
135 135
field_fixed_version: Přiřazeno k verzi
136
field_parent_issue: Child of
136 137
field_user: Uživatel
137 138
field_role: Role
138 139
field_homepage: Homepage
......
453 454
label_blocked_by: zablokován
454 455
label_precedes: předchází
455 456
label_follows: následuje
457
label_parents: child of
458
label_children: parent of
456 459
label_end_to_start: od konce do začátku
457 460
label_end_to_end: od konce do konce
458 461
label_start_to_start: od začátku do začátku
......
623 626
enumeration_activities: Aktivity (sledování času)
624 627
error_scm_annotate: "Položka neexistuje nebo nemůže být komentována."
625 628
label_planning: Plánování
629
error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent).
626 630
text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.'
627 631
label_and_its_subprojects: %s and its subprojects
628 632
mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:"
......
697 701
text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
698 702
permission_edit_own_messages: Edit own messages
699 703
permission_delete_won_messages: Delete own messages
704
button_add_subissue: Add sub-issue
lang/da.yml
133 133
field_assigned_to: Tildelt til
134 134
field_priority: Prioritet
135 135
field_fixed_version: Planlagt version
136
field_parent_issue: Child of
136 137
field_user: Bruger
137 138
field_role: Rolle
138 139
field_homepage: Hjemmeside
......
463 464
label_blocked_by: blokeret af
464 465
label_precedes: kommer før
465 466
label_follows: følger
467
label_parents: child of
468
label_children: parent of
466 469
label_end_to_start: slut til start
467 470
label_end_to_end: slut til slut
468 471
label_start_to_start: start til start
......
524 527
label_chronological_order: I kronologisk rækkefølge
525 528
label_reverse_chronological_order: I omvendt kronologisk rækkefølge
526 529
label_planning: Planlægning
530
error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent).
527 531
label_incoming_emails: Indkommende e-mails
528 532
label_generate_key: Generer en nøgle
529 533
label_issue_watchers: Overvågere
......
693 697
text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
694 698
permission_edit_own_messages: Edit own messages
695 699
permission_delete_won_messages: Delete own messages
700
button_add_subissue: Add sub-issue
lang/de.yml
133 133
field_assigned_to: Zugewiesen an
134 134
field_priority: Priorität
135 135
field_fixed_version: Zielversion
136
field_parent_issue: Child of
136 137
field_user: Benutzer
137 138
field_role: Rolle
138 139
field_homepage: Projekt-Homepage
......
514 515
label_blocked_by: Blockiert durch
515 516
label_precedes: Vorgänger von
516 517
label_follows: folgt
518
label_parents: child of
519
label_children: parent of
517 520
label_end_to_start: Ende - Anfang
518 521
label_end_to_end: Ende - Ende
519 522
label_start_to_start: Anfang - Anfang
......
694 697
enumeration_issue_priorities: Ticket-Prioritäten
695 698
enumeration_doc_categories: Dokumentenkategorien
696 699
enumeration_activities: Aktivitäten (Zeiterfassung)
700

  
701
error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent).
702

  
703
button_add_subissue: Add sub-issue
lang/en.yml
133 133
field_assigned_to: Assigned to
134 134
field_priority: Priority
135 135
field_fixed_version: Target version
136
field_parent_issue: Child of
136 137
field_user: User
137 138
field_role: Role
138 139
field_homepage: Homepage
......
514 515
label_blocked_by: blocked by
515 516
label_precedes: precedes
516 517
label_follows: follows
518
label_parents: child of
519
label_children: parent of
517 520
label_end_to_start: end to start
518 521
label_end_to_end: end to end
519 522
label_start_to_start: start to start
......
590 593
button_test: Test
591 594
button_edit: Edit
592 595
button_add: Add
596
button_add_subissue: Add sub-issue
593 597
button_change: Change
594 598
button_apply: Apply
595 599
button_clear: Clear
......
627 631
text_regexp_info: eg. ^[A-Z0-9]+$
628 632
text_min_max_length_info: 0 means no restriction
629 633
text_project_destroy_confirmation: Are you sure you want to delete this project and related data ?
634
error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent).
630 635
text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.'
631 636
text_workflow_edit: Select a role and a tracker to edit the workflow
632 637
text_are_you_sure: Are you sure ?
lang/es.yml
137 137
field_identifier: Identificador
138 138
field_is_closed: Petición resuelta
139 139
field_is_default: Estado por defecto
140
field_is_filter: Usado como filtro
141
field_is_for_all: Para todos los proyectos
142 140
field_is_in_chlog: Consultar las peticiones en el histórico
143 141
field_is_in_roadmap: Consultar las peticiones en el roadmap
144 142
field_is_public: Público
......
185 183
field_user: Usuario
186 184
field_value: Valor
187 185
field_version: Versión
186
field_parent: Proyecto padre
187
field_parent_issue: Child of
188 188
general_csv_decimal_separator: ','
189 189
general_csv_encoding: ISO-8859-15
190 190
general_csv_separator: ';'
......
501 501
label_workflow: Flujo de trabajo
502 502
label_year: Año
503 503
label_yesterday: ayer
504
label_parents: child of
505
label_children: parent of
504 506
mail_body_account_activation_request: "Un nuevo usuario (%s) ha sido registrado. Esta cuenta está pendiende de aprobación"
505 507
mail_body_account_information: Información sobre su cuenta
506 508
mail_body_account_information_external: Puede usar su cuenta "%s" para conectarse.
......
592 594
project_module_time_tracking: Control de tiempo
593 595
project_module_wiki: Wiki
594 596
setting_activity_days_default: Días a mostrar en la actividad de proyecto
595
setting_app_subtitle: Subtítulo de la aplicación
596
setting_app_title: Título de la aplicación
597
setting_attachment_max_size: Tamaño máximo del fichero
598
setting_autofetch_changesets: Autorellenar los commits del repositorio
599
setting_autologin: Conexión automática
600
setting_bcc_recipients: Ocultar las copias de carbón (bcc)
601
setting_commit_fix_keywords: Palabras clave para la corrección
602
setting_commit_logs_encoding: Codificación de los mensajes de commit
603
setting_commit_ref_keywords: Palabras clave para la referencia
604
setting_cross_project_issue_relations: Permitir relacionar peticiones de distintos proyectos
605
setting_date_format: Formato de la fecha
606
setting_default_language: Idioma por defecto
607
setting_default_projects_public: Los proyectos nuevos son públicos por defecto
608
setting_display_subprojects_issues: Mostrar peticiones de un subproyecto en el proyecto padre por defecto
609
setting_emails_footer: Pie de mensajes
597

  
598
error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent).
599

  
610 600
setting_enabled_scm: Activar SCM
611 601
setting_feeds_limit: Límite de contenido para sindicación
612 602
setting_gravatar_enabled: Usar iconos de usuario (Gravatar)
......
677 667
text_user_wrote: '%s escribió:'
678 668
text_wiki_destroy_confirmation: ¿Seguro que quiere borrar el wiki y todo su contenido?
679 669
text_workflow_edit: Seleccionar un flujo de trabajo para actualizar
670
field_is_filter: Used as a filter
671
setting_default_language: Default language
672
setting_emails_footer: Emails footer
673
setting_app_subtitle: Application subtitle
674
button_add_subissue: Add sub-issue
675
setting_cross_project_issue_relations: Allow cross-project issue relations
676
setting_autofetch_changesets: Autofetch commits
677
setting_attachment_max_size: Attachment max. size
678
setting_app_title: Application title
679
setting_date_format: Date format
680
setting_default_projects_public: New projects are public by default
681
setting_commit_fix_keywords: Fixing keywords
682
setting_autologin: Autologin
683
setting_display_subprojects_issues: Display subprojects issues on main projects by default
684
setting_bcc_recipients: Blind carbon copy recipients (bcc)
685
setting_commit_ref_keywords: Referencing keywords
686
field_is_for_all: For all projects
687
setting_commit_logs_encoding: Commit messages encoding
lang/fi.yml
692 692
text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
693 693
permission_edit_own_messages: Edit own messages
694 694
permission_delete_won_messages: Delete own messages
695
button_add_subissue: Add sub-issue
696
label_parents: child of
697
label_children: parent of
698
field_parent_issue: Child of
699
error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent).
lang/fr.yml
133 133
field_assigned_to: Assigné à
134 134
field_priority: Priorité
135 135
field_fixed_version: Version cible
136
field_parent_issue: Child of
136 137
field_user: Utilisateur
137 138
field_role: Rôle
138 139
field_homepage: Site web
......
514 515
label_blocked_by: bloqué par
515 516
label_precedes: précède
516 517
label_follows: suit
518
label_parents: child of
519
label_children: parent of
517 520
label_end_to_start: fin à début
518 521
label_end_to_end: fin à fin
519 522
label_start_to_start: début à début
......
627 630
text_regexp_info: ex. ^[A-Z0-9]+$
628 631
text_min_max_length_info: 0 pour aucune restriction
629 632
text_project_destroy_confirmation: Etes-vous sûr de vouloir supprimer ce projet et toutes ses données ?
633
error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent).
630 634
text_subprojects_destroy_warning: 'Ses sous-projets: %s seront également supprimés.'
631 635
text_workflow_edit: Sélectionner un tracker et un rôle pour éditer le workflow
632 636
text_are_you_sure: Etes-vous sûr ?
......
694 698
enumeration_issue_priorities: Priorités des demandes
695 699
enumeration_doc_categories: Catégories des documents
696 700
enumeration_activities: Activités (suivi du temps)
701
button_add_subissue: Add sub-issue
lang/he.yml
121 121
field_assigned_to: מוצב ל
122 122
field_priority: עדיפות
123 123
field_fixed_version: גירסאת יעד
124
field_parent_issue: Child of
124 125
field_user: מתשמש
125 126
field_role: תפקיד
126 127
field_homepage: דף הבית
......
618 619
setting_default_projects_public: פרויקטים חדשים הינם פומביים כברירת מחדל
619 620
error_scm_annotate: "הכניסה לא קיימת או שלא ניתן לתאר אותה."
620 621
label_planning: תכנון
622
error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent).
621 623
text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.'
622 624
label_and_its_subprojects: %s and its subprojects
623 625
mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:"
......
692 694
text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
693 695
permission_edit_own_messages: Edit own messages
694 696
permission_delete_won_messages: Delete own messages
697
button_add_subissue: Add sub-issue
698
label_parents: child of
699
label_children: parent of
lang/hu.yml
693 693
text_repository_usernames_mapping: "Állítsd be a felhasználó összerendeléseket a Redmine, és a tároló logban található felhasználók között.\nAz azonos felhasználó nevek összerendelése automatikusan megtörténik."
694 694
permission_edit_own_messages: Saját üzenetek szerkesztése
695 695
permission_delete_won_messages: Saját üzenetek törlése
696
button_add_subissue: Add sub-issue
697
label_parents: child of
698
label_children: parent of
699
field_parent_issue: Child of
700
error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent).
lang/it.yml
119 119
field_assigned_to: Assegnato a
120 120
field_priority: Priorita'
121 121
field_fixed_version: Versione prevista
122
field_parent_issue: Child of
122 123
field_user: Utente
123 124
field_role: Ruolo
124 125
field_homepage: Homepage
......
396 397
label_blocked_by: bloccato da
397 398
label_precedes: precede
398 399
label_follows: segue
400
label_parents: child of
401
label_children: parent of
399 402
label_end_to_start: end to start
400 403
label_end_to_end: end to end
401 404
label_start_to_start: start to start
......
618 621
setting_default_projects_public: I nuovi progetti sono pubblici per default
619 622
error_scm_annotate: "L'oggetto non esiste o non può essere annotato."
620 623
label_planning: Pianificazione
624
error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent).
621 625
text_subprojects_destroy_warning: 'Anche i suoi sottoprogetti: %s verranno eliminati.'
622 626
label_and_its_subprojects: %s ed i suoi sottoprogetti
623 627
mail_body_reminder: "%d segnalazioni che ti sono state assegnate scadranno nei prossimi %d giorni:"
......
692 696
text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
693 697
permission_edit_own_messages: Edit own messages
694 698
permission_delete_won_messages: Delete own messages
699
button_add_subissue: Add sub-issue
lang/ja.yml
120 120
field_assigned_to: 担当者
121 121
field_priority: 優先度
122 122
field_fixed_version: Target version
123
field_parent_issue: Child of
123 124
field_user: ユーザ
124 125
field_role: 役割
125 126
field_homepage: ホームページ
......
397 398
label_blocked_by: ブロックされている
398 399
label_precedes: 先行する
399 400
label_follows: 後続する
401
label_parents: child of
402
label_children: parent of
400 403
label_end_to_start: end to start
401 404
label_end_to_end: end to end
402 405
label_start_to_start: start to start
......
620 623
setting_default_projects_public: デフォルトで新しいプロジェクトは公開にする
621 624
error_scm_annotate: "エントリが存在しない、もしくはアノテートできません。"
622 625
label_planning: 計画
626
error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent).
623 627
text_subprojects_destroy_warning: 'サブプロジェクト %s も削除されます。'
624 628
label_and_its_subprojects: %s とサブプロジェクト
625 629
mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:"
......
693 697
text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
694 698
permission_edit_own_messages: Edit own messages
695 699
permission_delete_won_messages: Delete own messages
700
button_add_subissue: Add sub-issue
lang/ko.yml
121 121
field_assigned_to: 담당자
122 122
field_priority: 우선순위
123 123
field_fixed_version: 목표 버전
124
field_parent_issue: Child of
124 125
field_user: 유저
125 126
field_role: 역할
126 127
field_homepage: 홈페이지
......
403 404
label_blocked_by: 막고 있는 이슈
404 405
label_precedes: 다음 이슈보다 앞서서 처리해야 함.
405 406
label_follows: 선처리 이슈
407
label_parents: child of
408
label_children: parent of
406 409
label_end_to_start: end to start
407 410
label_end_to_end: end to end
408 411
label_start_to_start: start to start
......
618 621
setting_default_projects_public: 신규 프로젝트를  Public 으로 설정
619 622
error_scm_annotate: "The entry does not exist or can not be annotated."
620 623
label_planning: 계획(Planning)
624
error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent).
621 625
text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.'
622 626
label_and_its_subprojects: %s and its subprojects
623 627
mail_body_reminder: "%d issue(s) that are assigned to you are due in the next %d days:"
......
692 696
text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
693 697
permission_edit_own_messages: Edit own messages
694 698
permission_delete_won_messages: Delete own messages
699
button_add_subissue: Add sub-issue
lang/lt.yml
127 127
field_assigned_to: Paskirtas 
128 128
field_priority: Prioritetas 
129 129
field_fixed_version: Target version
130
field_parent_issue: Child of
130 131
field_user: Vartotojas 
131 132
field_role: Vaidmuo 
132 133
field_homepage: Pagrindinis puslapis 
......
415 416
label_blocked_by: blokuotas 
416 417
label_precedes: įvyksta pirma 
417 418
label_follows: seka 
419
label_parents: child of
420
label_children: parent of
418 421
label_end_to_start: užbaigti, kad pradėti
419 422
label_end_to_end: užbaigti, kad pabaigti 
420 423
label_start_to_start: pradėkite pradėti 
......
619 622
setting_default_projects_public: Naujas projektas viešas pagal nutylėjimą
620 623
error_scm_annotate: "Įrašas neegzituoja arba negalima jo atvaizduoti."
621 624
label_planning: Planavimas
625
error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent).
622 626
text_subprojects_destroy_warning: 'Šis(ie) subprojektas(ai): %s taip pat bus ištrintas(i).'
623 627
label_and_its_subprojects: %s projektas ir jo subprojektai
624 628

  
......
694 698
text_repository_usernames_mapping: "Select ou update the Redmine user mapped to each username found in the repository log.\nUsers with the same Redmine and repository username or email are automatically mapped."
695 699
permission_edit_own_messages: Edit own messages
696 700
permission_delete_won_messages: Delete own messages
701
button_add_subissue: Add sub-issue
lang/nl.yml
119 119
field_assigned_to: Toegewezen aan
120 120
field_priority: Prioriteit
121 121
field_fixed_version: Doel versie
122
field_parent_issue: Child of
122 123
field_user: Gebruiker
123 124
field_role: Rol
... This diff was truncated because it exceeds the maximum size that can be displayed.