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

Merge branch 'monthly-payment' into staging

This commit is contained in:
Sylvain 2021-02-10 10:26:57 +01:00
commit 1cafc99c2b
31 changed files with 310 additions and 134 deletions

View File

@ -6,6 +6,15 @@ class API::PaymentSchedulesController < API::ApiController
before_action :set_payment_schedule, only: %i[download cancel]
before_action :set_payment_schedule_item, only: %i[cash_check refresh_item pay_item]
def index
@payment_schedules = PaymentSchedule.where('invoicing_profile_id = ?', current_user.invoicing_profile.id)
.includes(:invoicing_profile, :payment_schedule_items, :subscription)
.joins(:invoicing_profile)
.order('payment_schedules.created_at DESC')
.page(params[:page])
.per(params[:size])
end
def list
authorize PaymentSchedule

View File

@ -33,9 +33,13 @@ function extractHumanReadableMessage(error: any): string {
return error;
}
// parse Rails errors (as JSON)
// parse Rails errors (as JSON) or API errors
let message = '';
if (error instanceof Object) {
// API errors
if (error.hasOwnProperty('error') && typeof error.error === 'string') {
return error.error;
}
// iterate through all the keys to build the message
for (const key in error) {
if (Object.prototype.hasOwnProperty.call(error, key)) {

View File

@ -14,6 +14,11 @@ export default class PaymentScheduleAPI {
return res?.data;
}
async index (query: PaymentScheduleIndexRequest): Promise<Array<PaymentSchedule>> {
const res: AxiosResponse = await apiClient.get(`/api/payment_schedules?page=${query.query.page}&size=${query.query.size}`);
return res?.data;
}
async cashCheck(paymentScheduleItemId: number): Promise<CashCheckResponse> {
const res: AxiosResponse = await apiClient.post(`/api/payment_schedules/items/${paymentScheduleItemId}/cash_check`);
return res?.data;
@ -33,10 +38,5 @@ export default class PaymentScheduleAPI {
const res: AxiosResponse = await apiClient.put(`/api/payment_schedules/${paymentScheduleId}/cancel`);
return res?.data;
}
static list (query: PaymentScheduleIndexRequest): IWrapPromise<Array<PaymentSchedule>> {
const api = new PaymentScheduleAPI();
return wrapPromise(api.list(query));
}
}

View File

@ -0,0 +1,94 @@
/**
* This component shows a list of all payment schedules with their associated deadlines (aka. PaymentScheduleItem) and invoices
* for the currentUser
*/
import React, { useEffect, useState } from 'react';
import { IApplication } from '../models/application';
import { useTranslation } from 'react-i18next';
import { Loader } from './loader';
import { react2angular } from 'react2angular';
import PaymentScheduleAPI from '../api/payment-schedule';
import { PaymentSchedulesTable } from './payment-schedules-table';
import { FabButton } from './fab-button';
import { User } from '../models/user';
import { PaymentSchedule } from '../models/payment-schedule';
declare var Application: IApplication;
interface PaymentSchedulesDashboardProps {
currentUser: User
}
const PAGE_SIZE = 20;
const PaymentSchedulesDashboard: React.FC<PaymentSchedulesDashboardProps> = ({ currentUser }) => {
const { t } = useTranslation('logged');
const [paymentSchedules, setPaymentSchedules] = useState<Array<PaymentSchedule>>([]);
const [pageNumber, setPageNumber] = useState<number>(1);
useEffect(() => {
handleRefreshList();
}, []);
/**
* Fetch from the API the next payment schedules to display, for the current filters, and append them to the current results table.
*/
const handleLoadMore = (): void => {
setPageNumber(pageNumber + 1);
const api = new PaymentScheduleAPI();
api.index({ query: { page: pageNumber + 1, size: PAGE_SIZE }}).then((res) => {
const list = paymentSchedules.concat(res);
setPaymentSchedules(list);
});
}
/**
* Reload from te API all the currently displayed payment schedules
*/
const handleRefreshList = (onError?: (msg: any) => void): void => {
const api = new PaymentScheduleAPI();
api.index({ query: { page: 1, size: PAGE_SIZE * pageNumber }}).then((res) => {
setPaymentSchedules(res);
}).catch((err) => {
if (typeof onError === 'function') { onError(err.message); }
});
}
/**
* Check if the current collection of payment schedules is empty or not.
*/
const hasSchedules = (): boolean => {
return paymentSchedules.length > 0;
}
/**
* Check if there are some results for the current filters that aren't currently shown.
*/
const hasMoreSchedules = (): boolean => {
return hasSchedules() && paymentSchedules.length < paymentSchedules[0].max_length;
}
return (
<div className="payment-schedules-dashboard">
{!hasSchedules() && <div>{t('app.logged.dashboard.payment_schedules.no_payment_schedules')}</div>}
{hasSchedules() && <div className="schedules-list">
<PaymentSchedulesTable paymentSchedules={paymentSchedules} showCustomer={false} refreshList={handleRefreshList} operator={currentUser} />
{hasMoreSchedules() && <FabButton className="load-more" onClick={handleLoadMore}>{t('app.logged.dashboard.payment_schedules.load_more')}</FabButton>}
</div>}
</div>
);
}
const PaymentSchedulesDashboardWrapper: React.FC<PaymentSchedulesDashboardProps> = ({ currentUser }) => {
return (
<Loader>
<PaymentSchedulesDashboard currentUser={currentUser} />
</Loader>
);
}
Application.Components.component('paymentSchedulesDashboard', react2angular(PaymentSchedulesDashboardWrapper, ['currentUser']));

View File

@ -2,7 +2,7 @@
* This component shows a list of all payment schedules with their associated deadlines (aka. PaymentScheduleItem) and invoices
*/
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { IApplication } from '../models/application';
import { useTranslation } from 'react-i18next';
import { Loader } from './loader';
@ -12,6 +12,7 @@ import { DocumentFilters } from './document-filters';
import { PaymentSchedulesTable } from './payment-schedules-table';
import { FabButton } from './fab-button';
import { User } from '../models/user';
import { PaymentSchedule } from '../models/payment-schedule';
declare var Application: IApplication;
@ -20,16 +21,19 @@ interface PaymentSchedulesListProps {
}
const PAGE_SIZE = 20;
const paymentSchedulesList = PaymentScheduleAPI.list({ query: { page: 1, size: 20 } });
const PaymentSchedulesList: React.FC<PaymentSchedulesListProps> = ({ currentUser }) => {
const { t } = useTranslation('admin');
const [paymentSchedules, setPaymentSchedules] = useState(paymentSchedulesList.read());
const [pageNumber, setPageNumber] = useState(1);
const [referenceFilter, setReferenceFilter] = useState(null);
const [customerFilter, setCustomerFilter] = useState(null);
const [dateFilter, setDateFilter] = useState(null);
const [paymentSchedules, setPaymentSchedules] = useState<Array<PaymentSchedule>>([]);
const [pageNumber, setPageNumber] = useState<number>(1);
const [referenceFilter, setReferenceFilter] = useState<string>(null);
const [customerFilter, setCustomerFilter] = useState<string>(null);
const [dateFilter, setDateFilter] = useState<Date>(null);
useEffect(() => {
handleRefreshList();
}, []);
/**
* Fetch from the API the payments schedules matching the given filters and reset the results table with the new schedules.
@ -61,10 +65,12 @@ const PaymentSchedulesList: React.FC<PaymentSchedulesListProps> = ({ currentUser
/**
* Reload from te API all the currently displayed payment schedules
*/
const handleRefreshList = (): void => {
const handleRefreshList = (onError?: (msg: any) => void): void => {
const api = new PaymentScheduleAPI();
api.list({ query: { reference: referenceFilter, customer: customerFilter, date: dateFilter, page: 1, size: PAGE_SIZE * pageNumber }}).then((res) => {
setPaymentSchedules(res);
}).catch((err) => {
if (typeof onError === 'function') { onError(err.message); }
});
}

View File

@ -25,12 +25,12 @@ declare var Fablab: IFablab;
interface PaymentSchedulesTableProps {
paymentSchedules: Array<PaymentSchedule>,
showCustomer?: boolean,
refreshList: () => void,
refreshList: (onError: (msg: any) => void) => void,
operator: User,
}
const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({ paymentSchedules, showCustomer, refreshList, operator }) => {
const { t } = useTranslation('admin');
const { t } = useTranslation('shared');
const [showExpanded, setShowExpanded] = useState<Map<number, boolean>>(new Map());
const [showConfirmCashing, setShowConfirmCashing] = useState<boolean>(false);
@ -114,7 +114,7 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
return (
<a href={link} target="_blank" className="download-button">
<i className="fas fa-download" />
{t('app.admin.invoices.schedules_table.download')}
{t('app.shared.schedules_table.download')}
</a>
);
}
@ -123,9 +123,9 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
* Return the human-readable string for the status of the provided deadline.
*/
const formatState = (item: PaymentScheduleItem): JSX.Element => {
let res = t(`app.admin.invoices.schedules_table.state_${item.state}`);
let res = t(`app.shared.schedules_table.state_${item.state}`);
if (item.state === PaymentScheduleItemState.Paid) {
const key = `app.admin.invoices.schedules_table.method_${item.payment_method}`
const key = `app.shared.schedules_table.method_${item.payment_method}`
res += ` (${t(key)})`;
}
return <span className={`state-${item.state}`}>{res}</span>;
@ -150,24 +150,24 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
return (
<FabButton onClick={handleConfirmCheckPayment(item)}
icon={<i className="fas fa-money-check" />}>
{t('app.admin.invoices.schedules_table.confirm_payment')}
{t('app.shared.schedules_table.confirm_payment')}
</FabButton>
);
} else {
return <span>{t('app.admin.invoices.schedules_table.please_ask_reception')}</span>
return <span>{t('app.shared.schedules_table.please_ask_reception')}</span>
}
case PaymentScheduleItemState.RequireAction:
return (
<FabButton onClick={handleSolveAction(item)}
icon={<i className="fas fa-wrench" />}>
{t('app.admin.invoices.schedules_table.solve')}
{t('app.shared.schedules_table.solve')}
</FabButton>
);
case PaymentScheduleItemState.RequirePaymentMethod:
return (
<FabButton onClick={handleUpdateCard(item, schedule)}
icon={<i className="fas fa-credit-card" />}>
{t('app.admin.invoices.schedules_table.update_card')}
{t('app.shared.schedules_table.update_card')}
</FabButton>
);
case PaymentScheduleItemState.Error:
@ -175,11 +175,11 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
return (
<FabButton onClick={handleCancelSubscription(schedule)}
icon={<i className="fas fa-times" />}>
{t('app.admin.invoices.schedules_table.cancel_subscription')}
{t('app.shared.schedules_table.cancel_subscription')}
</FabButton>
)
} else {
return <span>{t('app.admin.invoices.schedules_table.please_ask_reception')}</span>
return <span>{t('app.shared.schedules_table.please_ask_reception')}</span>
}
default:
return <span />
@ -203,12 +203,19 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
const api = new PaymentScheduleAPI();
api.cashCheck(tempDeadline.id).then((res) => {
if (res.state === PaymentScheduleItemState.Paid) {
refreshList();
refreshSchedulesTable();
toggleConfirmCashingModal();
}
});
}
/**
* Refresh all payment schedules in the table
*/
const refreshSchedulesTable = (): void => {
refreshList(setErrors);
}
/**
* Show/hide the modal dialog that enable to confirm the cashing of the check for a given deadline.
*/
@ -240,7 +247,7 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
toggleConfirmActionButton();
const api = new PaymentScheduleAPI();
api.refreshItem(tempDeadline.id).then(() => {
refreshList();
refreshSchedulesTable();
toggleResolveActionModal();
});
}
@ -297,10 +304,10 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
const handleCardUpdateSuccess = (): void => {
const api = new PaymentScheduleAPI();
api.payItem(tempDeadline.id).then(() => {
refreshList();
refreshSchedulesTable();
toggleUpdateCardModal();
}).catch((err) => {
handleCardUpdateError(err.error);
handleCardUpdateError(err);
});
}
@ -335,7 +342,7 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
const onCancelSubscriptionConfirmed = (): void => {
const api = new PaymentScheduleAPI();
api.cancel(tempSchedule.id).then(() => {
refreshList();
refreshSchedulesTable();
toggleCancelSubscriptionModal();
});
}
@ -346,16 +353,16 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
<thead>
<tr>
<th className="w-35" />
<th className="w-200">{t('app.admin.invoices.schedules_table.schedule_num')}</th>
<th className="w-200">{t('app.admin.invoices.schedules_table.date')}</th>
<th className="w-120">{t('app.admin.invoices.schedules_table.price')}</th>
{showCustomer && <th className="w-200">{t('app.admin.invoices.schedules_table.customer')}</th>}
<th className="w-200">{t('app.shared.schedules_table.schedule_num')}</th>
<th className="w-200">{t('app.shared.schedules_table.date')}</th>
<th className="w-120">{t('app.shared.schedules_table.price')}</th>
{showCustomer && <th className="w-200">{t('app.shared.schedules_table.customer')}</th>}
<th className="w-200"/>
</tr>
</thead>
<tbody>
{paymentSchedules.map(p => <tr key={p.id}>
<td colSpan={6}>
<td colSpan={showCustomer ? 6 : 5}>
<table className="schedules-table-body">
<tbody>
<tr>
@ -368,14 +375,14 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
</tr>
<tr style={{ display: statusDisplay(p.id) }}>
<td className="w-35" />
<td colSpan={5}>
<td colSpan={showCustomer ? 5 : 4}>
<div>
<table className="schedule-items-table">
<thead>
<tr>
<th className="w-120">{t('app.admin.invoices.schedules_table.deadline')}</th>
<th className="w-120">{t('app.admin.invoices.schedules_table.amount')}</th>
<th className="w-200">{t('app.admin.invoices.schedules_table.state')}</th>
<th className="w-120">{t('app.shared.schedules_table.deadline')}</th>
<th className="w-120">{t('app.shared.schedules_table.amount')}</th>
<th className="w-200">{t('app.shared.schedules_table.state')}</th>
<th className="w-200" />
</tr>
</thead>
@ -398,37 +405,37 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
</tbody>
</table>
<div className="modals">
<FabModal title={t('app.admin.invoices.schedules_table.confirm_check_cashing')}
<FabModal title={t('app.shared.schedules_table.confirm_check_cashing')}
isOpen={showConfirmCashing}
toggleModal={toggleConfirmCashingModal}
onConfirm={onCheckCashingConfirmed}
closeButton={true}
confirmButton={t('app.admin.invoices.schedules_table.confirm_button')}>
confirmButton={t('app.shared.schedules_table.confirm_button')}>
{tempDeadline && <span>
{t('app.admin.invoices.schedules_table.confirm_check_cashing_body', {
{t('app.shared.schedules_table.confirm_check_cashing_body', {
AMOUNT: formatPrice(tempDeadline.amount),
DATE: formatDate(tempDeadline.due_date)
})}
</span>}
</FabModal>
<FabModal title={t('app.admin.invoices.schedules_table.cancel_subscription')}
<FabModal title={t('app.shared.schedules_table.cancel_subscription')}
isOpen={showCancelSubscription}
toggleModal={toggleCancelSubscriptionModal}
onConfirm={onCancelSubscriptionConfirmed}
closeButton={true}
confirmButton={t('app.admin.invoices.schedules_table.confirm_button')}>
{t('app.admin.invoices.schedules_table.confirm_cancel_subscription')}
confirmButton={t('app.shared.schedules_table.confirm_button')}>
{t('app.shared.schedules_table.confirm_cancel_subscription')}
</FabModal>
<StripeElements>
<FabModal title={t('app.admin.invoices.schedules_table.resolve_action')}
<FabModal title={t('app.shared.schedules_table.resolve_action')}
isOpen={showResolveAction}
toggleModal={toggleResolveActionModal}
onConfirm={afterAction}
confirmButton={t('app.admin.invoices.schedules_table.ok_button')}
confirmButton={t('app.shared.schedules_table.ok_button')}
preventConfirm={isConfirmActionDisabled}>
{tempDeadline && <StripeConfirm clientSecret={tempDeadline.client_secret} onResponse={toggleConfirmActionButton} />}
</FabModal>
<FabModal title={t('app.admin.invoices.schedules_table.update_card')}
<FabModal title={t('app.shared.schedules_table.update_card')}
isOpen={showUpdateCard}
toggleModal={toggleUpdateCardModal}
closeButton={false}
@ -445,7 +452,7 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
</div>}
</StripeCardUpdate>}
<div className="submit-card">
{canSubmitUpdateCard && <button type="submit" disabled={!canSubmitUpdateCard} form="stripe-card" className="submit-card-btn">{t('app.admin.invoices.schedules_table.validate_button')}</button>}
{canSubmitUpdateCard && <button type="submit" disabled={!canSubmitUpdateCard} form="stripe-card" className="submit-card-btn">{t('app.shared.schedules_table.validate_button')}</button>}
{!canSubmitUpdateCard && <div className="payment-pending">
<div className="fa-2x">
<i className="fas fa-circle-notch fa-spin" />

View File

@ -30,13 +30,13 @@ Application.Controllers.controller('DashboardController', ['$scope', 'memberProm
const initialize = () => $scope.social.networks = filterNetworks();
/**
* Filter social network or website that are associated with the profile of the user provided in promise
* Filter the social networks or websites that are associated with the profile of the user provided in promise
* and return the filtered networks
* @return {Array}
*/
var filterNetworks = function () {
const filterNetworks = function () {
const networks = [];
for (let network of Array.from(SocialNetworks)) {
for (const network of Array.from(SocialNetworks)) {
if ($scope.user.profile[network] && ($scope.user.profile[network].length > 0)) {
networks.push(network);
}

View File

@ -205,6 +205,15 @@ angular.module('application.router', ['ui.router'])
}
}
})
.state('app.logged.dashboard.payment_schedules', {
url: '/payment_schedules',
views: {
'main@': {
templateUrl: '/dashboard/payment_schedules.html',
controller: 'DashboardController'
}
}
})
.state('app.logged.dashboard.wallet', {
url: '/wallet',
abstract: !Fablab.walletModule,

View File

@ -291,9 +291,9 @@
padding: 15px 0;
background-color: $bg-gray;
.wrap {
width: 100px;
height: 100px;
.wrap, .wrap-monthly {
width: 130px;
height: 130px;
display: inline-block;
background: white;
@ -302,38 +302,39 @@
border: 3px solid;
.price {
width: 84px;
width: 114px;
display: flex;
flex-direction: column;
justify-content: center;
@include border-radius(50%, 50%, 50%, 50%);
}
}
.wrap-monthly {
height: 100px;
width: 180px;
display: inline-block;
background: white;
border: 3px solid;
@include border-radius(25px, 25px, 25px, 25px);
& > .price {
& > .amount {
padding-top: 4px;
line-height: 1.2em;
}
.price {
width: 164px;
@include border-radius(20px, 20px, 20px, 20px);
& > .period {
padding-top: 4px;
}
}
}
.price {
position: relative;
top: 5px;
left: 5px;
height: 84px;
height: 114px;
background-color: black;
.amount {
padding-top: 16px;
padding-left: 4px;
padding-right: 4px;
font-weight: bold;
font-size: rem-calc(18);
font-size: rem-calc(17);
color: white;
}

View File

@ -31,5 +31,6 @@
@import "modules/payment-schedules-table";
@import "modules/payment-schedules-list";
@import "modules/stripe-confirm";
@import "modules/payment-schedule-dashboard";
@import "app.responsive";

View File

@ -0,0 +1,12 @@
.payment-schedules-dashboard {
margin: 30px 15px 15px;
.schedules-list {
text-align: center;
.load-more {
margin-top: 2em;
}
}
}

View File

@ -134,6 +134,7 @@
.stripe-errors {
padding: 4px 0;
color: #9e2146;
overflow: auto;
}
}
.submit-card {

View File

@ -12,6 +12,7 @@
.stripe-errors {
padding: 4px 0;
color: #9e2146;
overflow: auto;
margin-bottom: 1.2em;
}
}

View File

@ -16,6 +16,7 @@
<li ui-sref-active="active"><a class="text-black" href="#" ui-sref="app.logged.dashboard.trainings" translate>{{ 'app.public.common.my_trainings' }}</a></li>
<li ui-sref-active="active"><a class="text-black" href="#" 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" href="#" 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" href="#" ui-sref="app.logged.dashboard.payment_schedules" translate>{{ 'app.public.common.my_payment_schedules' }}</a></li>
<li ng-show="$root.modules.wallet" ui-sref-active="active"><a class="text-black" href="#" ui-sref="app.logged.dashboard.wallet" translate>{{ 'app.public.common.my_wallet' }}</a></li>
</ul>
</section>

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>
<payment-schedules-dashboard current-user="currentUser" />
</div>

View File

@ -10,7 +10,7 @@
<span class="avatar avatar-block text-center">
<fab-user-avatar ng-model="member.profile.user_avatar" avatar-class="thumb-50"></fab-user-avatar>
<!-- <i class="on b-white bottom"></i> -->
<a ><span class="user-name m-l-sm m-t-xs">{{member.name}}</span></a>
<a><span class="user-name m-l-sm m-t-xs">{{member.name}}</span></a>
</span>
</div>

View File

@ -40,6 +40,7 @@
<li><a ui-sref="app.logged.dashboard.trainings" translate>{{ 'app.public.common.my_trainings' }}</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>
<li ng-show="$root.modules.wallet"><a ui-sref="app.logged.dashboard.wallet" translate>{{ 'app.public.common.my_wallet' }}</a></li>
<li class="divider" ng-if="isAuthorized(['admin', 'manager'])"></li>
<li><a class="text-black pointer" ng-click="help($event)" ng-if="isAuthorized(['admin', 'manager'])"><i class="fa fa-question-circle"></i> <span translate>{{ 'app.public.common.help' }}</span> </a></li>

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
max_schedules = @payment_schedules.except(:offset, :limit, :order).count
json.array! @payment_schedules do |ps|
json.max_length max_schedules
json.partial! 'api/payment_schedules/payment_schedule', payment_schedule: ps
end

View File

@ -7,4 +7,4 @@
DATE: I18n.l(@attached_object.due_date, format: :long)) %>
<%= t('.body.error') %>
</p>
<p><%= t('.body.action', DASHBOARD: link_to(t('.body.your_dashboard'), "#{root_url}#!/dashboard/invoices")) %></p>
<p><%= t('.body.action_html', DASHBOARD: link_to(t('.body.your_dashboard'), "#{root_url}#!/dashboard/payment_schedules")) %></p>

View File

@ -14,7 +14,7 @@
<p><%= t('.body.schedule_in_your_dashboard_html',
DASHBOARD: link_to(
t('.body.your_dashboard'),
"#{root_url}#!/dashboard/invoices"
"#{root_url}#!/dashboard/payment_schedules"
)
) %>
</p>

View File

@ -644,36 +644,6 @@ en:
filter_schedules: "Filter schedules"
no_payment_schedules: "No payment schedules to display"
load_more: "Load more"
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: "Waiting for the cashing of the check"
state_requires_payment_method: "The credit card must be updated"
state_requires_action: "Action required"
state_paid: "Paid"
state_error: "Error"
state_canceled: "Canceled"
method_stripe: "by card"
method_check: "by check"
confirm_payment: "Confirm payment"
solve: "Solve"
update_card: "Update the card"
confirm_check_cashing: "Confirm the cashing of the check"
confirm_check_cashing_body: "You must cash a check of {AMOUNT} for the deadline of {DATE}. By confirming the cashing of the check, an invoice will be generated for this due date."
confirm_button: "Confirm"
resolve_action: "Resolve the action"
ok_button: "OK"
validate_button: "Validate the new card"
cancel_subscription: "Cancel the subscription"
confirm_cancel_subscription: "You're about to cancel this payment schedule and the related subscription. Are you sure?"
please_ask_reception: "For any questions, please contact the FabLab's reception."
document_filters:
reference: "Reference"
customer: "Customer"

View File

@ -644,36 +644,6 @@ fr:
filter_schedules: "Filtrer les échéanciers"
no_payment_schedules: "Pas d'échéancier à afficher"
load_more: "Voir plus"
schedules_table:
schedule_num: "Échéancier n°"
date: "Date"
price: "Prix"
customer: "Client"
deadline: "Échéance"
amount: "Montant"
state: "État"
download: "Télécharger"
state_new: "Pas encore à l'échéance"
state_pending: "En attente de l'encaissement du chèque"
state_requires_payment_method: "La carte bancaire doit être mise à jour"
state_requires_action: "Action requise"
state_paid: "Payée"
state_error: "Erreur"
state_canceled: "Annulée"
method_stripe: "par carte"
method_check: "par chèque"
confirm_payment: "Confirmer l'encaissement"
solve: "Résoudre"
update_card: "Mettre à jour la carte"
confirm_check_cashing: "Confirmer l'encaissement du chèque"
confirm_check_cashing_body: "Vous devez encaisser un chèque de {AMOUNT} pour l'échéance du {DATE}. En confirmant l'encaissement du chèque, une facture sera générée pour cette échéance."
confirm_button: "Confirmer"
resolve_action: "Résoudre l'action"
ok_button: "OK"
validate_button: "Valider la nouvelle carte"
cancel_subscription: "Annuler l'abonnement"
confirm_cancel_subscription: "Vous êtes sur le point d'annuler cet échéancier de paiement ainsi que l'abonnement lié. Êtes-vous sur ?"
please_ask_reception: "Pour toute question, merci de contacter l'accueil du FabLab."
document_filters:
reference: "Référence"
customer: "Client"

View File

@ -125,6 +125,9 @@ en:
download_the_invoice: "Download the invoice"
download_the_credit_note: "Download the refund invoice"
no_invoices_for_now: "No invoices for now."
payment_schedules:
no_payment_schedules: "No payment schedules to display"
load_more: "Load more"
#public profil of a member
members_show:
members_list: "Members list"

View File

@ -125,6 +125,9 @@ fr:
download_the_invoice: "Télécharger la facture"
download_the_credit_note: "Télécharger l'avoir"
no_invoices_for_now: "Aucune facture pour le moment."
payment_schedules:
no_payment_schedules: "Pas d'échéancier à afficher"
load_more: "Voir plus"
#public profil of a member
members_show:
members_list: "Liste des membres"

View File

@ -19,6 +19,7 @@ en:
my_trainings: "My Trainings"
my_events: "My Events"
my_invoices: "My Invoices"
my_payment_schedules: "My payment schedules"
my_wallet: "My Wallet"
#contextual help
help: "Help"

View File

@ -19,6 +19,7 @@ fr:
my_trainings: "Mes formations"
my_events: "Mes événements"
my_invoices: "Mes factures"
my_payment_schedules: "Mes échéanciers de paiement"
my_wallet: "Mon porte-monnaie"
#contextual help
help: "Aide"

View File

@ -478,3 +478,34 @@ en:
stripe_confirm:
pending: "Pending for action..."
success: "Thank you, your card setup is complete. The payment will be proceeded shortly."
# the summary table of all payment schedules
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: "Waiting for the cashing of the check"
state_requires_payment_method: "The credit card must be updated"
state_requires_action: "Action required"
state_paid: "Paid"
state_error: "Error"
state_canceled: "Canceled"
method_stripe: "by card"
method_check: "by check"
confirm_payment: "Confirm payment"
solve: "Solve"
update_card: "Update the card"
confirm_check_cashing: "Confirm the cashing of the check"
confirm_check_cashing_body: "You must cash a check of {AMOUNT} for the deadline of {DATE}. By confirming the cashing of the check, an invoice will be generated for this due date."
confirm_button: "Confirm"
resolve_action: "Resolve the action"
ok_button: "OK"
validate_button: "Validate the new card"
cancel_subscription: "Cancel the subscription"
confirm_cancel_subscription: "You're about to cancel this payment schedule and the related subscription. Are you sure?"
please_ask_reception: "For any questions, please contact the FabLab's reception."

View File

@ -478,3 +478,34 @@ fr:
stripe_confirm:
pending: "En attente de l'action ..."
success: "Merci, la configuration de votre carte est terminée. Le paiement sera effectué sous peu."
# the summary table of all payment schedules
schedules_table:
schedule_num: "Échéancier n°"
date: "Date"
price: "Prix"
customer: "Client"
deadline: "Échéance"
amount: "Montant"
state: "État"
download: "Télécharger"
state_new: "Pas encore à l'échéance"
state_pending: "En attente de l'encaissement du chèque"
state_requires_payment_method: "La carte bancaire doit être mise à jour"
state_requires_action: "Action requise"
state_paid: "Payée"
state_error: "Erreur"
state_canceled: "Annulée"
method_stripe: "par carte"
method_check: "par chèque"
confirm_payment: "Confirmer l'encaissement"
solve: "Résoudre"
update_card: "Mettre à jour la carte"
confirm_check_cashing: "Confirmer l'encaissement du chèque"
confirm_check_cashing_body: "Vous devez encaisser un chèque de {AMOUNT} pour l'échéance du {DATE}. En confirmant l'encaissement du chèque, une facture sera générée pour cette échéance."
confirm_button: "Confirmer"
resolve_action: "Résoudre l'action"
ok_button: "OK"
validate_button: "Valider la nouvelle carte"
cancel_subscription: "Annuler l'abonnement"
confirm_cancel_subscription: "Vous êtes sur le point d'annuler cet échéancier de paiement ainsi que l'abonnement lié. Êtes-vous sur ?"
please_ask_reception: "Pour toute question, merci de contacter l'accueil du FabLab."

View File

@ -305,7 +305,7 @@ en:
body:
remember: "In accordance with your %{REFERENCE} payment schedule, a debit by card of %{AMOUNT} was scheduled on %{DATE}."
error: "Unfortunately, this card debit was unable to complete successfully."
action: "Please check %{DASHBOARD} or contact a manager before 24 hours, otherwise your subscription may be interrupted."
action_html: "Please check %{DASHBOARD} or contact a manager before 24 hours, otherwise your subscription may be interrupted."
your_dashboard: "your dashboard"
notify_admin_payment_schedule_check_deadline:
subject: "Payment deadline"

View File

@ -305,7 +305,7 @@ fr:
body:
remember: "Conformément à votre échéancier de paiement %{REFERENCE}, un prélèvement par carte de %{AMOUNT} était prévu le %{DATE}."
error: "Malheureusement, ce prélèvement n'a pas pu être effectué correctement."
action: "Veuillez vous rendre dans votre %{DASHBOARD} ou prendre contact avec un gestionnaire sous 24 heures, faute de quoi votre abonnement risque d'être interrompu."
action_html: "Veuillez vous rendre dans %{DASHBOARD} ou prendre contact avec un gestionnaire sous 24 heures, faute de quoi votre abonnement risque d'être interrompu."
your_dashboard: "votre tableau de bord"
notify_admin_payment_schedule_check_deadline:
subject: "Échéance d'encaissement"

View File

@ -111,7 +111,7 @@ Rails.application.routes.draw do
get 'first', action: 'first', on: :collection
end
resources :payment_schedules, only: %i[show] do
resources :payment_schedules, only: %i[index show] do
post 'list', action: 'list', on: :collection
put 'cancel', on: :member
get 'download', on: :member