timelogging.patch

the patch - Artem Vasiliev, 2008-07-15 18:35

Download (62.4 KB)

View differences:

db/migrate/095_add_start_and_end_time_to_time_entries.rb (revision 0)
1
class AddStartAndEndTimeToTimeEntries < ActiveRecord::Migration
2
  def self.up
3
    add_column :time_entries, :start_time, :datetime
4
    add_column :time_entries, :end_time, :datetime
5
  end
6

  
7
  def self.down
8
    remove_column :time_entries, :start_time
9
    remove_column :time_entries, :end_time
10
  end
11
end
db/migrate/096_allow_null_time_entry_hours.rb (revision 0)
1
class AllowNullTimeEntryHours < ActiveRecord::Migration
2
  def self.up
3
    change_column :time_entries, :hours, :float, :null => true
4
  end
5

  
6
  def self.down
7
    # not needed
8
  end
9
end
app/views/issues/_edit.rhtml (working copy)
1
<% content_for :header_tags do %>
2
    <%= javascript_include_tag 'time' %>
3
<% end %>
1 4
<% labelled_tabular_form_for :issue, @issue,
2 5
                             :url => {:action => 'edit', :id => @issue},
3 6
                             :html => {:id => 'issue-form',
......
18 21
    <% if authorize_for('timelog', 'edit') %>
19 22
        <fieldset class="tabular"><legend><%= l(:button_log_time) %></legend>
20 23
        <% fields_for :time_entry, @time_entry, { :builder => TabularFormBuilder, :lang => current_language} do |time_entry| %>
24
        <%= time_entry.hidden_field :id %>
21 25
        <div class="splitcontentleft">
22 26
        <p><%= time_entry.text_field :hours, :size => 6, :label => :label_spent_time %> <%= l(:field_hours) %></p>
27
        <p><%= time_entry.time_field :start_time %></p>
23 28
        </div>
24 29
        <div class="splitcontentright">
25 30
        <p><%= time_entry.select :activity_id, activity_collection_for_select_options %></p>
31
        <p><%= time_entry.time_field :end_time %></p>
26 32
        </div>
27 33
        <p><%= time_entry.text_field :comments, :size => 60 %></p>
28 34
        <% end %>
app/views/issues/show.rhtml (working copy)
33 33
    <td><b><%=l(:field_category)%>:</b></td><td><%=h @issue.category ? @issue.category.name : "-" %></td>
34 34
    <% if User.current.allowed_to?(:view_time_entries, @project) %>
35 35
    <td><b><%=l(:label_spent_time)%>:</b></td>
36
    <td><%= @issue.spent_hours > 0 ? (link_to lwr(:label_f_hour, @issue.spent_hours), {:controller => 'timelog', :action => 'details', :project_id => @project, :issue_id => @issue}, :class => 'icon icon-time') : "-" %></td>
36
    <td><%= spent_hours(@issue) %></td>
37 37
    <% end %>
38 38
</tr>
39 39
<tr>
app/views/timelog/_list.rhtml (working copy)
2 2
<thead>
3 3
<tr>
4 4
<%= sort_header_tag('spent_on', :caption => l(:label_date), :default_order => 'desc') %>
5
<%= sort_header_tag('start_time', :caption => l(:field_start_time), :default_order => 'desc') %>
6
<%= sort_header_tag('end_time', :caption => l(:field_end_time), :default_order => 'desc') %>
7
<%= sort_header_tag('hours', :caption => l(:field_hours)) %>
5 8
<%= sort_header_tag('user_id', :caption => l(:label_member)) %>
6 9
<%= sort_header_tag('activity_id', :caption => l(:label_activity)) %>
7 10
<%= sort_header_tag("#{Project.table_name}.name", :caption => l(:label_project)) %>
8 11
<%= sort_header_tag('issue_id', :caption => l(:label_issue), :default_order => 'desc') %>
9 12
<th><%= l(:field_comments) %></th>
10
<%= sort_header_tag('hours', :caption => l(:field_hours)) %>
11 13
<th></th>
12 14
</tr>
13 15
</thead>
......
15 17
<% entries.each do |entry| -%>
16 18
<tr class="time-entry <%= cycle("odd", "even") %>">
17 19
<td class="spent_on"><%= format_date(entry.spent_on) %></td>
20
<td class="start_time"><%= format_time(entry.start_time) %></td>
21
<td class="end_time"><%= format_time(entry.end_time) %></td>
22
<td class="hours"><%= hours(entry) %></td>
18 23
<td class="user"><%=h entry.user %></td>
19 24
<td class="activity"><%=h entry.activity %></td>
20 25
<td class="project"><%=h entry.project %></td>
......
24 29
<% end -%>
25 30
</td>
26 31
<td class="comments"><%=h entry.comments %></td>
27
<td class="hours"><%= html_hours("%.2f" % entry.hours) %></td>
28 32
<td align="center">
29 33
<% if entry.editable_by?(User.current) -%>
30 34
    <%= link_to image_tag('edit.png'), {:controller => 'timelog', :action => 'edit', :id => entry},
app/views/timelog/edit.rhtml (working copy)
3 3
<% labelled_tabular_form_for :time_entry, @time_entry, :url => {:action => 'edit', :project_id => @time_entry.project} do |f| %>
4 4
<%= error_messages_for 'time_entry' %>
5 5
<%= back_url_hidden_field_tag %>
6
<% content_for :header_tags do %>
7
    <%= javascript_include_tag 'time' %>
8
<% end %>
6 9

  
10
<% content_for :header_tags do %>
11
  <script language="javascript">
12
    window.onload = function() {
13
      $('<%= @activate_field %>').activate();
14
    };
15
  </script>
16
<% end %>
17

  
7 18
<div class="box">
8 19
<p><%= f.text_field :issue_id, :size => 6 %> <em><%= h("#{@time_entry.issue.tracker.name} ##{@time_entry.issue.id}: #{@time_entry.issue.subject}") if @time_entry.issue %></em></p>
9 20
<p><%= f.text_field :spent_on, :size => 10, :required => true %><%= calendar_for('time_entry_spent_on') %></p>
10
<p><%= f.text_field :hours, :size => 6, :required => true %></p>
21
<p><%= f.time_field :start_time %></p>
22
<p><%= f.time_field :end_time %></p>
23
<p><%= f.text_field :hours, :size => 6 %> <em> <%= l(:text_clear_to_recalculate_time_by_range) %></em> </p> 
11 24
<p><%= f.text_field :comments, :size => 100 %></p>
12 25
<p><%= f.select :activity_id, activity_collection_for_select_options, :required => true %></p>
13 26
</div>
lib/tabular_form_builder.rb (working copy)
40 40
    END_SRC
41 41
    class_eval src, __FILE__, __LINE__
42 42
  end
43

  
44
  def time_field(field)
45
    value = nil
46
    value = @object.send(field) if @object
47
    value = value.strftime('%Y-%m-%d %H:%M') if value
48
    result = text_field field, :size => 15, :value => value
49
    result += (' <input type="button" onclick="maybeSetTimeNow($(\'%s\'))' + 
50
      '" value="' + l(:button_now) + 
51
      '" name="action"/>') % "#{@object_name}_#{field}"
52
    result
53
  end
43 54
  
44 55
  def select(field, choices, options = {}, html_options = {}) 
45 56
    label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
app/controllers/application.rb (working copy)
219 219
  def filename_for_content_disposition(name)
220 220
    request.env['HTTP_USER_AGENT'] =~ %r{MSIE} ? ERB::Util.url_encode(name) : name
221 221
  end
222

  
223
  def url_path(url_parameters)
224
    rs = ::ActionController::Routing::Routes
225
    rs.generate url_parameters
226
  end
222 227
end
app/controllers/issues_controller.rb (working copy)
101 101
    @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
102 102
    @edit_allowed = User.current.allowed_to?(:edit_issues, @project)
103 103
    @priorities = Enumeration::get_values('IPRI')
104
    @time_entry = TimeEntry.new
104
    @time_entry = @issue.time_entry_in_progress(User.current) || TimeEntry.new
105 105
    respond_to do |format|
106 106
      format.html { render :template => 'issues/show.rhtml' }
107 107
      format.atom { render :action => 'changes', :layout => false, :content_type => 'application/atom+xml' }
......
172 172
    end
173 173

  
174 174
    if request.post?
175
      @time_entry = TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
175
      @time_entry = TimeEntry.find_by_id(params[:time_entry][:id]) if params[:time_entry]
176
      @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, 
177
              :user => User.current, :spent_on => Date.today)
178
            
176 179
      @time_entry.attributes = params[:time_entry]
177 180
      attachments = attach_files(@issue, params[:attachments])
178 181
      attachments.each {|a| journal.details << JournalDetail.new(:property => 'attachment', :prop_key => a.id, :value => a.filename)}
app/controllers/timelog_controller.rb (working copy)
175 175
  end
176 176
  
177 177
  def edit
178
    return if redirect_if_in_progress
179
    
178 180
    render_403 and return if @time_entry && !@time_entry.editable_by?(User.current)
179 181
    @time_entry ||= TimeEntry.new(:project => @project, :issue => @issue, :user => User.current, :spent_on => Date.today)
180 182
    @time_entry.attributes = params[:time_entry]
183
    if !@time_entry.hours
184
      if !@time_entry.start_time
185
        @activate_field = 'time_entry_start_time'         
186
      else
187
        @activate_field = 'time_entry_end_time'
188
      end
189
    end
190
    
191
    url_writer = lambda do |entry| 
192
      "<a href = \"#{url_path(:controller => :timelog, :action => :edit, 
193
        :id => entry.id)}\">##{entry.issue_id}-#{entry.id}</a>"
194
    end
195
    
181 196
    if request.post? and @time_entry.save
182
      flash[:notice] = l(:notice_successful_update)
197
      intersecting = @time_entry.find_intersecting_entries
198
      logger.debug "intersecting = #{intersecting.inspect}"
199
      msg = l(:notice_successful_update)
200
      if !intersecting.empty? 
201
        
202
        list = lwr(:text_time_entry_intersecting_notice_entry, 
203
          intersecting.size) + ' ' + intersecting.
204
          map { |entry| url_writer.call(entry) }.
205
          to_sentence(:skip_last_comma => true, :connector => l(:text_and))
206
        
207
        msg += ' ' + l(:text_time_entry_intersecting_notice, 
208
          url_writer.call(@time_entry), list)
209
      end
210
      flash[:notice] = msg
183 211
      redirect_to(params[:back_url].blank? ? {:action => 'details', :project_id => @time_entry.project} : params[:back_url])
184 212
      return
185 213
    end    
......
258 286
    @from ||= (TimeEntry.minimum(:spent_on, :include => :project, :conditions => @project.project_condition(Setting.display_subprojects_issues?)) || Date.today) - 1
259 287
    @to   ||= (TimeEntry.maximum(:spent_on, :include => :project, :conditions => @project.project_condition(Setting.display_subprojects_issues?)) || Date.today)
260 288
  end
289
  
290
  def redirect_if_in_progress
291
    if !@time_entry && @issue
292
      in_progress_entry = @issue.time_entry_in_progress(User.current)
293
      if in_progress_entry
294
        #in order to avoid :id form parameter and not complicate :find_project filter
295
        redirect_to(:controller => 'timelog', :action => 'edit', 
296
            :id => in_progress_entry)
297
        return true
298
      end
299
    end
300
    false
301
  end
261 302
end
app/helpers/application_helper.rb (working copy)
70 70
    @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
71 71
    date.strftime(@date_format)
72 72
  end
73
  
73

  
74 74
  def format_time(time, include_date = true)
75 75
    return nil unless time
76 76
    time = time.to_time if time.is_a?(String)
......
78 78
    local = zone ? time.in_time_zone(zone) : (time.utc? ? time.utc_to_local : time)
79 79
    @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
80 80
    @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
81
    include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
81
    format = include_date ? "#{@date_format} #{@time_format}" : @time_format    
82
    local.strftime(format)
82 83
  end
83 84
  
84 85
  # Truncates and returns the string as a single line
app/helpers/issues_helper.rb (working copy)
181 181
    export.rewind
182 182
    export
183 183
  end
184
  
185
  def spent_hours(issue)
186
    return '-' if issue.time_entries.empty?
187
    spent_hours = lwr(:label_f_hour, issue.spent_hours)
188
    spent_hours += " (#{l(:text_in_progress)})" if issue.time_entry_in_progress
189
    link_to spent_hours, {:controller => 'timelog', :action => 'details', :project_id => issue.project, :issue_id => issue}, :class => 'icon icon-time'
190
  end
184 191
end
app/helpers/timelog_helper.rb (working copy)
36 36
    sum
37 37
  end
38 38
  
39
  def hours(entry)
40
    return '<span class="hours hours-dec">[%s]</span>' % l(:text_in_progress) if !entry.hours
41
    html_hours("%.2f" % entry.hours)
42
  end
43
  
39 44
  def options_for_period_select(value)
40 45
    options_for_select([[l(:label_all_time), 'all'],
41 46
                        [l(:label_today), 'today'],
app/models/issue.rb (working copy)
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 => :delete_all
31
  
31 32
  has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
32 33
  
33 34
  has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
......
251 252
  def to_s
252 253
    "#{tracker} ##{id}: #{subject}"
253 254
  end
255
  
256
  def time_entry_in_progress(user = nil)
257
    TimeEntry.find_by_issue_id(self.id,  
258
        :conditions => 'start_time IS NOT NULL and hours IS NULL' + (user ? " and user_id = #{user.id}" : ''))
259
  end
254 260
end
app/models/time_entry.rb (working copy)
25 25
  
26 26
  attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek
27 27

  
28
  acts_as_event :title => Proc.new {|o| "#{o.user}: #{lwr(:label_f_hour, o.hours)} (#{(o.issue || o.project).event_title})"},
29
                :url => Proc.new {|o| {:controller => 'timelog', :action => 'details', :project_id => o.project}},
28
  title_proc = Proc.new do |entry|    
29
    hours = entry.hours ? lwr(:label_f_hour, entry.hours) : "[#{l(:text_in_progress)}]"
30
    "#{entry.user}: #{hours} (#{(entry.issue || entry.project).event_title})"
31
  end
32
  
33
  acts_as_event :title => title_proc,
34
                :url => Proc.new {|entry| {:controller => 'timelog', :action => 'details', :project_id => entry.project}},
30 35
                :author => :user,
31 36
                :description => :comments
32 37
  
33
  validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
38
  validates_presence_of :user_id, :activity_id, :project_id, :spent_on
34 39
  validates_numericality_of :hours, :allow_nil => true
35 40
  validates_length_of :comments, :maximum => 255, :allow_nil => true
36 41

  
......
48 53
  
49 54
  def validate
50 55
    errors.add :hours, :activerecord_error_invalid if hours && (hours < 0 || hours >= 1000)
56
    
57
    if !start_time && !hours
58
      #rather verbose, but l() always translate to English here for some reason
59
      errors.add :hours, ll(User.current.language, 
60
          :activerecord_error_field_must_be_set_if_other_is_not, 
61
          ll(User.current.language, :field_start_time))
62

  
63
      errors.add :start_time, ll(User.current.language, 
64
          :activerecord_error_field_must_be_set_if_other_is_not, 
65
          ll(User.current.language, :field_hours))
66
    end
67
      
51 68
    errors.add :project_id, :activerecord_error_invalid if project.nil?
52 69
    errors.add :issue_id, :activerecord_error_invalid if (issue_id && !issue) || (issue && project!=issue.project)
53 70
  end
......
75 92
      yield
76 93
    end
77 94
  end
95
  
96
  def before_save
97
    if !hours && start_time && end_time
98
      self.hours = (end_time - start_time) / 3600
99
    end
100
  end
101
  
102
  def find_intersecting_entries
103
    params = {:start_time => start_time, :end_time => end_time, :id => id}
104
    
105
    self.class.find_all_by_user_id(user_id, :conditions => 
106
        ["(" + 
107
          #this entry's start time or end time is between other's start_time and end_time
108
          "start_time < :start_time and :start_time < end_time OR " + 
109
          "start_time < :end_time and :end_time < end_time OR " + 
110
          #other's entry's start time or end time is between this entry's start_time and end_time
111
          "start_time > :start_time and start_time < :end_time OR " + 
112
          "end_time > :start_time and end_time < :end_time" +
113
          ")" + 
114
          "and id <> :id", params])
115
  end
78 116
end
lang/bg.yml (working copy)
632 632
setting_mail_handler_api_enabled: Enable WS for incoming emails
633 633
setting_mail_handler_api_key: API key
634 634
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
635
field_start_time: Start Time
636
field_end_time: End Time
637
text_in_progress: in progress
638
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
639
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
640
text_and: and
641
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
642
text_time_entry_intersecting_notice_entry: entry
643
text_time_entry_intersecting_notice_entry_plural: entries
644
button_now: Now
lang/cs.yml (working copy)
637 637
setting_mail_handler_api_enabled: Enable WS for incoming emails
638 638
setting_mail_handler_api_key: API key
639 639
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
640
field_start_time: Start Time
641
field_end_time: End Time
642
text_in_progress: in progress
643
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
644
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
645
text_and: and
646
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
647
text_time_entry_intersecting_notice_entry: entry
648
text_time_entry_intersecting_notice_entry_plural: entries
649
button_now: Now
lang/da.yml (working copy)
634 634
setting_mail_handler_api_enabled: Enable WS for incoming emails
635 635
setting_mail_handler_api_key: API key
636 636
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
637
field_start_time: Start Time
638
field_end_time: End Time
639
text_in_progress: in progress
640
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
641
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
642
text_and: and
643
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
644
text_time_entry_intersecting_notice_entry: entry
645
text_time_entry_intersecting_notice_entry_plural: entries
646
button_now: Now
lang/de.yml (working copy)
633 633
setting_mail_handler_api_enabled: Enable WS for incoming emails
634 634
setting_mail_handler_api_key: API key
635 635
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
636
field_start_time: Start Time
637
field_end_time: End Time
638
text_in_progress: in progress
639
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
640
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
641
text_and: and
642
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
643
text_time_entry_intersecting_notice_entry: entry
644
text_time_entry_intersecting_notice_entry_plural: entries
645
button_now: Now
lang/en.yml (working copy)
170 170
field_hours: Hours
171 171
field_activity: Activity
172 172
field_spent_on: Date
173
field_start_time: Start Time
174
field_end_time: End Time
173 175
field_identifier: Identifier
174 176
field_is_filter: Used as a filter
175 177
field_issue_to_id: Related issue
......
558 560
button_annotate: Annotate
559 561
button_update: Update
560 562
button_configure: Configure
563
button_now: Now
561 564

  
562 565
status_active: active
563 566
status_registered: registered
......
633 636
enumeration_issue_priorities: Issue priorities
634 637
enumeration_doc_categories: Document categories
635 638
enumeration_activities: Activities (time tracking)
639
text_in_progress: in progress
640
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
641
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
642
text_and: and
643
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
644
text_time_entry_intersecting_notice_entry: entry
645
text_time_entry_intersecting_notice_entry_plural: entries
lang/es.yml (working copy)
635 635
setting_mail_handler_api_enabled: Enable WS for incoming emails
636 636
setting_mail_handler_api_key: API key
637 637
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
638
field_start_time: Start Time
639
field_end_time: End Time
640
text_in_progress: in progress
641
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
642
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
643
text_and: and
644
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
645
text_time_entry_intersecting_notice_entry: entry
646
text_time_entry_intersecting_notice_entry_plural: entries
647
button_now: Now
lang/fi.yml (working copy)
632 632
setting_mail_handler_api_enabled: Enable WS for incoming emails
633 633
setting_mail_handler_api_key: API key
634 634
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
635
field_start_time: Start Time
636
field_end_time: End Time
637
text_in_progress: in progress
638
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
639
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
640
text_and: and
641
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
642
text_time_entry_intersecting_notice_entry: entry
643
text_time_entry_intersecting_notice_entry_plural: entries
644
button_now: Now
lang/fr.yml (working copy)
633 633
enumeration_issue_priorities: Priorités des demandes
634 634
enumeration_doc_categories: Catégories des documents
635 635
enumeration_activities: Activités (suivi du temps)
636
field_start_time: Start Time
637
field_end_time: End Time
638
text_in_progress: in progress
639
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
640
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
641
text_and: and
642
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
643
text_time_entry_intersecting_notice_entry: entry
644
text_time_entry_intersecting_notice_entry_plural: entries
645
button_now: Now
lang/he.yml (working copy)
632 632
setting_mail_handler_api_enabled: Enable WS for incoming emails
633 633
setting_mail_handler_api_key: API key
634 634
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
635
field_start_time: Start Time
636
field_end_time: End Time
637
text_in_progress: in progress
638
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
639
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
640
text_and: and
641
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
642
text_time_entry_intersecting_notice_entry: entry
643
text_time_entry_intersecting_notice_entry_plural: entries
644
button_now: Now
lang/hu.yml (working copy)
633 633
setting_mail_handler_api_enabled: Web Service engedélyezése a beérkezett levelekhez
634 634
setting_mail_handler_api_key: API kulcs
635 635
text_email_delivery_not_configured: "Az E-mail küldés nincs konfigurálva, és az értesítések ki vannak kapcsolva.\nÁllítsd be az SMTP szervert a config/email.yml fájlban és indítsd újra az alkalmazást, hogy érvénybe lépjen."
636
field_start_time: Start Time
637
field_end_time: End Time
638
text_in_progress: in progress
639
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
640
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
641
text_and: and
642
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
643
text_time_entry_intersecting_notice_entry: entry
644
text_time_entry_intersecting_notice_entry_plural: entries
645
button_now: Now
lang/it.yml (working copy)
632 632
setting_mail_handler_api_enabled: Abilita WS per le e-mail in arrivo
633 633
setting_mail_handler_api_key: chiave API
634 634
text_email_delivery_not_configured: "La consegna via e-mail non è configurata e le notifiche sono disabilitate.\nConfigura il tuo server SMTP in config/email.yml e riavvia l'applicazione per abilitarle."
635
field_start_time: Start Time
636
field_end_time: End Time
637
text_in_progress: in progress
638
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
639
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
640
text_and: and
641
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
642
text_time_entry_intersecting_notice_entry: entry
643
text_time_entry_intersecting_notice_entry_plural: entries
644
button_now: Now
lang/ja.yml (working copy)
633 633
setting_mail_handler_api_enabled: Enable WS for incoming emails
634 634
setting_mail_handler_api_key: API key
635 635
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
636
field_start_time: Start Time
637
field_end_time: End Time
638
text_in_progress: in progress
639
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
640
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
641
text_and: and
642
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
643
text_time_entry_intersecting_notice_entry: entry
644
text_time_entry_intersecting_notice_entry_plural: entries
645
button_now: Now
lang/ko.yml (working copy)
632 632
setting_mail_handler_api_enabled: Enable WS for incoming emails
633 633
setting_mail_handler_api_key: API key
634 634
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
635
field_start_time: Start Time
636
field_end_time: End Time
637
text_in_progress: in progress
638
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
639
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
640
text_and: and
641
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
642
text_time_entry_intersecting_notice_entry: entry
643
text_time_entry_intersecting_notice_entry_plural: entries
644
button_now: Now
lang/lt.yml (working copy)
635 635
setting_mail_handler_api_key: API raktas
636 636

  
637 637
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
638
field_start_time: Start Time
639
field_end_time: End Time
640
text_in_progress: in progress
641
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
642
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
643
text_and: and
644
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
645
text_time_entry_intersecting_notice_entry: entry
646
text_time_entry_intersecting_notice_entry_plural: entries
647
button_now: Now
lang/nl.yml (working copy)
633 633
setting_mail_handler_api_enabled: Enable WS for incoming emails
634 634
setting_mail_handler_api_key: API key
635 635
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
636
field_start_time: Start Time
637
field_end_time: End Time
638
text_in_progress: in progress
639
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
640
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
641
text_and: and
642
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
643
text_time_entry_intersecting_notice_entry: entry
644
text_time_entry_intersecting_notice_entry_plural: entries
645
button_now: Now
lang/no.yml (working copy)
633 633
setting_mail_handler_api_enabled: Enable WS for incoming emails
634 634
setting_mail_handler_api_key: API key
635 635
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
636
field_start_time: Start Time
637
field_end_time: End Time
638
text_in_progress: in progress
639
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
640
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
641
text_and: and
642
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
643
text_time_entry_intersecting_notice_entry: entry
644
text_time_entry_intersecting_notice_entry_plural: entries
645
button_now: Now
lang/pl.yml (working copy)
632 632
setting_mail_handler_api_enabled: Enable WS for incoming emails
633 633
setting_mail_handler_api_key: API key
634 634
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
635
field_start_time: Start Time
636
field_end_time: End Time
637
text_in_progress: in progress
638
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
639
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
640
text_and: and
641
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
642
text_time_entry_intersecting_notice_entry: entry
643
text_time_entry_intersecting_notice_entry_plural: entries
644
button_now: Now
lang/pt-br.yml (working copy)
632 632
setting_mail_handler_api_enabled: Enable WS for incoming emails
633 633
setting_mail_handler_api_key: API key
634 634
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
635
field_start_time: Start Time
636
field_end_time: End Time
637
text_in_progress: in progress
638
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
639
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
640
text_and: and
641
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
642
text_time_entry_intersecting_notice_entry: entry
643
text_time_entry_intersecting_notice_entry_plural: entries
644
button_now: Now
lang/pt.yml (working copy)
632 632
setting_mail_handler_api_enabled: Enable WS for incoming emails
633 633
setting_mail_handler_api_key: API key
634 634
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
635
field_start_time: Start Time
636
field_end_time: End Time
637
text_in_progress: in progress
638
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
639
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
640
text_and: and
641
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
642
text_time_entry_intersecting_notice_entry: entry
643
text_time_entry_intersecting_notice_entry_plural: entries
644
button_now: Now
lang/ro.yml (working copy)
632 632
setting_mail_handler_api_enabled: Enable WS for incoming emails
633 633
setting_mail_handler_api_key: API key
634 634
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
635
field_start_time: Start Time
636
field_end_time: End Time
637
text_in_progress: in progress
638
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
639
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
640
text_and: and
641
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
642
text_time_entry_intersecting_notice_entry: entry
643
text_time_entry_intersecting_notice_entry_plural: entries
644
button_now: Now
lang/ru.yml (working copy)
164 164
field_url: URL
165 165
field_start_page: Стартовая страница
166 166
field_subproject: Подпроект
167
field_hours: Час(а,ов)
167
field_hours: Часов
168 168
field_activity: Деятельность
169 169
field_spent_on: Дата
170
field_start_time: Время начала
171
field_end_time: Время окончания
170 172
field_identifier: Ун. идентификатор
171 173
field_is_filter: Используется в качестве фильтра
172 174
field_issue_to_id: Связанные задачи
......
395 397
label_issue_tracking: Ситуация по задачам
396 398
label_spent_time: Затраченное время
397 399
label_f_hour: %.2f час
398
label_f_hour_plural: %.2f часов(а)
400
label_f_hour_plural: %.2f часов
399 401
label_time_tracking: Учет времени
400 402
label_change_plural: Правки
401 403
label_statistics: Статистика
......
508 510
button_copy: Копировать
509 511
button_annotate: Авторство
510 512
button_update: Обновить
513
button_now: Сейчас
511 514

  
512 515
status_active: Активен
513 516
status_registered: Зарегистрирован
......
636 639
setting_mail_handler_api_enabled: Включить веб-сервис для входящих сообщений
637 640
setting_mail_handler_api_key: API ключ
638 641
text_email_delivery_not_configured: "Параметры работы с почтовым сервером не настроены и функция уведомления по email не активна.\nНастроить параметры для вашего SMTP сервера вы можете в файле config/email.yml. Для применения изменений перезапустите приложение."
642
text_in_progress: в процессе
643
activerecord_error_field_must_be_set_if_other_is_not: необходимо задать, если значение поля '%s' не задано
644
text_clear_to_recalculate_time_by_range: Очистите, чтобы пересчитать по времени окончания
645
text_and: и
646
text_time_entry_intersecting_notice: Обратите внимание, что эта запись о потраченном времени (%s) пересекается с %s.
647
text_time_entry_intersecting_notice_entry: записью
648
text_time_entry_intersecting_notice_entry_plural: записями
lang/sr.yml (working copy)
633 633
setting_mail_handler_api_enabled: Enable WS for incoming emails
634 634
setting_mail_handler_api_key: API key
635 635
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
636
field_start_time: Start Time
637
field_end_time: End Time
638
text_in_progress: in progress
639
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
640
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
641
text_and: and
642
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
643
text_time_entry_intersecting_notice_entry: entry
644
text_time_entry_intersecting_notice_entry_plural: entries
645
button_now: Now
lang/sv.yml (working copy)
633 633
setting_mail_handler_api_enabled: Enable WS for incoming emails
634 634
setting_mail_handler_api_key: API key
635 635
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
636
field_start_time: Start Time
637
field_end_time: End Time
638
text_in_progress: in progress
639
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
640
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
641
text_and: and
642
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
643
text_time_entry_intersecting_notice_entry: entry
644
text_time_entry_intersecting_notice_entry_plural: entries
645
button_now: Now
lang/th.yml (working copy)
635 635
setting_mail_handler_api_enabled: Enable WS for incoming emails
636 636
setting_mail_handler_api_key: API key
637 637
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
638
field_start_time: Start Time
639
field_end_time: End Time
640
text_in_progress: in progress
641
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
642
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
643
text_and: and
644
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
645
text_time_entry_intersecting_notice_entry: entry
646
text_time_entry_intersecting_notice_entry_plural: entries
647
button_now: Now
lang/uk.yml (working copy)
634 634
setting_mail_handler_api_enabled: Enable WS for incoming emails
635 635
setting_mail_handler_api_key: API key
636 636
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
637
field_start_time: Start Time
638
field_end_time: End Time
639
text_in_progress: in progress
640
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
641
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
642
text_and: and
643
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
644
text_time_entry_intersecting_notice_entry: entry
645
text_time_entry_intersecting_notice_entry_plural: entries
646
button_now: Now
lang/zh-tw.yml (working copy)
633 633
enumeration_issue_priorities: 項目優先權
634 634
enumeration_doc_categories: 文件分類
635 635
enumeration_activities: 活動 (時間追蹤)
636
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
637
field_start_time: Start Time
638
field_end_time: End Time
639
text_in_progress: in progress
640
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
641
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
642
text_and: and
643
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
644
text_time_entry_intersecting_notice_entry: entry
645
text_time_entry_intersecting_notice_entry_plural: entries
646
button_now: Now
lang/zh.yml (working copy)
633 633
enumeration_doc_categories: 文档类别
634 634
enumeration_activities: 活动(时间跟踪)
635 635
text_email_delivery_not_configured: "Email delivery is not configured, and notifications are disabled.\nConfigure your SMTP server in config/email.yml and restart the application to enable them."
636
field_start_time: Start Time
637
field_end_time: End Time
638
text_in_progress: in progress
639
activerecord_error_field_must_be_set_if_other_is_not: must be set if %s is not
640
text_clear_to_recalculate_time_by_range: Clear to recalculate by start/end time
641
text_and: and
642
text_time_entry_intersecting_notice: Notice that this timelog entry (%s) intersects with %s.
643
text_time_entry_intersecting_notice_entry: entry
644
text_time_entry_intersecting_notice_entry_plural: entries
645
button_now: Now
public/javascripts/time.js (revision 0)
1
//taken from XPlanner
2
var dateFormatChars = "dMyhHmsa";
3

  
4
function formatDate(date, format) {
5
    return formatDate2(date, format, 0);
6
}
7

  
8
function formatDate2(date, format, offset) {
9
    if (offset >= format.length) {
10
        return "";
11
    } else if (dateFormatChars.indexOf(format.charAt(offset)) != -1) {
12
        return formatDateElement(date, format, offset);
13
    } else {
14
        return formatDateLiteral(date, format, offset);
15
    }
16
}
17

  
18
function formatDateElement(date, format, offset) {
19
    var end = offset;
20
    var ch = format.charAt(offset);
21
    while (++end < format.length && format.charAt(end) == ch);
22
    var count = end - offset;
23
    var value;
24
    if (ch == 'd') {
25
        value = padValue(count, date.getDate());
26
    }
27
    else if (ch == 'M') {
28
        value = padValue(count, date.getMonth()+1);
29
    }
30
    else if (ch == 'y') {
31
        value = padValue(count, date.getFullYear());
32
    }
33
    else if (ch == 'H') {
34
        value = padValue(count, date.getHours());
35
    }
36
    else if (ch == 'h') {
37
        value = padValue(count, date.getHours() % 12);
38
    }
39
    else if (ch == 'm') {
40
        value = padValue(count, date.getMinutes());
41
    }
42
    else if (ch == 's') {
43
        value = padValue(count, date.getSeconds());
44
    }
45
    else if (ch == 'a') {
46
        value = date.getHours() > 12 ? 'PM' : 'AM';
47
    }
48
    return value + formatDate2(date, format, end);
49
}
50

  
51
function padValue(count, value) {
52
    for (i = value.toString().length; i < count; i++) {
53
        value = '0'+value;
54
    }
55
    return value;
56
}
57

  
58
function formatDateLiteral(date, format, offset) {
59
    end = offset;
60
    while (++end < format.length && dateFormatChars.indexOf(format.charAt(end)) == -1);
61
    return format.substr(offset, end - offset) + formatDate2(date, format, end);
62
}
63

  
64
function maybeSetTimeNow(field) {
65
  if (field.value == "") {
66
    field.value = formatDate(new Date(), "yyyy-MM-dd HH:mm");
67
  }
68
}
test/fixtures/time_entries.yml (working copy)
55 55
  hours: 7.65
56 56
  user_id: 1
57 57
  tyear: 2007
58
  
58

  
59
#this is to run existing controller tests with Start/End time improvement - specific entries
60
time_entry_in_progress: 
61
  created_on: 2007-04-22 12:20:48 +02:00
62
  tweek: 16
63
  tmonth: 4
64
  project_id: 1
65
  comments: Time spent on a subproject (in progress)
66
  updated_on: 2007-04-22 12:20:48 +02:00
67
  activity_id: 10
68
  spent_on: 2007-04-22
69
  issue_id: 1
70
  id: 5
71
  user_id: 1
72
  tyear: 2007
73
  start_time: 2007-07-11 16:20
74

  
75
#same user but another issue and project, and intersects with time_entry_in_progress
76
intersecting_time_entry: 
77
  created_on: 2007-04-22 12:20:48 +02:00
78
  tweek: 16
79
  tmonth: 4
80
  project_id: 2
81
  comments: Time entry intersecting with time_entry_in_progress
82
  updated_on: 2007-04-22 12:20:48 +02:00
83
  activity_id: 10
84
  spent_on: 2007-04-22
85
  issue_id: 4
86
  id: 6
87
  user_id: 1
88
  tyear: 2007
89
  start_time: 2007-07-11 15:20
90
  end_time: 2007-07-11 17:20
91
  hours: 0 #to not break time calculation tests
92

  
93
#this spans several years - should intersect with time_entry_in_progress as well
94
big_intersecting_time_entry: 
95
  created_on: 2007-04-22 12:20:48 +02:00
96
  tweek: 16
97
  tmonth: 4
98
  project_id: 1
99
  comments: Time entry intersecting with time_entry_in_progress
100
  updated_on: 2007-04-22 12:20:48 +02:00
101
  activity_id: 10
102
  spent_on: 2007-04-22
103
  issue_id: 1
104
  id: 7
105
  user_id: 1
106
  tyear: 2007
107
  start_time: 2005-07-11 16:30
108
  end_time: 2007-07-11 17:20
109
  hours: 0 #to not break time calculation tests
test/functional/issues_controller_test.rb (working copy)
164 164
                                            :content => /Notes/ } }
165 165
  end
166 166

  
167
  def test_show_in_progress
168
    @request.session[:user_id] = 1
169
    get :show, :id => 1
170
    assert_equal time_entries(:time_entry_in_progress), assigns(:time_entry),
171
        'there should be an in-progress time entry for this user'
172
  end
173
  
174
  def test_show_not_in_progress
175
    @request.session[:user_id] = 2
176
    get :show, :id => 1
177
    assert_nil assigns(:time_entry).id, 'time entry for this user should be new'
178
  end
179
    
167 180
  def test_get_new
168 181
    @request.session[:user_id] = 2
169 182
    get :new, :project_id => 1, :tracker_id => 1
......
381 394
    issue = Issue.find(1)
382 395
    assert_equal 1, issue.status_id
383 396
    @request.session[:user_id] = 2
384
    assert_difference('TimeEntry.count', 0) do
397
    assert_difference('TimeEntry.count', 0) do #time entries count should not change because given entry is empty (hence invalid)
385 398
      post :edit,
386 399
           :id => 1,
387 400
           :issue => { :status_id => 2, :assigned_to_id => 3 },
......
399 412
    assert mail.body.include?("Status changed from New to Assigned")
400 413
  end
401 414
  
415
  def test_post_edit_with_task_in_progress
416
    user_id = 1
417
    @request.session[:user_id] = user_id
418
    issue = Issue.find(1)
419
    time_entry = issue.time_entry_in_progress(User.find(user_id))
420
    assert_not_nil time_entry, 'there should be time entry in progress'
421
    assert_difference('TimeEntry.count', 0) do #time entries count should not change because entry in progress should be updated
422
      post :edit,
423
           :id => 1,
424
           :issue => { },
425
           :notes => 'Assigned to dlopper',
426
           :time_entry => { :id => time_entry.id, :comments => 'xyz' }
427
    end
428
  end
429
  
402 430
  def test_post_edit_with_note_only
403 431
    notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
404 432
    # anonymous user
......
428 456
    
429 457
    issue = Issue.find(1)
430 458
    
431
    j = issue.journals.find(:first, :order => 'id DESC')
432
    assert_equal '2.5 hours added', j.notes
433
    assert_equal 0, j.details.size
459
    #:first doesn't work sometimes here with MySQL 5.0.51b
460
    e = issue.journals.find(:all, :order => 'id DESC')[0] 
461
    assert_equal '2.5 hours added', e.notes
462
    assert_equal 0, e.details.size
434 463
    
435
    t = issue.time_entries.find(:first, :order => 'id DESC')
464
    #:first doesn't work sometimes here with MySQL 5.0.51b
465
    t = issue.time_entries.find(:all, :order => 'id DESC')[0]
436 466
    assert_not_nil t
437 467
    assert_equal 2.5, t.hours
438 468
    assert_equal spent_hours_before + 2.5, issue.spent_hours
test/functional/timelog_controller_test.rb (working copy)
40 40
                                 :content => 'Development'
41 41
  end
42 42
  
43
  def test_get_edit_in_progress_by_entry_id
44
    @request.session[:user_id] = 1 #to avoid 'no permission' error
45
    get :edit, :id => time_entries(:time_entry_in_progress)
46
    assert_response :success
47
    assert_equal 'time_entry_end_time', assigns(:activate_field)
48
  end
49
  
50
  def test_get_edit_not_in_progress
51
    @request.session[:user_id] = 2 #there are no in-progress entries of this user for the issue
52
    get :edit, :issue_id => 1
53
    assert_response :success
54
    assert_equal 'time_entry_start_time', assigns(:activate_field)
55
  end
56
  
57
  def test_get_edit_in_progress_by_issue_id
58
    user_id = 1
59
    @request.session[:user_id] = user_id #there is in-progress entry of this user for the issue
60
    issue_id = 1
61
    get :edit, :issue_id => issue_id
62
    assert_redirected_to :action => "edit", :id => Issue.find(issue_id).time_entry_in_progress(User.find(user_id)).id
63
  end
64
  
43 65
  def test_post_edit
44 66
    @request.session[:user_id] = 3
45 67
    post :edit, :project_id => 1,
......
70 92
    post :edit, :id => 1,
71 93
                :time_entry => {:issue_id => '2',
72 94
                                :hours => '8'}
95
    assert_no_errors(assigns(:time_entry))
73 96
    assert_redirected_to 'projects/ecookbook/timelog/details'
74 97
    entry.reload
75 98
    
......
165 188
    assert_response :success
166 189
    assert_template 'details'
167 190
    assert_not_nil assigns(:entries)
168
    assert_equal 4, assigns(:entries).size
191
    assert_equal 6, assigns(:entries).size
169 192
    # project and subproject
170 193
    assert_equal [1, 3], assigns(:entries).collect(&:project_id).uniq.sort
171 194
    assert_not_nil assigns(:total_hours)
......
180 203
    assert_response :success
181 204
    assert_template 'details'
182 205
    assert_not_nil assigns(:entries)
183
    assert_equal 3, assigns(:entries).size
206
    assert_equal 5, assigns(:entries).size
184 207
    assert_not_nil assigns(:total_hours)
185 208
    assert_equal "12.90", "%.2f" % assigns(:total_hours)
186 209
    assert_equal '2007-03-20'.to_date, assigns(:from)
......
202 225
    assert_response :success
203 226
    assert_template 'details'
204 227
    assert_not_nil assigns(:entries)
205
    assert_equal 2, assigns(:entries).size
228
    assert_equal 4, assigns(:entries).size
206 229
    assert_not_nil assigns(:total_hours)
207 230
    assert_equal 154.25, assigns(:total_hours)
208 231
    # display all time by default
test/test_helper.rb (working copy)
64 64
    Dir.mkdir "#{RAILS_ROOT}/tmp/test/attachments" unless File.directory?("#{RAILS_ROOT}/tmp/test/attachments")
65 65
    Attachment.storage_path = "#{RAILS_ROOT}/tmp/test/attachments"
66 66
  end
67

  
68
  def assert_error_on(object, field)
69
    object.valid?
70
    assert object.errors.on(field), "expected error on #{field} attribute"
71
  end
72

  
73
  def assert_no_error_on(object, field)
74
    object.valid?
75
    assert !object.errors.on(field), "expected no error on #{field} attribute"
76
  end
77

  
78
  def assert_no_errors(object, options = {})
79
    object.valid? if options[:validate]
80
    assert_equal [], object.errors.full_messages
81
  end
67 82
end
test/unit/issue_test.rb (working copy)
181 181
    assert_nil Issue.find_by_id(1)
182 182
    assert_nil TimeEntry.find_by_issue_id(1)
183 183
  end
184
  
185
  def test_find_in_progress_success
186
    assert_equal time_entries(:time_entry_in_progress), 
187
        issues(:issues_001).time_entry_in_progress(users(:users_001))
188
  end
189

  
190
  def test_find_in_progress_failure
191
    assert_nil issues(:issues_001).time_entry_in_progress(users(:users_002))
192
  end
184 193
end
test/unit/time_entry_test.rb (working copy)
20 20
class TimeEntryTest < Test::Unit::TestCase
21 21
  fixtures :issues, :projects, :users, :time_entries
22 22

  
23
  def setup
24
    User.current.language = 'en'
25
  end
26
  
23 27
  def test_hours_format
24 28
    assertions = { "2"      => 2.0,
25 29
                   "21.1"   => 21.1,
......
43 47
      assert_equal v, t.hours
44 48
    end
45 49
  end
50
  
51
  def test_start_time_must_be_set_if_hours_are_not_and_reverse
52
    entry = TimeEntry.new
53
    assert_error_on(entry, :start_time)
54
    assert_error_on(entry, :hours)    
55
  end
56

  
57
  def test_start_time_can_be_absent_if_hours_are_set_and_reverse
58
    entry = TimeEntry.new :hours => 1
59
    entry.valid?
60
    assert_no_error_on(entry, :start_time)
61
    entry = TimeEntry.new :start_time => Time.now
62
    entry.valid?
63
    assert_no_error_on(entry, :hours)
64
  end
65

  
66
  def successful_params
67
    {:spent_on => '2008-07-13', :issue_id => 1, :user => users(:users_004), 
68
      :activity_id => Enumeration.get_values('ACTI').first}
69
  end
70
  
71
  def test_hours_not_calculated_if_set_explicitly
72
    #I worked on this time to time during the day, and it was 1 hour in sum
73
    entry = TimeEntry.new successful_params.merge(:hours => 1, 
74
      :start_time => '2008-07-13 10:56', :end_time => '2008-07-14 10:56')
75
      
76
    entry.save!    
77
    assert_equal 1, entry.hours
78
  end
79

  
80
  {['10:56', '11:56'] => 1, ['10:56', '11:26'] => 0.5, 
81
      ['10:56', '10:57'] => 0.0167,
82
      ['2008-07-13 23:50', '2008-07-14 00:20'] => 0.5}.each do |range, hours|
83
    
84
    define_method "test_hours_calculated_#{range[0]}_to_#{range[1]}" do
85
      
86
      #add default day if not specified
87
      range = range.map {|time| time['-'] ? time : '2008-07-13 ' + time} 
88
      
89
      entry = TimeEntry.new successful_params.merge(:hours => nil, 
90
        :start_time => range[0], :end_time => range[1])
91
      entry.save!    
92
      assert_in_delta hours, entry.hours, 0.0001
93
    end
94
  end
95
  
96
  def assert_intersects(source, dest)
97
    intersecting = time_entries(source).find_intersecting_entries
98
    
99
    assert !intersecting.empty?, 
100
      "there should be intersecting entries for #{source.inspect}"
101
    
102
    assert intersecting.map {|e| e.id}.
103
        include?(time_entries(dest).id), 
104
        "#{source.inspect} should intersect with #{dest.inspect}"
105
    
106
    intersecting
107
  end
108
  
109
  def test_find_intersecting_entries_for_incomplete
110
    assert_intersects(:time_entry_in_progress, :intersecting_time_entry)
111
  end
112

  
113
  def test_find_intersecting_entries_for_complete_doesnt_find_itself
114
    intersecting = assert_intersects(:intersecting_time_entry, 
115
      :time_entry_in_progress)
116

  
117
    assert !intersecting.map {|e| e.id}.include?(
118
      time_entries(:intersecting_time_entry)), 'time entry\'s ' + 
119
      'intersecting entries shouldn\'t include itself'
120
  end
121

  
122
  def test_find_intersecting_entries_for_big
123
    assert_intersects(:big_intersecting_time_entry, :time_entry_in_progress)
124
    assert_intersects(:big_intersecting_time_entry, :intersecting_time_entry)
125
  end
46 126
end
vendor/plugins/test_utils/MIT-LICENSE (revision 0)
1
Copyright (C) 2008 Texuna Technologies
2

  
3
Permission is hereby granted, free of charge, to any person obtaining
4
a copy of this software and associated documentation files (the
5
"Software"), to deal in the Software without restriction, including
6
without limitation the rights to use, copy, modify, merge, publish,
7
distribute, sublicense, and/or sell copies of the Software, and to
8
permit persons to whom the Software is furnished to do so, subject to
9
the following conditions:
10

  
11
The above copyright notice and this permission notice shall be
12
included in all copies or substantial portions of the Software.
13

  
14
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21

  
vendor/plugins/test_utils/tasks/test_utils.rake (revision 0)
1
##
2
# based on http://nubyonrails.com/articles/foscon-and-living-dangerously-with-rake
3
# Run a single test (or group of tests started with given string) in Rails.
4
#
5
#   rake blogs-list (or f_blogs-list)
6
#   => Runs test_list for BlogsController (functional test; use f_blogs-list to force it if unit test found)
7
#
8
#   rake blog-create (or u_blog-create)
9
#   => Runs test_create for BlogTest (unit test; use u_blog-create to force it if functional test found))
10

  
11
#test file will be matched in the order of this array items 
12
TEST_TYPES = [
13
    ['u_', "unit/[file_name]_test.rb"],
14
    ['f_', "functional/[file_name]_controller_test.rb"],
15
    ['i_', "integration/[file_name]_test.rb"],
16
    ['l_', "long/[file_name]_test.rb"],
17
]
18

  
19
rule "" do |t|
20
  all_flags = TEST_TYPES.map { |item| item[0] }
21
  if Regexp.new("(#{all_flags.join '|'}|)(.*)\\-([^.]+)$").match(t.name)
22
    flag = $1
23
    file_name = $2
24
    test_name = $3
25

  
26
    path_getter = lambda { |type_info| type_info[1].gsub '[file_name]', file_name}
27
    file_path = nil
28
    TEST_TYPES.each do |type_info|
29
      my_file_path = path_getter.call(type_info)
30
      if flag == type_info[0]
31
        type_info[1].match /((.+)\/)/
32
        type = $2
33
        puts "forced #{type} test"
34
        file_path = my_file_path
35
        break
36
      end
37
    end
38

  
39
    if file_path && !File.exist?("test/#{file_path}")
40
      raise "No file found for #{file_path}"
41
    end
42

  
43
    if !file_path
44
      TEST_TYPES.each do |type_info|
45
        my_file_path = path_getter.call(type_info)
46
        if File.exist? "test/#{my_file_path}"
47
          puts "found #{my_file_path}"
48
          file_path = my_file_path
49
          break
50
        end
51
      end
52
    end
53

  
54
    if !file_path
55
      raise "No file found for #{file_name}"
56
    end
57

  
58
		begin
59
      sh "ruby -Ilib:test test/#{file_path} -n /^test_#{test_name}/"
60
    rescue Exception => e
61
      #no logger here, oops!
62
    	#log.debug "error executing tests: #{e.inspect}"
63
      puts "error executing tests: #{e.inspect}"
64
    end
65
  end
66
end