class TrackRecordReport::Report

Class which manages a report.

Constants

FREQUENCY

Configure the handlers and human-readable labels for the ways in which reports get broken up, in terms of frequency. View code which presents a choice of report frequency should obtain the labels for the list with the ‘label’ method. Use the ‘#column_title’ method for a column “title”, shown alongside or above column headings. Use the ‘#column_heading’ method for per-column headings.

THESE MUST STAY IN THE SAME ORDER! If you add new entries, you must

add them at the end of the list.

Saved reports specify the reporting frequency by reference to this constant and an index into its array of entries. If you produce a new TrackRecord version that changes the meaning of any array index rather than merely adding new entries, you will have to include a migration that maps old indices to new for existing saved report records.

Attributes

active_task_ids[R]
column_count[R]

Number of columns after all calculations are complete; this is the same as the size of the ‘#column_ranges’ or ‘#column_totals’ arrays below, but using this explicit property is likely to make code more legible.

column_ranges[R]

Array of ranges, one per column, giving the range for each of the columns held within the rows. The indices into this array match indices into the rows’ cell arrays.

column_totals[R]

Array of ReportColumnTotal objects, one per column, giving the total hours for that column. The indices into this array match indices into the rows’ cell arrays.

customer_sort_field[RW]

Sort fields for customers, projects and tasks; grouping options.

filtered_tasks[RW]
filtered_users[RW]
frequency[R]

A row from the FREQUENCY constant and the current index into that array.

frequency_data[R]

A row from the FREQUENCY constant and the current index into that array.

inactive_task_ids[R]
project_sort_field[RW]

Sort fields for customers, projects and tasks; grouping options.

range[RW]

Complete date range for the whole report; array of user IDs used for per-user breakdowns; array of task IDs the report will represent.

range_end[R]

Range data for the ‘new’ view form. Custom attribute writer methods are used to call “rationalise_dates()” whenever a range value is altered.

range_month_end[R]
range_month_start[R]
range_start[R]

Range data for the ‘new’ view form. Custom attribute writer methods are used to call “rationalise_dates()” whenever a range value is altered.

range_week_end[R]
range_week_start[R]
reportable_user_ids[R]
rows[R]

Array of ReportRows making up the report. The row objects contain arrays of cells, corresponding to columns of the report.

sections[R]

Array of ReportSection objects describing per-section totals of various kinds. See the ReportSection class and TrackRecordSections module for details.

task_filter[RW]

Handle all (“all”), only billable (“billable”) or only non-billable (“non_billable”) tasks?

task_grouping[RW]
task_ids[R]
task_sort_field[RW]

Sort fields for customers, projects and tasks; grouping options.

tasks[R]

Read-only array of actual user and task objects based on the IDs. Not all users or tasks may be included, depending on security settings.

total_actual_remaining[R]

Total duration of all tasks in all rows; number of hours remaining (may be negative for overrun) after all hours worked in tasks with non-zero duration. If ‘nil’, all tasks had zero duration. The ‘actual’ value only accounts for committed hours, while the ‘potential’ value includes both committed and not-committed hours (thus, subject to change).

total_duration[R]

Total duration of all tasks in all rows; number of hours remaining (may be negative for overrun) after all hours worked in tasks with non-zero duration. If ‘nil’, all tasks had zero duration. The ‘actual’ value only accounts for committed hours, while the ‘potential’ value includes both committed and not-committed hours (thus, subject to change).

total_potential_remaining[R]

Total duration of all tasks in all rows; number of hours remaining (may be negative for overrun) after all hours worked in tasks with non-zero duration. If ‘nil’, all tasks had zero duration. The ‘actual’ value only accounts for committed hours, while the ‘potential’ value includes both committed and not-committed hours (thus, subject to change).

user_column_totals[R]

Array of ReportUserColumnTotal objects, each index corresponding to a user the “users” array at the same index. These give the total work done by that user across all rows.

users[R]

Read-only array of actual user and task objects based on the IDs. Not all users or tasks may be included, depending on security settings.

Public Class Methods

label( frequency ) click to toggle source

Class method equivalent of “label” above. Returns the label for the given frequency, which must be a valid index into Report::FREQUENCY. See also “labels” below.

# File lib/track_record_report.rb, line 357
def self.label( frequency )
  return Report::FREQUENCY[ frequency ][ :label ]
end
labels() click to toggle source

Class method which returns an array of labels for various report frequencies. The index into the array indicates the frequency index.

# File lib/track_record_report.rb, line 364
def self.labels
  return Report::FREQUENCY.map { | f | f[ :label ] }
end
new( current_user, params = nil ) click to toggle source

Create a new Report. In the first parameter, pass the current TrackRecord user. In the next parameter pass nothing to use default values for a ‘new report’ view form, or pass “params[ :report ]” (or similar) to create using a params hash from a ‘new report’ form submission.

# File lib/track_record_report.rb, line 190
def initialize( current_user, params = nil )
  @current_user = current_user

  @range_start           = nil
  @range_end             = nil
  @range_week_start      = nil
  @range_week_end        = nil
  @range_month_start     = nil
  @range_month_end       = nil
  @frequency             = 0

  @customer_sort_field   = 'title'
  @project_sort_field    = 'title'
  @task_sort_field       = Task::DEFAULT_SORT_COLUMN
  @task_grouping         = :default
  @task_filter           = 'all'

  @include_totals        = true
  @include_committed     = false
  @include_not_committed = false
  @exclude_zero_rows     = false
  @exclude_zero_cols     = false # Totals only - ignores zero com/non-com columns in CSV exports for total/com/non-com column groups with non-zero totals

  @tasks                 = []
  @filtered_tasks        = []
  @task_ids              = []
  @active_task_ids       = []
  @inactive_task_ids     = []

  @users                 = []
  @filtered_users        = []
  @reportable_user_ids   = []

  unless ( params.nil? )

    # Adapted from ActiveRecord::Base "attributes=", Rails 2.1.0
    # on 29-Jun-2008.

    attributes = params.dup
    attributes.stringify_keys!
    attributes.each do | key, value |
      if ( key.include?( '(' ) )
        raise( "Multi-parameter attributes are not supported." )
      else
        begin
          send( key + "=", value )
        rescue
          # Ignore errors
        end
      end
    end
  end
end

Public Instance Methods

active_task_ids=( ids ) click to toggle source

Build the ‘tasks’ array if ‘#active_task_ids’ is updated externally. The result will be the sum of existing inactive and updated active task IDs.

# File lib/track_record_report.rb, line 276
def active_task_ids=( ids )
  @provided_active_task_ids = map_raw_ids( ids )
  assign_actual_tasks_from_provided_ids()
end
active_tasks() click to toggle source

Virtual accessor for the active task list, which just filters the master task list and returns the result (less RAM than keeping dual lists and not speed-critical as not used that often in practice).

# File lib/track_record_report.rb, line 248
def active_tasks
  @tasks.select { | task | task.active }
end
column_heading( col_index ) click to toggle source

Helper method which returns a user-displayable column heading appropriate for the report type. Pass the column index.

# File lib/track_record_report.rb, line 379
def column_heading( col_index )
  col_range = @column_ranges[ col_index ]
  return send( @frequency_data[ :column ], col_range )
end
column_title() click to toggle source

Helper method which returns a user-displayable column title to be shown once, next to or near per-column headings (see “#column_heading”), appropriate for the report type.

# File lib/track_record_report.rb, line 372
def column_title
  return @frequency_data[ :title ]
end
compile() click to toggle source

Compile the report.

# File lib/track_record_report.rb, line 327
def compile
  rationalise_dates()
  apply_filters()
  sort_and_group()

  return if ( @filtered_tasks.empty? )

  add_rows()
  add_columns()
  calculate!()
end
display_range() click to toggle source

Helper method which returns a user-displayable range describing the total date range for this report.

# File lib/track_record_report.rb, line 349
def display_range
  return heading_total( @range )
end
frequency=( freq ) click to toggle source

Set the ‘#frequency_data’ field when ‘frequency’ is updated externally.

# File lib/track_record_report.rb, line 310
def frequency=( freq )
  @frequency      = freq.to_i
  @frequency_data = FREQUENCY[ @frequency ]
end
inactive_task_ids=( ids ) click to toggle source

Build the ‘tasks’ array if ‘#inactive_task_ids’ is updated externally. The result will be the sum of existing active and updated inactive task IDs.

# File lib/track_record_report.rb, line 284
def inactive_task_ids=( ids )
  @provided_inactive_task_ids = map_raw_ids( ids )
  assign_actual_tasks_from_provided_ids()
end
inactive_tasks() click to toggle source

As above, but for inactive tasks.

# File lib/track_record_report.rb, line 254
def inactive_tasks
  @tasks.select { | task | ! task.active }
end
label() click to toggle source

Helper method which returns a user-displayable label describing this report type. There’s a class method equivalent below.

# File lib/track_record_report.rb, line 342
def label
  return @frequency_data[ :label ]
end
partial_column?( col_index ) click to toggle source

Does the column at the given index only contain partial results, because it is the first or last column in the overall range and that range starts or ends somewhere in the middle? Returns ‘true’ if so, else ‘false’.

# File lib/track_record_report.rb, line 388
    def partial_column?( col_index )

# [TODO] Doesn't work, because col_range accurately reflects the column range
#        rather than the quantised range. Getting at the latter is tricky, so
#        leaving this for later. At present the method is only used for display
#        purposes in the column headings.

      col_range = @column_ranges[ col_index ]
      return ( col_range.min < @range.min or col_range.max > @range.max )
    end
range_end=( value ) click to toggle source
# File lib/track_record_report.rb, line 319
def range_end=( value );         @range_end         = value; rationalise_dates(); end
range_month_end=( value ) click to toggle source
# File lib/track_record_report.rb, line 323
def range_month_end=( value );   @range_month_end   = value; rationalise_dates(); end
range_month_start=( value ) click to toggle source
# File lib/track_record_report.rb, line 322
def range_month_start=( value ); @range_month_start = value; rationalise_dates(); end
range_start=( value ) click to toggle source

Rationalise overall date ranges whenever a related field is updated externally.

# File lib/track_record_report.rb, line 318
def range_start=( value );       @range_start       = value; rationalise_dates(); end
range_week_end=( value ) click to toggle source
# File lib/track_record_report.rb, line 321
def range_week_end=( value );    @range_week_end    = value; rationalise_dates(); end
range_week_start=( value ) click to toggle source
# File lib/track_record_report.rb, line 320
def range_week_start=( value );  @range_week_start  = value; rationalise_dates(); end
reportable_user_ids=( ids ) click to toggle source

Build the ‘user’ array if ‘#reportable_user_ids’ is updated externally.

# File lib/track_record_report.rb, line 291
def reportable_user_ids=( ids )
  ids ||= []
  ids = ids.values if ( ids.is_a?( Hash ) or ids.is_a?( HashWithIndifferentAccess ) )
  @reportable_user_ids = ids.map { | str | str.to_i }

  # Security - if the current user is restricted they might try and hack
  # the form to view other user details.

  if ( @current_user.restricted? )
    @reportable_user_ids = [ @current_user.id ] unless( @reportable_user_ids.empty? )
  end

  # Turn the list of (now numeric) user IDs into user objects.

  @users = User.where( :id => @reportable_user_ids )
end
task_ids=( ids ) click to toggle source

Build the ‘tasks’ array if ‘#task_ids’ is updated externally.

# File lib/track_record_report.rb, line 268
def task_ids=( ids )
  @provided_task_ids = map_raw_ids( ids )
  assign_actual_tasks_from_provided_ids()
end
tasks=( array ) click to toggle source

Set a task array directly (will always be filtered according to security settings for the current user).

# File lib/track_record_report.rb, line 261
def tasks=( array )
  @tasks = array
  update_internal_task_lists()
end