##// END OF EJS Templates
improved issues change history...
Jean-Philippe Lang -
r52:42181112ff73
parent child
Show More
@@ -0,0 +1,22
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 class Journal < ActiveRecord::Base
19 belongs_to :journalized, :polymorphic => true
20 belongs_to :user
21 has_many :details, :class_name => "JournalDetail", :dependent => true
22 end
@@ -0,0 +1,20
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18 class JournalDetail < ActiveRecord::Base
19 belongs_to :journal
20 end
@@ -0,0 +1,11
1 <% for journal in journals %>
2 <h4><%= format_time(journal.created_on) %> - <%= journal.user.name %></h4>
3 <ul>
4 <% for detail in journal.details %>
5 <li><%= show_detail(detail) %></li>
6 <% end %>
7 </ul>
8 <% if journal.notes? %>
9 <%= simple_format auto_link journal.notes %>
10 <% end %>
11 <% end %>
@@ -0,0 +1,6
1 <h3><%=l(:label_history)%></h3>
2 <div id="history">
3 <%= render :partial => 'history', :locals => { :journals => @journals } %>
4 </div>
5 <br />
6 <p><%= link_to l(:button_back), :action => 'show', :id => @issue %></p> No newline at end of file
@@ -0,0 +1,8
1 Issue #<%= @issue.id %> has been updated.
2 <%= @journal.user.name %>
3 <% for detail in @journal.details %>
4 <%= show_detail(detail) %>
5 <% end %>
6 <%= @journal.notes if @journal.notes? %>
7 ----------------------------------------
8 <%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %> No newline at end of file
@@ -0,0 +1,8
1 Issue #<%= @issue.id %> has been updated.
2 <%= @journal.user.name %>
3 <% for detail in @journal.details %>
4 <%= show_detail(detail) %>
5 <% end %>
6 <%= @journal.notes if @journal.notes? %>
7 ----------------------------------------
8 <%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %> No newline at end of file
@@ -0,0 +1,8
1 Issue #<%= @issue.id %> has been updated.
2 <%= @journal.user.name %>
3 <% for detail in @journal.details %>
4 <%= show_detail(detail) %>
5 <% end %>
6 <%= @journal.notes if @journal.notes? %>
7 ----------------------------------------
8 <%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %> No newline at end of file
@@ -0,0 +1,8
1 La demande #<%= @issue.id %> a été mise à jour.
2 <%= @journal.user.name %> - <%= format_date(@journal.created_on) %>
3 <% for detail in @journal.details %>
4 <%= show_detail(detail) %>
5 <% end %>
6 <%= journal.notes if journal.notes? %>
7 ----------------------------------------
8 <%= render :file => "_issue", :use_full_path => true, :locals => { :issue => @issue } %> No newline at end of file
@@ -0,0 +1,54
1 class CreateJournals < ActiveRecord::Migration
2
3 # model removed, but needed for data migration
4 class IssueHistory < ActiveRecord::Base; belongs_to :issue; end
5
6 def self.up
7 create_table :journals, :force => true do |t|
8 t.column "journalized_id", :integer, :default => 0, :null => false
9 t.column "journalized_type", :string, :limit => 30, :default => "", :null => false
10 t.column "user_id", :integer, :default => 0, :null => false
11 t.column "notes", :text
12 t.column "created_on", :datetime, :null => false
13 end
14 create_table :journal_details, :force => true do |t|
15 t.column "journal_id", :integer, :default => 0, :null => false
16 t.column "property", :string, :limit => 30, :default => "", :null => false
17 t.column "prop_key", :string, :limit => 30, :default => "", :null => false
18 t.column "old_value", :string
19 t.column "value", :string
20 end
21
22 # indexes
23 add_index "journals", ["journalized_id", "journalized_type"], :name => "journals_journalized_id"
24 add_index "journal_details", ["journal_id"], :name => "journal_details_journal_id"
25
26 Permission.create :controller => "issues", :action => "history", :description => "label_history", :sort => 1006, :is_public => true, :mail_option => 0, :mail_enabled => 0
27
28 # data migration
29 IssueHistory.find(:all, :include => :issue).each {|h|
30 j = Journal.new(:journalized => h.issue, :user_id => h.author_id, :notes => h.notes, :created_on => h.created_on)
31 j.details << JournalDetail.new(:property => 'attr', :prop_key => 'status_id', :value => h.status_id)
32 j.save
33 }
34
35 drop_table :issue_histories
36 end
37
38 def self.down
39 drop_table :journal_details
40 drop_table :journals
41
42 create_table "issue_histories", :force => true do |t|
43 t.column "issue_id", :integer, :default => 0, :null => false
44 t.column "status_id", :integer, :default => 0, :null => false
45 t.column "author_id", :integer, :default => 0, :null => false
46 t.column "notes", :text, :default => ""
47 t.column "created_on", :timestamp
48 end
49
50 add_index "issue_histories", ["issue_id"], :name => "issue_histories_issue_id"
51
52 Permission.find(:first, :conditions => ["controller=? and action=?", 'issues', 'history']).destroy
53 end
54 end
@@ -41,7 +41,7 class ApplicationController < ActionController::Base
41 if self.logged_in_user and self.logged_in_user.language and !self.logged_in_user.language.empty? and GLoc.valid_languages.include? self.logged_in_user.language.to_sym
41 if self.logged_in_user and self.logged_in_user.language and !self.logged_in_user.language.empty? and GLoc.valid_languages.include? self.logged_in_user.language.to_sym
42 self.logged_in_user.language
42 self.logged_in_user.language
43 elsif request.env['HTTP_ACCEPT_LANGUAGE']
43 elsif request.env['HTTP_ACCEPT_LANGUAGE']
44 accept_lang = HTTPUtils.parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.split('-').first
44 accept_lang = parse_qvalues(request.env['HTTP_ACCEPT_LANGUAGE']).first.split('-').first
45 if accept_lang and !accept_lang.empty? and GLoc.valid_languages.include? accept_lang.to_sym
45 if accept_lang and !accept_lang.empty? and GLoc.valid_languages.include? accept_lang.to_sym
46 accept_lang
46 accept_lang
47 end
47 end
@@ -104,4 +104,23 class ApplicationController < ActionController::Base
104 session[:return_to] = nil
104 session[:return_to] = nil
105 end
105 end
106 end
106 end
107
108 # qvalues http header parser
109 # code taken from webrick
110 def parse_qvalues(value)
111 tmp = []
112 if value
113 parts = value.split(/,\s*/)
114 parts.each {|part|
115 if m = %r{^([^\s,]+?)(?:;\s*q=(\d+(?:\.\d+)?))?$}.match(part)
116 val = m[1]
117 q = (m[2] or 1).to_f
118 tmp.push([val, q])
119 end
120 }
121 tmp = tmp.sort_by{|val, q| -q}
122 tmp.collect!{|val, q| val}
123 end
124 return tmp
125 end
107 end No newline at end of file
126 end
@@ -27,6 +27,13 class IssuesController < ApplicationController
27 def show
27 def show
28 @status_options = @issue.status.workflows.find(:all, :include => :new_status, :conditions => ["role_id=? and tracker_id=?", self.logged_in_user.role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if self.logged_in_user
28 @status_options = @issue.status.workflows.find(:all, :include => :new_status, :conditions => ["role_id=? and tracker_id=?", self.logged_in_user.role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if self.logged_in_user
29 @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
29 @custom_values = @issue.custom_values.find(:all, :include => :custom_field)
30 @journals_count = @issue.journals.count
31 @journals = @issue.journals.find(:all, :include => [:user, :details], :limit => 15, :order => "journals.created_on desc")
32 end
33
34 def history
35 @journals = @issue.journals.find(:all, :include => [:user, :details], :order => "journals.created_on desc")
36 @journals_count = @journals.length
30 end
37 end
31
38
32 def export_pdf
39 def export_pdf
@@ -41,6 +48,7 class IssuesController < ApplicationController
41 @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) }
48 @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) }
42 else
49 else
43 begin
50 begin
51 @issue.init_journal(self.logged_in_user)
44 # Retrieve custom fields and values
52 # Retrieve custom fields and values
45 @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]) }
53 @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]) }
46 @issue.custom_values = @custom_values
54 @issue.custom_values = @custom_values
@@ -57,13 +65,14 class IssuesController < ApplicationController
57 end
65 end
58
66
59 def add_note
67 def add_note
60 unless params[:history][:notes].empty?
68 unless params[:notes].empty?
61 @history = @issue.histories.build(params[:history])
69 journal = @issue.init_journal(self.logged_in_user, params[:notes])
62 @history.author_id = self.logged_in_user.id if self.logged_in_user
70 #@history = @issue.histories.build(params[:history])
63 @history.status = @issue.status
71 #@history.author_id = self.logged_in_user.id if self.logged_in_user
64 if @history.save
72 #@history.status = @issue.status
73 if @issue.save
65 flash[:notice] = l(:notice_successful_update)
74 flash[:notice] = l(:notice_successful_update)
66 Mailer.deliver_issue_add_note(@history) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled?
75 Mailer.deliver_issue_edit(journal) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled?
67 redirect_to :action => 'show', :id => @issue
76 redirect_to :action => 'show', :id => @issue
68 return
77 return
69 end
78 end
@@ -73,17 +82,20 class IssuesController < ApplicationController
73 end
82 end
74
83
75 def change_status
84 def change_status
76 @history = @issue.histories.build(params[:history])
85 #@history = @issue.histories.build(params[:history])
77 @status_options = @issue.status.workflows.find(:all, :conditions => ["role_id=? and tracker_id=?", self.logged_in_user.role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if self.logged_in_user
86 @status_options = @issue.status.workflows.find(:all, :conditions => ["role_id=? and tracker_id=?", self.logged_in_user.role_for_project(@project.id), @issue.tracker.id]).collect{ |w| w.new_status } if self.logged_in_user
87 @new_status = IssueStatus.find(params[:new_status_id])
78 if params[:confirm]
88 if params[:confirm]
79 begin
89 begin
80 @history.author_id = self.logged_in_user.id if self.logged_in_user
90 #@history.author_id = self.logged_in_user.id if self.logged_in_user
81 @issue.status = @history.status
91 #@issue.status = @history.status
82 @issue.fixed_version_id = (params[:issue][:fixed_version_id])
92 #@issue.fixed_version_id = (params[:issue][:fixed_version_id])
83 @issue.assigned_to_id = (params[:issue][:assigned_to_id])
93 #@issue.assigned_to_id = (params[:issue][:assigned_to_id])
84 @issue.done_ratio = (params[:issue][:done_ratio])
94 #@issue.done_ratio = (params[:issue][:done_ratio])
85 @issue.lock_version = (params[:issue][:lock_version])
95 #@issue.lock_version = (params[:issue][:lock_version])
86 if @issue.save
96 @issue.init_journal(self.logged_in_user, params[:notes])
97 @issue.status = @new_status
98 if @issue.update_attributes(params[:issue])
87 flash[:notice] = l(:notice_successful_update)
99 flash[:notice] = l(:notice_successful_update)
88 Mailer.deliver_issue_change_status(@issue) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled?
100 Mailer.deliver_issue_change_status(@issue) if Permission.find_by_controller_and_action(@params[:controller], @params[:action]).mail_enabled?
89 redirect_to :action => 'show', :id => @issue
101 redirect_to :action => 'show', :id => @issue
@@ -28,6 +28,7 class ProjectsController < ApplicationController
28 include CustomFieldsHelper
28 include CustomFieldsHelper
29 helper :ifpdf
29 helper :ifpdf
30 include IfpdfHelper
30 include IfpdfHelper
31 helper IssuesHelper
31
32
32 def index
33 def index
33 list
34 list
@@ -81,7 +81,7 module ApplicationHelper
81 end
81 end
82
82
83 def textilizable(text)
83 def textilizable(text)
84 $RDM_TEXTILE_DISABLED ? text : textilize(text)
84 $RDM_TEXTILE_DISABLED ? text : RedCloth.new(text).to_html
85 end
85 end
86
86
87 def error_messages_for(object_name, options = {})
87 def error_messages_for(object_name, options = {})
@@ -54,14 +54,19 module CustomFieldsHelper
54 # Return a string used to display a custom value
54 # Return a string used to display a custom value
55 def show_value(custom_value)
55 def show_value(custom_value)
56 return "" unless custom_value
56 return "" unless custom_value
57 format_value(custom_value.value, custom_value.custom_field.field_format)
58 end
57
59
58 case custom_value.custom_field.field_format
60 # Return a string used to display a custom value
61 def format_value(value, field_format)
62 return "" unless value
63 case field_format
59 when "date"
64 when "date"
60 custom_value.value.empty? ? "" : l_date(custom_value.value.to_date)
65 value.empty? ? "" : l_date(value.to_date)
61 when "bool"
66 when "bool"
62 l_YesNo(custom_value.value == "1")
67 l_YesNo(value == "1")
63 else
68 else
64 custom_value.value
69 value
65 end
70 end
66 end
71 end
67
72
@@ -25,12 +25,12 module IfpdfHelper
25
25
26 def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
26 def Cell(w,h=0,txt='',border=0,ln=0,align='',fill=0,link='')
27 @ic ||= Iconv.new('ISO-8859-1', 'UTF-8')
27 @ic ||= Iconv.new('ISO-8859-1', 'UTF-8')
28 super w,h,@ic.iconv(txt),border,ln,align,fill,link
28 txt = begin
29 @ic.iconv(txt)
30 rescue
31 txt
29 end
32 end
30
33 super w,h,txt,border,ln,align,fill,link
31 def MultiCell(w,h,txt,border=0,align='J',fill=0)
32 @ic ||= Iconv.new('ISO-8859-1', 'UTF-8')
33 super w,h,txt,border,align,fill
34 end
34 end
35
35
36 def Footer
36 def Footer
@@ -16,4 +16,59
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module IssuesHelper
18 module IssuesHelper
19
20 def show_detail(detail, no_html=false)
21 case detail.property
22 when 'attr'
23 label = l(("field_" + detail.prop_key.to_s.gsub(/\_id$/, "")).to_sym)
24 case detail.prop_key
25 when 'due_date', 'start_date'
26 value = format_date(detail.value.to_date) if detail.value
27 old_value = format_date(detail.old_value.to_date) if detail.old_value
28 when 'status_id'
29 s = IssueStatus.find_by_id(detail.value) and value = s.name if detail.value
30 s = IssueStatus.find_by_id(detail.old_value) and old_value = s.name if detail.old_value
31 when 'assigned_to_id'
32 u = User.find_by_id(detail.value) and value = u.name if detail.value
33 u = User.find_by_id(detail.old_value) and old_value = u.name if detail.old_value
34 when 'priority_id'
35 e = Enumeration.find_by_id(detail.value) and value = e.name if detail.value
36 e = Enumeration.find_by_id(detail.old_value) and old_value = e.name if detail.old_value
37 when 'category_id'
38 c = IssueCategory.find_by_id(detail.value) and value = c.name if detail.value
39 c = IssueCategory.find_by_id(detail.old_value) and old_value = c.name if detail.old_value
40 when 'fixed_version_id'
41 v = Version.find_by_id(detail.value) and value = v.name if detail.value
42 v = Version.find_by_id(detail.old_value) and old_value = v.name if detail.old_value
43 end
44 when 'cf'
45 custom_field = CustomField.find_by_id(detail.prop_key)
46 if custom_field
47 label = custom_field.name
48 value = format_value(detail.value, custom_field.field_format) if detail.value
49 old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
50 end
51 end
52
53 label ||= detail.prop_key
54 value ||= detail.value
55 old_value ||= detail.old_value
56
57 unless no_html
58 label = content_tag('strong', label)
59 old_value = content_tag("i", old_value) if old_value
60 old_value = content_tag("strike", old_value) if old_value and !value
61 value = content_tag("i", value) if value
62 end
63
64 if value
65 if old_value
66 label + " " + l(:text_journal_changed, old_value, value)
67 else
68 label + " " + l(:text_journal_set_to, value)
69 end
70 else
71 label + " " + l(:text_journal_deleted) + " (#{old_value})"
72 end
73 end
19 end
74 end
@@ -26,7 +26,8 class Issue < ActiveRecord::Base
26 belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
26 belongs_to :priority, :class_name => 'Enumeration', :foreign_key => 'priority_id'
27 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
27 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
28
28
29 has_many :histories, :class_name => 'IssueHistory', :dependent => true, :order => "issue_histories.created_on DESC", :include => :status
29 #has_many :histories, :class_name => 'IssueHistory', :dependent => true, :order => "issue_histories.created_on DESC", :include => :status
30 has_many :journals, :as => :journalized, :dependent => true
30 has_many :attachments, :as => :container, :dependent => true
31 has_many :attachments, :as => :container, :dependent => true
31
32
32 has_many :custom_values, :dependent => true, :as => :customized
33 has_many :custom_values, :dependent => true, :as => :customized
@@ -51,8 +52,28 class Issue < ActiveRecord::Base
51 end
52 end
52 end
53 end
53
54
54 def before_create
55 #def before_create
55 build_history
56 # build_history
57 #end
58
59 def before_save
60 if @current_journal
61 # attributes changes
62 (Issue.column_names - %w(id description)).each {|c|
63 @current_journal.details << JournalDetail.new(:property => 'attr',
64 :prop_key => c,
65 :old_value => @issue_before_change.send(c),
66 :value => send(c)) unless send(c)==@issue_before_change.send(c)
67 }
68 # custom fields changes
69 custom_values.each {|c|
70 @current_journal.details << JournalDetail.new(:property => 'cf',
71 :prop_key => c.custom_field_id,
72 :old_value => @custom_values_before_change[c.custom_field_id],
73 :value => c.value) unless @custom_values_before_change[c.custom_field_id]==c.value
74 }
75 @current_journal.save unless @current_journal.details.empty? and @current_journal.notes.empty?
76 end
56 end
77 end
57
78
58 def long_id
79 def long_id
@@ -64,11 +85,19 class Issue < ActiveRecord::Base
64 return nil
85 return nil
65 end
86 end
66
87
88 def init_journal(user, notes = "")
89 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
90 @issue_before_change = self.clone
91 @custom_values_before_change = {}
92 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
93 @current_journal
94 end
95
67 private
96 private
68 # Creates an history for the issue
97 # Creates an history for the issue
69 def build_history
98 #def build_history
70 @history = self.histories.build
99 # @history = self.histories.build
71 @history.status = self.status
100 # @history.status = self.status
72 @history.author = self.author
101 # @history.author = self.author
73 end
102 #end
74 end
103 end
@@ -17,7 +17,7
17
17
18 class Mailer < ActionMailer::Base
18 class Mailer < ActionMailer::Base
19
19
20 def issue_change_status(issue)
20 def issue_add(issue)
21 # Sends to all project members
21 # Sends to all project members
22 @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
22 @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
23 @from = $RDM_MAIL_FROM
23 @from = $RDM_MAIL_FROM
@@ -25,20 +25,14 class Mailer < ActionMailer::Base
25 @body['issue'] = issue
25 @body['issue'] = issue
26 end
26 end
27
27
28 def issue_add(issue)
28 def issue_edit(journal)
29 # Sends to all project members
29 # Sends to all project members
30 issue = journal.journalized
30 @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
31 @recipients = issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
31 @from = $RDM_MAIL_FROM
32 @from = $RDM_MAIL_FROM
32 @subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}"
33 @subject = "[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}] #{issue.status.name} - #{issue.subject}"
33 @body['issue'] = issue
34 @body['issue'] = issue
34 end
35 @body['journal']= journal
35
36 def issue_add_note(history)
37 # Sends to all project members
38 @recipients = history.issue.project.members.collect { |m| m.user.mail if m.user.mail_notification }
39 @from = $RDM_MAIL_FROM
40 @subject = "[#{history.issue.project.name} - #{history.issue.tracker.name} ##{history.issue.id}] #{history.issue.status.name} - #{history.issue.subject}"
41 @body['history'] = history
42 end
36 end
43
37
44 def lost_password(token)
38 def lost_password(token)
@@ -66,20 +66,25
66 pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY)
66 pdf.Line(pdf.GetX, pdf.GetY, 170, pdf.GetY)
67
67
68 pdf.Ln
68 pdf.Ln
69
69 pdf.SetFont('Arial','B',9)
70 pdf.SetFont('Arial','B',9)
70 pdf.Cell(190,5, l(:label_history),"B")
71 pdf.Cell(190,5, l(:label_history), "B")
71 pdf.Ln
72 pdf.Ln
72 for history in issue.histories.find(:all, :include => [:author, :status])
73 for journal in issue.journals.find(:all, :include => :user, :order => "journals.created_on desc")
73 pdf.SetFont('Arial','B',8)
74 pdf.SetFont('Arial','B',8)
74 pdf.Cell(100,5, history.status.name)
75 pdf.Cell(190,5, format_time(journal.created_on) + " - " + journal.user.name)
75 pdf.SetFont('Arial','',8)
76 pdf.Ln
76 pdf.Cell(20,5, format_date(history.created_on))
77 pdf.SetFont('Arial','I',8)
77 pdf.Cell(70,5, history.author.name,0,0,"R")
78 for detail in journal.details
78 pdf.SetFont('Arial','',8)
79 pdf.Cell(190,5, "- " + show_detail(detail, true))
79 pdf.Ln
80 pdf.Ln
80 pdf.Cell(10,4, "") and pdf.MultiCell(180,4, history.notes) if history.notes?
81 end
82 if journal.notes?
83 pdf.SetFont('Arial','',8)
84 pdf.MultiCell(190,5, journal.notes)
81 end
85 end
82 pdf.Ln
86 pdf.Ln
87 end
83
88
84 pdf.SetFont('Arial','B',9)
89 pdf.SetFont('Arial','B',9)
85 pdf.Cell(190,5, l(:label_attachment_plural), "B")
90 pdf.Cell(190,5, l(:label_attachment_plural), "B")
@@ -87,8 +92,8
87 for attachment in issue.attachments
92 for attachment in issue.attachments
88 pdf.SetFont('Arial','',8)
93 pdf.SetFont('Arial','',8)
89 pdf.Cell(80,5, attachment.filename)
94 pdf.Cell(80,5, attachment.filename)
90 pdf.Cell(20,5, human_size(attachment.filesize))
95 pdf.Cell(20,5, human_size(attachment.filesize),0,0,"R")
91 pdf.Cell(20,5, format_date(attachment.created_on))
96 pdf.Cell(20,5, format_date(attachment.created_on),0,0,"R")
92 pdf.Cell(70,5, attachment.author.name,0,0,"R")
97 pdf.Cell(70,5, attachment.author.name,0,0,"R")
93 pdf.Ln
98 pdf.Ln
94 end
99 end
@@ -1,13 +1,13
1 <h2><%=l(:label_issue)%> #<%= @issue.id %>: <%= @issue.subject %></h2>
1 <h2><%=l(:label_issue)%> #<%= @issue.id %>: <%= @issue.subject %></h2>
2
2
3 <%= error_messages_for 'history' %>
3 <%= error_messages_for 'issue' %>
4 <%= start_form_tag({:action => 'change_status', :id => @issue}, :class => "tabular") %>
4 <%= start_form_tag({:action => 'change_status', :id => @issue}, :class => "tabular") %>
5
5
6 <%= hidden_field_tag 'confirm', 1 %>
6 <%= hidden_field_tag 'confirm', 1 %>
7 <%= hidden_field 'history', 'status_id' %>
7 <%= hidden_field_tag 'new_status_id', @new_status.id %>
8
8
9 <div class="box">
9 <div class="box">
10 <p><label><%=l(:label_issue_status_new)%></label> <%= @history.status.name %></p>
10 <p><label><%=l(:label_issue_status_new)%></label> <%= @new_status.name %></p>
11
11
12 <p><label for="issue_assigned_to_id"><%=l(:field_assigned_to)%></label>
12 <p><label for="issue_assigned_to_id"><%=l(:field_assigned_to)%></label>
13 <select name="issue[assigned_to_id]">
13 <select name="issue[assigned_to_id]">
@@ -27,8 +27,9
27 <%= options_from_collection_for_select @issue.project.versions, "id", "name", @issue.fixed_version_id %>
27 <%= options_from_collection_for_select @issue.project.versions, "id", "name", @issue.fixed_version_id %>
28 </select></p>
28 </select></p>
29
29
30 <p><label for="history_notes"><%=l(:field_notes)%></label>
30 <p><label for="notes"><%= l(:field_notes) %></label>
31 <%= text_area 'history', 'notes', :cols => 60, :rows => 10 %></p>
31 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10 %></p>
32
32 </div>
33 </div>
33
34
34 <%= hidden_field 'issue', 'lock_version' %>
35 <%= hidden_field 'issue', 'lock_version' %>
@@ -19,7 +19,7
19
19
20 <div class="clear">
20 <div class="clear">
21 <p><%= f.text_field :subject, :size => 80, :required => true %></p>
21 <p><%= f.text_field :subject, :size => 80, :required => true %></p>
22 <p><%= f.text_area :description, :cols => 60, :rows => 10, :required => true %></p>
22 <p><%= f.text_area :description, :cols => 60, :rows => [[10, @issue.description.length / 50].max, 100].min, :required => true %></p>
23
23
24 <% for @custom_value in @custom_values %>
24 <% for @custom_value in @custom_values %>
25 <p><%= custom_field_tag_with_label @custom_value %></p>
25 <p><%= custom_field_tag_with_label @custom_value %></p>
@@ -44,8 +44,8 end %>
44
44
45 <b><%=l(:field_description)%> :</b><br /><br />
45 <b><%=l(:field_description)%> :</b><br /><br />
46 <%= textilizable @issue.description %>
46 <%= textilizable @issue.description %>
47
47 <br />
48 <p>
48 <div style="float:left;">
49 <% if authorize_for('issues', 'edit') %>
49 <% if authorize_for('issues', 'edit') %>
50 <%= start_form_tag ({:controller => 'issues', :action => 'edit', :id => @issue}, :method => "get" ) %>
50 <%= start_form_tag ({:controller => 'issues', :action => 'edit', :id => @issue}, :method => "get" ) %>
51 <%= submit_tag l(:button_edit) %>
51 <%= submit_tag l(:button_edit) %>
@@ -56,7 +56,7 end %>
56 <% if authorize_for('issues', 'change_status') and @status_options and !@status_options.empty? %>
56 <% if authorize_for('issues', 'change_status') and @status_options and !@status_options.empty? %>
57 <%= start_form_tag ({:controller => 'issues', :action => 'change_status', :id => @issue}) %>
57 <%= start_form_tag ({:controller => 'issues', :action => 'change_status', :id => @issue}) %>
58 <%=l(:label_change_status)%> :
58 <%=l(:label_change_status)%> :
59 <select name="history[status_id]">
59 <select name="new_status_id">
60 <%= options_from_collection_for_select @status_options, "id", "name" %>
60 <%= options_from_collection_for_select @status_options, "id", "name" %>
61 </select>
61 </select>
62 <%= submit_tag l(:button_change) %>
62 <%= submit_tag l(:button_change) %>
@@ -71,30 +71,25 end %>
71 <%= end_form_tag %>
71 <%= end_form_tag %>
72 &nbsp;&nbsp;
72 &nbsp;&nbsp;
73 <% end %>
73 <% end %>
74
74 </div>
75 <div style="float:right;">
75 <% if authorize_for('issues', 'destroy') %>
76 <% if authorize_for('issues', 'destroy') %>
76 <%= start_form_tag ({:controller => 'issues', :action => 'destroy', :id => @issue} ) %>
77 <%= start_form_tag ({:controller => 'issues', :action => 'destroy', :id => @issue} ) %>
77 <%= submit_tag l(:button_delete) %>
78 <%= submit_tag l(:button_delete) %>
78 <%= end_form_tag %>
79 <%= end_form_tag %>
79 &nbsp;&nbsp;
80 &nbsp;&nbsp;
80 <% end %>
81 <% end %>
81 </p>
82 </div>
83 <div class="clear"></div>
82 </div>
84 </div>
83
85
84 <div class="box">
86 <div id="history" class="box">
85 <h3><%=l(:label_history)%></h3>
87 <h3><%=l(:label_history)%>
86 <table width="100%">
88 <% if @journals_count > @journals.length %>(<%= l(:label_last_changes, @journals.length) %>)<% end %></h3>
87 <% for history in @issue.histories.find(:all, :include => [:author, :status]) %>
89 <%= render :partial => 'history', :locals => { :journals => @journals } %>
88 <tr>
90 <% if @journals_count > @journals.length %>
89 <td><%= format_date(history.created_on) %></td>
91 <p><center><small>[ <%= link_to l(:label_change_view_all), :action => 'history', :id => @issue %> ]</small></center></p>
90 <td><%= history.author.display_name %></td>
91 <td><b><%= history.status.name %></b></td>
92 </tr>
93 <% if history.notes? %>
94 <tr><td colspan=3><%= simple_format auto_link history.notes %></td></tr>
95 <% end %>
92 <% end %>
96 <% end %>
97 </table>
98 </div>
93 </div>
99
94
100 <div class="box">
95 <div class="box">
@@ -130,8 +125,8 end %>
130 <div class="box">
125 <div class="box">
131 <h3><%= l(:label_add_note) %></h3>
126 <h3><%= l(:label_add_note) %></h3>
132 <%= start_form_tag ({:controller => 'issues', :action => 'add_note', :id => @issue}, :class => "tabular" ) %>
127 <%= start_form_tag ({:controller => 'issues', :action => 'add_note', :id => @issue}, :class => "tabular" ) %>
133 <p><label for="history_notes"><%=l(:field_notes)%></label>
128 <p><label for="notes"><%=l(:field_notes)%></label>
134 <%= text_area 'history', 'notes', :cols => 60, :rows => 10 %></p>
129 <%= text_area_tag 'notes', '', :cols => 60, :rows => 10 %></p>
135 <%= submit_tag l(:button_add) %>
130 <%= submit_tag l(:button_add) %>
136 <%= end_form_tag %>
131 <%= end_form_tag %>
137 </div>
132 </div>
@@ -133,7 +133,7 var menu_contenu=' \
133 <div id="footer">
133 <div id="footer">
134 <p>
134 <p>
135 <%= auto_link $RDM_FOOTER_SIG %> |
135 <%= auto_link $RDM_FOOTER_SIG %> |
136 <a href="http://redmine.org/" target="_new"><%= RDM_APP_NAME %></a> <%= RDM_APP_VERSION %>
136 <a href="http://redmine.rubyforge.org/" target="_new"><%= RDM_APP_NAME %></a> <%= RDM_APP_VERSION %>
137 </p>
137 </p>
138 </div>
138 </div>
139
139
@@ -7,6 +7,7 http://redmine.org/
7
7
8 == xx/xx/2006 v0.x.x
8 == xx/xx/2006 v0.x.x
9
9
10 * improved issues change history
10 * new functionality: move an issue to another project or tracker
11 * new functionality: move an issue to another project or tracker
11 * new functionality: add a note to an issue
12 * new functionality: add a note to an issue
12 * new report: project activity
13 * new report: project activity
@@ -256,6 +256,8 label_calendar: Kalender
256 label_months_from: Monate von
256 label_months_from: Monate von
257 label_gantt_chart: Gantt Diagramm
257 label_gantt_chart: Gantt Diagramm
258 label_internal: Intern
258 label_internal: Intern
259 label_last_changes: %d änderungen des Letzten
260 label_change_view_all: Alle änderungen ansehen
259
261
260 button_login: Einloggen
262 button_login: Einloggen
261 button_submit: Einreichen
263 button_submit: Einreichen
@@ -285,6 +287,9 text_possible_values_info: Werte trennten sich mit |
285 text_project_destroy_confirmation: Sind sie sicher, daß sie das Projekt löschen wollen ?
287 text_project_destroy_confirmation: Sind sie sicher, daß sie das Projekt löschen wollen ?
286 text_workflow_edit: Auswahl Workflow zum Bearbeiten
288 text_workflow_edit: Auswahl Workflow zum Bearbeiten
287 text_are_you_sure: Sind sie sicher ?
289 text_are_you_sure: Sind sie sicher ?
290 text_journal_changed: geändert von %s zu %s
291 text_journal_set_to: gestellt zu %s
292 text_journal_deleted: gelöscht
288
293
289 default_role_manager: Manager
294 default_role_manager: Manager
290 default_role_developper: Developer
295 default_role_developper: Developer
@@ -256,6 +256,8 label_calendar: Calendar
256 label_months_from: months from
256 label_months_from: months from
257 label_gantt_chart: Gantt chart
257 label_gantt_chart: Gantt chart
258 label_internal: Internal
258 label_internal: Internal
259 label_last_changes: last %d changes
260 label_change_view_all: View all changes
259
261
260 button_login: Login
262 button_login: Login
261 button_submit: Submit
263 button_submit: Submit
@@ -285,6 +287,9 text_possible_values_info: values separated with |
285 text_project_destroy_confirmation: Are you sure you want to delete this project and all related data ?
287 text_project_destroy_confirmation: Are you sure you want to delete this project and all related data ?
286 text_workflow_edit: Select a role and a tracker to edit the workflow
288 text_workflow_edit: Select a role and a tracker to edit the workflow
287 text_are_you_sure: Are you sure ?
289 text_are_you_sure: Are you sure ?
290 text_journal_changed: changed from %s to %s
291 text_journal_set_to: set to %s
292 text_journal_deleted: deleted
288
293
289 default_role_manager: Manager
294 default_role_manager: Manager
290 default_role_developper: Developer
295 default_role_developper: Developer
@@ -256,6 +256,8 label_calendar: Calendario
256 label_months_from: meses de
256 label_months_from: meses de
257 label_gantt_chart: Diagrama de Gantt
257 label_gantt_chart: Diagrama de Gantt
258 label_internal: Interno
258 label_internal: Interno
259 label_last_changes: %d cambios del último
260 label_change_view_all: Ver todos los cambios
259
261
260 button_login: Conexión
262 button_login: Conexión
261 button_submit: Someter
263 button_submit: Someter
@@ -285,6 +287,9 text_possible_values_info: Los valores se separaron con |
285 text_project_destroy_confirmation: ¿ Estás seguro de querer eliminar el proyecto ?
287 text_project_destroy_confirmation: ¿ Estás seguro de querer eliminar el proyecto ?
286 text_workflow_edit: Seleccionar un workflow para actualizar
288 text_workflow_edit: Seleccionar un workflow para actualizar
287 text_are_you_sure: ¿ Estás seguro ?
289 text_are_you_sure: ¿ Estás seguro ?
290 text_journal_changed: cambiado de %s a %s
291 text_journal_set_to: fijado a %s
292 text_journal_deleted: suprimido
288
293
289 default_role_manager: Manager
294 default_role_manager: Manager
290 default_role_developper: Desarrollador
295 default_role_developper: Desarrollador
@@ -257,6 +257,8 label_calendar: Calendrier
257 label_months_from: mois depuis
257 label_months_from: mois depuis
258 label_gantt_chart: Diagramme de Gantt
258 label_gantt_chart: Diagramme de Gantt
259 label_internal: Interne
259 label_internal: Interne
260 label_last_changes: %d derniers changements
261 label_change_view_all: Voir tous les changements
260
262
261 button_login: Connexion
263 button_login: Connexion
262 button_submit: Soumettre
264 button_submit: Soumettre
@@ -286,6 +288,9 text_possible_values_info: valeurs séparées par |
286 text_project_destroy_confirmation: Etes-vous sûr de vouloir supprimer ce projet et tout ce qui lui est rattaché ?
288 text_project_destroy_confirmation: Etes-vous sûr de vouloir supprimer ce projet et tout ce qui lui est rattaché ?
287 text_workflow_edit: Sélectionner un tracker et un rôle pour éditer le workflow
289 text_workflow_edit: Sélectionner un tracker et un rôle pour éditer le workflow
288 text_are_you_sure: Etes-vous sûr ?
290 text_are_you_sure: Etes-vous sûr ?
291 text_journal_changed: changé de %s à %s
292 text_journal_set_to: mis à %s
293 text_journal_deleted: supprimé
289
294
290 default_role_manager: Manager
295 default_role_manager: Manager
291 default_role_developper: Développeur
296 default_role_developper: Développeur
@@ -186,10 +186,6 form {
186
186
187 .noborder {
187 .noborder {
188 border:0px;
188 border:0px;
189 Exception exceptions.AssertionError: <exceptions.AssertionError instance at 0xb7c0b20c> in <bound
190 method SubversionRepository.__del__ of <vclib.svn.SubversionRepository instance at 0xb7c1252c>>
191 ignored
192
193 background-color:#fff;
189 background-color:#fff;
194 width:100%;
190 width:100%;
195 }
191 }
@@ -292,7 +288,7 table.calenderTable td {
292 border:1px solid #578bb8;
288 border:1px solid #578bb8;
293 }
289 }
294
290
295 hr { border:none; border-bottom: dotted 2px #c0c0c0; }
291 hr { border:none; border-bottom: dotted 1px #c0c0c0; }
296
292
297
293
298 /**************** Sidebar styles ****************/
294 /**************** Sidebar styles ****************/
@@ -409,6 +405,17 img.calendar-trigger {
409 margin-left: 4px;
405 margin-left: 4px;
410 }
406 }
411
407
408 #history h4 {
409 font-size: 1em;
410 margin-bottom: 12px;
411 margin-top: 20px;
412 font-weight: normal;
413 border-bottom: dotted 1px #c0c0c0;
414 }
415
416 #history p {
417 margin-left: 34px;
418 }
412
419
413 /***** CSS FORM ******/
420 /***** CSS FORM ******/
414 .tabular p{
421 .tabular p{
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now