class TheFox::Timr::Model::Task

Attributes

current_track[R]
description[R]
foreign_id[R]
has_flat_rate[R]
hourly_rate[R]

Public Class Methods

create_task_from_hash(options) click to toggle source

Create a new Task using a Hash.

Options:

  • :name (String)

  • :description (String)

  • :estimation (String|Integer|Duration)

  • :hourly_rate (Integer)

# 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) click to toggle source

Load a Task from a file into a Task instance.

# 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) click to toggle source

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

# 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
new() click to toggle source
Calls superclass method TheFox::Timr::Model::BasicModel.new
# 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

Public Instance Methods

add_track(track, set_as_current_track = false) click to toggle source

Add a Track.

# 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) click to toggle source

Uses tracks() with options to filter.

Options:

  • :from

# 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) click to toggle source

Options:

  • :format

# 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) click to toggle source

Alias for duration method.

Options:

  • :billed (Boolean)

# File lib/timr/model/task.rb, line 660
def billed_duration(options = Hash.new)
    duration(options.merge({:billed => true}))
end
consumed_budge() click to toggle source

Get the actual consumed budge.

# 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) click to toggle source

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

# 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
description=(description) click to toggle source

Set description.

# File lib/timr/model/task.rb, line 72
def description=(description)
    @description = description
    
    # Mark Task as changed.
    changed
end
duration(options = Hash.new) click to toggle source

Consumed duration.

Options:

  • :billed (Boolean)

# 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) click to toggle source

Uses tracks() with options to filter.

Options:

  • :to

# 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) click to toggle source

Options:

  • :format

# 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) click to toggle source

Are two Tasks equal?

Uses ID for comparision.

# 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_budge() click to toggle source

Calculate the budge based on estimation.

# 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
estimation() click to toggle source

Get estimation.

# File lib/timr/model/task.rb, line 469
def estimation
    @estimation
end
estimation=(estimation) click to toggle source

Set estimation.

Either using a Duration instance, Integer or a String like 2h 30m. Estimation is parsed by 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 for more examples.

# 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_s() click to toggle source

Get estimation as String.

# 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) click to toggle source

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

# 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
foreign_id=(foreign_id) click to toggle source
# File lib/timr/model/task.rb, line 37
def foreign_id=(foreign_id)
    @foreign_id = foreign_id
    
    # Mark Task as changed.
    changed
end
formatted(options = Hash.new) click to toggle source

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

# 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
has_flat_rate=(has_flat_rate) click to toggle source

Set has_flat_rate.

# File lib/timr/model/task.rb, line 495
def has_flat_rate=(has_flat_rate)
    @has_flat_rate = has_flat_rate
    
    # Mark Task as changed.
    changed
end
hourly_rate=(new_hourly_rate) click to toggle source

Set hourly_rate.

# File lib/timr/model/task.rb, line 483
def hourly_rate=(new_hourly_rate)
    if new_hourly_rate.nil?
        @hourly_rate = nil
    else
        @hourly_rate = new_hourly_rate.to_f
    end
    
    # Mark Task as changed.
    changed
end
id_foreign_or_short() click to toggle source

Get Foreign ID or Short ID.

# File lib/timr/model/task.rb, line 33
def id_foreign_or_short
    @foreign_id ? @foreign_id : short_id
end
inspect() click to toggle source
# File lib/timr/model/task.rb, line 986
def inspect
    "#<Task_#{short_id} tracks=#{@tracks.count}>"
end
is_billed=(is_billed) click to toggle source

Set is_billed.

# 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_budge() click to toggle source

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

# 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) click to toggle source

Move a Track to another Task.

# 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) click to toggle source

Get name.

# 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) click to toggle source

Set name.

# File lib/timr/model/task.rb, line 45
def name=(name)
    @name = name
    
    # Mark Task as changed.
    changed
end
name_s(max_length = nil) click to toggle source

Get name or --- if name is not set.

# 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) click to toggle source

Pauses a current running Track.

# 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_time() click to toggle source

Get the remaining Time of estimation.

Returns a Duration instance.

# File lib/timr/model/task.rb, line 676
def remaining_time
    if @estimation
        estimation - duration
    end
end
remaining_time_percent() click to toggle source

Get the remaining Time as percent.

# 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_s() click to toggle source

Get the remaining Time Percent as String.

# 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_s() click to toggle source

Get the remaining Time as Human String.

  • Like 2h 30m.

  • Or --- when @estimation is nil.

# 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) click to toggle source

Remove a Track.

# 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
reset() click to toggle source
# 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) click to toggle source

Start a new Track by given options.

Options:

  • :foreign_id (String)

  • :track_id (String)

  • :no_stop (Boolean)

# 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
status() click to toggle source

Get Task status as Status instance.

# 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) click to toggle source

Stops a current running Track.

# 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_array() click to toggle source

Used to print informations to STDOUT.

# 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_str() click to toggle source

Used to print informations to STDOUT.

# File lib/timr/model/task.rb, line 801
def to_compact_str
    to_compact_array.join("\n")
end
to_detailed_array(options = Hash.new) click to toggle source

Used to print informations to STDOUT.

Options:

  • :full_id (Boolean) Show full Task ID.

# 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_str() click to toggle source

Used to print informations to STDOUT.

# File lib/timr/model/task.rb, line 821
def to_detailed_str
    to_detailed_array.join("\n")
end
to_s() click to toggle source

To String

# File lib/timr/model/task.rb, line 776
def to_s
    "Task_#{short_id}"
end
to_track_array(options = Hash.new) click to toggle source

Use to print informations for Track.

Options:

  • :full_id (Boolean) Show full Task ID.

# 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) click to toggle source

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                                 +---+
# 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) click to toggle source

Alias for duration method.

Options:

  • :billed (Boolean)

# File lib/timr/model/task.rb, line 669
def unbilled_duration(options = Hash.new)
    duration(options.merge({:billed => false}))
end