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:
commit
1cafc99c2b
@ -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
|
||||
|
||||
|
@ -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)) {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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']));
|
@ -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); }
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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" />
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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";
|
||||
|
@ -0,0 +1,12 @@
|
||||
.payment-schedules-dashboard {
|
||||
|
||||
margin: 30px 15px 15px;
|
||||
|
||||
.schedules-list {
|
||||
text-align: center;
|
||||
|
||||
.load-more {
|
||||
margin-top: 2em;
|
||||
}
|
||||
}
|
||||
}
|
@ -134,6 +134,7 @@
|
||||
.stripe-errors {
|
||||
padding: 4px 0;
|
||||
color: #9e2146;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
.submit-card {
|
||||
|
@ -12,6 +12,7 @@
|
||||
.stripe-errors {
|
||||
padding: 4px 0;
|
||||
color: #9e2146;
|
||||
overflow: auto;
|
||||
margin-bottom: 1.2em;
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
11
app/frontend/templates/dashboard/payment_schedules.html
Normal file
11
app/frontend/templates/dashboard/payment_schedules.html
Normal 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>
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
8
app/views/api/payment_schedules/index.json.jbuilder
Normal file
8
app/views/api/payment_schedules/index.json.jbuilder
Normal 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
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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."
|
||||
|
@ -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."
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user