1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-12-01 12:24:28 +01:00

Merge branch 'dev' for release 5.2.0

This commit is contained in:
Du Peng 2021-12-23 10:15:58 +01:00
commit 46259fce8e
60 changed files with 1225 additions and 537 deletions

View File

@ -1,5 +1,12 @@
# Changelog Fab-manager
## v5.2.0 2021 December 23
- Ability to configure prices for spaces by time slots different than the default hourly rate
- Updated portuguese translation
- Refactored the ReserveButton component to use the same user's data across all the component
- First optimization the load time of the payment schedules list
## v5.1.13 2021 November 16
- Fix a bug: unable to run the setup/upgrade scripts as root

View File

@ -1,4 +1,4 @@
#web: bundle exec rails server puma -p $PORT
web: bundle exec rails server puma -p $PORT
worker: bundle exec sidekiq -C ./config/sidekiq.yml
wp-client: bin/webpack-dev-server
wp-server: SERVER_BUNDLE_ONLY=yes bin/webpack --watch

View File

@ -4,6 +4,20 @@
# Prices are used in reservations (Machine, Space)
class API::PricesController < API::ApiController
before_action :authenticate_user!
before_action :set_price, only: %i[update destroy]
def create
@price = Price.new(price_create_params)
@price.amount *= 100
authorize @price
if @price.save
render json: @price, status: :created
else
render json: @price.errors, status: :unprocessable_entity
end
end
def index
@prices = PriceService.list(params)
@ -11,7 +25,6 @@ class API::PricesController < API::ApiController
def update
authorize Price
@price = Price.find(params[:id])
price_parameters = price_params
price_parameters[:amount] = price_parameters[:amount] * 100
if @price.update(price_parameters)
@ -21,6 +34,12 @@ class API::PricesController < API::ApiController
end
end
def destroy
authorize @price
@price.destroy
head :no_content
end
def compute
cs = CartService.new(current_user)
cart = cs.from_hash(params)
@ -29,7 +48,15 @@ class API::PricesController < API::ApiController
private
def set_price
@price = Price.find(params[:id])
end
def price_create_params
params.require(:price).permit(:amount, :duration, :group_id, :plan_id, :priceable_id, :priceable_type)
end
def price_params
params.require(:price).permit(:amount)
params.require(:price).permit(:amount, :duration)
end
end

View File

@ -14,11 +14,21 @@ export default class PriceAPI {
return res?.data;
}
static async create (price: Price): Promise<Price> {
const res: AxiosResponse<Price> = await apiClient.post('/api/prices', { price });
return res?.data;
}
static async update (price: Price): Promise<Price> {
const res: AxiosResponse<Price> = await apiClient.patch(`/api/prices/${price.id}`, { price });
return res?.data;
}
static async destroy (priceId: number): Promise<void> {
const res: AxiosResponse<void> = await apiClient.delete(`/api/prices/${priceId}`);
return res?.data;
}
private static filtersToQuery (filters?: PriceIndexFilter): string {
if (!filters) return '';

View File

@ -0,0 +1,15 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { Space } from '../models/space';
export default class SpaceAPI {
static async index (): Promise<Array<any>> {
const res: AxiosResponse<Array<Space>> = await apiClient.get('/api/spaces');
return res?.data;
}
static async get (id: number): Promise<Space> {
const res: AxiosResponse<Space> = await apiClient.get(`/api/spaces/${id}`);
return res?.data;
}
}

View File

@ -146,7 +146,7 @@ const ReserveButtonComponent: React.FC<ReserveButtonProps> = ({ currentUser, mac
// if the customer has already bought a pack or if there's no active packs for this machine,
// or customer has not any subscription if admin active pack only for subscription option
// let the customer reserve
if (machine.current_user_has_packs || !machine.has_prepaid_packs_for_current_user || (isPackOnlyForSubscription && !currentUser.subscribed_plan)) {
if (machine.current_user_has_packs || !machine.has_prepaid_packs_for_current_user || (isPackOnlyForSubscription && !user.subscribed_plan)) {
return onReserveMachine(machine);
}
@ -168,14 +168,14 @@ const ReserveButtonComponent: React.FC<ReserveButtonProps> = ({ currentUser, mac
user={user}
machine={machine}
onEnrollRequested={onEnrollRequested} />
{machine && currentUser && <ProposePacksModal isOpen={proposePacks}
{machine && user && <ProposePacksModal isOpen={proposePacks}
toggleModal={toggleProposePacksModal}
item={machine}
itemType="Machine"
onError={onError}
customer={currentUser}
customer={user}
onDecline={onReserveMachine}
operator={currentUser}
operator={user}
onSuccess={handlePackBought} />}
</span>

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { react2angular } from 'react2angular';
import { DocumentFilters } from '../document-filters';
@ -38,13 +38,6 @@ const PaymentSchedulesList: React.FC<PaymentSchedulesListProps> = ({ currentUser
// current filter, by date, for the schedules and the deadlines
const [dateFilter, setDateFilter] = useState<Date>(null);
/**
* When the component is loaded first, refresh the list of schedules to fill the first page.
*/
useEffect(() => {
handleRefreshList();
}, []);
/**
* Fetch from the API the payments schedules matching the given filters and reset the results table with the new schedules.
*/

View File

@ -1,12 +1,12 @@
import React, { ReactNode, useState } from 'react';
import { PrepaidPack } from '../../models/prepaid-pack';
import { PrepaidPack } from '../../../models/prepaid-pack';
import { useTranslation } from 'react-i18next';
import { FabPopover } from '../base/fab-popover';
import { FabPopover } from '../../base/fab-popover';
import { CreatePack } from './create-pack';
import PrepaidPackAPI from '../../api/prepaid-pack';
import PrepaidPackAPI from '../../../api/prepaid-pack';
import { DeletePack } from './delete-pack';
import { EditPack } from './edit-pack';
import FormatLib from '../../lib/format';
import FormatLib from '../../../lib/format';
interface ConfigurePacksButtonProps {
packsData: Array<PrepaidPack>,
@ -64,8 +64,8 @@ export const ConfigurePacksButton: React.FC<ConfigurePacksButtonProps> = ({ pack
};
return (
<div className="configure-packs-button">
<button className="packs-button" onClick={toggleShowList}>
<div className="configure-group">
<button className="configure-group-button" onClick={toggleShowList}>
<i className="fas fa-box" />
</button>
{showList && <FabPopover title={t('app.admin.configure_packs_button.packs')} headerButton={renderAddButton()} className="fab-popover__right">
@ -73,7 +73,7 @@ export const ConfigurePacksButton: React.FC<ConfigurePacksButtonProps> = ({ pack
{packs?.map(p =>
<li key={p.id} className={p.disabled ? 'disabled' : ''}>
{formatDuration(p.minutes)} - {FormatLib.price(p.amount)}
<span className="pack-actions">
<span className="group-actions">
<EditPack onSuccess={handleSuccess} onError={onError} pack={p} />
<DeletePack onSuccess={handleSuccess} onError={onError} pack={p} />
</span>

View File

@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { FabModal } from '../base/fab-modal';
import { FabModal } from '../../base/fab-modal';
import { PackForm } from './pack-form';
import { PrepaidPack } from '../../models/prepaid-pack';
import PrepaidPackAPI from '../../api/prepaid-pack';
import { PrepaidPack } from '../../../models/prepaid-pack';
import PrepaidPackAPI from '../../../api/prepaid-pack';
import { useTranslation } from 'react-i18next';
import { FabAlert } from '../base/fab-alert';
import { FabAlert } from '../../base/fab-alert';
interface CreatePackProps {
onSuccess: (message: string) => void,

View File

@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FabButton } from '../base/fab-button';
import { FabModal } from '../base/fab-modal';
import { Loader } from '../base/loader';
import { PrepaidPack } from '../../models/prepaid-pack';
import PrepaidPackAPI from '../../api/prepaid-pack';
import { FabButton } from '../../base/fab-button';
import { FabModal } from '../../base/fab-modal';
import { Loader } from '../../base/loader';
import { PrepaidPack } from '../../../models/prepaid-pack';
import PrepaidPackAPI from '../../../api/prepaid-pack';
interface DeletePackProps {
onSuccess: (message: string) => void,
@ -42,8 +42,8 @@ const DeletePackComponent: React.FC<DeletePackProps> = ({ onSuccess, onError, pa
};
return (
<div className="delete-pack">
<FabButton type='button' className="remove-pack-button" icon={<i className="fa fa-trash" />} onClick={toggleDeletionModal} />
<div className="delete-group">
<FabButton type='button' className="delete-group-button" icon={<i className="fa fa-trash" />} onClick={toggleDeletionModal} />
<FabModal title={t('app.admin.delete_pack.delete_pack')}
isOpen={deletionModal}
toggleModal={toggleDeletionModal}

View File

@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { FabModal } from '../base/fab-modal';
import { FabModal } from '../../base/fab-modal';
import { PackForm } from './pack-form';
import { PrepaidPack } from '../../models/prepaid-pack';
import PrepaidPackAPI from '../../api/prepaid-pack';
import { PrepaidPack } from '../../../models/prepaid-pack';
import PrepaidPackAPI from '../../../api/prepaid-pack';
import { useTranslation } from 'react-i18next';
import { FabButton } from '../base/fab-button';
import { FabButton } from '../../base/fab-button';
interface EditPackProps {
pack: PrepaidPack,
@ -54,16 +54,15 @@ export const EditPack: React.FC<EditPackProps> = ({ pack, onSuccess, onError })
};
return (
<div className="edit-pack">
<FabButton type='button' className="edit-pack-button" icon={<i className="fas fa-edit" />} onClick={handleRequestEdit} />
<div className="edit-group">
<FabButton type='button' icon={<i className="fas fa-edit" />} onClick={handleRequestEdit} />
<FabModal isOpen={isOpen}
toggleModal={toggleModal}
title={t('app.admin.edit_pack.edit_pack')}
className="edit-pack-modal"
closeButton
confirmButton={t('app.admin.edit_pack.confirm_changes')}
onConfirmSendFormId="edit-pack">
{packData && <PackForm formId="edit-pack" onSubmit={handleUpdate} pack={packData} />}
onConfirmSendFormId="edit-group">
{packData && <PackForm formId="edit-group" onSubmit={handleUpdate} pack={packData} />}
</FabModal>
</div>
);

View File

@ -1,25 +1,23 @@
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 { 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 { 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';
import PriceAPI from '../../../api/price';
import { Price } from '../../../models/price';
import PrepaidPackAPI from '../../../api/prepaid-pack';
import { PrepaidPack } from '../../../models/prepaid-pack';
import { useImmer } from 'use-immer';
import FormatLib from '../../lib/format';
import FormatLib from '../../../lib/format';
declare let Fablab: IFablab;
declare const Application: IApplication;
interface MachinesPricingProps {
@ -109,7 +107,7 @@ const MachinesPricing: React.FC<MachinesPricingProps> = ({ onError, onSuccess })
};
return (
<div className="machines-pricing">
<div className="pricing-list">
<FabAlert level="warning">
<p><HtmlTranslate trKey="app.admin.machines_pricing.prices_match_machine_hours_rates_html"/></p>
<p><HtmlTranslate trKey="app.admin.machines_pricing.prices_calculated_on_hourly_rate_html" options={{ DURATION: `${EXEMPLE_DURATION}`, RATE: examplePrice('hourly_rate'), PRICE: examplePrice('final_price') }} /></p>

View File

@ -1,11 +1,11 @@
import React, { BaseSyntheticEvent } from 'react';
import Select from 'react-select';
import Switch from 'react-switch';
import { PrepaidPack } from '../../models/prepaid-pack';
import { PrepaidPack } from '../../../models/prepaid-pack';
import { useTranslation } from 'react-i18next';
import { useImmer } from 'use-immer';
import { FabInput } from '../base/fab-input';
import { IFablab } from '../../models/fablab';
import { FabInput } from '../../base/fab-input';
import { IFablab } from '../../../models/fablab';
declare let Fablab: IFablab;
@ -103,7 +103,7 @@ export const PackForm: React.FC<PackFormProps> = ({ formId, onSubmit, pack }) =>
};
return (
<form id={formId} onSubmit={handleSubmit} className="pack-form">
<form id={formId} onSubmit={handleSubmit} className="group-form">
<label htmlFor="hours">{t('app.admin.pack_form.hours')} *</label>
<FabInput id="hours"
type="number"

View File

@ -0,0 +1,79 @@
import React, { ReactNode, useState } from 'react';
import { Price } from '../../../models/price';
import { useTranslation } from 'react-i18next';
import { FabPopover } from '../../base/fab-popover';
import { CreateExtendedPrice } from './create-extended-price';
import PriceAPI from '../../../api/price';
import FormatLib from '../../../lib/format';
import { EditExtendedPrice } from './edit-extended-price';
import { DeleteExtendedPrice } from './delete-extended-price';
interface ConfigureExtendedPriceButtonProps {
prices: Array<Price>,
onError: (message: string) => void,
onSuccess: (message: string) => void,
groupId: number,
priceableId: number,
priceableType: string,
}
/**
* This component is a button that shows the list of extendedPrices.
* It also triggers modal dialogs to configure (add/edit/remove) extendedPrices.
*/
export const ConfigureExtendedPriceButton: React.FC<ConfigureExtendedPriceButtonProps> = ({ prices, onError, onSuccess, groupId, priceableId, priceableType }) => {
const { t } = useTranslation('admin');
const [extendedPrices, setExtendedPrices] = useState<Array<Price>>(prices);
const [showList, setShowList] = useState<boolean>(false);
/**
* Open/closes the popover listing the existing extended prices
*/
const toggleShowList = (): void => {
setShowList(!showList);
};
/**
* Callback triggered when the extendedPrice was successfully created/deleted/updated.
* We refresh the list of extendedPrices.
*/
const handleSuccess = (message: string) => {
onSuccess(message);
PriceAPI.index({ group_id: groupId, priceable_id: priceableId, priceable_type: priceableType })
.then(data => setExtendedPrices(data.filter(p => p.duration !== 60)))
.catch(error => onError(error));
};
/**
* Render the button used to trigger the "new extended price" modal
*/
const renderAddButton = (): ReactNode => {
return <CreateExtendedPrice onSuccess={handleSuccess}
onError={onError}
groupId={groupId}
priceableId={priceableId}
priceableType={priceableType} />;
};
return (
<div className="configure-group">
<button className="configure-group-button" onClick={toggleShowList}>
<i className="fas fa-stopwatch" />
</button>
{showList && <FabPopover title={t('app.admin.configure_extendedPrices_button.extendedPrices')} headerButton={renderAddButton()} className="fab-popover__right">
<ul>
{extendedPrices?.map(extendedPrice =>
<li key={extendedPrice.id}>
{extendedPrice.duration} {t('app.admin.calendar.minutes')} - {FormatLib.price(extendedPrice.amount)}
<span className="group-actions">
<EditExtendedPrice onSuccess={handleSuccess} onError={onError} price={extendedPrice} />
<DeleteExtendedPrice onSuccess={handleSuccess} onError={onError} price={extendedPrice} />
</span>
</li>)}
</ul>
{extendedPrices?.length === 0 && <span>{t('app.admin.configure_extendedPrices_button.no_extendedPrices')}</span>}
</FabPopover>}
</div>
);
};

View File

@ -0,0 +1,69 @@
import React, { useState } from 'react';
import { FabModal } from '../../base/fab-modal';
import { ExtendedPriceForm } from './extended-price-form';
import { Price } from '../../../models/price';
import PriceAPI from '../../../api/price';
import { useTranslation } from 'react-i18next';
import { FabAlert } from '../../base/fab-alert';
interface CreateExtendedPriceProps {
onSuccess: (message: string) => void,
onError: (message: string) => void,
groupId: number,
priceableId: number,
priceableType: string,
}
/**
* This component shows a button.
* When clicked, we show a modal dialog handing the process of creating a new extended price
*/
export const CreateExtendedPrice: React.FC<CreateExtendedPriceProps> = ({ onSuccess, onError, groupId, priceableId, priceableType }) => {
const { t } = useTranslation('admin');
const [isOpen, setIsOpen] = useState<boolean>(false);
/**
* Open/closes the "new extended price" modal dialog
*/
const toggleModal = (): void => {
setIsOpen(!isOpen);
};
/**
* Callback triggered when the user has validated the creation of the new extended price
*/
const handleSubmit = (extendedPrice: Price): void => {
// set the already-known attributes of the new extended price
const newExtendedPrice = Object.assign<Price, Price>({} as Price, extendedPrice);
newExtendedPrice.group_id = groupId;
newExtendedPrice.priceable_id = priceableId;
newExtendedPrice.priceable_type = priceableType;
// create it on the API
PriceAPI.create(newExtendedPrice)
.then(() => {
onSuccess(t('app.admin.create_extendedPrice.extendedPrice_successfully_created'));
toggleModal();
})
.catch(error => onError(error));
};
return (
<div className="create-pack">
<button className="add-pack-button" onClick={toggleModal}><i className="fas fa-plus"/></button>
<FabModal isOpen={isOpen}
toggleModal={toggleModal}
title={t('app.admin.create_extendedPrice.new_extendedPrice')}
className="new-pack-modal"
closeButton
confirmButton={t('app.admin.create_extendedPrice.create_extendedPrice')}
onConfirmSendFormId="new-extended-price">
<FabAlert level="info">
{t('app.admin.create_extendedPrice.new_extendedPrice_info', { TYPE: priceableType })}
</FabAlert>
<ExtendedPriceForm formId="new-extended-price" onSubmit={handleSubmit} />
</FabModal>
</div>
);
};

View File

@ -0,0 +1,56 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { FabButton } from '../../base/fab-button';
import { FabModal } from '../../base/fab-modal';
import { Price } from '../../../models/price';
import PriceAPI from '../../../api/price';
interface DeleteExtendedPriceProps {
onSuccess: (message: string) => void,
onError: (message: string) => void,
price: Price,
}
/**
* This component shows a button.
* When clicked, we show a modal dialog to ask the user for confirmation about the deletion of the provided extended price.
*/
export const DeleteExtendedPrice: React.FC<DeleteExtendedPriceProps> = ({ onSuccess, onError, price }) => {
const { t } = useTranslation('admin');
const [deletionModal, setDeletionModal] = useState<boolean>(false);
/**
* Opens/closes the deletion modal
*/
const toggleDeletionModal = (): void => {
setDeletionModal(!deletionModal);
};
/**
* The deletion has been confirmed by the user.
* Call the API to trigger the deletion of the temporary set extended price
*/
const onDeleteConfirmed = (): void => {
PriceAPI.destroy(price.id).then(() => {
onSuccess(t('app.admin.delete_extendedPrice.extendedPrice_deleted'));
}).catch((error) => {
onError(t('app.admin.delete_extendedPrice.unable_to_delete') + error);
});
toggleDeletionModal();
};
return (
<div className="delete-group">
<FabButton type='button' className="delete-group-button" icon={<i className="fa fa-trash" />} onClick={toggleDeletionModal} />
<FabModal title={t('app.admin.delete_extendedPrice.delete_extendedPrice')}
isOpen={deletionModal}
toggleModal={toggleDeletionModal}
closeButton={true}
confirmButton={t('app.admin.delete_extendedPrice.confirm_delete')}
onConfirm={onDeleteConfirmed}>
<span>{t('app.admin.delete_extendedPrice.delete_confirmation')}</span>
</FabModal>
</div>
);
};

View File

@ -0,0 +1,65 @@
import React, { useState } from 'react';
import { FabModal } from '../../base/fab-modal';
import { ExtendedPriceForm } from './extended-price-form';
import { Price } from '../../../models/price';
import PriceAPI from '../../../api/price';
import { useTranslation } from 'react-i18next';
import { FabButton } from '../../base/fab-button';
interface EditExtendedPriceProps {
price: Price,
onSuccess: (message: string) => void,
onError: (message: string) => void
}
/**
* This component shows a button.
* When clicked, we show a modal dialog handing the process of creating a new extended price
*/
export const EditExtendedPrice: React.FC<EditExtendedPriceProps> = ({ price, onSuccess, onError }) => {
const { t } = useTranslation('admin');
const [isOpen, setIsOpen] = useState<boolean>(false);
const [extendedPriceData, setExtendedPriceData] = useState<Price>(price);
/**
* Open/closes the "edit extended price" modal dialog
*/
const toggleModal = (): void => {
setIsOpen(!isOpen);
};
/**
* When the user clicks on the edition button open te edition modal
*/
const handleRequestEdit = (): void => {
toggleModal();
};
/**
* Callback triggered when the user has validated the changes of the extended price
*/
const handleUpdate = (price: Price): void => {
PriceAPI.update(price)
.then(() => {
onSuccess(t('app.admin.edit_extendedPrice.extendedPrice_successfully_updated'));
setExtendedPriceData(price);
toggleModal();
})
.catch(error => onError(error));
};
return (
<div className="edit-group">
<FabButton type='button' icon={<i className="fas fa-edit" />} onClick={handleRequestEdit} />
<FabModal isOpen={isOpen}
toggleModal={toggleModal}
title={t('app.admin.edit_extendedPrice.edit_extendedPrice')}
closeButton
confirmButton={t('app.admin.edit_extendedPrice.confirm_changes')}
onConfirmSendFormId="edit-group">
{extendedPriceData && <ExtendedPriceForm formId="edit-group" onSubmit={handleUpdate} price={extendedPriceData} />}
</FabModal>
</div>
);
};

View File

@ -0,0 +1,74 @@
import React, { BaseSyntheticEvent } from 'react';
import { Price } from '../../../models/price';
import { useTranslation } from 'react-i18next';
import { useImmer } from 'use-immer';
import { FabInput } from '../../base/fab-input';
import { IFablab } from '../../../models/fablab';
declare let Fablab: IFablab;
interface ExtendedPriceFormProps {
formId: string,
onSubmit: (pack: Price) => void,
price?: Price,
}
/**
* A form component to create/edit a extended price.
* The form validation must be created elsewhere, using the attribute form={formId}.
*/
export const ExtendedPriceForm: React.FC<ExtendedPriceFormProps> = ({ formId, onSubmit, price }) => {
const [extendedPriceData, updateExtendedPriceData] = useImmer<Price>(price || {} as Price);
const { t } = useTranslation('admin');
/**
* Callback triggered when the user sends the form.
*/
const handleSubmit = (event: BaseSyntheticEvent): void => {
event.preventDefault();
onSubmit(extendedPriceData);
};
/**
* Callback triggered when the user inputs an amount for the current extended price.
*/
const handleUpdateAmount = (amount: string) => {
updateExtendedPriceData(draft => {
draft.amount = parseFloat(amount);
});
};
/**
* Callback triggered when the user inputs a number of minutes for the current extended price.
*/
const handleUpdateHours = (minutes: string) => {
updateExtendedPriceData(draft => {
draft.duration = parseInt(minutes, 10);
});
};
return (
<form id={formId} onSubmit={handleSubmit} className="group-form">
<label htmlFor="duration">{t('app.admin.calendar.minutes')} *</label>
<FabInput id="duration"
type="number"
defaultValue={extendedPriceData?.duration || ''}
onChange={handleUpdateHours}
step={1}
min={1}
icon={<i className="fas fa-clock" />}
required />
<label htmlFor="amount">{t('app.admin.extended_price_form.amount')} *</label>
<FabInput id="amount"
type="number"
step={0.01}
min={0}
defaultValue={extendedPriceData?.amount || ''}
onChange={handleUpdateAmount}
icon={<i className="fas fa-money-bill" />}
addOn={Fablab.intl_currency}
required />
</form>
);
};

View File

@ -0,0 +1,145 @@
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 SpaceAPI from '../../../api/space';
import GroupAPI from '../../../api/group';
import { Group } from '../../../models/group';
import { IApplication } from '../../../models/application';
import { Space } from '../../../models/space';
import { EditablePrice } from '../editable-price';
import { ConfigureExtendedPriceButton } from './configure-extended-price-button';
import PriceAPI from '../../../api/price';
import { Price } from '../../../models/price';
import { useImmer } from 'use-immer';
import FormatLib from '../../../lib/format';
declare const Application: IApplication;
interface SpacesPricingProps {
onError: (message: string) => void,
onSuccess: (message: string) => void,
}
/**
* Interface to set and edit the prices of spaces-hours, per group
*/
const SpacesPricing: React.FC<SpacesPricingProps> = ({ onError, onSuccess }) => {
const { t } = useTranslation('admin');
const [spaces, setSpaces] = useState<Array<Space>>(null);
const [groups, setGroups] = useState<Array<Group>>(null);
const [prices, updatePrices] = useImmer<Array<Price>>([]);
// retrieve the initial data
useEffect(() => {
SpaceAPI.index()
.then(data => setSpaces(data))
.catch(error => onError(error));
GroupAPI.index({ disabled: false, admins: false })
.then(data => setGroups(data))
.catch(error => onError(error));
PriceAPI.index({ priceable_type: 'Space', plan_id: null })
.then(data => updatePrices(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 FormatLib.price(hourlyRate);
}
const price = (hourlyRate / 60) * EXEMPLE_DURATION;
return FormatLib.price(price);
};
/**
* Find the default price (hourly rate) matching the given criterion
*/
const findPriceBy = (spaceId, groupId): Price => {
return prices.find(price => price.priceable_id === spaceId && price.group_id === groupId && price.duration === 60);
};
/**
* Find prices matching the given criterion, except the default hourly rate
*/
const findExtendedPricesBy = (spaceId, groupId): Array<Price> => {
return prices.filter(price => price.priceable_id === spaceId && price.group_id === groupId && price.duration !== 60);
};
/**
* Update the given price in the internal state
*/
const updatePrice = (price: Price): void => {
updatePrices(draft => {
const index = draft.findIndex(p => p.id === price.id);
draft[index] = price;
return draft;
});
};
/**
* Callback triggered when the user has confirmed to update a price
*/
const handleUpdatePrice = (price: Price): void => {
PriceAPI.update(price)
.then(() => {
onSuccess(t('app.admin.spaces_pricing.price_updated'));
updatePrice(price);
})
.catch(error => onError(error));
};
return (
<div className="pricing-list">
<FabAlert level="warning">
<p><HtmlTranslate trKey="app.admin.pricing.these_prices_match_space_hours_rates_html"/></p>
<p><HtmlTranslate trKey="app.admin.pricing.prices_calculated_on_hourly_rate_html" options={{ DURATION: `${EXEMPLE_DURATION}`, RATE: examplePrice('hourly_rate'), PRICE: examplePrice('final_price') }} /></p>
<p>{t('app.admin.pricing.you_can_override')}</p>
</FabAlert>
<table>
<thead>
<tr>
<th>{t('app.admin.pricing.spaces')}</th>
{groups?.map(group => <th key={group.id} className="group-name">{group.name}</th>)}
</tr>
</thead>
<tbody>
{spaces?.map(space => <tr key={space.id}>
<td>{space.name}</td>
{groups?.map(group => <td key={group.id}>
{prices.length && <EditablePrice price={findPriceBy(space.id, group.id)} onSave={handleUpdatePrice} />}
<ConfigureExtendedPriceButton
prices={findExtendedPricesBy(space.id, group.id)}
onError={onError}
onSuccess={onSuccess}
groupId={group.id}
priceableId={space.id}
priceableType='Space' />
</td>)}
</tr>)}
</tbody>
</table>
</div>
);
};
const SpacesPricingWrapper: React.FC<SpacesPricingProps> = ({ onError, onSuccess }) => {
return (
<Loader>
<SpacesPricing onError={onError} onSuccess={onSuccess} />
</Loader>
);
};
Application.Components.component('spacesPricing', react2angular(SpacesPricingWrapper, ['onError', 'onSuccess']));

View File

@ -461,7 +461,7 @@ Application.Controllers.controller('EditPricingController', ['$scope', '$state',
*/
$scope.findPriceBy = function (prices, machineId, groupId) {
for (const price of Array.from(prices)) {
if ((price.priceable_id === machineId) && (price.group_id === groupId)) {
if ((price.priceable_id === machineId) && (price.group_id === groupId) && (price.duration === 60)) {
return price;
}
}

View File

@ -11,7 +11,8 @@ export interface Price {
plan_id: number,
priceable_type: string,
priceable_id: number,
amount: number
amount: number,
duration?: number // in minutes
}
export interface ComputePriceResult {

View File

@ -111,7 +111,8 @@ export enum SettingName {
PublicAgendaModule = 'public_agenda_module',
RenewPackThreshold = 'renew_pack_threshold',
PackOnlyForSubscription = 'pack_only_for_subscription',
OverlappingCategories = 'overlapping_categories'
OverlappingCategories = 'overlapping_categories',
ExtendedPricesInSameDay = 'extended_prices_in_same_day'
}
export type SettingValue = string|boolean|number;

View File

@ -0,0 +1,15 @@
export interface Space {
id: number,
name: string,
description: string,
slug: string,
default_places: number,
disabled: boolean,
space_image: string,
space_file_attributes?: {
id: number,
attachment: string,
attachement_url: string,
}
}

View File

@ -1080,7 +1080,7 @@ angular.module('application.router', ['ui.router'])
"'reminder_delay', 'visibility_yearly', 'visibility_others', 'wallet_module', 'trainings_module', " +
"'display_name_enable', 'machines_sort_by', 'fab_analytics', 'statistics_module', 'address_required', " +
"'link_name', 'home_content', 'home_css', 'phone_required', 'upcoming_events_shown', 'public_agenda_module'," +
"'renew_pack_threshold', 'pack_only_for_subscription', 'overlapping_categories']"
"'renew_pack_threshold', 'pack_only_for_subscription', 'overlapping_categories', 'extended_prices_in_same_day']"
}).$promise;
}],
privacyDraftsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'privacy_draft', history: true }).$promise; }],

View File

@ -57,12 +57,12 @@
@import "modules/machines/machines-filters";
@import "modules/machines/required-training-modal";
@import "modules/user/avatar";
@import "modules/pricing/machines-pricing";
@import "modules/pricing/pricing-list";
@import "modules/pricing/editable-price";
@import "modules/pricing/configure-packs-button";
@import "modules/pricing/pack-form";
@import "modules/pricing/delete-pack";
@import "modules/pricing/edit-pack";
@import "modules/pricing/configure-group-button";
@import "modules/pricing/group-form";
@import "modules/pricing/delete-group";
@import "modules/pricing/edit-group";
@import "modules/settings/check-list-setting";
@import "modules/prepaid-packs/propose-packs-modal";
@import "modules/prepaid-packs/packs-summary";

View File

@ -1,9 +1,9 @@
.configure-packs-button {
.configure-group {
display: inline-block;
margin-left: 6px;
position: relative;
.packs-button {
&-button {
border: 1px solid #d0cccc;
border-radius: 50%;
cursor: pointer;
@ -44,7 +44,7 @@
line-height: 24px;
}
.pack-actions button {
.group-actions button {
font-size: 10px;
vertical-align: middle;
line-height: 10px;

View File

@ -1,7 +1,7 @@
.delete-pack {
.delete-group {
display: inline;
.remove-pack-button {
&-button {
background-color: #cb1117;
color: white;
}

View File

@ -1,4 +1,4 @@
.pack-form {
.group-form {
.interval-inputs {
display: flex;

View File

@ -1,4 +1,4 @@
.machines-pricing {
.pricing-list {
.fab-alert {
margin: 15px 0;
}
@ -28,4 +28,4 @@
border-top: 1px solid #ddd;
}
}
}
}

View File

@ -1,29 +1 @@
<div class="alert alert-warning m-t">
<p ng-bind-html="'app.admin.pricing.these_prices_match_space_hours_rates_html' | translate"></p>
<p ng-bind-html="'app.admin.pricing.prices_calculated_on_hourly_rate_html' | translate:{ DURATION:slotDuration, RATE: examplePrice('hourly_rate'), PRICE: examplePrice('final_price') }"></p>
<p translate>{{ 'app.admin.pricing.you_can_override' }}</p>
</div>
<table class="table">
<thead>
<tr>
<th style="width:20%" translate>{{ 'app.admin.pricing.spaces' }}</th>
<th style="width:20%" ng-repeat="group in enabledGroups">
<span class="text-u-c text-sm">{{group.name}}</span>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="space in enabledSpaces">
<td>
{{ space.name }}
</td>
<td ng-repeat="group in enabledGroups">
<span editable-number="findPriceBy(spacesPrices, space.id, group.id).amount"
e-step="any"
onbeforesave="updatePrice($data, findPriceBy(spacesPrices, space.id, group.id))">
{{ findPriceBy(spacesPrices, space.id, group.id).amount | currency}}
</span>
</td>
</tr>
</tbody>
</table>
<spaces-pricing on-success="onSuccess" on-error="onError">

View File

@ -117,6 +117,28 @@
required="true">
</number-setting>
</div>
<div class="section-separator"></div>
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.pack_only_for_subscription_info' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.pack_only_for_subscription_info_html' | translate"></p>
<boolean-setting name="pack_only_for_subscription"
settings="allSettings"
label="app.admin.settings.pack_only_for_subscription"
classes="m-l">
</boolean-setting>
</div>
<div class="section-separator"></div>
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.extended_prices' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.extended_prices_info_html' | translate"></p>
<boolean-setting name="extended_prices_in_same_day"
settings="allSettings"
label="app.admin.settings.extended_prices_in_same_day"
classes="m-l">
</boolean-setting>
</div>
</div>
</div>
</div>
@ -170,6 +192,8 @@
label="app.admin.settings.show_event"
classes="m-l"></boolean-setting>
</div>
<div class="section-separator"></div>
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.display_invite_to_renew_pack' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.packs_threshold_info_html' | translate"></p>
@ -182,14 +206,5 @@
step="0.01">
</number-setting>
</div>
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.pack_only_for_subscription_info' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.pack_only_for_subscription_info_html' | translate"></p>
<boolean-setting name="pack_only_for_subscription"
settings="allSettings"
label="app.admin.settings.pack_only_for_subscription"
classes="m-l">
</boolean-setting>
</div>
</div>
</div>

View File

@ -3,7 +3,7 @@
MINUTES_PER_HOUR = 60.0
SECONDS_PER_MINUTE = 60.0
GET_SLOT_PRICE_DEFAULT_OPTS = { has_credits: false, elements: nil, is_division: true, prepaid: { minutes: 0 } }.freeze
GET_SLOT_PRICE_DEFAULT_OPTS = { has_credits: false, elements: nil, is_division: true, prepaid: { minutes: 0 }, custom_duration: nil }.freeze
# A generic reservation added to the shopping cart
class CartItem::Reservation < CartItem::BaseItem
@ -16,19 +16,19 @@ class CartItem::Reservation < CartItem::BaseItem
end
def price
base_amount = @reservable.prices.find_by(group_id: @customer.group_id, plan_id: @plan.try(:id)).amount
is_privileged = @operator.privileged? && @operator.id != @customer.id
prepaid = { minutes: PrepaidPackService.minutes_available(@customer, @reservable) }
prices = applicable_prices
elements = { slots: [] }
amount = 0
hours_available = credits
@slots.each_with_index do |slot, index|
amount += get_slot_price(base_amount, slot, is_privileged,
elements: elements,
has_credits: (index < hours_available),
prepaid: prepaid)
amount += get_slot_price_from_prices(prices, slot, is_privileged,
elements: elements,
has_credits: (index < hours_available),
prepaid: prepaid)
end
{ elements: elements, amount: amount }
@ -61,6 +61,27 @@ class CartItem::Reservation < CartItem::BaseItem
0
end
##
# Compute the price of a single slot, according to the list of applicable prices.
# @param prices {{ prices: Array<{price: Price, duration: number}> }} list of prices to use with the current reservation
# @see get_slot_price
##
def get_slot_price_from_prices(prices, slot, is_privileged, options = {})
options = GET_SLOT_PRICE_DEFAULT_OPTS.merge(options)
slot_minutes = (slot[:end_at].to_time - slot[:start_at].to_time) / SECONDS_PER_MINUTE
price = prices[:prices].find { |p| p[:duration] <= slot_minutes && p[:duration].positive? }
price = prices[:prices].first if price.nil?
hourly_rate = (price[:price].amount.to_f / price[:price].duration) * MINUTES_PER_HOUR
# apply the base price to the real slot duration
real_price = get_slot_price(hourly_rate, slot, is_privileged, options)
price[:duration] -= slot_minutes
real_price
end
##
# Compute the price of a single slot, according to the base price and the ability for an admin
# to offer the slot.
@ -103,6 +124,35 @@ class CartItem::Reservation < CartItem::BaseItem
real_price
end
# We determine the list of prices applicable to current reservation
# The longest available price is always used in priority.
# Eg. If the reservation is for 12 hours, and there are prices for 3 hours, 7 hours,
# and the base price (1 hours), we use the 7 hours price, then 3 hours price, and finally the base price twice (7+3+1+1 = 12).
# All these prices are returned to be applied to the reservation.
def applicable_prices
all_slots_in_same_day = @slots.map { |slot| slot[:start_at].to_date }.uniq.size == 1
total_duration = @slots.map { |slot| (slot[:end_at].to_time - slot[:start_at].to_time) / SECONDS_PER_MINUTE }.reduce(:+)
rates = { prices: [] }
remaining_duration = total_duration
while remaining_duration.positive?
max_duration = @reservable.prices.where(group_id: @customer.group_id, plan_id: @plan.try(:id))
.where(Price.arel_table[:duration].lteq(remaining_duration))
.maximum(:duration)
max_duration = 60 if max_duration.nil? || Setting.get('extended_prices_in_same_day') && !all_slots_in_same_day
max_duration_price = @reservable.prices.find_by(group_id: @customer.group_id, plan_id: @plan.try(:id), duration: max_duration)
current_duration = [remaining_duration, max_duration].min
rates[:prices].push(price: max_duration_price, duration: current_duration)
remaining_duration -= current_duration
end
rates[:prices].sort! { |a, b| b[:duration] <=> a[:duration] }
rates
end
##
# Compute the number of remaining hours in the users current credits (for machine or space)
##

View File

@ -7,5 +7,5 @@ class Price < ApplicationRecord
belongs_to :priceable, polymorphic: true
validates :priceable, :group_id, :amount, presence: true
validates :priceable_id, uniqueness: { scope: %i[priceable_type plan_id group_id] }
validates :priceable_id, uniqueness: { scope: %i[priceable_type plan_id group_id duration] }
end

View File

@ -121,7 +121,8 @@ class Setting < ApplicationRecord
public_agenda_module
renew_pack_threshold
pack_only_for_subscription
overlapping_categories] }
overlapping_categories
extended_prices_in_same_day] }
# WARNING: when adding a new key, you may also want to add it in:
# - config/locales/en.yml#settings
# - app/frontend/src/javascript/models/setting.ts#SettingName

View File

@ -2,6 +2,14 @@
# Check the access policies for API::PricesController
class PricePolicy < ApplicationPolicy
def create?
user.admin? && record.duration != 60
end
def destroy?
user.admin? && record.duration != 60
end
def update?
user.admin?
end

View File

@ -127,7 +127,7 @@ class PaymentScheduleService
# @param filters {Hash} allowed filters: reference, customer, date.
##
def self.list(page, size, filters = {})
ps = PaymentSchedule.includes(:invoicing_profile, :payment_schedule_items)
ps = PaymentSchedule.includes(:invoicing_profile, :payment_schedule_items, :payment_schedule_objects)
.joins(:invoicing_profile)
.order('payment_schedules.created_at DESC')
.page(page)

View File

@ -61,10 +61,7 @@ class PrepaidPackService
## Total number of prepaid minutes available
def minutes_available(user, priceable)
is_pack_only_for_subscription = Setting.find_by(name: "pack_only_for_subscription")&.value
if is_pack_only_for_subscription == 'true' && !user.subscribed_plan
return 0
end
return 0 if Setting.get('pack_only_for_subscription') && !user.subscribed_plan
user_packs = user_packs(user, priceable)
total_available = user_packs.map { |up| up.prepaid_pack.minutes }.reduce(:+) || 0

View File

@ -1,2 +1,4 @@
json.extract! price, :id, :group_id, :plan_id, :priceable_type, :priceable_id
# frozen_string_literal: true
json.extract! price, :id, :group_id, :plan_id, :priceable_type, :priceable_id, :duration
json.amount price.amount / 100.0

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
json.price @amount[:total] / 100.00
json.price_without_coupon @amount[:before_coupon] / 100.00
if @amount[:elements]

View File

@ -0,0 +1,3 @@
# frozen_string_literal: true
json.partial! 'api/prices/price', price: @price

View File

@ -1 +1,3 @@
# frozen_string_literal: true
json.partial! 'api/prices/price', collection: @prices, as: :price

View File

@ -1 +1,3 @@
# frozen_string_literal: true
json.partial! 'api/prices/price', price: @price

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
json.array!(@spaces) do |space|
json.extract! space, :id, :name, :description, :slug, :default_places, :disabled
json.space_image space.space_image.attachment.medium.url if space.space_image

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
json.extract! @space, :id, :name, :description, :characteristics, :created_at, :updated_at, :slug, :default_places, :disabled
json.space_image @space.space_image.attachment.large.url if @space.space_image
json.space_files_attributes @space.space_files do |f|
@ -9,4 +11,4 @@ end
# using the space in the space_show screen
# json.space_projects @space.projects do |p|
# json.extract! p, :slug, :name
# end
# end

View File

@ -368,6 +368,8 @@ en:
status_enabled: "Enabled"
status_disabled: "Disabled"
status_all: "All"
spaces_pricing:
price_updated: "Price successfully updated"
machines_pricing:
prices_match_machine_hours_rates_html: "The prices below match one hour of machine usage, <strong>without subscription</strong>."
prices_calculated_on_hourly_rate_html: "All the prices will be automatically calculated based on the hourly rate defined here.<br/><em>For example</em>, if you define an hourly rate at {RATE}: a slot of {DURATION} minutes, will be charged <strong>{PRICE}</strong>."
@ -378,6 +380,11 @@ en:
packs: "Prepaid packs"
no_packs: "No packs for now"
pack_DURATION: "{DURATION} hours"
configure_extendedPrices_button:
extendedPrices: "Extended prices"
no_extendedPrices: "No extended price for now"
extended_prices_form:
amount: "Price"
pack_form:
hours: "Hours"
amount: "Price"
@ -404,6 +411,21 @@ en:
edit_pack: "Edit the pack"
confirm_changes: "Confirm changes"
pack_successfully_updated: "The prepaid pack was successfully updated."
create_extendedPrice:
new_extendedPrice: "New extended price"
new_extendedPrice_info: "Extended prices allows you to define prices based on custom durations, intead on the default hourly rates."
create_extendedPrice: "Create extended price"
extendedPrice_successfully_created: "The new extended price was successfully created."
delete_extendedPrice:
extendedPrice_deleted: "The extended price was successfully deleted."
unable_to_delete: "Unable to delete the extended price: "
delete_extendedPrice: "Delete the extended price"
confirm_delete: "Delete"
delete_confirmation: "Are you sure you want to delete this extended price? This won't be possible if it was already bought by users."
edit_extendedPrice:
edit_extendedPrice: "Edit the extended price"
confirm_changes: "Confirm changes"
extendedPrice_successfully_updated: "The extended price was successfully updated."
#ajouter un code promotionnel
coupons_new:
add_a_coupon: "Add a coupon"
@ -1235,6 +1257,9 @@ en:
pack_only_for_subscription_info_html: "If this option is activated, the purchase and use of a prepaid pack is only possible for the user with a valid subscription."
pack_only_for_subscription: "Subscription valid for purchase and use of a prepaid pack"
pack_only_for_subscription_info: "Make subscription mandatory for prepaid packs"
extended_prices: "Extended prices"
extended_prices_info_html: "Spaces can have different prices depending on the cumulated duration of the booking. You can choose if this apply to all bookings or only to those starting within the same day."
extended_prices_in_same_day: "Extended prices in the same day"
overlapping_options:
training_reservations: "Trainings"
machine_reservations: "Machines"

View File

@ -77,9 +77,9 @@ pt:
unable_to_delete_the_slot: "Não é possível deletar o slot {START} - {END}, provavelmente porque já foi reservado por um membro"
slots_not_deleted: "Em {TOTAL} slots, {COUNT, plural, one {} =1{um não foi deletado} other{{COUNT} não foram deletados}}. Talvez existam algumas reservas {COUNT, plural, =1{nela} other{nelas}}."
you_should_select_at_least_a_machine: "Você deveria selecionar a última máquina neste slot."
inconsistent_times: "Error: the end of the availability is before its beginning."
min_one_slot: "The availability must be split in one slot at least."
min_slot_duration: "You must specify a valid duration for the slots."
inconsistent_times: "Erro: a data final deve ser maior que a data de início."
min_one_slot: "A disponibilidade deve ser dividida em pelo menos um slot."
min_slot_duration: "Especifique uma duração válida para o slot."
export_is_running_you_ll_be_notified_when_its_ready: "A Exportação está em andamento. Você será notificado quando terminar."
actions: "Ações"
block_reservations: "Impedir reservas"
@ -373,37 +373,37 @@ pt:
prices_calculated_on_hourly_rate_html: "Todos os preços serão calculados automaticamente com base na taxa horária definida aqui.<br/><em>Por exemplo</em>, se você definir uma taxa horária em {RATE}: um slot de {DURATION} minutos, será cobrada a tarifa <strong>{PRICE}</strong>."
you_can_override: "Você pode substituir essa duração por cada disponibilidade que você criar na agenda. O preço será então ajustado em conformidade."
machines: "Máquinas"
price_updated: "Price successfully updated"
price_updated: "Preço atualizado com sucesso"
configure_packs_button:
packs: "Prepaid packs"
no_packs: "No packs for now"
pack_DURATION: "{DURATION} hours"
packs: "Pacotes pré-pagos"
no_packs: "Não há pacotes no momento"
pack_DURATION: "{DURATION} horas"
pack_form:
hours: "Hours"
amount: "Price"
disabled: "Disabled"
validity_count: "Maximum validity"
select_interval: "Interval..."
hours: "Horas"
amount: "Preço"
disabled: "Desabilitado"
validity_count: "Validade máxima"
select_interval: "Intervalo..."
intervals:
day: "{COUNT, plural, one{Day} other{Days}}"
week: "{COUNT, plural, one{Week} other{Weeks}}"
month: "{COUNT, plural, one{Month} other{Months}}"
year: "{COUNT, plural, one{Year} other{Years}}"
day: "{COUNT, plural, one{Dia} other{Dias}}"
week: "{COUNT, plural, =1 {Semana} other {Semanas}}"
month: "{COUNT, plural, =1 {Mês} other {Meses}}"
year: "{COUNT, plural, one{Ano} other{Anos}}"
create_pack:
new_pack: "New prepaid pack"
new_pack_info: "A prepaid pack allows users to buy {TYPE, select, Machine{machine} Space{space} other{}} hours before booking any slots. These packs can provide discounts on volumes purchases."
create_pack: "Create this pack"
pack_successfully_created: "The new prepaid pack was successfully created."
new_pack: "Novo pacote pré-pago"
new_pack_info: "Um pacote pré-pago permite que os usuários comprem {TYPE, select, Machine{Máquina} Space{Espaço} other{}} horas antes de reservar qualquer slot. Estes pacotes podem oferecer descontos em compras de volumes."
create_pack: "Criar este pacote"
pack_successfully_created: "O novo pacote pré-pago foi criado com sucesso."
delete_pack:
pack_deleted: "The prepaid pack was successfully deleted."
unable_to_delete: "Unable to delete the prepaid pack: "
delete_pack: "Delete the prepaid pack"
confirm_delete: "Delete"
delete_confirmation: "Are you sure you want to delete this prepaid pack? This won't be possible if the pack was already bought by users."
pack_deleted: "O pacote pré-pago foi deletado com sucesso."
unable_to_delete: "Não foi possível deletar o pacote pré-pago: "
delete_pack: "Excluir o pacote pré-pago"
confirm_delete: "Deletar"
delete_confirmation: "Tem certeza que deseja excluir este pacote pré-pago? Isto não será possível se o pacote já foi comprado pelos usuários."
edit_pack:
edit_pack: "Edit the pack"
confirm_changes: "Confirm changes"
pack_successfully_updated: "The prepaid pack was successfully updated."
edit_pack: "Editar o pacote"
confirm_changes: "Confirmar alterações"
pack_successfully_updated: "O pacote pré-pago foi atualizado com sucesso."
#ajouter un code promotionnel
coupons_new:
add_a_coupon: "Adicionar cupom"
@ -450,7 +450,7 @@ pt:
credit_note: "Nota de crédito"
display_more_invoices: "Mostrar mais faturas..."
no_invoices_for_now: "Nenhuma fatura."
payment_schedules_list: "Payment schedules"
payment_schedules_list: "Datas de pagamento"
invoicing_settings: "Configurações do faturamento"
warning_invoices_disabled: "Aviso: As faturas não estão ativadas. Nenhuma fatura será gerada pelo Fab-manager. No entanto, você deve preencher corretamente as informações abaixo, especialmente o IVA."
change_logo: "Mudar o logo"
@ -477,7 +477,7 @@ pt:
important_notes: "Notas importantes"
address_and_legal_information: "Endereço e informações legais"
invoice_reference: "Referencia de fatura"
text: "text"
text: "texto"
year: "Ano"
month: "Mês"
day: "Dia"
@ -485,7 +485,7 @@ pt:
online_sales: "Vendas online"
wallet: "Carteira"
refund: "Restituição"
payment_schedule: "Payment schedule"
payment_schedule: "Agendamento de pagamento"
model: "Modelo"
documentation: "Documentação"
2_digits_year: "2 dígitos ano (ex 70)"
@ -509,9 +509,9 @@ pt:
add_a_notice_regarding_refunds_only_if_the_invoice_is_concerned: "Adicionar um aviso sobre reembolsos, apenas se a fatura estiver envolvida."
this_will_never_be_added_when_an_online_sales_notice_is_present: "Isto nunca será adicionado quando uma notificação de vendas online estiver presente."
eg_RA_will_add_A_to_the_refund_invoices: '(ed. R[/A] irá adicionar "/A" nas faturas reembolsadas)'
add_a_notice_regarding_payment_schedule: "Add a notice regarding the payment schedules, only for concerned documents."
this_will_never_be_added_with_other_notices: "This will never be added when any other notice is present."
eg_SE_to_schedules: '(eg. S[/E] will add "/E" to the payment schedules)'
add_a_notice_regarding_payment_schedule: "Adicionar aviso sobre as ordens de pagamento, somente para documentos envolvidos."
this_will_never_be_added_with_other_notices: "Isso nunca será adicionado quando uma outra notificação estiver presente."
eg_SE_to_schedules: '(ex. S[/E] adicionará "/E" aos horários de pagamento)'
code: "Código"
enable_the_code: "Ativar código"
enabled: "Ativar"
@ -563,9 +563,9 @@ pt:
logo_successfully_saved: "Logo salvo com sucesso."
an_error_occurred_while_saving_the_logo: "Um erro ocorreu ao salvar o logo."
filename: "Nome do arquivo"
schedule_filename: "Schedule file name"
schedule_filename: "Nome do arquivo de agendamento"
prefix_info: "As faturas serão geradas como arquivos PDF, com o seguinte prefixo."
schedule_prefix_info: "The payment schedules will be generated as PDF files, named with the following prefix."
schedule_prefix_info: "As faturas serão geradas como arquivos PDF, com o seguinte prefixo."
prefix: "Prefixo"
prefix_successfully_saved: "Prefixo de arquivo salvo com sucesso"
an_error_occurred_while_saving_the_prefix: "Ocorreu um erro ao salvar o prefixo do arquivo"
@ -633,10 +633,10 @@ pt:
general_space_code: "Código de contabilidade para todos os espaços"
accounting_Space_label: "Rótulo de espaços"
general_space_label: "Rótulo de conta para todos os espaços"
accounting_Error_code: "Errors code"
general_error_code: "Accounting code for erroneous invoices"
accounting_Error_label: "Errors label"
general_error_label: "Account label for erroneous invoices"
accounting_Error_code: "Código do erro"
general_error_code: "Código de contabilidade para faturas erradas"
accounting_Error_label: "Rótulo dos erros"
general_error_label: "Rótulo de conta para faturas erradas"
codes_customization_success: "Customization of accounting codes successfully saved."
unexpected_error_occurred: "Ocorreu um erro inesperado ao salvar os códigos. Tente novamente mais tarde."
export_accounting_data: "Exportar dados de contabilidade"
@ -677,46 +677,46 @@ pt:
error_check_keys: "Erro: por favor, verifique as suas chaves da Stripe."
stripe_keys_saved: "Chaves Stripe salvas com sucesso."
error_saving_stripe_keys: "Não foi possível salvar as chaves do Stripe. Por favor, tente novamente mais tarde."
payzen_keys_info_html: "<p>To be able to collect online payments, you must configure the <a href='https://payzen.eu' target='_blank'>PayZen</a> identifiers and keys.</p><p>Retrieve them from <a href='https://secure.payzen.eu/vads-merchant/' target='_blank'>your merchant back office</a>.</p>"
client_keys: "Client key"
api_keys: "API keys"
payzen_keys_info_html: "<p>Para coletar pagamentos online, você deve configurar os identificadores e chaves do <a href='https://payzen.eu' target='_blank'>PayZen</a>.</p><p>Recupere eles da <a href='https://secure.payzen.eu/vads-merchant/' target='_blank'>sua área de administração</a>.</p>"
client_keys: "Chave de cliente"
api_keys: "Chaves de API"
edit_keys: "Editar chaves"
currency: "Moeda"
currency_info_html: "Por favor, especifique abaixo da moeda usada para pagamento online. Você deve fornecer um código ISO de três letras, da lista de <a href='https://stripe.com/docs/currencies' target='_blank'>moedas com suporte a Stripe</a>."
currency_alert_html: "<strong>Aviso</strong>: a moeda não pode ser alterada após o primeiro pagamento online ter sido feito. Defina essa configuração cuidadosamente antes de abrir o Fab-manager para seus membros."
stripe_currency: "Moeda do Stripe"
gateway_configuration_error: "An error occurred while configuring the payment gateway: "
gateway_configuration_error: "Ocorreu um erro ao configurar o gateway de pagamento: "
payzen:
payzen_keys: "PayZen keys"
payzen_username: "Username"
payzen_password: "Password"
payzen_endpoint: "REST API server name"
payzen_keys: "Chaves PayZen"
payzen_username: "Nome de Usuário"
payzen_password: "Senha"
payzen_endpoint: "Nome do servidor API REST"
payzen_hmac: "HMAC-SHA-256 key"
payzen_public_key: "Client public key"
currency: "Currency"
payzen_currency: "PayZen currency"
currency_info_html: "Please specify below the currency used for online payment. You should provide a three-letter ISO code, from the list of <a href='https://payzen.io/en-EN/payment-file/ips/list-of-supported-currencies.html' target='_blank'> PayZen supported currencies</a>."
save: "Save"
currency_error: "The inputted value is not a valid currency"
error_while_saving: "An error occurred while saving the currency: "
currency_updated: "The PayZen currency was successfully updated to {CURRENCY}."
payzen_public_key: "Chave pública do cliente"
currency: "Moeda"
payzen_currency: "Moeda PayZen"
currency_info_html: "Por favor, especifique abaixo a moeda usada para pagamento online. Você deve fornecer um código ISO de três letras, presente na <a href='https://payzen.io/en-EN/payment-file/ips/list-of-supported-currencies.html' target='_blank'>lista de moedas com suporte a Stripe</a>."
save: "Salvar"
currency_error: "O valor inserido não é uma moeda válida"
error_while_saving: "Ocorreu um erro ao salvar a moeda: "
currency_updated: "A moeda PayZen foi atualizada com sucesso para {CURRENCY}."
#select a payment gateway
gateway_modal:
select_gateway_title: "Select a payment gateway"
gateway_info: "To securely collect and process payments online, Fab-manager needs to use an third-party service authorized by the financial institutions, called a payment gateway."
select_gateway: "Please select an available gateway"
select_gateway_title: "Selecione o seu gateway de pagamento"
gateway_info: "Para coletar e processar de forma segura os pagamentos on-line, o Fab-manager precisa utilizar um serviço de terceiros autorizado pelas instituições financeiras, chamado gateway de pagamento."
select_gateway: "Por favor, selecione um gateway disponível"
stripe: "Stripe"
payzen: "PayZen"
confirm_button: "Validate the gateway"
confirm_button: "Validar o gateway"
payment_schedules:
filter_schedules: "Filter schedules"
no_payment_schedules: "No payment schedules to display"
load_more: "Load more"
card_updated_success: "The user's card was successfully updated"
filter_schedules: "Filtrar agendamentos"
no_payment_schedules: "Nenhuma assinatura para exibir"
load_more: "Ver mais"
card_updated_success: "O cartão do usuário foi atualizado com sucesso"
document_filters:
reference: "Reference"
customer: "Customer"
date: "Date"
reference: "Referências"
customer: "Cliente"
date: "Data"
#management of users, labels, groups, and so on
members:
users_management: "Gerenciamento de usuários"
@ -866,7 +866,7 @@ pt:
expires_at: "Experia em:"
price_: "Preço:"
offer_free_days: "Oferecer dias grátis"
renew_subscription: "Renew the subscription"
renew_subscription: "Renovar assinatura"
user_has_no_current_subscription: "O usuário não possui inscrição."
subscribe_to_a_plan: "Plano de inscrição"
trainings: "Treinamentos"
@ -892,7 +892,7 @@ pt:
a_problem_occurred_while_saving_the_date: "Um erro ocorreu ao salvar a data."
new_subscription: "Nova inscrição"
you_are_about_to_purchase_a_subscription_to_NAME: "Você está prestes a comprar uma assinatura para {NAME}."
with_schedule: "Subscribe with a monthly payment schedule"
with_schedule: "Inscreva-se com uma assinatura mensal"
subscription_successfully_purchased: "Assinatura adquirida com êxito."
a_problem_occurred_while_taking_the_subscription: "Ocorreu um problema ao fazer a assinatura"
wallet: "Carteira"
@ -901,32 +901,32 @@ pt:
cannot_extend_own_subscription: "Você não pode estender sua própria assinatura. Por favor, peça a outro gerente ou administrador para estender sua assinatura."
#extend a subscription for free
free_extend_modal:
extend_subscription: "Extend the subscription"
offer_free_days_infos: "You are about to extend the user's subscription by offering him free additional days."
credits_will_remain_unchanged: "The balance of free credits (training / machines / spaces) of the user will remain unchanged."
current_expiration: "Current subscription will expire at:"
extend_subscription: "Estender assinatura"
offer_free_days_infos: "Você está prestes a estender a assinatura do usuário, oferecendo gratuitamente mais dias."
credits_will_remain_unchanged: "O saldo de créditos gratuitos (treinamento / máquinas / espaços) do usuário permanecerá inalterado."
current_expiration: "A assinatura atual expirará em:"
DATE_TIME: "{DATE} {TIME}"
new_expiration_date: "New expiration date:"
number_of_free_days: "Number of free days:"
extend: "Extend"
extend_success: "The subscription was successfully extended for free"
new_expiration_date: "Nova data de expiração:"
number_of_free_days: "Número de dias grátis:"
extend: "Estender"
extend_success: "A assinatura foi estendida com sucesso gratuitamente"
#renew a subscription
renew_subscription_modal:
renew_subscription: "Renew the subscription"
renew_subscription_info: "You are about to renew the user's subscription by charging him again for his current subscription."
credits_will_be_reset: "The balance of free credits (training / machines / spaces) of the user will be reset, unused credits will be lost."
current_expiration: "Current subscription will expire at:"
new_start: "The new subscription will start at:"
new_expiration_date: "The new subscription will expire at:"
pay_in_one_go: "Pay in one go"
renew: "Renew"
renew_success: "The subscription was successfully renewed"
renew_subscription: "Renovar assinatura"
renew_subscription_info: "Você estenderá a assinatura do usuário cobrando-o novamente por sua assinatura atual."
credits_will_be_reset: "O saldo de créditos gratuitos (treinamento / máquinas / espaços) do usuário será redefinido, os créditos não utilizados serão perdidos."
current_expiration: "A assinatura atual expirará em:"
new_start: "A nova assinatura começará em:"
new_expiration_date: "A nova assinatura expirará em:"
pay_in_one_go: "Pagar de á vista"
renew: "Renovar"
renew_success: "A assinatura foi renovada com sucesso"
#take a new subscription
subscribe_modal:
subscribe_USER: "Subscribe {USER}"
subscribe: "Subscribe"
select_plan: "Please select a plan"
pay_in_one_go: "Pay in one go"
subscribe_USER: "Inscreva-se {USER}"
subscribe: "Inscrever-se"
select_plan: "Por favor, selecione um plano"
pay_in_one_go: "Paga á vista"
subscription_success: ""
#add a new administrator to the platform
admins_new:
@ -1164,7 +1164,7 @@ pt:
machines_sort_by: "ordem de exibição das máquinas"
fab_analytics: "Fab Analytics"
phone_required: "telefone é obrigatório"
address_required: "address required"
address_required: "o endereço é obrigatório"
tracking_id: "tracking ID"
facebook_app_id: "ID de Utilizador do Facebook"
twitter_analytics: "Analisador de conta Twitter"
@ -1176,9 +1176,9 @@ pt:
error_SETTING_locked: "Não foi possível atualizar a configuração: {SETTING} está bloqueado. Por favor contate o administrador do sistema."
an_error_occurred_saving_the_setting: "Ocorreu um erro ao salvar a configuração. Por favor, tente novamente mais tarde."
book_overlapping_slots_info: "Permitir / impedir a reserva de slots sobrepostos"
allow_booking: "Allow booking"
overlapping_categories: "Overlapping categories"
overlapping_categories_info: "Preventing booking on overlapping slots will be done by comparing the date and time of the following categories of reservations."
allow_booking: "Permitir reserva"
overlapping_categories: "Categorias sobrepostas"
overlapping_categories_info: "A prevenção de sobreposição de slots será feita comparando a data e hora das seguintes categorias de reservas."
default_slot_duration: "Duração padrão para slots"
duration_minutes: "Duração (em minutos)"
default_slot_duration_info: "Máquina e espaço disponíveis são divididos em vários slots desta duração. Esse valor pode ser substituído por disponibilidade."
@ -1191,10 +1191,10 @@ pt:
plans_info_html: "<p>As assinaturas fornecem uma maneira de segmentar seus preços e proporcionar benefícios aos usuários normais.</p><p><strong>Aviso:</strong> não é recomendável desativar os planos se pelo menos uma assinatura estiver ativa no sistema.</p>"
enable_plans: "Ativar os planos"
plans_module: "módulo de planos"
trainings: "Trainings"
trainings_info_html: "<p>Trainings are fully integrated into the Fab-manger's agenda. If enabled, your members will be able to book and pay trainings.</p><p>Trainings provides a way to prevent members to book some machines, if they do have not taken the prerequisite course.</p>"
enable_trainings: "Enable the trainings"
trainings_module: "trainings module"
trainings: "Treinamentos"
trainings_info_html: "<p>Os treinamentos estão totalmente integrados na agenda do Fabmanager. Se ativado, seus membros poderão reservar e pagar treinamentos.</p><p>A forma de impedir que os membros agendem determinadas máquinas é através dos pré-requisitos para uso das mesmas </p>"
enable_trainings: "Ativar treinamentos"
trainings_module: "módulo de treinamentos"
invoicing: "Faturamento"
invoicing_info_html: "<p>Você pode desativar completamente o módulo de faturamento.</p><p>Isso é útil se você tiver o seu próprio sistema de faturação, e não quer que o Fab-manager gere e envie faturas para os membros.</p><p><strong>Aviso:</strong> mesmo se você desativar o módulo de faturação, você deve configurar o IVA para evitar erros na contabilidade e nos preços. Faça isso na seção « Faturas > Configurações de faturação ».</p>"
enable_invoicing: "Habilitar faturamento"
@ -1203,9 +1203,9 @@ pt:
phone: "Telefone"
phone_is_required: "Telefone é obrigatório"
phone_required_info: "Você pode definir se o número de telefone deve ser exigido para registrar um novo usuário no Fab-manager."
address: "Address"
address_required_info_html: "You can define if the address should be required to register a new user on Fab-manager.<br/><strong>Please note</strong> that, depending on your country, the regulations may requires addresses for the invoices to be valid."
address_is_required: "Address is required"
address: "Endereço"
address_required_info_html: "Você pode definir se o endereço deve ser necessário para registrar um novo usuário no Fab-manager.<br/><strong>Por favor, note</strong> que, dependendo do seu país, as regulamentações podem exigir endereços para que as faturas sejam válidas."
address_is_required: "Endereço é obrigatório"
captcha: "Captcha"
captcha_info_html: "Você pode configurar uma proteção contra robôs, para evitar que eles criem contas de membros. Esta proteção está usando o Google reCAPTCHA. Inscreva-se para <a href='http://www.google.com/recaptcha/admin' target='_blank'>um par de chaves de API</a> para começar a usar o captcha."
site_key: "Chave do site"
@ -1226,20 +1226,20 @@ pt:
confirmation_required_info: "Opcionalmente, você pode forçar os usuários a confirmar o endereço de e-mail deles antes de poder acessar o Fab-manager."
confirmation_is_required: "Confirmação obrigatória"
wallet_module: "módulo de carteira"
public_agenda_module: "public agenda module"
public_agenda_module: "módulo da agenda pública"
statistics_module: "módulo de estatísticas"
upcoming_events_shown: "exibir limite para eventos futuros"
display_invite_to_renew_pack: "Display the invite to renew prepaid-packs"
packs_threshold_info_html: "You can define under how many hours the user will be invited to buy a new prepaid-pack, if his stock of prepaid hours is under this threshold.<br/>You can set a <strong>number of hours</strong> (<em>eg. 5</em>) or a <strong>percentage</strong> of his current pack pack (<em>eg. 0.05 means 5%</em>)."
renew_pack_threshold: "threshold for packs renewal"
pack_only_for_subscription_info_html: "If this option is activated, the purchase and use of a prepaid pack is only possible for the user with a valid subscription."
pack_only_for_subscription: "Subscription valid for purchase and use of a prepaid pack"
pack_only_for_subscription_info: "Make subscription mandatory for prepaid packs"
display_invite_to_renew_pack: "Mostrar o convite para renovar pacotes pré-pagos"
packs_threshold_info_html: "Você pode definir em quantas horas o usuário será convidado a comprar um novo pacote pré-pago, se o seu estoque de horas pré-pagas estiver abaixo desse limite.<br/>Você pode definir um <strong>número de horas</strong> (<em>por exemplo. 5</em>) ou uma <strong>porcentagem</strong> de seu pacote atual (<em>por exemplo, 0,05 significa 5%</em>)."
renew_pack_threshold: "limite para renovação de pacotes"
pack_only_for_subscription_info_html: "Se esta opção estiver ativada, a compra e uso de um pacote pré-pago só é possível para o usuário com uma assinatura válida."
pack_only_for_subscription: "Assinatura válida para compra e uso de um pacote pré-pago"
pack_only_for_subscription_info: "Tornar obrigatória a assinatura para pacotes pré-pagos"
overlapping_options:
training_reservations: "Trainings"
machine_reservations: "Machines"
space_reservations: "Spaces"
events_reservations: "Events"
training_reservations: "Treinamentos"
machine_reservations: "Máquinas"
space_reservations: "Espaços"
events_reservations: "Eventos"
general:
general: "Geral"
title: "Título"
@ -1276,9 +1276,9 @@ pt:
wallet: "Carteira"
wallet_info_html: "<p>A carteira virtual permite alocar uma soma de dinheiro aos usuários. Em seguida, pode gastar esse dinheiro como quiserem, no Fab-manager.</p><p>Membros não podem creditar suas carteiras, é um privilégio de gerentes e administradores.</p>"
enable_wallet: "Habilitar Carteira"
public_agenda: "Public agenda"
public_agenda_info_html: "<p>The public agenda offers to members and visitors a general overview of the Fablab's planning.</p><p>Please note that, even logged, users won't be able to book a reservation or modify anything from this agenda: this is a read-only page.</p>"
enable_public_agenda: "Enable public agenda"
public_agenda: "Agenda pública"
public_agenda_info_html: "<p>A agenda pública oferece aos membros e visitantes uma visão geral do planejamento do Fablab.</p><p>Por favor, note que, mesmo registrado, os usuários não poderão reservar uma reserva ou modificar nada desta agenda: esta é uma página somente leitura.</p>"
enable_public_agenda: "Tornar a agenda pública"
statistics: "Estatísticas"
statistics_info_html: "<p>Ativar ou desativar módulo de estatísticas.</p><p>Se ativado, todas as noites, os dados do dia que acabou de ser passado serão consolidados na base de dados de um poderoso motor de análise. Então, todos os administradores poderão navegar em gráficos estatísticos e tabelas na seção correspondente.</p>"
enable_statistics: "Habilitar estatísticas"
@ -1363,48 +1363,48 @@ pt:
report_removed: "O relatório foi eliminado"
failed_to_remove: "Ocorreu um erro, não é possível excluir o relatório"
plans_categories:
manage_plans_categories: "Manage plans' categories"
manage_plans_categories: "Gerenciar categoria dos planos"
plan_categories_list:
categories_list: "List of the plan's categories"
categories_list: "Lista de categorias do plano"
no_categories: "Sem categorias"
name: "Name"
significance: "Significance"
name: "Nome"
significance: "Importância"
create_plan_category:
new_category: "New category"
name: "Name"
significance: "Significance"
significance_info: "Categories will be shown ordered by signifiance. The higher you set the significance, the first the category will be shown."
confirm_create: "Create the category"
category_created: "The new category was successfully created"
unable_to_create: "Unable to create the category: "
new_category: "Nova categoria"
name: "Nome"
significance: "Importância"
significance_info: "As categorias serão mostradas ordenadas por importância. Quanto mais alto você definir a importância, mais em destaque ela estará."
confirm_create: "Criar nova categoria"
category_created: "A nova categoria foi criada com sucesso"
unable_to_create: "Não foi possível criar a categoria: "
edit_plan_category:
edit_category: "Edit the category"
name: "Name"
significance: "Significance"
confirm_edition: "Validate"
category_updated: "The category was successfully updated"
unable_to_update: "Unable to update the category: "
significance_info: "Categories will be shown ordered by signifiance. The higher you set the significance, the first the category will be shown."
edit_category: "Editar categoria"
name: "Nome"
significance: "Importância"
confirm_edition: "Validar"
category_updated: "A categoria foi atualizada com sucesso"
unable_to_update: "Não foi possível atualizar a categoria: "
significance_info: "As categorias serão ordenadas por importância. Quanto mais alto você definir a importância, mais em destaque ela estará."
delete_plan_category:
delete_category: "Delete a category"
confirm_delete: "Delete"
delete_confirmation: "Are you sure you want to delete this category? If you do, the plans associated with this category won't be sorted anymore."
category_deleted: "The category was successfully deleted"
unable_to_delete: "Unable to delete the category: "
delete_category: "Excluir categoria"
confirm_delete: "Deletar"
delete_confirmation: "Tem certeza que deseja excluir esta categoria? Se sim, os planos associados a esta categoria não serão mais classificados."
category_deleted: "Categoria foi excluída com sucesso"
unable_to_delete: "Não foi possível excluir a categoria: "
local_payment:
validate_cart: "Validate my cart"
offline_payment: "Payment on site"
about_to_cash: "You're about to confirm the cashing by an external payment mean. Please do not click on the button below until you have fully cashed the requested payment."
about_to_confirm: "You're about to confirm your {ITEM, select, subscription{subscription} other{reservation}}."
payment_method: "Payment method"
method_card: "Online by card"
method_check: "By check"
card_collection_info: "By validating, you'll be prompted for the member's card number. This card will be automatically charged at the deadlines."
check_collection_info: "By validating, you confirm that you have {DEADLINES} checks, allowing you to collect all the monthly payments."
online_payment_disabled: "Online payment is not available. You cannot collect this payment schedule by online card."
validate_cart: "Validar o meu carrinho"
offline_payment: "Pagamento pelo site"
about_to_cash: "Você está prestes a confirmar o recebimento de uma forma de pagamento externa. Por favor, não clique no botão abaixo até que você tenha recebido completamente o pagamento solicitado."
about_to_confirm: "Você está prestes a confirmar sua {ITEM, select, subscription{assinatura} other{reserva}}."
payment_method: "Método de pagamento"
method_card: "Online por cartão"
method_check: "Por verificação"
card_collection_info: "Ao validar, será solicitado o número do cartão do membro. Este cartão será cobrado automaticamente nos prazos."
check_collection_info: "Ao validar, você confirma que tem {DEADLINES} checks, permitindo que você colete todos os pagamentos mensais."
online_payment_disabled: "Pagamento online não está disponível. Você não pode selecionar este horário com pagamento online."
check_list_setting:
save: 'Save'
customization_of_SETTING_successfully_saved: "Customization of the {SETTING} successfully saved."
save: 'Salvar'
customization_of_SETTING_successfully_saved: "Personalização do {SETTING} salvo com êxito."
#feature tour
tour:
conclusion:
@ -1493,8 +1493,8 @@ pt:
title: "Nota de crédito"
content: "Permite gerar uma nota de crédito para a fatura nesta linha ou alguns de seus sub-elementos. <strong>Aviso:</strong> Isto só irá gerar o documento de contabilidade, o reembolso real do usuário será sempre sua responsabilidade."
payment-schedules:
title: "Payment schedules"
content: "<p>Some subscription plans may be configured to allow the members to pay them with a monthly payment schedule.</p><p>Here you can view all existing payment schedules and manage their deadlines.</p><p>Click on [+] at the beginning of a row to display all deadlines associated with a payment schedule, and run some actions on them.</p>"
title: "Datas de pagamento"
content: "<p>Alguns planos de assinatura podem ser configurados para permitir que os membros o paguem com um calendário de pagamento mensal.</p><p>Aqui você pode ver todas as agendas de pagamentos existentes e gerenciar seus prazos.</p><p>Clique em [+] no início de uma linha para exibir todos os prazos associados com um cronograma de pagamentos, e execute algumas ações nelas.</p>"
settings:
title: "Configurações"
content: "<p>Aqui você pode modificar os parâmetros para a geração de faturas. Clique no item em que está interessado para começar a editar.</p><p>Em particular, é aqui que você pode definir se está sujeito a IVA e à taxa aplicável.</p>"

View File

@ -109,10 +109,10 @@ pt:
your_previous_trainings: "Seus treinamentos anteriores"
your_approved_trainings: "Seus treinamentos aprovados"
no_trainings: "Sem treinamentos"
your_training_credits: "Your training credits"
subscribe_for_credits: "Subscribe to benefit from free trainings"
register_for_free: "Register for free to the following trainings:"
book_here: "Book here"
your_training_credits: "Seus créditos para treinamento"
subscribe_for_credits: "Inscreva-se para se beneficiar dos treinamentos gratuitos"
register_for_free: "Registre-se gratuitamente para os seguintes treinamentos:"
book_here: "Reserve aqui"
#dashboard: my events
events:
your_next_events: "Seus próximos eventos"
@ -130,9 +130,9 @@ pt:
download_the_credit_note: "Baixar fatura de reembolso"
no_invoices_for_now: "Nenhuma fatura."
payment_schedules:
no_payment_schedules: "No payment schedules to display"
load_more: "Load more"
card_updated_success: "Your card was successfully updated"
no_payment_schedules: "Nenhuma assinatura para exibir"
load_more: "Ver mais"
card_updated_success: "O seu cartão foi atualizado com sucesso"
#public profil of a member
members_show:
members_list: "Lista de membros"
@ -174,29 +174,29 @@ pt:
#modal telling users that they need to pass a training before booking a machine
required_training_modal:
to_book_MACHINE_requires_TRAINING_html: "Para agendar \"{MACHINE}\" você deve completar o treinamento <strong>{TRAINING}</strong>."
training_or_training_html: "</strong> or the training <strong>"
training_or_training_html: "</strong> ou o treinamento <strong>"
enroll_now: "Inscrever-se no treinamento"
no_enroll_for_now: "Não desejo me inscrever agora"
close: "Fechar"
propose_packs_modal:
available_packs: "Prepaid packs available"
packs_proposed: "You can buy a prepaid pack of hours for this machine. These packs allows you to benefit from volume discounts."
no_thanks: "No, thanks"
pack_DURATION: "{DURATION} hours"
buy_this_pack: "Buy this pack"
pack_bought_success: "You have successfully bought this pack of prepaid-hours. Your invoice will ba available soon from your dashboard."
validity: "Usable for {COUNT} {PERIODS}"
available_packs: "Pacotes pré-pagos disponíveis"
packs_proposed: "Você pode comprar um pacote de horas pré-pago para esta máquina. Estes pacotes permitem que você se beneficie de descontos por volume."
no_thanks: "Não, obrigado"
pack_DURATION: "{DURATION} horas"
buy_this_pack: "Comprar este pacote"
pack_bought_success: "Você comprou com sucesso este pacote de horas pré-pagas. Sua fatura estará disponível em breve no seu dashboard."
validity: "Usável para {COUNT} {PERIODS}"
period:
day: "{COUNT, plural, one{day} other{days}}"
week: "{COUNT, plural, one{week} other{weeks}}"
month: "{COUNT, plural, one{month} other{months}}"
year: "{COUNT, plural, one{year} other{years}}"
day: "{COUNT, plural, one{dia} other{dias}}"
week: "{COUNT, plural, =1 {semana} other {semanas}}"
month: "{COUNT, plural, =1 {mês} other {meses}}"
year: "{COUNT, plural, one{ano} other{anos}}"
packs_summary:
prepaid_hours: "Prepaid hours"
remaining_HOURS: "You have {HOURS} prepaid hours remaining for this {ITEM, select, Machine{machine} Space{space} other{}}."
no_hours: "You don't have any prepaid hours for this {ITEM, select, Machine{machine} Space{space} other{}}."
buy_a_new_pack: "Buy a new pack"
unable_to_use_pack_for_subsription_is_expired: "You must have a valid subscription to use your remaining hours."
prepaid_hours: "Horas pré-pagas"
remaining_HOURS: "Você tem {HOURS} horas pré-pagas restantes para esta {ITEM, select, Machine{máquina} Space{espaço} other{}}."
no_hours: "Você não tem horas pré-pagas para {ITEM, select, Machine{esta máquina} Space{este espaço} other{}}."
buy_a_new_pack: "Comprar um pacote novo"
unable_to_use_pack_for_subsription_is_expired: "Você precisa ter uma assinatura válida para usar as suas horas restantes."
#book a training
trainings_reserve:
trainings_planning: "Planos de treinamento"

View File

@ -19,7 +19,7 @@ pt:
my_trainings: "Meus Treinamentos"
my_events: "Meus Eventos"
my_invoices: "Minhas Contas"
my_payment_schedules: "My payment schedules"
my_payment_schedules: "Meus agendamentos de pagamento"
my_wallet: "Minha Carteira"
#contextual help
help: "Ajuda"
@ -84,8 +84,8 @@ pt:
birth_date_is_required: "Data de nascimento é obrigatório."
phone_number: "Número de telefone"
phone_number_is_required: "Número de telefone é obrigatório."
address: "Address"
address_is_required: "Address is required"
address: "Endereço"
address_is_required: "O endereço é necessário"
i_authorize_Fablab_users_registered_on_the_site_to_contact_me: "Eu autorizo usuários do FabLab, registrados no site, a entrarem em contato comigo"
i_accept_to_receive_information_from_the_fablab: "Eu aceito receber informações do FabLab"
i_ve_read_and_i_accept_: "Eu li e aceito"
@ -266,10 +266,10 @@ pt:
an_error_prevented_to_change_the_user_s_group: "Um erro impediu que o grupo de usuários fosse alterado com sucesso."
plans_filter:
i_am: "Eu sou"
select_group: "select a group"
i_want_duration: "I want to subscribe for"
all_durations: "All durations"
select_duration: "select a duration"
select_group: "seleccionar Grupo"
i_want_duration: "Quero me inscrever em"
all_durations: "Todas as durações"
select_duration: "selecione uma duração"
#Fablab's events list
events_list:
the_fablab_s_events: "Eventos do Fablab"

View File

@ -22,7 +22,7 @@ pt:
you_will_lose_any_unsaved_modification_if_you_quit_this_page: "Você irá perder todas as modificações não salvas se sair desta página"
you_will_lose_any_unsaved_modification_if_you_reload_this_page: "Você irá perder todas as modificações não salvas se recarregar desta página"
payment_card_error: "Ocorreu um problema com o seu cartão de crédito:"
payment_card_declined: "Your card was declined."
payment_card_declined: "O seu cartão foi recusado."
#user edition form
user:
man: "Homem"
@ -123,20 +123,20 @@ pt:
online_payment: "Pagamento Online"
i_have_read_and_accept_: "Eu li e aceito "
_the_general_terms_and_conditions: "os termos e condições."
payment_schedule_html: "<p>You're about to subscribe to a payment schedule of {DEADLINES} months.</p><p>By paying this bill, you agree to send instructions to the financial institution that issue your card, to take payments from your card account, for the whole duration of this subscription. This imply that your card data are saved by {GATEWAY} and a series of payments will be initiated on your behalf, conforming to the payment schedule previously shown.</p>"
confirm_payment_of_: "Pay: {AMOUNT}"
validate: "Validate"
payment_schedule_html: "<p>Você está prestes a assinar um plano de pagamento de {DEADLINES} meses.</p><p>Ao pagar esta conta, você concorda em enviar instruções para a instituição financeira que emite o seu cartão, para obter pagamentos de sua conta de cartão por toda a duração dessa assinatura. Isso implica que os dados do seu cartão são salvos por {GATEWAY} e uma série de pagamentos serão iniciados em seu nome, conforme o cronograma de pagamento mostrado anteriormente.</p>"
confirm_payment_of_: "Pagamento: {AMOUNT}"
validate: "Validado"
#dialog of on site payment for reservations
valid_reservation_modal:
booking_confirmation: "Confirmação de reserva"
here_is_the_summary_of_the_slots_to_book_for_the_current_user: "Aqui está um resumo das reservas para o usuário atual:"
subscription_confirmation: "Inscrição confirmada"
here_is_the_subscription_summary: "Here is the subscription summary:"
payment_method: "Payment method"
method_card: "Online by card"
method_check: "By check"
card_collection_info: "By validating, you'll be prompted for the member's card number. This card will be automatically charged at the deadlines."
check_collection_info: "By validating, you confirm that you have {DEADLINES} checks, allowing you to collect all the monthly payments."
here_is_the_subscription_summary: "Aqui está o resumo da assinatura:"
payment_method: "Método de pagamento"
method_card: "Online por cartão"
method_check: "Por verificação"
card_collection_info: "Ao confirmar, será solicitado o número do cartão do membro. Este cartão será cobrado automaticamente nos prazos determinados."
check_collection_info: "Ao confirmar, você confirma que tem {DEADLINES} checks, permitindo o desconto de todos os pagamentos mensalmente."
#event edition form
event:
title: "Título"
@ -161,9 +161,9 @@ pt:
0_equal_free: "0 = grátis"
tickets_available: "Tickets disponíveis"
event_themes: "Temas do evento"
select_theme: "Pick up a theme..."
select_theme: "Usar um tema..."
age_range: "Faixa etária"
add_price: "Add a price"
add_price: "Adicionar preço"
#subscription plan edition form
plan:
general_information: "Informação geral"
@ -179,7 +179,7 @@ pt:
all: "Todos"
transversal_all_groups: "Transversal (todos os grupos)"
group_is_required: "Grupo é obrigatório."
category: "Category"
category: "Categoria"
number_of_periods: "Número de períodos"
number_of_periods_is_required: "Número de períodos é obrigatório."
period: "Período"
@ -189,17 +189,17 @@ pt:
period_is_required: "Período é obrigatório."
subscription_price: "Preço de inscrição"
price_is_required: "Preço é obrigatório."
edit_amount_info: "Please note that if you change the price of this plan, the new price will only apply to new subscribers. Current subscriptions will stay unchanged, even those with running payment schedule."
edit_amount_info: "Por favor, note que se você alterar o preço deste plano, o novo preço só será aplicado aos novos assinantes. As assinaturas atuais permanecerão inalteradas, mesmo as com cronograma de pagamentos em execução."
visual_prominence_of_the_subscription: "Proeminência visual da assinatura"
on_the_subscriptions_page_the_most_prominent_subscriptions_will_be_placed_at_the_top_of_the_list: "Na página de inscrições a inscrição mais relevante aparecerá no topo da lista."
an_evelated_number_means_a_higher_prominence: "Um número elevado significa uma maior relevância."
rolling_subscription: "Assinatura contínua?"
a_rolling_subscription_will_begin_the_day_of_the_first_training: "A assinatura contínua começarrá no primeiro dia de treinamento."
otherwise_it_will_begin_as_soon_as_it_is_bought: "Caso contrário, elecomeçará assim que for comprado."
monthly_payment: "Monthly payment?"
monthly_payment_info: "If monthly payment is enabled, the members will be able to choose between a one-time payment or a payment schedule staged each months."
description: "Description"
type_a_short_description: "Type a short description"
monthly_payment: "Pagamento mensal?"
monthly_payment_info: "Se o pagamento mensal estiver ativado, os membros poderão escolher entre um pagamento único ou um pagamento recorrente."
description: "Descrição"
type_a_short_description: "Adicione uma descrição curta"
information_sheet: "Folha de informação"
attach_an_information_sheet: "Anexar folha de informação"
notified_partner: "Parceiro notificado"
@ -330,16 +330,16 @@ pt:
warning_uneditable_credit: "Aviso: uma vez validado, o valor creditado não será mais editado."
wallet_info:
you_have_AMOUNT_in_wallet: "Você tem {AMOUNT} em sua carteira"
wallet_pay_ITEM: "You pay your {ITEM} directly."
item_reservation: "reservation"
item_subscription: "subscription"
item_first_deadline: "first deadline"
item_other: "purchase"
credit_AMOUNT_for_pay_ITEM: "You still have {AMOUNT} to pay to validate your {ITEM}."
client_have_AMOUNT_in_wallet: "The member has {AMOUNT} on his wallet"
client_wallet_pay_ITEM: "The member can directly pay his {ITEM}."
client_credit_AMOUNT_for_pay_ITEM: "{AMOUNT} are remaining to pay to validate the {ITEM}"
other_deadlines_no_wallet: "Warning: the remaining wallet balance cannot be used for the next deadlines."
wallet_pay_ITEM: "Pagar {ITEM} diretamente."
item_reservation: "reservas"
item_subscription: "assinatura"
item_first_deadline: "primeiro período"
item_other: "comprar"
credit_AMOUNT_for_pay_ITEM: "Você ainda tem {AMOUNT} para pagar para poder validar seu {ITEM}."
client_have_AMOUNT_in_wallet: "O membro tem {AMOUNT} em sua carteira"
client_wallet_pay_ITEM: "O membro pode pagar diretamente seu {ITEM}."
client_credit_AMOUNT_for_pay_ITEM: "{AMOUNT} restam a ser pago {ITEM}"
other_deadlines_no_wallet: "Atenção: o saldo restante na carteira não pode ser usado para os próximos prazos."
#coupon (promotional) (creation/edition form)
coupon:
name: "Nome"
@ -357,8 +357,8 @@ pt:
validity_per_user: "Validar pelo usuário"
once: "Apenas uma vez"
forever: "Cada uso"
warn_validity_once: "Please note that when this coupon will be used with a payment schedule, the discount will be applied to the first deadline only."
warn_validity_forever: "Please note that when this coupon will be used with a payment schedule, the discount will be applied to each deadlines."
warn_validity_once: "Observe que quando este cupom será usado com um calendário de pagamentos, o desconto será aplicado para o primeiro agendamento apenas."
warn_validity_forever: "Por favor, note que quando este cupom for usado com um calendário de pagamentos, o desconto será aplicado a cada prazo."
validity_per_user_is_required: "Validação por usuário é obrigatório."
valid_until: "Valido até (incluso)"
leave_empty_for_no_limit: "Não especifique nenhum limite deixando o campo vazio."
@ -371,7 +371,7 @@ pt:
code_: "Código:"
the_coupon_has_been_applied_you_get_PERCENT_discount: "O cupom foi aplicado. Você recebeu {PERCENT}% de desconto."
the_coupon_has_been_applied_you_get_AMOUNT_CURRENCY: "O cupom foi aplicado. Você recebeu um desconto de {AMOUNT} {CURRENCY}."
coupon_validity_once: "This coupon is valid only once. In case of payment schedule, only for the first deadline."
coupon_validity_once: "Este cupom é válido apenas uma vez. Em caso de calendário de pagamento, apenas para o primeiro prazo."
unable_to_apply_the_coupon_because_disabled: "Não é possível aplicar o cupom: este código foi desabilitado."
unable_to_apply_the_coupon_because_expired: "Não é possível aplicar o cupom: este código expirou."
unable_to_apply_the_coupon_because_sold_out: "Não é possível aplicar o cupom: este código atingiu sua quota."
@ -399,7 +399,7 @@ pt:
cart:
summary: "Sumário"
select_one_or_more_slots_in_the_calendar: "Selecionar um {SINGLE, select, true{slot} other{ou mais slots}} no calendário"
select_a_plan: "Select a plan here"
select_a_plan: "Selecione um plano aqui"
you_ve_just_selected_the_slot: "Você selecionou apenas o slot:"
datetime_to_time: "{START_DATETIME} até {END_TIME}" #eg: Thursday, September 4, 1986 8:30 PM to 10:00 PM
cost_of_TYPE: "Custo de {TYPE, select, Machine{máquina slot} Training{o treinamento} Space{espaço slot} other{o elemento}}"
@ -410,15 +410,15 @@ pt:
view_our_subscriptions: "Ver suas inscrições"
or: "ou"
cost_of_the_subscription: "Custo da inscrição"
subscription_price: "Subscription price"
subscription_price: "Preço da assinatura"
you_ve_just_selected_a_subscription_html: "Você acabou de selecionar uma <strong>assinatura</strong>:"
monthly_payment: "Monthly payment"
your_payment_schedule: "Your payment schedule"
monthly_payment_NUMBER: "{NUMBER}{NUMBER, plural, =1{st} =2{nd} =3{rd} other{th}} monthly payment: "
NUMBER_monthly_payment_of_AMOUNT: "{NUMBER} monthly {NUMBER, plural, =1{payment} other{payments}} of {AMOUNT}"
first_debit: "First debit on the day of the order."
debit: "Debit on the day of the order."
view_full_schedule: "View the complete payment schedule"
monthly_payment: "Pagamento mensal"
your_payment_schedule: "Sua agenda de pagamento"
monthly_payment_NUMBER: "{NUMBER}{NUMBER, plural, one {} =1{º} =2{º} =3{º} other{º}} pagamento mensal: "
NUMBER_monthly_payment_of_AMOUNT: "{NUMBER} meses de {NUMBER, plural, one {} =1{pagamento} other{pagamentos}} de {AMOUNT}"
first_debit: "Primeiro débito no dia do pedido."
debit: "Primeiro débito no dia do pedido."
view_full_schedule: "Ver as agendas de pagamentos completa"
confirm_and_pay: "Confirmar e pagar"
you_have_settled_the_following_TYPE: "Você liquidou o seguinte {TYPE, select, Machine{slots de máquina} Training{training} other{elements}}:"
you_have_settled_a_: "Você tem liquidado:"
@ -442,7 +442,7 @@ pt:
do_you_really_want_to_cancel_this_reservation_html: "<p>Você realmente quer cancelar essa reserva?</p><p>Warning: if this reservation was made free of charge, as part of a subscription, the credits used will not be re-credited.</p>"
reservation_was_cancelled_successfully: "Reserva a foi cancelada com sucesso."
cancellation_failed: "Cancelamento falhou."
confirm_payment_of_html: "{METHOD, select, card{Pay by card} other{Pay on site}}: {AMOUNT}"
confirm_payment_of_html: "{METHOD, select, card{Pague com cartão} other{Pague pelo site}}: {AMOUNT}"
a_problem_occurred_during_the_payment_process_please_try_again_later: "Um problema ocorreu durante o processo de pagamento. Por favor tente novamente mais tarde."
none: "Vazio"
online_payment_disabled: "O pagamento online não está disponível. Entre em contato diretamente com a recepção do FabLab."
@ -474,46 +474,46 @@ pt:
guide: "Abrir manual do usuário"
#2nd factor authentication for card payments
stripe_confirm:
pending: "Pending for action..."
success: "Thank you, your card setup is complete. The payment will be proceeded shortly."
pending: "Pendente de ação..."
success: "Obrigado, a configuração do seu cartão está concluída. O pagamento será processado em breve."
#the summary table of all payment schedules
schedules_table:
schedule_num: "Schedule #"
schedule_num: "Agendamento #"
date: "Data"
price: "Preço"
customer: "Cliente"
deadline: "Deadline"
deadline: "Prazo"
amount: "Montante"
state: "State"
state: "Estado"
download: "Baixar"
state_new: "Not yet due"
state_pending: "Waiting for the cashing of the check"
state_requires_payment_method: "The credit card must be updated"
state_requires_action: "Action required"
state_paid: "Paid"
state_error: "Error"
state_canceled: "Canceled"
method_card: "by card"
method_check: "by check"
state_new: "Ainda não vencido"
state_pending: "Esperando a validação manual"
state_requires_payment_method: "O cartão de crédito deve ser atualizado"
state_requires_action: "Ação necessária"
state_paid: "Pago"
state_error: "Erro"
state_canceled: "Cancelado"
method_card: "por cartão"
method_check: "por verificação"
confirm_payment: "Confirmar pagamento"
solve: "Resolver"
update_card: "Update the card"
confirm_check_cashing: "Confirm the cashing of the check"
confirm_check_cashing_body: "You must cash a check of {AMOUNT} for the deadline of {DATE}. By confirming the cashing of the check, an invoice will be generated for this due date."
confirm_button: "Confirm"
resolve_action: "Resolve the action"
update_card: "Atualizar o cartão"
confirm_check_cashing: "Confirme o desconto de verificação"
confirm_check_cashing_body: "Você deve pagar uma verificação de {AMOUNT} pelo prazo de {DATE}. Ao confirmar o pagamento, uma fatura será gerada para este prazo."
confirm_button: "Confirmar"
resolve_action: "Resolver a ação"
ok_button: "OK"
cancel_subscription: "Cancel the subscription"
confirm_cancel_subscription: "You're about to cancel this payment schedule and the related subscription. Are you sure?"
please_ask_reception: "For any questions, please contact the FabLab's reception."
cancel_subscription: "Cancelar assinatura"
confirm_cancel_subscription: "Você está prestes a cancelar esta agenda de pagamento e a assinatura relacionada. Tem certeza?"
please_ask_reception: "Para qualquer dúvida, entre em contato com a recepção do FabLab."
payment_modal:
online_payment_disabled: "Online payment is not available. Please contact the FabLab's reception directly."
unexpected_error: "An error occurred. Please report this issue to the Fab-Manager's team."
online_payment_disabled: "O pagamento online não está disponível. Entre em contato diretamente com a recepção do FabLab."
unexpected_error: "Ocorreu um erro. Por favor, reporte este problema à equipe do FabLab."
update_card_modal:
unexpected_error: "An error occurred. Please report this issue to the Fab-Manager's team."
unexpected_error: "Ocorreu um erro. Por favor, reporte este problema à equipe do FabLab."
stripe_card_update_modal:
update_card: "Update the card"
validate_button: "Validate the new card"
update_card: "Atualizar o cartão"
validate_button: "Verificar o novo cartão"
payzen_card_update_modal:
update_card: "Update the card"
validate_button: "Validate the new card"
update_card: "Atualizar o cartão"
validate_button: "Verificar o novo cartão"

View File

@ -535,3 +535,4 @@ en:
renew_pack_threshold: "Threshold for packs renewal"
pack_only_for_subscription: "Restrict packs for subscribers"
overlapping_categories: "Categories for overlapping booking prevention"
extended_prices_in_same_day: "Extended prices in the same day"

View File

@ -100,7 +100,7 @@ pt:
subject: "A conta de usuário foi criada"
body:
new_account_created: "Uma nova conta de usuário foi criada no site:"
user_of_group_html: "The user has registered in the group <strong>%{GROUP}</strong>"
user_of_group_html: "O usuário se registrou no grupo <strong>%{GROUP}</strong>"
account_for_organization: "Esta conta gerencia uma organização:"
notify_admin_subscribed_plan:
subject: "Uma assinatura foi comprada"
@ -273,7 +273,7 @@ pt:
subject: "Um reembolso foi gerado"
body:
refund_created: "Um reembolso de %{AMOUNT} foi gerado na fatura %{INVOICE} do usuário %{USER}"
wallet_refund_created: "A refund of %{AMOUNT} has been generated for the credit of the wallet of user %{USER}"
wallet_refund_created: "Foi gerado um reembolso de %{AMOUNT} para o crédito da carteira de usuário %{USER}"
download: "Clique aqui para fazer o download da fatura de reembolso"
notify_admins_role_update:
subject: "O papel de um usuário foi alterado"
@ -290,29 +290,29 @@ pt:
body:
objects_sync: "Todos os membros, cupons, máquinas, treinamentos, espaços e planos foram sincronizados com sucesso no Stripe."
notify_member_payment_schedule_ready:
subject: "Your payment schedule"
subject: "Sua agenda de pagamentos"
body:
please_find_attached_html: "Please find attached your payment schedule, issued on {DATE}, with an amount of {AMOUNT} concerning your {TYPE, select, Reservation{reservation} other{subscription}}." #messageFormat interpolation
schedule_in_your_dashboard_html: "You can find this payment schedule at any time from %{DASHBOARD} on the Fab Lab's website."
your_dashboard: "your dashboard"
please_find_attached_html: "Por favor, anexe a sua agenda de pagamento, emitida em {DATE}, com um valor de {AMOUNT} relativo à sua reserva {TYPE, select, Reservation{reserva} other{assinatura}}." #messageFormat interpolation
schedule_in_your_dashboard_html: "Você pode encontrar a agenda de pagamento a qualquer momento a partir de %{DASHBOARD} no site do Fab Lab."
your_dashboard: "seu dashboard"
notify_admin_payment_schedule_failed:
subject: "[URGENT] Card debit failure"
subject: "[URGENT] Falha no débito do cartão"
body:
remember: "In accordance with the %{REFERENCE} payment schedule, a debit by card of %{AMOUNT} was scheduled on %{DATE}."
error: "Unfortunately, this card debit was unable to complete successfully."
action: "Please contact the member as soon as possible, then go to the payment schedule management interface to resolve the problem. After about 24 hours, the card subscription will be cancelled."
remember: "De acordo com o calendário de pagamento de %{REFERENCE}, um débito por cartão de %{AMOUNT} foi agendado em %{DATE}."
error: "Infelizmente, não foi possível completar o débito no cartão com sucesso."
action: "Por favor, entre em contato com o membro assim que possível, depois vá à interface de gestão de horários de pagamento para resolver o problema. Após cerca de 24 horas, a assinatura do cartão será cancelada."
notify_member_payment_schedule_failed:
subject: "[URGENT] Card debit failure"
subject: "[URGENT] Falha no débito do cartão"
body:
remember: "In accordance with your %{REFERENCE} payment schedule, a debit by card of %{AMOUNT} was scheduled on %{DATE}."
error: "Unfortunately, this card debit was unable to complete successfully."
action_html: "Please check %{DASHBOARD} or contact a manager before 24 hours, otherwise your subscription may be interrupted."
your_dashboard: "your dashboard"
remember: "De acordo com a sua agenda de pagamentos %{REFERENCE}, um débito por cartão de %{AMOUNT} foi agendado para %{DATE}."
error: "Infelizmente, não foi possível completar o débito no cartão com sucesso."
action_html: "Por favor, verifique %{DASHBOARD} ou entre em contato com um gerente antes de 24 horas, caso contrário sua assinatura pode ser interrompida."
your_dashboard: "seu dashboard"
notify_admin_payment_schedule_check_deadline:
subject: "Payment deadline"
subject: "Prazo de pagamento"
body:
remember: "In accordance with the %{REFERENCE} payment schedule, %{AMOUNT} was due to be debited on %{DATE}."
date: "This is a reminder to cash the scheduled check as soon as possible."
confirm: "Do not forget to confirm the receipt in your payment schedule management interface, so that the corresponding invoice will be generated."
remember: "De acordo com a agenda de pagamento %{REFERENCE}, %{AMOUNT} deveria ser debitado em %{DATE}."
date: "Este é um lembrete para descontar o pagamento programado o mais rápido possível."
confirm: "Não se esqueça de confirmar o recibo na interface de gestão de agenda de pagamento, para que a fatura correspondente seja gerada."
shared:
hello: "Olá %{user_name}"

View File

@ -39,7 +39,7 @@ pt:
must_be_in_the_past: "O período deve ser estritamente anterior à data de hoje."
apipie:
api_documentation: "Documentação da API"
code: "HTTP code"
code: "Código HTTP"
#error messages when importing an account from a SSO
omniauth:
email_already_linked_to_another_account_please_input_your_authentication_code: "E-mail \"%{OLD_MAIL}\" já está vinculado a outra conta, insira seu código de autenticação."
@ -112,21 +112,21 @@ pt:
subscription_of_NAME_extended_starting_from_STARTDATE_until_ENDDATE: "Assinatura de %{NAME} estendida (dias livres) a partir de% STARTDATE até %{ENDDATE}"
and: 'e'
invoice_text_example: "Nossa associação não está sujeita a IVA"
error_invoice: "Erroneous invoice. The items below ware not booked. Please contact the FabLab for a refund."
prepaid_pack: "Prepaid pack of hours"
pack_item: "Pack of %{COUNT} hours for the %{ITEM}"
error_invoice: "Esta fatura está incorreta. Os itens abaixo que não foram reservados. Por favor contate o FabLab para um reembolso."
prepaid_pack: "Pacote de horas pré-pago"
pack_item: "Pacote de %{COUNT} horas para a %{ITEM}"
#PDF payment schedule generation
payment_schedules:
schedule_reference: "Payment schedule reference: %{REF}"
schedule_issued_on_DATE: "Schedule issued on %{DATE}"
object: "Object: Payment schedule for %{ITEM}"
subscription_of_NAME_for_DURATION_starting_from_DATE: "the subscription of %{NAME} for %{DURATION} starting from %{DATE}"
deadlines: "Table of your deadlines"
deadline_date: "Payment date"
deadline_amount: "Amount including tax"
total_amount: "Total amount"
settlement_by_METHOD: "Debits will be made by {METHOD, select, card{card} other{check}} for each deadlines."
settlement_by_wallet: "%{AMOUNT} will be debited from your wallet to settle the first deadline."
schedule_reference: "Agendamento de pagamento: %{REF}"
schedule_issued_on_DATE: "Cronograma emitido em %{DATE}"
object: "Objeto: Agendamento de pagamento para %{ITEM}"
subscription_of_NAME_for_DURATION_starting_from_DATE: "assinatura de %{NAME} com %{DURATION} começando em %{DATE}"
deadlines: "Tabela de prazos"
deadline_date: "Data de pagamento"
deadline_amount: "Valor incluindo impostos"
total_amount: "Valor total"
settlement_by_METHOD: "Os débitos serão feitos por {METHOD, select, card{cartão} other{verificação}} para cada prazo."
settlement_by_wallet: "%{AMOUNT} será debitado da sua carteira fixando o primeiro prazo."
#CVS accounting export (columns headers)
accounting_export:
journal_code: "Código do diário"
@ -359,13 +359,13 @@ pt:
notify_admin_objects_stripe_sync:
all_objects_sync: "Todos os dados foram sincronizados com sucesso no Stripe."
notify_user_when_payment_schedule_ready:
your_schedule_is_ready_html: "Your payment schedule #%{REFERENCE}, of %{AMOUNT}, is ready. <a href='api/payment_schedules/%{SCHEDULE_ID}/download' target='_blank'>Click here to download</a>."
your_schedule_is_ready_html: "Sua fatura #%{REFERENCE}, de %{AMOUNT}, está pronta. <a href='api/invoices/%{SCHEDULE_ID}/download' target='_blank'>Clique aqui para fazer o download</a>."
notify_admin_payment_schedule_failed:
schedule_failed: "Failed card debit for the %{DATE} deadline, for schedule %{REFERENCE}"
schedule_failed: "Falha no débito para a data limite de %{DATE} para agendamento %{REFERENCE}"
notify_member_payment_schedule_failed:
schedule_failed: "Failed card debit for the %{DATE} deadline, for your schedule %{REFERENCE}"
schedule_failed: "Falha no débito para o período %{DATE} de agendamento %{REFERENCE}"
notify_admin_payment_schedule_check_deadline:
schedule_deadline: "You must cash the check for the %{DATE} deadline, for schedule %{REFERENCE}"
schedule_deadline: "Você deve realizar a verificação para a data limite de %{DATE} para agendar %{REFERENCE}"
#statistics tools for admins
statistics:
subscriptions: "Assinaturas"
@ -417,121 +417,121 @@ pt:
#name of the user's group for administrators
admins: 'Administradores'
cart_items:
free_extension: "Free extension of a subscription, until %{DATE}"
free_extension: "Extensão gratuita de uma assinatura, até %{DATE}"
statistic_profile:
birthday_in_past: "The date of birth must be in the past"
birthday_in_past: "A data de nascimento deve estar no passado"
settings:
locked_setting: "the setting is locked."
about_title: "\"About\" page title"
about_body: "\"About\" page content"
about_contacts: "\"About\" page contacts"
privacy_draft: "Privacy policy draft"
privacy_body: "Privacy policy"
privacy_dpo: "Data protection officer address"
twitter_name: "Twitter feed name"
home_blogpost: "Homepage's brief"
machine_explications_alert: "Explanation message on the machine reservation page"
training_explications_alert: "Explanation message on the training reservation page"
training_information_message: "Information message on the machine reservation page"
subscription_explications_alert: "Explanation message on the subscription page"
invoice_logo: "Invoices' logo"
invoice_reference: "Invoice's reference"
invoice_code-active: "Activation of the invoices' code"
invoice_code-value: "Invoices' code"
invoice_order-nb: "Invoice's order number"
invoice_VAT-active: "Activation of the VAT"
invoice_VAT-rate: "VAT rate"
invoice_text: "Invoices' text"
invoice_legals: "Invoices' legal information"
booking_window_start: "Opening time"
booking_window_end: "Closing time"
booking_move_enable: "Activation of reservations moving"
booking_move_delay: "Preventive delay before any reservation move"
booking_cancel_enable: "Activation of reservations cancelling"
booking_cancel_delay: "Preventive delay before any reservation cancellation"
main_color: "Main colour"
secondary_color: "Secondary colour"
fablab_name: "Fablab's name"
name_genre: "Title concordance"
reminder_enable: "Activation of reservations reminding"
reminder_delay: "Delay before sending the reminder"
event_explications_alert: "Explanation message on the event reservation page"
space_explications_alert: "Explanation message on the space reservation page"
visibility_yearly: "Maximum visibility for annual subscribers"
visibility_others: "Maximum visibility for other members"
display_name_enable: "Display names in the calendar"
machines_sort_by: "Machines display order"
accounting_journal_code: "Journal code"
accounting_card_client_code: "Card clients code"
accounting_card_client_label: "Card clients label"
accounting_wallet_client_code: "Wallet clients code"
accounting_wallet_client_label: "Wallet clients label"
accounting_other_client_code: "Other means client code"
accounting_other_client_label: "Other means client label"
accounting_wallet_code: "Wallet code"
accounting_wallet_label: "Wallet label"
locked_setting: "a configuração está bloqueada."
about_title: "\"Sobre\" título da página"
about_body: "\"Sobre\" conteúdo da página"
about_contacts: "\"Sobre\" página de contatos"
privacy_draft: "Rascunho da política de privacidade"
privacy_body: "Política de privacidade"
privacy_dpo: "Dados sobre proteção de dados"
twitter_name: "Twitter username"
home_blogpost: "Resumo da página inicial"
machine_explications_alert: "Mensagem explicativa na página de reserva da máquina"
training_explications_alert: "Mensagem de explicação na página de reservas de treinamento"
training_information_message: "Mensagem de informação na página de reserva da máquina"
subscription_explications_alert: "Mensagem de explicação na página de inscrição"
invoice_logo: "Logotipo da fatura"
invoice_reference: "Referência da fatura"
invoice_code-active: "Ativação do código das fatura"
invoice_code-value: "Código da fatura"
invoice_order-nb: "Número de serviço da fatura"
invoice_VAT-active: "Ativação do imposto"
invoice_VAT-rate: "Taxa de imposto"
invoice_text: "Texto das faturas"
invoice_legals: "Informação legal das faturas"
booking_window_start: "Horário de abertura"
booking_window_end: "Horário de fechamento"
booking_move_enable: "Ativação da movimentação de reservas"
booking_move_delay: "Atraso preventivo antes de qualquer reserva se mover"
booking_cancel_enable: "Ativação do cancelamento de reservas"
booking_cancel_delay: "Tempo prévio ara cancelar uma reserva"
main_color: "Cor principal"
secondary_color: "Cor secundária"
fablab_name: "Nome do FabLab"
name_genre: "Concordância do título"
reminder_enable: "Ativação da movimentação de reservas"
reminder_delay: "Tempo antes de enviar o lembrete"
event_explications_alert: "Mensagem de explicação do evento na página de reserva"
space_explications_alert: "Mensagem de explicação na página de reserva de espaço"
visibility_yearly: "Visibilidade máxima para assinantes anuais"
visibility_others: "Visibilidade máxima para outros membros"
display_name_enable: "Exibir os nomes no calendário"
machines_sort_by: "Ordem de exibição das máquinas"
accounting_journal_code: "Código do diário"
accounting_card_client_code: "Código do cartão do cliente"
accounting_card_client_label: "Rótulo do cartão dos clientes"
accounting_wallet_client_code: "Código das carteiras dos clientes"
accounting_wallet_client_label: "Rótulo das carteiras dos clientes"
accounting_other_client_code: "Outros meios do código do cliente"
accounting_other_client_label: "Outro tipo de rótulo do cliente"
accounting_wallet_code: "Código da carteira"
accounting_wallet_label: "Rótulo da carteira"
accounting_VAT_code: "VAT code"
accounting_VAT_label: "VAT label"
accounting_subscription_code: "Subscriptions code"
accounting_subscription_label: "Subscriptions label"
accounting_Machine_code: "Machines code"
accounting_Machine_label: "Machines label"
accounting_Training_code: "Trainings code"
accounting_Training_label: "Trainings label"
accounting_Event_code: "Events code"
accounting_Event_label: "Events label"
accounting_Space_code: "Spaces code"
accounting_Space_label: "Spaces label"
hub_last_version: "Last Fab-manager's version"
hub_public_key: "Instance public key"
fab_analytics: "Fab Analytics"
link_name: "Link title to the \"About\" page"
home_content: "The home page"
home_css: "Stylesheet of the home page"
origin: "Instance URL"
uuid: "Instance ID"
phone_required: "Phone required?"
tracking_id: "Tracking ID"
book_overlapping_slots: "Book overlapping slots"
slot_duration: "Default duration of booking slots"
events_in_calendar: "Display events in the calendar"
spaces_module: "Spaces module"
plans_module: "Plans modules"
invoicing_module: "Invoicing module"
facebook_app_id: "Facebook App ID"
twitter_analytics: "Twitter analytics account"
recaptcha_site_key: "reCAPTCHA Site Key"
recaptcha_secret_key: "reCAPTCHA Secret Key"
feature_tour_display: "Feature tour display mode"
email_from: "Expeditor's address"
accounting_VAT_label: "Etiqueta de imposto"
accounting_subscription_code: "Código das assinaturas"
accounting_subscription_label: "Rótulo das assinaturas"
accounting_Machine_code: "Código das máquinas"
accounting_Machine_label: "Rótulo das máquinas"
accounting_Training_code: "Código de treinamentos"
accounting_Training_label: "Rótulo de treinamentos"
accounting_Event_code: "Código de eventos"
accounting_Event_label: "Rótulo de eventos"
accounting_Space_code: "Código do espaço"
accounting_Space_label: "Rótulo de espaços"
hub_last_version: "Última versão do Fab-manager"
hub_public_key: "Instância de chave pública"
fab_analytics: "Estatísticas"
link_name: "Título do link para a página \"Sobre\""
home_content: "Página inicial"
home_css: "Stylesheet da página inicial"
origin: "Instância URL"
uuid: "ID da instância"
phone_required: "Telefone é obrigatório?"
tracking_id: "ID de rastreamento"
book_overlapping_slots: "Slots de agendamento sobrepostos"
slot_duration: "Duração padrão para os slots"
events_in_calendar: "Exibir os eventos no calendário"
spaces_module: "Módulo de espaços"
plans_module: "Módulo de planos"
invoicing_module: "Módulo de faturamento"
facebook_app_id: "ID de Utilizador do Facebook"
twitter_analytics: "Conta do Twitter analytics"
recaptcha_site_key: "chave do site reCAPTCHA"
recaptcha_secret_key: "chave secreta reCAPTCHA"
feature_tour_display: "Exibir tour de recursos"
email_from: "Endereço do expeditor"
disqus_shortname: "Disqus shortname"
allowed_cad_extensions: "Allowed CAD files extensions"
allowed_cad_mime_types: "Allowed CAD files MIME types"
allowed_cad_extensions: "Extensões CAD permitidas"
allowed_cad_mime_types: "Tipos MIME de arquivos CAD permitidos"
openlab_app_id: "OpenLab ID"
openlab_app_secret: "OpenLab secret"
openlab_default: "Default projects gallery view"
online_payment_module: "Online payments module"
stripe_public_key: "Stripe public key"
stripe_secret_key: "Stripe secret key"
stripe_currency: "Stripe currency"
invoice_prefix: "Invoices' files prefix"
confirmation_required: "Confirmation required"
wallet_module: "Wallet module"
statistics_module: "Statistics module"
upcoming_events_shown: "Display limit for upcoming events"
payment_schedule_prefix: "Payment schedule's files prefix"
trainings_module: "Trainings module"
address_required: "Address required"
accounting_Error_code: "Errors code"
accounting_Error_label: "Errors label"
payment_gateway: "Payment gateway"
openlab_default: "Visualização padrão da galeria de projetos"
online_payment_module: "Módulo de pagamento online"
stripe_public_key: "Chave pública do Stripe"
stripe_secret_key: "Chave Secreta de Api Stripe"
stripe_currency: "Moeda do Stripe"
invoice_prefix: "Prefixo dos arquivos das faturas"
confirmation_required: "Confirmação Obrigatória"
wallet_module: "Módulo de carteira"
statistics_module: "Módulo de estatísticas"
upcoming_events_shown: "Exibir limite para eventos futuros"
payment_schedule_prefix: "Prefixo de arquivos da agenda de pagamento"
trainings_module: "Módulo de treinamentos"
address_required: "Endereço é obrigatório"
accounting_Error_code: "Código do erro"
accounting_Error_label: "Rótulo do erro"
payment_gateway: "Gateway de pagamento"
payzen_username: "PayZen username"
payzen_password: "PayZen password"
payzen_password: "Senha PayZen"
payzen_endpoint: "PayZen API endpoint"
payzen_public_key: "PayZen client public key"
payzen_public_key: "Chave pública do cliente PayZen"
payzen_hmac: "PayZen HMAC-SHA-256 key"
payzen_currency: "PayZen currency"
public_agenda_module: "Public agenda module"
renew_pack_threshold: "Threshold for packs renewal"
pack_only_for_subscription: "Restrict packs for subscribers"
overlapping_categories: "Categories for overlapping booking prevention"
payzen_currency: "Moeda PayZen"
public_agenda_module: "Módulo da agenda pública"
renew_pack_threshold: "Limite para renovação de pacotes"
pack_only_for_subscription: "Restringir pacotes para assinantes"
overlapping_categories: "Categorias para prevenção de reservas sobrepostas"

View File

@ -75,7 +75,7 @@ Rails.application.routes.draw do
get 'pricing' => 'pricing#index'
put 'pricing' => 'pricing#update'
resources :prices, only: %i[index update] do
resources :prices, only: %i[create index update destroy] do
post 'compute', on: :collection
end
resources :prepaid_packs

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
# From this migration, we allow Prices to be configured by duration.
# For example, a Price for a 30-minute session could be configured to be twice the price of a 60-minute session.
# This is useful for things like "half-day" sessions, or full-day session when the price is different than the default hour-based price.
class AddDurationToPrice < ActiveRecord::Migration[5.2]
def change
add_column :prices, :duration, :integer, default: 60
end
end

View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2021_10_18_121822) do
ActiveRecord::Schema.define(version: 2021_12_20_143400) do
# These are extensions that must be enabled in order to support this database
enable_extension "fuzzystrmatch"
@ -492,6 +492,7 @@ ActiveRecord::Schema.define(version: 2021_10_18_121822) do
t.integer "weight"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "description"
end
create_table "plans", id: :serial, force: :cascade do |t|
@ -554,6 +555,7 @@ ActiveRecord::Schema.define(version: 2021_10_18_121822) do
t.integer "amount"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "duration", default: 60
t.index ["group_id"], name: "index_prices_on_group_id"
t.index ["plan_id"], name: "index_prices_on_plan_id"
t.index ["priceable_type", "priceable_id"], name: "index_prices_on_priceable_type_and_priceable_id"

View File

@ -905,6 +905,8 @@ unless Setting.find_by(name: 'overlapping_categories').try(:value)
Setting.set('overlapping_categories', 'training_reservations,machine_reservations,space_reservations,events_reservations')
end
Setting.set('extended_prices_in_same_day', false) unless Setting.find_by(name: 'extended_prices_in_same_day').try(:value)
if StatisticCustomAggregation.count.zero?
# available reservations hours for machines
machine_hours = StatisticType.find_by(key: 'hour', statistic_index_id: 2)

View File

@ -197,7 +197,7 @@ See [code.angularjs.org/i18n/angular-locale_*.js](https://code.angularjs.org/1.8
Configure the fullCalendar JS agenda library.
See [github.com/fullcalendar/fullcalendar/locale/*.js](https://github.com/fullcalendar/fullcalendar/tree/v3.10.2/locale) for a list of available locales. Default is **en-us**.
See [github.com/fullcalendar/fullcalendar/locale/*.js](https://github.com/fullcalendar/fullcalendar/tree/v3.10.2/locale) for a list of available locales. Default is **en**.
<a name="INTL_LOCALE"></a>
INTL_LOCALE

View File

@ -1,6 +1,6 @@
{
"name": "fab-manager",
"version": "5.1.13",
"version": "5.2.0",
"description": "Fab-manager is the FabLab management solution. It provides a comprehensive, web-based, open-source tool to simplify your administrative tasks and your marker's projects.",
"keywords": [
"fablab",

View File

@ -6,6 +6,7 @@ price_1:
priceable_id: 1
priceable_type: Machine
amount: 2400
duration: 60
created_at: 2016-04-04 14:11:34.242608000 Z
updated_at: 2016-04-04 14:11:34.242608000 Z
@ -16,6 +17,7 @@ price_2:
priceable_id: 1
priceable_type: Machine
amount: 5300
duration: 60
created_at: 2016-04-04 14:11:34.247363000 Z
updated_at: 2016-04-04 14:11:34.247363000 Z
@ -26,6 +28,7 @@ price_5:
priceable_id: 2
priceable_type: Machine
amount: 4200
duration: 60
created_at: 2016-04-04 14:11:34.290427000 Z
updated_at: 2016-04-04 14:11:34.290427000 Z
@ -36,6 +39,7 @@ price_6:
priceable_id: 2
priceable_type: Machine
amount: 1100
duration: 60
created_at: 2016-04-04 14:11:34.293603000 Z
updated_at: 2016-04-04 14:11:34.293603000 Z
@ -46,6 +50,7 @@ price_9:
priceable_id: 3
priceable_type: Machine
amount: 4100
duration: 60
created_at: 2016-04-04 14:11:34.320809000 Z
updated_at: 2016-04-04 14:11:34.320809000 Z
@ -56,6 +61,7 @@ price_10:
priceable_id: 3
priceable_type: Machine
amount: 5300
duration: 60
created_at: 2016-04-04 14:11:34.325274000 Z
updated_at: 2016-04-04 14:11:34.325274000 Z
@ -66,6 +72,7 @@ price_13:
priceable_id: 4
priceable_type: Machine
amount: 900
duration: 60
created_at: 2016-04-04 14:11:34.362313000 Z
updated_at: 2016-04-04 14:11:34.362313000 Z
@ -76,6 +83,7 @@ price_14:
priceable_id: 4
priceable_type: Machine
amount: 5100
duration: 60
created_at: 2016-04-04 14:11:34.366049000 Z
updated_at: 2016-04-04 14:11:34.366049000 Z
@ -86,6 +94,7 @@ price_17:
priceable_id: 5
priceable_type: Machine
amount: 1600
duration: 60
created_at: 2016-04-04 14:11:34.398206000 Z
updated_at: 2016-04-04 14:11:34.398206000 Z
@ -96,6 +105,7 @@ price_18:
priceable_id: 5
priceable_type: Machine
amount: 2000
duration: 60
created_at: 2016-04-04 14:11:34.407216000 Z
updated_at: 2016-04-04 14:11:34.407216000 Z
@ -106,6 +116,7 @@ price_21:
priceable_id: 6
priceable_type: Machine
amount: 3200
duration: 60
created_at: 2016-04-04 14:11:34.442054000 Z
updated_at: 2016-04-04 14:11:34.442054000 Z
@ -116,6 +127,7 @@ price_22:
priceable_id: 6
priceable_type: Machine
amount: 3400
duration: 60
created_at: 2016-04-04 14:11:34.445147000 Z
updated_at: 2016-04-04 14:11:34.445147000 Z
@ -126,6 +138,7 @@ price_25:
priceable_id: 1
priceable_type: Machine
amount: 1500
duration: 60
created_at: 2016-04-04 15:15:21.038387000 Z
updated_at: 2016-04-04 15:15:45.691674000 Z
@ -136,6 +149,7 @@ price_26:
priceable_id: 2
priceable_type: Machine
amount: 1500
duration: 60
created_at: 2016-04-04 15:15:21.048838000 Z
updated_at: 2016-04-04 15:15:45.693896000 Z
@ -146,6 +160,7 @@ price_27:
priceable_id: 3
priceable_type: Machine
amount: 2500
duration: 60
created_at: 2016-04-04 15:15:21.053412000 Z
updated_at: 2016-04-04 15:15:45.697794000 Z
@ -156,6 +171,7 @@ price_28:
priceable_id: 4
priceable_type: Machine
amount: 1500
duration: 60
created_at: 2016-04-04 15:15:21.057117000 Z
updated_at: 2016-04-04 15:15:45.700657000 Z
@ -166,6 +182,7 @@ price_29:
priceable_id: 5
priceable_type: Machine
amount: 1300
duration: 60
created_at: 2016-04-04 15:15:21.061171000 Z
updated_at: 2016-04-04 15:15:45.707564000 Z
@ -176,6 +193,7 @@ price_30:
priceable_id: 6
priceable_type: Machine
amount: 1500
duration: 60
created_at: 2016-04-04 15:15:21.065166000 Z
updated_at: 2016-04-04 15:15:45.710945000 Z
@ -186,6 +204,7 @@ price_31:
priceable_id: 1
priceable_type: Machine
amount: 1500
duration: 60
created_at: 2016-04-04 15:17:24.920457000 Z
updated_at: 2016-04-04 15:17:34.255229000 Z
@ -196,6 +215,7 @@ price_32:
priceable_id: 2
priceable_type: Machine
amount: 1500
duration: 60
created_at: 2016-04-04 15:17:24.926967000 Z
updated_at: 2016-04-04 15:17:34.257285000 Z
@ -206,6 +226,7 @@ price_33:
priceable_id: 3
priceable_type: Machine
amount: 2500
duration: 60
created_at: 2016-04-04 15:17:24.932723000 Z
updated_at: 2016-04-04 15:17:34.258741000 Z
@ -216,6 +237,7 @@ price_34:
priceable_id: 4
priceable_type: Machine
amount: 1500
duration: 60
created_at: 2016-04-04 15:17:24.937168000 Z
updated_at: 2016-04-04 15:17:34.260503000 Z
@ -226,6 +248,7 @@ price_35:
priceable_id: 5
priceable_type: Machine
amount: 1300
duration: 60
created_at: 2016-04-04 15:17:24.940520000 Z
updated_at: 2016-04-04 15:17:34.263627000 Z
@ -236,6 +259,7 @@ price_36:
priceable_id: 6
priceable_type: Machine
amount: 1500
duration: 60
created_at: 2016-04-04 15:17:24.944460000 Z
updated_at: 2016-04-04 15:17:34.267328000 Z
@ -246,6 +270,7 @@ price_37:
priceable_id: 1
priceable_type: Machine
amount: 1000
duration: 60
created_at: 2016-04-04 15:18:28.836899000 Z
updated_at: 2016-04-04 15:18:50.507019000 Z
@ -256,6 +281,7 @@ price_38:
priceable_id: 2
priceable_type: Machine
amount: 1000
duration: 60
created_at: 2016-04-04 15:18:28.842674000 Z
updated_at: 2016-04-04 15:18:50.508799000 Z
@ -266,6 +292,7 @@ price_39:
priceable_id: 3
priceable_type: Machine
amount: 1500
duration: 60
created_at: 2016-04-04 15:18:28.847736000 Z
updated_at: 2016-04-04 15:18:50.510437000 Z
@ -276,6 +303,7 @@ price_40:
priceable_id: 4
priceable_type: Machine
amount: 1000
duration: 60
created_at: 2016-04-04 15:18:28.852783000 Z
updated_at: 2016-04-04 15:18:50.512239000 Z
@ -286,6 +314,7 @@ price_41:
priceable_id: 5
priceable_type: Machine
amount: 800
duration: 60
created_at: 2016-04-04 15:18:28.856602000 Z
updated_at: 2016-04-04 15:18:50.514062000 Z
@ -296,6 +325,7 @@ price_42:
priceable_id: 6
priceable_type: Machine
amount: 1000
duration: 60
created_at: 2016-04-04 15:18:28.860220000 Z
updated_at: 2016-04-04 15:18:50.517702000 Z
@ -306,6 +336,7 @@ price_43:
priceable_id: 1
priceable_type: Machine
amount: 1000
duration: 60
created_at: 2016-04-04 15:18:28.836899000 Z
updated_at: 2016-04-04 15:18:50.507019000 Z
@ -316,6 +347,7 @@ price_44:
priceable_id: 2
priceable_type: Machine
amount: 1000
duration: 60
created_at: 2016-04-04 15:18:28.842674000 Z
updated_at: 2016-04-04 15:18:50.508799000 Z
@ -326,6 +358,7 @@ price_45:
priceable_id: 3
priceable_type: Machine
amount: 1500
duration: 60
created_at: 2016-04-04 15:18:28.847736000 Z
updated_at: 2016-04-04 15:18:50.510437000 Z
@ -336,6 +369,7 @@ price_46:
priceable_id: 4
priceable_type: Machine
amount: 1000
duration: 60
created_at: 2016-04-04 15:18:28.852783000 Z
updated_at: 2016-04-04 15:18:50.512239000 Z
@ -346,6 +380,7 @@ price_47:
priceable_id: 5
priceable_type: Machine
amount: 800
duration: 60
created_at: 2016-04-04 15:18:28.856602000 Z
updated_at: 2016-04-04 15:18:50.514062000 Z
@ -356,5 +391,6 @@ price_48:
priceable_id: 6
priceable_type: Machine
amount: 1000
duration: 60
created_at: 2016-04-04 15:18:28.860220000 Z
updated_at: 2016-04-04 15:18:50.517702000 Z