parent
d08a441f46
commit
8d348755a2
@ -1,7 +1,7 @@ |
||||
<% content_for :header_tags do %> |
||||
<%= stylesheet_link_tag 'excel_export', :plugin => 'redmine_additional_formats' %> |
||||
<%= stylesheet_link_tag 'excel_export', :plugin => 'xls_export' %> |
||||
<% end %> |
||||
<% if User.current.allowed_to? :export_issues, @project, :global => @project.nil? %> |
||||
<% if User.current.allowed_to? :export_work_packages, @project, :global => @project.nil? %> |
||||
<%= link_to(l(:export_to_excel), { :controller => "cost_reports" , :action => :index, |
||||
:format => 'xls', :project_id => @project }, :class => "icon icon-export-xls-descr") %> |
||||
<% end %> |
@ -0,0 +1,7 @@ |
||||
# Hooks to attach to the Redmine Issues. |
||||
module XlsReport |
||||
class CostReportHook < Redmine::Hook::ViewListener |
||||
# Renders the Cost Object subject and basic costs information |
||||
render_on :view_cost_report_table_bottom, :partial => 'hooks/xls_report/view_cost_report_other_formats' |
||||
end |
||||
end |
@ -0,0 +1,49 @@ |
||||
module OpenProject::XlsExport::Patches |
||||
module CostReportsControllerPatch |
||||
def self.included(base) # :nodoc: |
||||
base.prepend InstanceMethods |
||||
end |
||||
|
||||
module InstanceMethods |
||||
def excel_export? |
||||
(params["action"] == "index" or params[:action] == "all") && params["format"] == "xls" |
||||
end |
||||
|
||||
def ensure_project_scope? |
||||
!excel_export? && super |
||||
end |
||||
|
||||
# If the index action is called, hook the xls format into the cost report |
||||
def respond_to |
||||
if excel_export? |
||||
super do |format| |
||||
yield format |
||||
format.xls do |
||||
report = report_to_xls |
||||
time = DateTime.now.strftime('%d-%m-%Y-T-%H-%M-%S') |
||||
send_data(report, type: :xls, filename: "export-#{time}.xls") if report |
||||
end |
||||
end |
||||
else |
||||
super |
||||
end |
||||
end |
||||
|
||||
# Build an xls file from a cost report. |
||||
def report_to_xls |
||||
options = { query: @query, project: @project, cost_types: @cost_types } |
||||
|
||||
sb = if @query.group_bys.empty? |
||||
::OpenProject::XlsExport::XlsViews::CostEntryTable.generate(options) |
||||
elsif @query.depth_of(:column) + @query.depth_of(:row) == 1 |
||||
::OpenProject::XlsExport::XlsViews::SimpleCostReportTable.generate(options) |
||||
else |
||||
::OpenProject::XlsExport::XlsViews::CostReportTable.generate(options) |
||||
end |
||||
sb.xls |
||||
end |
||||
end |
||||
end |
||||
end |
||||
|
||||
CostReportsController.send(:include, OpenProject::XlsExport::Patches::CostReportsControllerPatch) |
@ -0,0 +1,65 @@ |
||||
class OpenProject::XlsExport::XlsViews |
||||
include Redmine::I18n |
||||
include ActionView::Helpers::NumberHelper |
||||
include ApplicationHelper |
||||
include ReportingHelper |
||||
include OpenProject::StaticRouting::UrlHelpers |
||||
|
||||
attr_accessor :spreadsheet, :query, :cost_type, :unit_id, :options |
||||
|
||||
# Overwrite a few mappings. |
||||
def field_representation_map(key, value) |
||||
case key.to_sym |
||||
when :units then value.to_i |
||||
when :spent_on then value |
||||
when :activity_id then mapped value, Enumeration, I18n.t(:caption_material_costs) |
||||
when :project_id then (I18n.t(:label_none) if value.to_i.zero?) or Project.find(value.to_i).name |
||||
when :user_id, :assigned_to_id then (I18n.t(:label_none) if value.to_i.zero?) or User.find(value.to_i).name |
||||
when :work_package_id |
||||
return I18n.t(:label_none) if value.to_i.zero? |
||||
|
||||
work_package = WorkPackage.find(value.to_i) |
||||
"#{work_package.project.name + ' - ' if @project}#{work_package.type} ##{work_package.id}: #{work_package.subject}" |
||||
else super(key, value) |
||||
end |
||||
end |
||||
|
||||
def show_result(row, unit_id = @unit_id, as_text = false) |
||||
return super(row, unit_id) if as_text |
||||
|
||||
case unit_id |
||||
when 0 then row.real_costs || '-' |
||||
else row.units |
||||
end |
||||
end |
||||
|
||||
def cost_type_unit_label(cost_type_id, cost_type_inst = nil, plural = true) |
||||
case cost_type_id |
||||
when -1 then l_hours(2).split[1..-1].join(" ") # get the plural for hours |
||||
when 0 then Setting.plugin_openproject_costs['costs_currency'] |
||||
else cost_type_label(cost_type_id, cost_type_inst, plural) |
||||
end |
||||
end |
||||
|
||||
def serialize_query_without_hidden(query) |
||||
serialized_query = query.serialize |
||||
serialized_query[:filters] = serialized_query[:filters].reject do |_, options| |
||||
options[:display] == false |
||||
end |
||||
serialized_query |
||||
end |
||||
|
||||
def self.generate(opts) |
||||
new.tap do |obj| |
||||
obj.query = opts[:query] |
||||
obj.cost_type = opts[:cost_type] |
||||
obj.unit_id = opts[:unit_id] |
||||
obj.options = opts |
||||
end.generate |
||||
end |
||||
end |
||||
|
||||
# Load subclasses |
||||
require_relative './xls_views/cost_entry_table.xls' |
||||
require_relative './xls_views/simple_cost_report_table.xls' |
||||
require_relative './xls_views/cost_report_table.xls' |
@ -0,0 +1,57 @@ |
||||
class OpenProject::XlsExport::XlsViews::CostEntryTable < OpenProject::XlsExport::XlsViews |
||||
def generate |
||||
spreadsheet = OpenProject::XlsExport::SpreadsheetBuilder.new(I18n.t(:label_money)) |
||||
default_query = serialize_query_without_hidden(@query) |
||||
|
||||
available_cost_type_tabs(options[:cost_types]).each_with_index do |ary, idx| |
||||
@query = CostQuery.deserialize(default_query) |
||||
@cost_type = nil |
||||
@unit_id = ary.first |
||||
name = ary.last |
||||
|
||||
if @unit_id != 0 |
||||
@query.filter :cost_type_id, operator: '=', value: @unit_id.to_s |
||||
@cost_type = CostType.find(unit_id) if unit_id.positive? |
||||
end |
||||
|
||||
spreadsheet.worksheet(idx, name) |
||||
build_spreadsheet(spreadsheet) |
||||
end |
||||
spreadsheet |
||||
end |
||||
|
||||
def build_spreadsheet(spreadsheet) |
||||
spreadsheet.add_title("#{@project.name + " >> " if @project}#{I18n.t(:cost_reports_title)} (#{format_date(Date.today)})") |
||||
|
||||
list = %i[spent_on user_id activity_id issue_id comments project_id] |
||||
headers = list.collect { |field| label_for(field) } |
||||
headers << I18n.t(:units) |
||||
headers << I18n.t(:field_cost_type) |
||||
headers << I18n.t(:field_costs) |
||||
spreadsheet.add_headers(headers) |
||||
|
||||
spreadsheet.add_format_option_to_column(headers.length - 1, number_format: number_to_currency(0.00)) |
||||
spreadsheet.add_format_option_to_column(headers.length - 2, number_format: "0.0") |
||||
|
||||
query.each_direct_result do |result| |
||||
row = list.collect { |field| show_field field, result.fields[field.to_s] } |
||||
current_cost_type_id = result.fields['cost_type_id'].to_i |
||||
|
||||
row << show_result(result, current_cost_type_id) # units |
||||
row << cost_type_label(current_cost_type_id, @cost_type) # cost type |
||||
row << show_result(result, 0) # costs/currency |
||||
|
||||
spreadsheet.add_row(row) |
||||
end |
||||
|
||||
footer = [''] * list.size |
||||
footer += if show_result(query, 0) != show_result(query) |
||||
[show_result(query), '', show_result(query, 0)] |
||||
else |
||||
['', '', show_result(query)] |
||||
end |
||||
spreadsheet.add_row(footer) # footer |
||||
|
||||
spreadsheet |
||||
end |
||||
end |
@ -0,0 +1,134 @@ |
||||
class OpenProject::XlsExport::XlsViews::CostReportTable < OpenProject::XlsExport::XlsViews |
||||
def final_row(final_row, cells) |
||||
row = [show_row(final_row)] |
||||
row += cells |
||||
row << show_result(final_row) |
||||
end |
||||
|
||||
def row(row, subrows) |
||||
unless row.fields.empty? |
||||
# Here we get the border setting, vertically. The rowspan #{subrows.size} need be |
||||
# converted to a proper Excel bordering |
||||
subrows = subrows.inject([]) do |array, subrow| |
||||
if subrow.flatten == subrow |
||||
array << subrow |
||||
else |
||||
array += subrow.collect(&:flatten) |
||||
end |
||||
end |
||||
subrows.each_with_index do |subrow, idx| |
||||
if idx.to_i.zero? |
||||
subrow.insert(0, show_row(row)) |
||||
subrow << show_result(row) |
||||
else |
||||
subrow.unshift("") |
||||
subrow << "" |
||||
end |
||||
end |
||||
end |
||||
subrows |
||||
end |
||||
|
||||
def cell(result) |
||||
show_result result |
||||
end |
||||
|
||||
def headers(list, first, first_in_col, last_in_col) |
||||
if first_in_col # Open a new header row |
||||
@header = [""] * query.depth_of(:row) # TODO: needs borders: rowspan=query.depth_of(:column) |
||||
end |
||||
|
||||
list.each do |column| |
||||
@header << show_row(column) |
||||
@header += [""] * (column.final_number(:column) - 1).abs |
||||
end |
||||
|
||||
if last_in_col # Finish this header row |
||||
@header += [""] * query.depth_of(:row) # TODO: needs borders: rowspan=query.depth_of(:column) |
||||
@headers << @header |
||||
end |
||||
end |
||||
|
||||
def footers(list, first, first_in_col, last_in_col) |
||||
if first_in_col # Open a new footer row |
||||
@footer = [""] * query.depth_of(:row) # TODO: needs borders: rowspan=query.depth_of(:column) |
||||
end |
||||
|
||||
list.each do |column| |
||||
@footer << show_result(column) |
||||
@footer += [""] * (column.final_number(:column) - 1).abs |
||||
end |
||||
|
||||
if last_in_col # Finish this footer row |
||||
if first |
||||
@footer << show_result(query) |
||||
@footer += [""] * (query.depth_of(:row) - 1).abs # TODO: add rowspan=query.depth_of(:column) |
||||
else |
||||
@footer += [""] * query.depth_of(:row) # TODO: add rowspan=query.depth_of(:column) |
||||
end |
||||
@footers << @footer |
||||
end |
||||
end |
||||
|
||||
def body(*line) |
||||
@rows += line.flatten |
||||
end |
||||
|
||||
def generate |
||||
@spreadsheet ||= OpenProject::XlsExport::SpreadsheetBuilder.new(I18n.t(:label_money)) |
||||
default_query = serialize_query_without_hidden(@query) |
||||
|
||||
available_cost_type_tabs(options[:cost_types]).each_with_index do |ary, idx| |
||||
@query = CostQuery.deserialize(default_query) |
||||
@unit_id = ary.first |
||||
name = ary.last |
||||
|
||||
if @unit_id != 0 |
||||
@query.filter :cost_type_id, operator: '=', value: @unit_id.to_s |
||||
@cost_type = CostType.find(unit_id) if unit_id.positive? |
||||
end |
||||
|
||||
spreadsheet.worksheet(idx, name) |
||||
run_walker |
||||
build_spreadsheet unless (@headers + @footers).empty? |
||||
end |
||||
spreadsheet |
||||
end |
||||
|
||||
def run_walker |
||||
walker = query.walker |
||||
walker.for_final_row &method(:final_row) |
||||
walker.for_row &method(:row) |
||||
walker.for_empty_cell { "" } |
||||
walker.for_cell &method(:cell) |
||||
|
||||
@headers = [] |
||||
@header = [] |
||||
walker.headers &method(:headers) |
||||
|
||||
@footers = [] |
||||
@footer = [] |
||||
walker.reverse_headers &method(:footers) |
||||
|
||||
@rows = [] |
||||
walker.body &method(:body) |
||||
end |
||||
|
||||
def build_spreadsheet |
||||
spreadsheet.add_title("#{@project.name + ' >> ' if @project}#{I18n.t(:cost_reports_title)} (#{format_date(Date.today)})") |
||||
spreadsheet.add_headers [label] |
||||
row_length = @headers.first.length |
||||
@headers.each { |head| spreadsheet.add_headers(head, spreadsheet.current_row) } |
||||
@rows.in_groups_of(row_length).each { |body| spreadsheet.add_row(body) } |
||||
@footers.each { |foot| spreadsheet.add_headers(foot, spreadsheet.current_row) } |
||||
spreadsheet |
||||
end |
||||
|
||||
def label |
||||
"#{I18n.t(:caption_cost_type)}: " + case unit_id |
||||
when -1 then I18n.t(:field_hours) |
||||
when 0 then "EUR" |
||||
else cost_type.unit_plural |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,52 @@ |
||||
class OpenProject::XlsExport::XlsViews::SimpleCostReportTable < OpenProject::XlsExport::XlsViews |
||||
def generate |
||||
spreadsheet = OpenProject::XlsExport::SpreadsheetBuilder.new(I18n.t(:label_money)) |
||||
default_query = serialize_query_without_hidden(@query) |
||||
|
||||
available_cost_type_tabs(options[:cost_types]).each_with_index do |ary, idx| |
||||
@query = CostQuery.deserialize(default_query) |
||||
@cost_type = nil |
||||
@unit_id = ary.first |
||||
name = ary.last |
||||
|
||||
if @unit_id != 0 |
||||
@query.filter :cost_type_id, operator: '=', value: @unit_id.to_s |
||||
@cost_type = CostType.find(unit_id) if unit_id.positive? |
||||
end |
||||
|
||||
spreadsheet.worksheet(idx, name) |
||||
build_spreadsheet(spreadsheet) |
||||
end |
||||
spreadsheet |
||||
end |
||||
|
||||
def build_spreadsheet(spreadsheet) |
||||
spreadsheet.add_title("#{@project.name + ' >> ' if @project}#{I18n.t(:cost_reports_title)} (#{format_date(Date.today)})") |
||||
|
||||
list = query.collect { |r| r.important_fields }.flatten.uniq |
||||
show_units = list.include? "cost_type_id" |
||||
headers = list.collect { |field| label_for(field) } |
||||
headers << label_for(:field_units) << "" if show_units |
||||
headers << label_for(:label_sum) << "" |
||||
spreadsheet.add_headers(headers) |
||||
|
||||
column = 0 |
||||
spreadsheet.add_format_option_to_column(headers.length - (column += 1), number_format: number_to_currency(0.00)) |
||||
spreadsheet.add_format_option_to_column(headers.length - (column += 1), number_format: "0.0 ?") if show_units |
||||
|
||||
query.each do |result| |
||||
current_cost_type_id = result.fields[:cost_type_id].to_i |
||||
row = [show_row(result)] |
||||
row << show_result(result, current_cost_type_id) if show_units |
||||
row << cost_type_unit_label(current_cost_type_id, @cost_type) if show_units |
||||
row << show_result(result) |
||||
row << cost_type_unit_label(@unit_id, @cost_type) |
||||
spreadsheet.add_row(row) |
||||
end |
||||
|
||||
footer = [''] * list.size |
||||
footer += ['', ''] if show_units |
||||
spreadsheet.add_row(footer + [show_result(query), cost_type_unit_label(@unit_id, @cost_type)]) # footer |
||||
spreadsheet |
||||
end |
||||
end |
Loading…
Reference in new issue