Project

General

Profile

Feature #5458 » 5458-use_time_in_issue_start_and_due_date_2.3_stable_V9_3.diff

Sergey Karpushin, 2016-01-28 16:02

View differences:

app/controllers/issues_controller.rb (working copy)
425 425
      @issue.project ||= @issue.allowed_target_projects.first
426 426
    end
427 427
    @issue.author ||= User.current
428
    @issue.start_date ||= Date.today if Setting.default_issue_start_date_to_creation_date?
428
    @issue.start_date ||= DateTime.now if Setting.default_issue_start_date_to_creation_date?
429 429

  
430 430
    attrs = (params[:issue] || {}).deep_dup
431 431
    if action_name == 'new' && params[:was_default_status] == attrs[:status_id]
app/helpers/application_helper.rb (working copy)
291 291
    end
292 292
  end
293 293

  
294
  def time_select_tag( name, stime, options = {} )
295
    time = stime.to_time(:utc)
296
    if time.nil?
297
      selected = {:hour => '', :min => ''}
298
    else
299
      zone = User.current.time_zone
300
      time = zone ? time.in_time_zone(zone) : (time.utc? ? time.localtime : time)
301
      selected = {:hour => time.hour, :min => time.min}
302
    end
303

  
304
    out = ''
305

  
306
    if options[:required]
307
      hours = []
308
      mins = []
309
    else
310
      hours = [['', '']]
311
      mins = [['', '']]
312
    end
313

  
314
    hours += (0..23).map{|i| ['%02d' % i, i] } # Zero pad
315
    out << select_tag(
316
      "#{name}[hour]",
317
      options_for_select( hours, selected[:hour] ),
318
      :style => 'min-width: 10px;max-width: 50px;'
319
    )
320

  
321
    out << ':'
322
    mins += (0..59).map{|i| ['%02d' % i, i] } # Zero pad
323
    out << select_tag(
324
      "#{name}[minute]",
325
      options_for_select( mins, selected[:min] ),
326
      :style => 'min-width: 10px;max-width: 50px;'
327
    )
328
  end
329

  
294 330
  def project_tree_options_for_select(projects, options = {})
295 331
    s = ''
296 332
    project_tree(projects) do |project, level|
app/helpers/queries_helper.rb (working copy)
151 151
        value.to_s(issue) {|other| link_to_issue(other, :subject => false, :tracker => false)}.html_safe,
152 152
        :class => value.css_classes_for(issue))
153 153
    else
154
      format_object(value)
154
              case value.class.name
155
              when 'Time'
156
                if ( column.name == :start_date or column.name == :due_date ) and
157
                  ( !issue.project.use_datetime_for_issues or value.strftime('%H%M')=='0000' )
158
                  format_date(value)
159
                else
160
                  format_time(value)
161
                end
162
              else
163
                format_object(value)
164
              end
155 165
    end
156 166
  end
157 167

  
app/models/issue.rb (working copy)
61 61
  DONE_RATIO_OPTIONS = %w(issue_field issue_status)
62 62

  
63 63
  attr_reader :current_journal
64
  attr_accessor :start_time
65
  attr_accessor :due_time
66

  
64 67
  delegate :notes, :notes=, :private_notes, :private_notes=, :to => :current_journal, :allow_nil => true
65 68

  
66 69
  validates_presence_of :subject, :priority, :project, :tracker, :author, :status
......
68 71
  validates_length_of :subject, :maximum => 255
69 72
  validates_inclusion_of :done_ratio, :in => 0..100
70 73
  validates :estimated_hours, :numericality => {:greater_than_or_equal_to => 0, :allow_nil => true, :message => :invalid}
71
  validates :start_date, :date => true
72
  validates :due_date, :date => true
74
  #validates :start_date, :date => true
75
  #validates :due_date, :date => true
73 76
  validate :validate_issue, :validate_required_fields
74 77

  
75 78
  scope :visible, lambda {|*args|
......
91 94

  
92 95
  before_validation :clear_disabled_fields
93 96
  before_create :default_assign
94
  before_save :close_duplicates, :update_done_ratio_from_issue_status,
95
              :force_updated_on_change, :update_closed_on, :set_assigned_to_was
97
  #Note very well - before_save runs for both updates AND creations (also, before_create is called after before_save)
98
  before_save :close_duplicates, :update_done_ratio_from_issue_status,
99
              :force_updated_on_change, :update_closed_on, :set_assigned_to_was, :add_start_and_due_time
96 100
  after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
97 101
  after_save :reschedule_following_issues, :update_nested_set_attributes,
98 102
             :update_parent_attributes, :create_journal
......
361 365
    'subject',
362 366
    'description',
363 367
    'start_date',
368
    'start_time',
364 369
    'due_date',
370
    'due_time',    
365 371
    'done_ratio',
366 372
    'estimated_hours',
367 373
    'custom_field_values',
......
454 460

  
455 461
    # mass-assignment security bypass
456 462
    assign_attributes attrs, :without_protection => true
463

  
464
    # Helper to make sure that when we load in the time it takes into account that it is supplied localised and must be converted back to utc.
465
    def load_localised_time(date, time, time_zone)
466
      localised_date = {:year => date.year, :month => date.month, :day => date.day}
467
      localised_datetime = date.in_time_zone(time_zone).change({:hour => time['hour'].to_i, :min => time['minute'].to_i})
468
      return localised_datetime.change(localised_date).utc
469
    end
470

  
471
    if (start_time = attrs.delete('start_time')) && safe_attribute?('start_time') && self.start_date.is_a?(Time)
472
      self.start_date = load_localised_time(self.start_date, start_time, user.time_zone)
473
    end
474

  
475
    if (due_time = attrs.delete('due_time')) && safe_attribute?('due_time') && self.due_date.is_a?(Time)
476
      self.due_date = load_localised_time(self.due_date, due_time, user.time_zone)
477
    end
457 478
  end
458 479

  
459 480
  def disabled_core_fields
......
1388 1409
    end
1389 1410
  end
1390 1411

  
1412
  # Callback on start and due time
1413
  def add_start_and_due_time
1414
    return if not project.use_datetime_for_issues
1415

  
1416
    # Not sure if this is a hack or not, but it works :)
1417
    time_zone = User.current.time_zone
1418
    system_time_zone = Time.zone
1419
    if time_zone
1420
      Time.zone = time_zone
1421
    end
1422

  
1423
    if st=start_time and sd=start_date
1424
      if st['hour'].to_i >= 0 or st['minute'].to_i >= 0
1425
        self.start_date = Time.zone.parse( "#{sd.year}.#{sd.month}.#{sd.day} #{st['hour']}:#{st['minute']}:00" ).utc # Parse in as local but save as UTC
1426
      end
1427
    end
1428

  
1429
    if dt=due_time and dd=due_date
1430
      if dt['hour'].to_i >= 0 or dt['minute'].to_i >= 0
1431
        self.due_date = Time.zone.parse( "#{dd.year}.#{dd.month}.#{dd.day} #{dt['hour']}:#{dt['minute']}:00").utc # Parse in as local but save as UTC
1432
      end
1433
    end
1434

  
1435
    # Since we fudged the timezone to get the values parsing in okay, let's reset it to the system timezone.
1436
    Time.zone = system_time_zone
1437
  end
1438

  
1391 1439
  # Default assignment based on category
1392 1440
  def default_assign
1393 1441
    if assigned_to.nil? && category && category.assigned_to
app/models/project.rb (working copy)
549 549
  # The earliest start date of a project, based on it's issues and versions
550 550
  def start_date
551 551
    @start_date ||= [
552
     issues.minimum('start_date'),
552
     issues.minimum('start_date').nil? ? nil : issues.minimum('start_date').to_date,
553 553
     shared_versions.minimum('effective_date'),
554
     Issue.fixed_version(shared_versions).minimum('start_date')
554
     Issue.fixed_version(shared_versions).minimum('start_date').nil? ? nil : Issue.fixed_version(shared_versions).minimum('start_date').to_date
555 555
    ].compact.min
556 556
  end
557 557

  
558 558
  # The latest due date of an issue or version
559 559
  def due_date
560 560
    @due_date ||= [
561
     issues.maximum('due_date'),
561
     issues.maximum('due_date').nil? ? nil : issues.maximum('due_date').to_date,
562 562
     shared_versions.maximum('effective_date'),
563
     Issue.fixed_version(shared_versions).maximum('due_date')
563
     Issue.fixed_version(shared_versions).maximum('due_date').nil? ? nil : Issue.fixed_version(shared_versions).maximum('due_date').to_date
564 564
    ].compact.max
565 565
  end
566 566

  
567 567
  def overdue?
568
    active? && !due_date.nil? && (due_date < Date.today)
568
    active? && !due_date.nil? && (due_date < DateTime.now)
569 569
  end
570 570

  
571 571
  # Returns the percent completed for this project, based on the
......
650 650
    'description',
651 651
    'homepage',
652 652
    'is_public',
653
    'use_datetime_for_issues',
653 654
    'identifier',
654 655
    'custom_field_values',
655 656
    'custom_fields',
app/models/version.rb (working copy)
100 100
    if completed_percent == 100
101 101
      return false
102 102
    elsif due_date && start_date
103
      done_date = start_date + ((due_date - start_date+1)* completed_percent/100).floor
103
      done_date = start_date.to_date + ((due_date.to_date - start_date.to_date + 1) * completed_percent/100).floor
104 104
      return done_date <= Date.today
105 105
    else
106 106
      false # No issues so it's not late
app/views/issues/_attributes.html.erb (working copy)
48 48

  
49 49
<% if @issue.safe_attribute? 'start_date' %>
50 50
<p id="start_date_area">
51
  <%= f.text_field(:start_date, :size => 10, :required => @issue.required_attribute?('start_date')) %>
51
  <%= f.text_field(:start_date, :value => (@issue.start_date ? localise_date(@issue.start_date).strftime('%Y-%m-%d') : ''), :size => 10, :required => @issue.required_attribute?('start_date')) %>
52 52
  <%= calendar_for('issue_start_date') if @issue.leaf? %>
53 53
</p>
54 54
<% end %>
55 55

  
56 56
<% if @issue.safe_attribute? 'due_date' %>
57 57
<p id="due_date_area">
58
  <%= f.text_field(:due_date, :size => 10, :required => @issue.required_attribute?('due_date')) %>
58
  <%= f.text_field(:due_date, :value => (@issue.due_date ? localise_date(@issue.due_date).strftime('%Y-%m-%d') : ''), :size => 10, :required => @issue.required_attribute?('due_date')) %>
59 59
  <%= calendar_for('issue_due_date') if @issue.leaf? %>
60 60
</p>
61 61
<% end %>
app/views/issues/show.html.erb (working copy)
47 47
  end
48 48

  
49 49
  unless @issue.disabled_core_fields.include?('start_date')
50
    rows.right l(:field_start_date), format_date(@issue.start_date), :class => 'start-date'
50
    rows.right l(:field_start_date), (@project.use_datetime_for_issues ? format_time(@issue.start_date) : format_date(@issue.start_date)), :class => 'start-date'
51 51
  end
52 52
  unless @issue.disabled_core_fields.include?('due_date')
53
    rows.right l(:field_due_date), format_date(@issue.due_date), :class => 'due-date'
53
    rows.right l(:field_due_date), (@project.use_datetime_for_issues ? format_time(@issue.due_date) : format_date(@issue.due_date)), :class => 'due-date'
54 54
  end
55 55
  unless @issue.disabled_core_fields.include?('done_ratio')
56 56
    rows.right l(:field_done_ratio), progress_bar(@issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%"), :class => 'progress'
app/views/projects/_form.html.erb (working copy)
11 11
<% end %></p>
12 12
<p><%= f.text_field :homepage, :size => 60 %></p>
13 13
<p><%= f.check_box :is_public %></p>
14
<p><%= f.check_box :use_datetime_for_issues %></p>
14 15

  
15 16
<% unless @project.allowed_parents.compact.empty? %>
16 17
    <p><%= label(:project, :parent_id, l(:field_parent)) %><%= parent_project_select_tag(@project) %></p>
config/locales/cs.yml (working copy)
311 311
  field_assigned_to_role: Role přiřaditele
312 312
  field_text: Textové pole
313 313
  field_visible: Viditelný
314

  
314
  field_use_datetime_for_issues: Použít u tiketů také čas
315 315
  setting_app_title: Název aplikace
316 316
  setting_app_subtitle: Podtitulek aplikace
317 317
  setting_welcome_text: Uvítací text
config/locales/en-GB.yml (working copy)
311 311
  field_assigned_to_role: "Assignee's role"
312 312
  field_text: Text field
313 313
  field_visible: Visible
314
  field_use_datetime_for_issues: Use time in tickets too
314 315
  field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text"
315 316

  
316 317
  setting_app_title: Application title
config/locales/en.yml (working copy)
314 314
  field_assigned_to_role: "Assignee's role"
315 315
  field_text: Text field
316 316
  field_visible: Visible
317
  field_use_datetime_for_issues: Use time in tickets too
317 318
  field_warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text"
318 319
  field_issues_visibility: Issues visibility
319 320
  field_is_private: Private
config/locales/es.yml (working copy)
1110 1110
  button_hide: Ocultar
1111 1111
  setting_non_working_week_days: Días no laborables
1112 1112
  label_in_the_next_days: en los próximos
1113
  field_use_datetime_for_issues: Usar hora en prog peticiones
1113 1114
  label_in_the_past_days: en los anteriores
1114 1115
  label_attribute_of_user: "%{name} del usuario"
1115 1116
  text_turning_multiple_off: Si desactiva los valores múltiples, éstos serán eliminados para dejar un único valor por elemento.
db/migrate/20130531174459_add_time_to_issue_start_date_and_issue_due_date.rb (working copy)
1
class AddTimeToIssueStartDateAndIssueDueDate < ActiveRecord::Migration
2
  def self.up
3
    change_column :issues, :start_date, :datetime
4
    change_column :issues, :due_date, :datetime
5
  end
6
  
7
  def self.down
8
    change_column :issues, :start_date, :date
9
    change_column :issues, :due_date, :date
10
  end
11
end
db/migrate/20130531174549_add_use_datetime_for_issues_to_projects.rb (working copy)
1
class AddUseDatetimeForIssuesToProjects < ActiveRecord::Migration
2

  
3
  def self.up
4
    add_column :projects, :use_datetime_for_issues, :boolean, :default => false
5
  end
6

  
7
  def self.down
8
    remove_column :projects, :use_datetime_for_issues
9
  end
10

  
11
end
lib/redmine/helpers/calendar.rb (working copy)
48 48
      # Sets calendar events
49 49
      def events=(events)
50 50
        @events = events
51
        @ending_events_by_days = @events.group_by {|event| event.due_date}
52
        @starting_events_by_days = @events.group_by {|event| event.start_date}
51
        @ending_events_by_days = @events.group_by {|event| (event.due_date.is_a?(Date) || event.due_date.nil? ? event.due_date : event.due_date.to_date) }
52
        @starting_events_by_days = @events.group_by {|event| (event.start_date.is_a?(Date) || event.start_date.nil?  ? event.start_date : event.start_date.to_date) }
53 53
      end
54 54

  
55 55
      # Returns events for the given day
lib/redmine/helpers/gantt.rb (working copy)
628 628
      private
629 629

  
630 630
      def coordinates(start_date, end_date, progress, zoom=nil)
631
        start_date = start_date.to_date if not start_date.nil?
632
        end_date = end_date.to_date if not end_date.nil?
633

  
631 634
        zoom ||= @zoom
632 635
        coords = {}
633 636
        if start_date && end_date && start_date < self.date_to && end_date > self.date_from
......
672 675
      end
673 676

  
674 677
      def calc_progress_date(start_date, end_date, progress)
675
        start_date + (end_date - start_date + 1) * (progress / 100.0)
678
        start_date.to_date + (end_date.to_date - start_date.to_date + 1) * (progress / 100.0)
676 679
      end
677 680

  
678 681
      # TODO: Sorts a collection of issues by start_date, due_date, id for gantt rendering
lib/redmine/i18n.rb (working copy)
52 52
      ::I18n.t(str.to_s, :value => value, :locale => lang.to_s.gsub(%r{(.+)\-(.+)$}) { "#{$1}-#{$2.upcase}" })
53 53
    end
54 54

  
55
    def localise_date(date)
56
      return if date.nil?
57

  
58
      zone = User.current.time_zone
59
      local = zone ? date.in_time_zone(zone) : date
60
      return local
61
    end
62

  
55 63
    def format_date(date)
56 64
      return nil unless date
57 65
      options = {}
lib/redmine/utils.rb (working copy)
60 60
          weeks = days / 7
61 61
          result = weeks * (7 - non_working_week_days.size)
62 62
          days_left = days - weeks * 7
63
          start_cwday = from.cwday
63
          start_cwday = from.to_date.cwday
64 64
          days_left.times do |i|
65 65
            unless non_working_week_days.include?(((start_cwday + i - 1) % 7) + 1)
66 66
              result += 1
......
78 78
          weeks = working_days / (7 - non_working_week_days.size)
79 79
          result = weeks * 7
80 80
          days_left = working_days - weeks * (7 - non_working_week_days.size)
81
          cwday = date.cwday
81
          cwday = date.to_date.cwday
82 82
          while days_left > 0
83 83
            cwday += 1
84 84
            unless non_working_week_days.include?(((cwday - 1) % 7) + 1)
......
94 94

  
95 95
      # Returns the date of the first day on or after the given date that is a working day
96 96
      def next_working_date(date)
97
        cwday = date.cwday
97
        cwday = date.to_date.cwday
98 98
        days = 0
99 99
        while non_working_week_days.include?(((cwday + days - 1) % 7) + 1)
100 100
          days += 1
test/functional/issues_controller_test.rb (working copy)
1799 1799
      assert_response :success
1800 1800
      assert_template 'new'
1801 1801
      assert_select 'input[name=?]', 'issue[start_date]'
1802
      assert_select 'input[name=?][value]', 'issue[start_date]', 0
1802
      assert_select 'input[name=?][value]', 'issue[start_date]', 1
1803 1803
    end
1804 1804
  end
1805 1805

  
......
2018 2018
    assert_equal 2, issue.author_id
2019 2019
    assert_equal 3, issue.tracker_id
2020 2020
    assert_equal 2, issue.status_id
2021
    assert_equal Date.parse('2010-11-07'), issue.start_date
2021
    assert_equal DateTime.parse('2010-11-07'), issue.start_date
2022 2022
    assert_nil issue.estimated_hours
2023 2023
    v = issue.custom_values.where(:custom_field_id => 2).first
2024 2024
    assert_not_nil v
......
2085 2085
                           :id => Issue.last.id
2086 2086
      issue = Issue.find_by_subject('This is the test_new issue')
2087 2087
      assert_not_nil issue
2088
      assert_equal Date.today, issue.start_date
2088
      assert_equal Date.today, issue.start_date.to_date
2089 2089
    end
2090 2090
  end
2091 2091

  
......
2260 2260
    end
2261 2261

  
2262 2262
    issue = Issue.order('id DESC').first
2263
    assert_equal Date.parse('2012-07-14'), issue.start_date
2263
    assert_equal DateTime.parse('2012-07-14'), issue.start_date
2264 2264
    assert_nil issue.due_date
2265 2265
    assert_equal 'value1', issue.custom_field_value(cf1)
2266 2266
    assert_nil issue.custom_field_value(cf2)
......
3675 3675
      assert_equal 2, issue.project_id, "Project is incorrect"
3676 3676
      assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
3677 3677
      assert_equal 1, issue.status_id, "Status is incorrect"
3678
      assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
3679
      assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
3678
      assert_equal '2009-12-01 00:00:00 UTC', issue.start_date.to_s, "Start date is incorrect"
3679
      assert_equal '2009-12-31 00:00:00 UTC', issue.due_date.to_s, "Due date is incorrect"
3680 3680
    end
3681 3681
  end
3682 3682

  
(16-16/18)