##// END OF EJS Templates
Added the hability to copy an issue....
Jean-Philippe Lang -
r860:0af6f347580d
parent child
Show More
1 NO CONTENT: new file 100644, binary diff hidden
@@ -1,216 +1,217
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class IssuesController < ApplicationController
19 19 layout 'base', :except => :export_pdf
20 20 before_filter :find_project, :authorize, :except => [:index, :preview]
21 21 accept_key_auth :index
22 22
23 23 cache_sweeper :issue_sweeper, :only => [ :edit, :change_status, :destroy ]
24 24
25 25 helper :projects
26 26 include ProjectsHelper
27 27 helper :custom_fields
28 28 include CustomFieldsHelper
29 29 helper :ifpdf
30 30 include IfpdfHelper
31 31 helper :issue_relations
32 32 include IssueRelationsHelper
33 33 helper :watchers
34 34 include WatchersHelper
35 35 helper :attachments
36 36 include AttachmentsHelper
37 37 helper :queries
38 38 helper :sort
39 39 include SortHelper
40 40
41 41 def index
42 42 sort_init "#{Issue.table_name}.id", "desc"
43 43 sort_update
44 44 retrieve_query
45 45 if @query.valid?
46 46 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
47 47 @issue_pages = Paginator.new self, @issue_count, 25, params['page']
48 48 @issues = Issue.find :all, :order => sort_clause,
49 49 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category ],
50 50 :conditions => @query.statement,
51 51 :limit => @issue_pages.items_per_page,
52 52 :offset => @issue_pages.current.offset
53 53 end
54 54 respond_to do |format|
55 55 format.html { render :layout => false if request.xhr? }
56 56 format.atom { render_feed(@issues, :title => l(:label_issue_plural)) }
57 57 end
58 58 end
59 59
60 60 def show
61 61 @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
62 62 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "#{Journal.table_name}.created_on ASC")
63 63
64 64 if params[:format]=='pdf'
65 65 @options_for_rfpdf ||= {}
66 66 @options_for_rfpdf[:file_name] = "#{@project.identifier}-#{@issue.id}.pdf"
67 67 render :template => 'issues/show.rfpdf', :layout => false
68 68 else
69 69 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
70 70 render :template => 'issues/show.rhtml'
71 71 end
72 72 end
73 73
74 74 def edit
75 75 @priorities = Enumeration::get_values('IPRI')
76 76 if request.get?
77 77 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| @issue.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x, :customized => @issue) }
78 78 else
79 79 begin
80 80 @issue.init_journal(self.logged_in_user)
81 81 # Retrieve custom fields and values
82 82 if params["custom_fields"]
83 83 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
84 84 @issue.custom_values = @custom_values
85 85 end
86 86 @issue.attributes = params[:issue]
87 87 if @issue.save
88 88 flash[:notice] = l(:notice_successful_update)
89 89 redirect_to(params[:back_to] || {:action => 'show', :id => @issue})
90 90 end
91 91 rescue ActiveRecord::StaleObjectError
92 92 # Optimistic locking exception
93 93 flash[:error] = l(:notice_locking_conflict)
94 94 end
95 95 end
96 96 end
97 97
98 98 def add_note
99 99 journal = @issue.init_journal(User.current, params[:notes])
100 100 params[:attachments].each { |file|
101 101 next unless file.size > 0
102 102 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
103 103 journal.details << JournalDetail.new(:property => 'attachment',
104 104 :prop_key => a.id,
105 105 :value => a.filename) unless a.new_record?
106 106 } if params[:attachments] and params[:attachments].is_a? Array
107 107 if journal.save
108 108 flash[:notice] = l(:notice_successful_update)
109 109 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
110 110 redirect_to :action => 'show', :id => @issue
111 111 return
112 112 end
113 113 show
114 114 end
115 115
116 116 def change_status
117 117 @status_options = @issue.status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker) if logged_in_user
118 118 @new_status = IssueStatus.find(params[:new_status_id])
119 119 if params[:confirm]
120 120 begin
121 121 journal = @issue.init_journal(self.logged_in_user, params[:notes])
122 122 @issue.status = @new_status
123 123 if @issue.update_attributes(params[:issue])
124 124 # Save attachments
125 125 params[:attachments].each { |file|
126 126 next unless file.size > 0
127 127 a = Attachment.create(:container => @issue, :file => file, :author => logged_in_user)
128 128 journal.details << JournalDetail.new(:property => 'attachment',
129 129 :prop_key => a.id,
130 130 :value => a.filename) unless a.new_record?
131 131 } if params[:attachments] and params[:attachments].is_a? Array
132 132
133 133 # Log time
134 134 if current_role.allowed_to?(:log_time)
135 135 @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => logged_in_user, :spent_on => Date.today)
136 136 @time_entry.attributes = params[:time_entry]
137 137 @time_entry.save
138 138 end
139 139
140 140 flash[:notice] = l(:notice_successful_update)
141 141 Mailer.deliver_issue_edit(journal) if Setting.notified_events.include?('issue_updated')
142 142 redirect_to :action => 'show', :id => @issue
143 143 end
144 144 rescue ActiveRecord::StaleObjectError
145 145 # Optimistic locking exception
146 146 flash[:error] = l(:notice_locking_conflict)
147 147 end
148 148 end
149 149 @assignable_to = @project.members.find(:all, :include => :user).collect{ |m| m.user }
150 150 @activities = Enumeration::get_values('ACTI')
151 151 end
152 152
153 153 def destroy
154 154 @issue.destroy
155 155 redirect_to :controller => 'projects', :action => 'list_issues', :id => @project
156 156 end
157 157
158 158 def destroy_attachment
159 159 a = @issue.attachments.find(params[:attachment_id])
160 160 a.destroy
161 161 journal = @issue.init_journal(self.logged_in_user)
162 162 journal.details << JournalDetail.new(:property => 'attachment',
163 163 :prop_key => a.id,
164 164 :old_value => a.filename)
165 165 journal.save
166 166 redirect_to :action => 'show', :id => @issue
167 167 end
168 168
169 169 def context_menu
170 170 @priorities = Enumeration.get_values('IPRI').reverse
171 171 @statuses = IssueStatus.find(:all, :order => 'position')
172 172 @allowed_statuses = @issue.status.find_new_statuses_allowed_to(User.current.role_for_project(@project), @issue.tracker)
173 173 @assignables = @issue.assignable_users
174 174 @assignables << @issue.assigned_to if @issue.assigned_to && !@assignables.include?(@issue.assigned_to)
175 175 @can = {:edit => User.current.allowed_to?(:edit_issues, @project),
176 176 :change_status => User.current.allowed_to?(:change_issue_status, @project),
177 :add => User.current.allowed_to?(:add_issues, @project),
177 178 :move => User.current.allowed_to?(:move_issues, @project),
178 179 :delete => User.current.allowed_to?(:delete_issues, @project)}
179 180 render :layout => false
180 181 end
181 182
182 183 def preview
183 184 issue = Issue.find_by_id(params[:id])
184 185 @attachements = issue.attachments if issue
185 186 @text = params[:issue][:description]
186 187 render :partial => 'common/preview'
187 188 end
188 189
189 190 private
190 191 def find_project
191 192 @issue = Issue.find(params[:id], :include => [:project, :tracker, :status, :author, :priority, :category])
192 193 @project = @issue.project
193 194 rescue ActiveRecord::RecordNotFound
194 195 render_404
195 196 end
196 197
197 198 # Retrieve query from session or build a new query
198 199 def retrieve_query
199 200 if params[:set_filter] or !session[:query] or session[:query].project_id
200 201 # Give it a name, required to be valid
201 202 @query = Query.new(:name => "_", :executed_by => logged_in_user)
202 203 if params[:fields] and params[:fields].is_a? Array
203 204 params[:fields].each do |field|
204 205 @query.add_filter(field, params[:operators][field], params[:values][field])
205 206 end
206 207 else
207 208 @query.available_filters.keys.each do |field|
208 209 @query.add_short_filter(field, params[field]) if params[field]
209 210 end
210 211 end
211 212 session[:query] = @query
212 213 else
213 214 @query = session[:query]
214 215 end
215 216 end
216 217 end
@@ -1,675 +1,677
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'csv'
19 19
20 20 class ProjectsController < ApplicationController
21 21 layout 'base'
22 22 before_filter :find_project, :except => [ :index, :list, :add ]
23 23 before_filter :authorize, :except => [ :index, :list, :add, :archive, :unarchive, :destroy ]
24 24 before_filter :require_admin, :only => [ :add, :archive, :unarchive, :destroy ]
25 25 accept_key_auth :activity, :calendar
26 26
27 27 cache_sweeper :project_sweeper, :only => [ :add, :edit, :archive, :unarchive, :destroy ]
28 28 cache_sweeper :issue_sweeper, :only => [ :add_issue ]
29 29 cache_sweeper :version_sweeper, :only => [ :add_version ]
30 30
31 31 helper :sort
32 32 include SortHelper
33 33 helper :custom_fields
34 34 include CustomFieldsHelper
35 35 helper :ifpdf
36 36 include IfpdfHelper
37 37 helper :issues
38 38 helper IssuesHelper
39 39 helper :queries
40 40 include QueriesHelper
41 41 helper :repositories
42 42 include RepositoriesHelper
43 43 include ProjectsHelper
44 44
45 45 def index
46 46 list
47 47 render :action => 'list' unless request.xhr?
48 48 end
49 49
50 50 # Lists visible projects
51 51 def list
52 52 projects = Project.find :all,
53 53 :conditions => Project.visible_by(logged_in_user),
54 54 :include => :parent
55 55 @project_tree = projects.group_by {|p| p.parent || p}
56 56 @project_tree.each_key {|p| @project_tree[p] -= [p]}
57 57 end
58 58
59 59 # Add a new project
60 60 def add
61 61 @custom_fields = IssueCustomField.find(:all)
62 62 @root_projects = Project.find(:all, :conditions => "parent_id IS NULL AND status = #{Project::STATUS_ACTIVE}")
63 63 @project = Project.new(params[:project])
64 64 @project.enabled_module_names = Redmine::AccessControl.available_project_modules
65 65 if request.get?
66 66 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project) }
67 67 else
68 68 @project.custom_fields = CustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
69 69 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => (params[:custom_fields] ? params["custom_fields"][x.id.to_s] : nil)) }
70 70 @project.custom_values = @custom_values
71 71 if @project.save
72 72 @project.enabled_module_names = params[:enabled_modules]
73 73 flash[:notice] = l(:notice_successful_create)
74 74 redirect_to :controller => 'admin', :action => 'projects'
75 75 end
76 76 end
77 77 end
78 78
79 79 # Show @project
80 80 def show
81 81 @custom_values = @project.custom_values.find(:all, :include => :custom_field)
82 82 @members_by_role = @project.members.find(:all, :include => [:user, :role], :order => 'position').group_by {|m| m.role}
83 83 @subprojects = @project.active_children
84 84 @news = @project.news.find(:all, :limit => 5, :include => [ :author, :project ], :order => "#{News.table_name}.created_on DESC")
85 85 @trackers = Tracker.find(:all, :order => 'position')
86 86 @open_issues_by_tracker = Issue.count(:group => :tracker, :joins => "INNER JOIN #{IssueStatus.table_name} ON #{IssueStatus.table_name}.id = #{Issue.table_name}.status_id", :conditions => ["project_id=? and #{IssueStatus.table_name}.is_closed=?", @project.id, false])
87 87 @total_issues_by_tracker = Issue.count(:group => :tracker, :conditions => ["project_id=?", @project.id])
88 88 @total_hours = @project.time_entries.sum(:hours)
89 89 @key = User.current.rss_key
90 90 end
91 91
92 92 def settings
93 93 @root_projects = Project::find(:all, :conditions => ["parent_id IS NULL AND status = #{Project::STATUS_ACTIVE} AND id <> ?", @project.id])
94 94 @custom_fields = IssueCustomField.find(:all)
95 95 @issue_category ||= IssueCategory.new
96 96 @member ||= @project.members.new
97 97 @custom_values ||= ProjectCustomField.find(:all).collect { |x| @project.custom_values.find_by_custom_field_id(x.id) || CustomValue.new(:custom_field => x) }
98 98 @repository ||= @project.repository
99 99 @wiki ||= @project.wiki
100 100 end
101 101
102 102 # Edit @project
103 103 def edit
104 104 if request.post?
105 105 @project.custom_fields = IssueCustomField.find(params[:custom_field_ids]) if params[:custom_field_ids]
106 106 if params[:custom_fields]
107 107 @custom_values = ProjectCustomField.find(:all).collect { |x| CustomValue.new(:custom_field => x, :customized => @project, :value => params["custom_fields"][x.id.to_s]) }
108 108 @project.custom_values = @custom_values
109 109 end
110 110 @project.attributes = params[:project]
111 111 if @project.save
112 112 flash[:notice] = l(:notice_successful_update)
113 113 redirect_to :action => 'settings', :id => @project
114 114 else
115 115 settings
116 116 render :action => 'settings'
117 117 end
118 118 end
119 119 end
120 120
121 121 def modules
122 122 @project.enabled_module_names = params[:enabled_modules]
123 123 redirect_to :action => 'settings', :id => @project, :tab => 'modules'
124 124 end
125 125
126 126 def archive
127 127 @project.archive if request.post? && @project.active?
128 128 redirect_to :controller => 'admin', :action => 'projects'
129 129 end
130 130
131 131 def unarchive
132 132 @project.unarchive if request.post? && !@project.active?
133 133 redirect_to :controller => 'admin', :action => 'projects'
134 134 end
135 135
136 136 # Delete @project
137 137 def destroy
138 138 @project_to_destroy = @project
139 139 if request.post? and params[:confirm]
140 140 @project_to_destroy.destroy
141 141 redirect_to :controller => 'admin', :action => 'projects'
142 142 end
143 143 # hide project in layout
144 144 @project = nil
145 145 end
146 146
147 147 # Add a new issue category to @project
148 148 def add_issue_category
149 149 @category = @project.issue_categories.build(params[:category])
150 150 if request.post? and @category.save
151 151 respond_to do |format|
152 152 format.html do
153 153 flash[:notice] = l(:notice_successful_create)
154 154 redirect_to :action => 'settings', :tab => 'categories', :id => @project
155 155 end
156 156 format.js do
157 157 # IE doesn't support the replace_html rjs method for select box options
158 158 render(:update) {|page| page.replace "issue_category_id",
159 159 content_tag('select', '<option></option>' + options_from_collection_for_select(@project.issue_categories, 'id', 'name', @category.id), :id => 'issue_category_id', :name => 'issue[category_id]')
160 160 }
161 161 end
162 162 end
163 163 end
164 164 end
165 165
166 166 # Add a new version to @project
167 167 def add_version
168 168 @version = @project.versions.build(params[:version])
169 169 if request.post? and @version.save
170 170 flash[:notice] = l(:notice_successful_create)
171 171 redirect_to :action => 'settings', :tab => 'versions', :id => @project
172 172 end
173 173 end
174 174
175 175 # Add a new document to @project
176 176 def add_document
177 177 @document = @project.documents.build(params[:document])
178 178 if request.post? and @document.save
179 179 # Save the attachments
180 180 params[:attachments].each { |a|
181 181 Attachment.create(:container => @document, :file => a, :author => logged_in_user) unless a.size == 0
182 182 } if params[:attachments] and params[:attachments].is_a? Array
183 183 flash[:notice] = l(:notice_successful_create)
184 184 Mailer.deliver_document_added(@document) if Setting.notified_events.include?('document_added')
185 185 redirect_to :action => 'list_documents', :id => @project
186 186 end
187 187 end
188 188
189 189 # Show documents list of @project
190 190 def list_documents
191 191 @documents = @project.documents.find :all, :include => :category
192 192 end
193 193
194 194 # Add a new issue to @project
195 # The new issue will be created from an existing one if copy_from parameter is given
195 196 def add_issue
196 @tracker = Tracker.find(params[:tracker_id])
197 @priorities = Enumeration::get_values('IPRI')
197 @issue = params[:copy_from] ? Issue.new.copy_from(params[:copy_from]) : Issue.new(params[:issue])
198 @issue.project = @project
199 @issue.author = User.current
200 @issue.tracker ||= Tracker.find(params[:tracker_id])
198 201
199 202 default_status = IssueStatus.default
200 203 unless default_status
201 flash.now[:error] = 'No default issue status defined. Please check your configuration.'
204 flash.now[:error] = 'No default issue status is defined. Please check your configuration (Go to "Administration -> Issue statuses").'
202 205 render :nothing => true, :layout => true
203 206 return
204 207 end
205 @issue = Issue.new(:project => @project, :tracker => @tracker)
206 208 @issue.status = default_status
207 209 @allowed_statuses = ([default_status] + default_status.find_new_statuses_allowed_to(logged_in_user.role_for_project(@project), @issue.tracker))if logged_in_user
210
208 211 if request.get?
209 @issue.start_date = Date.today
210 @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) }
212 @issue.start_date ||= Date.today
213 @custom_values = @issue.custom_values.empty? ?
214 @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue) } :
215 @issue.custom_values
211 216 else
212 @issue.attributes = params[:issue]
213
214 217 requested_status = IssueStatus.find_by_id(params[:issue][:status_id])
218 # Check that the user is allowed to apply the requested status
215 219 @issue.status = (@allowed_statuses.include? requested_status) ? requested_status : default_status
216
217 @issue.author_id = self.logged_in_user.id if self.logged_in_user
218 # Multiple file upload
219 @attachments = []
220 params[:attachments].each { |a|
221 @attachments << Attachment.new(:container => @issue, :file => a, :author => logged_in_user) unless a.size == 0
222 } if params[:attachments] and params[:attachments].is_a? Array
223 @custom_values = @project.custom_fields_for_issues(@tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
220 @custom_values = @project.custom_fields_for_issues(@issue.tracker).collect { |x| CustomValue.new(:custom_field => x, :customized => @issue, :value => params["custom_fields"][x.id.to_s]) }
224 221 @issue.custom_values = @custom_values
225 222 if @issue.save
226 @attachments.each(&:save)
223 if params[:attachments] && params[:attachments].is_a?(Array)
224 # Save attachments
225 params[:attachments].each {|a| Attachment.create(:container => @issue, :file => a, :author => User.current) unless a.size == 0}
226 end
227 227 flash[:notice] = l(:notice_successful_create)
228 228 Mailer.deliver_issue_add(@issue) if Setting.notified_events.include?('issue_added')
229 229 redirect_to :action => 'list_issues', :id => @project
230 return
230 231 end
231 232 end
233 @priorities = Enumeration::get_values('IPRI')
232 234 end
233 235
234 236 # Show filtered/sorted issues list of @project
235 237 def list_issues
236 238 sort_init "#{Issue.table_name}.id", "desc"
237 239 sort_update
238 240
239 241 retrieve_query
240 242
241 243 @results_per_page_options = [ 15, 25, 50, 100 ]
242 244 if params[:per_page] and @results_per_page_options.include? params[:per_page].to_i
243 245 @results_per_page = params[:per_page].to_i
244 246 session[:results_per_page] = @results_per_page
245 247 else
246 248 @results_per_page = session[:results_per_page] || 25
247 249 end
248 250
249 251 if @query.valid?
250 252 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
251 253 @issue_pages = Paginator.new self, @issue_count, @results_per_page, params['page']
252 254 @issues = Issue.find :all, :order => sort_clause,
253 255 :include => [ :assigned_to, :status, :tracker, :project, :priority, :category ],
254 256 :conditions => @query.statement,
255 257 :limit => @issue_pages.items_per_page,
256 258 :offset => @issue_pages.current.offset
257 259 end
258 260
259 261 render :layout => false if request.xhr?
260 262 end
261 263
262 264 # Export filtered/sorted issues list to CSV
263 265 def export_issues_csv
264 266 sort_init "#{Issue.table_name}.id", "desc"
265 267 sort_update
266 268
267 269 retrieve_query
268 270 render :action => 'list_issues' and return unless @query.valid?
269 271
270 272 @issues = Issue.find :all, :order => sort_clause,
271 273 :include => [ :assigned_to, :author, :status, :tracker, :priority, :project, {:custom_values => :custom_field} ],
272 274 :conditions => @query.statement,
273 275 :limit => Setting.issues_export_limit.to_i
274 276
275 277 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
276 278 export = StringIO.new
277 279 CSV::Writer.generate(export, l(:general_csv_separator)) do |csv|
278 280 # csv header fields
279 281 headers = [ "#", l(:field_status),
280 282 l(:field_project),
281 283 l(:field_tracker),
282 284 l(:field_priority),
283 285 l(:field_subject),
284 286 l(:field_assigned_to),
285 287 l(:field_author),
286 288 l(:field_start_date),
287 289 l(:field_due_date),
288 290 l(:field_done_ratio),
289 291 l(:field_created_on),
290 292 l(:field_updated_on)
291 293 ]
292 294 for custom_field in @project.all_custom_fields
293 295 headers << custom_field.name
294 296 end
295 297 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
296 298 # csv lines
297 299 @issues.each do |issue|
298 300 fields = [issue.id, issue.status.name,
299 301 issue.project.name,
300 302 issue.tracker.name,
301 303 issue.priority.name,
302 304 issue.subject,
303 305 (issue.assigned_to ? issue.assigned_to.name : ""),
304 306 issue.author.name,
305 307 issue.start_date ? l_date(issue.start_date) : nil,
306 308 issue.due_date ? l_date(issue.due_date) : nil,
307 309 issue.done_ratio,
308 310 l_datetime(issue.created_on),
309 311 l_datetime(issue.updated_on)
310 312 ]
311 313 for custom_field in @project.all_custom_fields
312 314 fields << (show_value issue.custom_value_for(custom_field))
313 315 end
314 316 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
315 317 end
316 318 end
317 319 export.rewind
318 320 send_data(export.read, :type => 'text/csv; header=present', :filename => 'export.csv')
319 321 end
320 322
321 323 # Export filtered/sorted issues to PDF
322 324 def export_issues_pdf
323 325 sort_init "#{Issue.table_name}.id", "desc"
324 326 sort_update
325 327
326 328 retrieve_query
327 329 render :action => 'list_issues' and return unless @query.valid?
328 330
329 331 @issues = Issue.find :all, :order => sort_clause,
330 332 :include => [ :author, :status, :tracker, :priority, :project ],
331 333 :conditions => @query.statement,
332 334 :limit => Setting.issues_export_limit.to_i
333 335
334 336 @options_for_rfpdf ||= {}
335 337 @options_for_rfpdf[:file_name] = "export.pdf"
336 338 render :layout => false
337 339 end
338 340
339 341 # Bulk edit issues
340 342 def bulk_edit_issues
341 343 if request.post?
342 344 status = params[:status_id].blank? ? nil : IssueStatus.find_by_id(params[:status_id])
343 345 priority = params[:priority_id].blank? ? nil : Enumeration.find_by_id(params[:priority_id])
344 346 assigned_to = params[:assigned_to_id].blank? ? nil : User.find_by_id(params[:assigned_to_id])
345 347 category = params[:category_id].blank? ? nil : @project.issue_categories.find_by_id(params[:category_id])
346 348 fixed_version = params[:fixed_version_id].blank? ? nil : @project.versions.find_by_id(params[:fixed_version_id])
347 349 issues = @project.issues.find_all_by_id(params[:issue_ids])
348 350 unsaved_issue_ids = []
349 351 issues.each do |issue|
350 352 journal = issue.init_journal(User.current, params[:notes])
351 353 issue.priority = priority if priority
352 354 issue.assigned_to = assigned_to if assigned_to || params[:assigned_to_id] == 'none'
353 355 issue.category = category if category
354 356 issue.fixed_version = fixed_version if fixed_version
355 357 issue.start_date = params[:start_date] unless params[:start_date].blank?
356 358 issue.due_date = params[:due_date] unless params[:due_date].blank?
357 359 issue.done_ratio = params[:done_ratio] unless params[:done_ratio].blank?
358 360 # Don't save any change to the issue if the user is not authorized to apply the requested status
359 361 if (status.nil? || (issue.status.new_status_allowed_to?(status, current_role, issue.tracker) && issue.status = status)) && issue.save
360 362 # Send notification for each issue (if changed)
361 363 Mailer.deliver_issue_edit(journal) if journal.details.any? && Setting.notified_events.include?('issue_updated')
362 364 else
363 365 # Keep unsaved issue ids to display them in flash error
364 366 unsaved_issue_ids << issue.id
365 367 end
366 368 end
367 369 if unsaved_issue_ids.empty?
368 370 flash[:notice] = l(:notice_successful_update) unless issues.empty?
369 371 else
370 372 flash[:error] = l(:notice_failed_to_save_issues, unsaved_issue_ids.size, issues.size, '#' + unsaved_issue_ids.join(', #'))
371 373 end
372 374 redirect_to :action => 'list_issues', :id => @project
373 375 return
374 376 end
375 377 if current_role && User.current.allowed_to?(:change_issue_status, @project)
376 378 # Find potential statuses the user could be allowed to switch issues to
377 379 @available_statuses = Workflow.find(:all, :include => :new_status,
378 380 :conditions => {:role_id => current_role.id}).collect(&:new_status).compact.uniq
379 381 end
380 382 render :update do |page|
381 383 page.hide 'query_form'
382 384 page.replace_html 'bulk-edit', :partial => 'issues/bulk_edit_form'
383 385 end
384 386 end
385 387
386 388 def move_issues
387 389 @issues = @project.issues.find(params[:issue_ids]) if params[:issue_ids]
388 390 redirect_to :action => 'list_issues', :id => @project and return unless @issues
389 391 @projects = []
390 392 # find projects to which the user is allowed to move the issue
391 393 User.current.memberships.each {|m| @projects << m.project if m.role.allowed_to?(:controller => 'projects', :action => 'move_issues')}
392 394 # issue can be moved to any tracker
393 395 @trackers = Tracker.find(:all)
394 396 if request.post? and params[:new_project_id] and params[:new_tracker_id]
395 397 new_project = Project.find_by_id(params[:new_project_id])
396 398 new_tracker = Tracker.find_by_id(params[:new_tracker_id])
397 399 @issues.each do |i|
398 400 if new_project && i.project_id != new_project.id
399 401 # issue is moved to another project
400 402 i.category = nil
401 403 i.fixed_version = nil
402 404 # delete issue relations
403 405 i.relations_from.clear
404 406 i.relations_to.clear
405 407 i.project = new_project
406 408 end
407 409 if new_tracker
408 410 i.tracker = new_tracker
409 411 end
410 412 i.save
411 413 end
412 414 flash[:notice] = l(:notice_successful_update)
413 415 redirect_to :action => 'list_issues', :id => @project
414 416 end
415 417 end
416 418
417 419 # Add a news to @project
418 420 def add_news
419 421 @news = News.new(:project => @project)
420 422 if request.post?
421 423 @news.attributes = params[:news]
422 424 @news.author_id = self.logged_in_user.id if self.logged_in_user
423 425 if @news.save
424 426 flash[:notice] = l(:notice_successful_create)
425 427 Mailer.deliver_news_added(@news) if Setting.notified_events.include?('news_added')
426 428 redirect_to :action => 'list_news', :id => @project
427 429 end
428 430 end
429 431 end
430 432
431 433 # Show news list of @project
432 434 def list_news
433 435 @news_pages, @newss = paginate :news, :per_page => 10, :conditions => ["project_id=?", @project.id], :include => :author, :order => "#{News.table_name}.created_on DESC"
434 436
435 437 respond_to do |format|
436 438 format.html { render :layout => false if request.xhr? }
437 439 format.atom { render_feed(@newss, :title => "#{@project.name}: #{l(:label_news_plural)}") }
438 440 end
439 441 end
440 442
441 443 def add_file
442 444 if request.post?
443 445 @version = @project.versions.find_by_id(params[:version_id])
444 446 # Save the attachments
445 447 @attachments = []
446 448 params[:attachments].each { |file|
447 449 next unless file.size > 0
448 450 a = Attachment.create(:container => @version, :file => file, :author => logged_in_user)
449 451 @attachments << a unless a.new_record?
450 452 } if params[:attachments] and params[:attachments].is_a? Array
451 453 Mailer.deliver_attachments_added(@attachments) if !@attachments.empty? && Setting.notified_events.include?('file_added')
452 454 redirect_to :controller => 'projects', :action => 'list_files', :id => @project
453 455 end
454 456 @versions = @project.versions.sort
455 457 end
456 458
457 459 def list_files
458 460 @versions = @project.versions.sort
459 461 end
460 462
461 463 # Show changelog for @project
462 464 def changelog
463 465 @trackers = Tracker.find(:all, :conditions => ["is_in_chlog=?", true], :order => 'position')
464 466 retrieve_selected_tracker_ids(@trackers)
465 467 @versions = @project.versions.sort
466 468 end
467 469
468 470 def roadmap
469 471 @trackers = Tracker.find(:all, :conditions => ["is_in_roadmap=?", true], :order => 'position')
470 472 retrieve_selected_tracker_ids(@trackers)
471 473 @versions = @project.versions.sort
472 474 @versions = @versions.select {|v| !v.completed? } unless params[:completed]
473 475 end
474 476
475 477 def activity
476 478 if params[:year] and params[:year].to_i > 1900
477 479 @year = params[:year].to_i
478 480 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
479 481 @month = params[:month].to_i
480 482 end
481 483 end
482 484 @year ||= Date.today.year
483 485 @month ||= Date.today.month
484 486
485 487 case params[:format]
486 488 when 'atom'
487 489 # 30 last days
488 490 @date_from = Date.today - 30
489 491 @date_to = Date.today + 1
490 492 else
491 493 # current month
492 494 @date_from = Date.civil(@year, @month, 1)
493 495 @date_to = @date_from >> 1
494 496 end
495 497
496 498 @event_types = %w(issues news files documents wiki_pages changesets)
497 499 @event_types.delete('wiki_pages') unless @project.wiki
498 500 @event_types.delete('changesets') unless @project.repository
499 501 # only show what the user is allowed to view
500 502 @event_types = @event_types.select {|o| User.current.allowed_to?("view_#{o}".to_sym, @project)}
501 503
502 504 @scope = @event_types.select {|t| params["show_#{t}"]}
503 505 # default events if none is specified in parameters
504 506 @scope = (@event_types - %w(wiki_pages))if @scope.empty?
505 507
506 508 @events = []
507 509
508 510 if @scope.include?('issues')
509 511 @events += @project.issues.find(:all, :include => [:author, :tracker], :conditions => ["#{Issue.table_name}.created_on>=? and #{Issue.table_name}.created_on<=?", @date_from, @date_to] )
510 512 end
511 513
512 514 if @scope.include?('news')
513 515 @events += @project.news.find(:all, :conditions => ["#{News.table_name}.created_on>=? and #{News.table_name}.created_on<=?", @date_from, @date_to], :include => :author )
514 516 end
515 517
516 518 if @scope.include?('files')
517 519 @events += Attachment.find(:all, :select => "#{Attachment.table_name}.*", :joins => "LEFT JOIN #{Version.table_name} ON #{Version.table_name}.id = #{Attachment.table_name}.container_id", :conditions => ["#{Attachment.table_name}.container_type='Version' and #{Version.table_name}.project_id=? and #{Attachment.table_name}.created_on>=? and #{Attachment.table_name}.created_on<=?", @project.id, @date_from, @date_to], :include => :author )
518 520 end
519 521
520 522 if @scope.include?('documents')
521 523 @events += @project.documents.find(:all, :conditions => ["#{Document.table_name}.created_on>=? and #{Document.table_name}.created_on<=?", @date_from, @date_to] )
522 524 @events += Attachment.find(:all, :select => "attachments.*", :joins => "LEFT JOIN #{Document.table_name} ON #{Document.table_name}.id = #{Attachment.table_name}.container_id", :conditions => ["#{Attachment.table_name}.container_type='Document' and #{Document.table_name}.project_id=? and #{Attachment.table_name}.created_on>=? and #{Attachment.table_name}.created_on<=?", @project.id, @date_from, @date_to], :include => :author )
523 525 end
524 526
525 527 if @scope.include?('wiki_pages')
526 528 select = "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " +
527 529 "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " +
528 530 "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " +
529 531 "#{WikiContent.versioned_table_name}.id"
530 532 joins = "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " +
531 533 "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id "
532 534 conditions = ["#{Wiki.table_name}.project_id = ? AND #{WikiContent.versioned_table_name}.updated_on BETWEEN ? AND ?",
533 535 @project.id, @date_from, @date_to]
534 536
535 537 @events += WikiContent.versioned_class.find(:all, :select => select, :joins => joins, :conditions => conditions)
536 538 end
537 539
538 540 if @scope.include?('changesets')
539 541 @events += @project.repository.changesets.find(:all, :conditions => ["#{Changeset.table_name}.committed_on BETWEEN ? AND ?", @date_from, @date_to])
540 542 end
541 543
542 544 @events_by_day = @events.group_by(&:event_date)
543 545
544 546 respond_to do |format|
545 547 format.html { render :layout => false if request.xhr? }
546 548 format.atom { render_feed(@events, :title => "#{@project.name}: #{l(:label_activity)}") }
547 549 end
548 550 end
549 551
550 552 def calendar
551 553 @trackers = Tracker.find(:all, :order => 'position')
552 554 retrieve_selected_tracker_ids(@trackers)
553 555
554 556 if params[:year] and params[:year].to_i > 1900
555 557 @year = params[:year].to_i
556 558 if params[:month] and params[:month].to_i > 0 and params[:month].to_i < 13
557 559 @month = params[:month].to_i
558 560 end
559 561 end
560 562 @year ||= Date.today.year
561 563 @month ||= Date.today.month
562 564 @calendar = Redmine::Helpers::Calendar.new(Date.civil(@year, @month, 1), current_language, :month)
563 565
564 566 events = []
565 567 @project.issues_with_subprojects(params[:with_subprojects]) do
566 568 events += Issue.find(:all,
567 569 :include => [:tracker, :status, :assigned_to, :priority, :project],
568 570 :conditions => ["((start_date BETWEEN ? AND ?) OR (due_date BETWEEN ? AND ?)) AND #{Issue.table_name}.tracker_id IN (#{@selected_tracker_ids.join(',')})", @calendar.startdt, @calendar.enddt, @calendar.startdt, @calendar.enddt]
569 571 ) unless @selected_tracker_ids.empty?
570 572 end
571 573 events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @calendar.startdt, @calendar.enddt])
572 574 @calendar.events = events
573 575
574 576 render :layout => false if request.xhr?
575 577 end
576 578
577 579 def gantt
578 580 @trackers = Tracker.find(:all, :order => 'position')
579 581 retrieve_selected_tracker_ids(@trackers)
580 582
581 583 if params[:year] and params[:year].to_i >0
582 584 @year_from = params[:year].to_i
583 585 if params[:month] and params[:month].to_i >=1 and params[:month].to_i <= 12
584 586 @month_from = params[:month].to_i
585 587 else
586 588 @month_from = 1
587 589 end
588 590 else
589 591 @month_from ||= Date.today.month
590 592 @year_from ||= Date.today.year
591 593 end
592 594
593 595 zoom = (params[:zoom] || User.current.pref[:gantt_zoom]).to_i
594 596 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
595 597 months = (params[:months] || User.current.pref[:gantt_months]).to_i
596 598 @months = (months > 0 && months < 25) ? months : 6
597 599
598 600 # Save gantt paramters as user preference (zoom and months count)
599 601 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
600 602 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
601 603 User.current.preference.save
602 604 end
603 605
604 606 @date_from = Date.civil(@year_from, @month_from, 1)
605 607 @date_to = (@date_from >> @months) - 1
606 608
607 609 @events = []
608 610 @project.issues_with_subprojects(params[:with_subprojects]) do
609 611 @events += Issue.find(:all,
610 612 :order => "start_date, due_date",
611 613 :include => [:tracker, :status, :assigned_to, :priority, :project],
612 614 :conditions => ["(((start_date>=? and start_date<=?) or (due_date>=? and due_date<=?) or (start_date<? and due_date>?)) and start_date is not null and due_date is not null and #{Issue.table_name}.tracker_id in (#{@selected_tracker_ids.join(',')}))", @date_from, @date_to, @date_from, @date_to, @date_from, @date_to]
613 615 ) unless @selected_tracker_ids.empty?
614 616 end
615 617 @events += @project.versions.find(:all, :conditions => ["effective_date BETWEEN ? AND ?", @date_from, @date_to])
616 618 @events.sort! {|x,y| x.start_date <=> y.start_date }
617 619
618 620 if params[:format]=='pdf'
619 621 @options_for_rfpdf ||= {}
620 622 @options_for_rfpdf[:file_name] = "#{@project.identifier}-gantt.pdf"
621 623 render :template => "projects/gantt.rfpdf", :layout => false
622 624 elsif params[:format]=='png' && respond_to?('gantt_image')
623 625 image = gantt_image(@events, @date_from, @months, @zoom)
624 626 image.format = 'PNG'
625 627 send_data(image.to_blob, :disposition => 'inline', :type => 'image/png', :filename => "#{@project.identifier}-gantt.png")
626 628 else
627 629 render :template => "projects/gantt.rhtml"
628 630 end
629 631 end
630 632
631 633 private
632 634 # Find project of id params[:id]
633 635 # if not found, redirect to project list
634 636 # Used as a before_filter
635 637 def find_project
636 638 @project = Project.find(params[:id])
637 639 rescue ActiveRecord::RecordNotFound
638 640 render_404
639 641 end
640 642
641 643 def retrieve_selected_tracker_ids(selectable_trackers)
642 644 if ids = params[:tracker_ids]
643 645 @selected_tracker_ids = (ids.is_a? Array) ? ids.collect { |id| id.to_i.to_s } : ids.split('/').collect { |id| id.to_i.to_s }
644 646 else
645 647 @selected_tracker_ids = selectable_trackers.collect {|t| t.id.to_s }
646 648 end
647 649 end
648 650
649 651 # Retrieve query from session or build a new query
650 652 def retrieve_query
651 653 if params[:query_id]
652 654 @query = @project.queries.find(params[:query_id])
653 655 @query.executed_by = logged_in_user
654 656 session[:query] = @query
655 657 else
656 658 if params[:set_filter] or !session[:query] or session[:query].project_id != @project.id
657 659 # Give it a name, required to be valid
658 660 @query = Query.new(:name => "_", :executed_by => logged_in_user)
659 661 @query.project = @project
660 662 if params[:fields] and params[:fields].is_a? Array
661 663 params[:fields].each do |field|
662 664 @query.add_filter(field, params[:operators][field], params[:values][field])
663 665 end
664 666 else
665 667 @query.available_filters.keys.each do |field|
666 668 @query.add_short_filter(field, params[field]) if params[field]
667 669 end
668 670 end
669 671 session[:query] = @query
670 672 else
671 673 @query = session[:query]
672 674 end
673 675 end
674 676 end
675 677 end
@@ -1,184 +1,191
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Issue < ActiveRecord::Base
19 19 belongs_to :project
20 20 belongs_to :tracker
21 21 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
22 22 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
23 23 belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
24 24 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
25 25 belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
26 26 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
27 27
28 28 has_many :journals, :as => :journalized, :dependent => :destroy
29 29 has_many :attachments, :as => :container, :dependent => :destroy
30 30 has_many :time_entries, :dependent => :nullify
31 31 has_many :custom_values, :dependent => :delete_all, :as => :customized
32 32 has_many :custom_fields, :through => :custom_values
33 33 has_and_belongs_to_many :changesets, :order => "revision ASC"
34 34
35 35 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
36 36 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
37 37
38 38 acts_as_watchable
39 39 acts_as_searchable :columns => ['subject', 'description'], :with => {:journal => :issue}
40 40 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id}: #{o.subject}"},
41 41 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}}
42 42
43 43 validates_presence_of :subject, :description, :priority, :tracker, :author, :status
44 44 validates_length_of :subject, :maximum => 255
45 45 validates_inclusion_of :done_ratio, :in => 0..100
46 46 validates_numericality_of :estimated_hours, :allow_nil => true
47 47 validates_associated :custom_values, :on => :update
48 48
49 49 def after_initialize
50 50 if new_record?
51 51 # set default values for new records only
52 52 self.status ||= IssueStatus.default
53 53 self.priority ||= Enumeration.default('IPRI')
54 54 end
55 55 end
56 56
57 def copy_from(arg)
58 issue = arg.is_a?(Issue) ? arg : Issue.find(arg)
59 self.attributes = issue.attributes.dup
60 self.custom_values = issue.custom_values.collect {|v| v.clone}
61 self
62 end
63
57 64 def priority_id=(pid)
58 65 self.priority = nil
59 66 write_attribute(:priority_id, pid)
60 67 end
61 68
62 69 def validate
63 70 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
64 71 errors.add :due_date, :activerecord_error_not_a_date
65 72 end
66 73
67 74 if self.due_date and self.start_date and self.due_date < self.start_date
68 75 errors.add :due_date, :activerecord_error_greater_than_start_date
69 76 end
70 77
71 78 if start_date && soonest_start && start_date < soonest_start
72 79 errors.add :start_date, :activerecord_error_invalid
73 80 end
74 81 end
75 82
76 83 def before_create
77 84 # default assignment based on category
78 85 if assigned_to.nil? && category && category.assigned_to
79 86 self.assigned_to = category.assigned_to
80 87 end
81 88 end
82 89
83 90 def before_save
84 91 if @current_journal
85 92 # attributes changes
86 93 (Issue.column_names - %w(id description)).each {|c|
87 94 @current_journal.details << JournalDetail.new(:property => 'attr',
88 95 :prop_key => c,
89 96 :old_value => @issue_before_change.send(c),
90 97 :value => send(c)) unless send(c)==@issue_before_change.send(c)
91 98 }
92 99 # custom fields changes
93 100 custom_values.each {|c|
94 101 next if (@custom_values_before_change[c.custom_field_id]==c.value ||
95 102 (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
96 103 @current_journal.details << JournalDetail.new(:property => 'cf',
97 104 :prop_key => c.custom_field_id,
98 105 :old_value => @custom_values_before_change[c.custom_field_id],
99 106 :value => c.value)
100 107 }
101 108 @current_journal.save
102 109 end
103 110 # Save the issue even if the journal is not saved (because empty)
104 111 true
105 112 end
106 113
107 114 def after_save
108 115 # Update start/due dates of following issues
109 116 relations_from.each(&:set_issue_to_dates)
110 117
111 118 # Close duplicates if the issue was closed
112 119 if @issue_before_change && !@issue_before_change.closed? && self.closed?
113 120 duplicates.each do |duplicate|
114 121 # Don't re-close it if it's already closed
115 122 next if duplicate.closed?
116 123 # Same user and notes
117 124 duplicate.init_journal(@current_journal.user, @current_journal.notes)
118 125 duplicate.update_attribute :status, self.status
119 126 end
120 127 end
121 128 end
122 129
123 130 def custom_value_for(custom_field)
124 131 self.custom_values.each {|v| return v if v.custom_field_id == custom_field.id }
125 132 return nil
126 133 end
127 134
128 135 def init_journal(user, notes = "")
129 136 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
130 137 @issue_before_change = self.clone
131 138 @custom_values_before_change = {}
132 139 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
133 140 @current_journal
134 141 end
135 142
136 143 # Return true if the issue is closed, otherwise false
137 144 def closed?
138 145 self.status.is_closed?
139 146 end
140 147
141 148 # Users the issue can be assigned to
142 149 def assignable_users
143 150 project.assignable_users
144 151 end
145 152
146 153 # Returns the mail adresses of users that should be notified for the issue
147 154 def recipients
148 155 recipients = project.recipients
149 156 # Author and assignee are always notified
150 157 recipients << author.mail if author
151 158 recipients << assigned_to.mail if assigned_to
152 159 recipients.compact.uniq
153 160 end
154 161
155 162 def spent_hours
156 163 @spent_hours ||= time_entries.sum(:hours) || 0
157 164 end
158 165
159 166 def relations
160 167 (relations_from + relations_to).sort
161 168 end
162 169
163 170 def all_dependent_issues
164 171 dependencies = []
165 172 relations_from.each do |relation|
166 173 dependencies << relation.issue_to
167 174 dependencies += relation.issue_to.all_dependent_issues
168 175 end
169 176 dependencies
170 177 end
171 178
172 179 # Returns an array of the duplicate issues
173 180 def duplicates
174 181 relations.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.other_issue(self)}
175 182 end
176 183
177 184 def duration
178 185 (start_date && due_date) ? due_date - start_date : 0
179 186 end
180 187
181 188 def soonest_start
182 189 @soonest_start ||= relations_to.collect{|relation| relation.successor_soonest_start}.compact.min
183 190 end
184 191 end
@@ -1,38 +1,40
1 1 <% back_to = url_for(:controller => 'projects', :action => 'list_issues', :id => @project) %>
2 2 <ul>
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 5 <li class="folder">
6 6 <a href="#" class="submenu" onclick="return false;"><%= l(:field_status) %></a>
7 7 <ul>
8 8 <% @statuses.each do |s| %>
9 9 <li><%= context_menu_link s.name, {:controller => 'issues', :action => 'change_status', :id => @issue, :new_status_id => s},
10 10 :selected => (s == @issue.status), :disabled => !(@can[:change_status] && @allowed_statuses.include?(s)) %></li>
11 11 <% end %>
12 12 </ul>
13 13 </li>
14 14 <li class="folder">
15 15 <a href="#" class="submenu"><%= l(:field_priority) %></a>
16 16 <ul>
17 17 <% @priorities.each do |p| %>
18 18 <li><%= context_menu_link p.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[priority_id]' => p, :back_to => back_to}, :method => :post,
19 19 :selected => (p == @issue.priority), :disabled => !@can[:edit] %></li>
20 20 <% end %>
21 21 </ul>
22 22 </li>
23 23 <li class="folder">
24 24 <a href="#" class="submenu"><%= l(:field_assigned_to) %></a>
25 25 <ul>
26 26 <% @assignables.each do |u| %>
27 27 <li><%= context_menu_link u.name, {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[assigned_to_id]' => u, :back_to => back_to}, :method => :post,
28 28 :selected => (u == @issue.assigned_to), :disabled => !(@can[:edit] || @can[:change_status]) %></li>
29 29 <% end %>
30 30 <li><%= context_menu_link l(:label_nobody), {:controller => 'issues', :action => 'edit', :id => @issue, 'issue[assigned_to_id]' => '', :back_to => back_to}, :method => :post,
31 31 :selected => @issue.assigned_to.nil?, :disabled => !(@can[:edit] || @can[:change_status]) %></li>
32 32 </ul>
33 33 </li>
34 <li><%= context_menu_link l(:button_copy), {:controller => 'projects', :action => 'add_issue', :id => @project, :copy_from => @issue},
35 :class => 'icon-copy', :disabled => !@can[:add] %></li>
34 36 <li><%= context_menu_link l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id },
35 37 :class => 'icon-move', :disabled => !@can[:move] %>
36 38 <li><%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue},
37 39 :method => :post, :confirm => l(:text_are_you_sure), :class => 'icon-del', :disabled => !@can[:delete] %></li>
38 40 </ul>
@@ -1,122 +1,123
1 1 <div class="contextual">
2 2 <%= show_and_goto_link(l(:label_add_note), 'add-note', :class => 'icon icon-note') if authorize_for('issues', 'add_note') %>
3 3 <%= link_to_if_authorized l(:button_edit), {:controller => 'issues', :action => 'edit', :id => @issue}, :class => 'icon icon-edit', :accesskey => accesskey(:edit) %>
4 4 <%= link_to_if_authorized l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon icon-time' %>
5 5 <%= watcher_tag(@issue, User.current) %>
6 <%= link_to_if_authorized l(:button_copy), {:controller => 'projects', :action => 'add_issue', :id => @project, :copy_from => @issue }, :class => 'icon icon-copy' %>
6 7 <%= link_to_if_authorized l(:button_move), {:controller => 'projects', :action => 'move_issues', :id => @project, "issue_ids[]" => @issue.id }, :class => 'icon icon-move' %>
7 8 <%= link_to_if_authorized l(:button_delete), {:controller => 'issues', :action => 'destroy', :id => @issue}, :confirm => l(:text_are_you_sure), :method => :post, :class => 'icon icon-del' %>
8 9 </div>
9 10
10 11 <h2><%= @issue.tracker.name %> #<%= @issue.id %></h2>
11 12
12 13 <div class="issue">
13 14 <h3><%=h @issue.subject %></h3>
14 15 <p class="author">
15 16 <%= authoring @issue.created_on, @issue.author %>.
16 17 <%= l(:label_updated_time, distance_of_time_in_words(Time.now, @issue.updated_on)) if @issue.created_on != @issue.updated_on %>.
17 18 </p>
18 19
19 20 <table width="100%">
20 21 <tr>
21 22 <td style="width:15%"><b><%=l(:field_status)%> :</b></td><td style="width:35%"><%= @issue.status.name %></td>
22 23 <td style="width:15%"><b><%=l(:field_start_date)%> :</b></td><td style="width:35%"><%= format_date(@issue.start_date) %></td>
23 24 </tr>
24 25 <tr>
25 26 <td><b><%=l(:field_priority)%> :</b></td><td><%= @issue.priority.name %></td>
26 27 <td><b><%=l(:field_due_date)%> :</b></td><td><%= format_date(@issue.due_date) %></td>
27 28 </tr>
28 29 <tr>
29 30 <td><b><%=l(:field_assigned_to)%> :</b></td><td><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
30 31 <td><b><%=l(:field_done_ratio)%> :</b></td><td><%= @issue.done_ratio %> %</td>
31 32 </tr>
32 33 <tr>
33 34 <td><b><%=l(:field_category)%> :</b></td><td><%=h @issue.category ? @issue.category.name : "-" %></td>
34 35 <% if User.current.allowed_to?(:view_time_entries, @project) %>
35 36 <td><b><%=l(:label_spent_time)%> :</b></td>
36 37 <td><%= @issue.spent_hours > 0 ? (link_to lwr(:label_f_hour, @issue.spent_hours), {:controller => 'timelog', :action => 'details', :issue_id => @issue}, :class => 'icon icon-time') : "-" %></td>
37 38 <% end %>
38 39 </tr>
39 40 <tr>
40 41 <td><b><%=l(:field_fixed_version)%> :</b></td><td><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
41 42 <% if @issue.estimated_hours %>
42 43 <td><b><%=l(:field_estimated_hours)%> :</b></td><td><%= lwr(:label_f_hour, @issue.estimated_hours) %></td>
43 44 <% end %>
44 45 </tr>
45 46 <tr>
46 47 <% n = 0
47 48 for custom_value in @custom_values %>
48 49 <td valign="top"><b><%= custom_value.custom_field.name %> :</b></td><td valign="top"><%= simple_format(h(show_value(custom_value))) %></td>
49 50 <% n = n + 1
50 51 if (n > 1)
51 52 n = 0 %>
52 53 </tr><tr>
53 54 <%end
54 55 end %>
55 56 </tr>
56 57 </table>
57 58 <hr />
58 59
59 60 <% if @issue.changesets.any? %>
60 61 <div style="float:right;">
61 62 <em><%= l(:label_revision_plural) %>: <%= @issue.changesets.collect{|changeset| link_to(changeset.revision, :controller => 'repositories', :action => 'revision', :id => @project, :rev => changeset.revision)}.join(", ") %></em>
62 63 </div>
63 64 <% end %>
64 65
65 66 <p><strong><%=l(:field_description)%></strong></p>
66 67 <%= textilizable @issue.description, :attachments => @issue.attachments %>
67 68
68 69 <% if @issue.attachments.any? %>
69 70 <%= link_to_attachments @issue.attachments, :delete_url => (authorize_for('issues', 'destroy_attachment') ? {:controller => 'issues', :action => 'destroy_attachment', :id => @issue} : nil) %>
70 71 <% end %>
71 72
72 73 <% if authorize_for('issue_relations', 'new') || @issue.relations.any? %>
73 74 <hr />
74 75 <div id="relations">
75 76 <%= render :partial => 'relations' %>
76 77 </div>
77 78 <% end %>
78 79
79 80 </div>
80 81
81 82 <% if authorize_for('issues', 'change_status') and @status_options and !@status_options.empty? %>
82 83 <% form_tag({:controller => 'issues', :action => 'change_status', :id => @issue}) do %>
83 84 <p><%=l(:label_change_status)%> :
84 85 <select name="new_status_id">
85 86 <%= options_from_collection_for_select @status_options, "id", "name" %>
86 87 </select>
87 88 <%= submit_tag l(:button_change) %></p>
88 89 <% end %>
89 90 <% end %>
90 91
91 92 <% if @journals.any? %>
92 93 <div id="history">
93 94 <h3><%=l(:label_history)%></h3>
94 95 <%= render :partial => 'history', :locals => { :journals => @journals } %>
95 96 </div>
96 97 <% end %>
97 98
98 99 <% if authorize_for('issues', 'add_note') %>
99 100 <a name="add-note-anchor"></a>
100 101 <div id="add-note" class="box" style="display:none;">
101 102 <h3><%= l(:label_add_note) %></h3>
102 103 <% form_tag({:controller => 'issues', :action => 'add_note', :id => @issue}, :class => "tabular", :multipart => true) do %>
103 104 <p><label for="notes"><%=l(:field_notes)%></label>
104 105 <%= text_area_tag 'notes', '', :cols => 60, :rows => 10, :class => 'wiki-edit' %></p>
105 106 <%= wikitoolbar_for 'notes' %>
106 107 <%= render :partial => 'attachments/form' %>
107 108 <%= submit_tag l(:button_add) %>
108 109 <%= toggle_link l(:button_cancel), 'add-note' %>
109 110 <% end %>
110 111 </div>
111 112 <% end %>
112 113
113 114 <div class="contextual">
114 115 <%= l(:label_export_to) %><%= link_to 'PDF', {:format => 'pdf'}, :class => 'icon icon-pdf' %>
115 116 </div>
116 117 &nbsp;
117 118
118 119 <% set_html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
119 120
120 121 <% content_for :sidebar do %>
121 122 <%= render :partial => 'issues/sidebar' %>
122 123 <% end %>
@@ -1,19 +1,19
1 <h2><%=l(:label_issue_new)%>: <%= @tracker.name %></h2>
1 <h2><%=l(:label_issue_new)%>: <%= @issue.tracker %></h2>
2 2
3 3 <% labelled_tabular_form_for :issue, @issue,
4 4 :url => {:action => 'add_issue'},
5 5 :html => {:multipart => true, :id => 'issue-form'} do |f| %>
6 <%= hidden_field_tag 'tracker_id', @tracker.id %>
6 <%= f.hidden_field :tracker_id %>
7 7 <%= render :partial => 'issues/form', :locals => {:f => f} %>
8 8 <%= submit_tag l(:button_create) %>
9 9 <%= link_to_remote l(:label_preview),
10 10 { :url => { :controller => 'issues', :action => 'preview', :id => @issue },
11 11 :method => 'post',
12 12 :update => 'preview',
13 13 :with => "Form.serialize('issue-form')",
14 14 :complete => "location.href='#preview-top'"
15 15 }, :accesskey => accesskey(:preview) %>
16 16 <% end %>
17 17
18 18 <a name="preview-top"></a>
19 19 <div id="preview" class="wiki"></div>
@@ -1,535 +1,536
1 1 _gloc_rule_default: '|n| n==1 ? "" : "_plural" '
2 2
3 3 actionview_datehelper_select_day_prefix:
4 4 actionview_datehelper_select_month_names: January,February,March,April,May,June,July,August,September,October,November,December
5 5 actionview_datehelper_select_month_names_abbr: Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec
6 6 actionview_datehelper_select_month_prefix:
7 7 actionview_datehelper_select_year_prefix:
8 8 actionview_datehelper_time_in_words_day: 1 day
9 9 actionview_datehelper_time_in_words_day_plural: %d days
10 10 actionview_datehelper_time_in_words_hour_about: about an hour
11 11 actionview_datehelper_time_in_words_hour_about_plural: about %d hours
12 12 actionview_datehelper_time_in_words_hour_about_single: about an hour
13 13 actionview_datehelper_time_in_words_minute: 1 minute
14 14 actionview_datehelper_time_in_words_minute_half: half a minute
15 15 actionview_datehelper_time_in_words_minute_less_than: less than a minute
16 16 actionview_datehelper_time_in_words_minute_plural: %d minutes
17 17 actionview_datehelper_time_in_words_minute_single: 1 minute
18 18 actionview_datehelper_time_in_words_second_less_than: less than a second
19 19 actionview_datehelper_time_in_words_second_less_than_plural: less than %d seconds
20 20 actionview_instancetag_blank_option: Please select
21 21
22 22 activerecord_error_inclusion: is not included in the list
23 23 activerecord_error_exclusion: is reserved
24 24 activerecord_error_invalid: is invalid
25 25 activerecord_error_confirmation: doesn't match confirmation
26 26 activerecord_error_accepted: must be accepted
27 27 activerecord_error_empty: can't be empty
28 28 activerecord_error_blank: can't be blank
29 29 activerecord_error_too_long: is too long
30 30 activerecord_error_too_short: is too short
31 31 activerecord_error_wrong_length: is the wrong length
32 32 activerecord_error_taken: has already been taken
33 33 activerecord_error_not_a_number: is not a number
34 34 activerecord_error_not_a_date: is not a valid date
35 35 activerecord_error_greater_than_start_date: must be greater than start date
36 36 activerecord_error_not_same_project: doesn't belong to the same project
37 37 activerecord_error_circular_dependency: This relation would create a circular dependency
38 38
39 39 general_fmt_age: %d yr
40 40 general_fmt_age_plural: %d yrs
41 41 general_fmt_date: %%m/%%d/%%Y
42 42 general_fmt_datetime: %%m/%%d/%%Y %%I:%%M %%p
43 43 general_fmt_datetime_short: %%b %%d, %%I:%%M %%p
44 44 general_fmt_time: %%I:%%M %%p
45 45 general_text_No: 'No'
46 46 general_text_Yes: 'Yes'
47 47 general_text_no: 'no'
48 48 general_text_yes: 'yes'
49 49 general_lang_name: 'English'
50 50 general_csv_separator: ','
51 51 general_csv_encoding: ISO-8859-1
52 52 general_pdf_encoding: ISO-8859-1
53 53 general_day_names: Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
54 54 general_first_day_of_week: '7'
55 55
56 56 notice_account_updated: Account was successfully updated.
57 57 notice_account_invalid_creditentials: Invalid user or password
58 58 notice_account_password_updated: Password was successfully updated.
59 59 notice_account_wrong_password: Wrong password
60 60 notice_account_register_done: Account was successfully created. To activate your account, click on the link that was emailed to you.
61 61 notice_account_unknown_email: Unknown user.
62 62 notice_can_t_change_password: This account uses an external authentication source. Impossible to change the password.
63 63 notice_account_lost_email_sent: An email with instructions to choose a new password has been sent to you.
64 64 notice_account_activated: Your account has been activated. You can now log in.
65 65 notice_successful_create: Successful creation.
66 66 notice_successful_update: Successful update.
67 67 notice_successful_delete: Successful deletion.
68 68 notice_successful_connection: Successful connection.
69 69 notice_file_not_found: The page you were trying to access doesn't exist or has been removed.
70 70 notice_locking_conflict: Data have been updated by another user.
71 71 notice_scm_error: Entry and/or revision doesn't exist in the repository.
72 72 notice_not_authorized: You are not authorized to access this page.
73 73 notice_email_sent: An email was sent to %s
74 74 notice_email_error: An error occurred while sending mail (%s)
75 75 notice_feeds_access_key_reseted: Your RSS access key was reseted.
76 76 notice_failed_to_save_issues: "Failed to save %d issue(s) on %d selected: %s."
77 77 notice_no_issue_selected: "No issue is selected! Please, check the issues you want to edit."
78 78
79 79 mail_subject_lost_password: Your Redmine password
80 80 mail_body_lost_password: 'To change your Redmine password, click on the following link:'
81 81 mail_subject_register: Redmine account activation
82 82 mail_body_register: 'To activate your Redmine account, click on the following link:'
83 83
84 84 gui_validation_error: 1 error
85 85 gui_validation_error_plural: %d errors
86 86
87 87 field_name: Name
88 88 field_description: Description
89 89 field_summary: Summary
90 90 field_is_required: Required
91 91 field_firstname: Firstname
92 92 field_lastname: Lastname
93 93 field_mail: Email
94 94 field_filename: File
95 95 field_filesize: Size
96 96 field_downloads: Downloads
97 97 field_author: Author
98 98 field_created_on: Created
99 99 field_updated_on: Updated
100 100 field_field_format: Format
101 101 field_is_for_all: For all projects
102 102 field_possible_values: Possible values
103 103 field_regexp: Regular expression
104 104 field_min_length: Minimum length
105 105 field_max_length: Maximum length
106 106 field_value: Value
107 107 field_category: Category
108 108 field_title: Title
109 109 field_project: Project
110 110 field_issue: Issue
111 111 field_status: Status
112 112 field_notes: Notes
113 113 field_is_closed: Issue closed
114 114 field_is_default: Default value
115 115 field_html_color: Color
116 116 field_tracker: Tracker
117 117 field_subject: Subject
118 118 field_due_date: Due date
119 119 field_assigned_to: Assigned to
120 120 field_priority: Priority
121 121 field_fixed_version: Fixed version
122 122 field_user: User
123 123 field_role: Role
124 124 field_homepage: Homepage
125 125 field_is_public: Public
126 126 field_parent: Subproject of
127 127 field_is_in_chlog: Issues displayed in changelog
128 128 field_is_in_roadmap: Issues displayed in roadmap
129 129 field_login: Login
130 130 field_mail_notification: Email notifications
131 131 field_admin: Administrator
132 132 field_last_login_on: Last connection
133 133 field_language: Language
134 134 field_effective_date: Date
135 135 field_password: Password
136 136 field_new_password: New password
137 137 field_password_confirmation: Confirmation
138 138 field_version: Version
139 139 field_type: Type
140 140 field_host: Host
141 141 field_port: Port
142 142 field_account: Account
143 143 field_base_dn: Base DN
144 144 field_attr_login: Login attribute
145 145 field_attr_firstname: Firstname attribute
146 146 field_attr_lastname: Lastname attribute
147 147 field_attr_mail: Email attribute
148 148 field_onthefly: On-the-fly user creation
149 149 field_start_date: Start
150 150 field_done_ratio: %% Done
151 151 field_auth_source: Authentication mode
152 152 field_hide_mail: Hide my email address
153 153 field_comments: Comment
154 154 field_url: URL
155 155 field_start_page: Start page
156 156 field_subproject: Subproject
157 157 field_hours: Hours
158 158 field_activity: Activity
159 159 field_spent_on: Date
160 160 field_identifier: Identifier
161 161 field_is_filter: Used as a filter
162 162 field_issue_to_id: Related issue
163 163 field_delay: Delay
164 164 field_assignable: Issues can be assigned to this role
165 165 field_redirect_existing_links: Redirect existing links
166 166 field_estimated_hours: Estimated time
167 167 field_column_names: Columns
168 168
169 169 setting_app_title: Application title
170 170 setting_app_subtitle: Application subtitle
171 171 setting_welcome_text: Welcome text
172 172 setting_default_language: Default language
173 173 setting_login_required: Authent. required
174 174 setting_self_registration: Self-registration enabled
175 175 setting_attachment_max_size: Attachment max. size
176 176 setting_issues_export_limit: Issues export limit
177 177 setting_mail_from: Emission email address
178 178 setting_host_name: Host name
179 179 setting_text_formatting: Text formatting
180 180 setting_wiki_compression: Wiki history compression
181 181 setting_feeds_limit: Feed content limit
182 182 setting_autofetch_changesets: Autofetch commits
183 183 setting_sys_api_enabled: Enable WS for repository management
184 184 setting_commit_ref_keywords: Referencing keywords
185 185 setting_commit_fix_keywords: Fixing keywords
186 186 setting_autologin: Autologin
187 187 setting_date_format: Date format
188 188 setting_cross_project_issue_relations: Allow cross-project issue relations
189 189 setting_issue_list_default_columns: Default columns displayed on the issue list
190 190 setting_repositories_encodings: Repositories encodings
191 191 setting_emails_footer: Emails footer
192 192
193 193 label_user: User
194 194 label_user_plural: Users
195 195 label_user_new: New user
196 196 label_project: Project
197 197 label_project_new: New project
198 198 label_project_plural: Projects
199 199 label_project_all: All Projects
200 200 label_project_latest: Latest projects
201 201 label_issue: Issue
202 202 label_issue_new: New issue
203 203 label_issue_plural: Issues
204 204 label_issue_view_all: View all issues
205 205 label_document: Document
206 206 label_document_new: New document
207 207 label_document_plural: Documents
208 208 label_role: Role
209 209 label_role_plural: Roles
210 210 label_role_new: New role
211 211 label_role_and_permissions: Roles and permissions
212 212 label_member: Member
213 213 label_member_new: New member
214 214 label_member_plural: Members
215 215 label_tracker: Tracker
216 216 label_tracker_plural: Trackers
217 217 label_tracker_new: New tracker
218 218 label_workflow: Workflow
219 219 label_issue_status: Issue status
220 220 label_issue_status_plural: Issue statuses
221 221 label_issue_status_new: New status
222 222 label_issue_category: Issue category
223 223 label_issue_category_plural: Issue categories
224 224 label_issue_category_new: New category
225 225 label_custom_field: Custom field
226 226 label_custom_field_plural: Custom fields
227 227 label_custom_field_new: New custom field
228 228 label_enumerations: Enumerations
229 229 label_enumeration_new: New value
230 230 label_information: Information
231 231 label_information_plural: Information
232 232 label_please_login: Please login
233 233 label_register: Register
234 234 label_password_lost: Lost password
235 235 label_home: Home
236 236 label_my_page: My page
237 237 label_my_account: My account
238 238 label_my_projects: My projects
239 239 label_administration: Administration
240 240 label_login: Sign in
241 241 label_logout: Sign out
242 242 label_help: Help
243 243 label_reported_issues: Reported issues
244 244 label_assigned_to_me_issues: Issues assigned to me
245 245 label_last_login: Last connection
246 246 label_last_updates: Last updated
247 247 label_last_updates_plural: %d last updated
248 248 label_registered_on: Registered on
249 249 label_activity: Activity
250 250 label_new: New
251 251 label_logged_as: Logged as
252 252 label_environment: Environment
253 253 label_authentication: Authentication
254 254 label_auth_source: Authentication mode
255 255 label_auth_source_new: New authentication mode
256 256 label_auth_source_plural: Authentication modes
257 257 label_subproject_plural: Subprojects
258 258 label_min_max_length: Min - Max length
259 259 label_list: List
260 260 label_date: Date
261 261 label_integer: Integer
262 262 label_float: Float
263 263 label_boolean: Boolean
264 264 label_string: Text
265 265 label_text: Long text
266 266 label_attribute: Attribute
267 267 label_attribute_plural: Attributes
268 268 label_download: %d Download
269 269 label_download_plural: %d Downloads
270 270 label_no_data: No data to display
271 271 label_change_status: Change status
272 272 label_history: History
273 273 label_attachment: File
274 274 label_attachment_new: New file
275 275 label_attachment_delete: Delete file
276 276 label_attachment_plural: Files
277 277 label_report: Report
278 278 label_report_plural: Reports
279 279 label_news: News
280 280 label_news_new: Add news
281 281 label_news_plural: News
282 282 label_news_latest: Latest news
283 283 label_news_view_all: View all news
284 284 label_change_log: Change log
285 285 label_settings: Settings
286 286 label_overview: Overview
287 287 label_version: Version
288 288 label_version_new: New version
289 289 label_version_plural: Versions
290 290 label_confirmation: Confirmation
291 291 label_export_to: Export to
292 292 label_read: Read...
293 293 label_public_projects: Public projects
294 294 label_open_issues: open
295 295 label_open_issues_plural: open
296 296 label_closed_issues: closed
297 297 label_closed_issues_plural: closed
298 298 label_total: Total
299 299 label_permissions: Permissions
300 300 label_current_status: Current status
301 301 label_new_statuses_allowed: New statuses allowed
302 302 label_all: all
303 303 label_none: none
304 304 label_nobody: nobody
305 305 label_next: Next
306 306 label_previous: Previous
307 307 label_used_by: Used by
308 308 label_details: Details
309 309 label_add_note: Add a note
310 310 label_per_page: Per page
311 311 label_calendar: Calendar
312 312 label_months_from: months from
313 313 label_gantt: Gantt
314 314 label_internal: Internal
315 315 label_last_changes: last %d changes
316 316 label_change_view_all: View all changes
317 317 label_personalize_page: Personalize this page
318 318 label_comment: Comment
319 319 label_comment_plural: Comments
320 320 label_comment_add: Add a comment
321 321 label_comment_added: Comment added
322 322 label_comment_delete: Delete comments
323 323 label_query: Custom query
324 324 label_query_plural: Custom queries
325 325 label_query_new: New query
326 326 label_filter_add: Add filter
327 327 label_filter_plural: Filters
328 328 label_equals: is
329 329 label_not_equals: is not
330 330 label_in_less_than: in less than
331 331 label_in_more_than: in more than
332 332 label_in: in
333 333 label_today: today
334 334 label_this_week: this week
335 335 label_less_than_ago: less than days ago
336 336 label_more_than_ago: more than days ago
337 337 label_ago: days ago
338 338 label_contains: contains
339 339 label_not_contains: doesn't contain
340 340 label_day_plural: days
341 341 label_repository: Repository
342 342 label_browse: Browse
343 343 label_modification: %d change
344 344 label_modification_plural: %d changes
345 345 label_revision: Revision
346 346 label_revision_plural: Revisions
347 347 label_added: added
348 348 label_modified: modified
349 349 label_deleted: deleted
350 350 label_latest_revision: Latest revision
351 351 label_latest_revision_plural: Latest revisions
352 352 label_view_revisions: View revisions
353 353 label_max_size: Maximum size
354 354 label_on: 'on'
355 355 label_sort_highest: Move to top
356 356 label_sort_higher: Move up
357 357 label_sort_lower: Move down
358 358 label_sort_lowest: Move to bottom
359 359 label_roadmap: Roadmap
360 360 label_roadmap_due_in: Due in
361 361 label_roadmap_overdue: %s late
362 362 label_roadmap_no_issues: No issues for this version
363 363 label_search: Search
364 364 label_result_plural: Results
365 365 label_all_words: All words
366 366 label_wiki: Wiki
367 367 label_wiki_edit: Wiki edit
368 368 label_wiki_edit_plural: Wiki edits
369 369 label_wiki_page: Wiki page
370 370 label_wiki_page_plural: Wiki pages
371 371 label_index_by_title: Index by title
372 372 label_index_by_date: Index by date
373 373 label_current_version: Current version
374 374 label_preview: Preview
375 375 label_feed_plural: Feeds
376 376 label_changes_details: Details of all changes
377 377 label_issue_tracking: Issue tracking
378 378 label_spent_time: Spent time
379 379 label_f_hour: %.2f hour
380 380 label_f_hour_plural: %.2f hours
381 381 label_time_tracking: Time tracking
382 382 label_change_plural: Changes
383 383 label_statistics: Statistics
384 384 label_commits_per_month: Commits per month
385 385 label_commits_per_author: Commits per author
386 386 label_view_diff: View differences
387 387 label_diff_inline: inline
388 388 label_diff_side_by_side: side by side
389 389 label_options: Options
390 390 label_copy_workflow_from: Copy workflow from
391 391 label_permissions_report: Permissions report
392 392 label_watched_issues: Watched issues
393 393 label_related_issues: Related issues
394 394 label_applied_status: Applied status
395 395 label_loading: Loading...
396 396 label_relation_new: New relation
397 397 label_relation_delete: Delete relation
398 398 label_relates_to: related to
399 399 label_duplicates: duplicates
400 400 label_blocks: blocks
401 401 label_blocked_by: blocked by
402 402 label_precedes: precedes
403 403 label_follows: follows
404 404 label_end_to_start: end to start
405 405 label_end_to_end: end to end
406 406 label_start_to_start: start to start
407 407 label_start_to_end: start to end
408 408 label_stay_logged_in: Stay logged in
409 409 label_disabled: disabled
410 410 label_show_completed_versions: Show completed versions
411 411 label_me: me
412 412 label_board: Forum
413 413 label_board_new: New forum
414 414 label_board_plural: Forums
415 415 label_topic_plural: Topics
416 416 label_message_plural: Messages
417 417 label_message_last: Last message
418 418 label_message_new: New message
419 419 label_reply_plural: Replies
420 420 label_send_information: Send account information to the user
421 421 label_year: Year
422 422 label_month: Month
423 423 label_week: Week
424 424 label_date_from: From
425 425 label_date_to: To
426 426 label_language_based: Language based
427 427 label_sort_by: Sort by "%s"
428 428 label_send_test_email: Send a test email
429 429 label_feeds_access_key_created_on: RSS access key created %s ago
430 430 label_module_plural: Modules
431 431 label_added_time_by: Added by %s %s ago
432 432 label_updated_time: Updated %s ago
433 433 label_jump_to_a_project: Jump to a project...
434 434 label_file_plural: Files
435 435 label_changeset_plural: Changesets
436 436 label_default_columns: Default columns
437 437 label_no_change_option: (No change)
438 438 label_bulk_edit_selected_issues: Bulk edit selected issues
439 439 label_theme: Theme
440 440 label_default: Default
441 441 label_search_titles_only: Search titles only
442 442 label_user_mail_option_all: "For any event on all my projects"
443 443 label_user_mail_option_selected: "For any event on the selected projects only..."
444 444 label_user_mail_option_none: "Only for things I watch or I'm involved in"
445 445
446 446 button_login: Login
447 447 button_submit: Submit
448 448 button_save: Save
449 449 button_check_all: Check all
450 450 button_uncheck_all: Uncheck all
451 451 button_delete: Delete
452 452 button_create: Create
453 453 button_test: Test
454 454 button_edit: Edit
455 455 button_add: Add
456 456 button_change: Change
457 457 button_apply: Apply
458 458 button_clear: Clear
459 459 button_lock: Lock
460 460 button_unlock: Unlock
461 461 button_download: Download
462 462 button_list: List
463 463 button_view: View
464 464 button_move: Move
465 465 button_back: Back
466 466 button_cancel: Cancel
467 467 button_activate: Activate
468 468 button_sort: Sort
469 469 button_log_time: Log time
470 470 button_rollback: Rollback to this version
471 471 button_watch: Watch
472 472 button_unwatch: Unwatch
473 473 button_reply: Reply
474 474 button_archive: Archive
475 475 button_unarchive: Unarchive
476 476 button_reset: Reset
477 477 button_rename: Rename
478 478 button_change_password: Change password
479 button_copy: Copy
479 480
480 481 status_active: active
481 482 status_registered: registered
482 483 status_locked: locked
483 484
484 485 text_select_mail_notifications: Select actions for which email notifications should be sent.
485 486 text_regexp_info: eg. ^[A-Z0-9]+$
486 487 text_min_max_length_info: 0 means no restriction
487 488 text_project_destroy_confirmation: Are you sure you want to delete this project and all related data ?
488 489 text_workflow_edit: Select a role and a tracker to edit the workflow
489 490 text_are_you_sure: Are you sure ?
490 491 text_journal_changed: changed from %s to %s
491 492 text_journal_set_to: set to %s
492 493 text_journal_deleted: deleted
493 494 text_tip_task_begin_day: task beginning this day
494 495 text_tip_task_end_day: task ending this day
495 496 text_tip_task_begin_end_day: task beginning and ending this day
496 497 text_project_identifier_info: 'Lower case letters (a-z), numbers and dashes allowed.<br />Once saved, the identifier can not be changed.'
497 498 text_caracters_maximum: %d characters maximum.
498 499 text_length_between: Length between %d and %d characters.
499 500 text_tracker_no_workflow: No workflow defined for this tracker
500 501 text_unallowed_characters: Unallowed characters
501 502 text_comma_separated: Multiple values allowed (comma separated).
502 503 text_issues_ref_in_commit_messages: Referencing and fixing issues in commit messages
503 504 text_issue_added: Issue %s has been reported.
504 505 text_issue_updated: Issue %s has been updated.
505 506 text_wiki_destroy_confirmation: Are you sure you want to delete this wiki and all its content ?
506 507 text_issue_category_destroy_question: Some issues (%d) are assigned to this category. What do you want to do ?
507 508 text_issue_category_destroy_assignments: Remove category assignments
508 509 text_issue_category_reassign_to: Reassign issues to this category
509 510 text_user_mail_option: "For unselected projects, you will only receive notifications about things you watch or you're involved in (eg. issues you're the author or assignee)."
510 511
511 512 default_role_manager: Manager
512 513 default_role_developper: Developer
513 514 default_role_reporter: Reporter
514 515 default_tracker_bug: Bug
515 516 default_tracker_feature: Feature
516 517 default_tracker_support: Support
517 518 default_issue_status_new: New
518 519 default_issue_status_assigned: Assigned
519 520 default_issue_status_resolved: Resolved
520 521 default_issue_status_feedback: Feedback
521 522 default_issue_status_closed: Closed
522 523 default_issue_status_rejected: Rejected
523 524 default_doc_category_user: User documentation
524 525 default_doc_category_tech: Technical documentation
525 526 default_priority_low: Low
526 527 default_priority_normal: Normal
527 528 default_priority_high: High
528 529 default_priority_urgent: Urgent
529 530 default_priority_immediate: Immediate
530 531 default_activity_design: Design
531 532 default_activity_development: Development
532 533
533 534 enumeration_issue_priorities: Issue priorities
534 535 enumeration_doc_categories: Document categories
535 536 enumeration_activities: Activities (time tracking)
@@ -1,535 +1,536
1 1 _gloc_rule_default: '|n| n==1 ? "" : "_plural" '
2 2
3 3 actionview_datehelper_select_day_prefix:
4 4 actionview_datehelper_select_month_names: Janvier,Février,Mars,Avril,Mai,Juin,Juillet,Août,Septembre,Octobre,Novembre,Décembre
5 5 actionview_datehelper_select_month_names_abbr: Jan,Fév,Mars,Avril,Mai,Juin,Juil,Août,Sept,Oct,Nov,Déc
6 6 actionview_datehelper_select_month_prefix:
7 7 actionview_datehelper_select_year_prefix:
8 8 actionview_datehelper_time_in_words_day: 1 jour
9 9 actionview_datehelper_time_in_words_day_plural: %d jours
10 10 actionview_datehelper_time_in_words_hour_about: environ une heure
11 11 actionview_datehelper_time_in_words_hour_about_plural: environ %d heures
12 12 actionview_datehelper_time_in_words_hour_about_single: environ une heure
13 13 actionview_datehelper_time_in_words_minute: 1 minute
14 14 actionview_datehelper_time_in_words_minute_half: 30 secondes
15 15 actionview_datehelper_time_in_words_minute_less_than: moins d'une minute
16 16 actionview_datehelper_time_in_words_minute_plural: %d minutes
17 17 actionview_datehelper_time_in_words_minute_single: 1 minute
18 18 actionview_datehelper_time_in_words_second_less_than: moins d'une seconde
19 19 actionview_datehelper_time_in_words_second_less_than_plural: moins de %d secondes
20 20 actionview_instancetag_blank_option: Choisir
21 21
22 22 activerecord_error_inclusion: n'est pas inclus dans la liste
23 23 activerecord_error_exclusion: est reservé
24 24 activerecord_error_invalid: est invalide
25 25 activerecord_error_confirmation: ne correspond pas à la confirmation
26 26 activerecord_error_accepted: doit être accepté
27 27 activerecord_error_empty: doit être renseigné
28 28 activerecord_error_blank: doit être renseigné
29 29 activerecord_error_too_long: est trop long
30 30 activerecord_error_too_short: est trop court
31 31 activerecord_error_wrong_length: n'est pas de la bonne longueur
32 32 activerecord_error_taken: est déjà utilisé
33 33 activerecord_error_not_a_number: n'est pas un nombre
34 34 activerecord_error_not_a_date: n'est pas une date valide
35 35 activerecord_error_greater_than_start_date: doit être postérieur à la date de début
36 36 activerecord_error_not_same_project: n'appartient pas au même projet
37 37 activerecord_error_circular_dependency: Cette relation créerait une dépendance circulaire
38 38
39 39 general_fmt_age: %d an
40 40 general_fmt_age_plural: %d ans
41 41 general_fmt_date: %%d/%%m/%%Y
42 42 general_fmt_datetime: %%d/%%m/%%Y %%H:%%M
43 43 general_fmt_datetime_short: %%d/%%m %%H:%%M
44 44 general_fmt_time: %%H:%%M
45 45 general_text_No: 'Non'
46 46 general_text_Yes: 'Oui'
47 47 general_text_no: 'non'
48 48 general_text_yes: 'oui'
49 49 general_lang_name: 'Français'
50 50 general_csv_separator: ';'
51 51 general_csv_encoding: ISO-8859-1
52 52 general_pdf_encoding: ISO-8859-1
53 53 general_day_names: Lundi,Mardi,Mercredi,Jeudi,Vendredi,Samedi,Dimanche
54 54 general_first_day_of_week: '1'
55 55
56 56 notice_account_updated: Le compte a été mis à jour avec succès.
57 57 notice_account_invalid_creditentials: Identifiant ou mot de passe invalide.
58 58 notice_account_password_updated: Mot de passe mis à jour avec succès.
59 59 notice_account_wrong_password: Mot de passe incorrect
60 60 notice_account_register_done: Un message contenant les instructions pour activer votre compte vous a été envoyé.
61 61 notice_account_unknown_email: Aucun compte ne correspond à cette adresse.
62 62 notice_can_t_change_password: Ce compte utilise une authentification externe. Impossible de changer le mot de passe.
63 63 notice_account_lost_email_sent: Un message contenant les instructions pour choisir un nouveau mot de passe vous a été envoyé.
64 64 notice_account_activated: Votre compte a été activé. Vous pouvez à présent vous connecter.
65 65 notice_successful_create: Création effectuée avec succès.
66 66 notice_successful_update: Mise à jour effectuée avec succès.
67 67 notice_successful_delete: Suppression effectuée avec succès.
68 68 notice_successful_connection: Connection réussie.
69 69 notice_file_not_found: "La page à laquelle vous souhaitez accéder n'existe pas ou a été supprimée."
70 70 notice_locking_conflict: Les données ont été mises à jour par un autre utilisateur. Mise à jour impossible.
71 71 notice_scm_error: "L'entrée et/ou la révision demandée n'existe pas dans le dépôt."
72 72 notice_not_authorized: "Vous n'êtes pas autorisés à accéder à cette page."
73 73 notice_email_sent: "Un email a été envoyé à %s"
74 74 notice_email_error: "Erreur lors de l'envoi de l'email (%s)"
75 75 notice_feeds_access_key_reseted: Votre clé d'accès aux flux RSS a été réinitialisée.
76 76 notice_failed_to_save_issues: "%d demande(s) sur les %d sélectionnées n'ont pas pu être mise(s) à jour: %s."
77 77 notice_no_issue_selected: "Aucune demande sélectionnée ! Cochez les demandes que vous voulez mettre à jour."
78 78
79 79 mail_subject_lost_password: Votre mot de passe redMine
80 80 mail_body_lost_password: 'Pour changer votre mot de passe Redmine, cliquez sur le lien suivant:'
81 81 mail_subject_register: Activation de votre compte redMine
82 82 mail_body_register: 'Pour activer votre compte Redmine, cliquez sur le lien suivant:'
83 83
84 84 gui_validation_error: 1 erreur
85 85 gui_validation_error_plural: %d erreurs
86 86
87 87 field_name: Nom
88 88 field_description: Description
89 89 field_summary: Résumé
90 90 field_is_required: Obligatoire
91 91 field_firstname: Prénom
92 92 field_lastname: Nom
93 93 field_mail: Email
94 94 field_filename: Fichier
95 95 field_filesize: Taille
96 96 field_downloads: Téléchargements
97 97 field_author: Auteur
98 98 field_created_on: Créé
99 99 field_updated_on: Mis à jour
100 100 field_field_format: Format
101 101 field_is_for_all: Pour tous les projets
102 102 field_possible_values: Valeurs possibles
103 103 field_regexp: Expression régulière
104 104 field_min_length: Longueur minimum
105 105 field_max_length: Longueur maximum
106 106 field_value: Valeur
107 107 field_category: Catégorie
108 108 field_title: Titre
109 109 field_project: Projet
110 110 field_issue: Demande
111 111 field_status: Statut
112 112 field_notes: Notes
113 113 field_is_closed: Demande fermée
114 114 field_is_default: Valeur par défaut
115 115 field_html_color: Couleur
116 116 field_tracker: Tracker
117 117 field_subject: Sujet
118 118 field_due_date: Date d'échéance
119 119 field_assigned_to: Assigné à
120 120 field_priority: Priorité
121 121 field_fixed_version: Version corrigée
122 122 field_user: Utilisateur
123 123 field_role: Rôle
124 124 field_homepage: Site web
125 125 field_is_public: Public
126 126 field_parent: Sous-projet de
127 127 field_is_in_chlog: Demandes affichées dans l'historique
128 128 field_is_in_roadmap: Demandes affichées dans la roadmap
129 129 field_login: Identifiant
130 130 field_mail_notification: Notifications par mail
131 131 field_admin: Administrateur
132 132 field_last_login_on: Dernière connexion
133 133 field_language: Langue
134 134 field_effective_date: Date
135 135 field_password: Mot de passe
136 136 field_new_password: Nouveau mot de passe
137 137 field_password_confirmation: Confirmation
138 138 field_version: Version
139 139 field_type: Type
140 140 field_host: Hôte
141 141 field_port: Port
142 142 field_account: Compte
143 143 field_base_dn: Base DN
144 144 field_attr_login: Attribut Identifiant
145 145 field_attr_firstname: Attribut Prénom
146 146 field_attr_lastname: Attribut Nom
147 147 field_attr_mail: Attribut Email
148 148 field_onthefly: Création des utilisateurs à la volée
149 149 field_start_date: Début
150 150 field_done_ratio: %% Réalisé
151 151 field_auth_source: Mode d'authentification
152 152 field_hide_mail: Cacher mon adresse mail
153 153 field_comments: Commentaire
154 154 field_url: URL
155 155 field_start_page: Page de démarrage
156 156 field_subproject: Sous-projet
157 157 field_hours: Heures
158 158 field_activity: Activité
159 159 field_spent_on: Date
160 160 field_identifier: Identifiant
161 161 field_is_filter: Utilisé comme filtre
162 162 field_issue_to_id: Demande liée
163 163 field_delay: Retard
164 164 field_assignable: Demandes assignables à ce rôle
165 165 field_redirect_existing_links: Rediriger les liens existants
166 166 field_estimated_hours: Temps estimé
167 167 field_column_names: Colonnes
168 168
169 169 setting_app_title: Titre de l'application
170 170 setting_app_subtitle: Sous-titre de l'application
171 171 setting_welcome_text: Texte d'accueil
172 172 setting_default_language: Langue par défaut
173 173 setting_login_required: Authentif. obligatoire
174 174 setting_self_registration: Enregistrement autorisé
175 175 setting_attachment_max_size: Taille max des fichiers
176 176 setting_issues_export_limit: Limite export demandes
177 177 setting_mail_from: Adresse d'émission
178 178 setting_host_name: Nom d'hôte
179 179 setting_text_formatting: Formatage du texte
180 180 setting_wiki_compression: Compression historique wiki
181 181 setting_feeds_limit: Limite du contenu des flux RSS
182 182 setting_autofetch_changesets: Récupération auto. des commits
183 183 setting_sys_api_enabled: Activer les WS pour la gestion des dépôts
184 184 setting_commit_ref_keywords: Mot-clés de référencement
185 185 setting_commit_fix_keywords: Mot-clés de résolution
186 186 setting_autologin: Autologin
187 187 setting_date_format: Format de date
188 188 setting_cross_project_issue_relations: Autoriser les relations entre demandes de différents projets
189 189 setting_issue_list_default_columns: Colonnes affichées par défaut sur la liste des demandes
190 190 setting_repositories_encodings: Encodages des dépôts
191 191 setting_emails_footer: Pied-de-page des emails
192 192
193 193 label_user: Utilisateur
194 194 label_user_plural: Utilisateurs
195 195 label_user_new: Nouvel utilisateur
196 196 label_project: Projet
197 197 label_project_new: Nouveau projet
198 198 label_project_plural: Projets
199 199 label_project_all: Tous les projets
200 200 label_project_latest: Derniers projets
201 201 label_issue: Demande
202 202 label_issue_new: Nouvelle demande
203 203 label_issue_plural: Demandes
204 204 label_issue_view_all: Voir toutes les demandes
205 205 label_document: Document
206 206 label_document_new: Nouveau document
207 207 label_document_plural: Documents
208 208 label_role: Rôle
209 209 label_role_plural: Rôles
210 210 label_role_new: Nouveau rôle
211 211 label_role_and_permissions: Rôles et permissions
212 212 label_member: Membre
213 213 label_member_new: Nouveau membre
214 214 label_member_plural: Membres
215 215 label_tracker: Tracker
216 216 label_tracker_plural: Trackers
217 217 label_tracker_new: Nouveau tracker
218 218 label_workflow: Workflow
219 219 label_issue_status: Statut de demandes
220 220 label_issue_status_plural: Statuts de demandes
221 221 label_issue_status_new: Nouveau statut
222 222 label_issue_category: Catégorie de demandes
223 223 label_issue_category_plural: Catégories de demandes
224 224 label_issue_category_new: Nouvelle catégorie
225 225 label_custom_field: Champ personnalisé
226 226 label_custom_field_plural: Champs personnalisés
227 227 label_custom_field_new: Nouveau champ personnalisé
228 228 label_enumerations: Listes de valeurs
229 229 label_enumeration_new: Nouvelle valeur
230 230 label_information: Information
231 231 label_information_plural: Informations
232 232 label_please_login: Identification
233 233 label_register: S'enregistrer
234 234 label_password_lost: Mot de passe perdu
235 235 label_home: Accueil
236 236 label_my_page: Ma page
237 237 label_my_account: Mon compte
238 238 label_my_projects: Mes projets
239 239 label_administration: Administration
240 240 label_login: Connexion
241 241 label_logout: Déconnexion
242 242 label_help: Aide
243 243 label_reported_issues: Demandes soumises
244 244 label_assigned_to_me_issues: Demandes qui me sont assignées
245 245 label_last_login: Dernière connexion
246 246 label_last_updates: Dernière mise à jour
247 247 label_last_updates_plural: %d dernières mises à jour
248 248 label_registered_on: Inscrit le
249 249 label_activity: Activité
250 250 label_new: Nouveau
251 251 label_logged_as: Connecté en tant que
252 252 label_environment: Environnement
253 253 label_authentication: Authentification
254 254 label_auth_source: Mode d'authentification
255 255 label_auth_source_new: Nouveau mode d'authentification
256 256 label_auth_source_plural: Modes d'authentification
257 257 label_subproject_plural: Sous-projets
258 258 label_min_max_length: Longueurs mini - maxi
259 259 label_list: Liste
260 260 label_date: Date
261 261 label_integer: Entier
262 262 label_float: Nombre décimal
263 263 label_boolean: Booléen
264 264 label_string: Texte
265 265 label_text: Texte long
266 266 label_attribute: Attribut
267 267 label_attribute_plural: Attributs
268 268 label_download: %d Téléchargement
269 269 label_download_plural: %d Téléchargements
270 270 label_no_data: Aucune donnée à afficher
271 271 label_change_status: Changer le statut
272 272 label_history: Historique
273 273 label_attachment: Fichier
274 274 label_attachment_new: Nouveau fichier
275 275 label_attachment_delete: Supprimer le fichier
276 276 label_attachment_plural: Fichiers
277 277 label_report: Rapport
278 278 label_report_plural: Rapports
279 279 label_news: Annonce
280 280 label_news_new: Nouvelle annonce
281 281 label_news_plural: Annonces
282 282 label_news_latest: Dernières annonces
283 283 label_news_view_all: Voir toutes les annonces
284 284 label_change_log: Historique
285 285 label_settings: Configuration
286 286 label_overview: Aperçu
287 287 label_version: Version
288 288 label_version_new: Nouvelle version
289 289 label_version_plural: Versions
290 290 label_confirmation: Confirmation
291 291 label_export_to: Exporter en
292 292 label_read: Lire...
293 293 label_public_projects: Projets publics
294 294 label_open_issues: ouvert
295 295 label_open_issues_plural: ouverts
296 296 label_closed_issues: fermé
297 297 label_closed_issues_plural: fermés
298 298 label_total: Total
299 299 label_permissions: Permissions
300 300 label_current_status: Statut actuel
301 301 label_new_statuses_allowed: Nouveaux statuts autorisés
302 302 label_all: tous
303 303 label_none: aucun
304 304 label_nobody: personne
305 305 label_next: Suivant
306 306 label_previous: Précédent
307 307 label_used_by: Utilisé par
308 308 label_details: Détails
309 309 label_add_note: Ajouter une note
310 310 label_per_page: Par page
311 311 label_calendar: Calendrier
312 312 label_months_from: mois depuis
313 313 label_gantt: Gantt
314 314 label_internal: Interne
315 315 label_last_changes: %d derniers changements
316 316 label_change_view_all: Voir tous les changements
317 317 label_personalize_page: Personnaliser cette page
318 318 label_comment: Commentaire
319 319 label_comment_plural: Commentaires
320 320 label_comment_add: Ajouter un commentaire
321 321 label_comment_added: Commentaire ajouté
322 322 label_comment_delete: Supprimer les commentaires
323 323 label_query: Rapport personnalisé
324 324 label_query_plural: Rapports personnalisés
325 325 label_query_new: Nouveau rapport
326 326 label_filter_add: Ajouter le filtre
327 327 label_filter_plural: Filtres
328 328 label_equals: égal
329 329 label_not_equals: différent
330 330 label_in_less_than: dans moins de
331 331 label_in_more_than: dans plus de
332 332 label_in: dans
333 333 label_today: aujourd'hui
334 334 label_this_week: cette semaine
335 335 label_less_than_ago: il y a moins de
336 336 label_more_than_ago: il y a plus de
337 337 label_ago: il y a
338 338 label_contains: contient
339 339 label_not_contains: ne contient pas
340 340 label_day_plural: jours
341 341 label_repository: Dépôt
342 342 label_browse: Parcourir
343 343 label_modification: %d modification
344 344 label_modification_plural: %d modifications
345 345 label_revision: Révision
346 346 label_revision_plural: Révisions
347 347 label_added: ajouté
348 348 label_modified: modifié
349 349 label_deleted: supprimé
350 350 label_latest_revision: Dernière révision
351 351 label_latest_revision_plural: Dernières révisions
352 352 label_view_revisions: Voir les révisions
353 353 label_max_size: Taille maximale
354 354 label_on: sur
355 355 label_sort_highest: Remonter en premier
356 356 label_sort_higher: Remonter
357 357 label_sort_lower: Descendre
358 358 label_sort_lowest: Descendre en dernier
359 359 label_roadmap: Roadmap
360 360 label_roadmap_due_in: Echéance dans
361 361 label_roadmap_overdue: En retard de %s
362 362 label_roadmap_no_issues: Aucune demande pour cette version
363 363 label_search: Recherche
364 364 label_result_plural: Résultats
365 365 label_all_words: Tous les mots
366 366 label_wiki: Wiki
367 367 label_wiki_edit: Révision wiki
368 368 label_wiki_edit_plural: Révisions wiki
369 369 label_wiki_page: Page wiki
370 370 label_wiki_page_plural: Pages wiki
371 371 label_index_by_title: Index par titre
372 372 label_index_by_date: Index par date
373 373 label_current_version: Version actuelle
374 374 label_preview: Prévisualisation
375 375 label_feed_plural: Flux RSS
376 376 label_changes_details: Détails de tous les changements
377 377 label_issue_tracking: Suivi des demandes
378 378 label_spent_time: Temps passé
379 379 label_f_hour: %.2f heure
380 380 label_f_hour_plural: %.2f heures
381 381 label_time_tracking: Suivi du temps
382 382 label_change_plural: Changements
383 383 label_statistics: Statistiques
384 384 label_commits_per_month: Commits par mois
385 385 label_commits_per_author: Commits par auteur
386 386 label_view_diff: Voir les différences
387 387 label_diff_inline: en ligne
388 388 label_diff_side_by_side: côte à côte
389 389 label_options: Options
390 390 label_copy_workflow_from: Copier le workflow de
391 391 label_permissions_report: Synthèse des permissions
392 392 label_watched_issues: Demandes surveillées
393 393 label_related_issues: Demandes liées
394 394 label_applied_status: Statut appliqué
395 395 label_loading: Chargement...
396 396 label_relation_new: Nouvelle relation
397 397 label_relation_delete: Supprimer la relation
398 398 label_relates_to: lié à
399 399 label_duplicates: doublon de
400 400 label_blocks: bloque
401 401 label_blocked_by: bloqué par
402 402 label_precedes: précède
403 403 label_follows: suit
404 404 label_end_to_start: fin à début
405 405 label_end_to_end: fin à fin
406 406 label_start_to_start: début à début
407 407 label_start_to_end: début à fin
408 408 label_stay_logged_in: Rester connecté
409 409 label_disabled: désactivé
410 410 label_show_completed_versions: Voire les versions passées
411 411 label_me: moi
412 412 label_board: Forum
413 413 label_board_new: Nouveau forum
414 414 label_board_plural: Forums
415 415 label_topic_plural: Discussions
416 416 label_message_plural: Messages
417 417 label_message_last: Dernier message
418 418 label_message_new: Nouveau message
419 419 label_reply_plural: Réponses
420 420 label_send_information: Envoyer les informations à l'utilisateur
421 421 label_year: Année
422 422 label_month: Mois
423 423 label_week: Semaine
424 424 label_date_from: Du
425 425 label_date_to: Au
426 426 label_language_based: Basé sur la langue
427 427 label_sort_by: Trier par "%s"
428 428 label_send_test_email: Envoyer un email de test
429 429 label_feeds_access_key_created_on: Clé d'accès RSS créée il y a %s
430 430 label_module_plural: Modules
431 431 label_added_time_by: Ajouté par %s il y a %s
432 432 label_updated_time: Mis à jour il y a %s
433 433 label_jump_to_a_project: Aller à un projet...
434 434 label_file_plural: Fichiers
435 435 label_changeset_plural: Révisions
436 436 label_default_columns: Colonnes par défaut
437 437 label_no_change_option: (Pas de changement)
438 438 label_bulk_edit_selected_issues: Modifier les demandes sélectionnées
439 439 label_theme: Thème
440 440 label_default: Défaut
441 441 label_search_titles_only: Uniquement dans les titres
442 442 label_user_mail_option_all: "Pour tous les événements de tous mes projets"
443 443 label_user_mail_option_selected: "Pour tous les événements des projets sélectionnés..."
444 444 label_user_mail_option_none: "Seulement pour ce que je surveille ou à quoi je participe"
445 445
446 446 button_login: Connexion
447 447 button_submit: Soumettre
448 448 button_save: Sauvegarder
449 449 button_check_all: Tout cocher
450 450 button_uncheck_all: Tout décocher
451 451 button_delete: Supprimer
452 452 button_create: Créer
453 453 button_test: Tester
454 454 button_edit: Modifier
455 455 button_add: Ajouter
456 456 button_change: Changer
457 457 button_apply: Appliquer
458 458 button_clear: Effacer
459 459 button_lock: Verrouiller
460 460 button_unlock: Déverrouiller
461 461 button_download: Télécharger
462 462 button_list: Lister
463 463 button_view: Voir
464 464 button_move: Déplacer
465 465 button_back: Retour
466 466 button_cancel: Annuler
467 467 button_activate: Activer
468 468 button_sort: Trier
469 469 button_log_time: Saisir temps
470 470 button_rollback: Revenir à cette version
471 471 button_watch: Surveiller
472 472 button_unwatch: Ne plus surveiller
473 473 button_reply: Répondre
474 474 button_archive: Archiver
475 475 button_unarchive: Désarchiver
476 476 button_reset: Réinitialiser
477 477 button_rename: Renommer
478 478 button_change_password: Changer de mot de passe
479 button_copy: Copier
479 480
480 481 status_active: actif
481 482 status_registered: enregistré
482 483 status_locked: vérouillé
483 484
484 485 text_select_mail_notifications: Sélectionner les actions pour lesquelles la notification par mail doit être activée.
485 486 text_regexp_info: ex. ^[A-Z0-9]+$
486 487 text_min_max_length_info: 0 pour aucune restriction
487 488 text_project_destroy_confirmation: Etes-vous sûr de vouloir supprimer ce projet et tout ce qui lui est rattaché ?
488 489 text_workflow_edit: Sélectionner un tracker et un rôle pour éditer le workflow
489 490 text_are_you_sure: Etes-vous sûr ?
490 491 text_journal_changed: changé de %s à %s
491 492 text_journal_set_to: mis à %s
492 493 text_journal_deleted: supprimé
493 494 text_tip_task_begin_day: tâche commençant ce jour
494 495 text_tip_task_end_day: tâche finissant ce jour
495 496 text_tip_task_begin_end_day: tâche commençant et finissant ce jour
496 497 text_project_identifier_info: 'Lettres minuscules (a-z), chiffres et tirets autorisés.<br />Un fois sauvegardé, l''identifiant ne pourra plus être modifié.'
497 498 text_caracters_maximum: %d caractères maximum.
498 499 text_length_between: Longueur comprise entre %d et %d caractères.
499 500 text_tracker_no_workflow: Aucun worflow n'est défini pour ce tracker
500 501 text_unallowed_characters: Caractères non autorisés
501 502 text_comma_separated: Plusieurs valeurs possibles (séparées par des virgules).
502 503 text_issues_ref_in_commit_messages: Référencement et résolution des demandes dans les commentaires de commits
503 504 text_issue_added: La demande %s a été soumise.
504 505 text_issue_updated: La demande %s a été mise à jour.
505 506 text_wiki_destroy_confirmation: Etes-vous sûr de vouloir supprimer ce wiki et tout son contenu ?
506 507 text_issue_category_destroy_question: Des demandes (%d) sont affectées à cette catégories. Que voulez-vous faire ?
507 508 text_issue_category_destroy_assignments: N'affecter les demandes à aucune autre catégorie
508 509 text_issue_category_reassign_to: Réaffecter les demandes à cette catégorie
509 510 text_user_mail_option: "Pour les projets non sélectionnés, vous recevrez seulement des notifications pour ce que vous surveillez ou à quoi vous participez (exemple: demandes dont vous êtes l'auteur ou la personne assignée)."
510 511
511 512 default_role_manager: Manager
512 513 default_role_developper: Développeur
513 514 default_role_reporter: Rapporteur
514 515 default_tracker_bug: Anomalie
515 516 default_tracker_feature: Evolution
516 517 default_tracker_support: Assistance
517 518 default_issue_status_new: Nouveau
518 519 default_issue_status_assigned: Assigné
519 520 default_issue_status_resolved: Résolu
520 521 default_issue_status_feedback: Commentaire
521 522 default_issue_status_closed: Fermé
522 523 default_issue_status_rejected: Rejeté
523 524 default_doc_category_user: Documentation utilisateur
524 525 default_doc_category_tech: Documentation technique
525 526 default_priority_low: Bas
526 527 default_priority_normal: Normal
527 528 default_priority_high: Haut
528 529 default_priority_urgent: Urgent
529 530 default_priority_immediate: Immédiat
530 531 default_activity_design: Conception
531 532 default_activity_development: Développement
532 533
533 534 enumeration_issue_priorities: Priorités des demandes
534 535 enumeration_doc_categories: Catégories des documents
535 536 enumeration_activities: Activités (suivi du temps)
@@ -1,478 +1,479
1 1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
2 2
3 3 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
4 4 h1 {margin:0; padding:0; font-size: 24px;}
5 5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
7 7 h4, .wiki h3 {font-size: 12px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
8 8
9 9 /***** Layout *****/
10 10 #top-menu {background: #2C4056;color: #fff;height:1.5em; padding: 2px 6px 0px 6px;}
11 11 #top-menu a {color: #fff; padding-right: 4px;}
12 12 #account {float:right;}
13 13
14 14 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
15 15 #header a {color:#f8f8f8;}
16 16 #quick-search {float:right;}
17 17
18 18 #main-menu {position: absolute; bottom: 0px; left:6px;}
19 19 #main-menu ul {margin: 0; padding: 0;}
20 20 #main-menu li {
21 21 float:left;
22 22 list-style-type:none;
23 23 margin: 0px 10px 0px 0px;
24 24 padding: 0px 0px 0px 0px;
25 25 white-space:nowrap;
26 26 }
27 27 #main-menu li a {
28 28 display: block;
29 29 color: #fff;
30 30 text-decoration: none;
31 31 margin: 0;
32 32 padding: 4px 4px 4px 4px;
33 33 background: #2C4056;
34 34 }
35 35 #main-menu li a:hover {background:#759FCF;}
36 36
37 37 #main {background: url(../images/mainbg.png) repeat-x; background-color:#EEEEEE;}
38 38
39 39 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
40 40 * html #sidebar{ width: 17%; }
41 41 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
42 42 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
43 43 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
44 44
45 45 #content { width: 80%; background: url(../images/contentbg.png) repeat-x; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; height:600px; min-height: 600px;}
46 46 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
47 47 html>body #content {
48 48 height: auto;
49 49 min-height: 600px;
50 50 }
51 51
52 52 #main.nosidebar #sidebar{ display: none; }
53 53 #main.nosidebar #content{ width: auto; border-right: 0; }
54 54
55 55 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
56 56
57 57 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
58 58 #login-form table td {padding: 6px;}
59 59 #login-form label {font-weight: bold;}
60 60
61 61 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
62 62
63 63 /***** Links *****/
64 64 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
65 65 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
66 66 a img{ border: 0; }
67 67
68 68 /***** Tables *****/
69 69 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
70 70 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
71 71 table.list td { overflow: hidden; text-overflow: ellipsis; vertical-align: top;}
72 72 table.list td.id { width: 2%; text-align: center;}
73 73 table.list td.checkbox { width: 15px; padding: 0px;}
74 74
75 75 tr.issue { text-align: center; white-space: nowrap; }
76 76 tr.issue td.subject, tr.issue td.category { white-space: normal; }
77 77 tr.issue td.subject { text-align: left; }
78 78
79 79 table.list tbody tr:hover { background-color:#ffffdd; }
80 80 table td {padding:2px;}
81 81 table p {margin:0;}
82 82 .odd {background-color:#f6f7f8;}
83 83 .even {background-color: #fff;}
84 84
85 85 .highlight { background-color: #FCFD8D;}
86 86 .highlight.token-1 { background-color: #faa;}
87 87 .highlight.token-2 { background-color: #afa;}
88 88 .highlight.token-3 { background-color: #aaf;}
89 89
90 90 .box{
91 91 padding:6px;
92 92 margin-bottom: 10px;
93 93 background-color:#f6f6f6;
94 94 color:#505050;
95 95 line-height:1.5em;
96 96 border: 1px solid #e4e4e4;
97 97 }
98 98
99 99 div.square {
100 100 border: 1px solid #999;
101 101 float: left;
102 102 margin: .3em .4em 0 .4em;
103 103 overflow: hidden;
104 104 width: .6em; height: .6em;
105 105 }
106 106
107 107 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px;font-size:0.9em;}
108 108 .splitcontentleft{float:left; width:49%;}
109 109 .splitcontentright{float:right; width:49%;}
110 110 form {display: inline;}
111 111 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
112 112 fieldset {border: 1px solid #e4e4e4; margin:0;}
113 113 legend {color: #484848;}
114 114 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
115 115 textarea.wiki-edit { width: 99%; }
116 116 li p {margin-top: 0;}
117 117 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
118 118 .autoscroll {overflow-x: auto; padding:1px; width:100%;}
119 119 #user_firstname, #user_lastname, #user_mail, #notification_option { width: 90%; }
120 120
121 121 /***** Tabular forms ******/
122 122 .tabular p{
123 123 margin: 0;
124 124 padding: 5px 0 8px 0;
125 125 padding-left: 180px; /*width of left column containing the label elements*/
126 126 height: 1%;
127 127 clear:left;
128 128 }
129 129
130 130 .tabular label{
131 131 font-weight: bold;
132 132 float: left;
133 133 text-align: right;
134 134 margin-left: -180px; /*width of left column*/
135 135 width: 175px; /*width of labels. Should be smaller than left column to create some right
136 136 margin*/
137 137 }
138 138
139 139 .tabular label.floating{
140 140 font-weight: normal;
141 141 margin-left: 0px;
142 142 text-align: left;
143 143 width: 200px;
144 144 }
145 145
146 146 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
147 147
148 148 #settings .tabular p{ padding-left: 300px; }
149 149 #settings .tabular label{ margin-left: -300px; width: 295px; }
150 150
151 151 .required {color: #bb0000;}
152 152 .summary {font-style: italic;}
153 153
154 154 div.attachments p { margin:4px 0 2px 0; }
155 155
156 156 /***** Flash & error messages ****/
157 157 #errorExplanation, div.flash, .nodata {
158 158 padding: 4px 4px 4px 30px;
159 159 margin-bottom: 12px;
160 160 font-size: 1.1em;
161 161 border: 2px solid;
162 162 }
163 163
164 164 div.flash {margin-top: 8px;}
165 165
166 166 div.flash.error, #errorExplanation {
167 167 background: url(../images/false.png) 8px 5px no-repeat;
168 168 background-color: #ffe3e3;
169 169 border-color: #dd0000;
170 170 color: #550000;
171 171 }
172 172
173 173 div.flash.notice {
174 174 background: url(../images/true.png) 8px 5px no-repeat;
175 175 background-color: #dfffdf;
176 176 border-color: #9fcf9f;
177 177 color: #005f00;
178 178 }
179 179
180 180 .nodata {
181 181 text-align: center;
182 182 background-color: #FFEBC1;
183 183 border-color: #FDBF3B;
184 184 color: #A6750C;
185 185 }
186 186
187 187 #errorExplanation ul { font-size: 0.9em;}
188 188
189 189 /***** Ajax indicator ******/
190 190 #ajax-indicator {
191 191 position: absolute; /* fixed not supported by IE */
192 192 background-color:#eee;
193 193 border: 1px solid #bbb;
194 194 top:35%;
195 195 left:40%;
196 196 width:20%;
197 197 font-weight:bold;
198 198 text-align:center;
199 199 padding:0.6em;
200 200 z-index:100;
201 201 filter:alpha(opacity=50);
202 202 -moz-opacity:0.5;
203 203 opacity: 0.5;
204 204 -khtml-opacity: 0.5;
205 205 }
206 206
207 207 html>body #ajax-indicator { position: fixed; }
208 208
209 209 #ajax-indicator span {
210 210 background-position: 0% 40%;
211 211 background-repeat: no-repeat;
212 212 background-image: url(../images/loading.gif);
213 213 padding-left: 26px;
214 214 vertical-align: bottom;
215 215 }
216 216
217 217 /***** Calendar *****/
218 218 table.cal {border-collapse: collapse; width: 100%; margin: 8px 0 6px 0;border: 1px solid #d7d7d7;}
219 219 table.cal thead th {width: 14%;}
220 220 table.cal tbody tr {height: 100px;}
221 221 table.cal th { background-color:#EEEEEE; padding: 4px; }
222 222 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
223 223 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
224 224 table.cal td.odd p.day-num {color: #bbb;}
225 225 table.cal td.today {background:#ffffdd;}
226 226 table.cal td.today p.day-num {font-weight: bold;}
227 227
228 228 /***** Tooltips ******/
229 229 .tooltip{position:relative;z-index:24;}
230 230 .tooltip:hover{z-index:25;color:#000;}
231 231 .tooltip span.tip{display: none; text-align:left;}
232 232
233 233 div.tooltip:hover span.tip{
234 234 display:block;
235 235 position:absolute;
236 236 top:12px; left:24px; width:270px;
237 237 border:1px solid #555;
238 238 background-color:#fff;
239 239 padding: 4px;
240 240 font-size: 0.8em;
241 241 color:#505050;
242 242 }
243 243
244 244 /***** Progress bar *****/
245 245 .progress {
246 246 border: 1px solid #D7D7D7;
247 247 border-collapse: collapse;
248 248 border-spacing: 0pt;
249 249 empty-cells: show;
250 250 padding: 3px;
251 251 width: 40em;
252 252 text-align: center;
253 253 }
254 254
255 255 .progress td { height: 1em; }
256 256 .progress .closed { background: #BAE0BA none repeat scroll 0%; }
257 257 .progress .open { background: #FFF none repeat scroll 0%; }
258 258
259 259 /***** Tabs *****/
260 260 #content .tabs{height: 2.6em;}
261 261 #content .tabs ul{margin:0;}
262 262 #content .tabs ul li{
263 263 float:left;
264 264 list-style-type:none;
265 265 white-space:nowrap;
266 266 margin-right:8px;
267 267 background:#fff;
268 268 }
269 269 #content .tabs ul li a{
270 270 display:block;
271 271 font-size: 0.9em;
272 272 text-decoration:none;
273 273 line-height:1em;
274 274 padding:4px;
275 275 border: 1px solid #c0c0c0;
276 276 }
277 277
278 278 #content .tabs ul li a.selected, #content .tabs ul li a:hover{
279 279 background-color: #507AAA;
280 280 border: 1px solid #507AAA;
281 281 color: #fff;
282 282 text-decoration:none;
283 283 }
284 284
285 285 /***** Diff *****/
286 286 .diff_out { background: #fcc; }
287 287 .diff_in { background: #cfc; }
288 288
289 289 /***** Wiki *****/
290 290 div.wiki table {
291 291 border: 1px solid #505050;
292 292 border-collapse: collapse;
293 293 }
294 294
295 295 div.wiki table, div.wiki td, div.wiki th {
296 296 border: 1px solid #bbb;
297 297 padding: 4px;
298 298 }
299 299
300 300 div.wiki .external {
301 301 background-position: 0% 60%;
302 302 background-repeat: no-repeat;
303 303 padding-left: 12px;
304 304 background-image: url(../images/external.png);
305 305 }
306 306
307 307 div.wiki a.new {
308 308 color: #b73535;
309 309 }
310 310
311 311 div.wiki pre {
312 312 margin: 1em 1em 1em 1.6em;
313 313 padding: 2px;
314 314 background-color: #fafafa;
315 315 border: 1px solid #dadada;
316 316 width:95%;
317 317 overflow-x: auto;
318 318 }
319 319
320 320 div.wiki div.toc {
321 321 background-color: #ffffdd;
322 322 border: 1px solid #e4e4e4;
323 323 padding: 4px;
324 324 line-height: 1.2em;
325 325 margin-bottom: 12px;
326 326 margin-right: 12px;
327 327 display: table
328 328 }
329 329 * html div.wiki div.toc { width: 50%; } /* IE6 doesn't autosize div */
330 330
331 331 div.wiki div.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
332 332 div.wiki div.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
333 333
334 334 div.wiki div.toc a {
335 335 display: block;
336 336 font-size: 0.9em;
337 337 font-weight: normal;
338 338 text-decoration: none;
339 339 color: #606060;
340 340 }
341 341 div.wiki div.toc a:hover { color: #c61a1a; text-decoration: underline;}
342 342
343 343 div.wiki div.toc a.heading2 { margin-left: 6px; }
344 344 div.wiki div.toc a.heading3 { margin-left: 12px; font-size: 0.8em; }
345 345
346 346 /***** My page layout *****/
347 347 .block-receiver {
348 348 border:1px dashed #c0c0c0;
349 349 margin-bottom: 20px;
350 350 padding: 15px 0 15px 0;
351 351 }
352 352
353 353 .mypage-box {
354 354 margin:0 0 20px 0;
355 355 color:#505050;
356 356 line-height:1.5em;
357 357 }
358 358
359 359 .handle {
360 360 cursor: move;
361 361 }
362 362
363 363 a.close-icon {
364 364 display:block;
365 365 margin-top:3px;
366 366 overflow:hidden;
367 367 width:12px;
368 368 height:12px;
369 369 background-repeat: no-repeat;
370 370 cursor:pointer;
371 371 background-image:url('../images/close.png');
372 372 }
373 373
374 374 a.close-icon:hover {
375 375 background-image:url('../images/close_hl.png');
376 376 }
377 377
378 378 /***** Gantt chart *****/
379 379 .gantt_hdr {
380 380 position:absolute;
381 381 top:0;
382 382 height:16px;
383 383 border-top: 1px solid #c0c0c0;
384 384 border-bottom: 1px solid #c0c0c0;
385 385 border-right: 1px solid #c0c0c0;
386 386 text-align: center;
387 387 overflow: hidden;
388 388 }
389 389
390 390 .task {
391 391 position: absolute;
392 392 height:8px;
393 393 font-size:0.8em;
394 394 color:#888;
395 395 padding:0;
396 396 margin:0;
397 397 line-height:0.8em;
398 398 }
399 399
400 400 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
401 401 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
402 402 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
403 403 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
404 404
405 405 /***** Icons *****/
406 406 .icon {
407 407 background-position: 0% 40%;
408 408 background-repeat: no-repeat;
409 409 padding-left: 20px;
410 410 padding-top: 2px;
411 411 padding-bottom: 3px;
412 412 }
413 413
414 414 .icon22 {
415 415 background-position: 0% 40%;
416 416 background-repeat: no-repeat;
417 417 padding-left: 26px;
418 418 line-height: 22px;
419 419 vertical-align: middle;
420 420 }
421 421
422 422 .icon-add { background-image: url(../images/add.png); }
423 423 .icon-edit { background-image: url(../images/edit.png); }
424 .icon-copy { background-image: url(../images/copy.png); }
424 425 .icon-del { background-image: url(../images/delete.png); }
425 426 .icon-move { background-image: url(../images/move.png); }
426 427 .icon-save { background-image: url(../images/save.png); }
427 428 .icon-cancel { background-image: url(../images/cancel.png); }
428 429 .icon-pdf { background-image: url(../images/pdf.png); }
429 430 .icon-csv { background-image: url(../images/csv.png); }
430 431 .icon-html { background-image: url(../images/html.png); }
431 432 .icon-image { background-image: url(../images/image.png); }
432 433 .icon-txt { background-image: url(../images/txt.png); }
433 434 .icon-file { background-image: url(../images/file.png); }
434 435 .icon-folder { background-image: url(../images/folder.png); }
435 436 .open .icon-folder { background-image: url(../images/folder_open.png); }
436 437 .icon-package { background-image: url(../images/package.png); }
437 438 .icon-home { background-image: url(../images/home.png); }
438 439 .icon-user { background-image: url(../images/user.png); }
439 440 .icon-mypage { background-image: url(../images/user_page.png); }
440 441 .icon-admin { background-image: url(../images/admin.png); }
441 442 .icon-projects { background-image: url(../images/projects.png); }
442 443 .icon-logout { background-image: url(../images/logout.png); }
443 444 .icon-help { background-image: url(../images/help.png); }
444 445 .icon-attachment { background-image: url(../images/attachment.png); }
445 446 .icon-index { background-image: url(../images/index.png); }
446 447 .icon-history { background-image: url(../images/history.png); }
447 448 .icon-feed { background-image: url(../images/feed.png); }
448 449 .icon-time { background-image: url(../images/time.png); }
449 450 .icon-stats { background-image: url(../images/stats.png); }
450 451 .icon-warning { background-image: url(../images/warning.png); }
451 452 .icon-fav { background-image: url(../images/fav.png); }
452 453 .icon-fav-off { background-image: url(../images/fav_off.png); }
453 454 .icon-reload { background-image: url(../images/reload.png); }
454 455 .icon-lock { background-image: url(../images/locked.png); }
455 456 .icon-unlock { background-image: url(../images/unlock.png); }
456 457 .icon-note { background-image: url(../images/note.png); }
457 458 .icon-checked { background-image: url(../images/true.png); }
458 459
459 460 .icon22-projects { background-image: url(../images/22x22/projects.png); }
460 461 .icon22-users { background-image: url(../images/22x22/users.png); }
461 462 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
462 463 .icon22-role { background-image: url(../images/22x22/role.png); }
463 464 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
464 465 .icon22-options { background-image: url(../images/22x22/options.png); }
465 466 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
466 467 .icon22-authent { background-image: url(../images/22x22/authent.png); }
467 468 .icon22-info { background-image: url(../images/22x22/info.png); }
468 469 .icon22-comment { background-image: url(../images/22x22/comment.png); }
469 470 .icon22-package { background-image: url(../images/22x22/package.png); }
470 471 .icon22-settings { background-image: url(../images/22x22/settings.png); }
471 472 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
472 473
473 474 /***** Media print specific styles *****/
474 475 @media print {
475 476 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual { display:none; }
476 477 #main { background: #fff; }
477 478 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; }
478 479 }
@@ -1,146 +1,165
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.dirname(__FILE__) + '/../test_helper'
19 19 require 'projects_controller'
20 20
21 21 # Re-raise errors caught by the controller.
22 22 class ProjectsController; def rescue_action(e) raise e end; end
23 23
24 24 class ProjectsControllerTest < Test::Unit::TestCase
25 fixtures :projects, :users, :roles, :enabled_modules, :enumerations
25 fixtures :projects, :users, :roles, :members, :issues, :enabled_modules, :enumerations
26 26
27 27 def setup
28 28 @controller = ProjectsController.new
29 29 @request = ActionController::TestRequest.new
30 30 @response = ActionController::TestResponse.new
31 31 end
32 32
33 33 def test_index
34 34 get :index
35 35 assert_response :success
36 36 assert_template 'list'
37 37 end
38 38
39 39 def test_list
40 40 get :list
41 41 assert_response :success
42 42 assert_template 'list'
43 43 assert_not_nil assigns(:project_tree)
44 44 end
45 45
46 46 def test_show
47 47 get :show, :id => 1
48 48 assert_response :success
49 49 assert_template 'show'
50 50 assert_not_nil assigns(:project)
51 51 end
52 52
53 53 def test_list_documents
54 54 get :list_documents, :id => 1
55 55 assert_response :success
56 56 assert_template 'list_documents'
57 57 assert_not_nil assigns(:documents)
58 58 end
59 59
60 60 def test_list_issues
61 61 get :list_issues, :id => 1
62 62 assert_response :success
63 63 assert_template 'list_issues'
64 64 assert_not_nil assigns(:issues)
65 65 end
66 66
67 67 def test_list_issues_with_filter
68 68 get :list_issues, :id => 1, :set_filter => 1
69 69 assert_response :success
70 70 assert_template 'list_issues'
71 71 assert_not_nil assigns(:issues)
72 72 end
73 73
74 74 def test_list_issues_reset_filter
75 75 post :list_issues, :id => 1
76 76 assert_response :success
77 77 assert_template 'list_issues'
78 78 assert_not_nil assigns(:issues)
79 79 end
80 80
81 81 def test_export_issues_csv
82 82 get :export_issues_csv, :id => 1
83 83 assert_response :success
84 84 assert_not_nil assigns(:issues)
85 85 end
86 86
87 87 def test_bulk_edit_issues
88 88 @request.session[:user_id] = 2
89 89 # update issues priority
90 90 post :bulk_edit_issues, :id => 1, :issue_ids => [1, 2], :priority_id => 7, :notes => 'Bulk editing', :assigned_to_id => ''
91 91 assert_response 302
92 92 # check that the issues were updated
93 93 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
94 94 assert_equal 'Bulk editing', Issue.find(1).journals.find(:first, :order => 'created_on DESC').notes
95 95 end
96 96
97 97 def test_list_news
98 98 get :list_news, :id => 1
99 99 assert_response :success
100 100 assert_template 'list_news'
101 101 assert_not_nil assigns(:newss)
102 102 end
103 103
104 104 def test_list_files
105 105 get :list_files, :id => 1
106 106 assert_response :success
107 107 assert_template 'list_files'
108 108 assert_not_nil assigns(:versions)
109 109 end
110 110
111 111 def test_changelog
112 112 get :changelog, :id => 1
113 113 assert_response :success
114 114 assert_template 'changelog'
115 115 assert_not_nil assigns(:versions)
116 116 end
117 117
118 118 def test_roadmap
119 119 get :roadmap, :id => 1
120 120 assert_response :success
121 121 assert_template 'roadmap'
122 122 assert_not_nil assigns(:versions)
123 123 end
124 124
125 125 def test_activity
126 126 get :activity, :id => 1
127 127 assert_response :success
128 128 assert_template 'activity'
129 129 assert_not_nil assigns(:events_by_day)
130 130 end
131 131
132 132 def test_archive
133 133 @request.session[:user_id] = 1 # admin
134 134 post :archive, :id => 1
135 135 assert_redirected_to 'admin/projects'
136 136 assert !Project.find(1).active?
137 137 end
138 138
139 139 def test_unarchive
140 140 @request.session[:user_id] = 1 # admin
141 141 Project.find(1).archive
142 142 post :unarchive, :id => 1
143 143 assert_redirected_to 'admin/projects'
144 144 assert Project.find(1).active?
145 145 end
146
147 def test_add_issue
148 @request.session[:user_id] = 2
149 get :add_issue, :id => 1, :tracker_id => 1
150 assert_response :success
151 assert_template 'add_issue'
152 post :add_issue, :id => 1, :issue => {:tracker_id => 1, :subject => 'This is the test_add_issue issue', :description => 'This is the description', :priority_id => 5}
153 assert_redirected_to 'projects/list_issues'
154 assert Issue.find_by_subject('This is the test_add_issue issue')
155 end
156
157 def test_copy_issue
158 @request.session[:user_id] = 2
159 get :add_issue, :id => 1, :copy_from => 1
160 assert_template 'add_issue'
161 assert_not_nil assigns(:issue)
162 orig = Issue.find(1)
163 assert_equal orig.subject, assigns(:issue).subject
164 end
146 165 end
@@ -1,52 +1,62
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.dirname(__FILE__) + '/../test_helper'
19 19
20 20 class IssueTest < Test::Unit::TestCase
21 fixtures :projects, :users, :members, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues
21 fixtures :projects, :users, :members, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values
22 22
23 23 def test_category_based_assignment
24 24 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'Assignment test', :description => 'Assignment test', :category_id => 1)
25 25 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
26 26 end
27 27
28 def test_copy
29 issue = Issue.new.copy_from(1)
30 assert issue.save
31 issue.reload
32 orig = Issue.find(1)
33 assert_equal orig.subject, issue.subject
34 assert_equal orig.tracker, issue.tracker
35 assert_equal orig.custom_values.first.value, issue.custom_values.first.value
36 end
37
28 38 def test_close_duplicates
29 39 # Create 3 issues
30 40 issue1 = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :priority => Enumeration.get_values('IPRI').first, :subject => 'Duplicates test', :description => 'Duplicates test')
31 41 assert issue1.save
32 42 issue2 = issue1.clone
33 43 assert issue2.save
34 44 issue3 = issue1.clone
35 45 assert issue3.save
36 46
37 47 # 2 is a dupe of 1
38 48 IssueRelation.create(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_DUPLICATES)
39 49 # And 3 is a dupe of 2
40 50 IssueRelation.create(:issue_from => issue2, :issue_to => issue3, :relation_type => IssueRelation::TYPE_DUPLICATES)
41 51
42 52 assert issue1.reload.duplicates.include?(issue2)
43 53
44 54 # Closing issue 1
45 55 issue1.init_journal(User.find(:first), "Closing issue1")
46 56 issue1.status = IssueStatus.find :first, :conditions => {:is_closed => true}
47 57 assert issue1.save
48 58 # 2 and 3 should be also closed
49 59 assert issue2.reload.closed?
50 60 assert issue3.reload.closed?
51 61 end
52 62 end
General Comments 0
You need to be logged in to leave comments. Login now