Fixes #2699 - Excel export of report or time accounting drops additional fields in row if integer field contain null/nil/undefined
.
This commit is contained in:
parent
2e3b7e07ad
commit
ba2afc0c16
7 changed files with 391 additions and 454 deletions
|
@ -61,7 +61,7 @@ class ReportsController < ApplicationController
|
|||
|
||||
# get data
|
||||
result = {}
|
||||
content = nil
|
||||
excel = nil
|
||||
filename = nil
|
||||
get_params[:metric][:backend].each do |backend|
|
||||
next if params[:downloadBackendSelected] != backend[:name]
|
||||
|
@ -89,14 +89,19 @@ class ReportsController < ApplicationController
|
|||
|
||||
# generate sheet
|
||||
if params[:sheet]
|
||||
content = sheet(get_params[:profile], backend[:display], result)
|
||||
excel = ExcelSheet::Ticket.new(
|
||||
title: "#{get_params[:profile].name} (#{backend[:display]})",
|
||||
ticket_ids: result[:ticket_ids],
|
||||
timezone: params[:timezone],
|
||||
locale: current_user.locale,
|
||||
)
|
||||
filename = "tickets-#{get_params[:profile].name}-#{backend[:display]}.xls"
|
||||
end
|
||||
break
|
||||
end
|
||||
if content
|
||||
if excel
|
||||
send_data(
|
||||
content,
|
||||
excel.content,
|
||||
filename: filename,
|
||||
type: 'application/vnd.ms-excel',
|
||||
disposition: 'attachment'
|
||||
|
@ -176,148 +181,4 @@ class ReportsController < ApplicationController
|
|||
}
|
||||
end
|
||||
|
||||
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')
|
||||
|
||||
# Write a formatted and unformatted string, row and column notation.
|
||||
worksheet.write_string(0, 0, "Tickets: #{profile.name} (#{title})", format)
|
||||
|
||||
format_header = workbook.add_format
|
||||
format_header.set_italic
|
||||
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)
|
||||
worksheet.write_string(2, 3, 'Priority', format_header)
|
||||
worksheet.write_string(2, 4, 'Group', format_header)
|
||||
worksheet.write_string(2, 5, 'Owner', format_header)
|
||||
worksheet.write_string(2, 6, 'Customer', format_header)
|
||||
worksheet.write_string(2, 7, 'Organization', format_header)
|
||||
worksheet.write_string(2, 8, 'Create Channel', format_header)
|
||||
worksheet.write_string(2, 9, 'Sender', format_header)
|
||||
worksheet.write_string(2, 10, 'Tags', format_header)
|
||||
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
|
||||
objects = ObjectManager::Attribute.where(editable: true,
|
||||
active: true,
|
||||
to_create: false,
|
||||
object_lookup_id: ObjectLookup.lookup(name: 'Ticket').id)
|
||||
.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
|
||||
|
||||
row = 2
|
||||
result[:ticket_ids].each do |ticket_id|
|
||||
|
||||
ticket = Ticket.lookup(id: ticket_id)
|
||||
row += 1
|
||||
worksheet.write_string(row, 0, ticket.number)
|
||||
worksheet.write_string(row, 1, ticket.title)
|
||||
worksheet.write_string(row, 2, ticket.state.name)
|
||||
worksheet.write_string(row, 3, ticket.priority.name)
|
||||
worksheet.write_string(row, 4, ticket.group.name)
|
||||
worksheet.write_string(row, 5, ticket.owner.fullname)
|
||||
worksheet.write_string(row, 6, ticket.customer.fullname)
|
||||
worksheet.write_string(row, 7, ticket.try(:organization).try(:name))
|
||||
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, 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
|
||||
objects.each do |object|
|
||||
key = object[:name]
|
||||
case object[:data_type]
|
||||
when 'boolean', 'select'
|
||||
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'
|
||||
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).to_s)
|
||||
end
|
||||
column += 1
|
||||
end
|
||||
rescue => e
|
||||
Rails.logger.error "SKIP: #{e.message}"
|
||||
|
||||
end
|
||||
|
||||
row += 2
|
||||
worksheet.write_string(row, 0, "#{Translation.translate(current_user.locale, 'Timezone')}: #{params[:timezone]}", format_footer)
|
||||
|
||||
workbook.close
|
||||
|
||||
# read file again
|
||||
file = File.new(temp_file, 'r')
|
||||
contents = file.read
|
||||
file.close
|
||||
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
|
||||
local_time = time.utc.iso8601.to_s.sub(/Z$/, '')
|
||||
local_time.sub(/T/, ' ')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,6 +21,8 @@ class TimeAccountingsController < ApplicationController
|
|||
end
|
||||
time_unit[record[0]][:time_unit] += record[1]
|
||||
end
|
||||
|
||||
if !params[:download]
|
||||
customers = {}
|
||||
organizations = {}
|
||||
agents = {}
|
||||
|
@ -62,175 +64,36 @@ class TimeAccountingsController < ApplicationController
|
|||
}
|
||||
results.push result
|
||||
end
|
||||
|
||||
if params[:download]
|
||||
header = [
|
||||
{
|
||||
name: 'Ticket#',
|
||||
width: 15,
|
||||
},
|
||||
{
|
||||
name: 'Title',
|
||||
width: 30,
|
||||
},
|
||||
{
|
||||
name: 'Customer',
|
||||
width: 20,
|
||||
},
|
||||
{
|
||||
name: 'Organization',
|
||||
width: 20,
|
||||
},
|
||||
{
|
||||
name: 'Agent',
|
||||
width: 20,
|
||||
},
|
||||
{
|
||||
name: 'Time Units',
|
||||
width: 10,
|
||||
},
|
||||
{
|
||||
name: 'Time Units Total',
|
||||
width: 10,
|
||||
},
|
||||
{
|
||||
name: 'Created at',
|
||||
width: 18,
|
||||
},
|
||||
{
|
||||
name: 'Closed at',
|
||||
width: 18,
|
||||
},
|
||||
{
|
||||
name: 'Close Escalation At',
|
||||
width: 18,
|
||||
},
|
||||
{
|
||||
name: 'Close In Min',
|
||||
width: 10,
|
||||
},
|
||||
{
|
||||
name: 'Close Diff In Min',
|
||||
width: 10,
|
||||
},
|
||||
{
|
||||
name: 'First Response At',
|
||||
width: 18,
|
||||
},
|
||||
{
|
||||
name: 'First Response Escalation At',
|
||||
width: 18,
|
||||
},
|
||||
{
|
||||
name: 'First Response In Min',
|
||||
width: 10,
|
||||
},
|
||||
{
|
||||
name: 'First Response Diff In Min',
|
||||
width: 10,
|
||||
},
|
||||
{
|
||||
name: 'Update Escalation At',
|
||||
width: 18,
|
||||
},
|
||||
{
|
||||
name: 'Update In Min',
|
||||
width: 10,
|
||||
},
|
||||
{
|
||||
name: 'Update Diff In Min',
|
||||
width: 10,
|
||||
},
|
||||
{
|
||||
name: 'Last Contact At',
|
||||
width: 18,
|
||||
},
|
||||
{
|
||||
name: 'Last Contact Agent At',
|
||||
width: 18,
|
||||
},
|
||||
{
|
||||
name: 'Last Contact Customer At',
|
||||
width: 18,
|
||||
},
|
||||
{
|
||||
name: 'Article Count',
|
||||
width: 10,
|
||||
},
|
||||
{
|
||||
name: 'Escalation At',
|
||||
width: 18,
|
||||
},
|
||||
]
|
||||
objects = ObjectManager::Attribute.where(editable: true,
|
||||
active: true,
|
||||
to_create: false,
|
||||
object_lookup_id: ObjectLookup.lookup(name: 'Ticket').id)
|
||||
.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: 18 })
|
||||
render json: results
|
||||
return
|
||||
end
|
||||
|
||||
result = []
|
||||
results.each do |row|
|
||||
result_row = [
|
||||
row[:ticket]['number'],
|
||||
row[:ticket]['title'],
|
||||
row[:customer],
|
||||
row[:organization],
|
||||
row[:agent],
|
||||
row[:time_unit],
|
||||
row[:ticket]['time_unit'],
|
||||
row[:ticket]['created_at'],
|
||||
row[:ticket]['close_at'],
|
||||
row[:ticket]['close_escalation_at'],
|
||||
row[:ticket]['close_in_min'],
|
||||
row[:ticket]['close_diff_in_min'],
|
||||
row[:ticket]['first_response_at'],
|
||||
row[:ticket]['first_response_escalation_at'],
|
||||
row[:ticket]['first_response_in_min'],
|
||||
row[:ticket]['first_response_diff_in_min'],
|
||||
row[:ticket]['update_escalation_at'],
|
||||
row[:ticket]['update_in_min'],
|
||||
row[:ticket]['update_diff_in_min'],
|
||||
row[:ticket]['last_contact_at'],
|
||||
row[:ticket]['last_contact_agent_at'],
|
||||
row[:ticket]['last_contact_customer_at'],
|
||||
row[:ticket]['article_count'],
|
||||
row[:ticket]['escalation_at'],
|
||||
]
|
||||
|
||||
# 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 = 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('')
|
||||
else
|
||||
# for text, integer and tree select
|
||||
row[:ticket][key].present? ? result_row.push(row[:ticket][key]) : result_row.push('')
|
||||
end
|
||||
ticket_ids = []
|
||||
additional_attributes = []
|
||||
additional_attributes_header = [{ display: 'Time Units', name: 'time_unit_for_range', width: 10, data_type: 'float' }]
|
||||
time_unit.each do |ticket_id, local_time_unit|
|
||||
ticket_ids.push ticket_id
|
||||
additional_attribute = {
|
||||
time_unit_for_range: local_time_unit[:time_unit],
|
||||
}
|
||||
additional_attributes.push additional_attribute
|
||||
end
|
||||
|
||||
result.push result_row
|
||||
end
|
||||
content = sheet("By Ticket #{year}-#{month}", header, result)
|
||||
excel = ExcelSheet::Ticket.new(
|
||||
title: "Tickets: #{year}-#{month}",
|
||||
ticket_ids: ticket_ids,
|
||||
additional_attributes: additional_attributes,
|
||||
additional_attributes_header: additional_attributes_header,
|
||||
timezone: params[:timezone],
|
||||
locale: current_user.locale,
|
||||
)
|
||||
|
||||
send_data(
|
||||
content,
|
||||
excel.content,
|
||||
filename: "by_ticket-#{year}-#{month}.xls",
|
||||
type: 'application/vnd.ms-excel',
|
||||
disposition: 'attachment'
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
render json: results
|
||||
end
|
||||
|
||||
def by_customer
|
||||
|
@ -289,9 +152,10 @@ class TimeAccountingsController < ApplicationController
|
|||
{
|
||||
name: 'Time Units',
|
||||
width: 10,
|
||||
data_type: 'float'
|
||||
}
|
||||
]
|
||||
result = []
|
||||
records = []
|
||||
results.each do |row|
|
||||
customer_name = User.find(row[:customer]['id']).fullname
|
||||
organization_name = ''
|
||||
|
@ -299,11 +163,18 @@ class TimeAccountingsController < ApplicationController
|
|||
organization_name = row[:organization]['name']
|
||||
end
|
||||
result_row = [customer_name, organization_name, row[:time_unit]]
|
||||
result.push result_row
|
||||
records.push result_row
|
||||
end
|
||||
content = sheet("By Customer #{year}-#{month}", header, result)
|
||||
|
||||
excel = ExcelSheet.new(
|
||||
title: "By Customer #{year}-#{month}",
|
||||
header: header,
|
||||
records: records,
|
||||
timezone: params[:timezone],
|
||||
locale: current_user.locale,
|
||||
)
|
||||
send_data(
|
||||
content,
|
||||
excel.content,
|
||||
filename: "by_customer-#{year}-#{month}.xls",
|
||||
type: 'application/vnd.ms-excel',
|
||||
disposition: 'attachment'
|
||||
|
@ -362,20 +233,28 @@ class TimeAccountingsController < ApplicationController
|
|||
{
|
||||
name: 'Time Units',
|
||||
width: 20,
|
||||
data_type: 'float',
|
||||
}
|
||||
]
|
||||
result = []
|
||||
records = []
|
||||
results.each do |row|
|
||||
organization_name = ''
|
||||
if row[:organization].present?
|
||||
organization_name = row[:organization]['name']
|
||||
end
|
||||
result_row = [organization_name, row[:time_unit]]
|
||||
result.push result_row
|
||||
records.push result_row
|
||||
end
|
||||
content = sheet("By Organization #{year}-#{month}", header, result)
|
||||
|
||||
excel = ExcelSheet.new(
|
||||
title: "By Organization #{year}-#{month}",
|
||||
header: header,
|
||||
records: records,
|
||||
timezone: params[:timezone],
|
||||
locale: current_user.locale,
|
||||
)
|
||||
send_data(
|
||||
content,
|
||||
excel.content,
|
||||
filename: "by_organization-#{year}-#{month}.xls",
|
||||
type: 'application/vnd.ms-excel',
|
||||
disposition: 'attachment'
|
||||
|
@ -385,90 +264,4 @@ class TimeAccountingsController < ApplicationController
|
|||
|
||||
render json: results
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
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)
|
||||
|
||||
# Add a worksheet
|
||||
worksheet = workbook.add_worksheet
|
||||
|
||||
# Add and define a format
|
||||
format = workbook.add_format # Add a format
|
||||
format.set_bold
|
||||
format.set_size(14)
|
||||
format.set_color('black')
|
||||
|
||||
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)
|
||||
|
||||
format_header = workbook.add_format # Add a format
|
||||
format_header.set_italic
|
||||
format_header.set_bg_color('gray')
|
||||
format_header.set_color('white')
|
||||
count = 0
|
||||
header.each do |item|
|
||||
if item[:width]
|
||||
worksheet.set_column(count, count, item[:width])
|
||||
end
|
||||
worksheet.write_string(2, count, item[:name], format_header)
|
||||
count += 1
|
||||
end
|
||||
|
||||
row_count = 2
|
||||
result.each do |row|
|
||||
row_count += 1
|
||||
row_item_count = 0
|
||||
row.each do |item|
|
||||
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.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
|
||||
file = File.new(temp_file, 'r')
|
||||
contents = file.read
|
||||
file.close
|
||||
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
|
||||
local_time = time.utc.iso8601.to_s.sub(/Z$/, '')
|
||||
local_time.sub(/T/, ' ')
|
||||
end
|
||||
end
|
||||
|
|
173
lib/excel_sheet.rb
Normal file
173
lib/excel_sheet.rb
Normal file
|
@ -0,0 +1,173 @@
|
|||
class ExcelSheet
|
||||
|
||||
def initialize(title:, header:, records:, timezone: nil, locale:)
|
||||
@title = title
|
||||
@header = header
|
||||
@records = records
|
||||
@timezone = timezone.presence || Setting.get('timezone_default')
|
||||
@timezone_offset = @timezone.present? ? Time.now.in_time_zone(@timezone).utc_offset : 0
|
||||
@locale = locale || 'en-en'
|
||||
@tempfile = Tempfile.new('excel-export.xls')
|
||||
@workbook = WriteExcel.new(@tempfile)
|
||||
@worksheet = @workbook.add_worksheet
|
||||
@contents = nil
|
||||
@current_row = 0
|
||||
@current_column = 0
|
||||
|
||||
@lookup_cache = {}
|
||||
|
||||
@format_time = @workbook.add_format(num_format: 'yyyy-mm-dd hh:mm:ss')
|
||||
@format_date = @workbook.add_format(num_format: 'yyyy-mm-dd')
|
||||
|
||||
@format_headline = @workbook.add_format
|
||||
@format_headline.set_bold
|
||||
@format_headline.set_size(14)
|
||||
@format_headline.set_color('black')
|
||||
|
||||
@format_header = @workbook.add_format
|
||||
@format_header.set_italic
|
||||
@format_header.set_bg_color('gray')
|
||||
@format_header.set_color('white')
|
||||
|
||||
@format_footer = @workbook.add_format
|
||||
@format_footer.set_italic
|
||||
@format_footer.set_color('gray')
|
||||
@format_footer.set_size(8)
|
||||
end
|
||||
|
||||
def contents
|
||||
file = File.new(@tempfile, 'r')
|
||||
contents = file.read
|
||||
file.close
|
||||
contents
|
||||
end
|
||||
|
||||
def content
|
||||
gen_header
|
||||
gen_rows
|
||||
gen_footer
|
||||
contents
|
||||
end
|
||||
|
||||
def gen_header
|
||||
@worksheet.write_string(@current_row, @current_column, @title, @format_headline)
|
||||
@worksheet.set_row(0, 18)
|
||||
|
||||
@current_row += 2
|
||||
@current_column = 0
|
||||
@header.each do |header|
|
||||
if header[:width]
|
||||
@worksheet.set_column(@current_column, @current_column, header[:width])
|
||||
end
|
||||
@worksheet.write_string(@current_row, @current_column, header[:display] || header[:name], @format_header)
|
||||
@current_column += 1
|
||||
end
|
||||
end
|
||||
|
||||
def gen_rows
|
||||
@records.each do |record|
|
||||
gen_row_by_array(record)
|
||||
end
|
||||
end
|
||||
|
||||
def gen_row_by_array(record)
|
||||
@current_row += 1
|
||||
@current_column = 0
|
||||
record.each do |item|
|
||||
begin
|
||||
if item.acts_like?(:time)
|
||||
value_convert(item, nil, { data_type: 'datetime' })
|
||||
elsif item.acts_like?(:date)
|
||||
value_convert(item, nil, { data_type: 'datetime' })
|
||||
elsif item.is_a?(Integer) || item.is_a?(Float)
|
||||
value_convert(item, nil, { data_type: 'integer' })
|
||||
else
|
||||
value_convert(item, nil, { data_type: 'string' })
|
||||
end
|
||||
rescue => e
|
||||
Rails.logger.error e
|
||||
end
|
||||
@current_column += 1
|
||||
end
|
||||
end
|
||||
|
||||
def gen_row_by_header(record, additional = {})
|
||||
@current_row += 1
|
||||
@current_column = 0
|
||||
@header.each do |header|
|
||||
begin
|
||||
value_convert(record, header[:name], header, additional)
|
||||
rescue => e
|
||||
Rails.logger.error e
|
||||
end
|
||||
@current_column += 1
|
||||
end
|
||||
end
|
||||
|
||||
def gen_footer
|
||||
@current_row += 2
|
||||
@worksheet.write_string(@current_row, 0, "#{Translation.translate(@locale, 'Timezone')}: #{@timezone}", @format_footer)
|
||||
@workbook.close
|
||||
end
|
||||
|
||||
def timestamp_in_localtime(time)
|
||||
return if time.blank?
|
||||
|
||||
(time + @timezone_offset).utc.strftime('%F %T') # "2019-08-19 16:21:52"
|
||||
end
|
||||
|
||||
def value_lookup(record, attribute, additional)
|
||||
value = record[attribute.to_sym]
|
||||
if attribute[-3, 3] == '_id'
|
||||
ref = attribute[0, attribute.length - 3]
|
||||
if record.respond_to?(ref.to_sym)
|
||||
@lookup_cache[attribute] ||= {}
|
||||
return @lookup_cache[attribute][value] if @lookup_cache[attribute][value]
|
||||
|
||||
ref_object = record.send(ref.to_sym)
|
||||
ref_name = value
|
||||
if ref_object.respond_to?(:fullname)
|
||||
ref_name = ref_object.fullname
|
||||
elsif ref_object.respond_to?(:name)
|
||||
ref_name = ref_object.name
|
||||
end
|
||||
@lookup_cache[attribute][value] = ref_name
|
||||
return ref_name
|
||||
end
|
||||
end
|
||||
value = record.try(attribute)
|
||||
|
||||
# if no value exists, check additional values
|
||||
if !value && additional && additional[attribute.to_sym]
|
||||
value = additional[attribute.to_sym]
|
||||
end
|
||||
if value.is_a?(Array)
|
||||
value = value.join(',')
|
||||
end
|
||||
value
|
||||
end
|
||||
|
||||
def value_convert(record, attribute, object, additional = {})
|
||||
value = if attribute
|
||||
value_lookup(record, attribute, additional)
|
||||
else
|
||||
record
|
||||
end
|
||||
case object[:data_type]
|
||||
when 'boolean', 'select'
|
||||
if object[:data_option] && object[:data_option]['options'] && object[:data_option]['options'][value]
|
||||
value = object[:data_option]['options'][value]
|
||||
end
|
||||
@worksheet.write_string(@current_row, @current_column, value) if value.present?
|
||||
when 'datetime'
|
||||
@worksheet.write_date_time(@current_row, @current_column, timestamp_in_localtime(value), @format_time) if value.present?
|
||||
when 'date'
|
||||
@worksheet.write_date_time(@current_row, @current_column, value.to_s, @format_date) if value.present?
|
||||
when 'integer'
|
||||
@worksheet.write_number(@current_row, @current_column, value) if value.present?
|
||||
else
|
||||
@worksheet.write_string(@current_row, @current_column, value.to_s) if value.present?
|
||||
end
|
||||
end
|
||||
|
||||
end
|
103
lib/excel_sheet/ticket.rb
Normal file
103
lib/excel_sheet/ticket.rb
Normal file
|
@ -0,0 +1,103 @@
|
|||
class ExcelSheet::Ticket < ExcelSheet
|
||||
|
||||
=begin
|
||||
|
||||
excel = ExcelSheet::Ticket.new(
|
||||
title: "#{year}-#{month}",
|
||||
ticket_ids: ticket_ids,
|
||||
additional_attributes: additional_attributes,
|
||||
additional_attributes_header: additional_attributes_header,
|
||||
timezone: params[:timezone],
|
||||
locale: current_user.locale,
|
||||
)
|
||||
|
||||
excel.content
|
||||
|
||||
=end
|
||||
|
||||
def initialize(params)
|
||||
@ticket_ids = params[:ticket_ids] || []
|
||||
@additional_attributes = params[:additional_attributes] || []
|
||||
@additional_attributes_header = params[:additional_attributes_header] || []
|
||||
|
||||
super(
|
||||
title: params[:title],
|
||||
header: ticket_header,
|
||||
records: [],
|
||||
timezone: params[:timezone],
|
||||
locale: params[:locale]
|
||||
)
|
||||
end
|
||||
|
||||
def ticket_header
|
||||
header = [
|
||||
{ display: '#', name: 'number', width: 18, data_type: 'string' },
|
||||
{ display: 'Title', name: 'title', width: 34, data_type: 'string' },
|
||||
{ display: 'State', name: 'state_id', width: 14, data_type: 'string' },
|
||||
{ display: 'Priority', name: 'priority_id', width: 14, data_type: 'string' },
|
||||
{ display: 'Group', name: 'group_id', width: 20, data_type: 'string' },
|
||||
{ display: 'Owner', name: 'owner_id', width: 20, data_type: 'string' },
|
||||
{ display: 'Customer', name: 'customer_id', width: 20, data_type: 'string' },
|
||||
{ display: 'Organization', name: 'organization_id', width: 20, data_type: 'string' },
|
||||
{ display: 'Create Channel', name: 'create_article_type_id', width: 10, data_type: 'string' },
|
||||
{ display: 'Sender', name: 'create_article_sender_id', width: 14, data_type: 'string' },
|
||||
{ display: 'Tags', name: 'tag_list', width: 20, data_type: 'string' },
|
||||
{ display: 'Time Units Total', name: 'time_unit', width: 10, data_type: 'float' },
|
||||
]
|
||||
|
||||
header = header.concat(@additional_attributes_header) if @additional_attributes_header
|
||||
|
||||
# ObjectManager attributes
|
||||
objects = ObjectManager::Attribute.where(active: true,
|
||||
to_create: false,
|
||||
object_lookup_id: ObjectLookup.lookup(name: 'Ticket').id)
|
||||
.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, width: 20 } }
|
||||
objects.each do |object|
|
||||
already_exists = false
|
||||
header.each do |local_header|
|
||||
next if local_header[:name] != object[:name]
|
||||
|
||||
already_exists = true
|
||||
break
|
||||
end
|
||||
next if already_exists
|
||||
|
||||
header.push object
|
||||
end
|
||||
|
||||
header = header.concat([
|
||||
{ display: 'Created At', name: 'created_at', width: 18, data_type: 'datetime' },
|
||||
{ display: 'Updated At', name: 'updated_at', width: 18, data_type: 'datetime' },
|
||||
{ display: 'Closed At', name: 'close_at', width: 18, data_type: 'datetime' },
|
||||
{ display: 'Close Escalation At', name: 'close_escalation_at', width: 18, data_type: 'datetime' },
|
||||
{ display: 'Close In Min', name: 'close_in_min', width: 10, data_type: 'integer' },
|
||||
{ display: 'Close Diff In Min', name: 'close_diff_in_min', width: 10, data_type: 'integer' },
|
||||
{ display: 'First Response At', name: 'first_response_at', width: 18, data_type: 'datetime' },
|
||||
{ display: 'First Response Escalation At', name: 'first_response_escalation_at', width: 18, data_type: 'datetime' },
|
||||
{ display: 'First Response In Min', name: 'first_response_in_min', width: 10, data_type: 'integer' },
|
||||
{ display: 'First Response Diff In Min', name: 'first_response_diff_in_min', width: 10, data_type: 'integer' },
|
||||
{ display: 'Update Escalation At', name: 'update_escalation_at', width: 18, data_type: 'datetime' },
|
||||
{ display: 'Update In Min', name: 'update_in_min', width: 10, data_type: 'integer' },
|
||||
{ display: 'Update Diff In Min', name: 'update_diff_in_min', width: 10, data_type: 'integer' },
|
||||
{ display: 'Last Contact At', name: 'last_contact_at', width: 18, data_type: 'datetime' },
|
||||
{ display: 'Last Contact Agent At', name: 'last_contact_agent_at', width: 18, data_type: 'datetime' },
|
||||
{ display: 'Last Contact Customer At', name: 'last_contact_customer_at', width: 18, data_type: 'datetime' },
|
||||
{ display: 'Article Count', name: 'article_count', width: 10, data_type: 'integer' },
|
||||
{ display: 'Escalation At', name: 'escalation_at', width: 18, data_type: 'datetime' },
|
||||
])
|
||||
header
|
||||
end
|
||||
|
||||
def gen_rows
|
||||
@ticket_ids.each_with_index do |ticket_id, index|
|
||||
ticket = ::Ticket.lookup(id: ticket_id)
|
||||
raise "Can't find Ticket with ID #{ticket_id} for '#{@title}' #{self.class.name} generation" if !ticket
|
||||
|
||||
gen_row_by_header(ticket, @additional_attributes[index])
|
||||
rescue => e
|
||||
Rails.logger.error e
|
||||
end
|
||||
end
|
||||
|
||||
end
|
14
spec/lib/excel_sheet_spec.rb
Normal file
14
spec/lib/excel_sheet_spec.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ExcelSheet do
|
||||
|
||||
describe '.timestamp_in_localtime' do
|
||||
|
||||
let(:document) { described_class.new(title: 'some title', header: [], records: [], timezone: 'Europe/Berlin', locale: 'de-de') }
|
||||
|
||||
it 'does convert UTC timestamp to local system based timestamp' do
|
||||
expect(document.timestamp_in_localtime(Time.parse('2019-08-08T01:00:05Z').in_time_zone)).to eq('2019-08-08 03:00:05')
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -81,10 +81,6 @@ RSpec.describe 'Report', type: :request, searchindex: true do
|
|||
expect(@response['Content-Type']).to eq('application/vnd.ms-excel')
|
||||
end
|
||||
|
||||
it 'does convert UTC timestamp to local system based timestamp' do
|
||||
expect(ReportsController.new.time_in_localtime_for_excel(Time.parse('2019-08-08T01:00:05Z').in_time_zone, 'Europe/Berlin')).to eq('2019-08-08 03:00:05')
|
||||
end
|
||||
|
||||
it 'does report example - deliver result' do
|
||||
skip('No ES configured') if !SearchIndexBackend.enabled?
|
||||
|
||||
|
|
|
@ -62,8 +62,5 @@ RSpec.describe 'Time Accounting API endpoints', type: :request do
|
|||
end
|
||||
end
|
||||
|
||||
it 'does convert UTC timestamp to local system based timestamp' do
|
||||
expect(TimeAccountingsController.new.instance_eval { time_in_localtime_for_excel(Time.parse('2019-08-08T01:00:05Z').in_time_zone, 'Europe/Berlin') }).to eq('2019-08-08 03:00:05')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue