Merge pull request #112 from bigcapitalhq/BIG-417-fcy-bcy-cashflow-accounts-transactions-api

Add FCY/BCY transactions to the account drawer.
This commit is contained in:
Ahmed Bouhuolia
2023-04-20 06:14:25 +02:00
committed by GitHub
8 changed files with 172 additions and 80 deletions

View File

@@ -177,7 +177,7 @@ export default class ItemsController extends BaseController {
/** /**
* Validate list query schema. * Validate list query schema.
*/ */
get validateListQuerySchema() { private get validateListQuerySchema() {
return [ return [
query('column_sort_by').optional().trim().escape(), query('column_sort_by').optional().trim().escape(),
query('sort_order').optional().isIn(['desc', 'asc']), query('sort_order').optional().isIn(['desc', 'asc']),
@@ -193,32 +193,20 @@ export default class ItemsController extends BaseController {
]; ];
} }
/**
* Validate autocomplete list query schema.
*/
get autocompleteQuerySchema() {
return [
query('column_sort_by').optional().trim().escape(),
query('sort_order').optional().isIn(['desc', 'asc']),
query('stringified_filter_roles').optional().isJSON(),
query('limit').optional().isNumeric().toInt(),
query('keyword').optional().isString().trim().escape(),
];
}
/** /**
* Stores the given item details to the storage. * Stores the given item details to the storage.
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
async newItem(req: Request, res: Response, next: NextFunction) { private async newItem(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId } = req;
const itemDTO: IItemDTO = this.matchedBodyData(req); const itemDTO: IItemDTO = this.matchedBodyData(req);
try { try {
const storedItem = await this.itemsApplication.createItem(tenantId, itemDTO); const storedItem = await this.itemsApplication.createItem(
tenantId,
itemDTO
);
return res.status(200).send({ return res.status(200).send({
id: storedItem.id, id: storedItem.id,
@@ -234,7 +222,7 @@ export default class ItemsController extends BaseController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
async editItem(req: Request, res: Response, next: NextFunction) { private async editItem(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId } = req;
const itemId: number = req.params.id; const itemId: number = req.params.id;
const item: IItemDTO = this.matchedBodyData(req); const item: IItemDTO = this.matchedBodyData(req);
@@ -257,7 +245,7 @@ export default class ItemsController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
*/ */
async activateItem(req: Request, res: Response, next: NextFunction) { private async activateItem(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId } = req;
const itemId: number = req.params.id; const itemId: number = req.params.id;
@@ -279,7 +267,11 @@ export default class ItemsController extends BaseController {
* @param {Response} res * @param {Response} res
* @param {NextFunction} next * @param {NextFunction} next
*/ */
async inactivateItem(req: Request, res: Response, next: NextFunction) { private async inactivateItem(
req: Request,
res: Response,
next: NextFunction
) {
const { tenantId } = req; const { tenantId } = req;
const itemId: number = req.params.id; const itemId: number = req.params.id;
@@ -300,7 +292,7 @@ export default class ItemsController extends BaseController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
async deleteItem(req: Request, res: Response, next: NextFunction) { private async deleteItem(req: Request, res: Response, next: NextFunction) {
const itemId: number = req.params.id; const itemId: number = req.params.id;
const { tenantId } = req; const { tenantId } = req;
@@ -322,7 +314,7 @@ export default class ItemsController extends BaseController {
* @param {Response} res * @param {Response} res
* @return {Response} * @return {Response}
*/ */
async getItem(req: Request, res: Response, next: NextFunction) { private async getItem(req: Request, res: Response, next: NextFunction) {
const itemId: number = req.params.id; const itemId: number = req.params.id;
const { tenantId } = req; const { tenantId } = req;
@@ -342,7 +334,7 @@ export default class ItemsController extends BaseController {
* @param {Request} req * @param {Request} req
* @param {Response} res * @param {Response} res
*/ */
async getItemsList(req: Request, res: Response, next: NextFunction) { private async getItemsList(req: Request, res: Response, next: NextFunction) {
const { tenantId } = req; const { tenantId } = req;
const filter = { const filter = {

View File

@@ -42,6 +42,7 @@ export enum AccountNormal {
export interface IAccountsTransactionsFilter { export interface IAccountsTransactionsFilter {
accountId?: number; accountId?: number;
limit?: number;
} }
export interface IAccountTransaction { export interface IAccountTransaction {

View File

@@ -106,7 +106,7 @@ export default class AccountTransactionTransformer extends Transformer {
* @returns {string} * @returns {string}
*/ */
protected formattedFcCredit(transaction: IAccountTransaction) { protected formattedFcCredit(transaction: IAccountTransaction) {
return this.formatMoney(this.fcDebit(transaction), { return this.formatMoney(this.fcCredit(transaction), {
currencyCode: transaction.currencyCode, currencyCode: transaction.currencyCode,
excerptZero: true, excerptZero: true,
}); });
@@ -117,7 +117,7 @@ export default class AccountTransactionTransformer extends Transformer {
* @returns {string} * @returns {string}
*/ */
protected formattedFcDebit(transaction: IAccountTransaction) { protected formattedFcDebit(transaction: IAccountTransaction) {
return this.formatMoney(this.fcCredit(transaction), { return this.formatMoney(this.fcDebit(transaction), {
currencyCode: transaction.currencyCode, currencyCode: transaction.currencyCode,
excerptZero: true, excerptZero: true,
}); });

View File

@@ -2,7 +2,7 @@
import React from 'react'; import React from 'react';
import withBreadcrumbs from 'react-router-breadcrumbs-hoc'; import withBreadcrumbs from 'react-router-breadcrumbs-hoc';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { getDashboardRoutes } from '@/routes/dashboard';
import { If, Icon } from '@/components'; import { If, Icon } from '@/components';
import { FormattedMessage as T } from '@/components'; import { FormattedMessage as T } from '@/components';
import withDashboard from '@/containers/Dashboard/withDashboard'; import withDashboard from '@/containers/Dashboard/withDashboard';

View File

@@ -4,23 +4,24 @@ import intl from 'react-intl-universal';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import styled from 'styled-components'; import styled from 'styled-components';
import { compose } from '@/utils';
import { TableStyle } from '@/constants'; import { TableStyle } from '@/constants';
import { Card, DataTable, If } from '@/components'; import { Card, DataTable, If } from '@/components';
import { AccountDrawerTableOptionsProvider } from './AccountDrawerTableOptionsProvider';
import { AccountDrawerTableHeader } from './AccountDrawerTableHeader';
import { useAccountReadEntriesColumns } from './utils'; import { useAccountReadEntriesColumns } from './utils';
import { useAppIntlContext } from '@/components/AppIntlProvider'; import { useAppIntlContext } from '@/components/AppIntlProvider';
import { useAccountDrawerContext } from './AccountDrawerProvider'; import { useAccountDrawerContext } from './AccountDrawerProvider';
import withDrawerActions from '@/containers/Drawer/withDrawerActions'; import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import { compose } from '@/utils';
/** /**
* account drawer table. * account drawer table.
*/ */
function AccountDrawerTable({ closeDrawer }) { function AccountDrawerTable({ closeDrawer }) {
const { account, accounts, drawerName } = useAccountDrawerContext(); const { accounts, drawerName } = useAccountDrawerContext();
// Account read-only entries table columns.
const columns = useAccountReadEntriesColumns();
// Handle view more link click. // Handle view more link click.
const handleLinkClick = () => { const handleLinkClick = () => {
@@ -31,27 +32,41 @@ function AccountDrawerTable({ closeDrawer }) {
return ( return (
<Card> <Card>
<DataTable <AccountDrawerTableOptionsProvider>
columns={columns} <AccountDrawerTableHeader />
data={accounts} <AccountDrawerDataTable />
payload={{ account }}
styleName={TableStyle.Constrant}
/>
<If condition={accounts.length > 0}> <If condition={accounts.length > 0}>
<TableFooter> <TableFooter>
<Link <Link
to={`/financial-reports/general-ledger`} to={`/financial-reports/general-ledger`}
onClick={handleLinkClick} onClick={handleLinkClick}
> >
{isRTL ? '→' : '←'} {intl.get('view_more_transactions')} {isRTL ? '→' : '←'} {intl.get('view_more_transactions')}
</Link> </Link>
</TableFooter> </TableFooter>
</If> </If>
</AccountDrawerTableOptionsProvider>
</Card> </Card>
); );
} }
function AccountDrawerDataTable() {
const { account, accounts } = useAccountDrawerContext();
// Account read-only entries table columns.
const columns = useAccountReadEntriesColumns();
return (
<DataTable
columns={columns}
data={accounts}
payload={{ account }}
styleName={TableStyle.Constrant}
/>
);
}
export default compose(withDrawerActions)(AccountDrawerTable); export default compose(withDrawerActions)(AccountDrawerTable);
const TableFooter = styled.div` const TableFooter = styled.div`

View File

@@ -0,0 +1,47 @@
import React from 'react';
import { Button, ButtonGroup } from '@blueprintjs/core';
import styled from 'styled-components';
import { useAccountDrawerTableOptionsContext } from './AccountDrawerTableOptionsProvider';
export function AccountDrawerTableHeader() {
const {
setBCYCurrencyType,
setFYCCurrencyType,
isBCYCurrencyType,
isFCYCurrencyType,
} = useAccountDrawerTableOptionsContext();
const handleBCYBtnClick = () => {
setBCYCurrencyType();
};
const handleFCYBtnClick = () => {
setFYCCurrencyType();
};
return (
<TableHeaderRoot>
<ButtonGroup>
<Button
small
outlined
onClick={handleFCYBtnClick}
active={isFCYCurrencyType}
>
FCY
</Button>
<Button
small
outlined
onClick={handleBCYBtnClick}
active={isBCYCurrencyType}
>
BCY
</Button>
</ButtonGroup>
</TableHeaderRoot>
);
}
const TableHeaderRoot = styled.div`
margin-bottom: 1rem;
`;

View File

@@ -0,0 +1,57 @@
import React, { useState, useCallback } from 'react';
interface AccountDrawerTableOptionsContextValue {
setFYCCurrencyType: () => void;
setBCYCurrencyType: () => void;
isFCYCurrencyType: boolean;
isBCYCurrencyType: boolean;
currencyType: ForeignCurrencyType;
}
const AccountDrawerTableOptionsContext = React.createContext(
{} as AccountDrawerTableOptionsContextValue,
);
enum ForeignCurrencyTypes {
FCY = 'FCY',
BCY = 'BCY',
}
type ForeignCurrencyType = ForeignCurrencyTypes.FCY | ForeignCurrencyTypes.BCY;
function AccountDrawerTableOptionsProvider({
initialCurrencyType = ForeignCurrencyTypes.FCY,
...props
}) {
const [currencyType, setCurrentType] =
useState<ForeignCurrencyType>(initialCurrencyType);
const setFYCCurrencyType = useCallback(
() => setCurrentType(ForeignCurrencyTypes.FCY),
[setCurrentType],
);
const setBCYCurrencyType = useCallback(
() => setCurrentType(ForeignCurrencyTypes.BCY),
[setCurrentType],
);
// Provider.
const provider = {
setFYCCurrencyType,
setBCYCurrencyType,
isFCYCurrencyType: currencyType === ForeignCurrencyTypes.FCY,
isBCYCurrencyType: currencyType === ForeignCurrencyTypes.BCY,
currencyType,
};
return (
<AccountDrawerTableOptionsContext.Provider value={provider} {...props} />
);
}
const useAccountDrawerTableOptionsContext = () =>
React.useContext(AccountDrawerTableOptionsContext);
export {
AccountDrawerTableOptionsProvider,
useAccountDrawerTableOptionsContext,
};

View File

@@ -3,27 +3,15 @@ import intl from 'react-intl-universal';
import React from 'react'; import React from 'react';
import { FormatDateCell } from '@/components'; import { FormatDateCell } from '@/components';
import { isBlank } from '@/utils'; import { useAccountDrawerTableOptionsContext } from './AccountDrawerTableOptionsProvider';
/**
* Debit/credit table cell.
*/
function DebitCreditTableCell({ value, payload: { account } }) {
return !isBlank(value) && value !== 0 ? account.formatted_amount : null;
}
/**
* Running balance table cell.
*/
function RunningBalanceTableCell({ value, payload: { account } }) {
return account.formatted_amount;
}
/** /**
* Retrieve entries columns of read-only account view. * Retrieve entries columns of read-only account view.
*/ */
export const useAccountReadEntriesColumns = () => export const useAccountReadEntriesColumns = () => {
React.useMemo( const { isFCYCurrencyType } = useAccountDrawerTableOptionsContext();
return React.useMemo(
() => [ () => [
{ {
Header: intl.get('transaction_date'), Header: intl.get('transaction_date'),
@@ -34,14 +22,15 @@ export const useAccountReadEntriesColumns = () =>
}, },
{ {
Header: intl.get('transaction_type'), Header: intl.get('transaction_type'),
accessor: 'reference_type_formatted', accessor: 'transaction_type_formatted',
width: 100, width: 100,
textOverview: true, textOverview: true,
}, },
{ {
Header: intl.get('credit'), Header: intl.get('credit'),
accessor: 'credit', accessor: isFCYCurrencyType
Cell: DebitCreditTableCell, ? 'formatted_fc_credit'
: 'formatted_credit',
width: 80, width: 80,
className: 'credit', className: 'credit',
align: 'right', align: 'right',
@@ -49,22 +38,13 @@ export const useAccountReadEntriesColumns = () =>
}, },
{ {
Header: intl.get('debit'), Header: intl.get('debit'),
accessor: 'debit', accessor: isFCYCurrencyType ? 'formatted_fc_debit' : 'formatted_debit',
Cell: DebitCreditTableCell,
width: 80, width: 80,
className: 'debit', className: 'debit',
align: 'right', align: 'right',
textOverview: true, textOverview: true,
}, },
{
Header: intl.get('running_balance'),
Cell: RunningBalanceTableCell,
accessor: 'running_balance',
width: 110,
className: 'running_balance',
align: 'right',
textOverview: true,
},
], ],
[], [isFCYCurrencyType],
); );
};