1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2024-11-29 10:24:20 +01:00

Merge branch 'dev' for release 5.4.8

This commit is contained in:
Du Peng 2022-06-29 17:36:05 +02:00
commit 80069dff80
95 changed files with 1908 additions and 595 deletions

View File

@ -2,6 +2,16 @@
## next deploy
## v5.4.8 2022 June 29
- My reservations dashboard
- Display reservations credits in the dashboard
- Added a test case for space reservation
- Updated Portugueses translations (thanks to [@ghabreu](https://github.com/ghabreu))
- Improved explanations about members CSV imports
- Fix a bug: unable to reserve a space
- Fix a bug: invalid users are reported in search
## v5.4.7 2022 June 29
- Removed the admins' button to scroll to the featured event

View File

@ -167,7 +167,7 @@ class API::AvailabilitiesController < API::ApiController
availabilities_filtered << a if filter_event?(a)
end
end
availabilities_filtered.delete_if(&method(:remove_completed?))
availabilities_filtered.delete_if(&method(:remove_full?))
end
def filter_training?(availability)
@ -186,8 +186,8 @@ class API::AvailabilitiesController < API::ApiController
params[:evt] && params[:evt] == 'true' && availability.available_type == 'event'
end
def remove_completed?(availability)
params[:dispo] == 'false' && (availability.is_reserved || (availability.try(:completed?) && availability.completed?))
def remove_full?(availability)
params[:dispo] == 'false' && (availability.is_reserved || (availability.try(:full?) && availability.full?))
end
def define_max_visibility

View File

@ -40,6 +40,19 @@ class API::CreditsController < API::ApiController
head :no_content
end
def user_resource
@user = User.find(params[:id])
authorize @user, policy_class: CreditPolicy
@credits = []
return unless @user.subscribed_plan
@credits = @user.subscribed_plan
.credits
.where(creditable_type: params[:resource])
.includes(:users_credits)
end
private
def set_credit

View File

@ -180,7 +180,7 @@ class API::MembersController < API::ApiController
end
def search
@members = Members::ListService.search(current_user, params[:query], params[:subscription], params[:project])
@members = Members::ListService.search(current_user, params[:query], params[:subscription], params[:include_admins])
end
def mapping

View File

@ -8,11 +8,12 @@ class API::ReservationsController < API::ApiController
respond_to :json
def index
if params[:reservable_id] && params[:reservable_type] && params[:user_id]
if params[:user_id]
params[:user_id] = current_user.id unless current_user.admin? || current_user.manager?
where_clause = params.permit(:reservable_id, :reservable_type).to_h
where_clause[:statistic_profile_id] = StatisticProfile.find_by!(user_id: params[:user_id])
where_clause = { statistic_profile_id: StatisticProfile.find_by!(user_id: params[:user_id]) }
where_clause[:reservable_type] = params[:reservable_type] if params[:reservable_type]
where_clause[:reservable_id] = params[:reservable_id] if params[:reservable_id]
@reservations = Reservation.where(where_clause)
elsif params[:reservable_id] && params[:reservable_type] && (current_user.admin? || current_user.manager?)

View File

@ -0,0 +1,15 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { Credit, CreditableType } from '../models/credit';
export default class CreditAPI {
static async index (): Promise<Array<Credit>> {
const res: AxiosResponse<Array<Credit>> = await apiClient.get('/api/credits');
return res?.data;
}
static async userResource (userId: number, resource: CreditableType): Promise<Array<Credit>> {
const res: AxiosResponse<Array<Credit>> = await apiClient.get(`/api/credits/user/${userId}/${resource}`);
return res?.data;
}
}

View File

@ -1,16 +1,11 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { Group, GroupIndexFilter } from '../models/group';
import ApiLib from '../lib/api';
export default class GroupAPI {
static async index (filters?: GroupIndexFilter): Promise<Array<Group>> {
const res: AxiosResponse<Array<Group>> = await apiClient.get(`/api/groups${this.filtersToQuery(filters)}`);
const res: AxiosResponse<Array<Group>> = await apiClient.get(`/api/groups${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
private static filtersToQuery (filters?: GroupIndexFilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -1,10 +1,11 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { Machine, MachineIndexFilter } from '../models/machine';
import ApiLib from '../lib/api';
export default class MachineAPI {
static async index (filters?: MachineIndexFilter): Promise<Array<Machine>> {
const res: AxiosResponse<Array<Machine>> = await apiClient.get(`/api/machines${this.filtersToQuery(filters)}`);
const res: AxiosResponse<Array<Machine>> = await apiClient.get(`/api/machines${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
@ -12,10 +13,4 @@ export default class MachineAPI {
const res: AxiosResponse<Machine> = await apiClient.get(`/api/machines/${id}`);
return res?.data;
}
private static filtersToQuery (filters?: MachineIndexFilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -1,10 +1,11 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { PackIndexFilter, PrepaidPack } from '../models/prepaid-pack';
import ApiLib from '../lib/api';
export default class PrepaidPackAPI {
static async index (filters?: PackIndexFilter): Promise<Array<PrepaidPack>> {
const res: AxiosResponse<Array<PrepaidPack>> = await apiClient.get(`/api/prepaid_packs${this.filtersToQuery(filters)}`);
const res: AxiosResponse<Array<PrepaidPack>> = await apiClient.get(`/api/prepaid_packs${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
@ -27,10 +28,4 @@ export default class PrepaidPackAPI {
const res: AxiosResponse<void> = await apiClient.delete(`/api/prepaid_packs/${packId}`);
return res?.data;
}
private static filtersToQuery (filters?: PackIndexFilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -2,6 +2,7 @@ import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { ShoppingCart } from '../models/payment';
import { ComputePriceResult, Price, PriceIndexFilter } from '../models/price';
import ApiLib from '../lib/api';
export default class PriceAPI {
static async compute (cart: ShoppingCart): Promise<ComputePriceResult> {
@ -10,7 +11,7 @@ export default class PriceAPI {
}
static async index (filters?: PriceIndexFilter): Promise<Array<Price>> {
const res: AxiosResponse = await apiClient.get(`/api/prices${this.filtersToQuery(filters)}`);
const res: AxiosResponse = await apiClient.get(`/api/prices${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
@ -28,10 +29,4 @@ export default class PriceAPI {
const res: AxiosResponse<void> = await apiClient.delete(`/api/prices/${priceId}`);
return res?.data;
}
private static filtersToQuery (filters?: PriceIndexFilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -1,10 +1,11 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { ProofOfIdentityFile, ProofOfIdentityFileIndexFilter } from '../models/proof-of-identity-file';
import ApiLib from '../lib/api';
export default class ProofOfIdentityFileAPI {
static async index (filters?: ProofOfIdentityFileIndexFilter): Promise<Array<ProofOfIdentityFile>> {
const res: AxiosResponse<Array<ProofOfIdentityFile>> = await apiClient.get(`/api/proof_of_identity_files${this.filtersToQuery(filters)}`);
const res: AxiosResponse<Array<ProofOfIdentityFile>> = await apiClient.get(`/api/proof_of_identity_files${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
@ -27,10 +28,4 @@ export default class ProofOfIdentityFileAPI {
const res: AxiosResponse<void> = await apiClient.delete(`/api/proof_of_identity_files/${proofOfIdentityFileId}`);
return res?.data;
}
private static filtersToQuery (filters?: ProofOfIdentityFileIndexFilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -1,10 +1,11 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { ProofOfIdentityRefusal, ProofOfIdentityRefusalIndexFilter } from '../models/proof-of-identity-refusal';
import ApiLib from '../lib/api';
export default class ProofOfIdentityRefusalAPI {
static async index (filters?: ProofOfIdentityRefusalIndexFilter): Promise<Array<ProofOfIdentityRefusal>> {
const res: AxiosResponse<Array<ProofOfIdentityRefusal>> = await apiClient.get(`/api/proof_of_identity_refusals${this.filtersToQuery(filters)}`);
const res: AxiosResponse<Array<ProofOfIdentityRefusal>> = await apiClient.get(`/api/proof_of_identity_refusals${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
@ -12,10 +13,4 @@ export default class ProofOfIdentityRefusalAPI {
const res: AxiosResponse<ProofOfIdentityRefusal> = await apiClient.post('/api/proof_of_identity_refusals', { proof_of_identity_refusal: proofOfIdentityRefusal });
return res?.data;
}
private static filtersToQuery (filters?: ProofOfIdentityRefusalIndexFilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -1,10 +1,11 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { ProofOfIdentityType, ProofOfIdentityTypeIndexfilter } from '../models/proof-of-identity-type';
import ApiLib from '../lib/api';
export default class ProofOfIdentityTypeAPI {
static async index (filters?: ProofOfIdentityTypeIndexfilter): Promise<Array<ProofOfIdentityType>> {
const res: AxiosResponse<Array<ProofOfIdentityType>> = await apiClient.get(`/api/proof_of_identity_types${this.filtersToQuery(filters)}`);
const res: AxiosResponse<Array<ProofOfIdentityType>> = await apiClient.get(`/api/proof_of_identity_types${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
@ -27,10 +28,4 @@ export default class ProofOfIdentityTypeAPI {
const res: AxiosResponse<void> = await apiClient.delete(`/api/proof_of_identity_types/${proofOfIdentityTypeId}`);
return res?.data;
}
private static filtersToQuery (filters?: ProofOfIdentityTypeIndexfilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -0,0 +1,11 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { Reservation, ReservationIndexFilter } from '../models/reservation';
import ApiLib from '../lib/api';
export default class ReservationAPI {
static async index (filters: ReservationIndexFilter): Promise<Array<Reservation>> {
const res: AxiosResponse<Array<Reservation>> = await apiClient.get(`/api/reservations${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
}

View File

@ -1,16 +1,11 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { Training, TrainingIndexFilter } from '../models/training';
import ApiLib from '../lib/api';
export default class TrainingAPI {
static async index (filters?: TrainingIndexFilter): Promise<Array<Training>> {
const res: AxiosResponse<Array<Training>> = await apiClient.get(`/api/trainings${this.filtersToQuery(filters)}`);
const res: AxiosResponse<Array<Training>> = await apiClient.get(`/api/trainings${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
private static filtersToQuery (filters?: TrainingIndexFilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -1,16 +1,11 @@
import apiClient from './clients/api-client';
import { UserPack, UserPackIndexFilter } from '../models/user-pack';
import { AxiosResponse } from 'axios';
import ApiLib from '../lib/api';
export default class UserPackAPI {
static async index (filters: UserPackIndexFilter): Promise<Array<UserPack>> {
const res: AxiosResponse<Array<UserPack>> = await apiClient.get(`/api/user_packs${this.filtersToQuery(filters)}`);
const res: AxiosResponse<Array<UserPack>> = await apiClient.get(`/api/user_packs${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
private static filtersToQuery (filters?: UserPackIndexFilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -4,12 +4,14 @@ interface FabPopoverProps {
title: string,
className?: string,
headerButton?: ReactNode,
position?: 'bottom' | 'right' | 'left'
}
/**
* This component is a template for a popovers (bottom) that wraps the application style
* This component is a template for a popovers (bottom) that wraps the application style.
* Please note that the parent element must be set `position: relative;` otherwise the popover won't be placed correctly.
*/
export const FabPopover: React.FC<FabPopoverProps> = ({ title, className, headerButton, children }) => {
export const FabPopover: React.FC<FabPopoverProps> = ({ title, className, headerButton, position = 'bottom', children }) => {
/**
* Check if the header button should be present
*/
@ -18,7 +20,7 @@ export const FabPopover: React.FC<FabPopoverProps> = ({ title, className, header
};
return (
<div className={`fab-popover ${className || ''}`}>
<div className={`fab-popover fab-popover__${position} ${className || ''}`}>
<div className="popover-title">
<h3>{title}</h3>
{hasHeaderButton() && headerButton}

View File

@ -0,0 +1,76 @@
import React, { ReactNode, useEffect, useState } from 'react';
import { FabPanel } from '../../base/fab-panel';
import { Loader } from '../../base/loader';
import { useTranslation } from 'react-i18next';
import { Credit, CreditableType } from '../../../models/credit';
import CreditAPI from '../../../api/credit';
import { HtmlTranslate } from '../../base/html-translate';
interface CreditsPanelProps {
userId: number,
onError: (message: string) => void,
reservableType: CreditableType
}
/**
* List all available credits for the given user and the given resource
*/
const CreditsPanel: React.FC<CreditsPanelProps> = ({ userId, onError, reservableType }) => {
const { t } = useTranslation('logged');
const [credits, setCredits] = useState<Array<Credit>>([]);
useEffect(() => {
CreditAPI.userResource(userId, reservableType)
.then(res => setCredits(res))
.catch(error => onError(error));
}, []);
/**
* Compute the remainings hours for the given credit
*/
const remainingHours = (credit: Credit): number => {
return credit.hours - credit.hours_used;
};
/**
* Display a placeholder when there's no credits to display
*/
const noCredits = (): ReactNode => {
return (
<li className="no-credits">{t('app.logged.dashboard.reservations.credits_panel.no_credits')}</li>
);
};
/**
* Panel title
*/
const header = (): ReactNode => {
return (
<div>
{t(`app.logged.dashboard.reservations.credits_panel.title_${reservableType}`)}
</div>
);
};
return (
<FabPanel className="credits-panel" header={header()}>
<ul>
{credits.map(c => <li key={c.id}>
<HtmlTranslate trKey="app.logged.dashboard.reservations.credits_panel.reamaining_credits_html" options={{ NAME: c.creditable.name, REMAINING: remainingHours(c), USED: c.hours_used }} />
</li>)}
{credits.length === 0 && noCredits()}
</ul>
</FabPanel>
);
};
const CreditsPanelWrapper: React.FC<CreditsPanelProps> = (props) => {
return (
<Loader>
<CreditsPanel {...props} />
</Loader>
);
};
export { CreditsPanelWrapper as CreditsPanel };

View File

@ -0,0 +1,38 @@
import React, { useEffect, useState } from 'react';
import { IApplication } from '../../../models/application';
import { react2angular } from 'react2angular';
import { ReservationsPanel } from './reservations-panel';
import SettingAPI from '../../../api/setting';
import { SettingName } from '../../../models/setting';
import { CreditsPanel } from './credits-panel';
declare const Application: IApplication;
interface ReservationsDashboardProps {
onError: (message: string) => void,
userId: number
}
/**
* User dashboard showing everything about his spaces/machine reservations and also remaining credits
*/
const ReservationsDashboard: React.FC<ReservationsDashboardProps> = ({ onError, userId }) => {
const [modules, setModules] = useState<Map<SettingName, string>>();
useEffect(() => {
SettingAPI.query([SettingName.SpacesModule, SettingName.MachinesModule])
.then(res => setModules(res))
.catch(error => onError(error));
}, []);
return (
<div className="reservations-dashboard">
{modules?.get(SettingName.MachinesModule) !== 'false' && <CreditsPanel userId={userId} onError={onError} reservableType="Machine" />}
{modules?.get(SettingName.SpacesModule) !== 'false' && <CreditsPanel userId={userId} onError={onError} reservableType="Space" />}
{modules?.get(SettingName.MachinesModule) !== 'false' && <ReservationsPanel userId={userId} onError={onError} reservableType="Machine" />}
{modules?.get(SettingName.SpacesModule) !== 'false' && <ReservationsPanel userId={userId} onError={onError} reservableType="Space" />}
</div>
);
};
Application.Components.component('reservationsDashboard', react2angular(ReservationsDashboard, ['onError', 'userId']));

View File

@ -0,0 +1,142 @@
import React, { ReactNode, useEffect, useState } from 'react';
import { FabPanel } from '../../base/fab-panel';
import { Reservation, ReservationSlot } from '../../../models/reservation';
import ReservationAPI from '../../../api/reservation';
import { useTranslation } from 'react-i18next';
import moment from 'moment';
import { Loader } from '../../base/loader';
import FormatLib from '../../../lib/format';
import { FabPopover } from '../../base/fab-popover';
import { useImmer } from 'use-immer';
import _ from 'lodash';
import { FabButton } from '../../base/fab-button';
interface SpaceReservationsProps {
userId: number,
onError: (message: string) => void,
reservableType: 'Machine' | 'Space'
}
/**
* List all reservations for the given user and the given type
*/
const ReservationsPanel: React.FC<SpaceReservationsProps> = ({ userId, onError, reservableType }) => {
const { t } = useTranslation('logged');
const [reservations, setReservations] = useState<Array<Reservation>>([]);
const [details, updateDetails] = useImmer<Record<number, boolean>>({});
const [showMore, setShowMore] = useState<boolean>(false);
useEffect(() => {
ReservationAPI.index({ user_id: userId, reservable_type: reservableType })
.then(res => setReservations(res))
.catch(error => onError(error));
}, []);
/**
* Return the reservations for the given period
*/
const reservationsByDate = (state: 'past' | 'futur'): Array<Reservation> => {
return reservations.filter(r => {
return !!r.slots_attributes.find(s => filterSlot(s, state));
});
};
/**
* Check if the given slot if past of futur
*/
const filterSlot = (slot: ReservationSlot, state: 'past' | 'futur'): boolean => {
return (state === 'past' && moment(slot.start_at).isBefore()) ||
(state === 'futur' && moment(slot.start_at).isAfter());
};
/**
* Panel title
*/
const header = (): ReactNode => {
return (
<div>
{t(`app.logged.dashboard.reservations.reservations_panel.title_${reservableType}`)}
</div>
);
};
/**
* Show/hide the slots details for the given reservation
*/
const toggleDetails = (reservationId: number): () => void => {
return () => {
updateDetails(draft => {
draft[reservationId] = !draft[reservationId];
});
};
};
/**
* Shows/hide the very old reservations list
*/
const toggleShowMore = (): void => {
setShowMore(!showMore);
};
/**
* Display a placeholder when there's no reservation to display
*/
const noReservations = (): ReactNode => {
return (
<li className="no-reservations">{t('app.logged.dashboard.reservations.reservations_panel.no_reservations')}</li>
);
};
/**
* Render the reservation in a user-friendly way
*/
const renderReservation = (reservation: Reservation, state: 'past' | 'futur'): ReactNode => {
return (
<li key={reservation.id} className="reservation">
<a className={`reservation-title ${details[reservation.id] ? 'clicked' : ''}`} onClick={toggleDetails(reservation.id)}>
{reservation.reservable.name} - {FormatLib.date(reservation.slots_attributes[0].start_at)}
</a>
{details[reservation.id] && <FabPopover title={t('app.logged.dashboard.reservations.reservations_panel.slots_details')}>
{reservation.slots_attributes.filter(s => filterSlot(s, state)).map(
slot => <span key={slot.id} className="slot-details">
{FormatLib.date(slot.start_at)}, {FormatLib.time(slot.start_at)} - {FormatLib.time(slot.end_at)}
</span>
)}
</FabPopover>}
</li>
);
};
const futur = reservationsByDate('futur');
const past = _.orderBy(reservationsByDate('past'), r => r.slots_attributes[0].start_at, 'desc');
return (
<FabPanel className="reservations-panel" header={header()}>
<h4>{t('app.logged.dashboard.reservations.reservations_panel.upcoming')}</h4>
<ul>
{futur.length === 0 && noReservations()}
{futur.map(r => renderReservation(r, 'futur'))}
</ul>
<h4>{t('app.logged.dashboard.reservations.reservations_panel.past')}</h4>
<ul>
{past.length === 0 && noReservations()}
{past.slice(0, 10).map(r => renderReservation(r, 'past'))}
{past.length > 10 && !showMore && <li className="show-more"><FabButton onClick={toggleShowMore}>
{t('app.logged.dashboard.reservations.reservations_panel.show_more')}
</FabButton></li>}
{past.length > 10 && showMore && past.slice(10).map(r => renderReservation(r, 'past'))}
</ul>
</FabPanel>
);
};
const ReservationsPanelWrapper: React.FC<SpaceReservationsProps> = (props) => {
return (
<Loader>
<ReservationsPanel {...props} />
</Loader>
);
};
export { ReservationsPanelWrapper as ReservationsPanel };

View File

@ -68,7 +68,7 @@ export const ConfigurePacksButton: React.FC<ConfigurePacksButtonProps> = ({ pack
<button className="packs-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">
{showList && <FabPopover title={t('app.admin.configure_packs_button.packs')} headerButton={renderAddButton()} position="right">
<ul>
{packs?.map(p =>
<li key={p.id} className={p.disabled ? 'disabled' : ''}>

View File

@ -68,7 +68,7 @@ export const ConfigureExtendedPricesButton: React.FC<ConfigureExtendedPricesButt
<button className="extended-prices-button" onClick={toggleShowList}>
<i className="fas fa-stopwatch" />
</button>
{showList && <FabPopover title={t('app.admin.configure_extended_prices_button.extended_prices')} headerButton={renderAddButton()} className="fab-popover__right">
{showList && <FabPopover title={t('app.admin.configure_extended_prices_button.extended_prices')} headerButton={renderAddButton()} position="right">
<ul>
{extendedPrices?.map(extendedPrice =>
<li key={extendedPrice.id}>

View File

@ -231,7 +231,7 @@ class ProjectsController {
const asciiName = Diacritics.remove(nameLookup);
Member.search(
{ query: asciiName, project: 'true' },
{ query: asciiName, include_admins: 'true' },
function (users) { $scope.matchingMembers = users; },
function (error) { console.error(error); }
);

View File

@ -0,0 +1,9 @@
import { ApiFilter } from '../models/api';
export default class ApiLib {
static filtersToQuery (filters?: ApiFilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -0,0 +1,3 @@
// ApiFilter should be extended by an interface listing all the filters allowed for a given API
// eslint-disable-next-line @typescript-eslint/ban-types
export type ApiFilter = {};

View File

@ -0,0 +1,18 @@
import { TDateISO } from '../typings/date-iso';
export type CreditableType = 'Training' | 'Machine' | 'Space';
export interface Credit {
id?: number,
creditable_id: number,
creditable_type: CreditableType,
created_at?: TDateISO,
updated_at?: TDateISO,
plan_id?: number,
hours: number,
creditable?: {
id: number,
name: string
},
hours_used?: number
}

View File

@ -14,7 +14,7 @@ export type ruleTypes = {
/**
* `error` and `warning` props can be manually set.
* Automatic error handling is done through the `formState` prop.
* Even for manual error/warning, the `formState` prop is required, because it is used to determine is the field is dirty.
* Even for manual error/warning, the `formState` prop is required, because it is used to determine if the field is dirty.
*/
export interface AbstractFormComponent<TFieldValues> {
error?: { message: string },

View File

@ -1,4 +1,6 @@
export interface GroupIndexFilter {
import { ApiFilter } from './api';
export interface GroupIndexFilter extends ApiFilter {
disabled?: boolean,
admins?: boolean,
}

View File

@ -1,6 +1,7 @@
import { Reservation } from './reservation';
import { ApiFilter } from './api';
export interface MachineIndexFilter {
export interface MachineIndexFilter extends ApiFilter {
disabled: boolean,
}

View File

@ -1,5 +1,6 @@
import { ApiFilter } from './api';
export interface PackIndexFilter {
export interface PackIndexFilter extends ApiFilter {
group_id?: number,
priceable_id?: number,
priceable_type?: string,

View File

@ -1,6 +1,7 @@
import { TDateISO } from '../typings/date-iso';
import { ApiFilter } from './api';
export interface PriceIndexFilter {
export interface PriceIndexFilter extends ApiFilter {
priceable_type?: string,
priceable_id?: number,
group_id?: number,

View File

@ -1,5 +1,6 @@
import { ApiFilter } from './api';
export interface ProofOfIdentityFileIndexFilter {
export interface ProofOfIdentityFileIndexFilter extends ApiFilter {
user_id: number,
}

View File

@ -1,5 +1,6 @@
import { ApiFilter } from './api';
export interface ProofOfIdentityRefusalIndexFilter {
export interface ProofOfIdentityRefusalIndexFilter extends ApiFilter {
user_id: number,
}

View File

@ -1,4 +1,6 @@
export interface ProofOfIdentityTypeIndexfilter {
import { ApiFilter } from './api';
export interface ProofOfIdentityTypeIndexfilter extends ApiFilter {
group_id?: number,
}

View File

@ -1,20 +1,50 @@
import { TDateISO } from '../typings/date-iso';
import { ApiFilter } from './api';
export type ReservableType = 'Training' | 'Event' | 'Space' | 'Machine';
export interface ReservationSlot {
id?: number,
start_at: TDateISO,
end_at: TDateISO,
availability_id: number,
offered: boolean
canceled_at?: TDateISO,
availability_id?: number,
offered?: boolean,
is_reserved?: boolean
}
export interface Reservation {
id?: number,
user_id?: number,
user_full_name?: string,
message?: string,
reservable_id: number,
reservable_type: string,
reservable_type: ReservableType,
slots_attributes: Array<ReservationSlot>,
reservable?: {
id: number,
name: string
},
nb_reserve_places?: number,
tickets_attributes?: {
event_price_category_id: number,
event_price_category?: {
id: number,
price_category_id: number,
price_category: {
id: number,
name: string
}
},
booked: boolean,
created_at?: TDateISO
},
total_booked_seats?: number,
created_at?: TDateISO,
}
export interface ReservationIndexFilter extends ApiFilter {
reservable_id?: number,
reservable_type?: ReservableType | Array<ReservableType>,
user_id?: number
}

View File

@ -1,3 +1,5 @@
import { ApiFilter } from './api';
export interface Training {
id?: number,
name: string,
@ -11,7 +13,7 @@ export interface Training {
training_image?: string,
}
export interface TrainingIndexFilter {
export interface TrainingIndexFilter extends ApiFilter {
disabled?: boolean,
public_page?: boolean,
requested_attributes?: ['availabillities'],

View File

@ -1,6 +1,7 @@
import { TDateISO } from '../typings/date-iso';
import { ApiFilter } from './api';
export interface UserPackIndexFilter {
export interface UserPackIndexFilter extends ApiFilter {
user_id?: number,
priceable_type: string,
priceable_id: number

View File

@ -191,6 +191,15 @@ angular.module('application.router', ['ui.router'])
}
}
})
.state('app.logged.dashboard.reservations', {
url: '/reservations',
views: {
'main@': {
templateUrl: '/dashboard/reservations.html',
controller: 'DashboardController'
}
}
})
.state('app.logged.dashboard.events', {
url: '/events',
views: {

View File

@ -30,6 +30,9 @@
@import "modules/base/fab-text-editor";
@import "modules/base/labelled-input";
@import "modules/calendar/calendar";
@import "modules/dashboard/reservations/credits-panel";
@import "modules/dashboard/reservations/reservations-dashboard";
@import "modules/dashboard/reservations/reservations-panel";
@import "modules/events/event";
@import "modules/form/abstract-form-item";
@import "modules/form/form-input";

View File

@ -0,0 +1,26 @@
.credits-panel {
.no-credits {
list-style: none;
text-align: center;
font-style: italic;
color: #aaa;
}
}
@media (min-width: 1460px) {
.credits-panel {
width: 45%;
}
}
@media (max-width: 1459px) {
.credits-panel {
width: 95%;
}
}
@media (max-width: 992px) {
.credits-panel {
width: 90%;
}
}

View File

@ -0,0 +1,5 @@
.reservations-dashboard {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}

View File

@ -0,0 +1,49 @@
.reservations-panel {
margin-bottom: 60px;
.reservation {
position: relative;
&-title {
cursor: pointer;
&.clicked {
color: var(--secondary-dark);
}
}
.slot-details {
display: block;
}
.fab-popover {
left: 0;
}
}
.no-reservations,
.show-more {
list-style: none;
text-align: center;
font-style: italic;
color: #aaa;
}
.show-more {
margin-top: 10px;
}
}
@media (min-width: 1460px) {
.reservations-panel {
width: 45%;
}
}
@media (max-width: 1459px) {
.reservations-panel {
width: 95%;
}
}
@media (max-width: 992px) {
.reservations-panel {
width: 90%;
}
}

View File

@ -34,7 +34,7 @@
<td>{{ reservation.created_at | amDateFormat:'LL LTS' }}</td>
<td>
<span ng-if="reservation.nb_reserve_places > 0">{{ 'app.admin.event_reservations.full_price_' | translate }} {{reservation.nb_reserve_places}}<br/></span>
<span ng-repeat="ticket in reservation.tickets">{{ticket.event_price_category.price_category.name}} : {{ticket.booked}}</span>
<span ng-repeat="ticket in reservation.tickets_attributes">{{ticket.event_price_category.price_category.name}} : {{ticket.booked}}</span>
<div ng-show="isCancelled(reservation)" class="canceled-marker" translate>{{ 'app.admin.event_reservations.canceled' }}</div>
</td>
<td>

View File

@ -121,9 +121,7 @@
<p class="alert alert-warning m-h" translate>
{{ 'app.admin.members_import.required_fields' }}
</p>
<p class="alert alert-warning m-h" translate>
{{ 'app.admin.members_import.about_example' }}
</p>
<p class="alert alert-warning m-h" ng-bind-html="'app.admin.members_import.about_example_html' | translate"></p>
</div>
<div class="fileinput input-group" data-provides="fileinput" ng-class="fileinputClass()">

View File

@ -12,7 +12,7 @@
</div>
<div class="col-xs-1 col-xs-offset-1 col-md-offset-2 b-l">
<section class="heading-actions wrapper" ng-show="isAuthorized('admin')">
<a role="button" class="btn btn-default b-2x rounded m-t-sm import-members" ui-sref="app.admin.members_import">
<a role="button" class="btn btn-default b-2x rounded m-t-sm import-members" ui-sref="app.admin.members_import" title="{{ 'app.admin.members.import' | translate }}">
<i class="fa fa-cloud-upload"></i>
</a>
</section>

View File

@ -15,6 +15,7 @@
<li ng-if="!isAuthorized(['admin', 'manager']) && hasProofOfIdentityTypes" ui-sref-active="active"><a class="text-black" ui-sref="app.logged.dashboard.proof_of_identity_files" translate>{{ 'app.public.common.my_supporting_documents_files' }}</a></li>
<li ui-sref-active="active"><a class="text-black" ui-sref="app.logged.dashboard.projects" translate>{{ 'app.public.common.my_projects' }}</a></li>
<li ui-sref-active="active"><a class="text-black" ui-sref="app.logged.dashboard.trainings" translate>{{ 'app.public.common.my_trainings' }}</a></li>
<li ui-sref-active="active"><a class="text-black" ui-sref="app.logged.dashboard.reservations" translate>{{ 'app.public.common.my_reservations' }}</a></li>
<li ui-sref-active="active"><a class="text-black" ui-sref="app.logged.dashboard.events" translate>{{ 'app.public.common.my_events' }}</a></li>
<li ui-sref-active="active" ng-show="$root.modules.invoicing"><a class="text-black" ui-sref="app.logged.dashboard.invoices" translate>{{ 'app.public.common.my_invoices' }}</a></li>
<li ui-sref-active="active" ng-show="$root.modules.invoicing"><a class="text-black" ui-sref="app.logged.dashboard.payment_schedules" translate>{{ 'app.public.common.my_payment_schedules' }}</a></li>

View File

@ -0,0 +1,11 @@
<div>
<section class="heading">
<div class="row no-gutter">
<ng-include src="'/dashboard/nav.html'"></ng-include>
</div>
</section>
<reservations-dashboard user-id="user.id" on-error="onError" />
</div>

View File

@ -44,6 +44,7 @@
<li ng-if="!isAuthorized(['admin', 'manager']) && hasProofOfIdentityTypes"><a ui-sref="app.logged.dashboard.proof_of_identity_files" translate>{{ 'app.public.common.my_supporting_documents_files' }}</a></li>
<li><a ui-sref="app.logged.dashboard.projects" translate>{{ 'app.public.common.my_projects' }}</a></li>
<li><a ui-sref="app.logged.dashboard.trainings" translate>{{ 'app.public.common.my_trainings' }}</a></li>
<li><a ui-sref="app.logged.dashboard.reservations" translate>{{ 'app.public.common.my_reservations' }}</a></li>
<li><a ui-sref="app.logged.dashboard.events" translate>{{ 'app.public.common.my_events' }}</a></li>
<li><a ui-sref="app.logged.dashboard.invoices" ng-show="$root.modules.invoicing" translate>{{ 'app.public.common.my_invoices' }}</a></li>
<li><a ui-sref="app.logged.dashboard.payment_schedules" ng-show="$root.modules.invoicing" translate>{{ 'app.public.common.my_payment_schedules' }}</a></li>

View File

@ -44,7 +44,7 @@ module AvailabilityHelper
def trainings_events_border_color(availability)
if availability.is_reserved
IS_RESERVED_BY_CURRENT_USER
elsif availability.completed?
elsif availability.full?
IS_COMPLETED
else
case availability.available_type

View File

@ -111,9 +111,9 @@ class Availability < ApplicationRecord
end
end
# return training reservations is complete?
# if haven't defined a nb_total_places, places are unlimited
def completed?
# check if the reservations are complete?
# if a nb_total_places hasn't been defined, then places are unlimited
def full?
return false if nb_total_places.blank?
if available_type == 'training' || available_type == 'space'

View File

@ -45,21 +45,21 @@ class CartItem::Reservation < CartItem::BaseItem
@slots.each do |slot|
availability = Availability.find_by(id: slot[:availability_id])
if availability.nil?
@errors[:slot] = 'slot availability no exist'
@errors[:slot] = 'slot availability does not exist'
return false
end
if availability.available_type == 'machines'
s = Slot.find_by(start_at: slot[:start_at], end_at: slot[:end_at], availability_id: slot[:availability_id], canceled_at: nil)
unless s.nil?
@errors[:slot] = 'slot has reserved'
@errors[:slot] = 'slot is reserved'
return false
end
elsif availability.available_type == 'space' && availability.spaces.first.disabled.nil?
elsif availability.available_type == 'space' && availability.spaces.first.disabled
@errors[:slot] = 'space is disabled'
return false
elsif availability.completed?
@errors[:slot] = 'availability has completed'
elsif availability.full?
@errors[:slot] = 'availability is complete'
return false
end

View File

@ -112,7 +112,7 @@ class Reservation < ApplicationRecord
def training_not_fully_reserved
slot = slots.first
errors.add(:training, 'already fully reserved') if Availability.find(slot.availability_id).completed?
errors.add(:training, 'already fully reserved') if Availability.find(slot.availability_id).full?
end
def slots_not_locked

View File

@ -1,3 +1,6 @@
# frozen_string_literal: true
# Check the access policies for API::CreditsController
class CreditPolicy < ApplicationPolicy
def index?
user.admin?
@ -14,4 +17,8 @@ class CreditPolicy < ApplicationPolicy
def destroy?
index?
end
def user_resource?
record.id == user.id
end
end

View File

@ -40,7 +40,7 @@ class Members::ListService
@query
end
def search(current_user, query, subscription, project)
def search(current_user, query, subscription, include_admins = 'false')
members = User.includes(:profile)
.joins(:profile,
:statistic_profile,
@ -69,11 +69,9 @@ class Members::ListService
members = members.where('subscriptions.id IS NULL OR subscriptions.expiration_date < :now', now: Date.today.to_s)
end
if project == 'false' || project.blank?
members = members.where("roles.name = 'member' OR roles.name = 'manager'")
end
members = members.where("roles.name = 'member' OR roles.name = 'manager'") if include_admins == 'false' || include_admins.blank?
members.to_a
members.to_a.filter(&:valid?)
end
private

View File

@ -25,7 +25,7 @@ json.array!(@availabilities) do |availability|
if availability.is_reserved
json.is_reserved true
json.title "#{availability.title}' - #{t('trainings.i_ve_reserved')}"
elsif availability.completed?
elsif availability.full?
json.is_completed true
json.title "#{availability.title} - #{t('trainings.completed')}"
end

View File

@ -5,7 +5,7 @@ json.array!(@availabilities) do |a|
if a.is_reserved
json.is_reserved true
json.title "#{a.trainings[0].name}' - #{t('trainings.i_ve_reserved')}"
elsif a.completed?
elsif a.full?
json.is_completed true
json.title "#{a.trainings[0].name} - #{t('trainings.completed')}"
else

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
json.extract! credit, :id, :creditable_id, :creditable_type, :created_at, :updated_at, :plan_id, :hours
if credit.creditable
json.creditable do
json.id credit.creditable.id
json.name credit.creditable.name
end
end

View File

@ -1,8 +1,5 @@
# frozen_string_literal: true
json.array!(@credits) do |credit|
json.extract! credit, :id, :creditable_id, :creditable_type, :plan_id, :hours
json.creditable do
json.id credit.creditable.id
json.name credit.creditable.name
end if credit.creditable.present?
json.partial! 'api/credits/credit', credit: credit
end

View File

@ -1,5 +1,3 @@
json.extract! @credit, :id, :creditable_id, :creditable_type, :created_at, :updated_at, :plan_id, :hours
json.creditable do
json.id @credit.creditable.id
json.name @credit.creditable.name
end if @credit.creditable
# frozen_string_literal: true
json.partial! 'api/credits/credit', credit: @credit

View File

@ -0,0 +1,6 @@
# frozen_string_literal: true
json.array!(@credits) do |credit|
json.partial! 'api/credits/credit', credit: credit
json.hours_used credit.users_credits.find_by(user_id: @user.id)&.hours_used
end

View File

@ -9,9 +9,10 @@ json.slots_attributes reservation.slots do |s|
json.start_at s.start_at.iso8601
json.end_at s.end_at.iso8601
json.canceled_at s.canceled_at&.iso8601
json.is_reserved true
end
json.nb_reserve_places reservation.nb_reserve_places
json.tickets reservation.tickets do |t|
json.tickets_attributes reservation.tickets do |t|
json.extract! t, :booked, :created_at
json.event_price_category do
json.extract! t.event_price_category, :id, :price_category_id
@ -24,3 +25,7 @@ json.total_booked_seats reservation.total_booked_seats(canceled: true)
json.created_at reservation.created_at.iso8601
json.reservable_id reservation.reservable_id
json.reservable_type reservation.reservable_type
json.reservable do
json.id reservation.reservable.id
json.name reservation.reservable.name
end

View File

@ -1,4 +1,5 @@
# frozen_string_literal: true
json.array!(@reservations) do |r|
json.partial! 'api/reservations/reservation', reservation: r
end

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true
json.id @reservation.id
json.user_id @reservation.statistic_profile.user_id
json.partial! 'api/reservations/reservation', reservation: @reservation
json.user do
json.id @reservation.user.id
if @reservation.user.subscribed_plan
@ -17,26 +16,3 @@ json.user do
json.hours_used mc.users_credits.find_by(user_id: @reservation.statistic_profile.user_id).hours_used
end
end
json.message @reservation.message
json.slots_attributes @reservation.slots do |s|
json.id s.id
json.start_at s.start_at.iso8601
json.end_at s.end_at.iso8601
json.is_reserved true
end
json.reservable do
json.id @reservation.reservable.id
json.name @reservation.reservable.name
end
json.nb_reserve_places @reservation.nb_reserve_places
json.tickets @reservation.tickets do |t|
json.extract! t, :booked, :created_at
json.event_price_category do
json.extract! t.event_price_category, :id, :price_category_id
json.price_category do
json.extract! t.event_price_category.price_category, :id, :name
end
end
end
json.total_booked_seats @reservation.total_booked_seats
json.created_at @reservation.created_at.iso8601

View File

@ -816,6 +816,7 @@ de:
#management of users, labels, groups, and so on
members:
users_management: "Benutzer-Verwaltung"
import: "Import members from a CSV file"
users: "Benutzer"
members: "Mitglieder"
subscriptions: "Abonnements"
@ -919,7 +920,7 @@ de:
import_members: "Mitglieder importieren"
info: "Sie können eine CSV-Datei hochladen, um neue Mitglieder zu erstellen oder bestehende zu aktualisieren. Ihre Datei muss die unten angegebenen Identifikatoren verwenden, um die Gruppe, die Schulungen und die Tags der Mitglieder festzulegen."
required_fields: "Ihre Datei muss mindestens folgende Informationen enthalten, die für jeden Benutzer erstellt werden: E-Mail, Name, Vorname und Gruppe. Wenn das Passwort leer ist, wird es generiert. Bei Aktualisierungen werden leere Felder so beibehalten, wie sie sind."
about_example: "Bitte nutzen Sie die angegebene Beispieldatei, um eine korrekte CSV-Datei zu generieren. Achten Sie auf die Verwendung des UTF-8-Encodings."
about_example_html: "Please refer to the provided example file to generate a correct CSV file.<br>This example will:<ol><li>create a new member (Jean Dupont) with a generated password</li><li>update the password of an existing membre (ID 43) using the new given password</li></ol><br>Be careful to use <strong>Unicode UTF-8</strong> encoding."
groups: "Gruppen"
group_name: "Gruppenname"
group_identifier: "Zu verwendende Kennung"
@ -1027,6 +1028,7 @@ de:
invalidate_member_success: "The member is invalidated"
validate_member_error: "An error occurred: impossible to validate from this member."
invalidate_member_error: "An error occurred: impossible to invalidate from this member."
supporting_documents: "Supporting documents"
#extend a subscription for free
free_extend_modal:
extend_subscription: "Abonnement verlängern"

View File

@ -816,6 +816,7 @@ en:
#management of users, labels, groups, and so on
members:
users_management: "Users management"
import: "Import members from a CSV file"
users: "Users"
members: "Members"
subscriptions: "Subscriptions"
@ -919,7 +920,7 @@ en:
import_members: "Import members"
info: "You can upload a CSV file to create new members or update existing ones. Your file must user the identifiers below to specify the group, the trainings and the tags of the members."
required_fields: "Your file must contain, at least, the following information for each user to create: email, name, first name and group. If the password is empty, it will be generated. On updates, the empty fields will be kept as is."
about_example: "Please refer to the provided example file to generate a correct CSV file. Be careful to use Unicode UTF-8 encoding."
about_example_html: "Please refer to the provided example file to generate a correct CSV file.<br>This example will:<ol><li>create a new member (Jean Dupont) with a generated password</li><li>update the password of an existing membre (ID 43) using the new given password</li></ol><br>Be careful to use <strong>Unicode UTF-8</strong> encoding."
groups: "Groups"
group_name: "Group name"
group_identifier: "Identifier to use"

View File

@ -816,6 +816,7 @@ es:
#management of users, labels, groups, and so on
members:
users_management: "Gestión de usuarios"
import: "Import members from a CSV file"
users: "Users"
members: "Miembros"
subscriptions: "Subscriptions"
@ -919,7 +920,7 @@ es:
import_members: "Import members"
info: "You can upload a CSV file to create new members or update existing ones. Your file must user the identifiers below to specify the group, the trainings and the tags of the members."
required_fields: "Your file must contain, at least, the following information for each user to create: email, name, first name and group. If the password is empty, it will be generated. On updates, the empty fields will be kept as is."
about_example: "Please refer to the provided example file to generate a correct CSV file. Be careful to use Unicode UTF-8 encoding."
about_example_html: "Please refer to the provided example file to generate a correct CSV file.<br>This example will:<ol><li>create a new member (Jean Dupont) with a generated password</li><li>update the password of an existing membre (ID 43) using the new given password</li></ol><br>Be careful to use <strong>Unicode UTF-8</strong> encoding."
groups: "Groups"
group_name: "Group name"
group_identifier: "Identifier to use"
@ -1027,6 +1028,7 @@ es:
invalidate_member_success: "The member is invalidated"
validate_member_error: "An error occurred: impossible to validate from this member."
invalidate_member_error: "An error occurred: impossible to invalidate from this member."
supporting_documents: "Supporting documents"
#extend a subscription for free
free_extend_modal:
extend_subscription: "Extend the subscription"

View File

@ -816,6 +816,7 @@ fr:
#management of users, labels, groups, and so on
members:
users_management: "Gestion des utilisateurs"
import: "Importer les membres à partir d'un fichier CSV"
users: "Utilisateurs"
members: "Membres"
subscriptions: "Abonnements"
@ -919,7 +920,7 @@ fr:
import_members: "Importer des membres"
info: "Vous pouvez téléverser un fichier CSV afin de créer des nouveaux membres ou de mettre à jour les existants. Votre fichier doit utiliser les identifiants ci-dessous pour spécifier le groupe, les formations et les étiquettes des membres."
required_fields: "Votre fichier doit obligatoirement comporter, au minimum, les informations suivantes pour chaque utilisateur à créer : courriel, nom, prénom et groupe. Si le mot passe n'est pas rempli, il sera généré automatiquement. Lors d'une mise à jour, les champs non remplis seront gardés tel quels."
about_example: "Merci de vous référer au fichier d'exemple fourni pour générer un fichier CSV au bon format. Attention à l'utiliser l'encodage Unicode UTF-8."
about_example_html: "Veuillez vous référer au fichier d'exemple fourni pour générer un fichier CSV correct.<br>Cet exemple va :<ol><li>créer un nouveau membre (Jean Dupont) avec un mot de passe généré.</li><li>mettre à jour le mot de passe d'un membre existant (ID 43) en utilisant le nouveau mot de passe donné</li></ol><br>Veillez à utiliser l'encodage <strong>Unicode UTF-8</strong>."
groups: "Groupes"
group_name: "Nom du groupe"
group_identifier: "Identifiant à utiliser"
@ -1027,6 +1028,7 @@ fr:
invalidate_member_success: "Le membre est invalidé"
validate_member_error: "Une erreur est survenue : impossible de valider ce membre."
invalidate_member_error: "Une erreur est survenue : impossible d'invalider ce membre."
supporting_documents: "Pièces justificatives"
#extend a subscription for free
free_extend_modal:
extend_subscription: "Prolonger l'abonnement"

View File

@ -816,6 +816,7 @@
#management of users, labels, groups, and so on
members:
users_management: "Brukeradministrasjon"
import: "Import members from a CSV file"
users: "Brukere"
members: "Medlemmer"
subscriptions: "Abonnementer"
@ -919,7 +920,7 @@
import_members: "Importer medlemmer"
info: "Du kan laste opp en CSV-fil for å opprette nye medlemmer eller oppdatere eksisterende. Filen må bruke identifikatoren under for å spesifisere gruppen, opplæring og taggene til medlemmene."
required_fields: "Filen må inneholde minimum følgende informasjon for hver bruker: e-post, navn, fornavn og gruppe. Dersom passordet er tomt, vil det bli generert. Ved oppdateringer vil de tomme feltene bli bevart som de er."
about_example: "Vennligst referer til den oppgitte eksempelfilen for å generere en riktig CSV-fil. Pass på å bruke Unicode UTF-8 koding."
about_example_html: "Please refer to the provided example file to generate a correct CSV file.<br>This example will:<ol><li>create a new member (Jean Dupont) with a generated password</li><li>update the password of an existing membre (ID 43) using the new given password</li></ol><br>Be careful to use <strong>Unicode UTF-8</strong> encoding."
groups: "Grupper"
group_name: "Gruppenavn"
group_identifier: "Identifikator å bruke"
@ -1027,6 +1028,7 @@
invalidate_member_success: "The member is invalidated"
validate_member_error: "An error occurred: impossible to validate from this member."
invalidate_member_error: "An error occurred: impossible to invalidate from this member."
supporting_documents: "Supporting documents"
#extend a subscription for free
free_extend_modal:
extend_subscription: "Forleng abonnementet"

View File

@ -18,7 +18,7 @@ pt:
events: "Eventos"
availabilities: "Disponíveis"
availabilities_notice: "Exportar para Excel livro com todos os slots disponíveis para reserva, e suas ocupações."
select_a_slot: "Please select a slot"
select_a_slot: "Por favor, selecione um slot"
info: "Informações"
tags: "Tags"
slot_duration: "Duração do Slot {DURATION} minutos"
@ -111,8 +111,8 @@ pt:
slots_of: "do"
minutes: "minutos"
deleted_user: "Usuário deletado"
select_type: "Please select a type to continue"
no_modules_available: "No reservable module available. Please enable at least one module (machines, spaces or trainings) in the Customization section."
select_type: "Por favor, selecione um tipo para continuar"
no_modules_available: "Nenhum módulo disponível. Por favor, ative pelo menos um módulo (máquinas, espaços ou treinamentos) na seção de Personalização."
#import external iCal calendar
icalendar:
icalendar_import: "importar iCalendar"
@ -444,32 +444,32 @@ pt:
categories_list: "Lista de categorias do plano"
no_categories: "Sem categorias"
name: "Nome"
description: "Description"
description: "Descrição"
significance: "Importância"
manage_plan_category:
create: "New category"
update: "Edit the category"
create: "Nova categoria"
update: "Editar categoria"
plan_category_form:
name: "Name"
description: "Description"
significance: "Significance"
info: "Categories will be shown ordered by signifiance. The higher you set the significance, the first the category will be shown."
name: "Nome"
description: "Descrição"
significance: "Importância"
info: "As categorias serão mostradas ordenadas por importância. Quanto mais alto você definir a importância, mais em destaque ela estará."
create:
title: "New category"
cta: "Create the category"
success: "The new category was successfully created"
error: "Unable to create the category: "
title: "Nova categoria"
cta: "Criar nova categoria"
success: "A nova categoria foi criada com sucesso"
error: "Não foi possível criar a categoria: "
update:
title: "Edit the category"
cta: "Validate"
success: "The category was successfully updated"
error: "Unable to update the category: "
title: "Editar categoria"
cta: "Validar"
success: "A categoria foi atualizada com sucesso"
error: "Não foi possível atualizar a categoria: "
delete_plan_category:
title: "Delete a category"
confirm: "Are you sure you want to delete this category? If you do, the plans associated with this category won't be sorted anymore."
cta: "Delete"
success: "The category was successfully deleted"
error: "Unable to delete the category: "
title: "Excluir categoria"
confirm: "Tem certeza que deseja excluir esta categoria? Se sim, os planos associados a esta categoria não serão mais classificados."
cta: "Deletar"
success: "A categoria foi excluída com sucesso"
error: "Não foi possível excluir a categoria: "
#ajouter un code promotionnel
coupons_new:
add_a_coupon: "Adicionar cupom"
@ -516,7 +516,7 @@ pt:
credit_note: "Nota de crédito"
display_more_invoices: "Mostrar mais faturas..."
no_invoices_for_now: "Nenhuma fatura."
payment_schedules: "Payment schedules"
payment_schedules: "Agendamento 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"
@ -593,7 +593,7 @@ pt:
VAT_notice: "Este parâmetro configura o caso geral da taxa de imposto e aplica-se a tudo vendido pelo Fablab. É possível sobrepor esse parâmetro definindo uma taxa de imposto específica para cada objeto."
edit_multi_VAT_button: "Mais opções"
multiVAT: "Imposto avançado"
multi_VAT_notice: "<strong>Please note</strong>: The current general rate is {RATE}%. Here you can define different VAT rates for each category.<br><br>For example, you can override this value, only for machine reservations, by filling in the corresponding field below. If no value is filled in, the general rate will apply."
multi_VAT_notice: "<strong>Atenção</strong>: A taxa geral atual é de {RATE}%. Aqui você pode definir diferentes taxas de imposto para cada categoria.<br><br>Por exemplo, você pode substituir este valor, apenas para reservas de máquina, preenchendo o campo correspondente abaixo. Se nenhum valor for preenchido, a taxa geral será aplicada."
VAT_rate_machine: "Reserva de máquina"
VAT_rate_space: "Reserva de espaço"
VAT_rate_training: "Reserva de treinamento"
@ -752,17 +752,17 @@ pt:
vat_rate: "Taxa de imposto"
amount: "Valor total"
payzen_keys_form:
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"
payzen_keys: "PayZen keys"
payzen_username: "Username"
payzen_password: "Password"
payzen_endpoint: "REST API server name"
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"
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"
stripe_keys_form:
stripe_keys_info_html: "<p>To be able to collect online payments, you must configure the <a href='https://stripe.com' target='_blank'>Stripe</a> API keys.</p><p>Retrieve them from <a href='https://dashboard.stripe.com/account/apikeys' target='_blank'>your dashboard</a>.</p><p>Updating these keys will trigger a synchronization of all users on Stripe, this may take some time. You'll receive a notification when it's done.</p>"
public_key: "Public key"
secret_key: "Secret key"
stripe_keys_info_html: "<p>Para coletar pagamentos online, você deve configurar as <a href='https://stripe.com' target='_blank'>chaves de API do Stripe</a>.</p><p>Recupere eles do <a href='https://dashboard.stripe.com/account/apikeys' target='_blank'>seu painel</a>.</p><p>A atualização dessas chaves ativará uma sincronização de todos os usuários no Stripe, isso pode levar algum tempo. Você receberá uma notificação quando estiver pronto.</p>"
public_key: "Chave pública"
secret_key: "Chave secreta"
payment:
payment_settings: "Configurações de pagamento"
online_payment: "Pagamento Online"
@ -780,28 +780,28 @@ pt:
stripe_currency: "Moeda do Stripe"
gateway_configuration_error: "Ocorreu um erro ao configurar o gateway de pagamento: "
payzen_settings:
edit_keys: "Edit keys"
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}."
edit_keys: "Editar chaves"
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
select_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_list:
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: "Nenhum calendário de pagamentos para exibir"
load_more: "Ver mais"
card_updated_success: "O cartão do usuário foi atualizado com sucesso"
document_filters:
reference: "Referências"
customer: "Cliente"
@ -816,6 +816,7 @@ pt:
#management of users, labels, groups, and so on
members:
users_management: "Gerenciamento de usuários"
import: "Import members from a CSV file"
users: "Usuários"
members: "Membros"
subscriptions: "Assinaturas"
@ -913,13 +914,13 @@ pt:
members_new:
add_a_member: "Adicionar membro"
user_is_an_organization: "Usuário é uma organização"
create_success: "Member successfully created"
create_success: "Membro criado com sucesso"
#members bulk import
members_import:
import_members: "Importar membros"
info: "Você pode enviar um arquivo CSV para criar novos membros ou atualizar os já existentes. Seu arquivo deve utilizar os identificadores abaixo para especificar o grupo, os treinamentos e as tags dos membros."
required_fields: "Seu arquivo deve conter, pelo menos, as seguintes informações para cada usuário criar: e-mail, nome, nome e grupo. Se a senha estiver vazia, ela será gerada. Em atualizações, os campos vazios serão mantidos como está."
about_example: "Por favor, consulte o arquivo de exemplo fornecido para gerar um arquivo CSV correto. Tenha cuidado ao usar a codificação Unicode UTF-8."
about_example_html: "Please refer to the provided example file to generate a correct CSV file.<br>This example will:<ol><li>create a new member (Jean Dupont) with a generated password</li><li>update the password of an existing membre (ID 43) using the new given password</li></ol><br>Be careful to use <strong>Unicode UTF-8</strong> encoding."
groups: "Grupos"
group_name: "Nome do grupo"
group_identifier: "Identificador a ser usado"
@ -951,25 +952,25 @@ pt:
failed: "Falhou"
error_details: "Detalhes do erro:"
user_validation:
validate_member_success: "Member successfully validated"
invalidate_member_success: "Member successfully invalidated"
validate_member_error: "An unexpected error occurred: unable to validate this member."
invalidate_member_error: "An unexpected error occurred: unable to invalidate this member."
validate_account: "Validate the account"
validate_member_success: "Membro validado com sucesso"
invalidate_member_success: "Membro invalidado com sucesso"
validate_member_error: "Ocorreu um erro inesperado: incapaz de validar este membro."
invalidate_member_error: "Ocorreu um erro inesperado: impossível invalidar este membro."
validate_account: "Validar conta"
supporting_documents_refusal_form:
refusal_comment: "Comment"
comment_placeholder: "Please type a comment here"
refusal_comment: "Comentário"
comment_placeholder: "Por favor, digite um comentário aqui"
supporting_documents_refusal_modal:
title: "Refuse some supporting documents"
refusal_successfully_sent: "The refusal has been successfully sent."
unable_to_send: "Unable to refuse the supporting documents: "
confirm: "Confirm"
title: "Recusar documentos"
refusal_successfully_sent: "A recusa foi enviada com sucesso."
unable_to_send: "Não é possível recusar os documentos: "
confirm: "Confirmar"
supporting_documents_validation:
title: "Supporting documents"
find_below_documents_files: "You will find below the supporting documents submitted by the member."
to_complete: "To complete"
refuse_documents: "Refusing the documents"
refuse_documents_info: "After verification, you may notify the member that the evidence submitted is not acceptable. You can specify the reasons for your refusal and indicate the actions to be taken. The member will be notified by e-mail."
title: "Documentos"
find_below_documents_files: "Você encontrará abaixo os documentos enviados pelo membro."
to_complete: "Completar"
refuse_documents: "Recusando documentos"
refuse_documents_info: "Após a verificação, poderá notificar o membro de que os documentos enviados não foram aceitos. Você pode especificar as razões de sua recusa e indicar as ações a tomar. O membro será notificado por e-mail."
#edit a member
members_edit:
change_role: "Alterar cargo"
@ -1017,16 +1018,17 @@ pt:
to_credit: 'Crédito'
cannot_credit_own_wallet: "Você não pode creditar sua própria carteira. Por favor, peça a outro gerente ou a um administrador para creditar sua carteira."
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."
update_success: "Member's profile successfully updated"
my_documents: "My documents"
save: "Save"
confirm: "Confirm"
cancel: "Cancel"
validate_account: "Validate the account"
validate_member_success: "The member is validated"
invalidate_member_success: "The member is invalidated"
validate_member_error: "An error occurred: impossible to validate from this member."
invalidate_member_error: "An error occurred: impossible to invalidate from this member."
update_success: "Perfil do membro atualizado com sucesso"
my_documents: "Meus documentos"
save: "Salvar"
confirm: "Confirmar"
cancel: "Cancelar"
validate_account: "Validar conta"
validate_member_success: "O membro foi validado"
invalidate_member_success: "O membro foi invalidado"
validate_member_error: "Ocorreu um erro: impossível validar este membro."
invalidate_member_error: "Ocorreu um erro: impossível invalidar este membro."
supporting_documents: "Documentos"
#extend a subscription for free
free_extend_modal:
extend_subscription: "Estender assinatura"
@ -1040,15 +1042,15 @@ pt:
extend_success: "A assinatura foi estendida com sucesso gratuitamente"
#renew a subscription
renew_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 uma só vez"
renew: "Renovar"
renew_success: "A assinatura foi renovada com sucesso"
DATE_TIME: "{DATE} {TIME}"
#take a new subscription
subscribe_modal:
@ -1056,7 +1058,7 @@ pt:
subscribe: "Inscrever-se"
select_plan: "Por favor, selecione um plano"
pay_in_one_go: "Paga á vista"
subscription_success: "Subscription successfully subscribed"
subscription_success: "Assinatura assinada com sucesso"
#add a new administrator to the platform
admins_new:
add_an_administrator: "Adicionar administrador"
@ -1096,97 +1098,97 @@ pt:
#authentication providers (SSO) components
authentication:
boolean_mapping_form:
mappings: "Mappings"
true_value: "True value"
false_value: "False value"
mappings: "Mapeamentos"
true_value: "Valor verdadeiro"
false_value: "Valor falso"
date_mapping_form:
input_format: "Input format"
date_format: "Date format"
input_format: "Formato de entrada"
date_format: "Formato da Data"
integer_mapping_form:
mappings: "Mappings"
mapping_from: "From"
mapping_to: "To"
mappings: "Mapeamentos"
mapping_from: "De"
mapping_to: "Até"
string_mapping_form:
mappings: "Mappings"
mapping_from: "From"
mapping_to: "To"
mappings: "Mapeamentos"
mapping_from: "De"
mapping_to: "Até"
data_mapping_form:
define_the_fields_mapping: "Define the fields mapping"
add_a_match: "Add a match"
model: "Model"
field: "Field"
data_mapping: "Data mapping"
define_the_fields_mapping: "Defina o mapeamento dos campos"
add_a_match: "Adicionar uma correspondência"
model: "Modelo"
field: "Campo"
data_mapping: "Mapeamento de dados"
oauth2_data_mapping_form:
api_endpoint_url: "API endpoint or URL"
api_type: "API type"
api_field: "API field"
api_field_help_html: '<a href="https://jsonpath.com/" target="_blank">JsonPath</a> syntax is supported.<br> If many fields are selected, the first one will be used.<br> Example: $.data[*].name'
api_endpoint_url: "API endpoint URL"
api_type: "Tipo de API"
api_field: "Campo API"
api_field_help_html: '<a href="https://jsonpath.com/" target="_blank">A sintaxe</a> JsonPath é suportada.<br> Se muitos campos forem selecionados, o primeiro será usado.<br> Exemplo: $.data[*].name'
openid_connect_data_mapping_form:
api_field: "Userinfo claim"
api_field_help_html: 'Set the field providing the corresponding data through <a href="https://openid.net/specs/openid-connect-core-1_0.html#Claims" target="_blank">the userinfo endpoint</a>.<br> <a href="https://jsonpath.com/" target="_blank">JsonPath</a> syntax is supported. If many fields are selected, the first one will be used.<br> <b>Example</b>: $.data[*].name'
openid_standard_configuration: "Use the OpenID standard configuration"
api_field: "Userinfo"
api_field_help_html: 'Defina o campo fornecendo os dados correspondentes através de <a href="https://openid.net/specs/openid-connect-core-1_0.html#Claims" target="_blank">o ponto de extremidade de userinfo</a>.<br> <a href="https://jsonpath.com/" target="_blank">JsonPath</a> sintaxe é suportada. Se muitos campos forem selecionados, o primeiro será usado.<br> <b>Exemplo</b>: $.data[*].name'
openid_standard_configuration: "Usar a configuração padrão OpenID"
type_mapping_modal:
data_mapping: "Data mapping"
TYPE_expected: "{TYPE} expected"
data_mapping: "Mapeamento de dados"
TYPE_expected: "{TYPE} esperado"
types:
integer: "integer"
integer: "número Inteiro"
string: "string"
text: "text"
date: "date"
boolean: "boolean"
text: "texto"
date: "data"
boolean: "booleano"
oauth2_form:
authorization_callback_url: "Authorization callback URL"
common_url: "Server root URL"
authorization_endpoint: "Authorization endpoint"
token_acquisition_endpoint: "Token acquisition endpoint"
profile_edition_url: "Profil edition URL"
profile_edition_url_help: "The URL of the page where the user can edit his profile."
authorization_callback_url: "Callback URL autorização"
common_url: "URL raiz do servidor"
authorization_endpoint: "Endpoint da autorização"
token_acquisition_endpoint: "Endpoint da aquisição de token"
profile_edition_url: "URL da edição do Perfil"
profile_edition_url_help: "A URL da página onde o usuário pode editar seu perfil."
client_identifier: "Client identifier"
client_secret: "Client secret"
client_secret: "Chave secreta"
scopes: "Scopes"
openid_connect_form:
issuer: "Issuer"
issuer_help: "Root url for the authorization server."
discovery: "Discovery"
discovery_help: "Should OpenID discovery be used. This is recommended if the IDP provides a discovery endpoint."
discovery_unavailable: "Discovery is unavailable for the configured issuer."
discovery_enabled: "Enable discovery"
discovery_disabled: "Disable discovery"
client_auth_method: "Client authentication method"
client_auth_method_help: "Which authentication method to use to authenticate Fab-manager with the authorization server."
client_auth_method_basic: "Basic"
issuer: "Emissor"
issuer_help: "Url raiz para o servidor de autorização."
discovery: "Descoberta"
discovery_help: "A descoberta do OpenID deve ser usada. Isto é recomendado se o IDP fornecer um ponto de descoberta."
discovery_unavailable: "A descoberta está indisponível para o emissor configurado."
discovery_enabled: "Ativar a descoberta"
discovery_disabled: "Desativar a descoberta"
client_auth_method: "Método de autenticação do cliente"
client_auth_method_help: "Qual método de autenticação usar para autenticar o Fab-manager com o servidor de autorização."
client_auth_method_basic: "Básico"
client_auth_method_jwks: "JWKS"
scope: "Scope"
scope_help_html: "Which OpenID scopes to include (openid is always required). <br> If <b>Discovery</b> is enabled, the available scopes will be automatically proposed."
scope_help_html: "Quais escopos OpenID a incluir (openid é sempre necessário). <br> Se <b>descoberta</b> estiver habilitada, os escopos disponíveis serão automaticamente propostos."
prompt: "Prompt"
prompt_help_html: "Which OpenID pages the user will be shown. <br> <b>None</b> - no authentication or consent user interface pages are shown. <br> <b>Login</b> - the authorization server prompt the user for reauthentication. <br> <b>Consent</b> - the authorization server prompt the user for consent before returning information to Fab-manager. <br> <b>Select account</b> - the authorization server prompt the user to select a user account."
prompt_none: "None"
prompt_help_html: "Quais páginas OpenID o usuário será exibido. <br> <b>Nenhum</b> - nenhuma autenticação ou consentimento de páginas de interface de usuário são mostradas. <br> <b>Login</b> - o servidor de autorização solicita reautenticação ao usuário. <br> <b>Consentimento</b> - o servidor de autorização solicitou consentimento ao usuário antes de retornar informações para o Fab-manager. <br> <b>Selecione a conta</b> - o servidor de autorização solicita ao usuário que selecione uma conta de usuário."
prompt_none: "Nenhum"
prompt_login: "Login"
prompt_consent: "Consent"
prompt_select_account: "Select account"
send_scope_to_token_endpoint: "Send scope to token endpoint?"
send_scope_to_token_endpoint_help: "Should the scope parameter be sent to the authorization token endpoint?"
send_scope_to_token_endpoint_false: "No"
send_scope_to_token_endpoint_true: "Yes"
profile_edition_url: "Profil edition URL"
profile_edition_url_help: "The URL of the page where the user can edit his profile."
client_options: "Client options"
client__identifier: "Identifier"
client__secret: "Secret"
client__authorization_endpoint: "Authorization endpoint"
client__token_endpoint: "Token endpoint"
client__userinfo_endpoint: "Userinfo endpoint"
prompt_consent: "Consentimento"
prompt_select_account: "Selecionar conta"
send_scope_to_token_endpoint: "Enviar escopo para o token endpoint?"
send_scope_to_token_endpoint_help: "O parâmetro do escopo deve ser enviado para o terminal de autorização do token?"
send_scope_to_token_endpoint_false: "Não"
send_scope_to_token_endpoint_true: "Sim"
profile_edition_url: "URL da edição do Perfil"
profile_edition_url_help: "A URL da página onde o usuário pode editar seu perfil."
client_options: "Opções do cliente"
client__identifier: "Identificador"
client__secret: "Senha"
client__authorization_endpoint: "Endpoint da autorização"
client__token_endpoint: "Endpoint do token"
client__userinfo_endpoint: "Endpoint do userinfo"
client__jwks_uri: "JWKS URI"
client__end_session_endpoint: "End session endpoint"
client__end_session_endpoint_help: "The url to call to log the user out at the authorization server."
client__end_session_endpoint: "Endpoint de término de sessão"
client__end_session_endpoint_help: "O Url para efetuar uma chamada para desconectar o usuário no servidor de autorização."
provider_form:
name: "Name"
authentication_type: "Authentication type"
save: "Save"
create_success: "Authentication provider created"
update_success: "Authentication provider updated"
name: "Nome"
authentication_type: "Tipo de autenticação"
save: "Salvar"
create_success: "Provedor de autenticação criado"
update_success: "Provedor de autenticação atualizado"
methods:
local_database: "Local database"
local_database: "Database local"
oauth2: "OAuth 2.0"
openid_connect: "OpenID Connect"
#create a new authentication provider (SSO)
@ -1257,10 +1259,10 @@ pt:
no_data_for_this_period: "Nenhum dado para este período"
date: "Data"
boolean_setting:
customization_of_SETTING_successfully_saved: "Customization of the {SETTING} successfully saved."
error_SETTING_locked: "Unable to update the setting: {SETTING} is locked. Please contact your system administrator."
an_error_occurred_saving_the_setting: "An error occurred while saving the setting. Please try again later."
save: "save"
customization_of_SETTING_successfully_saved: "Personalização do {SETTING} salvo com êxito."
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."
save: "salvar"
#global application parameters and customization
settings:
customize_the_application: "Customizar a aplicação"
@ -1305,7 +1307,7 @@ pt:
title_of_the_about_page: "Título da página sobre"
shift_enter_to_force_carriage_return: "SHIFT + ENTER para forçar o retorno"
input_the_main_content: "Introduza o conteúdo principal"
drag_and_drop_to_insert_images: "Drag and drop to insert images"
drag_and_drop_to_insert_images: "Arrastar e soltar para inserir imagens"
input_the_fablab_contacts: "Insira os contatos do FabLab"
reservations: "Reservas"
reservations_parameters: "Parâmetros das reservas"
@ -1354,8 +1356,8 @@ pt:
about_title: "\"Sobre\" título da página"
about_body: "\"Sobre\" conteúdo da página"
about_contacts: "\"Sobre\" página de contatos"
about_follow_us: "Follow us"
about_networks: "Social networks"
about_follow_us: "Siga-nos"
about_networks: "Redes sociais"
privacy_draft: "rascunho da política de privacidade"
privacy_body: "política de privacidade"
privacy_dpo: "endereço do oficial de proteção de dados"
@ -1401,10 +1403,10 @@ pt:
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."
modules: "Modulos"
machines: "Machines"
machines_info_html: "The module Reserve a machine module can be disabled."
enable_machines: "Enable the machines"
machines_module: "machines module"
machines: "Máquinas"
machines_info_html: "O módulo Reserva de uma máquina pode ser desabilitado."
enable_machines: "Ativar as máquinas"
machines_module: "módulo de Máquinas"
spaces: "Espaços"
spaces_info_html: "<p>Uma sala pode ser, por exemplo, uma floresta ou uma sala de reunião. A sua especificidade é que podem ser reservados por várias pessoas ao mesmo tempo.</p><p><strong>Aviso:</strong> não é recomendado desabilitar espaços se pelo menos uma reserva de espaço for feita no sistema.</p>"
enable_spaces: "Ativar os espaços"
@ -1414,7 +1416,7 @@ pt:
enable_plans: "Ativar os planos"
plans_module: "módulo de planos"
trainings: "Treinamentos"
trainings_info_html: "<p>Trainings are fully integrated Fab-manager'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>"
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"
@ -1447,10 +1449,10 @@ pt:
account_confirmation: "Confirmação da Conta"
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"
change_group: "Group change"
change_group_info: "After an user has created his account, you can restrict him from changing his group. In that case, only managers and administrators will be able to change the user's group."
allow_group_change: "Allow group change"
user_change_group: "users can change their group"
change_group: "Alteração de grupo"
change_group_info: "Depois que um usuário criar a sua conta, você pode restringi-lo em mudar o seu grupo. Nesse caso, apenas gerentes e administradores poderão alterar o grupo de usuários."
allow_group_change: "Permitir mudança de grupo"
user_change_group: "usuários podem mudar seu grupo"
wallet_module: "módulo de carteira"
public_agenda_module: "módulo da agenda pública"
statistics_module: "módulo de estatísticas"
@ -1505,7 +1507,7 @@ pt:
notifications: "Nofificações"
email: "Email"
email_info: "O endereço de e-mail do qual as notificações serão enviadas. Você pode usar um endereço não existente (como noreply@..) ou um endereço existente se você quiser permitir que seus membros respondam às notificações que receberão."
email_from: "Endereço do expeditor"
email_from: "Endereço de envio"
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"
@ -1516,67 +1518,67 @@ pt:
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"
account:
account: "Account"
customize_account_settings: "Customize account settings"
user_validation_required: "validation of accounts"
user_validation_required_title: "Validation of accounts"
user_validation_required_info: "By activating this option, only members whose account is validated by an administrator or a manager will be able to make reservations."
account: "Conta"
customize_account_settings: "Personalizar configurações de conta"
user_validation_required: "validação de contas"
user_validation_required_title: "Validação de contas"
user_validation_required_info: "Ao ativar esta opção, somente membros cuja conta seja validada por um administrador ou gerente poderão fazer reservas."
user_validation_setting:
customization_of_SETTING_successfully_saved: "Customization of the {SETTING} successfully saved."
error_SETTING_locked: "Unable to update the setting: {SETTING} is locked. Please contact your system administrator."
an_error_occurred_saving_the_setting: "An error occurred while saving the setting. Please try again later."
user_validation_required_option_label: "Activate the account validation option"
user_validation_required_list_title: "Member account validation information message"
user_validation_required_list_info: "Your administrator must validate your account. Then, you will be able to access all the booking features."
user_validation_required_list_other_info: "The resources selected below will be subject to member account validation."
save: "Save"
customization_of_SETTING_successfully_saved: "Personalização do {SETTING} salvo com êxito."
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."
user_validation_required_option_label: "Ativar a opção de validação de conta"
user_validation_required_list_title: "Mensagem de informações de validação de conta membro"
user_validation_required_list_info: "O seu administrador deve validar sua conta. Só após você poderá acessar todos os recursos de reserva."
user_validation_required_list_other_info: "Os recursos selecionados abaixo estarão sujeitos à validação de conta de membro."
save: "Salvar"
user_validation_required_list:
subscription: "Subscriptions"
machine: "Machines"
event: "Events"
space: "Spaces"
training: "Trainings"
pack: "Prepaid pack"
confirm: "Confirm"
confirmation_required: "Confirmation required"
organization: "Organization"
organization_profile_custom_fields_info: "You can display additional fields for users who declare themselves to be an organization. You can also choose to make them mandatory at account creation."
organization_profile_custom_fields_alert: "Warning: the activated fields will be automatically displayed on the issued invoices. Once configured, please do not modify them."
subscription: "Inscrições"
machine: "Máquinas"
event: "Eventos"
space: "Espaços"
training: "Treinamentos"
pack: "Pacotes pré-pagos"
confirm: "Confirmar"
confirmation_required: "Confirmação Obrigatória"
organization: "Organização"
organization_profile_custom_fields_info: "Você pode exibir campos adicionais para os usuários que declararem ser uma organização. Você também pode optar por torná-los obrigatórios na criação de contas."
organization_profile_custom_fields_alert: "Atenção: os campos ativados serão exibidos automaticamente nas faturas emitidas. Uma vez configurado, por favor, não os modifique."
supporting_documents_type_modal:
successfully_created: "The new supporting documents request has been created."
unable_to_create: "Unable to delete the supporting documents request: "
successfully_updated: "The supporting documents request has been updated."
unable_to_update: "Unable to modify the supporting documents request: "
new_type: "Create a supporting documents request"
edit_type: "Edit the supporting documents request"
create: "Create"
edit: "Edit"
successfully_created: "A nova solicitação de documentos de suporte foi criada."
unable_to_create: "Não foi possível excluir a solicitação de documentos: "
successfully_updated: "A solicitação de documentos foi atualizada."
unable_to_update: "Não é possível modificar a solicitação de documentos: "
new_type: "Criar uma solicitação de documentos"
edit_type: "Editar a solicitação de documentos"
create: "Criar"
edit: "Editar"
supporting_documents_type_form:
type_form_info: "Please define the supporting documents request settings below"
select_group: "Choose one or many group(s)"
name: "Name"
type_form_info: "Por favor, defina as configurações de solicitação de documentos abaixo"
select_group: "Escolha um ou mais grupo(s)"
name: "Nome"
supporting_documents_types_list:
add_supporting_documents_types: "Add supporting documents"
all_groups: 'All groups'
supporting_documents_type_info: "You can ask for supporting documents, according to the user's groups. This will ask your members to deposit those kind of documents in their personnal space. Each members will be informed that supporting documents are required to be provided in their personal space (My supporting documents tab). On your side, you'll be able to check the provided supporting documents and validate the member's account (if the Account Validation option is enabled)."
no_groups_info: "Supporting documents are necessarily applied to groups.<br>If you do not have any group yet, you can create one from the \"Users/Groups\" page (button on the right)."
create_groups: "Create groups"
supporting_documents_type_title: "Supporting documents requests"
add_type: "New supporting documents request"
group_name: "Group"
name: "Supporting documents"
no_types: "You do not have any supporting documents requests.<br>Make sure you have created at least one group in order to add a request."
add_supporting_documents_types: "Adicionar documentos"
all_groups: 'Todos os grupos'
supporting_documents_type_info: "Você pode pedir documentos de acordo com os grupos de usuário. Isso pedirá aos seus membros que deposite esses documentos no seu espaço pessoal. Cada membro será informado que os documentos de suporte devem ser fornecidos no seu espaço pessoal (Guia Meus documentos de suporte). Do seu lado, você poderá verificar os documentos de apoio fornecidos e validar a conta do membro (se a opção de validação da conta estiver ativada)."
no_groups_info: "Os documentos de apoio são necessariamente aplicados a grupos.<br>Se você não tiver nenhum grupo ainda, é possível criar um a partir da página \"Usuários/Grupos\" (botão à direita)."
create_groups: "Criar grupos"
supporting_documents_type_title: "Solicitações de documentos"
add_type: "Nova solicitação de documentos"
group_name: "Grupo"
name: "Documentos"
no_types: "Você não tem nenhuma solicitação de documentos.<br>Certifique-se de ter criado pelo menos um grupo para adicionar uma solicitação."
delete_supporting_documents_type_modal:
confirmation_required: "Confirmation required"
confirm: "Confirm"
deleted: "The supporting documents request has been deleted."
unable_to_delete: "Unable to delete the supporting documents request: "
confirm_delete_supporting_documents_type: "Do you really want to remove this requested type of supporting documents?"
confirmation_required: "Confirmação Obrigatória"
confirm: "Confirmar"
deleted: "O pedido de documentos foi excluído."
unable_to_delete: "Não foi possível excluir a solicitação de documentos: "
confirm_delete_supporting_documents_type: "Você realmente deseja remover este tipo de documentos solicitados?"
profile_custom_fields_list:
field_successfully_updated: "The organization field has been updated."
unable_to_update: "Impossible to modify the field : "
required: "Confirmation required"
actived: "Activate the field"
field_successfully_updated: "O campo organização foi atualizado."
unable_to_update: "Impossível modificar o campo: "
required: "Confirmação Obrigatória"
actived: "Ativar o campo"
home:
show_upcoming_events: "Mostrar próximos eventos"
upcoming_events:
@ -1658,19 +1660,19 @@ pt:
report_removed: "O relatório foi eliminado"
failed_to_remove: "Ocorreu um erro, não é possível excluir o relatório"
local_payment_form:
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"
method_transfer: "By bank transfer"
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."
transfer_collection_info: "<p>By validating, you confirm that you set up {DEADLINES} bank direct debits, allowing you to collect all the monthly payments.</p><p><strong>Please note:</strong> the bank transfers are not automatically handled by Fab-manager.</p>"
online_payment_disabled: "Online payment is not available. You cannot collect this payment schedule by online card."
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"
method_transfer: "Por transferência bancária"
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} cheques, permitindo que você colete todos os pagamentos mensais."
transfer_collection_info: "<p>Ao validar, você confirma que configurou {DEADLINES} débitos diretos do banco, permitindo que você colete todos os pagamentos mensais.</p><p><strong>Atenção:</strong> as transferências bancárias não são tratadas automaticamente pelo Fab-manager.</p>"
online_payment_disabled: "Pagamento online não está disponível. Você não pode coletar este horário de pagamento com cartão online."
local_payment_modal:
validate_cart: "Validate my cart"
offline_payment: "Payment on site"
validate_cart: "Validar o meu carrinho"
offline_payment: "Pagamento pelo site"
check_list_setting:
save: 'Salvar'
customization_of_SETTING_successfully_saved: "Personalização do {SETTING} salvo com êxito."

View File

@ -816,6 +816,7 @@ zu:
#management of users, labels, groups, and so on
members:
users_management: "crwdns7735:0crwdne7735:0"
import: "crwdns23710:0crwdne23710:0"
users: "crwdns20306:0crwdne20306:0"
members: "crwdns7737:0crwdne7737:0"
subscriptions: "crwdns7739:0crwdne7739:0"
@ -919,7 +920,7 @@ zu:
import_members: "crwdns7881:0crwdne7881:0"
info: "crwdns7883:0crwdne7883:0"
required_fields: "crwdns7885:0crwdne7885:0"
about_example: "crwdns7887:0crwdne7887:0"
about_example_html: "crwdns23712:0crwdne23712:0"
groups: "crwdns7889:0crwdne7889:0"
group_name: "crwdns7891:0crwdne7891:0"
group_identifier: "crwdns7893:0crwdne7893:0"
@ -1027,6 +1028,7 @@ zu:
invalidate_member_success: "crwdns22544:0crwdne22544:0"
validate_member_error: "crwdns22546:0crwdne22546:0"
invalidate_member_error: "crwdns22548:0crwdne22548:0"
supporting_documents: "crwdns23678:0crwdne23678:0"
#extend a subscription for free
free_extend_modal:
extend_subscription: "crwdns22047:0crwdne22047:0"

View File

@ -142,6 +142,20 @@ de:
save: "Speichern"
browse: "Durchsuchen"
edit: "Bearbeiten"
reservations:
reservations_panel:
title_Space: "My space reservations"
title_Machine: "My machines reservations"
upcoming: "Upcoming"
past: "Past"
slots_details: "Slots details"
no_reservations: "No reservations"
show_more: "Show more"
credits_panel:
title_Space: "My space credits"
title_Machine: "My machines credits"
reamaining_credits_html: "<strong>{NAME}</strong>: You can book <em>{REMAINING} {REMAINING, plural, one{slot} other{slots}}</em> for free. You have already used {USED} {USED, plural, one{credit} other{credits}} from your current subscription."
no_credits: "You don't have any credits yet. Some subscriptions may allow you to book some slots for free."
#public profil of a member
members_show:
members_list: "Mitgliederliste"

View File

@ -142,6 +142,20 @@ en:
save: "Save"
browse: "Browse"
edit: "Edit"
reservations:
reservations_panel:
title_Space: "My space reservations"
title_Machine: "My machines reservations"
upcoming: "Upcoming"
past: "Past"
slots_details: "Slots details"
no_reservations: "No reservations"
show_more: "Show more"
credits_panel:
title_Space: "My space credits"
title_Machine: "My machines credits"
reamaining_credits_html: "<strong>{NAME}</strong>: You can book <em>{REMAINING} {REMAINING, plural, one{slot} other{slots}}</em> for free. You have already used {USED} {USED, plural, one{credit} other{credits}} from your current subscription."
no_credits: "You don't have any credits yet. Some subscriptions may allow you to book some slots for free."
#public profil of a member
members_show:
members_list: "Members list"

View File

@ -142,6 +142,20 @@ es:
save: "Save"
browse: "Browse"
edit: "Edit"
reservations:
reservations_panel:
title_Space: "My space reservations"
title_Machine: "My machines reservations"
upcoming: "Upcoming"
past: "Past"
slots_details: "Slots details"
no_reservations: "No reservations"
show_more: "Show more"
credits_panel:
title_Space: "My space credits"
title_Machine: "My machines credits"
reamaining_credits_html: "<strong>{NAME}</strong>: You can book <em>{REMAINING} {REMAINING, plural, one{slot} other{slots}}</em> for free. You have already used {USED} {USED, plural, one{credit} other{credits}} from your current subscription."
no_credits: "You don't have any credits yet. Some subscriptions may allow you to book some slots for free."
#public profil of a member
members_show:
members_list: "Lista de miembros"

View File

@ -142,6 +142,20 @@ fr:
save: "Enregistrer"
browse: "Parcourir"
edit: "Modifier"
reservations:
reservations_panel:
title_Space: "Mes réservations d'espace"
title_Machine: "Mes réservations de machines"
upcoming: "À venir"
past: "Passées"
slots_details: "Détails des créneaux"
no_reservations: "Aucune réservation"
show_more: "Afficher plus"
credits_panel:
title_Space: "Mes crédits espace"
title_Machine: "Mes crédits machine"
reamaining_credits_html: "<strong>{NAME}</strong> : Vous pouvez réserver gratuitement <em>{REMAINING} {REMAINING, plural, =0{créneau} one{créneau} other{créneaux}}</em>. Vous avez déjà utilisé {USED} {USED, plural, =0{crédit} one{crédit} other{crédits}} de votre abonnement actuel."
no_credits: "Vous n'avez pas encore de crédits. Certains abonnements peuvent vous permettre de réserver des créneaux gratuitement."
#public profil of a member
members_show:
members_list: "Liste des membres"

View File

@ -142,6 +142,20 @@
save: "Lagre"
browse: "Bla igjennom"
edit: "Rediger"
reservations:
reservations_panel:
title_Space: "My space reservations"
title_Machine: "My machines reservations"
upcoming: "Upcoming"
past: "Past"
slots_details: "Slots details"
no_reservations: "No reservations"
show_more: "Show more"
credits_panel:
title_Space: "My space credits"
title_Machine: "My machines credits"
reamaining_credits_html: "<strong>{NAME}</strong>: You can book <em>{REMAINING} {REMAINING, plural, one{slot} other{slots}}</em> for free. You have already used {USED} {USED, plural, one{credit} other{credits}} from your current subscription."
no_credits: "You don't have any credits yet. Some subscriptions may allow you to book some slots for free."
#public profil of a member
members_show:
members_list: "Medlemsliste"

View File

@ -24,18 +24,18 @@ pt:
_the_fablab_policy: "a política do FabLab"
your_profile_has_been_successfully_updated: "Seu perfil foi actualizado com êxito."
completion_header_info:
rules_changed: "Please fill the following form to update your profile and continue to use the platform."
rules_changed: "Por favor, preencha o formulário a seguir para atualizar seu perfil e continuar a usar a plataforma."
sso_intro: "Você acabou de criar uma nova conta como {GENDER, select, male{o} female{a} neutral{} other{do}} {NAME}"
duplicate_email_info: "It looks like your email address is already used by another user. Check your email address and please input below the code you have received."
details_needed_info: "To finalize your account, we need some more details."
duplicate_email_info: "Parece que seu endereço de e-mail já é usado por outro usuário. Verifique seu endereço de e-mail e insira abaixo o código que você recebeu."
details_needed_info: "Para finalizar a sua conta, precisamos de mais alguns detalhes."
profile_form_option:
title: "Novo nessa plataforma?"
please_fill: "Please fill in the following form to create your account."
please_fill: "Por favor, preencha o seguinte formulário para criar sua conta."
disabled_data_from_sso: "Alguns dados podem já ter sido fornecidos por {NAME} e não podem ser modificados."
confirm_instructions_html: "Once you are done, please click on <strong>Save</strong> to confirm your account and start using the application."
duplicate_email_html: "It looks like your email address <strong>({EMAIL})</strong> is already associated with another account. If this account is not yours, please click on the following button to change the email associated with your {PROVIDER} account."
confirm_instructions_html: "Depois de finalizar, clique em <strong>Salvar</strong> para confirmar sua conta e começar a usar o aplicativo."
duplicate_email_html: "Parece que o endereço de e-mail <strong>({EMAIL})</strong> já está associado a outra conta. Se esta conta não é sua, por favor, clique no botão a seguir para alterar o e-mail associado à sua conta do {PROVIDER}."
edit_profile: "Alterar meus dados"
after_edition_info_html: "Once your data are up to date, <strong>click on the synchronization button below</strong>, or <strong>disconnect then reconnect</strong> for your changes to take effect."
after_edition_info_html: "Após atualizar seus dados, <strong>clique no botão de sincronização abaixo</strong>, ou <strong>desconecte-se, então reconecte</strong> para que as alterações tenham efeito."
sync_profile: "Sincronizar meu perfil"
dashboard:
#dashboard: public profile
@ -133,15 +133,29 @@ pt:
load_more: "Ver mais"
card_updated_success: "O seu cartão foi atualizado com sucesso"
supporting_documents_files:
file_successfully_uploaded: "The supporting documents were sent."
unable_to_upload: "Unable to send the supporting documents: "
supporting_documents_files: "Supporting documents"
my_documents_info: "Due to your group declaration, some supporting documents are required. Once submitted, these documents will be verified by the administrator."
upload_limits_alert_html: "Warning!<br>You can submit your documents as PDF or images (JPEG, PNG). Maximum allowed size: {SIZE} Mb"
file_size_error: "The file size exceeds the limit ({SIZE} MB)"
file_successfully_uploaded: "Os documentos foram enviados."
unable_to_upload: "Não foi possível enviar os documentos: "
supporting_documents_files: "Documentos"
my_documents_info: "Devido a sua declaração de grupo, alguns documentos são necessários. Uma vez enviados, esses documentos serão verificados pelo administrador."
upload_limits_alert_html: "Aviso!<br>Você pode enviar seus documentos como PDF ou imagens (JPEG, PNG). Tamanho máximo permitido: {SIZE} Mb"
file_size_error: "O tamanho do arquivo excede o limite de ({SIZE} MB)"
save: "Salvar"
browse: "Navegar"
edit: "Editar"
reservations:
reservations_panel:
title_Space: "My space reservations"
title_Machine: "My machines reservations"
upcoming: "Upcoming"
past: "Past"
slots_details: "Slots details"
no_reservations: "No reservations"
show_more: "Show more"
credits_panel:
title_Space: "My space credits"
title_Machine: "My machines credits"
reamaining_credits_html: "<strong>{NAME}</strong>: You can book <em>{REMAINING} {REMAINING, plural, one{slot} other{slots}}</em> for free. You have already used {USED} {USED, plural, one{credit} other{credits}} from your current subscription."
no_credits: "You don't have any credits yet. Some subscriptions may allow you to book some slots for free."
#public profil of a member
members_show:
members_list: "Lista de membros"

View File

@ -142,6 +142,20 @@ zu:
save: "crwdns23538:0crwdne23538:0"
browse: "crwdns23540:0crwdne23540:0"
edit: "crwdns23542:0crwdne23542:0"
reservations:
reservations_panel:
title_Space: "crwdns23682:0crwdne23682:0"
title_Machine: "crwdns23684:0crwdne23684:0"
upcoming: "crwdns23686:0crwdne23686:0"
past: "crwdns23704:0crwdne23704:0"
slots_details: "crwdns23690:0crwdne23690:0"
no_reservations: "crwdns23692:0crwdne23692:0"
show_more: "crwdns23694:0crwdne23694:0"
credits_panel:
title_Space: "crwdns23696:0crwdne23696:0"
title_Machine: "crwdns23698:0crwdne23698:0"
reamaining_credits_html: "crwdns23706:0NAME={NAME}crwdnd23706:0REMAINING={REMAINING}crwdnd23706:0REMAINING={REMAINING}crwdnd23706:0USED={USED}crwdnd23706:0USED={USED}crwdne23706:0"
no_credits: "crwdns23708:0crwdne23708:0"
#public profil of a member
members_show:
members_list: "crwdns8723:0crwdne8723:0"

View File

@ -18,6 +18,7 @@ de:
my_supporting_documents_files: "My supporting documents"
my_projects: "Meine Projekte"
my_trainings: "Meine Trainings"
my_reservations: "My reservations"
my_events: "Meine Veranstaltungen"
my_invoices: "Meine Rechnungen"
my_payment_schedules: "Meine Zahlungspläne"
@ -289,9 +290,8 @@ de:
add_an_event: "Veranstaltung hinzufügen"
load_the_next_events: "Die nächsten Events laden..."
full_price_: "Voller Preis:"
to_date: "bis" #eg. from 01/01 to 01/05
to_date: "bis" #e.g. from 01/01 to 01/05
all_themes: "Alle Themen"
show_featured: "Show the featured event"
#details and booking of an event
events_show:
event_description: "Beschreibung"
@ -302,8 +302,8 @@ de:
ending: "Endet:"
opening_hours: "Öffnungszeiten:"
all_day: "Ganztägig"
from_time: "Von" #eg. from 18:00 to 21:00
to_time: "bis" #eg. from 18:00 to 21:00
from_time: "Von" #e.g. from 18:00 to 21:00
to_time: "bis" #e.g. from 18:00 to 21:00
full_price_: "Voller Preis:"
tickets_still_availables: "noch verfügbare Tickets:"
sold_out: "Ausverkauft."

View File

@ -18,6 +18,7 @@ en:
my_supporting_documents_files: "My supporting documents"
my_projects: "My Projects"
my_trainings: "My Trainings"
my_reservations: "My reservations"
my_events: "My Events"
my_invoices: "My Invoices"
my_payment_schedules: "My payment schedules"
@ -289,7 +290,7 @@ en:
add_an_event: "Add an event"
load_the_next_events: "Load the next events..."
full_price_: "Full price:"
to_date: "to" #eg. from 01/01 to 01/05
to_date: "to" #e.g. from 01/01 to 01/05
all_themes: "All themes"
#details and booking of an event
events_show:
@ -301,8 +302,8 @@ en:
ending: "Ending:"
opening_hours: "Opening hours:"
all_day: "All day"
from_time: "From" #eg. from 18:00 to 21:00
to_time: "to" #eg. from 18:00 to 21:00
from_time: "From" #e.g. from 18:00 to 21:00
to_time: "to" #e.g. from 18:00 to 21:00
full_price_: "Full price:"
tickets_still_availables: "Tickets still available:"
sold_out: "Sold out."

View File

@ -18,6 +18,7 @@ es:
my_supporting_documents_files: "My supporting documents"
my_projects: "Mis proyectos"
my_trainings: "Mis cursos"
my_reservations: "My reservations"
my_events: "Mis eventos"
my_invoices: "Mis facturas"
my_payment_schedules: "My payment schedules"
@ -289,9 +290,8 @@ es:
add_an_event: "Add an event"
load_the_next_events: "Cargar los próximos eventos..."
full_price_: "Precio completo:"
to_date: "to" #eg. from 01/01 to 01/05
to_date: "to" #e.g. from 01/01 to 01/05
all_themes: "All themes"
show_featured: "Show the featured event"
#details and booking of an event
events_show:
event_description: "Descripción del evento"
@ -302,8 +302,8 @@ es:
ending: "Termina:"
opening_hours: "Hora de apertura:"
all_day: "Todo el día"
from_time: "Desde" #eg. from 18:00 to 21:00
to_time: "a" #eg. from 18:00 to 21:00
from_time: "Desde" #e.g. from 18:00 to 21:00
to_time: "a" #e.g. from 18:00 to 21:00
full_price_: "Precio completo:"
tickets_still_availables: "Entradas disponibles:"
sold_out: "Entradas vendidas."

View File

@ -18,6 +18,7 @@ fr:
my_supporting_documents_files: "Mes justificatifs"
my_projects: "Mes projets"
my_trainings: "Mes formations"
my_reservations: "Mes réservations"
my_events: "Mes événements"
my_invoices: "Mes factures"
my_payment_schedules: "Mes échéanciers"
@ -289,9 +290,8 @@ fr:
add_an_event: "Ajouter un événement"
load_the_next_events: "Charger les événements suivants..."
full_price_: "Plein tarif :"
to_date: "au" #eg. from 01/01 to 01/05
to_date: "au" #e.g. from 01/01 to 01/05
all_themes: "Toutes les thématiques"
show_featured: "Afficher l'événement du moment"
#details and booking of an event
events_show:
event_description: "Description de lévénement"
@ -302,8 +302,8 @@ fr:
ending: "Fin :"
opening_hours: "Horaires :"
all_day: "Toute la journée"
from_time: "De" #eg. from 18:00 to 21:00
to_time: "à" #eg. from 18:00 to 21:00
from_time: "De" #e.g. from 18:00 to 21:00
to_time: "à" #e.g. from 18:00 to 21:00
full_price_: "Plein tarif :"
tickets_still_availables: "Places encore disponibles :"
sold_out: "Événement complet."

View File

@ -18,6 +18,7 @@
my_supporting_documents_files: "My supporting documents"
my_projects: "Mine prosjekter"
my_trainings: "Mine opplæringer/kurs"
my_reservations: "My reservations"
my_events: "Mine arrangementer"
my_invoices: "Mine fakturaer"
my_payment_schedules: "Mine betalingsplaner"
@ -289,9 +290,8 @@
add_an_event: "Legg til arrangement"
load_the_next_events: "Last inn flere arrangementer..."
full_price_: "Full pris:"
to_date: "til" #eg. from 01/01 to 01/05
to_date: "til" #e.g. from 01/01 to 01/05
all_themes: "Alle temaer"
show_featured: "Show the featured event"
#details and booking of an event
events_show:
event_description: "Arrangementsbeskrivelse"
@ -302,8 +302,8 @@
ending: "Slutt:"
opening_hours: "Åpningstider:"
all_day: "Hele dagen"
from_time: "Fra" #eg. from 18:00 to 21:00
to_time: "til" #eg. from 18:00 to 21:00
from_time: "Fra" #e.g. from 18:00 to 21:00
to_time: "til" #e.g. from 18:00 to 21:00
full_price_: "Full pris:"
tickets_still_availables: "Ledige plasser:"
sold_out: "Utsolgt."

View File

@ -15,9 +15,10 @@ pt:
dashboard: "Painel de controle"
my_profile: "Meu Perfil"
my_settings: "Minhas Configurações"
my_supporting_documents_files: "My supporting documents"
my_supporting_documents_files: "Meus documentos"
my_projects: "Meus Projetos"
my_trainings: "Meus Treinamentos"
my_reservations: "My reservations"
my_events: "Meus Eventos"
my_invoices: "Minhas Contas"
my_payment_schedules: "Meus agendamentos de pagamento"
@ -41,12 +42,12 @@ pt:
reserve_a_space: "Reservar Espaço"
projects_gallery: "Galeria de Projetos"
subscriptions: "Assinaturas"
public_calendar: "Agenda"
public_calendar: "Calendário"
#left menu (admin)
trainings_monitoring: "Treinamentos"
manage_the_calendar: "Agenda"
manage_the_users: "Usuários"
manage_the_invoices: "Contas"
manage_the_invoices: "Faturas"
subscriptions_and_prices: "Assinaturas e Preços"
manage_the_events: "Eventos"
manage_the_machines: "Máquinas"
@ -92,8 +93,8 @@ pt:
i_ve_read_and_i_accept_: "Eu li e aceito"
_the_fablab_policy: "a política do FabLab"
field_required: "Campo obrigatório"
profile_custom_field_is_required: "{FEILD} is required"
user_supporting_documents_required: "Warning!<br>You have declared to be \"{GROUP}\", supporting documents may be requested."
profile_custom_field_is_required: "{FEILD} é obrigatório"
user_supporting_documents_required: "Atenção!<br>Você se declarou como \"{GROUP}\", é possível sejam solicitados documentos de comprovação."
unexpected_error_occurred: "Ocorreu um erro inesperado. Por favor, tenta novamente mais tarde."
used_for_statistics: "Estes dados serão utilizados para fins estatísticos"
used_for_invoicing: "Esses dados serão usados para fins de faturamento"
@ -153,7 +154,7 @@ pt:
event_card:
on_the_date: "Em {DATE}"
from_date_to_date: "De {START} até {END}"
from_time_to_time: "From {START} to {END}"
from_time_to_time: "De {START} até {END}"
all_day: "Dia todo"
still_available: "Locais disponíveis: "
event_full: "Evento lotado"
@ -289,9 +290,8 @@ pt:
add_an_event: "Adicionar um evento"
load_the_next_events: "Carregar os próximos eventos..."
full_price_: "Valor inteira:"
to_date: "até" #eg. from 01/01 to 01/05
to_date: "até" #e.g. from 01/01 to 01/05
all_themes: "Todos os temas"
show_featured: "Show the featured event"
#details and booking of an event
events_show:
event_description: "Descrição do evento"
@ -302,8 +302,8 @@ pt:
ending: "Término:"
opening_hours: "Abre ás:"
all_day: "O dia inteiro"
from_time: "Das" #eg. from 18:00 to 21:00
to_time: "ás" #eg. from 18:00 to 21:00
from_time: "Das" #e.g. from 18:00 to 21:00
to_time: "ás" #e.g. from 18:00 to 21:00
full_price_: "Valor inteira:"
tickets_still_availables: "Tickets ainda disponíveis:"
sold_out: "Esgotado."

View File

@ -18,6 +18,7 @@ zu:
my_supporting_documents_files: "crwdns23544:0crwdne23544:0"
my_projects: "crwdns8823:0crwdne8823:0"
my_trainings: "crwdns8825:0crwdne8825:0"
my_reservations: "crwdns23680:0crwdne23680:0"
my_events: "crwdns8827:0crwdne8827:0"
my_invoices: "crwdns8829:0crwdne8829:0"
my_payment_schedules: "crwdns21050:0crwdne21050:0"
@ -289,9 +290,8 @@ zu:
add_an_event: "crwdns9239:0crwdne9239:0"
load_the_next_events: "crwdns9241:0crwdne9241:0"
full_price_: "crwdns9243:0crwdne9243:0"
to_date: "crwdns19622:0crwdne19622:0" #eg. from 01/01 to 01/05
to_date: "crwdns19622:0crwdne19622:0" #e.g. from 01/01 to 01/05
all_themes: "crwdns19624:0crwdne19624:0"
show_featured: "crwdns23126:0crwdne23126:0"
#details and booking of an event
events_show:
event_description: "crwdns9247:0crwdne9247:0"
@ -302,8 +302,8 @@ zu:
ending: "crwdns9257:0crwdne9257:0"
opening_hours: "crwdns9259:0crwdne9259:0"
all_day: "crwdns9261:0crwdne9261:0"
from_time: "crwdns19626:0crwdne19626:0" #eg. from 18:00 to 21:00
to_time: "crwdns19628:0crwdne19628:0" #eg. from 18:00 to 21:00
from_time: "crwdns19626:0crwdne19626:0" #e.g. from 18:00 to 21:00
to_time: "crwdns19628:0crwdne19628:0" #e.g. from 18:00 to 21:00
full_price_: "crwdns9267:0crwdne9267:0"
tickets_still_availables: "crwdns9269:0crwdne9269:0"
sold_out: "crwdns9271:0crwdne9271:0"

View File

@ -23,100 +23,100 @@ pt:
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_declined: "O seu cartão foi recusado."
change_group:
title: "{OPERATOR, select, self{My group} other{User's group}}"
change: "Change {OPERATOR, select, self{my} other{his}} group"
cancel: "Cancel"
validate: "Validate group change"
success: "Group successfully changed"
title: "{OPERATOR, select, self{Meu grupo} other{Grupo do usuário}}"
change: "Mudar {OPERATOR, select, self{meu grupo} other{grupo do usuário}}"
cancel: "Cancelar"
validate: "Validar alteração de grupo"
success: "Grupo alterado com sucesso"
stripe_form:
payment_card_error: "A problem occurred with your payment card:"
payment_card_error: "Ocorreu um problema com seu cartão:"
#text editor
text_editor:
fab_text_editor:
text_placeholder: "Type something…"
text_placeholder: "Digite alguma coisa…"
menu_bar:
link_placeholder: "Paste link…"
url_placeholder: "Paste url…"
new_tab: "Open in a new tab"
add_link: "Insert a link"
add_video: "Embed a video"
add_image: "Insert an image"
link_placeholder: "Colar link…"
url_placeholder: "Colar Url…"
new_tab: "Abrir em uma nova aba"
add_link: "Inserir um link"
add_video: "Incorporar um vídeo"
add_image: "Inserir uma imagem"
#modal dialog
fab_modal:
close: "Close"
close: "Fechar"
fab_socials:
follow_us: "Follow us"
networks_update_success: "Social networks update successful"
networks_update_error: "Problem trying to update social networks"
url_placeholder: "Paste url…"
save: "Save"
website_invalid: "The website address is not a valid URL"
follow_us: "Siga-nos"
networks_update_success: "Redes sociais atualizadas com sucesso"
networks_update_error: "Problema ao tentar atualizar as redes sociais"
url_placeholder: "Colar Url…"
save: "Salvar"
website_invalid: "O endereço do site não é válido"
edit_socials:
url_placeholder: "Paste url…"
website_invalid: "The website address is not a valid URL"
url_placeholder: "Colar Url…"
website_invalid: "O endereço do site não é válido"
#user edition form
user_profile_form:
add_an_avatar: "Add an avatar"
personal_data: "Personal"
account_data: "Account"
account_networks: "Social networks"
organization_data: "Organization"
profile_data: "Profile"
preferences_data: "Preferences"
declare_organization: "I declare to be an organization"
declare_organization_help: "If you declare to be an organization, your invoices will be issued in the name of the organization."
pseudonym: "Nickname"
first_name: "First name"
surname: "Surname"
email_address: "Email address"
organization_name: "Organization name"
organization_address: "Organization address"
profile_custom_field_is_required: "{FEILD} is required"
date_of_birth: "Date of birth"
website: "Website"
website_invalid: "The website address is not a valid URL"
job: "Job"
interests: "Interests"
CAD_softwares_mastered: "CAD Softwares mastered"
birthday: "Date of birth"
birthday_is_required: "Date of birth is required."
address: "Address"
phone_number: "Phone number"
phone_number_invalid: "Phone number is invalid."
allow_public_profile: "I authorize users, registered on the site, to contact me"
allow_public_profile_help: "Your profile will be visible to other users and you'll be able to collaborate on projects."
allow_newsletter: "I accept to receive information from the FabLab"
allow_newsletter_help: "You may receive the newsletter."
used_for_statistics: "This data will be used for statistical purposes"
used_for_invoicing: "This data will be used for billing purposes"
used_for_reservation: "This data will be used in case of change on one of your bookings"
used_for_profile: "This data will only be displayed on your profile"
group: "Group"
trainings: "Trainings"
add_an_avatar: "Adicionar avatar"
personal_data: "Pessoal"
account_data: "Conta"
account_networks: "Redes sociais"
organization_data: "Organização"
profile_data: "Perfil"
preferences_data: "Preferências"
declare_organization: "Declaro ser uma organização"
declare_organization_help: "Se você declarar ser uma organização, suas faturas serão emitidas no nome da organização."
pseudonym: "Usuário"
first_name: "Primeiro nome"
surname: "Sobrenome"
email_address: "Endereço de e-mail"
organization_name: "Nome da organização"
organization_address: "Endereço da organização"
profile_custom_field_is_required: "{FEILD} é obrigatório"
date_of_birth: "Data de nascimento"
website: "Site"
website_invalid: "O endereço do site não é válido"
job: "Emprego"
interests: "Interesses"
CAD_softwares_mastered: "Softwares de CAD dominados"
birthday: "Data de nascimento"
birthday_is_required: "Data de nascimento é obrigatório."
address: "Endereço"
phone_number: "Número de telefone"
phone_number_invalid: "O número de telefone é inválido."
allow_public_profile: "Eu autorizo usuários do FabLab, registrados no site, a entrarem em contato comigo"
allow_public_profile_help: "Seu perfil ficará visível para outros usuários e você poderá colaborar em projetos."
allow_newsletter: "Eu aceito receber informações do FabLab"
allow_newsletter_help: "Você poderá receber a newsletter."
used_for_statistics: "Estes dados serão utilizados para fins estatísticos"
used_for_invoicing: "Esses dados serão usados para fins de faturamento"
used_for_reservation: "Estes dados serão utilizados em caso de alteração em uma das suas reservas"
used_for_profile: "Estes dados serão exibidos apenas no seu perfil"
group: "Grupo"
trainings: "Treinamentos"
tags: "Tags"
terms_and_conditions_html: "I've read and accept <a href=\"{POLICY_URL}\" target=\"_blank\">the terms and conditions<a/>"
must_accept_terms: "You must accept the terms and conditions"
save: "Save"
terms_and_conditions_html: "Eu li e aceito <a href=\"{POLICY_URL}\" target=\"_blank\">os termos e condições<a/>"
must_accept_terms: "Você deve aceitar os termos e condições"
save: "Salvar"
gender_input:
man: "Man"
woman: "Woman"
man: "Homem"
woman: "Mulher"
change_password:
change_my_password: "Change my password"
confirm_current: "Confirm your current password"
change_my_password: "Alterar a minha senha"
confirm_current: "Confirme sua senha atual"
confirm: "OK"
wrong_password: "Wrong password"
wrong_password: "Senha errada"
password_input:
new_password: "New password"
confirm_password: "Confirm password"
password_too_short: "Password is too short (must be at least 8 characters)"
confirmation_mismatch: "Confirmation mismatch with password."
new_password: "Nova senha"
confirm_password: "Confirmar a senha"
password_too_short: "Senha muito curta (mínimo 8 caracteres)"
confirmation_mismatch: "Confirmação de senha é diferente da senha."
#project edition form
project:
name: "Nome"
name_is_required: "Nome é obrigatório."
illustration: "Ilustração"
add_an_illustration: "Adicionar ilustração"
CAD_file: "Arquivo do CAD"
illustration: "Foto"
add_an_illustration: "Adicionar foto"
CAD_file: "Arquivo CAD"
allowed_extensions: "Extensões permitidas:"
add_a_new_file: "Adicionar novo arquivo"
description: "Descrição"
@ -165,15 +165,15 @@ pt:
member_select:
select_a_member: "Selecionar um membro"
start_typing: "Escrevendo..."
member_not_validated: "Warning:<br> The member was not validated."
member_not_validated: "Aviso:<br> O membro não foi validado."
#payment modal
abstract_payment_modal:
online_payment: "Online payment"
i_have_read_and_accept_: "I have read, and accept "
_the_general_terms_and_conditions: "the general terms and conditions."
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"
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>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: "Validar"
#dialog of on site payment for reservations
valid_reservation_modal:
booking_confirmation: "Confirmação de reserva"
@ -186,8 +186,8 @@ pt:
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_themes:
title: "Event themes"
select_theme: "Pick up a theme…"
title: "Temas do evento"
select_theme: "Escolher um tema…"
#event edition form
event:
title: "Título"
@ -412,14 +412,14 @@ pt:
default_places_is_required: "Tickets máximo padrão é obrigatório."
disable_space: "Desativar espaço"
payment_schedule_summary:
your_payment_schedule: "Your payment schedule"
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."
monthly_payment_NUMBER: "{NUMBER}{NUMBER, plural, =1{st} =2{nd} =3{rd} other{th}} monthly payment: "
debit: "Debit on the day of the order."
view_full_schedule: "View the complete payment schedule"
your_payment_schedule: "Sua agenda de pagamento"
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."
monthly_payment_NUMBER: "{NUMBER}{NUMBER, plural, one {} =1{º} =2{º} =3{º} other{º}} pagamento mensal: "
debit: "Débito no dia do pedido."
view_full_schedule: "Ver as agendas de pagamentos completa"
select_schedule:
monthly_payment: "Monthly payment"
monthly_payment: "Pagamento mensal"
#shopping cart module for reservations
cart:
summary: "Sumário"
@ -479,7 +479,7 @@ pt:
slot_tags: "Tags do Slot"
user_tags: "Etiquetas de usuários"
no_tags: "Sem etiquetas"
user_validation_required_alert: "Warning!<br>Your administrator must validate your account. Then, you'll then be able to access all the booking features."
user_validation_required_alert: "Aviso!<br>O seu administrador deve validar sua conta. Só após você poderá acessar todos os recursos de reserva."
#feature-tour modal
tour:
previous: "Anterior"
@ -492,7 +492,7 @@ pt:
tour: "Iniciar o tour em destaque"
guide: "Abrir manual do usuário"
stripe_confirm_modal:
resolve_action: "Resolve the action"
resolve_action: "Resolver a ação"
ok_button: "OK"
#2nd factor authentication for card payments
stripe_confirm:
@ -500,26 +500,26 @@ pt:
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
payment_schedules_table:
schedule_num: "Schedule #"
date: "Date"
price: "Price"
customer: "Customer"
deadline: "Deadline"
amount: "Amount"
state: "State"
download: "Download"
state_new: "Not yet due"
state_pending_check: "Waiting for the cashing of the check"
state_pending_transfer: "Waiting for the tranfer confirmation"
state_requires_payment_method: "The credit card must be updated"
state_requires_action: "Action required"
state_paid: "Paid"
state_error: "Error"
state_gateway_canceled: "Canceled by the payment gateway"
state_canceled: "Canceled"
method_card: "by card"
method_check: "by check"
method_transfer: "by transfer"
schedule_num: "Agendamento #"
date: "Data"
price: "Preço"
customer: "Cliente"
deadline: "Prazo"
amount: "Montante"
state: "Estado"
download: "Baixar"
state_new: "Ainda não vencido"
state_pending_check: "Esperando a validação manual"
state_pending_transfer: "Aguardando a confirmação da transferência"
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_gateway_canceled: "Cancelado pelo gateway de pagamento"
state_canceled: "Cancelado"
method_card: "por cartão"
method_check: "por cheque"
method_transfer: "por transferência"
payment_schedule_item_actions:
download: "Baixar"
cancel_subscription: "Cancelar assinatura"
@ -536,8 +536,8 @@ pt:
confirm_bank_transfer_body: "Você deve confirmar o recibo de {AMOUNT} para o prazo de {DATE}. Ao confirmar a transferência bancária, uma fatura será gerada para este prazo."
confirm_cancel_subscription: "Você está prestes a cancelar esta agenda de pagamento e a assinatura relacionada. Tem certeza?"
card_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: "Ocorreu um erro. Por favor, reporte este problema à equipe do FabLab."
stripe_card_update_modal:
@ -547,4 +547,4 @@ pt:
update_card: "Atualizar o cartão"
validate_button: "Verificar o novo cartão"
form_multi_select:
create_label: "Add {VALUE}"
create_label: "Adicionar {VALUE}"

View File

@ -24,14 +24,14 @@ pt:
subject: "Seu grupo mudou"
body:
warning: "Você mudou de grupo. As inspeções podem ser realizadas no laboratório para verificar a legitimidade dessa mudança."
user_invalidated: "Your account was invalidated, please upload your new supporting documents to validate your account."
user_invalidated: "Sua conta foi invalidada, por favor, carregue novos documentos para validar sua conta."
notify_admin_user_group_changed:
subject: "Um membro mudou de grupo"
body:
user_changed_group_html: "O usuário <em><strong>%{NAME}</strong></em> mudou de grupo."
previous_group: "Grupo anterior:"
new_group: "Novo grupo:"
user_invalidated: "The user's account is invalidated."
user_invalidated: "A conta do usuário está invalidada."
notify_admin_subscription_extended:
subject: "Uma assinatura foi estendida"
body:
@ -346,31 +346,31 @@ pt:
date: "Este é um lembrete para verificar se o débito bancário foi bem sucedido."
confirm: "Não se esqueça de confirmar o recibo na interface de gestão de pagamento, para que a fatura correspondente seja gerada."
notify_admin_user_proof_of_identity_files_created:
subject: "Supporting documents uploaded by a member"
subject: "Documentos enviados por um membro"
body:
proof_of_identity_files_uploaded_below: "Member %{NAME} has uploaded the following supporting documents:"
validate_user: "Please validate this account"
proof_of_identity_files_uploaded_below: "O membro %{NAME} enviou os seguintes documentos:"
validate_user: "Por favor, valide esta conta"
notify_admin_user_proof_of_identity_files_updated:
subject: "Member's supporting documents have changed"
subject: "Os documentos do membro mudaram"
body:
user_update_proof_of_identity_file: "Member %{NAME} has modified the supporting documents below:"
validate_user: "Please validate this account"
user_update_proof_of_identity_file: "O membro %{NAME} modificou os documentos abaixo:"
validate_user: "Por favor valide esta conta"
notify_user_is_validated:
subject: "The account is validated"
subject: "A conta está validada"
body:
account_validated: "Your account is valid."
account_validated: "Sua conta é válida."
notify_user_is_invalidated:
subject: "The account is invalid"
subject: "A conta é inválida"
body:
account_invalidated: "Your account is invalid."
account_invalidated: "Sua conta é inválida."
notify_user_proof_of_identity_refusal:
subject: "Your supporting documents were refused"
subject: "Seus documentos foram recusados"
body:
user_proof_of_identity_files_refusal: "Your supporting documents were refused:"
action: "Please re-upload some new supporting documents."
user_proof_of_identity_files_refusal: "Seus documentos foram recusados:"
action: "Por favor, recarregue novos documentos."
notify_admin_user_proof_of_identity_refusal:
subject: "A member's supporting documents were refused"
subject: "Documentos de um membro foram recusados"
body:
user_proof_of_identity_files_refusal: "Member %{NAME}'s supporting documents were rejected by %{OPERATOR}:"
user_proof_of_identity_files_refusal: "Os documentos do membro %{NAME} foram rejeitados por %{OPERATOR}:"
shared:
hello: "Olá %{user_name}"

View File

@ -64,8 +64,8 @@ pt:
requested_account_does_not_exists: "A conta requisitada não existe"
#SSO external authentication
authentication_providers:
local_database_provider_already_exists: 'A "Local Database" provider already exists. Unable to create another.'
matching_between_User_uid_and_API_required: "It is required to set the matching between User.uid and the API to add this provider."
local_database_provider_already_exists: 'Um provedor "Local Database" já existe. Não foi possível criar outro.'
matching_between_User_uid_and_API_required: "É necessário definir a correspondência entre User.uid e a API para adicionar este provedor."
#PDF invoices generation
invoices:
refund_invoice_reference: "Referência da fatura de reembolso: %{REF}"
@ -245,14 +245,14 @@ pt:
reservations: "Reservas"
available_seats: "Assentos disponíveis"
reservation_ics:
summary: "%{TYPE} reservation"
summary: "%{TYPE} reserva"
type:
Machine: "Machine"
Space: "Space"
Event: "Event"
Training: "Training"
description: "You have reserved %{COUNT} slots of %{ITEM}"
alarm_summary: "Remind your reservation"
Machine: "Máquina"
Space: "Espaço"
Event: "Evento"
Training: "Treinamento"
description: "Você reservou %{COUNT} vagas de %{ITEM}"
alarm_summary: "Lembrar a sua reserva"
roles:
member: "Membro"
manager: "Gestor"
@ -398,17 +398,17 @@ pt:
notify_admin_payment_schedule_transfer_deadline:
schedule_deadline: "Você deve realizar a verificação do débito para a data limite de %{DATE}, para o agendamento %{REFERENCE}"
notify_admin_user_proof_of_identity_files_created:
proof_of_identity_files_uploaded: "Proof of identity uploaded by member <strong><em>%{NAME}</strong></em>."
proof_of_identity_files_uploaded: "Comprovante de identidade enviado pelo membro <strong><em>%{NAME}</strong></em>."
notify_admin_user_proof_of_identity_files_updated:
proof_of_identity_files_uploaded: "Proof of identity changed by Member <strong><em>%{NAME}</strong></em>."
proof_of_identity_files_uploaded: "Comprovante de identidade alterado pelo membro <strong><em>%{NAME}</strong></em>."
notify_user_is_validated:
account_validated: "Your account is valid."
account_validated: "Sua conta é válida."
notify_user_is_invalidated:
account_invalidated: "Your account is invalid."
account_invalidated: "Sua conta é inválida."
notify_user_proof_of_identity_refusal:
refusal: "Your proof of identity are not accepted"
refusal: "Seu comprovante de identidade não foi aceito"
notify_admin_user_proof_of_identity_refusal:
refusal: "Member's proof of identity <strong><em>%{NAME}</strong></em> refused."
refusal: "Prova de identidade do membro <strong><em>%{NAME}</strong></em> recusada."
#statistics tools for admins
statistics:
subscriptions: "Assinaturas"
@ -593,5 +593,5 @@ pt:
pinterest: "pinterest"
lastfm: "lastfm"
flickr: "flickr"
machines_module: "Machines module"
user_change_group: "Allow users to change their group"
machines_module: "Módulo de Máquinas"
user_change_group: "Permitir que os usuários mudem de grupo"

View File

@ -154,7 +154,9 @@ Rails.application.routes.draw do
resources :trainings do
get :availabilities, on: :member
end
resources :credits
resources :credits do
get 'user/:id/:resource', action: :user_resource, on: :collection
end
resources :categories
resources :event_themes
resources :age_ranges

View File

@ -1,6 +1,6 @@
{
"name": "fab-manager",
"version": "5.4.7",
"version": "5.4.8",
"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

@ -394,3 +394,69 @@ price_48:
duration: 60
created_at: 2016-04-04 15:18:28.860220000 Z
updated_at: 2016-04-04 15:18:50.517702000 Z
price_49:
id: 49
group_id: 1
plan_id:
priceable_id: 1
priceable_type: Space
amount: 2000
duration: 60
created_at: 2016-04-04 15:18:28.860220000 Z
updated_at: 2016-04-04 15:18:50.517702000 Z
price_50:
id: 50
group_id: 2
plan_id:
priceable_id: 1
priceable_type: Space
amount: 1800
duration: 60
created_at: 2016-04-04 15:18:28.860220000 Z
updated_at: 2016-04-04 15:18:50.517702000 Z
price_51:
id: 51
group_id: 1
plan_id: 1
priceable_id: 1
priceable_type: Space
amount: 1800
duration: 60
created_at: 2016-04-04 15:18:28.860220000 Z
updated_at: 2016-04-04 15:18:50.517702000 Z
price_52:
id: 52
group_id: 1
plan_id: 2
priceable_id: 1
priceable_type: Space
amount: 1800
duration: 60
created_at: 2016-04-04 15:18:28.860220000 Z
updated_at: 2016-04-04 15:18:50.517702000 Z
price_53:
id: 53
group_id: 2
plan_id: 3
priceable_id: 1
priceable_type: Space
amount: 1500
duration: 60
created_at: 2016-04-04 15:18:28.860220000 Z
updated_at: 2016-04-04 15:18:50.517702000 Z
price_54:
id: 54
group_id: 1
plan_id: 4
priceable_id: 1
priceable_type: Space
amount: 1500
duration: 60
created_at: 2016-04-04 15:18:28.860220000 Z
updated_at: 2016-04-04 15:18:50.517702000 Z

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
require 'test_helper'
module Credits
class TrainingTest < ActionDispatch::IntegrationTest
# Called before every test method runs. Can be used

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
require 'test_helper'
module Credits
class TrainingTest < ActionDispatch::IntegrationTest
# Called before every test method runs. Can be used

View File

@ -0,0 +1,47 @@
# frozen_string_literal: true
require 'test_helper'
module Credits
class UserInfoTest < ActionDispatch::IntegrationTest
def setup
@user_without_subscription = User.find_by(username: 'lseguin')
@user_with_subscription = User.find_by(username: 'kdumas')
end
test 'user fetch her credits info' do
login_as(@user_with_subscription, scope: :user)
get "/api/credits/user/#{@user_with_subscription.id}/Machine"
# Check response format & status
assert_equal 200, response.status
assert_equal Mime[:json], response.content_type
# Check the correct credits was returned
credits = json_response(response.body)
assert_equal @user_with_subscription.subscribed_plan.credits.where(creditable_type: 'Machine').count,
credits.length,
'not all credits were returned'
end
test 'user without credits fetch his credits info' do
login_as(@user_without_subscription, scope: :user)
get "/api/credits/user/#{@user_without_subscription.id}/Machine"
# Check response format & status
assert_equal 200, response.status
assert_equal Mime[:json], response.content_type
# Check the correct credits was returned
credits = json_response(response.body)
assert_equal 0, credits.length, 'unexpected credits returned'
end
test 'user tries to fetch credits info from another user' do
login_as(@user_without_subscription, scope: :user)
get "/api/credits/user/#{@user_with_subscription.id}/Machine"
assert_equal 403, response.status
end
end
end

View File

@ -876,4 +876,78 @@ class Reservations::CreateTest < ActionDispatch::IntegrationTest
subscription = payment_schedule.payment_schedule_objects.find { |pso| pso.object_type == Subscription.name }.object
assert_equal plan.id, subscription.plan_id, 'subscribed plan does not match'
end
test 'user reserves a space with success' do
login_as(@user_without_subscription, scope: :user)
space = Space.first
availability = space.availabilities.first
reservations_count = Reservation.count
invoice_count = Invoice.count
invoice_items_count = InvoiceItem.count
users_credit_count = UsersCredit.count
subscriptions_count = Subscription.count
VCR.use_cassette('reservations_create_for_space_success') do
post '/api/stripe/confirm_payment',
params: {
payment_method_id: stripe_payment_method,
cart_items: {
items: [
{
reservation: {
reservable_id: space.id,
reservable_type: space.class.name,
slots_attributes: [
{
start_at: availability.start_at.to_s(:iso8601),
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
availability_id: availability.id
}
]
}
}
]
}
}.to_json, headers: default_headers
end
# general assertions
assert_equal 201, response.status
assert_equal reservations_count + 1, Reservation.count
assert_equal invoice_count + 1, Invoice.count
assert_equal invoice_items_count + 1, InvoiceItem.count
assert_equal users_credit_count, UsersCredit.count
assert_equal subscriptions_count, Subscription.count
# subscription assertions
assert_equal 0, @user_without_subscription.subscriptions.count
assert_nil @user_without_subscription.subscribed_plan
# reservation assertions
reservation = Reservation.last
assert reservation.original_invoice
assert_equal 1, reservation.original_invoice.invoice_items.count
# invoice_items assertions
invoice_item = InvoiceItem.last
assert_equal space.prices.find_by(group_id: @user_without_subscription.group_id, plan_id: nil).amount, invoice_item.amount
assert invoice_item.check_footprint
# invoice assertions
item = InvoiceItem.find_by(object: reservation)
invoice = item.invoice
assert_invoice_pdf invoice
assert_not_nil invoice.debug_footprint
refute invoice.payment_gateway_object.blank?
refute invoice.total.blank?
assert invoice.check_footprint
# notification
assert_not_empty Notification.where(attached_object: reservation)
end
end

View File

@ -0,0 +1,573 @@
---
http_interactions:
- request:
method: post
uri: https://api.stripe.com/v1/payment_methods
body:
encoding: UTF-8
string: type=card&card[number]=4242424242424242&card[exp_month]=4&card[exp_year]=2023&card[cvc]=314
headers:
User-Agent:
- Stripe/v1 RubyBindings/5.29.0
Authorization:
- Bearer sk_test_testfaketestfaketestfake
Content-Type:
- application/x-www-form-urlencoded
Stripe-Version:
- '2019-08-14'
X-Stripe-Client-User-Agent:
- '{"bindings_version":"5.29.0","lang":"ruby","lang_version":"2.6.10 p210 (2022-04-12)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux
version 5.18.7-arch1-1 (linux@archlinux) (gcc (GCC) 12.1.0, GNU ld (GNU Binutils)
2.38) #1 SMP PREEMPT_DYNAMIC Sat, 25 Jun 2022 20:22:01 +0000","hostname":"Sylvain-desktop"}'
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
response:
status:
code: 200
message: OK
headers:
Server:
- nginx
Date:
- Wed, 29 Jun 2022 08:56:57 GMT
Content-Type:
- application/json
Content-Length:
- '934'
Connection:
- keep-alive
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Allow-Methods:
- GET, POST, HEAD, OPTIONS, DELETE
Access-Control-Allow-Origin:
- "*"
Access-Control-Expose-Headers:
- Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required
Access-Control-Max-Age:
- '300'
Cache-Control:
- no-cache, no-store
Idempotency-Key:
- 2908b95f-5e36-4090-a298-47d71b96a9e2
Original-Request:
- req_QYEDXq2OVUNTgU
Request-Id:
- req_QYEDXq2OVUNTgU
Stripe-Should-Retry:
- 'false'
Stripe-Version:
- '2019-08-14'
Strict-Transport-Security:
- max-age=31556926; includeSubDomains; preload
body:
encoding: UTF-8
string: |
{
"id": "pm_1LFwhg2sOmf47Nz9GYPwlv3G",
"object": "payment_method",
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "unchecked"
},
"country": "US",
"exp_month": 4,
"exp_year": 2023,
"fingerprint": "o52jybR7bnmNn6AT",
"funding": "credit",
"generated_from": null,
"last4": "4242",
"networks": {
"available": [
"visa"
],
"preferred": null
},
"three_d_secure_usage": {
"supported": true
},
"wallet": null
},
"created": 1656493016,
"customer": null,
"livemode": false,
"metadata": {
},
"type": "card"
}
recorded_at: Wed, 29 Jun 2022 08:56:56 GMT
- request:
method: post
uri: https://api.stripe.com/v1/payment_intents
body:
encoding: UTF-8
string: payment_method=pm_1LFwhg2sOmf47Nz9GYPwlv3G&amount=2000&currency=usd&confirmation_method=manual&confirm=true&customer=cus_8Di1wjdVktv5kt
headers:
User-Agent:
- Stripe/v1 RubyBindings/5.29.0
Authorization:
- Bearer sk_test_testfaketestfaketestfake
Content-Type:
- application/x-www-form-urlencoded
X-Stripe-Client-Telemetry:
- '{"last_request_metrics":{"request_id":"req_QYEDXq2OVUNTgU","request_duration_ms":775}}'
Stripe-Version:
- '2019-08-14'
X-Stripe-Client-User-Agent:
- '{"bindings_version":"5.29.0","lang":"ruby","lang_version":"2.6.10 p210 (2022-04-12)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux
version 5.18.7-arch1-1 (linux@archlinux) (gcc (GCC) 12.1.0, GNU ld (GNU Binutils)
2.38) #1 SMP PREEMPT_DYNAMIC Sat, 25 Jun 2022 20:22:01 +0000","hostname":"Sylvain-desktop"}'
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
response:
status:
code: 200
message: OK
headers:
Server:
- nginx
Date:
- Wed, 29 Jun 2022 08:56:59 GMT
Content-Type:
- application/json
Content-Length:
- '4469'
Connection:
- keep-alive
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Allow-Methods:
- GET, POST, HEAD, OPTIONS, DELETE
Access-Control-Allow-Origin:
- "*"
Access-Control-Expose-Headers:
- Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required
Access-Control-Max-Age:
- '300'
Cache-Control:
- no-cache, no-store
Idempotency-Key:
- 1272b45e-26c3-4878-83e2-993fa7ac231f
Original-Request:
- req_wjFLn3PHqTJC36
Request-Id:
- req_wjFLn3PHqTJC36
Stripe-Should-Retry:
- 'false'
Stripe-Version:
- '2019-08-14'
Strict-Transport-Security:
- max-age=31556926; includeSubDomains; preload
body:
encoding: UTF-8
string: |
{
"id": "pi_3LFwhh2sOmf47Nz90TkhO3EE",
"object": "payment_intent",
"amount": 2000,
"amount_capturable": 0,
"amount_details": {
"tip": {
}
},
"amount_received": 2000,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": null,
"canceled_at": null,
"cancellation_reason": null,
"capture_method": "automatic",
"charges": {
"object": "list",
"data": [
{
"id": "ch_3LFwhh2sOmf47Nz90kTYKtOi",
"object": "charge",
"amount": 2000,
"amount_captured": 2000,
"amount_refunded": 0,
"application": null,
"application_fee": null,
"application_fee_amount": null,
"balance_transaction": "txn_3LFwhh2sOmf47Nz90q2qBSy1",
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"calculated_statement_descriptor": "Stripe",
"captured": true,
"created": 1656493017,
"currency": "usd",
"customer": "cus_8Di1wjdVktv5kt",
"description": null,
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {
},
"invoice": null,
"livemode": false,
"metadata": {
},
"on_behalf_of": null,
"order": null,
"outcome": {
"network_status": "approved_by_network",
"reason": null,
"risk_level": "normal",
"risk_score": 47,
"seller_message": "Payment complete.",
"type": "authorized"
},
"paid": true,
"payment_intent": "pi_3LFwhh2sOmf47Nz90TkhO3EE",
"payment_method": "pm_1LFwhg2sOmf47Nz9GYPwlv3G",
"payment_method_details": {
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 4,
"exp_year": 2023,
"fingerprint": "o52jybR7bnmNn6AT",
"funding": "credit",
"installments": null,
"last4": "4242",
"mandate": null,
"network": "visa",
"three_d_secure": null,
"wallet": null
},
"type": "card"
},
"receipt_email": null,
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/acct_103rE62sOmf47Nz9/ch_3LFwhh2sOmf47Nz90kTYKtOi/rcpt_LxsSxfXrNwvvrcmN9mq9YJu2Pjp82hU",
"refunded": false,
"refunds": {
"object": "list",
"data": [
],
"has_more": false,
"total_count": 0,
"url": "/v1/charges/ch_3LFwhh2sOmf47Nz90kTYKtOi/refunds"
},
"review": null,
"shipping": null,
"source": null,
"source_transfer": null,
"statement_descriptor": null,
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}
],
"has_more": false,
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_3LFwhh2sOmf47Nz90TkhO3EE"
},
"client_secret": "pi_3LFwhh2sOmf47Nz90TkhO3EE_secret_P1hDOTsqApMC7vxVXr5smwAky",
"confirmation_method": "manual",
"created": 1656493017,
"currency": "usd",
"customer": "cus_8Di1wjdVktv5kt",
"description": null,
"invoice": null,
"last_payment_error": null,
"livemode": false,
"metadata": {
},
"next_action": null,
"on_behalf_of": null,
"payment_method": "pm_1LFwhg2sOmf47Nz9GYPwlv3G",
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": null,
"review": null,
"setup_future_usage": null,
"shipping": null,
"source": null,
"statement_descriptor": null,
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}
recorded_at: Wed, 29 Jun 2022 08:56:59 GMT
- request:
method: post
uri: https://api.stripe.com/v1/payment_intents/pi_3LFwhh2sOmf47Nz90TkhO3EE
body:
encoding: UTF-8
string: description=Invoice+reference%3A+2206001%2FVL
headers:
User-Agent:
- Stripe/v1 RubyBindings/5.29.0
Authorization:
- Bearer sk_test_testfaketestfaketestfake
Content-Type:
- application/x-www-form-urlencoded
X-Stripe-Client-Telemetry:
- '{"last_request_metrics":{"request_id":"req_wjFLn3PHqTJC36","request_duration_ms":1907}}'
Stripe-Version:
- '2019-08-14'
X-Stripe-Client-User-Agent:
- '{"bindings_version":"5.29.0","lang":"ruby","lang_version":"2.6.10 p210 (2022-04-12)","platform":"x86_64-linux","engine":"ruby","publisher":"stripe","uname":"Linux
version 5.18.7-arch1-1 (linux@archlinux) (gcc (GCC) 12.1.0, GNU ld (GNU Binutils)
2.38) #1 SMP PREEMPT_DYNAMIC Sat, 25 Jun 2022 20:22:01 +0000","hostname":"Sylvain-desktop"}'
Accept-Encoding:
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
Accept:
- "*/*"
response:
status:
code: 200
message: OK
headers:
Server:
- nginx
Date:
- Wed, 29 Jun 2022 08:57:05 GMT
Content-Type:
- application/json
Content-Length:
- '4496'
Connection:
- keep-alive
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Allow-Methods:
- GET, POST, HEAD, OPTIONS, DELETE
Access-Control-Allow-Origin:
- "*"
Access-Control-Expose-Headers:
- Request-Id, Stripe-Manage-Version, X-Stripe-External-Auth-Required, X-Stripe-Privileged-Session-Required
Access-Control-Max-Age:
- '300'
Cache-Control:
- no-cache, no-store
Idempotency-Key:
- 12c3c56b-6a04-4c28-b3ef-20c09ff1a944
Original-Request:
- req_Eds9ZxXyg5d2g1
Request-Id:
- req_Eds9ZxXyg5d2g1
Stripe-Should-Retry:
- 'false'
Stripe-Version:
- '2019-08-14'
Strict-Transport-Security:
- max-age=31556926; includeSubDomains; preload
body:
encoding: UTF-8
string: |
{
"id": "pi_3LFwhh2sOmf47Nz90TkhO3EE",
"object": "payment_intent",
"amount": 2000,
"amount_capturable": 0,
"amount_details": {
"tip": {
}
},
"amount_received": 2000,
"application": null,
"application_fee_amount": null,
"automatic_payment_methods": null,
"canceled_at": null,
"cancellation_reason": null,
"capture_method": "automatic",
"charges": {
"object": "list",
"data": [
{
"id": "ch_3LFwhh2sOmf47Nz90kTYKtOi",
"object": "charge",
"amount": 2000,
"amount_captured": 2000,
"amount_refunded": 0,
"application": null,
"application_fee": null,
"application_fee_amount": null,
"balance_transaction": "txn_3LFwhh2sOmf47Nz90q2qBSy1",
"billing_details": {
"address": {
"city": null,
"country": null,
"line1": null,
"line2": null,
"postal_code": null,
"state": null
},
"email": null,
"name": null,
"phone": null
},
"calculated_statement_descriptor": "Stripe",
"captured": true,
"created": 1656493017,
"currency": "usd",
"customer": "cus_8Di1wjdVktv5kt",
"description": null,
"destination": null,
"dispute": null,
"disputed": false,
"failure_balance_transaction": null,
"failure_code": null,
"failure_message": null,
"fraud_details": {
},
"invoice": null,
"livemode": false,
"metadata": {
},
"on_behalf_of": null,
"order": null,
"outcome": {
"network_status": "approved_by_network",
"reason": null,
"risk_level": "normal",
"risk_score": 47,
"seller_message": "Payment complete.",
"type": "authorized"
},
"paid": true,
"payment_intent": "pi_3LFwhh2sOmf47Nz90TkhO3EE",
"payment_method": "pm_1LFwhg2sOmf47Nz9GYPwlv3G",
"payment_method_details": {
"card": {
"brand": "visa",
"checks": {
"address_line1_check": null,
"address_postal_code_check": null,
"cvc_check": "pass"
},
"country": "US",
"exp_month": 4,
"exp_year": 2023,
"fingerprint": "o52jybR7bnmNn6AT",
"funding": "credit",
"installments": null,
"last4": "4242",
"mandate": null,
"network": "visa",
"three_d_secure": null,
"wallet": null
},
"type": "card"
},
"receipt_email": null,
"receipt_number": null,
"receipt_url": "https://pay.stripe.com/receipts/acct_103rE62sOmf47Nz9/ch_3LFwhh2sOmf47Nz90kTYKtOi/rcpt_LxsSxfXrNwvvrcmN9mq9YJu2Pjp82hU",
"refunded": false,
"refunds": {
"object": "list",
"data": [
],
"has_more": false,
"total_count": 0,
"url": "/v1/charges/ch_3LFwhh2sOmf47Nz90kTYKtOi/refunds"
},
"review": null,
"shipping": null,
"source": null,
"source_transfer": null,
"statement_descriptor": null,
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}
],
"has_more": false,
"total_count": 1,
"url": "/v1/charges?payment_intent=pi_3LFwhh2sOmf47Nz90TkhO3EE"
},
"client_secret": "pi_3LFwhh2sOmf47Nz90TkhO3EE_secret_P1hDOTsqApMC7vxVXr5smwAky",
"confirmation_method": "manual",
"created": 1656493017,
"currency": "usd",
"customer": "cus_8Di1wjdVktv5kt",
"description": "Invoice reference: 2206001/VL",
"invoice": null,
"last_payment_error": null,
"livemode": false,
"metadata": {
},
"next_action": null,
"on_behalf_of": null,
"payment_method": "pm_1LFwhg2sOmf47Nz9GYPwlv3G",
"payment_method_options": {
"card": {
"installments": null,
"mandate_options": null,
"network": null,
"request_three_d_secure": "automatic"
}
},
"payment_method_types": [
"card"
],
"processing": null,
"receipt_email": null,
"review": null,
"setup_future_usage": null,
"shipping": null,
"source": null,
"statement_descriptor": null,
"statement_descriptor_suffix": null,
"status": "succeeded",
"transfer_data": null,
"transfer_group": null
}
recorded_at: Wed, 29 Jun 2022 08:57:05 GMT
recorded_with: VCR 6.0.0