subissues.diff

Sub-issues, v1.5 - Aleksei Gusev, 2008-12-09 11:08

Download (101 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, :before => true)
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
    @parent_issue = Issue.find( params[:issue_parent_issue_id]) unless params[:issue_parent_issue_id].blank?
227

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

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

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

  
431 548
  # Filter for bulk operations
432 549
  def find_issues
433 550
    @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.create do |relation|
204
        relation.issue_from = issue
205
        relation.issue_to = Issue.find(parent_id)
206
        relation.relation_type = IssueRelation::TYPE_PARENTS
207
        unless relation.save
208
          flash[:error] = "Can't set ##{parent_id} as parent for the issue."
209
        end
210
      end unless parent_id == '0'
211
    end    
212
  end
213
  
214
  def auto_complete_result_parent_issue(candidates, phrase)
215
    return "" if candidates.empty?
216
    candidates.map! do |c|
217
      content_tag("li", highlight( c.to_s, phrase), :id => String( c[:id]))
218
    end
219
    content_tag("ul", candidates.uniq)
220
  end
188 221
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
122

  
123
    unless children.empty?
124
      children_max_fixed_version = children.select { |i| i.fixed_version } .max { |a,b| a.fixed_version <=> b.fixed_version }
125
      if @attributes['fixed_version_id'] && children_max_fixed_version
126
        if Version.find_by_id( @attributes['fixed_version_id']) < children_max_fixed_version.fixed_version
127
          errors.add :fixed_version, "Can't set target version of parent issue lower than any of the children."
128
        end
129
      end
130
    end
123 131
  end
124 132
  
125 133
  def validate_on_create
......
140 148
        @current_journal.details << JournalDetail.new(:property => 'attr',
141 149
                                                      :prop_key => c,
142 150
                                                      :old_value => @issue_before_change.send(c),
143
                                                      :value => send(c)) unless send(c)==@issue_before_change.send(c)
151
                                                      :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 152
      }
145 153
      # custom fields changes
146 154
      custom_values.each {|c|
......
153 161
      }      
154 162
      @current_journal.save
155 163
    end
164

  
156 165
    # Save the issue even if the journal is not saved (because empty)
157 166
    true
158 167
  end
......
164 173
    # Update start/due dates of following issues
165 174
    relations_from.each(&:set_issue_to_dates)
166 175
    
176
    # Set default status of parent if new status openes the issue.
177
    relations_from.each do |relation|
178
      if relation.relation_type == IssueRelation::TYPE_PARENTS
179
        relation.set_issue_to_default_status
180
        relation.set_issue_to_target_version
181
      end
182
    end
183

  
167 184
    # Close duplicates if the issue was closed
168 185
    if @issue_before_change && !@issue_before_change.closed? && self.closed?
169 186
      duplicates.each do |duplicate|
......
177 194
      end
178 195
    end
179 196
  end
180
  
197

  
181 198
  def init_journal(user, notes = "")
182 199
    @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
183 200
    @issue_before_change = self.clone
......
189 206
    @current_journal
190 207
  end
191 208
  
209
  def priority_id=(pid)
210
    self.priority = nil
211
    write_attribute(:priority_id, pid)
212
  end
213

  
192 214
  # Return true if the issue is closed, otherwise false
193 215
  def closed?
194 216
    self.status.is_closed?
......
240 262
  # Returns the due date or the target due date if any
241 263
  # Used on gantt chart
242 264
  def due_before
243
    due_date || (fixed_version ? fixed_version.effective_date : nil)
265
    due_date || (fixed_version ? fixed_version.effective_date : 0)
266
  end
267
  
268
  def duration1
269
    (start_date && due_date) ? (due_date - start_date + 1) : 0
244 270
  end
245 271
  
246 272
  def duration
......
257 283
    end
258 284
  end
259 285
  
286
  def done_ratio
287
   if children?
288
     @total_planned_days ||= 0
289
     @total_actual_days ||= 0
290
     children.each do |child| # from every subtask get the total number of days and the number of days already "worked"
291
       planned_days = child.duration1
292
       actual_days = child.done_ratio ?  (planned_days * child.done_ratio / 100).floor : 0
293
       @total_planned_days += planned_days
294
       @total_actual_days += actual_days
295
     end
296
     @total_done_ratio = @total_planned_days != 0 ? (@total_actual_days * 100 / @total_planned_days).floor : 0
297
   else
298
     read_attribute(:done_ratio)
299
   end
300
  end
301
  
302
  def estimated_hours
303
    if children?
304
      is_set = false
305
      children.each do |child|
306
        if child.estimated_hours
307
          if is_set
308
            @est_hours += child.estimated_hours
309
          else
310
            @est_hours = child.estimated_hours
311
            is_set = true
312
          end
313
        end     
314
      end
315
      @est_hours
316
    else
317
      read_attribute(:estimated_hours)
318
    end
319
  end
320
  
321
  def start_date
322
    calculate 'start_date'
323
  end  
324
  
325
  def due_date
326
    calculate 'due_date'
327
  end  
328
  
329
  def calculate(field)
330
    if children?
331
      @value = eval "children.first.#{field}" 
332
      children.each do |child|        
333
        case field
334
          when 'start_date'
335
          if child.start_date && (!@value || @value > child.start_date)
336
            @value = child.start_date
337
          end
338
          when 'due_date'
339
          if child.due_date && (!@value || @value < child.due_date)
340
            @value = child.due_date
341
          end
342
        end        
343
      end
344
      @value
345
    else
346
      read_attribute(eval(":#{field}"))
347
    end
348
  end  
349

  
350
  def children?
351
    children != []
352
  end
353
  
354
  def children
355
    children = []
356
    relations_to.each do |relation|
357
      if relation.relation_type == IssueRelation::TYPE_PARENTS
358
        children << relation.other_issue(self)
359
      end
360
    end
361
    children
362
  end
363
  
364
  def parent
365
    relations_from.each do |relation|
366
      if relation.relation_type == IssueRelation::TYPE_PARENTS
367
        return parent = relation.other_issue(self)
368
      end
369
    end
370
    return nil
371
  end
372

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

  
401
  def orphan?
402
    root? && edge?
403
  end
404
  
405
  def self.find_with_parents( *args)
406
    issues = find( *args)
407
    return [] if issues.empty?
408
    issues.each do |i|
409
      while not i.root?
410
        issues += [ i.parent ]
411
        i = i.parent
412
      end
413
    end
414
    issues.uniq
415
  end
416
  
260 417
  def to_s
261 418
    "#{tracker} ##{id}: #{subject}"
262 419
  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

  
64
    # Set new status to parent if new status openes the issue.
65
    if TYPE_PARENTS == relation_type
66
      set_issue_to_default_status
67
      set_issue_to_target_version
68
    end
69

  
60 70
    set_issue_to_dates
61 71
  end
72

  
73
  def set_issue_to_default_status
74
    if issue_to.closed? && !issue_from.closed?
75
      issue_to.update_attribute :status, IssueStatus.default
76
    end
77
  end
78

  
79
  def set_issue_to_target_version
80
    if issue_to.fixed_version.nil? && issue_from.fixed_version or
81
        ( issue_to.fixed_version && issue_from.fixed_version and
82
          issue_to.fixed_version.project == issue_from.fixed_version.project and
83
          issue_to.fixed_version < issue_from.fixed_version )
84
      issue_to.update_attribute :fixed_version, issue_from.fixed_version
85
    end
86
  end
62 87
  
63 88
  def set_issue_to_dates
64 89
    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/_edit.rhtml
15 15
        <%= render :partial => (@edit_allowed ? 'form' : 'form_update'), :locals => {:f => f} %>
16 16
        </fieldset>
17 17
    <% end %>
18
    <% if authorize_for('timelog', 'edit') %>
18
    <% if authorize_for('timelog', 'edit') && @issue.edge? %>
19 19
        <fieldset class="tabular"><legend><%= l(:button_log_time) %></legend>
20 20
        <% fields_for :time_entry, @time_entry, { :builder => TabularFormBuilder, :lang => current_language} do |time_entry| %>
21 21
        <div class="splitcontentleft">
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),
......
35 44
</div>
36 45

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

  
44 73
<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>
8
<% else %>
9
<p><label><%= l(:field_done_ratio) %></label> <%= "#{@issue.done_ratio}%" %></p>
10
<% end %>
7 11
<%= content_tag('p', f.select(:fixed_version_id, 
8 12
                          (@project.versions.sort.collect {|v| [v.name, v.id]}),
9 13
                          { :include_blank => true })) unless @project.versions.empty? %>
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
15 15
	<li><%= context_menu_link l(:button_edit), {:controller => 'issues', :action => 'bulk_edit', :ids => @issues.collect(&:id)},
16 16
	        :class => 'icon-edit', :disabled => !@can[:edit] %></li>
17 17
<% end %>
18

  
19 18
	<li class="folder">			
20 19
		<a href="#" class="submenu"><%= l(:field_priority) %></a>
21 20
		<ul>
......
64 63
		</ul>
65 64
	</li>
66 65
	<% end -%>
66
	<% if @issue && @issue.edge? %>
67 67
	<li class="folder">
68 68
		<a href="#" class="submenu"><%= l(:field_done_ratio) %></a>
69 69
		<ul>
......
73 73
		<% end -%>
74 74
		</ul>
75 75
	</li>
76
	
76
	<% end %>
77 77
<% if !@issue.nil? %>
78 78
	<li><%= context_menu_link l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue},
79 79
	        :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 %>
app/views/versions/show.rhtml
31 31
<%= render :partial => 'versions/overview', :locals => {:version => @version} %>
32 32
<%= render(:partial => "wiki/content", :locals => {:content => @version.wiki_page.content}) if @version.wiki_page %>
33 33

  
34
<% issues = @version.fixed_issues.find(:all,
35
                                       :include => [:status, :tracker],
36
                                       :order => "#{Tracker.table_name}.position, #{Issue.table_name}.id") %>
37
<% if issues.size > 0 %>
34
<% if @issues.size > 0 %>
38 35
<fieldset class="related-issues"><legend><%= l(:label_related_issues) %></legend>
39
<ul>
40
<% issues.each do |issue| -%>
41
    <li><%= link_to_issue(issue) %>: <%=h issue.subject %></li>
42
<% end -%>
43
</ul>
36
<%= render_list_of_related_issues( @issues, @version) %>
44 37
</fieldset>
45 38
<% end %>
46 39
</div>
config/boot.rb
1 1
# Don't change this file!
2 2
# Configure your app in config/environment.rb and config/environments/*.rb
3 3

  
4
RAILS_ROOT = File.expand_path("#{File.dirname(__FILE__)}/..") unless defined?(RAILS_ROOT)
4
RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
5 5

  
6 6
module Rails
7 7
  class << self
......
82 82

  
83 83
      def load_rubygems
84 84
        require 'rubygems'
85

  
86
        unless rubygems_version >= '0.9.4'
87
          $stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.)
85
        min_version = '1.1.1'
86
        unless rubygems_version >= min_version
87
          $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
88 88
          exit 1
89 89
        end
90 90

  
91 91
      rescue LoadError
92
        $stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org)
92
        $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
93 93
        exit 1
94 94
      end
95 95

  
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_own_messages: Delete own messages
699

  
700
button_add_subissue: Add sub-issue
701
field_calendar_firstday: First day of week
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_own_messages: Delete own messages
696

  
697
button_add_subissue: Add sub-issue
698
label_parents: child of
699
label_children: parent of
700
field_parent_issue: Child of
701
error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent).
702
field_calendar_firstday: First day of week
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_own_messages: Delete own messages
704

  
705
button_add_subissue: Add sub-issue
706
field_calendar_firstday: First day of week
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_own_messages: Delete own messages
700

  
701
button_add_subissue: Add sub-issue
702
field_calendar_firstday: First day of week
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
704
field_calendar_firstday: First day of week
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
......
184 185
field_default_value: Default value
185 186
field_comments_sorting: Display comments
186 187
field_parent_title: Parent page
187

  
188
field_calendar_firstday: First day of week
189
  
188 190
setting_app_title: Application title
189 191
setting_app_subtitle: Application subtitle
190 192
setting_welcome_text: Welcome text
......
514 516
label_blocked_by: blocked by
515 517
label_precedes: precedes
516 518
label_follows: follows
519
label_parents: child of
520
label_children: parent of
517 521
label_end_to_start: end to start
518 522
label_end_to_end: end to end
519 523
label_start_to_start: start to start
......
590 594
button_test: Test
591 595
button_edit: Edit
592 596
button_add: Add
597
button_add_subissue: Add sub-issue
593 598
button_change: Change
594 599
button_apply: Apply
595 600
button_clear: Clear
......
627 632
text_regexp_info: eg. ^[A-Z0-9]+$
628 633
text_min_max_length_info: 0 means no restriction
629 634
text_project_destroy_confirmation: Are you sure you want to delete this project and related data ?
635
error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent).
630 636
text_subprojects_destroy_warning: 'Its subproject(s): %s will be also deleted.'
631 637
text_workflow_edit: Select a role and a tracker to edit the workflow
632 638
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
688
field_calendar_firstday: First day of week
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_own_messages: Delete own messages
695

  
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).
701
field_calendar_firstday: First day of week
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
702
field_calendar_firstday: First day of week
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_own_messages: Delete own messages
697

  
698
button_add_subissue: Add sub-issue
699
label_parents: child of
700
label_children: parent of
701
field_calendar_firstday: First day of week
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_own_messages: Saját üzenetek törlése
696

  
697
button_add_subissue: Add sub-issue
698
label_parents: child of
699
label_children: parent of
700
field_parent_issue: Child of
701
error_issue_can_have_only_one_parent: allows only one relation (a task can have only one parent).
702
field_calendar_firstday: First day of week
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_own_messages: Delete own messages
699

  
700
button_add_subissue: Add sub-issue
701
field_calendar_firstday: First day of week
... This diff was truncated because it exceeds the maximum size that can be displayed.