mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-18 07:52:23 +01:00
(wip) allow admin to reserve for himself
This commit is contained in:
parent
d4613cdbcc
commit
f9f60cba17
@ -7,9 +7,9 @@ Layout/LineLength:
|
||||
Metrics/MethodLength:
|
||||
Max: 35
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 13
|
||||
Max: 14
|
||||
Metrics/PerceivedComplexity:
|
||||
Max: 11
|
||||
Max: 14
|
||||
Metrics/AbcSize:
|
||||
Max: 45
|
||||
Metrics/ClassLength:
|
||||
|
@ -7,6 +7,7 @@
|
||||
- Fix a bug: no statistics on trainings and spaces reservations
|
||||
- Fix a security issue: updated nokogiri to 1.13.9 to fix [GHSA-2qc6-mcvw-92cw](https://github.com/advisories/GHSA-2qc6-mcvw-92cw)
|
||||
- [TODO DEPLOY] `rails fablab:maintenance:regenerate_statistics[2021,6]`
|
||||
- [TODO DEPLOY] `rails fablab:setup:set_admins_group`
|
||||
|
||||
## v5.4.25 2022 October 19
|
||||
|
||||
|
@ -157,7 +157,7 @@ class API::MembersController < API::ApiController
|
||||
end
|
||||
|
||||
def search
|
||||
@members = Members::ListService.search(current_user, params[:query], params[:subscription], params[:include_admins])
|
||||
@members = Members::ListService.search(current_user, params[:query], params[:subscription])
|
||||
end
|
||||
|
||||
def mapping
|
||||
|
@ -231,7 +231,7 @@ class ProjectsController {
|
||||
const asciiName = Diacritics.remove(nameLookup);
|
||||
|
||||
Member.search(
|
||||
{ query: asciiName, include_admins: 'true' },
|
||||
{ query: asciiName },
|
||||
function (users) { $scope.matchingMembers = users; },
|
||||
function (error) { console.error(error); }
|
||||
);
|
||||
|
@ -291,13 +291,11 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the currently logged user has the 'admin' role OR the 'manager' role, but is not taking reseravtion for himself
|
||||
* Check if the currently logged user has the 'admin' OR 'manager' role, but is not taking reseravtion for himself
|
||||
* @returns {boolean}
|
||||
*/
|
||||
$scope.isAuthorized = function () {
|
||||
if (AuthService.isAuthorized('admin')) return true;
|
||||
|
||||
if (AuthService.isAuthorized('manager')) {
|
||||
if (AuthService.isAuthorized(['admin', 'manager'])) {
|
||||
return ($rootScope.currentUser.id !== $scope.user.id);
|
||||
}
|
||||
|
||||
@ -823,11 +821,10 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
|
||||
return Wallet.getWalletByUser({ user_id: $scope.user.id }, function (wallet) {
|
||||
const amountToPay = helpers.getAmountToPay($scope.amountTotal, wallet.amount);
|
||||
if ((AuthService.isAuthorized(['member']) && (amountToPay > 0 || (amountToPay === 0 && hasOtherDeadlines()))) ||
|
||||
(AuthService.isAuthorized('manager') && $scope.user.id === $rootScope.currentUser.id && amountToPay > 0)) {
|
||||
($scope.user.id === $rootScope.currentUser.id && amountToPay > 0)) {
|
||||
return payOnline(items);
|
||||
} else {
|
||||
if (AuthService.isAuthorized(['admin']) ||
|
||||
(AuthService.isAuthorized('manager') && $scope.user.id !== $rootScope.currentUser.id) ||
|
||||
if (AuthService.isAuthorized(['admin', 'manager'] && $scope.user.id !== $rootScope.currentUser.id) ||
|
||||
(amountToPay === 0 && !hasOtherDeadlines())) {
|
||||
return payOnSite(items);
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ class AccountingPeriod < ApplicationRecord
|
||||
validates_with PeriodOverlapValidator
|
||||
validates_with PeriodIntegrityValidator
|
||||
|
||||
belongs_to :user, class_name: 'User', foreign_key: 'closed_by'
|
||||
belongs_to :user, class_name: 'User', foreign_key: 'closed_by', inverse_of: :accounting_periods
|
||||
|
||||
def delete
|
||||
false
|
||||
|
@ -43,9 +43,9 @@ class User < ApplicationRecord
|
||||
has_many :exports, dependent: :destroy
|
||||
has_many :imports, dependent: :nullify
|
||||
|
||||
has_one :payment_gateway_object, as: :item
|
||||
has_one :payment_gateway_object, as: :item, dependent: :nullify
|
||||
|
||||
has_many :accounting_periods, foreign_key: 'closed_by', dependent: :nullify
|
||||
has_many :accounting_periods, foreign_key: 'closed_by', dependent: :nullify, inverse_of: :user
|
||||
|
||||
has_many :proof_of_identity_files, dependent: :destroy
|
||||
has_many :proof_of_identity_refusals, dependent: :destroy
|
||||
@ -56,14 +56,15 @@ class User < ApplicationRecord
|
||||
end
|
||||
|
||||
before_create :assign_default_role
|
||||
after_commit :create_gateway_customer, on: [:create]
|
||||
after_commit :notify_admin_when_user_is_created, on: :create
|
||||
after_create :init_dependencies
|
||||
after_update :update_invoicing_profile, if: :invoicing_data_was_modified?
|
||||
after_update :update_statistic_profile, if: :statistic_data_was_modified?
|
||||
before_destroy :remove_orphan_drafts
|
||||
after_commit :create_gateway_customer, on: [:create]
|
||||
after_commit :notify_admin_when_user_is_created, on: :create
|
||||
|
||||
attr_accessor :cgu
|
||||
|
||||
delegate :first_name, to: :profile
|
||||
delegate :last_name, to: :profile
|
||||
delegate :subscriptions, to: :statistic_profile
|
||||
@ -116,11 +117,11 @@ class User < ApplicationRecord
|
||||
end
|
||||
|
||||
def self.online_payers
|
||||
User.with_any_role(:manager, :member)
|
||||
User.with_any_role(:admin, :manager, :member)
|
||||
end
|
||||
|
||||
def self.adminsys
|
||||
return unless Rails.application.secrets.adminsys_email.present?
|
||||
return if Rails.application.secrets.adminsys_email.blank?
|
||||
|
||||
User.find_by('lower(email) = ?', Rails.application.secrets.adminsys_email&.downcase)
|
||||
end
|
||||
@ -225,7 +226,7 @@ class User < ApplicationRecord
|
||||
def update_statistic_profile
|
||||
raise NoProfileError if statistic_profile.nil? || statistic_profile.id.nil?
|
||||
|
||||
statistic_profile.update_attributes(
|
||||
statistic_profile.update(
|
||||
group_id: group_id,
|
||||
role_id: roles.first.id
|
||||
)
|
||||
@ -255,7 +256,7 @@ class User < ApplicationRecord
|
||||
def remove_orphan_drafts
|
||||
orphans = my_projects
|
||||
.joins('LEFT JOIN project_users ON projects.id = project_users.project_id')
|
||||
.where('project_users.project_id IS NULL')
|
||||
.where(project_users: { project_id: nil })
|
||||
.where(state: 'draft')
|
||||
orphans.map(&:destroy!)
|
||||
end
|
||||
@ -342,7 +343,7 @@ class User < ApplicationRecord
|
||||
def update_invoicing_profile
|
||||
raise NoProfileError if invoicing_profile.nil?
|
||||
|
||||
invoicing_profile.update_attributes(
|
||||
invoicing_profile.update(
|
||||
email: email,
|
||||
first_name: first_name,
|
||||
last_name: last_name
|
||||
@ -351,7 +352,7 @@ class User < ApplicationRecord
|
||||
|
||||
def password_complexity
|
||||
return if password.blank? || SecurePassword.is_secured?(password)
|
||||
|
||||
|
||||
errors.add I18n.t("app.public.common.password_is_too_weak"), I18n.t("app.public.common.password_is_too_weak_explanations")
|
||||
end
|
||||
end
|
||||
|
@ -28,7 +28,7 @@ class Members::ListService
|
||||
if params[:search].size.positive?
|
||||
@query = @query.where('users.username ILIKE :search OR ' \
|
||||
'profiles.first_name ILIKE :search OR ' \
|
||||
'profiles.last_name ILIKE :search OR ' \
|
||||
'profiles.last_name ILIKE :search OR ' \
|
||||
'profiles.phone ILIKE :search OR ' \
|
||||
'email ILIKE :search OR ' \
|
||||
'groups.name ILIKE :search OR ' \
|
||||
@ -41,19 +41,19 @@ class Members::ListService
|
||||
@query
|
||||
end
|
||||
|
||||
def search(current_user, query, subscription, include_admins = 'false')
|
||||
def search(current_user, query, subscription)
|
||||
members = User.includes(:profile, :statistic_profile)
|
||||
.joins(:profile,
|
||||
:statistic_profile,
|
||||
:roles,
|
||||
'LEFT JOIN "subscriptions" ON "subscriptions"."statistic_profile_id" = "statistic_profiles"."id" AND ' \
|
||||
'"subscriptions"."created_at" = ( ' \
|
||||
'SELECT max("created_at") ' \
|
||||
'FROM "subscriptions" ' \
|
||||
'WHERE "statistic_profile_id" = "statistic_profiles"."id")')
|
||||
'SELECT max("created_at") ' \
|
||||
'FROM "subscriptions" ' \
|
||||
'WHERE "statistic_profile_id" = "statistic_profiles"."id")')
|
||||
.where("users.is_active = 'true'")
|
||||
.limit(50)
|
||||
query.downcase.split(' ').each do |word|
|
||||
query.downcase.split.each do |word|
|
||||
members = members.where('lower(f_unaccent(users.username)) ~ :search OR ' \
|
||||
'lower(f_unaccent(profiles.first_name)) ~ :search OR ' \
|
||||
'lower(f_unaccent(profiles.last_name)) ~ :search',
|
||||
@ -66,13 +66,11 @@ class Members::ListService
|
||||
members = members.where("users.is_allow_contact = 'true'")
|
||||
elsif subscription == 'true'
|
||||
# only admins have the ability to filter by subscription
|
||||
members = members.where('subscriptions.id IS NOT NULL AND subscriptions.expiration_date >= :now', now: Date.today.to_s)
|
||||
members = members.where('subscriptions.id IS NOT NULL AND subscriptions.expiration_date >= :now', now: Time.zone.today.to_s)
|
||||
elsif subscription == 'false'
|
||||
members = members.where('subscriptions.id IS NULL OR subscriptions.expiration_date < :now', now: Date.today.to_s)
|
||||
members = members.where('subscriptions.id IS NULL OR subscriptions.expiration_date < :now', now: Time.zone.today.to_s)
|
||||
end
|
||||
|
||||
members = members.where("roles.name = 'member' OR roles.name = 'manager'") if include_admins == 'false' || include_admins.blank?
|
||||
|
||||
members.to_a.filter(&:valid?)
|
||||
end
|
||||
|
||||
|
@ -1,27 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# asynchronously export the statistics to an excel file and send the result by email
|
||||
class StatisticsExportWorker
|
||||
include Sidekiq::Worker
|
||||
|
||||
def perform(export_id)
|
||||
export = Export.find(export_id)
|
||||
|
||||
unless export.user.admin?
|
||||
raise SecurityError, 'Not allowed to export'
|
||||
end
|
||||
raise SecurityError, 'Not allowed to export' unless export.user.admin?
|
||||
|
||||
unless export.category == 'statistics'
|
||||
raise KeyError, 'Wrong worker called'
|
||||
end
|
||||
raise KeyError, 'Wrong worker called' unless export.category == 'statistics'
|
||||
|
||||
service = StatisticsExportService.new
|
||||
method_name = "export_#{export.export_type}"
|
||||
|
||||
if %w(account event machine project subscription training space global).include?(export.export_type) and service.respond_to?(method_name)
|
||||
service.public_send(method_name, export)
|
||||
|
||||
NotificationCenter.call type: :notify_admin_export_complete,
|
||||
receiver: export.user,
|
||||
attached_object: export
|
||||
unless %w[account event machine project subscription training space global].include?(export.export_type) &&
|
||||
service.respond_to?(method_name)
|
||||
return
|
||||
end
|
||||
|
||||
service.public_send(method_name, export)
|
||||
|
||||
NotificationCenter.call type: :notify_admin_export_complete,
|
||||
receiver: export.user,
|
||||
attached_object: export
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -453,9 +453,6 @@ en:
|
||||
price_category:
|
||||
reduced_fare: "Reduced fare"
|
||||
reduced_fare_if_you_are_under_25_student_or_unemployed: "Reduced fare if you are under 25, student or unemployed."
|
||||
group:
|
||||
#name of the user's group for administrators
|
||||
admins: 'Administrators'
|
||||
cart_items:
|
||||
free_extension: "Free extension of a subscription, until %{DATE}"
|
||||
statistic_profile:
|
||||
|
@ -82,12 +82,10 @@ if Group.count.zero?
|
||||
])
|
||||
end
|
||||
|
||||
Group.create! name: I18n.t('group.admins'), slug: 'admins' unless Group.find_by(slug: 'admins')
|
||||
|
||||
# Create the default admin if none exists yet
|
||||
if Role.where(name: 'admin').joins(:users).count.zero?
|
||||
admin = User.new(username: 'admin', email: ENV['ADMIN_EMAIL'], password: ENV['ADMIN_PASSWORD'],
|
||||
password_confirmation: Rails.application.secrets.admin_password, group_id: Group.find_by(slug: 'admins').id,
|
||||
password_confirmation: Rails.application.secrets.admin_password, group_id: Group.first.id,
|
||||
profile_attributes: { first_name: 'admin', last_name: 'admin', phone: '0123456789' },
|
||||
statistic_profile_attributes: { gender: true, birthday: Date.current })
|
||||
admin.add_role 'admin'
|
||||
|
@ -53,15 +53,16 @@ class Stripe::Service < Payment::Service
|
||||
end
|
||||
|
||||
def create_user(user_id)
|
||||
StripeWorker.perform_async(:create_stripe_customer, user_id)
|
||||
StripeWorker.perform_async('create_stripe_customer', user_id)
|
||||
end
|
||||
|
||||
def create_coupon(coupon_id)
|
||||
coupon = Coupon.find(coupon_id)
|
||||
stp_coupon = { id: coupon.code }
|
||||
if coupon.type == 'percent_off'
|
||||
case coupon.type
|
||||
when 'percent_off'
|
||||
stp_coupon[:percent_off] = coupon.percent_off
|
||||
elsif coupon.type == stripe_amount('amount_off')
|
||||
when stripe_amount('amount_off')
|
||||
stp_coupon[:amount_off] = coupon.amount_off
|
||||
stp_coupon[:currency] = Setting.get('stripe_currency')
|
||||
end
|
||||
@ -97,7 +98,7 @@ class Stripe::Service < Payment::Service
|
||||
if stp_subscription.status == 'canceled'
|
||||
# the subscription was canceled by the gateway => notify & update the status
|
||||
notify_payment_schedule_gateway_canceled(payment_schedule_item)
|
||||
payment_schedule_item.update_attributes(state: 'gateway_canceled')
|
||||
payment_schedule_item.update(state: 'gateway_canceled')
|
||||
return
|
||||
end
|
||||
stp_invoice = Stripe::Invoice.retrieve(stp_subscription.latest_invoice, api_key: stripe_key)
|
||||
@ -107,7 +108,7 @@ class Stripe::Service < Payment::Service
|
||||
payment_method: 'card',
|
||||
payment_id: stp_invoice.payment_intent,
|
||||
payment_type: 'Stripe::PaymentIntent')
|
||||
payment_schedule_item.update_attributes(state: 'paid', payment_method: 'card')
|
||||
payment_schedule_item.update(state: 'paid', payment_method: 'card')
|
||||
pgo = PaymentGatewayObject.find_or_initialize_by(item: payment_schedule_item)
|
||||
pgo.gateway_object = stp_invoice
|
||||
pgo.save!
|
||||
@ -115,29 +116,30 @@ class Stripe::Service < Payment::Service
|
||||
##### Payment error
|
||||
notify_payment_schedule_item_failed(payment_schedule_item)
|
||||
stp_payment_intent = Stripe::PaymentIntent.retrieve(stp_invoice.payment_intent, api_key: stripe_key)
|
||||
payment_schedule_item.update_attributes(state: stp_payment_intent.status,
|
||||
client_secret: stp_payment_intent.client_secret)
|
||||
payment_schedule_item.update(state: stp_payment_intent.status,
|
||||
client_secret: stp_payment_intent.client_secret)
|
||||
pgo = PaymentGatewayObject.find_or_initialize_by(item: payment_schedule_item)
|
||||
pgo.gateway_object = stp_invoice
|
||||
pgo.save!
|
||||
elsif stp_invoice.status == 'draft'
|
||||
return # Could be that the stripe invoice does not yet reflect the payment made by the member, because we called that service just after payment is made. We call return here and PaymentScheduleItemWorker will anyway call that method every hour
|
||||
# Could be that the stripe invoice does not yet reflect the payment made by the member, because we called that service
|
||||
# just after payment is made. We just return here and PaymentScheduleItemWorker will anyway call that method every hour
|
||||
else
|
||||
notify_payment_schedule_item_error(payment_schedule_item)
|
||||
payment_schedule_item.update_attributes(state: 'error')
|
||||
payment_schedule_item.update(state: 'error')
|
||||
end
|
||||
end
|
||||
|
||||
def pay_payment_schedule_item(payment_schedule_item)
|
||||
stripe_key = Setting.get('stripe_secret_key')
|
||||
stp_invoice = Stripe::Invoice.pay(@payment_schedule_item.payment_gateway_object.gateway_object_id, {}, { api_key: stripe_key })
|
||||
PaymentScheduleItemWorker.new.perform(@payment_schedule_item.id)
|
||||
stripe_key = Setting.get('stripe_secret_key') # TODO, test me
|
||||
stp_invoice = Stripe::Invoice.pay(payment_schedule_item.payment_gateway_object.gateway_object_id, {}, { api_key: stripe_key })
|
||||
PaymentScheduleItemWorker.new.perform(payment_schedule_item.id)
|
||||
|
||||
{ status: stp_invoice.status }
|
||||
rescue Stripe::StripeError => e
|
||||
stripe_key = Setting.get('stripe_secret_key')
|
||||
stp_invoice = Stripe::Invoice.retrieve(@payment_schedule_item.payment_gateway_object.gateway_object_id, api_key: stripe_key)
|
||||
PaymentScheduleItemWorker.new.perform(@payment_schedule_item.id)
|
||||
stp_invoice = Stripe::Invoice.retrieve(payment_schedule_item.payment_gateway_object.gateway_object_id, api_key: stripe_key)
|
||||
PaymentScheduleItemWorker.new.perform(payment_schedule_item.id)
|
||||
|
||||
{ status: stp_invoice.status, error: e }
|
||||
end
|
||||
|
@ -29,22 +29,23 @@ namespace :fablab do
|
||||
puts "Date: #{invoice.created_at}"
|
||||
|
||||
print 'Delete [d], create the missing reservation [c] OR keep as error[e] ? > '
|
||||
confirm = STDIN.gets.chomp
|
||||
if confirm == 'd'
|
||||
confirm = $stdin.gets.chomp
|
||||
case confirm
|
||||
when 'd'
|
||||
puts "Destroying #{invoice.id}..."
|
||||
invoice.destroy
|
||||
elsif confirm == 'c'
|
||||
when 'c'
|
||||
if invoice.invoiced_type != 'Reservation'
|
||||
STDERR.puts "WARNING: Invoice #{invoice.id} is about #{invoice.invoiced_type}. Please handle manually."
|
||||
STDERR.puts 'Ignoring...'
|
||||
warn "WARNING: Invoice #{invoice.id} is about #{invoice.invoiced_type}. Please handle manually."
|
||||
warn 'Ignoring...'
|
||||
next
|
||||
end
|
||||
|
||||
reservable = find_reservable(ii)
|
||||
if reservable
|
||||
if reservable.is_a? Event
|
||||
STDERR.puts "WARNING: invoice #{invoice.id} is linked to Event #{reservable.id}. This is unsupported, please handle manually."
|
||||
STDERR.puts 'Ignoring...'
|
||||
warn "WARNING: invoice #{invoice.id} is linked to Event #{reservable.id}. This is unsupported, please handle manually."
|
||||
warn 'Ignoring...'
|
||||
next
|
||||
end
|
||||
reservation = ::Reservation.create!(
|
||||
@ -55,10 +56,10 @@ namespace :fablab do
|
||||
)
|
||||
invoice.update_attributes(invoiced: reservation)
|
||||
else
|
||||
STDERR.puts "WARNING: Unable to guess the reservable for invoice #{invoice.id}, please handle manually."
|
||||
STDERR.puts 'Ignoring...'
|
||||
warn "WARNING: Unable to guess the reservable for invoice #{invoice.id}, please handle manually."
|
||||
warn 'Ignoring...'
|
||||
end
|
||||
elsif confirm == 'e'
|
||||
when 'e'
|
||||
invoice.update_attributes(invoiced_type: 'Error')
|
||||
else
|
||||
puts "Operation #{confirm} unknown. Ignoring invoice #{invoice.id}..."
|
||||
@ -112,7 +113,7 @@ namespace :fablab do
|
||||
|
||||
availability = reservable.availabilities.where('start_at <= ? AND end_at >= ?', slot[0], slot[1]).first
|
||||
unless availability
|
||||
STDERR.puts "WARNING: Unable to find an availability for #{reservable.class.name} #{reservable.id}, at #{slot[0]}, creating..."
|
||||
warn "WARNING: Unable to find an availability for #{reservable.class.name} #{reservable.id}, at #{slot[0]}, creating..."
|
||||
availability = reservable.availabilities.create!(start_at: slot[0], end_at: slot[1])
|
||||
end
|
||||
availability
|
||||
|
@ -107,5 +107,30 @@ namespace :fablab do
|
||||
setting.save
|
||||
end
|
||||
end
|
||||
|
||||
desc 'migrate administrators to normal groups'
|
||||
task set_admins_group: :environment do
|
||||
groups = Group.where.not(slug: 'admins').where(disabled: [false, nil]).order(:id)
|
||||
User.admins.each do |admin|
|
||||
print "\e[91m::\e[0m \e[1mMove admin #{admin.profile} to group\e[0m:\n"
|
||||
admin.update(group_id: select_group(groups))
|
||||
PaymentGatewayService.new.create_user(admin.id)
|
||||
end
|
||||
print "\e[32m✅\e[0m \e[1mDone\e[0m\n"
|
||||
end
|
||||
|
||||
def select_group(groups)
|
||||
groups.each do |g|
|
||||
print "#{g.id}) #{g.name}\n"
|
||||
end
|
||||
print '> '
|
||||
group_id = $stdin.gets.chomp
|
||||
if groups.map(&:id).include?(group_id.to_i)
|
||||
group_id
|
||||
else
|
||||
warn "\e[91m[ ❌ ] Please select a valid group number \e[39m"
|
||||
select_group(groups)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user