From 04d829c7e94b0a9b4dc376bf0382e3ff28867763 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Mon, 24 Jun 2019 09:28:34 +0200 Subject: [PATCH] Fixes #2594 - Wrong date format in Excel Exports of reporting download. --- app/controllers/reports_controller.rb | 68 +++++++++++++---- .../time_accountings_controller.rb | 74 ++++++++++++------- 2 files changed, 101 insertions(+), 41 deletions(-) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 23b6612a7..a3d200c78 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -61,6 +61,8 @@ class ReportsController < ApplicationController # get data result = {} + content = nil + filename = nil get_params[:metric][:backend].each do |backend| next if params[:downloadBackendSelected] != backend[:name] @@ -86,17 +88,21 @@ class ReportsController < ApplicationController result = { count: 0, ticket_ids: [] } if result.nil? # generate sheet - next if !params[:sheet] - - content = sheet(get_params[:profile], backend[:display], result) + if params[:sheet] + content = sheet(get_params[:profile], backend[:display], result) + filename = "tickets-#{get_params[:profile].name}-#{backend[:display]}.xls" + end + break + end + if content send_data( content, - filename: "tickets-#{get_params[:profile].name}-#{backend[:display]}.xls", + filename: filename, type: 'application/vnd.ms-excel', disposition: 'attachment' ) + return end - return if params[:sheet] render json: result end @@ -172,24 +178,29 @@ class ReportsController < ApplicationController def sheet(profile, title, result) + params[:timezone] ||= Setting.get('timezone_default') + # Create a new Excel workbook temp_file = Tempfile.new('time_tracking.xls') workbook = WriteExcel.new(temp_file) # Add a worksheet worksheet = workbook.add_worksheet + worksheet.set_row(0, 18) worksheet.set_column(0, 0, 10) worksheet.set_column(1, 1, 34) worksheet.set_column(2, 2, 10) worksheet.set_column(3, 3, 10) worksheet.set_column(4, 8, 20) + worksheet.set_column(11, 0, 20) + worksheet.set_column(12, 0, 20) + worksheet.set_column(13, 0, 20) # Add and define a format format = workbook.add_format format.set_bold format.set_size(14) format.set_color('black') - worksheet.set_row(0, 0, 6) # Write a formatted and unformatted string, row and column notation. worksheet.write_string(0, 0, "Tickets: #{profile.name} (#{title})", format) @@ -199,6 +210,14 @@ class ReportsController < ApplicationController format_header.set_bg_color('gray') format_header.set_color('white') + format_time = workbook.add_format(num_format: 'yyyy-mm-dd hh:mm:ss') + format_date = workbook.add_format(num_format: 'yyyy-mm-dd') + + format_footer = workbook.add_format + format_footer.set_italic + format_footer.set_color('gray') + format_footer.set_size(8) + worksheet.write_string(2, 0, '#', format_header) worksheet.write_string(2, 1, 'Title', format_header) worksheet.write_string(2, 2, 'State', format_header) @@ -213,6 +232,7 @@ class ReportsController < ApplicationController worksheet.write_string(2, 11, 'Created at', format_header) worksheet.write_string(2, 12, 'Updated at', format_header) worksheet.write_string(2, 13, 'Closed at', format_header) + # ObjectManager attributes header_column = 14 # needs to be skipped @@ -223,6 +243,7 @@ class ReportsController < ApplicationController .pluck(:name, :display, :data_type, :data_option) .map { |name, display, data_type, data_option| { name: name, display: display, data_type: data_type, data_option: data_option } } objects.each do |object| + worksheet.set_column(header_column, 0, 16) worksheet.write_string(2, header_column, object[:display].capitalize, format_header) header_column += 1 end @@ -243,9 +264,11 @@ class ReportsController < ApplicationController worksheet.write_string(row, 8, ticket.create_article_type.name) worksheet.write_string(row, 9, ticket.create_article_sender.name) worksheet.write_string(row, 10, ticket.tag_list.join(',')) - worksheet.write_date_time(row, 11, ticket.created_at.to_time.iso8601) - worksheet.write_date_time(row, 12, ticket.updated_at.to_time.iso8601) - worksheet.write_date_time(row, 13, ticket.close_at.to_time.iso8601) if ticket.close_at.present? + + worksheet.write_date_time(row, 11, time_in_localtime_for_excel(ticket.created_at, params[:timezone]), format_time) + worksheet.write_date_time(row, 12, time_in_localtime_for_excel(ticket.updated_at, params[:timezone]), format_time) + worksheet.write_date_time(row, 13, time_in_localtime_for_excel(ticket.close_at, params[:timezone]), format_time) if ticket.close_at.present? + # Object Manager attributes column = 14 # We already queried ObjectManager::Attributes, so we just use objects @@ -253,13 +276,20 @@ class ReportsController < ApplicationController key = object[:name] case object[:data_type] when 'boolean', 'select' - value = object[:data_option]['options'][ticket.send(key.to_sym)] + value = ticket.send(key.to_sym) + if object[:data_option] && object[:data_option]['options'] && object[:data_option]['options'][ticket.send(key.to_sym)] + value = object[:data_option]['options'][ticket.send(key.to_sym)] + end worksheet.write_string(row, column, value) - when 'datetime', 'date' - worksheet.write_date_time(row, column, ticket.send(key.to_sym).to_time.iso8601) if ticket.send(key.to_sym).present? + when 'datetime' + worksheet.write_date_time(row, column, time_in_localtime_for_excel(ticket.send(key.to_sym), params[:timezone]), format_time) if ticket.send(key.to_sym).present? + when 'date' + worksheet.write_date_time(row, column, ticket.send(key.to_sym).to_s, format_date) if ticket.send(key.to_sym).present? + when 'integer' + worksheet.write_number(row, column, ticket.send(key.to_sym)) else # for text, integer and tree select - worksheet.write_string(row, column, ticket.send(key.to_sym)) + worksheet.write_string(row, column, ticket.send(key.to_sym).to_s) end column += 1 end @@ -268,6 +298,9 @@ class ReportsController < ApplicationController end end + row += 2 + worksheet.write_string(row, 0, "#{Translation.translate(current_user.locale, 'Timezone')}: #{params[:timezone]}", format_footer) + workbook.close # read file again @@ -277,4 +310,13 @@ class ReportsController < ApplicationController contents end + def time_in_localtime_for_excel(time, timezone) + return if time.blank? + + if timezone.present? + offset = time.in_time_zone(timezone).utc_offset + time -= offset + end + time.utc.iso8601.to_s.sub(/Z$/, '') + end end diff --git a/app/controllers/time_accountings_controller.rb b/app/controllers/time_accountings_controller.rb index 45ee8d163..4f89596c6 100644 --- a/app/controllers/time_accountings_controller.rb +++ b/app/controllers/time_accountings_controller.rb @@ -96,15 +96,15 @@ class TimeAccountingsController < ApplicationController }, { name: 'Created at', - width: 10, + width: 18, }, { name: 'Closed at', - width: 10, + width: 18, }, { name: 'Close Escalation At', - width: 10, + width: 18, }, { name: 'Close In Min', @@ -116,11 +116,11 @@ class TimeAccountingsController < ApplicationController }, { name: 'First Response At', - width: 10, + width: 18, }, { name: 'First Response Escalation At', - width: 10, + width: 18, }, { name: 'First Response In Min', @@ -132,7 +132,7 @@ class TimeAccountingsController < ApplicationController }, { name: 'Update Escalation At', - width: 10, + width: 18, }, { name: 'Update In Min', @@ -144,15 +144,15 @@ class TimeAccountingsController < ApplicationController }, { name: 'Last Contact At', - width: 10, + width: 18, }, { name: 'Last Contact Agent At', - width: 10, + width: 18, }, { name: 'Last Contact Customer At', - width: 10, + width: 18, }, { name: 'Article Count', @@ -160,7 +160,7 @@ class TimeAccountingsController < ApplicationController }, { name: 'Escalation At', - width: 10, + width: 18, }, ] objects = ObjectManager::Attribute.where(editable: true, @@ -170,18 +170,11 @@ class TimeAccountingsController < ApplicationController .pluck(:name, :display, :data_type, :data_option) .map { |name, display, data_type, data_option| { name: name, display: display, data_type: data_type, data_option: data_option } } objects.each do |object| - header.push({ name: object[:display], width: 10 }) + header.push({ name: object[:display], width: 18 }) end result = [] results.each do |row| - row[:ticket].each_key do |field| - next if row[:ticket][field].blank? - next if !row[:ticket][field].is_a?(ActiveSupport::TimeWithZone) - - row[:ticket][field] = row[:ticket][field].iso8601 - end - result_row = [ row[:ticket]['number'], row[:ticket]['title'], @@ -209,19 +202,17 @@ class TimeAccountingsController < ApplicationController row[:ticket]['escalation_at'], ] - # needed to get human values for boolean/select rather than true/false values - ticket = Ticket.lookup(id: row[:ticket]['id']) - # Object Manager attributes # We already queried ObjectManager::Attributes, so we just use objects objects.each do |object| key = object[:name] case object[:data_type] when 'boolean', 'select' - value = object[:data_option]['options'][ticket.send(key.to_sym)] + value = row[:ticket][key] + if object[:data_option] && object[:data_option]['options'] && object[:data_option]['options'][row[:ticket][key]] + value = object[:data_option]['options'][row[:ticket][key]] + end value.present? ? result_row.push(value) : result_row.push('') - when 'datetime', 'date' - row[:ticket][key].present? ? result_row.push(row[:ticket][key].to_time.iso8601) : result_row.push('') else # for text, integer and tree select row[:ticket][key].present? ? result_row.push(row[:ticket][key]) : result_row.push('') @@ -400,6 +391,8 @@ class TimeAccountingsController < ApplicationController def sheet(title, header, result) + params[:timezone] ||= Setting.get('timezone_default') + # Create a new Excel workbook temp_file = Tempfile.new('time_tracking.xls') workbook = WriteExcel.new(temp_file) @@ -412,7 +405,16 @@ class TimeAccountingsController < ApplicationController format.set_bold format.set_size(14) format.set_color('black') - worksheet.set_row(0, 0, header.count) + + format_time = workbook.add_format(num_format: 'yyyy-mm-dd hh:mm:ss') + format_date = workbook.add_format(num_format: 'yyyy-mm-dd') + + format_footer = workbook.add_format + format_footer.set_italic + format_footer.set_color('gray') + format_footer.set_size(8) + + worksheet.set_row(0, 18, header.count) # Write a formatted and unformatted string, row and column notation. worksheet.write_string(0, 0, title, format) @@ -435,15 +437,22 @@ class TimeAccountingsController < ApplicationController row_count += 1 row_item_count = 0 row.each do |item| - if item.acts_like?(:date) - worksheet.write_date_time(row_count, row_item_count, item.to_time.iso8601) + if item.acts_like?(:time) + worksheet.write_date_time(row_count, row_item_count, time_in_localtime_for_excel(item, params[:timezone]), format_time) if item.present? + elsif item.acts_like?(:date) + worksheet.write_date_time(row_count, row_item_count, item.to_s, format_date) if item.present? + elsif item.is_a?(Integer) || item.is_a?(Float) + worksheet.write_number(row_count, row_item_count, item) else - worksheet.write_string(row_count, row_item_count, item) + worksheet.write_string(row_count, row_item_count, item.to_s) end row_item_count += 1 end end + row_count += 2 + worksheet.write_string(row_count, 0, "#{Translation.translate(current_user.locale, 'Timezone')}: #{params[:timezone]}", format_footer) + workbook.close # read file again @@ -453,4 +462,13 @@ class TimeAccountingsController < ApplicationController contents end + def time_in_localtime_for_excel(time, timezone) + return if time.blank? + + if timezone.present? + offset = time.in_time_zone(timezone).utc_offset + time -= offset + end + time.utc.iso8601.to_s.sub(/Z$/, '') + end end