1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-12-04 15:24:23 +01:00
fab-manager/app/assets/javascripts/controllers/admin/pricing.js.erb
2019-04-04 18:00:19 +02:00

683 lines
25 KiB
Plaintext

/* eslint-disable
handle-callback-err,
no-return-assign,
no-undef,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
'use strict';
/**
* Controller used in the prices edition page
*/
Application.Controllers.controller('EditPricingController', ['$scope', '$state', '$uibModal', '$filter', 'TrainingsPricing', 'Credit', 'Pricing', 'Plan', 'Coupon', 'plans', 'groups', 'growl', 'machinesPricesPromise', 'Price', 'dialogs', 'trainingsPricingsPromise', 'trainingsPromise', 'machineCreditsPromise', 'machinesPromise', 'trainingCreditsPromise', 'couponsPromise', 'spacesPromise', 'spacesPricesPromise', 'spacesCreditsPromise', '_t',
function ($scope, $state, $uibModal, $filter, TrainingsPricing, Credit, Pricing, Plan, Coupon, plans, groups, growl, machinesPricesPromise, Price, dialogs, trainingsPricingsPromise, trainingsPromise, machineCreditsPromise, machinesPromise, trainingCreditsPromise, couponsPromise, spacesPromise, spacesPricesPromise, spacesCreditsPromise, _t) {
/* PUBLIC SCOPE */
// List of machines prices (not considering any plan)
$scope.machinesPrices = machinesPricesPromise;
// List of trainings pricing
$scope.trainingsPricings = trainingsPricingsPromise;
// List of available subscriptions plans (eg. student/month, PME/year ...)
$scope.plans = plans;
$scope.enabledPlans = plans.filter(function (p) { return !p.disabled; });
// List of groups (eg. normal, student ...)
$scope.groups = groups.filter(function (g) { return g.slug !== 'admins'; });
$scope.enabledGroups = groups.filter(function (g) { return (g.slug !== 'admins') && !g.disabled; });
// Associate free machine hours with subscriptions
$scope.machineCredits = machineCreditsPromise;
// Array of associations (plan <-> training)
$scope.trainingCredits = trainingCreditsPromise;
// Associate a plan with all its trainings ids
$scope.trainingCreditsGroups = {};
// List of trainings
$scope.trainings = trainingsPromise.filter(function (t) { return !t.disabled; });
// List of machines
$scope.machines = machinesPromise;
$scope.enabledMachines = machinesPromise.filter(function (m) { return !m.disabled; });
// List of coupons
$scope.coupons = couponsPromise;
$scope.couponsPage = 1;
// List of spaces
$scope.spaces = spacesPromise;
$scope.enabledSpaces = spacesPromise.filter(function (s) { return !s.disabled; });
// Associate free space hours with subscriptions
$scope.spaceCredits = spacesCreditsPromise;
// List of spaces prices (not considering any plan)
$scope.spacesPrices = spacesPricesPromise;
// The plans list ordering. Default: by group
$scope.orderPlans = 'group_id';
// Status of the drop-down menu in Credits tab
$scope.status =
{ isopen: false };
// Default: we show only enabled plans
$scope.planFiltering = 'enabled';
// Available options for filtering plans by status
$scope.filterDisabled = [
'enabled',
'disabled',
'all'
];
// Default: we do not filter coupons
$scope.filter = {
coupon: 'all',
};
// Available status for filtering coupons
$scope.couponStatus = [
'all',
'disabled',
'expired',
'sold_out',
'active'
];
$scope.findTrainingsPricing = function (trainingsPricings, trainingId, groupId) {
for (let trainingsPricing of Array.from(trainingsPricings)) {
if ((trainingsPricing.training_id === trainingId) && (trainingsPricing.group_id === groupId)) {
return trainingsPricing;
}
}
};
$scope.updateTrainingsPricing = function (data, trainingsPricing) {
if (data != null) {
return TrainingsPricing.update({ id: trainingsPricing.id }, { trainings_pricing: { amount: data } }).$promise;
} else {
return _t('pricing.please_specify_a_number');
}
};
/**
* Retrieve a plan from its given identifier and returns it
* @param id {number} plan ID
* @returns {Object} Plan, inherits from $resource
*/
$scope.getPlanFromId = function (id) {
for (let plan of Array.from($scope.plans)) {
if (plan.id === parseInt(id)) {
return plan;
}
}
};
/**
* Retrieve a group from its given identifier and returns it
* @param id {number} group ID
* @returns {Object} Group, inherits from $resource
*/
$scope.getGroupFromId = function (groups, id) {
for (let group of Array.from(groups)) {
if (group.id === parseInt(id)) {
return group;
}
}
};
/**
* Returns a human readable string of named trainings, according to the provided array.
* $scope.trainings may contains the full list of training. The returned string will only contains the trainings
* whom ID are given in the provided parameter
* @param trainings {Array<number>} trainings IDs array
*/
$scope.showTrainings = function (trainings) {
if (!angular.isArray(trainings) || !(trainings.length > 0)) {
return _t('pricing.none');
}
const selected = [];
angular.forEach($scope.trainings, function (t) {
if (trainings.indexOf(t.id) >= 0) {
return selected.push(t.name);
}
});
if (selected.length) { return selected.join(' | '); } else { return _t('pricing.none'); }
};
/**
* Validation callback when editing training's credits. Save the changes.
* @param newdata {Object} training and associated plans
* @param planId {number|string} plan id
*/
$scope.saveTrainingCredits = function (newdata, planId) {
// save the number of credits
Plan.update(
{ id: planId },
{ training_credit_nb: newdata.training_credits }
, angular.noop() // do nothing in case of success
, function (error) {
growl.error(_t('pricing.an_error_occurred_while_saving_the_number_of_credits'));
console.error(error);
}
);
// save the associated trainings
return angular.forEach($scope.trainingCreditsGroups, function (original, key) {
if (parseInt(key) === parseInt(planId)) { // we've got the original data
if (original.join('_') !== newdata.training_ids.join('_')) { // if any changes
// iterate through the previous credits to remove
angular.forEach(original, function (oldTrainingId) {
if (newdata.training_ids.indexOf(oldTrainingId) === -1) {
const tc = findTrainingCredit(oldTrainingId, planId);
if (tc) {
return tc.$delete({}
, function () {
$scope.trainingCredits.splice($scope.trainingCredits.indexOf(tc), 1);
return $scope.trainingCreditsGroups[planId].splice($scope.trainingCreditsGroups[planId].indexOf(tc.id), 1);
}
, function (error) {
growl.error(_t('pricing.an_error_occurred_while_deleting_credit_with_the_TRAINING', { TRAINING: tc.creditable.name }));
console.error(error);
});
} else {
return growl.error(_t('pricing.an_error_occurred_unable_to_find_the_credit_to_revoke'));
}
}
});
// iterate through the new credits to add
return angular.forEach(newdata.training_ids, function (newTrainingId) {
if (original.indexOf(newTrainingId) === -1) {
return Credit.save({
credit: {
creditable_id: newTrainingId,
creditable_type: 'Training',
plan_id: planId
}
}
, function (newTc) { // success
$scope.trainingCredits.push(newTc);
return $scope.trainingCreditsGroups[newTc.plan_id].push(newTc.creditable_id);
}
, function (error) { // failed
const training = getTrainingFromId(newTrainingId);
growl.error(_t('pricing.an_error_occurred_while_creating_credit_with_the_TRAINING', { TRAINING: training.name }));
return console.error(error);
});
}
});
}
}
});
};
/**
* Cancel the current training credit modification
* @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
*/
$scope.cancelTrainingCredit = function (rowform) { rowform.$cancel(); };
/**
* Create a new empty entry in the $scope.machineCredits array
* @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
*/
$scope.addMachineCredit = function (e) {
e.preventDefault();
e.stopPropagation();
$scope.inserted =
{ creditable_type: 'Machine' };
$scope.machineCredits.push($scope.inserted);
return $scope.status.isopen = !$scope.status.isopen;
};
/**
* In the Credits tab, return the name of the machine/space associated with the given credit
* @param credit {Object} credit object, inherited from $resource
* @returns {String}
*/
$scope.showCreditableName = function (credit) {
let selected = _t('pricing.not_set');
if (credit && credit.creditable_id) {
const object = $scope.getCreditable(credit);
selected = object.name;
if (credit.creditable_type === 'Machine') {
selected += ` ( id. ${object.id} )`;
}
}
return selected;
};
/**
* In the Credits tab, return the machine/space associated with the given credit
* @param credit {Object} credit object, inherited from $resource
* @returns {Object}
*/
$scope.getCreditable = function (credit) {
let selected;
if (credit && credit.creditable_id) {
if (credit.creditable_type === 'Machine') {
angular.forEach($scope.machines, function (m) {
if (m.id === credit.creditable_id) {
return selected = m;
}
});
} else if (credit.creditable_type === 'Space') {
angular.forEach($scope.spaces, function (s) {
if (s.id === credit.creditable_id) {
return selected = s;
}
});
}
}
return selected;
};
/**
* Validation callback when editing machine's credits. Save the changes.
* This will prevent the creation of two credits associating the same machine and plan.
* @param data {Object} machine, associated plan and number of credit hours.
* @param [id] {number} credit id for edition, create a new credit object if not provided
*/
$scope.saveMachineCredit = function (data, id) {
for (let mc of Array.from($scope.machineCredits)) {
if ((mc.plan_id === data.plan_id) && (mc.creditable_id === data.creditable_id) && ((id === null) || (mc.id !== id))) {
growl.error(_t('pricing.error_a_credit_linking_this_machine_with_that_subscription_already_exists'));
if (!id) {
$scope.machineCredits.pop();
}
return false;
}
}
if (id != null) {
return Credit.update({ id }, { credit: data }, function () { growl.success(_t('pricing.changes_have_been_successfully_saved')); });
} else {
data.creditable_type = 'Machine';
return Credit.save(
{ credit: data }
, function (resp) {
$scope.machineCredits[$scope.machineCredits.length - 1].id = resp.id;
return growl.success(_t('pricing.credit_was_successfully_saved'));
}
, function (err) {
$scope.machineCredits.pop();
growl.error(_t('pricing.error_creating_credit'));
console.error(err);
});
}
};
/**
* Removes the newly inserted but not saved machine credit / Cancel the current machine credit modification
* @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
* @param index {number} credit index in the $scope.machineCredits array
*/
$scope.cancelMachineCredit = function (rowform, index) {
if ($scope.machineCredits[index].id != null) {
return rowform.$cancel();
} else {
return $scope.machineCredits.splice(index, 1);
}
};
/**
* Deletes the machine credit at the specified index
* @param index {number} machine credit index in the $scope.machineCredits array
*/
$scope.removeMachineCredit = function (index) {
Credit.delete($scope.machineCredits[index]);
$scope.machineCredits.splice(index, 1);
};
/**
* Create a new empty entry in the $scope.spaceCredits array
* @param e {Object} see https://docs.angularjs.org/guide/expression#-event-
*/
$scope.addSpaceCredit = function (e) {
e.preventDefault();
e.stopPropagation();
$scope.inserted =
{ creditable_type: 'Space' };
$scope.spaceCredits.push($scope.inserted);
$scope.status.isopen = !$scope.status.isopen;
};
/**
* Validation callback when editing space's credits. Save the changes.
* This will prevent the creation of two credits associated with the same space and plan.
* @param data {Object} space, associated plan and number of credit hours.
* @param [id] {number} credit id for edition, create a new credit object if not provided
*/
$scope.saveSpaceCredit = function (data, id) {
for (let sc of Array.from($scope.spaceCredits)) {
if ((sc.plan_id === data.plan_id) && (sc.creditable_id === data.creditable_id) && ((id === null) || (sc.id !== id))) {
growl.error(_t('pricing.error_a_credit_linking_this_space_with_that_subscription_already_exists'));
if (!id) {
$scope.spaceCredits.pop();
}
return false;
}
}
if (id != null) {
return Credit.update({ id }, { credit: data }, function () { growl.success(_t('pricing.changes_have_been_successfully_saved')); });
} else {
data.creditable_type = 'Space';
return Credit.save(
{ credit: data }
, function (resp) {
$scope.spaceCredits[$scope.spaceCredits.length - 1].id = resp.id;
return growl.success(_t('pricing.credit_was_successfully_saved'));
}
, function (err) {
$scope.spaceCredits.pop();
return growl.error(_t('pricing.error_creating_credit'));
});
}
};
/**
* Removes the newly inserted but not saved space credit / Cancel the current space credit modification
* @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
* @param index {number} credit index in the $scope.spaceCredits array
*/
$scope.cancelSpaceCredit = function (rowform, index) {
if ($scope.spaceCredits[index].id != null) {
return rowform.$cancel();
} else {
return $scope.spaceCredits.splice(index, 1);
}
};
/**
* Deletes the space credit at the specified index
* @param index {number} space credit index in the $scope.spaceCredits array
*/
$scope.removeSpaceCredit = function (index) {
Credit.delete($scope.spaceCredits[index]);
return $scope.spaceCredits.splice(index, 1);
};
/**
* If the plan does not have a type, return a default value for display purposes
* @param type {string|undefined|null} plan's type (eg. 'partner')
* @returns {string}
*/
$scope.getPlanType = function (type) {
if (type === 'PartnerPlan') {
return _t('pricing.partner');
} else { return _t('pricing.standard'); }
};
/**
* Change the plans ordering criterion to the one provided
* @param orderBy {string} ordering criterion
*/
$scope.setOrderPlans = function (orderBy) {
if ($scope.orderPlans === orderBy) {
return $scope.orderPlans = `-${orderBy}`;
} else {
return $scope.orderPlans = orderBy;
}
};
/**
* Retrieve a price from prices array by a machineId and a groupId
*/
$scope.findPriceBy = function (prices, machineId, groupId) {
for (let price of Array.from(prices)) {
if ((price.priceable_id === machineId) && (price.group_id === groupId)) {
return price;
}
}
};
/**
* update a price for a machine and a group, not considering any plan
*/
$scope.updatePrice = function (data, price) {
if (data != null) {
return Price.update({ id: price.id }, { price: { amount: data } }).$promise;
} else {
return _t('pricing.please_specify_a_number');
}
};
/**
* Delete the specified subcription plan
* @param id {number} plan id
*/
$scope.deletePlan = function (plans, id) {
if (typeof id !== 'number') {
return console.error('[EditPricingController::deletePlan] Error: invalid id parameter');
} else {
// open a confirmation dialog
return dialogs.confirm(
{
resolve: {
object () {
return {
title: _t('pricing.confirmation_required'),
msg: _t('pricing.do_you_really_want_to_delete_this_subscription_plan')
};
}
}
},
function () {
// the admin has confirmed, delete the plan
Plan.delete(
{ id },
function (res) {
growl.success(_t('pricing.subscription_plan_was_successfully_deleted'));
return $scope.plans.splice(findItemIdxById(plans, id), 1);
},
function (error) {
if (error.statusText) { console.error(`[EditPricingController::deletePlan] Error: ${error.statusText}`); }
growl.error(_t('pricing.unable_to_delete_the_specified_subscription_an_error_occurred'));
}
);
}
);
}
};
/**
* Generate a string identifying the given plan by literal humain-readable name
* @param plan {Object} Plan object, as recovered from GET /api/plan/:id
* @param groups {Array} List of Groups objects, as recovered from GET /api/groups
* @param short {boolean} If true, the generated name will contains the group slug, otherwise the group full name
* will be included.
* @returns {String}
*/
$scope.humanReadablePlanName = function (plan, groups, short) { return `${$filter('humanReadablePlanName')(plan, groups, short)}`; };
/**
* Delete a coupon from the server's database and, in case of success, from the list in memory
* @param coupons {Array<Object>} should be called with $scope.coupons
* @param id {number} ID of the coupon to delete
*/
$scope.deleteCoupon = function (coupons, id) {
if (typeof id !== 'number') {
return console.error('[EditPricingController::deleteCoupon] Error: invalid id parameter');
} else {
// open a confirmation dialog
return dialogs.confirm({
resolve: {
object () {
return {
title: _t('pricing.confirmation_required'),
msg: _t('pricing.do_you_really_want_to_delete_this_coupon')
};
}
}
}
, function () {
// the admin has confirmed, delete the coupon
Coupon.delete({ id }, function (res) {
growl.success(_t('coupon_was_successfully_deleted'));
return $scope.coupons.splice(findItemIdxById(coupons, id), 1);
}
, function (error) {
if (error.statusText) { console.error(`[EditPricingController::deleteCoupon] Error: ${error.statusText}`); }
if (error.status === 422) {
return growl.error(_t('pricing.unable_to_delete_the_specified_coupon_already_in_use'));
} else {
return growl.error(_t('pricing.unable_to_delete_the_specified_coupon_an_unexpected_error_occurred'));
}
});
});
}
};
/**
* Open a modal allowing to select an user and send him the details of the provided coupon
* @param coupon {Object} The coupon to send
*/
$scope.sendCouponToUser = function (coupon) {
$uibModal.open({
templateUrl: '<%= asset_path "admin/pricing/sendCoupon.html" %>',
resolve: {
coupon () { return coupon; }
},
size: 'md',
controller: ['$scope', '$uibModalInstance', 'Coupon', 'coupon', '_t', function ($scope, $uibModalInstance, Coupon, coupon, _t) {
// Member, receiver of the coupon
$scope.ctrl =
{ member: null };
// Details of the coupon to send
$scope.coupon = coupon;
// Callback to validate sending of the coupon
$scope.ok = function () {
Coupon.send({ coupon_code: coupon.code, user_id: $scope.ctrl.member.id }, function (res) {
growl.success(_t('pricing.coupon_successfully_sent_to_USER', { USER: $scope.ctrl.member.name }));
return $uibModalInstance.close({ user_id: $scope.ctrl.member.id });
}
, function (err) {
growl.error(_t('pricing.an_error_occurred_unable_to_send_the_coupon'));
console.error(err);
});
};
// Callback to close the modal and cancel the sending process
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
}]
});
};
/**
* Load the next 10 coupons
*/
$scope.loadMore = function() {
$scope.couponsPage++;
Coupon.query({ page: $scope.couponsPage, filter: $scope.filter.coupon }, function (data) {
$scope.coupons = $scope.coupons.concat(data);
});
};
/**
* Reset the list of coupons according to the newly selected filter
*/
$scope.updateCouponFilter = function() {
$scope.couponsPage = 1;
Coupon.query({ page: $scope.couponsPage, filter: $scope.filter.coupon }, function (data) {
$scope.coupons = data;
});
}
/* PRIVATE SCOPE */
/**
* Kind of constructor: these actions will be realized first when the controller is loaded
*/
const initialize = function () {
$scope.trainingCreditsGroups = groupCreditsByPlan($scope.trainingCredits);
// adds empty array for plan which hasn't any credits yet
return (function () {
const result = [];
for (let plan of Array.from($scope.plans)) {
if ($scope.trainingCreditsGroups[plan.id] == null) {
result.push($scope.trainingCreditsGroups[plan.id] = []);
} else {
result.push(undefined);
}
}
return result;
})();
};
/**
* Retrieve an item index by its ID from the given array of objects
* @param items {Array<{id:number}>}
* @param id {number}
* @returns {number} item index in the provided array
*/
var findItemIdxById = function (items, id) {
return (items.map(function (item) { return item.id; })).indexOf(id);
};
/**
* Group the given credits array into a map associating the plan ID with its associated trainings/machines
* @return {Object} the association map
*/
var groupCreditsByPlan = function (credits) {
const creditsMap = {};
angular.forEach(credits, function (c) {
if (!creditsMap[c.plan_id]) {
creditsMap[c.plan_id] = [];
}
return creditsMap[c.plan_id].push(c.creditable_id);
});
return creditsMap;
};
/**
* Iterate through $scope.traininfCredits to find the credit matching the given criterion
* @param trainingId {number|string} training ID
* @param planId {number|string} plan ID
*/
var findTrainingCredit = function (trainingId, planId) {
trainingId = parseInt(trainingId);
planId = parseInt(planId);
for (let credit of Array.from($scope.trainingCredits)) {
if ((credit.plan_id === planId) && (credit.creditable_id === trainingId)) {
return credit;
}
}
};
/**
* Retrieve a training from its given identifier and returns it
* @param id {number} training ID
* @returns {Object} Training inherited from $resource
*/
var getTrainingFromId = function (id) {
for (let training of Array.from($scope.trainings)) {
if (training.id === parseInt(id)) {
return training;
}
}
};
// !!! MUST BE CALLED AT THE END of the controller
return initialize();
}
]);