diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bba52116..9ffb1e367 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog Fab-manager +## v5.5.6 2022 December 05 + +- Updated FabAnalytics reports to include new features +- Fix a bug: setting somes decimal amounts (e.g. 4,85) result in another amount (e.g. 4,84) +- 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 - Soft destroy of spaces and machines diff --git a/app/controllers/api/coupons_controller.rb b/app/controllers/api/coupons_controller.rb index 8081a7bf7..616248c96 100644 --- a/app/controllers/api/coupons_controller.rb +++ b/app/controllers/api/coupons_controller.rb @@ -3,6 +3,8 @@ # API Controller for resources of type Coupon # Coupons are used in payments class API::CouponsController < API::ApiController + include ApplicationHelper + before_action :authenticate_user!, except: %i[validate] before_action :set_coupon, only: %i[show update destroy] @@ -31,14 +33,13 @@ class API::CouponsController < API::ApiController if @coupon.nil? render json: { status: 'rejected' }, status: :not_found else - _user_id = if current_user&.admin? - params[:user_id] - else - current_user&.id - end + user_id = if current_user&.admin? + params[:user_id] + else + current_user&.id + end - amount = params[:amount].to_f * 100.0 - status = @coupon.status(_user_id, amount) + status = @coupon.status(user_id, to_centimes(params[:amount])) if status == 'active' render :validate, status: :ok, location: @coupon else @@ -89,7 +90,7 @@ class API::CouponsController < API::ApiController @parameters else @parameters = params - @parameters[:coupon][:amount_off] = @parameters[:coupon][:amount_off].to_f * 100.0 if @parameters[:coupon][:amount_off] + @parameters[:coupon][:amount_off] = to_centimes(@parameters[:coupon][:amount_off]) if @parameters[:coupon][:amount_off] @parameters = @parameters.require(:coupon).permit(:name, :code, :percent_off, :amount_off, :validity_per_user, :valid_until, :max_usages, :active) diff --git a/app/controllers/api/machines_controller.rb b/app/controllers/api/machines_controller.rb index 1879bd183..810f08b4a 100644 --- a/app/controllers/api/machines_controller.rb +++ b/app/controllers/api/machines_controller.rb @@ -37,11 +37,8 @@ class API::MachinesController < API::ApiController def destroy authorize @machine - if @machine.destroyable? - @machine.destroy - else - @machine.soft_destroy! - end + method = @machine.destroyable? ? :destroy : :soft_destroy! + @machine.send(method) head :no_content end diff --git a/app/controllers/api/plans_controller.rb b/app/controllers/api/plans_controller.rb index c43f2549c..8b5ce97a2 100644 --- a/app/controllers/api/plans_controller.rb +++ b/app/controllers/api/plans_controller.rb @@ -4,7 +4,9 @@ # Plan are used to define subscription's characteristics. # PartnerPlan is a special kind of plan which send notifications to an external user class API::PlansController < API::ApiController - before_action :authenticate_user!, except: [:index, :durations] + include ApplicationHelper + + before_action :authenticate_user!, except: %i[index durations] def index @plans = Plan.includes(:plan_file) @@ -70,10 +72,10 @@ class API::PlansController < API::ApiController @parameters else @parameters = params - @parameters[:plan][:amount] = @parameters[:plan][:amount].to_f * 100.0 if @parameters[:plan][:amount] + @parameters[:plan][:amount] = to_centimes(@parameters[:plan][:amount]) if @parameters[:plan][:amount] if @parameters[:plan][:prices_attributes] @parameters[:plan][:prices_attributes] = @parameters[:plan][:prices_attributes].map do |price| - { amount: price[:amount].to_f * 100.0, id: price[:id] } + { amount: to_centimes(price[:amount]), id: price[:id] } end end diff --git a/app/controllers/api/prepaid_packs_controller.rb b/app/controllers/api/prepaid_packs_controller.rb index 29e0104f4..8b02c6196 100644 --- a/app/controllers/api/prepaid_packs_controller.rb +++ b/app/controllers/api/prepaid_packs_controller.rb @@ -3,6 +3,8 @@ # API Controller for resources of type PrepaidPack # PrepaidPacks are used to provide discounts to users that bought many hours at once class API::PrepaidPacksController < API::ApiController + include ApplicationHelper + before_action :authenticate_user!, except: :index before_action :set_pack, only: %i[show update destroy] @@ -46,7 +48,7 @@ class API::PrepaidPacksController < API::ApiController def pack_params pack_params = params - pack_params[:pack][:amount] = pack_params[:pack][:amount].to_f * 100.0 if pack_params[:pack][:amount] + pack_params[:pack][:amount] = to_centimes(pack_params[:pack][:amount]) if pack_params[:pack][:amount] params.require(:pack).permit(:priceable_id, :priceable_type, :group_id, :amount, :minutes, :validity_count, :validity_interval, :disabled) end diff --git a/app/controllers/api/prices_controller.rb b/app/controllers/api/prices_controller.rb index fdcf6de1b..ab48e119e 100644 --- a/app/controllers/api/prices_controller.rb +++ b/app/controllers/api/prices_controller.rb @@ -3,12 +3,14 @@ # API Controller for resources of type Price # Prices are used in reservations (Machine, Space) class API::PricesController < API::ApiController + include ApplicationHelper + before_action :authenticate_user! before_action :set_price, only: %i[update destroy] def create @price = Price.new(price_create_params) - @price.amount *= 100 + @price.amount = to_centimes(price_create_params[:amount]) authorize @price @@ -26,7 +28,7 @@ class API::PricesController < API::ApiController def update authorize Price price_parameters = price_params - price_parameters[:amount] = price_parameters[:amount] * 100 + price_parameters[:amount] = to_centimes(price_parameters[:amount]) if @price.update(price_parameters) render status: :ok else diff --git a/app/controllers/api/pricing_controller.rb b/app/controllers/api/pricing_controller.rb index 990d3d7a3..78b13bf61 100644 --- a/app/controllers/api/pricing_controller.rb +++ b/app/controllers/api/pricing_controller.rb @@ -1,8 +1,12 @@ # frozen_string_literal: true +# @deprecated +# DEPRECATED: Please use API::PriceController instead. # API Controller for managing Plans prices class API::PricingController < API::ApiController - before_action :authenticate_user!, except: %i[index show] + include ApplicationHelper + + before_action :authenticate_user!, except: %i[index update] def index @group_pricing = Group.includes(:plans, :trainings_pricings) @@ -19,10 +23,10 @@ class API::PricingController < API::ApiController next unless group training_pricing = group.trainings_pricings.find_or_initialize_by(training_id: training.id) - training_pricing.amount = amount * 100 + training_pricing.amount = to_centimes(amount) training_pricing.save end end - head 200 + head :ok end end diff --git a/app/controllers/api/spaces_controller.rb b/app/controllers/api/spaces_controller.rb index 75390c85d..ac48ce512 100644 --- a/app/controllers/api/spaces_controller.rb +++ b/app/controllers/api/spaces_controller.rb @@ -3,6 +3,7 @@ # API Controller for resources of type Space class API::SpacesController < API::ApiController before_action :authenticate_user!, except: %i[index show] + before_action :set_space, only: %i[update destroy] respond_to :json def index @@ -27,7 +28,6 @@ class API::SpacesController < API::ApiController def update authorize Space - @space = get_space if @space.update(space_params) render :show, status: :ok, location: @space else @@ -36,19 +36,15 @@ class API::SpacesController < API::ApiController end def destroy - @space = get_space authorize @space - if @space.destroyable? - @space.destroy - else - @space.soft_destroy! - end + method = @space.destroyable? ? :destroy : :soft_destroy! + @space.send(method) head :no_content end private - def get_space + def set_space Space.friendly.find(params[:id]) end diff --git a/app/controllers/api/trainings_pricings_controller.rb b/app/controllers/api/trainings_pricings_controller.rb index 4e75fe51d..47f0e39f2 100644 --- a/app/controllers/api/trainings_pricings_controller.rb +++ b/app/controllers/api/trainings_pricings_controller.rb @@ -1,7 +1,11 @@ # frozen_string_literal: true +# @deprecated +# DEPRECATED: Please use API::PriceController instead. # API Controller for managing Training prices class API::TrainingsPricingsController < API::ApiController + include ApplicationHelper + before_action :authenticate_user! def index @@ -12,14 +16,14 @@ class API::TrainingsPricingsController < API::ApiController if current_user.admin? @trainings_pricing = TrainingsPricing.find(params[:id]) trainings_pricing_parameters = trainings_pricing_params - trainings_pricing_parameters[:amount] = trainings_pricing_parameters[:amount] * 100 + trainings_pricing_parameters[:amount] = to_centimes(trainings_pricing_parameters[:amount]) if @trainings_pricing.update(trainings_pricing_parameters) render status: :ok else render status: :unprocessable_entity end else - head 403 + head :forbidden end end diff --git a/app/controllers/api/wallet_controller.rb b/app/controllers/api/wallet_controller.rb index 159ee0a7e..846c3981a 100644 --- a/app/controllers/api/wallet_controller.rb +++ b/app/controllers/api/wallet_controller.rb @@ -14,7 +14,7 @@ class API::WalletController < API::ApiController def transactions @wallet = Wallet.find(params[:id]) authorize @wallet - @wallet_transactions = @wallet.wallet_transactions.includes(:invoice, :invoicing_profile).order(created_at: :desc) + @wallet_transactions = @wallet.wallet_transactions.includes(:invoice, :invoicing_profile, :payment_schedule).order(created_at: :desc) end def credit @@ -23,7 +23,7 @@ class API::WalletController < API::ApiController @wallet = Wallet.find(credit_params[:id]) authorize @wallet service = WalletService.new(user: current_user, wallet: @wallet) - transaction = service.credit(credit_params[:amount].to_f) + transaction = service.credit(credit_params[:amount]) if transaction service.create_avoir(transaction, credit_params[:avoir_date], credit_params[:avoir_description]) if credit_params[:avoir] render :show diff --git a/app/controllers/open_api/v1/machines_controller.rb b/app/controllers/open_api/v1/machines_controller.rb index 88462a071..9d11a01cb 100644 --- a/app/controllers/open_api/v1/machines_controller.rb +++ b/app/controllers/open_api/v1/machines_controller.rb @@ -8,7 +8,7 @@ class OpenAPI::V1::MachinesController < OpenAPI::V1::BaseController before_action :set_machine, only: %i[show update destroy] def index - @machines = Machine.order(:created_at) + @machines = Machine.order(:created_at).where(deleted_at: nil) end def create @@ -28,15 +28,14 @@ class OpenAPI::V1::MachinesController < OpenAPI::V1::BaseController end end - def show; end + def show + head :not_found if @machine.deleted_at + end def destroy - if @machine.destroyable? - @machine.destroy - head :no_content - else - render json: { error: 'has existing reservations' }, status: :unprocessable_entity - end + method = @machine.destroyable? ? :destroy : :soft_destroy! + @machine.send(method) + head :no_content end private diff --git a/app/controllers/open_api/v1/spaces_controller.rb b/app/controllers/open_api/v1/spaces_controller.rb index 06918d67f..18dcf9dd8 100644 --- a/app/controllers/open_api/v1/spaces_controller.rb +++ b/app/controllers/open_api/v1/spaces_controller.rb @@ -8,7 +8,7 @@ class OpenAPI::V1::SpacesController < OpenAPI::V1::BaseController before_action :set_space, only: %i[show] def index - @spaces = Space.order(:created_at) + @spaces = Space.order(:created_at).where(deleted_at: nil) end def show; end diff --git a/app/frontend/templates/admin/settings/analyticsModal.html b/app/frontend/templates/admin/settings/analyticsModal.html index f8f988d5a..0236e545e 100644 --- a/app/frontend/templates/admin/settings/analyticsModal.html +++ b/app/frontend/templates/admin/settings/analyticsModal.html @@ -9,11 +9,20 @@ {{ 'app.admin.settings.privacy.analytics.version' }}{{ data.version }} {{ 'app.admin.settings.privacy.analytics.members' }}{{ data.members }} {{ 'app.admin.settings.privacy.analytics.admins' }}{{ data.admins }} + {{ 'app.admin.settings.privacy.analytics.managers' }}{{ data.managers }} {{ 'app.admin.settings.privacy.analytics.availabilities' }}{{ data.availabilities }} {{ 'app.admin.settings.privacy.analytics.reservations' }}{{ data.reservations }} + {{ 'app.admin.settings.privacy.analytics.orders' }}{{ data.orders }} {{ 'app.admin.settings.privacy.analytics.plans' }}{{ data.plans }} {{ 'app.admin.settings.privacy.analytics.spaces' }}{{ data.spaces }} {{ 'app.admin.settings.privacy.analytics.online_payment' }}{{ data.online_payment }} + {{ 'app.admin.settings.privacy.analytics.gateway' }}{{ data.gateway }} + {{ 'app.admin.settings.privacy.analytics.wallet' }}{{ data.wallet }} + {{ 'app.admin.settings.privacy.analytics.statistics' }}{{ data.statistics }} + {{ 'app.admin.settings.privacy.analytics.trainings' }}{{ data.trainings }} + {{ 'app.admin.settings.privacy.analytics.public_agenda' }}{{ data.public_agenda }} + {{ 'app.admin.settings.privacy.analytics.machines' }}{{ data.machines }} + {{ 'app.admin.settings.privacy.analytics.store' }}{{ data.store }} {{ 'app.admin.settings.privacy.analytics.invoices' }}{{ data.invoices }} {{ 'app.admin.settings.privacy.analytics.openlab' }}{{ data.openlab }} diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 60ef47237..1a0846ae0 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -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} @@ -95,9 +78,15 @@ module ApplicationHelper res end + # Return the given amount in centimes, without floating-point imprecision errors + def to_centimes(amount) + (BigDecimal(amount.to_s) * 100.0).to_f + end + 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 +96,7 @@ module ApplicationHelper key end end + # rubocop:enable Rails/HelperInstanceVariable def html_safe_translation_key?(key) key.to_s =~ /(\b|_|\.)html$/ diff --git a/app/helpers/excel_helper.rb b/app/helpers/excel_helper.rb new file mode 100644 index 000000000..81653b6d2 --- /dev/null +++ b/app/helpers/excel_helper.rb @@ -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 diff --git a/app/models/concerns/amount_concern.rb b/app/models/concerns/amount_concern.rb index b744f85ac..d855de409 100644 --- a/app/models/concerns/amount_concern.rb +++ b/app/models/concerns/amount_concern.rb @@ -5,13 +5,14 @@ module AmountConcern extend ActiveSupport::Concern included do - validates_numericality_of :amount, greater_than_or_equal_to: 0 + include ApplicationHelper + validates :amount, numericality: { greater_than_or_equal_to: 0 } def amount=(amount) if amount.nil? write_attribute(:amount, amount) else - write_attribute(:amount, (amount * 100).to_i) + write_attribute(:amount, to_centimes(amount)) end end diff --git a/app/services/event_service.rb b/app/services/event_service.rb index 4e5eba4b1..f6b68dbdf 100644 --- a/app/services/event_service.rb +++ b/app/services/event_service.rb @@ -1,8 +1,12 @@ # frozen_string_literal: true +require './app/helpers/application_helper' + # Provides helper methods for Events resources and properties class EventService class << self + include ApplicationHelper + def process_params(params) # handle dates & times (whole-day events or not, maybe during many days) range = EventService.date_range({ date: params[:start_date], time: params[:start_time] }, @@ -14,14 +18,14 @@ class EventService available_type: 'event' }) .extract!(:start_date, :end_date, :start_time, :end_time, :all_day) # convert main price to centimes - params[:amount] = (params[:amount].to_f * 100 if params[:amount].present?) + params[:amount] = to_centimes(params[:amount]) if params[:amount].present? # delete non-complete "other" prices and convert them to centimes unless params[:event_price_categories_attributes].nil? params[:event_price_categories_attributes].delete_if do |price_cat| price_cat[:price_category_id].empty? || price_cat[:amount].empty? end params[:event_price_categories_attributes].each do |price_cat| - price_cat[:amount] = price_cat[:amount].to_f * 100 + price_cat[:amount] = to_centimes(price_cat[:amount]) end end # return the resulting params object diff --git a/app/services/export_service.rb b/app/services/export_service.rb index a77d07c57..51e826a97 100644 --- a/app/services/export_service.rb +++ b/app/services/export_service.rb @@ -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 diff --git a/app/services/health_service.rb b/app/services/health_service.rb index 0c82bbf47..d74047328 100644 --- a/app/services/health_service.rb +++ b/app/services/health_service.rb @@ -12,7 +12,7 @@ class HealthService end def self.redis? - !!Sidekiq.redis(&:info) # rubocop:disable Style/DoubleNegation + !!Sidekiq.redis(&:info) rescue Redis::CannotConnectError false end @@ -22,10 +22,8 @@ class HealthService client = Elasticsearch::Client.new host: "http://#{Rails.application.secrets.elaticsearch_host}:9200" response = client.perform_request 'GET', '_cluster/health' - !!response.body # rubocop:disable Style/DoubleNegation - rescue Elasticsearch::Transport::Transport::Error - false - rescue Faraday::ConnectionFailed + !!response.body + rescue Elasticsearch::Transport::Transport::Error, Faraday::ConnectionFailed false end @@ -39,11 +37,20 @@ class HealthService version: Version.current, members: User.members.count, admins: User.admins.count, + managers: User.managers.count, availabilities: last_week_availabilities, reservations: last_week_new_reservations, + orders: last_week_orders, plans: Setting.get('plans_module'), spaces: Setting.get('spaces_module'), online_payment: Setting.get('online_payment_module'), + gateway: Setting.get('payment_gateway'), + wallet: Setting.get('wallet_module'), + statistics: Setting.get('statistics_module'), + trainings: Setting.get('trainings_module'), + public_agenda: Setting.get('public_agenda_module'), + machines: Setting.get('machines_module'), + store: Setting.get('store_module'), invoices: Setting.get('invoicing_module'), openlab: Setting.get('openlab_app_secret').present? } @@ -74,5 +81,8 @@ class HealthService def self.last_week_new_reservations Reservation.where('created_at >= ? AND created_at < ?', DateTime.current - 7.days, DateTime.current).count end -end + def self.last_week_orders + Order.where('created_at >= ? AND created_at < ?', DateTime.current - 7.days, DateTime.current).where.not(state: 'cart').count + end +end diff --git a/app/services/product_service.rb b/app/services/product_service.rb index f42d67527..cf3317898 100644 --- a/app/services/product_service.rb +++ b/app/services/product_service.rb @@ -1,8 +1,12 @@ # frozen_string_literal: true +require './app/helpers/application_helper' + # Provides methods for Product class ProductService class << self + include ApplicationHelper + PRODUCTS_PER_PAGE = 12 MOVEMENTS_PER_PAGE = 10 @@ -28,11 +32,8 @@ class ProductService # amount params multiplied by hundred def amount_multiplied_by_hundred(amount) - if amount.present? - v = amount.to_f + return to_centimes(amount) if amount.present? - return v * 100 - end nil end diff --git a/app/services/statistics/builders/reservations_builder_service.rb b/app/services/statistics/builders/reservations_builder_service.rb index 57a1d7a3a..6c6bea826 100644 --- a/app/services/statistics/builders/reservations_builder_service.rb +++ b/app/services/statistics/builders/reservations_builder_service.rb @@ -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 diff --git a/app/services/statistics_export_service.rb b/app/services/statistics_export_service.rb index 43422b664..06aeeb21d 100644 --- a/app/services/statistics_export_service.rb +++ b/app/services/statistics_export_service.rb @@ -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 diff --git a/app/views/exports/statistics_current.xlsx.axlsx b/app/views/exports/statistics_current.xlsx.axlsx index 76c676854..e4b8062c4 100644 --- a/app/views/exports/statistics_current.xlsx.axlsx +++ b/app/views/exports/statistics_current.xlsx.axlsx @@ -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 \ No newline at end of file +end diff --git a/app/views/exports/statistics_global.xlsx.axlsx b/app/views/exports/statistics_global.xlsx.axlsx index f2953ff66..2b9a723d5 100644 --- a/app/views/exports/statistics_global.xlsx.axlsx +++ b/app/views/exports/statistics_global.xlsx.axlsx @@ -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 \ No newline at end of file +end diff --git a/config/locales/app.admin.de.yml b/config/locales/app.admin.de.yml index 0491d24f2..810a9613a 100644 --- a/config/locales/app.admin.de.yml +++ b/config/locales/app.admin.de.yml @@ -1653,11 +1653,20 @@ de: version: "Anwendungsversion" members: "Mitgliederanzahl" admins: "Anzahl der Administratoren" + managers: "Number of managers" availabilities: "Anzahl der Verfügbarkeiten der letzten 7 Tage" reservations: "Anzahl der Reservierungen in den letzten 7 Tagen" + orders: "Number of store orders during the last 7 days" plans: "Ist das Abonnement-Modul aktiv?" spaces: "Ist das Raum-Management-Modul aktiv?" online_payment: "Ist das Online-Zahlungsmodul aktiv?" + gateway: "The payment gateway used to collect online payments" + wallet: "Is the wallet module active?" + statistics: "Is the statistics module active?" + trainings: "Is the trainings module active?" + public_agenda: "Is the public agenda module active?" + machines: "Is the machines module active?" + store: "Is the store module active?" invoices: "Ist das Rechnungsmodul aktiv?" openlab: "Ist das Projektteilungsmodul (OpenLab) aktiv?" tracking_id_info_html: "Um die statistische Analyse der Besuche mithilfe von Google Analytics V4 zu ermöglichen, tragen Sie hier Ihre Tracking-ID in der Form G-XXXXXX ein. Besuchen Sie die Google Analytics Website, um eine Tracking-ID zu erstellen.
Warnung: wenn Sie dieses Feature aktivieren, wird ein Cookie erstellt. Denken Sie daran, es oben in Ihrer Datenschutzrichtlinie darauf hingewiesen werden." diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 7419041eb..72847b85e 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -1653,11 +1653,20 @@ en: version: "Application version" members: "Number of members" admins: "Number of administrators" + managers: "Number of managers" availabilities: "Number of availabilities of the last 7 days" reservations: "Number of reservations during the last 7 days" + orders: "Number of store orders during the last 7 days" plans: "Is the subscription module active?" spaces: "Is the space management module active?" online_payment: "Is the online payment module active?" + gateway: "The payment gateway used to collect online payments" + wallet: "Is the wallet module active?" + statistics: "Is the statistics module active?" + trainings: "Is the trainings module active?" + public_agenda: "Is the public agenda module active?" + machines: "Is the machines module active?" + store: "Is the store module active?" invoices: "Is the invoicing module active?" openlab: "Is the project sharing module (OpenLab) active?" tracking_id_info_html: "To enable the statistical tracking of the visits using Google Analytics V4, set your tracking ID here. It is in the form G-XXXXXX. Visit the Google Analytics website to get one.
Warning: if you enable this feature, a cookie will be created. Remember to write it down in your privacy policy, above." diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index 46ff635b9..77b2de597 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -1653,11 +1653,20 @@ es: version: "Application version" members: "Number of members" admins: "Number of administrators" + managers: "Number of managers" availabilities: "Number of availabilities of the last 7 days" reservations: "Number of reservations during the last 7 days" + orders: "Number of store orders during the last 7 days" plans: "Is the subscription module active?" spaces: "Is the space management module active?" online_payment: "Is the online payment module active?" + gateway: "The payment gateway used to collect online payments" + wallet: "Is the wallet module active?" + statistics: "Is the statistics module active?" + trainings: "Is the trainings module active?" + public_agenda: "Is the public agenda module active?" + machines: "Is the machines module active?" + store: "Is the store module active?" invoices: "Is the invoicing module active?" openlab: "Is the project sharing module (OpenLab) active?" tracking_id_info_html: "To enable the statistical tracking of the visits using Google Analytics V4, set your tracking ID here. It is in the form G-XXXXXX. Visit the Google Analytics website to get one.
Warning: if you enable this feature, a cookie will be created. Remember to write it down in your privacy policy, above." diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 2996db4cc..75971a2db 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -1653,11 +1653,20 @@ fr: version: "Version de l'application" members: "Nombre de membres" admins: "Nombre d'administrateurs" + managers: "Nombre de gestionnaires" availabilities: "Nombre de disponibilités les 7 dernier jours" reservations: "Nombre de réservations durant 7 dernier jours" + orders: "Nombre de commandes dans la boutique au cours des 7 derniers jours" plans: "Le module d'abonnements est-il actif ?" spaces: "Le module d'espaces est-il actif ?" online_payment: "Le module de paiement par carte bancaire est-il actif ?" + gateway: "La passerelle de paiement utilisée pour collecter les paiements en ligne" + wallet: "Le module porte-monnaie est-il actif ?" + statistics: "Le module de statistiques est-il actif ?" + trainings: "Le module de formation est-il actif ?" + public_agenda: "Le module d'agenda public est-il actif ?" + machines: "Le module machines est-il actif ?" + store: "Le module boutique est-il actif ?" invoices: "Le module est facturation est-il actif ?" openlab: "Le module de partage de projets (OpenLab) est-il actif ?" tracking_id_info_html: "Pour activer les suivi statistique des visites utilisant Google Analytics V4, définissez ici votre ID de suivi. Il se présente sous la forme G-XXXXXX-X. Visitez le site web de Google Analytics pour en obtenir un.
Attention : si vous activez cette fonctionnalité, un cookie sera créé. Pensez à l'indiquer dans votre politique de confidentialité, ci-dessus." diff --git a/config/locales/app.admin.no.yml b/config/locales/app.admin.no.yml index 2402060c8..524bec6a1 100644 --- a/config/locales/app.admin.no.yml +++ b/config/locales/app.admin.no.yml @@ -1653,11 +1653,20 @@ version: "Application version" members: "Number of members" admins: "Number of administrators" + managers: "Number of managers" availabilities: "Number of availabilities of the last 7 days" reservations: "Number of reservations during the last 7 days" + orders: "Number of store orders during the last 7 days" plans: "Is the subscription module active?" spaces: "Is the space management module active?" online_payment: "Is the online payment module active?" + gateway: "The payment gateway used to collect online payments" + wallet: "Is the wallet module active?" + statistics: "Is the statistics module active?" + trainings: "Is the trainings module active?" + public_agenda: "Is the public agenda module active?" + machines: "Is the machines module active?" + store: "Is the store module active?" invoices: "Is the invoicing module active?" openlab: "Is the project sharing module (OpenLab) active?" tracking_id_info_html: "To enable the statistical tracking of the visits using Google Analytics V4, set your tracking ID here. It is in the form G-XXXXXX. Visit the Google Analytics website to get one.
Warning: if you enable this feature, a cookie will be created. Remember to write it down in your privacy policy, above." diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index e6b848614..356485bb8 100644 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -1653,11 +1653,20 @@ pt: version: "Versão do aplicativo" members: "Número de membros" admins: "Número de administradores" + managers: "Number of managers" availabilities: "Número de disponibilidades dos últimos 7 dias" reservations: "Número de reservas nos últimos 7 dias" + orders: "Number of store orders during the last 7 days" plans: "O módulo de assinatura está ativo?" spaces: "O módulo de gerenciamento de espaço está ativo?" online_payment: "O módulo de pagamento online está ativo?" + gateway: "The payment gateway used to collect online payments" + wallet: "Is the wallet module active?" + statistics: "Is the statistics module active?" + trainings: "Is the trainings module active?" + public_agenda: "Is the public agenda module active?" + machines: "Is the machines module active?" + store: "Is the store module active?" invoices: "O módulo de faturação está ativo?" openlab: "O módulo de compartilhamento de projetos (OpenLab) está ativo?" tracking_id_info_html: "Para ativar o rastreamento estatístico das visitas usando o Google Analytics, defina seu ID de rastreamento aqui. Está na forma de G-XXXXXX. Visite o site do Google Analytics para obter um.
Aviso: se você ativar este recurso, um cookie será criado. Lembre-se de escrevê-lo na sua política de privacidade." diff --git a/config/locales/app.admin.zu.yml b/config/locales/app.admin.zu.yml index 27d20ae23..2f075770b 100644 --- a/config/locales/app.admin.zu.yml +++ b/config/locales/app.admin.zu.yml @@ -1653,11 +1653,20 @@ zu: version: "crwdns27010:0crwdne27010:0" members: "crwdns27012:0crwdne27012:0" admins: "crwdns27014:0crwdne27014:0" + managers: "crwdns31729:0crwdne31729:0" availabilities: "crwdns27016:0crwdne27016:0" reservations: "crwdns27018:0crwdne27018:0" + orders: "crwdns31731:0crwdne31731:0" plans: "crwdns27020:0crwdne27020:0" spaces: "crwdns27022:0crwdne27022:0" online_payment: "crwdns27024:0crwdne27024:0" + gateway: "crwdns31733:0crwdne31733:0" + wallet: "crwdns31735:0crwdne31735:0" + statistics: "crwdns31737:0crwdne31737:0" + trainings: "crwdns31739:0crwdne31739:0" + public_agenda: "crwdns31741:0crwdne31741:0" + machines: "crwdns31743:0crwdne31743:0" + store: "crwdns31745:0crwdne31745:0" invoices: "crwdns27026:0crwdne27026:0" openlab: "crwdns27028:0crwdne27028:0" tracking_id_info_html: "crwdns27030:0crwdne27030:0" diff --git a/config/locales/app.shared.de.yml b/config/locales/app.shared.de.yml index 551b3aa37..c80bd85aa 100644 --- a/config/locales/app.shared.de.yml +++ b/config/locales/app.shared.de.yml @@ -110,7 +110,7 @@ de: password_input: new_password: "New password" confirm_password: "Confirm password" - password_too_short: "Password is too short (must be at least 8 characters)" + password_too_short: "Password is too short (must be at least 12 characters)" confirmation_mismatch: "Confirmation mismatch with password." #project edition form project: diff --git a/config/locales/app.shared.en.yml b/config/locales/app.shared.en.yml index 5b693e267..8dbc6c09b 100644 --- a/config/locales/app.shared.en.yml +++ b/config/locales/app.shared.en.yml @@ -110,7 +110,7 @@ en: password_input: new_password: "New password" confirm_password: "Confirm password" - password_too_short: "Password is too short (must be at least 8 characters)" + password_too_short: "Password is too short (must be at least 12 characters)" confirmation_mismatch: "Confirmation mismatch with password." #project edition form project: diff --git a/config/locales/app.shared.es.yml b/config/locales/app.shared.es.yml index c6ed0eae9..5385f39d2 100644 --- a/config/locales/app.shared.es.yml +++ b/config/locales/app.shared.es.yml @@ -110,7 +110,7 @@ es: password_input: new_password: "New password" confirm_password: "Confirm password" - password_too_short: "Password is too short (must be at least 8 characters)" + password_too_short: "Password is too short (must be at least 12 characters)" confirmation_mismatch: "Confirmation mismatch with password." #project edition form project: diff --git a/config/locales/app.shared.fr.yml b/config/locales/app.shared.fr.yml index 963aa0cc7..37bcfda27 100644 --- a/config/locales/app.shared.fr.yml +++ b/config/locales/app.shared.fr.yml @@ -110,7 +110,7 @@ fr: password_input: new_password: "Nouveau mot de passe" confirm_password: "Confirmez le mot de passe" - password_too_short: "Le mot de passe est trop court (doit contenir au moins 8 caractères)" + password_too_short: "Le mot de passe est trop court (doit contenir au moins 12 caractères)" confirmation_mismatch: "La confirmation ne concorde pas avec le mot de passe." #project edition form project: diff --git a/config/locales/app.shared.no.yml b/config/locales/app.shared.no.yml index b658b6e81..24c220a12 100644 --- a/config/locales/app.shared.no.yml +++ b/config/locales/app.shared.no.yml @@ -110,7 +110,7 @@ password_input: new_password: "New password" confirm_password: "Confirm password" - password_too_short: "Password is too short (must be at least 8 characters)" + password_too_short: "Password is too short (must be at least 12 characters)" confirmation_mismatch: "Confirmation mismatch with password." #project edition form project: diff --git a/config/locales/app.shared.pt.yml b/config/locales/app.shared.pt.yml index 6c414b3ef..1239a6b51 100644 --- a/config/locales/app.shared.pt.yml +++ b/config/locales/app.shared.pt.yml @@ -110,7 +110,7 @@ pt: password_input: new_password: "Nova senha" confirm_password: "Confirmar a senha" - password_too_short: "Senha muito curta (mínimo 8 caracteres)" + password_too_short: "Password is too short (must be at least 12 characters)" confirmation_mismatch: "Confirmação de senha é diferente da senha." #project edition form project: diff --git a/config/locales/app.shared.zu.yml b/config/locales/app.shared.zu.yml index eaf1c0cc7..b5eea579a 100644 --- a/config/locales/app.shared.zu.yml +++ b/config/locales/app.shared.zu.yml @@ -110,7 +110,7 @@ zu: password_input: new_password: "crwdns28716:0crwdne28716:0" confirm_password: "crwdns28718:0crwdne28718:0" - password_too_short: "crwdns28720:0crwdne28720:0" + password_too_short: "crwdns31749:0crwdne31749:0" confirmation_mismatch: "crwdns28722:0crwdne28722:0" #project edition form project: diff --git a/config/locales/de.yml b/config/locales/de.yml index c49b7ac31..ce2d3a54d 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -474,6 +474,7 @@ de: type: "Typ" male: "Männlich" female: "Weiblich" + deleted_user: "Deleted user" #initial price's category for events, created to replace the old "reduced amount" property price_category: reduced_fare: "Ermäßigter Tarif" diff --git a/config/locales/en.yml b/config/locales/en.yml index 18f76841f..5dcc9c76f 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -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" diff --git a/config/locales/es.yml b/config/locales/es.yml index 75b12142c..2940a041e 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -474,6 +474,7 @@ es: type: "Tipo" male: "Hombre" female: "Mujer" + deleted_user: "Deleted user" #initial price's category for events, created to replace the old "reduced amount" property price_category: reduced_fare: "Tarifa reducida" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 62458c1c7..99ad4c9c0 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -474,6 +474,7 @@ fr: type: "Type" male: "Homme" female: "Femme" + deleted_user: "Utilisateur supprimé" #initial price's category for events, created to replace the old "reduced amount" property price_category: reduced_fare: "Tarif réduit" diff --git a/config/locales/no.yml b/config/locales/no.yml index 6a05cc42a..5c68b5f96 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -474,6 +474,7 @@ type: "Type" male: "Mann" female: "Kvinne" + deleted_user: "Deleted user" #initial price's category for events, created to replace the old "reduced amount" property price_category: reduced_fare: "Redusert avgift" diff --git a/config/locales/pt.yml b/config/locales/pt.yml index b41bbfa06..638ed419a 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -474,6 +474,7 @@ pt: type: "Tipo" male: "Homem" female: "Mulher" + deleted_user: "Deleted user" #initial price's category for events, created to replace the old "reduced amount" property price_category: reduced_fare: "Tarifa reduzida" diff --git a/config/locales/zu.yml b/config/locales/zu.yml index 4d580eb98..44f522b4d 100644 --- a/config/locales/zu.yml +++ b/config/locales/zu.yml @@ -474,6 +474,7 @@ zu: type: "crwdns3759:0crwdne3759:0" male: "crwdns3761:0crwdne3761:0" female: "crwdns3763:0crwdne3763:0" + deleted_user: "crwdns31747:0crwdne31747:0" #initial price's category for events, created to replace the old "reduced amount" property price_category: reduced_fare: "crwdns3765:0crwdne3765:0" diff --git a/doc/postgresql_readme.md b/doc/postgresql_readme.md index 36a1087dc..8de0d458e 100644 --- a/doc/postgresql_readme.md +++ b/doc/postgresql_readme.md @@ -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 ``` diff --git a/package.json b/package.json index 264204554..6f83acf6c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fab-manager", - "version": "5.5.5", + "version": "5.5.6", "description": "Fab-manager is the FabLab management solution. It provides a comprehensive, web-based, open-source tool to simplify your administrative tasks and your marker's projects.", "keywords": [ "fablab", diff --git a/test/fixtures/files/document.pdf b/test/fixtures/files/document.pdf new file mode 100644 index 000000000..8da0db0b5 Binary files /dev/null and b/test/fixtures/files/document.pdf differ diff --git a/test/fixtures/files/document2.pdf b/test/fixtures/files/document2.pdf new file mode 100644 index 000000000..2988b8d5d Binary files /dev/null and b/test/fixtures/files/document2.pdf differ diff --git a/test/fixtures/files/machines/Laser_cutting_machine.jpg b/test/fixtures/files/machines/Laser_cutting_machine.jpg new file mode 100644 index 000000000..72e0cb2b6 Binary files /dev/null and b/test/fixtures/files/machines/Laser_cutting_machine.jpg differ diff --git a/test/fixtures/files/spaces/Biology_laboratory.jpg b/test/fixtures/files/spaces/Biology_laboratory.jpg new file mode 100644 index 000000000..1834383e2 Binary files /dev/null and b/test/fixtures/files/spaces/Biology_laboratory.jpg differ diff --git a/test/fixtures/statistic_sub_types.yml b/test/fixtures/statistic_sub_types.yml index bb6ecc561..ee04c90ae 100644 --- a/test/fixtures/statistic_sub_types.yml +++ b/test/fixtures/statistic_sub_types.yml @@ -167,3 +167,11 @@ statistic_sub_type_24: label: Interrompue created_at: 2022-10-12 08:55:05.209986 Z updated_at: 2022-10-12 08:55:05.209986 Z + +statistic_sub_type_25: + id: 25 + key: atelier-bois + label: Atelier Bois + created_at: 2022-11-23 11:51:14.651651 Z + updated_at: 2022-11-23 11:51:14.651651 Z + diff --git a/test/fixtures/statistic_type_sub_types.yml b/test/fixtures/statistic_type_sub_types.yml index d031bd900..42be26ac4 100644 --- a/test/fixtures/statistic_type_sub_types.yml +++ b/test/fixtures/statistic_type_sub_types.yml @@ -258,3 +258,18 @@ statistic_type_sub_type_37: created_at: 2022-10-12 08:55:05.211284 updated_at: 2022-10-12 08:55:05.211284 +statistic_type_sub_type_38: + id: 38 + statistic_type_id: 12 + statistic_sub_type_id: 25 + created_at: 2022-11-23 11:51:14.651651 Z + updated_at: 2022-11-23 11:51:14.651651 Z + +statistic_type_sub_type_39: + id: 39 + statistic_type_id: 13 + statistic_sub_type_id: 25 + created_at: 2022-11-23 11:51:14.651651 Z + updated_at: 2022-11-23 11:51:14.651651 Z + + diff --git a/test/integration/machines_test.rb b/test/integration/machines_test.rb new file mode 100644 index 000000000..8a8c117d8 --- /dev/null +++ b/test/integration/machines_test.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'test_helper' + +class MachinesTest < ActionDispatch::IntegrationTest + def setup + @admin = User.find_by(username: 'admin') + login_as(@admin, scope: :user) + end + + test 'create a machine' do + name = 'IJFX 350 Laser' + post '/api/machines', + params: { + machine: { + name: name, + machine_image_attributes: { + attachment: fixture_file_upload('/files/machines/Laser_cutting_machine.jpg') + }, + description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore...', + spec: 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium...', + machine_files_attributes: [ + { attachment: fixture_file_upload('/files/document.pdf', 'document/pdf', true) }, + { attachment: fixture_file_upload('/files/document2.pdf', 'document/pdf', true) } + ], + disabled: false + } + }, + headers: upload_headers + + # Check response format & status + assert_equal 201, response.status, response.body + assert_equal Mime[:json], response.content_type + + # Check the machine was correctly created + db_machine = Machine.where(name: name).first + assert_not_nil db_machine + assert_not_nil db_machine.machine_image.attachment + assert_not_nil db_machine.machine_files[0].attachment + assert_not_nil db_machine.machine_files[1].attachment + assert_equal name, db_machine.name + assert_not_empty db_machine.spec + assert_not_empty db_machine.description + assert_not db_machine.disabled + assert_nil db_machine.deleted_at + end + + test 'update a machine' do + description = '

lorem ipsum dolor sit amet

' + put '/api/machines/3', + params: { + machine: { + description: description + } + }.to_json, + headers: default_headers + + # Check response format & status + assert_equal 200, response.status, response.body + assert_equal Mime[:json], response.content_type + + # Check the machine was correctly updated + db_machine = Machine.find(3) + assert_equal description, db_machine.description + machine = json_response(response.body) + assert_equal description, machine[:description] + end + + test 'delete a machine' do + delete '/api/machines/3', headers: default_headers + assert_response :success + assert_empty response.body + end + + test 'soft delete a machine' do + machine = Machine.find(4) + assert_not machine.destroyable? + delete '/api/machines/4', headers: default_headers + assert_response :success + assert_empty response.body + + machine.reload + assert_not_nil machine.deleted_at + end +end diff --git a/test/integration/open_api/machines_test.rb b/test/integration/open_api/machines_test.rb index 9aa00d5ee..50ac4c755 100644 --- a/test/integration/open_api/machines_test.rb +++ b/test/integration/open_api/machines_test.rb @@ -12,6 +12,8 @@ class OpenApi::MachinesTest < ActionDispatch::IntegrationTest test 'list all machines' do get '/open_api/v1/machines', headers: open_api_headers(@token) assert_response :success + machines = json_response(response.body) + assert_not_empty machines[:machines] end test 'create a machine' do @@ -19,7 +21,7 @@ class OpenApi::MachinesTest < ActionDispatch::IntegrationTest params: { machine: { name: 'IJFX 350 Laser', - description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore...', + description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et...', spec: 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium...', disabled: true } @@ -49,4 +51,15 @@ class OpenApi::MachinesTest < ActionDispatch::IntegrationTest delete '/open_api/v1/machines/3', headers: open_api_headers(@token) assert_response :success end + + test 'soft delete a machine' do + assert_not Machine.find(4).destroyable? + delete '/open_api/v1/machines/4', headers: open_api_headers(@token) + assert_response :success + get '/open_api/v1/machines/4', headers: open_api_headers(@token) + assert_response :not_found + get '/open_api/v1/machines', headers: open_api_headers(@token) + machines = json_response(response.body) + assert_not(machines[:machines].any? { |m| m[:id] == 4 }) + end end diff --git a/test/integration/spaces_test.rb b/test/integration/spaces_test.rb new file mode 100644 index 000000000..bcc34080c --- /dev/null +++ b/test/integration/spaces_test.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +require 'test_helper' + +class SpacesTest < ActionDispatch::IntegrationTest + def setup + @admin = User.find_by(username: 'admin') + login_as(@admin, scope: :user) + end + + test 'create a space' do + name = 'Biolab' + post '/api/spaces', + params: { + space: { + name: name, + space_image_attributes: { + attachment: fixture_file_upload('/files/spaces/Biology_laboratory.jpg') + }, + description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras ante mi, porta ac dictum quis, feugiat...', + characteristics: 'Sed fermentum ante ut elit lobortis, id auctor libero cursus. Sed augue lectus, mollis at luctus eu...', + default_places: 6, + space_files_attributes: [ + { attachment: fixture_file_upload('/files/document.pdf', 'document/pdf', true) }, + { attachment: fixture_file_upload('/files/document2.pdf', 'document/pdf', true) } + ], + disabled: false + } + }, + headers: upload_headers + + # Check response format & status + assert_equal 201, response.status, response.body + assert_equal Mime[:json], response.content_type + + # Check the space was correctly created + db_space = Space.where(name: name).first + assert_not_nil db_space + assert_not_nil db_space.space_image.attachment + assert_not_nil db_space.space_files[0].attachment + assert_not_nil db_space.space_files[1].attachment + assert_equal name, db_space.name + assert_equal 6, db_space.default_places + assert_not_empty db_space.characteristics + assert_not_empty db_space.description + assert_not db_space.disabled + assert_nil db_space.deleted_at + end + + test 'update a space' do + description = '

lorem ipsum dolor sit amet

' + put '/api/spaces/1', + params: { + space: { + description: description + } + }.to_json, + headers: default_headers + + # Check response format & status + assert_equal 200, response.status, response.body + assert_equal Mime[:json], response.content_type + + # Check the space was correctly updated + db_space = Space.find(1) + assert_equal description, db_space.description + space = json_response(response.body) + assert_equal description, space[:description] + end + + test 'delete a space' do + delete '/api/spaces/1', headers: default_headers + assert_response :success + assert_empty response.body + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 23ff51b82..17049da1d 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -41,6 +41,10 @@ class ActiveSupport::TestCase { 'Accept' => Mime[:json], 'Content-Type' => Mime[:json].to_s } end + def upload_headers + { 'Accept' => Mime[:json], 'Content-Type' => 'multipart/form-data' } + end + def open_api_headers(token) { 'Accept' => Mime[:json], 'Content-Type' => Mime[:json].to_s, 'Authorization' => "Token token=#{token}" } end