Conflicts: app/controllers/cost_reports_controller.rb app/models/report.rb app/views/cost_reports/index.rhtmlpull/6827/head
commit
7a43f4b57b
@ -1,240 +0,0 @@ |
||||
class CostReportsController < ApplicationController |
||||
before_filter :load_all |
||||
before_filter :find_optional_project, :only => [:index, :drill_down] |
||||
before_filter :generate_query, :only => [:index, :drill_down] |
||||
before_filter :set_cost_types, :only => [:index, :drill_down] |
||||
before_filter :save_query, :only => [:index, :drill_down] |
||||
|
||||
# rescue_from Exception do |exception| |
||||
# session.delete(:cost_query) |
||||
# @custom_errors ||= [] |
||||
# @custom_errors << l(:error_generic) |
||||
# render :layout => !request.xhr? |
||||
# end |
||||
|
||||
helper :reporting |
||||
include ReportingHelper |
||||
|
||||
def index |
||||
if @valid = valid_query? |
||||
if @query.group_bys.empty? |
||||
@table_partial = "cost_entry_table" |
||||
elsif @query.depth_of(:column) + @query.depth_of(:row) == 1 |
||||
@table_partial = "simple_cost_report_table" |
||||
else |
||||
if @query.depth_of(:column) == 0 || @query.depth_of(:row) == 0 |
||||
@query.depth_of(:column) == 0 ? @query.column(:singleton_value) : @query.row(:singleton_value) |
||||
end |
||||
@table_partial = "cost_report_table" |
||||
end |
||||
end |
||||
respond_to do |format| |
||||
format.html { render :layout => !request.xhr? } |
||||
end |
||||
end |
||||
|
||||
def drill_down |
||||
redirect_to :action => :index |
||||
end |
||||
|
||||
def available_values |
||||
filter = filter_class(params[:filter_name].to_s) |
||||
render_404 unless filter |
||||
can_answer = filter.respond_to? :available_values |
||||
@available_values = filter.available_values |
||||
|
||||
respond_to do |format| |
||||
format.html { can_answer ? render(:layout => !request.xhr?) : "" } |
||||
end |
||||
end |
||||
|
||||
## |
||||
# Determines if the request contains filters to set |
||||
def set_filter? #FIXME: rename to set_query? |
||||
params[:set_filter].to_i == 1 |
||||
end |
||||
|
||||
## |
||||
# Determines if the request sets a unit type |
||||
def set_unit? |
||||
params[:unit] |
||||
end |
||||
|
||||
## |
||||
# Find a query to search on and put it in the session |
||||
def filter_params |
||||
filters = http_filter_parameters if set_filter? |
||||
filters ||= session[:cost_query].try(:[], :filters) |
||||
filters ||= default_filter_parameters |
||||
end |
||||
|
||||
def group_params |
||||
groups = http_group_parameters if set_filter? |
||||
groups ||= session[:cost_query].try(:[], :groups) |
||||
groups ||= default_group_parameters |
||||
end |
||||
|
||||
## |
||||
# Extract active filters from the http params |
||||
def http_filter_parameters |
||||
params[:fields] ||= [] |
||||
(params[:fields].reject { |f| f.empty? } || []).inject({:operators => {}, :values => {}}) do |hash, field| |
||||
hash[:operators][field.to_sym] = params[:operators][field] |
||||
hash[:values][field.to_sym] = params[:values][field] |
||||
hash |
||||
end |
||||
end |
||||
|
||||
def http_group_parameters |
||||
if params[:groups] |
||||
rows = params[:groups][:rows] |
||||
columns = params[:groups][:columns] |
||||
end |
||||
{:rows => (rows || []), :columns => (columns || [])} |
||||
end |
||||
|
||||
## |
||||
# Set a default query to cut down initial load time |
||||
def default_filter_parameters |
||||
{:operators => {:user_id => "=", :spent_on => ">d"}, |
||||
:values => {:user_id => [User.current.id], :spent_on => [30.days.ago.strftime('%Y-%m-%d')]} |
||||
}.tap do |hash| |
||||
if @project |
||||
hash[:operators].merge! :project_id => "=" |
||||
hash[:values].merge! :project_id => [@project.id] |
||||
end |
||||
end |
||||
end |
||||
|
||||
## |
||||
# Set a default query to cut down initial load time |
||||
def default_group_parameters |
||||
{:columns => [:week], :rows => []}.tap do |h| |
||||
if @project |
||||
h[:rows] << :issue_id |
||||
else |
||||
h[:rows] << :project_id |
||||
end |
||||
end |
||||
end |
||||
|
||||
def force_default? |
||||
params[:default].to_i == 1 |
||||
end |
||||
|
||||
## |
||||
# We apply a project filter, except when we are just applying a brand new query |
||||
def ensure_project_scope!(filters) |
||||
return unless ensure_project_scope? |
||||
if @project |
||||
filters[:operators].merge! :project_id => "=" |
||||
filters[:values].merge! :project_id => @project.id.to_s |
||||
else |
||||
filters[:operators].delete :project_id |
||||
filters[:values].delete :project_id |
||||
end |
||||
end |
||||
|
||||
def ensure_project_scope? |
||||
!(set_filter? or set_unit?) |
||||
end |
||||
|
||||
def save_query |
||||
return unless params[:save_query].to_i == 1 || !User.current.allowed_to?(:save_queries, @project, :global => true) |
||||
@query.name = params[:name].present? ? params[:name] : l(:label_default) |
||||
@query.project ||= params[:project] || @project |
||||
@query.is_public = if User.current.allowed_to?(:manage_public_queries, @project) || User.current.admin? |
||||
params[:public] |
||||
end || false |
||||
@query.user_id ||= User.current.id |
||||
@query.save! |
||||
end |
||||
|
||||
## |
||||
# Build the query from the current request and save it to |
||||
# the session. |
||||
def generate_query |
||||
CostQuery::QueryUtils.cache.clear |
||||
|
||||
if params[:query] |
||||
return @query = CostQuery.deserialize(CostQuery.find(params[:query]).serialized) |
||||
end |
||||
filters = force_default? ? default_filter_parameters : filter_params |
||||
groups = force_default? ? default_group_parameters : group_params |
||||
ensure_project_scope! filters |
||||
|
||||
session[:cost_query] = {:filters => filters, :groups => groups} |
||||
|
||||
@query = CostQuery.new |
||||
@query.tap do |q| |
||||
filters[:operators].each do |filter, operator| |
||||
q.filter(filter.to_sym, |
||||
:operator => operator, |
||||
:values => filters[:values][filter]) |
||||
end |
||||
end |
||||
groups[:rows].reverse_each {|r| @query.row(r) } |
||||
groups[:columns].reverse_each {|c| @query.column(c) } |
||||
@query |
||||
end |
||||
|
||||
def valid_query? |
||||
return true unless @query |
||||
errornous = @query.filters ? @query.filters.select { |f| !f.valid? } : [] |
||||
@custom_errors = errornous.map do |err| |
||||
"Filter #{l(err.label)}: #{err.errors.join(", ")}" |
||||
end |
||||
errornous.empty? |
||||
end |
||||
|
||||
## |
||||
# Determine active cost types, the currently selected unit and corresponding cost type |
||||
def set_cost_types |
||||
set_active_cost_types |
||||
set_unit |
||||
set_cost_type |
||||
end |
||||
|
||||
# Determine the currently active unit from the parameters or session |
||||
# sets the @unit_id -> this is used in the index for determining the active unit tab |
||||
def set_unit |
||||
@unit_id = params[:unit].try(:to_i) || session[:unit_id].to_i |
||||
@unit_id = 0 unless @cost_types.include? @unit_id |
||||
session[:unit_id] = @unit_id |
||||
end |
||||
|
||||
# Determine the active cost type, if it is not labor or money, and add a hidden filter to the query |
||||
# sets the @cost_type -> this is used to select the proper units for display |
||||
def set_cost_type |
||||
if @unit_id != 0 |
||||
@query.filter :cost_type_id, :operator => '=', :value => @unit_id.to_s, :display => false |
||||
@cost_type = CostType.find(@unit_id) if @unit_id > 0 |
||||
end |
||||
end |
||||
|
||||
# set the @cost_types -> this is used to determine which tabs to display |
||||
def set_active_cost_types |
||||
unless @cost_types = session[:cost_query][:filters][:values][:cost_type_id].try(:collect, &:to_i) |
||||
relevant_cost_types = CostType.find(:all, :select => "id", :order => "id ASC").select do |t| |
||||
t.cost_entries.count > 0 |
||||
end.collect(&:id) |
||||
@cost_types = [-1, 0, *relevant_cost_types] |
||||
end |
||||
end |
||||
|
||||
def load_all |
||||
CostQuery::GroupBy.all |
||||
CostQuery::Filter.all |
||||
end |
||||
|
||||
private |
||||
## FIXME: Remove this once we moved to Redmine 1.0 |
||||
def find_optional_project |
||||
@project = Project.find(params[:project_id]) unless params[:project_id].blank? |
||||
|
||||
allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true) |
||||
allowed ? true : deny_access |
||||
rescue ActiveRecord::RecordNotFound |
||||
render_404 |
||||
end |
||||
end |
@ -1,226 +0,0 @@ |
||||
module ReportingHelper |
||||
include QueriesHelper |
||||
|
||||
def l(*values) |
||||
return values.first if values.size == 1 and values.first.respond_to? :to_str |
||||
super |
||||
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 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 link_to_project(project) |
||||
link_to project.name, :controller => 'projects', :action => 'show', :id => project |
||||
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 CostQuery::Filter.const_defined? name |
||||
l(CostQuery::Filter.const_get(name).label) |
||||
end |
||||
|
||||
def debug_fields(result, prefix = ", ") |
||||
#prefix << result.fields.inspect << ", " << result.key.inspect if params[:debug] |
||||
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, "<i>#{l(:caption_material_costs)}</i>" |
||||
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 |
||||
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) |
||||
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 = @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[:cost_query][:filters][:operators].dup, :values => session[:cost_query][: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 << "<b> #{options[:delim]} </b>" + 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 |
@ -1,80 +0,0 @@ |
||||
module CostQuery::CustomFieldMixin |
||||
include CostQuery::QueryUtils |
||||
|
||||
attr_reader :custom_field |
||||
SQL_TYPES = { |
||||
'string' => mysql? ? 'char' : 'varchar', |
||||
'list' => mysql? ? 'char' : 'varchar', |
||||
'text' => mysql? ? 'char' : 'text', |
||||
'bool' => mysql? ? 'unsigned' : 'boolean', |
||||
'date' => 'date', |
||||
'int' => 'decimal(60,3)', 'float' => 'decimal(60,3)' } |
||||
|
||||
def self.extended(base) |
||||
base.inherited_attribute :factory |
||||
base.factory = base |
||||
super |
||||
end |
||||
|
||||
def all |
||||
@all ||= generate_subclasses |
||||
end |
||||
|
||||
def generate_subclasses |
||||
IssueCustomField.all.map do |field| |
||||
class_name = class_name_for field.name |
||||
parent.send(:remove_const, class_name) if parent.const_defined? class_name |
||||
parent.const_set class_name, Class.new(self).prepare(field, class_name) |
||||
end |
||||
end |
||||
|
||||
def factory? |
||||
factory == self |
||||
end |
||||
|
||||
def on_prepare(&block) |
||||
return factory.on_prepare unless factory? |
||||
@on_prepare = block if block |
||||
@on_prepare ||= proc { } |
||||
@on_prepare |
||||
end |
||||
|
||||
def table_name |
||||
@class_name.demodulize.underscore.tableize.singularize |
||||
end |
||||
|
||||
def prepare(field, class_name) |
||||
@custom_field = field |
||||
label field.name |
||||
@class_name = class_name |
||||
dont_inherit :group_fields |
||||
db_field table_name |
||||
join_table (<<-SQL % [CustomValue.table_name, table_name, field.id, field.name, SQL_TYPES[field.field_format]]).gsub(/^ /, "") |
||||
-- BEGIN Custom Field Join: "%4$s" |
||||
LEFT OUTER JOIN ( |
||||
\tSELECT |
||||
\t\tCAST(value AS %5$s) AS %2$s, |
||||
\t\tcustomized_type, |
||||
\t\tcustom_field_id, |
||||
\t\tcustomized_id |
||||
\tFROM |
||||
\t\t%1$s) |
||||
AS %2$s |
||||
ON %2$s.customized_type = 'Issue' |
||||
AND %2$s.custom_field_id = %3$d |
||||
AND %2$s.customized_id = entries.issue_id |
||||
-- END Custom Field Join: "%4$s" |
||||
SQL |
||||
instance_eval(&on_prepare) |
||||
self |
||||
end |
||||
|
||||
def new(*) |
||||
fail "Only subclasses of #{self} should be instanciated." if factory? |
||||
super |
||||
end |
||||
|
||||
def class_name_for(field) |
||||
"CustomField" << field.split(/[ \-_]/).map { |part| part.gsub(/\W/, '').capitalize }.join |
||||
end |
||||
end |
@ -1,40 +0,0 @@ |
||||
require "set" |
||||
|
||||
module CostQuery::Filter |
||||
def self.all |
||||
@all ||= Set[ |
||||
CostQuery::Filter::ActivityId, |
||||
CostQuery::Filter::AssignedToId, |
||||
CostQuery::Filter::AuthorId, |
||||
CostQuery::Filter::CategoryId, |
||||
CostQuery::Filter::CostTypeId, |
||||
CostQuery::Filter::CreatedOn, |
||||
CostQuery::Filter::DueDate, |
||||
CostQuery::Filter::FixedVersionId, |
||||
CostQuery::Filter::IssueId, |
||||
CostQuery::Filter::OverriddenCosts, |
||||
CostQuery::Filter::PriorityId, |
||||
CostQuery::Filter::ProjectId, |
||||
CostQuery::Filter::SpentOn, |
||||
CostQuery::Filter::StartDate, |
||||
CostQuery::Filter::StatusId, |
||||
CostQuery::Filter::Subject, |
||||
CostQuery::Filter::TrackerId, |
||||
#CostQuery::Filter::Tweek, |
||||
#CostQuery::Filter::Tmonth, |
||||
#CostQuery::Filter::Tyear, |
||||
CostQuery::Filter::UpdatedOn, |
||||
CostQuery::Filter::UserId, |
||||
CostQuery::Filter::PermissionFilter, |
||||
*CostQuery::Filter::CustomField.all |
||||
] |
||||
end |
||||
|
||||
def self.all_grouped |
||||
all.group_by { |f| f.applies_for }.to_a.sort { |a,b| a.first.to_s <=> b.first.to_s } |
||||
end |
||||
|
||||
def self.from_hash |
||||
raise NotImplementedError |
||||
end |
||||
end |
@ -1,7 +0,0 @@ |
||||
class CostQuery::Filter::ActivityId < CostQuery::Filter::Base |
||||
label :field_activity |
||||
|
||||
def self.available_values(*) |
||||
TimeEntryActivity.find(:all, :order => 'name').map { |a| [a.name, a.id] } |
||||
end |
||||
end |
@ -1,10 +0,0 @@ |
||||
class CostQuery::Filter::AssignedToId < CostQuery::Filter::Base |
||||
use :null_operators |
||||
join_table Issue |
||||
applies_for :label_issue_attributes |
||||
label :field_assigned_to |
||||
|
||||
def self.available_values(*) |
||||
CostQuery::Filter::UserId.available_values |
||||
end |
||||
end |
@ -1,9 +0,0 @@ |
||||
class CostQuery::Filter::AuthorId < CostQuery::Filter::Base |
||||
join_table Issue |
||||
applies_for :label_issue_attributes |
||||
label :field_author |
||||
|
||||
def self.available_values(*) |
||||
CostQuery::Filter::UserId.available_values |
||||
end |
||||
end |
@ -1,11 +0,0 @@ |
||||
class CostQuery::Filter::CategoryId < CostQuery::Filter::Base |
||||
use :null_operators |
||||
join_table Issue |
||||
applies_for :label_issue_attributes |
||||
label :field_category |
||||
|
||||
def self.available_values(*) |
||||
categories = IssueCategory.find :all, :conditions => {:project_id => Project.visible.map{|p| p.id}} |
||||
categories.map { |c| ["#{c.project.name} - #{c.name} ", c.id] }.sort_by { |a| a.first.to_s + a.second.to_s } |
||||
end |
||||
end |
@ -1,9 +0,0 @@ |
||||
class CostQuery::Filter::CostObjectId < CostQuery::Filter::Base |
||||
join_table Project |
||||
label :field_cost_object |
||||
applies_for :label_issue_attributes |
||||
|
||||
def self.available_values(*) |
||||
([[l(:caption_labor), -1]] + CostObject.find(:all, :order => 'name').map { |t| [t.name, t.id] }) |
||||
end |
||||
end |
@ -1,18 +0,0 @@ |
||||
class CostQuery::Filter::CostTypeId < CostQuery::Filter::Base |
||||
label :field_cost_type |
||||
extra_options :display |
||||
|
||||
def initialize(child = nil, options = {}) |
||||
super |
||||
@display = options[:display] |
||||
end |
||||
|
||||
def display? |
||||
return super if @display.nil? |
||||
@display |
||||
end |
||||
|
||||
def self.available_values(*) |
||||
([[l(:caption_labor), -1]] + CostType.find(:all, :order => 'name').map { |t| [t.name, t.id] }) |
||||
end |
||||
end |
@ -1,5 +0,0 @@ |
||||
class CostQuery::Filter::CreatedOn < CostQuery::Filter::Base |
||||
db_field "entries.created_on" |
||||
use :time_operators |
||||
label :field_created_on |
||||
end |
@ -1,25 +0,0 @@ |
||||
module CostQuery::Filter |
||||
class CustomField < Base |
||||
extend CostQuery::CustomFieldMixin |
||||
|
||||
on_prepare do |
||||
applies_for :label_issue_attributes |
||||
# redmine internals just suck |
||||
case custom_field.field_format |
||||
when 'string', 'text' then use :string_operators |
||||
when 'list' then use :null_operators |
||||
when 'date' then use :time_operators |
||||
when 'int', 'float' then use :integer_operators |
||||
when 'bool' |
||||
@possible_values = [['true', 't'], ['false', 'f']] |
||||
use :null_operators |
||||
else |
||||
fail "cannot handle #{custom_field.field_format.inspect}" |
||||
end |
||||
end |
||||
|
||||
def self.available_values(*) |
||||
@possible_values || custom_field.possible_values |
||||
end |
||||
end |
||||
end |
@ -1,6 +0,0 @@ |
||||
class CostQuery::Filter::DueDate < CostQuery::Filter::Base |
||||
use :time_operators |
||||
join_table Issue |
||||
applies_for :label_issue_attributes |
||||
label :field_due_date |
||||
end |
@ -1,11 +0,0 @@ |
||||
class CostQuery::Filter::FixedVersionId < CostQuery::Filter::Base |
||||
use :null_operators |
||||
join_table Issue |
||||
applies_for :label_issue_attributes |
||||
label :field_fixed_version |
||||
|
||||
def self.available_values(*) |
||||
versions = Version.find :all, :conditions => {:project_id => Project.visible.map{|p| p.id}} |
||||
versions.map { |a| ["#{a.project.name} - #{a.name}", a.id] }.sort_by { |a| a.first.to_s + a.second.to_s } |
||||
end |
||||
end |
@ -1,25 +0,0 @@ |
||||
class CostQuery::Filter::IssueId < CostQuery::Filter::Base |
||||
label :field_issue |
||||
|
||||
def self.available_values(*) |
||||
issues = Project.visible.collect { |p| p.issues }.flatten.uniq.sort_by { |i| i.id } |
||||
issues.map { |i| [text_for_issue i, i.id] } |
||||
end |
||||
|
||||
def self.heavy? |
||||
true |
||||
end |
||||
not_selectable! if heavy? |
||||
|
||||
def self.text_for_issue(i) |
||||
i = i.first if i.is_a? Array |
||||
str = "##{i.id} " |
||||
str << (i.subject.length > 30 ? i.subject.first(26)+'...': i.subject) |
||||
end |
||||
|
||||
def self.text_for_id(i) |
||||
text_for_issue Issue.find(i) |
||||
rescue ActiveRecord::RecordNotFound |
||||
"" |
||||
end |
||||
end |
@ -1,8 +0,0 @@ |
||||
class CostQuery::Filter::NoFilter < CostQuery::Filter::Base |
||||
dont_display! |
||||
singleton |
||||
|
||||
def sql_statement |
||||
CostQuery::SqlStatement.for_entries |
||||
end |
||||
end |
@ -1,11 +0,0 @@ |
||||
class CostQuery::Filter::OverriddenCosts < CostQuery::Filter::Base |
||||
label :field_overridden_costs |
||||
|
||||
def self.available_operators |
||||
['y', 'n'].map { |s| s.to_operator } |
||||
end |
||||
|
||||
def self.available_values(*) |
||||
[] |
||||
end |
||||
end |
@ -1,35 +0,0 @@ |
||||
class CostQuery::Filter::PermissionFilter < CostQuery::Filter::Base |
||||
dont_display! |
||||
not_selectable! |
||||
db_field "" |
||||
singleton |
||||
|
||||
initialize_query_with { |query| query.filter self.to_s.demodulize.to_sym } |
||||
|
||||
def permission_statement(permission) |
||||
User.current.allowed_for(permission).gsub(/(user|project)s?\.id/, '\1_id') |
||||
end |
||||
|
||||
def permission_for(type) |
||||
"(#{permission_statement :"view_own_#{type}_entries"} " \ |
||||
"OR #{permission_statement :"view_#{type}_entries"})" |
||||
end |
||||
|
||||
def display_costs |
||||
"(#{permission_statement :view_hourly_rates} " \ |
||||
"AND #{permission_statement :view_cost_rates}) " \ |
||||
"OR " \ |
||||
"(#{permission_statement :view_own_hourly_rate} " \ |
||||
"AND type = 'TimeEntry')" |
||||
end |
||||
|
||||
def sql_statement |
||||
super.tap do |query| |
||||
query.from.each_subselect do |sub| |
||||
sub.where permission_for(sub == query.from.first ? 'time' : 'cost') |
||||
sub.select.delete_if { |f| f.end_with? "display_costs" } |
||||
sub.select :display_costs => switch(display_costs => '1', :else => 0) |
||||
end |
||||
end |
||||
end |
||||
end |
@ -1,9 +0,0 @@ |
||||
class CostQuery::Filter::PriorityId < CostQuery::Filter::Base |
||||
join_table Issue |
||||
applies_for :label_issue_attributes |
||||
label :field_priority |
||||
|
||||
def self.available_values(*) |
||||
IssuePriority.find(:all, :order => 'position DESC').map { |i| [i.name, i.id] } |
||||
end |
||||
end |
@ -1,26 +0,0 @@ |
||||
class CostQuery::Filter::ProjectId < CostQuery::Filter::Base |
||||
db_field "entries.project_id" |
||||
label :field_project |
||||
|
||||
def self.available_operators |
||||
["=", "!", "=_child_projects", "!_child_projects"].map { |s| s.to_operator } |
||||
end |
||||
|
||||
## |
||||
# Calculates the available values for this filter. |
||||
# Gives a map of [project_name, project_id, nesting_level_of_project]. |
||||
# The map is sorted such that projects appear in alphabetical order within a nesting level |
||||
# and so that descendant projects appear after their ancestors. |
||||
def self.available_values(*) |
||||
map = [] |
||||
ancestors = [] |
||||
Project.visible.sort_by(&:lft).each do |project| |
||||
while (ancestors.any? && !project.is_descendant_of?(ancestors.last)) |
||||
ancestors.pop |
||||
end |
||||
map << [project.name, project.id, ancestors.size] |
||||
ancestors << project |
||||
end |
||||
map |
||||
end |
||||
end |
@ -1,4 +0,0 @@ |
||||
class CostQuery::Filter::SpentOn < CostQuery::Filter::Base |
||||
use :time_operators |
||||
label :label_spent_on_reporting |
||||
end |
@ -1,6 +0,0 @@ |
||||
class CostQuery::Filter::StartDate < CostQuery::Filter::Base |
||||
use :time_operators |
||||
join_table Issue |
||||
applies_for :label_issue_attributes |
||||
label :field_start_date |
||||
end |
@ -1,10 +0,0 @@ |
||||
class CostQuery::Filter::StatusId < CostQuery::Filter::Base |
||||
available_operators 'c', 'o' |
||||
join_table Issue, IssueStatus => [Issue, :status] |
||||
applies_for :label_issue_attributes |
||||
label :field_status |
||||
|
||||
def self.available_values(*) |
||||
IssueStatus.find(:all, :order => 'name').map { |i| [i.name, i.id] } |
||||
end |
||||
end |
@ -1,6 +0,0 @@ |
||||
class CostQuery::Filter::Subject < CostQuery::Filter::Base |
||||
use :string_operators |
||||
join_table Issue |
||||
applies_for :label_issue_attributes |
||||
label :field_subject |
||||
end |
@ -1,8 +0,0 @@ |
||||
class CostQuery::Filter::Tmonth < CostQuery::Filter::Base |
||||
use :integer_operators |
||||
label :label_month_reporting |
||||
|
||||
def self.available_values(*) |
||||
1.upto(12).map {|i| [ ::I18n.t('date.month_names')[i], i ]} |
||||
end |
||||
end |
@ -1,9 +0,0 @@ |
||||
class CostQuery::Filter::TrackerId < CostQuery::Filter::Base |
||||
join_table Issue |
||||
applies_for :label_issue_attributes |
||||
label :field_tracker |
||||
|
||||
def self.available_values(*) |
||||
Tracker.find(:all, :order => 'name').map { |i| [i.name, i.id] } |
||||
end |
||||
end |
@ -1,8 +0,0 @@ |
||||
class CostQuery::Filter::Tweek < CostQuery::Filter::Base |
||||
use :integer_operators |
||||
label :label_week_reporting |
||||
|
||||
def self.available_values(*) |
||||
1.upto(53).map {|i| [ i.to_s, i ]} |
||||
end |
||||
end |
@ -1,8 +0,0 @@ |
||||
class CostQuery::Filter::Tyear < CostQuery::Filter::Base |
||||
use :integer_operators |
||||
label :label_year_reporting |
||||
|
||||
def self.available_values(*) |
||||
1970.upto(Date.today.year).map {|i| [ i.to_s, i ]}.reverse |
||||
end |
||||
end |
@ -1,5 +0,0 @@ |
||||
class CostQuery::Filter::UpdatedOn < CostQuery::Filter::Base |
||||
db_field "entries.updated_on" |
||||
use :time_operators |
||||
label :field_updated_on |
||||
end |
@ -1,12 +0,0 @@ |
||||
class CostQuery::Filter::UserId < CostQuery::Filter::Base |
||||
label :field_user |
||||
|
||||
def self.available_values(*) |
||||
users = Project.visible.collect {|p| p.users}.flatten.uniq.sort |
||||
values = users.map { |u| [u.name, u.id] } |
||||
values.delete_if { |u| (u.first.include? "Redmine Admin") || (u.first.include? "Anonymous")} |
||||
values.sort! |
||||
values.unshift ["<< #{l(:label_me)} >>", User.current.id.to_s] if User.current.logged? |
||||
values |
||||
end |
||||
end |
@ -1,36 +0,0 @@ |
||||
require "set" |
||||
|
||||
module CostQuery::GroupBy |
||||
def self.all |
||||
@all ||= Set[ |
||||
CostQuery::GroupBy::ActivityId, |
||||
CostQuery::GroupBy::CostObjectId, |
||||
CostQuery::GroupBy::CostTypeId, |
||||
CostQuery::GroupBy::FixedVersionId, |
||||
CostQuery::GroupBy::IssueId, |
||||
CostQuery::GroupBy::PriorityId, |
||||
CostQuery::GroupBy::ProjectId, |
||||
CostQuery::GroupBy::SpentOn, |
||||
CostQuery::GroupBy::SingletonValue, |
||||
CostQuery::GroupBy::Tmonth, |
||||
CostQuery::GroupBy::TrackerId, |
||||
#CostQuery::GroupBy::Tweek, |
||||
CostQuery::GroupBy::Tyear, |
||||
CostQuery::GroupBy::UserId, |
||||
CostQuery::GroupBy::Week, |
||||
CostQuery::GroupBy::AuthorId, |
||||
CostQuery::GroupBy::AssignedToId, |
||||
CostQuery::GroupBy::CategoryId, |
||||
CostQuery::GroupBy::StatusId, |
||||
*CostQuery::GroupBy::CustomField.all |
||||
] |
||||
end |
||||
|
||||
def self.all_grouped |
||||
all.group_by { |f| f.applies_for }.to_a.sort { |a,b| a.first.to_s <=> b.first.to_s } |
||||
end |
||||
|
||||
def self.from_hash |
||||
raise NotImplementedError |
||||
end |
||||
end |
@ -1,5 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class ActivityId < Base |
||||
label :field_activity |
||||
end |
||||
end |
@ -1,7 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class AssignedToId < Base |
||||
join_table Issue |
||||
applies_for :label_issue_attributes |
||||
label :field_assigned_to |
||||
end |
||||
end |
@ -1,7 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class AuthorId < Base |
||||
join_table Issue |
||||
applies_for :label_issue_attributes |
||||
label :field_author |
||||
end |
||||
end |
@ -1,7 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class CategoryId < Base |
||||
join_table Issue |
||||
applies_for :label_issue_attributes |
||||
label :field_category |
||||
end |
||||
end |
@ -1,7 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class CostObjectId < Base |
||||
join_table Issue |
||||
applies_for :label_issue_attributes |
||||
label :field_cost_object |
||||
end |
||||
end |
@ -1,5 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class CostTypeId < Base |
||||
label :field_cost_type |
||||
end |
||||
end |
@ -1,7 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class CustomField < Base |
||||
applies_for :label_issue_attributes |
||||
extend CostQuery::CustomFieldMixin |
||||
on_prepare { group_fields table_name } |
||||
end |
||||
end |
@ -1,7 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class FixedVersionId < Base |
||||
join_table Issue |
||||
applies_for :label_issue_attributes |
||||
label :field_fixed_version |
||||
end |
||||
end |
@ -1,5 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class IssueId < Base |
||||
label :field_issue |
||||
end |
||||
end |
@ -1,7 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class PriorityId < Base |
||||
join_table Issue |
||||
applies_for :label_issue_attributes |
||||
label :field_priority |
||||
end |
||||
end |
@ -1,5 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class ProjectId < Base |
||||
label :field_project |
||||
end |
||||
end |
@ -1,5 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class SingletonValue < Base |
||||
dont_display! |
||||
end |
||||
end |
@ -1,5 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class SpentOn < Base |
||||
label :label_spent_on_reporting |
||||
end |
||||
end |
@ -1,7 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class StatusId < Base |
||||
join_table Issue |
||||
applies_for :label_issue_attributes |
||||
label :field_status |
||||
end |
||||
end |
@ -1,5 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class Tmonth < Base |
||||
label :label_month_reporting |
||||
end |
||||
end |
@ -1,7 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class TrackerId < Base |
||||
join_table Issue |
||||
applies_for :label_issue_attributes |
||||
label :field_tracker |
||||
end |
||||
end |
@ -1,5 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class Tweek < Base |
||||
label :label_week_reporting |
||||
end |
||||
end |
@ -1,5 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class Tyear < Base |
||||
label :label_year_reporting |
||||
end |
||||
end |
@ -1,5 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class UserId < Base |
||||
label :field_user |
||||
end |
||||
end |
@ -1,5 +0,0 @@ |
||||
module CostQuery::GroupBy |
||||
class Week < Base |
||||
label :label_week_reporting |
||||
end |
||||
end |
@ -1,80 +0,0 @@ |
||||
require_dependency "time_entry" |
||||
require_dependency "cost_entry" |
||||
|
||||
module Entry |
||||
[TimeEntry, CostEntry].each { |e| e.send :include, self } |
||||
|
||||
class Delegator < ActiveRecord::Base |
||||
self.abstract_class = true |
||||
class << self |
||||
def ===(obj) |
||||
TimeEntry === obj or CostEntry === obj |
||||
end |
||||
|
||||
def calculate(type, *args) |
||||
a, b = TimeEntry.calculate(type, *args), CostEntry.calculate(type, *args) |
||||
case type |
||||
when :sum, :count then a + b |
||||
when :avg then (a + b) / 2 |
||||
when :min then [a, b].min |
||||
when :max then [a, b].max |
||||
else raise NotImplementedError |
||||
end |
||||
end |
||||
|
||||
%[find_by_sql count_by_sql count sum].each do |meth| |
||||
define_method(meth) { |*args| find_all(meth, *args) } |
||||
end |
||||
|
||||
undef_method :create, :update, :delete, :destroy, :new, :update_counters, |
||||
:increment_counter, :decrement_counter |
||||
|
||||
%w[update_all destroy_all delete_all].each do |meth| |
||||
define_method(meth) { |*args| send_all(meth, *args) } |
||||
end |
||||
|
||||
private |
||||
def find_initial(options) find_one :find_initial, options end |
||||
def find_last(options) find_one :find_last, options end |
||||
def find_every(options) find_many :find_every, options end |
||||
def find_from_ids(args, options) find_many :find_from_ids, options end |
||||
|
||||
def find_one(*args) |
||||
TimeEntry.send(*args) || CostEntry.send(*args) |
||||
end |
||||
|
||||
def find_many(*args) |
||||
TimeEntry.send(*args) + CostEntry.send(*args) |
||||
end |
||||
|
||||
def send_all(*args) |
||||
[TimeEntry.send(*args), CostEntry.send(*args)] |
||||
end |
||||
end |
||||
end |
||||
|
||||
def units |
||||
super |
||||
rescue NoMethodError |
||||
hours |
||||
end |
||||
|
||||
def cost_type |
||||
super |
||||
rescue NoMethodError |
||||
end |
||||
|
||||
def activity |
||||
super |
||||
rescue NoMethodError |
||||
end |
||||
|
||||
def activity_id |
||||
super |
||||
rescue NoMethodError |
||||
end |
||||
|
||||
def self.method_missing(*a, &b) |
||||
Delegator.send(*a, &b) |
||||
end |
||||
end |
@ -0,0 +1,15 @@ |
||||
require "set" |
||||
|
||||
class Report::Filter |
||||
def self.all |
||||
@all ||= Set[] |
||||
end |
||||
|
||||
def self.all_grouped |
||||
all.group_by { |f| f.applies_for }.to_a.sort { |a,b| a.first.to_s <=> b.first.to_s } |
||||
end |
||||
|
||||
def self.from_hash |
||||
raise NotImplementedError |
||||
end |
||||
end |
@ -0,0 +1,9 @@ |
||||
class Report::Filter::NoFilter < Report::Filter::Base |
||||
table_name "entries" |
||||
dont_display! |
||||
singleton |
||||
|
||||
def sql_statement |
||||
raise NotImplementedError, "My subclass should have overwritten 'sql_statement'" |
||||
end |
||||
end |
@ -0,0 +1,17 @@ |
||||
require "set" |
||||
|
||||
class Report::GroupBy |
||||
include Report::QueryUtils |
||||
|
||||
def self.all |
||||
Set[engine::GroupBy::SingletonValue] |
||||
end |
||||
|
||||
def self.all_grouped |
||||
all.group_by { |f| f.applies_for }.to_a.sort { |a,b| a.first.to_s <=> b.first.to_s } |
||||
end |
||||
|
||||
def self.from_hash |
||||
raise NotImplementedError |
||||
end |
||||
end |
@ -1,11 +1,11 @@ |
||||
module CostQuery::GroupBy |
||||
class Report::GroupBy |
||||
module RubyAggregation |
||||
def responsible_for_sql? |
||||
false |
||||
end |
||||
|
||||
## |
||||
# @return [CostQuery::Result] aggregation |
||||
# @return [Report::Result] aggregation |
||||
def compute_result |
||||
child.result.grouped_by(all_group_fields(false), type, group_fields) |
||||
end |
@ -0,0 +1,10 @@ |
||||
class Report::GroupBy |
||||
class SingletonValue < Base |
||||
dont_display! |
||||
|
||||
def define_group(sql) |
||||
sql.select "1 as singleton_value" |
||||
sql.group_by "singleton_value" |
||||
end |
||||
end |
||||
end |
@ -1,6 +1,6 @@ |
||||
require 'set' |
||||
|
||||
module CostQuery::InheritedAttribute |
||||
module Report::InheritedAttribute |
||||
def inherited_attribute(*attributes) |
||||
options = attributes.extract_options! |
||||
list = options[:list] |
@ -0,0 +1,63 @@ |
||||
module Report::InheritedNamespace |
||||
NESTED_NAMESPACES = %w[Validation Filter GroupBy Result Operator QueryUtils] |
||||
|
||||
module Hook |
||||
def const_missing(name, *) |
||||
super |
||||
rescue ArgumentError => error |
||||
# require 'ruby-debug'; debugger |
||||
rescue NameError => error |
||||
load_constant name, error |
||||
end |
||||
end |
||||
|
||||
def self.activate |
||||
Report.extend self |
||||
NESTED_NAMESPACES.each { |n| n.extend self } |
||||
end |
||||
|
||||
def inherited(klass) |
||||
super |
||||
propagate klass |
||||
end |
||||
|
||||
def included(klass) |
||||
super |
||||
propagate klass |
||||
end |
||||
|
||||
def propagate(klass) |
||||
klass.extend Report::InheritedNamespace |
||||
klass.extend Hook |
||||
return unless klass < Report |
||||
NESTED_NAMESPACES.each do |name| |
||||
if file = ActiveSupport::Dependencies.search_for_file("#{klass.name}::#{name}".underscore) |
||||
require_or_load file |
||||
propagate klass.const_get(name) |
||||
else |
||||
const_missing name |
||||
end |
||||
end |
||||
end |
||||
|
||||
def load_constant(name, error = NameError) |
||||
zuper = (Class === self ? superclass : ancestors.second).const_get(name) |
||||
klass = case zuper |
||||
when Class then const_set name, Class.new(zuper) |
||||
when Module then const_set name, Module.new { include zuper } |
||||
else const_set name, zuper |
||||
end |
||||
propagate klass |
||||
klass |
||||
rescue NameError, ArgumentError => new_error |
||||
if file = ActiveSupport::Dependencies.search_for_file("#{self.name}::#{name}".underscore) |
||||
require_or_load file |
||||
const_get name |
||||
else |
||||
error.message << "\n\tWas #{new_error.class}: #{new_error.message}" |
||||
new_error.backtrace[0..9].each { |l| error.message << "\n\t\t#{l}" } |
||||
error.message << "\n\t\t..." if new_error.backtrace.size > 10 |
||||
raise error |
||||
end |
||||
end |
||||
end |
@ -1,9 +1,9 @@ |
||||
# encoding: UTF-8 |
||||
require 'enumerator' |
||||
|
||||
class CostQuery::Table |
||||
class Report::Table |
||||
attr_accessor :query |
||||
include CostQuery::QueryUtils |
||||
include Report::QueryUtils |
||||
|
||||
def initialize(query) |
||||
@query = query |
@ -1,4 +1,4 @@ |
||||
module CostQuery::Validation |
||||
module Report::Validation |
||||
module Dates |
||||
def validate_dates(*values) |
||||
values = values.flatten |
@ -1,4 +1,4 @@ |
||||
module CostQuery::Validation |
||||
module Report::Validation |
||||
module Integers |
||||
def validate_integers(*values) |
||||
values = values.flatten |
@ -1,4 +1,4 @@ |
||||
module CostQuery::Validation |
||||
module Report::Validation |
||||
module Sql |
||||
def validate_sql(values = []) |
||||
raise NotImplementedError, "Haven't done SQL validation just yet!" |
@ -1,4 +1,4 @@ |
||||
class CostQuery::Walker |
||||
class Report::Walker |
||||
attr_accessor :query, :header_stack |
||||
def initialize(query) |
||||
@query = query |
@ -1,60 +0,0 @@ |
||||
<% list = [:spent_on, :user_id, :activity_id, :issue_id, :comments, :project_id] %> |
||||
|
||||
<table class="report detail-report" id="sortable-table"> |
||||
<thead> |
||||
<tr> |
||||
<% list.each do |field| %><th><%= label_for(field) %></th><% end %> |
||||
<th class='right'><%= @cost_type.try(:unit_plural) || l(:units) %></th> |
||||
<th class="right"><%= l(:field_costs) %></th> |
||||
<th></th> |
||||
</tr> |
||||
</thead> |
||||
<tfoot> |
||||
<tr> |
||||
<% if show_result(@query, 0) != show_result(@query) %> |
||||
<th class="inner right" colspan='<%= list.size + 1 %>'> |
||||
<%= show_result @query %> |
||||
</th> |
||||
<th class="result right"><%= show_result @query, 0 %></th> |
||||
<% else %> |
||||
<th class="result right" colspan='<%= list.size + 2 %>'><%= show_result @query %></th> |
||||
<% end %> |
||||
<th class="unsortable"></th> |
||||
</tr> |
||||
</tfoot> |
||||
<tbody> |
||||
<% @query.each_direct_result do |result| %> |
||||
<tr class='<%= cycle("odd", "even") %>'> |
||||
<% list.each do |field| %> |
||||
<td |
||||
raw-data="<%= raw_field(field, result.fields[field.to_s]) -%>" |
||||
class="left"> |
||||
<%= show_field field, result.fields[field.to_s] %> |
||||
</td> |
||||
<% end %> |
||||
<td class="units right" raw-data="<%= result.units -%>"><%= show_result(result, result.fields['cost_type_id'].to_i) %></td> |
||||
<td class="currency right" raw-data="<%= result.real_costs -%>"><%= show_result(result, 0) %></td> |
||||
<td style="width: 40px"> |
||||
<% with_project(result.fields['project_id']) do %> |
||||
<% if entry_for(result).editable_by? User.current %> |
||||
<%= link_to image_tag('edit.png'), |
||||
action_for(result, :action => 'edit'), :title => l(:button_edit) %> |
||||
<%= link_to image_tag('delete.png'), action_for(result, :action => 'destroy'), |
||||
:title => l(:button_edit), :confirm => l(:text_are_you_sure), |
||||
:method => :post, :title => l(:button_delete) %> |
||||
<% end %> |
||||
<% end %> |
||||
</td> |
||||
</tr> |
||||
<% if params[:debug] %> |
||||
<tr> |
||||
<td colspan='<%= list.size + 3 %>'> |
||||
<%= result.fields.reject {|k,v| list.include? k.to_sym }.inspect %> |
||||
</td> |
||||
</tr> |
||||
<% end %> |
||||
<% end %> |
||||
</tbody> |
||||
</table> |
||||
|
||||
<%= render :partial => 'sortable_init', :locals => { :sort_first_row => true } %> |
@ -1,102 +0,0 @@ |
||||
<% |
||||
|
||||
walker.for_final_row do |row, cells| |
||||
html = "<th class='normal inner left'>#{show_row row}#{debug_fields(row)}</th>" |
||||
html << cells.join |
||||
html << "<th class='normal inner right'>#{show_result(row)}#{debug_fields(row)}</th>" |
||||
end |
||||
|
||||
walker.for_row do |row, subrows| |
||||
subrows.flatten! |
||||
unless row.fields.empty? |
||||
subrows[0] = <<-HTML |
||||
<th class='top left' rowspan='#{subrows.size}'>#{show_row row}#{debug_fields(row)}</th> |
||||
#{subrows[0].gsub("class='normal", "class='top")} |
||||
<th class='top right' rowspan='#{subrows.size}'>#{show_result(row)}#{debug_fields(row )}</th> |
||||
HTML |
||||
end |
||||
subrows.last.gsub!("class='normal", "class='bottom") |
||||
subrows.last.gsub!("class='top", "class='bottom top") |
||||
subrows |
||||
end |
||||
|
||||
walker.for_empty_cell { "<td class='normal empty'> </td>" } |
||||
|
||||
walker.for_cell do |result| |
||||
"<td class='normal'>#{link_to_details(result)}#{show_result result}#{debug_fields(result)}</td>" |
||||
end |
||||
%> |
||||
|
||||
<table class='report'> |
||||
<thead> |
||||
<% walker.headers do |list, first, first_in_col, last_in_col| %> |
||||
<%= '<tr>' if first_in_col %> |
||||
<%= "<th rowspan='#{query.depth_of(:column)}' colspan='#{query.depth_of(:row)}'></th>" if first %> |
||||
<% list.each do |column| %> |
||||
<th colspan="<%= column.final_number(:column) %>" class="<%= "inner" if column.final? :column %>"> |
||||
<%= show_row column %> |
||||
</th> |
||||
<% end %> |
||||
<%= "<th rowspan='#{@query.depth_of(:column)}' colspan='#{query.depth_of(:row)}'></th>" if first %> |
||||
<%= '</tr>' if last_in_col %> |
||||
<% end %> |
||||
</thead> |
||||
|
||||
<tfoot> |
||||
<% walker.reverse_headers do |list, first, first_in_col, last_in_col| %> |
||||
<% if first_in_col %> |
||||
<tr> |
||||
<%= "<th rowspan='#{query.depth_of(:column)}' colspan='#{query.depth_of(:row)}' class='top'> </th>" if first %> |
||||
<% end %> |
||||
<% list.each do |column| %> |
||||
<th colspan="<%= column.final_number(:column) %>" class="<%= "inner" if first %>"> |
||||
<%= show_result column %><%= debug_fields(column) %> |
||||
</th> |
||||
<% end %> |
||||
<% if last_in_col %> |
||||
<% if first %> |
||||
<th rowspan='<%= @query.depth_of(:column) %>' colspan='<%= query.depth_of(:row) %>' class='top result'> |
||||
<%= show_result query %> |
||||
</th> |
||||
<% end %> |
||||
</tr> |
||||
<% end %> |
||||
<% end %> |
||||
</tfoot> |
||||
|
||||
<tbody> |
||||
<% first = true %> |
||||
<% walker.body do |line| %> |
||||
<% |
||||
if first |
||||
line.gsub!("class='normal", "class='top") |
||||
first = false |
||||
end |
||||
%> |
||||
<tr class='<%= cycle("odd", "even") %>'><%= line %></tr> |
||||
<% end %> |
||||
</tbody> |
||||
</table> |
||||
|
||||
<% if false or params[:debug] %> |
||||
<pre> |
||||
|
||||
[ Query ] |
||||
<% query.chain.each do |child| %> |
||||
- <%= h child.class.inspect %>, <%= h child.type %> |
||||
<% end %> |
||||
|
||||
[ RESULT ] |
||||
<% query.result.recursive_each_with_level do |level, result| %> |
||||
<%= ">>> " * (level+1) %><%= h result.inspect %>, |
||||
<%= " " * (level+1) %><%= h result.type.inspect %>, |
||||
<%= " " * (level+1) %><%= h result.fields.inspect %> |
||||
<% end %> |
||||
|
||||
[ HEADER STACK ] |
||||
<% walker.header_stack.each do |l| %> |
||||
<%= ">>> #{l.inspect}" %> |
||||
<% end %> |
||||
|
||||
</pre> |
||||
<% end %> |
@ -1,39 +0,0 @@ |
||||
<%# |
||||
This partial requires the following locals: |
||||
f An ActionView::Helpers::FormBuilder |
||||
query A CostQuery object |
||||
%> |
||||
|
||||
<%= javascript_include_tag "reporting", :plugin => "redmine_reporting" %> |
||||
<% grouped_filters = CostQuery::Filter.all_grouped.sort_by { |label, group_by_ary| l(label) } %> |
||||
<% partial_prefix = File.join(File.basename(File.dirname(__FILE__)), 'filters') %> |
||||
|
||||
|
||||
<table id="filter_table"> |
||||
<% grouped_filters.each do |label, filter_ary| %> |
||||
<tr id="tr_<%= label.to_s %>" style="display:none"><td><h3><%= l(label) %></h3></td></tr> |
||||
<% filter_ary.sort_by { |f| l(f.label)}.each do |filter| %> |
||||
<% next unless filter.display? %> |
||||
<tr id="tr_<%= filter.underscore_name %>" class="filter" style="display:none" data-label="tr_<%= label.to_s %>"> |
||||
<% html_elements(filter).each do |element| %> |
||||
<%= render :partial => File.join(partial_prefix, element[:name].to_s), |
||||
:locals => {:element => element, :f => f, :filter => filter, :query => query} %> |
||||
<% end %> |
||||
</tr> |
||||
<% end %> |
||||
<% end %> |
||||
</table> |
||||
<div id="add_filter_block"> |
||||
<select onchange="add_filter(this);" id="add_filter_select" class="select-small"> |
||||
<option value="">-- <%= l(:label_filter_add) %> --</option> |
||||
<% grouped_filters.each do |label, filter_ary| %> |
||||
<optgroup label="<%= l(label) %>"> |
||||
<% filter_ary.sort_by { |f| l(f.label)}.each do |filter| %> |
||||
<% next unless filter.selectable? %> |
||||
<option value="<%= filter.underscore_name %>"><%= l(filter.label) %></option> |
||||
<% end %> |
||||
</optgroup> |
||||
<% end %> |
||||
</select> |
||||
</div> |
||||
|
@ -1,74 +0,0 @@ |
||||
<%# |
||||
This partial requires the following locals: |
||||
f An ActionView::Helpers::FormBuilder |
||||
query A CostQuery object |
||||
%> |
||||
|
||||
<%#TODO: replace me with a drag&drop group_by selector %> |
||||
|
||||
<table style="border-collapse: collapse; border: 0pt none;" id="group_by_table"> |
||||
<tbody> |
||||
<tr> |
||||
<td colspan="2" rowspan="2"> </td> |
||||
<td> </td> |
||||
<td><h3>Columns</h3><td> |
||||
</tr> |
||||
<tr> |
||||
<td align="center" valign="top"> |
||||
<input type="button" class="buttons group_by sort sortUp" onclick="moveOptionUp(this.form.group_by_columns);"/><br /> |
||||
<input type="button" class="buttons group_by sort sortDown" onclick="moveOptionDown(this.form.group_by_columns);"/> |
||||
</td> |
||||
<td> |
||||
<select style="width: 180px;" size="4" name="groups[columns][]" multiple="multiple" id="group_by_columns"> |
||||
</select> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td> |
||||
|
||||
</td> |
||||
<td valign="bottom" style="padding-bottom: 0;"> |
||||
<h3>Rows</h3> |
||||
</td> |
||||
<td> |
||||
|
||||
</td> |
||||
<td align="left" valign="top"> |
||||
<input type="button" class="buttons group_by move moveUp" onclick="moveOptions(this.form.group_by_container, this.form.group_by_columns);"/> |
||||
<input type="button" class="buttons group_by move moveDown" onclick="moveOptions(this.form.group_by_columns, this.form.group_by_container);"/> |
||||
</td> |
||||
</tr> |
||||
<tr> |
||||
<td align="center" valign="top"> |
||||
<input type="button" class="buttons group_by sort sortUp" onclick="moveOptionUp(this.form.group_by_rows);" /><br /> |
||||
<input type="button" class="buttons group_by sort sortDown" onclick="moveOptionDown(this.form.group_by_rows);"/> |
||||
</td> |
||||
<td style="padding-left: 0pt;" valign="top"> |
||||
<select style="width: 180px;" size="4" name="groups[rows][]" multiple="multiple" id="group_by_rows"> |
||||
</select> |
||||
</td> |
||||
<td align="center" valign="top"> |
||||
<input type="button" class="buttons group_by move moveLeft" onclick="moveOptions(this.form.group_by_container, this.form.group_by_rows);"/><br /> |
||||
<input type="button" class="buttons group_by move moveRight" onclick="moveOptions(this.form.group_by_rows, this.form.group_by_container);"/> |
||||
</td> |
||||
<td> |
||||
<select style="width: 180px;" size="9" multiple="multiple" id="group_by_container"> |
||||
<% CostQuery::GroupBy.all_grouped.sort_by { |label, group_by_ary| l(label) }.each do |label, group_by_ary| %> |
||||
<optgroup label="<%= l(label) %>" data-category="<%= label.to_s %>"> |
||||
<% group_by_ary.sort_by { |g| l(g.label)}.each do |group_by| %> |
||||
<% next unless group_by.selectable? %> |
||||
<option value="<%= group_by.underscore_name %>" data-category="<%= label.to_s %>"><%= l(group_by.label) %></option> |
||||
<% end %> |
||||
</optgroup> |
||||
<% end %> |
||||
</select> |
||||
</td> |
||||
</tr></tbody> |
||||
</table> |
||||
|
||||
<%# |
||||
up ↑ |
||||
down ↓ |
||||
left ← |
||||
right → |
||||
%> |
@ -1,34 +0,0 @@ |
||||
<script type="text/javascript"> |
||||
//<![CDATA[ |
||||
|
||||
var set_filters, set_group_bys, restore_query_inputs; |
||||
window.global_prefix = '<%= ActionController::Base.relative_url_root %>'; |
||||
|
||||
set_filters = function () { |
||||
// Activate recent filters on loading |
||||
<% query.filters.select {|f| f.display? }.each do |f| %> |
||||
restore_filter("<%= f.class.underscore_name %>", |
||||
"<%= f.operator.to_s %>"<%= "," if f.values %> |
||||
<%= f.values.to_json if f.values %>); |
||||
<% end %> |
||||
}; |
||||
|
||||
set_group_bys = function () { |
||||
// Activate recent group_bys on loading |
||||
<% query.group_bys.each do |group_by| %> |
||||
<%= "show_group_by_column('#{group_by.class.underscore_name}');" if group_by.column? %> |
||||
<%= "show_group_by_row('#{group_by.class.underscore_name}');" if group_by.row? %> |
||||
<% end %> |
||||
}; |
||||
|
||||
restore_query_inputs = function () { |
||||
// init_group_bys(); |
||||
disable_all_filters(); |
||||
disable_all_group_bys(); |
||||
set_filters(); |
||||
set_group_bys(); |
||||
}; |
||||
|
||||
restore_query_inputs(); |
||||
//]]> |
||||
</script> |
@ -1,46 +0,0 @@ |
||||
<% |
||||
list = @query.collect {|r| r.important_fields }.flatten.uniq |
||||
show_units = list.include? "cost_type_id" |
||||
%> |
||||
|
||||
<table class="report" id="sortable-table"> |
||||
<thead> |
||||
<tr> |
||||
<% list.each do |field| %><th class="right"><%= label_for(field) %></th><% end %> |
||||
<% if show_units %> |
||||
<th class="right"><%= label_for(:field_units) %></th> |
||||
<% end %> |
||||
<th class="right"><%= label_for(:label_sum) %></th> |
||||
</tr> |
||||
</thead> |
||||
<tfoot> |
||||
<tr> |
||||
<th class="result inner" colspan='<%= list.size %>'></th> |
||||
<th class="result right" <%= "colspan='2'" if show_units %>> |
||||
<%= show_result @query %> |
||||
</th> |
||||
</tr> |
||||
</tfoot> |
||||
<tbody> |
||||
<% @query.each do |result| %> |
||||
<tr class='<%= cycle("odd", "even") %>'> |
||||
<td raw-data="<%= raw_field(*result.fields.first) -%>"> |
||||
<%= show_row result %> |
||||
</td> |
||||
<% if show_units %> |
||||
<td raw-data="<%= result.units -%>"><%= show_result result, result.fields[:cost_type_id].to_i %></td> |
||||
<% end %> |
||||
<td raw-data="<%= result.real_costs -%>"><%= show_result result %></td> |
||||
</tr> |
||||
<% if params[:debug] %> |
||||
<tr> |
||||
<td colspan='<%= list.size + 3 %>'> |
||||
<%= result.fields.reject {|k,v| list.include? k.to_sym }.inspect %> |
||||
</td> |
||||
</tr> |
||||
<% end %> |
||||
<% end %> |
||||
</tbody> |
||||
</table> |
||||
|
||||
<%= render :partial => 'sortable_init' %> |
@ -1,13 +0,0 @@ |
||||
<% sort_first_row = sort_first_row || false %> |
||||
|
||||
<script type="text/javascript"> |
||||
//<![CDATA[ |
||||
var table_date_header = $$('#sortable-table th').first(); |
||||
sortables_init(); |
||||
<% if sort_first_row %> |
||||
if (table_date_header.childElements().size() > 0) { |
||||
ts_resortTable(table_date_header.childElements().first(), table_date_header.cellIndex); |
||||
} |
||||
<% end %> |
||||
//]]> |
||||
</script> |
@ -1,5 +0,0 @@ |
||||
<% @available_values.each do |name, id, *args| %> |
||||
<%= render :partial => 'cost_reports/filters/available_value', |
||||
:locals => { :name => name, :id => id, :level => args.first }, |
||||
:layout => !request.xhr? %> |
||||
<% end %> |
@ -1,11 +0,0 @@ |
||||
<%# |
||||
This partial requires the following locals: |
||||
element a Hash containing the following keys: |
||||
- :name => :activate_filter |
||||
- :label => String: A text which is shown to the user as a label for this filter |
||||
- :width => Integer (optional): The width this partial may consume. If not given, a standard width will be applied |
||||
%> |
||||
|
||||
<td width="<%= element[:width] || 150 %>"> |
||||
<label id="label_<%= element[:filter_name]%>"><%= element[:label] %></label> |
||||
</td> |
@ -1,11 +0,0 @@ |
||||
<%# |
||||
This partial requires the following locals: |
||||
name String: The displayed name of the option |
||||
id Integer: The id the option refers to |
||||
level Integer: The indendation level of this option |
||||
%> |
||||
|
||||
<% name_prefix = ((level && level > 0) ? (' ' * 2 * level + '» ') : '') %> |
||||
<option value="<%= id %>"> |
||||
<%= name_prefix + h(name) %> |
||||
</option> |
@ -1,22 +0,0 @@ |
||||
<%# |
||||
This partial requires the following locals: |
||||
element a Hash containing the following keys: |
||||
- :name => :date |
||||
- :filter_name => String: The name of a filter (e.g. activity_id) |
||||
- :hide => Boolean (optional, default = true): whether the content of this partial is initially hidden or not |
||||
%> |
||||
<% name = "values[#{element[:filter_name]}][]" |
||||
id_prefix = "#{element[:filter_name]}_" %> |
||||
|
||||
<td <%= style="display:none" if element[:hide] %>> |
||||
<div style="" id="<%= id_prefix %>arg_1" class="filter_values"> |
||||
<%= text_field_tag "#{name}", "", :size => 10, :class => "select-small", :id => "#{id_prefix}arg_1_val"%> |
||||
<%= calendar_for("#{id_prefix}arg_1_val") %> |
||||
<span id="<%= id_prefix %>arg_2" class="between_tags"> |
||||
<%= |
||||
text_field_tag("#{name}", "", :size => 10, :class => "select-small", :id => "#{id_prefix}arg_2_val") + " " + |
||||
calendar_for("#{id_prefix}arg_2_val") |
||||
%> |
||||
</span> |
||||
</div> |
||||
</td> |
@ -1,21 +0,0 @@ |
||||
<%# |
||||
This partial requires the following locals: |
||||
element a Hash containing the following keys: |
||||
- :name => :heavy_values |
||||
- :filter_name => String: The name of a filter (e.g. activity_id) |
||||
- :hide => Boolean: If you want this to be hidden |
||||
query => the current CostQuery |
||||
%> |
||||
<% filter = query.filters.detect {|f| f.class.underscore_name == element[:filter_name] } |
||||
values_available = !!filter && !!filter.values %> |
||||
|
||||
<td <%= 'style="display:none"' if element[:hide] %> > |
||||
<div style="" id="<%= element[:filter_name] %>_arg_1" class="filter_values"> |
||||
<%= filter.values.map{ |v| filter.class.text_for_id v }.join '<br />' if values_available %> |
||||
<input type="hidden" |
||||
name="values[<%= element[:filter_name] %>][]" |
||||
id="<%= element[:filter_name] %>_arg_1_val" |
||||
<%= 'disabled="disabled"' unless values_available %> |
||||
value="<%= filter.values.to_json if values_available %>"/> |
||||
</div> |
||||
</td> |
@ -1,21 +0,0 @@ |
||||
<%# |
||||
This partial requires the following locals: |
||||
element a Hash containing the following keys: |
||||
- :name => :multi_values |
||||
- :filter_name => String: The name of a filter (e.g. activity_id) |
||||
- :hide => Boolean (optional, default = true): whether the content of this partial is initially hidden or not |
||||
%> |
||||
|
||||
<td <%= style="display:none" if element[:hide] %>> |
||||
<div style="" id="<%= element[:filter_name] %>_arg_1" class="filter_values"> |
||||
<select style="vertical-align: top;" |
||||
name="values[<%= element[:filter_name] %>][]" |
||||
id="<%= element[:filter_name] %>_arg_1_val" |
||||
class="select-small" |
||||
data-loading="ajax" |
||||
multiple="multiple"> <%# multiple will be disabled/enabled later by JavaScript anyhow. We need to specify multiple here because of a IE6-bug. %> |
||||
<%# content will be inserted on filter activation %> |
||||
</select> |
||||
<%= link_to_function image_tag('bullet_toggle_plus.png'), "toggle_multi_select($('#{element[:filter_name]}_arg_1_val'));", :style => "vertical-align: bottom;" %> |
||||
</div> |
||||
</td> |
@ -1,25 +0,0 @@ |
||||
<%# |
||||
This partial requires the following locals: |
||||
element a Hash containing the following keys: |
||||
- :name => :operators |
||||
- :filter_name => String: The name of a filter (e.g. activity_id) |
||||
- :operators => Array<CostQuery::Operator>, operators available for the user |
||||
- :width => Integer (optional): The width this partial may consume. If not given, a standard width will be applied |
||||
- :hide => Boolean (optional, default = true): whether the content of this partial is initially hidden or not |
||||
|
||||
#TODO: javascrpt to update elements depending on which operator is selected |
||||
%> |
||||
|
||||
<td width="<%= element[:width] || 100 %>" <%= style="display:none" if element[:hide] %>> |
||||
<select style="vertical-align: top;" |
||||
onchange="operator_changed('<%= element[:filter_name] %>', this);" |
||||
name="operators[<%= element[:filter_name] %>]" |
||||
id="operators_<%= element[:filter_name] %>" |
||||
class="select-small"> |
||||
<% element[:operators].each do |operator| %> |
||||
<option value="<%= h(operator.to_s) %>" data-arity="<%= operator.arity %>"> |
||||
<%= h(l(operator.label)) %> |
||||
</option> |
||||
<% end %> |
||||
</select> |
||||
</td> |
@ -1,10 +0,0 @@ |
||||
<%# |
||||
This partial requires the following locals: |
||||
element a Hash containing the following keys: |
||||
- :filter_name => String: The name of a filter (e.g. activity_id) |
||||
- :hide => Boolean (optional, default = true): whether the content of this partial is initially hidden or not |
||||
%> |
||||
<td width="25px"> |
||||
<input id= "rm_<%= element[:filter_name] %>" name="fields[]" onclick="remove_filter('<%= element[:filter_name] %>');" |
||||
type="button" value="" class="icon filter_rem icon-filter-rem"/> |
||||
</td> |
@ -1,12 +0,0 @@ |
||||
<%# |
||||
This partial requires the following locals: |
||||
element a Hash containing the following keys: |
||||
- :name => 'text' |
||||
- :text => String: The text that should be displayed |
||||
- :width => Integer (optional): The width this partial may consume. If not given, a standard width will be applied |
||||
- :hide => Boolean (optional, default = true): whether the content of this partial is hidden or not |
||||
%> |
||||
|
||||
<td width="<%= element[:width] || 100 %>" <%= style="display:none" if element[:hide] %>> |
||||
<%= element[:text] || '' %> |
||||
</td> |
@ -1,17 +0,0 @@ |
||||
<%# |
||||
This partial requires the following locals: |
||||
element a Hash containing the following keys: |
||||
- :name => :text_box |
||||
- :filter_name => String: The name of a filter (e.g. activity_id) |
||||
- :size => Integer, the size of the textboxt |
||||
- :hide => Boolean (optional, default = true): whether the content of this partial is initially hidden or not |
||||
%> |
||||
|
||||
<td <%= style="display:none" if element[:hide] %>> |
||||
<div style="" id="<%= element[:filter_name] %>_arg_1" class="filter_values"> |
||||
<%= text_field_tag("values[#{element[:filter_name]}]", "", |
||||
:size => element[:size], |
||||
:class => "select-small", |
||||
:id => "#{element[:filter_name]}_arg_1_val") %> |
||||
</div> |
||||
</td> |
@ -1,76 +0,0 @@ |
||||
<% content_for :header_tags do %> |
||||
<%= javascript_include_tag "select_list_move_optgroup", :plugin => "redmine_reporting" %> |
||||
<%= javascript_include_tag "reporting", :plugin => "redmine_reporting" %> |
||||
<%= javascript_include_tag "sortable", :plugin => "redmine_reporting" %> |
||||
<%= stylesheet_link_tag 'reporting', :plugin => 'redmine_reporting' %> |
||||
<% end %> |
||||
|
||||
<% if @custom_errors.present? %> |
||||
<% @custom_errors.each do |err| %> |
||||
<div class="flash error"><%= err %></div> |
||||
<% end %> |
||||
<% end %> |
||||
|
||||
<h2><%= l(:label_cost_report) %></h2> |
||||
<% html_title( l(:label_cost_report) ) %> |
||||
<%= render :partial => 'saved_queries', :locals => { :queries => CostQuery.all } %> |
||||
|
||||
<% form_for @query, :url => {:controller => 'cost_report', :action => 'new' }, :html => {:id => 'query_form', :method => :post} do |query_form| %> |
||||
<div id="query_form_content"> |
||||
<fieldset id="filters" class="collapsible"> |
||||
<legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend> |
||||
<div><%= render :partial => 'filters', :locals => {:f => query_form, :query => @query} %></div> |
||||
</fieldset> |
||||
|
||||
<fieldset id="group-by" class="collapsible"> |
||||
<legend onclick="toggleFieldset(this);"><%= l(:label_group_by) %></legend> |
||||
<div><%= render :partial => 'group_by', :locals => {:f => query_form, :query => @query} %></div> |
||||
</fieldset> |
||||
|
||||
<%= render :partial => 'restore_query', :locals => {:f => query_form, :query => @query} %> |
||||
<p class="buttons"> |
||||
<%= link_to_remote "<span><em>#{l(:button_apply)}</em></span>", |
||||
{ :url => { :set_filter => 1 }, |
||||
:before => 'selectAllOptions("group_by_rows");selectAllOptions("group_by_columns");', |
||||
:condition => 'Ajax.activeRequestCount === 0', |
||||
:update => "content", |
||||
:with => "Form.serialize('query_form')", |
||||
:eval_scripts => true |
||||
}, :class => 'reporting_button apply' %> |
||||
<%= link_to_function l(:button_clear), "disable_all_filters(); disable_all_group_bys();", :class => 'icon icon-reload' %> |
||||
<% if User.current.allowed_to?(:save_queries, @project, :global => true) && @valid %> |
||||
<%= render :partial => 'save_query' %> |
||||
<% end %> |
||||
</p> |
||||
</div> |
||||
<div class='cost_types'> |
||||
<b><%= l(:label_report) %>:</b> |
||||
<%= delimit( |
||||
available_cost_type_tabs(@cost_types).map do |id, label| |
||||
if id != @unit_id |
||||
link_to_remote label, { |
||||
:url => { :set_filter => 1, :unit => id }, |
||||
:condition => 'Ajax.activeRequestCount === 0', |
||||
:update => "content", |
||||
:before => 'selectAllOptions("group_by_rows");selectAllOptions("group_by_columns");', |
||||
:with => "Form.serialize('query_form')", |
||||
:eval_scripts => true } |
||||
else |
||||
"<b>#{label}</b>" |
||||
end |
||||
end) |
||||
%> |
||||
</div> |
||||
|
||||
<% end %> |
||||
|
||||
<% if @valid and @query.result.count > 0 %> |
||||
<%= render :partial => @table_partial, :locals => {:query => @query, :walker => @query.walker} %> |
||||
<p class="footnote"> |
||||
<%= l(:text_costs_are_rounded_note) %> |
||||
<%= "<br />#{l(:information_restricted_depending_on_permission)}" if !User.current.admin?%> |
||||
</p> |
||||
<%= call_hook(:view_cost_report_table_bottom) %> |
||||
<% else %> |
||||
<p class="nodata"><%= l(:label_no_data) %></p> |
||||
<% end %> |
@ -1,22 +0,0 @@ |
||||
<script type="text/javascript"> |
||||
//<![CDATA[ |
||||
remove_old_sidebar = function() { |
||||
if ($($("sidebar").down()).innerHTML === "<%= l(:label_spent_time) %>") { |
||||
var reporting_link, old_html; |
||||
// The sidebar is showing spent time, remove the links, which are in the second para |
||||
$("sidebar").down().siblings()[1].remove(); |
||||
// Make the hours a link, which are in the first para |
||||
reporting_link = "<%= link_to('PLACEHOLDER', { |
||||
:controller => 'cost_reports', :project_id => project, |
||||
:unit => -1, :set_filter => 1, |
||||
:values => {:project_id => [project.id]}, |
||||
:operators => {:project_id => '='}, |
||||
:fields => [:project_id] }).gsub('"', "'") %>"; |
||||
old_html = $("sidebar").down().siblings().first().innerHTML; |
||||
$("sidebar").down().siblings().first().innerHTML = reporting_link.replace("PLACEHOLDER", old_html) |
||||
} |
||||
} |
||||
|
||||
remove_old_sidebar(); |
||||
//]]> |
||||
</script> |
@ -1,4 +0,0 @@ |
||||
ActionController::Routing::Routes.draw do |map| |
||||
map.connect 'projects/:project_id/cost_reports.:format', :controller => 'cost_reports', :project_id => /.+/, :action => 'index' |
||||
map.connect 'projects/:project_id/cost_reports/:action/:id', :controller => 'cost_reports', :project_id => /.+/ |
||||
end |
@ -1,138 +0,0 @@ |
||||
<style type="text/css" media="screen"> |
||||
table { |
||||
empty-cells: show; |
||||
border-collapse: collapse; |
||||
} |
||||
td { |
||||
min-width: 50px; |
||||
text-align: center; |
||||
font-family: Arial; |
||||
background-color: #eee; |
||||
border: dashed 1px #000; |
||||
} |
||||
</style> |
||||
|
||||
<table cellspacing="0" cellpadding="5"> |
||||
<tr> |
||||
<td></td> |
||||
<td colspan='3'>a</td> |
||||
<td colspan='3'>b</td> |
||||
<td colspan='3'>c</td> |
||||
<td colspan='3'></td> |
||||
</tr> |
||||
<tr> |
||||
<td rowspan='3'>d</td> |
||||
<td>x</td> |
||||
<td>y</td> |
||||
<td>z</td> |
||||
<td>x</td> |
||||
<td>y</td> |
||||
<td>z</td> |
||||
<td>x</td> |
||||
<td>y</td> |
||||
<td>z</td> |
||||
<td>s</td> |
||||
<td rowspan='3'>s</td> |
||||
<td rowspan='6'>s</td> |
||||
</tr> |
||||
<tr> |
||||
<!-- <td rowspan='3'>d</td> --> |
||||
<td>x</td> |
||||
<td>y</td> |
||||
<td>z</td> |
||||
<td>x</td> |
||||
<td>y</td> |
||||
<td>z</td> |
||||
<td>x</td> |
||||
<td>y</td> |
||||
<td>z</td> |
||||
<td>s</td> |
||||
<!-- <td rowspan='3'>s</td> --> |
||||
<!-- <td rowspan='6'>s</td> --> |
||||
</tr> |
||||
<tr> |
||||
<!-- <td rowspan='3'>d</td> --> |
||||
<td>x</td> |
||||
<td>y</td> |
||||
<td>z</td> |
||||
<td>x</td> |
||||
<td>y</td> |
||||
<td>z</td> |
||||
<td>x</td> |
||||
<td>y</td> |
||||
<td>z</td> |
||||
<td>s</td> |
||||
<!-- <td rowspan='3'>s</td> --> |
||||
<!-- <td rowspan='6'>s</td> --> |
||||
</tr> |
||||
<tr> |
||||
<td rowspan='3'>e</td> |
||||
<td>x</td> |
||||
<td>y</td> |
||||
<td>z</td> |
||||
<td>x</td> |
||||
<td>y</td> |
||||
<td>z</td> |
||||
<td>x</td> |
||||
<td>y</td> |
||||
<td>z</td> |
||||
<td>s</td> |
||||
<td rowspan='3'>s</td> |
||||
<!-- <td rowspan='6'>s</td> --> |
||||
</tr> |
||||
<tr> |
||||
<!-- <td rowspan='3'>e</td> --> |
||||
<td>x</td> |
||||
<td>y</td> |
||||
<td>z</td> |
||||
<td>x</td> |
||||
<td>y</td> |
||||
<td>z</td> |
||||
<td>x</td> |
||||
<td>y</td> |
||||
<td>z</td> |
||||
<td>s</td> |
||||
<!-- <td rowspan='3'>s</td> --> |
||||
<!-- <td rowspan='6'>s</td> --> |
||||
</tr> |
||||
<tr> |
||||
<!-- <td rowspan='3'>e</td> --> |
||||
<td>x</td> |
||||
<td>y</td> |
||||
<td>z</td> |
||||
<td>x</td> |
||||
<td>y</td> |
||||
<td>z</td> |
||||
<td>x</td> |
||||
<td>y</td> |
||||
<td>z</td> |
||||
<td>s</td> |
||||
<!-- <td rowspan='3'>s</td> --> |
||||
<!-- <td rowspan='6'>s</td> --> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td>s</td> |
||||
<td>s</td> |
||||
<td>s</td> |
||||
<td>s</td> |
||||
<td>s</td> |
||||
<td>s</td> |
||||
<td>s</td> |
||||
<td>s</td> |
||||
<td>s</td> |
||||
<td colspan='3'></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td colspan='3'>s</td> |
||||
<td colspan='3'>s</td> |
||||
<td colspan='3'>s</td> |
||||
<td colspan='3'></td> |
||||
</tr> |
||||
<tr> |
||||
<td></td> |
||||
<td colspan='9'>s</td> |
||||
<td colspan='3'></td> |
||||
</tr> |
||||
</table> |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue