Class: TheFox::Timr::Model::Task

Inherits:
BasicModel show all
Includes:
Error
Defined in:
lib/timr/model/task.rb

Instance Attribute Summary collapse

Attributes inherited from BasicModel

#file_path, #has_changed

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from BasicModel

#changed, create_path_by_id, #created=, #delete_file, find_file_by_id, get_id_from_path, #id, #id=, #load_from_file, #modified=, #post_save_to_file, #pre_load_from_file, #save_to_file, #short_id

Constructor Details

#initializeTask

Returns a new instance of Task.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/timr/model/task.rb', line 16

def initialize
  super()
  
  # Meta
  @foreign_id = nil # --id
  @name = nil
  @description = nil
  @current_track = nil
  @estimation = nil
  @hourly_rate = nil
  @has_flat_rate = false
  
  # Data
  @tracks = Hash.new
end

Instance Attribute Details

#current_trackObject (readonly)

Returns the value of attribute current_track.



12
13
14
# File 'lib/timr/model/task.rb', line 12

def current_track
  @current_track
end

#descriptionObject

Returns the value of attribute description.



11
12
13
# File 'lib/timr/model/task.rb', line 11

def description
  @description
end

#foreign_idObject

Returns the value of attribute foreign_id.



10
11
12
# File 'lib/timr/model/task.rb', line 10

def foreign_id
  @foreign_id
end

#has_flat_rateObject

Returns the value of attribute has_flat_rate.



14
15
16
# File 'lib/timr/model/task.rb', line 14

def has_flat_rate
  @has_flat_rate
end

#hourly_rateObject

Returns the value of attribute hourly_rate.



13
14
15
# File 'lib/timr/model/task.rb', line 13

def hourly_rate
  @hourly_rate
end

Class Method Details

.create_task_from_hash(options) ⇒ Object

Create a new Task using a Hash.

Options:

  • ‘:name` (String)

  • ‘:description` (String)

  • ‘:estimation` (String|Integer|Duration)

  • ‘:hourly_rate` (Integer)



1017
1018
1019
1020
1021
1022
1023
1024
1025
# File 'lib/timr/model/task.rb', line 1017

def create_task_from_hash(options)
  task = Task.new
  task.name = options.fetch(:name, nil)
  task.description = options.fetch(:description, nil)
  task.estimation = options.fetch(:estimation, nil)
  task.hourly_rate = options.fetch(:hourly_rate, nil)
  task.has_flat_rate = options.fetch(:has_flat_rate, false)
  task
end

.load_task_from_file(path) ⇒ Object

Load a Task from a file into a Task instance.



994
995
996
997
998
# File 'lib/timr/model/task.rb', line 994

def load_task_from_file(path)
  task = Task.new
  task.load_from_file(path)
  task
end

.load_task_from_file_with_id(base_path, task_id) ⇒ Object

Search a Task in a base path for a Track by ID. If found a file load it into a Task instance.



1002
1003
1004
1005
1006
1007
# File 'lib/timr/model/task.rb', line 1002

def load_task_from_file_with_id(base_path, task_id)
  task_file_path = BasicModel.find_file_by_id(base_path, task_id)
  if task_file_path
    load_task_from_file(task_file_path)
  end
end

Instance Method Details

#add_track(track, set_as_current_track = false) ⇒ Object

Add a Track.



80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/timr/model/task.rb', line 80

def add_track(track, set_as_current_track = false)
  track.task = self
  
  @tracks[track.id] = track
  
  if set_as_current_track
    @current_track = track
  end
  
  # Mark Task as changed.
  changed
end

#begin_datetime(options = Hash.new) ⇒ Object

Uses ‘tracks()` with `options` to filter.

Options:

  • ‘:from`



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/timr/model/task.rb', line 317

def begin_datetime(options = Hash.new)
  from_opt = options.fetch(:from, nil)
  
  # Cache
  # if @begin_datetime
  #  return @begin_datetime
  # end
  # Cannot use this cache because of :from :to range limitation.
  # It needs always to be direct from child Tracks, because the
  # cache does not know when the begin and end datetimes of the
  # child Tracks change.
  
  # Do not sort. We only need to sort the tracks
  # by begin_datetime and take the first.
  options[:sort] = false
  
  first_track = tracks(options)
    .select{ |track_id, track| track.begin_datetime } # filter nil
    .sort_by{ |track_id, track| track.begin_datetime }
    .to_h # sort_by makes [[]]
    .values # no keys to take the first
    .first
  
  if first_track
    bdt = first_track.begin_datetime(options)
  end
  
  if from_opt && bdt && from_opt > bdt
    bdt = from_opt
  end
  
  bdt
end

#begin_datetime_s(options = Hash.new) ⇒ Object

Options:

  • ‘:format`



354
355
356
357
358
359
360
361
362
363
# File 'lib/timr/model/task.rb', line 354

def begin_datetime_s(options = Hash.new)
  format_opt = options.fetch(:format, HUMAN_DATETIME_FOMRAT)
  
  bdt = begin_datetime(options)
  if bdt
    bdt.strftime(format_opt)
  else
    '---'
  end
end

#billed_duration(options = Hash.new) ⇒ Object

Alias for ‘duration` method.

Options:

  • ‘:billed` (Boolean)



660
661
662
# File 'lib/timr/model/task.rb', line 660

def billed_duration(options = Hash.new)
  duration(options.merge({:billed => true}))
end

#consumed_budgeObject

Get the actual consumed budge.



503
504
505
506
507
508
509
# File 'lib/timr/model/task.rb', line 503

def consumed_budge
  if @hourly_rate
    duration.to_i.to_f / 3600.0 * @hourly_rate
  else
    0.0
  end
end

#continue(options = Hash.new) ⇒ Object

Continues the current Track. Only if it isn’t already running.



608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
# File 'lib/timr/model/task.rb', line 608

def continue(options = Hash.new)
  track_opt = options.fetch(:track, nil)
  
  if @current_track
    if @current_track.stopped?
      
      # Duplicate and start.
      @current_track = @current_track.dup
      @current_track.start(options)
      
      
      add_track(@current_track)
    else
      raise TrackError, "Cannot continue Track #{@current_track.short_id}, is already running."
    end
  else
    unless track_opt
      raise TaskError, 'No Track given.'
    end
    
    # Duplicate and start.
    @current_track = track_opt.dup
    @current_track.start(options)
    
    add_track(@current_track)
  end
  
  @current_track
end

#duration(options = Hash.new) ⇒ Object

Consumed duration.

Options:

  • ‘:billed` (Boolean)



643
644
645
646
647
648
649
650
651
652
653
# File 'lib/timr/model/task.rb', line 643

def duration(options = Hash.new)
  billed_opt = options.fetch(:billed, nil)
  
  duration = Duration.new
  @tracks.each do |track_id, track|
    if billed_opt.nil? || (billed_opt && track.is_billed) || (!billed_opt && !track.is_billed)
      duration += track.duration(options)
    end
  end
  duration
end

#end_datetime(options = Hash.new) ⇒ Object

Uses ‘tracks()` with `options` to filter.

Options:

  • ‘:to`



370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/timr/model/task.rb', line 370

def end_datetime(options = Hash.new)
  to_opt = options.fetch(:to, nil)
  
  # Cache
  # if @end_datetime
  #  return @end_datetime
  # end
  # Cannot use this cache because of :from :to range limitation.
  # It needs always to be direct from child Tracks, because the
  # cache does not know when the begin and end datetimes of the
  # child Tracks change.
  
  # Do not sort. We only need to sort the tracks
  # by end_datetime and take the last.
  options[:sort] = false
  
  last_track = tracks(options)
    .select{ |track_id, track| track.end_datetime } # filter nil
    .sort_by{ |track_id, track| track.end_datetime }
    .to_h # sort_by makes [[]]
    .values # no keys to take the last
    .last
  
  if last_track
    edt = last_track.end_datetime(options)
  end
  
  if to_opt && edt && to_opt < edt
    edt = to_opt
  end
  
  edt
end

#end_datetime_s(options = Hash.new) ⇒ Object

Options:

  • ‘:format`



407
408
409
410
411
412
413
414
415
416
# File 'lib/timr/model/task.rb', line 407

def end_datetime_s(options = Hash.new)
  format_opt = options.fetch(:format, HUMAN_DATETIME_FOMRAT)
  
  edt = end_datetime(options)
  if edt
    edt.strftime(format_opt)
  else
    '---'
  end
end

#eql?(task) ⇒ Boolean

Are two Tasks equal?

Uses ID for comparision.

Returns:

  • (Boolean)


767
768
769
770
771
772
773
# File 'lib/timr/model/task.rb', line 767

def eql?(task)
  unless task.is_a?(Task)
    raise TaskError, "task variable must be a Task instance. #{task.class} given."
  end
  
  self.id == task.id
end

#estimated_budgeObject

Calculate the budge based on estimation.



512
513
514
515
516
517
518
# File 'lib/timr/model/task.rb', line 512

def estimated_budge
  if @hourly_rate
    estimation.to_i.to_f / 3600.0 * @hourly_rate
  else
    0.0
  end
end

#estimationObject

Get estimation.



469
470
471
# File 'lib/timr/model/task.rb', line 469

def estimation
  @estimation
end

#estimation=(estimation) ⇒ Object

Set estimation.

Either using a Duration instance, Integer or a String like ‘2h 30m`. Estimation is parsed by [chronic_duration](github.com/henrypoydar/chronic_duration).

Examples:

  • ‘-e 2:10:5`

    Sets Estimation to 2h 10m 5s.

  • ‘-e ’2h 10m 5s’‘

    Sets Estimation to 2h 10m 5s.

Use ‘+` or `-` to calculate with Estimation Times:

  • ‘-e ’-45m’‘

    Subtracts 45 minutes from the original Estimation.

  • ‘-e ’+1h 30m’‘

    Adds 1 hour 30 minutes to the original Estimation.

See [chronic_duration](github.com/henrypoydar/chronic_duration) for more examples.



439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
# File 'lib/timr/model/task.rb', line 439

def estimation=(estimation)
  case estimation
  when String
    # Cannot use estimation.strip! because frozen.
    estimation = estimation.strip
    
    if estimation[0] == '+'
      estimation = estimation[1..-1]
      @estimation += Duration.parse(estimation)
    elsif estimation[0] == '-'
      estimation = estimation[1..-1]
      @estimation -= Duration.parse(estimation)
    else
      @estimation = Duration.parse(estimation)
    end
  when Integer
    @estimation = Duration.new(estimation)
  when Duration
    @estimation = estimation
  when nil
    @estimation = estimation
  else
    raise TaskError, "estimation needs to be an instance of String, Integer, Duration or nil, #{estimation.class} given."
  end
  
  # Mark Task as changed.
  changed
end

#estimation_sObject

Get estimation as String.



474
475
476
477
478
479
480
# File 'lib/timr/model/task.rb', line 474

def estimation_s
  if @estimation
    @estimation.to_human_s
  else
    '---'
  end
end

#find_track_by_id(track_id) ⇒ Object

Find a Track by ID even if the ID is not 40 characters long. When the ID is 40 characters long ‘@tracks` is faster. ;)



739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
# File 'lib/timr/model/task.rb', line 739

def find_track_by_id(track_id)
  track_id_len = track_id.length
  
  # puts "search track id '#{track_id}'"
  
  if track_id_len < 40
    found_track_id = nil
    @tracks.keys.each do |key|
      if track_id == key[0, track_id_len]
        if found_track_id
          raise TrackError, "Track ID '#{track_id}' is not a unique identifier."
        else
          found_track_id = key
          
          # Do not break the loop here.
          # Iterate all keys to make sure the ID is unique.
        end
      end
    end
    track_id = found_track_id
  end
  
  @tracks[track_id]
end

#formatted(options = Hash.new) ⇒ Object

Return formatted String.

Options:

  • ‘:format`

  • ‘:prefix` - Default: `%`

Format:

  • ‘%id` - ID

  • ‘%sid` - Short ID

  • ‘%fid` - Foreign ID

  • ‘%n` - Name

  • ‘%d` - Description

  • ‘%ds` - Duration Seconds

  • ‘%dh` - Duration Human Format



962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
# File 'lib/timr/model/task.rb', line 962

def formatted(options = Hash.new)
  format = options.fetch(:format, '')
  prefix = options.fetch(:prefix, '%')
  
  formatted_s = format
    .gsub("#{prefix}id", self.id)
    .gsub("#{prefix}sid", self.short_id ? self.short_id : '')
    .gsub("#{prefix}fid", self.foreign_id ? self.foreign_id : '')
    .gsub("#{prefix}n", self.name ? self.name : '')
    .gsub("#{prefix}ds", self.duration(options).to_s)
  
  duration_human = self.duration(options).to_human
  if duration_human
    formatted_s.gsub!('%dh', duration_human)
  else
    formatted_s.gsub!('%dh', '')
  end
  
  # Must not before %dh and %ds.
  formatted_s.gsub!("#{prefix}d", self.description ? self.description : '')
  
  formatted_s
end

#id_foreign_or_shortObject

Get Foreign ID or Short ID.



33
34
35
# File 'lib/timr/model/task.rb', line 33

def id_foreign_or_short
  @foreign_id ? @foreign_id : short_id
end

#inspectObject



986
987
988
# File 'lib/timr/model/task.rb', line 986

def inspect
  "#<Task_#{short_id} tracks=#{@tracks.count}>"
end

#is_billed=(is_billed) ⇒ Object

Set is_billed.



731
732
733
734
735
# File 'lib/timr/model/task.rb', line 731

def is_billed=(is_billed)
  @tracks.each do |track_id, track|
    track.is_billed = is_billed
  end
end

#loss_budgeObject

Calculates the budge loss when a Flat Rate is used and the consumed duration is greater than the estimation.



521
522
523
524
525
526
527
528
529
530
531
# File 'lib/timr/model/task.rb', line 521

def loss_budge
  if @has_flat_rate && @hourly_rate
    if duration > estimation
      (duration - estimation).to_i.to_f / 3600.0 * @hourly_rate
    else
      0.0
    end
  else
    0.0
  end
end

#move_track(track, target_task) ⇒ Object

Move a Track to another Task.



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/timr/model/task.rb', line 107

def move_track(track, target_task)
  if eql?(target_task)
    return false
  end
  
  unless remove_track(track)
    return false
  end
  
  set_as_current_track = false
  if @current_track && @current_track.eql?(track)
    @current_track = nil
    set_as_current_track = true
  end
  
  target_task.add_track(track, set_as_current_track)
  
  true
end

#name(max_length = nil) ⇒ Object

Get name.



53
54
55
56
57
58
59
# File 'lib/timr/model/task.rb', line 53

def name(max_length = nil)
  name = @name
  if name && max_length && name.length > max_length + 2
    name = name[0, max_length] << '...'
  end
  name
end

#name=(name) ⇒ Object

Set name.



45
46
47
48
49
50
# File 'lib/timr/model/task.rb', line 45

def name=(name)
  @name = name
  
  # Mark Task as changed.
  changed
end

#name_s(max_length = nil) ⇒ Object

Get name or ‘—` if name is not set.



62
63
64
65
66
67
68
69
# File 'lib/timr/model/task.rb', line 62

def name_s(max_length = nil)
  s = name(max_length)
  if s.nil?
    '---'
  else
    s
  end
end

#pause(options = Hash.new) ⇒ Object

Pauses a current running Track.



595
596
597
598
599
600
601
602
603
604
# File 'lib/timr/model/task.rb', line 595

def pause(options = Hash.new)
  if @current_track
    @current_track.stop(options)
    
    # Mark Task as changed.
    changed
    
    @current_track
  end
end

#remaining_timeObject

Get the remaining Time of estimation.

Returns a Duration instance.



676
677
678
679
680
# File 'lib/timr/model/task.rb', line 676

def remaining_time
  if @estimation
    estimation - duration
  end
end

#remaining_time_percentObject

Get the remaining Time as percent.



696
697
698
699
700
701
# File 'lib/timr/model/task.rb', line 696

def remaining_time_percent
  rmt = remaining_time
  if rmt && @estimation
    (rmt.to_i.to_f / @estimation.to_i.to_f) * 100.0
  end
end

#remaining_time_percent_sObject

Get the remaining Time Percent as String.



704
705
706
707
708
709
710
711
# File 'lib/timr/model/task.rb', line 704

def remaining_time_percent_s
  rmtp = remaining_time_percent
  if rmtp
    '%.1f %%' % [rmtp]
  else
    '---'
  end
end

#remaining_time_sObject

Get the remaining Time as Human String.

  • Like ‘2h 30m`.

  • Or ‘—` when `@estimation` is `nil`.



686
687
688
689
690
691
692
693
# File 'lib/timr/model/task.rb', line 686

def remaining_time_s
  rmt = remaining_time
  if rmt
    rmt.to_human_s
  else
    '---'
  end
end

#remove_track(track) ⇒ Object

Remove a Track.



94
95
96
97
98
99
100
101
102
103
104
# File 'lib/timr/model/task.rb', line 94

def remove_track(track)
  track.task = nil
  
  if @tracks.delete(track.id)
    # Mark Task as changed.
    changed
  else
    # Track is not assiged to this Task.
    false
  end
end

#resetObject



127
128
129
130
131
132
133
134
# File 'lib/timr/model/task.rb', line 127

def reset
  if @current_track
    @current_track = nil
    
    # Mark Task as changed.
    changed
  end
end

#start(options = Hash.new) ⇒ Object

Start a new Track by given ‘options`.

Options:

  • ‘:foreign_id` (String)

  • ‘:track_id` (String)

  • ‘:no_stop` (Boolean)



540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
# File 'lib/timr/model/task.rb', line 540

def start(options = Hash.new)
  foreign_id_opt = options.fetch(:foreign_id, nil)
  track_id_opt = options.fetch(:track_id, nil)
  
  # Used by Push.
  no_stop_opt = options.fetch(:no_stop, false)
  
  unless no_stop_opt
    # End current Track before starting a new one.
    # Leave options empty here for stop().
    stop
  end
  
  if foreign_id_opt && @foreign_id.nil?
    @foreign_id = foreign_id_opt
  end
  
  if track_id_opt
    found_track = find_track_by_id(track_id_opt)
    if found_track
      
      @current_track = found_track.dup
    else
      raise TrackError, "No Track found for Track ID '#{track_id_opt}'."
    end
  else
    @current_track = Track.new
    @current_track.task = self
  end
  
  @tracks[@current_track.id] = @current_track
  
  # Mark Task as changed.
  changed
  
  @current_track.start(options)
  @current_track
end

#statusObject

Get Task status as Status instance.



714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
# File 'lib/timr/model/task.rb', line 714

def status
  stati = @tracks.map{ |track_id, track| track.status.short_status }.to_set
  
  if @tracks.count == 0
    status = ?-
  elsif stati.include?(?R)
    status = ?R
  elsif stati.include?(?S)
    status = ?S
  else
    status = ?U
  end
  
  Status.new(status)
end

#stop(options = Hash.new) ⇒ Object

Stops a current running Track.



580
581
582
583
584
585
586
587
588
589
590
591
592
# File 'lib/timr/model/task.rb', line 580

def stop(options = Hash.new)
  if @current_track
    @current_track.stop(options)
    
    # Reset current Track variable.
    @current_track = nil
    
    # Mark Task as changed.
    changed
  end
  
  nil
end

#to_compact_arrayObject

Used to print informations to STDOUT.



806
807
808
809
810
811
812
813
814
815
816
817
818
# File 'lib/timr/model/task.rb', line 806

def to_compact_array
  full_id = self.foreign_id ? self.foreign_id : self.short_id
  
  to_ax = Array.new
  to_ax << 'Task: %s %s' % [full_id, self.name]
  if self.description
    to_ax << 'Description: %s' % [self.description]
  end
  if self.estimation
    to_ax << 'Estimation: %s' % [self.estimation.to_human_s]
  end
  to_ax
end

#to_compact_strObject

Used to print informations to STDOUT.



801
802
803
# File 'lib/timr/model/task.rb', line 801

def to_compact_str
  to_compact_array.join("\n")
end

#to_detailed_array(options = Hash.new) ⇒ Object

Used to print informations to STDOUT.

Options:

  • ‘:full_id` (Boolean) Show full Task ID.



830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
# File 'lib/timr/model/task.rb', line 830

def to_detailed_array(options = Hash.new)
  full_id_opt = options.fetch(:full_id, false)
  
  full_id = full_id_opt ? self.id : self.short_id
  
  to_ax = Array.new
  
  to_ax << 'Task: %s' % [full_id]
  
  if self.foreign_id
    to_ax << 'Foreign ID: %s' % [self.foreign_id]
  end
  
  to_ax << 'Name: %s' % [self.name]
  
  if self.description
    to_ax << 'Description: %s' % [self.description]
  end
  
  # Duration
  duration_human = self.duration.to_human_s
  to_ax << 'Duration: %s' % [duration_human]
  
  duration_man_days = self.duration.to_man_days
  if duration_human != duration_man_days
    to_ax << 'Man Unit: %s' % [duration_man_days]
  end
  
  # Billed Duration
  billed_duration_human = self.billed_duration.to_human
  to_ax << 'Billed   Duration: %s' % [billed_duration_human]
  
  # Unbilled Duration
  unbilled_duration_human = self.unbilled_duration.to_human
  to_ax << 'Unbilled Duration: %s' % [unbilled_duration_human]
  
  if self.estimation
    to_ax << 'Estimation:     %s' % [self.estimation.to_human]
    
    to_ax << 'Time Remaining: %s (%s)' % [self.remaining_time_s, self.remaining_time_percent_s]
    
    bar_options = {
      :total => self.estimation.to_i,
      :progress => self.duration.to_i,
      :length => 50,
      :progress_mark => '#',
      :remainder_mark => '-',
    }
    bar = ProgressBar.new(bar_options)
    
    to_ax << '                |%s|' % [bar.render]
  end
  
  if self.hourly_rate
    to_ax << 'Hourly Rate:     %.2f' % [self.hourly_rate]
    to_ax << 'Flat Rate:       %s' % [@has_flat_rate ? 'Yes' : 'No']
    
    to_ax << 'Consumed Budge:  %.2f' % [self.consumed_budge]
    
    if self.estimation
      to_ax << 'Estimated Budge: %.2f' % [self.estimated_budge]
    end
    
    if @has_flat_rate
      to_ax << 'Loss Budge:      %.2f' % [self.loss_budge]
    end
  end
  
  tracks = self.tracks
  first_track = tracks
    .select{ |track_id, track| track.begin_datetime }
    .sort_by{ |track_id, track| track.begin_datetime }
    .to_h
    .values
    .first
  if first_track
    to_ax << 'Begin Track: %s  %s' % [first_track.short_id, first_track.begin_datetime_s]
  end
  
  last_track = tracks
    .select{ |track_id, track| track.end_datetime }
    .sort_by{ |track_id, track| track.end_datetime }
    .to_h
    .values
    .last
  if last_track
    to_ax << 'End   Track: %s  %s' % [last_track.short_id, last_track.end_datetime_s]
  end
  
  status = self.status.colorized
  to_ax << 'Status: %s' % [status]
  
  if @current_track
    to_ax << 'Current Track: %s %s' % [@current_track.short_id, @current_track.title]
  end
  
  tracks_count = tracks.count
  to_ax << 'Tracks:          %d' % [tracks_count]
  
  billed_tracks_count = tracks({:billed => true}).count
  to_ax << 'Billed Tracks:   %d' % [billed_tracks_count]
  
  unbilled_tracks_count = tracks({:billed => false}).count
  to_ax << 'Unbilled Tracks: %d' % [unbilled_tracks_count]
  
  if tracks_count > 0 && @tracks_opt # --tracks
    to_ax << 'Track IDs: %s' % [tracks.map{ |track_id, track| track.short_id }.join(' ')]
  end
  
  if self.file_path
    to_ax << 'File path: %s' % [self.file_path]
  end
  
  to_ax
end

#to_detailed_strObject

Used to print informations to STDOUT.



821
822
823
# File 'lib/timr/model/task.rb', line 821

def to_detailed_str
  to_detailed_array.join("\n")
end

#to_sObject

To String



776
777
778
# File 'lib/timr/model/task.rb', line 776

def to_s
  "Task_#{short_id}"
end

#to_track_array(options = Hash.new) ⇒ Object

Use to print informations for Track.

Options:

  • ‘:full_id` (Boolean) Show full Task ID.



785
786
787
788
789
790
791
792
793
794
795
796
797
798
# File 'lib/timr/model/task.rb', line 785

def to_track_array(options = Hash.new)
  full_id_opt = options.fetch(:full_id, false) # @TODO full_id unit test
  
  full_id = full_id_opt ? self.id : ( self.foreign_id ? self.foreign_id : self.short_id )
  
  name_a = ['Task:', full_id]
  if self.name
    name_a << self.name
  end
  
  to_ax = Array.new
  to_ax << name_a.join(' ')
  to_ax
end

#tracks(options = Hash.new) ⇒ Object

Select Track by Time Range and/or Status.

Options:

  • ‘:from`, `:to` limit the begin and end datetimes to a specific range.

  • ‘:status` filter Tracks by Short Status.

  • ‘:billed` filter Tracks by is_billed flag.

- ‘true` filter billed Tracks. - `false` filter unbilled Tracks. - `nil` filter off.

Fixed Start and End (‘from != nil && to != nil`)

“‘ Selected Range |———-| Track A ----------------- Track B ------ Track C ------------ Track D ---- Track E --- Track F --- “`

  • Track A is bigger then the Options range. Take it.

  • Track B ends in the Options range. Take it.

  • Track C starts in the Options range. Take it.

  • Track D starts and ends within the Options range. Definitely take this.

  • Track E is out-of-score. Ignore it.

  • Track F is out-of-score. Ignore it.


Open End (‘to == nil`)

Take all except Track E.

“‘ Selected Range |———-> Track A ----------------- Track B ------ Track C ------------ Track D ---- Track E --- Track F --- “`


Open Start (‘from == nil`)

Take all except Track F.

“‘ Selected Range <———-| Track A ----------------- Track B ------ Track C ------------ Track D ---- Track E --- Track F --- “`



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/timr/model/task.rb', line 195

def tracks(options = Hash.new)
  from_opt = options.fetch(:from, nil)
  to_opt = options.fetch(:to, nil)
  status_opt = options.fetch(:status, nil)
  sort_opt = options.fetch(:sort, true)
  billed_opt = options.fetch(:billed, nil)
  
  if status_opt
    case status_opt
    when String
      status_opt = [status_opt]
    when Array
      # OK
    else
      raise TaskError, ":status needs to be an instance of String or Array, #{status_opt.class} given."
    end
  end
  
  if from_opt && to_opt && from_opt > to_opt
    raise TaskError, 'From cannot be bigger than To.'
  end
  
  filtered_tracks = Hash.new
  if from_opt.nil? && to_opt.nil?
    # Take all Tracks.
    filtered_tracks = @tracks.select{ |track_id, track|
      # Filter Tracks with no Begin DateTime.
      # This can happen when 'timr track add' without any DateTime.
      !track.begin_datetime.nil?
    }
  elsif !from_opt.nil? && to_opt.nil?
    # Open End (to_opt == nil)
    filtered_tracks = @tracks.select{ |track_id, track|
      bdt = track.begin_datetime
      edt = track.end_datetime || Time.now
      
      bdt && ( \
        # Track A, B
        bdt <  from_opt && edt >  from_opt || \
        
        # Track C, D, F
        bdt >= from_opt && edt >= from_opt
      )
    }
  elsif from_opt.nil? && !to_opt.nil?
    # Open Start (from_opt == nil)
    filtered_tracks = @tracks.select{ |track_id, track|
      bdt = track.begin_datetime
      edt = track.end_datetime || Time.now
      
      bdt && ( \
        # Track B, D, E
        bdt <  to_opt && edt <= to_opt || \
        
        # Track A, C
        bdt <  to_opt && edt >  to_opt
      )
    }
  elsif !from_opt.nil? && !to_opt.nil?
    # Fixed Start and End (from_opt != nil && to_opt != nil)
    filtered_tracks = @tracks.select{ |track_id, track|
      bdt = track.begin_datetime
      edt = track.end_datetime || Time.now
      
      bdt && ( \
        # Track D
        bdt >= from_opt && edt <= to_opt || \
        
        # Track A
        bdt <  from_opt && edt >  to_opt || \
        
        # Track B
        bdt <  from_opt && edt <= to_opt && edt > from_opt || \
        
        # Track C
        bdt >= from_opt && edt >  to_opt && bdt < to_opt
      )
    }
  else
    raise ThisShouldNeverHappenError, 'Should never happen, bug shit happens.'
  end
  
  if status_opt
    filtered_tracks.select!{ |track_id, track|
      status_opt.include?(track.status.short_status)
    }
  end
  
  unless billed_opt.nil?
    if billed_opt
      filtered_tracks.select!{ |track_id, track|
        track.is_billed
      }
    else
      filtered_tracks.select!{ |track_id, track|
        !track.is_billed
      }
    end
  end
  
  if sort_opt
    filtered_tracks.sort{ |t1, t2|
      t1 = t1.last
      t2 = t2.last
      
      cmp1 = t1.begin_datetime <=> t2.begin_datetime
      if cmp1.nil? || cmp1 == 0
        t1.end_datetime <=> t2.end_datetime
      else
        cmp1
      end
    }.to_h
  else
    filtered_tracks
  end
end

#unbilled_duration(options = Hash.new) ⇒ Object

Alias for ‘duration` method.

Options:

  • ‘:billed` (Boolean)



669
670
671
# File 'lib/timr/model/task.rb', line 669

def unbilled_duration(options = Hash.new)
  duration(options.merge({:billed => false}))
end