+
-
diff --git a/app/controllers/api/accounting_periods_controller.rb b/app/controllers/api/accounting_periods_controller.rb
new file mode 100644
index 000000000..f374e18bd
--- /dev/null
+++ b/app/controllers/api/accounting_periods_controller.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+# API Controller for resources of AccountingPeriod
+class API::AccountingPeriodsController < API::ApiController
+
+ before_action :authenticate_user!
+ before_action :set_period, only: %i[show download_archive]
+
+ def index
+ @accounting_periods = AccountingPeriodService.all_periods_with_users
+ end
+
+ def show; end
+
+ def create
+ authorize AccountingPeriod
+ @accounting_period = AccountingPeriod.new(period_params.merge(closed_at: DateTime.now, closed_by: current_user.id))
+ if @accounting_period.save
+ render :show, status: :created, location: @accounting_period
+ else
+ render json: @accounting_period.errors, status: :unprocessable_entity
+ end
+ end
+
+ def last_closing_end
+ authorize AccountingPeriod
+ last_period = AccountingPeriodService.find_last_period
+ if last_period.nil?
+ invoice = Invoice.order(:created_at).first
+ @last_end = invoice.created_at if invoice
+ else
+ @last_end = last_period.end_at + 1.day
+ end
+ end
+
+ def download_archive
+ authorize AccountingPeriod
+ send_file File.join(Rails.root, @accounting_period.archive_file), type: 'application/json', disposition: 'attachment'
+ end
+
+ private
+
+ def set_period
+ @accounting_period = AccountingPeriod.find(params[:id])
+ end
+
+ def period_params
+ params.require(:accounting_period).permit(:start_at, :end_at)
+ end
+end
diff --git a/app/controllers/api/events_controller.rb b/app/controllers/api/events_controller.rb
index 127b8fa5d..0791b8de0 100644
--- a/app/controllers/api/events_controller.rb
+++ b/app/controllers/api/events_controller.rb
@@ -96,7 +96,7 @@ class API::EventsController < API::ApiController
:recurrence_end_at, :category_id, :event_theme_ids, :age_range_id,
event_theme_ids: [],
event_image_attributes: [:attachment],
- event_files_attributes: %i[id attachment_destroy],
+ event_files_attributes: %i[id attachment _destroy],
event_price_categories_attributes: %i[id price_category_id amount _destroy])
EventService.process_params(event_preparams)
end
diff --git a/app/controllers/api/reservations_controller.rb b/app/controllers/api/reservations_controller.rb
index ea5e450b6..5ef8e1848 100644
--- a/app/controllers/api/reservations_controller.rb
+++ b/app/controllers/api/reservations_controller.rb
@@ -26,7 +26,7 @@ class API::ReservationsController < API::ApiController
user_id = current_user.admin? ? reservation_params[:user_id] : current_user.id
@reservation = Reservation.new(reservation_params)
- is_reserve = Reservations::Reserve.new(user_id)
+ is_reserve = Reservations::Reserve.new(user_id, current_user.id)
.pay_and_save(@reservation, method, coupon_params[:coupon_code])
if is_reserve
diff --git a/app/controllers/api/subscriptions_controller.rb b/app/controllers/api/subscriptions_controller.rb
index 0b11b13ea..9bd1d98bd 100644
--- a/app/controllers/api/subscriptions_controller.rb
+++ b/app/controllers/api/subscriptions_controller.rb
@@ -19,7 +19,7 @@ class API::SubscriptionsController < API::ApiController
user_id = current_user.admin? ? subscription_params[:user_id] : current_user.id
@subscription = Subscription.new(subscription_params)
- is_subscribe = Subscriptions::Subscribe.new(user_id)
+ is_subscribe = Subscriptions::Subscribe.new(user_id, current_user.id)
.pay_and_save(@subscription, method, coupon_params[:coupon_code], true)
if is_subscribe
@@ -35,7 +35,7 @@ class API::SubscriptionsController < API::ApiController
free_days = params[:subscription][:free] || false
- res = Subscriptions::Subscribe.new(@subscription.user_id)
+ res = Subscriptions::Subscribe.new(@subscription.user_id, current_user.id)
.extend_subscription(@subscription, subscription_update_params[:expired_at], free_days)
if res.is_a?(Subscription)
@subscription = res
diff --git a/app/controllers/api/version_controller.rb b/app/controllers/api/version_controller.rb
index b87c3077b..b466c27a9 100644
--- a/app/controllers/api/version_controller.rb
+++ b/app/controllers/api/version_controller.rb
@@ -1,4 +1,5 @@
# frozen_string_literal: true
+require 'version'
# API Controller to get the fab-manager version
class API::VersionController < API::ApiController
@@ -6,8 +7,7 @@ class API::VersionController < API::ApiController
def show
authorize :version
- package = File.read('package.json')
- version = JSON.parse(package)['version']
- render json: { version: version }, status: :ok
+
+ render json: { version: Version.current }, status: :ok
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 90a61671c..f59db1763 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -30,11 +30,16 @@ class ApplicationController < ActionController::Base
end
def configure_permitted_parameters
- devise_parameter_sanitizer.for(:sign_up) <<
- { profile_attributes: [:phone, :last_name, :first_name, :gender, :birthday, :interest, :software_mastered,
- organization_attributes: [:name, address_attributes: [:address]]] }
-
- devise_parameter_sanitizer.for(:sign_up).concat %i[username is_allow_contact is_allow_newsletter cgu group_id]
+ devise_parameter_sanitizer.permit(:sign_up,
+ keys: [
+ { profile_attributes: [
+ :phone, :last_name, :first_name, :gender, :birthday,
+ :interest, :software_mastered, organization_attributes: [
+ :name, address_attributes: [:address]
+ ]
+ ] },
+ :username, :is_allow_contact, :is_allow_newsletter, :cgu, :group_id
+ ])
end
def default_url_options
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 5488450ad..e7ee47a0e 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -4,7 +4,7 @@ class SessionsController < Devise::SessionsController
def new
active_provider = AuthProvider.active
if active_provider.providable_type != DatabaseProvider.name
- redirect_to user_omniauth_authorize_path(active_provider.strategy_name.to_sym)
+ redirect_to "/users/auth/#{active_provider.strategy_name}"
else
super
end
diff --git a/app/doc/open_api/api_doc.rb b/app/doc/open_api/api_doc.rb
index e984db547..cb7dabe97 100644
--- a/app/doc/open_api/api_doc.rb
+++ b/app/doc/open_api/api_doc.rb
@@ -1,8 +1,9 @@
+# frozen_string_literal: true
+
# app/concerns/controllers/api_doc.rb
#
# Controller extension with common API documentation shortcuts
#
-
module OpenAPI::ApiDoc
# Apipie doesn't allow to append anything to esisting
# description. It raises an error on double definition.
diff --git a/app/doc/open_api/application_doc.rb b/app/doc/open_api/application_doc.rb
index e677037e5..ff21bd2a5 100644
--- a/app/doc/open_api/application_doc.rb
+++ b/app/doc/open_api/application_doc.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# app/docs/application_doc.rb
#
# A common class for defining API docs
@@ -16,7 +18,6 @@
# end
# end
#
-
class OpenAPI::ApplicationDoc
extend OpenAPI::ApiDoc
diff --git a/app/doc/open_api/v1/base_doc.rb b/app/doc/open_api/v1/base_doc.rb
index 1772d0b5d..7e04b2110 100644
--- a/app/doc/open_api/v1/base_doc.rb
+++ b/app/doc/open_api/v1/base_doc.rb
@@ -1,5 +1,8 @@
+# frozen_string_literal: true
+
+# parent class for openAPI documentation
class OpenAPI::V1::BaseDoc < OpenAPI::ApplicationDoc
- API_VERSION = "v1"
- FORMATS = ['json']
+ API_VERSION = 'v1'
+ FORMATS = ['json'].freeze
PER_PAGE_DEFAULT = 20
end
diff --git a/app/doc/open_api/v1/bookable_machines_doc.rb b/app/doc/open_api/v1/bookable_machines_doc.rb
index f47ae11cd..c80fc6854 100644
--- a/app/doc/open_api/v1/bookable_machines_doc.rb
+++ b/app/doc/open_api/v1/bookable_machines_doc.rb
@@ -1,3 +1,6 @@
+# frozen_string_literal: true
+
+# openAPI documentation for bookable machines endpoint
class OpenAPI::V1::BookableMachinesDoc < OpenAPI::V1::BaseDoc
resource_description do
short 'Bookable machines'
@@ -7,10 +10,10 @@ class OpenAPI::V1::BookableMachinesDoc < OpenAPI::V1::BaseDoc
end
doc_for :index do
- api :GET, "/#{API_VERSION}/bookable_machines", "Bookable machines index"
- description "Machines that a given user is allowed to book."
- param :user_id, Integer, required: true, desc: "Id of the given user."
- example <<-EOS
+ api :GET, "/#{API_VERSION}/bookable_machines", 'Bookable machines index'
+ description 'Machines that a given user is allowed to book.'
+ param :user_id, Integer, required: true, desc: 'Id of the given user.'
+ example <<-MACHINES
# /open_api/v1/bookable_machines?user_id=522
{
"machines": [
@@ -67,6 +70,6 @@ class OpenAPI::V1::BookableMachinesDoc < OpenAPI::V1::BaseDoc
# ...
]
}
- EOS
+ MACHINES
end
end
diff --git a/app/doc/open_api/v1/concerns/param_groups.rb b/app/doc/open_api/v1/concerns/param_groups.rb
index d43efda6c..590d1396a 100644
--- a/app/doc/open_api/v1/concerns/param_groups.rb
+++ b/app/doc/open_api/v1/concerns/param_groups.rb
@@ -1,30 +1,13 @@
+# frozen_string_literal: true
+
+# openAPI pagination
module OpenAPI::V1::Concerns::ParamGroups
extend ActiveSupport::Concern
included do
define_param_group :pagination do
- param :page, Integer, desc: "Page number", optional: true
+ param :page, Integer, desc: 'Page number', optional: true
param :per_page, Integer, desc: "Number of objects per page. Default is #{OpenAPI::V1::BaseDoc::PER_PAGE_DEFAULT}.", optional: true
end
-
- # define_param_group :order_type do
- # param :order_type, ['asc', 'desc'], desc: "order type: descendant or ascendant. Default value is *desc*."
- # end
- #
- # define_param_group :filter_by_tags do
- # param :tagged_with, [String, Array], desc: 'If multiple tags are given, we use an *OR* function. See parameter *order_by_matching_tag_count* to order the result. It can also be a *comma* *separated* *string*. Example: tagged_with=science,museum'
- # param :order_by_matching_tag_count, ['t',1,'true'], desc: "You can use this parameter if you are sending a parameter *tagged_with*. Send this parameter to order by number of matching tags (descendant): result will be sort firstly by matching tags and secondly by order given by *order_by* parameter. Default to *false*."
- # end
- #
- # define_param_group :filter_by_blog do
- # param :blog_slug, String, desc: "Send the blog's *slug* to only return articles belonging to specific blog."
- # end
- #
- # define_param_group :filter_by_geolocation do
- # param :latitude, Numeric, desc: "Latitude. Example: *45.166670*"
- # param :longitude, Numeric, desc: "Longitude. Example: *5.7166700*"
- # param :radius, Numeric, desc: "To be combined with parameters latitude and longitude. Default to *10*."
- # param :order_by_distance, ['t',1,'true'], desc: "You can use this parameter if you are sending parameters *latitude* and *longitude*. Send this parameter to order by distance (descendant): result will be sort firstly by distance and secondly by order given by *order_by* parameter. Default to *false*."
- # end
end
end
diff --git a/app/doc/open_api/v1/events_doc.rb b/app/doc/open_api/v1/events_doc.rb
index 4b8f391de..7fd90c778 100644
--- a/app/doc/open_api/v1/events_doc.rb
+++ b/app/doc/open_api/v1/events_doc.rb
@@ -1,3 +1,6 @@
+# frozen_string_literal: true
+
+# openAPI documentation for events endpoint
class OpenAPI::V1::EventsDoc < OpenAPI::V1::BaseDoc
resource_description do
short 'Events'
@@ -9,19 +12,19 @@ class OpenAPI::V1::EventsDoc < OpenAPI::V1::BaseDoc
include OpenAPI::V1::Concerns::ParamGroups
doc_for :index do
- api :GET, "/#{API_VERSION}/events", "Events index"
+ api :GET, "/#{API_VERSION}/events", 'Events index'
param_group :pagination
- param :id, [Integer, Array], optional: true, desc: "Scope the request to one or various events."
- param :upcoming, [FalseClass, TrueClass], optional: true, desc: "Scope for the upcoming events."
- description "Events index. Order by *created_at* desc."
- example <<-EOS
+ param :id, [Integer, Array], optional: true, desc: 'Scope the request to one or various events.'
+ param :upcoming, [FalseClass, TrueClass], optional: true, desc: 'Scope for the upcoming events.'
+ description 'Events index. Order by *created_at* desc.'
+ example <<-EVENTS
# /open_api/v1/events?page=1&per_page=2
{
"events": [
{
"id": 183,
"title": "OPEN LAB",
- "description": "Que vous soyez Fab user, visiteur, curieux ou bricoleur, l’atelier de fabrication numérique vous ouvre ses portes les mercredis soirs pour avancer vos projets ou rencontrer la «communauté» Fab Lab. \r\n\r\nCe soir, venez spécialement découvrir les machines à commandes numérique du Fab Lab de La Casemate, venez comprendre ce lieux ouvert à tous. \r\n\r\n\r\nVenez découvrir un concept, une organisation, des machines, pour stimuler votre sens de la créativité.",
+ "description": "Que vous soyez Fab user, visiteur, curieux ou bricoleur, l’atelier de fabrication numérique vous ouvre ses portes les mercredis soirs pour avancer vos projets ou rencontrer la «communauté» Fab Lab. \r\n\r\nCe soir, venez spécialement découvrir les machines à commandes numérique de la Fabrique de Fab-manager, venez comprendre ce lieux ouvert à tous. \r\n\r\n\r\nVenez découvrir un concept, une organisation, des machines, pour stimuler votre sens de la créativité.",
"updated_at": "2016-04-25T10:49:40.055+02:00",
"created_at": "2016-04-25T10:49:40.055+02:00",
"nb_total_places": 18,
@@ -54,6 +57,6 @@ class OpenAPI::V1::EventsDoc < OpenAPI::V1::BaseDoc
}
]
}
- EOS
+ EVENTS
end
end
diff --git a/app/doc/open_api/v1/invoices_doc.rb b/app/doc/open_api/v1/invoices_doc.rb
index abc360596..d6289a599 100644
--- a/app/doc/open_api/v1/invoices_doc.rb
+++ b/app/doc/open_api/v1/invoices_doc.rb
@@ -1,3 +1,6 @@
+# frozen_string_literal: true
+
+# openAPI documentation for invoices endpoints
class OpenAPI::V1::InvoicesDoc < OpenAPI::V1::BaseDoc
resource_description do
short 'Invoices'
@@ -9,11 +12,11 @@ class OpenAPI::V1::InvoicesDoc < OpenAPI::V1::BaseDoc
include OpenAPI::V1::Concerns::ParamGroups
doc_for :index do
- api :GET, "/#{API_VERSION}/invoices", "Invoices index"
+ api :GET, "/#{API_VERSION}/invoices", 'Invoices index'
description "Index of users' invoices, with optional pagination. Order by *created_at* descendant."
param_group :pagination
- param :user_id, [Integer, Array], optional: true, desc: "Scope the request to one or various users."
- example <<-EOS
+ param :user_id, [Integer, Array], optional: true, desc: 'Scope the request to one or various users.'
+ example <<-INVOICES
# /open_api/v1/invoices?user_id=211&page=1&per_page=3
{
"invoices": [
@@ -64,15 +67,15 @@ class OpenAPI::V1::InvoicesDoc < OpenAPI::V1::BaseDoc
}
]
}
- EOS
+ INVOICES
end
- doc_for :download do
- api :GET, "/#{API_VERSION}/invoices/:id/download", "Download an invoice"
- param :id, Integer, desc: "Invoice id", required: true
+ doc_for :download do
+ api :GET, "/#{API_VERSION}/invoices/:id/download", 'Download an invoice'
+ param :id, Integer, desc: 'Invoice id', required: true
- example <<-EOS
- # /open_api/v1/invoices/2809/download
- EOS
- end
+ example <<-URL
+ # /open_api/v1/invoices/2809/download
+ URL
+ end
end
diff --git a/app/doc/open_api/v1/machines_doc.rb b/app/doc/open_api/v1/machines_doc.rb
index aa1a67e79..42edb050a 100644
--- a/app/doc/open_api/v1/machines_doc.rb
+++ b/app/doc/open_api/v1/machines_doc.rb
@@ -1,3 +1,6 @@
+# frozen_string_literal: true
+
+# openAPI documentation for machines endpoint
class OpenAPI::V1::MachinesDoc < OpenAPI::V1::BaseDoc
resource_description do
short 'Machines'
@@ -7,9 +10,9 @@ class OpenAPI::V1::MachinesDoc < OpenAPI::V1::BaseDoc
end
doc_for :index do
- api :GET, "/#{API_VERSION}/machines", "Machines index"
- description "Machines index. Order by *created_at* ascendant."
- example <<-EOS
+ api :GET, "/#{API_VERSION}/machines", 'Machines index'
+ description 'Machines index. Order by *created_at* ascendant.'
+ example <<-MACHINES
# /open_api/v1/machines
{
"machines": [
@@ -63,7 +66,7 @@ class OpenAPI::V1::MachinesDoc < OpenAPI::V1::BaseDoc
"description": "La fraiseuse numérique Roland Modela MDX-20\r\n\r\nInformations générales :\r\nCette machine est utilisée pour l'usinage et le scannage 3D de précision. Elle permet principalement d'usiner des circuits imprimés et des moules de petite taille. Le faible diamètre des fraises utilisées (Ø 0,3 mm à Ø 6mm) implique que certains temps d'usinages peuvent êtres long (> 12h), c'est pourquoi cette fraiseuse peut être laissée en autonomie toute une nuit afin d'obtenir le plus précis des usinages au FabLab.\r\n\r\nMatériaux usinables :\r\nLes principaux matériaux usinables sont : bois, plâtre, résine, cire usinable, cuivre.\r\n",
"spec": "Taille du plateau X/Y : 220 mm x 160 mm\r\nVolume maximal de travail: 203,2 mm (X), 152,4 mm (Y), 60,5 mm (Z)\r\nPrécision usinage: 0,00625 mm\r\nPrécision scannage: réglable de 0,05 à 5 mm (axes X,Y) et 0,025 mm (axe Z)\r\nVitesse d'analyse (scannage): 4-15 mm/sec\r\n \r\n \r\nLogiciel utilisé pour le fraisage: Roland Modela player 4 \r\nLogiciel utilisé pour l'usinage de circuits imprimés: Cad.py (linux)\r\nFormats acceptés: STL,PNG 3D\r\nFormat d'exportation des données scannées: DXF, VRML, STL, 3DMF, IGES, Grayscale, Point Group et BMP\r\n"
},
- #
+ #
# ....
#
{
@@ -78,6 +81,6 @@ class OpenAPI::V1::MachinesDoc < OpenAPI::V1::BaseDoc
}
]
}
- EOS
+ MACHINES
end
end
diff --git a/app/doc/open_api/v1/reservations_doc.rb b/app/doc/open_api/v1/reservations_doc.rb
index 279e70062..f56458ec7 100644
--- a/app/doc/open_api/v1/reservations_doc.rb
+++ b/app/doc/open_api/v1/reservations_doc.rb
@@ -1,3 +1,6 @@
+# frozen_string_literal: true
+
+# openAPI documentation for reservations endpoint
class OpenAPI::V1::ReservationsDoc < OpenAPI::V1::BaseDoc
resource_description do
short 'Reservations'
@@ -9,14 +12,14 @@ class OpenAPI::V1::ReservationsDoc < OpenAPI::V1::BaseDoc
include OpenAPI::V1::Concerns::ParamGroups
doc_for :index do
- api :GET, "/#{API_VERSION}/reservations", "Reservations index"
- description "Index of reservations made by users, with optional pagination. Order by *created_at* descendant."
+ api :GET, "/#{API_VERSION}/reservations", 'Reservations index'
+ description 'Index of reservations made by users, with optional pagination. Order by *created_at* descendant.'
param_group :pagination
- param :user_id, [Integer, Array], optional: true, desc: "Scope the request to one or various users."
- param :reservable_type, ['Event', 'Machine', 'Training'], optional: true, desc: "Scope the request to a specific type of reservable."
- param :reservable_id, [Integer, Array], optional: true, desc: "Scope the request to one or various reservables."
+ param :user_id, [Integer, Array], optional: true, desc: 'Scope the request to one or various users.'
+ param :reservable_type, %w[Event Machine Training], optional: true, desc: 'Scope the request to a specific type of reservable.'
+ param :reservable_id, [Integer, Array], optional: true, desc: 'Scope the request to one or various reservables.'
- example <<-EOS
+ example <<-RESERVATIONS
# /open_api/v1/reservations?reservable_type=Event&page=1&per_page=3
{
"reservations": [
@@ -85,6 +88,6 @@ class OpenAPI::V1::ReservationsDoc < OpenAPI::V1::BaseDoc
}
]
}
- EOS
+ RESERVATIONS
end
end
diff --git a/app/doc/open_api/v1/trainings_doc.rb b/app/doc/open_api/v1/trainings_doc.rb
index ade2975b0..9d0a28950 100644
--- a/app/doc/open_api/v1/trainings_doc.rb
+++ b/app/doc/open_api/v1/trainings_doc.rb
@@ -1,3 +1,6 @@
+# frozen_string_literal: true
+
+# openAPI documentation for trainings endpoint
class OpenAPI::V1::TrainingsDoc < OpenAPI::V1::BaseDoc
resource_description do
short 'Trainings'
@@ -7,9 +10,9 @@ class OpenAPI::V1::TrainingsDoc < OpenAPI::V1::BaseDoc
end
doc_for :index do
- api :GET, "/#{API_VERSION}/trainings", "Trainings index"
- description "Trainings index. Order by *created_at* ascendant."
- example <<-EOS
+ api :GET, "/#{API_VERSION}/trainings", 'Trainings index'
+ description 'Trainings index. Order by *created_at* ascendant.'
+ example <<-TRAININGS
# /open_api/v1/trainings
{
"trainings": [
@@ -75,6 +78,6 @@ class OpenAPI::V1::TrainingsDoc < OpenAPI::V1::BaseDoc
}
]
}
- EOS
+ TRAININGS
end
end
diff --git a/app/doc/open_api/v1/user_trainings_doc.rb b/app/doc/open_api/v1/user_trainings_doc.rb
index 232505abb..d2de34d17 100644
--- a/app/doc/open_api/v1/user_trainings_doc.rb
+++ b/app/doc/open_api/v1/user_trainings_doc.rb
@@ -1,3 +1,6 @@
+# frozen_string_literal: true
+
+# openAPI documentation for user's trainings endpoint
class OpenAPI::V1::UserTrainingsDoc < OpenAPI::V1::BaseDoc
resource_description do
short 'User trainings'
@@ -9,12 +12,12 @@ class OpenAPI::V1::UserTrainingsDoc < OpenAPI::V1::BaseDoc
include OpenAPI::V1::Concerns::ParamGroups
doc_for :index do
- api :GET, "/#{API_VERSION}/user_trainings", "User trainings index"
- description "Index of trainings accomplished by users, with optional pagination. Order by *created_at* descendant."
+ api :GET, "/#{API_VERSION}/user_trainings", 'User trainings index'
+ description 'Index of trainings accomplished by users, with optional pagination. Order by *created_at* descendant.'
param_group :pagination
- param :training_id, [Integer, Array], optional: true, desc: "Scope the request to one or various trainings."
- param :user_id, [Integer, Array], optional: true, desc: "Scope the request to one or various users."
- example <<-EOS
+ param :training_id, [Integer, Array], optional: true, desc: 'Scope the request to one or various trainings.'
+ param :user_id, [Integer, Array], optional: true, desc: 'Scope the request to one or various users.'
+ example <<-TRAININGS
# /open_api/v1/user_trainings?training_id[]=3&training_id[]=4&page=1&per_page=2
{
"user_trainings": [
@@ -94,6 +97,6 @@ class OpenAPI::V1::UserTrainingsDoc < OpenAPI::V1::BaseDoc
}
]
}
- EOS
+ TRAININGS
end
end
diff --git a/app/doc/open_api/v1/users_doc.rb b/app/doc/open_api/v1/users_doc.rb
index 12f4b70f4..3f1b3c6b5 100644
--- a/app/doc/open_api/v1/users_doc.rb
+++ b/app/doc/open_api/v1/users_doc.rb
@@ -1,3 +1,6 @@
+# frozen_string_literal: true
+
+# openAPI documentation for user endpoint
class OpenAPI::V1::UsersDoc < OpenAPI::V1::BaseDoc
resource_description do
short 'Users'
@@ -9,12 +12,12 @@ class OpenAPI::V1::UsersDoc < OpenAPI::V1::BaseDoc
include OpenAPI::V1::Concerns::ParamGroups
doc_for :index do
- api :GET, "/#{API_VERSION}/users", "Users index"
- description "Users index, with optional pagination. Order by *created_at* descendant."
+ api :GET, "/#{API_VERSION}/users", 'Users index'
+ description 'Users index, with optional pagination. Order by *created_at* descendant.'
param_group :pagination
- param :email, [String, Array], optional: true, desc: "Filter users by *email* using strict matching."
- param :user_id, [Integer, Array], optional: true, desc: "Filter users by *id* using strict matching."
- example <<-EOS
+ param :email, [String, Array], optional: true, desc: 'Filter users by *email* using strict matching.'
+ param :user_id, [Integer, Array], optional: true, desc: 'Filter users by *id* using strict matching.'
+ example <<-USERS
# /open_api/v1/users?page=1&per_page=4
{
"users": [
@@ -92,6 +95,6 @@ class OpenAPI::V1::UsersDoc < OpenAPI::V1::BaseDoc
}
]
}
- EOS
+ USERS
end
end
diff --git a/app/models/accounting_period.rb b/app/models/accounting_period.rb
new file mode 100644
index 000000000..b50e106bc
--- /dev/null
+++ b/app/models/accounting_period.rb
@@ -0,0 +1,146 @@
+# frozen_string_literal: true
+
+require 'checksum'
+require 'version'
+require 'zip'
+
+# AccountingPeriod is a period of N days (N > 0) which as been closed by an admin
+# to prevent writing new accounting lines (invoices & refunds) during this period of time.
+class AccountingPeriod < ActiveRecord::Base
+ before_destroy { false }
+ before_update { false }
+ before_create :compute_totals
+ after_create :archive_closed_data
+
+ validates :start_at, :end_at, :closed_at, :closed_by, presence: true
+ validates_with DateRangeValidator
+ validates_with PeriodOverlapValidator
+ validates_with PeriodIntegrityValidator
+
+ def delete
+ false
+ end
+
+ def invoices
+ Invoice.where('created_at >= :start_date AND CAST(created_at AS DATE) <= :end_date', start_date: start_at, end_date: end_at)
+ end
+
+ def invoices_with_vat(invoices)
+ invoices.map do |i|
+ if i.type == 'Avoir'
+ { invoice: i, vat_rate: vat_rate(i.avoir_date) }
+ else
+ { invoice: i, vat_rate: vat_rate(i.created_at) }
+ end
+ end
+ end
+
+ def archive_folder
+ dir = "accounting/#{id}"
+
+ # create directory if it doesn't exists (accounting)
+ FileUtils.mkdir_p dir
+ dir
+ end
+
+ def archive_file
+ "#{archive_folder}/#{start_at.iso8601}_#{end_at.iso8601}.zip"
+ end
+
+ def archive_json_file
+ "#{start_at.iso8601}_#{end_at.iso8601}.json"
+ end
+
+ def check_footprint
+ footprint == compute_footprint
+ end
+
+ def vat_rate(date)
+ @vat_rates = vat_history if @vat_rates.nil?
+
+ first_rate = @vat_rates.first
+ return first_rate[:rate] if date < first_rate[:date]
+
+ @vat_rates.each do |h|
+ return h[:rate] if h[:date] <= date
+ end
+ end
+
+ private
+
+ def vat_history
+ key_dates = []
+ Setting.find_by(name: 'invoice_VAT-rate').history_values.each do |rate|
+ key_dates.push(date: rate.created_at, rate: (rate.value.to_i / 100.0))
+ end
+ Setting.find_by(name: 'invoice_VAT-active').history_values.each do |v|
+ key_dates.push(date: v.created_at, rate: 0) if v.value == 'false'
+ end
+ key_dates.sort_by { |k| k[:date] }
+ end
+
+ def to_json_archive(invoices, previous_file, last_checksum)
+ code_checksum = Checksum.code
+ ApplicationController.new.view_context.render(
+ partial: 'archive/accounting',
+ locals: {
+ invoices: invoices_with_vat(invoices),
+ period_total: period_total,
+ perpetual_total: perpetual_total,
+ period_footprint: footprint,
+ code_checksum: code_checksum,
+ last_archive_checksum: last_checksum,
+ previous_file: previous_file,
+ software_version: Version.current,
+ date: Time.now.iso8601
+ },
+ formats: [:json],
+ handlers: [:jbuilder]
+ )
+ end
+
+ def previous_period
+ AccountingPeriod.where('closed_at < ?', closed_at).order(closed_at: :desc).limit(1).last
+ end
+
+ def archive_closed_data
+ data = invoices.includes(:invoice_items)
+ previous_file = previous_period&.archive_file
+ last_archive_checksum = previous_file ? Checksum.file(previous_file) : nil
+ json_data = to_json_archive(data, previous_file, last_archive_checksum)
+ current_archive_checksum = Checksum.text(json_data)
+
+ Zip::OutputStream.open(archive_file) do |io|
+ io.put_next_entry(archive_json_file)
+ io.write(json_data)
+ io.put_next_entry('checksum.sha256')
+ io.write("#{current_archive_checksum}\t#{archive_json_file}")
+ io.put_next_entry('chained.sha256')
+ io.write(Checksum.text("#{current_archive_checksum}#{last_archive_checksum}#{DateTime.iso8601}"))
+ end
+ end
+
+ def price_without_taxe(invoice)
+ invoice[:invoice].total - (invoice[:invoice].total * invoice[:vat_rate])
+ end
+
+ def compute_totals
+ period_invoices = invoices_with_vat(invoices.where(type: nil))
+ period_avoirs = invoices_with_vat(invoices.where(type: 'Avoir'))
+ self.period_total = (period_invoices.map(&method(:price_without_taxe)).reduce(:+) || 0) -
+ (period_avoirs.map(&method(:price_without_taxe)).reduce(:+) || 0)
+
+ all_invoices = invoices_with_vat(Invoice.where('CAST(created_at AS DATE) <= :end_date AND type IS NULL', end_date: end_at))
+ all_avoirs = invoices_with_vat(Invoice.where("CAST(created_at AS DATE) <= :end_date AND type = 'Avoir'", end_date: end_at))
+ self.perpetual_total = (all_invoices.map(&method(:price_without_taxe)).reduce(:+) || 0) -
+ (all_avoirs.map(&method(:price_without_taxe)).reduce(:+) || 0)
+ self.footprint = compute_footprint
+ end
+
+ def compute_footprint
+ columns = AccountingPeriod.columns.map(&:name)
+ .delete_if { |c| %w[id footprint created_at updated_at].include? c }
+
+ Checksum.text("#{columns.map { |c| self[c] }.join}#{previous_period ? previous_period.footprint : ''}")
+ end
+end
diff --git a/app/models/event_file.rb b/app/models/event_file.rb
index fb6c7e469..34d788366 100644
--- a/app/models/event_file.rb
+++ b/app/models/event_file.rb
@@ -1,5 +1,8 @@
+# frozen_string_literal: true
+
+# Event PDF attachements
class EventFile < Asset
- mount_uploader :attachment, ProjectCaoUploader
+ mount_uploader :attachment, EventFileUploader
validates :attachment, file_size: { maximum: 20.megabytes.to_i }
end
diff --git a/app/models/history_value.rb b/app/models/history_value.rb
index 902acf022..07a7660be 100644
--- a/app/models/history_value.rb
+++ b/app/models/history_value.rb
@@ -1,4 +1,32 @@
+# frozen_string_literal: true
+
+require 'checksum'
+
+# Setting values, kept history of modifications
class HistoryValue < ActiveRecord::Base
belongs_to :setting
belongs_to :user
+
+ def chain_record
+ self.footprint = compute_footprint
+ save!
+ end
+
+ def check_footprint
+ footprint == compute_footprint
+ end
+
+ private
+
+ def compute_footprint
+ max_date = created_at || Time.current
+ previous = HistoryValue.where('created_at < ?', max_date)
+ .order('created_at DESC')
+ .limit(1)
+
+ columns = HistoryValue.columns.map(&:name)
+ .delete_if { |c| %w[footprint updated_at].include? c }
+
+ Checksum.text("#{columns.map { |c| self[c] }.join}#{previous.first ? previous.first.footprint : ''}")
+ end
end
diff --git a/app/models/invoice.rb b/app/models/invoice.rb
index 90b437100..33aa18b68 100644
--- a/app/models/invoice.rb
+++ b/app/models/invoice.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'checksum'
+
# Invoice correspond to a single purchase made by an user. This purchase may
# include reservation(s) and/or a subscription
class Invoice < ActiveRecord::Base
@@ -15,10 +17,14 @@ class Invoice < ActiveRecord::Base
belongs_to :coupon
has_one :avoir, class_name: 'Invoice', foreign_key: :invoice_id, dependent: :destroy
+ belongs_to :operator, foreign_key: :operator_id, class_name: 'User'
- after_create :update_reference
+ before_create :add_environment
+ after_create :update_reference, :chain_record
after_commit :generate_and_send_invoice, on: [:create], if: :persisted?
+ validates_with ClosedPeriodValidator
+
def file
dir = "invoices/#{user.id}"
@@ -211,6 +217,19 @@ class Invoice < ActiveRecord::Base
total - (wallet_amount || 0)
end
+ def add_environment
+ self.environment = Rails.env
+ end
+
+ def chain_record
+ self.footprint = compute_footprint
+ save!
+ end
+
+ def check_footprint
+ invoice_items.map(&:check_footprint).all? && footprint == compute_footprint
+ end
+
private
def generate_and_send_invoice
@@ -256,4 +275,16 @@ class Invoice < ActiveRecord::Base
Invoice.where('created_at >= :start_date AND created_at < :end_date', start_date: start, end_date: ending).length
end
+ def compute_footprint
+ max_date = created_at || DateTime.now
+ previous = Invoice.where('created_at < ?', max_date)
+ .order('created_at DESC')
+ .limit(1)
+
+ columns = Invoice.columns.map(&:name)
+ .delete_if { |c| %w[footprint updated_at].include? c }
+
+ Checksum.text("#{columns.map { |c| self[c] }.join}#{previous.first ? previous.first.footprint : ''}")
+ end
+
end
diff --git a/app/models/invoice_item.rb b/app/models/invoice_item.rb
index 0477fd45b..7967b4347 100644
--- a/app/models/invoice_item.rb
+++ b/app/models/invoice_item.rb
@@ -1,9 +1,36 @@
# frozen_string_literal: true
+require 'checksum'
+
# A single line inside an invoice. Can be a subscription or a reservation
class InvoiceItem < ActiveRecord::Base
belongs_to :invoice
belongs_to :subscription
has_one :invoice_item # to associated invoice_items of an invoice to invoice_items of an avoir
+
+ after_create :chain_record
+
+ def chain_record
+ self.footprint = compute_footprint
+ save!
+ end
+
+ def check_footprint
+ footprint == compute_footprint
+ end
+
+ private
+
+ def compute_footprint
+ max_date = created_at || Time.current
+ previous = InvoiceItem.where('created_at < ?', max_date)
+ .order('created_at DESC')
+ .limit(1)
+
+ columns = InvoiceItem.columns.map(&:name)
+ .delete_if { |c| %w[footprint updated_at].include? c }
+
+ Checksum.text("#{columns.map { |c| self[c] }.join}#{previous.first ? previous.first.footprint : ''}")
+ end
end
diff --git a/app/models/notification_type.rb b/app/models/notification_type.rb
index 54503aa56..200cbf65e 100644
--- a/app/models/notification_type.rb
+++ b/app/models/notification_type.rb
@@ -43,6 +43,7 @@ class NotificationType
notify_member_about_coupon
notify_member_reservation_reminder
notify_admin_free_disk_space
+ notify_admin_close_period_reminder
]
# deprecated:
# - notify_member_subscribed_plan_is_changed
diff --git a/app/models/reservation.rb b/app/models/reservation.rb
index d110c0b22..9de1fe353 100644
--- a/app/models/reservation.rb
+++ b/app/models/reservation.rb
@@ -224,10 +224,10 @@ class Reservation < ActiveRecord::Base
invoice_items
end
- def save_with_payment(coupon_code = nil)
+ def save_with_payment(operator_id, coupon_code = nil)
begin
clean_pending_strip_invoice_items
- build_invoice(user: user)
+ build_invoice(user: user, operator_id: operator_id)
invoice_items = generate_invoice_items(false, coupon_code)
rescue StandardError => e
logger.error e
@@ -242,7 +242,7 @@ class Reservation < ActiveRecord::Base
if plan_id
self.subscription = Subscription.find_or_initialize_by(user_id: user.id)
subscription.attributes = { plan_id: plan_id, user_id: user.id, card_token: card_token, expiration_date: nil }
- if subscription.save_with_payment(false)
+ if subscription.save_with_payment(operator_id, false)
self.stp_invoice_id = invoice_items.first.refresh.invoice
invoice.stp_invoice_id = invoice_items.first.refresh.invoice
invoice.invoice_items.push InvoiceItem.new(
@@ -368,8 +368,8 @@ class Reservation < ActiveRecord::Base
pending_invoice_items.each(&:delete)
end
- def save_with_local_payment(coupon_code = nil)
- build_invoice(user: user)
+ def save_with_local_payment(operator_id, coupon_code = nil)
+ build_invoice(user: user, operator_id: operator_id)
generate_invoice_items(true, coupon_code)
return false unless valid?
@@ -377,7 +377,7 @@ class Reservation < ActiveRecord::Base
if plan_id
self.subscription = Subscription.find_or_initialize_by(user_id: user.id)
subscription.attributes = { plan_id: plan_id, user_id: user.id, expiration_date: nil }
- if subscription.save_with_local_payment(false)
+ if subscription.save_with_local_payment(operator_id, false)
invoice.invoice_items.push InvoiceItem.new(
amount: subscription.plan.amount,
description: subscription.plan.name,
diff --git a/app/models/subscription.rb b/app/models/subscription.rb
index c9f2de0c4..7d17219e7 100644
--- a/app/models/subscription.rb
+++ b/app/models/subscription.rb
@@ -20,7 +20,7 @@ class Subscription < ActiveRecord::Base
# Stripe subscription payment
# @params [invoice] if true then subscription pay itself, dont pay with reservation
# if false then subscription pay with reservation
- def save_with_payment(invoice = true, coupon_code = nil)
+ def save_with_payment(operator_id, invoice = true, coupon_code = nil)
return unless valid?
begin
@@ -75,7 +75,7 @@ class Subscription < ActiveRecord::Base
# generate invoice
stp_invoice = Stripe::Invoice.all(customer: user.stp_customer_id, limit: 1).data.first
if invoice
- db_invoice = generate_invoice(stp_invoice.id, coupon_code)
+ db_invoice = generate_invoice(operator_id, stp_invoice.id, coupon_code)
# debit wallet
wallet_transaction = debit_user_wallet
if wallet_transaction
@@ -129,7 +129,7 @@ class Subscription < ActiveRecord::Base
# @params [invoice] if true then only the subscription is payed, without reservation
# if false then the subscription is payed with reservation
- def save_with_local_payment(invoice = true, coupon_code = nil)
+ def save_with_local_payment(operator_id, invoice = true, coupon_code = nil)
return false unless valid?
set_expiration_date
@@ -142,7 +142,7 @@ class Subscription < ActiveRecord::Base
# debit wallet
wallet_transaction = debit_user_wallet
- invoc = generate_invoice(nil, coupon_code)
+ invoc = generate_invoice(operator_id, nil, coupon_code)
if wallet_transaction
invoc.wallet_amount = @wallet_amount_debit
invoc.wallet_transaction_id = wallet_transaction.id
@@ -152,7 +152,7 @@ class Subscription < ActiveRecord::Base
true
end
- def generate_invoice(stp_invoice_id = nil, coupon_code = nil)
+ def generate_invoice(operator_id, stp_invoice_id = nil, coupon_code = nil)
coupon_id = nil
total = plan.amount
@@ -165,13 +165,13 @@ class Subscription < ActiveRecord::Base
end
end
- invoice = Invoice.new(invoiced_id: id, invoiced_type: 'Subscription', user: user, total: total, stp_invoice_id: stp_invoice_id, coupon_id: coupon_id)
+ invoice = Invoice.new(invoiced_id: id, invoiced_type: 'Subscription', user: user, total: total, stp_invoice_id: stp_invoice_id, coupon_id: coupon_id, operator_id: operator_id)
invoice.invoice_items.push InvoiceItem.new(amount: plan.amount, stp_invoice_item_id: stp_subscription_id, description: plan.name, subscription_id: self.id)
invoice
end
- def generate_and_save_invoice(stp_invoice_id = nil)
- generate_invoice(stp_invoice_id).save
+ def generate_and_save_invoice(operator_id, stp_invoice_id = nil)
+ generate_invoice(operator_id, stp_invoice_id).save
end
def cancel
diff --git a/app/models/user.rb b/app/models/user.rb
index ca5dd6963..2c6f618e6 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -8,7 +8,7 @@ class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable,
- :confirmable, :async
+ :confirmable
rolify
# enable OmniAuth authentication only if needed
@@ -44,6 +44,7 @@ class User < ActiveRecord::Base
has_many :machine_credits, through: :users_credits, source: :machine_credit
has_many :invoices, dependent: :destroy
+ has_many :operated_invoices, foreign_key: :operator_id, class_name: 'Invoice', dependent: :nullify
has_many :user_tags, dependent: :destroy
has_many :tags, through: :user_tags
@@ -92,6 +93,12 @@ class User < ActiveRecord::Base
User.with_role(:admin)
end
+ def self.superadmin
+ return unless Rails.application.secrets.superadmin_email.present?
+
+ User.find_by(email: Rails.application.secrets.superadmin_email)
+ end
+
def training_machine?(machine)
return true if admin?
@@ -124,10 +131,10 @@ class User < ActiveRecord::Base
my_projects.to_a.concat projects
end
- def generate_subscription_invoice
+ def generate_subscription_invoice(operator_id)
return unless subscription
- subscription.generate_and_save_invoice
+ subscription.generate_and_save_invoice(operator_id)
end
def stripe_customer
@@ -318,6 +325,10 @@ class User < ActiveRecord::Base
create_wallet
end
+ def send_devise_notification(notification, *args)
+ devise_mailer.send(notification, self, *args).deliver_later
+ end
+
def notify_admin_when_user_is_created
if need_completion? && !provider.nil?
NotificationCenter.call type: 'notify_admin_when_user_is_imported',
diff --git a/app/pdfs/data/watermark-en.png b/app/pdfs/data/watermark-en.png
new file mode 100644
index 000000000..ed08817f2
Binary files /dev/null and b/app/pdfs/data/watermark-en.png differ
diff --git a/app/pdfs/data/watermark-es.png b/app/pdfs/data/watermark-es.png
new file mode 100644
index 000000000..239c451c4
Binary files /dev/null and b/app/pdfs/data/watermark-es.png differ
diff --git a/app/pdfs/data/watermark-fr.png b/app/pdfs/data/watermark-fr.png
new file mode 100644
index 000000000..d6cdec1e6
Binary files /dev/null and b/app/pdfs/data/watermark-fr.png differ
diff --git a/app/pdfs/data/watermark-pt.png b/app/pdfs/data/watermark-pt.png
new file mode 100644
index 000000000..dbbf88e54
Binary files /dev/null and b/app/pdfs/data/watermark-pt.png differ
diff --git a/app/pdfs/data/watermark.xcf b/app/pdfs/data/watermark.xcf
new file mode 100644
index 000000000..c5c17e090
Binary files /dev/null and b/app/pdfs/data/watermark.xcf differ
diff --git a/app/pdfs/pdf/invoice.rb b/app/pdfs/pdf/invoice.rb
index c958e5cb6..3cd030ea2 100644
--- a/app/pdfs/pdf/invoice.rb
+++ b/app/pdfs/pdf/invoice.rb
@@ -334,6 +334,15 @@ class PDF::Invoice < Prawn::Document
text line, align: :right, leading: 4, inline_format: true
end
end
+
+ # factice watermark
+ return unless %w[staging test development].include?(invoice.environment)
+
+ transparent(0.1) do
+ rotate(45, origin: [0, 0]) do
+ image "#{Rails.root}/app/pdfs/data/watermark-#{I18n.locale}.png", at: [90, 150]
+ end
+ end
end
private
diff --git a/app/policies/accounting_period_policy.rb b/app/policies/accounting_period_policy.rb
new file mode 100644
index 000000000..158ee8dc8
--- /dev/null
+++ b/app/policies/accounting_period_policy.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+# Check the access policies for API::AccountingPeriodsController
+class AccountingPeriodPolicy < ApplicationPolicy
+ %w[index show create last_closing_end download_archive].each do |action|
+ define_method "#{action}?" do
+ user.admin?
+ end
+ end
+end
diff --git a/app/services/accounting_period_service.rb b/app/services/accounting_period_service.rb
new file mode 100644
index 000000000..4f37945ab
--- /dev/null
+++ b/app/services/accounting_period_service.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+# Provides methods for accessing AccountingPeriods properties
+class AccountingPeriodService
+
+ def self.find_last_period
+ AccountingPeriod.where(end_at: AccountingPeriod.select('max(end_at)')).first
+ end
+
+ def self.all_periods_with_users
+ AccountingPeriod.joins("INNER JOIN #{User.arel_table.name} ON users.id = accounting_periods.closed_by
+ INNER JOIN #{Profile.arel_table.name} ON profiles.user_id = users.id")
+ .select("#{AccountingPeriod.arel_table.name}.*,
+ #{Profile.arel_table.name}.first_name,
+ #{Profile.arel_table.name}.last_name")
+ .order('start_at DESC')
+ end
+end
diff --git a/app/services/members/members_service.rb b/app/services/members/members_service.rb
index 669bceab7..51e0d36fd 100644
--- a/app/services/members/members_service.rb
+++ b/app/services/members/members_service.rb
@@ -31,7 +31,7 @@ class Members::MembersService
@member.generate_auth_migration_token if current_user.admin? && AuthProvider.active.providable_type != DatabaseProvider.name
if @member.save
- @member.generate_subscription_invoice
+ @member.generate_subscription_invoice(current_user.id)
@member.send_confirmation_instructions
UsersMailer.delay.notify_user_account_created(@member, @member.password)
true
diff --git a/app/services/reservations/reserve.rb b/app/services/reservations/reserve.rb
index 053c50b66..9cb8de571 100644
--- a/app/services/reservations/reserve.rb
+++ b/app/services/reservations/reserve.rb
@@ -1,18 +1,19 @@
module Reservations
class Reserve
- attr_accessor :user_id
+ attr_accessor :user_id, :operator_id
- def initialize(user_id)
+ def initialize(user_id, operator_id)
@user_id = user_id
+ @operator_id = operator_id
end
def pay_and_save(reservation, payment_method, coupon)
reservation.user_id = user_id
if payment_method == :local
- reservation.save_with_local_payment(coupon)
+ reservation.save_with_local_payment(operator_id, coupon)
elsif payment_method == :stripe
- reservation.save_with_payment(coupon)
+ reservation.save_with_payment(operator_id, coupon)
end
end
end
-end
\ No newline at end of file
+end
diff --git a/app/services/subscriptions/subscribe.rb b/app/services/subscriptions/subscribe.rb
index 58d1981dc..3af97c5f7 100644
--- a/app/services/subscriptions/subscribe.rb
+++ b/app/services/subscriptions/subscribe.rb
@@ -1,17 +1,18 @@
module Subscriptions
class Subscribe
- attr_accessor :user_id
+ attr_accessor :user_id, :operator_id
- def initialize(user_id)
+ def initialize(user_id, operator_id)
@user_id = user_id
+ @operator_id = operator_id
end
def pay_and_save(subscription, payment_method, coupon, invoice)
subscription.user_id = user_id
if payment_method == :local
- subscription.save_with_local_payment(invoice, coupon)
+ subscription.save_with_local_payment(operator_id, invoice, coupon)
elsif payment_method == :stripe
- subscription.save_with_payment(invoice, coupon)
+ subscription.save_with_payment(operator_id, invoice, coupon)
end
end
@@ -24,7 +25,7 @@ module Subscriptions
expiration_date: new_expiration_date
)
if new_sub.save
- new_sub.user.generate_subscription_invoice
+ new_sub.user.generate_subscription_invoice(operator_id)
return new_sub
end
false
diff --git a/app/uploaders/event_file_uploader.rb b/app/uploaders/event_file_uploader.rb
new file mode 100644
index 000000000..d794464e1
--- /dev/null
+++ b/app/uploaders/event_file_uploader.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+# CarrierWave uploader for event attachments
+class EventFileUploader < CarrierWave::Uploader::Base
+ # Include RMagick or MiniMagick support:
+ # include CarrierWave::RMagick
+ # include CarrierWave::MiniMagick
+ include UploadHelper
+
+ # Choose what kind of storage to use for this uploader:
+ storage :file
+ after :remove, :delete_empty_dirs
+ # storage :fog
+
+ # Override the directory where uploaded files will be stored.
+ # This is a sensible default for uploaders that are meant to be mounted:
+ def store_dir
+ "#{base_store_dir}/#{model.id}"
+ end
+
+ def base_store_dir
+ "uploads/#{model.class.to_s.underscore}"
+ end
+
+ # Provide a default URL as a default if there hasn't been a file uploaded:
+ # def default_url
+ # # For Rails 3.1+ asset pipeline compatibility:
+ # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
+ #
+ # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
+ # end
+
+ # Process files as they are uploaded:
+ # process :scale => [200, 300]
+ #
+ # def scale(width, height)
+ # # do something
+ # end
+
+
+ # Add a white list of extensions which are allowed to be uploaded.
+ # For images you might use something like this:
+ def extension_white_list
+ %w[pdf]
+ end
+
+ # Override the filename of the uploaded files:
+ # Avoid using model.id or version_name here, see uploader/store.rb for details.
+ # def filename
+ # # "avatar.#{file.extension}" if original_filename
+ # end
+end
diff --git a/app/validators/closed_period_validator.rb b/app/validators/closed_period_validator.rb
new file mode 100644
index 000000000..db6ae34e5
--- /dev/null
+++ b/app/validators/closed_period_validator.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+# Validates the current invoice is not generated within a closed accounting period
+class ClosedPeriodValidator < ActiveModel::Validator
+ def validate(record)
+ date = if record.is_a?(Avoir)
+ record.avoir_date
+ else
+ DateTime.now
+ end
+
+
+ AccountingPeriod.all.each do |period|
+ record.errors[:date] << I18n.t('errors.messages.in_closed_period') if date >= period.start_at && date <= period.end_at
+ end
+ end
+end
diff --git a/app/validators/date_range_validator.rb b/app/validators/date_range_validator.rb
new file mode 100644
index 000000000..fccd59092
--- /dev/null
+++ b/app/validators/date_range_validator.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+# Validates that start_at is same or before end_at in the given record
+class DateRangeValidator < ActiveModel::Validator
+ def validate(record)
+ the_end = record.end_at
+ the_start = record.start_at
+ return if the_end.present? && the_end >= the_start
+
+ record.errors[:end_at] << I18n.t('errors.messages.end_before_start', START: the_start)
+ end
+end
diff --git a/app/validators/period_integrity_validator.rb b/app/validators/period_integrity_validator.rb
new file mode 100644
index 000000000..8dc8e6f11
--- /dev/null
+++ b/app/validators/period_integrity_validator.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+# Validates that all invoices in the current accounting period are chained with footprints which ensure their integrity
+class PeriodIntegrityValidator < ActiveModel::Validator
+ def validate(record)
+ the_end = record.end_at
+ the_start = record.start_at
+
+ invoices = Invoice.where('created_at >= :start_date AND created_at < :end_date', start_date: the_start, end_date: the_end)
+ .includes(:invoice_items)
+
+
+ invoices.each do |i|
+ record.errors["invoice_#{i.reference}".to_sym] << I18n.t('errors.messages.invalid_footprint') unless i.check_footprint
+ end
+ end
+end
diff --git a/app/validators/period_overlap_validator.rb b/app/validators/period_overlap_validator.rb
new file mode 100644
index 000000000..8d718c9a8
--- /dev/null
+++ b/app/validators/period_overlap_validator.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# Validates the current accounting period does not overlap an existing one
+class PeriodOverlapValidator < ActiveModel::Validator
+ def validate(record)
+ the_end = record.end_at
+ the_start = record.start_at
+
+ AccountingPeriod.all.each do |period|
+ if the_start >= period.start_at && the_start <= period.end_at
+ record.errors[:start_at] << I18n.t('errors.messages.cannot_overlap')
+ end
+ if the_end >= period.start_at && the_end <= period.end_at
+ record.errors[:end_at] << I18n.t('errors.messages.cannot_overlap')
+ end
+ if period.start_at >= the_start && period.end_at <= the_end
+ record.errors[:end_at] << I18n.t('errors.messages.cannot_encompass')
+ end
+ end
+ end
+end
diff --git a/app/views/api/accounting_periods/index.json.jbuilder b/app/views/api/accounting_periods/index.json.jbuilder
new file mode 100644
index 000000000..3980cc0dd
--- /dev/null
+++ b/app/views/api/accounting_periods/index.json.jbuilder
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+json.array!(@accounting_periods) do |ap|
+ json.extract! ap, :id, :start_at, :end_at, :closed_at, :closed_by, :footprint, :created_at
+ json.period_total ap.period_total / 100.0
+ json.perpetual_total ap.perpetual_total / 100.0
+ json.chained_footprint ap.check_footprint
+ json.user_name "#{ap.first_name} #{ap.last_name}"
+end
diff --git a/app/views/api/accounting_periods/last_closing_end.json.jbuilder b/app/views/api/accounting_periods/last_closing_end.json.jbuilder
new file mode 100644
index 000000000..fe8ebedfa
--- /dev/null
+++ b/app/views/api/accounting_periods/last_closing_end.json.jbuilder
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+json.last_end_date @last_end
diff --git a/app/views/api/accounting_periods/show.json.jbuilder b/app/views/api/accounting_periods/show.json.jbuilder
new file mode 100644
index 000000000..a66aad1ab
--- /dev/null
+++ b/app/views/api/accounting_periods/show.json.jbuilder
@@ -0,0 +1,3 @@
+# frozen_string_literal: true
+
+json.extract! @accounting_period, :id, :start_at, :end_at, :closed_at, :closed_by, :created_at
diff --git a/app/views/api/auth_providers/active.json.jbuilder b/app/views/api/auth_providers/active.json.jbuilder
index fc0a96351..1f35ece38 100644
--- a/app/views/api/auth_providers/active.json.jbuilder
+++ b/app/views/api/auth_providers/active.json.jbuilder
@@ -4,9 +4,9 @@ json.link_to_sso_profile @provider.link_to_sso_profile
if @provider.providable_type == DatabaseProvider.name
json.link_to_sso_connect '/#'
else
- json.link_to_sso_connect user_omniauth_authorize_path(@provider.strategy_name.to_sym)
+ json.link_to_sso_connect "/users/auth/#{@provider.strategy_name}"
end
if @provider.providable_type == OAuth2Provider.name
json.domain @provider.providable.domain
-end
\ No newline at end of file
+end
diff --git a/app/views/api/events/_event.json.jbuilder b/app/views/api/events/_event.json.jbuilder
index d3f0499ee..51098ee40 100644
--- a/app/views/api/events/_event.json.jbuilder
+++ b/app/views/api/events/_event.json.jbuilder
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
json.extract! event, :id, :title, :description, :age_range_id
json.event_image event.event_image.attachment_url if event.event_image
json.event_files_attributes event.event_files do |f|
@@ -6,18 +8,22 @@ json.event_files_attributes event.event_files do |f|
json.attachment_url f.attachment_url
end
json.category_id event.category_id
-json.category do
- json.id event.category.id
- json.name event.category.name
-end if event.category
+if event.category
+ json.category do
+ json.id event.category.id
+ json.name event.category.name
+ end
+end
json.event_theme_ids event.event_theme_ids
json.event_themes event.event_themes do |e|
json.name e.name
end
json.age_range_id event.age_range_id
-json.age_range do
- json.name event.age_range.name
-end if event.age_range
+if event.age_range
+ json.age_range do
+ json.name event.age_range.name
+ end
+end
json.start_date event.availability.start_at
json.start_time event.availability.start_at
json.end_date event.availability.end_at
@@ -25,7 +31,7 @@ json.end_time event.availability.end_at
json.month t('date.month_names')[event.availability.start_at.month]
json.month_id event.availability.start_at.month
json.year event.availability.start_at.year
-json.all_day event.availability.start_at.hour == 0 ? 'true' : 'false'
+json.all_day event.availability.start_at.hour.zero? ? 'true' : 'false'
json.availability do
json.id event.availability.id
json.start_at event.availability.start_at
diff --git a/app/views/api/invoices/list.json.jbuilder b/app/views/api/invoices/list.json.jbuilder
index 648900267..629139c1d 100644
--- a/app/views/api/invoices/list.json.jbuilder
+++ b/app/views/api/invoices/list.json.jbuilder
@@ -12,4 +12,5 @@ json.array!(@invoices) do |invoice|
json.stripe invoice.stp_invoice_id?
json.date invoice.is_a?(Avoir) ? invoice.avoir_date : invoice.created_at
json.prevent_refund invoice.prevent_refund?
+ json.chained_footprint invoice.check_footprint
end
diff --git a/app/views/api/notifications/_notify_admin_close_period_reminder.json.jbuilder b/app/views/api/notifications/_notify_admin_close_period_reminder.json.jbuilder
new file mode 100644
index 000000000..3f8bde945
--- /dev/null
+++ b/app/views/api/notifications/_notify_admin_close_period_reminder.json.jbuilder
@@ -0,0 +1,7 @@
+json.title notification.notification_type
+if notification.attached_object.class.name == AccountingPeriod.name
+ json.description t('.warning_last_closed_period_over_1_year', LAST_END: notification.attached_object.end_at)
+else
+ json.description t('.warning_no_closed_periods', FIRST_DATE: notification.attached_object.created_at.to_date)
+end
+json.url notification_url(notification, format: :json)
diff --git a/app/views/api/notifications/_notify_admin_free_disk_space.json.jbuilder b/app/views/api/notifications/_notify_admin_free_disk_space.json.jbuilder
new file mode 100644
index 000000000..bf275289d
--- /dev/null
+++ b/app/views/api/notifications/_notify_admin_free_disk_space.json.jbuilder
@@ -0,0 +1,3 @@
+json.title notification.notification_type
+json.description t('.warning_free_disk_space', AVAILABLE: number_with_delimiter(notification.meta_data['mb_available']))
+json.url notification_url(notification, format: :json)
diff --git a/app/views/application/index.html.erb b/app/views/application/index.html.erb
index 84703a1ca..b0810ce43 100644
--- a/app/views/application/index.html.erb
+++ b/app/views/application/index.html.erb
@@ -9,14 +9,9 @@
<%=Setting.find_by(name: 'fablab_name').value%>
- <% if ENV['DEFAULT_HOST'] == 'fablab.lacasemate.fr' %>
-
-
- <% else %>
-
-
-
- <% end %>
+
+
+