mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-20 14:54:15 +01:00
Merge branch 'dev' for release 5.5.6
This commit is contained in:
commit
a5c9a7d3ab
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -1,8 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# @deprecated
|
||||
# <b>DEPRECATED:</b> Please use <tt>API::PriceController</tt> 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
|
||||
|
@ -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
|
||||
|
||||
|
@ -1,7 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# @deprecated
|
||||
# <b>DEPRECATED:</b> Please use <tt>API::PriceController</tt> 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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -9,11 +9,20 @@
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.version' }}</td><td>{{ data.version }}</td></tr>
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.members' }}</td><td>{{ data.members }}</td></tr>
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.admins' }}</td><td>{{ data.admins }}</td></tr>
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.managers' }}</td><td>{{ data.managers }}</td></tr>
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.availabilities' }}</td><td>{{ data.availabilities }}</td></tr>
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.reservations' }}</td><td>{{ data.reservations }}</td></tr>
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.orders' }}</td><td>{{ data.orders }}</td></tr>
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.plans' }}</td><td>{{ data.plans }}</td></tr>
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.spaces' }}</td><td>{{ data.spaces }}</td></tr>
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.online_payment' }}</td><td>{{ data.online_payment }}</td></tr>
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.gateway' }}</td><td>{{ data.gateway }}</td></tr>
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.wallet' }}</td><td>{{ data.wallet }}</td></tr>
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.statistics' }}</td><td>{{ data.statistics }}</td></tr>
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.trainings' }}</td><td>{{ data.trainings }}</td></tr>
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.public_agenda' }}</td><td>{{ data.public_agenda }}</td></tr>
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.machines' }}</td><td>{{ data.machines }}</td></tr>
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.store' }}</td><td>{{ data.store }}</td></tr>
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.invoices' }}</td><td>{{ data.invoices }}</td></tr>
|
||||
<tr><td translate>{{ 'app.admin.settings.privacy.analytics.openlab' }}</td><td>{{ data.openlab }}</td></tr>
|
||||
</tbody>
|
||||
|
@ -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$/
|
||||
|
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
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 <a href='https://analytics.google.com/analytics/web/' target='_blank'>die Google Analytics Website</a>, um eine Tracking-ID zu erstellen.<br/><strong>Warnung:</strong> wenn Sie dieses Feature aktivieren, wird ein Cookie erstellt. Denken Sie daran, es oben in Ihrer Datenschutzrichtlinie darauf hingewiesen werden."
|
||||
|
@ -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 <a href='https://analytics.google.com/analytics/web/' target='_blank'>the Google Analytics website</a> to get one.<br/><strong>Warning:</strong> if you enable this feature, a cookie will be created. Remember to write it down in your privacy policy, above."
|
||||
|
@ -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 <a href='https://analytics.google.com/analytics/web/' target='_blank'>the Google Analytics website</a> to get one.<br/><strong>Warning:</strong> if you enable this feature, a cookie will be created. Remember to write it down in your privacy policy, above."
|
||||
|
@ -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 <a href='https://analytics.google.com/analytics/web/' target='_blank'>le site web de Google Analytics</a> pour en obtenir un.<br/><strong>Attention :</strong> si vous activez cette fonctionnalité, un cookie sera créé. Pensez à l'indiquer dans votre politique de confidentialité, ci-dessus."
|
||||
|
@ -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 <a href='https://analytics.google.com/analytics/web/' target='_blank'>the Google Analytics website</a> to get one.<br/><strong>Warning:</strong> if you enable this feature, a cookie will be created. Remember to write it down in your privacy policy, above."
|
||||
|
@ -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 <a href='https://analytics.google.com/analytics/web/' target='_blank'>o site do Google Analytics</a> para obter um.<br/><strong>Aviso:</strong> se você ativar este recurso, um cookie será criado. Lembre-se de escrevê-lo na sua política de privacidade."
|
||||
|
@ -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"
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
```
|
||||
|
||||
|
@ -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",
|
||||
|
BIN
test/fixtures/files/document.pdf
vendored
Normal file
BIN
test/fixtures/files/document.pdf
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/files/document2.pdf
vendored
Normal file
BIN
test/fixtures/files/document2.pdf
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/files/machines/Laser_cutting_machine.jpg
vendored
Normal file
BIN
test/fixtures/files/machines/Laser_cutting_machine.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 228 KiB |
BIN
test/fixtures/files/spaces/Biology_laboratory.jpg
vendored
Normal file
BIN
test/fixtures/files/spaces/Biology_laboratory.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 MiB |
8
test/fixtures/statistic_sub_types.yml
vendored
8
test/fixtures/statistic_sub_types.yml
vendored
@ -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
|
||||
|
||||
|
15
test/fixtures/statistic_type_sub_types.yml
vendored
15
test/fixtures/statistic_type_sub_types.yml
vendored
@ -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
|
||||
|
||||
|
||||
|
85
test/integration/machines_test.rb
Normal file
85
test/integration/machines_test.rb
Normal file
@ -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 = '<p>lorem ipsum <strong>dolor</strong> sit amet</p>'
|
||||
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
|
@ -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
|
||||
|
76
test/integration/spaces_test.rb
Normal file
76
test/integration/spaces_test.rb
Normal file
@ -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 = '<p>lorem ipsum <strong>dolor</strong> sit amet</p>'
|
||||
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
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user