Skip to content

Commit

Permalink
UINV-522: display the exchanged calculated total amount when invoice …
Browse files Browse the repository at this point in the history
…in foreign currency (#757)

* UINV-522: display exchanged amount

* tests: fix failing snapshot tests

* add changelog

* debounce api request and rename component name

* tests: update failing snapshots

* disable usecallback linter warning

* tests: remove failing snapshot tests

* tests: add missing snapshots

* rename function name

* update `yarn test` command to update snapshot tests

* remove `--updateSnapshot` flag after fixing snapshot test issue

* regenerate snapshot tests

* refactor exchange calculation hook

* refactor `useExchangeCalculation` hook

* tests: update failing snapshot tests

* inline `useExchangeRateValue` hook arguments in one line

* fix sonar issue `Remove this unused import of 'useStripes'.`
  • Loading branch information
alisher-epam authored and NikitaSedyx committed Feb 21, 2024
1 parent 8e38736 commit 31a3fe9
Show file tree
Hide file tree
Showing 22 changed files with 377 additions and 410 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* *BREAKING:* Bump up okapi interfaces for `pieces` (3.0). Refs UINV-529.
* Invoice line Subscription fields are not populated correctly on Invoice line pane. Refs UINV-534.
* Include the currency in an invoice upon creation derived from a purchase order. Refs UINV-530
* Display the exchanged calculated total amount when invoice in foreign currency. Refs UINV-522.

## [5.0.1](https://github.com/folio-org/ui-invoice/tree/v5.0.1) (2023-10-31)
[Full Changelog](https://github.com/folio-org/ui-invoice/compare/v5.0.0...v5.0.1)
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@
"@folio/plugin-find-organization": "^5.0.0",
"@folio/plugin-find-po-line": "^5.0.0"
},
"resolutions": {
"@folio/stripes-acq-components": "https://github.com/folio-org/stripes-acq-components.git#develop"
},
"stripes": {
"actsAs": [
"app",
Expand Down Expand Up @@ -157,6 +160,8 @@
"batch-voucher.export-configurations.collection.get",
"configuration.entries.collection.get",
"finance.budgets.collection.get",
"finance.calculate-exchange.item.get",
"finance.fiscal-years.collection.get",
"finance.fiscal-years.item.get",
"finance.funds.collection.get",
"finance.funds.expense-classes.collection.get",
Expand All @@ -178,8 +183,7 @@
"users.item.get",
"voucher.voucher-lines.collection.get",
"voucher.vouchers.collection.get",
"voucher.vouchers.item.get",
"finance.fiscal-years.collection.get"
"voucher.vouchers.item.get"
]
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';

import { AmountWithCurrencyField } from '@folio/stripes-acq-components';
import { KeyValue } from '@folio/stripes/components';
import { useStripes } from '@folio/stripes/core';

import { useExchangeCalculation } from '../../hooks';

export const CalculatedExchangeAmount = ({ currency, exchangeRate, total }) => {
const stripes = useStripes();
const systemCurrency = stripes.currency;
const enabled = Boolean(systemCurrency !== currency && total);

const { exchangedAmount } = useExchangeCalculation({
amount: +total,
from: currency,
rate: +exchangeRate,
to: systemCurrency,
}, { enabled });

if (!enabled) {
return null;
}

return (
<KeyValue label={<FormattedMessage id="ui-invoice.invoice.details.information.calculatedTotalExchangeAmount" />}>
<AmountWithCurrencyField
amount={exchangedAmount || total}
currency={systemCurrency}
/>
</KeyValue>
);
};

CalculatedExchangeAmount.propTypes = {
currency: PropTypes.string,
exchangeRate: PropTypes.number,
total: PropTypes.number,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { render, screen } from '@folio/jest-config-stripes/testing-library/react';

import { useExchangeCalculation } from '../../hooks';
import { CalculatedExchangeAmount } from './CalculatedExchangeAmount';

jest.mock('@folio/stripes-acq-components', () => ({
...jest.requireActual('@folio/stripes-acq-components'),
AmountWithCurrencyField: jest.fn(() => 'AmountWithCurrencyField'),
}));
jest.mock('../../hooks', () => ({
...jest.requireActual('../../hooks'),
useExchangeCalculation: jest.fn(),
}));

const renderComponent = (props = {}) => render(
<CalculatedExchangeAmount {...props} />,
);

describe('CalculatedExchangeAmount', () => {
beforeEach(() => {
useExchangeCalculation.mockClear().mockReturnValue({
isLoading: false,
exchangedAmount: 30,
});
});

it('should not render component', async () => {
renderComponent({
currency: 'USD',
exchangeRate: 1,
total: 30,
});

expect(screen.queryByText(/30/)).not.toBeInTheDocument();
});

it('should render calculated exchange amount', async () => {
renderComponent({
currency: 'EUR',
exchangeRate: 1,
total: 30,
});

expect(screen.getByText('ui-invoice.invoice.details.information.calculatedTotalExchangeAmount')).toBeInTheDocument();
});
});
1 change: 1 addition & 0 deletions src/common/components/CalculatedExchangeAmount/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { CalculatedExchangeAmount } from './CalculatedExchangeAmount';
1 change: 1 addition & 0 deletions src/common/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export {
CredentialsFieldsGroup,
CredentialsToggle,
} from './credentials';
export { CalculatedExchangeAmount } from './CalculatedExchangeAmount';
export { default as DuplicateInvoiceList } from './DuplicateInvoiceList';
export * from './FieldFiscalYear';
export * from './FiscalYearValue';
Expand Down
1 change: 1 addition & 0 deletions src/common/constants/api.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const BATCH_GROUPS_API = 'batch-groups';
export const BATCH_VOUCHERS_API = 'batch-voucher/batch-vouchers';
export const BATCH_VOUCHER_EXPORTS_API = 'batch-voucher/batch-voucher-exports';
export const CALCULATE_EXCHANGE_API = 'finance/calculate-exchange';
export const CATEGORIES_API = 'organizations-storage/categories';
export const EXPORT_CONFIGURATIONS_API = 'batch-voucher/export-configurations';
export const FISCAL_YEARS_API = 'finance/fiscal-years';
Expand Down
1 change: 1 addition & 0 deletions src/common/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './useConfigsAdjustments';
export * from './useFiscalYear';
export * from './useFiscalYears';
export * from './useFundDistributionValidation';
export * from './useExchangeCalculation';
export * from './useInvoice';
export * from './useInvoiceMutation';
export * from './useInvoiceLine';
Expand Down
1 change: 1 addition & 0 deletions src/common/hooks/useExchangeCalculation/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useExchangeCalculation } from './useExchangeCalculation';
71 changes: 71 additions & 0 deletions src/common/hooks/useExchangeCalculation/useExchangeCalculation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { debounce } from 'lodash';
import {
useCallback,
useEffect,
useState,
} from 'react';
import { useQuery } from 'react-query';

import { useExchangeRateValue } from '@folio/stripes-acq-components';
import {
useOkapiKy,
useNamespace,
} from '@folio/stripes/core';

import { CALCULATE_EXCHANGE_API } from '../../constants';

const DEBOUNCE_DELAY = 500;

export const useExchangeCalculation = ({ from, to, amount, rate }, options = {}) => {
const { enabled = true, ...otherOptions } = options;
const ky = useOkapiKy();
const [namespace] = useNamespace({ key: 'exchange-calculation' });

const { exchangeRate } = useExchangeRateValue(from, to, rate);

const [searchParams, setSearchParams] = useState({
amount,
from,
rate: rate || exchangeRate,
to,
});

// eslint-disable-next-line react-hooks/exhaustive-deps
const debounceSetSearchParams = useCallback(debounce(() => {
setSearchParams({
amount,
from,
rate: rate || exchangeRate,
to,
});
}, DEBOUNCE_DELAY), [amount, from, rate, to, exchangeRate]);

useEffect(() => {
debounceSetSearchParams();

return () => debounceSetSearchParams.cancel();
}, [amount, debounceSetSearchParams, from, rate, to, exchangeRate]);

const {
amount: amountProp,
from: fromProp,
rate: rateProp,
to: toProp,
} = searchParams;

const { data, isLoading, isFetching } = useQuery(
[namespace, amountProp, fromProp, rateProp, toProp],
({ signal }) => ky.get(`${CALCULATE_EXCHANGE_API}`, { searchParams, signal }).json(),
{
keepPreviousData: true,
...otherOptions,
enabled: enabled && Boolean(amountProp && fromProp && rateProp && toProp),
},
);

return ({
exchangedAmount: data,
isFetching,
isLoading,
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
QueryClient,
QueryClientProvider,
} from 'react-query';

import { renderHook, waitFor } from '@folio/jest-config-stripes/testing-library/react';
import { useOkapiKy } from '@folio/stripes/core';

import { CALCULATE_EXCHANGE_API } from '../../constants';
import { useExchangeCalculation } from './useExchangeCalculation';

const queryClient = new QueryClient();

// eslint-disable-next-line react/prop-types
const wrapper = ({ children }) => (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);

const kyMock = {
get: jest.fn(() => ({
json: () => Promise.resolve(30),
})),
};

const searchParams = {
from: 'USD',
to: 'EUR',
amount: 100,
rate: 1.2,
};

describe('useExchangeCalculation', () => {
beforeEach(() => {
kyMock.get.mockClear();
useOkapiKy.mockClear().mockReturnValue(kyMock);
});

it('should return calculated exchange amount', async () => {
const { result } = renderHook(() => useExchangeCalculation(searchParams), { wrapper });

await waitFor(() => expect(result.current.isLoading).toBeFalsy());

expect(kyMock.get).toHaveBeenCalledWith(
`${CALCULATE_EXCHANGE_API}`,
expect.objectContaining({
searchParams,
}),
);
expect(result.current.exchangedAmount).toEqual(30);
});
});
10 changes: 10 additions & 0 deletions src/invoices/InvoiceDetails/Information/Information.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {

import {
ApprovedBy,
CalculatedExchangeAmount,
FiscalYearValueContainer as FiscalYearValue,
StatusValue,
} from '../../../common/components';
Expand All @@ -30,6 +31,7 @@ const Information = ({
approvalDate,
approvedBy,
batchGroupId,
exchangeRate,
fiscalYearId,
invoiceDate,
paymentDue,
Expand Down Expand Up @@ -182,6 +184,13 @@ const Information = ({
</Row>

<Row>
<Col xs={3}>
<CalculatedExchangeAmount
currency={currency}
exchangeRate={exchangeRate}
total={total}
/>
</Col>
{isLockTotal && (
<Col xs={3} data-testid="lock-total-amount">
<KeyValue label={<FormattedMessage id="ui-invoice.invoice.lockTotalAmount" />}>
Expand All @@ -202,6 +211,7 @@ Information.propTypes = {
approvalDate: PropTypes.string,
approvedBy: PropTypes.string,
batchGroupId: PropTypes.string.isRequired,
exchangeRate: PropTypes.number,
invoiceDate: PropTypes.string.isRequired,
fiscalYearId: PropTypes.string,
paymentDue: PropTypes.string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Information from './Information';
jest.mock('../../../common/hooks', () => ({
...jest.requireActual('../../../common/hooks'),
useFiscalYear: jest.fn(() => ({ fiscalYear: { code: 'FY2023' } })),
useExchangeCalculation: jest.fn(() => ({ isLoading: false, exchangedAmount: 30 })),
}));
jest.mock('../BatchGroupValue', () => {
return () => <span>BatchGroupValue</span>;
Expand Down
1 change: 1 addition & 0 deletions src/invoices/InvoiceDetails/InvoiceDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ function InvoiceDetails({
approvalDate={invoice.approvalDate}
approvedBy={invoice.approvedBy}
batchGroupId={invoice.batchGroupId}
exchangeRate={invoice.exchangeRate}
fiscalYearId={invoice.fiscalYearId}
invoiceDate={invoice.invoiceDate}
paymentDate={invoice.paymentDate}
Expand Down
Loading

0 comments on commit 31a3fe9

Please sign in to comment.