mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-18 07:52:23 +01:00
Merge branch 'monthly-payment' into staging
This commit is contained in:
commit
36123990df
@ -4,6 +4,7 @@
|
||||
class API::PaymentSchedulesController < API::ApiController
|
||||
before_action :authenticate_user!
|
||||
before_action :set_payment_schedule, only: %i[download]
|
||||
before_action :set_payment_schedule_item, only: %i[cash_check]
|
||||
|
||||
def list
|
||||
authorize PaymentSchedule
|
||||
@ -25,9 +26,22 @@ class API::PaymentSchedulesController < API::ApiController
|
||||
send_file File.join(Rails.root, @payment_schedule.file), type: 'application/pdf', disposition: 'attachment'
|
||||
end
|
||||
|
||||
def cash_check
|
||||
schedule = @payment_schedule_item.payment_schedule
|
||||
authorize schedule
|
||||
PaymentScheduleService.new.generate_invoice(@payment_schedule_item)
|
||||
@payment_schedule_item.update_attributes(state: 'paid', payment_method: 'check')
|
||||
|
||||
render :show, status: :ok, location: schedule
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_payment_schedule
|
||||
@payment_schedule = PaymentSchedule.find(params[:id])
|
||||
end
|
||||
|
||||
def set_payment_schedule_item
|
||||
@payment_schedule_item = PaymentScheduleItem.find(params[:id])
|
||||
end
|
||||
end
|
||||
|
@ -1,6 +1,6 @@
|
||||
import apiClient from './api-client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { PaymentSchedule, PaymentScheduleIndexRequest } from '../models/payment-schedule';
|
||||
import { PaymentSchedule, PaymentScheduleIndexRequest, PaymentScheduleItem } from '../models/payment-schedule';
|
||||
import wrapPromise, { IWrapPromise } from '../lib/wrap-promise';
|
||||
|
||||
export default class PaymentScheduleAPI {
|
||||
@ -9,6 +9,11 @@ export default class PaymentScheduleAPI {
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
async cashCheck(paymentScheduleItemId: number) {
|
||||
const res: AxiosResponse = await apiClient.post(`/api/payment_schedules/items/${paymentScheduleItemId}/cash_check`);
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static list (query: PaymentScheduleIndexRequest): IWrapPromise<Array<PaymentSchedule>> {
|
||||
const api = new PaymentScheduleAPI();
|
||||
return wrapPromise(api.list(query));
|
||||
|
38
app/frontend/src/javascript/components/fab-button.tsx
Normal file
38
app/frontend/src/javascript/components/fab-button.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* This component is a template for a clickable button that wraps the application style
|
||||
*/
|
||||
|
||||
import React, { ReactNode, SyntheticEvent } from 'react';
|
||||
|
||||
interface FabButtonProps {
|
||||
onClick?: (event: SyntheticEvent) => void,
|
||||
icon?: ReactNode,
|
||||
className?: string,
|
||||
}
|
||||
|
||||
|
||||
export const FabButton: React.FC<FabButtonProps> = ({ onClick, icon, className, children }) => {
|
||||
/**
|
||||
* Check if the current component was provided an icon to display
|
||||
*/
|
||||
const hasIcon = (): boolean => {
|
||||
return !!icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the action of the button
|
||||
*/
|
||||
const handleClick = (e: SyntheticEvent): void => {
|
||||
if (typeof onClick === 'function') {
|
||||
onClick(e);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<button onClick={handleClick} className={`fab-button ${className ? className : ''}`}>
|
||||
{hasIcon() && <span className="fab-button--icon">{icon}</span>}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
@ -2,12 +2,13 @@
|
||||
* This component is a template for a modal dialog that wraps the application style
|
||||
*/
|
||||
|
||||
import React, { ReactNode } from 'react';
|
||||
import React, { ReactNode, SyntheticEvent } from 'react';
|
||||
import Modal from 'react-modal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Loader } from './loader';
|
||||
import CustomAssetAPI from '../api/custom-asset';
|
||||
import { CustomAssetName } from '../models/custom-asset';
|
||||
import { FabButton } from './fab-button';
|
||||
|
||||
Modal.setAppElement('body');
|
||||
|
||||
@ -25,12 +26,13 @@ interface FabModalProps {
|
||||
closeButton?: boolean,
|
||||
className?: string,
|
||||
width?: ModalSize,
|
||||
customFooter?: ReactNode
|
||||
customFooter?: ReactNode,
|
||||
onConfirm?: (event: SyntheticEvent) => void
|
||||
}
|
||||
|
||||
const blackLogoFile = CustomAssetAPI.get(CustomAssetName.LogoBlackFile);
|
||||
|
||||
export const FabModal: React.FC<FabModalProps> = ({ title, isOpen, toggleModal, children, confirmButton, className, width = 'sm', closeButton, customFooter }) => {
|
||||
export const FabModal: React.FC<FabModalProps> = ({ title, isOpen, toggleModal, children, confirmButton, className, width = 'sm', closeButton, customFooter, onConfirm }) => {
|
||||
const { t } = useTranslation('shared');
|
||||
const blackLogo = blackLogoFile.read();
|
||||
|
||||
@ -56,7 +58,7 @@ export const FabModal: React.FC<FabModalProps> = ({ title, isOpen, toggleModal,
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen}
|
||||
<Modal isOpen={isOpen}onConfirm
|
||||
className={`fab-modal fab-modal-${width} ${className}`}
|
||||
overlayClassName="fab-modal-overlay"
|
||||
onRequestClose={toggleModal}>
|
||||
@ -73,8 +75,8 @@ export const FabModal: React.FC<FabModalProps> = ({ title, isOpen, toggleModal,
|
||||
</div>
|
||||
<div className="fab-modal-footer">
|
||||
<Loader>
|
||||
{hasCloseButton() &&<button className="modal-btn--close" onClick={toggleModal}>{t('app.shared.buttons.close')}</button>}
|
||||
{hasConfirmButton() && <span className="modal-btn--confirm">{confirmButton}</span>}
|
||||
{hasCloseButton() &&<FabButton className="modal-btn--close" onClick={toggleModal}>{t('app.shared.buttons.close')}</FabButton>}
|
||||
{hasConfirmButton() && <FabButton className="modal-btn--confirm" onClick={onConfirm}>{confirmButton}</FabButton>}
|
||||
{hasCustomFooter() && customFooter}
|
||||
</Loader>
|
||||
</div>
|
||||
|
@ -10,23 +10,73 @@ import { react2angular } from 'react2angular';
|
||||
import PaymentScheduleAPI from '../api/payment-schedule';
|
||||
import { DocumentFilters } from './document-filters';
|
||||
import { PaymentSchedulesTable } from './payment-schedules-table';
|
||||
import { FabButton } from './fab-button';
|
||||
|
||||
declare var Application: IApplication;
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
const paymentSchedulesList = PaymentScheduleAPI.list({ query: { page: 1, size: 20 } });
|
||||
|
||||
const PaymentSchedulesList: React.FC = () => {
|
||||
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);
|
||||
|
||||
/**
|
||||
* Fetch from the API the payments schedules matching the given filters and reset the results table with the new schedules.
|
||||
*/
|
||||
const handleFiltersChange = ({ reference, customer, date }): void => {
|
||||
setReferenceFilter(reference);
|
||||
setCustomerFilter(customer);
|
||||
setDateFilter(date);
|
||||
|
||||
const api = new PaymentScheduleAPI();
|
||||
api.list({ query: { reference, customer, date, page: 1, size: 20 }}).then((res) => {
|
||||
api.list({ query: { reference, customer, date, page: 1, size: PAGE_SIZE }}).then((res) => {
|
||||
setPaymentSchedules(res);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.list({ query: { reference: referenceFilter, customer: customerFilter, date: dateFilter, 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 = (): void => {
|
||||
const api = new PaymentScheduleAPI();
|
||||
api.list({ query: { reference: referenceFilter, customer: customerFilter, date: dateFilter, page: 1, size: PAGE_SIZE * pageNumber }}).then((res) => {
|
||||
setPaymentSchedules(res);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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-list">
|
||||
<h3>
|
||||
@ -36,7 +86,11 @@ const PaymentSchedulesList: React.FC = () => {
|
||||
<div className="schedules-filters">
|
||||
<DocumentFilters onFilterChange={handleFiltersChange} />
|
||||
</div>
|
||||
<PaymentSchedulesTable paymentSchedules={paymentSchedules} showCustomer={true} />
|
||||
{!hasSchedules() && <div>{t('app.admin.invoices.payment_schedules.no_payment_schedules')}</div>}
|
||||
{hasSchedules() && <div className="schedules-list">
|
||||
<PaymentSchedulesTable paymentSchedules={paymentSchedules} showCustomer={true} refreshList={handleRefreshList} />
|
||||
{hasMoreSchedules() && <FabButton className="load-more" onClick={handleLoadMore}>{t('app.admin.invoices.payment_schedules.load_more')}</FabButton>}
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -2,31 +2,37 @@
|
||||
* This component shows a list of all payment schedules with their associated deadlines (aka. PaymentScheduleItem) and invoices
|
||||
*/
|
||||
|
||||
import React, { ReactEventHandler, useState } from 'react';
|
||||
import React, { ReactEventHandler, ReactNode, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Loader } from './loader';
|
||||
import moment from 'moment';
|
||||
import { IFablab } from '../models/fablab';
|
||||
import _ from 'lodash';
|
||||
import { PaymentSchedule, PaymentScheduleItem, PaymentScheduleItemState } from '../models/payment-schedule';
|
||||
import { FabButton } from './fab-button';
|
||||
import { FabModal } from './fab-modal';
|
||||
import PaymentScheduleAPI from '../api/payment-schedule';
|
||||
|
||||
declare var Fablab: IFablab;
|
||||
|
||||
interface PaymentSchedulesTableProps {
|
||||
paymentSchedules: Array<PaymentSchedule>,
|
||||
showCustomer?: boolean
|
||||
showCustomer?: boolean,
|
||||
refreshList: () => void
|
||||
}
|
||||
|
||||
const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({ paymentSchedules, showCustomer }) => {
|
||||
const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({ paymentSchedules, showCustomer, refreshList }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const [showExpanded, setShowExpanded] = useState({});
|
||||
const [showExpanded, setShowExpanded] = useState<Map<number, boolean>>(new Map());
|
||||
const [showConfirmCashing, setShowConfirmCashing] = useState<boolean>(false);
|
||||
const [tempDeadline, setTempDeadline] = useState<PaymentScheduleItem>(null);
|
||||
|
||||
/**
|
||||
* Check if the requested payment schedule is displayed with its deadlines (PaymentScheduleItem) or without them
|
||||
*/
|
||||
const isExpanded = (paymentScheduleId: number): boolean => {
|
||||
return showExpanded[paymentScheduleId];
|
||||
return showExpanded.get(paymentScheduleId);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -70,9 +76,9 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
|
||||
const togglePaymentScheduleDetails = (paymentScheduleId: number): ReactEventHandler => {
|
||||
return (): void => {
|
||||
if (isExpanded(paymentScheduleId)) {
|
||||
setShowExpanded(Object.assign({}, showExpanded, { [paymentScheduleId]: false }));
|
||||
setShowExpanded((prev) => new Map(prev).set(paymentScheduleId, false));
|
||||
} else {
|
||||
setShowExpanded(Object.assign({}, showExpanded, { [paymentScheduleId]: true }));
|
||||
setShowExpanded((prev) => new Map(prev).set(paymentScheduleId, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -119,24 +125,24 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
|
||||
return downloadButton(TargetType.Invoice, item.invoice_id);
|
||||
case PaymentScheduleItemState.Pending:
|
||||
return (
|
||||
<button className="action-button" onClick={handleConfirmCheckPayment(item)}>
|
||||
<i className="fas fa-money-check" />
|
||||
<FabButton onClick={handleConfirmCheckPayment(item)}
|
||||
icon={<i className="fas fa-money-check" />}>
|
||||
{t('app.admin.invoices.schedules_table.confirm_payment')}
|
||||
</button>
|
||||
</FabButton>
|
||||
);
|
||||
case PaymentScheduleItemState.RequireAction:
|
||||
return (
|
||||
<button className="action-button" onClick={handleSolveAction(item)}>
|
||||
<i className="fas fa-wrench" />
|
||||
<FabButton onClick={handleSolveAction(item)}
|
||||
icon={<i className="fas fa-wrench" />}>
|
||||
{t('app.admin.invoices.schedules_table.solve')}
|
||||
</button>
|
||||
</FabButton>
|
||||
);
|
||||
case PaymentScheduleItemState.RequirePaymentMethod:
|
||||
return (
|
||||
<button className="action-button" onClick={handleUpdateCard(item)}>
|
||||
<i className="fas fa-credit-card" />
|
||||
<FabButton onClick={handleUpdateCard(item)}
|
||||
icon={<i className="fas fa-credit-card" />}>
|
||||
{t('app.admin.invoices.schedules_table.update_card')}
|
||||
</button>
|
||||
</FabButton>
|
||||
);
|
||||
default:
|
||||
return <span />
|
||||
@ -145,14 +151,42 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
|
||||
|
||||
const handleConfirmCheckPayment = (item: PaymentScheduleItem): ReactEventHandler => {
|
||||
return (): void => {
|
||||
/*
|
||||
TODO
|
||||
- display confirmation modal
|
||||
- create /api/payment_schedule/item/confirm_check endpoint and post to it
|
||||
*/
|
||||
setTempDeadline(item);
|
||||
toggleConfirmCashingModal();
|
||||
}
|
||||
}
|
||||
|
||||
const onCheckCashingConfirmed = (): void => {
|
||||
const api = new PaymentScheduleAPI();
|
||||
api.cashCheck(tempDeadline.id).then(() => {
|
||||
refreshList();
|
||||
toggleConfirmCashingModal();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show/hide the modal dialog that enable to confirm the cashing of the check for a given deadline.
|
||||
*/
|
||||
const toggleConfirmCashingModal = (): void => {
|
||||
setShowConfirmCashing(!showConfirmCashing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically build the content of the modal depending on the currently selected deadline
|
||||
*/
|
||||
const cashingModalContent = (): ReactNode => {
|
||||
if (tempDeadline) {
|
||||
return (
|
||||
<span>{t('app.admin.invoices.schedules_table.confirm_check_cashing_body', {
|
||||
AMOUNT: formatPrice(tempDeadline.amount),
|
||||
DATE: formatDate(tempDeadline.due_date)
|
||||
})}</span>
|
||||
);
|
||||
}
|
||||
|
||||
return <span />;
|
||||
}
|
||||
|
||||
const handleSolveAction = (item: PaymentScheduleItem): ReactEventHandler => {
|
||||
return (): void => {
|
||||
/*
|
||||
@ -238,16 +272,26 @@ const PaymentSchedulesTableComponent: React.FC<PaymentSchedulesTableProps> = ({
|
||||
</tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="modals">
|
||||
<FabModal title={t('app.admin.invoices.schedules_table.confirm_check_cashing')}
|
||||
isOpen={showConfirmCashing}
|
||||
toggleModal={toggleConfirmCashingModal}
|
||||
onConfirm={onCheckCashingConfirmed}
|
||||
closeButton={true}
|
||||
confirmButton={t('app.admin.invoices.schedules_table.confirm_button')}>
|
||||
{cashingModalContent()}
|
||||
</FabModal>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
PaymentSchedulesTableComponent.defaultProps = { showCustomer: false };
|
||||
|
||||
|
||||
export const PaymentSchedulesTable: React.FC<PaymentSchedulesTableProps> = ({ paymentSchedules, showCustomer }) => {
|
||||
export const PaymentSchedulesTable: React.FC<PaymentSchedulesTableProps> = ({ paymentSchedules, showCustomer, refreshList }) => {
|
||||
return (
|
||||
<Loader>
|
||||
<PaymentSchedulesTableComponent paymentSchedules={paymentSchedules} showCustomer={showCustomer} />
|
||||
<PaymentSchedulesTableComponent paymentSchedules={paymentSchedules} showCustomer={showCustomer} refreshList={refreshList} />
|
||||
</Loader>
|
||||
);
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ export interface PaymentScheduleItem {
|
||||
}
|
||||
|
||||
export interface PaymentSchedule {
|
||||
max_length: number;
|
||||
id: number,
|
||||
scheduled_type: string,
|
||||
scheduled_id: number,
|
||||
|
@ -22,6 +22,7 @@
|
||||
@import "modules/stripe";
|
||||
@import "modules/tour";
|
||||
@import "modules/fab-modal";
|
||||
@import "modules/fab-button";
|
||||
@import "modules/payment-schedule-summary";
|
||||
@import "modules/wallet-info";
|
||||
@import "modules/stripe-modal";
|
||||
|
39
app/frontend/src/stylesheets/modules/fab-button.scss
Normal file
39
app/frontend/src/stylesheets/modules/fab-button.scss
Normal file
@ -0,0 +1,39 @@
|
||||
.fab-button {
|
||||
color: black;
|
||||
background-color: #fbfbfb;
|
||||
display: inline-block;
|
||||
margin-bottom: 0;
|
||||
font-weight: normal;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
touch-action: manipulation;
|
||||
cursor: pointer;
|
||||
background-image: none;
|
||||
border: 1px solid #c9c9c9;
|
||||
padding: 6px 12px;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
border-radius: 4px;
|
||||
user-select: none;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
background-color: #f2f2f2;
|
||||
color: black;
|
||||
border-color: #aaaaaa;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: black;
|
||||
background-color: #f2f2f2;
|
||||
border-color: #aaaaaa;
|
||||
outline: 0;
|
||||
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
|
||||
}
|
||||
|
||||
&--icon {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
@ -69,23 +69,7 @@
|
||||
border-top: 1px solid #e5e5e5;
|
||||
|
||||
.modal-btn {
|
||||
margin-bottom: 0;
|
||||
margin-left: 5px;
|
||||
display: inline-block;
|
||||
font-weight: normal;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
touch-action: manipulation;
|
||||
cursor: pointer;
|
||||
background-image: none;
|
||||
padding: 6px 12px;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
border-radius: 4px;
|
||||
|
||||
&--close {
|
||||
@extend .modal-btn;
|
||||
color: black;
|
||||
background-color: #fbfbfb;
|
||||
border: 1px solid #c9c9c9;
|
||||
@ -96,7 +80,7 @@
|
||||
}
|
||||
|
||||
&--confirm {
|
||||
@extend .modal-btn;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,11 @@
|
||||
.schedules-filters {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.schedules-list {
|
||||
text-align: center;
|
||||
|
||||
.load-more {
|
||||
margin-top: 2em;
|
||||
}
|
||||
}
|
||||
|
@ -99,41 +99,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.download-button,
|
||||
.action-button {
|
||||
color: black;
|
||||
background-color: #fbfbfb;
|
||||
display: inline-block;
|
||||
margin-bottom: 0;
|
||||
font-weight: normal;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
touch-action: manipulation;
|
||||
cursor: pointer;
|
||||
background-image: none;
|
||||
border: 1px solid #c9c9c9;
|
||||
padding: 6px 12px;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
border-radius: 4px;
|
||||
user-select: none;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
background-color: #f2f2f2;
|
||||
color: black;
|
||||
border-color: #aaaaaa;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: black;
|
||||
background-color: #f2f2f2;
|
||||
border-color: #aaaaaa;
|
||||
outline: 0;
|
||||
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
|
||||
}
|
||||
.download-button {
|
||||
@extend .fab-button;
|
||||
|
||||
& > i {
|
||||
margin-right: 0.5em;
|
||||
|
@ -6,6 +6,10 @@ class PaymentSchedulePolicy < ApplicationPolicy
|
||||
user.admin? || user.manager?
|
||||
end
|
||||
|
||||
def cash_check?
|
||||
user.admin? || user.manager?
|
||||
end
|
||||
|
||||
def download?
|
||||
user.admin? || user.manager? || (record.invoicing_profile.user_id == user.id)
|
||||
end
|
||||
|
@ -309,3 +309,21 @@ section#cookies-modal div.cookies-consent .cookies-actions button.accept {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fab-modal {
|
||||
.fab-modal-footer {
|
||||
.modal-btn--confirm {
|
||||
& {
|
||||
background-color: $secondary;
|
||||
color: $secondary-text-color;
|
||||
border-color: $secondary
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $secondary-dark !important;
|
||||
border-color: $secondary-dark !important;
|
||||
color: $secondary-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
json.extract! payment_schedule, :id, :reference, :created_at, :payment_method
|
||||
json.total payment_schedule.total / 100.00
|
||||
json.chained_footprint payment_schedule.check_footprint
|
||||
json.user do
|
||||
json.name payment_schedule.invoicing_profile.full_name
|
||||
end
|
||||
if payment_schedule.operator_profile
|
||||
json.operator do
|
||||
json.id payment_schedule.operator_profile.user_id
|
||||
json.extract! payment_schedule.operator_profile, :first_name, :last_name
|
||||
end
|
||||
end
|
||||
json.items payment_schedule.payment_schedule_items do |item|
|
||||
json.extract! item, :id, :due_date, :state, :invoice_id, :payment_method
|
||||
json.amount item.amount / 100.00
|
||||
json.client_secret item.payment_intent.client_secret if item.stp_invoice_id && item.state == 'requires_action'
|
||||
end
|
@ -1,21 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
max_schedules = @payment_schedules.except(:offset, :limit, :order).count
|
||||
|
||||
json.array! @payment_schedules do |ps|
|
||||
json.extract! ps, :id, :reference, :created_at, :payment_method
|
||||
json.total ps.total / 100.00
|
||||
json.chained_footprint ps.check_footprint
|
||||
json.user do
|
||||
json.name ps.invoicing_profile.full_name
|
||||
end
|
||||
if ps.operator_profile
|
||||
json.operator do
|
||||
json.id ps.operator_profile.user_id
|
||||
json.extract! ps.operator_profile, :first_name, :last_name
|
||||
end
|
||||
end
|
||||
json.items ps.payment_schedule_items do |item|
|
||||
json.extract! item, :id, :due_date, :state, :invoice_id, :payment_method
|
||||
json.amount item.amount / 100.00
|
||||
json.client_secret item.payment_intent.client_secret if item.stp_invoice_id && item.state == 'requires_action'
|
||||
end
|
||||
json.max_length max_schedules
|
||||
json.partial! 'api/payment_schedules/payment_schedule', payment_schedule: ps
|
||||
end
|
||||
|
3
app/views/api/payment_schedules/show.json.jbuilder
Normal file
3
app/views/api/payment_schedules/show.json.jbuilder
Normal file
@ -0,0 +1,3 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
json.partial! 'api/payment_schedules/payment_schedule', payment_schedule: @payment_schedule_item.payment_schedule
|
@ -642,6 +642,8 @@ en:
|
||||
stripe_currency: "Stripe currency"
|
||||
payment_schedules:
|
||||
filter_schedules: "Filter schedules"
|
||||
no_payment_schedules: "No payment schedules to display"
|
||||
load_more: "Load more"
|
||||
schedules_table:
|
||||
schedule_num: "Schedule #"
|
||||
date: "Date"
|
||||
@ -662,6 +664,9 @@ en:
|
||||
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"
|
||||
document_filters:
|
||||
reference: "Reference"
|
||||
customer: "Customer"
|
||||
|
@ -642,6 +642,8 @@ fr:
|
||||
stripe_currency: "Devise Stripe"
|
||||
payment_schedules:
|
||||
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"
|
||||
@ -662,6 +664,9 @@ fr:
|
||||
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"
|
||||
document_filters:
|
||||
reference: "Référence"
|
||||
customer: "Client"
|
||||
|
@ -114,6 +114,7 @@ Rails.application.routes.draw do
|
||||
resources :payment_schedules, only: %i[show] do
|
||||
post 'list', action: 'list', on: :collection
|
||||
get 'download', on: :member
|
||||
post 'items/:id/cash_check', action: 'cash_check', on: :collection
|
||||
end
|
||||
|
||||
resources :i_calendar, only: %i[index create destroy] do
|
||||
|
Loading…
x
Reference in New Issue
Block a user