From 35ce577651a46bccfa1ffa0bbc010e1092dbab48 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 29 Apr 2020 10:57:32 +0200 Subject: [PATCH] [bug] subscription page shows the groups without any active plans [bug] cart price inconsistently updated after a subscription [feature] plans page for managers --- CHANGELOG.md | 2 + app/assets/javascripts/app.js | 2 - .../javascripts/controllers/plans.js.erb | 50 +++++++++++-------- app/assets/templates/plans/index.html.erb | 6 +-- .../api/subscriptions_controller.rb | 7 +-- app/models/user.rb | 4 ++ app/policies/subscription_context.rb | 5 +- app/policies/subscription_policy.rb | 2 +- config/locales/app.public.fr.yml | 1 + lib/tasks/fablab/stripe.rake | 11 +++- 10 files changed, 58 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 855735dd6..2578f7226 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ - Fix a bug: "Free entry" label for events without reservation - Fix a bug: updating a setting without any changes triggers an error - Fix a bug: plan edition does not show the associated group +- Fix a bug: subscription page shows the groups without any active plans +- Fix a bug: cart price inconsistently updated after a subscription ## v4.3.4 2020 April 14 diff --git a/app/assets/javascripts/app.js b/app/assets/javascripts/app.js index 47902e1db..0b08ad6cf 100644 --- a/app/assets/javascripts/app.js +++ b/app/assets/javascripts/app.js @@ -64,8 +64,6 @@ angular.module('application', ['ngCookies', 'ngResource', 'ngSanitize', 'ui.rout $translateProvider.useMessageFormatInterpolation(); // Set the language of the instance (from ruby configuration) $translateProvider.preferredLanguage(Fablab.locale); - // In any cases, fallback to english - $translateProvider.fallbackLanguage('en'); // End the tour when the user clicks the forward or back buttons of the browser TourConfigProvider.enableNavigationInterceptors(); }]).run(['$rootScope', '$log', 'AuthService', 'Auth', 'amMoment', '$state', 'editableOptions', 'Analytics', diff --git a/app/assets/javascripts/controllers/plans.js.erb b/app/assets/javascripts/controllers/plans.js.erb index a89f07f5f..7f4fa4386 100644 --- a/app/assets/javascripts/controllers/plans.js.erb +++ b/app/assets/javascripts/controllers/plans.js.erb @@ -12,8 +12,8 @@ */ 'use strict'; -Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScope', '$state', '$uibModal', 'Auth', 'dialogs', 'growl', 'plansPromise', 'groupsPromise', 'Subscription', 'Member', 'subscriptionExplicationsPromise', '_t', 'Wallet', 'helpers', - function ($scope, $rootScope, $state, $uibModal, Auth, dialogs, growl, plansPromise, groupsPromise, Subscription, Member, subscriptionExplicationsPromise, _t, Wallet, helpers) { +Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScope', '$state', '$uibModal', 'Auth', 'AuthService', 'dialogs', 'growl', 'plansPromise', 'groupsPromise', 'Subscription', 'Member', 'subscriptionExplicationsPromise', '_t', 'Wallet', 'helpers', + function ($scope, $rootScope, $state, $uibModal, Auth, AuthService, dialogs, growl, plansPromise, groupsPromise, Subscription, Member, subscriptionExplicationsPromise, _t, Wallet, helpers) { /* PUBLIC SCOPE */ // list of groups @@ -28,13 +28,6 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop // list of plans, classified by group $scope.plansClassifiedByGroup = []; - for (const group of Array.from($scope.groups)) { - const groupObj = { id: group.id, name: group.name, plans: [] }; - for (let plan of Array.from(plansPromise)) { - if (plan.group_id === group.id) { groupObj.plans.push(plan); } - } - $scope.plansClassifiedByGroup.push(groupObj); - } // user to deal with $scope.ctrl = { @@ -62,7 +55,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop /** * Callback to deal with the subscription of the user selected in the dropdown list instead of the current user's - * subscription. (admins only) + * subscription. (admins and managers only) */ $scope.updateMember = function () { $scope.selectedPlan = null; @@ -97,14 +90,17 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop $scope.openSubscribePlanModal = function () { Wallet.getWalletByUser({ user_id: $scope.ctrl.member.id }, function (wallet) { const amountToPay = helpers.getAmountToPay($scope.cart.total, wallet.amount); - if (($scope.currentUser.role !== 'admin') && (amountToPay > 0)) { + if ((AuthService.isAuthorized('member') && amountToPay > 0) + || (AuthService.isAuthorized('manager') && $scope.ctrl.member.id === $rootScope.currentUser.id && amountToPay > 0)) { if ($rootScope.fablabWithoutOnlinePayment) { growl.error(_t('app.public.plans.online_payment_disabled')); } else { return payByStripe(); } } else { - if (($scope.currentUser.role === 'admin') || (amountToPay === 0)) { + if (AuthService.isAuthorized('admin') + || (AuthService.isAuthorized('manager') && $scope.ctrl.member.id !== $rootScope.currentUser.id) + || amountToPay === 0) { return payOnSite(); } } @@ -130,7 +126,8 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop $scope.ctrl.member = user; $scope.group.change = false; $scope.selectedPlan = null; - if ($scope.currentUser.role !== 'admin') { + if (AuthService.isAuthorized('member') || + (AuthService.isAuthorized('manager') && $scope.currentUser.id !== $scope.ctrl.member.id)) { $rootScope.currentUser = user; Auth._currentUser.group_id = user.group_id; growl.success(_t('app.public.plans.your_group_was_successfully_changed')); @@ -139,7 +136,8 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop } } , function (err) { - if ($scope.currentUser.role !== 'admin') { + if (AuthService.isAuthorized('member') || + (AuthService.isAuthorized('manager') && $scope.currentUser.id !== $scope.ctrl.member.id)) { growl.error(_t('app.public.plans.an_error_prevented_your_group_from_being_changed')); } else { growl.error(_t('app.public.plans.an_error_prevented_to_change_the_user_s_group')); @@ -179,8 +177,20 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop * Kind of constructor: these actions will be realized first when the controller is loaded */ const initialize = function () { + // group all plans by Group + for (const group of $scope.groups) { + const groupObj = { id: group.id, name: group.name, plans: [], actives: 0 }; + for (let plan of plansPromise) { + if (plan.group_id === group.id) { + groupObj.plans.push(plan); + if (!plan.disabled) { groupObj.actives++; } + } + } + $scope.plansClassifiedByGroup.push(groupObj); + } + if ($scope.currentUser) { - if ($scope.currentUser.role !== 'admin') { + if (!AuthService.isAuthorized('admin')) { $scope.ctrl.member = $scope.currentUser; $scope.paid.plan = $scope.currentUser.subscribed_plan; $scope.group.id = $scope.currentUser.group_id; @@ -201,9 +211,9 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop * Compute the total amount for the current reservation according to the previously set parameters * and assign the result in $scope.reserve.amountTotal */ - var updateCartPrice = function () { - // first we check that a user was selected - if (Object.keys($scope.ctrl.member).length > 0) { + const updateCartPrice = function () { + // first we check the selection of a user + if (Object.keys($scope.ctrl.member).length > 0 && $scope.selectedPlan) { $scope.cart.total = $scope.selectedPlan.amount; // apply the coupon if any if ($scope.coupon.applied) { @@ -262,7 +272,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop CustomAsset.get({ name: 'cgv-file' }, function (cgv) { $scope.cgv = cgv.custom_asset; }); /** - * Callback for click on the 'proceed' button. + * Callback for a click on the 'proceed' button. * Handle the stripe's card tokenization process response and save the subscription to the API with the * card token just created. */ @@ -301,7 +311,7 @@ Application.Controllers.controller('PlansIndexController', ['$scope', '$rootScop // user wallet amount $scope.walletAmount = wallet.amount; - // subcription price, coupon subtracted if any + // subscription price, coupon subtracted if any $scope.price = price; // price to pay diff --git a/app/assets/templates/plans/index.html.erb b/app/assets/templates/plans/index.html.erb index 31eaff911..c6ba67b36 100644 --- a/app/assets/templates/plans/index.html.erb +++ b/app/assets/templates/plans/index.html.erb @@ -17,7 +17,7 @@
-
+

{{plansGroup.name}}

@@ -41,7 +41,7 @@
-
+
-
+
diff --git a/app/controllers/api/subscriptions_controller.rb b/app/controllers/api/subscriptions_controller.rb index 536c1faf2..182edb105 100644 --- a/app/controllers/api/subscriptions_controller.rb +++ b/app/controllers/api/subscriptions_controller.rb @@ -10,12 +10,13 @@ class API::SubscriptionsController < API::ApiController end # Admins can create any subscriptions. Members can directly create subscriptions if total = 0, - # otherwise, they must use payments_controller#confirm_payment + # otherwise, they must use payments_controller#confirm_payment. + # Managers can create subscriptions for other users def create - user_id = current_user.admin? ? params[:subscription][:user_id] : current_user.id + user_id = current_user.admin? || current_user.manager? ? params[:subscription][:user_id] : current_user.id amount = transaction_amount(current_user.admin?, user_id) - authorize SubscriptionContext.new(Subscription, amount) + authorize SubscriptionContext.new(Subscription, amount, user_id) @subscription = Subscription.new(subscription_params) is_subscribe = Subscriptions::Subscribe.new(current_user.invoicing_profile.id, user_id) diff --git a/app/models/user.rb b/app/models/user.rb index 6327617de..e3b7e47aa 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -105,6 +105,10 @@ class User < ApplicationRecord User.with_role(:manager) end + def self.online_payers + User.with_any_role(:manager, :member) + end + def self.superadmin return unless Rails.application.secrets.superadmin_email.present? diff --git a/app/policies/subscription_context.rb b/app/policies/subscription_context.rb index aa2c134f6..e784814a4 100644 --- a/app/policies/subscription_context.rb +++ b/app/policies/subscription_context.rb @@ -2,11 +2,12 @@ # Pundit Additional context to validate the price of a subscription class SubscriptionContext - attr_reader :subscription, :price + attr_reader :subscription, :price, :user_id - def initialize(subscription, price) + def initialize(subscription, price, user_id) @subscription = subscription @price = price + @user_id = user_id end def policy_class diff --git a/app/policies/subscription_policy.rb b/app/policies/subscription_policy.rb index d21f9ba24..a6866fb6c 100644 --- a/app/policies/subscription_policy.rb +++ b/app/policies/subscription_policy.rb @@ -4,7 +4,7 @@ class SubscriptionPolicy < ApplicationPolicy include FablabConfiguration def create? - !fablab_plans_deactivated? && (user.admin? || record.price.zero?) + !fablab_plans_deactivated? && (user.admin? || (user.manager? && record.user_id != user.id) || record.price.zero?) end def show? diff --git a/config/locales/app.public.fr.yml b/config/locales/app.public.fr.yml index 2ed3159e6..8f1971a5f 100644 --- a/config/locales/app.public.fr.yml +++ b/config/locales/app.public.fr.yml @@ -29,6 +29,7 @@ fr: #left menu notifications: "Notifications" admin: "Admin" + manager: "Gestionnaire" reduce_panel: "RĂ©duire le volet" #left menu (public) home: "Accueil" diff --git a/lib/tasks/fablab/stripe.rake b/lib/tasks/fablab/stripe.rake index 7b216db6f..486b86b02 100644 --- a/lib/tasks/fablab/stripe.rake +++ b/lib/tasks/fablab/stripe.rake @@ -48,7 +48,10 @@ namespace :fablab do desc 'sync users to the stripe database' task sync_members: :environment do - User.members.each do |member| + puts 'We create all non-existing customers on stripe. This may take a while, please wait...' + total = User.online_payers.count + User.online_payers.each_with_index do |member, index| + print_on_line "#{index} / #{total}" begin stp_customer = Stripe::Customer.retrieve member.stp_customer_id StripeWorker.perform_async(:create_stripe_customer, member.id) if stp_customer.nil? || stp_customer[:deleted] @@ -56,6 +59,12 @@ namespace :fablab do StripeWorker.perform_async(:create_stripe_customer, member.id) end end + puts 'Done' + end + + def print_on_line(str) + print "#{str}\r" + $stdout.flush end end end