require 'digest/md5' require 'date' module ReportingHelper # ======================= SHARED CODE START # include QueriesHelper include ApplicationHelper # Extends the definition in ApplicationHelper, to allow passing Dates as well # This is the definitions that is hit by the views and widgets def l(*values) return values.first if values.size == 1 and values.first.respond_to? :to_str if [Date, DateTime, Time].include? values.first.class ::I18n.l values.first else ApplicationHelper.l(*values) end end # ## # # For a given CostQuery::Filter filter, return an array of hashes, that contain # # the partials that should be rendered (:name) for that filter and necessary # # parameters. # # @param [CostQuery::Filter] the filter we want to render # def html_elements(filter) # return text_elements filter if CostQuery::Operator.string_operators.all? { |o| filter.available_operators.include? o } # return text_elements filter if CostQuery::Operator.integer_operators.all? { |o| filter.available_operators.include? o } #FIXME: have special filters designed for integer-operators, to give feedback if the user doesn't enter an int etc. # return date_elements filter if CostQuery::Operator.time_operators.all? { |o| filter.available_operators.include? o } # return heavy_object_elements filter if filter.heavy? # object_elements filter # end # def with_project(project) # project = Project.find(project) unless project.is_a? Project # project_was, @project = @project, project # yield # @project = project_was # end # def object_elements(filter) # [ # {:name => :activate_filter, :filter_name => filter.underscore_name, :label => l(filter.label)}, # {:name => :operators, :filter_name => filter.underscore_name, :operators => filter.available_operators}, # {:name => :multi_values, :filter_name => filter.underscore_name}, # {:name => :remove_filter, :filter_name => filter.underscore_name}] # end # def heavy_object_elements(filter) # [ # {:name => :activate_filter, :filter_name => filter.underscore_name, :label => l(filter.label)}, # {:name => :text, :text => l(:label_equals)}, # {:name => :heavy_values, :filter_name => filter.underscore_name, :disable_controls => true}, # {:name => :remove_filter, :filter_name => filter.underscore_name}] # end # def date_elements(filter) # [ # {:name => :activate_filter, :filter_name => filter.underscore_name, :label => l(filter.label)}, # {:name => :operators, :filter_name => filter.underscore_name, :operators => filter.available_operators}, # {:name => :date, :filter_name => filter.underscore_name}, # {:name => :remove_filter, :filter_name => filter.underscore_name}] # end # def text_elements(filter) # [ # {:name => :activate_filter, :filter_name => filter.underscore_name, :label => l(filter.label)}, # {:name => :operators, :filter_name => filter.underscore_name, :operators => filter.available_operators}, # {:name => :text_box, :filter_name => filter.underscore_name}, # {:name => :remove_filter, :filter_name => filter.underscore_name}] # end def mapped(value, klass, default) id = value.to_i return default if id < 0 klass.find(id).name end def label_for(field) name = field.to_s.camelcase return l(field) unless engine::Filter.const_defined? name l(engine::Filter.const_get(name).label) end def debug_fields(result, prefix = ", ") prefix << result.fields.inspect << ", " << result.important_fields.inspect << ', ' << result.key.inspect if params[:debug] end def month_name(index) Date::MONTHNAMES[index].to_s end # ======================= SHARED CODE END def show_field(key, value) @show_row ||= Hash.new { |h,k| h[k] = {}} @show_row[key][value] ||= field_representation_map(key, value) end def raw_field(key, value) @raw_row ||= Hash.new { |h,k| h[k] = {}} @raw_row[key][value] ||= field_sort_map(key, value) end def cost_object_link(cost_object_id) co = CostObject.find(cost_object_id) if User.current.allowed_to_with_inheritance?(:view_cost_objects, co.project) link_to_cost_object(co) else co.subject end end def field_representation_map(key, value) return l(:label_none) if value.blank? case key.to_sym when :activity_id then mapped value, Enumeration, "#{l(:caption_material_costs)}" when :project_id then link_to_project Project.find(value.to_i) when :user_id, :assigned_to_id, :author_id then link_to_user User.find(value.to_i) when :tyear, :units then value.to_s when :tweek then "#{l(:label_week)} ##{value}" when :tmonth then month_name(value.to_i) when :category_id then IssueCategory.find(value.to_i).name when :cost_type_id then mapped value, CostType, l(:caption_labor) when :cost_object_id then cost_object_link value when :issue_id then link_to_issue Issue.find(value.to_i) when :spent_on then format_date(value.to_date) when :tracker_id then Tracker.find(value.to_i).name when :week then "#{l(:label_week)} #%s" % value.to_i.modulo(100) when :priority_id then IssuePriority.find(value.to_i).name when :fixed_version_id then Version.find(value.to_i).name when :singleton_value then "" when :status_id then IssueStatus.find(value.to_i).name else value.to_s end end def field_sort_map(key, value) return "" if value.blank? case key.to_sym when :issue_id, :tweek, :tmonth, :week then value.to_i when :spent_on then value.to_date.mjd else h(field_representation_map(key, value).gsub(/<\/?[^>]*>/, "")) end end def show_result(row, unit_id = self.unit_id) case unit_id when -1 then l_hours(row.units) when 0 then row.real_costs ? number_to_currency(row.real_costs) : '-' else current_cost_type = @cost_type || CostType.find(unit_id) pluralize(row.units, current_cost_type.unit, current_cost_type.unit_plural) end end def set_filter_options(struct, key, value) struct[:operators][key] = "=" struct[:values][key] = value.to_s end def available_cost_type_tabs(cost_types) tabs = cost_types.to_a tabs.delete 0 # remove money from list tabs.unshift 0 # add money as first tab tabs.map {|cost_type_id| [cost_type_id, cost_type_label(cost_type_id)] } end def cost_type_label(cost_type_id, cost_type_inst = nil, plural = true) case cost_type_id when -1 then l(:caption_labor) when 0 then l(:label_money) else (cost_type_inst || CostType.find(cost_type_id)).name end end def link_to_details(result) return '' # unless result.respond_to? :fields # uncomment to display session_filter = {:operators => session[:report][:filters][:operators].dup, :values => session[:report][:filters][:values].dup } filters = result.fields.inject session_filter do |struct, (key, value)| key = key.to_sym case key when :week set_filter_options struct, :tweek, value.to_i.modulo(100) set_filter_options struct, :tyear, value.to_i / 100 when :month, :year set_filter_options struct, :"t#{key}", value when :count, :units, :costs, :display_costs, :sum, :real_costs else set_filter_options struct, key, value end struct end options = { :fields => filters[:operators].keys, :set_filter => 1, :action => :drill_down } link_to '[+]', filters.merge(options), :class => 'drill_down', :title => l(:description_drill_down) end ## # Create the appropriate action for an entry with the type of log to use def action_for(result, options = {}) options.merge :controller => result.fields['type'] == 'TimeEntry' ? 'timelog' : 'costlog', :id => result.fields['id'].to_i end ## # Create the appropriate action for an entry with the type of log to use def entry_for(result) type = result.fields['type'] == 'TimeEntry' ? TimeEntry : CostEntry type.find(result.fields['id'].to_i) end ## # For a given row, determine how to render it's contents according to usability and # localization rules def show_row(row) link_to_details(row) << row.render { |k,v| show_field(k,v) } end def delimit(items, options = {}) options[:step] ||= 1 options[:delim] ||= '•' delimited = [] items.each_with_index do |item, ix| if ix != 0 and ix % options[:step] == 0 delimited << " #{options[:delim]} " + item else delimited << item end end delimited end ## # Finds the Filter-Class for as specific filter name while being careful with the filter_name parameter as it is user input. def filter_class(filter_name) klass = CostQuery::Filter.const_get(filter_name.to_s.camelize) return klass if klass.is_a? Class nil rescue NameError return nil end end