From d7ba83f6a0b787cd518a64d3fcc59c874a2cec18 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 22 Jun 2021 11:13:44 +0200 Subject: [PATCH] WIP: migrate machine pricing edition interface to react --- app/controllers/api/groups_controller.rb | 7 +- app/controllers/api/machines_controller.rb | 7 +- app/controllers/api/prices_controller.rb | 15 +- app/frontend/src/javascript/api/group.ts | 12 +- app/frontend/src/javascript/api/machine.ts | 12 +- .../src/javascript/api/prepaid-pack.ts | 6 +- app/frontend/src/javascript/api/price.ts | 20 ++- .../components/configure-packs-button.tsx | 65 --------- .../pricing/configure-packs-button.tsx | 30 ++++ .../components/pricing/editable-price.tsx | 55 ++++++++ .../components/pricing/machines-pricing.tsx | 130 ++++++++++++++++++ .../javascript/controllers/admin/pricing.js | 21 ++- app/frontend/src/javascript/models/group.ts | 5 + app/frontend/src/javascript/models/machine.ts | 5 + .../src/javascript/models/prepaid-pack.ts | 2 +- app/frontend/src/javascript/models/price.ts | 5 + app/frontend/src/javascript/router.js | 1 - .../admin/pricing/machine_hours.html | 31 +---- .../machines/request_training_modal.html | 18 --- .../machines/training_reservation_modal.html | 10 -- app/services/group_service.rb | 19 +++ app/services/machine_service.rb | 19 +++ app/services/price_service.rb | 18 +++ config/locales/app.admin.en.yml | 7 +- 24 files changed, 352 insertions(+), 168 deletions(-) delete mode 100644 app/frontend/src/javascript/components/configure-packs-button.tsx create mode 100644 app/frontend/src/javascript/components/pricing/configure-packs-button.tsx create mode 100644 app/frontend/src/javascript/components/pricing/editable-price.tsx create mode 100644 app/frontend/src/javascript/components/pricing/machines-pricing.tsx delete mode 100644 app/frontend/templates/machines/request_training_modal.html delete mode 100644 app/frontend/templates/machines/training_reservation_modal.html create mode 100644 app/services/group_service.rb create mode 100644 app/services/machine_service.rb create mode 100644 app/services/price_service.rb diff --git a/app/controllers/api/groups_controller.rb b/app/controllers/api/groups_controller.rb index 3c0b1037f..03b2d1c3d 100644 --- a/app/controllers/api/groups_controller.rb +++ b/app/controllers/api/groups_controller.rb @@ -6,12 +6,7 @@ class API::GroupsController < API::ApiController before_action :authenticate_user!, except: :index def index - @groups = if current_user&.admin? - Group.all - else - Group.where.not(slug: 'admins') - end - + @groups = GroupService.list(current_user, params) end def create diff --git a/app/controllers/api/machines_controller.rb b/app/controllers/api/machines_controller.rb index c2c50a58d..a6baebc10 100644 --- a/app/controllers/api/machines_controller.rb +++ b/app/controllers/api/machines_controller.rb @@ -7,12 +7,7 @@ class API::MachinesController < API::ApiController respond_to :json def index - sort_by = Setting.get('machines_sort_by') || 'default' - @machines = if sort_by == 'default' - Machine.includes(:machine_image, :plans) - else - Machine.includes(:machine_image, :plans).order(sort_by) - end + @machines = MachineService.list(params) end def show diff --git a/app/controllers/api/prices_controller.rb b/app/controllers/api/prices_controller.rb index eb0ace25d..b51e6f7b0 100644 --- a/app/controllers/api/prices_controller.rb +++ b/app/controllers/api/prices_controller.rb @@ -7,21 +7,8 @@ class API::PricesController < API::ApiController def index authorize Price - @prices = Price.all - if params[:priceable_type] - @prices = @prices.where(priceable_type: params[:priceable_type]) - @prices = @prices.where(priceable_id: params[:priceable_id]) if params[:priceable_id] - end - if params[:plan_id] - plan_id = if /no|nil|null|undefined/i.match?(params[:plan_id]) - nil - else - params[:plan_id] - end - @prices = @prices.where(plan_id: plan_id) - end - @prices = @prices.where(group_id: params[:group_id]) if params[:group_id] + @prices = PriceService.list(params) end def update diff --git a/app/frontend/src/javascript/api/group.ts b/app/frontend/src/javascript/api/group.ts index 8918e9799..27690418d 100644 --- a/app/frontend/src/javascript/api/group.ts +++ b/app/frontend/src/javascript/api/group.ts @@ -1,11 +1,17 @@ import apiClient from './clients/api-client'; import { AxiosResponse } from 'axios'; -import { Group } from '../models/group'; +import { Group, GroupIndexFilter } from '../models/group'; export default class GroupAPI { - static async index (): Promise> { - const res: AxiosResponse> = await apiClient.get('/api/groups'); + static async index (filters?: Array): Promise> { + const res: AxiosResponse> = await apiClient.get(`/api/groups${GroupAPI.filtersToQuery(filters)}`); return res?.data; } + + private static filtersToQuery(filters?: Array): string { + if (!filters) return ''; + + return '?' + filters.map(f => `${f.key}=${f.value}`).join('&'); + } } diff --git a/app/frontend/src/javascript/api/machine.ts b/app/frontend/src/javascript/api/machine.ts index 14d981a78..47f4f3057 100644 --- a/app/frontend/src/javascript/api/machine.ts +++ b/app/frontend/src/javascript/api/machine.ts @@ -1,10 +1,10 @@ import apiClient from './clients/api-client'; import { AxiosResponse } from 'axios'; -import { Machine } from '../models/machine'; +import { Machine, MachineIndexFilter } from '../models/machine'; export default class MachineAPI { - static async index (): Promise> { - const res: AxiosResponse> = await apiClient.get(`/api/machines`); + static async index (filters?: Array): Promise> { + const res: AxiosResponse> = await apiClient.get(`/api/machines${MachineAPI.filtersToQuery(filters)}`); return res?.data; } @@ -12,5 +12,11 @@ export default class MachineAPI { const res: AxiosResponse = await apiClient.get(`/api/machines/${id}`); return res?.data; } + + private static filtersToQuery(filters?: Array): string { + if (!filters) return ''; + + return '?' + filters.map(f => `${f.key}=${f.value}`).join('&'); + } } diff --git a/app/frontend/src/javascript/api/prepaid-pack.ts b/app/frontend/src/javascript/api/prepaid-pack.ts index dc14823f2..f48463f65 100644 --- a/app/frontend/src/javascript/api/prepaid-pack.ts +++ b/app/frontend/src/javascript/api/prepaid-pack.ts @@ -1,9 +1,9 @@ import apiClient from './clients/api-client'; import { AxiosResponse } from 'axios'; -import { IndexFilter, PrepaidPack } from '../models/prepaid-pack'; +import { PackIndexFilter, PrepaidPack } from '../models/prepaid-pack'; export default class PrepaidPackAPI { - static async index (filters?: Array): Promise> { + static async index (filters?: Array): Promise> { const res: AxiosResponse> = await apiClient.get(`/api/prepaid_packs${PrepaidPackAPI.filtersToQuery(filters)}`); return res?.data; } @@ -28,7 +28,7 @@ export default class PrepaidPackAPI { return res?.data; } - private static filtersToQuery(filters?: Array): string { + private static filtersToQuery(filters?: Array): string { if (!filters) return ''; return '?' + filters.map(f => `${f.key}=${f.value}`).join('&'); diff --git a/app/frontend/src/javascript/api/price.ts b/app/frontend/src/javascript/api/price.ts index dfd9ea901..ec031fb13 100644 --- a/app/frontend/src/javascript/api/price.ts +++ b/app/frontend/src/javascript/api/price.ts @@ -1,12 +1,28 @@ import apiClient from './clients/api-client'; import { AxiosResponse } from 'axios'; import { ShoppingCart } from '../models/payment'; -import { ComputePriceResult } from '../models/price'; +import { ComputePriceResult, Price, PriceIndexFilter } from '../models/price'; export default class PriceAPI { static async compute (cart: ShoppingCart): Promise { - const res: AxiosResponse = await apiClient.post(`/api/prices/compute`, cart); + const res: AxiosResponse = await apiClient.post(`/api/prices/compute`, cart); return res?.data; } + + static async index (filters?: Array): Promise> { + const res: AxiosResponse = await apiClient.get(`/api/prices${this.filtersToQuery(filters)}`); + return res?.data; + } + + static async update (price: Price): Promise { + const res: AxiosResponse = await apiClient.patch(`/api/price/${price.id}`, { price }); + return res?.data; + } + + private static filtersToQuery(filters?: Array): string { + if (!filters) return ''; + + return '?' + filters.map(f => `${f.key}=${f.value}`).join('&'); + } } diff --git a/app/frontend/src/javascript/components/configure-packs-button.tsx b/app/frontend/src/javascript/components/configure-packs-button.tsx deleted file mode 100644 index 7645f886c..000000000 --- a/app/frontend/src/javascript/components/configure-packs-button.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import PrepaidPackAPI from '../api/prepaid-pack'; -import { IndexFilter, PrepaidPack } from '../models/prepaid-pack'; -import { Loader } from './base/loader'; -import { react2angular } from 'react2angular'; -import { IApplication } from '../models/application'; - -declare var Application: IApplication; - -interface ConfigurePacksButtonParams { - groupId: number, - priceableId: number, - priceableType: string, - onError: (message: string) => void, -} - -/** - * This component is a button that shows the list of prepaid-packs when moving the mouse over it. - * When clicked, it opens a modal dialog to configure (add/delete/edit/remove) prepaid-packs. - */ -const ConfigurePacksButton: React.FC = ({ groupId, priceableId, priceableType, onError }) => { - const [packs, setPacks] = useState>(null); - const [showList, setShowList] = useState(false); - - useEffect(() => { - PrepaidPackAPI.index(buildFilters()) - .then(data => setPacks(data)) - .catch(error => onError(error)) - }, []) - - /** - * Build the filters for the current ConfigurePackButton, to query the API and get the concerned packs. - */ - const buildFilters = (): Array => { - const res = []; - if (groupId) res.push({ key: 'group_id', value: groupId }); - if (priceableId) res.push({ key: 'priceable_id', value: priceableId }); - if (priceableType) res.push({ key: 'priceable_type', value: priceableType }); - - return res; - } - - const toggleShowList = (): void => { - setShowList(!showList); - } - - return ( -
- {packs && showList &&
- {packs.map(p =>
{p.minutes / 60}h - {p.amount}
)} -
} -
- ); -} -const ConfigurePacksButtonWrapper: React.FC = ({ groupId, priceableId, priceableType, onError }) => { - return ( - - - - ); -} - -Application.Components.component('configurePacksButton', react2angular(ConfigurePacksButtonWrapper, ['groupId', 'priceableId', 'priceableType', 'onError'])); - - diff --git a/app/frontend/src/javascript/components/pricing/configure-packs-button.tsx b/app/frontend/src/javascript/components/pricing/configure-packs-button.tsx new file mode 100644 index 000000000..0ebc1ece0 --- /dev/null +++ b/app/frontend/src/javascript/components/pricing/configure-packs-button.tsx @@ -0,0 +1,30 @@ +import React, { useState } from 'react'; +import { PrepaidPack } from '../../models/prepaid-pack'; + +interface ConfigurePacksButtonProps { + packs: Array, + onError: (message: string) => void, +} + +/** + * This component is a button that shows the list of prepaid-packs when moving the mouse over it. + * When clicked, it opens a modal dialog to configure (add/delete/edit/remove) prepaid-packs. + */ +export const ConfigurePacksButton: React.FC = ({ packs, onError }) => { + const [showList, setShowList] = useState(false); + + const toggleShowList = (): void => { + setShowList(!showList); + } + const handleAddPack = (): void => { + //TODO, open a modal to add a new pack + } + + return ( +
+ {packs && showList &&
+ {packs.map(p =>
{p.minutes / 60}h - {p.amount}
)} +
} +
+ ); +} diff --git a/app/frontend/src/javascript/components/pricing/editable-price.tsx b/app/frontend/src/javascript/components/pricing/editable-price.tsx new file mode 100644 index 000000000..d506972cc --- /dev/null +++ b/app/frontend/src/javascript/components/pricing/editable-price.tsx @@ -0,0 +1,55 @@ +import React, { useState } from 'react'; +import { IFablab } from '../../models/fablab'; +import { FabInput } from '../base/fab-input'; +import { FabButton } from '../base/fab-button'; +import { Price } from '../../models/price'; + +declare var Fablab: IFablab; + +interface EditablePriceProps { + price: Price, + onSave: (price: Price) => void, +} + +/** + * Display the given price. + * When the user clics on the price, switch to the edition mode to allow him modifying the price. + */ +export const EditablePrice: React.FC = ({ price, onSave }) => { + const [edit, setEdit] = useState(false); + const [tempPrice, setTempPrice] = useState(price.amount); + + /** + * Return the formatted localized amount for the price (eg. 20.5 => "20,50 €") + */ + const formatPrice = (): string => { + return new Intl.NumberFormat(Fablab.intl_locale, { style: 'currency', currency: Fablab.intl_currency }).format(price.amount); + } + + /** + * Saves the new price + */ + const handleValidateEdit = (): void => { + const newPrice: Price = Object.assign({}, price); + newPrice.amount = tempPrice; + onSave(newPrice); + } + + /** + * Enable or disable the edit mode + */ + const toggleEdit= (): void => { + setEdit(!edit); + } + + return ( + + {!edit && {formatPrice()}} + {edit && + + } className="approve-button" onClick={handleValidateEdit} /> + } className="cancel-button" onClick={toggleEdit} /> + } + + ); +} diff --git a/app/frontend/src/javascript/components/pricing/machines-pricing.tsx b/app/frontend/src/javascript/components/pricing/machines-pricing.tsx new file mode 100644 index 000000000..17ad2b560 --- /dev/null +++ b/app/frontend/src/javascript/components/pricing/machines-pricing.tsx @@ -0,0 +1,130 @@ +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { react2angular } from 'react2angular'; +import { Loader } from '../base/loader'; +import { FabAlert } from '../base/fab-alert'; +import { HtmlTranslate } from '../base/html-translate'; +import MachineAPI from '../../api/machine'; +import GroupAPI from '../../api/group'; +import { IFablab } from '../../models/fablab'; +import { Machine } from '../../models/machine'; +import { Group } from '../../models/group'; +import { IApplication } from '../../models/application'; +import { EditablePrice } from './editable-price'; +import { ConfigurePacksButton } from './configure-packs-button'; +import PriceAPI from '../../api/price'; +import { Price } from '../../models/price'; +import PrepaidPackAPI from '../../api/prepaid-pack'; +import { PrepaidPack } from '../../models/prepaid-pack'; + +declare var Fablab: IFablab; +declare var Application: IApplication; + +interface MachinesPricingProps { + onError: (message: string) => void, + onSuccess: (message: string) => void, +} + +/** + * Interface to set and edit the prices of machines-hours, per group + */ +const MachinesPricing: React.FC = ({ onError, onSuccess }) => { + const { t } = useTranslation('admin'); + + const [machines, setMachines] = useState>(null); + const [groups, setGroups] = useState>(null); + const [prices, setPrices] = useState>(null); + const [packs, setPacks] = useState>(null); + + // retrieve the initial data + useEffect(() => { + MachineAPI.index([{ key: 'disabled', value: false }]) + .then(data => setMachines(data)) + .catch(error => onError(error)); + GroupAPI.index([{ key: 'disabled', value: false }]) + .then(data => setGroups(data)) + .catch(error => onError(error)); + PriceAPI.index([{ key: 'priceable_type', value: 'Machine'}, { key: 'plan_id', value: null }]) + .then(data => setPrices(data)) + .catch(error => onError(error)); + PrepaidPackAPI.index() + .then(data => setPacks(data)) + .catch(error => onError(error)) + }, []); + + // duration of the example slot + const EXEMPLE_DURATION = 20; + + /** + * Return the exemple price, formatted + */ + const examplePrice = (type: 'hourly_rate' | 'final_price'): string => { + const hourlyRate = 10; + + if (type === 'hourly_rate') { + return new Intl.NumberFormat(Fablab.intl_locale, { style: 'currency', currency: Fablab.intl_currency }).format(hourlyRate); + } + + const price = (hourlyRate / 60) * EXEMPLE_DURATION; + return new Intl.NumberFormat(Fablab.intl_locale, { style: 'currency', currency: Fablab.intl_currency }).format(price); + }; + + /** + * Find the price matching the given criterion + */ + const findPriceBy = (machineId, groupId): Price => { + for (const price of prices) { + if ((price.priceable_id === machineId) && (price.group_id === groupId)) { + return price; + } + } + }; + + /** + * Callback triggered when the user has confirmed to update a price + */ + const handleUpdatePrice = (price: Price): void => { + PriceAPI.update(price) + .then(() => onSuccess(t('app.admin.machines_pricing.price_updated'))) + .catch(error => onError(error)) + } + + return ( +
+ +

+

+

{t('app.admin.machines_pricing.you_can_override')}

+
+ + + + + {groups?.map(group => )} + + + + {machines?.map(machine => + + {groups?.map(group => )} + )} + +
{t('app.admin.machines_pricing.machines')}{group.name}
{machine.name} + {prices && } + {packs && } +
+
+ ); +} + +const MachinesPricingWrapper: React.FC = ({ onError, onSuccess }) => { + return ( + + + + ); +} + +Application.Components.component('machinesPricing', react2angular(MachinesPricingWrapper, ['onError', 'onSuccess'])); + + diff --git a/app/frontend/src/javascript/controllers/admin/pricing.js b/app/frontend/src/javascript/controllers/admin/pricing.js index 3eaf3eed6..bf4baf11d 100644 --- a/app/frontend/src/javascript/controllers/admin/pricing.js +++ b/app/frontend/src/javascript/controllers/admin/pricing.js @@ -18,13 +18,10 @@ /** * 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', 'settingsPromise', '_t', 'Member', 'uiTourService', 'planCategories', - 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, settingsPromise, _t, Member, uiTourService, planCategories) { +Application.Controllers.controller('EditPricingController', ['$scope', '$state', '$uibModal', '$filter', 'TrainingsPricing', 'Credit', 'Pricing', 'Plan', 'Coupon', 'plans', 'groups', 'growl', 'Price', 'dialogs', 'trainingsPricingsPromise', 'trainingsPromise', 'machineCreditsPromise', 'machinesPromise', 'trainingCreditsPromise', 'couponsPromise', 'spacesPromise', 'spacesPricesPromise', 'spacesCreditsPromise', 'settingsPromise', '_t', 'Member', 'uiTourService', 'planCategories', + function ($scope, $state, $uibModal, $filter, TrainingsPricing, Credit, Pricing, Plan, Coupon, plans, groups, growl, Price, dialogs, trainingsPricingsPromise, trainingsPromise, machineCreditsPromise, machinesPromise, trainingCreditsPromise, couponsPromise, spacesPromise, spacesPricesPromise, spacesCreditsPromise, settingsPromise, _t, Member, uiTourService, planCategories) { /* PUBLIC SCOPE */ - // List of machines prices (not considering any plan) - $scope.machinesPrices = machinesPricesPromise; - // List of trainings pricing $scope.trainingsPricings = trainingsPricingsPromise; @@ -640,6 +637,20 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state', return $filter('currency')(price); }; + /** + * Callback triggered by react components + */ + $scope.onSuccess = function (message) { + growl.success(message); + }; + + /** + * Callback triggered by react components + */ + $scope.onError = function (message) { + growl.error(message); + }; + /** * Setup the feature-tour for the admin/pricing page. * This is intended as a contextual help (when pressing F1) diff --git a/app/frontend/src/javascript/models/group.ts b/app/frontend/src/javascript/models/group.ts index e1d00af4e..0c658e943 100644 --- a/app/frontend/src/javascript/models/group.ts +++ b/app/frontend/src/javascript/models/group.ts @@ -1,3 +1,8 @@ +export interface GroupIndexFilter { + key: 'disabled', + value: boolean, +} + export interface Group { id: number, slug: string, diff --git a/app/frontend/src/javascript/models/machine.ts b/app/frontend/src/javascript/models/machine.ts index 18d13aad9..f0371d557 100644 --- a/app/frontend/src/javascript/models/machine.ts +++ b/app/frontend/src/javascript/models/machine.ts @@ -1,5 +1,10 @@ import { Reservation } from './reservation'; +export interface MachineIndexFilter { + key: 'disabled', + value: boolean, +} + export interface Machine { id: number, name: string, diff --git a/app/frontend/src/javascript/models/prepaid-pack.ts b/app/frontend/src/javascript/models/prepaid-pack.ts index 62b09fac8..ac834d239 100644 --- a/app/frontend/src/javascript/models/prepaid-pack.ts +++ b/app/frontend/src/javascript/models/prepaid-pack.ts @@ -1,5 +1,5 @@ -export interface IndexFilter { +export interface PackIndexFilter { key: 'group_id' | 'priceable_id' | 'priceable_type', value: number|string, } diff --git a/app/frontend/src/javascript/models/price.ts b/app/frontend/src/javascript/models/price.ts index f48b8271d..a7b1abbd2 100644 --- a/app/frontend/src/javascript/models/price.ts +++ b/app/frontend/src/javascript/models/price.ts @@ -1,3 +1,8 @@ +export interface PriceIndexFilter { + key: 'priceable_type' | 'priceable_id' | 'group_id' | 'plan_id', + value?: number|string, +} + export interface Price { id: number, group_id: number, diff --git a/app/frontend/src/javascript/router.js b/app/frontend/src/javascript/router.js index bbb3db2e3..2cccf050e 100644 --- a/app/frontend/src/javascript/router.js +++ b/app/frontend/src/javascript/router.js @@ -771,7 +771,6 @@ angular.module('application.router', ['ui.router']) resolve: { plans: ['Plan', function (Plan) { return Plan.query().$promise; }], groups: ['Group', function (Group) { return Group.query().$promise; }], - machinesPricesPromise: ['Price', function (Price) { return Price.query({ priceable_type: 'Machine', plan_id: 'null' }).$promise; }], trainingsPricingsPromise: ['TrainingsPricing', function (TrainingsPricing) { return TrainingsPricing.query().$promise; }], trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }], machineCreditsPromise: ['Credit', function (Credit) { return Credit.query({ creditable_type: 'Machine' }).$promise; }], diff --git a/app/frontend/templates/admin/pricing/machine_hours.html b/app/frontend/templates/admin/pricing/machine_hours.html index d3187be11..4a9b6845d 100644 --- a/app/frontend/templates/admin/pricing/machine_hours.html +++ b/app/frontend/templates/admin/pricing/machine_hours.html @@ -1,30 +1 @@ -
-

-

-

{{ 'app.admin.pricing.you_can_override' }}

-
- - - - - - - - - - - - - -
{{ 'app.admin.pricing.machines' }} - {{group.name}} -
- {{ machine.name }} - - - {{ findPriceBy(machinesPrices, machine.id, group.id).amount | currency}} - - -
+ diff --git a/app/frontend/templates/machines/request_training_modal.html b/app/frontend/templates/machines/request_training_modal.html deleted file mode 100644 index 48a9b4812..000000000 --- a/app/frontend/templates/machines/request_training_modal.html +++ /dev/null @@ -1,18 +0,0 @@ - - - diff --git a/app/frontend/templates/machines/training_reservation_modal.html b/app/frontend/templates/machines/training_reservation_modal.html deleted file mode 100644 index 8a0be4e58..000000000 --- a/app/frontend/templates/machines/training_reservation_modal.html +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/services/group_service.rb b/app/services/group_service.rb new file mode 100644 index 000000000..94c642579 --- /dev/null +++ b/app/services/group_service.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Provides methods for Groups +class GroupService + def self.list(operator, filters = {}) + groups = if operator&.admin? + Group.where(nil) + else + Group.where.not(slug: 'admins') + end + + if filters[:disabled].present? + state = filters[:disabled] == 'false' ? [nil, false] : true + groups = groups.where(disabled: state) + end + + groups + end +end diff --git a/app/services/machine_service.rb b/app/services/machine_service.rb new file mode 100644 index 000000000..02f18c4f1 --- /dev/null +++ b/app/services/machine_service.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# Provides methods for Machines +class MachineService + def self.list(filters) + sort_by = Setting.get('machines_sort_by') || 'default' + machines = if sort_by == 'default' + Machine.includes(:machine_image, :plans) + else + Machine.includes(:machine_image, :plans).order(sort_by) + end + if filters[:disabled].present? + state = filters[:disabled] == 'false' ? [nil, false] : true + machines = machines.where(disabled: state) + end + + machines + end +end diff --git a/app/services/price_service.rb b/app/services/price_service.rb new file mode 100644 index 000000000..9fbd37ace --- /dev/null +++ b/app/services/price_service.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# Provides methods for Prices +class PriceService + def self.list(filters) + prices = Price.where(nil) + + prices = prices.where(priceable_type: filters[:priceable_type]) if filters[:priceable_type].present? + prices = prices.where(priceable_id: filters[:priceable_id]) if filters[:priceable_id].present? + prices = prices.where(group_id: filters[:group_id]) if filters[:group_id].present? + if filters[:plan_id].present? + plan_id = /no|nil|null|undefined/i.match?(filters[:plan_id]) ? nil : filters[:plan_id] + prices = prices.where(plan_id: plan_id) + end + + prices + end +end diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 9e8216fe0..f3b2700d8 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -307,7 +307,6 @@ en: prominence: "Prominence" price: "Price" machine_hours: "Machine slots" - these_prices_match_machine_hours_rates_html: "The prices below match one hour of machine usage, without subscription." prices_calculated_on_hourly_rate_html: "All the prices will be automatically calculated based on the hourly rate defined here.
For example, if you define an hourly rate at {RATE}: a slot of {DURATION} minutes (default), will be charged {PRICE}." you_can_override: "You can override this duration for each availability you create in the agenda. The price will then be adjusted accordingly." machines: "Machines" @@ -369,6 +368,12 @@ en: status_enabled: "Enabled" status_disabled: "Disabled" status_all: "All" + machines_pricing: + prices_match_machine_hours_rates_html: "The prices below match one hour of machine usage, without subscription." + prices_calculated_on_hourly_rate_html: "All the prices will be automatically calculated based on the hourly rate defined here.
For example, if you define an hourly rate at {RATE}: a slot of {DURATION} minutes (default), will be charged {PRICE}." + you_can_override: "You can override this duration for each availability you create in the agenda. The price will then be adjusted accordingly." + machines: "Machines" + price_updated: "Price successfully updated" #ajouter un code promotionnel coupons_new: add_a_coupon: "Add a coupon"