mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-20 14:54:15 +01:00
(bug) unable to export statistics
This commit is contained in:
parent
8eaeb2df39
commit
963d2e93bd
@ -1,5 +1,6 @@
|
||||
# Changelog Fab-manager
|
||||
|
||||
- Fix a bug: unable to export statistics
|
||||
- Fix a bug: soft destroyed machines and spaces are still reported in the OpenAPI
|
||||
|
||||
## v5.5.5 2022 November 22
|
||||
|
@ -47,7 +47,7 @@ module ApplicationHelper
|
||||
message = MessageFormat.new(I18n.t(scope_key_by_partial(key)), I18n.locale.to_s)
|
||||
text = message.format(interpolations)
|
||||
if html_safe_translation_key?(key)
|
||||
text.html_safe
|
||||
text.html_safe # rubocop:disable Rails/OutputSafety
|
||||
else
|
||||
text
|
||||
end
|
||||
@ -65,23 +65,6 @@ module ApplicationHelper
|
||||
amount / 100.00
|
||||
end
|
||||
|
||||
##
|
||||
# Retrieve an item in the given array of items
|
||||
# by default, the "id" is expected to match the given parameter but
|
||||
# this can be overridden by passing a third parameter to specify the
|
||||
# property to match
|
||||
##
|
||||
def get_item(array, id, key = nil)
|
||||
array.each do |i|
|
||||
if key.nil?
|
||||
return i if i.id == id
|
||||
elsif i[key] == id
|
||||
return i
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
##
|
||||
# Apply a correction for a future DateTime due to change in Daylight Saving Time (DST) period
|
||||
# @param reference {ActiveSupport::TimeWithZone}
|
||||
@ -98,6 +81,7 @@ module ApplicationHelper
|
||||
private
|
||||
|
||||
## inspired by gems/actionview-4.2.5/lib/action_view/helpers/translation_helper.rb
|
||||
# rubocop:disable Rails/HelperInstanceVariable
|
||||
def scope_key_by_partial(key)
|
||||
if key.to_s.first == '.'
|
||||
raise "Cannot use t(#{key.inspect}) shortcut because path is not available" unless @virtual_path
|
||||
@ -107,6 +91,7 @@ module ApplicationHelper
|
||||
key
|
||||
end
|
||||
end
|
||||
# rubocop:enable Rails/HelperInstanceVariable
|
||||
|
||||
def html_safe_translation_key?(key)
|
||||
key.to_s =~ /(\b|_|\.)html$/
|
||||
|
75
app/helpers/excel_helper.rb
Normal file
75
app/helpers/excel_helper.rb
Normal file
@ -0,0 +1,75 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Helpers for excel exports, for use with AXSLX gem
|
||||
module ExcelHelper
|
||||
# format the given source data for an Excell cell
|
||||
def format_xlsx_cell(source_data, data, styles, types, date_format: nil, source_data_type: '')
|
||||
case source_data_type
|
||||
when 'date'
|
||||
data.push Date.strptime(source_data, '%Y-%m-%d')
|
||||
styles.push date_format
|
||||
types.push :date
|
||||
when 'list'
|
||||
data.push source_data.map { |e| e['name'] }.join(', ')
|
||||
styles.push nil
|
||||
types.push :string
|
||||
when 'number'
|
||||
data.push source_data
|
||||
styles.push nil
|
||||
types.push :float
|
||||
else
|
||||
data.push source_data
|
||||
styles.push nil
|
||||
types.push :string
|
||||
end
|
||||
end
|
||||
|
||||
# build a new excel line for a statistic export
|
||||
def statistics_line(hit, user, type, subtype, date_format)
|
||||
data = [
|
||||
Date.strptime(hit['_source']['date'], '%Y-%m-%d'),
|
||||
user&.profile&.full_name || t('export.deleted_user'),
|
||||
user&.email || '',
|
||||
user&.profile&.phone || '',
|
||||
t("export.#{hit['_source']['gender']}"),
|
||||
hit['_source']['age'],
|
||||
subtype.nil? ? '' : subtype.label
|
||||
]
|
||||
styles = [date_format, nil, nil, nil, nil, nil, nil]
|
||||
types = %i[date string string string string integer string]
|
||||
# do not proceed with the 'stat' field if the type is declared as 'simple'
|
||||
unless type.simple
|
||||
data.push hit['_source']['stat']
|
||||
styles.push nil
|
||||
types.push :string
|
||||
end
|
||||
|
||||
[data, styles, types]
|
||||
end
|
||||
|
||||
# append a cell containing the CA amount
|
||||
def add_ca_cell(index, hit, data, styles, types)
|
||||
return unless index.ca
|
||||
|
||||
data.push hit['_source']['ca']
|
||||
styles.push nil
|
||||
types.push :float
|
||||
end
|
||||
|
||||
##
|
||||
# Retrieve an item in the given array of items
|
||||
# by default, the "id" is expected to match the given parameter but
|
||||
# this can be overridden by passing a third parameter to specify the
|
||||
# property to match
|
||||
##
|
||||
def get_item(array, id, key = nil)
|
||||
array.each do |i|
|
||||
if key.nil?
|
||||
return i if i.id == id
|
||||
elsif i[key] == id
|
||||
return i
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
end
|
@ -27,9 +27,9 @@ class ExportService
|
||||
|
||||
def query_last_export(category, export_type, query = nil, key = nil, extension = nil)
|
||||
export = Export.where(category: category, export_type: export_type)
|
||||
export.where(query: query) unless query.nil?
|
||||
export.where(key: key) unless key.nil?
|
||||
export.where(extension: extension) unless extension.nil?
|
||||
export = export.where(query: query) unless query.nil?
|
||||
export = export.where(key: key) unless key.nil?
|
||||
export = export.where(extension: extension) unless extension.nil?
|
||||
export
|
||||
end
|
||||
|
||||
|
@ -21,16 +21,34 @@ class Statistics::Builders::ReservationsBuilderService
|
||||
stat[:stat] = (type == 'booking' ? 1 : r[:nb_hours])
|
||||
stat["#{category}Id".to_sym] = r["#{category}_id".to_sym]
|
||||
|
||||
if category == 'event'
|
||||
stat[:eventDate] = r[:event_date]
|
||||
stat[:eventTheme] = r[:event_theme]
|
||||
stat[:ageRange] = r[:age_range]
|
||||
end
|
||||
|
||||
stat = add_custom_attributes(category, stat, r)
|
||||
stat.save
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_custom_attributes(category, stat, reservation_data)
|
||||
stat = add_event_attributes(category, stat, reservation_data)
|
||||
add_training_attributes(category, stat, reservation_data)
|
||||
end
|
||||
|
||||
def add_event_attributes(category, stat, reservation_data)
|
||||
return stat unless category == 'event'
|
||||
|
||||
stat[:eventDate] = reservation_data[:event_date]
|
||||
stat[:eventTheme] = reservation_data[:event_theme]
|
||||
stat[:ageRange] = reservation_data[:age_range]
|
||||
|
||||
stat
|
||||
end
|
||||
|
||||
def add_training_attributes(category, stat, reservation_data)
|
||||
return stat unless category == 'training'
|
||||
|
||||
stat[:trainingDate] = reservation_data[:training_date]
|
||||
|
||||
stat
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -6,12 +6,11 @@ require 'action_view'
|
||||
require 'active_record'
|
||||
|
||||
# require any helpers
|
||||
require './app/helpers/application_helper'
|
||||
require './app/helpers/excel_helper'
|
||||
|
||||
# Export statistics (from elasticsearch) to an excel file
|
||||
class StatisticsExportService
|
||||
|
||||
def export_global(export)
|
||||
|
||||
# query all stats with range arguments
|
||||
query = MultiJson.load(export.query)
|
||||
|
||||
@ -30,18 +29,19 @@ class StatisticsExportService
|
||||
|
||||
ActionController::Base.prepend_view_path './app/views/'
|
||||
# place data in view_assigns
|
||||
view_assigns = {results: @results, users: @users, indices: @indices}
|
||||
view_assigns = { results: @results, users: @users, indices: @indices }
|
||||
av = ActionView::Base.new(ActionController::Base.view_paths, view_assigns)
|
||||
av.class_eval do
|
||||
# include any needed helpers (for the view)
|
||||
include ApplicationHelper
|
||||
include ExcelHelper
|
||||
end
|
||||
|
||||
content = av.render template: 'exports/statistics_global.xlsx.axlsx'
|
||||
# write content to file
|
||||
File.open(export.file, 'w+b') { |f| f.write content }
|
||||
File.binwrite(export.file, content)
|
||||
end
|
||||
|
||||
# rubocop:disable Style/DocumentDynamicEvalDefinition
|
||||
%w[account event machine project subscription training space].each do |path|
|
||||
class_eval %{
|
||||
def export_#{path}(export)
|
||||
@ -71,14 +71,14 @@ class StatisticsExportService
|
||||
av = ActionView::Base.new(ActionController::Base.view_paths, view_assigns)
|
||||
av.class_eval do
|
||||
# include any needed helpers (for the view)
|
||||
include ApplicationHelper
|
||||
include ExcelHelper
|
||||
end
|
||||
|
||||
content = av.render template: 'exports/statistics_current.xlsx.axlsx'
|
||||
# write content to file
|
||||
File.open(export.file,"w+b") { |f| f.write content }
|
||||
File.binwrite(export.file, content)
|
||||
end
|
||||
}, __FILE__, __LINE__ - 35
|
||||
end
|
||||
|
||||
# rubocop:enable Style/DocumentDynamicEvalDefinition
|
||||
end
|
||||
|
@ -1,79 +1,46 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
wb = xlsx_package.workbook
|
||||
|
||||
bold = wb.styles.add_style :b => true
|
||||
header = wb.styles.add_style :b => true, :bg_color => Stylesheet.primary.upcase.gsub('#', 'FF'), :fg_color => 'FFFFFFFF'
|
||||
date = wb.styles.add_style :format_code => Rails.application.secrets.excel_date_format
|
||||
bold = wb.styles.add_style b: true
|
||||
header = wb.styles.add_style b: true, bg_color: Stylesheet.primary.upcase.gsub('#', 'FF'), fg_color: 'FFFFFFFF'
|
||||
date = wb.styles.add_style format_code: Rails.application.secrets.excel_date_format
|
||||
|
||||
wb.add_worksheet(name: @index.label) do |sheet|
|
||||
## heading stats for the current page
|
||||
sheet.add_row [t('export.entries'), @results['hits']['total']], :style => [bold, nil], :types => [:string, :integer]
|
||||
sheet.add_row [t('export.entries'), @results['hits']['total']], style: [bold, nil], types: %i[string integer]
|
||||
if @index.ca
|
||||
sheet.add_row [t('export.revenue'), @results['aggregations']['total_ca']['value']], :style => [bold, nil], :types => [:string, :float]
|
||||
sheet.add_row [t('export.revenue'), @results['aggregations']['total_ca']['value']], style: [bold, nil], types: %i[string float]
|
||||
end
|
||||
sheet.add_row [t('export.average_age'), @results['aggregations']['average_age']['value']], :style => [bold, nil], :types => [:string, :float]
|
||||
sheet.add_row [t('export.average_age'), @results['aggregations']['average_age']['value']], style: [bold, nil], types: %i[string float]
|
||||
unless @type.simple
|
||||
sheet.add_row ["#{t('export.total')} #{@type.label}", @results['aggregations']['total_stat']['value']], :style => [bold, nil], :types => [:string, :integer]
|
||||
sheet.add_row ["#{t('export.total')} #{@type.label}", @results['aggregations']['total_stat']['value']],
|
||||
style: [bold, nil],
|
||||
types: %i[string integer]
|
||||
end
|
||||
sheet.add_row []
|
||||
|
||||
## data table
|
||||
# heading labels
|
||||
columns = [t('export.date'), t('export.user'), t('export.email'), t('export.phone'), t('export.gender'), t('export.age'), t('export.type')]
|
||||
columns = [t('export.date'), t('export.user'), t('export.email'), t('export.phone'), t('export.gender'), t('export.age'),
|
||||
t('export.type')]
|
||||
columns.push @type.label unless @type.simple
|
||||
@fields.each do |f|
|
||||
columns.push f.label
|
||||
end
|
||||
columns.push t('export.revenue') if @index.ca
|
||||
sheet.add_row columns, :style => header
|
||||
sheet.add_row columns, style: header
|
||||
|
||||
# data rows
|
||||
@results['hits']['hits'].each do |hit|
|
||||
user = get_item(@users, hit['_source']['userId'])
|
||||
subtype = get_item(@subtypes, hit['_source']['subType'], 'key')
|
||||
data = [
|
||||
Date::strptime(hit['_source']['date'],'%Y-%m-%d'),
|
||||
(user ? user.profile.full_name : "ID #{hit['_source']['userId']}"),
|
||||
(user ? user.email : ''),
|
||||
(user ? user.profile.phone : ''),
|
||||
t("export.#{hit['_source']['gender']}"),
|
||||
hit['_source']['age'],
|
||||
subtype.nil? ? "" : subtype.label
|
||||
]
|
||||
styles = [date, nil, nil, nil, nil, nil, nil]
|
||||
types = [:date, :string, :string, :string, :string, :integer, :string]
|
||||
unless @type.simple
|
||||
data.push hit['_source']['stat']
|
||||
styles.push nil
|
||||
types.push :string
|
||||
end
|
||||
data, styles, types = statistics_line(hit, user, @type, subtype, date)
|
||||
@fields.each do |f|
|
||||
field_data = hit['_source'][f.key]
|
||||
case f.data_type
|
||||
when 'date'
|
||||
data.push Date::strptime(field_data, '%Y-%m-%d')
|
||||
styles.push date
|
||||
types.push :date
|
||||
when 'list'
|
||||
data.push field_data.map{|e| e['name'] }.join(', ')
|
||||
styles.push nil
|
||||
types.push :string
|
||||
when 'number'
|
||||
data.push field_data
|
||||
styles.push nil
|
||||
types.push :float
|
||||
else
|
||||
data.push field_data
|
||||
styles.push nil
|
||||
types.push :string
|
||||
end
|
||||
|
||||
end
|
||||
if @index.ca
|
||||
data.push hit['_source']['ca']
|
||||
styles.push nil
|
||||
types.push :float
|
||||
format_xlsx_cell(hit['_source'][f.key], data, styles, types, source_data_type: f.data_type, date_format: date)
|
||||
end
|
||||
add_ca_cell(@index, hit, data, styles, types)
|
||||
|
||||
sheet.add_row data, :style => styles, :types => types
|
||||
sheet.add_row data, style: styles, types: types
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,84 +1,48 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
wb = xlsx_package.workbook
|
||||
|
||||
header = wb.styles.add_style :b => true, :bg_color => Stylesheet.primary.upcase.gsub('#', 'FF'), :fg_color => 'FFFFFFFF'
|
||||
date = wb.styles.add_style :format_code => Rails.application.secrets.excel_date_format
|
||||
header = wb.styles.add_style b: true, bg_color: Stylesheet.primary.upcase.gsub('#', 'FF'), fg_color: 'FFFFFFFF'
|
||||
date = wb.styles.add_style format_code: Rails.application.secrets.excel_date_format
|
||||
|
||||
@indices.each do |index|
|
||||
if index.table
|
||||
index.statistic_types.each do |type|
|
||||
# see https://msdn.microsoft.com/fr-fr/library/c6bdca6y(v=vs.90).aspx for unauthorized character list
|
||||
sheet_name = "#{index.label} - #{type.label}".gsub(/[*|\\:"<>?\/]/,'').truncate(31)
|
||||
wb.add_worksheet(name: sheet_name) do |sheet|
|
||||
next unless index.table
|
||||
|
||||
## data table
|
||||
# heading labels
|
||||
columns = [t('export.date'), t('export.user'), t('export.email'), t('export.phone'), t('export.gender'), t('export.age'), t('export.type')]
|
||||
columns.push type.label unless type.simple
|
||||
index.statistic_types.each do |type|
|
||||
# see https://msdn.microsoft.com/fr-fr/library/c6bdca6y(v=vs.90).aspx for unauthorized character list
|
||||
sheet_name = "#{index.label} - #{type.label}".gsub(%r{[*|\\:"<>?/]}, '').truncate(31)
|
||||
wb.add_worksheet(name: sheet_name) do |sheet|
|
||||
## data table
|
||||
# heading labels
|
||||
columns = [t('export.date'), t('export.user'), t('export.email'), t('export.phone'), t('export.gender'), t('export.age'),
|
||||
t('export.type')]
|
||||
columns.push type.label unless type.simple
|
||||
index.statistic_fields.each do |f|
|
||||
columns.push f.label
|
||||
end
|
||||
columns.push t('export.revenue') if index.ca
|
||||
sheet.add_row columns, style: header
|
||||
|
||||
# data rows
|
||||
@results['hits']['hits'].each do |hit|
|
||||
# check that the current result is for the given index and type
|
||||
next unless hit['_type'] == index.es_type_key && hit['_source']['type'] == type.key
|
||||
|
||||
# get matching objects
|
||||
user = get_item(@users, hit['_source']['userId'])
|
||||
subtype = get_item(type.statistic_sub_types, hit['_source']['subType'], 'key')
|
||||
# start to fill data and associated styles and data-types
|
||||
data, styles, types = statistics_line(hit, user, type, subtype, date)
|
||||
# proceed additional fields
|
||||
index.statistic_fields.each do |f|
|
||||
columns.push f.label
|
||||
format_xlsx_cell(hit['_source'][f.key], data, styles, types, source_data_type: f.data_type, date_format: date)
|
||||
end
|
||||
columns.push t('export.revenue') if index.ca
|
||||
sheet.add_row columns, :style => header
|
||||
# proceed the 'ca' field if requested
|
||||
add_ca_cell(index, hit, data, styles, types)
|
||||
|
||||
# data rows
|
||||
@results['hits']['hits'].each do |hit|
|
||||
# check that the current result is for the given index and type
|
||||
if hit['_type'] == index.es_type_key and hit['_source']['type'] == type.key
|
||||
# get matching objects
|
||||
user = get_item(@users, hit['_source']['userId'])
|
||||
subtype = get_item(type.statistic_sub_types, hit['_source']['subType'], 'key')
|
||||
# start to fill data and associated styles and data-types
|
||||
data = [
|
||||
Date::strptime(hit['_source']['date'],'%Y-%m-%d'),
|
||||
(user ? user.profile.full_name : "ID #{hit['_source']['userId']}"),
|
||||
(user ? user.email : ''),
|
||||
(user ? user.profile.phone : ''),
|
||||
t("export.#{hit['_source']['gender']}"),
|
||||
hit['_source']['age'],
|
||||
subtype.nil? ? "" : subtype.label
|
||||
]
|
||||
styles = [date, nil, nil, nil, nil, nil, nil]
|
||||
types = [:date, :string, :string, :string, :string, :integer, :string]
|
||||
# do not proceed with the 'stat' field if the type is declared as 'simple'
|
||||
unless type.simple
|
||||
data.push hit['_source']['stat']
|
||||
styles.push nil
|
||||
types.push :string
|
||||
end
|
||||
# proceed additional fields
|
||||
index.statistic_fields.each do |f|
|
||||
field_data = hit['_source'][f.key]
|
||||
case f.data_type
|
||||
when 'date'
|
||||
data.push Date::strptime(field_data, '%Y-%m-%d')
|
||||
styles.push date
|
||||
types.push :date
|
||||
when 'list'
|
||||
data.push field_data.map{|e| e['name'] }.join(', ')
|
||||
styles.push nil
|
||||
types.push :string
|
||||
when 'number'
|
||||
data.push field_data
|
||||
styles.push nil
|
||||
types.push :float
|
||||
else
|
||||
data.push field_data
|
||||
styles.push nil
|
||||
types.push :string
|
||||
end
|
||||
|
||||
end
|
||||
# proceed the 'ca' field if requested
|
||||
if index.ca
|
||||
data.push hit['_source']['ca']
|
||||
styles.push nil
|
||||
types.push :float
|
||||
end
|
||||
# finally, add the data row to the workbook's sheet
|
||||
sheet.add_row data, :style => styles, :types => types
|
||||
end
|
||||
end
|
||||
# finally, add the data row to the workbook's sheet
|
||||
sheet.add_row data, style: styles, types: types
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -474,6 +474,7 @@ en:
|
||||
type: "Type"
|
||||
male: "Man"
|
||||
female: "Woman"
|
||||
deleted_user: "Deleted user"
|
||||
#initial price's category for events, created to replace the old "reduced amount" property
|
||||
price_category:
|
||||
reduced_fare: "Reduced fare"
|
||||
|
@ -19,8 +19,8 @@ cd /apps/fabmanager/
|
||||
docker-compose exec postgres bash
|
||||
cd /var/lib/postgresql/data/
|
||||
DB=$(psql -U postgres -c \\l | grep production | awk '{print $1}')
|
||||
pg_dump -U postgres "$DB" > "$DB_$(date -I).sql"
|
||||
tar cvzf "fabmanager_database_dump_$(date -I).tar.gz" "$DB_$(date -I).sql"
|
||||
pg_dump -U postgres "$DB" > "${DB}_$(date -I).sql"
|
||||
tar cvzf "fabmanager_database_dump_$(date -I).tar.gz" "${DB}_$(date -I).sql"
|
||||
```
|
||||
|
||||
If you're connected to your server thought SSH, you can download the resulting dump file using the following:
|
||||
@ -30,14 +30,15 @@ scp root@remote.server.fab:/apps/fabmanager/postgresql/fabmanager_database_dump_
|
||||
|
||||
Restore the dump with the following:
|
||||
```bash
|
||||
DUMP=$(tar -tvf "fabmanager_database_dump_$(date -I).tar.gz" | awk '{print $6}')
|
||||
tar xvf fabmanager_database_dump_$(date -I).tar.gz
|
||||
sudo cp fabmanager_production_$(date -I).sql /apps/fabmanager/postgresql/
|
||||
sudo cp "$DUMP" /apps/fabmanager/postgresql/
|
||||
cd /apps/fabmanager/
|
||||
docker-compose down
|
||||
docker-compose up -d postgres
|
||||
docker-compose exec postgres dropdb -U postgres fabmanager_production
|
||||
docker-compose exec postgres createdb -U postgres fabmanager_production
|
||||
docker-compose exec postgres psql -U postgres -d fabmanager_production -f /var/lib/postgresql/data/fabmanager_production_$(date -I).sql
|
||||
docker-compose exec postgres psql -U postgres -d fabmanager_production -f "/var/lib/postgresql/data/${DUMP}"
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user